Compare commits
293 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d3762e6c46 | |||
| 157eb140eb | |||
| 83a0c27259 | |||
| 5c4a8e37c4 | |||
| e4faf64ea3 | |||
| a4849fa4f0 | |||
| 32155ca63d | |||
| 9ea3327517 | |||
| 946a7810a7 | |||
| ea8021b359 | |||
| caa27841ef | |||
| d1cd13723a | |||
| 0e946a4b3b | |||
| 051c319890 | |||
| f2baa4ae65 | |||
| 05a93667eb | |||
| c137156c97 | |||
| 8f8a94cd66 | |||
| 4cdb599bf3 | |||
| 77a5ccd796 | |||
| 99ae18d06d | |||
| 90318d5690 | |||
| f133692002 | |||
| 0b17251f94 | |||
| 24724bb19f | |||
| b23d630daa | |||
| fcb954ff40 | |||
| 889225301c | |||
| 816d0e7ceb | |||
| f2c0ffab99 | |||
| f1f61a9038 | |||
| 77439f675b | |||
| c57f17233a | |||
| c91a56547f | |||
| 10dc2d1713 | |||
| d3c8a520ca | |||
| 681f888529 | |||
| bce8c23b05 | |||
| 788bfa7d4b | |||
| 6d331c873b | |||
| a03df7ed36 | |||
| b0b330903c | |||
| 4e16f963a8 | |||
| 8ef4b5537c | |||
| 8273e06e43 | |||
| b91305c490 | |||
| de086aa29e | |||
| 4c69243bef | |||
| 57d8edea0a | |||
| a098c100d7 | |||
| 8fb209440d | |||
| d05736d0eb | |||
| c92a9e61b0 | |||
| 2e04affb00 | |||
| 731b6e8bad | |||
| 2b63e50837 | |||
| e22f512f22 | |||
| 9a83393290 | |||
| 30ebe49875 | |||
| e15a850cfe | |||
| 34ebc06a8d | |||
| a86428cc4d | |||
| efce4cc04e | |||
| a8484cfcaf | |||
| cc55b98e80 | |||
| 0620194c78 | |||
| a00a67f3d1 | |||
| 6b9ad1a1c8 | |||
| 3d0b993c45 | |||
| dc74ac0d0b | |||
| 7ed923b347 | |||
| 1f63aa8cdf | |||
| 84f7eab95d | |||
| 7d6c7f49e5 | |||
| 021e523d63 | |||
| 266dafa4a9 | |||
| 579c903398 | |||
| 5d2e880bc5 | |||
| 8085f7cf11 | |||
| aea4cf7e8b | |||
| 3d56bfa114 | |||
| a7280bf7ed | |||
| 085f9817fc | |||
| fcca77bca5 | |||
| 4bf2e94a9d | |||
| 95c2494545 | |||
| 7662e0eb02 | |||
| 9f438dd648 | |||
| 08a1f3a5f7 | |||
| 26d77de67a | |||
| 4f9ab9db75 | |||
| 0b83518921 | |||
| 0f8de8d699 | |||
| aae23c285e | |||
| daf73dc964 | |||
| bc0893b518 | |||
| a87bda09f7 | |||
| aa7652279e | |||
| d86dea4081 | |||
| 15d476fd40 | |||
| 453a9ff61c | |||
| 26e9a0ef2a | |||
| 7849b2f05c | |||
| 2cbcc40ca9 | |||
| 184dad8185 | |||
| 46a7cd441f | |||
| 55249e07a3 | |||
| d4850b4a18 | |||
| 93819d5894 | |||
| 4a4cec3d69 | |||
| 432993121c | |||
| 1ce0c62c30 | |||
| b1343ba92a | |||
| 84a96d80b7 | |||
| 4b249a0ebb | |||
| 58de308f30 | |||
| f151eb6197 | |||
| 9eaac329b9 | |||
| a33123022f | |||
| 3eac9af403 | |||
| a371058096 | |||
| ee003160e5 | |||
| 847349dcbd | |||
| a2d6aff28b | |||
| cc3e1f2978 | |||
| dc9bf1a1d8 | |||
| 32af32d02e | |||
| d91c99c177 | |||
| d0e8419b2e | |||
| dad3f1041e | |||
| 7b70b2db29 | |||
| e5a04596e1 | |||
| 297f8b9987 | |||
| 9eddab3dd8 | |||
| ec97d2df91 | |||
| 3dd39defc1 | |||
| c0cc9b5247 | |||
| dc13afc071 | |||
| 628bd48f73 | |||
| c827675e14 | |||
| 09fb474921 | |||
| 7e1338e081 | |||
| 7a5d7337cd | |||
| b80041433c | |||
| dd32ab83cb | |||
| c8db448dfc | |||
| 51edca66d1 | |||
| bb5529098f | |||
| d5a54dd1fb | |||
| 329119fc3b | |||
| e43cf26bb5 | |||
| 93bc3bf94b | |||
| 675b5f039a | |||
| b3e4e39b49 | |||
| 4dc072f56d | |||
| b5d72c89f2 | |||
| d2b3900ed4 | |||
| 4e44553c07 | |||
| ec6b5fb155 | |||
| 16e53feaf4 | |||
| 182bdb34cd | |||
| 59d03b54d7 | |||
| 445969c449 | |||
| 29214a3bf3 | |||
| d979dc3535 | |||
| 399413a270 | |||
| d9c8142c51 | |||
| c951285049 | |||
| 0e372969b3 | |||
| 2d51f7b2be | |||
| 02a5dcf9ba | |||
| 9a70cfe24c | |||
| 023d875a00 | |||
| 640f74c612 | |||
| 768b005200 | |||
| fb315127f9 | |||
| 756e6cac5a | |||
| daae5b68cd | |||
| 0e391bee50 | |||
| 9627e65d6d | |||
| 830974500b | |||
| 0438964e17 | |||
| 226d44651f | |||
| db71ac5279 | |||
| 7e2a0b1483 | |||
| a219444953 | |||
| 5fec41055e | |||
| 5107e451d8 | |||
| 3b34cdbda2 | |||
| 35fcd32a96 | |||
| cf325b4267 | |||
| 860ef610b7 | |||
| 5c5e6a60a7 | |||
| 435c38fb27 | |||
| d640b7a5f5 | |||
| ae25b4eb4e | |||
| 315a686fed | |||
| 4dd79b07d9 | |||
| e88d803232 | |||
| 1489854444 | |||
| c5f9e276b4 | |||
| b2a8b359d1 | |||
| 72f6a9e9a3 | |||
| 809172bf34 | |||
| c34887240e | |||
| f041e7cb7d | |||
| 5eca777891 | |||
| a9e3b79eb1 | |||
| 0a376217c6 | |||
| 687d062bfc | |||
| e123498895 | |||
| a917d16c26 | |||
| 0884af038d | |||
| 72619f3dad | |||
| cf120dbcbf | |||
| 9906f1b1a7 | |||
| ea32bfa5fc | |||
| 14f6de8af1 | |||
| 176d5a15c5 | |||
| cfc7a5bd49 | |||
| 4b68ccf678 | |||
| cb57b216d0 | |||
| 222951139c | |||
| 821c7bfbd3 | |||
| 722c1d7917 | |||
| 4d065bc724 | |||
| 2a2cb3acc9 | |||
| 4a16b82700 | |||
| ea97f8cf7a | |||
| 28f113d992 | |||
| 46caf46ef7 | |||
| 185758b638 | |||
| 1e047aed80 | |||
| 8ef7676b4f | |||
| b2fc24d5ae | |||
| 065cd9dff8 | |||
| 532c762553 | |||
| 4857055eaf | |||
| 604379bf85 | |||
| 3d0f793cf6 | |||
| 9f8634ac11 | |||
| 6ab70b97b4 | |||
| 8c164ec833 | |||
| 09d47459b6 | |||
| 2916106f27 | |||
| 6b4292596a | |||
| 78aebb7d6c | |||
| 771bb35e9f | |||
| 453ed4227d | |||
| 88a284a21b | |||
| 9488b85705 | |||
| 8be3fd7ed4 | |||
| 8fdc0019ea | |||
| b21dd73ff2 | |||
| 24432701dd | |||
| 802cdabd75 | |||
| 8c83af1543 | |||
| deb465377e | |||
| bed2aef7f8 | |||
| 57b2b2492f | |||
| 87cdaee547 | |||
| a8b0ef2483 | |||
| 63e976e5cb | |||
| f73830d37b | |||
| 6511c4e810 | |||
| 008c58f52b | |||
| fa4a17f47b | |||
| 3fc2a763b4 | |||
| 1b2726a55c | |||
| 229fa27b73 | |||
| 072f44245d | |||
| dc837c430f | |||
| 429e270a9e | |||
| 985cefc297 | |||
| 285fc0ced3 | |||
| 777e14b6d4 | |||
| d18e8211ca | |||
| 2984980a54 | |||
| 2302f31f18 | |||
| f673677c2a | |||
| acc9bf77fb | |||
| 678769da0e | |||
| 2389208c95 | |||
| 28a1080ccc | |||
| 432d3fd3c3 | |||
| 8fc73b866b | |||
| dbc62aac93 | |||
| 79802a46fd | |||
| 3d8ecb42c1 | |||
| bcaf554ae4 | |||
| 3ac4a921e0 | |||
| 128f86b643 | |||
| 828fc690b6 |
@@ -31,6 +31,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Set up the environment
|
- name: Set up the environment
|
||||||
run: |
|
run: |
|
||||||
|
sed -r -i 's#FROM( --.*)? (.*:.*)?#FROM\1 ${{ secrets.DOCKER_REPO }}/mirrors/\2#g' Dockerfile
|
||||||
cat >> docker/custom-backend-pre-setup.sh <<EOF
|
cat >> docker/custom-backend-pre-setup.sh <<EOF
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
${{ vars.CUSTOM_BACKEND_PRE_SETUP }}
|
${{ vars.CUSTOM_BACKEND_PRE_SETUP }}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Set up the environment
|
- name: Set up the environment
|
||||||
run: |
|
run: |
|
||||||
sed -i 's#FROM #FROM ${{ secrets.DOCKER_REPO }}/mirrors/#g' Dockerfile
|
sed -r -i 's#FROM( --.*)? (.*:.*)?#FROM\1 ${{ secrets.DOCKER_REPO }}/mirrors/\2#g' Dockerfile
|
||||||
cat >> docker/custom-backend-pre-setup.sh <<EOF
|
cat >> docker/custom-backend-pre-setup.sh <<EOF
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
${{ vars.CUSTOM_BACKEND_PRE_SETUP }}
|
${{ vars.CUSTOM_BACKEND_PRE_SETUP }}
|
||||||
|
|||||||
+5
-4
@@ -1,5 +1,5 @@
|
|||||||
# Build backend binary file
|
# Build backend binary file
|
||||||
FROM golang:1.20.8-alpine3.17 AS be-builder
|
FROM golang:1.21.12-alpine3.20 AS be-builder
|
||||||
ARG RELEASE_BUILD
|
ARG RELEASE_BUILD
|
||||||
ENV RELEASE_BUILD=$RELEASE_BUILD
|
ENV RELEASE_BUILD=$RELEASE_BUILD
|
||||||
WORKDIR /go/src/github.com/mayswind/ezbookkeeping
|
WORKDIR /go/src/github.com/mayswind/ezbookkeeping
|
||||||
@@ -9,7 +9,7 @@ RUN apk add git gcc g++ libc-dev
|
|||||||
RUN ./build.sh backend
|
RUN ./build.sh backend
|
||||||
|
|
||||||
# Build frontend files
|
# Build frontend files
|
||||||
FROM node:18.17.1-alpine3.17 AS fe-builder
|
FROM --platform=$BUILDPLATFORM node:18.20.3-alpine3.20 AS fe-builder
|
||||||
ARG RELEASE_BUILD
|
ARG RELEASE_BUILD
|
||||||
ENV RELEASE_BUILD=$RELEASE_BUILD
|
ENV RELEASE_BUILD=$RELEASE_BUILD
|
||||||
WORKDIR /go/src/github.com/mayswind/ezbookkeeping
|
WORKDIR /go/src/github.com/mayswind/ezbookkeeping
|
||||||
@@ -19,7 +19,7 @@ RUN apk add git
|
|||||||
RUN ./build.sh frontend
|
RUN ./build.sh frontend
|
||||||
|
|
||||||
# Package docker image
|
# Package docker image
|
||||||
FROM alpine:3.17.5
|
FROM alpine:3.20.1
|
||||||
LABEL maintainer="MaysWind <i@mayswind.net>"
|
LABEL maintainer="MaysWind <i@mayswind.net>"
|
||||||
RUN addgroup -S -g 1000 ezbookkeeping && adduser -S -G ezbookkeeping -u 1000 ezbookkeeping
|
RUN addgroup -S -g 1000 ezbookkeeping && adduser -S -G ezbookkeeping -u 1000 ezbookkeeping
|
||||||
RUN apk --no-cache add tzdata
|
RUN apk --no-cache add tzdata
|
||||||
@@ -27,7 +27,8 @@ COPY docker/docker-entrypoint.sh /docker-entrypoint.sh
|
|||||||
RUN chmod +x /docker-entrypoint.sh
|
RUN chmod +x /docker-entrypoint.sh
|
||||||
RUN mkdir -p /ezbookkeeping && chown 1000:1000 /ezbookkeeping \
|
RUN mkdir -p /ezbookkeeping && chown 1000:1000 /ezbookkeeping \
|
||||||
&& mkdir -p /ezbookkeeping/data && chown 1000:1000 /ezbookkeeping/data \
|
&& mkdir -p /ezbookkeeping/data && chown 1000:1000 /ezbookkeeping/data \
|
||||||
&& mkdir -p /ezbookkeeping/log && chown 1000:1000 /ezbookkeeping/log
|
&& mkdir -p /ezbookkeeping/log && chown 1000:1000 /ezbookkeeping/log \
|
||||||
|
&& mkdir -p /ezbookkeeping/storage && chown 1000:1000 /ezbookkeeping/storage
|
||||||
WORKDIR /ezbookkeeping
|
WORKDIR /ezbookkeeping
|
||||||
COPY --from=be-builder --chown=1000:1000 /go/src/github.com/mayswind/ezbookkeeping/ezbookkeeping /ezbookkeeping/ezbookkeeping
|
COPY --from=be-builder --chown=1000:1000 /go/src/github.com/mayswind/ezbookkeeping/ezbookkeeping /ezbookkeeping/ezbookkeeping
|
||||||
COPY --from=fe-builder --chown=1000:1000 /go/src/github.com/mayswind/ezbookkeeping/dist /ezbookkeeping/public
|
COPY --from=fe-builder --chown=1000:1000 /go/src/github.com/mayswind/ezbookkeeping/dist /ezbookkeeping/public
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2020-2023 MaysWind (i@mayswind.net)
|
Copyright (c) 2020-2024 MaysWind (i@mayswind.net)
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
## Introduction
|
## Introduction
|
||||||
ezBookkeeping is a lightweight personal bookkeeping app hosted by yourself. It can be deployed on almost all platforms, including Windows, macOS and Linux on x86, amd64 and ARM architectures. You can even deploy it on an raspberry device. It also supports many different databases, including sqlite and mysql. With docker, you can just deploy it via one command without complicated configuration.
|
ezBookkeeping is a lightweight personal bookkeeping app hosted by yourself. It can be deployed on almost all platforms, including Windows, macOS and Linux on x86, amd64 and ARM architectures. You can even deploy it on an raspberry device. It also supports many different databases, including sqlite and mysql. With docker, you can just deploy it via one command without complicated configuration.
|
||||||
|
|
||||||
|
Online Demo: [https://ezbookkeeping-demo.mayswind.net](https://ezbookkeeping-demo.mayswind.net)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
1. Open source & Self-hosted
|
1. Open source & Self-hosted
|
||||||
2. Lightweight & Fast
|
2. Lightweight & Fast
|
||||||
@@ -53,19 +55,35 @@ Latest Daily Build:
|
|||||||
### Install from binary
|
### Install from binary
|
||||||
Latest release: [https://github.com/mayswind/ezbookkeeping/releases](https://github.com/mayswind/ezbookkeeping/releases)
|
Latest release: [https://github.com/mayswind/ezbookkeeping/releases](https://github.com/mayswind/ezbookkeeping/releases)
|
||||||
|
|
||||||
|
**Linux / macOS**
|
||||||
|
|
||||||
$ ./ezbookkeeping server run
|
$ ./ezbookkeeping server run
|
||||||
|
|
||||||
ezBookkeeping will listen at port 8080 as default. Then you can visit http://{YOUR_HOST_ADDRESS}:8080/ .
|
**Windows**
|
||||||
|
|
||||||
|
> .\ezbookkeeping.exe server run
|
||||||
|
|
||||||
|
ezBookkeeping will listen at port 8080 as default. Then you can visit `http://{YOUR_HOST_ADDRESS}:8080/` .
|
||||||
|
|
||||||
### Build from source
|
### Build from source
|
||||||
Make sure you have [Golang](https://golang.org/), [GCC](http://gcc.gnu.org/), [Node.js](https://nodejs.org/) and [NPM](https://www.npmjs.com/) installed. Then download the source code, and follow these steps:
|
Make sure you have [Golang](https://golang.org/), [GCC](http://gcc.gnu.org/), [Node.js](https://nodejs.org/) and [NPM](https://www.npmjs.com/) installed. Then download the source code, and follow these steps:
|
||||||
|
|
||||||
|
**Linux / macOS**
|
||||||
|
|
||||||
$ ./build.sh package -o ezbookkeeping.tar.gz
|
$ ./build.sh package -o ezbookkeeping.tar.gz
|
||||||
|
|
||||||
All the files will be packaged in `ezbookkeeping.tar.gz`.
|
All the files will be packaged in `ezbookkeeping.tar.gz`.
|
||||||
|
|
||||||
|
**Windows**
|
||||||
|
|
||||||
|
> .\build.bat package -o ezbookkeeping.zip
|
||||||
|
|
||||||
|
All the files will be packaged in `ezbookkeeping.zip`.
|
||||||
|
|
||||||
You can also build docker image, make sure you have [docker](https://www.docker.com/) installed, then follow these steps:
|
You can also build docker image, make sure you have [docker](https://www.docker.com/) installed, then follow these steps:
|
||||||
|
|
||||||
|
**Linux**
|
||||||
|
|
||||||
$ ./build.sh docker
|
$ ./build.sh docker
|
||||||
|
|
||||||
## Documents
|
## Documents
|
||||||
|
|||||||
@@ -0,0 +1,264 @@
|
|||||||
|
@echo off
|
||||||
|
|
||||||
|
set "TYPE="
|
||||||
|
set "NO_LINT=0"
|
||||||
|
set "NO_TEST=0"
|
||||||
|
set "RELEASE=%RELEASE_BUILD%"
|
||||||
|
set "RELEASE_TYPE=unknown"
|
||||||
|
set "VERSION="
|
||||||
|
set "COMMIT_HASH="
|
||||||
|
set "BUILD_UNIXTIME="
|
||||||
|
set "BUILD_DATE="
|
||||||
|
set "PACKAGE_FILENAME="
|
||||||
|
for /f %%a in ('"prompt $E$S & echo on & for %%b in (1) do rem"') do set "ESC=%%a"
|
||||||
|
|
||||||
|
if "%~1"=="" call :show_help & goto :end
|
||||||
|
goto :pre_parse_args
|
||||||
|
|
||||||
|
:echo_red
|
||||||
|
echo %ESC%[91m%~1%ESC%[0m
|
||||||
|
goto :eof
|
||||||
|
|
||||||
|
:set_unixtime
|
||||||
|
setlocal enableextensions
|
||||||
|
for /f %%x in ('wmic path win32_utctime get /format:list ^| findstr "="') do set %%x
|
||||||
|
set /a z=(14-100%Month%%%100)/12, y=10000%Year%%%10000-z
|
||||||
|
set /a ut=y*365+y/4-y/100+y/400+(153*(100%Month%%%100+12*z-3)+2)/5+Day-719469
|
||||||
|
set /a ut=ut*86400+100%Hour%%%100*3600+100%Minute%%%100*60+100%Second%%%100
|
||||||
|
endlocal & set "%1=%ut%" & goto :eof
|
||||||
|
|
||||||
|
:set_date
|
||||||
|
setlocal enableextensions
|
||||||
|
for /f %%x in ('wmic path win32_localtime get /format:list ^| findstr "="') do set %%x
|
||||||
|
if %Month% lss 10 set "Month=0%Month%"
|
||||||
|
if %Day% lss 10 set "Day=0%Day%"
|
||||||
|
endlocal & set "%1=%Year%%Month%%Day%" & goto :eof
|
||||||
|
|
||||||
|
:check_dependency
|
||||||
|
if "%~1"=="" goto :eof
|
||||||
|
where /q %~1 || call :echo_red "Error: "%~1" is required." && goto :end
|
||||||
|
|
||||||
|
shift
|
||||||
|
goto :check_dependency
|
||||||
|
|
||||||
|
:show_help
|
||||||
|
echo ezBookkeeping build script for Windows
|
||||||
|
echo.
|
||||||
|
echo Usage:
|
||||||
|
echo build.cmd type [options]
|
||||||
|
echo.
|
||||||
|
echo Types:
|
||||||
|
echo backend Build backend binary file
|
||||||
|
echo frontend Build frontend files
|
||||||
|
echo package Build package archive
|
||||||
|
echo.
|
||||||
|
echo Options:
|
||||||
|
echo /r, --release Build release (The script will use environment variable "RELEASE_BUILD" to detect whether this is release building by default)
|
||||||
|
echo /o, --output ^<filename^> Package file name (For "package" type only)
|
||||||
|
echo --no-lint Do not execute lint check before building
|
||||||
|
echo --no-test Do not execute unit testing before building
|
||||||
|
echo /h, --help Show help
|
||||||
|
goto :eof
|
||||||
|
|
||||||
|
:pre_parse_args
|
||||||
|
if "%~1"=="" goto :post_parse_args
|
||||||
|
|
||||||
|
if /i "%~1"=="backend" set "TYPE=%~1" & shift
|
||||||
|
if /i "%~1"=="frontend" set "TYPE=%~1" & shift
|
||||||
|
if /i "%~1"=="package" set "TYPE=%~1" & shift
|
||||||
|
|
||||||
|
:parse_args
|
||||||
|
if "%~1"=="" goto :post_parse_args
|
||||||
|
|
||||||
|
if /i "%~1"=="/r" set "RELEASE=1" & shift & goto :parse_args
|
||||||
|
if /i "%~1"=="-r" set "RELEASE=1" & shift & goto :parse_args
|
||||||
|
if /i "%~1"=="--release" set "RELEASE=1" & shift & goto :parse_args
|
||||||
|
|
||||||
|
if /i "%~1"=="/o" set "PACKAGE_FILENAME=%~2" & shift & shift & goto :parse_args
|
||||||
|
if /i "%~1"=="-o" set "PACKAGE_FILENAME=%~2" & shift & shift & goto :parse_args
|
||||||
|
if /i "%~1"=="--output" set "PACKAGE_FILENAME=%~2" & shift & shift & goto :parse_args
|
||||||
|
|
||||||
|
if /i "%~1"=="--no-lint" set "NO_LINT=1" & shift & goto :parse_args
|
||||||
|
if /i "%~1"=="--no-test" set "NO_TEST=1" & shift & goto :parse_args
|
||||||
|
|
||||||
|
if /i "%~1"=="/h" call :show_help & goto :end
|
||||||
|
if /i "%~1"=="-h" call :show_help & goto :end
|
||||||
|
if /i "%~1"=="--help" call :show_help & goto :end
|
||||||
|
|
||||||
|
call :echo_red "Invalid argument: %~1" & call :show_help & goto :end
|
||||||
|
|
||||||
|
:post_parse_args
|
||||||
|
if "%RELEASE%"=="" set "RELEASE=0"
|
||||||
|
|
||||||
|
if "%RELEASE%"=="0" (
|
||||||
|
set "RELEASE_TYPE=snapshot"
|
||||||
|
) else (
|
||||||
|
set "RELEASE_TYPE=release"
|
||||||
|
)
|
||||||
|
|
||||||
|
:check_type_dependencies
|
||||||
|
if not defined TYPE call :echo_red "Error: No specified type" & call :show_help & goto :end
|
||||||
|
|
||||||
|
call :check_dependency "git"
|
||||||
|
if "%TYPE%"=="backend" call :check_dependency "go" "gcc"
|
||||||
|
if "%TYPE%"=="frontend" call :check_dependency "node" "npm"
|
||||||
|
if "%TYPE%"=="package" call :check_dependency "go" "gcc" "node" "npm" "7z"
|
||||||
|
|
||||||
|
if not "%errorlevel%"=="0" goto :end
|
||||||
|
|
||||||
|
:set_build_parameters
|
||||||
|
for /f "tokens=2 delims=:" %%x in ('findstr "\"version\": \"*\"," package.json') do set "VERSION=%%x"
|
||||||
|
set VERSION=%VERSION: =%
|
||||||
|
set VERSION=%VERSION:,=%
|
||||||
|
set VERSION=%VERSION:"=%
|
||||||
|
for /f %%x in ('git rev-parse --short HEAD') do set "COMMIT_HASH=%%x"
|
||||||
|
call :set_unixtime BUILD_UNIXTIME
|
||||||
|
call :set_date BUILD_DATE
|
||||||
|
|
||||||
|
:main
|
||||||
|
if "%TYPE%"=="backend" call :build_backend & goto :end
|
||||||
|
if "%TYPE%"=="frontend" call :build_frontend & goto :end
|
||||||
|
if "%TYPE%"=="package" call :build_package & goto :end
|
||||||
|
goto :end
|
||||||
|
|
||||||
|
:build_backend
|
||||||
|
setlocal enabledelayedexpansion
|
||||||
|
echo Pulling backend dependencies...
|
||||||
|
call go get .
|
||||||
|
|
||||||
|
if "%NO_LINT%"=="0" (
|
||||||
|
echo Executing backend lint checking...
|
||||||
|
call go vet -v .\...
|
||||||
|
|
||||||
|
if !errorlevel! neq 0 (
|
||||||
|
call :echo_red "Error: Failed to pass lint checking"
|
||||||
|
goto :end
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%NO_TEST%"=="0" (
|
||||||
|
echo Executing backend unit testing...
|
||||||
|
call go clean -cache
|
||||||
|
call go test .\... -v
|
||||||
|
|
||||||
|
if !errorlevel! neq 0 (
|
||||||
|
call :echo_red "Error: Failed to pass unit testing"
|
||||||
|
goto :end
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
endlocal
|
||||||
|
|
||||||
|
set "CGO_ENABLED=1"
|
||||||
|
|
||||||
|
setlocal
|
||||||
|
set "backend_build_extra_arguments=-X main.Version=%VERSION%"
|
||||||
|
set "backend_build_extra_arguments=%backend_build_extra_arguments% -X main.CommitHash=%COMMIT_HASH%"
|
||||||
|
|
||||||
|
if "%RELEASE%"=="0" (
|
||||||
|
set "backend_build_extra_arguments=%backend_build_extra_arguments% -X main.BuildUnixTime=%BUILD_UNIXTIME%"
|
||||||
|
)
|
||||||
|
|
||||||
|
echo Building backend binary file (%RELEASE_TYPE%)...
|
||||||
|
|
||||||
|
call go build -a -v -trimpath -tags timetzdata -ldflags "-w -s -linkmode external -extldflags '-static' %backend_build_extra_arguments%" -o ezbookkeeping.exe ezbookkeeping.go
|
||||||
|
endlocal
|
||||||
|
|
||||||
|
set "CGO_ENABLED="
|
||||||
|
|
||||||
|
goto :eof
|
||||||
|
|
||||||
|
:build_frontend
|
||||||
|
setlocal enabledelayedexpansion
|
||||||
|
echo Pulling frontend dependencies...
|
||||||
|
call npm install
|
||||||
|
|
||||||
|
if "%NO_LINT%"=="0" (
|
||||||
|
echo Executing frontend lint checking...
|
||||||
|
|
||||||
|
call npm run lint
|
||||||
|
|
||||||
|
if !errorlevel! neq 0 (
|
||||||
|
call :echo_red "Error: Failed to pass lint checking"
|
||||||
|
goto :end
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
endlocal
|
||||||
|
|
||||||
|
echo Building frontend files(%RELEASE_TYPE%)...
|
||||||
|
|
||||||
|
if "%RELEASE%"=="0" (
|
||||||
|
set "buildUnixTime=%BUILD_UNIXTIME%"
|
||||||
|
call npm run build
|
||||||
|
set "buildUnixTime="
|
||||||
|
) else (
|
||||||
|
call npm run build
|
||||||
|
)
|
||||||
|
|
||||||
|
goto :eof
|
||||||
|
|
||||||
|
:build_package
|
||||||
|
setlocal enabledelayedexpansion
|
||||||
|
set "package_file_name=%VERSION%"
|
||||||
|
|
||||||
|
if "%RELEASE%"=="0" (
|
||||||
|
set "build_date="
|
||||||
|
set "package_file_name=%package_file_name%-%build_date%"
|
||||||
|
)
|
||||||
|
|
||||||
|
set "package_file_name=ezbookkeeping-%package_file_name%-windows.zip"
|
||||||
|
|
||||||
|
if defined PACKAGE_FILENAME set "package_file_name=%PACKAGE_FILENAME%"
|
||||||
|
|
||||||
|
echo Building package archive "%package_file_name%" (%RELEASE_TYPE%)...
|
||||||
|
|
||||||
|
call :build_backend
|
||||||
|
|
||||||
|
if !errorlevel! neq 0 (
|
||||||
|
goto :end
|
||||||
|
)
|
||||||
|
|
||||||
|
call :build_frontend
|
||||||
|
|
||||||
|
if !errorlevel! neq 0 (
|
||||||
|
goto :end
|
||||||
|
)
|
||||||
|
|
||||||
|
rmdir package /s /q
|
||||||
|
mkdir package
|
||||||
|
mkdir package\data
|
||||||
|
mkdir package\storage
|
||||||
|
mkdir package\log
|
||||||
|
xcopy ezbookkeeping.exe package\
|
||||||
|
xcopy dist package\public /e /i
|
||||||
|
xcopy conf package\conf /e /i
|
||||||
|
xcopy templates package\templates /e /i
|
||||||
|
xcopy LICENSE package\
|
||||||
|
|
||||||
|
cd package
|
||||||
|
|
||||||
|
if !errorlevel! neq 0 (
|
||||||
|
call :echo_red "Error: Build Failed"
|
||||||
|
goto :end
|
||||||
|
)
|
||||||
|
|
||||||
|
call 7z a -r -tzip -mx9 ..\%package_file_name% package *
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
endlocal
|
||||||
|
|
||||||
|
goto :eof
|
||||||
|
|
||||||
|
:end
|
||||||
|
set "TYPE="
|
||||||
|
set "NO_LINT="
|
||||||
|
set "NO_TEST="
|
||||||
|
set "RELEASE="
|
||||||
|
set "RELEASE_TYPE="
|
||||||
|
set "VERSION="
|
||||||
|
set "COMMIT_HASH="
|
||||||
|
set "BUILD_UNIXTIME="
|
||||||
|
set "BUILD_DATE="
|
||||||
|
set "PACKAGE_FILENAME="
|
||||||
|
exit /B
|
||||||
@@ -202,10 +202,12 @@ build_package() {
|
|||||||
rm -rf package
|
rm -rf package
|
||||||
mkdir package
|
mkdir package
|
||||||
mkdir package/data
|
mkdir package/data
|
||||||
|
mkdir package/storage
|
||||||
mkdir package/log
|
mkdir package/log
|
||||||
cp ezbookkeeping package/
|
cp ezbookkeeping package/
|
||||||
cp -R dist package/public
|
cp -R dist package/public
|
||||||
cp -R conf package/conf
|
cp -R conf package/conf
|
||||||
|
cp -R templates package/templates
|
||||||
cp LICENSE package/
|
cp LICENSE package/
|
||||||
|
|
||||||
cd package || { echo_red "Error: Build Failed"; exit 1; }
|
cd package || { echo_red "Error: Build Failed"; exit 1; }
|
||||||
|
|||||||
+10
-2
@@ -58,7 +58,7 @@ func updateAllDatabaseTablesStructure() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.BootInfof("[database.updateAllDatabaseTablesStructure] two factor table maintained successfully")
|
log.BootInfof("[database.updateAllDatabaseTablesStructure] two-factor table maintained successfully")
|
||||||
|
|
||||||
err = datastore.Container.UserStore.SyncStructs(new(models.TwoFactorRecoveryCode))
|
err = datastore.Container.UserStore.SyncStructs(new(models.TwoFactorRecoveryCode))
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ func updateAllDatabaseTablesStructure() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.BootInfof("[database.updateAllDatabaseTablesStructure] two factor recovery code table maintained successfully")
|
log.BootInfof("[database.updateAllDatabaseTablesStructure] two-factor recovery code table maintained successfully")
|
||||||
|
|
||||||
err = datastore.Container.TokenStore.SyncStructs(new(models.TokenRecord))
|
err = datastore.Container.TokenStore.SyncStructs(new(models.TokenRecord))
|
||||||
|
|
||||||
@@ -116,5 +116,13 @@ func updateAllDatabaseTablesStructure() error {
|
|||||||
|
|
||||||
log.BootInfof("[database.updateAllDatabaseTablesStructure] transaction tag index table maintained successfully")
|
log.BootInfof("[database.updateAllDatabaseTablesStructure] transaction tag index table maintained successfully")
|
||||||
|
|
||||||
|
err = datastore.Container.UserDataStore.SyncStructs(new(models.TransactionTemplate))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.BootInfof("[database.updateAllDatabaseTablesStructure] transaction template table maintained successfully")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,12 @@ import (
|
|||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/datastore"
|
"github.com/mayswind/ezbookkeeping/pkg/datastore"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/duplicatechecker"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/exchangerates"
|
"github.com/mayswind/ezbookkeeping/pkg/exchangerates"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/mail"
|
"github.com/mayswind/ezbookkeeping/pkg/mail"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/storage"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/uuid"
|
"github.com/mayswind/ezbookkeeping/pkg/uuid"
|
||||||
)
|
)
|
||||||
@@ -55,6 +57,10 @@ func initializeSystem(c *cli.Context) (*settings.Config, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.SecretKeyNoSet {
|
||||||
|
log.BootWarnf("[initializer.initializeSystem] \"secret_key\" in config file is not set, please change it to keep your user data safe")
|
||||||
|
}
|
||||||
|
|
||||||
settings.SetCurrentConfig(config)
|
settings.SetCurrentConfig(config)
|
||||||
|
|
||||||
err = datastore.InitializeDataStore(config)
|
err = datastore.InitializeDataStore(config)
|
||||||
@@ -75,6 +81,15 @@ func initializeSystem(c *cli.Context) (*settings.Config, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = storage.InitializeStorageContainer(config)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if !isDisableBootLog {
|
||||||
|
log.BootErrorf("[initializer.initializeSystem] initializes object storage failed, because %s", err.Error())
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
err = uuid.InitializeUuidGenerator(config)
|
err = uuid.InitializeUuidGenerator(config)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -84,6 +99,15 @@ func initializeSystem(c *cli.Context) (*settings.Config, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = duplicatechecker.InitializeDuplicateChecker(config)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if !isDisableBootLog {
|
||||||
|
log.BootErrorf("[initializer.initializeSystem] initializes duplicate checker failed, because %s", err.Error())
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
err = mail.InitializeMailer(config)
|
err = mail.InitializeMailer(config)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -121,7 +145,9 @@ func getConfigWithoutSensitiveData(config *settings.Config) *settings.Config {
|
|||||||
|
|
||||||
clonedConfig.DatabaseConfig.DatabasePassword = "****"
|
clonedConfig.DatabaseConfig.DatabasePassword = "****"
|
||||||
clonedConfig.SMTPConfig.SMTPPasswd = "****"
|
clonedConfig.SMTPConfig.SMTPPasswd = "****"
|
||||||
|
clonedConfig.MinIOConfig.SecretAccessKey = "****"
|
||||||
clonedConfig.SecretKey = "****"
|
clonedConfig.SecretKey = "****"
|
||||||
|
clonedConfig.AmapApplicationSecret = "****"
|
||||||
|
|
||||||
return clonedConfig
|
return clonedConfig
|
||||||
}
|
}
|
||||||
|
|||||||
+62
-6
@@ -2,13 +2,14 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
|
||||||
clis "github.com/mayswind/ezbookkeeping/pkg/cli"
|
clis "github.com/mayswind/ezbookkeeping/pkg/cli"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -229,9 +230,22 @@ var UserData = &cli.Command{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "transaction-tag-index-fix-transaction-time",
|
||||||
|
Usage: "Fix the transaction tag index data which does not have transaction time",
|
||||||
|
Action: fixTransactionTagIndexNotHaveTransactionTime,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "username",
|
||||||
|
Aliases: []string{"n"},
|
||||||
|
Required: true,
|
||||||
|
Usage: "Specific user name",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "transaction-export",
|
Name: "transaction-export",
|
||||||
Usage: "Export user all transactions to csv file",
|
Usage: "Export user all transactions to file",
|
||||||
Action: exportUserTransaction,
|
Action: exportUserTransaction,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
@@ -246,6 +260,12 @@ var UserData = &cli.Command{
|
|||||||
Required: true,
|
Required: true,
|
||||||
Usage: "Specific exported file path (e.g. transaction.csv)",
|
Usage: "Specific exported file path (e.g. transaction.csv)",
|
||||||
},
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "type",
|
||||||
|
Aliases: []string{"t"},
|
||||||
|
Required: false,
|
||||||
|
Usage: "Export file type, support csv or tsv, default is csv",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -468,11 +488,11 @@ func disableUser2FA(c *cli.Context) error {
|
|||||||
err = clis.UserData.DisableUserTwoFactorAuthorization(c, username)
|
err = clis.UserData.DisableUserTwoFactorAuthorization(c, username)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.BootErrorf("[user_data.disableUser2FA] error occurs when disabling user two factor authorization")
|
log.BootErrorf("[user_data.disableUser2FA] error occurs when disabling user two-factor authorization")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.BootInfof("[user_data.disableUser2FA] two factor authorization of user \"%s\" has been disabled", username)
|
log.BootInfof("[user_data.disableUser2FA] two-factor authorization of user \"%s\" has been disabled", username)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -546,6 +566,29 @@ func checkUserTransactionAndAccount(c *cli.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fixTransactionTagIndexNotHaveTransactionTime(c *cli.Context) error {
|
||||||
|
_, err := initializeSystem(c)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
username := c.String("username")
|
||||||
|
|
||||||
|
log.BootInfof("[user_data.fixTransactionTagIndexNotHaveTransactionTime] starting fixing user \"%s\" transaction tag index data", username)
|
||||||
|
|
||||||
|
_, err = clis.UserData.FixTransactionTagIndexWithTransactionTime(c, username)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.BootErrorf("[user_data.fixTransactionTagIndexNotHaveTransactionTime] error occurs when fixing user data")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.BootInfof("[user_data.fixTransactionTagIndexNotHaveTransactionTime] user transaction tag index data has been fixed successfully")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func exportUserTransaction(c *cli.Context) error {
|
func exportUserTransaction(c *cli.Context) error {
|
||||||
_, err := initializeSystem(c)
|
_, err := initializeSystem(c)
|
||||||
|
|
||||||
@@ -555,9 +598,15 @@ func exportUserTransaction(c *cli.Context) error {
|
|||||||
|
|
||||||
username := c.String("username")
|
username := c.String("username")
|
||||||
filePath := c.String("file")
|
filePath := c.String("file")
|
||||||
|
fileType := c.String("type")
|
||||||
|
|
||||||
|
if fileType != "" && fileType != "csv" && fileType != "tsv" {
|
||||||
|
log.BootErrorf("[user_data.exportUserTransaction] export file type is not supported")
|
||||||
|
return errs.ErrNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
log.BootErrorf("[user_data.exportUserTransaction] export file path is not specified")
|
log.BootErrorf("[user_data.exportUserTransaction] export file path is unspecified")
|
||||||
return os.ErrNotExist
|
return os.ErrNotExist
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -570,7 +619,7 @@ func exportUserTransaction(c *cli.Context) error {
|
|||||||
|
|
||||||
log.BootInfof("[user_data.exportUserTransaction] starting exporting user \"%s\" data", username)
|
log.BootInfof("[user_data.exportUserTransaction] starting exporting user \"%s\" data", username)
|
||||||
|
|
||||||
content, err := clis.UserData.ExportTransaction(c, username)
|
content, err := clis.UserData.ExportTransaction(c, username, fileType)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.BootErrorf("[user_data.exportUserTransaction] error occurs when exporting user data")
|
log.BootErrorf("[user_data.exportUserTransaction] error occurs when exporting user data")
|
||||||
@@ -605,6 +654,12 @@ func printUserInfo(user *models.User) {
|
|||||||
fmt.Printf("[ShortDateFormat] %s (%d)\n", user.ShortDateFormat, user.ShortDateFormat)
|
fmt.Printf("[ShortDateFormat] %s (%d)\n", user.ShortDateFormat, user.ShortDateFormat)
|
||||||
fmt.Printf("[LongTimeFormat] %s (%d)\n", user.LongTimeFormat, user.LongTimeFormat)
|
fmt.Printf("[LongTimeFormat] %s (%d)\n", user.LongTimeFormat, user.LongTimeFormat)
|
||||||
fmt.Printf("[ShortTimeFormat] %s (%d)\n", user.ShortTimeFormat, user.ShortTimeFormat)
|
fmt.Printf("[ShortTimeFormat] %s (%d)\n", user.ShortTimeFormat, user.ShortTimeFormat)
|
||||||
|
fmt.Printf("[DecimalSeparator] %s (%d)\n", user.DecimalSeparator, user.DecimalSeparator)
|
||||||
|
fmt.Printf("[DigitGroupingSymbol] %s (%d)\n", user.DigitGroupingSymbol, user.DigitGroupingSymbol)
|
||||||
|
fmt.Printf("[DigitGrouping] %s (%d)\n", user.DigitGrouping, user.DigitGrouping)
|
||||||
|
fmt.Printf("[CurrencyDisplayType] %s (%d)\n", user.CurrencyDisplayType, user.CurrencyDisplayType)
|
||||||
|
fmt.Printf("[ExpenseAmountColor] %s (%d)\n", user.ExpenseAmountColor, user.ExpenseAmountColor)
|
||||||
|
fmt.Printf("[IncomeAmountColor] %s (%d)\n", user.IncomeAmountColor, user.IncomeAmountColor)
|
||||||
fmt.Printf("[Deleted] %t\n", user.Deleted)
|
fmt.Printf("[Deleted] %t\n", user.Deleted)
|
||||||
fmt.Printf("[EmailVerified] %t\n", user.EmailVerified)
|
fmt.Printf("[EmailVerified] %t\n", user.EmailVerified)
|
||||||
fmt.Printf("[CreatedAt] %s (%d)\n", utils.FormatUnixTimeToLongDateTimeInServerTimezone(user.CreatedUnixTime), user.CreatedUnixTime)
|
fmt.Printf("[CreatedAt] %s (%d)\n", utils.FormatUnixTimeToLongDateTimeInServerTimezone(user.CreatedUnixTime), user.CreatedUnixTime)
|
||||||
@@ -625,5 +680,6 @@ func printUserInfo(user *models.User) {
|
|||||||
func printTokenInfo(token *models.TokenRecord) {
|
func printTokenInfo(token *models.TokenRecord) {
|
||||||
fmt.Printf("[CreatedAt] %s (%d)\n", utils.FormatUnixTimeToLongDateTimeInServerTimezone(token.CreatedUnixTime), token.CreatedUnixTime)
|
fmt.Printf("[CreatedAt] %s (%d)\n", utils.FormatUnixTimeToLongDateTimeInServerTimezone(token.CreatedUnixTime), token.CreatedUnixTime)
|
||||||
fmt.Printf("[ExpiredAt] %s (%d)\n", utils.FormatUnixTimeToLongDateTimeInServerTimezone(token.ExpiredUnixTime), token.ExpiredUnixTime)
|
fmt.Printf("[ExpiredAt] %s (%d)\n", utils.FormatUnixTimeToLongDateTimeInServerTimezone(token.ExpiredUnixTime), token.ExpiredUnixTime)
|
||||||
|
fmt.Printf("[LastSeen] %s (%d)\n", utils.FormatUnixTimeToLongDateTimeInServerTimezone(token.LastSeenUnixTime), token.LastSeenUnixTime)
|
||||||
fmt.Printf("[UserAgent] %s\n", token.UserAgent)
|
fmt.Printf("[UserAgent] %s\n", token.UserAgent)
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -66,7 +66,7 @@ func parseRequestId(c *cli.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
newRequestId := defaultGenerator.GenerateRequestId(net.IPv4zero.String())
|
newRequestId := defaultGenerator.GenerateRequestId(net.IPv4zero.String(), 0)
|
||||||
newRequestIdInfo, err := defaultGenerator.ParseRequestIdInfo(newRequestId)
|
newRequestIdInfo, err := defaultGenerator.ParseRequestIdInfo(newRequestId)
|
||||||
printRequestIdInfo(requestId, requestIdInfo, newRequestIdInfo)
|
printRequestIdInfo(requestId, requestIdInfo, newRequestIdInfo)
|
||||||
|
|
||||||
@@ -114,7 +114,6 @@ func printRequestIdInfo(requestId string, requestIdInfo *requestid.RequestIdInfo
|
|||||||
fmt.Printf("[SecondsElapsedToday] %d\n", requestIdInfo.SecondsElapsedToday)
|
fmt.Printf("[SecondsElapsedToday] %d\n", requestIdInfo.SecondsElapsedToday)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("[RandomNumber] %d\n", requestIdInfo.RandomNumber)
|
|
||||||
fmt.Printf("[RequestSeqId] %d\n", requestIdInfo.RequestSeqId)
|
fmt.Printf("[RequestSeqId] %d\n", requestIdInfo.RequestSeqId)
|
||||||
fmt.Printf("[IsClientIpv6] %t\n", requestIdInfo.IsClientIpv6)
|
fmt.Printf("[IsClientIpv6] %t\n", requestIdInfo.IsClientIpv6)
|
||||||
|
|
||||||
@@ -125,4 +124,6 @@ func printRequestIdInfo(requestId string, requestIdInfo *requestid.RequestIdInfo
|
|||||||
binary.BigEndian.PutUint32(ip, requestIdInfo.ClientIp)
|
binary.BigEndian.PutUint32(ip, requestIdInfo.ClientIp)
|
||||||
fmt.Printf("[ClientIpv4] %s\n", ip.String())
|
fmt.Printf("[ClientIpv4] %s\n", ip.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Printf("[ClientPort] %d\n", requestIdInfo.ClientPort)
|
||||||
}
|
}
|
||||||
|
|||||||
+75
-17
@@ -44,13 +44,13 @@ func startWebServer(c *cli.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.BootInfof("[server.startWebServer] static root path is %s", config.StaticRootPath)
|
log.BootInfof("[webserver.startWebServer] static root path is %s", config.StaticRootPath)
|
||||||
|
|
||||||
if config.AutoUpdateDatabase {
|
if config.AutoUpdateDatabase {
|
||||||
err = updateAllDatabaseTablesStructure()
|
err = updateAllDatabaseTablesStructure()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.BootErrorf("[server.startWebServer] update database table structure failed, because %s", err.Error())
|
log.BootErrorf("[webserver.startWebServer] update database table structure failed, because %s", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,7 +58,7 @@ func startWebServer(c *cli.Context) error {
|
|||||||
err = requestid.InitializeRequestIdGenerator(config)
|
err = requestid.InitializeRequestIdGenerator(config)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.BootErrorf("[server.startWebServer] initializes requestid generator failed, because %s", err.Error())
|
log.BootErrorf("[webserver.startWebServer] initializes requestid generator failed, because %s", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ func startWebServer(c *cli.Context) error {
|
|||||||
uuidServerInfo = fmt.Sprintf(", current uuid server id is %d", config.UuidServerId)
|
uuidServerInfo = fmt.Sprintf(", current uuid server id is %d", config.UuidServerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.BootInfof("[server.startWebServer] %s%s", serverInfo, uuidServerInfo)
|
log.BootInfof("[webserver.startWebServer] %s%s", serverInfo, uuidServerInfo)
|
||||||
|
|
||||||
if config.Mode == settings.MODE_PRODUCTION {
|
if config.Mode == settings.MODE_PRODUCTION {
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
@@ -89,6 +89,7 @@ func startWebServer(c *cli.Context) error {
|
|||||||
_ = v.RegisterValidation("validEmail", validators.ValidEmail)
|
_ = v.RegisterValidation("validEmail", validators.ValidEmail)
|
||||||
_ = v.RegisterValidation("validCurrency", validators.ValidCurrency)
|
_ = v.RegisterValidation("validCurrency", validators.ValidCurrency)
|
||||||
_ = v.RegisterValidation("validHexRGBColor", validators.ValidHexRGBColor)
|
_ = v.RegisterValidation("validHexRGBColor", validators.ValidHexRGBColor)
|
||||||
|
_ = v.RegisterValidation("validAmountFilter", validators.ValidAmountFilter)
|
||||||
}
|
}
|
||||||
|
|
||||||
router.NoRoute(bindApi(api.Default.ApiNotFound))
|
router.NoRoute(bindApi(api.Default.ApiNotFound))
|
||||||
@@ -144,6 +145,14 @@ func startWebServer(c *cli.Context) error {
|
|||||||
router.StaticFile("/desktop/"+workboxFileNames[i], filepath.Join(config.StaticRootPath, workboxFileNames[i]))
|
router.StaticFile("/desktop/"+workboxFileNames[i], filepath.Join(config.StaticRootPath, workboxFileNames[i]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.AvatarProvider == settings.InternalAvatarProvider {
|
||||||
|
avatarRoute := router.Group("/avatar")
|
||||||
|
avatarRoute.Use(bindMiddleware(middlewares.JWTAuthorizationByQueryString))
|
||||||
|
{
|
||||||
|
avatarRoute.GET("/:fileName", bindImage(api.Users.UserGetAvatarHandler))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
router.GET("/healthz.json", bindApi(api.Healths.HealthStatusHandler))
|
router.GET("/healthz.json", bindApi(api.Healths.HealthStatusHandler))
|
||||||
|
|
||||||
if config.Mode == settings.MODE_DEVELOPMENT {
|
if config.Mode == settings.MODE_DEVELOPMENT {
|
||||||
@@ -160,9 +169,17 @@ func startWebServer(c *cli.Context) error {
|
|||||||
config.MapProvider == settings.OpenTopoMapProvider ||
|
config.MapProvider == settings.OpenTopoMapProvider ||
|
||||||
config.MapProvider == settings.OPNVKarteMapProvider ||
|
config.MapProvider == settings.OPNVKarteMapProvider ||
|
||||||
config.MapProvider == settings.CyclOSMMapProvider ||
|
config.MapProvider == settings.CyclOSMMapProvider ||
|
||||||
config.MapProvider == settings.TomTomMapProvider {
|
config.MapProvider == settings.CartoDBMapProvider ||
|
||||||
|
config.MapProvider == settings.TomTomMapProvider ||
|
||||||
|
config.MapProvider == settings.TianDiTuProvider ||
|
||||||
|
config.MapProvider == settings.CustomProvider {
|
||||||
proxyRoute.GET("/map/tile/:zoomLevel/:coordinateX/:fileName", bindProxy(api.MapImages.MapTileImageProxyHandler))
|
proxyRoute.GET("/map/tile/:zoomLevel/:coordinateX/:fileName", bindProxy(api.MapImages.MapTileImageProxyHandler))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.MapProvider == settings.TianDiTuProvider ||
|
||||||
|
(config.MapProvider == settings.CustomProvider && config.CustomMapTileServerAnnotationLayerUrl != "") {
|
||||||
|
proxyRoute.GET("/map/annotation/:zoomLevel/:coordinateX/:fileName", bindProxy(api.MapImages.MapAnnotationImageProxyHandler))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,7 +195,7 @@ func startWebServer(c *cli.Context) error {
|
|||||||
qrCodeRoute.Use(bindMiddleware(middlewares.RequestId(config)))
|
qrCodeRoute.Use(bindMiddleware(middlewares.RequestId(config)))
|
||||||
{
|
{
|
||||||
qrCodeCacheStore := persistence.NewInMemoryStore(time.Minute)
|
qrCodeCacheStore := persistence.NewInMemoryStore(time.Minute)
|
||||||
qrCodeRoute.GET("/mobile_url.png", bindCachedPngImage(api.QrCodes.MobileUrlQrCodeHandler, qrCodeCacheStore))
|
qrCodeRoute.GET("/mobile_url.png", bindCachedImage(api.QrCodes.MobileUrlQrCodeHandler, qrCodeCacheStore))
|
||||||
}
|
}
|
||||||
|
|
||||||
apiRoute := router.Group("/api")
|
apiRoute := router.Group("/api")
|
||||||
@@ -236,11 +253,16 @@ func startWebServer(c *cli.Context) error {
|
|||||||
apiV1Route.GET("/users/profile/get.json", bindApi(api.Users.UserProfileHandler))
|
apiV1Route.GET("/users/profile/get.json", bindApi(api.Users.UserProfileHandler))
|
||||||
apiV1Route.POST("/users/profile/update.json", bindApiWithTokenUpdate(api.Users.UserUpdateProfileHandler, config))
|
apiV1Route.POST("/users/profile/update.json", bindApiWithTokenUpdate(api.Users.UserUpdateProfileHandler, config))
|
||||||
|
|
||||||
|
if config.AvatarProvider == settings.InternalAvatarProvider {
|
||||||
|
apiV1Route.POST("/users/avatar/update.json", bindApi(api.Users.UserUpdateAvatarHandler))
|
||||||
|
apiV1Route.POST("/users/avatar/remove.json", bindApi(api.Users.UserRemoveAvatarHandler))
|
||||||
|
}
|
||||||
|
|
||||||
if config.EnableUserVerifyEmail {
|
if config.EnableUserVerifyEmail {
|
||||||
apiV1Route.POST("/users/verify_email/resend.json", bindApi(api.Users.UserSendVerifyEmailByLoginedUserHandler))
|
apiV1Route.POST("/users/verify_email/resend.json", bindApi(api.Users.UserSendVerifyEmailByLoginedUserHandler))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Two Factor Authorization
|
// Two-Factor Authorization
|
||||||
if config.EnableTwoFactor {
|
if config.EnableTwoFactor {
|
||||||
apiV1Route.GET("/users/2fa/status.json", bindApi(api.TwoFactorAuthorizations.TwoFactorStatusHandler))
|
apiV1Route.GET("/users/2fa/status.json", bindApi(api.TwoFactorAuthorizations.TwoFactorStatusHandler))
|
||||||
apiV1Route.POST("/users/2fa/enable/request.json", bindApi(api.TwoFactorAuthorizations.TwoFactorEnableRequestHandler))
|
apiV1Route.POST("/users/2fa/enable/request.json", bindApi(api.TwoFactorAuthorizations.TwoFactorEnableRequestHandler))
|
||||||
@@ -254,7 +276,8 @@ func startWebServer(c *cli.Context) error {
|
|||||||
apiV1Route.POST("/data/clear.json", bindApi(api.DataManagements.ClearDataHandler))
|
apiV1Route.POST("/data/clear.json", bindApi(api.DataManagements.ClearDataHandler))
|
||||||
|
|
||||||
if config.EnableDataExport {
|
if config.EnableDataExport {
|
||||||
apiV1Route.GET("/data/export.csv", bindCsv(api.DataManagements.ExportDataHandler))
|
apiV1Route.GET("/data/export.csv", bindCsv(api.DataManagements.ExportDataToEzbookkeepingCSVHandler))
|
||||||
|
apiV1Route.GET("/data/export.tsv", bindTsv(api.DataManagements.ExportDataToEzbookkeepingTSVHandler))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Accounts
|
// Accounts
|
||||||
@@ -271,8 +294,8 @@ func startWebServer(c *cli.Context) error {
|
|||||||
apiV1Route.GET("/transactions/list.json", bindApi(api.Transactions.TransactionListHandler))
|
apiV1Route.GET("/transactions/list.json", bindApi(api.Transactions.TransactionListHandler))
|
||||||
apiV1Route.GET("/transactions/list/by_month.json", bindApi(api.Transactions.TransactionMonthListHandler))
|
apiV1Route.GET("/transactions/list/by_month.json", bindApi(api.Transactions.TransactionMonthListHandler))
|
||||||
apiV1Route.GET("/transactions/statistics.json", bindApi(api.Transactions.TransactionStatisticsHandler))
|
apiV1Route.GET("/transactions/statistics.json", bindApi(api.Transactions.TransactionStatisticsHandler))
|
||||||
|
apiV1Route.GET("/transactions/statistics/trends.json", bindApi(api.Transactions.TransactionStatisticsTrendsHandler))
|
||||||
apiV1Route.GET("/transactions/amounts.json", bindApi(api.Transactions.TransactionAmountsHandler))
|
apiV1Route.GET("/transactions/amounts.json", bindApi(api.Transactions.TransactionAmountsHandler))
|
||||||
apiV1Route.GET("/transactions/amounts/by_month.json", bindApi(api.Transactions.TransactionMonthAmountsHandler))
|
|
||||||
apiV1Route.GET("/transactions/get.json", bindApi(api.Transactions.TransactionGetHandler))
|
apiV1Route.GET("/transactions/get.json", bindApi(api.Transactions.TransactionGetHandler))
|
||||||
apiV1Route.POST("/transactions/add.json", bindApi(api.Transactions.TransactionCreateHandler))
|
apiV1Route.POST("/transactions/add.json", bindApi(api.Transactions.TransactionCreateHandler))
|
||||||
apiV1Route.POST("/transactions/modify.json", bindApi(api.Transactions.TransactionModifyHandler))
|
apiV1Route.POST("/transactions/modify.json", bindApi(api.Transactions.TransactionModifyHandler))
|
||||||
@@ -297,6 +320,15 @@ func startWebServer(c *cli.Context) error {
|
|||||||
apiV1Route.POST("/transaction/tags/move.json", bindApi(api.TransactionTags.TagMoveHandler))
|
apiV1Route.POST("/transaction/tags/move.json", bindApi(api.TransactionTags.TagMoveHandler))
|
||||||
apiV1Route.POST("/transaction/tags/delete.json", bindApi(api.TransactionTags.TagDeleteHandler))
|
apiV1Route.POST("/transaction/tags/delete.json", bindApi(api.TransactionTags.TagDeleteHandler))
|
||||||
|
|
||||||
|
// Transaction Templates
|
||||||
|
apiV1Route.GET("/transaction/templates/list.json", bindApi(api.TransactionTemplates.TemplateListHandler))
|
||||||
|
apiV1Route.GET("/transaction/templates/get.json", bindApi(api.TransactionTemplates.TemplateGetHandler))
|
||||||
|
apiV1Route.POST("/transaction/templates/add.json", bindApi(api.TransactionTemplates.TemplateCreateHandler))
|
||||||
|
apiV1Route.POST("/transaction/templates/modify.json", bindApi(api.TransactionTemplates.TemplateModifyHandler))
|
||||||
|
apiV1Route.POST("/transaction/templates/hide.json", bindApi(api.TransactionTemplates.TemplateHideHandler))
|
||||||
|
apiV1Route.POST("/transaction/templates/move.json", bindApi(api.TransactionTemplates.TemplateMoveHandler))
|
||||||
|
apiV1Route.POST("/transaction/templates/delete.json", bindApi(api.TransactionTemplates.TemplateDeleteHandler))
|
||||||
|
|
||||||
// Exchange Rates
|
// Exchange Rates
|
||||||
apiV1Route.GET("/exchange_rates/latest.json", bindApi(api.ExchangeRates.LatestExchangeRateHandler))
|
apiV1Route.GET("/exchange_rates/latest.json", bindApi(api.ExchangeRates.LatestExchangeRateHandler))
|
||||||
}
|
}
|
||||||
@@ -305,20 +337,20 @@ func startWebServer(c *cli.Context) error {
|
|||||||
listenAddr := fmt.Sprintf("%s:%d", config.HttpAddr, config.HttpPort)
|
listenAddr := fmt.Sprintf("%s:%d", config.HttpAddr, config.HttpPort)
|
||||||
|
|
||||||
if config.Protocol == settings.SCHEME_SOCKET {
|
if config.Protocol == settings.SCHEME_SOCKET {
|
||||||
log.BootInfof("[server.startWebServer] will run at socks:%s", config.UnixSocketPath)
|
log.BootInfof("[webserver.startWebServer] will run at socks:%s", config.UnixSocketPath)
|
||||||
err = router.RunUnix(config.UnixSocketPath)
|
err = router.RunUnix(config.UnixSocketPath)
|
||||||
} else if config.Protocol == settings.SCHEME_HTTP {
|
} else if config.Protocol == settings.SCHEME_HTTP {
|
||||||
log.BootInfof("[server.startWebServer] will run at http://%s", listenAddr)
|
log.BootInfof("[webserver.startWebServer] will run at http://%s", listenAddr)
|
||||||
err = router.Run(listenAddr)
|
err = router.Run(listenAddr)
|
||||||
} else if config.Protocol == settings.SCHEME_HTTPS {
|
} else if config.Protocol == settings.SCHEME_HTTPS {
|
||||||
log.BootInfof("[server.startWebServer] will run at https://%s", listenAddr)
|
log.BootInfof("[webserver.startWebServer] will run at https://%s", listenAddr)
|
||||||
err = router.RunTLS(listenAddr, config.CertFile, config.CertKeyFile)
|
err = router.RunTLS(listenAddr, config.CertFile, config.CertKeyFile)
|
||||||
} else {
|
} else {
|
||||||
err = errs.ErrInvalidProtocol
|
err = errs.ErrInvalidProtocol
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.BootErrorf("[server.startWebServer] cannot start, because %s", err)
|
log.BootErrorf("[webserver.startWebServer] cannot start, because %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -374,15 +406,41 @@ func bindCsv(fn core.DataHandlerFunc) gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func bindCachedPngImage(fn core.DataHandlerFunc, store persistence.CacheStore) gin.HandlerFunc {
|
func bindTsv(fn core.DataHandlerFunc) gin.HandlerFunc {
|
||||||
return cache.CachePage(store, time.Minute, func(ginCtx *gin.Context) {
|
return func(ginCtx *gin.Context) {
|
||||||
c := core.WrapContext(ginCtx)
|
c := core.WrapContext(ginCtx)
|
||||||
result, _, err := fn(c)
|
result, fileName, err := fn(c)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.PrintDataErrorResult(c, "text/text", err)
|
utils.PrintDataErrorResult(c, "text/text", err)
|
||||||
} else {
|
} else {
|
||||||
utils.PrintDataSuccessResult(c, "img/png", "", result)
|
utils.PrintDataSuccessResult(c, "text/tab-separated-values", fileName, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindImage(fn core.ImageHandlerFunc) gin.HandlerFunc {
|
||||||
|
return func(ginCtx *gin.Context) {
|
||||||
|
c := core.WrapContext(ginCtx)
|
||||||
|
result, contentType, err := fn(c)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
utils.PrintDataErrorResult(c, "text/text", err)
|
||||||
|
} else {
|
||||||
|
utils.PrintDataSuccessResult(c, contentType, "", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindCachedImage(fn core.ImageHandlerFunc, store persistence.CacheStore) gin.HandlerFunc {
|
||||||
|
return cache.CachePage(store, time.Minute, func(ginCtx *gin.Context) {
|
||||||
|
c := core.WrapContext(ginCtx)
|
||||||
|
result, contentType, err := fn(c)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
utils.PrintDataErrorResult(c, "text/text", err)
|
||||||
|
} else {
|
||||||
|
utils.PrintDataSuccessResult(c, contentType, "", result)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+126
-21
@@ -25,7 +25,7 @@ root_url = %(protocol)s://%(domain)s:%(http_port)s/
|
|||||||
cert_file =
|
cert_file =
|
||||||
cert_key_file =
|
cert_key_file =
|
||||||
|
|
||||||
# Unix socket path, for "socket" only
|
# Unix socket path, for "socket" protocol only
|
||||||
unix_socket =
|
unix_socket =
|
||||||
|
|
||||||
# Static file root path (relative or absolute path)
|
# Static file root path (relative or absolute path)
|
||||||
@@ -47,10 +47,10 @@ name = ezbookkeeping
|
|||||||
user = root
|
user = root
|
||||||
passwd =
|
passwd =
|
||||||
|
|
||||||
# For "postgres" only, Either "disable", "require" or "verify-full"
|
# For "postgres" database only, Either "disable", "require" or "verify-full"
|
||||||
ssl_mode = disable
|
ssl_mode = disable
|
||||||
|
|
||||||
# For "sqlite3" only, db file path (relative or absolute path)
|
# For "sqlite3" database only, database file path (relative or absolute path)
|
||||||
db_path = data/ezbookkeeping.db
|
db_path = data/ezbookkeeping.db
|
||||||
|
|
||||||
# Max idle connection number (0 - 65535, 0 means no idle connections are retained), default is 2
|
# Max idle connection number (0 - 65535, 0 means no idle connections are retained), default is 2
|
||||||
@@ -89,33 +89,88 @@ mode = console file
|
|||||||
# Either "debug", "info", "warn", "error", default is "info"
|
# Either "debug", "info", "warn", "error", default is "info"
|
||||||
level = info
|
level = info
|
||||||
|
|
||||||
# For "file" only, log file path (relative or absolute path)
|
# For "file" mode only, log file path (relative or absolute path)
|
||||||
log_path = log/ezbookkeeping.log
|
log_path = log/ezbookkeeping.log
|
||||||
|
|
||||||
|
# For "file" only, request log file path (relative or absolute path). Leave blank if you want to write request log in default log file
|
||||||
|
request_log_path =
|
||||||
|
|
||||||
|
# For "file" only, query log file path (relative or absolute path). Leave blank if you want to write query log in default log file
|
||||||
|
query_log_path =
|
||||||
|
|
||||||
|
# For "file" only, whether rotate the log files
|
||||||
|
log_file_rotate = false
|
||||||
|
|
||||||
|
# For "file" only, maximum size (1 - 4294967295 bytes) of the log file before it gets rotated
|
||||||
|
log_file_max_size = 104857600
|
||||||
|
|
||||||
|
# For "file" only, maximum number of days to retain old log files. Set to 0 to retain all logs
|
||||||
|
log_file_max_days = 7
|
||||||
|
|
||||||
|
[storage]
|
||||||
|
# Object storage type, supports "local_filesystem" and "minio" currently
|
||||||
|
type = local_filesystem
|
||||||
|
|
||||||
|
# For "local_filesystem" storage only, the storage root path (relative or absolute path)
|
||||||
|
local_filesystem_path = storage/
|
||||||
|
|
||||||
|
# For "minio" storage only, the minio connection configuration
|
||||||
|
minio_endpoint = 127.0.0.1:9000
|
||||||
|
minio_location =
|
||||||
|
minio_access_key_id =
|
||||||
|
minio_secret_access_key =
|
||||||
|
|
||||||
|
# For "minio" storage only, whether enable ssl for minio connection
|
||||||
|
minio_use_ssl = false
|
||||||
|
|
||||||
|
# For "minio" storage only, set to true to skip tls verification when connect minio
|
||||||
|
minio_skip_tls_verify = false
|
||||||
|
|
||||||
|
# For "minio" storage only, the minio bucket
|
||||||
|
minio_bucket = ezbookkeeping
|
||||||
|
|
||||||
|
# For "minio" storage only, the root path to store files in minio
|
||||||
|
minio_root_path = /
|
||||||
|
|
||||||
[uuid]
|
[uuid]
|
||||||
# Uuid generator type, supports "internal" currently
|
# Uuid generator type, supports "internal" currently
|
||||||
generator_type = internal
|
generator_type = internal
|
||||||
|
|
||||||
# For "internal" only, each server must have unique id (0 - 255)
|
# For "internal" uuid generator only, each server must have unique id (0 - 255)
|
||||||
server_id = 0
|
server_id = 0
|
||||||
|
|
||||||
|
[duplicate_checker]
|
||||||
|
# Duplicate checker type, supports "in_memory" currently
|
||||||
|
checker_type = in_memory
|
||||||
|
|
||||||
|
# For "in_memory" duplicate checker only, cleanup expired data interval seconds (1 - 4294967295), default is 60 (1 minutes)
|
||||||
|
cleanup_interval = 60
|
||||||
|
|
||||||
|
# The minimum interval seconds (0 - 4294967295) between duplicate submissions on the same page (exiting and re-entering the page is considered as a new session)
|
||||||
|
# Set to 0 to disable duplicate checker for new data submissions, default is 300 (5 minutes)
|
||||||
|
duplicate_submissions_interval = 300
|
||||||
|
|
||||||
[security]
|
[security]
|
||||||
# Used for signing, you must change it to keep your user data safe before you first run ezBookkeeping
|
# Used for signing, you must change it to keep your user data safe before you first run ezBookkeeping
|
||||||
secret_key =
|
secret_key =
|
||||||
|
|
||||||
# Set to true to enable two factor authorization
|
# Set to true to enable two-factor authorization
|
||||||
enable_two_factor = true
|
enable_two_factor = true
|
||||||
|
|
||||||
# Token expired seconds (0 - 4294967295), default is 2592000 (30 days)
|
# Token expired seconds (60 - 4294967295), default is 2592000 (30 days)
|
||||||
token_expired_time = 2592000
|
token_expired_time = 2592000
|
||||||
|
|
||||||
# Temporary token expired seconds (0 - 4294967295), default is 300 (5 minutes)
|
# Token minimum refresh interval (0 - 4294967295), the value should be less than token expired time
|
||||||
|
# Set to 0 to refresh the token every time when refreshing the front end, default is 86400 (1 day)
|
||||||
|
token_min_refresh_interval = 86400
|
||||||
|
|
||||||
|
# Temporary token expired seconds (60 - 4294967295), default is 300 (5 minutes)
|
||||||
temporary_token_expired_time = 300
|
temporary_token_expired_time = 300
|
||||||
|
|
||||||
# Email verify token expired seconds (0 - 4294967295), default is 3600 (60 minutes)
|
# Email verify token expired seconds (60 - 4294967295), default is 3600 (60 minutes)
|
||||||
email_verify_token_expired_time = 3600
|
email_verify_token_expired_time = 3600
|
||||||
|
|
||||||
# Password reset token expired seconds (0 - 4294967295), default is 3600 (60 minutes)
|
# Password reset token expired seconds (60 - 4294967295), default is 3600 (60 minutes)
|
||||||
password_reset_token_expired_time = 3600
|
password_reset_token_expired_time = 3600
|
||||||
|
|
||||||
# Add X-Request-Id header to response to track user request or error, default is true
|
# Add X-Request-Id header to response to track user request or error, default is true
|
||||||
@@ -138,14 +193,36 @@ enable_forget_password = true
|
|||||||
forget_password_require_email_verify = false
|
forget_password_require_email_verify = false
|
||||||
|
|
||||||
# User avatar provider, supports the following types:
|
# User avatar provider, supports the following types:
|
||||||
|
# "internal": Use the internal object storage to store user avatar (refer to "storage" settings), supports updating avatar by user self
|
||||||
# "gravatar": https://gravatar.com
|
# "gravatar": https://gravatar.com
|
||||||
# Leave blank if you want to disable user avatar
|
# Leave blank if you want to disable user avatar
|
||||||
avatar_provider =
|
avatar_provider = internal
|
||||||
|
|
||||||
[data]
|
[data]
|
||||||
# Set to true to allow users to export their data
|
# Set to true to allow users to export their data
|
||||||
enable_export = true
|
enable_export = true
|
||||||
|
|
||||||
|
[notification]
|
||||||
|
# Set to true to display custom notification in home page every time users register
|
||||||
|
enable_notification_after_register = false
|
||||||
|
|
||||||
|
# The notification content displayed each time users register, it supports multi-language configuration
|
||||||
|
# Add an underscore and a language tag after the setting key to configure the notification content in that language, the same below
|
||||||
|
# For example, after_login_notification_content_zh_hans means the notification content in Simplified Chinese
|
||||||
|
after_register_notification_content =
|
||||||
|
|
||||||
|
# Set to true to display custom notification in home page every time users login
|
||||||
|
enable_notification_after_login = false
|
||||||
|
|
||||||
|
# The notification content displayed each time users log in, it supports multi-language configuration
|
||||||
|
after_login_notification_content =
|
||||||
|
|
||||||
|
# Set to true to display custom notification in home page every time users open the app
|
||||||
|
enable_notification_after_open = false
|
||||||
|
|
||||||
|
# The notification content displayed each time users open the app, it supports multi-language configuration
|
||||||
|
after_open_notification_content =
|
||||||
|
|
||||||
[map]
|
[map]
|
||||||
# Map provider, supports the following types:
|
# Map provider, supports the following types:
|
||||||
# "openstreetmap": https://www.openstreetmap.org
|
# "openstreetmap": https://www.openstreetmap.org
|
||||||
@@ -153,41 +230,65 @@ enable_export = true
|
|||||||
# "opentopomap": https://opentopomap.org
|
# "opentopomap": https://opentopomap.org
|
||||||
# "opnvkarte": https://publictransportmap.org
|
# "opnvkarte": https://publictransportmap.org
|
||||||
# "cyclosm": https://www.cyclosm.org
|
# "cyclosm": https://www.cyclosm.org
|
||||||
|
# "cartodb": https://carto.com/basemaps
|
||||||
# "tomtom": https://www.tomtom.com
|
# "tomtom": https://www.tomtom.com
|
||||||
|
# "tianditu": https://www.tianditu.gov.cn
|
||||||
# "googlemap": https://map.google.com
|
# "googlemap": https://map.google.com
|
||||||
# "baidumap": https://map.baidu.com
|
# "baidumap": https://map.baidu.com
|
||||||
# "amap": https://amap.com
|
# "amap": https://amap.com
|
||||||
|
# "custom": custom map tile server url
|
||||||
# Leave blank if you want to disable map
|
# Leave blank if you want to disable map
|
||||||
map_provider = openstreetmap
|
map_provider = openstreetmap
|
||||||
|
|
||||||
# Set to true to use the ezbookkeeping server to proxy map data requests, for "openstreetmap", "openstreetmap_humanitarian", "opentopomap", "opnvkarte", "cyclosm" or "tomtom"
|
# Set to true to use the ezbookkeeping server to forward map data requests, for "openstreetmap", "openstreetmap_humanitarian", "opentopomap", "opnvkarte", "cyclosm", "cartodb", "tomtom", "tianditu" or "custom"
|
||||||
map_data_fetch_proxy = false
|
map_data_fetch_proxy = false
|
||||||
|
|
||||||
# For "tomtom" only, TomTom map API key, please visit https://developer.tomtom.com/how-to-get-tomtom-api-key
|
# Proxy for ezbookkeeping server requesting original map data when map_data_fetch_proxy is set to true, supports "system" (use system proxy), "none" (do not use proxy), or proxy URL which starts with "http://", "https://" or "socks5://", default is "system"
|
||||||
|
proxy = system
|
||||||
|
|
||||||
|
# For "tomtom" map provider only, TomTom map API key, please visit https://developer.tomtom.com/how-to-get-tomtom-api-key for more information
|
||||||
tomtom_map_api_key =
|
tomtom_map_api_key =
|
||||||
|
|
||||||
# For "googlemap" only, Google map JavaScript API key, please visit https://developers.google.com/maps/get-started for more information
|
# For "tianditu" map provider only, TianDiTu map application key, please visit https://console.tianditu.gov.cn/api/register for more information
|
||||||
|
tianditu_map_app_key =
|
||||||
|
|
||||||
|
# For "googlemap" map provider only, Google map JavaScript API key, please visit https://developers.google.com/maps/get-started for more information
|
||||||
google_map_api_key =
|
google_map_api_key =
|
||||||
|
|
||||||
# For "baidumap" only, Baidu map JavaScript API application key, please visit https://lbsyun.baidu.com/index.php?title=jspopular3.0/guide/getkey for more information
|
# For "baidumap" map provider only, Baidu map JavaScript API application key, please visit https://lbsyun.baidu.com/index.php?title=jspopular3.0/guide/getkey for more information
|
||||||
baidu_map_ak =
|
baidu_map_ak =
|
||||||
|
|
||||||
# For "amap" only, Amap JavaScript API application key, please visit https://lbs.amap.com/api/javascript-api/guide/abc/prepare for more information
|
# For "amap" map provider only, Amap JavaScript API application key, please visit https://lbs.amap.com/api/javascript-api/guide/abc/prepare for more information
|
||||||
amap_application_key =
|
amap_application_key =
|
||||||
|
|
||||||
# For "amap" only, Amap JavaScript API security verification method, supports the following methods:
|
# For "amap" map provider only, Amap JavaScript API security verification method, supports the following methods:
|
||||||
# "internal_proxy": use the internal proxy to request amap api with amap application secret (default)
|
# "internal_proxy": use the internal proxy to request amap api with amap application secret (default)
|
||||||
# "external_proxy": use an external proxy to request amap api (amap application secret should be set by external proxy)
|
# "external_proxy": use an external proxy to request amap api (amap application secret should be set by external proxy)
|
||||||
# "plain_text": append amap application secret to frontend request directly (insecurity for public network)
|
# "plain_text": append amap application secret to frontend request directly (insecurity for public network)
|
||||||
# Please visit https://developer.amap.com/api/jsapi-v2/guide/abc/load for more information
|
# Please visit https://developer.amap.com/api/jsapi-v2/guide/abc/load for more information
|
||||||
amap_security_verification_method = plain_text
|
amap_security_verification_method = plain_text
|
||||||
|
|
||||||
# For "amap" only, Amap JavaScript API application secret, this setting must be provided when "amap_security_verification_method" is set to "internal_proxy" or "plain_text", please visit https://lbs.amap.com/api/javascript-api/guide/abc/prepare for more information
|
# For "amap" map provider only, Amap JavaScript API application secret, this setting must be provided when "amap_security_verification_method" is set to "internal_proxy" or "plain_text", please visit https://lbs.amap.com/api/javascript-api/guide/abc/prepare for more information
|
||||||
amap_application_secret =
|
amap_application_secret =
|
||||||
|
|
||||||
# For "amap" only, Amap JavaScript API external proxy url, this setting must be provided when "amap_security_verification_method" is set to "external_proxy"
|
# For "amap" map provider only, Amap JavaScript API external proxy url, this setting must be provided when "amap_security_verification_method" is set to "external_proxy"
|
||||||
amap_api_external_proxy_url =
|
amap_api_external_proxy_url =
|
||||||
|
|
||||||
|
# For "custom" map provider only, the tile layer url of custom map tile server, supports {x}, {y} (coordinates) and {z} (zoom level) placeholders, like "https://tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||||
|
custom_map_tile_server_url =
|
||||||
|
|
||||||
|
# For "custom" map provider only, the optional annotation layer url of custom map tile server, supports {x}, {y} (coordinates) and {z} (zoom level) placeholders
|
||||||
|
custom_map_tile_server_annotation_url =
|
||||||
|
|
||||||
|
# For "custom" map provider only, the min zoom level (0 - 255) for custom map tile server, default is 1
|
||||||
|
custom_map_tile_server_min_zoom_level = 1
|
||||||
|
|
||||||
|
# For "custom" map provider only, the max zoom level (0 - 255) for custom map tile server, default is 18
|
||||||
|
custom_map_tile_server_max_zoom_level = 18
|
||||||
|
|
||||||
|
# For "custom" map provider only, the default zoom level (0 - 255) for custom map tile server, default is 14
|
||||||
|
custom_map_tile_server_default_zoom_level = 14
|
||||||
|
|
||||||
[exchange_rates]
|
[exchange_rates]
|
||||||
# Exchange rates data source, supports the following types:
|
# Exchange rates data source, supports the following types:
|
||||||
# "euro_central_bank"
|
# "euro_central_bank"
|
||||||
@@ -198,8 +299,12 @@ amap_api_external_proxy_url =
|
|||||||
# "monetary_authority_of_singapore"
|
# "monetary_authority_of_singapore"
|
||||||
data_source = euro_central_bank
|
data_source = euro_central_bank
|
||||||
|
|
||||||
# Requesting exchange rates data timeout (0 - 4294967295 milliseconds), default is 10000 (10 seconds)
|
# Requesting exchange rates data timeout (0 - 4294967295 milliseconds)
|
||||||
|
# Set to 0 to disable timeout for requesting exchange rates data, default is 10000 (10 seconds)
|
||||||
request_timeout = 10000
|
request_timeout = 10000
|
||||||
|
|
||||||
# Set to true skip tls verification when request exchange rates data
|
# Proxy for ezbookkeeping server requesting exchange rates data, supports "system" (use system proxy), "none" (do not use proxy), or proxy URL which starts with "http://", "https://" or "socks5://", default is "system"
|
||||||
|
proxy = system
|
||||||
|
|
||||||
|
# Set to true to skip tls verification when request exchange rates data
|
||||||
skip_tls_verify = false
|
skip_tls_verify = false
|
||||||
|
|||||||
@@ -1,65 +1,78 @@
|
|||||||
module github.com/mayswind/ezbookkeeping
|
module github.com/mayswind/ezbookkeeping
|
||||||
|
|
||||||
go 1.20
|
go 1.21
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/boombuler/barcode v1.0.1
|
github.com/boombuler/barcode v1.0.2
|
||||||
github.com/gin-contrib/cache v1.2.0
|
github.com/gin-contrib/cache v1.3.0
|
||||||
github.com/gin-contrib/gzip v0.0.6
|
github.com/gin-contrib/gzip v1.0.1
|
||||||
github.com/gin-gonic/gin v1.9.1
|
github.com/gin-gonic/gin v1.10.0
|
||||||
github.com/go-playground/validator/v10 v10.15.1
|
github.com/go-playground/validator/v10 v10.22.0
|
||||||
github.com/go-sql-driver/mysql v1.7.1
|
github.com/go-sql-driver/mysql v1.8.1
|
||||||
github.com/golang-jwt/jwt/v5 v5.0.0
|
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||||
github.com/lib/pq v1.10.9
|
github.com/lib/pq v1.10.9
|
||||||
github.com/mattn/go-sqlite3 v1.14.16
|
github.com/mattn/go-sqlite3 v1.14.22
|
||||||
|
github.com/minio/minio-go/v7 v7.0.74
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/pquerna/otp v1.4.0
|
github.com/pquerna/otp v1.4.0
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.9.0
|
||||||
github.com/urfave/cli/v2 v2.25.7
|
github.com/urfave/cli/v2 v2.27.1
|
||||||
github.com/wk8/go-ordered-map/v2 v2.1.8
|
github.com/wk8/go-ordered-map/v2 v2.1.8
|
||||||
golang.org/x/crypto v0.12.0
|
golang.org/x/crypto v0.25.0
|
||||||
gopkg.in/ini.v1 v1.67.0
|
gopkg.in/ini.v1 v1.67.0
|
||||||
gopkg.in/mail.v2 v2.3.1
|
gopkg.in/mail.v2 v2.3.1
|
||||||
xorm.io/xorm v1.3.2
|
xorm.io/builder v0.3.13
|
||||||
|
xorm.io/xorm v1.3.9
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d // indirect
|
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect
|
||||||
github.com/buger/jsonparser v1.1.1 // indirect
|
github.com/buger/jsonparser v1.1.1 // indirect
|
||||||
github.com/bytedance/sonic v1.9.1 // indirect
|
github.com/bytedance/sonic v1.11.6 // indirect
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||||
|
github.com/chenzhuoyu/iasm v0.9.1 // indirect
|
||||||
|
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||||
|
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
|
github.com/go-ini/ini v1.67.0 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/goccy/go-json v0.10.2 // indirect
|
github.com/goccy/go-json v0.10.3 // indirect
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/gomodule/redigo v1.8.9 // indirect
|
github.com/gomodule/redigo v1.8.9 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
github.com/klauspost/compress v1.17.9 // indirect
|
||||||
github.com/leodido/go-urn v1.2.4 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/memcachier/mc/v3 v3.0.3 // indirect
|
github.com/memcachier/mc/v3 v3.0.3 // indirect
|
||||||
|
github.com/minio/md5-simd v1.1.2 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62 // indirect
|
github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62 // indirect
|
||||||
|
github.com/rs/xid v1.5.0 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/syndtr/goleveldb v1.0.0 // indirect
|
github.com/syndtr/goleveldb v1.0.0 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||||
golang.org/x/arch v0.3.0 // indirect
|
golang.org/x/arch v0.8.0 // indirect
|
||||||
golang.org/x/net v0.10.0 // indirect
|
golang.org/x/net v0.26.0 // indirect
|
||||||
golang.org/x/sys v0.11.0 // indirect
|
golang.org/x/sys v0.22.0 // indirect
|
||||||
golang.org/x/text v0.12.0 // indirect
|
golang.org/x/text v0.16.0 // indirect
|
||||||
google.golang.org/protobuf v1.30.0 // indirect
|
google.golang.org/protobuf v1.34.1 // indirect
|
||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
xorm.io/builder v0.3.12 // indirect
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,778 +1,182 @@
|
|||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
|
|
||||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
|
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/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/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||||
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/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||||
github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
|
github.com/boombuler/barcode v1.0.2 h1:79yrbttoZrLGkL/oOI8hBrUKucwOL0oOjUgEguGMcJ4=
|
||||||
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
github.com/boombuler/barcode v1.0.2/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d h1:pVrfxiGfwelyab6n21ZBkbkmbevaf+WvMIiR7sr97hw=
|
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous=
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c=
|
||||||
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
|
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
|
||||||
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||||
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
||||||
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
||||||
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
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-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/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/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
||||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
||||||
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
|
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||||
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
|
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||||
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||||
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 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
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/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/gin-contrib/cache v1.3.0 h1:wEEw38uvb4rTraQJVpd9ex4ZotXNlc0fSaSUsuPXS/w=
|
||||||
github.com/gin-contrib/cache v1.2.0 h1:WA+AJR4kmHDTaLLShCHo/IeWVmmGRZ3Lsr3JQ46tFlE=
|
github.com/gin-contrib/cache v1.3.0/go.mod h1:EA63LrWGI5vwSI95TS5fgBrtxZ1tM2NKx+NrEeyEDcU=
|
||||||
github.com/gin-contrib/cache v1.2.0/go.mod h1:2KkFL8PSnPF3Tt5E2Jpc3HWuBAUKqGZnClCFMm0tXQI=
|
github.com/gin-contrib/gzip v1.0.1 h1:HQ8ENHODeLY7a4g1Au/46Z92bdGFl74OhxcZble9WJE=
|
||||||
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
|
github.com/gin-contrib/gzip v1.0.1/go.mod h1:njt428fdUNRvjuJf16tZMYZ2Yl+WQB53X5wmhDwXvC4=
|
||||||
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 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
|
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||||
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/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
|
||||||
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 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
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 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
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.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
|
||||||
github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k=
|
github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||||
github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||||
github.com/go-playground/validator/v10 v10.15.1 h1:BSe8uhN+xQ4r5guV/ywQI4gO59C2raYcGffYWZEjZzM=
|
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||||
github.com/go-playground/validator/v10 v10.15.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
||||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||||
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
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.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
|
||||||
github.com/goccy/go-json v0.10.2/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.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.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.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 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws=
|
github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws=
|
||||||
github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE=
|
github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE=
|
||||||
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 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
|
||||||
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/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
|
||||||
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/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 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
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/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
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 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
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/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
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/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
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 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
|
||||||
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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|
||||||
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 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
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/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
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-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
|
||||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
|
||||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
|
||||||
github.com/mattn/go-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/memcachier/mc/v3 v3.0.3 h1:qii+lDiPKi36O4Xg+HVKwHu6Oq+Gt17b+uEiA0Drwv4=
|
github.com/memcachier/mc/v3 v3.0.3 h1:qii+lDiPKi36O4Xg+HVKwHu6Oq+Gt17b+uEiA0Drwv4=
|
||||||
github.com/memcachier/mc/v3 v3.0.3/go.mod h1:GzjocBahcXPxt2cmqzknrgqCOmMxiSzhVKPOe90Tpug=
|
github.com/memcachier/mc/v3 v3.0.3/go.mod h1:GzjocBahcXPxt2cmqzknrgqCOmMxiSzhVKPOe90Tpug=
|
||||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/minio/minio-go/v7 v7.0.74 h1:fTo/XlPBTSpo3BAMshlwKL5RspXRv9us5UeHEGYCFe0=
|
||||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
github.com/minio/minio-go/v7 v7.0.74/go.mod h1:qydcVzV8Hqtj1VtEocfxbmVFa2siu6HGa+LDEPogjD8=
|
||||||
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/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
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 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
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.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/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/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/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||||
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
|
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||||
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
|
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||||
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||||
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.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
|
||||||
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
|
||||||
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
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 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
|
||||||
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
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 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
|
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
|
||||||
github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62 h1:pyecQtsPmlkCsMkYhT5iZ+sUXuwee+OvfuJjinEA3ko=
|
github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62 h1:pyecQtsPmlkCsMkYhT5iZ+sUXuwee+OvfuJjinEA3ko=
|
||||||
github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62/go.mod h1:65XQgovT59RWatovFwnwocoUxiI/eENTnOY5GK3STuY=
|
github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62/go.mod h1:65XQgovT59RWatovFwnwocoUxiI/eENTnOY5GK3STuY=
|
||||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
|
||||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
|
||||||
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/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
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/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.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.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
|
||||||
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.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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
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/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.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.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.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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
|
||||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
||||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
||||||
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 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
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.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho=
|
||||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
|
||||||
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.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
|
|
||||||
github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
|
|
||||||
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
|
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
|
||||||
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
|
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
|
||||||
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 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
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/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
||||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||||
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-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/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
|
||||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
|
||||||
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/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
|
||||||
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-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.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||||
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-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-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-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-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-20220704084225-05e143d24a9e/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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
|
||||||
golang.org/x/sys v0.11.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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
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/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
|
||||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
|
||||||
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=
|
|
||||||
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/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
|
||||||
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 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
|
||||||
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.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
|
||||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
|
||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
|
||||||
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 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
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 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk=
|
gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk=
|
||||||
gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw=
|
gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw=
|
||||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
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.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.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
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=
|
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||||
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 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU=
|
|
||||||
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 h1:rMZhRcWrba0y3nVmdiQ7kxAgOOSq2m2f2VzjHLgEs6U=
|
|
||||||
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 h1:wudcnJyjLj1aQQCXF3IM9Gz2X6UNjw+afIghzdtn0v8=
|
|
||||||
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 h1:PzIzOqtlzMDDcCzJ5cUP6h/Ku6Fa9iyflP2ccTY64aE=
|
|
||||||
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 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8=
|
|
||||||
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 h1:XRch8trV7GgvTec2i7jc33YlUI0RKVDBvZ5eZ5m8y14=
|
|
||||||
modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM=
|
|
||||||
modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A=
|
|
||||||
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
|
||||||
modernc.org/sqlite v1.14.2 h1:ohsW2+e+Qe2To1W6GNezzKGwjXwSax6R+CrhRxVaFbE=
|
|
||||||
modernc.org/sqlite v1.14.2/go.mod h1:yqfn85u8wVOE6ub5UT8VI9JjhrwBUUCNyTACN0h6Sx8=
|
|
||||||
modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs=
|
|
||||||
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 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
|
|
||||||
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=
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
xorm.io/builder v0.3.13 h1:a3jmiVVL19psGeXx8GIurTp7p0IIgqeDmwhcR6BAOAo=
|
||||||
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
|
xorm.io/builder v0.3.13/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
|
||||||
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
|
xorm.io/xorm v1.3.9 h1:TUovzS0ko+IQ1XnNLfs5dqK1cJl1H5uHpWbWqAQ04nU=
|
||||||
xorm.io/builder v0.3.12 h1:ASZYX7fQmy+o8UJdhlLHSW57JDOkM8DNhcAF5d0LiJM=
|
xorm.io/xorm v1.3.9/go.mod h1:LsCCffeeYp63ssk0pKumP6l96WZcHix7ChpurcLNuMw=
|
||||||
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=
|
|
||||||
|
|||||||
Generated
+2643
-2850
File diff suppressed because it is too large
Load Diff
+26
-27
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ezbookkeeping",
|
"name": "ezbookkeeping",
|
||||||
"version": "0.4.0",
|
"version": "0.5.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -18,47 +18,46 @@
|
|||||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore"
|
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mdi/js": "^7.2.96",
|
"@mdi/js": "^7.4.47",
|
||||||
"@vuepic/vue-datepicker": "^5.4.0",
|
"@vuepic/vue-datepicker": "^8.8.1",
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.7.2",
|
||||||
"cbor-js": "^0.1.0",
|
"cbor-js": "^0.1.0",
|
||||||
"clipboard": "^2.0.11",
|
"clipboard": "^2.0.11",
|
||||||
"crypto-js": "^4.1.1",
|
"crypto-js": "^4.2.0",
|
||||||
"dom7": "^4.0.6",
|
"dom7": "^4.0.6",
|
||||||
"echarts": "^5.4.3",
|
"echarts": "^5.5.1",
|
||||||
"framework7": "^8.3.0",
|
"framework7": "^8.3.3",
|
||||||
"framework7-icons": "^5.0.5",
|
"framework7-icons": "^5.0.5",
|
||||||
"framework7-vue": "^8.3.0",
|
"framework7-vue": "^8.3.3",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
"leaflet": "^1.9.4",
|
"leaflet": "^1.9.4",
|
||||||
"line-awesome": "^1.3.0",
|
"line-awesome": "^1.3.0",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.30.1",
|
||||||
"moment-timezone": "^0.5.43",
|
"moment-timezone": "^0.5.45",
|
||||||
"pinia": "^2.1.6",
|
"pinia": "^2.1.7",
|
||||||
"register-service-worker": "^1.7.2",
|
"register-service-worker": "^1.7.2",
|
||||||
"skeleton-elements": "^4.0.1",
|
"skeleton-elements": "^4.0.1",
|
||||||
"swiper": "^10.2.0",
|
"swiper": "^10.2.0",
|
||||||
"ua-parser-js": "^1.0.35",
|
"ua-parser-js": "^1.0.38",
|
||||||
"vue": "^3.3.4",
|
"vue": "^3.4.31",
|
||||||
"vue-echarts": "^6.6.1",
|
"vue-echarts": "^6.7.3",
|
||||||
"vue-i18n": "^9.2.2",
|
"vue-i18n": "^9.13.1",
|
||||||
"vue-router": "^4.2.4",
|
"vue-router": "^4.4.0",
|
||||||
"vue3-perfect-scrollbar": "^1.6.1",
|
"vue3-perfect-scrollbar": "^2.0.0",
|
||||||
"vuedraggable": "^4.1.0",
|
"vuedraggable": "^4.1.0",
|
||||||
"vuetify": "^3.3.16"
|
"vuetify": "^3.6.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^4.3.1",
|
"@vitejs/plugin-vue": "^5.0.5",
|
||||||
"@vue/compiler-sfc": "^3.3.4",
|
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"eslint": "^8.47.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-plugin-vue": "^9.17.0",
|
"eslint-plugin-vue": "^9.27.0",
|
||||||
"git-rev-sync": "^3.0.2",
|
"git-rev-sync": "^3.0.2",
|
||||||
"postcss-preset-env": "^9.1.1",
|
"postcss-preset-env": "^9.5.16",
|
||||||
"sass": "^1.66.1",
|
"sass": "^1.77.6",
|
||||||
"vite": "^4.4.9",
|
"vite": "^5.3.3",
|
||||||
"vite-plugin-pwa": "^0.16.4",
|
"vite-plugin-pwa": "^0.20.0",
|
||||||
"vite-plugin-vuetify": "^1.0.2"
|
"vite-plugin-vuetify": "^2.0.3"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"> 1%",
|
"> 1%",
|
||||||
|
|||||||
+55
-5
@@ -4,10 +4,13 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/duplicatechecker"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/services"
|
"github.com/mayswind/ezbookkeeping/pkg/services"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/validators"
|
"github.com/mayswind/ezbookkeeping/pkg/validators"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -143,9 +146,14 @@ func (a *AccountsApi) AccountCreateHandler(c *core.Context) (any, *errs.Error) {
|
|||||||
return nil, errs.ErrClientTimezoneOffsetInvalid
|
return nil, errs.ErrClientTimezoneOffsetInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if accountCreateReq.Category < models.ACCOUNT_CATEGORY_CASH || accountCreateReq.Category > models.ACCOUNT_CATEGORY_INVESTMENT {
|
||||||
|
log.WarnfWithRequestId(c, "[accounts.AccountCreateHandler] account category invalid, category is %d", accountCreateReq.Category)
|
||||||
|
return nil, errs.ErrAccountCategoryInvalid
|
||||||
|
}
|
||||||
|
|
||||||
if accountCreateReq.Type == models.ACCOUNT_TYPE_SINGLE_ACCOUNT {
|
if accountCreateReq.Type == models.ACCOUNT_TYPE_SINGLE_ACCOUNT {
|
||||||
if len(accountCreateReq.SubAccounts) > 0 {
|
if len(accountCreateReq.SubAccounts) > 0 {
|
||||||
log.WarnfWithRequestId(c, "[accounts.AccountCreateHandler] account cannot have any sub accounts")
|
log.WarnfWithRequestId(c, "[accounts.AccountCreateHandler] account cannot have any sub-accounts")
|
||||||
return nil, errs.ErrAccountCannotHaveSubAccounts
|
return nil, errs.ErrAccountCannotHaveSubAccounts
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +163,7 @@ func (a *AccountsApi) AccountCreateHandler(c *core.Context) (any, *errs.Error) {
|
|||||||
}
|
}
|
||||||
} else if accountCreateReq.Type == models.ACCOUNT_TYPE_MULTI_SUB_ACCOUNTS {
|
} else if accountCreateReq.Type == models.ACCOUNT_TYPE_MULTI_SUB_ACCOUNTS {
|
||||||
if len(accountCreateReq.SubAccounts) < 1 {
|
if len(accountCreateReq.SubAccounts) < 1 {
|
||||||
log.WarnfWithRequestId(c, "[accounts.AccountCreateHandler] account does not have any sub accounts")
|
log.WarnfWithRequestId(c, "[accounts.AccountCreateHandler] account does not have any sub-accounts")
|
||||||
return nil, errs.ErrAccountHaveNoSubAccount
|
return nil, errs.ErrAccountHaveNoSubAccount
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,17 +181,17 @@ func (a *AccountsApi) AccountCreateHandler(c *core.Context) (any, *errs.Error) {
|
|||||||
subAccount := accountCreateReq.SubAccounts[i]
|
subAccount := accountCreateReq.SubAccounts[i]
|
||||||
|
|
||||||
if subAccount.Category != accountCreateReq.Category {
|
if subAccount.Category != accountCreateReq.Category {
|
||||||
log.WarnfWithRequestId(c, "[accounts.AccountCreateHandler] category of sub account not equals to parent")
|
log.WarnfWithRequestId(c, "[accounts.AccountCreateHandler] category of sub-account not equals to parent")
|
||||||
return nil, errs.ErrSubAccountCategoryNotEqualsToParent
|
return nil, errs.ErrSubAccountCategoryNotEqualsToParent
|
||||||
}
|
}
|
||||||
|
|
||||||
if subAccount.Type != models.ACCOUNT_TYPE_SINGLE_ACCOUNT {
|
if subAccount.Type != models.ACCOUNT_TYPE_SINGLE_ACCOUNT {
|
||||||
log.WarnfWithRequestId(c, "[accounts.AccountCreateHandler] sub account type invalid")
|
log.WarnfWithRequestId(c, "[accounts.AccountCreateHandler] sub-account type invalid")
|
||||||
return nil, errs.ErrSubAccountTypeInvalid
|
return nil, errs.ErrSubAccountTypeInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
if subAccount.Currency == validators.ParentAccountCurrencyPlaceholder {
|
if subAccount.Currency == validators.ParentAccountCurrencyPlaceholder {
|
||||||
log.WarnfWithRequestId(c, "[accounts.AccountCreateHandler] sub account cannot set currency placeholder")
|
log.WarnfWithRequestId(c, "[accounts.AccountCreateHandler] sub-account cannot set currency placeholder")
|
||||||
return nil, errs.ErrAccountCurrencyInvalid
|
return nil, errs.ErrAccountCurrencyInvalid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -203,6 +211,42 @@ func (a *AccountsApi) AccountCreateHandler(c *core.Context) (any, *errs.Error) {
|
|||||||
mainAccount := a.createNewAccountModel(uid, &accountCreateReq, maxOrderId+1)
|
mainAccount := a.createNewAccountModel(uid, &accountCreateReq, maxOrderId+1)
|
||||||
childrenAccounts := a.createSubAccountModels(uid, &accountCreateReq)
|
childrenAccounts := a.createSubAccountModels(uid, &accountCreateReq)
|
||||||
|
|
||||||
|
if settings.Container.Current.EnableDuplicateSubmissionsCheck && accountCreateReq.ClientSessionId != "" {
|
||||||
|
found, remark := duplicatechecker.Container.Get(duplicatechecker.DUPLICATE_CHECKER_TYPE_NEW_ACCOUNT, uid, accountCreateReq.ClientSessionId)
|
||||||
|
|
||||||
|
if found {
|
||||||
|
log.InfofWithRequestId(c, "[accounts.AccountCreateHandler] another account \"id:%s\" has been created for user \"uid:%d\"", remark, uid)
|
||||||
|
accountId, err := utils.StringToInt64(remark)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
accountAndSubAccounts, err := a.accounts.GetAccountAndSubAccountsByAccountId(c, uid, accountId)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[accounts.AccountCreateHandler] failed to get existed account \"id:%d\" for user \"uid:%d\", because %s", accountId, uid, err.Error())
|
||||||
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
accountMap := a.accounts.GetAccountMapByList(accountAndSubAccounts)
|
||||||
|
mainAccount, exists := accountMap[accountId]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return nil, errs.ErrOperationFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
accountInfoResp := mainAccount.ToAccountInfoResponse()
|
||||||
|
|
||||||
|
for i := 0; i < len(accountAndSubAccounts); i++ {
|
||||||
|
if accountAndSubAccounts[i].ParentAccountId == mainAccount.AccountId {
|
||||||
|
subAccountResp := accountAndSubAccounts[i].ToAccountInfoResponse()
|
||||||
|
accountInfoResp.SubAccounts = append(accountInfoResp.SubAccounts, subAccountResp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return accountInfoResp, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err = a.accounts.CreateAccounts(c, mainAccount, childrenAccounts, utcOffset)
|
err = a.accounts.CreateAccounts(c, mainAccount, childrenAccounts, utcOffset)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -212,6 +256,7 @@ func (a *AccountsApi) AccountCreateHandler(c *core.Context) (any, *errs.Error) {
|
|||||||
|
|
||||||
log.InfofWithRequestId(c, "[accounts.AccountCreateHandler] user \"uid:%d\" has created a new account \"id:%d\" successfully", uid, mainAccount.AccountId)
|
log.InfofWithRequestId(c, "[accounts.AccountCreateHandler] user \"uid:%d\" has created a new account \"id:%d\" successfully", uid, mainAccount.AccountId)
|
||||||
|
|
||||||
|
duplicatechecker.Container.Set(duplicatechecker.DUPLICATE_CHECKER_TYPE_NEW_ACCOUNT, uid, accountCreateReq.ClientSessionId, utils.Int64ToString(mainAccount.AccountId))
|
||||||
accountInfoResp := mainAccount.ToAccountInfoResponse()
|
accountInfoResp := mainAccount.ToAccountInfoResponse()
|
||||||
|
|
||||||
if len(childrenAccounts) > 0 {
|
if len(childrenAccounts) > 0 {
|
||||||
@@ -235,6 +280,11 @@ func (a *AccountsApi) AccountModifyHandler(c *core.Context) (any, *errs.Error) {
|
|||||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if accountModifyReq.Category < models.ACCOUNT_CATEGORY_CASH || accountModifyReq.Category > models.ACCOUNT_CATEGORY_INVESTMENT {
|
||||||
|
log.WarnfWithRequestId(c, "[accounts.AccountModifyHandler] account category invalid, category is %d", accountModifyReq.Category)
|
||||||
|
return nil, errs.ErrAccountCategoryInvalid
|
||||||
|
}
|
||||||
|
|
||||||
uid := c.GetCurrentUid()
|
uid := c.GetCurrentUid()
|
||||||
accountAndSubAccounts, err := a.accounts.GetAccountAndSubAccountsByAccountId(c, uid, accountModifyReq.Id)
|
accountAndSubAccounts, err := a.accounts.GetAccountAndSubAccountsByAccountId(c, uid, accountModifyReq.Id)
|
||||||
|
|
||||||
|
|||||||
+14
-13
@@ -77,7 +77,7 @@ func (a *AuthorizationsApi) AuthorizeHandler(c *core.Context) (any, *errs.Error)
|
|||||||
twoFactorEnable, err = a.twoFactorAuthorizations.ExistsTwoFactorSetting(c, user.Uid)
|
twoFactorEnable, err = a.twoFactorAuthorizations.ExistsTwoFactorSetting(c, user.Uid)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorfWithRequestId(c, "[authorizations.AuthorizeHandler] failed to check two factor setting for user \"uid:%d\", because %s", user.Uid, err.Error())
|
log.ErrorfWithRequestId(c, "[authorizations.AuthorizeHandler] failed to check two-factor setting for user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||||
return nil, errs.Or(err, errs.ErrSystemError)
|
return nil, errs.Or(err, errs.ErrSystemError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -104,7 +104,7 @@ func (a *AuthorizationsApi) AuthorizeHandler(c *core.Context) (any, *errs.Error)
|
|||||||
|
|
||||||
log.InfofWithRequestId(c, "[authorizations.AuthorizeHandler] user \"uid:%d\" has logined, token type is %d, token will be expired at %d", user.Uid, claims.Type, claims.ExpiresAt)
|
log.InfofWithRequestId(c, "[authorizations.AuthorizeHandler] user \"uid:%d\" has logined, token type is %d, token will be expired at %d", user.Uid, claims.Type, claims.ExpiresAt)
|
||||||
|
|
||||||
authResp := a.getAuthResponse(token, twoFactorEnable, user)
|
authResp := a.getAuthResponse(c, token, twoFactorEnable, user)
|
||||||
return authResp, nil
|
return authResp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@ func (a *AuthorizationsApi) TwoFactorAuthorizeHandler(c *core.Context) (any, *er
|
|||||||
twoFactorSetting, err := a.twoFactorAuthorizations.GetUserTwoFactorSettingByUid(c, uid)
|
twoFactorSetting, err := a.twoFactorAuthorizations.GetUserTwoFactorSettingByUid(c, uid)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorfWithRequestId(c, "[authorizations.TwoFactorAuthorizeHandler] failed to get two factor setting for user \"uid:%d\", because %s", uid, err.Error())
|
log.ErrorfWithRequestId(c, "[authorizations.TwoFactorAuthorizeHandler] failed to get two-factor setting for user \"uid:%d\", because %s", uid, err.Error())
|
||||||
return nil, errs.Or(err, errs.ErrSystemError)
|
return nil, errs.Or(err, errs.ErrSystemError)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,9 +165,9 @@ func (a *AuthorizationsApi) TwoFactorAuthorizeHandler(c *core.Context) (any, *er
|
|||||||
c.SetTextualToken(token)
|
c.SetTextualToken(token)
|
||||||
c.SetTokenClaims(claims)
|
c.SetTokenClaims(claims)
|
||||||
|
|
||||||
log.InfofWithRequestId(c, "[authorizations.TwoFactorAuthorizeHandler] user \"uid:%d\" has authorized two factor via passcode, token will be expired at %d", user.Uid, claims.ExpiresAt)
|
log.InfofWithRequestId(c, "[authorizations.TwoFactorAuthorizeHandler] user \"uid:%d\" has authorized two-factor via passcode, token will be expired at %d", user.Uid, claims.ExpiresAt)
|
||||||
|
|
||||||
authResp := a.getAuthResponse(token, false, user)
|
authResp := a.getAuthResponse(c, token, false, user)
|
||||||
return authResp, nil
|
return authResp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +185,7 @@ func (a *AuthorizationsApi) TwoFactorAuthorizeByRecoveryCodeHandler(c *core.Cont
|
|||||||
enableTwoFactor, err := a.twoFactorAuthorizations.ExistsTwoFactorSetting(c, uid)
|
enableTwoFactor, err := a.twoFactorAuthorizations.ExistsTwoFactorSetting(c, uid)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WarnfWithRequestId(c, "[authorizations.TwoFactorAuthorizeByRecoveryCodeHandler] failed to get two factor setting for user \"uid:%d\", because %s", uid, err.Error())
|
log.WarnfWithRequestId(c, "[authorizations.TwoFactorAuthorizeByRecoveryCodeHandler] failed to get two-factor setting for user \"uid:%d\", because %s", uid, err.Error())
|
||||||
return nil, errs.Or(err, errs.ErrSystemError)
|
return nil, errs.Or(err, errs.ErrSystemError)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,7 +213,7 @@ func (a *AuthorizationsApi) TwoFactorAuthorizeByRecoveryCodeHandler(c *core.Cont
|
|||||||
err = a.twoFactorAuthorizations.GetAndUseUserTwoFactorRecoveryCode(c, uid, credential.RecoveryCode, user.Salt)
|
err = a.twoFactorAuthorizations.GetAndUseUserTwoFactorRecoveryCode(c, uid, credential.RecoveryCode, user.Salt)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WarnfWithRequestId(c, "[authorizations.TwoFactorAuthorizeByRecoveryCodeHandler] failed to get two factor recovery code for user \"uid:%d\", because %s", uid, err.Error())
|
log.WarnfWithRequestId(c, "[authorizations.TwoFactorAuthorizeByRecoveryCodeHandler] failed to get two-factor recovery code for user \"uid:%d\", because %s", uid, err.Error())
|
||||||
return nil, errs.Or(err, errs.ErrTwoFactorRecoveryCodeNotExist)
|
return nil, errs.Or(err, errs.ErrTwoFactorRecoveryCodeNotExist)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,16 +234,17 @@ func (a *AuthorizationsApi) TwoFactorAuthorizeByRecoveryCodeHandler(c *core.Cont
|
|||||||
c.SetTextualToken(token)
|
c.SetTextualToken(token)
|
||||||
c.SetTokenClaims(claims)
|
c.SetTokenClaims(claims)
|
||||||
|
|
||||||
log.InfofWithRequestId(c, "[authorizations.TwoFactorAuthorizeByRecoveryCodeHandler] user \"uid:%d\" has authorized two factor via recovery code \"%s\", token will be expired at %d", user.Uid, credential.RecoveryCode, claims.ExpiresAt)
|
log.InfofWithRequestId(c, "[authorizations.TwoFactorAuthorizeByRecoveryCodeHandler] user \"uid:%d\" has authorized two-factor via recovery code \"%s\", token will be expired at %d", user.Uid, credential.RecoveryCode, claims.ExpiresAt)
|
||||||
|
|
||||||
authResp := a.getAuthResponse(token, false, user)
|
authResp := a.getAuthResponse(c, token, false, user)
|
||||||
return authResp, nil
|
return authResp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AuthorizationsApi) getAuthResponse(token string, need2FA bool, user *models.User) *models.AuthResponse {
|
func (a *AuthorizationsApi) getAuthResponse(c *core.Context, token string, need2FA bool, user *models.User) *models.AuthResponse {
|
||||||
return &models.AuthResponse{
|
return &models.AuthResponse{
|
||||||
Token: token,
|
Token: token,
|
||||||
Need2FA: need2FA,
|
Need2FA: need2FA,
|
||||||
User: user.ToUserBasicInfo(),
|
User: user.ToUserBasicInfo(),
|
||||||
|
NotificationContent: settings.Container.GetAfterLoginNotificationContent(user.Language, c.GetClientLocale()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+126
-90
@@ -19,103 +19,40 @@ const pageCountForDataExport = 1000
|
|||||||
|
|
||||||
// DataManagementsApi represents data management api
|
// DataManagementsApi represents data management api
|
||||||
type DataManagementsApi struct {
|
type DataManagementsApi struct {
|
||||||
exporter *converters.EzBookKeepingCSVFileExporter
|
ezBookKeepingCsvExporter *converters.EzBookKeepingCSVFileExporter
|
||||||
tokens *services.TokenService
|
ezBookKeepingTsvExporter *converters.EzBookKeepingTSVFileExporter
|
||||||
users *services.UserService
|
tokens *services.TokenService
|
||||||
accounts *services.AccountService
|
users *services.UserService
|
||||||
transactions *services.TransactionService
|
accounts *services.AccountService
|
||||||
categories *services.TransactionCategoryService
|
transactions *services.TransactionService
|
||||||
tags *services.TransactionTagService
|
categories *services.TransactionCategoryService
|
||||||
|
tags *services.TransactionTagService
|
||||||
|
templates *services.TransactionTemplateService
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize a data management api singleton instance
|
// Initialize a data management api singleton instance
|
||||||
var (
|
var (
|
||||||
DataManagements = &DataManagementsApi{
|
DataManagements = &DataManagementsApi{
|
||||||
exporter: &converters.EzBookKeepingCSVFileExporter{},
|
ezBookKeepingCsvExporter: &converters.EzBookKeepingCSVFileExporter{},
|
||||||
tokens: services.Tokens,
|
ezBookKeepingTsvExporter: &converters.EzBookKeepingTSVFileExporter{},
|
||||||
users: services.Users,
|
tokens: services.Tokens,
|
||||||
accounts: services.Accounts,
|
users: services.Users,
|
||||||
transactions: services.Transactions,
|
accounts: services.Accounts,
|
||||||
categories: services.TransactionCategories,
|
transactions: services.Transactions,
|
||||||
tags: services.TransactionTags,
|
categories: services.TransactionCategories,
|
||||||
|
tags: services.TransactionTags,
|
||||||
|
templates: services.TransactionTemplates,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExportDataHandler returns exported data in csv format
|
// ExportDataToEzbookkeepingCSVHandler returns exported data in csv format
|
||||||
func (a *DataManagementsApi) ExportDataHandler(c *core.Context) ([]byte, string, *errs.Error) {
|
func (a *DataManagementsApi) ExportDataToEzbookkeepingCSVHandler(c *core.Context) ([]byte, string, *errs.Error) {
|
||||||
if !settings.Container.Current.EnableDataExport {
|
return a.getExportedFileContent(c, "csv")
|
||||||
return nil, "", errs.ErrDataExportNotAllowed
|
}
|
||||||
}
|
|
||||||
|
|
||||||
timezone := time.Local
|
// ExportDataToEzbookkeepingTSVHandler returns exported data in csv format
|
||||||
utcOffset, err := c.GetClientTimezoneOffset()
|
func (a *DataManagementsApi) ExportDataToEzbookkeepingTSVHandler(c *core.Context) ([]byte, string, *errs.Error) {
|
||||||
|
return a.getExportedFileContent(c, "tsv")
|
||||||
if err != nil {
|
|
||||||
log.WarnfWithRequestId(c, "[data_managements.ExportDataHandler] cannot get client timezone offset, because %s", err.Error())
|
|
||||||
} else {
|
|
||||||
timezone = time.FixedZone("Client Timezone", int(utcOffset)*60)
|
|
||||||
}
|
|
||||||
|
|
||||||
uid := c.GetCurrentUid()
|
|
||||||
user, err := a.users.GetUserById(c, uid)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if !errs.IsCustomError(err) {
|
|
||||||
log.WarnfWithRequestId(c, "[data_managements.ExportDataHandler] failed to get user for user \"uid:%d\", because %s", uid, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, "", errs.ErrUserNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
accounts, err := a.accounts.GetAllAccountsByUid(c, uid)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.ErrorfWithRequestId(c, "[data_managements.ExportDataHandler] failed to get all accounts for user \"uid:%d\", because %s", uid, err.Error())
|
|
||||||
return nil, "", errs.ErrOperationFailed
|
|
||||||
}
|
|
||||||
|
|
||||||
categories, err := a.categories.GetAllCategoriesByUid(c, uid, 0, -1)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.ErrorfWithRequestId(c, "[data_managements.ExportDataHandler] failed to get categories for user \"uid:%d\", because %s", uid, err.Error())
|
|
||||||
return nil, "", errs.ErrOperationFailed
|
|
||||||
}
|
|
||||||
|
|
||||||
tags, err := a.tags.GetAllTagsByUid(c, uid)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.ErrorfWithRequestId(c, "[data_managements.ExportDataHandler] failed to get tags for user \"uid:%d\", because %s", uid, err.Error())
|
|
||||||
return nil, "", errs.ErrOperationFailed
|
|
||||||
}
|
|
||||||
|
|
||||||
tagIndexs, err := a.tags.GetAllTagIdsOfAllTransactions(c, uid)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.ErrorfWithRequestId(c, "[data_managements.ExportDataHandler] failed to get tag index for user \"uid:%d\", because %s", uid, err.Error())
|
|
||||||
return nil, "", errs.ErrOperationFailed
|
|
||||||
}
|
|
||||||
|
|
||||||
accountMap := a.accounts.GetAccountMapByList(accounts)
|
|
||||||
categoryMap := a.categories.GetCategoryMapByList(categories)
|
|
||||||
tagMap := a.tags.GetTagMapByList(tags)
|
|
||||||
|
|
||||||
allTransactions, err := a.transactions.GetAllTransactions(c, uid, pageCountForDataExport, true)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.ErrorfWithRequestId(c, "[data_managements.ExportDataHandler] failed to all transactions user \"uid:%d\", because %s", uid, err.Error())
|
|
||||||
return nil, "", errs.ErrOperationFailed
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := a.exporter.ToExportedContent(uid, timezone, allTransactions, accountMap, categoryMap, tagMap, tagIndexs)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.ErrorfWithRequestId(c, "[data_managements.ExportDataHandler] failed to get csv format exported data for \"uid:%d\", because %s", uid, err.Error())
|
|
||||||
return nil, "", errs.Or(err, errs.ErrOperationFailed)
|
|
||||||
}
|
|
||||||
|
|
||||||
fileName := a.getFileName(user, timezone)
|
|
||||||
|
|
||||||
return result, fileName, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DataStatisticsHandler returns user data statistics
|
// DataStatisticsHandler returns user data statistics
|
||||||
@@ -149,11 +86,19 @@ func (a *DataManagementsApi) DataStatisticsHandler(c *core.Context) (any, *errs.
|
|||||||
return nil, errs.ErrOperationFailed
|
return nil, errs.ErrOperationFailed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
totalTransactionTemplateCount, err := a.templates.GetTotalNormalTemplateCountByUid(c, uid)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[data_managements.DataStatisticsHandler] failed to get total transaction template count for user \"uid:%d\", because %s", uid, err.Error())
|
||||||
|
return nil, errs.ErrOperationFailed
|
||||||
|
}
|
||||||
|
|
||||||
dataStatisticsResp := &models.DataStatisticsResponse{
|
dataStatisticsResp := &models.DataStatisticsResponse{
|
||||||
TotalAccountCount: totalAccountCount,
|
TotalAccountCount: totalAccountCount,
|
||||||
TotalTransactionCategoryCount: totalTransactionCategoryCount,
|
TotalTransactionCategoryCount: totalTransactionCategoryCount,
|
||||||
TotalTransactionTagCount: totalTransactionTagCount,
|
TotalTransactionTagCount: totalTransactionTagCount,
|
||||||
TotalTransactionCount: totalTransactionCount,
|
TotalTransactionCount: totalTransactionCount,
|
||||||
|
TotalTransactionTemplateCount: totalTransactionTemplateCount,
|
||||||
}
|
}
|
||||||
|
|
||||||
return dataStatisticsResp, nil
|
return dataStatisticsResp, nil
|
||||||
@@ -205,15 +150,106 @@ func (a *DataManagementsApi) ClearDataHandler(c *core.Context) (any, *errs.Error
|
|||||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = a.templates.DeleteAllTemplates(c, uid)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[data_managements.ClearDataHandler] failed to delete all transaction templates, because %s", err.Error())
|
||||||
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
log.InfofWithRequestId(c, "[data_managements.ClearDataHandler] user \"uid:%d\" has cleared all data", uid)
|
log.InfofWithRequestId(c, "[data_managements.ClearDataHandler] user \"uid:%d\" has cleared all data", uid)
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *DataManagementsApi) getFileName(user *models.User, timezone *time.Location) string {
|
func (a *DataManagementsApi) getExportedFileContent(c *core.Context, fileType string) ([]byte, string, *errs.Error) {
|
||||||
|
if !settings.Container.Current.EnableDataExport {
|
||||||
|
return nil, "", errs.ErrDataExportNotAllowed
|
||||||
|
}
|
||||||
|
|
||||||
|
timezone := time.Local
|
||||||
|
utcOffset, err := c.GetClientTimezoneOffset()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WarnfWithRequestId(c, "[data_managements.ExportDataHandler] cannot get client timezone offset, because %s", err.Error())
|
||||||
|
} else {
|
||||||
|
timezone = time.FixedZone("Client Timezone", int(utcOffset)*60)
|
||||||
|
}
|
||||||
|
|
||||||
|
uid := c.GetCurrentUid()
|
||||||
|
user, err := a.users.GetUserById(c, uid)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if !errs.IsCustomError(err) {
|
||||||
|
log.WarnfWithRequestId(c, "[data_managements.ExportDataHandler] failed to get user for user \"uid:%d\", because %s", uid, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, "", errs.ErrUserNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
accounts, err := a.accounts.GetAllAccountsByUid(c, uid)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[data_managements.ExportDataHandler] failed to get all accounts for user \"uid:%d\", because %s", uid, err.Error())
|
||||||
|
return nil, "", errs.ErrOperationFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
categories, err := a.categories.GetAllCategoriesByUid(c, uid, 0, -1)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[data_managements.ExportDataHandler] failed to get categories for user \"uid:%d\", because %s", uid, err.Error())
|
||||||
|
return nil, "", errs.ErrOperationFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
tags, err := a.tags.GetAllTagsByUid(c, uid)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[data_managements.ExportDataHandler] failed to get tags for user \"uid:%d\", because %s", uid, err.Error())
|
||||||
|
return nil, "", errs.ErrOperationFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
tagIndexes, err := a.tags.GetAllTagIdsMapOfAllTransactions(c, uid)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[data_managements.ExportDataHandler] failed to get tag index for user \"uid:%d\", because %s", uid, err.Error())
|
||||||
|
return nil, "", errs.ErrOperationFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
accountMap := a.accounts.GetAccountMapByList(accounts)
|
||||||
|
categoryMap := a.categories.GetCategoryMapByList(categories)
|
||||||
|
tagMap := a.tags.GetTagMapByList(tags)
|
||||||
|
|
||||||
|
allTransactions, err := a.transactions.GetAllTransactions(c, uid, pageCountForDataExport, true)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[data_managements.ExportDataHandler] failed to all transactions user \"uid:%d\", because %s", uid, err.Error())
|
||||||
|
return nil, "", errs.ErrOperationFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
var dataExporter converters.DataConverter
|
||||||
|
|
||||||
|
if fileType == "tsv" {
|
||||||
|
dataExporter = a.ezBookKeepingTsvExporter
|
||||||
|
} else {
|
||||||
|
dataExporter = a.ezBookKeepingCsvExporter
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := dataExporter.ToExportedContent(uid, allTransactions, accountMap, categoryMap, tagMap, tagIndexes)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[data_managements.ExportDataHandler] failed to get csv format exported data for \"uid:%d\", because %s", uid, err.Error())
|
||||||
|
return nil, "", errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileName := a.getFileName(user, timezone, fileType)
|
||||||
|
|
||||||
|
return result, fileName, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *DataManagementsApi) getFileName(user *models.User, timezone *time.Location, fileExtension string) string {
|
||||||
currentTime := utils.FormatUnixTimeToLongDateTimeWithoutSecond(time.Now().Unix(), timezone)
|
currentTime := utils.FormatUnixTimeToLongDateTimeWithoutSecond(time.Now().Unix(), timezone)
|
||||||
currentTime = strings.Replace(currentTime, "-", "_", -1)
|
currentTime = strings.Replace(currentTime, "-", "_", -1)
|
||||||
currentTime = strings.Replace(currentTime, " ", "_", -1)
|
currentTime = strings.Replace(currentTime, " ", "_", -1)
|
||||||
currentTime = strings.Replace(currentTime, ":", "_", -1)
|
currentTime = strings.Replace(currentTime, ":", "_", -1)
|
||||||
|
|
||||||
return fmt.Sprintf("%s_%s.csv", user.Username, currentTime)
|
return fmt.Sprintf("%s_%s.%s", user.Username, currentTime, fileExtension)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -13,6 +14,7 @@ import (
|
|||||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExchangeRatesApi represents exchange rate api
|
// ExchangeRatesApi represents exchange rate api
|
||||||
@@ -34,6 +36,7 @@ func (a *ExchangeRatesApi) LatestExchangeRateHandler(c *core.Context) (any, *err
|
|||||||
uid := c.GetCurrentUid()
|
uid := c.GetCurrentUid()
|
||||||
|
|
||||||
transport := http.DefaultTransport.(*http.Transport).Clone()
|
transport := http.DefaultTransport.(*http.Transport).Clone()
|
||||||
|
utils.SetProxyUrl(transport, settings.Container.Current.ExchangeRatesProxy)
|
||||||
|
|
||||||
if settings.Container.Current.ExchangeRatesSkipTLSVerify {
|
if settings.Container.Current.ExchangeRatesSkipTLSVerify {
|
||||||
transport.TLSClientConfig = &tls.Config{
|
transport.TLSClientConfig = &tls.Config{
|
||||||
@@ -50,7 +53,10 @@ func (a *ExchangeRatesApi) LatestExchangeRateHandler(c *core.Context) (any, *err
|
|||||||
exchangeRateResps := make([]*models.LatestExchangeRateResponse, 0, len(urls))
|
exchangeRateResps := make([]*models.LatestExchangeRateResponse, 0, len(urls))
|
||||||
|
|
||||||
for i := 0; i < len(urls); i++ {
|
for i := 0; i < len(urls); i++ {
|
||||||
resp, err := client.Get(urls[i])
|
req, _ := http.NewRequest("GET", urls[i], nil)
|
||||||
|
req.Header.Set("User-Agent", fmt.Sprintf("ezBookkeeping/%s ", settings.Version))
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorfWithRequestId(c, "[exchange_rates.LatestExchangeRateHandler] failed to request latest exchange rate data for user \"uid:%d\", because %s", uid, err.Error())
|
log.ErrorfWithRequestId(c, "[exchange_rates.LatestExchangeRateHandler] failed to request latest exchange rate data for user \"uid:%d\", because %s", uid, err.Error())
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -10,14 +9,18 @@ import (
|
|||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
const openStreetMapTileImageUrlFormat = "https://tile.openstreetmap.org/%s/%s/%s" // https://tile.openstreetmap.org/{z}/{x}/{y}.png
|
const openStreetMapTileImageUrlFormat = "https://tile.openstreetmap.org/{z}/{x}/{y}.png" // https://tile.openstreetmap.org/{z}/{x}/{y}.png
|
||||||
const openStreetMapHumanitarianStyleTileImageUrlFormat = "https://a.tile.openstreetmap.fr/hot/%s/%s/%s" // https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png
|
const openStreetMapHumanitarianStyleTileImageUrlFormat = "https://a.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png" // https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png
|
||||||
const openTopoMapTileImageUrlFormat = "https://tile.opentopomap.org/%s/%s/%s" // https://tile.opentopomap.org/{z}/{x}/{y}.png
|
const openTopoMapTileImageUrlFormat = "https://tile.opentopomap.org/{z}/{x}/{y}.png" // https://tile.opentopomap.org/{z}/{x}/{y}.png
|
||||||
const opnvKarteMapTileImageUrlFormat = "https://tileserver.memomaps.de/tilegen/%s/%s/%s" // https://tileserver.memomaps.de/tilegen/{z}/{x}/{y}.png
|
const opnvKarteMapTileImageUrlFormat = "https://tileserver.memomaps.de/tilegen/{z}/{x}/{y}.png" // https://tileserver.memomaps.de/tilegen/{z}/{x}/{y}.png
|
||||||
const cyclOSMMapTileImageUrlFormat = "https://a.tile-cyclosm.openstreetmap.fr/cyclosm/%s/%s/%s" // https://{s}.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png
|
const cyclOSMMapTileImageUrlFormat = "https://a.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png" // https://{s}.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png
|
||||||
const tomtomMapTileImageUrlFormat = "https://api.tomtom.com/map/1/tile/basic/main/%s/%s/%s" // https://api.tomtom.com/map/{versionNumber}/tile/{layer}/{style}/{z}/{x}/{y}.png?key={key}&language={language}
|
const cartoDBMapTileImageUrlFormat = "https://a.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{scale}.png" // https://{s}.basemaps.cartocdn.com/{style}/{z}/{x}/{y}{scale}.png
|
||||||
|
const tomtomMapTileImageUrlFormat = "https://api.tomtom.com/map/1/tile/basic/main/{z}/{x}/{y}.png" // https://api.tomtom.com/map/{versionNumber}/tile/{layer}/{style}/{z}/{x}/{y}.png?key={key}&language={language}
|
||||||
|
const tianDiTuMapTileImageUrlFormat = "https://t0.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}" // https://t{s}.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk={key}
|
||||||
|
const tianDiTuMapAnnotationUrlFormat = "https://t0.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}" // https://t{s}.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk={key}
|
||||||
|
|
||||||
// MapImageProxy represents map image proxy
|
// MapImageProxy represents map image proxy
|
||||||
type MapImageProxy struct {
|
type MapImageProxy struct {
|
||||||
@@ -30,36 +33,86 @@ var (
|
|||||||
|
|
||||||
// MapTileImageProxyHandler returns map tile image
|
// MapTileImageProxyHandler returns map tile image
|
||||||
func (p *MapImageProxy) MapTileImageProxyHandler(c *core.Context) (*httputil.ReverseProxy, *errs.Error) {
|
func (p *MapImageProxy) MapTileImageProxyHandler(c *core.Context) (*httputil.ReverseProxy, *errs.Error) {
|
||||||
|
return p.mapImageProxyHandler(c, func(c *core.Context, mapProvider string) (string, *errs.Error) {
|
||||||
|
if mapProvider == settings.OpenStreetMapProvider {
|
||||||
|
return openStreetMapTileImageUrlFormat, nil
|
||||||
|
} else if mapProvider == settings.OpenStreetMapHumanitarianStyleProvider {
|
||||||
|
return openStreetMapHumanitarianStyleTileImageUrlFormat, nil
|
||||||
|
} else if mapProvider == settings.OpenTopoMapProvider {
|
||||||
|
return openTopoMapTileImageUrlFormat, nil
|
||||||
|
} else if mapProvider == settings.OPNVKarteMapProvider {
|
||||||
|
return opnvKarteMapTileImageUrlFormat, nil
|
||||||
|
} else if mapProvider == settings.CyclOSMMapProvider {
|
||||||
|
return cyclOSMMapTileImageUrlFormat, nil
|
||||||
|
} else if mapProvider == settings.CartoDBMapProvider {
|
||||||
|
return cartoDBMapTileImageUrlFormat, nil
|
||||||
|
} else if mapProvider == settings.TomTomMapProvider {
|
||||||
|
targetUrl := tomtomMapTileImageUrlFormat + "?key=" + settings.Container.Current.TomTomMapAPIKey
|
||||||
|
language := c.Query("language")
|
||||||
|
|
||||||
|
if language != "" {
|
||||||
|
targetUrl = targetUrl + "&language=" + language
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetUrl, nil
|
||||||
|
} else if mapProvider == settings.TianDiTuProvider {
|
||||||
|
return tianDiTuMapTileImageUrlFormat + "&tk=" + settings.Container.Current.TianDiTuAPIKey, nil
|
||||||
|
} else if mapProvider == settings.CustomProvider {
|
||||||
|
return settings.Container.Current.CustomMapTileServerTileLayerUrl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", errs.ErrParameterInvalid
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// MapAnnotationImageProxyHandler returns map annotation image
|
||||||
|
func (p *MapImageProxy) MapAnnotationImageProxyHandler(c *core.Context) (*httputil.ReverseProxy, *errs.Error) {
|
||||||
|
return p.mapImageProxyHandler(c, func(c *core.Context, mapProvider string) (string, *errs.Error) {
|
||||||
|
if mapProvider == settings.TianDiTuProvider {
|
||||||
|
return tianDiTuMapAnnotationUrlFormat + "&tk=" + settings.Container.Current.TianDiTuAPIKey, nil
|
||||||
|
} else if mapProvider == settings.CustomProvider {
|
||||||
|
return settings.Container.Current.CustomMapTileServerAnnotationLayerUrl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", errs.ErrParameterInvalid
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *MapImageProxy) mapImageProxyHandler(c *core.Context, fn func(c *core.Context, mapProvider string) (string, *errs.Error)) (*httputil.ReverseProxy, *errs.Error) {
|
||||||
mapProvider := strings.Replace(c.Query("provider"), "-", "_", -1)
|
mapProvider := strings.Replace(c.Query("provider"), "-", "_", -1)
|
||||||
targetUrl := ""
|
targetUrl := ""
|
||||||
|
|
||||||
if mapProvider == settings.OpenStreetMapProvider {
|
if mapProvider != settings.Container.Current.MapProvider {
|
||||||
targetUrl = openStreetMapTileImageUrlFormat
|
return nil, errs.ErrMapProviderNotCurrent
|
||||||
} else if mapProvider == settings.OpenStreetMapHumanitarianStyleProvider {
|
|
||||||
targetUrl = openStreetMapHumanitarianStyleTileImageUrlFormat
|
|
||||||
} else if mapProvider == settings.OpenTopoMapProvider {
|
|
||||||
targetUrl = openTopoMapTileImageUrlFormat
|
|
||||||
} else if mapProvider == settings.OPNVKarteMapProvider {
|
|
||||||
targetUrl = opnvKarteMapTileImageUrlFormat
|
|
||||||
} else if mapProvider == settings.CyclOSMMapProvider {
|
|
||||||
targetUrl = cyclOSMMapTileImageUrlFormat
|
|
||||||
} else if mapProvider == settings.TomTomMapProvider {
|
|
||||||
targetUrl = tomtomMapTileImageUrlFormat + "?key=" + settings.Container.Current.TomTomMapAPIKey
|
|
||||||
language := c.Query("language")
|
|
||||||
|
|
||||||
if language != "" {
|
|
||||||
targetUrl = targetUrl + "&language=" + language
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, errs.ErrParameterInvalid
|
|
||||||
}
|
}
|
||||||
|
|
||||||
director := func(req *http.Request) {
|
zoomLevel := c.Param("zoomLevel")
|
||||||
zoomLevel := c.Param("zoomLevel")
|
coordinateX := c.Param("coordinateX")
|
||||||
coordinateX := c.Param("coordinateX")
|
fileName := c.Param("fileName")
|
||||||
fileName := c.Param("fileName")
|
fileNameParts := strings.Split(fileName, ".")
|
||||||
|
coordinateY := fileNameParts[0]
|
||||||
|
scale := c.Query("scale")
|
||||||
|
|
||||||
imageRawUrl := fmt.Sprintf(targetUrl, zoomLevel, coordinateX, fileName)
|
if len(fileNameParts) != 2 || fileNameParts[len(fileNameParts)-1] != "png" {
|
||||||
|
return nil, errs.ErrImageExtensionNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
var err *errs.Error
|
||||||
|
targetUrl, err = fn(c, mapProvider)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
transport := http.DefaultTransport.(*http.Transport).Clone()
|
||||||
|
utils.SetProxyUrl(transport, settings.Container.Current.MapProxy)
|
||||||
|
|
||||||
|
director := func(req *http.Request) {
|
||||||
|
imageRawUrl := targetUrl
|
||||||
|
imageRawUrl = strings.Replace(imageRawUrl, "{z}", zoomLevel, -1)
|
||||||
|
imageRawUrl = strings.Replace(imageRawUrl, "{x}", coordinateX, -1)
|
||||||
|
imageRawUrl = strings.Replace(imageRawUrl, "{y}", coordinateY, -1)
|
||||||
|
imageRawUrl = strings.Replace(imageRawUrl, "{scale}", scale, -1)
|
||||||
imageUrl, _ := url.Parse(imageRawUrl)
|
imageUrl, _ := url.Parse(imageRawUrl)
|
||||||
|
|
||||||
req.URL = imageUrl
|
req.URL = imageUrl
|
||||||
@@ -67,5 +120,8 @@ func (p *MapImageProxy) MapTileImageProxyHandler(c *core.Context) (*httputil.Rev
|
|||||||
req.Host = imageUrl.Host
|
req.Host = imageUrl.Host
|
||||||
}
|
}
|
||||||
|
|
||||||
return &httputil.ReverseProxy{Director: director}, nil
|
return &httputil.ReverseProxy{
|
||||||
|
Transport: transport,
|
||||||
|
Director: director,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -35,7 +35,7 @@ func (a *QrCodesApi) MobileUrlQrCodeHandler(c *core.Context) ([]byte, string, *e
|
|||||||
return nil, "", errs.ErrOperationFailed
|
return nil, "", errs.ErrOperationFailed
|
||||||
}
|
}
|
||||||
|
|
||||||
return data, "", nil
|
return data, "image/png", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *QrCodesApi) generateUrlQrCode(c *core.Context, url string) ([]byte, *errs.Error) {
|
func (a *QrCodesApi) generateUrlQrCode(c *core.Context, url string) ([]byte, *errs.Error) {
|
||||||
|
|||||||
+41
-6
@@ -2,12 +2,14 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"sort"
|
"sort"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/services"
|
"github.com/mayswind/ezbookkeeping/pkg/services"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -44,8 +46,7 @@ func (a *TokensApi) TokenListHandler(c *core.Context) (any, *errs.Error) {
|
|||||||
TokenId: a.tokens.GenerateTokenId(token),
|
TokenId: a.tokens.GenerateTokenId(token),
|
||||||
TokenType: token.TokenType,
|
TokenType: token.TokenType,
|
||||||
UserAgent: token.UserAgent,
|
UserAgent: token.UserAgent,
|
||||||
CreatedAt: token.CreatedUnixTime,
|
LastSeen: token.LastSeenUnixTime,
|
||||||
ExpiredAt: token.ExpiredUnixTime,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if token.Uid == claims.Uid && 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 {
|
||||||
@@ -176,6 +177,40 @@ func (a *TokensApi) TokenRefreshHandler(c *core.Context) (any, *errs.Error) {
|
|||||||
return nil, errs.ErrUserNotFound
|
return nil, errs.ErrUserNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
oldTokenClaims := c.GetTokenClaims()
|
||||||
|
|
||||||
|
if now-oldTokenClaims.IssuedAt < int64(settings.Container.Current.TokenMinRefreshInterval) {
|
||||||
|
log.InfofWithRequestId(c, "[token.TokenRefreshHandler] token of user \"uid:%d\" does not need to be refreshed", uid)
|
||||||
|
|
||||||
|
userTokenId, err := utils.StringToInt64(oldTokenClaims.UserTokenId)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WarnfWithRequestId(c, "[tokens.TokenRefreshHandler] parse user token id failed, because %s", err.Error())
|
||||||
|
} else {
|
||||||
|
tokenRecord := &models.TokenRecord{
|
||||||
|
Uid: oldTokenClaims.Uid,
|
||||||
|
UserTokenId: userTokenId,
|
||||||
|
CreatedUnixTime: oldTokenClaims.IssuedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenId := a.tokens.GenerateTokenId(tokenRecord)
|
||||||
|
|
||||||
|
err = a.tokens.UpdateTokenLastSeen(c, tokenRecord)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WarnfWithRequestId(c, "[token.TokenRefreshHandler] failed to update last seen of token \"id:%s\" for user \"uid:%d\", because %s", tokenId, uid, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshResp := &models.TokenRefreshResponse{
|
||||||
|
User: user.ToUserBasicInfo(),
|
||||||
|
NotificationContent: settings.Container.GetAfterOpenNotificationContent(user.Language, c.GetClientLocale()),
|
||||||
|
}
|
||||||
|
|
||||||
|
return refreshResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
token, claims, err := a.tokens.CreateToken(c, user)
|
token, claims, err := a.tokens.CreateToken(c, user)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -183,7 +218,6 @@ func (a *TokensApi) TokenRefreshHandler(c *core.Context) (any, *errs.Error) {
|
|||||||
return nil, errs.Or(err, errs.ErrTokenGenerating)
|
return nil, errs.Or(err, errs.ErrTokenGenerating)
|
||||||
}
|
}
|
||||||
|
|
||||||
oldTokenClaims := c.GetTokenClaims()
|
|
||||||
oldUserTokenId, _ := utils.StringToInt64(oldTokenClaims.UserTokenId)
|
oldUserTokenId, _ := utils.StringToInt64(oldTokenClaims.UserTokenId)
|
||||||
oldTokenRecord := &models.TokenRecord{
|
oldTokenRecord := &models.TokenRecord{
|
||||||
Uid: uid,
|
Uid: uid,
|
||||||
@@ -197,9 +231,10 @@ func (a *TokensApi) TokenRefreshHandler(c *core.Context) (any, *errs.Error) {
|
|||||||
log.InfofWithRequestId(c, "[token.TokenRefreshHandler] user \"uid:%d\" token refreshed, new token will be expired at %d", user.Uid, claims.ExpiresAt)
|
log.InfofWithRequestId(c, "[token.TokenRefreshHandler] user \"uid:%d\" token refreshed, new token will be expired at %d", user.Uid, claims.ExpiresAt)
|
||||||
|
|
||||||
refreshResp := &models.TokenRefreshResponse{
|
refreshResp := &models.TokenRefreshResponse{
|
||||||
NewToken: token,
|
NewToken: token,
|
||||||
OldTokenId: a.tokens.GenerateTokenId(oldTokenRecord),
|
OldTokenId: a.tokens.GenerateTokenId(oldTokenRecord),
|
||||||
User: user.ToUserBasicInfo(),
|
User: user.ToUserBasicInfo(),
|
||||||
|
NotificationContent: settings.Container.GetAfterOpenNotificationContent(user.Language, c.GetClientLocale()),
|
||||||
}
|
}
|
||||||
|
|
||||||
return refreshResp, nil
|
return refreshResp, nil
|
||||||
|
|||||||
@@ -6,10 +6,13 @@ import (
|
|||||||
"github.com/gin-gonic/gin/binding"
|
"github.com/gin-gonic/gin/binding"
|
||||||
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/duplicatechecker"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/services"
|
"github.com/mayswind/ezbookkeeping/pkg/services"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TransactionCategoriesApi represents transaction category api
|
// TransactionCategoriesApi represents transaction category api
|
||||||
@@ -119,6 +122,28 @@ func (a *TransactionCategoriesApi) CategoryCreateHandler(c *core.Context) (any,
|
|||||||
|
|
||||||
category := a.createNewCategoryModel(uid, &categoryCreateReq, maxOrderId+1)
|
category := a.createNewCategoryModel(uid, &categoryCreateReq, maxOrderId+1)
|
||||||
|
|
||||||
|
if settings.Container.Current.EnableDuplicateSubmissionsCheck && categoryCreateReq.ClientSessionId != "" {
|
||||||
|
found, remark := duplicatechecker.Container.Get(duplicatechecker.DUPLICATE_CHECKER_TYPE_NEW_CATEGORY, uid, categoryCreateReq.ClientSessionId)
|
||||||
|
|
||||||
|
if found {
|
||||||
|
log.InfofWithRequestId(c, "[transaction_categories.CategoryCreateHandler] another category \"id:%s\" has been created for user \"uid:%d\"", remark, uid)
|
||||||
|
categoryId, err := utils.StringToInt64(remark)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
category, err = a.categories.GetCategoryByCategoryId(c, uid, categoryId)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[transaction_categories.CategoryCreateHandler] failed to get existed category \"id:%d\" for user \"uid:%d\", because %s", categoryId, uid, err.Error())
|
||||||
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
categoryResp := category.ToTransactionCategoryInfoResponse()
|
||||||
|
|
||||||
|
return categoryResp, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err = a.categories.CreateCategory(c, category)
|
err = a.categories.CreateCategory(c, category)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -128,6 +153,7 @@ func (a *TransactionCategoriesApi) CategoryCreateHandler(c *core.Context) (any,
|
|||||||
|
|
||||||
log.InfofWithRequestId(c, "[transaction_categories.CategoryCreateHandler] user \"uid:%d\" has created a new category \"id:%d\" successfully", uid, category.CategoryId)
|
log.InfofWithRequestId(c, "[transaction_categories.CategoryCreateHandler] user \"uid:%d\" has created a new category \"id:%d\" successfully", uid, category.CategoryId)
|
||||||
|
|
||||||
|
duplicatechecker.Container.Set(duplicatechecker.DUPLICATE_CHECKER_TYPE_NEW_CATEGORY, uid, categoryCreateReq.ClientSessionId, utils.Int64ToString(category.CategoryId))
|
||||||
categoryResp := category.ToTransactionCategoryInfoResponse()
|
categoryResp := category.ToTransactionCategoryInfoResponse()
|
||||||
|
|
||||||
return categoryResp, nil
|
return categoryResp, nil
|
||||||
@@ -173,16 +199,18 @@ func (a *TransactionCategoriesApi) CategoryModifyHandler(c *core.Context) (any,
|
|||||||
}
|
}
|
||||||
|
|
||||||
newCategory := &models.TransactionCategory{
|
newCategory := &models.TransactionCategory{
|
||||||
CategoryId: category.CategoryId,
|
CategoryId: category.CategoryId,
|
||||||
Uid: uid,
|
Uid: uid,
|
||||||
Name: categoryModifyReq.Name,
|
ParentCategoryId: categoryModifyReq.ParentId,
|
||||||
Icon: categoryModifyReq.Icon,
|
Name: categoryModifyReq.Name,
|
||||||
Color: categoryModifyReq.Color,
|
Icon: categoryModifyReq.Icon,
|
||||||
Comment: categoryModifyReq.Comment,
|
Color: categoryModifyReq.Color,
|
||||||
Hidden: categoryModifyReq.Hidden,
|
Comment: categoryModifyReq.Comment,
|
||||||
|
Hidden: categoryModifyReq.Hidden,
|
||||||
}
|
}
|
||||||
|
|
||||||
if newCategory.Name == category.Name &&
|
if newCategory.ParentCategoryId == category.ParentCategoryId &&
|
||||||
|
newCategory.Name == category.Name &&
|
||||||
newCategory.Icon == category.Icon &&
|
newCategory.Icon == category.Icon &&
|
||||||
newCategory.Color == category.Color &&
|
newCategory.Color == category.Color &&
|
||||||
newCategory.Comment == category.Comment &&
|
newCategory.Comment == category.Comment &&
|
||||||
@@ -190,6 +218,38 @@ func (a *TransactionCategoriesApi) CategoryModifyHandler(c *core.Context) (any,
|
|||||||
return nil, errs.ErrNothingWillBeUpdated
|
return nil, errs.ErrNothingWillBeUpdated
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if category.ParentCategoryId == 0 && newCategory.ParentCategoryId != 0 {
|
||||||
|
return nil, errs.Or(err, errs.ErrNotAllowChangePrimaryTransactionCategoryToSecondary)
|
||||||
|
}
|
||||||
|
|
||||||
|
if category.ParentCategoryId != 0 && newCategory.ParentCategoryId == 0 {
|
||||||
|
return nil, errs.Or(err, errs.ErrNotAllowChangeSecondaryTransactionCategoryToPrimary)
|
||||||
|
}
|
||||||
|
|
||||||
|
if newCategory.ParentCategoryId != category.ParentCategoryId {
|
||||||
|
fromPrimaryCategory, err := a.categories.GetCategoryByCategoryId(c, uid, category.ParentCategoryId)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[transaction_categories.CategoryModifyHandler] failed to get old primary category \"id:%d\" of category \"id:%d\" for user \"uid:%d\", because %s", category.ParentCategoryId, categoryModifyReq.Id, uid, err.Error())
|
||||||
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
toPrimaryCategory, err := a.categories.GetCategoryByCategoryId(c, uid, newCategory.ParentCategoryId)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[transaction_categories.CategoryModifyHandler] failed to get new primary category \"id:%d\" of category \"id:%d\" for user \"uid:%d\", because %s", newCategory.ParentCategoryId, categoryModifyReq.Id, uid, err.Error())
|
||||||
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fromPrimaryCategory.Type != toPrimaryCategory.Type {
|
||||||
|
return nil, errs.Or(err, errs.ErrNotAllowChangePrimaryTransactionType)
|
||||||
|
}
|
||||||
|
|
||||||
|
if toPrimaryCategory.ParentCategoryId != 0 {
|
||||||
|
return nil, errs.Or(err, errs.ErrNotAllowUseSecondaryTransactionAsPrimaryCategory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err = a.categories.ModifyCategory(c, newCategory)
|
err = a.categories.ModifyCategory(c, newCategory)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -200,7 +260,6 @@ func (a *TransactionCategoriesApi) CategoryModifyHandler(c *core.Context) (any,
|
|||||||
log.InfofWithRequestId(c, "[transaction_categories.CategoryModifyHandler] user \"uid:%d\" has updated category \"id:%d\" successfully", uid, categoryModifyReq.Id)
|
log.InfofWithRequestId(c, "[transaction_categories.CategoryModifyHandler] user \"uid:%d\" has updated category \"id:%d\" successfully", uid, categoryModifyReq.Id)
|
||||||
|
|
||||||
newCategory.Type = category.Type
|
newCategory.Type = category.Type
|
||||||
newCategory.ParentCategoryId = category.ParentCategoryId
|
|
||||||
newCategory.DisplayOrder = category.DisplayOrder
|
newCategory.DisplayOrder = category.DisplayOrder
|
||||||
categoryResp := newCategory.ToTransactionCategoryInfoResponse()
|
categoryResp := newCategory.ToTransactionCategoryInfoResponse()
|
||||||
|
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ func (a *TransactionTagsApi) TagHideHandler(c *core.Context) (any, *errs.Error)
|
|||||||
err := c.ShouldBindJSON(&tagHideReq)
|
err := c.ShouldBindJSON(&tagHideReq)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WarnfWithRequestId(c, "[transaction_tags.CategoryHideHandler] parse request failed, because %s", err.Error())
|
log.WarnfWithRequestId(c, "[transaction_tags.TagHideHandler] parse request failed, because %s", err.Error())
|
||||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,11 +158,11 @@ func (a *TransactionTagsApi) TagHideHandler(c *core.Context) (any, *errs.Error)
|
|||||||
err = a.tags.HideTag(c, uid, []int64{tagHideReq.Id}, tagHideReq.Hidden)
|
err = a.tags.HideTag(c, uid, []int64{tagHideReq.Id}, tagHideReq.Hidden)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorfWithRequestId(c, "[transaction_tags.CategoryHideHandler] failed to hide tag \"id:%d\" for user \"uid:%d\", because %s", tagHideReq.Id, uid, err.Error())
|
log.ErrorfWithRequestId(c, "[transaction_tags.TagHideHandler] failed to hide tag \"id:%d\" for user \"uid:%d\", because %s", tagHideReq.Id, uid, err.Error())
|
||||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.InfofWithRequestId(c, "[transaction_tags.CategoryHideHandler] user \"uid:%d\" has hidden category \"id:%d\"", uid, tagHideReq.Id)
|
log.InfofWithRequestId(c, "[transaction_tags.TagHideHandler] user \"uid:%d\" has hidden tag \"id:%d\"", uid, tagHideReq.Id)
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,7 +172,7 @@ func (a *TransactionTagsApi) TagMoveHandler(c *core.Context) (any, *errs.Error)
|
|||||||
err := c.ShouldBindJSON(&tagMoveReq)
|
err := c.ShouldBindJSON(&tagMoveReq)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WarnfWithRequestId(c, "[transaction_tags.CategoryMoveHandler] parse request failed, because %s", err.Error())
|
log.WarnfWithRequestId(c, "[transaction_tags.TagMoveHandler] parse request failed, because %s", err.Error())
|
||||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,11 +193,11 @@ func (a *TransactionTagsApi) TagMoveHandler(c *core.Context) (any, *errs.Error)
|
|||||||
err = a.tags.ModifyTagDisplayOrders(c, uid, tags)
|
err = a.tags.ModifyTagDisplayOrders(c, uid, tags)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorfWithRequestId(c, "[transaction_tags.CategoryMoveHandler] failed to move tags for user \"uid:%d\", because %s", uid, err.Error())
|
log.ErrorfWithRequestId(c, "[transaction_tags.TagMoveHandler] failed to move tags for user \"uid:%d\", because %s", uid, err.Error())
|
||||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.InfofWithRequestId(c, "[transaction_tags.CategoryMoveHandler] user \"uid:%d\" has moved categories", uid)
|
log.InfofWithRequestId(c, "[transaction_tags.TagMoveHandler] user \"uid:%d\" has moved tags", uid)
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,321 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/duplicatechecker"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/services"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TransactionTemplatesApi represents transaction template api
|
||||||
|
type TransactionTemplatesApi struct {
|
||||||
|
templates *services.TransactionTemplateService
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize a transaction template api singleton instance
|
||||||
|
var (
|
||||||
|
TransactionTemplates = &TransactionTemplatesApi{
|
||||||
|
templates: services.TransactionTemplates,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// TemplateListHandler returns transaction template list of current user
|
||||||
|
func (a *TransactionTemplatesApi) TemplateListHandler(c *core.Context) (any, *errs.Error) {
|
||||||
|
var templateListReq models.TransactionTemplateListRequest
|
||||||
|
err := c.ShouldBindQuery(&templateListReq)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WarnfWithRequestId(c, "[transaction_templates.TemplateListHandler] parse request failed, because %s", err.Error())
|
||||||
|
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if templateListReq.TemplateType < models.TRANSACTION_TEMPLATE_TYPE_NORMAL || templateListReq.TemplateType > models.TRANSACTION_TEMPLATE_TYPE_NORMAL {
|
||||||
|
log.WarnfWithRequestId(c, "[transaction_templates.TemplateListHandler] template type invalid, type is %d", templateListReq.TemplateType)
|
||||||
|
return nil, errs.ErrTransactionTemplateTypeInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
uid := c.GetCurrentUid()
|
||||||
|
templates, err := a.templates.GetAllTemplatesByUid(c, uid, templateListReq.TemplateType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[transaction_templates.TemplateListHandler] failed to get templates for user \"uid:%d\", because %s", uid, err.Error())
|
||||||
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
templateResps := make(models.TransactionTemplateInfoResponseSlice, len(templates))
|
||||||
|
serverUtcOffset := utils.GetServerTimezoneOffsetMinutes()
|
||||||
|
|
||||||
|
for i := 0; i < len(templates); i++ {
|
||||||
|
templateResps[i] = templates[i].ToTransactionTemplateInfoResponse(serverUtcOffset)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(templateResps)
|
||||||
|
|
||||||
|
return templateResps, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TemplateGetHandler returns one specific transaction template of current user
|
||||||
|
func (a *TransactionTemplatesApi) TemplateGetHandler(c *core.Context) (any, *errs.Error) {
|
||||||
|
var templateGetReq models.TransactionTemplateGetRequest
|
||||||
|
err := c.ShouldBindQuery(&templateGetReq)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WarnfWithRequestId(c, "[transaction_templates.TemplateGetHandler] parse request failed, because %s", err.Error())
|
||||||
|
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
uid := c.GetCurrentUid()
|
||||||
|
template, err := a.templates.GetTemplateByTemplateId(c, uid, templateGetReq.Id)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[transaction_templates.TemplateGetHandler] failed to get template \"id:%d\" for user \"uid:%d\", because %s", templateGetReq.Id, uid, err.Error())
|
||||||
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
serverUtcOffset := utils.GetServerTimezoneOffsetMinutes()
|
||||||
|
templateResp := template.ToTransactionTemplateInfoResponse(serverUtcOffset)
|
||||||
|
|
||||||
|
return templateResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TemplateCreateHandler saves a new transaction template by request parameters for current user
|
||||||
|
func (a *TransactionTemplatesApi) TemplateCreateHandler(c *core.Context) (any, *errs.Error) {
|
||||||
|
var templateCreateReq models.TransactionTemplateCreateRequest
|
||||||
|
err := c.ShouldBindJSON(&templateCreateReq)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WarnfWithRequestId(c, "[transaction_templates.TemplateCreateHandler] parse request failed, because %s", err.Error())
|
||||||
|
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if templateCreateReq.TemplateType < models.TRANSACTION_TEMPLATE_TYPE_NORMAL || templateCreateReq.TemplateType > models.TRANSACTION_TEMPLATE_TYPE_NORMAL {
|
||||||
|
log.WarnfWithRequestId(c, "[transaction_templates.TemplateCreateHandler] template type invalid, type is %d", templateCreateReq.TemplateType)
|
||||||
|
return nil, errs.ErrTransactionTemplateTypeInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
if templateCreateReq.Type <= models.TRANSACTION_TYPE_MODIFY_BALANCE || templateCreateReq.Type > models.TRANSACTION_TYPE_TRANSFER {
|
||||||
|
log.WarnfWithRequestId(c, "[transaction_templates.TemplateCreateHandler] transaction type invalid, type is %d", templateCreateReq.Type)
|
||||||
|
return nil, errs.ErrTransactionTypeInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
uid := c.GetCurrentUid()
|
||||||
|
|
||||||
|
maxOrderId, err := a.templates.GetMaxDisplayOrder(c, uid, templateCreateReq.TemplateType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[transaction_templates.TemplateCreateHandler] failed to get max display order for user \"uid:%d\", because %s", uid, err.Error())
|
||||||
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
serverUtcOffset := utils.GetServerTimezoneOffsetMinutes()
|
||||||
|
template := a.createNewTemplateModel(uid, &templateCreateReq, maxOrderId+1)
|
||||||
|
|
||||||
|
if settings.Container.Current.EnableDuplicateSubmissionsCheck && templateCreateReq.ClientSessionId != "" {
|
||||||
|
found, remark := duplicatechecker.Container.Get(duplicatechecker.DUPLICATE_CHECKER_TYPE_NEW_TEMPLATE, uid, templateCreateReq.ClientSessionId)
|
||||||
|
|
||||||
|
if found {
|
||||||
|
log.InfofWithRequestId(c, "[transaction_templates.TemplateCreateHandler] another template \"id:%s\" has been created for user \"uid:%d\"", remark, uid)
|
||||||
|
templateId, err := utils.StringToInt64(remark)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
template, err = a.templates.GetTemplateByTemplateId(c, uid, templateId)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[transaction_templates.TemplateCreateHandler] failed to get existed template \"id:%d\" for user \"uid:%d\", because %s", templateId, uid, err.Error())
|
||||||
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
templateResp := template.ToTransactionTemplateInfoResponse(serverUtcOffset)
|
||||||
|
|
||||||
|
return templateResp, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.templates.CreateTemplate(c, template)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[transaction_templates.TemplateCreateHandler] failed to create template \"id:%d\" for user \"uid:%d\", because %s", template.TemplateId, uid, err.Error())
|
||||||
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.InfofWithRequestId(c, "[transaction_templates.TemplateCreateHandler] user \"uid:%d\" has created a new template \"id:%d\" successfully", uid, template.TemplateId)
|
||||||
|
|
||||||
|
duplicatechecker.Container.Set(duplicatechecker.DUPLICATE_CHECKER_TYPE_NEW_TEMPLATE, uid, templateCreateReq.ClientSessionId, utils.Int64ToString(template.TemplateId))
|
||||||
|
templateResp := template.ToTransactionTemplateInfoResponse(serverUtcOffset)
|
||||||
|
|
||||||
|
return templateResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TemplateModifyHandler saves an existed transaction template by request parameters for current user
|
||||||
|
func (a *TransactionTemplatesApi) TemplateModifyHandler(c *core.Context) (any, *errs.Error) {
|
||||||
|
var templateModifyReq models.TransactionTemplateModifyRequest
|
||||||
|
err := c.ShouldBindJSON(&templateModifyReq)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WarnfWithRequestId(c, "[transaction_templates.TemplateModifyHandler] parse request failed, because %s", err.Error())
|
||||||
|
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if templateModifyReq.Type <= models.TRANSACTION_TYPE_MODIFY_BALANCE || templateModifyReq.Type > models.TRANSACTION_TYPE_TRANSFER {
|
||||||
|
log.WarnfWithRequestId(c, "[transaction_templates.TemplateModifyHandler] transaction type invalid, type is %d", templateModifyReq.Type)
|
||||||
|
return nil, errs.ErrTransactionTypeInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
uid := c.GetCurrentUid()
|
||||||
|
template, err := a.templates.GetTemplateByTemplateId(c, uid, templateModifyReq.Id)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[transaction_templates.TemplateModifyHandler] failed to get template \"id:%d\" for user \"uid:%d\", because %s", templateModifyReq.Id, uid, err.Error())
|
||||||
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
newTemplate := &models.TransactionTemplate{
|
||||||
|
TemplateId: template.TemplateId,
|
||||||
|
Uid: uid,
|
||||||
|
Name: templateModifyReq.Name,
|
||||||
|
Type: templateModifyReq.Type,
|
||||||
|
CategoryId: templateModifyReq.CategoryId,
|
||||||
|
AccountId: templateModifyReq.SourceAccountId,
|
||||||
|
TagIds: strings.Join(templateModifyReq.TagIds, ","),
|
||||||
|
Amount: templateModifyReq.SourceAmount,
|
||||||
|
RelatedAccountId: templateModifyReq.DestinationAccountId,
|
||||||
|
RelatedAccountAmount: templateModifyReq.DestinationAmount,
|
||||||
|
HideAmount: templateModifyReq.HideAmount,
|
||||||
|
Comment: templateModifyReq.Comment,
|
||||||
|
}
|
||||||
|
|
||||||
|
if newTemplate.Name == template.Name &&
|
||||||
|
newTemplate.Type == template.Type &&
|
||||||
|
newTemplate.CategoryId == template.CategoryId &&
|
||||||
|
newTemplate.AccountId == template.AccountId &&
|
||||||
|
newTemplate.TagIds == template.TagIds &&
|
||||||
|
newTemplate.Amount == template.Amount &&
|
||||||
|
newTemplate.RelatedAccountId == template.RelatedAccountId &&
|
||||||
|
newTemplate.RelatedAccountAmount == template.RelatedAccountAmount &&
|
||||||
|
newTemplate.HideAmount == template.HideAmount &&
|
||||||
|
newTemplate.Comment == template.Comment {
|
||||||
|
return nil, errs.ErrNothingWillBeUpdated
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.templates.ModifyTemplate(c, newTemplate)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[transaction_templates.TemplateModifyHandler] failed to update template \"id:%d\" for user \"uid:%d\", because %s", templateModifyReq.Id, uid, err.Error())
|
||||||
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.InfofWithRequestId(c, "[transaction_templates.TemplateModifyHandler] user \"uid:%d\" has updated template \"id:%d\" successfully", uid, templateModifyReq.Id)
|
||||||
|
|
||||||
|
serverUtcOffset := utils.GetServerTimezoneOffsetMinutes()
|
||||||
|
newTemplate.TemplateType = template.TemplateType
|
||||||
|
newTemplate.DisplayOrder = template.DisplayOrder
|
||||||
|
newTemplate.Hidden = template.Hidden
|
||||||
|
templateResp := newTemplate.ToTransactionTemplateInfoResponse(serverUtcOffset)
|
||||||
|
|
||||||
|
return templateResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TemplateHideHandler hides an transaction template by request parameters for current user
|
||||||
|
func (a *TransactionTemplatesApi) TemplateHideHandler(c *core.Context) (any, *errs.Error) {
|
||||||
|
var templateHideReq models.TransactionTemplateHideRequest
|
||||||
|
err := c.ShouldBindJSON(&templateHideReq)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WarnfWithRequestId(c, "[transaction_templates.TemplateHideHandler] parse request failed, because %s", err.Error())
|
||||||
|
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
uid := c.GetCurrentUid()
|
||||||
|
err = a.templates.HideTemplate(c, uid, []int64{templateHideReq.Id}, templateHideReq.Hidden)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[transaction_templates.TemplateHideHandler] failed to hide template \"id:%d\" for user \"uid:%d\", because %s", templateHideReq.Id, uid, err.Error())
|
||||||
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.InfofWithRequestId(c, "[transaction_templates.TemplateHideHandler] user \"uid:%d\" has hidden template \"id:%d\"", uid, templateHideReq.Id)
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TemplateMoveHandler moves display order of existed transaction templates by request parameters for current user
|
||||||
|
func (a *TransactionTemplatesApi) TemplateMoveHandler(c *core.Context) (any, *errs.Error) {
|
||||||
|
var templateMoveReq models.TransactionTemplateMoveRequest
|
||||||
|
err := c.ShouldBindJSON(&templateMoveReq)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WarnfWithRequestId(c, "[transaction_templates.CategoryMoveHandler] parse request failed, because %s", err.Error())
|
||||||
|
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
uid := c.GetCurrentUid()
|
||||||
|
templates := make([]*models.TransactionTemplate, len(templateMoveReq.NewDisplayOrders))
|
||||||
|
|
||||||
|
for i := 0; i < len(templateMoveReq.NewDisplayOrders); i++ {
|
||||||
|
newDisplayOrder := templateMoveReq.NewDisplayOrders[i]
|
||||||
|
template := &models.TransactionTemplate{
|
||||||
|
Uid: uid,
|
||||||
|
TemplateId: newDisplayOrder.Id,
|
||||||
|
DisplayOrder: newDisplayOrder.DisplayOrder,
|
||||||
|
}
|
||||||
|
|
||||||
|
templates[i] = template
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.templates.ModifyTemplateDisplayOrders(c, uid, templates)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[transaction_templates.TemplateMoveHandler] failed to move templates for user \"uid:%d\", because %s", uid, err.Error())
|
||||||
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.InfofWithRequestId(c, "[transaction_templates.TemplateMoveHandler] user \"uid:%d\" has moved templates", uid)
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TemplateDeleteHandler deletes an existed transaction template by request parameters for current user
|
||||||
|
func (a *TransactionTemplatesApi) TemplateDeleteHandler(c *core.Context) (any, *errs.Error) {
|
||||||
|
var templateDeleteReq models.TransactionTemplateDeleteRequest
|
||||||
|
err := c.ShouldBindJSON(&templateDeleteReq)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WarnfWithRequestId(c, "[transaction_templates.TemplateDeleteHandler] parse request failed, because %s", err.Error())
|
||||||
|
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
uid := c.GetCurrentUid()
|
||||||
|
err = a.templates.DeleteTemplate(c, uid, templateDeleteReq.Id)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[transaction_templates.TemplateDeleteHandler] failed to delete template \"id:%d\" for user \"uid:%d\", because %s", templateDeleteReq.Id, uid, err.Error())
|
||||||
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.InfofWithRequestId(c, "[transaction_templates.TemplateDeleteHandler] user \"uid:%d\" has deleted template \"id:%d\"", uid, templateDeleteReq.Id)
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *TransactionTemplatesApi) createNewTemplateModel(uid int64, templateCreateReq *models.TransactionTemplateCreateRequest, order int32) *models.TransactionTemplate {
|
||||||
|
return &models.TransactionTemplate{
|
||||||
|
Uid: uid,
|
||||||
|
TemplateType: templateCreateReq.TemplateType,
|
||||||
|
Name: templateCreateReq.Name,
|
||||||
|
Type: templateCreateReq.Type,
|
||||||
|
CategoryId: templateCreateReq.CategoryId,
|
||||||
|
AccountId: templateCreateReq.SourceAccountId,
|
||||||
|
TagIds: strings.Join(templateCreateReq.TagIds, ","),
|
||||||
|
Amount: templateCreateReq.SourceAmount,
|
||||||
|
RelatedAccountId: templateCreateReq.DestinationAccountId,
|
||||||
|
RelatedAccountAmount: templateCreateReq.DestinationAmount,
|
||||||
|
HideAmount: templateCreateReq.HideAmount,
|
||||||
|
Comment: templateCreateReq.Comment,
|
||||||
|
DisplayOrder: order,
|
||||||
|
}
|
||||||
|
}
|
||||||
+236
-145
@@ -7,15 +7,15 @@ import (
|
|||||||
orderedmap "github.com/wk8/go-ordered-map/v2"
|
orderedmap "github.com/wk8/go-ordered-map/v2"
|
||||||
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/duplicatechecker"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/services"
|
"github.com/mayswind/ezbookkeeping/pkg/services"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
const pageCountForLoadTransactionAmounts = 1000
|
|
||||||
|
|
||||||
// TransactionsApi represents transaction api
|
// TransactionsApi represents transaction api
|
||||||
type TransactionsApi struct {
|
type TransactionsApi struct {
|
||||||
transactions *services.TransactionService
|
transactions *services.TransactionService
|
||||||
@@ -48,21 +48,33 @@ func (a *TransactionsApi) TransactionCountHandler(c *core.Context) (any, *errs.E
|
|||||||
|
|
||||||
uid := c.GetCurrentUid()
|
uid := c.GetCurrentUid()
|
||||||
|
|
||||||
allAccountIds, err := a.getAccountOrSubAccountIds(c, transactionCountReq.AccountId, uid)
|
allAccountIds, err := a.getAccountOrSubAccountIds(c, transactionCountReq.AccountIds, uid)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WarnfWithRequestId(c, "[transactions.TransactionCountHandler] get account error, because %s", err.Error())
|
log.WarnfWithRequestId(c, "[transactions.TransactionCountHandler] get account error, because %s", err.Error())
|
||||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
allCategoryIds, err := a.getCategoryOrSubCategoryIds(c, transactionCountReq.CategoryId, uid)
|
allCategoryIds, err := a.getCategoryOrSubCategoryIds(c, transactionCountReq.CategoryIds, uid)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WarnfWithRequestId(c, "[transactions.TransactionCountHandler] get transaction category error, because %s", err.Error())
|
log.WarnfWithRequestId(c, "[transactions.TransactionCountHandler] get transaction category error, because %s", err.Error())
|
||||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
totalCount, err := a.transactions.GetTransactionCount(c, uid, transactionCountReq.MaxTime, transactionCountReq.MinTime, transactionCountReq.Type, allCategoryIds, allAccountIds, transactionCountReq.Keyword)
|
var allTagIds []int64
|
||||||
|
noTags := transactionCountReq.TagIds == "none"
|
||||||
|
|
||||||
|
if !noTags {
|
||||||
|
allTagIds, err = a.getTagIds(transactionCountReq.TagIds)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WarnfWithRequestId(c, "[transactions.TransactionCountHandler] get transaction tag ids error, because %s", err.Error())
|
||||||
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
totalCount, err := a.transactions.GetTransactionCount(c, uid, transactionCountReq.MaxTime, transactionCountReq.MinTime, transactionCountReq.Type, allCategoryIds, allAccountIds, allTagIds, noTags, transactionCountReq.AmountFilter, transactionCountReq.Keyword)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorfWithRequestId(c, "[transactions.TransactionCountHandler] failed to get transaction count for user \"uid:%d\", because %s", uid, err.Error())
|
log.ErrorfWithRequestId(c, "[transactions.TransactionCountHandler] failed to get transaction count for user \"uid:%d\", because %s", uid, err.Error())
|
||||||
@@ -104,24 +116,36 @@ func (a *TransactionsApi) TransactionListHandler(c *core.Context) (any, *errs.Er
|
|||||||
return nil, errs.ErrUserNotFound
|
return nil, errs.ErrUserNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
allAccountIds, err := a.getAccountOrSubAccountIds(c, transactionListReq.AccountId, uid)
|
allAccountIds, err := a.getAccountOrSubAccountIds(c, transactionListReq.AccountIds, uid)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WarnfWithRequestId(c, "[transactions.TransactionListHandler] get account error, because %s", err.Error())
|
log.WarnfWithRequestId(c, "[transactions.TransactionListHandler] get account error, because %s", err.Error())
|
||||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
allCategoryIds, err := a.getCategoryOrSubCategoryIds(c, transactionListReq.CategoryId, uid)
|
allCategoryIds, err := a.getCategoryOrSubCategoryIds(c, transactionListReq.CategoryIds, uid)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WarnfWithRequestId(c, "[transactions.TransactionListHandler] get transaction category error, because %s", err.Error())
|
log.WarnfWithRequestId(c, "[transactions.TransactionListHandler] get transaction category error, because %s", err.Error())
|
||||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var allTagIds []int64
|
||||||
|
noTags := transactionListReq.TagIds == "none"
|
||||||
|
|
||||||
|
if !noTags {
|
||||||
|
allTagIds, err = a.getTagIds(transactionListReq.TagIds)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WarnfWithRequestId(c, "[transactions.TransactionListHandler] get transaction tag ids error, because %s", err.Error())
|
||||||
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var totalCount int64
|
var totalCount int64
|
||||||
|
|
||||||
if transactionListReq.WithCount {
|
if transactionListReq.WithCount {
|
||||||
totalCount, err = a.transactions.GetTransactionCount(c, uid, transactionListReq.MaxTime, transactionListReq.MinTime, transactionListReq.Type, allCategoryIds, allAccountIds, transactionListReq.Keyword)
|
totalCount, err = a.transactions.GetTransactionCount(c, uid, transactionListReq.MaxTime, transactionListReq.MinTime, transactionListReq.Type, allCategoryIds, allAccountIds, allTagIds, noTags, transactionListReq.AmountFilter, transactionListReq.Keyword)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorfWithRequestId(c, "[transactions.TransactionListHandler] failed to get transaction count for user \"uid:%d\", because %s", uid, err.Error())
|
log.ErrorfWithRequestId(c, "[transactions.TransactionListHandler] failed to get transaction count for user \"uid:%d\", because %s", uid, err.Error())
|
||||||
@@ -129,7 +153,7 @@ func (a *TransactionsApi) TransactionListHandler(c *core.Context) (any, *errs.Er
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
transactions, err := a.transactions.GetTransactionsByMaxTime(c, uid, transactionListReq.MaxTime, transactionListReq.MinTime, transactionListReq.Type, allCategoryIds, allAccountIds, transactionListReq.Keyword, transactionListReq.Page, transactionListReq.Count, true, true)
|
transactions, err := a.transactions.GetTransactionsByMaxTime(c, uid, transactionListReq.MaxTime, transactionListReq.MinTime, transactionListReq.Type, allCategoryIds, allAccountIds, allTagIds, noTags, transactionListReq.AmountFilter, transactionListReq.Keyword, transactionListReq.Page, transactionListReq.Count, true, true)
|
||||||
|
|
||||||
if err != nil {
|
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())
|
log.ErrorfWithRequestId(c, "[transactions.TransactionListHandler] failed to get transactions earlier than \"%d\" for user \"uid:%d\", because %s", transactionListReq.MaxTime, uid, err.Error())
|
||||||
@@ -195,21 +219,33 @@ func (a *TransactionsApi) TransactionMonthListHandler(c *core.Context) (any, *er
|
|||||||
return nil, errs.ErrUserNotFound
|
return nil, errs.ErrUserNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
allAccountIds, err := a.getAccountOrSubAccountIds(c, transactionListReq.AccountId, uid)
|
allAccountIds, err := a.getAccountOrSubAccountIds(c, transactionListReq.AccountIds, uid)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WarnfWithRequestId(c, "[transactions.TransactionMonthListHandler] get account error, because %s", err.Error())
|
log.WarnfWithRequestId(c, "[transactions.TransactionMonthListHandler] get account error, because %s", err.Error())
|
||||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
allCategoryIds, err := a.getCategoryOrSubCategoryIds(c, transactionListReq.CategoryId, uid)
|
allCategoryIds, err := a.getCategoryOrSubCategoryIds(c, transactionListReq.CategoryIds, uid)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WarnfWithRequestId(c, "[transactions.TransactionMonthListHandler] get transaction category error, because %s", err.Error())
|
log.WarnfWithRequestId(c, "[transactions.TransactionMonthListHandler] get transaction category error, because %s", err.Error())
|
||||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
transactions, err := a.transactions.GetTransactionsInMonthByPage(c, uid, transactionListReq.Year, transactionListReq.Month, transactionListReq.Type, allCategoryIds, allAccountIds, transactionListReq.Keyword)
|
var allTagIds []int64
|
||||||
|
noTags := transactionListReq.TagIds == "none"
|
||||||
|
|
||||||
|
if !noTags {
|
||||||
|
allTagIds, err = a.getTagIds(transactionListReq.TagIds)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WarnfWithRequestId(c, "[transactions.TransactionMonthListHandler] get transaction tag ids error, because %s", err.Error())
|
||||||
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transactions, err := a.transactions.GetTransactionsInMonthByPage(c, uid, transactionListReq.Year, transactionListReq.Month, transactionListReq.Type, allCategoryIds, allAccountIds, allTagIds, noTags, transactionListReq.AmountFilter, transactionListReq.Keyword)
|
||||||
|
|
||||||
if err != nil {
|
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())
|
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())
|
||||||
@@ -241,8 +277,15 @@ func (a *TransactionsApi) TransactionStatisticsHandler(c *core.Context) (any, *e
|
|||||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
utcOffset, err := c.GetClientTimezoneOffset()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WarnfWithRequestId(c, "[transactions.TransactionStatisticsHandler] cannot get client timezone offset, because %s", err.Error())
|
||||||
|
return nil, errs.ErrClientTimezoneOffsetInvalid
|
||||||
|
}
|
||||||
|
|
||||||
uid := c.GetCurrentUid()
|
uid := c.GetCurrentUid()
|
||||||
totalAmounts, err := a.transactions.GetAccountsAndCategoriesTotalIncomeAndExpense(c, uid, statisticReq.StartTime, statisticReq.EndTime)
|
totalAmounts, err := a.transactions.GetAccountsAndCategoriesTotalIncomeAndExpense(c, uid, statisticReq.StartTime, statisticReq.EndTime, utcOffset, statisticReq.UseTransactionTimezone)
|
||||||
|
|
||||||
if err != nil {
|
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())
|
log.ErrorfWithRequestId(c, "[transactions.TransactionStatisticsHandler] failed to get accounts and categories total income and expense for user \"uid:%d\", because %s", uid, err.Error())
|
||||||
@@ -268,6 +311,64 @@ func (a *TransactionsApi) TransactionStatisticsHandler(c *core.Context) (any, *e
|
|||||||
return statisticResp, nil
|
return statisticResp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TransactionStatisticsTrendsHandler returns transaction statistics trends of current user
|
||||||
|
func (a *TransactionsApi) TransactionStatisticsTrendsHandler(c *core.Context) (any, *errs.Error) {
|
||||||
|
var statisticTrendsReq models.TransactionStatisticTrendsRequest
|
||||||
|
err := c.ShouldBindQuery(&statisticTrendsReq)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WarnfWithRequestId(c, "[transactions.TransactionStatisticsTrendsHandler] parse request failed, because %s", err.Error())
|
||||||
|
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
utcOffset, err := c.GetClientTimezoneOffset()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WarnfWithRequestId(c, "[transactions.TransactionStatisticsTrendsHandler] cannot get client timezone offset, because %s", err.Error())
|
||||||
|
return nil, errs.ErrClientTimezoneOffsetInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
startYear, startMonth, endYear, endMonth, err := statisticTrendsReq.GetNumericYearMonthRange()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WarnfWithRequestId(c, "[transactions.TransactionStatisticsTrendsHandler] cannot parse year month, because %s", err.Error())
|
||||||
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
uid := c.GetCurrentUid()
|
||||||
|
allMonthlyTotalAmounts, err := a.transactions.GetAccountsAndCategoriesMonthlyIncomeAndExpense(c, uid, startYear, startMonth, endYear, endMonth, utcOffset, statisticTrendsReq.UseTransactionTimezone)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[transactions.TransactionStatisticsTrendsHandler] 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
statisticTrendsResp := make(models.TransactionStatisticTrendsItemSlice, 0, len(allMonthlyTotalAmounts))
|
||||||
|
|
||||||
|
for yearMonth, monthlyTotalAmounts := range allMonthlyTotalAmounts {
|
||||||
|
monthlyStatisticResp := &models.TransactionStatisticTrendsItem{
|
||||||
|
Year: yearMonth / 100,
|
||||||
|
Month: yearMonth % 100,
|
||||||
|
Items: make([]*models.TransactionStatisticResponseItem, len(monthlyTotalAmounts)),
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(monthlyTotalAmounts); i++ {
|
||||||
|
totalAmountItem := monthlyTotalAmounts[i]
|
||||||
|
monthlyStatisticResp.Items[i] = &models.TransactionStatisticResponseItem{
|
||||||
|
CategoryId: totalAmountItem.CategoryId,
|
||||||
|
AccountId: totalAmountItem.AccountId,
|
||||||
|
TotalAmount: totalAmountItem.Amount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
statisticTrendsResp = append(statisticTrendsResp, monthlyStatisticResp)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(statisticTrendsResp)
|
||||||
|
|
||||||
|
return statisticTrendsResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
// TransactionAmountsHandler returns transaction amounts of current user
|
// TransactionAmountsHandler returns transaction amounts of current user
|
||||||
func (a *TransactionsApi) TransactionAmountsHandler(c *core.Context) (any, *errs.Error) {
|
func (a *TransactionsApi) TransactionAmountsHandler(c *core.Context) (any, *errs.Error) {
|
||||||
var transactionAmountsReq models.TransactionAmountsRequest
|
var transactionAmountsReq models.TransactionAmountsRequest
|
||||||
@@ -295,6 +396,13 @@ func (a *TransactionsApi) TransactionAmountsHandler(c *core.Context) (any, *errs
|
|||||||
return nil, errs.ErrQueryItemsTooMuch
|
return nil, errs.ErrQueryItemsTooMuch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
utcOffset, err := c.GetClientTimezoneOffset()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WarnfWithRequestId(c, "[transactions.TransactionAmountsHandler] cannot get client timezone offset, because %s", err.Error())
|
||||||
|
return nil, errs.ErrClientTimezoneOffsetInvalid
|
||||||
|
}
|
||||||
|
|
||||||
uid := c.GetCurrentUid()
|
uid := c.GetCurrentUid()
|
||||||
|
|
||||||
accounts, err := a.accounts.GetAllAccountsByUid(c, uid)
|
accounts, err := a.accounts.GetAllAccountsByUid(c, uid)
|
||||||
@@ -310,7 +418,7 @@ func (a *TransactionsApi) TransactionAmountsHandler(c *core.Context) (any, *errs
|
|||||||
for i := 0; i < len(requestItems); i++ {
|
for i := 0; i < len(requestItems); i++ {
|
||||||
requestItem := requestItems[i]
|
requestItem := requestItems[i]
|
||||||
|
|
||||||
incomeAmounts, expenseAmounts, err := a.transactions.GetAccountsTotalIncomeAndExpense(c, uid, requestItem.StartTime, requestItem.EndTime)
|
incomeAmounts, expenseAmounts, err := a.transactions.GetAccountsTotalIncomeAndExpense(c, uid, requestItem.StartTime, requestItem.EndTime, utcOffset, transactionAmountsReq.UseTransactionTimezone)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorfWithRequestId(c, "[transactions.TransactionAmountsHandler] failed to get transaction amounts item for user \"uid:%d\", because %s", uid, err.Error())
|
log.ErrorfWithRequestId(c, "[transactions.TransactionAmountsHandler] failed to get transaction amounts item for user \"uid:%d\", because %s", uid, err.Error())
|
||||||
@@ -381,120 +489,6 @@ func (a *TransactionsApi) TransactionAmountsHandler(c *core.Context) (any, *errs
|
|||||||
return amountsResp, nil
|
return amountsResp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransactionMonthAmountsHandler returns every month transaction amounts of current user
|
|
||||||
func (a *TransactionsApi) TransactionMonthAmountsHandler(c *core.Context) (any, *errs.Error) {
|
|
||||||
var transactionAmountsReq models.TransactionMonthAmountsRequest
|
|
||||||
err := c.ShouldBindQuery(&transactionAmountsReq)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.WarnfWithRequestId(c, "[transactions.TransactionMonthAmountsHandler] parse request failed, because %s", err.Error())
|
|
||||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
utcOffset, err := c.GetClientTimezoneOffset()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.WarnfWithRequestId(c, "[transactions.TransactionMonthAmountsHandler] cannot get client timezone offset, because %s", err.Error())
|
|
||||||
return nil, errs.ErrClientTimezoneOffsetInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
startTime, endTime, err := transactionAmountsReq.GetStartTimeAndEndTime(utcOffset)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.WarnfWithRequestId(c, "[transactions.TransactionMonthAmountsHandler] parse request start or end date failed, because %s", err.Error())
|
|
||||||
return nil, errs.ErrParameterInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
uid := c.GetCurrentUid()
|
|
||||||
|
|
||||||
accounts, err := a.accounts.GetAllAccountsByUid(c, uid)
|
|
||||||
accountMap := a.accounts.GetAccountMapByList(accounts)
|
|
||||||
|
|
||||||
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.Or(err, errs.ErrOperationFailed)
|
|
||||||
}
|
|
||||||
|
|
||||||
totalAmounts, err := a.transactions.GetAccountsMonthTotalIncomeAndExpense(c, 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 {
|
|
||||||
for accountId, monthAccountAmounts := range monthAccountsAmounts {
|
|
||||||
account, exists := accountMap[accountId]
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
log.WarnfWithRequestId(c, "[transactions.TransactionMonthAmountsHandler] cannot find account for account \"id:%d\" of user \"uid:%d\"", accountId, uid)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
monthTotalAmounts, exists := amountsMap[yearMonth]
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
monthTotalAmounts = make(map[string]*models.TransactionAmountsResponseItemAmountInfo)
|
|
||||||
amountsMap[yearMonth] = monthTotalAmounts
|
|
||||||
}
|
|
||||||
|
|
||||||
monthTotalAmount, exists := monthTotalAmounts[account.Currency]
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
monthTotalAmount = &models.TransactionAmountsResponseItemAmountInfo{
|
|
||||||
Currency: account.Currency,
|
|
||||||
IncomeAmount: 0,
|
|
||||||
ExpenseAmount: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
monthTotalAmount.IncomeAmount += monthAccountAmounts.TotalIncomeAmount
|
|
||||||
monthTotalAmount.ExpenseAmount += monthAccountAmounts.TotalExpenseAmount
|
|
||||||
|
|
||||||
monthTotalAmounts[account.Currency] = monthTotalAmount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
amountsResp := make(models.TransactionMonthAmountsResponseItemSlice, 0)
|
|
||||||
|
|
||||||
for yearMonth, monthTotalAmounts := range amountsMap {
|
|
||||||
yearMonthItems := strings.Split(yearMonth, "-")
|
|
||||||
year, err := utils.StringToInt32(yearMonthItems[0])
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.WarnfWithRequestId(c, "[transactions.TransactionMonthAmountsHandler] cannot get year from year-month item \"%s\" for user \"uid:%d\"", yearMonth, uid)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
month, err := utils.StringToInt32(yearMonthItems[1])
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.WarnfWithRequestId(c, "[transactions.TransactionMonthAmountsHandler] cannot get month from year-month item \"%s\" for user \"uid:%d\"", yearMonth, uid)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
amounts := make(models.TransactionAmountsResponseItemAmountInfoSlice, 0, len(monthTotalAmounts))
|
|
||||||
|
|
||||||
for _, monthTotalAmount := range monthTotalAmounts {
|
|
||||||
amounts = append(amounts, monthTotalAmount)
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(amounts)
|
|
||||||
|
|
||||||
amountsResp = append(amountsResp, &models.TransactionMonthAmountsResponseItem{
|
|
||||||
Year: year,
|
|
||||||
Month: month,
|
|
||||||
Amounts: amounts,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(amountsResp)
|
|
||||||
|
|
||||||
return amountsResp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TransactionGetHandler returns one specific transaction of current user
|
// TransactionGetHandler returns one specific transaction of current user
|
||||||
func (a *TransactionsApi) TransactionGetHandler(c *core.Context) (any, *errs.Error) {
|
func (a *TransactionsApi) TransactionGetHandler(c *core.Context) (any, *errs.Error) {
|
||||||
var transactionGetReq models.TransactionGetRequest
|
var transactionGetReq models.TransactionGetRequest
|
||||||
@@ -669,6 +663,28 @@ func (a *TransactionsApi) TransactionCreateHandler(c *core.Context) (any, *errs.
|
|||||||
return nil, errs.ErrCannotCreateTransactionWithThisTransactionTime
|
return nil, errs.ErrCannotCreateTransactionWithThisTransactionTime
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if settings.Container.Current.EnableDuplicateSubmissionsCheck && transactionCreateReq.ClientSessionId != "" {
|
||||||
|
found, remark := duplicatechecker.Container.Get(duplicatechecker.DUPLICATE_CHECKER_TYPE_NEW_TRANSACTION, uid, transactionCreateReq.ClientSessionId)
|
||||||
|
|
||||||
|
if found {
|
||||||
|
log.InfofWithRequestId(c, "[transactions.TransactionCreateHandler] another transaction \"id:%s\" has been created for user \"uid:%d\"", remark, uid)
|
||||||
|
transactionId, err := utils.StringToInt64(remark)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
transaction, err = a.transactions.GetTransactionByTransactionId(c, uid, transactionId)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[transactions.TransactionCreateHandler] failed to get existed transaction \"id:%d\" for user \"uid:%d\", because %s", transactionId, uid, err.Error())
|
||||||
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
transactionResp := transaction.ToTransactionInfoResponse(tagIds, transactionEditable)
|
||||||
|
|
||||||
|
return transactionResp, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err = a.transactions.CreateTransaction(c, transaction, tagIds)
|
err = a.transactions.CreateTransaction(c, transaction, tagIds)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -678,6 +694,7 @@ func (a *TransactionsApi) TransactionCreateHandler(c *core.Context) (any, *errs.
|
|||||||
|
|
||||||
log.InfofWithRequestId(c, "[transactions.TransactionCreateHandler] user \"uid:%d\" has created a new transaction \"id:%d\" successfully", uid, transaction.TransactionId)
|
log.InfofWithRequestId(c, "[transactions.TransactionCreateHandler] user \"uid:%d\" has created a new transaction \"id:%d\" successfully", uid, transaction.TransactionId)
|
||||||
|
|
||||||
|
duplicatechecker.Container.Set(duplicatechecker.DUPLICATE_CHECKER_TYPE_NEW_TRANSACTION, uid, transactionCreateReq.ClientSessionId, utils.Int64ToString(transaction.TransactionId))
|
||||||
transactionResp := transaction.ToTransactionInfoResponse(tagIds, transactionEditable)
|
transactionResp := transaction.ToTransactionInfoResponse(tagIds, transactionEditable)
|
||||||
|
|
||||||
return transactionResp, nil
|
return transactionResp, nil
|
||||||
@@ -788,7 +805,7 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.Context) (any, *errs.
|
|||||||
return nil, errs.ErrCannotModifyTransactionWithThisTransactionTime
|
return nil, errs.ErrCannotModifyTransactionWithThisTransactionTime
|
||||||
}
|
}
|
||||||
|
|
||||||
err = a.transactions.ModifyTransaction(c, newTransaction, addTransactionTagIds, removeTransactionTagIds)
|
err = a.transactions.ModifyTransaction(c, newTransaction, len(transactionTagIds), addTransactionTagIds, removeTransactionTagIds)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorfWithRequestId(c, "[transactions.TransactionModifyHandler] failed to update transaction \"id:%d\" for user \"uid:%d\", because %s", transactionModifyReq.Id, uid, err.Error())
|
log.ErrorfWithRequestId(c, "[transactions.TransactionModifyHandler] failed to update transaction \"id:%d\" for user \"uid:%d\", because %s", transactionModifyReq.Id, uid, err.Error())
|
||||||
@@ -884,50 +901,124 @@ func (a *TransactionsApi) filterTransactions(c *core.Context, uid int64, transac
|
|||||||
return finalTransactions
|
return finalTransactions
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *TransactionsApi) getAccountOrSubAccountIds(c *core.Context, accountId int64, uid int64) ([]int64, error) {
|
func (a *TransactionsApi) getAccountOrSubAccountIds(c *core.Context, accountIds string, uid int64) ([]int64, error) {
|
||||||
|
if accountIds == "" || accountIds == "0" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
requestAccountIds, err := utils.StringArrayToInt64Array(strings.Split(accountIds, ","))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errs.Or(err, errs.ErrAccountIdInvalid)
|
||||||
|
}
|
||||||
|
|
||||||
var allAccountIds []int64
|
var allAccountIds []int64
|
||||||
|
|
||||||
if accountId > 0 {
|
if len(requestAccountIds) > 0 {
|
||||||
allSubAccounts, err := a.accounts.GetSubAccountsByAccountId(c, uid, accountId)
|
allSubAccounts, err := a.accounts.GetSubAccountsByAccountIds(c, uid, requestAccountIds)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(allSubAccounts) > 0 {
|
accountIdsMap := make(map[int64]int32, len(requestAccountIds))
|
||||||
for i := 0; i < len(allSubAccounts); i++ {
|
|
||||||
allAccountIds = append(allAccountIds, allSubAccounts[i].AccountId)
|
for i := 0; i < len(requestAccountIds); i++ {
|
||||||
|
accountIdsMap[requestAccountIds[i]] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(allSubAccounts); i++ {
|
||||||
|
subAccount := allSubAccounts[i]
|
||||||
|
|
||||||
|
if refCount, exists := accountIdsMap[subAccount.ParentAccountId]; exists {
|
||||||
|
accountIdsMap[subAccount.ParentAccountId] = refCount + 1
|
||||||
|
} else {
|
||||||
|
accountIdsMap[subAccount.ParentAccountId] = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, exists := accountIdsMap[subAccount.AccountId]; exists {
|
||||||
|
delete(accountIdsMap, subAccount.AccountId)
|
||||||
|
}
|
||||||
|
|
||||||
|
allAccountIds = append(allAccountIds, subAccount.AccountId)
|
||||||
|
}
|
||||||
|
|
||||||
|
for accountId, refCount := range accountIdsMap {
|
||||||
|
if refCount < 1 {
|
||||||
|
allAccountIds = append(allAccountIds, accountId)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
allAccountIds = append(allAccountIds, accountId)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return allAccountIds, nil
|
return allAccountIds, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *TransactionsApi) getCategoryOrSubCategoryIds(c *core.Context, categoryId int64, uid int64) ([]int64, error) {
|
func (a *TransactionsApi) getCategoryOrSubCategoryIds(c *core.Context, categoryIds string, uid int64) ([]int64, error) {
|
||||||
|
if categoryIds == "" || categoryIds == "0" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
requestCategoryIds, err := utils.StringArrayToInt64Array(strings.Split(categoryIds, ","))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errs.Or(err, errs.ErrTransactionCategoryIdInvalid)
|
||||||
|
}
|
||||||
|
|
||||||
var allCategoryIds []int64
|
var allCategoryIds []int64
|
||||||
|
|
||||||
if categoryId > 0 {
|
if len(requestCategoryIds) > 0 {
|
||||||
allSubCategories, err := a.transactionCategories.GetAllCategoriesByUid(c, uid, 0, categoryId)
|
allSubCategories, err := a.transactionCategories.GetSubCategoriesByCategoryIds(c, uid, requestCategoryIds)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(allSubCategories) > 0 {
|
categoryIdsMap := make(map[int64]int32, len(requestCategoryIds))
|
||||||
for i := 0; i < len(allSubCategories); i++ {
|
|
||||||
allCategoryIds = append(allCategoryIds, allSubCategories[i].CategoryId)
|
for i := 0; i < len(requestCategoryIds); i++ {
|
||||||
|
categoryIdsMap[requestCategoryIds[i]] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(allSubCategories); i++ {
|
||||||
|
subCategory := allSubCategories[i]
|
||||||
|
|
||||||
|
if refCount, exists := categoryIdsMap[subCategory.ParentCategoryId]; exists {
|
||||||
|
categoryIdsMap[subCategory.ParentCategoryId] = refCount + 1
|
||||||
|
} else {
|
||||||
|
categoryIdsMap[subCategory.ParentCategoryId] = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, exists := categoryIdsMap[subCategory.CategoryId]; exists {
|
||||||
|
delete(categoryIdsMap, subCategory.CategoryId)
|
||||||
|
}
|
||||||
|
|
||||||
|
allCategoryIds = append(allCategoryIds, subCategory.CategoryId)
|
||||||
|
}
|
||||||
|
|
||||||
|
for accountId, refCount := range categoryIdsMap {
|
||||||
|
if refCount < 1 {
|
||||||
|
allCategoryIds = append(allCategoryIds, accountId)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
allCategoryIds = append(allCategoryIds, categoryId)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return allCategoryIds, nil
|
return allCategoryIds, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *TransactionsApi) getTagIds(tagIds string) ([]int64, error) {
|
||||||
|
if tagIds == "" || tagIds == "0" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
requestTagIds, err := utils.StringArrayToInt64Array(strings.Split(tagIds, ","))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errs.Or(err, errs.ErrTransactionTagIdInvalid)
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestTagIds, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (a *TransactionsApi) getTransactionTagIds(allTransactionTagIds map[int64][]int64) []int64 {
|
func (a *TransactionsApi) getTransactionTagIds(allTransactionTagIds map[int64][]int64) []int64 {
|
||||||
allTagIds := make([]int64, 0, len(allTransactionTagIds))
|
allTagIds := make([]int64, 0, len(allTransactionTagIds))
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorStatusHandler(c *core.Context) (an
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorfWithRequestId(c, "[twofactor_authorizations.TwoFactorStatusHandler] failed to get two factor setting, because %s", err.Error())
|
log.ErrorfWithRequestId(c, "[twofactor_authorizations.TwoFactorStatusHandler] failed to get two-factor setting, because %s", err.Error())
|
||||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorEnableRequestHandler(c *core.Conte
|
|||||||
enabled, err := a.twoFactorAuthorizations.ExistsTwoFactorSetting(c, uid)
|
enabled, err := a.twoFactorAuthorizations.ExistsTwoFactorSetting(c, uid)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorfWithRequestId(c, "[twofactor_authorizations.TwoFactorEnableRequestHandler] failed to check two factor setting, because %s", err.Error())
|
log.ErrorfWithRequestId(c, "[twofactor_authorizations.TwoFactorEnableRequestHandler] failed to check two-factor setting, because %s", err.Error())
|
||||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,14 +84,14 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorEnableRequestHandler(c *core.Conte
|
|||||||
key, err := a.twoFactorAuthorizations.GenerateTwoFactorSecret(c, user)
|
key, err := a.twoFactorAuthorizations.GenerateTwoFactorSecret(c, user)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorfWithRequestId(c, "[twofactor_authorizations.TwoFactorEnableRequestHandler] failed to generate two factor secret, because %s", err.Error())
|
log.ErrorfWithRequestId(c, "[twofactor_authorizations.TwoFactorEnableRequestHandler] failed to generate two-factor secret, because %s", err.Error())
|
||||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
img, err := key.Image(240, 240)
|
img, err := key.Image(240, 240)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorfWithRequestId(c, "[twofactor_authorizations.TwoFactorEnableRequestHandler] failed to generate two factor qrcode, because %s", err.Error())
|
log.ErrorfWithRequestId(c, "[twofactor_authorizations.TwoFactorEnableRequestHandler] failed to generate two-factor qrcode, because %s", err.Error())
|
||||||
return nil, errs.ErrOperationFailed
|
return nil, errs.ErrOperationFailed
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +123,7 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorEnableConfirmHandler(c *core.Conte
|
|||||||
exists, err := a.twoFactorAuthorizations.ExistsTwoFactorSetting(c, uid)
|
exists, err := a.twoFactorAuthorizations.ExistsTwoFactorSetting(c, uid)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorfWithRequestId(c, "[twofactor_authorizations.TwoFactorEnableConfirmHandler] failed to check two factor setting, because %s", err.Error())
|
log.ErrorfWithRequestId(c, "[twofactor_authorizations.TwoFactorEnableConfirmHandler] failed to check two-factor setting, because %s", err.Error())
|
||||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,25 +154,25 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorEnableConfirmHandler(c *core.Conte
|
|||||||
recoveryCodes, err := a.twoFactorAuthorizations.GenerateTwoFactorRecoveryCodes()
|
recoveryCodes, err := a.twoFactorAuthorizations.GenerateTwoFactorRecoveryCodes()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorfWithRequestId(c, "[twofactor_authorizations.TwoFactorEnableConfirmHandler] failed to generate two factor recovery codes for user \"uid:%d\", because %s", uid, err.Error())
|
log.ErrorfWithRequestId(c, "[twofactor_authorizations.TwoFactorEnableConfirmHandler] failed to generate two-factor recovery codes for user \"uid:%d\", because %s", uid, err.Error())
|
||||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = a.twoFactorAuthorizations.CreateTwoFactorRecoveryCodes(c, uid, recoveryCodes, user.Salt)
|
err = a.twoFactorAuthorizations.CreateTwoFactorRecoveryCodes(c, uid, recoveryCodes, user.Salt)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorfWithRequestId(c, "[twofactor_authorizations.TwoFactorEnableConfirmHandler] failed to create two factor recovery codes for user \"uid:%d\", because %s", uid, err.Error())
|
log.ErrorfWithRequestId(c, "[twofactor_authorizations.TwoFactorEnableConfirmHandler] failed to create two-factor recovery codes for user \"uid:%d\", because %s", uid, err.Error())
|
||||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = a.twoFactorAuthorizations.CreateTwoFactorSetting(c, twoFactorSetting)
|
err = a.twoFactorAuthorizations.CreateTwoFactorSetting(c, twoFactorSetting)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorfWithRequestId(c, "[twofactor_authorizations.TwoFactorEnableConfirmHandler] failed to create two factor setting for user \"uid:%d\", because %s", uid, err.Error())
|
log.ErrorfWithRequestId(c, "[twofactor_authorizations.TwoFactorEnableConfirmHandler] failed to create two-factor setting for user \"uid:%d\", because %s", uid, err.Error())
|
||||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.InfofWithRequestId(c, "[twofactor_authorizations.TwoFactorEnableConfirmHandler] user \"uid:%d\" has enabled two factor authorization", uid)
|
log.InfofWithRequestId(c, "[twofactor_authorizations.TwoFactorEnableConfirmHandler] user \"uid:%d\" has enabled two-factor authorization", uid)
|
||||||
|
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
err = a.tokens.DeleteTokensBeforeTime(c, uid, now)
|
err = a.tokens.DeleteTokensBeforeTime(c, uid, now)
|
||||||
@@ -236,7 +236,7 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorDisableHandler(c *core.Context) (a
|
|||||||
enableTwoFactor, err := a.twoFactorAuthorizations.ExistsTwoFactorSetting(c, uid)
|
enableTwoFactor, err := a.twoFactorAuthorizations.ExistsTwoFactorSetting(c, uid)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorfWithRequestId(c, "[twofactor_authorizations.TwoFactorDisableHandler] failed to check two factor setting, because %s", err.Error())
|
log.ErrorfWithRequestId(c, "[twofactor_authorizations.TwoFactorDisableHandler] failed to check two-factor setting, because %s", err.Error())
|
||||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,18 +247,18 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorDisableHandler(c *core.Context) (a
|
|||||||
err = a.twoFactorAuthorizations.DeleteTwoFactorRecoveryCodes(c, uid)
|
err = a.twoFactorAuthorizations.DeleteTwoFactorRecoveryCodes(c, uid)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorfWithRequestId(c, "[twofactor_authorizations.TwoFactorDisableHandler] failed to delete two factor recovery codes for user \"uid:%d\"", uid)
|
log.ErrorfWithRequestId(c, "[twofactor_authorizations.TwoFactorDisableHandler] failed to delete two-factor recovery codes for user \"uid:%d\"", uid)
|
||||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = a.twoFactorAuthorizations.DeleteTwoFactorSetting(c, uid)
|
err = a.twoFactorAuthorizations.DeleteTwoFactorSetting(c, uid)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorfWithRequestId(c, "[twofactor_authorizations.TwoFactorDisableHandler] failed to delete two factor setting for user \"uid:%d\"", uid)
|
log.ErrorfWithRequestId(c, "[twofactor_authorizations.TwoFactorDisableHandler] failed to delete two-factor setting for user \"uid:%d\"", uid)
|
||||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.InfofWithRequestId(c, "[twofactor_authorizations.TwoFactorDisableHandler] user \"uid:%d\" has disabled two factor authorization", uid)
|
log.InfofWithRequestId(c, "[twofactor_authorizations.TwoFactorDisableHandler] user \"uid:%d\" has disabled two-factor authorization", uid)
|
||||||
|
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
@@ -291,7 +291,7 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorRecoveryCodeRegenerateHandler(c *c
|
|||||||
enableTwoFactor, err := a.twoFactorAuthorizations.ExistsTwoFactorSetting(c, uid)
|
enableTwoFactor, err := a.twoFactorAuthorizations.ExistsTwoFactorSetting(c, uid)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorfWithRequestId(c, "[twofactor_authorizations.TwoFactorRecoveryCodeRegenerateHandler] failed to check two factor setting, because %s", err.Error())
|
log.ErrorfWithRequestId(c, "[twofactor_authorizations.TwoFactorRecoveryCodeRegenerateHandler] failed to check two-factor setting, because %s", err.Error())
|
||||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,14 +302,14 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorRecoveryCodeRegenerateHandler(c *c
|
|||||||
recoveryCodes, err := a.twoFactorAuthorizations.GenerateTwoFactorRecoveryCodes()
|
recoveryCodes, err := a.twoFactorAuthorizations.GenerateTwoFactorRecoveryCodes()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorfWithRequestId(c, "[twofactor_authorizations.TwoFactorRecoveryCodeRegenerateHandler] failed to generate two factor recovery codes for user \"uid:%d\", because %s", uid, err.Error())
|
log.ErrorfWithRequestId(c, "[twofactor_authorizations.TwoFactorRecoveryCodeRegenerateHandler] failed to generate two-factor recovery codes for user \"uid:%d\", because %s", uid, err.Error())
|
||||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = a.twoFactorAuthorizations.CreateTwoFactorRecoveryCodes(c, uid, recoveryCodes, user.Salt)
|
err = a.twoFactorAuthorizations.CreateTwoFactorRecoveryCodes(c, uid, recoveryCodes, user.Salt)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorfWithRequestId(c, "[twofactor_authorizations.TwoFactorRecoveryCodeRegenerateHandler] failed to create two factor recovery codes for user \"uid:%d\", because %s", uid, err.Error())
|
log.ErrorfWithRequestId(c, "[twofactor_authorizations.TwoFactorRecoveryCodeRegenerateHandler] failed to create two-factor recovery codes for user \"uid:%d\", because %s", uid, err.Error())
|
||||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,7 +317,7 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorRecoveryCodeRegenerateHandler(c *c
|
|||||||
RecoveryCodes: recoveryCodes,
|
RecoveryCodes: recoveryCodes,
|
||||||
}
|
}
|
||||||
|
|
||||||
log.InfofWithRequestId(c, "[twofactor_authorizations.TwoFactorRecoveryCodeRegenerateHandler] user \"uid:%d\" has regenerated two factor recovery codes", uid)
|
log.InfofWithRequestId(c, "[twofactor_authorizations.TwoFactorRecoveryCodeRegenerateHandler] user \"uid:%d\" has regenerated two-factor recovery codes", uid)
|
||||||
|
|
||||||
return recoveryCodesResp, nil
|
return recoveryCodesResp, nil
|
||||||
}
|
}
|
||||||
|
|||||||
+276
-4
@@ -1,6 +1,8 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -8,10 +10,13 @@ import (
|
|||||||
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/locales"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/services"
|
"github.com/mayswind/ezbookkeeping/pkg/services"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/storage"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/validators"
|
"github.com/mayswind/ezbookkeeping/pkg/validators"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -86,8 +91,9 @@ func (a *UsersApi) UserRegisterHandler(c *core.Context) (any, *errs.Error) {
|
|||||||
|
|
||||||
authResp := &models.RegisterResponse{
|
authResp := &models.RegisterResponse{
|
||||||
AuthResponse: models.AuthResponse{
|
AuthResponse: models.AuthResponse{
|
||||||
Need2FA: false,
|
Need2FA: false,
|
||||||
User: user.ToUserBasicInfo(),
|
User: user.ToUserBasicInfo(),
|
||||||
|
NotificationContent: settings.Container.GetAfterRegisterNotificationContent(user.Language, c.GetClientLocale()),
|
||||||
},
|
},
|
||||||
NeedVerifyEmail: settings.Container.Current.EnableUserVerifyEmail && settings.Container.Current.EnableUserForceVerifyEmail,
|
NeedVerifyEmail: settings.Container.Current.EnableUserVerifyEmail && settings.Container.Current.EnableUserForceVerifyEmail,
|
||||||
PresetCategoriesSaved: presetCategoriesSaved,
|
PresetCategoriesSaved: presetCategoriesSaved,
|
||||||
@@ -182,6 +188,8 @@ func (a *UsersApi) UserEmailVerifyHandler(c *core.Context) (any, *errs.Error) {
|
|||||||
|
|
||||||
resp.NewToken = token
|
resp.NewToken = token
|
||||||
resp.User = user.ToUserBasicInfo()
|
resp.User = user.ToUserBasicInfo()
|
||||||
|
resp.NotificationContent = settings.Container.GetAfterLoginNotificationContent(user.Language, c.GetClientLocale())
|
||||||
|
|
||||||
c.SetTextualToken(token)
|
c.SetTextualToken(token)
|
||||||
c.SetTokenClaims(claims)
|
c.SetTokenClaims(claims)
|
||||||
|
|
||||||
@@ -262,12 +270,22 @@ func (a *UsersApi) UserUpdateProfileHandler(c *core.Context) (any, *errs.Error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if userUpdateReq.DefaultAccountId > 0 && userUpdateReq.DefaultAccountId != user.DefaultAccountId {
|
if userUpdateReq.DefaultAccountId > 0 && userUpdateReq.DefaultAccountId != user.DefaultAccountId {
|
||||||
accounts, err := a.accounts.GetAccountsByAccountIds(c, uid, []int64{userUpdateReq.DefaultAccountId})
|
accountMap, err := a.accounts.GetAccountsByAccountIds(c, uid, []int64{userUpdateReq.DefaultAccountId})
|
||||||
|
|
||||||
if err != nil || len(accounts) < 1 {
|
if err != nil || len(accountMap) < 1 {
|
||||||
return nil, errs.Or(err, errs.ErrUserDefaultAccountIsInvalid)
|
return nil, errs.Or(err, errs.ErrUserDefaultAccountIsInvalid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, exists := accountMap[userUpdateReq.DefaultAccountId]; !exists {
|
||||||
|
log.WarnfWithRequestId(c, "[users.UserUpdateProfileHandler] account \"id:%d\" does not exist for user \"uid:%d\"", userUpdateReq.DefaultAccountId, uid)
|
||||||
|
return nil, errs.ErrUserDefaultAccountIsInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
if accountMap[userUpdateReq.DefaultAccountId].Hidden {
|
||||||
|
log.WarnfWithRequestId(c, "[users.UserUpdateProfileHandler] account \"id:%d\" is hidden of user \"uid:%d\"", userUpdateReq.DefaultAccountId, uid)
|
||||||
|
return nil, errs.ErrUserDefaultAccountIsHidden
|
||||||
|
}
|
||||||
|
|
||||||
user.DefaultAccountId = userUpdateReq.DefaultAccountId
|
user.DefaultAccountId = userUpdateReq.DefaultAccountId
|
||||||
userNew.DefaultAccountId = userUpdateReq.DefaultAccountId
|
userNew.DefaultAccountId = userUpdateReq.DefaultAccountId
|
||||||
anythingUpdate = true
|
anythingUpdate = true
|
||||||
@@ -336,6 +354,81 @@ func (a *UsersApi) UserUpdateProfileHandler(c *core.Context) (any, *errs.Error)
|
|||||||
userNew.ShortTimeFormat = models.SHORT_TIME_FORMAT_INVALID
|
userNew.ShortTimeFormat = models.SHORT_TIME_FORMAT_INVALID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if userUpdateReq.DecimalSeparator != nil && *userUpdateReq.DecimalSeparator != user.DecimalSeparator {
|
||||||
|
user.DecimalSeparator = *userUpdateReq.DecimalSeparator
|
||||||
|
userNew.DecimalSeparator = *userUpdateReq.DecimalSeparator
|
||||||
|
anythingUpdate = true
|
||||||
|
} else {
|
||||||
|
userNew.DecimalSeparator = core.DECIMAL_SEPARATOR_INVALID
|
||||||
|
}
|
||||||
|
|
||||||
|
if userUpdateReq.DigitGroupingSymbol != nil && *userUpdateReq.DigitGroupingSymbol != user.DigitGroupingSymbol {
|
||||||
|
user.DigitGroupingSymbol = *userUpdateReq.DigitGroupingSymbol
|
||||||
|
userNew.DigitGroupingSymbol = *userUpdateReq.DigitGroupingSymbol
|
||||||
|
anythingUpdate = true
|
||||||
|
} else {
|
||||||
|
userNew.DigitGroupingSymbol = core.DIGIT_GROUPING_SYMBOL_INVALID
|
||||||
|
}
|
||||||
|
|
||||||
|
if userUpdateReq.DigitGrouping != nil && *userUpdateReq.DigitGrouping != user.DigitGrouping {
|
||||||
|
user.DigitGrouping = *userUpdateReq.DigitGrouping
|
||||||
|
userNew.DigitGrouping = *userUpdateReq.DigitGrouping
|
||||||
|
anythingUpdate = true
|
||||||
|
} else {
|
||||||
|
userNew.DigitGrouping = core.DIGIT_GROUPING_TYPE_INVALID
|
||||||
|
}
|
||||||
|
|
||||||
|
if userUpdateReq.CurrencyDisplayType != nil && *userUpdateReq.CurrencyDisplayType != user.CurrencyDisplayType {
|
||||||
|
user.CurrencyDisplayType = *userUpdateReq.CurrencyDisplayType
|
||||||
|
userNew.CurrencyDisplayType = *userUpdateReq.CurrencyDisplayType
|
||||||
|
anythingUpdate = true
|
||||||
|
} else {
|
||||||
|
userNew.CurrencyDisplayType = models.CURRENCY_DISPLAY_TYPE_INVALID
|
||||||
|
}
|
||||||
|
|
||||||
|
if userUpdateReq.ExpenseAmountColor != nil && *userUpdateReq.ExpenseAmountColor != user.ExpenseAmountColor {
|
||||||
|
user.ExpenseAmountColor = *userUpdateReq.ExpenseAmountColor
|
||||||
|
userNew.ExpenseAmountColor = *userUpdateReq.ExpenseAmountColor
|
||||||
|
anythingUpdate = true
|
||||||
|
} else {
|
||||||
|
userNew.ExpenseAmountColor = models.AMOUNT_COLOR_TYPE_INVALID
|
||||||
|
}
|
||||||
|
|
||||||
|
if userUpdateReq.IncomeAmountColor != nil && *userUpdateReq.IncomeAmountColor != user.IncomeAmountColor {
|
||||||
|
user.IncomeAmountColor = *userUpdateReq.IncomeAmountColor
|
||||||
|
userNew.IncomeAmountColor = *userUpdateReq.IncomeAmountColor
|
||||||
|
anythingUpdate = true
|
||||||
|
} else {
|
||||||
|
userNew.IncomeAmountColor = models.AMOUNT_COLOR_TYPE_INVALID
|
||||||
|
}
|
||||||
|
|
||||||
|
if modifyUserLanguage || userNew.DecimalSeparator != core.DECIMAL_SEPARATOR_INVALID || userNew.DigitGroupingSymbol != core.DIGIT_GROUPING_SYMBOL_INVALID {
|
||||||
|
decimalSeparator := userNew.DecimalSeparator
|
||||||
|
digitGroupingSymbol := userNew.DigitGroupingSymbol
|
||||||
|
|
||||||
|
if userNew.DecimalSeparator == core.DECIMAL_SEPARATOR_INVALID {
|
||||||
|
decimalSeparator = user.DecimalSeparator
|
||||||
|
}
|
||||||
|
|
||||||
|
if userNew.DigitGroupingSymbol == core.DIGIT_GROUPING_SYMBOL_INVALID {
|
||||||
|
digitGroupingSymbol = user.DigitGroupingSymbol
|
||||||
|
}
|
||||||
|
|
||||||
|
locale := user.Language
|
||||||
|
|
||||||
|
if modifyUserLanguage {
|
||||||
|
locale = userNew.Language
|
||||||
|
}
|
||||||
|
|
||||||
|
if locale == "" {
|
||||||
|
locale = c.GetClientLocale()
|
||||||
|
}
|
||||||
|
|
||||||
|
if locales.IsDecimalSeparatorEqualsDigitGroupingSymbol(decimalSeparator, digitGroupingSymbol, locale) {
|
||||||
|
return nil, errs.ErrDecimalSeparatorAndDigitGroupingSymbolCannotBeEqual
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !anythingUpdate {
|
if !anythingUpdate {
|
||||||
return nil, errs.ErrNothingWillBeUpdated
|
return nil, errs.ErrNothingWillBeUpdated
|
||||||
}
|
}
|
||||||
@@ -408,6 +501,128 @@ func (a *UsersApi) UserUpdateProfileHandler(c *core.Context) (any, *errs.Error)
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UserUpdateAvatarHandler saves user avatar by request parameters for current user
|
||||||
|
func (a *UsersApi) UserUpdateAvatarHandler(c *core.Context) (any, *errs.Error) {
|
||||||
|
uid := c.GetCurrentUid()
|
||||||
|
user, err := a.users.GetUserById(c, uid)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if !errs.IsCustomError(err) {
|
||||||
|
log.ErrorfWithRequestId(c, "[users.UserUpdateAvatarHandler] failed to get user, because %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errs.ErrUserNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
form, err := c.MultipartForm()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[users.UserUpdateAvatarHandler] failed to get multi-part form data for user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||||
|
return nil, errs.ErrParameterInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
avatars := form.File["avatar"]
|
||||||
|
|
||||||
|
if len(avatars) < 1 {
|
||||||
|
log.WarnfWithRequestId(c, "[users.UserUpdateAvatarHandler] there is no user avatar in request for user \"uid:%d\"", user.Uid)
|
||||||
|
return nil, errs.ErrNoUserAvatar
|
||||||
|
}
|
||||||
|
|
||||||
|
if avatars[0].Size < 1 {
|
||||||
|
log.WarnfWithRequestId(c, "[users.UserUpdateAvatarHandler] the size of user avatar in request is zero for user \"uid:%d\"", user.Uid)
|
||||||
|
return nil, errs.ErrUserAvatarIsEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
fileExtension := utils.GetFileNameExtension(avatars[0].Filename)
|
||||||
|
|
||||||
|
if utils.GetImageContentType(fileExtension) == "" {
|
||||||
|
log.WarnfWithRequestId(c, "[users.UserUpdateAvatarHandler] the file extension \"%s\" of user avatar in request is not supported for user \"uid:%d\"", fileExtension, user.Uid)
|
||||||
|
return nil, errs.ErrImageTypeNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
avatarFile, err := avatars[0].Open()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[users.UserUpdateAvatarHandler] failed to get avatar file from request for user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||||
|
return nil, errs.ErrOperationFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
defer avatarFile.Close()
|
||||||
|
|
||||||
|
err = storage.Container.SaveAvatar(user.Uid, avatarFile, fileExtension)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[users.UserUpdateAvatarHandler] failed to save avatar file for user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||||
|
return nil, errs.ErrOperationFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.users.UpdateUserAvatar(c, user.Uid, fileExtension)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[users.UserUpdateAvatarHandler] failed to update user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||||
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fileExtension != user.CustomAvatarType {
|
||||||
|
err = storage.Container.DeleteAvatar(user.Uid, user.CustomAvatarType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WarnfWithRequestId(c, "[users.UserUpdateAvatarHandler] failed to delete old avatar with extension \"%s\" for user \"uid:%d\", because %s", user.CustomAvatarType, user.Uid, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user.CustomAvatarType = fileExtension
|
||||||
|
userResp := user.ToUserProfileResponse()
|
||||||
|
return userResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserRemoveAvatarHandler removes user avatar by request parameters for current user
|
||||||
|
func (a *UsersApi) UserRemoveAvatarHandler(c *core.Context) (any, *errs.Error) {
|
||||||
|
uid := c.GetCurrentUid()
|
||||||
|
user, err := a.users.GetUserById(c, uid)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if !errs.IsCustomError(err) {
|
||||||
|
log.ErrorfWithRequestId(c, "[users.UserRemoveAvatarHandler] failed to get user, because %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errs.ErrUserNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.CustomAvatarType == "" {
|
||||||
|
return nil, errs.ErrNothingWillBeUpdated
|
||||||
|
}
|
||||||
|
|
||||||
|
err = storage.Container.DeleteAvatar(user.Uid, user.CustomAvatarType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[users.UserRemoveAvatarHandler] failed to delete avatar file for user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||||
|
|
||||||
|
exists, err := storage.Container.ExistsAvatar(user.Uid, user.CustomAvatarType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[users.UserRemoveAvatarHandler] failed to check whether avatar file exist for user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||||
|
return nil, errs.ErrOperationFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
if exists {
|
||||||
|
log.ErrorfWithRequestId(c, "[users.UserRemoveAvatarHandler] failed to delete whether avatar file exist for user \"uid:%d\", the avatar file still exist", user.Uid)
|
||||||
|
return nil, errs.ErrOperationFailed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.users.UpdateUserAvatar(c, user.Uid, "")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[users.UserRemoveAvatarHandler] failed to update user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||||
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
user.CustomAvatarType = ""
|
||||||
|
userResp := user.ToUserProfileResponse()
|
||||||
|
return userResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
// UserSendVerifyEmailByUnloginUserHandler sends unlogin user verify email
|
// UserSendVerifyEmailByUnloginUserHandler sends unlogin user verify email
|
||||||
func (a *UsersApi) UserSendVerifyEmailByUnloginUserHandler(c *core.Context) (any, *errs.Error) {
|
func (a *UsersApi) UserSendVerifyEmailByUnloginUserHandler(c *core.Context) (any, *errs.Error) {
|
||||||
if !settings.Container.Current.EnableUserVerifyEmail {
|
if !settings.Container.Current.EnableUserVerifyEmail {
|
||||||
@@ -507,3 +722,60 @@ func (a *UsersApi) UserSendVerifyEmailByLoginedUserHandler(c *core.Context) (any
|
|||||||
|
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UserGetAvatarHandler returns user avatar data for current user
|
||||||
|
func (a *UsersApi) UserGetAvatarHandler(c *core.Context) ([]byte, string, *errs.Error) {
|
||||||
|
uid := c.GetCurrentUid()
|
||||||
|
user, err := a.users.GetUserById(c, uid)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if !errs.IsCustomError(err) {
|
||||||
|
log.ErrorfWithRequestId(c, "[users.UserGetAvatarHandler] failed to get user, because %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, "", errs.ErrUserNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.CustomAvatarType == "" {
|
||||||
|
log.WarnfWithRequestId(c, "[users.UserGetAvatarHandler] user does not have avatar for user \"uid:%d\"", user.Uid)
|
||||||
|
return nil, "", errs.ErrUserAvatarNoExists
|
||||||
|
}
|
||||||
|
|
||||||
|
fileName := c.Param("fileName")
|
||||||
|
fileBaseName := utils.GetFileNameWithoutExtension(fileName)
|
||||||
|
|
||||||
|
if utils.Int64ToString(user.Uid) != fileBaseName {
|
||||||
|
log.WarnfWithRequestId(c, "[users.UserGetAvatarHandler] cannot get other user avatar \"uid:%s\" for user \"uid:%d\"", fileBaseName, user.Uid)
|
||||||
|
return nil, "", errs.ErrUserIdInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
fileExtension := utils.GetFileNameExtension(fileName)
|
||||||
|
|
||||||
|
if user.CustomAvatarType != fileExtension {
|
||||||
|
log.WarnfWithRequestId(c, "[users.UserGetAvatarHandler] user avatar extension is invalid \"%s\" for user \"uid:%d\"", fileExtension, user.Uid)
|
||||||
|
return nil, "", errs.ErrUserAvatarNoExists
|
||||||
|
}
|
||||||
|
|
||||||
|
avatarFile, err := storage.Container.ReadAvatar(user.Uid, fileExtension)
|
||||||
|
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
log.WarnfWithRequestId(c, "[users.UserGetAvatarHandler] user avatar file not exist for user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||||
|
return nil, "", errs.ErrUserAvatarNoExists
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[users.UserGetAvatarHandler] failed to get user avatar object for user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||||
|
return nil, "", errs.ErrOperationFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
defer avatarFile.Close()
|
||||||
|
|
||||||
|
avatarData, err := io.ReadAll(avatarFile)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[users.UserGetAvatarHandler] failed to read user avatar object data for user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||||
|
return nil, "", errs.ErrOperationFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
return avatarData, utils.GetImageContentType(fileExtension), nil
|
||||||
|
}
|
||||||
|
|||||||
+113
-26
@@ -20,6 +20,7 @@ const pageCountForDataExport = 1000
|
|||||||
// UserDataCli represents user data cli
|
// UserDataCli represents user data cli
|
||||||
type UserDataCli struct {
|
type UserDataCli struct {
|
||||||
ezBookKeepingCsvExporter *converters.EzBookKeepingCSVFileExporter
|
ezBookKeepingCsvExporter *converters.EzBookKeepingCSVFileExporter
|
||||||
|
ezBookKeepingTsvExporter *converters.EzBookKeepingTSVFileExporter
|
||||||
accounts *services.AccountService
|
accounts *services.AccountService
|
||||||
transactions *services.TransactionService
|
transactions *services.TransactionService
|
||||||
categories *services.TransactionCategoryService
|
categories *services.TransactionCategoryService
|
||||||
@@ -34,6 +35,7 @@ type UserDataCli struct {
|
|||||||
var (
|
var (
|
||||||
UserData = &UserDataCli{
|
UserData = &UserDataCli{
|
||||||
ezBookKeepingCsvExporter: &converters.EzBookKeepingCSVFileExporter{},
|
ezBookKeepingCsvExporter: &converters.EzBookKeepingCSVFileExporter{},
|
||||||
|
ezBookKeepingTsvExporter: &converters.EzBookKeepingTSVFileExporter{},
|
||||||
accounts: services.Accounts,
|
accounts: services.Accounts,
|
||||||
transactions: services.Transactions,
|
transactions: services.Transactions,
|
||||||
categories: services.TransactionCategories,
|
categories: services.TransactionCategories,
|
||||||
@@ -391,7 +393,7 @@ func (l *UserDataCli) DisableUserTwoFactorAuthorization(c *cli.Context, username
|
|||||||
enableTwoFactor, err := l.twoFactorAuthorizations.ExistsTwoFactorSetting(nil, uid)
|
enableTwoFactor, err := l.twoFactorAuthorizations.ExistsTwoFactorSetting(nil, uid)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.BootErrorf("[user_data.DisableUserTwoFactorAuthorization] failed to check two factor setting, because %s", err.Error())
|
log.BootErrorf("[user_data.DisableUserTwoFactorAuthorization] failed to check two-factor setting, because %s", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,14 +404,14 @@ func (l *UserDataCli) DisableUserTwoFactorAuthorization(c *cli.Context, username
|
|||||||
err = l.twoFactorAuthorizations.DeleteTwoFactorRecoveryCodes(nil, uid)
|
err = l.twoFactorAuthorizations.DeleteTwoFactorRecoveryCodes(nil, uid)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.BootErrorf("[user_data.DisableUserTwoFactorAuthorization] failed to delete two factor recovery codes for user \"%s\"", username)
|
log.BootErrorf("[user_data.DisableUserTwoFactorAuthorization] failed to delete two-factor recovery codes for user \"%s\"", username)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = l.twoFactorAuthorizations.DeleteTwoFactorSetting(nil, uid)
|
err = l.twoFactorAuthorizations.DeleteTwoFactorSetting(nil, uid)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.BootErrorf("[user_data.DisableUserTwoFactorAuthorization] failed to delete two factor setting for user \"%s\"", username)
|
log.BootErrorf("[user_data.DisableUserTwoFactorAuthorization] failed to delete two-factor setting for user \"%s\"", username)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,7 +432,7 @@ func (l *UserDataCli) CheckTransactionAndAccount(c *cli.Context, username string
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
accountMap, categoryMap, tagMap, tagIndexs, err := l.getUserEssentialData(uid, username)
|
accountMap, categoryMap, tagMap, tagIndexes, tagIndexesMap, err := l.getUserEssentialData(uid, username)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.BootErrorf("[user_data.CheckTransactionAndAccount] failed to get essential data for user \"%s\", because %s", username, err.Error())
|
log.BootErrorf("[user_data.CheckTransactionAndAccount] failed to get essential data for user \"%s\", because %s", username, err.Error())
|
||||||
@@ -470,7 +472,7 @@ func (l *UserDataCli) CheckTransactionAndAccount(c *cli.Context, username string
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = l.checkTransactionTag(c, transaction.TransactionId, tagIndexs, tagMap)
|
err = l.checkTransactionTag(c, transaction.TransactionId, tagIndexesMap, tagMap)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@@ -499,7 +501,7 @@ func (l *UserDataCli) CheckTransactionAndAccount(c *cli.Context, username string
|
|||||||
} else if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN {
|
} else if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN {
|
||||||
balance = balance + transaction.Amount
|
balance = balance + transaction.Amount
|
||||||
} else {
|
} else {
|
||||||
log.BootErrorf("[user_data.CheckAccountBalance] transaction type of transaction \"id:%d\" is invalid", transaction.TransactionId)
|
log.BootErrorf("[user_data.CheckTransactionAndAccount] transaction type of transaction \"id:%d\" is invalid", transaction.TransactionId)
|
||||||
return false, errs.ErrOperationFailed
|
return false, errs.ErrOperationFailed
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -514,12 +516,12 @@ func (l *UserDataCli) CheckTransactionAndAccount(c *cli.Context, username string
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !exists && account.Balance != 0 {
|
if !exists && account.Balance != 0 {
|
||||||
log.BootErrorf("[user_data.CheckAccountBalance] account \"id:%d\" balance is not correct, expected balance is %d, but there is no transaction actually", account.AccountId, account.Balance)
|
log.BootErrorf("[user_data.CheckTransactionAndAccount] account \"id:%d\" balance is not correct, expected balance is %d, but there is no transaction actually", account.AccountId, account.Balance)
|
||||||
return false, errs.ErrOperationFailed
|
return false, errs.ErrOperationFailed
|
||||||
}
|
}
|
||||||
|
|
||||||
if account.Balance != actualBalance {
|
if account.Balance != actualBalance {
|
||||||
log.BootErrorf("[user_data.CheckAccountBalance] account \"id:%d\" balance is not correct, expected balance is %d, but actual balance is %d", account.AccountId, account.Balance, actualBalance)
|
log.BootErrorf("[user_data.CheckTransactionAndAccount] account \"id:%d\" balance is not correct, expected balance is %d, but actual balance is %d", account.AccountId, account.Balance, actualBalance)
|
||||||
return false, errs.ErrOperationFailed
|
return false, errs.ErrOperationFailed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -528,7 +530,16 @@ func (l *UserDataCli) CheckTransactionAndAccount(c *cli.Context, username string
|
|||||||
_, exists := accountMap[accountId]
|
_, exists := accountMap[accountId]
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
log.BootErrorf("[user_data.CheckAccountBalance] account \"id:%d\" does not exist, but there are some transactions of this account actually, and actual balance is %d", accountId, actualBalance)
|
log.BootErrorf("[user_data.CheckTransactionAndAccount] account \"id:%d\" does not exist, but there are some transactions of this account actually, and actual balance is %d", accountId, actualBalance)
|
||||||
|
return false, errs.ErrOperationFailed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(tagIndexes); i++ {
|
||||||
|
tagIndex := tagIndexes[i]
|
||||||
|
|
||||||
|
if tagIndex.TransactionTime < 1 {
|
||||||
|
log.BootErrorf("[user_data.CheckTransactionAndAccount] transaction tag index \"id:%d\" does not have transaction time", tagIndex.TagIndexId)
|
||||||
return false, errs.ErrOperationFailed
|
return false, errs.ErrOperationFailed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -536,8 +547,74 @@ func (l *UserDataCli) CheckTransactionAndAccount(c *cli.Context, username string
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FixTransactionTagIndexWithTransactionTime fixes user transaction tag index data with transaction time
|
||||||
|
func (l *UserDataCli) FixTransactionTagIndexWithTransactionTime(c *cli.Context, username string) (bool, error) {
|
||||||
|
if username == "" {
|
||||||
|
log.BootErrorf("[user_data.FixTransactionTagIndexWithTransactionTime] user name is empty")
|
||||||
|
return false, errs.ErrUsernameIsEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
uid, err := l.getUserIdByUsername(c, username)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.BootErrorf("[user_data.FixTransactionTagIndexWithTransactionTime] error occurs when getting user id by user name")
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tagIndexes, err := l.tags.GetAllTagIdsOfAllTransactions(nil, uid)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.BootErrorf("[user_data.FixTransactionTagIndexWithTransactionTime] failed to get tag index for user \"%s\", because %s", username, err.Error())
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidTagIndexes := make([]*models.TransactionTagIndex, 0, len(tagIndexes))
|
||||||
|
|
||||||
|
for i := 0; i < len(tagIndexes); i++ {
|
||||||
|
tagIndex := tagIndexes[i]
|
||||||
|
|
||||||
|
if tagIndex.TransactionTime < 1 {
|
||||||
|
invalidTagIndexes = append(invalidTagIndexes, tagIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(invalidTagIndexes) < 1 {
|
||||||
|
log.BootErrorf("[user_data.FixTransactionTagIndexWithTransactionTime] all user transaction tag index data has been checked, there is no problem with user data")
|
||||||
|
return false, errs.ErrOperationFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
allTransactions, err := l.transactions.GetAllTransactions(nil, uid, pageCountForGettingTransactions, false)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.BootErrorf("[user_data.FixTransactionTagIndexWithTransactionTime] failed to all transactions for user \"%s\", because %s", username, err.Error())
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
transactionMap := l.transactions.GetTransactionMapByList(allTransactions)
|
||||||
|
|
||||||
|
for i := 0; i < len(invalidTagIndexes); i++ {
|
||||||
|
tagIndex := invalidTagIndexes[i]
|
||||||
|
transaction, exists := transactionMap[tagIndex.TransactionId]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
tagIndex.TransactionTime = transaction.TransactionTime
|
||||||
|
}
|
||||||
|
|
||||||
|
err = l.tags.ModifyTagIndexTransactionTime(nil, uid, invalidTagIndexes)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.BootErrorf("[user_data.FixTransactionTagIndexWithTransactionTime] failed to update transaction tag index for user \"%s\", because %s", username, err.Error())
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ExportTransaction returns csv file content according user all transactions
|
// ExportTransaction returns csv file content according user all transactions
|
||||||
func (l *UserDataCli) ExportTransaction(c *cli.Context, username string) ([]byte, error) {
|
func (l *UserDataCli) ExportTransaction(c *cli.Context, username string, fileType string) ([]byte, error) {
|
||||||
if username == "" {
|
if username == "" {
|
||||||
log.BootErrorf("[user_data.ExportTransaction] user name is empty")
|
log.BootErrorf("[user_data.ExportTransaction] user name is empty")
|
||||||
return nil, errs.ErrUsernameIsEmpty
|
return nil, errs.ErrUsernameIsEmpty
|
||||||
@@ -550,7 +627,7 @@ func (l *UserDataCli) ExportTransaction(c *cli.Context, username string) ([]byte
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
accountMap, categoryMap, tagMap, tagIndexs, err := l.getUserEssentialData(uid, username)
|
accountMap, categoryMap, tagMap, _, tagIndexesMap, err := l.getUserEssentialData(uid, username)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.BootErrorf("[user_data.ExportTransaction] failed to get essential data for user \"%s\", because %s", username, err.Error())
|
log.BootErrorf("[user_data.ExportTransaction] failed to get essential data for user \"%s\", because %s", username, err.Error())
|
||||||
@@ -564,7 +641,15 @@ func (l *UserDataCli) ExportTransaction(c *cli.Context, username string) ([]byte
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := l.ezBookKeepingCsvExporter.ToExportedContent(uid, time.Local, allTransactions, accountMap, categoryMap, tagMap, tagIndexs)
|
var dataExporter converters.DataConverter
|
||||||
|
|
||||||
|
if fileType == "tsv" {
|
||||||
|
dataExporter = l.ezBookKeepingTsvExporter
|
||||||
|
} else {
|
||||||
|
dataExporter = l.ezBookKeepingCsvExporter
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := dataExporter.ToExportedContent(uid, allTransactions, accountMap, categoryMap, tagMap, tagIndexesMap)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.BootErrorf("[user_data.ExportTransaction] failed to get csv format exported data for \"%s\", because %s", username, err.Error())
|
log.BootErrorf("[user_data.ExportTransaction] failed to get csv format exported data for \"%s\", because %s", username, err.Error())
|
||||||
@@ -585,17 +670,17 @@ func (l *UserDataCli) getUserIdByUsername(c *cli.Context, username string) (int6
|
|||||||
return user.Uid, nil
|
return user.Uid, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *UserDataCli) getUserEssentialData(uid int64, username string) (accountMap map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, tagIndexs map[int64][]int64, err error) {
|
func (l *UserDataCli) getUserEssentialData(uid int64, username string) (accountMap map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, tagIndexes []*models.TransactionTagIndex, tagIndexesMap map[int64][]int64, err error) {
|
||||||
if uid <= 0 {
|
if uid <= 0 {
|
||||||
log.BootErrorf("[user_data.getUserEssentialData] user uid \"%d\" is invalid", uid)
|
log.BootErrorf("[user_data.getUserEssentialData] user uid \"%d\" is invalid", uid)
|
||||||
return nil, nil, nil, nil, errs.ErrUserIdInvalid
|
return nil, nil, nil, nil, nil, errs.ErrUserIdInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
accounts, err := l.accounts.GetAllAccountsByUid(nil, uid)
|
accounts, err := l.accounts.GetAllAccountsByUid(nil, uid)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.BootErrorf("[user_data.getUserEssentialData] failed to get accounts for user \"%s\", because %s", username, err.Error())
|
log.BootErrorf("[user_data.getUserEssentialData] failed to get accounts for user \"%s\", because %s", username, err.Error())
|
||||||
return nil, nil, nil, nil, err
|
return nil, nil, nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
accountMap = l.accounts.GetAccountMapByList(accounts)
|
accountMap = l.accounts.GetAccountMapByList(accounts)
|
||||||
@@ -604,7 +689,7 @@ func (l *UserDataCli) getUserEssentialData(uid int64, username string) (accountM
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.BootErrorf("[user_data.getUserEssentialData] failed to get categories for user \"%s\", because %s", username, err.Error())
|
log.BootErrorf("[user_data.getUserEssentialData] failed to get categories for user \"%s\", because %s", username, err.Error())
|
||||||
return nil, nil, nil, nil, err
|
return nil, nil, nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
categoryMap = l.categories.GetCategoryMapByList(categories)
|
categoryMap = l.categories.GetCategoryMapByList(categories)
|
||||||
@@ -613,19 +698,21 @@ func (l *UserDataCli) getUserEssentialData(uid int64, username string) (accountM
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.BootErrorf("[user_data.getUserEssentialData] failed to get tags for user \"%s\", because %s", username, err.Error())
|
log.BootErrorf("[user_data.getUserEssentialData] failed to get tags for user \"%s\", because %s", username, err.Error())
|
||||||
return nil, nil, nil, nil, err
|
return nil, nil, nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tagMap = l.tags.GetTagMapByList(tags)
|
tagMap = l.tags.GetTagMapByList(tags)
|
||||||
|
|
||||||
tagIndexs, err = l.tags.GetAllTagIdsOfAllTransactions(nil, uid)
|
tagIndexes, err = l.tags.GetAllTagIdsOfAllTransactions(nil, uid)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.BootErrorf("[user_data.getUserEssentialData] failed to get tag index for user \"%s\", because %s", username, err.Error())
|
log.BootErrorf("[user_data.getUserEssentialData] failed to get tag index for user \"%s\", because %s", username, err.Error())
|
||||||
return nil, nil, nil, nil, err
|
return nil, nil, nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return accountMap, categoryMap, tagMap, tagIndexs, nil
|
tagIndexesMap = l.tags.GetGroupedTransactionTagIds(tagIndexes)
|
||||||
|
|
||||||
|
return accountMap, categoryMap, tagMap, tagIndexes, tagIndexesMap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *UserDataCli) checkTransactionAccount(c *cli.Context, transaction *models.Transaction, accountMap map[int64]*models.Account, accountHasChild map[int64]bool) error {
|
func (l *UserDataCli) checkTransactionAccount(c *cli.Context, transaction *models.Transaction, accountMap map[int64]*models.Account, accountHasChild map[int64]bool) error {
|
||||||
@@ -637,7 +724,7 @@ func (l *UserDataCli) checkTransactionAccount(c *cli.Context, transaction *model
|
|||||||
}
|
}
|
||||||
|
|
||||||
if account.ParentAccountId == models.LevelOneAccountParentId && accountHasChild[account.AccountId] {
|
if account.ParentAccountId == models.LevelOneAccountParentId && accountHasChild[account.AccountId] {
|
||||||
log.BootErrorf("[user_data.checkTransactionAccount] the account \"id:%d\" of transaction \"id:%d\" is not a sub account", transaction.AccountId, transaction.TransactionId)
|
log.BootErrorf("[user_data.checkTransactionAccount] the account \"id:%d\" of transaction \"id:%d\" is not a sub-account", transaction.AccountId, transaction.TransactionId)
|
||||||
return errs.ErrOperationFailed
|
return errs.ErrOperationFailed
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -650,7 +737,7 @@ func (l *UserDataCli) checkTransactionAccount(c *cli.Context, transaction *model
|
|||||||
}
|
}
|
||||||
|
|
||||||
if relatedAccount.ParentAccountId == models.LevelOneAccountParentId && accountHasChild[relatedAccount.AccountId] {
|
if relatedAccount.ParentAccountId == models.LevelOneAccountParentId && accountHasChild[relatedAccount.AccountId] {
|
||||||
log.BootErrorf("[user_data.checkTransactionAccount] the related account \"id:%d\" of transaction \"id:%d\" is not a sub account", transaction.RelatedAccountId, transaction.TransactionId)
|
log.BootErrorf("[user_data.checkTransactionAccount] the related account \"id:%d\" of transaction \"id:%d\" is not a sub-account", transaction.RelatedAccountId, transaction.TransactionId)
|
||||||
return errs.ErrOperationFailed
|
return errs.ErrOperationFailed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -683,15 +770,15 @@ func (l *UserDataCli) checkTransactionCategory(c *cli.Context, transaction *mode
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *UserDataCli) checkTransactionTag(c *cli.Context, transactionId int64, allTagIndexs map[int64][]int64, tagMap map[int64]*models.TransactionTag) error {
|
func (l *UserDataCli) checkTransactionTag(c *cli.Context, transactionId int64, allTagIndexesMap map[int64][]int64, tagMap map[int64]*models.TransactionTag) error {
|
||||||
tagIndexs, exists := allTagIndexs[transactionId]
|
tagIndexes, exists := allTagIndexesMap[transactionId]
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < len(tagIndexs); i++ {
|
for i := 0; i < len(tagIndexes); i++ {
|
||||||
tagIndex := tagIndexs[i]
|
tagIndex := tagIndexes[i]
|
||||||
tag, exists := tagMap[tagIndex]
|
tag, exists := tagMap[tagIndex]
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
package converters
|
package converters
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DataConverter defines the structure of data exporter
|
// DataConverter defines the structure of data exporter
|
||||||
type DataConverter interface {
|
type DataConverter interface {
|
||||||
// ToExportedContent returns the exported data
|
// ToExportedContent returns the exported data
|
||||||
ToExportedContent(uid int64, timezone *time.Location, transactions []*models.Transaction, accountMap map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, allTagIndexs map[int64][]int64) ([]byte, error)
|
ToExportedContent(uid int64, transactions []*models.Transaction, accountMap map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, allTagIndexes map[int64][]int64) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,179 +1,17 @@
|
|||||||
package converters
|
package converters
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// EzBookKeepingCSVFileExporter defines the structure of csv file exporter
|
// EzBookKeepingCSVFileExporter defines the structure of CSV file exporter
|
||||||
type EzBookKeepingCSVFileExporter struct {
|
type EzBookKeepingCSVFileExporter struct {
|
||||||
DataConverter
|
EzBookKeepingPlainFileExporter
|
||||||
}
|
}
|
||||||
|
|
||||||
const csvHeaderLine = "Time,Timezone,Type,Category,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount,Tags,Comment\n"
|
const csvSeparator = ","
|
||||||
const csvDataLineFormat = "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n"
|
|
||||||
|
|
||||||
// ToExportedContent returns the exported csv data
|
// ToExportedContent returns the exported CSV data
|
||||||
func (e *EzBookKeepingCSVFileExporter) ToExportedContent(uid int64, timezone *time.Location, transactions []*models.Transaction, accountMap map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, allTagIndexs map[int64][]int64) ([]byte, error) {
|
func (e *EzBookKeepingCSVFileExporter) ToExportedContent(uid int64, transactions []*models.Transaction, accountMap map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, allTagIndexes map[int64][]int64) ([]byte, error) {
|
||||||
var ret strings.Builder
|
return e.toExportedContent(uid, csvSeparator, transactions, accountMap, categoryMap, tagMap, allTagIndexes)
|
||||||
|
|
||||||
ret.Grow(len(transactions) * 100)
|
|
||||||
ret.WriteString(csvHeaderLine)
|
|
||||||
|
|
||||||
for i := 0; i < len(transactions); i++ {
|
|
||||||
transaction := transactions[i]
|
|
||||||
|
|
||||||
if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
transactionTimeZone := time.FixedZone("Transaction Timezone", int(transaction.TimezoneUtcOffset)*60)
|
|
||||||
transactionTime := utils.FormatUnixTimeToLongDateTimeWithoutSecond(utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime), transactionTimeZone)
|
|
||||||
transactionTimezone := utils.FormatTimezoneOffset(transactionTimeZone)
|
|
||||||
transactionType := e.getTransactionTypeName(transaction.Type)
|
|
||||||
category := e.getTransactionCategoryName(transaction.CategoryId, categoryMap)
|
|
||||||
subCategory := e.getTransactionSubCategoryName(transaction.CategoryId, categoryMap)
|
|
||||||
account := e.getAccountName(transaction.AccountId, accountMap)
|
|
||||||
accountCurrency := e.getAccountCurrency(transaction.AccountId, accountMap)
|
|
||||||
amount := e.getDisplayAmount(transaction.Amount)
|
|
||||||
account2 := ""
|
|
||||||
account2Currency := ""
|
|
||||||
account2Amount := ""
|
|
||||||
|
|
||||||
if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT {
|
|
||||||
account2 = e.getAccountName(transaction.RelatedAccountId, accountMap)
|
|
||||||
account2Currency = e.getAccountCurrency(transaction.RelatedAccountId, accountMap)
|
|
||||||
account2Amount = e.getDisplayAmount(transaction.RelatedAccountAmount)
|
|
||||||
}
|
|
||||||
|
|
||||||
tags := e.getTags(transaction.TransactionId, allTagIndexs, tagMap)
|
|
||||||
comment := e.getComment(transaction.Comment)
|
|
||||||
|
|
||||||
ret.WriteString(fmt.Sprintf(csvDataLineFormat, transactionTime, transactionTimezone, transactionType, category, subCategory, account, accountCurrency, amount, account2, account2Currency, account2Amount, tags, comment))
|
|
||||||
}
|
|
||||||
|
|
||||||
return []byte(ret.String()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EzBookKeepingCSVFileExporter) getTransactionTypeName(transactionDbType models.TransactionDbType) string {
|
|
||||||
if transactionDbType == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE {
|
|
||||||
return "Balance Modification"
|
|
||||||
} else if transactionDbType == models.TRANSACTION_DB_TYPE_INCOME {
|
|
||||||
return "Income"
|
|
||||||
} else if transactionDbType == models.TRANSACTION_DB_TYPE_EXPENSE {
|
|
||||||
return "Expense"
|
|
||||||
} else if transactionDbType == models.TRANSACTION_DB_TYPE_TRANSFER_OUT || transactionDbType == models.TRANSACTION_DB_TYPE_TRANSFER_IN {
|
|
||||||
return "Transfer"
|
|
||||||
} else {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EzBookKeepingCSVFileExporter) getTransactionCategoryName(categoryId int64, categoryMap map[int64]*models.TransactionCategory) string {
|
|
||||||
category, exists := categoryMap[categoryId]
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if category.ParentCategoryId == 0 {
|
|
||||||
return category.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
parentCategory, exists := categoryMap[category.ParentCategoryId]
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return parentCategory.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EzBookKeepingCSVFileExporter) getTransactionSubCategoryName(categoryId int64, categoryMap map[int64]*models.TransactionCategory) string {
|
|
||||||
category, exists := categoryMap[categoryId]
|
|
||||||
|
|
||||||
if exists {
|
|
||||||
return category.Name
|
|
||||||
} else {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EzBookKeepingCSVFileExporter) getAccountName(accountId int64, accountMap map[int64]*models.Account) string {
|
|
||||||
account, exists := accountMap[accountId]
|
|
||||||
|
|
||||||
if exists {
|
|
||||||
return account.Name
|
|
||||||
} else {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EzBookKeepingCSVFileExporter) getAccountCurrency(accountId int64, accountMap map[int64]*models.Account) string {
|
|
||||||
account, exists := accountMap[accountId]
|
|
||||||
|
|
||||||
if exists {
|
|
||||||
return account.Currency
|
|
||||||
} else {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EzBookKeepingCSVFileExporter) getDisplayAmount(amount int64) string {
|
|
||||||
displayAmount := utils.Int64ToString(amount)
|
|
||||||
integer := utils.SubString(displayAmount, 0, len(displayAmount)-2)
|
|
||||||
decimals := utils.SubString(displayAmount, -2, 2)
|
|
||||||
|
|
||||||
if integer == "" {
|
|
||||||
integer = "0"
|
|
||||||
} else if integer == "-" {
|
|
||||||
integer = "-0"
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(decimals) == 0 {
|
|
||||||
decimals = "00"
|
|
||||||
} else if len(decimals) == 1 {
|
|
||||||
decimals = "0" + decimals
|
|
||||||
}
|
|
||||||
|
|
||||||
return integer + "." + decimals
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EzBookKeepingCSVFileExporter) getTags(transactionId int64, allTagIndexs map[int64][]int64, tagMap map[int64]*models.TransactionTag) string {
|
|
||||||
tagIndexs, exists := allTagIndexs[transactionId]
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
var ret strings.Builder
|
|
||||||
|
|
||||||
for i := 0; i < len(tagIndexs); i++ {
|
|
||||||
if i > 0 {
|
|
||||||
ret.WriteString(";")
|
|
||||||
}
|
|
||||||
|
|
||||||
tagIndex := tagIndexs[i]
|
|
||||||
tag, exists := tagMap[tagIndex]
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
ret.WriteString(tag.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EzBookKeepingCSVFileExporter) getComment(comment string) string {
|
|
||||||
comment = strings.Replace(comment, ",", " ", -1)
|
|
||||||
comment = strings.Replace(comment, "\r\n", " ", -1)
|
|
||||||
comment = strings.Replace(comment, "\n", " ", -1)
|
|
||||||
|
|
||||||
return comment
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,195 @@
|
|||||||
|
package converters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EzBookKeepingPlainFileExporter defines the structure of plain file exporter
|
||||||
|
type EzBookKeepingPlainFileExporter struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
const lineSeparator = "\n"
|
||||||
|
const geoLocationSeparator = " "
|
||||||
|
const transactionTagSeparator = ";"
|
||||||
|
const headerLine = "Time,Timezone,Type,Category,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description" + lineSeparator
|
||||||
|
const dataLineFormat = "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s" + lineSeparator
|
||||||
|
|
||||||
|
// toExportedContent returns the exported plain data
|
||||||
|
func (e *EzBookKeepingPlainFileExporter) toExportedContent(uid int64, separator string, transactions []*models.Transaction, accountMap map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, allTagIndexes map[int64][]int64) ([]byte, error) {
|
||||||
|
var ret strings.Builder
|
||||||
|
|
||||||
|
ret.Grow(len(transactions) * 100)
|
||||||
|
|
||||||
|
actualHeaderLine := headerLine
|
||||||
|
actualDataLineFormat := dataLineFormat
|
||||||
|
|
||||||
|
if separator != "," {
|
||||||
|
actualHeaderLine = strings.Replace(headerLine, ",", separator, -1)
|
||||||
|
actualDataLineFormat = strings.Replace(dataLineFormat, ",", separator, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.WriteString(actualHeaderLine)
|
||||||
|
|
||||||
|
for i := 0; i < len(transactions); i++ {
|
||||||
|
transaction := transactions[i]
|
||||||
|
|
||||||
|
if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
transactionTimeZone := time.FixedZone("Transaction Timezone", int(transaction.TimezoneUtcOffset)*60)
|
||||||
|
transactionTime := utils.FormatUnixTimeToLongDateTime(utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime), transactionTimeZone)
|
||||||
|
transactionTimezone := utils.FormatTimezoneOffset(transactionTimeZone)
|
||||||
|
transactionType := e.getTransactionTypeName(transaction.Type)
|
||||||
|
category := e.replaceDelimiters(e.getTransactionCategoryName(transaction.CategoryId, categoryMap), separator)
|
||||||
|
subCategory := e.replaceDelimiters(e.getTransactionSubCategoryName(transaction.CategoryId, categoryMap), separator)
|
||||||
|
account := e.replaceDelimiters(e.getAccountName(transaction.AccountId, accountMap), separator)
|
||||||
|
accountCurrency := e.getAccountCurrency(transaction.AccountId, accountMap)
|
||||||
|
amount := e.getDisplayAmount(transaction.Amount)
|
||||||
|
account2 := ""
|
||||||
|
account2Currency := ""
|
||||||
|
account2Amount := ""
|
||||||
|
geoLocation := ""
|
||||||
|
|
||||||
|
if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT {
|
||||||
|
account2 = e.replaceDelimiters(e.getAccountName(transaction.RelatedAccountId, accountMap), separator)
|
||||||
|
account2Currency = e.getAccountCurrency(transaction.RelatedAccountId, accountMap)
|
||||||
|
account2Amount = e.getDisplayAmount(transaction.RelatedAccountAmount)
|
||||||
|
}
|
||||||
|
|
||||||
|
if transaction.GeoLongitude != 0 || transaction.GeoLatitude != 0 {
|
||||||
|
geoLocation = fmt.Sprintf("%f%s%f", transaction.GeoLongitude, geoLocationSeparator, transaction.GeoLatitude)
|
||||||
|
}
|
||||||
|
|
||||||
|
tags := e.replaceDelimiters(e.getTags(transaction.TransactionId, allTagIndexes, tagMap), separator)
|
||||||
|
comment := e.replaceDelimiters(transaction.Comment, separator)
|
||||||
|
|
||||||
|
ret.WriteString(fmt.Sprintf(actualDataLineFormat, transactionTime, transactionTimezone, transactionType, category, subCategory, account, accountCurrency, amount, account2, account2Currency, account2Amount, geoLocation, tags, comment))
|
||||||
|
}
|
||||||
|
|
||||||
|
return []byte(ret.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EzBookKeepingPlainFileExporter) getTransactionTypeName(transactionDbType models.TransactionDbType) string {
|
||||||
|
if transactionDbType == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE {
|
||||||
|
return "Balance Modification"
|
||||||
|
} else if transactionDbType == models.TRANSACTION_DB_TYPE_INCOME {
|
||||||
|
return "Income"
|
||||||
|
} else if transactionDbType == models.TRANSACTION_DB_TYPE_EXPENSE {
|
||||||
|
return "Expense"
|
||||||
|
} else if transactionDbType == models.TRANSACTION_DB_TYPE_TRANSFER_OUT || transactionDbType == models.TRANSACTION_DB_TYPE_TRANSFER_IN {
|
||||||
|
return "Transfer"
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EzBookKeepingPlainFileExporter) getTransactionCategoryName(categoryId int64, categoryMap map[int64]*models.TransactionCategory) string {
|
||||||
|
category, exists := categoryMap[categoryId]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if category.ParentCategoryId == 0 {
|
||||||
|
return category.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
parentCategory, exists := categoryMap[category.ParentCategoryId]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return parentCategory.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EzBookKeepingPlainFileExporter) getTransactionSubCategoryName(categoryId int64, categoryMap map[int64]*models.TransactionCategory) string {
|
||||||
|
category, exists := categoryMap[categoryId]
|
||||||
|
|
||||||
|
if exists {
|
||||||
|
return category.Name
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EzBookKeepingPlainFileExporter) getAccountName(accountId int64, accountMap map[int64]*models.Account) string {
|
||||||
|
account, exists := accountMap[accountId]
|
||||||
|
|
||||||
|
if exists {
|
||||||
|
return account.Name
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EzBookKeepingPlainFileExporter) getAccountCurrency(accountId int64, accountMap map[int64]*models.Account) string {
|
||||||
|
account, exists := accountMap[accountId]
|
||||||
|
|
||||||
|
if exists {
|
||||||
|
return account.Currency
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EzBookKeepingPlainFileExporter) getDisplayAmount(amount int64) string {
|
||||||
|
displayAmount := utils.Int64ToString(amount)
|
||||||
|
integer := utils.SubString(displayAmount, 0, len(displayAmount)-2)
|
||||||
|
decimals := utils.SubString(displayAmount, -2, 2)
|
||||||
|
|
||||||
|
if integer == "" {
|
||||||
|
integer = "0"
|
||||||
|
} else if integer == "-" {
|
||||||
|
integer = "-0"
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(decimals) == 0 {
|
||||||
|
decimals = "00"
|
||||||
|
} else if len(decimals) == 1 {
|
||||||
|
decimals = "0" + decimals
|
||||||
|
}
|
||||||
|
|
||||||
|
return integer + "." + decimals
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EzBookKeepingPlainFileExporter) getTags(transactionId int64, allTagIndexes map[int64][]int64, tagMap map[int64]*models.TransactionTag) string {
|
||||||
|
tagIndexes, exists := allTagIndexes[transactionId]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var ret strings.Builder
|
||||||
|
|
||||||
|
for i := 0; i < len(tagIndexes); i++ {
|
||||||
|
if i > 0 {
|
||||||
|
ret.WriteString(transactionTagSeparator)
|
||||||
|
}
|
||||||
|
|
||||||
|
tagIndex := tagIndexes[i]
|
||||||
|
tag, exists := tagMap[tagIndex]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.WriteString(tag.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EzBookKeepingPlainFileExporter) replaceDelimiters(text string, separator string) string {
|
||||||
|
text = strings.Replace(text, separator, " ", -1)
|
||||||
|
text = strings.Replace(text, "\r\n", " ", -1)
|
||||||
|
text = strings.Replace(text, "\n", " ", -1)
|
||||||
|
|
||||||
|
return text
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package converters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EzBookKeepingTSVFileExporter defines the structure of TSV file exporter
|
||||||
|
type EzBookKeepingTSVFileExporter struct {
|
||||||
|
EzBookKeepingPlainFileExporter
|
||||||
|
}
|
||||||
|
|
||||||
|
const tsvSeparator = "\t"
|
||||||
|
|
||||||
|
// ToExportedContent returns the exported TSV data
|
||||||
|
func (e *EzBookKeepingTSVFileExporter) ToExportedContent(uid int64, transactions []*models.Transaction, accountMap map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, allTagIndexes map[int64][]int64) ([]byte, error) {
|
||||||
|
return e.toExportedContent(uid, tsvSeparator, transactions, accountMap, categoryMap, tagMap, allTagIndexes)
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -16,6 +17,9 @@ const responseErrorFieldKey = "RESPONSE_ERROR"
|
|||||||
// AcceptLanguageHeaderName represents the header name of accept language
|
// AcceptLanguageHeaderName represents the header name of accept language
|
||||||
const AcceptLanguageHeaderName = "Accept-Language"
|
const AcceptLanguageHeaderName = "Accept-Language"
|
||||||
|
|
||||||
|
// RemoteClientPortHeader represents the header name of remote client source port
|
||||||
|
const RemoteClientPortHeader = "X-Real-Port"
|
||||||
|
|
||||||
// ClientTimezoneOffsetHeaderName represents the header name of client timezone offset
|
// ClientTimezoneOffsetHeaderName represents the header name of client timezone offset
|
||||||
const ClientTimezoneOffsetHeaderName = "X-Timezone-Offset"
|
const ClientTimezoneOffsetHeaderName = "X-Timezone-Offset"
|
||||||
|
|
||||||
@@ -25,6 +29,36 @@ type Context struct {
|
|||||||
// DO NOT ADD ANY FIELD IN THIS CONTEXT, THIS CONTEXT IS JUST A WRAPPER
|
// DO NOT ADD ANY FIELD IN THIS CONTEXT, THIS CONTEXT IS JUST A WRAPPER
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Context) ClientPort() uint16 {
|
||||||
|
remotePort := c.GetHeader(RemoteClientPortHeader)
|
||||||
|
|
||||||
|
if remotePort != "" {
|
||||||
|
remotePortNum, err := strconv.ParseInt(remotePort, 10, 32)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
return uint16(remotePortNum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Request == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
_, remotePort, err := net.SplitHostPort(c.Request.RemoteAddr)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
remotePortNum, err := strconv.ParseInt(remotePort, 10, 32)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return uint16(remotePortNum)
|
||||||
|
}
|
||||||
|
|
||||||
// SetRequestId sets the given request id to context
|
// SetRequestId sets the given request id to context
|
||||||
func (c *Context) SetRequestId(requestId string) {
|
func (c *Context) SetRequestId(requestId string) {
|
||||||
c.Set(requestIdFieldKey, requestId)
|
c.Set(requestIdFieldKey, requestId)
|
||||||
|
|||||||
+4
-1
@@ -12,8 +12,11 @@ type MiddlewareHandlerFunc func(*Context)
|
|||||||
// ApiHandlerFunc represents the api handler function
|
// ApiHandlerFunc represents the api handler function
|
||||||
type ApiHandlerFunc func(*Context) (any, *errs.Error)
|
type ApiHandlerFunc func(*Context) (any, *errs.Error)
|
||||||
|
|
||||||
// DataHandlerFunc represents the handler function that returns byte array
|
// DataHandlerFunc represents the handler function that returns file data byte array and file name
|
||||||
type DataHandlerFunc func(*Context) ([]byte, string, *errs.Error)
|
type DataHandlerFunc func(*Context) ([]byte, string, *errs.Error)
|
||||||
|
|
||||||
|
// ImageHandlerFunc represents the handler function that returns image byte array and content type
|
||||||
|
type ImageHandlerFunc func(*Context) ([]byte, string, *errs.Error)
|
||||||
|
|
||||||
// ProxyHandlerFunc represents the reverse proxy handler function
|
// ProxyHandlerFunc represents the reverse proxy handler function
|
||||||
type ProxyHandlerFunc func(*Context) (*httputil.ReverseProxy, *errs.Error)
|
type ProxyHandlerFunc func(*Context) (*httputil.ReverseProxy, *errs.Error)
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DecimalSeparator represents the type of decimal separator
|
||||||
|
type DecimalSeparator byte
|
||||||
|
|
||||||
|
// Decimal Separator
|
||||||
|
const (
|
||||||
|
DECIMAL_SEPARATOR_DEFAULT DecimalSeparator = 0
|
||||||
|
DECIMAL_SEPARATOR_DOT DecimalSeparator = 1
|
||||||
|
DECIMAL_SEPARATOR_COMMA DecimalSeparator = 2
|
||||||
|
DECIMAL_SEPARATOR_SPACE DecimalSeparator = 3
|
||||||
|
DECIMAL_SEPARATOR_INVALID DecimalSeparator = 255
|
||||||
|
)
|
||||||
|
|
||||||
|
// String returns a textual representation of the decimal separator enum
|
||||||
|
func (f DecimalSeparator) String() string {
|
||||||
|
switch f {
|
||||||
|
case DECIMAL_SEPARATOR_DEFAULT:
|
||||||
|
return "Default"
|
||||||
|
case DECIMAL_SEPARATOR_DOT:
|
||||||
|
return "Dot"
|
||||||
|
case DECIMAL_SEPARATOR_COMMA:
|
||||||
|
return "Comma"
|
||||||
|
case DECIMAL_SEPARATOR_SPACE:
|
||||||
|
return "Space"
|
||||||
|
case DECIMAL_SEPARATOR_INVALID:
|
||||||
|
return "Invalid"
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("Invalid(%d)", int(f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DigitGroupingSymbol represents the digit grouping symbol
|
||||||
|
type DigitGroupingSymbol byte
|
||||||
|
|
||||||
|
// Digit Grouping Symbol
|
||||||
|
const (
|
||||||
|
DIGIT_GROUPING_SYMBOL_DEFAULT DigitGroupingSymbol = 0
|
||||||
|
DIGIT_GROUPING_SYMBOL_DOT DigitGroupingSymbol = 1
|
||||||
|
DIGIT_GROUPING_SYMBOL_COMMA DigitGroupingSymbol = 2
|
||||||
|
DIGIT_GROUPING_SYMBOL_SPACE DigitGroupingSymbol = 3
|
||||||
|
DIGIT_GROUPING_SYMBOL_APOSTROPHE DigitGroupingSymbol = 4
|
||||||
|
DIGIT_GROUPING_SYMBOL_INVALID DigitGroupingSymbol = 255
|
||||||
|
)
|
||||||
|
|
||||||
|
// String returns a textual representation of the digit grouping symbol enum
|
||||||
|
func (f DigitGroupingSymbol) String() string {
|
||||||
|
switch f {
|
||||||
|
case DIGIT_GROUPING_SYMBOL_DEFAULT:
|
||||||
|
return "Default"
|
||||||
|
case DIGIT_GROUPING_SYMBOL_DOT:
|
||||||
|
return "Dot"
|
||||||
|
case DIGIT_GROUPING_SYMBOL_COMMA:
|
||||||
|
return "Comma"
|
||||||
|
case DIGIT_GROUPING_SYMBOL_SPACE:
|
||||||
|
return "Space"
|
||||||
|
case DIGIT_GROUPING_SYMBOL_APOSTROPHE:
|
||||||
|
return "Apostrophe"
|
||||||
|
case DIGIT_GROUPING_SYMBOL_INVALID:
|
||||||
|
return "Invalid"
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("Invalid(%d)", int(f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DigitGroupingType represents digit grouping type
|
||||||
|
type DigitGroupingType byte
|
||||||
|
|
||||||
|
// Digit Grouping Type
|
||||||
|
const (
|
||||||
|
DIGIT_GROUPING_TYPE_DEFAULT DigitGroupingType = 0
|
||||||
|
DIGIT_GROUPING_TYPE_NONE DigitGroupingType = 1
|
||||||
|
DIGIT_GROUPING_TYPE_THOUSANDS_SEPARATOR DigitGroupingType = 2
|
||||||
|
DIGIT_GROUPING_TYPE_INVALID DigitGroupingType = 255
|
||||||
|
)
|
||||||
|
|
||||||
|
// String returns a textual representation of the digit grouping type enum
|
||||||
|
func (d DigitGroupingType) String() string {
|
||||||
|
switch d {
|
||||||
|
case DIGIT_GROUPING_TYPE_DEFAULT:
|
||||||
|
return "Default"
|
||||||
|
case DIGIT_GROUPING_TYPE_NONE:
|
||||||
|
return "None"
|
||||||
|
case DIGIT_GROUPING_TYPE_THOUSANDS_SEPARATOR:
|
||||||
|
return "Thousands Separator"
|
||||||
|
case DIGIT_GROUPING_TYPE_INVALID:
|
||||||
|
return "Invalid"
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("Invalid(%d)", int(d))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,7 +33,7 @@ func (c *XOrmContextAdapter) Err() error {
|
|||||||
// if no value is associated with key.
|
// if no value is associated with key.
|
||||||
func (c *XOrmContextAdapter) Value(key any) any {
|
func (c *XOrmContextAdapter) Value(key any) any {
|
||||||
if key == log.SessionIDKey && c.requestId != "" {
|
if key == log.SessionIDKey && c.requestId != "" {
|
||||||
return fmt.Sprintf("r=%s", c.requestId)
|
return fmt.Sprintf("%s", c.requestId)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package duplicatechecker
|
||||||
|
|
||||||
|
// DuplicateChecker is common duplicate checker interface
|
||||||
|
type DuplicateChecker interface {
|
||||||
|
Get(checkerType DuplicateCheckerType, uid int64, identification string) (bool, string)
|
||||||
|
Set(checkerType DuplicateCheckerType, uid int64, identification string, remark string)
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package duplicatechecker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DuplicateCheckerContainer contains the current duplicate checker
|
||||||
|
type DuplicateCheckerContainer struct {
|
||||||
|
Current DuplicateChecker
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize a duplicate checker container singleton instance
|
||||||
|
var (
|
||||||
|
Container = &DuplicateCheckerContainer{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// InitializeDuplicateChecker initializes the current duplicate checker according to the config
|
||||||
|
func InitializeDuplicateChecker(config *settings.Config) error {
|
||||||
|
if config.DuplicateCheckerType == settings.InMemoryDuplicateCheckerType {
|
||||||
|
checker, err := NewInMemoryDuplicateChecker(config)
|
||||||
|
Container.Current = checker
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs.ErrInvalidDuplicateCheckerType
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns whether the same submission has been processed and related remark by the current duplicate checker
|
||||||
|
func (c *DuplicateCheckerContainer) Get(checkerType DuplicateCheckerType, uid int64, identification string) (bool, string) {
|
||||||
|
return c.Current.Get(checkerType, uid, identification)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set saves the identification and remark to in-memory cache by the current duplicate checker
|
||||||
|
func (c *DuplicateCheckerContainer) Set(checkerType DuplicateCheckerType, uid int64, identification string, remark string) {
|
||||||
|
c.Current.Set(checkerType, uid, identification, remark)
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package duplicatechecker
|
||||||
|
|
||||||
|
// DuplicateCheckerType represents duplicate checker type
|
||||||
|
type DuplicateCheckerType uint8
|
||||||
|
|
||||||
|
// Types of uuid
|
||||||
|
const (
|
||||||
|
DUPLICATE_CHECKER_TYPE_DEFAULT DuplicateCheckerType = 0
|
||||||
|
DUPLICATE_CHECKER_TYPE_NEW_ACCOUNT DuplicateCheckerType = 1
|
||||||
|
DUPLICATE_CHECKER_TYPE_NEW_CATEGORY DuplicateCheckerType = 2
|
||||||
|
DUPLICATE_CHECKER_TYPE_NEW_TRANSACTION DuplicateCheckerType = 3
|
||||||
|
DUPLICATE_CHECKER_TYPE_NEW_TEMPLATE DuplicateCheckerType = 4
|
||||||
|
)
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package duplicatechecker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/patrickmn/go-cache"
|
||||||
|
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InMemoryDuplicateChecker represents in-memory duplicate checker
|
||||||
|
type InMemoryDuplicateChecker struct {
|
||||||
|
cache *cache.Cache
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInMemoryDuplicateChecker returns a new in-memory duplicate checker
|
||||||
|
func NewInMemoryDuplicateChecker(config *settings.Config) (*InMemoryDuplicateChecker, error) {
|
||||||
|
checker := &InMemoryDuplicateChecker{
|
||||||
|
cache: cache.New(config.DuplicateSubmissionsIntervalDuration, config.InMemoryDuplicateCheckerCleanupIntervalDuration),
|
||||||
|
}
|
||||||
|
|
||||||
|
return checker, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns whether the same submission has been processed and related remark
|
||||||
|
func (c *InMemoryDuplicateChecker) Get(checkerType DuplicateCheckerType, uid int64, identification string) (bool, string) {
|
||||||
|
existedRemark, found := c.cache.Get(c.getCacheKey(checkerType, uid, identification))
|
||||||
|
|
||||||
|
if found {
|
||||||
|
return true, existedRemark.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set saves the identification and remark to in-memory cache
|
||||||
|
func (c *InMemoryDuplicateChecker) Set(checkerType DuplicateCheckerType, uid int64, identification string, remark string) {
|
||||||
|
c.cache.Set(c.getCacheKey(checkerType, uid, identification), remark, cache.DefaultExpiration)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *InMemoryDuplicateChecker) getCacheKey(checkerType DuplicateCheckerType, uid int64, identification string) string {
|
||||||
|
return fmt.Sprintf("%d|%d|%s", checkerType, uid, identification)
|
||||||
|
}
|
||||||
+6
-5
@@ -8,14 +8,15 @@ var (
|
|||||||
ErrAccountNotFound = NewNormalError(NormalSubcategoryAccount, 1, http.StatusBadRequest, "account not found")
|
ErrAccountNotFound = NewNormalError(NormalSubcategoryAccount, 1, http.StatusBadRequest, "account not found")
|
||||||
ErrAccountTypeInvalid = NewNormalError(NormalSubcategoryAccount, 2, http.StatusBadRequest, "account type is invalid")
|
ErrAccountTypeInvalid = NewNormalError(NormalSubcategoryAccount, 2, http.StatusBadRequest, "account type is invalid")
|
||||||
ErrAccountCurrencyInvalid = NewNormalError(NormalSubcategoryAccount, 3, http.StatusBadRequest, "account currency is invalid")
|
ErrAccountCurrencyInvalid = NewNormalError(NormalSubcategoryAccount, 3, http.StatusBadRequest, "account currency is invalid")
|
||||||
ErrAccountHaveNoSubAccount = NewNormalError(NormalSubcategoryAccount, 4, http.StatusBadRequest, "account must have at least one sub account")
|
ErrAccountHaveNoSubAccount = NewNormalError(NormalSubcategoryAccount, 4, http.StatusBadRequest, "account must have at least one sub-account")
|
||||||
ErrAccountCannotHaveSubAccounts = NewNormalError(NormalSubcategoryAccount, 5, http.StatusBadRequest, "account cannot have sub accounts")
|
ErrAccountCannotHaveSubAccounts = NewNormalError(NormalSubcategoryAccount, 5, http.StatusBadRequest, "account cannot have sub-accounts")
|
||||||
ErrParentAccountCannotSetCurrency = NewNormalError(NormalSubcategoryAccount, 6, http.StatusBadRequest, "parent account cannot set currency")
|
ErrParentAccountCannotSetCurrency = NewNormalError(NormalSubcategoryAccount, 6, http.StatusBadRequest, "parent account cannot set currency")
|
||||||
ErrParentAccountCannotSetBalance = NewNormalError(NormalSubcategoryAccount, 7, http.StatusBadRequest, "parent account cannot set balance")
|
ErrParentAccountCannotSetBalance = NewNormalError(NormalSubcategoryAccount, 7, http.StatusBadRequest, "parent account cannot set balance")
|
||||||
ErrSubAccountCategoryNotEqualsToParent = NewNormalError(NormalSubcategoryAccount, 8, http.StatusBadRequest, "sub account category not equals to parent")
|
ErrSubAccountCategoryNotEqualsToParent = NewNormalError(NormalSubcategoryAccount, 8, http.StatusBadRequest, "sub-account category not equals to parent")
|
||||||
ErrSubAccountTypeInvalid = NewNormalError(NormalSubcategoryAccount, 9, http.StatusBadRequest, "sub account type invalid")
|
ErrSubAccountTypeInvalid = NewNormalError(NormalSubcategoryAccount, 9, http.StatusBadRequest, "sub-account type invalid")
|
||||||
ErrCannotAddOrDeleteSubAccountsWhenModify = NewNormalError(NormalSubcategoryAccount, 10, http.StatusBadRequest, "cannot add or delete sub accounts when modify account")
|
ErrCannotAddOrDeleteSubAccountsWhenModify = NewNormalError(NormalSubcategoryAccount, 10, http.StatusBadRequest, "cannot add or delete sub-accounts when modify account")
|
||||||
ErrSourceAccountNotFound = NewNormalError(NormalSubcategoryAccount, 11, http.StatusBadRequest, "source account not found")
|
ErrSourceAccountNotFound = NewNormalError(NormalSubcategoryAccount, 11, http.StatusBadRequest, "source account not found")
|
||||||
ErrDestinationAccountNotFound = NewNormalError(NormalSubcategoryAccount, 12, http.StatusBadRequest, "destination account not found")
|
ErrDestinationAccountNotFound = NewNormalError(NormalSubcategoryAccount, 12, http.StatusBadRequest, "destination account not found")
|
||||||
ErrAccountInUseCannotBeDeleted = NewNormalError(NormalSubcategoryAccount, 13, http.StatusBadRequest, "account is in use and cannot be deleted")
|
ErrAccountInUseCannotBeDeleted = NewNormalError(NormalSubcategoryAccount, 13, http.StatusBadRequest, "account is in use and cannot be deleted")
|
||||||
|
ErrAccountCategoryInvalid = NewNormalError(NormalSubcategoryAccount, 14, http.StatusBadRequest, "account category is invalid")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ const (
|
|||||||
SystemSubcategorySetting = 1
|
SystemSubcategorySetting = 1
|
||||||
SystemSubcategoryDatabase = 2
|
SystemSubcategoryDatabase = 2
|
||||||
SystemSubcategoryMail = 3
|
SystemSubcategoryMail = 3
|
||||||
|
SystemSubcategoryLogging = 4
|
||||||
)
|
)
|
||||||
|
|
||||||
// Sub categories of normal error
|
// Sub categories of normal error
|
||||||
@@ -28,6 +29,8 @@ const (
|
|||||||
NormalSubcategoryCategory = 6
|
NormalSubcategoryCategory = 6
|
||||||
NormalSubcategoryTag = 7
|
NormalSubcategoryTag = 7
|
||||||
NormalSubcategoryDataManagement = 8
|
NormalSubcategoryDataManagement = 8
|
||||||
|
NormalSubcategoryMapProxy = 9
|
||||||
|
NormalSubcategoryTemplate = 10
|
||||||
)
|
)
|
||||||
|
|
||||||
// Error represents the specific error returned to user
|
// Error represents the specific error returned to user
|
||||||
@@ -73,6 +76,15 @@ func NewNormalError(subCategory int32, index int32, httpStatusCode int, message
|
|||||||
return New(CATEGORY_NORMAL, subCategory, index, httpStatusCode, message)
|
return New(CATEGORY_NORMAL, subCategory, index, httpStatusCode, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewLoggingError returns a new logging error instance
|
||||||
|
func NewLoggingError(message string, err ...error) *Error {
|
||||||
|
return New(ErrLoggingError.Category,
|
||||||
|
ErrLoggingError.SubCategory,
|
||||||
|
ErrLoggingError.Index,
|
||||||
|
ErrLoggingError.HttpStatusCode,
|
||||||
|
message, err...)
|
||||||
|
}
|
||||||
|
|
||||||
// NewIncompleteOrIncorrectSubmissionError returns a new incomplete or incorrect submission error instance
|
// NewIncompleteOrIncorrectSubmissionError returns a new incomplete or incorrect submission error instance
|
||||||
func NewIncompleteOrIncorrectSubmissionError(err error) *Error {
|
func NewIncompleteOrIncorrectSubmissionError(err error) *Error {
|
||||||
return New(ErrIncompleteOrIncorrectSubmission.Category,
|
return New(ErrIncompleteOrIncorrectSubmission.Category,
|
||||||
|
|||||||
+6
-1
@@ -16,7 +16,7 @@ var (
|
|||||||
ErrPageIndexInvalid = NewNormalError(NormalSubcategoryGlobal, 6, http.StatusBadRequest, "page index is invalid")
|
ErrPageIndexInvalid = NewNormalError(NormalSubcategoryGlobal, 6, http.StatusBadRequest, "page index is invalid")
|
||||||
ErrPageCountInvalid = NewNormalError(NormalSubcategoryGlobal, 7, http.StatusBadRequest, "page count is invalid")
|
ErrPageCountInvalid = NewNormalError(NormalSubcategoryGlobal, 7, http.StatusBadRequest, "page count is invalid")
|
||||||
ErrClientTimezoneOffsetInvalid = NewNormalError(NormalSubcategoryGlobal, 8, http.StatusBadRequest, "client timezone offset is invalid")
|
ErrClientTimezoneOffsetInvalid = NewNormalError(NormalSubcategoryGlobal, 8, http.StatusBadRequest, "client timezone offset is invalid")
|
||||||
ErrQueryItemsEmpty = NewNormalError(NormalSubcategoryGlobal, 9, http.StatusBadRequest, "query items cannot be empty")
|
ErrQueryItemsEmpty = NewNormalError(NormalSubcategoryGlobal, 9, http.StatusBadRequest, "query items cannot be blank")
|
||||||
ErrQueryItemsTooMuch = NewNormalError(NormalSubcategoryGlobal, 10, http.StatusBadRequest, "query items too much")
|
ErrQueryItemsTooMuch = NewNormalError(NormalSubcategoryGlobal, 10, http.StatusBadRequest, "query items too much")
|
||||||
ErrQueryItemsInvalid = NewNormalError(NormalSubcategoryGlobal, 11, http.StatusBadRequest, "query items have invalid item")
|
ErrQueryItemsInvalid = NewNormalError(NormalSubcategoryGlobal, 11, http.StatusBadRequest, "query items have invalid item")
|
||||||
ErrParameterInvalid = NewNormalError(NormalSubcategoryGlobal, 12, http.StatusBadRequest, "parameter invalid")
|
ErrParameterInvalid = NewNormalError(NormalSubcategoryGlobal, 12, http.StatusBadRequest, "parameter invalid")
|
||||||
@@ -82,3 +82,8 @@ func GetParameterInvalidCurrencyMessage(field string) string {
|
|||||||
func GetParameterInvalidHexRGBColorMessage(field string) string {
|
func GetParameterInvalidHexRGBColorMessage(field string) string {
|
||||||
return fmt.Sprintf("parameter \"%s\" is invalid color", field)
|
return fmt.Sprintf("parameter \"%s\" is invalid color", field)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetParameterInvalidAmountFilterMessage returns specific error message for invalid amount filter parameter error
|
||||||
|
func GetParameterInvalidAmountFilterMessage(field string) string {
|
||||||
|
return fmt.Sprintf("parameter \"%s\" is invalid amount filter", field)
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package errs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error codes related to logging
|
||||||
|
var (
|
||||||
|
ErrLoggingError = NewSystemError(SystemSubcategoryLogging, 0, http.StatusInternalServerError, "logging error")
|
||||||
|
)
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package errs
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// Error codes related to map image proxy
|
||||||
|
var (
|
||||||
|
ErrMapProviderNotCurrent = NewNormalError(NormalSubcategoryMapProxy, 0, http.StatusBadRequest, "specified map provider is not set")
|
||||||
|
ErrImageExtensionNotSupported = NewNormalError(NormalSubcategoryMapProxy, 0, http.StatusNotFound, "specified image extension is not supported")
|
||||||
|
)
|
||||||
+19
-7
@@ -4,11 +4,23 @@ import "net/http"
|
|||||||
|
|
||||||
// Error codes related to settings
|
// Error codes related to settings
|
||||||
var (
|
var (
|
||||||
ErrInvalidProtocol = NewSystemError(SystemSubcategorySetting, 0, http.StatusInternalServerError, "invalid server protocol")
|
ErrInvalidServerMode = NewSystemError(SystemSubcategorySetting, 0, http.StatusInternalServerError, "invalid server mode")
|
||||||
ErrInvalidLogMode = NewSystemError(SystemSubcategorySetting, 1, http.StatusInternalServerError, "invalid log mode")
|
ErrInvalidProtocol = NewSystemError(SystemSubcategorySetting, 1, http.StatusInternalServerError, "invalid server protocol")
|
||||||
ErrGettingLocalAddress = NewSystemError(SystemSubcategorySetting, 2, http.StatusInternalServerError, "failed to get local address")
|
ErrInvalidLogMode = NewSystemError(SystemSubcategorySetting, 2, http.StatusInternalServerError, "invalid log mode")
|
||||||
ErrInvalidUuidMode = NewSystemError(SystemSubcategorySetting, 3, http.StatusInternalServerError, "invalid uuid mode")
|
ErrInvalidLogLevel = NewSystemError(SystemSubcategorySetting, 3, http.StatusInternalServerError, "invalid log level")
|
||||||
ErrInvalidExchangeRatesDataSource = NewSystemError(SystemSubcategorySetting, 4, http.StatusInternalServerError, "invalid exchange rates data source")
|
ErrGettingLocalAddress = NewSystemError(SystemSubcategorySetting, 4, http.StatusInternalServerError, "failed to get local address")
|
||||||
ErrInvalidMapProvider = NewSystemError(SystemSubcategorySetting, 5, http.StatusInternalServerError, "invalid map provider")
|
ErrInvalidStorageType = NewSystemError(SystemSubcategorySetting, 5, http.StatusInternalServerError, "invalid storage type")
|
||||||
ErrInvalidAmapSecurityVerificationMethod = NewSystemError(SystemSubcategorySetting, 6, http.StatusInternalServerError, "invalid amap security verification method")
|
ErrInvalidLocalFileSystemStoragePath = NewSystemError(SystemSubcategorySetting, 6, http.StatusInternalServerError, "invalid local file system storage path")
|
||||||
|
ErrInvalidUuidMode = NewSystemError(SystemSubcategorySetting, 7, http.StatusInternalServerError, "invalid uuid mode")
|
||||||
|
ErrInvalidDuplicateCheckerType = NewSystemError(SystemSubcategorySetting, 8, http.StatusInternalServerError, "invalid duplicate checker type")
|
||||||
|
ErrInvalidInMemoryDuplicateCheckerCleanupInterval = NewSystemError(SystemSubcategorySetting, 9, http.StatusInternalServerError, "invalid in-memory duplicate checker cleanup interval")
|
||||||
|
ErrInvalidTokenExpiredTime = NewSystemError(SystemSubcategorySetting, 10, http.StatusInternalServerError, "invalid token expired time")
|
||||||
|
ErrInvalidTokenMinRefreshInterval = NewSystemError(SystemSubcategorySetting, 11, http.StatusInternalServerError, "invalid token min refresh interval")
|
||||||
|
ErrInvalidTemporaryTokenExpiredTime = NewSystemError(SystemSubcategorySetting, 12, http.StatusInternalServerError, "invalid temporary token expired time")
|
||||||
|
ErrInvalidEmailVerifyTokenExpiredTime = NewSystemError(SystemSubcategorySetting, 13, http.StatusInternalServerError, "invalid email verify token expired time")
|
||||||
|
ErrInvalidAvatarProvider = NewSystemError(SystemSubcategorySetting, 14, http.StatusInternalServerError, "invalid avatar provider")
|
||||||
|
ErrInvalidMapProvider = NewSystemError(SystemSubcategorySetting, 15, http.StatusInternalServerError, "invalid map provider")
|
||||||
|
ErrInvalidAmapSecurityVerificationMethod = NewSystemError(SystemSubcategorySetting, 16, http.StatusInternalServerError, "invalid amap security verification method")
|
||||||
|
ErrInvalidPasswordResetTokenExpiredTime = NewSystemError(SystemSubcategorySetting, 17, http.StatusInternalServerError, "invalid password reset token expired time")
|
||||||
|
ErrInvalidExchangeRatesDataSource = NewSystemError(SystemSubcategorySetting, 18, http.StatusInternalServerError, "invalid exchange rates data source")
|
||||||
)
|
)
|
||||||
|
|||||||
+7
-5
@@ -4,9 +4,11 @@ import "net/http"
|
|||||||
|
|
||||||
// Error codes related to transaction categories
|
// Error codes related to transaction categories
|
||||||
var (
|
var (
|
||||||
ErrSystemError = NewSystemError(SystemSubcategoryDefault, 0, http.StatusInternalServerError, "system error")
|
ErrSystemError = NewSystemError(SystemSubcategoryDefault, 0, http.StatusInternalServerError, "system error")
|
||||||
ErrApiNotFound = NewSystemError(SystemSubcategoryDefault, 1, http.StatusNotFound, "api not found")
|
ErrApiNotFound = NewSystemError(SystemSubcategoryDefault, 1, http.StatusNotFound, "api not found")
|
||||||
ErrMethodNotAllowed = NewSystemError(SystemSubcategoryDefault, 2, http.StatusMethodNotAllowed, "method not allowed")
|
ErrMethodNotAllowed = NewSystemError(SystemSubcategoryDefault, 2, http.StatusMethodNotAllowed, "method not allowed")
|
||||||
ErrNotImplemented = NewSystemError(SystemSubcategoryDefault, 3, http.StatusNotImplemented, "not implemented")
|
ErrNotImplemented = NewSystemError(SystemSubcategoryDefault, 3, http.StatusNotImplemented, "not implemented")
|
||||||
ErrSystemIsBusy = NewSystemError(SystemSubcategoryDefault, 4, http.StatusNotImplemented, "system is busy")
|
ErrSystemIsBusy = NewSystemError(SystemSubcategoryDefault, 4, http.StatusServiceUnavailable, "system is busy")
|
||||||
|
ErrNotSupported = NewSystemError(SystemSubcategoryDefault, 5, http.StatusBadRequest, "not supported")
|
||||||
|
ErrImageTypeNotSupported = NewSystemError(SystemSubcategoryDefault, 6, http.StatusBadRequest, "image type not supported")
|
||||||
)
|
)
|
||||||
|
|||||||
+2
-2
@@ -11,8 +11,8 @@ var (
|
|||||||
ErrCurrentInvalidToken = NewNormalError(NormalSubcategoryToken, 2, http.StatusUnauthorized, "current token is invalid")
|
ErrCurrentInvalidToken = NewNormalError(NormalSubcategoryToken, 2, http.StatusUnauthorized, "current token is invalid")
|
||||||
ErrCurrentTokenExpired = NewNormalError(NormalSubcategoryToken, 3, http.StatusUnauthorized, "current token is expired")
|
ErrCurrentTokenExpired = NewNormalError(NormalSubcategoryToken, 3, http.StatusUnauthorized, "current token is expired")
|
||||||
ErrCurrentInvalidTokenType = NewNormalError(NormalSubcategoryToken, 4, http.StatusUnauthorized, "current token type is invalid")
|
ErrCurrentInvalidTokenType = NewNormalError(NormalSubcategoryToken, 4, http.StatusUnauthorized, "current token type is invalid")
|
||||||
ErrCurrentTokenRequire2FA = NewNormalError(NormalSubcategoryToken, 5, http.StatusUnauthorized, "current token requires two factor authorization")
|
ErrCurrentTokenRequire2FA = NewNormalError(NormalSubcategoryToken, 5, http.StatusUnauthorized, "current token requires two-factor authorization")
|
||||||
ErrCurrentTokenNotRequire2FA = NewNormalError(NormalSubcategoryToken, 6, http.StatusUnauthorized, "current token does not require two factor authorization")
|
ErrCurrentTokenNotRequire2FA = NewNormalError(NormalSubcategoryToken, 6, http.StatusUnauthorized, "current token does not require two-factor authorization")
|
||||||
ErrInvalidToken = NewNormalError(NormalSubcategoryToken, 7, http.StatusBadRequest, "token is invalid")
|
ErrInvalidToken = NewNormalError(NormalSubcategoryToken, 7, http.StatusBadRequest, "token is invalid")
|
||||||
ErrInvalidTokenId = NewNormalError(NormalSubcategoryToken, 8, http.StatusBadRequest, "token id is invalid")
|
ErrInvalidTokenId = NewNormalError(NormalSubcategoryToken, 8, http.StatusBadRequest, "token id is invalid")
|
||||||
ErrInvalidUserTokenId = NewNormalError(NormalSubcategoryToken, 9, http.StatusBadRequest, "user token id is invalid")
|
ErrInvalidUserTokenId = NewNormalError(NormalSubcategoryToken, 9, http.StatusBadRequest, "user token id is invalid")
|
||||||
|
|||||||
@@ -18,7 +18,13 @@ var (
|
|||||||
ErrCannotAddTransactionToHiddenAccount = NewNormalError(NormalSubcategoryTransaction, 11, http.StatusBadRequest, "cannot add transaction to hidden account")
|
ErrCannotAddTransactionToHiddenAccount = NewNormalError(NormalSubcategoryTransaction, 11, http.StatusBadRequest, "cannot add transaction to hidden account")
|
||||||
ErrCannotModifyTransactionInHiddenAccount = NewNormalError(NormalSubcategoryTransaction, 12, http.StatusBadRequest, "cannot modify transaction of hidden account")
|
ErrCannotModifyTransactionInHiddenAccount = NewNormalError(NormalSubcategoryTransaction, 12, http.StatusBadRequest, "cannot modify transaction of hidden account")
|
||||||
ErrCannotDeleteTransactionInHiddenAccount = NewNormalError(NormalSubcategoryTransaction, 13, http.StatusBadRequest, "cannot delete transaction in hidden account")
|
ErrCannotDeleteTransactionInHiddenAccount = NewNormalError(NormalSubcategoryTransaction, 13, http.StatusBadRequest, "cannot delete transaction in hidden account")
|
||||||
ErrCannotCreateTransactionWithThisTransactionTime = NewNormalError(NormalSubcategoryTransaction, 14, http.StatusBadRequest, "cannot add transaction with this transaction time")
|
ErrCannotAddTransactionToParentAccount = NewNormalError(NormalSubcategoryTransaction, 14, http.StatusBadRequest, "cannot add transaction to parent account")
|
||||||
ErrCannotModifyTransactionWithThisTransactionTime = NewNormalError(NormalSubcategoryTransaction, 15, http.StatusBadRequest, "cannot modify transaction with this transaction time")
|
ErrCannotModifyTransactionInParentAccount = NewNormalError(NormalSubcategoryTransaction, 15, http.StatusBadRequest, "cannot modify transaction of parent account")
|
||||||
ErrCannotDeleteTransactionWithThisTransactionTime = NewNormalError(NormalSubcategoryTransaction, 16, http.StatusBadRequest, "cannot delete transaction with this transaction time")
|
ErrCannotDeleteTransactionInParentAccount = NewNormalError(NormalSubcategoryTransaction, 16, http.StatusBadRequest, "cannot delete transaction in parent account")
|
||||||
|
ErrCannotCreateTransactionWithThisTransactionTime = NewNormalError(NormalSubcategoryTransaction, 17, http.StatusBadRequest, "cannot add transaction with this transaction time")
|
||||||
|
ErrCannotModifyTransactionWithThisTransactionTime = NewNormalError(NormalSubcategoryTransaction, 18, http.StatusBadRequest, "cannot modify transaction with this transaction time")
|
||||||
|
ErrCannotDeleteTransactionWithThisTransactionTime = NewNormalError(NormalSubcategoryTransaction, 19, http.StatusBadRequest, "cannot delete transaction with this transaction time")
|
||||||
|
ErrCannotUseHiddenAccount = NewNormalError(NormalSubcategoryTransaction, 20, http.StatusBadRequest, "cannot use hidden account")
|
||||||
|
ErrCannotUseHiddenTransactionCategory = NewNormalError(NormalSubcategoryTransaction, 21, http.StatusBadRequest, "cannot use hidden transaction category")
|
||||||
|
ErrCannotUseHiddenTransactionTag = NewNormalError(NormalSubcategoryTransaction, 22, http.StatusBadRequest, "cannot use hidden transaction tag")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,11 +4,15 @@ import "net/http"
|
|||||||
|
|
||||||
// Error codes related to transaction categories
|
// Error codes related to transaction categories
|
||||||
var (
|
var (
|
||||||
ErrTransactionCategoryIdInvalid = NewNormalError(NormalSubcategoryCategory, 0, http.StatusBadRequest, "transaction category id is invalid")
|
ErrTransactionCategoryIdInvalid = NewNormalError(NormalSubcategoryCategory, 0, http.StatusBadRequest, "transaction category id is invalid")
|
||||||
ErrTransactionCategoryNotFound = NewNormalError(NormalSubcategoryCategory, 1, http.StatusBadRequest, "transaction category not found")
|
ErrTransactionCategoryNotFound = NewNormalError(NormalSubcategoryCategory, 1, http.StatusBadRequest, "transaction category not found")
|
||||||
ErrTransactionCategoryTypeInvalid = NewNormalError(NormalSubcategoryCategory, 2, http.StatusBadRequest, "transaction category type is invalid")
|
ErrTransactionCategoryTypeInvalid = NewNormalError(NormalSubcategoryCategory, 2, http.StatusBadRequest, "transaction category type is invalid")
|
||||||
ErrParentTransactionCategoryNotFound = NewNormalError(NormalSubcategoryCategory, 3, http.StatusBadRequest, "parent transaction category not found")
|
ErrParentTransactionCategoryNotFound = NewNormalError(NormalSubcategoryCategory, 3, http.StatusBadRequest, "parent transaction category not found")
|
||||||
ErrCannotAddToSecondaryTransactionCategory = NewNormalError(NormalSubcategoryCategory, 4, http.StatusBadRequest, "cannot add to secondary transaction category")
|
ErrCannotAddToSecondaryTransactionCategory = NewNormalError(NormalSubcategoryCategory, 4, http.StatusBadRequest, "cannot add to secondary transaction category")
|
||||||
ErrCannotUsePrimaryCategoryForTransaction = NewNormalError(NormalSubcategoryCategory, 5, http.StatusBadRequest, "cannot use primary category for transaction category")
|
ErrCannotUsePrimaryCategoryForTransaction = NewNormalError(NormalSubcategoryCategory, 5, http.StatusBadRequest, "cannot use primary category for transaction category")
|
||||||
ErrTransactionCategoryInUseCannotBeDeleted = NewNormalError(NormalSubcategoryCategory, 6, http.StatusBadRequest, "transaction category is in use and cannot be deleted")
|
ErrTransactionCategoryInUseCannotBeDeleted = NewNormalError(NormalSubcategoryCategory, 6, http.StatusBadRequest, "transaction category is in use and cannot be deleted")
|
||||||
|
ErrNotAllowChangePrimaryTransactionCategoryToSecondary = NewNormalError(NormalSubcategoryCategory, 7, http.StatusBadRequest, "not allow to change primary category to secondary category")
|
||||||
|
ErrNotAllowChangeSecondaryTransactionCategoryToPrimary = NewNormalError(NormalSubcategoryCategory, 8, http.StatusBadRequest, "not allow to change secondary category to primary category")
|
||||||
|
ErrNotAllowChangePrimaryTransactionType = NewNormalError(NormalSubcategoryCategory, 9, http.StatusBadRequest, "not allow to change primary category with different type")
|
||||||
|
ErrNotAllowUseSecondaryTransactionAsPrimaryCategory = NewNormalError(NormalSubcategoryCategory, 10, http.StatusBadRequest, "not allow to use secondary category as primary category")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,4 +9,5 @@ var (
|
|||||||
ErrTransactionTagNameIsEmpty = NewNormalError(NormalSubcategoryTag, 2, http.StatusBadRequest, "transaction tag name is empty")
|
ErrTransactionTagNameIsEmpty = NewNormalError(NormalSubcategoryTag, 2, http.StatusBadRequest, "transaction tag name is empty")
|
||||||
ErrTransactionTagNameAlreadyExists = NewNormalError(NormalSubcategoryTag, 3, http.StatusBadRequest, "transaction tag name already exists")
|
ErrTransactionTagNameAlreadyExists = NewNormalError(NormalSubcategoryTag, 3, http.StatusBadRequest, "transaction tag name already exists")
|
||||||
ErrTransactionTagInUseCannotBeDeleted = NewNormalError(NormalSubcategoryTag, 4, http.StatusBadRequest, "transaction tag is in use and cannot be deleted")
|
ErrTransactionTagInUseCannotBeDeleted = NewNormalError(NormalSubcategoryTag, 4, http.StatusBadRequest, "transaction tag is in use and cannot be deleted")
|
||||||
|
ErrTransactionTagIndexNotFound = NewNormalError(NormalSubcategoryTag, 5, http.StatusBadRequest, "transaction tag index not found")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package errs
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// Error codes related to transaction templates
|
||||||
|
var (
|
||||||
|
ErrTransactionTemplateIdInvalid = NewNormalError(NormalSubcategoryTemplate, 0, http.StatusBadRequest, "transaction template id is invalid")
|
||||||
|
ErrTransactionTemplateNotFound = NewNormalError(NormalSubcategoryTemplate, 1, http.StatusBadRequest, "transaction template not found")
|
||||||
|
ErrTransactionTemplateTypeInvalid = NewNormalError(NormalSubcategoryTemplate, 2, http.StatusBadRequest, "transaction template type is invalid")
|
||||||
|
)
|
||||||
@@ -2,11 +2,11 @@ package errs
|
|||||||
|
|
||||||
import "net/http"
|
import "net/http"
|
||||||
|
|
||||||
// Error codes related to two factor authorization
|
// Error codes related to two-factor authorization
|
||||||
var (
|
var (
|
||||||
ErrPasscodeInvalid = NewNormalError(NormalSubcategoryTwofactor, 0, http.StatusUnauthorized, "passcode is invalid")
|
ErrPasscodeInvalid = NewNormalError(NormalSubcategoryTwofactor, 0, http.StatusUnauthorized, "passcode is invalid")
|
||||||
ErrTwoFactorRecoveryCodeInvalid = NewNormalError(NormalSubcategoryTwofactor, 1, http.StatusUnauthorized, "two factor backup code is invalid")
|
ErrTwoFactorRecoveryCodeInvalid = NewNormalError(NormalSubcategoryTwofactor, 1, http.StatusUnauthorized, "two-factor backup code is invalid")
|
||||||
ErrTwoFactorRecoveryCodeNotExist = NewNormalError(NormalSubcategoryTwofactor, 2, http.StatusUnauthorized, "two factor backup code does not exist")
|
ErrTwoFactorRecoveryCodeNotExist = NewNormalError(NormalSubcategoryTwofactor, 2, http.StatusUnauthorized, "two-factor backup code does not exist")
|
||||||
ErrTwoFactorIsNotEnabled = NewNormalError(NormalSubcategoryTwofactor, 3, http.StatusBadRequest, "two factor is not enabled")
|
ErrTwoFactorIsNotEnabled = NewNormalError(NormalSubcategoryTwofactor, 3, http.StatusBadRequest, "two-factor is not enabled")
|
||||||
ErrTwoFactorAlreadyEnabled = NewNormalError(NormalSubcategoryTwofactor, 4, http.StatusBadRequest, "two factor has already been enabled")
|
ErrTwoFactorAlreadyEnabled = NewNormalError(NormalSubcategoryTwofactor, 4, http.StatusBadRequest, "two-factor has already been enabled")
|
||||||
)
|
)
|
||||||
|
|||||||
+28
-23
@@ -6,27 +6,32 @@ import (
|
|||||||
|
|
||||||
// Error codes related to users
|
// Error codes related to users
|
||||||
var (
|
var (
|
||||||
ErrLoginNameInvalid = NewNormalError(NormalSubcategoryUser, 0, http.StatusUnauthorized, "login name is invalid")
|
ErrLoginNameInvalid = NewNormalError(NormalSubcategoryUser, 0, http.StatusUnauthorized, "login name is invalid")
|
||||||
ErrLoginNameOrPasswordInvalid = NewNormalError(NormalSubcategoryUser, 1, http.StatusUnauthorized, "login name or password is invalid")
|
ErrLoginNameOrPasswordInvalid = NewNormalError(NormalSubcategoryUser, 1, http.StatusUnauthorized, "login name or password is invalid")
|
||||||
ErrLoginNameOrPasswordWrong = NewNormalError(NormalSubcategoryUser, 2, http.StatusUnauthorized, "login name or password is wrong")
|
ErrLoginNameOrPasswordWrong = NewNormalError(NormalSubcategoryUser, 2, http.StatusUnauthorized, "login name or password is wrong")
|
||||||
ErrUserIdInvalid = NewNormalError(NormalSubcategoryUser, 3, http.StatusBadRequest, "user id is invalid")
|
ErrUserIdInvalid = NewNormalError(NormalSubcategoryUser, 3, http.StatusBadRequest, "user id is invalid")
|
||||||
ErrUsernameIsEmpty = NewNormalError(NormalSubcategoryUser, 4, http.StatusBadRequest, "username is empty")
|
ErrUsernameIsEmpty = NewNormalError(NormalSubcategoryUser, 4, http.StatusBadRequest, "username is empty")
|
||||||
ErrEmailIsEmpty = NewNormalError(NormalSubcategoryUser, 5, http.StatusBadRequest, "email is empty")
|
ErrEmailIsEmpty = NewNormalError(NormalSubcategoryUser, 5, http.StatusBadRequest, "email is empty")
|
||||||
ErrNicknameIsEmpty = NewNormalError(NormalSubcategoryUser, 6, http.StatusBadRequest, "nickname is empty")
|
ErrNicknameIsEmpty = NewNormalError(NormalSubcategoryUser, 6, http.StatusBadRequest, "nickname is empty")
|
||||||
ErrPasswordIsEmpty = NewNormalError(NormalSubcategoryUser, 7, http.StatusBadRequest, "password is empty")
|
ErrPasswordIsEmpty = NewNormalError(NormalSubcategoryUser, 7, http.StatusBadRequest, "password is empty")
|
||||||
ErrUserDefaultCurrencyIsEmpty = NewNormalError(NormalSubcategoryUser, 8, http.StatusBadRequest, "user default currency is empty")
|
ErrUserDefaultCurrencyIsEmpty = NewNormalError(NormalSubcategoryUser, 8, http.StatusBadRequest, "user default currency is empty")
|
||||||
ErrUserDefaultCurrencyIsInvalid = NewNormalError(NormalSubcategoryUser, 9, http.StatusBadRequest, "user default currency is invalid")
|
ErrUserDefaultCurrencyIsInvalid = NewNormalError(NormalSubcategoryUser, 9, http.StatusBadRequest, "user default currency is invalid")
|
||||||
ErrUserNotFound = NewNormalError(NormalSubcategoryUser, 10, http.StatusBadRequest, "user not found")
|
ErrUserNotFound = NewNormalError(NormalSubcategoryUser, 10, http.StatusBadRequest, "user not found")
|
||||||
ErrUserPasswordWrong = NewNormalError(NormalSubcategoryUser, 11, http.StatusBadRequest, "password is wrong")
|
ErrUserPasswordWrong = NewNormalError(NormalSubcategoryUser, 11, http.StatusBadRequest, "password is wrong")
|
||||||
ErrUsernameAlreadyExists = NewNormalError(NormalSubcategoryUser, 12, http.StatusBadRequest, "username already exists")
|
ErrUsernameAlreadyExists = NewNormalError(NormalSubcategoryUser, 12, http.StatusBadRequest, "username already exists")
|
||||||
ErrUserEmailAlreadyExists = NewNormalError(NormalSubcategoryUser, 13, http.StatusBadRequest, "email already exists")
|
ErrUserEmailAlreadyExists = NewNormalError(NormalSubcategoryUser, 13, http.StatusBadRequest, "email already exists")
|
||||||
ErrUserRegistrationNotAllowed = NewNormalError(NormalSubcategoryUser, 14, http.StatusBadRequest, "user registration not allowed")
|
ErrUserRegistrationNotAllowed = NewNormalError(NormalSubcategoryUser, 14, http.StatusBadRequest, "user registration not allowed")
|
||||||
ErrUserDefaultAccountIsInvalid = NewNormalError(NormalSubcategoryUser, 15, http.StatusBadRequest, "user default account is invalid")
|
ErrUserDefaultAccountIsInvalid = NewNormalError(NormalSubcategoryUser, 15, http.StatusBadRequest, "user default account is invalid")
|
||||||
ErrUserIsDisabled = NewNormalError(NormalSubcategoryUser, 16, http.StatusBadRequest, "user is disabled")
|
ErrUserIsDisabled = NewNormalError(NormalSubcategoryUser, 16, http.StatusBadRequest, "user is disabled")
|
||||||
ErrEmptyIsInvalid = NewNormalError(NormalSubcategoryUser, 17, http.StatusBadRequest, "email is invalid")
|
ErrEmptyIsInvalid = NewNormalError(NormalSubcategoryUser, 17, http.StatusBadRequest, "email is invalid")
|
||||||
ErrEmailIsEmptyOrInvalid = NewNormalError(NormalSubcategoryUser, 18, http.StatusBadRequest, "email is empty or invalid")
|
ErrEmailIsEmptyOrInvalid = NewNormalError(NormalSubcategoryUser, 18, http.StatusBadRequest, "email is empty or invalid")
|
||||||
ErrNewPasswordEqualsOldInvalid = NewNormalError(NormalSubcategoryUser, 19, http.StatusBadRequest, "new password equals old password")
|
ErrNewPasswordEqualsOldInvalid = NewNormalError(NormalSubcategoryUser, 19, http.StatusBadRequest, "new password equals old password")
|
||||||
ErrEmailIsNotVerified = NewNormalError(NormalSubcategoryUser, 20, http.StatusBadRequest, "email is not verified")
|
ErrEmailIsNotVerified = NewNormalError(NormalSubcategoryUser, 20, http.StatusBadRequest, "email is not verified")
|
||||||
ErrEmailIsVerified = NewNormalError(NormalSubcategoryUser, 21, http.StatusBadRequest, "email is verified")
|
ErrEmailIsVerified = NewNormalError(NormalSubcategoryUser, 21, http.StatusBadRequest, "email is verified")
|
||||||
ErrEmailValidationNotAllowed = NewNormalError(NormalSubcategoryUser, 22, http.StatusBadRequest, "email validation not allowed")
|
ErrEmailValidationNotAllowed = NewNormalError(NormalSubcategoryUser, 22, http.StatusBadRequest, "email validation not allowed")
|
||||||
|
ErrDecimalSeparatorAndDigitGroupingSymbolCannotBeEqual = NewNormalError(NormalSubcategoryUser, 23, http.StatusBadRequest, "decimal separator and digit grouping symbol cannot be equal")
|
||||||
|
ErrUserDefaultAccountIsHidden = NewNormalError(NormalSubcategoryUser, 24, http.StatusBadRequest, "user default account is hidden")
|
||||||
|
ErrNoUserAvatar = NewNormalError(NormalSubcategoryUser, 25, http.StatusBadRequest, "no user avatar")
|
||||||
|
ErrUserAvatarIsEmpty = NewNormalError(NormalSubcategoryUser, 26, http.StatusBadRequest, "user avatar is empty")
|
||||||
|
ErrUserAvatarNoExists = NewNormalError(NormalSubcategoryUser, 27, http.StatusNotFound, "user avatar not exists")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ import (
|
|||||||
"github.com/mayswind/ezbookkeeping/pkg/validators"
|
"github.com/mayswind/ezbookkeeping/pkg/validators"
|
||||||
)
|
)
|
||||||
|
|
||||||
const nationalBankOfPolandDailyExchangeRateUrl = "https://www.nbp.pl/kursy/xml/en/lastaen.xml"
|
const nationalBankOfPolandDailyExchangeRateUrl = "https://api.nbp.pl/api/exchangerates/tables/A?format=xml"
|
||||||
const nationalBankOfPolandInconvertibleCurrencyExchangeRateUrl = "https://www.nbp.pl/kursy/xml/en/lastben.xml"
|
const nationalBankOfPolandInconvertibleCurrencyExchangeRateUrl = "https://api.nbp.pl/api/exchangerates/tables/B?format=xml"
|
||||||
const nationalBankOfPolandExchangeRateReferenceUrl = "https://www.nbp.pl/homen.aspx?f=/kursy/kursyen.htm"
|
const nationalBankOfPolandExchangeRateReferenceUrl = "https://nbp.pl/en/statistic-and-financial-reporting/rates/"
|
||||||
const nationalBankOfPolandDataSource = "Narodowy Bank Polski"
|
const nationalBankOfPolandDataSource = "Narodowy Bank Polski"
|
||||||
const nationalBankOfPolandBaseCurrency = "PLN"
|
const nationalBankOfPolandBaseCurrency = "PLN"
|
||||||
|
|
||||||
@@ -30,16 +30,15 @@ type NationalBankOfPolandDataSource struct {
|
|||||||
|
|
||||||
// NationalBankOfPolandExchangeRateData represents the whole data from National Bank of Poland
|
// NationalBankOfPolandExchangeRateData represents the whole data from National Bank of Poland
|
||||||
type NationalBankOfPolandExchangeRateData struct {
|
type NationalBankOfPolandExchangeRateData struct {
|
||||||
XMLName xml.Name `xml:"exchange_rates"`
|
XMLName xml.Name `xml:"ArrayOfExchangeRatesTable"`
|
||||||
Date string `xml:"date,attr"`
|
Date string `xml:"ExchangeRatesTable>EffectiveDate"`
|
||||||
AllExchangeRates []*NationalBankOfPolandExchangeRate `xml:"mid-rate"`
|
AllExchangeRates []*NationalBankOfPolandExchangeRate `xml:"ExchangeRatesTable>Rates>Rate"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NationalBankOfPolandExchangeRate represents the exchange rate data from National Bank of Poland
|
// NationalBankOfPolandExchangeRate represents the exchange rate data from National Bank of Poland
|
||||||
type NationalBankOfPolandExchangeRate struct {
|
type NationalBankOfPolandExchangeRate struct {
|
||||||
Currency string `xml:"code,attr"`
|
Currency string `xml:"Code"`
|
||||||
Units string `xml:"units,attr"`
|
Rate string `xml:"Mid"`
|
||||||
Rate string `xml:",chardata"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToLatestExchangeRateResponse returns a view-object according to original data from National Bank of Poland
|
// ToLatestExchangeRateResponse returns a view-object according to original data from National Bank of Poland
|
||||||
@@ -95,13 +94,6 @@ func (e *NationalBankOfPolandExchangeRateData) ToLatestExchangeRateResponse(c *c
|
|||||||
|
|
||||||
// ToLatestExchangeRate returns a data pair according to original data from National Bank of Poland
|
// ToLatestExchangeRate returns a data pair according to original data from National Bank of Poland
|
||||||
func (e *NationalBankOfPolandExchangeRate) ToLatestExchangeRate(c *core.Context) *models.LatestExchangeRate {
|
func (e *NationalBankOfPolandExchangeRate) ToLatestExchangeRate(c *core.Context) *models.LatestExchangeRate {
|
||||||
amount, err := utils.StringToInt64(e.Units)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.WarnfWithRequestId(c, "[national_bank_of_poland_datasource.ToLatestExchangeRate] failed to parse amount, currency is %s, amount is %s", e.Currency, e.Units)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
rate, err := utils.StringToFloat64(e.Rate)
|
rate, err := utils.StringToFloat64(e.Rate)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -114,7 +106,7 @@ func (e *NationalBankOfPolandExchangeRate) ToLatestExchangeRate(c *core.Context)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
finalRate := float64(amount) / rate
|
finalRate := 1 / rate
|
||||||
|
|
||||||
if math.IsInf(finalRate, 0) {
|
if math.IsInf(finalRate, 0) {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -10,11 +10,22 @@ import (
|
|||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
const nationalBankOfPolandMinimumRequiredContent = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n" +
|
const nationalBankOfPolandMinimumRequiredContent = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
|
||||||
"<exchange_rates table=\"A\" date=\"2021-04-02\" number=\"064/A/NBP/2021\" uid=\"21a064\">\n" +
|
"<ArrayOfExchangeRatesTable xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n" +
|
||||||
" <mid-rate currency=\"US Dollar\" units=\"1\" code=\"USD\">3.8986</mid-rate>\n" +
|
" <ExchangeRatesTable>\n" +
|
||||||
" <mid-rate currency=\"Yuan Renminbi\" units=\"1\" code=\"CNY\">0.5941</mid-rate>\n" +
|
" <EffectiveDate>2024-02-28</EffectiveDate>\n" +
|
||||||
"</exchange_rates>"
|
" <Rates>\n" +
|
||||||
|
" <Rate>\n" +
|
||||||
|
" <Code>USD</Code>\n" +
|
||||||
|
" <Mid>3.9922</Mid>\n" +
|
||||||
|
" </Rate>\n" +
|
||||||
|
" <Rate>\n" +
|
||||||
|
" <Code>CNY</Code>\n" +
|
||||||
|
" <Mid>0.5545</Mid>\n" +
|
||||||
|
" </Rate>\n" +
|
||||||
|
" </Rates>\n" +
|
||||||
|
" </ExchangeRatesTable>\n" +
|
||||||
|
"</ArrayOfExchangeRatesTable>"
|
||||||
|
|
||||||
func TestNationalBankOfPolandDataSource_StandardDataExtractBaseCurrency(t *testing.T) {
|
func TestNationalBankOfPolandDataSource_StandardDataExtractBaseCurrency(t *testing.T) {
|
||||||
dataSource := &NationalBankOfPolandDataSource{}
|
dataSource := &NationalBankOfPolandDataSource{}
|
||||||
@@ -37,11 +48,11 @@ func TestNationalBankOfPolandDataSource_StandardDataExtractExchangeRates(t *test
|
|||||||
assert.Equal(t, nil, err)
|
assert.Equal(t, nil, err)
|
||||||
assert.Contains(t, actualLatestExchangeRateResponse.ExchangeRates, &models.LatestExchangeRate{
|
assert.Contains(t, actualLatestExchangeRateResponse.ExchangeRates, &models.LatestExchangeRate{
|
||||||
Currency: "USD",
|
Currency: "USD",
|
||||||
Rate: "0.25650233417124096",
|
Rate: "0.2504884524823406",
|
||||||
})
|
})
|
||||||
assert.Contains(t, actualLatestExchangeRateResponse.ExchangeRates, &models.LatestExchangeRate{
|
assert.Contains(t, actualLatestExchangeRateResponse.ExchangeRates, &models.LatestExchangeRate{
|
||||||
Currency: "CNY",
|
Currency: "CNY",
|
||||||
Rate: "1.68321831341525",
|
Rate: "1.8034265103697025",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +72,33 @@ func TestNationalBankOfPolandDataSource_OnlyXMLHeader(t *testing.T) {
|
|||||||
Context: &gin.Context{},
|
Context: &gin.Context{},
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := dataSource.Parse(context, []byte("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>"))
|
_, err := dataSource.Parse(context, []byte("<?xml version=\"1.0\" encoding=\"utf-8\"?>"))
|
||||||
|
assert.NotEqual(t, nil, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNationalBankOfPolandDataSource_EmptyArrayOfExchangeRatesTable(t *testing.T) {
|
||||||
|
dataSource := &NationalBankOfPolandDataSource{}
|
||||||
|
context := &core.Context{
|
||||||
|
Context: &gin.Context{},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := dataSource.Parse(context, []byte("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"+
|
||||||
|
"<ArrayOfExchangeRatesTable xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"+
|
||||||
|
"</ArrayOfExchangeRatesTable>"))
|
||||||
|
assert.NotEqual(t, nil, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNationalBankOfPolandDataSource_EmptyExchangeRatesTable(t *testing.T) {
|
||||||
|
dataSource := &NationalBankOfPolandDataSource{}
|
||||||
|
context := &core.Context{
|
||||||
|
Context: &gin.Context{},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := dataSource.Parse(context, []byte("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"+
|
||||||
|
"<ArrayOfExchangeRatesTable xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"+
|
||||||
|
" <ExchangeRatesTable>\n"+
|
||||||
|
" </ExchangeRatesTable>\n"+
|
||||||
|
"</ArrayOfExchangeRatesTable>"))
|
||||||
assert.NotEqual(t, nil, err)
|
assert.NotEqual(t, nil, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,9 +108,14 @@ func TestNationalBankOfPolandDataSource_EmptyExchangeRatesContent(t *testing.T)
|
|||||||
Context: &gin.Context{},
|
Context: &gin.Context{},
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := dataSource.Parse(context, []byte("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"+
|
_, err := dataSource.Parse(context, []byte("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"+
|
||||||
"<exchange_rates table=\"A\" date=\"2021-04-02\" number=\"064/A/NBP/2021\" uid=\"21a064\">\n"+
|
"<ArrayOfExchangeRatesTable xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"+
|
||||||
"</exchange_rates>"))
|
" <ExchangeRatesTable>\n"+
|
||||||
|
" <EffectiveDate>2024-02-28</EffectiveDate>\n"+
|
||||||
|
" <Rates>\n"+
|
||||||
|
" </Rates>\n"+
|
||||||
|
" </ExchangeRatesTable>\n"+
|
||||||
|
"</ArrayOfExchangeRatesTable>"))
|
||||||
assert.NotEqual(t, nil, err)
|
assert.NotEqual(t, nil, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,10 +125,18 @@ func TestNationalBankOfPolandDataSource_InvalidCurrency(t *testing.T) {
|
|||||||
Context: &gin.Context{},
|
Context: &gin.Context{},
|
||||||
}
|
}
|
||||||
|
|
||||||
actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"+
|
actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"+
|
||||||
"<exchange_rates table=\"A\" date=\"2021-04-02\" number=\"064/A/NBP/2021\" uid=\"21a064\">\n"+
|
"<ArrayOfExchangeRatesTable xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"+
|
||||||
" <mid-rate currency=\"XXX\" units=\"1\" code=\"XXX\">1</mid-rate>\n"+
|
" <ExchangeRatesTable>\n"+
|
||||||
"</exchange_rates>"))
|
" <EffectiveDate>2024-02-28</EffectiveDate>\n"+
|
||||||
|
" <Rates>\n"+
|
||||||
|
" <Rate>\n"+
|
||||||
|
" <Code>XXX</Code>\n"+
|
||||||
|
" <Mid>1</Mid>\n"+
|
||||||
|
" </Rate>\n"+
|
||||||
|
" </Rates>\n"+
|
||||||
|
" </ExchangeRatesTable>\n"+
|
||||||
|
"</ArrayOfExchangeRatesTable>"))
|
||||||
assert.Equal(t, nil, err)
|
assert.Equal(t, nil, err)
|
||||||
assert.Len(t, actualLatestExchangeRateResponse.ExchangeRates, 0)
|
assert.Len(t, actualLatestExchangeRateResponse.ExchangeRates, 0)
|
||||||
}
|
}
|
||||||
@@ -97,10 +147,18 @@ func TestNationalBankOfPolandDataSource_EmptyRate(t *testing.T) {
|
|||||||
Context: &gin.Context{},
|
Context: &gin.Context{},
|
||||||
}
|
}
|
||||||
|
|
||||||
actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"+
|
actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"+
|
||||||
"<exchange_rates table=\"A\" date=\"2021-04-02\" number=\"064/A/NBP/2021\" uid=\"21a064\">\n"+
|
"<ArrayOfExchangeRatesTable xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"+
|
||||||
" <mid-rate currency=\"US Dollar\" units=\"1\" code=\"USD\"></mid-rate>\n"+
|
" <ExchangeRatesTable>\n"+
|
||||||
"</exchange_rates>"))
|
" <EffectiveDate>2024-02-28</EffectiveDate>\n"+
|
||||||
|
" <Rates>\n"+
|
||||||
|
" <Rate>\n"+
|
||||||
|
" <Code>USD</Code>\n"+
|
||||||
|
" <Mid></Mid>\n"+
|
||||||
|
" </Rate>\n"+
|
||||||
|
" </Rates>\n"+
|
||||||
|
" </ExchangeRatesTable>\n"+
|
||||||
|
"</ArrayOfExchangeRatesTable>"))
|
||||||
assert.Equal(t, nil, err)
|
assert.Equal(t, nil, err)
|
||||||
assert.Len(t, actualLatestExchangeRateResponse.ExchangeRates, 0)
|
assert.Len(t, actualLatestExchangeRateResponse.ExchangeRates, 0)
|
||||||
}
|
}
|
||||||
@@ -111,10 +169,18 @@ func TestNationalBankOfPolandDataSource_InvalidRate(t *testing.T) {
|
|||||||
Context: &gin.Context{},
|
Context: &gin.Context{},
|
||||||
}
|
}
|
||||||
|
|
||||||
actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"+
|
actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"+
|
||||||
"<exchange_rates table=\"A\" date=\"2021-04-02\" number=\"064/A/NBP/2021\" uid=\"21a064\">\n"+
|
"<ArrayOfExchangeRatesTable xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"+
|
||||||
" <mid-rate currency=\"US Dollar\" units=\"1\" code=\"USD\">null</mid-rate>\n"+
|
" <ExchangeRatesTable>\n"+
|
||||||
"</exchange_rates>"))
|
" <EffectiveDate>2024-02-28</EffectiveDate>\n"+
|
||||||
|
" <Rates>\n"+
|
||||||
|
" <Rate>\n"+
|
||||||
|
" <Code>USD</Code>\n"+
|
||||||
|
" <Mid>null</Mid>\n"+
|
||||||
|
" </Rate>\n"+
|
||||||
|
" </Rates>\n"+
|
||||||
|
" </ExchangeRatesTable>\n"+
|
||||||
|
"</ArrayOfExchangeRatesTable>"))
|
||||||
assert.Equal(t, nil, err)
|
assert.Equal(t, nil, err)
|
||||||
assert.Len(t, actualLatestExchangeRateResponse.ExchangeRates, 0)
|
assert.Len(t, actualLatestExchangeRateResponse.ExchangeRates, 0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
package locales
|
package locales
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
|
)
|
||||||
|
|
||||||
// DefaultLanguage represents the default language
|
// DefaultLanguage represents the default language
|
||||||
var DefaultLanguage = en
|
var DefaultLanguage = en
|
||||||
|
|
||||||
// AllLanguages represents all the supported language
|
// AllLanguages represents all the supported language
|
||||||
|
// To add new languages, please refer to https://ezbookkeeping.mayswind.net/translating
|
||||||
var AllLanguages = map[string]*LocaleInfo{
|
var AllLanguages = map[string]*LocaleInfo{
|
||||||
"en": {
|
"en": {
|
||||||
Content: en,
|
Content: en,
|
||||||
@@ -22,3 +27,25 @@ func GetLocaleTextItems(locale string) *LocaleTextItems {
|
|||||||
|
|
||||||
return DefaultLanguage
|
return DefaultLanguage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsDecimalSeparatorEqualsDigitGroupingSymbol(decimalSeparator core.DecimalSeparator, digitGroupingSymbol core.DigitGroupingSymbol, locale string) bool {
|
||||||
|
if decimalSeparator == core.DECIMAL_SEPARATOR_DEFAULT && digitGroupingSymbol == core.DIGIT_GROUPING_SYMBOL_DEFAULT {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if byte(decimalSeparator) == byte(digitGroupingSymbol) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
localeTextItems := GetLocaleTextItems(locale)
|
||||||
|
|
||||||
|
if decimalSeparator == core.DECIMAL_SEPARATOR_DEFAULT {
|
||||||
|
decimalSeparator = localeTextItems.DefaultTypes.DecimalSeparator
|
||||||
|
}
|
||||||
|
|
||||||
|
if digitGroupingSymbol == core.DIGIT_GROUPING_SYMBOL_DEFAULT {
|
||||||
|
digitGroupingSymbol = localeTextItems.DefaultTypes.DigitGroupingSymbol
|
||||||
|
}
|
||||||
|
|
||||||
|
return byte(decimalSeparator) == byte(digitGroupingSymbol)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +1,21 @@
|
|||||||
package locales
|
package locales
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
|
)
|
||||||
|
|
||||||
// LocaleTextItems represents all text items need to be translated
|
// LocaleTextItems represents all text items need to be translated
|
||||||
type LocaleTextItems struct {
|
type LocaleTextItems struct {
|
||||||
|
DefaultTypes *DefaultTypes
|
||||||
VerifyEmailTextItems *VerifyEmailTextItems
|
VerifyEmailTextItems *VerifyEmailTextItems
|
||||||
ForgetPasswordMailTextItems *ForgetPasswordMailTextItems
|
ForgetPasswordMailTextItems *ForgetPasswordMailTextItems
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DefaultTypes struct {
|
||||||
|
DecimalSeparator core.DecimalSeparator
|
||||||
|
DigitGroupingSymbol core.DigitGroupingSymbol
|
||||||
|
}
|
||||||
|
|
||||||
// VerifyEmailTextItems represents text items need to be translated in verify mail
|
// VerifyEmailTextItems represents text items need to be translated in verify mail
|
||||||
type VerifyEmailTextItems struct {
|
type VerifyEmailTextItems struct {
|
||||||
Title string
|
Title string
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
package locales
|
package locales
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
|
)
|
||||||
|
|
||||||
var en = &LocaleTextItems{
|
var en = &LocaleTextItems{
|
||||||
|
DefaultTypes: &DefaultTypes{
|
||||||
|
DecimalSeparator: core.DECIMAL_SEPARATOR_DOT,
|
||||||
|
DigitGroupingSymbol: core.DIGIT_GROUPING_SYMBOL_COMMA,
|
||||||
|
},
|
||||||
VerifyEmailTextItems: &VerifyEmailTextItems{
|
VerifyEmailTextItems: &VerifyEmailTextItems{
|
||||||
Title: "Verify Email",
|
Title: "Verify Email",
|
||||||
SalutationFormat: "Hi %s,",
|
SalutationFormat: "Hi %s,",
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
package locales
|
package locales
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
|
)
|
||||||
|
|
||||||
var zhHans = &LocaleTextItems{
|
var zhHans = &LocaleTextItems{
|
||||||
|
DefaultTypes: &DefaultTypes{
|
||||||
|
DecimalSeparator: core.DECIMAL_SEPARATOR_DOT,
|
||||||
|
DigitGroupingSymbol: core.DIGIT_GROUPING_SYMBOL_COMMA,
|
||||||
|
},
|
||||||
VerifyEmailTextItems: &VerifyEmailTextItems{
|
VerifyEmailTextItems: &VerifyEmailTextItems{
|
||||||
Title: "验证邮箱",
|
Title: "验证邮箱",
|
||||||
SalutationFormat: "%s 您好,",
|
SalutationFormat: "%s 您好,",
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func (f *LogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if requestId, exists := entry.Data[logFieldRequestId]; exists {
|
if requestId, exists := entry.Data[logFieldRequestId]; exists {
|
||||||
b.WriteString(fmt.Sprintf("[r=%s] ", requestId))
|
b.WriteString(fmt.Sprintf("[%s] ", requestId))
|
||||||
}
|
}
|
||||||
|
|
||||||
b.WriteString(entry.Message)
|
b.WriteString(entry.Message)
|
||||||
|
|||||||
+43
-9
@@ -41,37 +41,71 @@ func init() {
|
|||||||
// SetLoggerConfiguration sets the logger according to the config
|
// SetLoggerConfiguration sets the logger according to the config
|
||||||
func SetLoggerConfiguration(config *settings.Config, isDisableBootLog bool) error {
|
func SetLoggerConfiguration(config *settings.Config, isDisableBootLog bool) error {
|
||||||
var bootWriters []io.Writer
|
var bootWriters []io.Writer
|
||||||
var writers []io.Writer
|
var defaultWriters []io.Writer
|
||||||
|
var requestWriters []io.Writer
|
||||||
|
var queryWriters []io.Writer
|
||||||
|
|
||||||
if !isDisableBootLog {
|
if !isDisableBootLog {
|
||||||
bootWriters = append(bootWriters, os.Stdout)
|
bootWriters = append(bootWriters, os.Stdout)
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.EnableConsoleLog {
|
if config.EnableConsoleLog {
|
||||||
writers = append(writers, os.Stdout)
|
defaultWriters = append(defaultWriters, os.Stdout)
|
||||||
|
requestWriters = append(requestWriters, os.Stdout)
|
||||||
|
queryWriters = append(queryWriters, os.Stdout)
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.EnableFileLog {
|
if config.EnableFileLog {
|
||||||
logFile, err := os.OpenFile(config.FileLogPath, os.O_CREATE|os.O_WRONLY, 0666)
|
defaultWriter, err := NewRotateFileWriter(config.FileLogPath, config.LogFileRotate, int64(config.LogFileMaxSize), config.LogFileMaxDays)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isDisableBootLog {
|
if !isDisableBootLog {
|
||||||
bootWriters = append(bootWriters, logFile)
|
bootWriters = append(bootWriters, defaultWriter)
|
||||||
}
|
}
|
||||||
|
|
||||||
writers = append(writers, logFile)
|
defaultWriters = append(defaultWriters, defaultWriter)
|
||||||
|
|
||||||
|
if config.EnableRequestLog {
|
||||||
|
if config.RequestFileLogPath != "" && config.RequestFileLogPath != config.FileLogPath {
|
||||||
|
requestWriter, err := NewRotateFileWriter(config.RequestFileLogPath, config.LogFileRotate, int64(config.LogFileMaxSize), config.LogFileMaxDays)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
requestWriters = append(requestWriters, requestWriter)
|
||||||
|
} else {
|
||||||
|
requestWriters = append(requestWriters, defaultWriter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.EnableQueryLog {
|
||||||
|
if config.QueryFileLogPath != "" && config.QueryFileLogPath != config.FileLogPath {
|
||||||
|
queryWriter, err := NewRotateFileWriter(config.QueryFileLogPath, config.LogFileRotate, int64(config.LogFileMaxSize), config.LogFileMaxDays)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
queryWriters = append(queryWriters, queryWriter)
|
||||||
|
} else {
|
||||||
|
queryWriters = append(queryWriters, defaultWriter)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bootMultipleWriter := io.MultiWriter(bootWriters...)
|
bootMultipleWriter := io.MultiWriter(bootWriters...)
|
||||||
multipleWriter := io.MultiWriter(writers...)
|
defaultMultipleWriter := io.MultiWriter(defaultWriters...)
|
||||||
|
requestMultipleWriter := io.MultiWriter(requestWriters...)
|
||||||
|
queryMultipleWriter := io.MultiWriter(queryWriters...)
|
||||||
|
|
||||||
bootLogger.SetOutput(bootMultipleWriter)
|
bootLogger.SetOutput(bootMultipleWriter)
|
||||||
defaultLogger.SetOutput(multipleWriter)
|
defaultLogger.SetOutput(defaultMultipleWriter)
|
||||||
requestLogger.SetOutput(multipleWriter)
|
requestLogger.SetOutput(requestMultipleWriter)
|
||||||
sqlQueryLogger.SetOutput(multipleWriter)
|
sqlQueryLogger.SetOutput(queryMultipleWriter)
|
||||||
|
|
||||||
if config.LogLevel == settings.LOGLEVEL_DEBUG {
|
if config.LogLevel == settings.LOGLEVEL_DEBUG {
|
||||||
bootLogger.SetLevel(logrus.DebugLevel)
|
bootLogger.SetLevel(logrus.DebugLevel)
|
||||||
|
|||||||
@@ -0,0 +1,195 @@
|
|||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
logRotateSuffixDateFormat = "20060102150405"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RotateFileWriter struct {
|
||||||
|
EnableRotate bool
|
||||||
|
MaxFileSize int64
|
||||||
|
MaxFileDays uint32
|
||||||
|
|
||||||
|
filePath string
|
||||||
|
file *os.File
|
||||||
|
totalSize int64
|
||||||
|
|
||||||
|
mutex sync.Mutex
|
||||||
|
lastRemoveOldFilesDay int
|
||||||
|
}
|
||||||
|
|
||||||
|
var logFallbackLogger = logrus.New()
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
logFallbackLogger.SetFormatter(&LogFormatter{})
|
||||||
|
logFallbackLogger.SetOutput(os.Stdout)
|
||||||
|
logFallbackLogger.SetLevel(logrus.InfoLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRotateFileWriter returns a new rotate file writer
|
||||||
|
func NewRotateFileWriter(filePath string, enableRotate bool, maxFileSize int64, maxFileDays uint32) (*RotateFileWriter, error) {
|
||||||
|
writer := &RotateFileWriter{
|
||||||
|
EnableRotate: enableRotate,
|
||||||
|
MaxFileSize: maxFileSize,
|
||||||
|
MaxFileDays: maxFileDays,
|
||||||
|
filePath: filePath,
|
||||||
|
totalSize: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := writer.openFile()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return writer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write does log data to specified file
|
||||||
|
func (w *RotateFileWriter) Write(p []byte) (n int, err error) {
|
||||||
|
dataSize := int64(len(p))
|
||||||
|
|
||||||
|
if w.EnableRotate && w.totalSize > 0 && w.totalSize+dataSize >= w.MaxFileSize {
|
||||||
|
w.mutex.Lock()
|
||||||
|
defer w.mutex.Unlock()
|
||||||
|
|
||||||
|
if w.EnableRotate && w.totalSize > 0 && w.totalSize+dataSize >= w.MaxFileSize {
|
||||||
|
err := w.rotateFile()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logFallbackLogger.Errorf("[rotate_file_writer.Write] cannot rotate log file \"%s\", because %s", w.file.Name(), err.Error())
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writeSize, err := w.file.Write(p)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.totalSize += int64(writeSize)
|
||||||
|
|
||||||
|
if w.EnableRotate {
|
||||||
|
today := time.Now().Day()
|
||||||
|
|
||||||
|
if today != w.lastRemoveOldFilesDay && w.MaxFileDays > 0 {
|
||||||
|
w.lastRemoveOldFilesDay = today
|
||||||
|
go w.removeOldFiles()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return writeSize, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *RotateFileWriter) rotateFile() error {
|
||||||
|
currentFileName := w.file.Name()
|
||||||
|
err := w.file.Close()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return errs.NewLoggingError(fmt.Sprintf("cannot close log file \"%s\", because %s", w.file.Name(), err.Error()), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.file = nil
|
||||||
|
archiveFileName := fmt.Sprintf("%s.%s", currentFileName, time.Now().Format(logRotateSuffixDateFormat))
|
||||||
|
err = os.Rename(currentFileName, archiveFileName)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return errs.NewLoggingError(fmt.Sprintf("cannot rename log file \"%s\" to \"%s\", because %s", currentFileName, archiveFileName, err.Error()), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.openFile()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *RotateFileWriter) openFile() error {
|
||||||
|
if w.file != nil {
|
||||||
|
logFallbackLogger.Warnf("[rotate_file_writer.removeOldFiles] cannot reopen log file \"%s\"", w.file.Name())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.OpenFile(w.filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return errs.NewLoggingError(fmt.Sprintf("cannot open log file \"%s\", because %s", w.filePath, err.Error()), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.file = file
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *RotateFileWriter) removeOldFiles() {
|
||||||
|
dir := filepath.Dir(w.filePath)
|
||||||
|
logBaseFileName := filepath.Base(w.filePath) + "."
|
||||||
|
|
||||||
|
allLogFiles, err := os.ReadDir(dir)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
retainMinUnixTime := int64(0)
|
||||||
|
|
||||||
|
if w.MaxFileDays > 0 {
|
||||||
|
retainMinUnixTime = time.Now().AddDate(0, 0, -int(w.MaxFileDays)).Unix()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range allLogFiles {
|
||||||
|
if file.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
logFileName := filepath.Base(file.Name())
|
||||||
|
|
||||||
|
if !strings.HasPrefix(logFileName, logBaseFileName) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rotateDate := logFileName[len(logBaseFileName):]
|
||||||
|
dotIndex := strings.Index(rotateDate, ".")
|
||||||
|
|
||||||
|
if dotIndex > 0 {
|
||||||
|
rotateDate = rotateDate[0:dotIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rotateDate) != len(logRotateSuffixDateFormat) {
|
||||||
|
logFallbackLogger.Errorf("[rotate_file_writer.removeOldFiles] date suffix of old log file \"%s\" is invalid", file.Name())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rotateDateTime, err := time.ParseInLocation(logRotateSuffixDateFormat, rotateDate, time.Now().Location())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logFallbackLogger.Errorf("[rotate_file_writer.removeOldFiles] cannot parse rotate date of old log file \"%s\", because %s", file.Name(), err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if rotateDateTime.Unix() >= retainMinUnixTime {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Remove(filepath.Join(dir, file.Name()))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logFallbackLogger.Errorf("[rotate_file_writer.removeOldFiles] cannot remove old log file \"%s\", because %s", file.Name(), err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -47,7 +47,7 @@ func JWTTwoFactorAuthorization(c *core.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if claims.Type != core.USER_TOKEN_TYPE_REQUIRE_2FA {
|
if claims.Type != core.USER_TOKEN_TYPE_REQUIRE_2FA {
|
||||||
log.WarnfWithRequestId(c, "[authorization.JWTTwoFactorAuthorization] user \"uid:%d\" token is not need two factor authorization", claims.Uid)
|
log.WarnfWithRequestId(c, "[authorization.JWTTwoFactorAuthorization] user \"uid:%d\" token is not need two-factor authorization", claims.Uid)
|
||||||
utils.PrintJsonErrorResult(c, errs.ErrCurrentTokenNotRequire2FA)
|
utils.PrintJsonErrorResult(c, errs.ErrCurrentTokenNotRequire2FA)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ func RequestId(config *settings.Config) core.MiddlewareHandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
requestId := requestid.Container.Current.GenerateRequestId(c.ClientIP())
|
requestId := requestid.Container.Current.GenerateRequestId(c.ClientIP(), c.ClientPort())
|
||||||
c.SetRequestId(requestId)
|
c.SetRequestId(requestId)
|
||||||
|
|
||||||
if config.EnableRequestIdHeader {
|
if config.EnableRequestIdHeader {
|
||||||
|
|||||||
@@ -29,14 +29,37 @@ func ServerSettingsCookie(config *settings.Config) core.MiddlewareHandlerFunc {
|
|||||||
config.MapProvider == settings.OpenTopoMapProvider ||
|
config.MapProvider == settings.OpenTopoMapProvider ||
|
||||||
config.MapProvider == settings.OPNVKarteMapProvider ||
|
config.MapProvider == settings.OPNVKarteMapProvider ||
|
||||||
config.MapProvider == settings.CyclOSMMapProvider ||
|
config.MapProvider == settings.CyclOSMMapProvider ||
|
||||||
config.MapProvider == settings.TomTomMapProvider) {
|
config.MapProvider == settings.CartoDBMapProvider ||
|
||||||
|
config.MapProvider == settings.TomTomMapProvider ||
|
||||||
|
config.MapProvider == settings.TianDiTuProvider ||
|
||||||
|
config.MapProvider == settings.CustomProvider) {
|
||||||
settingsArr = append(settingsArr, buildBooleanSetting("mp", config.EnableMapDataFetchProxy))
|
settingsArr = append(settingsArr, buildBooleanSetting("mp", config.EnableMapDataFetchProxy))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.MapProvider == settings.CustomProvider {
|
||||||
|
settingsArr = append(settingsArr, buildStringSetting("cmzl", fmt.Sprintf("%d-%d-%d", config.CustomMapTileServerMinZoomLevel, config.CustomMapTileServerMaxZoomLevel, config.CustomMapTileServerDefaultZoomLevel)))
|
||||||
|
|
||||||
|
if !config.EnableMapDataFetchProxy {
|
||||||
|
settingsArr = append(settingsArr, buildEncodedStringSetting("cmsu", config.CustomMapTileServerTileLayerUrl))
|
||||||
|
|
||||||
|
if config.CustomMapTileServerAnnotationLayerUrl != "" {
|
||||||
|
settingsArr = append(settingsArr, buildEncodedStringSetting("cmau", config.CustomMapTileServerAnnotationLayerUrl))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if config.CustomMapTileServerAnnotationLayerUrl != "" {
|
||||||
|
settingsArr = append(settingsArr, buildBooleanSetting("cmap", config.EnableMapDataFetchProxy))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if config.MapProvider == settings.TomTomMapProvider && config.TomTomMapAPIKey != "" && !config.EnableMapDataFetchProxy {
|
if config.MapProvider == settings.TomTomMapProvider && config.TomTomMapAPIKey != "" && !config.EnableMapDataFetchProxy {
|
||||||
settingsArr = append(settingsArr, buildEncodedStringSetting("tmak", config.TomTomMapAPIKey))
|
settingsArr = append(settingsArr, buildEncodedStringSetting("tmak", config.TomTomMapAPIKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.MapProvider == settings.TianDiTuProvider && config.TianDiTuAPIKey != "" && !config.EnableMapDataFetchProxy {
|
||||||
|
settingsArr = append(settingsArr, buildEncodedStringSetting("tdak", config.TianDiTuAPIKey))
|
||||||
|
}
|
||||||
|
|
||||||
if config.MapProvider == settings.GoogleMapProvider && config.GoogleMapAPIKey != "" {
|
if config.MapProvider == settings.GoogleMapProvider && config.GoogleMapAPIKey != "" {
|
||||||
settingsArr = append(settingsArr, buildEncodedStringSetting("gmak", config.GoogleMapAPIKey))
|
settingsArr = append(settingsArr, buildEncodedStringSetting("gmak", config.GoogleMapAPIKey))
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-9
@@ -69,15 +69,16 @@ type Account struct {
|
|||||||
|
|
||||||
// AccountCreateRequest represents all parameters of account creation request
|
// AccountCreateRequest represents all parameters of account creation request
|
||||||
type AccountCreateRequest struct {
|
type AccountCreateRequest struct {
|
||||||
Name string `json:"name" binding:"required,notBlank,max=32"`
|
Name string `json:"name" binding:"required,notBlank,max=32"`
|
||||||
Category AccountCategory `json:"category" binding:"required"`
|
Category AccountCategory `json:"category" binding:"required"`
|
||||||
Type AccountType `json:"type" binding:"required"`
|
Type AccountType `json:"type" binding:"required"`
|
||||||
Icon int64 `json:"icon,string" binding:"required,min=1"`
|
Icon int64 `json:"icon,string" binding:"required,min=1"`
|
||||||
Color string `json:"color" binding:"required,len=6,validHexRGBColor"`
|
Color string `json:"color" binding:"required,len=6,validHexRGBColor"`
|
||||||
Currency string `json:"currency" binding:"required,len=3,validCurrency"`
|
Currency string `json:"currency" binding:"required,len=3,validCurrency"`
|
||||||
Balance int64 `json:"balance"`
|
Balance int64 `json:"balance"`
|
||||||
Comment string `json:"comment" binding:"max=255"`
|
Comment string `json:"comment" binding:"max=255"`
|
||||||
SubAccounts []*AccountCreateRequest `json:"subAccounts" binding:"omitempty"`
|
SubAccounts []*AccountCreateRequest `json:"subAccounts" binding:"omitempty"`
|
||||||
|
ClientSessionId string `json:"clientSessionId"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccountModifyRequest represents all parameters of account modification request
|
// AccountModifyRequest represents all parameters of account modification request
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ package models
|
|||||||
|
|
||||||
// AuthResponse returns a view-object of user authorization
|
// AuthResponse returns a view-object of user authorization
|
||||||
type AuthResponse struct {
|
type AuthResponse struct {
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
Need2FA bool `json:"need2FA"`
|
Need2FA bool `json:"need2FA"`
|
||||||
User *UserBasicInfo `json:"user"`
|
User *UserBasicInfo `json:"user"`
|
||||||
|
NotificationContent string `json:"notificationContent,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterResponse returns a view-object of user register response
|
// RegisterResponse returns a view-object of user register response
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
package models
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// CurrencyDisplayType represents the display type of amount with currency
|
||||||
|
type CurrencyDisplayType byte
|
||||||
|
|
||||||
|
// Currency Display Type
|
||||||
|
const (
|
||||||
|
CURRENCY_DISPLAY_TYPE_DEFAULT CurrencyDisplayType = 0
|
||||||
|
CURRENCY_DISPLAY_TYPE_NONE CurrencyDisplayType = 1
|
||||||
|
CURRENCY_DISPLAY_TYPE_SYMBOL_BEFORE_AMOUNT CurrencyDisplayType = 2
|
||||||
|
CURRENCY_DISPLAY_TYPE_SYMBOL_AFTER_AMOUNT CurrencyDisplayType = 3
|
||||||
|
CURRENCY_DISPLAY_TYPE_SYMBOL_BEFORE_AMOUNT_WITHOUT_SPACE CurrencyDisplayType = 4
|
||||||
|
CURRENCY_DISPLAY_TYPE_SYMBOL_AFTER_AMOUNT_WITHOUT_SPACE CurrencyDisplayType = 5
|
||||||
|
CURRENCY_DISPLAY_TYPE_CODE_BEFORE_AMOUNT CurrencyDisplayType = 6
|
||||||
|
CURRENCY_DISPLAY_TYPE_CODE_AFTER_AMOUNT CurrencyDisplayType = 7
|
||||||
|
CURRENCY_DISPLAY_TYPE_UNIT_BEFORE_AMOUNT CurrencyDisplayType = 8
|
||||||
|
CURRENCY_DISPLAY_TYPE_UNIT_AFTER_AMOUNT CurrencyDisplayType = 9
|
||||||
|
CURRENCY_DISPLAY_TYPE_NAME_BEFORE_AMOUNT CurrencyDisplayType = 10
|
||||||
|
CURRENCY_DISPLAY_TYPE_NAME_AFTER_AMOUNT CurrencyDisplayType = 11
|
||||||
|
CURRENCY_DISPLAY_TYPE_INVALID CurrencyDisplayType = 255
|
||||||
|
)
|
||||||
|
|
||||||
|
// String returns a textual representation of the currency display type enum
|
||||||
|
func (d CurrencyDisplayType) String() string {
|
||||||
|
switch d {
|
||||||
|
case CURRENCY_DISPLAY_TYPE_DEFAULT:
|
||||||
|
return "Default"
|
||||||
|
case CURRENCY_DISPLAY_TYPE_NONE:
|
||||||
|
return "None"
|
||||||
|
case CURRENCY_DISPLAY_TYPE_SYMBOL_BEFORE_AMOUNT:
|
||||||
|
return "Symbol Before Amount"
|
||||||
|
case CURRENCY_DISPLAY_TYPE_SYMBOL_AFTER_AMOUNT:
|
||||||
|
return "Symbol After Amount"
|
||||||
|
case CURRENCY_DISPLAY_TYPE_SYMBOL_BEFORE_AMOUNT_WITHOUT_SPACE:
|
||||||
|
return "Symbol Before Amount Without Space"
|
||||||
|
case CURRENCY_DISPLAY_TYPE_SYMBOL_AFTER_AMOUNT_WITHOUT_SPACE:
|
||||||
|
return "Symbol After Amount Without Space"
|
||||||
|
case CURRENCY_DISPLAY_TYPE_CODE_BEFORE_AMOUNT:
|
||||||
|
return "Code Before Amount"
|
||||||
|
case CURRENCY_DISPLAY_TYPE_CODE_AFTER_AMOUNT:
|
||||||
|
return "Code After Amount"
|
||||||
|
case CURRENCY_DISPLAY_TYPE_UNIT_BEFORE_AMOUNT:
|
||||||
|
return "Unit Before Amount"
|
||||||
|
case CURRENCY_DISPLAY_TYPE_UNIT_AFTER_AMOUNT:
|
||||||
|
return "Unit After Amount"
|
||||||
|
case CURRENCY_DISPLAY_TYPE_NAME_BEFORE_AMOUNT:
|
||||||
|
return "Name Before Amount"
|
||||||
|
case CURRENCY_DISPLAY_TYPE_NAME_AFTER_AMOUNT:
|
||||||
|
return "Name After Amount"
|
||||||
|
case CURRENCY_DISPLAY_TYPE_INVALID:
|
||||||
|
return "Invalid"
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("Invalid(%d)", int(d))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,4 +11,5 @@ type DataStatisticsResponse struct {
|
|||||||
TotalTransactionCategoryCount int64 `json:"totalTransactionCategoryCount,string"`
|
TotalTransactionCategoryCount int64 `json:"totalTransactionCategoryCount,string"`
|
||||||
TotalTransactionTagCount int64 `json:"totalTransactionTagCount,string"`
|
TotalTransactionTagCount int64 `json:"totalTransactionTagCount,string"`
|
||||||
TotalTransactionCount int64 `json:"totalTransactionCount,string"`
|
TotalTransactionCount int64 `json:"totalTransactionCount,string"`
|
||||||
|
TotalTransactionTemplateCount int64 `json:"totalTransactionTemplateCount,string"`
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-13
@@ -7,13 +7,14 @@ const TokenMaxUserAgentLength = 255
|
|||||||
|
|
||||||
// TokenRecord represents token data stored in database
|
// TokenRecord represents token data stored in database
|
||||||
type TokenRecord struct {
|
type TokenRecord struct {
|
||||||
Uid int64 `xorm:"PK INDEX(IDX_token_record_uid_type_expired_time)"`
|
Uid int64 `xorm:"PK INDEX(IDX_token_record_uid_type_expired_time)"`
|
||||||
UserTokenId int64 `xorm:"PK"`
|
UserTokenId int64 `xorm:"PK"`
|
||||||
TokenType core.TokenType `xorm:"INDEX(IDX_token_record_uid_type_expired_time) TINYINT NOT NULL"`
|
TokenType core.TokenType `xorm:"INDEX(IDX_token_record_uid_type_expired_time) TINYINT NOT NULL"`
|
||||||
Secret string `xorm:"VARCHAR(10) NOT NULL"`
|
Secret string `xorm:"VARCHAR(10) NOT NULL"`
|
||||||
UserAgent string `xorm:"VARCHAR(255)"`
|
UserAgent string `xorm:"VARCHAR(255)"`
|
||||||
CreatedUnixTime int64 `xorm:"PK"`
|
CreatedUnixTime int64 `xorm:"PK"`
|
||||||
ExpiredUnixTime int64 `xorm:"INDEX(IDX_token_record_uid_type_expired_time)"`
|
ExpiredUnixTime int64 `xorm:"INDEX(IDX_token_record_uid_type_expired_time)"`
|
||||||
|
LastSeenUnixTime int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// TokenRevokeRequest represents all parameters of token revoking request
|
// TokenRevokeRequest represents all parameters of token revoking request
|
||||||
@@ -23,9 +24,10 @@ type TokenRevokeRequest struct {
|
|||||||
|
|
||||||
// TokenRefreshResponse represents all parameters of token refreshing request
|
// TokenRefreshResponse represents all parameters of token refreshing request
|
||||||
type TokenRefreshResponse struct {
|
type TokenRefreshResponse struct {
|
||||||
NewToken string `json:"newToken"`
|
NewToken string `json:"newToken,omitempty"`
|
||||||
OldTokenId string `json:"oldTokenId"`
|
OldTokenId string `json:"oldTokenId,omitempty"`
|
||||||
User *UserBasicInfo `json:"user"`
|
User *UserBasicInfo `json:"user"`
|
||||||
|
NotificationContent string `json:"notificationContent,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TokenInfoResponse represents a view-object of token
|
// TokenInfoResponse represents a view-object of token
|
||||||
@@ -33,8 +35,7 @@ type TokenInfoResponse struct {
|
|||||||
TokenId string `json:"tokenId"`
|
TokenId string `json:"tokenId"`
|
||||||
TokenType core.TokenType `json:"tokenType"`
|
TokenType core.TokenType `json:"tokenType"`
|
||||||
UserAgent string `json:"userAgent"`
|
UserAgent string `json:"userAgent"`
|
||||||
CreatedAt int64 `json:"createdAt"`
|
LastSeen int64 `json:"lastSeen"`
|
||||||
ExpiredAt int64 `json:"expiredAt"`
|
|
||||||
IsCurrent bool `json:"isCurrent"`
|
IsCurrent bool `json:"isCurrent"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,5 +54,5 @@ func (a TokenInfoResponseSlice) Swap(i, j int) {
|
|||||||
|
|
||||||
// Less reports whether the first item is less than the second one
|
// Less reports whether the first item is less than the second one
|
||||||
func (a TokenInfoResponseSlice) Less(i, j int) bool {
|
func (a TokenInfoResponseSlice) Less(i, j int) bool {
|
||||||
return a[i].ExpiredAt > a[j].ExpiredAt
|
return a[i].LastSeen > a[j].LastSeen
|
||||||
}
|
}
|
||||||
|
|||||||
+56
-51
@@ -1,9 +1,7 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||||
@@ -76,6 +74,7 @@ type TransactionCreateRequest struct {
|
|||||||
TagIds []string `json:"tagIds"`
|
TagIds []string `json:"tagIds"`
|
||||||
Comment string `json:"comment" binding:"max=255"`
|
Comment string `json:"comment" binding:"max=255"`
|
||||||
GeoLocation *TransactionGeoLocationRequest `json:"geoLocation" binding:"omitempty"`
|
GeoLocation *TransactionGeoLocationRequest `json:"geoLocation" binding:"omitempty"`
|
||||||
|
ClientSessionId string `json:"clientSessionId"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransactionModifyRequest represents all parameters of transaction modification request
|
// TransactionModifyRequest represents all parameters of transaction modification request
|
||||||
@@ -96,19 +95,23 @@ type TransactionModifyRequest struct {
|
|||||||
|
|
||||||
// TransactionCountRequest represents transaction count request
|
// TransactionCountRequest represents transaction count request
|
||||||
type TransactionCountRequest struct {
|
type TransactionCountRequest struct {
|
||||||
Type TransactionDbType `form:"type" binding:"min=0,max=4"`
|
Type TransactionDbType `form:"type" binding:"min=0,max=4"`
|
||||||
CategoryId int64 `form:"category_id" binding:"min=0"`
|
CategoryIds string `form:"category_ids"`
|
||||||
AccountId int64 `form:"account_id" binding:"min=0"`
|
AccountIds string `form:"account_ids"`
|
||||||
Keyword string `form:"keyword"`
|
TagIds string `form:"tag_ids"`
|
||||||
MaxTime int64 `form:"max_time" binding:"min=0"`
|
AmountFilter string `form:"amount_filter" binding:"validAmountFilter"`
|
||||||
MinTime int64 `form:"min_time" binding:"min=0"`
|
Keyword string `form:"keyword"`
|
||||||
|
MaxTime int64 `form:"max_time" binding:"min=0"`
|
||||||
|
MinTime int64 `form:"min_time" binding:"min=0"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransactionListByMaxTimeRequest represents all parameters of transaction listing by max time request
|
// TransactionListByMaxTimeRequest represents all parameters of transaction listing by max time request
|
||||||
type TransactionListByMaxTimeRequest struct {
|
type TransactionListByMaxTimeRequest struct {
|
||||||
Type TransactionDbType `form:"type" binding:"min=0,max=4"`
|
Type TransactionDbType `form:"type" binding:"min=0,max=4"`
|
||||||
CategoryId int64 `form:"category_id" binding:"min=0"`
|
CategoryIds string `form:"category_ids"`
|
||||||
AccountId int64 `form:"account_id" binding:"min=0"`
|
AccountIds string `form:"account_ids"`
|
||||||
|
TagIds string `form:"tag_ids"`
|
||||||
|
AmountFilter string `form:"amount_filter" binding:"validAmountFilter"`
|
||||||
Keyword string `form:"keyword"`
|
Keyword string `form:"keyword"`
|
||||||
MaxTime int64 `form:"max_time" binding:"min=0"`
|
MaxTime int64 `form:"max_time" binding:"min=0"`
|
||||||
MinTime int64 `form:"min_time" binding:"min=0"`
|
MinTime int64 `form:"min_time" binding:"min=0"`
|
||||||
@@ -125,8 +128,10 @@ type TransactionListInMonthByPageRequest struct {
|
|||||||
Year int32 `form:"year" binding:"required,min=1"`
|
Year int32 `form:"year" binding:"required,min=1"`
|
||||||
Month int32 `form:"month" binding:"required,min=1"`
|
Month int32 `form:"month" binding:"required,min=1"`
|
||||||
Type TransactionDbType `form:"type" binding:"min=0,max=4"`
|
Type TransactionDbType `form:"type" binding:"min=0,max=4"`
|
||||||
CategoryId int64 `form:"category_id" binding:"min=0"`
|
CategoryIds string `form:"category_ids"`
|
||||||
AccountId int64 `form:"account_id" binding:"min=0"`
|
AccountIds string `form:"account_ids"`
|
||||||
|
TagIds string `form:"tag_ids"`
|
||||||
|
AmountFilter string `form:"amount_filter" binding:"validAmountFilter"`
|
||||||
Keyword string `form:"keyword"`
|
Keyword string `form:"keyword"`
|
||||||
TrimAccount bool `form:"trim_account"`
|
TrimAccount bool `form:"trim_account"`
|
||||||
TrimCategory bool `form:"trim_category"`
|
TrimCategory bool `form:"trim_category"`
|
||||||
@@ -135,13 +140,21 @@ type TransactionListInMonthByPageRequest struct {
|
|||||||
|
|
||||||
// TransactionStatisticRequest represents all parameters of transaction statistic request
|
// TransactionStatisticRequest represents all parameters of transaction statistic request
|
||||||
type TransactionStatisticRequest struct {
|
type TransactionStatisticRequest struct {
|
||||||
StartTime int64 `form:"start_time" binding:"min=0"`
|
StartTime int64 `form:"start_time" binding:"min=0"`
|
||||||
EndTime int64 `form:"end_time" binding:"min=0"`
|
EndTime int64 `form:"end_time" binding:"min=0"`
|
||||||
|
UseTransactionTimezone bool `form:"use_transaction_timezone"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransactionStatisticTrendsRequest represents all parameters of transaction statistic trends request
|
||||||
|
type TransactionStatisticTrendsRequest struct {
|
||||||
|
YearMonthRangeRequest
|
||||||
|
UseTransactionTimezone bool `form:"use_transaction_timezone"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransactionAmountsRequest represents all parameters of transaction amounts request
|
// TransactionAmountsRequest represents all parameters of transaction amounts request
|
||||||
type TransactionAmountsRequest struct {
|
type TransactionAmountsRequest struct {
|
||||||
Query string `form:"query"`
|
Query string `form:"query"`
|
||||||
|
UseTransactionTimezone bool `form:"use_transaction_timezone"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransactionAmountsRequestItem represents an item of transaction amounts request
|
// TransactionAmountsRequestItem represents an item of transaction amounts request
|
||||||
@@ -151,12 +164,6 @@ type TransactionAmountsRequestItem struct {
|
|||||||
EndTime int64
|
EndTime int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransactionMonthAmountsRequest represents all parameters of transaction month amounts request
|
|
||||||
type TransactionMonthAmountsRequest struct {
|
|
||||||
StartYearMonth string `form:"start_year_month"`
|
|
||||||
EndYearMonth string `form:"end_year_month"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// TransactionGetRequest represents all parameters of transaction getting request
|
// TransactionGetRequest represents all parameters of transaction getting request
|
||||||
type TransactionGetRequest struct {
|
type TransactionGetRequest struct {
|
||||||
Id int64 `form:"id,string" binding:"required,min=1"`
|
Id int64 `form:"id,string" binding:"required,min=1"`
|
||||||
@@ -170,14 +177,10 @@ type TransactionDeleteRequest struct {
|
|||||||
Id int64 `json:"id,string" binding:"required,min=1"`
|
Id int64 `json:"id,string" binding:"required,min=1"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransactionAccountsAmount represents transaction accounts amount map
|
// YearMonthRangeRequest represents all parameters of a request with year and month range
|
||||||
type TransactionAccountsAmount map[int64]*TransactionAccountAmount
|
type YearMonthRangeRequest struct {
|
||||||
|
StartYearMonth string `form:"start_year_month"`
|
||||||
// TransactionAccountAmount represents transaction account amount
|
EndYearMonth string `form:"end_year_month"`
|
||||||
type TransactionAccountAmount struct {
|
|
||||||
AccountId int64
|
|
||||||
TotalIncomeAmount int64
|
|
||||||
TotalExpenseAmount int64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransactionGeoLocationResponse represents a view-object of transaction geographic location info
|
// TransactionGeoLocationResponse represents a view-object of transaction geographic location info
|
||||||
@@ -227,7 +230,7 @@ type TransactionInfoPageWrapperResponse2 struct {
|
|||||||
TotalCount int64 `json:"totalCount"`
|
TotalCount int64 `json:"totalCount"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransactionStatisticResponse represents an item of transaction amounts
|
// TransactionStatisticResponse represents transaction statistic response
|
||||||
type TransactionStatisticResponse struct {
|
type TransactionStatisticResponse struct {
|
||||||
StartTime int64 `json:"startTime"`
|
StartTime int64 `json:"startTime"`
|
||||||
EndTime int64 `json:"endTime"`
|
EndTime int64 `json:"endTime"`
|
||||||
@@ -241,6 +244,13 @@ type TransactionStatisticResponseItem struct {
|
|||||||
TotalAmount int64 `json:"amount"`
|
TotalAmount int64 `json:"amount"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TransactionStatisticTrendsItem represents the data within each statistic interval
|
||||||
|
type TransactionStatisticTrendsItem struct {
|
||||||
|
Year int32 `json:"year"`
|
||||||
|
Month int32 `json:"month"`
|
||||||
|
Items []*TransactionStatisticResponseItem `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
// TransactionAmountsResponseItem represents an item of transaction amounts
|
// TransactionAmountsResponseItem represents an item of transaction amounts
|
||||||
type TransactionAmountsResponseItem struct {
|
type TransactionAmountsResponseItem struct {
|
||||||
StartTime int64 `json:"startTime"`
|
StartTime int64 `json:"startTime"`
|
||||||
@@ -380,33 +390,28 @@ func (t *TransactionAmountsRequest) GetTransactionAmountsRequestItems() ([]*Tran
|
|||||||
return requestItems, nil
|
return requestItems, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStartTimeAndEndTime returns start unix time and end unix time by request parameter
|
// GetNumericYearMonthRange returns numeric start year, start month, end year and end month
|
||||||
func (t *TransactionMonthAmountsRequest) GetStartTimeAndEndTime(utcOffset int16) (int64, int64, error) {
|
func (t *YearMonthRangeRequest) GetNumericYearMonthRange() (int32, int32, int32, int32, error) {
|
||||||
startUnixTime := int64(0)
|
var startYear, startMonth, endYear, endMonth int32
|
||||||
endUnixTime := time.Now().Unix()
|
var err error
|
||||||
|
|
||||||
if t.StartYearMonth != "" {
|
if t.StartYearMonth != "" {
|
||||||
startTime, err := utils.ParseFromShortDateTime(fmt.Sprintf("%s-1 0:0:0", t.StartYearMonth), utcOffset)
|
startYear, startMonth, err = utils.ParseNumericYearMonth(t.StartYearMonth)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, err
|
return 0, 0, 0, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
startUnixTime = startTime.Unix()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.EndYearMonth != "" {
|
if t.EndYearMonth != "" {
|
||||||
endTime, err := utils.ParseFromShortDateTime(fmt.Sprintf("%s-1 0:0:0", t.EndYearMonth), utcOffset)
|
endYear, endMonth, err = utils.ParseNumericYearMonth(t.EndYearMonth)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, err
|
return 0, 0, 0, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
endTime = endTime.AddDate(0, 1, 0)
|
|
||||||
endUnixTime = endTime.Unix() - 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return startUnixTime, endUnixTime, nil
|
return startYear, startMonth, endYear, endMonth, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransactionInfoResponseSlice represents the slice data structure of TransactionInfoResponse
|
// TransactionInfoResponseSlice represents the slice data structure of TransactionInfoResponse
|
||||||
@@ -431,26 +436,26 @@ func (s TransactionInfoResponseSlice) Less(i, j int) bool {
|
|||||||
return s[i].Id > s[j].Id
|
return s[i].Id > s[j].Id
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransactionMonthAmountsResponseItemSlice represents the slice data structure of TransactionMonthAmountsResponseItem
|
// TransactionStatisticTrendsItemSlice represents the slice data structure of TransactionStatisticTrendsItem
|
||||||
type TransactionMonthAmountsResponseItemSlice []*TransactionMonthAmountsResponseItem
|
type TransactionStatisticTrendsItemSlice []*TransactionStatisticTrendsItem
|
||||||
|
|
||||||
// Len returns the count of items
|
// Len returns the count of items
|
||||||
func (s TransactionMonthAmountsResponseItemSlice) Len() int {
|
func (s TransactionStatisticTrendsItemSlice) Len() int {
|
||||||
return len(s)
|
return len(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Swap swaps two items
|
// Swap swaps two items
|
||||||
func (s TransactionMonthAmountsResponseItemSlice) Swap(i, j int) {
|
func (s TransactionStatisticTrendsItemSlice) Swap(i, j int) {
|
||||||
s[i], s[j] = s[j], s[i]
|
s[i], s[j] = s[j], s[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Less reports whether the first item is less than the second one
|
// Less reports whether the first item is less than the second one
|
||||||
func (s TransactionMonthAmountsResponseItemSlice) Less(i, j int) bool {
|
func (s TransactionStatisticTrendsItemSlice) Less(i, j int) bool {
|
||||||
if s[i].Year != s[j].Year {
|
if s[i].Year != s[j].Year {
|
||||||
return s[i].Year > s[j].Year
|
return s[i].Year < s[j].Year
|
||||||
}
|
}
|
||||||
|
|
||||||
return s[i].Month > s[j].Month
|
return s[i].Month < s[j].Month
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransactionAmountsResponseItemAmountInfoSlice represents the slice data structure of TransactionAmountsResponseItemAmountInfo
|
// TransactionAmountsResponseItemAmountInfoSlice represents the slice data structure of TransactionAmountsResponseItemAmountInfo
|
||||||
|
|||||||
@@ -44,12 +44,13 @@ type TransactionCategoryGetRequest struct {
|
|||||||
|
|
||||||
// TransactionCategoryCreateRequest represents all parameters of single transaction category creation request
|
// TransactionCategoryCreateRequest represents all parameters of single transaction category creation request
|
||||||
type TransactionCategoryCreateRequest struct {
|
type TransactionCategoryCreateRequest struct {
|
||||||
Name string `json:"name" binding:"required,notBlank,max=32"`
|
Name string `json:"name" binding:"required,notBlank,max=32"`
|
||||||
Type TransactionCategoryType `json:"type" binding:"required"`
|
Type TransactionCategoryType `json:"type" binding:"required"`
|
||||||
ParentId int64 `json:"parentId,string" binding:"min=0"`
|
ParentId int64 `json:"parentId,string" binding:"min=0"`
|
||||||
Icon int64 `json:"icon,string" binding:"min=1"`
|
Icon int64 `json:"icon,string" binding:"min=1"`
|
||||||
Color string `json:"color" binding:"required,len=6,validHexRGBColor"`
|
Color string `json:"color" binding:"required,len=6,validHexRGBColor"`
|
||||||
Comment string `json:"comment" binding:"max=255"`
|
Comment string `json:"comment" binding:"max=255"`
|
||||||
|
ClientSessionId string `json:"clientSessionId"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransactionCategoryCreateBatchRequest represents all parameters of transaction category batch creation request
|
// TransactionCategoryCreateBatchRequest represents all parameters of transaction category batch creation request
|
||||||
@@ -69,12 +70,13 @@ type TransactionCategoryCreateWithSubCategories struct {
|
|||||||
|
|
||||||
// TransactionCategoryModifyRequest represents all parameters of transaction category modification request
|
// TransactionCategoryModifyRequest represents all parameters of transaction category modification request
|
||||||
type TransactionCategoryModifyRequest struct {
|
type TransactionCategoryModifyRequest struct {
|
||||||
Id int64 `json:"id,string" binding:"required,min=1"`
|
Id int64 `json:"id,string" binding:"required,min=1"`
|
||||||
Name string `json:"name" binding:"required,notBlank,max=32"`
|
Name string `json:"name" binding:"required,notBlank,max=32"`
|
||||||
Icon int64 `json:"icon,string" binding:"min=1"`
|
ParentId int64 `json:"parentId,string" binding:"min=0"`
|
||||||
Color string `json:"color" binding:"required,len=6,validHexRGBColor"`
|
Icon int64 `json:"icon,string" binding:"min=1"`
|
||||||
Comment string `json:"comment" binding:"max=255"`
|
Color string `json:"color" binding:"required,len=6,validHexRGBColor"`
|
||||||
Hidden bool `json:"hidden"`
|
Comment string `json:"comment" binding:"max=255"`
|
||||||
|
Hidden bool `json:"hidden"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransactionCategoryHideRequest represents all parameters of transaction category hiding request
|
// TransactionCategoryHideRequest represents all parameters of transaction category hiding request
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ package models
|
|||||||
// TransactionTag represents transaction tag data stored in database
|
// TransactionTag represents transaction tag data stored in database
|
||||||
type TransactionTag struct {
|
type TransactionTag struct {
|
||||||
TagId int64 `xorm:"PK"`
|
TagId int64 `xorm:"PK"`
|
||||||
Uid int64 `xorm:"INDEX(IDX_tag_uid_deleted_name) NOT NULL"`
|
Uid int64 `xorm:"INDEX(IDX_tag_uid_deleted_order) NOT NULL"`
|
||||||
Deleted bool `xorm:"INDEX(IDX_tag_uid_deleted_name) NOT NULL"`
|
Deleted bool `xorm:"INDEX(IDX_tag_uid_deleted_order) NOT NULL"`
|
||||||
Name string `xorm:"INDEX(IDX_tag_uid_deleted_name) VARCHAR(32) NOT NULL"`
|
Name string `xorm:"VARCHAR(32) NOT NULL"`
|
||||||
DisplayOrder int32 `xorm:"NOT NULL"`
|
DisplayOrder int32 `xorm:"INDEX(IDX_tag_uid_deleted_order) NOT NULL"`
|
||||||
Hidden bool `xorm:"NOT NULL"`
|
Hidden bool `xorm:"NOT NULL"`
|
||||||
CreatedUnixTime int64
|
CreatedUnixTime int64
|
||||||
UpdatedUnixTime int64
|
UpdatedUnixTime int64
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ package models
|
|||||||
// TransactionTagIndex represents transaction and transaction tag relation stored in database
|
// TransactionTagIndex represents transaction and transaction tag relation stored in database
|
||||||
type TransactionTagIndex struct {
|
type TransactionTagIndex struct {
|
||||||
TagIndexId int64 `xorm:"PK"`
|
TagIndexId int64 `xorm:"PK"`
|
||||||
Uid int64 `xorm:"INDEX(IDX_transaction_tag_index_uid_deleted_tag_id_transaction_id) INDEX(IDX_transaction_tag_index_uid_deleted_tag_id_transaction_time) INDEX(IDX_transaction_tag_index_uid_deleted_transaction_id)"`
|
Uid int64 `xorm:"INDEX(IDX_transaction_tag_index_uid_deleted_tag_id_transaction_id) INDEX(IDX_transaction_tag_index_uid_deleted_transaction_time_tag_id) INDEX(IDX_transaction_tag_index_uid_deleted_transaction_id)"`
|
||||||
Deleted bool `xorm:"INDEX(IDX_transaction_tag_index_uid_deleted_tag_id_transaction_id) INDEX(IDX_transaction_tag_index_uid_deleted_tag_id_transaction_time) INDEX(IDX_transaction_tag_index_uid_deleted_transaction_id) NOT NULL"`
|
Deleted bool `xorm:"INDEX(IDX_transaction_tag_index_uid_deleted_tag_id_transaction_id) INDEX(IDX_transaction_tag_index_uid_deleted_transaction_time_tag_id) INDEX(IDX_transaction_tag_index_uid_deleted_transaction_id) NOT NULL"`
|
||||||
TagId int64 `xorm:"INDEX(IDX_transaction_tag_index_uid_deleted_tag_id_transaction_id) INDEX(IDX_transaction_tag_index_uid_deleted_tag_id_transaction_time)"`
|
TransactionTime int64 `xorm:"INDEX(IDX_transaction_tag_index_uid_deleted_transaction_time_tag_id) NOT NULL"`
|
||||||
|
TagId int64 `xorm:"INDEX(IDX_transaction_tag_index_uid_deleted_tag_id_transaction_id) INDEX(IDX_transaction_tag_index_uid_deleted_transaction_time_tag_id)"`
|
||||||
TransactionId int64 `xorm:"INDEX(IDX_transaction_tag_index_uid_deleted_tag_id_transaction_id) INDEX(IDX_transaction_tag_index_uid_deleted_transaction_id)"`
|
TransactionId int64 `xorm:"INDEX(IDX_transaction_tag_index_uid_deleted_tag_id_transaction_id) INDEX(IDX_transaction_tag_index_uid_deleted_transaction_id)"`
|
||||||
TransactionTime int64 `xorm:"INDEX(IDX_transaction_tag_index_uid_deleted_tag_id_transaction_time) NOT NULL"`
|
|
||||||
CreatedUnixTime int64
|
CreatedUnixTime int64
|
||||||
UpdatedUnixTime int64
|
UpdatedUnixTime int64
|
||||||
DeletedUnixTime int64
|
DeletedUnixTime int64
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TransactionTemplateType represents transaction template type in database
|
||||||
|
type TransactionTemplateType byte
|
||||||
|
|
||||||
|
// Transaction template types
|
||||||
|
const (
|
||||||
|
TRANSACTION_TEMPLATE_TYPE_NORMAL TransactionTemplateType = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
// TransactionTemplate represents transaction template stored in database
|
||||||
|
type TransactionTemplate struct {
|
||||||
|
TemplateId int64 `xorm:"PK"`
|
||||||
|
Uid int64 `xorm:"INDEX(IDX_transaction_uid_deleted_template_type_order) NOT NULL"`
|
||||||
|
Deleted bool `xorm:"INDEX(IDX_transaction_uid_deleted_template_type_order) NOT NULL"`
|
||||||
|
TemplateType TransactionTemplateType `xorm:"INDEX(IDX_transaction_uid_deleted_template_type_order) NOT NULL"`
|
||||||
|
Name string `xorm:"VARCHAR(32) NOT NULL"`
|
||||||
|
Type TransactionType `xorm:"NOT NULL"`
|
||||||
|
CategoryId int64 `xorm:"NOT NULL"`
|
||||||
|
AccountId int64 `xorm:"NOT NULL"`
|
||||||
|
TagIds string `xorm:"VARCHAR(255) NOT NULL"`
|
||||||
|
Amount int64 `xorm:"NOT NULL"`
|
||||||
|
RelatedAccountId int64 `xorm:"NOT NULL"`
|
||||||
|
RelatedAccountAmount int64 `xorm:"NOT NULL"`
|
||||||
|
HideAmount bool `xorm:"NOT NULL"`
|
||||||
|
Comment string `xorm:"VARCHAR(255) NOT NULL"`
|
||||||
|
DisplayOrder int32 `xorm:"INDEX(IDX_transaction_uid_deleted_template_type_order) NOT NULL"`
|
||||||
|
Hidden bool `xorm:"NOT NULL"`
|
||||||
|
CreatedUnixTime int64
|
||||||
|
UpdatedUnixTime int64
|
||||||
|
DeletedUnixTime int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransactionTemplateListRequest represents all parameters of transaction template list request
|
||||||
|
type TransactionTemplateListRequest struct {
|
||||||
|
TemplateType TransactionTemplateType `form:"templateType"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransactionTemplateGetRequest represents all parameters of transaction template getting request
|
||||||
|
type TransactionTemplateGetRequest struct {
|
||||||
|
Id int64 `form:"id,string" binding:"required,min=1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransactionTemplateCreateRequest represents all parameters of transaction template creation request
|
||||||
|
type TransactionTemplateCreateRequest struct {
|
||||||
|
TemplateType TransactionTemplateType `json:"templateType"`
|
||||||
|
Name string `json:"name" binding:"required,notBlank,max=32"`
|
||||||
|
Type TransactionType `json:"type" binding:"required"`
|
||||||
|
CategoryId int64 `json:"categoryId,string" binding:"required,min=1"`
|
||||||
|
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"`
|
||||||
|
ClientSessionId string `json:"clientSessionId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransactionTemplateModifyNameRequest represents all parameters of transaction template name modification request
|
||||||
|
type TransactionTemplateModifyNameRequest struct {
|
||||||
|
Id int64 `json:"id,string" binding:"required,min=1"`
|
||||||
|
Name string `json:"name" binding:"required,notBlank,max=32"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransactionTemplateModifyRequest represents all parameters of transaction template modification request
|
||||||
|
type TransactionTemplateModifyRequest struct {
|
||||||
|
Id int64 `json:"id,string" binding:"required,min=1"`
|
||||||
|
Name string `json:"name" binding:"required,notBlank,max=32"`
|
||||||
|
Type TransactionType `json:"type" binding:"required"`
|
||||||
|
CategoryId int64 `json:"categoryId,string" binding:"required,min=1"`
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransactionTemplateHideRequest represents all parameters of transaction template hiding request
|
||||||
|
type TransactionTemplateHideRequest struct {
|
||||||
|
Id int64 `json:"id,string" binding:"required,min=1"`
|
||||||
|
Hidden bool `json:"hidden"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransactionTemplateMoveRequest represents all parameters of transaction template moving request
|
||||||
|
type TransactionTemplateMoveRequest struct {
|
||||||
|
NewDisplayOrders []*TransactionTemplateNewDisplayOrderRequest `json:"newDisplayOrders"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransactionTemplateNewDisplayOrderRequest represents a data pair of id and display order
|
||||||
|
type TransactionTemplateNewDisplayOrderRequest struct {
|
||||||
|
Id int64 `json:"id,string" binding:"required,min=1"`
|
||||||
|
DisplayOrder int32 `json:"displayOrder"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransactionTemplateDeleteRequest represents all parameters of transaction template deleting request
|
||||||
|
type TransactionTemplateDeleteRequest struct {
|
||||||
|
Id int64 `json:"id,string" binding:"required,min=1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransactionTemplateInfoResponse struct {
|
||||||
|
*TransactionInfoResponse
|
||||||
|
TemplateType TransactionTemplateType `json:"templateType"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
DisplayOrder int32 `json:"displayOrder"`
|
||||||
|
Hidden bool `json:"hidden"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToTransactionInfoResponse returns a view-object according to database model
|
||||||
|
func (t *TransactionTemplate) ToTransactionInfoResponse(utcOffset int16) *TransactionInfoResponse {
|
||||||
|
tagIds := make([]string, 0, 0)
|
||||||
|
|
||||||
|
if t.TagIds != "" {
|
||||||
|
tagIds = strings.Split(t.TagIds, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TransactionInfoResponse{
|
||||||
|
Id: t.TemplateId,
|
||||||
|
TimeSequenceId: utils.GetMinTransactionTimeFromUnixTime(t.CreatedUnixTime),
|
||||||
|
Type: t.Type,
|
||||||
|
CategoryId: t.CategoryId,
|
||||||
|
Time: 0,
|
||||||
|
UtcOffset: utcOffset,
|
||||||
|
SourceAccountId: t.AccountId,
|
||||||
|
DestinationAccountId: t.RelatedAccountId,
|
||||||
|
SourceAmount: t.Amount,
|
||||||
|
DestinationAmount: t.RelatedAccountAmount,
|
||||||
|
HideAmount: t.HideAmount,
|
||||||
|
TagIds: tagIds,
|
||||||
|
Comment: t.Comment,
|
||||||
|
GeoLocation: nil,
|
||||||
|
Editable: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToTransactionTemplateInfoResponse returns a view-object according to database model
|
||||||
|
func (t *TransactionTemplate) ToTransactionTemplateInfoResponse(utcOffset int16) *TransactionTemplateInfoResponse {
|
||||||
|
return &TransactionTemplateInfoResponse{
|
||||||
|
TransactionInfoResponse: t.ToTransactionInfoResponse(utcOffset),
|
||||||
|
TemplateType: t.TemplateType,
|
||||||
|
Name: t.Name,
|
||||||
|
DisplayOrder: t.DisplayOrder,
|
||||||
|
Hidden: t.Hidden,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransactionTemplateInfoResponseSlice represents the slice data structure of TransactionTemplateInfoResponse
|
||||||
|
type TransactionTemplateInfoResponseSlice []*TransactionTemplateInfoResponse
|
||||||
|
|
||||||
|
// Len returns the count of items
|
||||||
|
func (s TransactionTemplateInfoResponseSlice) Len() int {
|
||||||
|
return len(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap swaps two items
|
||||||
|
func (s TransactionTemplateInfoResponseSlice) Swap(i, j int) {
|
||||||
|
s[i], s[j] = s[j], s[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Less reports whether the first item is less than the second one
|
||||||
|
func (s TransactionTemplateInfoResponseSlice) Less(i, j int) bool {
|
||||||
|
return s[i].DisplayOrder < s[j].DisplayOrder
|
||||||
|
}
|
||||||
+110
-76
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||||
)
|
)
|
||||||
@@ -11,7 +12,7 @@ import (
|
|||||||
// TransactionEditScope represents the scope which transaction can be edited
|
// TransactionEditScope represents the scope which transaction can be edited
|
||||||
type TransactionEditScope byte
|
type TransactionEditScope byte
|
||||||
|
|
||||||
// Editable Transaction Scopes
|
// Editable Transaction Ranges
|
||||||
const (
|
const (
|
||||||
TRANSACTION_EDIT_SCOPE_NONE TransactionEditScope = 0
|
TRANSACTION_EDIT_SCOPE_NONE TransactionEditScope = 0
|
||||||
TRANSACTION_EDIT_SCOPE_ALL TransactionEditScope = 1
|
TRANSACTION_EDIT_SCOPE_ALL TransactionEditScope = 1
|
||||||
@@ -23,7 +24,7 @@ const (
|
|||||||
TRANSACTION_EDIT_SCOPE_INVALID TransactionEditScope = 255
|
TRANSACTION_EDIT_SCOPE_INVALID TransactionEditScope = 255
|
||||||
)
|
)
|
||||||
|
|
||||||
// String returns a textual representation of the editable transaction scopes enum
|
// String returns a textual representation of the editable transaction ranges enum
|
||||||
func (s TransactionEditScope) String() string {
|
func (s TransactionEditScope) String() string {
|
||||||
switch s {
|
switch s {
|
||||||
case TRANSACTION_EDIT_SCOPE_NONE:
|
case TRANSACTION_EDIT_SCOPE_NONE:
|
||||||
@@ -47,6 +48,39 @@ func (s TransactionEditScope) String() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AmountColorType represents the type of amount color in frontend
|
||||||
|
type AmountColorType byte
|
||||||
|
|
||||||
|
// Amount Color Types
|
||||||
|
const (
|
||||||
|
AMOUNT_COLOR_TYPE_DEFAULT AmountColorType = 0
|
||||||
|
AMOUNT_COLOR_TYPE_GREEN AmountColorType = 1
|
||||||
|
AMOUNT_COLOR_TYPE_RED AmountColorType = 2
|
||||||
|
AMOUNT_COLOR_TYPE_YELLOW AmountColorType = 3
|
||||||
|
AMOUNT_COLOR_TYPE_BLACK_OR_WHITE AmountColorType = 4
|
||||||
|
AMOUNT_COLOR_TYPE_INVALID AmountColorType = 255
|
||||||
|
)
|
||||||
|
|
||||||
|
// String returns a textual representation of the amount color type enum
|
||||||
|
func (s AmountColorType) String() string {
|
||||||
|
switch s {
|
||||||
|
case AMOUNT_COLOR_TYPE_DEFAULT:
|
||||||
|
return "Default"
|
||||||
|
case AMOUNT_COLOR_TYPE_GREEN:
|
||||||
|
return "Green"
|
||||||
|
case AMOUNT_COLOR_TYPE_RED:
|
||||||
|
return "Red"
|
||||||
|
case AMOUNT_COLOR_TYPE_YELLOW:
|
||||||
|
return "Yellow"
|
||||||
|
case AMOUNT_COLOR_TYPE_BLACK_OR_WHITE:
|
||||||
|
return "Black or White"
|
||||||
|
case AMOUNT_COLOR_TYPE_INVALID:
|
||||||
|
return "Invalid"
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("Invalid(%d)", int(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// User represents user data stored in database
|
// User represents user data stored in database
|
||||||
type User struct {
|
type User struct {
|
||||||
Uid int64 `xorm:"PK"`
|
Uid int64 `xorm:"PK"`
|
||||||
@@ -55,18 +89,25 @@ type User struct {
|
|||||||
Nickname string `xorm:"VARCHAR(64) NOT NULL"`
|
Nickname string `xorm:"VARCHAR(64) NOT NULL"`
|
||||||
Password string `xorm:"VARCHAR(64) NOT NULL"`
|
Password string `xorm:"VARCHAR(64) NOT NULL"`
|
||||||
Salt string `xorm:"VARCHAR(10) NOT NULL"`
|
Salt string `xorm:"VARCHAR(10) NOT NULL"`
|
||||||
|
CustomAvatarType string `xorm:"VARCHAR(10)"`
|
||||||
DefaultAccountId int64
|
DefaultAccountId int64
|
||||||
TransactionEditScope TransactionEditScope `xorm:"TINYINT NOT NULL"`
|
TransactionEditScope TransactionEditScope `xorm:"TINYINT NOT NULL"`
|
||||||
Language string `xorm:"VARCHAR(10)"`
|
Language string `xorm:"VARCHAR(10)"`
|
||||||
DefaultCurrency string `xorm:"VARCHAR(3) NOT NULL"`
|
DefaultCurrency string `xorm:"VARCHAR(3) NOT NULL"`
|
||||||
FirstDayOfWeek WeekDay `xorm:"TINYINT NOT NULL"`
|
FirstDayOfWeek WeekDay `xorm:"TINYINT NOT NULL"`
|
||||||
LongDateFormat LongDateFormat `xorm:"TINYINT"`
|
LongDateFormat LongDateFormat `xorm:"TINYINT"`
|
||||||
ShortDateFormat ShortDateFormat `xorm:"TINYINT"`
|
ShortDateFormat ShortDateFormat `xorm:"TINYINT"`
|
||||||
LongTimeFormat LongTimeFormat `xorm:"TINYINT"`
|
LongTimeFormat LongTimeFormat `xorm:"TINYINT"`
|
||||||
ShortTimeFormat ShortTimeFormat `xorm:"TINYINT"`
|
ShortTimeFormat ShortTimeFormat `xorm:"TINYINT"`
|
||||||
Disabled bool `xorm:"NOT NULL"`
|
DecimalSeparator core.DecimalSeparator `xorm:"TINYINT"`
|
||||||
Deleted bool `xorm:"NOT NULL"`
|
DigitGroupingSymbol core.DigitGroupingSymbol `xorm:"TINYINT"`
|
||||||
EmailVerified bool `xorm:"NOT NULL"`
|
DigitGrouping core.DigitGroupingType `xorm:"TINYINT"`
|
||||||
|
CurrencyDisplayType CurrencyDisplayType `xorm:"TINYINT"`
|
||||||
|
ExpenseAmountColor AmountColorType `xorm:"TINYINT"`
|
||||||
|
IncomeAmountColor AmountColorType `xorm:"TINYINT"`
|
||||||
|
Disabled bool
|
||||||
|
Deleted bool `xorm:"NOT NULL"`
|
||||||
|
EmailVerified bool `xorm:"NOT NULL"`
|
||||||
CreatedUnixTime int64
|
CreatedUnixTime int64
|
||||||
UpdatedUnixTime int64
|
UpdatedUnixTime int64
|
||||||
DeletedUnixTime int64
|
DeletedUnixTime int64
|
||||||
@@ -75,21 +116,27 @@ type User struct {
|
|||||||
|
|
||||||
// UserBasicInfo represents a view-object of user basic info
|
// UserBasicInfo represents a view-object of user basic info
|
||||||
type UserBasicInfo struct {
|
type UserBasicInfo struct {
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Nickname string `json:"nickname"`
|
Nickname string `json:"nickname"`
|
||||||
AvatarUrl string `json:"avatar"`
|
AvatarUrl string `json:"avatar"`
|
||||||
AvatarProvider string `json:"avatarProvider,omitempty"`
|
AvatarProvider string `json:"avatarProvider,omitempty"`
|
||||||
DefaultAccountId int64 `json:"defaultAccountId,string"`
|
DefaultAccountId int64 `json:"defaultAccountId,string"`
|
||||||
TransactionEditScope TransactionEditScope `json:"transactionEditScope"`
|
TransactionEditScope TransactionEditScope `json:"transactionEditScope"`
|
||||||
Language string `json:"language"`
|
Language string `json:"language"`
|
||||||
DefaultCurrency string `json:"defaultCurrency"`
|
DefaultCurrency string `json:"defaultCurrency"`
|
||||||
FirstDayOfWeek WeekDay `json:"firstDayOfWeek"`
|
FirstDayOfWeek WeekDay `json:"firstDayOfWeek"`
|
||||||
LongDateFormat LongDateFormat `json:"longDateFormat"`
|
LongDateFormat LongDateFormat `json:"longDateFormat"`
|
||||||
ShortDateFormat ShortDateFormat `json:"shortDateFormat"`
|
ShortDateFormat ShortDateFormat `json:"shortDateFormat"`
|
||||||
LongTimeFormat LongTimeFormat `json:"longTimeFormat"`
|
LongTimeFormat LongTimeFormat `json:"longTimeFormat"`
|
||||||
ShortTimeFormat ShortTimeFormat `json:"shortTimeFormat"`
|
ShortTimeFormat ShortTimeFormat `json:"shortTimeFormat"`
|
||||||
EmailVerified bool `json:"emailVerified"`
|
DecimalSeparator core.DecimalSeparator `json:"decimalSeparator"`
|
||||||
|
DigitGroupingSymbol core.DigitGroupingSymbol `json:"digitGroupingSymbol"`
|
||||||
|
DigitGrouping core.DigitGroupingType `json:"digitGrouping"`
|
||||||
|
CurrencyDisplayType CurrencyDisplayType `json:"currencyDisplayType"`
|
||||||
|
ExpenseAmountColor AmountColorType `json:"expenseAmountColor"`
|
||||||
|
IncomeAmountColor AmountColorType `json:"incomeAmountColor"`
|
||||||
|
EmailVerified bool `json:"emailVerified"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserLoginRequest represents all parameters of user login request
|
// UserLoginRequest represents all parameters of user login request
|
||||||
@@ -117,8 +164,9 @@ type UserVerifyEmailRequest struct {
|
|||||||
|
|
||||||
// UserVerifyEmailResponse represents all response parameters after user have verified email
|
// UserVerifyEmailResponse represents all response parameters after user have verified email
|
||||||
type UserVerifyEmailResponse struct {
|
type UserVerifyEmailResponse struct {
|
||||||
NewToken string `json:"newToken,omitempty"`
|
NewToken string `json:"newToken,omitempty"`
|
||||||
User *UserBasicInfo `json:"user"`
|
User *UserBasicInfo `json:"user"`
|
||||||
|
NotificationContent string `json:"notificationContent,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserResendVerifyEmailRequest represents all parameters of user resend verify email request
|
// UserResendVerifyEmailRequest represents all parameters of user resend verify email request
|
||||||
@@ -129,19 +177,25 @@ type UserResendVerifyEmailRequest struct {
|
|||||||
|
|
||||||
// UserProfileUpdateRequest represents all parameters of user updating profile request
|
// UserProfileUpdateRequest represents all parameters of user updating profile request
|
||||||
type UserProfileUpdateRequest struct {
|
type UserProfileUpdateRequest struct {
|
||||||
Email string `json:"email" binding:"omitempty,notBlank,max=100,validEmail"`
|
Email string `json:"email" binding:"omitempty,notBlank,max=100,validEmail"`
|
||||||
Nickname string `json:"nickname" binding:"omitempty,notBlank,max=64"`
|
Nickname string `json:"nickname" binding:"omitempty,notBlank,max=64"`
|
||||||
Password string `json:"password" binding:"omitempty,min=6,max=128"`
|
Password string `json:"password" binding:"omitempty,min=6,max=128"`
|
||||||
OldPassword string `json:"oldPassword" 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"`
|
DefaultAccountId int64 `json:"defaultAccountId,string" binding:"omitempty,min=1"`
|
||||||
TransactionEditScope *TransactionEditScope `json:"transactionEditScope" binding:"omitempty,min=0,max=7"`
|
TransactionEditScope *TransactionEditScope `json:"transactionEditScope" binding:"omitempty,min=0,max=6"`
|
||||||
Language string `json:"language" binding:"omitempty,min=2,max=16"`
|
Language string `json:"language" binding:"omitempty,min=2,max=16"`
|
||||||
DefaultCurrency string `json:"defaultCurrency" binding:"omitempty,len=3,validCurrency"`
|
DefaultCurrency string `json:"defaultCurrency" binding:"omitempty,len=3,validCurrency"`
|
||||||
FirstDayOfWeek *WeekDay `json:"firstDayOfWeek" binding:"omitempty,min=0,max=6"`
|
FirstDayOfWeek *WeekDay `json:"firstDayOfWeek" binding:"omitempty,min=0,max=6"`
|
||||||
LongDateFormat *LongDateFormat `json:"longDateFormat" binding:"omitempty,min=0,max=3"`
|
LongDateFormat *LongDateFormat `json:"longDateFormat" binding:"omitempty,min=0,max=3"`
|
||||||
ShortDateFormat *ShortDateFormat `json:"shortDateFormat" 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"`
|
LongTimeFormat *LongTimeFormat `json:"longTimeFormat" binding:"omitempty,min=0,max=3"`
|
||||||
ShortTimeFormat *ShortTimeFormat `json:"shortTimeFormat" binding:"omitempty,min=0,max=3"`
|
ShortTimeFormat *ShortTimeFormat `json:"shortTimeFormat" binding:"omitempty,min=0,max=3"`
|
||||||
|
DecimalSeparator *core.DecimalSeparator `json:"decimalSeparator" binding:"omitempty,min=0,max=3"`
|
||||||
|
DigitGroupingSymbol *core.DigitGroupingSymbol `json:"digitGroupingSymbol" binding:"omitempty,min=0,max=4"`
|
||||||
|
DigitGrouping *core.DigitGroupingType `json:"digitGrouping" binding:"omitempty,min=0,max=2"`
|
||||||
|
CurrencyDisplayType *CurrencyDisplayType `json:"currencyDisplayType" binding:"omitempty,min=0,max=11"`
|
||||||
|
ExpenseAmountColor *AmountColorType `json:"expenseAmountColor" binding:"omitempty,min=0,max=4"`
|
||||||
|
IncomeAmountColor *AmountColorType `json:"incomeAmountColor" binding:"omitempty,min=0,max=4"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserProfileUpdateResponse represents the data returns to frontend after updating profile
|
// UserProfileUpdateResponse represents the data returns to frontend after updating profile
|
||||||
@@ -152,22 +206,8 @@ type UserProfileUpdateResponse struct {
|
|||||||
|
|
||||||
// UserProfileResponse represents a view-object of user profile
|
// UserProfileResponse represents a view-object of user profile
|
||||||
type UserProfileResponse struct {
|
type UserProfileResponse struct {
|
||||||
Username string `json:"username"`
|
*UserBasicInfo
|
||||||
Email string `json:"email"`
|
LastLoginAt int64 `json:"lastLoginAt"`
|
||||||
Nickname string `json:"nickname"`
|
|
||||||
AvatarUrl string `json:"avatar"`
|
|
||||||
AvatarProvider string `json:"avatarProvider,omitempty"`
|
|
||||||
DefaultAccountId int64 `json:"defaultAccountId,string"`
|
|
||||||
TransactionEditScope TransactionEditScope `json:"transactionEditScope"`
|
|
||||||
Language string `json:"language"`
|
|
||||||
DefaultCurrency string `json:"defaultCurrency"`
|
|
||||||
FirstDayOfWeek WeekDay `json:"firstDayOfWeek"`
|
|
||||||
LongDateFormat LongDateFormat `json:"longDateFormat"`
|
|
||||||
ShortDateFormat ShortDateFormat `json:"shortDateFormat"`
|
|
||||||
LongTimeFormat LongTimeFormat `json:"longTimeFormat"`
|
|
||||||
ShortTimeFormat ShortTimeFormat `json:"shortTimeFormat"`
|
|
||||||
EmailVerified bool `json:"emailVerified"`
|
|
||||||
LastLoginAt int64 `json:"lastLoginAt"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanEditTransactionByTransactionTime returns whether this user can edit transaction with specified transaction time
|
// CanEditTransactionByTransactionTime returns whether this user can edit transaction with specified transaction time
|
||||||
@@ -229,6 +269,12 @@ func (u *User) ToUserBasicInfo() *UserBasicInfo {
|
|||||||
ShortDateFormat: u.ShortDateFormat,
|
ShortDateFormat: u.ShortDateFormat,
|
||||||
LongTimeFormat: u.LongTimeFormat,
|
LongTimeFormat: u.LongTimeFormat,
|
||||||
ShortTimeFormat: u.ShortTimeFormat,
|
ShortTimeFormat: u.ShortTimeFormat,
|
||||||
|
DecimalSeparator: u.DecimalSeparator,
|
||||||
|
DigitGroupingSymbol: u.DigitGroupingSymbol,
|
||||||
|
DigitGrouping: u.DigitGrouping,
|
||||||
|
CurrencyDisplayType: u.CurrencyDisplayType,
|
||||||
|
ExpenseAmountColor: u.ExpenseAmountColor,
|
||||||
|
IncomeAmountColor: u.IncomeAmountColor,
|
||||||
EmailVerified: u.EmailVerified,
|
EmailVerified: u.EmailVerified,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -236,22 +282,8 @@ func (u *User) ToUserBasicInfo() *UserBasicInfo {
|
|||||||
// ToUserProfileResponse returns a user profile view-object according to database model
|
// ToUserProfileResponse returns a user profile view-object according to database model
|
||||||
func (u *User) ToUserProfileResponse() *UserProfileResponse {
|
func (u *User) ToUserProfileResponse() *UserProfileResponse {
|
||||||
return &UserProfileResponse{
|
return &UserProfileResponse{
|
||||||
Username: u.Username,
|
UserBasicInfo: u.ToUserBasicInfo(),
|
||||||
Email: u.Email,
|
LastLoginAt: u.LastLoginUnixTime,
|
||||||
Nickname: u.Nickname,
|
|
||||||
AvatarUrl: u.getAvatarUrl(),
|
|
||||||
AvatarProvider: u.getAvatarProvider(),
|
|
||||||
DefaultAccountId: u.DefaultAccountId,
|
|
||||||
TransactionEditScope: u.TransactionEditScope,
|
|
||||||
Language: u.Language,
|
|
||||||
DefaultCurrency: u.DefaultCurrency,
|
|
||||||
FirstDayOfWeek: u.FirstDayOfWeek,
|
|
||||||
LongDateFormat: u.LongDateFormat,
|
|
||||||
ShortDateFormat: u.ShortDateFormat,
|
|
||||||
LongTimeFormat: u.LongTimeFormat,
|
|
||||||
ShortTimeFormat: u.ShortTimeFormat,
|
|
||||||
EmailVerified: u.EmailVerified,
|
|
||||||
LastLoginAt: u.LastLoginUnixTime,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,7 +294,9 @@ func (u *User) getAvatarProvider() string {
|
|||||||
func (u *User) getAvatarUrl() string {
|
func (u *User) getAvatarUrl() string {
|
||||||
avatarProvider := settings.Container.Current.AvatarProvider
|
avatarProvider := settings.Container.Current.AvatarProvider
|
||||||
|
|
||||||
if avatarProvider == settings.GravatarProvider {
|
if avatarProvider == settings.InternalAvatarProvider {
|
||||||
|
return utils.GetInternalAvatarUrl(u.Uid, u.CustomAvatarType, settings.Container.Current.RootUrl)
|
||||||
|
} else if avatarProvider == settings.GravatarProvider {
|
||||||
return utils.GetGravatarUrl(u.Email)
|
return utils.GetGravatarUrl(u.Email)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash/crc32"
|
"hash/crc32"
|
||||||
"math"
|
|
||||||
"net"
|
"net"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@@ -19,15 +18,18 @@ import (
|
|||||||
|
|
||||||
// Length and mask of all information in request id
|
// Length and mask of all information in request id
|
||||||
const (
|
const (
|
||||||
requestIdLength = 36
|
requestIdLength = 36
|
||||||
secondsTodayBits = 17
|
secondsTodayBits = 17
|
||||||
secondsTodayBitsMask = (1 << secondsTodayBits) - 1
|
secondsTodayBitsMask = (1 << secondsTodayBits) - 1
|
||||||
randomNumberBits = 15
|
clientPortNumberAllBits = 16
|
||||||
randomNumberBitsMask = (1 << randomNumberBits) - 1
|
clientPortNumberHigh1Bit = 1
|
||||||
reqSeqNumberBits = 31
|
clientPortNumberLow15Bits = clientPortNumberAllBits - clientPortNumberHigh1Bit
|
||||||
reqSeqNumberBitsMask = (1 << reqSeqNumberBits) - 1
|
clientPortNumberHigh1BitMask = 1 << clientPortNumberLow15Bits
|
||||||
clientIpv6Bit = 1
|
clientPortNumberLow15BitsMask = clientPortNumberHigh1BitMask - 1
|
||||||
clientIpv6BitMask = 1
|
reqSeqNumberBits = 30
|
||||||
|
reqSeqNumberBitsMask = (1 << reqSeqNumberBits) - 1
|
||||||
|
clientIpv6Bit = 1
|
||||||
|
clientIpv6BitMask = 1
|
||||||
)
|
)
|
||||||
|
|
||||||
// RequestIdInfo represents a struct which has all information in request id
|
// RequestIdInfo represents a struct which has all information in request id
|
||||||
@@ -35,10 +37,10 @@ type RequestIdInfo struct {
|
|||||||
ServerUniqId uint16
|
ServerUniqId uint16
|
||||||
InstanceUniqId uint16
|
InstanceUniqId uint16
|
||||||
SecondsElapsedToday uint32
|
SecondsElapsedToday uint32
|
||||||
RandomNumber uint32
|
|
||||||
RequestSeqId uint32
|
RequestSeqId uint32
|
||||||
IsClientIpv6 bool
|
IsClientIpv6 bool
|
||||||
ClientIp uint32
|
ClientIp uint32
|
||||||
|
ClientPort uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultRequestIdGenerator represents default request id generator
|
// DefaultRequestIdGenerator represents default request id generator
|
||||||
@@ -121,7 +123,7 @@ func (r *DefaultRequestIdGenerator) GetCurrentInstanceUniqId() uint16 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GenerateRequestId returns a new request id
|
// GenerateRequestId returns a new request id
|
||||||
func (r *DefaultRequestIdGenerator) GenerateRequestId(clientIpAddr string) string {
|
func (r *DefaultRequestIdGenerator) GenerateRequestId(clientIpAddr string, clientPort uint16) string {
|
||||||
ip := net.ParseIP(clientIpAddr)
|
ip := net.ParseIP(clientIpAddr)
|
||||||
isClientIpv6 := ip.To4() == nil
|
isClientIpv6 := ip.To4() == nil
|
||||||
var clientIp uint32
|
var clientIp uint32
|
||||||
@@ -132,38 +134,38 @@ func (r *DefaultRequestIdGenerator) GenerateRequestId(clientIpAddr string) strin
|
|||||||
clientIp = binary.BigEndian.Uint32(ip.To4())
|
clientIp = binary.BigEndian.Uint32(ip.To4())
|
||||||
}
|
}
|
||||||
|
|
||||||
requestId := r.getRequestId(r.serverUniqId, r.instanceUniqId, isClientIpv6, clientIp)
|
requestId := r.getRequestId(r.serverUniqId, r.instanceUniqId, isClientIpv6, clientIp, clientPort)
|
||||||
|
|
||||||
return requestId
|
return requestId
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *DefaultRequestIdGenerator) getRequestId(serverUniqId uint16, instanceUniqId uint16, clientIpV6 bool, clientIp uint32) string {
|
func (r *DefaultRequestIdGenerator) getRequestId(serverUniqId uint16, instanceUniqId uint16, clientIpV6 bool, clientIp uint32, clientPort uint16) string {
|
||||||
clientIpv6Flag := uint32(0)
|
clientIpv6Flag := uint32(0)
|
||||||
|
|
||||||
if clientIpV6 {
|
if clientIpV6 {
|
||||||
clientIpv6Flag = uint32(1)
|
clientIpv6Flag = uint32(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 128bits = serverUniqId(16bits) + instanceUniqId(16bits) + secondsElapsedToday(17bits) + randomNumber(15bits) + sequentialNumber(31bits) + clientIpv6Flag(1bit) + clientIp(32bits)
|
// 128bits = serverUniqId(16bits) + instanceUniqId(16bits) + secondsElapsedToday(17bits) + clientPortLow15Bits(15bits) + sequentialNumber(30bits) + clientPortHigh1Bit(1bit) + clientIpv6Flag(1bit) + clientIp(32bits)
|
||||||
|
|
||||||
secondsElapsedToday := r.getSecondsElapsedToday()
|
secondsElapsedToday := r.getSecondsElapsedToday()
|
||||||
secondsLow17bits := uint32(secondsElapsedToday & secondsTodayBitsMask)
|
secondsLow17bits := uint32(secondsElapsedToday & secondsTodayBitsMask)
|
||||||
|
|
||||||
randomNumber, _ := utils.GetRandomInteger(math.MaxInt16)
|
clientPortHigh1bit := uint32((clientPort & clientPortNumberHigh1BitMask) >> clientPortNumberLow15Bits)
|
||||||
randomNumberLow15bits := uint32(randomNumber & randomNumberBitsMask)
|
clientPortLow15bits := uint32(clientPort & clientPortNumberLow15BitsMask)
|
||||||
|
|
||||||
secondsAndRandomNumber := (secondsLow17bits << randomNumberBits) | randomNumberLow15bits
|
secondsAndClientPortLowBits := (secondsLow17bits << clientPortNumberLow15Bits) | clientPortLow15bits
|
||||||
|
|
||||||
seqId := r.requestSeqId.Add(1)
|
seqId := r.requestSeqId.Add(1)
|
||||||
seqIdLow31bits := seqId & reqSeqNumberBitsMask
|
seqIdLow30bits := seqId & reqSeqNumberBitsMask
|
||||||
|
|
||||||
seqIdAndClientIpv6Flag := (seqIdLow31bits << clientIpv6Bit) | (clientIpv6Flag & clientIpv6BitMask)
|
seqIdAndClientPortHighBitAndClientIpv6Flag := (seqIdLow30bits << (clientPortNumberHigh1Bit + clientIpv6Bit)) | (clientPortHigh1bit << clientPortNumberHigh1Bit) | (clientIpv6Flag & clientIpv6BitMask)
|
||||||
|
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
_ = binary.Write(buf, binary.BigEndian, serverUniqId)
|
_ = binary.Write(buf, binary.BigEndian, serverUniqId)
|
||||||
_ = binary.Write(buf, binary.BigEndian, instanceUniqId)
|
_ = binary.Write(buf, binary.BigEndian, instanceUniqId)
|
||||||
_ = binary.Write(buf, binary.BigEndian, secondsAndRandomNumber)
|
_ = binary.Write(buf, binary.BigEndian, secondsAndClientPortLowBits)
|
||||||
_ = binary.Write(buf, binary.BigEndian, seqIdAndClientIpv6Flag)
|
_ = binary.Write(buf, binary.BigEndian, seqIdAndClientPortHighBitAndClientIpv6Flag)
|
||||||
_ = binary.Write(buf, binary.BigEndian, clientIp)
|
_ = binary.Write(buf, binary.BigEndian, clientIp)
|
||||||
|
|
||||||
return r.getUuidFromRequestId(buf)
|
return r.getUuidFromRequestId(buf)
|
||||||
@@ -198,23 +200,25 @@ func (r *DefaultRequestIdGenerator) parseRequestIdInfo(data []byte) *RequestIdIn
|
|||||||
|
|
||||||
var serverUniqId uint16
|
var serverUniqId uint16
|
||||||
var instanceUniqId uint16
|
var instanceUniqId uint16
|
||||||
var secondsAndRandomNumber uint32
|
var secondsAndClientPortLowBits uint32
|
||||||
var seqIdAndClientIpv6Flag uint32
|
var seqIdAndClientPortHighBitAndClientIpv6Flag uint32
|
||||||
var clientIp uint32
|
var clientIp uint32
|
||||||
|
|
||||||
_ = binary.Read(buf, binary.BigEndian, &serverUniqId)
|
_ = binary.Read(buf, binary.BigEndian, &serverUniqId)
|
||||||
_ = binary.Read(buf, binary.BigEndian, &instanceUniqId)
|
_ = binary.Read(buf, binary.BigEndian, &instanceUniqId)
|
||||||
_ = binary.Read(buf, binary.BigEndian, &secondsAndRandomNumber)
|
_ = binary.Read(buf, binary.BigEndian, &secondsAndClientPortLowBits)
|
||||||
_ = binary.Read(buf, binary.BigEndian, &seqIdAndClientIpv6Flag)
|
_ = binary.Read(buf, binary.BigEndian, &seqIdAndClientPortHighBitAndClientIpv6Flag)
|
||||||
_ = binary.Read(buf, binary.BigEndian, &clientIp)
|
_ = binary.Read(buf, binary.BigEndian, &clientIp)
|
||||||
|
|
||||||
secondsElapsedToday := (secondsAndRandomNumber >> randomNumberBits) & secondsTodayBitsMask
|
secondsElapsedToday := (secondsAndClientPortLowBits >> clientPortNumberLow15Bits) & secondsTodayBitsMask
|
||||||
randomNumber := (secondsAndRandomNumber & randomNumberBitsMask)
|
|
||||||
|
|
||||||
seqId := (seqIdAndClientIpv6Flag >> clientIpv6Bit) & reqSeqNumberBitsMask
|
seqId := (seqIdAndClientPortHighBitAndClientIpv6Flag >> (clientPortNumberHigh1Bit + clientIpv6Bit)) & reqSeqNumberBitsMask
|
||||||
isClientIpv6Flag := (seqIdAndClientIpv6Flag & clientIpv6BitMask)
|
clientPortHigh1Bit := ((seqIdAndClientPortHighBitAndClientIpv6Flag >> clientIpv6Bit) << clientPortNumberLow15Bits) & clientPortNumberHigh1BitMask
|
||||||
|
isClientIpv6Flag := seqIdAndClientPortHighBitAndClientIpv6Flag & clientIpv6BitMask
|
||||||
isClientIpv6 := false
|
isClientIpv6 := false
|
||||||
|
|
||||||
|
clientPort := uint16(clientPortHigh1Bit | (secondsAndClientPortLowBits & clientPortNumberLow15BitsMask))
|
||||||
|
|
||||||
if isClientIpv6Flag == 1 {
|
if isClientIpv6Flag == 1 {
|
||||||
isClientIpv6 = true
|
isClientIpv6 = true
|
||||||
}
|
}
|
||||||
@@ -224,9 +228,9 @@ func (r *DefaultRequestIdGenerator) parseRequestIdInfo(data []byte) *RequestIdIn
|
|||||||
InstanceUniqId: instanceUniqId,
|
InstanceUniqId: instanceUniqId,
|
||||||
SecondsElapsedToday: secondsElapsedToday,
|
SecondsElapsedToday: secondsElapsedToday,
|
||||||
RequestSeqId: seqId,
|
RequestSeqId: seqId,
|
||||||
RandomNumber: randomNumber,
|
|
||||||
IsClientIpv6: isClientIpv6,
|
IsClientIpv6: isClientIpv6,
|
||||||
ClientIp: clientIp,
|
ClientIp: clientIp,
|
||||||
|
ClientPort: clientPort,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
|
|
||||||
func TestNewDefaultRequestIdGenerator_Http(t *testing.T) {
|
func TestNewDefaultRequestIdGenerator_Http(t *testing.T) {
|
||||||
generator, _ := NewDefaultRequestIdGenerator(&settings.Config{HttpAddr: "123.234.123.234", HttpPort: 8080, SecretKey: "secretkey"})
|
generator, _ := NewDefaultRequestIdGenerator(&settings.Config{HttpAddr: "123.234.123.234", HttpPort: 8080, SecretKey: "secretkey"})
|
||||||
requestId := generator.GenerateRequestId("127.0.0.1")
|
requestId := generator.GenerateRequestId("127.0.0.1", 20000)
|
||||||
requestIdInfo := generator.parseRequestIdInfo(generator.parseRequestIdFromUuid(requestId))
|
requestIdInfo := generator.parseRequestIdInfo(generator.parseRequestIdFromUuid(requestId))
|
||||||
|
|
||||||
expectedServerUniqId := uint16(0x2476) // crc32("123.234.123.234" + "_" + "secretkey") & 0xFFFF
|
expectedServerUniqId := uint16(0x2476) // crc32("123.234.123.234" + "_" + "secretkey") & 0xFFFF
|
||||||
@@ -24,7 +24,7 @@ func TestNewDefaultRequestIdGenerator_Http(t *testing.T) {
|
|||||||
|
|
||||||
func TestNewDefaultRequestIdGenerator_UnixSocket(t *testing.T) {
|
func TestNewDefaultRequestIdGenerator_UnixSocket(t *testing.T) {
|
||||||
generator, _ := NewDefaultRequestIdGenerator(&settings.Config{HttpAddr: "1.2.3.4", UnixSocketPath: "/var/lib/ezbookkeeping/ezbookkeeping.sock", Protocol: "socket", SecretKey: "secretkey"})
|
generator, _ := NewDefaultRequestIdGenerator(&settings.Config{HttpAddr: "1.2.3.4", UnixSocketPath: "/var/lib/ezbookkeeping/ezbookkeeping.sock", Protocol: "socket", SecretKey: "secretkey"})
|
||||||
requestId := generator.GenerateRequestId("127.0.0.1")
|
requestId := generator.GenerateRequestId("127.0.0.1", 20000)
|
||||||
requestIdInfo := generator.parseRequestIdInfo(generator.parseRequestIdFromUuid(requestId))
|
requestIdInfo := generator.parseRequestIdInfo(generator.parseRequestIdFromUuid(requestId))
|
||||||
|
|
||||||
expectedServerUniqId := uint16(0x5bdb) // crc32("1.2.3.4" + "_" + "secretkey") & 0xFFFF
|
expectedServerUniqId := uint16(0x5bdb) // crc32("1.2.3.4" + "_" + "secretkey") & 0xFFFF
|
||||||
@@ -38,7 +38,7 @@ func TestNewDefaultRequestIdGenerator_UnixSocket(t *testing.T) {
|
|||||||
|
|
||||||
func TestNewDefaultRequestIdGenerator_ClientIpv4(t *testing.T) {
|
func TestNewDefaultRequestIdGenerator_ClientIpv4(t *testing.T) {
|
||||||
generator, _ := NewDefaultRequestIdGenerator(&settings.Config{HttpAddr: "1.2.3.4", UnixSocketPath: "/var/lib/ezbookkeeping/ezbookkeeping.sock", Protocol: "socket", SecretKey: "secretkey"})
|
generator, _ := NewDefaultRequestIdGenerator(&settings.Config{HttpAddr: "1.2.3.4", UnixSocketPath: "/var/lib/ezbookkeeping/ezbookkeeping.sock", Protocol: "socket", SecretKey: "secretkey"})
|
||||||
requestId := generator.GenerateRequestId("127.0.0.1")
|
requestId := generator.GenerateRequestId("127.0.0.1", 20000)
|
||||||
requestIdInfo := generator.parseRequestIdInfo(generator.parseRequestIdFromUuid(requestId))
|
requestIdInfo := generator.parseRequestIdInfo(generator.parseRequestIdFromUuid(requestId))
|
||||||
|
|
||||||
expectedClientIp := uint32(0x7f000001) // 127.0.0.1
|
expectedClientIp := uint32(0x7f000001) // 127.0.0.1
|
||||||
@@ -49,7 +49,7 @@ func TestNewDefaultRequestIdGenerator_ClientIpv4(t *testing.T) {
|
|||||||
actualClientIpv6 := requestIdInfo.IsClientIpv6
|
actualClientIpv6 := requestIdInfo.IsClientIpv6
|
||||||
assert.Equal(t, expectedClientIpv6, actualClientIpv6)
|
assert.Equal(t, expectedClientIpv6, actualClientIpv6)
|
||||||
|
|
||||||
requestId = generator.GenerateRequestId("192.168.1.100")
|
requestId = generator.GenerateRequestId("192.168.1.100", 20000)
|
||||||
requestIdInfo = generator.parseRequestIdInfo(generator.parseRequestIdFromUuid(requestId))
|
requestIdInfo = generator.parseRequestIdInfo(generator.parseRequestIdFromUuid(requestId))
|
||||||
|
|
||||||
expectedClientIp = uint32(0xc0a80164) // 192.168.1.100
|
expectedClientIp = uint32(0xc0a80164) // 192.168.1.100
|
||||||
@@ -63,7 +63,7 @@ func TestNewDefaultRequestIdGenerator_ClientIpv4(t *testing.T) {
|
|||||||
|
|
||||||
func TestNewDefaultRequestIdGenerator_ClientIpv6(t *testing.T) {
|
func TestNewDefaultRequestIdGenerator_ClientIpv6(t *testing.T) {
|
||||||
generator, _ := NewDefaultRequestIdGenerator(&settings.Config{HttpAddr: "1.2.3.4", UnixSocketPath: "/var/lib/ezbookkeeping/ezbookkeeping.sock", Protocol: "socket", SecretKey: "secretkey"})
|
generator, _ := NewDefaultRequestIdGenerator(&settings.Config{HttpAddr: "1.2.3.4", UnixSocketPath: "/var/lib/ezbookkeeping/ezbookkeeping.sock", Protocol: "socket", SecretKey: "secretkey"})
|
||||||
requestId := generator.GenerateRequestId("2001:abc:def:1234::1")
|
requestId := generator.GenerateRequestId("2001:abc:def:1234::1", 20000)
|
||||||
requestIdInfo := generator.parseRequestIdInfo(generator.parseRequestIdFromUuid(requestId))
|
requestIdInfo := generator.parseRequestIdInfo(generator.parseRequestIdFromUuid(requestId))
|
||||||
|
|
||||||
expectedClientIp := uint32(0x76fe1b98) // crc32("2001:abc:def:1234::1")
|
expectedClientIp := uint32(0x76fe1b98) // crc32("2001:abc:def:1234::1")
|
||||||
@@ -74,7 +74,7 @@ func TestNewDefaultRequestIdGenerator_ClientIpv6(t *testing.T) {
|
|||||||
actualClientIpv6 := requestIdInfo.IsClientIpv6
|
actualClientIpv6 := requestIdInfo.IsClientIpv6
|
||||||
assert.Equal(t, expectedClientIpv6, actualClientIpv6)
|
assert.Equal(t, expectedClientIpv6, actualClientIpv6)
|
||||||
|
|
||||||
requestId = generator.GenerateRequestId("2400:abcd:1234:1:56ef:ab78:c90d:1e2f")
|
requestId = generator.GenerateRequestId("2400:abcd:1234:1:56ef:ab78:c90d:1e2f", 20000)
|
||||||
requestIdInfo = generator.parseRequestIdInfo(generator.parseRequestIdFromUuid(requestId))
|
requestIdInfo = generator.parseRequestIdInfo(generator.parseRequestIdFromUuid(requestId))
|
||||||
|
|
||||||
expectedClientIp = uint32(0xa0a25faa) // crc32("2400:abcd:1234:1:56ef:ab78:c90d:1e2f")
|
expectedClientIp = uint32(0xa0a25faa) // crc32("2400:abcd:1234:1:56ef:ab78:c90d:1e2f")
|
||||||
@@ -86,11 +86,56 @@ func TestNewDefaultRequestIdGenerator_ClientIpv6(t *testing.T) {
|
|||||||
assert.Equal(t, expectedClientIpv6, actualClientIpv6)
|
assert.Equal(t, expectedClientIpv6, actualClientIpv6)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewDefaultRequestIdGenerator_ClientPort(t *testing.T) {
|
||||||
|
generator, _ := NewDefaultRequestIdGenerator(&settings.Config{HttpAddr: "1.2.3.4", UnixSocketPath: "/var/lib/ezbookkeeping/ezbookkeeping.sock", Protocol: "socket", SecretKey: "secretkey"})
|
||||||
|
requestId := generator.GenerateRequestId("127.0.0.1", 0)
|
||||||
|
requestIdInfo := generator.parseRequestIdInfo(generator.parseRequestIdFromUuid(requestId))
|
||||||
|
|
||||||
|
expectedClientPort := uint16(0)
|
||||||
|
actualClientPort := requestIdInfo.ClientPort
|
||||||
|
assert.Equal(t, expectedClientPort, actualClientPort)
|
||||||
|
|
||||||
|
requestId = generator.GenerateRequestId("127.0.0.1", 12345)
|
||||||
|
requestIdInfo = generator.parseRequestIdInfo(generator.parseRequestIdFromUuid(requestId))
|
||||||
|
|
||||||
|
expectedClientPort = uint16(12345)
|
||||||
|
actualClientPort = requestIdInfo.ClientPort
|
||||||
|
assert.Equal(t, expectedClientPort, actualClientPort)
|
||||||
|
|
||||||
|
requestId = generator.GenerateRequestId("127.0.0.1", 32767)
|
||||||
|
requestIdInfo = generator.parseRequestIdInfo(generator.parseRequestIdFromUuid(requestId))
|
||||||
|
|
||||||
|
expectedClientPort = uint16(32767)
|
||||||
|
actualClientPort = requestIdInfo.ClientPort
|
||||||
|
assert.Equal(t, expectedClientPort, actualClientPort)
|
||||||
|
|
||||||
|
requestId = generator.GenerateRequestId("127.0.0.1", 32768)
|
||||||
|
requestIdInfo = generator.parseRequestIdInfo(generator.parseRequestIdFromUuid(requestId))
|
||||||
|
|
||||||
|
expectedClientPort = uint16(32768)
|
||||||
|
actualClientPort = requestIdInfo.ClientPort
|
||||||
|
assert.Equal(t, expectedClientPort, actualClientPort)
|
||||||
|
|
||||||
|
requestId = generator.GenerateRequestId("127.0.0.1", 56789)
|
||||||
|
requestIdInfo = generator.parseRequestIdInfo(generator.parseRequestIdFromUuid(requestId))
|
||||||
|
|
||||||
|
expectedClientPort = uint16(56789)
|
||||||
|
actualClientPort = requestIdInfo.ClientPort
|
||||||
|
assert.Equal(t, expectedClientPort, actualClientPort)
|
||||||
|
|
||||||
|
requestId = generator.GenerateRequestId("127.0.0.1", 65535)
|
||||||
|
requestIdInfo = generator.parseRequestIdInfo(generator.parseRequestIdFromUuid(requestId))
|
||||||
|
|
||||||
|
expectedClientPort = uint16(65535)
|
||||||
|
actualClientPort = requestIdInfo.ClientPort
|
||||||
|
assert.Equal(t, expectedClientPort, actualClientPort)
|
||||||
|
}
|
||||||
|
|
||||||
func TestGenerateRequestId_100Times(t *testing.T) {
|
func TestGenerateRequestId_100Times(t *testing.T) {
|
||||||
generator, _ := NewDefaultRequestIdGenerator(&settings.Config{HttpAddr: "1.2.3.4", HttpPort: 1234, SecretKey: "secretkey"})
|
generator, _ := NewDefaultRequestIdGenerator(&settings.Config{HttpAddr: "1.2.3.4", HttpPort: 1234, SecretKey: "secretkey"})
|
||||||
|
|
||||||
for i := 1; i <= 100; i++ {
|
for i := 1; i <= 100; i++ {
|
||||||
requestId := generator.GenerateRequestId("127.0.0.1")
|
requestId := generator.GenerateRequestId("127.0.0.1", 20000)
|
||||||
requestIdInfo := generator.parseRequestIdInfo(generator.parseRequestIdFromUuid(requestId))
|
requestIdInfo := generator.parseRequestIdInfo(generator.parseRequestIdFromUuid(requestId))
|
||||||
|
|
||||||
expectedRequestSeqId := uint32(i)
|
expectedRequestSeqId := uint32(i)
|
||||||
|
|||||||
@@ -27,6 +27,6 @@ func InitializeRequestIdGenerator(config *settings.Config) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GenerateRequestId returns a new request id by the current request id generator
|
// GenerateRequestId returns a new request id by the current request id generator
|
||||||
func (u *RequestIdContainer) GenerateRequestId(clientIpAddr string) string {
|
func (u *RequestIdContainer) GenerateRequestId(clientIpAddr string, clientPort uint16) string {
|
||||||
return u.Current.GenerateRequestId(clientIpAddr)
|
return u.Current.GenerateRequestId(clientIpAddr, clientPort)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package requestid
|
|||||||
|
|
||||||
// RequestIdGenerator is common request generator interface
|
// RequestIdGenerator is common request generator interface
|
||||||
type RequestIdGenerator interface {
|
type RequestIdGenerator interface {
|
||||||
GenerateRequestId(clientIpAddr string) string
|
GenerateRequestId(clientIpAddr string, clientPort uint16) string
|
||||||
GetCurrentServerUniqId() uint16
|
GetCurrentServerUniqId() uint16
|
||||||
GetCurrentInstanceUniqId() uint16
|
GetCurrentInstanceUniqId() uint16
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package services
|
package services
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
@@ -54,7 +55,7 @@ func (s *AccountService) GetAllAccountsByUid(c *core.Context, uid int64) ([]*mod
|
|||||||
return accounts, err
|
return accounts, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAccountAndSubAccountsByAccountId returns account model and sub account models according to account id
|
// GetAccountAndSubAccountsByAccountId returns account model and sub-account models according to account id
|
||||||
func (s *AccountService) GetAccountAndSubAccountsByAccountId(c *core.Context, uid int64, accountId int64) ([]*models.Account, error) {
|
func (s *AccountService) GetAccountAndSubAccountsByAccountId(c *core.Context, uid int64, accountId int64) ([]*models.Account, error) {
|
||||||
if uid <= 0 {
|
if uid <= 0 {
|
||||||
return nil, errs.ErrUserIdInvalid
|
return nil, errs.ErrUserIdInvalid
|
||||||
@@ -70,7 +71,7 @@ func (s *AccountService) GetAccountAndSubAccountsByAccountId(c *core.Context, ui
|
|||||||
return accounts, err
|
return accounts, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSubAccountsByAccountId returns sub account models according to account id
|
// GetSubAccountsByAccountId returns sub-account models according to account id
|
||||||
func (s *AccountService) GetSubAccountsByAccountId(c *core.Context, uid int64, accountId int64) ([]*models.Account, error) {
|
func (s *AccountService) GetSubAccountsByAccountId(c *core.Context, uid int64, accountId int64) ([]*models.Account, error) {
|
||||||
if uid <= 0 {
|
if uid <= 0 {
|
||||||
return nil, errs.ErrUserIdInvalid
|
return nil, errs.ErrUserIdInvalid
|
||||||
@@ -86,6 +87,48 @@ func (s *AccountService) GetSubAccountsByAccountId(c *core.Context, uid int64, a
|
|||||||
return accounts, err
|
return accounts, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetSubAccountsByAccountIds returns sub-account models according to account ids
|
||||||
|
func (s *AccountService) GetSubAccountsByAccountIds(c *core.Context, uid int64, accountIds []int64) ([]*models.Account, error) {
|
||||||
|
if uid <= 0 {
|
||||||
|
return nil, errs.ErrUserIdInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(accountIds) <= 0 {
|
||||||
|
return nil, errs.ErrAccountIdInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
condition := "uid=? AND deleted=?"
|
||||||
|
conditionParams := make([]any, 0, len(accountIds)+2)
|
||||||
|
conditionParams = append(conditionParams, uid)
|
||||||
|
conditionParams = append(conditionParams, false)
|
||||||
|
|
||||||
|
var accountIdConditions strings.Builder
|
||||||
|
|
||||||
|
for i := 0; i < len(accountIds); i++ {
|
||||||
|
if accountIds[i] <= 0 {
|
||||||
|
return nil, errs.ErrAccountIdInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
if accountIdConditions.Len() > 0 {
|
||||||
|
accountIdConditions.WriteString(",")
|
||||||
|
}
|
||||||
|
|
||||||
|
accountIdConditions.WriteString("?")
|
||||||
|
conditionParams = append(conditionParams, accountIds[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
if accountIdConditions.Len() > 1 {
|
||||||
|
condition = condition + " AND parent_account_id IN (" + accountIdConditions.String() + ")"
|
||||||
|
} else {
|
||||||
|
condition = condition + " AND parent_account_id = " + accountIdConditions.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
var accounts []*models.Account
|
||||||
|
err := s.UserDataDB(uid).NewSession(c).Where(condition, conditionParams...).OrderBy("display_order asc").Find(&accounts)
|
||||||
|
|
||||||
|
return accounts, err
|
||||||
|
}
|
||||||
|
|
||||||
// GetAccountsByAccountIds returns account models according to account ids
|
// GetAccountsByAccountIds returns account models according to account ids
|
||||||
func (s *AccountService) GetAccountsByAccountIds(c *core.Context, uid int64, accountIds []int64) (map[int64]*models.Account, error) {
|
func (s *AccountService) GetAccountsByAccountIds(c *core.Context, uid int64, accountIds []int64) (map[int64]*models.Account, error) {
|
||||||
if uid <= 0 {
|
if uid <= 0 {
|
||||||
@@ -127,7 +170,7 @@ func (s *AccountService) GetMaxDisplayOrder(c *core.Context, uid int64, category
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMaxSubAccountDisplayOrder returns the max display order of sub account according to account category and parent account id
|
// GetMaxSubAccountDisplayOrder returns the max display order of sub-account according to account category and parent account id
|
||||||
func (s *AccountService) GetMaxSubAccountDisplayOrder(c *core.Context, uid int64, category models.AccountCategory, parentAccountId int64) (int32, error) {
|
func (s *AccountService) GetMaxSubAccountDisplayOrder(c *core.Context, uid int64, category models.AccountCategory, parentAccountId int64) (int32, error) {
|
||||||
if uid <= 0 {
|
if uid <= 0 {
|
||||||
return 0, errs.ErrUserIdInvalid
|
return 0, errs.ErrUserIdInvalid
|
||||||
|
|||||||
+33
-7
@@ -58,7 +58,7 @@ func (s *TokenService) GetAllUnexpiredNormalTokensByUid(c *core.Context, uid int
|
|||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
|
|
||||||
var tokenRecords []*models.TokenRecord
|
var tokenRecords []*models.TokenRecord
|
||||||
err := s.TokenDB(uid).NewSession(c).Cols("uid", "user_token_id", "token_type", "user_agent", "created_unix_time", "expired_unix_time").Where("uid=? AND token_type=? AND expired_unix_time>?", uid, core.USER_TOKEN_TYPE_NORMAL, now).Find(&tokenRecords)
|
err := s.TokenDB(uid).NewSession(c).Cols("uid", "user_token_id", "token_type", "user_agent", "created_unix_time", "expired_unix_time", "last_seen_unix_time").Where("uid=? AND token_type=? AND expired_unix_time>?", uid, core.USER_TOKEN_TYPE_NORMAL, now).Find(&tokenRecords)
|
||||||
|
|
||||||
return tokenRecords, err
|
return tokenRecords, err
|
||||||
}
|
}
|
||||||
@@ -98,6 +98,31 @@ func (s *TokenService) CreatePasswordResetToken(c *core.Context, user *models.Us
|
|||||||
return s.createToken(c, user, core.USER_TOKEN_TYPE_PASSWORD_RESET, s.getUserAgent(c), s.CurrentConfig().PasswordResetTokenExpiredTimeDuration)
|
return s.createToken(c, user, core.USER_TOKEN_TYPE_PASSWORD_RESET, s.getUserAgent(c), s.CurrentConfig().PasswordResetTokenExpiredTimeDuration)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateTokenLastSeen updates the last seen time of specified token
|
||||||
|
func (s *TokenService) UpdateTokenLastSeen(c *core.Context, tokenRecord *models.TokenRecord) error {
|
||||||
|
if tokenRecord.Uid <= 0 {
|
||||||
|
return errs.ErrUserIdInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
if tokenRecord.UserTokenId <= 0 {
|
||||||
|
return errs.ErrInvalidUserTokenId
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenRecord.LastSeenUnixTime = time.Now().Unix()
|
||||||
|
|
||||||
|
return s.TokenDB(tokenRecord.Uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||||
|
updatedRows, err := sess.Cols("last_seen_unix_time").Where("uid=? AND user_token_id=? AND created_unix_time=?", tokenRecord.Uid, tokenRecord.UserTokenId, tokenRecord.CreatedUnixTime).Update(tokenRecord)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if updatedRows < 1 {
|
||||||
|
return errs.ErrTokenRecordNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteToken deletes given token from database
|
// DeleteToken deletes given token from database
|
||||||
func (s *TokenService) DeleteToken(c *core.Context, tokenRecord *models.TokenRecord) error {
|
func (s *TokenService) DeleteToken(c *core.Context, tokenRecord *models.TokenRecord) error {
|
||||||
if tokenRecord.Uid <= 0 {
|
if tokenRecord.Uid <= 0 {
|
||||||
@@ -294,12 +319,13 @@ func (s *TokenService) createToken(c *core.Context, user *models.User, tokenType
|
|||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
||||||
tokenRecord := &models.TokenRecord{
|
tokenRecord := &models.TokenRecord{
|
||||||
Uid: user.Uid,
|
Uid: user.Uid,
|
||||||
UserTokenId: s.getUserTokenId(),
|
UserTokenId: s.getUserTokenId(),
|
||||||
TokenType: tokenType,
|
TokenType: tokenType,
|
||||||
UserAgent: userAgent,
|
UserAgent: userAgent,
|
||||||
CreatedUnixTime: now.Unix(),
|
CreatedUnixTime: now.Unix(),
|
||||||
ExpiredUnixTime: now.Add(expiryDate).Unix(),
|
ExpiredUnixTime: now.Add(expiryDate).Unix(),
|
||||||
|
LastSeenUnixTime: now.Unix(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if tokenRecord.Secret, err = utils.GetRandomString(10); err != nil {
|
if tokenRecord.Secret, err = utils.GetRandomString(10); err != nil {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package services
|
package services
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
@@ -68,6 +69,48 @@ func (s *TransactionCategoryService) GetAllCategoriesByUid(c *core.Context, uid
|
|||||||
return categories, err
|
return categories, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetSubCategoriesByCategoryIds returns sub-category models according to category ids
|
||||||
|
func (s *TransactionCategoryService) GetSubCategoriesByCategoryIds(c *core.Context, uid int64, categoryIds []int64) ([]*models.TransactionCategory, error) {
|
||||||
|
if uid <= 0 {
|
||||||
|
return nil, errs.ErrUserIdInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(categoryIds) <= 0 {
|
||||||
|
return nil, errs.ErrTransactionCategoryIdInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
condition := "uid=? AND deleted=?"
|
||||||
|
conditionParams := make([]any, 0, len(categoryIds)+2)
|
||||||
|
conditionParams = append(conditionParams, uid)
|
||||||
|
conditionParams = append(conditionParams, false)
|
||||||
|
|
||||||
|
var categoryIdConditions strings.Builder
|
||||||
|
|
||||||
|
for i := 0; i < len(categoryIds); i++ {
|
||||||
|
if categoryIds[i] <= 0 {
|
||||||
|
return nil, errs.ErrTransactionCategoryIdInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
if categoryIdConditions.Len() > 0 {
|
||||||
|
categoryIdConditions.WriteString(",")
|
||||||
|
}
|
||||||
|
|
||||||
|
categoryIdConditions.WriteString("?")
|
||||||
|
conditionParams = append(conditionParams, categoryIds[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
if categoryIdConditions.Len() > 1 {
|
||||||
|
condition = condition + " AND parent_category_id IN (" + categoryIdConditions.String() + ")"
|
||||||
|
} else {
|
||||||
|
condition = condition + " AND parent_category_id = " + categoryIdConditions.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
var categories []*models.TransactionCategory
|
||||||
|
err := s.UserDataDB(uid).NewSession(c).Where(condition, conditionParams...).OrderBy("display_order asc").Find(&categories)
|
||||||
|
|
||||||
|
return categories, err
|
||||||
|
}
|
||||||
|
|
||||||
// GetCategoryByCategoryId returns a transaction category model according to transaction category id
|
// GetCategoryByCategoryId returns a transaction category model according to transaction category id
|
||||||
func (s *TransactionCategoryService) GetCategoryByCategoryId(c *core.Context, uid int64, categoryId int64) (*models.TransactionCategory, error) {
|
func (s *TransactionCategoryService) GetCategoryByCategoryId(c *core.Context, uid int64, categoryId int64) (*models.TransactionCategory, error) {
|
||||||
if uid <= 0 {
|
if uid <= 0 {
|
||||||
@@ -249,7 +292,7 @@ func (s *TransactionCategoryService) ModifyCategory(c *core.Context, category *m
|
|||||||
category.UpdatedUnixTime = time.Now().Unix()
|
category.UpdatedUnixTime = time.Now().Unix()
|
||||||
|
|
||||||
return s.UserDataDB(category.Uid).DoTransaction(c, func(sess *xorm.Session) error {
|
return s.UserDataDB(category.Uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||||
updatedRows, err := sess.ID(category.CategoryId).Cols("name", "icon", "color", "comment", "hidden", "updated_unix_time").Where("uid=? AND deleted=?", category.Uid, false).Update(category)
|
updatedRows, err := sess.ID(category.CategoryId).Cols("parent_category_id", "name", "icon", "color", "comment", "hidden", "updated_unix_time").Where("uid=? AND deleted=?", category.Uid, false).Update(category)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import (
|
|||||||
"github.com/mayswind/ezbookkeeping/pkg/uuid"
|
"github.com/mayswind/ezbookkeeping/pkg/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const pageCountForLoadAllTransactionTagIndexes = 1000
|
||||||
|
|
||||||
// TransactionTagService represents transaction tag service
|
// TransactionTagService represents transaction tag service
|
||||||
type TransactionTagService struct {
|
type TransactionTagService struct {
|
||||||
ServiceUsingDB
|
ServiceUsingDB
|
||||||
@@ -117,15 +119,60 @@ func (s *TransactionTagService) GetMaxDisplayOrder(c *core.Context, uid int64) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetAllTagIdsOfAllTransactions returns all transaction tag ids
|
// GetAllTagIdsOfAllTransactions returns all transaction tag ids
|
||||||
func (s *TransactionTagService) GetAllTagIdsOfAllTransactions(c *core.Context, uid int64) (map[int64][]int64, error) {
|
func (s *TransactionTagService) GetAllTagIdsOfAllTransactions(c *core.Context, uid int64) ([]*models.TransactionTagIndex, error) {
|
||||||
if uid <= 0 {
|
if uid <= 0 {
|
||||||
return nil, errs.ErrUserIdInvalid
|
return nil, errs.ErrUserIdInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
var tagIndexs []*models.TransactionTagIndex
|
condition := "uid=? AND deleted=?"
|
||||||
err := s.UserDataDB(uid).NewSession(c).Where("uid=? AND deleted=?", uid, false).Find(&tagIndexs)
|
conditionParams := make([]any, 0, 2)
|
||||||
|
conditionParams = append(conditionParams, uid)
|
||||||
|
conditionParams = append(conditionParams, false)
|
||||||
|
|
||||||
allTransactionTagIds := s.getGroupedTransactionTagIds(tagIndexs)
|
var allTransactionTagIndexes []*models.TransactionTagIndex
|
||||||
|
|
||||||
|
maxTransactionTagIndexId := int64(0)
|
||||||
|
|
||||||
|
for maxTransactionTagIndexId >= 0 {
|
||||||
|
var tagIndexes []*models.TransactionTagIndex
|
||||||
|
|
||||||
|
finalCondition := condition
|
||||||
|
finalConditionParams := make([]any, 0, 3)
|
||||||
|
finalConditionParams = append(finalConditionParams, conditionParams...)
|
||||||
|
|
||||||
|
if maxTransactionTagIndexId > 0 {
|
||||||
|
finalCondition = finalCondition + " AND tag_index_id<=?"
|
||||||
|
finalConditionParams = append(finalConditionParams, maxTransactionTagIndexId)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := s.UserDataDB(uid).NewSession(c).Where(finalCondition, finalConditionParams...).Limit(pageCountForLoadAllTransactionTagIndexes, 0).OrderBy("tag_index_id desc").Find(&tagIndexes)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
allTransactionTagIndexes = append(allTransactionTagIndexes, tagIndexes...)
|
||||||
|
|
||||||
|
if len(tagIndexes) < pageCountForLoadAllTransactionTagIndexes {
|
||||||
|
maxTransactionTagIndexId = -1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
maxTransactionTagIndexId = tagIndexes[len(tagIndexes)-1].TagIndexId - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return allTransactionTagIndexes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllTagIdsMapOfAllTransactions returns all transaction tag ids map grouped by transaction id
|
||||||
|
func (s *TransactionTagService) GetAllTagIdsMapOfAllTransactions(c *core.Context, uid int64) (map[int64][]int64, error) {
|
||||||
|
tagIndexes, err := s.GetAllTagIdsOfAllTransactions(c, uid)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
allTransactionTagIds := s.GetGroupedTransactionTagIds(tagIndexes)
|
||||||
|
|
||||||
return allTransactionTagIds, err
|
return allTransactionTagIds, err
|
||||||
}
|
}
|
||||||
@@ -136,10 +183,10 @@ func (s *TransactionTagService) GetAllTagIdsOfTransactions(c *core.Context, uid
|
|||||||
return nil, errs.ErrUserIdInvalid
|
return nil, errs.ErrUserIdInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
var tagIndexs []*models.TransactionTagIndex
|
var tagIndexes []*models.TransactionTagIndex
|
||||||
err := s.UserDataDB(uid).NewSession(c).Where("uid=? AND deleted=?", uid, false).In("transaction_id", transactionIds).Find(&tagIndexs)
|
err := s.UserDataDB(uid).NewSession(c).Where("uid=? AND deleted=?", uid, false).In("transaction_id", transactionIds).OrderBy("transaction_id asc, tag_index_id asc").Find(&tagIndexes)
|
||||||
|
|
||||||
allTransactionTagIds := s.getGroupedTransactionTagIds(tagIndexs)
|
allTransactionTagIds := s.GetGroupedTransactionTagIds(tagIndexes)
|
||||||
|
|
||||||
return allTransactionTagIds, err
|
return allTransactionTagIds, err
|
||||||
}
|
}
|
||||||
@@ -330,6 +377,32 @@ func (s *TransactionTagService) ExistsTagName(c *core.Context, uid int64, name s
|
|||||||
return s.UserDataDB(uid).NewSession(c).Cols("name").Where("uid=? AND deleted=? AND name=?", uid, false, name).Exist(&models.TransactionTag{})
|
return s.UserDataDB(uid).NewSession(c).Cols("name").Where("uid=? AND deleted=? AND name=?", uid, false, name).Exist(&models.TransactionTag{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ModifyTagIndexTransactionTime updates transaction time of given transaction tag indexes
|
||||||
|
func (s *TransactionTagService) ModifyTagIndexTransactionTime(c *core.Context, uid int64, tagIndexes []*models.TransactionTagIndex) error {
|
||||||
|
if uid <= 0 {
|
||||||
|
return errs.ErrUserIdInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(tagIndexes); i++ {
|
||||||
|
tagIndexes[i].UpdatedUnixTime = time.Now().Unix()
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.UserDataDB(uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||||
|
for i := 0; i < len(tagIndexes); i++ {
|
||||||
|
tagIndex := tagIndexes[i]
|
||||||
|
updatedRows, err := sess.ID(tagIndex.TagIndexId).Cols("transaction_time", "updated_unix_time").Where("uid=? AND deleted=?", uid, false).Update(tagIndex)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if updatedRows < 1 {
|
||||||
|
return errs.ErrTransactionTagIndexNotFound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// GetTagMapByList returns a transaction tag map by a list
|
// GetTagMapByList returns a transaction tag map by a list
|
||||||
func (s *TransactionTagService) GetTagMapByList(tags []*models.TransactionTag) map[int64]*models.TransactionTag {
|
func (s *TransactionTagService) GetTagMapByList(tags []*models.TransactionTag) map[int64]*models.TransactionTag {
|
||||||
tagMap := make(map[int64]*models.TransactionTag)
|
tagMap := make(map[int64]*models.TransactionTag)
|
||||||
@@ -341,11 +414,11 @@ func (s *TransactionTagService) GetTagMapByList(tags []*models.TransactionTag) m
|
|||||||
return tagMap
|
return tagMap
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TransactionTagService) getGroupedTransactionTagIds(tagIndexs []*models.TransactionTagIndex) map[int64][]int64 {
|
func (s *TransactionTagService) GetGroupedTransactionTagIds(tagIndexes []*models.TransactionTagIndex) map[int64][]int64 {
|
||||||
allTransactionTagIds := make(map[int64][]int64)
|
allTransactionTagIds := make(map[int64][]int64)
|
||||||
|
|
||||||
for i := 0; i < len(tagIndexs); i++ {
|
for i := 0; i < len(tagIndexes); i++ {
|
||||||
tagIndex := tagIndexs[i]
|
tagIndex := tagIndexes[i]
|
||||||
|
|
||||||
var transactionTagIds []int64
|
var transactionTagIds []int64
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,240 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
"xorm.io/xorm"
|
||||||
|
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/datastore"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TransactionTemplateService represents transaction template service
|
||||||
|
type TransactionTemplateService struct {
|
||||||
|
ServiceUsingDB
|
||||||
|
ServiceUsingUuid
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize a transaction template service singleton instance
|
||||||
|
var (
|
||||||
|
TransactionTemplates = &TransactionTemplateService{
|
||||||
|
ServiceUsingDB: ServiceUsingDB{
|
||||||
|
container: datastore.Container,
|
||||||
|
},
|
||||||
|
ServiceUsingUuid: ServiceUsingUuid{
|
||||||
|
container: uuid.Container,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetTotalNormalTemplateCountByUid returns total template count of user
|
||||||
|
func (s *TransactionTemplateService) GetTotalNormalTemplateCountByUid(c *core.Context, uid int64) (int64, error) {
|
||||||
|
if uid <= 0 {
|
||||||
|
return 0, errs.ErrUserIdInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := s.UserDataDB(uid).NewSession(c).Where("uid=? AND deleted=? AND template_type=?", uid, false, models.TRANSACTION_TEMPLATE_TYPE_NORMAL).Count(&models.TransactionTemplate{})
|
||||||
|
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllTemplatesByUid returns all transaction template models of user
|
||||||
|
func (s *TransactionTemplateService) GetAllTemplatesByUid(c *core.Context, uid int64, templateType models.TransactionTemplateType) ([]*models.TransactionTemplate, error) {
|
||||||
|
if uid <= 0 {
|
||||||
|
return nil, errs.ErrUserIdInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
var templates []*models.TransactionTemplate
|
||||||
|
err := s.UserDataDB(uid).NewSession(c).Where("uid=? AND deleted=? AND template_type=?", uid, false, templateType).Find(&templates)
|
||||||
|
|
||||||
|
return templates, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTemplateByTemplateId returns a transaction template model according to transaction template id
|
||||||
|
func (s *TransactionTemplateService) GetTemplateByTemplateId(c *core.Context, uid int64, templateId int64) (*models.TransactionTemplate, error) {
|
||||||
|
if uid <= 0 {
|
||||||
|
return nil, errs.ErrUserIdInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
if templateId <= 0 {
|
||||||
|
return nil, errs.ErrTransactionTemplateIdInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
template := &models.TransactionTemplate{}
|
||||||
|
has, err := s.UserDataDB(uid).NewSession(c).ID(templateId).Where("uid=? AND deleted=?", uid, false).Get(template)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if !has {
|
||||||
|
return nil, errs.ErrTransactionTemplateNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return template, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMaxDisplayOrder returns the max display order
|
||||||
|
func (s *TransactionTemplateService) GetMaxDisplayOrder(c *core.Context, uid int64, templateType models.TransactionTemplateType) (int32, error) {
|
||||||
|
if uid <= 0 {
|
||||||
|
return 0, errs.ErrUserIdInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
template := &models.TransactionTemplate{}
|
||||||
|
has, err := s.UserDataDB(uid).NewSession(c).Cols("uid", "deleted", "display_order").Where("uid=? AND deleted=? AND template_type=?", uid, false, templateType).OrderBy("display_order desc").Limit(1).Get(template)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if has {
|
||||||
|
return template.DisplayOrder, nil
|
||||||
|
} else {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTemplate saves a new transaction template model to database
|
||||||
|
func (s *TransactionTemplateService) CreateTemplate(c *core.Context, template *models.TransactionTemplate) error {
|
||||||
|
if template.Uid <= 0 {
|
||||||
|
return errs.ErrUserIdInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
template.TemplateId = s.GenerateUuid(uuid.UUID_TYPE_TEMPLATE)
|
||||||
|
|
||||||
|
if template.TemplateId < 1 {
|
||||||
|
return errs.ErrSystemIsBusy
|
||||||
|
}
|
||||||
|
|
||||||
|
template.Deleted = false
|
||||||
|
template.CreatedUnixTime = time.Now().Unix()
|
||||||
|
template.UpdatedUnixTime = time.Now().Unix()
|
||||||
|
|
||||||
|
return s.UserDataDB(template.Uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||||
|
_, err := sess.Insert(template)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModifyTemplate saves an existed transaction template model to database
|
||||||
|
func (s *TransactionTemplateService) ModifyTemplate(c *core.Context, template *models.TransactionTemplate) error {
|
||||||
|
if template.Uid <= 0 {
|
||||||
|
return errs.ErrUserIdInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
template.UpdatedUnixTime = time.Now().Unix()
|
||||||
|
|
||||||
|
return s.UserDataDB(template.Uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||||
|
updatedRows, err := sess.ID(template.TemplateId).Cols("name", "type", "category_id", "account_id", "tag_ids", "amount", "related_account_id", "related_account_amount", "hide_amount", "comment", "updated_unix_time").Where("uid=? AND deleted=?", template.Uid, false).Update(template)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if updatedRows < 1 {
|
||||||
|
return errs.ErrTransactionTemplateNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// HideTemplate updates hidden field of given transaction templates
|
||||||
|
func (s *TransactionTemplateService) HideTemplate(c *core.Context, uid int64, ids []int64, hidden bool) error {
|
||||||
|
if uid <= 0 {
|
||||||
|
return errs.ErrUserIdInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
|
||||||
|
updateModel := &models.TransactionTemplate{
|
||||||
|
Hidden: hidden,
|
||||||
|
UpdatedUnixTime: now,
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.UserDataDB(uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||||
|
updatedRows, err := sess.Cols("hidden", "updated_unix_time").Where("uid=? AND deleted=?", uid, false).In("template_id", ids).Update(updateModel)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if updatedRows < 1 {
|
||||||
|
return errs.ErrTransactionTemplateNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModifyTemplateDisplayOrders updates display order of given transaction templates
|
||||||
|
func (s *TransactionTemplateService) ModifyTemplateDisplayOrders(c *core.Context, uid int64, templates []*models.TransactionTemplate) error {
|
||||||
|
if uid <= 0 {
|
||||||
|
return errs.ErrUserIdInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(templates); i++ {
|
||||||
|
templates[i].UpdatedUnixTime = time.Now().Unix()
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.UserDataDB(uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||||
|
for i := 0; i < len(templates); i++ {
|
||||||
|
template := templates[i]
|
||||||
|
updatedRows, err := sess.ID(template.TemplateId).Cols("display_order", "updated_unix_time").Where("uid=? AND deleted=?", uid, false).Update(template)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if updatedRows < 1 {
|
||||||
|
return errs.ErrTransactionTemplateNotFound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteTemplate deletes an existed transaction template from database
|
||||||
|
func (s *TransactionTemplateService) DeleteTemplate(c *core.Context, uid int64, templateId int64) error {
|
||||||
|
if uid <= 0 {
|
||||||
|
return errs.ErrUserIdInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
|
||||||
|
updateModel := &models.TransactionTemplate{
|
||||||
|
Deleted: true,
|
||||||
|
DeletedUnixTime: now,
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.UserDataDB(uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||||
|
deletedRows, err := sess.ID(templateId).Cols("deleted", "deleted_unix_time").Where("uid=? AND deleted=?", uid, false).Update(updateModel)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if deletedRows < 1 {
|
||||||
|
return errs.ErrTransactionTemplateNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAllTemplates deletes all existed transaction templates from database
|
||||||
|
func (s *TransactionTemplateService) DeleteAllTemplates(c *core.Context, uid int64) error {
|
||||||
|
if uid <= 0 {
|
||||||
|
return errs.ErrUserIdInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
|
||||||
|
updateModel := &models.TransactionTemplate{
|
||||||
|
Deleted: true,
|
||||||
|
DeletedUnixTime: now,
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.UserDataDB(uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||||
|
_, err := sess.Cols("deleted", "deleted_unix_time").Where("uid=? AND deleted=?", uid, false).Update(updateModel)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
+535
-139
@@ -5,6 +5,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"xorm.io/builder"
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
@@ -15,6 +16,8 @@ import (
|
|||||||
"github.com/mayswind/ezbookkeeping/pkg/uuid"
|
"github.com/mayswind/ezbookkeeping/pkg/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const pageCountForLoadTransactionAmounts = 1000
|
||||||
|
|
||||||
// TransactionService represents transaction service
|
// TransactionService represents transaction service
|
||||||
type TransactionService struct {
|
type TransactionService struct {
|
||||||
ServiceUsingDB
|
ServiceUsingDB
|
||||||
@@ -71,11 +74,11 @@ func (s *TransactionService) GetAllTransactions(c *core.Context, uid int64, page
|
|||||||
|
|
||||||
// GetAllTransactionsByMaxTime returns all transactions before given time
|
// GetAllTransactionsByMaxTime returns all transactions before given time
|
||||||
func (s *TransactionService) GetAllTransactionsByMaxTime(c *core.Context, uid int64, maxTransactionTime int64, count int32, noDuplicated bool) ([]*models.Transaction, error) {
|
func (s *TransactionService) GetAllTransactionsByMaxTime(c *core.Context, uid int64, maxTransactionTime int64, count int32, noDuplicated bool) ([]*models.Transaction, error) {
|
||||||
return s.GetTransactionsByMaxTime(c, uid, maxTransactionTime, 0, 0, nil, nil, "", 1, count, false, noDuplicated)
|
return s.GetTransactionsByMaxTime(c, uid, maxTransactionTime, 0, 0, nil, nil, nil, false, "", "", 1, count, false, noDuplicated)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTransactionsByMaxTime returns transactions before given time
|
// GetTransactionsByMaxTime returns transactions before given time
|
||||||
func (s *TransactionService) GetTransactionsByMaxTime(c *core.Context, uid int64, maxTransactionTime int64, minTransactionTime int64, transactionType models.TransactionDbType, categoryIds []int64, accountIds []int64, keyword string, page int32, count int32, needOneMoreItem bool, noDuplicated bool) ([]*models.Transaction, error) {
|
func (s *TransactionService) GetTransactionsByMaxTime(c *core.Context, uid int64, maxTransactionTime int64, minTransactionTime int64, transactionType models.TransactionDbType, categoryIds []int64, accountIds []int64, tagIds []int64, noTags bool, amountFilter string, keyword string, page int32, count int32, needOneMoreItem bool, noDuplicated bool) ([]*models.Transaction, error) {
|
||||||
if uid <= 0 {
|
if uid <= 0 {
|
||||||
return nil, errs.ErrUserIdInvalid
|
return nil, errs.ErrUserIdInvalid
|
||||||
}
|
}
|
||||||
@@ -99,34 +102,44 @@ func (s *TransactionService) GetTransactionsByMaxTime(c *core.Context, uid int64
|
|||||||
actualCount++
|
actualCount++
|
||||||
}
|
}
|
||||||
|
|
||||||
condition, conditionParams := s.getTransactionQueryCondition(uid, maxTransactionTime, minTransactionTime, transactionType, categoryIds, accountIds, keyword, noDuplicated)
|
condition, conditionParams := s.getTransactionQueryCondition(uid, maxTransactionTime, minTransactionTime, transactionType, categoryIds, accountIds, tagIds, amountFilter, keyword, noDuplicated)
|
||||||
err = s.UserDataDB(uid).NewSession(c).Where(condition, conditionParams...).Limit(int(actualCount), int(count*(page-1))).OrderBy("transaction_time desc").Find(&transactions)
|
sess := s.UserDataDB(uid).NewSession(c).Where(condition, conditionParams...)
|
||||||
|
|
||||||
|
if len(tagIds) > 0 {
|
||||||
|
sess.In("transaction_id", s.getTransactionQueryByTagIdsCondition(uid, maxTransactionTime, minTransactionTime, tagIds))
|
||||||
|
} else if noTags {
|
||||||
|
sess.NotIn("transaction_id", s.getTransactionQueryByAllTagIdsCondition(uid, maxTransactionTime, minTransactionTime))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = sess.Limit(int(actualCount), int(count*(page-1))).OrderBy("transaction_time desc").Find(&transactions)
|
||||||
|
|
||||||
return transactions, err
|
return transactions, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTransactionsInMonthByPage returns all transactions in given year and month
|
// GetTransactionsInMonthByPage returns all transactions in given year and month
|
||||||
func (s *TransactionService) GetTransactionsInMonthByPage(c *core.Context, uid int64, year int32, month int32, transactionType models.TransactionDbType, categoryIds []int64, accountIds []int64, keyword string) ([]*models.Transaction, error) {
|
func (s *TransactionService) GetTransactionsInMonthByPage(c *core.Context, uid int64, year int32, month int32, transactionType models.TransactionDbType, categoryIds []int64, accountIds []int64, tagIds []int64, noTags bool, amountFilter string, keyword string) ([]*models.Transaction, error) {
|
||||||
if uid <= 0 {
|
if uid <= 0 {
|
||||||
return nil, errs.ErrUserIdInvalid
|
return nil, errs.ErrUserIdInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
startMinUnixTime, err := utils.ParseFromLongDateTimeToMinUnixTime(fmt.Sprintf("%d-%02d-01 00:00:00", year, month))
|
minTransactionTime, maxTransactionTime, err := utils.GetTransactionTimeRangeByYearMonth(year, month)
|
||||||
startMaxUnixTime, err := utils.ParseFromLongDateTimeToMaxUnixTime(fmt.Sprintf("%d-%02d-01 00:00:00", year, month))
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errs.ErrSystemError
|
return nil, errs.ErrSystemError
|
||||||
}
|
}
|
||||||
|
|
||||||
endMaxUnixTime := startMaxUnixTime.AddDate(0, 1, 0)
|
|
||||||
|
|
||||||
minTransactionTime := utils.GetMinTransactionTimeFromUnixTime(startMinUnixTime.Unix())
|
|
||||||
maxTransactionTime := utils.GetMinTransactionTimeFromUnixTime(endMaxUnixTime.Unix()) - 1
|
|
||||||
|
|
||||||
var transactions []*models.Transaction
|
var transactions []*models.Transaction
|
||||||
|
|
||||||
condition, conditionParams := s.getTransactionQueryCondition(uid, maxTransactionTime, minTransactionTime, transactionType, categoryIds, accountIds, keyword, true)
|
condition, conditionParams := s.getTransactionQueryCondition(uid, maxTransactionTime, minTransactionTime, transactionType, categoryIds, accountIds, tagIds, amountFilter, keyword, true)
|
||||||
err = s.UserDataDB(uid).NewSession(c).Where(condition, conditionParams...).OrderBy("transaction_time desc").Find(&transactions)
|
sess := s.UserDataDB(uid).NewSession(c).Where(condition, conditionParams...)
|
||||||
|
|
||||||
|
if len(tagIds) > 0 {
|
||||||
|
sess.In("transaction_id", s.getTransactionQueryByTagIdsCondition(uid, maxTransactionTime, minTransactionTime, tagIds))
|
||||||
|
} else if noTags {
|
||||||
|
sess.NotIn("transaction_id", s.getTransactionQueryByAllTagIdsCondition(uid, maxTransactionTime, minTransactionTime))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = sess.OrderBy("transaction_time desc").Find(&transactions)
|
||||||
|
|
||||||
transactionsInMonth := make([]*models.Transaction, 0, len(transactions))
|
transactionsInMonth := make([]*models.Transaction, 0, len(transactions))
|
||||||
|
|
||||||
@@ -167,37 +180,25 @@ func (s *TransactionService) GetTransactionByTransactionId(c *core.Context, uid
|
|||||||
|
|
||||||
// GetAllTransactionCount returns total count of transactions
|
// GetAllTransactionCount returns total count of transactions
|
||||||
func (s *TransactionService) GetAllTransactionCount(c *core.Context, uid int64) (int64, error) {
|
func (s *TransactionService) GetAllTransactionCount(c *core.Context, uid int64) (int64, error) {
|
||||||
return s.GetTransactionCount(c, uid, 0, 0, 0, nil, nil, "")
|
return s.GetTransactionCount(c, uid, 0, 0, 0, nil, nil, nil, false, "", "")
|
||||||
}
|
|
||||||
|
|
||||||
// GetMonthTransactionCount returns total count of transactions in given year and month
|
|
||||||
func (s *TransactionService) GetMonthTransactionCount(c *core.Context, 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
|
|
||||||
}
|
|
||||||
|
|
||||||
startTime, err := utils.ParseFromLongDateTime(fmt.Sprintf("%d-%02d-01 00:00:00", year, month), utcOffset)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return 0, errs.ErrSystemError
|
|
||||||
}
|
|
||||||
|
|
||||||
endTime := startTime.AddDate(0, 1, 0)
|
|
||||||
|
|
||||||
minTransactionTime := utils.GetMinTransactionTimeFromUnixTime(startTime.Unix())
|
|
||||||
maxTransactionTime := utils.GetMinTransactionTimeFromUnixTime(endTime.Unix()) - 1
|
|
||||||
|
|
||||||
return s.GetTransactionCount(c, uid, maxTransactionTime, minTransactionTime, transactionType, categoryIds, accountIds, keyword)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTransactionCount returns count of transactions
|
// GetTransactionCount returns count of transactions
|
||||||
func (s *TransactionService) GetTransactionCount(c *core.Context, uid int64, maxTransactionTime int64, minTransactionTime int64, transactionType models.TransactionDbType, categoryIds []int64, accountIds []int64, keyword string) (int64, error) {
|
func (s *TransactionService) GetTransactionCount(c *core.Context, uid int64, maxTransactionTime int64, minTransactionTime int64, transactionType models.TransactionDbType, categoryIds []int64, accountIds []int64, tagIds []int64, noTags bool, amountFilter string, keyword string) (int64, error) {
|
||||||
if uid <= 0 {
|
if uid <= 0 {
|
||||||
return 0, errs.ErrUserIdInvalid
|
return 0, errs.ErrUserIdInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
condition, conditionParams := s.getTransactionQueryCondition(uid, maxTransactionTime, minTransactionTime, transactionType, categoryIds, accountIds, keyword, true)
|
condition, conditionParams := s.getTransactionQueryCondition(uid, maxTransactionTime, minTransactionTime, transactionType, categoryIds, accountIds, tagIds, amountFilter, keyword, true)
|
||||||
return s.UserDataDB(uid).NewSession(c).Where(condition, conditionParams...).Count(&models.Transaction{})
|
sess := s.UserDataDB(uid).NewSession(c).Where(condition, conditionParams...)
|
||||||
|
|
||||||
|
if len(tagIds) > 0 {
|
||||||
|
sess.In("transaction_id", s.getTransactionQueryByTagIdsCondition(uid, maxTransactionTime, minTransactionTime, tagIds))
|
||||||
|
} else if noTags {
|
||||||
|
sess.NotIn("transaction_id", s.getTransactionQueryByAllTagIdsCondition(uid, maxTransactionTime, minTransactionTime))
|
||||||
|
}
|
||||||
|
|
||||||
|
return sess.Count(&models.Transaction{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateTransaction saves a new transaction to database
|
// CreateTransaction saves a new transaction to database
|
||||||
@@ -239,7 +240,7 @@ func (s *TransactionService) CreateTransaction(c *core.Context, transaction *mod
|
|||||||
transaction.UpdatedUnixTime = now
|
transaction.UpdatedUnixTime = now
|
||||||
|
|
||||||
tagIds = utils.ToUniqueInt64Slice(tagIds)
|
tagIds = utils.ToUniqueInt64Slice(tagIds)
|
||||||
transactionTagIndexs := make([]*models.TransactionTagIndex, len(tagIds))
|
transactionTagIndexes := make([]*models.TransactionTagIndex, len(tagIds))
|
||||||
|
|
||||||
for i := 0; i < len(tagIds); i++ {
|
for i := 0; i < len(tagIds); i++ {
|
||||||
tagIndexId := s.GenerateUuid(uuid.UUID_TYPE_TAG_INDEX)
|
tagIndexId := s.GenerateUuid(uuid.UUID_TYPE_TAG_INDEX)
|
||||||
@@ -248,7 +249,7 @@ func (s *TransactionService) CreateTransaction(c *core.Context, transaction *mod
|
|||||||
return errs.ErrSystemIsBusy
|
return errs.ErrSystemIsBusy
|
||||||
}
|
}
|
||||||
|
|
||||||
transactionTagIndexs[i] = &models.TransactionTagIndex{
|
transactionTagIndexes[i] = &models.TransactionTagIndex{
|
||||||
TagIndexId: tagIndexId,
|
TagIndexId: tagIndexId,
|
||||||
Uid: transaction.Uid,
|
Uid: transaction.Uid,
|
||||||
Deleted: false,
|
Deleted: false,
|
||||||
@@ -271,6 +272,10 @@ func (s *TransactionService) CreateTransaction(c *core.Context, transaction *mod
|
|||||||
return errs.ErrCannotAddTransactionToHiddenAccount
|
return errs.ErrCannotAddTransactionToHiddenAccount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if sourceAccount.Type == models.ACCOUNT_TYPE_MULTI_SUB_ACCOUNTS || (destinationAccount != nil && destinationAccount.Type == models.ACCOUNT_TYPE_MULTI_SUB_ACCOUNTS) {
|
||||||
|
return errs.ErrCannotAddTransactionToParentAccount
|
||||||
|
}
|
||||||
|
|
||||||
if (transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT || transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN) &&
|
if (transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT || transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN) &&
|
||||||
sourceAccount.Currency == destinationAccount.Currency && transaction.Amount != transaction.RelatedAccountAmount {
|
sourceAccount.Currency == destinationAccount.Currency && transaction.Amount != transaction.RelatedAccountAmount {
|
||||||
return errs.ErrTransactionSourceAndDestinationAmountNotEqual
|
return errs.ErrTransactionSourceAndDestinationAmountNotEqual
|
||||||
@@ -284,7 +289,7 @@ func (s *TransactionService) CreateTransaction(c *core.Context, transaction *mod
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get and verify tags
|
// Get and verify tags
|
||||||
err = s.isTagsValid(sess, transaction, transactionTagIndexs, tagIds)
|
err = s.isTagsValid(sess, transaction, transactionTagIndexes, tagIds)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -357,9 +362,11 @@ func (s *TransactionService) CreateTransaction(c *core.Context, transaction *mod
|
|||||||
err = nil
|
err = nil
|
||||||
|
|
||||||
// Insert transaction tag index
|
// Insert transaction tag index
|
||||||
if len(transactionTagIndexs) > 0 {
|
if len(transactionTagIndexes) > 0 {
|
||||||
for i := 0; i < len(transactionTagIndexs); i++ {
|
for i := 0; i < len(transactionTagIndexes); i++ {
|
||||||
transactionTagIndex := transactionTagIndexs[i]
|
transactionTagIndex := transactionTagIndexes[i]
|
||||||
|
transactionTagIndex.TransactionTime = transaction.TransactionTime
|
||||||
|
|
||||||
_, err := sess.Insert(transactionTagIndex)
|
_, err := sess.Insert(transactionTagIndex)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -423,7 +430,7 @@ func (s *TransactionService) CreateTransaction(c *core.Context, transaction *mod
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ModifyTransaction saves an existed transaction to database
|
// ModifyTransaction saves an existed transaction to database
|
||||||
func (s *TransactionService) ModifyTransaction(c *core.Context, transaction *models.Transaction, addTagIds []int64, removeTagIds []int64) error {
|
func (s *TransactionService) ModifyTransaction(c *core.Context, transaction *models.Transaction, currentTagIdsCount int, addTagIds []int64, removeTagIds []int64) error {
|
||||||
if transaction.Uid <= 0 {
|
if transaction.Uid <= 0 {
|
||||||
return errs.ErrUserIdInvalid
|
return errs.ErrUserIdInvalid
|
||||||
}
|
}
|
||||||
@@ -439,7 +446,7 @@ func (s *TransactionService) ModifyTransaction(c *core.Context, transaction *mod
|
|||||||
addTagIds = utils.ToUniqueInt64Slice(addTagIds)
|
addTagIds = utils.ToUniqueInt64Slice(addTagIds)
|
||||||
removeTagIds = utils.ToUniqueInt64Slice(removeTagIds)
|
removeTagIds = utils.ToUniqueInt64Slice(removeTagIds)
|
||||||
|
|
||||||
transactionTagIndexs := make([]*models.TransactionTagIndex, len(addTagIds))
|
transactionTagIndexes := make([]*models.TransactionTagIndex, len(addTagIds))
|
||||||
|
|
||||||
for i := 0; i < len(addTagIds); i++ {
|
for i := 0; i < len(addTagIds); i++ {
|
||||||
tagIndexId := s.GenerateUuid(uuid.UUID_TYPE_TAG_INDEX)
|
tagIndexId := s.GenerateUuid(uuid.UUID_TYPE_TAG_INDEX)
|
||||||
@@ -448,7 +455,7 @@ func (s *TransactionService) ModifyTransaction(c *core.Context, transaction *mod
|
|||||||
return errs.ErrSystemIsBusy
|
return errs.ErrSystemIsBusy
|
||||||
}
|
}
|
||||||
|
|
||||||
transactionTagIndexs[i] = &models.TransactionTagIndex{
|
transactionTagIndexes[i] = &models.TransactionTagIndex{
|
||||||
TagIndexId: tagIndexId,
|
TagIndexId: tagIndexId,
|
||||||
Uid: transaction.Uid,
|
Uid: transaction.Uid,
|
||||||
Deleted: false,
|
Deleted: false,
|
||||||
@@ -494,6 +501,10 @@ func (s *TransactionService) ModifyTransaction(c *core.Context, transaction *mod
|
|||||||
return errs.ErrCannotModifyTransactionInHiddenAccount
|
return errs.ErrCannotModifyTransactionInHiddenAccount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if sourceAccount.Type == models.ACCOUNT_TYPE_MULTI_SUB_ACCOUNTS || (destinationAccount != nil && destinationAccount.Type == models.ACCOUNT_TYPE_MULTI_SUB_ACCOUNTS) {
|
||||||
|
return errs.ErrCannotModifyTransactionInParentAccount
|
||||||
|
}
|
||||||
|
|
||||||
if (transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT || transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN) &&
|
if (transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT || transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN) &&
|
||||||
sourceAccount.Currency == destinationAccount.Currency && transaction.Amount != transaction.RelatedAccountAmount {
|
sourceAccount.Currency == destinationAccount.Currency && transaction.Amount != transaction.RelatedAccountAmount {
|
||||||
return errs.ErrTransactionSourceAndDestinationAmountNotEqual
|
return errs.ErrTransactionSourceAndDestinationAmountNotEqual
|
||||||
@@ -521,6 +532,8 @@ func (s *TransactionService) ModifyTransaction(c *core.Context, transaction *mod
|
|||||||
updateCols = append(updateCols, "category_id")
|
updateCols = append(updateCols, "category_id")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
modifyTransactionTime := false
|
||||||
|
|
||||||
if utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime) != utils.GetUnixTimeFromTransactionTime(oldTransaction.TransactionTime) {
|
if utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime) != utils.GetUnixTimeFromTransactionTime(oldTransaction.TransactionTime) {
|
||||||
sameSecondLatestTransaction := &models.Transaction{}
|
sameSecondLatestTransaction := &models.Transaction{}
|
||||||
minTransactionTime := utils.GetMinTransactionTimeFromUnixTime(utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime))
|
minTransactionTime := utils.GetMinTransactionTimeFromUnixTime(utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime))
|
||||||
@@ -539,6 +552,7 @@ func (s *TransactionService) ModifyTransaction(c *core.Context, transaction *mod
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateCols = append(updateCols, "transaction_time")
|
updateCols = append(updateCols, "transaction_time")
|
||||||
|
modifyTransactionTime = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if transaction.TimezoneUtcOffset != oldTransaction.TimezoneUtcOffset {
|
if transaction.TimezoneUtcOffset != oldTransaction.TimezoneUtcOffset {
|
||||||
@@ -586,7 +600,7 @@ func (s *TransactionService) ModifyTransaction(c *core.Context, transaction *mod
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get and verify tags
|
// Get and verify tags
|
||||||
err = s.isTagsValid(sess, transaction, transactionTagIndexs, addTagIds)
|
err = s.isTagsValid(sess, transaction, transactionTagIndexes, addTagIds)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -634,15 +648,27 @@ func (s *TransactionService) ModifyTransaction(c *core.Context, transaction *mod
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(transactionTagIndexs) > 0 {
|
if len(transactionTagIndexes) > 0 {
|
||||||
for i := 0; i < len(transactionTagIndexs); i++ {
|
for i := 0; i < len(transactionTagIndexes); i++ {
|
||||||
transactionTagIndex := transactionTagIndexs[i]
|
transactionTagIndex := transactionTagIndexes[i]
|
||||||
|
transactionTagIndex.TransactionTime = transaction.TransactionTime
|
||||||
|
|
||||||
_, err := sess.Insert(transactionTagIndex)
|
_, err := sess.Insert(transactionTagIndex)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if len(transactionTagIndexes) == 0 && currentTagIdsCount > 0 && modifyTransactionTime {
|
||||||
|
tagIndexUpdateModel := &models.TransactionTagIndex{
|
||||||
|
TransactionTime: transaction.TransactionTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := sess.Where("uid=? AND deleted=? AND transaction_id=?", transaction.Uid, false, transaction.TransactionId).Update(tagIndexUpdateModel)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update account table
|
// Update account table
|
||||||
@@ -839,6 +865,10 @@ func (s *TransactionService) DeleteTransaction(c *core.Context, uid int64, trans
|
|||||||
return errs.ErrCannotDeleteTransactionInHiddenAccount
|
return errs.ErrCannotDeleteTransactionInHiddenAccount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if sourceAccount.Type == models.ACCOUNT_TYPE_MULTI_SUB_ACCOUNTS || (destinationAccount != nil && destinationAccount.Type == models.ACCOUNT_TYPE_MULTI_SUB_ACCOUNTS) {
|
||||||
|
return errs.ErrCannotDeleteTransactionInParentAccount
|
||||||
|
}
|
||||||
|
|
||||||
// Update transaction row to deleted
|
// Update transaction row to deleted
|
||||||
deletedRows, err := sess.ID(oldTransaction.TransactionId).Cols("deleted", "deleted_unix_time").Where("uid=? AND deleted=?", uid, false).Update(updateModel)
|
deletedRows, err := sess.ID(oldTransaction.TransactionId).Cols("deleted", "deleted_unix_time").Where("uid=? AND deleted=?", uid, false).Update(updateModel)
|
||||||
|
|
||||||
@@ -1010,46 +1040,28 @@ func (s *TransactionService) GetRelatedTransferTransaction(originalTransaction *
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetAccountsTotalIncomeAndExpense returns the every accounts total income and expense amount by specific date range
|
// GetAccountsTotalIncomeAndExpense returns the every accounts total income and expense amount by specific date range
|
||||||
func (s *TransactionService) GetAccountsTotalIncomeAndExpense(c *core.Context, uid int64, startUnixTime int64, endUnixTime int64) (map[int64]int64, map[int64]int64, error) {
|
func (s *TransactionService) GetAccountsTotalIncomeAndExpense(c *core.Context, uid int64, startUnixTime int64, endUnixTime int64, utcOffset int16, useTransactionTimezone bool) (map[int64]int64, map[int64]int64, error) {
|
||||||
if uid <= 0 {
|
if uid <= 0 {
|
||||||
return nil, nil, errs.ErrUserIdInvalid
|
return nil, nil, errs.ErrUserIdInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
startTransactionTime := utils.GetMinTransactionTimeFromUnixTime(startUnixTime)
|
clientLocation := time.FixedZone("Client Timezone", int(utcOffset)*60)
|
||||||
endTransactionTime := utils.GetMaxTransactionTimeFromUnixTime(endUnixTime)
|
startLocalDateTime := utils.FormatUnixTimeToNumericLocalDateTime(startUnixTime, clientLocation)
|
||||||
|
endLocalDateTime := utils.FormatUnixTimeToNumericLocalDateTime(endUnixTime, clientLocation)
|
||||||
|
|
||||||
var transactionTotalAmounts []*models.Transaction
|
startUnixTime = utils.GetMinUnixTimeWithSameLocalDateTime(startUnixTime, utcOffset)
|
||||||
err := s.UserDataDB(uid).NewSession(c).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)
|
endUnixTime = utils.GetMaxUnixTimeWithSameLocalDateTime(endUnixTime, utcOffset)
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
incomeAmounts := make(map[int64]int64)
|
|
||||||
expenseAmounts := make(map[int64]int64)
|
|
||||||
|
|
||||||
for i := 0; i < len(transactionTotalAmounts); i++ {
|
|
||||||
transactionTotalAmount := transactionTotalAmounts[i]
|
|
||||||
|
|
||||||
if transactionTotalAmount.Type == models.TRANSACTION_DB_TYPE_INCOME {
|
|
||||||
incomeAmounts[transactionTotalAmount.AccountId] = transactionTotalAmount.Amount
|
|
||||||
} else if transactionTotalAmount.Type == models.TRANSACTION_DB_TYPE_EXPENSE {
|
|
||||||
expenseAmounts[transactionTotalAmount.AccountId] = transactionTotalAmount.Amount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return incomeAmounts, expenseAmounts, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAccountsMonthTotalIncomeAndExpense returns the every accounts total income and expense amount in month by specific date range
|
|
||||||
func (s *TransactionService) GetAccountsMonthTotalIncomeAndExpense(c *core.Context, uid int64, startUnixTime int64, endUnixTime int64, pageCount int) (map[string]models.TransactionAccountsAmount, error) {
|
|
||||||
if uid <= 0 {
|
|
||||||
return nil, errs.ErrUserIdInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
startTransactionTime := utils.GetMinTransactionTimeFromUnixTime(startUnixTime)
|
startTransactionTime := utils.GetMinTransactionTimeFromUnixTime(startUnixTime)
|
||||||
endTransactionTime := utils.GetMaxTransactionTimeFromUnixTime(endUnixTime)
|
endTransactionTime := utils.GetMaxTransactionTimeFromUnixTime(endUnixTime)
|
||||||
|
|
||||||
|
condition := "uid=? AND deleted=? AND (type=? OR type=?) AND transaction_time>=? AND transaction_time<=?"
|
||||||
|
conditionParams := make([]any, 0, 4)
|
||||||
|
conditionParams = append(conditionParams, uid)
|
||||||
|
conditionParams = append(conditionParams, false)
|
||||||
|
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_INCOME)
|
||||||
|
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_EXPENSE)
|
||||||
|
|
||||||
minTransactionTime := startTransactionTime
|
minTransactionTime := startTransactionTime
|
||||||
maxTransactionTime := endTransactionTime
|
maxTransactionTime := endTransactionTime
|
||||||
var allTransactions []*models.Transaction
|
var allTransactions []*models.Transaction
|
||||||
@@ -1057,15 +1069,20 @@ func (s *TransactionService) GetAccountsMonthTotalIncomeAndExpense(c *core.Conte
|
|||||||
for maxTransactionTime > 0 {
|
for maxTransactionTime > 0 {
|
||||||
var transactions []*models.Transaction
|
var transactions []*models.Transaction
|
||||||
|
|
||||||
err := s.UserDataDB(uid).NewSession(c).Select("uid, type, account_id, transaction_time, timezone_utc_offset, 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, minTransactionTime, maxTransactionTime).Limit(pageCount, 0).OrderBy("transaction_time desc").Find(&transactions)
|
finalConditionParams := make([]any, 0, 6)
|
||||||
|
finalConditionParams = append(finalConditionParams, conditionParams...)
|
||||||
|
finalConditionParams = append(finalConditionParams, minTransactionTime)
|
||||||
|
finalConditionParams = append(finalConditionParams, maxTransactionTime)
|
||||||
|
|
||||||
|
err := s.UserDataDB(uid).NewSession(c).Select("type, account_id, transaction_time, timezone_utc_offset, amount").Where(condition, finalConditionParams...).Limit(pageCountForLoadTransactionAmounts, 0).OrderBy("transaction_time desc").Find(&transactions)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
allTransactions = append(allTransactions, transactions...)
|
allTransactions = append(allTransactions, transactions...)
|
||||||
|
|
||||||
if len(transactions) < pageCount {
|
if len(transactions) < pageCountForLoadTransactionAmounts {
|
||||||
maxTransactionTime = 0
|
maxTransactionTime = 0
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -1073,74 +1090,269 @@ func (s *TransactionService) GetAccountsMonthTotalIncomeAndExpense(c *core.Conte
|
|||||||
maxTransactionTime = transactions[len(transactions)-1].TransactionTime - 1
|
maxTransactionTime = transactions[len(transactions)-1].TransactionTime - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
totalAmounts := make(map[string]models.TransactionAccountsAmount)
|
incomeAmounts := make(map[int64]int64)
|
||||||
|
expenseAmounts := make(map[int64]int64)
|
||||||
|
|
||||||
for i := 0; i < len(allTransactions); i++ {
|
for i := 0; i < len(allTransactions); i++ {
|
||||||
transaction := allTransactions[i]
|
transaction := allTransactions[i]
|
||||||
transactionTimeZone := time.FixedZone("Transaction Timezone", int(transaction.TimezoneUtcOffset)*60)
|
timeZone := clientLocation
|
||||||
yearMonth := utils.FormatUnixTimeToYearMonth(utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime), transactionTimeZone)
|
|
||||||
|
|
||||||
monthAccountsAmounts, exists := totalAmounts[yearMonth]
|
if useTransactionTimezone {
|
||||||
|
timeZone = time.FixedZone("Transaction Timezone", int(transaction.TimezoneUtcOffset)*60)
|
||||||
if !exists {
|
|
||||||
monthAccountsAmounts = make(models.TransactionAccountsAmount)
|
|
||||||
totalAmounts[yearMonth] = monthAccountsAmounts
|
|
||||||
}
|
}
|
||||||
|
|
||||||
monthAccountAmount, exists := monthAccountsAmounts[transaction.AccountId]
|
localDateTime := utils.FormatUnixTimeToNumericLocalDateTime(utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime), timeZone)
|
||||||
|
|
||||||
if !exists {
|
if localDateTime < startLocalDateTime || localDateTime > endLocalDateTime {
|
||||||
monthAccountAmount = &models.TransactionAccountAmount{
|
continue
|
||||||
AccountId: transaction.AccountId,
|
|
||||||
TotalIncomeAmount: 0,
|
|
||||||
TotalExpenseAmount: 0,
|
|
||||||
}
|
|
||||||
monthAccountsAmounts[transaction.AccountId] = monthAccountAmount
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var amountsMap map[int64]int64
|
||||||
|
|
||||||
if transaction.Type == models.TRANSACTION_DB_TYPE_INCOME {
|
if transaction.Type == models.TRANSACTION_DB_TYPE_INCOME {
|
||||||
monthAccountAmount.TotalIncomeAmount += transaction.Amount
|
amountsMap = incomeAmounts
|
||||||
} else if transaction.Type == models.TRANSACTION_DB_TYPE_EXPENSE {
|
} else if transaction.Type == models.TRANSACTION_DB_TYPE_EXPENSE {
|
||||||
monthAccountAmount.TotalExpenseAmount += transaction.Amount
|
amountsMap = expenseAmounts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
totalAmounts, exists := amountsMap[transaction.AccountId]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
totalAmounts = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
totalAmounts += transaction.Amount
|
||||||
|
amountsMap[transaction.AccountId] = totalAmounts
|
||||||
}
|
}
|
||||||
|
|
||||||
return totalAmounts, nil
|
return incomeAmounts, expenseAmounts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAccountsAndCategoriesTotalIncomeAndExpense returns the every accounts and categories total income and expense amount by specific date range
|
// GetAccountsAndCategoriesTotalIncomeAndExpense returns the every accounts and categories total income and expense amount by specific date range
|
||||||
func (s *TransactionService) GetAccountsAndCategoriesTotalIncomeAndExpense(c *core.Context, uid int64, startUnixTime int64, endUnixTime int64) ([]*models.Transaction, error) {
|
func (s *TransactionService) GetAccountsAndCategoriesTotalIncomeAndExpense(c *core.Context, uid int64, startUnixTime int64, endUnixTime int64, utcOffset int16, useTransactionTimezone bool) ([]*models.Transaction, error) {
|
||||||
if uid <= 0 {
|
if uid <= 0 {
|
||||||
return nil, errs.ErrUserIdInvalid
|
return nil, errs.ErrUserIdInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clientLocation := time.FixedZone("Client Timezone", int(utcOffset)*60)
|
||||||
|
var startLocalDateTime, endLocalDateTime, startTransactionTime, endTransactionTime int64
|
||||||
|
|
||||||
|
if startUnixTime > 0 {
|
||||||
|
startLocalDateTime = utils.FormatUnixTimeToNumericLocalDateTime(startUnixTime, clientLocation)
|
||||||
|
startUnixTime = utils.GetMinUnixTimeWithSameLocalDateTime(startUnixTime, utcOffset)
|
||||||
|
startTransactionTime = utils.GetMinTransactionTimeFromUnixTime(startUnixTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
if endUnixTime > 0 {
|
||||||
|
endLocalDateTime = utils.FormatUnixTimeToNumericLocalDateTime(endUnixTime, clientLocation)
|
||||||
|
endUnixTime = utils.GetMaxUnixTimeWithSameLocalDateTime(endUnixTime, utcOffset)
|
||||||
|
endTransactionTime = utils.GetMaxTransactionTimeFromUnixTime(endUnixTime)
|
||||||
|
}
|
||||||
|
|
||||||
condition := "uid=? AND deleted=? AND (type=? OR type=?)"
|
condition := "uid=? AND deleted=? AND (type=? OR type=?)"
|
||||||
conditionParams := make([]any, 0, 8)
|
conditionParams := make([]any, 0, 4)
|
||||||
conditionParams = append(conditionParams, uid)
|
conditionParams = append(conditionParams, uid)
|
||||||
conditionParams = append(conditionParams, false)
|
conditionParams = append(conditionParams, false)
|
||||||
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_INCOME)
|
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_INCOME)
|
||||||
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_EXPENSE)
|
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_EXPENSE)
|
||||||
|
|
||||||
if startUnixTime > 0 {
|
minTransactionTime := startTransactionTime
|
||||||
condition = condition + " AND transaction_time>=?"
|
maxTransactionTime := endTransactionTime
|
||||||
conditionParams = append(conditionParams, utils.GetMinTransactionTimeFromUnixTime(startUnixTime))
|
var allTransactions []*models.Transaction
|
||||||
|
|
||||||
|
for maxTransactionTime >= 0 {
|
||||||
|
var transactions []*models.Transaction
|
||||||
|
|
||||||
|
finalCondition := condition
|
||||||
|
finalConditionParams := make([]any, 0, 6)
|
||||||
|
finalConditionParams = append(finalConditionParams, conditionParams...)
|
||||||
|
|
||||||
|
if minTransactionTime > 0 {
|
||||||
|
finalCondition = finalCondition + " AND transaction_time>=?"
|
||||||
|
finalConditionParams = append(finalConditionParams, minTransactionTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
if maxTransactionTime > 0 {
|
||||||
|
finalCondition = finalCondition + " AND transaction_time<=?"
|
||||||
|
finalConditionParams = append(finalConditionParams, maxTransactionTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := s.UserDataDB(uid).NewSession(c).Select("category_id, account_id, transaction_time, timezone_utc_offset, amount").Where(finalCondition, finalConditionParams...).Limit(pageCountForLoadTransactionAmounts, 0).OrderBy("transaction_time desc").Find(&transactions)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
allTransactions = append(allTransactions, transactions...)
|
||||||
|
|
||||||
|
if len(transactions) < pageCountForLoadTransactionAmounts {
|
||||||
|
maxTransactionTime = -1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
maxTransactionTime = transactions[len(transactions)-1].TransactionTime - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if endUnixTime > 0 {
|
transactionTotalAmountsMap := make(map[string]*models.Transaction)
|
||||||
condition = condition + " AND transaction_time<=?"
|
|
||||||
conditionParams = append(conditionParams, utils.GetMaxTransactionTimeFromUnixTime(endUnixTime))
|
for i := 0; i < len(allTransactions); i++ {
|
||||||
|
transaction := allTransactions[i]
|
||||||
|
timeZone := clientLocation
|
||||||
|
|
||||||
|
if useTransactionTimezone {
|
||||||
|
timeZone = time.FixedZone("Transaction Timezone", int(transaction.TimezoneUtcOffset)*60)
|
||||||
|
}
|
||||||
|
|
||||||
|
localDateTime := utils.FormatUnixTimeToNumericLocalDateTime(utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime), timeZone)
|
||||||
|
|
||||||
|
if (startLocalDateTime > 0 && localDateTime < startLocalDateTime) || (endLocalDateTime > 0 && localDateTime > endLocalDateTime) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
groupKey := fmt.Sprintf("%d_%d", transaction.CategoryId, transaction.AccountId)
|
||||||
|
totalAmounts, exists := transactionTotalAmountsMap[groupKey]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
totalAmounts = &models.Transaction{
|
||||||
|
CategoryId: transaction.CategoryId,
|
||||||
|
AccountId: transaction.AccountId,
|
||||||
|
Amount: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
transactionTotalAmountsMap[groupKey] = totalAmounts
|
||||||
|
}
|
||||||
|
|
||||||
|
totalAmounts.Amount += transaction.Amount
|
||||||
}
|
}
|
||||||
|
|
||||||
var transactionTotalAmounts []*models.Transaction
|
transactionTotalAmounts := make([]*models.Transaction, 0, len(transactionTotalAmountsMap))
|
||||||
err := s.UserDataDB(uid).NewSession(c).Select("category_id, account_id, SUM(amount) as amount").Where(condition, conditionParams...).GroupBy("category_id, account_id").Find(&transactionTotalAmounts)
|
|
||||||
|
|
||||||
if err != nil {
|
for _, totalAmounts := range transactionTotalAmountsMap {
|
||||||
return nil, err
|
transactionTotalAmounts = append(transactionTotalAmounts, totalAmounts)
|
||||||
}
|
}
|
||||||
|
|
||||||
return transactionTotalAmounts, nil
|
return transactionTotalAmounts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAccountsAndCategoriesMonthlyIncomeAndExpense returns the every accounts monthly income and expense amount by specific date range
|
||||||
|
func (s *TransactionService) GetAccountsAndCategoriesMonthlyIncomeAndExpense(c *core.Context, uid int64, startYear int32, startMonth int32, endYear int32, endMonth int32, utcOffset int16, useTransactionTimezone bool) (map[int32][]*models.Transaction, error) {
|
||||||
|
if uid <= 0 {
|
||||||
|
return nil, errs.ErrUserIdInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
clientLocation := time.FixedZone("Client Timezone", int(utcOffset)*60)
|
||||||
|
var startTransactionTime, endTransactionTime int64
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if startYear > 0 && startMonth > 0 {
|
||||||
|
startTransactionTime, _, err = utils.GetTransactionTimeRangeByYearMonth(startYear, startMonth)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errs.ErrSystemError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if endYear > 0 && endMonth > 0 {
|
||||||
|
_, endTransactionTime, err = utils.GetTransactionTimeRangeByYearMonth(endYear, endMonth)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errs.ErrSystemError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
condition := "uid=? AND deleted=? AND (type=? OR type=?)"
|
||||||
|
conditionParams := make([]any, 0, 4)
|
||||||
|
conditionParams = append(conditionParams, uid)
|
||||||
|
conditionParams = append(conditionParams, false)
|
||||||
|
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_INCOME)
|
||||||
|
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_EXPENSE)
|
||||||
|
|
||||||
|
minTransactionTime := startTransactionTime
|
||||||
|
maxTransactionTime := endTransactionTime
|
||||||
|
var allTransactions []*models.Transaction
|
||||||
|
|
||||||
|
for maxTransactionTime >= 0 {
|
||||||
|
var transactions []*models.Transaction
|
||||||
|
|
||||||
|
finalCondition := condition
|
||||||
|
finalConditionParams := make([]any, 0, 6)
|
||||||
|
finalConditionParams = append(finalConditionParams, conditionParams...)
|
||||||
|
|
||||||
|
if minTransactionTime > 0 {
|
||||||
|
finalCondition = finalCondition + " AND transaction_time>=?"
|
||||||
|
finalConditionParams = append(finalConditionParams, minTransactionTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
if maxTransactionTime > 0 {
|
||||||
|
finalCondition = finalCondition + " AND transaction_time<=?"
|
||||||
|
finalConditionParams = append(finalConditionParams, maxTransactionTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := s.UserDataDB(uid).NewSession(c).Select("category_id, account_id, transaction_time, timezone_utc_offset, amount").Where(finalCondition, finalConditionParams...).Limit(pageCountForLoadTransactionAmounts, 0).OrderBy("transaction_time desc").Find(&transactions)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
allTransactions = append(allTransactions, transactions...)
|
||||||
|
|
||||||
|
if len(transactions) < pageCountForLoadTransactionAmounts {
|
||||||
|
maxTransactionTime = -1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
maxTransactionTime = transactions[len(transactions)-1].TransactionTime - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
startYearMonth := startYear*100 + startMonth
|
||||||
|
endYearMonth := endYear*100 + endMonth
|
||||||
|
transactionsMonthlyAmountsMap := make(map[string]*models.Transaction)
|
||||||
|
transactionsMonthlyAmounts := make(map[int32][]*models.Transaction)
|
||||||
|
|
||||||
|
for i := 0; i < len(allTransactions); i++ {
|
||||||
|
transaction := allTransactions[i]
|
||||||
|
timeZone := clientLocation
|
||||||
|
|
||||||
|
if useTransactionTimezone {
|
||||||
|
timeZone = time.FixedZone("Transaction Timezone", int(transaction.TimezoneUtcOffset)*60)
|
||||||
|
}
|
||||||
|
|
||||||
|
yearMonth := utils.FormatUnixTimeToNumericYearMonth(utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime), timeZone)
|
||||||
|
|
||||||
|
if (startYearMonth > 0 && yearMonth < startYearMonth) || (endYearMonth > 0 && yearMonth > endYearMonth) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
groupKey := fmt.Sprintf("%d_%d_%d", yearMonth, transaction.CategoryId, transaction.AccountId)
|
||||||
|
transactionAmounts, exists := transactionsMonthlyAmountsMap[groupKey]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
transactionAmounts = &models.Transaction{
|
||||||
|
CategoryId: transaction.CategoryId,
|
||||||
|
AccountId: transaction.AccountId,
|
||||||
|
}
|
||||||
|
transactionsMonthlyAmountsMap[groupKey] = transactionAmounts
|
||||||
|
}
|
||||||
|
|
||||||
|
transactionAmounts.Amount += transaction.Amount
|
||||||
|
}
|
||||||
|
|
||||||
|
for groupKey, transaction := range transactionsMonthlyAmountsMap {
|
||||||
|
groupKeyParts := strings.Split(groupKey, "_")
|
||||||
|
yearMonth, _ := utils.StringToInt32(groupKeyParts[0])
|
||||||
|
monthlyAmounts, exists := transactionsMonthlyAmounts[yearMonth]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
monthlyAmounts = make([]*models.Transaction, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
monthlyAmounts = append(monthlyAmounts, transaction)
|
||||||
|
transactionsMonthlyAmounts[yearMonth] = monthlyAmounts
|
||||||
|
}
|
||||||
|
|
||||||
|
return transactionsMonthlyAmounts, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetTransactionMapByList returns a transaction map by a list
|
// GetTransactionMapByList returns a transaction map by a list
|
||||||
func (s *TransactionService) GetTransactionMapByList(transactions []*models.Transaction) map[int64]*models.Transaction {
|
func (s *TransactionService) GetTransactionMapByList(transactions []*models.Transaction) map[int64]*models.Transaction {
|
||||||
transactionMap := make(map[int64]*models.Transaction)
|
transactionMap := make(map[int64]*models.Transaction)
|
||||||
@@ -1153,7 +1365,7 @@ func (s *TransactionService) GetTransactionMapByList(transactions []*models.Tran
|
|||||||
return transactionMap
|
return transactionMap
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TransactionService) getTransactionQueryCondition(uid int64, maxTransactionTime int64, minTransactionTime int64, transactionType models.TransactionDbType, categoryIds []int64, accountIds []int64, keyword string, noDuplicated bool) (string, []any) {
|
func (s *TransactionService) getTransactionQueryCondition(uid int64, maxTransactionTime int64, minTransactionTime int64, transactionType models.TransactionDbType, categoryIds []int64, accountIds []int64, tagIds []int64, amountFilter string, keyword string, noDuplicated bool) (string, []any) {
|
||||||
condition := "uid=? AND deleted=?"
|
condition := "uid=? AND deleted=?"
|
||||||
conditionParams := make([]any, 0, 16)
|
conditionParams := make([]any, 0, 16)
|
||||||
conditionParams = append(conditionParams, uid)
|
conditionParams = append(conditionParams, uid)
|
||||||
@@ -1169,6 +1381,18 @@ func (s *TransactionService) getTransactionQueryCondition(uid int64, maxTransact
|
|||||||
conditionParams = append(conditionParams, minTransactionTime)
|
conditionParams = append(conditionParams, minTransactionTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var accountIdsCondition strings.Builder
|
||||||
|
accountIdConditionParams := make([]any, 0, len(accountIds))
|
||||||
|
|
||||||
|
for i := 0; i < len(accountIds); i++ {
|
||||||
|
if i > 0 {
|
||||||
|
accountIdsCondition.WriteString(",")
|
||||||
|
}
|
||||||
|
|
||||||
|
accountIdsCondition.WriteString("?")
|
||||||
|
accountIdConditionParams = append(accountIdConditionParams, accountIds[i])
|
||||||
|
}
|
||||||
|
|
||||||
if models.TRANSACTION_DB_TYPE_MODIFY_BALANCE <= transactionType && transactionType <= models.TRANSACTION_DB_TYPE_EXPENSE {
|
if models.TRANSACTION_DB_TYPE_MODIFY_BALANCE <= transactionType && transactionType <= models.TRANSACTION_DB_TYPE_EXPENSE {
|
||||||
condition = condition + " AND type=?"
|
condition = condition + " AND type=?"
|
||||||
conditionParams = append(conditionParams, transactionType)
|
conditionParams = append(conditionParams, transactionType)
|
||||||
@@ -1176,18 +1400,35 @@ func (s *TransactionService) getTransactionQueryCondition(uid int64, maxTransact
|
|||||||
if len(accountIds) == 0 {
|
if len(accountIds) == 0 {
|
||||||
condition = condition + " AND type=?"
|
condition = condition + " AND type=?"
|
||||||
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_TRANSFER_OUT)
|
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_TRANSFER_OUT)
|
||||||
} else {
|
} else if len(accountIds) == 1 {
|
||||||
condition = condition + " AND (type=? OR type=?)"
|
condition = condition + " AND (type=? OR type=?)"
|
||||||
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_TRANSFER_OUT)
|
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_TRANSFER_OUT)
|
||||||
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_TRANSFER_IN)
|
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_TRANSFER_IN)
|
||||||
|
} else { // len(accountsIds) > 1
|
||||||
|
condition = condition + " AND (type=? OR (type=? AND related_account_id NOT IN (" + accountIdsCondition.String() + ")))"
|
||||||
|
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_TRANSFER_OUT)
|
||||||
|
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_TRANSFER_IN)
|
||||||
|
conditionParams = append(conditionParams, accountIdConditionParams...)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if noDuplicated && len(accountIds) == 0 {
|
if noDuplicated {
|
||||||
condition = condition + " AND (type=? OR type=? OR type=? OR type=?)"
|
if len(accountIds) == 0 {
|
||||||
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_MODIFY_BALANCE)
|
condition = condition + " AND (type=? OR type=? OR type=? OR type=?)"
|
||||||
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_INCOME)
|
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_MODIFY_BALANCE)
|
||||||
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_EXPENSE)
|
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_INCOME)
|
||||||
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_TRANSFER_OUT)
|
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_EXPENSE)
|
||||||
|
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_TRANSFER_OUT)
|
||||||
|
} else if len(accountIds) == 1 {
|
||||||
|
// Do Nothing
|
||||||
|
} else { // len(accountsIds) > 1
|
||||||
|
condition = condition + " AND (type=? OR type=? OR type=? OR type=? OR (type=? AND related_account_id NOT IN (" + accountIdsCondition.String() + ")))"
|
||||||
|
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_MODIFY_BALANCE)
|
||||||
|
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_INCOME)
|
||||||
|
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_EXPENSE)
|
||||||
|
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_TRANSFER_OUT)
|
||||||
|
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_TRANSFER_IN)
|
||||||
|
conditionParams = append(conditionParams, accountIdConditionParams...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1203,22 +1444,73 @@ func (s *TransactionService) getTransactionQueryCondition(uid int64, maxTransact
|
|||||||
conditionParams = append(conditionParams, categoryIds[i])
|
conditionParams = append(conditionParams, categoryIds[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
condition = condition + " AND category_id IN (" + conditions.String() + ")"
|
if conditions.Len() > 1 {
|
||||||
|
condition = condition + " AND category_id IN (" + conditions.String() + ")"
|
||||||
|
} else {
|
||||||
|
condition = condition + " AND category_id = " + conditions.String()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(accountIds) > 0 {
|
if len(accountIds) > 0 {
|
||||||
var conditions strings.Builder
|
if accountIdsCondition.Len() > 1 {
|
||||||
|
condition = condition + " AND account_id IN (" + accountIdsCondition.String() + ")"
|
||||||
for i := 0; i < len(accountIds); i++ {
|
} else {
|
||||||
if i > 0 {
|
condition = condition + " AND account_id = " + accountIdsCondition.String()
|
||||||
conditions.WriteString(",")
|
|
||||||
}
|
|
||||||
|
|
||||||
conditions.WriteString("?")
|
|
||||||
conditionParams = append(conditionParams, accountIds[i])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
condition = condition + " AND account_id IN (" + conditions.String() + ")"
|
conditionParams = append(conditionParams, accountIdConditionParams...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if amountFilter != "" {
|
||||||
|
amountFilterItems := strings.Split(amountFilter, ":")
|
||||||
|
|
||||||
|
if len(amountFilterItems) == 2 && amountFilterItems[0] == "gt" {
|
||||||
|
value, err := utils.StringToInt64(amountFilterItems[1])
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
condition = condition + " AND amount > ?"
|
||||||
|
conditionParams = append(conditionParams, value)
|
||||||
|
}
|
||||||
|
} else if len(amountFilterItems) == 2 && amountFilterItems[0] == "lt" {
|
||||||
|
value, err := utils.StringToInt64(amountFilterItems[1])
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
condition = condition + " AND amount < ?"
|
||||||
|
conditionParams = append(conditionParams, value)
|
||||||
|
}
|
||||||
|
} else if len(amountFilterItems) == 2 && amountFilterItems[0] == "eq" {
|
||||||
|
value, err := utils.StringToInt64(amountFilterItems[1])
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
condition = condition + " AND amount = ?"
|
||||||
|
conditionParams = append(conditionParams, value)
|
||||||
|
}
|
||||||
|
} else if len(amountFilterItems) == 2 && amountFilterItems[0] == "ne" {
|
||||||
|
value, err := utils.StringToInt64(amountFilterItems[1])
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
condition = condition + " AND amount <> ?"
|
||||||
|
conditionParams = append(conditionParams, value)
|
||||||
|
}
|
||||||
|
} else if len(amountFilterItems) == 3 && amountFilterItems[0] == "bt" {
|
||||||
|
value1, err := utils.StringToInt64(amountFilterItems[1])
|
||||||
|
value2, err := utils.StringToInt64(amountFilterItems[2])
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
condition = condition + " AND amount >= ? AND amount <= ?"
|
||||||
|
conditionParams = append(conditionParams, value1)
|
||||||
|
conditionParams = append(conditionParams, value2)
|
||||||
|
}
|
||||||
|
} else if len(amountFilterItems) == 3 && amountFilterItems[0] == "nb" {
|
||||||
|
value1, err := utils.StringToInt64(amountFilterItems[1])
|
||||||
|
value2, err := utils.StringToInt64(amountFilterItems[2])
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
condition = condition + " AND (amount < ? OR amount > ?)"
|
||||||
|
conditionParams = append(conditionParams, value1)
|
||||||
|
conditionParams = append(conditionParams, value2)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if keyword != "" {
|
if keyword != "" {
|
||||||
@@ -1229,6 +1521,40 @@ func (s *TransactionService) getTransactionQueryCondition(uid int64, maxTransact
|
|||||||
return condition, conditionParams
|
return condition, conditionParams
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *TransactionService) getTransactionQueryByTagIdsCondition(uid int64, maxTransactionTime int64, minTransactionTime int64, tagIds []int64) *builder.Builder {
|
||||||
|
if len(tagIds) > 0 {
|
||||||
|
condition := builder.And(builder.Eq{"uid": uid}, builder.Eq{"deleted": false})
|
||||||
|
|
||||||
|
if maxTransactionTime > 0 {
|
||||||
|
condition = condition.And(builder.Lte{"transaction_time": maxTransactionTime})
|
||||||
|
}
|
||||||
|
|
||||||
|
if minTransactionTime > 0 {
|
||||||
|
condition = condition.And(builder.Gte{"transaction_time": minTransactionTime})
|
||||||
|
}
|
||||||
|
|
||||||
|
condition = condition.And(builder.In("tag_id", tagIds))
|
||||||
|
|
||||||
|
return builder.Select("transaction_id").From("transaction_tag_index").Where(condition)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TransactionService) getTransactionQueryByAllTagIdsCondition(uid int64, maxTransactionTime int64, minTransactionTime int64) *builder.Builder {
|
||||||
|
condition := builder.And(builder.Eq{"uid": uid}, builder.Eq{"deleted": false})
|
||||||
|
|
||||||
|
if maxTransactionTime > 0 {
|
||||||
|
condition = condition.And(builder.Lte{"transaction_time": maxTransactionTime})
|
||||||
|
}
|
||||||
|
|
||||||
|
if minTransactionTime > 0 {
|
||||||
|
condition = condition.And(builder.Gte{"transaction_time": minTransactionTime})
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.Select("transaction_id").From("transaction_tag_index").Where(condition)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *TransactionService) isAccountIdValid(transaction *models.Transaction) error {
|
func (s *TransactionService) isAccountIdValid(transaction *models.Transaction) error {
|
||||||
if transaction.Type == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE {
|
if transaction.Type == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE {
|
||||||
if transaction.RelatedAccountId != 0 && transaction.RelatedAccountId != transaction.AccountId {
|
if transaction.RelatedAccountId != 0 && transaction.RelatedAccountId != transaction.AccountId {
|
||||||
@@ -1266,6 +1592,7 @@ func (s *TransactionService) getAccountModels(sess *xorm.Session, transaction *m
|
|||||||
return nil, nil, errs.ErrSourceAccountNotFound
|
return nil, nil, errs.ErrSourceAccountNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check whether the related account is valid
|
||||||
if transaction.Type == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE {
|
if transaction.Type == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE {
|
||||||
if transaction.RelatedAccountId != 0 && transaction.RelatedAccountId != transaction.AccountId {
|
if transaction.RelatedAccountId != 0 && transaction.RelatedAccountId != transaction.AccountId {
|
||||||
return nil, nil, errs.ErrAccountIdInvalid
|
return nil, nil, errs.ErrAccountIdInvalid
|
||||||
@@ -1292,6 +1619,54 @@ func (s *TransactionService) getAccountModels(sess *xorm.Session, transaction *m
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check whether the parent accounts are valid
|
||||||
|
if sourceAccount.ParentAccountId > 0 && destinationAccount != nil && sourceAccount.ParentAccountId != destinationAccount.ParentAccountId && destinationAccount.ParentAccountId > 0 {
|
||||||
|
var accounts []*models.Account
|
||||||
|
err := sess.Where("uid=? AND deleted=? and (account_id=? or account_id=?)", transaction.Uid, false, sourceAccount.ParentAccountId, destinationAccount.ParentAccountId).Find(&accounts)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(accounts) < 2 {
|
||||||
|
return nil, nil, errs.ErrAccountNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(accounts); i++ {
|
||||||
|
account := accounts[i]
|
||||||
|
|
||||||
|
if account.Hidden {
|
||||||
|
return nil, nil, errs.ErrCannotUseHiddenAccount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if sourceAccount.ParentAccountId > 0 && (destinationAccount == nil || sourceAccount.ParentAccountId == destinationAccount.ParentAccountId || destinationAccount.ParentAccountId == 0) {
|
||||||
|
sourceParentAccount := &models.Account{}
|
||||||
|
has, err = sess.ID(sourceAccount.ParentAccountId).Where("uid=? AND deleted=?", transaction.Uid, false).Get(sourceParentAccount)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
} else if !has {
|
||||||
|
return nil, nil, errs.ErrSourceAccountNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
if sourceParentAccount.Hidden {
|
||||||
|
return nil, nil, errs.ErrCannotUseHiddenAccount
|
||||||
|
}
|
||||||
|
} else if sourceAccount.ParentAccountId == 0 && destinationAccount != nil && destinationAccount.ParentAccountId > 0 {
|
||||||
|
destinationParentAccount := &models.Account{}
|
||||||
|
has, err = sess.ID(destinationAccount.ParentAccountId).Where("uid=? AND deleted=?", transaction.Uid, false).Get(destinationParentAccount)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
} else if !has {
|
||||||
|
return nil, nil, errs.ErrDestinationAccountNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
if destinationParentAccount.Hidden {
|
||||||
|
return nil, nil, errs.ErrCannotUseHiddenAccount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return sourceAccount, destinationAccount, nil
|
return sourceAccount, destinationAccount, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1360,6 +1735,10 @@ func (s *TransactionService) isCategoryValid(sess *xorm.Session, transaction *mo
|
|||||||
return errs.ErrTransactionCategoryNotFound
|
return errs.ErrTransactionCategoryNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if category.Hidden {
|
||||||
|
return errs.ErrCannotUseHiddenTransactionCategory
|
||||||
|
}
|
||||||
|
|
||||||
if category.ParentCategoryId < 1 {
|
if category.ParentCategoryId < 1 {
|
||||||
return errs.ErrCannotUsePrimaryCategoryForTransaction
|
return errs.ErrCannotUsePrimaryCategoryForTransaction
|
||||||
}
|
}
|
||||||
@@ -1369,13 +1748,26 @@ func (s *TransactionService) isCategoryValid(sess *xorm.Session, transaction *mo
|
|||||||
((transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT || transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN) && category.Type != models.CATEGORY_TYPE_TRANSFER) {
|
((transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT || transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN) && category.Type != models.CATEGORY_TYPE_TRANSFER) {
|
||||||
return errs.ErrTransactionCategoryTypeInvalid
|
return errs.ErrTransactionCategoryTypeInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parentCategory := &models.TransactionCategory{}
|
||||||
|
has, err = sess.ID(category.ParentCategoryId).Where("uid=? AND deleted=?", transaction.Uid, false).Get(parentCategory)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if !has {
|
||||||
|
return errs.ErrTransactionCategoryNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
if parentCategory.Hidden {
|
||||||
|
return errs.ErrCannotUseHiddenTransactionCategory
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TransactionService) isTagsValid(sess *xorm.Session, transaction *models.Transaction, transactionTagIndexs []*models.TransactionTagIndex, tagIds []int64) error {
|
func (s *TransactionService) isTagsValid(sess *xorm.Session, transaction *models.Transaction, transactionTagIndexes []*models.TransactionTagIndex, tagIds []int64) error {
|
||||||
if len(transactionTagIndexs) > 0 {
|
if len(transactionTagIndexes) > 0 {
|
||||||
var tags []*models.TransactionTag
|
var tags []*models.TransactionTag
|
||||||
err := sess.Where("uid=? AND deleted=?", transaction.Uid, false).In("tag_id", tagIds).Find(&tags)
|
err := sess.Where("uid=? AND deleted=?", transaction.Uid, false).In("tag_id", tagIds).Find(&tags)
|
||||||
|
|
||||||
@@ -1386,11 +1778,15 @@ func (s *TransactionService) isTagsValid(sess *xorm.Session, transaction *models
|
|||||||
tagMap := make(map[int64]*models.TransactionTag)
|
tagMap := make(map[int64]*models.TransactionTag)
|
||||||
|
|
||||||
for i := 0; i < len(tags); i++ {
|
for i := 0; i < len(tags); i++ {
|
||||||
|
if tags[i].Hidden {
|
||||||
|
return errs.ErrCannotUseHiddenTransactionTag
|
||||||
|
}
|
||||||
|
|
||||||
tagMap[tags[i].TagId] = tags[i]
|
tagMap[tags[i].TagId] = tags[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < len(transactionTagIndexs); i++ {
|
for i := 0; i < len(transactionTagIndexes); i++ {
|
||||||
if _, exists := tagMap[transactionTagIndexs[i].TagId]; !exists {
|
if _, exists := tagMap[transactionTagIndexes[i].TagId]; !exists {
|
||||||
return errs.ErrTransactionTagNotFound
|
return errs.ErrTransactionTagNotFound
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -248,6 +248,30 @@ func (s *UserService) UpdateUser(c *core.Context, user *models.User, modifyUserL
|
|||||||
updateCols = append(updateCols, "short_time_format")
|
updateCols = append(updateCols, "short_time_format")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if core.DECIMAL_SEPARATOR_DEFAULT <= user.DecimalSeparator && user.DecimalSeparator <= core.DECIMAL_SEPARATOR_SPACE {
|
||||||
|
updateCols = append(updateCols, "decimal_separator")
|
||||||
|
}
|
||||||
|
|
||||||
|
if core.DIGIT_GROUPING_SYMBOL_DEFAULT <= user.DigitGroupingSymbol && user.DigitGroupingSymbol <= core.DIGIT_GROUPING_SYMBOL_APOSTROPHE {
|
||||||
|
updateCols = append(updateCols, "digit_grouping_symbol")
|
||||||
|
}
|
||||||
|
|
||||||
|
if core.DIGIT_GROUPING_TYPE_DEFAULT <= user.DigitGrouping && user.DigitGrouping <= core.DIGIT_GROUPING_TYPE_THOUSANDS_SEPARATOR {
|
||||||
|
updateCols = append(updateCols, "digit_grouping")
|
||||||
|
}
|
||||||
|
|
||||||
|
if models.CURRENCY_DISPLAY_TYPE_DEFAULT <= user.CurrencyDisplayType && user.CurrencyDisplayType <= models.CURRENCY_DISPLAY_TYPE_NAME_AFTER_AMOUNT {
|
||||||
|
updateCols = append(updateCols, "currency_display_type")
|
||||||
|
}
|
||||||
|
|
||||||
|
if models.AMOUNT_COLOR_TYPE_DEFAULT <= user.ExpenseAmountColor && user.ExpenseAmountColor <= models.AMOUNT_COLOR_TYPE_BLACK_OR_WHITE {
|
||||||
|
updateCols = append(updateCols, "expense_amount_color")
|
||||||
|
}
|
||||||
|
|
||||||
|
if models.AMOUNT_COLOR_TYPE_DEFAULT <= user.IncomeAmountColor && user.IncomeAmountColor <= models.AMOUNT_COLOR_TYPE_BLACK_OR_WHITE {
|
||||||
|
updateCols = append(updateCols, "income_amount_color")
|
||||||
|
}
|
||||||
|
|
||||||
user.UpdatedUnixTime = now
|
user.UpdatedUnixTime = now
|
||||||
updateCols = append(updateCols, "updated_unix_time")
|
updateCols = append(updateCols, "updated_unix_time")
|
||||||
|
|
||||||
@@ -270,6 +294,25 @@ func (s *UserService) UpdateUser(c *core.Context, user *models.User, modifyUserL
|
|||||||
return keyProfileUpdated, emailSetToUnverified, nil
|
return keyProfileUpdated, emailSetToUnverified, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateUserAvatar updated the custom avatar type of specified user
|
||||||
|
func (s *UserService) UpdateUserAvatar(c *core.Context, uid int64, customAvatarType string) error {
|
||||||
|
if uid <= 0 {
|
||||||
|
return errs.ErrUserIdInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
|
||||||
|
updateModel := &models.User{
|
||||||
|
CustomAvatarType: customAvatarType,
|
||||||
|
UpdatedUnixTime: now,
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.UserDB().DoTransaction(c, func(sess *xorm.Session) error {
|
||||||
|
_, err := sess.ID(uid).Cols("custom_avatar_type", "updated_unix_time").Where("deleted=?", false).Update(updateModel)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateUserLastLoginTime updates the last login time field
|
// UpdateUserLastLoginTime updates the last login time field
|
||||||
func (s *UserService) UpdateUserLastLoginTime(c *core.Context, uid int64) error {
|
func (s *UserService) UpdateUserLastLoginTime(c *core.Context, uid int64) error {
|
||||||
if uid <= 0 {
|
if uid <= 0 {
|
||||||
|
|||||||
+321
-31
@@ -11,6 +11,7 @@ import (
|
|||||||
"gopkg.in/ini.v1"
|
"gopkg.in/ini.v1"
|
||||||
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/locales"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -57,14 +58,26 @@ const (
|
|||||||
Sqlite3DbType string = "sqlite3"
|
Sqlite3DbType string = "sqlite3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Object Storage types
|
||||||
|
const (
|
||||||
|
LocalFileSystemObjectStorageType string = "local_filesystem"
|
||||||
|
MinIOStorageType string = "minio"
|
||||||
|
)
|
||||||
|
|
||||||
// Uuid generator types
|
// Uuid generator types
|
||||||
const (
|
const (
|
||||||
InternalUuidGeneratorType string = "internal"
|
InternalUuidGeneratorType string = "internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Duplicate checker types
|
||||||
|
const (
|
||||||
|
InMemoryDuplicateCheckerType string = "in_memory"
|
||||||
|
)
|
||||||
|
|
||||||
// User avatar provider types
|
// User avatar provider types
|
||||||
const (
|
const (
|
||||||
GravatarProvider string = "gravatar"
|
InternalAvatarProvider string = "internal"
|
||||||
|
GravatarProvider string = "gravatar"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Map provider types
|
// Map provider types
|
||||||
@@ -74,10 +87,13 @@ const (
|
|||||||
OpenTopoMapProvider string = "opentopomap"
|
OpenTopoMapProvider string = "opentopomap"
|
||||||
OPNVKarteMapProvider string = "opnvkarte"
|
OPNVKarteMapProvider string = "opnvkarte"
|
||||||
CyclOSMMapProvider string = "cyclosm"
|
CyclOSMMapProvider string = "cyclosm"
|
||||||
GoogleMapProvider string = "googlemap"
|
CartoDBMapProvider string = "cartodb"
|
||||||
TomTomMapProvider string = "tomtom"
|
TomTomMapProvider string = "tomtom"
|
||||||
|
TianDiTuProvider string = "tianditu"
|
||||||
|
GoogleMapProvider string = "googlemap"
|
||||||
BaiduMapProvider string = "baidumap"
|
BaiduMapProvider string = "baidumap"
|
||||||
AmapProvider string = "amap"
|
AmapProvider string = "amap"
|
||||||
|
CustomProvider string = "custom"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Amap security verification method
|
// Amap security verification method
|
||||||
@@ -110,14 +126,19 @@ const (
|
|||||||
defaultDatabaseMaxOpenConn uint16 = 0
|
defaultDatabaseMaxOpenConn uint16 = 0
|
||||||
defaultDatabaseConnMaxLifetime uint32 = 14400
|
defaultDatabaseConnMaxLifetime uint32 = 14400
|
||||||
|
|
||||||
defaultLogMode string = "console"
|
defaultLogMode string = "console"
|
||||||
defaultLoglevel Level = LOGLEVEL_INFO
|
defaultLogFileMaxSize uint32 = 104857600 // 100 MB
|
||||||
|
defaultLogFileMaxDays uint32 = 7 // days
|
||||||
|
|
||||||
|
defaultInMemoryDuplicateCheckerCleanupInterval uint32 = 60 // 1 minutes
|
||||||
|
defaultDuplicateSubmissionsInterval uint32 = 300 // 5 minutes
|
||||||
|
|
||||||
defaultSecretKey string = "ezbookkeeping"
|
defaultSecretKey string = "ezbookkeeping"
|
||||||
defaultTokenExpiredTime uint32 = 604800 // 7 days
|
defaultTokenExpiredTime uint32 = 2592000 // 30 days
|
||||||
defaultTemporaryTokenExpiredTime uint32 = 300 // 5 minutes
|
defaultTokenMinRefreshInterval uint32 = 86400 // 1 day
|
||||||
defaultEmailVerifyTokenExpiredTime uint32 = 3600 // 60 minutes
|
defaultTemporaryTokenExpiredTime uint32 = 300 // 5 minutes
|
||||||
defaultPasswordResetTokenExpiredTime uint32 = 3600 // 60 minutes
|
defaultEmailVerifyTokenExpiredTime uint32 = 3600 // 60 minutes
|
||||||
|
defaultPasswordResetTokenExpiredTime uint32 = 3600 // 60 minutes
|
||||||
|
|
||||||
defaultExchangeRatesDataRequestTimeout uint32 = 10000 // 10 seconds
|
defaultExchangeRatesDataRequestTimeout uint32 = 10000 // 10 seconds
|
||||||
)
|
)
|
||||||
@@ -148,6 +169,25 @@ type SMTPConfig struct {
|
|||||||
FromAddress string
|
FromAddress string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MinIOConfig represents the MinIO setting config
|
||||||
|
type MinIOConfig struct {
|
||||||
|
Endpoint string
|
||||||
|
Location string
|
||||||
|
AccessKeyID string
|
||||||
|
SecretAccessKey string
|
||||||
|
UseSSL bool
|
||||||
|
SkipTLSVerify bool
|
||||||
|
Bucket string
|
||||||
|
RootPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotificationConfig represents a notification setting config
|
||||||
|
type NotificationConfig struct {
|
||||||
|
Enabled bool
|
||||||
|
DefaultContent string
|
||||||
|
MultiLanguageContent map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
// Config represents the global setting config
|
// Config represents the global setting config
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// Global
|
// Global
|
||||||
@@ -187,18 +227,38 @@ type Config struct {
|
|||||||
EnableConsoleLog bool
|
EnableConsoleLog bool
|
||||||
EnableFileLog bool
|
EnableFileLog bool
|
||||||
|
|
||||||
LogLevel Level
|
LogLevel Level
|
||||||
FileLogPath string
|
FileLogPath string
|
||||||
|
RequestFileLogPath string
|
||||||
|
QueryFileLogPath string
|
||||||
|
LogFileRotate bool
|
||||||
|
LogFileMaxSize uint32
|
||||||
|
LogFileMaxDays uint32
|
||||||
|
|
||||||
|
// Storage
|
||||||
|
StorageType string
|
||||||
|
LocalFileSystemPath string
|
||||||
|
MinIOConfig *MinIOConfig
|
||||||
|
|
||||||
// Uuid
|
// Uuid
|
||||||
UuidGeneratorType string
|
UuidGeneratorType string
|
||||||
UuidServerId uint8
|
UuidServerId uint8
|
||||||
|
|
||||||
|
// Duplicate Checker
|
||||||
|
DuplicateCheckerType string
|
||||||
|
InMemoryDuplicateCheckerCleanupInterval uint32
|
||||||
|
InMemoryDuplicateCheckerCleanupIntervalDuration time.Duration
|
||||||
|
EnableDuplicateSubmissionsCheck bool
|
||||||
|
DuplicateSubmissionsInterval uint32
|
||||||
|
DuplicateSubmissionsIntervalDuration time.Duration
|
||||||
|
|
||||||
// Secret
|
// Secret
|
||||||
|
SecretKeyNoSet bool
|
||||||
SecretKey string
|
SecretKey string
|
||||||
EnableTwoFactor bool
|
EnableTwoFactor bool
|
||||||
TokenExpiredTime uint32
|
TokenExpiredTime uint32
|
||||||
TokenExpiredTimeDuration time.Duration
|
TokenExpiredTimeDuration time.Duration
|
||||||
|
TokenMinRefreshInterval uint32
|
||||||
TemporaryTokenExpiredTime uint32
|
TemporaryTokenExpiredTime uint32
|
||||||
TemporaryTokenExpiredTimeDuration time.Duration
|
TemporaryTokenExpiredTimeDuration time.Duration
|
||||||
EmailVerifyTokenExpiredTime uint32
|
EmailVerifyTokenExpiredTime uint32
|
||||||
@@ -218,20 +278,33 @@ type Config struct {
|
|||||||
// Data
|
// Data
|
||||||
EnableDataExport bool
|
EnableDataExport bool
|
||||||
|
|
||||||
|
// Notification
|
||||||
|
AfterRegisterNotification NotificationConfig
|
||||||
|
AfterLoginNotification NotificationConfig
|
||||||
|
AfterOpenNotification NotificationConfig
|
||||||
|
|
||||||
// Map
|
// Map
|
||||||
MapProvider string
|
MapProvider string
|
||||||
TomTomMapAPIKey string
|
EnableMapDataFetchProxy bool
|
||||||
GoogleMapAPIKey string
|
MapProxy string
|
||||||
BaiduMapAK string
|
TomTomMapAPIKey string
|
||||||
AmapApplicationKey string
|
TianDiTuAPIKey string
|
||||||
AmapSecurityVerificationMethod string
|
GoogleMapAPIKey string
|
||||||
AmapApplicationSecret string
|
BaiduMapAK string
|
||||||
AmapApiExternalProxyUrl string
|
AmapApplicationKey string
|
||||||
EnableMapDataFetchProxy bool
|
AmapSecurityVerificationMethod string
|
||||||
|
AmapApplicationSecret string
|
||||||
|
AmapApiExternalProxyUrl string
|
||||||
|
CustomMapTileServerTileLayerUrl string
|
||||||
|
CustomMapTileServerAnnotationLayerUrl string
|
||||||
|
CustomMapTileServerMinZoomLevel uint8
|
||||||
|
CustomMapTileServerMaxZoomLevel uint8
|
||||||
|
CustomMapTileServerDefaultZoomLevel uint8
|
||||||
|
|
||||||
// Exchange Rates
|
// Exchange Rates
|
||||||
ExchangeRatesDataSource string
|
ExchangeRatesDataSource string
|
||||||
ExchangeRatesRequestTimeout uint32
|
ExchangeRatesRequestTimeout uint32
|
||||||
|
ExchangeRatesProxy string
|
||||||
ExchangeRatesSkipTLSVerify bool
|
ExchangeRatesSkipTLSVerify bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,12 +355,24 @@ func LoadConfiguration(configFilePath string) (*Config, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = loadStorageConfiguration(config, cfgFile, "storage")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
err = loadUuidConfiguration(config, cfgFile, "uuid")
|
err = loadUuidConfiguration(config, cfgFile, "uuid")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = loadDuplicateCheckerConfiguration(config, cfgFile, "duplicate_checker")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
err = loadSecurityConfiguration(config, cfgFile, "security")
|
err = loadSecurityConfiguration(config, cfgFile, "security")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -306,6 +391,12 @@ func LoadConfiguration(configFilePath string) (*Config, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = loadNotificationConfiguration(config, cfgFile, "notification")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
err = loadMapConfiguration(config, cfgFile, "map")
|
err = loadMapConfiguration(config, cfgFile, "map")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -341,10 +432,13 @@ func GetDefaultConfigFilePath() (string, error) {
|
|||||||
|
|
||||||
func loadGlobalConfiguration(config *Config, configFile *ini.File, sectionName string) error {
|
func loadGlobalConfiguration(config *Config, configFile *ini.File, sectionName string) error {
|
||||||
config.AppName = getConfigItemStringValue(configFile, sectionName, "app_name", defaultAppName)
|
config.AppName = getConfigItemStringValue(configFile, sectionName, "app_name", defaultAppName)
|
||||||
config.Mode = MODE_PRODUCTION
|
|
||||||
|
|
||||||
if getConfigItemStringValue(configFile, sectionName, "mode") == "development" {
|
if getConfigItemStringValue(configFile, sectionName, "mode") == "production" {
|
||||||
|
config.Mode = MODE_PRODUCTION
|
||||||
|
} else if getConfigItemStringValue(configFile, sectionName, "mode") == "development" {
|
||||||
config.Mode = MODE_DEVELOPMENT
|
config.Mode = MODE_DEVELOPMENT
|
||||||
|
} else {
|
||||||
|
return errs.ErrInvalidServerMode
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -398,6 +492,13 @@ func loadDatabaseConfiguration(config *Config, configFile *ini.File, sectionName
|
|||||||
dbConfig := &DatabaseConfig{}
|
dbConfig := &DatabaseConfig{}
|
||||||
|
|
||||||
dbConfig.DatabaseType = getConfigItemStringValue(configFile, sectionName, "type", MySqlDbType)
|
dbConfig.DatabaseType = getConfigItemStringValue(configFile, sectionName, "type", MySqlDbType)
|
||||||
|
|
||||||
|
if dbConfig.DatabaseType != MySqlDbType &&
|
||||||
|
dbConfig.DatabaseType != PostgresDbType &&
|
||||||
|
dbConfig.DatabaseType != Sqlite3DbType {
|
||||||
|
return errs.ErrDatabaseTypeInvalid
|
||||||
|
}
|
||||||
|
|
||||||
dbConfig.DatabaseHost = getConfigItemStringValue(configFile, sectionName, "host", defaultDatabaseHost)
|
dbConfig.DatabaseHost = getConfigItemStringValue(configFile, sectionName, "host", defaultDatabaseHost)
|
||||||
dbConfig.DatabaseName = getConfigItemStringValue(configFile, sectionName, "name", defaultDatabaseName)
|
dbConfig.DatabaseName = getConfigItemStringValue(configFile, sectionName, "name", defaultDatabaseName)
|
||||||
dbConfig.DatabaseUser = getConfigItemStringValue(configFile, sectionName, "user")
|
dbConfig.DatabaseUser = getConfigItemStringValue(configFile, sectionName, "user")
|
||||||
@@ -455,17 +556,83 @@ func loadLogConfiguration(config *Config, configFile *ini.File, sectionName stri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
config.LogLevel = getLogLevel(getConfigItemStringValue(configFile, sectionName, "level"), defaultLoglevel)
|
var err error
|
||||||
|
config.LogLevel, err = getLogLevel(getConfigItemStringValue(configFile, sectionName, "level"))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.LogLevel != LOGLEVEL_DEBUG &&
|
||||||
|
config.LogLevel != LOGLEVEL_INFO &&
|
||||||
|
config.LogLevel != LOGLEVEL_WARN &&
|
||||||
|
config.LogLevel != LOGLEVEL_ERROR {
|
||||||
|
return errs.ErrInvalidLogLevel
|
||||||
|
}
|
||||||
|
|
||||||
if config.EnableFileLog {
|
if config.EnableFileLog {
|
||||||
fileLogPath := getConfigItemStringValue(configFile, sectionName, "log_path")
|
fileLogPath := getConfigItemStringValue(configFile, sectionName, "log_path")
|
||||||
finalFileLogPath, _ := getFinalPath(config.WorkingPath, fileLogPath)
|
finalFileLogPath, _ := getFinalPath(config.WorkingPath, fileLogPath)
|
||||||
config.FileLogPath = finalFileLogPath
|
config.FileLogPath = finalFileLogPath
|
||||||
|
|
||||||
|
requestFileLogPath := getConfigItemStringValue(configFile, sectionName, "request_log_path")
|
||||||
|
|
||||||
|
if requestFileLogPath != "" {
|
||||||
|
finalRequestFileLogPath, _ := getFinalPath(config.WorkingPath, requestFileLogPath)
|
||||||
|
config.RequestFileLogPath = finalRequestFileLogPath
|
||||||
|
} else {
|
||||||
|
config.RequestFileLogPath = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
queryFileLogPath := getConfigItemStringValue(configFile, sectionName, "query_log_path")
|
||||||
|
|
||||||
|
if queryFileLogPath != "" {
|
||||||
|
finalQueryFileLogPath, _ := getFinalPath(config.WorkingPath, queryFileLogPath)
|
||||||
|
config.QueryFileLogPath = finalQueryFileLogPath
|
||||||
|
} else {
|
||||||
|
config.QueryFileLogPath = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
config.LogFileRotate = getConfigItemBoolValue(configFile, sectionName, "log_file_rotate", false)
|
||||||
|
config.LogFileMaxSize = getConfigItemUint32Value(configFile, sectionName, "log_file_max_size", defaultLogFileMaxSize)
|
||||||
|
config.LogFileMaxDays = getConfigItemUint32Value(configFile, sectionName, "log_file_max_days", defaultLogFileMaxDays)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadStorageConfiguration(config *Config, configFile *ini.File, sectionName string) error {
|
||||||
|
if getConfigItemStringValue(configFile, sectionName, "type") == LocalFileSystemObjectStorageType {
|
||||||
|
config.StorageType = LocalFileSystemObjectStorageType
|
||||||
|
} else if getConfigItemStringValue(configFile, sectionName, "type") == MinIOStorageType {
|
||||||
|
config.StorageType = MinIOStorageType
|
||||||
|
} else {
|
||||||
|
return errs.ErrInvalidStorageType
|
||||||
|
}
|
||||||
|
|
||||||
|
localFileSystemRootPath := getConfigItemStringValue(configFile, sectionName, "local_filesystem_path")
|
||||||
|
finalLocalFileSystemRootPath, err := getFinalPath(config.WorkingPath, localFileSystemRootPath)
|
||||||
|
config.LocalFileSystemPath = finalLocalFileSystemRootPath
|
||||||
|
|
||||||
|
if config.StorageType == LocalFileSystemObjectStorageType && err != nil {
|
||||||
|
return errs.ErrInvalidLocalFileSystemStoragePath
|
||||||
|
}
|
||||||
|
|
||||||
|
minIOConfig := &MinIOConfig{}
|
||||||
|
minIOConfig.Endpoint = getConfigItemStringValue(configFile, sectionName, "minio_endpoint")
|
||||||
|
minIOConfig.Location = getConfigItemStringValue(configFile, sectionName, "minio_location")
|
||||||
|
minIOConfig.AccessKeyID = getConfigItemStringValue(configFile, sectionName, "minio_access_key_id")
|
||||||
|
minIOConfig.SecretAccessKey = getConfigItemStringValue(configFile, sectionName, "minio_secret_access_key")
|
||||||
|
minIOConfig.UseSSL = getConfigItemBoolValue(configFile, sectionName, "minio_use_ssl", false)
|
||||||
|
minIOConfig.SkipTLSVerify = getConfigItemBoolValue(configFile, sectionName, "minio_skip_tls_verify", false)
|
||||||
|
minIOConfig.Bucket = getConfigItemStringValue(configFile, sectionName, "minio_bucket")
|
||||||
|
minIOConfig.RootPath = getConfigItemStringValue(configFile, sectionName, "minio_root_path")
|
||||||
|
|
||||||
|
config.MinIOConfig = minIOConfig
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func loadUuidConfiguration(config *Config, configFile *ini.File, sectionName string) error {
|
func loadUuidConfiguration(config *Config, configFile *ini.File, sectionName string) error {
|
||||||
if getConfigItemStringValue(configFile, sectionName, "generator_type") == InternalUuidGeneratorType {
|
if getConfigItemStringValue(configFile, sectionName, "generator_type") == InternalUuidGeneratorType {
|
||||||
config.UuidGeneratorType = InternalUuidGeneratorType
|
config.UuidGeneratorType = InternalUuidGeneratorType
|
||||||
@@ -478,20 +645,76 @@ func loadUuidConfiguration(config *Config, configFile *ini.File, sectionName str
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadDuplicateCheckerConfiguration(config *Config, configFile *ini.File, sectionName string) error {
|
||||||
|
if getConfigItemStringValue(configFile, sectionName, "checker_type") == InMemoryDuplicateCheckerType {
|
||||||
|
config.DuplicateCheckerType = InMemoryDuplicateCheckerType
|
||||||
|
} else {
|
||||||
|
return errs.ErrInvalidDuplicateCheckerType
|
||||||
|
}
|
||||||
|
|
||||||
|
config.InMemoryDuplicateCheckerCleanupInterval = getConfigItemUint32Value(configFile, sectionName, "cleanup_interval", defaultInMemoryDuplicateCheckerCleanupInterval)
|
||||||
|
|
||||||
|
if config.InMemoryDuplicateCheckerCleanupInterval < 1 {
|
||||||
|
return errs.ErrInvalidInMemoryDuplicateCheckerCleanupInterval
|
||||||
|
}
|
||||||
|
|
||||||
|
config.InMemoryDuplicateCheckerCleanupIntervalDuration = time.Duration(config.InMemoryDuplicateCheckerCleanupInterval) * time.Second
|
||||||
|
|
||||||
|
duplicateSubmissionsInterval := getConfigItemUint32Value(configFile, sectionName, "duplicate_submissions_interval", defaultDuplicateSubmissionsInterval)
|
||||||
|
|
||||||
|
config.EnableDuplicateSubmissionsCheck = duplicateSubmissionsInterval > 0
|
||||||
|
|
||||||
|
if duplicateSubmissionsInterval < 1 {
|
||||||
|
duplicateSubmissionsInterval = defaultDuplicateSubmissionsInterval
|
||||||
|
}
|
||||||
|
|
||||||
|
config.DuplicateSubmissionsInterval = duplicateSubmissionsInterval
|
||||||
|
config.DuplicateSubmissionsIntervalDuration = time.Duration(config.DuplicateSubmissionsInterval) * time.Second
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func loadSecurityConfiguration(config *Config, configFile *ini.File, sectionName string) error {
|
func loadSecurityConfiguration(config *Config, configFile *ini.File, sectionName string) error {
|
||||||
|
config.SecretKeyNoSet = !getConfigItemIsSet(configFile, sectionName, "secret_key")
|
||||||
config.SecretKey = getConfigItemStringValue(configFile, sectionName, "secret_key", defaultSecretKey)
|
config.SecretKey = getConfigItemStringValue(configFile, sectionName, "secret_key", defaultSecretKey)
|
||||||
config.EnableTwoFactor = getConfigItemBoolValue(configFile, sectionName, "enable_two_factor", true)
|
config.EnableTwoFactor = getConfigItemBoolValue(configFile, sectionName, "enable_two_factor", true)
|
||||||
|
|
||||||
config.TokenExpiredTime = getConfigItemUint32Value(configFile, sectionName, "token_expired_time", defaultTokenExpiredTime)
|
config.TokenExpiredTime = getConfigItemUint32Value(configFile, sectionName, "token_expired_time", defaultTokenExpiredTime)
|
||||||
|
|
||||||
|
if config.TokenExpiredTime < 60 {
|
||||||
|
return errs.ErrInvalidTokenExpiredTime
|
||||||
|
}
|
||||||
|
|
||||||
config.TokenExpiredTimeDuration = time.Duration(config.TokenExpiredTime) * time.Second
|
config.TokenExpiredTimeDuration = time.Duration(config.TokenExpiredTime) * time.Second
|
||||||
|
|
||||||
|
config.TokenMinRefreshInterval = getConfigItemUint32Value(configFile, sectionName, "token_min_refresh_interval", defaultTokenMinRefreshInterval)
|
||||||
|
|
||||||
|
if config.TokenMinRefreshInterval >= config.TokenExpiredTime {
|
||||||
|
return errs.ErrInvalidTokenMinRefreshInterval
|
||||||
|
}
|
||||||
|
|
||||||
config.TemporaryTokenExpiredTime = getConfigItemUint32Value(configFile, sectionName, "temporary_token_expired_time", defaultTemporaryTokenExpiredTime)
|
config.TemporaryTokenExpiredTime = getConfigItemUint32Value(configFile, sectionName, "temporary_token_expired_time", defaultTemporaryTokenExpiredTime)
|
||||||
|
|
||||||
|
if config.TemporaryTokenExpiredTime < 60 {
|
||||||
|
return errs.ErrInvalidTemporaryTokenExpiredTime
|
||||||
|
}
|
||||||
|
|
||||||
config.TemporaryTokenExpiredTimeDuration = time.Duration(config.TemporaryTokenExpiredTime) * time.Second
|
config.TemporaryTokenExpiredTimeDuration = time.Duration(config.TemporaryTokenExpiredTime) * time.Second
|
||||||
|
|
||||||
config.EmailVerifyTokenExpiredTime = getConfigItemUint32Value(configFile, sectionName, "email_verify_token_expired_time", defaultEmailVerifyTokenExpiredTime)
|
config.EmailVerifyTokenExpiredTime = getConfigItemUint32Value(configFile, sectionName, "email_verify_token_expired_time", defaultEmailVerifyTokenExpiredTime)
|
||||||
|
|
||||||
|
if config.EmailVerifyTokenExpiredTime < 60 {
|
||||||
|
return errs.ErrInvalidEmailVerifyTokenExpiredTime
|
||||||
|
}
|
||||||
|
|
||||||
config.EmailVerifyTokenExpiredTimeDuration = time.Duration(config.EmailVerifyTokenExpiredTime) * time.Second
|
config.EmailVerifyTokenExpiredTimeDuration = time.Duration(config.EmailVerifyTokenExpiredTime) * time.Second
|
||||||
|
|
||||||
config.PasswordResetTokenExpiredTime = getConfigItemUint32Value(configFile, sectionName, "password_reset_token_expired_time", defaultPasswordResetTokenExpiredTime)
|
config.PasswordResetTokenExpiredTime = getConfigItemUint32Value(configFile, sectionName, "password_reset_token_expired_time", defaultPasswordResetTokenExpiredTime)
|
||||||
|
|
||||||
|
if config.PasswordResetTokenExpiredTime < 60 {
|
||||||
|
return errs.ErrInvalidPasswordResetTokenExpiredTime
|
||||||
|
}
|
||||||
|
|
||||||
config.PasswordResetTokenExpiredTimeDuration = time.Duration(config.PasswordResetTokenExpiredTime) * time.Second
|
config.PasswordResetTokenExpiredTimeDuration = time.Duration(config.PasswordResetTokenExpiredTime) * time.Second
|
||||||
|
|
||||||
config.EnableRequestIdHeader = getConfigItemBoolValue(configFile, sectionName, "request_id_header", true)
|
config.EnableRequestIdHeader = getConfigItemBoolValue(configFile, sectionName, "request_id_header", true)
|
||||||
@@ -506,10 +729,14 @@ func loadUserConfiguration(config *Config, configFile *ini.File, sectionName str
|
|||||||
config.EnableUserForgetPassword = getConfigItemBoolValue(configFile, sectionName, "enable_forget_password", false)
|
config.EnableUserForgetPassword = getConfigItemBoolValue(configFile, sectionName, "enable_forget_password", false)
|
||||||
config.ForgetPasswordRequireVerifyEmail = getConfigItemBoolValue(configFile, sectionName, "forget_password_require_email_verify", false)
|
config.ForgetPasswordRequireVerifyEmail = getConfigItemBoolValue(configFile, sectionName, "forget_password_require_email_verify", false)
|
||||||
|
|
||||||
if getConfigItemStringValue(configFile, sectionName, "avatar_provider") == "" {
|
if getConfigItemStringValue(configFile, sectionName, "avatar_provider") == InternalAvatarProvider {
|
||||||
config.AvatarProvider = ""
|
config.AvatarProvider = InternalAvatarProvider
|
||||||
} else if getConfigItemStringValue(configFile, sectionName, "avatar_provider") == GravatarProvider {
|
} else if getConfigItemStringValue(configFile, sectionName, "avatar_provider") == GravatarProvider {
|
||||||
config.AvatarProvider = GravatarProvider
|
config.AvatarProvider = GravatarProvider
|
||||||
|
} else if getConfigItemStringValue(configFile, sectionName, "avatar_provider") == "" {
|
||||||
|
config.AvatarProvider = ""
|
||||||
|
} else {
|
||||||
|
return errs.ErrInvalidAvatarProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -521,6 +748,14 @@ func loadDataConfiguration(config *Config, configFile *ini.File, sectionName str
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadNotificationConfiguration(config *Config, configFile *ini.File, sectionName string) error {
|
||||||
|
config.AfterRegisterNotification = getNotificationConfiguration(configFile, sectionName, "enable_notification_after_register", "after_register_notification_content")
|
||||||
|
config.AfterLoginNotification = getNotificationConfiguration(configFile, sectionName, "enable_notification_after_login", "after_login_notification_content")
|
||||||
|
config.AfterOpenNotification = getNotificationConfiguration(configFile, sectionName, "enable_notification_after_open", "after_open_notification_content")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func loadMapConfiguration(config *Config, configFile *ini.File, sectionName string) error {
|
func loadMapConfiguration(config *Config, configFile *ini.File, sectionName string) error {
|
||||||
mapProvider := getConfigItemStringValue(configFile, sectionName, "map_provider")
|
mapProvider := getConfigItemStringValue(configFile, sectionName, "map_provider")
|
||||||
|
|
||||||
@@ -536,20 +771,28 @@ func loadMapConfiguration(config *Config, configFile *ini.File, sectionName stri
|
|||||||
config.MapProvider = OPNVKarteMapProvider
|
config.MapProvider = OPNVKarteMapProvider
|
||||||
} else if mapProvider == CyclOSMMapProvider {
|
} else if mapProvider == CyclOSMMapProvider {
|
||||||
config.MapProvider = CyclOSMMapProvider
|
config.MapProvider = CyclOSMMapProvider
|
||||||
} else if mapProvider == GoogleMapProvider {
|
} else if mapProvider == CartoDBMapProvider {
|
||||||
config.MapProvider = GoogleMapProvider
|
config.MapProvider = CartoDBMapProvider
|
||||||
} else if mapProvider == TomTomMapProvider {
|
} else if mapProvider == TomTomMapProvider {
|
||||||
config.MapProvider = TomTomMapProvider
|
config.MapProvider = TomTomMapProvider
|
||||||
|
} else if mapProvider == TianDiTuProvider {
|
||||||
|
config.MapProvider = TianDiTuProvider
|
||||||
|
} else if mapProvider == GoogleMapProvider {
|
||||||
|
config.MapProvider = GoogleMapProvider
|
||||||
} else if mapProvider == BaiduMapProvider {
|
} else if mapProvider == BaiduMapProvider {
|
||||||
config.MapProvider = BaiduMapProvider
|
config.MapProvider = BaiduMapProvider
|
||||||
} else if mapProvider == AmapProvider {
|
} else if mapProvider == AmapProvider {
|
||||||
config.MapProvider = AmapProvider
|
config.MapProvider = AmapProvider
|
||||||
|
} else if mapProvider == CustomProvider {
|
||||||
|
config.MapProvider = CustomProvider
|
||||||
} else {
|
} else {
|
||||||
return errs.ErrInvalidMapProvider
|
return errs.ErrInvalidMapProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
config.EnableMapDataFetchProxy = getConfigItemBoolValue(configFile, sectionName, "map_data_fetch_proxy", false)
|
config.EnableMapDataFetchProxy = getConfigItemBoolValue(configFile, sectionName, "map_data_fetch_proxy", false)
|
||||||
|
config.MapProxy = getConfigItemStringValue(configFile, sectionName, "proxy", "system")
|
||||||
config.TomTomMapAPIKey = getConfigItemStringValue(configFile, sectionName, "tomtom_map_api_key")
|
config.TomTomMapAPIKey = getConfigItemStringValue(configFile, sectionName, "tomtom_map_api_key")
|
||||||
|
config.TianDiTuAPIKey = getConfigItemStringValue(configFile, sectionName, "tianditu_map_app_key")
|
||||||
config.GoogleMapAPIKey = getConfigItemStringValue(configFile, sectionName, "google_map_api_key")
|
config.GoogleMapAPIKey = getConfigItemStringValue(configFile, sectionName, "google_map_api_key")
|
||||||
config.BaiduMapAK = getConfigItemStringValue(configFile, sectionName, "baidu_map_ak")
|
config.BaiduMapAK = getConfigItemStringValue(configFile, sectionName, "baidu_map_ak")
|
||||||
config.AmapApplicationKey = getConfigItemStringValue(configFile, sectionName, "amap_application_key")
|
config.AmapApplicationKey = getConfigItemStringValue(configFile, sectionName, "amap_application_key")
|
||||||
@@ -569,6 +812,12 @@ func loadMapConfiguration(config *Config, configFile *ini.File, sectionName stri
|
|||||||
config.AmapApplicationSecret = getConfigItemStringValue(configFile, sectionName, "amap_application_secret")
|
config.AmapApplicationSecret = getConfigItemStringValue(configFile, sectionName, "amap_application_secret")
|
||||||
config.AmapApiExternalProxyUrl = getConfigItemStringValue(configFile, sectionName, "amap_api_external_proxy_url")
|
config.AmapApiExternalProxyUrl = getConfigItemStringValue(configFile, sectionName, "amap_api_external_proxy_url")
|
||||||
|
|
||||||
|
config.CustomMapTileServerTileLayerUrl = getConfigItemStringValue(configFile, sectionName, "custom_map_tile_server_url")
|
||||||
|
config.CustomMapTileServerAnnotationLayerUrl = getConfigItemStringValue(configFile, sectionName, "custom_map_tile_server_annotation_url")
|
||||||
|
config.CustomMapTileServerMinZoomLevel = getConfigItemUint8Value(configFile, sectionName, "custom_map_tile_server_min_zoom_level", 1)
|
||||||
|
config.CustomMapTileServerMaxZoomLevel = getConfigItemUint8Value(configFile, sectionName, "custom_map_tile_server_max_zoom_level", 18)
|
||||||
|
config.CustomMapTileServerDefaultZoomLevel = getConfigItemUint8Value(configFile, sectionName, "custom_map_tile_server_default_zoom_level", 14)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func loadExchangeRatesConfiguration(config *Config, configFile *ini.File, sectionName string) error {
|
func loadExchangeRatesConfiguration(config *Config, configFile *ini.File, sectionName string) error {
|
||||||
@@ -590,6 +839,7 @@ func loadExchangeRatesConfiguration(config *Config, configFile *ini.File, sectio
|
|||||||
return errs.ErrInvalidExchangeRatesDataSource
|
return errs.ErrInvalidExchangeRatesDataSource
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config.ExchangeRatesProxy = getConfigItemStringValue(configFile, sectionName, "proxy", "system")
|
||||||
config.ExchangeRatesRequestTimeout = getConfigItemUint32Value(configFile, sectionName, "request_timeout", defaultExchangeRatesDataRequestTimeout)
|
config.ExchangeRatesRequestTimeout = getConfigItemUint32Value(configFile, sectionName, "request_timeout", defaultExchangeRatesDataRequestTimeout)
|
||||||
config.ExchangeRatesSkipTLSVerify = getConfigItemBoolValue(configFile, sectionName, "skip_tls_verify", false)
|
config.ExchangeRatesSkipTLSVerify = getConfigItemBoolValue(configFile, sectionName, "skip_tls_verify", false)
|
||||||
|
|
||||||
@@ -626,6 +876,44 @@ func getFinalPath(workingPath, p string) (string, error) {
|
|||||||
return p, err
|
return p, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getNotificationConfiguration(configFile *ini.File, sectionName string, enableKey string, contentKey string) NotificationConfig {
|
||||||
|
config := NotificationConfig{
|
||||||
|
Enabled: getConfigItemBoolValue(configFile, sectionName, enableKey, false),
|
||||||
|
DefaultContent: getConfigItemStringValue(configFile, sectionName, contentKey, ""),
|
||||||
|
MultiLanguageContent: make(map[string]string),
|
||||||
|
}
|
||||||
|
|
||||||
|
for languageTag := range locales.AllLanguages {
|
||||||
|
multiLanguageContentKey := strings.ToLower(languageTag)
|
||||||
|
multiLanguageContentKey = strings.Replace(multiLanguageContentKey, "-", "_", -1)
|
||||||
|
multiLanguageContentKey = contentKey + "_" + multiLanguageContentKey
|
||||||
|
content := getConfigItemStringValue(configFile, sectionName, multiLanguageContentKey, "")
|
||||||
|
|
||||||
|
if content != "" {
|
||||||
|
config.MultiLanguageContent[languageTag] = content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
func getConfigItemIsSet(configFile *ini.File, sectionName string, itemName string) bool {
|
||||||
|
environmentKey := getEnvironmentKey(sectionName, itemName)
|
||||||
|
environmentValue := os.Getenv(environmentKey)
|
||||||
|
|
||||||
|
if len(environmentValue) > 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
section := configFile.Section(sectionName)
|
||||||
|
|
||||||
|
if !section.HasKey(itemName) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return section.Key(itemName).String() != ""
|
||||||
|
}
|
||||||
|
|
||||||
func getConfigItemStringValue(configFile *ini.File, sectionName string, itemName string, defaultValue ...string) string {
|
func getConfigItemStringValue(configFile *ini.File, sectionName string, itemName string, defaultValue ...string) string {
|
||||||
environmentKey := getEnvironmentKey(sectionName, itemName)
|
environmentKey := getEnvironmentKey(sectionName, itemName)
|
||||||
environmentValue := os.Getenv(environmentKey)
|
environmentValue := os.Getenv(environmentKey)
|
||||||
@@ -729,14 +1017,16 @@ func getEnvironmentKey(sectionName string, itemName string) string {
|
|||||||
return fmt.Sprintf("%s_%s_%s", ebkEnvNamePrefix, strings.ToUpper(sectionName), strings.ToUpper(itemName))
|
return fmt.Sprintf("%s_%s_%s", ebkEnvNamePrefix, strings.ToUpper(sectionName), strings.ToUpper(itemName))
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLogLevel(logLevelStr string, defaultLogLevel Level) Level {
|
func getLogLevel(logLevelStr string) (Level, error) {
|
||||||
if logLevelStr == "debug" {
|
if logLevelStr == "debug" {
|
||||||
return LOGLEVEL_DEBUG
|
return LOGLEVEL_DEBUG, nil
|
||||||
|
} else if logLevelStr == "info" {
|
||||||
|
return LOGLEVEL_INFO, nil
|
||||||
} else if logLevelStr == "warn" {
|
} else if logLevelStr == "warn" {
|
||||||
return LOGLEVEL_WARN
|
return LOGLEVEL_WARN, nil
|
||||||
} else if logLevelStr == "error" {
|
} else if logLevelStr == "error" {
|
||||||
return LOGLEVEL_ERROR
|
return LOGLEVEL_ERROR, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return defaultLogLevel
|
return "", errs.ErrInvalidLogLevel
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,3 +16,60 @@ var (
|
|||||||
func SetCurrentConfig(config *Config) {
|
func SetCurrentConfig(config *Config) {
|
||||||
Container.Current = config
|
Container.Current = config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAfterRegisterNotificationContent returns the notification content displayed each time users register
|
||||||
|
func (c *ConfigContainer) GetAfterRegisterNotificationContent(userLanguage string, clientLanguage string) string {
|
||||||
|
language := userLanguage
|
||||||
|
|
||||||
|
if language == "" {
|
||||||
|
language = clientLanguage
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.Current.AfterRegisterNotification.Enabled {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if multiLanguageContent, exists := c.Current.AfterRegisterNotification.MultiLanguageContent[language]; exists {
|
||||||
|
return multiLanguageContent
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Current.AfterRegisterNotification.DefaultContent
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAfterLoginNotificationContent returns the notification content displayed each time users log in
|
||||||
|
func (c *ConfigContainer) GetAfterLoginNotificationContent(userLanguage string, clientLanguage string) string {
|
||||||
|
language := userLanguage
|
||||||
|
|
||||||
|
if language == "" {
|
||||||
|
language = clientLanguage
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.Current.AfterLoginNotification.Enabled {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if multiLanguageContent, exists := c.Current.AfterLoginNotification.MultiLanguageContent[language]; exists {
|
||||||
|
return multiLanguageContent
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Current.AfterLoginNotification.DefaultContent
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAfterOpenNotificationContent returns the notification content displayed each time users open the app
|
||||||
|
func (c *ConfigContainer) GetAfterOpenNotificationContent(userLanguage string, clientLanguage string) string {
|
||||||
|
language := userLanguage
|
||||||
|
|
||||||
|
if language == "" {
|
||||||
|
language = clientLanguage
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.Current.AfterOpenNotification.Enabled {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if multiLanguageContent, exists := c.Current.AfterOpenNotification.MultiLanguageContent[language]; exists {
|
||||||
|
return multiLanguageContent
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Current.AfterOpenNotification.DefaultContent
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LocalFileSystemObjectStorage represents local file system object storage
|
||||||
|
type LocalFileSystemObjectStorage struct {
|
||||||
|
rootPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLocalFileSystemObjectStorage returns a local file system object storage
|
||||||
|
func NewLocalFileSystemObjectStorage(config *settings.Config, pathPrefix string) (*LocalFileSystemObjectStorage, error) {
|
||||||
|
storage := &LocalFileSystemObjectStorage{
|
||||||
|
rootPath: filepath.Join(config.LocalFileSystemPath, pathPrefix),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(storage.rootPath, os.ModePerm); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return storage, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exists returns whether the file exists
|
||||||
|
func (s *LocalFileSystemObjectStorage) Exists(path string) (bool, error) {
|
||||||
|
return utils.IsExists(s.getFinalPath(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read returns the object instance according to specified the file path
|
||||||
|
func (s *LocalFileSystemObjectStorage) Read(path string) (ObjectInStorage, error) {
|
||||||
|
return os.Open(s.getFinalPath(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save returns whether save the object instance successfully
|
||||||
|
func (s *LocalFileSystemObjectStorage) Save(path string, object ObjectInStorage) error {
|
||||||
|
finalPath := s.getFinalPath(path)
|
||||||
|
|
||||||
|
if err := os.MkdirAll(filepath.Dir(finalPath), os.ModePerm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
targetFile, err := os.Create(finalPath)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer targetFile.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(targetFile, object)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete returns whether delete the object according to specified the file path successfully
|
||||||
|
func (s *LocalFileSystemObjectStorage) Delete(path string) error {
|
||||||
|
return os.Remove(s.getFinalPath(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LocalFileSystemObjectStorage) getFinalPath(path string) string {
|
||||||
|
return filepath.Join(s.rootPath, path)
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/minio/minio-go/v7"
|
||||||
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
|
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MinIOObjectStorage represents MinIO object storage
|
||||||
|
type MinIOObjectStorage struct {
|
||||||
|
minIOClient *minio.Client
|
||||||
|
minIOConfig *settings.MinIOConfig
|
||||||
|
rootPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMinIOObjectStorage returns a MinIO object storage
|
||||||
|
func NewMinIOObjectStorage(config *settings.Config, pathPrefix string) (*MinIOObjectStorage, error) {
|
||||||
|
minIOConfig := config.MinIOConfig
|
||||||
|
|
||||||
|
minIOClient, err := minio.New(minIOConfig.Endpoint, &minio.Options{
|
||||||
|
Region: minIOConfig.Location,
|
||||||
|
Creds: credentials.NewStaticV4(minIOConfig.AccessKeyID, minIOConfig.SecretAccessKey, ""),
|
||||||
|
Secure: minIOConfig.UseSSL,
|
||||||
|
Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: minIOConfig.SkipTLSVerify}},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
storage := &MinIOObjectStorage{
|
||||||
|
minIOClient: minIOClient,
|
||||||
|
minIOConfig: minIOConfig,
|
||||||
|
rootPath: minIOConfig.RootPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
storage.rootPath = storage.getFinalPath(pathPrefix)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
exists, err := minIOClient.BucketExists(ctx, minIOConfig.Bucket)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
err := minIOClient.MakeBucket(ctx, minIOConfig.Bucket, minio.MakeBucketOptions{
|
||||||
|
Region: minIOConfig.Location,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return storage, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exists returns whether the file exists
|
||||||
|
func (s *MinIOObjectStorage) Exists(path string) (bool, error) {
|
||||||
|
ctx := context.Background()
|
||||||
|
objectInfo, err := s.minIOClient.StatObject(ctx, s.minIOConfig.Bucket, s.getFinalPath(path), minio.StatObjectOptions{})
|
||||||
|
|
||||||
|
if err == nil && !objectInfo.IsDeleteMarker {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read returns the object instance according to specified the file path
|
||||||
|
func (s *MinIOObjectStorage) Read(path string) (ObjectInStorage, error) {
|
||||||
|
ctx := context.Background()
|
||||||
|
return s.minIOClient.GetObject(ctx, s.minIOConfig.Bucket, s.getFinalPath(path), minio.GetObjectOptions{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save returns whether save the object instance successfully
|
||||||
|
func (s *MinIOObjectStorage) Save(path string, object ObjectInStorage) error {
|
||||||
|
ctx := context.Background()
|
||||||
|
_, err := s.minIOClient.PutObject(ctx, s.minIOConfig.Bucket, s.getFinalPath(path), object, -1, minio.PutObjectOptions{})
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete returns whether delete the object according to specified the file path successfully
|
||||||
|
func (s *MinIOObjectStorage) Delete(path string) error {
|
||||||
|
ctx := context.Background()
|
||||||
|
return s.minIOClient.RemoveObject(ctx, s.minIOConfig.Bucket, s.getFinalPath(path), minio.RemoveObjectOptions{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MinIOObjectStorage) getFinalPath(path string) string {
|
||||||
|
rootPath := s.rootPath
|
||||||
|
|
||||||
|
if len(rootPath) < 1 || rootPath[len(rootPath)-1] != '/' {
|
||||||
|
rootPath = rootPath + "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(path) > 0 && path[0] == '/' {
|
||||||
|
path = path[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return rootPath + path
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ObjectInStorage represents the object instance in the storage
|
||||||
|
type ObjectInStorage interface {
|
||||||
|
io.ReadCloser
|
||||||
|
io.Seeker
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
// ObjectStorage represents an object storage to store file object
|
||||||
|
type ObjectStorage interface {
|
||||||
|
Exists(path string) (bool, error)
|
||||||
|
Read(path string) (ObjectInStorage, error)
|
||||||
|
Save(path string, object ObjectInStorage) error
|
||||||
|
Delete(path string) error
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const avatarPathPrefix = "avatar"
|
||||||
|
|
||||||
|
// StorageContainer contains the current object storage
|
||||||
|
type StorageContainer struct {
|
||||||
|
AvatarCurrentStorage ObjectStorage
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize a object storage container singleton instance
|
||||||
|
var (
|
||||||
|
Container = &StorageContainer{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// InitializeStorageContainer initializes the current object storage according to the config
|
||||||
|
func InitializeStorageContainer(config *settings.Config) error {
|
||||||
|
if config.StorageType == settings.LocalFileSystemObjectStorageType {
|
||||||
|
avatarStorage, err := NewLocalFileSystemObjectStorage(config, avatarPathPrefix)
|
||||||
|
Container.AvatarCurrentStorage = avatarStorage
|
||||||
|
|
||||||
|
return err
|
||||||
|
} else if config.StorageType == settings.MinIOStorageType {
|
||||||
|
avatarStorage, err := NewMinIOObjectStorage(config, avatarPathPrefix)
|
||||||
|
Container.AvatarCurrentStorage = avatarStorage
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs.ErrInvalidStorageType
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExistsAvatar returns whether the user avatar exists from the current object storage
|
||||||
|
func (s *StorageContainer) ExistsAvatar(uid int64, fileExtension string) (bool, error) {
|
||||||
|
return s.AvatarCurrentStorage.Exists(s.getUserAvatarPath(uid, fileExtension))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadAvatar returns the user avatar from the current object storage
|
||||||
|
func (s *StorageContainer) ReadAvatar(uid int64, fileExtension string) (ObjectInStorage, error) {
|
||||||
|
return s.AvatarCurrentStorage.Read(s.getUserAvatarPath(uid, fileExtension))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveAvatar returns whether save the user avatar into the current object storage successfully
|
||||||
|
func (s *StorageContainer) SaveAvatar(uid int64, object ObjectInStorage, fileExtension string) error {
|
||||||
|
return s.AvatarCurrentStorage.Save(s.getUserAvatarPath(uid, fileExtension), object)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAvatar returns whether delete the user avatar from the current object storage successfully
|
||||||
|
func (s *StorageContainer) DeleteAvatar(uid int64, fileExtension string) error {
|
||||||
|
return s.AvatarCurrentStorage.Delete(s.getUserAvatarPath(uid, fileExtension))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StorageContainer) getUserAvatarPath(uid int64, fileExtension string) string {
|
||||||
|
return fmt.Sprintf("%d.%s", uid, fileExtension)
|
||||||
|
}
|
||||||
@@ -110,6 +110,8 @@ func getValidationErrorText(err validator.FieldError) string {
|
|||||||
return errs.GetParameterInvalidCurrencyMessage(fieldName)
|
return errs.GetParameterInvalidCurrencyMessage(fieldName)
|
||||||
case "validHexRGBColor":
|
case "validHexRGBColor":
|
||||||
return errs.GetParameterInvalidHexRGBColorMessage(fieldName)
|
return errs.GetParameterInvalidHexRGBColorMessage(fieldName)
|
||||||
|
case "validAmountFilter":
|
||||||
|
return errs.GetParameterInvalidAmountFilterMessage(fieldName)
|
||||||
}
|
}
|
||||||
|
|
||||||
return errs.GetParameterInvalidMessage(fieldName)
|
return errs.GetParameterInvalidMessage(fieldName)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user