mirror of
https://github.com/sysadminsmedia/homebox.git
synced 2025-12-23 22:18:22 +01:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1279028d07 | ||
|
|
84bf67079b | ||
|
|
889197994b | ||
|
|
ae73b194c4 | ||
|
|
30014a77ca | ||
|
|
1b20a69c5e | ||
|
|
a8e1d2c447 | ||
|
|
14f1b93d38 | ||
|
|
92368dabf8 | ||
|
|
eca071f974 | ||
|
|
48a719d385 | ||
|
|
92c29a37e1 | ||
|
|
ea4e2f6da4 | ||
|
|
28a7adbffe | ||
|
|
9e19e89de4 | ||
|
|
e48109e530 | ||
|
|
e62529b314 | ||
|
|
b0f184a1b1 | ||
|
|
6960b235ea | ||
|
|
3198800329 | ||
|
|
1a69265601 | ||
|
|
2dc765efdf | ||
|
|
6a6e9e842e | ||
|
|
cc78b19d6e | ||
|
|
6b1110b3c5 | ||
|
|
3aae71ee18 | ||
|
|
340b9ac43d | ||
|
|
00c3e1bfc9 |
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,38 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help us improve
|
|
||||||
title: ''
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Describe the bug**
|
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
|
|
||||||
**To Reproduce**
|
|
||||||
Steps to reproduce the behavior:
|
|
||||||
1. Go to '...'
|
|
||||||
2. Click on '....'
|
|
||||||
3. Scroll down to '....'
|
|
||||||
4. See error
|
|
||||||
|
|
||||||
**Expected behavior**
|
|
||||||
A clear and concise description of what you expected to happen.
|
|
||||||
|
|
||||||
**Screenshots**
|
|
||||||
If applicable, add screenshots to help explain your problem.
|
|
||||||
|
|
||||||
**Desktop (please complete the following information):**
|
|
||||||
- OS: [e.g. iOS]
|
|
||||||
- Browser [e.g. chrome, safari]
|
|
||||||
- Version [e.g. 22]
|
|
||||||
|
|
||||||
**Smartphone (please complete the following information):**
|
|
||||||
- Device: [e.g. iPhone6]
|
|
||||||
- OS: [e.g. iOS8.1]
|
|
||||||
- Browser [e.g. stock browser, safari]
|
|
||||||
- Version [e.g. 22]
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context about the problem here.
|
|
||||||
60
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
60
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
---
|
||||||
|
name: "Bug Report"
|
||||||
|
description: "submit a bug report for the current release"
|
||||||
|
body:
|
||||||
|
- type: checkboxes
|
||||||
|
id: checks
|
||||||
|
attributes:
|
||||||
|
label: First Check
|
||||||
|
description: Please confirm and check all the following options.
|
||||||
|
options:
|
||||||
|
- label: This is not a feature request
|
||||||
|
required: true
|
||||||
|
- label: I added a very descriptive title to this issue.
|
||||||
|
required: true
|
||||||
|
- label: I used the GitHub search to find a similar issue and didn't find it.
|
||||||
|
required: true
|
||||||
|
- label: I searched the documentation, with the integrated search.
|
||||||
|
required: true
|
||||||
|
- label: I already read the docs and didn't find an answer.
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: homebox-version
|
||||||
|
attributes:
|
||||||
|
label: Homebox Version
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: What is the issue you are experiencing?
|
||||||
|
placeholder: A clear and concise description of what the bug is.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: reproduction
|
||||||
|
attributes:
|
||||||
|
description: If you do not provide a way to reproduce the issue, your issue will likely be closed.
|
||||||
|
label: How can the maintainer reproduce the issue?
|
||||||
|
placeholder: A clear step-by-step guide on how to reproduce the issue.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: os
|
||||||
|
attributes:
|
||||||
|
label: Deployment
|
||||||
|
description: What Deployment system are you using?
|
||||||
|
multiple: true
|
||||||
|
options:
|
||||||
|
- Docker (Linux)
|
||||||
|
- Docker (Windows)
|
||||||
|
- Docker (Synology)
|
||||||
|
- Unraid
|
||||||
|
- Other
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: os-details
|
||||||
|
attributes:
|
||||||
|
label: Deployment Details
|
||||||
|
description: You can add more details about your operating system here, in particular if you chose "Other". If you are experiencing issues with deployment, please provide your docker-compose or docker commands
|
||||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,20 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Suggest an idea for this project
|
|
||||||
title: ''
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
|
||||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
|
||||||
A clear and concise description of what you want to happen.
|
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context or screenshots about the feature request here.
|
|
||||||
36
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
36
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
name: "Feature Request"
|
||||||
|
description: "submit a feature request for the current release"
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: problem-statement
|
||||||
|
attributes:
|
||||||
|
label: What is the problem you are trying to solve with this feature?
|
||||||
|
placeholder: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
- type: textarea
|
||||||
|
id: feature-solution
|
||||||
|
attributes:
|
||||||
|
label: What is the solution you are proposing?
|
||||||
|
placeholder: A clear and concise description of what you want to happen.
|
||||||
|
- type: textarea
|
||||||
|
id: feature-alternatives
|
||||||
|
attributes:
|
||||||
|
label: What alternatives have you considered?
|
||||||
|
placeholder: A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
- type: textarea
|
||||||
|
id: feature-details
|
||||||
|
attributes:
|
||||||
|
label: Additional context
|
||||||
|
placeholder: Add any other context or screenshots about the feature request here.
|
||||||
|
- type: checkboxes
|
||||||
|
id: checks
|
||||||
|
attributes:
|
||||||
|
label: Contributions
|
||||||
|
description: Please confirm the following
|
||||||
|
options:
|
||||||
|
- label: I have searched through existing issues and feature requests to see if my idea has already been proposed.
|
||||||
|
required: true
|
||||||
|
- label: If this feature is accepted, I would be willing to help implement and maintain this feature.
|
||||||
|
required: false
|
||||||
|
- label: If this feature is accepted, I'm willing to sponsor the development of this feature.
|
||||||
|
required: false
|
||||||
21
.github/workflows/partial-publish.yaml
vendored
21
.github/workflows/partial-publish.yaml
vendored
@@ -44,28 +44,23 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
CR_PAT: ${{ secrets.GH_TOKEN }}
|
CR_PAT: ${{ secrets.GH_TOKEN }}
|
||||||
|
|
||||||
- name: Build and push
|
|
||||||
uses: docker/build-push-action@v3
|
|
||||||
with:
|
|
||||||
push: true
|
|
||||||
tags: user/app:latest
|
|
||||||
|
|
||||||
- name: build nightly the image
|
- name: build nightly the image
|
||||||
if: ${{ inputs.release == false }}
|
if: ${{ inputs.release == false }}
|
||||||
run: |
|
run: |
|
||||||
docker build --push \
|
docker build --push --no-cache \
|
||||||
--tag ghcr.io/hay-kot/homebox:{{ inputs.tag }} \
|
--tag=ghcr.io/hay-kot/homebox:${{ inputs.tag }} \
|
||||||
--build-arg COMMIT=$(git rev-parse HEAD) \
|
--build-arg=COMMIT=$(git rev-parse HEAD) \
|
||||||
--build-arg BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
|
--build-arg=BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
|
||||||
--platform linux/amd64,linux/arm64,linux/arm/v7 .
|
--platform=linux/amd64,linux/arm64,linux/arm/v7 .
|
||||||
|
|
||||||
- name: build release tagged the image
|
- name: build release tagged the image
|
||||||
if: ${{ inputs.release == true }}
|
if: ${{ inputs.release == true }}
|
||||||
run: |
|
run: |
|
||||||
docker build --push \
|
docker build --push --no-cache \
|
||||||
--tag ghcr.io/hay-kot/homebox:nightly \
|
--tag ghcr.io/hay-kot/homebox:nightly \
|
||||||
--tag ghcr.io/hay-kot/homebox:latest \
|
--tag ghcr.io/hay-kot/homebox:latest \
|
||||||
--tag ghcr.io/hay-kot/homebox:{{ inputs.tag }} \
|
--tag ghcr.io/hay-kot/homebox:${{ inputs.tag }} \
|
||||||
|
--build-arg VERSION=${{ inputs.tag }} \
|
||||||
--build-arg COMMIT=$(git rev-parse HEAD) \
|
--build-arg COMMIT=$(git rev-parse HEAD) \
|
||||||
--build-arg BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
|
--build-arg BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
|
||||||
--platform linux/amd64,linux/arm64,linux/arm/v7 .
|
--platform linux/amd64,linux/arm64,linux/arm/v7 .
|
||||||
|
|||||||
25
.github/workflows/publish.yaml
vendored
25
.github/workflows/publish.yaml
vendored
@@ -31,9 +31,21 @@ jobs:
|
|||||||
- uses: superfly/flyctl-actions/setup-flyctl@master
|
- uses: superfly/flyctl-actions/setup-flyctl@master
|
||||||
- run: flyctl deploy --remote-only
|
- run: flyctl deploy --remote-only
|
||||||
|
|
||||||
|
publish-nightly:
|
||||||
|
name: "Publish Nightly"
|
||||||
|
if: github.event_name != 'release'
|
||||||
|
needs:
|
||||||
|
- backend-tests
|
||||||
|
- frontend-tests
|
||||||
|
uses: hay-kot/homebox/.github/workflows/partial-publish.yaml@main
|
||||||
|
with:
|
||||||
|
tag: nightly
|
||||||
|
secrets:
|
||||||
|
GH_TOKEN: ${{ secrets.CR_PAT }}
|
||||||
|
|
||||||
publish-tag:
|
publish-tag:
|
||||||
if: github.event_name == 'release'
|
|
||||||
name: "Publish Tag"
|
name: "Publish Tag"
|
||||||
|
if: github.event_name == 'release'
|
||||||
needs:
|
needs:
|
||||||
- backend-tests
|
- backend-tests
|
||||||
- frontend-tests
|
- frontend-tests
|
||||||
@@ -44,17 +56,6 @@ jobs:
|
|||||||
secrets:
|
secrets:
|
||||||
GH_TOKEN: ${{ secrets.CR_PAT }}
|
GH_TOKEN: ${{ secrets.CR_PAT }}
|
||||||
|
|
||||||
publish-nightly:
|
|
||||||
name: "Publish Nightly"
|
|
||||||
needs:
|
|
||||||
- backend-tests
|
|
||||||
- frontend-tests
|
|
||||||
uses: hay-kot/homebox/.github/workflows/partial-publish.yaml@main
|
|
||||||
with:
|
|
||||||
tag: nightly
|
|
||||||
secrets:
|
|
||||||
GH_TOKEN: ${{ secrets.CR_PAT }}
|
|
||||||
|
|
||||||
deploy-docs:
|
deploy-docs:
|
||||||
name: Deploy docs
|
name: Deploy docs
|
||||||
needs:
|
needs:
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ RUN pnpm build
|
|||||||
FROM golang:alpine AS builder
|
FROM golang:alpine AS builder
|
||||||
ARG BUILD_TIME
|
ARG BUILD_TIME
|
||||||
ARG COMMIT
|
ARG COMMIT
|
||||||
|
ARG VERSION
|
||||||
RUN apk update && \
|
RUN apk update && \
|
||||||
apk upgrade && \
|
apk upgrade && \
|
||||||
apk add --update git build-base gcc g++
|
apk add --update git build-base gcc g++
|
||||||
@@ -22,7 +23,7 @@ RUN go get -d -v ./...
|
|||||||
RUN rm -rf ./app/api/public
|
RUN rm -rf ./app/api/public
|
||||||
COPY --from=frontend-builder /app/.output/public ./app/api/public
|
COPY --from=frontend-builder /app/.output/public ./app/api/public
|
||||||
RUN CGO_ENABLED=1 GOOS=linux go build \
|
RUN CGO_ENABLED=1 GOOS=linux go build \
|
||||||
-ldflags "-s -w -X main.Commit=$COMMIT -X main.BuildTime=$BUILD_TIME" \
|
-ldflags "-s -w -X main.commit=$COMMIT -X main.buildTime=$BUILD_TIME -X main.version=$VERSION" \
|
||||||
-o /go/bin/api \
|
-o /go/bin/api \
|
||||||
-v ./app/api/*.go
|
-v ./app/api/*.go
|
||||||
|
|
||||||
|
|||||||
62
backend/app/api/demo.go
Normal file
62
backend/app/api/demo.go
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/csv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hay-kot/homebox/backend/internal/services"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *app) SetupDemo() {
|
||||||
|
csvText := `Import Ref,Location,Labels,Quantity,Name,Description,Insured,Serial Number,Model Number,Manufacturer,Notes,Purchase From,Purchased Price,Purchased Time,Lifetime Warranty,Warranty Expires,Warranty Details,Sold To,Sold Price,Sold Time,Sold Notes
|
||||||
|
,Garage,IOT;Home Assistant; Z-Wave,1,Zooz Universal Relay ZEN17,"Zooz 700 Series Z-Wave Universal Relay ZEN17 for Awnings, Garage Doors, Sprinklers, and More | 2 NO-C-NC Relays (20A, 10A) | Signal Repeater | Hub Required (Compatible with SmartThings and Hubitat)",,,ZEN17,Zooz,,Amazon,39.95,10/13/2021,,,,,,,
|
||||||
|
,Living Room,IOT;Home Assistant; Z-Wave,1,Zooz Motion Sensor,"Zooz Z-Wave Plus S2 Motion Sensor ZSE18 with Magnetic Mount, Works with Vera and SmartThings",,,ZSE18,Zooz,,Amazon,29.95,10/15/2021,,,,,,,
|
||||||
|
,Office,IOT;Home Assistant; Z-Wave,1,Zooz 110v Power Switch,"Zooz Z-Wave Plus Power Switch ZEN15 for 110V AC Units, Sump Pumps, Humidifiers, and More",,,ZEN15,Zooz,,Amazon,39.95,10/13/2021,,,,,,,
|
||||||
|
,Downstairs,IOT;Home Assistant; Z-Wave,1,Ecolink Z-Wave PIR Motion Sensor,"Ecolink Z-Wave PIR Motion Detector Pet Immune, White (PIRZWAVE2.5-ECO)",,,PIRZWAVE2.5-ECO,Ecolink,,Amazon,35.58,10/21/2020,,,,,,,
|
||||||
|
,Entry,IOT;Home Assistant; Z-Wave,1,Yale Security Touchscreen Deadbolt,"Yale Security YRD226-ZW2-619 YRD226ZW2619 Touchscreen Deadbolt, Satin Nickel",,,YRD226ZW2619,Yale,,Amazon,120.39,10/14/2020,,,,,,,
|
||||||
|
,Kitchen,IOT;Home Assistant; Z-Wave,1,Smart Rocker Light Dimmer,"UltraPro Z-Wave Smart Rocker Light Dimmer with QuickFit and SimpleWire, 3-Way Ready, Compatible with Alexa, Google Assistant, ZWave Hub Required, Repeater/Range Extender, White Paddle Only, 39351",,,39351,Honeywell,,Amazon,65.98,09/30/0202,,,,,,,
|
||||||
|
`
|
||||||
|
|
||||||
|
var (
|
||||||
|
registration = services.UserRegistration{
|
||||||
|
Email: "demo@example.com",
|
||||||
|
Name: "Demo",
|
||||||
|
Password: "demo",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// First check if we've already setup a demo user and skip if so
|
||||||
|
_, err := a.services.User.Login(context.Background(), registration.Email, registration.Password)
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = a.services.User.RegisterUser(context.Background(), registration)
|
||||||
|
if err != nil {
|
||||||
|
log.Err(err).Msg("Failed to register demo user")
|
||||||
|
log.Fatal().Msg("Failed to setup demo")
|
||||||
|
}
|
||||||
|
|
||||||
|
token, _ := a.services.User.Login(context.Background(), registration.Email, registration.Password)
|
||||||
|
self, _ := a.services.User.GetSelf(context.Background(), token.Raw)
|
||||||
|
|
||||||
|
// Read CSV Text
|
||||||
|
reader := csv.NewReader(strings.NewReader(csvText))
|
||||||
|
reader.Comma = ','
|
||||||
|
|
||||||
|
records, err := reader.ReadAll()
|
||||||
|
if err != nil {
|
||||||
|
log.Err(err).Msg("Failed to read CSV")
|
||||||
|
log.Fatal().Msg("Failed to setup demo")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.services.Items.CsvImport(context.Background(), self.GroupID, records)
|
||||||
|
if err != nil {
|
||||||
|
log.Err(err).Msg("Failed to import CSV")
|
||||||
|
log.Fatal().Msg("Failed to setup demo")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().Msg("Demo setup complete")
|
||||||
|
}
|
||||||
@@ -70,26 +70,51 @@ const docTemplate = `{
|
|||||||
"Items"
|
"Items"
|
||||||
],
|
],
|
||||||
"summary": "Get All Items",
|
"summary": "Get All Items",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "search string",
|
||||||
|
"name": "q",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page number",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "items per page",
|
||||||
|
"name": "pageSize",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"collectionFormat": "multi",
|
||||||
|
"description": "label Ids",
|
||||||
|
"name": "labels",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"collectionFormat": "multi",
|
||||||
|
"description": "location Ids",
|
||||||
|
"name": "locations",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"allOf": [
|
"$ref": "#/definitions/repo.PaginationResult-repo_ItemSummary"
|
||||||
{
|
|
||||||
"$ref": "#/definitions/server.Results"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"items": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/repo.ItemSummary"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,7 +178,7 @@ const docTemplate = `{
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": ""
|
"description": "No Content"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -254,7 +279,7 @@ const docTemplate = `{
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": ""
|
"description": "No Content"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -354,7 +379,7 @@ const docTemplate = `{
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": ""
|
"description": "OK"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -470,7 +495,7 @@ const docTemplate = `{
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": ""
|
"description": "No Content"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -634,7 +659,7 @@ const docTemplate = `{
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": ""
|
"description": "No Content"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -798,7 +823,7 @@ const docTemplate = `{
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": ""
|
"description": "No Content"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -846,7 +871,7 @@ const docTemplate = `{
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": ""
|
"description": "No Content"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -903,7 +928,7 @@ const docTemplate = `{
|
|||||||
"summary": "User Logout",
|
"summary": "User Logout",
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": ""
|
"description": "No Content"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -922,7 +947,7 @@ const docTemplate = `{
|
|||||||
"summary": "User Token Refresh",
|
"summary": "User Token Refresh",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": ""
|
"description": "OK"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -949,7 +974,7 @@ const docTemplate = `{
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": ""
|
"description": "No Content"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1049,7 +1074,7 @@ const docTemplate = `{
|
|||||||
"summary": "Deletes the user account",
|
"summary": "Deletes the user account",
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": ""
|
"description": "No Content"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1070,7 +1095,7 @@ const docTemplate = `{
|
|||||||
"summary": "Update the current user's password // TODO:",
|
"summary": "Update the current user's password // TODO:",
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": ""
|
"description": "No Content"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1488,6 +1513,26 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"repo.PaginationResult-repo_ItemSummary": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"items": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/repo.ItemSummary"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"page": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"pageSize": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"repo.UserOut": {
|
"repo.UserOut": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -1541,9 +1586,7 @@ const docTemplate = `{
|
|||||||
"server.Results": {
|
"server.Results": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"items": {
|
"items": {}
|
||||||
"type": "any"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"server.ValidationError": {
|
"server.ValidationError": {
|
||||||
@@ -1580,6 +1623,9 @@ const docTemplate = `{
|
|||||||
"build": {
|
"build": {
|
||||||
"$ref": "#/definitions/v1.Build"
|
"$ref": "#/definitions/v1.Build"
|
||||||
},
|
},
|
||||||
|
"demo": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"health": {
|
"health": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -62,26 +62,51 @@
|
|||||||
"Items"
|
"Items"
|
||||||
],
|
],
|
||||||
"summary": "Get All Items",
|
"summary": "Get All Items",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "search string",
|
||||||
|
"name": "q",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page number",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "items per page",
|
||||||
|
"name": "pageSize",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"collectionFormat": "multi",
|
||||||
|
"description": "label Ids",
|
||||||
|
"name": "labels",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"collectionFormat": "multi",
|
||||||
|
"description": "location Ids",
|
||||||
|
"name": "locations",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"allOf": [
|
"$ref": "#/definitions/repo.PaginationResult-repo_ItemSummary"
|
||||||
{
|
|
||||||
"$ref": "#/definitions/server.Results"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"items": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/repo.ItemSummary"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,7 +170,7 @@
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": ""
|
"description": "No Content"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -246,7 +271,7 @@
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": ""
|
"description": "No Content"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -346,7 +371,7 @@
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": ""
|
"description": "OK"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -462,7 +487,7 @@
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": ""
|
"description": "No Content"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -626,7 +651,7 @@
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": ""
|
"description": "No Content"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -790,7 +815,7 @@
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": ""
|
"description": "No Content"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -838,7 +863,7 @@
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": ""
|
"description": "No Content"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -895,7 +920,7 @@
|
|||||||
"summary": "User Logout",
|
"summary": "User Logout",
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": ""
|
"description": "No Content"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -914,7 +939,7 @@
|
|||||||
"summary": "User Token Refresh",
|
"summary": "User Token Refresh",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": ""
|
"description": "OK"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -941,7 +966,7 @@
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": ""
|
"description": "No Content"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1041,7 +1066,7 @@
|
|||||||
"summary": "Deletes the user account",
|
"summary": "Deletes the user account",
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": ""
|
"description": "No Content"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1062,7 +1087,7 @@
|
|||||||
"summary": "Update the current user's password // TODO:",
|
"summary": "Update the current user's password // TODO:",
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": ""
|
"description": "No Content"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1480,6 +1505,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"repo.PaginationResult-repo_ItemSummary": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"items": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/repo.ItemSummary"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"page": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"pageSize": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"repo.UserOut": {
|
"repo.UserOut": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -1533,9 +1578,7 @@
|
|||||||
"server.Results": {
|
"server.Results": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"items": {
|
"items": {}
|
||||||
"type": "any"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"server.ValidationError": {
|
"server.ValidationError": {
|
||||||
@@ -1572,6 +1615,9 @@
|
|||||||
"build": {
|
"build": {
|
||||||
"$ref": "#/definitions/v1.Build"
|
"$ref": "#/definitions/v1.Build"
|
||||||
},
|
},
|
||||||
|
"demo": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"health": {
|
"health": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -275,6 +275,19 @@ definitions:
|
|||||||
updatedAt:
|
updatedAt:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
repo.PaginationResult-repo_ItemSummary:
|
||||||
|
properties:
|
||||||
|
items:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/repo.ItemSummary'
|
||||||
|
type: array
|
||||||
|
page:
|
||||||
|
type: integer
|
||||||
|
pageSize:
|
||||||
|
type: integer
|
||||||
|
total:
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
repo.UserOut:
|
repo.UserOut:
|
||||||
properties:
|
properties:
|
||||||
email:
|
email:
|
||||||
@@ -310,8 +323,7 @@ definitions:
|
|||||||
type: object
|
type: object
|
||||||
server.Results:
|
server.Results:
|
||||||
properties:
|
properties:
|
||||||
items:
|
items: {}
|
||||||
type: any
|
|
||||||
type: object
|
type: object
|
||||||
server.ValidationError:
|
server.ValidationError:
|
||||||
properties:
|
properties:
|
||||||
@@ -335,6 +347,8 @@ definitions:
|
|||||||
properties:
|
properties:
|
||||||
build:
|
build:
|
||||||
$ref: '#/definitions/v1.Build'
|
$ref: '#/definitions/v1.Build'
|
||||||
|
demo:
|
||||||
|
type: boolean
|
||||||
health:
|
health:
|
||||||
type: boolean
|
type: boolean
|
||||||
message:
|
message:
|
||||||
@@ -424,20 +438,40 @@ paths:
|
|||||||
- User
|
- User
|
||||||
/v1/items:
|
/v1/items:
|
||||||
get:
|
get:
|
||||||
|
parameters:
|
||||||
|
- description: search string
|
||||||
|
in: query
|
||||||
|
name: q
|
||||||
|
type: string
|
||||||
|
- description: page number
|
||||||
|
in: query
|
||||||
|
name: page
|
||||||
|
type: integer
|
||||||
|
- description: items per page
|
||||||
|
in: query
|
||||||
|
name: pageSize
|
||||||
|
type: integer
|
||||||
|
- collectionFormat: multi
|
||||||
|
description: label Ids
|
||||||
|
in: query
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
name: labels
|
||||||
|
type: array
|
||||||
|
- collectionFormat: multi
|
||||||
|
description: location Ids
|
||||||
|
in: query
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
name: locations
|
||||||
|
type: array
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
allOf:
|
$ref: '#/definitions/repo.PaginationResult-repo_ItemSummary'
|
||||||
- $ref: '#/definitions/server.Results'
|
|
||||||
- properties:
|
|
||||||
items:
|
|
||||||
items:
|
|
||||||
$ref: '#/definitions/repo.ItemSummary'
|
|
||||||
type: array
|
|
||||||
type: object
|
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: Get All Items
|
summary: Get All Items
|
||||||
@@ -475,7 +509,7 @@ paths:
|
|||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"204":
|
"204":
|
||||||
description: ""
|
description: No Content
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: deletes a item
|
summary: deletes a item
|
||||||
@@ -581,7 +615,7 @@ paths:
|
|||||||
type: string
|
type: string
|
||||||
responses:
|
responses:
|
||||||
"204":
|
"204":
|
||||||
description: ""
|
description: No Content
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: retrieves an attachment for an item
|
summary: retrieves an attachment for an item
|
||||||
@@ -656,7 +690,7 @@ paths:
|
|||||||
- application/octet-stream
|
- application/octet-stream
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: ""
|
description: OK
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: retrieves an attachment for an item
|
summary: retrieves an attachment for an item
|
||||||
@@ -674,7 +708,7 @@ paths:
|
|||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"204":
|
"204":
|
||||||
description: ""
|
description: No Content
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: imports items into the database
|
summary: imports items into the database
|
||||||
@@ -733,7 +767,7 @@ paths:
|
|||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"204":
|
"204":
|
||||||
description: ""
|
description: No Content
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: deletes a label
|
summary: deletes a label
|
||||||
@@ -830,7 +864,7 @@ paths:
|
|||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"204":
|
"204":
|
||||||
description: ""
|
description: No Content
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: deletes a location
|
summary: deletes a location
|
||||||
@@ -897,7 +931,7 @@ paths:
|
|||||||
$ref: '#/definitions/v1.ChangePassword'
|
$ref: '#/definitions/v1.ChangePassword'
|
||||||
responses:
|
responses:
|
||||||
"204":
|
"204":
|
||||||
description: ""
|
description: No Content
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: Updates the users password
|
summary: Updates the users password
|
||||||
@@ -933,7 +967,7 @@ paths:
|
|||||||
post:
|
post:
|
||||||
responses:
|
responses:
|
||||||
"204":
|
"204":
|
||||||
description: ""
|
description: No Content
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: User Logout
|
summary: User Logout
|
||||||
@@ -946,7 +980,7 @@ paths:
|
|||||||
This does not validate that the user still exists within the database.
|
This does not validate that the user still exists within the database.
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: ""
|
description: OK
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: User Token Refresh
|
summary: User Token Refresh
|
||||||
@@ -965,7 +999,7 @@ paths:
|
|||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"204":
|
"204":
|
||||||
description: ""
|
description: No Content
|
||||||
summary: Get the current user
|
summary: Get the current user
|
||||||
tags:
|
tags:
|
||||||
- User
|
- User
|
||||||
@@ -975,7 +1009,7 @@ paths:
|
|||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"204":
|
"204":
|
||||||
description: ""
|
description: No Content
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: Deletes the user account
|
summary: Deletes the user account
|
||||||
@@ -1030,7 +1064,7 @@ paths:
|
|||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"204":
|
"204":
|
||||||
description: ""
|
description: No Content
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: 'Update the current user''s password // TODO:'
|
summary: 'Update the current user''s password // TODO:'
|
||||||
|
|||||||
@@ -20,28 +20,23 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Version = "0.1.0"
|
version = "nightly"
|
||||||
Commit = "HEAD"
|
commit = "HEAD"
|
||||||
BuildTime = "now"
|
buildTime = "now"
|
||||||
)
|
)
|
||||||
|
|
||||||
// @title Go API Templates
|
// @title Go API Templates
|
||||||
// @version 1.0
|
// @version 1.0
|
||||||
// @description This is a simple Rest API Server Template that implements some basic User and Authentication patterns to help you get started and bootstrap your next project!.
|
// @description This is a simple Rest API Server Template that implements some basic User and Authentication patterns to help you get started and bootstrap your next project!.
|
||||||
// @contact.name Don't
|
// @contact.name Don't
|
||||||
// @license.name MIT
|
// @license.name MIT
|
||||||
// @BasePath /api
|
// @BasePath /api
|
||||||
// @securityDefinitions.apikey Bearer
|
// @securityDefinitions.apikey Bearer
|
||||||
// @in header
|
// @in header
|
||||||
// @name Authorization
|
// @name Authorization
|
||||||
// @description "Type 'Bearer TOKEN' to correctly set the API Key"
|
// @description "Type 'Bearer TOKEN' to correctly set the API Key"
|
||||||
func main() {
|
func main() {
|
||||||
path := ""
|
cfg, err := config.New()
|
||||||
if len(os.Args) > 1 {
|
|
||||||
path = os.Args[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg, err := config.NewConfig(path)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -144,5 +139,11 @@ func run(cfg *config.Config) error {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// TODO: Remove through external API that does setup
|
||||||
|
if cfg.Demo {
|
||||||
|
log.Info().Msg("Running in demo mode, creating demo data")
|
||||||
|
app.SetupDemo()
|
||||||
|
}
|
||||||
|
|
||||||
return app.server.Start(routes)
|
return app.server.Start(routes)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,58 +42,59 @@ func (a *app) newRouter(repos *repo.AllRepos) *chi.Mux {
|
|||||||
// API Version 1
|
// API Version 1
|
||||||
|
|
||||||
v1Base := v1.BaseUrlFunc(prefix)
|
v1Base := v1.BaseUrlFunc(prefix)
|
||||||
v1Ctrl := v1.NewControllerV1(a.services, v1.WithMaxUploadSize(a.conf.Web.MaxUploadSize))
|
v1Ctrl := v1.NewControllerV1(a.services,
|
||||||
{
|
v1.WithMaxUploadSize(a.conf.Web.MaxUploadSize),
|
||||||
r.Get(v1Base("/status"), v1Ctrl.HandleBase(func() bool { return true }, v1.Build{
|
v1.WithDemoStatus(a.conf.Demo), // Disable Password Change in Demo Mode
|
||||||
Version: Version,
|
)
|
||||||
Commit: Commit,
|
r.Get(v1Base("/status"), v1Ctrl.HandleBase(func() bool { return true }, v1.Build{
|
||||||
BuildTime: BuildTime,
|
Version: version,
|
||||||
}))
|
Commit: commit,
|
||||||
|
BuildTime: buildTime,
|
||||||
|
}))
|
||||||
|
|
||||||
r.Post(v1Base("/users/register"), v1Ctrl.HandleUserRegistration())
|
r.Post(v1Base("/users/register"), v1Ctrl.HandleUserRegistration())
|
||||||
r.Post(v1Base("/users/login"), v1Ctrl.HandleAuthLogin())
|
r.Post(v1Base("/users/login"), v1Ctrl.HandleAuthLogin())
|
||||||
|
|
||||||
// Attachment download URl needs a `token` query param to be passed in the request.
|
// Attachment download URl needs a `token` query param to be passed in the request.
|
||||||
// and also needs to be outside of the `auth` middleware.
|
// and also needs to be outside of the `auth` middleware.
|
||||||
r.Get(v1Base("/items/{id}/attachments/download"), v1Ctrl.HandleItemAttachmentDownload())
|
r.Get(v1Base("/items/{id}/attachments/download"), v1Ctrl.HandleItemAttachmentDownload())
|
||||||
|
|
||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
r.Use(a.mwAuthToken)
|
r.Use(a.mwAuthToken)
|
||||||
r.Get(v1Base("/users/self"), v1Ctrl.HandleUserSelf())
|
r.Get(v1Base("/users/self"), v1Ctrl.HandleUserSelf())
|
||||||
r.Put(v1Base("/users/self"), v1Ctrl.HandleUserSelfUpdate())
|
r.Put(v1Base("/users/self"), v1Ctrl.HandleUserSelfUpdate())
|
||||||
r.Delete(v1Base("/users/self"), v1Ctrl.HandleUserSelfDelete())
|
r.Delete(v1Base("/users/self"), v1Ctrl.HandleUserSelfDelete())
|
||||||
r.Put(v1Base("/users/self/password"), v1Ctrl.HandleUserUpdatePassword())
|
r.Put(v1Base("/users/self/password"), v1Ctrl.HandleUserUpdatePassword())
|
||||||
r.Post(v1Base("/users/logout"), v1Ctrl.HandleAuthLogout())
|
r.Post(v1Base("/users/logout"), v1Ctrl.HandleAuthLogout())
|
||||||
r.Get(v1Base("/users/refresh"), v1Ctrl.HandleAuthRefresh())
|
r.Get(v1Base("/users/refresh"), v1Ctrl.HandleAuthRefresh())
|
||||||
r.Put(v1Base("/users/self/change-password"), v1Ctrl.HandleUserSelfChangePassword())
|
r.Put(v1Base("/users/self/change-password"), v1Ctrl.HandleUserSelfChangePassword())
|
||||||
|
|
||||||
r.Post(v1Base("/groups/invitations"), v1Ctrl.HandleGroupInvitationsCreate())
|
r.Post(v1Base("/groups/invitations"), v1Ctrl.HandleGroupInvitationsCreate())
|
||||||
|
|
||||||
r.Get(v1Base("/locations"), v1Ctrl.HandleLocationGetAll())
|
r.Get(v1Base("/locations"), v1Ctrl.HandleLocationGetAll())
|
||||||
r.Post(v1Base("/locations"), v1Ctrl.HandleLocationCreate())
|
r.Post(v1Base("/locations"), v1Ctrl.HandleLocationCreate())
|
||||||
r.Get(v1Base("/locations/{id}"), v1Ctrl.HandleLocationGet())
|
r.Get(v1Base("/locations/{id}"), v1Ctrl.HandleLocationGet())
|
||||||
r.Put(v1Base("/locations/{id}"), v1Ctrl.HandleLocationUpdate())
|
r.Put(v1Base("/locations/{id}"), v1Ctrl.HandleLocationUpdate())
|
||||||
r.Delete(v1Base("/locations/{id}"), v1Ctrl.HandleLocationDelete())
|
r.Delete(v1Base("/locations/{id}"), v1Ctrl.HandleLocationDelete())
|
||||||
|
|
||||||
r.Get(v1Base("/labels"), v1Ctrl.HandleLabelsGetAll())
|
r.Get(v1Base("/labels"), v1Ctrl.HandleLabelsGetAll())
|
||||||
r.Post(v1Base("/labels"), v1Ctrl.HandleLabelsCreate())
|
r.Post(v1Base("/labels"), v1Ctrl.HandleLabelsCreate())
|
||||||
r.Get(v1Base("/labels/{id}"), v1Ctrl.HandleLabelGet())
|
r.Get(v1Base("/labels/{id}"), v1Ctrl.HandleLabelGet())
|
||||||
r.Put(v1Base("/labels/{id}"), v1Ctrl.HandleLabelUpdate())
|
r.Put(v1Base("/labels/{id}"), v1Ctrl.HandleLabelUpdate())
|
||||||
r.Delete(v1Base("/labels/{id}"), v1Ctrl.HandleLabelDelete())
|
r.Delete(v1Base("/labels/{id}"), v1Ctrl.HandleLabelDelete())
|
||||||
|
|
||||||
r.Get(v1Base("/items"), v1Ctrl.HandleItemsGetAll())
|
r.Get(v1Base("/items"), v1Ctrl.HandleItemsGetAll())
|
||||||
r.Post(v1Base("/items/import"), v1Ctrl.HandleItemsImport())
|
r.Post(v1Base("/items/import"), v1Ctrl.HandleItemsImport())
|
||||||
r.Post(v1Base("/items"), v1Ctrl.HandleItemsCreate())
|
r.Post(v1Base("/items"), v1Ctrl.HandleItemsCreate())
|
||||||
r.Get(v1Base("/items/{id}"), v1Ctrl.HandleItemGet())
|
r.Get(v1Base("/items/{id}"), v1Ctrl.HandleItemGet())
|
||||||
r.Put(v1Base("/items/{id}"), v1Ctrl.HandleItemUpdate())
|
r.Put(v1Base("/items/{id}"), v1Ctrl.HandleItemUpdate())
|
||||||
r.Delete(v1Base("/items/{id}"), v1Ctrl.HandleItemDelete())
|
r.Delete(v1Base("/items/{id}"), v1Ctrl.HandleItemDelete())
|
||||||
|
|
||||||
r.Post(v1Base("/items/{id}/attachments"), v1Ctrl.HandleItemAttachmentCreate())
|
r.Post(v1Base("/items/{id}/attachments"), v1Ctrl.HandleItemAttachmentCreate())
|
||||||
r.Get(v1Base("/items/{id}/attachments/{attachment_id}"), v1Ctrl.HandleItemAttachmentToken())
|
r.Get(v1Base("/items/{id}/attachments/{attachment_id}"), v1Ctrl.HandleItemAttachmentToken())
|
||||||
r.Put(v1Base("/items/{id}/attachments/{attachment_id}"), v1Ctrl.HandleItemAttachmentUpdate())
|
r.Put(v1Base("/items/{id}/attachments/{attachment_id}"), v1Ctrl.HandleItemAttachmentUpdate())
|
||||||
r.Delete(v1Base("/items/{id}/attachments/{attachment_id}"), v1Ctrl.HandleItemAttachmentDelete())
|
r.Delete(v1Base("/items/{id}/attachments/{attachment_id}"), v1Ctrl.HandleItemAttachmentDelete())
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
r.NotFound(notFoundHandler())
|
r.NotFound(notFoundHandler())
|
||||||
return r
|
return r
|
||||||
|
|||||||
@@ -13,9 +13,16 @@ func WithMaxUploadSize(maxUploadSize int64) func(*V1Controller) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithDemoStatus(demoStatus bool) func(*V1Controller) {
|
||||||
|
return func(ctrl *V1Controller) {
|
||||||
|
ctrl.isDemo = demoStatus
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type V1Controller struct {
|
type V1Controller struct {
|
||||||
svc *services.AllServices
|
svc *services.AllServices
|
||||||
maxUploadSize int64
|
maxUploadSize int64
|
||||||
|
isDemo bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -30,7 +37,8 @@ type (
|
|||||||
Versions []string `json:"versions"`
|
Versions []string `json:"versions"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Build Build
|
Build Build `json:"build"`
|
||||||
|
Demo bool `json:"demo"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -48,17 +56,21 @@ func NewControllerV1(svc *services.AllServices, options ...func(*V1Controller))
|
|||||||
svc: svc,
|
svc: svc,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, opt := range options {
|
||||||
|
opt(ctrl)
|
||||||
|
}
|
||||||
|
|
||||||
return ctrl
|
return ctrl
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReadyFunc func() bool
|
type ReadyFunc func() bool
|
||||||
|
|
||||||
// HandleBase godoc
|
// HandleBase godoc
|
||||||
// @Summary Retrieves the basic information about the API
|
// @Summary Retrieves the basic information about the API
|
||||||
// @Tags Base
|
// @Tags Base
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} ApiSummary
|
// @Success 200 {object} ApiSummary
|
||||||
// @Router /v1/status [GET]
|
// @Router /v1/status [GET]
|
||||||
func (ctrl *V1Controller) HandleBase(ready ReadyFunc, build Build) http.HandlerFunc {
|
func (ctrl *V1Controller) HandleBase(ready ReadyFunc, build Build) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
server.Respond(w, http.StatusOK, ApiSummary{
|
server.Respond(w, http.StatusOK, ApiSummary{
|
||||||
@@ -66,6 +78,7 @@ func (ctrl *V1Controller) HandleBase(ready ReadyFunc, build Build) http.HandlerF
|
|||||||
Title: "Go API Template",
|
Title: "Go API Template",
|
||||||
Message: "Welcome to the Go API Template Application!",
|
Message: "Welcome to the Go API Template Application!",
|
||||||
Build: build,
|
Build: build,
|
||||||
|
Demo: ctrl.isDemo,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,15 +23,15 @@ type (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// HandleAuthLogin godoc
|
// HandleAuthLogin godoc
|
||||||
// @Summary User Login
|
// @Summary User Login
|
||||||
// @Tags Authentication
|
// @Tags Authentication
|
||||||
// @Accept x-www-form-urlencoded
|
// @Accept x-www-form-urlencoded
|
||||||
// @Accept application/json
|
// @Accept application/json
|
||||||
// @Param username formData string false "string" example(admin@admin.com)
|
// @Param username formData string false "string" example(admin@admin.com)
|
||||||
// @Param password formData string false "string" example(admin)
|
// @Param password formData string false "string" example(admin)
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} TokenResponse
|
// @Success 200 {object} TokenResponse
|
||||||
// @Router /v1/users/login [POST]
|
// @Router /v1/users/login [POST]
|
||||||
func (ctrl *V1Controller) HandleAuthLogin() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleAuthLogin() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
loginForm := &LoginForm{}
|
loginForm := &LoginForm{}
|
||||||
@@ -80,11 +80,11 @@ func (ctrl *V1Controller) HandleAuthLogin() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HandleAuthLogout godoc
|
// HandleAuthLogout godoc
|
||||||
// @Summary User Logout
|
// @Summary User Logout
|
||||||
// @Tags Authentication
|
// @Tags Authentication
|
||||||
// @Success 204
|
// @Success 204
|
||||||
// @Router /v1/users/logout [POST]
|
// @Router /v1/users/logout [POST]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleAuthLogout() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleAuthLogout() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
token := services.UseTokenCtx(r.Context())
|
token := services.UseTokenCtx(r.Context())
|
||||||
@@ -106,13 +106,13 @@ func (ctrl *V1Controller) HandleAuthLogout() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HandleAuthLogout godoc
|
// HandleAuthLogout godoc
|
||||||
// @Summary User Token Refresh
|
// @Summary User Token Refresh
|
||||||
// @Description handleAuthRefresh returns a handler that will issue a new token from an existing token.
|
// @Description handleAuthRefresh returns a handler that will issue a new token from an existing token.
|
||||||
// @Description This does not validate that the user still exists within the database.
|
// @Description This does not validate that the user still exists within the database.
|
||||||
// @Tags Authentication
|
// @Tags Authentication
|
||||||
// @Success 200
|
// @Success 200
|
||||||
// @Router /v1/users/refresh [GET]
|
// @Router /v1/users/refresh [GET]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleAuthRefresh() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleAuthRefresh() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
requestToken := services.UseTokenCtx(r.Context())
|
requestToken := services.UseTokenCtx(r.Context())
|
||||||
|
|||||||
@@ -23,13 +23,13 @@ type (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// HandleUserSelf godoc
|
// HandleUserSelf godoc
|
||||||
// @Summary Get the current user
|
// @Summary Get the current user
|
||||||
// @Tags User
|
// @Tags User
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param payload body GroupInvitationCreate true "User Data"
|
// @Param payload body GroupInvitationCreate true "User Data"
|
||||||
// @Success 200 {object} GroupInvitation
|
// @Success 200 {object} GroupInvitation
|
||||||
// @Router /v1/groups/invitations [Post]
|
// @Router /v1/groups/invitations [Post]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleGroupInvitationsCreate() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleGroupInvitationsCreate() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
data := GroupInvitationCreate{}
|
data := GroupInvitationCreate{}
|
||||||
|
|||||||
@@ -3,41 +3,84 @@ package v1
|
|||||||
import (
|
import (
|
||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/hay-kot/homebox/backend/internal/repo"
|
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||||
"github.com/hay-kot/homebox/backend/internal/services"
|
"github.com/hay-kot/homebox/backend/internal/services"
|
||||||
"github.com/hay-kot/homebox/backend/pkgs/server"
|
"github.com/hay-kot/homebox/backend/pkgs/server"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func uuidList(params url.Values, key string) []uuid.UUID {
|
||||||
|
var ids []uuid.UUID
|
||||||
|
for _, id := range params[key] {
|
||||||
|
uid, err := uuid.Parse(id)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ids = append(ids, uid)
|
||||||
|
}
|
||||||
|
return ids
|
||||||
|
}
|
||||||
|
|
||||||
|
func intOrNegativeOne(s string) int {
|
||||||
|
i, err := strconv.Atoi(s)
|
||||||
|
if err != nil {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractQuery(r *http.Request) repo.ItemQuery {
|
||||||
|
params := r.URL.Query()
|
||||||
|
|
||||||
|
page := intOrNegativeOne(params.Get("page"))
|
||||||
|
perPage := intOrNegativeOne(params.Get("perPage"))
|
||||||
|
|
||||||
|
return repo.ItemQuery{
|
||||||
|
Page: page,
|
||||||
|
PageSize: perPage,
|
||||||
|
Search: params.Get("q"),
|
||||||
|
LocationIDs: uuidList(params, "locations"),
|
||||||
|
LabelIDs: uuidList(params, "labels"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// HandleItemsGetAll godoc
|
// HandleItemsGetAll godoc
|
||||||
// @Summary Get All Items
|
// @Summary Get All Items
|
||||||
// @Tags Items
|
// @Tags Items
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} server.Results{items=[]repo.ItemSummary}
|
// @Param q query string false "search string"
|
||||||
// @Router /v1/items [GET]
|
// @Param page query int false "page number"
|
||||||
// @Security Bearer
|
// @Param pageSize query int false "items per page"
|
||||||
|
// @Param labels query []string false "label Ids" collectionFormat(multi)
|
||||||
|
// @Param locations query []string false "location Ids" collectionFormat(multi)
|
||||||
|
// @Success 200 {object} repo.PaginationResult[repo.ItemSummary]{}
|
||||||
|
// @Router /v1/items [GET]
|
||||||
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleItemsGetAll() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleItemsGetAll() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
user := services.UseUserCtx(r.Context())
|
ctx := services.NewContext(r.Context())
|
||||||
items, err := ctrl.svc.Items.GetAll(r.Context(), user.GroupID)
|
items, err := ctrl.svc.Items.Query(ctx, extractQuery(r))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("failed to get items")
|
log.Err(err).Msg("failed to get items")
|
||||||
server.RespondServerError(w)
|
server.RespondServerError(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
server.Respond(w, http.StatusOK, server.Results{Items: items})
|
server.Respond(w, http.StatusOK, items)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleItemsCreate godoc
|
// HandleItemsCreate godoc
|
||||||
// @Summary Create a new item
|
// @Summary Create a new item
|
||||||
// @Tags Items
|
// @Tags Items
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param payload body repo.ItemCreate true "Item Data"
|
// @Param payload body repo.ItemCreate true "Item Data"
|
||||||
// @Success 200 {object} repo.ItemSummary
|
// @Success 200 {object} repo.ItemSummary
|
||||||
// @Router /v1/items [POST]
|
// @Router /v1/items [POST]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleItemsCreate() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleItemsCreate() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
createData := repo.ItemCreate{}
|
createData := repo.ItemCreate{}
|
||||||
@@ -60,13 +103,13 @@ func (ctrl *V1Controller) HandleItemsCreate() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HandleItemDelete godocs
|
// HandleItemDelete godocs
|
||||||
// @Summary deletes a item
|
// @Summary deletes a item
|
||||||
// @Tags Items
|
// @Tags Items
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param id path string true "Item ID"
|
// @Param id path string true "Item ID"
|
||||||
// @Success 204
|
// @Success 204
|
||||||
// @Router /v1/items/{id} [DELETE]
|
// @Router /v1/items/{id} [DELETE]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleItemDelete() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleItemDelete() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
uid, user, err := ctrl.partialParseIdAndUser(w, r)
|
uid, user, err := ctrl.partialParseIdAndUser(w, r)
|
||||||
@@ -85,13 +128,13 @@ func (ctrl *V1Controller) HandleItemDelete() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HandleItemGet godocs
|
// HandleItemGet godocs
|
||||||
// @Summary Gets a item and fields
|
// @Summary Gets a item and fields
|
||||||
// @Tags Items
|
// @Tags Items
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param id path string true "Item ID"
|
// @Param id path string true "Item ID"
|
||||||
// @Success 200 {object} repo.ItemOut
|
// @Success 200 {object} repo.ItemOut
|
||||||
// @Router /v1/items/{id} [GET]
|
// @Router /v1/items/{id} [GET]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleItemGet() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleItemGet() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
uid, user, err := ctrl.partialParseIdAndUser(w, r)
|
uid, user, err := ctrl.partialParseIdAndUser(w, r)
|
||||||
@@ -110,14 +153,14 @@ func (ctrl *V1Controller) HandleItemGet() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HandleItemUpdate godocs
|
// HandleItemUpdate godocs
|
||||||
// @Summary updates a item
|
// @Summary updates a item
|
||||||
// @Tags Items
|
// @Tags Items
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param id path string true "Item ID"
|
// @Param id path string true "Item ID"
|
||||||
// @Param payload body repo.ItemUpdate true "Item Data"
|
// @Param payload body repo.ItemUpdate true "Item Data"
|
||||||
// @Success 200 {object} repo.ItemOut
|
// @Success 200 {object} repo.ItemOut
|
||||||
// @Router /v1/items/{id} [PUT]
|
// @Router /v1/items/{id} [PUT]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleItemUpdate() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleItemUpdate() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
body := repo.ItemUpdate{}
|
body := repo.ItemUpdate{}
|
||||||
@@ -143,13 +186,13 @@ func (ctrl *V1Controller) HandleItemUpdate() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HandleItemsImport godocs
|
// HandleItemsImport godocs
|
||||||
// @Summary imports items into the database
|
// @Summary imports items into the database
|
||||||
// @Tags Items
|
// @Tags Items
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 204
|
// @Success 204
|
||||||
// @Param csv formData file true "Image to upload"
|
// @Param csv formData file true "Image to upload"
|
||||||
// @Router /v1/items/import [Post]
|
// @Router /v1/items/import [Post]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleItemsImport() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleItemsImport() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
|||||||
@@ -21,17 +21,17 @@ type (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// HandleItemsImport godocs
|
// HandleItemsImport godocs
|
||||||
// @Summary imports items into the database
|
// @Summary imports items into the database
|
||||||
// @Tags Items
|
// @Tags Items
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param id path string true "Item ID"
|
// @Param id path string true "Item ID"
|
||||||
// @Param file formData file true "File attachment"
|
// @Param file formData file true "File attachment"
|
||||||
// @Param type formData string true "Type of file"
|
// @Param type formData string true "Type of file"
|
||||||
// @Param name formData string true "name of the file including extension"
|
// @Param name formData string true "name of the file including extension"
|
||||||
// @Success 200 {object} repo.ItemOut
|
// @Success 200 {object} repo.ItemOut
|
||||||
// @Failure 422 {object} []server.ValidationError
|
// @Failure 422 {object} []server.ValidationError
|
||||||
// @Router /v1/items/{id}/attachments [POST]
|
// @Router /v1/items/{id}/attachments [POST]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleItemAttachmentCreate() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleItemAttachmentCreate() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
err := r.ParseMultipartForm(ctrl.maxUploadSize << 20)
|
err := r.ParseMultipartForm(ctrl.maxUploadSize << 20)
|
||||||
@@ -98,14 +98,14 @@ func (ctrl *V1Controller) HandleItemAttachmentCreate() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HandleItemAttachmentGet godocs
|
// HandleItemAttachmentGet godocs
|
||||||
// @Summary retrieves an attachment for an item
|
// @Summary retrieves an attachment for an item
|
||||||
// @Tags Items
|
// @Tags Items
|
||||||
// @Produce application/octet-stream
|
// @Produce application/octet-stream
|
||||||
// @Param id path string true "Item ID"
|
// @Param id path string true "Item ID"
|
||||||
// @Param token query string true "Attachment token"
|
// @Param token query string true "Attachment token"
|
||||||
// @Success 200
|
// @Success 200
|
||||||
// @Router /v1/items/{id}/attachments/download [GET]
|
// @Router /v1/items/{id}/attachments/download [GET]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleItemAttachmentDownload() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleItemAttachmentDownload() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
token := server.GetParam(r, "token", "")
|
token := server.GetParam(r, "token", "")
|
||||||
@@ -125,39 +125,39 @@ func (ctrl *V1Controller) HandleItemAttachmentDownload() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HandleItemAttachmentToken godocs
|
// HandleItemAttachmentToken godocs
|
||||||
// @Summary retrieves an attachment for an item
|
// @Summary retrieves an attachment for an item
|
||||||
// @Tags Items
|
// @Tags Items
|
||||||
// @Produce application/octet-stream
|
// @Produce application/octet-stream
|
||||||
// @Param id path string true "Item ID"
|
// @Param id path string true "Item ID"
|
||||||
// @Param attachment_id path string true "Attachment ID"
|
// @Param attachment_id path string true "Attachment ID"
|
||||||
// @Success 200 {object} ItemAttachmentToken
|
// @Success 200 {object} ItemAttachmentToken
|
||||||
// @Router /v1/items/{id}/attachments/{attachment_id} [GET]
|
// @Router /v1/items/{id}/attachments/{attachment_id} [GET]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleItemAttachmentToken() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleItemAttachmentToken() http.HandlerFunc {
|
||||||
return ctrl.handleItemAttachmentsHandler
|
return ctrl.handleItemAttachmentsHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleItemAttachmentDelete godocs
|
// HandleItemAttachmentDelete godocs
|
||||||
// @Summary retrieves an attachment for an item
|
// @Summary retrieves an attachment for an item
|
||||||
// @Tags Items
|
// @Tags Items
|
||||||
// @Param id path string true "Item ID"
|
// @Param id path string true "Item ID"
|
||||||
// @Param attachment_id path string true "Attachment ID"
|
// @Param attachment_id path string true "Attachment ID"
|
||||||
// @Success 204
|
// @Success 204
|
||||||
// @Router /v1/items/{id}/attachments/{attachment_id} [DELETE]
|
// @Router /v1/items/{id}/attachments/{attachment_id} [DELETE]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleItemAttachmentDelete() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleItemAttachmentDelete() http.HandlerFunc {
|
||||||
return ctrl.handleItemAttachmentsHandler
|
return ctrl.handleItemAttachmentsHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleItemAttachmentUpdate godocs
|
// HandleItemAttachmentUpdate godocs
|
||||||
// @Summary retrieves an attachment for an item
|
// @Summary retrieves an attachment for an item
|
||||||
// @Tags Items
|
// @Tags Items
|
||||||
// @Param id path string true "Item ID"
|
// @Param id path string true "Item ID"
|
||||||
// @Param attachment_id path string true "Attachment ID"
|
// @Param attachment_id path string true "Attachment ID"
|
||||||
// @Param payload body repo.ItemAttachmentUpdate true "Attachment Update"
|
// @Param payload body repo.ItemAttachmentUpdate true "Attachment Update"
|
||||||
// @Success 200 {object} repo.ItemOut
|
// @Success 200 {object} repo.ItemOut
|
||||||
// @Router /v1/items/{id}/attachments/{attachment_id} [PUT]
|
// @Router /v1/items/{id}/attachments/{attachment_id} [PUT]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleItemAttachmentUpdate() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleItemAttachmentUpdate() http.HandlerFunc {
|
||||||
return ctrl.handleItemAttachmentsHandler
|
return ctrl.handleItemAttachmentsHandler
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,12 +11,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// HandleLabelsGetAll godoc
|
// HandleLabelsGetAll godoc
|
||||||
// @Summary Get All Labels
|
// @Summary Get All Labels
|
||||||
// @Tags Labels
|
// @Tags Labels
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} server.Results{items=[]repo.LabelOut}
|
// @Success 200 {object} server.Results{items=[]repo.LabelOut}
|
||||||
// @Router /v1/labels [GET]
|
// @Router /v1/labels [GET]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleLabelsGetAll() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleLabelsGetAll() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
user := services.UseUserCtx(r.Context())
|
user := services.UseUserCtx(r.Context())
|
||||||
@@ -31,13 +31,13 @@ func (ctrl *V1Controller) HandleLabelsGetAll() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HandleLabelsCreate godoc
|
// HandleLabelsCreate godoc
|
||||||
// @Summary Create a new label
|
// @Summary Create a new label
|
||||||
// @Tags Labels
|
// @Tags Labels
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param payload body repo.LabelCreate true "Label Data"
|
// @Param payload body repo.LabelCreate true "Label Data"
|
||||||
// @Success 200 {object} repo.LabelSummary
|
// @Success 200 {object} repo.LabelSummary
|
||||||
// @Router /v1/labels [POST]
|
// @Router /v1/labels [POST]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleLabelsCreate() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleLabelsCreate() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
createData := repo.LabelCreate{}
|
createData := repo.LabelCreate{}
|
||||||
@@ -61,13 +61,13 @@ func (ctrl *V1Controller) HandleLabelsCreate() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HandleLabelDelete godocs
|
// HandleLabelDelete godocs
|
||||||
// @Summary deletes a label
|
// @Summary deletes a label
|
||||||
// @Tags Labels
|
// @Tags Labels
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param id path string true "Label ID"
|
// @Param id path string true "Label ID"
|
||||||
// @Success 204
|
// @Success 204
|
||||||
// @Router /v1/labels/{id} [DELETE]
|
// @Router /v1/labels/{id} [DELETE]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleLabelDelete() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleLabelDelete() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
uid, user, err := ctrl.partialParseIdAndUser(w, r)
|
uid, user, err := ctrl.partialParseIdAndUser(w, r)
|
||||||
@@ -86,13 +86,13 @@ func (ctrl *V1Controller) HandleLabelDelete() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HandleLabelGet godocs
|
// HandleLabelGet godocs
|
||||||
// @Summary Gets a label and fields
|
// @Summary Gets a label and fields
|
||||||
// @Tags Labels
|
// @Tags Labels
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param id path string true "Label ID"
|
// @Param id path string true "Label ID"
|
||||||
// @Success 200 {object} repo.LabelOut
|
// @Success 200 {object} repo.LabelOut
|
||||||
// @Router /v1/labels/{id} [GET]
|
// @Router /v1/labels/{id} [GET]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleLabelGet() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleLabelGet() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
uid, user, err := ctrl.partialParseIdAndUser(w, r)
|
uid, user, err := ctrl.partialParseIdAndUser(w, r)
|
||||||
@@ -118,13 +118,13 @@ func (ctrl *V1Controller) HandleLabelGet() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HandleLabelUpdate godocs
|
// HandleLabelUpdate godocs
|
||||||
// @Summary updates a label
|
// @Summary updates a label
|
||||||
// @Tags Labels
|
// @Tags Labels
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param id path string true "Label ID"
|
// @Param id path string true "Label ID"
|
||||||
// @Success 200 {object} repo.LabelOut
|
// @Success 200 {object} repo.LabelOut
|
||||||
// @Router /v1/labels/{id} [PUT]
|
// @Router /v1/labels/{id} [PUT]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleLabelUpdate() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleLabelUpdate() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
body := repo.LabelUpdate{}
|
body := repo.LabelUpdate{}
|
||||||
|
|||||||
@@ -11,12 +11,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// HandleLocationGetAll godoc
|
// HandleLocationGetAll godoc
|
||||||
// @Summary Get All Locations
|
// @Summary Get All Locations
|
||||||
// @Tags Locations
|
// @Tags Locations
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} server.Results{items=[]repo.LocationOutCount}
|
// @Success 200 {object} server.Results{items=[]repo.LocationOutCount}
|
||||||
// @Router /v1/locations [GET]
|
// @Router /v1/locations [GET]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleLocationGetAll() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleLocationGetAll() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
user := services.UseUserCtx(r.Context())
|
user := services.UseUserCtx(r.Context())
|
||||||
@@ -32,13 +32,13 @@ func (ctrl *V1Controller) HandleLocationGetAll() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HandleLocationCreate godoc
|
// HandleLocationCreate godoc
|
||||||
// @Summary Create a new location
|
// @Summary Create a new location
|
||||||
// @Tags Locations
|
// @Tags Locations
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param payload body repo.LocationCreate true "Location Data"
|
// @Param payload body repo.LocationCreate true "Location Data"
|
||||||
// @Success 200 {object} repo.LocationSummary
|
// @Success 200 {object} repo.LocationSummary
|
||||||
// @Router /v1/locations [POST]
|
// @Router /v1/locations [POST]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleLocationCreate() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleLocationCreate() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
createData := repo.LocationCreate{}
|
createData := repo.LocationCreate{}
|
||||||
@@ -61,13 +61,13 @@ func (ctrl *V1Controller) HandleLocationCreate() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HandleLocationDelete godocs
|
// HandleLocationDelete godocs
|
||||||
// @Summary deletes a location
|
// @Summary deletes a location
|
||||||
// @Tags Locations
|
// @Tags Locations
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param id path string true "Location ID"
|
// @Param id path string true "Location ID"
|
||||||
// @Success 204
|
// @Success 204
|
||||||
// @Router /v1/locations/{id} [DELETE]
|
// @Router /v1/locations/{id} [DELETE]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleLocationDelete() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleLocationDelete() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
uid, user, err := ctrl.partialParseIdAndUser(w, r)
|
uid, user, err := ctrl.partialParseIdAndUser(w, r)
|
||||||
@@ -86,13 +86,13 @@ func (ctrl *V1Controller) HandleLocationDelete() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HandleLocationGet godocs
|
// HandleLocationGet godocs
|
||||||
// @Summary Gets a location and fields
|
// @Summary Gets a location and fields
|
||||||
// @Tags Locations
|
// @Tags Locations
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param id path string true "Location ID"
|
// @Param id path string true "Location ID"
|
||||||
// @Success 200 {object} repo.LocationOut
|
// @Success 200 {object} repo.LocationOut
|
||||||
// @Router /v1/locations/{id} [GET]
|
// @Router /v1/locations/{id} [GET]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleLocationGet() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleLocationGet() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
uid, user, err := ctrl.partialParseIdAndUser(w, r)
|
uid, user, err := ctrl.partialParseIdAndUser(w, r)
|
||||||
@@ -123,13 +123,13 @@ func (ctrl *V1Controller) HandleLocationGet() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HandleLocationUpdate godocs
|
// HandleLocationUpdate godocs
|
||||||
// @Summary updates a location
|
// @Summary updates a location
|
||||||
// @Tags Locations
|
// @Tags Locations
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param id path string true "Location ID"
|
// @Param id path string true "Location ID"
|
||||||
// @Success 200 {object} repo.LocationOut
|
// @Success 200 {object} repo.LocationOut
|
||||||
// @Router /v1/locations/{id} [PUT]
|
// @Router /v1/locations/{id} [PUT]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleLocationUpdate() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleLocationUpdate() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
body := repo.LocationUpdate{}
|
body := repo.LocationUpdate{}
|
||||||
|
|||||||
@@ -11,12 +11,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// HandleUserSelf godoc
|
// HandleUserSelf godoc
|
||||||
// @Summary Get the current user
|
// @Summary Get the current user
|
||||||
// @Tags User
|
// @Tags User
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param payload body services.UserRegistration true "User Data"
|
// @Param payload body services.UserRegistration true "User Data"
|
||||||
// @Success 204
|
// @Success 204
|
||||||
// @Router /v1/users/register [Post]
|
// @Router /v1/users/register [Post]
|
||||||
func (ctrl *V1Controller) HandleUserRegistration() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleUserRegistration() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
regData := services.UserRegistration{}
|
regData := services.UserRegistration{}
|
||||||
@@ -39,12 +39,12 @@ func (ctrl *V1Controller) HandleUserRegistration() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HandleUserSelf godoc
|
// HandleUserSelf godoc
|
||||||
// @Summary Get the current user
|
// @Summary Get the current user
|
||||||
// @Tags User
|
// @Tags User
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} server.Result{item=repo.UserOut}
|
// @Success 200 {object} server.Result{item=repo.UserOut}
|
||||||
// @Router /v1/users/self [GET]
|
// @Router /v1/users/self [GET]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleUserSelf() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleUserSelf() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
token := services.UseTokenCtx(r.Context())
|
token := services.UseTokenCtx(r.Context())
|
||||||
@@ -60,13 +60,13 @@ func (ctrl *V1Controller) HandleUserSelf() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HandleUserSelfUpdate godoc
|
// HandleUserSelfUpdate godoc
|
||||||
// @Summary Update the current user
|
// @Summary Update the current user
|
||||||
// @Tags User
|
// @Tags User
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param payload body repo.UserUpdate true "User Data"
|
// @Param payload body repo.UserUpdate true "User Data"
|
||||||
// @Success 200 {object} server.Result{item=repo.UserUpdate}
|
// @Success 200 {object} server.Result{item=repo.UserUpdate}
|
||||||
// @Router /v1/users/self [PUT]
|
// @Router /v1/users/self [PUT]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleUserSelfUpdate() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleUserSelfUpdate() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
updateData := repo.UserUpdate{}
|
updateData := repo.UserUpdate{}
|
||||||
@@ -90,26 +90,31 @@ func (ctrl *V1Controller) HandleUserSelfUpdate() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HandleUserUpdatePassword godoc
|
// HandleUserUpdatePassword godoc
|
||||||
// @Summary Update the current user's password // TODO:
|
// @Summary Update the current user's password // TODO:
|
||||||
// @Tags User
|
// @Tags User
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 204
|
// @Success 204
|
||||||
// @Router /v1/users/self/password [PUT]
|
// @Router /v1/users/self/password [PUT]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleUserUpdatePassword() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleUserUpdatePassword() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleUserSelfDelete godoc
|
// HandleUserSelfDelete godoc
|
||||||
// @Summary Deletes the user account
|
// @Summary Deletes the user account
|
||||||
// @Tags User
|
// @Tags User
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 204
|
// @Success 204
|
||||||
// @Router /v1/users/self [DELETE]
|
// @Router /v1/users/self [DELETE]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleUserSelfDelete() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleUserSelfDelete() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if ctrl.isDemo {
|
||||||
|
server.RespondError(w, http.StatusForbidden, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
actor := services.UseUserCtx(r.Context())
|
actor := services.UseUserCtx(r.Context())
|
||||||
if err := ctrl.svc.User.DeleteSelf(r.Context(), actor.ID); err != nil {
|
if err := ctrl.svc.User.DeleteSelf(r.Context(), actor.ID); err != nil {
|
||||||
server.RespondError(w, http.StatusInternalServerError, err)
|
server.RespondError(w, http.StatusInternalServerError, err)
|
||||||
@@ -128,14 +133,19 @@ type (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// HandleUserSelfChangePassword godoc
|
// HandleUserSelfChangePassword godoc
|
||||||
// @Summary Updates the users password
|
// @Summary Updates the users password
|
||||||
// @Tags User
|
// @Tags User
|
||||||
// @Success 204
|
// @Success 204
|
||||||
// @Param payload body ChangePassword true "Password Payload"
|
// @Param payload body ChangePassword true "Password Payload"
|
||||||
// @Router /v1/users/change-password [PUT]
|
// @Router /v1/users/change-password [PUT]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleUserSelfChangePassword() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleUserSelfChangePassword() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if ctrl.isDemo {
|
||||||
|
server.RespondError(w, http.StatusForbidden, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var cp ChangePassword
|
var cp ChangePassword
|
||||||
err := server.Decode(r, &cp)
|
err := server.Decode(r, &cp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -42,6 +42,5 @@ require (
|
|||||||
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 // indirect
|
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 // indirect
|
||||||
golang.org/x/text v0.3.7 // indirect
|
golang.org/x/text v0.3.7 // indirect
|
||||||
golang.org/x/tools v0.1.13-0.20220804200503-81c7dc4e4efa // indirect
|
golang.org/x/tools v0.1.13-0.20220804200503-81c7dc4e4efa // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/ardanlabs/conf/v2"
|
"github.com/ardanlabs/conf/v2"
|
||||||
"github.com/ardanlabs/conf/v2/yaml"
|
|
||||||
|
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
@@ -23,6 +22,7 @@ type Config struct {
|
|||||||
Log LoggerConf `yaml:"logger"`
|
Log LoggerConf `yaml:"logger"`
|
||||||
Mailer MailerConf `yaml:"mailer"`
|
Mailer MailerConf `yaml:"mailer"`
|
||||||
Swagger SwaggerConf `yaml:"swagger"`
|
Swagger SwaggerConf `yaml:"swagger"`
|
||||||
|
Demo bool `yaml:"demo"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SwaggerConf struct {
|
type SwaggerConf struct {
|
||||||
@@ -36,24 +36,13 @@ type WebConfig struct {
|
|||||||
MaxUploadSize int64 `yaml:"max_file_upload" conf:"default:10"`
|
MaxUploadSize int64 `yaml:"max_file_upload" conf:"default:10"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConfig parses the CLI/Config file and returns a Config struct. If the file argument is an empty string, the
|
// New parses the CLI/Config file and returns a Config struct. If the file argument is an empty string, the
|
||||||
// file is not read. If the file is not empty, the file is read and the Config struct is returned.
|
// file is not read. If the file is not empty, the file is read and the Config struct is returned.
|
||||||
func NewConfig(file string) (*Config, error) {
|
func New() (*Config, error) {
|
||||||
var cfg Config
|
var cfg Config
|
||||||
|
|
||||||
const prefix = "HBOX"
|
const prefix = "HBOX"
|
||||||
|
|
||||||
help, err := func() (string, error) {
|
help, err := conf.Parse(prefix, &cfg)
|
||||||
if _, err := os.Stat(file); errors.Is(err, os.ErrNotExist) {
|
|
||||||
return conf.Parse(prefix, &cfg)
|
|
||||||
} else {
|
|
||||||
yamlData, err := os.ReadFile(file)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return conf.Parse(prefix, &cfg, yaml.WithData(yamlData))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, conf.ErrHelpWanted) {
|
if errors.Is(err, conf.ErrHelpWanted) {
|
||||||
|
|||||||
12
backend/internal/repo/pagination.go
Normal file
12
backend/internal/repo/pagination.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package repo
|
||||||
|
|
||||||
|
type PaginationResult[T any] struct {
|
||||||
|
Page int `json:"page"`
|
||||||
|
PageSize int `json:"pageSize"`
|
||||||
|
Total int `json:"total"`
|
||||||
|
Items []T `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func calculateOffset(page, pageSize int) int {
|
||||||
|
return (page - 1) * pageSize
|
||||||
|
}
|
||||||
@@ -8,6 +8,8 @@ import (
|
|||||||
"github.com/hay-kot/homebox/backend/ent"
|
"github.com/hay-kot/homebox/backend/ent"
|
||||||
"github.com/hay-kot/homebox/backend/ent/group"
|
"github.com/hay-kot/homebox/backend/ent/group"
|
||||||
"github.com/hay-kot/homebox/backend/ent/item"
|
"github.com/hay-kot/homebox/backend/ent/item"
|
||||||
|
"github.com/hay-kot/homebox/backend/ent/label"
|
||||||
|
"github.com/hay-kot/homebox/backend/ent/location"
|
||||||
"github.com/hay-kot/homebox/backend/ent/predicate"
|
"github.com/hay-kot/homebox/backend/ent/predicate"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,6 +18,14 @@ type ItemsRepository struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
ItemQuery struct {
|
||||||
|
Page int
|
||||||
|
PageSize int
|
||||||
|
Search string `json:"search"`
|
||||||
|
LocationIDs []uuid.UUID `json:"locationIds"`
|
||||||
|
LabelIDs []uuid.UUID `json:"labelIds"`
|
||||||
|
}
|
||||||
|
|
||||||
ItemCreate struct {
|
ItemCreate struct {
|
||||||
ImportRef string `json:"-"`
|
ImportRef string `json:"-"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
@@ -206,6 +216,64 @@ func (e *ItemsRepository) GetOneByGroup(ctx context.Context, gid, id uuid.UUID)
|
|||||||
return e.getOne(ctx, item.ID(id), item.HasGroupWith(group.ID(gid)))
|
return e.getOne(ctx, item.ID(id), item.HasGroupWith(group.ID(gid)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QueryByGroup returns a list of items that belong to a specific group based on the provided query.
|
||||||
|
func (e *ItemsRepository) QueryByGroup(ctx context.Context, gid uuid.UUID, q ItemQuery) (PaginationResult[ItemSummary], error) {
|
||||||
|
qb := e.db.Item.Query().Where(item.HasGroupWith(group.ID(gid)))
|
||||||
|
|
||||||
|
if len(q.LabelIDs) > 0 {
|
||||||
|
labels := make([]predicate.Item, 0, len(q.LabelIDs))
|
||||||
|
for _, l := range q.LabelIDs {
|
||||||
|
labels = append(labels, item.HasLabelWith(label.ID(l)))
|
||||||
|
}
|
||||||
|
qb = qb.Where(item.Or(labels...))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(q.LocationIDs) > 0 {
|
||||||
|
locations := make([]predicate.Item, 0, len(q.LocationIDs))
|
||||||
|
for _, l := range q.LocationIDs {
|
||||||
|
locations = append(locations, item.HasLocationWith(location.ID(l)))
|
||||||
|
}
|
||||||
|
qb = qb.Where(item.Or(locations...))
|
||||||
|
}
|
||||||
|
|
||||||
|
if q.Search != "" {
|
||||||
|
qb.Where(
|
||||||
|
item.Or(
|
||||||
|
item.NameContainsFold(q.Search),
|
||||||
|
item.DescriptionContainsFold(q.Search),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if q.Page != -1 || q.PageSize != -1 {
|
||||||
|
qb = qb.
|
||||||
|
Offset(calculateOffset(q.Page, q.PageSize)).
|
||||||
|
Limit(q.PageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
items, err := mapItemsSummaryErr(
|
||||||
|
qb.WithLabel().
|
||||||
|
WithLocation().
|
||||||
|
All(ctx),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return PaginationResult[ItemSummary]{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := qb.Count(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return PaginationResult[ItemSummary]{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return PaginationResult[ItemSummary]{
|
||||||
|
Page: q.Page,
|
||||||
|
PageSize: q.PageSize,
|
||||||
|
Total: count,
|
||||||
|
Items: items,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// GetAll returns all the items in the database with the Labels and Locations eager loaded.
|
// GetAll returns all the items in the database with the Labels and Locations eager loaded.
|
||||||
func (e *ItemsRepository) GetAll(ctx context.Context, gid uuid.UUID) ([]ItemSummary, error) {
|
func (e *ItemsRepository) GetAll(ctx context.Context, gid uuid.UUID) ([]ItemSummary, error) {
|
||||||
return mapItemsSummaryErr(e.db.Item.Query().
|
return mapItemsSummaryErr(e.db.Item.Query().
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ func (svc *ItemService) GetOne(ctx context.Context, gid uuid.UUID, id uuid.UUID)
|
|||||||
return svc.repo.Items.GetOneByGroup(ctx, gid, id)
|
return svc.repo.Items.GetOneByGroup(ctx, gid, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (svc *ItemService) Query(ctx Context, q repo.ItemQuery) (repo.PaginationResult[repo.ItemSummary], error) {
|
||||||
|
return svc.repo.Items.QueryByGroup(ctx, ctx.GID, q)
|
||||||
|
}
|
||||||
|
|
||||||
func (svc *ItemService) GetAll(ctx context.Context, gid uuid.UUID) ([]repo.ItemSummary, error) {
|
func (svc *ItemService) GetAll(ctx context.Context, gid uuid.UUID) ([]repo.ItemSummary, error) {
|
||||||
return svc.repo.Items.GetAll(ctx, gid)
|
return svc.repo.Items.GetAll(ctx, gid)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ func (f *Faker) Path() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *Faker) Email() string {
|
func (f *Faker) Email() string {
|
||||||
return f.Str(10) + "@email.com"
|
return f.Str(10) + "@example.com"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Faker) Bool() bool {
|
func (f *Faker) Bool() bool {
|
||||||
|
|||||||
1
fly.toml
1
fly.toml
@@ -8,6 +8,7 @@ processes = []
|
|||||||
|
|
||||||
[env]
|
[env]
|
||||||
PORT = "7745"
|
PORT = "7745"
|
||||||
|
HBOX_DEMO = "true"
|
||||||
|
|
||||||
[experimental]
|
[experimental]
|
||||||
allowed_public_ports = []
|
allowed_public_ports = []
|
||||||
|
|||||||
@@ -18,6 +18,10 @@
|
|||||||
name: "Home",
|
name: "Home",
|
||||||
href: "/home",
|
href: "/home",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Items",
|
||||||
|
href: "/items",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Logout",
|
name: "Logout",
|
||||||
action: logout,
|
action: logout,
|
||||||
|
|||||||
@@ -1,17 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="label" class="dropdown dropdown-end dropdown-top w-full">
|
<div ref="label" class="dropdown dropdown-end dropdown-top w-full">
|
||||||
<FormTextField v-model="dateText" tabindex="0" label="Date" :inline="inline" readonly />
|
<FormTextField v-model="dateText" tabindex="0" label="Date" :inline="inline" readonly />
|
||||||
<div tabindex="0" class="mt-1 card compact dropdown-content shadow bg-base-100 rounded-box w-64" @blur="resetTime">
|
<div tabindex="0" class="card compact dropdown-content shadow bg-base-100 rounded-box w-64" @blur="resetTime">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="flex justify-between items-center">
|
|
||||||
<button class="btn btn-xs" @click="prevMonth">
|
|
||||||
<Icon class="h-5 w-5" name="mdi-arrow-left"></Icon>
|
|
||||||
</button>
|
|
||||||
<p class="text-center">{{ month }} {{ year }}</p>
|
|
||||||
<button class="btn btn-xs" @click="nextMonth">
|
|
||||||
<Icon class="h-5 w-5" name="mdi-arrow-right"></Icon>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-7 gap-2">
|
<div class="grid grid-cols-7 gap-2">
|
||||||
<div v-for="d in daysIdx" :key="d">
|
<div v-for="d in daysIdx" :key="d">
|
||||||
<p class="text-center">
|
<p class="text-center">
|
||||||
@@ -30,6 +21,15 @@
|
|||||||
<div v-else :key="`${day.number}-empty`"></div>
|
<div v-else :key="`${day.number}-empty`"></div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex justify-between mt-1 items-center">
|
||||||
|
<button class="btn btn-xs" @click="prevMonth">
|
||||||
|
<Icon class="h-5 w-5" name="mdi-arrow-left"></Icon>
|
||||||
|
</button>
|
||||||
|
<p class="text-center">{{ month }} {{ year }}</p>
|
||||||
|
<button class="btn btn-xs" @click="nextMonth">
|
||||||
|
<Icon class="h-5 w-5" name="mdi-arrow-right"></Icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,7 +11,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<ul
|
<ul
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
class="dropdown-content mb-1 menu shadow border border-gray-400 rounded bg-base-100 w-full z-[9999] max-h-60 overflow-y-scroll scroll-bar"
|
style="display: inline"
|
||||||
|
class="dropdown-content mb-1 menu shadow border border-gray-400 rounded bg-base-100 w-full z-[9999] max-h-60 overflow-y-scroll"
|
||||||
>
|
>
|
||||||
<li
|
<li
|
||||||
v-for="(obj, idx) in items"
|
v-for="(obj, idx) in items"
|
||||||
|
|||||||
@@ -42,54 +42,31 @@
|
|||||||
default: null,
|
default: null,
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
selectFirst: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function syncSelect() {
|
const selectedIdx = ref(-1);
|
||||||
if (!props.modelValue) {
|
const internalSelected = useVModel(props, "modelValue", emit);
|
||||||
if (props.selectFirst) {
|
|
||||||
selectedIdx.value = 0;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Check if we're already synced
|
|
||||||
if (props.value) {
|
|
||||||
if (props.modelValue[props.value] === props.items[selectedIdx.value][props.value]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (props.modelValue === props.items[selectedIdx.value]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const idx = props.items.findIndex(item => {
|
watch(selectedIdx, newVal => {
|
||||||
if (props.value) {
|
internalSelected.value = props.items[newVal];
|
||||||
return item[props.value] === props.modelValue;
|
});
|
||||||
}
|
|
||||||
return item === props.modelValue;
|
|
||||||
});
|
|
||||||
|
|
||||||
selectedIdx.value = idx;
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
function compare(a: any, b: any): boolean {
|
||||||
|
if (props.value != null) {
|
||||||
|
return a[props.value] === b[props.value];
|
||||||
|
}
|
||||||
|
return a === b;
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.modelValue,
|
internalSelected,
|
||||||
() => {
|
() => {
|
||||||
syncSelect();
|
const idx = props.items.findIndex(item => compare(item, internalSelected.value));
|
||||||
}
|
selectedIdx.value = idx;
|
||||||
);
|
},
|
||||||
|
{
|
||||||
const selectedIdx = ref(0);
|
immediate: true,
|
||||||
watch(
|
|
||||||
() => selectedIdx.value,
|
|
||||||
() => {
|
|
||||||
if (props.value) {
|
|
||||||
emit("update:modelValue", props.items[selectedIdx.value][props.value]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
emit("update:modelValue", props.items[selectedIdx.value]);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -3,13 +3,13 @@
|
|||||||
<label class="label">
|
<label class="label">
|
||||||
<span class="label-text">{{ label }}</span>
|
<span class="label-text">{{ label }}</span>
|
||||||
</label>
|
</label>
|
||||||
<input ref="input" v-model="value" :type="type" class="input input-bordered w-full" />
|
<input ref="input" v-model="value" :placeholder="placeholder" :type="type" class="input input-bordered w-full" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="sm:grid sm:grid-cols-4 sm:items-start sm:gap-4">
|
<div v-else class="sm:grid sm:grid-cols-4 sm:items-start sm:gap-4">
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<span class="label-text">{{ label }}</span>
|
<span class="label-text">{{ label }}</span>
|
||||||
</label>
|
</label>
|
||||||
<input v-model="value" class="input input-bordered col-span-3 w-full mt-2" />
|
<input v-model="value" :placeholder="placeholder" class="input input-bordered col-span-3 w-full mt-2" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -35,6 +35,10 @@
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const input = ref<HTMLElement | null>(null);
|
const input = ref<HTMLElement | null>(null);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<BaseModal v-model="modal">
|
<BaseModal v-model="modal">
|
||||||
<template #title> Create Item </template>
|
<template #title> Create Item </template>
|
||||||
<form @submit.prevent="create">
|
<form @submit.prevent="create">
|
||||||
<FormSelect v-model="form.location" label="Location" :items="locations ?? []" select-first />
|
<FormSelect v-model="form.location" label="Location" :items="locations ?? []" />
|
||||||
<FormTextField
|
<FormTextField
|
||||||
ref="locationNameRef"
|
ref="locationNameRef"
|
||||||
v-model="form.name"
|
v-model="form.name"
|
||||||
|
|||||||
32
frontend/composables/use-min-loader.ts
Normal file
32
frontend/composables/use-min-loader.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { WritableComputedRef } from "vue";
|
||||||
|
|
||||||
|
export function useMinLoader(ms = 500): WritableComputedRef<boolean> {
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
|
const locked = ref(false);
|
||||||
|
|
||||||
|
const minLoading = computed({
|
||||||
|
get: () => loading.value,
|
||||||
|
set: value => {
|
||||||
|
if (value) {
|
||||||
|
loading.value = true;
|
||||||
|
|
||||||
|
if (!locked.value) {
|
||||||
|
locked.value = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
locked.value = false;
|
||||||
|
}, ms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!value && !locked.value) {
|
||||||
|
loading.value = false;
|
||||||
|
} else if (!value && locked.value) {
|
||||||
|
setTimeout(() => {
|
||||||
|
loading.value = false;
|
||||||
|
}, ms);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return minLoading;
|
||||||
|
}
|
||||||
@@ -8,12 +8,19 @@ import {
|
|||||||
ItemSummary,
|
ItemSummary,
|
||||||
ItemUpdate,
|
ItemUpdate,
|
||||||
} from "../types/data-contracts";
|
} from "../types/data-contracts";
|
||||||
import { AttachmentTypes } from "../types/non-generated";
|
import { AttachmentTypes, PaginationResult } from "../types/non-generated";
|
||||||
import { Results } from "./types";
|
|
||||||
|
export type ItemsQuery = {
|
||||||
|
page?: number;
|
||||||
|
pageSize?: number;
|
||||||
|
locations?: string[];
|
||||||
|
labels?: string[];
|
||||||
|
q?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export class ItemsApi extends BaseAPI {
|
export class ItemsApi extends BaseAPI {
|
||||||
getAll() {
|
getAll(q: ItemsQuery = {}) {
|
||||||
return this.http.get<Results<ItemSummary>>({ url: route("/items") });
|
return this.http.get<PaginationResult<ItemSummary>>({ url: route("/items", q) });
|
||||||
}
|
}
|
||||||
|
|
||||||
create(item: ItemCreate) {
|
create(item: ItemCreate) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { BaseAPI, route } from "../base";
|
import { BaseAPI, route } from "../base";
|
||||||
import { LabelCreate, LabelOut } from "../types/data-contracts";
|
import { LabelCreate, LabelOut } from "../types/data-contracts";
|
||||||
import { Results } from "./types";
|
import { Results } from "../types/non-generated";
|
||||||
|
|
||||||
export class LabelsApi extends BaseAPI {
|
export class LabelsApi extends BaseAPI {
|
||||||
getAll() {
|
getAll() {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { BaseAPI, route } from "../base";
|
import { BaseAPI, route } from "../base";
|
||||||
import { LocationOutCount, LocationCreate, LocationOut } from "../types/data-contracts";
|
import { LocationOutCount, LocationCreate, LocationOut } from "../types/data-contracts";
|
||||||
import { Results } from "./types";
|
import { Results } from "../types/non-generated";
|
||||||
|
|
||||||
export type LocationUpdate = LocationCreate;
|
export type LocationUpdate = LocationCreate;
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
export type Results<T> = {
|
|
||||||
items: T[];
|
|
||||||
};
|
|
||||||
@@ -187,6 +187,7 @@ export interface LocationSummary {
|
|||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface UserOut {
|
export interface UserOut {
|
||||||
email: string;
|
email: string;
|
||||||
groupId: string;
|
groupId: string;
|
||||||
@@ -227,6 +228,7 @@ export interface UserRegistration {
|
|||||||
|
|
||||||
export interface ApiSummary {
|
export interface ApiSummary {
|
||||||
build: Build;
|
build: Build;
|
||||||
|
demo: boolean;
|
||||||
health: boolean;
|
health: boolean;
|
||||||
message: string;
|
message: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
|||||||
@@ -8,3 +8,14 @@ export enum AttachmentTypes {
|
|||||||
export type Result<T> = {
|
export type Result<T> = {
|
||||||
item: T;
|
item: T;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type Results<T> = {
|
||||||
|
items: T[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface PaginationResult<T> {
|
||||||
|
items: T[];
|
||||||
|
page: number;
|
||||||
|
pageSize: number;
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|||||||
@@ -157,13 +157,6 @@
|
|||||||
</BaseCard>
|
</BaseCard>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
|
||||||
<BaseSectionHeader class="mb-5"> Labels </BaseSectionHeader>
|
|
||||||
<div class="flex gap-2 flex-wrap">
|
|
||||||
<LabelChip v-for="label in labels" :key="label.id" size="lg" :label="label" />
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<BaseSectionHeader class="mb-5"> Storage Locations </BaseSectionHeader>
|
<BaseSectionHeader class="mb-5"> Storage Locations </BaseSectionHeader>
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 card md:grid-cols-3 gap-4">
|
<div class="grid grid-cols-1 sm:grid-cols-2 card md:grid-cols-3 gap-4">
|
||||||
@@ -172,9 +165,9 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<BaseSectionHeader class="mb-5"> Items </BaseSectionHeader>
|
<BaseSectionHeader class="mb-5"> Labels </BaseSectionHeader>
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
<div class="flex gap-2 flex-wrap">
|
||||||
<ItemCard v-for="item in items" :key="item.id" :item="item" />
|
<LabelChip v-for="label in labels" :key="label.id" size="lg" :label="label" />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</BaseContainer>
|
</BaseContainer>
|
||||||
|
|||||||
@@ -11,6 +11,23 @@
|
|||||||
const api = usePublicApi();
|
const api = usePublicApi();
|
||||||
const toast = useNotifier();
|
const toast = useNotifier();
|
||||||
|
|
||||||
|
const { data: status } = useAsyncData(async () => {
|
||||||
|
const { data } = await api.status();
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
username.value = "demo@example.com";
|
||||||
|
password.value = "demo";
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
|
||||||
|
whenever(status, status => {
|
||||||
|
if (status?.demo) {
|
||||||
|
email.value = "demo@example.com";
|
||||||
|
loginPassword.value = "demo";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
if (!authStore.isTokenExpired) {
|
if (!authStore.isTokenExpired) {
|
||||||
navigateTo("/home");
|
navigateTo("/home");
|
||||||
@@ -178,6 +195,11 @@
|
|||||||
<Icon name="heroicons-user" class="mr-1 w-7 h-7" />
|
<Icon name="heroicons-user" class="mr-1 w-7 h-7" />
|
||||||
Login
|
Login
|
||||||
</h2>
|
</h2>
|
||||||
|
<template v-if="status && status.demo">
|
||||||
|
<p class="text-xs italic text-center">This is a demo instance</p>
|
||||||
|
<p class="text-xs text-center"><b>Email</b> demo@example.com</p>
|
||||||
|
<p class="text-xs text-center"><b>Password</b> demo</p>
|
||||||
|
</template>
|
||||||
<FormTextField v-model="email" label="Email" />
|
<FormTextField v-model="email" label="Email" />
|
||||||
<FormTextField v-model="loginPassword" label="Password" type="password" />
|
<FormTextField v-model="loginPassword" label="Password" type="password" />
|
||||||
<div class="card-actions justify-end mt-2">
|
<div class="card-actions justify-end mt-2">
|
||||||
@@ -200,6 +222,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<footer v-if="status" class="absolute text-center w-full bottom-0 pb-4">
|
||||||
|
<p class="text-center text-sm">Version: {{ status.build.version }} ~ Build: {{ status.build.commit }}</p>
|
||||||
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,13 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (locations) {
|
||||||
|
const location = locations.value.find(l => l.id === data.location.id);
|
||||||
|
if (location) {
|
||||||
|
data.location = location;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
});
|
});
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -308,7 +315,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</BaseSectionHeader>
|
</BaseSectionHeader>
|
||||||
<div class="px-5 mb-6 grid md:grid-cols-2 gap-4">
|
<div class="px-5 mb-6 grid md:grid-cols-2 gap-4">
|
||||||
<FormSelect v-model="item.location" label="Location" :items="locations ?? []" select-first />
|
<FormSelect v-if="item" v-model="item.location" label="Location" :items="locations ?? []" />
|
||||||
<FormMultiselect v-model="item.labels" label="Labels" :items="labels ?? []" />
|
<FormMultiselect v-model="item.labels" label="Labels" :items="labels ?? []" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
109
frontend/pages/items.vue
Normal file
109
frontend/pages/items.vue
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ItemSummary } from "~~/lib/api/types/data-contracts";
|
||||||
|
import { useLabelStore } from "~~/stores/labels";
|
||||||
|
import { useLocationStore } from "~~/stores/locations";
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
layout: "home",
|
||||||
|
});
|
||||||
|
useHead({
|
||||||
|
title: "Homebox | Home",
|
||||||
|
});
|
||||||
|
|
||||||
|
const api = useUserApi();
|
||||||
|
|
||||||
|
const query = ref("");
|
||||||
|
const loading = useMinLoader(2000);
|
||||||
|
const results = ref<ItemSummary[]>([]);
|
||||||
|
|
||||||
|
async function search() {
|
||||||
|
loading.value = true;
|
||||||
|
|
||||||
|
const locations = selectedLocations.value.map(l => l.id);
|
||||||
|
const labels = selectedLabels.value.map(l => l.id);
|
||||||
|
|
||||||
|
const { data, error } = await api.items.getAll({ q: query.value, locations, labels });
|
||||||
|
if (error) {
|
||||||
|
loading.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
results.value = data.items;
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
search();
|
||||||
|
});
|
||||||
|
|
||||||
|
const locationsStore = useLocationStore();
|
||||||
|
const locations = computed(() => locationsStore.locations);
|
||||||
|
|
||||||
|
const labelStore = useLabelStore();
|
||||||
|
const labels = computed(() => labelStore.labels);
|
||||||
|
|
||||||
|
const advanced = ref(false);
|
||||||
|
const selectedLocations = ref([]);
|
||||||
|
const selectedLabels = ref([]);
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
if (!advanced.value) {
|
||||||
|
selectedLocations.value = [];
|
||||||
|
selectedLabels.value = [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
watchDebounced(query, search, { debounce: 250, maxWait: 1000 });
|
||||||
|
watchDebounced(selectedLocations, search, { debounce: 250, maxWait: 1000 });
|
||||||
|
watchDebounced(selectedLabels, search, { debounce: 250, maxWait: 1000 });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<BaseContainer class="mb-16">
|
||||||
|
<FormTextField v-model="query" placeholder="Search" />
|
||||||
|
<div class="flex mt-1">
|
||||||
|
<label class="ml-auto label cursor-pointer">
|
||||||
|
<input v-model="advanced" type="checkbox" class="toggle toggle-primary" />
|
||||||
|
<span class="label-text text-neutral-content ml-2"> Filters </span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<BaseCard v-if="advanced" class="my-1 overflow-visible">
|
||||||
|
<template #title> Filters </template>
|
||||||
|
<template #subtitle>
|
||||||
|
Location and label filters use the 'OR' operation. If more than one is selected only one will be required for a
|
||||||
|
match
|
||||||
|
</template>
|
||||||
|
<div class="px-4 pb-4">
|
||||||
|
<FormMultiselect v-model="selectedLabels" label="Labels" :items="labels ?? []" />
|
||||||
|
<FormMultiselect v-model="selectedLocations" label="Labels" :items="locations ?? []" />
|
||||||
|
</div>
|
||||||
|
</BaseCard>
|
||||||
|
<section class="mt-10">
|
||||||
|
<BaseSectionHeader class="mb-5"> Items </BaseSectionHeader>
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
|
<TransitionGroup name="list">
|
||||||
|
<ItemCard v-for="item in results" :key="item.id" :item="item" />
|
||||||
|
</TransitionGroup>
|
||||||
|
<div class="hidden first:inline text-xl">No Items Found</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</BaseContainer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="css">
|
||||||
|
.list-move,
|
||||||
|
.list-enter-active,
|
||||||
|
.list-leave-active {
|
||||||
|
transition: all 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-enter-from,
|
||||||
|
.list-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-leave-active {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -10,6 +10,13 @@
|
|||||||
title: "Homebox | Profile",
|
title: "Homebox | Profile",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const pubApi = usePublicApi();
|
||||||
|
const { data: status } = useAsyncData(async () => {
|
||||||
|
const { data } = await pubApi.status();
|
||||||
|
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
|
||||||
const { setTheme } = useTheme();
|
const { setTheme } = useTheme();
|
||||||
|
|
||||||
type ThemeOption = {
|
type ThemeOption = {
|
||||||
@@ -341,6 +348,9 @@
|
|||||||
</template>
|
</template>
|
||||||
</BaseCard>
|
</BaseCard>
|
||||||
</BaseContainer>
|
</BaseContainer>
|
||||||
|
<footer v-if="status" class="text-center w-full bottom-0 pb-4">
|
||||||
|
<p class="text-center text-sm">Version: {{ status.build.version }} ~ Build: {{ status.build.commit }}</p>
|
||||||
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ def date_types(*names: list[str]) -> dict[re.Pattern, str]:
|
|||||||
|
|
||||||
|
|
||||||
regex_replace: dict[re.Pattern, str] = {
|
regex_replace: dict[re.Pattern, str] = {
|
||||||
|
re.compile(r" PaginationResultRepo"): "PaginationResult",
|
||||||
re.compile(r" Repo"): " ",
|
re.compile(r" Repo"): " ",
|
||||||
re.compile(r" Services"): " ",
|
re.compile(r" Services"): " ",
|
||||||
re.compile(r" V1"): " ",
|
re.compile(r" V1"): " ",
|
||||||
|
|||||||
Reference in New Issue
Block a user