mirror of
https://github.com/sysadminsmedia/homebox.git
synced 2025-12-21 21:33:02 +01:00
Compare commits
1 Commits
v0.3.0
...
fix/datepi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a89ac74888 |
@@ -1,5 +0,0 @@
|
|||||||
# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18, 16, 14, 18-bullseye, 16-bullseye, 14-bullseye, 18-buster, 16-buster, 14-buster
|
|
||||||
ARG VARIANT=16-bullseye
|
|
||||||
FROM mcr.microsoft.com/vscode/devcontainers/typescript-node:0-${VARIANT}
|
|
||||||
|
|
||||||
RUN sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b ~/.local/bin
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
|
|
||||||
// https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/typescript-node
|
|
||||||
{
|
|
||||||
"name": "Node.js & TypeScript",
|
|
||||||
"build": {
|
|
||||||
"dockerfile": "Dockerfile",
|
|
||||||
// Update 'VARIANT' to pick a Node version: 18, 16, 14.
|
|
||||||
// Append -bullseye or -buster to pin to an OS version.
|
|
||||||
// Use -bullseye variants on local on arm64/Apple Silicon.
|
|
||||||
"args": {
|
|
||||||
"VARIANT": "18-bullseye"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Configure tool-specific properties.
|
|
||||||
"customizations": {
|
|
||||||
// Configure properties specific to VS Code.
|
|
||||||
"vscode": {
|
|
||||||
// Add the IDs of extensions you want installed when the container is created.
|
|
||||||
"extensions": [
|
|
||||||
"dbaeumer.vscode-eslint"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
|
||||||
"forwardPorts": [
|
|
||||||
7745,
|
|
||||||
3000
|
|
||||||
],
|
|
||||||
|
|
||||||
// Use 'postCreateCommand' to run commands after the container is created.
|
|
||||||
"postCreateCommand": "go install github.com/go-task/task/v3/cmd/task@latest && npm install -g pnpm && task setup",
|
|
||||||
|
|
||||||
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
|
||||||
"remoteUser": "node",
|
|
||||||
"features": {
|
|
||||||
"golang": "1.19"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@@ -1 +0,0 @@
|
|||||||
github: [hay-kot]
|
|
||||||
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,7 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: "Bug Report"
|
name: "Bug Report"
|
||||||
description: "Submit a bug report for the current release"
|
description: "submit a bug report for the current release"
|
||||||
labels: ["bug"]
|
|
||||||
body:
|
body:
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
id: checks
|
id: checks
|
||||||
|
|||||||
3
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
3
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -1,7 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: "Feature Request"
|
name: "Feature Request"
|
||||||
description: "Submit a feature request for the current release"
|
description: "submit a feature request for the current release"
|
||||||
labels: ["feature-request"]
|
|
||||||
body:
|
body:
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: problem-statement
|
id: problem-statement
|
||||||
|
|||||||
1
.github/workflows/partial-publish.yaml
vendored
1
.github/workflows/partial-publish.yaml
vendored
@@ -60,7 +60,6 @@ jobs:
|
|||||||
--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 .
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ 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++
|
||||||
@@ -23,7 +22,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 -X main.version=$VERSION" \
|
-ldflags "-s -w -X main.Commit=$COMMIT -X main.BuildTime=$BUILD_TIME" \
|
||||||
-o /go/bin/api \
|
-o /go/bin/api \
|
||||||
-v ./app/api/*.go
|
-v ./app/api/*.go
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,6 @@ env:
|
|||||||
HBOX_STORAGE_SQLITE_URL: .data/homebox.db?_fk=1
|
HBOX_STORAGE_SQLITE_URL: .data/homebox.db?_fk=1
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
setup:
|
|
||||||
desc: Install dependencies
|
|
||||||
cmds:
|
|
||||||
- go install github.com/swaggo/swag/cmd/swag@latest
|
|
||||||
- cd backend && go mod tidy
|
|
||||||
- cd frontend && pnpm install --shamefully-hoist
|
|
||||||
generate:
|
generate:
|
||||||
desc: |
|
desc: |
|
||||||
Generates collateral files from the backend project
|
Generates collateral files from the backend project
|
||||||
@@ -33,7 +27,6 @@ tasks:
|
|||||||
- "./scripts/process-types.py"
|
- "./scripts/process-types.py"
|
||||||
generates:
|
generates:
|
||||||
- "./frontend/lib/api/types/data-contracts.ts"
|
- "./frontend/lib/api/types/data-contracts.ts"
|
||||||
- "./backend/ent/schema"
|
|
||||||
- "./backend/app/api/docs/swagger.json"
|
- "./backend/app/api/docs/swagger.json"
|
||||||
- "./backend/app/api/docs/swagger.yaml"
|
- "./backend/app/api/docs/swagger.yaml"
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ func (a *app) SetupDemo() {
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
registration = services.UserRegistration{
|
registration = services.UserRegistration{
|
||||||
Email: "demo@example.com",
|
Email: "demo@email.com",
|
||||||
Name: "Demo",
|
Name: "Demo",
|
||||||
Password: "demo",
|
Password: "demo",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,63 +21,6 @@ const docTemplate = `{
|
|||||||
"host": "{{.Host}}",
|
"host": "{{.Host}}",
|
||||||
"basePath": "{{.BasePath}}",
|
"basePath": "{{.BasePath}}",
|
||||||
"paths": {
|
"paths": {
|
||||||
"/v1/groups": {
|
|
||||||
"get": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"Bearer": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Group"
|
|
||||||
],
|
|
||||||
"summary": "Get the current user's group",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/repo.Group"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"put": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"Bearer": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Group"
|
|
||||||
],
|
|
||||||
"summary": "Updates some fields of the current users group",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"description": "User Data",
|
|
||||||
"name": "payload",
|
|
||||||
"in": "body",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/repo.GroupUpdate"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/repo.Group"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/v1/groups/invitations": {
|
"/v1/groups/invitations": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -89,7 +32,7 @@ const docTemplate = `{
|
|||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"Group"
|
"User"
|
||||||
],
|
],
|
||||||
"summary": "Get the current user",
|
"summary": "Get the current user",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@@ -127,51 +70,26 @@ 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": {
|
||||||
"$ref": "#/definitions/repo.PaginationResult-repo_ItemSummary"
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/server.Results"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"items": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/repo.ItemSummary"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -235,7 +153,7 @@ const docTemplate = `{
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": "No Content"
|
"description": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -336,7 +254,7 @@ const docTemplate = `{
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": "No Content"
|
"description": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -436,7 +354,7 @@ const docTemplate = `{
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK"
|
"description": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -552,7 +470,7 @@ const docTemplate = `{
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": "No Content"
|
"description": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -716,7 +634,7 @@ const docTemplate = `{
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": "No Content"
|
"description": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -880,7 +798,7 @@ const docTemplate = `{
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": "No Content"
|
"description": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -928,7 +846,7 @@ const docTemplate = `{
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": "No Content"
|
"description": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -985,7 +903,7 @@ const docTemplate = `{
|
|||||||
"summary": "User Logout",
|
"summary": "User Logout",
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": "No Content"
|
"description": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1004,7 +922,7 @@ const docTemplate = `{
|
|||||||
"summary": "User Token Refresh",
|
"summary": "User Token Refresh",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK"
|
"description": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1031,7 +949,7 @@ const docTemplate = `{
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": "No Content"
|
"description": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1131,7 +1049,7 @@ const docTemplate = `{
|
|||||||
"summary": "Deletes the user account",
|
"summary": "Deletes the user account",
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": "No Content"
|
"description": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1152,7 +1070,7 @@ const docTemplate = `{
|
|||||||
"summary": "Update the current user's password // TODO:",
|
"summary": "Update the current user's password // TODO:",
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": "No Content"
|
"description": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1173,37 +1091,6 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"repo.Group": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"createdAt": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"currency": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"updatedAt": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"repo.GroupUpdate": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"currency": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"repo.ItemAttachment": {
|
"repo.ItemAttachment": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -1601,26 +1488,6 @@ 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": {
|
||||||
@@ -1674,7 +1541,9 @@ const docTemplate = `{
|
|||||||
"server.Results": {
|
"server.Results": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"items": {}
|
"items": {
|
||||||
|
"type": "any"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"server.ValidationError": {
|
"server.ValidationError": {
|
||||||
|
|||||||
@@ -13,63 +13,6 @@
|
|||||||
},
|
},
|
||||||
"basePath": "/api",
|
"basePath": "/api",
|
||||||
"paths": {
|
"paths": {
|
||||||
"/v1/groups": {
|
|
||||||
"get": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"Bearer": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Group"
|
|
||||||
],
|
|
||||||
"summary": "Get the current user's group",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/repo.Group"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"put": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"Bearer": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Group"
|
|
||||||
],
|
|
||||||
"summary": "Updates some fields of the current users group",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"description": "User Data",
|
|
||||||
"name": "payload",
|
|
||||||
"in": "body",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/repo.GroupUpdate"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/repo.Group"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/v1/groups/invitations": {
|
"/v1/groups/invitations": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -81,7 +24,7 @@
|
|||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"Group"
|
"User"
|
||||||
],
|
],
|
||||||
"summary": "Get the current user",
|
"summary": "Get the current user",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@@ -119,51 +62,26 @@
|
|||||||
"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": {
|
||||||
"$ref": "#/definitions/repo.PaginationResult-repo_ItemSummary"
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/server.Results"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"items": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/repo.ItemSummary"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -227,7 +145,7 @@
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": "No Content"
|
"description": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -328,7 +246,7 @@
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": "No Content"
|
"description": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -428,7 +346,7 @@
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK"
|
"description": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -544,7 +462,7 @@
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": "No Content"
|
"description": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -708,7 +626,7 @@
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": "No Content"
|
"description": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -872,7 +790,7 @@
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": "No Content"
|
"description": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -920,7 +838,7 @@
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": "No Content"
|
"description": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -977,7 +895,7 @@
|
|||||||
"summary": "User Logout",
|
"summary": "User Logout",
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": "No Content"
|
"description": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -996,7 +914,7 @@
|
|||||||
"summary": "User Token Refresh",
|
"summary": "User Token Refresh",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK"
|
"description": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1023,7 +941,7 @@
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": "No Content"
|
"description": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1123,7 +1041,7 @@
|
|||||||
"summary": "Deletes the user account",
|
"summary": "Deletes the user account",
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": "No Content"
|
"description": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1144,7 +1062,7 @@
|
|||||||
"summary": "Update the current user's password // TODO:",
|
"summary": "Update the current user's password // TODO:",
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": "No Content"
|
"description": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1165,37 +1083,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"repo.Group": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"createdAt": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"currency": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"updatedAt": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"repo.GroupUpdate": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"currency": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"repo.ItemAttachment": {
|
"repo.ItemAttachment": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -1593,26 +1480,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"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": {
|
||||||
@@ -1666,7 +1533,9 @@
|
|||||||
"server.Results": {
|
"server.Results": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"items": {}
|
"items": {
|
||||||
|
"type": "any"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"server.ValidationError": {
|
"server.ValidationError": {
|
||||||
|
|||||||
@@ -9,26 +9,6 @@ definitions:
|
|||||||
title:
|
title:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
repo.Group:
|
|
||||||
properties:
|
|
||||||
createdAt:
|
|
||||||
type: string
|
|
||||||
currency:
|
|
||||||
type: string
|
|
||||||
id:
|
|
||||||
type: string
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
updatedAt:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
repo.GroupUpdate:
|
|
||||||
properties:
|
|
||||||
currency:
|
|
||||||
type: string
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
repo.ItemAttachment:
|
repo.ItemAttachment:
|
||||||
properties:
|
properties:
|
||||||
createdAt:
|
createdAt:
|
||||||
@@ -295,19 +275,6 @@ 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:
|
||||||
@@ -343,7 +310,8 @@ 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:
|
||||||
@@ -435,40 +403,6 @@ info:
|
|||||||
title: Go API Templates
|
title: Go API Templates
|
||||||
version: "1.0"
|
version: "1.0"
|
||||||
paths:
|
paths:
|
||||||
/v1/groups:
|
|
||||||
get:
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/repo.Group'
|
|
||||||
security:
|
|
||||||
- Bearer: []
|
|
||||||
summary: Get the current user's group
|
|
||||||
tags:
|
|
||||||
- Group
|
|
||||||
put:
|
|
||||||
parameters:
|
|
||||||
- description: User Data
|
|
||||||
in: body
|
|
||||||
name: payload
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/repo.GroupUpdate'
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/repo.Group'
|
|
||||||
security:
|
|
||||||
- Bearer: []
|
|
||||||
summary: Updates some fields of the current users group
|
|
||||||
tags:
|
|
||||||
- Group
|
|
||||||
/v1/groups/invitations:
|
/v1/groups/invitations:
|
||||||
post:
|
post:
|
||||||
parameters:
|
parameters:
|
||||||
@@ -489,43 +423,23 @@ paths:
|
|||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: Get the current user
|
summary: Get the current user
|
||||||
tags:
|
tags:
|
||||||
- Group
|
- 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:
|
||||||
$ref: '#/definitions/repo.PaginationResult-repo_ItemSummary'
|
allOf:
|
||||||
|
- $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
|
||||||
@@ -563,7 +477,7 @@ paths:
|
|||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"204":
|
"204":
|
||||||
description: No Content
|
description: ""
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: deletes a item
|
summary: deletes a item
|
||||||
@@ -669,7 +583,7 @@ paths:
|
|||||||
type: string
|
type: string
|
||||||
responses:
|
responses:
|
||||||
"204":
|
"204":
|
||||||
description: No Content
|
description: ""
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: retrieves an attachment for an item
|
summary: retrieves an attachment for an item
|
||||||
@@ -744,7 +658,7 @@ paths:
|
|||||||
- application/octet-stream
|
- application/octet-stream
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: ""
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: retrieves an attachment for an item
|
summary: retrieves an attachment for an item
|
||||||
@@ -762,7 +676,7 @@ paths:
|
|||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"204":
|
"204":
|
||||||
description: No Content
|
description: ""
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: imports items into the database
|
summary: imports items into the database
|
||||||
@@ -821,7 +735,7 @@ paths:
|
|||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"204":
|
"204":
|
||||||
description: No Content
|
description: ""
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: deletes a label
|
summary: deletes a label
|
||||||
@@ -918,7 +832,7 @@ paths:
|
|||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"204":
|
"204":
|
||||||
description: No Content
|
description: ""
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: deletes a location
|
summary: deletes a location
|
||||||
@@ -985,7 +899,7 @@ paths:
|
|||||||
$ref: '#/definitions/v1.ChangePassword'
|
$ref: '#/definitions/v1.ChangePassword'
|
||||||
responses:
|
responses:
|
||||||
"204":
|
"204":
|
||||||
description: No Content
|
description: ""
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: Updates the users password
|
summary: Updates the users password
|
||||||
@@ -1021,7 +935,7 @@ paths:
|
|||||||
post:
|
post:
|
||||||
responses:
|
responses:
|
||||||
"204":
|
"204":
|
||||||
description: No Content
|
description: ""
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: User Logout
|
summary: User Logout
|
||||||
@@ -1034,7 +948,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: OK
|
description: ""
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: User Token Refresh
|
summary: User Token Refresh
|
||||||
@@ -1053,7 +967,7 @@ paths:
|
|||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"204":
|
"204":
|
||||||
description: No Content
|
description: ""
|
||||||
summary: Get the current user
|
summary: Get the current user
|
||||||
tags:
|
tags:
|
||||||
- User
|
- User
|
||||||
@@ -1063,7 +977,7 @@ paths:
|
|||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"204":
|
"204":
|
||||||
description: No Content
|
description: ""
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: Deletes the user account
|
summary: Deletes the user account
|
||||||
@@ -1118,7 +1032,7 @@ paths:
|
|||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"204":
|
"204":
|
||||||
description: No Content
|
description: ""
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: 'Update the current user''s password // TODO:'
|
summary: 'Update the current user''s password // TODO:'
|
||||||
|
|||||||
@@ -20,21 +20,21 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
version = "nightly"
|
Version = "0.1.0"
|
||||||
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() {
|
||||||
cfg, err := config.New()
|
cfg, err := config.New()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -110,7 +110,7 @@ func run(cfg *config.Config) error {
|
|||||||
|
|
||||||
app.db = c
|
app.db = c
|
||||||
app.repos = repo.New(c, cfg.Storage.Data)
|
app.repos = repo.New(c, cfg.Storage.Data)
|
||||||
app.services = services.New(app.repos)
|
app.services = services.NewServices(app.repos)
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// Start Server
|
// Start Server
|
||||||
@@ -138,14 +138,6 @@ func run(cfg *config.Config) error {
|
|||||||
Msg("failed to purge expired tokens")
|
Msg("failed to purge expired tokens")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
go app.startBgTask(time.Duration(24)*time.Hour, func() {
|
|
||||||
_, err := app.repos.Groups.InvitationPurge(context.Background())
|
|
||||||
if err != nil {
|
|
||||||
log.Error().
|
|
||||||
Err(err).
|
|
||||||
Msg("failed to purge expired invitations")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// TODO: Remove through external API that does setup
|
// TODO: Remove through external API that does setup
|
||||||
if cfg.Demo {
|
if cfg.Demo {
|
||||||
|
|||||||
@@ -44,13 +44,12 @@ func (a *app) newRouter(repos *repo.AllRepos) *chi.Mux {
|
|||||||
v1Base := v1.BaseUrlFunc(prefix)
|
v1Base := v1.BaseUrlFunc(prefix)
|
||||||
v1Ctrl := v1.NewControllerV1(a.services,
|
v1Ctrl := v1.NewControllerV1(a.services,
|
||||||
v1.WithMaxUploadSize(a.conf.Web.MaxUploadSize),
|
v1.WithMaxUploadSize(a.conf.Web.MaxUploadSize),
|
||||||
v1.WithRegistration(a.conf.AllowRegistration),
|
|
||||||
v1.WithDemoStatus(a.conf.Demo), // Disable Password Change in Demo Mode
|
v1.WithDemoStatus(a.conf.Demo), // Disable Password Change in Demo Mode
|
||||||
)
|
)
|
||||||
r.Get(v1Base("/status"), v1Ctrl.HandleBase(func() bool { return true }, v1.Build{
|
r.Get(v1Base("/status"), v1Ctrl.HandleBase(func() bool { return true }, v1.Build{
|
||||||
Version: version,
|
Version: Version,
|
||||||
Commit: commit,
|
Commit: Commit,
|
||||||
BuildTime: buildTime,
|
BuildTime: BuildTime,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
r.Post(v1Base("/users/register"), v1Ctrl.HandleUserRegistration())
|
r.Post(v1Base("/users/register"), v1Ctrl.HandleUserRegistration())
|
||||||
@@ -72,10 +71,6 @@ func (a *app) newRouter(repos *repo.AllRepos) *chi.Mux {
|
|||||||
|
|
||||||
r.Post(v1Base("/groups/invitations"), v1Ctrl.HandleGroupInvitationsCreate())
|
r.Post(v1Base("/groups/invitations"), v1Ctrl.HandleGroupInvitationsCreate())
|
||||||
|
|
||||||
// TODO: I don't like /groups being the URL for users
|
|
||||||
r.Get(v1Base("/groups"), v1Ctrl.HandleGroupGet())
|
|
||||||
r.Put(v1Base("/groups"), v1Ctrl.HandleGroupUpdate())
|
|
||||||
|
|
||||||
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())
|
||||||
|
|||||||
@@ -19,17 +19,10 @@ func WithDemoStatus(demoStatus bool) func(*V1Controller) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithRegistration(allowRegistration bool) func(*V1Controller) {
|
|
||||||
return func(ctrl *V1Controller) {
|
|
||||||
ctrl.allowRegistration = allowRegistration
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type V1Controller struct {
|
type V1Controller struct {
|
||||||
svc *services.AllServices
|
svc *services.AllServices
|
||||||
maxUploadSize int64
|
maxUploadSize int64
|
||||||
isDemo bool
|
isDemo bool
|
||||||
allowRegistration bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -60,8 +53,7 @@ func BaseUrlFunc(prefix string) func(s string) string {
|
|||||||
|
|
||||||
func NewControllerV1(svc *services.AllServices, options ...func(*V1Controller)) *V1Controller {
|
func NewControllerV1(svc *services.AllServices, options ...func(*V1Controller)) *V1Controller {
|
||||||
ctrl := &V1Controller{
|
ctrl := &V1Controller{
|
||||||
svc: svc,
|
svc: svc,
|
||||||
allowRegistration: true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, opt := range options {
|
for _, opt := range options {
|
||||||
@@ -74,11 +66,11 @@ func NewControllerV1(svc *services.AllServices, options ...func(*V1Controller))
|
|||||||
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{
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -2,10 +2,8 @@ package v1
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"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"
|
||||||
@@ -24,75 +22,21 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// HandleGroupGet godoc
|
// HandleUserSelf godoc
|
||||||
// @Summary Get the current user's group
|
// @Summary Get the current user
|
||||||
// @Tags Group
|
// @Tags User
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} repo.Group
|
// @Param payload body GroupInvitationCreate true "User Data"
|
||||||
// @Router /v1/groups [Get]
|
// @Success 200 {object} GroupInvitation
|
||||||
// @Security Bearer
|
// @Router /v1/groups/invitations [Post]
|
||||||
func (ctrl *V1Controller) HandleGroupGet() http.HandlerFunc {
|
// @Security Bearer
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ctx := services.NewContext(r.Context())
|
|
||||||
|
|
||||||
group, err := ctrl.svc.Group.Get(ctx)
|
|
||||||
if err != nil {
|
|
||||||
log.Err(err).Msg("failed to get group")
|
|
||||||
server.RespondError(w, http.StatusInternalServerError, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
group.Currency = strings.ToUpper(group.Currency) // TODO: Hack to fix the currency enums being lower caseÍ
|
|
||||||
|
|
||||||
server.Respond(w, http.StatusOK, group)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleGroupUpdate godoc
|
|
||||||
// @Summary Updates some fields of the current users group
|
|
||||||
// @Tags Group
|
|
||||||
// @Produce json
|
|
||||||
// @Param payload body repo.GroupUpdate true "User Data"
|
|
||||||
// @Success 200 {object} repo.Group
|
|
||||||
// @Router /v1/groups [Put]
|
|
||||||
// @Security Bearer
|
|
||||||
func (ctrl *V1Controller) HandleGroupUpdate() http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
data := repo.GroupUpdate{}
|
|
||||||
|
|
||||||
if err := server.Decode(r, &data); err != nil {
|
|
||||||
server.RespondError(w, http.StatusBadRequest, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := services.NewContext(r.Context())
|
|
||||||
|
|
||||||
group, err := ctrl.svc.Group.UpdateGroup(ctx, data)
|
|
||||||
if err != nil {
|
|
||||||
log.Err(err).Msg("failed to update group")
|
|
||||||
server.RespondError(w, http.StatusInternalServerError, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
group.Currency = strings.ToUpper(group.Currency) // TODO: Hack to fix the currency enums being lower case
|
|
||||||
server.Respond(w, http.StatusOK, group)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleGroupInvitationsCreate godoc
|
|
||||||
// @Summary Get the current user
|
|
||||||
// @Tags Group
|
|
||||||
// @Produce json
|
|
||||||
// @Param payload body GroupInvitationCreate true "User Data"
|
|
||||||
// @Success 200 {object} GroupInvitation
|
|
||||||
// @Router /v1/groups/invitations [Post]
|
|
||||||
// @Security Bearer
|
|
||||||
func (ctrl *V1Controller) HandleGroupInvitationsCreate() 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{}
|
||||||
|
|
||||||
if err := server.Decode(r, &data); err != nil {
|
if err := server.Decode(r, &data); err != nil {
|
||||||
log.Err(err).Msg("failed to decode user registration data")
|
log.Err(err).Msg("failed to decode user registration data")
|
||||||
server.RespondError(w, http.StatusBadRequest, err)
|
server.RespondError(w, http.StatusInternalServerError, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +46,7 @@ func (ctrl *V1Controller) HandleGroupInvitationsCreate() http.HandlerFunc {
|
|||||||
|
|
||||||
ctx := services.NewContext(r.Context())
|
ctx := services.NewContext(r.Context())
|
||||||
|
|
||||||
token, err := ctrl.svc.Group.NewInvitation(ctx, data.Uses, data.ExpiresAt)
|
token, err := ctrl.svc.User.NewInvitation(ctx, data.Uses, data.ExpiresAt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("failed to create new token")
|
log.Err(err).Msg("failed to create new token")
|
||||||
server.RespondError(w, http.StatusInternalServerError, err)
|
server.RespondError(w, http.StatusInternalServerError, err)
|
||||||
|
|||||||
@@ -3,84 +3,41 @@ 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
|
||||||
// @Param q query string false "search string"
|
// @Success 200 {object} server.Results{items=[]repo.ItemSummary}
|
||||||
// @Param page query int false "page number"
|
// @Router /v1/items [GET]
|
||||||
// @Param pageSize query int false "items per page"
|
// @Security Bearer
|
||||||
// @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) {
|
||||||
ctx := services.NewContext(r.Context())
|
user := services.UseUserCtx(r.Context())
|
||||||
items, err := ctrl.svc.Items.Query(ctx, extractQuery(r))
|
items, err := ctrl.svc.Items.GetAll(r.Context(), user.GroupID)
|
||||||
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, items)
|
server.Respond(w, http.StatusOK, server.Results{Items: 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{}
|
||||||
@@ -103,13 +60,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)
|
||||||
@@ -128,13 +85,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)
|
||||||
@@ -153,14 +110,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{}
|
||||||
@@ -186,13 +143,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{}
|
||||||
@@ -27,11 +27,6 @@ func (ctrl *V1Controller) HandleUserRegistration() http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ctrl.allowRegistration && regData.GroupToken == "" {
|
|
||||||
server.RespondError(w, http.StatusForbidden, nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := ctrl.svc.User.RegisterUser(r.Context(), regData)
|
_, err := ctrl.svc.User.RegisterUser(r.Context(), regData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("failed to register user")
|
log.Err(err).Msg("failed to register user")
|
||||||
@@ -44,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())
|
||||||
@@ -65,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{}
|
||||||
@@ -95,31 +90,26 @@ 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)
|
||||||
@@ -138,12 +128,12 @@ 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 {
|
if ctrl.isDemo {
|
||||||
|
|||||||
@@ -121,9 +121,6 @@ const DefaultCurrency = CurrencyUsd
|
|||||||
// Currency values.
|
// Currency values.
|
||||||
const (
|
const (
|
||||||
CurrencyUsd Currency = "usd"
|
CurrencyUsd Currency = "usd"
|
||||||
CurrencyEur Currency = "eur"
|
|
||||||
CurrencyGbp Currency = "gbp"
|
|
||||||
CurrencyJpy Currency = "jpy"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c Currency) String() string {
|
func (c Currency) String() string {
|
||||||
@@ -133,7 +130,7 @@ func (c Currency) String() string {
|
|||||||
// CurrencyValidator is a validator for the "currency" field enum values. It is called by the builders before save.
|
// CurrencyValidator is a validator for the "currency" field enum values. It is called by the builders before save.
|
||||||
func CurrencyValidator(c Currency) error {
|
func CurrencyValidator(c Currency) error {
|
||||||
switch c {
|
switch c {
|
||||||
case CurrencyUsd, CurrencyEur, CurrencyGbp, CurrencyJpy:
|
case CurrencyUsd:
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("group: invalid enum value for currency field: %q", c)
|
return fmt.Errorf("group: invalid enum value for currency field: %q", c)
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ var (
|
|||||||
{Name: "created_at", Type: field.TypeTime},
|
{Name: "created_at", Type: field.TypeTime},
|
||||||
{Name: "updated_at", Type: field.TypeTime},
|
{Name: "updated_at", Type: field.TypeTime},
|
||||||
{Name: "name", Type: field.TypeString, Size: 255},
|
{Name: "name", Type: field.TypeString, Size: 255},
|
||||||
{Name: "currency", Type: field.TypeEnum, Enums: []string{"usd", "eur", "gbp", "jpy"}, Default: "usd"},
|
{Name: "currency", Type: field.TypeEnum, Enums: []string{"usd"}, Default: "usd"},
|
||||||
}
|
}
|
||||||
// GroupsTable holds the schema information for the "groups" table.
|
// GroupsTable holds the schema information for the "groups" table.
|
||||||
GroupsTable = &schema.Table{
|
GroupsTable = &schema.Table{
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func (Group) Fields() []ent.Field {
|
|||||||
NotEmpty(),
|
NotEmpty(),
|
||||||
field.Enum("currency").
|
field.Enum("currency").
|
||||||
Default("usd").
|
Default("usd").
|
||||||
Values("usd", "eur", "gbp", "jpy"), // TODO: add more currencies
|
Values("usd"), // TODO: add more currencies
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,14 +16,13 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Mode string `yaml:"mode" conf:"default:development"` // development or production
|
Mode string `yaml:"mode" conf:"default:development"` // development or production
|
||||||
Web WebConfig `yaml:"web"`
|
Web WebConfig `yaml:"web"`
|
||||||
Storage Storage `yaml:"storage"`
|
Storage Storage `yaml:"storage"`
|
||||||
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"`
|
Demo bool `yaml:"demo"`
|
||||||
AllowRegistration bool `yaml:"disable_registration" conf:"default:true"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type SwaggerConf struct {
|
type SwaggerConf struct {
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"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/groupinvitationtoken"
|
"github.com/hay-kot/homebox/backend/ent/groupinvitationtoken"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,16 +15,11 @@ type GroupRepository struct {
|
|||||||
|
|
||||||
type (
|
type (
|
||||||
Group struct {
|
Group struct {
|
||||||
ID uuid.UUID `json:"id,omitempty"`
|
ID uuid.UUID
|
||||||
Name string `json:"name,omitempty"`
|
Name string
|
||||||
CreatedAt time.Time `json:"createdAt,omitempty"`
|
CreatedAt time.Time
|
||||||
UpdatedAt time.Time `json:"updatedAt,omitempty"`
|
UpdatedAt time.Time
|
||||||
Currency string `json:"currency,omitempty"`
|
Currency string
|
||||||
}
|
|
||||||
|
|
||||||
GroupUpdate struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Currency string `json:"currency"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GroupInvitationCreate struct {
|
GroupInvitationCreate struct {
|
||||||
@@ -75,17 +69,6 @@ func (r *GroupRepository) GroupCreate(ctx context.Context, name string) (Group,
|
|||||||
Save(ctx))
|
Save(ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *GroupRepository) GroupUpdate(ctx context.Context, ID uuid.UUID, data GroupUpdate) (Group, error) {
|
|
||||||
currency := group.Currency(data.Currency)
|
|
||||||
|
|
||||||
entity, err := r.db.Group.UpdateOneID(ID).
|
|
||||||
SetName(data.Name).
|
|
||||||
SetCurrency(currency).
|
|
||||||
Save(ctx)
|
|
||||||
|
|
||||||
return mapToGroupErr(entity, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *GroupRepository) GroupByID(ctx context.Context, id uuid.UUID) (Group, error) {
|
func (r *GroupRepository) GroupByID(ctx context.Context, id uuid.UUID) (Group, error) {
|
||||||
return mapToGroupErr(r.db.Group.Get(ctx, id))
|
return mapToGroupErr(r.db.Group.Get(ctx, id))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,16 +18,3 @@ func Test_Group_Create(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, g.ID, foundGroup.ID)
|
assert.Equal(t, g.ID, foundGroup.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_Group_Update(t *testing.T) {
|
|
||||||
g, err := tRepos.Groups.GroupCreate(context.Background(), "test")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
g, err = tRepos.Groups.GroupUpdate(context.Background(), g.ID, GroupUpdate{
|
|
||||||
Name: "test2",
|
|
||||||
Currency: "eur",
|
|
||||||
})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, "test2", g.Name)
|
|
||||||
assert.Equal(t, "eur", g.Currency)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,15 +16,6 @@ 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"`
|
|
||||||
SortBy string `json:"sortBy"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ItemCreate struct {
|
ItemCreate struct {
|
||||||
ImportRef string `json:"-"`
|
ImportRef string `json:"-"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
@@ -217,65 +206,6 @@ 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.Order(ent.Asc(item.FieldName)).
|
|
||||||
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().
|
||||||
|
|||||||
@@ -84,7 +84,6 @@ func (r *LabelRepository) GetOneByGroup(ctx context.Context, gid, ld uuid.UUID)
|
|||||||
func (r *LabelRepository) GetAll(ctx context.Context, groupId uuid.UUID) ([]LabelSummary, error) {
|
func (r *LabelRepository) GetAll(ctx context.Context, groupId uuid.UUID) ([]LabelSummary, error) {
|
||||||
return mapLabelsOut(r.db.Label.Query().
|
return mapLabelsOut(r.db.Label.Query().
|
||||||
Where(label.HasGroupWith(group.ID(groupId))).
|
Where(label.HasGroupWith(group.ID(groupId))).
|
||||||
Order(ent.Asc(label.FieldName)).
|
|
||||||
WithGroup().
|
WithGroup().
|
||||||
All(ctx),
|
All(ctx),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -94,9 +94,7 @@ func (r *LocationRepository) GetAll(ctx context.Context, groupId uuid.UUID) ([]L
|
|||||||
locations
|
locations
|
||||||
WHERE
|
WHERE
|
||||||
locations.group_locations = ?
|
locations.group_locations = ?
|
||||||
ORDER BY
|
`
|
||||||
locations.name ASC
|
|
||||||
`
|
|
||||||
|
|
||||||
rows, err := r.db.Sql().QueryContext(ctx, query, groupId)
|
rows, err := r.db.Sql().QueryContext(ctx, query, groupId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -4,20 +4,18 @@ import "github.com/hay-kot/homebox/backend/internal/repo"
|
|||||||
|
|
||||||
type AllServices struct {
|
type AllServices struct {
|
||||||
User *UserService
|
User *UserService
|
||||||
Group *GroupService
|
|
||||||
Location *LocationService
|
Location *LocationService
|
||||||
Labels *LabelService
|
Labels *LabelService
|
||||||
Items *ItemService
|
Items *ItemService
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(repos *repo.AllRepos) *AllServices {
|
func NewServices(repos *repo.AllRepos) *AllServices {
|
||||||
if repos == nil {
|
if repos == nil {
|
||||||
panic("repos cannot be nil")
|
panic("repos cannot be nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &AllServices{
|
return &AllServices{
|
||||||
User: &UserService{repos},
|
User: &UserService{repos},
|
||||||
Group: &GroupService{repos},
|
|
||||||
Location: &LocationService{repos},
|
Location: &LocationService{repos},
|
||||||
Labels: &LabelService{repos},
|
Labels: &LabelService{repos},
|
||||||
Items: &ItemService{
|
Items: &ItemService{
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ func TestMain(m *testing.M) {
|
|||||||
|
|
||||||
tClient = client
|
tClient = client
|
||||||
tRepos = repo.New(tClient, os.TempDir()+"/homebox")
|
tRepos = repo.New(tClient, os.TempDir()+"/homebox")
|
||||||
tSvc = New(tRepos)
|
tSvc = NewServices(tRepos)
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
|
|
||||||
bootstrap()
|
bootstrap()
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
package services
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hay-kot/homebox/backend/internal/repo"
|
|
||||||
"github.com/hay-kot/homebox/backend/pkgs/hasher"
|
|
||||||
)
|
|
||||||
|
|
||||||
type GroupService struct {
|
|
||||||
repos *repo.AllRepos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (svc *GroupService) Get(ctx Context) (repo.Group, error) {
|
|
||||||
return svc.repos.Groups.GroupByID(ctx.Context, ctx.GID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (svc *GroupService) UpdateGroup(ctx Context, data repo.GroupUpdate) (repo.Group, error) {
|
|
||||||
if data.Name == "" {
|
|
||||||
data.Name = ctx.User.GroupName
|
|
||||||
}
|
|
||||||
|
|
||||||
if data.Currency == "" {
|
|
||||||
return repo.Group{}, errors.New("currency cannot be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
data.Currency = strings.ToLower(data.Currency)
|
|
||||||
|
|
||||||
return svc.repos.Groups.GroupUpdate(ctx.Context, ctx.GID, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (svc *GroupService) NewInvitation(ctx Context, uses int, expiresAt time.Time) (string, error) {
|
|
||||||
token := hasher.GenerateToken()
|
|
||||||
|
|
||||||
_, err := svc.repos.Groups.InvitationCreate(ctx, ctx.GID, repo.GroupInvitationCreate{
|
|
||||||
Token: token.Hash,
|
|
||||||
Uses: uses,
|
|
||||||
ExpiresAt: expiresAt,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return token.Raw, nil
|
|
||||||
}
|
|
||||||
@@ -28,10 +28,6 @@ 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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -186,6 +186,21 @@ func (svc *UserService) DeleteSelf(ctx context.Context, ID uuid.UUID) error {
|
|||||||
return svc.repos.Users.Delete(ctx, ID)
|
return svc.repos.Users.Delete(ctx, ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (svc *UserService) NewInvitation(ctx Context, uses int, expiresAt time.Time) (string, error) {
|
||||||
|
token := hasher.GenerateToken()
|
||||||
|
|
||||||
|
_, err := svc.repos.Groups.InvitationCreate(ctx, ctx.GID, repo.GroupInvitationCreate{
|
||||||
|
Token: token.Hash,
|
||||||
|
Uses: uses,
|
||||||
|
ExpiresAt: expiresAt,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return token.Raw, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (svc *UserService) ChangePassword(ctx Context, current string, new string) (ok bool) {
|
func (svc *UserService) ChangePassword(ctx Context, current string, new string) (ok bool) {
|
||||||
usr, err := svc.repos.Users.GetOneId(ctx, ctx.UID)
|
usr, err := svc.repos.Users.GetOneId(ctx, ctx.UID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ func (f *Faker) Path() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *Faker) Email() string {
|
func (f *Faker) Email() string {
|
||||||
return f.Str(10) + "@example.com"
|
return f.Str(10) + "@email.com"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Faker) Bool() bool {
|
func (f *Faker) Bool() bool {
|
||||||
|
|||||||
@@ -15,20 +15,19 @@ docker run --name=homebox \
|
|||||||
|
|
||||||
```yml
|
```yml
|
||||||
version: "3.4"
|
version: "3.4"
|
||||||
|
services:
|
||||||
services:
|
homebox:
|
||||||
homebox:
|
image: ghcr.io/hay-kot/homebox:latest
|
||||||
image: ghcr.io/hay-kot/homebox:latest
|
container_name: homebox
|
||||||
container_name: homebox
|
restart: always
|
||||||
restart: always
|
environment:
|
||||||
environment:
|
- HBOX_LOG_LEVEL=info
|
||||||
- HBOX_LOG_LEVEL=info
|
- HBOX_LOG_FORMAT=text
|
||||||
- HBOX_LOG_FORMAT=text
|
- HBOX_WEB_MAX_UPLOAD_SIZE=10
|
||||||
- HBOX_WEB_MAX_UPLOAD_SIZE=10
|
volumes:
|
||||||
volumes:
|
- homebox-data:/data/
|
||||||
- homebox-data:/data/
|
ports:
|
||||||
ports:
|
- 3100:7745
|
||||||
- 3100:7745
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
homebox-data:
|
homebox-data:
|
||||||
@@ -42,10 +41,9 @@ volumes:
|
|||||||
| HBOX_MODE | production | application mode used for runtime behavior can be one of: development, production |
|
| HBOX_MODE | production | application mode used for runtime behavior can be one of: development, production |
|
||||||
| HBOX_WEB_PORT | 7745 | port to run the web server on, in you're using docker do not change this |
|
| HBOX_WEB_PORT | 7745 | port to run the web server on, in you're using docker do not change this |
|
||||||
| HBOX_WEB_HOST | | host to run the web server on, in you're using docker do not change this |
|
| HBOX_WEB_HOST | | host to run the web server on, in you're using docker do not change this |
|
||||||
| HBOX_ALLOW_REGISTRATION | true | allow users to register themselves |
|
|
||||||
| HBOX_WEB_MAX_UPLOAD_SIZE | 10 | maximum file upload size supported in MB |
|
|
||||||
| HBOX_STORAGE_DATA | /data/ | path to the data directory, do not change this if you're using docker |
|
| HBOX_STORAGE_DATA | /data/ | path to the data directory, do not change this if you're using docker |
|
||||||
| HBOX_STORAGE_SQLITE_URL | /data/homebox.db?_fk=1 | sqlite database url, in you're using docker do not change this |
|
| HBOX_STORAGE_SQLITE_URL | /data/homebox.db?_fk=1 | sqlite database url, in you're using docker do not change this |
|
||||||
|
| HBOX_WEB_MAX_UPLOAD_SIZE | 10 | maximum file upload size supported in MB |
|
||||||
| HBOX_LOG_LEVEL | info | log level to use, can be one of: trace, debug, info, warn, error, critical |
|
| HBOX_LOG_LEVEL | info | log level to use, can be one of: trace, debug, info, warn, error, critical |
|
||||||
| HBOX_LOG_FORMAT | text | log format to use, can be one of: text, json |
|
| HBOX_LOG_FORMAT | text | log format to use, can be one of: text, json |
|
||||||
| HBOX_MAILER_HOST | | email host to use, if not set no email provider will be used |
|
| HBOX_MAILER_HOST | | email host to use, if not set no email provider will be used |
|
||||||
@@ -78,8 +76,6 @@ volumes:
|
|||||||
--mailer-from/$HBOX_MAILER_FROM <string>
|
--mailer-from/$HBOX_MAILER_FROM <string>
|
||||||
--swagger-host/$HBOX_SWAGGER_HOST <string> (default: localhost:7745)
|
--swagger-host/$HBOX_SWAGGER_HOST <string> (default: localhost:7745)
|
||||||
--swagger-scheme/$HBOX_SWAGGER_SCHEME <string> (default: http)
|
--swagger-scheme/$HBOX_SWAGGER_SCHEME <string> (default: http)
|
||||||
--demo/$HBOX_DEMO <bool>
|
|
||||||
--allow-registration/$HBOX_ALLOW_REGISTRATION <bool> (default: true)
|
|
||||||
--help/-h
|
--help/-h
|
||||||
display this help message
|
display this help message
|
||||||
```
|
```
|
||||||
3
fly.toml
3
fly.toml
@@ -5,9 +5,6 @@ kill_signal = "SIGINT"
|
|||||||
kill_timeout = 5
|
kill_timeout = 5
|
||||||
processes = []
|
processes = []
|
||||||
|
|
||||||
[build.args]
|
|
||||||
COMMIT = "HEAD"
|
|
||||||
VERSION = "nightly"
|
|
||||||
|
|
||||||
[env]
|
[env]
|
||||||
PORT = "7745"
|
PORT = "7745"
|
||||||
|
|||||||
@@ -18,10 +18,6 @@
|
|||||||
name: "Home",
|
name: "Home",
|
||||||
href: "/home",
|
href: "/home",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "Items",
|
|
||||||
href: "/items",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "Logout",
|
name: "Logout",
|
||||||
action: logout,
|
action: logout,
|
||||||
|
|||||||
@@ -11,8 +11,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<ul
|
<ul
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
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 scroll-bar"
|
||||||
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"
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
const emit = defineEmits(["update:modelValue", "update:value"]);
|
const emit = defineEmits(["update:modelValue"]);
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
label: {
|
label: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
},
|
},
|
||||||
modelValue: {
|
modelValue: {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
type: [Object, String] as any,
|
type: [Object, String, Boolean] as any,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
items: {
|
items: {
|
||||||
@@ -37,51 +37,59 @@
|
|||||||
type: String,
|
type: String,
|
||||||
default: "name",
|
default: "name",
|
||||||
},
|
},
|
||||||
valueKey: {
|
|
||||||
type: String,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
value: {
|
value: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "",
|
default: null,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
selectFirst: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const selectedIdx = ref(-1);
|
function syncSelect() {
|
||||||
|
if (!props.modelValue) {
|
||||||
const internalSelected = useVModel(props, "modelValue", emit);
|
if (props.selectFirst) {
|
||||||
|
selectedIdx.value = 0;
|
||||||
watch(selectedIdx, newVal => {
|
}
|
||||||
internalSelected.value = props.items[newVal];
|
return;
|
||||||
});
|
|
||||||
|
|
||||||
watch(internalSelected, newVal => {
|
|
||||||
if (props.valueKey) {
|
|
||||||
emit("update:value", newVal[props.valueKey]);
|
|
||||||
}
|
}
|
||||||
});
|
// Check if we're already synced
|
||||||
|
if (props.value) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
if (props.modelValue[props.value] === props.items[selectedIdx.value][props.value]) {
|
||||||
function compare(a: any, b: any): boolean {
|
return;
|
||||||
if (a === b) {
|
}
|
||||||
return true;
|
} else if (props.modelValue === props.items[selectedIdx.value]) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!a || !b) {
|
const idx = props.items.findIndex(item => {
|
||||||
return false;
|
if (props.value) {
|
||||||
}
|
return item[props.value] === props.modelValue;
|
||||||
|
}
|
||||||
|
return item === props.modelValue;
|
||||||
|
});
|
||||||
|
|
||||||
return JSON.stringify(a) === JSON.stringify(b);
|
selectedIdx.value = idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
internalSelected,
|
() => props.modelValue,
|
||||||
() => {
|
() => {
|
||||||
const idx = props.items.findIndex(item => compare(item, internalSelected.value));
|
syncSelect();
|
||||||
selectedIdx.value = idx;
|
}
|
||||||
},
|
);
|
||||||
{
|
|
||||||
immediate: true,
|
const selectedIdx = ref(0);
|
||||||
|
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" :placeholder="placeholder" :type="type" class="input input-bordered w-full" />
|
<input ref="input" v-model="value" :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" :placeholder="placeholder" class="input input-bordered col-span-3 w-full mt-2" />
|
<input v-model="value" class="input input-bordered col-span-3 w-full mt-2" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -35,10 +35,6 @@
|
|||||||
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 ?? []" />
|
<FormSelect v-model="form.location" label="Location" :items="locations ?? []" select-first />
|
||||||
<FormTextField
|
<FormTextField
|
||||||
ref="locationNameRef"
|
ref="locationNameRef"
|
||||||
v-model="form.name"
|
v-model="form.name"
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
<template>
|
|
||||||
{{ value }}
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
const props = defineProps({
|
|
||||||
amount: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const fmt = await useFormatCurrency();
|
|
||||||
|
|
||||||
const value = computed(() => {
|
|
||||||
if (!props.amount || props.amount === "0") {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt(props.amount);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -7,8 +7,9 @@
|
|||||||
</dt>
|
</dt>
|
||||||
<dd class="mt-1 text-sm text-base-content sm:col-span-2 sm:mt-0">
|
<dd class="mt-1 text-sm text-base-content sm:col-span-2 sm:mt-0">
|
||||||
<slot :name="detail.slot || detail.name" v-bind="{ detail }">
|
<slot :name="detail.slot || detail.name" v-bind="{ detail }">
|
||||||
<DateTime v-if="detail.type == 'date'" :date="detail.text" />
|
<template v-if="detail.type == 'date'">
|
||||||
<Currency v-else-if="detail.type == 'currency'" :amount="detail.text" />
|
<DateTime :date="detail.text" />
|
||||||
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
{{ detail.text }}
|
{{ detail.text }}
|
||||||
</template>
|
</template>
|
||||||
@@ -20,11 +21,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { CustomDetail, Detail } from "./types";
|
import type { DateDetail, Detail } from "./types";
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
details: {
|
details: {
|
||||||
type: Object as () => (Detail | CustomDetail)[],
|
type: Object as () => (Detail | DateDetail)[],
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,25 +1,15 @@
|
|||||||
export type StringLike = string | number | boolean;
|
export type StringLike = string | number | boolean;
|
||||||
|
|
||||||
type BaseDetail = {
|
export type DateDetail = {
|
||||||
name: string;
|
name: string;
|
||||||
|
text: string | Date;
|
||||||
slot?: string;
|
slot?: string;
|
||||||
};
|
|
||||||
|
|
||||||
type DateDetail = BaseDetail & {
|
|
||||||
type: "date";
|
type: "date";
|
||||||
text: Date | string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type CurrencyDetail = BaseDetail & {
|
export type Detail = {
|
||||||
type: "currency";
|
name: string;
|
||||||
text: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type CustomDetail = DateDetail | CurrencyDetail;
|
|
||||||
|
|
||||||
export type Detail = BaseDetail & {
|
|
||||||
text: StringLike;
|
text: StringLike;
|
||||||
|
slot?: string;
|
||||||
type?: "text";
|
type?: "text";
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Details = Array<Detail | CustomDetail>;
|
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
const cache = {
|
|
||||||
currency: "",
|
|
||||||
};
|
|
||||||
|
|
||||||
export function ResetCurrency() {
|
|
||||||
cache.currency = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function useFormatCurrency() {
|
|
||||||
if (!cache.currency) {
|
|
||||||
const client = useUserApi();
|
|
||||||
|
|
||||||
const { data: group } = await client.group.get();
|
|
||||||
|
|
||||||
if (group) {
|
|
||||||
cache.currency = group.currency;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (value: number | string) => fmtCurrency(value, cache.currency);
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,35 @@
|
|||||||
import { Ref } from "vue";
|
import { Ref } from "vue";
|
||||||
import { DaisyTheme } from "~~/lib/data/themes";
|
|
||||||
|
export type DaisyTheme =
|
||||||
|
| "light"
|
||||||
|
| "dark"
|
||||||
|
| "cupcake"
|
||||||
|
| "bumblebee"
|
||||||
|
| "emerald"
|
||||||
|
| "corporate"
|
||||||
|
| "synthwave"
|
||||||
|
| "retro"
|
||||||
|
| "cyberpunk"
|
||||||
|
| "valentine"
|
||||||
|
| "halloween"
|
||||||
|
| "garden"
|
||||||
|
| "forest"
|
||||||
|
| "aqua"
|
||||||
|
| "lofi"
|
||||||
|
| "pastel"
|
||||||
|
| "fantasy"
|
||||||
|
| "wireframe"
|
||||||
|
| "black"
|
||||||
|
| "luxury"
|
||||||
|
| "dracula"
|
||||||
|
| "cmyk"
|
||||||
|
| "autumn"
|
||||||
|
| "business"
|
||||||
|
| "acid"
|
||||||
|
| "lemonade"
|
||||||
|
| "night"
|
||||||
|
| "coffee"
|
||||||
|
| "winter";
|
||||||
|
|
||||||
export type LocationViewPreferences = {
|
export type LocationViewPreferences = {
|
||||||
showDetails: boolean;
|
showDetails: boolean;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ComputedRef } from "vue";
|
import { ComputedRef } from "vue";
|
||||||
import { DaisyTheme } from "~~/lib/data/themes";
|
import { DaisyTheme } from "./use-preferences";
|
||||||
|
|
||||||
export interface UseTheme {
|
export interface UseTheme {
|
||||||
theme: ComputedRef<DaisyTheme>;
|
theme: ComputedRef<DaisyTheme>;
|
||||||
|
|||||||
@@ -1,70 +1,10 @@
|
|||||||
|
<script setup lang="ts"></script>
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<AppToast />
|
<AppToast />
|
||||||
<AppHeader />
|
<AppHeader />
|
||||||
<main>
|
<main class="p-8 dark:bg-gray-800 dark:text-white bg-white text-gray-800 min-h-screen">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { useItemStore } from "~~/stores/items";
|
|
||||||
import { useLabelStore } from "~~/stores/labels";
|
|
||||||
import { useLocationStore } from "~~/stores/locations";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store Provider Initialization
|
|
||||||
*/
|
|
||||||
|
|
||||||
const labelStore = useLabelStore();
|
|
||||||
const reLabel = /\/api\/v1\/labels\/.*/gm;
|
|
||||||
const rmLabelStoreObserver = defineObserver("labelStore", {
|
|
||||||
handler: r => {
|
|
||||||
if (r.status === 201 || r.url.match(reLabel)) {
|
|
||||||
labelStore.refresh();
|
|
||||||
}
|
|
||||||
console.debug("labelStore handler called by observer");
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const locationStore = useLocationStore();
|
|
||||||
const reLocation = /\/api\/v1\/locations\/.*/gm;
|
|
||||||
const rmLocationStoreObserver = defineObserver("locationStore", {
|
|
||||||
handler: r => {
|
|
||||||
if (r.status === 201 || r.url.match(reLocation)) {
|
|
||||||
locationStore.refresh();
|
|
||||||
}
|
|
||||||
console.debug("locationStore handler called by observer");
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const itemStore = useItemStore();
|
|
||||||
const reItem = /\/api\/v1\/items\/.*/gm;
|
|
||||||
const rmItemStoreObserver = defineObserver("itemStore", {
|
|
||||||
handler: r => {
|
|
||||||
if (r.status === 201 || r.url.match(reItem)) {
|
|
||||||
itemStore.refresh();
|
|
||||||
}
|
|
||||||
console.debug("itemStore handler called by observer");
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const eventBus = useEventBus();
|
|
||||||
eventBus.on(
|
|
||||||
EventTypes.ClearStores,
|
|
||||||
() => {
|
|
||||||
labelStore.refresh();
|
|
||||||
itemStore.refresh();
|
|
||||||
locationStore.refresh();
|
|
||||||
},
|
|
||||||
"stores"
|
|
||||||
);
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
rmLabelStoreObserver();
|
|
||||||
rmLocationStoreObserver();
|
|
||||||
rmItemStoreObserver();
|
|
||||||
eventBus.off(EventTypes.ClearStores, "stores");
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
69
frontend/layouts/home.vue
Normal file
69
frontend/layouts/home.vue
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<AppToast />
|
||||||
|
<AppHeader />
|
||||||
|
<main>
|
||||||
|
<slot></slot>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { useItemStore } from "~~/stores/items";
|
||||||
|
import { useLabelStore } from "~~/stores/labels";
|
||||||
|
import { useLocationStore } from "~~/stores/locations";
|
||||||
|
/**
|
||||||
|
* Store Provider Initialization
|
||||||
|
*/
|
||||||
|
|
||||||
|
const labelStore = useLabelStore();
|
||||||
|
const reLabel = /\/api\/v1\/labels\/.*/gm;
|
||||||
|
const rmLabelStoreObserver = defineObserver("labelStore", {
|
||||||
|
handler: r => {
|
||||||
|
if (r.status === 201 || r.url.match(reLabel)) {
|
||||||
|
labelStore.refresh();
|
||||||
|
}
|
||||||
|
console.debug("labelStore handler called by observer");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const locationStore = useLocationStore();
|
||||||
|
const reLocation = /\/api\/v1\/locations\/.*/gm;
|
||||||
|
const rmLocationStoreObserver = defineObserver("locationStore", {
|
||||||
|
handler: r => {
|
||||||
|
if (r.status === 201 || r.url.match(reLocation)) {
|
||||||
|
locationStore.refresh();
|
||||||
|
}
|
||||||
|
console.debug("locationStore handler called by observer");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const itemStore = useItemStore();
|
||||||
|
const reItem = /\/api\/v1\/items\/.*/gm;
|
||||||
|
const rmItemStoreObserver = defineObserver("itemStore", {
|
||||||
|
handler: r => {
|
||||||
|
if (r.status === 201 || r.url.match(reItem)) {
|
||||||
|
itemStore.refresh();
|
||||||
|
}
|
||||||
|
console.debug("itemStore handler called by observer");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const eventBus = useEventBus();
|
||||||
|
eventBus.on(
|
||||||
|
EventTypes.ClearStores,
|
||||||
|
() => {
|
||||||
|
labelStore.refresh();
|
||||||
|
itemStore.refresh();
|
||||||
|
locationStore.refresh();
|
||||||
|
},
|
||||||
|
"stores"
|
||||||
|
);
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
rmLabelStoreObserver();
|
||||||
|
rmLocationStoreObserver();
|
||||||
|
rmItemStoreObserver();
|
||||||
|
eventBus.off(EventTypes.ClearStores, "stores");
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { describe, test, expect } from "vitest";
|
import { describe, test, expect } from "vitest";
|
||||||
import { factories } from "./factories";
|
import { factories } from "./factories";
|
||||||
|
import { sharedUserClient } from "./test-utils";
|
||||||
|
|
||||||
describe("[GET] /api/v1/status", () => {
|
describe("[GET] /api/v1/status", () => {
|
||||||
test("server should respond", async () => {
|
test("server should respond", async () => {
|
||||||
@@ -31,4 +32,43 @@ describe("first time user workflow (register, login, join group)", () => {
|
|||||||
expect(response.status).toBe(204);
|
expect(response.status).toBe(204);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("user should be able to join create join token and have user signup", async () => {
|
||||||
|
// Setup User 1 Token
|
||||||
|
|
||||||
|
const client = await sharedUserClient();
|
||||||
|
const { data: user1 } = await client.user.self();
|
||||||
|
|
||||||
|
const { response, data } = await client.group.createInvitation({
|
||||||
|
expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 7),
|
||||||
|
uses: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.status).toBe(201);
|
||||||
|
expect(data.token).toBeTruthy();
|
||||||
|
|
||||||
|
// Create User 2 with token
|
||||||
|
|
||||||
|
const duplicateUser = factories.user();
|
||||||
|
duplicateUser.token = data.token;
|
||||||
|
|
||||||
|
const { response: registerResp } = await api.register(duplicateUser);
|
||||||
|
expect(registerResp.status).toBe(204);
|
||||||
|
|
||||||
|
const { response: loginResp, data: loginData } = await api.login(duplicateUser.email, duplicateUser.password);
|
||||||
|
expect(loginResp.status).toBe(200);
|
||||||
|
|
||||||
|
// Get Self and Assert
|
||||||
|
|
||||||
|
const client2 = factories.client.user(loginData.token);
|
||||||
|
|
||||||
|
const { data: user2 } = await client2.user.self();
|
||||||
|
|
||||||
|
user2.item.groupName = user1.item.groupName;
|
||||||
|
|
||||||
|
// Cleanup User 2
|
||||||
|
|
||||||
|
const { response: deleteResp } = await client2.user.delete();
|
||||||
|
expect(deleteResp.status).toBe(204);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
import { faker } from "@faker-js/faker";
|
|
||||||
import { describe, test, expect } from "vitest";
|
|
||||||
import { factories } from "../factories";
|
|
||||||
import { sharedUserClient } from "../test-utils";
|
|
||||||
|
|
||||||
describe("first time user workflow (register, login, join group)", () => {
|
|
||||||
test("user should be able to update group", async () => {
|
|
||||||
const { client } = await factories.client.singleUse();
|
|
||||||
|
|
||||||
const name = faker.name.firstName();
|
|
||||||
|
|
||||||
const { response, data: group } = await client.group.update({
|
|
||||||
name,
|
|
||||||
currency: "eur",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
|
||||||
expect(group.name).toBe(name);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("user should be able to get own group", async () => {
|
|
||||||
const { client } = await factories.client.singleUse();
|
|
||||||
|
|
||||||
const { response, data: group } = await client.group.get();
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
|
||||||
expect(group.name).toBeTruthy();
|
|
||||||
expect(group.currency).toBe("USD");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("user should be able to join create join token and have user signup", async () => {
|
|
||||||
const api = factories.client.public();
|
|
||||||
|
|
||||||
// Setup User 1 Token
|
|
||||||
const client = await sharedUserClient();
|
|
||||||
const { data: user1 } = await client.user.self();
|
|
||||||
|
|
||||||
const { response, data } = await client.group.createInvitation({
|
|
||||||
expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 7),
|
|
||||||
uses: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.status).toBe(201);
|
|
||||||
expect(data.token).toBeTruthy();
|
|
||||||
|
|
||||||
// Create User 2 with token
|
|
||||||
const duplicateUser = factories.user();
|
|
||||||
duplicateUser.token = data.token;
|
|
||||||
|
|
||||||
const { response: registerResp } = await api.register(duplicateUser);
|
|
||||||
expect(registerResp.status).toBe(204);
|
|
||||||
|
|
||||||
const { response: loginResp, data: loginData } = await api.login(duplicateUser.email, duplicateUser.password);
|
|
||||||
expect(loginResp.status).toBe(200);
|
|
||||||
|
|
||||||
// Get Self and Assert
|
|
||||||
const client2 = factories.client.user(loginData.token);
|
|
||||||
const { data: user2 } = await client2.user.self();
|
|
||||||
|
|
||||||
user2.item.groupName = user1.item.groupName;
|
|
||||||
|
|
||||||
// Cleanup User 2
|
|
||||||
const { response: deleteResp } = await client2.user.delete();
|
|
||||||
expect(deleteResp.status).toBe(204);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { BaseAPI, route } from "../base";
|
import { BaseAPI, route } from "../base";
|
||||||
import { Group, GroupInvitation, GroupInvitationCreate, GroupUpdate } from "../types/data-contracts";
|
import { GroupInvitation, GroupInvitationCreate } from "../types/data-contracts";
|
||||||
|
|
||||||
export class GroupApi extends BaseAPI {
|
export class GroupApi extends BaseAPI {
|
||||||
createInvitation(data: GroupInvitationCreate) {
|
createInvitation(data: GroupInvitationCreate) {
|
||||||
@@ -8,17 +8,4 @@ export class GroupApi extends BaseAPI {
|
|||||||
body: data,
|
body: data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
update(data: GroupUpdate) {
|
|
||||||
return this.http.put<GroupUpdate, Group>({
|
|
||||||
url: route("/groups"),
|
|
||||||
body: data,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get() {
|
|
||||||
return this.http.get<Group>({
|
|
||||||
url: route("/groups"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,19 +8,12 @@ import {
|
|||||||
ItemSummary,
|
ItemSummary,
|
||||||
ItemUpdate,
|
ItemUpdate,
|
||||||
} from "../types/data-contracts";
|
} from "../types/data-contracts";
|
||||||
import { AttachmentTypes, PaginationResult } from "../types/non-generated";
|
import { AttachmentTypes } 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(q: ItemsQuery = {}) {
|
getAll() {
|
||||||
return this.http.get<PaginationResult<ItemSummary>>({ url: route("/items", q) });
|
return this.http.get<Results<ItemSummary>>({ url: route("/items") });
|
||||||
}
|
}
|
||||||
|
|
||||||
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/non-generated";
|
import { Results } from "./types";
|
||||||
|
|
||||||
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/non-generated";
|
import { Results } from "./types";
|
||||||
|
|
||||||
export type LocationUpdate = LocationCreate;
|
export type LocationUpdate = LocationCreate;
|
||||||
|
|
||||||
|
|||||||
3
frontend/lib/api/classes/types/index.ts
Normal file
3
frontend/lib/api/classes/types/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export type Results<T> = {
|
||||||
|
items: T[];
|
||||||
|
};
|
||||||
@@ -16,19 +16,6 @@ export interface DocumentOut {
|
|||||||
title: string;
|
title: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Group {
|
|
||||||
createdAt: Date;
|
|
||||||
currency: string;
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
updatedAt: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GroupUpdate {
|
|
||||||
currency: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ItemAttachment {
|
export interface ItemAttachment {
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
document: DocumentOut;
|
document: DocumentOut;
|
||||||
@@ -200,13 +187,6 @@ export interface LocationSummary {
|
|||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PaginationResultRepoItemSummary {
|
|
||||||
items: ItemSummary[];
|
|
||||||
page: number;
|
|
||||||
pageSize: number;
|
|
||||||
total: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UserOut {
|
export interface UserOut {
|
||||||
email: string;
|
email: string;
|
||||||
groupId: string;
|
groupId: string;
|
||||||
|
|||||||
@@ -8,14 +8,3 @@ 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;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
export type Codes = "USD" | "EUR" | "GBP" | "JPY";
|
|
||||||
|
|
||||||
export type Currency = {
|
|
||||||
code: Codes;
|
|
||||||
local: string;
|
|
||||||
symbol: string;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const currencies: Currency[] = [
|
|
||||||
{
|
|
||||||
code: "USD",
|
|
||||||
local: "en-US",
|
|
||||||
symbol: "$",
|
|
||||||
name: "US Dollar",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "EUR",
|
|
||||||
local: "de-DE",
|
|
||||||
symbol: "€",
|
|
||||||
name: "Euro",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "GBP",
|
|
||||||
local: "en-GB",
|
|
||||||
symbol: "£",
|
|
||||||
name: "British Pound",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "JPY",
|
|
||||||
local: "ja-JP",
|
|
||||||
symbol: "¥",
|
|
||||||
name: "Japanese Yen",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
export type DaisyTheme =
|
|
||||||
| "light"
|
|
||||||
| "dark"
|
|
||||||
| "cupcake"
|
|
||||||
| "bumblebee"
|
|
||||||
| "emerald"
|
|
||||||
| "corporate"
|
|
||||||
| "synthwave"
|
|
||||||
| "retro"
|
|
||||||
| "cyberpunk"
|
|
||||||
| "valentine"
|
|
||||||
| "halloween"
|
|
||||||
| "garden"
|
|
||||||
| "forest"
|
|
||||||
| "aqua"
|
|
||||||
| "lofi"
|
|
||||||
| "pastel"
|
|
||||||
| "fantasy"
|
|
||||||
| "wireframe"
|
|
||||||
| "black"
|
|
||||||
| "luxury"
|
|
||||||
| "dracula"
|
|
||||||
| "cmyk"
|
|
||||||
| "autumn"
|
|
||||||
| "business"
|
|
||||||
| "acid"
|
|
||||||
| "lemonade"
|
|
||||||
| "night"
|
|
||||||
| "coffee"
|
|
||||||
| "winter";
|
|
||||||
|
|
||||||
export type ThemeOption = {
|
|
||||||
label: string;
|
|
||||||
value: DaisyTheme;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const themes: ThemeOption[] = [
|
|
||||||
{
|
|
||||||
label: "Garden",
|
|
||||||
value: "garden",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Light",
|
|
||||||
value: "light",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Cupcake",
|
|
||||||
value: "cupcake",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Bumblebee",
|
|
||||||
value: "bumblebee",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Emerald",
|
|
||||||
value: "emerald",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Corporate",
|
|
||||||
value: "corporate",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Synthwave",
|
|
||||||
value: "synthwave",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Retro",
|
|
||||||
value: "retro",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Cyberpunk",
|
|
||||||
value: "cyberpunk",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Valentine",
|
|
||||||
value: "valentine",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Halloween",
|
|
||||||
value: "halloween",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Forest",
|
|
||||||
value: "forest",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Aqua",
|
|
||||||
value: "aqua",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Lofi",
|
|
||||||
value: "lofi",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Pastel",
|
|
||||||
value: "pastel",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Fantasy",
|
|
||||||
value: "fantasy",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Wireframe",
|
|
||||||
value: "wireframe",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Black",
|
|
||||||
value: "black",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Luxury",
|
|
||||||
value: "luxury",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Dracula",
|
|
||||||
value: "dracula",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Cmyk",
|
|
||||||
value: "cmyk",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Autumn",
|
|
||||||
value: "autumn",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Business",
|
|
||||||
value: "business",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Acid",
|
|
||||||
value: "acid",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Lemonade",
|
|
||||||
value: "lemonade",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Night",
|
|
||||||
value: "night",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Coffee",
|
|
||||||
value: "coffee",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Winter",
|
|
||||||
value: "winter",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { useAuthStore } from "~~/stores/auth";
|
|
||||||
|
|
||||||
export default defineNuxtRouteMiddleware(async () => {
|
|
||||||
const auth = useAuthStore();
|
|
||||||
const api = useUserApi();
|
|
||||||
|
|
||||||
if (!auth.self) {
|
|
||||||
const { data, error } = await api.user.self();
|
|
||||||
if (error) {
|
|
||||||
navigateTo("/");
|
|
||||||
}
|
|
||||||
|
|
||||||
auth.$patch({ self: data.item });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -5,9 +5,8 @@
|
|||||||
import { useLocationStore } from "~~/stores/locations";
|
import { useLocationStore } from "~~/stores/locations";
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
middleware: ["auth"],
|
layout: "home",
|
||||||
});
|
});
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
title: "Homebox | Home",
|
title: "Homebox | Home",
|
||||||
});
|
});
|
||||||
@@ -16,6 +15,15 @@
|
|||||||
|
|
||||||
const auth = useAuthStore();
|
const auth = useAuthStore();
|
||||||
|
|
||||||
|
if (auth.self === null) {
|
||||||
|
const { data, error } = await api.user.self();
|
||||||
|
if (error) {
|
||||||
|
navigateTo("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
auth.$patch({ self: data.item });
|
||||||
|
}
|
||||||
|
|
||||||
const itemsStore = useItemStore();
|
const itemsStore = useItemStore();
|
||||||
const items = computed(() => itemsStore.items);
|
const items = computed(() => itemsStore.items);
|
||||||
|
|
||||||
@@ -149,6 +157,13 @@
|
|||||||
</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">
|
||||||
@@ -157,9 +172,9 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<BaseSectionHeader class="mb-5"> Labels </BaseSectionHeader>
|
<BaseSectionHeader class="mb-5"> Items </BaseSectionHeader>
|
||||||
<div class="flex gap-2 flex-wrap">
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
<LabelChip v-for="label in labels" :key="label.id" size="lg" :label="label" />
|
<ItemCard v-for="item in items" :key="item.id" :item="item" />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</BaseContainer>
|
</BaseContainer>
|
||||||
|
|||||||
@@ -15,7 +15,8 @@
|
|||||||
const { data } = await api.status();
|
const { data } = await api.status();
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
username.value = "demo@example.com";
|
console.log(data);
|
||||||
|
username.value = "demo@email.com";
|
||||||
password.value = "demo";
|
password.value = "demo";
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
@@ -23,7 +24,7 @@
|
|||||||
|
|
||||||
whenever(status, status => {
|
whenever(status, status => {
|
||||||
if (status?.demo) {
|
if (status?.demo) {
|
||||||
email.value = "demo@example.com";
|
email.value = "demo@email.com";
|
||||||
loginPassword.value = "demo";
|
loginPassword.value = "demo";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -147,10 +148,10 @@
|
|||||||
<a href="https://twitter.com/haybytes" class="tooltip" data-tip="Follow The Developer" target="_blank">
|
<a href="https://twitter.com/haybytes" class="tooltip" data-tip="Follow The Developer" target="_blank">
|
||||||
<Icon name="mdi-twitter" class="h-8 w-8" />
|
<Icon name="mdi-twitter" class="h-8 w-8" />
|
||||||
</a>
|
</a>
|
||||||
<a href="https://discord.gg/tuncmNrE4z" class="tooltip" data-tip="Join The Discord" target="_blank">
|
<a href="https://discord.gg/tuncmNrE4z" class="tooltip" data-tip="Join The Discord">
|
||||||
<Icon name="mdi-discord" class="h-8 w-8" />
|
<Icon name="mdi-discord" class="h-8 w-8" />
|
||||||
</a>
|
</a>
|
||||||
<a href="https://hay-kot.github.io/homebox/" class="tooltip" data-tip="Read The Docs" target="_blank">
|
<a href="https://hay-kot.github.io/homebox/" class="tooltip" data-tip="Read The Docs">
|
||||||
<Icon name="mdi-folder" class="h-8 w-8" />
|
<Icon name="mdi-folder" class="h-8 w-8" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -197,7 +198,7 @@
|
|||||||
</h2>
|
</h2>
|
||||||
<template v-if="status && status.demo">
|
<template v-if="status && status.demo">
|
||||||
<p class="text-xs italic text-center">This is a demo instance</p>
|
<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>Email</b> demo@email.com</p>
|
||||||
<p class="text-xs text-center"><b>Password</b> demo</p>
|
<p class="text-xs text-center"><b>Password</b> demo</p>
|
||||||
</template>
|
</template>
|
||||||
<FormTextField v-model="email" label="Email" />
|
<FormTextField v-model="email" label="Email" />
|
||||||
@@ -222,9 +223,6 @@
|
|||||||
</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>
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
import { capitalize } from "~~/lib/strings";
|
import { capitalize } from "~~/lib/strings";
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
middleware: ["auth"],
|
layout: "home",
|
||||||
});
|
});
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
@@ -30,13 +30,6 @@
|
|||||||
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(() => {
|
||||||
@@ -118,12 +111,12 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "text",
|
type: "text",
|
||||||
label: "Purchase Price",
|
label: "Purchased Price",
|
||||||
ref: "purchasePrice",
|
ref: "purchasePrice",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "date",
|
type: "date",
|
||||||
label: "Purchase Date",
|
label: "Purchased At",
|
||||||
ref: "purchaseTime",
|
ref: "purchaseTime",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -233,7 +226,6 @@
|
|||||||
loading: false,
|
loading: false,
|
||||||
|
|
||||||
// Values
|
// Values
|
||||||
obj: {},
|
|
||||||
id: "",
|
id: "",
|
||||||
title: "",
|
title: "",
|
||||||
type: "",
|
type: "",
|
||||||
@@ -249,13 +241,11 @@
|
|||||||
editState.title = attachment.document.title;
|
editState.title = attachment.document.title;
|
||||||
editState.type = attachment.type;
|
editState.type = attachment.type;
|
||||||
editState.modal = true;
|
editState.modal = true;
|
||||||
|
|
||||||
editState.obj = attachmentOpts.find(o => o.value === attachment.type);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateAttachment() {
|
async function updateAttachment() {
|
||||||
editState.loading = true;
|
editState.loading = true;
|
||||||
console.log(editState.type);
|
|
||||||
const { error, data } = await api.items.updateAttachment(itemId.value, editState.id, {
|
const { error, data } = await api.items.updateAttachment(itemId.value, editState.id, {
|
||||||
title: editState.title,
|
title: editState.title,
|
||||||
type: editState.type,
|
type: editState.type,
|
||||||
@@ -285,14 +275,7 @@
|
|||||||
<template #title> Attachment Edit </template>
|
<template #title> Attachment Edit </template>
|
||||||
|
|
||||||
<FormTextField v-model="editState.title" label="Attachment Title" />
|
<FormTextField v-model="editState.title" label="Attachment Title" />
|
||||||
<FormSelect
|
<FormSelect v-model="editState.type" label="Attachment Type" value="value" name="text" :items="attachmentOpts" />
|
||||||
v-model="editState.obj"
|
|
||||||
v-model:value="editState.type"
|
|
||||||
label="Attachment Type"
|
|
||||||
value-key="value"
|
|
||||||
name="text"
|
|
||||||
:items="attachmentOpts"
|
|
||||||
/>
|
|
||||||
<div class="modal-action">
|
<div class="modal-action">
|
||||||
<BaseButton :loading="editState.loading" @click="updateAttachment"> Update </BaseButton>
|
<BaseButton :loading="editState.loading" @click="updateAttachment"> Update </BaseButton>
|
||||||
</div>
|
</div>
|
||||||
@@ -325,7 +308,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-if="item" v-model="item.location" label="Location" :items="locations ?? []" />
|
<FormSelect v-model="item.location" label="Location" :items="locations ?? []" select-first />
|
||||||
<FormMultiselect v-model="item.labels" label="Labels" :items="labels ?? []" />
|
<FormMultiselect v-model="item.labels" label="Labels" :items="labels ?? []" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Detail, Details } from "~~/components/global/DetailsSection/types";
|
import { DateDetail, Detail } from "~~/components/global/DetailsSection/types";
|
||||||
import { ItemAttachment } from "~~/lib/api/types/data-contracts";
|
import { ItemAttachment } from "~~/lib/api/types/data-contracts";
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
middleware: ["auth"],
|
layout: "home",
|
||||||
});
|
});
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
text: item.value?.serialNumber,
|
text: item.value?.serialNumber,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Model Number",
|
name: "Mode Number",
|
||||||
text: item.value?.modelNumber,
|
text: item.value?.modelNumber,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -145,7 +145,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
const warrantyDetails = computed(() => {
|
const warrantyDetails = computed(() => {
|
||||||
const details: Details = [
|
const details: (Detail | DateDetail)[] = [
|
||||||
{
|
{
|
||||||
name: "Lifetime Warranty",
|
name: "Lifetime Warranty",
|
||||||
text: item.value?.lifetimeWarranty ? "Yes" : "No",
|
text: item.value?.lifetimeWarranty ? "Yes" : "No",
|
||||||
@@ -180,16 +180,15 @@
|
|||||||
return item.value?.purchaseFrom || item.value?.purchasePrice !== "0";
|
return item.value?.purchaseFrom || item.value?.purchasePrice !== "0";
|
||||||
});
|
});
|
||||||
|
|
||||||
const purchaseDetails = computed<Details>(() => {
|
const purchaseDetails = computed<Array<Detail | DateDetail>>(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: "Purchased From",
|
name: "Purchase From",
|
||||||
text: item.value?.purchaseFrom || "",
|
text: item.value?.purchaseFrom || "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Purchase Price",
|
name: "Purchase Price",
|
||||||
text: item.value?.purchasePrice || "",
|
text: item.value?.purchasePrice ? fmtCurrency(item.value.purchasePrice) : "",
|
||||||
type: "currency",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Purchase Date",
|
name: "Purchase Date",
|
||||||
@@ -206,7 +205,7 @@
|
|||||||
return item.value?.soldTo || item.value?.soldPrice !== "0";
|
return item.value?.soldTo || item.value?.soldPrice !== "0";
|
||||||
});
|
});
|
||||||
|
|
||||||
const soldDetails = computed<Details>(() => {
|
const soldDetails = computed<Array<Detail | DateDetail>>(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: "Sold To",
|
name: "Sold To",
|
||||||
@@ -214,8 +213,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Sold Price",
|
name: "Sold Price",
|
||||||
text: item.value?.soldPrice || "",
|
text: item.value?.soldPrice ? fmtCurrency(item.value.soldPrice) : "",
|
||||||
type: "currency",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Sold At",
|
name: "Sold At",
|
||||||
@@ -327,17 +325,17 @@
|
|||||||
</BaseCard>
|
</BaseCard>
|
||||||
|
|
||||||
<BaseCard v-if="showPurchase">
|
<BaseCard v-if="showPurchase">
|
||||||
<template #title> Purchase Details </template>
|
<template #title> Purchase </template>
|
||||||
<DetailsSection :details="purchaseDetails" />
|
<DetailsSection :details="purchaseDetails" />
|
||||||
</BaseCard>
|
</BaseCard>
|
||||||
|
|
||||||
<BaseCard v-if="showWarranty">
|
<BaseCard v-if="showWarranty">
|
||||||
<template #title> Warranty Details </template>
|
<template #title> Warranty </template>
|
||||||
<DetailsSection :details="warrantyDetails" />
|
<DetailsSection :details="warrantyDetails" />
|
||||||
</BaseCard>
|
</BaseCard>
|
||||||
|
|
||||||
<BaseCard v-if="showSold">
|
<BaseCard v-if="showSold">
|
||||||
<template #title> Sold Details </template>
|
<template #title> Sold </template>
|
||||||
<DetailsSection :details="soldDetails" />
|
<DetailsSection :details="soldDetails" />
|
||||||
</BaseCard>
|
</BaseCard>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
middleware: ["auth"],
|
layout: "home",
|
||||||
});
|
});
|
||||||
|
|
||||||
const show = reactive({
|
const show = reactive({
|
||||||
|
|||||||
@@ -1,110 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ItemSummary } from "~~/lib/api/types/data-contracts";
|
|
||||||
import { useLabelStore } from "~~/stores/labels";
|
|
||||||
import { useLocationStore } from "~~/stores/locations";
|
|
||||||
|
|
||||||
definePageMeta({
|
|
||||||
middleware: ["auth"],
|
|
||||||
});
|
|
||||||
|
|
||||||
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="Locations" :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>
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { CustomDetail, Detail } from "~~/components/global/DetailsSection/types";
|
import type { DateDetail, Detail } from "~~/components/global/DetailsSection/types";
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
middleware: ["auth"],
|
layout: "home",
|
||||||
});
|
});
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
return data;
|
return data;
|
||||||
});
|
});
|
||||||
|
|
||||||
const details = computed<(Detail | CustomDetail)[]>(() => {
|
const details = computed<(Detail | DateDetail)[]>(() => {
|
||||||
const details = [
|
const details = [
|
||||||
{
|
{
|
||||||
name: "Name",
|
name: "Name",
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Detail, CustomDetail } from "~~/components/global/DetailsSection/types";
|
import { Detail, DateDetail } from "~~/components/global/DetailsSection/types";
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
middleware: ["auth"],
|
layout: "home",
|
||||||
});
|
});
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
return data;
|
return data;
|
||||||
});
|
});
|
||||||
|
|
||||||
const details = computed<(Detail | CustomDetail)[]>(() => {
|
const details = computed<(Detail | DateDetail)[]>(() => {
|
||||||
const details = [
|
const details = [
|
||||||
{
|
{
|
||||||
name: "Name",
|
name: "Name",
|
||||||
|
|||||||
@@ -1,79 +1,137 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Detail } from "~~/components/global/DetailsSection/types";
|
import { Detail } from "~~/components/global/DetailsSection/types";
|
||||||
|
import { DaisyTheme } from "~~/composables/use-preferences";
|
||||||
import { useAuthStore } from "~~/stores/auth";
|
import { useAuthStore } from "~~/stores/auth";
|
||||||
import { themes } from "~~/lib/data/themes";
|
|
||||||
import { currencies, Currency } from "~~/lib/data/currency";
|
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
middleware: ["auth"],
|
layout: "home",
|
||||||
});
|
});
|
||||||
useHead({
|
useHead({
|
||||||
title: "Homebox | Profile",
|
title: "Homebox | Profile",
|
||||||
});
|
});
|
||||||
|
|
||||||
const api = useUserApi();
|
|
||||||
const confirm = useConfirm();
|
|
||||||
const notify = useNotifier();
|
|
||||||
|
|
||||||
// Currency Selection
|
|
||||||
const currency = ref<Currency>(currencies[0]);
|
|
||||||
|
|
||||||
watch(currency, () => {
|
|
||||||
if (group.value) {
|
|
||||||
group.value.currency = currency.value.code;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(group.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
const currencyExample = computed(() => {
|
|
||||||
const formatter = new Intl.NumberFormat("en-US", {
|
|
||||||
style: "currency",
|
|
||||||
currency: currency.value ? currency.value.code : "USD",
|
|
||||||
});
|
|
||||||
|
|
||||||
return formatter.format(1000);
|
|
||||||
});
|
|
||||||
|
|
||||||
const { data: group } = useAsyncData(async () => {
|
|
||||||
const { data } = await api.group.get();
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Sync Initial Currency
|
|
||||||
watch(group, () => {
|
|
||||||
if (group.value) {
|
|
||||||
const found = currencies.find(c => c.code === group.value.currency);
|
|
||||||
if (found) {
|
|
||||||
currency.value = found;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
async function updateGroup() {
|
|
||||||
const { data, error } = await api.group.update({
|
|
||||||
name: group.value.name,
|
|
||||||
currency: group.value.currency,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
notify.error("Failed to update group");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
group.value = data;
|
|
||||||
notify.success("Group updated");
|
|
||||||
}
|
|
||||||
|
|
||||||
const pubApi = usePublicApi();
|
|
||||||
const { data: status } = useAsyncData(async () => {
|
|
||||||
const { data } = await pubApi.status();
|
|
||||||
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
|
|
||||||
const { setTheme } = useTheme();
|
const { setTheme } = useTheme();
|
||||||
|
|
||||||
|
type ThemeOption = {
|
||||||
|
label: string;
|
||||||
|
value: DaisyTheme;
|
||||||
|
};
|
||||||
|
|
||||||
|
const themes: ThemeOption[] = [
|
||||||
|
{
|
||||||
|
label: "Garden",
|
||||||
|
value: "garden",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Light",
|
||||||
|
value: "light",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Cupcake",
|
||||||
|
value: "cupcake",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Bumblebee",
|
||||||
|
value: "bumblebee",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Emerald",
|
||||||
|
value: "emerald",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Corporate",
|
||||||
|
value: "corporate",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Synthwave",
|
||||||
|
value: "synthwave",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Retro",
|
||||||
|
value: "retro",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Cyberpunk",
|
||||||
|
value: "cyberpunk",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Valentine",
|
||||||
|
value: "valentine",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Halloween",
|
||||||
|
value: "halloween",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Forest",
|
||||||
|
value: "forest",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Aqua",
|
||||||
|
value: "aqua",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Lofi",
|
||||||
|
value: "lofi",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Pastel",
|
||||||
|
value: "pastel",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Fantasy",
|
||||||
|
value: "fantasy",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Wireframe",
|
||||||
|
value: "wireframe",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Black",
|
||||||
|
value: "black",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Luxury",
|
||||||
|
value: "luxury",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Dracula",
|
||||||
|
value: "dracula",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Cmyk",
|
||||||
|
value: "cmyk",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Autumn",
|
||||||
|
value: "autumn",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Business",
|
||||||
|
value: "business",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Acid",
|
||||||
|
value: "acid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Lemonade",
|
||||||
|
value: "lemonade",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Night",
|
||||||
|
value: "night",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Coffee",
|
||||||
|
value: "coffee",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Winter",
|
||||||
|
value: "winter",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const auth = useAuthStore();
|
const auth = useAuthStore();
|
||||||
|
|
||||||
const details = computed(() => {
|
const details = computed(() => {
|
||||||
@@ -89,6 +147,10 @@
|
|||||||
] as Detail[];
|
] as Detail[];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const api = useUserApi();
|
||||||
|
const confirm = useConfirm();
|
||||||
|
const notify = useNotifier();
|
||||||
|
|
||||||
async function deleteProfile() {
|
async function deleteProfile() {
|
||||||
const result = await confirm.open(
|
const result = await confirm.open(
|
||||||
"Are you sure you want to delete your account? If you are the last member in your group all your data will be deleted. This action cannot be undone."
|
"Are you sure you want to delete your account? If you are the last member in your group all your data will be deleted. This action cannot be undone."
|
||||||
@@ -214,27 +276,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</BaseCard>
|
</BaseCard>
|
||||||
|
|
||||||
<BaseCard>
|
|
||||||
<template #title>
|
|
||||||
<BaseSectionHeader class="pb-0">
|
|
||||||
<Icon name="mdi-accounts" class="mr-2 -mt-1 text-base-600" />
|
|
||||||
<span class="text-base-600"> Group Settings </span>
|
|
||||||
<template #description>
|
|
||||||
Shared Group Settings. You may need to refresh your browser for some settings to apply.
|
|
||||||
</template>
|
|
||||||
</BaseSectionHeader>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div v-if="group" class="p-5 pt-0">
|
|
||||||
<FormSelect v-model="currency" label="Currency Format" :items="currencies" />
|
|
||||||
<p class="m-2 text-sm">Example: {{ currencyExample }}</p>
|
|
||||||
|
|
||||||
<div class="mt-4 flex justify-end">
|
|
||||||
<BaseButton @click="updateGroup"> Update Group </BaseButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</BaseCard>
|
|
||||||
|
|
||||||
<BaseCard>
|
<BaseCard>
|
||||||
<template #title>
|
<template #title>
|
||||||
<BaseSectionHeader>
|
<BaseSectionHeader>
|
||||||
@@ -300,9 +341,6 @@
|
|||||||
</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,7 +22,6 @@ 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