Compare commits

..

138 Commits

Author SHA1 Message Date
MaysWind e608e85d56 support disable map 2023-06-10 17:14:58 +08:00
MaysWind 58104a9a4d append thousands separator in data management page 2023-06-10 17:04:35 +08:00
MaysWind 8d68dcabb5 list item selection sheet supports large size 2023-06-06 01:01:24 +08:00
MaysWind a5e5389d6c fix some text not changed when user language has been changed 2023-06-06 00:56:39 +08:00
MaysWind 8de862c82f optimize the style when the text is too long 2023-06-06 00:52:35 +08:00
MaysWind 5141303ee1 support turning on dark theme manually 2023-06-05 23:54:01 +08:00
MaysWind 3d95680cbc remove unused code 2023-06-05 09:47:09 +08:00
MaysWind 78663b873c support only match the first locale part when get browser language 2023-06-05 00:56:04 +08:00
MaysWind 0a106026dd user settings supports language and date&time format 2023-06-05 00:54:07 +08:00
MaysWind 999ca6274c remove unused file 2023-06-04 21:08:38 +08:00
MaysWind 36b9177069 fix building issue 2023-06-04 15:43:38 +08:00
MaysWind 8cf7bf859b support map provider and whether use map data proxy settings 2023-06-04 15:06:14 +08:00
MaysWind 2e54b62f60 set cookies in development mode 2023-06-04 14:25:51 +08:00
MaysWind df46069d92 code refactor 2023-06-04 13:15:09 +08:00
MaysWind e31014dde4 code refactor 2023-06-04 13:04:15 +08:00
MaysWind f9a14581e1 fix typo 2023-06-04 01:24:08 +08:00
MaysWind 95ec005894 add icons 2023-06-04 01:22:43 +08:00
MaysWind 73d271b8bc improve ui 2023-06-04 01:22:33 +08:00
MaysWind 0c3b56e44a set the color of input and confirm button to red in password input sheet when clear user data 2023-06-03 20:11:03 +08:00
MaysWind 736f340979 code refactor 2023-06-03 16:49:19 +08:00
MaysWind 49e62d35c3 exchange rate datasource supports Monetary Authority of Singapore 2023-05-29 01:04:16 +08:00
MaysWind 810bce7495 update latest available currencies 2023-05-29 00:27:30 +08:00
MaysWind aff876aa05 only trigger gitea docker-snapshot workflow when push to main branch 2023-05-28 17:59:25 +08:00
MaysWind 9511644ce6 update third party dependencies 2023-05-28 17:59:08 +08:00
MaysWind 21d73e5f69 update golang to 1.20.4, node to 18.16.0, base docker image to alpine 3.17.3 2023-05-28 12:18:40 +08:00
MaysWind d62f3fb936 update github actions 2023-05-21 00:06:11 +08:00
MaysWind 0a011b6075 add gitea actions 2023-05-20 23:49:00 +08:00
MaysWind 4e561dc764 change map marker icon 2023-05-15 23:31:53 +08:00
MaysWind a55256ad82 code refactor 2023-05-15 22:36:21 +08:00
MaysWind ab26ef64a5 code refactor 2023-05-15 22:35:59 +08:00
mayswind 6d1610eee0 fix wrong proxy path 2023-05-15 00:49:02 +08:00
MaysWind 2ba143d6ea support showing geolocation on map 2023-05-15 00:08:03 +08:00
MaysWind bd542ac308 modify geolocation storage type in database 2023-05-14 19:42:57 +08:00
MaysWind e71ffd1a77 support storing geo location in transaction 2023-04-29 13:45:19 +08:00
MaysWind 1ac968f63c improve ui 2023-04-28 17:21:27 +08:00
MaysWind dc4f62a085 modify Simplified Chinese translation 2023-04-28 16:26:52 +08:00
MaysWind ad91d4f1ce add show/hide hidden transaction category/tag menu 2023-04-28 16:24:15 +08:00
MaysWind 8d4c7512ab reduce unit test execute times 2023-04-24 00:54:24 +08:00
MaysWind 12d5837526 modify unit test case fail cause message 2023-04-23 21:49:27 +08:00
MaysWind c82d2abab4 fix problem that cannot switch to other date range sometimes in transaction list page 2023-04-23 01:27:38 +08:00
MaysWind 26cb717a08 build when push to non-main branch 2023-04-23 01:00:27 +08:00
MaysWind 0b1cc0ef5b add lint checking and unit testing in build script 2023-04-23 00:45:56 +08:00
MaysWind 05dc9138b4 fix problem that cannot get transaction statistics when db is set to only_full_group_by 2023-04-23 00:03:26 +08:00
MaysWind a7dcacb26c add log 2023-04-22 23:54:57 +08:00
MaysWind 3ac3f871e4 fix the problem that account list page does not update when modify category of account 2023-04-22 23:12:41 +08:00
MaysWind b180c0bbe6 modify link text 2023-04-22 22:35:36 +08:00
MaysWind e4987f3bde modify vue page name 2023-04-22 22:28:04 +08:00
MaysWind ee2690c2cc fix the problem that cannot change language in transaction preset category page 2023-04-22 21:50:08 +08:00
MaysWind 4cad26f793 change moment api 2023-04-22 21:26:42 +08:00
MaysWind 84f3d5fec5 remove unused code 2023-04-22 21:09:01 +08:00
MaysWind bb3939d570 show none when user does not have visible account or category 2023-04-22 21:03:41 +08:00
MaysWind b303f708f5 improve ui 2023-04-22 20:52:20 +08:00
MaysWind d39550e090 improve ui 2023-04-22 20:47:25 +08:00
MaysWind 22d956f38a improve ui 2023-04-22 20:24:53 +08:00
MaysWind dab7728138 update prompt text 2023-04-22 17:29:30 +08:00
MaysWind 4038b6bc51 improve ui 2023-04-22 17:00:56 +08:00
MaysWind bcaf3a246c show no available category / account in transaction statistics filter page when there are no available category or account 2023-04-22 16:19:10 +08:00
MaysWind 4fb115fcbc remove unused code 2023-04-22 01:37:16 +08:00
MaysWind bfd4b2b6de code refactor 2023-04-22 01:33:38 +08:00
MaysWind c0cd3fc5c2 improve ui 2023-04-22 01:24:49 +08:00
MaysWind f95c4393d2 fix the problem that the day of week in date time / date range picker is wrong 2023-04-22 01:10:22 +08:00
MaysWind e2eb5fabcc improve ui 2023-04-22 00:49:47 +08:00
MaysWind 7275e8ff0d modify infinite distance 2023-04-22 00:31:19 +08:00
MaysWind accfc3df12 improve ui 2023-04-21 23:56:52 +08:00
MaysWind 35392483d9 code refactor 2023-04-21 23:56:44 +08:00
MaysWind 9770851fd4 fix the problem that page jump infinitely 2023-04-21 23:56:02 +08:00
MaysWind e013a6f087 fix the display amount in statistics page when there are no transaction 2023-04-21 23:53:19 +08:00
MaysWind 1ec9ff20b1 remove unused code 2023-04-21 23:41:53 +08:00
MaysWind 9b0dea80c9 don't allow clear the value in datetime picker 2023-04-21 23:14:26 +08:00
MaysWind eea1ea7ed0 always show date range picker in center 2023-04-21 22:36:57 +08:00
MaysWind 85cd46bfc7 fix problem the category separate icon in transaction page does not display 2023-04-21 22:30:07 +08:00
MaysWind a7ca394864 code refactor 2023-04-21 22:20:11 +08:00
MaysWind e178a0795a code refactor 2023-04-21 22:16:35 +08:00
MaysWind e8b0470ceb code refactor 2023-04-21 22:11:10 +08:00
MaysWind c08abfdfdf fix frontend build issue 2023-04-21 08:47:15 +08:00
mayswind b1c765eb51 Upgrade to vue3 (#16)
* upgrade to vue 3.x and framework7 8.x
* change calendar plugin to vue-datepicker
* disable export button when user does not hava any transaction
* implement new pin code input
* append thousands separator in amount in exchange rates page
2023-04-21 01:45:00 +08:00
mayswind 4b0f7d45e8 Merge pull request #15 from vigdail/bugfix/atomic_alignment
Proper fields alignment in `InternalUuidGenerator` struct
2023-04-20 22:49:49 +08:00
vigdail 9e6271b1dc Rearrange fields of InternalUuidGenerator struct to fit atomic alignment requirements 2023-04-20 19:39:38 +06:00
MaysWind a96eb31dc9 redirect to login page when user logout without token 2023-04-16 02:00:59 +08:00
MaysWind 221a7809b6 fix npe error 2023-04-12 00:24:20 +08:00
MaysWind 8c33243c90 code refactor 2023-04-09 01:04:00 +08:00
MaysWind c4b07b98aa update third party dependencies 2023-04-08 14:53:26 +08:00
MaysWind 1fda80a86b bump version to 0.3.0 2023-04-08 14:49:39 +08:00
MaysWind 1287c729f2 modify font color style in transaction view page 2023-04-03 00:29:25 +08:00
MaysWind c069faa6f4 modify item header color in ios dark theme 2023-04-03 00:25:50 +08:00
MaysWind 286fd91b2b modify setting ui 2023-04-02 23:29:41 +08:00
MaysWind 33250d2f3d optimize user data export process 2023-04-02 23:18:05 +08:00
MaysWind 44ca940ca3 add splash screen images for ios 2023-04-02 21:05:59 +08:00
MaysWind 3b0ef7a96d data management page shows all user data statistics 2023-04-02 19:36:10 +08:00
MaysWind dfb6c593e4 format code 2023-04-02 18:14:16 +08:00
MaysWind 853b01e2ca show message when force update exchange rates data and the data is up to date 2023-04-02 18:06:36 +08:00
MaysWind 5a924fa382 code refactor 2023-04-02 17:35:31 +08:00
MaysWind d4985a024d use unambiguous numeric variable type 2023-03-27 23:58:33 +08:00
MaysWind 2797266de6 check numeric setting value, add numeric value range comment in config file 2023-03-27 23:13:18 +08:00
MaysWind b476cc91ca bump year 2023-03-27 22:10:57 +08:00
MaysWind 9e3aa19a09 make related transaction has the same unix time with the original transaction 2023-03-27 00:11:39 +08:00
MaysWind 7443e8a532 uuid generator supports generating more than 1 uuid in one time 2023-03-27 00:02:48 +08:00
MaysWind 1d6dbf63c0 code refactor 2023-03-26 23:53:52 +08:00
MaysWind e17687f80d fix wrong string format placeholder 2023-03-26 23:24:29 +08:00
MaysWind 27f4e14a4e code refactor 2023-03-26 22:24:26 +08:00
MaysWind 8d5de98218 record transaction created ip 2023-03-26 22:10:04 +08:00
MaysWind dbf5c0a5bd update golang to 1.20 and update nodejs to 18.15 2023-03-24 00:45:33 +08:00
MaysWind c1422f789a update third party dependencies 2023-03-24 00:40:46 +08:00
MaysWind 613af9399a update badge image 2023-01-08 21:57:22 +08:00
MaysWind 34a6108bb2 modify the end time of last 7/30 days to the end time in today 2023-01-05 22:34:45 +08:00
MaysWind 66a5508abe update third party dependencies 2022-12-06 01:16:02 +08:00
MaysWind 3a273ea64f update base image 2022-12-06 01:14:39 +08:00
MaysWind 6f88e6ef26 add health check api 2022-12-05 22:59:26 +08:00
MaysWind fd7905833e optimize statistics page style 2022-08-01 00:33:19 +08:00
MaysWind 1977e436d6 add "sort by" drop down list in statistics page 2022-07-25 01:11:35 +08:00
MaysWind 0dfb3d00e9 code refactor 2022-07-25 00:22:04 +08:00
MaysWind 785ec9bdb1 update docker base image 2022-07-24 20:35:55 +08:00
MaysWind eeccc4fd49 update third party dependencies 2022-07-24 20:14:39 +08:00
MaysWind efea9a7c37 update year 2022-07-24 19:20:40 +08:00
MaysWind 04eafd6705 code refactor 2022-07-24 19:00:16 +08:00
MaysWind 73b554aa48 auto set transaction type in transaction adding page according to the category id 2022-07-24 17:56:49 +08:00
MaysWind d3ddcf4c20 bump version to 0.2.0 2022-07-24 15:45:12 +08:00
MaysWind ed9ea2f1d3 modify cancel button text and position in license popup 2022-07-21 01:10:36 +08:00
MaysWind 7eae3e9923 show currency code when show currency name 2022-07-21 01:07:43 +08:00
MaysWind 03d95033d7 allow set base amount in exchange rate page 2022-07-21 00:41:37 +08:00
MaysWind 9a79606565 support set user default account 2022-04-18 00:16:47 +08:00
MaysWind c5a101aad2 fix bug 2022-04-17 23:32:00 +08:00
MaysWind 2dbf4d652d update third party dependencies 2022-03-20 23:20:11 +08:00
MaysWind 7364380312 support filter by parent account in transaction list page 2022-03-20 22:26:27 +08:00
MaysWind b7fe70aba3 change method name 2022-03-07 00:27:12 +08:00
MaysWind bca9982c57 hide add icon when filter parent account in transaction list page 2022-03-07 00:02:00 +08:00
MaysWind 7c16435010 make parent account clickable in account list page 2022-03-06 23:55:18 +08:00
MaysWind a79def625c update third party dependencies 2022-03-06 22:59:24 +08:00
MaysWind 8d5f804a60 modify save button text 2022-01-03 20:25:56 +08:00
mayswind 0167381f0d Merge pull request #1 from jiangshengwu/fix_transaction_amount
remove uid in selected columns
2021-07-12 20:31:15 +08:00
Shengwu Jiang 4e2c4b39bb remove uid in selected columns 2021-07-12 20:18:00 +08:00
MaysWind 7bfc84abc8 update comment 2021-07-04 22:57:28 +08:00
MaysWind 5ac6c64079 add data & log folder for building package 2021-06-29 23:57:21 +08:00
MaysWind e7c4261b86 add generating secret key utility 2021-06-28 00:41:14 +08:00
MaysWind 163b75e81b modify method name 2021-06-28 00:39:12 +08:00
MaysWind 949132ef5a modify timezone 2021-06-27 23:15:10 +08:00
MaysWind 5617d31ed8 update build.sh help message 2021-06-23 00:13:17 +08:00
MaysWind 832d865397 modify comments in config file 2021-06-21 00:54:14 +08:00
236 changed files with 20340 additions and 21086 deletions
+13
View File
@@ -0,0 +1,13 @@
module.exports = {
'root': true,
'env': {
'node': true
},
'extends': [
'eslint:recommended',
'plugin:vue/vue3-essential'
],
'rules': {
'vue/no-use-v-if-with-v-for': 'off'
}
}
+50
View File
@@ -0,0 +1,50 @@
name: Docker Release
on:
push:
tags:
- v*
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: |
${{ secrets.DOCKER_REPO }}/mayswind/ezbookkeeping
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest
- name: Set up the environment
run: |
cat >> docker/custom-backend-pre-setup.sh <<EOF
#!/bin/sh
${{ secrets.CUSTOM_BACKEND_PRE_SETUP }}
EOF
cat >> docker/custom-frontend-pre-setup.sh <<EOF
#!/bin/sh
${{ secrets.CUSTOM_FRONTEND_PRE_SETUP }}
EOF
chmod +x docker/custom-backend-pre-setup.sh
chmod +x docker/custom-frontend-pre-setup.sh
- name: Build and push
uses: docker/build-push-action@v4
env:
ACTIONS_RUNTIME_TOKEN: '' # See https://gitea.com/gitea/act_runner/issues/119
with:
file: Dockerfile
context: .
push: true
build-args: |
RELEASE_BUILD=1
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
+49
View File
@@ -0,0 +1,49 @@
name: Docker Snapshot
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: |
${{ secrets.DOCKER_REPO }}/mayswind/ezbookkeeping
tags: |
type=raw,value=SNAPSHOT-{{date 'YYYYMMDD'}}
type=raw,value=latest-snapshot
type=sha,format=short,prefix=SNAPSHOT-
- name: Set up the environment
run: |
sed -i 's#FROM #FROM ${{ secrets.DOCKER_REPO }}/mirrors/#g' Dockerfile
cat >> docker/custom-backend-pre-setup.sh <<EOF
#!/bin/sh
${{ secrets.CUSTOM_BACKEND_PRE_SETUP }}
EOF
cat >> docker/custom-frontend-pre-setup.sh <<EOF
#!/bin/sh
${{ secrets.CUSTOM_FRONTEND_PRE_SETUP }}
EOF
chmod +x docker/custom-backend-pre-setup.sh
chmod +x docker/custom-frontend-pre-setup.sh
- name: Build and push
uses: docker/build-push-action@v4
env:
ACTIONS_RUNTIME_TOKEN: '' # See https://gitea.com/gitea/act_runner/issues/119
with:
file: Dockerfile
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
+32 -23
View File
@@ -9,35 +9,44 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up docker tag
id: vars
run: echo ::set-output name=RELEASE_TAG::${GITHUB_REF/refs\/tags\/v/}
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login to DockerHub
uses: docker/login-action@v1
- name: Checkout
uses: actions/checkout@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: |
${{ secrets.DOCKER_USERNAME }}/ezbookkeeping
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
-
name: Build and push
uses: docker/build-push-action@v2
- name: Build and push
uses: docker/build-push-action@v4
with:
file: Dockerfile
context: .
platforms: linux/amd64,linux/arm64/v8,linux/arm/v7,linux/arm/v6
platforms: |
linux/amd64
linux/arm64/v8
linux/arm/v7
linux/arm/v6
push: true
build-args: |
RELEASE_BUILD=1
tags: |
${{ secrets.DOCKER_USERNAME }}/ezbookkeeping:${{ steps.vars.outputs.RELEASE_TAG }}
${{ secrets.DOCKER_USERNAME }}/ezbookkeeping:latest
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
+31 -23
View File
@@ -9,33 +9,41 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up docker tag
id: vars
run: echo ::set-output name=BUILD_DATE::$(date '+%Y%m%d')
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login to DockerHub
uses: docker/login-action@v1
- name: Checkout
uses: actions/checkout@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: |
${{ secrets.DOCKER_USERNAME }}/ezbookkeeping
tags: |
type=raw,value=SNAPSHOT-{{date 'YYYYMMDD'}}
type=raw,value=latest-snapshot
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
-
name: Build and push
uses: docker/build-push-action@v2
- name: Build and push
uses: docker/build-push-action@v4
with:
file: Dockerfile
context: .
platforms: linux/amd64,linux/arm64/v8,linux/arm/v7,linux/arm/v6
platforms: |
linux/amd64
linux/arm64/v8
linux/arm/v7
linux/arm/v6
push: true
tags: |
${{ secrets.DOCKER_USERNAME }}/ezbookkeeping:SNAPSHOT-${{ steps.vars.outputs.BUILD_DATE }}
${{ secrets.DOCKER_USERNAME }}/ezbookkeeping:latest-snapshot
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
@@ -0,0 +1,28 @@
name: Docker Snapshot
on:
push:
branches-ignore:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
-
name: Build
uses: docker/build-push-action@v2
with:
file: Dockerfile
context: .
platforms: linux/amd64
push: false
+3 -3
View File
@@ -1,5 +1,5 @@
# Build backend binary file
FROM golang:1.16.5-alpine3.13 AS be-builder
FROM golang:1.20.4-alpine3.17 AS be-builder
ARG RELEASE_BUILD
ENV RELEASE_BUILD=$RELEASE_BUILD
WORKDIR /go/src/github.com/mayswind/ezbookkeeping
@@ -9,7 +9,7 @@ RUN apk add git gcc g++ libc-dev
RUN ./build.sh backend
# Build frontend files
FROM node:14.17.0-alpine3.13 AS fe-builder
FROM node:18.16.0-alpine3.17 AS fe-builder
ARG RELEASE_BUILD
ENV RELEASE_BUILD=$RELEASE_BUILD
WORKDIR /go/src/github.com/mayswind/ezbookkeeping
@@ -19,7 +19,7 @@ RUN apk add git
RUN ./build.sh frontend
# Package docker image
FROM alpine:3.13.5
FROM alpine:3.17.3
LABEL maintainer="MaysWind <i@mayswind.net>"
RUN addgroup -S -g 1000 ezbookkeeping && adduser -S -G ezbookkeeping -u 1000 ezbookkeeping
RUN apk --no-cache add tzdata
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2020-2021 MaysWind (i@mayswind.net)
Copyright (c) 2020-2023 MaysWind (i@mayswind.net)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+1 -1
View File
@@ -1,6 +1,6 @@
# ezBookkeeping
[![License](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/mayswind/ezbookkeeping/blob/master/LICENSE)
[![Latest Build](https://img.shields.io/github/workflow/status/mayswind/ezbookkeeping/Docker%20Release?style=flat)](https://github.com/mayswind/ezbookkeeping/actions)
[![Latest Build](https://img.shields.io/github/actions/workflow/status/mayswind/ezbookkeeping/docker-snapshot.yml?branch=main)](https://github.com/mayswind/ezbookkeeping/actions)
[![Go Report](https://goreportcard.com/badge/github.com/mayswind/ezbookkeeping)](https://goreportcard.com/report/github.com/mayswind/ezbookkeeping)
[![Latest Docker Image Size](https://img.shields.io/docker/image-size/mayswind/ezbookkeeping.svg?style=flat)](https://hub.docker.com/r/mayswind/ezbookkeeping)
[![Latest Release](https://img.shields.io/github/release/mayswind/ezbookkeeping.svg?style=flat)](https://github.com/mayswind/ezbookkeeping/releases)
-5
View File
@@ -1,5 +0,0 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}
+57 -15
View File
@@ -1,6 +1,8 @@
#!/usr/bin/env sh
TYPE=""
NO_LINT="0"
NO_TEST="0"
RELEASE=${RELEASE_BUILD:-"0"}
RELEASE_TYPE="unknown"
VERSION=""
@@ -31,16 +33,18 @@ Usage:
build.sh type [options]
Types:
backend Build backend binary file
frontend Build frontend files
package Build package archive
docker Build docker image
backend Build backend binary file
frontend Build frontend files
package Build package archive
docker Build docker image
Options:
-r, --release Build release (The script will use environment variable "RELEASE_BUILD" to detect whether this is release building by default)
-o, --output Package file name (For "package" type only)
-t, --tag Docker tag (For "docker" type only)
-h, --help Show help
-r, --release Build release (The script will use environment variable "RELEASE_BUILD" to detect whether this is release building by default)
-o, --output <filename> Package file name (For "package" type only)
-t, --tag Docker tag (For "docker" type only)
--no-lint Do not execute lint check before building
--no-test Do not execute unit testing before building
-h, --help Show help
EOF
}
@@ -63,6 +67,12 @@ parse_args() {
DOCKER_TAG="$2"
shift
;;
--no-lint)
NO_LINT="1"
;;
--no-test)
NO_TEST="1"
;;
--help | -h)
show_help
exit 0
@@ -111,6 +121,27 @@ set_build_parameters() {
}
build_backend() {
if [ "$NO_LINT" = "0" ]; then
echo "Executing backend lint checking..."
go vet -v ./...
if [ "$?" != "0" ]; then
echo_red "Error: Failed to pass lint checking"
exit 1
fi
fi
if [ "$NO_TEST" = "0" ]; then
echo "Executing backend unit testing..."
go clean -cache
go test ./... -v
if [ "$?" != "0" ]; then
echo_red "Error: Failed to pass unit testing"
exit 1
fi
fi
backend_build_extra_arguments="-X main.Version=$VERSION"
backend_build_extra_arguments="$backend_build_extra_arguments -X main.CommitHash=$COMMIT_HASH"
@@ -125,17 +156,26 @@ build_backend() {
}
build_frontend() {
frontend_build_arguments="";
if [ "$RELEASE" = "0" ]; then
frontend_build_arguments="--buildUnixTime=$BUILD_UNIXTIME"
fi
echo "Pulling frontend dependencies..."
npm install
if [ "$NO_LINT" = "0" ]; then
echo "Executing frontend lint checking..."
npm run lint
if [ "$?" != "0" ]; then
echo_red "Error: Failed to pass lint checking"
exit 1
fi
fi
echo "Building frontend files ($RELEASE_TYPE)..."
npm run build -- "$frontend_build_arguments"
if [ "$RELEASE" = "0" ]; then
buildUnixTime=$BUILD_UNIXTIME npm run build
else
npm run build
fi
}
build_package() {
@@ -158,6 +198,8 @@ build_package() {
rm -rf package
mkdir package
mkdir package/data
mkdir package/log
cp ezbookkeeping package/
cp -R dist package/public
cp -R conf package/conf
+49
View File
@@ -0,0 +1,49 @@
package cmd
import (
"fmt"
"github.com/urfave/cli/v2"
"github.com/mayswind/ezbookkeeping/pkg/utils"
)
// SecurityUtils represents the security command
var SecurityUtils = &cli.Command{
Name: "security",
Usage: "ezBookkeeping security utilities",
Subcommands: []*cli.Command{
{
Name: "gen-secret-key",
Usage: "Generate a random secret key",
Action: genSecretKey,
Flags: []cli.Flag{
&cli.IntFlag{
Name: "length",
Aliases: []string{"l"},
Required: false,
DefaultText: "32",
Usage: "The length of secret key",
},
},
},
},
}
func genSecretKey(c *cli.Context) error {
length := c.Int("length")
if length <= 0 {
length = 32
}
secretKey, err := utils.GetRandomNumberOrLetter(length)
if err != nil {
return err
}
fmt.Printf("[Secret Key] %s\n", secretKey)
return nil
}
+8 -2
View File
@@ -398,9 +398,15 @@ func printUserInfo(user *models.User) {
fmt.Printf("[Nickname] %s\n", user.Nickname)
fmt.Printf("[Password] %s\n", user.Password)
fmt.Printf("[Salt] %s\n", user.Salt)
fmt.Printf("[DefaultAccountId] %d\n", user.DefaultAccountId)
fmt.Printf("[TransactionEditScope] %s (%d)\n", user.TransactionEditScope, user.TransactionEditScope)
fmt.Printf("[Language] %s\n", user.Language)
fmt.Printf("[DefaultCurrency] %s\n", user.DefaultCurrency)
fmt.Printf("[FirstDayOfWeek] %s\n", user.FirstDayOfWeek)
fmt.Printf("[TransactionEditScope] %s\n", user.TransactionEditScope)
fmt.Printf("[FirstDayOfWeek] %s (%d)\n", user.FirstDayOfWeek, user.FirstDayOfWeek)
fmt.Printf("[LongDateFormat] %s (%d)\n", user.LongDateFormat, user.LongDateFormat)
fmt.Printf("[ShortDateFormat] %s (%d)\n", user.ShortDateFormat, user.ShortDateFormat)
fmt.Printf("[LongTimeFormat] %s (%d)\n", user.LongTimeFormat, user.LongTimeFormat)
fmt.Printf("[ShortTimeFormat] %s (%d)\n", user.ShortTimeFormat, user.ShortTimeFormat)
fmt.Printf("[Deleted] %t\n", user.Deleted)
fmt.Printf("[EmailVerified] %t\n", user.EmailVerified)
fmt.Printf("[CreatedAt] %s (%d)\n", utils.FormatUnixTimeToLongDateTimeInServerTimezone(user.CreatedUnixTime), user.CreatedUnixTime)
+40 -10
View File
@@ -110,13 +110,17 @@ func startWebServer(c *cli.Context) error {
router.Static("/mobile/css", filepath.Join(config.StaticRootPath, "css"))
router.Static("/mobile/img", filepath.Join(config.StaticRootPath, "img"))
router.Static("/mobile/fonts", filepath.Join(config.StaticRootPath, "fonts"))
router.Static("/mobile/sw", filepath.Join(config.StaticRootPath, "sw"))
router.StaticFile("/mobile/favicon.ico", filepath.Join(config.StaticRootPath, "favicon.ico"))
router.StaticFile("/mobile/favicon.png", filepath.Join(config.StaticRootPath, "favicon.png"))
router.StaticFile("/mobile/touchicon.png", filepath.Join(config.StaticRootPath, "touchicon.png"))
router.StaticFile("/mobile/manifest.json", filepath.Join(config.StaticRootPath, "manifest.json"))
router.StaticFile("/mobile/sw.js", filepath.Join(config.StaticRootPath, "sw.js"))
workboxFileNames := utils.ListFileNamesWithPrefixAndSuffix(config.StaticRootPath, "workbox-", ".js")
for i := 0; i < len(workboxFileNames); i++ {
router.StaticFile("/mobile/"+workboxFileNames[i], filepath.Join(config.StaticRootPath, workboxFileNames[i]))
}
desktopEntryRoute := router.Group("/desktop")
desktopEntryRoute.Use(bindMiddleware(middlewares.ServerSettingsCookie(config)))
{
@@ -131,6 +135,23 @@ func startWebServer(c *cli.Context) error {
router.StaticFile("/desktop/touchicon.png", filepath.Join(config.StaticRootPath, "touchicon.png"))
router.StaticFile("/desktop/manifest.json", filepath.Join(config.StaticRootPath, "manifest.json"))
router.GET("/healthz.json", bindApi(api.Healths.HealthStatusHandler))
if config.Mode == settings.MODE_DEVELOPMENT {
devRoute := router.Group("/dev")
devRoute.GET("/cookies", bindMiddleware(middlewares.ServerSettingsCookie(config)))
}
proxyRoute := router.Group("/proxy")
proxyRoute.Use(bindMiddleware(middlewares.JWTAuthorizationByQueryString))
{
if config.EnableMapDataFetchProxy {
if config.MapProvider == settings.OpenStreetMapProvider {
proxyRoute.GET("/openstreetmap/tile/:zoomLevel/:coordinateX/:fileName", bindProxy(api.MapImages.OpenStreetMapTileImageProxyHandler))
}
}
}
apiRoute := router.Group("/api")
apiRoute.Use(bindMiddleware(middlewares.RequestId(config)))
@@ -151,15 +172,6 @@ func startWebServer(c *cli.Context) error {
apiRoute.POST("/register.json", bindApi(api.Users.UserRegisterHandler))
}
if config.EnableDataExport {
dataRoute := apiRoute.Group("/data")
dataRoute.Use(bindMiddleware(middlewares.HeaderInQueryString))
dataRoute.Use(bindMiddleware(middlewares.JWTAuthorizationByQueryString))
{
dataRoute.GET("/export.csv", bindCsv(api.DataManagements.ExportDataHandler))
}
}
apiRoute.GET("/logout.json", bindApi(api.Tokens.TokenRevokeCurrentHandler))
apiV1Route := apiRoute.Group("/v1")
@@ -185,8 +197,13 @@ func startWebServer(c *cli.Context) error {
}
// Data
apiV1Route.GET("/data/statistics.json", bindApi(api.DataManagements.DataStatisticsHandler))
apiV1Route.POST("/data/clear.json", bindApi(api.DataManagements.ClearDataHandler))
if config.EnableDataExport {
apiV1Route.GET("/data/export.csv", bindCsv(api.DataManagements.ExportDataHandler))
}
// Accounts
apiV1Route.GET("/accounts/list.json", bindApi(api.Accounts.AccountListHandler))
apiV1Route.GET("/accounts/get.json", bindApi(api.Accounts.AccountGetHandler))
@@ -286,3 +303,16 @@ func bindCsv(fn core.DataHandlerFunc) gin.HandlerFunc {
}
}
}
func bindProxy(fn core.ProxyHandlerFunc) gin.HandlerFunc {
return func(ginCtx *gin.Context) {
c := core.WrapContext(ginCtx)
proxy, err := fn(c)
if err != nil {
utils.PrintDataErrorResult(c, "text/text", err)
} else {
proxy.ServeHTTP(c.Writer, c.Request)
}
}
}
+26 -13
View File
@@ -28,13 +28,13 @@ cert_key_file =
# Unix socket path, for "socket" only
unix_socket =
# Static file root path (relative or absolute)
# Static file root path (relative or absolute path)
static_root_path = public
# Enable GZip
enable_gzip = false
# Set to true to log each request and execution times
# Set to true to log each request and execution time
log_request = true
[database]
@@ -50,19 +50,19 @@ passwd =
# For "postgres" only, Either "disable", "require" or "verify-full"
ssl_mode = disable
# For "sqlite3" only, absolute path of db file
# For "sqlite3" only, db file path (relative or absolute path)
db_path = data/ezbookkeeping.db
# Max idle connection number, default is 2
# Max idle connection number (0 - 65535, 0 means no idle connections are retained), default is 2
max_idle_conn = 2
# Max opened connection number, default is 0 (unlimited)
# Max opened connection number (0 - 65535), default is 0 (unlimited)
max_open_conn = 0
# Max connection lifetime (seconds), default is 14400 (4 hours)
# Max connection lifetime (0 - 4294967295 seconds), default is 14400 (4 hours)
conn_max_lifetime = 14400
# Set to true to log each sql statement and execution times
# Set to true to log each sql statement and execution time
log_query = false
# Set to true to automatically update database structure when starting web server
@@ -76,14 +76,14 @@ mode = console file
# Either "debug", "info", "warn", "error", default is "info"
level = info
# For "file" only, absolute path of log file
# For "file" only, log file path (relative or absolute path)
log_path = log/ezbookkeeping.log
[uuid]
# Uuid generator type, supports "internal" currently
generator_type = internal
# For "internal" only, each server must have unique id
# For "internal" only, each server must have unique id (0 - 255)
server_id = 0
[security]
@@ -93,10 +93,10 @@ secret_key =
# Set to true to enable two factor authorization
enable_two_factor = true
# Token expired seconds, default is 2592000 (30 days)
# Token expired seconds (0 - 4294967295), default is 2592000 (30 days)
token_expired_time = 2592000
# Temporary token expired seconds, default is 300 (5 minutes)
# Temporary token expired seconds (0 - 4294967295), default is 300 (5 minutes)
temporary_token_expired_time = 300
# Add X-Request-Id header to response to track user request or error, default is true
@@ -110,9 +110,22 @@ enable_register = true
# Set to true to allow users to export their data
enable_export = true
[map]
# Map provider, supports `openstreetmap`
map_provider = openstreetmap
# Set to true to use the ezbookkeeping server to proxy map data requests, for "openstreetmap"
map_data_fetch_proxy = false
[exchange_rates]
# Exchange rates data source, supports "euro_central_bank", "bank_of_canada", "reserve_bank_of_australia", "czech_national_bank", "national_bank_of_poland" currently
# Exchange rates data source, supports the following types:
# "euro_central_bank"
# "bank_of_canada"
# "reserve_bank_of_australia",
# "czech_national_bank"
# "national_bank_of_poland"
# "monetary_authority_of_singapore"
data_source = euro_central_bank
# Requesting exchange rates data timeout (milliseconds), default is 10000 (10 seconds)
# Requesting exchange rates data timeout (0 - 4294967295 milliseconds), default is 10000 (10 seconds)
request_timeout = 10000
+5
View File
@@ -9,6 +9,7 @@ import (
"github.com/urfave/cli/v2"
"github.com/mayswind/ezbookkeeping/cmd"
"github.com/mayswind/ezbookkeeping/pkg/settings"
"github.com/mayswind/ezbookkeeping/pkg/utils"
)
@@ -24,6 +25,9 @@ var (
)
func main() {
settings.Version = Version
settings.CommitHash = CommitHash
app := &cli.App{
Name: "ezBookkeeping",
Usage: "A lightweight personal bookkeeping app hosted by yourself.",
@@ -32,6 +36,7 @@ func main() {
cmd.WebServer,
cmd.Database,
cmd.UserData,
cmd.SecurityUtils,
},
Flags: []cli.Flag{
&cli.StringFlag{
+49 -16
View File
@@ -1,21 +1,54 @@
module github.com/mayswind/ezbookkeeping
go 1.14
go 1.20
require (
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/gin-contrib/gzip v0.0.3
github.com/gin-gonic/gin v1.6.3
github.com/go-playground/validator/v10 v10.4.1
github.com/go-sql-driver/mysql v1.5.0
github.com/lib/pq v1.8.0
github.com/mattn/go-sqlite3 v1.14.4
github.com/pquerna/otp v1.3.0
github.com/sirupsen/logrus v1.7.0
github.com/smartystreets/goconvey v1.6.4 // indirect
github.com/stretchr/testify v1.6.1
github.com/urfave/cli/v2 v2.3.0
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897
gopkg.in/ini.v1 v1.62.0
xorm.io/xorm v1.0.5
github.com/gin-contrib/gzip v0.0.6
github.com/gin-gonic/gin v1.9.0
github.com/go-playground/validator/v10 v10.14.0
github.com/go-sql-driver/mysql v1.7.1
github.com/golang-jwt/jwt/v5 v5.0.0
github.com/lib/pq v1.10.9
github.com/mattn/go-sqlite3 v1.14.16
github.com/pquerna/otp v1.4.0
github.com/sirupsen/logrus v1.9.0
github.com/stretchr/testify v1.8.2
github.com/urfave/cli/v2 v2.25.4
golang.org/x/crypto v0.9.0
gopkg.in/ini.v1 v1.67.0
xorm.io/xorm v1.3.2
)
require (
github.com/boombuler/barcode v1.0.1 // indirect
github.com/bytedance/sonic v1.8.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/goccy/go-json v0.10.0 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mattn/go-isatty v0.0.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.6 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.9 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
xorm.io/builder v0.3.12 // indirect
)
+652 -80
View File
@@ -1,141 +1,713 @@
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
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=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
gitee.com/travelliu/dm v1.8.11192/go.mod h1:DHTzyhCrM843x9VdKVbZ+GKXGRbKM2sJ4LxihRxShkE=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA=
github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
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/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gin-contrib/gzip v0.0.3 h1:etUaeesHhEORpZMp18zoOhepboiWnFtXrBZxszWUn4k=
github.com/gin-contrib/gzip v0.0.3/go.mod h1:YxxswVZIqOvcHEQpsSn+QF5guQtO1dCfy0shBPy4jFc=
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/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
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-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
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.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.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
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/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
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.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
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.5.3/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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
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/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk=
github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
github.com/jackc/pgconn v1.8.1/go.mod h1:JV6m6b6jhjdmzchES0drzCcYcAHS1OPD5xu3OZ/lE2g=
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0=
github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po=
github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ=
github.com/jackc/pgtype v1.7.0/go.mod h1:ZnHF+rMePVqDKaOfJVI4Q8IVvAQMryDlDkZnKOI75BE=
github.com/jackc/pgtype v1.8.0/go.mod h1:PqDKcEBtllAtk/2p6z6SHdXW5UB+MhE75tUol2OKexE=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA=
github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o=
github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg=
github.com/jackc/pgx/v4 v4.11.0/go.mod h1:i62xJgdrtVDsnL3U8ekyrQXEwGNTRoG7/8r+CIdYfcc=
github.com/jackc/pgx/v4 v4.12.0/go.mod h1:fE547h6VulLPA3kySjfnSG/e2D861g/50JlVUa/ub60=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.8/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/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
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/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
github.com/mattn/go-sqlite3 v1.14.4 h1:4rQjbDxdu9fSgI/r3KN72G3c2goxknAqHHgPWWs8UlI=
github.com/mattn/go-sqlite3 v1.14.4/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
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.1/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/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
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/pquerna/otp v1.3.0 h1:oJV/SkzR33anKXwQU3Of42rL4wbrffP4uvUf1SvS5Xs=
github.com/pquerna/otp v1.3.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
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/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
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.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
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 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
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.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU=
github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli/v2 v2.25.4 h1:HyYwPrTO3im9rYhUff/ZNs78eolxt0nJ4LN+9yJKSH4=
github.com/urfave/cli/v2 v2.25.4/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
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-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/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-20190125091013-d26f9f9a57f3/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-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
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/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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/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-20201126233918-771906719818/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-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
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.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/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-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
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-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/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-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
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-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
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.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
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/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
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=
xorm.io/builder v0.3.7 h1:2pETdKRK+2QG4mLX4oODHEhn5Z8j1m8sXa7jfu+/SZI=
xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
xorm.io/xorm v1.0.5 h1:LRr5PfOUb4ODPR63YwbowkNDwcolT2LnkwP/TUaMaB0=
xorm.io/xorm v1.0.5/go.mod h1:uF9EtbhODq5kNWxMbnBEj8hRRZnlcNSz2t2N7HW/+A4=
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=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/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=
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.33.9/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.33.11/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.34.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.4/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.5/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.7/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.8/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.10/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.15/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.16/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.17/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.18/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60=
modernc.org/ccgo/v3 v3.10.0/go.mod h1:c0yBmkRFi7uW4J7fwx/JiijwOjeAeR2NoSaRVFPmjMw=
modernc.org/ccgo/v3 v3.11.0/go.mod h1:dGNposbDp9TOZ/1KBxghxtUp/bzErD0/0QW4hhSaBMI=
modernc.org/ccgo/v3 v3.11.1/go.mod h1:lWHxfsn13L3f7hgGsGlU28D9eUOf6y3ZYHKoPaKU0ag=
modernc.org/ccgo/v3 v3.11.3/go.mod h1:0oHunRBMBiXOKdaglfMlRPBALQqsfrCKXgw9okQ3GEw=
modernc.org/ccgo/v3 v3.12.4/go.mod h1:Bk+m6m2tsooJchP/Yk5ji56cClmN6R1cqc9o/YtbgBQ=
modernc.org/ccgo/v3 v3.12.6/go.mod h1:0Ji3ruvpFPpz+yu+1m0wk68pdr/LENABhTrDkMDWH6c=
modernc.org/ccgo/v3 v3.12.8/go.mod h1:Hq9keM4ZfjCDuDXxaHptpv9N24JhgBZmUG5q60iLgUo=
modernc.org/ccgo/v3 v3.12.11/go.mod h1:0jVcmyDwDKDGWbcrzQ+xwJjbhZruHtouiBEvDfoIsdg=
modernc.org/ccgo/v3 v3.12.14/go.mod h1:GhTu1k0YCpJSuWwtRAEHAol5W7g1/RRfS4/9hc9vF5I=
modernc.org/ccgo/v3 v3.12.18/go.mod h1:jvg/xVdWWmZACSgOiAhpWpwHWylbJaSzayCqNOJKIhs=
modernc.org/ccgo/v3 v3.12.20/go.mod h1:aKEdssiu7gVgSy/jjMastnv/q6wWGRbszbheXgWRHc8=
modernc.org/ccgo/v3 v3.12.21/go.mod h1:ydgg2tEprnyMn159ZO/N4pLBqpL7NOkJ88GT5zNU2dE=
modernc.org/ccgo/v3 v3.12.22/go.mod h1:nyDVFMmMWhMsgQw+5JH6B6o4MnZ+UQNw1pp52XYFPRk=
modernc.org/ccgo/v3 v3.12.25/go.mod h1:UaLyWI26TwyIT4+ZFNjkyTbsPsY3plAEB6E7L/vZV3w=
modernc.org/ccgo/v3 v3.12.29/go.mod h1:FXVjG7YLf9FetsS2OOYcwNhcdOLGt8S9bQ48+OP75cE=
modernc.org/ccgo/v3 v3.12.36/go.mod h1:uP3/Fiezp/Ga8onfvMLpREq+KUjUmYMxXPO8tETHtA8=
modernc.org/ccgo/v3 v3.12.38/go.mod h1:93O0G7baRST1vNj4wnZ49b1kLxt0xCW5Hsa2qRaZPqc=
modernc.org/ccgo/v3 v3.12.43/go.mod h1:k+DqGXd3o7W+inNujK15S5ZYuPoWYLpF5PYougCmthU=
modernc.org/ccgo/v3 v3.12.46/go.mod h1:UZe6EvMSqOxaJ4sznY7b23/k13R8XNlyWsO5bAmSgOE=
modernc.org/ccgo/v3 v3.12.47/go.mod h1:m8d6p0zNps187fhBwzY/ii6gxfjob1VxWb919Nk1HUk=
modernc.org/ccgo/v3 v3.12.50/go.mod h1:bu9YIwtg+HXQxBhsRDE+cJjQRuINuT9PUK4orOco/JI=
modernc.org/ccgo/v3 v3.12.51/go.mod h1:gaIIlx4YpmGO2bLye04/yeblmvWEmE4BBBls4aJXFiE=
modernc.org/ccgo/v3 v3.12.53/go.mod h1:8xWGGTFkdFEWBEsUmi+DBjwu/WLy3SSOrqEmKUjMeEg=
modernc.org/ccgo/v3 v3.12.54/go.mod h1:yANKFTm9llTFVX1FqNKHE0aMcQb1fuPJx6p8AcUx+74=
modernc.org/ccgo/v3 v3.12.55/go.mod h1:rsXiIyJi9psOwiBkplOaHye5L4MOOaCjHg1Fxkj7IeU=
modernc.org/ccgo/v3 v3.12.56/go.mod h1:ljeFks3faDseCkr60JMpeDb2GSO3TKAmrzm7q9YOcMU=
modernc.org/ccgo/v3 v3.12.57/go.mod h1:hNSF4DNVgBl8wYHpMvPqQWDQx8luqxDnNGCMM4NFNMc=
modernc.org/ccgo/v3 v3.12.60/go.mod h1:k/Nn0zdO1xHVWjPYVshDeWKqbRWIfif5dtsIOCUVMqM=
modernc.org/ccgo/v3 v3.12.65/go.mod h1:D6hQtKxPNZiY6wDBtehSGKFKmyXn53F8nGTpH+POmS4=
modernc.org/ccgo/v3 v3.12.66/go.mod h1:jUuxlCFZTUZLMV08s7B1ekHX5+LIAurKTTaugUr/EhQ=
modernc.org/ccgo/v3 v3.12.67/go.mod h1:Bll3KwKvGROizP2Xj17GEGOTrlvB1XcVaBrC90ORO84=
modernc.org/ccgo/v3 v3.12.73/go.mod h1:hngkB+nUUqzOf3iqsM48Gf1FZhY599qzVg1iX+BT3cQ=
modernc.org/ccgo/v3 v3.12.81/go.mod h1:p2A1duHoBBg1mFtYvnhAnQyI6vL0uw5PGYLSIgF6rYY=
modernc.org/ccgo/v3 v3.12.82/go.mod h1:ApbflUfa5BKadjHynCficldU1ghjen84tuM5jRynB7w=
modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q=
modernc.org/libc v1.11.0/go.mod h1:2lOfPmj7cz+g1MrPNmX65QCzVxgNq2C5o0jdLY2gAYg=
modernc.org/libc v1.11.2/go.mod h1:ioIyrl3ETkugDO3SGZ+6EOKvlP3zSOycUETe4XM4n8M=
modernc.org/libc v1.11.5/go.mod h1:k3HDCP95A6U111Q5TmG3nAyUcp3kR5YFZTeDS9v8vSU=
modernc.org/libc v1.11.6/go.mod h1:ddqmzR6p5i4jIGK1d/EiSw97LBcE3dK24QEwCFvgNgE=
modernc.org/libc v1.11.11/go.mod h1:lXEp9QOOk4qAYOtL3BmMve99S5Owz7Qyowzvg6LiZso=
modernc.org/libc v1.11.13/go.mod h1:ZYawJWlXIzXy2Pzghaf7YfM8OKacP3eZQI81PDLFdY8=
modernc.org/libc v1.11.16/go.mod h1:+DJquzYi+DMRUtWI1YNxrlQO6TcA5+dRRiq8HWBWRC8=
modernc.org/libc v1.11.19/go.mod h1:e0dgEame6mkydy19KKaVPBeEnyJB4LGNb0bBH1EtQ3I=
modernc.org/libc v1.11.24/go.mod h1:FOSzE0UwookyT1TtCJrRkvsOrX2k38HoInhw+cSCUGk=
modernc.org/libc v1.11.26/go.mod h1:SFjnYi9OSd2W7f4ct622o/PAYqk7KHv6GS8NZULIjKY=
modernc.org/libc v1.11.27/go.mod h1:zmWm6kcFXt/jpzeCgfvUNswM0qke8qVwxqZrnddlDiE=
modernc.org/libc v1.11.28/go.mod h1:Ii4V0fTFcbq3qrv3CNn+OGHAvzqMBvC7dBNyC4vHZlg=
modernc.org/libc v1.11.31/go.mod h1:FpBncUkEAtopRNJj8aRo29qUiyx5AvAlAxzlx9GNaVM=
modernc.org/libc v1.11.34/go.mod h1:+Tzc4hnb1iaX/SKAutJmfzES6awxfU1BPvrrJO0pYLg=
modernc.org/libc v1.11.37/go.mod h1:dCQebOwoO1046yTrfUE5nX1f3YpGZQKNcITUYWlrAWo=
modernc.org/libc v1.11.39/go.mod h1:mV8lJMo2S5A31uD0k1cMu7vrJbSA3J3waQJxpV4iqx8=
modernc.org/libc v1.11.42/go.mod h1:yzrLDU+sSjLE+D4bIhS7q1L5UwXDOw99PLSX0BlZvSQ=
modernc.org/libc v1.11.44/go.mod h1:KFq33jsma7F5WXiYelU8quMJasCCTnHK0mkri4yPHgA=
modernc.org/libc v1.11.45/go.mod h1:Y192orvfVQQYFzCNsn+Xt0Hxt4DiO4USpLNXBlXg/tM=
modernc.org/libc v1.11.47/go.mod h1:tPkE4PzCTW27E6AIKIR5IwHAQKCAtudEIeAV1/SiyBg=
modernc.org/libc v1.11.49/go.mod h1:9JrJuK5WTtoTWIFQ7QjX2Mb/bagYdZdscI3xrvHbXjE=
modernc.org/libc v1.11.51/go.mod h1:R9I8u9TS+meaWLdbfQhq2kFknTW0O3aw3kEMqDDxMaM=
modernc.org/libc v1.11.53/go.mod h1:5ip5vWYPAoMulkQ5XlSJTy12Sz5U6blOQiYasilVPsU=
modernc.org/libc v1.11.54/go.mod h1:S/FVnskbzVUrjfBqlGFIPA5m7UwB3n9fojHhCNfSsnw=
modernc.org/libc v1.11.55/go.mod h1:j2A5YBRm6HjNkoSs/fzZrSxCuwWqcMYTDPLNx0URn3M=
modernc.org/libc v1.11.56/go.mod h1:pakHkg5JdMLt2OgRadpPOTnyRXm/uzu+Yyg/LSLdi18=
modernc.org/libc v1.11.58/go.mod h1:ns94Rxv0OWyoQrDqMFfWwka2BcaF6/61CqJRK9LP7S8=
modernc.org/libc v1.11.70/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw=
modernc.org/libc v1.11.71/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw=
modernc.org/libc v1.11.75/go.mod h1:dGRVugT6edz361wmD9gk6ax1AbDSe0x5vji0dGJiPT0=
modernc.org/libc v1.11.82/go.mod h1:NF+Ek1BOl2jeC7lw3a7Jj5PWyHPwWD4aq3wVKxqV1fI=
modernc.org/libc v1.11.86/go.mod h1:ePuYgoQLmvxdNT06RpGnaDKJmDNEkV7ZPKI2jnsvZoE=
modernc.org/libc v1.11.87/go.mod h1:Qvd5iXTeLhI5PS0XSyqMY99282y+3euapQFxM7jYnpY=
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc=
modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM=
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.14.2/go.mod h1:yqfn85u8wVOE6ub5UT8VI9JjhrwBUUCNyTACN0h6Sx8=
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
modernc.org/tcl v1.8.13/go.mod h1:V+q/Ef0IJaNUSECieLU4o+8IScapxnMyFV6i/7uQlAY=
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.2.19/go.mod h1:+ZpP0pc4zz97eukOzW3xagV/lS82IpPN9NGG5pNF9vY=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
xorm.io/builder v0.3.12 h1:ASZYX7fQmy+o8UJdhlLHSW57JDOkM8DNhcAF5d0LiJM=
xorm.io/builder v0.3.12/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
xorm.io/xorm v1.3.2 h1:uTRRKF2jYzbZ5nsofXVUx6ncMaek+SHjWYtCXyZo1oM=
xorm.io/xorm v1.3.2/go.mod h1:9NbjqdnjX6eyjRRhh01GHm64r6N9shTb/8Ak3YRt8Nw=
+6522 -12569
View File
File diff suppressed because it is too large Load Diff
+32 -44
View File
@@ -1,6 +1,6 @@
{
"name": "ezbookkeeping",
"version": "0.1.0",
"version": "0.3.0",
"private": true,
"repository": {
"type": "git",
@@ -12,56 +12,44 @@
"url": "https://github.com/mayswind/ezbookkeeping/issues"
},
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
"serve": "cross-env NODE_ENV=development vite",
"build": "cross-env NODE_ENV=production vite build",
"serve:dist": "vite preview",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore"
},
"dependencies": {
"axios": "^0.21.1",
"@vuepic/vue-datepicker": "^5.1.2",
"axios": "^1.4.0",
"cbor-js": "^0.1.0",
"core-js": "^3.6.5",
"crypto-js": "^4.0.0",
"framework7": "^5.7.14",
"framework7-icons": "^3.0.1",
"framework7-vue": "^5.7.14",
"js-cookie": "^2.2.1",
"clipboard": "^2.0.11",
"crypto-js": "^4.1.1",
"dom7": "^4.0.6",
"framework7": "^8.0.5",
"framework7-icons": "^5.0.5",
"framework7-vue": "^8.0.5",
"js-cookie": "^3.0.5",
"leaflet": "^1.9.4",
"line-awesome": "^1.3.0",
"moment": "^2.29.1",
"moment-timezone": "^0.5.33",
"moment": "^2.29.4",
"moment-timezone": "^0.5.43",
"register-service-worker": "^1.7.2",
"ua-parser-js": "^0.7.28",
"vue": "^2.6.12",
"vue-clipboard2": "^0.3.1",
"vue-i18n": "^8.24.3",
"vue-pincode-input": "^0.4.0",
"vuex": "^3.6.2"
"skeleton-elements": "^4.0.1",
"swiper": "^9.3.2",
"ua-parser-js": "^1.0.35",
"vue": "^3.3.4",
"vue-i18n": "^9.2.2",
"vuex": "^4.1.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.5.11",
"@vue/cli-plugin-eslint": "^4.5.11",
"@vue/cli-plugin-pwa": "^4.5.11",
"@vue/cli-service": "^4.5.11",
"babel-eslint": "^10.1.0",
"babel-plugin-component": "^1.1.1",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
"git-revision-webpack-plugin": "^3.0.6",
"moment-locales-webpack-plugin": "^1.2.0",
"vue-template-compiler": "^2.6.12"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
"@vitejs/plugin-vue": "^4.2.0",
"@vue/compiler-sfc": "^3.3.4",
"cross-env": "^7.0.3",
"eslint": "^8.41.0",
"eslint-plugin-vue": "^9.14.1",
"git-rev-sync": "^3.0.2",
"postcss-preset-env": "^8.4.1",
"vite": "^4.3.5",
"vite-plugin-pwa": "^0.15.1"
},
"browserslist": [
"> 1%",
+6 -6
View File
@@ -38,7 +38,7 @@ func (a *AccountsApi) AccountListHandler(c *core.Context) (interface{}, *errs.Er
if err != nil {
log.ErrorfWithRequestId(c, "[accounts.AccountListHandler] failed to get all accounts for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.ErrOperationFailed
return nil, errs.Or(err, errs.ErrOperationFailed)
}
userAllAccountResps := make([]*models.AccountInfoResponse, len(accounts))
@@ -98,7 +98,7 @@ func (a *AccountsApi) AccountGetHandler(c *core.Context) (interface{}, *errs.Err
if err != nil {
log.ErrorfWithRequestId(c, "[accounts.AccountGetHandler] failed to get account \"id:%d\" for user \"uid:%d\", because %s", accountGetReq.Id, uid, err.Error())
return nil, errs.ErrOperationFailed
return nil, errs.Or(err, errs.ErrOperationFailed)
}
accountRespMap := make(map[int64]*models.AccountInfoResponse)
@@ -190,7 +190,7 @@ func (a *AccountsApi) AccountCreateHandler(c *core.Context) (interface{}, *errs.
if err != nil {
log.ErrorfWithRequestId(c, "[accounts.AccountCreateHandler] failed to get max display order for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.ErrOperationFailed
return nil, errs.Or(err, errs.ErrOperationFailed)
}
mainAccount := a.createNewAccountModel(uid, &accountCreateReq, maxOrderId+1)
@@ -233,7 +233,7 @@ func (a *AccountsApi) AccountModifyHandler(c *core.Context) (interface{}, *errs.
if err != nil {
log.ErrorfWithRequestId(c, "[accounts.AccountModifyHandler] failed to get account \"id:%d\" for user \"uid:%d\", because %s", accountModifyReq.Id, uid, err.Error())
return nil, errs.ErrOperationFailed
return nil, errs.Or(err, errs.ErrOperationFailed)
}
accountMap := a.accounts.GetAccountMapByList(accountAndSubAccounts)
@@ -403,7 +403,7 @@ func (a *AccountsApi) AccountDeleteHandler(c *core.Context) (interface{}, *errs.
return true, nil
}
func (a *AccountsApi) createNewAccountModel(uid int64, accountCreateReq *models.AccountCreateRequest, order int) *models.Account {
func (a *AccountsApi) createNewAccountModel(uid int64, accountCreateReq *models.AccountCreateRequest, order int32) *models.Account {
return &models.Account{
Uid: uid,
Name: accountCreateReq.Name,
@@ -425,7 +425,7 @@ func (a *AccountsApi) createSubAccountModels(uid int64, accountCreateReq *models
childrenAccounts := make([]*models.Account, len(accountCreateReq.SubAccounts))
for i := 0; i < len(accountCreateReq.SubAccounts); i++ {
for i := int32(0); i < int32(len(accountCreateReq.SubAccounts)); i++ {
childrenAccounts[i] = a.createNewAccountModel(uid, accountCreateReq.SubAccounts[i], i+1)
}
+44 -3
View File
@@ -118,6 +118,47 @@ func (a *DataManagementsApi) ExportDataHandler(c *core.Context) ([]byte, string,
return result, fileName, nil
}
// DataStatisticsHandler returns user data statistics
func (a *DataManagementsApi) DataStatisticsHandler(c *core.Context) (interface{}, *errs.Error) {
uid := c.GetCurrentUid()
totalAccountCount, err := a.accounts.GetTotalAccountCountByUid(uid)
if err != nil {
log.ErrorfWithRequestId(c, "[data_managements.DataStatisticsHandler] failed to get total account count for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.ErrOperationFailed
}
totalTransactionCategoryCount, err := a.categories.GetTotalCategoryCountByUid(uid)
if err != nil {
log.ErrorfWithRequestId(c, "[data_managements.DataStatisticsHandler] failed to get total transaction category count for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.ErrOperationFailed
}
totalTransactionTagCount, err := a.tags.GetTotalTagCountByUid(uid)
if err != nil {
log.ErrorfWithRequestId(c, "[data_managements.DataStatisticsHandler] failed to get total transaction tag count for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.ErrOperationFailed
}
totalTransactionCount, err := a.transactions.GetTotalTransactionCountByUid(uid)
if err != nil {
log.ErrorfWithRequestId(c, "[data_managements.DataStatisticsHandler] failed to get total transaction count for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.ErrOperationFailed
}
dataStatisticsResp := &models.DataStatisticsResponse{
TotalAccountCount: totalAccountCount,
TotalTransactionCategoryCount: totalTransactionCategoryCount,
TotalTransactionTagCount: totalTransactionTagCount,
TotalTransactionCount: totalTransactionCount,
}
return dataStatisticsResp, nil
}
// ClearDataHandler deletes all user data
func (a *DataManagementsApi) ClearDataHandler(c *core.Context) (interface{}, *errs.Error) {
var clearDataReq models.ClearDataRequest
@@ -147,21 +188,21 @@ func (a *DataManagementsApi) ClearDataHandler(c *core.Context) (interface{}, *er
if err != nil {
log.ErrorfWithRequestId(c, "[data_managements.ClearDataHandler] failed to delete all transactions, because %s", err.Error())
return nil, errs.ErrOperationFailed
return nil, errs.Or(err, errs.ErrOperationFailed)
}
err = a.categories.DeleteAllCategories(uid)
if err != nil {
log.ErrorfWithRequestId(c, "[data_managements.ClearDataHandler] failed to delete all transaction categories, because %s", err.Error())
return nil, errs.ErrOperationFailed
return nil, errs.Or(err, errs.ErrOperationFailed)
}
err = a.tags.DeleteAllTags(uid)
if err != nil {
log.ErrorfWithRequestId(c, "[data_managements.ClearDataHandler] failed to delete all transaction tags, because %s", err.Error())
return nil, errs.ErrOperationFailed
return nil, errs.Or(err, errs.ErrOperationFailed)
}
log.InfofWithRequestId(c, "[data_managements.ClearDataHandler] user \"uid:%d\" has cleared all data", uid)
+2 -2
View File
@@ -1,7 +1,7 @@
package api
import (
"io/ioutil"
"io"
"net/http"
"sort"
"time"
@@ -53,7 +53,7 @@ func (a *ExchangeRatesApi) LatestExchangeRateHandler(c *core.Context) (interface
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
exchangeRateResp, err := dataSource.Parse(c, body)
if err != nil {
+26
View File
@@ -0,0 +1,26 @@
package api
import (
"github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/errs"
"github.com/mayswind/ezbookkeeping/pkg/settings"
)
// HealthsApi represents health api
type HealthsApi struct{}
// Initialize a healths api singleton instance
var (
Healths = &HealthsApi{}
)
// HealthStatusHandler returns the health status of current service
func (a *HealthsApi) HealthStatusHandler(c *core.Context) (interface{}, *errs.Error) {
result := make(map[string]string)
result["version"] = settings.Version
result["commit"] = settings.CommitHash
result["status"] = "ok"
return result, nil
}
+40
View File
@@ -0,0 +1,40 @@
package api
import (
"fmt"
"net/http"
"net/http/httputil"
"net/url"
"github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/errs"
)
const openStreetMapTileImageUrlFormat = "https://tile.openstreetmap.org/%s/%s/%s" // https://tile.openstreetmap.org/{z}/{x}/{y}.png
// MapImageProxy represents map image proxy
type MapImageProxy struct {
}
// Initialize a map image proxy singleton instance
var (
MapImages = &MapImageProxy{}
)
// OpenStreetMapTileImageProxyHandler returns open street map tile image
func (p *MapImageProxy) OpenStreetMapTileImageProxyHandler(c *core.Context) (*httputil.ReverseProxy, *errs.Error) {
director := func(req *http.Request) {
zoomLevel := c.Param("zoomLevel")
coordinateX := c.Param("coordinateX")
fileName := c.Param("fileName")
imageRawUrl := fmt.Sprintf(openStreetMapTileImageUrlFormat, zoomLevel, coordinateX, fileName)
imageUrl, _ := url.Parse(imageRawUrl)
req.URL = imageUrl
req.RequestURI = req.URL.RequestURI()
req.Host = imageUrl.Host
}
return &httputil.ReverseProxy{Director: director}, nil
}
+9 -16
View File
@@ -32,7 +32,7 @@ func (a *TokensApi) TokenListHandler(c *core.Context) (interface{}, *errs.Error)
if err != nil {
log.ErrorfWithRequestId(c, "[tokens.TokenListHandler] failed to get all tokens for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.ErrOperationFailed
return nil, errs.Or(err, errs.ErrOperationFailed)
}
tokenResps := make(models.TokenInfoResponseSlice, len(tokens))
@@ -48,7 +48,7 @@ func (a *TokensApi) TokenListHandler(c *core.Context) (interface{}, *errs.Error)
ExpiredAt: token.ExpiredUnixTime,
}
if utils.Int64ToString(token.Uid) == claims.Id && utils.Int64ToString(token.UserTokenId) == claims.UserTokenId && token.CreatedUnixTime == claims.IssuedAt {
if token.Uid == claims.Uid && utils.Int64ToString(token.UserTokenId) == claims.UserTokenId && token.CreatedUnixTime == claims.IssuedAt {
tokenResp.IsCurrent = true
}
@@ -62,17 +62,10 @@ func (a *TokensApi) TokenListHandler(c *core.Context) (interface{}, *errs.Error)
// TokenRevokeCurrentHandler revokes current token of current user
func (a *TokensApi) TokenRevokeCurrentHandler(c *core.Context) (interface{}, *errs.Error) {
_, claims, err := a.tokens.ParseToken(c)
_, claims, err := a.tokens.ParseTokenByHeader(c)
if err != nil {
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
}
uid, err := utils.StringToInt64(claims.Id)
if err != nil {
log.WarnfWithRequestId(c, "[tokens.TokenRevokeCurrentHandler] parse user id failed, because %s", err.Error())
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
return nil, errs.Or(err, errs.NewIncompleteOrIncorrectSubmissionError(err))
}
userTokenId, err := utils.StringToInt64(claims.UserTokenId)
@@ -83,7 +76,7 @@ func (a *TokensApi) TokenRevokeCurrentHandler(c *core.Context) (interface{}, *er
}
tokenRecord := &models.TokenRecord{
Uid: uid,
Uid: claims.Uid,
UserTokenId: userTokenId,
CreatedUnixTime: claims.IssuedAt,
}
@@ -92,11 +85,11 @@ func (a *TokensApi) TokenRevokeCurrentHandler(c *core.Context) (interface{}, *er
err = a.tokens.DeleteToken(tokenRecord)
if err != nil {
log.ErrorfWithRequestId(c, "[token.TokenRevokeCurrentHandler] failed to revoke token \"id:%s\" for user \"uid:%d\", because %s", tokenId, uid, err.Error())
log.ErrorfWithRequestId(c, "[token.TokenRevokeCurrentHandler] failed to revoke token \"id:%s\" for user \"uid:%d\", because %s", tokenId, claims.Uid, err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed)
}
log.InfofWithRequestId(c, "[token.TokenRevokeCurrentHandler] user \"uid:%d\" has revoked token \"id:%s\"", uid, tokenId)
log.InfofWithRequestId(c, "[token.TokenRevokeCurrentHandler] user \"uid:%d\" has revoked token \"id:%s\"", claims.Uid, tokenId)
return true, nil
}
@@ -145,7 +138,7 @@ func (a *TokensApi) TokenRevokeAllHandler(c *core.Context) (interface{}, *errs.E
if err != nil {
log.ErrorfWithRequestId(c, "[tokens.TokenRevokeAllHandler] failed to get all tokens for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.ErrOperationFailed
return nil, errs.Or(err, errs.ErrOperationFailed)
}
claims := c.GetTokenClaims()
@@ -154,7 +147,7 @@ func (a *TokensApi) TokenRevokeAllHandler(c *core.Context) (interface{}, *errs.E
for i := 0; i < len(tokens); i++ {
token := tokens[i]
if utils.Int64ToString(token.Uid) == claims.Id && utils.Int64ToString(token.UserTokenId) == claims.UserTokenId && token.CreatedUnixTime == claims.IssuedAt {
if token.Uid == claims.Uid && utils.Int64ToString(token.UserTokenId) == claims.UserTokenId && token.CreatedUnixTime == claims.IssuedAt {
currentTokenIndex = i
break
}
+9 -9
View File
@@ -37,7 +37,7 @@ func (a *TransactionCategoriesApi) CategoryListHandler(c *core.Context) (interfa
if err != nil {
log.ErrorfWithRequestId(c, "[transaction_categories.CategoryListHandler] failed to get categories for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.ErrOperationFailed
return nil, errs.Or(err, errs.ErrOperationFailed)
}
return a.getTransactionCategoryListByTypeResponse(categories, categoryListReq.ParentId)
@@ -58,7 +58,7 @@ func (a *TransactionCategoriesApi) CategoryGetHandler(c *core.Context) (interfac
if err != nil {
log.ErrorfWithRequestId(c, "[transaction_categories.CategoryGetHandler] failed to get category \"id:%d\" for user \"uid:%d\", because %s", categoryGetReq.Id, uid, err.Error())
return nil, errs.ErrOperationFailed
return nil, errs.Or(err, errs.ErrOperationFailed)
}
categoryResp := category.ToTransactionCategoryInfoResponse()
@@ -102,7 +102,7 @@ func (a *TransactionCategoriesApi) CategoryCreateHandler(c *core.Context) (inter
}
}
var maxOrderId int
var maxOrderId int32
if categoryCreateReq.ParentId <= 0 {
maxOrderId, err = a.categories.GetMaxDisplayOrder(uid, categoryCreateReq.Type)
@@ -112,7 +112,7 @@ func (a *TransactionCategoriesApi) CategoryCreateHandler(c *core.Context) (inter
if err != nil {
log.ErrorfWithRequestId(c, "[transaction_categories.CategoryCreateHandler] failed to get max display order for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.ErrOperationFailed
return nil, errs.Or(err, errs.ErrOperationFailed)
}
category := a.createNewCategoryModel(uid, &categoryCreateReq, maxOrderId+1)
@@ -143,7 +143,7 @@ func (a *TransactionCategoriesApi) CategoryCreateBatchHandler(c *core.Context) (
uid := c.GetCurrentUid()
categoryTypeMaxOrderMap := make(map[models.TransactionCategoryType]int)
categoryTypeMaxOrderMap := make(map[models.TransactionCategoryType]int32)
categoriesMap := make(map[*models.TransactionCategory][]*models.TransactionCategory)
categoriesMap[nil] = make([]*models.TransactionCategory, len(categoryCreateBatchReq.Categories))
totalCount := 0
@@ -157,7 +157,7 @@ func (a *TransactionCategoriesApi) CategoryCreateBatchHandler(c *core.Context) (
if err != nil {
log.ErrorfWithRequestId(c, "[transaction_categories.CategoryCreateBatchHandler] failed to get max display order for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.ErrOperationFailed
return nil, errs.Or(err, errs.ErrOperationFailed)
}
}
@@ -170,7 +170,7 @@ func (a *TransactionCategoriesApi) CategoryCreateBatchHandler(c *core.Context) (
categoriesMap[category] = make([]*models.TransactionCategory, len(categoryCreateReq.SubCategories))
for j := 0; j < len(categoryCreateReq.SubCategories); j++ {
for j := int32(0); j < int32(len(categoryCreateReq.SubCategories)); j++ {
subCategory := a.createNewCategoryModel(uid, categoryCreateReq.SubCategories[j], j+1)
categoriesMap[category][j] = subCategory
totalCount++
@@ -208,7 +208,7 @@ func (a *TransactionCategoriesApi) CategoryModifyHandler(c *core.Context) (inter
if err != nil {
log.ErrorfWithRequestId(c, "[transaction_categories.CategoryModifyHandler] failed to get category \"id:%d\" for user \"uid:%d\", because %s", categoryModifyReq.Id, uid, err.Error())
return nil, errs.ErrOperationFailed
return nil, errs.Or(err, errs.ErrOperationFailed)
}
newCategory := &models.TransactionCategory{
@@ -325,7 +325,7 @@ func (a *TransactionCategoriesApi) CategoryDeleteHandler(c *core.Context) (inter
return true, nil
}
func (a *TransactionCategoriesApi) createNewCategoryModel(uid int64, categoryCreateReq *models.TransactionCategoryCreateRequest, order int) *models.TransactionCategory {
func (a *TransactionCategoriesApi) createNewCategoryModel(uid int64, categoryCreateReq *models.TransactionCategoryCreateRequest, order int32) *models.TransactionCategory {
return &models.TransactionCategory{
Uid: uid,
Name: categoryCreateReq.Name,
+5 -5
View File
@@ -29,7 +29,7 @@ func (a *TransactionTagsApi) TagListHandler(c *core.Context) (interface{}, *errs
if err != nil {
log.ErrorfWithRequestId(c, "[transaction_tags.TagListHandler] failed to get tags for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.ErrOperationFailed
return nil, errs.Or(err, errs.ErrOperationFailed)
}
tagResps := make(models.TransactionTagInfoResponseSlice, len(tags))
@@ -58,7 +58,7 @@ func (a *TransactionTagsApi) TagGetHandler(c *core.Context) (interface{}, *errs.
if err != nil {
log.ErrorfWithRequestId(c, "[transaction_tags.TagGetHandler] failed to get tag \"id:%d\" for user \"uid:%d\", because %s", tagGetReq.Id, uid, err.Error())
return nil, errs.ErrOperationFailed
return nil, errs.Or(err, errs.ErrOperationFailed)
}
tagResp := tag.ToTransactionTagInfoResponse()
@@ -82,7 +82,7 @@ func (a *TransactionTagsApi) TagCreateHandler(c *core.Context) (interface{}, *er
if err != nil {
log.ErrorfWithRequestId(c, "[transaction_tags.TagCreateHandler] failed to get max display order for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.ErrOperationFailed
return nil, errs.Or(err, errs.ErrOperationFailed)
}
tag := a.createNewTagModel(uid, &tagCreateReq, maxOrderId+1)
@@ -116,7 +116,7 @@ func (a *TransactionTagsApi) TagModifyHandler(c *core.Context) (interface{}, *er
if err != nil {
log.ErrorfWithRequestId(c, "[transaction_tags.TagModifyHandler] failed to get tag \"id:%d\" for user \"uid:%d\", because %s", tagModifyReq.Id, uid, err.Error())
return nil, errs.ErrOperationFailed
return nil, errs.Or(err, errs.ErrOperationFailed)
}
newTag := &models.TransactionTag{
@@ -223,7 +223,7 @@ func (a *TransactionTagsApi) TagDeleteHandler(c *core.Context) (interface{}, *er
return true, nil
}
func (a *TransactionTagsApi) createNewTagModel(uid int64, tagCreateReq *models.TransactionTagCreateRequest, order int) *models.TransactionTag {
func (a *TransactionTagsApi) createNewTagModel(uid int64, tagCreateReq *models.TransactionTagCreateRequest, order int32) *models.TransactionTag {
return &models.TransactionTag{
Uid: uid,
Name: tagCreateReq.Name,
+99 -27
View File
@@ -46,14 +46,26 @@ func (a *TransactionsApi) TransactionCountHandler(c *core.Context) (interface{},
uid := c.GetCurrentUid()
allCategoryIds, err := a.getCategoryAndSubCategoryIds(transactionCountReq.CategoryId, uid)
allAccountIds, err := a.getAccountOrSubAccountIds(transactionCountReq.AccountId, uid)
if err != nil {
log.WarnfWithRequestId(c, "[transactions.TransactionCountHandler] get account error, because %s", err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed)
}
allCategoryIds, err := a.getCategoryOrSubCategoryIds(transactionCountReq.CategoryId, uid)
if err != nil {
log.WarnfWithRequestId(c, "[transactions.TransactionCountHandler] get transaction category error, because %s", err.Error())
return nil, errs.ErrOperationFailed
return nil, errs.Or(err, errs.ErrOperationFailed)
}
totalCount, err := a.transactions.GetTransactionCount(uid, transactionCountReq.MaxTime, transactionCountReq.MinTime, transactionCountReq.Type, allCategoryIds, transactionCountReq.AccountId, transactionCountReq.Keyword)
totalCount, err := a.transactions.GetTransactionCount(uid, transactionCountReq.MaxTime, transactionCountReq.MinTime, transactionCountReq.Type, allCategoryIds, allAccountIds, transactionCountReq.Keyword)
if err != nil {
log.ErrorfWithRequestId(c, "[transactions.TransactionCountHandler] failed to get transaction count for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed)
}
countResp := &models.TransactionCountResponse{
TotalCount: totalCount,
@@ -90,24 +102,31 @@ func (a *TransactionsApi) TransactionListHandler(c *core.Context) (interface{},
return nil, errs.ErrUserNotFound
}
allCategoryIds, err := a.getCategoryAndSubCategoryIds(transactionListReq.CategoryId, uid)
allAccountIds, err := a.getAccountOrSubAccountIds(transactionListReq.AccountId, uid)
if err != nil {
log.WarnfWithRequestId(c, "[transactions.TransactionListHandler] get account error, because %s", err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed)
}
allCategoryIds, err := a.getCategoryOrSubCategoryIds(transactionListReq.CategoryId, uid)
if err != nil {
log.WarnfWithRequestId(c, "[transactions.TransactionListHandler] get transaction category error, because %s", err.Error())
return nil, errs.ErrOperationFailed
return nil, errs.Or(err, errs.ErrOperationFailed)
}
transactions, err := a.transactions.GetTransactionsByMaxTime(uid, transactionListReq.MaxTime, transactionListReq.MinTime, transactionListReq.Type, allCategoryIds, transactionListReq.AccountId, transactionListReq.Keyword, transactionListReq.Count+1, true)
transactions, err := a.transactions.GetTransactionsByMaxTime(uid, transactionListReq.MaxTime, transactionListReq.MinTime, transactionListReq.Type, allCategoryIds, allAccountIds, transactionListReq.Keyword, transactionListReq.Count+1, true)
if err != nil {
log.ErrorfWithRequestId(c, "[transactions.TransactionListHandler] failed to get transactions earlier than \"%d\" for user \"uid:%d\", because %s", transactionListReq.MaxTime, uid, err.Error())
return nil, errs.ErrOperationFailed
return nil, errs.Or(err, errs.ErrOperationFailed)
}
hasMore := false
var nextTimeSequenceId *int64
if len(transactions) > transactionListReq.Count {
if len(transactions) > int(transactionListReq.Count) {
hasMore = true
nextTimeSequenceId = &transactions[transactionListReq.Count].TransactionTime
transactions = transactions[:transactionListReq.Count]
@@ -159,25 +178,32 @@ func (a *TransactionsApi) TransactionMonthListHandler(c *core.Context) (interfac
return nil, errs.ErrUserNotFound
}
allCategoryIds, err := a.getCategoryAndSubCategoryIds(transactionListReq.CategoryId, uid)
allAccountIds, err := a.getAccountOrSubAccountIds(transactionListReq.AccountId, uid)
if err != nil {
log.WarnfWithRequestId(c, "[transactions.TransactionMonthListHandler] get account error, because %s", err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed)
}
allCategoryIds, err := a.getCategoryOrSubCategoryIds(transactionListReq.CategoryId, uid)
if err != nil {
log.WarnfWithRequestId(c, "[transactions.TransactionMonthListHandler] get transaction category error, because %s", err.Error())
return nil, errs.ErrOperationFailed
return nil, errs.Or(err, errs.ErrOperationFailed)
}
transactions, err := a.transactions.GetTransactionsInMonthByPage(uid, transactionListReq.Year, transactionListReq.Month, transactionListReq.Type, allCategoryIds, transactionListReq.AccountId, transactionListReq.Keyword, transactionListReq.Page, transactionListReq.Count, utcOffset)
transactions, err := a.transactions.GetTransactionsInMonthByPage(uid, transactionListReq.Year, transactionListReq.Month, transactionListReq.Type, allCategoryIds, allAccountIds, transactionListReq.Keyword, transactionListReq.Page, transactionListReq.Count, utcOffset)
if err != nil {
log.ErrorfWithRequestId(c, "[transactions.TransactionMonthListHandler] failed to get transactions in month \"%d-%d\" for user \"uid:%d\", because %s", transactionListReq.Year, transactionListReq.Month, uid, err.Error())
return nil, errs.ErrOperationFailed
return nil, errs.Or(err, errs.ErrOperationFailed)
}
totalCount, err := a.transactions.GetMonthTransactionCount(uid, transactionListReq.Year, transactionListReq.Month, transactionListReq.Type, allCategoryIds, transactionListReq.AccountId, transactionListReq.Keyword, utcOffset)
totalCount, err := a.transactions.GetMonthTransactionCount(uid, transactionListReq.Year, transactionListReq.Month, transactionListReq.Type, allCategoryIds, allAccountIds, transactionListReq.Keyword, utcOffset)
if err != nil {
log.ErrorfWithRequestId(c, "[transactions.TransactionMonthListHandler] failed to get transaction count in month \"%d-%d\" for user \"uid:%d\", because %s", transactionListReq.Year, transactionListReq.Month, uid, err.Error())
return nil, errs.ErrOperationFailed
return nil, errs.Or(err, errs.ErrOperationFailed)
}
transactionResult, err := a.getTransactionListResult(c, user, transactions, utcOffset, transactionListReq.TrimAccount, transactionListReq.TrimCategory, transactionListReq.TrimTag)
@@ -208,6 +234,11 @@ func (a *TransactionsApi) TransactionStatisticsHandler(c *core.Context) (interfa
uid := c.GetCurrentUid()
totalAmounts, err := a.transactions.GetAccountsAndCategoriesTotalIncomeAndExpense(uid, statisticReq.StartTime, statisticReq.EndTime)
if err != nil {
log.ErrorfWithRequestId(c, "[transactions.TransactionStatisticsHandler] failed to get accounts and categories total income and expense for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed)
}
statisticResp := &models.TransactionStatisticResponse{
StartTime: statisticReq.StartTime,
EndTime: statisticReq.EndTime,
@@ -261,7 +292,7 @@ func (a *TransactionsApi) TransactionAmountsHandler(c *core.Context) (interface{
if err != nil {
log.ErrorfWithRequestId(c, "[transactions.TransactionAmountsHandler] failed to get all accounts for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.ErrOperationFailed
return nil, errs.Or(err, errs.ErrOperationFailed)
}
amountsResp := make(map[string]*models.TransactionAmountsResponseItem)
@@ -273,7 +304,7 @@ func (a *TransactionsApi) TransactionAmountsHandler(c *core.Context) (interface{
if err != nil {
log.ErrorfWithRequestId(c, "[transactions.TransactionAmountsHandler] failed to get transaction amounts item for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.ErrOperationFailed
return nil, errs.Or(err, errs.ErrOperationFailed)
}
amountsMap := make(map[string]*models.TransactionAmountsResponseItemAmountInfo)
@@ -369,10 +400,16 @@ func (a *TransactionsApi) TransactionMonthAmountsHandler(c *core.Context) (inter
if err != nil {
log.ErrorfWithRequestId(c, "[transactions.TransactionMonthAmountsHandler] failed to get all accounts for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.ErrOperationFailed
return nil, errs.Or(err, errs.ErrOperationFailed)
}
totalAmounts, err := a.transactions.GetAccountsMonthTotalIncomeAndExpense(uid, startTime, endTime, pageCountForLoadTransactionAmounts)
if err != nil {
log.ErrorfWithRequestId(c, "[transactions.TransactionMonthAmountsHandler] failed to get accounts month total income and expense for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed)
}
amountsMap := make(map[string]map[string]*models.TransactionAmountsResponseItemAmountInfo)
for yearMonth, monthAccountsAmounts := range totalAmounts {
@@ -476,11 +513,11 @@ func (a *TransactionsApi) TransactionGetHandler(c *core.Context) (interface{}, *
if err != nil {
log.ErrorfWithRequestId(c, "[transactions.TransactionGetHandler] failed to get transaction \"id:%d\" for user \"uid:%d\", because %s", transactionGetReq.Id, uid, err.Error())
return nil, errs.ErrOperationFailed
return nil, errs.Or(err, errs.ErrOperationFailed)
}
if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN {
transaction = a.transactions.GetRelatedTransferTransaction(transaction, transaction.RelatedId)
transaction = a.transactions.GetRelatedTransferTransaction(transaction)
}
accountIds := make([]int64, 0, 2)
@@ -509,7 +546,7 @@ func (a *TransactionsApi) TransactionGetHandler(c *core.Context) (interface{}, *
if err != nil {
log.ErrorfWithRequestId(c, "[transactions.TransactionGetHandler] failed to get transactions tag ids for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.ErrOperationFailed
return nil, errs.Or(err, errs.ErrOperationFailed)
}
var category *models.TransactionCategory
@@ -520,7 +557,7 @@ func (a *TransactionsApi) TransactionGetHandler(c *core.Context) (interface{}, *
if err != nil {
log.ErrorfWithRequestId(c, "[transactions.TransactionGetHandler] failed to get transactions category for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.ErrOperationFailed
return nil, errs.Or(err, errs.ErrOperationFailed)
}
}
@@ -529,7 +566,7 @@ func (a *TransactionsApi) TransactionGetHandler(c *core.Context) (interface{}, *
if err != nil {
log.ErrorfWithRequestId(c, "[transactions.TransactionGetHandler] failed to get transactions tags for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.ErrOperationFailed
return nil, errs.Or(err, errs.ErrOperationFailed)
}
}
@@ -611,7 +648,7 @@ func (a *TransactionsApi) TransactionCreateHandler(c *core.Context) (interface{}
return nil, errs.ErrUserNotFound
}
transaction := a.createNewTransactionModel(uid, &transactionCreateReq)
transaction := a.createNewTransactionModel(uid, &transactionCreateReq, c.ClientIP())
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, transactionCreateReq.UtcOffset)
if !transactionEditable {
@@ -676,7 +713,7 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.Context) (interface{}
if err != nil {
log.ErrorfWithRequestId(c, "[transactions.TransactionModifyHandler] failed to get transactions tag ids for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.ErrOperationFailed
return nil, errs.Or(err, errs.ErrOperationFailed)
}
transactionTagIds := allTransactionTagIds[transaction.TransactionId]
@@ -702,6 +739,11 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.Context) (interface{}
newTransaction.RelatedAccountAmount = transactionModifyReq.DestinationAmount
}
if transactionModifyReq.GeoLocation != nil {
newTransaction.GeoLongitude = transactionModifyReq.GeoLocation.Longitude
newTransaction.GeoLatitude = transactionModifyReq.GeoLocation.Latitude
}
if newTransaction.CategoryId == transaction.CategoryId &&
utils.GetUnixTimeFromTransactionTime(newTransaction.TransactionTime) == utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime) &&
newTransaction.TimezoneUtcOffset == transaction.TimezoneUtcOffset &&
@@ -711,6 +753,8 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.Context) (interface{}
(transaction.Type != models.TRANSACTION_DB_TYPE_TRANSFER_OUT || newTransaction.RelatedAccountAmount == transaction.RelatedAccountAmount) &&
newTransaction.HideAmount == transaction.HideAmount &&
newTransaction.Comment == transaction.Comment &&
newTransaction.GeoLongitude == transaction.GeoLongitude &&
newTransaction.GeoLatitude == transaction.GeoLatitude &&
utils.Int64SliceEquals(tagIds, transactionTagIds) {
return nil, errs.ErrNothingWillBeUpdated
}
@@ -826,7 +870,29 @@ func (a *TransactionsApi) filterTransactions(c *core.Context, uid int64, transac
return finalTransactions
}
func (a *TransactionsApi) getCategoryAndSubCategoryIds(categoryId int64, uid int64) ([]int64, error) {
func (a *TransactionsApi) getAccountOrSubAccountIds(accountId int64, uid int64) ([]int64, error) {
var allAccountIds []int64
if accountId > 0 {
allSubAccounts, err := a.accounts.GetSubAccountsByAccountId(uid, accountId)
if err != nil {
return nil, err
}
if len(allSubAccounts) > 0 {
for i := 0; i < len(allSubAccounts); i++ {
allAccountIds = append(allAccountIds, allSubAccounts[i].AccountId)
}
} else {
allAccountIds = append(allAccountIds, accountId)
}
}
return allAccountIds, nil
}
func (a *TransactionsApi) getCategoryOrSubCategoryIds(categoryId int64, uid int64) ([]int64, error) {
var allCategoryIds []int64
if categoryId > 0 {
@@ -940,7 +1006,7 @@ func (a *TransactionsApi) getTransactionListResult(c *core.Context, user *models
transaction := transactions[i]
if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN {
transaction = a.transactions.GetRelatedTransferTransaction(transaction, transaction.RelatedId)
transaction = a.transactions.GetRelatedTransferTransaction(transaction)
}
transactionEditable := transaction.IsEditable(user, utcOffset, allAccounts[transaction.AccountId], allAccounts[transaction.RelatedAccountId])
@@ -973,7 +1039,7 @@ func (a *TransactionsApi) getTransactionListResult(c *core.Context, user *models
return result, nil
}
func (a *TransactionsApi) createNewTransactionModel(uid int64, transactionCreateReq *models.TransactionCreateRequest) *models.Transaction {
func (a *TransactionsApi) createNewTransactionModel(uid int64, transactionCreateReq *models.TransactionCreateRequest, clientIp string) *models.Transaction {
var transactionDbType models.TransactionDbType
if transactionCreateReq.Type == models.TRANSACTION_TYPE_MODIFY_BALANCE {
@@ -996,6 +1062,7 @@ func (a *TransactionsApi) createNewTransactionModel(uid int64, transactionCreate
Amount: transactionCreateReq.SourceAmount,
HideAmount: transactionCreateReq.HideAmount,
Comment: transactionCreateReq.Comment,
CreatedIp: clientIp,
}
if transactionCreateReq.Type == models.TRANSACTION_TYPE_TRANSFER {
@@ -1003,5 +1070,10 @@ func (a *TransactionsApi) createNewTransactionModel(uid int64, transactionCreate
transaction.RelatedAccountAmount = transactionCreateReq.DestinationAmount
}
if transactionCreateReq.GeoLocation != nil {
transaction.GeoLongitude = transactionCreateReq.GeoLocation.Longitude
transaction.GeoLatitude = transactionCreateReq.GeoLocation.Latitude
}
return transaction
}
+65 -9
View File
@@ -15,15 +15,17 @@ import (
// UsersApi represents user api
type UsersApi struct {
users *services.UserService
tokens *services.TokenService
users *services.UserService
tokens *services.TokenService
accounts *services.AccountService
}
// Initialize a user api singleton instance
var (
Users = &UsersApi{
users: services.Users,
tokens: services.Tokens,
users: services.Users,
tokens: services.Tokens,
accounts: services.Accounts,
}
)
@@ -55,6 +57,7 @@ func (a *UsersApi) UserRegisterHandler(c *core.Context) (interface{}, *errs.Erro
Email: userRegisterReq.Email,
Nickname: userRegisterReq.Nickname,
Password: userRegisterReq.Password,
Language: userRegisterReq.Language,
DefaultCurrency: userRegisterReq.DefaultCurrency,
FirstDayOfWeek: userRegisterReq.FirstDayOfWeek,
TransactionEditScope: models.TRANSACTION_EDIT_SCOPE_ALL,
@@ -159,6 +162,35 @@ func (a *UsersApi) UserUpdateProfileHandler(c *core.Context) (interface{}, *errs
anythingUpdate = true
}
if userUpdateReq.DefaultAccountId > 0 && userUpdateReq.DefaultAccountId != user.DefaultAccountId {
accounts, err := a.accounts.GetAccountsByAccountIds(uid, []int64{userUpdateReq.DefaultAccountId})
if err != nil || len(accounts) < 1 {
return nil, errs.Or(err, errs.ErrUserDefaultAccountIsInvalid)
}
user.DefaultAccountId = userUpdateReq.DefaultAccountId
userNew.DefaultAccountId = userUpdateReq.DefaultAccountId
anythingUpdate = true
}
if userUpdateReq.TransactionEditScope != nil && *userUpdateReq.TransactionEditScope != user.TransactionEditScope {
user.TransactionEditScope = *userUpdateReq.TransactionEditScope
userNew.TransactionEditScope = *userUpdateReq.TransactionEditScope
anythingUpdate = true
} else {
userNew.TransactionEditScope = models.TRANSACTION_EDIT_SCOPE_INVALID
}
modifyUserLanguage := false
if userUpdateReq.Language != user.Language {
user.Language = userUpdateReq.Language
userNew.Language = userUpdateReq.Language
modifyUserLanguage = true
anythingUpdate = true
}
if userUpdateReq.DefaultCurrency != "" && userUpdateReq.DefaultCurrency != user.DefaultCurrency {
user.DefaultCurrency = userUpdateReq.DefaultCurrency
userNew.DefaultCurrency = userUpdateReq.DefaultCurrency
@@ -173,19 +205,43 @@ func (a *UsersApi) UserUpdateProfileHandler(c *core.Context) (interface{}, *errs
userNew.FirstDayOfWeek = models.WEEKDAY_INVALID
}
if userUpdateReq.TransactionEditScope != nil && *userUpdateReq.TransactionEditScope != user.TransactionEditScope {
user.TransactionEditScope = *userUpdateReq.TransactionEditScope
userNew.TransactionEditScope = *userUpdateReq.TransactionEditScope
if userUpdateReq.LongDateFormat != nil && *userUpdateReq.LongDateFormat != user.LongDateFormat {
user.LongDateFormat = *userUpdateReq.LongDateFormat
userNew.LongDateFormat = *userUpdateReq.LongDateFormat
anythingUpdate = true
} else {
userNew.TransactionEditScope = models.TRANSACTION_EDIT_SCOPE_INVALID
userNew.LongDateFormat = models.LONG_DATE_FORMAT_INVALID
}
if userUpdateReq.ShortDateFormat != nil && *userUpdateReq.ShortDateFormat != user.ShortDateFormat {
user.ShortDateFormat = *userUpdateReq.ShortDateFormat
userNew.ShortDateFormat = *userUpdateReq.ShortDateFormat
anythingUpdate = true
} else {
userNew.ShortDateFormat = models.SHORT_DATE_FORMAT_INVALID
}
if userUpdateReq.LongTimeFormat != nil && *userUpdateReq.LongTimeFormat != user.LongTimeFormat {
user.LongTimeFormat = *userUpdateReq.LongTimeFormat
userNew.LongTimeFormat = *userUpdateReq.LongTimeFormat
anythingUpdate = true
} else {
userNew.LongTimeFormat = models.LONG_TIME_FORMAT_INVALID
}
if userUpdateReq.ShortTimeFormat != nil && *userUpdateReq.ShortTimeFormat != user.ShortTimeFormat {
user.ShortTimeFormat = *userUpdateReq.ShortTimeFormat
userNew.ShortTimeFormat = *userUpdateReq.ShortTimeFormat
anythingUpdate = true
} else {
userNew.ShortTimeFormat = models.SHORT_TIME_FORMAT_INVALID
}
if !anythingUpdate {
return nil, errs.ErrNothingWillBeUpdated
}
keyProfileUpdated, err := a.users.UpdateUser(userNew)
keyProfileUpdated, err := a.users.UpdateUser(userNew, modifyUserLanguage)
if err != nil {
log.ErrorfWithRequestId(c, "[users.UserUpdateProfileHandler] failed to update user \"uid:%d\", because %s", user.Uid, err.Error())
+1 -1
View File
@@ -142,7 +142,7 @@ func (l *UserDataCli) ModifyUserPassword(c *cli.Context, username string, passwo
Password: password,
}
_, err = l.users.UpdateUser(userNew)
_, err = l.users.UpdateUser(userNew, false)
if err != nil {
log.BootErrorf("[user_data.ModifyUserPassword] failed to update user \"%s\" password, because %s", user.Username, err.Error())
+1 -7
View File
@@ -61,13 +61,7 @@ func (c *Context) GetCurrentUid() int64 {
return 0
}
uid, err := strconv.ParseInt(claims.Id, 10, 64)
if err != nil {
return 0
}
return uid
return claims.Uid
}
// GetClientTimezoneOffset returns the client timezone offset
+8 -1
View File
@@ -1,6 +1,10 @@
package core
import "github.com/mayswind/ezbookkeeping/pkg/errs"
import (
"net/http/httputil"
"github.com/mayswind/ezbookkeeping/pkg/errs"
)
// MiddlewareHandlerFunc represents the middleware handler function
type MiddlewareHandlerFunc func(*Context)
@@ -10,3 +14,6 @@ type ApiHandlerFunc func(*Context) (interface{}, *errs.Error)
// DataHandlerFunc represents the handler function that returns byte array
type DataHandlerFunc func(*Context) ([]byte, string, *errs.Error)
// ProxyHandlerFunc represents the reverse proxy handler function
type ProxyHandlerFunc func(*Context) (*httputil.ReverseProxy, *errs.Error)
+40 -2
View File
@@ -1,7 +1,9 @@
package core
import (
"github.com/dgrijalva/jwt-go"
"time"
"github.com/golang-jwt/jwt/v5"
)
// TokenType represents token type
@@ -16,7 +18,43 @@ const (
// UserTokenClaims represents user token
type UserTokenClaims struct {
UserTokenId string `json:"userTokenId"`
Uid int64 `json:"jti,string"`
Username string `json:"username,omitempty"`
Type TokenType `json:"type"`
jwt.StandardClaims
IssuedAt int64 `json:"iat"`
ExpiresAt int64 `json:"exp"`
}
// GetExpirationTime returns the expiration time of this token
func (c *UserTokenClaims) GetExpirationTime() (*jwt.NumericDate, error) {
return &jwt.NumericDate{
Time: time.Unix(c.ExpiresAt, 0),
}, nil
}
// GetIssuedAt returns the issue time of this token
func (c *UserTokenClaims) GetIssuedAt() (*jwt.NumericDate, error) {
return &jwt.NumericDate{
Time: time.Unix(c.IssuedAt, 0),
}, nil
}
// GetNotBefore returns the earliest valid time of this token
func (c *UserTokenClaims) GetNotBefore() (*jwt.NumericDate, error) {
return &jwt.NumericDate{}, nil
}
// GetIssuer returns the issuer of this token
func (c *UserTokenClaims) GetIssuer() (string, error) {
return "", nil
}
// GetSubject returns the subject of this token
func (c *UserTokenClaims) GetSubject() (string, error) {
return "", nil
}
// GetAudience returns the audience of this token
func (c *UserTokenClaims) GetAudience() (jwt.ClaimStrings, error) {
return jwt.ClaimStrings{}, nil
}
+2 -2
View File
@@ -98,8 +98,8 @@ func initializeDatabase(dbConfig *settings.DatabaseConfig) (*Database, error) {
return nil, err
}
engineGroup.SetMaxIdleConns(dbConfig.MaxIdleConnection)
engineGroup.SetMaxOpenConns(dbConfig.MaxOpenConnection)
engineGroup.SetMaxIdleConns(int(dbConfig.MaxIdleConnection))
engineGroup.SetMaxOpenConns(int(dbConfig.MaxOpenConnection))
engineGroup.SetConnMaxLifetime(time.Duration(dbConfig.ConnectionMaxLifeTime) * time.Second)
return &Database{
+8 -8
View File
@@ -1,7 +1,7 @@
package errs
// ErrorCategory represents error category
type ErrorCategory int
type ErrorCategory int32
// Error categories
const (
@@ -32,8 +32,8 @@ const (
// Error represents the specific error returned to user
type Error struct {
Category ErrorCategory
SubCategory int
Index int
SubCategory int32
Index int32
HttpStatusCode int
Message string
BaseError []error
@@ -45,12 +45,12 @@ func (err *Error) Error() string {
}
// Code returns the error code
func (err *Error) Code() int {
return int(err.Category)*100000 + err.SubCategory*1000 + err.Index
func (err *Error) Code() int32 {
return int32(err.Category)*100000 + err.SubCategory*1000 + err.Index
}
// New returns a new error instance
func New(category ErrorCategory, subCategory int, index int, httpStatusCode int, message string, baseError ...error) *Error {
func New(category ErrorCategory, subCategory int32, index int32, httpStatusCode int, message string, baseError ...error) *Error {
return &Error{
Category: category,
SubCategory: subCategory,
@@ -62,12 +62,12 @@ func New(category ErrorCategory, subCategory int, index int, httpStatusCode int,
}
// NewSystemError returns a new system error instance
func NewSystemError(subCategory int, index int, httpStatusCode int, message string) *Error {
func NewSystemError(subCategory int32, index int32, httpStatusCode int, message string) *Error {
return New(CATEGORY_SYSTEM, subCategory, index, httpStatusCode, message)
}
// NewNormalError returns a new normal error instance
func NewNormalError(subCategory int, index int, httpStatusCode int, message string) *Error {
func NewNormalError(subCategory int32, index int32, httpStatusCode int, message string) *Error {
return New(CATEGORY_NORMAL, subCategory, index, httpStatusCode, message)
}
+1
View File
@@ -9,4 +9,5 @@ var (
ErrGettingLocalAddress = NewSystemError(SystemSubcategorySetting, 2, http.StatusInternalServerError, "failed to get local address")
ErrInvalidUuidMode = NewSystemError(SystemSubcategorySetting, 3, http.StatusInternalServerError, "invalid uuid mode")
ErrInvalidExchangeRatesDataSource = NewSystemError(SystemSubcategorySetting, 4, http.StatusInternalServerError, "invalid exchange rates data source")
ErrInvalidMapProvider = NewSystemError(SystemSubcategorySetting, 5, http.StatusInternalServerError, "invalid map provider")
)
+1
View File
@@ -18,4 +18,5 @@ var (
ErrInvalidUserTokenId = NewNormalError(NormalSubcategoryToken, 9, http.StatusBadRequest, "user token id is invalid")
ErrTokenRecordNotFound = NewNormalError(NormalSubcategoryToken, 10, http.StatusBadRequest, "token is not found")
ErrTokenExpired = NewNormalError(NormalSubcategoryToken, 11, http.StatusBadRequest, "token is expired")
ErrTokenIsEmpty = NewNormalError(NormalSubcategoryToken, 12, http.StatusBadRequest, "token is empty")
)
+1
View File
@@ -21,4 +21,5 @@ var (
ErrUsernameAlreadyExists = NewNormalError(NormalSubcategoryUser, 12, http.StatusBadRequest, "username already exists")
ErrUserEmailAlreadyExists = NewNormalError(NormalSubcategoryUser, 13, http.StatusBadRequest, "email already exists")
ErrUserRegistrationNotAllowed = NewNormalError(NormalSubcategoryUser, 14, http.StatusBadRequest, "user registration not allowed")
ErrUserDefaultAccountIsInvalid = NewNormalError(NormalSubcategoryUser, 15, http.StatusBadRequest, "user default account is invalid")
)
@@ -18,7 +18,7 @@ const euroCentralBankDataSource = "European Central Bank"
const euroCentralBankBaseCurrency = "EUR"
const euroCentralBankDataUpdateDateFormat = "2006-01-02 15"
const euroCentralBankDataUpdateDateTimezone = "Etc/GMT-1" // UTC+01:00
const euroCentralBankDataUpdateDateTimezone = "Europe/Berlin"
// EuroCentralBankDataSource defines the structure of exchange rates data source of euro central bank
type EuroCentralBankDataSource struct {
@@ -32,6 +32,9 @@ func InitializeExchangeRatesDataSource(config *settings.Config) error {
} else if config.ExchangeRatesDataSource == settings.NationalBankOfPolandDataSource {
Container.Current = &NationalBankOfPolandDataSource{}
return nil
} else if config.ExchangeRatesDataSource == settings.MonetaryAuthorityOfSingaporeDataSource {
Container.Current = &MonetaryAuthorityOfSingaporeDataSource{}
return nil
}
return errs.ErrInvalidExchangeRatesDataSource
@@ -0,0 +1,179 @@
package exchangerates
import (
"encoding/json"
"math"
"strings"
"time"
"github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/errs"
"github.com/mayswind/ezbookkeeping/pkg/log"
"github.com/mayswind/ezbookkeeping/pkg/models"
"github.com/mayswind/ezbookkeeping/pkg/utils"
"github.com/mayswind/ezbookkeeping/pkg/validators"
)
const monetaryAuthorityOfSingaporeExchangeRateUrl = "https://eservices.mas.gov.sg/api/action/datastore/search.json?resource_id=95932927-c8bc-4e7a-b484-68a66a24edfe&sort=end_of_day+desc&limit=1"
const monetaryAuthorityOfSingaporeExchangeRateReferenceUrl = "https://eservices.mas.gov.sg/Statistics/msb/ExchangeRates.aspx"
const monetaryAuthorityOfSingaporeDataSource = "Monetary Authority of Singapore"
const monetaryAuthorityOfSingaporeBaseCurrency = "SGD"
const monetaryAuthorityOfSingaporeDataUpdateDateFormat = "2006-01-02 15"
const monetaryAuthorityOfSingaporeDataUpdateDateTimezone = "Asia/Singapore"
// MonetaryAuthorityOfSingaporeDataSource defines the structure of exchange rates data source of Monetary Authority of Singapore
type MonetaryAuthorityOfSingaporeDataSource struct {
ExchangeRatesDataSource
}
// MonetaryAuthorityOfSingaporeExchangeRateData represents the whole data from Monetary Authority of Singapore
type MonetaryAuthorityOfSingaporeExchangeRateData struct {
Success bool `json:"success"`
Result *MonetaryAuthorityOfSingaporeResult `json:"result"`
}
// MonetaryAuthorityOfSingaporeResult represents the actual result from Monetary Authority of Singapore
type MonetaryAuthorityOfSingaporeResult struct {
Records []MonetaryAuthorityOfSingaporeRecord `json:"records"`
}
// MonetaryAuthorityOfSingaporeRecord represents the record from Monetary Authority of Singapore
type MonetaryAuthorityOfSingaporeRecord map[string]string
// ToLatestExchangeRateResponse returns a view-object according to original data from Monetary Authority of Singapore
func (e *MonetaryAuthorityOfSingaporeExchangeRateData) ToLatestExchangeRateResponse(c *core.Context) *models.LatestExchangeRateResponse {
if !e.Success {
log.ErrorfWithRequestId(c, "[monetary_authority_of_singapore_datasource.ToLatestExchangeRateResponse] response is not success")
return nil
}
if e.Result == nil {
log.ErrorfWithRequestId(c, "[monetary_authority_of_singapore_datasource.ToLatestExchangeRateResponse] result is null")
return nil
}
if len(e.Result.Records) < 1 {
log.ErrorfWithRequestId(c, "[monetary_authority_of_singapore_datasource.ToLatestExchangeRateResponse] records is empty")
return nil
}
lastDayRecord := e.Result.Records[0]
exchangeRates := make(models.LatestExchangeRateSlice, 0, len(lastDayRecord))
latestUpdateDate := ""
for key, value := range lastDayRecord {
if key == "end_of_day" {
latestUpdateDate = value
continue
}
exchangeRate := e.parseExchangeRateResponse(c, key, value)
if exchangeRate == nil {
continue
}
exchangeRates = append(exchangeRates, exchangeRate)
}
timezone, err := time.LoadLocation(monetaryAuthorityOfSingaporeDataUpdateDateTimezone)
if err != nil {
log.ErrorfWithRequestId(c, "[monetary_authority_of_singapore_datasource.ToLatestExchangeRateResponse] failed to get timezone, timezone name is %s", monetaryAuthorityOfSingaporeDataUpdateDateTimezone)
return nil
}
updateDateTime := latestUpdateDate + " 12" // These rates are the average of buying and selling interbank rates quoted around midday in Singapore
updateTime, err := time.ParseInLocation(monetaryAuthorityOfSingaporeDataUpdateDateFormat, updateDateTime, timezone)
if err != nil {
log.ErrorfWithRequestId(c, "[monetary_authority_of_singapore_datasource.ToLatestExchangeRateResponse] failed to parse update date, datetime is %s", updateDateTime)
return nil
}
latestExchangeRateResp := &models.LatestExchangeRateResponse{
DataSource: monetaryAuthorityOfSingaporeDataSource,
ReferenceUrl: monetaryAuthorityOfSingaporeExchangeRateReferenceUrl,
UpdateTime: updateTime.Unix(),
BaseCurrency: monetaryAuthorityOfSingaporeBaseCurrency,
ExchangeRates: exchangeRates,
}
return latestExchangeRateResp
}
func (e *MonetaryAuthorityOfSingaporeExchangeRateData) parseExchangeRateResponse(c *core.Context, key string, value string) *models.LatestExchangeRate {
if !strings.Contains(key, "_") {
return nil
}
items := strings.Split(key, "_")
if len(items) < 2 {
return nil
}
fromCurrencyCode := strings.ToUpper(items[0])
toCurrencyCode := strings.ToUpper(items[1])
if _, exists := validators.AllCurrencyNames[fromCurrencyCode]; !exists {
return nil
}
if toCurrencyCode != monetaryAuthorityOfSingaporeBaseCurrency {
return nil
}
rate, err := utils.StringToFloat64(value)
if err != nil {
log.WarnfWithRequestId(c, "[monetary_authority_of_singapore_datasource.parseExchangeRateResponse] failed to parse rate, rate is %s", value)
return nil
}
if rate <= 0 {
log.WarnfWithRequestId(c, "[monetary_authority_of_singapore_datasource.parseExchangeRateResponse] rate is invalid, rate is %s", value)
return nil
}
finalRate := 1 / rate
if math.IsInf(finalRate, 0) {
return nil
}
if len(items) == 3 && items[2] == "100" {
finalRate = finalRate * 100
}
return &models.LatestExchangeRate{
Currency: fromCurrencyCode,
Rate: utils.Float64ToString(finalRate),
}
}
// GetRequestUrls returns the Monetary Authority of Singapore data source urls
func (e *MonetaryAuthorityOfSingaporeDataSource) GetRequestUrls() []string {
return []string{monetaryAuthorityOfSingaporeExchangeRateUrl}
}
// Parse returns the common response entity according to the Monetary Authority of Singapore data source raw response
func (e *MonetaryAuthorityOfSingaporeDataSource) Parse(c *core.Context, content []byte) (*models.LatestExchangeRateResponse, error) {
monetaryAuthorityOfSingaporeData := &MonetaryAuthorityOfSingaporeExchangeRateData{}
err := json.Unmarshal(content, monetaryAuthorityOfSingaporeData)
if err != nil {
log.ErrorfWithRequestId(c, "[monetary_authority_of_singapore_datasource.Parse] failed to parse json data, content is %s, because %s", string(content), err.Error())
return nil, errs.ErrFailedToRequestRemoteApi
}
latestExchangeRateResponse := monetaryAuthorityOfSingaporeData.ToLatestExchangeRateResponse(c)
if latestExchangeRateResponse == nil {
log.ErrorfWithRequestId(c, "[monetary_authority_of_singapore_datasource.Parse] failed to parse latest exchange rate data, content is %s", string(content))
return nil, errs.ErrFailedToRequestRemoteApi
}
return latestExchangeRateResponse, nil
}
@@ -0,0 +1,206 @@
package exchangerates
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/gin-gonic/gin"
"github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/models"
)
const monetaryAuthorityOfSingaporeMinimumRequiredContent = "{\n" +
" \"success\": true,\n" +
" \"result\": {\n" +
" \"records\": [\n" +
" {\n" +
" \"end_of_day\": \"2023-05-26\",\n" +
" \"usd_sgd\": \"1.3528\",\n" +
" \"cny_sgd_100\": \"19.16\"\n" +
" }\n" +
" ]\n" +
" }\n" +
"}"
func TestMonetaryAuthorityOfSingaporeDataSource_StandardDataExtractBaseCurrency(t *testing.T) {
dataSource := &MonetaryAuthorityOfSingaporeDataSource{}
context := &core.Context{
Context: &gin.Context{},
}
actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte(monetaryAuthorityOfSingaporeMinimumRequiredContent))
assert.Equal(t, nil, err)
assert.Equal(t, "SGD", actualLatestExchangeRateResponse.BaseCurrency)
}
func TestMonetaryAuthorityOfSingaporeDataSource_StandardDataExtractExchangeRates(t *testing.T) {
dataSource := &MonetaryAuthorityOfSingaporeDataSource{}
context := &core.Context{
Context: &gin.Context{},
}
actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte(monetaryAuthorityOfSingaporeMinimumRequiredContent))
assert.Equal(t, nil, err)
assert.Contains(t, actualLatestExchangeRateResponse.ExchangeRates, &models.LatestExchangeRate{
Currency: "USD",
Rate: "0.7392075694855116",
})
assert.Contains(t, actualLatestExchangeRateResponse.ExchangeRates, &models.LatestExchangeRate{
Currency: "CNY",
Rate: "5.219206680584551",
})
}
func TestMonetaryAuthorityOfSingaporeDataSource_BlankContent(t *testing.T) {
dataSource := &MonetaryAuthorityOfSingaporeDataSource{}
context := &core.Context{
Context: &gin.Context{},
}
_, err := dataSource.Parse(context, []byte(""))
assert.NotEqual(t, nil, err)
}
func TestMonetaryAuthorityOfSingaporeDataSource_EmptyJsonObject(t *testing.T) {
dataSource := &MonetaryAuthorityOfSingaporeDataSource{}
context := &core.Context{
Context: &gin.Context{},
}
_, err := dataSource.Parse(context, []byte("{}"))
assert.NotEqual(t, nil, err)
}
func TestMonetaryAuthorityOfSingaporeDataSource_ResponseSuccessIsFalseObject(t *testing.T) {
dataSource := &MonetaryAuthorityOfSingaporeDataSource{}
context := &core.Context{
Context: &gin.Context{},
}
_, err := dataSource.Parse(context, []byte("{\n"+
" \"success\": false,\n"+
" \"result\": {\n"+
" \"records\": [\n"+
" {\n"+
" \"end_of_day\": \"2023-05-26\",\n"+
" \"usd_sgd\": \"1.3528\",\n"+
" \"cny_sgd_100\": \"19.16\"\n"+
" }\n"+
" ]\n"+
" }\n"+
"}"))
assert.NotEqual(t, nil, err)
}
func TestMonetaryAuthorityOfSingaporeDataSource_NoResultContent(t *testing.T) {
dataSource := &MonetaryAuthorityOfSingaporeDataSource{}
context := &core.Context{
Context: &gin.Context{},
}
_, err := dataSource.Parse(context, []byte("{\n"+
" \"success\": true\n"+
"}"))
assert.NotEqual(t, nil, err)
}
func TestMonetaryAuthorityOfSingaporeDataSource_EmptyRecordContent(t *testing.T) {
dataSource := &MonetaryAuthorityOfSingaporeDataSource{}
context := &core.Context{
Context: &gin.Context{},
}
_, err := dataSource.Parse(context, []byte("{\n"+
" \"success\": true,\n"+
" \"result\": {\n"+
" \"records\": [\n"+
" ]\n"+
" }\n"+
"}"))
assert.NotEqual(t, nil, err)
}
func TestMonetaryAuthorityOfSingaporeDataSource_TargetCurrencyIsNotBaseCurrency(t *testing.T) {
dataSource := &MonetaryAuthorityOfSingaporeDataSource{}
context := &core.Context{
Context: &gin.Context{},
}
actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte("{\n"+
" \"success\": true,\n"+
" \"result\": {\n"+
" \"records\": [\n"+
" {\n"+
" \"end_of_day\": \"2023-05-26\",\n"+
" \"usd_cny\": \"1\""+
" }\n"+
" ]\n"+
" }\n"+
"}"))
assert.Equal(t, nil, err)
assert.Len(t, actualLatestExchangeRateResponse.ExchangeRates, 0)
}
func TestMonetaryAuthorityOfSingaporeDataSource_InvalidCurrency(t *testing.T) {
dataSource := &MonetaryAuthorityOfSingaporeDataSource{}
context := &core.Context{
Context: &gin.Context{},
}
actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte("{\n"+
" \"success\": true,\n"+
" \"result\": {\n"+
" \"records\": [\n"+
" {\n"+
" \"end_of_day\": \"2023-05-26\",\n"+
" \"xxx_sgd\": \"1.3528\""+
" }\n"+
" ]\n"+
" }\n"+
"}"))
assert.Equal(t, nil, err)
assert.Len(t, actualLatestExchangeRateResponse.ExchangeRates, 0)
}
func TestMonetaryAuthorityOfSingaporeDataSource_EmptyRate(t *testing.T) {
dataSource := &MonetaryAuthorityOfSingaporeDataSource{}
context := &core.Context{
Context: &gin.Context{},
}
actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte("{\n"+
" \"success\": true,\n"+
" \"result\": {\n"+
" \"records\": [\n"+
" {\n"+
" \"end_of_day\": \"2023-05-26\",\n"+
" \"usd_sgd\": \"\""+
" }\n"+
" ]\n"+
" }\n"+
"}"))
assert.Equal(t, nil, err)
assert.Len(t, actualLatestExchangeRateResponse.ExchangeRates, 0)
}
func TestMonetaryAuthorityOfSingaporeDataSource_InvalidRate(t *testing.T) {
dataSource := &MonetaryAuthorityOfSingaporeDataSource{}
context := &core.Context{
Context: &gin.Context{},
}
actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte("{\n"+
" \"success\": true,\n"+
" \"result\": {\n"+
" \"records\": [\n"+
" {\n"+
" \"end_of_day\": \"2023-05-26\",\n"+
" \"usd_sgd\": null"+
" }\n"+
" ]\n"+
" }\n"+
"}"))
assert.Equal(t, nil, err)
assert.Len(t, actualLatestExchangeRateResponse.ExchangeRates, 0)
}
+44 -22
View File
@@ -1,7 +1,7 @@
package middlewares
import (
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/errs"
@@ -10,11 +10,20 @@ import (
"github.com/mayswind/ezbookkeeping/pkg/utils"
)
// TokenSourceType represents token source
type TokenSourceType byte
// Token source types
const (
TOKEN_SOURCE_TYPE_HEADER TokenSourceType = 1
TOKEN_SOURCE_TYPE_ARGUMENT TokenSourceType = 2
)
const tokenQueryStringParam = "token"
// JWTAuthorization verifies whether current request is valid by jwt token
func JWTAuthorization(c *core.Context) {
claims, err := getTokenClaims(c)
claims, err := getTokenClaims(c, TOKEN_SOURCE_TYPE_HEADER)
if err != nil {
utils.PrintJsonErrorResult(c, err)
@@ -22,13 +31,13 @@ func JWTAuthorization(c *core.Context) {
}
if claims.Type == core.USER_TOKEN_TYPE_REQUIRE_2FA {
log.WarnfWithRequestId(c, "[authorization.JWTAuthorization] user \"uid:%s\" token requires 2fa", claims.Id)
log.WarnfWithRequestId(c, "[authorization.JWTAuthorization] user \"uid:%d\" token requires 2fa", claims.Uid)
utils.PrintJsonErrorResult(c, errs.ErrCurrentTokenRequire2FA)
return
}
if claims.Type != core.USER_TOKEN_TYPE_NORMAL {
log.WarnfWithRequestId(c, "[authorization.JWTAuthorization] user \"uid:%s\" token type is invalid", claims.Id)
log.WarnfWithRequestId(c, "[authorization.JWTAuthorization] user \"uid:%d\" token type is invalid", claims.Uid)
utils.PrintJsonErrorResult(c, errs.ErrCurrentInvalidTokenType)
return
}
@@ -39,22 +48,32 @@ func JWTAuthorization(c *core.Context) {
// JWTAuthorizationByQueryString verifies whether current request is valid by jwt token
func JWTAuthorizationByQueryString(c *core.Context) {
token, exists := c.GetQuery(tokenQueryStringParam)
claims, err := getTokenClaims(c, TOKEN_SOURCE_TYPE_ARGUMENT)
if !exists {
log.WarnfWithRequestId(c, "[authorization.JWTAuthorizationByQueryString] no token provided")
utils.PrintJsonErrorResult(c, errs.ErrUnauthorizedAccess)
if err != nil {
utils.PrintJsonErrorResult(c, err)
return
}
c.Request.Header.Set("Authorization", token)
if claims.Type == core.USER_TOKEN_TYPE_REQUIRE_2FA {
log.WarnfWithRequestId(c, "[authorization.JWTAuthorizationByQueryString] user \"uid:%d\" token requires 2fa", claims.Uid)
utils.PrintJsonErrorResult(c, errs.ErrCurrentTokenRequire2FA)
return
}
JWTAuthorization(c)
if claims.Type != core.USER_TOKEN_TYPE_NORMAL {
log.WarnfWithRequestId(c, "[authorization.JWTAuthorizationByQueryString] user \"uid:%d\" token type is invalid", claims.Uid)
utils.PrintJsonErrorResult(c, errs.ErrCurrentInvalidTokenType)
return
}
c.SetTokenClaims(claims)
c.Next()
}
// JWTTwoFactorAuthorization verifies whether current request is valid by 2fa passcode
func JWTTwoFactorAuthorization(c *core.Context) {
claims, err := getTokenClaims(c)
claims, err := getTokenClaims(c, TOKEN_SOURCE_TYPE_HEADER)
if err != nil {
utils.PrintJsonErrorResult(c, err)
@@ -62,7 +81,7 @@ func JWTTwoFactorAuthorization(c *core.Context) {
}
if claims.Type != core.USER_TOKEN_TYPE_REQUIRE_2FA {
log.WarnfWithRequestId(c, "[authorization.JWTTwoFactorAuthorization] user \"uid:%s\" token is not need two factor authorization", claims.Id)
log.WarnfWithRequestId(c, "[authorization.JWTTwoFactorAuthorization] user \"uid:%d\" token is not need two factor authorization", claims.Uid)
utils.PrintJsonErrorResult(c, errs.ErrCurrentTokenNotRequire2FA)
return
}
@@ -71,12 +90,12 @@ func JWTTwoFactorAuthorization(c *core.Context) {
c.Next()
}
func getTokenClaims(c *core.Context) (*core.UserTokenClaims, *errs.Error) {
token, claims, err := services.Tokens.ParseToken(c)
func getTokenClaims(c *core.Context, source TokenSourceType) (*core.UserTokenClaims, *errs.Error) {
token, claims, err := parseToken(c, source)
if err != nil {
log.WarnfWithRequestId(c, "[authorization.getTokenClaims] failed to parse token, because %s", err.Error())
return nil, errs.ErrUnauthorizedAccess
return nil, errs.Or(err, errs.ErrUnauthorizedAccess)
}
if !token.Valid {
@@ -84,15 +103,18 @@ func getTokenClaims(c *core.Context) (*core.UserTokenClaims, *errs.Error) {
return nil, errs.ErrCurrentInvalidToken
}
if !claims.VerifyExpiresAt(time.Now().Unix(), true) {
log.WarnfWithRequestId(c, "[authorization.getTokenClaims] token is expired")
return nil, errs.ErrCurrentTokenExpired
}
if claims.Id == "" {
log.WarnfWithRequestId(c, "[authorization.getTokenClaims] user id in token is empty")
if claims.Uid <= 0 {
log.WarnfWithRequestId(c, "[authorization.getTokenClaims] user id in token is invalid")
return nil, errs.ErrCurrentInvalidToken
}
return claims, nil
}
func parseToken(c *core.Context, source TokenSourceType) (*jwt.Token, *core.UserTokenClaims, error) {
if source == TOKEN_SOURCE_TYPE_ARGUMENT {
return services.Tokens.ParseTokenByArgument(c, tokenQueryStringParam)
}
return services.Tokens.ParseTokenByHeader(c)
}
-16
View File
@@ -1,16 +0,0 @@
package middlewares
import (
"github.com/mayswind/ezbookkeeping/pkg/core"
)
const utcOffsetQueryStringParam = "utc_offset"
// HeaderInQueryString puts some headers from query string
func HeaderInQueryString(c *core.Context) {
utcOffset, exists := c.GetQuery(utcOffsetQueryStringParam)
if exists {
c.Request.Header.Set(core.ClientTimezoneOffsetHeaderName, utcOffset)
}
}
+2 -2
View File
@@ -3,7 +3,7 @@ package middlewares
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"runtime"
"github.com/mayswind/ezbookkeeping/pkg/core"
@@ -49,7 +49,7 @@ func stack(skip int) []byte {
fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc)
if file != lastFile {
data, err := ioutil.ReadFile(file)
data, err := os.ReadFile(file)
if err != nil {
continue
+3 -2
View File
@@ -5,6 +5,7 @@ import (
"github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/log"
"github.com/mayswind/ezbookkeeping/pkg/utils"
)
// RequestLog logs the http request log
@@ -18,7 +19,7 @@ func RequestLog(c *core.Context) {
now := time.Now()
statusCode := c.Writer.Status()
errorCode := 0
errorCode := int32(0)
userId := "-"
claims := c.GetTokenClaims()
@@ -28,7 +29,7 @@ func RequestLog(c *core.Context) {
method := c.Request.Method
if claims != nil {
userId = claims.Id
userId = utils.Int64ToString(claims.Uid)
}
if err != nil {
+7 -1
View File
@@ -16,15 +16,21 @@ func ServerSettingsCookie(config *settings.Config) core.MiddlewareHandlerFunc {
settingsArr := []string{
buildBooleanSetting("r", config.EnableUserRegister),
buildBooleanSetting("e", config.EnableDataExport),
buildStringSetting("m", config.MapProvider),
buildBooleanSetting("mp", config.EnableMapDataFetchProxy),
}
bundledSettings := strings.Join(settingsArr, "_")
c.SetCookie(settingsCookieName, bundledSettings, config.TokenExpiredTime, "", "", false, false)
c.SetCookie(settingsCookieName, bundledSettings, int(config.TokenExpiredTime), "", "", false, false)
c.Next()
}
}
func buildStringSetting(key string, value string) string {
return fmt.Sprintf("%s.%s", key, strings.Replace(value, ".", "-", -1))
}
func buildBooleanSetting(key string, value bool) string {
if value {
return fmt.Sprintf("%s.1", key)
+3 -3
View File
@@ -55,7 +55,7 @@ type Account struct {
Type AccountType `xorm:"NOT NULL"`
ParentAccountId int64 `xorm:"INDEX(IDX_account_uid_deleted_parent_account_id_order) NOT NULL"`
Name string `xorm:"VARCHAR(32) NOT NULL"`
DisplayOrder int `xorm:"INDEX(IDX_account_uid_deleted_parent_account_id_order) NOT NULL"`
DisplayOrder int32 `xorm:"INDEX(IDX_account_uid_deleted_parent_account_id_order) NOT NULL"`
Icon int64 `xorm:"NOT NULL"`
Color string `xorm:"VARCHAR(6) NOT NULL"`
Currency string `xorm:"VARCHAR(3) NOT NULL"`
@@ -116,7 +116,7 @@ type AccountMoveRequest struct {
// AccountNewDisplayOrderRequest represents a data pair of id and display order
type AccountNewDisplayOrderRequest struct {
Id int64 `json:"id,string" binding:"required,min=1"`
DisplayOrder int `json:"displayOrder"`
DisplayOrder int32 `json:"displayOrder"`
}
// AccountDeleteRequest represents all parameters of account deleting request
@@ -136,7 +136,7 @@ type AccountInfoResponse struct {
Currency string `json:"currency"`
Balance int64 `json:"balance"`
Comment string `json:"comment"`
DisplayOrder int `json:"displayOrder"`
DisplayOrder int32 `json:"displayOrder"`
IsAsset bool `json:"isAsset,omitempty"`
IsLiability bool `json:"isLiability,omitempty"`
Hidden bool `json:"hidden"`
+8
View File
@@ -4,3 +4,11 @@ package models
type ClearDataRequest struct {
Password string `json:"password" binding:"omitempty,min=6,max=128"`
}
// DataStatisticsResponse represents a view-object of user data statistic
type DataStatisticsResponse struct {
TotalAccountCount int64 `json:"totalAccountCount,string"`
TotalTransactionCategoryCount int64 `json:"totalTransactionCategoryCount,string"`
TotalTransactionTagCount int64 `json:"totalTransactionTagCount,string"`
TotalTransactionCount int64 `json:"totalTransactionCount,string"`
}
+162
View File
@@ -0,0 +1,162 @@
package models
import "fmt"
// WeekDay represents week day
type WeekDay byte
// Week days
const (
WEEKDAY_SUNDAY WeekDay = 0
WEEKDAY_MONDAY WeekDay = 1
WEEKDAY_TUESDAY WeekDay = 2
WEEKDAY_WEDNESDAY WeekDay = 3
WEEKDAY_THURSDAY WeekDay = 4
WEEKDAY_FRIDAY WeekDay = 5
WEEKDAY_SATURDAY WeekDay = 6
WEEKDAY_INVALID WeekDay = 255
)
// String returns a textual representation of the week day enum
func (d WeekDay) String() string {
switch d {
case WEEKDAY_SUNDAY:
return "Sunday"
case WEEKDAY_MONDAY:
return "Monday"
case WEEKDAY_TUESDAY:
return "Tuesday"
case WEEKDAY_WEDNESDAY:
return "Wednesday"
case WEEKDAY_THURSDAY:
return "Thursday"
case WEEKDAY_FRIDAY:
return "Friday"
case WEEKDAY_SATURDAY:
return "Saturday"
case WEEKDAY_INVALID:
return "Invalid"
default:
return fmt.Sprintf("Invalid(%d)", int(d))
}
}
// LongDateFormat represents long date format
type LongDateFormat byte
// Long Date Format
const (
LONG_DATE_FORMAT_DEFAULT LongDateFormat = 0
LONG_DATE_FORMAT_YYYY_M_D LongDateFormat = 1
LONG_DATE_FORMAT_M_D_YYYY LongDateFormat = 2
LONG_DATE_FORMAT_D_M_YYYY LongDateFormat = 3
LONG_DATE_FORMAT_INVALID LongDateFormat = 255
)
// String returns a textual representation of the long date format enum
func (f LongDateFormat) String() string {
switch f {
case LONG_DATE_FORMAT_DEFAULT:
return "Default"
case LONG_DATE_FORMAT_YYYY_M_D:
return "YYYY_MM_D"
case LONG_DATE_FORMAT_M_D_YYYY:
return "M_D_YYYY"
case LONG_DATE_FORMAT_D_M_YYYY:
return "D_M_YYYY"
case LONG_DATE_FORMAT_INVALID:
return "Invalid"
default:
return fmt.Sprintf("Invalid(%d)", int(f))
}
}
// ShortDateFormat represents short date format
type ShortDateFormat byte
// Short Date Format
const (
SHORT_DATE_FORMAT_DEFAULT ShortDateFormat = 0
SHORT_DATE_FORMAT_YYYY_M_D ShortDateFormat = 1
SHORT_DATE_FORMAT_M_D_YYYY ShortDateFormat = 2
SHORT_DATE_FORMAT_D_M_YYYY ShortDateFormat = 3
SHORT_DATE_FORMAT_INVALID ShortDateFormat = 255
)
// String returns a textual representation of the short date format enum
func (f ShortDateFormat) String() string {
switch f {
case SHORT_DATE_FORMAT_DEFAULT:
return "Default"
case SHORT_DATE_FORMAT_YYYY_M_D:
return "YYYY_MM_D"
case SHORT_DATE_FORMAT_M_D_YYYY:
return "M_D_YYYY"
case SHORT_DATE_FORMAT_D_M_YYYY:
return "D_M_YYYY"
case SHORT_DATE_FORMAT_INVALID:
return "Invalid"
default:
return fmt.Sprintf("Invalid(%d)", int(f))
}
}
// LongTimeFormat represents long time format
type LongTimeFormat byte
// Long Time Format
const (
LONG_TIME_FORMAT_DEFAULT LongTimeFormat = 0
LONG_TIME_FORMAT_HH_MM_SS LongTimeFormat = 1
LONG_TIME_FORMAT_A_HH_MM_SS LongTimeFormat = 2
LONG_TIME_FORMAT_HH_MM_SS_A LongTimeFormat = 3
LONG_TIME_FORMAT_INVALID LongTimeFormat = 255
)
// String returns a textual representation of the long time format enum
func (f LongTimeFormat) String() string {
switch f {
case LONG_TIME_FORMAT_DEFAULT:
return "Default"
case LONG_TIME_FORMAT_HH_MM_SS:
return "HH_MM_SS"
case LONG_TIME_FORMAT_A_HH_MM_SS:
return "A_HH_MM_SS"
case LONG_TIME_FORMAT_HH_MM_SS_A:
return "HH_MM_SS_A"
case LONG_TIME_FORMAT_INVALID:
return "Invalid"
default:
return fmt.Sprintf("Invalid(%d)", int(f))
}
}
// ShortTimeFormat represents short time format
type ShortTimeFormat byte
// Short Time Format
const (
SHORT_TIME_FORMAT_DEFAULT ShortTimeFormat = 0
SHORT_TIME_FORMAT_HH_MM ShortTimeFormat = 1
SHORT_TIME_FORMAT_A_HH_MM ShortTimeFormat = 2
SHORT_TIME_FORMAT_HH_MM_A ShortTimeFormat = 3
SHORT_TIME_FORMAT_INVALID ShortTimeFormat = 255
)
// String returns a textual representation of the short time format enum
func (f ShortTimeFormat) String() string {
switch f {
case SHORT_TIME_FORMAT_DEFAULT:
return "Default"
case SHORT_TIME_FORMAT_HH_MM:
return "HH_MM"
case SHORT_TIME_FORMAT_A_HH_MM:
return "A_HH_MM"
case SHORT_TIME_FORMAT_HH_MM_A:
return "HH_MM_A"
case SHORT_TIME_FORMAT_INVALID:
return "Invalid"
default:
return fmt.Sprintf("Invalid(%d)", int(f))
}
}
+59 -31
View File
@@ -35,8 +35,8 @@ const (
// Transaction represents transaction data stored in database
type Transaction struct {
TransactionId int64 `xorm:"PK"`
Uid int64 `xorm:"UNIQUE(UQE_transaction_uid_time) INDEX(IDX_transaction_uid_deleted_time) INDEX(IDX_transaction_uid_deleted_type_time) INDEX(IDX_transaction_uid_deleted_category_id_time) INDEX(IDX_transaction_uid_deleted_account_id_time) NOT NULL"`
Deleted bool `xorm:"INDEX(IDX_transaction_uid_deleted_time) INDEX(IDX_transaction_uid_deleted_type_time) INDEX(IDX_transaction_uid_deleted_category_id_time) INDEX(IDX_transaction_uid_deleted_account_id_time) NOT NULL"`
Uid int64 `xorm:"UNIQUE(UQE_transaction_uid_time) INDEX(IDX_transaction_uid_deleted_time) INDEX(IDX_transaction_uid_deleted_type_time) INDEX(IDX_transaction_uid_deleted_category_id_time) INDEX(IDX_transaction_uid_deleted_account_id_time) INDEX(IDX_transaction_uid_deleted_time_longitude_latitude) NOT NULL"`
Deleted bool `xorm:"INDEX(IDX_transaction_uid_deleted_time) INDEX(IDX_transaction_uid_deleted_type_time) INDEX(IDX_transaction_uid_deleted_category_id_time) INDEX(IDX_transaction_uid_deleted_account_id_time) INDEX(IDX_transaction_uid_deleted_time_longitude_latitude) NOT NULL"`
Type TransactionDbType `xorm:"INDEX(IDX_transaction_uid_deleted_type_time) NOT NULL"`
CategoryId int64 `xorm:"INDEX(IDX_transaction_uid_deleted_category_id_time) NOT NULL"`
AccountId int64 `xorm:"INDEX(IDX_transaction_uid_deleted_account_id_time) NOT NULL"`
@@ -48,39 +48,50 @@ type Transaction struct {
RelatedAccountAmount int64 `xorm:"NOT NULL"`
HideAmount bool `xorm:"NOT NULL"`
Comment string `xorm:"VARCHAR(255) NOT NULL"`
GeoLongitude float64 `xorm:"INDEX(IDX_transaction_uid_deleted_time_longitude_latitude)"`
GeoLatitude float64 `xorm:"INDEX(IDX_transaction_uid_deleted_time_longitude_latitude)"`
CreatedIp string `xorm:"VARCHAR(39)"`
CreatedUnixTime int64
UpdatedUnixTime int64
DeletedUnixTime int64
}
// TransactionGeoLocationRequest represents all parameters of transaction geographic location info update request
type TransactionGeoLocationRequest struct {
Latitude float64 `json:"latitude" binding:"required"`
Longitude float64 `json:"longitude" binding:"required"`
}
// TransactionCreateRequest represents all parameters of transaction creation request
type TransactionCreateRequest struct {
Type TransactionType `json:"type" binding:"required"`
CategoryId int64 `json:"categoryId,string"`
Time int64 `json:"time" binding:"required,min=1"`
UtcOffset int16 `json:"utcOffset" binding:"min=-720,max=840"`
SourceAccountId int64 `json:"sourceAccountId,string" binding:"required,min=1"`
DestinationAccountId int64 `json:"destinationAccountId,string" binding:"min=0"`
SourceAmount int64 `json:"sourceAmount" binding:"min=-99999999999,max=99999999999"`
DestinationAmount int64 `json:"destinationAmount" binding:"min=-99999999999,max=99999999999"`
HideAmount bool `json:"hideAmount"`
TagIds []string `json:"tagIds"`
Comment string `json:"comment" binding:"max=255"`
Type TransactionType `json:"type" binding:"required"`
CategoryId int64 `json:"categoryId,string"`
Time int64 `json:"time" binding:"required,min=1"`
UtcOffset int16 `json:"utcOffset" binding:"min=-720,max=840"`
SourceAccountId int64 `json:"sourceAccountId,string" binding:"required,min=1"`
DestinationAccountId int64 `json:"destinationAccountId,string" binding:"min=0"`
SourceAmount int64 `json:"sourceAmount" binding:"min=-99999999999,max=99999999999"`
DestinationAmount int64 `json:"destinationAmount" binding:"min=-99999999999,max=99999999999"`
HideAmount bool `json:"hideAmount"`
TagIds []string `json:"tagIds"`
Comment string `json:"comment" binding:"max=255"`
GeoLocation *TransactionGeoLocationRequest `json:"geoLocation" binding:"omitempty"`
}
// TransactionModifyRequest represents all parameters of transaction modification request
type TransactionModifyRequest struct {
Id int64 `json:"id,string" binding:"required,min=1"`
CategoryId int64 `json:"categoryId,string"`
Time int64 `json:"time" binding:"required,min=1"`
UtcOffset int16 `json:"utcOffset" binding:"min=-720,max=840"`
SourceAccountId int64 `json:"sourceAccountId,string" binding:"required,min=1"`
DestinationAccountId int64 `json:"destinationAccountId,string" binding:"min=0"`
SourceAmount int64 `json:"sourceAmount" binding:"min=-99999999999,max=99999999999"`
DestinationAmount int64 `json:"destinationAmount" binding:"min=-99999999999,max=99999999999"`
HideAmount bool `json:"hideAmount"`
TagIds []string `json:"tagIds"`
Comment string `json:"comment" binding:"max=255"`
Id int64 `json:"id,string" binding:"required,min=1"`
CategoryId int64 `json:"categoryId,string"`
Time int64 `json:"time" binding:"required,min=1"`
UtcOffset int16 `json:"utcOffset" binding:"min=-720,max=840"`
SourceAccountId int64 `json:"sourceAccountId,string" binding:"required,min=1"`
DestinationAccountId int64 `json:"destinationAccountId,string" binding:"min=0"`
SourceAmount int64 `json:"sourceAmount" binding:"min=-99999999999,max=99999999999"`
DestinationAmount int64 `json:"destinationAmount" binding:"min=-99999999999,max=99999999999"`
HideAmount bool `json:"hideAmount"`
TagIds []string `json:"tagIds"`
Comment string `json:"comment" binding:"max=255"`
GeoLocation *TransactionGeoLocationRequest `json:"geoLocation" binding:"omitempty"`
}
// TransactionCountRequest represents transaction count request
@@ -101,7 +112,7 @@ type TransactionListByMaxTimeRequest struct {
Keyword string `form:"keyword"`
MaxTime int64 `form:"max_time" binding:"min=0"`
MinTime int64 `form:"min_time" binding:"min=0"`
Count int `form:"count" binding:"required,min=1,max=50"`
Count int32 `form:"count" binding:"required,min=1,max=50"`
TrimAccount bool `form:"trim_account"`
TrimCategory bool `form:"trim_category"`
TrimTag bool `form:"trim_tag"`
@@ -109,14 +120,14 @@ type TransactionListByMaxTimeRequest struct {
// TransactionListInMonthByPageRequest represents all parameters of transaction listing by month request
type TransactionListInMonthByPageRequest struct {
Year int `form:"year" binding:"required,min=1"`
Month int `form:"month" binding:"required,min=1"`
Year int32 `form:"year" binding:"required,min=1"`
Month int32 `form:"month" binding:"required,min=1"`
Type TransactionDbType `form:"type" binding:"min=0,max=4"`
CategoryId int64 `form:"category_id" binding:"min=0"`
AccountId int64 `form:"account_id" binding:"min=0"`
Keyword string `form:"keyword"`
Page int `form:"page" binding:"required,min=1"`
Count int `form:"count" binding:"required,min=1,max=50"`
Page int32 `form:"page" binding:"required,min=1"`
Count int32 `form:"count" binding:"required,min=1,max=50"`
TrimAccount bool `form:"trim_account"`
TrimCategory bool `form:"trim_category"`
TrimTag bool `form:"trim_tag"`
@@ -169,6 +180,12 @@ type TransactionAccountAmount struct {
TotalExpenseAmount int64
}
// TransactionGeoLocationResponse represents a view-object of transaction geographic location info
type TransactionGeoLocationResponse struct {
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
}
// TransactionInfoResponse represents a view-object of transaction
type TransactionInfoResponse struct {
Id int64 `json:"id,string"`
@@ -188,6 +205,7 @@ type TransactionInfoResponse struct {
TagIds []string `json:"tagIds"`
Tags []*TransactionTagInfoResponse `json:"tags,omitempty"`
Comment string `json:"comment"`
GeoLocation *TransactionGeoLocationResponse `json:"geoLocation,omitempty"`
Editable bool `json:"editable"`
}
@@ -231,8 +249,8 @@ type TransactionAmountsResponseItem struct {
// TransactionMonthAmountsResponseItem represents an item of transaction month amounts
type TransactionMonthAmountsResponseItem struct {
Year int `json:"year"`
Month int `json:"month"`
Year int32 `json:"year"`
Month int32 `json:"month"`
Amounts []*TransactionAmountsResponseItemAmountInfo `json:"amounts"`
}
@@ -297,6 +315,15 @@ func (t *Transaction) ToTransactionInfoResponse(tagIds []int64, editable bool) *
destinationAmount = t.Amount
}
geoLocation := &TransactionGeoLocationResponse{}
if t.GeoLongitude != 0 || t.GeoLatitude != 0 {
geoLocation.Longitude = t.GeoLongitude
geoLocation.Latitude = t.GeoLatitude
} else {
geoLocation = nil
}
return &TransactionInfoResponse{
Id: t.TransactionId,
TimeSequenceId: t.TransactionTime,
@@ -311,6 +338,7 @@ func (t *Transaction) ToTransactionInfoResponse(tagIds []int64, editable bool) *
HideAmount: t.HideAmount,
TagIds: utils.Int64ArrayToStringArray(tagIds),
Comment: t.Comment,
GeoLocation: geoLocation,
Editable: editable,
}
}
+3 -3
View File
@@ -21,7 +21,7 @@ type TransactionCategory struct {
Type TransactionCategoryType `xorm:"INDEX(IDX_category_uid_deleted_type_parent_category_id_order) NOT NULL"`
ParentCategoryId int64 `xorm:"INDEX(IDX_category_uid_deleted_type_parent_category_id_order) NOT NULL"`
Name string `xorm:"VARCHAR(32) NOT NULL"`
DisplayOrder int `xorm:"INDEX(IDX_category_uid_deleted_type_parent_category_id_order) NOT NULL"`
DisplayOrder int32 `xorm:"INDEX(IDX_category_uid_deleted_type_parent_category_id_order) NOT NULL"`
Icon int64 `xorm:"NOT NULL"`
Color string `xorm:"VARCHAR(6) NOT NULL"`
Hidden bool `xorm:"NOT NULL"`
@@ -91,7 +91,7 @@ type TransactionCategoryMoveRequest struct {
// TransactionCategoryNewDisplayOrderRequest represents a data pair of id and display order
type TransactionCategoryNewDisplayOrderRequest struct {
Id int64 `json:"id,string" binding:"required,min=1"`
DisplayOrder int `json:"displayOrder"`
DisplayOrder int32 `json:"displayOrder"`
}
// TransactionCategoryDeleteRequest represents all parameters of transaction category deleting request
@@ -108,7 +108,7 @@ type TransactionCategoryInfoResponse struct {
Icon int64 `json:"icon,string"`
Color string `json:"color"`
Comment string `json:"comment"`
DisplayOrder int `json:"displayOrder"`
DisplayOrder int32 `json:"displayOrder"`
Hidden bool `json:"hidden"`
SubCategories TransactionCategoryInfoResponseSlice `json:"subCategories,omitempty"`
}
+3 -3
View File
@@ -6,7 +6,7 @@ type TransactionTag struct {
Uid int64 `xorm:"INDEX(IDX_tag_uid_deleted_name) NOT NULL"`
Deleted bool `xorm:"INDEX(IDX_tag_uid_deleted_name) NOT NULL"`
Name string `xorm:"INDEX(IDX_tag_uid_deleted_name) VARCHAR(32) NOT NULL"`
DisplayOrder int `xorm:"NOT NULL"`
DisplayOrder int32 `xorm:"NOT NULL"`
Hidden bool `xorm:"NOT NULL"`
CreatedUnixTime int64
UpdatedUnixTime int64
@@ -43,7 +43,7 @@ type TransactionTagMoveRequest struct {
// TransactionTagNewDisplayOrderRequest represents a data pair of id and display order
type TransactionTagNewDisplayOrderRequest struct {
Id int64 `json:"id,string" binding:"required,min=1"`
DisplayOrder int `json:"displayOrder"`
DisplayOrder int32 `json:"displayOrder"`
}
// TransactionTagDeleteRequest represents all parameters of transaction tag deleting request
@@ -55,7 +55,7 @@ type TransactionTagDeleteRequest struct {
type TransactionTagInfoResponse struct {
Id int64 `json:"id,string"`
Name string `json:"name"`
DisplayOrder int `json:"displayOrder"`
DisplayOrder int32 `json:"displayOrder"`
Hidden bool `json:"hidden"`
}
+49 -51
View File
@@ -7,45 +7,6 @@ import (
"github.com/mayswind/ezbookkeeping/pkg/utils"
)
// WeekDay represents week day
type WeekDay byte
// Week days
const (
WEEKDAY_SUNDAY WeekDay = 0
WEEKDAY_MONDAY WeekDay = 1
WEEKDAY_TUESDAY WeekDay = 2
WEEKDAY_WEDNESDAY WeekDay = 3
WEEKDAY_THURSDAY WeekDay = 4
WEEKDAY_FRIDAY WeekDay = 5
WEEKDAY_SATURDAY WeekDay = 6
WEEKDAY_INVALID WeekDay = 255
)
// String returns a textual representation of the week day enum
func (d WeekDay) String() string {
switch d {
case WEEKDAY_SUNDAY:
return "Sunday"
case WEEKDAY_MONDAY:
return "Monday"
case WEEKDAY_TUESDAY:
return "Tuesday"
case WEEKDAY_WEDNESDAY:
return "Wednesday"
case WEEKDAY_THURSDAY:
return "Thursday"
case WEEKDAY_FRIDAY:
return "Friday"
case WEEKDAY_SATURDAY:
return "Saturday"
case WEEKDAY_INVALID:
return "Invalid"
default:
return fmt.Sprintf("Invalid(%d)", int(d))
}
}
// TransactionEditScope represents the scope which transaction can be edited
type TransactionEditScope byte
@@ -87,15 +48,21 @@ func (s TransactionEditScope) String() string {
// User represents user data stored in database
type User struct {
Uid int64 `xorm:"PK"`
Username string `xorm:"VARCHAR(32) UNIQUE NOT NULL"`
Email string `xorm:"VARCHAR(100) UNIQUE NOT NULL"`
Nickname string `xorm:"VARCHAR(64) NOT NULL"`
Password string `xorm:"VARCHAR(64) NOT NULL"`
Salt string `xorm:"VARCHAR(10) NOT NULL"`
Uid int64 `xorm:"PK"`
Username string `xorm:"VARCHAR(32) UNIQUE NOT NULL"`
Email string `xorm:"VARCHAR(100) UNIQUE NOT NULL"`
Nickname string `xorm:"VARCHAR(64) NOT NULL"`
Password string `xorm:"VARCHAR(64) NOT NULL"`
Salt string `xorm:"VARCHAR(10) NOT NULL"`
DefaultAccountId int64
TransactionEditScope TransactionEditScope `xorm:"TINYINT NOT NULL"`
Language string `xorm:"VARCHAR(10)"`
DefaultCurrency string `xorm:"VARCHAR(3) NOT NULL"`
FirstDayOfWeek WeekDay `xorm:"TINYINT NOT NULL"`
TransactionEditScope TransactionEditScope `xorm:"TINYINT NOT NULL"`
LongDateFormat LongDateFormat `xorm:"TINYINT"`
ShortDateFormat ShortDateFormat `xorm:"TINYINT"`
LongTimeFormat LongTimeFormat `xorm:"TINYINT"`
ShortTimeFormat ShortTimeFormat `xorm:"TINYINT"`
Deleted bool `xorm:"NOT NULL"`
EmailVerified bool `xorm:"NOT NULL"`
CreatedUnixTime int64
@@ -109,9 +76,15 @@ type UserBasicInfo struct {
Username string `json:"username"`
Email string `json:"email"`
Nickname string `json:"nickname"`
DefaultAccountId int64 `json:"defaultAccountId,string"`
TransactionEditScope TransactionEditScope `json:"transactionEditScope"`
Language string `json:"language"`
DefaultCurrency string `json:"defaultCurrency"`
FirstDayOfWeek WeekDay `json:"firstDayOfWeek"`
TransactionEditScope TransactionEditScope `json:"transactionEditScope"`
LongDateFormat LongDateFormat `json:"longDateFormat"`
ShortDateFormat ShortDateFormat `json:"shortDateFormat"`
LongTimeFormat LongTimeFormat `json:"longTimeFormat"`
ShortTimeFormat ShortTimeFormat `json:"shortTimeFormat"`
}
// UserLoginRequest represents all parameters of user login request
@@ -126,6 +99,7 @@ type UserRegisterRequest struct {
Email string `json:"email" binding:"required,notBlank,max=100,validEmail"`
Nickname string `json:"nickname" binding:"required,notBlank,max=64"`
Password string `json:"password" binding:"required,min=6,max=128"`
Language string `json:"language" binding:"required,min=2,max=16"`
DefaultCurrency string `json:"defaultCurrency" binding:"required,len=3,validCurrency"`
FirstDayOfWeek WeekDay `json:"firstDayOfWeek" binding:"min=0,max=6"`
}
@@ -136,9 +110,15 @@ type UserProfileUpdateRequest struct {
Nickname string `json:"nickname" binding:"omitempty,notBlank,max=64"`
Password string `json:"password" binding:"omitempty,min=6,max=128"`
OldPassword string `json:"oldPassword" binding:"omitempty,min=6,max=128"`
DefaultAccountId int64 `json:"defaultAccountId,string" binding:"omitempty,min=1"`
TransactionEditScope *TransactionEditScope `json:"transactionEditScope" binding:"omitempty,min=0,max=7"`
Language string `json:"language" binding:"omitempty,min=2,max=16"`
DefaultCurrency string `json:"defaultCurrency" binding:"omitempty,len=3,validCurrency"`
FirstDayOfWeek *WeekDay `json:"firstDayOfWeek" binding:"omitempty,min=0,max=6"`
TransactionEditScope *TransactionEditScope `json:"transactionEditScope" binding:"omitempty,min=0,max=7"`
LongDateFormat *LongDateFormat `json:"longDateFormat" binding:"omitempty,min=0,max=3"`
ShortDateFormat *ShortDateFormat `json:"shortDateFormat" binding:"omitempty,min=0,max=3"`
LongTimeFormat *LongTimeFormat `json:"longTimeFormat" binding:"omitempty,min=0,max=3"`
ShortTimeFormat *ShortTimeFormat `json:"shortTimeFormat" binding:"omitempty,min=0,max=3"`
}
// UserProfileUpdateResponse represents the data returns to frontend after updating profile
@@ -152,9 +132,15 @@ type UserProfileResponse struct {
Username string `json:"username"`
Email string `json:"email"`
Nickname string `json:"nickname"`
DefaultAccountId int64 `json:"defaultAccountId,string"`
TransactionEditScope TransactionEditScope `json:"transactionEditScope"`
Language string `json:"language"`
DefaultCurrency string `json:"defaultCurrency"`
FirstDayOfWeek WeekDay `json:"firstDayOfWeek"`
TransactionEditScope TransactionEditScope `json:"transactionEditScope"`
LongDateFormat LongDateFormat `json:"longDateFormat"`
ShortDateFormat ShortDateFormat `json:"shortDateFormat"`
LongTimeFormat LongTimeFormat `json:"longTimeFormat"`
ShortTimeFormat ShortTimeFormat `json:"shortTimeFormat"`
LastLoginAt int64 `json:"lastLoginAt"`
}
@@ -206,9 +192,15 @@ func (u *User) ToUserBasicInfo() *UserBasicInfo {
Username: u.Username,
Email: u.Email,
Nickname: u.Nickname,
DefaultAccountId: u.DefaultAccountId,
TransactionEditScope: u.TransactionEditScope,
Language: u.Language,
DefaultCurrency: u.DefaultCurrency,
FirstDayOfWeek: u.FirstDayOfWeek,
TransactionEditScope: u.TransactionEditScope,
LongDateFormat: u.LongDateFormat,
ShortDateFormat: u.ShortDateFormat,
LongTimeFormat: u.LongTimeFormat,
ShortTimeFormat: u.ShortTimeFormat,
}
}
@@ -218,9 +210,15 @@ func (u *User) ToUserProfileResponse() *UserProfileResponse {
Username: u.Username,
Email: u.Email,
Nickname: u.Nickname,
DefaultAccountId: u.DefaultAccountId,
TransactionEditScope: u.TransactionEditScope,
Language: u.Language,
DefaultCurrency: u.DefaultCurrency,
FirstDayOfWeek: u.FirstDayOfWeek,
TransactionEditScope: u.TransactionEditScope,
LongDateFormat: u.LongDateFormat,
ShortDateFormat: u.ShortDateFormat,
LongTimeFormat: u.LongTimeFormat,
ShortTimeFormat: u.ShortTimeFormat,
LastLoginAt: u.LastLoginUnixTime,
}
}
+29 -2
View File
@@ -30,6 +30,17 @@ var (
}
)
// GetTotalAccountCountByUid returns total account count of user
func (s *AccountService) GetTotalAccountCountByUid(uid int64) (int64, error) {
if uid <= 0 {
return 0, errs.ErrUserIdInvalid
}
count, err := s.UserDataDB(uid).Where("uid=? AND deleted=?", uid, false).Count(&models.Account{})
return count, err
}
// GetAllAccountsByUid returns all account models of user
func (s *AccountService) GetAllAccountsByUid(uid int64) ([]*models.Account, error) {
if uid <= 0 {
@@ -58,6 +69,22 @@ func (s *AccountService) GetAccountAndSubAccountsByAccountId(uid int64, accountI
return accounts, err
}
// GetSubAccountsByAccountId returns sub account models according to account id
func (s *AccountService) GetSubAccountsByAccountId(uid int64, accountId int64) ([]*models.Account, error) {
if uid <= 0 {
return nil, errs.ErrUserIdInvalid
}
if accountId <= 0 {
return nil, errs.ErrAccountIdInvalid
}
var accounts []*models.Account
err := s.UserDataDB(uid).Where("uid=? AND deleted=? AND parent_account_id=?", uid, false, accountId).OrderBy("display_order asc").Find(&accounts)
return accounts, err
}
// GetAccountsByAccountIds returns account models according to account ids
func (s *AccountService) GetAccountsByAccountIds(uid int64, accountIds []int64) (map[int64]*models.Account, error) {
if uid <= 0 {
@@ -80,7 +107,7 @@ func (s *AccountService) GetAccountsByAccountIds(uid int64, accountIds []int64)
}
// GetMaxDisplayOrder returns the max display order according to account category
func (s *AccountService) GetMaxDisplayOrder(uid int64, category models.AccountCategory) (int, error) {
func (s *AccountService) GetMaxDisplayOrder(uid int64, category models.AccountCategory) (int32, error) {
if uid <= 0 {
return 0, errs.ErrUserIdInvalid
}
@@ -100,7 +127,7 @@ func (s *AccountService) GetMaxDisplayOrder(uid int64, category models.AccountCa
}
// GetMaxSubAccountDisplayOrder returns the max display order of sub account according to account category and parent account id
func (s *AccountService) GetMaxSubAccountDisplayOrder(uid int64, category models.AccountCategory, parentAccountId int64) (int, error) {
func (s *AccountService) GetMaxSubAccountDisplayOrder(uid int64, category models.AccountCategory, parentAccountId int64) (int32, error) {
if uid <= 0 {
return 0, errs.ErrUserIdInvalid
}
+5
View File
@@ -45,3 +45,8 @@ type ServiceUsingUuid struct {
func (s *ServiceUsingUuid) GenerateUuid(uuidType uuid.UuidType) int64 {
return s.container.GenerateUuid(uuidType)
}
// GenerateUuids generates new uuids according to given uuid type and count
func (s *ServiceUsingUuid) GenerateUuids(uuidType uuid.UuidType, count uint8) []int64 {
return s.container.GenerateUuids(uuidType, count)
}
+73 -54
View File
@@ -6,8 +6,8 @@ import (
"strings"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/dgrijalva/jwt-go/request"
"github.com/golang-jwt/jwt/v5"
"github.com/golang-jwt/jwt/v5/request"
"xorm.io/xorm"
"github.com/mayswind/ezbookkeeping/pkg/core"
@@ -63,47 +63,14 @@ func (s *TokenService) GetAllUnexpiredNormalTokensByUid(uid int64) ([]*models.To
return tokenRecords, err
}
// ParseToken returns the token model according to request data
func (s *TokenService) ParseToken(c *core.Context) (*jwt.Token, *core.UserTokenClaims, error) {
claims := &core.UserTokenClaims{}
// ParseTokenByHeader returns the token model according to request data
func (s *TokenService) ParseTokenByHeader(c *core.Context) (*jwt.Token, *core.UserTokenClaims, error) {
return s.parseToken(c, request.BearerExtractor{})
}
token, err := request.ParseFromRequest(c.Request, request.AuthorizationHeaderExtractor,
func(token *jwt.Token) (interface{}, error) {
uid, err := utils.StringToInt64(claims.Id)
now := time.Now().Unix()
if err != nil {
log.WarnfWithRequestId(c, "[tokens.ParseToken] user \"uid:%s\" in token is invalid, because %s", claims.Id, err.Error())
return nil, errs.ErrInvalidToken
}
userTokenId, err := utils.StringToInt64(claims.UserTokenId)
if err != nil {
log.WarnfWithRequestId(c, "[tokens.ParseToken] token \"utid:%s\" in token of user \"uid:%s\" is invalid, because %s", claims.UserTokenId, claims.Id, err.Error())
return nil, errs.ErrInvalidUserTokenId
}
tokenRecord, err := s.getTokenRecord(uid, userTokenId, claims.IssuedAt)
if err != nil {
log.WarnfWithRequestId(c, "[tokens.ParseToken] token \"utid:%s\" of user \"uid:%s\" record not found, because %s", claims.UserTokenId, claims.Id, err.Error())
return nil, errs.ErrTokenRecordNotFound
}
if tokenRecord.ExpiredUnixTime < now {
log.WarnfWithRequestId(c, "[tokens.ParseToken] token \"utid:%s\" of user \"uid:%s\" record is expired", claims.UserTokenId, claims.Id)
return nil, errs.ErrTokenExpired
}
return []byte(tokenRecord.Secret), nil
}, request.WithClaims(claims))
if err != nil {
return nil, nil, err
}
return token, claims, err
// ParseTokenByArgument returns the token model according to request data
func (s *TokenService) ParseTokenByArgument(c *core.Context, tokenParameterName string) (*jwt.Token, *core.UserTokenClaims, error) {
return s.parseToken(c, request.ArgumentExtractor{tokenParameterName})
}
// CreateToken generates a new normal token and saves to database
@@ -163,19 +130,17 @@ func (s *TokenService) DeleteTokens(uid int64, tokenRecords []*models.TokenRecor
// DeleteTokenByClaims deletes given token from database
func (s *TokenService) DeleteTokenByClaims(claims *core.UserTokenClaims) error {
uid, err := utils.StringToInt64(claims.Id)
if err != nil {
return errs.ErrUserIdInvalid
}
userTokenId, err := utils.StringToInt64(claims.UserTokenId)
if err != nil {
return errs.ErrInvalidUserTokenId
}
return s.DeleteToken(&models.TokenRecord{Uid: uid, UserTokenId: userTokenId, CreatedUnixTime: claims.IssuedAt})
return s.DeleteToken(&models.TokenRecord{
Uid: claims.Uid,
UserTokenId: userTokenId,
CreatedUnixTime: claims.IssuedAt,
})
}
// DeleteTokensBeforeTime deletes tokens that is created before specific tim
@@ -230,6 +195,62 @@ func (s *TokenService) GenerateTokenId(tokenRecord *models.TokenRecord) string {
return fmt.Sprintf("%d:%d:%d", tokenRecord.Uid, tokenRecord.CreatedUnixTime, tokenRecord.UserTokenId)
}
func (s *TokenService) parseToken(c *core.Context, extractor request.Extractor) (*jwt.Token, *core.UserTokenClaims, error) {
claims := &core.UserTokenClaims{}
token, err := request.ParseFromRequest(c.Request, extractor,
func(token *jwt.Token) (interface{}, error) {
now := time.Now().Unix()
userTokenId, err := utils.StringToInt64(claims.UserTokenId)
if err != nil {
log.WarnfWithRequestId(c, "[tokens.ParseToken] token \"utid:%s\" in token of user \"uid:%d\" is invalid, because %s", claims.UserTokenId, claims.Uid, err.Error())
return nil, errs.ErrInvalidUserTokenId
}
tokenRecord, err := s.getTokenRecord(claims.Uid, userTokenId, claims.IssuedAt)
if err != nil {
log.WarnfWithRequestId(c, "[tokens.ParseToken] token \"utid:%s\" of user \"uid:%d\" record not found, because %s", claims.UserTokenId, claims.Uid, err.Error())
return nil, errs.ErrTokenRecordNotFound
}
if tokenRecord.ExpiredUnixTime < now {
log.WarnfWithRequestId(c, "[tokens.ParseToken] token \"utid:%s\" of user \"uid:%d\" record is expired", claims.UserTokenId, claims.Uid)
return nil, errs.ErrTokenExpired
}
return []byte(tokenRecord.Secret), nil
},
request.WithClaims(claims),
request.WithParser(jwt.NewParser(jwt.WithIssuedAt())),
)
if err != nil {
if err == request.ErrNoTokenInRequest {
return nil, nil, errs.ErrTokenIsEmpty
}
if err == jwt.ErrTokenMalformed || err == jwt.ErrTokenUnverifiable || err == jwt.ErrTokenSignatureInvalid {
log.WarnfWithRequestId(c, "[tokens.ParseToken] token is invalid, because %s", err.Error())
return nil, nil, errs.ErrCurrentInvalidToken
}
if err == jwt.ErrTokenExpired {
return nil, nil, errs.ErrCurrentTokenExpired
}
if err == jwt.ErrTokenUsedBeforeIssued {
log.WarnfWithRequestId(c, "[tokens.ParseToken] token is invalid, because issue time is later than now")
return nil, nil, errs.ErrCurrentInvalidToken
}
return nil, nil, err
}
return token, claims, err
}
func (s *TokenService) createToken(user *models.User, tokenType core.TokenType, userAgent string, expiryDate time.Duration) (string, *core.UserTokenClaims, error) {
var err error
now := time.Now()
@@ -249,13 +270,11 @@ func (s *TokenService) createToken(user *models.User, tokenType core.TokenType,
claims := &core.UserTokenClaims{
UserTokenId: utils.Int64ToString(tokenRecord.UserTokenId),
Uid: tokenRecord.Uid,
Username: user.Username,
Type: tokenRecord.TokenType,
StandardClaims: jwt.StandardClaims{
Id: utils.Int64ToString(tokenRecord.Uid),
IssuedAt: tokenRecord.CreatedUnixTime,
ExpiresAt: tokenRecord.ExpiredUnixTime,
},
IssuedAt: tokenRecord.CreatedUnixTime,
ExpiresAt: tokenRecord.ExpiredUnixTime,
}
jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+13 -2
View File
@@ -29,6 +29,17 @@ var (
}
)
// GetTotalCategoryCountByUid returns total category count of user
func (s *TransactionCategoryService) GetTotalCategoryCountByUid(uid int64) (int64, error) {
if uid <= 0 {
return 0, errs.ErrUserIdInvalid
}
count, err := s.UserDataDB(uid).Where("uid=? AND deleted=?", uid, false).Count(&models.TransactionCategory{})
return count, err
}
// GetAllCategoriesByUid returns all transaction category models of user
func (s *TransactionCategoryService) GetAllCategoriesByUid(uid int64, categoryType models.TransactionCategoryType, parentCategoryId int64) ([]*models.TransactionCategory, error) {
if uid <= 0 {
@@ -100,7 +111,7 @@ func (s *TransactionCategoryService) GetCategoriesByCategoryIds(uid int64, categ
}
// GetMaxDisplayOrder returns the max display order according to transaction category type
func (s *TransactionCategoryService) GetMaxDisplayOrder(uid int64, categoryType models.TransactionCategoryType) (int, error) {
func (s *TransactionCategoryService) GetMaxDisplayOrder(uid int64, categoryType models.TransactionCategoryType) (int32, error) {
if uid <= 0 {
return 0, errs.ErrUserIdInvalid
}
@@ -120,7 +131,7 @@ func (s *TransactionCategoryService) GetMaxDisplayOrder(uid int64, categoryType
}
// GetMaxSubCategoryDisplayOrder returns the max display order of sub transaction category according to transaction category type and parent transaction category id
func (s *TransactionCategoryService) GetMaxSubCategoryDisplayOrder(uid int64, categoryType models.TransactionCategoryType, parentCategoryId int64) (int, error) {
func (s *TransactionCategoryService) GetMaxSubCategoryDisplayOrder(uid int64, categoryType models.TransactionCategoryType, parentCategoryId int64) (int32, error) {
if uid <= 0 {
return 0, errs.ErrUserIdInvalid
}
+12 -1
View File
@@ -29,6 +29,17 @@ var (
}
)
// GetTotalTagCountByUid returns total tag count of user
func (s *TransactionTagService) GetTotalTagCountByUid(uid int64) (int64, error) {
if uid <= 0 {
return 0, errs.ErrUserIdInvalid
}
count, err := s.UserDataDB(uid).Where("uid=? AND deleted=?", uid, false).Count(&models.TransactionTag{})
return count, err
}
// GetAllTagsByUid returns all transaction tag models of user
func (s *TransactionTagService) GetAllTagsByUid(uid int64) ([]*models.TransactionTag, error) {
if uid <= 0 {
@@ -85,7 +96,7 @@ func (s *TransactionTagService) GetTagsByTagIds(uid int64, tagIds []int64) (map[
}
// GetMaxDisplayOrder returns the max display order
func (s *TransactionTagService) GetMaxDisplayOrder(uid int64) (int, error) {
func (s *TransactionTagService) GetMaxDisplayOrder(uid int64) (int32, error) {
if uid <= 0 {
return 0, errs.ErrUserIdInvalid
}
+72 -29
View File
@@ -32,8 +32,19 @@ var (
}
)
// GetTotalTransactionCountByUid returns total transaction count of user
func (s *TransactionService) GetTotalTransactionCountByUid(uid int64) (int64, error) {
if uid <= 0 {
return 0, errs.ErrUserIdInvalid
}
count, err := s.UserDataDB(uid).Where("uid=? AND deleted=?", uid, false).Count(&models.Transaction{})
return count, err
}
// GetAllTransactions returns all transactions
func (s *TransactionService) GetAllTransactions(uid int64, pageCount int, noDuplicated bool) ([]*models.Transaction, error) {
func (s *TransactionService) GetAllTransactions(uid int64, pageCount int32, noDuplicated bool) ([]*models.Transaction, error) {
maxTransactionTime := utils.GetMaxTransactionTimeFromUnixTime(time.Now().Unix())
var allTransactions []*models.Transaction
@@ -46,7 +57,7 @@ func (s *TransactionService) GetAllTransactions(uid int64, pageCount int, noDupl
allTransactions = append(allTransactions, transactions...)
if len(transactions) < pageCount {
if len(transactions) < int(pageCount) {
maxTransactionTime = 0
break
}
@@ -58,12 +69,12 @@ func (s *TransactionService) GetAllTransactions(uid int64, pageCount int, noDupl
}
// GetAllTransactionsByMaxTime returns all transactions before given time
func (s *TransactionService) GetAllTransactionsByMaxTime(uid int64, maxTransactionTime int64, count int, noDuplicated bool) ([]*models.Transaction, error) {
return s.GetTransactionsByMaxTime(uid, maxTransactionTime, 0, 0, nil, 0, "", count, noDuplicated)
func (s *TransactionService) GetAllTransactionsByMaxTime(uid int64, maxTransactionTime int64, count int32, noDuplicated bool) ([]*models.Transaction, error) {
return s.GetTransactionsByMaxTime(uid, maxTransactionTime, 0, 0, nil, nil, "", count, noDuplicated)
}
// GetTransactionsByMaxTime returns transactions before given time
func (s *TransactionService) GetTransactionsByMaxTime(uid int64, maxTransactionTime int64, minTransactionTime int64, transactionType models.TransactionDbType, categoryIds []int64, accountId int64, keyword string, count int, noDuplicated bool) ([]*models.Transaction, error) {
func (s *TransactionService) GetTransactionsByMaxTime(uid int64, maxTransactionTime int64, minTransactionTime int64, transactionType models.TransactionDbType, categoryIds []int64, accountIds []int64, keyword string, count int32, noDuplicated bool) ([]*models.Transaction, error) {
if uid <= 0 {
return nil, errs.ErrUserIdInvalid
}
@@ -75,14 +86,14 @@ func (s *TransactionService) GetTransactionsByMaxTime(uid int64, maxTransactionT
var transactions []*models.Transaction
var err error
condition, conditionParams := s.getTransactionQueryCondition(uid, maxTransactionTime, minTransactionTime, transactionType, categoryIds, accountId, keyword, noDuplicated)
err = s.UserDataDB(uid).Where(condition, conditionParams...).Limit(count, 0).OrderBy("transaction_time desc").Find(&transactions)
condition, conditionParams := s.getTransactionQueryCondition(uid, maxTransactionTime, minTransactionTime, transactionType, categoryIds, accountIds, keyword, noDuplicated)
err = s.UserDataDB(uid).Where(condition, conditionParams...).Limit(int(count), 0).OrderBy("transaction_time desc").Find(&transactions)
return transactions, err
}
// GetTransactionsInMonthByPage returns transactions in given year and month
func (s *TransactionService) GetTransactionsInMonthByPage(uid int64, year int, month int, transactionType models.TransactionDbType, categoryIds []int64, accountId int64, keyword string, page int, count int, utcOffset int16) ([]*models.Transaction, error) {
func (s *TransactionService) GetTransactionsInMonthByPage(uid int64, year int32, month int32, transactionType models.TransactionDbType, categoryIds []int64, accountIds []int64, keyword string, page int32, count int32, utcOffset int16) ([]*models.Transaction, error) {
if uid <= 0 {
return nil, errs.ErrUserIdInvalid
}
@@ -108,8 +119,8 @@ func (s *TransactionService) GetTransactionsInMonthByPage(uid int64, year int, m
var transactions []*models.Transaction
condition, conditionParams := s.getTransactionQueryCondition(uid, maxTransactionTime, minTransactionTime, transactionType, categoryIds, accountId, keyword, true)
err = s.UserDataDB(uid).Where(condition, conditionParams...).Limit(count, count*(page-1)).OrderBy("transaction_time desc").Find(&transactions)
condition, conditionParams := s.getTransactionQueryCondition(uid, maxTransactionTime, minTransactionTime, transactionType, categoryIds, accountIds, keyword, true)
err = s.UserDataDB(uid).Where(condition, conditionParams...).Limit(int(count), int(count*(page-1))).OrderBy("transaction_time desc").Find(&transactions)
return transactions, err
}
@@ -138,11 +149,11 @@ func (s *TransactionService) GetTransactionByTransactionId(uid int64, transactio
// GetAllTransactionCount returns total count of transactions
func (s *TransactionService) GetAllTransactionCount(uid int64) (int64, error) {
return s.GetTransactionCount(uid, 0, 0, 0, nil, 0, "")
return s.GetTransactionCount(uid, 0, 0, 0, nil, nil, "")
}
// GetMonthTransactionCount returns total count of transactions in given year and month
func (s *TransactionService) GetMonthTransactionCount(uid int64, year int, month int, transactionType models.TransactionDbType, categoryIds []int64, accountId int64, keyword string, utcOffset int16) (int64, error) {
func (s *TransactionService) GetMonthTransactionCount(uid int64, year int32, month int32, transactionType models.TransactionDbType, categoryIds []int64, accountIds []int64, keyword string, utcOffset int16) (int64, error) {
if uid <= 0 {
return 0, errs.ErrUserIdInvalid
}
@@ -158,16 +169,16 @@ func (s *TransactionService) GetMonthTransactionCount(uid int64, year int, month
minTransactionTime := utils.GetMinTransactionTimeFromUnixTime(startTime.Unix())
maxTransactionTime := utils.GetMinTransactionTimeFromUnixTime(endTime.Unix()) - 1
return s.GetTransactionCount(uid, maxTransactionTime, minTransactionTime, transactionType, categoryIds, accountId, keyword)
return s.GetTransactionCount(uid, maxTransactionTime, minTransactionTime, transactionType, categoryIds, accountIds, keyword)
}
// GetTransactionCount returns count of transactions
func (s *TransactionService) GetTransactionCount(uid int64, maxTransactionTime int64, minTransactionTime int64, transactionType models.TransactionDbType, categoryIds []int64, accountId int64, keyword string) (int64, error) {
func (s *TransactionService) GetTransactionCount(uid int64, maxTransactionTime int64, minTransactionTime int64, transactionType models.TransactionDbType, categoryIds []int64, accountIds []int64, keyword string) (int64, error) {
if uid <= 0 {
return 0, errs.ErrUserIdInvalid
}
condition, conditionParams := s.getTransactionQueryCondition(uid, maxTransactionTime, minTransactionTime, transactionType, categoryIds, accountId, keyword, true)
condition, conditionParams := s.getTransactionQueryCondition(uid, maxTransactionTime, minTransactionTime, transactionType, categoryIds, accountIds, keyword, true)
return s.UserDataDB(uid).Where(condition, conditionParams...).Count(&models.Transaction{})
}
@@ -186,7 +197,19 @@ func (s *TransactionService) CreateTransaction(transaction *models.Transaction,
now := time.Now().Unix()
transaction.TransactionId = s.GenerateUuid(uuid.UUID_TYPE_TRANSACTION)
needUuidCount := 1
if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT || transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN {
needUuidCount = 2
}
uuids := s.GenerateUuids(uuid.UUID_TYPE_TRANSACTION, uint8(needUuidCount))
transaction.TransactionId = uuids[0]
if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT || transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN {
transaction.RelatedId = uuids[1]
}
transaction.TransactionTime = utils.GetMinTransactionTimeFromUnixTime(utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime))
transaction.CreatedUnixTime = now
@@ -256,8 +279,7 @@ func (s *TransactionService) CreateTransaction(transaction *models.Transaction,
var relatedTransaction *models.Transaction
if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT || transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN {
relatedTransaction = s.GetRelatedTransferTransaction(transaction, s.GenerateUuid(uuid.UUID_TYPE_TRANSACTION))
transaction.RelatedId = relatedTransaction.TransactionId
relatedTransaction = s.GetRelatedTransferTransaction(transaction)
}
createdRows, err := sess.Insert(transaction)
@@ -520,6 +542,14 @@ func (s *TransactionService) ModifyTransaction(transaction *models.Transaction,
updateCols = append(updateCols, "comment")
}
if transaction.GeoLongitude != oldTransaction.GeoLongitude {
updateCols = append(updateCols, "geo_longitude")
}
if transaction.GeoLatitude != oldTransaction.GeoLatitude {
updateCols = append(updateCols, "geo_latitude")
}
// Get and verify tags
err = s.isTagsValid(sess, transaction, transactionTagIndexs, addTagIds)
@@ -537,7 +567,7 @@ func (s *TransactionService) ModifyTransaction(transaction *models.Transaction,
}
if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT || transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN {
relatedTransaction := s.GetRelatedTransferTransaction(transaction, transaction.RelatedId)
relatedTransaction := s.GetRelatedTransferTransaction(transaction)
if utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime) != utils.GetUnixTimeFromTransactionTime(relatedTransaction.TransactionTime) {
return errs.ErrTooMuchTransactionInOneSecond
@@ -905,7 +935,7 @@ func (s *TransactionService) DeleteAllTransactions(uid int64) error {
}
// GetRelatedTransferTransaction returns the related transaction for transfer transaction
func (s *TransactionService) GetRelatedTransferTransaction(originalTransaction *models.Transaction, relatedTransactionId int64) *models.Transaction {
func (s *TransactionService) GetRelatedTransferTransaction(originalTransaction *models.Transaction) *models.Transaction {
var relatedType models.TransactionDbType
var relatedTransactionTime int64
@@ -920,7 +950,7 @@ func (s *TransactionService) GetRelatedTransferTransaction(originalTransaction *
}
relatedTransaction := &models.Transaction{
TransactionId: relatedTransactionId,
TransactionId: originalTransaction.RelatedId,
Uid: originalTransaction.Uid,
Deleted: originalTransaction.Deleted,
Type: relatedType,
@@ -933,6 +963,9 @@ func (s *TransactionService) GetRelatedTransferTransaction(originalTransaction *
RelatedAccountId: originalTransaction.AccountId,
RelatedAccountAmount: originalTransaction.Amount,
Comment: originalTransaction.Comment,
GeoLongitude: originalTransaction.GeoLongitude,
GeoLatitude: originalTransaction.GeoLatitude,
CreatedIp: originalTransaction.CreatedIp,
CreatedUnixTime: originalTransaction.CreatedUnixTime,
UpdatedUnixTime: originalTransaction.UpdatedUnixTime,
DeletedUnixTime: originalTransaction.DeletedUnixTime,
@@ -951,7 +984,7 @@ func (s *TransactionService) GetAccountsTotalIncomeAndExpense(uid int64, startUn
endTransactionTime := utils.GetMaxTransactionTimeFromUnixTime(endUnixTime)
var transactionTotalAmounts []*models.Transaction
err := s.UserDataDB(uid).Select("uid, type, account_id, SUM(amount) as amount").Where("uid=? AND deleted=? AND (type=? OR type=?) AND transaction_time>=? AND transaction_time<=?", uid, false, models.TRANSACTION_DB_TYPE_INCOME, models.TRANSACTION_DB_TYPE_EXPENSE, startTransactionTime, endTransactionTime).GroupBy("type, account_id").Find(&transactionTotalAmounts)
err := s.UserDataDB(uid).Select("type, account_id, SUM(amount) as amount").Where("uid=? AND deleted=? AND (type=? OR type=?) AND transaction_time>=? AND transaction_time<=?", uid, false, models.TRANSACTION_DB_TYPE_INCOME, models.TRANSACTION_DB_TYPE_EXPENSE, startTransactionTime, endTransactionTime).GroupBy("type, account_id").Find(&transactionTotalAmounts)
if err != nil {
return nil, nil, err
@@ -1064,7 +1097,7 @@ func (s *TransactionService) GetAccountsAndCategoriesTotalIncomeAndExpense(uid i
}
var transactionTotalAmounts []*models.Transaction
err := s.UserDataDB(uid).Select("uid, category_id, account_id, SUM(amount) as amount").Where(condition, conditionParams...).GroupBy("category_id, account_id").Find(&transactionTotalAmounts)
err := s.UserDataDB(uid).Select("category_id, account_id, SUM(amount) as amount").Where(condition, conditionParams...).GroupBy("category_id, account_id").Find(&transactionTotalAmounts)
if err != nil {
return nil, err
@@ -1085,7 +1118,7 @@ func (s *TransactionService) GetTransactionMapByList(transactions []*models.Tran
return transactionMap
}
func (s *TransactionService) getTransactionQueryCondition(uid int64, maxTransactionTime int64, minTransactionTime int64, transactionType models.TransactionDbType, categoryIds []int64, accountId int64, keyword string, noDuplicated bool) (string, []interface{}) {
func (s *TransactionService) getTransactionQueryCondition(uid int64, maxTransactionTime int64, minTransactionTime int64, transactionType models.TransactionDbType, categoryIds []int64, accountIds []int64, keyword string, noDuplicated bool) (string, []interface{}) {
condition := "uid=? AND deleted=?"
conditionParams := make([]interface{}, 0, 16)
conditionParams = append(conditionParams, uid)
@@ -1105,7 +1138,7 @@ func (s *TransactionService) getTransactionQueryCondition(uid int64, maxTransact
condition = condition + " AND type=?"
conditionParams = append(conditionParams, transactionType)
} else if transactionType == models.TRANSACTION_DB_TYPE_TRANSFER_OUT || transactionType == models.TRANSACTION_DB_TYPE_TRANSFER_IN {
if accountId == 0 {
if len(accountIds) == 0 {
condition = condition + " AND type=?"
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_TRANSFER_OUT)
} else {
@@ -1114,7 +1147,7 @@ func (s *TransactionService) getTransactionQueryCondition(uid int64, maxTransact
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_TRANSFER_IN)
}
} else {
if noDuplicated && accountId == 0 {
if noDuplicated && len(accountIds) == 0 {
condition = condition + " AND (type=? OR type=? OR type=? OR type=?)"
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_MODIFY_BALANCE)
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_INCOME)
@@ -1138,9 +1171,19 @@ func (s *TransactionService) getTransactionQueryCondition(uid int64, maxTransact
condition = condition + " AND category_id IN (" + conditions.String() + ")"
}
if accountId > 0 {
condition = condition + " AND account_id=?"
conditionParams = append(conditionParams, accountId)
if len(accountIds) > 0 {
var conditions strings.Builder
for i := 0; i < len(accountIds); i++ {
if i > 0 {
conditions.WriteString(",")
}
conditions.WriteString("?")
conditionParams = append(conditionParams, accountIds[i])
}
condition = condition + " AND account_id IN (" + conditions.String() + ")"
}
if keyword != "" {
+1 -1
View File
@@ -159,7 +159,7 @@ func (s *TwoFactorAuthorizationService) GenerateTwoFactorRecoveryCodes() ([]stri
recoveryCodes := make([]string, twoFactorRecoveryCodeCount)
for i := 0; i < twoFactorRecoveryCodeCount; i++ {
recoveryCode, err := utils.GetRandomNumberOrLetter(twoFactorRecoveryCodeLength)
recoveryCode, err := utils.GetRandomNumberOrLowercaseLetter(twoFactorRecoveryCodeLength)
if err != nil {
return nil, err
+27 -3
View File
@@ -150,7 +150,7 @@ func (s *UserService) CreateUser(user *models.User) error {
}
// UpdateUser saves an existed user model to database
func (s *UserService) UpdateUser(user *models.User) (keyProfileUpdated bool, err error) {
func (s *UserService) UpdateUser(user *models.User, modifyUserLanguage bool) (keyProfileUpdated bool, err error) {
if user.Uid <= 0 {
return false, errs.ErrUserIdInvalid
}
@@ -186,6 +186,18 @@ func (s *UserService) UpdateUser(user *models.User) (keyProfileUpdated bool, err
updateCols = append(updateCols, "nickname")
}
if user.DefaultAccountId > 0 {
updateCols = append(updateCols, "default_account_id")
}
if models.TRANSACTION_EDIT_SCOPE_NONE <= user.TransactionEditScope && user.TransactionEditScope <= models.TRANSACTION_EDIT_SCOPE_THIS_YEAR_OR_LATER {
updateCols = append(updateCols, "transaction_edit_scope")
}
if modifyUserLanguage || user.Language != "" {
updateCols = append(updateCols, "language")
}
if user.DefaultCurrency != "" {
updateCols = append(updateCols, "default_currency")
}
@@ -194,8 +206,20 @@ func (s *UserService) UpdateUser(user *models.User) (keyProfileUpdated bool, err
updateCols = append(updateCols, "first_day_of_week")
}
if models.TRANSACTION_EDIT_SCOPE_NONE <= user.TransactionEditScope && user.TransactionEditScope <= models.TRANSACTION_EDIT_SCOPE_THIS_YEAR_OR_LATER {
updateCols = append(updateCols, "transaction_edit_scope")
if models.LONG_DATE_FORMAT_DEFAULT <= user.LongDateFormat && user.LongDateFormat <= models.LONG_DATE_FORMAT_D_M_YYYY {
updateCols = append(updateCols, "long_date_format")
}
if models.SHORT_DATE_FORMAT_DEFAULT <= user.ShortDateFormat && user.ShortDateFormat <= models.SHORT_DATE_FORMAT_D_M_YYYY {
updateCols = append(updateCols, "short_date_format")
}
if models.LONG_TIME_FORMAT_DEFAULT <= user.LongTimeFormat && user.LongTimeFormat <= models.LONG_TIME_FORMAT_HH_MM_SS_A {
updateCols = append(updateCols, "long_time_format")
}
if models.SHORT_TIME_FORMAT_DEFAULT <= user.ShortTimeFormat && user.ShortTimeFormat <= models.SHORT_TIME_FORMAT_HH_MM_A {
updateCols = append(updateCols, "short_time_format")
}
user.UpdatedUnixTime = now
+115 -34
View File
@@ -62,36 +62,42 @@ const (
InternalUuidGeneratorType string = "internal"
)
// Map provider types
const (
OpenStreetMapProvider string = "openstreetmap"
)
// Exchange rates data source types
const (
EuroCentralBankDataSource string = "euro_central_bank"
BankOfCanadaDataSource string = "bank_of_canada"
ReserveBankOfAustraliaDataSource string = "reserve_bank_of_australia"
CzechNationalBankDataSource string = "czech_national_bank"
NationalBankOfPolandDataSource string = "national_bank_of_poland"
EuroCentralBankDataSource string = "euro_central_bank"
BankOfCanadaDataSource string = "bank_of_canada"
ReserveBankOfAustraliaDataSource string = "reserve_bank_of_australia"
CzechNationalBankDataSource string = "czech_national_bank"
NationalBankOfPolandDataSource string = "national_bank_of_poland"
MonetaryAuthorityOfSingaporeDataSource string = "monetary_authority_of_singapore"
)
const (
defaultAppName string = "ezBookkeeping"
defaultHttpAddr string = "0.0.0.0"
defaultHttpPort int = 8080
defaultHttpPort uint16 = 8080
defaultDomain string = "localhost"
defaultDatabaseHost string = "127.0.0.1:3306"
defaultDatabaseName string = "ezbookkeeping"
defaultDatabaseMaxIdleConn int = 2
defaultDatabaseMaxOpenConn int = 0
defaultDatabaseConnMaxLifetime int = 14400
defaultDatabaseMaxIdleConn uint16 = 2
defaultDatabaseMaxOpenConn uint16 = 0
defaultDatabaseConnMaxLifetime uint32 = 14400
defaultLogMode string = "console"
defaultLoglevel Level = LOGLEVEL_INFO
defaultSecretKey string = "ezbookkeeping"
defaultTokenExpiredTime int = 604800 // 7 days
defaultTemporaryTokenExpiredTime int = 300 // 5 minutes
defaultTokenExpiredTime uint32 = 604800 // 7 days
defaultTemporaryTokenExpiredTime uint32 = 300 // 5 minutes
defaultExchangeRatesDataRequestTimeout int = 10000 // 10 seconds
defaultExchangeRatesDataRequestTimeout uint32 = 10000 // 10 seconds
)
// DatabaseConfig represents the database setting config
@@ -106,9 +112,9 @@ type DatabaseConfig struct {
DatabasePath string
MaxIdleConnection int
MaxOpenConnection int
ConnectionMaxLifeTime int
MaxIdleConnection uint16
MaxOpenConnection uint16
ConnectionMaxLifeTime uint32
}
// Config represents the global setting config
@@ -121,7 +127,7 @@ type Config struct {
// Server
Protocol Scheme
HttpAddr string
HttpPort int
HttpPort uint16
Domain string
RootUrl string
@@ -156,9 +162,9 @@ type Config struct {
// Secret
SecretKey string
EnableTwoFactor bool
TokenExpiredTime int
TokenExpiredTime uint32
TokenExpiredTimeDuration time.Duration
TemporaryTokenExpiredTime int
TemporaryTokenExpiredTime uint32
TemporaryTokenExpiredTimeDuration time.Duration
EnableRequestIdHeader bool
@@ -168,9 +174,13 @@ type Config struct {
// Data
EnableDataExport bool
// Map
MapProvider string
EnableMapDataFetchProxy bool
// Exchange Rates
ExchangeRatesDataSource string
ExchangeRatesRequestTimeout int
ExchangeRatesRequestTimeout uint32
}
// LoadConfiguration loads setting config from given config file path
@@ -238,6 +248,12 @@ func LoadConfiguration(configFilePath string) (*Config, error) {
return nil, err
}
err = loadMapConfiguration(config, cfgFile, "map")
if err != nil {
return nil, err
}
err = loadExchangeRatesConfiguration(config, cfgFile, "exchange_rates")
if err != nil {
@@ -281,12 +297,12 @@ func loadServerConfiguration(config *Config, configFile *ini.File, sectionName s
config.Protocol = SCHEME_HTTP
config.HttpAddr = getConfigItemStringValue(configFile, sectionName, "http_addr", defaultHttpAddr)
config.HttpPort = getConfigItemIntValue(configFile, sectionName, "http_port", defaultHttpPort)
config.HttpPort = getConfigItemUint16Value(configFile, sectionName, "http_port", defaultHttpPort)
} else if getConfigItemStringValue(configFile, sectionName, "protocol") == "https" {
config.Protocol = SCHEME_HTTPS
config.HttpAddr = getConfigItemStringValue(configFile, sectionName, "http_addr", defaultHttpAddr)
config.HttpPort = getConfigItemIntValue(configFile, sectionName, "http_port", defaultHttpPort)
config.HttpPort = getConfigItemUint16Value(configFile, sectionName, "http_port", defaultHttpPort)
config.CertFile = getConfigItemStringValue(configFile, sectionName, "cert_file")
config.CertKeyFile = getConfigItemStringValue(configFile, sectionName, "cert_key_file")
@@ -339,9 +355,9 @@ func loadDatabaseConfiguration(config *Config, configFile *ini.File, sectionName
dbConfig.DatabasePath = finalStaticDBPath
}
dbConfig.MaxIdleConnection = getConfigItemIntValue(configFile, sectionName, "max_idle_conn", defaultDatabaseMaxIdleConn)
dbConfig.MaxOpenConnection = getConfigItemIntValue(configFile, sectionName, "max_open_conn", defaultDatabaseMaxOpenConn)
dbConfig.ConnectionMaxLifeTime = getConfigItemIntValue(configFile, sectionName, "conn_max_lifetime", defaultDatabaseConnMaxLifetime)
dbConfig.MaxIdleConnection = getConfigItemUint16Value(configFile, sectionName, "max_idle_conn", defaultDatabaseMaxIdleConn)
dbConfig.MaxOpenConnection = getConfigItemUint16Value(configFile, sectionName, "max_open_conn", defaultDatabaseMaxOpenConn)
dbConfig.ConnectionMaxLifeTime = getConfigItemUint32Value(configFile, sectionName, "conn_max_lifetime", defaultDatabaseConnMaxLifetime)
config.DatabaseConfig = dbConfig
config.EnableQueryLog = getConfigItemBoolValue(configFile, sectionName, "log_query", false)
@@ -383,7 +399,7 @@ func loadUuidConfiguration(config *Config, configFile *ini.File, sectionName str
return errs.ErrInvalidUuidMode
}
config.UuidServerId = uint8(getConfigItemIntValue(configFile, sectionName, "server_id", 0))
config.UuidServerId = getConfigItemUint8Value(configFile, sectionName, "server_id", 0)
return nil
}
@@ -392,10 +408,10 @@ func loadSecurityConfiguration(config *Config, configFile *ini.File, sectionName
config.SecretKey = getConfigItemStringValue(configFile, sectionName, "secret_key", defaultSecretKey)
config.EnableTwoFactor = getConfigItemBoolValue(configFile, sectionName, "enable_two_factor", true)
config.TokenExpiredTime = getConfigItemIntValue(configFile, sectionName, "token_expired_time", defaultTokenExpiredTime)
config.TokenExpiredTime = getConfigItemUint32Value(configFile, sectionName, "token_expired_time", defaultTokenExpiredTime)
config.TokenExpiredTimeDuration = time.Duration(config.TokenExpiredTime) * time.Second
config.TemporaryTokenExpiredTime = getConfigItemIntValue(configFile, sectionName, "temporary_token_expired_time", defaultTemporaryTokenExpiredTime)
config.TemporaryTokenExpiredTime = getConfigItemUint32Value(configFile, sectionName, "temporary_token_expired_time", defaultTemporaryTokenExpiredTime)
config.TemporaryTokenExpiredTimeDuration = time.Duration(config.TemporaryTokenExpiredTime) * time.Second
config.EnableRequestIdHeader = getConfigItemBoolValue(configFile, sectionName, "request_id_header", true)
@@ -415,6 +431,19 @@ func loadDataConfiguration(config *Config, configFile *ini.File, sectionName str
return nil
}
func loadMapConfiguration(config *Config, configFile *ini.File, sectionName string) error {
if getConfigItemStringValue(configFile, sectionName, "map_provider") == "" {
config.MapProvider = ""
} else if getConfigItemStringValue(configFile, sectionName, "map_provider") == OpenStreetMapProvider {
config.MapProvider = OpenStreetMapProvider
} else {
return errs.ErrInvalidMapProvider
}
config.EnableMapDataFetchProxy = getConfigItemBoolValue(configFile, sectionName, "map_data_fetch_proxy", false)
return nil
}
func loadExchangeRatesConfiguration(config *Config, configFile *ini.File, sectionName string) error {
if getConfigItemStringValue(configFile, sectionName, "data_source") == EuroCentralBankDataSource {
config.ExchangeRatesDataSource = EuroCentralBankDataSource
@@ -426,11 +455,13 @@ func loadExchangeRatesConfiguration(config *Config, configFile *ini.File, sectio
config.ExchangeRatesDataSource = CzechNationalBankDataSource
} else if getConfigItemStringValue(configFile, sectionName, "data_source") == NationalBankOfPolandDataSource {
config.ExchangeRatesDataSource = NationalBankOfPolandDataSource
} else if getConfigItemStringValue(configFile, sectionName, "data_source") == MonetaryAuthorityOfSingaporeDataSource {
config.ExchangeRatesDataSource = MonetaryAuthorityOfSingaporeDataSource
} else {
return errs.ErrInvalidExchangeRatesDataSource
}
config.ExchangeRatesRequestTimeout = getConfigItemIntValue(configFile, sectionName, "request_timeout", defaultExchangeRatesDataRequestTimeout)
config.ExchangeRatesRequestTimeout = getConfigItemUint32Value(configFile, sectionName, "request_timeout", defaultExchangeRatesDataRequestTimeout)
return nil
}
@@ -482,23 +513,73 @@ func getConfigItemStringValue(configFile *ini.File, sectionName string, itemName
}
}
func getConfigItemIntValue(configFile *ini.File, sectionName string, itemName string, defaultValue ...int) int {
func getConfigItemUint8Value(configFile *ini.File, sectionName string, itemName string, defaultValue uint8) uint8 {
environmentKey := getEnvironmentKey(sectionName, itemName)
environmentValue := os.Getenv(environmentKey)
if len(environmentValue) > 0 {
value, err := strconv.ParseInt(environmentValue, 0, 64)
value, err := strconv.ParseUint(environmentValue, 10, 8)
if err == nil {
return int(value)
return uint8(value)
}
}
section := configFile.Section(sectionName)
return section.Key(itemName).MustInt(defaultValue...)
value, err := strconv.ParseUint(section.Key(itemName).String(), 10, 8)
if err == nil {
return uint8(value)
}
return defaultValue
}
func getConfigItemBoolValue(configFile *ini.File, sectionName string, itemName string, defaultValue ...bool) bool {
func getConfigItemUint16Value(configFile *ini.File, sectionName string, itemName string, defaultValue uint16) uint16 {
environmentKey := getEnvironmentKey(sectionName, itemName)
environmentValue := os.Getenv(environmentKey)
if len(environmentValue) > 0 {
value, err := strconv.ParseUint(environmentValue, 10, 16)
if err == nil {
return uint16(value)
}
}
section := configFile.Section(sectionName)
value, err := strconv.ParseUint(section.Key(itemName).String(), 10, 16)
if err == nil {
return uint16(value)
}
return defaultValue
}
func getConfigItemUint32Value(configFile *ini.File, sectionName string, itemName string, defaultValue uint32) uint32 {
environmentKey := getEnvironmentKey(sectionName, itemName)
environmentValue := os.Getenv(environmentKey)
if len(environmentValue) > 0 {
value, err := strconv.ParseUint(environmentValue, 10, 32)
if err == nil {
return uint32(value)
}
}
section := configFile.Section(sectionName)
value, err := strconv.ParseUint(section.Key(itemName).String(), 10, 32)
if err == nil {
return uint32(value)
}
return defaultValue
}
func getConfigItemBoolValue(configFile *ini.File, sectionName string, itemName string, defaultValue bool) bool {
environmentKey := getEnvironmentKey(sectionName, itemName)
environmentValue := os.Getenv(environmentKey)
@@ -511,7 +592,7 @@ func getConfigItemBoolValue(configFile *ini.File, sectionName string, itemName s
}
section := configFile.Section(sectionName)
return section.Key(itemName).MustBool(defaultValue...)
return section.Key(itemName).MustBool(defaultValue)
}
func getEnvironmentKey(sectionName string, itemName string) string {
+3 -1
View File
@@ -7,7 +7,9 @@ type ConfigContainer struct {
// Initialize a config container singleton instance
var (
Container = &ConfigContainer{}
Version string
CommitHash string
Container = &ConfigContainer{}
)
// SetCurrentConfig sets the current config by a given config
+18 -7
View File
@@ -2,20 +2,20 @@ package utils
import "strconv"
// Int32ToString returns the textual representation of this number
func Int32ToString(num int) string {
// IntToString returns the textual representation of this number
func IntToString(num int) string {
return strconv.Itoa(num)
}
// StringToInt32 parses a textual representation of the number to int32
func StringToInt32(str string) (int, error) {
// StringToInt parses a textual representation of the number to int
func StringToInt(str string) (int, error) {
return strconv.Atoi(str)
}
// StringTryToInt32 parses a textual representation of the number to int32 if str is valid,
// StringTryToInt parses a textual representation of the number to int if str is valid,
// or returns the default value
func StringTryToInt32(str string, defaultValue int) int {
num, err := StringToInt32(str)
func StringTryToInt(str string, defaultValue int) int {
num, err := StringToInt(str)
if err != nil {
return defaultValue
@@ -24,6 +24,17 @@ func StringTryToInt32(str string, defaultValue int) int {
return num
}
// StringToInt32 parses a textual representation of the number to int32
func StringToInt32(str string) (int32, error) {
val, err := strconv.ParseInt(str, 10, 32)
if err != nil {
return 0, err
}
return int32(val), nil
}
// Int64ToString returns the textual representation of this number
func Int64ToString(num int64) string {
return strconv.FormatInt(num, 10)
+28 -5
View File
@@ -6,19 +6,42 @@ import (
"github.com/stretchr/testify/assert"
)
func TestInt32ToString(t *testing.T) {
func TestIntToString(t *testing.T) {
expectedValue := "-123456789"
actualValue := Int32ToString(-123456789)
actualValue := IntToString(-123456789)
assert.Equal(t, expectedValue, actualValue)
}
func TestStringToInt32(t *testing.T) {
func TestStringToInt(t *testing.T) {
expectedValue := -123456789
actualValue, err := StringToInt("-123456789")
assert.Equal(t, nil, err)
assert.Equal(t, expectedValue, actualValue)
}
func TestStringToInt_InvalidNumber(t *testing.T) {
_, err := StringToInt("")
assert.NotEqual(t, nil, err)
_, err = StringToInt("null")
assert.NotEqual(t, nil, err)
}
func TestStringToInt32(t *testing.T) {
expectedValue := int32(-123456789)
actualValue, err := StringToInt32("-123456789")
assert.Equal(t, nil, err)
assert.Equal(t, expectedValue, actualValue)
}
func TestStringToInt32_OutOfRange(t *testing.T) {
_, err := StringToInt32("2147483648")
assert.NotEqual(t, nil, err)
_, err = StringToInt32("-2147483649")
assert.NotEqual(t, nil, err)
}
func TestStringToInt32_InvalidNumber(t *testing.T) {
_, err := StringToInt32("")
assert.NotEqual(t, nil, err)
@@ -29,10 +52,10 @@ func TestStringToInt32_InvalidNumber(t *testing.T) {
func TestStringTryToInt32_InvalidNumber(t *testing.T) {
expectedValue := -1
actualValue := StringTryToInt32("", -1)
actualValue := StringTryToInt("", -1)
assert.Equal(t, expectedValue, actualValue)
actualValue = StringTryToInt32("null", -1)
actualValue = StringTryToInt("null", -1)
assert.Equal(t, expectedValue, actualValue)
}
+2 -2
View File
@@ -102,13 +102,13 @@ func ParseFromTimezoneOffset(tzOffset string) (*time.Location, error) {
return nil, errs.ErrFormatInvalid
}
hourAbsOffset, err := StringToInt32(offsets[0])
hourAbsOffset, err := StringToInt(offsets[0])
if err != nil {
return nil, err
}
minuteAbsOffset, err := StringToInt32(offsets[1])
minuteAbsOffset, err := StringToInt(offsets[1])
if err != nil {
return nil, err
+30
View File
@@ -3,8 +3,38 @@ package utils
import (
"io"
"os"
"strings"
)
// ListFileNamesWithPrefixAndSuffix returns file name list which has specified prefix and suffix
func ListFileNamesWithPrefixAndSuffix(path string, prefix string, suffix string) []string {
dir, err := os.Open(path)
if err != nil {
return nil
}
fileInfos, err := dir.Readdir(0)
if err != nil {
return nil
}
var fileNames []string
for i := 0; i < len(fileInfos); i++ {
fileInfo := fileInfos[i]
if !fileInfo.IsDir() &&
strings.HasPrefix(fileInfo.Name(), prefix) &&
strings.HasSuffix(fileInfo.Name(), suffix) {
fileNames = append(fileNames, fileInfo.Name())
}
}
return fileNames
}
// IsExists returns whether specified file or directory path exits
func IsExists(path string) (bool, error) {
_, err := os.Stat(path)
+23 -4
View File
@@ -16,10 +16,12 @@ import (
)
const (
availableCharacters = "!#$&()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_abcdefghijklmnopqrstuvwxyz{|}~"
availableNumberAndLetters = "0123456789abcdefghijklmnopqrstuvwxyz"
availableCharactersLength = len(availableCharacters)
availableNumberAndLettersLength = len(availableNumberAndLetters)
availableCharacters = "!#$&()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_abcdefghijklmnopqrstuvwxyz{|}~"
availableNumberAndLetters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
availableNumberAndLowercaseLetters = "0123456789abcdefghijklmnopqrstuvwxyz"
availableCharactersLength = len(availableCharacters)
availableNumberAndLettersLength = len(availableNumberAndLetters)
availableNumberAndLowercaseLettersLength = len(availableNumberAndLowercaseLetters)
)
// SubString returns part of the source string according to start index and length
@@ -107,6 +109,23 @@ func GetRandomNumberOrLetter(n int) (string, error) {
return string(result), nil
}
// GetRandomNumberOrLowercaseLetter returns a random string which only contains number or letter characters
func GetRandomNumberOrLowercaseLetter(n int) (string, error) {
var result = make([]byte, n)
for i := 0; i < n; i++ {
index, err := GetRandomInteger(availableNumberAndLowercaseLettersLength)
if err != nil {
return "", err
}
result[i] = availableNumberAndLowercaseLetters[index]
}
return string(result), nil
}
// MD5Encode returns a hashed string by md5
func MD5Encode(data []byte) []byte {
m := md5.New()
+5
View File
@@ -83,6 +83,11 @@ func TestGetRandomNumberOrLetter(t *testing.T) {
assert.Equal(t, nil, err)
assert.Len(t, actualValue, 10)
}
func TestGetRandomNumberOrLowercaseLetter(t *testing.T) {
actualValue, err := GetRandomNumberOrLowercaseLetter(10)
assert.Equal(t, nil, err)
assert.Len(t, actualValue, 10)
}
func TestMD5Encode(t *testing.T) {
str := "foobar"
+37 -17
View File
@@ -35,8 +35,8 @@ type InternalUuidInfo struct {
// InternalUuidGenerator represents internal bundled uuid generator
type InternalUuidGenerator struct {
uuidServerId uint8
uuidSeqNumbers [1 << internalUuidTypeBits]uint64
uuidServerId uint8
}
// NewInternalUuidGenerator returns a new internal uuid generator
@@ -48,40 +48,51 @@ func NewInternalUuidGenerator(config *settings.Config) (*InternalUuidGenerator,
return generator, nil
}
// GenerateUuid returns a new uuid
// GenerateUuid generates a new uuid
func (u *InternalUuidGenerator) GenerateUuid(idType UuidType) int64 {
uuids := u.GenerateUuids(idType, 1)
return uuids[0]
}
// GenerateUuids generates new uuids
func (u *InternalUuidGenerator) GenerateUuids(idType UuidType, count uint8) []int64 {
// 63bits = unixTime(32bits) + uuidType(4bits) + uuidServerId(8bits) + sequentialNumber(19bits)
uuids := make([]int64, count)
if count < 1 {
return uuids
}
var unixTime uint64
var newSeqId uint64
var newFirstSeqId uint64
var newLastSeqId uint64
uuidType := uint8(idType)
for {
unixTime = uint64(time.Now().Unix())
newSeqId = atomic.AddUint64(&u.uuidSeqNumbers[uuidType], 1)
newLastSeqId = atomic.AddUint64(&u.uuidSeqNumbers[uuidType], uint64(count))
if newSeqId>>seqNumberIdBits == unixTime {
if newLastSeqId>>seqNumberIdBits == unixTime {
newFirstSeqId = newLastSeqId - uint64(count-1)
break
}
currentSeqId := newSeqId
newSeqId = unixTime << seqNumberIdBits
currentSeqId := newLastSeqId
newFirstSeqId = unixTime << seqNumberIdBits
newLastSeqId = newFirstSeqId + uint64(count-1)
if atomic.CompareAndSwapUint64(&u.uuidSeqNumbers[uuidType], currentSeqId, newSeqId) {
if atomic.CompareAndSwapUint64(&u.uuidSeqNumbers[uuidType], currentSeqId, newLastSeqId) {
break
}
}
seqId := newSeqId & seqNumberIdMask
for i := 0; i < int(count); i++ {
seqId := (newFirstSeqId + uint64(i)) & seqNumberIdMask
uuids[i] = u.assembleUuid(unixTime, uuidType, seqId)
}
unixTimePart := (int64(unixTime) & internalUuidUnixTimeMask) << (internalUuidTypeBits + internalUuidServerIdBits + internalUuidSeqIdBits)
uuidTypePart := (int64(uuidType) & internalUuidTypeMask) << (internalUuidServerIdBits + internalUuidSeqIdBits)
uuidServerIdPart := (int64(u.uuidServerId) & internalUuidServerIdMask) << internalUuidSeqIdBits
seqIdPart := int64(seqId) & internalUuidSeqIdMask
uuid := unixTimePart | uuidTypePart | uuidServerIdPart | seqIdPart
return uuid
return uuids
}
// ParseUuidInfo returns a info struct which contains all information in uuid
@@ -104,3 +115,12 @@ func (u *InternalUuidGenerator) ParseUuidInfo(uuid int64) *InternalUuidInfo {
SequentialId: seqId,
}
}
func (u *InternalUuidGenerator) assembleUuid(unixTime uint64, uuidType uint8, seqId uint64) int64 {
unixTimePart := (int64(unixTime) & internalUuidUnixTimeMask) << (internalUuidTypeBits + internalUuidServerIdBits + internalUuidSeqIdBits)
uuidTypePart := (int64(uuidType) & internalUuidTypeMask) << (internalUuidServerIdBits + internalUuidSeqIdBits)
uuidServerIdPart := (int64(u.uuidServerId) & internalUuidServerIdMask) << internalUuidSeqIdBits
seqIdPart := int64(seqId) & internalUuidSeqIdMask
return unixTimePart | uuidTypePart | uuidServerIdPart | seqIdPart
}
+129 -13
View File
@@ -54,7 +54,7 @@ func TestGenerateUuid_MultiType(t *testing.T) {
assert.Equal(t, uint32(expectedSeqId), actualSeqId)
}
func TestGenerateUuid_1000Times(t *testing.T) {
func TestGenerateUuid_2000TimesIn2Seconds(t *testing.T) {
generator, _ := NewInternalUuidGenerator(&settings.Config{UuidServerId: 2})
expectedUnixTime := time.Now().Unix()
@@ -78,21 +78,18 @@ func TestGenerateUuid_1000Times(t *testing.T) {
}
}
func TestGenerateUuid_1000000TimesConcurrent(t *testing.T) {
func TestGenerateUuid_10000TimesConcurrent(t *testing.T) {
concurrentCount := 10
generator, _ := NewInternalUuidGenerator(&settings.Config{UuidServerId: 3})
var mutex sync.Mutex
var generatedIds sync.Map
var waitGroup sync.WaitGroup
for i := 0; i < 50; i++ {
go func() {
for routineIndex := 0; routineIndex < concurrentCount; routineIndex++ {
go func(currentRoutineIndex int) {
waitGroup.Add(1)
for j := 0; j < 40000; j++ {
if j%10000 == 0 { // echo server can only generate 500,000 (50 * 10000) uuids in one second
time.Sleep(1000 * time.Millisecond)
}
for cycle := 0; cycle < 1000; cycle++ {
expectedUnixTime := time.Now().Unix()
uuid := generator.GenerateUuid(UUID_TYPE_USER)
uuidInfo := generator.ParseUuidInfo(uuid)
@@ -104,18 +101,137 @@ func TestGenerateUuid_1000000TimesConcurrent(t *testing.T) {
}
if uuidInfo.SequentialId == 0 {
if existedUnixTime, exists := generatedIds.Load(uuid); exists {
if existedRoutineIndex, exists := generatedIds.Load(uuid); exists {
mutex.Lock()
assert.Fail(t, fmt.Sprintf("uuid \"%d\" conflicts, seq id is %d, existed unixtime is %d, current unix time is %d", uuid, uuidInfo.SequentialId, existedUnixTime, uuidInfo.UnixTime))
assert.Fail(t, fmt.Sprintf("uuid \"%d\" conflicts, unix time is %d, seq id is %d, existed routine index is %d, current routine index is %d", uuid, uuidInfo.UnixTime, uuidInfo.SequentialId, existedRoutineIndex, currentRoutineIndex))
mutex.Unlock()
}
generatedIds.Store(uuid, uuidInfo.UnixTime)
generatedIds.Store(uuid, currentRoutineIndex)
}
}
waitGroup.Done()
}()
}(routineIndex)
}
waitGroup.Wait()
}
func TestGenerateUuids_Count0(t *testing.T) {
expectedUuidServerId := uint8(90)
expectedUuidType := UUID_TYPE_TRANSACTION
generator, _ := NewInternalUuidGenerator(&settings.Config{UuidServerId: expectedUuidServerId})
uuids := generator.GenerateUuids(expectedUuidType, 0)
assert.NotEqual(t, nil, uuids)
assert.Equal(t, 0, len(uuids))
}
func TestGenerateUuids_Count255(t *testing.T) {
expectedUnixTime := time.Now().Unix()
expectedUuidServerId := uint8(90)
expectedUuidType := UUID_TYPE_TRANSACTION
expectedUuidCount := uint8(255)
generator, _ := NewInternalUuidGenerator(&settings.Config{UuidServerId: expectedUuidServerId})
uuids := generator.GenerateUuids(expectedUuidType, expectedUuidCount)
for i := 0; i < int(expectedUuidCount); i++ {
uuidInfo := generator.ParseUuidInfo(uuids[i])
actualUnixTime := uuidInfo.UnixTime
assert.Equal(t, uint32(expectedUnixTime), actualUnixTime)
actualUuidServerId := uuidInfo.UuidServerId
assert.Equal(t, expectedUuidServerId, actualUuidServerId)
actualUuidType := uuidInfo.UuidType
assert.Equal(t, uint8(expectedUuidType), actualUuidType)
expectedSeqId := i
actualSeqId := uuidInfo.SequentialId
assert.Equal(t, uint32(expectedSeqId), actualSeqId)
}
assert.Equal(t, int(expectedUuidCount), len(uuids))
}
func TestGenerateUuids_30TimesIn3Seconds(t *testing.T) {
expectedUuidServerId := uint8(90)
expectedUuidType := UUID_TYPE_TRANSACTION
expectedUuidCount := uint8(255)
generator, _ := NewInternalUuidGenerator(&settings.Config{UuidServerId: expectedUuidServerId})
for cycle := 0; cycle < 30; cycle++ {
expectedUnixTime := time.Now().Unix()
uuids := generator.GenerateUuids(expectedUuidType, expectedUuidCount)
var firstSeqId uint32
for i := 0; i < int(expectedUuidCount); i++ {
uuidInfo := generator.ParseUuidInfo(uuids[i])
actualUnixTime := uuidInfo.UnixTime
assert.Equal(t, uint32(expectedUnixTime), actualUnixTime)
actualUuidServerId := uuidInfo.UuidServerId
assert.Equal(t, expectedUuidServerId, actualUuidServerId)
actualUuidType := uuidInfo.UuidType
assert.Equal(t, uint8(expectedUuidType), actualUuidType)
if i == 0 {
firstSeqId = uuidInfo.SequentialId
} else {
expectedSeqId := firstSeqId + uint32(i)
actualSeqId := uuidInfo.SequentialId
assert.Equal(t, expectedSeqId, actualSeqId)
}
}
time.Sleep(100 * time.Millisecond)
}
}
func TestGenerateUuids_20000TimesConcurrent(t *testing.T) {
concurrentCount := 10
expectedUuidCount := uint8(20)
generator, _ := NewInternalUuidGenerator(&settings.Config{UuidServerId: 3})
var mutex sync.Mutex
var generatedIds sync.Map
var waitGroup sync.WaitGroup
for routineIndex := 0; routineIndex < concurrentCount; routineIndex++ {
go func(currentRoutineIndex int) {
waitGroup.Add(1)
for cycle := 0; cycle < 100; cycle++ {
expectedUnixTime := time.Now().Unix()
uuids := generator.GenerateUuids(UUID_TYPE_USER, expectedUuidCount)
for i := 0; i < int(expectedUuidCount); i++ {
uuidInfo := generator.ParseUuidInfo(uuids[i])
if uint32(expectedUnixTime) != uuidInfo.UnixTime {
mutex.Lock()
assert.Equal(t, uint32(expectedUnixTime), uuidInfo.UnixTime)
mutex.Unlock()
}
if existedRoutineIndex, exists := generatedIds.Load(uuids[i]); exists {
mutex.Lock()
assert.Fail(t, fmt.Sprintf("uuid \"%d\" conflicts, unix time is %d, seq id is %d, existed routine index is %d, current routine index is %d", uuids[i], uuidInfo.UnixTime, uuidInfo.SequentialId, existedRoutineIndex, currentRoutineIndex))
mutex.Unlock()
}
generatedIds.Store(uuids[i], currentRoutineIndex)
}
}
waitGroup.Done()
}(routineIndex)
}
waitGroup.Wait()
+5
View File
@@ -31,3 +31,8 @@ func InitializeUuidGenerator(config *settings.Config) error {
func (u *UuidContainer) GenerateUuid(uuidType UuidType) int64 {
return u.Current.GenerateUuid(uuidType)
}
// GenerateUuids returns new uuids by the current uuid generator
func (u *UuidContainer) GenerateUuids(uuidType UuidType, count uint8) []int64 {
return u.Current.GenerateUuids(uuidType, count)
}
+1
View File
@@ -3,4 +3,5 @@ package uuid
// UuidGenerator is common uuid generator interface
type UuidGenerator interface {
GenerateUuid(uuidType UuidType) int64
GenerateUuids(uuidType UuidType, count uint8) []int64
}
+4 -1
View File
@@ -132,7 +132,8 @@ var AllCurrencyNames = map[string]bool{
"SEK": true, //Swedish Krona
"SGD": true, //Singapore Dollar
"SHP": true, //Saint Helena Pound
"SLL": true, //Leone
"SLE": true, //Leone (new leone)
"SLL": true, //Leone (old leone)
"SOS": true, //Somali Shilling
"SRD": true, //Surinam Dollar
"SSP": true, //South Sudanese Pound
@@ -154,6 +155,8 @@ var AllCurrencyNames = map[string]bool{
"USD": true, //US Dollar
"UYU": true, //Peso Uruguayo
"UZS": true, //Uzbekistan Sum
"VED": true, //Bolívar Digital
"VEF": true, //Bolívar Fuerte
"VES": true, //Bolívar Soberano
"VND": true, //Dong
"VUV": true, //Vatu
+5
View File
@@ -0,0 +1,5 @@
module.exports = {
plugins: {
'postcss-preset-env': {},
},
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 936 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 736 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

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