forked from mirrors/homebox
Compare commits
392 commits
fix/datepi
...
main
Author | SHA1 | Date | |
---|---|---|---|
6e1e5a7280 | |||
b5fbe698be | |||
14466cde2e | |||
90e495a748 | |||
e32ca9442d | |||
9895088668 | |||
|
6fd8457e5a | ||
|
c2511d56d7 | ||
|
fbb17d66aa | ||
|
98429ae8c2 | ||
|
69adbacd25 | ||
|
43ff04317a | ||
|
361c2121d8 | ||
|
0041c277ad | ||
|
f621d3ad5d | ||
|
af9aa239af | ||
|
f8a2c70f2c | ||
|
89b68da86e | ||
|
03e9596b7a | ||
|
90535c8691 | ||
|
b7fc0e903c | ||
|
eb420d50b1 | ||
|
8f3a081670 | ||
|
2867a05c92 | ||
|
77b4d594af | ||
|
8f4fed1df9 | ||
|
b65da05d11 | ||
|
c2c8441625 | ||
|
a1eb6e314c | ||
|
b0c9dcdeee | ||
|
0a609adb0b | ||
|
19ee6c2687 | ||
|
b77c9be36f | ||
|
be2910e0df | ||
|
749ee367d4 | ||
|
c36ce7ac2d | ||
|
d0128c7bd7 | ||
|
f91b33db38 | ||
|
cf166ac641 | ||
|
ae86e09970 | ||
|
a7e0d2d127 | ||
|
9866fdddb8 | ||
|
ff9759ab20 | ||
|
b655cfab28 | ||
|
4c9ddac395 | ||
|
c708b1759e | ||
|
3ed50f5a1b | ||
|
e6d77b3f72 | ||
|
747815982e | ||
|
7e23a75908 | ||
|
4c8a97fe0d | ||
|
87b07ab47d | ||
|
cf8000d8ee | ||
|
31b10194f1 | ||
|
3d748021dc | ||
|
0f8e1fee35 | ||
|
8d18b06012 | ||
|
6a32762c98 | ||
|
2311eda44b | ||
|
5b52aa8abf | ||
|
aace77ec40 | ||
|
c55d421326 | ||
|
5ca8e0c35d | ||
|
aba853d598 | ||
|
2c8fd163ee | ||
|
d9497bd69e | ||
|
893f3f6df6 | ||
|
ca55e5ba94 | ||
|
7753213657 | ||
|
b3f7b59243 | ||
|
18a9b21a87 | ||
|
853b473668 | ||
|
40905bc100 | ||
|
8be9df2b8d | ||
|
bd9eb69313 | ||
|
fa31bb2448 | ||
|
fcd33e59d9 | ||
|
84aee20208 | ||
|
2b79788fbe | ||
|
ce923a5b4c | ||
|
e83ff89c9d | ||
|
c71c4f3b16 | ||
|
33661587fd | ||
|
27b72603e1 | ||
|
a4d91adc1e | ||
|
03df23d97c | ||
|
5e83b28ff5 | ||
|
d759fad40c | ||
|
1cc9bf459a | ||
|
0c535aa8d8 | ||
|
3eb4c21263 | ||
|
19c53dce2f | ||
|
3967bc86d0 | ||
|
2b6e42b11f | ||
|
d48de6b9f6 | ||
|
477a5fce97 | ||
|
65efe7f575 | ||
|
1b77b15db4 | ||
|
fc3bc3f010 | ||
|
81e76d9dd4 | ||
|
bd1a241be1 | ||
|
bc4c0c0b89 | ||
|
c7936fc478 | ||
|
09ee4fef3a | ||
|
d6b0062ae9 | ||
|
df26a3eeb7 | ||
|
798d873f76 | ||
|
a26ea3bac5 | ||
|
812b464bc7 | ||
|
e1f34c2507 | ||
|
79be938531 | ||
|
e3ddc68eb2 | ||
|
eb9175ab27 | ||
|
54b7b2c35f | ||
|
ab98870350 | ||
|
13c437c418 | ||
|
ba2e66a014 | ||
|
1adf24e109 | ||
|
742ece7923 | ||
|
f74736b369 | ||
|
3a3be89b90 | ||
|
4082092560 | ||
|
31839eb444 | ||
|
6e203e7833 | ||
|
0ef9d0deb8 | ||
|
c71f077466 | ||
|
36e13ab03b | ||
|
c0f19cdf05 | ||
|
b20c88e256 | ||
|
ce16b37b97 | ||
|
6ed1893bed | ||
|
bd9ea571aa | ||
|
10d9c4fbcc | ||
|
eff5db3664 | ||
|
f8a6160039 | ||
|
4375ff8bd6 | ||
|
9bee6e9863 | ||
|
b42d20b6a6 | ||
|
ba1e3a905b | ||
|
973d5ca97d | ||
|
57baf22534 | ||
|
c615707cbc | ||
|
992eea3278 | ||
|
03288b52ee | ||
|
a274bbcf64 | ||
|
64e9889d41 | ||
|
58e80ab3e0 | ||
|
94e81d14fa | ||
|
0b021e898f | ||
|
be8d6e8235 | ||
|
d3dcb599ca | ||
|
0cbe516ca3 | ||
|
26911e9530 | ||
|
5524dfa83d | ||
|
21d54eb2ac | ||
|
4152199f3b | ||
|
1d40251f29 | ||
|
97e137c411 | ||
|
0de6c2338d | ||
|
a2479155b2 | ||
|
e2dace75f4 | ||
|
ef9a7cd811 | ||
|
964270e054 | ||
|
6b9cdf3294 | ||
|
6fd228f1f4 | ||
|
0eb0b283b2 | ||
|
4b071bccda | ||
|
65a7947932 | ||
|
a3e607a887 | ||
|
feab9f4c46 | ||
|
fe5622d62a | ||
|
e759f2817e | ||
|
60cc5c2710 | ||
|
25ccd678c9 | ||
|
a77b4cbe71 | ||
|
aae32b0d74 | ||
|
a94b43a19e | ||
|
9a4c2df552 | ||
|
d5b89a755e | ||
|
a9acf62d93 | ||
|
c896e198dd | ||
|
c538518b4b | ||
|
f66d14eeea | ||
|
bc8feac83c | ||
|
40a98bcf30 | ||
|
045e91d9ac | ||
|
a80ab0f3e9 | ||
|
e5d209d407 | ||
|
ef1531e561 | ||
|
4dd036abb2 | ||
|
81e909ccfb | ||
|
0cb9d2a8e4 | ||
|
cb16c0e829 | ||
|
9e067ee230 | ||
|
8b53d40a2a | ||
|
4c0ad7a5d8 | ||
|
66e25ba068 | ||
|
56c98e6e3a | ||
|
01f305a98e | ||
|
e14cdaccdd | ||
|
4ece25b58d | ||
|
c1957bb927 | ||
|
636ca155e5 | ||
|
17a5b43609 | ||
|
b2b3ccf923 | ||
|
2272c7eb6b | ||
|
181c324dd4 | ||
|
89912b18d7 | ||
|
85f2af4bc3 | ||
|
21ad5a32c1 | ||
|
4b51a4ad9a | ||
|
dd7e634b69 | ||
|
351ec64bbc | ||
|
3a758e012f | ||
|
c16084d99f | ||
|
5591267124 | ||
|
d46c16f01f | ||
|
18c22e8a68 | ||
|
64b3ac3e94 | ||
|
c36b9dcf5d | ||
|
d3b6c93b63 | ||
|
3b862e36c8 | ||
|
f3bb86d905 | ||
|
4dd925caf0 | ||
|
ced5aef6d1 | ||
|
6a853c07a0 | ||
|
f0b9a0fce4 | ||
|
00f09fec2f | ||
|
6e1863b515 | ||
|
dfe2084c74 | ||
|
0825f055a7 | ||
|
e1e04d49aa | ||
|
9020587c9e | ||
|
bd0db1ea37 | ||
|
d2985ff72c | ||
|
8c57ff841e | ||
|
0264bfb8c1 | ||
|
5dd6844536 | ||
|
0f8db862b4 | ||
|
be6b5c9c56 | ||
|
faed343eda | ||
|
ed1230e17d | ||
|
2d768e2b9c | ||
|
40e76bac0c | ||
|
840d220d4f | ||
|
975e636fb6 | ||
|
d1076baf84 | ||
|
40fcef4e9b | ||
|
97fb94d231 | ||
|
4a8ba6231d | ||
|
db80f8a159 | ||
|
184b494fc3 | ||
|
5a3fa23332 | ||
|
ef0690d511 | ||
|
9e55c880f6 | ||
|
cb9b20e2d2 | ||
|
a79e780b4e | ||
|
90cbb9bfd1 | ||
|
dc08dbbd7a | ||
|
1f47d96e4c | ||
|
23b5892aef | ||
|
2665b666f1 | ||
|
14315cb88a | ||
|
cf536393f5 | ||
|
025521431e | ||
|
70297b9d27 | ||
|
729293745f | ||
|
a6bcb36c5b | ||
|
a005fa5b9b | ||
|
a2dfa9dcef | ||
|
32216f63bd | ||
|
ef190e26df | ||
|
c3e3702a7e | ||
|
fb57120067 | ||
|
9d9b05d8a6 | ||
|
3ac6c7c858 | ||
|
859d3b9ffe | ||
|
6cfa6c9fc8 | ||
|
12975ce26e | ||
|
bd321af29f | ||
|
88f9ff90d4 | ||
|
354f1adbee | ||
|
2a62a43493 | ||
|
673db41f37 | ||
|
830ce2b0a9 | ||
|
e8e6a425dd | ||
|
da00db0608 | ||
|
efd7069fe4 | ||
|
dd349aa98e | ||
|
607b06d2f2 | ||
|
44f13f751a | ||
|
986d2c586e | ||
|
9361997a42 | ||
|
2e96d8c4c2 | ||
|
ff75daf6b3 | ||
|
c0953bbd26 | ||
|
1f7e917c70 | ||
|
6ff2d64996 | ||
|
ab22ea6a25 | ||
|
ce2fc7712a | ||
|
bd933af874 | ||
|
f36f17b57d | ||
|
504569bed0 | ||
|
bd06fdafaf | ||
|
7b28973c60 | ||
|
cbac17c059 | ||
|
6ed1f3695a | ||
|
3d295b5132 | ||
|
4d220cdd9c | ||
|
ad20e4e39b | ||
|
3e2f6a96bf | ||
|
2ece97c42a | ||
|
a8ee4d421b | ||
|
91d0c588d9 | ||
|
c19fe94c08 | ||
|
f532b39c46 | ||
|
243742a75c | ||
|
2b7c1fa429 | ||
|
125b8f5ecd | ||
|
580de45cec | ||
|
da52b4ec64 | ||
|
5b0dba854d | ||
|
984c1a08ac | ||
|
8419a17109 | ||
|
f3406eec19 | ||
|
3ed07a909f | ||
|
07441eec8e | ||
|
c78f10ba5d | ||
|
490a0ece86 | ||
|
891d41b75f | ||
|
58d6f9a28c | ||
|
6a8a25e3f8 | ||
|
a3954dab0f | ||
|
08d483d907 | ||
|
a53f89ceac | ||
|
5bbb969763 | ||
|
d6da63187b | ||
|
de419dc37d | ||
|
974d6914a2 | ||
|
d2aa022347 | ||
|
6af048dc93 | ||
|
73c42f4784 | ||
|
f42a917390 | ||
|
f149c3e4ab | ||
|
1dc1ee54e2 | ||
|
e8f215ce34 | ||
|
1aafbcd201 | ||
|
6dc2ae1bea | ||
|
976f68252d | ||
|
8e1947d971 | ||
|
fbcbde836a | ||
|
b7aacb3cde | ||
|
b6b2a2d889 | ||
|
592d4eda55 | ||
|
2fb5a437a2 | ||
|
7e0f1fac23 | ||
|
a886fa86ca | ||
|
c722495fdd | ||
|
4a9d21d604 | ||
|
cd82fe0d89 | ||
|
6529549289 | ||
|
e2d93f8523 | ||
|
82269e8a95 | ||
|
4aee60c242 | ||
|
d151d42081 | ||
|
a4b4fe3454 | ||
|
fe6cd431a6 | ||
|
1f0c8d09c2 | ||
|
2d34557f69 | ||
|
97a34475c8 | ||
|
18488f5b15 | ||
|
434f1fa411 | ||
|
57f9372e49 | ||
|
dbaaf4ad0a | ||
|
5596740cd2 | ||
|
bb86a51b05 | ||
|
72bdf524c2 | ||
|
461be2afca | ||
|
1cc38d6a5c | ||
|
50bd2ab86e | ||
|
5adb8fbad7 | ||
|
dea2dcfde8 | ||
|
ba8367f637 | ||
|
b87cdc8164 | ||
|
0f51e51f63 | ||
|
1279028d07 | ||
|
84bf67079b | ||
|
889197994b | ||
|
ae73b194c4 | ||
|
30014a77ca | ||
|
1b20a69c5e | ||
|
a8e1d2c447 |
495 changed files with 64793 additions and 28373 deletions
5
.devcontainer/Dockerfile
Normal file
5
.devcontainer/Dockerfile
Normal file
|
@ -0,0 +1,5 @@
|
|||
# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18, 16, 14, 18-bullseye, 16-bullseye, 14-bullseye, 18-buster, 16-buster, 14-buster
|
||||
ARG VARIANT=16-bullseye
|
||||
FROM mcr.microsoft.com/vscode/devcontainers/typescript-node:0-${VARIANT}
|
||||
|
||||
RUN sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b ~/.local/bin
|
40
.devcontainer/devcontainer.json
Normal file
40
.devcontainer/devcontainer.json
Normal file
|
@ -0,0 +1,40 @@
|
|||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
|
||||
// https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/typescript-node
|
||||
{
|
||||
"name": "Node.js & TypeScript",
|
||||
"build": {
|
||||
"dockerfile": "Dockerfile",
|
||||
// Update 'VARIANT' to pick a Node version: 18, 16, 14.
|
||||
// Append -bullseye or -buster to pin to an OS version.
|
||||
// Use -bullseye variants on local on arm64/Apple Silicon.
|
||||
"args": {
|
||||
"VARIANT": "18-bullseye"
|
||||
}
|
||||
},
|
||||
|
||||
// Configure tool-specific properties.
|
||||
"customizations": {
|
||||
// Configure properties specific to VS Code.
|
||||
"vscode": {
|
||||
// Add the IDs of extensions you want installed when the container is created.
|
||||
"extensions": [
|
||||
"dbaeumer.vscode-eslint"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
"forwardPorts": [
|
||||
7745,
|
||||
3000
|
||||
],
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
"postCreateCommand": "go install github.com/go-task/task/v3/cmd/task@latest && npm install -g pnpm && task setup",
|
||||
|
||||
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
||||
"remoteUser": "node",
|
||||
"features": {
|
||||
"golang": "1.21"
|
||||
}
|
||||
}
|
|
@ -22,3 +22,4 @@
|
|||
**/secrets.dev.yaml
|
||||
**/values.dev.yaml
|
||||
README.md
|
||||
!Dockerfile.rootless
|
||||
|
|
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
github: [hay-kot]
|
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
@ -1,6 +1,7 @@
|
|||
---
|
||||
name: "Bug Report"
|
||||
description: "submit a bug report for the current release"
|
||||
description: "Submit a bug report for the current release"
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: checks
|
||||
|
|
3
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
3
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
|
@ -1,6 +1,7 @@
|
|||
---
|
||||
name: "Feature Request"
|
||||
description: "submit a feature request for the current release"
|
||||
description: "Submit a feature request for the current release"
|
||||
labels: ["feature-request"]
|
||||
body:
|
||||
- type: textarea
|
||||
id: problem-statement
|
||||
|
|
31
.github/dependabot.yml
vendored
31
.github/dependabot.yml
vendored
|
@ -1,31 +0,0 @@
|
|||
version: 2
|
||||
updates:
|
||||
# Fetch and update latest `npm` packages
|
||||
- package-ecosystem: npm
|
||||
directory: "/frontend"
|
||||
schedule:
|
||||
interval: daily
|
||||
time: "00:00"
|
||||
open-pull-requests-limit: 10
|
||||
reviewers:
|
||||
- hay-kot
|
||||
assignees:
|
||||
- hay-kot
|
||||
commit-message:
|
||||
prefix: fix
|
||||
prefix-development: chore
|
||||
include: scope
|
||||
- package-ecosystem: gomod
|
||||
directory: backend
|
||||
schedule:
|
||||
interval: daily
|
||||
time: "00:00"
|
||||
open-pull-requests-limit: 10
|
||||
reviewers:
|
||||
- hay-kot
|
||||
assignees:
|
||||
- hay-kot
|
||||
commit-message:
|
||||
prefix: fix
|
||||
prefix-development: chore
|
||||
include: scope
|
14
.github/workflows/partial-backend.yaml
vendored
14
.github/workflows/partial-backend.yaml
vendored
|
@ -7,18 +7,20 @@ jobs:
|
|||
Go:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.19
|
||||
go-version: "1.21"
|
||||
|
||||
- name: Install Task
|
||||
uses: arduino/setup-task@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
uses: golangci/golangci-lint-action@v4
|
||||
with:
|
||||
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
|
||||
version: latest
|
||||
|
@ -28,7 +30,7 @@ jobs:
|
|||
args: --timeout=6m
|
||||
|
||||
- name: Build API
|
||||
run: task api:build
|
||||
run: task go:build
|
||||
|
||||
- name: Test
|
||||
run: task api:coverage
|
||||
run: task go:coverage
|
||||
|
|
44
.github/workflows/partial-frontend.yaml
vendored
44
.github/workflows/partial-frontend.yaml
vendored
|
@ -4,27 +4,55 @@ on:
|
|||
workflow_call:
|
||||
|
||||
jobs:
|
||||
Frontend:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: pnpm/action-setup@v3.0.0
|
||||
with:
|
||||
version: 6.0.2
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --shamefully-hoist
|
||||
working-directory: frontend
|
||||
|
||||
- name: Run Lint
|
||||
run: pnpm run lint:ci
|
||||
working-directory: frontend
|
||||
|
||||
- name: Run Typecheck
|
||||
run: pnpm run typecheck
|
||||
working-directory: frontend
|
||||
|
||||
integration-tests:
|
||||
name: Integration Tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Task
|
||||
uses: arduino/setup-task@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.19
|
||||
go-version: "1.21"
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- uses: pnpm/action-setup@v2.2.2
|
||||
- uses: pnpm/action-setup@v3.0.0
|
||||
with:
|
||||
version: 6.0.2
|
||||
|
||||
|
@ -32,9 +60,5 @@ jobs:
|
|||
run: pnpm install
|
||||
working-directory: frontend
|
||||
|
||||
- name: Run linter 👀
|
||||
run: pnpm lint
|
||||
working-directory: "frontend"
|
||||
|
||||
- name: Run Integration Tests
|
||||
run: task test:ci
|
||||
|
|
36
.github/workflows/partial-publish.yaml
vendored
36
.github/workflows/partial-publish.yaml
vendored
|
@ -20,22 +20,22 @@ jobs:
|
|||
name: "Publish Homebox"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.19
|
||||
go-version: "1.20"
|
||||
|
||||
- name: Set up QEMU
|
||||
id: qemu
|
||||
uses: docker/setup-qemu-action@v1
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
image: tonistiigi/binfmt:latest
|
||||
platforms: all
|
||||
|
||||
- name: install buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
install: true
|
||||
|
||||
|
@ -44,7 +44,7 @@ jobs:
|
|||
env:
|
||||
CR_PAT: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
- name: build nightly the image
|
||||
- name: build nightly image
|
||||
if: ${{ inputs.release == false }}
|
||||
run: |
|
||||
docker build --push --no-cache \
|
||||
|
@ -53,6 +53,16 @@ jobs:
|
|||
--build-arg=BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
|
||||
--platform=linux/amd64,linux/arm64,linux/arm/v7 .
|
||||
|
||||
- name: build nightly-rootless image
|
||||
if: ${{ inputs.release == false }}
|
||||
run: |
|
||||
docker build --push --no-cache \
|
||||
--tag=ghcr.io/hay-kot/homebox:${{ inputs.tag }}-rootless \
|
||||
--build-arg=COMMIT=$(git rev-parse HEAD) \
|
||||
--build-arg=BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
|
||||
--file Dockerfile.rootless \
|
||||
--platform=linux/amd64,linux/arm64,linux/arm/v7 .
|
||||
|
||||
- name: build release tagged the image
|
||||
if: ${{ inputs.release == true }}
|
||||
run: |
|
||||
|
@ -60,6 +70,20 @@ jobs:
|
|||
--tag ghcr.io/hay-kot/homebox:nightly \
|
||||
--tag ghcr.io/hay-kot/homebox:latest \
|
||||
--tag ghcr.io/hay-kot/homebox:${{ inputs.tag }} \
|
||||
--build-arg VERSION=${{ inputs.tag }} \
|
||||
--build-arg COMMIT=$(git rev-parse HEAD) \
|
||||
--build-arg BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
|
||||
--platform linux/amd64,linux/arm64,linux/arm/v7 .
|
||||
|
||||
- name: build release tagged the rootless image
|
||||
if: ${{ inputs.release == true }}
|
||||
run: |
|
||||
docker build --push --no-cache \
|
||||
--tag ghcr.io/hay-kot/homebox:nightly-rootless \
|
||||
--tag ghcr.io/hay-kot/homebox:latest-rootless \
|
||||
--tag ghcr.io/hay-kot/homebox:${{ inputs.tag }}-rootless \
|
||||
--build-arg VERSION=${{ inputs.tag }} \
|
||||
--build-arg COMMIT=$(git rev-parse HEAD) \
|
||||
--build-arg BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
|
||||
--platform linux/amd64,linux/arm64,linux/arm/v7 \
|
||||
--file Dockerfile.rootless .
|
||||
|
|
48
.github/workflows/publish.yaml
vendored
48
.github/workflows/publish.yaml
vendored
|
@ -1,73 +1,29 @@
|
|||
name: Build Nightly
|
||||
name: Publish Dockers
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
|
||||
env:
|
||||
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
|
||||
|
||||
jobs:
|
||||
backend-tests:
|
||||
name: "Backend Server Tests"
|
||||
uses: hay-kot/homebox/.github/workflows/partial-backend.yaml@main
|
||||
|
||||
frontend-tests:
|
||||
name: "Frontend and End-to-End Tests"
|
||||
uses: hay-kot/homebox/.github/workflows/partial-frontend.yaml@main
|
||||
|
||||
deploy:
|
||||
name: "Deploy Nightly to Fly.io"
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- backend-tests
|
||||
- frontend-tests
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- uses: superfly/flyctl-actions/setup-flyctl@master
|
||||
- run: flyctl deploy --remote-only
|
||||
|
||||
publish-nightly:
|
||||
name: "Publish Nightly"
|
||||
if: github.event_name != 'release'
|
||||
needs:
|
||||
- backend-tests
|
||||
- frontend-tests
|
||||
uses: hay-kot/homebox/.github/workflows/partial-publish.yaml@main
|
||||
with:
|
||||
tag: nightly
|
||||
secrets:
|
||||
GH_TOKEN: ${{ secrets.CR_PAT }}
|
||||
|
||||
publish-tag:
|
||||
name: "Publish Tag"
|
||||
if: github.event_name == 'release'
|
||||
needs:
|
||||
- backend-tests
|
||||
- frontend-tests
|
||||
uses: hay-kot/homebox/.github/workflows/partial-publish.yaml@main
|
||||
with:
|
||||
release: true
|
||||
tag: ${{ github.event.release.tag_name }}
|
||||
secrets:
|
||||
GH_TOKEN: ${{ secrets.CR_PAT }}
|
||||
|
||||
deploy-docs:
|
||||
name: Deploy docs
|
||||
needs:
|
||||
- publish-tag
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout main
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Deploy docs
|
||||
uses: mhausenblas/mkdocs-deploy-gh-pages@master
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CONFIG_FILE: docs/mkdocs.yml
|
||||
EXTRA_PACKAGES: build-base
|
||||
|
|
4
.github/workflows/pull-requests.yaml
vendored
4
.github/workflows/pull-requests.yaml
vendored
|
@ -8,8 +8,8 @@ on:
|
|||
jobs:
|
||||
backend-tests:
|
||||
name: "Backend Server Tests"
|
||||
uses: hay-kot/homebox/.github/workflows/partial-backend.yaml@main
|
||||
uses: ./.github/workflows/partial-backend.yaml
|
||||
|
||||
frontend-tests:
|
||||
name: "Frontend and End-to-End Tests"
|
||||
uses: hay-kot/homebox/.github/workflows/partial-frontend.yaml@main
|
||||
uses: ./.github/workflows/partial-frontend.yaml
|
77
.github/workflows/tag.yaml
vendored
Normal file
77
.github/workflows/tag.yaml
vendored
Normal file
|
@ -0,0 +1,77 @@
|
|||
name: Publish Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
|
||||
env:
|
||||
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
|
||||
|
||||
jobs:
|
||||
backend-tests:
|
||||
name: "Backend Server Tests"
|
||||
uses: hay-kot/homebox/.github/workflows/partial-backend.yaml@main
|
||||
|
||||
frontend-tests:
|
||||
name: "Frontend and End-to-End Tests"
|
||||
uses: hay-kot/homebox/.github/workflows/partial-frontend.yaml@main
|
||||
|
||||
goreleaser:
|
||||
name: goreleaser
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 7.30.1
|
||||
|
||||
- name: Build Frontend and Copy to Backend
|
||||
working-directory: frontend
|
||||
run: |
|
||||
pnpm install --shamefully-hoist
|
||||
pnpm run build
|
||||
cp -r ./.output/public ../backend/app/api/static/
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v5
|
||||
with:
|
||||
workdir: "backend"
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
publish-tag:
|
||||
name: "Publish Tag"
|
||||
uses: hay-kot/homebox/.github/workflows/partial-publish.yaml@main
|
||||
with:
|
||||
release: true
|
||||
tag: ${{ github.ref_name }}
|
||||
secrets:
|
||||
GH_TOKEN: ${{ secrets.CR_PAT }}
|
||||
|
||||
deploy-docs:
|
||||
name: Deploy docs
|
||||
needs:
|
||||
- publish-tag
|
||||
- goreleaser
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout main
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Deploy docs
|
||||
uses: mhausenblas/mkdocs-deploy-gh-pages@master
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CONFIG_FILE: docs/mkdocs.yml
|
||||
EXTRA_PACKAGES: build-base
|
12
.gitignore
vendored
12
.gitignore
vendored
|
@ -3,7 +3,6 @@ backend/.data/*
|
|||
config.yml
|
||||
homebox.db
|
||||
.idea
|
||||
|
||||
.DS_Store
|
||||
test-mailer.json
|
||||
node_modules
|
||||
|
@ -32,6 +31,7 @@ node_modules
|
|||
go.work
|
||||
.task/
|
||||
backend/.env
|
||||
build/*
|
||||
|
||||
# Output Directory for Nuxt/Frontend during build step
|
||||
backend/app/api/public/*
|
||||
|
@ -45,3 +45,13 @@ node_modules
|
|||
.output
|
||||
.env
|
||||
dist
|
||||
|
||||
.pnpm-store
|
||||
backend/app/api/app
|
||||
backend/app/api/__debug_bin
|
||||
dist/
|
||||
|
||||
# Nuxt Publish Dir
|
||||
backend/app/api/static/public/*
|
||||
!backend/app/api/static/public/.gitkeep
|
||||
backend/api
|
33
.scaffold/model/scaffold.yaml
Normal file
33
.scaffold/model/scaffold.yaml
Normal file
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
# yaml-language-server: $schema=https://hay-kot.github.io/scaffold/schema.json
|
||||
messages:
|
||||
pre: |
|
||||
# Ent Model Generation
|
||||
|
||||
With Boilerplate!
|
||||
post: |
|
||||
Complete!
|
||||
|
||||
questions:
|
||||
- name: "model"
|
||||
prompt:
|
||||
message: "What is the name of the model? (PascalCase)"
|
||||
required: true
|
||||
|
||||
- name: "by_group"
|
||||
prompt:
|
||||
confirm: "Include a Group Edge? (group_id -> id)"
|
||||
required: true
|
||||
|
||||
rewrites:
|
||||
- from: 'templates/model.go'
|
||||
to: 'backend/internal/data/ent/schema/{{ lower .Scaffold.model }}.go'
|
||||
|
||||
inject:
|
||||
- name: "Insert Groups Edge"
|
||||
path: 'backend/internal/data/ent/schema/group.go'
|
||||
at: // $scaffold_edge
|
||||
template: |
|
||||
{{- if .Scaffold.by_group -}}
|
||||
owned("{{ lower .Scaffold.model }}s", {{ .Scaffold.model }}.Type),
|
||||
{{- end -}}
|
40
.scaffold/model/templates/model.go
Normal file
40
.scaffold/model/templates/model.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
package schema
|
||||
|
||||
import (
|
||||
"entgo.io/ent"
|
||||
|
||||
"github.com/hay-kot/homebox/backend/internal/data/ent/schema/mixins"
|
||||
)
|
||||
|
||||
type {{ .Scaffold.model }} struct {
|
||||
ent.Schema
|
||||
}
|
||||
|
||||
func ({{ .Scaffold.model }}) Mixin() []ent.Mixin {
|
||||
return []ent.Mixin{
|
||||
mixins.BaseMixin{},
|
||||
{{- if .Scaffold.by_group }}
|
||||
GroupMixin{ref: "{{ snakecase .Scaffold.model }}s"},
|
||||
{{- end }}
|
||||
}
|
||||
}
|
||||
|
||||
// Fields of the {{ .Scaffold.model }}.
|
||||
func ({{ .Scaffold.model }}) Fields() []ent.Field {
|
||||
return []ent.Field{
|
||||
// field.String("name").
|
||||
}
|
||||
}
|
||||
|
||||
// Edges of the {{ .Scaffold.model }}.
|
||||
func ({{ .Scaffold.model }}) Edges() []ent.Edge {
|
||||
return []ent.Edge{
|
||||
// edge.From("group", Group.Type).
|
||||
}
|
||||
}
|
||||
|
||||
func ({{ .Scaffold.model }}) Indexes() []ent.Index {
|
||||
return []ent.Index{
|
||||
// index.Fields("token"),
|
||||
}
|
||||
}
|
47
.vscode/launch.json
vendored
Normal file
47
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"compounds": [
|
||||
{
|
||||
"name": "Full Stack",
|
||||
"configurations": ["Launch Backend", "Launch Frontend"],
|
||||
"stopAll": true
|
||||
}
|
||||
],
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch Backend",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "debug",
|
||||
"program": "${workspaceRoot}/backend/app/api/",
|
||||
"args": [],
|
||||
"env": {
|
||||
"HBOX_DEMO": "true",
|
||||
"HBOX_LOG_LEVEL": "debug",
|
||||
"HBOX_DEBUG_ENABLED": "true",
|
||||
"HBOX_STORAGE_DATA": "${workspaceRoot}/backend/.data",
|
||||
"HBOX_STORAGE_SQLITE_URL": "${workspaceRoot}/backend/.data/homebox.db?_fk=1"
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "Launch Frontend",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "pnpm",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"dev"
|
||||
],
|
||||
"cwd": "${workspaceFolder}/frontend",
|
||||
"serverReadyAction": {
|
||||
"action": "debugWithChrome",
|
||||
"pattern": "Local: http://localhost:([0-9]+)",
|
||||
"uriFormat": "http://localhost:%s",
|
||||
"webRoot": "${workspaceFolder}/frontend"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
28
.vscode/settings.json
vendored
28
.vscode/settings.json
vendored
|
@ -1,7 +1,4 @@
|
|||
{
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
},
|
||||
"yaml.schemas": {
|
||||
"https://squidfunk.github.io/mkdocs-material/schema.json": "mkdocs.yml"
|
||||
},
|
||||
|
@ -10,5 +7,28 @@
|
|||
"package.json": "package-lock.json, yarn.lock, .eslintrc.js, tsconfig.json, .prettierrc, .editorconfig, pnpm-lock.yaml, postcss.config.js, tailwind.config.js",
|
||||
"docker-compose.yml": "Dockerfile, .dockerignore, docker-compose.dev.yml, docker-compose.yml",
|
||||
"README.md": "LICENSE, SECURITY.md"
|
||||
}
|
||||
},
|
||||
"cSpell.words": [
|
||||
"debughandlers",
|
||||
"Homebox"
|
||||
],
|
||||
// use ESLint to format code on save
|
||||
"editor.formatOnSave": false,
|
||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
||||
},
|
||||
"eslint.format.enable": true,
|
||||
"css.validate": false,
|
||||
"tailwindCSS.includeLanguages": {
|
||||
"vue": "html",
|
||||
"vue-html": "html"
|
||||
},
|
||||
"editor.quickSuggestions": {
|
||||
"strings": true
|
||||
},
|
||||
"tailwindCSS.experimental.configFile": "./frontend/tailwind.config.js"
|
||||
}
|
||||
|
|
57
CONTRIBUTING.md
Normal file
57
CONTRIBUTING.md
Normal file
|
@ -0,0 +1,57 @@
|
|||
# Contributing
|
||||
|
||||
## We Develop with GitHub
|
||||
|
||||
We use GitHub to host code, to track issues and feature requests, as well as accept pull requests.
|
||||
|
||||
## Branch Flow
|
||||
|
||||
We use the `main` branch as the development branch. All PRs should be made to the `main` branch from a feature branch. To create a pull request, you can use the following steps:
|
||||
|
||||
1. Fork the repository and create a new branch from `main`.
|
||||
2. If you've added code that should be tested, add tests.
|
||||
3. If you've changed APIs, update the documentation.
|
||||
4. Ensure that the test suite and linters pass
|
||||
5. Issue your pull request
|
||||
|
||||
## How To Get Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
There is a devcontainer available for this project. If you are using VSCode, you can use the devcontainer to get started. If you are not using VSCode, you need to ensure that you have the following tools installed:
|
||||
|
||||
- [Go 1.19+](https://golang.org/doc/install)
|
||||
- [Swaggo](https://github.com/swaggo/swag)
|
||||
- [Node.js 16+](https://nodejs.org/en/download/)
|
||||
- [pnpm](https://pnpm.io/installation)
|
||||
- [Taskfile](https://taskfile.dev/#/installation) (Optional but recommended)
|
||||
- For code generation, you'll need to have `python3` available on your path. In most cases, this is already installed and available.
|
||||
|
||||
If you're using `taskfile` you can run `task --list-all` for a list of all commands and their descriptions.
|
||||
|
||||
### Setup
|
||||
|
||||
If you're using the taskfile, you can use the `task setup` command to run the required setup commands. Otherwise, you can review the commands required in the `Taskfile.yml` file.
|
||||
|
||||
Note that when installing dependencies with pnpm you must use the `--shamefully-hoist` flag. If you don't use this flag, you will get an error when running the frontend server.
|
||||
|
||||
### API Development Notes
|
||||
|
||||
start command `task go:run`
|
||||
|
||||
1. API Server does not auto reload. You'll need to restart the server after making changes.
|
||||
2. Unit tests should be written in Go, however, end-to-end or user story tests should be written in TypeScript using the client library in the frontend directory.
|
||||
|
||||
### Frontend Development Notes
|
||||
|
||||
start command `task: ui:dev`
|
||||
|
||||
1. The frontend is a Vue 3 app with Nuxt.js that uses Tailwind and DaisyUI for styling.
|
||||
2. We're using Vitest for our automated testing. You can run these with `task ui:watch`.
|
||||
3. Tests require the API server to be running, and in some cases the first run will fail due to a race condition. If this happens, just run the tests again and they should pass.
|
||||
|
||||
## Publishing Release
|
||||
|
||||
Create a new tag in GitHub with the version number vX.X.X. This will trigger a new release to be created.
|
||||
|
||||
Test -> Goreleaser -> Publish Release -> Trigger Docker Builds -> Deploy Docs + Fly.io Demo
|
24
Dockerfile
24
Dockerfile
|
@ -1,45 +1,45 @@
|
|||
|
||||
# Build Nuxt
|
||||
FROM node:17-alpine as frontend-builder
|
||||
FROM r.batts.cloud/nodejs:18 as frontend-builder
|
||||
WORKDIR /app
|
||||
RUN npm install -g pnpm
|
||||
RUN npm install -g pnpm@latest-9
|
||||
COPY frontend/package.json frontend/pnpm-lock.yaml ./
|
||||
RUN pnpm install --frozen-lockfile --shamefully-hoist
|
||||
COPY frontend .
|
||||
RUN pnpm build
|
||||
|
||||
# Build API
|
||||
FROM golang:alpine AS builder
|
||||
FROM r.batts.cloud/golang:1.24 AS builder
|
||||
ARG BUILD_TIME
|
||||
ARG COMMIT
|
||||
RUN apk update && \
|
||||
apk upgrade && \
|
||||
apk add --update git build-base gcc g++
|
||||
ARG VERSION
|
||||
RUN apt update && \
|
||||
apt install -y git build-essential gcc g++
|
||||
|
||||
WORKDIR /go/src/app
|
||||
COPY ./backend .
|
||||
RUN go get -d -v ./...
|
||||
RUN rm -rf ./app/api/public
|
||||
COPY --from=frontend-builder /app/.output/public ./app/api/public
|
||||
RUN CGO_ENABLED=1 GOOS=linux go build \
|
||||
-ldflags "-s -w -X main.Commit=$COMMIT -X main.BuildTime=$BUILD_TIME" \
|
||||
COPY --from=frontend-builder /app/.output/public ./app/api/static/public
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build \
|
||||
-ldflags "-s -w -X main.commit=$COMMIT -X main.buildTime=$BUILD_TIME -X main.version=$VERSION" \
|
||||
-o /go/bin/api \
|
||||
-v ./app/api/*.go
|
||||
|
||||
# Production Stage
|
||||
FROM alpine:latest
|
||||
FROM r.batts.cloud/debian:trixie
|
||||
|
||||
ENV HBOX_MODE=production
|
||||
ENV HBOX_STORAGE_DATA=/data/
|
||||
ENV HBOX_STORAGE_SQLITE_URL=/data/homebox.db?_fk=1
|
||||
ENV HBOX_STORAGE_SQLITE_URL=/data/homebox.db?_pragma=busy_timeout=2000&_pragma=journal_mode=WAL&_fk=1
|
||||
|
||||
RUN apk --no-cache add ca-certificates
|
||||
RUN mkdir /app
|
||||
COPY --from=builder /go/bin/api /app
|
||||
|
||||
RUN chmod +x /app/api
|
||||
|
||||
LABEL Name=homebox Version=0.0.1
|
||||
LABEL org.opencontainers.image.source="https://github.com/hay-kot/homebox"
|
||||
EXPOSE 7745
|
||||
WORKDIR /app
|
||||
VOLUME [ "/data" ]
|
||||
|
|
53
Dockerfile.rootless
Normal file
53
Dockerfile.rootless
Normal file
|
@ -0,0 +1,53 @@
|
|||
|
||||
# Build Nuxt
|
||||
FROM node:17-alpine as frontend-builder
|
||||
WORKDIR /app
|
||||
RUN npm install -g pnpm
|
||||
COPY frontend/package.json frontend/pnpm-lock.yaml ./
|
||||
RUN pnpm install --frozen-lockfile --shamefully-hoist
|
||||
COPY frontend .
|
||||
RUN pnpm build
|
||||
|
||||
# Build API
|
||||
FROM golang:alpine AS builder
|
||||
ARG BUILD_TIME
|
||||
ARG COMMIT
|
||||
ARG VERSION
|
||||
RUN apk update && \
|
||||
apk upgrade && \
|
||||
apk add --update git build-base gcc g++
|
||||
|
||||
WORKDIR /go/src/app
|
||||
COPY ./backend .
|
||||
RUN go get -d -v ./...
|
||||
RUN rm -rf ./app/api/public
|
||||
COPY --from=frontend-builder /app/.output/public ./app/api/static/public
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build \
|
||||
-ldflags "-s -w -X main.commit=$COMMIT -X main.buildTime=$BUILD_TIME -X main.version=$VERSION" \
|
||||
-o /go/bin/api \
|
||||
-v ./app/api/*.go && \
|
||||
chmod +x /go/bin/api && \
|
||||
# create a directory so that we can copy it in the next stage
|
||||
mkdir /data
|
||||
|
||||
# Production Stage
|
||||
FROM gcr.io/distroless/static
|
||||
|
||||
ENV HBOX_MODE=production
|
||||
ENV HBOX_STORAGE_DATA=/data/
|
||||
ENV HBOX_STORAGE_SQLITE_URL=/data/homebox.db?_fk=1
|
||||
|
||||
# Copy the binary and the (empty) /data dir and
|
||||
# change the ownership to the low-privileged user
|
||||
COPY --from=builder --chown=nonroot /go/bin/api /app
|
||||
COPY --from=builder --chown=nonroot /data /data
|
||||
|
||||
LABEL Name=homebox Version=0.0.1
|
||||
LABEL org.opencontainers.image.source="https://github.com/hay-kot/homebox"
|
||||
EXPOSE 7745
|
||||
VOLUME [ "/data" ]
|
||||
|
||||
# Drop root and run as low-privileged user
|
||||
USER nonroot
|
||||
ENTRYPOINT [ "/app" ]
|
||||
CMD [ "/data/config.yml" ]
|
24
README.md
24
README.md
|
@ -16,12 +16,28 @@
|
|||
[Configuration & Docker Compose](https://hay-kot.github.io/homebox/quick-start)
|
||||
|
||||
```bash
|
||||
docker run --name=homebox \
|
||||
--restart=always \
|
||||
--publish=3100:7745 \
|
||||
ghcr.io/hay-kot/homebox:latest
|
||||
# If using the rootless image, ensure data
|
||||
# folder has correct permissions
|
||||
mkdir -p /path/to/data/folder
|
||||
chown 65532:65532 -R /path/to/data/folder
|
||||
docker run -d \
|
||||
--name homebox \
|
||||
--restart unless-stopped \
|
||||
--publish 3100:7745 \
|
||||
--env TZ=Europe/Bucharest \
|
||||
--volume /path/to/data/folder/:/data \
|
||||
ghcr.io/hay-kot/homebox:latest
|
||||
# ghcr.io/hay-kot/homebox:latest-rootless
|
||||
```
|
||||
|
||||
<!-- CONTRIBUTING -->
|
||||
## Contributing
|
||||
|
||||
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.
|
||||
|
||||
If you are not a coder, you can still contribute financially. Financial contributions help me prioritize working on this project over others and helps me know that there is a real demand for project development.
|
||||
|
||||
<a href="https://www.buymeacoffee.com/haykot" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-green.png" alt="Buy Me A Coffee" style="height: 30px !important;width: 107px !important;" ></a>
|
||||
## Credits
|
||||
|
||||
- Logo by [@lakotelman](https://github.com/lakotelman)
|
||||
|
|
179
Taskfile.yml
179
Taskfile.yml
|
@ -1,63 +1,145 @@
|
|||
version: "3"
|
||||
|
||||
env:
|
||||
HBOX_STORAGE_SQLITE_URL: .data/homebox.db?_fk=1
|
||||
|
||||
HBOX_LOG_LEVEL: debug
|
||||
HBOX_STORAGE_SQLITE_URL: .data/homebox.db?_pragma=busy_timeout=1000&_pragma=journal_mode=WAL&_fk=1
|
||||
HBOX_OPTIONS_ALLOW_REGISTRATION: true
|
||||
UNSAFE_DISABLE_PASSWORD_PROJECTION: "yes_i_am_sure"
|
||||
tasks:
|
||||
generate:
|
||||
desc: |
|
||||
Generates collateral files from the backend project
|
||||
including swagger docs and typescripts type for the frontend
|
||||
deps:
|
||||
- db:generate
|
||||
setup:
|
||||
desc: Install development dependencies
|
||||
cmds:
|
||||
- go install github.com/swaggo/swag/cmd/swag@latest
|
||||
- cd backend && go mod tidy
|
||||
- cd frontend && pnpm install --shamefully-hoist
|
||||
|
||||
swag:
|
||||
desc: Generate swagger docs
|
||||
dir: backend/app/api/static/
|
||||
vars:
|
||||
API: "../"
|
||||
INTERNAL: "../../../internal"
|
||||
PKGS: "../../../pkgs"
|
||||
cmds:
|
||||
- swag fmt --dir={{ .API }}
|
||||
- swag init --dir={{ .API }},{{ .INTERNAL }}/core/services,{{ .INTERNAL }}/data/repo --parseDependency
|
||||
sources:
|
||||
- "./backend/app/api/**/*"
|
||||
- "./backend/internal/data/**"
|
||||
- "./backend/internal/core/services/**/*"
|
||||
- "./backend/app/tools/typegen/main.go"
|
||||
|
||||
typescript-types:
|
||||
desc: Generates typescript types from swagger definition
|
||||
cmds:
|
||||
- cd backend/app/api/ && swag fmt
|
||||
- cd backend/app/api/ && swag init --dir=./,../../internal,../../pkgs
|
||||
- |
|
||||
npx swagger-typescript-api \
|
||||
--no-client \
|
||||
--modular \
|
||||
--path ./backend/app/api/docs/swagger.json \
|
||||
--path ./backend/app/api/static/docs/swagger.json \
|
||||
--output ./frontend/lib/api/types
|
||||
- python3 ./scripts/process-types.py ./frontend/lib/api/types/data-contracts.ts
|
||||
- go run ./backend/app/tools/typegen/main.go ./frontend/lib/api/types/data-contracts.ts
|
||||
sources:
|
||||
- "./backend/app/api/**/*"
|
||||
- "./backend/internal/repo/**/*"
|
||||
- "./backend/internal/services/**/*"
|
||||
- "./scripts/process-types.py"
|
||||
generates:
|
||||
- "./frontend/lib/api/types/data-contracts.ts"
|
||||
- "./backend/app/api/docs/swagger.json"
|
||||
- "./backend/app/api/docs/swagger.yaml"
|
||||
- ./backend/app/tools/typegen/main.go
|
||||
- ./backend/app/api/static/docs/swagger.json
|
||||
|
||||
api:
|
||||
generate:
|
||||
deps:
|
||||
- db:generate
|
||||
cmds:
|
||||
- task: swag
|
||||
- task: typescript-types
|
||||
- cp ./backend/app/api/static/docs/swagger.json docs/docs/api/openapi-2.0.json
|
||||
|
||||
go:run:
|
||||
desc: Starts the backend api server (depends on generate task)
|
||||
dir: backend
|
||||
deps:
|
||||
- generate
|
||||
cmds:
|
||||
- cd backend && go run ./app/api/ {{ .CLI_ARGS }}
|
||||
- go run ./app/api/ {{ .CLI_ARGS }}
|
||||
silent: false
|
||||
|
||||
api:build:
|
||||
go:test:
|
||||
desc: Runs all go tests using gotestsum - supports passing gotestsum args
|
||||
dir: backend
|
||||
cmds:
|
||||
- cd backend && go build ./app/api/
|
||||
- gotestsum {{ .CLI_ARGS }} ./...
|
||||
|
||||
go:coverage:
|
||||
desc: Runs all go tests with -race flag and generates a coverage report
|
||||
dir: backend
|
||||
cmds:
|
||||
- go test -race -coverprofile=coverage.out -covermode=atomic ./app/... ./internal/... ./pkgs/... -v -cover
|
||||
silent: true
|
||||
|
||||
api:test:
|
||||
go:tidy:
|
||||
desc: Runs go mod tidy on the backend
|
||||
dir: backend
|
||||
cmds:
|
||||
- cd backend && go test ./app/api/
|
||||
silent: true
|
||||
- go mod tidy
|
||||
|
||||
api:watch:
|
||||
go:lint:
|
||||
desc: Runs golangci-lint
|
||||
dir: backend
|
||||
cmds:
|
||||
- cd backend && gotestsum --watch ./...
|
||||
- golangci-lint run ./...
|
||||
|
||||
api:coverage:
|
||||
go:all:
|
||||
desc: Runs all go test and lint related tasks
|
||||
cmds:
|
||||
- cd backend && go test -race -coverprofile=coverage.out -covermode=atomic ./app/... ./internal/... ./pkgs/... -v -cover
|
||||
silent: true
|
||||
- task: go:tidy
|
||||
- task: go:lint
|
||||
- task: go:test
|
||||
|
||||
go:build:
|
||||
desc: Builds the backend binary
|
||||
dir: backend
|
||||
cmds:
|
||||
- go build -o ../build/backend ./app/api
|
||||
|
||||
db:generate:
|
||||
desc: Run Entgo.io Code Generation
|
||||
dir: backend/internal/
|
||||
cmds:
|
||||
- |
|
||||
go generate ./...
|
||||
sources:
|
||||
- "./backend/internal/data/ent/schema/**/*"
|
||||
|
||||
db:migration:
|
||||
desc: Runs the database diff engine to generate a SQL migration files
|
||||
deps:
|
||||
- db:generate
|
||||
cmds:
|
||||
- cd backend && go run app/tools/migrations/main.go {{ .CLI_ARGS }}
|
||||
|
||||
ui:watch:
|
||||
desc: Starts the vitest test runner in watch mode
|
||||
dir: frontend
|
||||
cmds:
|
||||
- pnpm run test:watch
|
||||
|
||||
ui:dev:
|
||||
desc: Run frontend development server
|
||||
dir: frontend
|
||||
cmds:
|
||||
- pnpm dev
|
||||
|
||||
ui:fix:
|
||||
desc: Runs prettier and eslint on the frontend
|
||||
dir: frontend
|
||||
cmds:
|
||||
- pnpm run lint:fix
|
||||
|
||||
ui:check:
|
||||
desc: Runs type checking
|
||||
dir: frontend
|
||||
cmds:
|
||||
- pnpm run typecheck
|
||||
|
||||
test:ci:
|
||||
desc: Runs end-to-end test on a live server (only for use in CI)
|
||||
cmds:
|
||||
- cd backend && go build ./app/api
|
||||
- backend/api &
|
||||
|
@ -65,30 +147,11 @@ tasks:
|
|||
- cd frontend && pnpm run test:ci
|
||||
silent: true
|
||||
|
||||
frontend:watch:
|
||||
desc: Starts the vitest test runner in watch mode
|
||||
pr:
|
||||
desc: Runs all tasks required for a PR
|
||||
cmds:
|
||||
- cd frontend && pnpm vitest --watch
|
||||
|
||||
frontend:
|
||||
desc: Run frontend development server
|
||||
cmds:
|
||||
- cd frontend && pnpm dev
|
||||
|
||||
db:generate:
|
||||
desc: Run Entgo.io Code Generation
|
||||
cmds:
|
||||
- |
|
||||
cd backend && go generate ./... \
|
||||
--template=ent/schema/templates/has_id.tmpl
|
||||
sources:
|
||||
- "./backend/ent/schema/**/*"
|
||||
generates:
|
||||
- "./backend/ent/"
|
||||
|
||||
db:migration:
|
||||
desc: Runs the database diff engine to generate a SQL migration files
|
||||
deps:
|
||||
- db:generate
|
||||
cmds:
|
||||
- cd backend && go run app/migrations/main.go {{ .CLI_ARGS }}
|
||||
- task: generate
|
||||
- task: go:all
|
||||
- task: ui:check
|
||||
- task: ui:fix
|
||||
- task: test:ci
|
||||
|
|
2
backend/.gitignore
vendored
Normal file
2
backend/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
|
||||
dist/
|
74
backend/.golangci.yml
Normal file
74
backend/.golangci.yml
Normal file
|
@ -0,0 +1,74 @@
|
|||
run:
|
||||
timeout: 10m
|
||||
skip-dirs:
|
||||
- internal/data/ent.*
|
||||
linters-settings:
|
||||
goconst:
|
||||
min-len: 5
|
||||
min-occurrences: 5
|
||||
exhaustive:
|
||||
default-signifies-exhaustive: true
|
||||
revive:
|
||||
ignore-generated-header: false
|
||||
severity: warning
|
||||
confidence: 3
|
||||
depguard:
|
||||
rules:
|
||||
main:
|
||||
deny:
|
||||
- pkg: io/util
|
||||
desc: |
|
||||
Deprecated: As of Go 1.16, the same functionality is now provided by
|
||||
package io or package os, and those implementations should be
|
||||
preferred in new code. See the specific function documentation for
|
||||
details.
|
||||
gocritic:
|
||||
enabled-checks:
|
||||
- ruleguard
|
||||
testifylint:
|
||||
enable-all: true
|
||||
tagalign:
|
||||
order:
|
||||
- json
|
||||
- schema
|
||||
- yaml
|
||||
- yml
|
||||
- toml
|
||||
- validate
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- asciicheck
|
||||
- bodyclose
|
||||
- depguard
|
||||
- dogsled
|
||||
- errcheck
|
||||
- errorlint
|
||||
- exhaustive
|
||||
- exportloopref
|
||||
- gochecknoinits
|
||||
- goconst
|
||||
- gocritic
|
||||
- gocyclo
|
||||
- gofmt
|
||||
- goprintffuncname
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- misspell
|
||||
- nakedret
|
||||
- revive
|
||||
- staticcheck
|
||||
- stylecheck
|
||||
- tagalign
|
||||
- testifylint
|
||||
- typecheck
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unused
|
||||
- whitespace
|
||||
- zerologlint
|
||||
- sqlclosecheck
|
||||
issues:
|
||||
exclude-use-default: false
|
||||
fix: true
|
54
backend/.goreleaser.yaml
Normal file
54
backend/.goreleaser.yaml
Normal file
|
@ -0,0 +1,54 @@
|
|||
# This is an example .goreleaser.yml file with some sensible defaults.
|
||||
# Make sure to check the documentation at https://goreleaser.com
|
||||
before:
|
||||
hooks:
|
||||
# you may remove this if you don't need go generate
|
||||
- go generate ./...
|
||||
builds:
|
||||
- main: ./app/api
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
- windows
|
||||
- darwin
|
||||
goarch:
|
||||
- amd64
|
||||
- "386"
|
||||
- arm
|
||||
- arm64
|
||||
ignore:
|
||||
- goos: windows
|
||||
goarch: arm
|
||||
- goos: windows
|
||||
goarch: "386"
|
||||
|
||||
archives:
|
||||
- format: tar.gz
|
||||
# this name template makes the OS and Arch compatible with the results of uname.
|
||||
name_template: >-
|
||||
{{ .ProjectName }}_
|
||||
{{- title .Os }}_
|
||||
{{- if eq .Arch "amd64" }}x86_64
|
||||
{{- else if eq .Arch "386" }}i386
|
||||
{{- else }}{{ .Arch }}{{ end }}
|
||||
{{- if .Arm }}v{{ .Arm }}{{ end }}
|
||||
# use zip for windows archives
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
snapshot:
|
||||
name_template: "{{ incpatch .Version }}-next"
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- '^docs:'
|
||||
- '^test:'
|
||||
|
||||
# The lines beneath this are called `modelines`. See `:help modeline`
|
||||
# Feel free to remove those if you don't want/use them.
|
||||
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
|
||||
# vim: set ts=2 sw=2 tw=0 fo=cnqoj
|
|
@ -1,23 +1,21 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/hay-kot/homebox/backend/ent"
|
||||
"github.com/hay-kot/homebox/backend/internal/config"
|
||||
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||
"github.com/hay-kot/homebox/backend/internal/services"
|
||||
"github.com/hay-kot/homebox/backend/internal/core/services"
|
||||
"github.com/hay-kot/homebox/backend/internal/core/services/reporting/eventbus"
|
||||
"github.com/hay-kot/homebox/backend/internal/data/ent"
|
||||
"github.com/hay-kot/homebox/backend/internal/data/repo"
|
||||
"github.com/hay-kot/homebox/backend/internal/sys/config"
|
||||
"github.com/hay-kot/homebox/backend/pkgs/mailer"
|
||||
"github.com/hay-kot/homebox/backend/pkgs/server"
|
||||
)
|
||||
|
||||
type app struct {
|
||||
conf *config.Config
|
||||
mailer mailer.Mailer
|
||||
db *ent.Client
|
||||
server *server.Server
|
||||
repos *repo.AllRepos
|
||||
services *services.AllServices
|
||||
bus *eventbus.EventBus
|
||||
}
|
||||
|
||||
func new(conf *config.Config) *app {
|
||||
|
@ -35,10 +33,3 @@ func new(conf *config.Config) *app {
|
|||
|
||||
return s
|
||||
}
|
||||
|
||||
func (a *app) startBgTask(t time.Duration, fn func()) {
|
||||
for {
|
||||
a.server.Background(fn)
|
||||
time.Sleep(t)
|
||||
}
|
||||
}
|
||||
|
|
37
backend/app/api/bgrunner.go
Normal file
37
backend/app/api/bgrunner.go
Normal file
|
@ -0,0 +1,37 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
type BackgroundTask struct {
|
||||
name string
|
||||
Interval time.Duration
|
||||
Fn func(context.Context)
|
||||
}
|
||||
|
||||
func (tsk *BackgroundTask) Name() string {
|
||||
return tsk.name
|
||||
}
|
||||
|
||||
func NewTask(name string, interval time.Duration, fn func(context.Context)) *BackgroundTask {
|
||||
return &BackgroundTask{
|
||||
Interval: interval,
|
||||
Fn: fn,
|
||||
}
|
||||
}
|
||||
|
||||
func (tsk *BackgroundTask) Start(ctx context.Context) error {
|
||||
timer := time.NewTimer(tsk.Interval)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case <-timer.C:
|
||||
timer.Reset(tsk.Interval)
|
||||
tsk.Fn(ctx)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,57 +2,61 @@ package main
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/csv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hay-kot/homebox/backend/internal/services"
|
||||
"github.com/hay-kot/homebox/backend/internal/core/services"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (a *app) SetupDemo() {
|
||||
csvText := `Import Ref,Location,Labels,Quantity,Name,Description,Insured,Serial Number,Model Number,Manufacturer,Notes,Purchase From,Purchased Price,Purchased Time,Lifetime Warranty,Warranty Expires,Warranty Details,Sold To,Sold Price,Sold Time,Sold Notes
|
||||
csvText := `HB.import_ref,HB.location,HB.labels,HB.quantity,HB.name,HB.description,HB.insured,HB.serial_number,HB.model_number,HB.manufacturer,HB.notes,HB.purchase_from,HB.purchase_price,HB.purchase_time,HB.lifetime_warranty,HB.warranty_expires,HB.warranty_details,HB.sold_to,HB.sold_price,HB.sold_time,HB.sold_notes
|
||||
,Garage,IOT;Home Assistant; Z-Wave,1,Zooz Universal Relay ZEN17,"Zooz 700 Series Z-Wave Universal Relay ZEN17 for Awnings, Garage Doors, Sprinklers, and More | 2 NO-C-NC Relays (20A, 10A) | Signal Repeater | Hub Required (Compatible with SmartThings and Hubitat)",,,ZEN17,Zooz,,Amazon,39.95,10/13/2021,,,,,,,
|
||||
,Living Room,IOT;Home Assistant; Z-Wave,1,Zooz Motion Sensor,"Zooz Z-Wave Plus S2 Motion Sensor ZSE18 with Magnetic Mount, Works with Vera and SmartThings",,,ZSE18,Zooz,,Amazon,29.95,10/15/2021,,,,,,,
|
||||
,Office,IOT;Home Assistant; Z-Wave,1,Zooz 110v Power Switch,"Zooz Z-Wave Plus Power Switch ZEN15 for 110V AC Units, Sump Pumps, Humidifiers, and More",,,ZEN15,Zooz,,Amazon,39.95,10/13/2021,,,,,,,
|
||||
,Downstairs,IOT;Home Assistant; Z-Wave,1,Ecolink Z-Wave PIR Motion Sensor,"Ecolink Z-Wave PIR Motion Detector Pet Immune, White (PIRZWAVE2.5-ECO)",,,PIRZWAVE2.5-ECO,Ecolink,,Amazon,35.58,10/21/2020,,,,,,,
|
||||
,Entry,IOT;Home Assistant; Z-Wave,1,Yale Security Touchscreen Deadbolt,"Yale Security YRD226-ZW2-619 YRD226ZW2619 Touchscreen Deadbolt, Satin Nickel",,,YRD226ZW2619,Yale,,Amazon,120.39,10/14/2020,,,,,,,
|
||||
,Kitchen,IOT;Home Assistant; Z-Wave,1,Smart Rocker Light Dimmer,"UltraPro Z-Wave Smart Rocker Light Dimmer with QuickFit and SimpleWire, 3-Way Ready, Compatible with Alexa, Google Assistant, ZWave Hub Required, Repeater/Range Extender, White Paddle Only, 39351",,,39351,Honeywell,,Amazon,65.98,09/30/0202,,,,,,,
|
||||
,Kitchen,IOT;Home Assistant; Z-Wave,1,Smart Rocker Light Dimmer,"UltraPro Z-Wave Smart Rocker Light Dimmer with QuickFit and SimpleWire, 3-Way Ready, Compatible with Alexa, Google Assistant, ZWave Hub Required, Repeater/Range Extender, White Paddle Only, 39351",,,39351,Honeywell,,Amazon,65.98,09/30/0202,,,,,,,
|
||||
`
|
||||
|
||||
var (
|
||||
registration = services.UserRegistration{
|
||||
Email: "demo@email.com",
|
||||
Name: "Demo",
|
||||
Password: "demo",
|
||||
}
|
||||
)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
registration := services.UserRegistration{
|
||||
Email: "demo@example.com",
|
||||
Name: "Demo",
|
||||
Password: "demo",
|
||||
}
|
||||
|
||||
// First check if we've already setup a demo user and skip if so
|
||||
_, err := a.services.User.Login(context.Background(), registration.Email, registration.Password)
|
||||
log.Debug().Msg("Checking if demo user already exists")
|
||||
_, err := a.services.User.Login(ctx, registration.Email, registration.Password, false)
|
||||
if err == nil {
|
||||
log.Info().Msg("Demo user already exists, skipping setup")
|
||||
return
|
||||
}
|
||||
|
||||
_, err = a.services.User.RegisterUser(context.Background(), registration)
|
||||
log.Debug().Msg("Demo user does not exist, setting up demo")
|
||||
_, err = a.services.User.RegisterUser(ctx, registration)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to register demo user")
|
||||
log.Fatal().Msg("Failed to setup demo")
|
||||
}
|
||||
|
||||
token, _ := a.services.User.Login(context.Background(), registration.Email, registration.Password)
|
||||
self, _ := a.services.User.GetSelf(context.Background(), token.Raw)
|
||||
|
||||
// Read CSV Text
|
||||
reader := csv.NewReader(strings.NewReader(csvText))
|
||||
reader.Comma = ','
|
||||
|
||||
records, err := reader.ReadAll()
|
||||
token, err := a.services.User.Login(ctx, registration.Email, registration.Password, false)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to read CSV")
|
||||
log.Err(err).Msg("Failed to login demo user")
|
||||
log.Fatal().Msg("Failed to setup demo")
|
||||
return
|
||||
}
|
||||
self, err := a.services.User.GetSelf(ctx, token.Raw)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to get self")
|
||||
log.Fatal().Msg("Failed to setup demo")
|
||||
return
|
||||
}
|
||||
|
||||
err = a.services.Items.CsvImport(context.Background(), self.GroupID, records)
|
||||
_, err = a.services.Items.CsvImport(ctx, self.GroupID, strings.NewReader(csvText))
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to import CSV")
|
||||
log.Fatal().Msg("Failed to setup demo")
|
||||
|
|
File diff suppressed because it is too large
Load diff
17
backend/app/api/handlers/debughandlers/debug.go
Normal file
17
backend/app/api/handlers/debughandlers/debug.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
// Package debughandlers provides handlers for debugging.
|
||||
package debughandlers
|
||||
|
||||
import (
|
||||
"expvar"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
)
|
||||
|
||||
func New(mux *http.ServeMux) {
|
||||
mux.HandleFunc("/debug/pprof", pprof.Index)
|
||||
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
||||
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
||||
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
||||
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
||||
mux.Handle("/debug/vars", expvar.Handler())
|
||||
}
|
BIN
backend/app/api/handlers/v1/assets/QRIcon.png
Normal file
BIN
backend/app/api/handlers/v1/assets/QRIcon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.5 KiB |
213
backend/app/api/handlers/v1/controller.go
Normal file
213
backend/app/api/handlers/v1/controller.go
Normal file
|
@ -0,0 +1,213 @@
|
|||
// Package v1 provides the API handlers for version 1 of the API.
|
||||
package v1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/hay-kot/homebox/backend/internal/core/services"
|
||||
"github.com/hay-kot/homebox/backend/internal/core/services/reporting/eventbus"
|
||||
"github.com/hay-kot/homebox/backend/internal/data/repo"
|
||||
"github.com/hay-kot/httpkit/errchain"
|
||||
"github.com/hay-kot/httpkit/server"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/olahol/melody"
|
||||
)
|
||||
|
||||
type Results[T any] struct {
|
||||
Items []T `json:"items"`
|
||||
}
|
||||
|
||||
func WrapResults[T any](items []T) Results[T] {
|
||||
return Results[T]{Items: items}
|
||||
}
|
||||
|
||||
type Wrapped struct {
|
||||
Item interface{} `json:"item"`
|
||||
}
|
||||
|
||||
func Wrap(v any) Wrapped {
|
||||
return Wrapped{Item: v}
|
||||
}
|
||||
|
||||
func WithMaxUploadSize(maxUploadSize int64) func(*V1Controller) {
|
||||
return func(ctrl *V1Controller) {
|
||||
ctrl.maxUploadSize = maxUploadSize
|
||||
}
|
||||
}
|
||||
|
||||
func WithDemoStatus(demoStatus bool) func(*V1Controller) {
|
||||
return func(ctrl *V1Controller) {
|
||||
ctrl.isDemo = demoStatus
|
||||
}
|
||||
}
|
||||
|
||||
func WithRegistration(allowRegistration bool) func(*V1Controller) {
|
||||
return func(ctrl *V1Controller) {
|
||||
ctrl.allowRegistration = allowRegistration
|
||||
}
|
||||
}
|
||||
|
||||
func WithSecureCookies(secure bool) func(*V1Controller) {
|
||||
return func(ctrl *V1Controller) {
|
||||
ctrl.cookieSecure = secure
|
||||
}
|
||||
}
|
||||
|
||||
type V1Controller struct {
|
||||
cookieSecure bool
|
||||
repo *repo.AllRepos
|
||||
svc *services.AllServices
|
||||
maxUploadSize int64
|
||||
isDemo bool
|
||||
allowRegistration bool
|
||||
bus *eventbus.EventBus
|
||||
}
|
||||
|
||||
type (
|
||||
ReadyFunc func() bool
|
||||
|
||||
Build struct {
|
||||
Version string `json:"version"`
|
||||
Commit string `json:"commit"`
|
||||
BuildTime string `json:"buildTime"`
|
||||
}
|
||||
|
||||
APISummary struct {
|
||||
Healthy bool `json:"health"`
|
||||
Versions []string `json:"versions"`
|
||||
Title string `json:"title"`
|
||||
Message string `json:"message"`
|
||||
Build Build `json:"build"`
|
||||
Demo bool `json:"demo"`
|
||||
AllowRegistration bool `json:"allowRegistration"`
|
||||
}
|
||||
)
|
||||
|
||||
func BaseURLFunc(prefix string) func(s string) string {
|
||||
return func(s string) string {
|
||||
return prefix + "/v1" + s
|
||||
}
|
||||
}
|
||||
|
||||
func NewControllerV1(svc *services.AllServices, repos *repo.AllRepos, bus *eventbus.EventBus, options ...func(*V1Controller)) *V1Controller {
|
||||
ctrl := &V1Controller{
|
||||
repo: repos,
|
||||
svc: svc,
|
||||
allowRegistration: true,
|
||||
bus: bus,
|
||||
}
|
||||
|
||||
for _, opt := range options {
|
||||
opt(ctrl)
|
||||
}
|
||||
|
||||
return ctrl
|
||||
}
|
||||
|
||||
// HandleBase godoc
|
||||
//
|
||||
// @Summary Application Info
|
||||
// @Tags Base
|
||||
// @Produce json
|
||||
// @Success 200 {object} APISummary
|
||||
// @Router /v1/status [GET]
|
||||
func (ctrl *V1Controller) HandleBase(ready ReadyFunc, build Build) errchain.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) error {
|
||||
return server.JSON(w, http.StatusOK, APISummary{
|
||||
Healthy: ready(),
|
||||
Title: "Homebox",
|
||||
Message: "Track, Manage, and Organize your Things",
|
||||
Build: build,
|
||||
Demo: ctrl.isDemo,
|
||||
AllowRegistration: ctrl.allowRegistration,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// HandleCurrency godoc
|
||||
//
|
||||
// @Summary Currency
|
||||
// @Tags Base
|
||||
// @Produce json
|
||||
// @Success 200 {object} currencies.Currency
|
||||
// @Router /v1/currency [GET]
|
||||
func (ctrl *V1Controller) HandleCurrency() errchain.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) error {
|
||||
// Set Cache for 10 Minutes
|
||||
w.Header().Set("Cache-Control", "max-age=600")
|
||||
|
||||
return server.JSON(w, http.StatusOK, ctrl.svc.Currencies.Slice())
|
||||
}
|
||||
}
|
||||
|
||||
func (ctrl *V1Controller) HandleCacheWS() errchain.HandlerFunc {
|
||||
type eventMsg struct {
|
||||
Event string `json:"event"`
|
||||
}
|
||||
|
||||
m := melody.New()
|
||||
|
||||
m.HandleConnect(func(s *melody.Session) {
|
||||
auth := services.NewContext(s.Request.Context())
|
||||
s.Set("gid", auth.GID)
|
||||
})
|
||||
|
||||
factory := func(e string) func(data any) {
|
||||
return func(data any) {
|
||||
eventData, ok := data.(eventbus.GroupMutationEvent)
|
||||
if !ok {
|
||||
log.Log().Msgf("invalid event data: %v", data)
|
||||
return
|
||||
}
|
||||
|
||||
msg := &eventMsg{Event: e}
|
||||
|
||||
jsonBytes, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
log.Log().Msgf("error marshling event data %v: %v", data, err)
|
||||
return
|
||||
}
|
||||
|
||||
_ = m.BroadcastFilter(jsonBytes, func(s *melody.Session) bool {
|
||||
groupIDStr, ok := s.Get("gid")
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
GID := groupIDStr.(uuid.UUID)
|
||||
return GID == eventData.GID
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
ctrl.bus.Subscribe(eventbus.EventLabelMutation, factory("label.mutation"))
|
||||
ctrl.bus.Subscribe(eventbus.EventLocationMutation, factory("location.mutation"))
|
||||
ctrl.bus.Subscribe(eventbus.EventItemMutation, factory("item.mutation"))
|
||||
|
||||
// Persistent asynchronous ticker that keeps all websocket connections alive with periodic pings.
|
||||
go func() {
|
||||
const interval = 10 * time.Second
|
||||
|
||||
ping := time.NewTicker(interval)
|
||||
defer ping.Stop()
|
||||
|
||||
for range ping.C {
|
||||
msg := &eventMsg{Event: "ping"}
|
||||
|
||||
pingBytes, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
log.Log().Msgf("error marshaling ping: %v", err)
|
||||
} else {
|
||||
_ = m.Broadcast(pingBytes)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) error {
|
||||
return m.HandleRequest(w, r)
|
||||
}
|
||||
}
|
27
backend/app/api/handlers/v1/partials.go
Normal file
27
backend/app/api/handlers/v1/partials.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/google/uuid"
|
||||
"github.com/hay-kot/homebox/backend/internal/sys/validate"
|
||||
)
|
||||
|
||||
// routeID extracts the ID from the request URL. If the ID is not in a valid
|
||||
// format, an error is returned. If a error is returned, it can be directly returned
|
||||
// from the handler. the validate.ErrInvalidID error is known by the error middleware
|
||||
// and will be handled accordingly.
|
||||
//
|
||||
// Example: /api/v1/ac614db5-d8b8-4659-9b14-6e913a6eb18a -> uuid.UUID{ac614db5-d8b8-4659-9b14-6e913a6eb18a}
|
||||
func (ctrl *V1Controller) routeID(r *http.Request) (uuid.UUID, error) {
|
||||
return ctrl.routeUUID(r, "id")
|
||||
}
|
||||
|
||||
func (ctrl *V1Controller) routeUUID(r *http.Request, key string) (uuid.UUID, error) {
|
||||
ID, err := uuid.Parse(chi.URLParam(r, key))
|
||||
if err != nil {
|
||||
return uuid.Nil, validate.NewRouteKeyError(key)
|
||||
}
|
||||
return ID, nil
|
||||
}
|
36
backend/app/api/handlers/v1/query_params.go
Normal file
36
backend/app/api/handlers/v1/query_params.go
Normal file
|
@ -0,0 +1,36 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func queryUUIDList(params url.Values, key string) []uuid.UUID {
|
||||
var ids []uuid.UUID
|
||||
for _, id := range params[key] {
|
||||
uid, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
ids = append(ids, uid)
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
func queryIntOrNegativeOne(s string) int {
|
||||
i, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func queryBool(s string) bool {
|
||||
b, err := strconv.ParseBool(s)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return b
|
||||
}
|
83
backend/app/api/handlers/v1/v1_ctrl_actions.go
Normal file
83
backend/app/api/handlers/v1/v1_ctrl_actions.go
Normal file
|
@ -0,0 +1,83 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/hay-kot/homebox/backend/internal/core/services"
|
||||
"github.com/hay-kot/homebox/backend/internal/sys/validate"
|
||||
"github.com/hay-kot/httpkit/errchain"
|
||||
"github.com/hay-kot/httpkit/server"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type ActionAmountResult struct {
|
||||
Completed int `json:"completed"`
|
||||
}
|
||||
|
||||
func actionHandlerFactory(ref string, fn func(context.Context, uuid.UUID) (int, error)) errchain.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) error {
|
||||
ctx := services.NewContext(r.Context())
|
||||
|
||||
totalCompleted, err := fn(ctx, ctx.GID)
|
||||
if err != nil {
|
||||
log.Err(err).Str("action_ref", ref).Msg("failed to run action")
|
||||
return validate.NewRequestError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return server.JSON(w, http.StatusOK, ActionAmountResult{Completed: totalCompleted})
|
||||
}
|
||||
}
|
||||
|
||||
// HandleEnsureAssetID godoc
|
||||
//
|
||||
// @Summary Ensure Asset IDs
|
||||
// @Description Ensures all items in the database have an asset ID
|
||||
// @Tags Actions
|
||||
// @Produce json
|
||||
// @Success 200 {object} ActionAmountResult
|
||||
// @Router /v1/actions/ensure-asset-ids [Post]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleEnsureAssetID() errchain.HandlerFunc {
|
||||
return actionHandlerFactory("ensure asset IDs", ctrl.svc.Items.EnsureAssetID)
|
||||
}
|
||||
|
||||
// HandleEnsureImportRefs godoc
|
||||
//
|
||||
// @Summary Ensures Import Refs
|
||||
// @Description Ensures all items in the database have an import ref
|
||||
// @Tags Actions
|
||||
// @Produce json
|
||||
// @Success 200 {object} ActionAmountResult
|
||||
// @Router /v1/actions/ensure-import-refs [Post]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleEnsureImportRefs() errchain.HandlerFunc {
|
||||
return actionHandlerFactory("ensure import refs", ctrl.svc.Items.EnsureImportRef)
|
||||
}
|
||||
|
||||
// HandleItemDateZeroOut godoc
|
||||
//
|
||||
// @Summary Zero Out Time Fields
|
||||
// @Description Resets all item date fields to the beginning of the day
|
||||
// @Tags Actions
|
||||
// @Produce json
|
||||
// @Success 200 {object} ActionAmountResult
|
||||
// @Router /v1/actions/zero-item-time-fields [Post]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemDateZeroOut() errchain.HandlerFunc {
|
||||
return actionHandlerFactory("zero out date time", ctrl.repo.Items.ZeroOutTimeFields)
|
||||
}
|
||||
|
||||
// HandleSetPrimaryPhotos godoc
|
||||
//
|
||||
// @Summary Set Primary Photos
|
||||
// @Description Sets the first photo of each item as the primary photo
|
||||
// @Tags Actions
|
||||
// @Produce json
|
||||
// @Success 200 {object} ActionAmountResult
|
||||
// @Router /v1/actions/set-primary-photos [Post]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleSetPrimaryPhotos() errchain.HandlerFunc {
|
||||
return actionHandlerFactory("ensure asset IDs", ctrl.repo.Items.SetPrimaryPhotos)
|
||||
}
|
62
backend/app/api/handlers/v1/v1_ctrl_assets.go
Normal file
62
backend/app/api/handlers/v1/v1_ctrl_assets.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/hay-kot/homebox/backend/internal/core/services"
|
||||
"github.com/hay-kot/homebox/backend/internal/data/repo"
|
||||
"github.com/hay-kot/homebox/backend/internal/sys/validate"
|
||||
"github.com/hay-kot/httpkit/errchain"
|
||||
"github.com/hay-kot/httpkit/server"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// HandleAssetGet godocs
|
||||
//
|
||||
// @Summary Get Item by Asset ID
|
||||
// @Tags Items
|
||||
// @Produce json
|
||||
// @Param id path string true "Asset ID"
|
||||
// @Success 200 {object} repo.PaginationResult[repo.ItemSummary]{}
|
||||
// @Router /v1/assets/{id} [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleAssetGet() errchain.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) error {
|
||||
ctx := services.NewContext(r.Context())
|
||||
assetIDParam := chi.URLParam(r, "id")
|
||||
assetIDParam = strings.ReplaceAll(assetIDParam, "-", "") // Remove dashes
|
||||
// Convert the asset ID to an int64
|
||||
assetID, err := strconv.ParseInt(assetIDParam, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pageParam := r.URL.Query().Get("page")
|
||||
var page int64 = -1
|
||||
if pageParam != "" {
|
||||
page, err = strconv.ParseInt(pageParam, 10, 64)
|
||||
if err != nil {
|
||||
return server.JSON(w, http.StatusBadRequest, "Invalid page number")
|
||||
}
|
||||
}
|
||||
|
||||
pageSizeParam := r.URL.Query().Get("pageSize")
|
||||
var pageSize int64 = -1
|
||||
if pageSizeParam != "" {
|
||||
pageSize, err = strconv.ParseInt(pageSizeParam, 10, 64)
|
||||
if err != nil {
|
||||
return server.JSON(w, http.StatusBadRequest, "Invalid page size")
|
||||
}
|
||||
}
|
||||
|
||||
items, err := ctrl.repo.Items.QueryByAssetID(r.Context(), ctx.GID, repo.AssetID(assetID), int(page), int(pageSize))
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to get item")
|
||||
return validate.NewRequestError(err, http.StatusInternalServerError)
|
||||
}
|
||||
return server.JSON(w, http.StatusOK, items)
|
||||
}
|
||||
}
|
251
backend/app/api/handlers/v1/v1_ctrl_auth.go
Normal file
251
backend/app/api/handlers/v1/v1_ctrl_auth.go
Normal file
|
@ -0,0 +1,251 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hay-kot/homebox/backend/internal/core/services"
|
||||
"github.com/hay-kot/homebox/backend/internal/sys/validate"
|
||||
"github.com/hay-kot/httpkit/errchain"
|
||||
"github.com/hay-kot/httpkit/server"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
cookieNameToken = "hb.auth.token"
|
||||
cookieNameRemember = "hb.auth.remember"
|
||||
cookieNameSession = "hb.auth.session"
|
||||
)
|
||||
|
||||
type (
|
||||
TokenResponse struct {
|
||||
Token string `json:"token"`
|
||||
ExpiresAt time.Time `json:"expiresAt"`
|
||||
AttachmentToken string `json:"attachmentToken"`
|
||||
}
|
||||
|
||||
LoginForm struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
StayLoggedIn bool `json:"stayLoggedIn"`
|
||||
}
|
||||
)
|
||||
|
||||
type CookieContents struct {
|
||||
Token string
|
||||
ExpiresAt time.Time
|
||||
Remember bool
|
||||
}
|
||||
|
||||
func GetCookies(r *http.Request) (*CookieContents, error) {
|
||||
cookie, err := r.Cookie(cookieNameToken)
|
||||
if err != nil {
|
||||
return nil, errors.New("authorization cookie is required")
|
||||
}
|
||||
|
||||
rememberCookie, err := r.Cookie(cookieNameRemember)
|
||||
if err != nil {
|
||||
return nil, errors.New("remember cookie is required")
|
||||
}
|
||||
|
||||
return &CookieContents{
|
||||
Token: cookie.Value,
|
||||
ExpiresAt: cookie.Expires,
|
||||
Remember: rememberCookie.Value == "true",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// AuthProvider is an interface that can be implemented by any authentication provider.
|
||||
// to extend authentication methods for the API.
|
||||
type AuthProvider interface {
|
||||
// Name returns the name of the authentication provider. This should be a unique name.
|
||||
// that is URL friendly.
|
||||
//
|
||||
// Example: "local", "ldap"
|
||||
Name() string
|
||||
// Authenticate is called when a user attempts to login to the API. The implementation
|
||||
// should return an error if the user cannot be authenticated. If an error is returned
|
||||
// the API controller will return a vague error message to the user.
|
||||
//
|
||||
// Authenticate should do the following:
|
||||
//
|
||||
// 1. Ensure that the user exists within the database (either create, or get)
|
||||
// 2. On successful authentication, they must set the user cookies.
|
||||
Authenticate(w http.ResponseWriter, r *http.Request) (services.UserAuthTokenDetail, error)
|
||||
}
|
||||
|
||||
// HandleAuthLogin godoc
|
||||
//
|
||||
// @Summary User Login
|
||||
// @Tags Authentication
|
||||
// @Accept x-www-form-urlencoded
|
||||
// @Accept application/json
|
||||
// @Param username formData string false "string" example(admin@admin.com)
|
||||
// @Param password formData string false "string" example(admin)
|
||||
// @Param payload body LoginForm true "Login Data"
|
||||
// @Param provider query string false "auth provider"
|
||||
// @Produce json
|
||||
// @Success 200 {object} TokenResponse
|
||||
// @Router /v1/users/login [POST]
|
||||
func (ctrl *V1Controller) HandleAuthLogin(ps ...AuthProvider) errchain.HandlerFunc {
|
||||
if len(ps) == 0 {
|
||||
panic("no auth providers provided")
|
||||
}
|
||||
|
||||
providers := make(map[string]AuthProvider)
|
||||
for _, p := range ps {
|
||||
log.Info().Str("name", p.Name()).Msg("registering auth provider")
|
||||
providers[p.Name()] = p
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) error {
|
||||
// Extract provider query
|
||||
provider := r.URL.Query().Get("provider")
|
||||
if provider == "" {
|
||||
provider = "local"
|
||||
}
|
||||
|
||||
// Get the provider
|
||||
p, ok := providers[provider]
|
||||
if !ok {
|
||||
return validate.NewRequestError(errors.New("invalid auth provider"), http.StatusBadRequest)
|
||||
}
|
||||
|
||||
newToken, err := p.Authenticate(w, r)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to authenticate")
|
||||
return server.JSON(w, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
ctrl.setCookies(w, noPort(r.Host), newToken.Raw, newToken.ExpiresAt, true)
|
||||
return server.JSON(w, http.StatusOK, TokenResponse{
|
||||
Token: "Bearer " + newToken.Raw,
|
||||
ExpiresAt: newToken.ExpiresAt,
|
||||
AttachmentToken: newToken.AttachmentToken,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// HandleAuthLogout godoc
|
||||
//
|
||||
// @Summary User Logout
|
||||
// @Tags Authentication
|
||||
// @Success 204
|
||||
// @Router /v1/users/logout [POST]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleAuthLogout() errchain.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) error {
|
||||
token := services.UseTokenCtx(r.Context())
|
||||
if token == "" {
|
||||
return validate.NewRequestError(errors.New("no token within request context"), http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
err := ctrl.svc.User.Logout(r.Context(), token)
|
||||
if err != nil {
|
||||
return validate.NewRequestError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
ctrl.unsetCookies(w, noPort(r.Host))
|
||||
return server.JSON(w, http.StatusNoContent, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleAuthRefresh godoc
|
||||
//
|
||||
// @Summary User Token Refresh
|
||||
// @Description handleAuthRefresh returns a handler that will issue a new token from an existing token.
|
||||
// @Description This does not validate that the user still exists within the database.
|
||||
// @Tags Authentication
|
||||
// @Success 200
|
||||
// @Router /v1/users/refresh [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleAuthRefresh() errchain.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) error {
|
||||
requestToken := services.UseTokenCtx(r.Context())
|
||||
if requestToken == "" {
|
||||
return validate.NewRequestError(errors.New("no token within request context"), http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
newToken, err := ctrl.svc.User.RenewToken(r.Context(), requestToken)
|
||||
if err != nil {
|
||||
return validate.NewUnauthorizedError()
|
||||
}
|
||||
|
||||
ctrl.setCookies(w, noPort(r.Host), newToken.Raw, newToken.ExpiresAt, false)
|
||||
return server.JSON(w, http.StatusOK, newToken)
|
||||
}
|
||||
}
|
||||
|
||||
func noPort(host string) string {
|
||||
return strings.Split(host, ":")[0]
|
||||
}
|
||||
|
||||
func (ctrl *V1Controller) setCookies(w http.ResponseWriter, domain, token string, expires time.Time, remember bool) {
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: cookieNameRemember,
|
||||
Value: strconv.FormatBool(remember),
|
||||
Expires: expires,
|
||||
Domain: domain,
|
||||
Secure: ctrl.cookieSecure,
|
||||
HttpOnly: true,
|
||||
Path: "/",
|
||||
})
|
||||
|
||||
// Set HTTP only cookie
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: cookieNameToken,
|
||||
Value: token,
|
||||
Expires: expires,
|
||||
Domain: domain,
|
||||
Secure: ctrl.cookieSecure,
|
||||
HttpOnly: true,
|
||||
Path: "/",
|
||||
})
|
||||
|
||||
// Set Fake Session cookie
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: cookieNameSession,
|
||||
Value: "true",
|
||||
Expires: expires,
|
||||
Domain: domain,
|
||||
Secure: ctrl.cookieSecure,
|
||||
HttpOnly: false,
|
||||
Path: "/",
|
||||
})
|
||||
}
|
||||
|
||||
func (ctrl *V1Controller) unsetCookies(w http.ResponseWriter, domain string) {
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: cookieNameToken,
|
||||
Value: "",
|
||||
Expires: time.Unix(0, 0),
|
||||
Domain: domain,
|
||||
Secure: ctrl.cookieSecure,
|
||||
HttpOnly: true,
|
||||
Path: "/",
|
||||
})
|
||||
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: cookieNameRemember,
|
||||
Value: "false",
|
||||
Expires: time.Unix(0, 0),
|
||||
Domain: domain,
|
||||
Secure: ctrl.cookieSecure,
|
||||
HttpOnly: true,
|
||||
Path: "/",
|
||||
})
|
||||
|
||||
// Set Fake Session cookie
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: cookieNameSession,
|
||||
Value: "false",
|
||||
Expires: time.Unix(0, 0),
|
||||
Domain: domain,
|
||||
Secure: ctrl.cookieSecure,
|
||||
HttpOnly: false,
|
||||
Path: "/",
|
||||
})
|
||||
}
|
97
backend/app/api/handlers/v1/v1_ctrl_group.go
Normal file
97
backend/app/api/handlers/v1/v1_ctrl_group.go
Normal file
|
@ -0,0 +1,97 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/hay-kot/homebox/backend/internal/core/services"
|
||||
"github.com/hay-kot/homebox/backend/internal/data/repo"
|
||||
"github.com/hay-kot/homebox/backend/internal/sys/validate"
|
||||
"github.com/hay-kot/homebox/backend/internal/web/adapters"
|
||||
"github.com/hay-kot/httpkit/errchain"
|
||||
)
|
||||
|
||||
type (
|
||||
GroupInvitationCreate struct {
|
||||
Uses int `json:"uses" validate:"required,min=1,max=100"`
|
||||
ExpiresAt time.Time `json:"expiresAt"`
|
||||
}
|
||||
|
||||
GroupInvitation struct {
|
||||
Token string `json:"token"`
|
||||
ExpiresAt time.Time `json:"expiresAt"`
|
||||
Uses int `json:"uses"`
|
||||
}
|
||||
)
|
||||
|
||||
// HandleGroupGet godoc
|
||||
//
|
||||
// @Summary Get Group
|
||||
// @Tags Group
|
||||
// @Produce json
|
||||
// @Success 200 {object} repo.Group
|
||||
// @Router /v1/groups [Get]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleGroupGet() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request) (repo.Group, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
return ctrl.repo.Groups.GroupByID(auth, auth.GID)
|
||||
}
|
||||
|
||||
return adapters.Command(fn, http.StatusOK)
|
||||
}
|
||||
|
||||
// HandleGroupUpdate godoc
|
||||
//
|
||||
// @Summary Update Group
|
||||
// @Tags Group
|
||||
// @Produce json
|
||||
// @Param payload body repo.GroupUpdate true "User Data"
|
||||
// @Success 200 {object} repo.Group
|
||||
// @Router /v1/groups [Put]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleGroupUpdate() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request, body repo.GroupUpdate) (repo.Group, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
|
||||
ok := ctrl.svc.Currencies.IsSupported(body.Currency)
|
||||
if !ok {
|
||||
return repo.Group{}, validate.NewFieldErrors(
|
||||
validate.NewFieldError("currency", "currency '"+body.Currency+"' is not supported"),
|
||||
)
|
||||
}
|
||||
|
||||
return ctrl.svc.Group.UpdateGroup(auth, body)
|
||||
}
|
||||
|
||||
return adapters.Action(fn, http.StatusOK)
|
||||
}
|
||||
|
||||
// HandleGroupInvitationsCreate godoc
|
||||
//
|
||||
// @Summary Create Group Invitation
|
||||
// @Tags Group
|
||||
// @Produce json
|
||||
// @Param payload body GroupInvitationCreate true "User Data"
|
||||
// @Success 200 {object} GroupInvitation
|
||||
// @Router /v1/groups/invitations [Post]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleGroupInvitationsCreate() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request, body GroupInvitationCreate) (GroupInvitation, error) {
|
||||
if body.ExpiresAt.IsZero() {
|
||||
body.ExpiresAt = time.Now().Add(time.Hour * 24)
|
||||
}
|
||||
|
||||
auth := services.NewContext(r.Context())
|
||||
|
||||
token, err := ctrl.svc.Group.NewInvitation(auth, body.Uses, body.ExpiresAt)
|
||||
|
||||
return GroupInvitation{
|
||||
Token: token,
|
||||
ExpiresAt: body.ExpiresAt,
|
||||
Uses: body.Uses,
|
||||
}, err
|
||||
}
|
||||
|
||||
return adapters.Action(fn, http.StatusCreated)
|
||||
}
|
339
backend/app/api/handlers/v1/v1_ctrl_items.go
Normal file
339
backend/app/api/handlers/v1/v1_ctrl_items.go
Normal file
|
@ -0,0 +1,339 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/csv"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/hay-kot/homebox/backend/internal/core/services"
|
||||
"github.com/hay-kot/homebox/backend/internal/data/repo"
|
||||
"github.com/hay-kot/homebox/backend/internal/sys/validate"
|
||||
"github.com/hay-kot/homebox/backend/internal/web/adapters"
|
||||
"github.com/hay-kot/httpkit/errchain"
|
||||
"github.com/hay-kot/httpkit/server"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// HandleItemsGetAll godoc
|
||||
//
|
||||
// @Summary Query All Items
|
||||
// @Tags Items
|
||||
// @Produce json
|
||||
// @Param q query string false "search string"
|
||||
// @Param page query int false "page number"
|
||||
// @Param pageSize query int false "items per page"
|
||||
// @Param labels query []string false "label Ids" collectionFormat(multi)
|
||||
// @Param locations query []string false "location Ids" collectionFormat(multi)
|
||||
// @Param parentIds query []string false "parent Ids" collectionFormat(multi)
|
||||
// @Success 200 {object} repo.PaginationResult[repo.ItemSummary]{}
|
||||
// @Router /v1/items [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemsGetAll() errchain.HandlerFunc {
|
||||
extractQuery := func(r *http.Request) repo.ItemQuery {
|
||||
params := r.URL.Query()
|
||||
|
||||
filterFieldItems := func(raw []string) []repo.FieldQuery {
|
||||
var items []repo.FieldQuery
|
||||
|
||||
for _, v := range raw {
|
||||
parts := strings.SplitN(v, "=", 2)
|
||||
if len(parts) == 2 {
|
||||
items = append(items, repo.FieldQuery{
|
||||
Name: parts[0],
|
||||
Value: parts[1],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
v := repo.ItemQuery{
|
||||
Page: queryIntOrNegativeOne(params.Get("page")),
|
||||
PageSize: queryIntOrNegativeOne(params.Get("pageSize")),
|
||||
Search: params.Get("q"),
|
||||
LocationIDs: queryUUIDList(params, "locations"),
|
||||
LabelIDs: queryUUIDList(params, "labels"),
|
||||
ParentItemIDs: queryUUIDList(params, "parentIds"),
|
||||
IncludeArchived: queryBool(params.Get("includeArchived")),
|
||||
Fields: filterFieldItems(params["fields"]),
|
||||
OrderBy: params.Get("orderBy"),
|
||||
}
|
||||
|
||||
if strings.HasPrefix(v.Search, "#") {
|
||||
aidStr := strings.TrimPrefix(v.Search, "#")
|
||||
|
||||
aid, ok := repo.ParseAssetID(aidStr)
|
||||
if ok {
|
||||
v.Search = ""
|
||||
v.AssetID = aid
|
||||
}
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) error {
|
||||
ctx := services.NewContext(r.Context())
|
||||
|
||||
items, err := ctrl.repo.Items.QueryByGroup(ctx, ctx.GID, extractQuery(r))
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return server.JSON(w, http.StatusOK, repo.PaginationResult[repo.ItemSummary]{
|
||||
Items: []repo.ItemSummary{},
|
||||
})
|
||||
}
|
||||
log.Err(err).Msg("failed to get items")
|
||||
return validate.NewRequestError(err, http.StatusInternalServerError)
|
||||
}
|
||||
return server.JSON(w, http.StatusOK, items)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleItemFullPath godoc
|
||||
//
|
||||
// @Summary Get the full path of an item
|
||||
// @Tags Items
|
||||
// @Produce json
|
||||
// @Param id path string true "Item ID"
|
||||
// @Success 200 {object} []repo.ItemPath
|
||||
// @Router /v1/items/{id}/path [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemFullPath() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request, ID uuid.UUID) ([]repo.ItemPath, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
item, err := ctrl.repo.Items.GetOneByGroup(auth, auth.GID, ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
paths, err := ctrl.repo.Locations.PathForLoc(auth, auth.GID, item.Location.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if item.Parent != nil {
|
||||
paths = append(paths, repo.ItemPath{
|
||||
Type: repo.ItemTypeItem,
|
||||
ID: item.Parent.ID,
|
||||
Name: item.Parent.Name,
|
||||
})
|
||||
}
|
||||
|
||||
paths = append(paths, repo.ItemPath{
|
||||
Type: repo.ItemTypeItem,
|
||||
ID: item.ID,
|
||||
Name: item.Name,
|
||||
})
|
||||
|
||||
return paths, nil
|
||||
}
|
||||
|
||||
return adapters.CommandID("id", fn, http.StatusOK)
|
||||
}
|
||||
|
||||
// HandleItemsCreate godoc
|
||||
//
|
||||
// @Summary Create Item
|
||||
// @Tags Items
|
||||
// @Produce json
|
||||
// @Param payload body repo.ItemCreate true "Item Data"
|
||||
// @Success 201 {object} repo.ItemSummary
|
||||
// @Router /v1/items [POST]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemsCreate() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request, body repo.ItemCreate) (repo.ItemOut, error) {
|
||||
return ctrl.svc.Items.Create(services.NewContext(r.Context()), body)
|
||||
}
|
||||
|
||||
return adapters.Action(fn, http.StatusCreated)
|
||||
}
|
||||
|
||||
// HandleItemGet godocs
|
||||
//
|
||||
// @Summary Get Item
|
||||
// @Tags Items
|
||||
// @Produce json
|
||||
// @Param id path string true "Item ID"
|
||||
// @Success 200 {object} repo.ItemOut
|
||||
// @Router /v1/items/{id} [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemGet() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request, ID uuid.UUID) (repo.ItemOut, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
|
||||
return ctrl.repo.Items.GetOneByGroup(auth, auth.GID, ID)
|
||||
}
|
||||
|
||||
return adapters.CommandID("id", fn, http.StatusOK)
|
||||
}
|
||||
|
||||
// HandleItemDelete godocs
|
||||
//
|
||||
// @Summary Delete Item
|
||||
// @Tags Items
|
||||
// @Produce json
|
||||
// @Param id path string true "Item ID"
|
||||
// @Success 204
|
||||
// @Router /v1/items/{id} [DELETE]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemDelete() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request, ID uuid.UUID) (any, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
err := ctrl.repo.Items.DeleteByGroup(auth, auth.GID, ID)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return adapters.CommandID("id", fn, http.StatusNoContent)
|
||||
}
|
||||
|
||||
// HandleItemUpdate godocs
|
||||
//
|
||||
// @Summary Update Item
|
||||
// @Tags Items
|
||||
// @Produce json
|
||||
// @Param id path string true "Item ID"
|
||||
// @Param payload body repo.ItemUpdate true "Item Data"
|
||||
// @Success 200 {object} repo.ItemOut
|
||||
// @Router /v1/items/{id} [PUT]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemUpdate() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request, ID uuid.UUID, body repo.ItemUpdate) (repo.ItemOut, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
|
||||
body.ID = ID
|
||||
return ctrl.repo.Items.UpdateByGroup(auth, auth.GID, body)
|
||||
}
|
||||
|
||||
return adapters.ActionID("id", fn, http.StatusOK)
|
||||
}
|
||||
|
||||
// HandleItemPatch godocs
|
||||
//
|
||||
// @Summary Update Item
|
||||
// @Tags Items
|
||||
// @Produce json
|
||||
// @Param id path string true "Item ID"
|
||||
// @Param payload body repo.ItemPatch true "Item Data"
|
||||
// @Success 200 {object} repo.ItemOut
|
||||
// @Router /v1/items/{id} [Patch]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemPatch() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request, ID uuid.UUID, body repo.ItemPatch) (repo.ItemOut, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
|
||||
body.ID = ID
|
||||
err := ctrl.repo.Items.Patch(auth, auth.GID, ID, body)
|
||||
if err != nil {
|
||||
return repo.ItemOut{}, err
|
||||
}
|
||||
|
||||
return ctrl.repo.Items.GetOneByGroup(auth, auth.GID, ID)
|
||||
}
|
||||
|
||||
return adapters.ActionID("id", fn, http.StatusOK)
|
||||
}
|
||||
|
||||
// HandleGetAllCustomFieldNames godocs
|
||||
//
|
||||
// @Summary Get All Custom Field Names
|
||||
// @Tags Items
|
||||
// @Produce json
|
||||
// @Success 200
|
||||
// @Router /v1/items/fields [GET]
|
||||
// @Success 200 {object} []string
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleGetAllCustomFieldNames() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request) ([]string, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
return ctrl.repo.Items.GetAllCustomFieldNames(auth, auth.GID)
|
||||
}
|
||||
|
||||
return adapters.Command(fn, http.StatusOK)
|
||||
}
|
||||
|
||||
// HandleGetAllCustomFieldValues godocs
|
||||
//
|
||||
// @Summary Get All Custom Field Values
|
||||
// @Tags Items
|
||||
// @Produce json
|
||||
// @Success 200
|
||||
// @Router /v1/items/fields/values [GET]
|
||||
// @Success 200 {object} []string
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleGetAllCustomFieldValues() errchain.HandlerFunc {
|
||||
type query struct {
|
||||
Field string `schema:"field" validate:"required"`
|
||||
}
|
||||
|
||||
fn := func(r *http.Request, q query) ([]string, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
return ctrl.repo.Items.GetAllCustomFieldValues(auth, auth.GID, q.Field)
|
||||
}
|
||||
|
||||
return adapters.Query(fn, http.StatusOK)
|
||||
}
|
||||
|
||||
// HandleItemsImport godocs
|
||||
//
|
||||
// @Summary Import Items
|
||||
// @Tags Items
|
||||
// @Produce json
|
||||
// @Success 204
|
||||
// @Param csv formData file true "Image to upload"
|
||||
// @Router /v1/items/import [Post]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemsImport() errchain.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) error {
|
||||
err := r.ParseMultipartForm(ctrl.maxUploadSize << 20)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to parse multipart form")
|
||||
return validate.NewRequestError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
file, _, err := r.FormFile("csv")
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to get file from form")
|
||||
return validate.NewRequestError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
user := services.UseUserCtx(r.Context())
|
||||
|
||||
_, err = ctrl.svc.Items.CsvImport(r.Context(), user.GroupID, file)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to import items")
|
||||
return validate.NewRequestError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return server.JSON(w, http.StatusNoContent, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleItemsExport godocs
|
||||
//
|
||||
// @Summary Export Items
|
||||
// @Tags Items
|
||||
// @Success 200 {string} string "text/csv"
|
||||
// @Router /v1/items/export [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemsExport() errchain.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) error {
|
||||
ctx := services.NewContext(r.Context())
|
||||
|
||||
csvData, err := ctrl.svc.Items.ExportTSV(r.Context(), ctx.GID)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to export items")
|
||||
return validate.NewRequestError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/tsv")
|
||||
w.Header().Set("Content-Disposition", "attachment;filename=homebox-items.tsv")
|
||||
|
||||
writer := csv.NewWriter(w)
|
||||
writer.Comma = '\t'
|
||||
return writer.WriteAll(csvData)
|
||||
}
|
||||
}
|
199
backend/app/api/handlers/v1/v1_ctrl_items_attachments.go
Normal file
199
backend/app/api/handlers/v1/v1_ctrl_items_attachments.go
Normal file
|
@ -0,0 +1,199 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/hay-kot/homebox/backend/internal/core/services"
|
||||
"github.com/hay-kot/homebox/backend/internal/data/ent/attachment"
|
||||
"github.com/hay-kot/homebox/backend/internal/data/repo"
|
||||
"github.com/hay-kot/homebox/backend/internal/sys/validate"
|
||||
"github.com/hay-kot/httpkit/errchain"
|
||||
"github.com/hay-kot/httpkit/server"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type (
|
||||
ItemAttachmentToken struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
)
|
||||
|
||||
// HandleItemAttachmentCreate godocs
|
||||
//
|
||||
// @Summary Create Item Attachment
|
||||
// @Tags Items Attachments
|
||||
// @Produce json
|
||||
// @Param id path string true "Item ID"
|
||||
// @Param file formData file true "File attachment"
|
||||
// @Param type formData string true "Type of file"
|
||||
// @Param name formData string true "name of the file including extension"
|
||||
// @Success 200 {object} repo.ItemOut
|
||||
// @Failure 422 {object} validate.ErrorResponse
|
||||
// @Router /v1/items/{id}/attachments [POST]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemAttachmentCreate() errchain.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) error {
|
||||
err := r.ParseMultipartForm(ctrl.maxUploadSize << 20)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to parse multipart form")
|
||||
return validate.NewRequestError(errors.New("failed to parse multipart form"), http.StatusBadRequest)
|
||||
}
|
||||
|
||||
errs := validate.NewFieldErrors()
|
||||
|
||||
file, _, err := r.FormFile("file")
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, http.ErrMissingFile):
|
||||
log.Debug().Msg("file for attachment is missing")
|
||||
errs = errs.Append("file", "file is required")
|
||||
default:
|
||||
log.Err(err).Msg("failed to get file from form")
|
||||
return validate.NewRequestError(err, http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
attachmentName := r.FormValue("name")
|
||||
if attachmentName == "" {
|
||||
log.Debug().Msg("failed to get name from form")
|
||||
errs = errs.Append("name", "name is required")
|
||||
}
|
||||
|
||||
if !errs.Nil() {
|
||||
return server.JSON(w, http.StatusUnprocessableEntity, errs)
|
||||
}
|
||||
|
||||
attachmentType := r.FormValue("type")
|
||||
if attachmentType == "" {
|
||||
// Attempt to auto-detect the type of the file
|
||||
ext := filepath.Ext(attachmentName)
|
||||
|
||||
switch strings.ToLower(ext) {
|
||||
case ".jpg", ".jpeg", ".png", ".webp", ".gif", ".bmp", ".tiff":
|
||||
attachmentType = attachment.TypePhoto.String()
|
||||
default:
|
||||
attachmentType = attachment.TypeAttachment.String()
|
||||
}
|
||||
}
|
||||
|
||||
id, err := ctrl.routeID(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := services.NewContext(r.Context())
|
||||
|
||||
item, err := ctrl.svc.Items.AttachmentAdd(
|
||||
ctx,
|
||||
id,
|
||||
attachmentName,
|
||||
attachment.Type(attachmentType),
|
||||
file,
|
||||
)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to add attachment")
|
||||
return validate.NewRequestError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return server.JSON(w, http.StatusCreated, item)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleItemAttachmentGet godocs
|
||||
//
|
||||
// @Summary Get Item Attachment
|
||||
// @Tags Items Attachments
|
||||
// @Produce application/octet-stream
|
||||
// @Param id path string true "Item ID"
|
||||
// @Param attachment_id path string true "Attachment ID"
|
||||
// @Success 200 {object} ItemAttachmentToken
|
||||
// @Router /v1/items/{id}/attachments/{attachment_id} [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemAttachmentGet() errchain.HandlerFunc {
|
||||
return ctrl.handleItemAttachmentsHandler
|
||||
}
|
||||
|
||||
// HandleItemAttachmentDelete godocs
|
||||
//
|
||||
// @Summary Delete Item Attachment
|
||||
// @Tags Items Attachments
|
||||
// @Param id path string true "Item ID"
|
||||
// @Param attachment_id path string true "Attachment ID"
|
||||
// @Success 204
|
||||
// @Router /v1/items/{id}/attachments/{attachment_id} [DELETE]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemAttachmentDelete() errchain.HandlerFunc {
|
||||
return ctrl.handleItemAttachmentsHandler
|
||||
}
|
||||
|
||||
// HandleItemAttachmentUpdate godocs
|
||||
//
|
||||
// @Summary Update Item Attachment
|
||||
// @Tags Items Attachments
|
||||
// @Param id path string true "Item ID"
|
||||
// @Param attachment_id path string true "Attachment ID"
|
||||
// @Param payload body repo.ItemAttachmentUpdate true "Attachment Update"
|
||||
// @Success 200 {object} repo.ItemOut
|
||||
// @Router /v1/items/{id}/attachments/{attachment_id} [PUT]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemAttachmentUpdate() errchain.HandlerFunc {
|
||||
return ctrl.handleItemAttachmentsHandler
|
||||
}
|
||||
|
||||
func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r *http.Request) error {
|
||||
ID, err := ctrl.routeID(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
attachmentID, err := ctrl.routeUUID(r, "attachment_id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := services.NewContext(r.Context())
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
doc, err := ctrl.svc.Items.AttachmentPath(r.Context(), attachmentID)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to get attachment path")
|
||||
return validate.NewRequestError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
http.ServeFile(w, r, doc.Path)
|
||||
return nil
|
||||
|
||||
// Delete Attachment Handler
|
||||
case http.MethodDelete:
|
||||
err = ctrl.svc.Items.AttachmentDelete(r.Context(), ctx.GID, ID, attachmentID)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to delete attachment")
|
||||
return validate.NewRequestError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return server.JSON(w, http.StatusNoContent, nil)
|
||||
|
||||
// Update Attachment Handler
|
||||
case http.MethodPut:
|
||||
var attachment repo.ItemAttachmentUpdate
|
||||
err = server.Decode(r, &attachment)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to decode attachment")
|
||||
return validate.NewRequestError(err, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
attachment.ID = attachmentID
|
||||
val, err := ctrl.svc.Items.AttachmentUpdate(ctx, ID, &attachment)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to delete attachment")
|
||||
return validate.NewRequestError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return server.JSON(w, http.StatusOK, val)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
102
backend/app/api/handlers/v1/v1_ctrl_labels.go
Normal file
102
backend/app/api/handlers/v1/v1_ctrl_labels.go
Normal file
|
@ -0,0 +1,102 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/hay-kot/homebox/backend/internal/core/services"
|
||||
"github.com/hay-kot/homebox/backend/internal/data/repo"
|
||||
"github.com/hay-kot/homebox/backend/internal/web/adapters"
|
||||
"github.com/hay-kot/httpkit/errchain"
|
||||
)
|
||||
|
||||
// HandleLabelsGetAll godoc
|
||||
//
|
||||
// @Summary Get All Labels
|
||||
// @Tags Labels
|
||||
// @Produce json
|
||||
// @Success 200 {object} []repo.LabelOut
|
||||
// @Router /v1/labels [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleLabelsGetAll() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request) ([]repo.LabelSummary, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
return ctrl.repo.Labels.GetAll(auth, auth.GID)
|
||||
}
|
||||
|
||||
return adapters.Command(fn, http.StatusOK)
|
||||
}
|
||||
|
||||
// HandleLabelsCreate godoc
|
||||
//
|
||||
// @Summary Create Label
|
||||
// @Tags Labels
|
||||
// @Produce json
|
||||
// @Param payload body repo.LabelCreate true "Label Data"
|
||||
// @Success 200 {object} repo.LabelSummary
|
||||
// @Router /v1/labels [POST]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleLabelsCreate() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request, data repo.LabelCreate) (repo.LabelOut, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
return ctrl.repo.Labels.Create(auth, auth.GID, data)
|
||||
}
|
||||
|
||||
return adapters.Action(fn, http.StatusCreated)
|
||||
}
|
||||
|
||||
// HandleLabelDelete godocs
|
||||
//
|
||||
// @Summary Delete Label
|
||||
// @Tags Labels
|
||||
// @Produce json
|
||||
// @Param id path string true "Label ID"
|
||||
// @Success 204
|
||||
// @Router /v1/labels/{id} [DELETE]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleLabelDelete() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request, ID uuid.UUID) (any, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
err := ctrl.repo.Labels.DeleteByGroup(auth, auth.GID, ID)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return adapters.CommandID("id", fn, http.StatusNoContent)
|
||||
}
|
||||
|
||||
// HandleLabelGet godocs
|
||||
//
|
||||
// @Summary Get Label
|
||||
// @Tags Labels
|
||||
// @Produce json
|
||||
// @Param id path string true "Label ID"
|
||||
// @Success 200 {object} repo.LabelOut
|
||||
// @Router /v1/labels/{id} [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleLabelGet() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request, ID uuid.UUID) (repo.LabelOut, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
return ctrl.repo.Labels.GetOneByGroup(auth, auth.GID, ID)
|
||||
}
|
||||
|
||||
return adapters.CommandID("id", fn, http.StatusOK)
|
||||
}
|
||||
|
||||
// HandleLabelUpdate godocs
|
||||
//
|
||||
// @Summary Update Label
|
||||
// @Tags Labels
|
||||
// @Produce json
|
||||
// @Param id path string true "Label ID"
|
||||
// @Success 200 {object} repo.LabelOut
|
||||
// @Router /v1/labels/{id} [PUT]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleLabelUpdate() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request, ID uuid.UUID, data repo.LabelUpdate) (repo.LabelOut, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
data.ID = ID
|
||||
return ctrl.repo.Labels.UpdateByGroup(auth, auth.GID, data)
|
||||
}
|
||||
|
||||
return adapters.ActionID("id", fn, http.StatusOK)
|
||||
}
|
122
backend/app/api/handlers/v1/v1_ctrl_locations.go
Normal file
122
backend/app/api/handlers/v1/v1_ctrl_locations.go
Normal file
|
@ -0,0 +1,122 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/hay-kot/homebox/backend/internal/core/services"
|
||||
"github.com/hay-kot/homebox/backend/internal/data/repo"
|
||||
"github.com/hay-kot/homebox/backend/internal/web/adapters"
|
||||
"github.com/hay-kot/httpkit/errchain"
|
||||
)
|
||||
|
||||
// HandleLocationTreeQuery godoc
|
||||
//
|
||||
// @Summary Get Locations Tree
|
||||
// @Tags Locations
|
||||
// @Produce json
|
||||
// @Param withItems query bool false "include items in response tree"
|
||||
// @Success 200 {object} []repo.TreeItem
|
||||
// @Router /v1/locations/tree [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleLocationTreeQuery() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request, query repo.TreeQuery) ([]repo.TreeItem, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
return ctrl.repo.Locations.Tree(auth, auth.GID, query)
|
||||
}
|
||||
|
||||
return adapters.Query(fn, http.StatusOK)
|
||||
}
|
||||
|
||||
// HandleLocationGetAll godoc
|
||||
//
|
||||
// @Summary Get All Locations
|
||||
// @Tags Locations
|
||||
// @Produce json
|
||||
// @Param filterChildren query bool false "Filter locations with parents"
|
||||
// @Success 200 {object} []repo.LocationOutCount
|
||||
// @Router /v1/locations [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleLocationGetAll() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request, q repo.LocationQuery) ([]repo.LocationOutCount, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
return ctrl.repo.Locations.GetAll(auth, auth.GID, q)
|
||||
}
|
||||
|
||||
return adapters.Query(fn, http.StatusOK)
|
||||
}
|
||||
|
||||
// HandleLocationCreate godoc
|
||||
//
|
||||
// @Summary Create Location
|
||||
// @Tags Locations
|
||||
// @Produce json
|
||||
// @Param payload body repo.LocationCreate true "Location Data"
|
||||
// @Success 200 {object} repo.LocationSummary
|
||||
// @Router /v1/locations [POST]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleLocationCreate() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request, createData repo.LocationCreate) (repo.LocationOut, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
return ctrl.repo.Locations.Create(auth, auth.GID, createData)
|
||||
}
|
||||
|
||||
return adapters.Action(fn, http.StatusCreated)
|
||||
}
|
||||
|
||||
// HandleLocationDelete godoc
|
||||
//
|
||||
// @Summary Delete Location
|
||||
// @Tags Locations
|
||||
// @Produce json
|
||||
// @Param id path string true "Location ID"
|
||||
// @Success 204
|
||||
// @Router /v1/locations/{id} [DELETE]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleLocationDelete() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request, ID uuid.UUID) (any, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
err := ctrl.repo.Locations.DeleteByGroup(auth, auth.GID, ID)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return adapters.CommandID("id", fn, http.StatusNoContent)
|
||||
}
|
||||
|
||||
// HandleLocationGet godoc
|
||||
//
|
||||
// @Summary Get Location
|
||||
// @Tags Locations
|
||||
// @Produce json
|
||||
// @Param id path string true "Location ID"
|
||||
// @Success 200 {object} repo.LocationOut
|
||||
// @Router /v1/locations/{id} [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleLocationGet() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request, ID uuid.UUID) (repo.LocationOut, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
return ctrl.repo.Locations.GetOneByGroup(auth, auth.GID, ID)
|
||||
}
|
||||
|
||||
return adapters.CommandID("id", fn, http.StatusOK)
|
||||
}
|
||||
|
||||
// HandleLocationUpdate godoc
|
||||
//
|
||||
// @Summary Update Location
|
||||
// @Tags Locations
|
||||
// @Produce json
|
||||
// @Param id path string true "Location ID"
|
||||
// @Param payload body repo.LocationUpdate true "Location Data"
|
||||
// @Success 200 {object} repo.LocationOut
|
||||
// @Router /v1/locations/{id} [PUT]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleLocationUpdate() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request, ID uuid.UUID, body repo.LocationUpdate) (repo.LocationOut, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
body.ID = ID
|
||||
return ctrl.repo.Locations.UpdateByGroup(auth, auth.GID, ID, body)
|
||||
}
|
||||
|
||||
return adapters.ActionID("id", fn, http.StatusOK)
|
||||
}
|
82
backend/app/api/handlers/v1/v1_ctrl_maint_entry.go
Normal file
82
backend/app/api/handlers/v1/v1_ctrl_maint_entry.go
Normal file
|
@ -0,0 +1,82 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/hay-kot/homebox/backend/internal/core/services"
|
||||
"github.com/hay-kot/homebox/backend/internal/data/repo"
|
||||
"github.com/hay-kot/homebox/backend/internal/web/adapters"
|
||||
"github.com/hay-kot/httpkit/errchain"
|
||||
)
|
||||
|
||||
// HandleMaintenanceLogGet godoc
|
||||
//
|
||||
// @Summary Get Maintenance Log
|
||||
// @Tags Maintenance
|
||||
// @Produce json
|
||||
// @Success 200 {object} repo.MaintenanceLog
|
||||
// @Router /v1/items/{id}/maintenance [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleMaintenanceLogGet() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request, ID uuid.UUID, q repo.MaintenanceLogQuery) (repo.MaintenanceLog, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
return ctrl.repo.MaintEntry.GetLog(auth, auth.GID, ID, q)
|
||||
}
|
||||
|
||||
return adapters.QueryID("id", fn, http.StatusOK)
|
||||
}
|
||||
|
||||
// HandleMaintenanceEntryCreate godoc
|
||||
//
|
||||
// @Summary Create Maintenance Entry
|
||||
// @Tags Maintenance
|
||||
// @Produce json
|
||||
// @Param payload body repo.MaintenanceEntryCreate true "Entry Data"
|
||||
// @Success 201 {object} repo.MaintenanceEntry
|
||||
// @Router /v1/items/{id}/maintenance [POST]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleMaintenanceEntryCreate() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request, itemID uuid.UUID, body repo.MaintenanceEntryCreate) (repo.MaintenanceEntry, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
return ctrl.repo.MaintEntry.Create(auth, itemID, body)
|
||||
}
|
||||
|
||||
return adapters.ActionID("id", fn, http.StatusCreated)
|
||||
}
|
||||
|
||||
// HandleMaintenanceEntryDelete godoc
|
||||
//
|
||||
// @Summary Delete Maintenance Entry
|
||||
// @Tags Maintenance
|
||||
// @Produce json
|
||||
// @Success 204
|
||||
// @Router /v1/items/{id}/maintenance/{entry_id} [DELETE]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleMaintenanceEntryDelete() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request, entryID uuid.UUID) (any, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
err := ctrl.repo.MaintEntry.Delete(auth, entryID)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return adapters.CommandID("entry_id", fn, http.StatusNoContent)
|
||||
}
|
||||
|
||||
// HandleMaintenanceEntryUpdate godoc
|
||||
//
|
||||
// @Summary Update Maintenance Entry
|
||||
// @Tags Maintenance
|
||||
// @Produce json
|
||||
// @Param payload body repo.MaintenanceEntryUpdate true "Entry Data"
|
||||
// @Success 200 {object} repo.MaintenanceEntry
|
||||
// @Router /v1/items/{id}/maintenance/{entry_id} [PUT]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleMaintenanceEntryUpdate() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request, entryID uuid.UUID, body repo.MaintenanceEntryUpdate) (repo.MaintenanceEntry, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
return ctrl.repo.MaintEntry.Update(auth, entryID, body)
|
||||
}
|
||||
|
||||
return adapters.ActionID("entry_id", fn, http.StatusOK)
|
||||
}
|
105
backend/app/api/handlers/v1/v1_ctrl_notifiers.go
Normal file
105
backend/app/api/handlers/v1/v1_ctrl_notifiers.go
Normal file
|
@ -0,0 +1,105 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/containrrr/shoutrrr"
|
||||
"github.com/google/uuid"
|
||||
"github.com/hay-kot/homebox/backend/internal/core/services"
|
||||
"github.com/hay-kot/homebox/backend/internal/data/repo"
|
||||
"github.com/hay-kot/homebox/backend/internal/web/adapters"
|
||||
"github.com/hay-kot/httpkit/errchain"
|
||||
)
|
||||
|
||||
// HandleGetUserNotifiers godoc
|
||||
//
|
||||
// @Summary Get Notifiers
|
||||
// @Tags Notifiers
|
||||
// @Produce json
|
||||
// @Success 200 {object} []repo.NotifierOut
|
||||
// @Router /v1/notifiers [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleGetUserNotifiers() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request, _ struct{}) ([]repo.NotifierOut, error) {
|
||||
user := services.UseUserCtx(r.Context())
|
||||
return ctrl.repo.Notifiers.GetByUser(r.Context(), user.ID)
|
||||
}
|
||||
|
||||
return adapters.Query(fn, http.StatusOK)
|
||||
}
|
||||
|
||||
// HandleCreateNotifier godoc
|
||||
//
|
||||
// @Summary Create Notifier
|
||||
// @Tags Notifiers
|
||||
// @Produce json
|
||||
// @Param payload body repo.NotifierCreate true "Notifier Data"
|
||||
// @Success 200 {object} repo.NotifierOut
|
||||
// @Router /v1/notifiers [POST]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleCreateNotifier() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request, in repo.NotifierCreate) (repo.NotifierOut, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
return ctrl.repo.Notifiers.Create(auth, auth.GID, auth.UID, in)
|
||||
}
|
||||
|
||||
return adapters.Action(fn, http.StatusCreated)
|
||||
}
|
||||
|
||||
// HandleDeleteNotifier godocs
|
||||
//
|
||||
// @Summary Delete a Notifier
|
||||
// @Tags Notifiers
|
||||
// @Param id path string true "Notifier ID"
|
||||
// @Success 204
|
||||
// @Router /v1/notifiers/{id} [DELETE]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleDeleteNotifier() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request, ID uuid.UUID) (any, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
return nil, ctrl.repo.Notifiers.Delete(auth, auth.UID, ID)
|
||||
}
|
||||
|
||||
return adapters.CommandID("id", fn, http.StatusNoContent)
|
||||
}
|
||||
|
||||
// HandleUpdateNotifier godocs
|
||||
//
|
||||
// @Summary Update Notifier
|
||||
// @Tags Notifiers
|
||||
// @Param id path string true "Notifier ID"
|
||||
// @Param payload body repo.NotifierUpdate true "Notifier Data"
|
||||
// @Success 200 {object} repo.NotifierOut
|
||||
// @Router /v1/notifiers/{id} [PUT]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleUpdateNotifier() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request, ID uuid.UUID, in repo.NotifierUpdate) (repo.NotifierOut, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
return ctrl.repo.Notifiers.Update(auth, auth.UID, ID, in)
|
||||
}
|
||||
|
||||
return adapters.ActionID("id", fn, http.StatusOK)
|
||||
}
|
||||
|
||||
// HandlerNotifierTest godoc
|
||||
//
|
||||
// @Summary Test Notifier
|
||||
// @Tags Notifiers
|
||||
// @Produce json
|
||||
// @Param id path string true "Notifier ID"
|
||||
// @Param url query string true "URL"
|
||||
// @Success 204
|
||||
// @Router /v1/notifiers/test [POST]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandlerNotifierTest() errchain.HandlerFunc {
|
||||
type body struct {
|
||||
URL string `json:"url" validate:"required"`
|
||||
}
|
||||
|
||||
fn := func(r *http.Request, q body) (any, error) {
|
||||
err := shoutrrr.Send(q.URL, "Test message from Homebox")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return adapters.Action(fn, http.StatusOK)
|
||||
}
|
72
backend/app/api/handlers/v1/v1_ctrl_qrcode.go
Normal file
72
backend/app/api/handlers/v1/v1_ctrl_qrcode.go
Normal file
|
@ -0,0 +1,72 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"image/png"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/hay-kot/homebox/backend/internal/web/adapters"
|
||||
"github.com/hay-kot/httpkit/errchain"
|
||||
"github.com/yeqown/go-qrcode/v2"
|
||||
"github.com/yeqown/go-qrcode/writer/standard"
|
||||
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed assets/QRIcon.png
|
||||
var qrcodeLogo []byte
|
||||
|
||||
// HandleGenerateQRCode godoc
|
||||
//
|
||||
// @Summary Create QR Code
|
||||
// @Tags Items
|
||||
// @Produce json
|
||||
// @Param data query string false "data to be encoded into qrcode"
|
||||
// @Success 200 {string} string "image/jpeg"
|
||||
// @Router /v1/qrcode [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleGenerateQRCode() errchain.HandlerFunc {
|
||||
type query struct {
|
||||
// 4,296 characters is the maximum length of a QR code
|
||||
Data string `schema:"data" validate:"required,max=4296"`
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) error {
|
||||
q, err := adapters.DecodeQuery[query](r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
image, err := png.Decode(bytes.NewReader(qrcodeLogo))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
decodedStr, err := url.QueryUnescape(q.Data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
qrc, err := qrcode.New(decodedStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
toWriteCloser := struct {
|
||||
io.Writer
|
||||
io.Closer
|
||||
}{
|
||||
Writer: w,
|
||||
Closer: io.NopCloser(nil),
|
||||
}
|
||||
|
||||
qrwriter := standard.NewWithWriter(toWriteCloser, standard.WithLogoImage(image))
|
||||
|
||||
// Return the QR code as a jpeg image
|
||||
w.Header().Set("Content-Type", "image/jpeg")
|
||||
w.Header().Set("Content-Disposition", "attachment; filename=qrcode.jpg")
|
||||
return qrc.Save(qrwriter)
|
||||
}
|
||||
}
|
32
backend/app/api/handlers/v1/v1_ctrl_reporting.go
Normal file
32
backend/app/api/handlers/v1/v1_ctrl_reporting.go
Normal file
|
@ -0,0 +1,32 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/hay-kot/homebox/backend/internal/core/services"
|
||||
"github.com/hay-kot/httpkit/errchain"
|
||||
)
|
||||
|
||||
// HandleBillOfMaterialsExport godoc
|
||||
//
|
||||
// @Summary Export Bill of Materials
|
||||
// @Tags Reporting
|
||||
// @Produce json
|
||||
// @Success 200 {string} string "text/csv"
|
||||
// @Router /v1/reporting/bill-of-materials [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleBillOfMaterialsExport() errchain.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) error {
|
||||
actor := services.UseUserCtx(r.Context())
|
||||
|
||||
csv, err := ctrl.svc.Items.ExportBillOfMaterialsTSV(r.Context(), actor.GroupID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/tsv")
|
||||
w.Header().Set("Content-Disposition", "attachment; filename=bill-of-materials.tsv")
|
||||
_, err = w.Write(csv)
|
||||
return err
|
||||
}
|
||||
}
|
104
backend/app/api/handlers/v1/v1_ctrl_statistics.go
Normal file
104
backend/app/api/handlers/v1/v1_ctrl_statistics.go
Normal file
|
@ -0,0 +1,104 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/hay-kot/homebox/backend/internal/core/services"
|
||||
"github.com/hay-kot/homebox/backend/internal/data/repo"
|
||||
"github.com/hay-kot/homebox/backend/internal/sys/validate"
|
||||
"github.com/hay-kot/homebox/backend/internal/web/adapters"
|
||||
"github.com/hay-kot/httpkit/errchain"
|
||||
"github.com/hay-kot/httpkit/server"
|
||||
)
|
||||
|
||||
// HandleGroupStatisticsLocations godoc
|
||||
//
|
||||
// @Summary Get Location Statistics
|
||||
// @Tags Statistics
|
||||
// @Produce json
|
||||
// @Success 200 {object} []repo.TotalsByOrganizer
|
||||
// @Router /v1/groups/statistics/locations [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleGroupStatisticsLocations() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request) ([]repo.TotalsByOrganizer, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
return ctrl.repo.Groups.StatsLocationsByPurchasePrice(auth, auth.GID)
|
||||
}
|
||||
|
||||
return adapters.Command(fn, http.StatusOK)
|
||||
}
|
||||
|
||||
// HandleGroupStatisticsLabels godoc
|
||||
//
|
||||
// @Summary Get Label Statistics
|
||||
// @Tags Statistics
|
||||
// @Produce json
|
||||
// @Success 200 {object} []repo.TotalsByOrganizer
|
||||
// @Router /v1/groups/statistics/labels [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleGroupStatisticsLabels() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request) ([]repo.TotalsByOrganizer, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
return ctrl.repo.Groups.StatsLabelsByPurchasePrice(auth, auth.GID)
|
||||
}
|
||||
|
||||
return adapters.Command(fn, http.StatusOK)
|
||||
}
|
||||
|
||||
// HandleGroupStatistics godoc
|
||||
//
|
||||
// @Summary Get Group Statistics
|
||||
// @Tags Statistics
|
||||
// @Produce json
|
||||
// @Success 200 {object} repo.GroupStatistics
|
||||
// @Router /v1/groups/statistics [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleGroupStatistics() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request) (repo.GroupStatistics, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
return ctrl.repo.Groups.StatsGroup(auth, auth.GID)
|
||||
}
|
||||
|
||||
return adapters.Command(fn, http.StatusOK)
|
||||
}
|
||||
|
||||
// HandleGroupStatisticsPriceOverTime godoc
|
||||
//
|
||||
// @Summary Get Purchase Price Statistics
|
||||
// @Tags Statistics
|
||||
// @Produce json
|
||||
// @Success 200 {object} repo.ValueOverTime
|
||||
// @Param start query string false "start date"
|
||||
// @Param end query string false "end date"
|
||||
// @Router /v1/groups/statistics/purchase-price [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleGroupStatisticsPriceOverTime() errchain.HandlerFunc {
|
||||
parseDate := func(datestr string, defaultDate time.Time) (time.Time, error) {
|
||||
if datestr == "" {
|
||||
return defaultDate, nil
|
||||
}
|
||||
return time.Parse("2006-01-02", datestr)
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) error {
|
||||
ctx := services.NewContext(r.Context())
|
||||
|
||||
startDate, err := parseDate(r.URL.Query().Get("start"), time.Now().AddDate(0, -1, 0))
|
||||
if err != nil {
|
||||
return validate.NewRequestError(err, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
endDate, err := parseDate(r.URL.Query().Get("end"), time.Now())
|
||||
if err != nil {
|
||||
return validate.NewRequestError(err, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
stats, err := ctrl.repo.Groups.StatsPurchasePrice(ctx, ctx.GID, startDate, endDate)
|
||||
if err != nil {
|
||||
return validate.NewRequestError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return server.JSON(w, http.StatusOK, stats)
|
||||
}
|
||||
}
|
154
backend/app/api/handlers/v1/v1_ctrl_user.go
Normal file
154
backend/app/api/handlers/v1/v1_ctrl_user.go
Normal file
|
@ -0,0 +1,154 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/hay-kot/homebox/backend/internal/core/services"
|
||||
"github.com/hay-kot/homebox/backend/internal/data/repo"
|
||||
"github.com/hay-kot/homebox/backend/internal/sys/validate"
|
||||
"github.com/hay-kot/httpkit/errchain"
|
||||
"github.com/hay-kot/httpkit/server"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// HandleUserRegistration godoc
|
||||
//
|
||||
// @Summary Register New User
|
||||
// @Tags User
|
||||
// @Produce json
|
||||
// @Param payload body services.UserRegistration true "User Data"
|
||||
// @Success 204
|
||||
// @Router /v1/users/register [Post]
|
||||
func (ctrl *V1Controller) HandleUserRegistration() errchain.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) error {
|
||||
regData := services.UserRegistration{}
|
||||
|
||||
if err := server.Decode(r, ®Data); err != nil {
|
||||
log.Err(err).Msg("failed to decode user registration data")
|
||||
return validate.NewRequestError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
if !ctrl.allowRegistration && regData.GroupToken == "" {
|
||||
return validate.NewRequestError(fmt.Errorf("user registration disabled"), http.StatusForbidden)
|
||||
}
|
||||
|
||||
_, err := ctrl.svc.User.RegisterUser(r.Context(), regData)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to register user")
|
||||
return validate.NewRequestError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return server.JSON(w, http.StatusNoContent, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleUserSelf godoc
|
||||
//
|
||||
// @Summary Get User Self
|
||||
// @Tags User
|
||||
// @Produce json
|
||||
// @Success 200 {object} Wrapped{item=repo.UserOut}
|
||||
// @Router /v1/users/self [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleUserSelf() errchain.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) error {
|
||||
token := services.UseTokenCtx(r.Context())
|
||||
usr, err := ctrl.svc.User.GetSelf(r.Context(), token)
|
||||
if usr.ID == uuid.Nil || err != nil {
|
||||
log.Err(err).Msg("failed to get user")
|
||||
return validate.NewRequestError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return server.JSON(w, http.StatusOK, Wrap(usr))
|
||||
}
|
||||
}
|
||||
|
||||
// HandleUserSelfUpdate godoc
|
||||
//
|
||||
// @Summary Update Account
|
||||
// @Tags User
|
||||
// @Produce json
|
||||
// @Param payload body repo.UserUpdate true "User Data"
|
||||
// @Success 200 {object} Wrapped{item=repo.UserUpdate}
|
||||
// @Router /v1/users/self [PUT]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleUserSelfUpdate() errchain.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) error {
|
||||
updateData := repo.UserUpdate{}
|
||||
if err := server.Decode(r, &updateData); err != nil {
|
||||
log.Err(err).Msg("failed to decode user update data")
|
||||
return validate.NewRequestError(err, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
actor := services.UseUserCtx(r.Context())
|
||||
newData, err := ctrl.svc.User.UpdateSelf(r.Context(), actor.ID, updateData)
|
||||
if err != nil {
|
||||
return validate.NewRequestError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return server.JSON(w, http.StatusOK, Wrap(newData))
|
||||
}
|
||||
}
|
||||
|
||||
// HandleUserSelfDelete godoc
|
||||
//
|
||||
// @Summary Delete Account
|
||||
// @Tags User
|
||||
// @Produce json
|
||||
// @Success 204
|
||||
// @Router /v1/users/self [DELETE]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleUserSelfDelete() errchain.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) error {
|
||||
if ctrl.isDemo {
|
||||
return validate.NewRequestError(nil, http.StatusForbidden)
|
||||
}
|
||||
|
||||
actor := services.UseUserCtx(r.Context())
|
||||
if err := ctrl.svc.User.DeleteSelf(r.Context(), actor.ID); err != nil {
|
||||
return validate.NewRequestError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return server.JSON(w, http.StatusNoContent, nil)
|
||||
}
|
||||
}
|
||||
|
||||
type (
|
||||
ChangePassword struct {
|
||||
Current string `json:"current,omitempty"`
|
||||
New string `json:"new,omitempty"`
|
||||
}
|
||||
)
|
||||
|
||||
// HandleUserSelfChangePassword godoc
|
||||
//
|
||||
// @Summary Change Password
|
||||
// @Tags User
|
||||
// @Success 204
|
||||
// @Param payload body ChangePassword true "Password Payload"
|
||||
// @Router /v1/users/change-password [PUT]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleUserSelfChangePassword() errchain.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) error {
|
||||
if ctrl.isDemo {
|
||||
return validate.NewRequestError(nil, http.StatusForbidden)
|
||||
}
|
||||
|
||||
var cp ChangePassword
|
||||
err := server.Decode(r, &cp)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("user failed to change password")
|
||||
}
|
||||
|
||||
ctx := services.NewContext(r.Context())
|
||||
|
||||
ok := ctrl.svc.User.ChangePassword(ctx, cp.Current, cp.New)
|
||||
if !ok {
|
||||
return validate.NewRequestError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return server.JSON(w, http.StatusNoContent, nil)
|
||||
}
|
||||
}
|
|
@ -2,9 +2,8 @@ package main
|
|||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/hay-kot/homebox/backend/internal/config"
|
||||
"github.com/hay-kot/homebox/backend/internal/sys/config"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
@ -15,27 +14,11 @@ func (a *app) setupLogger() {
|
|||
// Logger Init
|
||||
// zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
|
||||
if a.conf.Log.Format != config.LogFormatJSON {
|
||||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
||||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).With().Caller().Logger()
|
||||
}
|
||||
|
||||
log.Level(getLevel(a.conf.Log.Level))
|
||||
}
|
||||
|
||||
func getLevel(l string) zerolog.Level {
|
||||
switch strings.ToLower(l) {
|
||||
case "debug":
|
||||
return zerolog.DebugLevel
|
||||
case "info":
|
||||
return zerolog.InfoLevel
|
||||
case "warn":
|
||||
return zerolog.WarnLevel
|
||||
case "error":
|
||||
return zerolog.ErrorLevel
|
||||
case "fatal":
|
||||
return zerolog.FatalLevel
|
||||
case "panic":
|
||||
return zerolog.PanicLevel
|
||||
default:
|
||||
return zerolog.InfoLevel
|
||||
level, err := zerolog.ParseLevel(a.conf.Log.Level)
|
||||
if err == nil {
|
||||
zerolog.SetGlobalLevel(level)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,48 +1,68 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
atlas "ariga.io/atlas/sql/migrate"
|
||||
"entgo.io/ent/dialect/sql/schema"
|
||||
"github.com/hay-kot/homebox/backend/app/api/docs"
|
||||
"github.com/hay-kot/homebox/backend/ent"
|
||||
"github.com/hay-kot/homebox/backend/internal/config"
|
||||
"github.com/hay-kot/homebox/backend/internal/migrations"
|
||||
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||
"github.com/hay-kot/homebox/backend/internal/services"
|
||||
"github.com/hay-kot/homebox/backend/pkgs/server"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
|
||||
"github.com/hay-kot/homebox/backend/internal/core/currencies"
|
||||
"github.com/hay-kot/homebox/backend/internal/core/services"
|
||||
"github.com/hay-kot/homebox/backend/internal/core/services/reporting/eventbus"
|
||||
"github.com/hay-kot/homebox/backend/internal/data/ent"
|
||||
"github.com/hay-kot/homebox/backend/internal/data/migrations"
|
||||
"github.com/hay-kot/homebox/backend/internal/data/repo"
|
||||
"github.com/hay-kot/homebox/backend/internal/sys/config"
|
||||
"github.com/hay-kot/homebox/backend/internal/web/mid"
|
||||
"github.com/hay-kot/httpkit/errchain"
|
||||
"github.com/hay-kot/httpkit/graceful"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/rs/zerolog/pkgerrors"
|
||||
|
||||
_ "github.com/hay-kot/homebox/backend/pkgs/cgofreesqlite"
|
||||
)
|
||||
|
||||
var (
|
||||
Version = "0.1.0"
|
||||
Commit = "HEAD"
|
||||
BuildTime = "now"
|
||||
version = "nightly"
|
||||
commit = "HEAD"
|
||||
buildTime = "now"
|
||||
)
|
||||
|
||||
// @title Go API Templates
|
||||
// @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 build() string {
|
||||
short := commit
|
||||
if len(short) > 7 {
|
||||
short = short[:7]
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s, commit %s, built at %s", version, short, buildTime)
|
||||
}
|
||||
|
||||
// @title Homebox API
|
||||
// @version 1.0
|
||||
// @description Track, Manage, and Organize your Things.
|
||||
// @contact.name Don't
|
||||
// @BasePath /api
|
||||
// @securityDefinitions.apikey Bearer
|
||||
// @in header
|
||||
// @name Authorization
|
||||
// @description "Type 'Bearer TOKEN' to correctly set the API Key"
|
||||
func main() {
|
||||
cfg, err := config.New()
|
||||
zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
|
||||
|
||||
cfg, err := config.New(build(), "Homebox inventory management system")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
docs.SwaggerInfo.Host = cfg.Swagger.Host
|
||||
|
||||
if err := run(cfg); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -55,17 +75,17 @@ func run(cfg *config.Config) error {
|
|||
// =========================================================================
|
||||
// Initialize Database & Repos
|
||||
|
||||
err := os.MkdirAll(cfg.Storage.Data, 0755)
|
||||
err := os.MkdirAll(cfg.Storage.Data, 0o755)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("failed to create data directory")
|
||||
}
|
||||
|
||||
c, err := ent.Open("sqlite3", cfg.Storage.SqliteUrl)
|
||||
c, err := ent.Open("sqlite3", cfg.Storage.SqliteURL)
|
||||
if err != nil {
|
||||
log.Fatal().
|
||||
Err(err).
|
||||
Str("driver", "sqlite").
|
||||
Str("url", cfg.Storage.SqliteUrl).
|
||||
Str("url", cfg.Storage.SqliteURL).
|
||||
Msg("failed opening connection to sqlite")
|
||||
}
|
||||
defer func(c *ent.Client) {
|
||||
|
@ -98,7 +118,7 @@ func run(cfg *config.Config) error {
|
|||
log.Fatal().
|
||||
Err(err).
|
||||
Str("driver", "sqlite").
|
||||
Str("url", cfg.Storage.SqliteUrl).
|
||||
Str("url", cfg.Storage.SqliteURL).
|
||||
Msg("failed creating schema resources")
|
||||
}
|
||||
|
||||
|
@ -108,42 +128,145 @@ func run(cfg *config.Config) error {
|
|||
return err
|
||||
}
|
||||
|
||||
collectFuncs := []currencies.CollectorFunc{
|
||||
currencies.CollectDefaults(),
|
||||
}
|
||||
|
||||
if cfg.Options.CurrencyConfig != "" {
|
||||
log.Info().
|
||||
Str("path", cfg.Options.CurrencyConfig).
|
||||
Msg("loading currency config file")
|
||||
|
||||
content, err := os.ReadFile(cfg.Options.CurrencyConfig)
|
||||
if err != nil {
|
||||
log.Fatal().
|
||||
Err(err).
|
||||
Str("path", cfg.Options.CurrencyConfig).
|
||||
Msg("failed to read currency config file")
|
||||
}
|
||||
|
||||
collectFuncs = append(collectFuncs, currencies.CollectJSON(bytes.NewReader(content)))
|
||||
}
|
||||
|
||||
currencies, err := currencies.CollectionCurrencies(collectFuncs...)
|
||||
if err != nil {
|
||||
log.Fatal().
|
||||
Err(err).
|
||||
Msg("failed to collect currencies")
|
||||
}
|
||||
|
||||
app.bus = eventbus.New()
|
||||
app.db = c
|
||||
app.repos = repo.New(c, cfg.Storage.Data)
|
||||
app.services = services.NewServices(app.repos)
|
||||
app.repos = repo.New(c, app.bus, cfg.Storage.Data)
|
||||
app.services = services.New(
|
||||
app.repos,
|
||||
services.WithAutoIncrementAssetID(cfg.Options.AutoIncrementAssetID),
|
||||
services.WithCurrencies(currencies),
|
||||
)
|
||||
|
||||
// =========================================================================
|
||||
// Start Server
|
||||
app.server = server.NewServer(
|
||||
server.WithHost(app.conf.Web.Host),
|
||||
server.WithPort(app.conf.Web.Port),
|
||||
|
||||
logger := log.With().Caller().Logger()
|
||||
|
||||
router := chi.NewMux()
|
||||
router.Use(
|
||||
middleware.RequestID,
|
||||
middleware.RealIP,
|
||||
mid.Logger(logger),
|
||||
middleware.Recoverer,
|
||||
middleware.StripSlashes,
|
||||
)
|
||||
|
||||
routes := app.newRouter(app.repos)
|
||||
chain := errchain.New(mid.Errors(logger))
|
||||
|
||||
if app.conf.Mode != config.ModeDevelopment {
|
||||
app.logRoutes(routes)
|
||||
}
|
||||
app.mountRoutes(router, chain, app.repos)
|
||||
|
||||
log.Info().Msgf("Starting HTTP Server on %s:%s", app.server.Host, app.server.Port)
|
||||
runner := graceful.NewRunner()
|
||||
|
||||
runner.AddFunc("server", func(ctx context.Context) error {
|
||||
httpserver := http.Server{
|
||||
Addr: fmt.Sprintf("%s:%s", cfg.Web.Host, cfg.Web.Port),
|
||||
Handler: router,
|
||||
ReadTimeout: cfg.Web.ReadTimeout,
|
||||
WriteTimeout: cfg.Web.WriteTimeout,
|
||||
IdleTimeout: cfg.Web.IdleTimeout,
|
||||
}
|
||||
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
_ = httpserver.Shutdown(context.Background())
|
||||
}()
|
||||
|
||||
log.Info().Msgf("Server is running on %s:%s", cfg.Web.Host, cfg.Web.Port)
|
||||
return httpserver.ListenAndServe()
|
||||
})
|
||||
|
||||
// =========================================================================
|
||||
// Start Reoccurring Tasks
|
||||
|
||||
go app.startBgTask(time.Duration(24)*time.Hour, func() {
|
||||
_, err := app.repos.AuthTokens.PurgeExpiredTokens(context.Background())
|
||||
runner.AddFunc("eventbus", app.bus.Run)
|
||||
|
||||
runner.AddFunc("seed_database", func(ctx context.Context) error {
|
||||
// TODO: Remove through external API that does setup
|
||||
if cfg.Demo {
|
||||
log.Info().Msg("Running in demo mode, creating demo data")
|
||||
app.SetupDemo()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
runner.AddPlugin(NewTask("purge-tokens", time.Duration(24)*time.Hour, func(ctx context.Context) {
|
||||
_, err := app.repos.AuthTokens.PurgeExpiredTokens(ctx)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
Msg("failed to purge expired tokens")
|
||||
}
|
||||
})
|
||||
}))
|
||||
|
||||
// TODO: Remove through external API that does setup
|
||||
if cfg.Demo {
|
||||
log.Info().Msg("Running in demo mode, creating demo data")
|
||||
app.SetupDemo()
|
||||
runner.AddPlugin(NewTask("purge-invitations", time.Duration(24)*time.Hour, func(ctx context.Context) {
|
||||
_, err := app.repos.Groups.InvitationPurge(ctx)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
Msg("failed to purge expired invitations")
|
||||
}
|
||||
}))
|
||||
|
||||
runner.AddPlugin(NewTask("send-notifications", time.Duration(1)*time.Hour, func(ctx context.Context) {
|
||||
now := time.Now()
|
||||
|
||||
if now.Hour() == 8 {
|
||||
fmt.Println("run notifiers")
|
||||
err := app.services.BackgroundService.SendNotifiersToday(context.Background())
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
Msg("failed to send notifiers")
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
if cfg.Debug.Enabled {
|
||||
runner.AddFunc("debug", func(ctx context.Context) error {
|
||||
debugserver := http.Server{
|
||||
Addr: fmt.Sprintf("%s:%s", cfg.Web.Host, cfg.Debug.Port),
|
||||
Handler: app.debugRouter(),
|
||||
ReadTimeout: cfg.Web.ReadTimeout,
|
||||
WriteTimeout: cfg.Web.WriteTimeout,
|
||||
IdleTimeout: cfg.Web.IdleTimeout,
|
||||
}
|
||||
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
_ = debugserver.Shutdown(context.Background())
|
||||
}()
|
||||
|
||||
log.Info().Msgf("Debug server is running on %s:%s", cfg.Web.Host, cfg.Debug.Port)
|
||||
return debugserver.ListenAndServe()
|
||||
})
|
||||
}
|
||||
|
||||
return app.server.Start(routes)
|
||||
return runner.Start(context.Background())
|
||||
}
|
||||
|
|
|
@ -1,160 +1,154 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/hay-kot/homebox/backend/internal/config"
|
||||
"github.com/hay-kot/homebox/backend/internal/services"
|
||||
"github.com/hay-kot/homebox/backend/pkgs/server"
|
||||
"github.com/rs/zerolog/log"
|
||||
v1 "github.com/hay-kot/homebox/backend/app/api/handlers/v1"
|
||||
"github.com/hay-kot/homebox/backend/internal/core/services"
|
||||
"github.com/hay-kot/homebox/backend/internal/data/ent"
|
||||
"github.com/hay-kot/homebox/backend/internal/sys/validate"
|
||||
"github.com/hay-kot/httpkit/errchain"
|
||||
)
|
||||
|
||||
func (a *app) setGlobalMiddleware(r *chi.Mux) {
|
||||
// =========================================================================
|
||||
// Middleware
|
||||
r.Use(middleware.RequestID)
|
||||
r.Use(middleware.RealIP)
|
||||
r.Use(mwStripTrailingSlash)
|
||||
type tokenHasKey struct {
|
||||
key string
|
||||
}
|
||||
|
||||
// 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)
|
||||
var hashedToken = tokenHasKey{key: "hashedToken"}
|
||||
|
||||
type RoleMode int
|
||||
|
||||
const (
|
||||
RoleModeOr RoleMode = 0
|
||||
RoleModeAnd RoleMode = 1
|
||||
)
|
||||
|
||||
// mwRoles is a middleware that will validate the required roles are met. All roles
|
||||
// are required to be met for the request to be allowed. If the user does not have
|
||||
// the required roles, a 403 Forbidden will be returned.
|
||||
//
|
||||
// WARNING: This middleware _MUST_ be called after mwAuthToken or else it will panic
|
||||
func (a *app) mwRoles(rm RoleMode, required ...string) errchain.Middleware {
|
||||
return func(next errchain.Handler) errchain.Handler {
|
||||
return errchain.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
|
||||
ctx := r.Context()
|
||||
|
||||
maybeToken := ctx.Value(hashedToken)
|
||||
if maybeToken == nil {
|
||||
panic("mwRoles: token not found in context, you must call mwAuthToken before mwRoles")
|
||||
}
|
||||
|
||||
token := maybeToken.(string)
|
||||
|
||||
roles, err := a.repos.AuthTokens.GetRoles(r.Context(), token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
outer:
|
||||
switch rm {
|
||||
case RoleModeOr:
|
||||
for _, role := range required {
|
||||
if roles.Contains(role) {
|
||||
break outer
|
||||
}
|
||||
}
|
||||
return validate.NewRequestError(errors.New("Forbidden"), http.StatusForbidden)
|
||||
case RoleModeAnd:
|
||||
for _, req := range required {
|
||||
if !roles.Contains(req) {
|
||||
return validate.NewRequestError(errors.New("Unauthorized"), http.StatusForbidden)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
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))
|
||||
type KeyFunc func(r *http.Request) (string, error)
|
||||
|
||||
func getBearer(r *http.Request) (string, error) {
|
||||
auth := r.Header.Get("Authorization")
|
||||
if auth == "" {
|
||||
return "", errors.New("authorization header is required")
|
||||
}
|
||||
|
||||
return auth, nil
|
||||
}
|
||||
|
||||
func getQuery(r *http.Request) (string, error) {
|
||||
token := r.URL.Query().Get("access_token")
|
||||
if token == "" {
|
||||
return "", errors.New("access_token query is required")
|
||||
}
|
||||
|
||||
token, err := url.QueryUnescape(token)
|
||||
if err != nil {
|
||||
return "", errors.New("access_token query is required")
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// mwAuthToken is a middleware that will check the database for a stateful token
|
||||
// and attach it to the request context with the user, or return a 401 if it doesn't exist.
|
||||
func (a *app) mwAuthToken(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
requestToken := r.Header.Get("Authorization")
|
||||
// and attach it's user to the request context, or return an appropriate error.
|
||||
// Authorization support is by token via Headers or Query Parameter
|
||||
//
|
||||
// Example:
|
||||
// - header = "Bearer 1234567890"
|
||||
// - query = "?access_token=1234567890"
|
||||
func (a *app) mwAuthToken(next errchain.Handler) errchain.Handler {
|
||||
return errchain.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
|
||||
var requestToken string
|
||||
|
||||
// We ignore the error to allow the next strategy to be attempted
|
||||
{
|
||||
cookies, _ := v1.GetCookies(r)
|
||||
if cookies != nil {
|
||||
requestToken = cookies.Token
|
||||
}
|
||||
}
|
||||
|
||||
if requestToken == "" {
|
||||
server.RespondUnauthorized(w)
|
||||
return
|
||||
keyFuncs := [...]KeyFunc{
|
||||
getBearer,
|
||||
getQuery,
|
||||
}
|
||||
|
||||
for _, keyFunc := range keyFuncs {
|
||||
token, err := keyFunc(r)
|
||||
if err == nil {
|
||||
requestToken = token
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if requestToken == "" {
|
||||
return validate.NewRequestError(errors.New("authorization header or query is required"), http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
requestToken = strings.TrimPrefix(requestToken, "Bearer ")
|
||||
|
||||
r = r.WithContext(context.WithValue(r.Context(), hashedToken, requestToken))
|
||||
|
||||
usr, err := a.services.User.GetSelf(r.Context(), requestToken)
|
||||
|
||||
// Check the database for the token
|
||||
|
||||
if err != nil {
|
||||
server.RespondUnauthorized(w)
|
||||
return
|
||||
if ent.IsNotFound(err) {
|
||||
return validate.NewRequestError(errors.New("valid authorization token is required"), http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
r = r.WithContext(services.SetUserCtx(r.Context(), &usr, requestToken))
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// mwAdminOnly is a middleware that extends the mwAuthToken middleware to only allow
|
||||
// requests from superusers.
|
||||
// func (a *app) mwAdminOnly(next http.Handler) http.Handler {
|
||||
// mw := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// usr := services.UseUserCtx(r.Context())
|
||||
|
||||
// if !usr.IsSuperuser {
|
||||
// server.RespondUnauthorized(w)
|
||||
// return
|
||||
// }
|
||||
|
||||
// next.ServeHTTP(w, r)
|
||||
// })
|
||||
// return a.mwAuthToken(mw)
|
||||
// }
|
||||
|
||||
// mqStripTrailingSlash is a middleware that will strip trailing slashes from the request path.
|
||||
func mwStripTrailingSlash(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
r.URL.Path = strings.TrimSuffix(r.URL.Path, "/")
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
type StatusRecorder struct {
|
||||
http.ResponseWriter
|
||||
Status int
|
||||
}
|
||||
|
||||
func (r *StatusRecorder) WriteHeader(status int) {
|
||||
r.Status = status
|
||||
r.ResponseWriter.WriteHeader(status)
|
||||
}
|
||||
|
||||
func (a *app) mwStructLogger(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
record := &StatusRecorder{ResponseWriter: w, Status: http.StatusOK}
|
||||
next.ServeHTTP(record, r)
|
||||
|
||||
scheme := "http"
|
||||
if r.TLS != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s://%s%s %s", scheme, r.Host, r.RequestURI, r.Proto)
|
||||
|
||||
log.Info().
|
||||
Str("id", middleware.GetReqID(r.Context())).
|
||||
Str("url", url).
|
||||
Str("method", r.Method).
|
||||
Str("remote_addr", r.RemoteAddr).
|
||||
Int("status", record.Status).
|
||||
Msg(url)
|
||||
})
|
||||
}
|
||||
|
||||
func (a *app) mwSummaryLogger(next http.Handler) http.Handler {
|
||||
bold := func(s string) string { return "\033[1m" + s + "\033[0m" }
|
||||
orange := func(s string) string { return "\033[33m" + s + "\033[0m" }
|
||||
aqua := func(s string) string { return "\033[36m" + s + "\033[0m" }
|
||||
red := func(s string) string { return "\033[31m" + s + "\033[0m" }
|
||||
green := func(s string) string { return "\033[32m" + s + "\033[0m" }
|
||||
|
||||
fmtCode := func(code int) string {
|
||||
switch {
|
||||
case code >= 500:
|
||||
return red(fmt.Sprintf("%d", code))
|
||||
case code >= 400:
|
||||
return orange(fmt.Sprintf("%d", code))
|
||||
case code >= 300:
|
||||
return aqua(fmt.Sprintf("%d", code))
|
||||
default:
|
||||
return green(fmt.Sprintf("%d", code))
|
||||
}
|
||||
}
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
record := &StatusRecorder{ResponseWriter: w, Status: http.StatusOK}
|
||||
next.ServeHTTP(record, r) // Blocks until the next handler returns.
|
||||
|
||||
scheme := "http"
|
||||
if r.TLS != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s://%s%s %s", scheme, r.Host, r.RequestURI, r.Proto)
|
||||
|
||||
log.Info().
|
||||
Msgf("%s %s %s",
|
||||
bold(orange(""+r.Method+"")),
|
||||
aqua(url),
|
||||
bold(fmtCode(record.Status)),
|
||||
)
|
||||
return next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
|
2
backend/app/api/providers/doc.go
Normal file
2
backend/app/api/providers/doc.go
Normal file
|
@ -0,0 +1,2 @@
|
|||
// Package providers provides a authentication abstraction for the backend.
|
||||
package providers
|
55
backend/app/api/providers/extractors.go
Normal file
55
backend/app/api/providers/extractors.go
Normal file
|
@ -0,0 +1,55 @@
|
|||
package providers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/hay-kot/homebox/backend/internal/sys/validate"
|
||||
"github.com/hay-kot/httpkit/server"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type LoginForm struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
StayLoggedIn bool `json:"stayLoggedIn"`
|
||||
}
|
||||
|
||||
func getLoginForm(r *http.Request) (LoginForm, error) {
|
||||
loginForm := LoginForm{}
|
||||
|
||||
switch r.Header.Get("Content-Type") {
|
||||
case "application/x-www-form-urlencoded":
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
return loginForm, errors.New("failed to parse form")
|
||||
}
|
||||
|
||||
loginForm.Username = r.PostFormValue("username")
|
||||
loginForm.Password = r.PostFormValue("password")
|
||||
loginForm.StayLoggedIn = r.PostFormValue("stayLoggedIn") == "true"
|
||||
case "application/json":
|
||||
err := server.Decode(r, &loginForm)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to decode login form")
|
||||
return loginForm, errors.New("failed to decode login form")
|
||||
}
|
||||
default:
|
||||
return loginForm, errors.New("invalid content type")
|
||||
}
|
||||
|
||||
if loginForm.Username == "" || loginForm.Password == "" {
|
||||
return loginForm, validate.NewFieldErrors(
|
||||
validate.FieldError{
|
||||
Field: "username",
|
||||
Error: "username or password is empty",
|
||||
},
|
||||
validate.FieldError{
|
||||
Field: "password",
|
||||
Error: "username or password is empty",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return loginForm, nil
|
||||
}
|
30
backend/app/api/providers/local.go
Normal file
30
backend/app/api/providers/local.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
package providers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/hay-kot/homebox/backend/internal/core/services"
|
||||
)
|
||||
|
||||
type LocalProvider struct {
|
||||
service *services.UserService
|
||||
}
|
||||
|
||||
func NewLocalProvider(service *services.UserService) *LocalProvider {
|
||||
return &LocalProvider{
|
||||
service: service,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *LocalProvider) Name() string {
|
||||
return "local"
|
||||
}
|
||||
|
||||
func (p *LocalProvider) Authenticate(w http.ResponseWriter, r *http.Request) (services.UserAuthTokenDetail, error) {
|
||||
loginForm, err := getLoginForm(r)
|
||||
if err != nil {
|
||||
return services.UserAuthTokenDetail{}, err
|
||||
}
|
||||
|
||||
return p.service.Login(r.Context(), loginForm.Username, loginForm.Password, loginForm.StayLoggedIn)
|
||||
}
|
|
@ -3,7 +3,6 @@ package main
|
|||
import (
|
||||
"embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
|
@ -11,11 +10,14 @@ import (
|
|||
"path/filepath"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
_ "github.com/hay-kot/homebox/backend/app/api/docs"
|
||||
v1 "github.com/hay-kot/homebox/backend/app/api/v1"
|
||||
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||
"github.com/rs/zerolog/log"
|
||||
httpSwagger "github.com/swaggo/http-swagger" // http-swagger middleware
|
||||
"github.com/hay-kot/homebox/backend/app/api/handlers/debughandlers"
|
||||
v1 "github.com/hay-kot/homebox/backend/app/api/handlers/v1"
|
||||
"github.com/hay-kot/homebox/backend/app/api/providers"
|
||||
_ "github.com/hay-kot/homebox/backend/app/api/static/docs"
|
||||
"github.com/hay-kot/homebox/backend/internal/data/ent/authroles"
|
||||
"github.com/hay-kot/homebox/backend/internal/data/repo"
|
||||
"github.com/hay-kot/httpkit/errchain"
|
||||
httpSwagger "github.com/swaggo/http-swagger/v2" // http-swagger middleware
|
||||
)
|
||||
|
||||
const prefix = "/api"
|
||||
|
@ -23,102 +25,145 @@ const prefix = "/api"
|
|||
var (
|
||||
ErrDir = errors.New("path is dir")
|
||||
|
||||
//go:embed all:public/*
|
||||
//go:embed all:static/public/*
|
||||
public embed.FS
|
||||
)
|
||||
|
||||
func (a *app) debugRouter() *http.ServeMux {
|
||||
dbg := http.NewServeMux()
|
||||
debughandlers.New(dbg)
|
||||
|
||||
return dbg
|
||||
}
|
||||
|
||||
// registerRoutes registers all the routes for the API
|
||||
func (a *app) newRouter(repos *repo.AllRepos) *chi.Mux {
|
||||
func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllRepos) {
|
||||
registerMimes()
|
||||
|
||||
r := chi.NewRouter()
|
||||
a.setGlobalMiddleware(r)
|
||||
|
||||
r.Get("/swagger/*", httpSwagger.Handler(
|
||||
httpSwagger.URL(fmt.Sprintf("%s://%s/swagger/doc.json", a.conf.Swagger.Scheme, a.conf.Swagger.Host)),
|
||||
httpSwagger.URL("/swagger/doc.json"),
|
||||
))
|
||||
|
||||
// =========================================================================
|
||||
// API Version 1
|
||||
|
||||
v1Base := v1.BaseUrlFunc(prefix)
|
||||
v1Ctrl := v1.NewControllerV1(a.services,
|
||||
v1Base := v1.BaseURLFunc(prefix)
|
||||
|
||||
v1Ctrl := v1.NewControllerV1(
|
||||
a.services,
|
||||
a.repos,
|
||||
a.bus,
|
||||
v1.WithMaxUploadSize(a.conf.Web.MaxUploadSize),
|
||||
v1.WithRegistration(a.conf.Options.AllowRegistration),
|
||||
v1.WithDemoStatus(a.conf.Demo), // Disable Password Change in Demo Mode
|
||||
)
|
||||
r.Get(v1Base("/status"), v1Ctrl.HandleBase(func() bool { return true }, v1.Build{
|
||||
Version: Version,
|
||||
Commit: Commit,
|
||||
BuildTime: BuildTime,
|
||||
}))
|
||||
|
||||
r.Post(v1Base("/users/register"), v1Ctrl.HandleUserRegistration())
|
||||
r.Post(v1Base("/users/login"), v1Ctrl.HandleAuthLogin())
|
||||
r.Get(v1Base("/status"), chain.ToHandlerFunc(v1Ctrl.HandleBase(func() bool { return true }, v1.Build{
|
||||
Version: version,
|
||||
Commit: commit,
|
||||
BuildTime: buildTime,
|
||||
})))
|
||||
|
||||
// Attachment download URl needs a `token` query param to be passed in the request.
|
||||
// and also needs to be outside of the `auth` middleware.
|
||||
r.Get(v1Base("/items/{id}/attachments/download"), v1Ctrl.HandleItemAttachmentDownload())
|
||||
r.Get(v1Base("/currencies"), chain.ToHandlerFunc(v1Ctrl.HandleCurrency()))
|
||||
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(a.mwAuthToken)
|
||||
r.Get(v1Base("/users/self"), v1Ctrl.HandleUserSelf())
|
||||
r.Put(v1Base("/users/self"), v1Ctrl.HandleUserSelfUpdate())
|
||||
r.Delete(v1Base("/users/self"), v1Ctrl.HandleUserSelfDelete())
|
||||
r.Put(v1Base("/users/self/password"), v1Ctrl.HandleUserUpdatePassword())
|
||||
r.Post(v1Base("/users/logout"), v1Ctrl.HandleAuthLogout())
|
||||
r.Get(v1Base("/users/refresh"), v1Ctrl.HandleAuthRefresh())
|
||||
r.Put(v1Base("/users/self/change-password"), v1Ctrl.HandleUserSelfChangePassword())
|
||||
|
||||
r.Post(v1Base("/groups/invitations"), v1Ctrl.HandleGroupInvitationsCreate())
|
||||
|
||||
r.Get(v1Base("/locations"), v1Ctrl.HandleLocationGetAll())
|
||||
r.Post(v1Base("/locations"), v1Ctrl.HandleLocationCreate())
|
||||
r.Get(v1Base("/locations/{id}"), v1Ctrl.HandleLocationGet())
|
||||
r.Put(v1Base("/locations/{id}"), v1Ctrl.HandleLocationUpdate())
|
||||
r.Delete(v1Base("/locations/{id}"), v1Ctrl.HandleLocationDelete())
|
||||
|
||||
r.Get(v1Base("/labels"), v1Ctrl.HandleLabelsGetAll())
|
||||
r.Post(v1Base("/labels"), v1Ctrl.HandleLabelsCreate())
|
||||
r.Get(v1Base("/labels/{id}"), v1Ctrl.HandleLabelGet())
|
||||
r.Put(v1Base("/labels/{id}"), v1Ctrl.HandleLabelUpdate())
|
||||
r.Delete(v1Base("/labels/{id}"), v1Ctrl.HandleLabelDelete())
|
||||
|
||||
r.Get(v1Base("/items"), v1Ctrl.HandleItemsGetAll())
|
||||
r.Post(v1Base("/items/import"), v1Ctrl.HandleItemsImport())
|
||||
r.Post(v1Base("/items"), v1Ctrl.HandleItemsCreate())
|
||||
r.Get(v1Base("/items/{id}"), v1Ctrl.HandleItemGet())
|
||||
r.Put(v1Base("/items/{id}"), v1Ctrl.HandleItemUpdate())
|
||||
r.Delete(v1Base("/items/{id}"), v1Ctrl.HandleItemDelete())
|
||||
|
||||
r.Post(v1Base("/items/{id}/attachments"), v1Ctrl.HandleItemAttachmentCreate())
|
||||
r.Get(v1Base("/items/{id}/attachments/{attachment_id}"), v1Ctrl.HandleItemAttachmentToken())
|
||||
r.Put(v1Base("/items/{id}/attachments/{attachment_id}"), v1Ctrl.HandleItemAttachmentUpdate())
|
||||
r.Delete(v1Base("/items/{id}/attachments/{attachment_id}"), v1Ctrl.HandleItemAttachmentDelete())
|
||||
})
|
||||
|
||||
r.NotFound(notFoundHandler())
|
||||
return r
|
||||
}
|
||||
|
||||
// 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
|
||||
providers := []v1.AuthProvider{
|
||||
providers.NewLocalProvider(a.services.User),
|
||||
}
|
||||
|
||||
if err := chi.Walk(r, walkFunc); err != nil {
|
||||
fmt.Printf("Logging err: %s\n", err.Error())
|
||||
r.Post(v1Base("/users/register"), chain.ToHandlerFunc(v1Ctrl.HandleUserRegistration()))
|
||||
r.Post(v1Base("/users/login"), chain.ToHandlerFunc(v1Ctrl.HandleAuthLogin(providers...)))
|
||||
|
||||
userMW := []errchain.Middleware{
|
||||
a.mwAuthToken,
|
||||
a.mwRoles(RoleModeOr, authroles.RoleUser.String()),
|
||||
}
|
||||
|
||||
r.Get(v1Base("/ws/events"), chain.ToHandlerFunc(v1Ctrl.HandleCacheWS(), userMW...))
|
||||
r.Get(v1Base("/users/self"), chain.ToHandlerFunc(v1Ctrl.HandleUserSelf(), userMW...))
|
||||
r.Put(v1Base("/users/self"), chain.ToHandlerFunc(v1Ctrl.HandleUserSelfUpdate(), userMW...))
|
||||
r.Delete(v1Base("/users/self"), chain.ToHandlerFunc(v1Ctrl.HandleUserSelfDelete(), userMW...))
|
||||
r.Post(v1Base("/users/logout"), chain.ToHandlerFunc(v1Ctrl.HandleAuthLogout(), userMW...))
|
||||
r.Get(v1Base("/users/refresh"), chain.ToHandlerFunc(v1Ctrl.HandleAuthRefresh(), userMW...))
|
||||
r.Put(v1Base("/users/self/change-password"), chain.ToHandlerFunc(v1Ctrl.HandleUserSelfChangePassword(), userMW...))
|
||||
|
||||
r.Post(v1Base("/groups/invitations"), chain.ToHandlerFunc(v1Ctrl.HandleGroupInvitationsCreate(), userMW...))
|
||||
r.Get(v1Base("/groups/statistics"), chain.ToHandlerFunc(v1Ctrl.HandleGroupStatistics(), userMW...))
|
||||
r.Get(v1Base("/groups/statistics/purchase-price"), chain.ToHandlerFunc(v1Ctrl.HandleGroupStatisticsPriceOverTime(), userMW...))
|
||||
r.Get(v1Base("/groups/statistics/locations"), chain.ToHandlerFunc(v1Ctrl.HandleGroupStatisticsLocations(), userMW...))
|
||||
r.Get(v1Base("/groups/statistics/labels"), chain.ToHandlerFunc(v1Ctrl.HandleGroupStatisticsLabels(), userMW...))
|
||||
|
||||
// TODO: I don't like /groups being the URL for users
|
||||
r.Get(v1Base("/groups"), chain.ToHandlerFunc(v1Ctrl.HandleGroupGet(), userMW...))
|
||||
r.Put(v1Base("/groups"), chain.ToHandlerFunc(v1Ctrl.HandleGroupUpdate(), userMW...))
|
||||
|
||||
r.Post(v1Base("/actions/ensure-asset-ids"), chain.ToHandlerFunc(v1Ctrl.HandleEnsureAssetID(), userMW...))
|
||||
r.Post(v1Base("/actions/zero-item-time-fields"), chain.ToHandlerFunc(v1Ctrl.HandleItemDateZeroOut(), userMW...))
|
||||
r.Post(v1Base("/actions/ensure-import-refs"), chain.ToHandlerFunc(v1Ctrl.HandleEnsureImportRefs(), userMW...))
|
||||
r.Post(v1Base("/actions/set-primary-photos"), chain.ToHandlerFunc(v1Ctrl.HandleSetPrimaryPhotos(), userMW...))
|
||||
|
||||
r.Get(v1Base("/locations"), chain.ToHandlerFunc(v1Ctrl.HandleLocationGetAll(), userMW...))
|
||||
r.Post(v1Base("/locations"), chain.ToHandlerFunc(v1Ctrl.HandleLocationCreate(), userMW...))
|
||||
r.Get(v1Base("/locations/tree"), chain.ToHandlerFunc(v1Ctrl.HandleLocationTreeQuery(), userMW...))
|
||||
r.Get(v1Base("/locations/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLocationGet(), userMW...))
|
||||
r.Put(v1Base("/locations/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLocationUpdate(), userMW...))
|
||||
r.Delete(v1Base("/locations/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLocationDelete(), userMW...))
|
||||
|
||||
r.Get(v1Base("/labels"), chain.ToHandlerFunc(v1Ctrl.HandleLabelsGetAll(), userMW...))
|
||||
r.Post(v1Base("/labels"), chain.ToHandlerFunc(v1Ctrl.HandleLabelsCreate(), userMW...))
|
||||
r.Get(v1Base("/labels/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLabelGet(), userMW...))
|
||||
r.Put(v1Base("/labels/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLabelUpdate(), userMW...))
|
||||
r.Delete(v1Base("/labels/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLabelDelete(), userMW...))
|
||||
|
||||
r.Get(v1Base("/items"), chain.ToHandlerFunc(v1Ctrl.HandleItemsGetAll(), userMW...))
|
||||
r.Post(v1Base("/items"), chain.ToHandlerFunc(v1Ctrl.HandleItemsCreate(), userMW...))
|
||||
r.Post(v1Base("/items/import"), chain.ToHandlerFunc(v1Ctrl.HandleItemsImport(), userMW...))
|
||||
r.Get(v1Base("/items/export"), chain.ToHandlerFunc(v1Ctrl.HandleItemsExport(), userMW...))
|
||||
r.Get(v1Base("/items/fields"), chain.ToHandlerFunc(v1Ctrl.HandleGetAllCustomFieldNames(), userMW...))
|
||||
r.Get(v1Base("/items/fields/values"), chain.ToHandlerFunc(v1Ctrl.HandleGetAllCustomFieldValues(), userMW...))
|
||||
|
||||
r.Get(v1Base("/items/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemGet(), userMW...))
|
||||
r.Get(v1Base("/items/{id}/path"), chain.ToHandlerFunc(v1Ctrl.HandleItemFullPath(), userMW...))
|
||||
r.Put(v1Base("/items/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemUpdate(), userMW...))
|
||||
r.Patch(v1Base("/items/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemPatch(), userMW...))
|
||||
r.Delete(v1Base("/items/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemDelete(), userMW...))
|
||||
|
||||
r.Post(v1Base("/items/{id}/attachments"), chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentCreate(), userMW...))
|
||||
r.Put(v1Base("/items/{id}/attachments/{attachment_id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentUpdate(), userMW...))
|
||||
r.Delete(v1Base("/items/{id}/attachments/{attachment_id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentDelete(), userMW...))
|
||||
|
||||
r.Get(v1Base("/items/{id}/maintenance"), chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceLogGet(), userMW...))
|
||||
r.Post(v1Base("/items/{id}/maintenance"), chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceEntryCreate(), userMW...))
|
||||
r.Put(v1Base("/items/{id}/maintenance/{entry_id}"), chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceEntryUpdate(), userMW...))
|
||||
r.Delete(v1Base("/items/{id}/maintenance/{entry_id}"), chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceEntryDelete(), userMW...))
|
||||
|
||||
r.Get(v1Base("/assets/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleAssetGet(), userMW...))
|
||||
|
||||
// Notifiers
|
||||
r.Get(v1Base("/notifiers"), chain.ToHandlerFunc(v1Ctrl.HandleGetUserNotifiers(), userMW...))
|
||||
r.Post(v1Base("/notifiers"), chain.ToHandlerFunc(v1Ctrl.HandleCreateNotifier(), userMW...))
|
||||
r.Put(v1Base("/notifiers/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleUpdateNotifier(), userMW...))
|
||||
r.Delete(v1Base("/notifiers/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleDeleteNotifier(), userMW...))
|
||||
r.Post(v1Base("/notifiers/test"), chain.ToHandlerFunc(v1Ctrl.HandlerNotifierTest(), userMW...))
|
||||
|
||||
// Asset-Like endpoints
|
||||
assetMW := []errchain.Middleware{
|
||||
a.mwAuthToken,
|
||||
a.mwRoles(RoleModeOr, authroles.RoleUser.String(), authroles.RoleAttachments.String()),
|
||||
}
|
||||
|
||||
r.Get(
|
||||
v1Base("/qrcode"),
|
||||
chain.ToHandlerFunc(v1Ctrl.HandleGenerateQRCode(), assetMW...),
|
||||
)
|
||||
r.Get(
|
||||
v1Base("/items/{id}/attachments/{attachment_id}"),
|
||||
chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentGet(), assetMW...),
|
||||
)
|
||||
|
||||
// Reporting Services
|
||||
r.Get(v1Base("/reporting/bill-of-materials"), chain.ToHandlerFunc(v1Ctrl.HandleBillOfMaterialsExport(), userMW...))
|
||||
|
||||
r.NotFound(chain.ToHandlerFunc(notFoundHandler()))
|
||||
}
|
||||
|
||||
func registerMimes() {
|
||||
|
@ -135,13 +180,13 @@ func registerMimes() {
|
|||
|
||||
// notFoundHandler perform the main logic around handling the internal SPA embed and ensuring that
|
||||
// the client side routing is handled correctly.
|
||||
func notFoundHandler() http.HandlerFunc {
|
||||
func notFoundHandler() errchain.HandlerFunc {
|
||||
tryRead := func(fs embed.FS, prefix, requestedPath string, w http.ResponseWriter) error {
|
||||
f, err := fs.Open(path.Join(prefix, requestedPath))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
stat, _ := f.Stat()
|
||||
if stat.IsDir() {
|
||||
|
@ -154,17 +199,16 @@ func notFoundHandler() http.HandlerFunc {
|
|||
return err
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
err := tryRead(public, "public", r.URL.Path, w)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
log.Debug().
|
||||
Str("path", r.URL.Path).
|
||||
Msg("served from embed not found - serving index.html")
|
||||
err = tryRead(public, "public", "index.html", w)
|
||||
return func(w http.ResponseWriter, r *http.Request) error {
|
||||
err := tryRead(public, "static/public", r.URL.Path, w)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
// Fallback to the index.html file.
|
||||
// should succeed in all cases.
|
||||
err = tryRead(public, "static/public", "index.html", w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
1881
backend/app/api/static/docs/swagger.yaml
Normal file
1881
backend/app/api/static/docs/swagger.yaml
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,84 +0,0 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/hay-kot/homebox/backend/internal/services"
|
||||
"github.com/hay-kot/homebox/backend/pkgs/server"
|
||||
)
|
||||
|
||||
func WithMaxUploadSize(maxUploadSize int64) func(*V1Controller) {
|
||||
return func(ctrl *V1Controller) {
|
||||
ctrl.maxUploadSize = maxUploadSize
|
||||
}
|
||||
}
|
||||
|
||||
func WithDemoStatus(demoStatus bool) func(*V1Controller) {
|
||||
return func(ctrl *V1Controller) {
|
||||
ctrl.isDemo = demoStatus
|
||||
}
|
||||
}
|
||||
|
||||
type V1Controller struct {
|
||||
svc *services.AllServices
|
||||
maxUploadSize int64
|
||||
isDemo bool
|
||||
}
|
||||
|
||||
type (
|
||||
Build struct {
|
||||
Version string `json:"version"`
|
||||
Commit string `json:"commit"`
|
||||
BuildTime string `json:"buildTime"`
|
||||
}
|
||||
|
||||
ApiSummary struct {
|
||||
Healthy bool `json:"health"`
|
||||
Versions []string `json:"versions"`
|
||||
Title string `json:"title"`
|
||||
Message string `json:"message"`
|
||||
Build Build `json:"build"`
|
||||
Demo bool `json:"demo"`
|
||||
}
|
||||
)
|
||||
|
||||
func BaseUrlFunc(prefix string) func(s string) string {
|
||||
v1Base := prefix + "/v1"
|
||||
prefixFunc := func(s string) string {
|
||||
return v1Base + s
|
||||
}
|
||||
|
||||
return prefixFunc
|
||||
}
|
||||
|
||||
func NewControllerV1(svc *services.AllServices, options ...func(*V1Controller)) *V1Controller {
|
||||
ctrl := &V1Controller{
|
||||
svc: svc,
|
||||
}
|
||||
|
||||
for _, opt := range options {
|
||||
opt(ctrl)
|
||||
}
|
||||
|
||||
return ctrl
|
||||
}
|
||||
|
||||
type ReadyFunc func() bool
|
||||
|
||||
// HandleBase godoc
|
||||
// @Summary Retrieves the basic information about the API
|
||||
// @Tags Base
|
||||
// @Produce json
|
||||
// @Success 200 {object} ApiSummary
|
||||
// @Router /v1/status [GET]
|
||||
func (ctrl *V1Controller) HandleBase(ready ReadyFunc, build Build) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
server.Respond(w, http.StatusOK, ApiSummary{
|
||||
Healthy: ready(),
|
||||
Title: "Go API Template",
|
||||
Message: "Welcome to the Go API Template Application!",
|
||||
Build: build,
|
||||
Demo: ctrl.isDemo,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/google/uuid"
|
||||
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||
"github.com/hay-kot/homebox/backend/internal/services"
|
||||
"github.com/hay-kot/homebox/backend/pkgs/server"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
/*
|
||||
This is where we put partial snippets/functions for actions that are commonly
|
||||
used within the controller class. This _hopefully_ helps with code duplication
|
||||
and makes it a little more consistent when error handling and logging.
|
||||
*/
|
||||
|
||||
// partialParseIdAndUser parses the ID from the requests URL and pulls the user
|
||||
// from the context. If either of these fail, it will return an error. When an error
|
||||
// occurs it will also write the error to the response. As such, if an error is returned
|
||||
// from this function you can return immediately without writing to the response.
|
||||
func (ctrl *V1Controller) partialParseIdAndUser(w http.ResponseWriter, r *http.Request) (uuid.UUID, *repo.UserOut, error) {
|
||||
uid, err := uuid.Parse(chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to parse id")
|
||||
server.RespondError(w, http.StatusBadRequest, err)
|
||||
return uuid.Nil, &repo.UserOut{}, err
|
||||
}
|
||||
|
||||
user := services.UseUserCtx(r.Context())
|
||||
return uid, user, nil
|
||||
}
|
|
@ -1,134 +0,0 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/hay-kot/homebox/backend/internal/services"
|
||||
"github.com/hay-kot/homebox/backend/pkgs/server"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type (
|
||||
TokenResponse struct {
|
||||
Token string `json:"token"`
|
||||
ExpiresAt time.Time `json:"expiresAt"`
|
||||
}
|
||||
|
||||
LoginForm struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
)
|
||||
|
||||
// HandleAuthLogin godoc
|
||||
// @Summary User Login
|
||||
// @Tags Authentication
|
||||
// @Accept x-www-form-urlencoded
|
||||
// @Accept application/json
|
||||
// @Param username formData string false "string" example(admin@admin.com)
|
||||
// @Param password formData string false "string" example(admin)
|
||||
// @Produce json
|
||||
// @Success 200 {object} TokenResponse
|
||||
// @Router /v1/users/login [POST]
|
||||
func (ctrl *V1Controller) HandleAuthLogin() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
loginForm := &LoginForm{}
|
||||
|
||||
switch r.Header.Get("Content-Type") {
|
||||
case server.ContentFormUrlEncoded:
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
server.Respond(w, http.StatusBadRequest, server.Wrap(err))
|
||||
log.Error().Err(err).Msg("failed to parse form")
|
||||
return
|
||||
}
|
||||
|
||||
loginForm.Username = r.PostFormValue("username")
|
||||
loginForm.Password = r.PostFormValue("password")
|
||||
case server.ContentJSON:
|
||||
err := server.Decode(r, loginForm)
|
||||
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to decode login form")
|
||||
server.Respond(w, http.StatusBadRequest, server.Wrap(err))
|
||||
return
|
||||
}
|
||||
default:
|
||||
server.Respond(w, http.StatusBadRequest, errors.New("invalid content type"))
|
||||
return
|
||||
}
|
||||
|
||||
if loginForm.Username == "" || loginForm.Password == "" {
|
||||
server.RespondError(w, http.StatusBadRequest, errors.New("username and password are required"))
|
||||
return
|
||||
}
|
||||
|
||||
newToken, err := ctrl.svc.User.Login(r.Context(), loginForm.Username, loginForm.Password)
|
||||
|
||||
if err != nil {
|
||||
server.RespondError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
server.Respond(w, http.StatusOK, TokenResponse{
|
||||
Token: "Bearer " + newToken.Raw,
|
||||
ExpiresAt: newToken.ExpiresAt,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// HandleAuthLogout godoc
|
||||
// @Summary User Logout
|
||||
// @Tags Authentication
|
||||
// @Success 204
|
||||
// @Router /v1/users/logout [POST]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleAuthLogout() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
token := services.UseTokenCtx(r.Context())
|
||||
|
||||
if token == "" {
|
||||
server.RespondError(w, http.StatusUnauthorized, errors.New("no token within request context"))
|
||||
return
|
||||
}
|
||||
|
||||
err := ctrl.svc.User.Logout(r.Context(), token)
|
||||
|
||||
if err != nil {
|
||||
server.RespondError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
server.Respond(w, http.StatusNoContent, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleAuthLogout godoc
|
||||
// @Summary User Token Refresh
|
||||
// @Description handleAuthRefresh returns a handler that will issue a new token from an existing token.
|
||||
// @Description This does not validate that the user still exists within the database.
|
||||
// @Tags Authentication
|
||||
// @Success 200
|
||||
// @Router /v1/users/refresh [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleAuthRefresh() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
requestToken := services.UseTokenCtx(r.Context())
|
||||
|
||||
if requestToken == "" {
|
||||
server.RespondError(w, http.StatusUnauthorized, errors.New("no user token found"))
|
||||
return
|
||||
}
|
||||
|
||||
newToken, err := ctrl.svc.User.RenewToken(r.Context(), requestToken)
|
||||
|
||||
if err != nil {
|
||||
server.RespondUnauthorized(w)
|
||||
return
|
||||
}
|
||||
|
||||
server.Respond(w, http.StatusOK, newToken)
|
||||
}
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/hay-kot/homebox/backend/internal/services"
|
||||
"github.com/hay-kot/homebox/backend/pkgs/server"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type (
|
||||
GroupInvitationCreate struct {
|
||||
Uses int `json:"uses"`
|
||||
ExpiresAt time.Time `json:"expiresAt"`
|
||||
}
|
||||
|
||||
GroupInvitation struct {
|
||||
Token string `json:"token"`
|
||||
ExpiresAt time.Time `json:"expiresAt"`
|
||||
Uses int `json:"uses"`
|
||||
}
|
||||
)
|
||||
|
||||
// HandleUserSelf godoc
|
||||
// @Summary Get the current user
|
||||
// @Tags User
|
||||
// @Produce json
|
||||
// @Param payload body GroupInvitationCreate true "User Data"
|
||||
// @Success 200 {object} GroupInvitation
|
||||
// @Router /v1/groups/invitations [Post]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleGroupInvitationsCreate() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
data := GroupInvitationCreate{}
|
||||
|
||||
if err := server.Decode(r, &data); err != nil {
|
||||
log.Err(err).Msg("failed to decode user registration data")
|
||||
server.RespondError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
if data.ExpiresAt.IsZero() {
|
||||
data.ExpiresAt = time.Now().Add(time.Hour * 24)
|
||||
}
|
||||
|
||||
ctx := services.NewContext(r.Context())
|
||||
|
||||
token, err := ctrl.svc.User.NewInvitation(ctx, data.Uses, data.ExpiresAt)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to create new token")
|
||||
server.RespondError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
server.Respond(w, http.StatusCreated, GroupInvitation{
|
||||
Token: token,
|
||||
ExpiresAt: data.ExpiresAt,
|
||||
Uses: data.Uses,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,189 +0,0 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"net/http"
|
||||
|
||||
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||
"github.com/hay-kot/homebox/backend/internal/services"
|
||||
"github.com/hay-kot/homebox/backend/pkgs/server"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// HandleItemsGetAll godoc
|
||||
// @Summary Get All Items
|
||||
// @Tags Items
|
||||
// @Produce json
|
||||
// @Success 200 {object} server.Results{items=[]repo.ItemSummary}
|
||||
// @Router /v1/items [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemsGetAll() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
user := services.UseUserCtx(r.Context())
|
||||
items, err := ctrl.svc.Items.GetAll(r.Context(), user.GroupID)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to get items")
|
||||
server.RespondServerError(w)
|
||||
return
|
||||
}
|
||||
server.Respond(w, http.StatusOK, server.Results{Items: items})
|
||||
}
|
||||
}
|
||||
|
||||
// HandleItemsCreate godoc
|
||||
// @Summary Create a new item
|
||||
// @Tags Items
|
||||
// @Produce json
|
||||
// @Param payload body repo.ItemCreate true "Item Data"
|
||||
// @Success 200 {object} repo.ItemSummary
|
||||
// @Router /v1/items [POST]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemsCreate() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
createData := repo.ItemCreate{}
|
||||
if err := server.Decode(r, &createData); err != nil {
|
||||
log.Err(err).Msg("failed to decode request body")
|
||||
server.RespondError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
user := services.UseUserCtx(r.Context())
|
||||
item, err := ctrl.svc.Items.Create(r.Context(), user.GroupID, createData)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to create item")
|
||||
server.RespondServerError(w)
|
||||
return
|
||||
}
|
||||
|
||||
server.Respond(w, http.StatusCreated, item)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleItemDelete godocs
|
||||
// @Summary deletes a item
|
||||
// @Tags Items
|
||||
// @Produce json
|
||||
// @Param id path string true "Item ID"
|
||||
// @Success 204
|
||||
// @Router /v1/items/{id} [DELETE]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemDelete() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
uid, user, err := ctrl.partialParseIdAndUser(w, r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = ctrl.svc.Items.Delete(r.Context(), user.GroupID, uid)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to delete item")
|
||||
server.RespondServerError(w)
|
||||
return
|
||||
}
|
||||
server.Respond(w, http.StatusNoContent, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleItemGet godocs
|
||||
// @Summary Gets a item and fields
|
||||
// @Tags Items
|
||||
// @Produce json
|
||||
// @Param id path string true "Item ID"
|
||||
// @Success 200 {object} repo.ItemOut
|
||||
// @Router /v1/items/{id} [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemGet() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
uid, user, err := ctrl.partialParseIdAndUser(w, r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
items, err := ctrl.svc.Items.GetOne(r.Context(), user.GroupID, uid)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to get item")
|
||||
server.RespondServerError(w)
|
||||
return
|
||||
}
|
||||
server.Respond(w, http.StatusOK, items)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleItemUpdate godocs
|
||||
// @Summary updates a item
|
||||
// @Tags Items
|
||||
// @Produce json
|
||||
// @Param id path string true "Item ID"
|
||||
// @Param payload body repo.ItemUpdate true "Item Data"
|
||||
// @Success 200 {object} repo.ItemOut
|
||||
// @Router /v1/items/{id} [PUT]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemUpdate() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
body := repo.ItemUpdate{}
|
||||
if err := server.Decode(r, &body); err != nil {
|
||||
log.Err(err).Msg("failed to decode request body")
|
||||
server.RespondError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
uid, user, err := ctrl.partialParseIdAndUser(w, r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
body.ID = uid
|
||||
result, err := ctrl.svc.Items.Update(r.Context(), user.GroupID, body)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to update item")
|
||||
server.RespondServerError(w)
|
||||
return
|
||||
}
|
||||
server.Respond(w, http.StatusOK, result)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleItemsImport godocs
|
||||
// @Summary imports items into the database
|
||||
// @Tags Items
|
||||
// @Produce json
|
||||
// @Success 204
|
||||
// @Param csv formData file true "Image to upload"
|
||||
// @Router /v1/items/import [Post]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemsImport() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
err := r.ParseMultipartForm(ctrl.maxUploadSize << 20)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to parse multipart form")
|
||||
server.RespondServerError(w)
|
||||
return
|
||||
}
|
||||
|
||||
file, _, err := r.FormFile("csv")
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to get file from form")
|
||||
server.RespondServerError(w)
|
||||
return
|
||||
}
|
||||
|
||||
reader := csv.NewReader(file)
|
||||
data, err := reader.ReadAll()
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to read csv")
|
||||
server.RespondServerError(w)
|
||||
return
|
||||
}
|
||||
|
||||
user := services.UseUserCtx(r.Context())
|
||||
|
||||
err = ctrl.svc.Items.CsvImport(r.Context(), user.GroupID, data)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to import items")
|
||||
server.RespondServerError(w)
|
||||
return
|
||||
}
|
||||
|
||||
server.Respond(w, http.StatusNoContent, nil)
|
||||
}
|
||||
}
|
|
@ -1,242 +0,0 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/google/uuid"
|
||||
"github.com/hay-kot/homebox/backend/ent/attachment"
|
||||
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||
"github.com/hay-kot/homebox/backend/internal/services"
|
||||
"github.com/hay-kot/homebox/backend/pkgs/server"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type (
|
||||
ItemAttachmentToken struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
)
|
||||
|
||||
// HandleItemsImport godocs
|
||||
// @Summary imports items into the database
|
||||
// @Tags Items
|
||||
// @Produce json
|
||||
// @Param id path string true "Item ID"
|
||||
// @Param file formData file true "File attachment"
|
||||
// @Param type formData string true "Type of file"
|
||||
// @Param name formData string true "name of the file including extension"
|
||||
// @Success 200 {object} repo.ItemOut
|
||||
// @Failure 422 {object} []server.ValidationError
|
||||
// @Router /v1/items/{id}/attachments [POST]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemAttachmentCreate() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
err := r.ParseMultipartForm(ctrl.maxUploadSize << 20)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to parse multipart form")
|
||||
server.RespondError(w, http.StatusBadRequest, errors.New("failed to parse multipart form"))
|
||||
return
|
||||
}
|
||||
|
||||
errs := make(server.ValidationErrors, 0)
|
||||
|
||||
file, _, err := r.FormFile("file")
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, http.ErrMissingFile):
|
||||
log.Debug().Msg("file for attachment is missing")
|
||||
errs = errs.Append("file", "file is required")
|
||||
default:
|
||||
log.Err(err).Msg("failed to get file from form")
|
||||
server.RespondServerError(w)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
attachmentName := r.FormValue("name")
|
||||
if attachmentName == "" {
|
||||
log.Debug().Msg("failed to get name from form")
|
||||
errs = errs.Append("name", "name is required")
|
||||
}
|
||||
|
||||
if errs.HasErrors() {
|
||||
server.Respond(w, http.StatusUnprocessableEntity, errs)
|
||||
return
|
||||
}
|
||||
|
||||
attachmentType := r.FormValue("type")
|
||||
if attachmentType == "" {
|
||||
attachmentType = attachment.TypeAttachment.String()
|
||||
}
|
||||
|
||||
id, _, err := ctrl.partialParseIdAndUser(w, r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ctx := services.NewContext(r.Context())
|
||||
|
||||
item, err := ctrl.svc.Items.AttachmentAdd(
|
||||
ctx,
|
||||
id,
|
||||
attachmentName,
|
||||
attachment.Type(attachmentType),
|
||||
file,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to add attachment")
|
||||
server.RespondServerError(w)
|
||||
return
|
||||
}
|
||||
|
||||
server.Respond(w, http.StatusCreated, item)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleItemAttachmentGet godocs
|
||||
// @Summary retrieves an attachment for an item
|
||||
// @Tags Items
|
||||
// @Produce application/octet-stream
|
||||
// @Param id path string true "Item ID"
|
||||
// @Param token query string true "Attachment token"
|
||||
// @Success 200
|
||||
// @Router /v1/items/{id}/attachments/download [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemAttachmentDownload() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
token := server.GetParam(r, "token", "")
|
||||
|
||||
doc, err := ctrl.svc.Items.AttachmentPath(r.Context(), token)
|
||||
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to get attachment")
|
||||
server.RespondServerError(w)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", doc.Title))
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
http.ServeFile(w, r, doc.Path)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleItemAttachmentToken godocs
|
||||
// @Summary retrieves an attachment for an item
|
||||
// @Tags Items
|
||||
// @Produce application/octet-stream
|
||||
// @Param id path string true "Item ID"
|
||||
// @Param attachment_id path string true "Attachment ID"
|
||||
// @Success 200 {object} ItemAttachmentToken
|
||||
// @Router /v1/items/{id}/attachments/{attachment_id} [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemAttachmentToken() http.HandlerFunc {
|
||||
return ctrl.handleItemAttachmentsHandler
|
||||
}
|
||||
|
||||
// HandleItemAttachmentDelete godocs
|
||||
// @Summary retrieves an attachment for an item
|
||||
// @Tags Items
|
||||
// @Param id path string true "Item ID"
|
||||
// @Param attachment_id path string true "Attachment ID"
|
||||
// @Success 204
|
||||
// @Router /v1/items/{id}/attachments/{attachment_id} [DELETE]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemAttachmentDelete() http.HandlerFunc {
|
||||
return ctrl.handleItemAttachmentsHandler
|
||||
}
|
||||
|
||||
// HandleItemAttachmentUpdate godocs
|
||||
// @Summary retrieves an attachment for an item
|
||||
// @Tags Items
|
||||
// @Param id path string true "Item ID"
|
||||
// @Param attachment_id path string true "Attachment ID"
|
||||
// @Param payload body repo.ItemAttachmentUpdate true "Attachment Update"
|
||||
// @Success 200 {object} repo.ItemOut
|
||||
// @Router /v1/items/{id}/attachments/{attachment_id} [PUT]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemAttachmentUpdate() http.HandlerFunc {
|
||||
return ctrl.handleItemAttachmentsHandler
|
||||
}
|
||||
|
||||
func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
uid, user, err := ctrl.partialParseIdAndUser(w, r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
attachmentId, err := uuid.Parse(chi.URLParam(r, "attachment_id"))
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to parse attachment_id param")
|
||||
server.RespondError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := services.NewContext(r.Context())
|
||||
|
||||
switch r.Method {
|
||||
|
||||
// Token Handler
|
||||
case http.MethodGet:
|
||||
token, err := ctrl.svc.Items.AttachmentToken(ctx, uid, attachmentId)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case services.ErrNotFound:
|
||||
log.Err(err).
|
||||
Str("id", attachmentId.String()).
|
||||
Msg("failed to find attachment with id")
|
||||
|
||||
server.RespondError(w, http.StatusNotFound, err)
|
||||
|
||||
case services.ErrFileNotFound:
|
||||
log.Err(err).
|
||||
Str("id", attachmentId.String()).
|
||||
Msg("failed to find file path for attachment with id")
|
||||
log.Warn().Msg("attachment with no file path removed from database")
|
||||
|
||||
server.RespondError(w, http.StatusNotFound, err)
|
||||
|
||||
default:
|
||||
log.Err(err).Msg("failed to get attachment")
|
||||
server.RespondServerError(w)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
server.Respond(w, http.StatusOK, ItemAttachmentToken{Token: token})
|
||||
|
||||
// Delete Attachment Handler
|
||||
case http.MethodDelete:
|
||||
err = ctrl.svc.Items.AttachmentDelete(r.Context(), user.GroupID, uid, attachmentId)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to delete attachment")
|
||||
server.RespondServerError(w)
|
||||
return
|
||||
}
|
||||
|
||||
server.Respond(w, http.StatusNoContent, nil)
|
||||
|
||||
// Update Attachment Handler
|
||||
case http.MethodPut:
|
||||
var attachment repo.ItemAttachmentUpdate
|
||||
err = server.Decode(r, &attachment)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to decode attachment")
|
||||
server.RespondError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
attachment.ID = attachmentId
|
||||
val, err := ctrl.svc.Items.AttachmentUpdate(ctx, uid, &attachment)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to delete attachment")
|
||||
server.RespondServerError(w)
|
||||
return
|
||||
}
|
||||
|
||||
server.Respond(w, http.StatusOK, val)
|
||||
}
|
||||
}
|
|
@ -1,150 +0,0 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/hay-kot/homebox/backend/ent"
|
||||
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||
"github.com/hay-kot/homebox/backend/internal/services"
|
||||
"github.com/hay-kot/homebox/backend/pkgs/server"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// HandleLabelsGetAll godoc
|
||||
// @Summary Get All Labels
|
||||
// @Tags Labels
|
||||
// @Produce json
|
||||
// @Success 200 {object} server.Results{items=[]repo.LabelOut}
|
||||
// @Router /v1/labels [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleLabelsGetAll() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
user := services.UseUserCtx(r.Context())
|
||||
labels, err := ctrl.svc.Labels.GetAll(r.Context(), user.GroupID)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("error getting labels")
|
||||
server.RespondServerError(w)
|
||||
return
|
||||
}
|
||||
server.Respond(w, http.StatusOK, server.Results{Items: labels})
|
||||
}
|
||||
}
|
||||
|
||||
// HandleLabelsCreate godoc
|
||||
// @Summary Create a new label
|
||||
// @Tags Labels
|
||||
// @Produce json
|
||||
// @Param payload body repo.LabelCreate true "Label Data"
|
||||
// @Success 200 {object} repo.LabelSummary
|
||||
// @Router /v1/labels [POST]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleLabelsCreate() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
createData := repo.LabelCreate{}
|
||||
if err := server.Decode(r, &createData); err != nil {
|
||||
log.Err(err).Msg("error decoding label create data")
|
||||
server.RespondError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
user := services.UseUserCtx(r.Context())
|
||||
label, err := ctrl.svc.Labels.Create(r.Context(), user.GroupID, createData)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("error creating label")
|
||||
server.RespondServerError(w)
|
||||
return
|
||||
}
|
||||
|
||||
server.Respond(w, http.StatusCreated, label)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// HandleLabelDelete godocs
|
||||
// @Summary deletes a label
|
||||
// @Tags Labels
|
||||
// @Produce json
|
||||
// @Param id path string true "Label ID"
|
||||
// @Success 204
|
||||
// @Router /v1/labels/{id} [DELETE]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleLabelDelete() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
uid, user, err := ctrl.partialParseIdAndUser(w, r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = ctrl.svc.Labels.Delete(r.Context(), user.GroupID, uid)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("error deleting label")
|
||||
server.RespondServerError(w)
|
||||
return
|
||||
}
|
||||
server.Respond(w, http.StatusNoContent, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleLabelGet godocs
|
||||
// @Summary Gets a label and fields
|
||||
// @Tags Labels
|
||||
// @Produce json
|
||||
// @Param id path string true "Label ID"
|
||||
// @Success 200 {object} repo.LabelOut
|
||||
// @Router /v1/labels/{id} [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleLabelGet() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
uid, user, err := ctrl.partialParseIdAndUser(w, r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
labels, err := ctrl.svc.Labels.Get(r.Context(), user.GroupID, uid)
|
||||
if err != nil {
|
||||
if ent.IsNotFound(err) {
|
||||
log.Err(err).
|
||||
Str("id", uid.String()).
|
||||
Msg("label not found")
|
||||
server.RespondError(w, http.StatusNotFound, err)
|
||||
return
|
||||
}
|
||||
log.Err(err).Msg("error getting label")
|
||||
server.RespondServerError(w)
|
||||
return
|
||||
}
|
||||
server.Respond(w, http.StatusOK, labels)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleLabelUpdate godocs
|
||||
// @Summary updates a label
|
||||
// @Tags Labels
|
||||
// @Produce json
|
||||
// @Param id path string true "Label ID"
|
||||
// @Success 200 {object} repo.LabelOut
|
||||
// @Router /v1/labels/{id} [PUT]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleLabelUpdate() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
body := repo.LabelUpdate{}
|
||||
if err := server.Decode(r, &body); err != nil {
|
||||
log.Err(err).Msg("error decoding label update data")
|
||||
server.RespondError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
uid, user, err := ctrl.partialParseIdAndUser(w, r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
body.ID = uid
|
||||
result, err := ctrl.svc.Labels.Update(r.Context(), user.GroupID, body)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("error updating label")
|
||||
server.RespondServerError(w)
|
||||
return
|
||||
}
|
||||
server.Respond(w, http.StatusOK, result)
|
||||
}
|
||||
}
|
|
@ -1,157 +0,0 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/hay-kot/homebox/backend/ent"
|
||||
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||
"github.com/hay-kot/homebox/backend/internal/services"
|
||||
"github.com/hay-kot/homebox/backend/pkgs/server"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// HandleLocationGetAll godoc
|
||||
// @Summary Get All Locations
|
||||
// @Tags Locations
|
||||
// @Produce json
|
||||
// @Success 200 {object} server.Results{items=[]repo.LocationOutCount}
|
||||
// @Router /v1/locations [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleLocationGetAll() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
user := services.UseUserCtx(r.Context())
|
||||
locations, err := ctrl.svc.Location.GetAll(r.Context(), user.GroupID)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to get locations")
|
||||
server.RespondServerError(w)
|
||||
return
|
||||
}
|
||||
|
||||
server.Respond(w, http.StatusOK, server.Results{Items: locations})
|
||||
}
|
||||
}
|
||||
|
||||
// HandleLocationCreate godoc
|
||||
// @Summary Create a new location
|
||||
// @Tags Locations
|
||||
// @Produce json
|
||||
// @Param payload body repo.LocationCreate true "Location Data"
|
||||
// @Success 200 {object} repo.LocationSummary
|
||||
// @Router /v1/locations [POST]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleLocationCreate() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
createData := repo.LocationCreate{}
|
||||
if err := server.Decode(r, &createData); err != nil {
|
||||
log.Err(err).Msg("failed to decode location create data")
|
||||
server.RespondError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
user := services.UseUserCtx(r.Context())
|
||||
location, err := ctrl.svc.Location.Create(r.Context(), user.GroupID, createData)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to create location")
|
||||
server.RespondServerError(w)
|
||||
return
|
||||
}
|
||||
|
||||
server.Respond(w, http.StatusCreated, location)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleLocationDelete godocs
|
||||
// @Summary deletes a location
|
||||
// @Tags Locations
|
||||
// @Produce json
|
||||
// @Param id path string true "Location ID"
|
||||
// @Success 204
|
||||
// @Router /v1/locations/{id} [DELETE]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleLocationDelete() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
uid, user, err := ctrl.partialParseIdAndUser(w, r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = ctrl.svc.Location.Delete(r.Context(), user.GroupID, uid)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to delete location")
|
||||
server.RespondServerError(w)
|
||||
return
|
||||
}
|
||||
server.Respond(w, http.StatusNoContent, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleLocationGet godocs
|
||||
// @Summary Gets a location and fields
|
||||
// @Tags Locations
|
||||
// @Produce json
|
||||
// @Param id path string true "Location ID"
|
||||
// @Success 200 {object} repo.LocationOut
|
||||
// @Router /v1/locations/{id} [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleLocationGet() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
uid, user, err := ctrl.partialParseIdAndUser(w, r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
location, err := ctrl.svc.Location.GetOne(r.Context(), user.GroupID, uid)
|
||||
if err != nil {
|
||||
if ent.IsNotFound(err) {
|
||||
log.Err(err).
|
||||
Str("id", uid.String()).
|
||||
Str("gid", user.GroupID.String()).
|
||||
Msg("location not found")
|
||||
server.RespondError(w, http.StatusNotFound, err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Err(err).
|
||||
Str("id", uid.String()).
|
||||
Str("gid", user.GroupID.String()).
|
||||
Msg("failed to get location")
|
||||
server.RespondServerError(w)
|
||||
return
|
||||
}
|
||||
server.Respond(w, http.StatusOK, location)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleLocationUpdate godocs
|
||||
// @Summary updates a location
|
||||
// @Tags Locations
|
||||
// @Produce json
|
||||
// @Param id path string true "Location ID"
|
||||
// @Success 200 {object} repo.LocationOut
|
||||
// @Router /v1/locations/{id} [PUT]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleLocationUpdate() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
body := repo.LocationUpdate{}
|
||||
if err := server.Decode(r, &body); err != nil {
|
||||
log.Err(err).Msg("failed to decode location update data")
|
||||
server.RespondError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
uid, user, err := ctrl.partialParseIdAndUser(w, r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
body.ID = uid
|
||||
|
||||
result, err := ctrl.svc.Location.Update(r.Context(), user.GroupID, body)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to update location")
|
||||
server.RespondServerError(w)
|
||||
return
|
||||
}
|
||||
server.Respond(w, http.StatusOK, result)
|
||||
}
|
||||
}
|
|
@ -1,160 +0,0 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||
"github.com/hay-kot/homebox/backend/internal/services"
|
||||
"github.com/hay-kot/homebox/backend/pkgs/server"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// HandleUserSelf godoc
|
||||
// @Summary Get the current user
|
||||
// @Tags User
|
||||
// @Produce json
|
||||
// @Param payload body services.UserRegistration true "User Data"
|
||||
// @Success 204
|
||||
// @Router /v1/users/register [Post]
|
||||
func (ctrl *V1Controller) HandleUserRegistration() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
regData := services.UserRegistration{}
|
||||
|
||||
if err := server.Decode(r, ®Data); 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)
|
||||
}
|
||||
}
|
|
@ -2,10 +2,11 @@ package main
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/hay-kot/homebox/backend/ent/migrate"
|
||||
"github.com/hay-kot/homebox/backend/internal/data/ent/migrate"
|
||||
|
||||
atlas "ariga.io/atlas/sql/migrate"
|
||||
_ "ariga.io/atlas/sql/sqlite"
|
||||
|
@ -17,7 +18,7 @@ import (
|
|||
func main() {
|
||||
ctx := context.Background()
|
||||
// Create a local migration directory able to understand Atlas migration file format for replay.
|
||||
dir, err := atlas.NewLocalDir("internal/migrations/migrations")
|
||||
dir, err := atlas.NewLocalDir("internal/data/migrations/migrations")
|
||||
if err != nil {
|
||||
log.Fatalf("failed creating atlas migration directory: %v", err)
|
||||
}
|
||||
|
@ -39,4 +40,6 @@ func main() {
|
|||
if err != nil {
|
||||
log.Fatalf("failed generating migration file: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("Migration file generated successfully.")
|
||||
}
|
81
backend/app/tools/typegen/main.go
Normal file
81
backend/app/tools/typegen/main.go
Normal file
|
@ -0,0 +1,81 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type ReReplace struct {
|
||||
Regex *regexp.Regexp
|
||||
Text string
|
||||
}
|
||||
|
||||
func NewReReplace(regex string, replace string) ReReplace {
|
||||
return ReReplace{
|
||||
Regex: regexp.MustCompile(regex),
|
||||
Text: replace,
|
||||
}
|
||||
}
|
||||
|
||||
func NewReDate(dateStr string) ReReplace {
|
||||
return ReReplace{
|
||||
Regex: regexp.MustCompile(fmt.Sprintf(`%s: string`, dateStr)),
|
||||
Text: fmt.Sprintf(`%s: Date | string`, dateStr),
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) != 2 {
|
||||
fmt.Println("Please provide a file path as an argument")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
path := os.Args[1]
|
||||
|
||||
fmt.Printf("Processing %s\n", path)
|
||||
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
fmt.Printf("File %s does not exist\n", path)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
text := "/* post-processed by ./scripts/process-types.go */\n"
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
text += string(data)
|
||||
|
||||
replaces := [...]ReReplace{
|
||||
NewReReplace(` Repo`, " "),
|
||||
NewReReplace(` PaginationResultRepo`, " PaginationResult"),
|
||||
NewReReplace(` Services`, " "),
|
||||
NewReReplace(` V1`, " "),
|
||||
NewReReplace(`\?:`, ":"),
|
||||
NewReReplace(`(\w+):\s(.*null.*)`, "$1?: $2"), // make null union types optional
|
||||
NewReDate("createdAt"),
|
||||
NewReDate("updatedAt"),
|
||||
NewReDate("soldTime"),
|
||||
NewReDate("purchaseTime"),
|
||||
NewReDate("warrantyExpires"),
|
||||
NewReDate("expiresAt"),
|
||||
NewReDate("date"),
|
||||
NewReDate("completedDate"),
|
||||
NewReDate("scheduledDate"),
|
||||
}
|
||||
|
||||
for _, replace := range replaces {
|
||||
fmt.Printf("Replacing '%v' -> '%s'\n", replace.Regex, replace.Text)
|
||||
text = replace.Regex.ReplaceAllString(text, replace.Text)
|
||||
}
|
||||
|
||||
err = os.WriteFile(path, []byte(text), 0644)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,69 +0,0 @@
|
|||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package ent
|
||||
|
||||
import (
|
||||
"entgo.io/ent"
|
||||
"entgo.io/ent/dialect"
|
||||
)
|
||||
|
||||
// Option function to configure the client.
|
||||
type Option func(*config)
|
||||
|
||||
// Config is the configuration for the client and its builder.
|
||||
type config struct {
|
||||
// driver used for executing database requests.
|
||||
driver dialect.Driver
|
||||
// debug enable a debug logging.
|
||||
debug bool
|
||||
// log used for logging on debug mode.
|
||||
log func(...any)
|
||||
// hooks to execute on mutations.
|
||||
hooks *hooks
|
||||
}
|
||||
|
||||
// hooks per client, for fast access.
|
||||
type hooks struct {
|
||||
Attachment []ent.Hook
|
||||
AuthTokens []ent.Hook
|
||||
Document []ent.Hook
|
||||
DocumentToken []ent.Hook
|
||||
Group []ent.Hook
|
||||
GroupInvitationToken []ent.Hook
|
||||
Item []ent.Hook
|
||||
ItemField []ent.Hook
|
||||
Label []ent.Hook
|
||||
Location []ent.Hook
|
||||
User []ent.Hook
|
||||
}
|
||||
|
||||
// Options applies the options on the config object.
|
||||
func (c *config) options(opts ...Option) {
|
||||
for _, opt := range opts {
|
||||
opt(c)
|
||||
}
|
||||
if c.debug {
|
||||
c.driver = dialect.Debug(c.driver, c.log)
|
||||
}
|
||||
}
|
||||
|
||||
// Debug enables debug logging on the ent.Driver.
|
||||
func Debug() Option {
|
||||
return func(c *config) {
|
||||
c.debug = true
|
||||
}
|
||||
}
|
||||
|
||||
// Log sets the logging function for debug mode.
|
||||
func Log(fn func(...any)) Option {
|
||||
return func(c *config) {
|
||||
c.log = fn
|
||||
}
|
||||
}
|
||||
|
||||
// Driver configures the client driver.
|
||||
func Driver(driver dialect.Driver) Option {
|
||||
return func(c *config) {
|
||||
c.driver = driver
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package ent
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type clientCtxKey struct{}
|
||||
|
||||
// FromContext returns a Client stored inside a context, or nil if there isn't one.
|
||||
func FromContext(ctx context.Context) *Client {
|
||||
c, _ := ctx.Value(clientCtxKey{}).(*Client)
|
||||
return c
|
||||
}
|
||||
|
||||
// NewContext returns a new context with the given Client attached.
|
||||
func NewContext(parent context.Context, c *Client) context.Context {
|
||||
return context.WithValue(parent, clientCtxKey{}, c)
|
||||
}
|
||||
|
||||
type txCtxKey struct{}
|
||||
|
||||
// TxFromContext returns a Tx stored inside a context, or nil if there isn't one.
|
||||
func TxFromContext(ctx context.Context) *Tx {
|
||||
tx, _ := ctx.Value(txCtxKey{}).(*Tx)
|
||||
return tx
|
||||
}
|
||||
|
||||
// NewTxContext returns a new context with the given Tx attached.
|
||||
func NewTxContext(parent context.Context, tx *Tx) context.Context {
|
||||
return context.WithValue(parent, txCtxKey{}, tx)
|
||||
}
|
|
@ -1,190 +0,0 @@
|
|||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package ent
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"github.com/google/uuid"
|
||||
"github.com/hay-kot/homebox/backend/ent/document"
|
||||
"github.com/hay-kot/homebox/backend/ent/documenttoken"
|
||||
)
|
||||
|
||||
// DocumentToken is the model entity for the DocumentToken schema.
|
||||
type DocumentToken struct {
|
||||
config `json:"-"`
|
||||
// ID of the ent.
|
||||
ID uuid.UUID `json:"id,omitempty"`
|
||||
// CreatedAt holds the value of the "created_at" field.
|
||||
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||
// UpdatedAt holds the value of the "updated_at" field.
|
||||
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
||||
// Token holds the value of the "token" field.
|
||||
Token []byte `json:"token,omitempty"`
|
||||
// Uses holds the value of the "uses" field.
|
||||
Uses int `json:"uses,omitempty"`
|
||||
// ExpiresAt holds the value of the "expires_at" field.
|
||||
ExpiresAt time.Time `json:"expires_at,omitempty"`
|
||||
// Edges holds the relations/edges for other nodes in the graph.
|
||||
// The values are being populated by the DocumentTokenQuery when eager-loading is set.
|
||||
Edges DocumentTokenEdges `json:"edges"`
|
||||
document_document_tokens *uuid.UUID
|
||||
}
|
||||
|
||||
// DocumentTokenEdges holds the relations/edges for other nodes in the graph.
|
||||
type DocumentTokenEdges struct {
|
||||
// Document holds the value of the document edge.
|
||||
Document *Document `json:"document,omitempty"`
|
||||
// loadedTypes holds the information for reporting if a
|
||||
// type was loaded (or requested) in eager-loading or not.
|
||||
loadedTypes [1]bool
|
||||
}
|
||||
|
||||
// DocumentOrErr returns the Document value or an error if the edge
|
||||
// was not loaded in eager-loading, or loaded but was not found.
|
||||
func (e DocumentTokenEdges) DocumentOrErr() (*Document, error) {
|
||||
if e.loadedTypes[0] {
|
||||
if e.Document == nil {
|
||||
// Edge was loaded but was not found.
|
||||
return nil, &NotFoundError{label: document.Label}
|
||||
}
|
||||
return e.Document, nil
|
||||
}
|
||||
return nil, &NotLoadedError{edge: "document"}
|
||||
}
|
||||
|
||||
// scanValues returns the types for scanning values from sql.Rows.
|
||||
func (*DocumentToken) scanValues(columns []string) ([]any, error) {
|
||||
values := make([]any, len(columns))
|
||||
for i := range columns {
|
||||
switch columns[i] {
|
||||
case documenttoken.FieldToken:
|
||||
values[i] = new([]byte)
|
||||
case documenttoken.FieldUses:
|
||||
values[i] = new(sql.NullInt64)
|
||||
case documenttoken.FieldCreatedAt, documenttoken.FieldUpdatedAt, documenttoken.FieldExpiresAt:
|
||||
values[i] = new(sql.NullTime)
|
||||
case documenttoken.FieldID:
|
||||
values[i] = new(uuid.UUID)
|
||||
case documenttoken.ForeignKeys[0]: // document_document_tokens
|
||||
values[i] = &sql.NullScanner{S: new(uuid.UUID)}
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected column %q for type DocumentToken", columns[i])
|
||||
}
|
||||
}
|
||||
return values, nil
|
||||
}
|
||||
|
||||
// assignValues assigns the values that were returned from sql.Rows (after scanning)
|
||||
// to the DocumentToken fields.
|
||||
func (dt *DocumentToken) assignValues(columns []string, values []any) error {
|
||||
if m, n := len(values), len(columns); m < n {
|
||||
return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
|
||||
}
|
||||
for i := range columns {
|
||||
switch columns[i] {
|
||||
case documenttoken.FieldID:
|
||||
if value, ok := values[i].(*uuid.UUID); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field id", values[i])
|
||||
} else if value != nil {
|
||||
dt.ID = *value
|
||||
}
|
||||
case documenttoken.FieldCreatedAt:
|
||||
if value, ok := values[i].(*sql.NullTime); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field created_at", values[i])
|
||||
} else if value.Valid {
|
||||
dt.CreatedAt = value.Time
|
||||
}
|
||||
case documenttoken.FieldUpdatedAt:
|
||||
if value, ok := values[i].(*sql.NullTime); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field updated_at", values[i])
|
||||
} else if value.Valid {
|
||||
dt.UpdatedAt = value.Time
|
||||
}
|
||||
case documenttoken.FieldToken:
|
||||
if value, ok := values[i].(*[]byte); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field token", values[i])
|
||||
} else if value != nil {
|
||||
dt.Token = *value
|
||||
}
|
||||
case documenttoken.FieldUses:
|
||||
if value, ok := values[i].(*sql.NullInt64); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field uses", values[i])
|
||||
} else if value.Valid {
|
||||
dt.Uses = int(value.Int64)
|
||||
}
|
||||
case documenttoken.FieldExpiresAt:
|
||||
if value, ok := values[i].(*sql.NullTime); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field expires_at", values[i])
|
||||
} else if value.Valid {
|
||||
dt.ExpiresAt = value.Time
|
||||
}
|
||||
case documenttoken.ForeignKeys[0]:
|
||||
if value, ok := values[i].(*sql.NullScanner); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field document_document_tokens", values[i])
|
||||
} else if value.Valid {
|
||||
dt.document_document_tokens = new(uuid.UUID)
|
||||
*dt.document_document_tokens = *value.S.(*uuid.UUID)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// QueryDocument queries the "document" edge of the DocumentToken entity.
|
||||
func (dt *DocumentToken) QueryDocument() *DocumentQuery {
|
||||
return (&DocumentTokenClient{config: dt.config}).QueryDocument(dt)
|
||||
}
|
||||
|
||||
// Update returns a builder for updating this DocumentToken.
|
||||
// Note that you need to call DocumentToken.Unwrap() before calling this method if this DocumentToken
|
||||
// was returned from a transaction, and the transaction was committed or rolled back.
|
||||
func (dt *DocumentToken) Update() *DocumentTokenUpdateOne {
|
||||
return (&DocumentTokenClient{config: dt.config}).UpdateOne(dt)
|
||||
}
|
||||
|
||||
// Unwrap unwraps the DocumentToken entity that was returned from a transaction after it was closed,
|
||||
// so that all future queries will be executed through the driver which created the transaction.
|
||||
func (dt *DocumentToken) Unwrap() *DocumentToken {
|
||||
_tx, ok := dt.config.driver.(*txDriver)
|
||||
if !ok {
|
||||
panic("ent: DocumentToken is not a transactional entity")
|
||||
}
|
||||
dt.config.driver = _tx.drv
|
||||
return dt
|
||||
}
|
||||
|
||||
// String implements the fmt.Stringer.
|
||||
func (dt *DocumentToken) String() string {
|
||||
var builder strings.Builder
|
||||
builder.WriteString("DocumentToken(")
|
||||
builder.WriteString(fmt.Sprintf("id=%v, ", dt.ID))
|
||||
builder.WriteString("created_at=")
|
||||
builder.WriteString(dt.CreatedAt.Format(time.ANSIC))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("updated_at=")
|
||||
builder.WriteString(dt.UpdatedAt.Format(time.ANSIC))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("token=")
|
||||
builder.WriteString(fmt.Sprintf("%v", dt.Token))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("uses=")
|
||||
builder.WriteString(fmt.Sprintf("%v", dt.Uses))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("expires_at=")
|
||||
builder.WriteString(dt.ExpiresAt.Format(time.ANSIC))
|
||||
builder.WriteByte(')')
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
// DocumentTokens is a parsable slice of DocumentToken.
|
||||
type DocumentTokens []*DocumentToken
|
||||
|
||||
func (dt DocumentTokens) config(cfg config) {
|
||||
for _i := range dt {
|
||||
dt[_i].config = cfg
|
||||
}
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package documenttoken
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
// Label holds the string label denoting the documenttoken type in the database.
|
||||
Label = "document_token"
|
||||
// FieldID holds the string denoting the id field in the database.
|
||||
FieldID = "id"
|
||||
// FieldCreatedAt holds the string denoting the created_at field in the database.
|
||||
FieldCreatedAt = "created_at"
|
||||
// FieldUpdatedAt holds the string denoting the updated_at field in the database.
|
||||
FieldUpdatedAt = "updated_at"
|
||||
// FieldToken holds the string denoting the token field in the database.
|
||||
FieldToken = "token"
|
||||
// FieldUses holds the string denoting the uses field in the database.
|
||||
FieldUses = "uses"
|
||||
// FieldExpiresAt holds the string denoting the expires_at field in the database.
|
||||
FieldExpiresAt = "expires_at"
|
||||
// EdgeDocument holds the string denoting the document edge name in mutations.
|
||||
EdgeDocument = "document"
|
||||
// Table holds the table name of the documenttoken in the database.
|
||||
Table = "document_tokens"
|
||||
// DocumentTable is the table that holds the document relation/edge.
|
||||
DocumentTable = "document_tokens"
|
||||
// DocumentInverseTable is the table name for the Document entity.
|
||||
// It exists in this package in order to avoid circular dependency with the "document" package.
|
||||
DocumentInverseTable = "documents"
|
||||
// DocumentColumn is the table column denoting the document relation/edge.
|
||||
DocumentColumn = "document_document_tokens"
|
||||
)
|
||||
|
||||
// Columns holds all SQL columns for documenttoken fields.
|
||||
var Columns = []string{
|
||||
FieldID,
|
||||
FieldCreatedAt,
|
||||
FieldUpdatedAt,
|
||||
FieldToken,
|
||||
FieldUses,
|
||||
FieldExpiresAt,
|
||||
}
|
||||
|
||||
// ForeignKeys holds the SQL foreign-keys that are owned by the "document_tokens"
|
||||
// table and are not defined as standalone fields in the schema.
|
||||
var ForeignKeys = []string{
|
||||
"document_document_tokens",
|
||||
}
|
||||
|
||||
// ValidColumn reports if the column name is valid (part of the table columns).
|
||||
func ValidColumn(column string) bool {
|
||||
for i := range Columns {
|
||||
if column == Columns[i] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for i := range ForeignKeys {
|
||||
if column == ForeignKeys[i] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var (
|
||||
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
|
||||
DefaultCreatedAt func() time.Time
|
||||
// DefaultUpdatedAt holds the default value on creation for the "updated_at" field.
|
||||
DefaultUpdatedAt func() time.Time
|
||||
// UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field.
|
||||
UpdateDefaultUpdatedAt func() time.Time
|
||||
// TokenValidator is a validator for the "token" field. It is called by the builders before save.
|
||||
TokenValidator func([]byte) error
|
||||
// DefaultUses holds the default value on creation for the "uses" field.
|
||||
DefaultUses int
|
||||
// DefaultExpiresAt holds the default value on creation for the "expires_at" field.
|
||||
DefaultExpiresAt func() time.Time
|
||||
// DefaultID holds the default value on creation for the "id" field.
|
||||
DefaultID func() uuid.UUID
|
||||
)
|
|
@ -1,498 +0,0 @@
|
|||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package documenttoken
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||
"github.com/google/uuid"
|
||||
"github.com/hay-kot/homebox/backend/ent/predicate"
|
||||
)
|
||||
|
||||
// ID filters vertices based on their ID field.
|
||||
func ID(id uuid.UUID) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.EQ(s.C(FieldID), id))
|
||||
})
|
||||
}
|
||||
|
||||
// IDEQ applies the EQ predicate on the ID field.
|
||||
func IDEQ(id uuid.UUID) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.EQ(s.C(FieldID), id))
|
||||
})
|
||||
}
|
||||
|
||||
// IDNEQ applies the NEQ predicate on the ID field.
|
||||
func IDNEQ(id uuid.UUID) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.NEQ(s.C(FieldID), id))
|
||||
})
|
||||
}
|
||||
|
||||
// IDIn applies the In predicate on the ID field.
|
||||
func IDIn(ids ...uuid.UUID) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
v := make([]any, len(ids))
|
||||
for i := range v {
|
||||
v[i] = ids[i]
|
||||
}
|
||||
s.Where(sql.In(s.C(FieldID), v...))
|
||||
})
|
||||
}
|
||||
|
||||
// IDNotIn applies the NotIn predicate on the ID field.
|
||||
func IDNotIn(ids ...uuid.UUID) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
v := make([]any, len(ids))
|
||||
for i := range v {
|
||||
v[i] = ids[i]
|
||||
}
|
||||
s.Where(sql.NotIn(s.C(FieldID), v...))
|
||||
})
|
||||
}
|
||||
|
||||
// IDGT applies the GT predicate on the ID field.
|
||||
func IDGT(id uuid.UUID) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.GT(s.C(FieldID), id))
|
||||
})
|
||||
}
|
||||
|
||||
// IDGTE applies the GTE predicate on the ID field.
|
||||
func IDGTE(id uuid.UUID) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.GTE(s.C(FieldID), id))
|
||||
})
|
||||
}
|
||||
|
||||
// IDLT applies the LT predicate on the ID field.
|
||||
func IDLT(id uuid.UUID) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.LT(s.C(FieldID), id))
|
||||
})
|
||||
}
|
||||
|
||||
// IDLTE applies the LTE predicate on the ID field.
|
||||
func IDLTE(id uuid.UUID) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.LTE(s.C(FieldID), id))
|
||||
})
|
||||
}
|
||||
|
||||
// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ.
|
||||
func CreatedAt(v time.Time) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.EQ(s.C(FieldCreatedAt), v))
|
||||
})
|
||||
}
|
||||
|
||||
// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ.
|
||||
func UpdatedAt(v time.Time) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.EQ(s.C(FieldUpdatedAt), v))
|
||||
})
|
||||
}
|
||||
|
||||
// Token applies equality check predicate on the "token" field. It's identical to TokenEQ.
|
||||
func Token(v []byte) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.EQ(s.C(FieldToken), v))
|
||||
})
|
||||
}
|
||||
|
||||
// Uses applies equality check predicate on the "uses" field. It's identical to UsesEQ.
|
||||
func Uses(v int) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.EQ(s.C(FieldUses), v))
|
||||
})
|
||||
}
|
||||
|
||||
// ExpiresAt applies equality check predicate on the "expires_at" field. It's identical to ExpiresAtEQ.
|
||||
func ExpiresAt(v time.Time) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.EQ(s.C(FieldExpiresAt), v))
|
||||
})
|
||||
}
|
||||
|
||||
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
|
||||
func CreatedAtEQ(v time.Time) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.EQ(s.C(FieldCreatedAt), v))
|
||||
})
|
||||
}
|
||||
|
||||
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
|
||||
func CreatedAtNEQ(v time.Time) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.NEQ(s.C(FieldCreatedAt), v))
|
||||
})
|
||||
}
|
||||
|
||||
// CreatedAtIn applies the In predicate on the "created_at" field.
|
||||
func CreatedAtIn(vs ...time.Time) predicate.DocumentToken {
|
||||
v := make([]any, len(vs))
|
||||
for i := range v {
|
||||
v[i] = vs[i]
|
||||
}
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.In(s.C(FieldCreatedAt), v...))
|
||||
})
|
||||
}
|
||||
|
||||
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
|
||||
func CreatedAtNotIn(vs ...time.Time) predicate.DocumentToken {
|
||||
v := make([]any, len(vs))
|
||||
for i := range v {
|
||||
v[i] = vs[i]
|
||||
}
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.NotIn(s.C(FieldCreatedAt), v...))
|
||||
})
|
||||
}
|
||||
|
||||
// CreatedAtGT applies the GT predicate on the "created_at" field.
|
||||
func CreatedAtGT(v time.Time) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.GT(s.C(FieldCreatedAt), v))
|
||||
})
|
||||
}
|
||||
|
||||
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
|
||||
func CreatedAtGTE(v time.Time) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.GTE(s.C(FieldCreatedAt), v))
|
||||
})
|
||||
}
|
||||
|
||||
// CreatedAtLT applies the LT predicate on the "created_at" field.
|
||||
func CreatedAtLT(v time.Time) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.LT(s.C(FieldCreatedAt), v))
|
||||
})
|
||||
}
|
||||
|
||||
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
|
||||
func CreatedAtLTE(v time.Time) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.LTE(s.C(FieldCreatedAt), v))
|
||||
})
|
||||
}
|
||||
|
||||
// UpdatedAtEQ applies the EQ predicate on the "updated_at" field.
|
||||
func UpdatedAtEQ(v time.Time) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.EQ(s.C(FieldUpdatedAt), v))
|
||||
})
|
||||
}
|
||||
|
||||
// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field.
|
||||
func UpdatedAtNEQ(v time.Time) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.NEQ(s.C(FieldUpdatedAt), v))
|
||||
})
|
||||
}
|
||||
|
||||
// UpdatedAtIn applies the In predicate on the "updated_at" field.
|
||||
func UpdatedAtIn(vs ...time.Time) predicate.DocumentToken {
|
||||
v := make([]any, len(vs))
|
||||
for i := range v {
|
||||
v[i] = vs[i]
|
||||
}
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.In(s.C(FieldUpdatedAt), v...))
|
||||
})
|
||||
}
|
||||
|
||||
// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field.
|
||||
func UpdatedAtNotIn(vs ...time.Time) predicate.DocumentToken {
|
||||
v := make([]any, len(vs))
|
||||
for i := range v {
|
||||
v[i] = vs[i]
|
||||
}
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.NotIn(s.C(FieldUpdatedAt), v...))
|
||||
})
|
||||
}
|
||||
|
||||
// UpdatedAtGT applies the GT predicate on the "updated_at" field.
|
||||
func UpdatedAtGT(v time.Time) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.GT(s.C(FieldUpdatedAt), v))
|
||||
})
|
||||
}
|
||||
|
||||
// UpdatedAtGTE applies the GTE predicate on the "updated_at" field.
|
||||
func UpdatedAtGTE(v time.Time) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.GTE(s.C(FieldUpdatedAt), v))
|
||||
})
|
||||
}
|
||||
|
||||
// UpdatedAtLT applies the LT predicate on the "updated_at" field.
|
||||
func UpdatedAtLT(v time.Time) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.LT(s.C(FieldUpdatedAt), v))
|
||||
})
|
||||
}
|
||||
|
||||
// UpdatedAtLTE applies the LTE predicate on the "updated_at" field.
|
||||
func UpdatedAtLTE(v time.Time) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.LTE(s.C(FieldUpdatedAt), v))
|
||||
})
|
||||
}
|
||||
|
||||
// TokenEQ applies the EQ predicate on the "token" field.
|
||||
func TokenEQ(v []byte) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.EQ(s.C(FieldToken), v))
|
||||
})
|
||||
}
|
||||
|
||||
// TokenNEQ applies the NEQ predicate on the "token" field.
|
||||
func TokenNEQ(v []byte) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.NEQ(s.C(FieldToken), v))
|
||||
})
|
||||
}
|
||||
|
||||
// TokenIn applies the In predicate on the "token" field.
|
||||
func TokenIn(vs ...[]byte) predicate.DocumentToken {
|
||||
v := make([]any, len(vs))
|
||||
for i := range v {
|
||||
v[i] = vs[i]
|
||||
}
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.In(s.C(FieldToken), v...))
|
||||
})
|
||||
}
|
||||
|
||||
// TokenNotIn applies the NotIn predicate on the "token" field.
|
||||
func TokenNotIn(vs ...[]byte) predicate.DocumentToken {
|
||||
v := make([]any, len(vs))
|
||||
for i := range v {
|
||||
v[i] = vs[i]
|
||||
}
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.NotIn(s.C(FieldToken), v...))
|
||||
})
|
||||
}
|
||||
|
||||
// TokenGT applies the GT predicate on the "token" field.
|
||||
func TokenGT(v []byte) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.GT(s.C(FieldToken), v))
|
||||
})
|
||||
}
|
||||
|
||||
// TokenGTE applies the GTE predicate on the "token" field.
|
||||
func TokenGTE(v []byte) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.GTE(s.C(FieldToken), v))
|
||||
})
|
||||
}
|
||||
|
||||
// TokenLT applies the LT predicate on the "token" field.
|
||||
func TokenLT(v []byte) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.LT(s.C(FieldToken), v))
|
||||
})
|
||||
}
|
||||
|
||||
// TokenLTE applies the LTE predicate on the "token" field.
|
||||
func TokenLTE(v []byte) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.LTE(s.C(FieldToken), v))
|
||||
})
|
||||
}
|
||||
|
||||
// UsesEQ applies the EQ predicate on the "uses" field.
|
||||
func UsesEQ(v int) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.EQ(s.C(FieldUses), v))
|
||||
})
|
||||
}
|
||||
|
||||
// UsesNEQ applies the NEQ predicate on the "uses" field.
|
||||
func UsesNEQ(v int) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.NEQ(s.C(FieldUses), v))
|
||||
})
|
||||
}
|
||||
|
||||
// UsesIn applies the In predicate on the "uses" field.
|
||||
func UsesIn(vs ...int) predicate.DocumentToken {
|
||||
v := make([]any, len(vs))
|
||||
for i := range v {
|
||||
v[i] = vs[i]
|
||||
}
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.In(s.C(FieldUses), v...))
|
||||
})
|
||||
}
|
||||
|
||||
// UsesNotIn applies the NotIn predicate on the "uses" field.
|
||||
func UsesNotIn(vs ...int) predicate.DocumentToken {
|
||||
v := make([]any, len(vs))
|
||||
for i := range v {
|
||||
v[i] = vs[i]
|
||||
}
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.NotIn(s.C(FieldUses), v...))
|
||||
})
|
||||
}
|
||||
|
||||
// UsesGT applies the GT predicate on the "uses" field.
|
||||
func UsesGT(v int) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.GT(s.C(FieldUses), v))
|
||||
})
|
||||
}
|
||||
|
||||
// UsesGTE applies the GTE predicate on the "uses" field.
|
||||
func UsesGTE(v int) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.GTE(s.C(FieldUses), v))
|
||||
})
|
||||
}
|
||||
|
||||
// UsesLT applies the LT predicate on the "uses" field.
|
||||
func UsesLT(v int) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.LT(s.C(FieldUses), v))
|
||||
})
|
||||
}
|
||||
|
||||
// UsesLTE applies the LTE predicate on the "uses" field.
|
||||
func UsesLTE(v int) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.LTE(s.C(FieldUses), v))
|
||||
})
|
||||
}
|
||||
|
||||
// ExpiresAtEQ applies the EQ predicate on the "expires_at" field.
|
||||
func ExpiresAtEQ(v time.Time) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.EQ(s.C(FieldExpiresAt), v))
|
||||
})
|
||||
}
|
||||
|
||||
// ExpiresAtNEQ applies the NEQ predicate on the "expires_at" field.
|
||||
func ExpiresAtNEQ(v time.Time) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.NEQ(s.C(FieldExpiresAt), v))
|
||||
})
|
||||
}
|
||||
|
||||
// ExpiresAtIn applies the In predicate on the "expires_at" field.
|
||||
func ExpiresAtIn(vs ...time.Time) predicate.DocumentToken {
|
||||
v := make([]any, len(vs))
|
||||
for i := range v {
|
||||
v[i] = vs[i]
|
||||
}
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.In(s.C(FieldExpiresAt), v...))
|
||||
})
|
||||
}
|
||||
|
||||
// ExpiresAtNotIn applies the NotIn predicate on the "expires_at" field.
|
||||
func ExpiresAtNotIn(vs ...time.Time) predicate.DocumentToken {
|
||||
v := make([]any, len(vs))
|
||||
for i := range v {
|
||||
v[i] = vs[i]
|
||||
}
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.NotIn(s.C(FieldExpiresAt), v...))
|
||||
})
|
||||
}
|
||||
|
||||
// ExpiresAtGT applies the GT predicate on the "expires_at" field.
|
||||
func ExpiresAtGT(v time.Time) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.GT(s.C(FieldExpiresAt), v))
|
||||
})
|
||||
}
|
||||
|
||||
// ExpiresAtGTE applies the GTE predicate on the "expires_at" field.
|
||||
func ExpiresAtGTE(v time.Time) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.GTE(s.C(FieldExpiresAt), v))
|
||||
})
|
||||
}
|
||||
|
||||
// ExpiresAtLT applies the LT predicate on the "expires_at" field.
|
||||
func ExpiresAtLT(v time.Time) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.LT(s.C(FieldExpiresAt), v))
|
||||
})
|
||||
}
|
||||
|
||||
// ExpiresAtLTE applies the LTE predicate on the "expires_at" field.
|
||||
func ExpiresAtLTE(v time.Time) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s.Where(sql.LTE(s.C(FieldExpiresAt), v))
|
||||
})
|
||||
}
|
||||
|
||||
// HasDocument applies the HasEdge predicate on the "document" edge.
|
||||
func HasDocument() predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
step := sqlgraph.NewStep(
|
||||
sqlgraph.From(Table, FieldID),
|
||||
sqlgraph.To(DocumentTable, FieldID),
|
||||
sqlgraph.Edge(sqlgraph.M2O, true, DocumentTable, DocumentColumn),
|
||||
)
|
||||
sqlgraph.HasNeighbors(s, step)
|
||||
})
|
||||
}
|
||||
|
||||
// HasDocumentWith applies the HasEdge predicate on the "document" edge with a given conditions (other predicates).
|
||||
func HasDocumentWith(preds ...predicate.Document) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
step := sqlgraph.NewStep(
|
||||
sqlgraph.From(Table, FieldID),
|
||||
sqlgraph.To(DocumentInverseTable, FieldID),
|
||||
sqlgraph.Edge(sqlgraph.M2O, true, DocumentTable, DocumentColumn),
|
||||
)
|
||||
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
|
||||
for _, p := range preds {
|
||||
p(s)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// And groups predicates with the AND operator between them.
|
||||
func And(predicates ...predicate.DocumentToken) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s1 := s.Clone().SetP(nil)
|
||||
for _, p := range predicates {
|
||||
p(s1)
|
||||
}
|
||||
s.Where(s1.P())
|
||||
})
|
||||
}
|
||||
|
||||
// Or groups predicates with the OR operator between them.
|
||||
func Or(predicates ...predicate.DocumentToken) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
s1 := s.Clone().SetP(nil)
|
||||
for i, p := range predicates {
|
||||
if i > 0 {
|
||||
s1.Or()
|
||||
}
|
||||
p(s1)
|
||||
}
|
||||
s.Where(s1.P())
|
||||
})
|
||||
}
|
||||
|
||||
// Not applies the not operator on the given predicate.
|
||||
func Not(p predicate.DocumentToken) predicate.DocumentToken {
|
||||
return predicate.DocumentToken(func(s *sql.Selector) {
|
||||
p(s.Not())
|
||||
})
|
||||
}
|
|
@ -1,418 +0,0 @@
|
|||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package ent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||
"entgo.io/ent/schema/field"
|
||||
"github.com/google/uuid"
|
||||
"github.com/hay-kot/homebox/backend/ent/document"
|
||||
"github.com/hay-kot/homebox/backend/ent/documenttoken"
|
||||
)
|
||||
|
||||
// DocumentTokenCreate is the builder for creating a DocumentToken entity.
|
||||
type DocumentTokenCreate struct {
|
||||
config
|
||||
mutation *DocumentTokenMutation
|
||||
hooks []Hook
|
||||
}
|
||||
|
||||
// SetCreatedAt sets the "created_at" field.
|
||||
func (dtc *DocumentTokenCreate) SetCreatedAt(t time.Time) *DocumentTokenCreate {
|
||||
dtc.mutation.SetCreatedAt(t)
|
||||
return dtc
|
||||
}
|
||||
|
||||
// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
|
||||
func (dtc *DocumentTokenCreate) SetNillableCreatedAt(t *time.Time) *DocumentTokenCreate {
|
||||
if t != nil {
|
||||
dtc.SetCreatedAt(*t)
|
||||
}
|
||||
return dtc
|
||||
}
|
||||
|
||||
// SetUpdatedAt sets the "updated_at" field.
|
||||
func (dtc *DocumentTokenCreate) SetUpdatedAt(t time.Time) *DocumentTokenCreate {
|
||||
dtc.mutation.SetUpdatedAt(t)
|
||||
return dtc
|
||||
}
|
||||
|
||||
// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil.
|
||||
func (dtc *DocumentTokenCreate) SetNillableUpdatedAt(t *time.Time) *DocumentTokenCreate {
|
||||
if t != nil {
|
||||
dtc.SetUpdatedAt(*t)
|
||||
}
|
||||
return dtc
|
||||
}
|
||||
|
||||
// SetToken sets the "token" field.
|
||||
func (dtc *DocumentTokenCreate) SetToken(b []byte) *DocumentTokenCreate {
|
||||
dtc.mutation.SetToken(b)
|
||||
return dtc
|
||||
}
|
||||
|
||||
// SetUses sets the "uses" field.
|
||||
func (dtc *DocumentTokenCreate) SetUses(i int) *DocumentTokenCreate {
|
||||
dtc.mutation.SetUses(i)
|
||||
return dtc
|
||||
}
|
||||
|
||||
// SetNillableUses sets the "uses" field if the given value is not nil.
|
||||
func (dtc *DocumentTokenCreate) SetNillableUses(i *int) *DocumentTokenCreate {
|
||||
if i != nil {
|
||||
dtc.SetUses(*i)
|
||||
}
|
||||
return dtc
|
||||
}
|
||||
|
||||
// SetExpiresAt sets the "expires_at" field.
|
||||
func (dtc *DocumentTokenCreate) SetExpiresAt(t time.Time) *DocumentTokenCreate {
|
||||
dtc.mutation.SetExpiresAt(t)
|
||||
return dtc
|
||||
}
|
||||
|
||||
// SetNillableExpiresAt sets the "expires_at" field if the given value is not nil.
|
||||
func (dtc *DocumentTokenCreate) SetNillableExpiresAt(t *time.Time) *DocumentTokenCreate {
|
||||
if t != nil {
|
||||
dtc.SetExpiresAt(*t)
|
||||
}
|
||||
return dtc
|
||||
}
|
||||
|
||||
// SetID sets the "id" field.
|
||||
func (dtc *DocumentTokenCreate) SetID(u uuid.UUID) *DocumentTokenCreate {
|
||||
dtc.mutation.SetID(u)
|
||||
return dtc
|
||||
}
|
||||
|
||||
// SetNillableID sets the "id" field if the given value is not nil.
|
||||
func (dtc *DocumentTokenCreate) SetNillableID(u *uuid.UUID) *DocumentTokenCreate {
|
||||
if u != nil {
|
||||
dtc.SetID(*u)
|
||||
}
|
||||
return dtc
|
||||
}
|
||||
|
||||
// SetDocumentID sets the "document" edge to the Document entity by ID.
|
||||
func (dtc *DocumentTokenCreate) SetDocumentID(id uuid.UUID) *DocumentTokenCreate {
|
||||
dtc.mutation.SetDocumentID(id)
|
||||
return dtc
|
||||
}
|
||||
|
||||
// SetNillableDocumentID sets the "document" edge to the Document entity by ID if the given value is not nil.
|
||||
func (dtc *DocumentTokenCreate) SetNillableDocumentID(id *uuid.UUID) *DocumentTokenCreate {
|
||||
if id != nil {
|
||||
dtc = dtc.SetDocumentID(*id)
|
||||
}
|
||||
return dtc
|
||||
}
|
||||
|
||||
// SetDocument sets the "document" edge to the Document entity.
|
||||
func (dtc *DocumentTokenCreate) SetDocument(d *Document) *DocumentTokenCreate {
|
||||
return dtc.SetDocumentID(d.ID)
|
||||
}
|
||||
|
||||
// Mutation returns the DocumentTokenMutation object of the builder.
|
||||
func (dtc *DocumentTokenCreate) Mutation() *DocumentTokenMutation {
|
||||
return dtc.mutation
|
||||
}
|
||||
|
||||
// Save creates the DocumentToken in the database.
|
||||
func (dtc *DocumentTokenCreate) Save(ctx context.Context) (*DocumentToken, error) {
|
||||
var (
|
||||
err error
|
||||
node *DocumentToken
|
||||
)
|
||||
dtc.defaults()
|
||||
if len(dtc.hooks) == 0 {
|
||||
if err = dtc.check(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
node, err = dtc.sqlSave(ctx)
|
||||
} else {
|
||||
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
|
||||
mutation, ok := m.(*DocumentTokenMutation)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected mutation type %T", m)
|
||||
}
|
||||
if err = dtc.check(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dtc.mutation = mutation
|
||||
if node, err = dtc.sqlSave(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mutation.id = &node.ID
|
||||
mutation.done = true
|
||||
return node, err
|
||||
})
|
||||
for i := len(dtc.hooks) - 1; i >= 0; i-- {
|
||||
if dtc.hooks[i] == nil {
|
||||
return nil, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)")
|
||||
}
|
||||
mut = dtc.hooks[i](mut)
|
||||
}
|
||||
v, err := mut.Mutate(ctx, dtc.mutation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nv, ok := v.(*DocumentToken)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected node type %T returned from DocumentTokenMutation", v)
|
||||
}
|
||||
node = nv
|
||||
}
|
||||
return node, err
|
||||
}
|
||||
|
||||
// SaveX calls Save and panics if Save returns an error.
|
||||
func (dtc *DocumentTokenCreate) SaveX(ctx context.Context) *DocumentToken {
|
||||
v, err := dtc.Save(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Exec executes the query.
|
||||
func (dtc *DocumentTokenCreate) Exec(ctx context.Context) error {
|
||||
_, err := dtc.Save(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// ExecX is like Exec, but panics if an error occurs.
|
||||
func (dtc *DocumentTokenCreate) ExecX(ctx context.Context) {
|
||||
if err := dtc.Exec(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// defaults sets the default values of the builder before save.
|
||||
func (dtc *DocumentTokenCreate) defaults() {
|
||||
if _, ok := dtc.mutation.CreatedAt(); !ok {
|
||||
v := documenttoken.DefaultCreatedAt()
|
||||
dtc.mutation.SetCreatedAt(v)
|
||||
}
|
||||
if _, ok := dtc.mutation.UpdatedAt(); !ok {
|
||||
v := documenttoken.DefaultUpdatedAt()
|
||||
dtc.mutation.SetUpdatedAt(v)
|
||||
}
|
||||
if _, ok := dtc.mutation.Uses(); !ok {
|
||||
v := documenttoken.DefaultUses
|
||||
dtc.mutation.SetUses(v)
|
||||
}
|
||||
if _, ok := dtc.mutation.ExpiresAt(); !ok {
|
||||
v := documenttoken.DefaultExpiresAt()
|
||||
dtc.mutation.SetExpiresAt(v)
|
||||
}
|
||||
if _, ok := dtc.mutation.ID(); !ok {
|
||||
v := documenttoken.DefaultID()
|
||||
dtc.mutation.SetID(v)
|
||||
}
|
||||
}
|
||||
|
||||
// check runs all checks and user-defined validators on the builder.
|
||||
func (dtc *DocumentTokenCreate) check() error {
|
||||
if _, ok := dtc.mutation.CreatedAt(); !ok {
|
||||
return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "DocumentToken.created_at"`)}
|
||||
}
|
||||
if _, ok := dtc.mutation.UpdatedAt(); !ok {
|
||||
return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "DocumentToken.updated_at"`)}
|
||||
}
|
||||
if _, ok := dtc.mutation.Token(); !ok {
|
||||
return &ValidationError{Name: "token", err: errors.New(`ent: missing required field "DocumentToken.token"`)}
|
||||
}
|
||||
if v, ok := dtc.mutation.Token(); ok {
|
||||
if err := documenttoken.TokenValidator(v); err != nil {
|
||||
return &ValidationError{Name: "token", err: fmt.Errorf(`ent: validator failed for field "DocumentToken.token": %w`, err)}
|
||||
}
|
||||
}
|
||||
if _, ok := dtc.mutation.Uses(); !ok {
|
||||
return &ValidationError{Name: "uses", err: errors.New(`ent: missing required field "DocumentToken.uses"`)}
|
||||
}
|
||||
if _, ok := dtc.mutation.ExpiresAt(); !ok {
|
||||
return &ValidationError{Name: "expires_at", err: errors.New(`ent: missing required field "DocumentToken.expires_at"`)}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dtc *DocumentTokenCreate) sqlSave(ctx context.Context) (*DocumentToken, error) {
|
||||
_node, _spec := dtc.createSpec()
|
||||
if err := sqlgraph.CreateNode(ctx, dtc.driver, _spec); err != nil {
|
||||
if sqlgraph.IsConstraintError(err) {
|
||||
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if _spec.ID.Value != nil {
|
||||
if id, ok := _spec.ID.Value.(*uuid.UUID); ok {
|
||||
_node.ID = *id
|
||||
} else if err := _node.ID.Scan(_spec.ID.Value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return _node, nil
|
||||
}
|
||||
|
||||
func (dtc *DocumentTokenCreate) createSpec() (*DocumentToken, *sqlgraph.CreateSpec) {
|
||||
var (
|
||||
_node = &DocumentToken{config: dtc.config}
|
||||
_spec = &sqlgraph.CreateSpec{
|
||||
Table: documenttoken.Table,
|
||||
ID: &sqlgraph.FieldSpec{
|
||||
Type: field.TypeUUID,
|
||||
Column: documenttoken.FieldID,
|
||||
},
|
||||
}
|
||||
)
|
||||
if id, ok := dtc.mutation.ID(); ok {
|
||||
_node.ID = id
|
||||
_spec.ID.Value = &id
|
||||
}
|
||||
if value, ok := dtc.mutation.CreatedAt(); ok {
|
||||
_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
|
||||
Type: field.TypeTime,
|
||||
Value: value,
|
||||
Column: documenttoken.FieldCreatedAt,
|
||||
})
|
||||
_node.CreatedAt = value
|
||||
}
|
||||
if value, ok := dtc.mutation.UpdatedAt(); ok {
|
||||
_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
|
||||
Type: field.TypeTime,
|
||||
Value: value,
|
||||
Column: documenttoken.FieldUpdatedAt,
|
||||
})
|
||||
_node.UpdatedAt = value
|
||||
}
|
||||
if value, ok := dtc.mutation.Token(); ok {
|
||||
_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
|
||||
Type: field.TypeBytes,
|
||||
Value: value,
|
||||
Column: documenttoken.FieldToken,
|
||||
})
|
||||
_node.Token = value
|
||||
}
|
||||
if value, ok := dtc.mutation.Uses(); ok {
|
||||
_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
|
||||
Type: field.TypeInt,
|
||||
Value: value,
|
||||
Column: documenttoken.FieldUses,
|
||||
})
|
||||
_node.Uses = value
|
||||
}
|
||||
if value, ok := dtc.mutation.ExpiresAt(); ok {
|
||||
_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
|
||||
Type: field.TypeTime,
|
||||
Value: value,
|
||||
Column: documenttoken.FieldExpiresAt,
|
||||
})
|
||||
_node.ExpiresAt = value
|
||||
}
|
||||
if nodes := dtc.mutation.DocumentIDs(); len(nodes) > 0 {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.M2O,
|
||||
Inverse: true,
|
||||
Table: documenttoken.DocumentTable,
|
||||
Columns: []string{documenttoken.DocumentColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: &sqlgraph.FieldSpec{
|
||||
Type: field.TypeUUID,
|
||||
Column: document.FieldID,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, k := range nodes {
|
||||
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||
}
|
||||
_node.document_document_tokens = &nodes[0]
|
||||
_spec.Edges = append(_spec.Edges, edge)
|
||||
}
|
||||
return _node, _spec
|
||||
}
|
||||
|
||||
// DocumentTokenCreateBulk is the builder for creating many DocumentToken entities in bulk.
|
||||
type DocumentTokenCreateBulk struct {
|
||||
config
|
||||
builders []*DocumentTokenCreate
|
||||
}
|
||||
|
||||
// Save creates the DocumentToken entities in the database.
|
||||
func (dtcb *DocumentTokenCreateBulk) Save(ctx context.Context) ([]*DocumentToken, error) {
|
||||
specs := make([]*sqlgraph.CreateSpec, len(dtcb.builders))
|
||||
nodes := make([]*DocumentToken, len(dtcb.builders))
|
||||
mutators := make([]Mutator, len(dtcb.builders))
|
||||
for i := range dtcb.builders {
|
||||
func(i int, root context.Context) {
|
||||
builder := dtcb.builders[i]
|
||||
builder.defaults()
|
||||
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
|
||||
mutation, ok := m.(*DocumentTokenMutation)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected mutation type %T", m)
|
||||
}
|
||||
if err := builder.check(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
builder.mutation = mutation
|
||||
nodes[i], specs[i] = builder.createSpec()
|
||||
var err error
|
||||
if i < len(mutators)-1 {
|
||||
_, err = mutators[i+1].Mutate(root, dtcb.builders[i+1].mutation)
|
||||
} else {
|
||||
spec := &sqlgraph.BatchCreateSpec{Nodes: specs}
|
||||
// Invoke the actual operation on the latest mutation in the chain.
|
||||
if err = sqlgraph.BatchCreate(ctx, dtcb.driver, spec); err != nil {
|
||||
if sqlgraph.IsConstraintError(err) {
|
||||
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mutation.id = &nodes[i].ID
|
||||
mutation.done = true
|
||||
return nodes[i], nil
|
||||
})
|
||||
for i := len(builder.hooks) - 1; i >= 0; i-- {
|
||||
mut = builder.hooks[i](mut)
|
||||
}
|
||||
mutators[i] = mut
|
||||
}(i, ctx)
|
||||
}
|
||||
if len(mutators) > 0 {
|
||||
if _, err := mutators[0].Mutate(ctx, dtcb.builders[0].mutation); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
// SaveX is like Save, but panics if an error occurs.
|
||||
func (dtcb *DocumentTokenCreateBulk) SaveX(ctx context.Context) []*DocumentToken {
|
||||
v, err := dtcb.Save(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Exec executes the query.
|
||||
func (dtcb *DocumentTokenCreateBulk) Exec(ctx context.Context) error {
|
||||
_, err := dtcb.Save(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// ExecX is like Exec, but panics if an error occurs.
|
||||
func (dtcb *DocumentTokenCreateBulk) ExecX(ctx context.Context) {
|
||||
if err := dtcb.Exec(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package ent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||
"entgo.io/ent/schema/field"
|
||||
"github.com/hay-kot/homebox/backend/ent/documenttoken"
|
||||
"github.com/hay-kot/homebox/backend/ent/predicate"
|
||||
)
|
||||
|
||||
// DocumentTokenDelete is the builder for deleting a DocumentToken entity.
|
||||
type DocumentTokenDelete struct {
|
||||
config
|
||||
hooks []Hook
|
||||
mutation *DocumentTokenMutation
|
||||
}
|
||||
|
||||
// Where appends a list predicates to the DocumentTokenDelete builder.
|
||||
func (dtd *DocumentTokenDelete) Where(ps ...predicate.DocumentToken) *DocumentTokenDelete {
|
||||
dtd.mutation.Where(ps...)
|
||||
return dtd
|
||||
}
|
||||
|
||||
// Exec executes the deletion query and returns how many vertices were deleted.
|
||||
func (dtd *DocumentTokenDelete) Exec(ctx context.Context) (int, error) {
|
||||
var (
|
||||
err error
|
||||
affected int
|
||||
)
|
||||
if len(dtd.hooks) == 0 {
|
||||
affected, err = dtd.sqlExec(ctx)
|
||||
} else {
|
||||
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
|
||||
mutation, ok := m.(*DocumentTokenMutation)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected mutation type %T", m)
|
||||
}
|
||||
dtd.mutation = mutation
|
||||
affected, err = dtd.sqlExec(ctx)
|
||||
mutation.done = true
|
||||
return affected, err
|
||||
})
|
||||
for i := len(dtd.hooks) - 1; i >= 0; i-- {
|
||||
if dtd.hooks[i] == nil {
|
||||
return 0, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)")
|
||||
}
|
||||
mut = dtd.hooks[i](mut)
|
||||
}
|
||||
if _, err := mut.Mutate(ctx, dtd.mutation); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return affected, err
|
||||
}
|
||||
|
||||
// ExecX is like Exec, but panics if an error occurs.
|
||||
func (dtd *DocumentTokenDelete) ExecX(ctx context.Context) int {
|
||||
n, err := dtd.Exec(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (dtd *DocumentTokenDelete) sqlExec(ctx context.Context) (int, error) {
|
||||
_spec := &sqlgraph.DeleteSpec{
|
||||
Node: &sqlgraph.NodeSpec{
|
||||
Table: documenttoken.Table,
|
||||
ID: &sqlgraph.FieldSpec{
|
||||
Type: field.TypeUUID,
|
||||
Column: documenttoken.FieldID,
|
||||
},
|
||||
},
|
||||
}
|
||||
if ps := dtd.mutation.predicates; len(ps) > 0 {
|
||||
_spec.Predicate = func(selector *sql.Selector) {
|
||||
for i := range ps {
|
||||
ps[i](selector)
|
||||
}
|
||||
}
|
||||
}
|
||||
affected, err := sqlgraph.DeleteNodes(ctx, dtd.driver, _spec)
|
||||
if err != nil && sqlgraph.IsConstraintError(err) {
|
||||
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||
}
|
||||
return affected, err
|
||||
}
|
||||
|
||||
// DocumentTokenDeleteOne is the builder for deleting a single DocumentToken entity.
|
||||
type DocumentTokenDeleteOne struct {
|
||||
dtd *DocumentTokenDelete
|
||||
}
|
||||
|
||||
// Exec executes the deletion query.
|
||||
func (dtdo *DocumentTokenDeleteOne) Exec(ctx context.Context) error {
|
||||
n, err := dtdo.dtd.Exec(ctx)
|
||||
switch {
|
||||
case err != nil:
|
||||
return err
|
||||
case n == 0:
|
||||
return &NotFoundError{documenttoken.Label}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ExecX is like Exec, but panics if an error occurs.
|
||||
func (dtdo *DocumentTokenDeleteOne) ExecX(ctx context.Context) {
|
||||
dtdo.dtd.ExecX(ctx)
|
||||
}
|
|
@ -1,614 +0,0 @@
|
|||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package ent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||
"entgo.io/ent/schema/field"
|
||||
"github.com/google/uuid"
|
||||
"github.com/hay-kot/homebox/backend/ent/document"
|
||||
"github.com/hay-kot/homebox/backend/ent/documenttoken"
|
||||
"github.com/hay-kot/homebox/backend/ent/predicate"
|
||||
)
|
||||
|
||||
// DocumentTokenQuery is the builder for querying DocumentToken entities.
|
||||
type DocumentTokenQuery struct {
|
||||
config
|
||||
limit *int
|
||||
offset *int
|
||||
unique *bool
|
||||
order []OrderFunc
|
||||
fields []string
|
||||
predicates []predicate.DocumentToken
|
||||
withDocument *DocumentQuery
|
||||
withFKs bool
|
||||
// intermediate query (i.e. traversal path).
|
||||
sql *sql.Selector
|
||||
path func(context.Context) (*sql.Selector, error)
|
||||
}
|
||||
|
||||
// Where adds a new predicate for the DocumentTokenQuery builder.
|
||||
func (dtq *DocumentTokenQuery) Where(ps ...predicate.DocumentToken) *DocumentTokenQuery {
|
||||
dtq.predicates = append(dtq.predicates, ps...)
|
||||
return dtq
|
||||
}
|
||||
|
||||
// Limit adds a limit step to the query.
|
||||
func (dtq *DocumentTokenQuery) Limit(limit int) *DocumentTokenQuery {
|
||||
dtq.limit = &limit
|
||||
return dtq
|
||||
}
|
||||
|
||||
// Offset adds an offset step to the query.
|
||||
func (dtq *DocumentTokenQuery) Offset(offset int) *DocumentTokenQuery {
|
||||
dtq.offset = &offset
|
||||
return dtq
|
||||
}
|
||||
|
||||
// Unique configures the query builder to filter duplicate records on query.
|
||||
// By default, unique is set to true, and can be disabled using this method.
|
||||
func (dtq *DocumentTokenQuery) Unique(unique bool) *DocumentTokenQuery {
|
||||
dtq.unique = &unique
|
||||
return dtq
|
||||
}
|
||||
|
||||
// Order adds an order step to the query.
|
||||
func (dtq *DocumentTokenQuery) Order(o ...OrderFunc) *DocumentTokenQuery {
|
||||
dtq.order = append(dtq.order, o...)
|
||||
return dtq
|
||||
}
|
||||
|
||||
// QueryDocument chains the current query on the "document" edge.
|
||||
func (dtq *DocumentTokenQuery) QueryDocument() *DocumentQuery {
|
||||
query := &DocumentQuery{config: dtq.config}
|
||||
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
|
||||
if err := dtq.prepareQuery(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
selector := dtq.sqlQuery(ctx)
|
||||
if err := selector.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
step := sqlgraph.NewStep(
|
||||
sqlgraph.From(documenttoken.Table, documenttoken.FieldID, selector),
|
||||
sqlgraph.To(document.Table, document.FieldID),
|
||||
sqlgraph.Edge(sqlgraph.M2O, true, documenttoken.DocumentTable, documenttoken.DocumentColumn),
|
||||
)
|
||||
fromU = sqlgraph.SetNeighbors(dtq.driver.Dialect(), step)
|
||||
return fromU, nil
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
// First returns the first DocumentToken entity from the query.
|
||||
// Returns a *NotFoundError when no DocumentToken was found.
|
||||
func (dtq *DocumentTokenQuery) First(ctx context.Context) (*DocumentToken, error) {
|
||||
nodes, err := dtq.Limit(1).All(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(nodes) == 0 {
|
||||
return nil, &NotFoundError{documenttoken.Label}
|
||||
}
|
||||
return nodes[0], nil
|
||||
}
|
||||
|
||||
// FirstX is like First, but panics if an error occurs.
|
||||
func (dtq *DocumentTokenQuery) FirstX(ctx context.Context) *DocumentToken {
|
||||
node, err := dtq.First(ctx)
|
||||
if err != nil && !IsNotFound(err) {
|
||||
panic(err)
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// FirstID returns the first DocumentToken ID from the query.
|
||||
// Returns a *NotFoundError when no DocumentToken ID was found.
|
||||
func (dtq *DocumentTokenQuery) FirstID(ctx context.Context) (id uuid.UUID, err error) {
|
||||
var ids []uuid.UUID
|
||||
if ids, err = dtq.Limit(1).IDs(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
if len(ids) == 0 {
|
||||
err = &NotFoundError{documenttoken.Label}
|
||||
return
|
||||
}
|
||||
return ids[0], nil
|
||||
}
|
||||
|
||||
// FirstIDX is like FirstID, but panics if an error occurs.
|
||||
func (dtq *DocumentTokenQuery) FirstIDX(ctx context.Context) uuid.UUID {
|
||||
id, err := dtq.FirstID(ctx)
|
||||
if err != nil && !IsNotFound(err) {
|
||||
panic(err)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// Only returns a single DocumentToken entity found by the query, ensuring it only returns one.
|
||||
// Returns a *NotSingularError when more than one DocumentToken entity is found.
|
||||
// Returns a *NotFoundError when no DocumentToken entities are found.
|
||||
func (dtq *DocumentTokenQuery) Only(ctx context.Context) (*DocumentToken, error) {
|
||||
nodes, err := dtq.Limit(2).All(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch len(nodes) {
|
||||
case 1:
|
||||
return nodes[0], nil
|
||||
case 0:
|
||||
return nil, &NotFoundError{documenttoken.Label}
|
||||
default:
|
||||
return nil, &NotSingularError{documenttoken.Label}
|
||||
}
|
||||
}
|
||||
|
||||
// OnlyX is like Only, but panics if an error occurs.
|
||||
func (dtq *DocumentTokenQuery) OnlyX(ctx context.Context) *DocumentToken {
|
||||
node, err := dtq.Only(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// OnlyID is like Only, but returns the only DocumentToken ID in the query.
|
||||
// Returns a *NotSingularError when more than one DocumentToken ID is found.
|
||||
// Returns a *NotFoundError when no entities are found.
|
||||
func (dtq *DocumentTokenQuery) OnlyID(ctx context.Context) (id uuid.UUID, err error) {
|
||||
var ids []uuid.UUID
|
||||
if ids, err = dtq.Limit(2).IDs(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
switch len(ids) {
|
||||
case 1:
|
||||
id = ids[0]
|
||||
case 0:
|
||||
err = &NotFoundError{documenttoken.Label}
|
||||
default:
|
||||
err = &NotSingularError{documenttoken.Label}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// OnlyIDX is like OnlyID, but panics if an error occurs.
|
||||
func (dtq *DocumentTokenQuery) OnlyIDX(ctx context.Context) uuid.UUID {
|
||||
id, err := dtq.OnlyID(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// All executes the query and returns a list of DocumentTokens.
|
||||
func (dtq *DocumentTokenQuery) All(ctx context.Context) ([]*DocumentToken, error) {
|
||||
if err := dtq.prepareQuery(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dtq.sqlAll(ctx)
|
||||
}
|
||||
|
||||
// AllX is like All, but panics if an error occurs.
|
||||
func (dtq *DocumentTokenQuery) AllX(ctx context.Context) []*DocumentToken {
|
||||
nodes, err := dtq.All(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return nodes
|
||||
}
|
||||
|
||||
// IDs executes the query and returns a list of DocumentToken IDs.
|
||||
func (dtq *DocumentTokenQuery) IDs(ctx context.Context) ([]uuid.UUID, error) {
|
||||
var ids []uuid.UUID
|
||||
if err := dtq.Select(documenttoken.FieldID).Scan(ctx, &ids); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// IDsX is like IDs, but panics if an error occurs.
|
||||
func (dtq *DocumentTokenQuery) IDsX(ctx context.Context) []uuid.UUID {
|
||||
ids, err := dtq.IDs(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
// Count returns the count of the given query.
|
||||
func (dtq *DocumentTokenQuery) Count(ctx context.Context) (int, error) {
|
||||
if err := dtq.prepareQuery(ctx); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return dtq.sqlCount(ctx)
|
||||
}
|
||||
|
||||
// CountX is like Count, but panics if an error occurs.
|
||||
func (dtq *DocumentTokenQuery) CountX(ctx context.Context) int {
|
||||
count, err := dtq.Count(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// Exist returns true if the query has elements in the graph.
|
||||
func (dtq *DocumentTokenQuery) Exist(ctx context.Context) (bool, error) {
|
||||
if err := dtq.prepareQuery(ctx); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return dtq.sqlExist(ctx)
|
||||
}
|
||||
|
||||
// ExistX is like Exist, but panics if an error occurs.
|
||||
func (dtq *DocumentTokenQuery) ExistX(ctx context.Context) bool {
|
||||
exist, err := dtq.Exist(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return exist
|
||||
}
|
||||
|
||||
// Clone returns a duplicate of the DocumentTokenQuery builder, including all associated steps. It can be
|
||||
// used to prepare common query builders and use them differently after the clone is made.
|
||||
func (dtq *DocumentTokenQuery) Clone() *DocumentTokenQuery {
|
||||
if dtq == nil {
|
||||
return nil
|
||||
}
|
||||
return &DocumentTokenQuery{
|
||||
config: dtq.config,
|
||||
limit: dtq.limit,
|
||||
offset: dtq.offset,
|
||||
order: append([]OrderFunc{}, dtq.order...),
|
||||
predicates: append([]predicate.DocumentToken{}, dtq.predicates...),
|
||||
withDocument: dtq.withDocument.Clone(),
|
||||
// clone intermediate query.
|
||||
sql: dtq.sql.Clone(),
|
||||
path: dtq.path,
|
||||
unique: dtq.unique,
|
||||
}
|
||||
}
|
||||
|
||||
// WithDocument tells the query-builder to eager-load the nodes that are connected to
|
||||
// the "document" edge. The optional arguments are used to configure the query builder of the edge.
|
||||
func (dtq *DocumentTokenQuery) WithDocument(opts ...func(*DocumentQuery)) *DocumentTokenQuery {
|
||||
query := &DocumentQuery{config: dtq.config}
|
||||
for _, opt := range opts {
|
||||
opt(query)
|
||||
}
|
||||
dtq.withDocument = query
|
||||
return dtq
|
||||
}
|
||||
|
||||
// GroupBy is used to group vertices by one or more fields/columns.
|
||||
// It is often used with aggregate functions, like: count, max, mean, min, sum.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// var v []struct {
|
||||
// CreatedAt time.Time `json:"created_at,omitempty"`
|
||||
// Count int `json:"count,omitempty"`
|
||||
// }
|
||||
//
|
||||
// client.DocumentToken.Query().
|
||||
// GroupBy(documenttoken.FieldCreatedAt).
|
||||
// Aggregate(ent.Count()).
|
||||
// Scan(ctx, &v)
|
||||
func (dtq *DocumentTokenQuery) GroupBy(field string, fields ...string) *DocumentTokenGroupBy {
|
||||
grbuild := &DocumentTokenGroupBy{config: dtq.config}
|
||||
grbuild.fields = append([]string{field}, fields...)
|
||||
grbuild.path = func(ctx context.Context) (prev *sql.Selector, err error) {
|
||||
if err := dtq.prepareQuery(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dtq.sqlQuery(ctx), nil
|
||||
}
|
||||
grbuild.label = documenttoken.Label
|
||||
grbuild.flds, grbuild.scan = &grbuild.fields, grbuild.Scan
|
||||
return grbuild
|
||||
}
|
||||
|
||||
// Select allows the selection one or more fields/columns for the given query,
|
||||
// instead of selecting all fields in the entity.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// var v []struct {
|
||||
// CreatedAt time.Time `json:"created_at,omitempty"`
|
||||
// }
|
||||
//
|
||||
// client.DocumentToken.Query().
|
||||
// Select(documenttoken.FieldCreatedAt).
|
||||
// Scan(ctx, &v)
|
||||
func (dtq *DocumentTokenQuery) Select(fields ...string) *DocumentTokenSelect {
|
||||
dtq.fields = append(dtq.fields, fields...)
|
||||
selbuild := &DocumentTokenSelect{DocumentTokenQuery: dtq}
|
||||
selbuild.label = documenttoken.Label
|
||||
selbuild.flds, selbuild.scan = &dtq.fields, selbuild.Scan
|
||||
return selbuild
|
||||
}
|
||||
|
||||
func (dtq *DocumentTokenQuery) prepareQuery(ctx context.Context) error {
|
||||
for _, f := range dtq.fields {
|
||||
if !documenttoken.ValidColumn(f) {
|
||||
return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
|
||||
}
|
||||
}
|
||||
if dtq.path != nil {
|
||||
prev, err := dtq.path(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dtq.sql = prev
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dtq *DocumentTokenQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*DocumentToken, error) {
|
||||
var (
|
||||
nodes = []*DocumentToken{}
|
||||
withFKs = dtq.withFKs
|
||||
_spec = dtq.querySpec()
|
||||
loadedTypes = [1]bool{
|
||||
dtq.withDocument != nil,
|
||||
}
|
||||
)
|
||||
if dtq.withDocument != nil {
|
||||
withFKs = true
|
||||
}
|
||||
if withFKs {
|
||||
_spec.Node.Columns = append(_spec.Node.Columns, documenttoken.ForeignKeys...)
|
||||
}
|
||||
_spec.ScanValues = func(columns []string) ([]any, error) {
|
||||
return (*DocumentToken).scanValues(nil, columns)
|
||||
}
|
||||
_spec.Assign = func(columns []string, values []any) error {
|
||||
node := &DocumentToken{config: dtq.config}
|
||||
nodes = append(nodes, node)
|
||||
node.Edges.loadedTypes = loadedTypes
|
||||
return node.assignValues(columns, values)
|
||||
}
|
||||
for i := range hooks {
|
||||
hooks[i](ctx, _spec)
|
||||
}
|
||||
if err := sqlgraph.QueryNodes(ctx, dtq.driver, _spec); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(nodes) == 0 {
|
||||
return nodes, nil
|
||||
}
|
||||
if query := dtq.withDocument; query != nil {
|
||||
if err := dtq.loadDocument(ctx, query, nodes, nil,
|
||||
func(n *DocumentToken, e *Document) { n.Edges.Document = e }); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
func (dtq *DocumentTokenQuery) loadDocument(ctx context.Context, query *DocumentQuery, nodes []*DocumentToken, init func(*DocumentToken), assign func(*DocumentToken, *Document)) error {
|
||||
ids := make([]uuid.UUID, 0, len(nodes))
|
||||
nodeids := make(map[uuid.UUID][]*DocumentToken)
|
||||
for i := range nodes {
|
||||
if nodes[i].document_document_tokens == nil {
|
||||
continue
|
||||
}
|
||||
fk := *nodes[i].document_document_tokens
|
||||
if _, ok := nodeids[fk]; !ok {
|
||||
ids = append(ids, fk)
|
||||
}
|
||||
nodeids[fk] = append(nodeids[fk], nodes[i])
|
||||
}
|
||||
query.Where(document.IDIn(ids...))
|
||||
neighbors, err := query.All(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, n := range neighbors {
|
||||
nodes, ok := nodeids[n.ID]
|
||||
if !ok {
|
||||
return fmt.Errorf(`unexpected foreign-key "document_document_tokens" returned %v`, n.ID)
|
||||
}
|
||||
for i := range nodes {
|
||||
assign(nodes[i], n)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dtq *DocumentTokenQuery) sqlCount(ctx context.Context) (int, error) {
|
||||
_spec := dtq.querySpec()
|
||||
_spec.Node.Columns = dtq.fields
|
||||
if len(dtq.fields) > 0 {
|
||||
_spec.Unique = dtq.unique != nil && *dtq.unique
|
||||
}
|
||||
return sqlgraph.CountNodes(ctx, dtq.driver, _spec)
|
||||
}
|
||||
|
||||
func (dtq *DocumentTokenQuery) sqlExist(ctx context.Context) (bool, error) {
|
||||
switch _, err := dtq.FirstID(ctx); {
|
||||
case IsNotFound(err):
|
||||
return false, nil
|
||||
case err != nil:
|
||||
return false, fmt.Errorf("ent: check existence: %w", err)
|
||||
default:
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (dtq *DocumentTokenQuery) querySpec() *sqlgraph.QuerySpec {
|
||||
_spec := &sqlgraph.QuerySpec{
|
||||
Node: &sqlgraph.NodeSpec{
|
||||
Table: documenttoken.Table,
|
||||
Columns: documenttoken.Columns,
|
||||
ID: &sqlgraph.FieldSpec{
|
||||
Type: field.TypeUUID,
|
||||
Column: documenttoken.FieldID,
|
||||
},
|
||||
},
|
||||
From: dtq.sql,
|
||||
Unique: true,
|
||||
}
|
||||
if unique := dtq.unique; unique != nil {
|
||||
_spec.Unique = *unique
|
||||
}
|
||||
if fields := dtq.fields; len(fields) > 0 {
|
||||
_spec.Node.Columns = make([]string, 0, len(fields))
|
||||
_spec.Node.Columns = append(_spec.Node.Columns, documenttoken.FieldID)
|
||||
for i := range fields {
|
||||
if fields[i] != documenttoken.FieldID {
|
||||
_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
if ps := dtq.predicates; len(ps) > 0 {
|
||||
_spec.Predicate = func(selector *sql.Selector) {
|
||||
for i := range ps {
|
||||
ps[i](selector)
|
||||
}
|
||||
}
|
||||
}
|
||||
if limit := dtq.limit; limit != nil {
|
||||
_spec.Limit = *limit
|
||||
}
|
||||
if offset := dtq.offset; offset != nil {
|
||||
_spec.Offset = *offset
|
||||
}
|
||||
if ps := dtq.order; len(ps) > 0 {
|
||||
_spec.Order = func(selector *sql.Selector) {
|
||||
for i := range ps {
|
||||
ps[i](selector)
|
||||
}
|
||||
}
|
||||
}
|
||||
return _spec
|
||||
}
|
||||
|
||||
func (dtq *DocumentTokenQuery) sqlQuery(ctx context.Context) *sql.Selector {
|
||||
builder := sql.Dialect(dtq.driver.Dialect())
|
||||
t1 := builder.Table(documenttoken.Table)
|
||||
columns := dtq.fields
|
||||
if len(columns) == 0 {
|
||||
columns = documenttoken.Columns
|
||||
}
|
||||
selector := builder.Select(t1.Columns(columns...)...).From(t1)
|
||||
if dtq.sql != nil {
|
||||
selector = dtq.sql
|
||||
selector.Select(selector.Columns(columns...)...)
|
||||
}
|
||||
if dtq.unique != nil && *dtq.unique {
|
||||
selector.Distinct()
|
||||
}
|
||||
for _, p := range dtq.predicates {
|
||||
p(selector)
|
||||
}
|
||||
for _, p := range dtq.order {
|
||||
p(selector)
|
||||
}
|
||||
if offset := dtq.offset; offset != nil {
|
||||
// limit is mandatory for offset clause. We start
|
||||
// with default value, and override it below if needed.
|
||||
selector.Offset(*offset).Limit(math.MaxInt32)
|
||||
}
|
||||
if limit := dtq.limit; limit != nil {
|
||||
selector.Limit(*limit)
|
||||
}
|
||||
return selector
|
||||
}
|
||||
|
||||
// DocumentTokenGroupBy is the group-by builder for DocumentToken entities.
|
||||
type DocumentTokenGroupBy struct {
|
||||
config
|
||||
selector
|
||||
fields []string
|
||||
fns []AggregateFunc
|
||||
// intermediate query (i.e. traversal path).
|
||||
sql *sql.Selector
|
||||
path func(context.Context) (*sql.Selector, error)
|
||||
}
|
||||
|
||||
// Aggregate adds the given aggregation functions to the group-by query.
|
||||
func (dtgb *DocumentTokenGroupBy) Aggregate(fns ...AggregateFunc) *DocumentTokenGroupBy {
|
||||
dtgb.fns = append(dtgb.fns, fns...)
|
||||
return dtgb
|
||||
}
|
||||
|
||||
// Scan applies the group-by query and scans the result into the given value.
|
||||
func (dtgb *DocumentTokenGroupBy) Scan(ctx context.Context, v any) error {
|
||||
query, err := dtgb.path(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dtgb.sql = query
|
||||
return dtgb.sqlScan(ctx, v)
|
||||
}
|
||||
|
||||
func (dtgb *DocumentTokenGroupBy) sqlScan(ctx context.Context, v any) error {
|
||||
for _, f := range dtgb.fields {
|
||||
if !documenttoken.ValidColumn(f) {
|
||||
return &ValidationError{Name: f, err: fmt.Errorf("invalid field %q for group-by", f)}
|
||||
}
|
||||
}
|
||||
selector := dtgb.sqlQuery()
|
||||
if err := selector.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
rows := &sql.Rows{}
|
||||
query, args := selector.Query()
|
||||
if err := dtgb.driver.Query(ctx, query, args, rows); err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
return sql.ScanSlice(rows, v)
|
||||
}
|
||||
|
||||
func (dtgb *DocumentTokenGroupBy) sqlQuery() *sql.Selector {
|
||||
selector := dtgb.sql.Select()
|
||||
aggregation := make([]string, 0, len(dtgb.fns))
|
||||
for _, fn := range dtgb.fns {
|
||||
aggregation = append(aggregation, fn(selector))
|
||||
}
|
||||
// If no columns were selected in a custom aggregation function, the default
|
||||
// selection is the fields used for "group-by", and the aggregation functions.
|
||||
if len(selector.SelectedColumns()) == 0 {
|
||||
columns := make([]string, 0, len(dtgb.fields)+len(dtgb.fns))
|
||||
for _, f := range dtgb.fields {
|
||||
columns = append(columns, selector.C(f))
|
||||
}
|
||||
columns = append(columns, aggregation...)
|
||||
selector.Select(columns...)
|
||||
}
|
||||
return selector.GroupBy(selector.Columns(dtgb.fields...)...)
|
||||
}
|
||||
|
||||
// DocumentTokenSelect is the builder for selecting fields of DocumentToken entities.
|
||||
type DocumentTokenSelect struct {
|
||||
*DocumentTokenQuery
|
||||
selector
|
||||
// intermediate query (i.e. traversal path).
|
||||
sql *sql.Selector
|
||||
}
|
||||
|
||||
// Scan applies the selector query and scans the result into the given value.
|
||||
func (dts *DocumentTokenSelect) Scan(ctx context.Context, v any) error {
|
||||
if err := dts.prepareQuery(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
dts.sql = dts.DocumentTokenQuery.sqlQuery(ctx)
|
||||
return dts.sqlScan(ctx, v)
|
||||
}
|
||||
|
||||
func (dts *DocumentTokenSelect) sqlScan(ctx context.Context, v any) error {
|
||||
rows := &sql.Rows{}
|
||||
query, args := dts.sql.Query()
|
||||
if err := dts.driver.Query(ctx, query, args, rows); err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
return sql.ScanSlice(rows, v)
|
||||
}
|
|
@ -1,582 +0,0 @@
|
|||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package ent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||
"entgo.io/ent/schema/field"
|
||||
"github.com/google/uuid"
|
||||
"github.com/hay-kot/homebox/backend/ent/document"
|
||||
"github.com/hay-kot/homebox/backend/ent/documenttoken"
|
||||
"github.com/hay-kot/homebox/backend/ent/predicate"
|
||||
)
|
||||
|
||||
// DocumentTokenUpdate is the builder for updating DocumentToken entities.
|
||||
type DocumentTokenUpdate struct {
|
||||
config
|
||||
hooks []Hook
|
||||
mutation *DocumentTokenMutation
|
||||
}
|
||||
|
||||
// Where appends a list predicates to the DocumentTokenUpdate builder.
|
||||
func (dtu *DocumentTokenUpdate) Where(ps ...predicate.DocumentToken) *DocumentTokenUpdate {
|
||||
dtu.mutation.Where(ps...)
|
||||
return dtu
|
||||
}
|
||||
|
||||
// SetUpdatedAt sets the "updated_at" field.
|
||||
func (dtu *DocumentTokenUpdate) SetUpdatedAt(t time.Time) *DocumentTokenUpdate {
|
||||
dtu.mutation.SetUpdatedAt(t)
|
||||
return dtu
|
||||
}
|
||||
|
||||
// SetToken sets the "token" field.
|
||||
func (dtu *DocumentTokenUpdate) SetToken(b []byte) *DocumentTokenUpdate {
|
||||
dtu.mutation.SetToken(b)
|
||||
return dtu
|
||||
}
|
||||
|
||||
// SetUses sets the "uses" field.
|
||||
func (dtu *DocumentTokenUpdate) SetUses(i int) *DocumentTokenUpdate {
|
||||
dtu.mutation.ResetUses()
|
||||
dtu.mutation.SetUses(i)
|
||||
return dtu
|
||||
}
|
||||
|
||||
// SetNillableUses sets the "uses" field if the given value is not nil.
|
||||
func (dtu *DocumentTokenUpdate) SetNillableUses(i *int) *DocumentTokenUpdate {
|
||||
if i != nil {
|
||||
dtu.SetUses(*i)
|
||||
}
|
||||
return dtu
|
||||
}
|
||||
|
||||
// AddUses adds i to the "uses" field.
|
||||
func (dtu *DocumentTokenUpdate) AddUses(i int) *DocumentTokenUpdate {
|
||||
dtu.mutation.AddUses(i)
|
||||
return dtu
|
||||
}
|
||||
|
||||
// SetExpiresAt sets the "expires_at" field.
|
||||
func (dtu *DocumentTokenUpdate) SetExpiresAt(t time.Time) *DocumentTokenUpdate {
|
||||
dtu.mutation.SetExpiresAt(t)
|
||||
return dtu
|
||||
}
|
||||
|
||||
// SetNillableExpiresAt sets the "expires_at" field if the given value is not nil.
|
||||
func (dtu *DocumentTokenUpdate) SetNillableExpiresAt(t *time.Time) *DocumentTokenUpdate {
|
||||
if t != nil {
|
||||
dtu.SetExpiresAt(*t)
|
||||
}
|
||||
return dtu
|
||||
}
|
||||
|
||||
// SetDocumentID sets the "document" edge to the Document entity by ID.
|
||||
func (dtu *DocumentTokenUpdate) SetDocumentID(id uuid.UUID) *DocumentTokenUpdate {
|
||||
dtu.mutation.SetDocumentID(id)
|
||||
return dtu
|
||||
}
|
||||
|
||||
// SetNillableDocumentID sets the "document" edge to the Document entity by ID if the given value is not nil.
|
||||
func (dtu *DocumentTokenUpdate) SetNillableDocumentID(id *uuid.UUID) *DocumentTokenUpdate {
|
||||
if id != nil {
|
||||
dtu = dtu.SetDocumentID(*id)
|
||||
}
|
||||
return dtu
|
||||
}
|
||||
|
||||
// SetDocument sets the "document" edge to the Document entity.
|
||||
func (dtu *DocumentTokenUpdate) SetDocument(d *Document) *DocumentTokenUpdate {
|
||||
return dtu.SetDocumentID(d.ID)
|
||||
}
|
||||
|
||||
// Mutation returns the DocumentTokenMutation object of the builder.
|
||||
func (dtu *DocumentTokenUpdate) Mutation() *DocumentTokenMutation {
|
||||
return dtu.mutation
|
||||
}
|
||||
|
||||
// ClearDocument clears the "document" edge to the Document entity.
|
||||
func (dtu *DocumentTokenUpdate) ClearDocument() *DocumentTokenUpdate {
|
||||
dtu.mutation.ClearDocument()
|
||||
return dtu
|
||||
}
|
||||
|
||||
// Save executes the query and returns the number of nodes affected by the update operation.
|
||||
func (dtu *DocumentTokenUpdate) Save(ctx context.Context) (int, error) {
|
||||
var (
|
||||
err error
|
||||
affected int
|
||||
)
|
||||
dtu.defaults()
|
||||
if len(dtu.hooks) == 0 {
|
||||
if err = dtu.check(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
affected, err = dtu.sqlSave(ctx)
|
||||
} else {
|
||||
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
|
||||
mutation, ok := m.(*DocumentTokenMutation)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected mutation type %T", m)
|
||||
}
|
||||
if err = dtu.check(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
dtu.mutation = mutation
|
||||
affected, err = dtu.sqlSave(ctx)
|
||||
mutation.done = true
|
||||
return affected, err
|
||||
})
|
||||
for i := len(dtu.hooks) - 1; i >= 0; i-- {
|
||||
if dtu.hooks[i] == nil {
|
||||
return 0, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)")
|
||||
}
|
||||
mut = dtu.hooks[i](mut)
|
||||
}
|
||||
if _, err := mut.Mutate(ctx, dtu.mutation); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return affected, err
|
||||
}
|
||||
|
||||
// SaveX is like Save, but panics if an error occurs.
|
||||
func (dtu *DocumentTokenUpdate) SaveX(ctx context.Context) int {
|
||||
affected, err := dtu.Save(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return affected
|
||||
}
|
||||
|
||||
// Exec executes the query.
|
||||
func (dtu *DocumentTokenUpdate) Exec(ctx context.Context) error {
|
||||
_, err := dtu.Save(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// ExecX is like Exec, but panics if an error occurs.
|
||||
func (dtu *DocumentTokenUpdate) ExecX(ctx context.Context) {
|
||||
if err := dtu.Exec(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// defaults sets the default values of the builder before save.
|
||||
func (dtu *DocumentTokenUpdate) defaults() {
|
||||
if _, ok := dtu.mutation.UpdatedAt(); !ok {
|
||||
v := documenttoken.UpdateDefaultUpdatedAt()
|
||||
dtu.mutation.SetUpdatedAt(v)
|
||||
}
|
||||
}
|
||||
|
||||
// check runs all checks and user-defined validators on the builder.
|
||||
func (dtu *DocumentTokenUpdate) check() error {
|
||||
if v, ok := dtu.mutation.Token(); ok {
|
||||
if err := documenttoken.TokenValidator(v); err != nil {
|
||||
return &ValidationError{Name: "token", err: fmt.Errorf(`ent: validator failed for field "DocumentToken.token": %w`, err)}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dtu *DocumentTokenUpdate) sqlSave(ctx context.Context) (n int, err error) {
|
||||
_spec := &sqlgraph.UpdateSpec{
|
||||
Node: &sqlgraph.NodeSpec{
|
||||
Table: documenttoken.Table,
|
||||
Columns: documenttoken.Columns,
|
||||
ID: &sqlgraph.FieldSpec{
|
||||
Type: field.TypeUUID,
|
||||
Column: documenttoken.FieldID,
|
||||
},
|
||||
},
|
||||
}
|
||||
if ps := dtu.mutation.predicates; len(ps) > 0 {
|
||||
_spec.Predicate = func(selector *sql.Selector) {
|
||||
for i := range ps {
|
||||
ps[i](selector)
|
||||
}
|
||||
}
|
||||
}
|
||||
if value, ok := dtu.mutation.UpdatedAt(); ok {
|
||||
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
|
||||
Type: field.TypeTime,
|
||||
Value: value,
|
||||
Column: documenttoken.FieldUpdatedAt,
|
||||
})
|
||||
}
|
||||
if value, ok := dtu.mutation.Token(); ok {
|
||||
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
|
||||
Type: field.TypeBytes,
|
||||
Value: value,
|
||||
Column: documenttoken.FieldToken,
|
||||
})
|
||||
}
|
||||
if value, ok := dtu.mutation.Uses(); ok {
|
||||
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
|
||||
Type: field.TypeInt,
|
||||
Value: value,
|
||||
Column: documenttoken.FieldUses,
|
||||
})
|
||||
}
|
||||
if value, ok := dtu.mutation.AddedUses(); ok {
|
||||
_spec.Fields.Add = append(_spec.Fields.Add, &sqlgraph.FieldSpec{
|
||||
Type: field.TypeInt,
|
||||
Value: value,
|
||||
Column: documenttoken.FieldUses,
|
||||
})
|
||||
}
|
||||
if value, ok := dtu.mutation.ExpiresAt(); ok {
|
||||
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
|
||||
Type: field.TypeTime,
|
||||
Value: value,
|
||||
Column: documenttoken.FieldExpiresAt,
|
||||
})
|
||||
}
|
||||
if dtu.mutation.DocumentCleared() {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.M2O,
|
||||
Inverse: true,
|
||||
Table: documenttoken.DocumentTable,
|
||||
Columns: []string{documenttoken.DocumentColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: &sqlgraph.FieldSpec{
|
||||
Type: field.TypeUUID,
|
||||
Column: document.FieldID,
|
||||
},
|
||||
},
|
||||
}
|
||||
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
||||
}
|
||||
if nodes := dtu.mutation.DocumentIDs(); len(nodes) > 0 {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.M2O,
|
||||
Inverse: true,
|
||||
Table: documenttoken.DocumentTable,
|
||||
Columns: []string{documenttoken.DocumentColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: &sqlgraph.FieldSpec{
|
||||
Type: field.TypeUUID,
|
||||
Column: document.FieldID,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, k := range nodes {
|
||||
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||
}
|
||||
_spec.Edges.Add = append(_spec.Edges.Add, edge)
|
||||
}
|
||||
if n, err = sqlgraph.UpdateNodes(ctx, dtu.driver, _spec); err != nil {
|
||||
if _, ok := err.(*sqlgraph.NotFoundError); ok {
|
||||
err = &NotFoundError{documenttoken.Label}
|
||||
} else if sqlgraph.IsConstraintError(err) {
|
||||
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// DocumentTokenUpdateOne is the builder for updating a single DocumentToken entity.
|
||||
type DocumentTokenUpdateOne struct {
|
||||
config
|
||||
fields []string
|
||||
hooks []Hook
|
||||
mutation *DocumentTokenMutation
|
||||
}
|
||||
|
||||
// SetUpdatedAt sets the "updated_at" field.
|
||||
func (dtuo *DocumentTokenUpdateOne) SetUpdatedAt(t time.Time) *DocumentTokenUpdateOne {
|
||||
dtuo.mutation.SetUpdatedAt(t)
|
||||
return dtuo
|
||||
}
|
||||
|
||||
// SetToken sets the "token" field.
|
||||
func (dtuo *DocumentTokenUpdateOne) SetToken(b []byte) *DocumentTokenUpdateOne {
|
||||
dtuo.mutation.SetToken(b)
|
||||
return dtuo
|
||||
}
|
||||
|
||||
// SetUses sets the "uses" field.
|
||||
func (dtuo *DocumentTokenUpdateOne) SetUses(i int) *DocumentTokenUpdateOne {
|
||||
dtuo.mutation.ResetUses()
|
||||
dtuo.mutation.SetUses(i)
|
||||
return dtuo
|
||||
}
|
||||
|
||||
// SetNillableUses sets the "uses" field if the given value is not nil.
|
||||
func (dtuo *DocumentTokenUpdateOne) SetNillableUses(i *int) *DocumentTokenUpdateOne {
|
||||
if i != nil {
|
||||
dtuo.SetUses(*i)
|
||||
}
|
||||
return dtuo
|
||||
}
|
||||
|
||||
// AddUses adds i to the "uses" field.
|
||||
func (dtuo *DocumentTokenUpdateOne) AddUses(i int) *DocumentTokenUpdateOne {
|
||||
dtuo.mutation.AddUses(i)
|
||||
return dtuo
|
||||
}
|
||||
|
||||
// SetExpiresAt sets the "expires_at" field.
|
||||
func (dtuo *DocumentTokenUpdateOne) SetExpiresAt(t time.Time) *DocumentTokenUpdateOne {
|
||||
dtuo.mutation.SetExpiresAt(t)
|
||||
return dtuo
|
||||
}
|
||||
|
||||
// SetNillableExpiresAt sets the "expires_at" field if the given value is not nil.
|
||||
func (dtuo *DocumentTokenUpdateOne) SetNillableExpiresAt(t *time.Time) *DocumentTokenUpdateOne {
|
||||
if t != nil {
|
||||
dtuo.SetExpiresAt(*t)
|
||||
}
|
||||
return dtuo
|
||||
}
|
||||
|
||||
// SetDocumentID sets the "document" edge to the Document entity by ID.
|
||||
func (dtuo *DocumentTokenUpdateOne) SetDocumentID(id uuid.UUID) *DocumentTokenUpdateOne {
|
||||
dtuo.mutation.SetDocumentID(id)
|
||||
return dtuo
|
||||
}
|
||||
|
||||
// SetNillableDocumentID sets the "document" edge to the Document entity by ID if the given value is not nil.
|
||||
func (dtuo *DocumentTokenUpdateOne) SetNillableDocumentID(id *uuid.UUID) *DocumentTokenUpdateOne {
|
||||
if id != nil {
|
||||
dtuo = dtuo.SetDocumentID(*id)
|
||||
}
|
||||
return dtuo
|
||||
}
|
||||
|
||||
// SetDocument sets the "document" edge to the Document entity.
|
||||
func (dtuo *DocumentTokenUpdateOne) SetDocument(d *Document) *DocumentTokenUpdateOne {
|
||||
return dtuo.SetDocumentID(d.ID)
|
||||
}
|
||||
|
||||
// Mutation returns the DocumentTokenMutation object of the builder.
|
||||
func (dtuo *DocumentTokenUpdateOne) Mutation() *DocumentTokenMutation {
|
||||
return dtuo.mutation
|
||||
}
|
||||
|
||||
// ClearDocument clears the "document" edge to the Document entity.
|
||||
func (dtuo *DocumentTokenUpdateOne) ClearDocument() *DocumentTokenUpdateOne {
|
||||
dtuo.mutation.ClearDocument()
|
||||
return dtuo
|
||||
}
|
||||
|
||||
// Select allows selecting one or more fields (columns) of the returned entity.
|
||||
// The default is selecting all fields defined in the entity schema.
|
||||
func (dtuo *DocumentTokenUpdateOne) Select(field string, fields ...string) *DocumentTokenUpdateOne {
|
||||
dtuo.fields = append([]string{field}, fields...)
|
||||
return dtuo
|
||||
}
|
||||
|
||||
// Save executes the query and returns the updated DocumentToken entity.
|
||||
func (dtuo *DocumentTokenUpdateOne) Save(ctx context.Context) (*DocumentToken, error) {
|
||||
var (
|
||||
err error
|
||||
node *DocumentToken
|
||||
)
|
||||
dtuo.defaults()
|
||||
if len(dtuo.hooks) == 0 {
|
||||
if err = dtuo.check(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
node, err = dtuo.sqlSave(ctx)
|
||||
} else {
|
||||
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
|
||||
mutation, ok := m.(*DocumentTokenMutation)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected mutation type %T", m)
|
||||
}
|
||||
if err = dtuo.check(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dtuo.mutation = mutation
|
||||
node, err = dtuo.sqlSave(ctx)
|
||||
mutation.done = true
|
||||
return node, err
|
||||
})
|
||||
for i := len(dtuo.hooks) - 1; i >= 0; i-- {
|
||||
if dtuo.hooks[i] == nil {
|
||||
return nil, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)")
|
||||
}
|
||||
mut = dtuo.hooks[i](mut)
|
||||
}
|
||||
v, err := mut.Mutate(ctx, dtuo.mutation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nv, ok := v.(*DocumentToken)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected node type %T returned from DocumentTokenMutation", v)
|
||||
}
|
||||
node = nv
|
||||
}
|
||||
return node, err
|
||||
}
|
||||
|
||||
// SaveX is like Save, but panics if an error occurs.
|
||||
func (dtuo *DocumentTokenUpdateOne) SaveX(ctx context.Context) *DocumentToken {
|
||||
node, err := dtuo.Save(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// Exec executes the query on the entity.
|
||||
func (dtuo *DocumentTokenUpdateOne) Exec(ctx context.Context) error {
|
||||
_, err := dtuo.Save(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// ExecX is like Exec, but panics if an error occurs.
|
||||
func (dtuo *DocumentTokenUpdateOne) ExecX(ctx context.Context) {
|
||||
if err := dtuo.Exec(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// defaults sets the default values of the builder before save.
|
||||
func (dtuo *DocumentTokenUpdateOne) defaults() {
|
||||
if _, ok := dtuo.mutation.UpdatedAt(); !ok {
|
||||
v := documenttoken.UpdateDefaultUpdatedAt()
|
||||
dtuo.mutation.SetUpdatedAt(v)
|
||||
}
|
||||
}
|
||||
|
||||
// check runs all checks and user-defined validators on the builder.
|
||||
func (dtuo *DocumentTokenUpdateOne) check() error {
|
||||
if v, ok := dtuo.mutation.Token(); ok {
|
||||
if err := documenttoken.TokenValidator(v); err != nil {
|
||||
return &ValidationError{Name: "token", err: fmt.Errorf(`ent: validator failed for field "DocumentToken.token": %w`, err)}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dtuo *DocumentTokenUpdateOne) sqlSave(ctx context.Context) (_node *DocumentToken, err error) {
|
||||
_spec := &sqlgraph.UpdateSpec{
|
||||
Node: &sqlgraph.NodeSpec{
|
||||
Table: documenttoken.Table,
|
||||
Columns: documenttoken.Columns,
|
||||
ID: &sqlgraph.FieldSpec{
|
||||
Type: field.TypeUUID,
|
||||
Column: documenttoken.FieldID,
|
||||
},
|
||||
},
|
||||
}
|
||||
id, ok := dtuo.mutation.ID()
|
||||
if !ok {
|
||||
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "DocumentToken.id" for update`)}
|
||||
}
|
||||
_spec.Node.ID.Value = id
|
||||
if fields := dtuo.fields; len(fields) > 0 {
|
||||
_spec.Node.Columns = make([]string, 0, len(fields))
|
||||
_spec.Node.Columns = append(_spec.Node.Columns, documenttoken.FieldID)
|
||||
for _, f := range fields {
|
||||
if !documenttoken.ValidColumn(f) {
|
||||
return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
|
||||
}
|
||||
if f != documenttoken.FieldID {
|
||||
_spec.Node.Columns = append(_spec.Node.Columns, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
if ps := dtuo.mutation.predicates; len(ps) > 0 {
|
||||
_spec.Predicate = func(selector *sql.Selector) {
|
||||
for i := range ps {
|
||||
ps[i](selector)
|
||||
}
|
||||
}
|
||||
}
|
||||
if value, ok := dtuo.mutation.UpdatedAt(); ok {
|
||||
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
|
||||
Type: field.TypeTime,
|
||||
Value: value,
|
||||
Column: documenttoken.FieldUpdatedAt,
|
||||
})
|
||||
}
|
||||
if value, ok := dtuo.mutation.Token(); ok {
|
||||
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
|
||||
Type: field.TypeBytes,
|
||||
Value: value,
|
||||
Column: documenttoken.FieldToken,
|
||||
})
|
||||
}
|
||||
if value, ok := dtuo.mutation.Uses(); ok {
|
||||
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
|
||||
Type: field.TypeInt,
|
||||
Value: value,
|
||||
Column: documenttoken.FieldUses,
|
||||
})
|
||||
}
|
||||
if value, ok := dtuo.mutation.AddedUses(); ok {
|
||||
_spec.Fields.Add = append(_spec.Fields.Add, &sqlgraph.FieldSpec{
|
||||
Type: field.TypeInt,
|
||||
Value: value,
|
||||
Column: documenttoken.FieldUses,
|
||||
})
|
||||
}
|
||||
if value, ok := dtuo.mutation.ExpiresAt(); ok {
|
||||
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
|
||||
Type: field.TypeTime,
|
||||
Value: value,
|
||||
Column: documenttoken.FieldExpiresAt,
|
||||
})
|
||||
}
|
||||
if dtuo.mutation.DocumentCleared() {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.M2O,
|
||||
Inverse: true,
|
||||
Table: documenttoken.DocumentTable,
|
||||
Columns: []string{documenttoken.DocumentColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: &sqlgraph.FieldSpec{
|
||||
Type: field.TypeUUID,
|
||||
Column: document.FieldID,
|
||||
},
|
||||
},
|
||||
}
|
||||
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
||||
}
|
||||
if nodes := dtuo.mutation.DocumentIDs(); len(nodes) > 0 {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.M2O,
|
||||
Inverse: true,
|
||||
Table: documenttoken.DocumentTable,
|
||||
Columns: []string{documenttoken.DocumentColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: &sqlgraph.FieldSpec{
|
||||
Type: field.TypeUUID,
|
||||
Column: document.FieldID,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, k := range nodes {
|
||||
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||
}
|
||||
_spec.Edges.Add = append(_spec.Edges.Add, edge)
|
||||
}
|
||||
_node = &DocumentToken{config: dtuo.config}
|
||||
_spec.Assign = _node.assignValues
|
||||
_spec.ScanValues = _node.scanValues
|
||||
if err = sqlgraph.UpdateNode(ctx, dtuo.driver, _spec); err != nil {
|
||||
if _, ok := err.(*sqlgraph.NotFoundError); ok {
|
||||
err = &NotFoundError{documenttoken.Label}
|
||||
} else if sqlgraph.IsConstraintError(err) {
|
||||
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return _node, nil
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
package ent
|
||||
|
||||
//go:generate go run -mod=mod entgo.io/ent/cmd/ent generate --feature sql/versioned-migration ./schema
|
|
@ -1,138 +0,0 @@
|
|||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package group
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
// Label holds the string label denoting the group type in the database.
|
||||
Label = "group"
|
||||
// FieldID holds the string denoting the id field in the database.
|
||||
FieldID = "id"
|
||||
// FieldCreatedAt holds the string denoting the created_at field in the database.
|
||||
FieldCreatedAt = "created_at"
|
||||
// FieldUpdatedAt holds the string denoting the updated_at field in the database.
|
||||
FieldUpdatedAt = "updated_at"
|
||||
// FieldName holds the string denoting the name field in the database.
|
||||
FieldName = "name"
|
||||
// FieldCurrency holds the string denoting the currency field in the database.
|
||||
FieldCurrency = "currency"
|
||||
// EdgeUsers holds the string denoting the users edge name in mutations.
|
||||
EdgeUsers = "users"
|
||||
// EdgeLocations holds the string denoting the locations edge name in mutations.
|
||||
EdgeLocations = "locations"
|
||||
// EdgeItems holds the string denoting the items edge name in mutations.
|
||||
EdgeItems = "items"
|
||||
// EdgeLabels holds the string denoting the labels edge name in mutations.
|
||||
EdgeLabels = "labels"
|
||||
// EdgeDocuments holds the string denoting the documents edge name in mutations.
|
||||
EdgeDocuments = "documents"
|
||||
// EdgeInvitationTokens holds the string denoting the invitation_tokens edge name in mutations.
|
||||
EdgeInvitationTokens = "invitation_tokens"
|
||||
// Table holds the table name of the group in the database.
|
||||
Table = "groups"
|
||||
// UsersTable is the table that holds the users relation/edge.
|
||||
UsersTable = "users"
|
||||
// UsersInverseTable is the table name for the User entity.
|
||||
// It exists in this package in order to avoid circular dependency with the "user" package.
|
||||
UsersInverseTable = "users"
|
||||
// UsersColumn is the table column denoting the users relation/edge.
|
||||
UsersColumn = "group_users"
|
||||
// LocationsTable is the table that holds the locations relation/edge.
|
||||
LocationsTable = "locations"
|
||||
// LocationsInverseTable is the table name for the Location entity.
|
||||
// It exists in this package in order to avoid circular dependency with the "location" package.
|
||||
LocationsInverseTable = "locations"
|
||||
// LocationsColumn is the table column denoting the locations relation/edge.
|
||||
LocationsColumn = "group_locations"
|
||||
// ItemsTable is the table that holds the items relation/edge.
|
||||
ItemsTable = "items"
|
||||
// ItemsInverseTable is the table name for the Item entity.
|
||||
// It exists in this package in order to avoid circular dependency with the "item" package.
|
||||
ItemsInverseTable = "items"
|
||||
// ItemsColumn is the table column denoting the items relation/edge.
|
||||
ItemsColumn = "group_items"
|
||||
// LabelsTable is the table that holds the labels relation/edge.
|
||||
LabelsTable = "labels"
|
||||
// LabelsInverseTable is the table name for the Label entity.
|
||||
// It exists in this package in order to avoid circular dependency with the "label" package.
|
||||
LabelsInverseTable = "labels"
|
||||
// LabelsColumn is the table column denoting the labels relation/edge.
|
||||
LabelsColumn = "group_labels"
|
||||
// DocumentsTable is the table that holds the documents relation/edge.
|
||||
DocumentsTable = "documents"
|
||||
// DocumentsInverseTable is the table name for the Document entity.
|
||||
// It exists in this package in order to avoid circular dependency with the "document" package.
|
||||
DocumentsInverseTable = "documents"
|
||||
// DocumentsColumn is the table column denoting the documents relation/edge.
|
||||
DocumentsColumn = "group_documents"
|
||||
// InvitationTokensTable is the table that holds the invitation_tokens relation/edge.
|
||||
InvitationTokensTable = "group_invitation_tokens"
|
||||
// InvitationTokensInverseTable is the table name for the GroupInvitationToken entity.
|
||||
// It exists in this package in order to avoid circular dependency with the "groupinvitationtoken" package.
|
||||
InvitationTokensInverseTable = "group_invitation_tokens"
|
||||
// InvitationTokensColumn is the table column denoting the invitation_tokens relation/edge.
|
||||
InvitationTokensColumn = "group_invitation_tokens"
|
||||
)
|
||||
|
||||
// Columns holds all SQL columns for group fields.
|
||||
var Columns = []string{
|
||||
FieldID,
|
||||
FieldCreatedAt,
|
||||
FieldUpdatedAt,
|
||||
FieldName,
|
||||
FieldCurrency,
|
||||
}
|
||||
|
||||
// ValidColumn reports if the column name is valid (part of the table columns).
|
||||
func ValidColumn(column string) bool {
|
||||
for i := range Columns {
|
||||
if column == Columns[i] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var (
|
||||
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
|
||||
DefaultCreatedAt func() time.Time
|
||||
// DefaultUpdatedAt holds the default value on creation for the "updated_at" field.
|
||||
DefaultUpdatedAt func() time.Time
|
||||
// UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field.
|
||||
UpdateDefaultUpdatedAt func() time.Time
|
||||
// NameValidator is a validator for the "name" field. It is called by the builders before save.
|
||||
NameValidator func(string) error
|
||||
// DefaultID holds the default value on creation for the "id" field.
|
||||
DefaultID func() uuid.UUID
|
||||
)
|
||||
|
||||
// Currency defines the type for the "currency" enum field.
|
||||
type Currency string
|
||||
|
||||
// CurrencyUsd is the default value of the Currency enum.
|
||||
const DefaultCurrency = CurrencyUsd
|
||||
|
||||
// Currency values.
|
||||
const (
|
||||
CurrencyUsd Currency = "usd"
|
||||
)
|
||||
|
||||
func (c Currency) String() string {
|
||||
return string(c)
|
||||
}
|
||||
|
||||
// CurrencyValidator is a validator for the "currency" field enum values. It is called by the builders before save.
|
||||
func CurrencyValidator(c Currency) error {
|
||||
switch c {
|
||||
case CurrencyUsd:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("group: invalid enum value for currency field: %q", c)
|
||||
}
|
||||
}
|
|
@ -1,196 +0,0 @@
|
|||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package item
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
// Label holds the string label denoting the item type in the database.
|
||||
Label = "item"
|
||||
// FieldID holds the string denoting the id field in the database.
|
||||
FieldID = "id"
|
||||
// FieldCreatedAt holds the string denoting the created_at field in the database.
|
||||
FieldCreatedAt = "created_at"
|
||||
// FieldUpdatedAt holds the string denoting the updated_at field in the database.
|
||||
FieldUpdatedAt = "updated_at"
|
||||
// FieldName holds the string denoting the name field in the database.
|
||||
FieldName = "name"
|
||||
// FieldDescription holds the string denoting the description field in the database.
|
||||
FieldDescription = "description"
|
||||
// FieldImportRef holds the string denoting the import_ref field in the database.
|
||||
FieldImportRef = "import_ref"
|
||||
// FieldNotes holds the string denoting the notes field in the database.
|
||||
FieldNotes = "notes"
|
||||
// FieldQuantity holds the string denoting the quantity field in the database.
|
||||
FieldQuantity = "quantity"
|
||||
// FieldInsured holds the string denoting the insured field in the database.
|
||||
FieldInsured = "insured"
|
||||
// FieldSerialNumber holds the string denoting the serial_number field in the database.
|
||||
FieldSerialNumber = "serial_number"
|
||||
// FieldModelNumber holds the string denoting the model_number field in the database.
|
||||
FieldModelNumber = "model_number"
|
||||
// FieldManufacturer holds the string denoting the manufacturer field in the database.
|
||||
FieldManufacturer = "manufacturer"
|
||||
// FieldLifetimeWarranty holds the string denoting the lifetime_warranty field in the database.
|
||||
FieldLifetimeWarranty = "lifetime_warranty"
|
||||
// FieldWarrantyExpires holds the string denoting the warranty_expires field in the database.
|
||||
FieldWarrantyExpires = "warranty_expires"
|
||||
// FieldWarrantyDetails holds the string denoting the warranty_details field in the database.
|
||||
FieldWarrantyDetails = "warranty_details"
|
||||
// FieldPurchaseTime holds the string denoting the purchase_time field in the database.
|
||||
FieldPurchaseTime = "purchase_time"
|
||||
// FieldPurchaseFrom holds the string denoting the purchase_from field in the database.
|
||||
FieldPurchaseFrom = "purchase_from"
|
||||
// FieldPurchasePrice holds the string denoting the purchase_price field in the database.
|
||||
FieldPurchasePrice = "purchase_price"
|
||||
// FieldSoldTime holds the string denoting the sold_time field in the database.
|
||||
FieldSoldTime = "sold_time"
|
||||
// FieldSoldTo holds the string denoting the sold_to field in the database.
|
||||
FieldSoldTo = "sold_to"
|
||||
// FieldSoldPrice holds the string denoting the sold_price field in the database.
|
||||
FieldSoldPrice = "sold_price"
|
||||
// FieldSoldNotes holds the string denoting the sold_notes field in the database.
|
||||
FieldSoldNotes = "sold_notes"
|
||||
// EdgeGroup holds the string denoting the group edge name in mutations.
|
||||
EdgeGroup = "group"
|
||||
// EdgeLabel holds the string denoting the label edge name in mutations.
|
||||
EdgeLabel = "label"
|
||||
// EdgeLocation holds the string denoting the location edge name in mutations.
|
||||
EdgeLocation = "location"
|
||||
// EdgeFields holds the string denoting the fields edge name in mutations.
|
||||
EdgeFields = "fields"
|
||||
// EdgeAttachments holds the string denoting the attachments edge name in mutations.
|
||||
EdgeAttachments = "attachments"
|
||||
// Table holds the table name of the item in the database.
|
||||
Table = "items"
|
||||
// GroupTable is the table that holds the group relation/edge.
|
||||
GroupTable = "items"
|
||||
// GroupInverseTable is the table name for the Group entity.
|
||||
// It exists in this package in order to avoid circular dependency with the "group" package.
|
||||
GroupInverseTable = "groups"
|
||||
// GroupColumn is the table column denoting the group relation/edge.
|
||||
GroupColumn = "group_items"
|
||||
// LabelTable is the table that holds the label relation/edge. The primary key declared below.
|
||||
LabelTable = "label_items"
|
||||
// LabelInverseTable is the table name for the Label entity.
|
||||
// It exists in this package in order to avoid circular dependency with the "label" package.
|
||||
LabelInverseTable = "labels"
|
||||
// LocationTable is the table that holds the location relation/edge.
|
||||
LocationTable = "items"
|
||||
// LocationInverseTable is the table name for the Location entity.
|
||||
// It exists in this package in order to avoid circular dependency with the "location" package.
|
||||
LocationInverseTable = "locations"
|
||||
// LocationColumn is the table column denoting the location relation/edge.
|
||||
LocationColumn = "location_items"
|
||||
// FieldsTable is the table that holds the fields relation/edge.
|
||||
FieldsTable = "item_fields"
|
||||
// FieldsInverseTable is the table name for the ItemField entity.
|
||||
// It exists in this package in order to avoid circular dependency with the "itemfield" package.
|
||||
FieldsInverseTable = "item_fields"
|
||||
// FieldsColumn is the table column denoting the fields relation/edge.
|
||||
FieldsColumn = "item_fields"
|
||||
// AttachmentsTable is the table that holds the attachments relation/edge.
|
||||
AttachmentsTable = "attachments"
|
||||
// AttachmentsInverseTable is the table name for the Attachment entity.
|
||||
// It exists in this package in order to avoid circular dependency with the "attachment" package.
|
||||
AttachmentsInverseTable = "attachments"
|
||||
// AttachmentsColumn is the table column denoting the attachments relation/edge.
|
||||
AttachmentsColumn = "item_attachments"
|
||||
)
|
||||
|
||||
// Columns holds all SQL columns for item fields.
|
||||
var Columns = []string{
|
||||
FieldID,
|
||||
FieldCreatedAt,
|
||||
FieldUpdatedAt,
|
||||
FieldName,
|
||||
FieldDescription,
|
||||
FieldImportRef,
|
||||
FieldNotes,
|
||||
FieldQuantity,
|
||||
FieldInsured,
|
||||
FieldSerialNumber,
|
||||
FieldModelNumber,
|
||||
FieldManufacturer,
|
||||
FieldLifetimeWarranty,
|
||||
FieldWarrantyExpires,
|
||||
FieldWarrantyDetails,
|
||||
FieldPurchaseTime,
|
||||
FieldPurchaseFrom,
|
||||
FieldPurchasePrice,
|
||||
FieldSoldTime,
|
||||
FieldSoldTo,
|
||||
FieldSoldPrice,
|
||||
FieldSoldNotes,
|
||||
}
|
||||
|
||||
// ForeignKeys holds the SQL foreign-keys that are owned by the "items"
|
||||
// table and are not defined as standalone fields in the schema.
|
||||
var ForeignKeys = []string{
|
||||
"group_items",
|
||||
"location_items",
|
||||
}
|
||||
|
||||
var (
|
||||
// LabelPrimaryKey and LabelColumn2 are the table columns denoting the
|
||||
// primary key for the label relation (M2M).
|
||||
LabelPrimaryKey = []string{"label_id", "item_id"}
|
||||
)
|
||||
|
||||
// ValidColumn reports if the column name is valid (part of the table columns).
|
||||
func ValidColumn(column string) bool {
|
||||
for i := range Columns {
|
||||
if column == Columns[i] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for i := range ForeignKeys {
|
||||
if column == ForeignKeys[i] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var (
|
||||
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
|
||||
DefaultCreatedAt func() time.Time
|
||||
// DefaultUpdatedAt holds the default value on creation for the "updated_at" field.
|
||||
DefaultUpdatedAt func() time.Time
|
||||
// UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field.
|
||||
UpdateDefaultUpdatedAt func() time.Time
|
||||
// NameValidator is a validator for the "name" field. It is called by the builders before save.
|
||||
NameValidator func(string) error
|
||||
// DescriptionValidator is a validator for the "description" field. It is called by the builders before save.
|
||||
DescriptionValidator func(string) error
|
||||
// ImportRefValidator is a validator for the "import_ref" field. It is called by the builders before save.
|
||||
ImportRefValidator func(string) error
|
||||
// NotesValidator is a validator for the "notes" field. It is called by the builders before save.
|
||||
NotesValidator func(string) error
|
||||
// DefaultQuantity holds the default value on creation for the "quantity" field.
|
||||
DefaultQuantity int
|
||||
// DefaultInsured holds the default value on creation for the "insured" field.
|
||||
DefaultInsured bool
|
||||
// SerialNumberValidator is a validator for the "serial_number" field. It is called by the builders before save.
|
||||
SerialNumberValidator func(string) error
|
||||
// ModelNumberValidator is a validator for the "model_number" field. It is called by the builders before save.
|
||||
ModelNumberValidator func(string) error
|
||||
// ManufacturerValidator is a validator for the "manufacturer" field. It is called by the builders before save.
|
||||
ManufacturerValidator func(string) error
|
||||
// DefaultLifetimeWarranty holds the default value on creation for the "lifetime_warranty" field.
|
||||
DefaultLifetimeWarranty bool
|
||||
// WarrantyDetailsValidator is a validator for the "warranty_details" field. It is called by the builders before save.
|
||||
WarrantyDetailsValidator func(string) error
|
||||
// DefaultPurchasePrice holds the default value on creation for the "purchase_price" field.
|
||||
DefaultPurchasePrice float64
|
||||
// DefaultSoldPrice holds the default value on creation for the "sold_price" field.
|
||||
DefaultSoldPrice float64
|
||||
// SoldNotesValidator is a validator for the "sold_notes" field. It is called by the builders before save.
|
||||
SoldNotesValidator func(string) error
|
||||
// DefaultID holds the default value on creation for the "id" field.
|
||||
DefaultID func() uuid.UUID
|
||||
)
|
|
@ -1,89 +0,0 @@
|
|||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package location
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
// Label holds the string label denoting the location type in the database.
|
||||
Label = "location"
|
||||
// FieldID holds the string denoting the id field in the database.
|
||||
FieldID = "id"
|
||||
// FieldCreatedAt holds the string denoting the created_at field in the database.
|
||||
FieldCreatedAt = "created_at"
|
||||
// FieldUpdatedAt holds the string denoting the updated_at field in the database.
|
||||
FieldUpdatedAt = "updated_at"
|
||||
// FieldName holds the string denoting the name field in the database.
|
||||
FieldName = "name"
|
||||
// FieldDescription holds the string denoting the description field in the database.
|
||||
FieldDescription = "description"
|
||||
// EdgeGroup holds the string denoting the group edge name in mutations.
|
||||
EdgeGroup = "group"
|
||||
// EdgeItems holds the string denoting the items edge name in mutations.
|
||||
EdgeItems = "items"
|
||||
// Table holds the table name of the location in the database.
|
||||
Table = "locations"
|
||||
// GroupTable is the table that holds the group relation/edge.
|
||||
GroupTable = "locations"
|
||||
// GroupInverseTable is the table name for the Group entity.
|
||||
// It exists in this package in order to avoid circular dependency with the "group" package.
|
||||
GroupInverseTable = "groups"
|
||||
// GroupColumn is the table column denoting the group relation/edge.
|
||||
GroupColumn = "group_locations"
|
||||
// ItemsTable is the table that holds the items relation/edge.
|
||||
ItemsTable = "items"
|
||||
// ItemsInverseTable is the table name for the Item entity.
|
||||
// It exists in this package in order to avoid circular dependency with the "item" package.
|
||||
ItemsInverseTable = "items"
|
||||
// ItemsColumn is the table column denoting the items relation/edge.
|
||||
ItemsColumn = "location_items"
|
||||
)
|
||||
|
||||
// Columns holds all SQL columns for location fields.
|
||||
var Columns = []string{
|
||||
FieldID,
|
||||
FieldCreatedAt,
|
||||
FieldUpdatedAt,
|
||||
FieldName,
|
||||
FieldDescription,
|
||||
}
|
||||
|
||||
// ForeignKeys holds the SQL foreign-keys that are owned by the "locations"
|
||||
// table and are not defined as standalone fields in the schema.
|
||||
var ForeignKeys = []string{
|
||||
"group_locations",
|
||||
}
|
||||
|
||||
// ValidColumn reports if the column name is valid (part of the table columns).
|
||||
func ValidColumn(column string) bool {
|
||||
for i := range Columns {
|
||||
if column == Columns[i] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for i := range ForeignKeys {
|
||||
if column == ForeignKeys[i] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var (
|
||||
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
|
||||
DefaultCreatedAt func() time.Time
|
||||
// DefaultUpdatedAt holds the default value on creation for the "updated_at" field.
|
||||
DefaultUpdatedAt func() time.Time
|
||||
// UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field.
|
||||
UpdateDefaultUpdatedAt func() time.Time
|
||||
// NameValidator is a validator for the "name" field. It is called by the builders before save.
|
||||
NameValidator func(string) error
|
||||
// DescriptionValidator is a validator for the "description" field. It is called by the builders before save.
|
||||
DescriptionValidator func(string) error
|
||||
// DefaultID holds the default value on creation for the "id" field.
|
||||
DefaultID func() uuid.UUID
|
||||
)
|
|
@ -1,50 +0,0 @@
|
|||
package schema
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"entgo.io/ent"
|
||||
"entgo.io/ent/schema/edge"
|
||||
"entgo.io/ent/schema/field"
|
||||
"entgo.io/ent/schema/index"
|
||||
"github.com/hay-kot/homebox/backend/ent/schema/mixins"
|
||||
)
|
||||
|
||||
// DocumentToken holds the schema definition for the DocumentToken entity.
|
||||
type DocumentToken struct {
|
||||
ent.Schema
|
||||
}
|
||||
|
||||
func (DocumentToken) Mixin() []ent.Mixin {
|
||||
return []ent.Mixin{
|
||||
mixins.BaseMixin{},
|
||||
}
|
||||
}
|
||||
|
||||
// Fields of the DocumentToken.
|
||||
func (DocumentToken) Fields() []ent.Field {
|
||||
return []ent.Field{
|
||||
field.Bytes("token").
|
||||
NotEmpty().
|
||||
Unique(),
|
||||
field.Int("uses").
|
||||
Default(1),
|
||||
field.Time("expires_at").
|
||||
Default(func() time.Time { return time.Now().Add(time.Minute * 10) }),
|
||||
}
|
||||
}
|
||||
|
||||
// Edges of the DocumentToken.
|
||||
func (DocumentToken) Edges() []ent.Edge {
|
||||
return []ent.Edge{
|
||||
edge.From("document", Document.Type).
|
||||
Ref("document_tokens").
|
||||
Unique(),
|
||||
}
|
||||
}
|
||||
|
||||
func (DocumentToken) Indexes() []ent.Index {
|
||||
return []ent.Index{
|
||||
index.Fields("token"),
|
||||
}
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
package schema
|
||||
|
||||
import (
|
||||
"entgo.io/ent"
|
||||
"entgo.io/ent/dialect/entsql"
|
||||
"entgo.io/ent/schema/edge"
|
||||
"entgo.io/ent/schema/field"
|
||||
"github.com/hay-kot/homebox/backend/ent/schema/mixins"
|
||||
)
|
||||
|
||||
// Group holds the schema definition for the Group entity.
|
||||
type Group struct {
|
||||
ent.Schema
|
||||
}
|
||||
|
||||
func (Group) Mixin() []ent.Mixin {
|
||||
return []ent.Mixin{
|
||||
mixins.BaseMixin{},
|
||||
}
|
||||
}
|
||||
|
||||
// Fields of the Home.
|
||||
func (Group) Fields() []ent.Field {
|
||||
return []ent.Field{
|
||||
field.String("name").
|
||||
MaxLen(255).
|
||||
NotEmpty(),
|
||||
field.Enum("currency").
|
||||
Default("usd").
|
||||
Values("usd"), // TODO: add more currencies
|
||||
}
|
||||
}
|
||||
|
||||
// Edges of the Home.
|
||||
func (Group) Edges() []ent.Edge {
|
||||
return []ent.Edge{
|
||||
edge.To("users", User.Type).
|
||||
Annotations(entsql.Annotation{
|
||||
OnDelete: entsql.Cascade,
|
||||
}),
|
||||
edge.To("locations", Location.Type).
|
||||
Annotations(entsql.Annotation{
|
||||
OnDelete: entsql.Cascade,
|
||||
}),
|
||||
edge.To("items", Item.Type).
|
||||
Annotations(entsql.Annotation{
|
||||
OnDelete: entsql.Cascade,
|
||||
}),
|
||||
edge.To("labels", Label.Type).
|
||||
Annotations(entsql.Annotation{
|
||||
OnDelete: entsql.Cascade,
|
||||
}),
|
||||
edge.To("documents", Document.Type).
|
||||
Annotations(entsql.Annotation{
|
||||
OnDelete: entsql.Cascade,
|
||||
}),
|
||||
edge.To("invitation_tokens", GroupInvitationToken.Type).
|
||||
Annotations(entsql.Annotation{
|
||||
OnDelete: entsql.Cascade,
|
||||
}),
|
||||
}
|
||||
}
|
|
@ -1,46 +1,77 @@
|
|||
module github.com/hay-kot/homebox/backend
|
||||
|
||||
go 1.19
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.24.2
|
||||
|
||||
require (
|
||||
ariga.io/atlas v0.7.2-0.20220927111110-867ee0cca56a
|
||||
entgo.io/ent v0.11.3
|
||||
github.com/ardanlabs/conf/v2 v2.2.0
|
||||
github.com/go-chi/chi/v5 v5.0.7
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/mattn/go-sqlite3 v1.14.15
|
||||
github.com/rs/zerolog v1.28.0
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/swaggo/http-swagger v1.3.3
|
||||
github.com/swaggo/swag v1.8.6
|
||||
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90
|
||||
ariga.io/atlas v0.32.0
|
||||
entgo.io/ent v0.14.4
|
||||
github.com/ardanlabs/conf/v3 v3.7.1
|
||||
github.com/containrrr/shoutrrr v0.8.0
|
||||
github.com/go-chi/chi/v5 v5.2.1
|
||||
github.com/go-playground/validator/v10 v10.26.0
|
||||
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/schema v1.4.1
|
||||
github.com/hay-kot/httpkit v0.0.11
|
||||
github.com/mattn/go-sqlite3 v1.14.27
|
||||
github.com/olahol/melody v1.2.1
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/rs/zerolog v1.34.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/swaggo/http-swagger/v2 v2.0.2
|
||||
github.com/swaggo/swag v1.16.4
|
||||
github.com/yeqown/go-qrcode/v2 v2.2.5
|
||||
github.com/yeqown/go-qrcode/writer/standard v1.2.5
|
||||
golang.org/x/crypto v0.37.0
|
||||
modernc.org/sqlite v1.37.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/agext/levenshtein v1.2.3 // indirect
|
||||
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
||||
github.com/bmatcuk/doublestar v1.3.4 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/go-openapi/inflect v0.19.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.0 // indirect
|
||||
github.com/go-openapi/spec v0.20.7 // indirect
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/hashicorp/hcl/v2 v2.14.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fogleman/gg v1.3.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/go-openapi/inflect v0.21.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.1 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/spec v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.1 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/hashicorp/hcl/v2 v2.23.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/kr/pretty v0.3.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a // indirect
|
||||
github.com/zclconf/go-cty v1.11.0 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/net v0.0.0-20220923203811-8be639271d50 // indirect
|
||||
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/tools v0.1.13-0.20220804200503-81c7dc4e4efa // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/swaggo/files/v2 v2.0.2 // indirect
|
||||
github.com/yeqown/reedsolomon v1.0.0 // indirect
|
||||
github.com/zclconf/go-cty v1.16.2 // indirect
|
||||
github.com/zclconf/go-cty-yaml v1.1.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
|
||||
golang.org/x/image v0.26.0 // indirect
|
||||
golang.org/x/mod v0.24.0 // indirect
|
||||
golang.org/x/net v0.39.0 // indirect
|
||||
golang.org/x/sync v0.13.0 // indirect
|
||||
golang.org/x/sys v0.32.0 // indirect
|
||||
golang.org/x/text v0.24.0 // indirect
|
||||
golang.org/x/tools v0.32.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
modernc.org/libc v1.62.1 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.9.1 // indirect
|
||||
)
|
||||
|
|
265
backend/go.sum
265
backend/go.sum
|
@ -1,125 +1,190 @@
|
|||
ariga.io/atlas v0.7.2-0.20220927111110-867ee0cca56a h1:6/nt4DODfgxzHTTg3tYy7YkVzruGQGZ/kRvXpA45KUo=
|
||||
ariga.io/atlas v0.7.2-0.20220927111110-867ee0cca56a/go.mod h1:ft47uSh5hWGDCmQC9DsztZg6Xk+KagM5Ts/mZYKb9JE=
|
||||
entgo.io/ent v0.11.3 h1:F5FBGAWiDCGder7YT+lqMnyzXl6d0xU3xMBM/SO3CMc=
|
||||
entgo.io/ent v0.11.3/go.mod h1:mvDhvynOzAsOe7anH7ynPPtMjA/eeXP96kAfweevyxc=
|
||||
ariga.io/atlas v0.32.0 h1:y+77nueMrExLiKlz1CcPKh/nU7VSlWfBbwCShsJyvCw=
|
||||
ariga.io/atlas v0.32.0/go.mod h1:Oe1xWPuu5q9LzyrWfbZmEZxFYeu4BHTyzfjeW2aZp/w=
|
||||
entgo.io/ent v0.14.4 h1:/DhDraSLXIkBhyiVoJeSshr4ZYi7femzhj6/TckzZuI=
|
||||
entgo.io/ent v0.14.4/go.mod h1:aDPE/OziPEu8+OWbzy4UlvWmD2/kbRuWfK2A40hcxJM=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
|
||||
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
|
||||
github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw=
|
||||
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
|
||||
github.com/ardanlabs/conf/v2 v2.2.0 h1:ar1+TYIYAh2Tdeg2DQroh7ruR56/vJR8BDfzDIrXgtk=
|
||||
github.com/ardanlabs/conf/v2 v2.2.0/go.mod h1:m37ZKdW9jwMUEhGX36jRNt8VzSQ/HVmSziLZH2p33nY=
|
||||
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
|
||||
github.com/ardanlabs/conf/v3 v3.7.1 h1:GIV7ylesF/0NexhnJdLmzsi2NIVYY2wVhR0UfvpmAeQ=
|
||||
github.com/ardanlabs/conf/v3 v3.7.1/go.mod h1:IIucqD+601gt3jfhMXVukxoT16LnoGVd2DzRC2GhHiA=
|
||||
github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0=
|
||||
github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
|
||||
github.com/containrrr/shoutrrr v0.8.0 h1:mfG2ATzIS7NR2Ec6XL+xyoHzN97H8WPjir8aYzJUSec=
|
||||
github.com/containrrr/shoutrrr v0.8.0/go.mod h1:ioyQAyu1LJY6sILuNyKaQaw+9Ttik5QePU8atnAdO2o=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
|
||||
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4=
|
||||
github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA=
|
||||
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
|
||||
github.com/go-openapi/spec v0.20.7 h1:1Rlu/ZrOCCob0n+JKKJAWhNWMPW8bOZRg8FJaY+0SKI=
|
||||
github.com/go-openapi/spec v0.20.7/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
|
||||
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
|
||||
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
||||
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
|
||||
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-openapi/inflect v0.21.2 h1:0gClGlGcxifcJR56zwvhaOulnNgnhc4qTAkob5ObnSM=
|
||||
github.com/go-openapi/inflect v0.21.2/go.mod h1:INezMuUu7SJQc2AyR3WO0DqqYUJSj8Kb4hBd7WtjlAw=
|
||||
github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=
|
||||
github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
|
||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
|
||||
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
|
||||
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
|
||||
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
|
||||
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
|
||||
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 h1:FWNFq4fM1wPfcK40yHE5UO3RUdSNPaBC+j3PokzA6OQ=
|
||||
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hashicorp/hcl/v2 v2.14.1 h1:x0BpjfZ+CYdbiz+8yZTQ+gdLO7IXvOut7Da+XJayx34=
|
||||
github.com/hashicorp/hcl/v2 v2.14.1/go.mod h1:e4z5nxYlWNPdDSNYX+ph14EvWYMFm3eP0zIUqPc2jr0=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E=
|
||||
github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos=
|
||||
github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
|
||||
github.com/hay-kot/httpkit v0.0.11 h1:ZdB2uqsFBSDpfUoClGK5c5orjBjQkEVSXh7fZX5FKEk=
|
||||
github.com/hay-kot/httpkit v0.0.11/go.mod h1:0kZdk5/swzdfqfg2c6pBWimcgeJ9PTyO97EbHnYl2Sw=
|
||||
github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc=
|
||||
github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
|
||||
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.27 h1:drZCnuvf37yPfs95E5jd9s3XhdVWLal+6BOK6qrv6IU=
|
||||
github.com/mattn/go-sqlite3 v1.14.27/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/olahol/melody v1.2.1 h1:xdwRkzHxf+B0w4TKbGpUSSkV516ZucQZJIWLztOWICQ=
|
||||
github.com/olahol/melody v1.2.1/go.mod h1:GgkTl6Y7yWj/HtfD48Q5vLKPVoZOH+Qqgfa7CvJgJM4=
|
||||
github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU=
|
||||
github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts=
|
||||
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
|
||||
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY=
|
||||
github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
|
||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a h1:kAe4YSu0O0UFn1DowNo2MY5p6xzqtJ/wQ7LZynSvGaY=
|
||||
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w=
|
||||
github.com/swaggo/http-swagger v1.3.3 h1:Hu5Z0L9ssyBLofaama21iYaF2VbWyA8jdohaaCGpHsc=
|
||||
github.com/swaggo/http-swagger v1.3.3/go.mod h1:sE+4PjD89IxMPm77FnkDz0sdO+p5lbXzrVWT6OTVVGo=
|
||||
github.com/swaggo/swag v1.8.6 h1:2rgOaLbonWu1PLP6G+/rYjSvPg0jQE0HtrEKuE380eg=
|
||||
github.com/swaggo/swag v1.8.6/go.mod h1:jMLeXOOmYyjk8PvHTsXBdrubsNd9gUJTTCzL5iBnseg=
|
||||
github.com/zclconf/go-cty v1.11.0 h1:726SxLdi2SDnjY+BStqB9J1hNp4+2WlzyXLuimibIe0=
|
||||
github.com/zclconf/go-cty v1.11.0/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA=
|
||||
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM=
|
||||
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220923203811-8be639271d50 h1:vKyz8L3zkd+xrMeIaBsQ/MNVPVFSffdaU3ZyYlBGFnI=
|
||||
golang.org/x/net v0.0.0-20220923203811-8be639271d50/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/swaggo/files/v2 v2.0.2 h1:Bq4tgS/yxLB/3nwOMcul5oLEUKa877Ykgz3CJMVbQKU=
|
||||
github.com/swaggo/files/v2 v2.0.2/go.mod h1:TVqetIzZsO9OhHX1Am9sRf9LdrFZqoK49N37KON/jr0=
|
||||
github.com/swaggo/http-swagger/v2 v2.0.2 h1:FKCdLsl+sFCx60KFsyM0rDarwiUSZ8DqbfSyIKC9OBg=
|
||||
github.com/swaggo/http-swagger/v2 v2.0.2/go.mod h1:r7/GBkAWIfK6E/OLnE8fXnviHiDeAHmgIyooa4xm3AQ=
|
||||
github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
|
||||
github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
|
||||
github.com/yeqown/go-qrcode/v2 v2.2.5 h1:HCOe2bSjkhZyYoyyNaXNzh4DJZll6inVJQQw+8228Zk=
|
||||
github.com/yeqown/go-qrcode/v2 v2.2.5/go.mod h1:uHpt9CM0V1HeXLz+Wg5MN50/sI/fQhfkZlOM+cOTHxw=
|
||||
github.com/yeqown/go-qrcode/writer/standard v1.2.5 h1:m+5BUIcbsaG2md76FIqI/oZULrAju8tsk47eOohovQ0=
|
||||
github.com/yeqown/go-qrcode/writer/standard v1.2.5/go.mod h1:O4MbzsotGCvy8upYPCR91j81dr5XLT7heuljcNXW+oQ=
|
||||
github.com/yeqown/reedsolomon v1.0.0 h1:x1h/Ej/uJnNu8jaX7GLHBWmZKCAWjEJTetkqaabr4B0=
|
||||
github.com/yeqown/reedsolomon v1.0.0/go.mod h1:P76zpcn2TCuL0ul1Fso373qHRc69LKwAw/Iy6g1WiiM=
|
||||
github.com/zclconf/go-cty v1.16.2 h1:LAJSwc3v81IRBZyUVQDUdZ7hs3SYs9jv0eZJDWHD/70=
|
||||
github.com/zclconf/go-cty v1.16.2/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
|
||||
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
|
||||
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
|
||||
github.com/zclconf/go-cty-yaml v1.1.0 h1:nP+jp0qPHv2IhUVqmQSzjvqAWcObN0KBkUl2rWBdig0=
|
||||
github.com/zclconf/go-cty-yaml v1.1.0/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs=
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
|
||||
golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY=
|
||||
golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c=
|
||||
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 h1:h+EGohizhe9XlX18rfpa8k8RAc5XyaeamM+0VHRd4lc=
|
||||
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.1.13-0.20220804200503-81c7dc4e4efa h1:uKcci2q7Qtp6nMTC/AAvfNUAldFtJuHWV9/5QWiypts=
|
||||
golang.org/x/tools v0.1.13-0.20220804200503-81c7dc4e4efa/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||
golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
|
||||
golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
modernc.org/cc/v4 v4.25.2 h1:T2oH7sZdGvTaie0BRNFbIYsabzCxUQg8nLqCdQ2i0ic=
|
||||
modernc.org/cc/v4 v4.25.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.25.1 h1:TFSzPrAGmDsdnhT9X2UrcPMI3N/mJ9/X9ykKXwLhDsU=
|
||||
modernc.org/ccgo/v4 v4.25.1/go.mod h1:njjuAYiPflywOOrm3B7kCB444ONP5pAVr8PIEoE0uDw=
|
||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/libc v1.62.1 h1:s0+fv5E3FymN8eJVmnk0llBe6rOxCu/DEU+XygRbS8s=
|
||||
modernc.org/libc v1.62.1/go.mod h1:iXhATfJQLjG3NWy56a6WVU73lWOcdYVxsvwCgoPljuo=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.9.1 h1:V/Z1solwAVmMW1yttq3nDdZPJqV1rM05Ccq6KMSZ34g=
|
||||
modernc.org/memory v1.9.1/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.37.0 h1:s1TMe7T3Q3ovQiK2Ouz4Jwh7dw4ZDqbebSDTlSJdfjI=
|
||||
modernc.org/sqlite v1.37.0/go.mod h1:5YiWv+YviqGMuGw4V+PNplcyaJ5v+vQd7TQOgkACoJM=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/ardanlabs/conf/v2"
|
||||
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
ModeDevelopment = "development"
|
||||
ModeProduction = "production"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Mode string `yaml:"mode" conf:"default:development"` // development or production
|
||||
Web WebConfig `yaml:"web"`
|
||||
Storage Storage `yaml:"storage"`
|
||||
Log LoggerConf `yaml:"logger"`
|
||||
Mailer MailerConf `yaml:"mailer"`
|
||||
Swagger SwaggerConf `yaml:"swagger"`
|
||||
Demo bool `yaml:"demo"`
|
||||
}
|
||||
|
||||
type SwaggerConf struct {
|
||||
Host string `yaml:"host" conf:"default:localhost:7745"`
|
||||
Scheme string `yaml:"scheme" conf:"default:http"`
|
||||
}
|
||||
|
||||
type WebConfig struct {
|
||||
Port string `yaml:"port" conf:"default:7745"`
|
||||
Host string `yaml:"host"`
|
||||
MaxUploadSize int64 `yaml:"max_file_upload" conf:"default:10"`
|
||||
}
|
||||
|
||||
// New parses the CLI/Config file and returns a Config struct. If the file argument is an empty string, the
|
||||
// file is not read. If the file is not empty, the file is read and the Config struct is returned.
|
||||
func New() (*Config, error) {
|
||||
var cfg Config
|
||||
const prefix = "HBOX"
|
||||
|
||||
help, err := conf.Parse(prefix, &cfg)
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, conf.ErrHelpWanted) {
|
||||
fmt.Println(help)
|
||||
os.Exit(0)
|
||||
}
|
||||
return &cfg, fmt.Errorf("parsing config: %w", err)
|
||||
}
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
// Print prints the configuration to stdout as a json indented string
|
||||
// This is useful for debugging. If the marshaller errors out, it will panic.
|
||||
func (c *Config) Print() {
|
||||
res, err := json.MarshalIndent(c, "", " ")
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(string(res))
|
||||
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
package config
|
||||
|
||||
const (
|
||||
DriverSqlite3 = "sqlite3"
|
||||
)
|
||||
|
||||
type Storage struct {
|
||||
// Data is the path to the root directory
|
||||
Data string `yaml:"data" conf:"default:./.data"`
|
||||
SqliteUrl string `yaml:"sqlite-url" conf:"default:./.data/homebox.db?_fk=1"`
|
||||
}
|
104
backend/internal/core/currencies/currencies.go
Normal file
104
backend/internal/core/currencies/currencies.go
Normal file
|
@ -0,0 +1,104 @@
|
|||
// Package currencies provides a shared definition of currencies. This uses a global
|
||||
// variable to hold the currencies.
|
||||
package currencies
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
//go:embed currencies.json
|
||||
var defaults []byte
|
||||
|
||||
type CollectorFunc func() ([]Currency, error)
|
||||
|
||||
func CollectJSON(reader io.Reader) CollectorFunc {
|
||||
return func() ([]Currency, error) {
|
||||
var currencies []Currency
|
||||
err := json.NewDecoder(reader).Decode(¤cies)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return currencies, nil
|
||||
}
|
||||
}
|
||||
|
||||
func CollectDefaults() CollectorFunc {
|
||||
return CollectJSON(bytes.NewReader(defaults))
|
||||
}
|
||||
|
||||
func CollectionCurrencies(collectors ...CollectorFunc) ([]Currency, error) {
|
||||
out := make([]Currency, 0, len(collectors))
|
||||
for i := range collectors {
|
||||
c, err := collectors[i]()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out = append(out, c...)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type Currency struct {
|
||||
Name string `json:"name"`
|
||||
Code string `json:"code"`
|
||||
Local string `json:"local"`
|
||||
Symbol string `json:"symbol"`
|
||||
}
|
||||
|
||||
type CurrencyRegistry struct {
|
||||
mu sync.RWMutex
|
||||
registry map[string]Currency
|
||||
}
|
||||
|
||||
func NewCurrencyService(currencies []Currency) *CurrencyRegistry {
|
||||
registry := make(map[string]Currency, len(currencies))
|
||||
for i := range currencies {
|
||||
registry[currencies[i].Code] = currencies[i]
|
||||
}
|
||||
|
||||
return &CurrencyRegistry{
|
||||
registry: registry,
|
||||
}
|
||||
}
|
||||
|
||||
func (cs *CurrencyRegistry) Slice() []Currency {
|
||||
cs.mu.RLock()
|
||||
defer cs.mu.RUnlock()
|
||||
|
||||
out := make([]Currency, 0, len(cs.registry))
|
||||
for key := range cs.registry {
|
||||
out = append(out, cs.registry[key])
|
||||
}
|
||||
|
||||
slices.SortFunc(out, func(a, b Currency) int {
|
||||
if a.Name < b.Name {
|
||||
return -1
|
||||
}
|
||||
|
||||
if a.Name > b.Name {
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
})
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (cs *CurrencyRegistry) IsSupported(code string) bool {
|
||||
upper := strings.ToUpper(code)
|
||||
|
||||
cs.mu.RLock()
|
||||
defer cs.mu.RUnlock()
|
||||
_, ok := cs.registry[upper]
|
||||
return ok
|
||||
}
|
638
backend/internal/core/currencies/currencies.json
Normal file
638
backend/internal/core/currencies/currencies.json
Normal file
|
@ -0,0 +1,638 @@
|
|||
[
|
||||
{
|
||||
"code": "USD",
|
||||
"local": "United States",
|
||||
"symbol": "$",
|
||||
"name": "United States Dollar"
|
||||
},
|
||||
{
|
||||
"code": "AED",
|
||||
"local": "United Arab Emirates",
|
||||
"symbol": "د.إ",
|
||||
"name": "United Arab Emirates Dirham"
|
||||
},
|
||||
{
|
||||
"code": "AFN",
|
||||
"local": "Afghanistan",
|
||||
"symbol": "؋",
|
||||
"name": "Afghan Afghani"
|
||||
},
|
||||
{
|
||||
"code": "ALL",
|
||||
"local": "Albania",
|
||||
"symbol": "L",
|
||||
"name": "Albanian Lek"
|
||||
},
|
||||
{
|
||||
"code": "AMD",
|
||||
"local": "Armenia",
|
||||
"symbol": "֏",
|
||||
"name": "Armenian Dram"
|
||||
},
|
||||
{
|
||||
"code": "ANG",
|
||||
"local": "Netherlands Antilles",
|
||||
"symbol": "ƒ",
|
||||
"name": "Netherlands Antillean Guilder"
|
||||
},
|
||||
{
|
||||
"code": "AOA",
|
||||
"local": "Angola",
|
||||
"symbol": "Kz",
|
||||
"name": "Angolan Kwanza"
|
||||
},
|
||||
{
|
||||
"code": "ARS",
|
||||
"local": "Argentina",
|
||||
"symbol": "$",
|
||||
"name": "Argentine Peso"
|
||||
},
|
||||
{
|
||||
"code": "AUD",
|
||||
"local": "Australia",
|
||||
"symbol": "A$",
|
||||
"name": "Australian Dollar"
|
||||
},
|
||||
{
|
||||
"code": "AWG",
|
||||
"local": "Aruba",
|
||||
"symbol": "ƒ",
|
||||
"name": "Aruban Florin"
|
||||
},
|
||||
{
|
||||
"code": "AZN",
|
||||
"local": "Azerbaijan",
|
||||
"symbol": "₼",
|
||||
"name": "Azerbaijani Manat"
|
||||
},
|
||||
{
|
||||
"code": "BAM",
|
||||
"local": "Bosnia and Herzegovina",
|
||||
"symbol": "KM",
|
||||
"name": "Bosnia and Herzegovina Convertible Mark"
|
||||
},
|
||||
{
|
||||
"code": "BBD",
|
||||
"local": "Barbados",
|
||||
"symbol": "Bds$",
|
||||
"name": "Barbadian Dollar"
|
||||
},
|
||||
{
|
||||
"code": "BDT",
|
||||
"local": "Bangladesh",
|
||||
"symbol": "৳",
|
||||
"name": "Bangladeshi Taka"
|
||||
},
|
||||
{
|
||||
"code": "BGN",
|
||||
"local": "Bulgaria",
|
||||
"symbol": "лв",
|
||||
"name": "Bulgarian lev"
|
||||
},
|
||||
{
|
||||
"code": "BHD",
|
||||
"local": "Bahrain",
|
||||
"symbol": "ب.د",
|
||||
"name": "Bahraini Dinar"
|
||||
},
|
||||
{
|
||||
"code": "BIF",
|
||||
"local": "Burundi",
|
||||
"symbol": "FBu",
|
||||
"name": "Burundian Franc"
|
||||
},
|
||||
{
|
||||
"code": "BMD",
|
||||
"local": "Bermuda",
|
||||
"symbol": "BD$",
|
||||
"name": "Bermudian Dollar"
|
||||
},
|
||||
{
|
||||
"code": "BND",
|
||||
"local": "Brunei",
|
||||
"symbol": "B$",
|
||||
"name": "Brunei Dollar"
|
||||
},
|
||||
{
|
||||
"code": "BOB",
|
||||
"local": "Bolivia",
|
||||
"symbol": "Bs.",
|
||||
"name": "Bolivian Boliviano"
|
||||
},
|
||||
{
|
||||
"code": "BRL",
|
||||
"local": "Brazil",
|
||||
"symbol": "R$",
|
||||
"name": "Brazilian Real"
|
||||
},
|
||||
{
|
||||
"code": "BSD",
|
||||
"local": "Bahamas",
|
||||
"symbol": "B$",
|
||||
"name": "Bahamian Dollar"
|
||||
},
|
||||
{
|
||||
"code": "BTN",
|
||||
"local": "Bhutan",
|
||||
"symbol": "Nu.",
|
||||
"name": "Bhutanese Ngultrum"
|
||||
},
|
||||
{
|
||||
"code": "BWP",
|
||||
"local": "Botswana",
|
||||
"symbol": "P",
|
||||
"name": "Botswana Pula"
|
||||
},
|
||||
{
|
||||
"code": "BYN",
|
||||
"local": "Belarus",
|
||||
"symbol": "Br",
|
||||
"name": "Belarusian Ruble"
|
||||
},
|
||||
{
|
||||
"code": "BZD",
|
||||
"local": "Belize",
|
||||
"symbol": "BZ$",
|
||||
"name": "Belize Dollar"
|
||||
},
|
||||
{
|
||||
"code": "CAD",
|
||||
"local": "Canada",
|
||||
"symbol": "C$",
|
||||
"name": "Canadian Dollar"
|
||||
},
|
||||
{
|
||||
"code": "CDF",
|
||||
"local": "Democratic Republic of the Congo",
|
||||
"symbol": "FC",
|
||||
"name": "Congolese Franc"
|
||||
},
|
||||
{
|
||||
"code": "CHF",
|
||||
"local": "Switzerland",
|
||||
"symbol": "CHF",
|
||||
"name": "Swiss Franc"
|
||||
},
|
||||
{
|
||||
"code": "CLP",
|
||||
"local": "Chile",
|
||||
"symbol": "CL$",
|
||||
"name": "Chilean Peso"
|
||||
},
|
||||
{
|
||||
"code": "CNY",
|
||||
"local": "China",
|
||||
"symbol": "¥",
|
||||
"name": "Chinese Yuan"
|
||||
},
|
||||
{
|
||||
"code": "COP",
|
||||
"local": "Colombia",
|
||||
"symbol": "COL$",
|
||||
"name": "Colombian Peso"
|
||||
},
|
||||
{
|
||||
"code": "CRC",
|
||||
"local": "Costa Rica",
|
||||
"symbol": "₡",
|
||||
"name": "Costa Rican Colón"
|
||||
},
|
||||
{
|
||||
"code": "CUP",
|
||||
"local": "Cuba",
|
||||
"symbol": "₱",
|
||||
"name": "Cuban Peso"
|
||||
},
|
||||
{
|
||||
"code": "CVE",
|
||||
"local": "Cape Verde",
|
||||
"symbol": "$",
|
||||
"name": "Cape Verdean Escudo"
|
||||
},
|
||||
{
|
||||
"code": "CZK",
|
||||
"local": "Czech Republic",
|
||||
"symbol": "Kč",
|
||||
"name": "Czech Koruna"
|
||||
},
|
||||
{
|
||||
"code": "DJF",
|
||||
"local": "Djibouti",
|
||||
"symbol": "Fdj",
|
||||
"name": "Djiboutian Franc"
|
||||
},
|
||||
{
|
||||
"code": "DKK",
|
||||
"local": "Denmark",
|
||||
"symbol": "kr",
|
||||
"name": "Danish Krone"
|
||||
},
|
||||
{
|
||||
"code": "DOP",
|
||||
"local": "Dominican Republic",
|
||||
"symbol": "RD$",
|
||||
"name": "Dominican Peso"
|
||||
},
|
||||
{
|
||||
"code": "DZD",
|
||||
"local": "Algeria",
|
||||
"symbol": "د.ج",
|
||||
"name": "Algerian Dinar"
|
||||
},
|
||||
{
|
||||
"code": "EGP",
|
||||
"local": "Egypt",
|
||||
"symbol": "£",
|
||||
"name": "Egyptian Pound"
|
||||
},
|
||||
{
|
||||
"code": "ERN",
|
||||
"local": "Eritrea",
|
||||
"symbol": "Nfk",
|
||||
"name": "Eritrean Nakfa"
|
||||
},
|
||||
{
|
||||
"code": "ETB",
|
||||
"local": "Ethiopia",
|
||||
"symbol": "Br",
|
||||
"name": "Ethiopian Birr"
|
||||
},
|
||||
{
|
||||
"code": "EUR",
|
||||
"local": "Eurozone",
|
||||
"symbol": "€",
|
||||
"name": "Euro"
|
||||
},
|
||||
{
|
||||
"code": "FJD",
|
||||
"local": "Fiji",
|
||||
"symbol": "FJ$",
|
||||
"name": "Fijian Dollar"
|
||||
},
|
||||
{
|
||||
"code": "FKP",
|
||||
"local": "Falkland Islands",
|
||||
"symbol": "£",
|
||||
"name": "Falkland Islands Pound"
|
||||
},
|
||||
{
|
||||
"code": "FOK",
|
||||
"local": "Faroe Islands",
|
||||
"symbol": "kr",
|
||||
"name": "Faroese Króna"
|
||||
},
|
||||
{
|
||||
"code": "GBP",
|
||||
"local": "United Kingdom",
|
||||
"symbol": "£",
|
||||
"name": "British Pound Sterling"
|
||||
},
|
||||
{
|
||||
"code": "GEL",
|
||||
"local": "Georgia",
|
||||
"symbol": "₾",
|
||||
"name": "Georgian Lari"
|
||||
},
|
||||
{
|
||||
"code": "GGP",
|
||||
"local": "Guernsey",
|
||||
"symbol": "£",
|
||||
"name": "Guernsey Pound"
|
||||
},
|
||||
{
|
||||
"code": "GHS",
|
||||
"local": "Ghana",
|
||||
"symbol": "GH₵",
|
||||
"name": "Ghanaian Cedi"
|
||||
},
|
||||
{
|
||||
"code": "GIP",
|
||||
"local": "Gibraltar",
|
||||
"symbol": "£",
|
||||
"name": "Gibraltar Pound"
|
||||
},
|
||||
{
|
||||
"code": "GMD",
|
||||
"local": "Gambia",
|
||||
"symbol": "D",
|
||||
"name": "Gambian Dalasi"
|
||||
},
|
||||
{
|
||||
"code": "GNF",
|
||||
"local": "Guinea",
|
||||
"symbol": "FG",
|
||||
"name": "Guinean Franc"
|
||||
},
|
||||
{
|
||||
"code": "GTQ",
|
||||
"local": "Guatemala",
|
||||
"symbol": "Q",
|
||||
"name": "Guatemalan Quetzal"
|
||||
},
|
||||
{
|
||||
"code": "GYD",
|
||||
"local": "Guyana",
|
||||
"symbol": "GY$",
|
||||
"name": "Guyanese Dollar"
|
||||
},
|
||||
{
|
||||
"code": "HKD",
|
||||
"local": "Hong Kong",
|
||||
"symbol": "HK$",
|
||||
"name": "Hong Kong Dollar"
|
||||
},
|
||||
{
|
||||
"code": "HNL",
|
||||
"local": "Honduras",
|
||||
"symbol": "L",
|
||||
"name": "Honduran Lempira"
|
||||
},
|
||||
{
|
||||
"code": "HRK",
|
||||
"local": "Croatia",
|
||||
"symbol": "kn",
|
||||
"name": "Croatian Kuna"
|
||||
},
|
||||
{
|
||||
"code": "HTG",
|
||||
"local": "Haiti",
|
||||
"symbol": "G",
|
||||
"name": "Haitian Gourde"
|
||||
},
|
||||
{
|
||||
"code": "HUF",
|
||||
"local": "Hungary",
|
||||
"symbol": "Ft",
|
||||
"name": "Hungarian Forint"
|
||||
},
|
||||
{
|
||||
"code": "IDR",
|
||||
"local": "Indonesia",
|
||||
"symbol": "Rp",
|
||||
"name": "Indonesian Rupiah"
|
||||
},
|
||||
{
|
||||
"code": "ILS",
|
||||
"local": "Israel",
|
||||
"symbol": "₪",
|
||||
"name": "Israeli New Shekel"
|
||||
},
|
||||
{
|
||||
"code": "IMP",
|
||||
"local": "Isle of Man",
|
||||
"symbol": "£",
|
||||
"name": "Manx Pound"
|
||||
},
|
||||
{
|
||||
"code": "INR",
|
||||
"local": "India",
|
||||
"symbol": "₹",
|
||||
"name": "Indian Rupee"
|
||||
},
|
||||
{
|
||||
"code": "IQD",
|
||||
"local": "Iraq",
|
||||
"symbol": "ع.د",
|
||||
"name": "Iraqi Dinar"
|
||||
},
|
||||
{
|
||||
"code": "IRR",
|
||||
"local": "Iran",
|
||||
"symbol": "﷼",
|
||||
"name": "Iranian Rial"
|
||||
},
|
||||
{
|
||||
"code": "ISK",
|
||||
"local": "Iceland",
|
||||
"symbol": "kr",
|
||||
"name": "Icelandic Króna"
|
||||
},
|
||||
{
|
||||
"code": "JEP",
|
||||
"local": "Jersey",
|
||||
"symbol": "£",
|
||||
"name": "Jersey Pound"
|
||||
},
|
||||
{
|
||||
"code": "JMD",
|
||||
"local": "Jamaica",
|
||||
"symbol": "J$",
|
||||
"name": "Jamaican Dollar"
|
||||
},
|
||||
{
|
||||
"code": "JOD",
|
||||
"local": "Jordan",
|
||||
"symbol": "د.ا",
|
||||
"name": "Jordanian Dinar"
|
||||
},
|
||||
{
|
||||
"code": "JPY",
|
||||
"local": "Japan",
|
||||
"symbol": "¥",
|
||||
"name": "Japanese Yen"
|
||||
},
|
||||
{
|
||||
"code": "KES",
|
||||
"local": "Kenya",
|
||||
"symbol": "KSh",
|
||||
"name": "Kenyan Shilling"
|
||||
},
|
||||
{
|
||||
"code": "KGS",
|
||||
"local": "Kyrgyzstan",
|
||||
"symbol": "с",
|
||||
"name": "Kyrgyzstani Som"
|
||||
},
|
||||
{
|
||||
"code": "KHR",
|
||||
"local": "Cambodia",
|
||||
"symbol": "៛",
|
||||
"name": "Cambodian Riel"
|
||||
},
|
||||
{
|
||||
"code": "KID",
|
||||
"local": "Kiribati",
|
||||
"symbol": "$",
|
||||
"name": "Kiribati Dollar"
|
||||
},
|
||||
{
|
||||
"code": "KMF",
|
||||
"local": "Comoros",
|
||||
"symbol": "CF",
|
||||
"name": "Comorian Franc"
|
||||
},
|
||||
{
|
||||
"code": "KRW",
|
||||
"local": "South Korea",
|
||||
"symbol": "₩",
|
||||
"name": "South Korean Won"
|
||||
},
|
||||
{
|
||||
"code": "KWD",
|
||||
"local": "Kuwait",
|
||||
"symbol": "د.ك",
|
||||
"name": "Kuwaiti Dinar"
|
||||
},
|
||||
{
|
||||
"code": "KYD",
|
||||
"local": "Cayman Islands",
|
||||
"symbol": "CI$",
|
||||
"name": "Cayman Islands Dollar"
|
||||
},
|
||||
{
|
||||
"code": "KZT",
|
||||
"local": "Kazakhstan",
|
||||
"symbol": "₸",
|
||||
"name": "Kazakhstani Tenge"
|
||||
},
|
||||
{
|
||||
"code": "LAK",
|
||||
"local": "Laos",
|
||||
"symbol": "₭",
|
||||
"name": "Lao Kip"
|
||||
},
|
||||
{
|
||||
"code": "LBP",
|
||||
"local": "Lebanon",
|
||||
"symbol": "ل.ل",
|
||||
"name": "Lebanese Pound"
|
||||
},
|
||||
{
|
||||
"code": "LKR",
|
||||
"local": "Sri Lanka",
|
||||
"symbol": "₨",
|
||||
"name": "Sri Lankan Rupee"
|
||||
},
|
||||
{
|
||||
"code": "LRD",
|
||||
"local": "Liberia",
|
||||
"symbol": "L$",
|
||||
"name": "Liberian Dollar"
|
||||
},
|
||||
{
|
||||
"code": "LSL",
|
||||
"local": "Lesotho",
|
||||
"symbol": "M",
|
||||
"name": "Lesotho Loti"
|
||||
},
|
||||
{
|
||||
"code": "LYD",
|
||||
"local": "Libya",
|
||||
"symbol": "ل.د",
|
||||
"name": "Libyan Dinar"
|
||||
},
|
||||
{
|
||||
"code": "MAD",
|
||||
"local": "Morocco",
|
||||
"symbol": "د.م.",
|
||||
"name": "Moroccan Dirham"
|
||||
},
|
||||
{
|
||||
"code": "MDL",
|
||||
"local": "Moldova",
|
||||
"symbol": "lei",
|
||||
"name": "Moldovan Leu"
|
||||
},
|
||||
{
|
||||
"code": "MGA",
|
||||
"local": "Madagascar",
|
||||
"symbol": "Ar",
|
||||
"name": "Malagasy Ariary"
|
||||
},
|
||||
{
|
||||
"code": "MKD",
|
||||
"local": "North Macedonia",
|
||||
"symbol": "ден",
|
||||
"name": "Macedonian Denar"
|
||||
},
|
||||
{
|
||||
"code": "MMK",
|
||||
"local": "Myanmar",
|
||||
"symbol": "K",
|
||||
"name": "Myanmar Kyat"
|
||||
},
|
||||
{
|
||||
"code": "MNT",
|
||||
"local": "Mongolia",
|
||||
"symbol": "₮",
|
||||
"name": "Mongolian Tugrik"
|
||||
},
|
||||
{
|
||||
"code": "MOP",
|
||||
"local": "Macau",
|
||||
"symbol": "MOP$",
|
||||
"name": "Macanese Pataca"
|
||||
},
|
||||
{
|
||||
"code": "MRU",
|
||||
"local": "Mauritania",
|
||||
"symbol": "UM",
|
||||
"name": "Mauritanian Ouguiya"
|
||||
},
|
||||
{
|
||||
"code": "MUR",
|
||||
"local": "Mauritius",
|
||||
"symbol": "₨",
|
||||
"name": "Mauritian Rupee"
|
||||
},
|
||||
{
|
||||
"code": "MVR",
|
||||
"local": "Maldives",
|
||||
"symbol": "Rf",
|
||||
"name": "Maldivian Rufiyaa"
|
||||
},
|
||||
{
|
||||
"code": "MWK",
|
||||
"local": "Malawi",
|
||||
"symbol": "MK",
|
||||
"name": "Malawian Kwacha"
|
||||
},
|
||||
{
|
||||
"code": "MXN",
|
||||
"local": "Mexico",
|
||||
"symbol": "Mex$",
|
||||
"name": "Mexican Peso"
|
||||
},
|
||||
{
|
||||
"code": "MYR",
|
||||
"local": "Malaysia",
|
||||
"symbol": "RM",
|
||||
"name": "Malaysian Ringgit"
|
||||
},
|
||||
{
|
||||
"code": "MZN",
|
||||
"local": "Mozambique",
|
||||
"symbol": "MT",
|
||||
"name": "Mozambican Metical"
|
||||
},
|
||||
{
|
||||
"code": "NAD",
|
||||
"local": "Namibia",
|
||||
"symbol": "N$",
|
||||
"name": "Namibian Dollar"
|
||||
},
|
||||
{
|
||||
"code": "NGN",
|
||||
"local": "Nigeria",
|
||||
"symbol": "₦",
|
||||
"name": "Nigerian Naira"
|
||||
},
|
||||
{
|
||||
"code": "NIO",
|
||||
"local": "Nicaragua",
|
||||
"symbol": "C$",
|
||||
"name": "Nicaraguan Córdoba"
|
||||
},
|
||||
{
|
||||
"code": "NOK",
|
||||
"local": "Norway",
|
||||
"symbol": "kr",
|
||||
"name": "Norwegian Krone"
|
||||
},
|
||||
{
|
||||
"code": "UAH",
|
||||
"local": "Ukraine",
|
||||
"symbol": "₴",
|
||||
"name": "Ukrainian Hryvnia"
|
||||
}
|
||||
]
|
67
backend/internal/core/services/all.go
Normal file
67
backend/internal/core/services/all.go
Normal file
|
@ -0,0 +1,67 @@
|
|||
// Package services provides the core business logic for the application.
|
||||
package services
|
||||
|
||||
import (
|
||||
"github.com/hay-kot/homebox/backend/internal/core/currencies"
|
||||
"github.com/hay-kot/homebox/backend/internal/data/repo"
|
||||
)
|
||||
|
||||
type AllServices struct {
|
||||
User *UserService
|
||||
Group *GroupService
|
||||
Items *ItemService
|
||||
BackgroundService *BackgroundService
|
||||
Currencies *currencies.CurrencyRegistry
|
||||
}
|
||||
|
||||
type OptionsFunc func(*options)
|
||||
|
||||
type options struct {
|
||||
autoIncrementAssetID bool
|
||||
currencies []currencies.Currency
|
||||
}
|
||||
|
||||
func WithAutoIncrementAssetID(v bool) func(*options) {
|
||||
return func(o *options) {
|
||||
o.autoIncrementAssetID = v
|
||||
}
|
||||
}
|
||||
|
||||
func WithCurrencies(v []currencies.Currency) func(*options) {
|
||||
return func(o *options) {
|
||||
o.currencies = v
|
||||
}
|
||||
}
|
||||
|
||||
func New(repos *repo.AllRepos, opts ...OptionsFunc) *AllServices {
|
||||
if repos == nil {
|
||||
panic("repos cannot be nil")
|
||||
}
|
||||
|
||||
defaultCurrencies, err := currencies.CollectionCurrencies(
|
||||
currencies.CollectDefaults(),
|
||||
)
|
||||
if err != nil {
|
||||
panic("failed to collect default currencies")
|
||||
}
|
||||
|
||||
options := &options{
|
||||
autoIncrementAssetID: true,
|
||||
currencies: defaultCurrencies,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(options)
|
||||
}
|
||||
|
||||
return &AllServices{
|
||||
User: &UserService{repos},
|
||||
Group: &GroupService{repos},
|
||||
Items: &ItemService{
|
||||
repo: repos,
|
||||
autoIncrementAssetID: options.autoIncrementAssetID,
|
||||
},
|
||||
BackgroundService: &BackgroundService{repos},
|
||||
Currencies: currencies.NewCurrencyService(options.currencies),
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ import (
|
|||
"context"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||
"github.com/hay-kot/homebox/backend/internal/data/repo"
|
||||
)
|
||||
|
||||
type contextKeys struct {
|
|
@ -5,7 +5,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||
"github.com/hay-kot/homebox/backend/internal/data/repo"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
|
@ -3,19 +3,20 @@ package services
|
|||
import (
|
||||
"context"
|
||||
"log"
|
||||
"math/rand"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hay-kot/homebox/backend/ent"
|
||||
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||
"github.com/hay-kot/homebox/backend/internal/core/currencies"
|
||||
"github.com/hay-kot/homebox/backend/internal/core/services/reporting/eventbus"
|
||||
"github.com/hay-kot/homebox/backend/internal/data/ent"
|
||||
"github.com/hay-kot/homebox/backend/internal/data/repo"
|
||||
"github.com/hay-kot/homebox/backend/pkgs/faker"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
var (
|
||||
fk = faker.NewFaker()
|
||||
fk = faker.NewFaker()
|
||||
tbus = eventbus.New()
|
||||
|
||||
tCtx = Context{}
|
||||
tClient *ent.Client
|
||||
|
@ -49,8 +50,6 @@ func bootstrap() {
|
|||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
rand.Seed(int64(time.Now().Unix()))
|
||||
|
||||
client, err := ent.Open("sqlite3", "file:ent?mode=memory&cache=shared&_fk=1")
|
||||
if err != nil {
|
||||
log.Fatalf("failed opening connection to sqlite: %v", err)
|
||||
|
@ -62,9 +61,14 @@ func TestMain(m *testing.M) {
|
|||
}
|
||||
|
||||
tClient = client
|
||||
tRepos = repo.New(tClient, os.TempDir()+"/homebox")
|
||||
tSvc = NewServices(tRepos)
|
||||
defer client.Close()
|
||||
tRepos = repo.New(tClient, tbus, os.TempDir()+"/homebox")
|
||||
|
||||
defaults, _ := currencies.CollectionCurrencies(
|
||||
currencies.CollectDefaults(),
|
||||
)
|
||||
|
||||
tSvc = New(tRepos, WithCurrencies(defaults))
|
||||
defer func() { _ = client.Close() }()
|
||||
|
||||
bootstrap()
|
||||
tCtx = Context{
|
|
@ -0,0 +1,5 @@
|
|||
HB.location,HB.name,HB.quantity,HB.description,HB.field.Custom Field 1,HB.field.Custom Field 2,HB.field.Custom Field 3
|
||||
loc,Item 1,1,Description 1,Value 1[1],Value 1[2],Value 1[3]
|
||||
loc,Item 2,2,Description 2,Value 2[1],Value 2[2],Value 2[3]
|
||||
loc,Item 3,3,Description 3,Value 3[1],Value 3[2],Value 3[3]
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
HB.location,HB.name,HB.quantity,HB.description
|
||||
loc,Item 1,1,Description 1
|
||||
loc,Item 2,2,Description 2
|
||||
loc,Item 3,3,Description 3
|
|
|
@ -0,0 +1,4 @@
|
|||
HB.name,HB.asset_id,HB.location,HB.labels
|
||||
Item 1,1,Path / To / Location 1,L1 ; L2 ; L3
|
||||
Item 2,000-002,Path /To/ Location 2,L1;L2;L3
|
||||
Item 3,1000-003,Path / To /Location 3 , L1;L2; L3
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue