Compare commits

..

1 Commits

Author SHA1 Message Date
tonya
646a80f494 feat: begin work on language updates 2025-01-04 13:11:23 +00:00
479 changed files with 15967 additions and 29397 deletions

View File

@@ -1,3 +1,5 @@
FROM mcr.microsoft.com/vscode/devcontainers/typescript-node:22-bullseye
# [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
RUN sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b ~/.local/bin

View File

@@ -3,7 +3,13 @@
{
"name": "Node.js & TypeScript",
"build": {
"dockerfile": "Dockerfile"
"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.

View File

@@ -1,106 +1,65 @@
#!/usr/bin/env python3
import json
import logging
import sys
from pathlib import Path
import requests
from requests.adapters import HTTPAdapter, Retry
API_URL = 'https://restcountries.com/v3.1/all?fields=name,common,currencies'
SAVE_PATH = Path('backend/internal/core/currencies/currencies.json')
TIMEOUT = 10 # seconds
def setup_logging():
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s %(levelname)s: %(message)s'
)
import json
import os
def fetch_currencies():
session = requests.Session()
retries = Retry(
total=3,
backoff_factor=1,
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=frozenset(['GET'])
)
session.mount('https://', HTTPAdapter(max_retries=retries))
try:
resp = session.get(API_URL, timeout=TIMEOUT)
resp.raise_for_status()
response = requests.get('https://restcountries.com/v3.1/all')
response.raise_for_status()
except requests.exceptions.Timeout:
print("Request to the API timed out.")
return []
except requests.exceptions.RequestException as e:
logging.error("API request failed: %s", e)
return None # signal fatal
print(f"An error occurred while making the request: {e}")
return []
try:
countries = resp.json()
except ValueError as e:
logging.error("Failed to parse JSON response: %s", e)
return None # signal fatal
countries = response.json()
except json.JSONDecodeError:
print("Failed to decode JSON from the response.")
return []
results = []
currencies_list = []
for country in countries:
country_name = country.get('name', {}).get('common') or "Unknown"
for code, info in country.get('currencies', {}).items():
results.append({
'code': code,
'local': country_name,
'symbol': info.get('symbol', ''),
'name': info.get('name', '')
country_name = country.get('name', {}).get('common')
country_currencies = country.get('currencies', {})
for currency_code, currency_info in country_currencies.items():
symbol = currency_info.get('symbol', '')
currencies_list.append({
'code': currency_code,
'local': country_name,
'symbol': symbol,
'name': currency_info.get('name')
})
# sort by country name for consistency
return sorted(results, key=lambda x: x['local'].lower())
return currencies_list
def load_existing(path: Path):
if not path.exists():
return []
def save_currencies(currencies, file_path):
try:
with path.open('r', encoding='utf-8') as f:
os.makedirs(os.path.dirname(file_path), exist_ok=True)
with open(file_path, 'w', encoding='utf-8') as f:
json.dump(currencies, f, ensure_ascii=False, indent=4)
except IOError as e:
print(f"An error occurred while writing to the file: {e}")
def load_existing_currencies(file_path):
try:
with open(file_path, 'r', encoding='utf-8') as f:
return json.load(f)
except (IOError, ValueError) as e:
logging.warning("Could not load existing file (%s): %s", path, e)
return []
def save_currencies(data, path: Path):
path.parent.mkdir(parents=True, exist_ok=True)
with path.open('w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=4)
logging.info("Wrote %d entries to %s", len(data), path)
except (IOError, json.JSONDecodeError):
return [] # Return an empty list if file doesn't exist or is invalid
def main():
setup_logging()
logging.info("🔄 Starting currency update")
existing = load_existing(SAVE_PATH)
new = fetch_currencies()
# Fatal API / parse error → exit non-zero so the workflow will fail
if new is None:
logging.error("Aborting: failed to fetch or parse API data.")
sys.exit(1)
# Empty array → log & exit zero (no file change)
if not new:
logging.warning("API returned empty list; skipping write.")
sys.exit(0)
# Identical to existing → nothing to do
if new == existing:
logging.info("Up-to-date; skipping write.")
sys.exit(0)
# Otherwise actually overwrite
save_currencies(new, SAVE_PATH)
logging.info("✅ Currency file updated.")
save_path = 'backend/internal/core/currencies/currencies.json'
existing_currencies = load_existing_currencies(save_path)
new_currencies = fetch_currencies()
if new_currencies == existing_currencies:
print("Currencies up-to-date with API, skipping commit.")
else:
save_currencies(new_currencies, save_path)
print("Currencies updated and saved.")
if __name__ == "__main__":
main()
main()

View File

@@ -5,6 +5,14 @@ on:
tags: [ 'v*.*.*' ]
jobs:
# backend-tests:
# name: "Backend Server Tests"
# uses: sysadminsmedia/homebox/.github/workflows/partial-backend.yaml@main
# frontend-tests:
# name: "Frontend and End-to-End Tests"
# uses: sysadminsmedia/homebox/.github/workflows/partial-frontend.yaml@main
goreleaser:
name: goreleaser
runs-on: ubuntu-latest
@@ -19,20 +27,15 @@ jobs:
- uses: pnpm/action-setup@v2
with:
version: 9.15.3
version: 7.30.1
- name: Build Frontend and Copy to Backend
working-directory: frontend
run: |
pnpm install
pnpm install --shamefully-hoist
pnpm run build
cp -r ./.output/public ../backend/app/api/static/
- name: Install CoSign
working-directory: backend
run: |
go install github.com/sigstore/cosign/cmd/cosign@latest
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v5
with:

View File

@@ -1,52 +0,0 @@
name: Docker Cleanup
on:
schedule:
- cron: '00 0 * * *'
workflow_dispatch:
jobs:
delete-old-images-main:
name: Delete Untagged Images
runs-on: ubuntu-latest
permissions:
packages: write
steps:
- name: Fetch multi-platform package version SHAs
id: multi-arch-digests
run: |
package1=$(docker manifest inspect ghcr.io/sysadminsmedia/homebox | jq -r '.manifests.[] | .digest' | paste -s -d ' ' -)
echo "multi-arch-digests=$package1" >> $GITHUB_OUTPUT
- uses: snok/container-retention-policy@v3.0.0
with:
skip-shas: ${{ steps.multi-arch-digests.outputs.multi-arch-digests }}
# The type of account. Can be either 'org' or 'personal'.
account: sysadminsmedia
# Image name to delete. Supports passing several names as a comma-separated list.
image-names: homebox
# The cut-off for which to delete images older than. For example '2 days ago UTC'. Timezone is required.
cut-off: 90d
# Personal access token with read and delete scopes.
token: ${{ secrets.CLEANUP_PAT }}
# Restrict deletions to images without specific tags. Supports Unix-shell style wildcards
skip-tags: "!latest,!latest-rootless,!0.*,!0.*-rootless,!main,!main-rootless,!vnext,!vnext-rootless,!0,!0-rootless" # optional
# Do not actually delete images. Print output showing what would have been deleted.
dry-run: true # optional, default is false
delete-old-images-devcache:
name: Delete Cache Old Images
runs-on: ubuntu-latest
permissions:
packages: write
steps:
- uses: snok/container-retention-policy@v3.0.0
with:
# The type of account. Can be either 'org' or 'personal'.
account: sysadminsmedia
image-names: devcache
# The cut-off for which to delete images older than. For example '2 days ago UTC'. Timezone is required.
cut-off: 90d
# Personal access token with read and delete scopes.
token: ${{ secrets.CLEANUP_PAT }}
# Do not actually delete images. Print output showing what would have been deleted.
dry-run: true # optional, default is false

View File

@@ -0,0 +1,105 @@
name: Docker publish ARM
on:
schedule:
- cron: '00 0 * * *'
push:
branches: [ "main" ]
paths:
- 'backend/**'
- 'frontend/**'
- 'Dockerfile'
- 'Dockerfile.rootless'
- '.dockerignore'
- '.github/workflows'
# Publish semver tags as releases.
tags: [ 'v*.*.*' ]
pull_request:
branches: [ "main" ]
paths:
- 'backend/**'
- 'frontend/**'
- 'Dockerfile'
- 'Dockerfile.rootless'
- '.dockerignore'
- '.github/workflows'
env:
# Use docker.io for Docker Hub if empty
REGISTRY: ghcr.io
# github.repository as <account>/<repo>
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
attestations: write
id-token: write
steps:
# Step 1: Checkout repository
- name: Checkout repository
uses: actions/checkout@v4
# Step 2: Set up Buildx without specifying driver
# Let it use default settings to avoid the 'no remote endpoint' issue
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.0.0
with:
install: true # Ensure Buildx is installed and set up properly
use: true # Use Buildx instance directly for this job
# Step 3: Login against Docker registry except on PR
- name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request'
uses: docker/login-action@v3.0.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Step 4: Extract metadata for Docker images
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5.0.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=schedule,pattern=nightly
flavor: |
suffix=-arm,onlatest=true
# Step 5: Build and push the Docker image
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@v5.0.0
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/arm64,linux/arm/v7
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
VERSION=${{ github.ref_name }}
COMMIT=${{ github.sha }}
# Step 6: Attest built image to prove build provenance
- name: Attest
uses: actions/attest-build-provenance@v1
id: attest
if: ${{ github.event_name != 'pull_request' }}
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
subject-digest: ${{ steps.build-and-push.outputs.digest }}
push-to-registry: true

View File

@@ -0,0 +1,104 @@
name: Docker publish rootless ARM
on:
schedule:
- cron: '00 0 * * *'
push:
branches: [ "main" ]
paths:
- 'backend/**'
- 'frontend/**'
- 'Dockerfile'
- 'Dockerfile.rootless'
- '.dockerignore'
- '.github/workflows'
# Publish semver tags as releases.
tags: [ 'v*.*.*' ]
pull_request:
branches: [ "main" ]
paths:
- 'backend/**'
- 'frontend/**'
- 'Dockerfile'
- 'Dockerfile.rootless'
- '.dockerignore'
- '.github/workflows'
env:
# Use docker.io for Docker Hub if empty
REGISTRY: ghcr.io
# github.repository as <account>/<repo>
IMAGE_NAME: ${{ github.repository }}
jobs:
build-rootless:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
attestations: write
id-token: write
steps:
# Step 1: Checkout repository
- name: Checkout repository
uses: actions/checkout@v4
# Step 2: Set up Buildx without specifying driver
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.0.0
with:
install: true # Ensure Buildx is installed and set up properly
use: true # Use Buildx instance directly for this job
# Step 3: Login to Docker registry except on PR
- name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request'
uses: docker/login-action@v3.0.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Step 4: Extract metadata for Docker images
- name: Extract Docker metadata
id: metadata
uses: docker/metadata-action@v5.0.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=schedule,pattern=nightly
flavor: |
suffix=-rootless-arm,onlatest=true
# Step 5: Build and push the Docker image
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@v5.0.0
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }}
platforms: linux/arm64,linux/arm/v7
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
VERSION=${{ github.ref_name }}
COMMIT=${{ github.sha }}
# Step 6: Attest built image to prove build provenance
- name: Attest
uses: actions/attest-build-provenance@v1
id: attest
if: ${{ github.event_name != 'pull_request' }}
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
subject-digest: ${{ steps.build-and-push.outputs.digest }}
push-to-registry: true

View File

@@ -9,10 +9,10 @@ on:
- 'backend/**'
- 'frontend/**'
- 'Dockerfile'
- 'Dockerfile.rootless'
- '.dockerignore'
- '.github/workflows/docker-publish-rootless.yaml'
ignore:
- 'docs/**'
- '.github/workflows'
# Publish semver tags as releases.
tags: [ 'v*.*.*' ]
pull_request:
branches: [ "main" ]
@@ -20,170 +20,57 @@ on:
- 'backend/**'
- 'frontend/**'
- 'Dockerfile'
- 'Dockerfile.rootless'
- '.dockerignore'
- '.github/workflows/docker-publish-rootless.yaml'
ignore:
- 'docs/**'
- '.github/workflows'
permissions:
contents: read # Access to repository contents
packages: write # Write access for pushing to GHCR
id-token: write # Required for OIDC authentication (if used)
attestations: write # Required for signing and attestation (if needed)
env:
DOCKERHUB_REPO: sysadminsmedia/homebox
GHCR_REPO: ghcr.io/sysadminsmedia/homebox
# Use docker.io for Docker Hub if empty
REGISTRY: ghcr.io
# github.repository as <account>/<repo>
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
build-rootless:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
# This is used to complete the identity challenge
# with sigstore/fulcio when running outside of PRs.
attestations: write
strategy:
fail-fast: false
matrix:
platform:
- linux/amd64
- linux/arm64
- linux/arm/v7
id-token: write
steps:
- name: Enable Debug Logs
run: echo "##[debug]Enabling debug logging"
env:
ACTIONS_RUNNER_DEBUG: true
ACTIONS_STEP_DEBUG: true
- name: Checkout repository
uses: actions/checkout@v4
- name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
branch=${{ github.event.pull_request.number || github.ref_name }}
echo "BRANCH=${branch//\//-}" >> $GITHUB_ENV
echo "DOCKERNAMES=${{ env.DOCKERHUB_REPO }},${{ env.GHCR_REPO }}" >> $GITHUB_ENV
if [[ "${{ github.event_name }}" != "schedule" ]] || [[ "${{ github.ref }}" != refs/tags/* ]]; then
echo "DOCKERNAMES=${{ env.GHCR_REPO }}" >> $GITHUB_ENV
fi
# Set up BuildKit Docker container builder to be able to build
# multi-platform images and export cache
# https://github.com/docker/setup-buildx-action
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.0.0 # v3.0.0
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
# Login against a Docker registry except on PR
# https://github.com/docker/login-action
- name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request'
uses: docker/login-action@v3.0.0 # v3.0.0
with:
images: |
name=${{ env.DOCKERHUB_REPO }},enable=${{ github.event_name == 'schedule' || startsWith(github.ref, 'refs/tags/') }}
name=${{ env.GHCR_REPO }}
- name: Login to Docker Hub
uses: docker/login-action@v3
if: github.event_name == 'schedule' || startsWith(github.ref, 'refs/tags/')
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: metadata
uses: docker/metadata-action@v5.0.0 # v5.0.0
with:
image: ghcr.io/amitie10g/binfmt:latest
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver-opts: |
image=ghcr.io/amitie10g/buildkit:master
- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
with:
context: . # Explicitly specify the build context
file: ./Dockerfile.rootless # Explicitly specify the Dockerfile
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,"name=${{ env.DOCKERNAMES }}",push-by-digest=true,name-canonical=true,push=${{ github.event_name != 'pull_request' }}
cache-from: type=registry,ref=ghcr.io/sysadminsmedia/devcache:${{ env.PLATFORM_PAIR }}-${{ env.BRANCH }}-rootless
cache-to: type=registry,ref=ghcr.io/sysadminsmedia/devcache:${{ env.PLATFORM_PAIR }}-${{ env.BRANCH }}-rootless,mode=max,ignore-error=true
build-args: |
VERSION=${{ github.ref_name }}
COMMIT=${{ github.sha }}
provenance: true
sbom: true
annotations: ${{ steps.meta.outputs.annotations }}
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
merge:
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
attestations: write
needs:
- build
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true
- name: Login to Docker Hub
uses: docker/login-action@v3
if: github.event_name == 'schedule' || startsWith(github.ref, 'refs/tags/')
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver-opts: |
image=ghcr.io/amitie10g/buildkit:master
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: |
name=${{ env.DOCKERHUB_REPO }},enable=${{ github.event_name == 'schedule' || startsWith(github.ref, 'refs/tags/') }}
name=${{ env.GHCR_REPO }}
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
@@ -193,18 +80,29 @@ jobs:
type=schedule,pattern=nightly
flavor: |
suffix=-rootless,onlatest=true
# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@v5.0.0 # v5.0.0
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }}
platforms: linux/amd64
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
VERSION=${{ github.ref_name }}
COMMIT=${{ github.sha }}
- name: Create manifest list and push GHCR
id: push-ghcr
working-directory: /tmp/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.GHCR_REPO }}@sha256:%s ' *)
- name: Create manifest list and push Dockerhub
id: push-dockerhub
working-directory: /tmp/digests
if: github.event_name == 'schedule' || startsWith(github.ref, 'refs/tags/')
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.DOCKERHUB_REPO }}@sha256:%s ' *)
- name: Attest
uses: actions/attest-build-provenance@v1
id: attest
if: ${{ github.event_name != 'pull_request' }}
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
subject-digest: ${{ steps.build-and-push.outputs.digest }}
push-to-registry: true

View File

@@ -9,10 +9,10 @@ on:
- 'backend/**'
- 'frontend/**'
- 'Dockerfile'
- 'Dockerfile.rootless'
- '.dockerignore'
- '.github/workflows/docker-publish.yaml'
ignore:
- 'docs/**'
- '.github/workflows'
# Publish semver tags as releases.
tags: [ 'v*.*.*' ]
pull_request:
branches: [ "main" ]
@@ -20,162 +20,56 @@ on:
- 'backend/**'
- 'frontend/**'
- 'Dockerfile'
- 'Dockerfile.rootless'
- '.dockerignore'
- '.github/workflows/docker-publish.yaml'
ignore:
- 'docs/**'
- '.github/workflows'
env:
DOCKERHUB_REPO: sysadminsmedia/homebox
GHCR_REPO: ghcr.io/sysadminsmedia/homebox
# Use docker.io for Docker Hub if empty
REGISTRY: ghcr.io
# github.repository as <account>/<repo>
IMAGE_NAME: ${{ github.repository }}
permissions:
contents: read # Access to repository contents
packages: write # Write access for pushing to GHCR
id-token: write # Required for OIDC authentication (if used)
attestations: write # Required for signing and attestation (if needed)
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read # Allows access to repository contents (read-only)
packages: write # Allows pushing to GHCR
id-token: write # Allows identity token write access for authentication
attestations: write # Needed for signing and attestation (if required)
strategy:
fail-fast: false
matrix:
platform:
- linux/amd64
- linux/arm64
- linux/arm/v7
contents: read
packages: write
# This is used to complete the identity challenge
# with sigstore/fulcio when running outside of PRs.
attestations: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
branch=${{ github.event.pull_request.number || github.ref_name }}
echo "BRANCH=${branch//\//-}" >> $GITHUB_ENV
echo "DOCKERNAMES=${{ env.DOCKERHUB_REPO }},${{ env.GHCR_REPO }}" >> $GITHUB_ENV
if [[ "${{ github.event_name }}" != "schedule" ]] || [[ "${{ github.ref }}" != refs/tags/* ]]; then
echo "DOCKERNAMES=${{ env.GHCR_REPO }}" >> $GITHUB_ENV
fi
# Set up BuildKit Docker container builder to be able to build
# multi-platform images and export cache
# https://github.com/docker/setup-buildx-action
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.0.0 # v3.0.0
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
# Login against a Docker registry except on PR
# https://github.com/docker/login-action
- name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request'
uses: docker/login-action@v3.0.0 # v3.0.0
with:
images: |
name=${{ env.DOCKERHUB_REPO }},enable=${{ github.event_name == 'schedule' || startsWith(github.ref, 'refs/tags/') }}
name=${{ env.GHCR_REPO }}
- name: Login to Docker Hub
uses: docker/login-action@v3
if: github.event_name == 'schedule' || startsWith(github.ref, 'refs/tags/')
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
with:
image: ghcr.io/amitie10g/binfmt:latest
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver-opts: |
image=ghcr.io/amitie10g/buildkit:master
- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
with:
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,"name=${{ env.DOCKERNAMES }}",push-by-digest=true,name-canonical=true,push=${{ github.event_name != 'pull_request' }}
cache-from: type=registry,ref=ghcr.io/sysadminsmedia/devcache:${{ env.PLATFORM_PAIR }}-${{ env.BRANCH }}
cache-to: type=registry,ref=ghcr.io/sysadminsmedia/devcache:${{ env.PLATFORM_PAIR}}-${{ env.BRANCH }},mode=max,ignore-error=true
build-args: |
VERSION=${{ github.ref_name }}
COMMIT=${{ github.sha }}
provenance: true
sbom: true
annotations: ${{ steps.meta.outputs.annotations }}
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
merge:
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
permissions:
contents: read # Allows access to repository contents (read-only)
packages: write # Allows pushing to GHCR
id-token: write # Allows identity token write access for authentication
attestations: write # Needed for signing and attestation (if required)
needs:
- build
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver-opts: |
image=ghcr.io/amitie10g/buildkit:master
- name: Docker meta
# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@v5.0.0 # v5.0.0
with:
images: |
name=${{ env.DOCKERHUB_REPO }},enable=${{ github.event_name == 'schedule' || startsWith(github.ref, 'refs/tags/') }}
name=${{ env.GHCR_REPO }}
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
@@ -184,18 +78,28 @@ jobs:
type=semver,pattern={{major}}
type=schedule,pattern=nightly
- name: Create manifest list and push GHCR
id: push-ghcr
working-directory: /tmp/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.GHCR_REPO }}@sha256:%s ' *)
- name: Create manifest list and push Dockerhub
id: push-dockerhub
working-directory: /tmp/digests
if: github.event_name == 'schedule' || startsWith(github.ref, 'refs/tags/')
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.DOCKERHUB_REPO }}@sha256:%s ' *)
# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@v5.0.0 # v5.0.0
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
VERSION=${{ github.ref_name }}
COMMIT=${{ github.sha }}
- name: Attest
uses: actions/attest-build-provenance@v1
id: attest
if: ${{ github.event_name != 'pull_request' }}
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
subject-digest: ${{ steps.build-and-push.outputs.digest }}
push-to-registry: true

View File

@@ -1,94 +0,0 @@
name: E2E (Playwright)
on:
workflow_call:
jobs:
playwright-tests:
timeout-minutes: 60
strategy:
matrix:
shardIndex: [1,2,3,4]
shardTotal: [4]
name: E2E Playwright Testing ${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
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@v5
with:
go-version: "1.21"
- uses: actions/setup-node@v4
with:
node-version: lts/*
- uses: pnpm/action-setup@v3.0.0
with:
version: 9.12.2
- name: Install dependencies
run: pnpm install
working-directory: frontend
- name: Install Go Dependencies
run: go mod download
working-directory: backend
- name: Run E2E Tests
run: task test:e2e -- --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
- uses: actions/upload-artifact@v4
name: Upload partial Playwright report
if: ${{ !cancelled() }}
with:
name: blob-report-${{ matrix.shardIndex }}
path: frontend/blob-report/
retention-days: 2
merge-reports:
# Merge reports after playwright-tests, even if some shards have failed
if: ${{ !cancelled() }}
needs: [playwright-tests]
name: Merge Playwright Reports
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
- uses: pnpm/action-setup@v3.0.0
with:
version: 9.12.2
- name: Install dependencies
run: pnpm install
working-directory: frontend
- name: Download blob reports from GitHub Actions Artifacts
uses: actions/download-artifact@v4
with:
path: frontend/all-blob-reports
pattern: blob-report-*
merge-multiple: true
- name: Merge into HTML Report
run: pnpm exec playwright merge-reports --reporter html,github ./all-blob-reports
working-directory: frontend
- name: Upload HTML report
uses: actions/upload-artifact@v4
with:
name: html-report--attempt-${{ github.run_attempt }}
path: frontend/playwright-report
retention-days: 30

View File

@@ -20,7 +20,7 @@ jobs:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: golangci-lint
uses: golangci/golangci-lint-action@v7
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
@@ -34,8 +34,3 @@ jobs:
- name: Test
run: task go:coverage
- name: Validate OpenAPI definition
uses: swaggerexpert/swagger-editor-validate@v1
with:
definition-file: backend/app/api/static/docs/swagger.json

View File

@@ -1,4 +1,4 @@
name: Frontend
name: Frontend / E2E
on:
workflow_call:
@@ -18,7 +18,7 @@ jobs:
version: 9.12.2
- name: Install dependencies
run: pnpm install
run: pnpm install --shamefully-hoist
working-directory: frontend
- name: Run Lint
@@ -32,20 +32,6 @@ jobs:
integration-tests:
name: Integration Tests
runs-on: ubuntu-latest
services:
postgres:
image: postgres:17
env:
POSTGRES_USER: homebox
POSTGRES_PASSWORD: homebox
POSTGRES_DB: homebox
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -76,53 +62,3 @@ jobs:
- name: Run Integration Tests
run: task test:ci
integration-tests-pgsql:
strategy:
matrix:
database_version: [17,16,15]
name: Integration Tests PGSQL ${{ matrix.database_version }}
runs-on: ubuntu-latest
services:
postgres:
image: postgres:${{ matrix.database_version }}
env:
POSTGRES_USER: homebox
POSTGRES_PASSWORD: homebox
POSTGRES_DB: homebox
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
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@v5
with:
go-version: "1.21"
- uses: actions/setup-node@v4
with:
node-version: lts/*
- uses: pnpm/action-setup@v3.0.0
with:
version: 9.12.2
- name: Install dependencies
run: pnpm install
working-directory: frontend
- name: Run Integration Tests
run: task test:ci:postgresql

View File

@@ -4,12 +4,10 @@ on:
pull_request:
branches:
- main
- vnext
paths:
- 'backend/**'
- 'frontend/**'
- '.github/workflows/**'
jobs:
backend-tests:
@@ -17,9 +15,5 @@ jobs:
uses: ./.github/workflows/partial-backend.yaml
frontend-tests:
name: "Frontend Tests"
uses: ./.github/workflows/partial-frontend.yaml
e2e-tests:
name: "End-to-End Playwright Tests"
uses: ./.github/workflows/e2e-partial.yaml
name: "Frontend and End-to-End Tests"
uses: ./.github/workflows/partial-frontend.yaml

View File

@@ -2,8 +2,8 @@ name: Update Currencies
on:
push:
branches: [ main ]
workflow_dispatch:
branches:
- main
jobs:
update-currencies:
@@ -11,45 +11,90 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
fetch-depth: 0
uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v2
with:
python-version: '3.8'
cache: 'pip'
cache-dependency-path: .github/workflows/update-currencies/requirements.txt
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install requests
- name: Run currency update script
- name: Run currency fetch script
run: python .github/scripts/update_currencies.py
- name: Check for file changes
id: changes
- name: Check for changes
id: check_changes
run: |
if git diff --quiet; then
echo "changed=false" >> $GITHUB_ENV
if [[ $(git status --porcelain) ]]; then
echo "Changes detected."
echo "changes=true" >> $GITHUB_ENV
else
echo "changed=true" >> $GITHUB_ENV
echo "No changes detected."
echo "changes=false" >> $GITHUB_ENV
fi
- name: Create Pull Request
if: env.changed == 'true'
uses: peter-evans/create-pull-request@v7.0.8
with:
token: ${{ secrets.GITHUB_TOKEN }}
branch: update-currencies
base: main
title: "Update currencies.json"
commit-message: "chore: update currencies.json"
path: backend/internal/core/currencies/currencies.json
- name: Delete existing update-currencies branch
run: |
if git show-ref --verify --quiet refs/heads/update-currencies; then
git branch -D update-currencies
echo "Deleted existing update-currencies branch."
else
echo "No existing update-currencies branch to delete."
fi
- name: No updates needed
if: env.changed == 'false'
run: echo "✅ currencies.json is already up-to-date"
- name: Create new update-currencies branch
if: env.changes == 'true'
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
# Create a new branch
git checkout -b update-currencies
git add backend/internal/core/currencies/currencies.json
git commit -m "Update currencies.json"
# Fetch the latest changes from the remote
git fetch origin
# Attempt to rebase with the latest changes
if git show-ref --verify --quiet refs/remotes/origin/update-currencies; then
if ! git rebase origin/update-currencies; then
echo "Rebase conflicts occurred. Please resolve them manually."
echo "To resolve conflicts, check out the 'update-currencies' branch locally."
exit 1
fi
else
echo "No existing remote branch 'update-currencies'. Skipping rebase."
fi
# Push the new branch to the remote
if ! git push --set-upstream origin update-currencies; then
echo "Push failed, trying to fetch and rebase again."
git fetch origin
if git show-ref --verify --quiet refs/remotes/origin/update-currencies; then
if ! git rebase origin/update-currencies; then
echo "Second rebase failed. Please resolve manually."
exit 1
fi
else
echo "No existing remote branch 'update-currencies'. Skipping rebase."
fi
if ! git push --set-upstream origin update-currencies; then
echo "Second push failed. Please resolve manually."
exit 1
fi
fi
# Create a pull request
curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-X POST \
-d '{"title": "Update currencies", "head": "update-currencies", "base": "main"}' \
https://api.github.com/repos/${{ github.repository }}/pulls
- name: Notify no changes
if: env.changes == 'false'
run: echo "Currencies up-to-date with API, skipping commit."

View File

@@ -1 +0,0 @@
requests

14
.gitignore vendored
View File

@@ -2,9 +2,6 @@
backend/.data/*
config.yml
homebox.db
homebox.db-journal
homebox.db-shm
homebox.db-wal
.idea
.DS_Store
test-mailer.json
@@ -36,7 +33,7 @@ go.work
backend/.env
build/*
# Output Directory for Nuxt/Frontend during build steps
# Output Directory for Nuxt/Frontend during build step
backend/app/api/public/*
!backend/app/api/public/.gitkeep
@@ -59,11 +56,4 @@ backend/app/api/static/public/*
!backend/app/api/static/public/.gitkeep
backend/api
docs/.vitepress/cache/
/.data/
# Playwright
frontend/test-results/
frontend/playwright-report/
frontend/blob-report/
frontend/playwright/.cache/
docs/.vitepress/cache/

2
.vscode/launch.json vendored
View File

@@ -23,7 +23,7 @@
"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&_time_format=sqlite"
"HBOX_STORAGE_SQLITE_URL": "${workspaceRoot}/backend/.data/homebox.db?_fk=1"
},
"console": "integratedTerminal",
},

View File

@@ -30,8 +30,5 @@
"editor.quickSuggestions": {
"strings": true
},
"tailwindCSS.experimental.configFile": "./frontend/tailwind.config.js",
"[go]": {
"editor.defaultFormatter": "golang.go"
},
"tailwindCSS.experimental.configFile": "./frontend/tailwind.config.js"
}

View File

@@ -60,7 +60,7 @@ representative at an online or offline event.
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
[support@sysadminsmedia.com](mailto:support@sysadminsmedia.com).
[support@sysadminemedia.com](mailto:support@sysadminemedia.com).
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the

View File

@@ -33,6 +33,8 @@ If you're using `taskfile` you can run `task --list-all` for a list of all comma
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`
@@ -42,9 +44,9 @@ start command `task go:run`
### Frontend Development Notes
start command `task ui:dev`
start command `task: ui:dev`
1. The frontend is a Vue 3 app with Nuxt.js that uses Tailwind and Shadcn-vue for styling.
1. The frontend is a Vue 3 app with Nuxt.js that uses Tailwind and DaisyUI for styling.
2. We're using Vitest for our automated testing. You can run these with `task ui:watch`.
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.
@@ -52,4 +54,4 @@ start command `task ui:dev`
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
Test -> Goreleaser -> Publish Release -> Trigger Docker Builds -> Deploy Docs + Fly.io Demo

View File

@@ -1,5 +1,5 @@
# Node dependencies stage
FROM public.ecr.aws/docker/library/node:lts-alpine AS frontend-dependencies
FROM --platform=$TARGETPLATFORM node:18-alpine AS frontend-dependencies
WORKDIR /app
# Install pnpm globally (caching layer)
@@ -7,10 +7,10 @@ RUN npm install -g pnpm
# Copy package.json and lockfile to leverage caching
COPY frontend/package.json frontend/pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
RUN pnpm install --frozen-lockfile --shamefully-hoist
# Build Nuxt (frontend) stage
FROM public.ecr.aws/docker/library/node:lts-alpine AS frontend-builder
FROM --platform=$TARGETPLATFORM node:18-alpine AS frontend-builder
WORKDIR /app
# Install pnpm globally again (it can reuse the cache if not changed)
@@ -22,7 +22,7 @@ COPY --from=frontend-dependencies /app/node_modules ./node_modules
RUN pnpm build
# Go dependencies stage
FROM public.ecr.aws/docker/library/golang:alpine AS builder-dependencies
FROM --platform=$TARGETPLATFORM golang:alpine AS builder-dependencies
WORKDIR /go/src/app
# Copy go.mod and go.sum for better caching
@@ -30,7 +30,7 @@ COPY ./backend/go.mod ./backend/go.sum ./
RUN go mod download
# Build API stage
FROM public.ecr.aws/docker/library/golang:alpine AS builder
FROM --platform=$TARGETPLATFORM golang:alpine AS builder
ARG BUILD_TIME
ARG COMMIT
ARG VERSION
@@ -58,10 +58,10 @@ RUN --mount=type=cache,target=/root/.cache/go-build \
-v ./app/api/*.go
# Production stage
FROM public.ecr.aws/docker/library/alpine:latest
FROM --platform=$TARGETPLATFORM alpine:latest
ENV HBOX_MODE=production
ENV HBOX_STORAGE_DATA=/data/
ENV HBOX_DATABASE_SQLITE_PATH=/data/homebox.db?_pragma=busy_timeout=2000&_pragma=journal_mode=WAL&_fk=1&_time_format=sqlite
ENV HBOX_STORAGE_SQLITE_URL=/data/homebox.db?_pragma=busy_timeout=2000&_pragma=journal_mode=WAL&_fk=1
# Install necessary runtime dependencies
RUN apk --no-cache add ca-certificates wget
@@ -75,7 +75,7 @@ RUN chmod +x /app/api
LABEL Name=homebox Version=0.0.1
LABEL org.opencontainers.image.source="https://github.com/sysadminsmedia/homebox"
# Expose necessary ports for Homebox
# Expose necessary ports
EXPOSE 7745
WORKDIR /app

View File

@@ -1,97 +1,73 @@
# Node dependencies stage
FROM public.ecr.aws/docker/library/node:lts-alpine AS frontend-dependencies
# Node dependencies
FROM node:18-alpine AS frontend-dependencies
WORKDIR /app
# Install pnpm globally (caching layer)
RUN npm install -g pnpm
# Copy package.json and lockfile to leverage caching
COPY frontend/package.json frontend/pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
RUN pnpm install --frozen-lockfile --shamefully-hoist
# Build Nuxt (frontend) stage
FROM public.ecr.aws/docker/library/node:lts-alpine AS frontend-builder
# Build Nuxt
FROM node:18-alpine AS frontend-builder
WORKDIR /app
# Install pnpm globally again (it can reuse the cache if not changed)
RUN npm install -g pnpm
# Copy over source files and node_modules from dependencies stage
COPY frontend .
COPY frontend ./
COPY --from=frontend-dependencies /app/node_modules ./node_modules
RUN pnpm build
# Go dependencies stage
FROM public.ecr.aws/docker/library/golang:alpine AS builder-dependencies
# Build Go dependencies
FROM golang:alpine AS builder-dependencies
WORKDIR /go/src/app
# Copy go.mod and go.sum for better caching
COPY ./backend/go.mod ./backend/go.sum ./
RUN go mod download
# Build API stage
FROM public.ecr.aws/docker/library/golang:alpine AS builder
# Build API
FROM golang:alpine AS builder
ARG BUILD_TIME
ARG COMMIT
ARG VERSION
# Install necessary build tools
RUN apk update && \
apk upgrade && \
apk add --no-cache git build-base gcc g++
RUN apk update && apk upgrade && apk add --no-cache git build-base gcc g++
WORKDIR /go/src/app
# Copy Go modules (from dependencies stage) and source code
COPY --from=builder-dependencies /go/pkg/mod /go/pkg/mod
COPY ./backend .
# Clear old public files and copy new ones from frontend build
RUN rm -rf ./app/api/public
COPY --from=frontend-builder /app/.output/public ./app/api/static/public
COPY --from=builder-dependencies /go/pkg/mod /go/pkg/mod
# Use cache for Go build artifacts
# Use cache for Go build
RUN --mount=type=cache,target=/root/.cache/go-build \
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
-o /go/bin/api ./app/api/*.go
RUN mkdir /data
# Production stage with distroless
FROM gcr.io/distroless/static:latest
# Production stage
FROM public.ecr.aws/docker/library/alpine:latest
ENV HBOX_MODE=production
ENV HBOX_STORAGE_DATA=/data/
ENV HBOX_DATABASE_SQLITE_PATH=/data/homebox.db?_pragma=busy_timeout=2000&_pragma=journal_mode=WAL&_fk=1&_time_format=sqlite
ENV HBOX_STORAGE_SQLITE_URL=/data/homebox.db?_fk=1
# Install necessary runtime dependencies
RUN apk --no-cache add ca-certificates wget
# Create a nonroot user with UID/GID 65532
RUN addgroup -g 65532 nonroot && adduser -u 65532 -G nonroot -S nonroot
# Create application directory and copy over built Go binary
RUN mkdir /app
# Copy the binary and data directory, change ownership
COPY --from=builder --chown=nonroot /go/bin/api /app
COPY --from=builder --chown=nonroot /data /data
RUN chmod +x /app/api
# Labels and configuration for the final image
# Add wget to the image
# Note: If using distroless, this may not be applicable
# as distroless images do not include package managers.
# This line may be omitted if you're relying on another way to handle healthchecks.
COPY --from=alpine:latest /bin/wget /usr/bin/wget
LABEL Name=homebox Version=0.0.1
LABEL org.opencontainers.image.source="https://github.com/sysadminsmedia/homebox"
# Expose necessary ports for Homebox
EXPOSE 7745
WORKDIR /app
# Healthcheck configuration
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
CMD [ "wget", "--no-verbose", "--tries=1", "-O", "-", "http://localhost:7745/api/v1/status" ]
HEALTHCHECK --interval=30s \
--timeout=5s \
--start-period=5s \
--retries=3 \
CMD ["/usr/bin/wget", "--no-verbose", "--tries=1", "-O", "-", "http://localhost:7745/api/v1/status"]
# Persist volume for data
VOLUME [ "/data" ]
VOLUME ["/data"]
# Entrypoint and CMD
# Drop root and run as a low-privileged user
USER nonroot
ENTRYPOINT [ "/app/api" ]
CMD [ "/data/config.yml" ]
ENTRYPOINT ["/app"]
CMD ["/data/config.yml"]

View File

@@ -13,7 +13,7 @@
## What is HomeBox
HomeBox is the inventory and organization system built for the Home User! With a focus on simplicity and ease of use, Homebox is the perfect solution for your home inventory, organization, and management needs. While developing this project, I've tried to keep the following principles in mind:
Homebox is the inventory and organization system built for the Home User! With a focus on simplicity and ease of use, Homebox is the perfect solution for your home inventory, organization, and management needs. While developing this project, I've tried to keep the following principles in mind:
- _Simple_ - Homebox is designed to be simple and easy to use. No complicated setup or configuration required. Use either a single docker container, or deploy yourself by compiling the binary for your platform of choice.
- _Blazingly Fast_ - Homebox is written in Go, which makes it extremely fast and requires minimal resources to deploy. In general, idle memory usage is less than 50MB for the whole container.
@@ -21,10 +21,6 @@ HomeBox is the inventory and organization system built for the Home User! With a
# Screenshots
Check out screenshots of the project [here](https://imgur.com/a/5gLWt2j).
You can also try the demo instances of Homebox:
- [Demo](https://demo.homebox.software)
- [Nightly](https://nightly.homebox.software)
- [VNext](https://vnext.homebox.software/)
## Quick Start

View File

@@ -2,8 +2,7 @@ version: "3"
env:
HBOX_LOG_LEVEL: debug
HBOX_DATABASE_DRIVER: sqlite3
HBOX_DATABASE_SQLITE_PATH: .data/homebox.db?_pragma=busy_timeout=1000&_pragma=journal_mode=WAL&_fk=1&_time_format=sqlite
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:
@@ -11,9 +10,8 @@ tasks:
desc: Install development dependencies
cmds:
- go install github.com/swaggo/swag/cmd/swag@latest
- go install github.com/pressly/goose/v3/cmd/goose@v3.8.0
- cd backend && go mod tidy
- cd frontend && pnpm install
- cd frontend && pnpm install --shamefully-hoist
swag:
desc: Generate swagger docs
@@ -30,11 +28,12 @@ tasks:
- "./backend/internal/data/**"
- "./backend/internal/core/services/**/*"
- "./backend/app/tools/typegen/main.go"
typescript-types:
desc: Generates typescript types from swagger definition
cmds:
- |
pnpm dlx swagger-typescript-api generate \
npx swagger-typescript-api \
--no-client \
--modular \
--path ./backend/app/api/static/docs/swagger.json \
@@ -50,12 +49,9 @@ tasks:
cmds:
- task: swag
- task: typescript-types
- cp ./backend/app/api/static/docs/swagger.json docs/en/api/openapi-2.0.json
- cp ./backend/app/api/static/docs/swagger.yaml docs/en/api/openapi-2.0.yaml
- cp ./backend/app/api/static/docs/swagger.json docs/docs/api/openapi-2.0.json
go:run:
env:
HBOX_DEMO: true
desc: Starts the backend api server (depends on generate task)
dir: backend
deps:
@@ -64,33 +60,6 @@ tasks:
- go run ./app/api/ {{ .CLI_ARGS }}
silent: false
go:run:postgresql:
env:
HBOX_DEMO: true
HBOX_DATABASE_DRIVER: postgres
HBOX_DATABASE_USERNAME: homebox
HBOX_DATABASE_PASSWORD: homebox
HBOX_DATABASE_DATABASE: homebox
HBOX_DATABASE_HOST: localhost
HBOX_DATABASE_PORT: 5432
HBOX_DATABASE_SSL_MODE: disable
desc: Starts the backend api server with postgresql (depends on generate task)
dir: backend
deps:
- generate
cmds:
- go run ./app/api/ {{ .CLI_ARGS }}
silent: false
go:ci:
env:
HBOX_DEMO: true
desc: Runs all go test and lint related tasks
dir: backend
cmds:
- go run ./app/api/ {{ .CLI_ARGS }} &
silent: true
go:test:
desc: Runs all go tests using gotestsum - supports passing gotestsum args
dir: backend
@@ -138,6 +107,13 @@ tasks:
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
@@ -150,13 +126,6 @@ tasks:
cmds:
- pnpm dev
ui:ci:
desc: Run frontend build in CI mode
dir: frontend
cmds:
- pnpm dev &
silent: true
ui:fix:
desc: Runs prettier and eslint on the frontend
dir: frontend
@@ -174,38 +143,10 @@ tasks:
cmds:
- cd backend && go build ./app/api
- backend/api &
- sleep 10
- sleep 5
- cd frontend && pnpm run test:ci
silent: true
test:ci:postgresql:
env:
HBOX_DATABASE_DRIVER: postgres
HBOX_DATABASE_USERNAME: homebox
HBOX_DATABASE_PASSWORD: homebox
HBOX_DATABASE_DATABASE: homebox
HBOX_DATABASE_HOST: 127.0.0.1
HBOX_DATABASE_PORT: 5432
HBOX_DATABASE_SSL_MODE: disable
desc: Runs end-to-end test on a live server with postgresql (only for use in CI)
cmds:
- cd backend && go build ./app/api
- backend/api &
- sleep 10
- cd frontend && pnpm run test:ci
silent: true
test:e2e:
desc: Runs end-to-end test on a live server
dir: frontend
cmds:
- task: go:ci
- task: ui:ci
- pnpm exec playwright install-deps
- pnpm exec playwright install
- sleep 30
- TEST_SHUTDOWN_API_SERVER=true pnpm exec playwright test -c ./test/playwright.config.ts {{ .CLI_ARGS }}
pr:
desc: Runs all tasks required for a PR
cmds:

View File

@@ -1,81 +1,74 @@
version: "2"
run:
timeout: 10m
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:
default: none
disable-all: true
enable:
- asciicheck
- bodyclose
- copyloopvar
- depguard
- dogsled
- errcheck
- errorlint
- exhaustive
- copyloopvar
- gochecknoinits
- goconst
- gocritic
- gocyclo
- gofmt
- goprintffuncname
- gosimple
- govet
- ineffassign
- misspell
- nakedret
- revive
- sqlclosecheck
- staticcheck
- stylecheck
- tagalign
- testifylint
- typecheck
- typecheck
- unconvert
- unused
- whitespace
- zerologlint
settings:
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.
exhaustive:
default-signifies-exhaustive: true
goconst:
min-len: 5
min-occurrences: 5
gocritic:
enabled-checks:
- ruleguard
revive:
confidence: 3
severity: warning
tagalign:
order:
- json
- schema
- yaml
- yml
- toml
- validate
testifylint:
enable-all: true
exclusions:
generated: lax
paths:
- internal/data/ent.*
- third_party$
- builtin$
- examples$
- sqlclosecheck
issues:
exclude-use-default: false
exclude-dirs:
- internal/data/ent.*
fix: true
formatters:
enable:
- gofmt
exclusions:
generated: lax
paths:
- internal/data/ent.*
- third_party$
- builtin$
- examples$

View File

@@ -1,5 +1,3 @@
version: 2
# This is an example .goreleaser.yml file with some sensible defaults.
# Make sure to check the documentation at https://goreleaser.com
before:
@@ -25,19 +23,8 @@ builds:
- goos: windows
goarch: "386"
signs:
- cmd: cosign
stdin: "{{ .Env.COSIGN_PWD }}"
args:
- "sign-blob"
- "--key=cosign.key"
- "--output-signature=${signature}"
- "${artifact}"
- "--yes" # needed on cosign 2.0.0+
artifacts: all
archives:
- formats: [ 'tar.gz' ]
- format: tar.gz
# this name template makes the OS and Arch compatible with the results of uname.
name_template: >-
{{ .ProjectName }}_
@@ -49,16 +36,11 @@ archives:
# use zip for windows archives
format_overrides:
- goos: windows
formats: [ 'zip' ]
release:
extra_files:
- glob: dist/*.sig
format: zip
checksum:
name_template: 'checksums.txt'
snapshot:
version_template: "{{ incpatch .Version }}-next"
name_template: "{{ incpatch .Version }}-next"
changelog:
sort: asc
filters:

View File

@@ -23,9 +23,8 @@ func NewTask(name string, interval time.Duration, fn func(context.Context)) *Bac
}
func (tsk *BackgroundTask) Start(ctx context.Context) error {
tsk.Fn(ctx)
timer := time.NewTimer(tsk.Interval)
for {
select {
case <-ctx.Done():

View File

@@ -13,7 +13,6 @@ import (
"github.com/sysadminsmedia/homebox/backend/internal/core/services"
"github.com/sysadminsmedia/homebox/backend/internal/core/services/reporting/eventbus"
"github.com/sysadminsmedia/homebox/backend/internal/data/repo"
"github.com/sysadminsmedia/homebox/backend/internal/sys/config"
"github.com/olahol/melody"
)
@@ -73,7 +72,6 @@ type V1Controller struct {
allowRegistration bool
bus *eventbus.EventBus
url string
config *config.Config
}
type (
@@ -86,25 +84,22 @@ type (
}
APISummary struct {
Healthy bool `json:"health"`
Versions []string `json:"versions"`
Title string `json:"title"`
Message string `json:"message"`
Build Build `json:"build"`
Latest services.Latest `json:"latest"`
Demo bool `json:"demo"`
AllowRegistration bool `json:"allowRegistration"`
LabelPrinting bool `json:"labelPrinting"`
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 NewControllerV1(svc *services.AllServices, repos *repo.AllRepos, bus *eventbus.EventBus, config *config.Config, options ...func(*V1Controller)) *V1Controller {
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,
config: config,
}
for _, opt := range options {
@@ -116,11 +111,11 @@ func NewControllerV1(svc *services.AllServices, repos *repo.AllRepos, bus *event
// HandleBase godoc
//
// @Summary Application Info
// @Tags Base
// @Produce json
// @Success 200 {object} APISummary
// @Router /v1/status [GET]
// @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{
@@ -128,21 +123,19 @@ func (ctrl *V1Controller) HandleBase(ready ReadyFunc, build Build) errchain.Hand
Title: "Homebox",
Message: "Track, Manage, and Organize your Things",
Build: build,
Latest: ctrl.svc.BackgroundService.GetLatestVersion(),
Demo: ctrl.isDemo,
AllowRegistration: ctrl.allowRegistration,
LabelPrinting: ctrl.config.LabelMaker.PrintCommand != nil,
})
}
}
// HandleCurrency godoc
//
// @Summary Currency
// @Tags Base
// @Produce json
// @Success 200 {object} currencies.Currency
// @Router /v1/currency [GET]
// @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

View File

@@ -1,30 +0,0 @@
package v1
import (
"net/url"
"github.com/rs/zerolog/log"
)
func GetHBURL(refererHeader, fallback string) (hbURL string) {
hbURL = refererHeader
if hbURL == "" {
hbURL = fallback
}
return stripPathFromURL(hbURL)
}
// stripPathFromURL removes the path from a URL.
// ex. https://example.com/tools -> https://example.com
func stripPathFromURL(rawURL string) string {
parsedURL, err := url.Parse(rawURL)
if err != nil {
log.Err(err).Msg("failed to parse URL")
return ""
}
strippedURL := url.URL{Scheme: parsedURL.Scheme, Host: parsedURL.Host}
return strippedURL.String()
}

View File

@@ -32,52 +32,52 @@ func actionHandlerFactory(ref string, fn func(context.Context, uuid.UUID) (int,
// 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
// @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
// @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
// @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
// @Summary Set Primary Photos
// @Description Sets the first photo of each item as the primary photo
// @Tags Actions
// @Produce json
// @Success 200 {object} ActionAmountResult
// @Router /v1/actions/set-primary-photos [Post]
// @Security Bearer
func (ctrl *V1Controller) HandleSetPrimaryPhotos() errchain.HandlerFunc {
return actionHandlerFactory("ensure asset IDs", ctrl.repo.Items.SetPrimaryPhotos)
}

View File

@@ -17,13 +17,13 @@ import (
// 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
// @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())

View File

@@ -28,8 +28,8 @@ type (
}
LoginForm struct {
Username string `json:"username" example:"admin@admin.com"`
Password string `json:"password" example:"admin"`
Username string `json:"username"`
Password string `json:"password"`
StayLoggedIn bool `json:"stayLoggedIn"`
}
)
@@ -83,6 +83,8 @@ type AuthProvider interface {
// @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
@@ -129,11 +131,11 @@ func (ctrl *V1Controller) HandleAuthLogin(ps ...AuthProvider) errchain.HandlerFu
// HandleAuthLogout godoc
//
// @Summary User Logout
// @Tags Authentication
// @Success 204
// @Router /v1/users/logout [POST]
// @Security Bearer
// @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())
@@ -153,13 +155,13 @@ func (ctrl *V1Controller) HandleAuthLogout() errchain.HandlerFunc {
// 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
// @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())

View File

@@ -26,12 +26,12 @@ type (
// HandleGroupGet godoc
//
// @Summary Get Group
// @Tags Group
// @Produce json
// @Success 200 {object} repo.Group
// @Router /v1/groups [Get]
// @Security Bearer
// @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())
@@ -43,13 +43,13 @@ func (ctrl *V1Controller) HandleGroupGet() errchain.HandlerFunc {
// 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
// @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())
@@ -69,13 +69,13 @@ func (ctrl *V1Controller) HandleGroupUpdate() errchain.HandlerFunc {
// 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
// @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() {

View File

@@ -4,11 +4,10 @@ import (
"database/sql"
"encoding/csv"
"errors"
"fmt"
"math/big"
"net/http"
"net/url"
"strings"
"time"
"github.com/google/uuid"
"github.com/hay-kot/httpkit/errchain"
@@ -22,18 +21,18 @@ import (
// 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
// @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()
@@ -55,18 +54,16 @@ func (ctrl *V1Controller) HandleItemsGetAll() errchain.HandlerFunc {
}
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"),
NegateLabels: queryBool(params.Get("negateLabels")),
OnlyWithoutPhoto: queryBool(params.Get("onlyWithoutPhoto")),
OnlyWithPhoto: queryBool(params.Get("onlyWithPhoto")),
ParentItemIDs: queryUUIDList(params, "parentIds"),
IncludeArchived: queryBool(params.Get("includeArchived")),
Fields: filterFieldItems(params["fields"]),
OrderBy: params.Get("orderBy"),
Page: queryIntOrNegativeOne(params.Get("page")),
PageSize: queryIntOrNegativeOne(params.Get("pageSize")),
Search: params.Get("q"),
LocationIDs: queryUUIDList(params, "locations"),
LabelIDs: queryUUIDList(params, "labels"),
NegateLabels: queryBool(params.Get("negateLabels")),
ParentItemIDs: queryUUIDList(params, "parentIds"),
IncludeArchived: queryBool(params.Get("includeArchived")),
Fields: filterFieldItems(params["fields"]),
OrderBy: params.Get("orderBy"),
}
if strings.HasPrefix(v.Search, "#") {
@@ -88,9 +85,6 @@ func (ctrl *V1Controller) HandleItemsGetAll() errchain.HandlerFunc {
items, err := ctrl.repo.Items.QueryByGroup(ctx, ctx.GID, extractQuery(r))
totalPrice := new(big.Int)
for _, item := range items.Items {
if !item.SoldTime.IsZero() { // Skip items with a non-null SoldDate
continue
}
totalPrice.Add(totalPrice, big.NewInt(int64(item.PurchasePrice*100)))
}
@@ -112,13 +106,13 @@ func (ctrl *V1Controller) HandleItemsGetAll() errchain.HandlerFunc {
// 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
// @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())
@@ -154,13 +148,13 @@ func (ctrl *V1Controller) HandleItemFullPath() errchain.HandlerFunc {
// 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
// @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)
@@ -171,13 +165,13 @@ func (ctrl *V1Controller) HandleItemsCreate() errchain.HandlerFunc {
// 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
// @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())
@@ -190,13 +184,13 @@ func (ctrl *V1Controller) HandleItemGet() errchain.HandlerFunc {
// 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
// @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())
@@ -209,14 +203,14 @@ func (ctrl *V1Controller) HandleItemDelete() errchain.HandlerFunc {
// 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
// @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())
@@ -230,14 +224,14 @@ func (ctrl *V1Controller) HandleItemUpdate() errchain.HandlerFunc {
// 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
// @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())
@@ -256,13 +250,13 @@ func (ctrl *V1Controller) HandleItemPatch() errchain.HandlerFunc {
// 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
// @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())
@@ -274,13 +268,13 @@ func (ctrl *V1Controller) HandleGetAllCustomFieldNames() errchain.HandlerFunc {
// 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
// @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"`
@@ -298,7 +292,6 @@ func (ctrl *V1Controller) HandleGetAllCustomFieldValues() errchain.HandlerFunc {
//
// @Summary Import Items
// @Tags Items
// @Accept multipart/form-data
// @Produce json
// @Success 204
// @Param csv formData file true "Image to upload"
@@ -332,29 +325,49 @@ func (ctrl *V1Controller) HandleItemsImport() errchain.HandlerFunc {
// HandleItemsExport godocs
//
// @Summary Export Items
// @Tags Items
// @Success 200 {string} string "text/csv"
// @Router /v1/items/export [GET]
// @Security Bearer
// @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.ExportCSV(r.Context(), ctx.GID, GetHBURL(r.Header.Get("Referer"), ctrl.url))
csvData, err := ctrl.svc.Items.ExportCSV(r.Context(), ctx.GID, getHBURL(r.Header.Get("Referer"), ctrl.url))
if err != nil {
log.Err(err).Msg("failed to export items")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
timestamp := time.Now().Format("2006-01-02_15-04-05") // YYYY-MM-DD_HH-MM-SS format
filename := fmt.Sprintf("homebox-items_%s.csv", timestamp) // add timestamp to filename
w.Header().Set("Content-Type", "text/csv")
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment;filename=%s", filename))
w.Header().Set("Content-Disposition", "attachment;filename=homebox-items.csv")
writer := csv.NewWriter(w)
writer.Comma = ','
return writer.WriteAll(csvData)
}
}
func getHBURL(refererHeader, fallback string) (hbURL string) {
hbURL = refererHeader
if hbURL == "" {
hbURL = fallback
}
return stripPathFromURL(hbURL)
}
// stripPathFromURL removes the path from a URL.
// ex. https://example.com/tools -> https://example.com
func stripPathFromURL(rawURL string) string {
parsedURL, err := url.Parse(rawURL)
if err != nil {
log.Err(err).Msg("failed to parse URL")
return ""
}
strippedURL := url.URL{Scheme: parsedURL.Scheme, Host: parsedURL.Host}
return strippedURL.String()
}

View File

@@ -4,7 +4,6 @@ import (
"errors"
"net/http"
"path/filepath"
"strconv"
"strings"
"github.com/hay-kot/httpkit/errchain"
@@ -26,12 +25,10 @@ type (
//
// @Summary Create Item Attachment
// @Tags Items Attachments
// @Accept multipart/form-data
// @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 primary formData bool false "Is this the primary attachment"
// @Param name formData string true "name of the file including extension"
// @Success 200 {object} repo.ItemOut
// @Failure 422 {object} validate.ErrorResponse
@@ -82,12 +79,6 @@ func (ctrl *V1Controller) HandleItemAttachmentCreate() errchain.HandlerFunc {
}
}
primary, err := strconv.ParseBool(r.FormValue("primary"))
if err != nil {
log.Debug().Msg("failed to parse primary from form")
primary = false
}
id, err := ctrl.routeID(r)
if err != nil {
return err
@@ -100,7 +91,6 @@ func (ctrl *V1Controller) HandleItemAttachmentCreate() errchain.HandlerFunc {
id,
attachmentName,
attachment.Type(attachmentType),
primary,
file,
)
if err != nil {
@@ -114,41 +104,41 @@ func (ctrl *V1Controller) HandleItemAttachmentCreate() errchain.HandlerFunc {
// 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
// @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
// @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
// @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
}
@@ -172,7 +162,7 @@ func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r
log.Err(err).Msg("failed to get attachment path")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
// w.Header().Set("Content-Disposition", "attachment; filename="+doc.Title)
http.ServeFile(w, r, doc.Path)
return nil

View File

@@ -1,136 +0,0 @@
package v1
import (
"fmt"
"net/http"
"strconv"
"strings"
"github.com/go-chi/chi/v5"
"github.com/hay-kot/httpkit/errchain"
"github.com/sysadminsmedia/homebox/backend/internal/core/services"
"github.com/sysadminsmedia/homebox/backend/internal/data/repo"
"github.com/sysadminsmedia/homebox/backend/internal/sys/validate"
"github.com/sysadminsmedia/homebox/backend/internal/web/adapters"
"github.com/sysadminsmedia/homebox/backend/pkgs/labelmaker"
)
func generateOrPrint(ctrl *V1Controller, w http.ResponseWriter, r *http.Request, title string, description string, url string) error {
params := labelmaker.NewGenerateParams(int(ctrl.config.LabelMaker.Width), int(ctrl.config.LabelMaker.Height), int(ctrl.config.LabelMaker.Margin), int(ctrl.config.LabelMaker.Padding), ctrl.config.LabelMaker.FontSize, title, description, url, ctrl.config.LabelMaker.DynamicLength, ctrl.config.LabelMaker.AdditionalInformation)
print := queryBool(r.URL.Query().Get("print"))
if print {
err := labelmaker.PrintLabel(ctrl.config, &params)
if err != nil {
return err
}
_, err = w.Write([]byte("Printed!"))
return err
} else {
return labelmaker.GenerateLabel(w, &params)
}
}
// HandleGetLocationLabel godoc
//
// @Summary Get Location label
// @Tags Locations
// @Produce json
// @Param id path string true "Location ID"
// @Param print query bool false "Print this label, defaults to false"
// @Success 200 {string} string "image/png"
// @Router /v1/labelmaker/location/{id} [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleGetLocationLabel() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
ID, err := adapters.RouteUUID(r, "id")
if err != nil {
return err
}
auth := services.NewContext(r.Context())
location, err := ctrl.repo.Locations.GetOneByGroup(auth, auth.GID, ID)
if err != nil {
return err
}
hbURL := GetHBURL(r.Header.Get("Referer"), ctrl.url)
return generateOrPrint(ctrl, w, r, location.Name, "Homebox Location", fmt.Sprintf("%s/location/%s", hbURL, location.ID))
}
}
// HandleGetItemLabel godoc
//
// @Summary Get Item label
// @Tags Items
// @Produce json
// @Param id path string true "Item ID"
// @Param print query bool false "Print this label, defaults to false"
// @Success 200 {string} string "image/png"
// @Router /v1/labelmaker/item/{id} [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleGetItemLabel() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
ID, err := adapters.RouteUUID(r, "id")
if err != nil {
return err
}
auth := services.NewContext(r.Context())
item, err := ctrl.repo.Items.GetOneByGroup(auth, auth.GID, ID)
if err != nil {
return err
}
description := ""
if item.Location != nil {
description += fmt.Sprintf("\nLocation: %s", item.Location.Name)
}
hbURL := GetHBURL(r.Header.Get("Referer"), ctrl.url)
return generateOrPrint(ctrl, w, r, item.Name, description, fmt.Sprintf("%s/item/%s", hbURL, item.ID))
}
}
// HandleGetAssetLabel godoc
//
// @Summary Get Asset label
// @Tags Items
// @Produce json
// @Param id path string true "Asset ID"
// @Param print query bool false "Print this label, defaults to false"
// @Success 200 {string} string "image/png"
// @Router /v1/labelmaker/assets/{id} [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleGetAssetLabel() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
assetIDParam := chi.URLParam(r, "id")
assetIDParam = strings.ReplaceAll(assetIDParam, "-", "")
assetID, err := strconv.ParseInt(assetIDParam, 10, 64)
if err != nil {
return err
}
auth := services.NewContext(r.Context())
item, err := ctrl.repo.Items.QueryByAssetID(auth, auth.GID, repo.AssetID(assetID), 0, 1)
if err != nil {
return err
}
if len(item.Items) == 0 {
return validate.NewRequestError(fmt.Errorf("failed to find asset id"), http.StatusNotFound)
}
description := item.Items[0].Name
if item.Items[0].Location != nil {
description += fmt.Sprintf("\nLocation: %s", item.Items[0].Location.Name)
}
hbURL := GetHBURL(r.Header.Get("Referer"), ctrl.url)
return generateOrPrint(ctrl, w, r, item.Items[0].AssetID.String(), description, fmt.Sprintf("%s/a/%s", hbURL, item.Items[0].AssetID.String()))
}
}

View File

@@ -12,12 +12,12 @@ import (
// HandleLabelsGetAll godoc
//
// @Summary Get All Labels
// @Tags Labels
// @Produce json
// @Success 200 {object} []repo.LabelOut
// @Router /v1/labels [GET]
// @Security Bearer
// @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())
@@ -29,13 +29,13 @@ func (ctrl *V1Controller) HandleLabelsGetAll() errchain.HandlerFunc {
// 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
// @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())
@@ -47,13 +47,13 @@ func (ctrl *V1Controller) HandleLabelsCreate() errchain.HandlerFunc {
// 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
// @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())
@@ -66,13 +66,13 @@ func (ctrl *V1Controller) HandleLabelDelete() errchain.HandlerFunc {
// 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
// @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())
@@ -84,13 +84,13 @@ func (ctrl *V1Controller) HandleLabelGet() errchain.HandlerFunc {
// 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
// @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())

View File

@@ -0,0 +1,51 @@
package v1
import (
"encoding/json"
"net/http"
"github.com/google/uuid"
"github.com/hay-kot/httpkit/errchain"
"github.com/sysadminsmedia/homebox/backend/internal/core/services"
"github.com/sysadminsmedia/homebox/backend/internal/data/repo"
"github.com/sysadminsmedia/homebox/backend/internal/web/adapters"
)
type Locales struct {
Locales []string `json:"locales"`
}
// HandleLocalesGetAll godoc
//
// @Summary Get All Locales
// @Tags Locales
// @Produce json
// @Success 200 {object} []Locales
// @Router /v1/locales [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleLocalesGetAll() errchain.HandlerFunc {
fn := func(r *http.Request) ([]Locales, error) {
// TODO: get a list of locales from files
return []Locales{}, nil
}
return adapters.Command(fn, http.StatusOK)
}
// HandleLocalesGet godoc
//
// @Summary Get Locale
// @Tags Locales
// @Produce json
// @Param id path string true "Locale ID"
// @Success 200 {object} interface{}
// @Router /v1/locales/{id} [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleLocalesGet() errchain.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID) (interface{}, error) {
// TODO: get the current locale
return interface{}, nil
}
return adapters.CommandID("id", fn, http.StatusOK)
}

View File

@@ -14,13 +14,13 @@ import (
// 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
// @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())
@@ -32,13 +32,13 @@ func (ctrl *V1Controller) HandleLocationTreeQuery() errchain.HandlerFunc {
// 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
// @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())
@@ -50,13 +50,13 @@ func (ctrl *V1Controller) HandleLocationGetAll() errchain.HandlerFunc {
// 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
// @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())
@@ -68,13 +68,13 @@ func (ctrl *V1Controller) HandleLocationCreate() errchain.HandlerFunc {
// 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
// @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())
@@ -99,11 +99,6 @@ func (ctrl *V1Controller) GetLocationWithPrice(auth context.Context, gid uuid.UU
}
for _, item := range items.Items {
// Skip items with a non-zero SoldTime
if !item.SoldTime.IsZero() {
continue
}
// Convert item.Quantity to float64 for multiplication
quantity := float64(item.Quantity)
itemTotal := big.NewInt(int64(item.PurchasePrice * quantity * 100))
@@ -129,13 +124,13 @@ func (ctrl *V1Controller) GetLocationWithPrice(auth context.Context, gid uuid.UU
// 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
// @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())
@@ -149,14 +144,14 @@ func (ctrl *V1Controller) HandleLocationGet() errchain.HandlerFunc {
// 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
// @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())

View File

@@ -15,7 +15,6 @@ import (
// @Summary Get Maintenance Log
// @Tags Item Maintenance
// @Produce json
// @Param id path string true "Item ID"
// @Param filters query repo.MaintenanceFilters false "which maintenance to retrieve"
// @Success 200 {array} repo.MaintenanceEntryWithDetails[]
// @Router /v1/items/{id}/maintenance [GET]
@@ -34,7 +33,6 @@ func (ctrl *V1Controller) HandleMaintenanceLogGet() errchain.HandlerFunc {
// @Summary Create Maintenance Entry
// @Tags Item Maintenance
// @Produce json
// @Param id path string true "Item ID"
// @Param payload body repo.MaintenanceEntryCreate true "Entry Data"
// @Success 201 {object} repo.MaintenanceEntry
// @Router /v1/items/{id}/maintenance [POST]

View File

@@ -12,13 +12,13 @@ import (
// HandleMaintenanceGetAll godoc
//
// @Summary Query All Maintenance
// @Tags Maintenance
// @Produce json
// @Param filters query repo.MaintenanceFilters false "which maintenance to retrieve"
// @Success 200 {array} repo.MaintenanceEntryWithDetails[]
// @Router /v1/maintenance [GET]
// @Security Bearer
// @Summary Query All Maintenance
// @Tags Maintenance
// @Produce json
// @Param filters query repo.MaintenanceFilters false "which maintenance to retrieve"
// @Success 200 {array} repo.MaintenanceEntryWithDetails[]
// @Router /v1/maintenance [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleMaintenanceGetAll() errchain.HandlerFunc {
fn := func(r *http.Request, filters repo.MaintenanceFilters) ([]repo.MaintenanceEntryWithDetails, error) {
auth := services.NewContext(r.Context())
@@ -33,7 +33,6 @@ func (ctrl *V1Controller) HandleMaintenanceGetAll() errchain.HandlerFunc {
// @Summary Update Maintenance Entry
// @Tags Maintenance
// @Produce json
// @Param id path string true "Maintenance ID"
// @Param payload body repo.MaintenanceEntryUpdate true "Entry Data"
// @Success 200 {object} repo.MaintenanceEntry
// @Router /v1/maintenance/{id} [PUT]
@@ -52,7 +51,6 @@ func (ctrl *V1Controller) HandleMaintenanceEntryUpdate() errchain.HandlerFunc {
// @Summary Delete Maintenance Entry
// @Tags Maintenance
// @Produce json
// @Param id path string true "Maintenance ID"
// @Success 204
// @Router /v1/maintenance/{id} [DELETE]
// @Security Bearer

View File

@@ -13,12 +13,12 @@ import (
// HandleGetUserNotifiers godoc
//
// @Summary Get Notifiers
// @Tags Notifiers
// @Produce json
// @Success 200 {object} []repo.NotifierOut
// @Router /v1/notifiers [GET]
// @Security Bearer
// @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())
@@ -30,13 +30,13 @@ func (ctrl *V1Controller) HandleGetUserNotifiers() errchain.HandlerFunc {
// 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
// @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())
@@ -48,12 +48,12 @@ func (ctrl *V1Controller) HandleCreateNotifier() errchain.HandlerFunc {
// HandleDeleteNotifier godocs
//
// @Summary Delete a Notifier
// @Tags Notifiers
// @Param id path string true "Notifier ID"
// @Success 204
// @Router /v1/notifiers/{id} [DELETE]
// @Security Bearer
// @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())
@@ -65,13 +65,13 @@ func (ctrl *V1Controller) HandleDeleteNotifier() errchain.HandlerFunc {
// 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
// @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())
@@ -86,6 +86,7 @@ func (ctrl *V1Controller) HandleUpdateNotifier() errchain.HandlerFunc {
// @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]

View File

@@ -20,13 +20,13 @@ 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
// @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

View File

@@ -8,12 +8,12 @@ import (
// 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
// @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())

View File

@@ -14,12 +14,12 @@ import (
// HandleGroupStatisticsLocations godoc
//
// @Summary Get Location Statistics
// @Tags Statistics
// @Produce json
// @Success 200 {object} []repo.TotalsByOrganizer
// @Router /v1/groups/statistics/locations [GET]
// @Security Bearer
// @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())
@@ -31,12 +31,12 @@ func (ctrl *V1Controller) HandleGroupStatisticsLocations() errchain.HandlerFunc
// HandleGroupStatisticsLabels godoc
//
// @Summary Get Label Statistics
// @Tags Statistics
// @Produce json
// @Success 200 {object} []repo.TotalsByOrganizer
// @Router /v1/groups/statistics/labels [GET]
// @Security Bearer
// @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())
@@ -48,12 +48,12 @@ func (ctrl *V1Controller) HandleGroupStatisticsLabels() errchain.HandlerFunc {
// HandleGroupStatistics godoc
//
// @Summary Get Group Statistics
// @Tags Statistics
// @Produce json
// @Success 200 {object} repo.GroupStatistics
// @Router /v1/groups/statistics [GET]
// @Security Bearer
// @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())
@@ -65,14 +65,14 @@ func (ctrl *V1Controller) HandleGroupStatistics() errchain.HandlerFunc {
// 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
// @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 == "" {

View File

@@ -15,12 +15,12 @@ import (
// 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]
// @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{}
@@ -46,12 +46,12 @@ func (ctrl *V1Controller) HandleUserRegistration() errchain.HandlerFunc {
// HandleUserSelf godoc
//
// @Summary Get User Self
// @Tags User
// @Produce json
// @Success 200 {object} Wrapped{item=repo.UserOut}
// @Router /v1/users/self [GET]
// @Security Bearer
// @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())
@@ -67,13 +67,13 @@ func (ctrl *V1Controller) HandleUserSelf() errchain.HandlerFunc {
// 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
// @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{}
@@ -94,12 +94,12 @@ func (ctrl *V1Controller) HandleUserSelfUpdate() errchain.HandlerFunc {
// HandleUserSelfDelete godoc
//
// @Summary Delete Account
// @Tags User
// @Produce json
// @Success 204
// @Router /v1/users/self [DELETE]
// @Security Bearer
// @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 {
@@ -124,12 +124,12 @@ type (
// HandleUserSelfChangePassword godoc
//
// @Summary Change Password
// @Tags User
// @Success 204
// @Param payload body ChangePassword true "Password Payload"
// @Router /v1/users/change-password [PUT]
// @Security Bearer
// @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 {

View File

@@ -6,11 +6,11 @@ import (
"fmt"
"net/http"
"os"
"strings"
"path/filepath"
"time"
"github.com/pressly/goose/v3"
atlas "ariga.io/atlas/sql/migrate"
"entgo.io/ent/dialect/sql/schema"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
@@ -25,13 +25,9 @@ import (
"github.com/sysadminsmedia/homebox/backend/internal/data/ent"
"github.com/sysadminsmedia/homebox/backend/internal/data/migrations"
"github.com/sysadminsmedia/homebox/backend/internal/data/repo"
"github.com/sysadminsmedia/homebox/backend/internal/sys/analytics"
"github.com/sysadminsmedia/homebox/backend/internal/sys/config"
"github.com/sysadminsmedia/homebox/backend/internal/web/mid"
_ "github.com/lib/pq"
_ "github.com/sysadminsmedia/homebox/backend/internal/data/migrations/postgres"
_ "github.com/sysadminsmedia/homebox/backend/internal/data/migrations/sqlite3"
_ "github.com/sysadminsmedia/homebox/backend/pkgs/cgofreesqlite"
)
@@ -50,33 +46,15 @@ func build() string {
return fmt.Sprintf("%s, commit %s, built at %s", version, short, buildTime)
}
func validatePostgresSSLMode(sslMode string) bool {
validModes := map[string]bool{
"": true,
"disable": true,
"allow": true,
"prefer": true,
"require": true,
"verify-ca": true,
"verify-full": true,
}
return validModes[strings.ToLower(strings.TrimSpace(sslMode))]
}
// @title Homebox API
// @version 1.0
// @description Track, Manage, and Organize your Things.
// @contact.name Homebox Team
// @contact.url https://discord.homebox.software
// @host demo.homebox.software
// @schemes https http
// @contact.name Don't
// @BasePath /api
// @securityDefinitions.apikey Bearer
// @in header
// @name Authorization
// @description "Type 'Bearer TOKEN' to correctly set the API Key"
// @externalDocs.url https://homebox.software/en/api
func main() {
zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
@@ -94,10 +72,6 @@ func run(cfg *config.Config) error {
app := new(cfg)
app.setupLogger()
if cfg.Options.AllowAnalytics {
analytics.Send(version, build())
}
// =========================================================================
// Initialize Database & Repos
@@ -106,44 +80,52 @@ func run(cfg *config.Config) error {
log.Fatal().Err(err).Msg("failed to create data directory")
}
if strings.ToLower(cfg.Database.Driver) == "postgres" {
if !validatePostgresSSLMode(cfg.Database.SslMode) {
log.Fatal().Str("sslmode", cfg.Database.SslMode).Msg("invalid sslmode")
}
}
// Set up the database URL based on the driver because for some reason a common URL format is not used
databaseURL := ""
switch strings.ToLower(cfg.Database.Driver) {
case "sqlite3":
databaseURL = cfg.Database.SqlitePath
case "postgres":
databaseURL = fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s", cfg.Database.Host, cfg.Database.Port, cfg.Database.Username, cfg.Database.Password, cfg.Database.Database, cfg.Database.SslMode)
default:
log.Fatal().Str("driver", cfg.Database.Driver).Msg("unsupported database driver")
}
c, err := ent.Open(strings.ToLower(cfg.Database.Driver), databaseURL)
c, err := ent.Open("sqlite3", cfg.Storage.SqliteURL)
if err != nil {
log.Fatal().
Err(err).
Str("driver", strings.ToLower(cfg.Database.Driver)).
Str("host", cfg.Database.Host).
Str("port", cfg.Database.Port).
Str("database", cfg.Database.Database).
Msg("failed opening connection to {driver} database at {host}:{port}/{database}")
Str("driver", "sqlite").
Str("url", cfg.Storage.SqliteURL).
Msg("failed opening connection to sqlite")
}
defer func(c *ent.Client) {
err := c.Close()
if err != nil {
log.Fatal().Err(err).Msg("failed to close database connection")
}
}(c)
temp := filepath.Join(os.TempDir(), "migrations")
err = migrations.Write(temp)
if err != nil {
return err
}
goose.SetBaseFS(migrations.Migrations(strings.ToLower(cfg.Database.Driver)))
err = goose.SetDialect(strings.ToLower(cfg.Database.Driver))
dir, err := atlas.NewLocalDir(temp)
if err != nil {
log.Fatal().Str("driver", cfg.Database.Driver).Msg("unsupported database driver")
return fmt.Errorf("unsupported database driver: %s", cfg.Database.Driver)
return err
}
err = goose.Up(c.Sql(), strings.ToLower(cfg.Database.Driver))
options := []schema.MigrateOption{
schema.WithDir(dir),
schema.WithDropColumn(true),
schema.WithDropIndex(true),
}
err = c.Schema.Create(context.Background(), options...)
if err != nil {
log.Error().Err(err).Msg("failed to migrate database")
log.Error().
Err(err).
Str("driver", "sqlite").
Str("url", cfg.Storage.SqliteURL).
Msg("failed creating schema resources")
return err
}
err = os.RemoveAll(temp)
if err != nil {
log.Error().Err(err).Msg("failed to remove temporary directory for database migrations")
return err
}
@@ -272,14 +254,15 @@ func run(cfg *config.Config) error {
}
}))
if cfg.Options.GithubReleaseCheck {
runner.AddPlugin(NewTask("get-latest-github-release", time.Hour, func(ctx context.Context) {
log.Debug().Msg("running get latest github release")
err := app.services.BackgroundService.GetLatestGithubRelease(context.Background())
// TODO: read from env var0 GOOS=linux go build
if true {
runner.AddPlugin(NewTask("locale-update", time.Duration(24)*time.Hour, func(ctx context.Context) {
log.Debug().Msg("running locale update")
err := app.services.BackgroundService.UpdateLocales(ctx)
if err != nil {
log.Error().
Err(err).
Msg("failed to get latest github release")
Msg("failed to update locales")
}
}))
}

View File

@@ -52,7 +52,6 @@ func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllR
a.services,
a.repos,
a.bus,
a.conf,
v1.WithMaxUploadSize(a.conf.Web.MaxUploadSize),
v1.WithRegistration(a.conf.Options.AllowRegistration),
v1.WithDemoStatus(a.conf.Demo), // Disable Password Change in Demo Mode
@@ -162,11 +161,6 @@ func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllR
chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentGet(), assetMW...),
)
// Labelmaker
r.Get("/labelmaker/location/{id}", chain.ToHandlerFunc(v1Ctrl.HandleGetLocationLabel(), userMW...))
r.Get("/labelmaker/item/{id}", chain.ToHandlerFunc(v1Ctrl.HandleGetItemLabel(), userMW...))
r.Get("/labelmaker/asset/{id}", chain.ToHandlerFunc(v1Ctrl.HandleGetAssetLabel(), userMW...))
// Reporting Services
r.Get("/reporting/bill-of-materials", chain.ToHandlerFunc(v1Ctrl.HandleBillOfMaterialsExport(), userMW...))

View File

@@ -10,8 +10,7 @@ const docTemplate = `{
"description": "{{escape .Description}}",
"title": "{{.Title}}",
"contact": {
"name": "Homebox Team",
"url": "https://discord.homebox.software"
"name": "Don't"
},
"version": "{{.Version}}"
},
@@ -567,9 +566,6 @@ const docTemplate = `{
"Bearer": []
}
],
"consumes": [
"multipart/form-data"
],
"produces": [
"application/json"
],
@@ -741,9 +737,6 @@ const docTemplate = `{
"Bearer": []
}
],
"consumes": [
"multipart/form-data"
],
"produces": [
"application/json"
],
@@ -773,12 +766,6 @@ const docTemplate = `{
"in": "formData",
"required": true
},
{
"type": "boolean",
"description": "Is this the primary attachment",
"name": "primary",
"in": "formData"
},
{
"type": "string",
"description": "name of the file including extension",
@@ -934,13 +921,6 @@ const docTemplate = `{
],
"summary": "Get Maintenance Log",
"parameters": [
{
"type": "string",
"description": "Item ID",
"name": "id",
"in": "path",
"required": true
},
{
"enum": [
"scheduled",
@@ -983,13 +963,6 @@ const docTemplate = `{
],
"summary": "Create Maintenance Entry",
"parameters": [
{
"type": "string",
"description": "Item ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "Entry Data",
"name": "payload",
@@ -1046,123 +1019,6 @@ const docTemplate = `{
}
}
},
"/v1/labelmaker/assets/{id}": {
"get": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Items"
],
"summary": "Get Asset label",
"parameters": [
{
"type": "string",
"description": "Asset ID",
"name": "id",
"in": "path",
"required": true
},
{
"type": "boolean",
"description": "Print this label, defaults to false",
"name": "print",
"in": "query"
}
],
"responses": {
"200": {
"description": "image/png",
"schema": {
"type": "string"
}
}
}
}
},
"/v1/labelmaker/item/{id}": {
"get": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Items"
],
"summary": "Get Item label",
"parameters": [
{
"type": "string",
"description": "Item ID",
"name": "id",
"in": "path",
"required": true
},
{
"type": "boolean",
"description": "Print this label, defaults to false",
"name": "print",
"in": "query"
}
],
"responses": {
"200": {
"description": "image/png",
"schema": {
"type": "string"
}
}
}
}
},
"/v1/labelmaker/location/{id}": {
"get": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Locations"
],
"summary": "Get Location label",
"parameters": [
{
"type": "string",
"description": "Location ID",
"name": "id",
"in": "path",
"required": true
},
{
"type": "boolean",
"description": "Print this label, defaults to false",
"name": "print",
"in": "query"
}
],
"responses": {
"200": {
"description": "image/png",
"schema": {
"type": "string"
}
}
}
}
},
"/v1/labels": {
"get": {
"security": [
@@ -1578,13 +1434,6 @@ const docTemplate = `{
],
"summary": "Update Maintenance Entry",
"parameters": [
{
"type": "string",
"description": "Maintenance ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "Entry Data",
"name": "payload",
@@ -1617,15 +1466,6 @@ const docTemplate = `{
"Maintenance"
],
"summary": "Delete Maintenance Entry",
"parameters": [
{
"type": "string",
"description": "Maintenance ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"204": {
"description": "No Content"
@@ -1708,6 +1548,13 @@ const docTemplate = `{
],
"summary": "Test Notifier",
"parameters": [
{
"type": "string",
"description": "Notifier ID",
"name": "id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "URL",
@@ -1905,6 +1752,20 @@ const docTemplate = `{
],
"summary": "User Login",
"parameters": [
{
"type": "string",
"example": "admin@admin.com",
"description": "string",
"name": "username",
"in": "formData"
},
{
"type": "string",
"example": "admin",
"description": "string",
"name": "password",
"in": "formData"
},
{
"description": "Login Data",
"name": "payload",
@@ -2114,6 +1975,20 @@ const docTemplate = `{
}
}
},
"repo.DocumentOut": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"path": {
"type": "string"
},
"title": {
"type": "string"
}
}
},
"repo.Group": {
"type": "object",
"properties": {
@@ -2174,18 +2049,15 @@ const docTemplate = `{
"createdAt": {
"type": "string"
},
"id": {
"type": "string"
"document": {
"$ref": "#/definitions/repo.DocumentOut"
},
"path": {
"id": {
"type": "string"
},
"primary": {
"type": "boolean"
},
"title": {
"type": "string"
},
"type": {
"type": "string"
},
@@ -2236,9 +2108,6 @@ const docTemplate = `{
"parentId": {
"type": "string",
"x-nullable": true
},
"quantity": {
"type": "integer"
}
}
},
@@ -2373,9 +2242,6 @@ const docTemplate = `{
"soldTo": {
"type": "string"
},
"syncChildItemsLocations": {
"type": "boolean"
},
"updatedAt": {
"type": "string"
},
@@ -2420,10 +2286,6 @@ const docTemplate = `{
"archived": {
"type": "boolean"
},
"assetId": {
"type": "string",
"example": "0"
},
"createdAt": {
"type": "string"
},
@@ -2464,10 +2326,6 @@ const docTemplate = `{
"quantity": {
"type": "integer"
},
"soldTime": {
"description": "Sale details",
"type": "string"
},
"updatedAt": {
"type": "string"
}
@@ -2582,9 +2440,6 @@ const docTemplate = `{
"type": "string",
"maxLength": 255
},
"syncChildItemsLocations": {
"type": "boolean"
},
"warrantyDetails": {
"type": "string"
},
@@ -3070,17 +2925,6 @@ const docTemplate = `{
}
}
},
"services.Latest": {
"type": "object",
"properties": {
"date": {
"type": "string"
},
"version": {
"type": "string"
}
}
},
"services.UserRegistration": {
"type": "object",
"properties": {
@@ -3113,12 +2957,6 @@ const docTemplate = `{
"health": {
"type": "boolean"
},
"labelPrinting": {
"type": "boolean"
},
"latest": {
"$ref": "#/definitions/services.Latest"
},
"message": {
"type": "string"
},
@@ -3208,15 +3046,13 @@ const docTemplate = `{
"type": "object",
"properties": {
"password": {
"type": "string",
"example": "admin"
"type": "string"
},
"stayLoggedIn": {
"type": "boolean"
},
"username": {
"type": "string",
"example": "admin@admin.com"
"type": "string"
}
}
},
@@ -3265,9 +3101,9 @@ const docTemplate = `{
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{
Version: "1.0",
Host: "demo.homebox.software",
Host: "",
BasePath: "/api",
Schemes: []string{"https", "http"},
Schemes: []string{},
Title: "Homebox API",
Description: "Track, Manage, and Organize your Things.",
InfoInstanceName: "swagger",

View File

@@ -1,19 +1,13 @@
{
"schemes": [
"https",
"http"
],
"swagger": "2.0",
"info": {
"description": "Track, Manage, and Organize your Things.",
"title": "Homebox API",
"contact": {
"name": "Homebox Team",
"url": "https://discord.homebox.software"
"name": "Don't"
},
"version": "1.0"
},
"host": "demo.homebox.software",
"basePath": "/api",
"paths": {
"/v1/actions/ensure-asset-ids": {
@@ -565,9 +559,6 @@
"Bearer": []
}
],
"consumes": [
"multipart/form-data"
],
"produces": [
"application/json"
],
@@ -739,9 +730,6 @@
"Bearer": []
}
],
"consumes": [
"multipart/form-data"
],
"produces": [
"application/json"
],
@@ -771,12 +759,6 @@
"in": "formData",
"required": true
},
{
"type": "boolean",
"description": "Is this the primary attachment",
"name": "primary",
"in": "formData"
},
{
"type": "string",
"description": "name of the file including extension",
@@ -932,13 +914,6 @@
],
"summary": "Get Maintenance Log",
"parameters": [
{
"type": "string",
"description": "Item ID",
"name": "id",
"in": "path",
"required": true
},
{
"enum": [
"scheduled",
@@ -981,13 +956,6 @@
],
"summary": "Create Maintenance Entry",
"parameters": [
{
"type": "string",
"description": "Item ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "Entry Data",
"name": "payload",
@@ -1044,123 +1012,6 @@
}
}
},
"/v1/labelmaker/assets/{id}": {
"get": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Items"
],
"summary": "Get Asset label",
"parameters": [
{
"type": "string",
"description": "Asset ID",
"name": "id",
"in": "path",
"required": true
},
{
"type": "boolean",
"description": "Print this label, defaults to false",
"name": "print",
"in": "query"
}
],
"responses": {
"200": {
"description": "image/png",
"schema": {
"type": "string"
}
}
}
}
},
"/v1/labelmaker/item/{id}": {
"get": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Items"
],
"summary": "Get Item label",
"parameters": [
{
"type": "string",
"description": "Item ID",
"name": "id",
"in": "path",
"required": true
},
{
"type": "boolean",
"description": "Print this label, defaults to false",
"name": "print",
"in": "query"
}
],
"responses": {
"200": {
"description": "image/png",
"schema": {
"type": "string"
}
}
}
}
},
"/v1/labelmaker/location/{id}": {
"get": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Locations"
],
"summary": "Get Location label",
"parameters": [
{
"type": "string",
"description": "Location ID",
"name": "id",
"in": "path",
"required": true
},
{
"type": "boolean",
"description": "Print this label, defaults to false",
"name": "print",
"in": "query"
}
],
"responses": {
"200": {
"description": "image/png",
"schema": {
"type": "string"
}
}
}
}
},
"/v1/labels": {
"get": {
"security": [
@@ -1576,13 +1427,6 @@
],
"summary": "Update Maintenance Entry",
"parameters": [
{
"type": "string",
"description": "Maintenance ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "Entry Data",
"name": "payload",
@@ -1615,15 +1459,6 @@
"Maintenance"
],
"summary": "Delete Maintenance Entry",
"parameters": [
{
"type": "string",
"description": "Maintenance ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"204": {
"description": "No Content"
@@ -1706,6 +1541,13 @@
],
"summary": "Test Notifier",
"parameters": [
{
"type": "string",
"description": "Notifier ID",
"name": "id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "URL",
@@ -1903,6 +1745,20 @@
],
"summary": "User Login",
"parameters": [
{
"type": "string",
"example": "admin@admin.com",
"description": "string",
"name": "username",
"in": "formData"
},
{
"type": "string",
"example": "admin",
"description": "string",
"name": "password",
"in": "formData"
},
{
"description": "Login Data",
"name": "payload",
@@ -2112,6 +1968,20 @@
}
}
},
"repo.DocumentOut": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"path": {
"type": "string"
},
"title": {
"type": "string"
}
}
},
"repo.Group": {
"type": "object",
"properties": {
@@ -2172,18 +2042,15 @@
"createdAt": {
"type": "string"
},
"id": {
"type": "string"
"document": {
"$ref": "#/definitions/repo.DocumentOut"
},
"path": {
"id": {
"type": "string"
},
"primary": {
"type": "boolean"
},
"title": {
"type": "string"
},
"type": {
"type": "string"
},
@@ -2234,9 +2101,6 @@
"parentId": {
"type": "string",
"x-nullable": true
},
"quantity": {
"type": "integer"
}
}
},
@@ -2371,9 +2235,6 @@
"soldTo": {
"type": "string"
},
"syncChildItemsLocations": {
"type": "boolean"
},
"updatedAt": {
"type": "string"
},
@@ -2418,10 +2279,6 @@
"archived": {
"type": "boolean"
},
"assetId": {
"type": "string",
"example": "0"
},
"createdAt": {
"type": "string"
},
@@ -2462,10 +2319,6 @@
"quantity": {
"type": "integer"
},
"soldTime": {
"description": "Sale details",
"type": "string"
},
"updatedAt": {
"type": "string"
}
@@ -2580,9 +2433,6 @@
"type": "string",
"maxLength": 255
},
"syncChildItemsLocations": {
"type": "boolean"
},
"warrantyDetails": {
"type": "string"
},
@@ -3068,17 +2918,6 @@
}
}
},
"services.Latest": {
"type": "object",
"properties": {
"date": {
"type": "string"
},
"version": {
"type": "string"
}
}
},
"services.UserRegistration": {
"type": "object",
"properties": {
@@ -3111,12 +2950,6 @@
"health": {
"type": "boolean"
},
"labelPrinting": {
"type": "boolean"
},
"latest": {
"$ref": "#/definitions/services.Latest"
},
"message": {
"type": "string"
},
@@ -3206,15 +3039,13 @@
"type": "object",
"properties": {
"password": {
"type": "string",
"example": "admin"
"type": "string"
},
"stayLoggedIn": {
"type": "boolean"
},
"username": {
"type": "string",
"example": "admin@admin.com"
"type": "string"
}
}
},

View File

@@ -11,6 +11,15 @@ definitions:
symbol:
type: string
type: object
repo.DocumentOut:
properties:
id:
type: string
path:
type: string
title:
type: string
type: object
repo.Group:
properties:
createdAt:
@@ -50,14 +59,12 @@ definitions:
properties:
createdAt:
type: string
document:
$ref: '#/definitions/repo.DocumentOut'
id:
type: string
path:
type: string
primary:
type: boolean
title:
type: string
type:
type: string
updatedAt:
@@ -91,8 +98,6 @@ definitions:
parentId:
type: string
x-nullable: true
quantity:
type: integer
required:
- name
type: object
@@ -183,8 +188,6 @@ definitions:
type: string
soldTo:
type: string
syncChildItemsLocations:
type: boolean
updatedAt:
type: string
warrantyDetails:
@@ -214,9 +217,6 @@ definitions:
properties:
archived:
type: boolean
assetId:
example: "0"
type: string
createdAt:
type: string
description:
@@ -243,9 +243,6 @@ definitions:
type: number
quantity:
type: integer
soldTime:
description: Sale details
type: string
updatedAt:
type: string
type: object
@@ -326,8 +323,6 @@ definitions:
soldTo:
maxLength: 255
type: string
syncChildItemsLocations:
type: boolean
warrantyDetails:
type: string
warrantyExpires:
@@ -652,13 +647,6 @@ definitions:
value:
type: number
type: object
services.Latest:
properties:
date:
type: string
version:
type: string
type: object
services.UserRegistration:
properties:
email:
@@ -680,10 +668,6 @@ definitions:
type: boolean
health:
type: boolean
labelPrinting:
type: boolean
latest:
$ref: '#/definitions/services.Latest'
message:
type: string
title:
@@ -742,12 +726,10 @@ definitions:
v1.LoginForm:
properties:
password:
example: admin
type: string
stayLoggedIn:
type: boolean
username:
example: admin@admin.com
type: string
type: object
v1.TokenResponse:
@@ -770,11 +752,9 @@ definitions:
fields:
type: string
type: object
host: demo.homebox.software
info:
contact:
name: Homebox Team
url: https://discord.homebox.software
name: Don't
description: Track, Manage, and Organize your Things.
title: Homebox API
version: "1.0"
@@ -1152,8 +1132,6 @@ paths:
- Items
/v1/items/{id}/attachments:
post:
consumes:
- multipart/form-data
parameters:
- description: Item ID
in: path
@@ -1170,10 +1148,6 @@ paths:
name: type
required: true
type: string
- description: Is this the primary attachment
in: formData
name: primary
type: boolean
- description: name of the file including extension
in: formData
name: name
@@ -1271,11 +1245,6 @@ paths:
/v1/items/{id}/maintenance:
get:
parameters:
- description: Item ID
in: path
name: id
required: true
type: string
- enum:
- scheduled
- completed
@@ -1303,11 +1272,6 @@ paths:
- Item Maintenance
post:
parameters:
- description: Item ID
in: path
name: id
required: true
type: string
- description: Entry Data
in: body
name: payload
@@ -1394,8 +1358,6 @@ paths:
- Items
/v1/items/import:
post:
consumes:
- multipart/form-data
parameters:
- description: Image to upload
in: formData
@@ -1412,78 +1374,6 @@ paths:
summary: Import Items
tags:
- Items
/v1/labelmaker/assets/{id}:
get:
parameters:
- description: Asset ID
in: path
name: id
required: true
type: string
- description: Print this label, defaults to false
in: query
name: print
type: boolean
produces:
- application/json
responses:
"200":
description: image/png
schema:
type: string
security:
- Bearer: []
summary: Get Asset label
tags:
- Items
/v1/labelmaker/item/{id}:
get:
parameters:
- description: Item ID
in: path
name: id
required: true
type: string
- description: Print this label, defaults to false
in: query
name: print
type: boolean
produces:
- application/json
responses:
"200":
description: image/png
schema:
type: string
security:
- Bearer: []
summary: Get Item label
tags:
- Items
/v1/labelmaker/location/{id}:
get:
parameters:
- description: Location ID
in: path
name: id
required: true
type: string
- description: Print this label, defaults to false
in: query
name: print
type: boolean
produces:
- application/json
responses:
"200":
description: image/png
schema:
type: string
security:
- Bearer: []
summary: Get Location label
tags:
- Locations
/v1/labels:
get:
produces:
@@ -1730,12 +1620,6 @@ paths:
- Maintenance
/v1/maintenance/{id}:
delete:
parameters:
- description: Maintenance ID
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
@@ -1748,11 +1632,6 @@ paths:
- Maintenance
put:
parameters:
- description: Maintenance ID
in: path
name: id
required: true
type: string
- description: Entry Data
in: body
name: payload
@@ -1849,6 +1728,11 @@ paths:
/v1/notifiers/test:
post:
parameters:
- description: Notifier ID
in: path
name: id
required: true
type: string
- description: URL
in: query
name: url
@@ -1932,6 +1816,16 @@ paths:
- application/x-www-form-urlencoded
- application/json
parameters:
- description: string
example: admin@admin.com
in: formData
name: username
type: string
- description: string
example: admin
in: formData
name: password
type: string
- description: Login Data
in: body
name: payload
@@ -2047,9 +1941,6 @@ paths:
summary: Update Account
tags:
- User
schemes:
- https
- http
securityDefinitions:
Bearer:
description: '"Type ''Bearer TOKEN'' to correctly set the API Key"'

View File

@@ -0,0 +1,45 @@
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/migrate"
atlas "ariga.io/atlas/sql/migrate"
_ "ariga.io/atlas/sql/sqlite"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql/schema"
_ "github.com/mattn/go-sqlite3"
)
func main() {
ctx := context.Background()
// Create a local migration directory able to understand Atlas migration file format for replay.
dir, err := atlas.NewLocalDir("internal/data/migrations/migrations")
if err != nil {
log.Fatalf("failed creating atlas migration directory: %v", err)
}
// Migrate diff options.
opts := []schema.MigrateOption{
schema.WithDir(dir), // provide migration directory
schema.WithMigrationMode(schema.ModeReplay), // provide migration mode
schema.WithDialect(dialect.SQLite), // Ent dialect to use
schema.WithFormatter(atlas.DefaultFormatter),
schema.WithDropIndex(true),
schema.WithDropColumn(true),
}
if len(os.Args) != 2 {
log.Fatalln("migration name is required. Use: 'go run -mod=mod ent/migrate/main.go <name>'")
}
// Generate migrations using Atlas support for MySQL (note the Ent dialect option passed above).
err = migrate.NamedDiff(ctx, "sqlite://.data/homebox.migration.db?_fk=1", os.Args[1], opts...)
if err != nil {
log.Fatalf("failed generating migration file: %v", err)
}
fmt.Println("Migration file generated successfully.")
}

View File

@@ -1,11 +0,0 @@
-----BEGIN ENCRYPTED SIGSTORE PRIVATE KEY-----
eyJrZGYiOnsibmFtZSI6InNjcnlwdCIsInBhcmFtcyI6eyJOIjo2NTUzNiwiciI6
OCwicCI6MX0sInNhbHQiOiJ3bmU3TTd2dndlL2FBS1piUEE2QktsdFNzMkhkSk9v
eXlvOTNLMnByRXdJPSJ9LCJjaXBoZXIiOnsibmFtZSI6Im5hY2wvc2VjcmV0Ym94
Iiwibm9uY2UiOiJoOWdIMHRsYk9zMnZIbVBTYk5zaGxBQU5TYUlkcVZoQiJ9LCJj
aXBoZXJ0ZXh0IjoiTERiQk5ac3ZlVnRMbTlQdkRTa2t6bzRrWGExVGRTTEY5VzVO
cGd6M05GNVJLRWlGRmJQRDJDYzhnTWNkRmkrTU8xd2FTUzFGWWdXU3BIdnI3QXZ3
K0tUTXVWLzhSZ1pnOE9ieHNJY2xKSlZldHRLTzdzWXY2aWgxM09iZlVBV0lQcGpS
ZUQ5UmE3WjJwbWd0SkpBdjl2dlk1RGNNeGRKcFFrOEY1UStLZytSbnhLRUd6Z1ZN
MWUxdjF3UGhsOWhVRGRMSFVSTzE5Z0w3aFE9PSJ9
-----END ENCRYPTED SIGSTORE PRIVATE KEY-----

View File

@@ -1,4 +0,0 @@
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2DXKcerPznDayM+rMJ/25w+ubI8g
e3ZTbm07VqLFz6uI2vXqN8X7/72dygtJlUw07FpR0oLXaSia0adaywz1JA==
-----END PUBLIC KEY-----

View File

@@ -3,92 +3,74 @@ module github.com/sysadminsmedia/homebox/backend
go 1.23.0
require (
entgo.io/ent v0.14.4
github.com/ardanlabs/conf/v3 v3.7.2
ariga.io/atlas v0.19.1
entgo.io/ent v0.14.1
github.com/ardanlabs/conf/v3 v3.1.8
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/go-chi/chi/v5 v5.1.0
github.com/go-playground/validator/v10 v10.22.1
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/lib/pq v1.10.9
github.com/mattn/go-sqlite3 v1.14.28
github.com/mattn/go-sqlite3 v1.14.24
github.com/olahol/melody v1.2.1
github.com/pkg/errors v0.9.1
github.com/pressly/goose/v3 v3.24.2
github.com/rs/zerolog v1.34.0
github.com/shirou/gopsutil/v4 v4.25.3
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/stretchr/testify v1.10.0
github.com/rs/zerolog v1.33.0
github.com/stretchr/testify v1.9.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
github.com/zeebo/blake3 v0.2.4
golang.org/x/crypto v0.37.0
modernc.org/sqlite v1.37.0
)
require (
ariga.io/atlas v0.32.0 // indirect
github.com/ebitengine/purego v0.8.2 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect
github.com/mfridman/interpolate v0.0.2 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/sethvargo/go-retry v0.3.0 // indirect
github.com/tklauser/go-sysconf v0.3.15 // indirect
github.com/tklauser/numcpus v0.10.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
github.com/zclconf/go-cty-yaml v1.1.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
github.com/swaggo/swag v1.16.3
github.com/yeqown/go-qrcode/v2 v2.2.4
github.com/yeqown/go-qrcode/writer/standard v1.2.4
golang.org/x/crypto v0.28.0
modernc.org/sqlite v1.33.1
)
require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/agext/levenshtein v1.2.3 // 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/dustin/go-humanize v1.0.1 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/fogleman/gg v1.3.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.9 // 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/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/go-openapi/inflect v0.19.0 // indirect
github.com/go-openapi/jsonpointer v0.20.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/spec v0.20.9 // indirect
github.com/go-openapi/swag v0.22.4 // 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
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/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/gorilla/websocket v1.5.1 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/hashicorp/hcl/v2 v2.19.1 // indirect
github.com/josharian/intern v1.0.0 // 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/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // 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/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/swaggo/files/v2 v2.0.2 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/swaggo/files/v2 v2.0.0 // indirect
github.com/yeqown/reedsolomon v1.0.0 // indirect
github.com/zclconf/go-cty v1.16.2 // indirect
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
golang.org/x/image v0.26.0
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
github.com/zclconf/go-cty v1.14.1 // indirect
golang.org/x/image v0.18.0 // indirect
golang.org/x/mod v0.20.0 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
golang.org/x/tools v0.24.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.63.0 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.10.0 // indirect
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
modernc.org/libc v1.55.3 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.8.0 // indirect
modernc.org/strutil v1.2.0 // indirect
modernc.org/token v1.1.0 // indirect
)

View File

@@ -1,7 +1,11 @@
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=
ariga.io/atlas v0.19.1 h1:QzBHkakwzEhmPWOzNhw8Yr/Bbicj6Iq5hwEoNI/Jr9A=
ariga.io/atlas v0.19.1/go.mod h1:VPlcXdd4w2KqKnH54yEZcry79UAhpaWaxEsmn5JRNoE=
ariga.io/atlas v0.28.0 h1:qmn9tUyJypJkIw+X3ECUwDtkMTiFupgstHbjRN4xGH0=
ariga.io/atlas v0.28.0/go.mod h1:LOOp18LCL9r+VifvVlJqgYJwYl271rrXD9/wIyzJ8sw=
entgo.io/ent v0.12.5 h1:KREM5E4CSoej4zeGa88Ou/gfturAnpUv0mzAjch1sj4=
entgo.io/ent v0.12.5/go.mod h1:Y3JVAjtlIk8xVZYSn3t3mf8xlZIn5SAOXZQxD6kKI+Q=
entgo.io/ent v0.14.1 h1:fUERL506Pqr92EPHJqr8EYxbPioflJo6PudkrEA8a/s=
entgo.io/ent v0.14.1/go.mod h1:MH6XLG0KXpkcDQhKiHfANZSzR55TJyPL5IGNpI8wpco=
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=
@@ -10,50 +14,54 @@ github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7l
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
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.2 h1:s2VBuDJM6OQfR0erDuopiZ+dHUQVqGxZeLrTsls03dw=
github.com/ardanlabs/conf/v3 v3.7.2/go.mod h1:XlL9P0quWP4m1weOVFmlezabinbZLI05niDof/+Ochk=
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/ardanlabs/conf/v3 v3.1.8 h1:r0KUV9/Hni5XdeWR2+A1BiedIDnry5CjezoqgJ0rnFQ=
github.com/ardanlabs/conf/v3 v3.1.8/go.mod h1:OIi6NK95fj8jKFPdZ/UmcPlY37JBg99hdP9o5XmNK9c=
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/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/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/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/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
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/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
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.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
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/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
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-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
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-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/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ=
github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA=
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/spec v0.20.9 h1:xnlYNQAwKd2VQRRfwTEI0DcK+2cbuvI/0c7jx3gA8/8=
github.com/go-openapi/spec v0.20.9/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/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
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-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
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=
@@ -65,55 +73,60 @@ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF0
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/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
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/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hashicorp/hcl/v2 v2.19.1 h1://i05Jqznmb2EXqa39Nsvyan2o5XyMowW5fnCKW5RPI=
github.com/hashicorp/hcl/v2 v2.19.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE=
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/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
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/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
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/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr325bN2FD2ISlRRztXibcX6e8f5FR5Dc=
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
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/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.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
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-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-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg=
github.com/mattn/go-sqlite3 v1.14.23 h1:gbShiuAP1W5j9UOksQ06aiiqPMxYecovVGwmTxWtuw0=
github.com/mattn/go-sqlite3 v1.14.23/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
github.com/mattn/go-sqlite3 v1.14.24/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/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
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/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
@@ -126,113 +139,105 @@ 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/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/pressly/goose/v3 v3.24.2 h1:c/ie0Gm8rnIVKvnDQ/scHErv46jrDv9b4I0WRcFJzYU=
github.com/pressly/goose/v3 v3.24.2/go.mod h1:kjefwFB0eR4w30Td2Gj2Mznyw94vSP+2jJYkOVNbD1k=
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.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
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/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE=
github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas=
github.com/shirou/gopsutil/v4 v4.25.3 h1:SeA68lsu8gLggyMbmCn8cmp97V1TI9ld9sVzAUcKcKE=
github.com/shirou/gopsutil/v4 v4.25.3/go.mod h1:xbuxyoZj+UsgnZrENu3lQivsngRR5BdjbJwf2fv4szA=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
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.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
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/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw=
github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM=
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/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
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/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg=
github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk=
github.com/yeqown/go-qrcode/v2 v2.2.4 h1:cXdYlrhzHzVAnJHiwr/T6lAUmS9MtEStjEZBjArrvnc=
github.com/yeqown/go-qrcode/v2 v2.2.4/go.mod h1:uHpt9CM0V1HeXLz+Wg5MN50/sI/fQhfkZlOM+cOTHxw=
github.com/yeqown/go-qrcode/writer/standard v1.2.4 h1:41e/aLr1AMVWlug6oUMkDg2r0+dv5ofB7UaTkekKZBc=
github.com/yeqown/go-qrcode/writer/standard v1.2.4/go.mod h1:H8nLSGYUWBpNyBPjDcJzAanMzYBBYMFtrU2lwoSRn+k=
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/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
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=
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
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-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
github.com/zclconf/go-cty v1.14.1 h1:t9fyA35fwjjUMcmL5hLER+e/rEPqrbCK1/OSE4SI9KA=
github.com/zclconf/go-cty v1.14.1/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8=
github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
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/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
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/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.26.0 h1:QMYvbVduUGH0rrO+5mqF/PSPPRZNpRtg2CLELy7vUpA=
modernc.org/cc/v4 v4.26.0/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.26.0 h1:gVzXaDzGeBYJ2uXTOpR8FR7OlksDOe9jxnjhIKCsiTc=
modernc.org/ccgo/v4 v4.26.0/go.mod h1:Sem8f7TFUtVXkG2fiaChQtyyfkqhJBg/zjEJBkmuAVY=
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.63.0 h1:wKzb61wOGCzgahQBORb1b0dZonh8Ufzl/7r4Yf1D5YA=
modernc.org/libc v1.63.0/go.mod h1:wDzH1mgz1wUIEwottFt++POjGRO9sgyQKrpXaz3x89E=
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.10.0 h1:fzumd51yQ1DxcOxSO+S6X7+QTuVU+n8/Aj7swYjFfC4=
modernc.org/memory v1.10.0/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/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U=
modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
modernc.org/sqlite v1.33.0 h1:WWkA/T2G17okiLGgKAj4/RMIvgyMT19yQ038160IeYk=
modernc.org/sqlite v1.33.0/go.mod h1:9uQ9hF/pCZoYZK73D/ud5Z7cIRIILSZI8NdIemVMTX8=
modernc.org/sqlite v1.33.1 h1:trb6Z3YYoeM9eDL1O8do81kP+0ejv+YzgyFo+Gwy0nM=
modernc.org/sqlite v1.33.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=

File diff suppressed because it is too large Load Diff

View File

@@ -61,7 +61,7 @@ func New(repos *repo.AllRepos, opts ...OptionsFunc) *AllServices {
repo: repos,
autoIncrementAssetID: options.autoIncrementAssetID,
},
BackgroundService: &BackgroundService{repos, Latest{}},
BackgroundService: &BackgroundService{repos},
Currencies: currencies.NewCurrencyService(options.currencies),
}
}

View File

@@ -50,7 +50,7 @@ func bootstrap() {
}
func MainNoExit(m *testing.M) int {
client, err := ent.Open("sqlite3", "file:ent?mode=memory&cache=shared&_fk=1&_time_format=sqlite")
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)
}

View File

@@ -2,9 +2,6 @@ package services
import (
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
@@ -14,13 +11,8 @@ import (
"github.com/sysadminsmedia/homebox/backend/internal/data/types"
)
type Latest struct {
Version string `json:"version"`
Date string `json:"date"`
}
type BackgroundService struct {
repos *repo.AllRepos
latest Latest
repos *repo.AllRepos
}
func (svc *BackgroundService) SendNotifiersToday(ctx context.Context) error {
@@ -88,51 +80,19 @@ func (svc *BackgroundService) SendNotifiersToday(ctx context.Context) error {
return nil
}
func (svc *BackgroundService) GetLatestGithubRelease(ctx context.Context) error {
url := "https://api.github.com/repos/sysadminsmedia/homebox/releases/latest"
func (svc *BackgroundService) UpdateLocales(ctx context.Context) error {
log.Debug().Msg("updating locales")
// fetch list of locales from github
// is it worth checking if any changes have been made?
// download locales overwriting files in static/public/locales
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return fmt.Errorf("failed to create latest version request: %w", err)
}
// curl -H "Accept: application/vnd.github.v3+json" \
// -H "If-Modified-Since: Thu, 31 Oct 2024 09:59:02 GMT" \
// -o /dev/null -s -w "%{http_code}\n" \
// https://api.github.com/repos/sysadminsmedia/homebox/contents/frontend/locales
// keep track of last modified date
req.Header.Set("User-Agent", "Homebox-Version-Checker")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("failed to make latest version request: %w", err)
}
defer func() {
err := resp.Body.Close()
if err != nil {
log.Printf("error closing latest version response body: %v", err)
}
}()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("latest version unexpected status code: %d", resp.StatusCode)
}
// ignoring fields that are not relevant
type Release struct {
ReleaseVersion string `json:"tag_name"`
PublishedAt time.Time `json:"published_at"`
}
var release Release
if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
return fmt.Errorf("failed to decode latest version response: %w", err)
}
svc.latest = Latest{
Version: release.ReleaseVersion,
Date: release.PublishedAt.String(),
}
return nil
}
func (svc *BackgroundService) GetLatestVersion() Latest {
return svc.latest
}
}

View File

@@ -2,21 +2,23 @@ package services
import (
"context"
"io"
"os"
"github.com/google/uuid"
"github.com/rs/zerolog/log"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/attachment"
"github.com/sysadminsmedia/homebox/backend/internal/data/repo"
"io"
)
func (svc *ItemService) AttachmentPath(ctx context.Context, attachmentID uuid.UUID) (*ent.Attachment, error) {
func (svc *ItemService) AttachmentPath(ctx context.Context, attachmentID uuid.UUID) (*ent.Document, error) {
attachment, err := svc.repo.Attachments.Get(ctx, attachmentID)
if err != nil {
return nil, err
}
return attachment, nil
return attachment.Edges.Document, nil
}
func (svc *ItemService) AttachmentUpdate(ctx Context, itemID uuid.UUID, data *repo.ItemAttachmentUpdate) (repo.ItemOut, error) {
@@ -27,8 +29,8 @@ func (svc *ItemService) AttachmentUpdate(ctx Context, itemID uuid.UUID, data *re
}
// Update Document
attDoc := attachment
_, err = svc.repo.Attachments.Rename(ctx, attDoc.ID, data.Title)
attDoc := attachment.Edges.Document
_, err = svc.repo.Docs.Rename(ctx, attDoc.ID, data.Title)
if err != nil {
return repo.ItemOut{}, err
}
@@ -39,28 +41,50 @@ func (svc *ItemService) AttachmentUpdate(ctx Context, itemID uuid.UUID, data *re
// AttachmentAdd adds an attachment to an item by creating an entry in the Documents table and linking it to the Attachment
// Table and Items table. The file provided via the reader is stored on the file system based on the provided
// relative path during construction of the service.
func (svc *ItemService) AttachmentAdd(ctx Context, itemID uuid.UUID, filename string, attachmentType attachment.Type, primary bool, file io.Reader) (repo.ItemOut, error) {
func (svc *ItemService) AttachmentAdd(ctx Context, itemID uuid.UUID, filename string, attachmentType attachment.Type, file io.Reader) (repo.ItemOut, error) {
// Get the Item
_, err := svc.repo.Items.GetOneByGroup(ctx, ctx.GID, itemID)
if err != nil {
return repo.ItemOut{}, err
}
// Create the document
doc, err := svc.repo.Docs.Create(ctx, ctx.GID, repo.DocumentCreate{Title: filename, Content: file})
if err != nil {
log.Err(err).Msg("failed to create document")
return repo.ItemOut{}, err
}
// Create the attachment
_, err = svc.repo.Attachments.Create(ctx, itemID, repo.ItemCreateAttachment{Title: filename, Content: file}, attachmentType, primary)
_, err = svc.repo.Attachments.Create(ctx, itemID, doc.ID, attachmentType)
if err != nil {
log.Err(err).Msg("failed to create attachment")
return repo.ItemOut{}, err
}
return svc.repo.Items.GetOneByGroup(ctx, ctx.GID, itemID)
}
func (svc *ItemService) AttachmentDelete(ctx context.Context, gid, itemID, attachmentID uuid.UUID) error {
// Delete the attachment
err := svc.repo.Attachments.Delete(ctx, attachmentID)
// Get the Item
_, err := svc.repo.Items.GetOneByGroup(ctx, gid, itemID)
if err != nil {
return err
}
attachment, err := svc.repo.Attachments.Get(ctx, attachmentID)
if err != nil {
return err
}
// Delete the attachment
err = svc.repo.Attachments.Delete(ctx, attachmentID)
if err != nil {
return err
}
// Remove File
err = os.Remove(attachment.Edges.Document.Path)
return err
}

View File

@@ -45,12 +45,12 @@ func TestItemService_AddAttachment(t *testing.T) {
reader := strings.NewReader(contents)
// Setup
afterAttachment, err := svc.AttachmentAdd(tCtx, itm.ID, "testfile.txt", "attachment", false, reader)
afterAttachment, err := svc.AttachmentAdd(tCtx, itm.ID, "testfile.txt", "attachment", reader)
require.NoError(t, err)
assert.NotNil(t, afterAttachment)
// Check that the file exists
storedPath := afterAttachment.Attachments[0].Path
storedPath := afterAttachment.Attachments[0].Document.Path
// {root}/{group}/{item}/{attachment}
assert.Equal(t, path.Join(temp, "homebox", tGroup.ID.String(), "documents"), path.Dir(storedPath))

View File

@@ -11,6 +11,7 @@ import (
"entgo.io/ent/dialect/sql"
"github.com/google/uuid"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/attachment"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/document"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/item"
)
@@ -27,24 +28,23 @@ type Attachment struct {
Type attachment.Type `json:"type,omitempty"`
// Primary holds the value of the "primary" field.
Primary bool `json:"primary,omitempty"`
// Title holds the value of the "title" field.
Title string `json:"title,omitempty"`
// Path holds the value of the "path" field.
Path string `json:"path,omitempty"`
// Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the AttachmentQuery when eager-loading is set.
Edges AttachmentEdges `json:"edges"`
item_attachments *uuid.UUID
selectValues sql.SelectValues
Edges AttachmentEdges `json:"edges"`
document_attachments *uuid.UUID
item_attachments *uuid.UUID
selectValues sql.SelectValues
}
// AttachmentEdges holds the relations/edges for other nodes in the graph.
type AttachmentEdges struct {
// Item holds the value of the item edge.
Item *Item `json:"item,omitempty"`
// 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
loadedTypes [2]bool
}
// ItemOrErr returns the Item value or an error if the edge
@@ -58,6 +58,17 @@ func (e AttachmentEdges) ItemOrErr() (*Item, error) {
return nil, &NotLoadedError{edge: "item"}
}
// 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 AttachmentEdges) DocumentOrErr() (*Document, error) {
if e.Document != nil {
return e.Document, nil
} else if e.loadedTypes[1] {
return nil, &NotFoundError{label: document.Label}
}
return nil, &NotLoadedError{edge: "document"}
}
// scanValues returns the types for scanning values from sql.Rows.
func (*Attachment) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
@@ -65,13 +76,15 @@ func (*Attachment) scanValues(columns []string) ([]any, error) {
switch columns[i] {
case attachment.FieldPrimary:
values[i] = new(sql.NullBool)
case attachment.FieldType, attachment.FieldTitle, attachment.FieldPath:
case attachment.FieldType:
values[i] = new(sql.NullString)
case attachment.FieldCreatedAt, attachment.FieldUpdatedAt:
values[i] = new(sql.NullTime)
case attachment.FieldID:
values[i] = new(uuid.UUID)
case attachment.ForeignKeys[0]: // item_attachments
case attachment.ForeignKeys[0]: // document_attachments
values[i] = &sql.NullScanner{S: new(uuid.UUID)}
case attachment.ForeignKeys[1]: // item_attachments
values[i] = &sql.NullScanner{S: new(uuid.UUID)}
default:
values[i] = new(sql.UnknownType)
@@ -118,19 +131,14 @@ func (a *Attachment) assignValues(columns []string, values []any) error {
} else if value.Valid {
a.Primary = value.Bool
}
case attachment.FieldTitle:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field title", values[i])
} else if value.Valid {
a.Title = value.String
}
case attachment.FieldPath:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field path", values[i])
} else if value.Valid {
a.Path = value.String
}
case attachment.ForeignKeys[0]:
if value, ok := values[i].(*sql.NullScanner); !ok {
return fmt.Errorf("unexpected type %T for field document_attachments", values[i])
} else if value.Valid {
a.document_attachments = new(uuid.UUID)
*a.document_attachments = *value.S.(*uuid.UUID)
}
case attachment.ForeignKeys[1]:
if value, ok := values[i].(*sql.NullScanner); !ok {
return fmt.Errorf("unexpected type %T for field item_attachments", values[i])
} else if value.Valid {
@@ -155,6 +163,11 @@ func (a *Attachment) QueryItem() *ItemQuery {
return NewAttachmentClient(a.config).QueryItem(a)
}
// QueryDocument queries the "document" edge of the Attachment entity.
func (a *Attachment) QueryDocument() *DocumentQuery {
return NewAttachmentClient(a.config).QueryDocument(a)
}
// Update returns a builder for updating this Attachment.
// Note that you need to call Attachment.Unwrap() before calling this method if this Attachment
// was returned from a transaction, and the transaction was committed or rolled back.
@@ -189,12 +202,6 @@ func (a *Attachment) String() string {
builder.WriteString(", ")
builder.WriteString("primary=")
builder.WriteString(fmt.Sprintf("%v", a.Primary))
builder.WriteString(", ")
builder.WriteString("title=")
builder.WriteString(a.Title)
builder.WriteString(", ")
builder.WriteString("path=")
builder.WriteString(a.Path)
builder.WriteByte(')')
return builder.String()
}

View File

@@ -24,12 +24,10 @@ const (
FieldType = "type"
// FieldPrimary holds the string denoting the primary field in the database.
FieldPrimary = "primary"
// FieldTitle holds the string denoting the title field in the database.
FieldTitle = "title"
// FieldPath holds the string denoting the path field in the database.
FieldPath = "path"
// EdgeItem holds the string denoting the item edge name in mutations.
EdgeItem = "item"
// EdgeDocument holds the string denoting the document edge name in mutations.
EdgeDocument = "document"
// Table holds the table name of the attachment in the database.
Table = "attachments"
// ItemTable is the table that holds the item relation/edge.
@@ -39,6 +37,13 @@ const (
ItemInverseTable = "items"
// ItemColumn is the table column denoting the item relation/edge.
ItemColumn = "item_attachments"
// DocumentTable is the table that holds the document relation/edge.
DocumentTable = "attachments"
// 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_attachments"
)
// Columns holds all SQL columns for attachment fields.
@@ -48,13 +53,12 @@ var Columns = []string{
FieldUpdatedAt,
FieldType,
FieldPrimary,
FieldTitle,
FieldPath,
}
// ForeignKeys holds the SQL foreign-keys that are owned by the "attachments"
// table and are not defined as standalone fields in the schema.
var ForeignKeys = []string{
"document_attachments",
"item_attachments",
}
@@ -82,10 +86,6 @@ var (
UpdateDefaultUpdatedAt func() time.Time
// DefaultPrimary holds the default value on creation for the "primary" field.
DefaultPrimary bool
// DefaultTitle holds the default value on creation for the "title" field.
DefaultTitle string
// DefaultPath holds the default value on creation for the "path" field.
DefaultPath string
// DefaultID holds the default value on creation for the "id" field.
DefaultID func() uuid.UUID
)
@@ -147,22 +147,19 @@ func ByPrimary(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPrimary, opts...).ToFunc()
}
// ByTitle orders the results by the title field.
func ByTitle(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldTitle, opts...).ToFunc()
}
// ByPath orders the results by the path field.
func ByPath(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPath, opts...).ToFunc()
}
// ByItemField orders the results by item field.
func ByItemField(field string, opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newItemStep(), sql.OrderByField(field, opts...))
}
}
// ByDocumentField orders the results by document field.
func ByDocumentField(field string, opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newDocumentStep(), sql.OrderByField(field, opts...))
}
}
func newItemStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
@@ -170,3 +167,10 @@ func newItemStep() *sqlgraph.Step {
sqlgraph.Edge(sqlgraph.M2O, true, ItemTable, ItemColumn),
)
}
func newDocumentStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(DocumentInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, DocumentTable, DocumentColumn),
)
}

View File

@@ -71,16 +71,6 @@ func Primary(v bool) predicate.Attachment {
return predicate.Attachment(sql.FieldEQ(FieldPrimary, v))
}
// Title applies equality check predicate on the "title" field. It's identical to TitleEQ.
func Title(v string) predicate.Attachment {
return predicate.Attachment(sql.FieldEQ(FieldTitle, v))
}
// Path applies equality check predicate on the "path" field. It's identical to PathEQ.
func Path(v string) predicate.Attachment {
return predicate.Attachment(sql.FieldEQ(FieldPath, v))
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
func CreatedAtEQ(v time.Time) predicate.Attachment {
return predicate.Attachment(sql.FieldEQ(FieldCreatedAt, v))
@@ -191,136 +181,6 @@ func PrimaryNEQ(v bool) predicate.Attachment {
return predicate.Attachment(sql.FieldNEQ(FieldPrimary, v))
}
// TitleEQ applies the EQ predicate on the "title" field.
func TitleEQ(v string) predicate.Attachment {
return predicate.Attachment(sql.FieldEQ(FieldTitle, v))
}
// TitleNEQ applies the NEQ predicate on the "title" field.
func TitleNEQ(v string) predicate.Attachment {
return predicate.Attachment(sql.FieldNEQ(FieldTitle, v))
}
// TitleIn applies the In predicate on the "title" field.
func TitleIn(vs ...string) predicate.Attachment {
return predicate.Attachment(sql.FieldIn(FieldTitle, vs...))
}
// TitleNotIn applies the NotIn predicate on the "title" field.
func TitleNotIn(vs ...string) predicate.Attachment {
return predicate.Attachment(sql.FieldNotIn(FieldTitle, vs...))
}
// TitleGT applies the GT predicate on the "title" field.
func TitleGT(v string) predicate.Attachment {
return predicate.Attachment(sql.FieldGT(FieldTitle, v))
}
// TitleGTE applies the GTE predicate on the "title" field.
func TitleGTE(v string) predicate.Attachment {
return predicate.Attachment(sql.FieldGTE(FieldTitle, v))
}
// TitleLT applies the LT predicate on the "title" field.
func TitleLT(v string) predicate.Attachment {
return predicate.Attachment(sql.FieldLT(FieldTitle, v))
}
// TitleLTE applies the LTE predicate on the "title" field.
func TitleLTE(v string) predicate.Attachment {
return predicate.Attachment(sql.FieldLTE(FieldTitle, v))
}
// TitleContains applies the Contains predicate on the "title" field.
func TitleContains(v string) predicate.Attachment {
return predicate.Attachment(sql.FieldContains(FieldTitle, v))
}
// TitleHasPrefix applies the HasPrefix predicate on the "title" field.
func TitleHasPrefix(v string) predicate.Attachment {
return predicate.Attachment(sql.FieldHasPrefix(FieldTitle, v))
}
// TitleHasSuffix applies the HasSuffix predicate on the "title" field.
func TitleHasSuffix(v string) predicate.Attachment {
return predicate.Attachment(sql.FieldHasSuffix(FieldTitle, v))
}
// TitleEqualFold applies the EqualFold predicate on the "title" field.
func TitleEqualFold(v string) predicate.Attachment {
return predicate.Attachment(sql.FieldEqualFold(FieldTitle, v))
}
// TitleContainsFold applies the ContainsFold predicate on the "title" field.
func TitleContainsFold(v string) predicate.Attachment {
return predicate.Attachment(sql.FieldContainsFold(FieldTitle, v))
}
// PathEQ applies the EQ predicate on the "path" field.
func PathEQ(v string) predicate.Attachment {
return predicate.Attachment(sql.FieldEQ(FieldPath, v))
}
// PathNEQ applies the NEQ predicate on the "path" field.
func PathNEQ(v string) predicate.Attachment {
return predicate.Attachment(sql.FieldNEQ(FieldPath, v))
}
// PathIn applies the In predicate on the "path" field.
func PathIn(vs ...string) predicate.Attachment {
return predicate.Attachment(sql.FieldIn(FieldPath, vs...))
}
// PathNotIn applies the NotIn predicate on the "path" field.
func PathNotIn(vs ...string) predicate.Attachment {
return predicate.Attachment(sql.FieldNotIn(FieldPath, vs...))
}
// PathGT applies the GT predicate on the "path" field.
func PathGT(v string) predicate.Attachment {
return predicate.Attachment(sql.FieldGT(FieldPath, v))
}
// PathGTE applies the GTE predicate on the "path" field.
func PathGTE(v string) predicate.Attachment {
return predicate.Attachment(sql.FieldGTE(FieldPath, v))
}
// PathLT applies the LT predicate on the "path" field.
func PathLT(v string) predicate.Attachment {
return predicate.Attachment(sql.FieldLT(FieldPath, v))
}
// PathLTE applies the LTE predicate on the "path" field.
func PathLTE(v string) predicate.Attachment {
return predicate.Attachment(sql.FieldLTE(FieldPath, v))
}
// PathContains applies the Contains predicate on the "path" field.
func PathContains(v string) predicate.Attachment {
return predicate.Attachment(sql.FieldContains(FieldPath, v))
}
// PathHasPrefix applies the HasPrefix predicate on the "path" field.
func PathHasPrefix(v string) predicate.Attachment {
return predicate.Attachment(sql.FieldHasPrefix(FieldPath, v))
}
// PathHasSuffix applies the HasSuffix predicate on the "path" field.
func PathHasSuffix(v string) predicate.Attachment {
return predicate.Attachment(sql.FieldHasSuffix(FieldPath, v))
}
// PathEqualFold applies the EqualFold predicate on the "path" field.
func PathEqualFold(v string) predicate.Attachment {
return predicate.Attachment(sql.FieldEqualFold(FieldPath, v))
}
// PathContainsFold applies the ContainsFold predicate on the "path" field.
func PathContainsFold(v string) predicate.Attachment {
return predicate.Attachment(sql.FieldContainsFold(FieldPath, v))
}
// HasItem applies the HasEdge predicate on the "item" edge.
func HasItem() predicate.Attachment {
return predicate.Attachment(func(s *sql.Selector) {
@@ -344,6 +204,29 @@ func HasItemWith(preds ...predicate.Item) predicate.Attachment {
})
}
// HasDocument applies the HasEdge predicate on the "document" edge.
func HasDocument() predicate.Attachment {
return predicate.Attachment(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, 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.Attachment {
return predicate.Attachment(func(s *sql.Selector) {
step := newDocumentStep()
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.Attachment) predicate.Attachment {
return predicate.Attachment(sql.AndPredicates(predicates...))

View File

@@ -12,6 +12,7 @@ import (
"entgo.io/ent/schema/field"
"github.com/google/uuid"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/attachment"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/document"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/item"
)
@@ -78,34 +79,6 @@ func (ac *AttachmentCreate) SetNillablePrimary(b *bool) *AttachmentCreate {
return ac
}
// SetTitle sets the "title" field.
func (ac *AttachmentCreate) SetTitle(s string) *AttachmentCreate {
ac.mutation.SetTitle(s)
return ac
}
// SetNillableTitle sets the "title" field if the given value is not nil.
func (ac *AttachmentCreate) SetNillableTitle(s *string) *AttachmentCreate {
if s != nil {
ac.SetTitle(*s)
}
return ac
}
// SetPath sets the "path" field.
func (ac *AttachmentCreate) SetPath(s string) *AttachmentCreate {
ac.mutation.SetPath(s)
return ac
}
// SetNillablePath sets the "path" field if the given value is not nil.
func (ac *AttachmentCreate) SetNillablePath(s *string) *AttachmentCreate {
if s != nil {
ac.SetPath(*s)
}
return ac
}
// SetID sets the "id" field.
func (ac *AttachmentCreate) SetID(u uuid.UUID) *AttachmentCreate {
ac.mutation.SetID(u)
@@ -131,6 +104,17 @@ func (ac *AttachmentCreate) SetItem(i *Item) *AttachmentCreate {
return ac.SetItemID(i.ID)
}
// SetDocumentID sets the "document" edge to the Document entity by ID.
func (ac *AttachmentCreate) SetDocumentID(id uuid.UUID) *AttachmentCreate {
ac.mutation.SetDocumentID(id)
return ac
}
// SetDocument sets the "document" edge to the Document entity.
func (ac *AttachmentCreate) SetDocument(d *Document) *AttachmentCreate {
return ac.SetDocumentID(d.ID)
}
// Mutation returns the AttachmentMutation object of the builder.
func (ac *AttachmentCreate) Mutation() *AttachmentMutation {
return ac.mutation
@@ -182,14 +166,6 @@ func (ac *AttachmentCreate) defaults() {
v := attachment.DefaultPrimary
ac.mutation.SetPrimary(v)
}
if _, ok := ac.mutation.Title(); !ok {
v := attachment.DefaultTitle
ac.mutation.SetTitle(v)
}
if _, ok := ac.mutation.Path(); !ok {
v := attachment.DefaultPath
ac.mutation.SetPath(v)
}
if _, ok := ac.mutation.ID(); !ok {
v := attachment.DefaultID()
ac.mutation.SetID(v)
@@ -215,15 +191,12 @@ func (ac *AttachmentCreate) check() error {
if _, ok := ac.mutation.Primary(); !ok {
return &ValidationError{Name: "primary", err: errors.New(`ent: missing required field "Attachment.primary"`)}
}
if _, ok := ac.mutation.Title(); !ok {
return &ValidationError{Name: "title", err: errors.New(`ent: missing required field "Attachment.title"`)}
}
if _, ok := ac.mutation.Path(); !ok {
return &ValidationError{Name: "path", err: errors.New(`ent: missing required field "Attachment.path"`)}
}
if len(ac.mutation.ItemIDs()) == 0 {
return &ValidationError{Name: "item", err: errors.New(`ent: missing required edge "Attachment.item"`)}
}
if len(ac.mutation.DocumentIDs()) == 0 {
return &ValidationError{Name: "document", err: errors.New(`ent: missing required edge "Attachment.document"`)}
}
return nil
}
@@ -275,14 +248,6 @@ func (ac *AttachmentCreate) createSpec() (*Attachment, *sqlgraph.CreateSpec) {
_spec.SetField(attachment.FieldPrimary, field.TypeBool, value)
_node.Primary = value
}
if value, ok := ac.mutation.Title(); ok {
_spec.SetField(attachment.FieldTitle, field.TypeString, value)
_node.Title = value
}
if value, ok := ac.mutation.Path(); ok {
_spec.SetField(attachment.FieldPath, field.TypeString, value)
_node.Path = value
}
if nodes := ac.mutation.ItemIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
@@ -300,6 +265,23 @@ func (ac *AttachmentCreate) createSpec() (*Attachment, *sqlgraph.CreateSpec) {
_node.item_attachments = &nodes[0]
_spec.Edges = append(_spec.Edges, edge)
}
if nodes := ac.mutation.DocumentIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: attachment.DocumentTable,
Columns: []string{attachment.DocumentColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_node.document_attachments = &nodes[0]
_spec.Edges = append(_spec.Edges, edge)
}
return _node, _spec
}

View File

@@ -13,6 +13,7 @@ import (
"entgo.io/ent/schema/field"
"github.com/google/uuid"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/attachment"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/document"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/item"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/predicate"
)
@@ -20,12 +21,13 @@ import (
// AttachmentQuery is the builder for querying Attachment entities.
type AttachmentQuery struct {
config
ctx *QueryContext
order []attachment.OrderOption
inters []Interceptor
predicates []predicate.Attachment
withItem *ItemQuery
withFKs bool
ctx *QueryContext
order []attachment.OrderOption
inters []Interceptor
predicates []predicate.Attachment
withItem *ItemQuery
withDocument *DocumentQuery
withFKs bool
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
@@ -84,6 +86,28 @@ func (aq *AttachmentQuery) QueryItem() *ItemQuery {
return query
}
// QueryDocument chains the current query on the "document" edge.
func (aq *AttachmentQuery) QueryDocument() *DocumentQuery {
query := (&DocumentClient{config: aq.config}).Query()
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := aq.prepareQuery(ctx); err != nil {
return nil, err
}
selector := aq.sqlQuery(ctx)
if err := selector.Err(); err != nil {
return nil, err
}
step := sqlgraph.NewStep(
sqlgraph.From(attachment.Table, attachment.FieldID, selector),
sqlgraph.To(document.Table, document.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, attachment.DocumentTable, attachment.DocumentColumn),
)
fromU = sqlgraph.SetNeighbors(aq.driver.Dialect(), step)
return fromU, nil
}
return query
}
// First returns the first Attachment entity from the query.
// Returns a *NotFoundError when no Attachment was found.
func (aq *AttachmentQuery) First(ctx context.Context) (*Attachment, error) {
@@ -271,12 +295,13 @@ func (aq *AttachmentQuery) Clone() *AttachmentQuery {
return nil
}
return &AttachmentQuery{
config: aq.config,
ctx: aq.ctx.Clone(),
order: append([]attachment.OrderOption{}, aq.order...),
inters: append([]Interceptor{}, aq.inters...),
predicates: append([]predicate.Attachment{}, aq.predicates...),
withItem: aq.withItem.Clone(),
config: aq.config,
ctx: aq.ctx.Clone(),
order: append([]attachment.OrderOption{}, aq.order...),
inters: append([]Interceptor{}, aq.inters...),
predicates: append([]predicate.Attachment{}, aq.predicates...),
withItem: aq.withItem.Clone(),
withDocument: aq.withDocument.Clone(),
// clone intermediate query.
sql: aq.sql.Clone(),
path: aq.path,
@@ -294,6 +319,17 @@ func (aq *AttachmentQuery) WithItem(opts ...func(*ItemQuery)) *AttachmentQuery {
return aq
}
// WithDocument tells the query-builder to eager-load the nodes that are connected to
// the "document" edge. The optional arguments are used to configure the query builder of the edge.
func (aq *AttachmentQuery) WithDocument(opts ...func(*DocumentQuery)) *AttachmentQuery {
query := (&DocumentClient{config: aq.config}).Query()
for _, opt := range opts {
opt(query)
}
aq.withDocument = query
return aq
}
// 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.
//
@@ -373,11 +409,12 @@ func (aq *AttachmentQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*A
nodes = []*Attachment{}
withFKs = aq.withFKs
_spec = aq.querySpec()
loadedTypes = [1]bool{
loadedTypes = [2]bool{
aq.withItem != nil,
aq.withDocument != nil,
}
)
if aq.withItem != nil {
if aq.withItem != nil || aq.withDocument != nil {
withFKs = true
}
if withFKs {
@@ -407,6 +444,12 @@ func (aq *AttachmentQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*A
return nil, err
}
}
if query := aq.withDocument; query != nil {
if err := aq.loadDocument(ctx, query, nodes, nil,
func(n *Attachment, e *Document) { n.Edges.Document = e }); err != nil {
return nil, err
}
}
return nodes, nil
}
@@ -442,6 +485,38 @@ func (aq *AttachmentQuery) loadItem(ctx context.Context, query *ItemQuery, nodes
}
return nil
}
func (aq *AttachmentQuery) loadDocument(ctx context.Context, query *DocumentQuery, nodes []*Attachment, init func(*Attachment), assign func(*Attachment, *Document)) error {
ids := make([]uuid.UUID, 0, len(nodes))
nodeids := make(map[uuid.UUID][]*Attachment)
for i := range nodes {
if nodes[i].document_attachments == nil {
continue
}
fk := *nodes[i].document_attachments
if _, ok := nodeids[fk]; !ok {
ids = append(ids, fk)
}
nodeids[fk] = append(nodeids[fk], nodes[i])
}
if len(ids) == 0 {
return nil
}
query.Where(document.IDIn(ids...))
neighbors, err := query.All(ctx)
if err != nil {
return err
}
for _, n := range neighbors {
nodes, ok := nodeids[n.ID]
if !ok {
return fmt.Errorf(`unexpected foreign-key "document_attachments" returned %v`, n.ID)
}
for i := range nodes {
assign(nodes[i], n)
}
}
return nil
}
func (aq *AttachmentQuery) sqlCount(ctx context.Context) (int, error) {
_spec := aq.querySpec()

View File

@@ -13,6 +13,7 @@ import (
"entgo.io/ent/schema/field"
"github.com/google/uuid"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/attachment"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/document"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/item"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/predicate"
)
@@ -64,34 +65,6 @@ func (au *AttachmentUpdate) SetNillablePrimary(b *bool) *AttachmentUpdate {
return au
}
// SetTitle sets the "title" field.
func (au *AttachmentUpdate) SetTitle(s string) *AttachmentUpdate {
au.mutation.SetTitle(s)
return au
}
// SetNillableTitle sets the "title" field if the given value is not nil.
func (au *AttachmentUpdate) SetNillableTitle(s *string) *AttachmentUpdate {
if s != nil {
au.SetTitle(*s)
}
return au
}
// SetPath sets the "path" field.
func (au *AttachmentUpdate) SetPath(s string) *AttachmentUpdate {
au.mutation.SetPath(s)
return au
}
// SetNillablePath sets the "path" field if the given value is not nil.
func (au *AttachmentUpdate) SetNillablePath(s *string) *AttachmentUpdate {
if s != nil {
au.SetPath(*s)
}
return au
}
// SetItemID sets the "item" edge to the Item entity by ID.
func (au *AttachmentUpdate) SetItemID(id uuid.UUID) *AttachmentUpdate {
au.mutation.SetItemID(id)
@@ -103,6 +76,17 @@ func (au *AttachmentUpdate) SetItem(i *Item) *AttachmentUpdate {
return au.SetItemID(i.ID)
}
// SetDocumentID sets the "document" edge to the Document entity by ID.
func (au *AttachmentUpdate) SetDocumentID(id uuid.UUID) *AttachmentUpdate {
au.mutation.SetDocumentID(id)
return au
}
// SetDocument sets the "document" edge to the Document entity.
func (au *AttachmentUpdate) SetDocument(d *Document) *AttachmentUpdate {
return au.SetDocumentID(d.ID)
}
// Mutation returns the AttachmentMutation object of the builder.
func (au *AttachmentUpdate) Mutation() *AttachmentMutation {
return au.mutation
@@ -114,6 +98,12 @@ func (au *AttachmentUpdate) ClearItem() *AttachmentUpdate {
return au
}
// ClearDocument clears the "document" edge to the Document entity.
func (au *AttachmentUpdate) ClearDocument() *AttachmentUpdate {
au.mutation.ClearDocument()
return au
}
// Save executes the query and returns the number of nodes affected by the update operation.
func (au *AttachmentUpdate) Save(ctx context.Context) (int, error) {
au.defaults()
@@ -160,6 +150,9 @@ func (au *AttachmentUpdate) check() error {
if au.mutation.ItemCleared() && len(au.mutation.ItemIDs()) > 0 {
return errors.New(`ent: clearing a required unique edge "Attachment.item"`)
}
if au.mutation.DocumentCleared() && len(au.mutation.DocumentIDs()) > 0 {
return errors.New(`ent: clearing a required unique edge "Attachment.document"`)
}
return nil
}
@@ -184,12 +177,6 @@ func (au *AttachmentUpdate) sqlSave(ctx context.Context) (n int, err error) {
if value, ok := au.mutation.Primary(); ok {
_spec.SetField(attachment.FieldPrimary, field.TypeBool, value)
}
if value, ok := au.mutation.Title(); ok {
_spec.SetField(attachment.FieldTitle, field.TypeString, value)
}
if value, ok := au.mutation.Path(); ok {
_spec.SetField(attachment.FieldPath, field.TypeString, value)
}
if au.mutation.ItemCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
@@ -219,6 +206,35 @@ func (au *AttachmentUpdate) sqlSave(ctx context.Context) (n int, err error) {
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if au.mutation.DocumentCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: attachment.DocumentTable,
Columns: []string{attachment.DocumentColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := au.mutation.DocumentIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: attachment.DocumentTable,
Columns: []string{attachment.DocumentColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID),
},
}
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, au.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{attachment.Label}
@@ -273,34 +289,6 @@ func (auo *AttachmentUpdateOne) SetNillablePrimary(b *bool) *AttachmentUpdateOne
return auo
}
// SetTitle sets the "title" field.
func (auo *AttachmentUpdateOne) SetTitle(s string) *AttachmentUpdateOne {
auo.mutation.SetTitle(s)
return auo
}
// SetNillableTitle sets the "title" field if the given value is not nil.
func (auo *AttachmentUpdateOne) SetNillableTitle(s *string) *AttachmentUpdateOne {
if s != nil {
auo.SetTitle(*s)
}
return auo
}
// SetPath sets the "path" field.
func (auo *AttachmentUpdateOne) SetPath(s string) *AttachmentUpdateOne {
auo.mutation.SetPath(s)
return auo
}
// SetNillablePath sets the "path" field if the given value is not nil.
func (auo *AttachmentUpdateOne) SetNillablePath(s *string) *AttachmentUpdateOne {
if s != nil {
auo.SetPath(*s)
}
return auo
}
// SetItemID sets the "item" edge to the Item entity by ID.
func (auo *AttachmentUpdateOne) SetItemID(id uuid.UUID) *AttachmentUpdateOne {
auo.mutation.SetItemID(id)
@@ -312,6 +300,17 @@ func (auo *AttachmentUpdateOne) SetItem(i *Item) *AttachmentUpdateOne {
return auo.SetItemID(i.ID)
}
// SetDocumentID sets the "document" edge to the Document entity by ID.
func (auo *AttachmentUpdateOne) SetDocumentID(id uuid.UUID) *AttachmentUpdateOne {
auo.mutation.SetDocumentID(id)
return auo
}
// SetDocument sets the "document" edge to the Document entity.
func (auo *AttachmentUpdateOne) SetDocument(d *Document) *AttachmentUpdateOne {
return auo.SetDocumentID(d.ID)
}
// Mutation returns the AttachmentMutation object of the builder.
func (auo *AttachmentUpdateOne) Mutation() *AttachmentMutation {
return auo.mutation
@@ -323,6 +322,12 @@ func (auo *AttachmentUpdateOne) ClearItem() *AttachmentUpdateOne {
return auo
}
// ClearDocument clears the "document" edge to the Document entity.
func (auo *AttachmentUpdateOne) ClearDocument() *AttachmentUpdateOne {
auo.mutation.ClearDocument()
return auo
}
// Where appends a list predicates to the AttachmentUpdate builder.
func (auo *AttachmentUpdateOne) Where(ps ...predicate.Attachment) *AttachmentUpdateOne {
auo.mutation.Where(ps...)
@@ -382,6 +387,9 @@ func (auo *AttachmentUpdateOne) check() error {
if auo.mutation.ItemCleared() && len(auo.mutation.ItemIDs()) > 0 {
return errors.New(`ent: clearing a required unique edge "Attachment.item"`)
}
if auo.mutation.DocumentCleared() && len(auo.mutation.DocumentIDs()) > 0 {
return errors.New(`ent: clearing a required unique edge "Attachment.document"`)
}
return nil
}
@@ -423,12 +431,6 @@ func (auo *AttachmentUpdateOne) sqlSave(ctx context.Context) (_node *Attachment,
if value, ok := auo.mutation.Primary(); ok {
_spec.SetField(attachment.FieldPrimary, field.TypeBool, value)
}
if value, ok := auo.mutation.Title(); ok {
_spec.SetField(attachment.FieldTitle, field.TypeString, value)
}
if value, ok := auo.mutation.Path(); ok {
_spec.SetField(attachment.FieldPath, field.TypeString, value)
}
if auo.mutation.ItemCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
@@ -458,6 +460,35 @@ func (auo *AttachmentUpdateOne) sqlSave(ctx context.Context) (_node *Attachment,
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if auo.mutation.DocumentCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: attachment.DocumentTable,
Columns: []string{attachment.DocumentColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := auo.mutation.DocumentIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: attachment.DocumentTable,
Columns: []string{attachment.DocumentColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
_node = &Attachment{config: auo.config}
_spec.Assign = _node.assignValues
_spec.ScanValues = _node.scanValues

View File

@@ -19,6 +19,7 @@ import (
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/attachment"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/authroles"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/authtokens"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/document"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/group"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/groupinvitationtoken"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/item"
@@ -41,6 +42,8 @@ type Client struct {
AuthRoles *AuthRolesClient
// AuthTokens is the client for interacting with the AuthTokens builders.
AuthTokens *AuthTokensClient
// Document is the client for interacting with the Document builders.
Document *DocumentClient
// Group is the client for interacting with the Group builders.
Group *GroupClient
// GroupInvitationToken is the client for interacting with the GroupInvitationToken builders.
@@ -73,6 +76,7 @@ func (c *Client) init() {
c.Attachment = NewAttachmentClient(c.config)
c.AuthRoles = NewAuthRolesClient(c.config)
c.AuthTokens = NewAuthTokensClient(c.config)
c.Document = NewDocumentClient(c.config)
c.Group = NewGroupClient(c.config)
c.GroupInvitationToken = NewGroupInvitationTokenClient(c.config)
c.Item = NewItemClient(c.config)
@@ -177,6 +181,7 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) {
Attachment: NewAttachmentClient(cfg),
AuthRoles: NewAuthRolesClient(cfg),
AuthTokens: NewAuthTokensClient(cfg),
Document: NewDocumentClient(cfg),
Group: NewGroupClient(cfg),
GroupInvitationToken: NewGroupInvitationTokenClient(cfg),
Item: NewItemClient(cfg),
@@ -208,6 +213,7 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error)
Attachment: NewAttachmentClient(cfg),
AuthRoles: NewAuthRolesClient(cfg),
AuthTokens: NewAuthTokensClient(cfg),
Document: NewDocumentClient(cfg),
Group: NewGroupClient(cfg),
GroupInvitationToken: NewGroupInvitationTokenClient(cfg),
Item: NewItemClient(cfg),
@@ -246,9 +252,9 @@ func (c *Client) Close() error {
// In order to add hooks to a specific client, call: `client.Node.Use(...)`.
func (c *Client) Use(hooks ...Hook) {
for _, n := range []interface{ Use(...Hook) }{
c.Attachment, c.AuthRoles, c.AuthTokens, c.Group, c.GroupInvitationToken,
c.Item, c.ItemField, c.Label, c.Location, c.MaintenanceEntry, c.Notifier,
c.User,
c.Attachment, c.AuthRoles, c.AuthTokens, c.Document, c.Group,
c.GroupInvitationToken, c.Item, c.ItemField, c.Label, c.Location,
c.MaintenanceEntry, c.Notifier, c.User,
} {
n.Use(hooks...)
}
@@ -258,9 +264,9 @@ func (c *Client) Use(hooks ...Hook) {
// In order to add interceptors to a specific client, call: `client.Node.Intercept(...)`.
func (c *Client) Intercept(interceptors ...Interceptor) {
for _, n := range []interface{ Intercept(...Interceptor) }{
c.Attachment, c.AuthRoles, c.AuthTokens, c.Group, c.GroupInvitationToken,
c.Item, c.ItemField, c.Label, c.Location, c.MaintenanceEntry, c.Notifier,
c.User,
c.Attachment, c.AuthRoles, c.AuthTokens, c.Document, c.Group,
c.GroupInvitationToken, c.Item, c.ItemField, c.Label, c.Location,
c.MaintenanceEntry, c.Notifier, c.User,
} {
n.Intercept(interceptors...)
}
@@ -275,6 +281,8 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) {
return c.AuthRoles.mutate(ctx, m)
case *AuthTokensMutation:
return c.AuthTokens.mutate(ctx, m)
case *DocumentMutation:
return c.Document.mutate(ctx, m)
case *GroupMutation:
return c.Group.mutate(ctx, m)
case *GroupInvitationTokenMutation:
@@ -422,6 +430,22 @@ func (c *AttachmentClient) QueryItem(a *Attachment) *ItemQuery {
return query
}
// QueryDocument queries the document edge of a Attachment.
func (c *AttachmentClient) QueryDocument(a *Attachment) *DocumentQuery {
query := (&DocumentClient{config: c.config}).Query()
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
id := a.ID
step := sqlgraph.NewStep(
sqlgraph.From(attachment.Table, attachment.FieldID, id),
sqlgraph.To(document.Table, document.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, attachment.DocumentTable, attachment.DocumentColumn),
)
fromV = sqlgraph.Neighbors(a.driver.Dialect(), step)
return fromV, nil
}
return query
}
// Hooks returns the client hooks.
func (c *AttachmentClient) Hooks() []Hook {
return c.hooks.Attachment
@@ -761,6 +785,171 @@ func (c *AuthTokensClient) mutate(ctx context.Context, m *AuthTokensMutation) (V
}
}
// DocumentClient is a client for the Document schema.
type DocumentClient struct {
config
}
// NewDocumentClient returns a client for the Document from the given config.
func NewDocumentClient(c config) *DocumentClient {
return &DocumentClient{config: c}
}
// Use adds a list of mutation hooks to the hooks stack.
// A call to `Use(f, g, h)` equals to `document.Hooks(f(g(h())))`.
func (c *DocumentClient) Use(hooks ...Hook) {
c.hooks.Document = append(c.hooks.Document, hooks...)
}
// Intercept adds a list of query interceptors to the interceptors stack.
// A call to `Intercept(f, g, h)` equals to `document.Intercept(f(g(h())))`.
func (c *DocumentClient) Intercept(interceptors ...Interceptor) {
c.inters.Document = append(c.inters.Document, interceptors...)
}
// Create returns a builder for creating a Document entity.
func (c *DocumentClient) Create() *DocumentCreate {
mutation := newDocumentMutation(c.config, OpCreate)
return &DocumentCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// CreateBulk returns a builder for creating a bulk of Document entities.
func (c *DocumentClient) CreateBulk(builders ...*DocumentCreate) *DocumentCreateBulk {
return &DocumentCreateBulk{config: c.config, builders: builders}
}
// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
// a builder and applies setFunc on it.
func (c *DocumentClient) MapCreateBulk(slice any, setFunc func(*DocumentCreate, int)) *DocumentCreateBulk {
rv := reflect.ValueOf(slice)
if rv.Kind() != reflect.Slice {
return &DocumentCreateBulk{err: fmt.Errorf("calling to DocumentClient.MapCreateBulk with wrong type %T, need slice", slice)}
}
builders := make([]*DocumentCreate, rv.Len())
for i := 0; i < rv.Len(); i++ {
builders[i] = c.Create()
setFunc(builders[i], i)
}
return &DocumentCreateBulk{config: c.config, builders: builders}
}
// Update returns an update builder for Document.
func (c *DocumentClient) Update() *DocumentUpdate {
mutation := newDocumentMutation(c.config, OpUpdate)
return &DocumentUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOne returns an update builder for the given entity.
func (c *DocumentClient) UpdateOne(d *Document) *DocumentUpdateOne {
mutation := newDocumentMutation(c.config, OpUpdateOne, withDocument(d))
return &DocumentUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOneID returns an update builder for the given id.
func (c *DocumentClient) UpdateOneID(id uuid.UUID) *DocumentUpdateOne {
mutation := newDocumentMutation(c.config, OpUpdateOne, withDocumentID(id))
return &DocumentUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// Delete returns a delete builder for Document.
func (c *DocumentClient) Delete() *DocumentDelete {
mutation := newDocumentMutation(c.config, OpDelete)
return &DocumentDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// DeleteOne returns a builder for deleting the given entity.
func (c *DocumentClient) DeleteOne(d *Document) *DocumentDeleteOne {
return c.DeleteOneID(d.ID)
}
// DeleteOneID returns a builder for deleting the given entity by its id.
func (c *DocumentClient) DeleteOneID(id uuid.UUID) *DocumentDeleteOne {
builder := c.Delete().Where(document.ID(id))
builder.mutation.id = &id
builder.mutation.op = OpDeleteOne
return &DocumentDeleteOne{builder}
}
// Query returns a query builder for Document.
func (c *DocumentClient) Query() *DocumentQuery {
return &DocumentQuery{
config: c.config,
ctx: &QueryContext{Type: TypeDocument},
inters: c.Interceptors(),
}
}
// Get returns a Document entity by its id.
func (c *DocumentClient) Get(ctx context.Context, id uuid.UUID) (*Document, error) {
return c.Query().Where(document.ID(id)).Only(ctx)
}
// GetX is like Get, but panics if an error occurs.
func (c *DocumentClient) GetX(ctx context.Context, id uuid.UUID) *Document {
obj, err := c.Get(ctx, id)
if err != nil {
panic(err)
}
return obj
}
// QueryGroup queries the group edge of a Document.
func (c *DocumentClient) QueryGroup(d *Document) *GroupQuery {
query := (&GroupClient{config: c.config}).Query()
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
id := d.ID
step := sqlgraph.NewStep(
sqlgraph.From(document.Table, document.FieldID, id),
sqlgraph.To(group.Table, group.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, document.GroupTable, document.GroupColumn),
)
fromV = sqlgraph.Neighbors(d.driver.Dialect(), step)
return fromV, nil
}
return query
}
// QueryAttachments queries the attachments edge of a Document.
func (c *DocumentClient) QueryAttachments(d *Document) *AttachmentQuery {
query := (&AttachmentClient{config: c.config}).Query()
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
id := d.ID
step := sqlgraph.NewStep(
sqlgraph.From(document.Table, document.FieldID, id),
sqlgraph.To(attachment.Table, attachment.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, document.AttachmentsTable, document.AttachmentsColumn),
)
fromV = sqlgraph.Neighbors(d.driver.Dialect(), step)
return fromV, nil
}
return query
}
// Hooks returns the client hooks.
func (c *DocumentClient) Hooks() []Hook {
return c.hooks.Document
}
// Interceptors returns the client interceptors.
func (c *DocumentClient) Interceptors() []Interceptor {
return c.inters.Document
}
func (c *DocumentClient) mutate(ctx context.Context, m *DocumentMutation) (Value, error) {
switch m.Op() {
case OpCreate:
return (&DocumentCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdate:
return (&DocumentUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdateOne:
return (&DocumentUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpDelete, OpDeleteOne:
return (&DocumentDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
default:
return nil, fmt.Errorf("ent: unknown Document mutation op: %q", m.Op())
}
}
// GroupClient is a client for the Group schema.
type GroupClient struct {
config
@@ -933,6 +1122,22 @@ func (c *GroupClient) QueryLabels(gr *Group) *LabelQuery {
return query
}
// QueryDocuments queries the documents edge of a Group.
func (c *GroupClient) QueryDocuments(gr *Group) *DocumentQuery {
query := (&DocumentClient{config: c.config}).Query()
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
id := gr.ID
step := sqlgraph.NewStep(
sqlgraph.From(group.Table, group.FieldID, id),
sqlgraph.To(document.Table, document.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, group.DocumentsTable, group.DocumentsColumn),
)
fromV = sqlgraph.Neighbors(gr.driver.Dialect(), step)
return fromV, nil
}
return query
}
// QueryInvitationTokens queries the invitation_tokens edge of a Group.
func (c *GroupClient) QueryInvitationTokens(gr *Group) *GroupInvitationTokenQuery {
query := (&GroupInvitationTokenClient{config: c.config}).Query()
@@ -2409,11 +2614,11 @@ func (c *UserClient) mutate(ctx context.Context, m *UserMutation) (Value, error)
// hooks and interceptors per client, for fast access.
type (
hooks struct {
Attachment, AuthRoles, AuthTokens, Group, GroupInvitationToken, Item, ItemField,
Label, Location, MaintenanceEntry, Notifier, User []ent.Hook
Attachment, AuthRoles, AuthTokens, Document, Group, GroupInvitationToken, Item,
ItemField, Label, Location, MaintenanceEntry, Notifier, User []ent.Hook
}
inters struct {
Attachment, AuthRoles, AuthTokens, Group, GroupInvitationToken, Item, ItemField,
Label, Location, MaintenanceEntry, Notifier, User []ent.Interceptor
Attachment, AuthRoles, AuthTokens, Document, Group, GroupInvitationToken, Item,
ItemField, Label, Location, MaintenanceEntry, Notifier, User []ent.Interceptor
}
)

View File

@@ -0,0 +1,195 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"fmt"
"strings"
"time"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"github.com/google/uuid"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/document"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/group"
)
// Document is the model entity for the Document schema.
type Document 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"`
// Title holds the value of the "title" field.
Title string `json:"title,omitempty"`
// Path holds the value of the "path" field.
Path string `json:"path,omitempty"`
// Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the DocumentQuery when eager-loading is set.
Edges DocumentEdges `json:"edges"`
group_documents *uuid.UUID
selectValues sql.SelectValues
}
// DocumentEdges holds the relations/edges for other nodes in the graph.
type DocumentEdges struct {
// Group holds the value of the group edge.
Group *Group `json:"group,omitempty"`
// Attachments holds the value of the attachments edge.
Attachments []*Attachment `json:"attachments,omitempty"`
// loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not.
loadedTypes [2]bool
}
// GroupOrErr returns the Group value or an error if the edge
// was not loaded in eager-loading, or loaded but was not found.
func (e DocumentEdges) GroupOrErr() (*Group, error) {
if e.Group != nil {
return e.Group, nil
} else if e.loadedTypes[0] {
return nil, &NotFoundError{label: group.Label}
}
return nil, &NotLoadedError{edge: "group"}
}
// AttachmentsOrErr returns the Attachments value or an error if the edge
// was not loaded in eager-loading.
func (e DocumentEdges) AttachmentsOrErr() ([]*Attachment, error) {
if e.loadedTypes[1] {
return e.Attachments, nil
}
return nil, &NotLoadedError{edge: "attachments"}
}
// scanValues returns the types for scanning values from sql.Rows.
func (*Document) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case document.FieldTitle, document.FieldPath:
values[i] = new(sql.NullString)
case document.FieldCreatedAt, document.FieldUpdatedAt:
values[i] = new(sql.NullTime)
case document.FieldID:
values[i] = new(uuid.UUID)
case document.ForeignKeys[0]: // group_documents
values[i] = &sql.NullScanner{S: new(uuid.UUID)}
default:
values[i] = new(sql.UnknownType)
}
}
return values, nil
}
// assignValues assigns the values that were returned from sql.Rows (after scanning)
// to the Document fields.
func (d *Document) 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 document.FieldID:
if value, ok := values[i].(*uuid.UUID); !ok {
return fmt.Errorf("unexpected type %T for field id", values[i])
} else if value != nil {
d.ID = *value
}
case document.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 {
d.CreatedAt = value.Time
}
case document.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 {
d.UpdatedAt = value.Time
}
case document.FieldTitle:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field title", values[i])
} else if value.Valid {
d.Title = value.String
}
case document.FieldPath:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field path", values[i])
} else if value.Valid {
d.Path = value.String
}
case document.ForeignKeys[0]:
if value, ok := values[i].(*sql.NullScanner); !ok {
return fmt.Errorf("unexpected type %T for field group_documents", values[i])
} else if value.Valid {
d.group_documents = new(uuid.UUID)
*d.group_documents = *value.S.(*uuid.UUID)
}
default:
d.selectValues.Set(columns[i], values[i])
}
}
return nil
}
// Value returns the ent.Value that was dynamically selected and assigned to the Document.
// This includes values selected through modifiers, order, etc.
func (d *Document) Value(name string) (ent.Value, error) {
return d.selectValues.Get(name)
}
// QueryGroup queries the "group" edge of the Document entity.
func (d *Document) QueryGroup() *GroupQuery {
return NewDocumentClient(d.config).QueryGroup(d)
}
// QueryAttachments queries the "attachments" edge of the Document entity.
func (d *Document) QueryAttachments() *AttachmentQuery {
return NewDocumentClient(d.config).QueryAttachments(d)
}
// Update returns a builder for updating this Document.
// Note that you need to call Document.Unwrap() before calling this method if this Document
// was returned from a transaction, and the transaction was committed or rolled back.
func (d *Document) Update() *DocumentUpdateOne {
return NewDocumentClient(d.config).UpdateOne(d)
}
// Unwrap unwraps the Document 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 (d *Document) Unwrap() *Document {
_tx, ok := d.config.driver.(*txDriver)
if !ok {
panic("ent: Document is not a transactional entity")
}
d.config.driver = _tx.drv
return d
}
// String implements the fmt.Stringer.
func (d *Document) String() string {
var builder strings.Builder
builder.WriteString("Document(")
builder.WriteString(fmt.Sprintf("id=%v, ", d.ID))
builder.WriteString("created_at=")
builder.WriteString(d.CreatedAt.Format(time.ANSIC))
builder.WriteString(", ")
builder.WriteString("updated_at=")
builder.WriteString(d.UpdatedAt.Format(time.ANSIC))
builder.WriteString(", ")
builder.WriteString("title=")
builder.WriteString(d.Title)
builder.WriteString(", ")
builder.WriteString("path=")
builder.WriteString(d.Path)
builder.WriteByte(')')
return builder.String()
}
// Documents is a parsable slice of Document.
type Documents []*Document

View File

@@ -0,0 +1,154 @@
// Code generated by ent, DO NOT EDIT.
package document
import (
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"github.com/google/uuid"
)
const (
// Label holds the string label denoting the document type in the database.
Label = "document"
// 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"
// FieldTitle holds the string denoting the title field in the database.
FieldTitle = "title"
// FieldPath holds the string denoting the path field in the database.
FieldPath = "path"
// EdgeGroup holds the string denoting the group edge name in mutations.
EdgeGroup = "group"
// EdgeAttachments holds the string denoting the attachments edge name in mutations.
EdgeAttachments = "attachments"
// Table holds the table name of the document in the database.
Table = "documents"
// GroupTable is the table that holds the group relation/edge.
GroupTable = "documents"
// 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_documents"
// 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 = "document_attachments"
)
// Columns holds all SQL columns for document fields.
var Columns = []string{
FieldID,
FieldCreatedAt,
FieldUpdatedAt,
FieldTitle,
FieldPath,
}
// ForeignKeys holds the SQL foreign-keys that are owned by the "documents"
// table and are not defined as standalone fields in the schema.
var ForeignKeys = []string{
"group_documents",
}
// 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
// TitleValidator is a validator for the "title" field. It is called by the builders before save.
TitleValidator func(string) error
// PathValidator is a validator for the "path" field. It is called by the builders before save.
PathValidator func(string) error
// DefaultID holds the default value on creation for the "id" field.
DefaultID func() uuid.UUID
)
// OrderOption defines the ordering options for the Document queries.
type OrderOption func(*sql.Selector)
// ByID orders the results by the id field.
func ByID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldID, opts...).ToFunc()
}
// ByCreatedAt orders the results by the created_at field.
func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldCreatedAt, opts...).ToFunc()
}
// ByUpdatedAt orders the results by the updated_at field.
func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc()
}
// ByTitle orders the results by the title field.
func ByTitle(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldTitle, opts...).ToFunc()
}
// ByPath orders the results by the path field.
func ByPath(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPath, opts...).ToFunc()
}
// ByGroupField orders the results by group field.
func ByGroupField(field string, opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newGroupStep(), sql.OrderByField(field, opts...))
}
}
// ByAttachmentsCount orders the results by attachments count.
func ByAttachmentsCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborsCount(s, newAttachmentsStep(), opts...)
}
}
// ByAttachments orders the results by attachments terms.
func ByAttachments(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newAttachmentsStep(), append([]sql.OrderTerm{term}, terms...)...)
}
}
func newGroupStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(GroupInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, GroupTable, GroupColumn),
)
}
func newAttachmentsStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(AttachmentsInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, AttachmentsTable, AttachmentsColumn),
)
}

View File

@@ -0,0 +1,348 @@
// Code generated by ent, DO NOT EDIT.
package document
import (
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"github.com/google/uuid"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/predicate"
)
// ID filters vertices based on their ID field.
func ID(id uuid.UUID) predicate.Document {
return predicate.Document(sql.FieldEQ(FieldID, id))
}
// IDEQ applies the EQ predicate on the ID field.
func IDEQ(id uuid.UUID) predicate.Document {
return predicate.Document(sql.FieldEQ(FieldID, id))
}
// IDNEQ applies the NEQ predicate on the ID field.
func IDNEQ(id uuid.UUID) predicate.Document {
return predicate.Document(sql.FieldNEQ(FieldID, id))
}
// IDIn applies the In predicate on the ID field.
func IDIn(ids ...uuid.UUID) predicate.Document {
return predicate.Document(sql.FieldIn(FieldID, ids...))
}
// IDNotIn applies the NotIn predicate on the ID field.
func IDNotIn(ids ...uuid.UUID) predicate.Document {
return predicate.Document(sql.FieldNotIn(FieldID, ids...))
}
// IDGT applies the GT predicate on the ID field.
func IDGT(id uuid.UUID) predicate.Document {
return predicate.Document(sql.FieldGT(FieldID, id))
}
// IDGTE applies the GTE predicate on the ID field.
func IDGTE(id uuid.UUID) predicate.Document {
return predicate.Document(sql.FieldGTE(FieldID, id))
}
// IDLT applies the LT predicate on the ID field.
func IDLT(id uuid.UUID) predicate.Document {
return predicate.Document(sql.FieldLT(FieldID, id))
}
// IDLTE applies the LTE predicate on the ID field.
func IDLTE(id uuid.UUID) predicate.Document {
return predicate.Document(sql.FieldLTE(FieldID, id))
}
// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ.
func CreatedAt(v time.Time) predicate.Document {
return predicate.Document(sql.FieldEQ(FieldCreatedAt, v))
}
// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ.
func UpdatedAt(v time.Time) predicate.Document {
return predicate.Document(sql.FieldEQ(FieldUpdatedAt, v))
}
// Title applies equality check predicate on the "title" field. It's identical to TitleEQ.
func Title(v string) predicate.Document {
return predicate.Document(sql.FieldEQ(FieldTitle, v))
}
// Path applies equality check predicate on the "path" field. It's identical to PathEQ.
func Path(v string) predicate.Document {
return predicate.Document(sql.FieldEQ(FieldPath, v))
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
func CreatedAtEQ(v time.Time) predicate.Document {
return predicate.Document(sql.FieldEQ(FieldCreatedAt, v))
}
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
func CreatedAtNEQ(v time.Time) predicate.Document {
return predicate.Document(sql.FieldNEQ(FieldCreatedAt, v))
}
// CreatedAtIn applies the In predicate on the "created_at" field.
func CreatedAtIn(vs ...time.Time) predicate.Document {
return predicate.Document(sql.FieldIn(FieldCreatedAt, vs...))
}
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
func CreatedAtNotIn(vs ...time.Time) predicate.Document {
return predicate.Document(sql.FieldNotIn(FieldCreatedAt, vs...))
}
// CreatedAtGT applies the GT predicate on the "created_at" field.
func CreatedAtGT(v time.Time) predicate.Document {
return predicate.Document(sql.FieldGT(FieldCreatedAt, v))
}
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
func CreatedAtGTE(v time.Time) predicate.Document {
return predicate.Document(sql.FieldGTE(FieldCreatedAt, v))
}
// CreatedAtLT applies the LT predicate on the "created_at" field.
func CreatedAtLT(v time.Time) predicate.Document {
return predicate.Document(sql.FieldLT(FieldCreatedAt, v))
}
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
func CreatedAtLTE(v time.Time) predicate.Document {
return predicate.Document(sql.FieldLTE(FieldCreatedAt, v))
}
// UpdatedAtEQ applies the EQ predicate on the "updated_at" field.
func UpdatedAtEQ(v time.Time) predicate.Document {
return predicate.Document(sql.FieldEQ(FieldUpdatedAt, v))
}
// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field.
func UpdatedAtNEQ(v time.Time) predicate.Document {
return predicate.Document(sql.FieldNEQ(FieldUpdatedAt, v))
}
// UpdatedAtIn applies the In predicate on the "updated_at" field.
func UpdatedAtIn(vs ...time.Time) predicate.Document {
return predicate.Document(sql.FieldIn(FieldUpdatedAt, vs...))
}
// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field.
func UpdatedAtNotIn(vs ...time.Time) predicate.Document {
return predicate.Document(sql.FieldNotIn(FieldUpdatedAt, vs...))
}
// UpdatedAtGT applies the GT predicate on the "updated_at" field.
func UpdatedAtGT(v time.Time) predicate.Document {
return predicate.Document(sql.FieldGT(FieldUpdatedAt, v))
}
// UpdatedAtGTE applies the GTE predicate on the "updated_at" field.
func UpdatedAtGTE(v time.Time) predicate.Document {
return predicate.Document(sql.FieldGTE(FieldUpdatedAt, v))
}
// UpdatedAtLT applies the LT predicate on the "updated_at" field.
func UpdatedAtLT(v time.Time) predicate.Document {
return predicate.Document(sql.FieldLT(FieldUpdatedAt, v))
}
// UpdatedAtLTE applies the LTE predicate on the "updated_at" field.
func UpdatedAtLTE(v time.Time) predicate.Document {
return predicate.Document(sql.FieldLTE(FieldUpdatedAt, v))
}
// TitleEQ applies the EQ predicate on the "title" field.
func TitleEQ(v string) predicate.Document {
return predicate.Document(sql.FieldEQ(FieldTitle, v))
}
// TitleNEQ applies the NEQ predicate on the "title" field.
func TitleNEQ(v string) predicate.Document {
return predicate.Document(sql.FieldNEQ(FieldTitle, v))
}
// TitleIn applies the In predicate on the "title" field.
func TitleIn(vs ...string) predicate.Document {
return predicate.Document(sql.FieldIn(FieldTitle, vs...))
}
// TitleNotIn applies the NotIn predicate on the "title" field.
func TitleNotIn(vs ...string) predicate.Document {
return predicate.Document(sql.FieldNotIn(FieldTitle, vs...))
}
// TitleGT applies the GT predicate on the "title" field.
func TitleGT(v string) predicate.Document {
return predicate.Document(sql.FieldGT(FieldTitle, v))
}
// TitleGTE applies the GTE predicate on the "title" field.
func TitleGTE(v string) predicate.Document {
return predicate.Document(sql.FieldGTE(FieldTitle, v))
}
// TitleLT applies the LT predicate on the "title" field.
func TitleLT(v string) predicate.Document {
return predicate.Document(sql.FieldLT(FieldTitle, v))
}
// TitleLTE applies the LTE predicate on the "title" field.
func TitleLTE(v string) predicate.Document {
return predicate.Document(sql.FieldLTE(FieldTitle, v))
}
// TitleContains applies the Contains predicate on the "title" field.
func TitleContains(v string) predicate.Document {
return predicate.Document(sql.FieldContains(FieldTitle, v))
}
// TitleHasPrefix applies the HasPrefix predicate on the "title" field.
func TitleHasPrefix(v string) predicate.Document {
return predicate.Document(sql.FieldHasPrefix(FieldTitle, v))
}
// TitleHasSuffix applies the HasSuffix predicate on the "title" field.
func TitleHasSuffix(v string) predicate.Document {
return predicate.Document(sql.FieldHasSuffix(FieldTitle, v))
}
// TitleEqualFold applies the EqualFold predicate on the "title" field.
func TitleEqualFold(v string) predicate.Document {
return predicate.Document(sql.FieldEqualFold(FieldTitle, v))
}
// TitleContainsFold applies the ContainsFold predicate on the "title" field.
func TitleContainsFold(v string) predicate.Document {
return predicate.Document(sql.FieldContainsFold(FieldTitle, v))
}
// PathEQ applies the EQ predicate on the "path" field.
func PathEQ(v string) predicate.Document {
return predicate.Document(sql.FieldEQ(FieldPath, v))
}
// PathNEQ applies the NEQ predicate on the "path" field.
func PathNEQ(v string) predicate.Document {
return predicate.Document(sql.FieldNEQ(FieldPath, v))
}
// PathIn applies the In predicate on the "path" field.
func PathIn(vs ...string) predicate.Document {
return predicate.Document(sql.FieldIn(FieldPath, vs...))
}
// PathNotIn applies the NotIn predicate on the "path" field.
func PathNotIn(vs ...string) predicate.Document {
return predicate.Document(sql.FieldNotIn(FieldPath, vs...))
}
// PathGT applies the GT predicate on the "path" field.
func PathGT(v string) predicate.Document {
return predicate.Document(sql.FieldGT(FieldPath, v))
}
// PathGTE applies the GTE predicate on the "path" field.
func PathGTE(v string) predicate.Document {
return predicate.Document(sql.FieldGTE(FieldPath, v))
}
// PathLT applies the LT predicate on the "path" field.
func PathLT(v string) predicate.Document {
return predicate.Document(sql.FieldLT(FieldPath, v))
}
// PathLTE applies the LTE predicate on the "path" field.
func PathLTE(v string) predicate.Document {
return predicate.Document(sql.FieldLTE(FieldPath, v))
}
// PathContains applies the Contains predicate on the "path" field.
func PathContains(v string) predicate.Document {
return predicate.Document(sql.FieldContains(FieldPath, v))
}
// PathHasPrefix applies the HasPrefix predicate on the "path" field.
func PathHasPrefix(v string) predicate.Document {
return predicate.Document(sql.FieldHasPrefix(FieldPath, v))
}
// PathHasSuffix applies the HasSuffix predicate on the "path" field.
func PathHasSuffix(v string) predicate.Document {
return predicate.Document(sql.FieldHasSuffix(FieldPath, v))
}
// PathEqualFold applies the EqualFold predicate on the "path" field.
func PathEqualFold(v string) predicate.Document {
return predicate.Document(sql.FieldEqualFold(FieldPath, v))
}
// PathContainsFold applies the ContainsFold predicate on the "path" field.
func PathContainsFold(v string) predicate.Document {
return predicate.Document(sql.FieldContainsFold(FieldPath, v))
}
// HasGroup applies the HasEdge predicate on the "group" edge.
func HasGroup() predicate.Document {
return predicate.Document(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, GroupTable, GroupColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasGroupWith applies the HasEdge predicate on the "group" edge with a given conditions (other predicates).
func HasGroupWith(preds ...predicate.Group) predicate.Document {
return predicate.Document(func(s *sql.Selector) {
step := newGroupStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
})
}
// HasAttachments applies the HasEdge predicate on the "attachments" edge.
func HasAttachments() predicate.Document {
return predicate.Document(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, AttachmentsTable, AttachmentsColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasAttachmentsWith applies the HasEdge predicate on the "attachments" edge with a given conditions (other predicates).
func HasAttachmentsWith(preds ...predicate.Attachment) predicate.Document {
return predicate.Document(func(s *sql.Selector) {
step := newAttachmentsStep()
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.Document) predicate.Document {
return predicate.Document(sql.AndPredicates(predicates...))
}
// Or groups predicates with the OR operator between them.
func Or(predicates ...predicate.Document) predicate.Document {
return predicate.Document(sql.OrPredicates(predicates...))
}
// Not applies the not operator on the given predicate.
func Not(p predicate.Document) predicate.Document {
return predicate.Document(sql.NotPredicates(p))
}

View File

@@ -0,0 +1,351 @@
// 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/sysadminsmedia/homebox/backend/internal/data/ent/attachment"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/document"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/group"
)
// DocumentCreate is the builder for creating a Document entity.
type DocumentCreate struct {
config
mutation *DocumentMutation
hooks []Hook
}
// SetCreatedAt sets the "created_at" field.
func (dc *DocumentCreate) SetCreatedAt(t time.Time) *DocumentCreate {
dc.mutation.SetCreatedAt(t)
return dc
}
// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
func (dc *DocumentCreate) SetNillableCreatedAt(t *time.Time) *DocumentCreate {
if t != nil {
dc.SetCreatedAt(*t)
}
return dc
}
// SetUpdatedAt sets the "updated_at" field.
func (dc *DocumentCreate) SetUpdatedAt(t time.Time) *DocumentCreate {
dc.mutation.SetUpdatedAt(t)
return dc
}
// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil.
func (dc *DocumentCreate) SetNillableUpdatedAt(t *time.Time) *DocumentCreate {
if t != nil {
dc.SetUpdatedAt(*t)
}
return dc
}
// SetTitle sets the "title" field.
func (dc *DocumentCreate) SetTitle(s string) *DocumentCreate {
dc.mutation.SetTitle(s)
return dc
}
// SetPath sets the "path" field.
func (dc *DocumentCreate) SetPath(s string) *DocumentCreate {
dc.mutation.SetPath(s)
return dc
}
// SetID sets the "id" field.
func (dc *DocumentCreate) SetID(u uuid.UUID) *DocumentCreate {
dc.mutation.SetID(u)
return dc
}
// SetNillableID sets the "id" field if the given value is not nil.
func (dc *DocumentCreate) SetNillableID(u *uuid.UUID) *DocumentCreate {
if u != nil {
dc.SetID(*u)
}
return dc
}
// SetGroupID sets the "group" edge to the Group entity by ID.
func (dc *DocumentCreate) SetGroupID(id uuid.UUID) *DocumentCreate {
dc.mutation.SetGroupID(id)
return dc
}
// SetGroup sets the "group" edge to the Group entity.
func (dc *DocumentCreate) SetGroup(g *Group) *DocumentCreate {
return dc.SetGroupID(g.ID)
}
// AddAttachmentIDs adds the "attachments" edge to the Attachment entity by IDs.
func (dc *DocumentCreate) AddAttachmentIDs(ids ...uuid.UUID) *DocumentCreate {
dc.mutation.AddAttachmentIDs(ids...)
return dc
}
// AddAttachments adds the "attachments" edges to the Attachment entity.
func (dc *DocumentCreate) AddAttachments(a ...*Attachment) *DocumentCreate {
ids := make([]uuid.UUID, len(a))
for i := range a {
ids[i] = a[i].ID
}
return dc.AddAttachmentIDs(ids...)
}
// Mutation returns the DocumentMutation object of the builder.
func (dc *DocumentCreate) Mutation() *DocumentMutation {
return dc.mutation
}
// Save creates the Document in the database.
func (dc *DocumentCreate) Save(ctx context.Context) (*Document, error) {
dc.defaults()
return withHooks(ctx, dc.sqlSave, dc.mutation, dc.hooks)
}
// SaveX calls Save and panics if Save returns an error.
func (dc *DocumentCreate) SaveX(ctx context.Context) *Document {
v, err := dc.Save(ctx)
if err != nil {
panic(err)
}
return v
}
// Exec executes the query.
func (dc *DocumentCreate) Exec(ctx context.Context) error {
_, err := dc.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (dc *DocumentCreate) ExecX(ctx context.Context) {
if err := dc.Exec(ctx); err != nil {
panic(err)
}
}
// defaults sets the default values of the builder before save.
func (dc *DocumentCreate) defaults() {
if _, ok := dc.mutation.CreatedAt(); !ok {
v := document.DefaultCreatedAt()
dc.mutation.SetCreatedAt(v)
}
if _, ok := dc.mutation.UpdatedAt(); !ok {
v := document.DefaultUpdatedAt()
dc.mutation.SetUpdatedAt(v)
}
if _, ok := dc.mutation.ID(); !ok {
v := document.DefaultID()
dc.mutation.SetID(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (dc *DocumentCreate) check() error {
if _, ok := dc.mutation.CreatedAt(); !ok {
return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "Document.created_at"`)}
}
if _, ok := dc.mutation.UpdatedAt(); !ok {
return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "Document.updated_at"`)}
}
if _, ok := dc.mutation.Title(); !ok {
return &ValidationError{Name: "title", err: errors.New(`ent: missing required field "Document.title"`)}
}
if v, ok := dc.mutation.Title(); ok {
if err := document.TitleValidator(v); err != nil {
return &ValidationError{Name: "title", err: fmt.Errorf(`ent: validator failed for field "Document.title": %w`, err)}
}
}
if _, ok := dc.mutation.Path(); !ok {
return &ValidationError{Name: "path", err: errors.New(`ent: missing required field "Document.path"`)}
}
if v, ok := dc.mutation.Path(); ok {
if err := document.PathValidator(v); err != nil {
return &ValidationError{Name: "path", err: fmt.Errorf(`ent: validator failed for field "Document.path": %w`, err)}
}
}
if len(dc.mutation.GroupIDs()) == 0 {
return &ValidationError{Name: "group", err: errors.New(`ent: missing required edge "Document.group"`)}
}
return nil
}
func (dc *DocumentCreate) sqlSave(ctx context.Context) (*Document, error) {
if err := dc.check(); err != nil {
return nil, err
}
_node, _spec := dc.createSpec()
if err := sqlgraph.CreateNode(ctx, dc.driver, _spec); err != nil {
if sqlgraph.IsConstraintError(err) {
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
}
}
dc.mutation.id = &_node.ID
dc.mutation.done = true
return _node, nil
}
func (dc *DocumentCreate) createSpec() (*Document, *sqlgraph.CreateSpec) {
var (
_node = &Document{config: dc.config}
_spec = sqlgraph.NewCreateSpec(document.Table, sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID))
)
if id, ok := dc.mutation.ID(); ok {
_node.ID = id
_spec.ID.Value = &id
}
if value, ok := dc.mutation.CreatedAt(); ok {
_spec.SetField(document.FieldCreatedAt, field.TypeTime, value)
_node.CreatedAt = value
}
if value, ok := dc.mutation.UpdatedAt(); ok {
_spec.SetField(document.FieldUpdatedAt, field.TypeTime, value)
_node.UpdatedAt = value
}
if value, ok := dc.mutation.Title(); ok {
_spec.SetField(document.FieldTitle, field.TypeString, value)
_node.Title = value
}
if value, ok := dc.mutation.Path(); ok {
_spec.SetField(document.FieldPath, field.TypeString, value)
_node.Path = value
}
if nodes := dc.mutation.GroupIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: document.GroupTable,
Columns: []string{document.GroupColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_node.group_documents = &nodes[0]
_spec.Edges = append(_spec.Edges, edge)
}
if nodes := dc.mutation.AttachmentsIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: document.AttachmentsTable,
Columns: []string{document.AttachmentsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges = append(_spec.Edges, edge)
}
return _node, _spec
}
// DocumentCreateBulk is the builder for creating many Document entities in bulk.
type DocumentCreateBulk struct {
config
err error
builders []*DocumentCreate
}
// Save creates the Document entities in the database.
func (dcb *DocumentCreateBulk) Save(ctx context.Context) ([]*Document, error) {
if dcb.err != nil {
return nil, dcb.err
}
specs := make([]*sqlgraph.CreateSpec, len(dcb.builders))
nodes := make([]*Document, len(dcb.builders))
mutators := make([]Mutator, len(dcb.builders))
for i := range dcb.builders {
func(i int, root context.Context) {
builder := dcb.builders[i]
builder.defaults()
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
mutation, ok := m.(*DocumentMutation)
if !ok {
return nil, fmt.Errorf("unexpected mutation type %T", m)
}
if err := builder.check(); err != nil {
return nil, err
}
builder.mutation = mutation
var err error
nodes[i], specs[i] = builder.createSpec()
if i < len(mutators)-1 {
_, err = mutators[i+1].Mutate(root, dcb.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, dcb.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, dcb.builders[0].mutation); err != nil {
return nil, err
}
}
return nodes, nil
}
// SaveX is like Save, but panics if an error occurs.
func (dcb *DocumentCreateBulk) SaveX(ctx context.Context) []*Document {
v, err := dcb.Save(ctx)
if err != nil {
panic(err)
}
return v
}
// Exec executes the query.
func (dcb *DocumentCreateBulk) Exec(ctx context.Context) error {
_, err := dcb.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (dcb *DocumentCreateBulk) ExecX(ctx context.Context) {
if err := dcb.Exec(ctx); err != nil {
panic(err)
}
}

View File

@@ -0,0 +1,88 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/document"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/predicate"
)
// DocumentDelete is the builder for deleting a Document entity.
type DocumentDelete struct {
config
hooks []Hook
mutation *DocumentMutation
}
// Where appends a list predicates to the DocumentDelete builder.
func (dd *DocumentDelete) Where(ps ...predicate.Document) *DocumentDelete {
dd.mutation.Where(ps...)
return dd
}
// Exec executes the deletion query and returns how many vertices were deleted.
func (dd *DocumentDelete) Exec(ctx context.Context) (int, error) {
return withHooks(ctx, dd.sqlExec, dd.mutation, dd.hooks)
}
// ExecX is like Exec, but panics if an error occurs.
func (dd *DocumentDelete) ExecX(ctx context.Context) int {
n, err := dd.Exec(ctx)
if err != nil {
panic(err)
}
return n
}
func (dd *DocumentDelete) sqlExec(ctx context.Context) (int, error) {
_spec := sqlgraph.NewDeleteSpec(document.Table, sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID))
if ps := dd.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
affected, err := sqlgraph.DeleteNodes(ctx, dd.driver, _spec)
if err != nil && sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
dd.mutation.done = true
return affected, err
}
// DocumentDeleteOne is the builder for deleting a single Document entity.
type DocumentDeleteOne struct {
dd *DocumentDelete
}
// Where appends a list predicates to the DocumentDelete builder.
func (ddo *DocumentDeleteOne) Where(ps ...predicate.Document) *DocumentDeleteOne {
ddo.dd.mutation.Where(ps...)
return ddo
}
// Exec executes the deletion query.
func (ddo *DocumentDeleteOne) Exec(ctx context.Context) error {
n, err := ddo.dd.Exec(ctx)
switch {
case err != nil:
return err
case n == 0:
return &NotFoundError{document.Label}
default:
return nil
}
}
// ExecX is like Exec, but panics if an error occurs.
func (ddo *DocumentDeleteOne) ExecX(ctx context.Context) {
if err := ddo.Exec(ctx); err != nil {
panic(err)
}
}

View File

@@ -0,0 +1,691 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"database/sql/driver"
"fmt"
"math"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/google/uuid"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/attachment"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/document"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/group"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/predicate"
)
// DocumentQuery is the builder for querying Document entities.
type DocumentQuery struct {
config
ctx *QueryContext
order []document.OrderOption
inters []Interceptor
predicates []predicate.Document
withGroup *GroupQuery
withAttachments *AttachmentQuery
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 DocumentQuery builder.
func (dq *DocumentQuery) Where(ps ...predicate.Document) *DocumentQuery {
dq.predicates = append(dq.predicates, ps...)
return dq
}
// Limit the number of records to be returned by this query.
func (dq *DocumentQuery) Limit(limit int) *DocumentQuery {
dq.ctx.Limit = &limit
return dq
}
// Offset to start from.
func (dq *DocumentQuery) Offset(offset int) *DocumentQuery {
dq.ctx.Offset = &offset
return dq
}
// Unique configures the query builder to filter duplicate records on query.
// By default, unique is set to true, and can be disabled using this method.
func (dq *DocumentQuery) Unique(unique bool) *DocumentQuery {
dq.ctx.Unique = &unique
return dq
}
// Order specifies how the records should be ordered.
func (dq *DocumentQuery) Order(o ...document.OrderOption) *DocumentQuery {
dq.order = append(dq.order, o...)
return dq
}
// QueryGroup chains the current query on the "group" edge.
func (dq *DocumentQuery) QueryGroup() *GroupQuery {
query := (&GroupClient{config: dq.config}).Query()
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := dq.prepareQuery(ctx); err != nil {
return nil, err
}
selector := dq.sqlQuery(ctx)
if err := selector.Err(); err != nil {
return nil, err
}
step := sqlgraph.NewStep(
sqlgraph.From(document.Table, document.FieldID, selector),
sqlgraph.To(group.Table, group.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, document.GroupTable, document.GroupColumn),
)
fromU = sqlgraph.SetNeighbors(dq.driver.Dialect(), step)
return fromU, nil
}
return query
}
// QueryAttachments chains the current query on the "attachments" edge.
func (dq *DocumentQuery) QueryAttachments() *AttachmentQuery {
query := (&AttachmentClient{config: dq.config}).Query()
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := dq.prepareQuery(ctx); err != nil {
return nil, err
}
selector := dq.sqlQuery(ctx)
if err := selector.Err(); err != nil {
return nil, err
}
step := sqlgraph.NewStep(
sqlgraph.From(document.Table, document.FieldID, selector),
sqlgraph.To(attachment.Table, attachment.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, document.AttachmentsTable, document.AttachmentsColumn),
)
fromU = sqlgraph.SetNeighbors(dq.driver.Dialect(), step)
return fromU, nil
}
return query
}
// First returns the first Document entity from the query.
// Returns a *NotFoundError when no Document was found.
func (dq *DocumentQuery) First(ctx context.Context) (*Document, error) {
nodes, err := dq.Limit(1).All(setContextOp(ctx, dq.ctx, ent.OpQueryFirst))
if err != nil {
return nil, err
}
if len(nodes) == 0 {
return nil, &NotFoundError{document.Label}
}
return nodes[0], nil
}
// FirstX is like First, but panics if an error occurs.
func (dq *DocumentQuery) FirstX(ctx context.Context) *Document {
node, err := dq.First(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return node
}
// FirstID returns the first Document ID from the query.
// Returns a *NotFoundError when no Document ID was found.
func (dq *DocumentQuery) FirstID(ctx context.Context) (id uuid.UUID, err error) {
var ids []uuid.UUID
if ids, err = dq.Limit(1).IDs(setContextOp(ctx, dq.ctx, ent.OpQueryFirstID)); err != nil {
return
}
if len(ids) == 0 {
err = &NotFoundError{document.Label}
return
}
return ids[0], nil
}
// FirstIDX is like FirstID, but panics if an error occurs.
func (dq *DocumentQuery) FirstIDX(ctx context.Context) uuid.UUID {
id, err := dq.FirstID(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return id
}
// Only returns a single Document entity found by the query, ensuring it only returns one.
// Returns a *NotSingularError when more than one Document entity is found.
// Returns a *NotFoundError when no Document entities are found.
func (dq *DocumentQuery) Only(ctx context.Context) (*Document, error) {
nodes, err := dq.Limit(2).All(setContextOp(ctx, dq.ctx, ent.OpQueryOnly))
if err != nil {
return nil, err
}
switch len(nodes) {
case 1:
return nodes[0], nil
case 0:
return nil, &NotFoundError{document.Label}
default:
return nil, &NotSingularError{document.Label}
}
}
// OnlyX is like Only, but panics if an error occurs.
func (dq *DocumentQuery) OnlyX(ctx context.Context) *Document {
node, err := dq.Only(ctx)
if err != nil {
panic(err)
}
return node
}
// OnlyID is like Only, but returns the only Document ID in the query.
// Returns a *NotSingularError when more than one Document ID is found.
// Returns a *NotFoundError when no entities are found.
func (dq *DocumentQuery) OnlyID(ctx context.Context) (id uuid.UUID, err error) {
var ids []uuid.UUID
if ids, err = dq.Limit(2).IDs(setContextOp(ctx, dq.ctx, ent.OpQueryOnlyID)); err != nil {
return
}
switch len(ids) {
case 1:
id = ids[0]
case 0:
err = &NotFoundError{document.Label}
default:
err = &NotSingularError{document.Label}
}
return
}
// OnlyIDX is like OnlyID, but panics if an error occurs.
func (dq *DocumentQuery) OnlyIDX(ctx context.Context) uuid.UUID {
id, err := dq.OnlyID(ctx)
if err != nil {
panic(err)
}
return id
}
// All executes the query and returns a list of Documents.
func (dq *DocumentQuery) All(ctx context.Context) ([]*Document, error) {
ctx = setContextOp(ctx, dq.ctx, ent.OpQueryAll)
if err := dq.prepareQuery(ctx); err != nil {
return nil, err
}
qr := querierAll[[]*Document, *DocumentQuery]()
return withInterceptors[[]*Document](ctx, dq, qr, dq.inters)
}
// AllX is like All, but panics if an error occurs.
func (dq *DocumentQuery) AllX(ctx context.Context) []*Document {
nodes, err := dq.All(ctx)
if err != nil {
panic(err)
}
return nodes
}
// IDs executes the query and returns a list of Document IDs.
func (dq *DocumentQuery) IDs(ctx context.Context) (ids []uuid.UUID, err error) {
if dq.ctx.Unique == nil && dq.path != nil {
dq.Unique(true)
}
ctx = setContextOp(ctx, dq.ctx, ent.OpQueryIDs)
if err = dq.Select(document.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
}
// IDsX is like IDs, but panics if an error occurs.
func (dq *DocumentQuery) IDsX(ctx context.Context) []uuid.UUID {
ids, err := dq.IDs(ctx)
if err != nil {
panic(err)
}
return ids
}
// Count returns the count of the given query.
func (dq *DocumentQuery) Count(ctx context.Context) (int, error) {
ctx = setContextOp(ctx, dq.ctx, ent.OpQueryCount)
if err := dq.prepareQuery(ctx); err != nil {
return 0, err
}
return withInterceptors[int](ctx, dq, querierCount[*DocumentQuery](), dq.inters)
}
// CountX is like Count, but panics if an error occurs.
func (dq *DocumentQuery) CountX(ctx context.Context) int {
count, err := dq.Count(ctx)
if err != nil {
panic(err)
}
return count
}
// Exist returns true if the query has elements in the graph.
func (dq *DocumentQuery) Exist(ctx context.Context) (bool, error) {
ctx = setContextOp(ctx, dq.ctx, ent.OpQueryExist)
switch _, err := dq.FirstID(ctx); {
case IsNotFound(err):
return false, nil
case err != nil:
return false, fmt.Errorf("ent: check existence: %w", err)
default:
return true, nil
}
}
// ExistX is like Exist, but panics if an error occurs.
func (dq *DocumentQuery) ExistX(ctx context.Context) bool {
exist, err := dq.Exist(ctx)
if err != nil {
panic(err)
}
return exist
}
// Clone returns a duplicate of the DocumentQuery builder, including all associated steps. It can be
// used to prepare common query builders and use them differently after the clone is made.
func (dq *DocumentQuery) Clone() *DocumentQuery {
if dq == nil {
return nil
}
return &DocumentQuery{
config: dq.config,
ctx: dq.ctx.Clone(),
order: append([]document.OrderOption{}, dq.order...),
inters: append([]Interceptor{}, dq.inters...),
predicates: append([]predicate.Document{}, dq.predicates...),
withGroup: dq.withGroup.Clone(),
withAttachments: dq.withAttachments.Clone(),
// clone intermediate query.
sql: dq.sql.Clone(),
path: dq.path,
}
}
// WithGroup tells the query-builder to eager-load the nodes that are connected to
// the "group" edge. The optional arguments are used to configure the query builder of the edge.
func (dq *DocumentQuery) WithGroup(opts ...func(*GroupQuery)) *DocumentQuery {
query := (&GroupClient{config: dq.config}).Query()
for _, opt := range opts {
opt(query)
}
dq.withGroup = query
return dq
}
// WithAttachments tells the query-builder to eager-load the nodes that are connected to
// the "attachments" edge. The optional arguments are used to configure the query builder of the edge.
func (dq *DocumentQuery) WithAttachments(opts ...func(*AttachmentQuery)) *DocumentQuery {
query := (&AttachmentClient{config: dq.config}).Query()
for _, opt := range opts {
opt(query)
}
dq.withAttachments = query
return dq
}
// 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.Document.Query().
// GroupBy(document.FieldCreatedAt).
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (dq *DocumentQuery) GroupBy(field string, fields ...string) *DocumentGroupBy {
dq.ctx.Fields = append([]string{field}, fields...)
grbuild := &DocumentGroupBy{build: dq}
grbuild.flds = &dq.ctx.Fields
grbuild.label = document.Label
grbuild.scan = 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.Document.Query().
// Select(document.FieldCreatedAt).
// Scan(ctx, &v)
func (dq *DocumentQuery) Select(fields ...string) *DocumentSelect {
dq.ctx.Fields = append(dq.ctx.Fields, fields...)
sbuild := &DocumentSelect{DocumentQuery: dq}
sbuild.label = document.Label
sbuild.flds, sbuild.scan = &dq.ctx.Fields, sbuild.Scan
return sbuild
}
// Aggregate returns a DocumentSelect configured with the given aggregations.
func (dq *DocumentQuery) Aggregate(fns ...AggregateFunc) *DocumentSelect {
return dq.Select().Aggregate(fns...)
}
func (dq *DocumentQuery) prepareQuery(ctx context.Context) error {
for _, inter := range dq.inters {
if inter == nil {
return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)")
}
if trv, ok := inter.(Traverser); ok {
if err := trv.Traverse(ctx, dq); err != nil {
return err
}
}
}
for _, f := range dq.ctx.Fields {
if !document.ValidColumn(f) {
return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
}
if dq.path != nil {
prev, err := dq.path(ctx)
if err != nil {
return err
}
dq.sql = prev
}
return nil
}
func (dq *DocumentQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Document, error) {
var (
nodes = []*Document{}
withFKs = dq.withFKs
_spec = dq.querySpec()
loadedTypes = [2]bool{
dq.withGroup != nil,
dq.withAttachments != nil,
}
)
if dq.withGroup != nil {
withFKs = true
}
if withFKs {
_spec.Node.Columns = append(_spec.Node.Columns, document.ForeignKeys...)
}
_spec.ScanValues = func(columns []string) ([]any, error) {
return (*Document).scanValues(nil, columns)
}
_spec.Assign = func(columns []string, values []any) error {
node := &Document{config: dq.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, dq.driver, _spec); err != nil {
return nil, err
}
if len(nodes) == 0 {
return nodes, nil
}
if query := dq.withGroup; query != nil {
if err := dq.loadGroup(ctx, query, nodes, nil,
func(n *Document, e *Group) { n.Edges.Group = e }); err != nil {
return nil, err
}
}
if query := dq.withAttachments; query != nil {
if err := dq.loadAttachments(ctx, query, nodes,
func(n *Document) { n.Edges.Attachments = []*Attachment{} },
func(n *Document, e *Attachment) { n.Edges.Attachments = append(n.Edges.Attachments, e) }); err != nil {
return nil, err
}
}
return nodes, nil
}
func (dq *DocumentQuery) loadGroup(ctx context.Context, query *GroupQuery, nodes []*Document, init func(*Document), assign func(*Document, *Group)) error {
ids := make([]uuid.UUID, 0, len(nodes))
nodeids := make(map[uuid.UUID][]*Document)
for i := range nodes {
if nodes[i].group_documents == nil {
continue
}
fk := *nodes[i].group_documents
if _, ok := nodeids[fk]; !ok {
ids = append(ids, fk)
}
nodeids[fk] = append(nodeids[fk], nodes[i])
}
if len(ids) == 0 {
return nil
}
query.Where(group.IDIn(ids...))
neighbors, err := query.All(ctx)
if err != nil {
return err
}
for _, n := range neighbors {
nodes, ok := nodeids[n.ID]
if !ok {
return fmt.Errorf(`unexpected foreign-key "group_documents" returned %v`, n.ID)
}
for i := range nodes {
assign(nodes[i], n)
}
}
return nil
}
func (dq *DocumentQuery) loadAttachments(ctx context.Context, query *AttachmentQuery, nodes []*Document, init func(*Document), assign func(*Document, *Attachment)) error {
fks := make([]driver.Value, 0, len(nodes))
nodeids := make(map[uuid.UUID]*Document)
for i := range nodes {
fks = append(fks, nodes[i].ID)
nodeids[nodes[i].ID] = nodes[i]
if init != nil {
init(nodes[i])
}
}
query.withFKs = true
query.Where(predicate.Attachment(func(s *sql.Selector) {
s.Where(sql.InValues(s.C(document.AttachmentsColumn), fks...))
}))
neighbors, err := query.All(ctx)
if err != nil {
return err
}
for _, n := range neighbors {
fk := n.document_attachments
if fk == nil {
return fmt.Errorf(`foreign-key "document_attachments" is nil for node %v`, n.ID)
}
node, ok := nodeids[*fk]
if !ok {
return fmt.Errorf(`unexpected referenced foreign-key "document_attachments" returned %v for node %v`, *fk, n.ID)
}
assign(node, n)
}
return nil
}
func (dq *DocumentQuery) sqlCount(ctx context.Context) (int, error) {
_spec := dq.querySpec()
_spec.Node.Columns = dq.ctx.Fields
if len(dq.ctx.Fields) > 0 {
_spec.Unique = dq.ctx.Unique != nil && *dq.ctx.Unique
}
return sqlgraph.CountNodes(ctx, dq.driver, _spec)
}
func (dq *DocumentQuery) querySpec() *sqlgraph.QuerySpec {
_spec := sqlgraph.NewQuerySpec(document.Table, document.Columns, sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID))
_spec.From = dq.sql
if unique := dq.ctx.Unique; unique != nil {
_spec.Unique = *unique
} else if dq.path != nil {
_spec.Unique = true
}
if fields := dq.ctx.Fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, document.FieldID)
for i := range fields {
if fields[i] != document.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
}
}
}
if ps := dq.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if limit := dq.ctx.Limit; limit != nil {
_spec.Limit = *limit
}
if offset := dq.ctx.Offset; offset != nil {
_spec.Offset = *offset
}
if ps := dq.order; len(ps) > 0 {
_spec.Order = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
return _spec
}
func (dq *DocumentQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(dq.driver.Dialect())
t1 := builder.Table(document.Table)
columns := dq.ctx.Fields
if len(columns) == 0 {
columns = document.Columns
}
selector := builder.Select(t1.Columns(columns...)...).From(t1)
if dq.sql != nil {
selector = dq.sql
selector.Select(selector.Columns(columns...)...)
}
if dq.ctx.Unique != nil && *dq.ctx.Unique {
selector.Distinct()
}
for _, p := range dq.predicates {
p(selector)
}
for _, p := range dq.order {
p(selector)
}
if offset := dq.ctx.Offset; offset != nil {
// limit is mandatory for offset clause. We start
// with default value, and override it below if needed.
selector.Offset(*offset).Limit(math.MaxInt32)
}
if limit := dq.ctx.Limit; limit != nil {
selector.Limit(*limit)
}
return selector
}
// DocumentGroupBy is the group-by builder for Document entities.
type DocumentGroupBy struct {
selector
build *DocumentQuery
}
// Aggregate adds the given aggregation functions to the group-by query.
func (dgb *DocumentGroupBy) Aggregate(fns ...AggregateFunc) *DocumentGroupBy {
dgb.fns = append(dgb.fns, fns...)
return dgb
}
// Scan applies the selector query and scans the result into the given value.
func (dgb *DocumentGroupBy) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, dgb.build.ctx, ent.OpQueryGroupBy)
if err := dgb.build.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*DocumentQuery, *DocumentGroupBy](ctx, dgb.build, dgb, dgb.build.inters, v)
}
func (dgb *DocumentGroupBy) sqlScan(ctx context.Context, root *DocumentQuery, v any) error {
selector := root.sqlQuery(ctx).Select()
aggregation := make([]string, 0, len(dgb.fns))
for _, fn := range dgb.fns {
aggregation = append(aggregation, fn(selector))
}
if len(selector.SelectedColumns()) == 0 {
columns := make([]string, 0, len(*dgb.flds)+len(dgb.fns))
for _, f := range *dgb.flds {
columns = append(columns, selector.C(f))
}
columns = append(columns, aggregation...)
selector.Select(columns...)
}
selector.GroupBy(selector.Columns(*dgb.flds...)...)
if err := selector.Err(); err != nil {
return err
}
rows := &sql.Rows{}
query, args := selector.Query()
if err := dgb.build.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}
// DocumentSelect is the builder for selecting fields of Document entities.
type DocumentSelect struct {
*DocumentQuery
selector
}
// Aggregate adds the given aggregation functions to the selector query.
func (ds *DocumentSelect) Aggregate(fns ...AggregateFunc) *DocumentSelect {
ds.fns = append(ds.fns, fns...)
return ds
}
// Scan applies the selector query and scans the result into the given value.
func (ds *DocumentSelect) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, ds.ctx, ent.OpQuerySelect)
if err := ds.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*DocumentQuery, *DocumentSelect](ctx, ds.DocumentQuery, ds, ds.inters, v)
}
func (ds *DocumentSelect) sqlScan(ctx context.Context, root *DocumentQuery, v any) error {
selector := root.sqlQuery(ctx)
aggregation := make([]string, 0, len(ds.fns))
for _, fn := range ds.fns {
aggregation = append(aggregation, fn(selector))
}
switch n := len(*ds.selector.flds); {
case n == 0 && len(aggregation) > 0:
selector.Select(aggregation...)
case n != 0 && len(aggregation) > 0:
selector.AppendSelect(aggregation...)
}
rows := &sql.Rows{}
query, args := selector.Query()
if err := ds.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}

View File

@@ -0,0 +1,579 @@
// 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/sysadminsmedia/homebox/backend/internal/data/ent/attachment"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/document"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/group"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/predicate"
)
// DocumentUpdate is the builder for updating Document entities.
type DocumentUpdate struct {
config
hooks []Hook
mutation *DocumentMutation
}
// Where appends a list predicates to the DocumentUpdate builder.
func (du *DocumentUpdate) Where(ps ...predicate.Document) *DocumentUpdate {
du.mutation.Where(ps...)
return du
}
// SetUpdatedAt sets the "updated_at" field.
func (du *DocumentUpdate) SetUpdatedAt(t time.Time) *DocumentUpdate {
du.mutation.SetUpdatedAt(t)
return du
}
// SetTitle sets the "title" field.
func (du *DocumentUpdate) SetTitle(s string) *DocumentUpdate {
du.mutation.SetTitle(s)
return du
}
// SetNillableTitle sets the "title" field if the given value is not nil.
func (du *DocumentUpdate) SetNillableTitle(s *string) *DocumentUpdate {
if s != nil {
du.SetTitle(*s)
}
return du
}
// SetPath sets the "path" field.
func (du *DocumentUpdate) SetPath(s string) *DocumentUpdate {
du.mutation.SetPath(s)
return du
}
// SetNillablePath sets the "path" field if the given value is not nil.
func (du *DocumentUpdate) SetNillablePath(s *string) *DocumentUpdate {
if s != nil {
du.SetPath(*s)
}
return du
}
// SetGroupID sets the "group" edge to the Group entity by ID.
func (du *DocumentUpdate) SetGroupID(id uuid.UUID) *DocumentUpdate {
du.mutation.SetGroupID(id)
return du
}
// SetGroup sets the "group" edge to the Group entity.
func (du *DocumentUpdate) SetGroup(g *Group) *DocumentUpdate {
return du.SetGroupID(g.ID)
}
// AddAttachmentIDs adds the "attachments" edge to the Attachment entity by IDs.
func (du *DocumentUpdate) AddAttachmentIDs(ids ...uuid.UUID) *DocumentUpdate {
du.mutation.AddAttachmentIDs(ids...)
return du
}
// AddAttachments adds the "attachments" edges to the Attachment entity.
func (du *DocumentUpdate) AddAttachments(a ...*Attachment) *DocumentUpdate {
ids := make([]uuid.UUID, len(a))
for i := range a {
ids[i] = a[i].ID
}
return du.AddAttachmentIDs(ids...)
}
// Mutation returns the DocumentMutation object of the builder.
func (du *DocumentUpdate) Mutation() *DocumentMutation {
return du.mutation
}
// ClearGroup clears the "group" edge to the Group entity.
func (du *DocumentUpdate) ClearGroup() *DocumentUpdate {
du.mutation.ClearGroup()
return du
}
// ClearAttachments clears all "attachments" edges to the Attachment entity.
func (du *DocumentUpdate) ClearAttachments() *DocumentUpdate {
du.mutation.ClearAttachments()
return du
}
// RemoveAttachmentIDs removes the "attachments" edge to Attachment entities by IDs.
func (du *DocumentUpdate) RemoveAttachmentIDs(ids ...uuid.UUID) *DocumentUpdate {
du.mutation.RemoveAttachmentIDs(ids...)
return du
}
// RemoveAttachments removes "attachments" edges to Attachment entities.
func (du *DocumentUpdate) RemoveAttachments(a ...*Attachment) *DocumentUpdate {
ids := make([]uuid.UUID, len(a))
for i := range a {
ids[i] = a[i].ID
}
return du.RemoveAttachmentIDs(ids...)
}
// Save executes the query and returns the number of nodes affected by the update operation.
func (du *DocumentUpdate) Save(ctx context.Context) (int, error) {
du.defaults()
return withHooks(ctx, du.sqlSave, du.mutation, du.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (du *DocumentUpdate) SaveX(ctx context.Context) int {
affected, err := du.Save(ctx)
if err != nil {
panic(err)
}
return affected
}
// Exec executes the query.
func (du *DocumentUpdate) Exec(ctx context.Context) error {
_, err := du.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (du *DocumentUpdate) ExecX(ctx context.Context) {
if err := du.Exec(ctx); err != nil {
panic(err)
}
}
// defaults sets the default values of the builder before save.
func (du *DocumentUpdate) defaults() {
if _, ok := du.mutation.UpdatedAt(); !ok {
v := document.UpdateDefaultUpdatedAt()
du.mutation.SetUpdatedAt(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (du *DocumentUpdate) check() error {
if v, ok := du.mutation.Title(); ok {
if err := document.TitleValidator(v); err != nil {
return &ValidationError{Name: "title", err: fmt.Errorf(`ent: validator failed for field "Document.title": %w`, err)}
}
}
if v, ok := du.mutation.Path(); ok {
if err := document.PathValidator(v); err != nil {
return &ValidationError{Name: "path", err: fmt.Errorf(`ent: validator failed for field "Document.path": %w`, err)}
}
}
if du.mutation.GroupCleared() && len(du.mutation.GroupIDs()) > 0 {
return errors.New(`ent: clearing a required unique edge "Document.group"`)
}
return nil
}
func (du *DocumentUpdate) sqlSave(ctx context.Context) (n int, err error) {
if err := du.check(); err != nil {
return n, err
}
_spec := sqlgraph.NewUpdateSpec(document.Table, document.Columns, sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID))
if ps := du.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if value, ok := du.mutation.UpdatedAt(); ok {
_spec.SetField(document.FieldUpdatedAt, field.TypeTime, value)
}
if value, ok := du.mutation.Title(); ok {
_spec.SetField(document.FieldTitle, field.TypeString, value)
}
if value, ok := du.mutation.Path(); ok {
_spec.SetField(document.FieldPath, field.TypeString, value)
}
if du.mutation.GroupCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: document.GroupTable,
Columns: []string{document.GroupColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := du.mutation.GroupIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: document.GroupTable,
Columns: []string{document.GroupColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if du.mutation.AttachmentsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: document.AttachmentsTable,
Columns: []string{document.AttachmentsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := du.mutation.RemovedAttachmentsIDs(); len(nodes) > 0 && !du.mutation.AttachmentsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: document.AttachmentsTable,
Columns: []string{document.AttachmentsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := du.mutation.AttachmentsIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: document.AttachmentsTable,
Columns: []string{document.AttachmentsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID),
},
}
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, du.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{document.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return 0, err
}
du.mutation.done = true
return n, nil
}
// DocumentUpdateOne is the builder for updating a single Document entity.
type DocumentUpdateOne struct {
config
fields []string
hooks []Hook
mutation *DocumentMutation
}
// SetUpdatedAt sets the "updated_at" field.
func (duo *DocumentUpdateOne) SetUpdatedAt(t time.Time) *DocumentUpdateOne {
duo.mutation.SetUpdatedAt(t)
return duo
}
// SetTitle sets the "title" field.
func (duo *DocumentUpdateOne) SetTitle(s string) *DocumentUpdateOne {
duo.mutation.SetTitle(s)
return duo
}
// SetNillableTitle sets the "title" field if the given value is not nil.
func (duo *DocumentUpdateOne) SetNillableTitle(s *string) *DocumentUpdateOne {
if s != nil {
duo.SetTitle(*s)
}
return duo
}
// SetPath sets the "path" field.
func (duo *DocumentUpdateOne) SetPath(s string) *DocumentUpdateOne {
duo.mutation.SetPath(s)
return duo
}
// SetNillablePath sets the "path" field if the given value is not nil.
func (duo *DocumentUpdateOne) SetNillablePath(s *string) *DocumentUpdateOne {
if s != nil {
duo.SetPath(*s)
}
return duo
}
// SetGroupID sets the "group" edge to the Group entity by ID.
func (duo *DocumentUpdateOne) SetGroupID(id uuid.UUID) *DocumentUpdateOne {
duo.mutation.SetGroupID(id)
return duo
}
// SetGroup sets the "group" edge to the Group entity.
func (duo *DocumentUpdateOne) SetGroup(g *Group) *DocumentUpdateOne {
return duo.SetGroupID(g.ID)
}
// AddAttachmentIDs adds the "attachments" edge to the Attachment entity by IDs.
func (duo *DocumentUpdateOne) AddAttachmentIDs(ids ...uuid.UUID) *DocumentUpdateOne {
duo.mutation.AddAttachmentIDs(ids...)
return duo
}
// AddAttachments adds the "attachments" edges to the Attachment entity.
func (duo *DocumentUpdateOne) AddAttachments(a ...*Attachment) *DocumentUpdateOne {
ids := make([]uuid.UUID, len(a))
for i := range a {
ids[i] = a[i].ID
}
return duo.AddAttachmentIDs(ids...)
}
// Mutation returns the DocumentMutation object of the builder.
func (duo *DocumentUpdateOne) Mutation() *DocumentMutation {
return duo.mutation
}
// ClearGroup clears the "group" edge to the Group entity.
func (duo *DocumentUpdateOne) ClearGroup() *DocumentUpdateOne {
duo.mutation.ClearGroup()
return duo
}
// ClearAttachments clears all "attachments" edges to the Attachment entity.
func (duo *DocumentUpdateOne) ClearAttachments() *DocumentUpdateOne {
duo.mutation.ClearAttachments()
return duo
}
// RemoveAttachmentIDs removes the "attachments" edge to Attachment entities by IDs.
func (duo *DocumentUpdateOne) RemoveAttachmentIDs(ids ...uuid.UUID) *DocumentUpdateOne {
duo.mutation.RemoveAttachmentIDs(ids...)
return duo
}
// RemoveAttachments removes "attachments" edges to Attachment entities.
func (duo *DocumentUpdateOne) RemoveAttachments(a ...*Attachment) *DocumentUpdateOne {
ids := make([]uuid.UUID, len(a))
for i := range a {
ids[i] = a[i].ID
}
return duo.RemoveAttachmentIDs(ids...)
}
// Where appends a list predicates to the DocumentUpdate builder.
func (duo *DocumentUpdateOne) Where(ps ...predicate.Document) *DocumentUpdateOne {
duo.mutation.Where(ps...)
return duo
}
// Select allows selecting one or more fields (columns) of the returned entity.
// The default is selecting all fields defined in the entity schema.
func (duo *DocumentUpdateOne) Select(field string, fields ...string) *DocumentUpdateOne {
duo.fields = append([]string{field}, fields...)
return duo
}
// Save executes the query and returns the updated Document entity.
func (duo *DocumentUpdateOne) Save(ctx context.Context) (*Document, error) {
duo.defaults()
return withHooks(ctx, duo.sqlSave, duo.mutation, duo.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (duo *DocumentUpdateOne) SaveX(ctx context.Context) *Document {
node, err := duo.Save(ctx)
if err != nil {
panic(err)
}
return node
}
// Exec executes the query on the entity.
func (duo *DocumentUpdateOne) Exec(ctx context.Context) error {
_, err := duo.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (duo *DocumentUpdateOne) ExecX(ctx context.Context) {
if err := duo.Exec(ctx); err != nil {
panic(err)
}
}
// defaults sets the default values of the builder before save.
func (duo *DocumentUpdateOne) defaults() {
if _, ok := duo.mutation.UpdatedAt(); !ok {
v := document.UpdateDefaultUpdatedAt()
duo.mutation.SetUpdatedAt(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (duo *DocumentUpdateOne) check() error {
if v, ok := duo.mutation.Title(); ok {
if err := document.TitleValidator(v); err != nil {
return &ValidationError{Name: "title", err: fmt.Errorf(`ent: validator failed for field "Document.title": %w`, err)}
}
}
if v, ok := duo.mutation.Path(); ok {
if err := document.PathValidator(v); err != nil {
return &ValidationError{Name: "path", err: fmt.Errorf(`ent: validator failed for field "Document.path": %w`, err)}
}
}
if duo.mutation.GroupCleared() && len(duo.mutation.GroupIDs()) > 0 {
return errors.New(`ent: clearing a required unique edge "Document.group"`)
}
return nil
}
func (duo *DocumentUpdateOne) sqlSave(ctx context.Context) (_node *Document, err error) {
if err := duo.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(document.Table, document.Columns, sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID))
id, ok := duo.mutation.ID()
if !ok {
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "Document.id" for update`)}
}
_spec.Node.ID.Value = id
if fields := duo.fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, document.FieldID)
for _, f := range fields {
if !document.ValidColumn(f) {
return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
if f != document.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, f)
}
}
}
if ps := duo.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if value, ok := duo.mutation.UpdatedAt(); ok {
_spec.SetField(document.FieldUpdatedAt, field.TypeTime, value)
}
if value, ok := duo.mutation.Title(); ok {
_spec.SetField(document.FieldTitle, field.TypeString, value)
}
if value, ok := duo.mutation.Path(); ok {
_spec.SetField(document.FieldPath, field.TypeString, value)
}
if duo.mutation.GroupCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: document.GroupTable,
Columns: []string{document.GroupColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := duo.mutation.GroupIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: document.GroupTable,
Columns: []string{document.GroupColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if duo.mutation.AttachmentsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: document.AttachmentsTable,
Columns: []string{document.AttachmentsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := duo.mutation.RemovedAttachmentsIDs(); len(nodes) > 0 && !duo.mutation.AttachmentsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: document.AttachmentsTable,
Columns: []string{document.AttachmentsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := duo.mutation.AttachmentsIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: document.AttachmentsTable,
Columns: []string{document.AttachmentsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
_node = &Document{config: duo.config}
_spec.Assign = _node.assignValues
_spec.ScanValues = _node.scanValues
if err = sqlgraph.UpdateNode(ctx, duo.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{document.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return nil, err
}
duo.mutation.done = true
return _node, nil
}

View File

@@ -15,6 +15,7 @@ import (
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/attachment"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/authroles"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/authtokens"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/document"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/group"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/groupinvitationtoken"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/item"
@@ -87,6 +88,7 @@ func checkColumn(table, column string) error {
attachment.Table: attachment.ValidColumn,
authroles.Table: authroles.ValidColumn,
authtokens.Table: authtokens.ValidColumn,
document.Table: document.ValidColumn,
group.Table: group.ValidColumn,
groupinvitationtoken.Table: groupinvitationtoken.ValidColumn,
item.Table: item.ValidColumn,

View File

@@ -42,13 +42,15 @@ type GroupEdges struct {
Items []*Item `json:"items,omitempty"`
// Labels holds the value of the labels edge.
Labels []*Label `json:"labels,omitempty"`
// Documents holds the value of the documents edge.
Documents []*Document `json:"documents,omitempty"`
// InvitationTokens holds the value of the invitation_tokens edge.
InvitationTokens []*GroupInvitationToken `json:"invitation_tokens,omitempty"`
// Notifiers holds the value of the notifiers edge.
Notifiers []*Notifier `json:"notifiers,omitempty"`
// loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not.
loadedTypes [6]bool
loadedTypes [7]bool
}
// UsersOrErr returns the Users value or an error if the edge
@@ -87,10 +89,19 @@ func (e GroupEdges) LabelsOrErr() ([]*Label, error) {
return nil, &NotLoadedError{edge: "labels"}
}
// DocumentsOrErr returns the Documents value or an error if the edge
// was not loaded in eager-loading.
func (e GroupEdges) DocumentsOrErr() ([]*Document, error) {
if e.loadedTypes[4] {
return e.Documents, nil
}
return nil, &NotLoadedError{edge: "documents"}
}
// InvitationTokensOrErr returns the InvitationTokens value or an error if the edge
// was not loaded in eager-loading.
func (e GroupEdges) InvitationTokensOrErr() ([]*GroupInvitationToken, error) {
if e.loadedTypes[4] {
if e.loadedTypes[5] {
return e.InvitationTokens, nil
}
return nil, &NotLoadedError{edge: "invitation_tokens"}
@@ -99,7 +110,7 @@ func (e GroupEdges) InvitationTokensOrErr() ([]*GroupInvitationToken, error) {
// NotifiersOrErr returns the Notifiers value or an error if the edge
// was not loaded in eager-loading.
func (e GroupEdges) NotifiersOrErr() ([]*Notifier, error) {
if e.loadedTypes[5] {
if e.loadedTypes[6] {
return e.Notifiers, nil
}
return nil, &NotLoadedError{edge: "notifiers"}
@@ -194,6 +205,11 @@ func (gr *Group) QueryLabels() *LabelQuery {
return NewGroupClient(gr.config).QueryLabels(gr)
}
// QueryDocuments queries the "documents" edge of the Group entity.
func (gr *Group) QueryDocuments() *DocumentQuery {
return NewGroupClient(gr.config).QueryDocuments(gr)
}
// QueryInvitationTokens queries the "invitation_tokens" edge of the Group entity.
func (gr *Group) QueryInvitationTokens() *GroupInvitationTokenQuery {
return NewGroupClient(gr.config).QueryInvitationTokens(gr)

View File

@@ -31,6 +31,8 @@ const (
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"
// EdgeNotifiers holds the string denoting the notifiers edge name in mutations.
@@ -65,6 +67,13 @@ const (
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.
@@ -199,6 +208,20 @@ func ByLabels(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
}
}
// ByDocumentsCount orders the results by documents count.
func ByDocumentsCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborsCount(s, newDocumentsStep(), opts...)
}
}
// ByDocuments orders the results by documents terms.
func ByDocuments(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newDocumentsStep(), append([]sql.OrderTerm{term}, terms...)...)
}
}
// ByInvitationTokensCount orders the results by invitation_tokens count.
func ByInvitationTokensCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
@@ -254,6 +277,13 @@ func newLabelsStep() *sqlgraph.Step {
sqlgraph.Edge(sqlgraph.O2M, false, LabelsTable, LabelsColumn),
)
}
func newDocumentsStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(DocumentsInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, DocumentsTable, DocumentsColumn),
)
}
func newInvitationTokensStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),

View File

@@ -378,6 +378,29 @@ func HasLabelsWith(preds ...predicate.Label) predicate.Group {
})
}
// HasDocuments applies the HasEdge predicate on the "documents" edge.
func HasDocuments() predicate.Group {
return predicate.Group(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, DocumentsTable, DocumentsColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasDocumentsWith applies the HasEdge predicate on the "documents" edge with a given conditions (other predicates).
func HasDocumentsWith(preds ...predicate.Document) predicate.Group {
return predicate.Group(func(s *sql.Selector) {
step := newDocumentsStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
})
}
// HasInvitationTokens applies the HasEdge predicate on the "invitation_tokens" edge.
func HasInvitationTokens() predicate.Group {
return predicate.Group(func(s *sql.Selector) {

View File

@@ -11,6 +11,7 @@ import (
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/google/uuid"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/document"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/group"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/groupinvitationtoken"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/item"
@@ -149,6 +150,21 @@ func (gc *GroupCreate) AddLabels(l ...*Label) *GroupCreate {
return gc.AddLabelIDs(ids...)
}
// AddDocumentIDs adds the "documents" edge to the Document entity by IDs.
func (gc *GroupCreate) AddDocumentIDs(ids ...uuid.UUID) *GroupCreate {
gc.mutation.AddDocumentIDs(ids...)
return gc
}
// AddDocuments adds the "documents" edges to the Document entity.
func (gc *GroupCreate) AddDocuments(d ...*Document) *GroupCreate {
ids := make([]uuid.UUID, len(d))
for i := range d {
ids[i] = d[i].ID
}
return gc.AddDocumentIDs(ids...)
}
// AddInvitationTokenIDs adds the "invitation_tokens" edge to the GroupInvitationToken entity by IDs.
func (gc *GroupCreate) AddInvitationTokenIDs(ids ...uuid.UUID) *GroupCreate {
gc.mutation.AddInvitationTokenIDs(ids...)
@@ -366,6 +382,22 @@ func (gc *GroupCreate) createSpec() (*Group, *sqlgraph.CreateSpec) {
}
_spec.Edges = append(_spec.Edges, edge)
}
if nodes := gc.mutation.DocumentsIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: group.DocumentsTable,
Columns: []string{group.DocumentsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges = append(_spec.Edges, edge)
}
if nodes := gc.mutation.InvitationTokensIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,

View File

@@ -13,6 +13,7 @@ import (
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/google/uuid"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/document"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/group"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/groupinvitationtoken"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/item"
@@ -34,6 +35,7 @@ type GroupQuery struct {
withLocations *LocationQuery
withItems *ItemQuery
withLabels *LabelQuery
withDocuments *DocumentQuery
withInvitationTokens *GroupInvitationTokenQuery
withNotifiers *NotifierQuery
// intermediate query (i.e. traversal path).
@@ -160,6 +162,28 @@ func (gq *GroupQuery) QueryLabels() *LabelQuery {
return query
}
// QueryDocuments chains the current query on the "documents" edge.
func (gq *GroupQuery) QueryDocuments() *DocumentQuery {
query := (&DocumentClient{config: gq.config}).Query()
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := gq.prepareQuery(ctx); err != nil {
return nil, err
}
selector := gq.sqlQuery(ctx)
if err := selector.Err(); err != nil {
return nil, err
}
step := sqlgraph.NewStep(
sqlgraph.From(group.Table, group.FieldID, selector),
sqlgraph.To(document.Table, document.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, group.DocumentsTable, group.DocumentsColumn),
)
fromU = sqlgraph.SetNeighbors(gq.driver.Dialect(), step)
return fromU, nil
}
return query
}
// QueryInvitationTokens chains the current query on the "invitation_tokens" edge.
func (gq *GroupQuery) QueryInvitationTokens() *GroupInvitationTokenQuery {
query := (&GroupInvitationTokenClient{config: gq.config}).Query()
@@ -400,6 +424,7 @@ func (gq *GroupQuery) Clone() *GroupQuery {
withLocations: gq.withLocations.Clone(),
withItems: gq.withItems.Clone(),
withLabels: gq.withLabels.Clone(),
withDocuments: gq.withDocuments.Clone(),
withInvitationTokens: gq.withInvitationTokens.Clone(),
withNotifiers: gq.withNotifiers.Clone(),
// clone intermediate query.
@@ -452,6 +477,17 @@ func (gq *GroupQuery) WithLabels(opts ...func(*LabelQuery)) *GroupQuery {
return gq
}
// WithDocuments tells the query-builder to eager-load the nodes that are connected to
// the "documents" edge. The optional arguments are used to configure the query builder of the edge.
func (gq *GroupQuery) WithDocuments(opts ...func(*DocumentQuery)) *GroupQuery {
query := (&DocumentClient{config: gq.config}).Query()
for _, opt := range opts {
opt(query)
}
gq.withDocuments = query
return gq
}
// WithInvitationTokens tells the query-builder to eager-load the nodes that are connected to
// the "invitation_tokens" edge. The optional arguments are used to configure the query builder of the edge.
func (gq *GroupQuery) WithInvitationTokens(opts ...func(*GroupInvitationTokenQuery)) *GroupQuery {
@@ -552,11 +588,12 @@ func (gq *GroupQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Group,
var (
nodes = []*Group{}
_spec = gq.querySpec()
loadedTypes = [6]bool{
loadedTypes = [7]bool{
gq.withUsers != nil,
gq.withLocations != nil,
gq.withItems != nil,
gq.withLabels != nil,
gq.withDocuments != nil,
gq.withInvitationTokens != nil,
gq.withNotifiers != nil,
}
@@ -607,6 +644,13 @@ func (gq *GroupQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Group,
return nil, err
}
}
if query := gq.withDocuments; query != nil {
if err := gq.loadDocuments(ctx, query, nodes,
func(n *Group) { n.Edges.Documents = []*Document{} },
func(n *Group, e *Document) { n.Edges.Documents = append(n.Edges.Documents, e) }); err != nil {
return nil, err
}
}
if query := gq.withInvitationTokens; query != nil {
if err := gq.loadInvitationTokens(ctx, query, nodes,
func(n *Group) { n.Edges.InvitationTokens = []*GroupInvitationToken{} },
@@ -750,6 +794,37 @@ func (gq *GroupQuery) loadLabels(ctx context.Context, query *LabelQuery, nodes [
}
return nil
}
func (gq *GroupQuery) loadDocuments(ctx context.Context, query *DocumentQuery, nodes []*Group, init func(*Group), assign func(*Group, *Document)) error {
fks := make([]driver.Value, 0, len(nodes))
nodeids := make(map[uuid.UUID]*Group)
for i := range nodes {
fks = append(fks, nodes[i].ID)
nodeids[nodes[i].ID] = nodes[i]
if init != nil {
init(nodes[i])
}
}
query.withFKs = true
query.Where(predicate.Document(func(s *sql.Selector) {
s.Where(sql.InValues(s.C(group.DocumentsColumn), fks...))
}))
neighbors, err := query.All(ctx)
if err != nil {
return err
}
for _, n := range neighbors {
fk := n.group_documents
if fk == nil {
return fmt.Errorf(`foreign-key "group_documents" is nil for node %v`, n.ID)
}
node, ok := nodeids[*fk]
if !ok {
return fmt.Errorf(`unexpected referenced foreign-key "group_documents" returned %v for node %v`, *fk, n.ID)
}
assign(node, n)
}
return nil
}
func (gq *GroupQuery) loadInvitationTokens(ctx context.Context, query *GroupInvitationTokenQuery, nodes []*Group, init func(*Group), assign func(*Group, *GroupInvitationToken)) error {
fks := make([]driver.Value, 0, len(nodes))
nodeids := make(map[uuid.UUID]*Group)

View File

@@ -12,6 +12,7 @@ import (
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/google/uuid"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/document"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/group"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/groupinvitationtoken"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/item"
@@ -129,6 +130,21 @@ func (gu *GroupUpdate) AddLabels(l ...*Label) *GroupUpdate {
return gu.AddLabelIDs(ids...)
}
// AddDocumentIDs adds the "documents" edge to the Document entity by IDs.
func (gu *GroupUpdate) AddDocumentIDs(ids ...uuid.UUID) *GroupUpdate {
gu.mutation.AddDocumentIDs(ids...)
return gu
}
// AddDocuments adds the "documents" edges to the Document entity.
func (gu *GroupUpdate) AddDocuments(d ...*Document) *GroupUpdate {
ids := make([]uuid.UUID, len(d))
for i := range d {
ids[i] = d[i].ID
}
return gu.AddDocumentIDs(ids...)
}
// AddInvitationTokenIDs adds the "invitation_tokens" edge to the GroupInvitationToken entity by IDs.
func (gu *GroupUpdate) AddInvitationTokenIDs(ids ...uuid.UUID) *GroupUpdate {
gu.mutation.AddInvitationTokenIDs(ids...)
@@ -248,6 +264,27 @@ func (gu *GroupUpdate) RemoveLabels(l ...*Label) *GroupUpdate {
return gu.RemoveLabelIDs(ids...)
}
// ClearDocuments clears all "documents" edges to the Document entity.
func (gu *GroupUpdate) ClearDocuments() *GroupUpdate {
gu.mutation.ClearDocuments()
return gu
}
// RemoveDocumentIDs removes the "documents" edge to Document entities by IDs.
func (gu *GroupUpdate) RemoveDocumentIDs(ids ...uuid.UUID) *GroupUpdate {
gu.mutation.RemoveDocumentIDs(ids...)
return gu
}
// RemoveDocuments removes "documents" edges to Document entities.
func (gu *GroupUpdate) RemoveDocuments(d ...*Document) *GroupUpdate {
ids := make([]uuid.UUID, len(d))
for i := range d {
ids[i] = d[i].ID
}
return gu.RemoveDocumentIDs(ids...)
}
// ClearInvitationTokens clears all "invitation_tokens" edges to the GroupInvitationToken entity.
func (gu *GroupUpdate) ClearInvitationTokens() *GroupUpdate {
gu.mutation.ClearInvitationTokens()
@@ -537,6 +574,51 @@ func (gu *GroupUpdate) sqlSave(ctx context.Context) (n int, err error) {
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if gu.mutation.DocumentsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: group.DocumentsTable,
Columns: []string{group.DocumentsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := gu.mutation.RemovedDocumentsIDs(); len(nodes) > 0 && !gu.mutation.DocumentsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: group.DocumentsTable,
Columns: []string{group.DocumentsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := gu.mutation.DocumentsIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: group.DocumentsTable,
Columns: []string{group.DocumentsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if gu.mutation.InvitationTokensCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
@@ -741,6 +823,21 @@ func (guo *GroupUpdateOne) AddLabels(l ...*Label) *GroupUpdateOne {
return guo.AddLabelIDs(ids...)
}
// AddDocumentIDs adds the "documents" edge to the Document entity by IDs.
func (guo *GroupUpdateOne) AddDocumentIDs(ids ...uuid.UUID) *GroupUpdateOne {
guo.mutation.AddDocumentIDs(ids...)
return guo
}
// AddDocuments adds the "documents" edges to the Document entity.
func (guo *GroupUpdateOne) AddDocuments(d ...*Document) *GroupUpdateOne {
ids := make([]uuid.UUID, len(d))
for i := range d {
ids[i] = d[i].ID
}
return guo.AddDocumentIDs(ids...)
}
// AddInvitationTokenIDs adds the "invitation_tokens" edge to the GroupInvitationToken entity by IDs.
func (guo *GroupUpdateOne) AddInvitationTokenIDs(ids ...uuid.UUID) *GroupUpdateOne {
guo.mutation.AddInvitationTokenIDs(ids...)
@@ -860,6 +957,27 @@ func (guo *GroupUpdateOne) RemoveLabels(l ...*Label) *GroupUpdateOne {
return guo.RemoveLabelIDs(ids...)
}
// ClearDocuments clears all "documents" edges to the Document entity.
func (guo *GroupUpdateOne) ClearDocuments() *GroupUpdateOne {
guo.mutation.ClearDocuments()
return guo
}
// RemoveDocumentIDs removes the "documents" edge to Document entities by IDs.
func (guo *GroupUpdateOne) RemoveDocumentIDs(ids ...uuid.UUID) *GroupUpdateOne {
guo.mutation.RemoveDocumentIDs(ids...)
return guo
}
// RemoveDocuments removes "documents" edges to Document entities.
func (guo *GroupUpdateOne) RemoveDocuments(d ...*Document) *GroupUpdateOne {
ids := make([]uuid.UUID, len(d))
for i := range d {
ids[i] = d[i].ID
}
return guo.RemoveDocumentIDs(ids...)
}
// ClearInvitationTokens clears all "invitation_tokens" edges to the GroupInvitationToken entity.
func (guo *GroupUpdateOne) ClearInvitationTokens() *GroupUpdateOne {
guo.mutation.ClearInvitationTokens()
@@ -1179,6 +1297,51 @@ func (guo *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if guo.mutation.DocumentsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: group.DocumentsTable,
Columns: []string{group.DocumentsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := guo.mutation.RemovedDocumentsIDs(); len(nodes) > 0 && !guo.mutation.DocumentsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: group.DocumentsTable,
Columns: []string{group.DocumentsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := guo.mutation.DocumentsIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: group.DocumentsTable,
Columns: []string{group.DocumentsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if guo.mutation.InvitationTokensCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,

View File

@@ -16,6 +16,10 @@ func (at *AuthTokens) GetID() uuid.UUID {
return at.ID
}
func (d *Document) GetID() uuid.UUID {
return d.ID
}
func (gr *Group) GetID() uuid.UUID {
return gr.ID
}

View File

@@ -45,6 +45,18 @@ func (f AuthTokensFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value,
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.AuthTokensMutation", m)
}
// The DocumentFunc type is an adapter to allow the use of ordinary
// function as Document mutator.
type DocumentFunc func(context.Context, *ent.DocumentMutation) (ent.Value, error)
// Mutate calls f(ctx, m).
func (f DocumentFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
if mv, ok := m.(*ent.DocumentMutation); ok {
return f(ctx, mv)
}
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.DocumentMutation", m)
}
// The GroupFunc type is an adapter to allow the use of ordinary
// function as Group mutator.
type GroupFunc func(context.Context, *ent.GroupMutation) (ent.Value, error)

View File

@@ -40,8 +40,6 @@ type Item struct {
Archived bool `json:"archived,omitempty"`
// AssetID holds the value of the "asset_id" field.
AssetID int `json:"asset_id,omitempty"`
// SyncChildItemsLocations holds the value of the "sync_child_items_locations" field.
SyncChildItemsLocations bool `json:"sync_child_items_locations,omitempty"`
// SerialNumber holds the value of the "serial_number" field.
SerialNumber string `json:"serial_number,omitempty"`
// ModelNumber holds the value of the "model_number" field.
@@ -183,7 +181,7 @@ func (*Item) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case item.FieldInsured, item.FieldArchived, item.FieldSyncChildItemsLocations, item.FieldLifetimeWarranty:
case item.FieldInsured, item.FieldArchived, item.FieldLifetimeWarranty:
values[i] = new(sql.NullBool)
case item.FieldPurchasePrice, item.FieldSoldPrice:
values[i] = new(sql.NullFloat64)
@@ -282,12 +280,6 @@ func (i *Item) assignValues(columns []string, values []any) error {
} else if value.Valid {
i.AssetID = int(value.Int64)
}
case item.FieldSyncChildItemsLocations:
if value, ok := values[j].(*sql.NullBool); !ok {
return fmt.Errorf("unexpected type %T for field sync_child_items_locations", values[j])
} else if value.Valid {
i.SyncChildItemsLocations = value.Bool
}
case item.FieldSerialNumber:
if value, ok := values[j].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field serial_number", values[j])
@@ -493,9 +485,6 @@ func (i *Item) String() string {
builder.WriteString("asset_id=")
builder.WriteString(fmt.Sprintf("%v", i.AssetID))
builder.WriteString(", ")
builder.WriteString("sync_child_items_locations=")
builder.WriteString(fmt.Sprintf("%v", i.SyncChildItemsLocations))
builder.WriteString(", ")
builder.WriteString("serial_number=")
builder.WriteString(i.SerialNumber)
builder.WriteString(", ")

View File

@@ -35,8 +35,6 @@ const (
FieldArchived = "archived"
// FieldAssetID holds the string denoting the asset_id field in the database.
FieldAssetID = "asset_id"
// FieldSyncChildItemsLocations holds the string denoting the sync_child_items_locations field in the database.
FieldSyncChildItemsLocations = "sync_child_items_locations"
// 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.
@@ -144,7 +142,6 @@ var Columns = []string{
FieldInsured,
FieldArchived,
FieldAssetID,
FieldSyncChildItemsLocations,
FieldSerialNumber,
FieldModelNumber,
FieldManufacturer,
@@ -212,8 +209,6 @@ var (
DefaultArchived bool
// DefaultAssetID holds the default value on creation for the "asset_id" field.
DefaultAssetID int
// DefaultSyncChildItemsLocations holds the default value on creation for the "sync_child_items_locations" field.
DefaultSyncChildItemsLocations 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.
@@ -292,11 +287,6 @@ func ByAssetID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldAssetID, opts...).ToFunc()
}
// BySyncChildItemsLocations orders the results by the sync_child_items_locations field.
func BySyncChildItemsLocations(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSyncChildItemsLocations, opts...).ToFunc()
}
// BySerialNumber orders the results by the serial_number field.
func BySerialNumber(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSerialNumber, opts...).ToFunc()

View File

@@ -106,11 +106,6 @@ func AssetID(v int) predicate.Item {
return predicate.Item(sql.FieldEQ(FieldAssetID, v))
}
// SyncChildItemsLocations applies equality check predicate on the "sync_child_items_locations" field. It's identical to SyncChildItemsLocationsEQ.
func SyncChildItemsLocations(v bool) predicate.Item {
return predicate.Item(sql.FieldEQ(FieldSyncChildItemsLocations, v))
}
// SerialNumber applies equality check predicate on the "serial_number" field. It's identical to SerialNumberEQ.
func SerialNumber(v string) predicate.Item {
return predicate.Item(sql.FieldEQ(FieldSerialNumber, v))
@@ -646,16 +641,6 @@ func AssetIDLTE(v int) predicate.Item {
return predicate.Item(sql.FieldLTE(FieldAssetID, v))
}
// SyncChildItemsLocationsEQ applies the EQ predicate on the "sync_child_items_locations" field.
func SyncChildItemsLocationsEQ(v bool) predicate.Item {
return predicate.Item(sql.FieldEQ(FieldSyncChildItemsLocations, v))
}
// SyncChildItemsLocationsNEQ applies the NEQ predicate on the "sync_child_items_locations" field.
func SyncChildItemsLocationsNEQ(v bool) predicate.Item {
return predicate.Item(sql.FieldNEQ(FieldSyncChildItemsLocations, v))
}
// SerialNumberEQ applies the EQ predicate on the "serial_number" field.
func SerialNumberEQ(v string) predicate.Item {
return predicate.Item(sql.FieldEQ(FieldSerialNumber, v))

View File

@@ -159,20 +159,6 @@ func (ic *ItemCreate) SetNillableAssetID(i *int) *ItemCreate {
return ic
}
// SetSyncChildItemsLocations sets the "sync_child_items_locations" field.
func (ic *ItemCreate) SetSyncChildItemsLocations(b bool) *ItemCreate {
ic.mutation.SetSyncChildItemsLocations(b)
return ic
}
// SetNillableSyncChildItemsLocations sets the "sync_child_items_locations" field if the given value is not nil.
func (ic *ItemCreate) SetNillableSyncChildItemsLocations(b *bool) *ItemCreate {
if b != nil {
ic.SetSyncChildItemsLocations(*b)
}
return ic
}
// SetSerialNumber sets the "serial_number" field.
func (ic *ItemCreate) SetSerialNumber(s string) *ItemCreate {
ic.mutation.SetSerialNumber(s)
@@ -552,10 +538,6 @@ func (ic *ItemCreate) defaults() {
v := item.DefaultAssetID
ic.mutation.SetAssetID(v)
}
if _, ok := ic.mutation.SyncChildItemsLocations(); !ok {
v := item.DefaultSyncChildItemsLocations
ic.mutation.SetSyncChildItemsLocations(v)
}
if _, ok := ic.mutation.LifetimeWarranty(); !ok {
v := item.DefaultLifetimeWarranty
ic.mutation.SetLifetimeWarranty(v)
@@ -617,9 +599,6 @@ func (ic *ItemCreate) check() error {
if _, ok := ic.mutation.AssetID(); !ok {
return &ValidationError{Name: "asset_id", err: errors.New(`ent: missing required field "Item.asset_id"`)}
}
if _, ok := ic.mutation.SyncChildItemsLocations(); !ok {
return &ValidationError{Name: "sync_child_items_locations", err: errors.New(`ent: missing required field "Item.sync_child_items_locations"`)}
}
if v, ok := ic.mutation.SerialNumber(); ok {
if err := item.SerialNumberValidator(v); err != nil {
return &ValidationError{Name: "serial_number", err: fmt.Errorf(`ent: validator failed for field "Item.serial_number": %w`, err)}
@@ -732,10 +711,6 @@ func (ic *ItemCreate) createSpec() (*Item, *sqlgraph.CreateSpec) {
_spec.SetField(item.FieldAssetID, field.TypeInt, value)
_node.AssetID = value
}
if value, ok := ic.mutation.SyncChildItemsLocations(); ok {
_spec.SetField(item.FieldSyncChildItemsLocations, field.TypeBool, value)
_node.SyncChildItemsLocations = value
}
if value, ok := ic.mutation.SerialNumber(); ok {
_spec.SetField(item.FieldSerialNumber, field.TypeString, value)
_node.SerialNumber = value

View File

@@ -185,20 +185,6 @@ func (iu *ItemUpdate) AddAssetID(i int) *ItemUpdate {
return iu
}
// SetSyncChildItemsLocations sets the "sync_child_items_locations" field.
func (iu *ItemUpdate) SetSyncChildItemsLocations(b bool) *ItemUpdate {
iu.mutation.SetSyncChildItemsLocations(b)
return iu
}
// SetNillableSyncChildItemsLocations sets the "sync_child_items_locations" field if the given value is not nil.
func (iu *ItemUpdate) SetNillableSyncChildItemsLocations(b *bool) *ItemUpdate {
if b != nil {
iu.SetSyncChildItemsLocations(*b)
}
return iu
}
// SetSerialNumber sets the "serial_number" field.
func (iu *ItemUpdate) SetSerialNumber(s string) *ItemUpdate {
iu.mutation.SetSerialNumber(s)
@@ -850,9 +836,6 @@ func (iu *ItemUpdate) sqlSave(ctx context.Context) (n int, err error) {
if value, ok := iu.mutation.AddedAssetID(); ok {
_spec.AddField(item.FieldAssetID, field.TypeInt, value)
}
if value, ok := iu.mutation.SyncChildItemsLocations(); ok {
_spec.SetField(item.FieldSyncChildItemsLocations, field.TypeBool, value)
}
if value, ok := iu.mutation.SerialNumber(); ok {
_spec.SetField(item.FieldSerialNumber, field.TypeString, value)
}
@@ -1410,20 +1393,6 @@ func (iuo *ItemUpdateOne) AddAssetID(i int) *ItemUpdateOne {
return iuo
}
// SetSyncChildItemsLocations sets the "sync_child_items_locations" field.
func (iuo *ItemUpdateOne) SetSyncChildItemsLocations(b bool) *ItemUpdateOne {
iuo.mutation.SetSyncChildItemsLocations(b)
return iuo
}
// SetNillableSyncChildItemsLocations sets the "sync_child_items_locations" field if the given value is not nil.
func (iuo *ItemUpdateOne) SetNillableSyncChildItemsLocations(b *bool) *ItemUpdateOne {
if b != nil {
iuo.SetSyncChildItemsLocations(*b)
}
return iuo
}
// SetSerialNumber sets the "serial_number" field.
func (iuo *ItemUpdateOne) SetSerialNumber(s string) *ItemUpdateOne {
iuo.mutation.SetSerialNumber(s)
@@ -2105,9 +2074,6 @@ func (iuo *ItemUpdateOne) sqlSave(ctx context.Context) (_node *Item, err error)
if value, ok := iuo.mutation.AddedAssetID(); ok {
_spec.AddField(item.FieldAssetID, field.TypeInt, value)
}
if value, ok := iuo.mutation.SyncChildItemsLocations(); ok {
_spec.SetField(item.FieldSyncChildItemsLocations, field.TypeBool, value)
}
if value, ok := iuo.mutation.SerialNumber(); ok {
_spec.SetField(item.FieldSerialNumber, field.TypeString, value)
}

View File

@@ -15,8 +15,7 @@ var (
{Name: "updated_at", Type: field.TypeTime},
{Name: "type", Type: field.TypeEnum, Enums: []string{"photo", "manual", "warranty", "attachment", "receipt"}, Default: "attachment"},
{Name: "primary", Type: field.TypeBool, Default: false},
{Name: "title", Type: field.TypeString, Default: ""},
{Name: "path", Type: field.TypeString, Default: ""},
{Name: "document_attachments", Type: field.TypeUUID},
{Name: "item_attachments", Type: field.TypeUUID},
}
// AttachmentsTable holds the schema information for the "attachments" table.
@@ -25,9 +24,15 @@ var (
Columns: AttachmentsColumns,
PrimaryKey: []*schema.Column{AttachmentsColumns[0]},
ForeignKeys: []*schema.ForeignKey{
{
Symbol: "attachments_documents_attachments",
Columns: []*schema.Column{AttachmentsColumns[5]},
RefColumns: []*schema.Column{DocumentsColumns[0]},
OnDelete: schema.Cascade,
},
{
Symbol: "attachments_items_attachments",
Columns: []*schema.Column{AttachmentsColumns[7]},
Columns: []*schema.Column{AttachmentsColumns[6]},
RefColumns: []*schema.Column{ItemsColumns[0]},
OnDelete: schema.Cascade,
},
@@ -83,6 +88,29 @@ var (
},
},
}
// DocumentsColumns holds the columns for the "documents" table.
DocumentsColumns = []*schema.Column{
{Name: "id", Type: field.TypeUUID},
{Name: "created_at", Type: field.TypeTime},
{Name: "updated_at", Type: field.TypeTime},
{Name: "title", Type: field.TypeString, Size: 255},
{Name: "path", Type: field.TypeString, Size: 500},
{Name: "group_documents", Type: field.TypeUUID},
}
// DocumentsTable holds the schema information for the "documents" table.
DocumentsTable = &schema.Table{
Name: "documents",
Columns: DocumentsColumns,
PrimaryKey: []*schema.Column{DocumentsColumns[0]},
ForeignKeys: []*schema.ForeignKey{
{
Symbol: "documents_groups_documents",
Columns: []*schema.Column{DocumentsColumns[5]},
RefColumns: []*schema.Column{GroupsColumns[0]},
OnDelete: schema.Cascade,
},
},
}
// GroupsColumns holds the columns for the "groups" table.
GroupsColumns = []*schema.Column{
{Name: "id", Type: field.TypeUUID},
@@ -134,7 +162,6 @@ var (
{Name: "insured", Type: field.TypeBool, Default: false},
{Name: "archived", Type: field.TypeBool, Default: false},
{Name: "asset_id", Type: field.TypeInt, Default: 0},
{Name: "sync_child_items_locations", Type: field.TypeBool, Default: false},
{Name: "serial_number", Type: field.TypeString, Nullable: true, Size: 255},
{Name: "model_number", Type: field.TypeString, Nullable: true, Size: 255},
{Name: "manufacturer", Type: field.TypeString, Nullable: true, Size: 255},
@@ -160,19 +187,19 @@ var (
ForeignKeys: []*schema.ForeignKey{
{
Symbol: "items_groups_items",
Columns: []*schema.Column{ItemsColumns[25]},
Columns: []*schema.Column{ItemsColumns[24]},
RefColumns: []*schema.Column{GroupsColumns[0]},
OnDelete: schema.Cascade,
},
{
Symbol: "items_items_children",
Columns: []*schema.Column{ItemsColumns[26]},
Columns: []*schema.Column{ItemsColumns[25]},
RefColumns: []*schema.Column{ItemsColumns[0]},
OnDelete: schema.SetNull,
},
{
Symbol: "items_locations_items",
Columns: []*schema.Column{ItemsColumns[27]},
Columns: []*schema.Column{ItemsColumns[26]},
RefColumns: []*schema.Column{LocationsColumns[0]},
OnDelete: schema.Cascade,
},
@@ -186,17 +213,17 @@ var (
{
Name: "item_manufacturer",
Unique: false,
Columns: []*schema.Column{ItemsColumns[14]},
Columns: []*schema.Column{ItemsColumns[13]},
},
{
Name: "item_model_number",
Unique: false,
Columns: []*schema.Column{ItemsColumns[13]},
Columns: []*schema.Column{ItemsColumns[12]},
},
{
Name: "item_serial_number",
Unique: false,
Columns: []*schema.Column{ItemsColumns[12]},
Columns: []*schema.Column{ItemsColumns[11]},
},
{
Name: "item_archived",
@@ -429,6 +456,7 @@ var (
AttachmentsTable,
AuthRolesTable,
AuthTokensTable,
DocumentsTable,
GroupsTable,
GroupInvitationTokensTable,
ItemsTable,
@@ -443,9 +471,11 @@ var (
)
func init() {
AttachmentsTable.ForeignKeys[0].RefTable = ItemsTable
AttachmentsTable.ForeignKeys[0].RefTable = DocumentsTable
AttachmentsTable.ForeignKeys[1].RefTable = ItemsTable
AuthRolesTable.ForeignKeys[0].RefTable = AuthTokensTable
AuthTokensTable.ForeignKeys[0].RefTable = UsersTable
DocumentsTable.ForeignKeys[0].RefTable = GroupsTable
GroupInvitationTokensTable.ForeignKeys[0].RefTable = GroupsTable
ItemsTable.ForeignKeys[0].RefTable = GroupsTable
ItemsTable.ForeignKeys[1].RefTable = ItemsTable

File diff suppressed because it is too large Load Diff

View File

@@ -15,6 +15,9 @@ type AuthRoles func(*sql.Selector)
// AuthTokens is the predicate function for authtokens builders.
type AuthTokens func(*sql.Selector)
// Document is the predicate function for document builders.
type Document func(*sql.Selector)
// Group is the predicate function for group builders.
type Group func(*sql.Selector)

View File

@@ -8,6 +8,7 @@ import (
"github.com/google/uuid"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/attachment"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/authtokens"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/document"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/group"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/groupinvitationtoken"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/item"
@@ -43,14 +44,6 @@ func init() {
attachmentDescPrimary := attachmentFields[1].Descriptor()
// attachment.DefaultPrimary holds the default value on creation for the primary field.
attachment.DefaultPrimary = attachmentDescPrimary.Default.(bool)
// attachmentDescTitle is the schema descriptor for title field.
attachmentDescTitle := attachmentFields[2].Descriptor()
// attachment.DefaultTitle holds the default value on creation for the title field.
attachment.DefaultTitle = attachmentDescTitle.Default.(string)
// attachmentDescPath is the schema descriptor for path field.
attachmentDescPath := attachmentFields[3].Descriptor()
// attachment.DefaultPath holds the default value on creation for the path field.
attachment.DefaultPath = attachmentDescPath.Default.(string)
// attachmentDescID is the schema descriptor for id field.
attachmentDescID := attachmentMixinFields0[0].Descriptor()
// attachment.DefaultID holds the default value on creation for the id field.
@@ -80,6 +73,61 @@ func init() {
authtokensDescID := authtokensMixinFields0[0].Descriptor()
// authtokens.DefaultID holds the default value on creation for the id field.
authtokens.DefaultID = authtokensDescID.Default.(func() uuid.UUID)
documentMixin := schema.Document{}.Mixin()
documentMixinFields0 := documentMixin[0].Fields()
_ = documentMixinFields0
documentFields := schema.Document{}.Fields()
_ = documentFields
// documentDescCreatedAt is the schema descriptor for created_at field.
documentDescCreatedAt := documentMixinFields0[1].Descriptor()
// document.DefaultCreatedAt holds the default value on creation for the created_at field.
document.DefaultCreatedAt = documentDescCreatedAt.Default.(func() time.Time)
// documentDescUpdatedAt is the schema descriptor for updated_at field.
documentDescUpdatedAt := documentMixinFields0[2].Descriptor()
// document.DefaultUpdatedAt holds the default value on creation for the updated_at field.
document.DefaultUpdatedAt = documentDescUpdatedAt.Default.(func() time.Time)
// document.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
document.UpdateDefaultUpdatedAt = documentDescUpdatedAt.UpdateDefault.(func() time.Time)
// documentDescTitle is the schema descriptor for title field.
documentDescTitle := documentFields[0].Descriptor()
// document.TitleValidator is a validator for the "title" field. It is called by the builders before save.
document.TitleValidator = func() func(string) error {
validators := documentDescTitle.Validators
fns := [...]func(string) error{
validators[0].(func(string) error),
validators[1].(func(string) error),
}
return func(title string) error {
for _, fn := range fns {
if err := fn(title); err != nil {
return err
}
}
return nil
}
}()
// documentDescPath is the schema descriptor for path field.
documentDescPath := documentFields[1].Descriptor()
// document.PathValidator is a validator for the "path" field. It is called by the builders before save.
document.PathValidator = func() func(string) error {
validators := documentDescPath.Validators
fns := [...]func(string) error{
validators[0].(func(string) error),
validators[1].(func(string) error),
}
return func(_path string) error {
for _, fn := range fns {
if err := fn(_path); err != nil {
return err
}
}
return nil
}
}()
// documentDescID is the schema descriptor for id field.
documentDescID := documentMixinFields0[0].Descriptor()
// document.DefaultID holds the default value on creation for the id field.
document.DefaultID = documentDescID.Default.(func() uuid.UUID)
groupMixin := schema.Group{}.Mixin()
groupMixinFields0 := groupMixin[0].Fields()
_ = groupMixinFields0
@@ -211,40 +259,36 @@ func init() {
itemDescAssetID := itemFields[5].Descriptor()
// item.DefaultAssetID holds the default value on creation for the asset_id field.
item.DefaultAssetID = itemDescAssetID.Default.(int)
// itemDescSyncChildItemsLocations is the schema descriptor for sync_child_items_locations field.
itemDescSyncChildItemsLocations := itemFields[6].Descriptor()
// item.DefaultSyncChildItemsLocations holds the default value on creation for the sync_child_items_locations field.
item.DefaultSyncChildItemsLocations = itemDescSyncChildItemsLocations.Default.(bool)
// itemDescSerialNumber is the schema descriptor for serial_number field.
itemDescSerialNumber := itemFields[7].Descriptor()
itemDescSerialNumber := itemFields[6].Descriptor()
// item.SerialNumberValidator is a validator for the "serial_number" field. It is called by the builders before save.
item.SerialNumberValidator = itemDescSerialNumber.Validators[0].(func(string) error)
// itemDescModelNumber is the schema descriptor for model_number field.
itemDescModelNumber := itemFields[8].Descriptor()
itemDescModelNumber := itemFields[7].Descriptor()
// item.ModelNumberValidator is a validator for the "model_number" field. It is called by the builders before save.
item.ModelNumberValidator = itemDescModelNumber.Validators[0].(func(string) error)
// itemDescManufacturer is the schema descriptor for manufacturer field.
itemDescManufacturer := itemFields[9].Descriptor()
itemDescManufacturer := itemFields[8].Descriptor()
// item.ManufacturerValidator is a validator for the "manufacturer" field. It is called by the builders before save.
item.ManufacturerValidator = itemDescManufacturer.Validators[0].(func(string) error)
// itemDescLifetimeWarranty is the schema descriptor for lifetime_warranty field.
itemDescLifetimeWarranty := itemFields[10].Descriptor()
itemDescLifetimeWarranty := itemFields[9].Descriptor()
// item.DefaultLifetimeWarranty holds the default value on creation for the lifetime_warranty field.
item.DefaultLifetimeWarranty = itemDescLifetimeWarranty.Default.(bool)
// itemDescWarrantyDetails is the schema descriptor for warranty_details field.
itemDescWarrantyDetails := itemFields[12].Descriptor()
itemDescWarrantyDetails := itemFields[11].Descriptor()
// item.WarrantyDetailsValidator is a validator for the "warranty_details" field. It is called by the builders before save.
item.WarrantyDetailsValidator = itemDescWarrantyDetails.Validators[0].(func(string) error)
// itemDescPurchasePrice is the schema descriptor for purchase_price field.
itemDescPurchasePrice := itemFields[15].Descriptor()
itemDescPurchasePrice := itemFields[14].Descriptor()
// item.DefaultPurchasePrice holds the default value on creation for the purchase_price field.
item.DefaultPurchasePrice = itemDescPurchasePrice.Default.(float64)
// itemDescSoldPrice is the schema descriptor for sold_price field.
itemDescSoldPrice := itemFields[18].Descriptor()
itemDescSoldPrice := itemFields[17].Descriptor()
// item.DefaultSoldPrice holds the default value on creation for the sold_price field.
item.DefaultSoldPrice = itemDescSoldPrice.Default.(float64)
// itemDescSoldNotes is the schema descriptor for sold_notes field.
itemDescSoldNotes := itemFields[19].Descriptor()
itemDescSoldNotes := itemFields[18].Descriptor()
// item.SoldNotesValidator is a validator for the "sold_notes" field. It is called by the builders before save.
item.SoldNotesValidator = itemDescSoldNotes.Validators[0].(func(string) error)
// itemDescID is the schema descriptor for id field.

View File

@@ -5,6 +5,6 @@ package runtime
// The schema-stitching logic is generated in github.com/sysadminsmedia/homebox/backend/internal/data/ent/runtime.go
const (
Version = "v0.14.4" // Version of ent codegen.
Sum = "h1:/DhDraSLXIkBhyiVoJeSshr4ZYi7femzhj6/TckzZuI=" // Sum of ent codegen.
Version = "v0.14.1" // Version of ent codegen.
Sum = "h1:fUERL506Pqr92EPHJqr8EYxbPioflJo6PudkrEA8a/s=" // Sum of ent codegen.
)

View File

@@ -21,10 +21,11 @@ func (Attachment) Mixin() []ent.Mixin {
// Fields of the Attachment.
func (Attachment) Fields() []ent.Field {
return []ent.Field{
field.Enum("type").Values("photo", "manual", "warranty", "attachment", "receipt").Default("attachment"),
field.Bool("primary").Default(false),
field.String("title").Default(""),
field.String("path").Default(""),
field.Enum("type").
Values("photo", "manual", "warranty", "attachment", "receipt").
Default("attachment"),
field.Bool("primary").
Default(false),
}
}
@@ -35,5 +36,9 @@ func (Attachment) Edges() []ent.Edge {
Ref("attachments").
Required().
Unique(),
edge.From("document", Document.Type).
Ref("attachments").
Required().
Unique(),
}
}

View File

@@ -0,0 +1,43 @@
package schema
import (
"entgo.io/ent"
"entgo.io/ent/dialect/entsql"
"entgo.io/ent/schema/edge"
"entgo.io/ent/schema/field"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/schema/mixins"
)
// Document holds the schema definition for the Document entity.
type Document struct {
ent.Schema
}
func (Document) Mixin() []ent.Mixin {
return []ent.Mixin{
mixins.BaseMixin{},
GroupMixin{ref: "documents"},
}
}
// Fields of the Document.
func (Document) Fields() []ent.Field {
return []ent.Field{
field.String("title").
MaxLen(255).
NotEmpty(),
field.String("path").
MaxLen(500).
NotEmpty(),
}
}
// Edges of the Document.
func (Document) Edges() []ent.Edge {
return []ent.Edge{
edge.To("attachments", Attachment.Type).
Annotations(entsql.Annotation{
OnDelete: entsql.Cascade,
}),
}
}

View File

@@ -46,6 +46,7 @@ func (Group) Edges() []ent.Edge {
owned("locations", Location.Type),
owned("items", Item.Type),
owned("labels", Label.Type),
owned("documents", Document.Type),
owned("invitation_tokens", GroupInvitationToken.Type),
owned("notifiers", Notifier.Type),
// $scaffold_edge

View File

@@ -51,8 +51,6 @@ func (Item) Fields() []ent.Field {
Default(false),
field.Int("asset_id").
Default(0),
field.Bool("sync_child_items_locations").
Default(false),
// ------------------------------------
// item identification

View File

@@ -18,6 +18,8 @@ type Tx struct {
AuthRoles *AuthRolesClient
// AuthTokens is the client for interacting with the AuthTokens builders.
AuthTokens *AuthTokensClient
// Document is the client for interacting with the Document builders.
Document *DocumentClient
// Group is the client for interacting with the Group builders.
Group *GroupClient
// GroupInvitationToken is the client for interacting with the GroupInvitationToken builders.
@@ -170,6 +172,7 @@ func (tx *Tx) init() {
tx.Attachment = NewAttachmentClient(tx.config)
tx.AuthRoles = NewAuthRolesClient(tx.config)
tx.AuthTokens = NewAuthTokensClient(tx.config)
tx.Document = NewDocumentClient(tx.config)
tx.Group = NewGroupClient(tx.config)
tx.GroupInvitationToken = NewGroupInvitationTokenClient(tx.config)
tx.Item = NewItemClient(tx.config)

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