Init
2
.gitattributes
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
576
.gitignore
vendored
Normal file
|
@ -0,0 +1,576 @@
|
|||
### JetBrains template
|
||||
.idea/
|
||||
|
||||
### Linux template
|
||||
*~
|
||||
|
||||
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||
.fuse_hidden*
|
||||
|
||||
# KDE directory preferences
|
||||
.directory
|
||||
|
||||
# Linux trash folder which might appear on any partition or disk
|
||||
.Trash-*
|
||||
|
||||
# .nfs files are created when an open file is removed but is still being accessed
|
||||
.nfs*
|
||||
|
||||
### Node template
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
### Go template
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
### VisualStudio template
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
### Windows template
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
Thumbs.db:encryptable
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Dump file
|
||||
*.stackdump
|
||||
|
||||
# Folder config file
|
||||
[Dd]esktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
### macOS template
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
storage/
|
||||
docs/
|
||||
dist/
|
||||
tmp/
|
||||
|
||||
docker/**/*.sqlite3
|
||||
docker/**/*.txt
|
9
.gitlab-ci.yml
Normal file
|
@ -0,0 +1,9 @@
|
|||
stages:
|
||||
- build
|
||||
- deploy
|
||||
|
||||
include:
|
||||
- local: .gitlab/_common.gitlab-ci.yml
|
||||
- local: .gitlab/_rules.gitlab-ci.yml
|
||||
- local: /.gitlab/build.gitlab-ci.yml
|
||||
- local: /.gitlab/deploy.gitlab-ci.yml
|
14
.gitlab/_common.gitlab-ci.yml
Normal file
|
@ -0,0 +1,14 @@
|
|||
variables:
|
||||
LATEST_IMAGE: '$CI_REGISTRY_IMAGE:latest'
|
||||
DOCKER_VERSION: '24.0.2'
|
||||
GOLANG_VERSION: '1.20'
|
||||
NODE_VERSION: '18'
|
||||
ALPINE_VERSION: '3'
|
||||
DEBIAN_VERSION: '12'
|
||||
TELEPORT_VERSION: '13.1.1'
|
||||
|
||||
image: docker:$DOCKER_VERSION-git
|
||||
|
||||
.login_registry:
|
||||
before_script:
|
||||
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
17
.gitlab/_rules.gitlab-ci.yml
Normal file
|
@ -0,0 +1,17 @@
|
|||
.if-release-candidate-tag: &if-release-candidate-tag
|
||||
if: '$CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+-rc[0-9]+/'
|
||||
|
||||
.if-stable-release-tag: &if-stable-release-tag
|
||||
if: '$CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+$/'
|
||||
|
||||
.if-default-branch: &if-default-branch
|
||||
if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
|
||||
.rules:release:
|
||||
rules:
|
||||
- <<: *if-release-candidate-tag
|
||||
- <<: *if-stable-release-tag
|
||||
|
||||
.rules:default:
|
||||
rules:
|
||||
- <<: *if-default-branch
|
24
.gitlab/build.gitlab-ci.yml
Normal file
|
@ -0,0 +1,24 @@
|
|||
build_release:
|
||||
rules: !reference [.rules:release, rules]
|
||||
stage: build
|
||||
extends: .login_registry
|
||||
services:
|
||||
- name: docker:$DOCKER_VERSION-dind
|
||||
alias: docker
|
||||
variables:
|
||||
DOCKER_TLS_CERTDIR: '/certs'
|
||||
CURRENT_IMAGE: '$CI_REGISTRY_IMAGE:$CI_COMMIT_TAG'
|
||||
script:
|
||||
- >
|
||||
docker build .
|
||||
--file docker/Dockerfile
|
||||
--build-arg GOLANG_VERSION=$GOLANG_VERSION
|
||||
--build-arg NODE_VERSION=$NODE_VERSION
|
||||
--build-arg ALPINE_VERSION=$ALPINE_VERSION
|
||||
--build-arg APP_VERSION=$CI_COMMIT_TAG
|
||||
--build-arg BUILD_TIME=$CI_JOB_STARTED_AT
|
||||
--tag $CURRENT_IMAGE
|
||||
--tag $LATEST_IMAGE
|
||||
- docker inspect $CURRENT_IMAGE
|
||||
- docker push $CURRENT_IMAGE
|
||||
- docker push $LATEST_IMAGE
|
18
.gitlab/deploy.gitlab-ci.yml
Normal file
|
@ -0,0 +1,18 @@
|
|||
deploy_release:
|
||||
rules: !reference [.rules:release, rules]
|
||||
stage: deploy
|
||||
image: debian:${DEBIAN_VERSION}-slim
|
||||
id_tokens:
|
||||
TBOT_GITLAB_JWT:
|
||||
aud: tp.fhoss.de
|
||||
script:
|
||||
- apt-get update && apt-get install curl -y
|
||||
- cd /tmp
|
||||
- 'curl -O https://cdn.teleport.dev/teleport-v${TELEPORT_VERSION}-linux-amd64-bin.tar.gz'
|
||||
- tar -xvf teleport-v${TELEPORT_VERSION}-linux-amd64-bin.tar.gz
|
||||
- ./teleport/install
|
||||
- 'tbot start --token=gitlab --destination-dir=/tmp/tbot-user --data-dir=/tmp/tbot-data --auth-server=tp.fhoss.de:443 --join-method=gitlab --oneshot'
|
||||
- 'tsh -i /tmp/tbot-user/identity --proxy tp.fhoss.de:443 ssh bot@cafe "docker compose -f /opt/docker/cafe/docker-compose.yml up -d --pull always"'
|
||||
- 'tsh -i /tmp/tbot-user/identity --proxy tp.fhoss.de:443 ssh bot@cafe "docker system prune --force"'
|
||||
environment:
|
||||
name: production
|
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 Florian Hoss
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
76
README.md
Normal file
|
@ -0,0 +1,76 @@
|
|||
# Café Plätschwiesle
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
## docker-compose example
|
||||
|
||||
```yaml
|
||||
services:
|
||||
cafe:
|
||||
image: ghcr.io/flohoss/cafe-plaetschwiesle:latest
|
||||
container_name: cafe
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- ALLOWED_HOSTS=http://localhost:5000,https://home.example.com
|
||||
- SWAGGER=true
|
||||
- LOG_LEVEL=info # trace,debug,info,warn,error,fatal,panic
|
||||
volumes:
|
||||
- ./storage:/app/storage
|
||||
ports:
|
||||
- '127.0.0.1:5000:5000'
|
||||
```
|
||||
|
||||
## docker-compose example with MariaDB as database
|
||||
|
||||
```yaml
|
||||
networks:
|
||||
net:
|
||||
external: false
|
||||
|
||||
services:
|
||||
cafe-db:
|
||||
image: lscr.io/linuxserver/mariadb:latest
|
||||
container_name: cafe-db
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- MYSQL_ROOT_PASSWORD=root
|
||||
- TZ=Europe/Berlin
|
||||
- MYSQL_DATABASE=db
|
||||
- MYSQL_USER=user
|
||||
- MYSQL_PASSWORD=password
|
||||
volumes:
|
||||
- ./db:/config
|
||||
expose:
|
||||
- 3306
|
||||
networks:
|
||||
- net
|
||||
|
||||
cafe:
|
||||
image: ghcr.io/flohoss/cafe-plaetschwiesle:latest
|
||||
container_name: cafe
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- cafe-db
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- ALLOWED_HOSTS=http://localhost:5000,https://home.example.com
|
||||
- SWAGGER=true
|
||||
- LOG_LEVEL=info # trace,debug,info,warn,error,fatal,panic
|
||||
- MYSQL_URL=cafe-db:3306
|
||||
- MYSQL_USER=user
|
||||
- MYSQL_PASSWORD=password
|
||||
- MYSQL_DATABASE=db
|
||||
volumes:
|
||||
- ./storage:/app/storage
|
||||
ports:
|
||||
- '127.0.0.1:5000:5000'
|
||||
networks:
|
||||
- net
|
||||
```
|
52
api/middlwares.go
Normal file
|
@ -0,0 +1,52 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"cafe/config"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-contrib/cors"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func myLogger() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if logrus.GetLevel() != logrus.TraceLevel {
|
||||
return
|
||||
}
|
||||
reqUri := c.Request.RequestURI
|
||||
if strings.Contains(reqUri, "/storage") {
|
||||
return
|
||||
}
|
||||
startTime := time.Now()
|
||||
c.Next()
|
||||
endTime := time.Now()
|
||||
latencyTime := endTime.Sub(startTime)
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"status": http.StatusText(c.Writer.Status()),
|
||||
"latency": latencyTime,
|
||||
"client": c.ClientIP(),
|
||||
"method": c.Request.Method,
|
||||
}).Trace(reqUri)
|
||||
}
|
||||
}
|
||||
|
||||
func authHeader() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.Writer.Header().Set("Remote-Groups", c.Request.Header.Get("Remote-Groups"))
|
||||
c.Writer.Header().Set("Remote-Name", c.Request.Header.Get("Remote-Name"))
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Api) SetMiddlewares() {
|
||||
a.Router.Use(myLogger())
|
||||
a.Router.Use(gin.Recovery())
|
||||
a.Router.Use(cors.Default())
|
||||
_ = a.Router.SetTrustedProxies(nil)
|
||||
a.Router.MaxMultipartMemory = 8 << 20 // 8 MiB
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"allowedOrigins": config.Cafe.AllowedHosts,
|
||||
}).Debug("Middlewares set")
|
||||
}
|
62
api/router.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (a *Api) SetupRouter() {
|
||||
api := a.Router.Group("/api")
|
||||
{
|
||||
tableGroup := api.Group("/tables")
|
||||
{
|
||||
tableGroup.GET("", a.getTables)
|
||||
tableGroup.POST("", a.createTable)
|
||||
tableGroup.DELETE("", a.deleteTable)
|
||||
}
|
||||
orderGroup := api.Group("/orders")
|
||||
{
|
||||
orderGroup.GET("", a.getOrders)
|
||||
orderGroup.POST("", a.createOrder)
|
||||
orderGroup.DELETE("", a.deleteOrder)
|
||||
orderGroup.PUT("", a.updateOrder)
|
||||
orderGroup.GET("/ws", a.serveWs)
|
||||
orderItemGroup := orderGroup.Group("/items")
|
||||
{
|
||||
orderItemGroup.GET("", a.getOrderItems)
|
||||
orderItemGroup.POST("", a.createOrderItem)
|
||||
orderItemGroup.PUT("", a.updateOrderItem)
|
||||
orderItemGroup.DELETE("/:id", a.deleteOrderItem)
|
||||
}
|
||||
}
|
||||
billGroup := api.Group("/bills")
|
||||
{
|
||||
billGroup.GET("", a.getBills)
|
||||
billGroup.POST("", a.createBill)
|
||||
billGroup.DELETE("/:id", a.deleteBill)
|
||||
billItemGroup := billGroup.Group("/items")
|
||||
{
|
||||
billItemGroup.GET("", a.getBillItems)
|
||||
}
|
||||
}
|
||||
userGroup := api.Group("/users")
|
||||
{
|
||||
userGroup.GET("/:username", a.getUser)
|
||||
userGroup.PUT("", a.updateUser)
|
||||
}
|
||||
health := api.Group("/health")
|
||||
{
|
||||
health.Use(authHeader())
|
||||
health.GET("", func(c *gin.Context) {
|
||||
c.Status(http.StatusOK)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
a.Router.NoRoute(func(c *gin.Context) {
|
||||
c.HTML(http.StatusOK, "index.html", nil)
|
||||
})
|
||||
logrus.WithField("amount", len(a.Router.Routes())).Debug("Routes initialized")
|
||||
}
|
112
api/routesBill.go
Normal file
|
@ -0,0 +1,112 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"cafe/service"
|
||||
"cafe/types"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Schemes
|
||||
// @Summary get all bills
|
||||
// @Description gets all bills as array
|
||||
// @Tags bills
|
||||
// @Produce json
|
||||
// @Param year query int true "year"
|
||||
// @Param month query int true "month (1-12)"
|
||||
// @Param day query int true "day (1-31)"
|
||||
// @Success 200 {array} service.Bill
|
||||
// @Router /bills [get]
|
||||
func (a *Api) getBills(c *gin.Context) {
|
||||
year, presentYear := c.GetQuery("year")
|
||||
month, presentMonth := c.GetQuery("month")
|
||||
day, presentDay := c.GetQuery("day")
|
||||
if !presentYear || !presentMonth || !presentDay {
|
||||
c.JSON(http.StatusBadRequest, errorResponse{types.MissingInformation.String()})
|
||||
return
|
||||
}
|
||||
bills, err := service.GetAllBills(year, month, day)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, errorResponse{err.Error()})
|
||||
} else {
|
||||
c.JSON(http.StatusOK, bills)
|
||||
}
|
||||
}
|
||||
|
||||
// @Schemes
|
||||
// @Summary get all billItems
|
||||
// @Description gets all billItems for bill
|
||||
// @Tags bills
|
||||
// @Produce json
|
||||
// @Param bill query int true "Bill ID"
|
||||
// @Success 200 {array} service.BillItem
|
||||
// @Router /bills/items [get]
|
||||
func (a *Api) getBillItems(c *gin.Context) {
|
||||
bill, err := service.DoesBillExist(c.Query("bill"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, errorResponse{err.Error()})
|
||||
return
|
||||
}
|
||||
billItems, err := service.GetAllBillItems(bill.ID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, errorResponse{err.Error()})
|
||||
} else {
|
||||
c.JSON(http.StatusOK, billItems)
|
||||
}
|
||||
}
|
||||
|
||||
// @Schemes
|
||||
// @Summary create new bill
|
||||
// @Description creates a new bill and returns it
|
||||
// @Tags bills
|
||||
// @Produce json
|
||||
// @Param table query int true "Table ID"
|
||||
// @Param filter query string false "filter"
|
||||
// @Success 201 {object} service.Bill
|
||||
// @Failure 404 "Not Found"
|
||||
// @Failure 500 {object} errorResponse
|
||||
// @Router /bills [post]
|
||||
func (a *Api) createBill(c *gin.Context) {
|
||||
table, tableErr := strconv.ParseUint(c.Query("table"), 10, 64)
|
||||
if tableErr != nil {
|
||||
c.JSON(http.StatusBadRequest, errorResponse{types.MissingInformation.String()})
|
||||
return
|
||||
}
|
||||
stringFiler, filterPresent := c.GetQuery("filter")
|
||||
var filter []string
|
||||
if filterPresent {
|
||||
filter = strings.Split(stringFiler, ",")
|
||||
}
|
||||
bill, err := service.CreateBill(service.GetOrderOptions{TableId: table, Grouped: true, Filter: filter})
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, errorResponse{err.Error()})
|
||||
}
|
||||
c.JSON(http.StatusCreated, bill)
|
||||
}
|
||||
|
||||
// @Schemes
|
||||
// @Summary delete a bill
|
||||
// @Description deletes a bill
|
||||
// @Tags bills
|
||||
// @Produce json
|
||||
// @Param id path int true "Bill ID"
|
||||
// @Success 200 "OK"
|
||||
// @Failure 404 "Not Found"
|
||||
// @Failure 500 {object} errorResponse
|
||||
// @Router /bills/{id} [delete]
|
||||
func (a *Api) deleteBill(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
bill, err := service.DoesBillExist(id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, errorResponse{err.Error()})
|
||||
return
|
||||
}
|
||||
err = service.DeleteBill(&bill)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, errorResponse{err.Error()})
|
||||
}
|
||||
c.Status(http.StatusOK)
|
||||
}
|
258
api/routesOrder.go
Normal file
|
@ -0,0 +1,258 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"cafe/hub"
|
||||
"cafe/service"
|
||||
"cafe/types"
|
||||
ws "cafe/websocket"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// @Schemes
|
||||
// @Summary get all orders
|
||||
// @Description gets all orders as array
|
||||
// @Tags orders
|
||||
// @Produce json
|
||||
// @Param table query int false "Table ID"
|
||||
// @Param grouping query bool false "grouping"
|
||||
// @Param filter query string false "filter"
|
||||
// @Success 200 {array} service.Order
|
||||
// @Router /orders [get]
|
||||
func (a *Api) getOrders(c *gin.Context) {
|
||||
table, _ := strconv.ParseUint(c.Query("table"), 10, 64)
|
||||
grouping, _ := strconv.ParseBool(c.Query("grouping"))
|
||||
stringFiler, filterPresent := c.GetQuery("filter")
|
||||
var filter []string
|
||||
if filterPresent {
|
||||
filter = strings.Split(stringFiler, ",")
|
||||
}
|
||||
options := service.GetOrderOptions{TableId: table, Grouped: grouping, Filter: filter}
|
||||
var orders []service.Order
|
||||
if options.TableId == 0 {
|
||||
orders = service.GetAllActiveOrders()
|
||||
} else {
|
||||
orders = service.GetAllOrdersForTable(options)
|
||||
}
|
||||
c.JSON(http.StatusOK, orders)
|
||||
}
|
||||
|
||||
// @Schemes
|
||||
// @Summary create new order
|
||||
// @Description creates a new order and returns it
|
||||
// @Tags orders
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param item query int true "OrderItem ID"
|
||||
// @Param table query int true "Table ID"
|
||||
// @Success 201 {object} service.Order
|
||||
// @Failure 400 {object} errorResponse
|
||||
// @Failure 500 {object} errorResponse
|
||||
// @Router /orders [post]
|
||||
func (a *Api) createOrder(c *gin.Context) {
|
||||
table, err1 := strconv.ParseUint(c.Query("table"), 10, 64)
|
||||
item, err2 := strconv.ParseUint(c.Query("item"), 10, 64)
|
||||
if err1 != nil || err2 != nil {
|
||||
c.JSON(http.StatusBadRequest, errorResponse{types.MissingInformation.String()})
|
||||
return
|
||||
}
|
||||
order := service.Order{TableID: table, OrderItemID: item, IsServed: false}
|
||||
err := service.CreateOrder(&order)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, errorResponse{err.Error()})
|
||||
} else {
|
||||
c.JSON(http.StatusCreated, order)
|
||||
}
|
||||
}
|
||||
|
||||
// @Schemes
|
||||
// @Summary delete an order
|
||||
// @Description deletes an order from the database
|
||||
// @Tags orders
|
||||
// @Produce json
|
||||
// @Param item query int true "OrderItem ID"
|
||||
// @Param table query int true "Table ID"
|
||||
// @Success 200 "OK"
|
||||
// @Failure 400 {object} errorResponse
|
||||
// @Failure 500 {object} errorResponse
|
||||
// @Router /orders [delete]
|
||||
func (a *Api) deleteOrder(c *gin.Context) {
|
||||
item := c.Query("item")
|
||||
table := c.Query("table")
|
||||
if table == "" || item == "" {
|
||||
c.JSON(http.StatusBadRequest, errorResponse{types.MissingInformation.String()})
|
||||
return
|
||||
}
|
||||
err := service.DeleteOrder(table, item)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, errorResponse{err.Error()})
|
||||
} else {
|
||||
c.Status(http.StatusOK)
|
||||
}
|
||||
}
|
||||
|
||||
// @Schemes
|
||||
// @Summary update an order
|
||||
// @Description updates an order with provided information
|
||||
// @Tags orders
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param order body service.Order true "updated Order"
|
||||
// @Success 200 {object} service.Order
|
||||
// @Failure 400 {object} errorResponse
|
||||
// @Failure 404 "Not Found" errorResponse
|
||||
// @Failure 500 {object} errorResponse
|
||||
// @Router /orders [put]
|
||||
func (a *Api) updateOrder(c *gin.Context) {
|
||||
var newOrder service.Order
|
||||
err := c.ShouldBindJSON(&newOrder)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, errorResponse{types.MissingInformation.String()})
|
||||
return
|
||||
}
|
||||
oldOrder, err := service.DoesOrderExist(strconv.Itoa(int(newOrder.ID)))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, errorResponse{err.Error()})
|
||||
return
|
||||
}
|
||||
err = service.UpdateOrder(&oldOrder, &newOrder)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, errorResponse{err.Error()})
|
||||
} else {
|
||||
c.JSON(http.StatusOK, newOrder)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Api) serveWs(c *gin.Context) {
|
||||
conn, err := ws.Upgrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Warning("Cannot upgrade websocket")
|
||||
return
|
||||
}
|
||||
messageChan := make(hub.NotifierChan)
|
||||
a.Hub.NewClients <- messageChan
|
||||
defer func() {
|
||||
a.Hub.ClosingClients <- messageChan
|
||||
conn.Close()
|
||||
}()
|
||||
go ws.ReadPump(conn)
|
||||
for {
|
||||
msg, ok := <-messageChan
|
||||
if !ok {
|
||||
err := conn.WriteMessage(websocket.CloseMessage, []byte{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
err := conn.WriteJSON(msg)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @Schemes
|
||||
// @Summary get all orderItems
|
||||
// @Description gets all orderItems as array
|
||||
// @Tags orderItems
|
||||
// @Produce json
|
||||
// @Param type query int true "ItemType"
|
||||
// @Success 200 {array} service.OrderItem
|
||||
// @Router /orders/items [get]
|
||||
func (a *Api) getOrderItems(c *gin.Context) {
|
||||
orderType := c.Query("type")
|
||||
if orderType == "" {
|
||||
c.JSON(http.StatusBadRequest, errorResponse{types.MissingInformation.String()})
|
||||
} else {
|
||||
c.JSON(http.StatusOK, service.GetOrderItemsForType(orderType))
|
||||
}
|
||||
}
|
||||
|
||||
// @Schemes
|
||||
// @Summary create new orderItem
|
||||
// @Description creates a new orderItem and returns it
|
||||
// @Tags orderItems
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param order body service.OrderItem true "OrderItem ID"
|
||||
// @Success 201 {object} service.OrderItem
|
||||
// @Failure 400 {object} errorResponse
|
||||
// @Failure 500 {object} errorResponse
|
||||
// @Router /orders/items [post]
|
||||
func (a *Api) createOrderItem(c *gin.Context) {
|
||||
var orderItem service.OrderItem
|
||||
err := c.ShouldBindJSON(&orderItem)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, errorResponse{types.MissingInformation.String()})
|
||||
return
|
||||
}
|
||||
err = service.CreateOrderItem(&orderItem)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, errorResponse{err.Error()})
|
||||
} else {
|
||||
c.JSON(http.StatusCreated, orderItem)
|
||||
}
|
||||
}
|
||||
|
||||
// @Schemes
|
||||
// @Summary update a orderItem
|
||||
// @Description updates a orderItem with provided information
|
||||
// @Tags orderItems
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param orderItem body service.OrderItem true "updated OrderItem"
|
||||
// @Success 200 {object} service.OrderItem
|
||||
// @Failure 400 {object} errorResponse
|
||||
// @Failure 404 "Not Found" errorResponse
|
||||
// @Failure 500 {object} errorResponse
|
||||
// @Router /orders/items [put]
|
||||
func (a *Api) updateOrderItem(c *gin.Context) {
|
||||
var newOrderItem service.OrderItem
|
||||
err := c.ShouldBindJSON(&newOrderItem)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, errorResponse{types.MissingInformation.String()})
|
||||
return
|
||||
}
|
||||
oldOrderItem, err := service.DoesOrderItemExist(strconv.Itoa(int(newOrderItem.ID)))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, errorResponse{err.Error()})
|
||||
return
|
||||
}
|
||||
err = service.UpdateOrderItem(&oldOrderItem, &newOrderItem)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, errorResponse{err.Error()})
|
||||
} else {
|
||||
c.JSON(http.StatusOK, newOrderItem)
|
||||
}
|
||||
}
|
||||
|
||||
// @Schemes
|
||||
// @Summary delete an orderItem
|
||||
// @Description deletes an orderItem from the database
|
||||
// @Tags orderItems
|
||||
// @Produce json
|
||||
// @Param id path int true "OrderItem ID"
|
||||
// @Success 200 "OK"
|
||||
// @Failure 404 "Not Found"
|
||||
// @Failure 500 {object} errorResponse
|
||||
// @Router /orders/items/{id} [delete]
|
||||
func (a *Api) deleteOrderItem(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
orderItem, err := service.DoesOrderItemExist(id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, errorResponse{err.Error()})
|
||||
return
|
||||
}
|
||||
err = service.DeleteOrderItem(&orderItem)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, errorResponse{err.Error()})
|
||||
} else {
|
||||
c.Status(http.StatusOK)
|
||||
}
|
||||
}
|
54
api/routesTable.go
Normal file
|
@ -0,0 +1,54 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"cafe/service"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Schemes
|
||||
// @Summary get all active tables
|
||||
// @Description gets all active tables as array
|
||||
// @Tags tables
|
||||
// @Produce json
|
||||
// @Success 200 {array} service.Table
|
||||
// @Router /tables [get]
|
||||
func (a *Api) getTables(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, service.GetAllTables())
|
||||
}
|
||||
|
||||
// @Schemes
|
||||
// @Summary create new table
|
||||
// @Description creates a new table and returns it
|
||||
// @Tags tables
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 201 {object} service.Table "Table has been created"
|
||||
// @Failure 500 {object} errorResponse "Cannot create table"
|
||||
// @Router /tables [post]
|
||||
func (a *Api) createTable(c *gin.Context) {
|
||||
table, err := service.CreateNewTable()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, errorResponse{err.Error()})
|
||||
} else {
|
||||
c.JSON(http.StatusCreated, table)
|
||||
}
|
||||
}
|
||||
|
||||
// @Schemes
|
||||
// @Summary delete the latest table
|
||||
// @Description deletes the latest table from the database
|
||||
// @Tags tables
|
||||
// @Produce json
|
||||
// @Success 200 "OK"
|
||||
// @Failure 500 {object} errorResponse "Cannot delete table"
|
||||
// @Router /tables [delete]
|
||||
func (a *Api) deleteTable(c *gin.Context) {
|
||||
err := service.DeleteLatestTable()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, errorResponse{err.Error()})
|
||||
} else {
|
||||
c.Status(http.StatusOK)
|
||||
}
|
||||
}
|
60
api/routesUser.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"cafe/types"
|
||||
"cafe/user"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Schemes
|
||||
// @Summary get a user
|
||||
// @Description gets a user
|
||||
// @Tags users
|
||||
// @Produce json
|
||||
// @Param username path string true "Username"
|
||||
// @Success 200 {object} user.User
|
||||
// @Failure 500 {object} errorResponse
|
||||
// @Router /users/{username} [get]
|
||||
func (a *Api) getUser(c *gin.Context) {
|
||||
username := c.Param("username")
|
||||
u, err := user.GetUserOrCreate(username)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, errorResponse{err.Error()})
|
||||
} else {
|
||||
c.JSON(http.StatusOK, u)
|
||||
}
|
||||
}
|
||||
|
||||
// @Schemes
|
||||
// @Summary update a user
|
||||
// @Description updates a user with provided information
|
||||
// @Tags users
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param user body user.User true "updated User"
|
||||
// @Success 200 {object} user.User
|
||||
// @Failure 400 {object} errorResponse
|
||||
// @Failure 404 "Not Found" errorResponse
|
||||
// @Failure 500 {object} errorResponse
|
||||
// @Router /users [put]
|
||||
func (a *Api) updateUser(c *gin.Context) {
|
||||
var newUser user.User
|
||||
err := c.ShouldBindJSON(&newUser)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, errorResponse{types.MissingInformation.String()})
|
||||
return
|
||||
}
|
||||
oldUser, err := user.DoesUserExist(newUser.Username)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, errorResponse{err.Error()})
|
||||
return
|
||||
}
|
||||
err = user.UpdateUser(&oldUser, &newUser)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, errorResponse{err.Error()})
|
||||
} else {
|
||||
c.JSON(http.StatusOK, newUser)
|
||||
}
|
||||
}
|
26
api/static.go
Normal file
|
@ -0,0 +1,26 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"cafe/config"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-contrib/static"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (a *Api) HandleStaticFiles() {
|
||||
a.Router.LoadHTMLFiles(config.TemplatesDir + "index.html")
|
||||
a.serveFoldersInTemplates()
|
||||
}
|
||||
|
||||
func (a *Api) serveFoldersInTemplates() {
|
||||
_ = filepath.WalkDir(config.TemplatesDir, func(path string, info os.DirEntry, err error) error {
|
||||
if info.IsDir() && info.Name() != strings.TrimSuffix(config.TemplatesDir, "/") {
|
||||
a.Router.Use(static.Serve("/"+info.Name(), static.LocalFile(config.TemplatesDir+info.Name(), false)))
|
||||
logrus.WithField("folder", info.Name()).Debug("Serve static folder")
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
31
api/swagger.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"cafe/config"
|
||||
"cafe/docs"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/sirupsen/logrus"
|
||||
swaggerFiles "github.com/swaggo/files"
|
||||
ginSwagger "github.com/swaggo/gin-swagger"
|
||||
)
|
||||
|
||||
func (a *Api) SetupSwagger() {
|
||||
if config.Cafe.Swagger {
|
||||
docs.SwaggerInfo.Title = "Cafe"
|
||||
docs.SwaggerInfo.Description = "This is the backend of a cafe"
|
||||
docs.SwaggerInfo.Version = os.Getenv("VERSION")
|
||||
docs.SwaggerInfo.BasePath = "/api"
|
||||
parsed, _ := url.Parse(config.Cafe.AllowedHosts[0])
|
||||
docs.SwaggerInfo.Host = parsed.Host
|
||||
|
||||
a.Router.GET("/swagger", func(c *gin.Context) {
|
||||
c.Redirect(http.StatusMovedPermanently, "/swagger/index.html")
|
||||
})
|
||||
a.Router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
||||
logrus.WithField("url", config.Cafe.AllowedHosts[0]+"/swagger").Info("Swagger running")
|
||||
}
|
||||
}
|
16
api/types.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"cafe/hub"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Api struct {
|
||||
Router *gin.Engine
|
||||
Hub hub.Hub
|
||||
}
|
||||
|
||||
type errorResponse struct {
|
||||
Error string `json:"error" validate:"required"`
|
||||
}
|
10
config.toml
Normal file
|
@ -0,0 +1,10 @@
|
|||
ALLOWED_HOSTS = "https://cafe.test"
|
||||
LOG_LEVEL = "info"
|
||||
PORT = 5000
|
||||
SWAGGER = true
|
||||
|
||||
[MYSQL]
|
||||
DATABASE = ""
|
||||
PASSWORD = ""
|
||||
URL = ""
|
||||
USER = ""
|
72
config/config.go
Normal file
|
@ -0,0 +1,72 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"cafe/database"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/unjx-de/go-folder"
|
||||
)
|
||||
|
||||
const StorageDir = "storage/"
|
||||
const TemplatesDir = "templates/"
|
||||
|
||||
type Config struct {
|
||||
Port int
|
||||
AllowedHosts []string `mapstructure:"ALLOWED_HOSTS"`
|
||||
Swagger bool
|
||||
Bookmarks bool
|
||||
LogLevel string `mapstructure:"LOG_LEVEL"`
|
||||
Database database.MySQL `mapstructure:"MYSQL"`
|
||||
}
|
||||
|
||||
var Cafe = Config{}
|
||||
|
||||
func configLogger() {
|
||||
logrus.SetFormatter(&logrus.TextFormatter{TimestampFormat: "2006/01/02 15:04:05", FullTimestamp: true})
|
||||
}
|
||||
|
||||
func readConfig() {
|
||||
viper.AddConfigPath(".")
|
||||
viper.SetConfigName("config")
|
||||
viper.SetConfigType("toml")
|
||||
err := viper.ReadInConfig()
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Fatal("Failed opening config file")
|
||||
}
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||
viper.AutomaticEnv()
|
||||
err = viper.Unmarshal(&Cafe, viper.DecodeHook(mapstructure.StringToSliceHookFunc(",")))
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Fatal("Failed reading environment variables")
|
||||
}
|
||||
logrus.WithField("file", viper.ConfigFileUsed()).Info("Initializing configuration")
|
||||
}
|
||||
|
||||
func setLogLevel() {
|
||||
logLevel, err := logrus.ParseLevel(Cafe.LogLevel)
|
||||
if err != nil {
|
||||
logrus.SetLevel(logrus.InfoLevel)
|
||||
} else {
|
||||
logrus.SetLevel(logLevel)
|
||||
}
|
||||
logrus.WithField("logLevel", logLevel.String()).Debug("Log level set")
|
||||
}
|
||||
|
||||
func createFolderStructure() {
|
||||
folders := []string{StorageDir, TemplatesDir}
|
||||
err := folder.CreateFolders(folders, 0755)
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Fatal("Failed creating folders")
|
||||
}
|
||||
logrus.WithField("folders", folders).Debug("Folders created")
|
||||
}
|
||||
|
||||
func init() {
|
||||
configLogger()
|
||||
readConfig()
|
||||
setLogLevel()
|
||||
createFolderStructure()
|
||||
}
|
80
database/database.go
Normal file
|
@ -0,0 +1,80 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type MySQL struct {
|
||||
Url string
|
||||
User string
|
||||
Password string
|
||||
Database string
|
||||
ORM *gorm.DB
|
||||
}
|
||||
|
||||
func (config *MySQL) MigrateHelper(i interface{}, name string) {
|
||||
err := config.ORM.AutoMigrate(i)
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Fatalf("Failed to migrate %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
func (config *MySQL) tryDbConnection() {
|
||||
i := 1
|
||||
total := 20
|
||||
for i <= total {
|
||||
ln, err := net.DialTimeout("tcp", config.Url, 1*time.Second)
|
||||
if err != nil {
|
||||
if i == total {
|
||||
logrus.WithField("attempt", i).Fatal("Failed connecting to database")
|
||||
}
|
||||
logrus.WithField("attempt", i).Warning("Connecting to database")
|
||||
time.Sleep(2 * time.Second)
|
||||
i++
|
||||
} else {
|
||||
_ = ln.Close()
|
||||
logrus.WithField("attempt", i).Info("Connected to database")
|
||||
i = total + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (config *MySQL) initializeMySql() {
|
||||
var err error
|
||||
config.tryDbConnection()
|
||||
dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
|
||||
config.User,
|
||||
config.Password,
|
||||
config.Url,
|
||||
config.Database,
|
||||
)
|
||||
config.ORM, err = gorm.Open(mysql.Open(dsn), &gorm.Config{PrepareStmt: true})
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Fatal("Failed to open database")
|
||||
}
|
||||
}
|
||||
|
||||
func (config *MySQL) initializeSqLite(storageDir string) {
|
||||
var err error
|
||||
absPath := storageDir + "db.sqlite"
|
||||
config.ORM, err = gorm.Open(sqlite.Open(absPath), &gorm.Config{PrepareStmt: true})
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Fatal("Failed to open database")
|
||||
}
|
||||
}
|
||||
|
||||
func (config *MySQL) Initialize(storageDir string) {
|
||||
if config.Url == "" {
|
||||
config.initializeSqLite(storageDir)
|
||||
} else {
|
||||
config.initializeMySql()
|
||||
}
|
||||
logrus.WithField("dialect", config.ORM.Dialector.Name()).Debug("Database initialized")
|
||||
}
|
129
docker-compose.yml
Normal file
|
@ -0,0 +1,129 @@
|
|||
networks:
|
||||
net:
|
||||
|
||||
secrets:
|
||||
jwt:
|
||||
file: ./docker/secrets/not_secure
|
||||
session:
|
||||
file: ./docker/secrets/not_secure
|
||||
storage:
|
||||
file: ./docker/secrets/not_secure
|
||||
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:2.10
|
||||
container_name: traefik
|
||||
restart: unless-stopped
|
||||
command:
|
||||
- '--api=true'
|
||||
- '--api.dashboard=true'
|
||||
- '--api.insecure=false'
|
||||
- '--pilot.dashboard=false'
|
||||
- '--global.sendAnonymousUsage=false'
|
||||
- '--global.checkNewVersion=false'
|
||||
- '--providers.docker=true'
|
||||
- '--providers.docker.exposedByDefault=false'
|
||||
- '--entryPoints.http=true'
|
||||
- '--entryPoints.http.address=:80/tcp'
|
||||
- '--entryPoints.http.http.redirections.entryPoint.to=https'
|
||||
- '--entryPoints.http.http.redirections.entryPoint.scheme=https'
|
||||
- '--entryPoints.http.forwardedHeaders.trustedIPs=172.23.0.0/16'
|
||||
- '--entryPoints.http.proxyProtocol.trustedIPs=172.23.0.0/16'
|
||||
- '--entryPoints.http.forwardedHeaders.insecure=false'
|
||||
- '--entryPoints.http.proxyProtocol.insecure=false'
|
||||
- '--entryPoints.https=true'
|
||||
- '--entryPoints.https.address=:443/tcp'
|
||||
- '--entryPoints.https.forwardedHeaders.trustedIPs=172.23.0.0/16'
|
||||
- '--entryPoints.https.proxyProtocol.trustedIPs=172.23.0.0/16'
|
||||
- '--entryPoints.https.forwardedHeaders.insecure=false'
|
||||
- '--entryPoints.https.proxyProtocol.insecure=false'
|
||||
environment:
|
||||
- TZ=Europe/Berlin
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
ports:
|
||||
- '80:80'
|
||||
- '443:443'
|
||||
labels:
|
||||
- 'traefik.enable=true'
|
||||
- 'traefik.http.middlewares.authelia.forwardAuth.address=http://authelia:9091/api/verify?rd=https%3A%2F%2Fcafe.test/auth%2F'
|
||||
- 'traefik.http.middlewares.authelia.forwardAuth.trustForwardHeader=true'
|
||||
- 'traefik.http.middlewares.authelia.forwardAuth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Name,Remote-Email'
|
||||
- 'traefik.http.routers.api.rule=Host(`proxy.cafe.test`)'
|
||||
- 'traefik.http.routers.api.entryPoints=https'
|
||||
- 'traefik.http.routers.api.tls=true'
|
||||
- 'traefik.http.routers.api.service=api@internal'
|
||||
networks:
|
||||
- net
|
||||
|
||||
authelia:
|
||||
image: authelia/authelia:latest
|
||||
container_name: authelia
|
||||
secrets:
|
||||
- jwt
|
||||
- session
|
||||
- storage
|
||||
environment:
|
||||
- TZ=Europe/Berlin
|
||||
- AUTHELIA_JWT_SECRET_FILE=/run/secrets/jwt
|
||||
- AUTHELIA_SESSION_SECRET_FILE=/run/secrets/session
|
||||
- AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE=/run/secrets/storage
|
||||
labels:
|
||||
- 'traefik.enable=true'
|
||||
- 'traefik.http.routers.authelia.rule=Host(`cafe.test`) && PathPrefix(`/auth`)'
|
||||
- 'traefik.http.routers.authelia.entryPoints=https'
|
||||
- 'traefik.http.routers.authelia.tls=true'
|
||||
volumes:
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- ./docker/authelia:/config
|
||||
healthcheck:
|
||||
disable: true
|
||||
expose:
|
||||
- 9091
|
||||
networks:
|
||||
- net
|
||||
|
||||
cafe-frontend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile_inline: |
|
||||
FROM node:18
|
||||
container_name: cafe-frontend
|
||||
entrypoint: yarn run serve
|
||||
working_dir: /app
|
||||
labels:
|
||||
- 'traefik.enable=true'
|
||||
- 'traefik.http.routers.frontend.rule=Host(`cafe.test`)'
|
||||
- 'traefik.http.routers.frontend.entryPoints=https'
|
||||
- 'traefik.http.routers.frontend.tls=true'
|
||||
- 'traefik.http.routers.frontend.middlewares=authelia@docker'
|
||||
ports:
|
||||
- '8080:8080'
|
||||
networks:
|
||||
- net
|
||||
volumes:
|
||||
- ./frontend:/app/
|
||||
|
||||
cafe-backend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/Dockerfile.dev
|
||||
args:
|
||||
- GOLANG_VERSION=${GOLANG_VERSION}
|
||||
container_name: cafe-backend
|
||||
entrypoint: air --build.exclude_dir "node_modules,frontend,static,docs,storage,tmp,dist"
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
labels:
|
||||
- 'traefik.enable=true'
|
||||
- 'traefik.http.routers.backend.rule=Host(`cafe.test`) && PathPrefix(`/api`)'
|
||||
- 'traefik.http.routers.backend.entryPoints=https'
|
||||
- 'traefik.http.routers.backend.tls=true'
|
||||
- 'traefik.http.routers.backend.middlewares=authelia@docker'
|
||||
expose:
|
||||
- 5000
|
||||
networks:
|
||||
- net
|
||||
volumes:
|
||||
- ./:/app/
|
51
docker/Dockerfile
Normal file
|
@ -0,0 +1,51 @@
|
|||
ARG GOLANG_VERSION
|
||||
ARG NODE_VERSION
|
||||
ARG ALPINE_VERSION
|
||||
FROM golang:${GOLANG_VERSION}-alpine AS goBuilder
|
||||
RUN apk add cmake g++ gcc
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./swagger.sh .
|
||||
RUN ./swagger.sh install
|
||||
|
||||
COPY ./go.mod .
|
||||
COPY ./go.sum .
|
||||
RUN go mod download
|
||||
|
||||
COPY . .
|
||||
RUN ./swagger.sh init
|
||||
RUN go build -ldflags="-s -w"
|
||||
|
||||
FROM node:${NODE_VERSION}-alpine AS nodeBuilder
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./frontend/package.json .
|
||||
COPY ./frontend/yarn.lock .
|
||||
RUN yarn install --frozen-lockfile
|
||||
|
||||
COPY --from=goBuilder /app/docs/swagger.json ../docs/swagger.json
|
||||
COPY ./frontend/ .
|
||||
RUN yarn run types:openapi
|
||||
RUN yarn run build
|
||||
|
||||
FROM alpine:${ALPINE_VERSION} AS logo
|
||||
RUN apk add figlet
|
||||
RUN figlet Cafe > logo.txt
|
||||
|
||||
FROM alpine:${ALPINE_VERSION} AS final
|
||||
RUN apk add tzdata
|
||||
|
||||
WORKDIR /app
|
||||
COPY ./scripts/entrypoint.sh .
|
||||
|
||||
COPY --from=logo /logo.txt .
|
||||
COPY --from=nodeBuilder /app/dist/ ./templates/
|
||||
COPY --from=goBuilder /app/cafe .
|
||||
COPY config.toml .
|
||||
|
||||
ARG VERSION
|
||||
ENV VERSION=$VERSION
|
||||
ARG BUILD_TIME
|
||||
ENV BUILD_TIME=$BUILD_TIME
|
||||
|
||||
ENTRYPOINT ["/app/entrypoint.sh"]
|
15
docker/Dockerfile.dev
Normal file
|
@ -0,0 +1,15 @@
|
|||
ARG GOLANG_VERSION
|
||||
FROM golang:${GOLANG_VERSION}-alpine
|
||||
RUN apk add cmake g++ gcc
|
||||
|
||||
WORKDIR /app
|
||||
COPY ./go.mod .
|
||||
COPY ./go.sum .
|
||||
RUN go mod download
|
||||
|
||||
RUN go install github.com/cosmtrek/air@latest
|
||||
|
||||
ENV VERSION=v0.0.0-DEV
|
||||
ENV BUILD_TIME=2023-06-01T08:07:43.454Z
|
||||
|
||||
CMD ["air"]
|
50
docker/authelia/configuration.yml
Normal file
|
@ -0,0 +1,50 @@
|
|||
default_redirection_url: http://cafe.test
|
||||
|
||||
server:
|
||||
host: authelia
|
||||
path: auth
|
||||
port: 9091
|
||||
buffers:
|
||||
read: 8192
|
||||
write: 8192
|
||||
|
||||
log:
|
||||
level: error
|
||||
|
||||
theme: auto
|
||||
|
||||
authentication_backend:
|
||||
password_reset:
|
||||
disable: true
|
||||
file:
|
||||
path: /config/users_database.yml
|
||||
|
||||
access_control:
|
||||
default_policy: deny
|
||||
rules:
|
||||
- domain_regex: 'cafe.test'
|
||||
policy: one_factor
|
||||
|
||||
totp:
|
||||
disable: true
|
||||
|
||||
webauthn:
|
||||
disable: true
|
||||
|
||||
session:
|
||||
name: auth_cafe_plaetschwiesle
|
||||
domain: cafe.test
|
||||
|
||||
regulation:
|
||||
max_retries: 3
|
||||
find_time: 2m
|
||||
ban_time: 5m
|
||||
|
||||
storage:
|
||||
local:
|
||||
path: /config/db.sqlite3
|
||||
|
||||
notifier:
|
||||
disable_startup_check: false
|
||||
filesystem:
|
||||
filename: /config/notification.txt
|
14
docker/authelia/users_database.yml
Normal file
|
@ -0,0 +1,14 @@
|
|||
users:
|
||||
besitzer:
|
||||
displayname: 'Besitzer'
|
||||
password: '$argon2id$v=19$m=65536,t=3,p=4$Qno2VXJTVVNNNERjVkVXbQ$rEUoGFLekVIVXm76ahP8hcqLHjstRpM1pMLf0tUTBJM'
|
||||
email: mail@example.com
|
||||
groups:
|
||||
- account
|
||||
- serve
|
||||
bedienung:
|
||||
displayname: 'Bedienung'
|
||||
password: '$argon2id$v=19$m=65536,t=3,p=4$WjlhejJVSXc5TVNLQVprUw$i6DzQukeTsXh3VL36KtCyt+rAdbJSG5AMe3c8Xiw34Q'
|
||||
email: mail@example.com
|
||||
groups:
|
||||
- serve
|
1
docker/secrets/not_secure
Normal file
|
@ -0,0 +1 @@
|
|||
this_is_not_a_secure_password
|
2
frontend/.dockerignore
Normal file
|
@ -0,0 +1,2 @@
|
|||
src/services/openapi/
|
||||
node_modules/
|
24
frontend/.gitignore
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
/src/services/openapi/
|
29
frontend/README.md
Normal file
|
@ -0,0 +1,29 @@
|
|||
# frontend
|
||||
|
||||
## Project setup
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
npm run serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Run your unit tests
|
||||
```
|
||||
npm run test:unit
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
3
frontend/babel.config.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
presets: ["@vue/cli-plugin-babel/preset"],
|
||||
};
|
98
frontend/package.json
Normal file
|
@ -0,0 +1,98 @@
|
|||
{
|
||||
"name": "frontend",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"types:openapi": "openapi -i ../docs/swagger.json -o src/services/openapi",
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"test:unit": "vue-cli-service test:unit",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^6.1.1",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.1.1",
|
||||
"@fortawesome/vue-fontawesome": "^3.0.1",
|
||||
"@vuelidate/core": "^2.0.0",
|
||||
"@vuelidate/validators": "^2.0.0",
|
||||
"core-js": "^3.8.3",
|
||||
"moment": "^2.29.3",
|
||||
"primeflex": "^3.3.0",
|
||||
"primeicons": "^6.0.1",
|
||||
"primevue": "^3.23.0",
|
||||
"vue": "^3.2.13",
|
||||
"vue-router": "^4.0.3",
|
||||
"vuex": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.8.0",
|
||||
"@types/jest": "^29.5.2",
|
||||
"@typescript-eslint/eslint-plugin": "^5.4.0",
|
||||
"@typescript-eslint/parser": "^5.4.0",
|
||||
"@vue/cli-plugin-babel": "~5.0.0",
|
||||
"@vue/cli-plugin-eslint": "~5.0.0",
|
||||
"@vue/cli-plugin-router": "~5.0.0",
|
||||
"@vue/cli-plugin-typescript": "~5.0.0",
|
||||
"@vue/cli-plugin-unit-jest": "~5.0.0",
|
||||
"@vue/cli-plugin-vuex": "~5.0.0",
|
||||
"@vue/cli-service": "~5.0.0",
|
||||
"@vue/compiler-dom": "^3.0.1",
|
||||
"@vue/eslint-config-typescript": "^11.0.3",
|
||||
"@vue/server-renderer": "^3.0.1",
|
||||
"@vue/test-utils": "^2.0.0-0",
|
||||
"@vue/vue3-jest": "^29.2.4",
|
||||
"babel-jest": "^29.5.0",
|
||||
"eslint": "^8.44.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-vue": "^9.15.1",
|
||||
"jest": "^29.5.0",
|
||||
"less": "^4.0.0",
|
||||
"less-loader": "^11.1.3",
|
||||
"openapi-typescript": "^6.2.8",
|
||||
"openapi-typescript-codegen": "^0.24.0",
|
||||
"prettier": "^2.4.1",
|
||||
"ts-jest": "^29.1.1",
|
||||
"typescript": ">=3.3.1 <5.1.0",
|
||||
"webpack": "^5.0.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/vue3-essential",
|
||||
"eslint:recommended",
|
||||
"@vue/typescript/recommended",
|
||||
"plugin:prettier/recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2020
|
||||
},
|
||||
"rules": {},
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"**/__tests__/*.{j,t}s?(x)",
|
||||
"**/tests/unit/**/*.spec.{j,t}s?(x)"
|
||||
],
|
||||
"env": {
|
||||
"jest": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"prettier": {
|
||||
"printWidth": 160
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead",
|
||||
"not ie 11"
|
||||
],
|
||||
"jest": {
|
||||
"preset": "@vue/cli-plugin-unit-jest/presets/typescript-and-babel"
|
||||
}
|
||||
}
|
BIN
frontend/public/favicon/android-chrome-192x192.png
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
frontend/public/favicon/android-chrome-512x512.png
Normal file
After Width: | Height: | Size: 178 KiB |
BIN
frontend/public/favicon/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 32 KiB |
9
frontend/public/favicon/browserconfig.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="/mstile-150x150.png"/>
|
||||
<TileColor>#da532c</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
BIN
frontend/public/favicon/favicon-16x16.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
frontend/public/favicon/favicon-32x32.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
frontend/public/favicon/favicon.ico
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
frontend/public/favicon/mstile-150x150.png
Normal file
After Width: | Height: | Size: 19 KiB |
153
frontend/public/favicon/safari-pinned-tab.svg
Normal file
|
@ -0,0 +1,153 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="750.000000pt" height="750.000000pt" viewBox="0 0 750.000000 750.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.14, written by Peter Selinger 2001-2017
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,750.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M1137 7112 c-16 -18 -103 -205 -117 -253 -9 -30 4 -59 26 -59 8 0 33
|
||||
9 56 21 22 11 44 17 48 12 4 -4 12 -33 19 -63 10 -48 42 -115 83 -171 15 -21
|
||||
-18 -99 -196 -459 -148 -298 -181 -381 -173 -438 3 -20 21 -122 41 -225 41
|
||||
-212 56 -383 56 -630 l0 -169 -85 -176 c-48 -97 -99 -193 -114 -212 -15 -19
|
||||
-36 -51 -46 -70 -27 -53 -133 -151 -202 -187 -77 -42 -138 -44 -215 -8 -64 30
|
||||
-99 32 -123 5 -25 -28 -13 -82 27 -115 48 -41 236 -131 298 -144 63 -12 112
|
||||
-4 162 27 92 57 162 92 169 85 11 -11 37 -328 49 -603 27 -623 35 -718 66
|
||||
-813 9 -27 12 -52 8 -57 -5 -4 -63 -22 -129 -40 -129 -34 -339 -112 -367 -138
|
||||
-22 -18 -23 -45 -3 -103 8 -24 16 -59 18 -78 6 -59 22 -60 154 -7 293 115 659
|
||||
181 1147 206 199 10 344 28 400 50 14 5 28 20 32 35 7 27 33 33 78 15 39 -14
|
||||
54 -3 59 43 2 23 16 123 31 222 14 99 31 223 37 275 21 190 76 428 148 640 98
|
||||
290 105 321 91 392 -6 32 -8 63 -5 68 10 17 -23 67 -52 78 -15 6 -53 16 -85
|
||||
22 -71 14 -128 52 -209 139 -68 73 -109 130 -164 223 -45 77 -114 158 -136
|
||||
158 -20 0 -56 -43 -160 -190 -100 -144 -245 -291 -322 -330 -33 -16 -71 -30
|
||||
-83 -30 -25 0 -49 -28 -59 -70 -4 -16 7 -67 30 -140 103 -324 199 -953 181
|
||||
-1183 -7 -94 -35 -164 -68 -171 -13 -3 -70 -10 -128 -16 -58 -7 -150 -20 -205
|
||||
-30 -55 -10 -103 -16 -108 -13 -4 2 -3 13 3 23 5 10 10 28 10 39 0 11 10 36
|
||||
21 56 15 26 20 46 17 73 -3 20 -2 96 3 167 4 73 3 242 -3 385 -5 140 -11 368
|
||||
-12 505 -3 269 -5 281 -63 386 l-27 50 56 112 c94 188 230 488 306 677 62 152
|
||||
94 229 162 385 29 66 60 140 69 165 9 25 24 59 33 75 9 17 41 91 71 165 31 74
|
||||
76 180 102 235 25 55 50 109 54 120 18 46 86 179 155 305 78 143 183 279 270
|
||||
350 75 61 85 57 138 -57 61 -130 108 -288 108 -359 0 -14 13 -68 29 -122 16
|
||||
-53 31 -117 34 -142 3 -25 13 -65 22 -90 9 -25 27 -97 40 -160 27 -134 105
|
||||
-414 136 -491 12 -30 34 -90 49 -134 75 -219 250 -554 339 -648 40 -43 127
|
||||
-90 153 -82 55 15 158 59 158 68 0 5 -32 43 -70 85 -49 52 -83 100 -111 158
|
||||
-22 45 -44 84 -48 86 -14 5 -159 339 -195 450 -19 59 -40 137 -46 175 -7 37
|
||||
-24 118 -40 178 -15 61 -31 130 -35 155 -4 25 -19 99 -35 165 -16 66 -32 147
|
||||
-35 180 -3 33 -12 89 -20 125 -7 36 -16 84 -19 108 -9 61 -63 227 -96 292 -40
|
||||
77 -83 135 -171 228 -63 66 -84 82 -115 87 -22 3 -54 13 -73 21 -55 24 -120
|
||||
28 -189 10 -137 -35 -244 -137 -385 -366 -78 -127 -217 -398 -217 -423 0 -9
|
||||
-4 -18 -9 -21 -5 -3 -20 -34 -34 -68 -14 -35 -36 -88 -50 -118 -41 -92 -104
|
||||
-241 -238 -568 -142 -345 -146 -355 -158 -348 -4 3 -11 88 -14 188 -7 188 -32
|
||||
363 -60 431 -16 37 -16 39 7 75 52 79 202 357 302 556 106 212 119 253 89 287
|
||||
-9 9 -29 68 -46 130 -16 62 -42 136 -58 164 l-29 51 29 32 c16 18 29 37 29 42
|
||||
0 16 -60 49 -129 70 -78 25 -103 25 -124 2z m936 -2859 c56 -73 228 -243 292
|
||||
-288 69 -50 79 -67 71 -128 -4 -29 -34 -133 -67 -232 -54 -159 -65 -211 -104
|
||||
-445 -47 -288 -85 -594 -85 -677 0 -29 -3 -53 -6 -53 -3 0 -33 11 -67 24 -100
|
||||
38 -128 46 -171 46 -23 0 -57 3 -77 6 l-36 6 5 122 c5 123 -14 335 -43 493 -8
|
||||
43 -26 143 -40 223 -30 174 -72 334 -120 459 -40 107 -41 111 -18 111 10 0 49
|
||||
12 88 27 57 21 82 39 135 92 36 37 81 95 100 129 37 67 87 142 94 142 3 0 25
|
||||
-26 49 -57z"/>
|
||||
<path d="M1965 3159 c-9 -13 5 -149 17 -172 16 -30 51 -17 65 23 16 46 16 133
|
||||
1 148 -15 15 -74 16 -83 1z"/>
|
||||
<path d="M3685 6514 c-16 -2 -73 -9 -125 -15 -97 -11 -292 -49 -390 -75 -97
|
||||
-27 -180 -55 -180 -62 0 -32 37 -165 47 -169 7 -3 47 7 90 21 135 44 294 77
|
||||
523 107 119 15 478 12 620 -6 407 -51 792 -184 1123 -389 774 -478 1263 -1259
|
||||
1357 -2166 13 -127 13 -430 -1 -566 -51 -523 -247 -1025 -566 -1453 -119 -160
|
||||
-400 -439 -565 -562 -423 -315 -899 -498 -1426 -550 -149 -15 -510 -6 -652 16
|
||||
-645 100 -1214 398 -1657 869 -100 107 -270 331 -343 451 -56 92 -53 91 -146
|
||||
74 -79 -14 -94 -19 -94 -30 0 -5 27 -55 61 -110 431 -714 1136 -1225 1928
|
||||
-1398 241 -53 360 -65 646 -66 265 0 352 7 555 46 489 94 928 296 1320 608
|
||||
181 145 410 386 557 586 720 983 776 2345 140 3391 -137 226 -284 409 -489
|
||||
609 -386 378 -855 638 -1382 765 -237 58 -357 72 -651 75 -148 2 -283 1 -300
|
||||
-1z"/>
|
||||
<path d="M2203 5810 c-52 -11 -137 -51 -192 -90 -57 -42 -148 -183 -177 -277
|
||||
-16 -48 -25 -101 -26 -153 -1 -76 1 -82 40 -148 62 -104 123 -166 200 -204 57
|
||||
-28 77 -33 142 -33 95 0 135 19 216 101 96 99 143 207 159 364 8 77 -15 160
|
||||
-75 281 -48 95 -100 143 -169 158 -58 12 -59 12 -118 1z m-51 -192 c10 -29 -3
|
||||
-113 -19 -125 -29 -22 -103 -10 -103 17 0 11 100 120 110 120 4 0 10 -6 12
|
||||
-12z m196 -20 c5 -7 13 -41 17 -75 8 -69 3 -73 -50 -53 -21 8 -24 14 -19 37
|
||||
24 107 30 117 52 91z m-244 -276 c4 -7 -15 -102 -30 -151 -6 -18 -39 11 -60
|
||||
51 -17 34 -18 95 -1 101 20 9 85 8 91 -1z m241 -1 c8 -14 -31 -110 -56 -137
|
||||
-32 -34 -49 -31 -49 9 1 47 19 120 33 129 17 11 65 10 72 -1z"/>
|
||||
<path d="M5872 3183 c-5 -10 -15 -46 -21 -80 -7 -35 -30 -123 -52 -195 -98
|
||||
-327 -103 -473 -17 -514 25 -12 40 -13 60 -5 32 12 50 41 38 62 -8 12 -13 12
|
||||
-30 1 -32 -20 -57 -7 -63 33 -12 73 37 294 117 534 47 137 48 160 10 175 -25
|
||||
9 -33 7 -42 -11z"/>
|
||||
<path d="M2915 2988 c-85 -30 -261 -190 -340 -309 -96 -144 -128 -252 -89
|
||||
-307 20 -28 64 -30 64 -3 0 11 -4 23 -10 26 -5 3 -10 27 -10 53 1 133 225 398
|
||||
405 478 91 40 180 27 222 -32 49 -68 67 -203 38 -284 -24 -68 -97 -169 -191
|
||||
-263 -62 -61 -85 -90 -77 -98 45 -45 256 175 313 327 28 77 28 209 -1 265 -27
|
||||
54 -92 123 -133 143 -39 18 -144 20 -191 4z"/>
|
||||
<path d="M4585 2952 c-6 -4 -18 -32 -29 -62 -10 -30 -26 -73 -36 -95 -28 -62
|
||||
-120 -314 -120 -328 0 -44 -47 -136 -98 -195 -143 -164 -163 -176 -190 -120
|
||||
-23 48 25 205 77 252 20 18 21 18 30 -7 17 -45 49 -46 70 -1 35 77 -20 156
|
||||
-91 132 -52 -18 -118 -123 -152 -240 -9 -32 -20 -58 -24 -58 -4 0 -31 24 -60
|
||||
53 -52 53 -65 90 -41 114 18 18 2 43 -26 43 -35 0 -65 -33 -65 -73 0 -33 25
|
||||
-77 95 -167 51 -67 45 -103 -21 -116 -40 -7 -63 13 -69 61 -3 22 -10 44 -16
|
||||
50 -16 16 -39 -23 -39 -67 0 -75 89 -137 166 -116 30 7 86 60 96 89 3 8 17 3
|
||||
46 -16 33 -21 49 -26 75 -21 37 7 99 66 138 131 l24 40 3 -57 c2 -31 9 -61 16
|
||||
-67 22 -18 44 -2 50 37 9 52 102 234 142 279 41 47 43 33 12 -72 -20 -67 -21
|
||||
-80 -10 -122 16 -57 28 -73 55 -73 18 0 19 4 12 53 -12 83 -7 123 30 234 19
|
||||
57 35 112 35 123 0 27 -36 45 -64 32 -19 -8 -112 -123 -153 -188 -23 -36 -14
|
||||
16 13 81 14 33 41 101 61 150 19 50 46 114 59 143 13 29 24 56 24 61 0 5 7 14
|
||||
16 21 8 7 14 24 12 38 -3 27 -37 55 -53 44z"/>
|
||||
<path d="M5166 2904 c-24 -23 -18 -41 18 -61 47 -26 78 24 38 60 -23 21 -36
|
||||
21 -56 1z"/>
|
||||
<path d="M6054 2881 c-94 -57 -191 -270 -168 -368 27 -113 114 -159 210 -110
|
||||
72 37 140 123 151 192 8 47 -12 45 -42 -5 -34 -57 -99 -116 -149 -135 -47 -18
|
||||
-47 -18 -71 5 -20 19 -25 33 -25 72 0 27 7 64 15 84 13 33 16 34 53 28 57 -9
|
||||
96 15 128 79 36 69 38 132 5 158 -29 24 -67 24 -107 0z m30 -128 c-20 -40 -54
|
||||
-58 -54 -28 0 18 58 81 67 72 3 -3 -3 -23 -13 -44z"/>
|
||||
<path d="M3906 2858 c-9 -12 -16 -28 -16 -36 0 -20 -46 -106 -66 -123 -9 -8
|
||||
-51 -23 -93 -35 -68 -18 -76 -23 -79 -46 -5 -40 27 -53 72 -31 20 9 39 14 42
|
||||
10 4 -4 -16 -55 -43 -114 -28 -60 -65 -153 -82 -208 -28 -88 -39 -107 -87
|
||||
-159 -42 -46 -57 -57 -66 -48 -15 15 11 96 53 162 58 94 58 130 -2 130 -18 0
|
||||
-32 7 -39 19 -14 27 -62 36 -98 17 -64 -33 -210 -213 -253 -313 -36 -84 -21
|
||||
-151 39 -174 57 -22 99 4 204 130 l27 32 13 -27 c32 -65 80 -57 140 23 l42 57
|
||||
17 -41 c19 -45 39 -60 84 -65 45 -5 44 25 -1 51 l-37 22 6 72 c4 58 18 104 72
|
||||
241 39 97 80 182 96 201 27 30 34 33 103 37 48 3 85 11 102 23 26 17 27 18 9
|
||||
31 -11 8 -43 14 -75 14 -31 0 -59 3 -63 6 -3 4 5 23 18 43 29 41 32 83 9 105
|
||||
-21 21 -30 20 -48 -6z m-461 -558 c8 -12 -21 -62 -99 -172 -95 -135 -146 -179
|
||||
-146 -128 0 46 81 171 173 268 41 42 60 51 72 32z"/>
|
||||
<path d="M5572 2840 c-13 -6 -30 -27 -39 -47 -25 -60 -12 -129 41 -228 52 -96
|
||||
63 -165 26 -165 -26 0 -43 34 -50 100 -3 30 -9 55 -15 55 -5 0 -19 -16 -32
|
||||
-35 -56 -86 -71 -107 -115 -153 -29 -30 -54 -47 -65 -45 -29 5 -55 66 -50 117
|
||||
6 59 23 75 65 60 26 -9 37 -8 57 5 33 22 97 139 102 189 6 55 -24 97 -68 97
|
||||
-45 0 -127 -87 -172 -181 -30 -64 -41 -102 -63 -215 -5 -28 -74 -98 -86 -86
|
||||
-14 14 38 228 83 339 16 39 -1 66 -40 61 -22 -3 -26 -9 -37 -73 -15 -90 -44
|
||||
-195 -55 -195 -4 0 -10 26 -14 58 -20 183 -39 256 -70 268 -9 4 -17 5 -19 3
|
||||
-2 -2 5 -62 15 -134 20 -142 25 -299 9 -339 -20 -55 -63 -17 -74 66 -9 59 -34
|
||||
93 -63 84 -12 -4 -25 -17 -30 -29 -14 -38 -88 -167 -95 -167 -23 0 -6 56 65
|
||||
224 47 112 50 120 36 145 -10 17 -23 26 -39 26 -21 0 -25 -7 -36 -60 -7 -33
|
||||
-22 -81 -33 -107 -31 -73 -61 -181 -61 -222 0 -63 47 -108 95 -90 17 7 55 75
|
||||
74 137 7 20 16 37 21 37 5 0 15 -19 22 -42 18 -54 56 -83 110 -83 23 0 43 5
|
||||
45 11 2 6 9 19 16 28 11 16 15 16 41 2 26 -13 33 -13 57 -1 15 8 41 31 57 51
|
||||
l30 37 40 -42 c43 -45 80 -53 119 -26 23 16 110 127 120 151 9 25 19 15 34
|
||||
-33 19 -63 62 -90 114 -73 19 6 44 23 56 38 29 38 20 82 -42 202 -45 87 -50
|
||||
103 -47 151 3 50 5 54 28 54 36 0 39 37 4 64 -29 23 -38 24 -72 11z m-152
|
||||
-167 c0 -40 -69 -117 -87 -99 -9 8 10 47 42 84 30 36 45 41 45 15z"/>
|
||||
<path d="M2963 2683 c-7 -2 -18 -23 -24 -46 -6 -23 -30 -73 -54 -112 -23 -38
|
||||
-75 -130 -114 -203 -175 -321 -246 -531 -198 -579 28 -28 44 -9 49 59 6 76 72
|
||||
246 148 382 28 49 50 93 50 97 0 5 20 39 44 76 24 37 67 109 95 160 29 51 56
|
||||
95 61 98 16 10 11 34 -10 55 -21 21 -26 22 -47 13z"/>
|
||||
<path d="M3320 2661 c-5 -11 -10 -25 -10 -32 0 -6 -49 -94 -109 -194 -234
|
||||
-390 -288 -518 -247 -580 19 -28 47 -33 66 -10 10 13 10 19 -4 33 -9 10 -16
|
||||
28 -16 41 0 42 148 311 294 536 122 187 119 180 88 208 -25 22 -49 21 -62 -2z"/>
|
||||
<path d="M3396 2551 c-21 -24 -14 -57 15 -66 46 -15 65 44 23 71 -18 13 -23
|
||||
12 -38 -5z"/>
|
||||
<path d="M3499 2554 c-17 -20 -1 -48 31 -52 35 -6 46 13 26 42 -19 30 -38 33
|
||||
-57 10z"/>
|
||||
<path d="M5709 2096 c-2 -3 -42 -8 -89 -11 -97 -7 -121 -10 -585 -64 -424 -50
|
||||
-592 -72 -831 -111 -104 -17 -207 -33 -229 -36 -39 -5 -229 -38 -525 -90 -80
|
||||
-14 -185 -35 -235 -46 -49 -11 -117 -26 -150 -33 -33 -7 -87 -20 -120 -29 -32
|
||||
-9 -74 -16 -93 -16 -72 0 -94 -41 -45 -80 34 -26 66 -23 173 16 41 15 122 37
|
||||
180 50 164 36 336 72 435 89 50 9 126 23 170 31 44 8 105 19 135 24 112 17
|
||||
135 22 153 31 9 6 23 9 30 8 13 -3 71 5 197 26 47 8 121 19 165 25 44 6 100
|
||||
16 125 21 25 5 124 18 220 29 219 26 389 48 460 59 30 5 91 12 135 15 44 3
|
||||
132 12 195 20 63 8 144 17 180 21 63 6 157 21 193 31 9 3 15 10 12 15 -6 9
|
||||
-247 13 -256 5z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 10 KiB |
19
frontend/public/favicon/site.webmanifest
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "",
|
||||
"short_name": "",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
}
|
24
frontend/public/index.html
Normal file
|
@ -0,0 +1,24 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="<%= BASE_URL %>favicon/apple-touch-icon.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="<%= BASE_URL %>favicon/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="<%= BASE_URL %>favicon/favicon-16x16.png" />
|
||||
<link rel="manifest" href="<%= BASE_URL %>favicon/site.webmanifest" />
|
||||
<link rel="mask-icon" href="<%= BASE_URL %>favicon/safari-pinned-tab.svg" color="#5bbad5" />
|
||||
<meta name="msapplication-TileColor" content="#da532c" />
|
||||
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff" />
|
||||
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#1f2d40" />
|
||||
<title>Cafe Plätschwiesle</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
91
frontend/src/App.vue
Normal file
|
@ -0,0 +1,91 @@
|
|||
<template>
|
||||
<Toast style="width: 90vw" position="bottom-right" group="br" />
|
||||
<TheNavigation @logout="logout" />
|
||||
<div class="m-2">
|
||||
<router-view v-slot="{ Component }">
|
||||
<Transition mode="out-in">
|
||||
<component :is="Component" />
|
||||
</Transition>
|
||||
</router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import TheNavigation from "@/components/UI/TheNavigation.vue";
|
||||
import Toast from "primevue/toast";
|
||||
|
||||
export default defineComponent({
|
||||
name: "App",
|
||||
components: { TheNavigation, Toast },
|
||||
setup() {
|
||||
async function logout() {
|
||||
const response = await fetch("/auth/api/logout", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({}),
|
||||
});
|
||||
const result = await response.json();
|
||||
result.status === "OK" && window.location.reload();
|
||||
}
|
||||
return { logout };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import "primevue/resources/themes/saga-blue/theme.css";
|
||||
@import "primevue/resources/themes/vela-blue/theme.css" screen and (prefers-color-scheme: dark);
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
img {
|
||||
content: url(@/assets/logos/logo.png);
|
||||
}
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
img {
|
||||
content: url(@/assets/logos/logo_white.png);
|
||||
}
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "roboto";
|
||||
src: url(@/assets/fonts/roboto.ttf);
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
.p-button.p-button-success:enabled:focus,
|
||||
.p-button.p-button-danger:enabled:focus,
|
||||
.p-button.p-button-success:enabled:active,
|
||||
.p-button.p-button-danger:enabled:active {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: var(--surface-b);
|
||||
}
|
||||
html,
|
||||
body {
|
||||
font-size: 1.2em;
|
||||
font-family: "roboto", sans-serif;
|
||||
}
|
||||
.p-component {
|
||||
font-family: "roboto", sans-serif;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
.v-enter-active {
|
||||
transition: opacity 0.2s ease-in;
|
||||
}
|
||||
.v-leave-active {
|
||||
transition: opacity 0.1s ease-out;
|
||||
}
|
||||
.v-enter-from,
|
||||
.v-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
BIN
frontend/src/assets/fonts/roboto.ttf
Normal file
BIN
frontend/src/assets/logos/logo.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
frontend/src/assets/logos/logo_white.png
Normal file
After Width: | Height: | Size: 25 KiB |
107
frontend/src/components/Bills/BillModal.vue
Normal file
|
@ -0,0 +1,107 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<div class="flex flex-column align-items-center justify-content-center">
|
||||
<img alt="logo" class="mb-3" style="height: auto; width: 5rem" src="" />
|
||||
<div class="text-center text-sm">Plätschwiesen 2, 72622 Nürtingen<br />Baden-Württemberg</div>
|
||||
</div>
|
||||
<Transition>
|
||||
<WaveSpinner v-if="isLoading" />
|
||||
<div v-else>
|
||||
<div class="flex justify-content-between my-5">
|
||||
<div>{{ date }}</div>
|
||||
<div>|</div>
|
||||
<div class="mb-1">Tisch {{ bill.table_id }}</div>
|
||||
<div>|</div>
|
||||
<div>{{ time }}</div>
|
||||
</div>
|
||||
<div class="text-lg">
|
||||
<div v-for="billItem in billItems" :key="billItem.id" class="flex flex-column mb-1">
|
||||
<div class="flex align-items-center justify-content-between">
|
||||
<div class="entry white-space-nowrap overflow-hidden">{{ billItem.description }}</div>
|
||||
<div>{{ convertToEur(billItem.total) }}</div>
|
||||
</div>
|
||||
<div v-if="billItem.amount !== 1" class="ml-4 font-italic text-sm">{{ billItem.amount }} x {{ convertToEur(billItem.price) }}</div>
|
||||
</div>
|
||||
<div class="flex justify-content-end font-bold mt-5 mb-3">Total: {{ convertToEur(bill.total) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, onMounted, PropType, ref } from "vue";
|
||||
import { BillsService, service_Bill, service_BillItem } from "@/services/openapi";
|
||||
import { convertToEur } from "@/utils";
|
||||
import WaveSpinner from "@/components/UI/WaveSpinner.vue";
|
||||
import moment from "moment";
|
||||
|
||||
export default defineComponent({
|
||||
name: "BillModal",
|
||||
components: { WaveSpinner },
|
||||
props: { bill: { type: Object as PropType<service_Bill>, required: true } },
|
||||
setup(props) {
|
||||
const isLoading = ref(true);
|
||||
const billItems = ref<service_BillItem[]>();
|
||||
const date = computed(() => props.bill.created_at && moment.unix(props.bill.created_at).format("DD.MM.YYYY"));
|
||||
const time = computed(() => props.bill.created_at && moment.unix(props.bill.created_at).format("HH:mm") + " Uhr");
|
||||
onMounted(() => {
|
||||
props.bill.id &&
|
||||
BillsService.getBillsItems(props.bill.id)
|
||||
.then((res) => {
|
||||
billItems.value = res;
|
||||
})
|
||||
.finally(() => (isLoading.value = false));
|
||||
});
|
||||
|
||||
return { convertToEur, isLoading, billItems, date, time };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-enter-active {
|
||||
transition: opacity 0.2s ease-in;
|
||||
}
|
||||
.v-enter-from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
--bs-gutter-x: 0;
|
||||
--bs-gutter-y: 0;
|
||||
width: 100%;
|
||||
padding-right: 0.5rem;
|
||||
padding-left: 0.5rem;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
@media (min-width: 576px) {
|
||||
.container {
|
||||
max-width: 540px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 720px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
.container {
|
||||
max-width: 960px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
.container {
|
||||
max-width: 1140px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1400px) {
|
||||
.container {
|
||||
max-width: 1320px;
|
||||
}
|
||||
}
|
||||
.entry:first-child:after {
|
||||
content: " . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ";
|
||||
}
|
||||
</style>
|
189
frontend/src/components/OrderItem/OrderItemList.vue
Normal file
|
@ -0,0 +1,189 @@
|
|||
<template>
|
||||
<BaseCard>
|
||||
<ConfirmDialog></ConfirmDialog>
|
||||
<div class="p-card shadow-1 md:p-3">
|
||||
<DataTable :value="orderItems" dataKey="id" :filters="filters" responsiveLayout="scroll" stripedRows class="p-datatable-sm">
|
||||
<template #header>
|
||||
<div class="grid p-fluid align-items-center">
|
||||
<div class="col-9">
|
||||
<span class="p-input-icon-left">
|
||||
<i class="pi pi-search" />
|
||||
<InputText v-model="filters['global'].value" placeholder="Suchen" @keydown.esc="filters['global'].value = null" />
|
||||
<span v-if="filters['global'].value !== null" class="leftMiddle styling" @click="filters['global'].value = null">
|
||||
<i class="pi pi-times"></i>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-3 text-right">
|
||||
<Button :disabled="isDisabled" icon="pi pi-plus" class="p-button-rounded" @click="modal = true" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<Column field="description">
|
||||
<template #body="slotProps">
|
||||
<span class="white-space-nowrap">{{ slotProps.data.description }}</span>
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="price" style="text-align: right">
|
||||
<template #body="slotProps">{{ convertToEur(slotProps.data.price) }}</template>
|
||||
</Column>
|
||||
<Column style="width: 3.5rem">
|
||||
<template #body="slotProps">
|
||||
<div class="flex align-items-center justify-content-end">
|
||||
<div
|
||||
class="mr-2"
|
||||
:style="{ color: isDisabled ? 'grey' : 'green', cursor: isDisabled ? 'default' : 'pointer' }"
|
||||
@click="editOrderItem(slotProps.data)"
|
||||
>
|
||||
<i class="pi pi-pencil"></i>
|
||||
</div>
|
||||
<div :style="{ color: isDisabled ? 'grey' : 'red', cursor: isDisabled ? 'default' : 'pointer' }" @click="confirmDeleteProduct(slotProps.data)">
|
||||
<i class="pi pi-trash"></i>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<template #empty><div class="mb-1">Keine Einträge</div></template>
|
||||
</DataTable>
|
||||
</div>
|
||||
|
||||
<Dialog position="top" v-model:visible="modal" :modal="true" :showHeader="false" @hide="resetModal" style="min-width: 50vw">
|
||||
<div class="p-fluid">
|
||||
<div class="field mt-5">
|
||||
<InputText :disabled="isDisabled" id="name" v-model.trim="orderItem.description" required="true" autofocus @keydown.enter="saveOrderItem" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<InputNumber
|
||||
:disabled="isDisabled"
|
||||
id="currency-germany"
|
||||
v-model="orderItem.price"
|
||||
mode="currency"
|
||||
currency="EUR"
|
||||
locale="de-DE"
|
||||
@keydown.enter="saveOrderItem"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-content-end">
|
||||
<Button :disabled="isDisabled" icon="pi pi-times" class="p-button-text p-button-rounded p-button-secondary mr-2" @click="resetModal" />
|
||||
<Button :loading="isDisabled" icon="pi pi-check" class="p-button-rounded p-button-success" @click="saveOrderItem" />
|
||||
</div>
|
||||
</Dialog>
|
||||
</BaseCard>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType, ref, watch } from "vue";
|
||||
import BaseCard from "@/components/UI/BaseCard.vue";
|
||||
import { OrderItemsService, service_OrderItem } from "@/services/openapi";
|
||||
import InputText from "primevue/inputtext";
|
||||
import { FilterMatchMode } from "primevue/api";
|
||||
import DataTable from "primevue/datatable";
|
||||
import Column from "primevue/column";
|
||||
import Button from "primevue/button";
|
||||
import { convertToEur, errorToast } from "@/utils";
|
||||
import Dialog from "primevue/dialog";
|
||||
import InputNumber from "primevue/inputnumber";
|
||||
import { useConfirm } from "primevue/useconfirm";
|
||||
import ConfirmDialog from "primevue/confirmdialog";
|
||||
import { useToast } from "primevue/usetoast";
|
||||
|
||||
export default defineComponent({
|
||||
name: "OrderItemList",
|
||||
// eslint-disable-next-line
|
||||
components: { BaseCard, InputText, DataTable, Column, Button, Dialog, InputNumber, ConfirmDialog },
|
||||
props: {
|
||||
orderItems: { type: Array as PropType<service_OrderItem[]>, default: () => [] },
|
||||
emptyOrderItem: { type: Object as PropType<service_OrderItem>, default: () => ({}) },
|
||||
title: { type: String, default: "" },
|
||||
},
|
||||
emits: ["orderItemChanged", "orderItemDeleted", "orderItemCreated"],
|
||||
setup(props, { emit }) {
|
||||
const isDisabled = ref(false);
|
||||
const toast = useToast();
|
||||
const confirm = useConfirm();
|
||||
const modal = ref(false);
|
||||
const filters = ref({
|
||||
global: { value: null, matchMode: FilterMatchMode.CONTAINS },
|
||||
});
|
||||
const orderItem = ref<service_OrderItem>({ ...props.emptyOrderItem });
|
||||
function editOrderItem(item: service_OrderItem) {
|
||||
orderItem.value = { ...item };
|
||||
modal.value = true;
|
||||
}
|
||||
watch(props.emptyOrderItem, () => resetModal());
|
||||
|
||||
function saveOrderItem() {
|
||||
if (isDisabled.value) return;
|
||||
isDisabled.value = true;
|
||||
if (orderItem.value.id) {
|
||||
OrderItemsService.putOrdersItems(orderItem.value)
|
||||
.then((res) => emit("orderItemChanged", res))
|
||||
.catch((err) => errorToast(toast, err.body.error))
|
||||
.finally(() => resetModal());
|
||||
} else {
|
||||
OrderItemsService.postOrdersItems(orderItem.value)
|
||||
.then((res) => emit("orderItemCreated", res))
|
||||
.finally(() => resetModal());
|
||||
}
|
||||
}
|
||||
|
||||
function confirmDeleteProduct(item: service_OrderItem) {
|
||||
if (isDisabled.value) return;
|
||||
confirm.require({
|
||||
message: item.description + " löschen?",
|
||||
header: "Achtung",
|
||||
position: "top",
|
||||
accept: () => {
|
||||
isDisabled.value = true;
|
||||
item.id &&
|
||||
OrderItemsService.deleteOrdersItems(item.id)
|
||||
.then(() => emit("orderItemDeleted", item))
|
||||
.finally(() => resetModal());
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function resetModal() {
|
||||
modal.value = false;
|
||||
orderItem.value = { ...props.emptyOrderItem };
|
||||
isDisabled.value = false;
|
||||
}
|
||||
|
||||
return { filters, convertToEur, editOrderItem, saveOrderItem, confirmDeleteProduct, modal, orderItem, resetModal, isDisabled };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.p-datatable .p-datatable-header,
|
||||
.p-datatable .p-datatable-footer {
|
||||
background: transparent !important;
|
||||
}
|
||||
.p-datatable .p-datatable-header,
|
||||
.p-datatable .p-datatable-footer,
|
||||
.p-datatable .p-datatable-tbody > tr,
|
||||
.p-datatable .p-datatable-tbody > tr > td {
|
||||
border-width: 0 !important;
|
||||
}
|
||||
.p-datatable-thead {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
.styling {
|
||||
cursor: pointer;
|
||||
color: gray;
|
||||
border-radius: 50%;
|
||||
padding: 0.2rem;
|
||||
}
|
||||
.leftMiddle {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
</style>
|
53
frontend/src/components/Orders/OrderCard.vue
Normal file
|
@ -0,0 +1,53 @@
|
|||
<template>
|
||||
<SmallCard bgColor="d" :badgeTwo="badgeTwo">
|
||||
<template #description>{{ order.order_item.description }}</template>
|
||||
<template #badgeOne>{{ since }}</template>
|
||||
<template #badgeTwo>Tisch {{ order.table_id }}</template>
|
||||
<template #right>
|
||||
<div class="flex align-items-center">
|
||||
<Button v-if="!newOrder" :disabled="isDisabled" icon="pi pi-check" class="p-button-rounded p-button-success" @click="$emit('orderDone', order)" />
|
||||
<TheBadge v-else color="danger">NEU</TheBadge>
|
||||
</div>
|
||||
</template>
|
||||
</SmallCard>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, onMounted, onUnmounted, PropType, ref } from "vue";
|
||||
import { service_Order, types_ItemType } from "@/services/openapi";
|
||||
import { convertToEur, getCurrentTimeSince, lessThan15SecondsAgo } from "@/utils";
|
||||
import Button from "primevue/button";
|
||||
import moment from "moment";
|
||||
import TheBadge from "@/components/UI/TheBadge.vue";
|
||||
import SmallCard from "@/components/UI/SmallCard.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "OrderCard",
|
||||
// eslint-disable-next-line
|
||||
components: { SmallCard, TheBadge, Button },
|
||||
props: {
|
||||
order: { type: Object as PropType<service_Order>, required: true },
|
||||
isDisabled: { type: Boolean, default: false },
|
||||
itemType: { type: Number as PropType<types_ItemType>, required: false },
|
||||
},
|
||||
emits: ["orderDone"],
|
||||
setup(props) {
|
||||
moment.locale("de");
|
||||
// eslint-disable-next-line
|
||||
let ticker: any;
|
||||
const since = ref(getCurrentTimeSince(props.order.updated_at));
|
||||
const newOrder = ref(lessThan15SecondsAgo(props.order.updated_at));
|
||||
const badgeTwo = computed(() => props.itemType === types_ItemType.ColdDrink);
|
||||
|
||||
onMounted(() => {
|
||||
ticker = setInterval(() => {
|
||||
since.value = getCurrentTimeSince(props.order.updated_at);
|
||||
newOrder.value === true && (newOrder.value = lessThan15SecondsAgo(props.order.updated_at));
|
||||
}, 1000);
|
||||
});
|
||||
onUnmounted(() => ticker && clearInterval(ticker));
|
||||
|
||||
return { convertToEur, since, newOrder, badgeTwo };
|
||||
},
|
||||
});
|
||||
</script>
|
60
frontend/src/components/Orders/OrderSection.vue
Normal file
|
@ -0,0 +1,60 @@
|
|||
<template>
|
||||
<div v-if="orders.length !== 0">
|
||||
<BaseToolbar :icon="detailedItemTypeIcon(itemType)" :title="title || detailedItemTypeString(itemType)" btnIcon="check" @click="checkAllOpenOrders" />
|
||||
<div class="grid">
|
||||
<OrderCard
|
||||
v-for="order in orders"
|
||||
v-bind:key="order.id"
|
||||
:order="order"
|
||||
:isDisabled="isDisabled"
|
||||
:bigRight="true"
|
||||
@orderDone="(o) => orderDone(o)"
|
||||
:itemType="itemType"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, inject, PropType, ref } from "vue";
|
||||
import { OrdersService, service_Order, types_ItemType } from "@/services/openapi";
|
||||
import { detailedItemTypeIcon, detailedItemTypeString, errorToast, lessThan15SecondsAgo } from "@/utils";
|
||||
import OrderCard from "@/components/Orders/OrderCard.vue";
|
||||
import BaseToolbar from "@/components/UI/BaseToolbar.vue";
|
||||
import { useToast } from "primevue/usetoast";
|
||||
import { disabled } from "@/keys";
|
||||
|
||||
export default defineComponent({
|
||||
name: "OrderSection",
|
||||
components: { OrderCard, BaseToolbar },
|
||||
props: {
|
||||
orders: { type: Object as PropType<service_Order[]>, required: true },
|
||||
icon: { type: String, required: false },
|
||||
title: { type: String, required: false },
|
||||
itemType: { type: Number as PropType<types_ItemType>, required: false },
|
||||
},
|
||||
emits: ["filterOrders"],
|
||||
setup(props, { emit }) {
|
||||
const toast = useToast();
|
||||
const isDisabled = inject(disabled, ref(false));
|
||||
const collapseOrders = ref(true);
|
||||
const collapseIcon = computed(() => (collapseOrders.value ? "chevron-down" : "chevron-up"));
|
||||
|
||||
function checkAllOpenOrders() {
|
||||
props.orders.forEach((order) => {
|
||||
if (!lessThan15SecondsAgo(order.updated_at)) orderDone(order);
|
||||
});
|
||||
}
|
||||
|
||||
function orderDone(order: service_Order) {
|
||||
isDisabled.value = true;
|
||||
order.is_served = true;
|
||||
OrdersService.putOrders(order)
|
||||
.then(() => emit("filterOrders", order.id))
|
||||
.catch((err) => errorToast(toast, err.body.error))
|
||||
.finally(() => (isDisabled.value = false));
|
||||
}
|
||||
return { detailedItemTypeIcon, detailedItemTypeString, checkAllOpenOrders, orderDone, isDisabled, collapseIcon, collapseOrders };
|
||||
},
|
||||
});
|
||||
</script>
|
25
frontend/src/components/Tables/OrderAmountChange.vue
Normal file
|
@ -0,0 +1,25 @@
|
|||
<template>
|
||||
<div class="flex align-items-center">
|
||||
<div @click="!isDisabled && $emit('decrementOrder')" :style="{ color: isDisabled ? 'grey' : 'red' }" style="cursor: pointer">
|
||||
<i class="pi pi-minus"></i>
|
||||
</div>
|
||||
<div class="mx-2 font-bold">{{ order.order_count }}</div>
|
||||
<div @click="!isDisabled && $emit('incrementOrder')" :style="{ color: isDisabled ? 'grey' : 'green' }" style="cursor: pointer">
|
||||
<i class="pi pi-plus"></i>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from "vue";
|
||||
import { service_Order } from "@/services/openapi";
|
||||
|
||||
export default defineComponent({
|
||||
name: "OrderAmountChange",
|
||||
props: {
|
||||
order: { type: Object as PropType<service_Order>, required: true },
|
||||
isDisabled: { type: Boolean, default: false },
|
||||
},
|
||||
emits: ["incrementOrder", "decrementOrder"],
|
||||
});
|
||||
</script>
|
48
frontend/src/components/Tables/OverviewPerType.vue
Normal file
|
@ -0,0 +1,48 @@
|
|||
<template>
|
||||
<div>
|
||||
<BaseToolbar :title="generalItemTypeString(type)" :icon="generalItemTypeIcon(type)" @click="$emit('openModal', type)" btnIcon="plus" />
|
||||
<div class="grid">
|
||||
<TableOrderCard v-for="order in OrdersForType" v-bind:key="order.id" :order="order">
|
||||
<div class="flex align-items-end">
|
||||
<OrderAmountChange :order="order" :isDisabled="isLoading" @incrementOrder="incrementOrder(order)" @decrementOrder="decrementOrder(order)" />
|
||||
</div>
|
||||
</TableOrderCard>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, inject, PropType, ref } from "vue";
|
||||
import { OrdersService, service_Order } from "@/services/openapi";
|
||||
import { convertToEur, generalItemTypeString, generalItemTypeIcon } from "@/utils";
|
||||
import BaseToolbar from "@/components/UI/BaseToolbar.vue";
|
||||
import TableOrderCard from "@/components/Tables/TableOrderCard.vue";
|
||||
import OrderAmountChange from "@/components/Tables/OrderAmountChange.vue";
|
||||
import { loading } from "@/keys";
|
||||
|
||||
export default defineComponent({
|
||||
name: "OverviewPerType",
|
||||
components: { TableOrderCard, BaseToolbar, OrderAmountChange },
|
||||
props: {
|
||||
orders: { type: Array as PropType<service_Order[]>, default: () => [] },
|
||||
type: { type: Array as PropType<number[]>, required: true },
|
||||
},
|
||||
emits: ["openModal", "getData"],
|
||||
setup(props, { emit }) {
|
||||
const OrdersForType = computed(() => props.orders.filter((order) => props.type.includes(order.order_item.item_type)));
|
||||
const isLoading = inject(loading, ref(false));
|
||||
|
||||
function incrementOrder(order: service_Order) {
|
||||
isLoading.value = true;
|
||||
OrdersService.postOrders(order.order_item_id, order.table_id).finally(() => emit("getData"));
|
||||
}
|
||||
|
||||
function decrementOrder(order: service_Order) {
|
||||
isLoading.value = true;
|
||||
OrdersService.deleteOrders(order.order_item_id, order.table_id).finally(() => emit("getData"));
|
||||
}
|
||||
|
||||
return { OrdersForType, isLoading, convertToEur, incrementOrder, decrementOrder, generalItemTypeIcon, generalItemTypeString };
|
||||
},
|
||||
});
|
||||
</script>
|
48
frontend/src/components/Tables/TableCard.vue
Normal file
|
@ -0,0 +1,48 @@
|
|||
<template>
|
||||
<SmallCard bgColor="a" :to="'/tables/' + table.id">
|
||||
<template #description>Tisch {{ table.id }}</template>
|
||||
<template #badge>{{ since }}</template>
|
||||
<template #right>
|
||||
<div class="flex align-items-end">
|
||||
<TheBadge v-if="table.order_count" class="topRight">{{ table.order_count }}</TheBadge>
|
||||
<div v-if="table.total" class="font-bold">{{ convertToEur(table.total) }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</SmallCard>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted, onUnmounted, PropType, ref } from "vue";
|
||||
import { service_Table } from "@/services/openapi";
|
||||
import moment from "moment";
|
||||
import { convertToEur, getCurrentTimeSince } from "@/utils";
|
||||
import TheBadge from "@/components/UI/TheBadge.vue";
|
||||
import SmallCard from "@/components/UI/SmallCard.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "TableCard",
|
||||
components: { TheBadge, SmallCard },
|
||||
props: { table: { type: Object as PropType<service_Table>, required: true } },
|
||||
setup(props) {
|
||||
moment.locale("de");
|
||||
// eslint-disable-next-line
|
||||
let ticker: any;
|
||||
const since = ref(getCurrentTimeSince(props.table.updated_at));
|
||||
onMounted(() => {
|
||||
ticker = setInterval(() => {
|
||||
since.value = getCurrentTimeSince(props.table.updated_at);
|
||||
}, 1000);
|
||||
});
|
||||
onUnmounted(() => ticker && clearInterval(ticker));
|
||||
return { since, convertToEur };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.topRight {
|
||||
position: absolute;
|
||||
top: -0.2rem;
|
||||
right: 0.3rem;
|
||||
}
|
||||
</style>
|
30
frontend/src/components/Tables/TableOrderCard.vue
Normal file
|
@ -0,0 +1,30 @@
|
|||
<template>
|
||||
<SmallCard bgColor="d" :badgeTwo="order.total !== order.order_item.price">
|
||||
<template #description>{{ order.order_item.description }}</template>
|
||||
<template #badgeOne>{{ convertToEur(order.order_item.price) }}</template>
|
||||
<template #badgeTwo>{{ convertToEur(order.total) }}</template>
|
||||
<template #right>
|
||||
<slot></slot>
|
||||
</template>
|
||||
</SmallCard>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, PropType } from "vue";
|
||||
import { service_Order, types_ItemType } from "@/services/openapi";
|
||||
import { convertToEur } from "@/utils";
|
||||
import SmallCard from "@/components/UI/SmallCard.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "TableOrderCard",
|
||||
components: { SmallCard },
|
||||
props: {
|
||||
order: { type: Object as PropType<service_Order>, required: true },
|
||||
},
|
||||
emits: ["decrementOrder", "incrementOrder"],
|
||||
setup(props) {
|
||||
const showTotal = computed(() => props.order.order_item.price !== props.order.total);
|
||||
return { convertToEur, types_ItemType, showTotal };
|
||||
},
|
||||
});
|
||||
</script>
|
172
frontend/src/components/Tables/TableOverview.vue
Normal file
|
@ -0,0 +1,172 @@
|
|||
<template>
|
||||
<BaseCard>
|
||||
<Transition>
|
||||
<WaveSpinner v-if="initialLoading" />
|
||||
<div v-else>
|
||||
<OverviewPerType :type="[types_ItemType.Food]" :orders="orders" @getData="getData" @openModal="(t) => addBeverage(t)" />
|
||||
<OverviewPerType :type="[types_ItemType.ColdDrink, types_ItemType.HotDrink]" :orders="orders" @getData="getData" @openModal="(t) => addBeverage(t)" />
|
||||
<div class="h-4rem"></div>
|
||||
|
||||
<BottomNavigation>
|
||||
<template #left>
|
||||
<router-link :to="{ name: 'Tables' }" class="no-underline">
|
||||
<Button :disabled="isLoading" icon="pi pi-arrow-left" class="p-button-rounded" />
|
||||
</router-link>
|
||||
</template>
|
||||
<template #middle>
|
||||
<div class="flex flex-column align-items-center">
|
||||
<div class="text-sm">Tisch {{ table }}</div>
|
||||
<div class="font-bold">{{ convertToEur(total) }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #right>
|
||||
<router-link
|
||||
:style="{ cursor: isLoading || orders.length === 0 ? 'default' : 'pointer' }"
|
||||
:to="{ name: isLoading || orders.length === 0 ? 'TableDetail' : 'Checkout' }"
|
||||
class="no-underline"
|
||||
>
|
||||
<Button :disabled="isLoading || orders.length === 0" icon="pi pi-money-bill" class="p-button-danger p-button-rounded" />
|
||||
</router-link>
|
||||
</template>
|
||||
</BottomNavigation>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<Sidebar v-model:visible="newOrderModal" :baseZIndex="10000" position="full">
|
||||
<div class="p-fluid">
|
||||
<Listbox
|
||||
v-model="selected"
|
||||
:options="options"
|
||||
:filter="true"
|
||||
optionLabel="description"
|
||||
dataKey="id"
|
||||
optionValue="id"
|
||||
listStyle="max-height:65vh"
|
||||
filterPlaceholder="Suchen"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex justify-content-end mt-4">
|
||||
<Button :loading="isLoading" label="Speichern" icon="pi pi-check" class="p-button p-button-success mr-3" @click="postOrder" />
|
||||
</div>
|
||||
</Sidebar>
|
||||
</BaseCard>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, provide, ref } from "vue";
|
||||
import BaseCard from "@/components/UI/BaseCard.vue";
|
||||
import { useStore } from "vuex";
|
||||
import { OrdersService, service_Order, service_OrderItem, types_ItemType } from "@/services/openapi";
|
||||
import BottomNavigation from "@/components/UI/BottomNavigation.vue";
|
||||
import Button from "primevue/button";
|
||||
import { convertToEur } from "@/utils";
|
||||
import WaveSpinner from "@/components/UI/WaveSpinner.vue";
|
||||
import Sidebar from "primevue/sidebar";
|
||||
import Listbox from "primevue/listbox";
|
||||
import OverviewPerType from "@/components/Tables/OverviewPerType.vue";
|
||||
import { loading } from "@/keys";
|
||||
|
||||
export default defineComponent({
|
||||
name: "TableOverview",
|
||||
// eslint-disable-next-line
|
||||
components: { OverviewPerType, WaveSpinner, BottomNavigation, BaseCard, Button, Sidebar, Listbox },
|
||||
props: { id: { type: String, default: "0" } },
|
||||
setup(props) {
|
||||
const initialLoading = ref(false);
|
||||
const isLoading = ref(false);
|
||||
provide(loading, isLoading);
|
||||
const newOrderModal = ref(false);
|
||||
const store = useStore();
|
||||
const selectedOrder = ref();
|
||||
const table = computed(() => parseInt(props.id));
|
||||
const total = ref(0);
|
||||
const orderItems = computed(() => store.getters.getOrderItems);
|
||||
const options = ref();
|
||||
const orders = ref<service_Order[]>([]);
|
||||
|
||||
store.dispatch("getAllOrderItems");
|
||||
|
||||
getData(true);
|
||||
function getData(initial = false) {
|
||||
initial && (initialLoading.value = true);
|
||||
OrdersService.getOrders(table.value, true)
|
||||
.then((res) => (orders.value = res))
|
||||
.finally(() => {
|
||||
updateTotal();
|
||||
resetValues();
|
||||
});
|
||||
}
|
||||
|
||||
function resetValues() {
|
||||
newOrderModal.value = false;
|
||||
selectedOrder.value = undefined;
|
||||
isLoading.value = false;
|
||||
initialLoading.value = false;
|
||||
}
|
||||
|
||||
function updateTotal() {
|
||||
let temp = 0;
|
||||
orders.value.forEach((order) => (temp += order.total));
|
||||
total.value = temp;
|
||||
}
|
||||
|
||||
function addBeverage(itemType: types_ItemType[]) {
|
||||
newOrderModal.value = true;
|
||||
options.value = [];
|
||||
itemType.forEach((type) => {
|
||||
options.value = options.value.concat(orderItems.value.get(type));
|
||||
});
|
||||
options.value.sort((a: service_OrderItem, b: service_OrderItem) => {
|
||||
const x = a.description.toLowerCase();
|
||||
const y = b.description.toLowerCase();
|
||||
if (x < y) return -1;
|
||||
if (x > y) return 1;
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
function postOrder() {
|
||||
isLoading.value = true;
|
||||
if (selectedOrder.value) {
|
||||
OrdersService.postOrders(selectedOrder.value, table.value).finally(() => getData());
|
||||
} else isLoading.value = false;
|
||||
}
|
||||
|
||||
return {
|
||||
initialLoading,
|
||||
isLoading,
|
||||
newOrderModal,
|
||||
selected: selectedOrder,
|
||||
options,
|
||||
table,
|
||||
total,
|
||||
convertToEur,
|
||||
addBeverage,
|
||||
types_ItemType,
|
||||
postOrder,
|
||||
orders,
|
||||
getData,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-enter-active {
|
||||
transition: opacity 0.2s ease-in;
|
||||
}
|
||||
.v-enter-from {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.p-sidebar-content {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.p-listbox {
|
||||
border: 0 !important;
|
||||
}
|
||||
</style>
|
50
frontend/src/components/UI/BaseCard.vue
Normal file
|
@ -0,0 +1,50 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "BaseCard",
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
--bs-gutter-x: 0;
|
||||
--bs-gutter-y: 0;
|
||||
width: 100%;
|
||||
padding-right: calc(var(--bs-gutter-x) * 0.5);
|
||||
padding-left: calc(var(--bs-gutter-x) * 0.5);
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
@media (min-width: 576px) {
|
||||
.container {
|
||||
max-width: 540px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 720px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
.container {
|
||||
max-width: 960px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
.container {
|
||||
max-width: 1140px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1400px) {
|
||||
.container {
|
||||
max-width: 1320px;
|
||||
}
|
||||
}
|
||||
</style>
|
17
frontend/src/components/UI/BaseItem.vue
Normal file
|
@ -0,0 +1,17 @@
|
|||
<template>
|
||||
<div class="p-card p-2 shadow-1" :style="`color: var(--text-color); background-color: var(--surface-${bgColor})`" :class="`pr-${paddingRight}`">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "BaseItem",
|
||||
props: {
|
||||
paddingRight: { type: String, default: "2" },
|
||||
bgColor: { type: String, default: "a" },
|
||||
},
|
||||
});
|
||||
</script>
|
43
frontend/src/components/UI/BaseToolbar.vue
Normal file
|
@ -0,0 +1,43 @@
|
|||
<template>
|
||||
<Toolbar class="border-0 shadow-1 my-2 p-2 bg-color">
|
||||
<template #start>
|
||||
<div class="flex align-items-center">
|
||||
<div class="font-bold text-2xl">{{ title }}</div>
|
||||
<div v-if="icon" class="ml-3"><font-awesome-icon :icon="icon" style="font-size: 1.5rem"></font-awesome-icon></div>
|
||||
</div>
|
||||
</template>
|
||||
<template #end>
|
||||
<Button v-if="btnIcon" :disabled="isLoading" :icon="'pi pi-' + btnIcon" class="p-button-success p-button-rounded" @click="$emit('click')" />
|
||||
</template>
|
||||
</Toolbar>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, inject, ref } from "vue";
|
||||
import Toolbar from "primevue/toolbar";
|
||||
import Button from "primevue/button";
|
||||
import { loading } from "@/keys";
|
||||
|
||||
export default defineComponent({
|
||||
name: "BaseToolbar",
|
||||
// eslint-disable-next-line
|
||||
components: { Toolbar, Button },
|
||||
emits: ["click"],
|
||||
props: {
|
||||
title: { type: String, default: "" },
|
||||
icon: { type: String, default: "" },
|
||||
btnIcon: { type: String, default: "" },
|
||||
},
|
||||
setup() {
|
||||
const isLoading = inject(loading, ref(false));
|
||||
return { isLoading };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.bg-color {
|
||||
background-color: var(--surface-a);
|
||||
color: var(--text-color);
|
||||
}
|
||||
</style>
|
33
frontend/src/components/UI/BottomNavigation.vue
Normal file
|
@ -0,0 +1,33 @@
|
|||
<template>
|
||||
<div class="fixed-bottom">
|
||||
<div class="flex justify-content-between align-items-center border-round-xs py-2 px-3 bg-color shadow-1">
|
||||
<div><slot name="left"></slot></div>
|
||||
<div><slot name="middle"></slot></div>
|
||||
<div><slot name="right"></slot></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "BottomNavigation",
|
||||
setup() {
|
||||
return {};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.fixed-bottom {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.bg-color {
|
||||
background-color: var(--surface-a);
|
||||
color: var(--text-color);
|
||||
}
|
||||
</style>
|
60
frontend/src/components/UI/SmallCard.vue
Normal file
|
@ -0,0 +1,60 @@
|
|||
<template>
|
||||
<div v-if="!to && !bigRight" class="col-12 lg:col-6">
|
||||
<BaseItem class="relative" :bgColor="bgColor">
|
||||
<div class="flex flex-column justify-content-between">
|
||||
<div class="white-space-nowrap font-bold overflow-hidden text-overflow-ellipsis"><slot name="description"></slot></div>
|
||||
<div class="flex justify-content-between">
|
||||
<div class="flex align-items-center mt-1">
|
||||
<TheBadge size="sm" color="info"><slot name="badgeOne"></slot></TheBadge>
|
||||
<TheBadge v-if="badgeTwo" size="sm" color="warning" class="ml-2"><slot name="badgeTwo"></slot></TheBadge>
|
||||
</div>
|
||||
<slot name="right"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</BaseItem>
|
||||
</div>
|
||||
<div v-else-if="bigRight" class="col-12 lg:col-6">
|
||||
<BaseItem class="relative" :bgColor="bgColor">
|
||||
<div class="flex justify-content-between">
|
||||
<div class="flex flex-column overflow-hidden">
|
||||
<div class="white-space-nowrap font-bold overflow-hidden text-overflow-ellipsis"><slot name="description"></slot></div>
|
||||
<div class="flex align-items-center mt-1">
|
||||
<TheBadge size="sm" color="info"><slot name="badgeOne"></slot></TheBadge>
|
||||
<TheBadge v-if="badgeTwo" size="sm" color="warning" class="ml-2"><slot name="badgeTwo"></slot></TheBadge>
|
||||
</div>
|
||||
</div>
|
||||
<slot name="right"></slot>
|
||||
</div>
|
||||
</BaseItem>
|
||||
</div>
|
||||
<router-link v-else class="col-12 lg:col-6 no-underline" :to="to">
|
||||
<BaseItem class="relative" :bgColor="bgColor">
|
||||
<div class="flex justify-content-between overflow-hidden">
|
||||
<div class="flex flex-column align-items-start">
|
||||
<div class="white-space-nowrap overflow-hidden text-overflow-ellipsis font-bold"><slot name="description"></slot></div>
|
||||
<div class="flex align-items-center mt-1">
|
||||
<TheBadge size="sm" color="success"><slot name="badge"></slot></TheBadge>
|
||||
</div>
|
||||
</div>
|
||||
<slot name="right"></slot>
|
||||
</div>
|
||||
</BaseItem>
|
||||
</router-link>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import TheBadge from "@/components/UI/TheBadge.vue";
|
||||
import BaseItem from "@/components/UI/BaseItem.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "SmallCard",
|
||||
components: { TheBadge, BaseItem },
|
||||
props: {
|
||||
bgColor: { type: String, default: "a" },
|
||||
to: { type: String, default: "" },
|
||||
badgeTwo: { type: Boolean, default: true },
|
||||
bigRight: { type: Boolean, default: false },
|
||||
},
|
||||
});
|
||||
</script>
|
36
frontend/src/components/UI/TheBadge.vue
Normal file
|
@ -0,0 +1,36 @@
|
|||
<template>
|
||||
<div class="badge p-badge" :class="`p-badge-${color}`">
|
||||
<span :class="`text-${size}`"><slot></slot></span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "TheBadge",
|
||||
props: { color: { type: String, default: "primary" }, size: { type: String, default: "xs" } },
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.badge {
|
||||
--bs-badge-padding-x: 0.5em;
|
||||
--bs-badge-padding-y: 0.3em;
|
||||
--bs-badge-font-size: 0.75em;
|
||||
--bs-badge-font-weight: 700;
|
||||
--bs-badge-border-radius: 0.375rem;
|
||||
display: inline-block;
|
||||
padding: var(--bs-badge-padding-y) var(--bs-badge-padding-x);
|
||||
font-size: var(--bs-badge-font-size);
|
||||
font-weight: var(--bs-badge-font-weight);
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: baseline;
|
||||
border-radius: var(--bs-badge-border-radius, 0);
|
||||
}
|
||||
.badge:empty {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
109
frontend/src/components/UI/TheNavigation.vue
Normal file
|
@ -0,0 +1,109 @@
|
|||
<template>
|
||||
<UserSettings />
|
||||
<Menubar :model="items" class="py-1 px-3 mb-3 shadow-1 border-0 bg-color">
|
||||
<template #start>
|
||||
<router-link class="no-underline" to="/tables"><img alt="logo" class="mr-2" width="50" height="50" src="@/assets/logos/logo.png" /></router-link>
|
||||
</template>
|
||||
<template #end>
|
||||
<div v-if="tablePath">
|
||||
<Button v-if="tablesCount !== 0" :disabled="isLoading" icon="pi pi-minus" class="p-button-danger p-button-rounded mr-2" @click="removeTable" />
|
||||
<Button :disabled="isLoading" icon="pi pi-plus" class="p-button-success p-button-rounded" @click="addTable" />
|
||||
</div>
|
||||
<router-link v-else :to="{ name: 'Tables' }" class="no-underline">
|
||||
<Button label="Tische" class="p-button-secondary" icon="pi pi-table" />
|
||||
</router-link>
|
||||
</template>
|
||||
</Menubar>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, provide, ref } from "vue";
|
||||
import Menubar from "primevue/menubar";
|
||||
import { useStore } from "vuex";
|
||||
import Button from "primevue/button";
|
||||
import { useRoute } from "vue-router";
|
||||
import { TablesService, types_ItemType } from "@/services/openapi";
|
||||
import { detailedItemTypeString, errorToast } from "@/utils";
|
||||
import { useToast } from "primevue/usetoast";
|
||||
import { visible } from "@/keys";
|
||||
import UserSettings from "@/components/User/UserSettings.vue";
|
||||
import { MenuItem } from "primevue/menuitem";
|
||||
|
||||
export default defineComponent({
|
||||
name: "TheNavigation",
|
||||
// eslint-disable-next-line
|
||||
components: { UserSettings, Menubar, Button },
|
||||
emits: ["logout"],
|
||||
setup(_, { emit }) {
|
||||
const toast = useToast();
|
||||
const store = useStore();
|
||||
const route = useRoute();
|
||||
const isLoading = ref(false);
|
||||
const tablesCount = computed(() => store.getters.getTablesCount);
|
||||
const tablePath = computed(() => route.path === "/tables");
|
||||
const user = computed<string>(() => store.getters.getUsername);
|
||||
|
||||
const settingsVisible = ref(false);
|
||||
provide(visible, settingsVisible);
|
||||
|
||||
function removeTable() {
|
||||
isLoading.value = true;
|
||||
TablesService.deleteTables()
|
||||
.then(() => {
|
||||
store.dispatch("removeLastTable");
|
||||
})
|
||||
.catch((err) => {
|
||||
errorToast(toast, err.body.error);
|
||||
})
|
||||
.finally(() => {
|
||||
isLoading.value = false;
|
||||
});
|
||||
}
|
||||
function addTable() {
|
||||
isLoading.value = true;
|
||||
TablesService.postTables()
|
||||
.then((res) => {
|
||||
store.dispatch("addTable", res);
|
||||
})
|
||||
.catch((err) => {
|
||||
errorToast(toast, err.body.error);
|
||||
})
|
||||
.finally(() => {
|
||||
isLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
const items = ref<MenuItem[]>([
|
||||
{ label: "Bestellungen", icon: "pi pi-fw pi-history", to: "/orders" },
|
||||
{
|
||||
label: "Artikel",
|
||||
icon: "pi pi-fw pi-shopping-cart",
|
||||
items: [
|
||||
{ label: detailedItemTypeString(types_ItemType.Food), icon: "pi pi-fw pi-shopping-cart", to: "/items/" + types_ItemType.Food },
|
||||
{ label: detailedItemTypeString(types_ItemType.ColdDrink), icon: "pi pi-fw pi-shopping-cart", to: "/items/" + types_ItemType.ColdDrink },
|
||||
{ label: detailedItemTypeString(types_ItemType.HotDrink), icon: "pi pi-fw pi-shopping-cart", to: "/items/" + types_ItemType.HotDrink },
|
||||
],
|
||||
},
|
||||
{ label: "Rechnungen", icon: "pi pi-fw pi-euro", to: "/bills", visible: () => store.getters.getGroups.includes("account") },
|
||||
{ separator: true },
|
||||
{
|
||||
label: user.value,
|
||||
icon: "pi pi-fw pi-user",
|
||||
items: [
|
||||
{ label: "Einstellungen", icon: "pi pi-fw pi-cog", command: () => (settingsVisible.value = true) },
|
||||
{ label: "Abmelden", icon: "pi pi-fw pi-power-off", command: () => emit("logout") },
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
return { items, tablePath, removeTable, addTable, isLoading, tablesCount };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.bg-color {
|
||||
background-color: var(--surface-a);
|
||||
color: var(--text-color);
|
||||
}
|
||||
</style>
|
87
frontend/src/components/UI/WaveSpinner.vue
Normal file
|
@ -0,0 +1,87 @@
|
|||
<template>
|
||||
<div v-if="isShowing" class="center">
|
||||
<div class="lds-ellipsis">
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "WaveSpinner",
|
||||
setup() {
|
||||
const isShowing = ref(false);
|
||||
setTimeout(() => (isShowing.value = true), 200);
|
||||
return { isShowing };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.center {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.lds-ellipsis {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
.lds-ellipsis div {
|
||||
position: absolute;
|
||||
top: 33px;
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
border-radius: 50%;
|
||||
background: var(--primary-color);
|
||||
animation-timing-function: cubic-bezier(0, 1, 1, 0);
|
||||
}
|
||||
.lds-ellipsis div:nth-child(1) {
|
||||
left: 8px;
|
||||
animation: lds-ellipsis1 0.6s infinite;
|
||||
}
|
||||
.lds-ellipsis div:nth-child(2) {
|
||||
left: 8px;
|
||||
animation: lds-ellipsis2 0.6s infinite;
|
||||
}
|
||||
.lds-ellipsis div:nth-child(3) {
|
||||
left: 32px;
|
||||
animation: lds-ellipsis2 0.6s infinite;
|
||||
}
|
||||
.lds-ellipsis div:nth-child(4) {
|
||||
left: 56px;
|
||||
animation: lds-ellipsis3 0.6s infinite;
|
||||
}
|
||||
@keyframes lds-ellipsis1 {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
@keyframes lds-ellipsis3 {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
100% {
|
||||
transform: scale(0);
|
||||
}
|
||||
}
|
||||
@keyframes lds-ellipsis2 {
|
||||
0% {
|
||||
transform: translate(0, 0);
|
||||
}
|
||||
100% {
|
||||
transform: translate(24px, 0);
|
||||
}
|
||||
}
|
||||
</style>
|
48
frontend/src/components/User/UserSettings.vue
Normal file
|
@ -0,0 +1,48 @@
|
|||
<template>
|
||||
<Sidebar v-model:visible="isVisible" :baseZIndex="10000" class="pl-3">
|
||||
<div class="font-bold mb-3">Bestellungen anzeigen:</div>
|
||||
<div class="flex align-items-center mb-3">
|
||||
<InputSwitch :disabled="isLoading" id="show_cold_drinks" v-model="user.show_cold_drinks" @change="updateUser()" />
|
||||
<label for="show_cold_drinks" class="ml-3">Kaltgetränke</label>
|
||||
</div>
|
||||
<div class="flex align-items-center">
|
||||
<InputSwitch :disabled="isLoading" id="show_hot_drinks" v-model="user.show_hot_drinks" @change="updateUser()" />
|
||||
<label for="show_hot_drinks" class="ml-3">Heiß/Eiskaffee & Speisen</label>
|
||||
</div>
|
||||
</Sidebar>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, inject, ref } from "vue";
|
||||
import Sidebar from "primevue/sidebar";
|
||||
import { visible } from "@/keys";
|
||||
import { user_User, UsersService } from "@/services/openapi";
|
||||
import InputSwitch from "primevue/inputswitch";
|
||||
import { useStore } from "vuex";
|
||||
import { errorToast } from "@/utils";
|
||||
import { useToast } from "primevue/usetoast";
|
||||
|
||||
export default defineComponent({
|
||||
name: "UserSettings",
|
||||
components: { Sidebar, InputSwitch },
|
||||
setup() {
|
||||
const store = useStore();
|
||||
const toast = useToast();
|
||||
const isLoading = ref(false);
|
||||
const isVisible = inject(visible, ref(false));
|
||||
const user = computed<user_User>(() => store.getters.getUser);
|
||||
|
||||
function updateUser() {
|
||||
isLoading.value = true;
|
||||
UsersService.putUsers(user.value)
|
||||
.then((res) => store.commit("setUser", res))
|
||||
.catch((err) => errorToast(toast, err.body.error))
|
||||
.finally(() => (isLoading.value = false));
|
||||
}
|
||||
|
||||
return { isVisible, user, updateUser, isLoading };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
5
frontend/src/keys.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { InjectionKey, Ref } from "vue";
|
||||
|
||||
export const loading = Symbol() as InjectionKey<Ref<boolean>>;
|
||||
export const disabled = Symbol() as InjectionKey<Ref<boolean>>;
|
||||
export const visible = Symbol() as InjectionKey<Ref<boolean>>;
|
138
frontend/src/main.ts
Normal file
|
@ -0,0 +1,138 @@
|
|||
import { createApp } from "vue";
|
||||
import { OpenAPI, UsersService } from "@/services/openapi";
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
import store from "./store";
|
||||
import PrimeVue from "primevue/config";
|
||||
import ConfirmationService from "primevue/confirmationservice";
|
||||
import ToastService from "primevue/toastservice";
|
||||
import { library } from "@fortawesome/fontawesome-svg-core";
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
import { faMugHot } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faChampagneGlasses } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faCheese } from "@fortawesome/free-solid-svg-icons";
|
||||
library.add(faMugHot, faChampagneGlasses, faCheese);
|
||||
|
||||
import "primevue/resources/primevue.min.css";
|
||||
import "primeicons/primeicons.css";
|
||||
import "primeflex/primeflex.css";
|
||||
|
||||
export const API_ENDPOINT_URL = window.origin + "/api";
|
||||
export const WEBSOCKET_ENDPOINT_URL = API_ENDPOINT_URL.replace("http", "ws") + "/orders/ws";
|
||||
OpenAPI.BASE = API_ENDPOINT_URL;
|
||||
|
||||
async function getHealth() {
|
||||
const response = await fetch(API_ENDPOINT_URL + "/health");
|
||||
const groups = response.headers.get("remote-groups")?.split(",");
|
||||
store.commit("setGroups", groups);
|
||||
const user = await UsersService.getUsers(response.headers.get("remote-name") || "Benutzer");
|
||||
store.commit("setUser", user);
|
||||
}
|
||||
|
||||
getHealth().then(() => {
|
||||
const app = createApp(App);
|
||||
app.use(store);
|
||||
app.use(router);
|
||||
app.use(PrimeVue, {
|
||||
locale: {
|
||||
startsWith: "Beginnt mit",
|
||||
contains: "enthält",
|
||||
notContains: "enthält nicht",
|
||||
endsWith: "endet mit",
|
||||
equals: "entspricht",
|
||||
notEquals: "entspricht nicht",
|
||||
noFilter: "Kein Filter",
|
||||
lt: "Weniger als",
|
||||
lte: "Weniger als oder gleich viel",
|
||||
gt: "Mehr als",
|
||||
gte: "Mehr als oder gleich viel",
|
||||
dateIs: "Datum ist",
|
||||
dateIsNot: "Datum ist nicht",
|
||||
dateBefore: "Datum liegt vor",
|
||||
dateAfter: "Datum liegt nach",
|
||||
clear: "Löschen",
|
||||
apply: "Anwenden",
|
||||
matchAll: "Alle abgleichen",
|
||||
matchAny: "Mit jedem abgleichen",
|
||||
addRule: "Regel hinzufügen",
|
||||
removeRule: "Regel entfernen",
|
||||
accept: "Ja",
|
||||
reject: "Nein",
|
||||
choose: "Auswählen",
|
||||
upload: "Hochladen",
|
||||
cancel: "Abbrechen",
|
||||
dayNames: ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"],
|
||||
dayNamesShort: ["Son", "Mon", "Die", "Mit", "Don", "Fre", "Sam"],
|
||||
dayNamesMin: ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"],
|
||||
monthNames: ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"],
|
||||
monthNamesShort: ["Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"],
|
||||
today: "Heute",
|
||||
weekHeader: "Wo",
|
||||
firstDayOfWeek: 1,
|
||||
dateFormat: "dd.mm.yy",
|
||||
weak: "Schwach",
|
||||
medium: "Medium",
|
||||
strong: "Stark",
|
||||
passwordPrompt: "Passwort eingeben",
|
||||
emptySearchMessage: "Keine Ergebnisse gefunden",
|
||||
emptyMessage: "Keine verfügbaren Optionen",
|
||||
aria: {
|
||||
trueLabel: "True",
|
||||
falseLabel: "False",
|
||||
nullLabel: "Nicht ausgewählt",
|
||||
star: "1 Stern",
|
||||
stars: "{star} Sterne",
|
||||
selectAll: "Alle Artikel ausgewählt",
|
||||
unselectAll: "Alle Artikel nicht ausgewählt",
|
||||
close: "Schließen",
|
||||
previous: "Vorherig",
|
||||
next: "Nächste",
|
||||
navigation: "Navigation",
|
||||
scrollTop: "Nach Oben scrollen",
|
||||
moveTop: "Nach oben bewegen",
|
||||
moveUp: "Aufsteigen",
|
||||
moveDown: "Absteigen",
|
||||
moveBottom: "Nach unten bewegen",
|
||||
moveToTarget: "Move to Target",
|
||||
moveToSource: "Move to Source",
|
||||
moveAllToTarget: "Move All to Target",
|
||||
moveAllToSource: "Move All to Source",
|
||||
pageLabel: "{page}",
|
||||
firstPageLabel: "Erste Seite",
|
||||
lastPageLabel: "Letzte Seite",
|
||||
nextPageLabel: "Nächste Seite",
|
||||
prevPageLabel: "Vorherige Seite",
|
||||
rowsPerPageLabel: "Reihen pro Seite",
|
||||
jumpToPageDropdownLabel: "Jump to Page Dropdown",
|
||||
jumpToPageInputLabel: "Jump to Page Input",
|
||||
selectRow: "Reihe ausgewählt",
|
||||
unselectRow: "Reihe abgewählt",
|
||||
expandRow: "Row Expanded",
|
||||
collapseRow: "Row Collapsed",
|
||||
showFilterMenu: "Show Filter Menu",
|
||||
hideFilterMenu: "Hide Filter Menu",
|
||||
filterOperator: "Filter Operator",
|
||||
filterConstraint: "Filter Constraint",
|
||||
editRow: "Row Edit",
|
||||
saveEdit: "Save Edit",
|
||||
cancelEdit: "Cancel Edit",
|
||||
listView: "List View",
|
||||
gridView: "Grid View",
|
||||
slide: "Slide",
|
||||
slideNumber: "{slideNumber}",
|
||||
zoomImage: "Zoom Image",
|
||||
zoomIn: "Zoom In",
|
||||
zoomOut: "Zoom Out",
|
||||
rotateRight: "Rotate Right",
|
||||
rotateLeft: "Rotate Left",
|
||||
},
|
||||
},
|
||||
});
|
||||
app.use(ConfirmationService);
|
||||
app.use(ToastService);
|
||||
app.component("font-awesome-icon", FontAwesomeIcon);
|
||||
|
||||
router.isReady().then(() => {
|
||||
app.mount("#app");
|
||||
});
|
||||
});
|
33
frontend/src/router/index.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
|
||||
import TableView from "@/views/Tables.vue";
|
||||
import ItemView from "@/views/Items.vue";
|
||||
import OrderView from "@/views/Orders.vue";
|
||||
import BillView from "@/views/Bills.vue";
|
||||
import CheckoutView from "@/views/Checkout.vue";
|
||||
import TableDetail from "@/components/Tables/TableOverview.vue";
|
||||
import { useStore } from "vuex";
|
||||
|
||||
const routes: Array<RouteRecordRaw> = [
|
||||
{ path: "/tables", name: "Tables", component: TableView, meta: { needsAuth: true } },
|
||||
{ path: "/tables/:id", name: "TableDetail", props: true, component: TableDetail, meta: { needsAuth: true } },
|
||||
{ path: "/tables/:id/checkout", name: "Checkout", props: true, component: CheckoutView, meta: { needsAuth: true } },
|
||||
{ path: "/orders", name: "Orders", component: OrderView, meta: { needsAuth: true } },
|
||||
{ path: "/items/:id", name: "Items", props: true, component: ItemView, meta: { needsAuth: true } },
|
||||
{ path: "/bills", name: "Bills", component: BillView, meta: { needsAuth: true } },
|
||||
{ path: "/:pathMatch(.*)*", redirect: { name: "Tables" } },
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
routes,
|
||||
history: createWebHistory(process.env.BASE_URL),
|
||||
});
|
||||
|
||||
router.beforeEach(async (to) => {
|
||||
const store = useStore();
|
||||
if (to.name === "Bills") {
|
||||
if (!store.getters.getGroups.includes("account")) return "/tables";
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
export default router;
|
6
frontend/src/shims-vue.d.ts
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
/* eslint-disable */
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue'
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
43
frontend/src/store/index.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
import { createStore } from "vuex";
|
||||
import tableStore from "@/store/tables";
|
||||
import orderItemStore from "@/store/orderItems";
|
||||
import { user_User } from "@/services/openapi";
|
||||
|
||||
interface AppStateModel {
|
||||
user: user_User;
|
||||
groups: string[];
|
||||
}
|
||||
export default createStore({
|
||||
state: {
|
||||
user: {
|
||||
username: "",
|
||||
show_cold_drinks: true,
|
||||
show_hot_drinks: true,
|
||||
},
|
||||
groups: [""],
|
||||
},
|
||||
getters: {
|
||||
getUser(state: AppStateModel) {
|
||||
return state.user;
|
||||
},
|
||||
getGroups(state: AppStateModel) {
|
||||
return state.groups;
|
||||
},
|
||||
getUsername(state: AppStateModel) {
|
||||
return state.user.username;
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
setUser(state: AppStateModel, _user: user_User) {
|
||||
state.user = _user;
|
||||
},
|
||||
setGroups(state: AppStateModel, groups: string[]) {
|
||||
state.groups = groups;
|
||||
},
|
||||
},
|
||||
actions: {},
|
||||
modules: {
|
||||
tableStore,
|
||||
orderItemStore,
|
||||
},
|
||||
});
|
76
frontend/src/store/orderItems/index.ts
Normal file
|
@ -0,0 +1,76 @@
|
|||
import { OrderItemsService, service_OrderItem, types_ItemType } from "@/services/openapi";
|
||||
|
||||
interface AppStateModel {
|
||||
orderItems: Map<number, service_OrderItem[]>;
|
||||
}
|
||||
|
||||
interface mutationOrderItems {
|
||||
orderItems: service_OrderItem[];
|
||||
orderType: types_ItemType;
|
||||
}
|
||||
|
||||
const orderItemStore = {
|
||||
state: () => ({
|
||||
orderItems: new Map<number, service_OrderItem[]>(),
|
||||
}),
|
||||
getters: {
|
||||
getOrderItems(state: AppStateModel) {
|
||||
return state.orderItems;
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
setOrderItems(state: AppStateModel, payload: mutationOrderItems) {
|
||||
state.orderItems.set(payload.orderType, payload.orderItems);
|
||||
},
|
||||
pushOrderItem(state: AppStateModel, orderItem: service_OrderItem) {
|
||||
const tempOrderItems = state.orderItems.get(orderItem.item_type);
|
||||
tempOrderItems && tempOrderItems.push(orderItem);
|
||||
},
|
||||
filterOrderItem(state: AppStateModel, orderItem: service_OrderItem) {
|
||||
const tempOrderItems = state.orderItems.get(orderItem.item_type);
|
||||
tempOrderItems &&
|
||||
state.orderItems.set(
|
||||
orderItem.item_type,
|
||||
tempOrderItems.filter((origItem: service_OrderItem) => origItem.id !== orderItem.id)
|
||||
);
|
||||
},
|
||||
putOrderItem(state: AppStateModel, orderItem: service_OrderItem) {
|
||||
const tempOrderItems = state.orderItems.get(orderItem.item_type);
|
||||
tempOrderItems &&
|
||||
state.orderItems.set(
|
||||
orderItem.item_type,
|
||||
tempOrderItems.map((origItem: service_OrderItem) => (origItem.id === orderItem.id ? orderItem : origItem))
|
||||
);
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
// eslint-disable-next-line
|
||||
async getAllOrderItems(context: any) {
|
||||
await context.dispatch("getOrderItems", types_ItemType.Food);
|
||||
await context.dispatch("getOrderItems", types_ItemType.ColdDrink);
|
||||
await context.dispatch("getOrderItems", types_ItemType.HotDrink);
|
||||
},
|
||||
// eslint-disable-next-line
|
||||
async getOrderItems(context: any, orderType: types_ItemType) {
|
||||
const orderTypeArray = context.getters.getOrderItems;
|
||||
if (!orderTypeArray.get(orderType)) {
|
||||
const orderItems: service_OrderItem[] | null = await OrderItemsService.getOrdersItems(orderType);
|
||||
context.commit("setOrderItems", { orderItems, orderType });
|
||||
}
|
||||
},
|
||||
// eslint-disable-next-line
|
||||
addOrderItem(context: any, orderItem: service_OrderItem) {
|
||||
context.commit("pushOrderItem", orderItem);
|
||||
},
|
||||
// eslint-disable-next-line
|
||||
deleteOrderItem(context: any, orderItem: service_OrderItem) {
|
||||
context.commit("filterOrderItem", orderItem);
|
||||
},
|
||||
// eslint-disable-next-line
|
||||
updateOrderItem(context: any, orderItem: service_OrderItem) {
|
||||
context.commit("putOrderItem", orderItem);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default orderItemStore;
|
47
frontend/src/store/tables/index.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
import { service_Table, TablesService } from "@/services/openapi";
|
||||
|
||||
interface AppStateModel {
|
||||
tables: service_Table[] | null;
|
||||
}
|
||||
|
||||
const tableStore = {
|
||||
state: () => ({
|
||||
tables: null,
|
||||
}),
|
||||
getters: {
|
||||
getTables(state: AppStateModel) {
|
||||
return state.tables;
|
||||
},
|
||||
getTablesCount(state: AppStateModel) {
|
||||
return state.tables && state.tables.length;
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
setTables(state: AppStateModel, tables: service_Table[]) {
|
||||
state.tables = tables;
|
||||
},
|
||||
popTables(state: AppStateModel) {
|
||||
state.tables && state.tables.pop();
|
||||
},
|
||||
pushTable(state: AppStateModel, table: service_Table) {
|
||||
state.tables && state.tables.push(table);
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
// eslint-disable-next-line
|
||||
fetchTables(context: any) {
|
||||
context.commit("setTables", null);
|
||||
return TablesService.getTables();
|
||||
},
|
||||
// eslint-disable-next-line
|
||||
removeLastTable(context: any) {
|
||||
context.commit("popTables");
|
||||
},
|
||||
// eslint-disable-next-line
|
||||
addTable(context: any, table: service_Table) {
|
||||
context.commit("pushTable", table);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default tableStore;
|
78
frontend/src/utils.ts
Normal file
|
@ -0,0 +1,78 @@
|
|||
import { service_Bill, service_Order, types_ItemType } from "@/services/openapi";
|
||||
|
||||
export function convertToEur(value: number | undefined) {
|
||||
const temp: number = value ? value : 0;
|
||||
return temp.toLocaleString("de-DE", { style: "currency", currency: "EUR" });
|
||||
}
|
||||
|
||||
export function detailedItemTypeString(type: types_ItemType | undefined) {
|
||||
switch (type) {
|
||||
case types_ItemType.Food:
|
||||
return "Speisen";
|
||||
case types_ItemType.ColdDrink:
|
||||
return "Kaltgetränke";
|
||||
default:
|
||||
return "Heiß/Eiskaffee";
|
||||
}
|
||||
}
|
||||
|
||||
export function generalItemTypeString(type: types_ItemType[]) {
|
||||
if (type.includes(types_ItemType.Food)) {
|
||||
return "Speisen";
|
||||
} else {
|
||||
return "Getränke";
|
||||
}
|
||||
}
|
||||
|
||||
export function detailedItemTypeIcon(type: types_ItemType | undefined) {
|
||||
switch (type) {
|
||||
case types_ItemType.Food:
|
||||
return "fa-cheese";
|
||||
case types_ItemType.ColdDrink:
|
||||
return "fa-champagne-glasses";
|
||||
case types_ItemType.HotDrink:
|
||||
return "fa-mug-hot";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
export function generalItemTypeIcon(type: types_ItemType[]) {
|
||||
if (type.includes(types_ItemType.Food)) {
|
||||
return "fa-cheese";
|
||||
} else {
|
||||
return "fa-champagne-glasses";
|
||||
}
|
||||
}
|
||||
|
||||
export interface WebSocketMsg {
|
||||
type: NotifierType;
|
||||
payload: service_Order[];
|
||||
}
|
||||
|
||||
export enum NotifierType {
|
||||
Create,
|
||||
Delete,
|
||||
DeleteAll,
|
||||
}
|
||||
|
||||
import { ToastServiceMethods } from "primevue/toastservice";
|
||||
import moment from "moment";
|
||||
|
||||
const timeToLife = 3600;
|
||||
|
||||
export function errorToast(toast: ToastServiceMethods, message: string) {
|
||||
toast.removeAllGroups();
|
||||
toast.add({ severity: "error", summary: "Fehler", detail: message, group: "br", life: timeToLife });
|
||||
}
|
||||
|
||||
export function getCurrentTimeSince(updated_at: number | undefined) {
|
||||
return updated_at ? moment.unix(updated_at).fromNow() : "";
|
||||
}
|
||||
|
||||
export function lessThan15SecondsAgo(updated_at: number | undefined) {
|
||||
const updated = updated_at ? moment.unix(updated_at) : moment();
|
||||
return moment().diff(updated, "seconds") < 15;
|
||||
}
|
||||
|
||||
export const emptyBill: service_Bill = { table_id: 0, total: 0 };
|
158
frontend/src/views/Bills.vue
Normal file
|
@ -0,0 +1,158 @@
|
|||
<template>
|
||||
<BaseCard>
|
||||
<ConfirmDialog></ConfirmDialog>
|
||||
<Transition>
|
||||
<WaveSpinner v-if="isLoading" />
|
||||
<div v-else class="p-card shadow-1 md:p-3">
|
||||
<DataTable :value="bills" dataKey="id" :filters="filters" responsiveLayout="scroll" stripedRows class="p-datatable-sm">
|
||||
<template #header>
|
||||
<div class="grid p-fluid align-items-center">
|
||||
<div class="col-12 md:col-4">
|
||||
<Calendar id="basic" v-model="today" autocomplete="off" :inputStyle="{ 'text-align': 'center' }" :manualInput="false" />
|
||||
</div>
|
||||
<div class="col-12 md:col-8">
|
||||
<span class="p-input-icon-left">
|
||||
<i class="pi pi-search" />
|
||||
<InputText v-model="filters['global'].value" placeholder="Suchen" @keydown.esc="filters['global'].value = null" />
|
||||
<span v-if="filters['global'].value !== null" class="leftMiddle styling" @click="filters['global'].value = null">
|
||||
<i class="pi pi-times"></i>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<Column field="table_id">
|
||||
<template #body="slotProps">
|
||||
<span class="white-space-nowrap">
|
||||
Tisch {{ slotProps.data.table_id }} <span class="text-sm">({{ time(slotProps.data.created_at) }})</span>
|
||||
</span>
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="total" style="text-align: right">
|
||||
<template #body="slotProps">{{ convertToEur(slotProps.data.total) }}</template>
|
||||
</Column>
|
||||
<Column style="width: 3.5rem">
|
||||
<template #body="slotProps">
|
||||
<div class="flex align-items-center justify-content-end">
|
||||
<div class="mr-2" :style="{ color: isDisabled ? 'grey' : 'green' }" style="cursor: pointer" @click="openBill(slotProps.data.id)">
|
||||
<i class="pi pi-eye"></i>
|
||||
</div>
|
||||
<div :style="{ color: isDisabled ? 'grey' : 'red' }" style="cursor: pointer" @click="deleteBill(slotProps.data.id)">
|
||||
<i class="pi pi-trash"></i>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<template #empty><div class="mb-1">Keine Rechnungen</div></template>
|
||||
</DataTable>
|
||||
</div>
|
||||
</Transition>
|
||||
<Sidebar v-model:visible="billModal" :baseZIndex="10000" position="full">
|
||||
<BillModal :bill="bill" />
|
||||
</Sidebar>
|
||||
</BaseCard>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watch } from "vue";
|
||||
import BaseCard from "@/components/UI/BaseCard.vue";
|
||||
import Calendar from "primevue/calendar";
|
||||
import { BillsService, service_Bill } from "@/services/openapi";
|
||||
import Sidebar from "primevue/sidebar";
|
||||
import BillModal from "@/components/Bills/BillModal.vue";
|
||||
import { convertToEur, emptyBill, errorToast } from "@/utils";
|
||||
import { FilterMatchMode } from "primevue/api";
|
||||
import DataTable from "primevue/datatable";
|
||||
import Column from "primevue/column";
|
||||
import InputText from "primevue/inputtext";
|
||||
import moment from "moment";
|
||||
import { useConfirm } from "primevue/useconfirm";
|
||||
import { useToast } from "primevue/usetoast";
|
||||
import ConfirmDialog from "primevue/confirmdialog";
|
||||
import WaveSpinner from "@/components/UI/WaveSpinner.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "BillView",
|
||||
components: { BillModal, BaseCard, Calendar, Sidebar, DataTable, Column, InputText, ConfirmDialog, WaveSpinner },
|
||||
setup() {
|
||||
const confirm = useConfirm();
|
||||
const toast = useToast();
|
||||
const today = ref(new Date());
|
||||
const bills = ref<service_Bill[]>([]);
|
||||
const isLoading = ref(false);
|
||||
const isDisabled = ref(false);
|
||||
const billModal = ref(false);
|
||||
const bill = ref<service_Bill>({ ...emptyBill });
|
||||
const filters = ref({
|
||||
global: { value: null, matchMode: FilterMatchMode.CONTAINS },
|
||||
});
|
||||
|
||||
getData();
|
||||
watch(today, () => getData());
|
||||
|
||||
function getData() {
|
||||
isLoading.value = true;
|
||||
BillsService.getBills(today.value.getFullYear(), today.value.getUTCMonth() + 1, today.value.getDate())
|
||||
.then((res) => (bills.value = res))
|
||||
.catch((err) => errorToast(toast, err.body.error))
|
||||
.finally(() => {
|
||||
isLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function openBill(billId: number) {
|
||||
if (isDisabled.value) return;
|
||||
const temp: service_Bill | undefined = bills.value.find((bill) => bill.id === billId);
|
||||
temp && (bill.value = temp);
|
||||
billModal.value = true;
|
||||
}
|
||||
|
||||
function time(unixDate: number) {
|
||||
return moment.unix(unixDate).format("HH:mm") + " Uhr";
|
||||
}
|
||||
|
||||
function deleteBill(billId: number) {
|
||||
if (isDisabled.value) return;
|
||||
confirm.require({
|
||||
message: "Rechnung löschen?",
|
||||
header: "Rechnung",
|
||||
icon: "pi pi-info-circle",
|
||||
acceptClass: "p-button-danger",
|
||||
accept: () => {
|
||||
isDisabled.value = true;
|
||||
BillsService.deleteBills(billId)
|
||||
.then(() => (bills.value = bills.value.filter((bill) => bill.id !== billId)))
|
||||
.catch((err) => errorToast(toast, err.body.error))
|
||||
.finally(() => (isDisabled.value = false));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return { convertToEur, openBill, deleteBill, today, bills, isLoading, isDisabled, filters, billModal, bill, time };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-enter-active {
|
||||
transition: opacity 0.2s ease-in;
|
||||
}
|
||||
.v-enter-from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.styling {
|
||||
cursor: pointer;
|
||||
color: gray;
|
||||
border-radius: 50%;
|
||||
padding: 0.2rem;
|
||||
}
|
||||
.leftMiddle {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
</style>
|
195
frontend/src/views/Checkout.vue
Normal file
|
@ -0,0 +1,195 @@
|
|||
<template>
|
||||
<BaseCard>
|
||||
<ConfirmDialog></ConfirmDialog>
|
||||
<Transition>
|
||||
<WaveSpinner v-if="isLoading" />
|
||||
<div v-else>
|
||||
<BaseItem>
|
||||
<div class="field-checkbox mt-1">
|
||||
<Checkbox id="binary" v-model="checkAll" :binary="true" @click="checkAllClicked" />
|
||||
<label for="binary">Alle Auswählen</label>
|
||||
</div>
|
||||
<hr style="color: var(--text-color)" class="my-3" />
|
||||
<div v-for="order in orders" :key="order.id" class="field-checkbox">
|
||||
<Checkbox :id="order.id" name="order" :value="order.id" v-model="orderFilter" />
|
||||
<label :for="'' + order.id" class="w-full">
|
||||
<span class="flex justify-content-between">
|
||||
<span class="overflow-hidden text-overflow-ellipsis white-space-nowrap">{{ order.order_item.description }}</span>
|
||||
<span>{{ convertToEur(order.order_item.price) }}</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</BaseItem>
|
||||
<div class="h-4rem"></div>
|
||||
|
||||
<BottomNavigation>
|
||||
<template #left>
|
||||
<router-link :to="{ name: 'TableDetail' }" class="no-underline">
|
||||
<Button :disabled="applyFilterLoading" icon="pi pi-arrow-left" class="p-button-rounded" />
|
||||
</router-link>
|
||||
</template>
|
||||
<template #middle>
|
||||
<div class="flex flex-column align-items-center">
|
||||
<div class="text-sm">Tisch {{ table }}</div>
|
||||
<div class="font-bold">{{ convertToEur(total) }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #right>
|
||||
<Button
|
||||
:disabled="total === 0"
|
||||
:loading="applyFilterLoading"
|
||||
icon="pi pi-money-bill"
|
||||
class="p-button-danger p-button-rounded"
|
||||
@click="generateBill"
|
||||
/>
|
||||
</template>
|
||||
</BottomNavigation>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<Sidebar v-model:visible="billModal" :baseZIndex="10000" position="full" @hide="billModalClosed">
|
||||
<BillModal :bill="bill" />
|
||||
</Sidebar>
|
||||
</BaseCard>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, ref, watch } from "vue";
|
||||
import { BillsService, OrdersService, service_Bill, service_Order } from "@/services/openapi";
|
||||
import Checkbox from "primevue/checkbox";
|
||||
import { convertToEur, emptyBill, errorToast } from "@/utils";
|
||||
import Button from "primevue/button";
|
||||
import WaveSpinner from "@/components/UI/WaveSpinner.vue";
|
||||
import BaseCard from "@/components/UI/BaseCard.vue";
|
||||
import BottomNavigation from "@/components/UI/BottomNavigation.vue";
|
||||
import BaseItem from "@/components/UI/BaseItem.vue";
|
||||
import ConfirmDialog from "primevue/confirmdialog";
|
||||
import { useConfirm } from "primevue/useconfirm";
|
||||
import { useToast } from "primevue/usetoast";
|
||||
import Sidebar from "primevue/sidebar";
|
||||
import BillModal from "@/components/Bills/BillModal.vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
export default defineComponent({
|
||||
name: "CheckoutView",
|
||||
// eslint-disable-next-line
|
||||
components: { BaseItem, BaseCard, WaveSpinner, Checkbox, Button, BottomNavigation, ConfirmDialog, Sidebar, BillModal },
|
||||
props: { id: { type: String, default: "0" } },
|
||||
setup(props) {
|
||||
const confirm = useConfirm();
|
||||
const toast = useToast();
|
||||
const router = useRouter();
|
||||
const table = computed(() => parseInt(props.id));
|
||||
const orders = ref<service_Order[]>([]);
|
||||
const orderFilter = ref<number[]>([]);
|
||||
const isLoading = ref(false);
|
||||
const applyFilterLoading = ref(false);
|
||||
const checkAll = ref(false);
|
||||
const total = ref(0);
|
||||
const bill = ref<service_Bill>({ ...emptyBill });
|
||||
const billModal = ref(false);
|
||||
|
||||
function checkAllCheck() {
|
||||
if (orderFilter.value) checkAll.value = orderFilter.value.length === orders.value.length;
|
||||
}
|
||||
|
||||
function calculateTotal() {
|
||||
let temp = 0;
|
||||
orders.value.forEach((order) => {
|
||||
if (order.id && orderFilter.value.includes(order.id)) temp += order.order_item.price;
|
||||
});
|
||||
total.value = temp;
|
||||
}
|
||||
|
||||
watch(orderFilter, () => {
|
||||
checkAllCheck();
|
||||
calculateTotal();
|
||||
});
|
||||
|
||||
getData();
|
||||
function getData() {
|
||||
isLoading.value = true;
|
||||
OrdersService.getOrders(table.value, false)
|
||||
.then((res) => {
|
||||
orders.value = res;
|
||||
setAllOrdersSelected();
|
||||
checkAllCheck();
|
||||
})
|
||||
.finally(() => (isLoading.value = false));
|
||||
}
|
||||
|
||||
function setAllOrdersSelected() {
|
||||
const temp: number[] = [];
|
||||
orders.value.forEach((order) => order.id && temp.push(order.id));
|
||||
orderFilter.value = temp;
|
||||
}
|
||||
|
||||
function checkAllClicked() {
|
||||
if (orderFilter.value.length === orders.value.length) {
|
||||
orderFilter.value = [];
|
||||
} else {
|
||||
setAllOrdersSelected();
|
||||
}
|
||||
}
|
||||
|
||||
function generateBill() {
|
||||
applyFilterLoading.value = true;
|
||||
confirm.require({
|
||||
message: "Alle ausgewählte Bestellungen abrechnen?",
|
||||
header: "Abrechnen",
|
||||
icon: "pi pi-exclamation-triangle",
|
||||
acceptClass: "p-button-danger",
|
||||
rejectClass: "p-button-secondary",
|
||||
accept: () => {
|
||||
BillsService.postBills(table.value, orderFilter.value.toString())
|
||||
.then((res) => {
|
||||
bill.value = res;
|
||||
billModal.value = true;
|
||||
getData();
|
||||
})
|
||||
.catch((err) => errorToast(toast, err.body.error))
|
||||
.finally(() => (applyFilterLoading.value = false));
|
||||
},
|
||||
reject: () => {
|
||||
applyFilterLoading.value = false;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function billModalClosed() {
|
||||
if (orderFilter.value.length === 0) {
|
||||
router.push({ name: "Bills" });
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
orders,
|
||||
orderFilter,
|
||||
checkAll,
|
||||
checkAllClicked,
|
||||
convertToEur,
|
||||
generateBill,
|
||||
isLoading,
|
||||
applyFilterLoading,
|
||||
total,
|
||||
table,
|
||||
bill,
|
||||
billModal,
|
||||
billModalClosed,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-enter-active {
|
||||
transition: opacity 0.2s ease-in;
|
||||
}
|
||||
.v-enter-from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.field-checkbox:last-child {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
</style>
|
21
frontend/src/views/Empty.vue
Normal file
|
@ -0,0 +1,21 @@
|
|||
<template>
|
||||
<BaseCard>
|
||||
<BaseItem bgColor="c">
|
||||
<div class="p-3 text-center">{{ message }}</div>
|
||||
</BaseItem>
|
||||
</BaseCard>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import BaseCard from "@/components/UI/BaseCard.vue";
|
||||
import BaseItem from "@/components/UI/BaseItem.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "EmptyView",
|
||||
components: { BaseItem, BaseCard },
|
||||
props: { message: { type: String, default: "Bis jetzt noch nichts..." } },
|
||||
});
|
||||
</script>
|
||||
|
||||
<style></style>
|
78
frontend/src/views/Items.vue
Normal file
|
@ -0,0 +1,78 @@
|
|||
<template>
|
||||
<BaseCard>
|
||||
<Transition>
|
||||
<WaveSpinner v-if="isLoading" />
|
||||
<OrderItemList
|
||||
v-else
|
||||
:orderItems="currentOrderItems"
|
||||
:emptyOrderItem="emptyOrderItem"
|
||||
@orderItemChanged="(item) => orderItemChanged(item)"
|
||||
@orderItemDeleted="(item) => orderItemDeleted(item)"
|
||||
@orderItemCreated="(item) => orderItemCreated(item)"
|
||||
/>
|
||||
</Transition>
|
||||
</BaseCard>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, reactive, ref, watch } from "vue";
|
||||
import BaseCard from "@/components/UI/BaseCard.vue";
|
||||
import { service_OrderItem, types_ItemType } from "@/services/openapi";
|
||||
import OrderItemList from "@/components/OrderItem/OrderItemList.vue";
|
||||
import { useStore } from "vuex";
|
||||
import WaveSpinner from "@/components/UI/WaveSpinner.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ItemView",
|
||||
components: { OrderItemList, BaseCard, WaveSpinner },
|
||||
props: { id: { type: String, default: "0" } },
|
||||
setup(props) {
|
||||
const store = useStore();
|
||||
const isLoading = ref(true);
|
||||
const orderItems = computed(() => store.getters.getOrderItems);
|
||||
const currentOrderItems = ref();
|
||||
const emptyOrderItem = reactive<service_OrderItem>({ description: "", item_type: 0, price: 0 });
|
||||
const intId = ref<types_ItemType>(parseInt(props.id));
|
||||
|
||||
getData();
|
||||
async function getData() {
|
||||
isLoading.value = true;
|
||||
intId.value = parseInt(props.id);
|
||||
await store.dispatch("getOrderItems", intId.value);
|
||||
emptyOrderItem.item_type = intId.value;
|
||||
refreshMap();
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
||||
function refreshMap() {
|
||||
currentOrderItems.value = orderItems.value.get(intId.value);
|
||||
}
|
||||
|
||||
watch(props, () => getData());
|
||||
|
||||
function orderItemChanged(item: service_OrderItem) {
|
||||
store.dispatch("updateOrderItem", item);
|
||||
refreshMap();
|
||||
}
|
||||
function orderItemDeleted(item: service_OrderItem) {
|
||||
store.dispatch("deleteOrderItem", item);
|
||||
refreshMap();
|
||||
}
|
||||
function orderItemCreated(item: service_OrderItem) {
|
||||
store.dispatch("addOrderItem", item);
|
||||
refreshMap();
|
||||
}
|
||||
|
||||
return { currentOrderItems, orderItemChanged, orderItemDeleted, orderItemCreated, emptyOrderItem, isLoading };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-enter-active {
|
||||
transition: opacity 0.2s ease-in;
|
||||
}
|
||||
.v-enter-from {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
155
frontend/src/views/Orders.vue
Normal file
|
@ -0,0 +1,155 @@
|
|||
<template>
|
||||
<BaseCard>
|
||||
<Transition>
|
||||
<WaveSpinner v-if="isLoading" />
|
||||
<EmptyView v-else-if="empty" message="Keine offenen Bestellungen" />
|
||||
<EmptyView v-else-if="!user.show_cold_drinks && !user.show_hot_drinks" message="Keine Bestellungen in den Einstellungen gewählt" />
|
||||
<div v-else>
|
||||
<template v-if="user.show_hot_drinks">
|
||||
<OrderSection
|
||||
v-for="[key, orders] in otherOrders"
|
||||
v-bind:key="key"
|
||||
:orders="orders"
|
||||
:title="'Tisch ' + orders[0].table_id"
|
||||
@filterOrders="(id) => filterOrder(id)"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="user.show_cold_drinks">
|
||||
<OrderSection :orders="coldOrders" :itemType="types_ItemType.ColdDrink" @filterOrders="(id) => filterOrder(id)" />
|
||||
</template>
|
||||
</div>
|
||||
</Transition>
|
||||
</BaseCard>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, onUnmounted, provide, ref } from "vue";
|
||||
import BaseCard from "@/components/UI/BaseCard.vue";
|
||||
import { OrdersService, service_Order, types_ItemType, user_User } from "@/services/openapi";
|
||||
import { detailedItemTypeIcon, detailedItemTypeString, NotifierType, WebSocketMsg } from "@/utils";
|
||||
import { WEBSOCKET_ENDPOINT_URL } from "@/main";
|
||||
import EmptyView from "@/views/Empty.vue";
|
||||
import WaveSpinner from "@/components/UI/WaveSpinner.vue";
|
||||
import { disabled, loading } from "@/keys";
|
||||
import OrderSection from "@/components/Orders/OrderSection.vue";
|
||||
import { useStore } from "vuex";
|
||||
|
||||
export default defineComponent({
|
||||
name: "OrderView",
|
||||
components: { OrderSection, EmptyView, BaseCard, WaveSpinner },
|
||||
setup() {
|
||||
const store = useStore();
|
||||
const isLoading = ref(true);
|
||||
const isDisabled = ref(false);
|
||||
provide(disabled, isDisabled);
|
||||
provide(loading, isDisabled);
|
||||
const orders = ref<service_Order[]>([]);
|
||||
const user = computed<user_User>(() => store.getters.getUser);
|
||||
|
||||
const empty = computed(() => {
|
||||
return (
|
||||
(user.value.show_cold_drinks && user.value.show_hot_drinks && orders.value.length === 0) ||
|
||||
(user.value.show_cold_drinks && !user.value.show_hot_drinks && coldOrders.value.length === 0) ||
|
||||
(!user.value.show_cold_drinks && user.value.show_hot_drinks && otherOrders.value.size === 0)
|
||||
);
|
||||
});
|
||||
|
||||
const otherOrders = computed(() => {
|
||||
const temp = new Map<number, service_Order[]>();
|
||||
orders.value.forEach((order) => {
|
||||
if (order.order_item.item_type === types_ItemType.ColdDrink) return;
|
||||
const existing = temp.get(order.table_id);
|
||||
if (existing) {
|
||||
existing.push(order);
|
||||
} else {
|
||||
temp.set(order.table_id, [order]);
|
||||
}
|
||||
});
|
||||
return new Map([...temp.entries()].sort());
|
||||
});
|
||||
const coldOrders = computed(() => orders.value.filter((order) => order.order_item.item_type === types_ItemType.ColdDrink));
|
||||
const ws = ref<WebSocket | null>(null);
|
||||
|
||||
getData();
|
||||
function getData() {
|
||||
isLoading.value = true;
|
||||
OrdersService.getOrders()
|
||||
.then((res) => (orders.value = res))
|
||||
.finally(() => {
|
||||
isLoading.value = false;
|
||||
startWebsocket();
|
||||
});
|
||||
}
|
||||
onUnmounted(() => stopWebsocket());
|
||||
|
||||
function filterOrder(id: number) {
|
||||
orders.value = orders.value.filter((old) => old.id !== id);
|
||||
}
|
||||
|
||||
function startWebsocket() {
|
||||
ws.value = new WebSocket(WEBSOCKET_ENDPOINT_URL);
|
||||
ws.value.addEventListener("message", parseWebsocket);
|
||||
ws.value.addEventListener("error", handleWebsocketError);
|
||||
}
|
||||
|
||||
function stopWebsocket() {
|
||||
if (ws.value) {
|
||||
ws.value.removeEventListener("message", parseWebsocket);
|
||||
ws.value.removeEventListener("error", handleWebsocketError);
|
||||
ws.value.close();
|
||||
}
|
||||
}
|
||||
|
||||
function handleWebsocketError() {
|
||||
stopWebsocket();
|
||||
setTimeout(() => {
|
||||
startWebsocket();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function parseWebsocket(evt: Event) {
|
||||
isDisabled.value = true;
|
||||
const messageEvent = evt as MessageEvent;
|
||||
const webSocketMsg: WebSocketMsg = JSON.parse(messageEvent.data);
|
||||
if (webSocketMsg.type === NotifierType.Create) {
|
||||
orders.value.push(webSocketMsg.payload[0]);
|
||||
} else if (webSocketMsg.type === NotifierType.Delete) {
|
||||
orders.value = orders.value.filter((o) => o.id !== webSocketMsg.payload[0].id);
|
||||
} else if (webSocketMsg.type === NotifierType.DeleteAll) {
|
||||
webSocketMsg.payload.forEach((obj) => {
|
||||
orders.value = orders.value.filter((o) => o.id !== obj.id);
|
||||
});
|
||||
}
|
||||
sortOrders();
|
||||
isDisabled.value = false;
|
||||
}
|
||||
|
||||
function sortOrders() {
|
||||
orders.value.sort((a, b) => (a.updated_at && b.updated_at ? a.updated_at - b.updated_at : 0));
|
||||
}
|
||||
|
||||
return {
|
||||
orders,
|
||||
otherOrders,
|
||||
coldOrders,
|
||||
filterOrder,
|
||||
types_ItemType,
|
||||
isLoading,
|
||||
isDisabled,
|
||||
detailedItemTypeString,
|
||||
detailedItemTypeIcon,
|
||||
user,
|
||||
empty,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-enter-active {
|
||||
transition: opacity 0.2s ease-in;
|
||||
}
|
||||
.v-enter-from {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
50
frontend/src/views/Tables.vue
Normal file
|
@ -0,0 +1,50 @@
|
|||
<template>
|
||||
<BaseCard>
|
||||
<Transition>
|
||||
<WaveSpinner v-if="isLoading" />
|
||||
<EmptyView v-else-if="tables && tables.length === 0" message="Keine Tische" />
|
||||
<div v-else class="grid">
|
||||
<TableCard v-for="table in tables" v-bind:key="table.id" :table="table" />
|
||||
</div>
|
||||
</Transition>
|
||||
</BaseCard>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, ref } from "vue";
|
||||
import BaseCard from "@/components/UI/BaseCard.vue";
|
||||
import { useStore } from "vuex";
|
||||
import TableCard from "@/components/Tables/TableCard.vue";
|
||||
import EmptyView from "@/views/Empty.vue";
|
||||
import WaveSpinner from "@/components/UI/WaveSpinner.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "TablesView",
|
||||
components: { WaveSpinner, EmptyView, TableCard, BaseCard },
|
||||
setup() {
|
||||
const isLoading = ref(false);
|
||||
const store = useStore();
|
||||
const tables = computed(() => store.getters.getTables);
|
||||
|
||||
getData();
|
||||
function getData() {
|
||||
isLoading.value = true;
|
||||
store.dispatch("fetchTables").then((res) => {
|
||||
store.commit("setTables", res);
|
||||
isLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
return { tables, isLoading };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-enter-active {
|
||||
transition: opacity 0.2s ease-in;
|
||||
}
|
||||
.v-enter-from {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
12
frontend/tests/unit/example.spec.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { shallowMount } from "@vue/test-utils";
|
||||
import HelloWorld from "@/components/HelloWorld.vue";
|
||||
|
||||
describe("HelloWorld.vue", () => {
|
||||
it("renders props.msg when passed", () => {
|
||||
const msg = "new message";
|
||||
const wrapper = shallowMount(HelloWorld, {
|
||||
props: { msg },
|
||||
});
|
||||
expect(wrapper.text()).toMatch(msg);
|
||||
});
|
||||
});
|
23
frontend/tsconfig.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"moduleResolution": "node",
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"useDefineForClassFields": true,
|
||||
"sourceMap": true,
|
||||
"baseUrl": ".",
|
||||
"types": ["webpack-env", "jest"],
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
},
|
||||
"lib": ["esnext", "dom", "dom.iterable", "scripthost"]
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "tests/**/*.ts", "tests/**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
7
frontend/vue.config.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
const { defineConfig } = require("@vue/cli-service");
|
||||
module.exports = defineConfig({
|
||||
transpileDependencies: true,
|
||||
devServer: {
|
||||
allowedHosts: 'all'
|
||||
}
|
||||
});
|
9160
frontend/yarn.lock
Normal file
70
go.mod
Normal file
|
@ -0,0 +1,70 @@
|
|||
module cafe
|
||||
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/gin-contrib/cors v1.4.0
|
||||
github.com/gin-contrib/static v0.0.1
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/go-playground/assert/v2 v2.2.0
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/viper v1.16.0
|
||||
github.com/swaggo/files v1.0.1
|
||||
github.com/swaggo/gin-swagger v1.6.0
|
||||
github.com/swaggo/swag v1.16.1
|
||||
github.com/unjx-de/go-folder v1.0.7
|
||||
gorm.io/driver/mysql v1.5.1
|
||||
gorm.io/driver/sqlite v1.5.2
|
||||
gorm.io/gorm v1.25.2
|
||||
gorm.io/plugin/soft_delete v1.2.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/bytedance/sonic v1.9.2 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/spec v0.20.9 // indirect
|
||||
github.com/go-openapi/swag v0.22.4 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.14.1 // indirect
|
||||
github.com/go-sql-driver/mysql v1.7.1 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.17 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
||||
github.com/spf13/afero v1.9.5 // indirect
|
||||
github.com/spf13/cast v1.5.1 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/crypto v0.10.0 // indirect
|
||||
golang.org/x/net v0.11.0 // indirect
|
||||
golang.org/x/sys v0.9.0 // indirect
|
||||
golang.org/x/text v0.10.0 // indirect
|
||||
golang.org/x/tools v0.10.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
662
go.sum
Normal file
|
@ -0,0 +1,662 @@
|
|||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
||||
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.9.2 h1:GDaNjuWSGu09guE9Oql0MSTNhNCLlWwO8y/xM5BzcbM=
|
||||
github.com/bytedance/sonic v1.9.2/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||
github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g=
|
||||
github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURUfmBoMlcs=
|
||||
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-contrib/static v0.0.1 h1:JVxuvHPuUfkoul12N7dtQw7KRn/pSMq7Ue1Va9Swm1U=
|
||||
github.com/gin-contrib/static v0.0.1/go.mod h1:CSxeF+wep05e0kCOsqWdAWbSszmc31zTIbD8TvWl7Hs=
|
||||
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
|
||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
|
||||
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
||||
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
|
||||
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
|
||||
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
|
||||
github.com/go-openapi/spec v0.20.9 h1:xnlYNQAwKd2VQRRfwTEI0DcK+2cbuvI/0c7jx3gA8/8=
|
||||
github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
|
||||
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
||||
github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k=
|
||||
github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
||||
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
|
||||
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
|
||||
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
|
||||
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
|
||||
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc=
|
||||
github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
|
||||
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
||||
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
|
||||
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
|
||||
github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M=
|
||||
github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo=
|
||||
github.com/swaggo/swag v1.16.1 h1:fTNRhKstPKxcnoKsytm4sahr8FaYzUcT7i1/3nd/fBg=
|
||||
github.com/swaggo/swag v1.16.1/go.mod h1:9/LMvHycG3NFHfR6LwvikHv5iFvmPADQ359cKikGxto=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/unjx-de/go-folder v1.0.7 h1:OVKvqjcVB0ASidVshYndRtkmlqS1h6MIhSr0vqX3Q6A=
|
||||
github.com/unjx-de/go-folder v1.0.7/go.mod h1:sbcRrRgLE49QI6CZqGBMdneRuNOOhoRU1gx9DYlyD2g=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
|
||||
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
|
||||
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
||||
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg=
|
||||
golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/mysql v1.5.1 h1:WUEH5VF9obL/lTtzjmML/5e6VfFR/788coz2uaVCAZw=
|
||||
gorm.io/driver/mysql v1.5.1/go.mod h1:Jo3Xu7mMhCyj8dlrb3WoCaRd1FhsVh+yMXb1jUInf5o=
|
||||
gorm.io/driver/sqlite v1.1.3/go.mod h1:AKDgRWk8lcSQSw+9kxCJnX/yySj8G3rdwYlU57cB45c=
|
||||
gorm.io/driver/sqlite v1.5.2 h1:TpQ+/dqCY4uCigCFyrfnrJnrW9zjpelWVoEVNy5qJkc=
|
||||
gorm.io/driver/sqlite v1.5.2/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
|
||||
gorm.io/gorm v1.20.1/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
|
||||
gorm.io/gorm v1.23.0/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
||||
gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho=
|
||||
gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gorm.io/plugin/soft_delete v1.2.1 h1:qx9D/c4Xu6w5KT8LviX8DgLcB9hkKl6JC9f44Tj7cGU=
|
||||
gorm.io/plugin/soft_delete v1.2.1/go.mod h1:Zv7vQctOJTGOsJ/bWgrN1n3od0GBAZgnLjEx+cApLGk=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
55
hub/hub.go
Normal file
|
@ -0,0 +1,55 @@
|
|||
package hub
|
||||
|
||||
import (
|
||||
"cafe/service"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type (
|
||||
NotifierChan chan service.WebSocketMsg
|
||||
|
||||
Hub struct {
|
||||
Notifier NotifierChan
|
||||
NewClients chan NotifierChan
|
||||
ClosingClients chan NotifierChan
|
||||
clients map[NotifierChan]struct{}
|
||||
}
|
||||
)
|
||||
|
||||
func (h *Hub) Initialize() {
|
||||
h.Notifier = make(NotifierChan)
|
||||
h.NewClients = make(chan NotifierChan)
|
||||
h.ClosingClients = make(chan NotifierChan)
|
||||
h.clients = make(map[NotifierChan]struct{})
|
||||
go h.listen()
|
||||
go func() {
|
||||
for {
|
||||
if msg, ok := <-service.LiveCh; ok {
|
||||
h.Notifier <- msg
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (h *Hub) listen() {
|
||||
for {
|
||||
select {
|
||||
case s := <-h.NewClients:
|
||||
h.clients[s] = struct{}{}
|
||||
logrus.WithField("openConnections", len(h.clients)).Trace("Websocket connection added")
|
||||
case s := <-h.ClosingClients:
|
||||
delete(h.clients, s)
|
||||
logrus.WithField("openConnections", len(h.clients)).Trace("Websocket connection removed")
|
||||
case event := <-h.Notifier:
|
||||
for client := range h.clients {
|
||||
select {
|
||||
case client <- event:
|
||||
default:
|
||||
close(client)
|
||||
delete(h.clients, client)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
39
main.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"cafe/api"
|
||||
"cafe/config"
|
||||
"cafe/service"
|
||||
"cafe/user"
|
||||
"fmt"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
|
||||
config.Cafe.Database.Initialize(config.StorageDir)
|
||||
config.Cafe.Database.MigrateHelper(service.Table{}, "table")
|
||||
config.Cafe.Database.MigrateHelper(service.Order{}, "order")
|
||||
config.Cafe.Database.MigrateHelper(service.OrderItem{}, "orderItem")
|
||||
config.Cafe.Database.MigrateHelper(service.Bill{}, "bill")
|
||||
config.Cafe.Database.MigrateHelper(service.BillItem{}, "billItem")
|
||||
config.Cafe.Database.MigrateHelper(user.User{}, "user")
|
||||
|
||||
a := api.Api{}
|
||||
service.Initialize()
|
||||
a.Hub.Initialize()
|
||||
|
||||
a.Router = gin.New()
|
||||
a.SetMiddlewares()
|
||||
a.HandleStaticFiles()
|
||||
a.SetupSwagger()
|
||||
a.SetupRouter()
|
||||
logrus.WithField("port", config.Cafe.Port).Info("Server running")
|
||||
err := a.Router.Run(fmt.Sprintf(":%d", config.Cafe.Port))
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Fatal("Cannot start server")
|
||||
}
|
||||
}
|
25
scripts/dev.sh
Executable file
|
@ -0,0 +1,25 @@
|
|||
#!/bin/sh
|
||||
parse_yaml() {
|
||||
local prefix=$2
|
||||
local s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs=$(echo @ | tr @ '\034')
|
||||
sed -ne "s|^\($s\)\($w\)$s:$s\"\(.*\)\"$s\$|\1$fs\2$fs\3|p" \
|
||||
-e "s|^\($s\)\($w\)$s:$s\(.*\)$s\$|\1$fs\2$fs\3|p" $1 |
|
||||
awk -F$fs '{
|
||||
indent = length($1)/2;
|
||||
vname[indent] = $2;
|
||||
for (i in vname) {if (i > indent) {delete vname[i]}}
|
||||
if (length($3) > 0) {
|
||||
vn=""; for (i=0; i<indent; i++) {vn=(vn)(vname[i])("_")}
|
||||
printf("%s%s%s=\"%s\"\n", "'$prefix'",vn, $2, $3);
|
||||
}
|
||||
}'
|
||||
}
|
||||
|
||||
eval $(parse_yaml .gitlab/_common.gitlab-ci.yml)
|
||||
|
||||
echo "DOCKER_VERSION="$variables_DOCKER_VERSION >.env
|
||||
echo "GOLANG_VERSION="$variables_GOLANG_VERSION >>.env
|
||||
echo "NODE_VERSION="$variables_NODE_VERSION >>.env
|
||||
echo "ALPINE_VERSION="$variables_ALPINE_VERSION >>.env
|
||||
echo "DEBIAN_VERSION="$variables_DEBIAN_VERSION >>.env
|
||||
echo "TELEPORT_VERSION="$variables_TELEPORT_VERSION >>.env
|
34
scripts/entrypoint.sh
Executable file
|
@ -0,0 +1,34 @@
|
|||
#!/bin/sh
|
||||
|
||||
cat logo.txt
|
||||
echo ""
|
||||
|
||||
if [ -n "$PUID" ] || [ -n "$PGID" ]; then
|
||||
USER=appuser
|
||||
HOME=/app
|
||||
|
||||
if ! grep -q "$USER" /etc/passwd; then
|
||||
# Usage: addgroup [-g GID] [-S] [USER] GROUP
|
||||
#
|
||||
# Add a group or add a user to a group
|
||||
# -g GID Group id
|
||||
addgroup -g "$PGID" "$USER"
|
||||
|
||||
# Usage: adduser [OPTIONS] USER [GROUP]
|
||||
# Create new user, or add USER to GROUP
|
||||
# -h DIR Home directory
|
||||
# -g GECOS GECOS field
|
||||
# -G GRP Group
|
||||
# -D Don't assign a password
|
||||
# -H Don't create home directory
|
||||
# -u UID User id
|
||||
adduser -h "$HOME" -g "" -G "$USER" -D -H -u "$PUID" "$USER"
|
||||
fi
|
||||
|
||||
chown "$USER":"$USER" "$HOME" -R
|
||||
printf "UID: %s GID: %s\n\n" "$PUID" "$PGID"
|
||||
exec su -c - $USER ./cafe
|
||||
else
|
||||
printf "WARNING: Running docker as root\n\n"
|
||||
exec ./cafe
|
||||
fi
|
15
scripts/swagger.sh
Executable file
|
@ -0,0 +1,15 @@
|
|||
#!/bin/sh
|
||||
|
||||
action=$1
|
||||
|
||||
case $action in
|
||||
"install")
|
||||
go install github.com/swaggo/swag/cmd/swag@latest
|
||||
;;
|
||||
"init")
|
||||
swag init -g api/swagger.go
|
||||
;;
|
||||
*)
|
||||
exit 0
|
||||
;;
|
||||
esac
|
124
service/bill.go
Normal file
|
@ -0,0 +1,124 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"cafe/config"
|
||||
"cafe/types"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
Bill struct {
|
||||
ID uint64 `gorm:"primaryKey" json:"id" validate:"optional"`
|
||||
TableID uint64 `json:"table_id" validate:"required"`
|
||||
Total float32 `json:"total" validate:"required"`
|
||||
CreatedAt int64 `json:"created_at" validate:"optional"`
|
||||
}
|
||||
|
||||
BillItem struct {
|
||||
ID uint64 `gorm:"primaryKey" json:"id" validate:"optional"`
|
||||
BillID uint64 `json:"bill_id" validate:"required"`
|
||||
Description string `json:"description" validate:"required"`
|
||||
Total float32 `json:"total" validate:"required"`
|
||||
Price float32 `json:"price" validate:"required"`
|
||||
Amount uint64 `json:"amount" validate:"required"`
|
||||
ItemType types.ItemType `json:"item_type" validate:"required"`
|
||||
}
|
||||
)
|
||||
|
||||
func DoesBillExist(id string) (Bill, error) {
|
||||
var bill Bill
|
||||
result := config.Cafe.Database.ORM.Limit(1).Find(&bill, id)
|
||||
if result.RowsAffected == 0 {
|
||||
return bill, fmt.Errorf(types.CannotFind.String())
|
||||
}
|
||||
return bill, nil
|
||||
}
|
||||
|
||||
func GetAllBillItems(billId uint64) ([]BillItem, error) {
|
||||
var billItems []BillItem
|
||||
result := config.Cafe.Database.ORM.Where("bill_id = ?", billId).Find(&billItems)
|
||||
if result.RowsAffected == 0 {
|
||||
return billItems, fmt.Errorf(types.CannotFind.String())
|
||||
}
|
||||
return billItems, nil
|
||||
}
|
||||
|
||||
func getDate(year string, month string, day string) (time.Time, error) {
|
||||
yearI, yearErr := strconv.Atoi(year)
|
||||
if yearErr != nil {
|
||||
return time.Time{}, fmt.Errorf("jahr " + types.CannotParse.String())
|
||||
}
|
||||
monthI, monthErr := strconv.Atoi(month)
|
||||
if monthErr != nil {
|
||||
return time.Time{}, fmt.Errorf("monat " + types.CannotParse.String())
|
||||
}
|
||||
dayI, dayErr := strconv.Atoi(day)
|
||||
if dayErr != nil {
|
||||
return time.Time{}, fmt.Errorf("tag " + types.CannotParse.String())
|
||||
}
|
||||
loc, locErr := time.LoadLocation("Local")
|
||||
if locErr != nil {
|
||||
return time.Time{}, fmt.Errorf("timezone " + types.CannotParse.String())
|
||||
}
|
||||
return time.Date(yearI, time.Month(monthI), dayI, 0, 0, 0, 0, loc), nil
|
||||
}
|
||||
|
||||
func GetAllBills(year string, month string, day string) ([]Bill, error) {
|
||||
var bills []Bill
|
||||
today, err := getDate(year, month, day)
|
||||
if err != nil {
|
||||
return bills, err
|
||||
}
|
||||
beginningOfDay := today.Unix()
|
||||
endOfDay := today.Add(23 * time.Hour).Add(59 * time.Minute).Add(59 * time.Second).Unix()
|
||||
config.Cafe.Database.ORM.Where("created_at BETWEEN ? AND ?", beginningOfDay, endOfDay).Order("created_at").Find(&bills)
|
||||
return bills, nil
|
||||
}
|
||||
|
||||
func CreateBill(options GetOrderOptions) (Bill, error) {
|
||||
orders := GetAllOrdersForTable(options)
|
||||
var bill Bill
|
||||
var total float32 = 0
|
||||
for _, order := range orders {
|
||||
total += order.Total
|
||||
}
|
||||
bill.TableID = options.TableId
|
||||
bill.Total = total
|
||||
err := config.Cafe.Database.ORM.Create(&bill).Error
|
||||
if err != nil {
|
||||
return bill, fmt.Errorf(types.CannotCreate.String())
|
||||
}
|
||||
for _, order := range orders {
|
||||
billItem := BillItem{
|
||||
BillID: bill.ID,
|
||||
Description: order.OrderItem.Description,
|
||||
Total: order.Total,
|
||||
Price: order.OrderItem.Price,
|
||||
Amount: order.OrderCount,
|
||||
ItemType: order.OrderItem.ItemType,
|
||||
}
|
||||
config.Cafe.Database.ORM.Create(&billItem)
|
||||
}
|
||||
ordersToDelete := GetAllOrdersForTable(GetOrderOptions{TableId: options.TableId, Grouped: false, Filter: options.Filter})
|
||||
err = config.Cafe.Database.ORM.Delete(&ordersToDelete).Error
|
||||
if err != nil {
|
||||
return bill, fmt.Errorf(types.CannotDelete.String())
|
||||
}
|
||||
LiveCh <- WebSocketMsg{
|
||||
Type: types.DeleteAll,
|
||||
Payload: ordersToDelete,
|
||||
}
|
||||
return bill, nil
|
||||
}
|
||||
|
||||
func DeleteBill(bill *Bill) error {
|
||||
err := config.Cafe.Database.ORM.Delete(bill).Error
|
||||
if err != nil {
|
||||
return fmt.Errorf(types.CannotDelete.String())
|
||||
}
|
||||
billItemsToDelete, _ := GetAllBillItems(bill.ID)
|
||||
config.Cafe.Database.ORM.Delete(&billItemsToDelete)
|
||||
return nil
|
||||
}
|
169
service/order.go
Normal file
|
@ -0,0 +1,169 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"cafe/config"
|
||||
"cafe/types"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type (
|
||||
Order struct {
|
||||
ID uint64 `gorm:"primaryKey" json:"id" validate:"optional"`
|
||||
TableID uint64 `json:"table_id" validate:"required"`
|
||||
OrderItemID uint64 `json:"order_item_id" validate:"required"`
|
||||
OrderItem OrderItem `json:"order_item" validate:"required"`
|
||||
UpdatedAt int64 `json:"updated_at" validate:"optional"`
|
||||
IsServed bool `json:"is_served" default:"false" validate:"required"`
|
||||
Total float32 `json:"total" validate:"required"`
|
||||
OrderCount uint64 `json:"order_count" validate:"required"`
|
||||
}
|
||||
|
||||
OrderItem struct {
|
||||
ID uint64 `gorm:"primaryKey" json:"id" validate:"optional"`
|
||||
ItemType types.ItemType `json:"item_type" validate:"required"`
|
||||
Description string `json:"description" validate:"required"`
|
||||
Price float32 `json:"price" validate:"required"`
|
||||
}
|
||||
|
||||
GetOrderOptions struct {
|
||||
TableId uint64 `json:"table_id"`
|
||||
Grouped bool `json:"grouped"`
|
||||
Filter []string `json:"filter"`
|
||||
}
|
||||
)
|
||||
|
||||
func updateTableUpdatedAt(tx *gorm.DB, o *Order) {
|
||||
var table Table
|
||||
tx.Where("id = ?", o.TableID).First(&table)
|
||||
table.UpdatedAt = time.Now().Unix()
|
||||
tx.Save(&table)
|
||||
}
|
||||
|
||||
func (o *Order) AfterCreate(tx *gorm.DB) (err error) {
|
||||
updateTableUpdatedAt(tx, o)
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Order) AfterDelete(tx *gorm.DB) (err error) {
|
||||
updateTableUpdatedAt(tx, o)
|
||||
return
|
||||
}
|
||||
|
||||
func DoesOrderItemExist(id string) (OrderItem, error) {
|
||||
var orderItem OrderItem
|
||||
result := config.Cafe.Database.ORM.Limit(1).Find(&orderItem, id)
|
||||
if result.RowsAffected == 0 {
|
||||
return orderItem, fmt.Errorf(types.CannotFind.String())
|
||||
}
|
||||
return orderItem, nil
|
||||
}
|
||||
|
||||
func DoesOrderExist(id string) (Order, error) {
|
||||
var order Order
|
||||
result := config.Cafe.Database.ORM.Limit(1).Find(&order, id)
|
||||
if result.RowsAffected == 0 {
|
||||
return order, fmt.Errorf(types.CannotFind.String())
|
||||
}
|
||||
return order, nil
|
||||
}
|
||||
|
||||
func GetAllActiveOrders() []Order {
|
||||
var orders []Order
|
||||
config.Cafe.Database.ORM.Model(&Order{}).Joins("OrderItem").Where("is_served = ?", 0).Order("updated_at").Find(&orders)
|
||||
return orders
|
||||
}
|
||||
|
||||
func GetAllOrdersForTable(options GetOrderOptions) []Order {
|
||||
var orders []Order
|
||||
if options.Grouped {
|
||||
if len(options.Filter) == 0 {
|
||||
config.Cafe.Database.ORM.Model(&Order{}).Joins("OrderItem").Select("table_id, order_item_id, sum(price) as total, count(order_item_id) as order_count").Group("order_item_id").Where("table_id = ?", options.TableId).Order("item_type, description").Find(&orders)
|
||||
} else {
|
||||
config.Cafe.Database.ORM.Model(&Order{}).Find(&orders, options.Filter).Joins("OrderItem").Select("table_id, order_item_id, sum(price) as total, count(order_item_id) as order_count").Group("order_item_id").Where("table_id = ?", options.TableId).Order("item_type, description").Find(&orders)
|
||||
}
|
||||
} else {
|
||||
if len(options.Filter) == 0 {
|
||||
config.Cafe.Database.ORM.Model(&Order{}).Joins("OrderItem").Where("table_id = ?", options.TableId).Order("item_type, description").Find(&orders)
|
||||
} else {
|
||||
config.Cafe.Database.ORM.Model(&Order{}).Find(&orders, options.Filter).Where("table_id = ?", options.TableId).Find(&orders)
|
||||
}
|
||||
}
|
||||
return orders
|
||||
}
|
||||
|
||||
func CreateOrder(order *Order) error {
|
||||
err := config.Cafe.Database.ORM.Create(order).Error
|
||||
if err != nil {
|
||||
return fmt.Errorf(types.CannotCreate.String())
|
||||
}
|
||||
config.Cafe.Database.ORM.Model(&Order{}).Joins("OrderItem").First(order)
|
||||
LiveCh <- WebSocketMsg{
|
||||
Type: types.Create,
|
||||
Payload: []Order{*order},
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func UpdateOrder(old *Order, new *Order) error {
|
||||
err := config.Cafe.Database.ORM.First(old).Updates(new).Error
|
||||
if err != nil {
|
||||
return fmt.Errorf(types.CannotUpdate.String())
|
||||
}
|
||||
if new.IsServed {
|
||||
LiveCh <- WebSocketMsg{
|
||||
Type: types.Delete,
|
||||
Payload: []Order{*new},
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DeleteOrder(tableId string, orderItemId string) error {
|
||||
var order Order
|
||||
err := config.Cafe.Database.ORM.Where("table_id = ? AND order_item_id = ?", tableId, orderItemId).Last(&order).Error
|
||||
if err != nil {
|
||||
return fmt.Errorf(types.CannotFind.String())
|
||||
}
|
||||
err = config.Cafe.Database.ORM.Delete(&order).Error
|
||||
if err != nil {
|
||||
return fmt.Errorf(types.CannotDelete.String())
|
||||
}
|
||||
LiveCh <- WebSocketMsg{
|
||||
Type: types.Delete,
|
||||
Payload: []Order{order},
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetOrderItemsForType(itemType string) []OrderItem {
|
||||
var orderItems []OrderItem
|
||||
config.Cafe.Database.ORM.Order("description").Where("item_type = ?", types.ParseItemType(itemType)).Find(&orderItems)
|
||||
return orderItems
|
||||
}
|
||||
|
||||
func CreateOrderItem(oderItem *OrderItem) error {
|
||||
err := config.Cafe.Database.ORM.Create(oderItem).Error
|
||||
if err != nil {
|
||||
return fmt.Errorf(types.CannotCreate.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func UpdateOrderItem(old *OrderItem, new *OrderItem) error {
|
||||
err := config.Cafe.Database.ORM.First(old).Updates(new).Error
|
||||
if err != nil {
|
||||
return fmt.Errorf(types.CannotUpdate.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DeleteOrderItem(oderItem *OrderItem) error {
|
||||
err := config.Cafe.Database.ORM.Delete(oderItem).Error
|
||||
if err != nil {
|
||||
return fmt.Errorf(types.CannotDelete.String())
|
||||
}
|
||||
return nil
|
||||
}
|
18
service/service.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"cafe/types"
|
||||
)
|
||||
|
||||
type (
|
||||
WebSocketMsg struct {
|
||||
Type types.NotifierType `json:"type"`
|
||||
Payload []Order `json:"payload"`
|
||||
}
|
||||
)
|
||||
|
||||
var LiveCh chan WebSocketMsg
|
||||
|
||||
func Initialize() {
|
||||
LiveCh = make(chan WebSocketMsg)
|
||||
}
|
75
service/table.go
Normal file
|
@ -0,0 +1,75 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"cafe/config"
|
||||
"cafe/types"
|
||||
"fmt"
|
||||
|
||||
"gorm.io/plugin/soft_delete"
|
||||
)
|
||||
|
||||
type Table struct {
|
||||
ID uint64 `gorm:"primaryKey" json:"id" validate:"optional"`
|
||||
OrderCount uint64 `json:"order_count" validate:"required"`
|
||||
Total float32 `json:"total" validate:"required"`
|
||||
UpdatedAt int64 `json:"updated_at" validate:"optional"`
|
||||
IsDeleted soft_delete.DeletedAt `gorm:"softDelete:flag" json:"is_deleted" swaggerignore:"true"`
|
||||
}
|
||||
|
||||
func GetAllTables() []Table {
|
||||
var tables []Table
|
||||
config.Cafe.Database.ORM.Model(
|
||||
&Table{},
|
||||
).Joins(
|
||||
"left join orders on tables.id = orders.table_id",
|
||||
).Joins(
|
||||
"left join order_items on orders.order_item_id = order_items.id",
|
||||
).Select(
|
||||
"tables.id, tables.updated_at, sum(order_items.price) as total, count(orders.id) as order_count",
|
||||
).Group(
|
||||
"tables.id",
|
||||
).Order("tables.id").Find(&tables)
|
||||
return tables
|
||||
}
|
||||
|
||||
func CreateNewTable() (Table, error) {
|
||||
var table Table
|
||||
var err error
|
||||
result := config.Cafe.Database.ORM.Unscoped().Where("is_deleted = ?", 1).Limit(1).Find(&table)
|
||||
if result.RowsAffected == 0 {
|
||||
err = config.Cafe.Database.ORM.Create(&table).Error
|
||||
} else {
|
||||
table.IsDeleted = 0
|
||||
err = config.Cafe.Database.ORM.Unscoped().Save(&table).Error
|
||||
}
|
||||
if err != nil {
|
||||
return table, fmt.Errorf(types.CannotCreate.String())
|
||||
}
|
||||
return table, nil
|
||||
}
|
||||
|
||||
func DeleteLatestTable() error {
|
||||
var table Table
|
||||
err := config.Cafe.Database.ORM.Model(
|
||||
&Table{},
|
||||
).Joins(
|
||||
"left join orders on tables.id = orders.table_id",
|
||||
).Joins(
|
||||
"left join order_items on orders.order_item_id = order_items.id",
|
||||
).Select(
|
||||
"tables.id, count(orders.id) as order_count",
|
||||
).Group(
|
||||
"tables.id",
|
||||
).Last(&table).Error
|
||||
if err != nil {
|
||||
return fmt.Errorf(types.CannotFind.String())
|
||||
}
|
||||
if table.OrderCount != 0 {
|
||||
return fmt.Errorf(types.StillInUse.String())
|
||||
}
|
||||
err = config.Cafe.Database.ORM.Delete(&table).Error
|
||||
if err != nil {
|
||||
return fmt.Errorf(types.CannotDelete.String())
|
||||
}
|
||||
return nil
|
||||
}
|
12
templates/index.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Test</title>
|
||||
</head>
|
||||
<body>
|
||||
<div>Working</div>
|
||||
</body>
|
||||
</html>
|
59
types/types.go
Normal file
|
@ -0,0 +1,59 @@
|
|||
package types
|
||||
|
||||
type (
|
||||
ErrorResponses uint
|
||||
ItemType uint
|
||||
NotifierType uint
|
||||
)
|
||||
|
||||
const (
|
||||
Create NotifierType = iota
|
||||
Delete
|
||||
DeleteAll
|
||||
)
|
||||
|
||||
const (
|
||||
Food ItemType = iota
|
||||
ColdDrink
|
||||
HotDrink
|
||||
)
|
||||
|
||||
const (
|
||||
MissingInformation ErrorResponses = iota
|
||||
CannotCreate
|
||||
CannotUpdate
|
||||
CannotDelete
|
||||
CannotFind
|
||||
StillInUse
|
||||
CannotParse
|
||||
)
|
||||
|
||||
func ParseItemType(itemType string) ItemType {
|
||||
switch itemType {
|
||||
case "0":
|
||||
return Food
|
||||
case "1":
|
||||
return ColdDrink
|
||||
default:
|
||||
return HotDrink
|
||||
}
|
||||
}
|
||||
|
||||
func (e ErrorResponses) String() string {
|
||||
switch e {
|
||||
case MissingInformation:
|
||||
return "fehlende Informationen"
|
||||
case CannotCreate:
|
||||
return "kann nicht gespeichert werden"
|
||||
case CannotUpdate:
|
||||
return "kann nicht geändert werden"
|
||||
case CannotDelete:
|
||||
return "kann nicht gelöscht werden"
|
||||
case CannotFind:
|
||||
return "kann nicht gefunden werden"
|
||||
case StillInUse:
|
||||
return "noch in Verwendung"
|
||||
default:
|
||||
return "kann nicht verarbeitet werden"
|
||||
}
|
||||
}
|
23
types/types_test.go
Normal file
|
@ -0,0 +1,23 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/go-playground/assert/v2"
|
||||
)
|
||||
|
||||
func TestErrorResponses_String(t *testing.T) {
|
||||
assert.Equal(t, MissingInformation.String(), "fehlende Informationen")
|
||||
assert.Equal(t, CannotCreate.String(), "kann nicht gespeichert werden")
|
||||
assert.Equal(t, CannotUpdate.String(), "kann nicht geändert werden")
|
||||
assert.Equal(t, CannotDelete.String(), "kann nicht gelöscht werden")
|
||||
assert.Equal(t, CannotFind.String(), "kann nicht gefunden werden")
|
||||
assert.Equal(t, StillInUse.String(), "noch in Verwendung")
|
||||
assert.Equal(t, CannotParse.String(), "kann nicht verarbeitet werden")
|
||||
}
|
||||
|
||||
func TestParseItemType(t *testing.T) {
|
||||
assert.Equal(t, ParseItemType("0"), Food)
|
||||
assert.Equal(t, ParseItemType("1"), ColdDrink)
|
||||
assert.Equal(t, ParseItemType("2"), HotDrink)
|
||||
}
|
42
user/user.go
Normal file
|
@ -0,0 +1,42 @@
|
|||
package user
|
||||
|
||||
import (
|
||||
"cafe/config"
|
||||
"cafe/types"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Username string `gorm:"primaryKey" json:"username" validate:"required"`
|
||||
ShowColdDrinks bool `json:"show_cold_drinks" validate:"required"`
|
||||
ShowHotDrinks bool `json:"show_hot_drinks" validate:"required"`
|
||||
}
|
||||
|
||||
func DoesUserExist(username string) (User, error) {
|
||||
var user User
|
||||
result := config.Cafe.Database.ORM.Limit(1).Find(&user, "username = ?", username)
|
||||
if result.RowsAffected == 0 {
|
||||
return user, fmt.Errorf(types.CannotFind.String())
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func GetUserOrCreate(username string) (User, error) {
|
||||
var user User
|
||||
err := config.Cafe.Database.ORM.Where(User{Username: username}).Attrs(User{ShowHotDrinks: true, ShowColdDrinks: true}).FirstOrCreate(&user).Error
|
||||
if err != nil {
|
||||
return user, fmt.Errorf(types.CannotCreate.String())
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func UpdateUser(old *User, new *User) error {
|
||||
err := config.Cafe.Database.ORM.First(old).Updates(map[string]interface{}{
|
||||
"Username": new.Username,
|
||||
"ShowColdDrinks": new.ShowColdDrinks,
|
||||
"ShowHotDrinks": new.ShowHotDrinks}).Error
|
||||
if err != nil {
|
||||
return fmt.Errorf(types.CannotUpdate.String())
|
||||
}
|
||||
return nil
|
||||
}
|
36
websocket/websocket.go
Normal file
|
@ -0,0 +1,36 @@
|
|||
package websocket
|
||||
|
||||
import (
|
||||
"cafe/config"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
func inAllowedHosts(str string) bool {
|
||||
for _, a := range config.Cafe.AllowedHosts {
|
||||
if a == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var Upgrader = websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
origin := r.Header.Get("Origin")
|
||||
return inAllowedHosts(origin)
|
||||
},
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
}
|
||||
|
||||
func ReadPump(conn *websocket.Conn) {
|
||||
defer conn.Close()
|
||||
for {
|
||||
_, _, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|