Compare commits
441 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 55d06640b5 | |||
| 3874a8da21 | |||
| 6a3ecd5b09 | |||
| afcd4f7262 | |||
| b0ae731fa6 | |||
| 0b678fe69a | |||
| 4cecc78a74 | |||
| 92273d2fc6 | |||
| 04ec749c3c | |||
| 165377816c | |||
| 729904e1c3 | |||
| 6bc4fa0a82 | |||
| f12403672b | |||
| 97daff8d4f | |||
| a32451fd7f | |||
| ca14770971 | |||
| 35ac0695c7 | |||
| 589b614a53 | |||
| 64ea3e05d8 | |||
| 5d0e115438 | |||
| 9f35c1eded | |||
| ff07346fe9 | |||
| 22fffc2f8c | |||
| 205363dd42 | |||
| d2297b882f | |||
| 48bf8dbc5b | |||
| 9585c760d5 | |||
| 8c2cd0aa4d | |||
| 2e680b04c9 | |||
| e2b81f7b57 | |||
| c38b277887 | |||
| a1f6304b22 | |||
| 09d7f56efc | |||
| 9221f3fc96 | |||
| 6b30a0aebc | |||
| 158563f387 | |||
| b0fc5752e2 | |||
| 28903615ed | |||
| caa83c0432 | |||
| 045f4a42db | |||
| 3275bc9cae | |||
| 6d14eaefe1 | |||
| 03274725be | |||
| 0951006063 | |||
| 616bfc6a2a | |||
| c0bfe429ee | |||
| c1d90485a1 | |||
| 6c30527684 | |||
| 4ac751f492 | |||
| de7b137257 | |||
| 0bf689fa8d | |||
| f31ef1649f | |||
| c66bc62c41 | |||
| 9ee59215c8 | |||
| a991adecaf | |||
| 601b1d5c89 | |||
| 8b593883a7 | |||
| bb549c9a89 | |||
| 9f2005622a | |||
| 4238bde13a | |||
| 2d4ce1aac0 | |||
| 37269abde2 | |||
| 54038eabd4 | |||
| 57922e3071 | |||
| ad36dfd448 | |||
| 6e334d2efb | |||
| d3dc1401fd | |||
| 0f59c9911d | |||
| 0ebfd2bc76 | |||
| 734625c1e3 | |||
| eaaea8a64f | |||
| c026ab1777 | |||
| 2c7193efea | |||
| 21f5ef469b | |||
| e7f9eb6e06 | |||
| 9db9c382ab | |||
| b2ba626cde | |||
| 85d93c4f4b | |||
| 09f6dd8d82 | |||
| 2aa6df48c6 | |||
| 2a16260b05 | |||
| c28158b041 | |||
| 661199850c | |||
| 67b69a45cc | |||
| 579322578b | |||
| 0de65042b0 | |||
| f9643f8651 | |||
| fc9ac4c40e | |||
| 84843066f2 | |||
| af3d5c4654 | |||
| 676f3daf50 | |||
| 54970015cb | |||
| 2b49430530 | |||
| feea5f3518 | |||
| 04e580b40f | |||
| f07a3ba97d | |||
| f44e3a81ab | |||
| 77d3bd019e | |||
| 72725d5ab4 | |||
| aa717ed1fe | |||
| 292b49ba79 | |||
| c600eb5d5a | |||
| 6d2f788fa2 | |||
| b8acff3e7c | |||
| b8bdb03fc0 | |||
| 7257abefb4 | |||
| 3095613a76 | |||
| db12b64b3a | |||
| a081edde25 | |||
| 5496c4a10a | |||
| c968ded99a | |||
| 4224a833b4 | |||
| df470f1a5e | |||
| ed0100a82c | |||
| 0ad72e8334 | |||
| 50ee5d1f49 | |||
| 94283a8da2 | |||
| 86e9a3e838 | |||
| d77b9ef7c9 | |||
| e29afa3155 | |||
| 7376fbe7a1 | |||
| 04e98e1c39 | |||
| ddca6e7ec9 | |||
| 1ac12ac668 | |||
| 399936e3ab | |||
| 3d086992dc | |||
| 62ded1290c | |||
| 33cbdfbd13 | |||
| f5ce6ed4bc | |||
| eb9a2ac2e0 | |||
| 8bed529d05 | |||
| 41a8b8007a | |||
| 141dc843f3 | |||
| 902825404e | |||
| 749eaaab30 | |||
| 8f5767b992 | |||
| 715f0c5853 | |||
| a960fd3d56 | |||
| 17b8ac6d0b | |||
| 4ac78fe4d1 | |||
| 957bcf790f | |||
| 66f0b38008 | |||
| f6a2246aab | |||
| fa3e941069 | |||
| 9bb049f746 | |||
| c41f2a4d65 | |||
| 06ff9f2499 | |||
| f91f9fcc94 | |||
| 9a626b0d4f | |||
| ac2adaf4ba | |||
| 3ab198615b | |||
| 7c2831098c | |||
| 2454e22ea2 | |||
| b1fb40ca61 | |||
| e403e938c3 | |||
| 952ba1f1ea | |||
| a25690c2d3 | |||
| 6b6b9c61d7 | |||
| fcc5e522a7 | |||
| c33c0487cf | |||
| 1753a6c247 | |||
| 195f513416 | |||
| c6d38bb3d7 | |||
| 9b2fba9600 | |||
| c88f6501fa | |||
| c511346160 | |||
| 0b02daac1d | |||
| 2390c085e4 | |||
| 698d94feed | |||
| a9a6d39127 | |||
| 395bd31898 | |||
| 7e24492ce8 | |||
| 19d3d80013 | |||
| 8c7875d7ea | |||
| 110ce0d4c6 | |||
| ff8c57fdab | |||
| 54ccdc57bf | |||
| 2463f06ba1 | |||
| 88d5b1f98f | |||
| 43522e9c81 | |||
| 6e798f739f | |||
| 5b5f1280af | |||
| 2188e8dd78 | |||
| 8659e9ea37 | |||
| 4cff481d61 | |||
| 6da910d8fb | |||
| cc08ad46e3 | |||
| 5b52c1adbb | |||
| a20958a89b | |||
| dea36d4b80 | |||
| 6cb7e4caf7 | |||
| 6e41668b25 | |||
| d3e1acddc5 | |||
| a9c511eb2e | |||
| ffef33a9fc | |||
| 982917ddbb | |||
| 07406a50bb | |||
| 78f325e127 | |||
| e1bb97a1db | |||
| 831952806d | |||
| 4c57b7a009 | |||
| 683188f67a | |||
| 848e5271ab | |||
| 7ca5614c44 | |||
| 70fc781a03 | |||
| aafdbab781 | |||
| 5dd0f7ea10 | |||
| 9393d9105c | |||
| 2c3856be3c | |||
| dc746a51a5 | |||
| 0f2c9354f0 | |||
| af00032ee9 | |||
| 5d7e685dc4 | |||
| bfcb79c02b | |||
| ebee6273b0 | |||
| b45900e5bc | |||
| 9f0657683a | |||
| 35b8d8ca25 | |||
| 8f7095ce19 | |||
| d9c8dd20e9 | |||
| 8dcc462a30 | |||
| b561948030 | |||
| 2cbd8684cf | |||
| 107c9fce94 | |||
| 4c18b3e059 | |||
| 9960ec4d58 | |||
| 2a09e048e4 | |||
| 9711f3ba72 | |||
| 0622d2b81b | |||
| a372d1fb60 | |||
| 99e55e730e | |||
| 57d1e915e6 | |||
| 96ad6228bd | |||
| 678f9fbe87 | |||
| 714933df56 | |||
| f0ef9ad51e | |||
| 0255213934 | |||
| 0ad92a999c | |||
| b06456d573 | |||
| 6f1fc2c9b4 | |||
| 44a1982d87 | |||
| 6b0cf5aa96 | |||
| 2cfac7a772 | |||
| 99aaf35e0b | |||
| 942ed1fb55 | |||
| 25f83a98e3 | |||
| ed4040f2ec | |||
| 41034de676 | |||
| 2db0f1a6c8 | |||
| 6b06cc7ef5 | |||
| 2bf26212af | |||
| 9d273c172d | |||
| 6ae3bc82bb | |||
| c782002274 | |||
| cd0906d041 | |||
| f936ecca33 | |||
| 8794e3bc53 | |||
| 4503e2a222 | |||
| 015725f88c | |||
| 39451f0e37 | |||
| 2bb0b7faa5 | |||
| 8db7c3769a | |||
| d4e32de882 | |||
| db75dea9ee | |||
| 489bba9c4b | |||
| 4accc49d60 | |||
| 5a47cd5216 | |||
| 439608cf27 | |||
| f9df7a1b5a | |||
| 78c801994a | |||
| 882fd68cf9 | |||
| 3433b73edf | |||
| a235d6a8cd | |||
| d76e88af21 | |||
| 3f6c6c443a | |||
| 13c6d406cf | |||
| 19fea4e761 | |||
| c84c96dcd1 | |||
| cdd8ccc2d4 | |||
| 09210d5d40 | |||
| 8f44a26037 | |||
| 5e986b2d04 | |||
| 298c0922cb | |||
| dc127ea6a3 | |||
| 522ed94c32 | |||
| 6edf66a599 | |||
| 475ec24528 | |||
| b555af0df7 | |||
| f90430e544 | |||
| 4ccb75818c | |||
| c5c9ed24c3 | |||
| 386aa0adc1 | |||
| 0b26b75699 | |||
| d013f67c70 | |||
| dc7c0e61fd | |||
| 89bd041d29 | |||
| ac730d6086 | |||
| 427eaed544 | |||
| 00f783c0b6 | |||
| 8e1e53d55e | |||
| 5c9a5c13b8 | |||
| c1f03a8e75 | |||
| 1affa83620 | |||
| 2aa8180113 | |||
| e6001d83a4 | |||
| 8550c8fde9 | |||
| 48d9a09307 | |||
| 9d0b874488 | |||
| 062a13b2c2 | |||
| 937f8723ed | |||
| 0c5cabbd79 | |||
| 95d8f710d8 | |||
| bb9b8b34e5 | |||
| 54c1164bd7 | |||
| 89c1158d95 | |||
| 9cae189941 | |||
| 53a31cd4c4 | |||
| d72a615481 | |||
| 8dcffa80a8 | |||
| 7cf53acd18 | |||
| 748cf68055 | |||
| 9cd22bdc06 | |||
| 5830d4b91c | |||
| b42f7f566b | |||
| db8223ca98 | |||
| 9403afc392 | |||
| 2b2d3b9517 | |||
| 3eebdfcdb3 | |||
| f7766bc3d4 | |||
| f2614abbdd | |||
| 58824ea879 | |||
| 9ef7a18847 | |||
| 58c0167696 | |||
| 9adfd286f9 | |||
| 4e8f530fbb | |||
| 3e694b0772 | |||
| 2fd5b04d4d | |||
| 4688b9a9c9 | |||
| 153e0035ba | |||
| 88b6ecc557 | |||
| 9df55874a6 | |||
| 87d4ab827a | |||
| 318166f23a | |||
| cc3f712659 | |||
| ee399d8a08 | |||
| 96c233d5c5 | |||
| 652b3e1954 | |||
| a8b76d5537 | |||
| e041b70cdd | |||
| 63bf912b3e | |||
| 0cbe7b1655 | |||
| 7bbec29c5b | |||
| 7cec7dbac8 | |||
| 09a19b5f42 | |||
| 8fe765c097 | |||
| b11e8e07c4 | |||
| f72763306d | |||
| e3d1a476e2 | |||
| f0bc86d42f | |||
| a62e806175 | |||
| 5bcf5bf93e | |||
| 4f35ba0931 | |||
| 2bcdfe778a | |||
| c89da1d0f7 | |||
| 46a1eda029 | |||
| 1c39819112 | |||
| 0efe617c03 | |||
| 10df947efe | |||
| fb7790ba4a | |||
| a9338ed822 | |||
| eaab8cdd93 | |||
| edfcd0dc6e | |||
| 838b56089b | |||
| 178810f908 | |||
| 69f5aca853 | |||
| 8e6aece9ae | |||
| 59b883ff7f | |||
| 548d34fbf4 | |||
| bb6345ccfa | |||
| d59a10f718 | |||
| aab1c5419a | |||
| 9b83785b95 | |||
| 099b710eb1 | |||
| a5424afc38 | |||
| 626325066d | |||
| 37195f6008 | |||
| fcbc68cefe | |||
| 9241953e31 | |||
| 651a912498 | |||
| a05f6fb6b5 | |||
| d6440d31f2 | |||
| 88d5dc2f17 | |||
| a17ad85858 | |||
| 4b49c1f30f | |||
| a9e36b9a59 | |||
| 80429bbfb8 | |||
| f39e20d7a7 | |||
| a03bac5d74 | |||
| 1aff09598a | |||
| 4036a71ee1 | |||
| a966be2f46 | |||
| a0b3bc1cab | |||
| eb2e2b0a26 | |||
| 55ad7b2e81 | |||
| dbcd2897a4 | |||
| 68a6d1c166 | |||
| fe82ec6fc2 | |||
| 5f2819a961 | |||
| 812bfc7cf5 | |||
| d164cafd33 | |||
| 3ada4183d9 | |||
| fa68621b41 | |||
| 4f2b9d39da | |||
| fd01c9269f | |||
| 82d150e53a | |||
| 251f3fe2da | |||
| 4f0e1e6b3d | |||
| a5dbf5d4b7 | |||
| 38baf77c30 | |||
| bfb8b03fc9 | |||
| 2dd38d9b03 | |||
| 307c64bc1e | |||
| 782e3a85f9 | |||
| 3bae6e749a | |||
| 530ef6b83e | |||
| 5b334eb2d5 | |||
| 28dc2e425a | |||
| 83b72e7403 | |||
| 01e1f65ffe | |||
| 9b15f888e6 | |||
| 171b8afa8e | |||
| 27f2f9f13a | |||
| 2c3983cead | |||
| bebd043d58 | |||
| a1c828fe62 | |||
| dfb885f38d | |||
| 702c095544 | |||
| b3e886d444 | |||
| 46d85e92cd | |||
| 0d84f2857f | |||
| ac6c80db90 |
@@ -23,28 +23,33 @@ jobs:
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=raw,value=latest
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Set up the environment
|
||||
run: |
|
||||
cat >> docker/custom-backend-pre-setup.sh <<EOF
|
||||
#!/bin/sh
|
||||
${{ secrets.CUSTOM_BACKEND_PRE_SETUP }}
|
||||
${{ vars.CUSTOM_BACKEND_PRE_SETUP }}
|
||||
EOF
|
||||
cat >> docker/custom-frontend-pre-setup.sh <<EOF
|
||||
#!/bin/sh
|
||||
${{ secrets.CUSTOM_FRONTEND_PRE_SETUP }}
|
||||
${{ vars.CUSTOM_FRONTEND_PRE_SETUP }}
|
||||
EOF
|
||||
chmod +x docker/custom-backend-pre-setup.sh
|
||||
chmod +x docker/custom-frontend-pre-setup.sh
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
env:
|
||||
ACTIONS_RUNTIME_TOKEN: '' # See https://gitea.com/gitea/act_runner/issues/119
|
||||
with:
|
||||
file: Dockerfile
|
||||
context: .
|
||||
platforms: ${{ vars.BUILD_RELEASE_PLATFORMS }}
|
||||
push: true
|
||||
build-args: |
|
||||
RELEASE_BUILD=1
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
@@ -23,27 +23,32 @@ jobs:
|
||||
type=raw,value=latest-snapshot
|
||||
type=sha,format=short,prefix=SNAPSHOT-
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Set up the environment
|
||||
run: |
|
||||
sed -i 's#FROM #FROM ${{ secrets.DOCKER_REPO }}/mirrors/#g' Dockerfile
|
||||
cat >> docker/custom-backend-pre-setup.sh <<EOF
|
||||
#!/bin/sh
|
||||
${{ secrets.CUSTOM_BACKEND_PRE_SETUP }}
|
||||
${{ vars.CUSTOM_BACKEND_PRE_SETUP }}
|
||||
EOF
|
||||
cat >> docker/custom-frontend-pre-setup.sh <<EOF
|
||||
#!/bin/sh
|
||||
${{ secrets.CUSTOM_FRONTEND_PRE_SETUP }}
|
||||
${{ vars.CUSTOM_FRONTEND_PRE_SETUP }}
|
||||
EOF
|
||||
chmod +x docker/custom-backend-pre-setup.sh
|
||||
chmod +x docker/custom-frontend-pre-setup.sh
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
env:
|
||||
ACTIONS_RUNTIME_TOKEN: '' # See https://gitea.com/gitea/act_runner/issues/119
|
||||
with:
|
||||
file: Dockerfile
|
||||
context: .
|
||||
platforms: ${{ vars.BUILD_SNAPSHOT_PLATFORMS }}
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Build backend binary file
|
||||
FROM golang:1.20.4-alpine3.17 AS be-builder
|
||||
FROM golang:1.20.8-alpine3.17 AS be-builder
|
||||
ARG RELEASE_BUILD
|
||||
ENV RELEASE_BUILD=$RELEASE_BUILD
|
||||
WORKDIR /go/src/github.com/mayswind/ezbookkeeping
|
||||
@@ -9,7 +9,7 @@ RUN apk add git gcc g++ libc-dev
|
||||
RUN ./build.sh backend
|
||||
|
||||
# Build frontend files
|
||||
FROM node:18.16.0-alpine3.17 AS fe-builder
|
||||
FROM node:18.17.1-alpine3.17 AS fe-builder
|
||||
ARG RELEASE_BUILD
|
||||
ENV RELEASE_BUILD=$RELEASE_BUILD
|
||||
WORKDIR /go/src/github.com/mayswind/ezbookkeeping
|
||||
@@ -19,7 +19,7 @@ RUN apk add git
|
||||
RUN ./build.sh frontend
|
||||
|
||||
# Package docker image
|
||||
FROM alpine:3.17.3
|
||||
FROM alpine:3.17.5
|
||||
LABEL maintainer="MaysWind <i@mayswind.net>"
|
||||
RUN addgroup -S -g 1000 ezbookkeeping && adduser -S -G ezbookkeeping -u 1000 ezbookkeeping
|
||||
RUN apk --no-cache add tzdata
|
||||
@@ -32,6 +32,7 @@ WORKDIR /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 --chown=1000:1000 conf /ezbookkeeping/conf
|
||||
COPY --chown=1000:1000 templates /ezbookkeeping/templates
|
||||
COPY --chown=1000:1000 LICENSE /ezbookkeeping/LICENSE
|
||||
USER 1000:1000
|
||||
EXPOSE 8080
|
||||
|
||||
@@ -13,12 +13,14 @@ ezBookkeeping is a lightweight personal bookkeeping app hosted by yourself. It c
|
||||
2. Lightweight & Fast
|
||||
3. Easy to install
|
||||
* Docker support
|
||||
* Multiple database support (sqlite, mysql, etc.)
|
||||
* Multiple os & architecture support (Windows, macOS, Linux & x86, amd64, ARM)
|
||||
* Multiple database support (SQLite, MySQL, PostgreSQL, etc.)
|
||||
* Multiple operation system & hardware support (Windows, macOS, Linux & x86, amd64, ARM)
|
||||
4. User-friendly interface
|
||||
* Both desktop and mobile UI
|
||||
* Close to native app experience (for mobile device)
|
||||
* Two-level account & two-level category support
|
||||
* Plentiful preset categories
|
||||
* Geographic location and map support
|
||||
* Searching & filtering history records
|
||||
* Data statistics
|
||||
* Dark theme
|
||||
@@ -26,12 +28,15 @@ ezBookkeeping is a lightweight personal bookkeeping app hosted by yourself. It c
|
||||
6. Multiple timezone support
|
||||
7. Multi-language support
|
||||
8. Two-factor authentication
|
||||
9. Application lock (WebAuthn support)
|
||||
9. Application lock (PIN code / WebAuthn)
|
||||
10. Data export
|
||||
|
||||
## Screenshots
|
||||
### Mobile Device
|
||||
[](https://raw.githubusercontent.com/wiki/mayswind/ezbookkeeping/img/en.png)
|
||||
### Desktop Version
|
||||
[](https://raw.githubusercontent.com/wiki/mayswind/ezbookkeeping/img/desktop/en.png)
|
||||
|
||||
### Mobile Version
|
||||
[](https://raw.githubusercontent.com/wiki/mayswind/ezbookkeeping/img/mobile/en.png)
|
||||
|
||||
## Installation
|
||||
### Ship with docker
|
||||
|
||||
@@ -121,6 +121,9 @@ set_build_parameters() {
|
||||
}
|
||||
|
||||
build_backend() {
|
||||
echo "Pulling backend dependencies..."
|
||||
go get .
|
||||
|
||||
if [ "$NO_LINT" = "0" ]; then
|
||||
echo "Executing backend lint checking..."
|
||||
go vet -v ./...
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/mayswind/ezbookkeeping/pkg/datastore"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/exchangerates"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/mail"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/uuid"
|
||||
@@ -17,29 +18,40 @@ import (
|
||||
func initializeSystem(c *cli.Context) (*settings.Config, error) {
|
||||
var err error
|
||||
configFilePath := c.String("conf-path")
|
||||
isDisableBootLog := c.Bool("no-boot-log")
|
||||
|
||||
if configFilePath != "" {
|
||||
if _, err = os.Stat(configFilePath); err != nil {
|
||||
log.BootErrorf("[initializer.initializeSystem] cannot load configuration from custom config path %s, because file not exists", configFilePath)
|
||||
if !isDisableBootLog {
|
||||
log.BootErrorf("[initializer.initializeSystem] cannot load configuration from custom config path %s, because file not exists", configFilePath)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.BootInfof("[initializer.initializeSystem] will loading configuration from custom config path %s", configFilePath)
|
||||
if !isDisableBootLog {
|
||||
log.BootInfof("[initializer.initializeSystem] will loading configuration from custom config path %s", configFilePath)
|
||||
}
|
||||
} else {
|
||||
configFilePath, err = settings.GetDefaultConfigFilePath()
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[initializer.initializeSystem] cannot get default configuration path, because %s", err.Error())
|
||||
if !isDisableBootLog {
|
||||
log.BootErrorf("[initializer.initializeSystem] cannot get default configuration path, because %s", err.Error())
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.BootInfof("[initializer.initializeSystem] will load configuration from default config path %s", configFilePath)
|
||||
if !isDisableBootLog {
|
||||
log.BootInfof("[initializer.initializeSystem] will load configuration from default config path %s", configFilePath)
|
||||
}
|
||||
}
|
||||
|
||||
config, err := settings.LoadConfiguration(configFilePath)
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[initializer.initializeSystem] cannot load configuration, because %s", err.Error())
|
||||
if !isDisableBootLog {
|
||||
log.BootErrorf("[initializer.initializeSystem] cannot load configuration, because %s", err.Error())
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -48,33 +60,53 @@ func initializeSystem(c *cli.Context) (*settings.Config, error) {
|
||||
err = datastore.InitializeDataStore(config)
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[initializer.initializeSystem] initializes data store failed, because %s", err.Error())
|
||||
if !isDisableBootLog {
|
||||
log.BootErrorf("[initializer.initializeSystem] initializes data store failed, because %s", err.Error())
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = log.SetLoggerConfiguration(config)
|
||||
err = log.SetLoggerConfiguration(config, isDisableBootLog)
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[initializer.initializeSystem] sets logger configuration failed, because %s", err.Error())
|
||||
if !isDisableBootLog {
|
||||
log.BootErrorf("[initializer.initializeSystem] sets logger configuration failed, because %s", err.Error())
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = uuid.InitializeUuidGenerator(config)
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[initializer.initializeSystem] initializes uuid generator failed, because %s", err.Error())
|
||||
if !isDisableBootLog {
|
||||
log.BootErrorf("[initializer.initializeSystem] initializes uuid generator failed, because %s", err.Error())
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = mail.InitializeMailer(config)
|
||||
|
||||
if err != nil {
|
||||
if !isDisableBootLog {
|
||||
log.BootErrorf("[initializer.initializeSystem] initializes mailer failed, because %s", err.Error())
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = exchangerates.InitializeExchangeRatesDataSource(config)
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[initializer.initializeSystem] initializes exchange rates data source failed, because %s", err.Error())
|
||||
if !isDisableBootLog {
|
||||
log.BootErrorf("[initializer.initializeSystem] initializes exchange rates data source failed, because %s", err.Error())
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfgJson, _ := json.Marshal(getConfigWithoutSensitiveData(config))
|
||||
log.BootInfof("[initializer.initializeSystem] has loaded configuration %s", cfgJson)
|
||||
|
||||
if !isDisableBootLog {
|
||||
log.BootInfof("[initializer.initializeSystem] has loaded configuration %s", cfgJson)
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
@@ -88,6 +120,7 @@ func getConfigWithoutSensitiveData(config *settings.Config) *settings.Config {
|
||||
}
|
||||
|
||||
clonedConfig.DatabaseConfig.DatabasePassword = "****"
|
||||
clonedConfig.SMTPConfig.SMTPPasswd = "****"
|
||||
clonedConfig.SecretKey = "****"
|
||||
|
||||
return clonedConfig
|
||||
|
||||
@@ -86,6 +86,71 @@ var UserData = &cli.Command{
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "user-enable",
|
||||
Usage: "Enable specified user",
|
||||
Action: enableUser,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "username",
|
||||
Aliases: []string{"n"},
|
||||
Required: true,
|
||||
Usage: "Specific user name",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "user-disable",
|
||||
Usage: "Disable specified user",
|
||||
Action: disableUser,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "username",
|
||||
Aliases: []string{"n"},
|
||||
Required: true,
|
||||
Usage: "Specific user name",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "user-resend-verify-email",
|
||||
Usage: "Resend user verify email",
|
||||
Action: resendUserVerifyEmail,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "username",
|
||||
Aliases: []string{"n"},
|
||||
Required: true,
|
||||
Usage: "Specific user name",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "user-set-email-verified",
|
||||
Usage: "Set user email address verified",
|
||||
Action: setUserEmailVerified,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "username",
|
||||
Aliases: []string{"n"},
|
||||
Required: true,
|
||||
Usage: "Specific user name",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "user-set-email-unverified",
|
||||
Usage: "Set user email address unverified",
|
||||
Action: setUserEmailUnverified,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "username",
|
||||
Aliases: []string{"n"},
|
||||
Required: true,
|
||||
Usage: "Specific user name",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "user-delete",
|
||||
Usage: "Delete specified user",
|
||||
@@ -138,6 +203,19 @@ var UserData = &cli.Command{
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "send-password-reset-mail",
|
||||
Usage: "Send password reset mail",
|
||||
Action: sendPasswordResetMail,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "username",
|
||||
Aliases: []string{"n"},
|
||||
Required: true,
|
||||
Usage: "Specific user name",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "transaction-check",
|
||||
Usage: "Check whether user all transactions and accounts are correct",
|
||||
@@ -239,6 +317,126 @@ func modifyUserPassword(c *cli.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func sendPasswordResetMail(c *cli.Context) error {
|
||||
_, err := initializeSystem(c)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
username := c.String("username")
|
||||
err = clis.UserData.SendPasswordResetMail(c, username)
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[user_data.sendPasswordResetMail] error occurs when sending password reset email")
|
||||
return err
|
||||
}
|
||||
|
||||
log.BootInfof("[user_data.sendPasswordResetMail] a password reset email for user \"%s\" has been sent", username)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func enableUser(c *cli.Context) error {
|
||||
_, err := initializeSystem(c)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
username := c.String("username")
|
||||
err = clis.UserData.EnableUser(c, username)
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[user_data.enableUser] error occurs when setting user enabled")
|
||||
return err
|
||||
}
|
||||
|
||||
log.BootInfof("[user_data.enableUser] user \"%s\" has been set enabled", username)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func disableUser(c *cli.Context) error {
|
||||
_, err := initializeSystem(c)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
username := c.String("username")
|
||||
err = clis.UserData.DisableUser(c, username)
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[user_data.disableUser] error occurs when setting user disabled")
|
||||
return err
|
||||
}
|
||||
|
||||
log.BootInfof("[user_data.disableUser] user \"%s\" has been set disabled", username)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resendUserVerifyEmail(c *cli.Context) error {
|
||||
_, err := initializeSystem(c)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
username := c.String("username")
|
||||
err = clis.UserData.ResendVerifyEmail(c, username)
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[user_data.resendUserVerifyEmail] error occurs when resending user verify email")
|
||||
return err
|
||||
}
|
||||
|
||||
log.BootInfof("[user_data.resendUserVerifyEmail] verify email for user \"%s\" has been resent", username)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setUserEmailVerified(c *cli.Context) error {
|
||||
_, err := initializeSystem(c)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
username := c.String("username")
|
||||
err = clis.UserData.SetUserEmailVerified(c, username)
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[user_data.setUserEmailVerified] error occurs when setting user email address verified")
|
||||
return err
|
||||
}
|
||||
|
||||
log.BootInfof("[user_data.setUserEmailVerified] user \"%s\" email address has been set verified", username)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setUserEmailUnverified(c *cli.Context) error {
|
||||
_, err := initializeSystem(c)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
username := c.String("username")
|
||||
err = clis.UserData.SetUserEmailUnverified(c, username)
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[user_data.setUserEmailUnverified] error occurs when setting user email address unverified")
|
||||
return err
|
||||
}
|
||||
|
||||
log.BootInfof("[user_data.setUserEmailUnverified] user \"%s\" email address has been set unverified", username)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteUser(c *cli.Context) error {
|
||||
_, err := initializeSystem(c)
|
||||
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/mail"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/requestid"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||
)
|
||||
|
||||
// Utilities represents the utilities command
|
||||
var Utilities = &cli.Command{
|
||||
Name: "utility",
|
||||
Usage: "ezBookkeeping utilities",
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
Name: "parse-default-request-id",
|
||||
Usage: "Parse a request id which is generated by default request generator and show the details",
|
||||
Action: parseRequestId,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "id",
|
||||
Required: true,
|
||||
Usage: "Request ID",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "send-test-mail",
|
||||
Usage: "Send an email to specified e-mail address",
|
||||
Action: sendTestMail,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "to",
|
||||
Required: true,
|
||||
Usage: "To e-mail address",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func parseRequestId(c *cli.Context) error {
|
||||
config, err := initializeSystem(c)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = requestid.InitializeRequestIdGenerator(config)
|
||||
defaultGenerator, err := requestid.NewDefaultRequestIdGenerator(config)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
requestId := c.String("id")
|
||||
requestIdInfo, err := defaultGenerator.ParseRequestIdInfo(requestId)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newRequestId := defaultGenerator.GenerateRequestId(net.IPv4zero.String())
|
||||
newRequestIdInfo, err := defaultGenerator.ParseRequestIdInfo(newRequestId)
|
||||
printRequestIdInfo(requestId, requestIdInfo, newRequestIdInfo)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func sendTestMail(c *cli.Context) error {
|
||||
config, err := initializeSystem(c)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !config.EnableSMTP || mail.Container.Current == nil {
|
||||
return errs.ErrSMTPServerNotEnabled
|
||||
}
|
||||
|
||||
toAddress := c.String("to")
|
||||
|
||||
err = mail.Container.Current.SendMail(&mail.MailMessage{
|
||||
To: toAddress,
|
||||
Subject: "ezBookkeeping test e-mail",
|
||||
Body: "This is a test e-mail",
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Test e-mail has been sent")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func printRequestIdInfo(requestId string, requestIdInfo *requestid.RequestIdInfo, newRequestIdInfo *requestid.RequestIdInfo) {
|
||||
fmt.Printf("[RequestId] %s\n", requestId)
|
||||
fmt.Printf("[ServerUniqId] %d (Current Server %d)\n", requestIdInfo.ServerUniqId, newRequestIdInfo.ServerUniqId)
|
||||
fmt.Printf("[InstanceUniqId] %d (Current Server %d)\n", requestIdInfo.InstanceUniqId, newRequestIdInfo.InstanceUniqId)
|
||||
|
||||
displayTime, err := utils.ParseFromElapsedSeconds(int(requestIdInfo.SecondsElapsedToday))
|
||||
|
||||
if err == nil {
|
||||
fmt.Printf("[SecondsElapsedToday] %d (%s)\n", requestIdInfo.SecondsElapsedToday, displayTime)
|
||||
} else {
|
||||
fmt.Printf("[SecondsElapsedToday] %d\n", requestIdInfo.SecondsElapsedToday)
|
||||
}
|
||||
|
||||
fmt.Printf("[RandomNumber] %d\n", requestIdInfo.RandomNumber)
|
||||
fmt.Printf("[RequestSeqId] %d\n", requestIdInfo.RequestSeqId)
|
||||
fmt.Printf("[IsClientIpv6] %t\n", requestIdInfo.IsClientIpv6)
|
||||
|
||||
if requestIdInfo.IsClientIpv6 {
|
||||
fmt.Printf("[ClientIpv6Hash] %d\n", requestIdInfo.ClientIp)
|
||||
} else {
|
||||
ip := make(net.IP, 4)
|
||||
binary.BigEndian.PutUint32(ip, requestIdInfo.ClientIp)
|
||||
fmt.Printf("[ClientIpv4] %s\n", ip.String())
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,10 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/gin-contrib/cache"
|
||||
"github.com/gin-contrib/cache/persistence"
|
||||
"github.com/gin-contrib/gzip"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
@@ -71,6 +74,8 @@ func startWebServer(c *cli.Context) error {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
|
||||
workboxFileNames := utils.ListFileNamesWithPrefixAndSuffix(config.StaticRootPath, "workbox-", ".js")
|
||||
|
||||
router := gin.New()
|
||||
router.Use(bindMiddleware(middlewares.Recovery))
|
||||
|
||||
@@ -116,7 +121,6 @@ func startWebServer(c *cli.Context) error {
|
||||
router.StaticFile("/mobile/manifest.json", filepath.Join(config.StaticRootPath, "manifest.json"))
|
||||
router.StaticFile("/mobile/sw.js", filepath.Join(config.StaticRootPath, "sw.js"))
|
||||
|
||||
workboxFileNames := utils.ListFileNamesWithPrefixAndSuffix(config.StaticRootPath, "workbox-", ".js")
|
||||
for i := 0; i < len(workboxFileNames); i++ {
|
||||
router.StaticFile("/mobile/"+workboxFileNames[i], filepath.Join(config.StaticRootPath, workboxFileNames[i]))
|
||||
}
|
||||
@@ -134,6 +138,11 @@ func startWebServer(c *cli.Context) error {
|
||||
router.StaticFile("/desktop/favicon.png", filepath.Join(config.StaticRootPath, "favicon.png"))
|
||||
router.StaticFile("/desktop/touchicon.png", filepath.Join(config.StaticRootPath, "touchicon.png"))
|
||||
router.StaticFile("/desktop/manifest.json", filepath.Join(config.StaticRootPath, "manifest.json"))
|
||||
router.StaticFile("/desktop/sw.js", filepath.Join(config.StaticRootPath, "sw.js"))
|
||||
|
||||
for i := 0; i < len(workboxFileNames); i++ {
|
||||
router.StaticFile("/desktop/"+workboxFileNames[i], filepath.Join(config.StaticRootPath, workboxFileNames[i]))
|
||||
}
|
||||
|
||||
router.GET("/healthz.json", bindApi(api.Healths.HealthStatusHandler))
|
||||
|
||||
@@ -146,33 +155,73 @@ func startWebServer(c *cli.Context) error {
|
||||
proxyRoute.Use(bindMiddleware(middlewares.JWTAuthorizationByQueryString))
|
||||
{
|
||||
if config.EnableMapDataFetchProxy {
|
||||
if config.MapProvider == settings.OpenStreetMapProvider {
|
||||
proxyRoute.GET("/openstreetmap/tile/:zoomLevel/:coordinateX/:fileName", bindProxy(api.MapImages.OpenStreetMapTileImageProxyHandler))
|
||||
if config.MapProvider == settings.OpenStreetMapProvider ||
|
||||
config.MapProvider == settings.OpenStreetMapHumanitarianStyleProvider ||
|
||||
config.MapProvider == settings.OpenTopoMapProvider ||
|
||||
config.MapProvider == settings.OPNVKarteMapProvider ||
|
||||
config.MapProvider == settings.CyclOSMMapProvider ||
|
||||
config.MapProvider == settings.TomTomMapProvider {
|
||||
proxyRoute.GET("/map/tile/:zoomLevel/:coordinateX/:fileName", bindProxy(api.MapImages.MapTileImageProxyHandler))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if config.MapProvider == settings.AmapProvider && config.AmapSecurityVerificationMethod == settings.AmapSecurityVerificationInternalProxyMethod {
|
||||
amapApiProxyRoute := router.Group("/_AMapService")
|
||||
amapApiProxyRoute.Use(bindMiddleware(middlewares.JWTAuthorizationByCookie))
|
||||
{
|
||||
amapApiProxyRoute.GET("/*action", bindProxy(api.AmapApis.AmapApiProxyHandler))
|
||||
}
|
||||
}
|
||||
|
||||
qrCodeRoute := router.Group("/qrcode")
|
||||
qrCodeRoute.Use(bindMiddleware(middlewares.RequestId(config)))
|
||||
{
|
||||
qrCodeCacheStore := persistence.NewInMemoryStore(time.Minute)
|
||||
qrCodeRoute.GET("/mobile_url.png", bindCachedPngImage(api.QrCodes.MobileUrlQrCodeHandler, qrCodeCacheStore))
|
||||
}
|
||||
|
||||
apiRoute := router.Group("/api")
|
||||
|
||||
apiRoute.Use(bindMiddleware(middlewares.RequestId(config)))
|
||||
apiRoute.Use(bindMiddleware(middlewares.RequestLog))
|
||||
{
|
||||
apiRoute.POST("/authorize.json", bindApi(api.Authorizations.AuthorizeHandler))
|
||||
apiRoute.POST("/authorize.json", bindApiWithTokenUpdate(api.Authorizations.AuthorizeHandler, config))
|
||||
|
||||
if config.EnableTwoFactor {
|
||||
twoFactorRoute := apiRoute.Group("/2fa")
|
||||
twoFactorRoute.Use(bindMiddleware(middlewares.JWTTwoFactorAuthorization))
|
||||
{
|
||||
twoFactorRoute.POST("/authorize.json", bindApi(api.Authorizations.TwoFactorAuthorizeHandler))
|
||||
twoFactorRoute.POST("/recovery.json", bindApi(api.Authorizations.TwoFactorAuthorizeByRecoveryCodeHandler))
|
||||
twoFactorRoute.POST("/authorize.json", bindApiWithTokenUpdate(api.Authorizations.TwoFactorAuthorizeHandler, config))
|
||||
twoFactorRoute.POST("/recovery.json", bindApiWithTokenUpdate(api.Authorizations.TwoFactorAuthorizeByRecoveryCodeHandler, config))
|
||||
}
|
||||
}
|
||||
|
||||
if config.EnableUserRegister {
|
||||
apiRoute.POST("/register.json", bindApi(api.Users.UserRegisterHandler))
|
||||
apiRoute.POST("/register.json", bindApiWithTokenUpdate(api.Users.UserRegisterHandler, config))
|
||||
}
|
||||
|
||||
apiRoute.GET("/logout.json", bindApi(api.Tokens.TokenRevokeCurrentHandler))
|
||||
if config.EnableUserVerifyEmail {
|
||||
apiRoute.POST("/verify_email/resend.json", bindApi(api.Users.UserSendVerifyEmailByUnloginUserHandler))
|
||||
|
||||
emailVerifyRoute := apiRoute.Group("/verify_email")
|
||||
emailVerifyRoute.Use(bindMiddleware(middlewares.JWTEmailVerifyAuthorization))
|
||||
{
|
||||
emailVerifyRoute.POST("/by_token.json", bindApi(api.Users.UserEmailVerifyHandler))
|
||||
}
|
||||
}
|
||||
|
||||
if config.EnableUserForgetPassword {
|
||||
apiRoute.POST("/forget_password/request.json", bindApi(api.ForgetPasswords.UserForgetPasswordRequestHandler))
|
||||
|
||||
resetPasswordRoute := apiRoute.Group("/forget_password/reset")
|
||||
resetPasswordRoute.Use(bindMiddleware(middlewares.JWTResetPasswordAuthorization))
|
||||
{
|
||||
resetPasswordRoute.POST("/by_token.json", bindApi(api.ForgetPasswords.UserResetPasswordHandler))
|
||||
}
|
||||
}
|
||||
|
||||
apiRoute.GET("/logout.json", bindApiWithTokenUpdate(api.Tokens.TokenRevokeCurrentHandler, config))
|
||||
|
||||
apiV1Route := apiRoute.Group("/v1")
|
||||
apiV1Route.Use(bindMiddleware(middlewares.JWTAuthorization))
|
||||
@@ -181,17 +230,21 @@ func startWebServer(c *cli.Context) error {
|
||||
apiV1Route.GET("/tokens/list.json", bindApi(api.Tokens.TokenListHandler))
|
||||
apiV1Route.POST("/tokens/revoke.json", bindApi(api.Tokens.TokenRevokeHandler))
|
||||
apiV1Route.POST("/tokens/revoke_all.json", bindApi(api.Tokens.TokenRevokeAllHandler))
|
||||
apiV1Route.POST("/tokens/refresh.json", bindApi(api.Tokens.TokenRefreshHandler))
|
||||
apiV1Route.POST("/tokens/refresh.json", bindApiWithTokenUpdate(api.Tokens.TokenRefreshHandler, config))
|
||||
|
||||
// Users
|
||||
apiV1Route.GET("/users/profile/get.json", bindApi(api.Users.UserProfileHandler))
|
||||
apiV1Route.POST("/users/profile/update.json", bindApi(api.Users.UserUpdateProfileHandler))
|
||||
apiV1Route.POST("/users/profile/update.json", bindApiWithTokenUpdate(api.Users.UserUpdateProfileHandler, config))
|
||||
|
||||
if config.EnableUserVerifyEmail {
|
||||
apiV1Route.POST("/users/verify_email/resend.json", bindApi(api.Users.UserSendVerifyEmailByLoginedUserHandler))
|
||||
}
|
||||
|
||||
// Two Factor Authorization
|
||||
if config.EnableTwoFactor {
|
||||
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/confirm.json", bindApi(api.TwoFactorAuthorizations.TwoFactorEnableConfirmHandler))
|
||||
apiV1Route.POST("/users/2fa/enable/confirm.json", bindApiWithTokenUpdate(api.TwoFactorAuthorizations.TwoFactorEnableConfirmHandler, config))
|
||||
apiV1Route.POST("/users/2fa/disable.json", bindApi(api.TwoFactorAuthorizations.TwoFactorDisableHandler))
|
||||
apiV1Route.POST("/users/2fa/recovery/regenerate.json", bindApi(api.TwoFactorAuthorizations.TwoFactorRecoveryCodeRegenerateHandler))
|
||||
}
|
||||
@@ -291,6 +344,23 @@ func bindApi(fn core.ApiHandlerFunc) gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func bindApiWithTokenUpdate(fn core.ApiHandlerFunc, config *settings.Config) gin.HandlerFunc {
|
||||
return func(ginCtx *gin.Context) {
|
||||
c := core.WrapContext(ginCtx)
|
||||
result, err := fn(c)
|
||||
|
||||
if err == nil && config.MapProvider == settings.AmapProvider && config.AmapSecurityVerificationMethod == settings.AmapSecurityVerificationInternalProxyMethod {
|
||||
middlewares.AmapApiProxyAuthCookie(c, config)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
utils.PrintJsonErrorResult(c, err)
|
||||
} else {
|
||||
utils.PrintJsonSuccessResult(c, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func bindCsv(fn core.DataHandlerFunc) gin.HandlerFunc {
|
||||
return func(ginCtx *gin.Context) {
|
||||
c := core.WrapContext(ginCtx)
|
||||
@@ -304,6 +374,19 @@ func bindCsv(fn core.DataHandlerFunc) gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func bindCachedPngImage(fn core.DataHandlerFunc, store persistence.CacheStore) gin.HandlerFunc {
|
||||
return cache.CachePage(store, time.Minute, func(ginCtx *gin.Context) {
|
||||
c := core.WrapContext(ginCtx)
|
||||
result, _, err := fn(c)
|
||||
|
||||
if err != nil {
|
||||
utils.PrintDataErrorResult(c, "text/text", err)
|
||||
} else {
|
||||
utils.PrintDataSuccessResult(c, "img/png", "", result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func bindProxy(fn core.ProxyHandlerFunc) gin.HandlerFunc {
|
||||
return func(ginCtx *gin.Context) {
|
||||
c := core.WrapContext(ginCtx)
|
||||
|
||||
@@ -68,6 +68,19 @@ log_query = false
|
||||
# Set to true to automatically update database structure when starting web server
|
||||
auto_update_database = true
|
||||
|
||||
[mail]
|
||||
# Set to true to enable sending mail by SMTP server
|
||||
enable_smtp = false
|
||||
|
||||
# SMTP Server connection configuration
|
||||
smtp_host = 127.0.0.1:25
|
||||
smtp_user =
|
||||
smtp_passwd =
|
||||
smtp_skip_tls_verify = false
|
||||
|
||||
# Mail from address. This can be just an email address, or the "Name" <user@domain.com> format.
|
||||
from_address =
|
||||
|
||||
[log]
|
||||
# Either "console", "file", default is "console"
|
||||
# Use space to separate multiple modes, e.g. "console file"
|
||||
@@ -99,6 +112,12 @@ token_expired_time = 2592000
|
||||
# Temporary token expired seconds (0 - 4294967295), default is 300 (5 minutes)
|
||||
temporary_token_expired_time = 300
|
||||
|
||||
# Email verify token expired seconds (0 - 4294967295), default is 3600 (60 minutes)
|
||||
email_verify_token_expired_time = 3600
|
||||
|
||||
# Password reset token expired seconds (0 - 4294967295), default is 3600 (60 minutes)
|
||||
password_reset_token_expired_time = 3600
|
||||
|
||||
# Add X-Request-Id header to response to track user request or error, default is true
|
||||
request_id_header = true
|
||||
|
||||
@@ -106,17 +125,69 @@ request_id_header = true
|
||||
# Set to true to allow users to register account by themselves
|
||||
enable_register = true
|
||||
|
||||
# Set to true to allow users to verify email address
|
||||
enable_email_verify = false
|
||||
|
||||
# Set to true to require email must be verified when login
|
||||
enable_force_email_verify = false
|
||||
|
||||
# Set to true to allow users to reset password
|
||||
enable_forget_password = true
|
||||
|
||||
# Set to true to require email must be verified when use forget password
|
||||
forget_password_require_email_verify = false
|
||||
|
||||
# User avatar provider, supports the following types:
|
||||
# "gravatar": https://gravatar.com
|
||||
# Leave blank if you want to disable user avatar
|
||||
avatar_provider =
|
||||
|
||||
[data]
|
||||
# Set to true to allow users to export their data
|
||||
enable_export = true
|
||||
|
||||
[map]
|
||||
# Map provider, supports `openstreetmap`
|
||||
# Map provider, supports the following types:
|
||||
# "openstreetmap": https://www.openstreetmap.org
|
||||
# "openstreetmap_humanitarian": http://map.hotosm.org
|
||||
# "opentopomap": https://opentopomap.org
|
||||
# "opnvkarte": https://publictransportmap.org
|
||||
# "cyclosm": https://www.cyclosm.org
|
||||
# "tomtom": https://www.tomtom.com
|
||||
# "googlemap": https://map.google.com
|
||||
# "baidumap": https://map.baidu.com
|
||||
# "amap": https://amap.com
|
||||
# Leave blank if you want to disable map
|
||||
map_provider = openstreetmap
|
||||
|
||||
# Set to true to use the ezbookkeeping server to proxy map data requests, for "openstreetmap"
|
||||
# Set to true to use the ezbookkeeping server to proxy map data requests, for "openstreetmap", "openstreetmap_humanitarian", "opentopomap", "opnvkarte", "cyclosm" or "tomtom"
|
||||
map_data_fetch_proxy = false
|
||||
|
||||
# For "tomtom" only, TomTom map API key, please visit https://developer.tomtom.com/how-to-get-tomtom-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
|
||||
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
|
||||
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
|
||||
amap_application_key =
|
||||
|
||||
# For "amap" 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)
|
||||
# "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)
|
||||
# Please visit https://developer.amap.com/api/jsapi-v2/guide/abc/load for more information
|
||||
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
|
||||
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"
|
||||
amap_api_external_proxy_url =
|
||||
|
||||
[exchange_rates]
|
||||
# Exchange rates data source, supports the following types:
|
||||
# "euro_central_bank"
|
||||
@@ -129,3 +200,6 @@ data_source = euro_central_bank
|
||||
|
||||
# Requesting exchange rates data timeout (0 - 4294967295 milliseconds), default is 10000 (10 seconds)
|
||||
request_timeout = 10000
|
||||
|
||||
# Set to true skip tls verification when request exchange rates data
|
||||
skip_tls_verify = false
|
||||
|
||||
@@ -37,12 +37,17 @@ func main() {
|
||||
cmd.Database,
|
||||
cmd.UserData,
|
||||
cmd.SecurityUtils,
|
||||
cmd.Utilities,
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "conf-path",
|
||||
Usage: "Custom config `FILE` path",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "no-boot-log",
|
||||
Usage: "Disable boot log",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -3,25 +3,31 @@ module github.com/mayswind/ezbookkeeping
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/boombuler/barcode v1.0.1
|
||||
github.com/gin-contrib/cache v1.2.0
|
||||
github.com/gin-contrib/gzip v0.0.6
|
||||
github.com/gin-gonic/gin v1.9.0
|
||||
github.com/go-playground/validator/v10 v10.14.0
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/go-playground/validator/v10 v10.15.1
|
||||
github.com/go-sql-driver/mysql v1.7.1
|
||||
github.com/golang-jwt/jwt/v5 v5.0.0
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/mattn/go-sqlite3 v1.14.16
|
||||
github.com/pquerna/otp v1.4.0
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/stretchr/testify v1.8.2
|
||||
github.com/urfave/cli/v2 v2.25.4
|
||||
golang.org/x/crypto v0.9.0
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/urfave/cli/v2 v2.25.7
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8
|
||||
golang.org/x/crypto v0.12.0
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
gopkg.in/mail.v2 v2.3.1
|
||||
xorm.io/xorm v1.3.2
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/boombuler/barcode v1.0.1 // indirect
|
||||
github.com/bytedance/sonic v1.8.0 // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d // indirect
|
||||
github.com/buger/jsonparser v1.1.1 // indirect
|
||||
github.com/bytedance/sonic v1.9.1 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
@@ -29,26 +35,31 @@ require (
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/goccy/go-json v0.10.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/gomodule/redigo v1.8.9 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/memcachier/mc/v3 v3.0.3 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/syndtr/goleveldb v1.0.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.9 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/net v0.10.0 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
golang.org/x/sys v0.11.0 // indirect
|
||||
golang.org/x/text v0.12.0 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
xorm.io/builder v0.3.12 // indirect
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
|
||||
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=
|
||||
@@ -22,6 +23,8 @@ github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6l
|
||||
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/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=
|
||||
@@ -29,9 +32,13 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
|
||||
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.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d h1:pVrfxiGfwelyab6n21ZBkbkmbevaf+WvMIiR7sr97hw=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
||||
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/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA=
|
||||
github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
||||
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
@@ -75,13 +82,15 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gin-contrib/cache v1.2.0 h1:WA+AJR4kmHDTaLLShCHo/IeWVmmGRZ3Lsr3JQ46tFlE=
|
||||
github.com/gin-contrib/cache v1.2.0/go.mod h1:2KkFL8PSnPF3Tt5E2Jpc3HWuBAUKqGZnClCFMm0tXQI=
|
||||
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
|
||||
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
|
||||
github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
|
||||
github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
|
||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
|
||||
@@ -89,6 +98,7 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
|
||||
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/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
@@ -96,8 +106,10 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
||||
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
|
||||
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k=
|
||||
github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/go-playground/validator/v10 v10.15.1 h1:BSe8uhN+xQ4r5guV/ywQI4gO59C2raYcGffYWZEjZzM=
|
||||
github.com/go-playground/validator/v10 v10.15.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
||||
@@ -105,8 +117,8 @@ github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
|
||||
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/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=
|
||||
@@ -128,16 +140,20 @@ github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8l
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws=
|
||||
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/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 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=
|
||||
@@ -167,6 +183,7 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
|
||||
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/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
@@ -220,6 +237,7 @@ github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dv
|
||||
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/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=
|
||||
@@ -227,20 +245,24 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
|
||||
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/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 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=
|
||||
@@ -255,6 +277,8 @@ 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/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
@@ -267,13 +291,15 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
|
||||
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.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
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/go.mod h1:GzjocBahcXPxt2cmqzknrgqCOmMxiSzhVKPOe90Tpug=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
@@ -301,7 +327,9 @@ github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtb
|
||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
|
||||
@@ -316,8 +344,8 @@ github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIw
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
|
||||
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
|
||||
github.com/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=
|
||||
@@ -347,10 +375,14 @@ github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R
|
||||
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/go.mod h1:65XQgovT59RWatovFwnwocoUxiI/eENTnOY5GK3STuY=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0 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=
|
||||
@@ -369,8 +401,8 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
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=
|
||||
@@ -394,8 +426,10 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/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/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
||||
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=
|
||||
@@ -403,12 +437,14 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||
github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU=
|
||||
github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli/v2 v2.25.4 h1:HyYwPrTO3im9rYhUff/ZNs78eolxt0nJ4LN+9yJKSH4=
|
||||
github.com/urfave/cli/v2 v2.25.4/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
|
||||
github.com/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/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/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||
@@ -431,8 +467,9 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E
|
||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
@@ -451,6 +488,8 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y
|
||||
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=
|
||||
@@ -460,6 +499,7 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl
|
||||
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=
|
||||
@@ -518,10 +558,13 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
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-20220811171246-fbc7d0a398ab/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.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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=
|
||||
@@ -531,6 +574,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/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=
|
||||
@@ -550,11 +595,13 @@ golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
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=
|
||||
@@ -576,25 +623,33 @@ google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
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/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||
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/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
|
||||
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk=
|
||||
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/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.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-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
@@ -604,6 +659,7 @@ honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWh
|
||||
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=
|
||||
@@ -618,6 +674,7 @@ 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=
|
||||
@@ -653,6 +710,7 @@ modernc.org/ccgo/v3 v3.12.66/go.mod h1:jUuxlCFZTUZLMV08s7B1ekHX5+LIAurKTTaugUr/E
|
||||
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=
|
||||
@@ -690,17 +748,24 @@ 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=
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ezbookkeeping",
|
||||
"version": "0.3.0",
|
||||
"version": "0.4.0",
|
||||
"private": true,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -18,38 +18,47 @@
|
||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vuepic/vue-datepicker": "^5.1.2",
|
||||
"@mdi/js": "^7.2.96",
|
||||
"@vuepic/vue-datepicker": "^5.4.0",
|
||||
"axios": "^1.4.0",
|
||||
"cbor-js": "^0.1.0",
|
||||
"clipboard": "^2.0.11",
|
||||
"crypto-js": "^4.1.1",
|
||||
"dom7": "^4.0.6",
|
||||
"framework7": "^8.0.5",
|
||||
"echarts": "^5.4.3",
|
||||
"framework7": "^8.3.0",
|
||||
"framework7-icons": "^5.0.5",
|
||||
"framework7-vue": "^8.0.5",
|
||||
"framework7-vue": "^8.3.0",
|
||||
"js-cookie": "^3.0.5",
|
||||
"leaflet": "^1.9.4",
|
||||
"line-awesome": "^1.3.0",
|
||||
"moment": "^2.29.4",
|
||||
"moment-timezone": "^0.5.43",
|
||||
"pinia": "^2.1.6",
|
||||
"register-service-worker": "^1.7.2",
|
||||
"skeleton-elements": "^4.0.1",
|
||||
"swiper": "^9.3.2",
|
||||
"swiper": "^10.2.0",
|
||||
"ua-parser-js": "^1.0.35",
|
||||
"vue": "^3.3.4",
|
||||
"vue-echarts": "^6.6.1",
|
||||
"vue-i18n": "^9.2.2",
|
||||
"vuex": "^4.1.0"
|
||||
"vue-router": "^4.2.4",
|
||||
"vue3-perfect-scrollbar": "^1.6.1",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"vuetify": "^3.3.16"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.2.0",
|
||||
"@vitejs/plugin-vue": "^4.3.1",
|
||||
"@vue/compiler-sfc": "^3.3.4",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.41.0",
|
||||
"eslint-plugin-vue": "^9.14.1",
|
||||
"eslint": "^8.47.0",
|
||||
"eslint-plugin-vue": "^9.17.0",
|
||||
"git-rev-sync": "^3.0.2",
|
||||
"postcss-preset-env": "^8.4.1",
|
||||
"vite": "^4.3.5",
|
||||
"vite-plugin-pwa": "^0.15.1"
|
||||
"postcss-preset-env": "^9.1.1",
|
||||
"sass": "^1.66.1",
|
||||
"vite": "^4.4.9",
|
||||
"vite-plugin-pwa": "^0.16.4",
|
||||
"vite-plugin-vuetify": "^1.0.2"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
|
||||
@@ -24,7 +24,7 @@ var (
|
||||
)
|
||||
|
||||
// AccountListHandler returns accounts list of current user
|
||||
func (a *AccountsApi) AccountListHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *AccountsApi) AccountListHandler(c *core.Context) (any, *errs.Error) {
|
||||
var accountListReq models.AccountListRequest
|
||||
err := c.ShouldBindQuery(&accountListReq)
|
||||
|
||||
@@ -34,7 +34,7 @@ func (a *AccountsApi) AccountListHandler(c *core.Context) (interface{}, *errs.Er
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
accounts, err := a.accounts.GetAllAccountsByUid(uid)
|
||||
accounts, err := a.accounts.GetAllAccountsByUid(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[accounts.AccountListHandler] failed to get all accounts for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -84,7 +84,7 @@ func (a *AccountsApi) AccountListHandler(c *core.Context) (interface{}, *errs.Er
|
||||
}
|
||||
|
||||
// AccountGetHandler returns one specific account of current user
|
||||
func (a *AccountsApi) AccountGetHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *AccountsApi) AccountGetHandler(c *core.Context) (any, *errs.Error) {
|
||||
var accountGetReq models.AccountGetRequest
|
||||
err := c.ShouldBindQuery(&accountGetReq)
|
||||
|
||||
@@ -94,7 +94,7 @@ func (a *AccountsApi) AccountGetHandler(c *core.Context) (interface{}, *errs.Err
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
accountAndSubAccounts, err := a.accounts.GetAccountAndSubAccountsByAccountId(uid, accountGetReq.Id)
|
||||
accountAndSubAccounts, err := a.accounts.GetAccountAndSubAccountsByAccountId(c, uid, accountGetReq.Id)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[accounts.AccountGetHandler] failed to get account \"id:%d\" for user \"uid:%d\", because %s", accountGetReq.Id, uid, err.Error())
|
||||
@@ -127,7 +127,7 @@ func (a *AccountsApi) AccountGetHandler(c *core.Context) (interface{}, *errs.Err
|
||||
}
|
||||
|
||||
// AccountCreateHandler saves a new account by request parameters for current user
|
||||
func (a *AccountsApi) AccountCreateHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *AccountsApi) AccountCreateHandler(c *core.Context) (any, *errs.Error) {
|
||||
var accountCreateReq models.AccountCreateRequest
|
||||
err := c.ShouldBindJSON(&accountCreateReq)
|
||||
|
||||
@@ -136,6 +136,13 @@ func (a *AccountsApi) AccountCreateHandler(c *core.Context) (interface{}, *errs.
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
utcOffset, err := c.GetClientTimezoneOffset()
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[accounts.AccountCreateHandler] cannot get client timezone offset, because %s", err.Error())
|
||||
return nil, errs.ErrClientTimezoneOffsetInvalid
|
||||
}
|
||||
|
||||
if accountCreateReq.Type == models.ACCOUNT_TYPE_SINGLE_ACCOUNT {
|
||||
if len(accountCreateReq.SubAccounts) > 0 {
|
||||
log.WarnfWithRequestId(c, "[accounts.AccountCreateHandler] account cannot have any sub accounts")
|
||||
@@ -186,7 +193,7 @@ func (a *AccountsApi) AccountCreateHandler(c *core.Context) (interface{}, *errs.
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
maxOrderId, err := a.accounts.GetMaxDisplayOrder(uid, accountCreateReq.Category)
|
||||
maxOrderId, err := a.accounts.GetMaxDisplayOrder(c, uid, accountCreateReq.Category)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[accounts.AccountCreateHandler] failed to get max display order for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -196,7 +203,7 @@ func (a *AccountsApi) AccountCreateHandler(c *core.Context) (interface{}, *errs.
|
||||
mainAccount := a.createNewAccountModel(uid, &accountCreateReq, maxOrderId+1)
|
||||
childrenAccounts := a.createSubAccountModels(uid, &accountCreateReq)
|
||||
|
||||
err = a.accounts.CreateAccounts(mainAccount, childrenAccounts)
|
||||
err = a.accounts.CreateAccounts(c, mainAccount, childrenAccounts, utcOffset)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[accounts.AccountCreateHandler] failed to create account \"id:%d\" for user \"uid:%d\", because %s", mainAccount.AccountId, uid, err.Error())
|
||||
@@ -219,7 +226,7 @@ func (a *AccountsApi) AccountCreateHandler(c *core.Context) (interface{}, *errs.
|
||||
}
|
||||
|
||||
// AccountModifyHandler saves an existed account by request parameters for current user
|
||||
func (a *AccountsApi) AccountModifyHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *AccountsApi) AccountModifyHandler(c *core.Context) (any, *errs.Error) {
|
||||
var accountModifyReq models.AccountModifyRequest
|
||||
err := c.ShouldBindJSON(&accountModifyReq)
|
||||
|
||||
@@ -229,7 +236,7 @@ func (a *AccountsApi) AccountModifyHandler(c *core.Context) (interface{}, *errs.
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
accountAndSubAccounts, err := a.accounts.GetAccountAndSubAccountsByAccountId(uid, accountModifyReq.Id)
|
||||
accountAndSubAccounts, err := a.accounts.GetAccountAndSubAccountsByAccountId(c, uid, accountModifyReq.Id)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[accounts.AccountModifyHandler] failed to get account \"id:%d\" for user \"uid:%d\", because %s", accountModifyReq.Id, uid, err.Error())
|
||||
@@ -275,7 +282,7 @@ func (a *AccountsApi) AccountModifyHandler(c *core.Context) (interface{}, *errs.
|
||||
return nil, errs.ErrNothingWillBeUpdated
|
||||
}
|
||||
|
||||
err = a.accounts.ModifyAccounts(uid, toUpdateAccounts)
|
||||
err = a.accounts.ModifyAccounts(c, uid, toUpdateAccounts)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[accounts.AccountModifyHandler] failed to update account \"id:%d\" for user \"uid:%d\", because %s", accountModifyReq.Id, uid, err.Error())
|
||||
@@ -325,7 +332,7 @@ func (a *AccountsApi) AccountModifyHandler(c *core.Context) (interface{}, *errs.
|
||||
}
|
||||
|
||||
// AccountHideHandler hides an existed account by request parameters for current user
|
||||
func (a *AccountsApi) AccountHideHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *AccountsApi) AccountHideHandler(c *core.Context) (any, *errs.Error) {
|
||||
var accountHideReq models.AccountHideRequest
|
||||
err := c.ShouldBindJSON(&accountHideReq)
|
||||
|
||||
@@ -335,7 +342,7 @@ func (a *AccountsApi) AccountHideHandler(c *core.Context) (interface{}, *errs.Er
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
err = a.accounts.HideAccount(uid, []int64{accountHideReq.Id}, accountHideReq.Hidden)
|
||||
err = a.accounts.HideAccount(c, uid, []int64{accountHideReq.Id}, accountHideReq.Hidden)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[accounts.AccountHideHandler] failed to hide account \"id:%d\" for user \"uid:%d\", because %s", accountHideReq.Id, uid, err.Error())
|
||||
@@ -347,7 +354,7 @@ func (a *AccountsApi) AccountHideHandler(c *core.Context) (interface{}, *errs.Er
|
||||
}
|
||||
|
||||
// AccountMoveHandler moves display order of existed accounts by request parameters for current user
|
||||
func (a *AccountsApi) AccountMoveHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *AccountsApi) AccountMoveHandler(c *core.Context) (any, *errs.Error) {
|
||||
var accountMoveReq models.AccountMoveRequest
|
||||
err := c.ShouldBindJSON(&accountMoveReq)
|
||||
|
||||
@@ -370,7 +377,7 @@ func (a *AccountsApi) AccountMoveHandler(c *core.Context) (interface{}, *errs.Er
|
||||
accounts[i] = account
|
||||
}
|
||||
|
||||
err = a.accounts.ModifyAccountDisplayOrders(uid, accounts)
|
||||
err = a.accounts.ModifyAccountDisplayOrders(c, uid, accounts)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[accounts.AccountMoveHandler] failed to move accounts for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -382,7 +389,7 @@ func (a *AccountsApi) AccountMoveHandler(c *core.Context) (interface{}, *errs.Er
|
||||
}
|
||||
|
||||
// AccountDeleteHandler deletes an existed account by request parameters for current user
|
||||
func (a *AccountsApi) AccountDeleteHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *AccountsApi) AccountDeleteHandler(c *core.Context) (any, *errs.Error) {
|
||||
var accountDeleteReq models.AccountDeleteRequest
|
||||
err := c.ShouldBindJSON(&accountDeleteReq)
|
||||
|
||||
@@ -392,7 +399,7 @@ func (a *AccountsApi) AccountDeleteHandler(c *core.Context) (interface{}, *errs.
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
err = a.accounts.DeleteAccount(uid, accountDeleteReq.Id)
|
||||
err = a.accounts.DeleteAccount(c, uid, accountDeleteReq.Id)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[accounts.AccountDeleteHandler] failed to delete account \"id:%d\" for user \"uid:%d\", because %s", accountDeleteReq.Id, uid, err.Error())
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||
)
|
||||
|
||||
const amapCustomMapStylesUrl = "https://webapi.amap.com/v4/map/styles"
|
||||
const amapOverseasMapUrl = "https://fmap01.amap.com/v3/vectormap"
|
||||
const amapRestApiUrl = "https://restapi.amap.com/"
|
||||
|
||||
// AmapApiProxy represents amap api proxy
|
||||
type AmapApiProxy struct {
|
||||
}
|
||||
|
||||
// Initialize a amap api proxy singleton instance
|
||||
var (
|
||||
AmapApis = &AmapApiProxy{}
|
||||
)
|
||||
|
||||
// AmapApiProxyHandler returns amap api response
|
||||
func (p *AmapApiProxy) AmapApiProxyHandler(c *core.Context) (*httputil.ReverseProxy, *errs.Error) {
|
||||
var targetUrl string
|
||||
|
||||
if strings.HasPrefix(c.Request.RequestURI, "/_AMapService/v4/map/styles") {
|
||||
targetUrl = amapCustomMapStylesUrl + strings.TrimPrefix(c.Request.URL.Path, "/_AMapService/v4/map/styles")
|
||||
} else if strings.HasPrefix(c.Request.RequestURI, "/_AMapService/v3/vectormap") {
|
||||
targetUrl = amapOverseasMapUrl + strings.TrimPrefix(c.Request.URL.Path, "/_AMapService/v3/vectormap")
|
||||
} else {
|
||||
targetUrl = amapRestApiUrl + strings.TrimPrefix(c.Request.URL.Path, "/_AMapService/")
|
||||
}
|
||||
|
||||
director := func(req *http.Request) {
|
||||
targetRawUrl := fmt.Sprintf("%s?%s&jscode=%s", targetUrl, req.URL.RawQuery, settings.Container.Current.AmapApplicationSecret)
|
||||
targetUrl, _ := url.Parse(targetRawUrl)
|
||||
|
||||
oldCookies := req.Cookies()
|
||||
req.Header.Del("Cookie")
|
||||
|
||||
for i := 0; i < len(oldCookies); i++ {
|
||||
if strings.HasPrefix(oldCookies[i].Name, "ebk_") {
|
||||
continue
|
||||
}
|
||||
|
||||
req.AddCookie(oldCookies[i])
|
||||
}
|
||||
|
||||
req.URL = targetUrl
|
||||
req.RequestURI = req.URL.RequestURI()
|
||||
req.Host = targetUrl.Host
|
||||
}
|
||||
|
||||
return &httputil.ReverseProxy{Director: director}, nil
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/services"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||
)
|
||||
|
||||
// AuthorizationsApi represents authorization api
|
||||
@@ -27,7 +28,7 @@ var (
|
||||
)
|
||||
|
||||
// AuthorizeHandler verifies and authorizes current login request
|
||||
func (a *AuthorizationsApi) AuthorizeHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *AuthorizationsApi) AuthorizeHandler(c *core.Context) (any, *errs.Error) {
|
||||
var credential models.UserLoginRequest
|
||||
err := c.ShouldBindJSON(&credential)
|
||||
|
||||
@@ -36,14 +37,35 @@ func (a *AuthorizationsApi) AuthorizeHandler(c *core.Context) (interface{}, *err
|
||||
return nil, errs.ErrLoginNameOrPasswordInvalid
|
||||
}
|
||||
|
||||
user, err := a.users.GetUserByUsernameOrEmailAndPassword(credential.LoginName, credential.Password)
|
||||
user, err := a.users.GetUserByUsernameOrEmailAndPassword(c, credential.LoginName, credential.Password)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[authorizations.AuthorizeHandler] login failed for user \"%s\", because %s", credential.LoginName, err.Error())
|
||||
return nil, errs.ErrLoginNameOrPasswordWrong
|
||||
}
|
||||
|
||||
err = a.users.UpdateUserLastLoginTime(user.Uid)
|
||||
if user.Disabled {
|
||||
log.WarnfWithRequestId(c, "[authorizations.AuthorizeHandler] login failed for user \"%s\", because user is disabled", credential.LoginName)
|
||||
return nil, errs.ErrUserIsDisabled
|
||||
}
|
||||
|
||||
if settings.Container.Current.EnableUserForceVerifyEmail && !user.EmailVerified {
|
||||
hasValidEmailVerifyToken, err := a.tokens.ExistsValidTokenByType(c, user.Uid, core.USER_TOKEN_TYPE_EMAIL_VERIFY)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[authorizations.AuthorizeHandler] failed check whether user \"uid:%d\" has valid verify email token, because %s", user.Uid, err.Error())
|
||||
hasValidEmailVerifyToken = false
|
||||
}
|
||||
|
||||
log.WarnfWithRequestId(c, "[authorizations.AuthorizeHandler] login failed for user \"%s\", because user has not verified email", credential.LoginName)
|
||||
|
||||
return nil, errs.NewErrorWithContext(errs.ErrEmailIsNotVerified, map[string]any{
|
||||
"email": user.Email,
|
||||
"hasValidEmailVerifyToken": hasValidEmailVerifyToken,
|
||||
})
|
||||
}
|
||||
|
||||
err = a.users.UpdateUserLastLoginTime(c, user.Uid)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[authorizations.AuthorizeHandler] failed to update last login time for user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||
@@ -52,7 +74,7 @@ func (a *AuthorizationsApi) AuthorizeHandler(c *core.Context) (interface{}, *err
|
||||
twoFactorEnable := a.tokens.CurrentConfig().EnableTwoFactor
|
||||
|
||||
if twoFactorEnable {
|
||||
twoFactorEnable, err = a.twoFactorAuthorizations.ExistsTwoFactorSetting(user.Uid)
|
||||
twoFactorEnable, err = a.twoFactorAuthorizations.ExistsTwoFactorSetting(c, user.Uid)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[authorizations.AuthorizeHandler] failed to check two factor setting for user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||
@@ -64,9 +86,9 @@ func (a *AuthorizationsApi) AuthorizeHandler(c *core.Context) (interface{}, *err
|
||||
var claims *core.UserTokenClaims
|
||||
|
||||
if twoFactorEnable {
|
||||
token, claims, err = a.tokens.CreateRequire2FAToken(user, c)
|
||||
token, claims, err = a.tokens.CreateRequire2FAToken(c, user)
|
||||
} else {
|
||||
token, claims, err = a.tokens.CreateToken(user, c)
|
||||
token, claims, err = a.tokens.CreateToken(c, user)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -74,6 +96,10 @@ func (a *AuthorizationsApi) AuthorizeHandler(c *core.Context) (interface{}, *err
|
||||
return nil, errs.ErrTokenGenerating
|
||||
}
|
||||
|
||||
if !twoFactorEnable {
|
||||
c.SetTextualToken(token)
|
||||
}
|
||||
|
||||
c.SetTokenClaims(claims)
|
||||
|
||||
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)
|
||||
@@ -83,7 +109,7 @@ func (a *AuthorizationsApi) AuthorizeHandler(c *core.Context) (interface{}, *err
|
||||
}
|
||||
|
||||
// TwoFactorAuthorizeHandler verifies and authorizes current 2fa login by passcode
|
||||
func (a *AuthorizationsApi) TwoFactorAuthorizeHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *AuthorizationsApi) TwoFactorAuthorizeHandler(c *core.Context) (any, *errs.Error) {
|
||||
var credential models.TwoFactorLoginRequest
|
||||
err := c.ShouldBindJSON(&credential)
|
||||
|
||||
@@ -93,7 +119,7 @@ func (a *AuthorizationsApi) TwoFactorAuthorizeHandler(c *core.Context) (interfac
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
twoFactorSetting, err := a.twoFactorAuthorizations.GetUserTwoFactorSettingByUid(uid)
|
||||
twoFactorSetting, err := a.twoFactorAuthorizations.GetUserTwoFactorSettingByUid(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[authorizations.TwoFactorAuthorizeHandler] failed to get two factor setting for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -105,27 +131,38 @@ func (a *AuthorizationsApi) TwoFactorAuthorizeHandler(c *core.Context) (interfac
|
||||
return nil, errs.ErrPasscodeInvalid
|
||||
}
|
||||
|
||||
user, err := a.users.GetUserById(uid)
|
||||
user, err := a.users.GetUserById(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[authorizations.TwoFactorAuthorizeHandler] failed to get user \"uid:%d\" info, because %s", user.Uid, err.Error())
|
||||
return nil, errs.ErrUserNotFound
|
||||
}
|
||||
|
||||
if user.Disabled {
|
||||
log.WarnfWithRequestId(c, "[authorizations.TwoFactorAuthorizeHandler] user \"uid:%d\" is disabled", user.Uid)
|
||||
return nil, errs.ErrUserIsDisabled
|
||||
}
|
||||
|
||||
if settings.Container.Current.EnableUserForceVerifyEmail && !user.EmailVerified {
|
||||
log.WarnfWithRequestId(c, "[authorizations.TwoFactorAuthorizeHandler] user \"uid:%d\" has not verified email", user.Uid)
|
||||
return nil, errs.ErrEmailIsNotVerified
|
||||
}
|
||||
|
||||
oldTokenClaims := c.GetTokenClaims()
|
||||
err = a.tokens.DeleteTokenByClaims(oldTokenClaims)
|
||||
err = a.tokens.DeleteTokenByClaims(c, oldTokenClaims)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[authorizations.TwoFactorAuthorizeHandler] failed to revoke temporary token \"utid:%s\" for user \"uid:%d\", because %s", oldTokenClaims.UserTokenId, user.Uid, err.Error())
|
||||
}
|
||||
|
||||
token, claims, err := a.tokens.CreateToken(user, c)
|
||||
token, claims, err := a.tokens.CreateToken(c, user)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[authorizations.TwoFactorAuthorizeHandler] failed to create token for user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||
return nil, errs.ErrTokenGenerating
|
||||
}
|
||||
|
||||
c.SetTextualToken(token)
|
||||
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)
|
||||
@@ -135,7 +172,7 @@ func (a *AuthorizationsApi) TwoFactorAuthorizeHandler(c *core.Context) (interfac
|
||||
}
|
||||
|
||||
// TwoFactorAuthorizeByRecoveryCodeHandler verifies and authorizes current 2fa login by recovery code
|
||||
func (a *AuthorizationsApi) TwoFactorAuthorizeByRecoveryCodeHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *AuthorizationsApi) TwoFactorAuthorizeByRecoveryCodeHandler(c *core.Context) (any, *errs.Error) {
|
||||
var credential models.TwoFactorRecoveryCodeLoginRequest
|
||||
err := c.ShouldBindJSON(&credential)
|
||||
|
||||
@@ -145,7 +182,7 @@ func (a *AuthorizationsApi) TwoFactorAuthorizeByRecoveryCodeHandler(c *core.Cont
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
enableTwoFactor, err := a.twoFactorAuthorizations.ExistsTwoFactorSetting(uid)
|
||||
enableTwoFactor, err := a.twoFactorAuthorizations.ExistsTwoFactorSetting(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[authorizations.TwoFactorAuthorizeByRecoveryCodeHandler] failed to get two factor setting for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -156,14 +193,24 @@ func (a *AuthorizationsApi) TwoFactorAuthorizeByRecoveryCodeHandler(c *core.Cont
|
||||
return nil, errs.ErrTwoFactorIsNotEnabled
|
||||
}
|
||||
|
||||
user, err := a.users.GetUserById(uid)
|
||||
user, err := a.users.GetUserById(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[authorizations.TwoFactorAuthorizeByRecoveryCodeHandler] failed to get user \"uid:%d\" info, because %s", user.Uid, err.Error())
|
||||
return nil, errs.ErrUserNotFound
|
||||
}
|
||||
|
||||
err = a.twoFactorAuthorizations.GetAndUseUserTwoFactorRecoveryCode(uid, credential.RecoveryCode, user.Salt)
|
||||
if user.Disabled {
|
||||
log.WarnfWithRequestId(c, "[authorizations.TwoFactorAuthorizeByRecoveryCodeHandler] user \"uid:%d\" is disabled", user.Uid)
|
||||
return nil, errs.ErrUserIsDisabled
|
||||
}
|
||||
|
||||
if settings.Container.Current.EnableUserForceVerifyEmail && !user.EmailVerified {
|
||||
log.WarnfWithRequestId(c, "[authorizations.TwoFactorAuthorizeByRecoveryCodeHandler] user \"uid:%d\" has not verified email", user.Uid)
|
||||
return nil, errs.ErrEmailIsNotVerified
|
||||
}
|
||||
|
||||
err = a.twoFactorAuthorizations.GetAndUseUserTwoFactorRecoveryCode(c, uid, credential.RecoveryCode, user.Salt)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[authorizations.TwoFactorAuthorizeByRecoveryCodeHandler] failed to get two factor recovery code for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -171,19 +218,20 @@ func (a *AuthorizationsApi) TwoFactorAuthorizeByRecoveryCodeHandler(c *core.Cont
|
||||
}
|
||||
|
||||
oldTokenClaims := c.GetTokenClaims()
|
||||
err = a.tokens.DeleteTokenByClaims(oldTokenClaims)
|
||||
err = a.tokens.DeleteTokenByClaims(c, oldTokenClaims)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[authorizations.TwoFactorAuthorizeByRecoveryCodeHandler] failed to revoke temporary token \"utid:%s\" for user \"uid:%d\", because %s", oldTokenClaims.UserTokenId, user.Uid, err.Error())
|
||||
}
|
||||
|
||||
token, claims, err := a.tokens.CreateToken(user, c)
|
||||
token, claims, err := a.tokens.CreateToken(c, user)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[authorizations.TwoFactorAuthorizeByRecoveryCodeHandler] failed to create token for user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||
return nil, errs.ErrTokenGenerating
|
||||
}
|
||||
|
||||
c.SetTextualToken(token)
|
||||
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)
|
||||
|
||||
@@ -57,7 +57,7 @@ func (a *DataManagementsApi) ExportDataHandler(c *core.Context) ([]byte, string,
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
user, err := a.users.GetUserById(uid)
|
||||
user, err := a.users.GetUserById(c, uid)
|
||||
|
||||
if err != nil {
|
||||
if !errs.IsCustomError(err) {
|
||||
@@ -67,28 +67,28 @@ func (a *DataManagementsApi) ExportDataHandler(c *core.Context) ([]byte, string,
|
||||
return nil, "", errs.ErrUserNotFound
|
||||
}
|
||||
|
||||
accounts, err := a.accounts.GetAllAccountsByUid(uid)
|
||||
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(uid, 0, -1)
|
||||
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(uid)
|
||||
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(uid)
|
||||
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())
|
||||
@@ -99,7 +99,7 @@ func (a *DataManagementsApi) ExportDataHandler(c *core.Context) ([]byte, string,
|
||||
categoryMap := a.categories.GetCategoryMapByList(categories)
|
||||
tagMap := a.tags.GetTagMapByList(tags)
|
||||
|
||||
allTransactions, err := a.transactions.GetAllTransactions(uid, pageCountForDataExport, true)
|
||||
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())
|
||||
@@ -119,30 +119,30 @@ func (a *DataManagementsApi) ExportDataHandler(c *core.Context) ([]byte, string,
|
||||
}
|
||||
|
||||
// DataStatisticsHandler returns user data statistics
|
||||
func (a *DataManagementsApi) DataStatisticsHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *DataManagementsApi) DataStatisticsHandler(c *core.Context) (any, *errs.Error) {
|
||||
uid := c.GetCurrentUid()
|
||||
totalAccountCount, err := a.accounts.GetTotalAccountCountByUid(uid)
|
||||
totalAccountCount, err := a.accounts.GetTotalAccountCountByUid(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[data_managements.DataStatisticsHandler] failed to get total account count for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.ErrOperationFailed
|
||||
}
|
||||
|
||||
totalTransactionCategoryCount, err := a.categories.GetTotalCategoryCountByUid(uid)
|
||||
totalTransactionCategoryCount, err := a.categories.GetTotalCategoryCountByUid(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[data_managements.DataStatisticsHandler] failed to get total transaction category count for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.ErrOperationFailed
|
||||
}
|
||||
|
||||
totalTransactionTagCount, err := a.tags.GetTotalTagCountByUid(uid)
|
||||
totalTransactionTagCount, err := a.tags.GetTotalTagCountByUid(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[data_managements.DataStatisticsHandler] failed to get total transaction tag count for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.ErrOperationFailed
|
||||
}
|
||||
|
||||
totalTransactionCount, err := a.transactions.GetTotalTransactionCountByUid(uid)
|
||||
totalTransactionCount, err := a.transactions.GetTotalTransactionCountByUid(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[data_managements.DataStatisticsHandler] failed to get total transaction count for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -160,7 +160,7 @@ func (a *DataManagementsApi) DataStatisticsHandler(c *core.Context) (interface{}
|
||||
}
|
||||
|
||||
// ClearDataHandler deletes all user data
|
||||
func (a *DataManagementsApi) ClearDataHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *DataManagementsApi) ClearDataHandler(c *core.Context) (any, *errs.Error) {
|
||||
var clearDataReq models.ClearDataRequest
|
||||
err := c.ShouldBindJSON(&clearDataReq)
|
||||
|
||||
@@ -170,7 +170,7 @@ func (a *DataManagementsApi) ClearDataHandler(c *core.Context) (interface{}, *er
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
user, err := a.users.GetUserById(uid)
|
||||
user, err := a.users.GetUserById(c, uid)
|
||||
|
||||
if err != nil {
|
||||
if !errs.IsCustomError(err) {
|
||||
@@ -184,21 +184,21 @@ func (a *DataManagementsApi) ClearDataHandler(c *core.Context) (interface{}, *er
|
||||
return nil, errs.ErrUserPasswordWrong
|
||||
}
|
||||
|
||||
err = a.transactions.DeleteAllTransactions(uid)
|
||||
err = a.transactions.DeleteAllTransactions(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[data_managements.ClearDataHandler] failed to delete all transactions, because %s", err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
err = a.categories.DeleteAllCategories(uid)
|
||||
err = a.categories.DeleteAllCategories(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[data_managements.ClearDataHandler] failed to delete all transaction categories, because %s", err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
err = a.tags.DeleteAllTags(uid)
|
||||
err = a.tags.DeleteAllTags(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[data_managements.ClearDataHandler] failed to delete all transaction tags, because %s", err.Error())
|
||||
|
||||
@@ -14,11 +14,11 @@ var (
|
||||
)
|
||||
|
||||
// ApiNotFound returns api not found error
|
||||
func (a *DefaultApi) ApiNotFound(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *DefaultApi) ApiNotFound(c *core.Context) (any, *errs.Error) {
|
||||
return nil, errs.ErrApiNotFound
|
||||
}
|
||||
|
||||
// MethodNotAllowed returns method not allowed error
|
||||
func (a *DefaultApi) MethodNotAllowed(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *DefaultApi) MethodNotAllowed(c *core.Context) (any, *errs.Error) {
|
||||
return nil, errs.ErrMethodNotAllowed
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net/http"
|
||||
"sort"
|
||||
@@ -23,7 +24,7 @@ var (
|
||||
)
|
||||
|
||||
// LatestExchangeRateHandler returns latest exchange rate data
|
||||
func (a *ExchangeRatesApi) LatestExchangeRateHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *ExchangeRatesApi) LatestExchangeRateHandler(c *core.Context) (any, *errs.Error) {
|
||||
dataSource := exchangerates.Container.Current
|
||||
|
||||
if dataSource == nil {
|
||||
@@ -32,8 +33,17 @@ func (a *ExchangeRatesApi) LatestExchangeRateHandler(c *core.Context) (interface
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
|
||||
transport := http.DefaultTransport.(*http.Transport).Clone()
|
||||
|
||||
if settings.Container.Current.ExchangeRatesSkipTLSVerify {
|
||||
transport.TLSClientConfig = &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: time.Duration(settings.Container.Current.ExchangeRatesRequestTimeout) * time.Millisecond,
|
||||
Transport: transport,
|
||||
Timeout: time.Duration(settings.Container.Current.ExchangeRatesRequestTimeout) * time.Millisecond,
|
||||
}
|
||||
|
||||
urls := dataSource.GetRequestUrls()
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/services"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||
)
|
||||
|
||||
// ForgetPasswordsApi represents user forget password api
|
||||
type ForgetPasswordsApi struct {
|
||||
users *services.UserService
|
||||
tokens *services.TokenService
|
||||
forgetPasswords *services.ForgetPasswordService
|
||||
}
|
||||
|
||||
// Initialize a user api singleton instance
|
||||
var (
|
||||
ForgetPasswords = &ForgetPasswordsApi{
|
||||
users: services.Users,
|
||||
tokens: services.Tokens,
|
||||
forgetPasswords: services.ForgetPasswords,
|
||||
}
|
||||
)
|
||||
|
||||
// UserForgetPasswordRequestHandler generates password reset link and send user an email with this link
|
||||
func (a *ForgetPasswordsApi) UserForgetPasswordRequestHandler(c *core.Context) (any, *errs.Error) {
|
||||
var request models.ForgetPasswordRequest
|
||||
err := c.ShouldBindJSON(&request)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[forget_passwords.UserForgetPasswordRequestHandler] parse request failed, because %s", err.Error())
|
||||
return nil, errs.ErrEmailIsEmptyOrInvalid
|
||||
}
|
||||
|
||||
user, err := a.users.GetUserByEmail(c, request.Email)
|
||||
|
||||
if err != nil {
|
||||
if !errs.IsCustomError(err) {
|
||||
log.ErrorfWithRequestId(c, "[forget_passwords.UserForgetPasswordRequestHandler] failed to get user, because %s", err.Error())
|
||||
}
|
||||
|
||||
return nil, errs.ErrUserNotFound
|
||||
}
|
||||
|
||||
if user.Disabled {
|
||||
log.WarnfWithRequestId(c, "[forget_passwords.UserForgetPasswordRequestHandler] user \"uid:%d\" is disabled", user.Uid)
|
||||
return nil, errs.ErrUserIsDisabled
|
||||
}
|
||||
|
||||
if settings.Container.Current.ForgetPasswordRequireVerifyEmail && !user.EmailVerified {
|
||||
log.WarnfWithRequestId(c, "[forget_passwords.UserForgetPasswordRequestHandler] user \"uid:%d\" has not verified email", user.Uid)
|
||||
return nil, errs.ErrEmailIsNotVerified
|
||||
}
|
||||
|
||||
if !settings.Container.Current.EnableSMTP {
|
||||
return nil, errs.ErrSMTPServerNotEnabled
|
||||
}
|
||||
|
||||
token, _, err := a.tokens.CreatePasswordResetToken(c, user)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[forget_passwords.UserForgetPasswordRequestHandler] failed to create token for user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||
return nil, errs.ErrTokenGenerating
|
||||
}
|
||||
|
||||
go func() {
|
||||
err = a.forgetPasswords.SendPasswordResetEmail(c, user, token, c.GetClientLocale())
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[forget_passwords.UserForgetPasswordRequestHandler] cannot send email to \"%s\", because %s", user.Email, err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// UserResetPasswordHandler resets user password by request parameters
|
||||
func (a *ForgetPasswordsApi) UserResetPasswordHandler(c *core.Context) (any, *errs.Error) {
|
||||
var request models.PasswordResetRequest
|
||||
err := c.ShouldBindJSON(&request)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[forget_passwords.UserResetPasswordHandler] parse request failed, because %s", err.Error())
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
user, err := a.users.GetUserById(c, uid)
|
||||
|
||||
if err != nil {
|
||||
if !errs.IsCustomError(err) {
|
||||
log.ErrorfWithRequestId(c, "[forget_passwords.UserResetPasswordHandler] failed to get user, because %s", err.Error())
|
||||
}
|
||||
|
||||
return nil, errs.ErrUserNotFound
|
||||
}
|
||||
|
||||
if user.Disabled {
|
||||
log.WarnfWithRequestId(c, "[forget_passwords.UserResetPasswordHandler] user \"uid:%d\" is disabled", user.Uid)
|
||||
return nil, errs.ErrUserIsDisabled
|
||||
}
|
||||
|
||||
if settings.Container.Current.ForgetPasswordRequireVerifyEmail && !user.EmailVerified {
|
||||
log.WarnfWithRequestId(c, "[forget_passwords.UserResetPasswordHandler] user \"uid:%d\" has not verified email", user.Uid)
|
||||
return nil, errs.ErrEmailIsNotVerified
|
||||
}
|
||||
|
||||
if user.Email != request.Email {
|
||||
log.WarnfWithRequestId(c, "[forget_passwords.UserResetPasswordHandler] request email not equals the user email")
|
||||
return nil, errs.ErrEmptyIsInvalid
|
||||
}
|
||||
|
||||
if a.users.IsPasswordEqualsUserPassword(request.Password, user) {
|
||||
oldTokenClaims := c.GetTokenClaims()
|
||||
err = a.tokens.DeleteTokenByClaims(c, oldTokenClaims)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[forget_passwords.UserResetPasswordHandler] failed to revoke password reset token \"utid:%s\" for user \"uid:%d\", because %s", oldTokenClaims.UserTokenId, user.Uid, err.Error())
|
||||
}
|
||||
|
||||
return nil, errs.ErrNewPasswordEqualsOldInvalid
|
||||
}
|
||||
|
||||
userNew := &models.User{
|
||||
Uid: user.Uid,
|
||||
Salt: user.Salt,
|
||||
Password: request.Password,
|
||||
}
|
||||
|
||||
_, _, err = a.users.UpdateUser(c, userNew, false)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[forget_passwords.UserResetPasswordHandler] failed to update user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
err = a.tokens.DeleteTokensBeforeTime(c, uid, now)
|
||||
|
||||
if err == nil {
|
||||
log.InfofWithRequestId(c, "[forget_passwords.UserResetPasswordHandler] revoke old tokens before unix time \"%d\" for user \"uid:%d\"", now, user.Uid)
|
||||
} else {
|
||||
log.WarnfWithRequestId(c, "[forget_passwords.UserResetPasswordHandler] failed to revoke old tokens for user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
@@ -15,7 +15,7 @@ var (
|
||||
)
|
||||
|
||||
// HealthStatusHandler returns the health status of current service
|
||||
func (a *HealthsApi) HealthStatusHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *HealthsApi) HealthStatusHandler(c *core.Context) (any, *errs.Error) {
|
||||
result := make(map[string]string)
|
||||
|
||||
result["version"] = settings.Version
|
||||
|
||||
@@ -5,12 +5,19 @@ import (
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||
)
|
||||
|
||||
const openStreetMapTileImageUrlFormat = "https://tile.openstreetmap.org/%s/%s/%s" // https://tile.openstreetmap.org/{z}/{x}/{y}.png
|
||||
const openStreetMapTileImageUrlFormat = "https://tile.openstreetmap.org/%s/%s/%s" // 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 openTopoMapTileImageUrlFormat = "https://tile.opentopomap.org/%s/%s/%s" // 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 cyclOSMMapTileImageUrlFormat = "https://a.tile-cyclosm.openstreetmap.fr/cyclosm/%s/%s/%s" // 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}
|
||||
|
||||
// MapImageProxy represents map image proxy
|
||||
type MapImageProxy struct {
|
||||
@@ -21,14 +28,38 @@ var (
|
||||
MapImages = &MapImageProxy{}
|
||||
)
|
||||
|
||||
// OpenStreetMapTileImageProxyHandler returns open street map tile image
|
||||
func (p *MapImageProxy) OpenStreetMapTileImageProxyHandler(c *core.Context) (*httputil.ReverseProxy, *errs.Error) {
|
||||
// MapTileImageProxyHandler returns map tile image
|
||||
func (p *MapImageProxy) MapTileImageProxyHandler(c *core.Context) (*httputil.ReverseProxy, *errs.Error) {
|
||||
mapProvider := strings.Replace(c.Query("provider"), "-", "_", -1)
|
||||
targetUrl := ""
|
||||
|
||||
if mapProvider == settings.OpenStreetMapProvider {
|
||||
targetUrl = openStreetMapTileImageUrlFormat
|
||||
} 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")
|
||||
coordinateX := c.Param("coordinateX")
|
||||
fileName := c.Param("fileName")
|
||||
|
||||
imageRawUrl := fmt.Sprintf(openStreetMapTileImageUrlFormat, zoomLevel, coordinateX, fileName)
|
||||
imageRawUrl := fmt.Sprintf(targetUrl, zoomLevel, coordinateX, fileName)
|
||||
imageUrl, _ := url.Parse(imageRawUrl)
|
||||
|
||||
req.URL = imageUrl
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"image/png"
|
||||
|
||||
"github.com/boombuler/barcode"
|
||||
"github.com/boombuler/barcode/qr"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||
)
|
||||
|
||||
const (
|
||||
qrCodeDefaultWidth int = 320
|
||||
qrCodeDefaultHeight int = 320
|
||||
)
|
||||
|
||||
// QrCodesApi represents qrcode generator api
|
||||
type QrCodesApi struct {
|
||||
}
|
||||
|
||||
// Initialize a qrcode generator api singleton instance
|
||||
var (
|
||||
QrCodes = &QrCodesApi{}
|
||||
)
|
||||
|
||||
// MobileUrlQrCodeHandler returns a mobile url qr code image
|
||||
func (a *QrCodesApi) MobileUrlQrCodeHandler(c *core.Context) ([]byte, string, *errs.Error) {
|
||||
fullUrl := settings.Container.Current.RootUrl + "mobile"
|
||||
data, err := a.generateUrlQrCode(c, fullUrl)
|
||||
|
||||
if err != nil {
|
||||
return nil, "", errs.ErrOperationFailed
|
||||
}
|
||||
|
||||
return data, "", nil
|
||||
}
|
||||
|
||||
func (a *QrCodesApi) generateUrlQrCode(c *core.Context, url string) ([]byte, *errs.Error) {
|
||||
qrCodeImg, _ := qr.Encode(url, qr.M, qr.Auto)
|
||||
qrCodeImg, _ = barcode.Scale(qrCodeImg, qrCodeDefaultWidth, qrCodeDefaultHeight)
|
||||
imgData := &bytes.Buffer{}
|
||||
|
||||
if err := png.Encode(imgData, qrCodeImg); err != nil {
|
||||
return nil, errs.ErrOperationFailed
|
||||
}
|
||||
|
||||
return imgData.Bytes(), nil
|
||||
}
|
||||
@@ -26,9 +26,9 @@ var (
|
||||
)
|
||||
|
||||
// TokenListHandler returns available token list of current user
|
||||
func (a *TokensApi) TokenListHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *TokensApi) TokenListHandler(c *core.Context) (any, *errs.Error) {
|
||||
uid := c.GetCurrentUid()
|
||||
tokens, err := a.tokens.GetAllUnexpiredNormalTokensByUid(uid)
|
||||
tokens, err := a.tokens.GetAllUnexpiredNormalTokensByUid(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[tokens.TokenListHandler] failed to get all tokens for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -61,7 +61,7 @@ func (a *TokensApi) TokenListHandler(c *core.Context) (interface{}, *errs.Error)
|
||||
}
|
||||
|
||||
// TokenRevokeCurrentHandler revokes current token of current user
|
||||
func (a *TokensApi) TokenRevokeCurrentHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *TokensApi) TokenRevokeCurrentHandler(c *core.Context) (any, *errs.Error) {
|
||||
_, claims, err := a.tokens.ParseTokenByHeader(c)
|
||||
|
||||
if err != nil {
|
||||
@@ -82,7 +82,7 @@ func (a *TokensApi) TokenRevokeCurrentHandler(c *core.Context) (interface{}, *er
|
||||
}
|
||||
|
||||
tokenId := a.tokens.GenerateTokenId(tokenRecord)
|
||||
err = a.tokens.DeleteToken(tokenRecord)
|
||||
err = a.tokens.DeleteToken(c, tokenRecord)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[token.TokenRevokeCurrentHandler] failed to revoke token \"id:%s\" for user \"uid:%d\", because %s", tokenId, claims.Uid, err.Error())
|
||||
@@ -94,7 +94,7 @@ func (a *TokensApi) TokenRevokeCurrentHandler(c *core.Context) (interface{}, *er
|
||||
}
|
||||
|
||||
// TokenRevokeHandler revokes specific token of current user
|
||||
func (a *TokensApi) TokenRevokeHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *TokensApi) TokenRevokeHandler(c *core.Context) (any, *errs.Error) {
|
||||
var tokenRevokeReq models.TokenRevokeRequest
|
||||
err := c.ShouldBindJSON(&tokenRevokeReq)
|
||||
|
||||
@@ -120,7 +120,7 @@ func (a *TokensApi) TokenRevokeHandler(c *core.Context) (interface{}, *errs.Erro
|
||||
return nil, errs.ErrInvalidTokenId
|
||||
}
|
||||
|
||||
err = a.tokens.DeleteToken(tokenRecord)
|
||||
err = a.tokens.DeleteToken(c, tokenRecord)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[token.TokenRevokeHandler] failed to revoke token \"id:%s\" for user \"uid:%d\", because %s", tokenRevokeReq.TokenId, uid, err.Error())
|
||||
@@ -132,9 +132,9 @@ func (a *TokensApi) TokenRevokeHandler(c *core.Context) (interface{}, *errs.Erro
|
||||
}
|
||||
|
||||
// TokenRevokeAllHandler revokes all tokens of current user except current token
|
||||
func (a *TokensApi) TokenRevokeAllHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *TokensApi) TokenRevokeAllHandler(c *core.Context) (any, *errs.Error) {
|
||||
uid := c.GetCurrentUid()
|
||||
tokens, err := a.tokens.GetAllTokensByUid(uid)
|
||||
tokens, err := a.tokens.GetAllTokensByUid(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[tokens.TokenRevokeAllHandler] failed to get all tokens for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -155,7 +155,7 @@ func (a *TokensApi) TokenRevokeAllHandler(c *core.Context) (interface{}, *errs.E
|
||||
|
||||
tokens = append(tokens[:currentTokenIndex], tokens[currentTokenIndex+1:]...)
|
||||
|
||||
err = a.tokens.DeleteTokens(uid, tokens)
|
||||
err = a.tokens.DeleteTokens(c, uid, tokens)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[token.TokenRevokeAllHandler] failed to revoke all tokens for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -167,16 +167,16 @@ func (a *TokensApi) TokenRevokeAllHandler(c *core.Context) (interface{}, *errs.E
|
||||
}
|
||||
|
||||
// TokenRefreshHandler refresh current token of current user
|
||||
func (a *TokensApi) TokenRefreshHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *TokensApi) TokenRefreshHandler(c *core.Context) (any, *errs.Error) {
|
||||
uid := c.GetCurrentUid()
|
||||
user, err := a.users.GetUserById(uid)
|
||||
user, err := a.users.GetUserById(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[token.TokenRefreshHandler] failed to get user \"uid:%d\" info, because %s", uid, err.Error())
|
||||
return nil, errs.ErrUserNotFound
|
||||
}
|
||||
|
||||
token, claims, err := a.tokens.CreateToken(user, c)
|
||||
token, claims, err := a.tokens.CreateToken(c, user)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[token.TokenRefreshHandler] failed to create token for user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||
@@ -191,6 +191,7 @@ func (a *TokensApi) TokenRefreshHandler(c *core.Context) (interface{}, *errs.Err
|
||||
CreatedUnixTime: oldTokenClaims.IssuedAt,
|
||||
}
|
||||
|
||||
c.SetTextualToken(token)
|
||||
c.SetTokenClaims(claims)
|
||||
|
||||
log.InfofWithRequestId(c, "[token.TokenRefreshHandler] user \"uid:%d\" token refreshed, new token will be expired at %d", user.Uid, claims.ExpiresAt)
|
||||
|
||||
@@ -3,6 +3,8 @@ package api
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||
@@ -23,7 +25,7 @@ var (
|
||||
)
|
||||
|
||||
// CategoryListHandler returns transaction category list of current user
|
||||
func (a *TransactionCategoriesApi) CategoryListHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *TransactionCategoriesApi) CategoryListHandler(c *core.Context) (any, *errs.Error) {
|
||||
var categoryListReq models.TransactionCategoryListRequest
|
||||
err := c.ShouldBindQuery(&categoryListReq)
|
||||
|
||||
@@ -33,7 +35,7 @@ func (a *TransactionCategoriesApi) CategoryListHandler(c *core.Context) (interfa
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
categories, err := a.categories.GetAllCategoriesByUid(uid, categoryListReq.Type, categoryListReq.ParentId)
|
||||
categories, err := a.categories.GetAllCategoriesByUid(c, uid, categoryListReq.Type, categoryListReq.ParentId)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transaction_categories.CategoryListHandler] failed to get categories for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -44,7 +46,7 @@ func (a *TransactionCategoriesApi) CategoryListHandler(c *core.Context) (interfa
|
||||
}
|
||||
|
||||
// CategoryGetHandler returns one specific transaction category of current user
|
||||
func (a *TransactionCategoriesApi) CategoryGetHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *TransactionCategoriesApi) CategoryGetHandler(c *core.Context) (any, *errs.Error) {
|
||||
var categoryGetReq models.TransactionCategoryGetRequest
|
||||
err := c.ShouldBindQuery(&categoryGetReq)
|
||||
|
||||
@@ -54,7 +56,7 @@ func (a *TransactionCategoriesApi) CategoryGetHandler(c *core.Context) (interfac
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
category, err := a.categories.GetCategoryByCategoryId(uid, categoryGetReq.Id)
|
||||
category, err := a.categories.GetCategoryByCategoryId(c, uid, categoryGetReq.Id)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transaction_categories.CategoryGetHandler] failed to get category \"id:%d\" for user \"uid:%d\", because %s", categoryGetReq.Id, uid, err.Error())
|
||||
@@ -67,7 +69,7 @@ func (a *TransactionCategoriesApi) CategoryGetHandler(c *core.Context) (interfac
|
||||
}
|
||||
|
||||
// CategoryCreateHandler saves a new transaction category by request parameters for current user
|
||||
func (a *TransactionCategoriesApi) CategoryCreateHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *TransactionCategoriesApi) CategoryCreateHandler(c *core.Context) (any, *errs.Error) {
|
||||
var categoryCreateReq models.TransactionCategoryCreateRequest
|
||||
err := c.ShouldBindJSON(&categoryCreateReq)
|
||||
|
||||
@@ -84,7 +86,7 @@ func (a *TransactionCategoriesApi) CategoryCreateHandler(c *core.Context) (inter
|
||||
uid := c.GetCurrentUid()
|
||||
|
||||
if categoryCreateReq.ParentId > 0 {
|
||||
parentCategory, err := a.categories.GetCategoryByCategoryId(uid, categoryCreateReq.ParentId)
|
||||
parentCategory, err := a.categories.GetCategoryByCategoryId(c, uid, categoryCreateReq.ParentId)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transaction_categories.CategoryCreateHandler] failed to get parent category \"id:%d\" for user \"uid:%d\", because %s", categoryCreateReq.ParentId, uid, err.Error())
|
||||
@@ -105,9 +107,9 @@ func (a *TransactionCategoriesApi) CategoryCreateHandler(c *core.Context) (inter
|
||||
var maxOrderId int32
|
||||
|
||||
if categoryCreateReq.ParentId <= 0 {
|
||||
maxOrderId, err = a.categories.GetMaxDisplayOrder(uid, categoryCreateReq.Type)
|
||||
maxOrderId, err = a.categories.GetMaxDisplayOrder(c, uid, categoryCreateReq.Type)
|
||||
} else {
|
||||
maxOrderId, err = a.categories.GetMaxSubCategoryDisplayOrder(uid, categoryCreateReq.Type, categoryCreateReq.ParentId)
|
||||
maxOrderId, err = a.categories.GetMaxSubCategoryDisplayOrder(c, uid, categoryCreateReq.Type, categoryCreateReq.ParentId)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -117,7 +119,7 @@ func (a *TransactionCategoriesApi) CategoryCreateHandler(c *core.Context) (inter
|
||||
|
||||
category := a.createNewCategoryModel(uid, &categoryCreateReq, maxOrderId+1)
|
||||
|
||||
err = a.categories.CreateCategory(category)
|
||||
err = a.categories.CreateCategory(c, category)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transaction_categories.CategoryCreateHandler] failed to create category \"id:%d\" for user \"uid:%d\", because %s", category.CategoryId, uid, err.Error())
|
||||
@@ -132,9 +134,9 @@ func (a *TransactionCategoriesApi) CategoryCreateHandler(c *core.Context) (inter
|
||||
}
|
||||
|
||||
// CategoryCreateBatchHandler saves some new transaction category by request parameters for current user
|
||||
func (a *TransactionCategoriesApi) CategoryCreateBatchHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *TransactionCategoriesApi) CategoryCreateBatchHandler(c *core.Context) (any, *errs.Error) {
|
||||
var categoryCreateBatchReq models.TransactionCategoryCreateBatchRequest
|
||||
err := c.ShouldBindJSON(&categoryCreateBatchReq)
|
||||
err := c.ShouldBindBodyWith(&categoryCreateBatchReq, binding.JSON)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[transaction_categories.CategoryCreateBatchHandler] parse request failed, because %s", err.Error())
|
||||
@@ -143,6 +145,149 @@ func (a *TransactionCategoriesApi) CategoryCreateBatchHandler(c *core.Context) (
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
|
||||
categories, err := a.createBatchCategories(c, uid, &categoryCreateBatchReq)
|
||||
|
||||
if err != nil {
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
return a.getTransactionCategoryListByTypeResponse(categories, 0)
|
||||
}
|
||||
|
||||
// CategoryModifyHandler saves an existed transaction category by request parameters for current user
|
||||
func (a *TransactionCategoriesApi) CategoryModifyHandler(c *core.Context) (any, *errs.Error) {
|
||||
var categoryModifyReq models.TransactionCategoryModifyRequest
|
||||
err := c.ShouldBindJSON(&categoryModifyReq)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[transaction_categories.CategoryModifyHandler] parse request failed, because %s", err.Error())
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
category, err := a.categories.GetCategoryByCategoryId(c, uid, categoryModifyReq.Id)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transaction_categories.CategoryModifyHandler] failed to get category \"id:%d\" for user \"uid:%d\", because %s", categoryModifyReq.Id, uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
newCategory := &models.TransactionCategory{
|
||||
CategoryId: category.CategoryId,
|
||||
Uid: uid,
|
||||
Name: categoryModifyReq.Name,
|
||||
Icon: categoryModifyReq.Icon,
|
||||
Color: categoryModifyReq.Color,
|
||||
Comment: categoryModifyReq.Comment,
|
||||
Hidden: categoryModifyReq.Hidden,
|
||||
}
|
||||
|
||||
if newCategory.Name == category.Name &&
|
||||
newCategory.Icon == category.Icon &&
|
||||
newCategory.Color == category.Color &&
|
||||
newCategory.Comment == category.Comment &&
|
||||
newCategory.Hidden == category.Hidden {
|
||||
return nil, errs.ErrNothingWillBeUpdated
|
||||
}
|
||||
|
||||
err = a.categories.ModifyCategory(c, newCategory)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transaction_categories.CategoryModifyHandler] failed to update category \"id:%d\" for user \"uid:%d\", because %s", categoryModifyReq.Id, uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
log.InfofWithRequestId(c, "[transaction_categories.CategoryModifyHandler] user \"uid:%d\" has updated category \"id:%d\" successfully", uid, categoryModifyReq.Id)
|
||||
|
||||
newCategory.Type = category.Type
|
||||
newCategory.ParentCategoryId = category.ParentCategoryId
|
||||
newCategory.DisplayOrder = category.DisplayOrder
|
||||
categoryResp := newCategory.ToTransactionCategoryInfoResponse()
|
||||
|
||||
return categoryResp, nil
|
||||
}
|
||||
|
||||
// CategoryHideHandler hides an existed transaction category by request parameters for current user
|
||||
func (a *TransactionCategoriesApi) CategoryHideHandler(c *core.Context) (any, *errs.Error) {
|
||||
var categoryHideReq models.TransactionCategoryHideRequest
|
||||
err := c.ShouldBindJSON(&categoryHideReq)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[transaction_categories.CategoryHideHandler] parse request failed, because %s", err.Error())
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
err = a.categories.HideCategory(c, uid, []int64{categoryHideReq.Id}, categoryHideReq.Hidden)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transaction_categories.CategoryHideHandler] failed to hide category \"id:%d\" for user \"uid:%d\", because %s", categoryHideReq.Id, uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
log.InfofWithRequestId(c, "[transaction_categories.CategoryHideHandler] user \"uid:%d\" has hidden category \"id:%d\"", uid, categoryHideReq.Id)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// CategoryMoveHandler moves display order of existed transaction categories by request parameters for current user
|
||||
func (a *TransactionCategoriesApi) CategoryMoveHandler(c *core.Context) (any, *errs.Error) {
|
||||
var categoryMoveReq models.TransactionCategoryMoveRequest
|
||||
err := c.ShouldBindJSON(&categoryMoveReq)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[transaction_categories.CategoryMoveHandler] parse request failed, because %s", err.Error())
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
categories := make([]*models.TransactionCategory, len(categoryMoveReq.NewDisplayOrders))
|
||||
|
||||
for i := 0; i < len(categoryMoveReq.NewDisplayOrders); i++ {
|
||||
newDisplayOrder := categoryMoveReq.NewDisplayOrders[i]
|
||||
category := &models.TransactionCategory{
|
||||
Uid: uid,
|
||||
CategoryId: newDisplayOrder.Id,
|
||||
DisplayOrder: newDisplayOrder.DisplayOrder,
|
||||
}
|
||||
|
||||
categories[i] = category
|
||||
}
|
||||
|
||||
err = a.categories.ModifyCategoryDisplayOrders(c, uid, categories)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transaction_categories.CategoryMoveHandler] failed to move categories for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
log.InfofWithRequestId(c, "[transaction_categories.CategoryMoveHandler] user \"uid:%d\" has moved categories", uid)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// CategoryDeleteHandler deletes an existed transaction category by request parameters for current user
|
||||
func (a *TransactionCategoriesApi) CategoryDeleteHandler(c *core.Context) (any, *errs.Error) {
|
||||
var categoryDeleteReq models.TransactionCategoryDeleteRequest
|
||||
err := c.ShouldBindJSON(&categoryDeleteReq)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[transaction_categories.CategoryDeleteHandler] parse request failed, because %s", err.Error())
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
err = a.categories.DeleteCategory(c, uid, categoryDeleteReq.Id)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transaction_categories.CategoryDeleteHandler] failed to delete category \"id:%d\" for user \"uid:%d\", because %s", categoryDeleteReq.Id, uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
log.InfofWithRequestId(c, "[transaction_categories.CategoryDeleteHandler] user \"uid:%d\" has deleted category \"id:%d\"", uid, categoryDeleteReq.Id)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (a *TransactionCategoriesApi) createBatchCategories(c *core.Context, uid int64, categoryCreateBatchReq *models.TransactionCategoryCreateBatchRequest) ([]*models.TransactionCategory, error) {
|
||||
var err error
|
||||
categoryTypeMaxOrderMap := make(map[models.TransactionCategoryType]int32)
|
||||
categoriesMap := make(map[*models.TransactionCategory][]*models.TransactionCategory)
|
||||
categoriesMap[nil] = make([]*models.TransactionCategory, len(categoryCreateBatchReq.Categories))
|
||||
@@ -153,7 +298,7 @@ func (a *TransactionCategoriesApi) CategoryCreateBatchHandler(c *core.Context) (
|
||||
var maxOrderId, exists = categoryTypeMaxOrderMap[categoryCreateReq.Type]
|
||||
|
||||
if !exists {
|
||||
maxOrderId, err = a.categories.GetMaxDisplayOrder(uid, categoryCreateReq.Type)
|
||||
maxOrderId, err = a.categories.GetMaxDisplayOrder(c, uid, categoryCreateReq.Type)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transaction_categories.CategoryCreateBatchHandler] failed to get max display order for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -181,148 +326,16 @@ func (a *TransactionCategoriesApi) CategoryCreateBatchHandler(c *core.Context) (
|
||||
totalCount++
|
||||
}
|
||||
|
||||
categories, err := a.categories.CreateCategories(uid, categoriesMap)
|
||||
categories, err := a.categories.CreateCategories(c, uid, categoriesMap)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transaction_categories.CategoryCreateBatchHandler] failed to create categories for user \"uid:%d\", because %s", uid, err.Error())
|
||||
log.ErrorfWithRequestId(c, "[transaction_categories.createBatchCategories] failed to create categories for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
log.InfofWithRequestId(c, "[transaction_categories.CategoryCreateBatchHandler] user \"uid:%d\" has created categoroies successfully", uid)
|
||||
log.InfofWithRequestId(c, "[transaction_categories.createBatchCategories] user \"uid:%d\" has created categories successfully", uid)
|
||||
|
||||
return a.getTransactionCategoryListByTypeResponse(categories, 0)
|
||||
}
|
||||
|
||||
// CategoryModifyHandler saves an existed transaction category by request parameters for current user
|
||||
func (a *TransactionCategoriesApi) CategoryModifyHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
var categoryModifyReq models.TransactionCategoryModifyRequest
|
||||
err := c.ShouldBindJSON(&categoryModifyReq)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[transaction_categories.CategoryModifyHandler] parse request failed, because %s", err.Error())
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
category, err := a.categories.GetCategoryByCategoryId(uid, categoryModifyReq.Id)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transaction_categories.CategoryModifyHandler] failed to get category \"id:%d\" for user \"uid:%d\", because %s", categoryModifyReq.Id, uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
newCategory := &models.TransactionCategory{
|
||||
CategoryId: category.CategoryId,
|
||||
Uid: uid,
|
||||
Name: categoryModifyReq.Name,
|
||||
Icon: categoryModifyReq.Icon,
|
||||
Color: categoryModifyReq.Color,
|
||||
Comment: categoryModifyReq.Comment,
|
||||
Hidden: categoryModifyReq.Hidden,
|
||||
}
|
||||
|
||||
if newCategory.Name == category.Name &&
|
||||
newCategory.Icon == category.Icon &&
|
||||
newCategory.Color == category.Color &&
|
||||
newCategory.Comment == category.Comment &&
|
||||
newCategory.Hidden == category.Hidden {
|
||||
return nil, errs.ErrNothingWillBeUpdated
|
||||
}
|
||||
|
||||
err = a.categories.ModifyCategory(newCategory)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transaction_categories.CategoryModifyHandler] failed to update category \"id:%d\" for user \"uid:%d\", because %s", categoryModifyReq.Id, uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
log.InfofWithRequestId(c, "[transaction_categories.CategoryModifyHandler] user \"uid:%d\" has updated category \"id:%d\" successfully", uid, categoryModifyReq.Id)
|
||||
|
||||
newCategory.Type = category.Type
|
||||
newCategory.ParentCategoryId = category.ParentCategoryId
|
||||
newCategory.DisplayOrder = category.DisplayOrder
|
||||
categoryResp := newCategory.ToTransactionCategoryInfoResponse()
|
||||
|
||||
return categoryResp, nil
|
||||
}
|
||||
|
||||
// CategoryHideHandler hides an existed transaction category by request parameters for current user
|
||||
func (a *TransactionCategoriesApi) CategoryHideHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
var categoryHideReq models.TransactionCategoryHideRequest
|
||||
err := c.ShouldBindJSON(&categoryHideReq)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[transaction_categories.CategoryHideHandler] parse request failed, because %s", err.Error())
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
err = a.categories.HideCategory(uid, []int64{categoryHideReq.Id}, categoryHideReq.Hidden)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transaction_categories.CategoryHideHandler] failed to hide category \"id:%d\" for user \"uid:%d\", because %s", categoryHideReq.Id, uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
log.InfofWithRequestId(c, "[transaction_categories.CategoryHideHandler] user \"uid:%d\" has hidden category \"id:%d\"", uid, categoryHideReq.Id)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// CategoryMoveHandler moves display order of existed transaction categories by request parameters for current user
|
||||
func (a *TransactionCategoriesApi) CategoryMoveHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
var categoryMoveReq models.TransactionCategoryMoveRequest
|
||||
err := c.ShouldBindJSON(&categoryMoveReq)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[transaction_categories.CategoryMoveHandler] parse request failed, because %s", err.Error())
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
categories := make([]*models.TransactionCategory, len(categoryMoveReq.NewDisplayOrders))
|
||||
|
||||
for i := 0; i < len(categoryMoveReq.NewDisplayOrders); i++ {
|
||||
newDisplayOrder := categoryMoveReq.NewDisplayOrders[i]
|
||||
category := &models.TransactionCategory{
|
||||
Uid: uid,
|
||||
CategoryId: newDisplayOrder.Id,
|
||||
DisplayOrder: newDisplayOrder.DisplayOrder,
|
||||
}
|
||||
|
||||
categories[i] = category
|
||||
}
|
||||
|
||||
err = a.categories.ModifyCategoryDisplayOrders(uid, categories)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transaction_categories.CategoryMoveHandler] failed to move categories for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
log.InfofWithRequestId(c, "[transaction_categories.CategoryMoveHandler] user \"uid:%d\" has moved categories", uid)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// CategoryDeleteHandler deletes an existed transaction category by request parameters for current user
|
||||
func (a *TransactionCategoriesApi) CategoryDeleteHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
var categoryDeleteReq models.TransactionCategoryDeleteRequest
|
||||
err := c.ShouldBindJSON(&categoryDeleteReq)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[transaction_categories.CategoryDeleteHandler] parse request failed, because %s", err.Error())
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
err = a.categories.DeleteCategory(uid, categoryDeleteReq.Id)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transaction_categories.CategoryDeleteHandler] failed to delete category \"id:%d\" for user \"uid:%d\", because %s", categoryDeleteReq.Id, uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
log.InfofWithRequestId(c, "[transaction_categories.CategoryDeleteHandler] user \"uid:%d\" has deleted category \"id:%d\"", uid, categoryDeleteReq.Id)
|
||||
return true, nil
|
||||
return categories, nil
|
||||
}
|
||||
|
||||
func (a *TransactionCategoriesApi) createNewCategoryModel(uid int64, categoryCreateReq *models.TransactionCategoryCreateRequest, order int32) *models.TransactionCategory {
|
||||
|
||||
@@ -23,9 +23,9 @@ var (
|
||||
)
|
||||
|
||||
// TagListHandler returns transaction tag list of current user
|
||||
func (a *TransactionTagsApi) TagListHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *TransactionTagsApi) TagListHandler(c *core.Context) (any, *errs.Error) {
|
||||
uid := c.GetCurrentUid()
|
||||
tags, err := a.tags.GetAllTagsByUid(uid)
|
||||
tags, err := a.tags.GetAllTagsByUid(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transaction_tags.TagListHandler] failed to get tags for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -44,7 +44,7 @@ func (a *TransactionTagsApi) TagListHandler(c *core.Context) (interface{}, *errs
|
||||
}
|
||||
|
||||
// TagGetHandler returns one specific transaction tag of current user
|
||||
func (a *TransactionTagsApi) TagGetHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *TransactionTagsApi) TagGetHandler(c *core.Context) (any, *errs.Error) {
|
||||
var tagGetReq models.TransactionTagGetRequest
|
||||
err := c.ShouldBindQuery(&tagGetReq)
|
||||
|
||||
@@ -54,7 +54,7 @@ func (a *TransactionTagsApi) TagGetHandler(c *core.Context) (interface{}, *errs.
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
tag, err := a.tags.GetTagByTagId(uid, tagGetReq.Id)
|
||||
tag, err := a.tags.GetTagByTagId(c, uid, tagGetReq.Id)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transaction_tags.TagGetHandler] failed to get tag \"id:%d\" for user \"uid:%d\", because %s", tagGetReq.Id, uid, err.Error())
|
||||
@@ -67,7 +67,7 @@ func (a *TransactionTagsApi) TagGetHandler(c *core.Context) (interface{}, *errs.
|
||||
}
|
||||
|
||||
// TagCreateHandler saves a new transaction tag by request parameters for current user
|
||||
func (a *TransactionTagsApi) TagCreateHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *TransactionTagsApi) TagCreateHandler(c *core.Context) (any, *errs.Error) {
|
||||
var tagCreateReq models.TransactionTagCreateRequest
|
||||
err := c.ShouldBindJSON(&tagCreateReq)
|
||||
|
||||
@@ -78,7 +78,7 @@ func (a *TransactionTagsApi) TagCreateHandler(c *core.Context) (interface{}, *er
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
|
||||
maxOrderId, err := a.tags.GetMaxDisplayOrder(uid)
|
||||
maxOrderId, err := a.tags.GetMaxDisplayOrder(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transaction_tags.TagCreateHandler] failed to get max display order for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -87,7 +87,7 @@ func (a *TransactionTagsApi) TagCreateHandler(c *core.Context) (interface{}, *er
|
||||
|
||||
tag := a.createNewTagModel(uid, &tagCreateReq, maxOrderId+1)
|
||||
|
||||
err = a.tags.CreateTag(tag)
|
||||
err = a.tags.CreateTag(c, tag)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transaction_tags.TagCreateHandler] failed to create tag \"id:%d\" for user \"uid:%d\", because %s", tag.TagId, uid, err.Error())
|
||||
@@ -102,7 +102,7 @@ func (a *TransactionTagsApi) TagCreateHandler(c *core.Context) (interface{}, *er
|
||||
}
|
||||
|
||||
// TagModifyHandler saves an existed transaction tag by request parameters for current user
|
||||
func (a *TransactionTagsApi) TagModifyHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *TransactionTagsApi) TagModifyHandler(c *core.Context) (any, *errs.Error) {
|
||||
var tagModifyReq models.TransactionTagModifyRequest
|
||||
err := c.ShouldBindJSON(&tagModifyReq)
|
||||
|
||||
@@ -112,7 +112,7 @@ func (a *TransactionTagsApi) TagModifyHandler(c *core.Context) (interface{}, *er
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
tag, err := a.tags.GetTagByTagId(uid, tagModifyReq.Id)
|
||||
tag, err := a.tags.GetTagByTagId(c, uid, tagModifyReq.Id)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transaction_tags.TagModifyHandler] failed to get tag \"id:%d\" for user \"uid:%d\", because %s", tagModifyReq.Id, uid, err.Error())
|
||||
@@ -129,7 +129,7 @@ func (a *TransactionTagsApi) TagModifyHandler(c *core.Context) (interface{}, *er
|
||||
return nil, errs.ErrNothingWillBeUpdated
|
||||
}
|
||||
|
||||
err = a.tags.ModifyTag(newTag)
|
||||
err = a.tags.ModifyTag(c, newTag)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transaction_tags.TagModifyHandler] failed to update tag \"id:%d\" for user \"uid:%d\", because %s", tagModifyReq.Id, uid, err.Error())
|
||||
@@ -145,7 +145,7 @@ func (a *TransactionTagsApi) TagModifyHandler(c *core.Context) (interface{}, *er
|
||||
}
|
||||
|
||||
// TagHideHandler hides an transaction tag by request parameters for current user
|
||||
func (a *TransactionTagsApi) TagHideHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *TransactionTagsApi) TagHideHandler(c *core.Context) (any, *errs.Error) {
|
||||
var tagHideReq models.TransactionTagHideRequest
|
||||
err := c.ShouldBindJSON(&tagHideReq)
|
||||
|
||||
@@ -155,7 +155,7 @@ func (a *TransactionTagsApi) TagHideHandler(c *core.Context) (interface{}, *errs
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
err = a.tags.HideTag(uid, []int64{tagHideReq.Id}, tagHideReq.Hidden)
|
||||
err = a.tags.HideTag(c, uid, []int64{tagHideReq.Id}, tagHideReq.Hidden)
|
||||
|
||||
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())
|
||||
@@ -167,7 +167,7 @@ func (a *TransactionTagsApi) TagHideHandler(c *core.Context) (interface{}, *errs
|
||||
}
|
||||
|
||||
// TagMoveHandler moves display order of existed transaction tags by request parameters for current user
|
||||
func (a *TransactionTagsApi) TagMoveHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *TransactionTagsApi) TagMoveHandler(c *core.Context) (any, *errs.Error) {
|
||||
var tagMoveReq models.TransactionTagMoveRequest
|
||||
err := c.ShouldBindJSON(&tagMoveReq)
|
||||
|
||||
@@ -190,7 +190,7 @@ func (a *TransactionTagsApi) TagMoveHandler(c *core.Context) (interface{}, *errs
|
||||
tags[i] = tag
|
||||
}
|
||||
|
||||
err = a.tags.ModifyTagDisplayOrders(uid, tags)
|
||||
err = a.tags.ModifyTagDisplayOrders(c, uid, tags)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transaction_tags.CategoryMoveHandler] failed to move tags for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -202,7 +202,7 @@ func (a *TransactionTagsApi) TagMoveHandler(c *core.Context) (interface{}, *errs
|
||||
}
|
||||
|
||||
// TagDeleteHandler deletes an existed transaction tag by request parameters for current user
|
||||
func (a *TransactionTagsApi) TagDeleteHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *TransactionTagsApi) TagDeleteHandler(c *core.Context) (any, *errs.Error) {
|
||||
var tagDeleteReq models.TransactionTagDeleteRequest
|
||||
err := c.ShouldBindJSON(&tagDeleteReq)
|
||||
|
||||
@@ -212,7 +212,7 @@ func (a *TransactionTagsApi) TagDeleteHandler(c *core.Context) (interface{}, *er
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
err = a.tags.DeleteTag(uid, tagDeleteReq.Id)
|
||||
err = a.tags.DeleteTag(c, uid, tagDeleteReq.Id)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transaction_tags.TagDeleteHandler] failed to delete tag \"id:%d\" for user \"uid:%d\", because %s", tagDeleteReq.Id, uid, err.Error())
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
orderedmap "github.com/wk8/go-ordered-map/v2"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||
@@ -35,7 +37,7 @@ var (
|
||||
)
|
||||
|
||||
// TransactionCountHandler returns transaction total count of current user
|
||||
func (a *TransactionsApi) TransactionCountHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *TransactionsApi) TransactionCountHandler(c *core.Context) (any, *errs.Error) {
|
||||
var transactionCountReq models.TransactionCountRequest
|
||||
err := c.ShouldBindQuery(&transactionCountReq)
|
||||
|
||||
@@ -46,21 +48,21 @@ func (a *TransactionsApi) TransactionCountHandler(c *core.Context) (interface{},
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
|
||||
allAccountIds, err := a.getAccountOrSubAccountIds(transactionCountReq.AccountId, uid)
|
||||
allAccountIds, err := a.getAccountOrSubAccountIds(c, transactionCountReq.AccountId, uid)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[transactions.TransactionCountHandler] get account error, because %s", err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
allCategoryIds, err := a.getCategoryOrSubCategoryIds(transactionCountReq.CategoryId, uid)
|
||||
allCategoryIds, err := a.getCategoryOrSubCategoryIds(c, transactionCountReq.CategoryId, uid)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[transactions.TransactionCountHandler] get transaction category error, because %s", err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
totalCount, err := a.transactions.GetTransactionCount(uid, transactionCountReq.MaxTime, transactionCountReq.MinTime, transactionCountReq.Type, allCategoryIds, allAccountIds, transactionCountReq.Keyword)
|
||||
totalCount, err := a.transactions.GetTransactionCount(c, uid, transactionCountReq.MaxTime, transactionCountReq.MinTime, transactionCountReq.Type, allCategoryIds, allAccountIds, transactionCountReq.Keyword)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transactions.TransactionCountHandler] failed to get transaction count for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -75,7 +77,7 @@ func (a *TransactionsApi) TransactionCountHandler(c *core.Context) (interface{},
|
||||
}
|
||||
|
||||
// TransactionListHandler returns transaction list of current user
|
||||
func (a *TransactionsApi) TransactionListHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *TransactionsApi) TransactionListHandler(c *core.Context) (any, *errs.Error) {
|
||||
var transactionListReq models.TransactionListByMaxTimeRequest
|
||||
err := c.ShouldBindQuery(&transactionListReq)
|
||||
|
||||
@@ -92,7 +94,7 @@ func (a *TransactionsApi) TransactionListHandler(c *core.Context) (interface{},
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
user, err := a.users.GetUserById(uid)
|
||||
user, err := a.users.GetUserById(c, uid)
|
||||
|
||||
if err != nil {
|
||||
if !errs.IsCustomError(err) {
|
||||
@@ -102,21 +104,32 @@ func (a *TransactionsApi) TransactionListHandler(c *core.Context) (interface{},
|
||||
return nil, errs.ErrUserNotFound
|
||||
}
|
||||
|
||||
allAccountIds, err := a.getAccountOrSubAccountIds(transactionListReq.AccountId, uid)
|
||||
allAccountIds, err := a.getAccountOrSubAccountIds(c, transactionListReq.AccountId, uid)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[transactions.TransactionListHandler] get account error, because %s", err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
allCategoryIds, err := a.getCategoryOrSubCategoryIds(transactionListReq.CategoryId, uid)
|
||||
allCategoryIds, err := a.getCategoryOrSubCategoryIds(c, transactionListReq.CategoryId, uid)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[transactions.TransactionListHandler] get transaction category error, because %s", err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
transactions, err := a.transactions.GetTransactionsByMaxTime(uid, transactionListReq.MaxTime, transactionListReq.MinTime, transactionListReq.Type, allCategoryIds, allAccountIds, transactionListReq.Keyword, transactionListReq.Count+1, true)
|
||||
var totalCount int64
|
||||
|
||||
if transactionListReq.WithCount {
|
||||
totalCount, err = a.transactions.GetTransactionCount(c, uid, transactionListReq.MaxTime, transactionListReq.MinTime, transactionListReq.Type, allCategoryIds, allAccountIds, transactionListReq.Keyword)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transactions.TransactionListHandler] failed to get transaction count for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
}
|
||||
|
||||
transactions, err := a.transactions.GetTransactionsByMaxTime(c, uid, transactionListReq.MaxTime, transactionListReq.MinTime, transactionListReq.Type, allCategoryIds, allAccountIds, transactionListReq.Keyword, transactionListReq.Page, transactionListReq.Count, true, true)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transactions.TransactionListHandler] failed to get transactions earlier than \"%d\" for user \"uid:%d\", because %s", transactionListReq.MaxTime, uid, err.Error())
|
||||
@@ -147,11 +160,15 @@ func (a *TransactionsApi) TransactionListHandler(c *core.Context) (interface{},
|
||||
transactionResps.NextTimeSequenceId = nextTimeSequenceId
|
||||
}
|
||||
|
||||
if transactionListReq.WithCount {
|
||||
transactionResps.TotalCount = &totalCount
|
||||
}
|
||||
|
||||
return transactionResps, nil
|
||||
}
|
||||
|
||||
// TransactionMonthListHandler returns transaction list of current user by month
|
||||
func (a *TransactionsApi) TransactionMonthListHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
// TransactionMonthListHandler returns all transaction list of current user by month
|
||||
func (a *TransactionsApi) TransactionMonthListHandler(c *core.Context) (any, *errs.Error) {
|
||||
var transactionListReq models.TransactionListInMonthByPageRequest
|
||||
err := c.ShouldBindQuery(&transactionListReq)
|
||||
|
||||
@@ -168,7 +185,7 @@ func (a *TransactionsApi) TransactionMonthListHandler(c *core.Context) (interfac
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
user, err := a.users.GetUserById(uid)
|
||||
user, err := a.users.GetUserById(c, uid)
|
||||
|
||||
if err != nil {
|
||||
if !errs.IsCustomError(err) {
|
||||
@@ -178,34 +195,27 @@ func (a *TransactionsApi) TransactionMonthListHandler(c *core.Context) (interfac
|
||||
return nil, errs.ErrUserNotFound
|
||||
}
|
||||
|
||||
allAccountIds, err := a.getAccountOrSubAccountIds(transactionListReq.AccountId, uid)
|
||||
allAccountIds, err := a.getAccountOrSubAccountIds(c, transactionListReq.AccountId, uid)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[transactions.TransactionMonthListHandler] get account error, because %s", err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
allCategoryIds, err := a.getCategoryOrSubCategoryIds(transactionListReq.CategoryId, uid)
|
||||
allCategoryIds, err := a.getCategoryOrSubCategoryIds(c, transactionListReq.CategoryId, uid)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[transactions.TransactionMonthListHandler] get transaction category error, because %s", err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
transactions, err := a.transactions.GetTransactionsInMonthByPage(uid, transactionListReq.Year, transactionListReq.Month, transactionListReq.Type, allCategoryIds, allAccountIds, transactionListReq.Keyword, transactionListReq.Page, transactionListReq.Count, utcOffset)
|
||||
transactions, err := a.transactions.GetTransactionsInMonthByPage(c, uid, transactionListReq.Year, transactionListReq.Month, transactionListReq.Type, allCategoryIds, allAccountIds, transactionListReq.Keyword)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transactions.TransactionMonthListHandler] failed to get transactions in month \"%d-%d\" for user \"uid:%d\", because %s", transactionListReq.Year, transactionListReq.Month, uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
totalCount, err := a.transactions.GetMonthTransactionCount(uid, transactionListReq.Year, transactionListReq.Month, transactionListReq.Type, allCategoryIds, allAccountIds, transactionListReq.Keyword, utcOffset)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transactions.TransactionMonthListHandler] failed to get transaction count in month \"%d-%d\" for user \"uid:%d\", because %s", transactionListReq.Year, transactionListReq.Month, uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
transactionResult, err := a.getTransactionListResult(c, user, transactions, utcOffset, transactionListReq.TrimAccount, transactionListReq.TrimCategory, transactionListReq.TrimTag)
|
||||
|
||||
if err != nil {
|
||||
@@ -215,14 +225,14 @@ func (a *TransactionsApi) TransactionMonthListHandler(c *core.Context) (interfac
|
||||
|
||||
transactionResps := &models.TransactionInfoPageWrapperResponse2{
|
||||
Items: transactionResult,
|
||||
TotalCount: totalCount,
|
||||
TotalCount: int64(transactionResult.Len()),
|
||||
}
|
||||
|
||||
return transactionResps, nil
|
||||
}
|
||||
|
||||
// TransactionStatisticsHandler returns transaction statistics of current user
|
||||
func (a *TransactionsApi) TransactionStatisticsHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *TransactionsApi) TransactionStatisticsHandler(c *core.Context) (any, *errs.Error) {
|
||||
var statisticReq models.TransactionStatisticRequest
|
||||
err := c.ShouldBindQuery(&statisticReq)
|
||||
|
||||
@@ -232,7 +242,7 @@ func (a *TransactionsApi) TransactionStatisticsHandler(c *core.Context) (interfa
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
totalAmounts, err := a.transactions.GetAccountsAndCategoriesTotalIncomeAndExpense(uid, statisticReq.StartTime, statisticReq.EndTime)
|
||||
totalAmounts, err := a.transactions.GetAccountsAndCategoriesTotalIncomeAndExpense(c, uid, statisticReq.StartTime, statisticReq.EndTime)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transactions.TransactionStatisticsHandler] failed to get accounts and categories total income and expense for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -259,7 +269,7 @@ func (a *TransactionsApi) TransactionStatisticsHandler(c *core.Context) (interfa
|
||||
}
|
||||
|
||||
// TransactionAmountsHandler returns transaction amounts of current user
|
||||
func (a *TransactionsApi) TransactionAmountsHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *TransactionsApi) TransactionAmountsHandler(c *core.Context) (any, *errs.Error) {
|
||||
var transactionAmountsReq models.TransactionAmountsRequest
|
||||
err := c.ShouldBindQuery(&transactionAmountsReq)
|
||||
|
||||
@@ -280,14 +290,14 @@ func (a *TransactionsApi) TransactionAmountsHandler(c *core.Context) (interface{
|
||||
return nil, errs.ErrQueryItemsEmpty
|
||||
}
|
||||
|
||||
if len(requestItems) > 5 {
|
||||
if len(requestItems) > 20 {
|
||||
log.WarnfWithRequestId(c, "[transactions.TransactionAmountsHandler] parse request failed, because there are too many items")
|
||||
return nil, errs.ErrQueryItemsTooMuch
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
|
||||
accounts, err := a.accounts.GetAllAccountsByUid(uid)
|
||||
accounts, err := a.accounts.GetAllAccountsByUid(c, uid)
|
||||
accountMap := a.accounts.GetAccountMapByList(accounts)
|
||||
|
||||
if err != nil {
|
||||
@@ -295,12 +305,12 @@ func (a *TransactionsApi) TransactionAmountsHandler(c *core.Context) (interface{
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
amountsResp := make(map[string]*models.TransactionAmountsResponseItem)
|
||||
amountsResp := orderedmap.New[string, *models.TransactionAmountsResponseItem]()
|
||||
|
||||
for i := 0; i < len(requestItems); i++ {
|
||||
requestItem := requestItems[i]
|
||||
|
||||
incomeAmounts, expenseAmounts, err := a.transactions.GetAccountsTotalIncomeAndExpense(uid, requestItem.StartTime, requestItem.EndTime)
|
||||
incomeAmounts, expenseAmounts, err := a.transactions.GetAccountsTotalIncomeAndExpense(c, uid, requestItem.StartTime, requestItem.EndTime)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transactions.TransactionAmountsHandler] failed to get transaction amounts item for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -353,24 +363,26 @@ func (a *TransactionsApi) TransactionAmountsHandler(c *core.Context) (interface{
|
||||
amountsMap[account.Currency] = totalAmounts
|
||||
}
|
||||
|
||||
allTotalAmounts := make([]*models.TransactionAmountsResponseItemAmountInfo, 0)
|
||||
allTotalAmounts := make(models.TransactionAmountsResponseItemAmountInfoSlice, 0)
|
||||
|
||||
for _, totalAmounts := range amountsMap {
|
||||
allTotalAmounts = append(allTotalAmounts, totalAmounts)
|
||||
}
|
||||
|
||||
amountsResp[requestItem.Name] = &models.TransactionAmountsResponseItem{
|
||||
sort.Sort(allTotalAmounts)
|
||||
|
||||
amountsResp.Set(requestItem.Name, &models.TransactionAmountsResponseItem{
|
||||
StartTime: requestItem.StartTime,
|
||||
EndTime: requestItem.EndTime,
|
||||
Amounts: allTotalAmounts,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return amountsResp, nil
|
||||
}
|
||||
|
||||
// TransactionMonthAmountsHandler returns every month transaction amounts of current user
|
||||
func (a *TransactionsApi) TransactionMonthAmountsHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *TransactionsApi) TransactionMonthAmountsHandler(c *core.Context) (any, *errs.Error) {
|
||||
var transactionAmountsReq models.TransactionMonthAmountsRequest
|
||||
err := c.ShouldBindQuery(&transactionAmountsReq)
|
||||
|
||||
@@ -395,7 +407,7 @@ func (a *TransactionsApi) TransactionMonthAmountsHandler(c *core.Context) (inter
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
|
||||
accounts, err := a.accounts.GetAllAccountsByUid(uid)
|
||||
accounts, err := a.accounts.GetAllAccountsByUid(c, uid)
|
||||
accountMap := a.accounts.GetAccountMapByList(accounts)
|
||||
|
||||
if err != nil {
|
||||
@@ -403,7 +415,7 @@ func (a *TransactionsApi) TransactionMonthAmountsHandler(c *core.Context) (inter
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
totalAmounts, err := a.transactions.GetAccountsMonthTotalIncomeAndExpense(uid, startTime, endTime, pageCountForLoadTransactionAmounts)
|
||||
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())
|
||||
@@ -463,12 +475,14 @@ func (a *TransactionsApi) TransactionMonthAmountsHandler(c *core.Context) (inter
|
||||
continue
|
||||
}
|
||||
|
||||
amounts := make([]*models.TransactionAmountsResponseItemAmountInfo, 0, len(monthTotalAmounts))
|
||||
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,
|
||||
@@ -482,7 +496,7 @@ func (a *TransactionsApi) TransactionMonthAmountsHandler(c *core.Context) (inter
|
||||
}
|
||||
|
||||
// TransactionGetHandler returns one specific transaction of current user
|
||||
func (a *TransactionsApi) TransactionGetHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *TransactionsApi) TransactionGetHandler(c *core.Context) (any, *errs.Error) {
|
||||
var transactionGetReq models.TransactionGetRequest
|
||||
err := c.ShouldBindQuery(&transactionGetReq)
|
||||
|
||||
@@ -499,7 +513,7 @@ func (a *TransactionsApi) TransactionGetHandler(c *core.Context) (interface{}, *
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
user, err := a.users.GetUserById(uid)
|
||||
user, err := a.users.GetUserById(c, uid)
|
||||
|
||||
if err != nil {
|
||||
if !errs.IsCustomError(err) {
|
||||
@@ -509,7 +523,7 @@ func (a *TransactionsApi) TransactionGetHandler(c *core.Context) (interface{}, *
|
||||
return nil, errs.ErrUserNotFound
|
||||
}
|
||||
|
||||
transaction, err := a.transactions.GetTransactionByTransactionId(uid, transactionGetReq.Id)
|
||||
transaction, err := a.transactions.GetTransactionByTransactionId(c, uid, transactionGetReq.Id)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transactions.TransactionGetHandler] failed to get transaction \"id:%d\" for user \"uid:%d\", because %s", transactionGetReq.Id, uid, err.Error())
|
||||
@@ -528,7 +542,7 @@ func (a *TransactionsApi) TransactionGetHandler(c *core.Context) (interface{}, *
|
||||
accountIds = utils.ToUniqueInt64Slice(accountIds)
|
||||
}
|
||||
|
||||
accountMap, err := a.accounts.GetAccountsByAccountIds(uid, accountIds)
|
||||
accountMap, err := a.accounts.GetAccountsByAccountIds(c, uid, accountIds)
|
||||
|
||||
if _, exists := accountMap[transaction.AccountId]; !exists {
|
||||
log.WarnfWithRequestId(c, "[transactions.TransactionGetHandler] account of transaction \"id:%d\" does not exist for user \"uid:%d\"", transaction.TransactionId, uid)
|
||||
@@ -542,7 +556,7 @@ func (a *TransactionsApi) TransactionGetHandler(c *core.Context) (interface{}, *
|
||||
}
|
||||
}
|
||||
|
||||
allTransactionTagIds, err := a.transactionTags.GetAllTagIdsOfTransactions(uid, []int64{transaction.TransactionId})
|
||||
allTransactionTagIds, err := a.transactionTags.GetAllTagIdsOfTransactions(c, uid, []int64{transaction.TransactionId})
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transactions.TransactionGetHandler] failed to get transactions tag ids for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -553,7 +567,7 @@ func (a *TransactionsApi) TransactionGetHandler(c *core.Context) (interface{}, *
|
||||
var tagMap map[int64]*models.TransactionTag
|
||||
|
||||
if !transactionGetReq.TrimCategory {
|
||||
category, err = a.transactionCategories.GetCategoryByCategoryId(uid, transaction.CategoryId)
|
||||
category, err = a.transactionCategories.GetCategoryByCategoryId(c, uid, transaction.CategoryId)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transactions.TransactionGetHandler] failed to get transactions category for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -562,7 +576,7 @@ func (a *TransactionsApi) TransactionGetHandler(c *core.Context) (interface{}, *
|
||||
}
|
||||
|
||||
if !transactionGetReq.TrimTag {
|
||||
tagMap, err = a.transactionTags.GetTagsByTagIds(uid, utils.ToUniqueInt64Slice(a.getTransactionTagIds(allTransactionTagIds)))
|
||||
tagMap, err = a.transactionTags.GetTagsByTagIds(c, uid, utils.ToUniqueInt64Slice(a.getTransactionTagIds(allTransactionTagIds)))
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transactions.TransactionGetHandler] failed to get transactions tags for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -598,7 +612,7 @@ func (a *TransactionsApi) TransactionGetHandler(c *core.Context) (interface{}, *
|
||||
}
|
||||
|
||||
// TransactionCreateHandler saves a new transaction by request parameters for current user
|
||||
func (a *TransactionsApi) TransactionCreateHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *TransactionsApi) TransactionCreateHandler(c *core.Context) (any, *errs.Error) {
|
||||
var transactionCreateReq models.TransactionCreateRequest
|
||||
err := c.ShouldBindJSON(&transactionCreateReq)
|
||||
|
||||
@@ -638,7 +652,7 @@ func (a *TransactionsApi) TransactionCreateHandler(c *core.Context) (interface{}
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
user, err := a.users.GetUserById(uid)
|
||||
user, err := a.users.GetUserById(c, uid)
|
||||
|
||||
if err != nil {
|
||||
if !errs.IsCustomError(err) {
|
||||
@@ -655,7 +669,7 @@ func (a *TransactionsApi) TransactionCreateHandler(c *core.Context) (interface{}
|
||||
return nil, errs.ErrCannotCreateTransactionWithThisTransactionTime
|
||||
}
|
||||
|
||||
err = a.transactions.CreateTransaction(transaction, tagIds)
|
||||
err = a.transactions.CreateTransaction(c, transaction, tagIds)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transactions.TransactionCreateHandler] failed to create transaction \"id:%d\" for user \"uid:%d\", because %s", transaction.TransactionId, uid, err.Error())
|
||||
@@ -670,7 +684,7 @@ func (a *TransactionsApi) TransactionCreateHandler(c *core.Context) (interface{}
|
||||
}
|
||||
|
||||
// TransactionModifyHandler saves an existed transaction by request parameters for current user
|
||||
func (a *TransactionsApi) TransactionModifyHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *TransactionsApi) TransactionModifyHandler(c *core.Context) (any, *errs.Error) {
|
||||
var transactionModifyReq models.TransactionModifyRequest
|
||||
err := c.ShouldBindJSON(&transactionModifyReq)
|
||||
|
||||
@@ -687,7 +701,7 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.Context) (interface{}
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
user, err := a.users.GetUserById(uid)
|
||||
user, err := a.users.GetUserById(c, uid)
|
||||
|
||||
if err != nil {
|
||||
if !errs.IsCustomError(err) {
|
||||
@@ -697,7 +711,7 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.Context) (interface{}
|
||||
return nil, errs.ErrUserNotFound
|
||||
}
|
||||
|
||||
transaction, err := a.transactions.GetTransactionByTransactionId(uid, transactionModifyReq.Id)
|
||||
transaction, err := a.transactions.GetTransactionByTransactionId(c, uid, transactionModifyReq.Id)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transactions.TransactionModifyHandler] failed to get transaction \"id:%d\" for user \"uid:%d\", because %s", transactionModifyReq.Id, uid, err.Error())
|
||||
@@ -709,7 +723,7 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.Context) (interface{}
|
||||
return nil, errs.ErrTransactionTypeInvalid
|
||||
}
|
||||
|
||||
allTransactionTagIds, err := a.transactionTags.GetAllTagIdsOfTransactions(uid, []int64{transaction.TransactionId})
|
||||
allTransactionTagIds, err := a.transactionTags.GetAllTagIdsOfTransactions(c, uid, []int64{transaction.TransactionId})
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transactions.TransactionModifyHandler] failed to get transactions tag ids for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -774,7 +788,7 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.Context) (interface{}
|
||||
return nil, errs.ErrCannotModifyTransactionWithThisTransactionTime
|
||||
}
|
||||
|
||||
err = a.transactions.ModifyTransaction(newTransaction, addTransactionTagIds, removeTransactionTagIds)
|
||||
err = a.transactions.ModifyTransaction(c, newTransaction, addTransactionTagIds, removeTransactionTagIds)
|
||||
|
||||
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())
|
||||
@@ -790,7 +804,7 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.Context) (interface{}
|
||||
}
|
||||
|
||||
// TransactionDeleteHandler deletes an existed transaction by request parameters for current user
|
||||
func (a *TransactionsApi) TransactionDeleteHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *TransactionsApi) TransactionDeleteHandler(c *core.Context) (any, *errs.Error) {
|
||||
var transactionDeleteReq models.TransactionDeleteRequest
|
||||
err := c.ShouldBindJSON(&transactionDeleteReq)
|
||||
|
||||
@@ -807,7 +821,7 @@ func (a *TransactionsApi) TransactionDeleteHandler(c *core.Context) (interface{}
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
user, err := a.users.GetUserById(uid)
|
||||
user, err := a.users.GetUserById(c, uid)
|
||||
|
||||
if err != nil {
|
||||
if !errs.IsCustomError(err) {
|
||||
@@ -817,7 +831,7 @@ func (a *TransactionsApi) TransactionDeleteHandler(c *core.Context) (interface{}
|
||||
return nil, errs.ErrUserNotFound
|
||||
}
|
||||
|
||||
transaction, err := a.transactions.GetTransactionByTransactionId(uid, transactionDeleteReq.Id)
|
||||
transaction, err := a.transactions.GetTransactionByTransactionId(c, uid, transactionDeleteReq.Id)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transactions.TransactionDeleteHandler] failed to get transaction \"id:%d\" for user \"uid:%d\", because %s", transactionDeleteReq.Id, uid, err.Error())
|
||||
@@ -835,7 +849,7 @@ func (a *TransactionsApi) TransactionDeleteHandler(c *core.Context) (interface{}
|
||||
return nil, errs.ErrCannotDeleteTransactionWithThisTransactionTime
|
||||
}
|
||||
|
||||
err = a.transactions.DeleteTransaction(uid, transactionDeleteReq.Id)
|
||||
err = a.transactions.DeleteTransaction(c, uid, transactionDeleteReq.Id)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transactions.TransactionDeleteHandler] failed to delete transaction \"id:%d\" for user \"uid:%d\", because %s", transactionDeleteReq.Id, uid, err.Error())
|
||||
@@ -870,11 +884,11 @@ func (a *TransactionsApi) filterTransactions(c *core.Context, uid int64, transac
|
||||
return finalTransactions
|
||||
}
|
||||
|
||||
func (a *TransactionsApi) getAccountOrSubAccountIds(accountId int64, uid int64) ([]int64, error) {
|
||||
func (a *TransactionsApi) getAccountOrSubAccountIds(c *core.Context, accountId int64, uid int64) ([]int64, error) {
|
||||
var allAccountIds []int64
|
||||
|
||||
if accountId > 0 {
|
||||
allSubAccounts, err := a.accounts.GetSubAccountsByAccountId(uid, accountId)
|
||||
allSubAccounts, err := a.accounts.GetSubAccountsByAccountId(c, uid, accountId)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -892,11 +906,11 @@ func (a *TransactionsApi) getAccountOrSubAccountIds(accountId int64, uid int64)
|
||||
return allAccountIds, nil
|
||||
}
|
||||
|
||||
func (a *TransactionsApi) getCategoryOrSubCategoryIds(categoryId int64, uid int64) ([]int64, error) {
|
||||
func (a *TransactionsApi) getCategoryOrSubCategoryIds(c *core.Context, categoryId int64, uid int64) ([]int64, error) {
|
||||
var allCategoryIds []int64
|
||||
|
||||
if categoryId > 0 {
|
||||
allSubCategories, err := a.transactionCategories.GetAllCategoriesByUid(uid, 0, categoryId)
|
||||
allSubCategories, err := a.transactionCategories.GetAllCategoriesByUid(c, uid, 0, categoryId)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -963,7 +977,7 @@ func (a *TransactionsApi) getTransactionListResult(c *core.Context, user *models
|
||||
categoryIds = append(categoryIds, transactions[i].CategoryId)
|
||||
}
|
||||
|
||||
allAccounts, err := a.accounts.GetAccountsByAccountIds(uid, utils.ToUniqueInt64Slice(accountIds))
|
||||
allAccounts, err := a.accounts.GetAccountsByAccountIds(c, uid, utils.ToUniqueInt64Slice(accountIds))
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transactions.getTransactionListResult] failed to get accounts for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -972,7 +986,7 @@ func (a *TransactionsApi) getTransactionListResult(c *core.Context, user *models
|
||||
|
||||
transactions = a.filterTransactions(c, uid, transactions, allAccounts)
|
||||
|
||||
allTransactionTagIds, err := a.transactionTags.GetAllTagIdsOfTransactions(uid, transactionIds)
|
||||
allTransactionTagIds, err := a.transactionTags.GetAllTagIdsOfTransactions(c, uid, transactionIds)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transactions.getTransactionListResult] failed to get transactions tag ids for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -983,7 +997,7 @@ func (a *TransactionsApi) getTransactionListResult(c *core.Context, user *models
|
||||
var tagMap map[int64]*models.TransactionTag
|
||||
|
||||
if !trimCategory {
|
||||
categoryMap, err = a.transactionCategories.GetCategoriesByCategoryIds(uid, utils.ToUniqueInt64Slice(categoryIds))
|
||||
categoryMap, err = a.transactionCategories.GetCategoriesByCategoryIds(c, uid, utils.ToUniqueInt64Slice(categoryIds))
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transactions.getTransactionListResult] failed to get transactions categories for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -992,7 +1006,7 @@ func (a *TransactionsApi) getTransactionListResult(c *core.Context, user *models
|
||||
}
|
||||
|
||||
if !trimTag {
|
||||
tagMap, err = a.transactionTags.GetTagsByTagIds(uid, utils.ToUniqueInt64Slice(a.getTransactionTagIds(allTransactionTagIds)))
|
||||
tagMap, err = a.transactionTags.GetTagsByTagIds(c, uid, utils.ToUniqueInt64Slice(a.getTransactionTagIds(allTransactionTagIds)))
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transactions.getTransactionListResult] failed to get transactions tags for user \"uid:%d\", because %s", uid, err.Error())
|
||||
|
||||
@@ -32,9 +32,9 @@ var (
|
||||
)
|
||||
|
||||
// TwoFactorStatusHandler returns 2fa status of current user
|
||||
func (a *TwoFactorAuthorizationsApi) TwoFactorStatusHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *TwoFactorAuthorizationsApi) TwoFactorStatusHandler(c *core.Context) (any, *errs.Error) {
|
||||
uid := c.GetCurrentUid()
|
||||
twoFactorSetting, err := a.twoFactorAuthorizations.GetUserTwoFactorSettingByUid(uid)
|
||||
twoFactorSetting, err := a.twoFactorAuthorizations.GetUserTwoFactorSettingByUid(c, uid)
|
||||
|
||||
if err == errs.ErrTwoFactorIsNotEnabled {
|
||||
statusResp := &models.TwoFactorStatusResponse{
|
||||
@@ -58,9 +58,9 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorStatusHandler(c *core.Context) (in
|
||||
}
|
||||
|
||||
// TwoFactorEnableRequestHandler returns a new 2fa secret and qr code for current user to set 2fa and verify passcode next
|
||||
func (a *TwoFactorAuthorizationsApi) TwoFactorEnableRequestHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *TwoFactorAuthorizationsApi) TwoFactorEnableRequestHandler(c *core.Context) (any, *errs.Error) {
|
||||
uid := c.GetCurrentUid()
|
||||
enabled, err := a.twoFactorAuthorizations.ExistsTwoFactorSetting(uid)
|
||||
enabled, err := a.twoFactorAuthorizations.ExistsTwoFactorSetting(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[twofactor_authorizations.TwoFactorEnableRequestHandler] failed to check two factor setting, because %s", err.Error())
|
||||
@@ -71,7 +71,7 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorEnableRequestHandler(c *core.Conte
|
||||
return nil, errs.ErrTwoFactorAlreadyEnabled
|
||||
}
|
||||
|
||||
user, err := a.users.GetUserById(uid)
|
||||
user, err := a.users.GetUserById(c, uid)
|
||||
|
||||
if err != nil {
|
||||
if !errs.IsCustomError(err) {
|
||||
@@ -81,7 +81,7 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorEnableRequestHandler(c *core.Conte
|
||||
return nil, errs.ErrUserNotFound
|
||||
}
|
||||
|
||||
key, err := a.twoFactorAuthorizations.GenerateTwoFactorSecret(user)
|
||||
key, err := a.twoFactorAuthorizations.GenerateTwoFactorSecret(c, user)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[twofactor_authorizations.TwoFactorEnableRequestHandler] failed to generate two factor secret, because %s", err.Error())
|
||||
@@ -110,7 +110,7 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorEnableRequestHandler(c *core.Conte
|
||||
}
|
||||
|
||||
// TwoFactorEnableConfirmHandler enables 2fa for current user
|
||||
func (a *TwoFactorAuthorizationsApi) TwoFactorEnableConfirmHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *TwoFactorAuthorizationsApi) TwoFactorEnableConfirmHandler(c *core.Context) (any, *errs.Error) {
|
||||
var confirmReq models.TwoFactorEnableConfirmRequest
|
||||
err := c.ShouldBindJSON(&confirmReq)
|
||||
|
||||
@@ -120,7 +120,7 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorEnableConfirmHandler(c *core.Conte
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
exists, err := a.twoFactorAuthorizations.ExistsTwoFactorSetting(uid)
|
||||
exists, err := a.twoFactorAuthorizations.ExistsTwoFactorSetting(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[twofactor_authorizations.TwoFactorEnableConfirmHandler] failed to check two factor setting, because %s", err.Error())
|
||||
@@ -131,7 +131,7 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorEnableConfirmHandler(c *core.Conte
|
||||
return nil, errs.ErrTwoFactorAlreadyEnabled
|
||||
}
|
||||
|
||||
user, err := a.users.GetUserById(uid)
|
||||
user, err := a.users.GetUserById(c, uid)
|
||||
|
||||
if err != nil {
|
||||
if !errs.IsCustomError(err) {
|
||||
@@ -158,14 +158,14 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorEnableConfirmHandler(c *core.Conte
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
err = a.twoFactorAuthorizations.CreateTwoFactorRecoveryCodes(uid, recoveryCodes, user.Salt)
|
||||
err = a.twoFactorAuthorizations.CreateTwoFactorRecoveryCodes(c, uid, recoveryCodes, user.Salt)
|
||||
|
||||
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())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
err = a.twoFactorAuthorizations.CreateTwoFactorSetting(twoFactorSetting)
|
||||
err = a.twoFactorAuthorizations.CreateTwoFactorSetting(c, twoFactorSetting)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[twofactor_authorizations.TwoFactorEnableConfirmHandler] failed to create two factor setting for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -175,7 +175,7 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorEnableConfirmHandler(c *core.Conte
|
||||
log.InfofWithRequestId(c, "[twofactor_authorizations.TwoFactorEnableConfirmHandler] user \"uid:%d\" has enabled two factor authorization", uid)
|
||||
|
||||
now := time.Now().Unix()
|
||||
err = a.tokens.DeleteTokensBeforeTime(uid, now)
|
||||
err = a.tokens.DeleteTokensBeforeTime(c, uid, now)
|
||||
|
||||
if err == nil {
|
||||
log.InfofWithRequestId(c, "[twofactor_authorizations.TwoFactorEnableConfirmHandler] revoke old tokens before unix time \"%d\" for user \"uid:%d\"", now, user.Uid)
|
||||
@@ -183,7 +183,7 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorEnableConfirmHandler(c *core.Conte
|
||||
log.WarnfWithRequestId(c, "[twofactor_authorizations.TwoFactorEnableConfirmHandler] failed to revoke old tokens for user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||
}
|
||||
|
||||
token, claims, err := a.tokens.CreateToken(user, c)
|
||||
token, claims, err := a.tokens.CreateToken(c, user)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[twofactor_authorizations.TwoFactorEnableConfirmHandler] failed to create token for user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||
@@ -195,6 +195,7 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorEnableConfirmHandler(c *core.Conte
|
||||
return confirmResp, nil
|
||||
}
|
||||
|
||||
c.SetTextualToken(token)
|
||||
c.SetTokenClaims(claims)
|
||||
|
||||
log.InfofWithRequestId(c, "[twofactor_authorizations.TwoFactorEnableConfirmHandler] user \"uid:%d\" token refreshed, new token will be expired at %d", user.Uid, claims.ExpiresAt)
|
||||
@@ -208,7 +209,7 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorEnableConfirmHandler(c *core.Conte
|
||||
}
|
||||
|
||||
// TwoFactorDisableHandler disables 2fa for current user
|
||||
func (a *TwoFactorAuthorizationsApi) TwoFactorDisableHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *TwoFactorAuthorizationsApi) TwoFactorDisableHandler(c *core.Context) (any, *errs.Error) {
|
||||
var disableReq models.TwoFactorDisableRequest
|
||||
err := c.ShouldBindJSON(&disableReq)
|
||||
|
||||
@@ -218,7 +219,7 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorDisableHandler(c *core.Context) (i
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
user, err := a.users.GetUserById(uid)
|
||||
user, err := a.users.GetUserById(c, uid)
|
||||
|
||||
if err != nil {
|
||||
if !errs.IsCustomError(err) {
|
||||
@@ -232,7 +233,7 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorDisableHandler(c *core.Context) (i
|
||||
return nil, errs.ErrUserPasswordWrong
|
||||
}
|
||||
|
||||
enableTwoFactor, err := a.twoFactorAuthorizations.ExistsTwoFactorSetting(uid)
|
||||
enableTwoFactor, err := a.twoFactorAuthorizations.ExistsTwoFactorSetting(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[twofactor_authorizations.TwoFactorDisableHandler] failed to check two factor setting, because %s", err.Error())
|
||||
@@ -243,14 +244,14 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorDisableHandler(c *core.Context) (i
|
||||
return nil, errs.ErrTwoFactorIsNotEnabled
|
||||
}
|
||||
|
||||
err = a.twoFactorAuthorizations.DeleteTwoFactorRecoveryCodes(uid)
|
||||
err = a.twoFactorAuthorizations.DeleteTwoFactorRecoveryCodes(c, uid)
|
||||
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
err = a.twoFactorAuthorizations.DeleteTwoFactorSetting(uid)
|
||||
err = a.twoFactorAuthorizations.DeleteTwoFactorSetting(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[twofactor_authorizations.TwoFactorDisableHandler] failed to delete two factor setting for user \"uid:%d\"", uid)
|
||||
@@ -263,7 +264,7 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorDisableHandler(c *core.Context) (i
|
||||
}
|
||||
|
||||
// TwoFactorRecoveryCodeRegenerateHandler returns new 2fa recovery codes and revokes old recovery codes for current user
|
||||
func (a *TwoFactorAuthorizationsApi) TwoFactorRecoveryCodeRegenerateHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *TwoFactorAuthorizationsApi) TwoFactorRecoveryCodeRegenerateHandler(c *core.Context) (any, *errs.Error) {
|
||||
var regenerateReq models.TwoFactorRegenerateRecoveryCodeRequest
|
||||
err := c.ShouldBindJSON(®enerateReq)
|
||||
|
||||
@@ -273,7 +274,7 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorRecoveryCodeRegenerateHandler(c *c
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
user, err := a.users.GetUserById(uid)
|
||||
user, err := a.users.GetUserById(c, uid)
|
||||
|
||||
if err != nil {
|
||||
if !errs.IsCustomError(err) {
|
||||
@@ -287,7 +288,7 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorRecoveryCodeRegenerateHandler(c *c
|
||||
return nil, errs.ErrUserPasswordWrong
|
||||
}
|
||||
|
||||
enableTwoFactor, err := a.twoFactorAuthorizations.ExistsTwoFactorSetting(uid)
|
||||
enableTwoFactor, err := a.twoFactorAuthorizations.ExistsTwoFactorSetting(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[twofactor_authorizations.TwoFactorRecoveryCodeRegenerateHandler] failed to check two factor setting, because %s", err.Error())
|
||||
@@ -305,7 +306,7 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorRecoveryCodeRegenerateHandler(c *c
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
err = a.twoFactorAuthorizations.CreateTwoFactorRecoveryCodes(uid, recoveryCodes, user.Salt)
|
||||
err = a.twoFactorAuthorizations.CreateTwoFactorRecoveryCodes(c, uid, recoveryCodes, user.Salt)
|
||||
|
||||
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())
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||
@@ -30,13 +32,13 @@ var (
|
||||
)
|
||||
|
||||
// UserRegisterHandler saves a new user by request parameters
|
||||
func (a *UsersApi) UserRegisterHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *UsersApi) UserRegisterHandler(c *core.Context) (any, *errs.Error) {
|
||||
if !settings.Container.Current.EnableUserRegister {
|
||||
return nil, errs.ErrUserRegistrationNotAllowed
|
||||
}
|
||||
|
||||
var userRegisterReq models.UserRegisterRequest
|
||||
err := c.ShouldBindJSON(&userRegisterReq)
|
||||
err := c.ShouldBindBodyWith(&userRegisterReq, binding.JSON)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[users.UserRegisterHandler] parse request failed, because %s", err.Error())
|
||||
@@ -63,7 +65,7 @@ func (a *UsersApi) UserRegisterHandler(c *core.Context) (interface{}, *errs.Erro
|
||||
TransactionEditScope: models.TRANSACTION_EDIT_SCOPE_ALL,
|
||||
}
|
||||
|
||||
err = a.users.CreateUser(user)
|
||||
err = a.users.CreateUser(c, user)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[users.UserRegisterHandler] failed to create user \"%s\", because %s", user.Username, err.Error())
|
||||
@@ -72,12 +74,46 @@ func (a *UsersApi) UserRegisterHandler(c *core.Context) (interface{}, *errs.Erro
|
||||
|
||||
log.InfofWithRequestId(c, "[users.UserRegisterHandler] user \"%s\" has registered successfully, uid is %d", user.Username, user.Uid)
|
||||
|
||||
authResp := &models.AuthResponse{
|
||||
Need2FA: false,
|
||||
User: user.ToUserBasicInfo(),
|
||||
presetCategoriesSaved := false
|
||||
|
||||
if len(userRegisterReq.Categories) > 0 {
|
||||
_, err = TransactionCategories.createBatchCategories(c, user.Uid, &userRegisterReq.TransactionCategoryCreateBatchRequest)
|
||||
|
||||
if err == nil {
|
||||
presetCategoriesSaved = true
|
||||
}
|
||||
}
|
||||
|
||||
token, claims, err := a.tokens.CreateToken(user, c)
|
||||
authResp := &models.RegisterResponse{
|
||||
AuthResponse: models.AuthResponse{
|
||||
Need2FA: false,
|
||||
User: user.ToUserBasicInfo(),
|
||||
},
|
||||
NeedVerifyEmail: settings.Container.Current.EnableUserVerifyEmail && settings.Container.Current.EnableUserForceVerifyEmail,
|
||||
PresetCategoriesSaved: presetCategoriesSaved,
|
||||
}
|
||||
|
||||
if settings.Container.Current.EnableUserVerifyEmail && settings.Container.Current.EnableSMTP {
|
||||
token, _, err := a.tokens.CreateEmailVerifyToken(c, user)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[users.UserRegisterHandler] failed to create email verify token for user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||
} else {
|
||||
go func() {
|
||||
err = a.users.SendVerifyEmail(user, token, c.GetClientLocale())
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[users.UserRegisterHandler] cannot send verify email to \"%s\", because %s", user.Email, err.Error())
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
if settings.Container.Current.EnableUserForceVerifyEmail {
|
||||
return authResp, nil
|
||||
}
|
||||
|
||||
token, claims, err := a.tokens.CreateToken(c, user)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[users.UserRegisterHandler] failed to create token for user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||
@@ -85,6 +121,7 @@ func (a *UsersApi) UserRegisterHandler(c *core.Context) (interface{}, *errs.Erro
|
||||
}
|
||||
|
||||
authResp.Token = token
|
||||
c.SetTextualToken(token)
|
||||
c.SetTokenClaims(claims)
|
||||
|
||||
log.InfofWithRequestId(c, "[users.UserRegisterHandler] user \"uid:%d\" has logined, token will be expired at %d", user.Uid, claims.ExpiresAt)
|
||||
@@ -92,10 +129,72 @@ func (a *UsersApi) UserRegisterHandler(c *core.Context) (interface{}, *errs.Erro
|
||||
return authResp, nil
|
||||
}
|
||||
|
||||
// UserProfileHandler returns user profile of current user
|
||||
func (a *UsersApi) UserProfileHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
// UserEmailVerifyHandler sets user email address verified
|
||||
func (a *UsersApi) UserEmailVerifyHandler(c *core.Context) (any, *errs.Error) {
|
||||
var userVerifyEmailReq models.UserVerifyEmailRequest
|
||||
err := c.ShouldBindJSON(&userVerifyEmailReq)
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
user, err := a.users.GetUserById(uid)
|
||||
user, err := a.users.GetUserById(c, uid)
|
||||
|
||||
if err != nil {
|
||||
if !errs.IsCustomError(err) {
|
||||
log.ErrorfWithRequestId(c, "[users.UserEmailVerifyHandler] failed to get user, because %s", err.Error())
|
||||
}
|
||||
|
||||
return nil, errs.ErrUserNotFound
|
||||
}
|
||||
|
||||
if user.Disabled {
|
||||
log.WarnfWithRequestId(c, "[users.UserEmailVerifyHandler] user \"uid:%d\" is disabled", user.Uid)
|
||||
return nil, errs.ErrUserIsDisabled
|
||||
}
|
||||
|
||||
if user.EmailVerified {
|
||||
log.WarnfWithRequestId(c, "[users.UserEmailVerifyHandler] user \"uid:%d\" email has been verified", user.Uid)
|
||||
return nil, errs.ErrEmailIsVerified
|
||||
}
|
||||
|
||||
err = a.users.SetUserEmailVerified(c, user.Username)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[users.UserEmailVerifyHandler] failed to update user \"uid:%d\" email address verified, because %s", user.Uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
err = a.tokens.DeleteTokensByType(c, uid, core.USER_TOKEN_TYPE_EMAIL_VERIFY)
|
||||
|
||||
if err == nil {
|
||||
log.InfofWithRequestId(c, "[users.UserEmailVerifyHandler] revoke old email verify tokens for user \"uid:%d\"", user.Uid)
|
||||
} else {
|
||||
log.WarnfWithRequestId(c, "[users.UserEmailVerifyHandler] failed to revoke old email verify tokens for user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||
}
|
||||
|
||||
resp := &models.UserVerifyEmailResponse{}
|
||||
|
||||
if userVerifyEmailReq.RequestNewToken {
|
||||
token, claims, err := a.tokens.CreateToken(c, user)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[users.UserEmailVerifyHandler] failed to create token for user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
resp.NewToken = token
|
||||
resp.User = user.ToUserBasicInfo()
|
||||
c.SetTextualToken(token)
|
||||
c.SetTokenClaims(claims)
|
||||
|
||||
log.InfofWithRequestId(c, "[users.UserEmailVerifyHandler] user \"uid:%d\" token created, new token will be expired at %d", user.Uid, claims.ExpiresAt)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// UserProfileHandler returns user profile of current user
|
||||
func (a *UsersApi) UserProfileHandler(c *core.Context) (any, *errs.Error) {
|
||||
uid := c.GetCurrentUid()
|
||||
user, err := a.users.GetUserById(c, uid)
|
||||
|
||||
if err != nil {
|
||||
if !errs.IsCustomError(err) {
|
||||
@@ -110,7 +209,7 @@ func (a *UsersApi) UserProfileHandler(c *core.Context) (interface{}, *errs.Error
|
||||
}
|
||||
|
||||
// UserUpdateProfileHandler saves user profile by request parameters for current user
|
||||
func (a *UsersApi) UserUpdateProfileHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
func (a *UsersApi) UserUpdateProfileHandler(c *core.Context) (any, *errs.Error) {
|
||||
var userUpdateReq models.UserProfileUpdateRequest
|
||||
err := c.ShouldBindJSON(&userUpdateReq)
|
||||
|
||||
@@ -120,7 +219,7 @@ func (a *UsersApi) UserUpdateProfileHandler(c *core.Context) (interface{}, *errs
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
user, err := a.users.GetUserById(uid)
|
||||
user, err := a.users.GetUserById(c, uid)
|
||||
|
||||
if err != nil {
|
||||
if !errs.IsCustomError(err) {
|
||||
@@ -163,7 +262,7 @@ func (a *UsersApi) UserUpdateProfileHandler(c *core.Context) (interface{}, *errs
|
||||
}
|
||||
|
||||
if userUpdateReq.DefaultAccountId > 0 && userUpdateReq.DefaultAccountId != user.DefaultAccountId {
|
||||
accounts, err := a.accounts.GetAccountsByAccountIds(uid, []int64{userUpdateReq.DefaultAccountId})
|
||||
accounts, err := a.accounts.GetAccountsByAccountIds(c, uid, []int64{userUpdateReq.DefaultAccountId})
|
||||
|
||||
if err != nil || len(accounts) < 1 {
|
||||
return nil, errs.Or(err, errs.ErrUserDefaultAccountIsInvalid)
|
||||
@@ -241,22 +340,48 @@ func (a *UsersApi) UserUpdateProfileHandler(c *core.Context) (interface{}, *errs
|
||||
return nil, errs.ErrNothingWillBeUpdated
|
||||
}
|
||||
|
||||
keyProfileUpdated, err := a.users.UpdateUser(userNew, modifyUserLanguage)
|
||||
keyProfileUpdated, emailSetToUnverified, err := a.users.UpdateUser(c, userNew, modifyUserLanguage)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[users.UserUpdateProfileHandler] failed to update user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
if emailSetToUnverified {
|
||||
user.EmailVerified = false
|
||||
}
|
||||
|
||||
log.InfofWithRequestId(c, "[users.UserUpdateProfileHandler] user \"uid:%d\" has updated successfully", user.Uid)
|
||||
|
||||
resp := &models.UserProfileUpdateResponse{
|
||||
User: user.ToUserBasicInfo(),
|
||||
}
|
||||
|
||||
if emailSetToUnverified && settings.Container.Current.EnableUserVerifyEmail && settings.Container.Current.EnableSMTP {
|
||||
err = a.tokens.DeleteTokensByType(c, uid, core.USER_TOKEN_TYPE_EMAIL_VERIFY)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[users.UserUpdateProfileHandler] failed to revoke old email verify tokens for user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||
} else {
|
||||
token, _, err := a.tokens.CreateEmailVerifyToken(c, user)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[users.UserUpdateProfileHandler] failed to create email verify token for user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||
} else {
|
||||
go func() {
|
||||
err = a.users.SendVerifyEmail(user, token, c.GetClientLocale())
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[users.UserUpdateProfileHandler] cannot send verify email to \"%s\", because %s", user.Email, err.Error())
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if keyProfileUpdated {
|
||||
now := time.Now().Unix()
|
||||
err = a.tokens.DeleteTokensBeforeTime(uid, now)
|
||||
err = a.tokens.DeleteTokensBeforeTime(c, uid, now)
|
||||
|
||||
if err == nil {
|
||||
log.InfofWithRequestId(c, "[users.UserUpdateProfileHandler] revoke old tokens before unix time \"%d\" for user \"uid:%d\"", now, user.Uid)
|
||||
@@ -264,7 +389,7 @@ func (a *UsersApi) UserUpdateProfileHandler(c *core.Context) (interface{}, *errs
|
||||
log.WarnfWithRequestId(c, "[users.UserUpdateProfileHandler] failed to revoke old tokens for user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||
}
|
||||
|
||||
token, claims, err := a.tokens.CreateToken(user, c)
|
||||
token, claims, err := a.tokens.CreateToken(c, user)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[users.UserUpdateProfileHandler] failed to create token for user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||
@@ -272,6 +397,7 @@ func (a *UsersApi) UserUpdateProfileHandler(c *core.Context) (interface{}, *errs
|
||||
}
|
||||
|
||||
resp.NewToken = token
|
||||
c.SetTextualToken(token)
|
||||
c.SetTokenClaims(claims)
|
||||
|
||||
log.InfofWithRequestId(c, "[users.UserUpdateProfileHandler] user \"uid:%d\" token refreshed, new token will be expired at %d", user.Uid, claims.ExpiresAt)
|
||||
@@ -281,3 +407,103 @@ func (a *UsersApi) UserUpdateProfileHandler(c *core.Context) (interface{}, *errs
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// UserSendVerifyEmailByUnloginUserHandler sends unlogin user verify email
|
||||
func (a *UsersApi) UserSendVerifyEmailByUnloginUserHandler(c *core.Context) (any, *errs.Error) {
|
||||
if !settings.Container.Current.EnableUserVerifyEmail {
|
||||
return nil, errs.ErrEmailValidationNotAllowed
|
||||
}
|
||||
|
||||
var userResendVerifyEmailReq models.UserResendVerifyEmailRequest
|
||||
err := c.ShouldBindJSON(&userResendVerifyEmailReq)
|
||||
|
||||
user, err := a.users.GetUserByEmail(c, userResendVerifyEmailReq.Email)
|
||||
|
||||
if err != nil {
|
||||
if !errs.IsCustomError(err) {
|
||||
log.ErrorfWithRequestId(c, "[users.UserSendVerifyEmailByUnloginUserHandler] failed to get user, because %s", err.Error())
|
||||
}
|
||||
|
||||
return nil, errs.ErrUserNotFound
|
||||
}
|
||||
|
||||
if !a.users.IsPasswordEqualsUserPassword(userResendVerifyEmailReq.Password, user) {
|
||||
log.WarnfWithRequestId(c, "[users.UserSendVerifyEmailByUnloginUserHandler] request password not equals to the user password")
|
||||
return nil, errs.ErrUserPasswordWrong
|
||||
}
|
||||
|
||||
if user.Disabled {
|
||||
log.WarnfWithRequestId(c, "[users.UserSendVerifyEmailByUnloginUserHandler] user \"uid:%d\" is disabled", user.Uid)
|
||||
return nil, errs.ErrUserIsDisabled
|
||||
}
|
||||
|
||||
if user.EmailVerified {
|
||||
log.WarnfWithRequestId(c, "[users.UserSendVerifyEmailByUnloginUserHandler] user \"uid:%d\" email has been verified", user.Uid)
|
||||
return nil, errs.ErrEmailIsVerified
|
||||
}
|
||||
|
||||
if !settings.Container.Current.EnableSMTP {
|
||||
return nil, errs.ErrSMTPServerNotEnabled
|
||||
}
|
||||
|
||||
token, _, err := a.tokens.CreateEmailVerifyToken(c, user)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[users.UserSendVerifyEmailByUnloginUserHandler] failed to create token for user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||
return nil, errs.ErrTokenGenerating
|
||||
}
|
||||
|
||||
go func() {
|
||||
err = a.users.SendVerifyEmail(user, token, c.GetClientLocale())
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[users.UserSendVerifyEmailByUnloginUserHandler] cannot send email to \"%s\", because %s", user.Email, err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// UserSendVerifyEmailByLoginedUserHandler sends logined user verify email
|
||||
func (a *UsersApi) UserSendVerifyEmailByLoginedUserHandler(c *core.Context) (any, *errs.Error) {
|
||||
if !settings.Container.Current.EnableUserVerifyEmail {
|
||||
return nil, errs.ErrEmailValidationNotAllowed
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
user, err := a.users.GetUserById(c, uid)
|
||||
|
||||
if err != nil {
|
||||
if !errs.IsCustomError(err) {
|
||||
log.ErrorfWithRequestId(c, "[users.UserSendVerifyEmailByLoginedUserHandler] failed to get user, because %s", err.Error())
|
||||
}
|
||||
|
||||
return nil, errs.ErrUserNotFound
|
||||
}
|
||||
|
||||
if user.EmailVerified {
|
||||
log.WarnfWithRequestId(c, "[users.UserSendVerifyEmailByLoginedUserHandler] user \"uid:%d\" email has been verified", user.Uid)
|
||||
return nil, errs.ErrEmailIsVerified
|
||||
}
|
||||
|
||||
if !settings.Container.Current.EnableSMTP {
|
||||
return nil, errs.ErrSMTPServerNotEnabled
|
||||
}
|
||||
|
||||
token, _, err := a.tokens.CreateEmailVerifyToken(c, user)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[users.UserSendVerifyEmailByLoginedUserHandler] failed to create token for user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||
return nil, errs.ErrTokenGenerating
|
||||
}
|
||||
|
||||
go func() {
|
||||
err = a.users.SendVerifyEmail(user, token, c.GetClientLocale())
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[users.UserSendVerifyEmailByLoginedUserHandler] cannot send email to \"%s\", because %s", user.Email, err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"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/validators"
|
||||
)
|
||||
|
||||
@@ -26,6 +27,7 @@ type UserDataCli struct {
|
||||
users *services.UserService
|
||||
twoFactorAuthorizations *services.TwoFactorAuthorizationService
|
||||
tokens *services.TokenService
|
||||
forgetPasswords *services.ForgetPasswordService
|
||||
}
|
||||
|
||||
// Initialize an user data cli singleton instance
|
||||
@@ -39,6 +41,7 @@ var (
|
||||
users: services.Users,
|
||||
twoFactorAuthorizations: services.TwoFactorAuthorizations,
|
||||
tokens: services.Tokens,
|
||||
forgetPasswords: services.ForgetPasswords,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -84,7 +87,7 @@ func (l *UserDataCli) AddNewUser(c *cli.Context, username string, email string,
|
||||
TransactionEditScope: models.TRANSACTION_EDIT_SCOPE_ALL,
|
||||
}
|
||||
|
||||
err := l.users.CreateUser(user)
|
||||
err := l.users.CreateUser(nil, user)
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[user_data.AddNewUser] failed to create user \"%s\", because %s", user.Username, err.Error())
|
||||
@@ -103,7 +106,7 @@ func (l *UserDataCli) GetUserByUsername(c *cli.Context, username string) (*model
|
||||
return nil, errs.ErrUsernameIsEmpty
|
||||
}
|
||||
|
||||
user, err := l.users.GetUserByUsername(username)
|
||||
user, err := l.users.GetUserByUsername(nil, username)
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[user_data.GetUserByUsername] failed to get user by user name \"%s\", because %s", username, err.Error())
|
||||
@@ -125,7 +128,7 @@ func (l *UserDataCli) ModifyUserPassword(c *cli.Context, username string, passwo
|
||||
return errs.ErrPasswordIsEmpty
|
||||
}
|
||||
|
||||
user, err := l.users.GetUserByUsername(username)
|
||||
user, err := l.users.GetUserByUsername(nil, username)
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[user_data.ModifyUserPassword] failed to get user by user name \"%s\", because %s", username, err.Error())
|
||||
@@ -142,7 +145,7 @@ func (l *UserDataCli) ModifyUserPassword(c *cli.Context, username string, passwo
|
||||
Password: password,
|
||||
}
|
||||
|
||||
_, err = l.users.UpdateUser(userNew, false)
|
||||
_, _, err = l.users.UpdateUser(nil, userNew, false)
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[user_data.ModifyUserPassword] failed to update user \"%s\" password, because %s", user.Username, err.Error())
|
||||
@@ -150,7 +153,7 @@ func (l *UserDataCli) ModifyUserPassword(c *cli.Context, username string, passwo
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
err = l.tokens.DeleteTokensBeforeTime(user.Uid, now)
|
||||
err = l.tokens.DeleteTokensBeforeTime(nil, user.Uid, now)
|
||||
|
||||
if err == nil {
|
||||
log.BootInfof("[user_data.ModifyUserPassword] revoke old tokens before unix time \"%d\" for user \"%s\"", now, user.Username)
|
||||
@@ -161,6 +164,150 @@ func (l *UserDataCli) ModifyUserPassword(c *cli.Context, username string, passwo
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendPasswordResetMail sends an email with password reset link
|
||||
func (l *UserDataCli) SendPasswordResetMail(c *cli.Context, username string) error {
|
||||
if username == "" {
|
||||
log.BootErrorf("[user_data.SendPasswordResetMail] user name is empty")
|
||||
return errs.ErrUsernameIsEmpty
|
||||
}
|
||||
|
||||
user, err := l.users.GetUserByUsername(nil, username)
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[user_data.SendPasswordResetMail] failed to get user by user name \"%s\", because %s", username, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
if settings.Container.Current.ForgetPasswordRequireVerifyEmail && !user.EmailVerified {
|
||||
log.BootWarnf("[user_data.SendPasswordResetMail] user \"uid:%d\" has not verified email", user.Uid)
|
||||
return errs.ErrEmailIsNotVerified
|
||||
}
|
||||
|
||||
token, _, err := l.tokens.CreatePasswordResetToken(nil, user)
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[user_data.SendPasswordResetMail] failed to create token for user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
err = l.forgetPasswords.SendPasswordResetEmail(nil, user, token, "")
|
||||
|
||||
if err != nil {
|
||||
log.BootWarnf("[user_data.SendPasswordResetMail] cannot send email to \"%s\", because %s", user.Email, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnableUser sets user enabled according to the specified user name
|
||||
func (l *UserDataCli) EnableUser(c *cli.Context, username string) error {
|
||||
if username == "" {
|
||||
log.BootErrorf("[user_data.EnableUser] user name is empty")
|
||||
return errs.ErrUsernameIsEmpty
|
||||
}
|
||||
|
||||
err := l.users.EnableUser(nil, username)
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[user_data.EnableUser] failed to set user enabled by user name \"%s\", because %s", username, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DisableUser sets user disabled according to the specified user name
|
||||
func (l *UserDataCli) DisableUser(c *cli.Context, username string) error {
|
||||
if username == "" {
|
||||
log.BootErrorf("[user_data.DisableUser] user name is empty")
|
||||
return errs.ErrUsernameIsEmpty
|
||||
}
|
||||
|
||||
err := l.users.DisableUser(nil, username)
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[user_data.DisableUser] failed to set user disabled by user name \"%s\", because %s", username, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResendVerifyEmail resends an email with account activation link
|
||||
func (l *UserDataCli) ResendVerifyEmail(c *cli.Context, username string) error {
|
||||
if !settings.Container.Current.EnableUserVerifyEmail {
|
||||
return errs.ErrEmailValidationNotAllowed
|
||||
}
|
||||
|
||||
if username == "" {
|
||||
log.BootErrorf("[user_data.ResendVerifyEmail] user name is empty")
|
||||
return errs.ErrUsernameIsEmpty
|
||||
}
|
||||
|
||||
user, err := l.users.GetUserByUsername(nil, username)
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[user_data.ResendVerifyEmail] failed to get user by user name \"%s\", because %s", username, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
if user.EmailVerified {
|
||||
log.BootWarnf("[user_data.ResendVerifyEmail] user \"uid:%d\" email has been verified", user.Uid)
|
||||
return errs.ErrEmailIsVerified
|
||||
}
|
||||
|
||||
token, _, err := l.tokens.CreateEmailVerifyToken(nil, user)
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[user_data.ResendVerifyEmail] failed to create token for user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||
return errs.ErrTokenGenerating
|
||||
}
|
||||
|
||||
err = l.users.SendVerifyEmail(user, token, "")
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[user_data.ResendVerifyEmail] cannot send email to \"%s\", because %s", user.Email, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetUserEmailVerified sets user email address verified
|
||||
func (l *UserDataCli) SetUserEmailVerified(c *cli.Context, username string) error {
|
||||
if username == "" {
|
||||
log.BootErrorf("[user_data.SetUserEmailVerified] user name is empty")
|
||||
return errs.ErrUsernameIsEmpty
|
||||
}
|
||||
|
||||
err := l.users.SetUserEmailVerified(nil, username)
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[user_data.SetUserEmailVerified] failed to set user email address verified by user name \"%s\", because %s", username, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetUserEmailUnverified sets user email address unverified
|
||||
func (l *UserDataCli) SetUserEmailUnverified(c *cli.Context, username string) error {
|
||||
if username == "" {
|
||||
log.BootErrorf("[user_data.SetUserEmailUnverified] user name is empty")
|
||||
return errs.ErrUsernameIsEmpty
|
||||
}
|
||||
|
||||
err := l.users.SetUserEmailUnverified(nil, username)
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[user_data.SetUserEmailUnverified] failed to set user email address unverified by user name \"%s\", because %s", username, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteUser deletes user according to the specified user name
|
||||
func (l *UserDataCli) DeleteUser(c *cli.Context, username string) error {
|
||||
if username == "" {
|
||||
@@ -168,7 +315,7 @@ func (l *UserDataCli) DeleteUser(c *cli.Context, username string) error {
|
||||
return errs.ErrUsernameIsEmpty
|
||||
}
|
||||
|
||||
err := l.users.DeleteUser(username)
|
||||
err := l.users.DeleteUser(nil, username)
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[user_data.DeleteUser] failed to delete user by user name \"%s\", because %s", username, err.Error())
|
||||
@@ -192,7 +339,7 @@ func (l *UserDataCli) ListUserTokens(c *cli.Context, username string) ([]*models
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tokens, err := l.tokens.GetAllUnexpiredNormalTokensByUid(uid)
|
||||
tokens, err := l.tokens.GetAllUnexpiredNormalTokensByUid(nil, uid)
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[user_data.ListUserTokens] failed to get tokens of user \"%s\", because %s", username, err.Error())
|
||||
@@ -217,7 +364,7 @@ func (l *UserDataCli) ClearUserTokens(c *cli.Context, username string) error {
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
err = l.tokens.DeleteTokensBeforeTime(uid, now)
|
||||
err = l.tokens.DeleteTokensBeforeTime(nil, uid, now)
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[user_data.ClearUserTokens] failed to delete tokens of user \"%s\", because %s", username, err.Error())
|
||||
@@ -241,7 +388,7 @@ func (l *UserDataCli) DisableUserTwoFactorAuthorization(c *cli.Context, username
|
||||
return err
|
||||
}
|
||||
|
||||
enableTwoFactor, err := l.twoFactorAuthorizations.ExistsTwoFactorSetting(uid)
|
||||
enableTwoFactor, err := l.twoFactorAuthorizations.ExistsTwoFactorSetting(nil, uid)
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[user_data.DisableUserTwoFactorAuthorization] failed to check two factor setting, because %s", err.Error())
|
||||
@@ -252,14 +399,14 @@ func (l *UserDataCli) DisableUserTwoFactorAuthorization(c *cli.Context, username
|
||||
return errs.ErrTwoFactorIsNotEnabled
|
||||
}
|
||||
|
||||
err = l.twoFactorAuthorizations.DeleteTwoFactorRecoveryCodes(uid)
|
||||
err = l.twoFactorAuthorizations.DeleteTwoFactorRecoveryCodes(nil, uid)
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[user_data.DisableUserTwoFactorAuthorization] failed to delete two factor recovery codes for user \"%s\"", username)
|
||||
return err
|
||||
}
|
||||
|
||||
err = l.twoFactorAuthorizations.DeleteTwoFactorSetting(uid)
|
||||
err = l.twoFactorAuthorizations.DeleteTwoFactorSetting(nil, uid)
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[user_data.DisableUserTwoFactorAuthorization] failed to delete two factor setting for user \"%s\"", username)
|
||||
@@ -298,7 +445,7 @@ func (l *UserDataCli) CheckTransactionAndAccount(c *cli.Context, username string
|
||||
}
|
||||
}
|
||||
|
||||
allTransactions, err := l.transactions.GetAllTransactions(uid, pageCountForGettingTransactions, false)
|
||||
allTransactions, err := l.transactions.GetAllTransactions(nil, uid, pageCountForGettingTransactions, false)
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[user_data.CheckTransactionAndAccount] failed to all transactions for user \"%s\", because %s", username, err.Error())
|
||||
@@ -410,7 +557,7 @@ func (l *UserDataCli) ExportTransaction(c *cli.Context, username string) ([]byte
|
||||
return nil, err
|
||||
}
|
||||
|
||||
allTransactions, err := l.transactions.GetAllTransactions(uid, pageCountForDataExport, true)
|
||||
allTransactions, err := l.transactions.GetAllTransactions(nil, uid, pageCountForDataExport, true)
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[user_data.ExportTransaction] failed to all transactions for user \"%s\", because %s", username, err.Error())
|
||||
@@ -444,7 +591,7 @@ func (l *UserDataCli) getUserEssentialData(uid int64, username string) (accountM
|
||||
return nil, nil, nil, nil, errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
accounts, err := l.accounts.GetAllAccountsByUid(uid)
|
||||
accounts, err := l.accounts.GetAllAccountsByUid(nil, uid)
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[user_data.getUserEssentialData] failed to get accounts for user \"%s\", because %s", username, err.Error())
|
||||
@@ -453,7 +600,7 @@ func (l *UserDataCli) getUserEssentialData(uid int64, username string) (accountM
|
||||
|
||||
accountMap = l.accounts.GetAccountMapByList(accounts)
|
||||
|
||||
categories, err := l.categories.GetAllCategoriesByUid(uid, 0, -1)
|
||||
categories, err := l.categories.GetAllCategoriesByUid(nil, uid, 0, -1)
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[user_data.getUserEssentialData] failed to get categories for user \"%s\", because %s", username, err.Error())
|
||||
@@ -462,7 +609,7 @@ func (l *UserDataCli) getUserEssentialData(uid int64, username string) (accountM
|
||||
|
||||
categoryMap = l.categories.GetCategoryMapByList(categories)
|
||||
|
||||
tags, err := l.tags.GetAllTagsByUid(uid)
|
||||
tags, err := l.tags.GetAllTagsByUid(nil, uid)
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[user_data.getUserEssentialData] failed to get tags for user \"%s\", because %s", username, err.Error())
|
||||
@@ -471,7 +618,7 @@ func (l *UserDataCli) getUserEssentialData(uid int64, username string) (accountM
|
||||
|
||||
tagMap = l.tags.GetTagMapByList(tags)
|
||||
|
||||
tagIndexs, err = l.tags.GetAllTagIdsOfAllTransactions(uid)
|
||||
tagIndexs, err = l.tags.GetAllTagIdsOfAllTransactions(nil, uid)
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[user_data.getUserEssentialData] failed to get tag index for user \"%s\", because %s", username, err.Error())
|
||||
|
||||
@@ -9,9 +9,13 @@ import (
|
||||
)
|
||||
|
||||
const requestIdFieldKey = "REQUEST_ID"
|
||||
const textualTokenFieldKey = "TOKEN_STRING"
|
||||
const tokenClaimsFieldKey = "TOKEN_CLAIMS"
|
||||
const responseErrorFieldKey = "RESPONSE_ERROR"
|
||||
|
||||
// AcceptLanguageHeaderName represents the header name of accept language
|
||||
const AcceptLanguageHeaderName = "Accept-Language"
|
||||
|
||||
// ClientTimezoneOffsetHeaderName represents the header name of client timezone offset
|
||||
const ClientTimezoneOffsetHeaderName = "X-Timezone-Offset"
|
||||
|
||||
@@ -37,7 +41,23 @@ func (c *Context) GetRequestId() string {
|
||||
return requestId.(string)
|
||||
}
|
||||
|
||||
// SetTokenClaims sets the given user token id to context
|
||||
// SetTextualToken sets the given user token to context
|
||||
func (c *Context) SetTextualToken(token string) {
|
||||
c.Set(textualTokenFieldKey, token)
|
||||
}
|
||||
|
||||
// GetTextualToken returns the current user textual token
|
||||
func (c *Context) GetTextualToken() string {
|
||||
token, exists := c.Get(textualTokenFieldKey)
|
||||
|
||||
if !exists {
|
||||
return ""
|
||||
}
|
||||
|
||||
return token.(string)
|
||||
}
|
||||
|
||||
// SetTokenClaims sets the given user token to context
|
||||
func (c *Context) SetTokenClaims(claims *UserTokenClaims) {
|
||||
c.Set(tokenClaimsFieldKey, claims)
|
||||
}
|
||||
@@ -64,6 +84,13 @@ func (c *Context) GetCurrentUid() int64 {
|
||||
return claims.Uid
|
||||
}
|
||||
|
||||
// GetClientLocale returns the client locale name
|
||||
func (c *Context) GetClientLocale() string {
|
||||
value := c.GetHeader(AcceptLanguageHeaderName)
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
// GetClientTimezoneOffset returns the client timezone offset
|
||||
func (c *Context) GetClientTimezoneOffset() (int16, error) {
|
||||
value := c.GetHeader(ClientTimezoneOffsetHeaderName)
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
type MiddlewareHandlerFunc func(*Context)
|
||||
|
||||
// ApiHandlerFunc represents the api handler function
|
||||
type ApiHandlerFunc func(*Context) (interface{}, *errs.Error)
|
||||
type ApiHandlerFunc func(*Context) (any, *errs.Error)
|
||||
|
||||
// DataHandlerFunc represents the handler function that returns byte array
|
||||
type DataHandlerFunc func(*Context) ([]byte, string, *errs.Error)
|
||||
|
||||
@@ -11,8 +11,10 @@ type TokenType byte
|
||||
|
||||
// Token types
|
||||
const (
|
||||
USER_TOKEN_TYPE_NORMAL TokenType = 1
|
||||
USER_TOKEN_TYPE_REQUIRE_2FA TokenType = 2
|
||||
USER_TOKEN_TYPE_NORMAL TokenType = 1
|
||||
USER_TOKEN_TYPE_REQUIRE_2FA TokenType = 2
|
||||
USER_TOKEN_TYPE_EMAIL_VERIFY TokenType = 3
|
||||
USER_TOKEN_TYPE_PASSWORD_RESET TokenType = 4
|
||||
)
|
||||
|
||||
// UserTokenClaims represents user token
|
||||
|
||||
@@ -1,15 +1,29 @@
|
||||
package datastore
|
||||
|
||||
import "xorm.io/xorm"
|
||||
import (
|
||||
"xorm.io/xorm"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
)
|
||||
|
||||
// Database represents a database instance
|
||||
type Database struct {
|
||||
*xorm.EngineGroup
|
||||
engineGroup *xorm.EngineGroup
|
||||
}
|
||||
|
||||
// NewSession starts a new session with the specified context
|
||||
func (db *Database) NewSession(c *core.Context) *xorm.Session {
|
||||
return db.engineGroup.Context(NewXOrmContextAdapter(c))
|
||||
}
|
||||
|
||||
// DoTransaction runs a new database transaction
|
||||
func (db *Database) DoTransaction(fn func(sess *xorm.Session) error) (err error) {
|
||||
sess := db.NewSession()
|
||||
func (db *Database) DoTransaction(c *core.Context, fn func(sess *xorm.Session) error) (err error) {
|
||||
sess := db.engineGroup.NewSession()
|
||||
|
||||
if c != nil {
|
||||
sess.Context(NewXOrmContextAdapter(c))
|
||||
}
|
||||
|
||||
defer sess.Close()
|
||||
|
||||
if err = sess.Begin(); err != nil {
|
||||
|
||||
@@ -3,6 +3,7 @@ package datastore
|
||||
import (
|
||||
"xorm.io/xorm"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
)
|
||||
|
||||
@@ -17,21 +18,21 @@ func (s *DataStore) Choose(key int64) *Database {
|
||||
}
|
||||
|
||||
// Query returns a new database session in a specific database by sharding key
|
||||
func (s *DataStore) Query(key int64) *xorm.Session {
|
||||
return s.Choose(key).NewSession()
|
||||
func (s *DataStore) Query(c *core.Context, key int64) *xorm.Session {
|
||||
return s.Choose(key).NewSession(c)
|
||||
}
|
||||
|
||||
// DoTransaction runs a new database transaction in a specific database by sharding key
|
||||
func (s *DataStore) DoTransaction(key int64, fn func(sess *xorm.Session) error) (err error) {
|
||||
return s.Choose(key).DoTransaction(fn)
|
||||
func (s *DataStore) DoTransaction(key int64, c *core.Context, fn func(sess *xorm.Session) error) (err error) {
|
||||
return s.Choose(key).DoTransaction(c, fn)
|
||||
}
|
||||
|
||||
// SyncStructs updates database structs by database models
|
||||
func (s *DataStore) SyncStructs(beans ...interface{}) error {
|
||||
func (s *DataStore) SyncStructs(beans ...any) error {
|
||||
var err error
|
||||
|
||||
for i := 0; i < len(s.databases); i++ {
|
||||
err = s.databases[i].Sync2(beans...)
|
||||
err = s.databases[i].engineGroup.Sync2(beans...)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -2,6 +2,7 @@ package datastore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -103,14 +104,14 @@ func initializeDatabase(dbConfig *settings.DatabaseConfig) (*Database, error) {
|
||||
engineGroup.SetConnMaxLifetime(time.Duration(dbConfig.ConnectionMaxLifeTime) * time.Second)
|
||||
|
||||
return &Database{
|
||||
EngineGroup: engineGroup,
|
||||
engineGroup: engineGroup,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func setDatabaseLogger(database *Database, config *settings.Config) {
|
||||
if config.EnableQueryLog {
|
||||
database.SetLogger(NewXOrmLoggerAdapter(config.EnableQueryLog, config.LogLevel))
|
||||
database.ShowSQL(true)
|
||||
database.engineGroup.SetLogger(NewXOrmLoggerAdapter(config.EnableQueryLog, config.LogLevel))
|
||||
database.engineGroup.ShowSQL(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,16 +127,12 @@ func getMysqlConnectionString(dbConfig *settings.DatabaseConfig) (string, error)
|
||||
}
|
||||
|
||||
func getPostgresConnectionString(dbConfig *settings.DatabaseConfig) (string, error) {
|
||||
host, port := "", ""
|
||||
fields := strings.Split(dbConfig.DatabaseHost, ":")
|
||||
host, port, err := net.SplitHostPort(dbConfig.DatabaseHost)
|
||||
|
||||
if len(fields) != 2 {
|
||||
if err != nil {
|
||||
return "", errs.ErrDatabaseHostInvalid
|
||||
}
|
||||
|
||||
host = strings.TrimSpace(fields[0])
|
||||
port = strings.TrimSpace(fields[1])
|
||||
|
||||
if strings.HasPrefix(dbConfig.DatabaseHost, "/") { // unix socket path
|
||||
return fmt.Sprintf("postgres://%s:%s@:%s/%s?sslmode=%s&host=%s",
|
||||
url.QueryEscape(dbConfig.DatabaseUser), url.QueryEscape(dbConfig.DatabasePassword), port, dbConfig.DatabaseName, dbConfig.DatabaseSSLMode, host), nil
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package datastore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"xorm.io/xorm/log"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
)
|
||||
|
||||
// XOrmContextAdapter represents the context adapter for xorm
|
||||
type XOrmContextAdapter struct {
|
||||
requestId string
|
||||
}
|
||||
|
||||
// Deadline does nothing
|
||||
func (c *XOrmContextAdapter) Deadline() (deadline time.Time, ok bool) {
|
||||
return
|
||||
}
|
||||
|
||||
// Done always returns nil
|
||||
func (c *XOrmContextAdapter) Done() <-chan struct{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Err always returns nil
|
||||
func (c *XOrmContextAdapter) Err() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value returns the value associated with this context for key, or nil
|
||||
// if no value is associated with key.
|
||||
func (c *XOrmContextAdapter) Value(key any) any {
|
||||
if key == log.SessionIDKey && c.requestId != "" {
|
||||
return fmt.Sprintf("r=%s", c.requestId)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewXOrmContextAdapter(c *core.Context) *XOrmContextAdapter {
|
||||
if c != nil {
|
||||
return &XOrmContextAdapter{
|
||||
requestId: c.GetRequestId(),
|
||||
}
|
||||
}
|
||||
|
||||
return &XOrmContextAdapter{}
|
||||
}
|
||||
@@ -14,42 +14,42 @@ type XOrmLoggerAdapter struct {
|
||||
}
|
||||
|
||||
// Debug logs debug log
|
||||
func (logger XOrmLoggerAdapter) Debug(v ...interface{}) {
|
||||
func (logger XOrmLoggerAdapter) Debug(v ...any) {
|
||||
log.SqlQuery(v...)
|
||||
}
|
||||
|
||||
// Debugf logs debug log with custom format
|
||||
func (logger XOrmLoggerAdapter) Debugf(format string, v ...interface{}) {
|
||||
func (logger XOrmLoggerAdapter) Debugf(format string, v ...any) {
|
||||
log.SqlQueryf(format, v...)
|
||||
}
|
||||
|
||||
// Info logs info log
|
||||
func (logger XOrmLoggerAdapter) Info(v ...interface{}) {
|
||||
func (logger XOrmLoggerAdapter) Info(v ...any) {
|
||||
log.SqlQuery(v...)
|
||||
}
|
||||
|
||||
// Infof logs info log with custom format
|
||||
func (logger XOrmLoggerAdapter) Infof(format string, v ...interface{}) {
|
||||
func (logger XOrmLoggerAdapter) Infof(format string, v ...any) {
|
||||
log.SqlQueryf(format, v...)
|
||||
}
|
||||
|
||||
// Warn logs warn log
|
||||
func (logger XOrmLoggerAdapter) Warn(v ...interface{}) {
|
||||
func (logger XOrmLoggerAdapter) Warn(v ...any) {
|
||||
log.SqlQuery(v...)
|
||||
}
|
||||
|
||||
// Warnf logs warn log with custom format
|
||||
func (logger XOrmLoggerAdapter) Warnf(format string, v ...interface{}) {
|
||||
func (logger XOrmLoggerAdapter) Warnf(format string, v ...any) {
|
||||
log.SqlQueryf(format, v...)
|
||||
}
|
||||
|
||||
// Error logs error log
|
||||
func (logger XOrmLoggerAdapter) Error(v ...interface{}) {
|
||||
func (logger XOrmLoggerAdapter) Error(v ...any) {
|
||||
log.SqlQuery(v...)
|
||||
}
|
||||
|
||||
// Errorf logs error log with custom format
|
||||
func (logger XOrmLoggerAdapter) Errorf(format string, v ...interface{}) {
|
||||
func (logger XOrmLoggerAdapter) Errorf(format string, v ...any) {
|
||||
log.SqlQueryf(format, v...)
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ const (
|
||||
SystemSubcategoryDefault = 0
|
||||
SystemSubcategorySetting = 1
|
||||
SystemSubcategoryDatabase = 2
|
||||
SystemSubcategoryMail = 3
|
||||
)
|
||||
|
||||
// Sub categories of normal error
|
||||
@@ -37,6 +38,7 @@ type Error struct {
|
||||
HttpStatusCode int
|
||||
Message string
|
||||
BaseError []error
|
||||
Context any
|
||||
}
|
||||
|
||||
// Error returns the error message
|
||||
@@ -80,6 +82,19 @@ func NewIncompleteOrIncorrectSubmissionError(err error) *Error {
|
||||
ErrIncompleteOrIncorrectSubmission.Message, err)
|
||||
}
|
||||
|
||||
// NewErrorWithContext returns a new error instance with specified context
|
||||
func NewErrorWithContext(baseError *Error, context any) *Error {
|
||||
return &Error{
|
||||
Category: baseError.Category,
|
||||
SubCategory: baseError.SubCategory,
|
||||
Index: baseError.Index,
|
||||
HttpStatusCode: baseError.HttpStatusCode,
|
||||
Message: baseError.Message,
|
||||
BaseError: baseError.BaseError,
|
||||
Context: context,
|
||||
}
|
||||
}
|
||||
|
||||
// Or would return the error from err parameter if the this error is defined in this project,
|
||||
// or return the default error
|
||||
func Or(err error, defaultErr *Error) *Error {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package errs
|
||||
|
||||
import "net/http"
|
||||
|
||||
// Error codes related to mail
|
||||
var (
|
||||
ErrSMTPServerNotEnabled = NewSystemError(SystemSubcategoryMail, 0, http.StatusInternalServerError, "SMTP server is not enabled")
|
||||
ErrSMTPServerHostInvalid = NewSystemError(SystemSubcategoryMail, 1, http.StatusInternalServerError, "SMTP server host is invalid")
|
||||
)
|
||||
@@ -4,10 +4,11 @@ import "net/http"
|
||||
|
||||
// Error codes related to settings
|
||||
var (
|
||||
ErrInvalidProtocol = NewSystemError(SystemSubcategorySetting, 0, http.StatusInternalServerError, "invalid server protocol")
|
||||
ErrInvalidLogMode = NewSystemError(SystemSubcategorySetting, 1, http.StatusInternalServerError, "invalid log mode")
|
||||
ErrGettingLocalAddress = NewSystemError(SystemSubcategorySetting, 2, http.StatusInternalServerError, "failed to get local address")
|
||||
ErrInvalidUuidMode = NewSystemError(SystemSubcategorySetting, 3, http.StatusInternalServerError, "invalid uuid mode")
|
||||
ErrInvalidExchangeRatesDataSource = NewSystemError(SystemSubcategorySetting, 4, http.StatusInternalServerError, "invalid exchange rates data source")
|
||||
ErrInvalidMapProvider = NewSystemError(SystemSubcategorySetting, 5, http.StatusInternalServerError, "invalid map provider")
|
||||
ErrInvalidProtocol = NewSystemError(SystemSubcategorySetting, 0, http.StatusInternalServerError, "invalid server protocol")
|
||||
ErrInvalidLogMode = NewSystemError(SystemSubcategorySetting, 1, http.StatusInternalServerError, "invalid log mode")
|
||||
ErrGettingLocalAddress = NewSystemError(SystemSubcategorySetting, 2, http.StatusInternalServerError, "failed to get local address")
|
||||
ErrInvalidUuidMode = NewSystemError(SystemSubcategorySetting, 3, http.StatusInternalServerError, "invalid uuid mode")
|
||||
ErrInvalidExchangeRatesDataSource = NewSystemError(SystemSubcategorySetting, 4, http.StatusInternalServerError, "invalid exchange rates data source")
|
||||
ErrInvalidMapProvider = NewSystemError(SystemSubcategorySetting, 5, http.StatusInternalServerError, "invalid map provider")
|
||||
ErrInvalidAmapSecurityVerificationMethod = NewSystemError(SystemSubcategorySetting, 6, http.StatusInternalServerError, "invalid amap security verification method")
|
||||
)
|
||||
|
||||
@@ -8,4 +8,5 @@ var (
|
||||
ErrApiNotFound = NewSystemError(SystemSubcategoryDefault, 1, http.StatusNotFound, "api not found")
|
||||
ErrMethodNotAllowed = NewSystemError(SystemSubcategoryDefault, 2, http.StatusMethodNotAllowed, "method not allowed")
|
||||
ErrNotImplemented = NewSystemError(SystemSubcategoryDefault, 3, http.StatusNotImplemented, "not implemented")
|
||||
ErrSystemIsBusy = NewSystemError(SystemSubcategoryDefault, 4, http.StatusNotImplemented, "system is busy")
|
||||
)
|
||||
|
||||
@@ -6,17 +6,19 @@ import (
|
||||
|
||||
// Error codes related to tokens
|
||||
var (
|
||||
ErrTokenGenerating = NewNormalError(NormalSubcategoryToken, 0, http.StatusInternalServerError, "failed to generate token")
|
||||
ErrUnauthorizedAccess = NewNormalError(NormalSubcategoryToken, 1, http.StatusUnauthorized, "unauthorized access")
|
||||
ErrCurrentInvalidToken = NewNormalError(NormalSubcategoryToken, 2, http.StatusUnauthorized, "current token is invalid")
|
||||
ErrCurrentTokenExpired = NewNormalError(NormalSubcategoryToken, 3, http.StatusUnauthorized, "current token is expired")
|
||||
ErrCurrentInvalidTokenType = NewNormalError(NormalSubcategoryToken, 4, http.StatusUnauthorized, "current token type is invalid")
|
||||
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")
|
||||
ErrInvalidToken = NewNormalError(NormalSubcategoryToken, 7, http.StatusBadRequest, "token is invalid")
|
||||
ErrInvalidTokenId = NewNormalError(NormalSubcategoryToken, 8, http.StatusBadRequest, "token id is invalid")
|
||||
ErrInvalidUserTokenId = NewNormalError(NormalSubcategoryToken, 9, http.StatusBadRequest, "user token id is invalid")
|
||||
ErrTokenRecordNotFound = NewNormalError(NormalSubcategoryToken, 10, http.StatusBadRequest, "token is not found")
|
||||
ErrTokenExpired = NewNormalError(NormalSubcategoryToken, 11, http.StatusBadRequest, "token is expired")
|
||||
ErrTokenIsEmpty = NewNormalError(NormalSubcategoryToken, 12, http.StatusBadRequest, "token is empty")
|
||||
ErrTokenGenerating = NewNormalError(NormalSubcategoryToken, 0, http.StatusInternalServerError, "failed to generate token")
|
||||
ErrUnauthorizedAccess = NewNormalError(NormalSubcategoryToken, 1, http.StatusUnauthorized, "unauthorized access")
|
||||
ErrCurrentInvalidToken = NewNormalError(NormalSubcategoryToken, 2, http.StatusUnauthorized, "current token is invalid")
|
||||
ErrCurrentTokenExpired = NewNormalError(NormalSubcategoryToken, 3, http.StatusUnauthorized, "current token is expired")
|
||||
ErrCurrentInvalidTokenType = NewNormalError(NormalSubcategoryToken, 4, http.StatusUnauthorized, "current token type is invalid")
|
||||
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")
|
||||
ErrInvalidToken = NewNormalError(NormalSubcategoryToken, 7, http.StatusBadRequest, "token is invalid")
|
||||
ErrInvalidTokenId = NewNormalError(NormalSubcategoryToken, 8, http.StatusBadRequest, "token id is invalid")
|
||||
ErrInvalidUserTokenId = NewNormalError(NormalSubcategoryToken, 9, http.StatusBadRequest, "user token id is invalid")
|
||||
ErrTokenRecordNotFound = NewNormalError(NormalSubcategoryToken, 10, http.StatusBadRequest, "token is not found")
|
||||
ErrTokenExpired = NewNormalError(NormalSubcategoryToken, 11, http.StatusBadRequest, "token is expired")
|
||||
ErrTokenIsEmpty = NewNormalError(NormalSubcategoryToken, 12, http.StatusBadRequest, "token is empty")
|
||||
ErrEmailVerifyTokenIsInvalidOrExpired = NewNormalError(NormalSubcategoryToken, 13, http.StatusBadRequest, "email verify token is invalid or expired")
|
||||
ErrPasswordResetTokenIsInvalidOrExpired = NewNormalError(NormalSubcategoryToken, 14, http.StatusBadRequest, "password reset token is invalid or expired")
|
||||
)
|
||||
|
||||
@@ -22,4 +22,11 @@ var (
|
||||
ErrUserEmailAlreadyExists = NewNormalError(NormalSubcategoryUser, 13, http.StatusBadRequest, "email already exists")
|
||||
ErrUserRegistrationNotAllowed = NewNormalError(NormalSubcategoryUser, 14, http.StatusBadRequest, "user registration not allowed")
|
||||
ErrUserDefaultAccountIsInvalid = NewNormalError(NormalSubcategoryUser, 15, http.StatusBadRequest, "user default account is invalid")
|
||||
ErrUserIsDisabled = NewNormalError(NormalSubcategoryUser, 16, http.StatusBadRequest, "user is disabled")
|
||||
ErrEmptyIsInvalid = NewNormalError(NormalSubcategoryUser, 17, http.StatusBadRequest, "email is invalid")
|
||||
ErrEmailIsEmptyOrInvalid = NewNormalError(NormalSubcategoryUser, 18, http.StatusBadRequest, "email is empty or invalid")
|
||||
ErrNewPasswordEqualsOldInvalid = NewNormalError(NormalSubcategoryUser, 19, http.StatusBadRequest, "new password equals old password")
|
||||
ErrEmailIsNotVerified = NewNormalError(NormalSubcategoryUser, 20, http.StatusBadRequest, "email is not verified")
|
||||
ErrEmailIsVerified = NewNormalError(NormalSubcategoryUser, 21, http.StatusBadRequest, "email is verified")
|
||||
ErrEmailValidationNotAllowed = NewNormalError(NormalSubcategoryUser, 22, http.StatusBadRequest, "email validation not allowed")
|
||||
)
|
||||
|
||||
@@ -33,7 +33,7 @@ type BankOfCanadaExchangeRateData struct {
|
||||
}
|
||||
|
||||
// BankOfCanadaObservationData represents the observation data from bank of Canada
|
||||
type BankOfCanadaObservationData map[string]interface{}
|
||||
type BankOfCanadaObservationData map[string]any
|
||||
|
||||
// ToLatestExchangeRateResponse returns a view-object according to original data from bank of Canada
|
||||
func (e *BankOfCanadaExchangeRateData) ToLatestExchangeRateResponse(c *core.Context) *models.LatestExchangeRateResponse {
|
||||
@@ -62,7 +62,7 @@ func (e *BankOfCanadaExchangeRateData) ToLatestExchangeRateResponse(c *core.Cont
|
||||
|
||||
currencyCode := utils.SubString(typeName, 2, 3)
|
||||
|
||||
if data, ok := exchangeRateData.(map[string]interface{}); ok {
|
||||
if data, ok := exchangeRateData.(map[string]any); ok {
|
||||
exchangeRate := data["v"]
|
||||
|
||||
if exchangeRateValue, ok2 := exchangeRate.(string); ok2 {
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package locales
|
||||
|
||||
// DefaultLanguage represents the default language
|
||||
var DefaultLanguage = en
|
||||
|
||||
// AllLanguages represents all the supported language
|
||||
var AllLanguages = map[string]*LocaleInfo{
|
||||
"en": {
|
||||
Content: en,
|
||||
},
|
||||
"zh-Hans": {
|
||||
Content: zhHans,
|
||||
},
|
||||
}
|
||||
|
||||
func GetLocaleTextItems(locale string) *LocaleTextItems {
|
||||
localeInfo, exists := AllLanguages[locale]
|
||||
|
||||
if exists {
|
||||
return localeInfo.Content
|
||||
}
|
||||
|
||||
return DefaultLanguage
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package locales
|
||||
|
||||
// LocaleTextItems represents all text items need to be translated
|
||||
type LocaleTextItems struct {
|
||||
VerifyEmailTextItems *VerifyEmailTextItems
|
||||
ForgetPasswordMailTextItems *ForgetPasswordMailTextItems
|
||||
}
|
||||
|
||||
// VerifyEmailTextItems represents text items need to be translated in verify mail
|
||||
type VerifyEmailTextItems struct {
|
||||
Title string
|
||||
SalutationFormat string
|
||||
DescriptionAboveBtn string
|
||||
VerifyEmail string
|
||||
DescriptionBelowBtnFormat string
|
||||
}
|
||||
|
||||
// ForgetPasswordMailTextItems represents text items need to be translated in forget password mail
|
||||
type ForgetPasswordMailTextItems struct {
|
||||
Title string
|
||||
SalutationFormat string
|
||||
DescriptionAboveBtn string
|
||||
ResetPassword string
|
||||
DescriptionBelowBtnFormat string
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package locales
|
||||
|
||||
var en = &LocaleTextItems{
|
||||
VerifyEmailTextItems: &VerifyEmailTextItems{
|
||||
Title: "Verify Email",
|
||||
SalutationFormat: "Hi %s,",
|
||||
DescriptionAboveBtn: "Please click the link below to confirm your email address.",
|
||||
VerifyEmail: "Verify Email",
|
||||
DescriptionBelowBtnFormat: "If you did not sign up for %s account, please simply disregard this email. If you cannot click the link above, please copy the above url and paste it into your browser. The verify email link will be expired after %v minutes.",
|
||||
},
|
||||
ForgetPasswordMailTextItems: &ForgetPasswordMailTextItems{
|
||||
Title: "Reset Your Password",
|
||||
SalutationFormat: "Hi %s,",
|
||||
DescriptionAboveBtn: "We recently received a request to reset your password. You can click the link below to reset your password.",
|
||||
ResetPassword: "Reset Password",
|
||||
DescriptionBelowBtnFormat: "If you did not request to reset your password, please simply disregard this email. If you cannot click the link above, please copy the above url and paste it into your browser. The password reset link will be expired after %v minutes.",
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package locales
|
||||
|
||||
// LocaleInfo represents locale info
|
||||
type LocaleInfo struct {
|
||||
Aliases []string
|
||||
Content *LocaleTextItems
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package locales
|
||||
|
||||
var zhHans = &LocaleTextItems{
|
||||
VerifyEmailTextItems: &VerifyEmailTextItems{
|
||||
Title: "验证邮箱",
|
||||
SalutationFormat: "%s 您好,",
|
||||
DescriptionAboveBtn: "请点击下方的链接确认您的邮箱地址。",
|
||||
VerifyEmail: "验证邮箱",
|
||||
DescriptionBelowBtnFormat: "如果您没有注册 %s 账户,请直接忽略本邮件。如果您无法点击上述链接,请复制下方的地址然后在您的浏览器中粘贴。邮箱验证链接将在 %v 分钟后过期。",
|
||||
},
|
||||
ForgetPasswordMailTextItems: &ForgetPasswordMailTextItems{
|
||||
Title: "重置密码",
|
||||
SalutationFormat: "%s 您好,",
|
||||
DescriptionAboveBtn: "我们刚才收到重置您密码的请求。您可以点击下方链接重置您的密码。",
|
||||
ResetPassword: "重置密码",
|
||||
DescriptionBelowBtnFormat: "如果您没有请求重置密码,请直接忽略本邮件。如果您无法点击上述链接,请复制下方的地址然后在您的浏览器中粘贴。重置密码链接将在 %v 分钟后过期。",
|
||||
},
|
||||
}
|
||||
@@ -41,12 +41,12 @@ func (f *LogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
|
||||
b.WriteString("] ")
|
||||
}
|
||||
|
||||
b.WriteString(entry.Message)
|
||||
|
||||
if requestId, exists := entry.Data[logFieldRequestId]; exists {
|
||||
b.WriteString(fmt.Sprintf(", r=%s", requestId))
|
||||
b.WriteString(fmt.Sprintf("[r=%s] ", requestId))
|
||||
}
|
||||
|
||||
b.WriteString(entry.Message)
|
||||
|
||||
b.WriteString("\n")
|
||||
|
||||
if extra, exists := entry.Data[logFieldExtra]; exists {
|
||||
|
||||
@@ -39,11 +39,13 @@ func init() {
|
||||
}
|
||||
|
||||
// SetLoggerConfiguration sets the logger according to the config
|
||||
func SetLoggerConfiguration(config *settings.Config) error {
|
||||
func SetLoggerConfiguration(config *settings.Config, isDisableBootLog bool) error {
|
||||
var bootWriters []io.Writer
|
||||
var writers []io.Writer
|
||||
|
||||
bootWriters = append(bootWriters, os.Stdout)
|
||||
if !isDisableBootLog {
|
||||
bootWriters = append(bootWriters, os.Stdout)
|
||||
}
|
||||
|
||||
if config.EnableConsoleLog {
|
||||
writers = append(writers, os.Stdout)
|
||||
@@ -56,7 +58,10 @@ func SetLoggerConfiguration(config *settings.Config) error {
|
||||
return err
|
||||
}
|
||||
|
||||
bootWriters = append(bootWriters, logFile)
|
||||
if !isDisableBootLog {
|
||||
bootWriters = append(bootWriters, logFile)
|
||||
}
|
||||
|
||||
writers = append(writers, logFile)
|
||||
}
|
||||
|
||||
@@ -94,93 +99,93 @@ func SetLoggerConfiguration(config *settings.Config) error {
|
||||
}
|
||||
|
||||
// Debugf logs debug log with custom format
|
||||
func Debugf(format string, args ...interface{}) {
|
||||
defaultLogger.Debugf(getFinalLog(format, args...))
|
||||
func Debugf(format string, args ...any) {
|
||||
defaultLogger.Debug(getFinalLog(format, args...))
|
||||
}
|
||||
|
||||
// DebugfWithRequestId logs debug log with custom format and request id
|
||||
func DebugfWithRequestId(c *core.Context, format string, args ...interface{}) {
|
||||
defaultLogger.WithField(logFieldRequestId, c.GetRequestId()).Debugf(getFinalLog(format, args...))
|
||||
func DebugfWithRequestId(c *core.Context, format string, args ...any) {
|
||||
defaultLogger.WithField(logFieldRequestId, c.GetRequestId()).Debug(getFinalLog(format, args...))
|
||||
}
|
||||
|
||||
// Infof logs info log with custom format
|
||||
func Infof(format string, args ...interface{}) {
|
||||
defaultLogger.Infof(getFinalLog(format, args...))
|
||||
func Infof(format string, args ...any) {
|
||||
defaultLogger.Info(getFinalLog(format, args...))
|
||||
}
|
||||
|
||||
// InfofWithRequestId logs info log with custom format and request id
|
||||
func InfofWithRequestId(c *core.Context, format string, args ...interface{}) {
|
||||
defaultLogger.WithField(logFieldRequestId, c.GetRequestId()).Infof(getFinalLog(format, args...))
|
||||
func InfofWithRequestId(c *core.Context, format string, args ...any) {
|
||||
defaultLogger.WithField(logFieldRequestId, c.GetRequestId()).Info(getFinalLog(format, args...))
|
||||
}
|
||||
|
||||
// Warnf logs warn log with custom format
|
||||
func Warnf(format string, args ...interface{}) {
|
||||
defaultLogger.Warnf(getFinalLog(format, args...))
|
||||
func Warnf(format string, args ...any) {
|
||||
defaultLogger.Warn(getFinalLog(format, args...))
|
||||
}
|
||||
|
||||
// WarnfWithRequestId logs warn log with custom format and request id
|
||||
func WarnfWithRequestId(c *core.Context, format string, args ...interface{}) {
|
||||
defaultLogger.WithField(logFieldRequestId, c.GetRequestId()).Warnf(getFinalLog(format, args...))
|
||||
func WarnfWithRequestId(c *core.Context, format string, args ...any) {
|
||||
defaultLogger.WithField(logFieldRequestId, c.GetRequestId()).Warn(getFinalLog(format, args...))
|
||||
}
|
||||
|
||||
// Errorf logs error log with custom format
|
||||
func Errorf(format string, args ...interface{}) {
|
||||
defaultLogger.Errorf(getFinalLog(format, args...))
|
||||
func Errorf(format string, args ...any) {
|
||||
defaultLogger.Error(getFinalLog(format, args...))
|
||||
}
|
||||
|
||||
// ErrorfWithRequestId logs error log with custom format and request id
|
||||
func ErrorfWithRequestId(c *core.Context, format string, args ...interface{}) {
|
||||
defaultLogger.WithField(logFieldRequestId, c.GetRequestId()).Errorf(getFinalLog(format, args...))
|
||||
func ErrorfWithRequestId(c *core.Context, format string, args ...any) {
|
||||
defaultLogger.WithField(logFieldRequestId, c.GetRequestId()).Error(getFinalLog(format, args...))
|
||||
}
|
||||
|
||||
// ErrorfWithRequestIdAndExtra logs error log with custom format and request id and extra info
|
||||
func ErrorfWithRequestIdAndExtra(c *core.Context, extraString string, format string, args ...interface{}) {
|
||||
defaultLogger.WithField(logFieldRequestId, c.GetRequestId()).WithField(logFieldExtra, extraString).Errorf(getFinalLog(format, args...))
|
||||
func ErrorfWithRequestIdAndExtra(c *core.Context, extraString string, format string, args ...any) {
|
||||
defaultLogger.WithField(logFieldRequestId, c.GetRequestId()).WithField(logFieldExtra, extraString).Error(getFinalLog(format, args...))
|
||||
}
|
||||
|
||||
// BootInfof logs boot info log
|
||||
func BootInfof(format string, args ...interface{}) {
|
||||
func BootInfof(format string, args ...any) {
|
||||
if bootLogger != nil {
|
||||
bootLogger.Infof(getFinalLog(format, args...))
|
||||
bootLogger.Info(getFinalLog(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
// BootWarnf logs boot warn log
|
||||
func BootWarnf(format string, args ...interface{}) {
|
||||
func BootWarnf(format string, args ...any) {
|
||||
if bootLogger != nil {
|
||||
bootLogger.Warnf(getFinalLog(format, args...))
|
||||
bootLogger.Warn(getFinalLog(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
// BootErrorf logs boot error log
|
||||
func BootErrorf(format string, args ...interface{}) {
|
||||
func BootErrorf(format string, args ...any) {
|
||||
if bootLogger != nil {
|
||||
bootLogger.Errorf(getFinalLog(format, args...))
|
||||
bootLogger.Error(getFinalLog(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
// Requestf logs http request log with custom format
|
||||
func Requestf(c *core.Context, format string, args ...interface{}) {
|
||||
func Requestf(c *core.Context, format string, args ...any) {
|
||||
if requestLogger != nil {
|
||||
requestLogger.WithField(logFieldRequestId, c.GetRequestId()).Infof(getFinalLog(format, args...))
|
||||
requestLogger.WithField(logFieldRequestId, c.GetRequestId()).Info(getFinalLog(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
// SqlQuery logs sql query log
|
||||
func SqlQuery(args ...interface{}) {
|
||||
func SqlQuery(args ...any) {
|
||||
if sqlQueryLogger != nil {
|
||||
sqlQueryLogger.Info(args...)
|
||||
}
|
||||
}
|
||||
|
||||
// SqlQueryf logs sql query log with custom format
|
||||
func SqlQueryf(format string, args ...interface{}) {
|
||||
func SqlQueryf(format string, args ...any) {
|
||||
if sqlQueryLogger != nil {
|
||||
sqlQueryLogger.Infof(getFinalLog(format, args...))
|
||||
sqlQueryLogger.Info(getFinalLog(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
func getFinalLog(format string, args ...interface{}) string {
|
||||
func getFinalLog(format string, args ...any) string {
|
||||
result := fmt.Sprintf(format, args...)
|
||||
result = strings.Replace(result, "\n", " ", -1)
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package mail
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
|
||||
"gopkg.in/mail.v2"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||
)
|
||||
|
||||
// DefaultMailer represents default mailer
|
||||
type DefaultMailer struct {
|
||||
dialer *mail.Dialer
|
||||
fromAddress string
|
||||
}
|
||||
|
||||
// NewDefaultMailer returns a new default mailer
|
||||
func NewDefaultMailer(smtpConfig *settings.SMTPConfig) (*DefaultMailer, error) {
|
||||
host, portStr, err := net.SplitHostPort(smtpConfig.SMTPHost)
|
||||
|
||||
if err != nil {
|
||||
return nil, errs.ErrSMTPServerHostInvalid
|
||||
}
|
||||
|
||||
port, err := utils.StringToInt(portStr)
|
||||
|
||||
if err != nil {
|
||||
return nil, errs.ErrSMTPServerHostInvalid
|
||||
}
|
||||
|
||||
dialer := mail.NewDialer(host, port, smtpConfig.SMTPUser, smtpConfig.SMTPPasswd)
|
||||
dialer.TLSConfig = &tls.Config{
|
||||
ServerName: host,
|
||||
InsecureSkipVerify: smtpConfig.SMTPSkipTLSVerify,
|
||||
}
|
||||
|
||||
mailer := &DefaultMailer{
|
||||
dialer: dialer,
|
||||
fromAddress: smtpConfig.FromAddress,
|
||||
}
|
||||
|
||||
return mailer, nil
|
||||
}
|
||||
|
||||
// SendMail sends an email according to argument
|
||||
func (m *DefaultMailer) SendMail(message *MailMessage) error {
|
||||
if m.dialer == nil {
|
||||
return errs.ErrSMTPServerNotEnabled
|
||||
}
|
||||
|
||||
mailMessage := mail.NewMessage()
|
||||
mailMessage.SetHeader("From", m.fromAddress)
|
||||
mailMessage.SetHeader("To", message.To)
|
||||
mailMessage.SetHeader("Subject", message.Subject)
|
||||
mailMessage.SetBody("text/html", message.Body)
|
||||
|
||||
err := m.dialer.DialAndSend(mailMessage)
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package mail
|
||||
|
||||
// MailMessage represents an email entity
|
||||
type MailMessage struct {
|
||||
To string
|
||||
Subject string
|
||||
Body string
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package mail
|
||||
|
||||
// Mailer is email sender interface
|
||||
type Mailer interface {
|
||||
SendMail(message *MailMessage) error
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package mail
|
||||
|
||||
import (
|
||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||
)
|
||||
|
||||
// MailerContainer contains the current mailer
|
||||
type MailerContainer struct {
|
||||
Current Mailer
|
||||
}
|
||||
|
||||
// Initialize a mailer container singleton instance
|
||||
var (
|
||||
Container = &MailerContainer{}
|
||||
)
|
||||
|
||||
// InitializeMailer initializes the current mailer according to the config
|
||||
func InitializeMailer(config *settings.Config) error {
|
||||
if !config.EnableSMTP {
|
||||
Container.Current = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
mailer, err := NewDefaultMailer(config.SMTPConfig)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Container.Current = mailer
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendMail sends an email according to argument
|
||||
func (u *MailerContainer) SendMail(message *MailMessage) error {
|
||||
return u.Current.SendMail(message)
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||
)
|
||||
|
||||
const tokenCookieParam = "ebk_auth_token"
|
||||
|
||||
// AmapApiProxyAuthCookie adds amap api proxy auth cookie to cookies in response
|
||||
func AmapApiProxyAuthCookie(c *core.Context, config *settings.Config) {
|
||||
token := c.GetTextualToken()
|
||||
|
||||
if token != "" {
|
||||
c.SetCookie(tokenCookieParam, token, int(config.TokenExpiredTime), "/_AMapService", "", false, true)
|
||||
} else {
|
||||
c.SetCookie(tokenCookieParam, "", -1, "/_AMapService", "", false, true)
|
||||
}
|
||||
}
|
||||
@@ -17,58 +17,24 @@ type TokenSourceType byte
|
||||
const (
|
||||
TOKEN_SOURCE_TYPE_HEADER TokenSourceType = 1
|
||||
TOKEN_SOURCE_TYPE_ARGUMENT TokenSourceType = 2
|
||||
TOKEN_SOURCE_TYPE_COOKIE TokenSourceType = 3
|
||||
)
|
||||
|
||||
const tokenQueryStringParam = "token"
|
||||
|
||||
// JWTAuthorization verifies whether current request is valid by jwt token
|
||||
// JWTAuthorization verifies whether current request is valid by jwt token in header
|
||||
func JWTAuthorization(c *core.Context) {
|
||||
claims, err := getTokenClaims(c, TOKEN_SOURCE_TYPE_HEADER)
|
||||
|
||||
if err != nil {
|
||||
utils.PrintJsonErrorResult(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
if claims.Type == core.USER_TOKEN_TYPE_REQUIRE_2FA {
|
||||
log.WarnfWithRequestId(c, "[authorization.JWTAuthorization] user \"uid:%d\" token requires 2fa", claims.Uid)
|
||||
utils.PrintJsonErrorResult(c, errs.ErrCurrentTokenRequire2FA)
|
||||
return
|
||||
}
|
||||
|
||||
if claims.Type != core.USER_TOKEN_TYPE_NORMAL {
|
||||
log.WarnfWithRequestId(c, "[authorization.JWTAuthorization] user \"uid:%d\" token type is invalid", claims.Uid)
|
||||
utils.PrintJsonErrorResult(c, errs.ErrCurrentInvalidTokenType)
|
||||
return
|
||||
}
|
||||
|
||||
c.SetTokenClaims(claims)
|
||||
c.Next()
|
||||
jwtAuthorization(c, TOKEN_SOURCE_TYPE_HEADER)
|
||||
}
|
||||
|
||||
// JWTAuthorizationByQueryString verifies whether current request is valid by jwt token
|
||||
// JWTAuthorizationByQueryString verifies whether current request is valid by jwt token in query string
|
||||
func JWTAuthorizationByQueryString(c *core.Context) {
|
||||
claims, err := getTokenClaims(c, TOKEN_SOURCE_TYPE_ARGUMENT)
|
||||
jwtAuthorization(c, TOKEN_SOURCE_TYPE_ARGUMENT)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
utils.PrintJsonErrorResult(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
if claims.Type == core.USER_TOKEN_TYPE_REQUIRE_2FA {
|
||||
log.WarnfWithRequestId(c, "[authorization.JWTAuthorizationByQueryString] user \"uid:%d\" token requires 2fa", claims.Uid)
|
||||
utils.PrintJsonErrorResult(c, errs.ErrCurrentTokenRequire2FA)
|
||||
return
|
||||
}
|
||||
|
||||
if claims.Type != core.USER_TOKEN_TYPE_NORMAL {
|
||||
log.WarnfWithRequestId(c, "[authorization.JWTAuthorizationByQueryString] user \"uid:%d\" token type is invalid", claims.Uid)
|
||||
utils.PrintJsonErrorResult(c, errs.ErrCurrentInvalidTokenType)
|
||||
return
|
||||
}
|
||||
|
||||
c.SetTokenClaims(claims)
|
||||
c.Next()
|
||||
// JWTAuthorizationByCookie verifies whether current request is valid by jwt token in cookie
|
||||
func JWTAuthorizationByCookie(c *core.Context) {
|
||||
jwtAuthorization(c, TOKEN_SOURCE_TYPE_COOKIE)
|
||||
}
|
||||
|
||||
// JWTTwoFactorAuthorization verifies whether current request is valid by 2fa passcode
|
||||
@@ -90,6 +56,68 @@ func JWTTwoFactorAuthorization(c *core.Context) {
|
||||
c.Next()
|
||||
}
|
||||
|
||||
// JWTEmailVerifyAuthorization verifies whether current request is email verification
|
||||
func JWTEmailVerifyAuthorization(c *core.Context) {
|
||||
claims, err := getTokenClaims(c, TOKEN_SOURCE_TYPE_ARGUMENT)
|
||||
|
||||
if err != nil {
|
||||
utils.PrintJsonErrorResult(c, errs.ErrEmailVerifyTokenIsInvalidOrExpired)
|
||||
return
|
||||
}
|
||||
|
||||
if claims.Type != core.USER_TOKEN_TYPE_EMAIL_VERIFY {
|
||||
log.WarnfWithRequestId(c, "[authorization.JWTEmailVerifyAuthorization] user \"uid:%d\" token is not for email verification", claims.Uid)
|
||||
utils.PrintJsonErrorResult(c, errs.ErrCurrentInvalidToken)
|
||||
return
|
||||
}
|
||||
|
||||
c.SetTokenClaims(claims)
|
||||
c.Next()
|
||||
}
|
||||
|
||||
// JWTResetPasswordAuthorization verifies whether current request is password reset
|
||||
func JWTResetPasswordAuthorization(c *core.Context) {
|
||||
claims, err := getTokenClaims(c, TOKEN_SOURCE_TYPE_ARGUMENT)
|
||||
|
||||
if err != nil {
|
||||
utils.PrintJsonErrorResult(c, errs.ErrPasswordResetTokenIsInvalidOrExpired)
|
||||
return
|
||||
}
|
||||
|
||||
if claims.Type != core.USER_TOKEN_TYPE_PASSWORD_RESET {
|
||||
log.WarnfWithRequestId(c, "[authorization.JWTResetPasswordAuthorization] user \"uid:%d\" token is not for password request", claims.Uid)
|
||||
utils.PrintJsonErrorResult(c, errs.ErrCurrentInvalidToken)
|
||||
return
|
||||
}
|
||||
|
||||
c.SetTokenClaims(claims)
|
||||
c.Next()
|
||||
}
|
||||
|
||||
func jwtAuthorization(c *core.Context, source TokenSourceType) {
|
||||
claims, err := getTokenClaims(c, source)
|
||||
|
||||
if err != nil {
|
||||
utils.PrintJsonErrorResult(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
if claims.Type == core.USER_TOKEN_TYPE_REQUIRE_2FA {
|
||||
log.WarnfWithRequestId(c, "[authorization.jwtAuthorization] user \"uid:%d\" token requires 2fa", claims.Uid)
|
||||
utils.PrintJsonErrorResult(c, errs.ErrCurrentTokenRequire2FA)
|
||||
return
|
||||
}
|
||||
|
||||
if claims.Type != core.USER_TOKEN_TYPE_NORMAL {
|
||||
log.WarnfWithRequestId(c, "[authorization.jwtAuthorization] user \"uid:%d\" token type is invalid", claims.Uid)
|
||||
utils.PrintJsonErrorResult(c, errs.ErrCurrentInvalidTokenType)
|
||||
return
|
||||
}
|
||||
|
||||
c.SetTokenClaims(claims)
|
||||
c.Next()
|
||||
}
|
||||
|
||||
func getTokenClaims(c *core.Context, source TokenSourceType) (*core.UserTokenClaims, *errs.Error) {
|
||||
token, claims, err := parseToken(c, source)
|
||||
|
||||
@@ -114,6 +142,8 @@ func getTokenClaims(c *core.Context, source TokenSourceType) (*core.UserTokenCla
|
||||
func parseToken(c *core.Context, source TokenSourceType) (*jwt.Token, *core.UserTokenClaims, error) {
|
||||
if source == TOKEN_SOURCE_TYPE_ARGUMENT {
|
||||
return services.Tokens.ParseTokenByArgument(c, tokenQueryStringParam)
|
||||
} else if source == TOKEN_SOURCE_TYPE_COOKIE {
|
||||
return services.Tokens.ParseTokenByCookie(c, tokenCookieParam)
|
||||
}
|
||||
|
||||
return services.Tokens.ParseTokenByHeader(c)
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
@@ -15,9 +17,48 @@ func ServerSettingsCookie(config *settings.Config) core.MiddlewareHandlerFunc {
|
||||
return func(c *core.Context) {
|
||||
settingsArr := []string{
|
||||
buildBooleanSetting("r", config.EnableUserRegister),
|
||||
buildBooleanSetting("f", config.EnableUserForgetPassword),
|
||||
buildBooleanSetting("v", config.EnableUserVerifyEmail),
|
||||
buildBooleanSetting("e", config.EnableDataExport),
|
||||
buildStringSetting("m", config.MapProvider),
|
||||
buildBooleanSetting("mp", config.EnableMapDataFetchProxy),
|
||||
buildStringSetting("m", strings.Replace(config.MapProvider, "_", "-", -1)),
|
||||
}
|
||||
|
||||
if config.EnableMapDataFetchProxy &&
|
||||
(config.MapProvider == settings.OpenStreetMapProvider ||
|
||||
config.MapProvider == settings.OpenStreetMapHumanitarianStyleProvider ||
|
||||
config.MapProvider == settings.OpenTopoMapProvider ||
|
||||
config.MapProvider == settings.OPNVKarteMapProvider ||
|
||||
config.MapProvider == settings.CyclOSMMapProvider ||
|
||||
config.MapProvider == settings.TomTomMapProvider) {
|
||||
settingsArr = append(settingsArr, buildBooleanSetting("mp", config.EnableMapDataFetchProxy))
|
||||
}
|
||||
|
||||
if config.MapProvider == settings.TomTomMapProvider && config.TomTomMapAPIKey != "" && !config.EnableMapDataFetchProxy {
|
||||
settingsArr = append(settingsArr, buildEncodedStringSetting("tmak", config.TomTomMapAPIKey))
|
||||
}
|
||||
|
||||
if config.MapProvider == settings.GoogleMapProvider && config.GoogleMapAPIKey != "" {
|
||||
settingsArr = append(settingsArr, buildEncodedStringSetting("gmak", config.GoogleMapAPIKey))
|
||||
}
|
||||
|
||||
if config.MapProvider == settings.BaiduMapProvider && config.BaiduMapAK != "" {
|
||||
settingsArr = append(settingsArr, buildEncodedStringSetting("bmak", config.BaiduMapAK))
|
||||
}
|
||||
|
||||
if config.MapProvider == settings.AmapProvider && config.AmapApplicationKey != "" {
|
||||
settingsArr = append(settingsArr, buildEncodedStringSetting("amak", config.AmapApplicationKey))
|
||||
}
|
||||
|
||||
if config.MapProvider == settings.AmapProvider && config.AmapSecurityVerificationMethod != "" {
|
||||
settingsArr = append(settingsArr, buildStringSetting("amsv", strings.Replace(config.AmapSecurityVerificationMethod, "_", "", -1)))
|
||||
|
||||
if config.AmapSecurityVerificationMethod == settings.AmapSecurityVerificationExternalProxyMethod {
|
||||
settingsArr = append(settingsArr, buildEncodedStringSetting("amep", config.AmapApiExternalProxyUrl))
|
||||
}
|
||||
|
||||
if config.AmapSecurityVerificationMethod == settings.AmapSecurityVerificationPlainTextMethod {
|
||||
settingsArr = append(settingsArr, buildEncodedStringSetting("amas", config.AmapApplicationSecret))
|
||||
}
|
||||
}
|
||||
|
||||
bundledSettings := strings.Join(settingsArr, "_")
|
||||
@@ -28,7 +69,13 @@ func ServerSettingsCookie(config *settings.Config) core.MiddlewareHandlerFunc {
|
||||
}
|
||||
|
||||
func buildStringSetting(key string, value string) string {
|
||||
return fmt.Sprintf("%s.%s", key, strings.Replace(value, ".", "-", -1))
|
||||
return fmt.Sprintf("%s.%s", key, value)
|
||||
}
|
||||
|
||||
func buildEncodedStringSetting(key string, value string) string {
|
||||
urlEncodedValue := url.QueryEscape(value)
|
||||
base64Value := base64.StdEncoding.EncodeToString([]byte(urlEncodedValue))
|
||||
return fmt.Sprintf("%s.%s", key, base64Value)
|
||||
}
|
||||
|
||||
func buildBooleanSetting(key string, value bool) string {
|
||||
|
||||
@@ -6,3 +6,10 @@ type AuthResponse struct {
|
||||
Need2FA bool `json:"need2FA"`
|
||||
User *UserBasicInfo `json:"user"`
|
||||
}
|
||||
|
||||
// RegisterResponse returns a view-object of user register response
|
||||
type RegisterResponse struct {
|
||||
AuthResponse
|
||||
NeedVerifyEmail bool `json:"needVerifyEmail"`
|
||||
PresetCategoriesSaved bool `json:"presetCategoriesSaved"`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package models
|
||||
|
||||
// ForgetPasswordRequest represents all parameters of forget password request
|
||||
type ForgetPasswordRequest struct {
|
||||
Email string `json:"email" binding:"required,notBlank,max=100,validEmail"`
|
||||
}
|
||||
|
||||
// PasswordResetRequest represents all parameters of reset password request
|
||||
type PasswordResetRequest struct {
|
||||
Email string `json:"email" binding:"required,notBlank,max=100,validEmail"`
|
||||
Password string `json:"password" binding:"required,min=6,max=128"`
|
||||
}
|
||||
@@ -112,7 +112,9 @@ type TransactionListByMaxTimeRequest struct {
|
||||
Keyword string `form:"keyword"`
|
||||
MaxTime int64 `form:"max_time" binding:"min=0"`
|
||||
MinTime int64 `form:"min_time" binding:"min=0"`
|
||||
Page int32 `form:"page" binding:"min=0"`
|
||||
Count int32 `form:"count" binding:"required,min=1,max=50"`
|
||||
WithCount bool `form:"with_count"`
|
||||
TrimAccount bool `form:"trim_account"`
|
||||
TrimCategory bool `form:"trim_category"`
|
||||
TrimTag bool `form:"trim_tag"`
|
||||
@@ -126,8 +128,6 @@ type TransactionListInMonthByPageRequest struct {
|
||||
CategoryId int64 `form:"category_id" binding:"min=0"`
|
||||
AccountId int64 `form:"account_id" binding:"min=0"`
|
||||
Keyword string `form:"keyword"`
|
||||
Page int32 `form:"page" binding:"required,min=1"`
|
||||
Count int32 `form:"count" binding:"required,min=1,max=50"`
|
||||
TrimAccount bool `form:"trim_account"`
|
||||
TrimCategory bool `form:"trim_category"`
|
||||
TrimTag bool `form:"trim_tag"`
|
||||
@@ -218,6 +218,7 @@ type TransactionCountResponse struct {
|
||||
type TransactionInfoPageWrapperResponse struct {
|
||||
Items TransactionInfoResponseSlice `json:"items"`
|
||||
NextTimeSequenceId *int64 `json:"nextTimeSequenceId,string"`
|
||||
TotalCount *int64 `json:"totalCount,omitempty"`
|
||||
}
|
||||
|
||||
// TransactionInfoPageWrapperResponse2 represents a response of transaction which contains items and count
|
||||
@@ -451,3 +452,21 @@ func (s TransactionMonthAmountsResponseItemSlice) Less(i, j int) bool {
|
||||
|
||||
return s[i].Month > s[j].Month
|
||||
}
|
||||
|
||||
// TransactionAmountsResponseItemAmountInfoSlice represents the slice data structure of TransactionAmountsResponseItemAmountInfo
|
||||
type TransactionAmountsResponseItemAmountInfoSlice []*TransactionAmountsResponseItemAmountInfo
|
||||
|
||||
// Len returns the count of items
|
||||
func (s TransactionAmountsResponseItemAmountInfoSlice) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
// Swap swaps two items
|
||||
func (s TransactionAmountsResponseItemAmountInfoSlice) 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 TransactionAmountsResponseItemAmountInfoSlice) Less(i, j int) bool {
|
||||
return strings.Compare(s[i].Currency, s[j].Currency) < 0
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||
)
|
||||
|
||||
@@ -63,6 +64,7 @@ type User struct {
|
||||
ShortDateFormat ShortDateFormat `xorm:"TINYINT"`
|
||||
LongTimeFormat LongTimeFormat `xorm:"TINYINT"`
|
||||
ShortTimeFormat ShortTimeFormat `xorm:"TINYINT"`
|
||||
Disabled bool `xorm:"NOT NULL"`
|
||||
Deleted bool `xorm:"NOT NULL"`
|
||||
EmailVerified bool `xorm:"NOT NULL"`
|
||||
CreatedUnixTime int64
|
||||
@@ -76,6 +78,8 @@ type UserBasicInfo struct {
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
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"`
|
||||
@@ -85,6 +89,7 @@ type UserBasicInfo struct {
|
||||
ShortDateFormat ShortDateFormat `json:"shortDateFormat"`
|
||||
LongTimeFormat LongTimeFormat `json:"longTimeFormat"`
|
||||
ShortTimeFormat ShortTimeFormat `json:"shortTimeFormat"`
|
||||
EmailVerified bool `json:"emailVerified"`
|
||||
}
|
||||
|
||||
// UserLoginRequest represents all parameters of user login request
|
||||
@@ -102,6 +107,24 @@ type UserRegisterRequest struct {
|
||||
Language string `json:"language" binding:"required,min=2,max=16"`
|
||||
DefaultCurrency string `json:"defaultCurrency" binding:"required,len=3,validCurrency"`
|
||||
FirstDayOfWeek WeekDay `json:"firstDayOfWeek" binding:"min=0,max=6"`
|
||||
TransactionCategoryCreateBatchRequest
|
||||
}
|
||||
|
||||
// UserVerifyEmailRequest represents all parameters of user verify email request
|
||||
type UserVerifyEmailRequest struct {
|
||||
RequestNewToken bool `json:"requestNewToken" binding:"omitempty"`
|
||||
}
|
||||
|
||||
// UserVerifyEmailResponse represents all response parameters after user have verified email
|
||||
type UserVerifyEmailResponse struct {
|
||||
NewToken string `json:"newToken,omitempty"`
|
||||
User *UserBasicInfo `json:"user"`
|
||||
}
|
||||
|
||||
// UserResendVerifyEmailRequest represents all parameters of user resend verify email request
|
||||
type UserResendVerifyEmailRequest struct {
|
||||
Email string `json:"email" binding:"omitempty,max=100,validEmail"`
|
||||
Password string `json:"password" binding:"omitempty,min=6,max=128"`
|
||||
}
|
||||
|
||||
// UserProfileUpdateRequest represents all parameters of user updating profile request
|
||||
@@ -132,6 +155,8 @@ type UserProfileResponse struct {
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
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"`
|
||||
@@ -141,6 +166,7 @@ type UserProfileResponse struct {
|
||||
ShortDateFormat ShortDateFormat `json:"shortDateFormat"`
|
||||
LongTimeFormat LongTimeFormat `json:"longTimeFormat"`
|
||||
ShortTimeFormat ShortTimeFormat `json:"shortTimeFormat"`
|
||||
EmailVerified bool `json:"emailVerified"`
|
||||
LastLoginAt int64 `json:"lastLoginAt"`
|
||||
}
|
||||
|
||||
@@ -192,6 +218,8 @@ func (u *User) ToUserBasicInfo() *UserBasicInfo {
|
||||
Username: u.Username,
|
||||
Email: u.Email,
|
||||
Nickname: u.Nickname,
|
||||
AvatarUrl: u.getAvatarUrl(),
|
||||
AvatarProvider: u.getAvatarProvider(),
|
||||
DefaultAccountId: u.DefaultAccountId,
|
||||
TransactionEditScope: u.TransactionEditScope,
|
||||
Language: u.Language,
|
||||
@@ -201,6 +229,7 @@ func (u *User) ToUserBasicInfo() *UserBasicInfo {
|
||||
ShortDateFormat: u.ShortDateFormat,
|
||||
LongTimeFormat: u.LongTimeFormat,
|
||||
ShortTimeFormat: u.ShortTimeFormat,
|
||||
EmailVerified: u.EmailVerified,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,6 +239,8 @@ func (u *User) ToUserProfileResponse() *UserProfileResponse {
|
||||
Username: u.Username,
|
||||
Email: u.Email,
|
||||
Nickname: u.Nickname,
|
||||
AvatarUrl: u.getAvatarUrl(),
|
||||
AvatarProvider: u.getAvatarProvider(),
|
||||
DefaultAccountId: u.DefaultAccountId,
|
||||
TransactionEditScope: u.TransactionEditScope,
|
||||
Language: u.Language,
|
||||
@@ -219,6 +250,21 @@ func (u *User) ToUserProfileResponse() *UserProfileResponse {
|
||||
ShortDateFormat: u.ShortDateFormat,
|
||||
LongTimeFormat: u.LongTimeFormat,
|
||||
ShortTimeFormat: u.ShortTimeFormat,
|
||||
EmailVerified: u.EmailVerified,
|
||||
LastLoginAt: u.LastLoginUnixTime,
|
||||
}
|
||||
}
|
||||
|
||||
func (u *User) getAvatarProvider() string {
|
||||
return settings.Container.Current.AvatarProvider
|
||||
}
|
||||
|
||||
func (u *User) getAvatarUrl() string {
|
||||
avatarProvider := settings.Container.Current.AvatarProvider
|
||||
|
||||
if avatarProvider == settings.GravatarProvider {
|
||||
return utils.GetGravatarUrl(u.Email)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ type RequestIdInfo struct {
|
||||
type DefaultRequestIdGenerator struct {
|
||||
serverUniqId uint16
|
||||
instanceUniqId uint16
|
||||
requestSeqId uint32
|
||||
requestSeqId atomic.Uint32
|
||||
}
|
||||
|
||||
// NewDefaultRequestIdGenerator returns a new default request id generator
|
||||
@@ -154,7 +154,7 @@ func (r *DefaultRequestIdGenerator) getRequestId(serverUniqId uint16, instanceUn
|
||||
|
||||
secondsAndRandomNumber := (secondsLow17bits << randomNumberBits) | randomNumberLow15bits
|
||||
|
||||
seqId := atomic.AddUint32(&r.requestSeqId, 1)
|
||||
seqId := r.requestSeqId.Add(1)
|
||||
seqIdLow31bits := seqId & reqSeqNumberBitsMask
|
||||
|
||||
seqIdAndClientIpv6Flag := (seqIdLow31bits << clientIpv6Bit) | (clientIpv6Flag & clientIpv6BitMask)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"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"
|
||||
@@ -31,30 +32,30 @@ var (
|
||||
)
|
||||
|
||||
// GetTotalAccountCountByUid returns total account count of user
|
||||
func (s *AccountService) GetTotalAccountCountByUid(uid int64) (int64, error) {
|
||||
func (s *AccountService) GetTotalAccountCountByUid(c *core.Context, uid int64) (int64, error) {
|
||||
if uid <= 0 {
|
||||
return 0, errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
count, err := s.UserDataDB(uid).Where("uid=? AND deleted=?", uid, false).Count(&models.Account{})
|
||||
count, err := s.UserDataDB(uid).NewSession(c).Where("uid=? AND deleted=?", uid, false).Count(&models.Account{})
|
||||
|
||||
return count, err
|
||||
}
|
||||
|
||||
// GetAllAccountsByUid returns all account models of user
|
||||
func (s *AccountService) GetAllAccountsByUid(uid int64) ([]*models.Account, error) {
|
||||
func (s *AccountService) GetAllAccountsByUid(c *core.Context, uid int64) ([]*models.Account, error) {
|
||||
if uid <= 0 {
|
||||
return nil, errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
var accounts []*models.Account
|
||||
err := s.UserDataDB(uid).Where("uid=? AND deleted=?", uid, false).OrderBy("parent_account_id asc, display_order asc").Find(&accounts)
|
||||
err := s.UserDataDB(uid).NewSession(c).Where("uid=? AND deleted=?", uid, false).OrderBy("parent_account_id asc, display_order asc").Find(&accounts)
|
||||
|
||||
return accounts, err
|
||||
}
|
||||
|
||||
// GetAccountAndSubAccountsByAccountId returns account model and sub account models according to account id
|
||||
func (s *AccountService) GetAccountAndSubAccountsByAccountId(uid int64, accountId int64) ([]*models.Account, error) {
|
||||
func (s *AccountService) GetAccountAndSubAccountsByAccountId(c *core.Context, uid int64, accountId int64) ([]*models.Account, error) {
|
||||
if uid <= 0 {
|
||||
return nil, errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -64,13 +65,13 @@ func (s *AccountService) GetAccountAndSubAccountsByAccountId(uid int64, accountI
|
||||
}
|
||||
|
||||
var accounts []*models.Account
|
||||
err := s.UserDataDB(uid).Where("uid=? AND deleted=? AND (account_id=? OR parent_account_id=?)", uid, false, accountId, accountId).OrderBy("parent_account_id asc, display_order asc").Find(&accounts)
|
||||
err := s.UserDataDB(uid).NewSession(c).Where("uid=? AND deleted=? AND (account_id=? OR parent_account_id=?)", uid, false, accountId, accountId).OrderBy("parent_account_id asc, display_order asc").Find(&accounts)
|
||||
|
||||
return accounts, err
|
||||
}
|
||||
|
||||
// GetSubAccountsByAccountId returns sub account models according to account id
|
||||
func (s *AccountService) GetSubAccountsByAccountId(uid int64, accountId int64) ([]*models.Account, error) {
|
||||
func (s *AccountService) GetSubAccountsByAccountId(c *core.Context, uid int64, accountId int64) ([]*models.Account, error) {
|
||||
if uid <= 0 {
|
||||
return nil, errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -80,13 +81,13 @@ func (s *AccountService) GetSubAccountsByAccountId(uid int64, accountId int64) (
|
||||
}
|
||||
|
||||
var accounts []*models.Account
|
||||
err := s.UserDataDB(uid).Where("uid=? AND deleted=? AND parent_account_id=?", uid, false, accountId).OrderBy("display_order asc").Find(&accounts)
|
||||
err := s.UserDataDB(uid).NewSession(c).Where("uid=? AND deleted=? AND parent_account_id=?", uid, false, accountId).OrderBy("display_order asc").Find(&accounts)
|
||||
|
||||
return accounts, err
|
||||
}
|
||||
|
||||
// GetAccountsByAccountIds returns account models according to account ids
|
||||
func (s *AccountService) GetAccountsByAccountIds(uid int64, accountIds []int64) (map[int64]*models.Account, error) {
|
||||
func (s *AccountService) GetAccountsByAccountIds(c *core.Context, uid int64, accountIds []int64) (map[int64]*models.Account, error) {
|
||||
if uid <= 0 {
|
||||
return nil, errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -96,7 +97,7 @@ func (s *AccountService) GetAccountsByAccountIds(uid int64, accountIds []int64)
|
||||
}
|
||||
|
||||
var accounts []*models.Account
|
||||
err := s.UserDataDB(uid).Where("uid=? AND deleted=?", uid, false).In("account_id", accountIds).Find(&accounts)
|
||||
err := s.UserDataDB(uid).NewSession(c).Where("uid=? AND deleted=?", uid, false).In("account_id", accountIds).Find(&accounts)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -107,13 +108,13 @@ func (s *AccountService) GetAccountsByAccountIds(uid int64, accountIds []int64)
|
||||
}
|
||||
|
||||
// GetMaxDisplayOrder returns the max display order according to account category
|
||||
func (s *AccountService) GetMaxDisplayOrder(uid int64, category models.AccountCategory) (int32, error) {
|
||||
func (s *AccountService) GetMaxDisplayOrder(c *core.Context, uid int64, category models.AccountCategory) (int32, error) {
|
||||
if uid <= 0 {
|
||||
return 0, errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
account := &models.Account{}
|
||||
has, err := s.UserDataDB(uid).Cols("uid", "deleted", "parent_account_id", "display_order").Where("uid=? AND deleted=? AND parent_account_id=? AND category=?", uid, false, models.LevelOneAccountParentId, category).OrderBy("display_order desc").Limit(1).Get(account)
|
||||
has, err := s.UserDataDB(uid).NewSession(c).Cols("uid", "deleted", "parent_account_id", "display_order").Where("uid=? AND deleted=? AND parent_account_id=? AND category=?", uid, false, models.LevelOneAccountParentId, category).OrderBy("display_order desc").Limit(1).Get(account)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -127,7 +128,7 @@ func (s *AccountService) GetMaxDisplayOrder(uid int64, category models.AccountCa
|
||||
}
|
||||
|
||||
// GetMaxSubAccountDisplayOrder returns the max display order of sub account according to account category and parent account id
|
||||
func (s *AccountService) GetMaxSubAccountDisplayOrder(uid int64, category models.AccountCategory, parentAccountId int64) (int32, error) {
|
||||
func (s *AccountService) GetMaxSubAccountDisplayOrder(c *core.Context, uid int64, category models.AccountCategory, parentAccountId int64) (int32, error) {
|
||||
if uid <= 0 {
|
||||
return 0, errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -137,7 +138,7 @@ func (s *AccountService) GetMaxSubAccountDisplayOrder(uid int64, category models
|
||||
}
|
||||
|
||||
account := &models.Account{}
|
||||
has, err := s.UserDataDB(uid).Cols("uid", "deleted", "parent_account_id", "display_order").Where("uid=? AND deleted=? AND parent_account_id=? AND category=?", uid, false, parentAccountId, category).OrderBy("display_order desc").Limit(1).Get(account)
|
||||
has, err := s.UserDataDB(uid).NewSession(c).Cols("uid", "deleted", "parent_account_id", "display_order").Where("uid=? AND deleted=? AND parent_account_id=? AND category=?", uid, false, parentAccountId, category).OrderBy("display_order desc").Limit(1).Get(account)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -151,7 +152,7 @@ func (s *AccountService) GetMaxSubAccountDisplayOrder(uid int64, category models
|
||||
}
|
||||
|
||||
// CreateAccounts saves a new account model to database
|
||||
func (s *AccountService) CreateAccounts(mainAccount *models.Account, childrenAccounts []*models.Account) error {
|
||||
func (s *AccountService) CreateAccounts(c *core.Context, mainAccount *models.Account, childrenAccounts []*models.Account, utcOffset int16) error {
|
||||
if mainAccount.Uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -162,12 +163,22 @@ func (s *AccountService) CreateAccounts(mainAccount *models.Account, childrenAcc
|
||||
var allInitTransactions []*models.Transaction
|
||||
|
||||
mainAccount.AccountId = s.GenerateUuid(uuid.UUID_TYPE_ACCOUNT)
|
||||
|
||||
if mainAccount.AccountId < 1 {
|
||||
return errs.ErrSystemIsBusy
|
||||
}
|
||||
|
||||
allAccounts[0] = mainAccount
|
||||
|
||||
if mainAccount.Type == models.ACCOUNT_TYPE_MULTI_SUB_ACCOUNTS {
|
||||
for i := 0; i < len(childrenAccounts); i++ {
|
||||
childAccount := childrenAccounts[i]
|
||||
childAccount.AccountId = s.GenerateUuid(uuid.UUID_TYPE_ACCOUNT)
|
||||
|
||||
if childAccount.AccountId < 1 {
|
||||
return errs.ErrSystemIsBusy
|
||||
}
|
||||
|
||||
childAccount.ParentAccountId = mainAccount.AccountId
|
||||
childAccount.Uid = mainAccount.Uid
|
||||
childAccount.Type = models.ACCOUNT_TYPE_SINGLE_ACCOUNT
|
||||
@@ -184,12 +195,19 @@ func (s *AccountService) CreateAccounts(mainAccount *models.Account, childrenAcc
|
||||
allAccounts[i].UpdatedUnixTime = now
|
||||
|
||||
if allAccounts[i].Balance != 0 {
|
||||
transactionId := s.GenerateUuid(uuid.UUID_TYPE_TRANSACTION)
|
||||
|
||||
if transactionId < 1 {
|
||||
return errs.ErrSystemIsBusy
|
||||
}
|
||||
|
||||
newTransaction := &models.Transaction{
|
||||
TransactionId: s.GenerateUuid(uuid.UUID_TYPE_TRANSACTION),
|
||||
TransactionId: transactionId,
|
||||
Uid: allAccounts[i].Uid,
|
||||
Deleted: false,
|
||||
Type: models.TRANSACTION_DB_TYPE_MODIFY_BALANCE,
|
||||
TransactionTime: transactionTime,
|
||||
TimezoneUtcOffset: utcOffset,
|
||||
AccountId: allAccounts[i].AccountId,
|
||||
Amount: allAccounts[i].Balance,
|
||||
RelatedAccountId: allAccounts[i].AccountId,
|
||||
@@ -203,7 +221,7 @@ func (s *AccountService) CreateAccounts(mainAccount *models.Account, childrenAcc
|
||||
}
|
||||
}
|
||||
|
||||
return s.UserDataDB(mainAccount.Uid).DoTransaction(func(sess *xorm.Session) error {
|
||||
return s.UserDataDB(mainAccount.Uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||
for i := 0; i < len(allAccounts); i++ {
|
||||
account := allAccounts[i]
|
||||
_, err := sess.Insert(account)
|
||||
@@ -227,7 +245,7 @@ func (s *AccountService) CreateAccounts(mainAccount *models.Account, childrenAcc
|
||||
}
|
||||
|
||||
// ModifyAccounts saves an existed account model to database
|
||||
func (s *AccountService) ModifyAccounts(uid int64, accounts []*models.Account) error {
|
||||
func (s *AccountService) ModifyAccounts(c *core.Context, uid int64, accounts []*models.Account) error {
|
||||
if uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -238,7 +256,7 @@ func (s *AccountService) ModifyAccounts(uid int64, accounts []*models.Account) e
|
||||
accounts[i].UpdatedUnixTime = now
|
||||
}
|
||||
|
||||
return s.UserDataDB(uid).DoTransaction(func(sess *xorm.Session) error {
|
||||
return s.UserDataDB(uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||
for i := 0; i < len(accounts); i++ {
|
||||
account := accounts[i]
|
||||
updatedRows, err := sess.ID(account.AccountId).Cols("name", "category", "icon", "color", "comment", "hidden", "updated_unix_time").Where("uid=? AND deleted=?", uid, false).Update(account)
|
||||
@@ -255,7 +273,7 @@ func (s *AccountService) ModifyAccounts(uid int64, accounts []*models.Account) e
|
||||
}
|
||||
|
||||
// HideAccount updates hidden field of given accounts
|
||||
func (s *AccountService) HideAccount(uid int64, ids []int64, hidden bool) error {
|
||||
func (s *AccountService) HideAccount(c *core.Context, uid int64, ids []int64, hidden bool) error {
|
||||
if uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -267,7 +285,7 @@ func (s *AccountService) HideAccount(uid int64, ids []int64, hidden bool) error
|
||||
UpdatedUnixTime: now,
|
||||
}
|
||||
|
||||
return s.UserDataDB(uid).DoTransaction(func(sess *xorm.Session) error {
|
||||
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("account_id", ids).Update(updateModel)
|
||||
|
||||
if err != nil {
|
||||
@@ -281,7 +299,7 @@ func (s *AccountService) HideAccount(uid int64, ids []int64, hidden bool) error
|
||||
}
|
||||
|
||||
// ModifyAccountDisplayOrders updates display order of given accounts
|
||||
func (s *AccountService) ModifyAccountDisplayOrders(uid int64, accounts []*models.Account) error {
|
||||
func (s *AccountService) ModifyAccountDisplayOrders(c *core.Context, uid int64, accounts []*models.Account) error {
|
||||
if uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -290,7 +308,7 @@ func (s *AccountService) ModifyAccountDisplayOrders(uid int64, accounts []*model
|
||||
accounts[i].UpdatedUnixTime = time.Now().Unix()
|
||||
}
|
||||
|
||||
return s.UserDataDB(uid).DoTransaction(func(sess *xorm.Session) error {
|
||||
return s.UserDataDB(uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||
for i := 0; i < len(accounts); i++ {
|
||||
account := accounts[i]
|
||||
updatedRows, err := sess.ID(account.AccountId).Cols("display_order", "updated_unix_time").Where("uid=? AND deleted=?", uid, false).Update(account)
|
||||
@@ -307,7 +325,7 @@ func (s *AccountService) ModifyAccountDisplayOrders(uid int64, accounts []*model
|
||||
}
|
||||
|
||||
// DeleteAccount deletes an existed account from database
|
||||
func (s *AccountService) DeleteAccount(uid int64, accountId int64) error {
|
||||
func (s *AccountService) DeleteAccount(c *core.Context, uid int64, accountId int64) error {
|
||||
if uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -320,7 +338,7 @@ func (s *AccountService) DeleteAccount(uid int64, accountId int64) error {
|
||||
DeletedUnixTime: now,
|
||||
}
|
||||
|
||||
return s.UserDataDB(uid).DoTransaction(func(sess *xorm.Session) error {
|
||||
return s.UserDataDB(uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||
var accountAndSubAccounts []*models.Account
|
||||
err := sess.Where("uid=? AND deleted=? AND (account_id=? OR parent_account_id=?)", uid, false, accountId, accountId).Find(&accountAndSubAccounts)
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ package services
|
||||
|
||||
import (
|
||||
"github.com/mayswind/ezbookkeeping/pkg/datastore"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/mail"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/uuid"
|
||||
)
|
||||
@@ -36,6 +38,20 @@ func (s *ServiceUsingConfig) CurrentConfig() *settings.Config {
|
||||
return s.container.Current
|
||||
}
|
||||
|
||||
// ServiceUsingMailer represents a service that need to use mailer
|
||||
type ServiceUsingMailer struct {
|
||||
container *mail.MailerContainer
|
||||
}
|
||||
|
||||
// SendMail sends an email according to argument
|
||||
func (s *ServiceUsingMailer) SendMail(message *mail.MailMessage) error {
|
||||
if s.container.Current == nil {
|
||||
return errs.ErrSMTPServerNotEnabled
|
||||
}
|
||||
|
||||
return s.container.Current.SendMail(message)
|
||||
}
|
||||
|
||||
// ServiceUsingUuid represents a service that need to use uuid
|
||||
type ServiceUsingUuid struct {
|
||||
container *uuid.UuidContainer
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/locales"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/mail"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/templates"
|
||||
)
|
||||
|
||||
const passwordResetUrlFormat = "%sdesktop/#/resetpassword?token=%s"
|
||||
|
||||
// ForgetPasswordService represents forget password service
|
||||
type ForgetPasswordService struct {
|
||||
ServiceUsingConfig
|
||||
ServiceUsingMailer
|
||||
}
|
||||
|
||||
// Initialize a forget password service singleton instance
|
||||
var (
|
||||
ForgetPasswords = &ForgetPasswordService{
|
||||
ServiceUsingConfig: ServiceUsingConfig{
|
||||
container: settings.Container,
|
||||
},
|
||||
ServiceUsingMailer: ServiceUsingMailer{
|
||||
container: mail.Container,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// SendPasswordResetEmail sends password reset email according to specified parameters
|
||||
func (s *ForgetPasswordService) SendPasswordResetEmail(c *core.Context, user *models.User, passwordResetToken string, backupLocale string) error {
|
||||
if !s.CurrentConfig().EnableSMTP {
|
||||
return errs.ErrSMTPServerNotEnabled
|
||||
}
|
||||
|
||||
locale := user.Language
|
||||
|
||||
if locale == "" {
|
||||
locale = backupLocale
|
||||
}
|
||||
|
||||
localeTextItems := locales.GetLocaleTextItems(locale)
|
||||
forgetPasswordTextItems := localeTextItems.ForgetPasswordMailTextItems
|
||||
|
||||
expireTimeInMinutes := s.CurrentConfig().PasswordResetTokenExpiredTimeDuration.Minutes()
|
||||
passwordResetUrl := fmt.Sprintf(passwordResetUrlFormat, s.CurrentConfig().RootUrl, url.QueryEscape(passwordResetToken))
|
||||
|
||||
tmpl, err := templates.GetTemplate(templates.TEMPLATE_PASSWORD_RESET)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
templateParams := map[string]any{
|
||||
"AppName": s.CurrentConfig().AppName,
|
||||
"ForgetPasswordMail": map[string]any{
|
||||
"Title": forgetPasswordTextItems.Title,
|
||||
"Salutation": fmt.Sprintf(forgetPasswordTextItems.SalutationFormat, user.Nickname),
|
||||
"DescriptionAboveBtn": forgetPasswordTextItems.DescriptionAboveBtn,
|
||||
"ResetPasswordUrl": passwordResetUrl,
|
||||
"ResetPassword": forgetPasswordTextItems.ResetPassword,
|
||||
"DescriptionBelowBtn": fmt.Sprintf(forgetPasswordTextItems.DescriptionBelowBtnFormat, expireTimeInMinutes),
|
||||
},
|
||||
}
|
||||
|
||||
var bodyBuffer bytes.Buffer
|
||||
err = tmpl.Execute(&bodyBuffer, templateParams)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
message := &mail.MailMessage{
|
||||
To: user.Email,
|
||||
Subject: forgetPasswordTextItems.Title,
|
||||
Body: bodyBuffer.String(),
|
||||
}
|
||||
|
||||
err = s.SendMail(message)
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -38,19 +38,19 @@ var (
|
||||
)
|
||||
|
||||
// GetAllTokensByUid returns all token models of given user
|
||||
func (s *TokenService) GetAllTokensByUid(uid int64) ([]*models.TokenRecord, error) {
|
||||
func (s *TokenService) GetAllTokensByUid(c *core.Context, uid int64) ([]*models.TokenRecord, error) {
|
||||
if uid <= 0 {
|
||||
return nil, errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
var tokenRecords []*models.TokenRecord
|
||||
err := s.TokenDB(uid).Cols("uid", "user_token_id", "token_type", "user_agent", "created_unix_time", "expired_unix_time").Where("uid=?", uid).Find(&tokenRecords)
|
||||
err := s.TokenDB(uid).NewSession(c).Cols("uid", "user_token_id", "token_type", "user_agent", "created_unix_time", "expired_unix_time").Where("uid=?", uid).Find(&tokenRecords)
|
||||
|
||||
return tokenRecords, err
|
||||
}
|
||||
|
||||
// GetAllUnexpiredNormalTokensByUid returns all available token models of given user
|
||||
func (s *TokenService) GetAllUnexpiredNormalTokensByUid(uid int64) ([]*models.TokenRecord, error) {
|
||||
func (s *TokenService) GetAllUnexpiredNormalTokensByUid(c *core.Context, uid int64) ([]*models.TokenRecord, error) {
|
||||
if uid <= 0 {
|
||||
return nil, errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -58,7 +58,7 @@ func (s *TokenService) GetAllUnexpiredNormalTokensByUid(uid int64) ([]*models.To
|
||||
now := time.Now().Unix()
|
||||
|
||||
var tokenRecords []*models.TokenRecord
|
||||
err := s.TokenDB(uid).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").Where("uid=? AND token_type=? AND expired_unix_time>?", uid, core.USER_TOKEN_TYPE_NORMAL, now).Find(&tokenRecords)
|
||||
|
||||
return tokenRecords, err
|
||||
}
|
||||
@@ -73,18 +73,33 @@ func (s *TokenService) ParseTokenByArgument(c *core.Context, tokenParameterName
|
||||
return s.parseToken(c, request.ArgumentExtractor{tokenParameterName})
|
||||
}
|
||||
|
||||
// ParseTokenByCookie returns the token model according to request data
|
||||
func (s *TokenService) ParseTokenByCookie(c *core.Context, tokenCookieName string) (*jwt.Token, *core.UserTokenClaims, error) {
|
||||
return s.parseToken(c, utils.CookieExtractor{tokenCookieName})
|
||||
}
|
||||
|
||||
// CreateToken generates a new normal token and saves to database
|
||||
func (s *TokenService) CreateToken(user *models.User, ctx *core.Context) (string, *core.UserTokenClaims, error) {
|
||||
return s.createToken(user, core.USER_TOKEN_TYPE_NORMAL, s.getUserAgent(ctx), s.CurrentConfig().TokenExpiredTimeDuration)
|
||||
func (s *TokenService) CreateToken(c *core.Context, user *models.User) (string, *core.UserTokenClaims, error) {
|
||||
return s.createToken(c, user, core.USER_TOKEN_TYPE_NORMAL, s.getUserAgent(c), s.CurrentConfig().TokenExpiredTimeDuration)
|
||||
}
|
||||
|
||||
// CreateRequire2FAToken generates a new token requiring user to verify 2fa passcode and saves to database
|
||||
func (s *TokenService) CreateRequire2FAToken(user *models.User, ctx *core.Context) (string, *core.UserTokenClaims, error) {
|
||||
return s.createToken(user, core.USER_TOKEN_TYPE_REQUIRE_2FA, s.getUserAgent(ctx), s.CurrentConfig().TemporaryTokenExpiredTimeDuration)
|
||||
func (s *TokenService) CreateRequire2FAToken(c *core.Context, user *models.User) (string, *core.UserTokenClaims, error) {
|
||||
return s.createToken(c, user, core.USER_TOKEN_TYPE_REQUIRE_2FA, s.getUserAgent(c), s.CurrentConfig().TemporaryTokenExpiredTimeDuration)
|
||||
}
|
||||
|
||||
// CreateEmailVerifyToken generates a new email verify token and saves to database
|
||||
func (s *TokenService) CreateEmailVerifyToken(c *core.Context, user *models.User) (string, *core.UserTokenClaims, error) {
|
||||
return s.createToken(c, user, core.USER_TOKEN_TYPE_EMAIL_VERIFY, s.getUserAgent(c), s.CurrentConfig().EmailVerifyTokenExpiredTimeDuration)
|
||||
}
|
||||
|
||||
// CreatePasswordResetToken generates a new password reset token and saves to database
|
||||
func (s *TokenService) CreatePasswordResetToken(c *core.Context, user *models.User) (string, *core.UserTokenClaims, error) {
|
||||
return s.createToken(c, user, core.USER_TOKEN_TYPE_PASSWORD_RESET, s.getUserAgent(c), s.CurrentConfig().PasswordResetTokenExpiredTimeDuration)
|
||||
}
|
||||
|
||||
// DeleteToken deletes given token from database
|
||||
func (s *TokenService) DeleteToken(tokenRecord *models.TokenRecord) error {
|
||||
func (s *TokenService) DeleteToken(c *core.Context, tokenRecord *models.TokenRecord) error {
|
||||
if tokenRecord.Uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -93,7 +108,7 @@ func (s *TokenService) DeleteToken(tokenRecord *models.TokenRecord) error {
|
||||
return errs.ErrInvalidUserTokenId
|
||||
}
|
||||
|
||||
return s.TokenDB(tokenRecord.Uid).DoTransaction(func(sess *xorm.Session) error {
|
||||
return s.TokenDB(tokenRecord.Uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||
deletedRows, err := sess.Where("uid=? AND user_token_id=? AND created_unix_time=?", tokenRecord.Uid, tokenRecord.UserTokenId, tokenRecord.CreatedUnixTime).Delete(&models.TokenRecord{})
|
||||
|
||||
if err != nil {
|
||||
@@ -107,12 +122,12 @@ func (s *TokenService) DeleteToken(tokenRecord *models.TokenRecord) error {
|
||||
}
|
||||
|
||||
// DeleteTokens deletes given tokens from database
|
||||
func (s *TokenService) DeleteTokens(uid int64, tokenRecords []*models.TokenRecord) error {
|
||||
func (s *TokenService) DeleteTokens(c *core.Context, uid int64, tokenRecords []*models.TokenRecord) error {
|
||||
if uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
return s.TokenDB(uid).DoTransaction(func(sess *xorm.Session) error {
|
||||
return s.TokenDB(uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||
for i := 0; i < len(tokenRecords); i++ {
|
||||
tokenRecord := tokenRecords[i]
|
||||
deletedRows, err := sess.Where("uid=? AND user_token_id=? AND created_unix_time=?", uid, tokenRecord.UserTokenId, tokenRecord.CreatedUnixTime).Delete(&models.TokenRecord{})
|
||||
@@ -129,32 +144,55 @@ func (s *TokenService) DeleteTokens(uid int64, tokenRecords []*models.TokenRecor
|
||||
}
|
||||
|
||||
// DeleteTokenByClaims deletes given token from database
|
||||
func (s *TokenService) DeleteTokenByClaims(claims *core.UserTokenClaims) error {
|
||||
func (s *TokenService) DeleteTokenByClaims(c *core.Context, claims *core.UserTokenClaims) error {
|
||||
userTokenId, err := utils.StringToInt64(claims.UserTokenId)
|
||||
|
||||
if err != nil {
|
||||
return errs.ErrInvalidUserTokenId
|
||||
}
|
||||
|
||||
return s.DeleteToken(&models.TokenRecord{
|
||||
return s.DeleteToken(c, &models.TokenRecord{
|
||||
Uid: claims.Uid,
|
||||
UserTokenId: userTokenId,
|
||||
CreatedUnixTime: claims.IssuedAt,
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteTokensBeforeTime deletes tokens that is created before specific tim
|
||||
func (s *TokenService) DeleteTokensBeforeTime(uid int64, expireTime int64) error {
|
||||
// DeleteTokensBeforeTime deletes tokens that is created before specific time
|
||||
func (s *TokenService) DeleteTokensBeforeTime(c *core.Context, uid int64, expireTime int64) error {
|
||||
if uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
return s.TokenDB(uid).DoTransaction(func(sess *xorm.Session) error {
|
||||
return s.TokenDB(uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||
_, err := sess.Where("uid=? AND created_unix_time<?", uid, expireTime).Delete(&models.TokenRecord{})
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteTokensByType deletes specified type tokens
|
||||
func (s *TokenService) DeleteTokensByType(c *core.Context, uid int64, tokenType core.TokenType) error {
|
||||
if uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
return s.TokenDB(uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||
_, err := sess.Where("uid=? AND token_type=?", uid, tokenType).Delete(&models.TokenRecord{})
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// ExistsValidTokenByType returns whether the given token type exists
|
||||
func (s *TokenService) ExistsValidTokenByType(c *core.Context, uid int64, tokenType core.TokenType) (bool, error) {
|
||||
if uid <= 0 {
|
||||
return false, errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
|
||||
return s.TokenDB(uid).NewSession(c).Cols("uid", "user_token_id", "expired_unix_time").Where("uid=? AND token_type=? AND expired_unix_time>?", uid, tokenType, now).Exist(&models.TokenRecord{})
|
||||
}
|
||||
|
||||
// ParseFromTokenId returns token model according to token id
|
||||
func (s *TokenService) ParseFromTokenId(tokenId string) (*models.TokenRecord, error) {
|
||||
pairs := strings.Split(tokenId, ":")
|
||||
@@ -199,7 +237,7 @@ func (s *TokenService) parseToken(c *core.Context, extractor request.Extractor)
|
||||
claims := &core.UserTokenClaims{}
|
||||
|
||||
token, err := request.ParseFromRequest(c.Request, extractor,
|
||||
func(token *jwt.Token) (interface{}, error) {
|
||||
func(token *jwt.Token) (any, error) {
|
||||
now := time.Now().Unix()
|
||||
userTokenId, err := utils.StringToInt64(claims.UserTokenId)
|
||||
|
||||
@@ -208,7 +246,7 @@ func (s *TokenService) parseToken(c *core.Context, extractor request.Extractor)
|
||||
return nil, errs.ErrInvalidUserTokenId
|
||||
}
|
||||
|
||||
tokenRecord, err := s.getTokenRecord(claims.Uid, userTokenId, claims.IssuedAt)
|
||||
tokenRecord, err := s.getTokenRecord(c, claims.Uid, userTokenId, claims.IssuedAt)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[tokens.ParseToken] token \"utid:%s\" of user \"uid:%d\" record not found, because %s", claims.UserTokenId, claims.Uid, err.Error())
|
||||
@@ -251,7 +289,7 @@ func (s *TokenService) parseToken(c *core.Context, extractor request.Extractor)
|
||||
return token, claims, err
|
||||
}
|
||||
|
||||
func (s *TokenService) createToken(user *models.User, tokenType core.TokenType, userAgent string, expiryDate time.Duration) (string, *core.UserTokenClaims, error) {
|
||||
func (s *TokenService) createToken(c *core.Context, user *models.User, tokenType core.TokenType, userAgent string, expiryDate time.Duration) (string, *core.UserTokenClaims, error) {
|
||||
var err error
|
||||
now := time.Now()
|
||||
|
||||
@@ -284,7 +322,7 @@ func (s *TokenService) createToken(user *models.User, tokenType core.TokenType,
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
err = s.createTokenRecord(tokenRecord)
|
||||
err = s.createTokenRecord(c, tokenRecord)
|
||||
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
@@ -293,7 +331,7 @@ func (s *TokenService) createToken(user *models.User, tokenType core.TokenType,
|
||||
return tokenString, claims, err
|
||||
}
|
||||
|
||||
func (s *TokenService) getTokenRecord(uid int64, userTokenId int64, createUnixTime int64) (*models.TokenRecord, error) {
|
||||
func (s *TokenService) getTokenRecord(c *core.Context, uid int64, userTokenId int64, createUnixTime int64) (*models.TokenRecord, error) {
|
||||
if uid <= 0 {
|
||||
return nil, errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -303,7 +341,7 @@ func (s *TokenService) getTokenRecord(uid int64, userTokenId int64, createUnixTi
|
||||
}
|
||||
|
||||
tokenRecord := &models.TokenRecord{}
|
||||
has, err := s.TokenDB(uid).Where("uid=? AND user_token_id=? AND created_unix_time=?", uid, userTokenId, createUnixTime).Limit(1).Get(tokenRecord)
|
||||
has, err := s.TokenDB(uid).NewSession(c).Where("uid=? AND user_token_id=? AND created_unix_time=?", uid, userTokenId, createUnixTime).Limit(1).Get(tokenRecord)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -316,7 +354,7 @@ func (s *TokenService) getTokenRecord(uid int64, userTokenId int64, createUnixTi
|
||||
return tokenRecord, nil
|
||||
}
|
||||
|
||||
func (s *TokenService) createTokenRecord(tokenRecord *models.TokenRecord) error {
|
||||
func (s *TokenService) createTokenRecord(c *core.Context, tokenRecord *models.TokenRecord) error {
|
||||
if tokenRecord.Uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -325,7 +363,7 @@ func (s *TokenService) createTokenRecord(tokenRecord *models.TokenRecord) error
|
||||
return errs.ErrInvalidUserTokenId
|
||||
}
|
||||
|
||||
return s.TokenDB(tokenRecord.Uid).DoTransaction(func(sess *xorm.Session) error {
|
||||
return s.TokenDB(tokenRecord.Uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||
_, err := sess.Insert(tokenRecord)
|
||||
return err
|
||||
})
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"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"
|
||||
@@ -30,24 +31,24 @@ var (
|
||||
)
|
||||
|
||||
// GetTotalCategoryCountByUid returns total category count of user
|
||||
func (s *TransactionCategoryService) GetTotalCategoryCountByUid(uid int64) (int64, error) {
|
||||
func (s *TransactionCategoryService) GetTotalCategoryCountByUid(c *core.Context, uid int64) (int64, error) {
|
||||
if uid <= 0 {
|
||||
return 0, errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
count, err := s.UserDataDB(uid).Where("uid=? AND deleted=?", uid, false).Count(&models.TransactionCategory{})
|
||||
count, err := s.UserDataDB(uid).NewSession(c).Where("uid=? AND deleted=?", uid, false).Count(&models.TransactionCategory{})
|
||||
|
||||
return count, err
|
||||
}
|
||||
|
||||
// GetAllCategoriesByUid returns all transaction category models of user
|
||||
func (s *TransactionCategoryService) GetAllCategoriesByUid(uid int64, categoryType models.TransactionCategoryType, parentCategoryId int64) ([]*models.TransactionCategory, error) {
|
||||
func (s *TransactionCategoryService) GetAllCategoriesByUid(c *core.Context, uid int64, categoryType models.TransactionCategoryType, parentCategoryId int64) ([]*models.TransactionCategory, error) {
|
||||
if uid <= 0 {
|
||||
return nil, errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
condition := "uid=? AND deleted=?"
|
||||
conditionParams := make([]interface{}, 0, 8)
|
||||
conditionParams := make([]any, 0, 8)
|
||||
conditionParams = append(conditionParams, uid)
|
||||
conditionParams = append(conditionParams, false)
|
||||
|
||||
@@ -62,13 +63,13 @@ func (s *TransactionCategoryService) GetAllCategoriesByUid(uid int64, categoryTy
|
||||
}
|
||||
|
||||
var categories []*models.TransactionCategory
|
||||
err := s.UserDataDB(uid).Where(condition, conditionParams...).OrderBy("type asc, parent_category_id asc, display_order asc").Find(&categories)
|
||||
err := s.UserDataDB(uid).NewSession(c).Where(condition, conditionParams...).OrderBy("type asc, parent_category_id asc, display_order asc").Find(&categories)
|
||||
|
||||
return categories, err
|
||||
}
|
||||
|
||||
// GetCategoryByCategoryId returns a transaction category model according to transaction category id
|
||||
func (s *TransactionCategoryService) GetCategoryByCategoryId(uid int64, categoryId int64) (*models.TransactionCategory, error) {
|
||||
func (s *TransactionCategoryService) GetCategoryByCategoryId(c *core.Context, uid int64, categoryId int64) (*models.TransactionCategory, error) {
|
||||
if uid <= 0 {
|
||||
return nil, errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -78,7 +79,7 @@ func (s *TransactionCategoryService) GetCategoryByCategoryId(uid int64, category
|
||||
}
|
||||
|
||||
category := &models.TransactionCategory{}
|
||||
has, err := s.UserDataDB(uid).ID(categoryId).Where("uid=? AND deleted=?", uid, false).Get(category)
|
||||
has, err := s.UserDataDB(uid).NewSession(c).ID(categoryId).Where("uid=? AND deleted=?", uid, false).Get(category)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -90,7 +91,7 @@ func (s *TransactionCategoryService) GetCategoryByCategoryId(uid int64, category
|
||||
}
|
||||
|
||||
// GetCategoriesByCategoryIds returns transaction category models according to transaction category ids
|
||||
func (s *TransactionCategoryService) GetCategoriesByCategoryIds(uid int64, categoryIds []int64) (map[int64]*models.TransactionCategory, error) {
|
||||
func (s *TransactionCategoryService) GetCategoriesByCategoryIds(c *core.Context, uid int64, categoryIds []int64) (map[int64]*models.TransactionCategory, error) {
|
||||
if uid <= 0 {
|
||||
return nil, errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -100,7 +101,7 @@ func (s *TransactionCategoryService) GetCategoriesByCategoryIds(uid int64, categ
|
||||
}
|
||||
|
||||
var categories []*models.TransactionCategory
|
||||
err := s.UserDataDB(uid).Where("uid=? AND deleted=?", uid, false).In("category_id", categoryIds).Find(&categories)
|
||||
err := s.UserDataDB(uid).NewSession(c).Where("uid=? AND deleted=?", uid, false).In("category_id", categoryIds).Find(&categories)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -111,13 +112,13 @@ func (s *TransactionCategoryService) GetCategoriesByCategoryIds(uid int64, categ
|
||||
}
|
||||
|
||||
// GetMaxDisplayOrder returns the max display order according to transaction category type
|
||||
func (s *TransactionCategoryService) GetMaxDisplayOrder(uid int64, categoryType models.TransactionCategoryType) (int32, error) {
|
||||
func (s *TransactionCategoryService) GetMaxDisplayOrder(c *core.Context, uid int64, categoryType models.TransactionCategoryType) (int32, error) {
|
||||
if uid <= 0 {
|
||||
return 0, errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
category := &models.TransactionCategory{}
|
||||
has, err := s.UserDataDB(uid).Cols("uid", "deleted", "parent_category_id", "display_order").Where("uid=? AND deleted=? AND type=? AND parent_category_id=?", uid, false, categoryType, models.LevelOneTransactionParentId).OrderBy("display_order desc").Limit(1).Get(category)
|
||||
has, err := s.UserDataDB(uid).NewSession(c).Cols("uid", "deleted", "parent_category_id", "display_order").Where("uid=? AND deleted=? AND type=? AND parent_category_id=?", uid, false, categoryType, models.LevelOneTransactionParentId).OrderBy("display_order desc").Limit(1).Get(category)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -131,7 +132,7 @@ func (s *TransactionCategoryService) GetMaxDisplayOrder(uid int64, categoryType
|
||||
}
|
||||
|
||||
// GetMaxSubCategoryDisplayOrder returns the max display order of sub transaction category according to transaction category type and parent transaction category id
|
||||
func (s *TransactionCategoryService) GetMaxSubCategoryDisplayOrder(uid int64, categoryType models.TransactionCategoryType, parentCategoryId int64) (int32, error) {
|
||||
func (s *TransactionCategoryService) GetMaxSubCategoryDisplayOrder(c *core.Context, uid int64, categoryType models.TransactionCategoryType, parentCategoryId int64) (int32, error) {
|
||||
if uid <= 0 {
|
||||
return 0, errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -141,7 +142,7 @@ func (s *TransactionCategoryService) GetMaxSubCategoryDisplayOrder(uid int64, ca
|
||||
}
|
||||
|
||||
category := &models.TransactionCategory{}
|
||||
has, err := s.UserDataDB(uid).Cols("uid", "deleted", "parent_category_id", "display_order").Where("uid=? AND deleted=? AND type=? AND parent_category_id=?", uid, false, categoryType, parentCategoryId).OrderBy("display_order desc").Limit(1).Get(category)
|
||||
has, err := s.UserDataDB(uid).NewSession(c).Cols("uid", "deleted", "parent_category_id", "display_order").Where("uid=? AND deleted=? AND type=? AND parent_category_id=?", uid, false, categoryType, parentCategoryId).OrderBy("display_order desc").Limit(1).Get(category)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -155,25 +156,29 @@ func (s *TransactionCategoryService) GetMaxSubCategoryDisplayOrder(uid int64, ca
|
||||
}
|
||||
|
||||
// CreateCategory saves a new transaction category model to database
|
||||
func (s *TransactionCategoryService) CreateCategory(category *models.TransactionCategory) error {
|
||||
func (s *TransactionCategoryService) CreateCategory(c *core.Context, category *models.TransactionCategory) error {
|
||||
if category.Uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
category.CategoryId = s.GenerateUuid(uuid.UUID_TYPE_CATEGORY)
|
||||
|
||||
if category.CategoryId < 1 {
|
||||
return errs.ErrSystemIsBusy
|
||||
}
|
||||
|
||||
category.Deleted = false
|
||||
category.CreatedUnixTime = time.Now().Unix()
|
||||
category.UpdatedUnixTime = time.Now().Unix()
|
||||
|
||||
return s.UserDataDB(category.Uid).DoTransaction(func(sess *xorm.Session) error {
|
||||
return s.UserDataDB(category.Uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||
_, err := sess.Insert(category)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// CreateCategories saves a few transaction category models to database
|
||||
func (s *TransactionCategoryService) CreateCategories(uid int64, categories map[*models.TransactionCategory][]*models.TransactionCategory) ([]*models.TransactionCategory, error) {
|
||||
func (s *TransactionCategoryService) CreateCategories(c *core.Context, uid int64, categories map[*models.TransactionCategory][]*models.TransactionCategory) ([]*models.TransactionCategory, error) {
|
||||
if uid <= 0 {
|
||||
return nil, errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -185,6 +190,10 @@ func (s *TransactionCategoryService) CreateCategories(uid int64, categories map[
|
||||
primaryCategory := primaryCategories[i]
|
||||
primaryCategory.CategoryId = s.GenerateUuid(uuid.UUID_TYPE_CATEGORY)
|
||||
|
||||
if primaryCategory.CategoryId < 1 {
|
||||
return nil, errs.ErrSystemIsBusy
|
||||
}
|
||||
|
||||
primaryCategory.Deleted = false
|
||||
primaryCategory.CreatedUnixTime = time.Now().Unix()
|
||||
primaryCategory.UpdatedUnixTime = time.Now().Unix()
|
||||
@@ -196,6 +205,11 @@ func (s *TransactionCategoryService) CreateCategories(uid int64, categories map[
|
||||
for j := 0; j < len(secondaryCategories); j++ {
|
||||
secondaryCategory := secondaryCategories[j]
|
||||
secondaryCategory.CategoryId = s.GenerateUuid(uuid.UUID_TYPE_CATEGORY)
|
||||
|
||||
if secondaryCategory.CategoryId < 1 {
|
||||
return nil, errs.ErrSystemIsBusy
|
||||
}
|
||||
|
||||
secondaryCategory.ParentCategoryId = primaryCategory.CategoryId
|
||||
|
||||
secondaryCategory.Deleted = false
|
||||
@@ -206,7 +220,7 @@ func (s *TransactionCategoryService) CreateCategories(uid int64, categories map[
|
||||
}
|
||||
}
|
||||
|
||||
err := s.UserDataDB(uid).DoTransaction(func(sess *xorm.Session) error {
|
||||
err := s.UserDataDB(uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||
for i := 0; i < len(allCategories); i++ {
|
||||
category := allCategories[i]
|
||||
_, err := sess.Insert(category)
|
||||
@@ -227,14 +241,14 @@ func (s *TransactionCategoryService) CreateCategories(uid int64, categories map[
|
||||
}
|
||||
|
||||
// ModifyCategory saves an existed transaction category model to database
|
||||
func (s *TransactionCategoryService) ModifyCategory(category *models.TransactionCategory) error {
|
||||
func (s *TransactionCategoryService) ModifyCategory(c *core.Context, category *models.TransactionCategory) error {
|
||||
if category.Uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
category.UpdatedUnixTime = time.Now().Unix()
|
||||
|
||||
return s.UserDataDB(category.Uid).DoTransaction(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)
|
||||
|
||||
if err != nil {
|
||||
@@ -248,7 +262,7 @@ func (s *TransactionCategoryService) ModifyCategory(category *models.Transaction
|
||||
}
|
||||
|
||||
// HideCategory updates hidden field of given transaction categories
|
||||
func (s *TransactionCategoryService) HideCategory(uid int64, ids []int64, hidden bool) error {
|
||||
func (s *TransactionCategoryService) HideCategory(c *core.Context, uid int64, ids []int64, hidden bool) error {
|
||||
if uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -260,7 +274,7 @@ func (s *TransactionCategoryService) HideCategory(uid int64, ids []int64, hidden
|
||||
UpdatedUnixTime: now,
|
||||
}
|
||||
|
||||
return s.UserDataDB(uid).DoTransaction(func(sess *xorm.Session) error {
|
||||
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("category_id", ids).Update(updateModel)
|
||||
|
||||
if err != nil {
|
||||
@@ -274,7 +288,7 @@ func (s *TransactionCategoryService) HideCategory(uid int64, ids []int64, hidden
|
||||
}
|
||||
|
||||
// ModifyCategoryDisplayOrders updates display order of given transaction categories
|
||||
func (s *TransactionCategoryService) ModifyCategoryDisplayOrders(uid int64, categories []*models.TransactionCategory) error {
|
||||
func (s *TransactionCategoryService) ModifyCategoryDisplayOrders(c *core.Context, uid int64, categories []*models.TransactionCategory) error {
|
||||
if uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -283,7 +297,7 @@ func (s *TransactionCategoryService) ModifyCategoryDisplayOrders(uid int64, cate
|
||||
categories[i].UpdatedUnixTime = time.Now().Unix()
|
||||
}
|
||||
|
||||
return s.UserDataDB(uid).DoTransaction(func(sess *xorm.Session) error {
|
||||
return s.UserDataDB(uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||
for i := 0; i < len(categories); i++ {
|
||||
category := categories[i]
|
||||
updatedRows, err := sess.ID(category.CategoryId).Cols("display_order", "updated_unix_time").Where("uid=? AND deleted=?", uid, false).Update(category)
|
||||
@@ -300,7 +314,7 @@ func (s *TransactionCategoryService) ModifyCategoryDisplayOrders(uid int64, cate
|
||||
}
|
||||
|
||||
// DeleteCategory deletes an existed transaction category from database
|
||||
func (s *TransactionCategoryService) DeleteCategory(uid int64, categoryId int64) error {
|
||||
func (s *TransactionCategoryService) DeleteCategory(c *core.Context, uid int64, categoryId int64) error {
|
||||
if uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -312,7 +326,7 @@ func (s *TransactionCategoryService) DeleteCategory(uid int64, categoryId int64)
|
||||
DeletedUnixTime: now,
|
||||
}
|
||||
|
||||
return s.UserDataDB(uid).DoTransaction(func(sess *xorm.Session) error {
|
||||
return s.UserDataDB(uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||
var categoryAndSubCategories []*models.TransactionCategory
|
||||
err := sess.Where("uid=? AND deleted=? AND (category_id=? OR parent_category_id=?)", uid, false, categoryId, categoryId).Find(&categoryAndSubCategories)
|
||||
|
||||
@@ -349,7 +363,7 @@ func (s *TransactionCategoryService) DeleteCategory(uid int64, categoryId int64)
|
||||
}
|
||||
|
||||
// DeleteAllCategories deletes all existed transaction categories from database
|
||||
func (s *TransactionCategoryService) DeleteAllCategories(uid int64) error {
|
||||
func (s *TransactionCategoryService) DeleteAllCategories(c *core.Context, uid int64) error {
|
||||
if uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -361,7 +375,7 @@ func (s *TransactionCategoryService) DeleteAllCategories(uid int64) error {
|
||||
DeletedUnixTime: now,
|
||||
}
|
||||
|
||||
return s.UserDataDB(uid).DoTransaction(func(sess *xorm.Session) error {
|
||||
return s.UserDataDB(uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||
exists, err := sess.Cols("uid", "deleted", "category_id").Where("uid=? AND deleted=? AND category_id<>?", uid, false, 0).Limit(1).Exist(&models.Transaction{})
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"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"
|
||||
@@ -30,30 +31,30 @@ var (
|
||||
)
|
||||
|
||||
// GetTotalTagCountByUid returns total tag count of user
|
||||
func (s *TransactionTagService) GetTotalTagCountByUid(uid int64) (int64, error) {
|
||||
func (s *TransactionTagService) GetTotalTagCountByUid(c *core.Context, uid int64) (int64, error) {
|
||||
if uid <= 0 {
|
||||
return 0, errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
count, err := s.UserDataDB(uid).Where("uid=? AND deleted=?", uid, false).Count(&models.TransactionTag{})
|
||||
count, err := s.UserDataDB(uid).NewSession(c).Where("uid=? AND deleted=?", uid, false).Count(&models.TransactionTag{})
|
||||
|
||||
return count, err
|
||||
}
|
||||
|
||||
// GetAllTagsByUid returns all transaction tag models of user
|
||||
func (s *TransactionTagService) GetAllTagsByUid(uid int64) ([]*models.TransactionTag, error) {
|
||||
func (s *TransactionTagService) GetAllTagsByUid(c *core.Context, uid int64) ([]*models.TransactionTag, error) {
|
||||
if uid <= 0 {
|
||||
return nil, errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
var tags []*models.TransactionTag
|
||||
err := s.UserDataDB(uid).Where("uid=? AND deleted=?", uid, false).Find(&tags)
|
||||
err := s.UserDataDB(uid).NewSession(c).Where("uid=? AND deleted=?", uid, false).Find(&tags)
|
||||
|
||||
return tags, err
|
||||
}
|
||||
|
||||
// GetTagByTagId returns a transaction tag model according to transaction tag id
|
||||
func (s *TransactionTagService) GetTagByTagId(uid int64, tagId int64) (*models.TransactionTag, error) {
|
||||
func (s *TransactionTagService) GetTagByTagId(c *core.Context, uid int64, tagId int64) (*models.TransactionTag, error) {
|
||||
if uid <= 0 {
|
||||
return nil, errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -63,7 +64,7 @@ func (s *TransactionTagService) GetTagByTagId(uid int64, tagId int64) (*models.T
|
||||
}
|
||||
|
||||
tag := &models.TransactionTag{}
|
||||
has, err := s.UserDataDB(uid).ID(tagId).Where("uid=? AND deleted=?", uid, false).Get(tag)
|
||||
has, err := s.UserDataDB(uid).NewSession(c).ID(tagId).Where("uid=? AND deleted=?", uid, false).Get(tag)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -75,7 +76,7 @@ func (s *TransactionTagService) GetTagByTagId(uid int64, tagId int64) (*models.T
|
||||
}
|
||||
|
||||
// GetTagsByTagIds returns transaction tag models according to transaction tag ids
|
||||
func (s *TransactionTagService) GetTagsByTagIds(uid int64, tagIds []int64) (map[int64]*models.TransactionTag, error) {
|
||||
func (s *TransactionTagService) GetTagsByTagIds(c *core.Context, uid int64, tagIds []int64) (map[int64]*models.TransactionTag, error) {
|
||||
if uid <= 0 {
|
||||
return nil, errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -85,7 +86,7 @@ func (s *TransactionTagService) GetTagsByTagIds(uid int64, tagIds []int64) (map[
|
||||
}
|
||||
|
||||
var tags []*models.TransactionTag
|
||||
err := s.UserDataDB(uid).Where("uid=? AND deleted=?", uid, false).In("tag_id", tagIds).Find(&tags)
|
||||
err := s.UserDataDB(uid).NewSession(c).Where("uid=? AND deleted=?", uid, false).In("tag_id", tagIds).Find(&tags)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -96,13 +97,13 @@ func (s *TransactionTagService) GetTagsByTagIds(uid int64, tagIds []int64) (map[
|
||||
}
|
||||
|
||||
// GetMaxDisplayOrder returns the max display order
|
||||
func (s *TransactionTagService) GetMaxDisplayOrder(uid int64) (int32, error) {
|
||||
func (s *TransactionTagService) GetMaxDisplayOrder(c *core.Context, uid int64) (int32, error) {
|
||||
if uid <= 0 {
|
||||
return 0, errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
tag := &models.TransactionTag{}
|
||||
has, err := s.UserDataDB(uid).Cols("uid", "deleted", "display_order").Where("uid=? AND deleted=?", uid, false).OrderBy("display_order desc").Limit(1).Get(tag)
|
||||
has, err := s.UserDataDB(uid).NewSession(c).Cols("uid", "deleted", "display_order").Where("uid=? AND deleted=?", uid, false).OrderBy("display_order desc").Limit(1).Get(tag)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -116,13 +117,13 @@ func (s *TransactionTagService) GetMaxDisplayOrder(uid int64) (int32, error) {
|
||||
}
|
||||
|
||||
// GetAllTagIdsOfAllTransactions returns all transaction tag ids
|
||||
func (s *TransactionTagService) GetAllTagIdsOfAllTransactions(uid int64) (map[int64][]int64, error) {
|
||||
func (s *TransactionTagService) GetAllTagIdsOfAllTransactions(c *core.Context, uid int64) (map[int64][]int64, error) {
|
||||
if uid <= 0 {
|
||||
return nil, errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
var tagIndexs []*models.TransactionTagIndex
|
||||
err := s.UserDataDB(uid).Where("uid=? AND deleted=?", uid, false).Find(&tagIndexs)
|
||||
err := s.UserDataDB(uid).NewSession(c).Where("uid=? AND deleted=?", uid, false).Find(&tagIndexs)
|
||||
|
||||
allTransactionTagIds := s.getGroupedTransactionTagIds(tagIndexs)
|
||||
|
||||
@@ -130,13 +131,13 @@ func (s *TransactionTagService) GetAllTagIdsOfAllTransactions(uid int64) (map[in
|
||||
}
|
||||
|
||||
// GetAllTagIdsOfTransactions returns transaction tag ids for given transactions
|
||||
func (s *TransactionTagService) GetAllTagIdsOfTransactions(uid int64, transactionIds []int64) (map[int64][]int64, error) {
|
||||
func (s *TransactionTagService) GetAllTagIdsOfTransactions(c *core.Context, uid int64, transactionIds []int64) (map[int64][]int64, error) {
|
||||
if uid <= 0 {
|
||||
return nil, errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
var tagIndexs []*models.TransactionTagIndex
|
||||
err := s.UserDataDB(uid).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).Find(&tagIndexs)
|
||||
|
||||
allTransactionTagIds := s.getGroupedTransactionTagIds(tagIndexs)
|
||||
|
||||
@@ -144,12 +145,12 @@ func (s *TransactionTagService) GetAllTagIdsOfTransactions(uid int64, transactio
|
||||
}
|
||||
|
||||
// CreateTag saves a new transaction tag model to database
|
||||
func (s *TransactionTagService) CreateTag(tag *models.TransactionTag) error {
|
||||
func (s *TransactionTagService) CreateTag(c *core.Context, tag *models.TransactionTag) error {
|
||||
if tag.Uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
exists, err := s.ExistsTagName(tag.Uid, tag.Name)
|
||||
exists, err := s.ExistsTagName(c, tag.Uid, tag.Name)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -159,23 +160,27 @@ func (s *TransactionTagService) CreateTag(tag *models.TransactionTag) error {
|
||||
|
||||
tag.TagId = s.GenerateUuid(uuid.UUID_TYPE_TAG)
|
||||
|
||||
if tag.TagId < 1 {
|
||||
return errs.ErrSystemIsBusy
|
||||
}
|
||||
|
||||
tag.Deleted = false
|
||||
tag.CreatedUnixTime = time.Now().Unix()
|
||||
tag.UpdatedUnixTime = time.Now().Unix()
|
||||
|
||||
return s.UserDataDB(tag.Uid).DoTransaction(func(sess *xorm.Session) error {
|
||||
return s.UserDataDB(tag.Uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||
_, err := sess.Insert(tag)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// ModifyTag saves an existed transaction tag model to database
|
||||
func (s *TransactionTagService) ModifyTag(tag *models.TransactionTag) error {
|
||||
func (s *TransactionTagService) ModifyTag(c *core.Context, tag *models.TransactionTag) error {
|
||||
if tag.Uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
exists, err := s.ExistsTagName(tag.Uid, tag.Name)
|
||||
exists, err := s.ExistsTagName(c, tag.Uid, tag.Name)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -185,7 +190,7 @@ func (s *TransactionTagService) ModifyTag(tag *models.TransactionTag) error {
|
||||
|
||||
tag.UpdatedUnixTime = time.Now().Unix()
|
||||
|
||||
return s.UserDataDB(tag.Uid).DoTransaction(func(sess *xorm.Session) error {
|
||||
return s.UserDataDB(tag.Uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||
updatedRows, err := sess.ID(tag.TagId).Cols("name", "updated_unix_time").Where("uid=? AND deleted=?", tag.Uid, false).Update(tag)
|
||||
|
||||
if err != nil {
|
||||
@@ -199,7 +204,7 @@ func (s *TransactionTagService) ModifyTag(tag *models.TransactionTag) error {
|
||||
}
|
||||
|
||||
// HideTag updates hidden field of given transaction tags
|
||||
func (s *TransactionTagService) HideTag(uid int64, ids []int64, hidden bool) error {
|
||||
func (s *TransactionTagService) HideTag(c *core.Context, uid int64, ids []int64, hidden bool) error {
|
||||
if uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -211,7 +216,7 @@ func (s *TransactionTagService) HideTag(uid int64, ids []int64, hidden bool) err
|
||||
UpdatedUnixTime: now,
|
||||
}
|
||||
|
||||
return s.UserDataDB(uid).DoTransaction(func(sess *xorm.Session) error {
|
||||
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("tag_id", ids).Update(updateModel)
|
||||
|
||||
if err != nil {
|
||||
@@ -225,7 +230,7 @@ func (s *TransactionTagService) HideTag(uid int64, ids []int64, hidden bool) err
|
||||
}
|
||||
|
||||
// ModifyTagDisplayOrders updates display order of given transaction tags
|
||||
func (s *TransactionTagService) ModifyTagDisplayOrders(uid int64, tags []*models.TransactionTag) error {
|
||||
func (s *TransactionTagService) ModifyTagDisplayOrders(c *core.Context, uid int64, tags []*models.TransactionTag) error {
|
||||
if uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -234,7 +239,7 @@ func (s *TransactionTagService) ModifyTagDisplayOrders(uid int64, tags []*models
|
||||
tags[i].UpdatedUnixTime = time.Now().Unix()
|
||||
}
|
||||
|
||||
return s.UserDataDB(uid).DoTransaction(func(sess *xorm.Session) error {
|
||||
return s.UserDataDB(uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||
for i := 0; i < len(tags); i++ {
|
||||
tag := tags[i]
|
||||
updatedRows, err := sess.ID(tag.TagId).Cols("display_order", "updated_unix_time").Where("uid=? AND deleted=?", uid, false).Update(tag)
|
||||
@@ -251,7 +256,7 @@ func (s *TransactionTagService) ModifyTagDisplayOrders(uid int64, tags []*models
|
||||
}
|
||||
|
||||
// DeleteTag deletes an existed transaction tag from database
|
||||
func (s *TransactionTagService) DeleteTag(uid int64, tagId int64) error {
|
||||
func (s *TransactionTagService) DeleteTag(c *core.Context, uid int64, tagId int64) error {
|
||||
if uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -263,7 +268,7 @@ func (s *TransactionTagService) DeleteTag(uid int64, tagId int64) error {
|
||||
DeletedUnixTime: now,
|
||||
}
|
||||
|
||||
return s.UserDataDB(uid).DoTransaction(func(sess *xorm.Session) error {
|
||||
return s.UserDataDB(uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||
exists, err := sess.Cols("uid", "tag_id").Where("uid=? AND deleted=? AND tag_id=?", uid, false, tagId).Limit(1).Exist(&models.TransactionTagIndex{})
|
||||
|
||||
if err != nil {
|
||||
@@ -285,7 +290,7 @@ func (s *TransactionTagService) DeleteTag(uid int64, tagId int64) error {
|
||||
}
|
||||
|
||||
// DeleteAllTags deletes all existed transaction tags from database
|
||||
func (s *TransactionTagService) DeleteAllTags(uid int64) error {
|
||||
func (s *TransactionTagService) DeleteAllTags(c *core.Context, uid int64) error {
|
||||
if uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -297,7 +302,7 @@ func (s *TransactionTagService) DeleteAllTags(uid int64) error {
|
||||
DeletedUnixTime: now,
|
||||
}
|
||||
|
||||
return s.UserDataDB(uid).DoTransaction(func(sess *xorm.Session) error {
|
||||
return s.UserDataDB(uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||
exists, err := sess.Cols("uid", "deleted").Where("uid=? AND deleted=?", uid, false).Limit(1).Exist(&models.TransactionTagIndex{})
|
||||
|
||||
if err != nil {
|
||||
@@ -317,12 +322,12 @@ func (s *TransactionTagService) DeleteAllTags(uid int64) error {
|
||||
}
|
||||
|
||||
// ExistsTagName returns whether the given tag name exists
|
||||
func (s *TransactionTagService) ExistsTagName(uid int64, name string) (bool, error) {
|
||||
func (s *TransactionTagService) ExistsTagName(c *core.Context, uid int64, name string) (bool, error) {
|
||||
if name == "" {
|
||||
return false, errs.ErrTransactionTagNameIsEmpty
|
||||
}
|
||||
|
||||
return s.UserDataDB(uid).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{})
|
||||
}
|
||||
|
||||
// GetTagMapByList returns a transaction tag map by a list
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"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"
|
||||
@@ -33,23 +34,23 @@ var (
|
||||
)
|
||||
|
||||
// GetTotalTransactionCountByUid returns total transaction count of user
|
||||
func (s *TransactionService) GetTotalTransactionCountByUid(uid int64) (int64, error) {
|
||||
func (s *TransactionService) GetTotalTransactionCountByUid(c *core.Context, uid int64) (int64, error) {
|
||||
if uid <= 0 {
|
||||
return 0, errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
count, err := s.UserDataDB(uid).Where("uid=? AND deleted=?", uid, false).Count(&models.Transaction{})
|
||||
count, err := s.UserDataDB(uid).NewSession(c).Where("uid=? AND deleted=?", uid, false).Count(&models.Transaction{})
|
||||
|
||||
return count, err
|
||||
}
|
||||
|
||||
// GetAllTransactions returns all transactions
|
||||
func (s *TransactionService) GetAllTransactions(uid int64, pageCount int32, noDuplicated bool) ([]*models.Transaction, error) {
|
||||
func (s *TransactionService) GetAllTransactions(c *core.Context, uid int64, pageCount int32, noDuplicated bool) ([]*models.Transaction, error) {
|
||||
maxTransactionTime := utils.GetMaxTransactionTimeFromUnixTime(time.Now().Unix())
|
||||
var allTransactions []*models.Transaction
|
||||
|
||||
for maxTransactionTime > 0 {
|
||||
transactions, err := s.GetAllTransactionsByMaxTime(uid, maxTransactionTime, pageCount, noDuplicated)
|
||||
transactions, err := s.GetAllTransactionsByMaxTime(c, uid, maxTransactionTime, pageCount, noDuplicated)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -69,16 +70,22 @@ func (s *TransactionService) GetAllTransactions(uid int64, pageCount int32, noDu
|
||||
}
|
||||
|
||||
// GetAllTransactionsByMaxTime returns all transactions before given time
|
||||
func (s *TransactionService) GetAllTransactionsByMaxTime(uid int64, maxTransactionTime int64, count int32, noDuplicated bool) ([]*models.Transaction, error) {
|
||||
return s.GetTransactionsByMaxTime(uid, maxTransactionTime, 0, 0, nil, nil, "", count, noDuplicated)
|
||||
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)
|
||||
}
|
||||
|
||||
// GetTransactionsByMaxTime returns transactions before given time
|
||||
func (s *TransactionService) GetTransactionsByMaxTime(uid int64, maxTransactionTime int64, minTransactionTime int64, transactionType models.TransactionDbType, categoryIds []int64, accountIds []int64, keyword string, count int32, noDuplicated bool) ([]*models.Transaction, error) {
|
||||
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) {
|
||||
if uid <= 0 {
|
||||
return nil, errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
if page < 0 {
|
||||
return nil, errs.ErrPageIndexInvalid
|
||||
} else if page == 0 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
if count < 1 {
|
||||
return nil, errs.ErrPageCountInvalid
|
||||
}
|
||||
@@ -86,47 +93,58 @@ func (s *TransactionService) GetTransactionsByMaxTime(uid int64, maxTransactionT
|
||||
var transactions []*models.Transaction
|
||||
var err error
|
||||
|
||||
actualCount := count
|
||||
|
||||
if needOneMoreItem {
|
||||
actualCount++
|
||||
}
|
||||
|
||||
condition, conditionParams := s.getTransactionQueryCondition(uid, maxTransactionTime, minTransactionTime, transactionType, categoryIds, accountIds, keyword, noDuplicated)
|
||||
err = s.UserDataDB(uid).Where(condition, conditionParams...).Limit(int(count), 0).OrderBy("transaction_time desc").Find(&transactions)
|
||||
err = s.UserDataDB(uid).NewSession(c).Where(condition, conditionParams...).Limit(int(actualCount), int(count*(page-1))).OrderBy("transaction_time desc").Find(&transactions)
|
||||
|
||||
return transactions, err
|
||||
}
|
||||
|
||||
// GetTransactionsInMonthByPage returns transactions in given year and month
|
||||
func (s *TransactionService) GetTransactionsInMonthByPage(uid int64, year int32, month int32, transactionType models.TransactionDbType, categoryIds []int64, accountIds []int64, keyword string, page int32, count int32, utcOffset int16) ([]*models.Transaction, error) {
|
||||
// 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) {
|
||||
if uid <= 0 {
|
||||
return nil, errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
if page < 1 {
|
||||
return nil, errs.ErrPageIndexInvalid
|
||||
}
|
||||
|
||||
if count < 1 {
|
||||
return nil, errs.ErrPageCountInvalid
|
||||
}
|
||||
|
||||
startTime, err := utils.ParseFromLongDateTime(fmt.Sprintf("%d-%02d-01 00:00:00", year, month), utcOffset)
|
||||
startMinUnixTime, err := utils.ParseFromLongDateTimeToMinUnixTime(fmt.Sprintf("%d-%02d-01 00:00:00", year, month))
|
||||
startMaxUnixTime, err := utils.ParseFromLongDateTimeToMaxUnixTime(fmt.Sprintf("%d-%02d-01 00:00:00", year, month))
|
||||
|
||||
if err != nil {
|
||||
return nil, errs.ErrSystemError
|
||||
}
|
||||
|
||||
endTime := startTime.AddDate(0, 1, 0)
|
||||
endMaxUnixTime := startMaxUnixTime.AddDate(0, 1, 0)
|
||||
|
||||
minTransactionTime := utils.GetMinTransactionTimeFromUnixTime(startTime.Unix())
|
||||
maxTransactionTime := utils.GetMinTransactionTimeFromUnixTime(endTime.Unix()) - 1
|
||||
minTransactionTime := utils.GetMinTransactionTimeFromUnixTime(startMinUnixTime.Unix())
|
||||
maxTransactionTime := utils.GetMinTransactionTimeFromUnixTime(endMaxUnixTime.Unix()) - 1
|
||||
|
||||
var transactions []*models.Transaction
|
||||
|
||||
condition, conditionParams := s.getTransactionQueryCondition(uid, maxTransactionTime, minTransactionTime, transactionType, categoryIds, accountIds, keyword, true)
|
||||
err = s.UserDataDB(uid).Where(condition, conditionParams...).Limit(int(count), int(count*(page-1))).OrderBy("transaction_time desc").Find(&transactions)
|
||||
err = s.UserDataDB(uid).NewSession(c).Where(condition, conditionParams...).OrderBy("transaction_time desc").Find(&transactions)
|
||||
|
||||
return transactions, err
|
||||
transactionsInMonth := make([]*models.Transaction, 0, len(transactions))
|
||||
|
||||
for i := 0; i < len(transactions); i++ {
|
||||
transaction := transactions[i]
|
||||
transactionUnixTime := utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime)
|
||||
transactionTimeZone := time.FixedZone("Transaction Timezone", int(transaction.TimezoneUtcOffset)*60)
|
||||
|
||||
if utils.IsUnixTimeEqualsYearAndMonth(transactionUnixTime, transactionTimeZone, year, month) {
|
||||
transactionsInMonth = append(transactionsInMonth, transaction)
|
||||
}
|
||||
}
|
||||
|
||||
return transactionsInMonth, err
|
||||
}
|
||||
|
||||
// GetTransactionByTransactionId returns a transaction model according to transaction id
|
||||
func (s *TransactionService) GetTransactionByTransactionId(uid int64, transactionId int64) (*models.Transaction, error) {
|
||||
func (s *TransactionService) GetTransactionByTransactionId(c *core.Context, uid int64, transactionId int64) (*models.Transaction, error) {
|
||||
if uid <= 0 {
|
||||
return nil, errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -136,7 +154,7 @@ func (s *TransactionService) GetTransactionByTransactionId(uid int64, transactio
|
||||
}
|
||||
|
||||
transaction := &models.Transaction{}
|
||||
has, err := s.UserDataDB(uid).ID(transactionId).Where("uid=? AND deleted=?", uid, false).Get(transaction)
|
||||
has, err := s.UserDataDB(uid).NewSession(c).ID(transactionId).Where("uid=? AND deleted=?", uid, false).Get(transaction)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -148,12 +166,12 @@ func (s *TransactionService) GetTransactionByTransactionId(uid int64, transactio
|
||||
}
|
||||
|
||||
// GetAllTransactionCount returns total count of transactions
|
||||
func (s *TransactionService) GetAllTransactionCount(uid int64) (int64, error) {
|
||||
return s.GetTransactionCount(uid, 0, 0, 0, nil, nil, "")
|
||||
func (s *TransactionService) GetAllTransactionCount(c *core.Context, uid int64) (int64, error) {
|
||||
return s.GetTransactionCount(c, uid, 0, 0, 0, nil, nil, "")
|
||||
}
|
||||
|
||||
// GetMonthTransactionCount returns total count of transactions in given year and month
|
||||
func (s *TransactionService) GetMonthTransactionCount(uid int64, year int32, month int32, transactionType models.TransactionDbType, categoryIds []int64, accountIds []int64, keyword string, utcOffset int16) (int64, error) {
|
||||
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
|
||||
}
|
||||
@@ -169,21 +187,21 @@ func (s *TransactionService) GetMonthTransactionCount(uid int64, year int32, mon
|
||||
minTransactionTime := utils.GetMinTransactionTimeFromUnixTime(startTime.Unix())
|
||||
maxTransactionTime := utils.GetMinTransactionTimeFromUnixTime(endTime.Unix()) - 1
|
||||
|
||||
return s.GetTransactionCount(uid, maxTransactionTime, minTransactionTime, transactionType, categoryIds, accountIds, keyword)
|
||||
return s.GetTransactionCount(c, uid, maxTransactionTime, minTransactionTime, transactionType, categoryIds, accountIds, keyword)
|
||||
}
|
||||
|
||||
// GetTransactionCount returns count of transactions
|
||||
func (s *TransactionService) GetTransactionCount(uid int64, maxTransactionTime int64, minTransactionTime int64, transactionType models.TransactionDbType, categoryIds []int64, 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, keyword string) (int64, error) {
|
||||
if uid <= 0 {
|
||||
return 0, errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
condition, conditionParams := s.getTransactionQueryCondition(uid, maxTransactionTime, minTransactionTime, transactionType, categoryIds, accountIds, keyword, true)
|
||||
return s.UserDataDB(uid).Where(condition, conditionParams...).Count(&models.Transaction{})
|
||||
return s.UserDataDB(uid).NewSession(c).Where(condition, conditionParams...).Count(&models.Transaction{})
|
||||
}
|
||||
|
||||
// CreateTransaction saves a new transaction to database
|
||||
func (s *TransactionService) CreateTransaction(transaction *models.Transaction, tagIds []int64) error {
|
||||
func (s *TransactionService) CreateTransaction(c *core.Context, transaction *models.Transaction, tagIds []int64) error {
|
||||
if transaction.Uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -204,6 +222,11 @@ func (s *TransactionService) CreateTransaction(transaction *models.Transaction,
|
||||
}
|
||||
|
||||
uuids := s.GenerateUuids(uuid.UUID_TYPE_TRANSACTION, uint8(needUuidCount))
|
||||
|
||||
if len(uuids) < needUuidCount {
|
||||
return errs.ErrSystemIsBusy
|
||||
}
|
||||
|
||||
transaction.TransactionId = uuids[0]
|
||||
|
||||
if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT || transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN {
|
||||
@@ -219,8 +242,14 @@ func (s *TransactionService) CreateTransaction(transaction *models.Transaction,
|
||||
transactionTagIndexs := make([]*models.TransactionTagIndex, len(tagIds))
|
||||
|
||||
for i := 0; i < len(tagIds); i++ {
|
||||
tagIndexId := s.GenerateUuid(uuid.UUID_TYPE_TAG_INDEX)
|
||||
|
||||
if tagIndexId < 1 {
|
||||
return errs.ErrSystemIsBusy
|
||||
}
|
||||
|
||||
transactionTagIndexs[i] = &models.TransactionTagIndex{
|
||||
TagIndexId: s.GenerateUuid(uuid.UUID_TYPE_TAG_INDEX),
|
||||
TagIndexId: tagIndexId,
|
||||
Uid: transaction.Uid,
|
||||
Deleted: false,
|
||||
TagId: tagIds[i],
|
||||
@@ -230,7 +259,7 @@ func (s *TransactionService) CreateTransaction(transaction *models.Transaction,
|
||||
}
|
||||
}
|
||||
|
||||
return s.UserDataDB(transaction.Uid).DoTransaction(func(sess *xorm.Session) error {
|
||||
return s.UserDataDB(transaction.Uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||
// Get and verify source and destination account
|
||||
sourceAccount, destinationAccount, err := s.getAccountModels(sess, transaction)
|
||||
|
||||
@@ -394,7 +423,7 @@ func (s *TransactionService) CreateTransaction(transaction *models.Transaction,
|
||||
}
|
||||
|
||||
// ModifyTransaction saves an existed transaction to database
|
||||
func (s *TransactionService) ModifyTransaction(transaction *models.Transaction, addTagIds []int64, removeTagIds []int64) error {
|
||||
func (s *TransactionService) ModifyTransaction(c *core.Context, transaction *models.Transaction, addTagIds []int64, removeTagIds []int64) error {
|
||||
if transaction.Uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -413,8 +442,14 @@ func (s *TransactionService) ModifyTransaction(transaction *models.Transaction,
|
||||
transactionTagIndexs := make([]*models.TransactionTagIndex, len(addTagIds))
|
||||
|
||||
for i := 0; i < len(addTagIds); i++ {
|
||||
tagIndexId := s.GenerateUuid(uuid.UUID_TYPE_TAG_INDEX)
|
||||
|
||||
if tagIndexId < 1 {
|
||||
return errs.ErrSystemIsBusy
|
||||
}
|
||||
|
||||
transactionTagIndexs[i] = &models.TransactionTagIndex{
|
||||
TagIndexId: s.GenerateUuid(uuid.UUID_TYPE_TAG_INDEX),
|
||||
TagIndexId: tagIndexId,
|
||||
Uid: transaction.Uid,
|
||||
Deleted: false,
|
||||
TagId: addTagIds[i],
|
||||
@@ -424,7 +459,7 @@ func (s *TransactionService) ModifyTransaction(transaction *models.Transaction,
|
||||
}
|
||||
}
|
||||
|
||||
err := s.UserDataDB(transaction.Uid).DoTransaction(func(sess *xorm.Session) error {
|
||||
err := s.UserDataDB(transaction.Uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||
// Get and verify current transaction
|
||||
oldTransaction := &models.Transaction{}
|
||||
has, err := sess.ID(transaction.TransactionId).Where("uid=? AND deleted=?", transaction.Uid, false).Get(oldTransaction)
|
||||
@@ -765,7 +800,7 @@ func (s *TransactionService) ModifyTransaction(transaction *models.Transaction,
|
||||
}
|
||||
|
||||
// DeleteTransaction deletes an existed transaction from database
|
||||
func (s *TransactionService) DeleteTransaction(uid int64, transactionId int64) error {
|
||||
func (s *TransactionService) DeleteTransaction(c *core.Context, uid int64, transactionId int64) error {
|
||||
if uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -782,7 +817,7 @@ func (s *TransactionService) DeleteTransaction(uid int64, transactionId int64) e
|
||||
DeletedUnixTime: now,
|
||||
}
|
||||
|
||||
return s.UserDataDB(uid).DoTransaction(func(sess *xorm.Session) error {
|
||||
return s.UserDataDB(uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||
// Get and verify current transaction
|
||||
oldTransaction := &models.Transaction{}
|
||||
has, err := sess.ID(transactionId).Where("uid=? AND deleted=?", uid, false).Get(oldTransaction)
|
||||
@@ -885,7 +920,7 @@ func (s *TransactionService) DeleteTransaction(uid int64, transactionId int64) e
|
||||
}
|
||||
|
||||
// DeleteAllTransactions deletes all existed transactions from database
|
||||
func (s *TransactionService) DeleteAllTransactions(uid int64) error {
|
||||
func (s *TransactionService) DeleteAllTransactions(c *core.Context, uid int64) error {
|
||||
if uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -908,7 +943,7 @@ func (s *TransactionService) DeleteAllTransactions(uid int64) error {
|
||||
DeletedUnixTime: now,
|
||||
}
|
||||
|
||||
return s.UserDataDB(uid).DoTransaction(func(sess *xorm.Session) error {
|
||||
return s.UserDataDB(uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||
// Update all transaction to deleted
|
||||
_, err := sess.Cols("deleted", "deleted_unix_time").Where("uid=? AND deleted=?", uid, false).Update(updateModel)
|
||||
|
||||
@@ -975,7 +1010,7 @@ func (s *TransactionService) GetRelatedTransferTransaction(originalTransaction *
|
||||
}
|
||||
|
||||
// GetAccountsTotalIncomeAndExpense returns the every accounts total income and expense amount by specific date range
|
||||
func (s *TransactionService) GetAccountsTotalIncomeAndExpense(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) (map[int64]int64, map[int64]int64, error) {
|
||||
if uid <= 0 {
|
||||
return nil, nil, errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -984,7 +1019,7 @@ func (s *TransactionService) GetAccountsTotalIncomeAndExpense(uid int64, startUn
|
||||
endTransactionTime := utils.GetMaxTransactionTimeFromUnixTime(endUnixTime)
|
||||
|
||||
var transactionTotalAmounts []*models.Transaction
|
||||
err := s.UserDataDB(uid).Select("type, account_id, SUM(amount) as amount").Where("uid=? AND deleted=? AND (type=? OR type=?) AND transaction_time>=? AND transaction_time<=?", uid, false, models.TRANSACTION_DB_TYPE_INCOME, models.TRANSACTION_DB_TYPE_EXPENSE, startTransactionTime, endTransactionTime).GroupBy("type, account_id").Find(&transactionTotalAmounts)
|
||||
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)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -1007,7 +1042,7 @@ func (s *TransactionService) GetAccountsTotalIncomeAndExpense(uid int64, startUn
|
||||
}
|
||||
|
||||
// GetAccountsMonthTotalIncomeAndExpense returns the every accounts total income and expense amount in month by specific date range
|
||||
func (s *TransactionService) GetAccountsMonthTotalIncomeAndExpense(uid int64, startUnixTime int64, endUnixTime int64, pageCount int) (map[string]models.TransactionAccountsAmount, error) {
|
||||
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
|
||||
}
|
||||
@@ -1022,7 +1057,7 @@ func (s *TransactionService) GetAccountsMonthTotalIncomeAndExpense(uid int64, st
|
||||
for maxTransactionTime > 0 {
|
||||
var transactions []*models.Transaction
|
||||
|
||||
err := s.UserDataDB(uid).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)
|
||||
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)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1074,13 +1109,13 @@ func (s *TransactionService) GetAccountsMonthTotalIncomeAndExpense(uid int64, st
|
||||
}
|
||||
|
||||
// GetAccountsAndCategoriesTotalIncomeAndExpense returns the every accounts and categories total income and expense amount by specific date range
|
||||
func (s *TransactionService) GetAccountsAndCategoriesTotalIncomeAndExpense(uid int64, startUnixTime int64, endUnixTime int64) ([]*models.Transaction, error) {
|
||||
func (s *TransactionService) GetAccountsAndCategoriesTotalIncomeAndExpense(c *core.Context, uid int64, startUnixTime int64, endUnixTime int64) ([]*models.Transaction, error) {
|
||||
if uid <= 0 {
|
||||
return nil, errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
condition := "uid=? AND deleted=? AND (type=? OR type=?)"
|
||||
conditionParams := make([]interface{}, 0, 8)
|
||||
conditionParams := make([]any, 0, 8)
|
||||
conditionParams = append(conditionParams, uid)
|
||||
conditionParams = append(conditionParams, false)
|
||||
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_INCOME)
|
||||
@@ -1097,7 +1132,7 @@ func (s *TransactionService) GetAccountsAndCategoriesTotalIncomeAndExpense(uid i
|
||||
}
|
||||
|
||||
var transactionTotalAmounts []*models.Transaction
|
||||
err := s.UserDataDB(uid).Select("category_id, account_id, SUM(amount) as amount").Where(condition, conditionParams...).GroupBy("category_id, account_id").Find(&transactionTotalAmounts)
|
||||
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 {
|
||||
return nil, err
|
||||
@@ -1118,9 +1153,9 @@ func (s *TransactionService) GetTransactionMapByList(transactions []*models.Tran
|
||||
return transactionMap
|
||||
}
|
||||
|
||||
func (s *TransactionService) getTransactionQueryCondition(uid int64, maxTransactionTime int64, minTransactionTime int64, transactionType models.TransactionDbType, categoryIds []int64, accountIds []int64, keyword string, noDuplicated bool) (string, []interface{}) {
|
||||
func (s *TransactionService) getTransactionQueryCondition(uid int64, maxTransactionTime int64, minTransactionTime int64, transactionType models.TransactionDbType, categoryIds []int64, accountIds []int64, keyword string, noDuplicated bool) (string, []any) {
|
||||
condition := "uid=? AND deleted=?"
|
||||
conditionParams := make([]interface{}, 0, 16)
|
||||
conditionParams := make([]any, 0, 16)
|
||||
conditionParams = append(conditionParams, uid)
|
||||
conditionParams = append(conditionParams, false)
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/pquerna/otp/totp"
|
||||
"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"
|
||||
@@ -45,13 +46,13 @@ var (
|
||||
)
|
||||
|
||||
// GetUserTwoFactorSettingByUid returns the 2fa setting model according to user uid
|
||||
func (s *TwoFactorAuthorizationService) GetUserTwoFactorSettingByUid(uid int64) (*models.TwoFactor, error) {
|
||||
func (s *TwoFactorAuthorizationService) GetUserTwoFactorSettingByUid(c *core.Context, uid int64) (*models.TwoFactor, error) {
|
||||
if uid <= 0 {
|
||||
return nil, errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
twoFactor := &models.TwoFactor{}
|
||||
has, err := s.UserDB().Where("uid=?", uid).Get(twoFactor)
|
||||
has, err := s.UserDB().NewSession(c).Where("uid=?", uid).Get(twoFactor)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -69,7 +70,7 @@ func (s *TwoFactorAuthorizationService) GetUserTwoFactorSettingByUid(uid int64)
|
||||
}
|
||||
|
||||
// GenerateTwoFactorSecret generates a new 2fa secret
|
||||
func (s *TwoFactorAuthorizationService) GenerateTwoFactorSecret(user *models.User) (*otp.Key, error) {
|
||||
func (s *TwoFactorAuthorizationService) GenerateTwoFactorSecret(c *core.Context, user *models.User) (*otp.Key, error) {
|
||||
if user == nil {
|
||||
return nil, errs.ErrUserNotFound
|
||||
}
|
||||
@@ -85,7 +86,7 @@ func (s *TwoFactorAuthorizationService) GenerateTwoFactorSecret(user *models.Use
|
||||
}
|
||||
|
||||
// CreateTwoFactorSetting saves a new 2fa setting to database
|
||||
func (s *TwoFactorAuthorizationService) CreateTwoFactorSetting(twoFactor *models.TwoFactor) error {
|
||||
func (s *TwoFactorAuthorizationService) CreateTwoFactorSetting(c *core.Context, twoFactor *models.TwoFactor) error {
|
||||
if twoFactor.Uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -99,19 +100,19 @@ func (s *TwoFactorAuthorizationService) CreateTwoFactorSetting(twoFactor *models
|
||||
|
||||
twoFactor.CreatedUnixTime = time.Now().Unix()
|
||||
|
||||
return s.UserDB().DoTransaction(func(sess *xorm.Session) error {
|
||||
return s.UserDB().DoTransaction(c, func(sess *xorm.Session) error {
|
||||
_, err := sess.Insert(twoFactor)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteTwoFactorSetting deletes an existed 2fa setting from database
|
||||
func (s *TwoFactorAuthorizationService) DeleteTwoFactorSetting(uid int64) error {
|
||||
func (s *TwoFactorAuthorizationService) DeleteTwoFactorSetting(c *core.Context, uid int64) error {
|
||||
if uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
return s.UserDB().DoTransaction(func(sess *xorm.Session) error {
|
||||
return s.UserDB().DoTransaction(c, func(sess *xorm.Session) error {
|
||||
deletedRows, err := sess.Where("uid=?", uid).Delete(&models.TwoFactor{})
|
||||
|
||||
if err != nil {
|
||||
@@ -125,22 +126,22 @@ func (s *TwoFactorAuthorizationService) DeleteTwoFactorSetting(uid int64) error
|
||||
}
|
||||
|
||||
// ExistsTwoFactorSetting returns whether the given user has existed 2fa setting
|
||||
func (s *TwoFactorAuthorizationService) ExistsTwoFactorSetting(uid int64) (bool, error) {
|
||||
func (s *TwoFactorAuthorizationService) ExistsTwoFactorSetting(c *core.Context, uid int64) (bool, error) {
|
||||
if uid <= 0 {
|
||||
return false, errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
return s.UserDB().Cols("uid").Where("uid=?", uid).Exist(&models.TwoFactor{})
|
||||
return s.UserDB().NewSession(c).Cols("uid").Where("uid=?", uid).Exist(&models.TwoFactor{})
|
||||
}
|
||||
|
||||
// GetAndUseUserTwoFactorRecoveryCode checks whether the given 2fa recovery code exists and marks it used
|
||||
func (s *TwoFactorAuthorizationService) GetAndUseUserTwoFactorRecoveryCode(uid int64, recoveryCode string, salt string) error {
|
||||
func (s *TwoFactorAuthorizationService) GetAndUseUserTwoFactorRecoveryCode(c *core.Context, uid int64, recoveryCode string, salt string) error {
|
||||
if uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
recoveryCode = utils.EncodePassword(recoveryCode, salt)
|
||||
exists, err := s.UserDB().Cols("uid", "recovery_code").Where("uid=? AND recovery_code=? AND used=?", uid, recoveryCode, false).Exist(&models.TwoFactorRecoveryCode{})
|
||||
exists, err := s.UserDB().NewSession(c).Cols("uid", "recovery_code").Where("uid=? AND recovery_code=? AND used=?", uid, recoveryCode, false).Exist(&models.TwoFactorRecoveryCode{})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -148,7 +149,7 @@ func (s *TwoFactorAuthorizationService) GetAndUseUserTwoFactorRecoveryCode(uid i
|
||||
return errs.ErrTwoFactorRecoveryCodeNotExist
|
||||
}
|
||||
|
||||
return s.UserDB().DoTransaction(func(sess *xorm.Session) error {
|
||||
return s.UserDB().DoTransaction(c, func(sess *xorm.Session) error {
|
||||
_, err := sess.Cols("used", "used_unix_time").Where("uid=? AND recovery_code=?", uid, recoveryCode).Update(&models.TwoFactorRecoveryCode{Used: true, UsedUnixTime: time.Now().Unix()})
|
||||
return err
|
||||
})
|
||||
@@ -172,7 +173,7 @@ func (s *TwoFactorAuthorizationService) GenerateTwoFactorRecoveryCodes() ([]stri
|
||||
}
|
||||
|
||||
// CreateTwoFactorRecoveryCodes saves new 2fa recovery codes to database
|
||||
func (s *TwoFactorAuthorizationService) CreateTwoFactorRecoveryCodes(uid int64, recoveryCodes []string, salt string) error {
|
||||
func (s *TwoFactorAuthorizationService) CreateTwoFactorRecoveryCodes(c *core.Context, uid int64, recoveryCodes []string, salt string) error {
|
||||
twoFactorRecoveryCodes := make([]*models.TwoFactorRecoveryCode, len(recoveryCodes))
|
||||
|
||||
for i := 0; i < len(recoveryCodes); i++ {
|
||||
@@ -184,7 +185,7 @@ func (s *TwoFactorAuthorizationService) CreateTwoFactorRecoveryCodes(uid int64,
|
||||
}
|
||||
}
|
||||
|
||||
return s.UserDB().DoTransaction(func(sess *xorm.Session) error {
|
||||
return s.UserDB().DoTransaction(c, func(sess *xorm.Session) error {
|
||||
_, err := sess.Where("uid=?", uid).Delete(&models.TwoFactorRecoveryCode{})
|
||||
|
||||
if err != nil {
|
||||
@@ -205,12 +206,12 @@ func (s *TwoFactorAuthorizationService) CreateTwoFactorRecoveryCodes(uid int64,
|
||||
}
|
||||
|
||||
// DeleteTwoFactorRecoveryCodes deletes existed 2fa recovery codes from database
|
||||
func (s *TwoFactorAuthorizationService) DeleteTwoFactorRecoveryCodes(uid int64) error {
|
||||
func (s *TwoFactorAuthorizationService) DeleteTwoFactorRecoveryCodes(c *core.Context, uid int64) error {
|
||||
if uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
return s.UserDB().DoTransaction(func(sess *xorm.Session) error {
|
||||
return s.UserDB().DoTransaction(c, func(sess *xorm.Session) error {
|
||||
_, err := sess.Where("uid=?", uid).Delete(&models.TwoFactorRecoveryCode{})
|
||||
return err
|
||||
})
|
||||
|
||||
@@ -1,20 +1,32 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"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/locales"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/mail"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/templates"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/uuid"
|
||||
)
|
||||
|
||||
const verifyEmailUrlFormat = "%sdesktop/#/verify_email?token=%s"
|
||||
|
||||
// UserService represents user service
|
||||
type UserService struct {
|
||||
ServiceUsingDB
|
||||
ServiceUsingConfig
|
||||
ServiceUsingMailer
|
||||
ServiceUsingUuid
|
||||
}
|
||||
|
||||
@@ -24,6 +36,12 @@ var (
|
||||
ServiceUsingDB: ServiceUsingDB{
|
||||
container: datastore.Container,
|
||||
},
|
||||
ServiceUsingConfig: ServiceUsingConfig{
|
||||
container: settings.Container,
|
||||
},
|
||||
ServiceUsingMailer: ServiceUsingMailer{
|
||||
container: mail.Container,
|
||||
},
|
||||
ServiceUsingUuid: ServiceUsingUuid{
|
||||
container: uuid.Container,
|
||||
},
|
||||
@@ -31,14 +49,14 @@ var (
|
||||
)
|
||||
|
||||
// GetUserByUsernameOrEmailAndPassword returns the user model according to login name and password
|
||||
func (s *UserService) GetUserByUsernameOrEmailAndPassword(loginname string, password string) (*models.User, error) {
|
||||
func (s *UserService) GetUserByUsernameOrEmailAndPassword(c *core.Context, loginname string, password string) (*models.User, error) {
|
||||
var user *models.User
|
||||
var err error
|
||||
|
||||
if utils.IsValidUsername(loginname) {
|
||||
user, err = s.GetUserByUsername(loginname)
|
||||
user, err = s.GetUserByUsername(c, loginname)
|
||||
} else if utils.IsValidEmail(loginname) {
|
||||
user, err = s.GetUserByEmail(loginname)
|
||||
user, err = s.GetUserByEmail(c, loginname)
|
||||
} else {
|
||||
err = errs.ErrLoginNameInvalid
|
||||
}
|
||||
@@ -55,13 +73,13 @@ func (s *UserService) GetUserByUsernameOrEmailAndPassword(loginname string, pass
|
||||
}
|
||||
|
||||
// GetUserById returns the user model according to user uid
|
||||
func (s *UserService) GetUserById(uid int64) (*models.User, error) {
|
||||
func (s *UserService) GetUserById(c *core.Context, uid int64) (*models.User, error) {
|
||||
if uid <= 0 {
|
||||
return nil, errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
user := &models.User{}
|
||||
has, err := s.UserDB().ID(uid).Where("deleted=?", false).Get(user)
|
||||
has, err := s.UserDB().NewSession(c).ID(uid).Where("deleted=?", false).Get(user)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -73,13 +91,13 @@ func (s *UserService) GetUserById(uid int64) (*models.User, error) {
|
||||
}
|
||||
|
||||
// GetUserByUsername returns the user model according to user name
|
||||
func (s *UserService) GetUserByUsername(username string) (*models.User, error) {
|
||||
func (s *UserService) GetUserByUsername(c *core.Context, username string) (*models.User, error) {
|
||||
if username == "" {
|
||||
return nil, errs.ErrUsernameIsEmpty
|
||||
}
|
||||
|
||||
user := &models.User{}
|
||||
has, err := s.UserDB().Where("username=? AND deleted=?", username, false).Get(user)
|
||||
has, err := s.UserDB().NewSession(c).Where("username=? AND deleted=?", username, false).Get(user)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -91,13 +109,13 @@ func (s *UserService) GetUserByUsername(username string) (*models.User, error) {
|
||||
}
|
||||
|
||||
// GetUserByEmail returns the user model according to user email
|
||||
func (s *UserService) GetUserByEmail(email string) (*models.User, error) {
|
||||
func (s *UserService) GetUserByEmail(c *core.Context, email string) (*models.User, error) {
|
||||
if email == "" {
|
||||
return nil, errs.ErrEmailIsEmpty
|
||||
}
|
||||
|
||||
user := &models.User{}
|
||||
has, err := s.UserDB().Where("email=? AND deleted=?", email, false).Get(user)
|
||||
has, err := s.UserDB().NewSession(c).Where("email=? AND deleted=?", email, false).Get(user)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -109,8 +127,8 @@ func (s *UserService) GetUserByEmail(email string) (*models.User, error) {
|
||||
}
|
||||
|
||||
// CreateUser saves a new user model to database
|
||||
func (s *UserService) CreateUser(user *models.User) error {
|
||||
exists, err := s.ExistsUsername(user.Username)
|
||||
func (s *UserService) CreateUser(c *core.Context, user *models.User) error {
|
||||
exists, err := s.ExistsUsername(c, user.Username)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -118,7 +136,7 @@ func (s *UserService) CreateUser(user *models.User) error {
|
||||
return errs.ErrUsernameAlreadyExists
|
||||
}
|
||||
|
||||
exists, err = s.ExistsEmail(user.Email)
|
||||
exists, err = s.ExistsEmail(c, user.Email)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -135,6 +153,11 @@ func (s *UserService) CreateUser(user *models.User) error {
|
||||
}
|
||||
|
||||
user.Uid = s.GenerateUuid(uuid.UUID_TYPE_USER)
|
||||
|
||||
if user.Uid < 1 {
|
||||
return errs.ErrSystemIsBusy
|
||||
}
|
||||
|
||||
user.Password = utils.EncodePassword(user.Password, user.Salt)
|
||||
|
||||
user.Deleted = false
|
||||
@@ -143,36 +166,39 @@ func (s *UserService) CreateUser(user *models.User) error {
|
||||
user.UpdatedUnixTime = time.Now().Unix()
|
||||
user.LastLoginUnixTime = time.Now().Unix()
|
||||
|
||||
return s.UserDB().DoTransaction(func(sess *xorm.Session) error {
|
||||
return s.UserDB().DoTransaction(c, func(sess *xorm.Session) error {
|
||||
_, err := sess.Insert(user)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateUser saves an existed user model to database
|
||||
func (s *UserService) UpdateUser(user *models.User, modifyUserLanguage bool) (keyProfileUpdated bool, err error) {
|
||||
func (s *UserService) UpdateUser(c *core.Context, user *models.User, modifyUserLanguage bool) (keyProfileUpdated bool, emailSetToUnverified bool, err error) {
|
||||
if user.Uid <= 0 {
|
||||
return false, errs.ErrUserIdInvalid
|
||||
return false, false, errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
updateCols := make([]string, 0, 8)
|
||||
|
||||
now := time.Now().Unix()
|
||||
keyProfileUpdated = false
|
||||
emailSetToUnverified = false
|
||||
|
||||
if user.Email != "" {
|
||||
exists, err := s.ExistsEmail(user.Email)
|
||||
exists, err := s.ExistsEmail(c, user.Email)
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false, false, err
|
||||
} else if exists {
|
||||
return false, errs.ErrUserEmailAlreadyExists
|
||||
return false, false, errs.ErrUserEmailAlreadyExists
|
||||
}
|
||||
|
||||
user.EmailVerified = false
|
||||
|
||||
updateCols = append(updateCols, "email")
|
||||
updateCols = append(updateCols, "email_verified")
|
||||
|
||||
emailSetToUnverified = true
|
||||
}
|
||||
|
||||
if user.Password != "" {
|
||||
@@ -225,7 +251,7 @@ func (s *UserService) UpdateUser(user *models.User, modifyUserLanguage bool) (ke
|
||||
user.UpdatedUnixTime = now
|
||||
updateCols = append(updateCols, "updated_unix_time")
|
||||
|
||||
err = s.UserDB().DoTransaction(func(sess *xorm.Session) error {
|
||||
err = s.UserDB().DoTransaction(c, func(sess *xorm.Session) error {
|
||||
updatedRows, err := sess.ID(user.Uid).Cols(updateCols...).Where("deleted=?", false).Update(user)
|
||||
|
||||
if err != nil {
|
||||
@@ -238,26 +264,118 @@ func (s *UserService) UpdateUser(user *models.User, modifyUserLanguage bool) (ke
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false, false, err
|
||||
}
|
||||
|
||||
return keyProfileUpdated, nil
|
||||
return keyProfileUpdated, emailSetToUnverified, nil
|
||||
}
|
||||
|
||||
// UpdateUserLastLoginTime updates the last login time field
|
||||
func (s *UserService) UpdateUserLastLoginTime(uid int64) error {
|
||||
func (s *UserService) UpdateUserLastLoginTime(c *core.Context, uid int64) error {
|
||||
if uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
return s.UserDB().DoTransaction(func(sess *xorm.Session) error {
|
||||
return s.UserDB().DoTransaction(c, func(sess *xorm.Session) error {
|
||||
_, err := sess.ID(uid).Cols("last_login_unix_time").Where("deleted=?", false).Update(&models.User{LastLoginUnixTime: time.Now().Unix()})
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// EnableUser sets user enabled
|
||||
func (s *UserService) EnableUser(c *core.Context, username string) error {
|
||||
if username == "" {
|
||||
return errs.ErrUsernameIsEmpty
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
|
||||
updateModel := &models.User{
|
||||
Disabled: false,
|
||||
UpdatedUnixTime: now,
|
||||
}
|
||||
|
||||
updatedRows, err := s.UserDB().NewSession(c).Cols("disabled", "updated_unix_time").Where("username=? AND deleted=?", username, false).Update(updateModel)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
} else if updatedRows < 1 {
|
||||
return errs.ErrUserNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DisableUser sets user disabled
|
||||
func (s *UserService) DisableUser(c *core.Context, username string) error {
|
||||
if username == "" {
|
||||
return errs.ErrUsernameIsEmpty
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
|
||||
updateModel := &models.User{
|
||||
Disabled: true,
|
||||
UpdatedUnixTime: now,
|
||||
}
|
||||
|
||||
updatedRows, err := s.UserDB().NewSession(c).Cols("disabled", "updated_unix_time").Where("username=? AND deleted=?", username, false).Update(updateModel)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
} else if updatedRows < 1 {
|
||||
return errs.ErrUserNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetUserEmailVerified sets user email address verified
|
||||
func (s *UserService) SetUserEmailVerified(c *core.Context, username string) error {
|
||||
if username == "" {
|
||||
return errs.ErrUsernameIsEmpty
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
|
||||
updateModel := &models.User{
|
||||
EmailVerified: true,
|
||||
UpdatedUnixTime: now,
|
||||
}
|
||||
|
||||
updatedRows, err := s.UserDB().NewSession(c).Cols("email_verified", "updated_unix_time").Where("username=? AND deleted=?", username, false).Update(updateModel)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
} else if updatedRows < 1 {
|
||||
return errs.ErrUserNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetUserEmailUnverified sets user email address unverified
|
||||
func (s *UserService) SetUserEmailUnverified(c *core.Context, username string) error {
|
||||
if username == "" {
|
||||
return errs.ErrUsernameIsEmpty
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
|
||||
updateModel := &models.User{
|
||||
EmailVerified: false,
|
||||
UpdatedUnixTime: now,
|
||||
}
|
||||
|
||||
updatedRows, err := s.UserDB().NewSession(c).Cols("email_verified", "updated_unix_time").Where("username=? AND deleted=?", username, false).Update(updateModel)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
} else if updatedRows < 1 {
|
||||
return errs.ErrUserNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteUser deletes an existed user from database
|
||||
func (s *UserService) DeleteUser(username string) error {
|
||||
func (s *UserService) DeleteUser(c *core.Context, username string) error {
|
||||
if username == "" {
|
||||
return errs.ErrUsernameIsEmpty
|
||||
}
|
||||
@@ -269,7 +387,7 @@ func (s *UserService) DeleteUser(username string) error {
|
||||
DeletedUnixTime: now,
|
||||
}
|
||||
|
||||
deletedRows, err := s.UserDB().Cols("deleted", "deleted_unix_time").Where("username=? AND deleted=?", username, false).Update(updateModel)
|
||||
deletedRows, err := s.UserDB().NewSession(c).Cols("deleted", "deleted_unix_time").Where("username=? AND deleted=?", username, false).Update(updateModel)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -280,21 +398,75 @@ func (s *UserService) DeleteUser(username string) error {
|
||||
}
|
||||
|
||||
// ExistsUsername returns whether the given user name exists
|
||||
func (s *UserService) ExistsUsername(username string) (bool, error) {
|
||||
func (s *UserService) ExistsUsername(c *core.Context, username string) (bool, error) {
|
||||
if username == "" {
|
||||
return false, errs.ErrUsernameIsEmpty
|
||||
}
|
||||
|
||||
return s.UserDB().Cols("username").Where("username=? AND deleted=?", username, false).Exist(&models.User{})
|
||||
return s.UserDB().NewSession(c).Cols("username").Where("username=? AND deleted=?", username, false).Exist(&models.User{})
|
||||
}
|
||||
|
||||
// ExistsEmail returns whether the given user email exists
|
||||
func (s *UserService) ExistsEmail(email string) (bool, error) {
|
||||
func (s *UserService) ExistsEmail(c *core.Context, email string) (bool, error) {
|
||||
if email == "" {
|
||||
return false, errs.ErrEmailIsEmpty
|
||||
}
|
||||
|
||||
return s.UserDB().Cols("email").Where("email=? AND deleted=?", email, false).Exist(&models.User{})
|
||||
return s.UserDB().NewSession(c).Cols("email").Where("email=? AND deleted=?", email, false).Exist(&models.User{})
|
||||
}
|
||||
|
||||
// SendVerifyEmail sends verify email according to specified parameters
|
||||
func (s *UserService) SendVerifyEmail(user *models.User, verifyEmailToken string, backupLocale string) error {
|
||||
if !s.CurrentConfig().EnableSMTP {
|
||||
return errs.ErrSMTPServerNotEnabled
|
||||
}
|
||||
|
||||
locale := user.Language
|
||||
|
||||
if locale == "" {
|
||||
locale = backupLocale
|
||||
}
|
||||
|
||||
localeTextItems := locales.GetLocaleTextItems(locale)
|
||||
verifyEmailTextItems := localeTextItems.VerifyEmailTextItems
|
||||
|
||||
expireTimeInMinutes := s.CurrentConfig().EmailVerifyTokenExpiredTimeDuration.Minutes()
|
||||
verifyEmailUrl := fmt.Sprintf(verifyEmailUrlFormat, s.CurrentConfig().RootUrl, url.QueryEscape(verifyEmailToken))
|
||||
|
||||
tmpl, err := templates.GetTemplate(templates.TEMPLATE_VERIFY_EMAIL)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
templateParams := map[string]any{
|
||||
"AppName": s.CurrentConfig().AppName,
|
||||
"VerifyEmail": map[string]any{
|
||||
"Title": verifyEmailTextItems.Title,
|
||||
"Salutation": fmt.Sprintf(verifyEmailTextItems.SalutationFormat, user.Nickname),
|
||||
"DescriptionAboveBtn": verifyEmailTextItems.DescriptionAboveBtn,
|
||||
"VerifyEmailUrl": verifyEmailUrl,
|
||||
"VerifyEmail": verifyEmailTextItems.VerifyEmail,
|
||||
"DescriptionBelowBtn": fmt.Sprintf(verifyEmailTextItems.DescriptionBelowBtnFormat, s.CurrentConfig().AppName, expireTimeInMinutes),
|
||||
},
|
||||
}
|
||||
|
||||
var bodyBuffer bytes.Buffer
|
||||
err = tmpl.Execute(&bodyBuffer, templateParams)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
message := &mail.MailMessage{
|
||||
To: user.Email,
|
||||
Subject: verifyEmailTextItems.Title,
|
||||
Body: bodyBuffer.String(),
|
||||
}
|
||||
|
||||
err = s.SendMail(message)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// IsPasswordEqualsUserPassword returns whether the given password is correct
|
||||
|
||||
@@ -62,9 +62,29 @@ const (
|
||||
InternalUuidGeneratorType string = "internal"
|
||||
)
|
||||
|
||||
// User avatar provider types
|
||||
const (
|
||||
GravatarProvider string = "gravatar"
|
||||
)
|
||||
|
||||
// Map provider types
|
||||
const (
|
||||
OpenStreetMapProvider string = "openstreetmap"
|
||||
OpenStreetMapProvider string = "openstreetmap"
|
||||
OpenStreetMapHumanitarianStyleProvider string = "openstreetmap_humanitarian"
|
||||
OpenTopoMapProvider string = "opentopomap"
|
||||
OPNVKarteMapProvider string = "opnvkarte"
|
||||
CyclOSMMapProvider string = "cyclosm"
|
||||
GoogleMapProvider string = "googlemap"
|
||||
TomTomMapProvider string = "tomtom"
|
||||
BaiduMapProvider string = "baidumap"
|
||||
AmapProvider string = "amap"
|
||||
)
|
||||
|
||||
// Amap security verification method
|
||||
const (
|
||||
AmapSecurityVerificationInternalProxyMethod string = "internal_proxy"
|
||||
AmapSecurityVerificationExternalProxyMethod string = "external_proxy"
|
||||
AmapSecurityVerificationPlainTextMethod string = "plain_text"
|
||||
)
|
||||
|
||||
// Exchange rates data source types
|
||||
@@ -93,9 +113,11 @@ const (
|
||||
defaultLogMode string = "console"
|
||||
defaultLoglevel Level = LOGLEVEL_INFO
|
||||
|
||||
defaultSecretKey string = "ezbookkeeping"
|
||||
defaultTokenExpiredTime uint32 = 604800 // 7 days
|
||||
defaultTemporaryTokenExpiredTime uint32 = 300 // 5 minutes
|
||||
defaultSecretKey string = "ezbookkeeping"
|
||||
defaultTokenExpiredTime uint32 = 604800 // 7 days
|
||||
defaultTemporaryTokenExpiredTime uint32 = 300 // 5 minutes
|
||||
defaultEmailVerifyTokenExpiredTime uint32 = 3600 // 60 minutes
|
||||
defaultPasswordResetTokenExpiredTime uint32 = 3600 // 60 minutes
|
||||
|
||||
defaultExchangeRatesDataRequestTimeout uint32 = 10000 // 10 seconds
|
||||
)
|
||||
@@ -117,6 +139,15 @@ type DatabaseConfig struct {
|
||||
ConnectionMaxLifeTime uint32
|
||||
}
|
||||
|
||||
// SMTPConfig represents the SMTP setting config
|
||||
type SMTPConfig struct {
|
||||
SMTPHost string
|
||||
SMTPUser string
|
||||
SMTPPasswd string
|
||||
SMTPSkipTLSVerify bool
|
||||
FromAddress string
|
||||
}
|
||||
|
||||
// Config represents the global setting config
|
||||
type Config struct {
|
||||
// Global
|
||||
@@ -147,6 +178,10 @@ type Config struct {
|
||||
EnableQueryLog bool
|
||||
AutoUpdateDatabase bool
|
||||
|
||||
// Mail
|
||||
EnableSMTP bool
|
||||
SMTPConfig *SMTPConfig
|
||||
|
||||
// Log
|
||||
LogModes []string
|
||||
EnableConsoleLog bool
|
||||
@@ -160,27 +195,44 @@ type Config struct {
|
||||
UuidServerId uint8
|
||||
|
||||
// Secret
|
||||
SecretKey string
|
||||
EnableTwoFactor bool
|
||||
TokenExpiredTime uint32
|
||||
TokenExpiredTimeDuration time.Duration
|
||||
TemporaryTokenExpiredTime uint32
|
||||
TemporaryTokenExpiredTimeDuration time.Duration
|
||||
EnableRequestIdHeader bool
|
||||
SecretKey string
|
||||
EnableTwoFactor bool
|
||||
TokenExpiredTime uint32
|
||||
TokenExpiredTimeDuration time.Duration
|
||||
TemporaryTokenExpiredTime uint32
|
||||
TemporaryTokenExpiredTimeDuration time.Duration
|
||||
EmailVerifyTokenExpiredTime uint32
|
||||
EmailVerifyTokenExpiredTimeDuration time.Duration
|
||||
PasswordResetTokenExpiredTime uint32
|
||||
PasswordResetTokenExpiredTimeDuration time.Duration
|
||||
EnableRequestIdHeader bool
|
||||
|
||||
// User
|
||||
EnableUserRegister bool
|
||||
EnableUserRegister bool
|
||||
EnableUserVerifyEmail bool
|
||||
EnableUserForceVerifyEmail bool
|
||||
EnableUserForgetPassword bool
|
||||
ForgetPasswordRequireVerifyEmail bool
|
||||
AvatarProvider string
|
||||
|
||||
// Data
|
||||
EnableDataExport bool
|
||||
|
||||
// Map
|
||||
MapProvider string
|
||||
EnableMapDataFetchProxy bool
|
||||
MapProvider string
|
||||
TomTomMapAPIKey string
|
||||
GoogleMapAPIKey string
|
||||
BaiduMapAK string
|
||||
AmapApplicationKey string
|
||||
AmapSecurityVerificationMethod string
|
||||
AmapApplicationSecret string
|
||||
AmapApiExternalProxyUrl string
|
||||
EnableMapDataFetchProxy bool
|
||||
|
||||
// Exchange Rates
|
||||
ExchangeRatesDataSource string
|
||||
ExchangeRatesRequestTimeout uint32
|
||||
ExchangeRatesSkipTLSVerify bool
|
||||
}
|
||||
|
||||
// LoadConfiguration loads setting config from given config file path
|
||||
@@ -218,6 +270,12 @@ func LoadConfiguration(configFilePath string) (*Config, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = loadMailConfiguration(config, cfgFile, "mail")
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = loadLogConfiguration(config, cfgFile, "log")
|
||||
|
||||
if err != nil {
|
||||
@@ -366,6 +424,22 @@ func loadDatabaseConfiguration(config *Config, configFile *ini.File, sectionName
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadMailConfiguration(config *Config, configFile *ini.File, sectionName string) error {
|
||||
config.EnableSMTP = getConfigItemBoolValue(configFile, sectionName, "enable_smtp", false)
|
||||
|
||||
smtpConfig := &SMTPConfig{}
|
||||
smtpConfig.SMTPHost = getConfigItemStringValue(configFile, sectionName, "smtp_host")
|
||||
smtpConfig.SMTPUser = getConfigItemStringValue(configFile, sectionName, "smtp_user")
|
||||
smtpConfig.SMTPPasswd = getConfigItemStringValue(configFile, sectionName, "smtp_passwd")
|
||||
smtpConfig.SMTPSkipTLSVerify = getConfigItemBoolValue(configFile, sectionName, "smtp_skip_tls_verify", false)
|
||||
|
||||
smtpConfig.FromAddress = getConfigItemStringValue(configFile, sectionName, "from_address")
|
||||
|
||||
config.SMTPConfig = smtpConfig
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadLogConfiguration(config *Config, configFile *ini.File, sectionName string) error {
|
||||
config.LogModes = strings.Split(getConfigItemStringValue(configFile, sectionName, "mode", defaultLogMode), " ")
|
||||
|
||||
@@ -414,6 +488,12 @@ func loadSecurityConfiguration(config *Config, configFile *ini.File, sectionName
|
||||
config.TemporaryTokenExpiredTime = getConfigItemUint32Value(configFile, sectionName, "temporary_token_expired_time", defaultTemporaryTokenExpiredTime)
|
||||
config.TemporaryTokenExpiredTimeDuration = time.Duration(config.TemporaryTokenExpiredTime) * time.Second
|
||||
|
||||
config.EmailVerifyTokenExpiredTime = getConfigItemUint32Value(configFile, sectionName, "email_verify_token_expired_time", defaultEmailVerifyTokenExpiredTime)
|
||||
config.EmailVerifyTokenExpiredTimeDuration = time.Duration(config.EmailVerifyTokenExpiredTime) * time.Second
|
||||
|
||||
config.PasswordResetTokenExpiredTime = getConfigItemUint32Value(configFile, sectionName, "password_reset_token_expired_time", defaultPasswordResetTokenExpiredTime)
|
||||
config.PasswordResetTokenExpiredTimeDuration = time.Duration(config.PasswordResetTokenExpiredTime) * time.Second
|
||||
|
||||
config.EnableRequestIdHeader = getConfigItemBoolValue(configFile, sectionName, "request_id_header", true)
|
||||
|
||||
return nil
|
||||
@@ -421,6 +501,16 @@ func loadSecurityConfiguration(config *Config, configFile *ini.File, sectionName
|
||||
|
||||
func loadUserConfiguration(config *Config, configFile *ini.File, sectionName string) error {
|
||||
config.EnableUserRegister = getConfigItemBoolValue(configFile, sectionName, "enable_register", false)
|
||||
config.EnableUserVerifyEmail = getConfigItemBoolValue(configFile, sectionName, "enable_email_verify", false)
|
||||
config.EnableUserForceVerifyEmail = getConfigItemBoolValue(configFile, sectionName, "enable_force_email_verify", false)
|
||||
config.EnableUserForgetPassword = getConfigItemBoolValue(configFile, sectionName, "enable_forget_password", false)
|
||||
config.ForgetPasswordRequireVerifyEmail = getConfigItemBoolValue(configFile, sectionName, "forget_password_require_email_verify", false)
|
||||
|
||||
if getConfigItemStringValue(configFile, sectionName, "avatar_provider") == "" {
|
||||
config.AvatarProvider = ""
|
||||
} else if getConfigItemStringValue(configFile, sectionName, "avatar_provider") == GravatarProvider {
|
||||
config.AvatarProvider = GravatarProvider
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -432,36 +522,76 @@ func loadDataConfiguration(config *Config, configFile *ini.File, sectionName str
|
||||
}
|
||||
|
||||
func loadMapConfiguration(config *Config, configFile *ini.File, sectionName string) error {
|
||||
if getConfigItemStringValue(configFile, sectionName, "map_provider") == "" {
|
||||
mapProvider := getConfigItemStringValue(configFile, sectionName, "map_provider")
|
||||
|
||||
if mapProvider == "" {
|
||||
config.MapProvider = ""
|
||||
} else if getConfigItemStringValue(configFile, sectionName, "map_provider") == OpenStreetMapProvider {
|
||||
} else if mapProvider == OpenStreetMapProvider {
|
||||
config.MapProvider = OpenStreetMapProvider
|
||||
} else if mapProvider == OpenStreetMapHumanitarianStyleProvider {
|
||||
config.MapProvider = OpenStreetMapHumanitarianStyleProvider
|
||||
} else if mapProvider == OpenTopoMapProvider {
|
||||
config.MapProvider = OpenTopoMapProvider
|
||||
} else if mapProvider == OPNVKarteMapProvider {
|
||||
config.MapProvider = OPNVKarteMapProvider
|
||||
} else if mapProvider == CyclOSMMapProvider {
|
||||
config.MapProvider = CyclOSMMapProvider
|
||||
} else if mapProvider == GoogleMapProvider {
|
||||
config.MapProvider = GoogleMapProvider
|
||||
} else if mapProvider == TomTomMapProvider {
|
||||
config.MapProvider = TomTomMapProvider
|
||||
} else if mapProvider == BaiduMapProvider {
|
||||
config.MapProvider = BaiduMapProvider
|
||||
} else if mapProvider == AmapProvider {
|
||||
config.MapProvider = AmapProvider
|
||||
} else {
|
||||
return errs.ErrInvalidMapProvider
|
||||
}
|
||||
|
||||
config.EnableMapDataFetchProxy = getConfigItemBoolValue(configFile, sectionName, "map_data_fetch_proxy", false)
|
||||
config.TomTomMapAPIKey = getConfigItemStringValue(configFile, sectionName, "tomtom_map_api_key")
|
||||
config.GoogleMapAPIKey = getConfigItemStringValue(configFile, sectionName, "google_map_api_key")
|
||||
config.BaiduMapAK = getConfigItemStringValue(configFile, sectionName, "baidu_map_ak")
|
||||
config.AmapApplicationKey = getConfigItemStringValue(configFile, sectionName, "amap_application_key")
|
||||
|
||||
amapSecurityVerificationMethod := getConfigItemStringValue(configFile, sectionName, "amap_security_verification_method")
|
||||
|
||||
if amapSecurityVerificationMethod == AmapSecurityVerificationInternalProxyMethod {
|
||||
config.AmapSecurityVerificationMethod = AmapSecurityVerificationInternalProxyMethod
|
||||
} else if amapSecurityVerificationMethod == AmapSecurityVerificationExternalProxyMethod {
|
||||
config.AmapSecurityVerificationMethod = AmapSecurityVerificationExternalProxyMethod
|
||||
} else if amapSecurityVerificationMethod == AmapSecurityVerificationPlainTextMethod {
|
||||
config.AmapSecurityVerificationMethod = AmapSecurityVerificationPlainTextMethod
|
||||
} else {
|
||||
return errs.ErrInvalidAmapSecurityVerificationMethod
|
||||
}
|
||||
|
||||
config.AmapApplicationSecret = getConfigItemStringValue(configFile, sectionName, "amap_application_secret")
|
||||
config.AmapApiExternalProxyUrl = getConfigItemStringValue(configFile, sectionName, "amap_api_external_proxy_url")
|
||||
|
||||
return nil
|
||||
}
|
||||
func loadExchangeRatesConfiguration(config *Config, configFile *ini.File, sectionName string) error {
|
||||
if getConfigItemStringValue(configFile, sectionName, "data_source") == EuroCentralBankDataSource {
|
||||
dataSource := getConfigItemStringValue(configFile, sectionName, "data_source")
|
||||
|
||||
if dataSource == EuroCentralBankDataSource {
|
||||
config.ExchangeRatesDataSource = EuroCentralBankDataSource
|
||||
} else if getConfigItemStringValue(configFile, sectionName, "data_source") == BankOfCanadaDataSource {
|
||||
} else if dataSource == BankOfCanadaDataSource {
|
||||
config.ExchangeRatesDataSource = BankOfCanadaDataSource
|
||||
} else if getConfigItemStringValue(configFile, sectionName, "data_source") == ReserveBankOfAustraliaDataSource {
|
||||
} else if dataSource == ReserveBankOfAustraliaDataSource {
|
||||
config.ExchangeRatesDataSource = ReserveBankOfAustraliaDataSource
|
||||
} else if getConfigItemStringValue(configFile, sectionName, "data_source") == CzechNationalBankDataSource {
|
||||
} else if dataSource == CzechNationalBankDataSource {
|
||||
config.ExchangeRatesDataSource = CzechNationalBankDataSource
|
||||
} else if getConfigItemStringValue(configFile, sectionName, "data_source") == NationalBankOfPolandDataSource {
|
||||
} else if dataSource == NationalBankOfPolandDataSource {
|
||||
config.ExchangeRatesDataSource = NationalBankOfPolandDataSource
|
||||
} else if getConfigItemStringValue(configFile, sectionName, "data_source") == MonetaryAuthorityOfSingaporeDataSource {
|
||||
} else if dataSource == MonetaryAuthorityOfSingaporeDataSource {
|
||||
config.ExchangeRatesDataSource = MonetaryAuthorityOfSingaporeDataSource
|
||||
} else {
|
||||
return errs.ErrInvalidExchangeRatesDataSource
|
||||
}
|
||||
|
||||
config.ExchangeRatesRequestTimeout = getConfigItemUint32Value(configFile, sectionName, "request_timeout", defaultExchangeRatesDataRequestTimeout)
|
||||
config.ExchangeRatesSkipTLSVerify = getConfigItemBoolValue(configFile, sectionName, "skip_tls_verify", false)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package templates
|
||||
|
||||
type KnownTemplate string
|
||||
|
||||
// Known templates
|
||||
const (
|
||||
TEMPLATE_VERIFY_EMAIL KnownTemplate = "email/verify_email"
|
||||
TEMPLATE_PASSWORD_RESET KnownTemplate = "email/password_reset"
|
||||
)
|
||||
@@ -0,0 +1,42 @@
|
||||
package templates
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const templateBasePath = "templates"
|
||||
const templateFileExtension = "tmpl"
|
||||
|
||||
var templateCache = make(map[KnownTemplate]*CachedTemplate)
|
||||
|
||||
// CachedTemplate represents a cached template
|
||||
type CachedTemplate struct {
|
||||
templateName KnownTemplate
|
||||
templateContent *template.Template
|
||||
}
|
||||
|
||||
// GetTemplate returns a cached template instance according to the template name
|
||||
func GetTemplate(templateName KnownTemplate) (*template.Template, error) {
|
||||
fullPath := filepath.Join(templateBasePath, fmt.Sprintf("%s.%s", templateName, templateFileExtension))
|
||||
|
||||
cachedTemplate, exists := templateCache[templateName]
|
||||
|
||||
if exists {
|
||||
return cachedTemplate.templateContent, nil
|
||||
}
|
||||
|
||||
tmpl, err := template.ParseFiles(fullPath)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
templateCache[templateName] = &CachedTemplate{
|
||||
templateName: templateName,
|
||||
templateContent: tmpl,
|
||||
}
|
||||
|
||||
return tmpl, err
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
// PrintJsonSuccessResult writes success response in json format to current http context
|
||||
func PrintJsonSuccessResult(c *core.Context, result interface{}) {
|
||||
func PrintJsonSuccessResult(c *core.Context, result any) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"result": result,
|
||||
@@ -45,12 +45,18 @@ func PrintJsonErrorResult(c *core.Context, err *errs.Error) {
|
||||
}
|
||||
}
|
||||
|
||||
c.AbortWithStatusJSON(err.HttpStatusCode, gin.H{
|
||||
result := gin.H{
|
||||
"success": false,
|
||||
"errorCode": err.Code(),
|
||||
"errorMessage": errorMessage,
|
||||
"path": c.Request.URL.Path,
|
||||
})
|
||||
}
|
||||
|
||||
if err.Context != nil {
|
||||
result["context"] = err.Context
|
||||
}
|
||||
|
||||
c.AbortWithStatusJSON(err.HttpStatusCode, result)
|
||||
}
|
||||
|
||||
// PrintDataErrorResult writes error response in custom content type to current http context
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const gravatarUrlFormat = "https://www.gravatar.com/avatar/%s"
|
||||
|
||||
// GetGravatarUrl returns the Gravatar url according to the specified user email address
|
||||
func GetGravatarUrl(email string) string {
|
||||
email = strings.TrimSpace(email)
|
||||
email = strings.ToLower(email)
|
||||
emailMd5 := MD5EncodeToString([]byte(email))
|
||||
return fmt.Sprintf(gravatarUrlFormat, emailMd5)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetGravatarUrl(t *testing.T) {
|
||||
// Reference: https://en.gravatar.com/site/implement/hash/
|
||||
expectedValue := "https://www.gravatar.com/avatar/0bc83cb571cd1c50ba6f3e8a78ef1346"
|
||||
actualValue := GetGravatarUrl("MyEmailAddress@example.com")
|
||||
assert.Equal(t, expectedValue, actualValue)
|
||||
}
|
||||
@@ -13,6 +13,8 @@ const (
|
||||
longDateTimeWithoutSecondFormat = "2006-01-02 15:04"
|
||||
shortDateTimeFormat = "2006-1-2 15:4:5"
|
||||
yearMonthDateTimeFormat = "2006-01"
|
||||
westernmostTimezoneUtcOffset = -720 // Etc/GMT+12 (UTC-12:00)
|
||||
easternmostTimezoneUtcOffset = 840 // Pacific/Kiritimati (UTC+14:00)
|
||||
)
|
||||
|
||||
// FormatUnixTimeToLongDateTimeInServerTimezone returns a textual representation of the unix time formatted by long date time format
|
||||
@@ -42,6 +44,18 @@ func FormatUnixTimeToYearMonth(unixTime int64, timezone *time.Location) string {
|
||||
return t.Format(yearMonthDateTimeFormat)
|
||||
}
|
||||
|
||||
// ParseFromLongDateTimeToMinUnixTime parses a formatted string in long date time format to minimal unix time (the westernmost timezone)
|
||||
func ParseFromLongDateTimeToMinUnixTime(t string) (time.Time, error) {
|
||||
timezone := time.FixedZone("Timezone", easternmostTimezoneUtcOffset*60)
|
||||
return time.ParseInLocation(longDateTimeFormat, t, timezone)
|
||||
}
|
||||
|
||||
// ParseFromLongDateTimeToMaxUnixTime parses a formatted string in long date time format to maximal unix time (the easternmost timezone)
|
||||
func ParseFromLongDateTimeToMaxUnixTime(t string) (time.Time, error) {
|
||||
timezone := time.FixedZone("Timezone", westernmostTimezoneUtcOffset*60)
|
||||
return time.ParseInLocation(longDateTimeFormat, t, timezone)
|
||||
}
|
||||
|
||||
// ParseFromLongDateTime parses a formatted string in long date time format
|
||||
func ParseFromLongDateTime(t string, utcOffset int16) (time.Time, error) {
|
||||
timezone := time.FixedZone("Timezone", int(utcOffset)*60)
|
||||
@@ -59,6 +73,30 @@ func ParseFromShortDateTime(t string, utcOffset int16) (time.Time, error) {
|
||||
return time.ParseInLocation(shortDateTimeFormat, t, timezone)
|
||||
}
|
||||
|
||||
func ParseFromElapsedSeconds(elapsedSeconds int) (string, error) {
|
||||
if elapsedSeconds < 0 || elapsedSeconds >= 86400 {
|
||||
return "", errs.ErrFormatInvalid
|
||||
}
|
||||
|
||||
second := elapsedSeconds % 60
|
||||
elapsedSeconds = elapsedSeconds - second
|
||||
elapsedSeconds = elapsedSeconds / 60
|
||||
|
||||
minute := elapsedSeconds % 60
|
||||
elapsedSeconds = elapsedSeconds - minute
|
||||
elapsedSeconds = elapsedSeconds / 60
|
||||
|
||||
hour := elapsedSeconds
|
||||
|
||||
return fmt.Sprintf("%02d:%02d:%02d", hour, minute, second), nil
|
||||
}
|
||||
|
||||
// IsUnixTimeEqualsYearAndMonth returns whether year and month of the unix time are equals to the specified year and month
|
||||
func IsUnixTimeEqualsYearAndMonth(unixTime int64, timezone *time.Location, year int32, month int32) bool {
|
||||
date := parseFromUnixTime(unixTime).In(timezone)
|
||||
return date.Year() == int(year) && int(date.Month()) == int(month)
|
||||
}
|
||||
|
||||
// GetTimezoneOffsetMinutes returns offset minutes according specified timezone
|
||||
func GetTimezoneOffsetMinutes(timezone *time.Location) int16 {
|
||||
_, tzOffset := time.Now().In(timezone).Zone()
|
||||
|
||||
@@ -35,6 +35,24 @@ func TestFormatUnixTimeToYearMonth(t *testing.T) {
|
||||
assert.Equal(t, expectedValue, actualValue)
|
||||
}
|
||||
|
||||
func TestParseFromLongDateTimeToMinUnixTime(t *testing.T) {
|
||||
expectedValue := int64(1690797600)
|
||||
actualTime, err := ParseFromLongDateTimeToMinUnixTime("2023-08-01 00:00:00")
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
actualValue := actualTime.Unix()
|
||||
assert.Equal(t, expectedValue, actualValue)
|
||||
}
|
||||
|
||||
func TestParseFromLongDateTimeToMaxUnixTime(t *testing.T) {
|
||||
expectedValue := int64(1690891200)
|
||||
actualTime, err := ParseFromLongDateTimeToMaxUnixTime("2023-08-01 00:00:00")
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
actualValue := actualTime.Unix()
|
||||
assert.Equal(t, expectedValue, actualValue)
|
||||
}
|
||||
|
||||
func TestParseFromLongDateTime(t *testing.T) {
|
||||
expectedValue := int64(1617228083)
|
||||
actualTime, err := ParseFromLongDateTime("2021-04-01 06:01:23", 480)
|
||||
@@ -44,6 +62,15 @@ func TestParseFromLongDateTime(t *testing.T) {
|
||||
assert.Equal(t, expectedValue, actualValue)
|
||||
}
|
||||
|
||||
func TestParseFromLongDateTimeWithoutSecond(t *testing.T) {
|
||||
expectedValue := int64(1691947440)
|
||||
actualTime, err := ParseFromLongDateTimeWithoutSecond("2023-08-13 17:24", time.UTC)
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
actualValue := actualTime.Unix()
|
||||
assert.Equal(t, expectedValue, actualValue)
|
||||
}
|
||||
|
||||
func TestParseFromShortDateTime(t *testing.T) {
|
||||
expectedValue := int64(1617228083)
|
||||
actualTime, err := ParseFromShortDateTime("2021-4-1 6:1:23", 480)
|
||||
@@ -53,6 +80,49 @@ func TestParseFromShortDateTime(t *testing.T) {
|
||||
assert.Equal(t, expectedValue, actualValue)
|
||||
}
|
||||
|
||||
func TestParseFromElapsedSeconds(t *testing.T) {
|
||||
expectedValue := "00:00:00"
|
||||
actualValue, err := ParseFromElapsedSeconds(0)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, expectedValue, actualValue)
|
||||
|
||||
expectedValue = "00:00:09"
|
||||
actualValue, err = ParseFromElapsedSeconds(9)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, expectedValue, actualValue)
|
||||
|
||||
expectedValue = "00:01:08"
|
||||
actualValue, err = ParseFromElapsedSeconds(68)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, expectedValue, actualValue)
|
||||
|
||||
expectedValue = "01:00:07"
|
||||
actualValue, err = ParseFromElapsedSeconds(3607)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, expectedValue, actualValue)
|
||||
|
||||
expectedValue = "23:59:59"
|
||||
actualValue, err = ParseFromElapsedSeconds(86399)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, expectedValue, actualValue)
|
||||
}
|
||||
|
||||
func TestParseFromElapsedSeconds_InvalidTime(t *testing.T) {
|
||||
_, err := ParseFromElapsedSeconds(-1)
|
||||
assert.NotEqual(t, nil, err)
|
||||
|
||||
_, err = ParseFromElapsedSeconds(86400)
|
||||
assert.NotEqual(t, nil, err)
|
||||
}
|
||||
|
||||
func TestIsUnixTimeEqualsYearAndMonth(t *testing.T) {
|
||||
actualValue := IsUnixTimeEqualsYearAndMonth(1691947440, time.UTC, 2023, 8)
|
||||
assert.Equal(t, true, actualValue)
|
||||
|
||||
actualValue = IsUnixTimeEqualsYearAndMonth(1690847999, time.UTC, 2023, 8)
|
||||
assert.Equal(t, false, actualValue)
|
||||
}
|
||||
|
||||
func TestGetTimezoneOffsetMinutes(t *testing.T) {
|
||||
timezone := time.FixedZone("Test Timezone", 120*60)
|
||||
expectedValue := int16(120)
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5/request"
|
||||
)
|
||||
|
||||
// CookieExtractor extracts a token from request cookies
|
||||
type CookieExtractor []string
|
||||
|
||||
func (e CookieExtractor) ExtractToken(req *http.Request) (string, error) {
|
||||
for _, arg := range e {
|
||||
if cookie, _ := req.Cookie(arg); cookie != nil {
|
||||
return cookie.Value, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", request.ErrNoTokenInRequest
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
// Clone deep-clones src object to dst object
|
||||
func Clone(src, dst interface{}) error {
|
||||
func Clone(src, dst any) error {
|
||||
var buf bytes.Buffer
|
||||
|
||||
if err := gob.NewEncoder(&buf).Encode(src); err != nil {
|
||||
@@ -25,7 +25,7 @@ func Clone(src, dst interface{}) error {
|
||||
}
|
||||
|
||||
// PrintObjectFields prints all fields in specified object
|
||||
func PrintObjectFields(obj interface{}) {
|
||||
func PrintObjectFields(obj any) {
|
||||
if obj == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
@@ -133,6 +134,12 @@ func MD5Encode(data []byte) []byte {
|
||||
return m.Sum(nil)
|
||||
}
|
||||
|
||||
// MD5EncodeToString returns a hashed string by md5
|
||||
func MD5EncodeToString(data []byte) string {
|
||||
hash := MD5Encode(data)
|
||||
return hex.EncodeToString(hash)
|
||||
}
|
||||
|
||||
// AESGCMEncrypt returns a encrypted string by aes-gcm
|
||||
func AESGCMEncrypt(key []byte, plainText []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
|
||||
@@ -72,6 +72,16 @@ func TestSubString_OverBoundary(t *testing.T) {
|
||||
assert.Equal(t, expectedValue, actualValue)
|
||||
}
|
||||
|
||||
func TestGetFirstLowerCharString(t *testing.T) {
|
||||
expectedValue := "fooBar"
|
||||
actualValue := GetFirstLowerCharString("fooBar")
|
||||
assert.Equal(t, expectedValue, actualValue)
|
||||
|
||||
expectedValue = "fooBar"
|
||||
actualValue = GetFirstLowerCharString("FooBar")
|
||||
assert.Equal(t, expectedValue, actualValue)
|
||||
}
|
||||
|
||||
func TestGetRandomString(t *testing.T) {
|
||||
actualValue, err := GetRandomString(10)
|
||||
assert.Equal(t, nil, err)
|
||||
@@ -103,6 +113,26 @@ func TestMD5Encode(t *testing.T) {
|
||||
assert.Equal(t, expectedValue, actualValue)
|
||||
}
|
||||
|
||||
func TestMD5EncodeToString(t *testing.T) {
|
||||
str := "foobar"
|
||||
expectedValue := "3858f62230ac3c915f300c664312c63f"
|
||||
actualValue := MD5EncodeToString([]byte(str))
|
||||
assert.Equal(t, expectedValue, actualValue)
|
||||
|
||||
str = ""
|
||||
expectedValue = "d41d8cd98f00b204e9800998ecf8427e"
|
||||
actualValue = MD5EncodeToString([]byte(str))
|
||||
assert.Equal(t, expectedValue, actualValue)
|
||||
}
|
||||
|
||||
func TestEncodePassword(t *testing.T) {
|
||||
password := "foobar"
|
||||
salt := "salt"
|
||||
expectedValue := "QrpKShMygoe4Ym4ibnA7cNDzCcSonBkgFl69IrtnDmp3oROft3/Td/DNXjsweosa"
|
||||
actualValue := EncodePassword(password, salt)
|
||||
assert.Equal(t, expectedValue, actualValue)
|
||||
}
|
||||
|
||||
func TestEncryptSecretAndDecryptSecret(t *testing.T) {
|
||||
str := "foo"
|
||||
key := "bar"
|
||||
|
||||
@@ -35,7 +35,7 @@ type InternalUuidInfo struct {
|
||||
|
||||
// InternalUuidGenerator represents internal bundled uuid generator
|
||||
type InternalUuidGenerator struct {
|
||||
uuidSeqNumbers [1 << internalUuidTypeBits]uint64
|
||||
uuidSeqNumbers [1 << internalUuidTypeBits]atomic.Uint64
|
||||
uuidServerId uint8
|
||||
}
|
||||
|
||||
@@ -51,6 +51,11 @@ func NewInternalUuidGenerator(config *settings.Config) (*InternalUuidGenerator,
|
||||
// GenerateUuid generates a new uuid
|
||||
func (u *InternalUuidGenerator) GenerateUuid(idType UuidType) int64 {
|
||||
uuids := u.GenerateUuids(idType, 1)
|
||||
|
||||
if len(uuids) < 1 {
|
||||
return 0
|
||||
}
|
||||
|
||||
return uuids[0]
|
||||
}
|
||||
|
||||
@@ -71,32 +76,40 @@ func (u *InternalUuidGenerator) GenerateUuids(idType UuidType, count uint8) []in
|
||||
|
||||
for {
|
||||
unixTime = uint64(time.Now().Unix())
|
||||
newLastSeqId = atomic.AddUint64(&u.uuidSeqNumbers[uuidType], uint64(count))
|
||||
newLastSeqId = u.uuidSeqNumbers[uuidType].Add(uint64(count))
|
||||
newSeqUnixTime := newLastSeqId >> seqNumberIdBits
|
||||
|
||||
if newLastSeqId>>seqNumberIdBits == unixTime {
|
||||
if unixTime == newSeqUnixTime {
|
||||
newFirstSeqId = newLastSeqId - uint64(count-1)
|
||||
break
|
||||
} else if unixTime < newSeqUnixTime {
|
||||
continue
|
||||
}
|
||||
|
||||
currentSeqId := newLastSeqId
|
||||
newFirstSeqId = unixTime << seqNumberIdBits
|
||||
newLastSeqId = newFirstSeqId + uint64(count-1)
|
||||
|
||||
if atomic.CompareAndSwapUint64(&u.uuidSeqNumbers[uuidType], currentSeqId, newLastSeqId) {
|
||||
if u.uuidSeqNumbers[uuidType].CompareAndSwap(currentSeqId, newLastSeqId) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < int(count); i++ {
|
||||
seqId := (newFirstSeqId + uint64(i)) & seqNumberIdMask
|
||||
|
||||
// the internal uuid generator can only generate 524,287 ids per second for specified type
|
||||
if seqId > internalUuidSeqIdMask {
|
||||
return nil
|
||||
}
|
||||
|
||||
uuids[i] = u.assembleUuid(unixTime, uuidType, seqId)
|
||||
}
|
||||
|
||||
return uuids
|
||||
}
|
||||
|
||||
// ParseUuidInfo returns a info struct which contains all information in uuid
|
||||
func (u *InternalUuidGenerator) ParseUuidInfo(uuid int64) *InternalUuidInfo {
|
||||
func (u *InternalUuidGenerator) parseInternalUuidInfo(uuid int64) *InternalUuidInfo {
|
||||
seqId := uint32(uuid & internalUuidSeqIdMask)
|
||||
uuid = uuid >> internalUuidSeqIdBits
|
||||
|
||||
|
||||
@@ -12,16 +12,21 @@ import (
|
||||
)
|
||||
|
||||
func TestGenerateUuid(t *testing.T) {
|
||||
expectedUnixTime := time.Now().Unix()
|
||||
expectedUuidServerId := uint8(90)
|
||||
expectedUuidType := UUID_TYPE_TRANSACTION
|
||||
|
||||
generator, _ := NewInternalUuidGenerator(&settings.Config{UuidServerId: expectedUuidServerId})
|
||||
uuid := generator.GenerateUuid(expectedUuidType)
|
||||
uuidInfo := generator.ParseUuidInfo(uuid)
|
||||
|
||||
actualUnixTime := uuidInfo.UnixTime
|
||||
assert.Equal(t, uint32(expectedUnixTime), actualUnixTime)
|
||||
generationStartUnixTime := time.Now().Unix()
|
||||
uuid := generator.GenerateUuid(expectedUuidType)
|
||||
generationEndUnixTime := time.Now().Unix()
|
||||
|
||||
uuidInfo := generator.parseInternalUuidInfo(uuid)
|
||||
|
||||
if generationStartUnixTime == generationEndUnixTime {
|
||||
actualUnixTime := uuidInfo.UnixTime
|
||||
assert.Equal(t, uint32(generationStartUnixTime), actualUnixTime)
|
||||
}
|
||||
|
||||
actualUuidServerId := uuidInfo.UuidServerId
|
||||
assert.Equal(t, expectedUuidServerId, actualUuidServerId)
|
||||
@@ -37,43 +42,53 @@ func TestGenerateUuid(t *testing.T) {
|
||||
func TestGenerateUuid_MultiType(t *testing.T) {
|
||||
generator, _ := NewInternalUuidGenerator(&settings.Config{UuidServerId: 1})
|
||||
uuid := generator.GenerateUuid(UUID_TYPE_USER)
|
||||
uuidInfo := generator.ParseUuidInfo(uuid)
|
||||
uuidInfo := generator.parseInternalUuidInfo(uuid)
|
||||
|
||||
expectedSeqId := 0
|
||||
actualSeqId := uuidInfo.SequentialId
|
||||
assert.Equal(t, uint32(expectedSeqId), actualSeqId)
|
||||
|
||||
uuid = generator.GenerateUuid(UUID_TYPE_ACCOUNT)
|
||||
uuidInfo = generator.ParseUuidInfo(uuid)
|
||||
uuidInfo = generator.parseInternalUuidInfo(uuid)
|
||||
actualSeqId = uuidInfo.SequentialId
|
||||
assert.Equal(t, uint32(expectedSeqId), actualSeqId)
|
||||
|
||||
uuid = generator.GenerateUuid(UUID_TYPE_TRANSACTION)
|
||||
uuidInfo = generator.ParseUuidInfo(uuid)
|
||||
uuidInfo = generator.parseInternalUuidInfo(uuid)
|
||||
actualSeqId = uuidInfo.SequentialId
|
||||
assert.Equal(t, uint32(expectedSeqId), actualSeqId)
|
||||
}
|
||||
|
||||
func TestGenerateUuid_2000TimesIn2Seconds(t *testing.T) {
|
||||
generator, _ := NewInternalUuidGenerator(&settings.Config{UuidServerId: 2})
|
||||
expectedUnixTime := time.Now().Unix()
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
generationStartUnixTime := time.Now().Unix()
|
||||
uuid := generator.GenerateUuid(UUID_TYPE_USER)
|
||||
uuidInfo := generator.ParseUuidInfo(uuid)
|
||||
generationEndUnixTime := time.Now().Unix()
|
||||
|
||||
uuidInfo := generator.parseInternalUuidInfo(uuid)
|
||||
|
||||
if generationStartUnixTime == generationEndUnixTime {
|
||||
assert.Equal(t, uint32(generationStartUnixTime), uuidInfo.UnixTime)
|
||||
}
|
||||
|
||||
assert.Equal(t, uint32(expectedUnixTime), uuidInfo.UnixTime)
|
||||
assert.Equal(t, uint32(i), uuidInfo.SequentialId)
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
expectedUnixTime = time.Now().Unix()
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
generationStartUnixTime := time.Now().Unix()
|
||||
uuid := generator.GenerateUuid(UUID_TYPE_USER)
|
||||
uuidInfo := generator.ParseUuidInfo(uuid)
|
||||
generationEndUnixTime := time.Now().Unix()
|
||||
|
||||
uuidInfo := generator.parseInternalUuidInfo(uuid)
|
||||
|
||||
if generationStartUnixTime == generationEndUnixTime {
|
||||
assert.Equal(t, uint32(generationStartUnixTime), uuidInfo.UnixTime)
|
||||
}
|
||||
|
||||
assert.Equal(t, uint32(expectedUnixTime), uuidInfo.UnixTime)
|
||||
assert.Equal(t, uint32(i), uuidInfo.SequentialId)
|
||||
}
|
||||
}
|
||||
@@ -86,21 +101,77 @@ func TestGenerateUuid_10000TimesConcurrent(t *testing.T) {
|
||||
var waitGroup sync.WaitGroup
|
||||
|
||||
for routineIndex := 0; routineIndex < concurrentCount; routineIndex++ {
|
||||
waitGroup.Add(1)
|
||||
|
||||
go func(currentRoutineIndex int) {
|
||||
waitGroup.Add(1)
|
||||
|
||||
for cycle := 0; cycle < 1000; cycle++ {
|
||||
expectedUnixTime := time.Now().Unix()
|
||||
generationStartUnixTime := time.Now().Unix()
|
||||
uuid := generator.GenerateUuid(UUID_TYPE_USER)
|
||||
uuidInfo := generator.ParseUuidInfo(uuid)
|
||||
generationEndUnixTime := time.Now().Unix()
|
||||
|
||||
if uint32(expectedUnixTime) != uuidInfo.UnixTime {
|
||||
mutex.Lock()
|
||||
assert.Equal(t, uint32(expectedUnixTime), uuidInfo.UnixTime)
|
||||
mutex.Unlock()
|
||||
uuidInfo := generator.parseInternalUuidInfo(uuid)
|
||||
|
||||
if generationStartUnixTime == generationEndUnixTime {
|
||||
if uint32(generationStartUnixTime) != uuidInfo.UnixTime {
|
||||
mutex.Lock()
|
||||
assert.Equal(t, uint32(generationStartUnixTime), uuidInfo.UnixTime)
|
||||
mutex.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
if uuidInfo.SequentialId == 0 {
|
||||
fmt.Printf("routine#%d generate uuid %d\n", currentRoutineIndex, uuid)
|
||||
|
||||
if existedRoutineIndex, exists := generatedIds.Load(uuid); exists {
|
||||
mutex.Lock()
|
||||
assert.Fail(t, fmt.Sprintf("uuid \"%d\" conflicts, unix time is %d, seq id is %d, existed routine index is %d, current routine index is %d", uuid, uuidInfo.UnixTime, uuidInfo.SequentialId, existedRoutineIndex, currentRoutineIndex))
|
||||
mutex.Unlock()
|
||||
}
|
||||
|
||||
generatedIds.Store(uuid, currentRoutineIndex)
|
||||
}
|
||||
}
|
||||
|
||||
waitGroup.Done()
|
||||
}(routineIndex)
|
||||
}
|
||||
|
||||
waitGroup.Wait()
|
||||
}
|
||||
|
||||
func TestGenerateUuid_1000000TimesConcurrent(t *testing.T) {
|
||||
concurrentCount := 10
|
||||
generator, _ := NewInternalUuidGenerator(&settings.Config{UuidServerId: 3})
|
||||
var mutex sync.Mutex
|
||||
var generatedIds sync.Map
|
||||
var waitGroup sync.WaitGroup
|
||||
|
||||
for routineIndex := 0; routineIndex < concurrentCount; routineIndex++ {
|
||||
waitGroup.Add(1)
|
||||
|
||||
go func(currentRoutineIndex int) {
|
||||
for cycle := 0; cycle < 100000; cycle++ {
|
||||
if cycle%50000 == 0 { // each server can only generate 500,000 (10 * 50000) uuids in one second
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
}
|
||||
|
||||
generationStartUnixTime := time.Now().Unix()
|
||||
uuid := generator.GenerateUuid(UUID_TYPE_USER)
|
||||
generationEndUnixTime := time.Now().Unix()
|
||||
|
||||
uuidInfo := generator.parseInternalUuidInfo(uuid)
|
||||
|
||||
if generationStartUnixTime == generationEndUnixTime {
|
||||
if uint32(generationStartUnixTime) != uuidInfo.UnixTime {
|
||||
mutex.Lock()
|
||||
assert.Equal(t, uint32(generationStartUnixTime), uuidInfo.UnixTime)
|
||||
mutex.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
if uuidInfo.SequentialId == 0 {
|
||||
fmt.Printf("routine#%d generate uuid %d\n", currentRoutineIndex, uuid)
|
||||
|
||||
if existedRoutineIndex, exists := generatedIds.Load(uuid); exists {
|
||||
mutex.Lock()
|
||||
assert.Fail(t, fmt.Sprintf("uuid \"%d\" conflicts, unix time is %d, seq id is %d, existed routine index is %d, current routine index is %d", uuid, uuidInfo.UnixTime, uuidInfo.SequentialId, existedRoutineIndex, currentRoutineIndex))
|
||||
@@ -130,19 +201,23 @@ func TestGenerateUuids_Count0(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGenerateUuids_Count255(t *testing.T) {
|
||||
expectedUnixTime := time.Now().Unix()
|
||||
expectedUuidServerId := uint8(90)
|
||||
expectedUuidType := UUID_TYPE_TRANSACTION
|
||||
expectedUuidCount := uint8(255)
|
||||
|
||||
generator, _ := NewInternalUuidGenerator(&settings.Config{UuidServerId: expectedUuidServerId})
|
||||
|
||||
generationStartUnixTime := time.Now().Unix()
|
||||
uuids := generator.GenerateUuids(expectedUuidType, expectedUuidCount)
|
||||
generationEndUnixTime := time.Now().Unix()
|
||||
|
||||
for i := 0; i < int(expectedUuidCount); i++ {
|
||||
uuidInfo := generator.ParseUuidInfo(uuids[i])
|
||||
uuidInfo := generator.parseInternalUuidInfo(uuids[i])
|
||||
|
||||
actualUnixTime := uuidInfo.UnixTime
|
||||
assert.Equal(t, uint32(expectedUnixTime), actualUnixTime)
|
||||
if generationStartUnixTime == generationEndUnixTime {
|
||||
actualUnixTime := uuidInfo.UnixTime
|
||||
assert.Equal(t, uint32(generationStartUnixTime), actualUnixTime)
|
||||
}
|
||||
|
||||
actualUuidServerId := uuidInfo.UuidServerId
|
||||
assert.Equal(t, expectedUuidServerId, actualUuidServerId)
|
||||
@@ -166,15 +241,18 @@ func TestGenerateUuids_30TimesIn3Seconds(t *testing.T) {
|
||||
generator, _ := NewInternalUuidGenerator(&settings.Config{UuidServerId: expectedUuidServerId})
|
||||
|
||||
for cycle := 0; cycle < 30; cycle++ {
|
||||
expectedUnixTime := time.Now().Unix()
|
||||
generationStartUnixTime := time.Now().Unix()
|
||||
uuids := generator.GenerateUuids(expectedUuidType, expectedUuidCount)
|
||||
generationEndUnixTime := time.Now().Unix()
|
||||
var firstSeqId uint32
|
||||
|
||||
for i := 0; i < int(expectedUuidCount); i++ {
|
||||
uuidInfo := generator.ParseUuidInfo(uuids[i])
|
||||
uuidInfo := generator.parseInternalUuidInfo(uuids[i])
|
||||
|
||||
actualUnixTime := uuidInfo.UnixTime
|
||||
assert.Equal(t, uint32(expectedUnixTime), actualUnixTime)
|
||||
if generationStartUnixTime == generationEndUnixTime {
|
||||
actualUnixTime := uuidInfo.UnixTime
|
||||
assert.Equal(t, uint32(generationStartUnixTime), actualUnixTime)
|
||||
}
|
||||
|
||||
actualUuidServerId := uuidInfo.UuidServerId
|
||||
assert.Equal(t, expectedUuidServerId, actualUuidServerId)
|
||||
@@ -204,29 +282,36 @@ func TestGenerateUuids_20000TimesConcurrent(t *testing.T) {
|
||||
var waitGroup sync.WaitGroup
|
||||
|
||||
for routineIndex := 0; routineIndex < concurrentCount; routineIndex++ {
|
||||
go func(currentRoutineIndex int) {
|
||||
waitGroup.Add(1)
|
||||
waitGroup.Add(1)
|
||||
|
||||
go func(currentRoutineIndex int) {
|
||||
for cycle := 0; cycle < 100; cycle++ {
|
||||
expectedUnixTime := time.Now().Unix()
|
||||
generationStartUnixTime := time.Now().Unix()
|
||||
uuids := generator.GenerateUuids(UUID_TYPE_USER, expectedUuidCount)
|
||||
generationEndUnixTime := time.Now().Unix()
|
||||
|
||||
for i := 0; i < int(expectedUuidCount); i++ {
|
||||
uuidInfo := generator.ParseUuidInfo(uuids[i])
|
||||
uuidInfo := generator.parseInternalUuidInfo(uuids[i])
|
||||
|
||||
if uint32(expectedUnixTime) != uuidInfo.UnixTime {
|
||||
mutex.Lock()
|
||||
assert.Equal(t, uint32(expectedUnixTime), uuidInfo.UnixTime)
|
||||
mutex.Unlock()
|
||||
if generationStartUnixTime == generationEndUnixTime {
|
||||
if uint32(generationStartUnixTime) != uuidInfo.UnixTime {
|
||||
mutex.Lock()
|
||||
assert.Equal(t, uint32(generationStartUnixTime), uuidInfo.UnixTime)
|
||||
mutex.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
if existedRoutineIndex, exists := generatedIds.Load(uuids[i]); exists {
|
||||
mutex.Lock()
|
||||
assert.Fail(t, fmt.Sprintf("uuid \"%d\" conflicts, unix time is %d, seq id is %d, existed routine index is %d, current routine index is %d", uuids[i], uuidInfo.UnixTime, uuidInfo.SequentialId, existedRoutineIndex, currentRoutineIndex))
|
||||
mutex.Unlock()
|
||||
}
|
||||
if uuidInfo.SequentialId == 0 {
|
||||
fmt.Printf("routine#%d generate uuid %d\n", currentRoutineIndex, uuids[i])
|
||||
|
||||
generatedIds.Store(uuids[i], currentRoutineIndex)
|
||||
if existedRoutineIndex, exists := generatedIds.Load(uuids[i]); exists {
|
||||
mutex.Lock()
|
||||
assert.Fail(t, fmt.Sprintf("uuid \"%d\" conflicts, unix time is %d, seq id is %d, existed routine index is %d, current routine index is %d", uuids[i], uuidInfo.UnixTime, uuidInfo.SequentialId, existedRoutineIndex, currentRoutineIndex))
|
||||
mutex.Unlock()
|
||||
}
|
||||
|
||||
generatedIds.Store(uuids[i], currentRoutineIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,3 +321,78 @@ func TestGenerateUuids_20000TimesConcurrent(t *testing.T) {
|
||||
|
||||
waitGroup.Wait()
|
||||
}
|
||||
|
||||
func TestGenerateUuids_1000000TimesConcurrent(t *testing.T) {
|
||||
concurrentCount := 10
|
||||
expectedUuidCount := uint8(250)
|
||||
generator, _ := NewInternalUuidGenerator(&settings.Config{UuidServerId: 3})
|
||||
var mutex sync.Mutex
|
||||
var generatedIds sync.Map
|
||||
var waitGroup sync.WaitGroup
|
||||
|
||||
for routineIndex := 0; routineIndex < concurrentCount; routineIndex++ {
|
||||
waitGroup.Add(1)
|
||||
|
||||
go func(currentRoutineIndex int) {
|
||||
for cycle := 0; cycle < 400; cycle++ {
|
||||
if cycle%200 == 0 { // each server can only generate 500,000 (10 * 250 * 200) uuids in one second
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
}
|
||||
|
||||
generationStartUnixTime := time.Now().Unix()
|
||||
uuids := generator.GenerateUuids(UUID_TYPE_USER, expectedUuidCount)
|
||||
generationEndUnixTime := time.Now().Unix()
|
||||
|
||||
for i := 0; i < int(expectedUuidCount); i++ {
|
||||
uuidInfo := generator.parseInternalUuidInfo(uuids[i])
|
||||
|
||||
if generationStartUnixTime == generationEndUnixTime {
|
||||
if uint32(generationStartUnixTime) != uuidInfo.UnixTime {
|
||||
mutex.Lock()
|
||||
assert.Equal(t, uint32(generationStartUnixTime), uuidInfo.UnixTime)
|
||||
mutex.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
if uuidInfo.SequentialId == 0 {
|
||||
fmt.Printf("routine#%d generate uuid %d\n", currentRoutineIndex, uuids[i])
|
||||
|
||||
if existedRoutineIndex, exists := generatedIds.Load(uuids[i]); exists {
|
||||
mutex.Lock()
|
||||
assert.Fail(t, fmt.Sprintf("uuid \"%d\" conflicts, unix time is %d, seq id is %d, existed routine index is %d, current routine index is %d", uuids[i], uuidInfo.UnixTime, uuidInfo.SequentialId, existedRoutineIndex, currentRoutineIndex))
|
||||
mutex.Unlock()
|
||||
}
|
||||
|
||||
generatedIds.Store(uuids[i], currentRoutineIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
waitGroup.Done()
|
||||
}(routineIndex)
|
||||
}
|
||||
|
||||
waitGroup.Wait()
|
||||
}
|
||||
|
||||
func TestGenerateUuid_Over524287Times(t *testing.T) {
|
||||
generator, _ := NewInternalUuidGenerator(&settings.Config{UuidServerId: 1})
|
||||
onceGenerateCount := uint8(255)
|
||||
generationStartUnixTime := time.Now().Unix()
|
||||
|
||||
for i := 0; i < 2057; i++ { // 2056*255=524280, 2057*255=524,535 (only can generates 524,287 uuids per second)
|
||||
uuids := generator.GenerateUuids(UUID_TYPE_USER, onceGenerateCount)
|
||||
|
||||
if i < 2056 {
|
||||
if len(uuids) < int(onceGenerateCount) {
|
||||
assert.Fail(t, fmt.Sprintf("%d uuids should be generated", onceGenerateCount))
|
||||
}
|
||||
} else {
|
||||
generationEndUnixTime := time.Now().Unix()
|
||||
|
||||
if generationStartUnixTime == generationEndUnixTime && len(uuids) > 0 {
|
||||
assert.Fail(t, fmt.Sprintf("uuids should not be generated because there are too many uuids in one second"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0"?>
|
||||
<svg width="750" height="300" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<g class="layer">
|
||||
<title>Layer 1</title>
|
||||
<path d="m2.38122,215.61524l748.61878,-113.11523l0,197.99999l-750,0l1.38122,-84.88476z" fill="#111111" id="svg_3" stroke="#000000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="0" stroke-width="null"/>
|
||||
<g id="BACKGROUND"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 449 B |
@@ -0,0 +1,73 @@
|
||||
<?xml version="1.0"?>
|
||||
<svg width="750" height="300" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<g class="layer">
|
||||
<title>Layer 1</title>
|
||||
<path d="m2.381218,216.615238l748.618787,-113.11523l0,197.999993l-750.000002,0l1.381215,-84.884763z" fill="#f9f4ea" id="svg_3" stroke="#000000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="0" stroke-width="null"/>
|
||||
<g id="BACKGROUND"/>
|
||||
<g id="svg_1">
|
||||
<path d="m623,300.642881c-63.833,0 -127.667,0 -191.5,0c0.109,-1.4 0.038,-2.844 0.365,-4.191c0.775,-3.196 1.715,-6.353 2.636,-9.511c0.602,-2.062 1.084,-4.195 1.979,-6.129c2.321,-5.011 3.543,-10.485 6.466,-15.235c0.126,-0.205 0.181,-0.471 0.207,-0.716c0.161,-1.56 1.197,-2.67 1.938,-3.947c1.587,-2.735 3.482,-5.229 5.48,-7.668c1.135,-1.386 2.09,-2.918 3.127,-4.385c0.878,0.766 1.792,1.494 2.627,2.303c3.598,3.486 7.44,6.677 11.787,9.17c6.411,3.678 13.171,5.353 20.6,3.493c4.237,-1.061 8.191,-2.676 12.041,-4.69c7.177,-3.755 13.324,-8.74 18.465,-14.989c0.938,-1.14 1.95,-2.218 2.932,-3.33c1.704,0.772 3.2,1.503 4.737,2.135c5.842,2.405 11.935,3.647 18.246,3.4c5.454,-0.214 10.695,-1.651 15.701,-3.762c10.855,-4.578 21.031,-10.344 30.219,-17.776c0.768,-0.622 1.628,-1.13 2.446,-1.692c2.325,-0.514 4.693,-0.884 6.966,-1.569c6.536,-1.969 12.432,-5.303 18.194,-8.877c2.296,-1.424 4.526,-2.953 6.786,-4.435c2.576,-0.065 5.17,-0.355 7.723,-0.145c4.5,0.369 8.904,1.307 12.737,3.908c2.238,1.519 3.939,3.496 4.736,6.04c-0.14,0.221 -0.167,0.323 -0.227,0.349c-1.063,0.465 -2.143,0.893 -3.194,1.383c-6.055,2.821 -10.479,7.337 -13.259,13.349c-2.424,5.241 -1.382,10.402 1.047,15.386c1.19,2.441 3.62,3.714 6.339,3.293c4.946,-0.766 8.461,-3.775 11.243,-7.64c3.751,-5.209 4.589,-11.135 3.601,-17.373c-0.335,-2.116 -0.785,-4.215 -1.232,-6.582c4.976,-0.537 9.83,-1.131 14.698,-1.569c6.83,-0.614 13.517,-1.964 19.931,-4.322c7.326,-2.694 14.531,-5.719 21.76,-8.672c7.81,-3.19 14.893,-7.747 22.164,-11.949c6.273,-3.625 11.789,-8.086 15.927,-14.114c0.129,-0.188 0.372,-0.296 0.562,-0.442c0,14.833 0,29.667 0,44.5c-2.472,0.148 -4.942,0.406 -7.415,0.423c-6.998,0.048 -13.85,1.277 -20.66,2.647c-6.182,1.243 -12.155,3.319 -18.031,5.599c-4.17,1.619 -8.171,3.69 -12.377,5.197c-6.222,2.23 -11.549,6.109 -17.404,9.01c-6.341,3.141 -11.549,7.948 -17.087,12.288c-6.142,4.813 -11.179,10.666 -16.643,16.128c-2.606,2.605 -4.496,5.696 -6.591,8.626c-3.688,5.156 -7.004,10.578 -10.453,15.904c-0.214,0.329 -0.232,0.783 -0.34,1.179z" fill="#FEDBD0" id="svg_4"/>
|
||||
<path d="m623,300.642881c0.108,-0.396 0.126,-0.85 0.338,-1.178c3.449,-5.325 6.766,-10.747 10.453,-15.904c2.095,-2.93 3.985,-6.021 6.591,-8.626c5.464,-5.462 10.501,-11.315 16.643,-16.128c5.538,-4.34 10.746,-9.147 17.087,-12.288c5.855,-2.901 11.182,-6.78 17.404,-9.01c4.206,-1.507 8.207,-3.578 12.377,-5.197c5.876,-2.281 11.849,-4.356 18.031,-5.599c6.81,-1.37 13.662,-2.598 20.66,-2.647c2.472,-0.017 4.943,-0.275 7.415,-0.423c0,25.667 0,51.333 0,77c-42.332,0 -84.666,0 -126.999,0zm53.375,-36.965c1.139,-0.251 2.619,-0.443 3.985,-0.943c0.563,-0.206 1.229,-1.084 1.215,-1.641c-0.013,-0.519 -0.836,-1.415 -1.355,-1.454c-2.359,-0.18 -4.741,-0.217 -7.104,-0.089c-0.962,0.052 -1.502,0.973 -1.518,1.987c-0.016,0.99 0.656,1.517 1.505,1.71c0.952,0.218 1.942,0.265 3.272,0.43zm31.44,-5.633c-1.287,0.123 -2.369,0.146 -3.416,0.349c-0.971,0.189 -1.705,0.856 -1.602,1.897c0.107,1.09 0.798,1.832 2.005,1.764c2.388,-0.135 4.776,-0.288 7.159,-0.498c0.801,-0.071 1.394,-0.547 1.424,-1.445c0.031,-0.915 -0.549,-1.353 -1.359,-1.482c-1.467,-0.232 -2.94,-0.411 -4.211,-0.585zm18.439,-17.441c1.112,-0.215 2.701,-0.371 4.167,-0.88c0.615,-0.213 1.278,-1.134 1.369,-1.807c0.134,-0.982 -0.882,-1.442 -1.701,-1.451c-2.232,-0.024 -4.478,0.013 -6.689,0.279c-0.592,0.071 -1.399,0.978 -1.532,1.621c-0.188,0.906 0.367,1.814 1.446,2.009c0.808,0.145 1.643,0.135 2.94,0.229zm-28.051,11.011c0.01,0.05 0.02,0.1 0.03,0.149c1.375,-0.248 2.772,-0.412 4.117,-0.773c0.882,-0.237 1.453,-1.041 1.088,-1.923c-0.234,-0.566 -0.959,-1.235 -1.526,-1.297c-1.551,-0.169 -3.138,-0.088 -4.705,-0.005c-0.552,0.029 -1.29,0.196 -1.585,0.577c-0.46,0.595 -0.888,1.461 -0.802,2.145c0.056,0.438 1.022,0.872 1.655,1.091c0.52,0.178 1.149,0.036 1.728,0.036zm16.873,29.366c0.595,-0.074 1.654,-0.193 2.711,-0.339c1.004,-0.139 1.888,-0.673 1.714,-1.707c-0.109,-0.646 -0.848,-1.376 -1.491,-1.706c-1.902,-0.976 -3.9,-0.309 -5.789,0.143c-0.519,0.124 -1.159,1.292 -1.087,1.904c0.073,0.618 0.87,1.313 1.518,1.651c0.518,0.27 1.289,0.054 2.424,0.054zm-62.107,-1.702c0.992,-0.141 2.333,-0.217 3.6,-0.565c0.519,-0.142 1.203,-0.862 1.214,-1.333c0.01,-0.437 -0.739,-1.228 -1.219,-1.282c-2.214,-0.249 -4.455,-0.413 -6.676,-0.329c-0.544,0.02 -1.336,1.018 -1.5,1.692c-0.23,0.941 0.638,1.49 1.508,1.636c0.896,0.15 1.823,0.117 3.073,0.181zm56.714,10.81c-0.95,0.101 -1.623,0.107 -2.263,0.255c-0.974,0.227 -1.67,0.848 -1.699,1.888c-0.03,1.088 0.736,1.636 1.698,1.861c1.885,0.441 3.648,-0.083 5.244,-1.002c0.951,-0.548 0.863,-1.725 -0.147,-2.201c-0.951,-0.447 -2.046,-0.59 -2.833,-0.801zm26.315,-37.502c0.766,-0.263 1.911,-0.507 2.907,-1.034c1.329,-0.703 1.286,-2.119 -0.137,-2.548c-1.368,-0.413 -2.888,-0.43 -4.34,-0.418c-1.058,0.008 -1.957,0.585 -1.937,1.817c0.019,1.175 0.899,1.694 1.904,1.985c0.389,0.112 0.813,0.105 1.603,0.198zm0.345,19.21c0.952,-0.19 1.982,-0.219 2.829,-0.627c0.533,-0.258 1.055,-1.068 1.089,-1.659c0.026,-0.456 -0.629,-1.207 -1.134,-1.38c-0.902,-0.31 -1.919,-0.375 -2.888,-0.376c-2.054,-0.003 -3.157,0.781 -3.079,2.046c0.086,1.385 0.91,1.871 3.183,1.996zm-51.014,9.677c1.121,-0.151 2.125,-0.188 3.072,-0.44c1.06,-0.282 1.275,-1.309 0.548,-2.125c-0.839,-0.942 -4.41,-1.793 -5.578,-1.336c-0.85,0.332 -1.374,0.897 -1.335,1.857c0.039,0.958 0.583,1.513 1.482,1.72c0.639,0.147 1.292,0.233 1.811,0.324zm54.676,11.76c-1.539,-1.969 -3.594,-2.067 -5.636,-1.96c-0.518,0.027 -1.346,0.675 -1.414,1.13c-0.09,0.604 0.254,1.474 0.712,1.921c1.518,1.481 4.853,0.946 6.338,-1.091zm-63.279,1.341c0.587,-0.147 1.677,-0.286 2.637,-0.716c0.486,-0.218 1.047,-0.983 1.011,-1.458c-0.039,-0.507 -0.658,-1.215 -1.173,-1.388c-0.918,-0.307 -1.947,-0.334 -2.934,-0.373c-1.421,-0.057 -2.583,0.753 -2.604,1.715c-0.021,1.006 1.352,2.18 3.063,2.22zm-31.358,-4.32c-0.518,0.079 -1.188,0.107 -1.81,0.293c-0.819,0.245 -1.502,0.673 -1.465,1.719c0.041,1.148 0.804,1.672 1.744,1.718c1.049,0.052 2.16,-0.065 3.152,-0.39c0.58,-0.19 1.252,-0.858 1.4,-1.436c0.245,-0.953 -0.599,-1.458 -1.413,-1.727c-0.457,-0.149 -0.975,-0.114 -1.608,-0.177z" fill="#548099" id="svg_7"/>
|
||||
<path d="m346.953,290.630881c0.806,1.415 1.73,2.778 2.39,4.258c0.832,1.868 1.448,3.832 2.157,5.754c-43.5,0 -87,0 -130.5,0c0.664,-4.841 1.467,-9.669 1.949,-14.528c0.409,-4.123 0.447,-8.283 0.649,-12.426c0.655,-0.292 1.496,-0.416 1.932,-0.903c1.935,-2.161 4.26,-3.808 6.56,-5.537c3.753,-2.82 7.508,-5.604 11.694,-7.767c0.521,-0.269 0.858,-0.895 1.28,-1.356c1.969,-0.804 3.904,-1.705 5.914,-2.389c2.802,-0.953 5.66,-1.745 8.494,-2.605c0.444,0.781 0.854,1.584 1.34,2.338c1.034,1.602 3.777,1.039 4.439,-0.312c0.597,-1.219 1.348,-2.508 0.685,-3.985c1.22,-0.01 2.44,-0.019 3.659,-0.029c0.702,1.529 1.924,2.014 3.551,1.953c1.667,-0.062 1.744,-1.442 2.288,-2.46c1.394,-0.16 2.786,-0.341 4.183,-0.474c0.906,-0.086 1.406,0.366 1.468,1.282c-0.581,0.457 -1.236,0.848 -1.725,1.388c-0.997,1.1 -1.005,2.435 -0.49,3.73c0.443,1.113 1.448,1.554 2.592,1.627c1.41,0.09 3.026,-0.987 3.526,-2.301c0.455,-1.196 0.04,-2.505 -1.157,-3.529c-0.436,-0.373 -0.922,-0.689 -1.385,-1.031c0.254,-0.166 0.511,-0.479 0.762,-0.474c1.561,0.031 3.12,0.133 4.678,0.211c1.094,0.858 2.293,1.005 3.583,0.518c0.516,0.348 0.986,0.863 1.554,1.015c2.989,0.797 5.976,1.653 9.016,2.189c2.214,0.391 4.217,1.307 6.306,1.99c2.187,0.715 4.316,1.674 6.363,2.734c2.392,1.238 4.669,2.697 6.993,4.066c3.399,2.002 6.29,4.673 9.358,7.11c-0.825,0.879 -1.67,1.742 -2.471,2.642c-2.811,3.157 -5.61,6.324 -8.407,9.493c-0.931,1.055 -1.882,2.098 -2.752,3.203c-1.282,1.629 -1.073,4.988 0.312,5.917c1.948,1.307 4.248,0.857 5.65,-1.118c0.623,-0.878 1.155,-1.829 1.845,-2.649c3.565,-4.239 7.427,-8.242 10.273,-13.047c2.732,1.785 4.457,4.533 6.55,6.923c0.331,0.378 0.315,1.06 0.46,1.601c-0.496,0.652 -1.051,1.268 -1.474,1.965c-0.803,1.321 -0.775,3.932 -0.021,4.694c1.143,1.154 3.035,1.425 4.766,0.701c0.375,-0.158 0.775,-0.257 1.163,-0.382zm-72.407,-25.96c0.019,-2.027 -1.097,-3.298 -2.884,-3.284c-1.897,0.015 -4.097,2.687 -4.097,4.975c0,1.415 1.258,2.597 2.755,2.588c2.478,-0.015 4.202,-1.761 4.226,-4.279zm25.954,-5.911c0.013,-1.961 -1.734,-3.932 -3.438,-3.88c-1.956,0.061 -3.375,1.636 -3.444,3.82c-0.054,1.74 1.356,2.824 3.628,2.788c1.744,-0.026 3.244,-1.285 3.254,-2.728zm-11.518,6.038c0.038,-2.048 -0.319,-2.983 -1.43,-3.368c-1.198,-0.415 -2.281,-0.054 -3.198,0.77c-1.084,0.975 -1.515,2.591 -1.055,3.791c0.59,1.539 1.254,2.056 2.702,2.102c1.99,0.063 2.939,-0.986 2.981,-3.295z" fill="#DAF1FF" id="svg_9"/>
|
||||
<path d="m703.429,159.265881c0.502,-1.426 1.928,-1.133 2.946,-1.451c1.315,-0.41 2.746,-0.447 4.128,-0.646c-0.073,1.037 -0.284,2.089 -0.18,3.108c0.102,0.994 0.945,1.465 1.891,1.687c1.031,0.242 1.717,-0.193 2.278,-1.028c0.839,-1.249 1.542,-2.52 1.195,-4.107c2.698,-0.336 5.388,-0.789 8.096,-0.966c1.798,-0.118 3.675,0.481 5.423,0.193c4.629,-0.762 9.263,-0.294 13.889,-0.384c2.297,-0.044 4.602,0.301 6.904,0.47c0,6 0,12 0,18c-0.168,0.168 -0.379,0.31 -0.499,0.507c-3.606,5.916 -8.197,10.898 -14.1,14.566c-9.541,5.927 -19.201,11.641 -29.712,15.749c-4.875,1.905 -9.688,3.974 -14.602,5.768c-6.175,2.255 -12.495,3.985 -19.078,4.65c-6.024,0.609 -12.036,1.349 -18.086,2.036c-0.09,-0.077 -0.245,-0.163 -0.332,-0.294c-0.23,-0.345 -0.451,-0.7 -0.642,-1.069c-2.945,-5.662 -7.74,-8.766 -13.872,-9.97c-2.517,-0.494 -5.059,-0.863 -7.59,-1.289c0.122,-1.386 1.372,-1.852 2.257,-2.453c3.107,-2.111 5.83,-4.582 8.397,-7.337c2.64,-2.832 5.637,-5.351 8.608,-7.852c3.084,-2.596 6.282,-5.065 9.508,-7.485c4.242,-3.181 8.981,-5.539 13.649,-8.011c1.312,-0.695 2.5,-1.622 3.798,-2.346c2.435,-1.357 4.861,-2.746 7.38,-3.931c3.057,-1.438 5.85,-3.482 9.246,-4.157c1.207,-0.24 2.427,-0.418 3.641,-0.625c0.038,0.747 0.084,1.493 0.112,2.241c0.048,1.279 0.794,1.688 1.971,1.714c3.458,0.077 4.892,-2.151 3.376,-5.288z" fill="#FEDBD0" id="svg_10"/>
|
||||
<path d="m57.51,258.254881c7.145,-2.064 14.546,-3.315 21.271,-6.71c0.294,-0.148 0.596,-0.312 0.913,-0.376c1.115,-0.226 1.846,0.461 1.457,1.539c-0.502,1.392 -1.12,2.772 -1.899,4.026c-1.746,2.811 -3.623,5.542 -5.446,8.305c-3.48,3.206 -6.708,6.758 -10.503,9.534c-5.403,3.951 -11.477,6.726 -18.266,7.474c-3.769,0.415 -7.528,1.343 -11.356,0.469c-0.543,-0.124 -1.098,-0.198 -1.702,-0.304c-2.741,5.966 -4.551,12.065 -5.48,18.43c-0.667,0 -1.333,0 -2,0c0.047,-0.996 -0.041,-2.022 0.163,-2.985c1.855,-8.751 4.84,-17.099 9.313,-24.86c2.197,-3.813 4.602,-7.509 7.008,-11.196c1.63,-2.497 3.424,-4.887 5.145,-7.325c0.707,-0.793 1.414,-1.587 1.826,-2.049c-1.337,-4.444 -2.733,-8.452 -3.722,-12.558c-0.906,-3.763 -0.302,-7.588 0.281,-11.383c0.427,-2.786 0.691,-5.602 0.898,-8.414c0.539,-7.303 1.03,-14.61 1.491,-21.919c0.141,-2.242 0.072,-4.498 0.198,-6.741c0.083,-1.468 -0.072,-3.421 1.695,-3.792c1.799,-0.378 2.727,1.386 3.382,2.777c1.878,3.985 3.742,7.986 4.607,12.361c0.5,2.527 1.048,5.048 1.682,7.544c1.66,6.54 1.353,13.035 -0.341,19.504c-1.01,3.857 -2.083,7.696 -3.115,11.547c-0.081,0.302 -0.059,0.632 -0.138,1.602c4.488,-4.461 7.787,-9.186 11.478,-13.553c3.756,-4.445 7.3,-9.081 10.746,-13.773c3.441,-4.686 6.087,-9.859 8.756,-15.053c-1.95,-0.825 -3.721,-1.493 -5.418,-2.314c-1.995,-0.966 -3.474,-2.515 -4.659,-4.396c-2.918,-4.634 -4.822,-9.683 -6.084,-14.97c-1.099,-4.607 -2.018,-9.257 -2.983,-13.895c-0.37,-1.781 0.934,-2.944 2.664,-2.336c0.675,0.237 1.307,0.735 1.828,1.246c5.293,5.196 10.216,10.715 14.295,16.93c0.951,1.448 1.61,3.09 2.377,4.656c0.715,1.462 1.396,2.941 2.091,4.412c0.184,-0.025 0.369,-0.049 0.553,-0.074c0.57,-2.137 1.031,-4.311 1.741,-6.4c0.686,-2.019 0.513,-3.751 -0.461,-5.692c-1.933,-3.85 -2.263,-8.14 -2.788,-12.352c-0.267,-2.142 -0.713,-4.277 -0.791,-6.425c-0.243,-6.719 2.212,-12.722 5.395,-18.432c1.78,-3.192 3.947,-6.169 5.951,-9.235c0.226,-0.346 0.484,-0.688 0.789,-0.962c1.019,-0.913 1.987,-0.691 2.548,0.593c0.197,0.453 0.33,0.948 0.404,1.438c0.771,5.099 1.548,10.196 1.283,15.386c-0.242,4.729 -0.409,9.461 -0.621,14.191c-0.263,5.86 -1.892,11.295 -5.25,16.133c-0.69,0.994 -1.642,1.891 -2.657,2.548c-0.904,0.586 -1.382,1.11 -1.026,2.166c0.173,0.034 0.378,0.141 0.461,0.081c4.238,-3.071 9.005,-4.968 13.999,-6.39c3.119,-0.888 6.217,-1.852 9.316,-2.809c0.709,-0.219 1.533,-0.754 1.974,0.12c0.211,0.418 -0.073,1.317 -0.429,1.756c-3.838,4.724 -7.825,9.269 -13.147,12.472c-4.119,2.479 -8.495,3.238 -13.181,2.755c-0.401,-0.041 -0.801,-0.094 -1.403,-0.165c-0.513,1.27 -1.061,2.461 -1.48,3.695c-1.126,3.313 -2.51,6.49 -4.453,9.422c-0.589,0.889 -0.936,1.94 -1.393,2.917c-0.092,0.177 -0.184,0.354 -0.276,0.531c0.237,-0.025 0.475,-0.049 0.712,-0.074c8.518,-4.686 17.852,-5.489 27.302,-5.726c4.238,-0.106 8.496,0.056 12.66,-0.997c0.32,-0.081 0.688,-0.208 0.977,-0.122c0.431,0.129 0.934,0.345 1.165,0.687c0.135,0.199 -0.127,0.769 -0.346,1.076c-0.233,0.327 -0.62,0.546 -0.949,0.8c-4.471,3.456 -8.851,7.037 -13.446,10.319c-3.093,2.209 -6.334,4.341 -9.788,5.883c-4.479,1.999 -9.254,2.422 -14.099,0.841c-3.231,-1.054 -6.577,-1.775 -9.11,-4.412c-5.55,7.005 -10.925,13.789 -16.3,20.573c0.1,0.131 0.201,0.262 0.301,0.394c1.158,-0.542 2.28,-1.184 3.48,-1.603c2.105,-0.736 4.22,-1.533 6.399,-1.952c4.669,-0.897 9.384,-1.167 14.146,-0.602c2.304,0.273 4.653,0.157 6.981,0.233c0.495,0.016 1.018,0.013 1.476,0.172c0.931,0.323 1.159,1.218 0.56,1.997c-0.15,0.194 -0.348,0.364 -0.553,0.501c-5.39,3.602 -10.583,7.572 -16.826,9.66c-4.521,1.513 -9.105,2.594 -13.959,1.56c-2.984,-0.636 -6.028,-0.992 -9.407,-1.532c-1.229,1.622 -2.828,3.432 -4.06,5.465c-0.787,1.297 -1.522,2.517 -2.777,3.402c-0.313,0.22 -0.452,0.686 -0.672,1.038c-2.79,3.854 -5.6,7.693 -8.352,11.574c-0.505,0.712 -0.774,1.592 -1.151,2.396c-0.148,0.187 -0.298,0.372 -0.446,0.558c-0.049,0.137 -0.098,0.273 -0.148,0.409c0.085,-0.101 0.169,-0.202 0.254,-0.303c0.175,-0.159 0.349,-0.318 0.524,-0.476c0.359,-0.195 0.757,-0.343 1.072,-0.594c4.498,-3.578 9.142,-6.901 14.687,-8.726c0.769,-0.253 1.457,-0.754 2.182,-1.141zm-3.085,-15.668c-0.545,0.543 -0.931,1.13 -0.546,2.46c0.526,-0.893 0.821,-1.396 1.117,-1.898c-0.19,-0.188 -0.381,-0.375 -0.571,-0.562z" fill="#56819B" id="svg_11"/>
|
||||
<path d="m203.5,300.642881c-28.167,0 -56.333,0 -84.5,0c1.579,-2.025 3.197,-4.021 4.731,-6.079c6.246,-8.378 11.41,-17.326 14.302,-27.453c0.29,-1.015 0.785,-1.708 1.872,-2.113c1.625,-0.605 3.261,-1.3 4.707,-2.241c2.703,-1.757 5.273,-3.719 7.899,-5.595c0.724,0.841 1.492,0.323 2.277,0.105c1.509,-0.419 3.015,-0.961 4.557,-1.123c1.518,-0.159 2.939,-0.342 4.212,-1.261c0.46,-0.332 1.027,-0.601 1.581,-0.702c3.082,-0.565 6.171,-1.099 9.267,-1.587c3.507,-0.553 7.013,-1.126 10.537,-1.545c4.593,-0.547 9.198,-1.015 13.808,-1.394c3.384,-0.278 6.784,-0.345 10.175,-0.546c0.372,-0.022 0.727,-0.309 1.091,-0.473c0.773,0.86 1.621,1.664 2.303,2.59c1.619,2.199 3.404,4.325 4.683,6.715c3.164,5.915 4.317,12.338 4.11,19.02c-1.822,1.284 -3.545,2.656 -4.742,4.6c-0.692,1.125 -1.486,2.192 -2.282,3.248c-1.578,2.094 -3.217,4.141 -4.778,6.247c-1.37,1.847 -2.75,3.695 -3.969,5.642c-0.765,1.221 -1.238,2.624 -1.841,3.945z" fill="#B2CDDD" id="svg_12"/>
|
||||
<path d="m0,167.642881c1.953,0.858 4.023,1.524 5.837,2.613c4.339,2.606 8.422,5.588 12.183,9.001c7.607,6.902 12.849,15.19 15.549,25.179c1.135,4.2 1.898,8.407 2.177,12.703c0.491,7.553 -0.901,14.816 -3.643,21.862c-1.81,4.652 -4.023,9.07 -6.872,13.17c-0.178,0.257 -0.213,0.613 -0.314,0.923c-0.706,0.697 -1.407,1.399 -2.12,2.089c-1.599,1.548 -3.107,3.208 -4.825,4.612c-4.805,3.927 -9.685,7.762 -14.564,11.598c-1.008,0.792 -1.893,1.912 -3.409,1.75c0.001,-35.167 0.001,-70.333 0.001,-105.5z" fill="#54809A" id="svg_14"/>
|
||||
<path d="m26.5,300.642881c0.929,-6.365 2.74,-12.464 5.48,-18.43c0.603,0.106 1.158,0.18 1.702,0.304c3.828,0.874 7.586,-0.054 11.356,-0.469c6.789,-0.748 12.863,-3.524 18.266,-7.474c3.796,-2.775 7.023,-6.328 10.503,-9.534c3.102,2.025 6.632,2.88 10.186,3.581c4.458,0.88 8.946,1.606 13.421,2.397c0.163,0.029 0.331,0.078 0.493,0.066c5.706,-0.412 11.501,0.316 17.092,-1.612c2.083,-0.719 4.354,-0.881 6.519,-1.383c0.977,-0.227 2.248,-0.094 2.553,-1.51c3.712,1.091 7.464,0.778 11.605,0.016c-0.403,1.373 -0.662,2.386 -0.997,3.374c-3.081,9.089 -8.229,17.02 -13.851,24.685c-1.456,1.986 -2.886,3.991 -4.328,5.988c-30,0.001 -60,0.001 -90,0.001z" fill="#B2CDDD" id="svg_15"/>
|
||||
<path d="m453.697,248.860881c-1.036,1.466 -1.992,2.999 -3.127,4.385c-1.997,2.439 -3.893,4.933 -5.48,7.668c-0.741,1.277 -1.777,2.387 -1.938,3.947c-0.025,0.244 -0.081,0.511 -0.207,0.716c-2.923,4.751 -4.145,10.224 -6.466,15.235c-0.896,1.934 -1.377,4.067 -1.979,6.129c-0.922,3.158 -1.861,6.316 -2.636,9.511c-0.327,1.347 -0.255,2.791 -0.365,4.191c-8.333,0 -16.667,0 -25,0c-0.064,-0.492 -0.127,-0.984 -0.191,-1.476c-0.786,-6.001 -1.833,-11.982 -2.289,-18.008c-0.5,-6.618 -0.296,-13.268 0.94,-19.837c1.29,-6.857 3.538,-13.302 7.994,-18.814c1.804,-2.231 3.905,-4.056 6.617,-5.126c7.006,-2.763 13.85,-2.131 20.494,1.109c5.215,2.544 9.46,6.425 13.633,10.37z" fill="#FEF6F3" id="svg_18"/>
|
||||
<path d="m453.697,248.860881c-4.173,-3.945 -8.418,-7.826 -13.632,-10.369c-6.644,-3.241 -13.488,-3.872 -20.494,-1.109c-2.712,1.069 -4.814,2.894 -6.617,5.126c-4.456,5.512 -6.704,11.957 -7.994,18.814c-1.236,6.569 -1.44,13.219 -0.94,19.837c0.456,6.026 1.503,12.007 2.289,18.008c0.064,0.492 0.128,0.984 0.191,1.476c-1,0 -2,0 -3,0c-0.048,-0.899 -0.047,-1.804 -0.152,-2.696c-0.636,-5.436 -1.441,-10.857 -1.901,-16.307c-0.335,-3.965 -0.414,-7.975 -0.272,-11.952c0.276,-7.714 1.882,-15.169 5.327,-22.127c2.157,-4.356 4.886,-8.252 9.027,-11.079c4.653,-3.177 9.791,-4.007 15.208,-3.599c7.091,0.533 12.977,3.859 18.324,8.305c2.236,1.859 4.459,3.733 6.689,5.599c3.79,3.227 7.337,6.807 11.421,9.605c7.432,5.091 15.641,6.85 24.394,3.67c4.21,-1.529 8.286,-3.37 12.08,-5.845c5.415,-3.533 10.19,-7.729 14.251,-12.749c0.603,-0.745 1.155,-1.531 1.799,-2.388c-4.865,-3.29 -8.586,-7.267 -11.563,-11.948c-1.977,-4.036 -3.754,-8.144 -4.246,-12.674c-0.618,-5.684 0.519,-10.88 4.306,-15.343c7.45,-8.777 17.131,-4.118 20.673,1.989c3.019,5.207 4.006,10.856 2.952,16.788c-0.522,2.935 -1.291,5.827 -1.948,8.738c-1.193,2.637 -2.33,5.303 -3.604,7.9c-0.639,1.302 -1.527,2.482 -2.352,3.795c1.172,0.621 2.086,1.221 3.08,1.615c10.465,4.143 20.984,4.634 31.595,0.488c5.979,-2.336 11.681,-5.231 17.151,-8.551c3.608,-2.19 6.999,-4.739 10.487,-7.127c2.397,-1.768 4.815,-3.507 7.186,-5.31c7.254,-5.519 15.14,-9.814 23.882,-12.49c3.984,-1.219 8.052,-1.862 12.21,-1.993c0.661,-0.021 1.321,-0.104 1.981,-0.157c2.531,0.427 5.073,0.795 7.59,1.29c6.133,1.204 10.928,4.308 13.872,9.97c0.191,0.368 0.411,0.723 0.642,1.069c0.087,0.131 0.242,0.217 0.332,0.294c6.05,-0.686 12.062,-1.426 18.086,-2.036c6.583,-0.666 12.902,-2.396 19.078,-4.65c4.914,-1.794 9.728,-3.863 14.602,-5.768c10.511,-4.108 20.171,-9.822 29.712,-15.749c5.903,-3.667 10.494,-8.65 14.1,-14.566c0.12,-0.197 0.331,-0.339 0.499,-0.507c0,1.667 0,3.333 0,5c-0.19,0.146 -0.434,0.254 -0.562,0.442c-4.137,6.029 -9.654,10.489 -15.927,14.114c-7.271,4.202 -14.354,8.758 -22.164,11.949c-7.229,2.953 -14.434,5.979 -21.76,8.672c-6.414,2.358 -13.101,3.708 -19.931,4.322c-4.868,0.438 -9.722,1.032 -14.698,1.569c0.447,2.367 0.896,4.466 1.232,6.582c0.989,6.238 0.15,12.164 -3.601,17.373c-2.782,3.864 -6.297,6.874 -11.243,7.64c-2.72,0.421 -5.149,-0.852 -6.339,-3.293c-2.429,-4.984 -3.471,-10.145 -1.047,-15.386c2.78,-6.012 7.204,-10.527 13.259,-13.349c1.051,-0.49 2.131,-0.918 3.194,-1.383c0.06,-0.026 0.087,-0.129 0.227,-0.349c-0.797,-2.545 -2.498,-4.521 -4.736,-6.04c-3.833,-2.602 -8.237,-3.539 -12.737,-3.908c-2.553,-0.209 -5.147,0.08 -7.722,0.145c-2.839,0.556 -5.759,0.858 -8.5,1.723c-5.473,1.727 -10.671,4.173 -15.496,7.29c-2.762,1.783 -5.307,3.901 -7.95,5.869c-0.817,0.561 -1.677,1.07 -2.446,1.692c-9.188,7.432 -19.365,13.198 -30.219,17.776c-5.006,2.111 -10.247,3.548 -15.701,3.762c-6.311,0.247 -12.404,-0.995 -18.246,-3.4c-1.536,-0.632 -3.032,-1.364 -4.737,-2.135c-0.982,1.112 -1.995,2.19 -2.932,3.33c-5.141,6.249 -11.288,11.234 -18.465,14.989c-3.85,2.014 -7.804,3.629 -12.041,4.69c-7.43,1.86 -14.189,0.184 -20.6,-3.493c-4.347,-2.493 -8.189,-5.684 -11.787,-9.17c-0.834,-0.816 -1.748,-1.545 -2.626,-2.31zm73.068,-22.082c1.719,-5.217 2.952,-10.54 2.417,-16.06c-0.418,-4.322 -1.932,-8.262 -5.474,-11.091c-3.886,-3.104 -8.374,-3.952 -12.441,0.361c-3.003,3.185 -4.691,7.101 -4.647,11.502c0.057,5.679 1.696,10.943 5.001,15.629c2.474,4.007 5.76,7.16 9.901,9.719c2.297,-3.261 4.164,-6.449 5.243,-10.06zm125.196,-5.674c-5.95,1.56 -10.071,5.141 -13.571,9.623c-2.34,2.996 -2.873,6.522 -2.83,10.114c0.026,2.222 0.639,4.441 1.956,6.347c1.279,1.852 2.618,2.387 4.71,1.598c1.375,-0.518 2.773,-1.183 3.906,-2.097c6.705,-5.413 8.435,-12.613 7.132,-20.786c-0.256,-1.601 -0.846,-3.15 -1.303,-4.799z" fill="#5D859D" id="svg_20"/>
|
||||
<path d="m0,273.142881c1.516,0.161 2.401,-0.958 3.409,-1.75c4.879,-3.836 9.759,-7.671 14.564,-11.598c1.717,-1.404 3.226,-3.064 4.825,-4.612c0.713,-0.69 1.414,-1.392 2.12,-2.089c0.999,0.956 2.174,0.507 3.297,0.397c2.798,-0.273 5.591,-0.608 8.392,-0.847c3.306,-0.282 6.367,0.969 9.522,1.631c-1.72,2.438 -3.515,4.828 -5.144,7.325c-2.406,3.687 -4.811,7.384 -7.008,11.196c-4.473,7.761 -7.457,16.109 -9.313,24.86c-0.204,0.963 -0.116,1.988 -0.163,2.985c-8.167,0 -16.333,0 -24.5,0c-0.001,-9.165 -0.001,-18.331 -0.001,-27.498z" fill="#B2CDDD" id="svg_24"/>
|
||||
<path d="m116.5,300.642881c1.442,-1.996 2.872,-4.002 4.328,-5.988c5.622,-7.665 10.77,-15.596 13.851,-24.685c0.335,-0.988 0.594,-2.002 0.997,-3.374c-4.141,0.762 -7.892,1.075 -11.605,-0.016c-2.444,-2.027 -4.795,-4.112 -5.824,-7.288c-2.189,-6.757 -0.875,-12.83 3.686,-18.161c2.091,-2.444 5.436,-2.988 8.48,-1.804c4.269,1.661 6.988,4.729 8.105,9.135c0.943,3.719 1.461,7.501 0.999,11.362c-0.087,0.726 -0.013,1.471 -0.013,2.42c0.622,-0.123 1.191,-0.128 1.66,-0.349c1.127,-0.53 2.259,-1.079 3.295,-1.764c5.63,-3.723 11.032,-7.753 15.879,-12.467c8.534,-8.302 18.932,-11.766 30.552,-10.834c8.313,0.666 15.79,3.901 21.214,10.745c0.508,0.641 1.185,1.147 1.784,1.715c1.658,2.364 3.474,4.635 4.939,7.113c3.137,5.308 4.259,11.225 4.77,17.286c-0.202,4.143 -0.24,8.303 -0.649,12.426c-0.482,4.859 -1.285,9.687 -1.949,14.528c-0.667,0 -1.333,0 -2,0c0.064,-0.902 0.077,-1.811 0.199,-2.704c0.954,-6.964 1.934,-13.924 1.911,-20.977c0.208,-6.682 -0.946,-13.105 -4.11,-19.02c-1.279,-2.39 -3.064,-4.516 -4.683,-6.715c-0.682,-0.926 -1.53,-1.731 -2.303,-2.59c-1.733,-2.164 -3.651,-4.181 -6.227,-5.273c-3.279,-1.39 -6.602,-2.749 -10.019,-3.724c-4.067,-1.161 -8.247,-0.643 -12.392,-0.074c-6.704,0.92 -12.55,3.703 -17.688,8.031c-3.75,3.16 -7.454,6.375 -11.178,9.566c-2.626,1.876 -5.196,3.838 -7.899,5.595c-1.447,0.941 -3.082,1.635 -4.707,2.241c-1.087,0.405 -1.582,1.099 -1.872,2.113c-2.892,10.127 -8.056,19.075 -14.302,27.453c-1.535,2.058 -3.152,4.055 -4.731,6.079c-0.831,-0.001 -1.665,-0.001 -2.498,-0.001zm21.085,-44.076c-0.226,-1.841 -0.346,-3.67 -0.69,-5.455c-0.702,-3.64 -2.383,-6.701 -5.587,-8.784c-3.248,-2.112 -6.703,-1.67 -8.715,1.513c-2.711,4.287 -3.974,9.005 -2.481,14.042c1.149,3.876 3.627,6.344 7.888,6.96c2.418,0.349 4.596,-0.35 6.873,-0.68c1.263,-0.183 1.866,-0.953 2.018,-2.163c0.228,-1.812 0.463,-3.622 0.694,-5.433z" fill="#5E879F" id="svg_33"/>
|
||||
<path d="m221.11,276.961881c0.023,7.053 -0.957,14.013 -1.911,20.977c-0.123,0.894 -0.136,1.803 -0.199,2.704c-5.167,0 -10.333,0 -15.5,0c0.603,-1.321 1.076,-2.724 1.839,-3.944c1.218,-1.946 2.599,-3.795 3.969,-5.642c1.561,-2.105 3.2,-4.153 4.778,-6.247c0.796,-1.057 1.59,-2.123 2.282,-3.248c1.197,-1.945 2.92,-3.316 4.742,-4.6z" fill="#DAF1FF" id="svg_34"/>
|
||||
<path d="m508.133,227.129881c2.977,4.682 6.698,8.658 11.563,11.948c-0.643,0.857 -1.196,1.643 -1.799,2.388c-4.06,5.019 -8.836,9.216 -14.251,12.749c-3.794,2.475 -7.869,4.315 -12.08,5.845c-8.754,3.18 -16.962,1.421 -24.394,-3.67c-4.084,-2.797 -7.631,-6.378 -11.421,-9.605c0.51,-0.232 1.208,-0.327 1.499,-0.721c1.406,-1.904 3.562,-2.659 5.458,-3.826c0.553,-0.341 0.885,-1.041 1.319,-1.576c2.188,-1.133 4.354,-2.31 6.567,-3.391c5.425,-2.651 10.837,-5.319 16.666,-7.044c1.654,-0.49 3.333,-0.605 4.974,-0.974c5.244,-1.179 10.582,-1.552 15.899,-2.123z" fill="#FEDBD0" id="svg_39"/>
|
||||
<path d="m586.228,224.746881c-3.488,2.388 -6.878,4.937 -10.487,7.127c-5.47,3.32 -11.172,6.215 -17.151,8.551c-10.612,4.146 -21.13,3.656 -31.595,-0.488c-0.995,-0.394 -1.908,-0.993 -3.08,-1.615c0.824,-1.313 1.713,-2.492 2.352,-3.795c1.274,-2.598 2.411,-5.263 3.604,-7.9c5.575,0.006 11.151,0.061 16.726,-0.003c2.524,-0.029 5.044,-0.372 7.568,-0.427c1.561,-0.034 3.128,0.21 4.693,0.321c0.579,0.041 1.161,0.112 1.738,0.083c2.645,-0.133 5.286,-0.381 7.931,-0.43c5.194,-0.097 10.349,-0.52 15.467,-1.418c0.723,-0.127 1.488,-0.01 2.234,-0.006z" fill="#FEDAD0" id="svg_40"/>
|
||||
<path d="m595.508,185.574881c1.815,-0.428 3.265,-0.768 4.714,-1.114c1.426,-0.341 2.629,-0.045 3.518,1.197c0.932,1.301 0.614,2.648 -0.249,3.691c-1.258,1.521 -2.76,2.843 -4.187,4.221c-0.475,0.459 -1.03,0.837 -1.562,1.234c-0.678,0.505 -1.005,1.152 -0.635,1.948c0.77,1.659 0.227,2.937 -1.15,3.892c-1.168,0.81 -2.502,0.376 -4.104,-1.156c-0.049,-0.047 -0.145,-0.046 -0.414,-0.123c-1.114,0.772 -2.349,1.596 -3.549,2.469c-1.042,0.758 -2.035,0.944 -3.203,0.204c-1.139,-0.721 -1.505,-1.711 -1.397,-2.964c0.128,-1.487 0.271,-2.972 0.428,-4.683c-0.666,-0.273 -1.329,-0.554 -1.999,-0.816c-0.692,-0.271 -1.439,-0.442 -2.076,-0.806c-0.871,-0.497 -1.611,-2.212 -1.29,-3.033c0.402,-1.03 1.054,-1.877 2.306,-2.007c1.484,-0.154 2.961,-0.413 4.449,-0.496c1.31,-0.073 2.229,-0.56 2.663,-1.83c0.614,-1.798 1.3,-3.576 1.818,-5.402c0.548,-1.934 1.235,-2.706 2.722,-2.679c1.553,0.028 2.468,0.811 2.643,2.367c0.21,1.888 0.358,3.784 0.554,5.886z" fill="#57829B" id="svg_41"/>
|
||||
<path d="m715.687,156.829881c0.347,1.587 -0.356,2.857 -1.195,4.107c-0.56,0.834 -1.247,1.27 -2.278,1.028c-0.946,-0.222 -1.79,-0.693 -1.891,-1.687c-0.104,-1.019 0.107,-2.071 0.18,-3.108c0.053,-0.063 0.104,-0.129 0.16,-0.19c2.162,-2.296 2.731,-2.313 5.024,-0.15z" fill="#648CA3" id="svg_57"/>
|
||||
<path d="m706.431,153.888881c-1.644,0.036 -3.161,-1.439 -3.192,-3.104c-0.028,-1.499 1.198,-2.548 3.001,-2.567c1.618,-0.017 3.153,1.284 3.199,2.789c0.058,1.866 -1.115,2.593 -3.008,2.882z" fill="#6F92A7" id="svg_58"/>
|
||||
<path d="m703.429,159.265881c1.516,3.137 0.082,5.365 -3.374,5.289c-1.177,-0.026 -1.923,-0.435 -1.971,-1.714c-0.028,-0.747 -0.074,-1.493 -0.112,-2.241c1.107,-1.124 2.482,-1.565 4.025,-1.576c0.175,0.01 0.35,0.019 0.525,0.029c0.302,0.072 0.604,0.142 0.907,0.213z" fill="#6F92A7" id="svg_61"/>
|
||||
<path d="m702.521,159.053881c-0.175,-0.009 -0.35,-0.019 -0.525,-0.029c0.175,0.009 0.35,0.019 0.525,0.029z" fill="#FEDBD0" id="svg_63"/>
|
||||
<path d="m223.597,273.687881c-0.512,-6.061 -1.633,-11.978 -4.77,-17.286c-1.465,-2.479 -3.282,-4.749 -4.939,-7.113c4.654,-0.328 9.281,-0.385 13.788,1.146c0.932,0.317 1.925,0.51 2.906,0.615c4.351,0.469 7.965,2.621 11.503,4.953c1.017,0.67 1.987,1.413 2.979,2.123c-0.421,0.461 -0.758,1.086 -1.279,1.356c-4.186,2.163 -7.941,4.946 -11.694,7.767c-2.3,1.728 -4.625,3.375 -6.56,5.537c-0.438,0.486 -1.279,0.611 -1.934,0.902z" fill="#B2CDDD" id="svg_64"/>
|
||||
<path d="m335.508,275.129881c-2.846,4.805 -6.708,8.808 -10.273,13.047c-0.69,0.82 -1.222,1.771 -1.845,2.649c-1.402,1.974 -3.702,2.424 -5.65,1.118c-1.385,-0.929 -1.593,-4.288 -0.312,-5.917c0.87,-1.105 1.82,-2.147 2.752,-3.203c2.797,-3.169 5.596,-6.336 8.407,-9.493c0.802,-0.9 1.646,-1.763 2.471,-2.642c0.73,-0.128 1.455,-0.312 2.191,-0.373c1.829,-0.152 2.838,0.825 2.697,2.636c-0.056,0.733 -0.286,1.452 -0.438,2.178z" fill="#B4CFDE" id="svg_66"/>
|
||||
<path d="m365.213,292.639881c-2.923,0.011 -4.061,-1.133 -3.662,-3.836c0.188,-1.269 0.596,-2.692 1.385,-3.648c2.256,-2.732 4.501,-5.508 7.436,-7.601c2.437,-1.738 4.98,-1.037 5.309,1.618c0.102,0.82 -0.158,1.866 -0.631,2.545c-2.263,3.244 -4.637,6.413 -7.017,9.575c-0.726,0.965 -1.689,1.614 -2.82,1.347z" fill="#B7D0DE" id="svg_68"/>
|
||||
<path d="m345.5,268.101881c-2.654,0.057 -4.406,-1.231 -4.777,-3.208c-0.12,-0.641 0.009,-1.577 0.414,-2.029c2.596,-2.899 5.285,-5.716 7.986,-8.519c0.445,-0.463 1.105,-0.714 1.651,-1.085c1.131,-0.771 2.18,-0.518 3.214,0.206c1.045,0.731 1.135,1.807 0.959,2.898c-0.359,2.215 -1.79,3.89 -3.09,5.585c-1.255,1.636 -2.622,3.204 -4.082,4.659c-0.761,0.758 -1.828,1.209 -2.275,1.493z" fill="#B7D0DE" id="svg_69"/>
|
||||
<path d="m362.953,268.123881c-2.726,-0.059 -4.533,-2.69 -3.744,-5.101c0.43,-1.314 1.096,-2.661 2.005,-3.683c1.368,-1.538 2.89,-3.059 4.636,-4.101c1.254,-0.749 3.001,-1.022 4.494,-0.932c1.821,0.11 2.8,2.638 1.747,4.331c-0.991,1.594 -1.889,3.258 -3.578,4.394c-1.045,0.703 -1.697,2 -2.499,3.05c-0.892,1.168 -1.963,2 -3.061,2.042z" fill="#B7D0DE" id="svg_70"/>
|
||||
<path d="m346.953,290.630881c-0.389,0.126 -0.788,0.224 -1.164,0.382c-1.732,0.724 -3.623,0.453 -4.766,-0.701c-0.755,-0.762 -0.782,-3.373 0.021,-4.694c0.423,-0.696 0.978,-1.312 1.474,-1.965c0.473,-0.289 1.11,-0.468 1.391,-0.887c1.734,-2.582 4.36,-3.985 6.856,-5.494c2.881,1.421 3.464,2.328 2.74,4.45c-0.288,0.844 -0.712,1.688 -1.258,2.389c-1.717,2.21 -3.522,4.352 -5.294,6.52z" fill="#B7D0DE" id="svg_71"/>
|
||||
<path d="m57.51,258.254881c-0.725,0.387 -1.413,0.888 -2.182,1.141c-5.545,1.826 -10.189,5.148 -14.687,8.726c-0.315,0.251 -0.713,0.398 -1.073,0.593c-0.061,-0.064 -0.122,-0.126 -0.184,-0.188c0.378,-0.802 0.647,-1.682 1.152,-2.394c2.752,-3.881 5.562,-7.72 8.352,-11.574c0.196,1.12 1.068,0.99 1.89,1.103c2.438,0.336 4.816,0.879 6.732,2.593z" fill="#B2CDDD" id="svg_72"/>
|
||||
<path d="m270.464,237.841881c-0.074,1.702 -1.733,3.845 -3.338,4.195c-1.004,0.219 -1.98,0.172 -2.694,-0.786c-0.887,-1.19 -1.42,-2.497 -0.845,-3.92c0.328,-0.812 0.93,-1.582 1.586,-2.177c0.994,-0.901 2.15,-1.678 3.602,-0.953c1.113,0.558 1.759,2.025 1.689,3.641z" fill="#5E879F" id="svg_77"/>
|
||||
<path d="m286.407,233.645881c-0.038,2.102 -1.875,3.799 -4.097,3.782c-1.9,-0.014 -3.265,-1.56 -3.191,-3.616c0.07,-1.971 2.163,-4.021 4.041,-3.957c1.579,0.054 3.278,2.038 3.247,3.791z" fill="#5D859D" id="svg_79"/>
|
||||
<path d="m265.936,251.172881c0.663,1.477 -0.088,2.766 -0.685,3.985c-0.662,1.351 -3.405,1.914 -4.439,0.312c-0.486,-0.754 -0.896,-1.557 -1.34,-2.338c0.027,-0.161 0.077,-0.321 0.078,-0.482c0.005,-1.561 0.793,-2.748 2.171,-3.216c1.382,-0.47 2.73,0.014 3.716,1.205c0.155,0.188 0.332,0.357 0.499,0.534z" fill="#5E879F" id="svg_81"/>
|
||||
<path d="m298.765,241.316881c1.714,-0.007 3.078,1.376 3.141,3.186c0.065,1.862 -1.575,3.436 -3.519,3.378c-1.716,-0.052 -3.134,-1.535 -3.102,-3.246c0.029,-1.599 1.824,-3.311 3.48,-3.318z" fill="#5E879F" id="svg_82"/>
|
||||
<path d="m282.445,251.329881c0.463,0.342 0.949,0.658 1.385,1.031c1.197,1.024 1.613,2.333 1.157,3.529c-0.5,1.314 -2.117,2.391 -3.526,2.301c-1.144,-0.073 -2.149,-0.514 -2.592,-1.627c-0.515,-1.295 -0.507,-2.629 0.49,-3.73c0.49,-0.54 1.145,-0.931 1.725,-1.388c0.454,-0.038 0.908,-0.077 1.361,-0.116z" fill="#5E879F" id="svg_83"/>
|
||||
<path d="m275.434,250.637881c-0.544,1.018 -0.621,2.398 -2.288,2.46c-1.627,0.06 -2.849,-0.425 -3.551,-1.953c-0.009,-0.574 -0.08,-1.155 -0.016,-1.721c0.219,-1.933 1.62,-3.182 3.424,-3.108c1.562,0.064 2.446,1.248 2.477,3.336c0.005,0.328 -0.03,0.657 -0.046,0.986z" fill="#648CA3" id="svg_85"/>
|
||||
<path d="m291.468,251.583881c-1.29,0.487 -2.489,0.34 -3.583,-0.518c-0.97,-2.772 -0.614,-3.836 1.427,-5.153c1.211,-0.782 2.179,-0.419 3.161,0.28c1.001,0.713 1.325,1.786 0.845,2.838c-0.429,0.938 -1.219,1.709 -1.85,2.553z" fill="#648CA3" id="svg_86"/>
|
||||
<path d="m281.534,241.295881c1.544,-0.012 2.727,1.275 2.695,2.933c-0.03,1.558 -1.463,2.836 -3.168,2.825c-1.485,-0.01 -2.853,-1.174 -2.926,-2.491c-0.089,-1.609 1.621,-3.253 3.399,-3.267z" fill="#648CA3" id="svg_87"/>
|
||||
<path d="m293.736,231.754881c1.644,0.208 2.646,1.505 2.404,3.111c-0.277,1.838 -1.463,3.036 -2.797,2.827c-1.772,-0.278 -3.009,-1.846 -2.752,-3.487c0.254,-1.615 1.58,-2.649 3.145,-2.451z" fill="#648CA3" id="svg_88"/>
|
||||
<path d="m593.498,223.123881c2.643,-1.967 5.188,-4.085 7.95,-5.869c4.826,-3.116 10.023,-5.563 15.496,-7.29c2.742,-0.865 5.661,-1.167 8.5,-1.723c-2.26,1.482 -4.49,3.011 -6.786,4.435c-5.762,3.575 -11.658,6.908 -18.194,8.877c-2.273,0.686 -4.641,1.056 -6.966,1.57z" fill="#FDFDFE" id="svg_122"/>
|
||||
<path d="m676.375,263.677881c-1.33,-0.165 -2.321,-0.213 -3.273,-0.429c-0.848,-0.193 -1.521,-0.721 -1.505,-1.71c0.016,-1.015 0.557,-1.935 1.518,-1.987c2.362,-0.128 4.745,-0.09 7.104,0.089c0.519,0.039 1.342,0.936 1.355,1.454c0.014,0.556 -0.651,1.434 -1.215,1.641c-1.365,0.5 -2.844,0.691 -3.984,0.942z" fill="#EDD1CA" id="svg_131"/>
|
||||
<path d="m707.815,258.044881c1.271,0.174 2.745,0.353 4.21,0.585c0.81,0.129 1.39,0.567 1.359,1.482c-0.03,0.897 -0.622,1.374 -1.424,1.445c-2.382,0.211 -4.77,0.363 -7.159,0.498c-1.207,0.068 -1.898,-0.673 -2.005,-1.764c-0.103,-1.041 0.632,-1.708 1.602,-1.897c1.047,-0.203 2.13,-0.226 3.417,-0.349z" fill="#EDD1CA" id="svg_132"/>
|
||||
<path d="m726.254,240.603881c-1.297,-0.094 -2.132,-0.084 -2.94,-0.23c-1.079,-0.195 -1.634,-1.103 -1.446,-2.009c0.133,-0.642 0.94,-1.549 1.532,-1.621c2.211,-0.265 4.457,-0.303 6.689,-0.279c0.819,0.009 1.835,0.469 1.701,1.451c-0.092,0.674 -0.755,1.594 -1.369,1.807c-1.466,0.51 -3.055,0.666 -4.167,0.881z" fill="#EDD1CA" id="svg_133"/>
|
||||
<path d="m698.203,251.614881c-0.58,0 -1.209,0.142 -1.727,-0.036c-0.633,-0.219 -1.599,-0.652 -1.655,-1.091c-0.087,-0.684 0.341,-1.55 0.802,-2.145c0.295,-0.381 1.033,-0.548 1.585,-0.577c1.567,-0.083 3.154,-0.164 4.705,0.005c0.566,0.062 1.292,0.731 1.526,1.297c0.365,0.882 -0.206,1.686 -1.088,1.923c-1.345,0.361 -2.742,0.525 -4.117,0.773c-0.01,-0.05 -0.02,-0.1 -0.031,-0.149z" fill="#EDD1CA" id="svg_134"/>
|
||||
<path d="m715.076,280.980881c-1.135,0 -1.906,0.215 -2.424,-0.055c-0.648,-0.338 -1.446,-1.033 -1.518,-1.651c-0.072,-0.612 0.568,-1.78 1.087,-1.904c1.89,-0.452 3.887,-1.119 5.789,-0.143c0.643,0.33 1.382,1.06 1.491,1.706c0.174,1.034 -0.71,1.569 -1.714,1.707c-1.056,0.147 -2.116,0.266 -2.711,0.34z" fill="#EDD1CA" id="svg_135"/>
|
||||
<path d="m652.969,279.278881c-1.25,-0.064 -2.178,-0.031 -3.074,-0.181c-0.87,-0.146 -1.738,-0.696 -1.508,-1.636c0.165,-0.674 0.957,-1.671 1.5,-1.692c2.221,-0.083 4.462,0.08 6.676,0.329c0.48,0.054 1.229,0.845 1.219,1.282c-0.011,0.471 -0.695,1.191 -1.214,1.333c-1.265,0.348 -2.607,0.425 -3.599,0.565z" fill="#EDD1CA" id="svg_136"/>
|
||||
<path d="m709.683,290.088881c0.787,0.211 1.882,0.354 2.833,0.801c1.011,0.476 1.098,1.653 0.147,2.201c-1.596,0.919 -3.36,1.443 -5.244,1.002c-0.962,-0.225 -1.728,-0.773 -1.698,-1.861c0.029,-1.04 0.724,-1.662 1.699,-1.888c0.64,-0.148 1.313,-0.154 2.263,-0.255z" fill="#EDD1CA" id="svg_137"/>
|
||||
<path d="m735.998,252.586881c-0.79,-0.092 -1.214,-0.085 -1.604,-0.198c-1.005,-0.291 -1.885,-0.81 -1.904,-1.985c-0.02,-1.232 0.879,-1.808 1.937,-1.817c1.452,-0.012 2.972,0.005 4.34,0.418c1.423,0.429 1.466,1.845 0.137,2.548c-0.995,0.527 -2.14,0.771 -2.906,1.034z" fill="#EDD1CA" id="svg_138"/>
|
||||
<path d="m736.343,271.796881c-2.273,-0.125 -3.097,-0.611 -3.183,-1.997c-0.078,-1.265 1.025,-2.049 3.079,-2.046c0.969,0.001 1.987,0.067 2.888,0.376c0.505,0.173 1.16,0.925 1.134,1.38c-0.034,0.591 -0.556,1.402 -1.089,1.659c-0.846,0.409 -1.876,0.438 -2.829,0.628z" fill="#EDD1CA" id="svg_139"/>
|
||||
<path d="m685.329,281.473881c-0.519,-0.091 -1.172,-0.177 -1.811,-0.324c-0.899,-0.207 -1.443,-0.763 -1.482,-1.72c-0.039,-0.96 0.485,-1.524 1.335,-1.857c1.168,-0.457 4.739,0.394 5.578,1.336c0.727,0.816 0.512,1.843 -0.548,2.125c-0.947,0.252 -1.951,0.289 -3.072,0.44z" fill="#EDD1CA" id="svg_140"/>
|
||||
<path d="m740.005,293.233881c-1.484,2.038 -4.82,2.573 -6.338,1.091c-0.458,-0.447 -0.802,-1.318 -0.712,-1.921c0.068,-0.455 0.896,-1.103 1.414,-1.13c2.041,-0.107 4.097,-0.01 5.636,1.96z" fill="#EDD1CA" id="svg_141"/>
|
||||
<path d="m676.726,294.574881c-1.711,-0.04 -3.084,-1.214 -3.063,-2.22c0.021,-0.962 1.182,-1.772 2.604,-1.715c0.987,0.039 2.016,0.066 2.934,0.373c0.515,0.172 1.134,0.88 1.173,1.388c0.036,0.475 -0.525,1.241 -1.011,1.458c-0.96,0.43 -2.05,0.569 -2.637,0.716z" fill="#EDD1CA" id="svg_142"/>
|
||||
<path d="m645.368,290.254881c0.633,0.063 1.151,0.028 1.608,0.179c0.814,0.269 1.658,0.774 1.413,1.727c-0.148,0.577 -0.82,1.246 -1.4,1.436c-0.992,0.324 -2.103,0.442 -3.152,0.39c-0.94,-0.046 -1.703,-0.571 -1.744,-1.718c-0.038,-1.047 0.646,-1.474 1.465,-1.719c0.622,-0.188 1.291,-0.216 1.81,-0.295z" fill="#EDD1CA" id="svg_143"/>
|
||||
<path d="m274.546,264.670881c-0.023,2.518 -1.747,4.264 -4.225,4.279c-1.497,0.009 -2.755,-1.173 -2.755,-2.588c0,-2.289 2.2,-4.961 4.097,-4.975c1.785,-0.014 2.901,1.257 2.883,3.284z" fill="#5C869F" id="svg_145"/>
|
||||
<path d="m300.5,258.759881c-0.01,1.443 -1.51,2.701 -3.254,2.729c-2.272,0.036 -3.682,-1.048 -3.628,-2.788c0.068,-2.185 1.488,-3.76 3.444,-3.82c1.704,-0.053 3.451,1.919 3.438,3.879z" fill="#5C869F" id="svg_146"/>
|
||||
<path d="m288.982,264.797881c-0.042,2.309 -0.991,3.358 -2.981,3.296c-1.448,-0.045 -2.113,-0.562 -2.702,-2.102c-0.46,-1.2 -0.029,-2.816 1.055,-3.791c0.917,-0.825 2,-1.185 3.198,-0.77c1.111,0.384 1.468,1.319 1.43,3.367z" fill="#5E879F" id="svg_147"/>
|
||||
<path d="m54.996,243.147881c-0.296,0.503 -0.591,1.005 -1.117,1.898c-0.385,-1.33 0.001,-1.917 0.546,-2.46c0.191,0.188 0.381,0.375 0.571,0.562z" fill="#B7D0DE" id="svg_148"/>
|
||||
<path d="m54.996,243.147881c-0.19,-0.187 -0.381,-0.375 -0.571,-0.562c0.19,0.188 0.381,0.375 0.571,0.562z" fill="#FFF6F3" id="svg_149"/>
|
||||
<path d="m85.732,206.903881c-0.237,0.025 -0.475,0.049 -0.712,0.074c0.092,-0.177 0.185,-0.353 0.276,-0.531c0.145,0.152 0.291,0.304 0.436,0.457z" fill="#FFF6F3" id="svg_150"/>
|
||||
<path d="m38.938,269.085881c0.15,-0.185 0.299,-0.37 0.447,-0.557c0.061,0.06 0.122,0.123 0.183,0.187c-0.174,0.159 -0.349,0.318 -0.523,0.477l-0.107,-0.107z" fill="#B2CDDD" id="svg_151"/>
|
||||
<path d="m39.045,269.192881c-0.085,0.101 -0.169,0.202 -0.254,0.303c0.049,-0.136 0.099,-0.272 0.148,-0.409c-0.001,-0.001 0.106,0.106 0.106,0.106z" fill="#B2CDDD" id="svg_152"/>
|
||||
<path d="m152.512,257.161881c3.724,-3.191 7.427,-6.406 11.178,-9.566c5.138,-4.329 10.983,-7.111 17.688,-8.031c4.144,-0.569 8.325,-1.086 12.392,0.074c3.417,0.975 6.74,2.334 10.019,3.724c2.576,1.092 4.493,3.109 6.227,5.273c-0.363,0.164 -0.719,0.451 -1.09,0.473c-3.391,0.201 -6.791,0.268 -10.175,0.546c-4.61,0.379 -9.215,0.848 -13.808,1.394c-3.524,0.419 -7.03,0.992 -10.537,1.545c-3.095,0.488 -6.184,1.022 -9.267,1.587c-0.554,0.102 -1.122,0.37 -1.581,0.702c-1.272,0.919 -2.694,1.102 -4.212,1.261c-1.542,0.161 -3.047,0.703 -4.557,1.123c-0.786,0.218 -1.553,0.736 -2.277,-0.105z" fill="#FCF4F2" id="svg_153"/>
|
||||
<path d="m511.621,227.118881c-3.305,-4.685 -4.944,-9.95 -5.001,-15.629c-0.044,-4.401 1.644,-8.317 4.647,-11.502c4.067,-4.313 8.555,-3.465 12.441,-0.361c3.542,2.83 5.056,6.77 5.474,11.091c0.534,5.52 -0.699,10.843 -2.418,16.059c-0.983,-0.041 -1.967,-0.142 -2.947,-0.111c-4.066,0.131 -8.131,0.299 -12.196,0.453z" fill="#FDFDFE" id="svg_154"/>
|
||||
<path d="m651.961,221.104881c0.457,1.649 1.046,3.198 1.302,4.8c1.302,8.173 -0.427,15.373 -7.132,20.786c-1.133,0.914 -2.531,1.579 -3.906,2.097c-2.092,0.788 -3.43,0.253 -4.71,-1.598c-1.317,-1.906 -1.929,-4.125 -1.956,-6.347c-0.043,-3.592 0.49,-7.119 2.83,-10.114c3.502,-4.483 7.622,-8.064 13.572,-9.624z" fill="#FCDACF" id="svg_155"/>
|
||||
<path d="m511.621,227.118881c4.065,-0.154 8.13,-0.323 12.197,-0.451c0.98,-0.031 1.964,0.07 2.947,0.111c-1.078,3.612 -2.945,6.8 -5.242,10.06c-4.142,-2.56 -7.428,-5.713 -9.902,-9.72z" fill="#FCDACF" id="svg_156"/>
|
||||
<path d="m137.585,256.566881c-0.232,1.81 -0.466,3.621 -0.694,5.431c-0.152,1.21 -0.755,1.98 -2.018,2.163c-2.277,0.33 -4.455,1.03 -6.873,0.68c-4.262,-0.616 -6.739,-3.083 -7.888,-6.96c-1.494,-5.037 -0.23,-9.754 2.481,-14.042c2.013,-3.184 5.467,-3.625 8.715,-1.513c3.204,2.084 4.885,5.144 5.587,8.784c0.345,1.788 0.465,3.616 0.69,5.457z" fill="#FCF4F2" id="svg_163"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
@@ -0,0 +1,7 @@
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
|
||||
<svg width="256px" height="256px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
|
||||
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<g id="SVGRepo_iconCarrier"> <path opacity="0.5" d="M3 10C3 6.22876 3 4.34315 4.17157 3.17157C5.34315 2 7.22876 2 11 2H13C16.7712 2 18.6569 2 19.8284 3.17157C21 4.34315 21 6.22876 21 10V14C21 17.7712 21 19.6569 19.8284 20.8284C18.6569 22 16.7712 22 13 22H11C7.22876 22 5.34315 22 4.17157 20.8284C3 19.6569 3 17.7712 3 14V10Z" fill="#c67e48"/> <path d="M16.5189 16.5013C16.6939 16.3648 16.8526 16.2061 17.1701 15.8886L21.1275 11.9312C21.2231 11.8356 21.1793 11.6708 21.0515 11.6264C20.5844 11.4644 19.9767 11.1601 19.4083 10.5917C18.8399 10.0233 18.5356 9.41561 18.3736 8.94849C18.3292 8.82066 18.1644 8.77687 18.0688 8.87254L14.1114 12.8299C13.7939 13.1474 13.6352 13.3061 13.4987 13.4811C13.3377 13.6876 13.1996 13.9109 13.087 14.1473C12.9915 14.3476 12.9205 14.5606 12.7786 14.9865L12.5951 15.5368L12.3034 16.4118L12.0299 17.2323C11.9601 17.4419 12.0146 17.6729 12.1708 17.8292C12.3271 17.9854 12.5581 18.0399 12.7677 17.9701L13.5882 17.6966L14.4632 17.4049L15.0135 17.2214L15.0136 17.2214C15.4394 17.0795 15.6524 17.0085 15.8527 16.913C16.0891 16.8004 16.3124 16.6623 16.5189 16.5013Z" fill="#c67e48"/> <path d="M22.3665 10.6922C23.2112 9.84754 23.2112 8.47812 22.3665 7.63348C21.5219 6.78884 20.1525 6.78884 19.3078 7.63348L19.1806 7.76071C19.0578 7.88348 19.0022 8.05496 19.0329 8.22586C19.0522 8.33336 19.0879 8.49053 19.153 8.67807C19.2831 9.05314 19.5288 9.54549 19.9917 10.0083C20.4545 10.4712 20.9469 10.7169 21.3219 10.847C21.5095 10.9121 21.6666 10.9478 21.7741 10.9671C21.945 10.9978 22.1165 10.9422 22.2393 10.8194L22.3665 10.6922Z" fill="#c67e48"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M7.25 9C7.25 8.58579 7.58579 8.25 8 8.25H14.5C14.9142 8.25 15.25 8.58579 15.25 9C15.25 9.41421 14.9142 9.75 14.5 9.75H8C7.58579 9.75 7.25 9.41421 7.25 9ZM7.25 13C7.25 12.5858 7.58579 12.25 8 12.25H11C11.4142 12.25 11.75 12.5858 11.75 13C11.75 13.4142 11.4142 13.75 11 13.75H8C7.58579 13.75 7.25 13.4142 7.25 13ZM7.25 17C7.25 16.5858 7.58579 16.25 8 16.25H9.5C9.91421 16.25 10.25 16.5858 10.25 17C10.25 17.4142 9.91421 17.75 9.5 17.75H8C7.58579 17.75 7.25 17.4142 7.25 17Z" fill="#c67e48"/> </g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1,406 @@
|
||||
<?xml version="1.0"?>
|
||||
<svg width="180" height="140" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<g class="layer">
|
||||
<title>Layer 1</title>
|
||||
<g id="BACKGROUND"/>
|
||||
<g id="OBJECTS">
|
||||
<g id="svg_2">
|
||||
<g id="svg_3">
|
||||
<g id="svg_5"/>
|
||||
<g id="svg_20"/>
|
||||
<g id="svg_35"/>
|
||||
</g>
|
||||
<g id="svg_48">
|
||||
<g id="svg_51"/>
|
||||
<g id="svg_56"/>
|
||||
<g id="svg_70">
|
||||
<g id="svg_74"/>
|
||||
<g id="svg_76"/>
|
||||
<g id="svg_78"/>
|
||||
<g id="svg_80"/>
|
||||
</g>
|
||||
<g id="svg_82">
|
||||
<g id="svg_86"/>
|
||||
<g id="svg_88"/>
|
||||
<g id="svg_90"/>
|
||||
<g id="svg_92"/>
|
||||
</g>
|
||||
<g id="svg_95"/>
|
||||
<g id="svg_100"/>
|
||||
</g>
|
||||
<g id="svg_104">
|
||||
<g id="svg_116"/>
|
||||
<g id="svg_118"/>
|
||||
<g id="svg_126"/>
|
||||
<g id="svg_135"/>
|
||||
</g>
|
||||
<g id="svg_146">
|
||||
<g id="svg_155"/>
|
||||
<g id="svg_157"/>
|
||||
<g id="svg_165"/>
|
||||
<g id="svg_175"/>
|
||||
<g id="svg_180">
|
||||
<g id="svg_184"/>
|
||||
<g id="svg_186"/>
|
||||
<g id="svg_188"/>
|
||||
<g id="svg_190"/>
|
||||
</g>
|
||||
<g id="svg_192">
|
||||
<g id="svg_196"/>
|
||||
<g id="svg_198"/>
|
||||
<g id="svg_200"/>
|
||||
<g id="svg_202"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_205">
|
||||
<g id="svg_206"/>
|
||||
<g id="svg_209"/>
|
||||
</g>
|
||||
<g id="svg_213">
|
||||
<g id="svg_214">
|
||||
<g id="svg_216"/>
|
||||
</g>
|
||||
<g id="svg_219">
|
||||
<g id="svg_221"/>
|
||||
</g>
|
||||
<g id="svg_224">
|
||||
<g id="svg_226"/>
|
||||
</g>
|
||||
<g id="svg_228"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_231">
|
||||
<g id="svg_232">
|
||||
<g id="svg_233">
|
||||
<g id="svg_234">
|
||||
<path d="m76.933,96.939l-6.102,30.954l-0.712,0l4.014,-32.367c0,0 2.8,2.119 2.8,1.413z" fill="#2D294C" id="svg_235"/>
|
||||
<path d="m81.83,96.939l-6.102,30.954l-0.712,0l4.014,-32.367c0.001,0 2.8,2.119 2.8,1.413z" fill="#2D294C" id="svg_236"/>
|
||||
</g>
|
||||
<g id="svg_237">
|
||||
<path d="m98.866,96.939l6.102,30.954l0.712,0l-4.014,-32.367c-0.001,0 -2.8,2.119 -2.8,1.413z" fill="#2D294C" id="svg_238"/>
|
||||
<path d="m93.968,96.939l6.102,30.954l0.712,0l-4.014,-32.367c0,0 -2.8,2.119 -2.8,1.413z" fill="#2D294C" id="svg_239"/>
|
||||
</g>
|
||||
</g>
|
||||
<path d="m103.771,77.118l-3.711,-11.503c-1.121,-3.475 -4.355,-5.83 -8.007,-5.83l-1.946,0l-1.864,0l-4.498,0c-3.651,0 -6.886,2.355 -8.007,5.83l-3.711,11.503l-0.748,0c-1.852,0 -3.353,1.501 -3.353,3.353l0,5.309c0,6.848 5.551,12.399 12.399,12.399l7.917,0l1.864,0l5.365,0c6.848,0 12.399,-5.551 12.399,-12.399l0,-5.309c0,-1.852 -1.501,-3.353 -3.353,-3.353l-0.746,0z" fill="#9292AA" id="svg_240"/>
|
||||
<g id="svg_241">
|
||||
<g id="svg_242">
|
||||
<rect fill="#2D294C" height="30.178" id="svg_243" transform="matrix(0.374 -0.9274 0.9274 0.374 31.4581 419.478)" width="0.486" x="310.463764" y="-78.932512"/>
|
||||
</g>
|
||||
<g id="svg_244">
|
||||
<rect fill="#2D294C" height="0.486" id="svg_245" transform="matrix(0.9271 -0.3748 0.3748 0.9271 -46.1495 135.573)" width="30.178" x="119.351398" y="23.748997"/>
|
||||
</g>
|
||||
</g>
|
||||
<path d="m103.942,77.118l-0.171,0l-3.711,-11.503c-1.121,-3.475 -4.355,-5.83 -8.007,-5.83l-1.946,0l-1.864,0l-4.498,0c-3.651,0 -6.886,2.355 -8.007,5.83l-3.711,11.503l-0.01,0l0,12.579c0,4.58 3.633,8.447 8.213,8.482c0.032,0 0.064,0 0.096,0l7.917,0l1.864,0l5.365,0c0.088,0 0.176,-0.001 0.264,-0.003c4.577,-0.096 8.206,-3.907 8.206,-8.485l0,-12.573z" fill="#CFCCDF" id="svg_246"/>
|
||||
<path d="m70.573,93.419c2.27,2.894 5.79,4.76 9.753,4.76l7.917,0l1.864,0l5.365,0c3.963,0 7.483,-1.866 9.753,-4.76l-34.652,0z" fill="#9292AA" id="svg_247"/>
|
||||
</g>
|
||||
<g id="svg_248">
|
||||
<g id="svg_249"/>
|
||||
<g id="svg_251"/>
|
||||
<g id="svg_253"/>
|
||||
<g id="svg_255"/>
|
||||
<g id="svg_260"/>
|
||||
<g id="svg_264"/>
|
||||
<g id="svg_268">
|
||||
<g id="svg_269"/>
|
||||
<g id="svg_271"/>
|
||||
<g id="svg_273"/>
|
||||
<g id="svg_275"/>
|
||||
<g id="svg_277"/>
|
||||
<g id="svg_279"/>
|
||||
<g id="svg_281"/>
|
||||
<g id="svg_283"/>
|
||||
<g id="svg_285"/>
|
||||
<g id="svg_287"/>
|
||||
<g id="svg_289"/>
|
||||
<g id="svg_291"/>
|
||||
<g id="svg_293"/>
|
||||
<g id="svg_295"/>
|
||||
<g id="svg_297"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_299">
|
||||
<g id="svg_300"/>
|
||||
<g id="svg_308"/>
|
||||
<g id="svg_313"/>
|
||||
<g id="svg_315">
|
||||
<g id="svg_319"/>
|
||||
<g id="svg_321"/>
|
||||
<g id="svg_323"/>
|
||||
<g id="svg_325"/>
|
||||
</g>
|
||||
<g id="svg_329"/>
|
||||
</g>
|
||||
<g id="svg_345">
|
||||
<polygon fill="#D7914E" id="svg_346" points="105.989990234375,118.74800109863281 106.02999877929688,125.25300598144531 106.30999755859375,127.28900146484375 109.94100952148438,127.48800659179688 110.94900512695312,126.00199890136719 110.30099487304688,117.8699951171875 "/>
|
||||
<rect fill="#2D294C" height="15.55" id="svg_347" width="21.717" x="76.059" y="78.354"/>
|
||||
<path d="m68.326,77.424c-0.902,-0.263 -1.782,-0.6 -2.629,-1.005c-1.383,-0.661 -2.848,-1.829 -2.702,-3.355c0.096,-1.006 0.995,-3.146 1.705,-3.865c1.915,-1.94 7.913,-13.124 9.945,-14.721c0.888,-0.698 9.498,-4.708 9.904,-2.435c0.077,0.43 -0.507,3.353 -0.629,3.772c-0.972,3.357 -3.12,4.862 -5.219,7.656c-1.819,2.422 -4.013,4.552 -5.728,7.048c0.632,1.036 1.411,2.12 2.385,2.843c3.033,2.25 6.071,4.491 9.116,6.724c-0.729,0.863 -1.156,1.903 -1.474,2.987c-5.004,-1.626 -9.621,-4.173 -14.674,-5.649z" fill="#D7914E" id="svg_348"/>
|
||||
<path d="m103.921,77.424c0.902,-0.263 1.782,-0.6 2.629,-1.005c1.383,-0.661 2.848,-1.829 2.702,-3.355c-0.096,-1.006 -0.995,-3.146 -1.705,-3.865c-1.915,-1.94 -7.913,-13.124 -9.945,-14.721c-0.888,-0.698 -9.498,-4.708 -9.904,-2.435c-0.077,0.43 0.507,3.353 0.629,3.772c0.972,3.357 3.12,4.862 5.219,7.656c1.819,2.422 4.013,4.552 5.728,7.048c-0.632,1.036 -1.411,2.12 -2.385,2.843c-3.033,2.25 -6.071,4.491 -9.116,6.724c0.729,0.863 1.156,1.903 1.474,2.987c5.004,-1.626 9.621,-4.173 14.674,-5.649z" fill="#D7914E" id="svg_349"/>
|
||||
<path d="m92.764,74.378c0,1.592 -1.348,2.882 -3.011,2.882c-1.663,0 -3.011,-1.29 -3.011,-2.882c0,-1.592 1.348,-2.882 3.011,-2.882c1.663,-0.001 3.011,1.29 3.011,2.882z" fill="#A0616B" id="svg_350"/>
|
||||
<g id="svg_351">
|
||||
<path d="m104.514,121.741l-0.079,-1.984c-0.668,-10.878 -2.243,-21.139 -3.588,-24.237c-1.649,-1.06 -7.638,-2.66 -13.028,-3.43l-4.808,-0.687l1.373,-9.615l4.808,0.687c4.352,0.622 14.889,2.484 18.744,6.338c1.096,1.095 4.427,1.511 6.193,30.349l-0.116,2.139l-9.499,0.44zm-3.804,-26.502l0,0l0,0z" fill="#2D294C" id="svg_352"/>
|
||||
</g>
|
||||
<g id="svg_353">
|
||||
<path d="m75.533,124.821l-0.868,-4.778c-0.162,-0.892 -2.938,-16.203 -3.432,-23.825c-0.527,-8.127 4.433,-11.396 7.13,-11.99l4.743,-1.044l2.088,9.485l-4.083,0.899c-0.103,0.258 -0.261,0.86 -0.186,2.021c0.458,7.067 3.258,22.503 3.296,22.716l0.869,4.778l-9.557,1.738z" fill="#2D294C" id="svg_354"/>
|
||||
</g>
|
||||
<path d="m99.534,58.12c-0.233,-3.776 -5.585,-7.346 -12.204,-7.346c-6.619,0 -12.365,1.538 -13.534,5.16c-0.585,1.811 2.424,21.23 2.401,22.425c-0.075,3.845 -0.257,5.671 -0.257,5.671l21.993,0.187c0,0 0.083,-2.617 -0.09,-5.498c-0.11,-1.83 1.812,-18.636 1.691,-20.599z" fill="#D7914E" id="svg_355"/>
|
||||
<path d="m86.871,53.229l-0.575,0c-1.537,0 -2.783,-1.246 -2.783,-2.783l0,-8.716l6.141,0l0,8.716c0,1.537 -1.246,2.783 -2.783,2.783z" fill="#EFAFC0" id="svg_356"/>
|
||||
<path d="m83.514,44.073c0.813,2.108 2.586,5.356 6.14,6.155l0,-8.499l-5.55,0l-0.59,0.259l0,2.085z" fill="#D98E9D" id="svg_357" opacity="0.5"/>
|
||||
<path d="m88.507,31.913l-1.4,0.188c-2.784,0.374 -4.738,2.933 -4.364,5.717l0.64,4.77c0.378,2.815 2.966,4.791 5.781,4.413l2.485,-0.334c2.154,-0.289 3.665,-2.269 3.376,-4.423l-0.801,-5.967c-0.373,-2.783 -2.933,-4.737 -5.717,-4.364z" fill="#EFAFC0" id="svg_358"/>
|
||||
<path d="m85.319,41.204c0.201,1.498 -0.85,2.874 -2.348,3.075c-1.497,0.201 -2.874,-0.85 -3.075,-2.348c-0.201,-1.498 0.85,-2.874 2.348,-3.075c1.497,-0.201 2.874,0.85 3.075,2.348z" fill="#EFAFC0" id="svg_359"/>
|
||||
<path d="m82.98,39.098c0.342,0.948 0.611,1.922 0.805,2.91c0.098,-0.121 0.301,-0.197 0.452,-0.161" fill="none" id="svg_360"/>
|
||||
<path d="m79.405,36.266c0.064,0.203 0.171,0.389 0.277,0.573c0.448,0.779 0.895,1.558 1.343,2.337c0.533,-0.343 1.228,-0.251 1.862,-0.25c0.168,0.696 0.253,1.418 0.279,2.134c0.23,-0.047 0.468,-0.064 0.703,-0.052c-0.081,-0.824 -0.109,-1.66 -0.135,-2.487c0.207,-0.25 0.524,-0.439 0.753,-0.669c-0.368,-0.4 -0.571,-0.938 -0.615,-1.479c-0.056,-0.687 0.26,-1.236 0.6,-1.836c0.34,-0.6 0.843,-1.132 1.487,-1.379c1.383,-0.531 2.731,-1.006 4.207,-0.891c0.582,0.045 1.221,0.088 1.716,0.399c0.765,-0.017 1.343,-0.848 1.326,-1.613c-0.017,-0.765 -0.473,-1.453 -1.003,-2.004c-0.374,-0.389 -0.797,-0.736 -1.275,-0.987c-0.595,-0.313 -1.261,-0.47 -1.928,-0.554c-1.757,-0.221 -3.575,0.058 -5.185,0.796c0.294,0.06 0.505,0.162 0.799,0.222c-0.498,0.05 -0.985,0.172 -1.483,0.222c-0.53,0.053 -1.065,0.108 -1.57,0.277c-0.922,0.309 -1.687,0.985 -2.259,1.772c-0.572,0.787 -0.969,1.684 -1.345,2.581c-0.163,0.389 -0.326,0.803 -0.264,1.22c0.051,0.347 0.254,0.652 0.469,0.93c0.272,0.349 0.797,0.732 1.241,0.738z" fill="#2D294C" id="svg_361"/>
|
||||
<path d="m90.769,42.967l-2.971,-0.714c0,0 0.493,1.603 1.441,1.755c0.947,0.15 1.53,-1.041 1.53,-1.041z" fill="#FFFFFF" id="svg_362"/>
|
||||
<path d="m89.656,38.335c0.04,0.302 -0.171,0.58 -0.473,0.62c-0.302,0.04 -0.58,-0.171 -0.62,-0.473c-0.04,-0.302 0.171,-0.58 0.473,-0.62c0.302,-0.041 0.579,0.171 0.62,0.473z" fill="#2D294C" id="svg_363"/>
|
||||
<path d="m93.483,37.821c0.04,0.302 -0.171,0.58 -0.473,0.62c-0.302,0.041 -0.579,-0.171 -0.62,-0.473c-0.04,-0.302 0.171,-0.58 0.473,-0.62c0.301,-0.041 0.579,0.171 0.62,0.473z" fill="#2D294C" id="svg_364"/>
|
||||
<polygon fill="#FFFFFF" id="svg_365" points="86.84201049804688,52.28199768066406 82.7130126953125,51.47599792480469 83.10000610351562,54.055999755859375 87.48599243164062,60.11799621582031 91.87100219726562,54.82899475097656 89.93600463867188,51.08900451660156 "/>
|
||||
<path d="m83.382,49.978c0,0 3.161,1.747 6.287,0l0.359,1.527l-0.773,3.712l-2.315,-2.559l-1.996,2.689l-2.102,-3.483l0.54,-1.886z" fill="#EFF2F7" id="svg_366"/>
|
||||
<g id="svg_367">
|
||||
<path d="m77.129,132.034c0,0 -0.359,1.321 -0.166,2.048c0.193,0.728 0.945,2.038 2.77,2.094c1.825,0.057 4.203,0.173 4.061,-4.926l-6.665,0.784z" fill="#3F3E66" id="svg_368"/>
|
||||
<path d="m79.816,123.896c0.162,-0.288 -0.354,-2.445 1.152,-2.371c1.506,0.074 1.678,0.793 1.567,1.853c-0.111,1.06 -0.807,5.388 -0.807,5.388l-2.112,0.193l0.2,-5.063z" fill="#2D294C" id="svg_369"/>
|
||||
<path d="m79.5,122.394c0,0 -0.642,3.727 -0.709,3.871c-0.067,0.145 -1.992,5.885 -1.714,6.789c0.2,0.651 1.325,1.583 3.732,1.492c1.852,-0.07 3.219,-1.763 2.945,-3.596c-0.243,-1.626 -0.549,-4.7 -0.547,-4.746c0.005,-0.089 -0.298,-3.866 -0.298,-3.866s-1.764,2.646 -1.904,5.904l-0.65,-0.035c-0.001,0.001 0.162,-5.996 -0.855,-5.813z" fill="#2D294C" id="svg_370"/>
|
||||
<g id="svg_371">
|
||||
<path d="m82.319,124.27c-0.062,0 -0.124,-0.024 -0.172,-0.072c-1.036,-1.056 -2.13,-0.157 -2.176,-0.118c-0.101,0.085 -0.252,0.073 -0.339,-0.028c-0.086,-0.101 -0.074,-0.252 0.027,-0.338c0.506,-0.432 1.748,-0.955 2.831,0.148c0.093,0.095 0.092,0.247 -0.003,0.34c-0.046,0.045 -0.107,0.068 -0.168,0.068z" fill="#3F3E66" id="svg_372"/>
|
||||
</g>
|
||||
<g id="svg_373">
|
||||
<path d="m82.161,125.502c-0.062,0 -0.124,-0.024 -0.172,-0.072c-1.036,-1.055 -2.13,-0.157 -2.176,-0.118c-0.101,0.086 -0.252,0.073 -0.339,-0.028c-0.086,-0.101 -0.074,-0.252 0.027,-0.338c0.506,-0.432 1.747,-0.956 2.831,0.147c0.093,0.095 0.092,0.247 -0.003,0.34c-0.046,0.046 -0.107,0.069 -0.168,0.069z" fill="#3F3E66" id="svg_374"/>
|
||||
</g>
|
||||
<g id="svg_375">
|
||||
<path d="m82.003,126.735c-0.062,0 -0.124,-0.024 -0.172,-0.072c-1.037,-1.055 -2.13,-0.157 -2.176,-0.118c-0.101,0.085 -0.252,0.073 -0.339,-0.028c-0.086,-0.102 -0.074,-0.252 0.027,-0.338c0.505,-0.432 1.747,-0.956 2.831,0.147c0.093,0.095 0.091,0.247 -0.003,0.34c-0.046,0.046 -0.107,0.069 -0.168,0.069z" fill="#3F3E66" id="svg_376"/>
|
||||
</g>
|
||||
<g id="svg_377">
|
||||
<path d="m81.845,127.968c-0.062,0 -0.124,-0.024 -0.172,-0.072c-1.036,-1.055 -2.13,-0.157 -2.176,-0.118c-0.101,0.086 -0.252,0.073 -0.338,-0.028c-0.086,-0.101 -0.074,-0.252 0.027,-0.338c0.505,-0.432 1.747,-0.956 2.831,0.147c0.093,0.095 0.092,0.247 -0.003,0.34c-0.048,0.046 -0.109,0.069 -0.169,0.069z" fill="#3F3E66" id="svg_378"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_379">
|
||||
<path d="m109.648,124.917c0,0 -0.906,-1.496 -0.021,-1.749c0.885,-0.253 1.434,0.241 1.434,0.241l2.491,2.198l1.365,1.652l-1.831,-0.581l-3.438,-1.761z" fill="#2D294C" id="svg_380"/>
|
||||
<path d="m106.055,124.579c0,0 0.944,1.669 1.662,1.261c0.406,-0.231 0.985,-1.755 1.573,-1.617c0.588,0.138 2.961,1.476 3.837,1.794c0.876,0.318 -2.173,-2.574 -2.173,-2.574s2.521,1.913 3.304,2.2c0.784,0.286 7.197,1.07 7.591,2.986l0.064,1.261c0,0 -1.789,2.128 -6.783,1.15c-4.995,-0.978 -7.744,-0.907 -8.65,-1.133c-0.907,-0.227 -1.107,-5.191 -0.425,-5.328z" fill="#2D294C" id="svg_381"/>
|
||||
<path d="m116.03,130.259c-2.331,-0.074 -6.343,-1.231 -7.828,-1.202c-0.855,0.017 -1.737,-0.166 -2.331,-0.325c0.14,0.637 0.344,1.108 0.609,1.174c0.907,0.226 3.656,0.155 8.65,1.133c4.995,0.978 6.783,-1.15 6.783,-1.15l-0.064,-1.261c-0.423,1.066 -3.488,1.705 -5.819,1.631z" fill="#3F3E66" id="svg_382"/>
|
||||
<g id="svg_383">
|
||||
<path d="m111.068,126.016c-0.039,0.005 -0.08,0.001 -0.119,-0.014c-0.124,-0.048 -0.186,-0.187 -0.138,-0.311c0.017,-0.045 0.434,-1.105 1.172,-1.355c0.297,-0.102 0.611,-0.059 0.908,0.123c0.113,0.069 0.149,0.217 0.08,0.33c-0.069,0.113 -0.217,0.149 -0.331,0.079c-0.176,-0.107 -0.34,-0.133 -0.503,-0.078c-0.437,0.148 -0.782,0.825 -0.877,1.073c-0.032,0.086 -0.108,0.142 -0.192,0.153z" fill="#3F3E66" id="svg_384"/>
|
||||
</g>
|
||||
<g id="svg_385">
|
||||
<path d="m111.893,126.603c-0.039,0.005 -0.08,0.001 -0.119,-0.014c-0.124,-0.048 -0.186,-0.187 -0.138,-0.311c0.017,-0.045 0.434,-1.105 1.172,-1.355c0.296,-0.102 0.611,-0.059 0.908,0.123c0.113,0.069 0.149,0.217 0.08,0.33c-0.069,0.113 -0.217,0.149 -0.331,0.079c-0.176,-0.107 -0.34,-0.133 -0.503,-0.078c-0.437,0.148 -0.782,0.825 -0.878,1.073c-0.032,0.086 -0.107,0.142 -0.191,0.153z" fill="#3F3E66" id="svg_386"/>
|
||||
</g>
|
||||
<g id="svg_387">
|
||||
<path d="m112.861,126.952c-0.039,0.005 -0.08,0.001 -0.119,-0.014c-0.124,-0.048 -0.186,-0.187 -0.138,-0.311c0.017,-0.045 0.434,-1.105 1.172,-1.355c0.297,-0.1 0.612,-0.058 0.908,0.124c0.113,0.069 0.149,0.217 0.079,0.33c-0.069,0.113 -0.217,0.149 -0.331,0.079c-0.175,-0.108 -0.339,-0.133 -0.502,-0.078c-0.435,0.147 -0.781,0.824 -0.878,1.073c-0.032,0.085 -0.107,0.141 -0.191,0.152z" fill="#3F3E66" id="svg_388"/>
|
||||
</g>
|
||||
<g id="svg_389">
|
||||
<path d="m113.865,127.252c-0.039,0.005 -0.08,0.001 -0.119,-0.014c-0.124,-0.048 -0.186,-0.187 -0.138,-0.311c0.017,-0.045 0.434,-1.105 1.172,-1.355c0.297,-0.103 0.611,-0.058 0.908,0.123c0.113,0.069 0.149,0.217 0.08,0.33c-0.069,0.113 -0.217,0.149 -0.331,0.079c-0.175,-0.107 -0.339,-0.133 -0.503,-0.078c-0.437,0.148 -0.782,0.825 -0.877,1.073c-0.033,0.086 -0.108,0.141 -0.192,0.153z" fill="#3F3E66" id="svg_390"/>
|
||||
</g>
|
||||
</g>
|
||||
<path d="m85.292,83.099c-0.866,-0.069 -1.783,-0.265 -2.389,-0.888c-0.606,-0.622 0.539,-1.594 1.239,-2.108c0.968,0.44 2.076,-0.063 3.133,-0.182c0.641,-0.072 1.289,0.003 1.926,0.105c0.817,0.131 1.636,0.311 2.374,0.685c0.738,0.374 1.394,0.96 1.713,1.723c0.075,0.179 -0.024,0.35 -0.217,0.353c-0.194,0.003 -0.358,-0.133 -0.51,-0.253c-0.477,-0.376 -1.026,-0.66 -1.608,-0.834c-0.105,-0.031 -0.185,0.144 -0.086,0.191c1.456,0.689 1.801,1.611 1.775,1.694c-0.028,0.09 -0.139,0.124 -0.234,0.122c-0.171,-0.003 -0.452,-0.259 -0.594,-0.354c-0.603,-0.399 -1.146,-0.472 -1.146,-0.472s-0.619,0.036 0.899,0.801c0.187,0.094 0.381,0.221 0.443,0.421c0.011,0.035 0.017,0.073 0.01,0.109c-0.008,0.043 -0.035,0.081 -0.067,0.112c-0.098,0.093 -0.242,0.124 -0.376,0.112c-0.134,-0.011 -0.262,-0.06 -0.388,-0.109c-0.618,-0.237 -1.235,-0.475 -1.853,-0.712c0.163,0.103 1.282,0.662 1.403,0.813c0.047,0.059 0.092,0.131 0.077,0.204c-0.023,0.109 -0.157,0.146 -0.268,0.157c-0.743,0.074 -2.45,-0.542 -3.146,-0.813c-0.695,-0.269 -1.365,-0.818 -2.11,-0.877z" fill="#EFAFC0" id="svg_391"/>
|
||||
<g id="svg_392">
|
||||
<path d="m87.677,40.908c-1.249,0 -2.339,-0.926 -2.51,-2.197c-0.09,-0.671 0.086,-1.337 0.497,-1.875c0.411,-0.538 1.006,-0.884 1.677,-0.974c1.386,-0.186 2.663,0.79 2.849,2.174c0.186,1.385 -0.79,2.662 -2.175,2.849c-0.113,0.016 -0.226,0.023 -0.338,0.023zm0.003,-4.826c-0.102,0 -0.203,0.007 -0.306,0.021c-0.606,0.081 -1.145,0.394 -1.516,0.88c-0.372,0.487 -0.531,1.089 -0.45,1.695c0.169,1.252 1.324,2.137 2.575,1.966c1.252,-0.168 2.134,-1.324 1.966,-2.576c-0.154,-1.148 -1.139,-1.986 -2.269,-1.986z" fill="#2D294C" id="svg_393"/>
|
||||
</g>
|
||||
<g id="svg_394">
|
||||
<path d="m93.067,40.184c-0.552,0 -1.086,-0.179 -1.532,-0.52c-0.538,-0.411 -0.884,-1.006 -0.974,-1.677c-0.186,-1.385 0.789,-2.663 2.174,-2.849c1.384,-0.184 2.663,0.79 2.849,2.175c0.09,0.671 -0.086,1.336 -0.497,1.874c-0.411,0.538 -1.006,0.884 -1.678,0.974c-0.114,0.015 -0.228,0.023 -0.342,0.023zm0.007,-4.826c-0.102,0 -0.203,0.007 -0.306,0.021c-1.252,0.168 -2.133,1.324 -1.965,2.576c0.082,0.607 0.394,1.145 0.88,1.517c0.488,0.372 1.094,0.532 1.695,0.449c0.607,-0.081 1.146,-0.394 1.517,-0.88c0.372,-0.487 0.531,-1.088 0.45,-1.695c-0.156,-1.15 -1.141,-1.988 -2.271,-1.988z" fill="#2D294C" id="svg_395"/>
|
||||
</g>
|
||||
<g id="svg_396">
|
||||
<rect fill="#2D294C" height="0.242" id="svg_397" transform="matrix(0.9187 -0.3949 0.3949 0.9187 -20.6624 136.864)" width="2.505" x="133.483205" y="-48.057873"/>
|
||||
</g>
|
||||
<g id="svg_398">
|
||||
<path d="m90.262,87.938l20.542,0l0,-1.506l-20.542,0c-0.416,0 -0.753,0.337 -0.753,0.753l0,0c0,0.415 0.337,0.753 0.753,0.753z" fill="#A8A5C4" id="svg_399"/>
|
||||
<path d="m92.011,87.938l19.809,0c0.876,0 1.643,-0.59 1.867,-1.436l3.496,-13.172c0.325,-1.226 -0.599,-2.428 -1.867,-2.428l-17.297,0c-0.876,0 -1.643,0.589 -1.867,1.436l-4.141,15.6z" fill="#CFCCDF" id="svg_400"/>
|
||||
<path d="m115.315,70.902c1.268,0 2.193,1.202 1.867,2.428l-3.496,13.172c-0.108,0.406 -0.342,0.751 -0.652,1.002l-12.041,-16.601l14.322,0l0,-0.001z" fill="#CFCCDF" id="svg_401"/>
|
||||
<path d="m103.676,80.586c-0.217,0.859 0.324,1.556 1.208,1.556c0.884,0 1.776,-0.696 1.993,-1.556c0.217,-0.859 -0.324,-1.556 -1.208,-1.556c-0.884,0 -1.776,0.697 -1.993,1.556z" fill="#FFFFFF" id="svg_402"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_403">
|
||||
<path d="m126.823,42.216l-2.049,-3.268c0,0 0.64,-1.378 -0.06,-2.56c-0.699,-1.182 -1.769,-3.318 -2.053,-3.046s0.287,2.342 0.287,2.342s-1.883,-3.209 -3.159,-3.401c-1.276,-0.193 1.325,3.309 1.325,3.309s-2.943,-3.063 -3.122,-2.455s1.769,3.318 1.933,3.488c0.164,0.17 -2.237,-2.215 -2.411,-1.829c-0.175,0.386 2.694,4.394 3.295,4.906c0.602,0.513 5.102,3.607 5.102,3.607l0.912,-1.093z" fill="#EFAFC0" id="svg_404"/>
|
||||
<path d="m152.166,57.969l-0.035,-0.084l-9.44,-2.281l-1.141,4.72c-0.16,0.665 -4.486,17.513 -4.967,25.197c-0.286,4.586 11.326,34.167 12.018,35.98l0.099,1.43l-1.833,2.344c0,0 1.511,0.448 3.066,0.122c1.555,-0.326 2.9,-1.025 2.9,-1.025l-1.123,-4.079c0,0 -3.448,-21.082 -4.531,-27.574c-0.438,-2.627 -1.976,-4.794 -1.954,-5.541c0.256,-4.092 3.397,-11.984 5.887,-17.698c-0.193,5.235 -0.476,12.31 -0.735,16.441c-0.282,4.514 4.782,36.055 4.784,37.293l0.006,-0.012l-0.008,0.041l-2.354,2.58c0,0 1.511,0.448 3.066,0.122c1.555,-0.326 2.573,-1.911 2.573,-1.911s1.925,-36.981 2.142,-40.473c0.463,-7.413 0.469,-20.196 0.469,-20.737l0,-4.856l-8.889,0l0,0.001z" fill="#EFAFC0" id="svg_405"/>
|
||||
<path d="m141.222,34.996c-0.081,1.39 0.046,2.794 -0.178,4.168c-0.224,1.374 -0.881,2.774 -2.104,3.438c-1.778,0.965 -3.985,0.055 -5.662,-1.077c-1.677,-1.132 -3.352,-2.546 -5.374,-2.628c-2.174,-0.088 -4.267,1.672 -4.554,3.829c-0.278,2.09 1.001,4.141 2.706,5.38c1.706,1.239 3.786,1.824 5.818,2.387c2.631,0.728 5.511,1.443 8.015,0.354c1.623,-0.706 2.845,-2.08 3.982,-3.437c2.616,-3.121 5.148,-6.53 5.932,-10.527c0.536,-2.731 0.007,-8.52 -3.222,-9.671c-3.815,-1.359 -5.207,5.17 -5.359,7.784z" fill="#5387C4" id="svg_406"/>
|
||||
<path d="m163.17,56.037l0.452,10.385l-2.032,17.61l-25.06,-1.355l0.249,-2.57c0.734,-7.587 2.437,-15.048 5.067,-22.202l0.846,-2.301l20.478,0.433z" fill="#DC614B" id="svg_407"/>
|
||||
<path d="m166.608,27.766c-1.695,-1.277 -2.558,-3.404 -2.871,-5.503c-0.313,-2.1 -0.152,-4.237 -0.217,-6.359c-0.046,-1.511 -0.218,-3.057 -0.898,-4.407c-0.68,-1.35 -1.955,-2.479 -3.46,-2.629c-0.924,-0.092 -1.869,0.183 -2.779,-0.006c-0.198,0.405 -0.265,0.944 0.064,1.251c0.33,0.307 0.747,0.518 1.037,0.863c0.396,0.469 0.501,1.116 0.519,1.729c0.082,2.845 -1.37,5.539 -1.477,8.384c-0.052,1.381 0.227,2.787 0.901,3.993c0.674,1.206 1.758,2.199 3.058,2.666c1.841,0.659 4.217,0.449 6.123,0.018z" fill="#2D294C" id="svg_408"/>
|
||||
<path d="m148.76,25.747l1.141,-0.145c0,0 1.821,1.121 2.287,1.037c1.09,-0.195 1.289,-0.589 1.289,-0.589s1.077,-0.719 1.563,-0.685c6.145,0.436 5.828,4.114 6.358,6.234c0.928,3.716 -2.395,9.115 -2.395,9.115s4.126,13.236 3.942,15.548l-11.556,1.9c-3.17,0.521 -6.421,-0.151 -9.124,-1.887l0,0l1.264,-11.978l-0.833,-2.272c-1.126,-3.07 -1.414,-6.387 -0.821,-9.603c0.253,-1.368 0.523,-2.637 0.751,-3.252c0.644,-1.736 4.295,-3.189 6.134,-3.423z" fill="#6F9EDE" id="svg_409"/>
|
||||
<g id="svg_410">
|
||||
<g id="svg_411">
|
||||
<path d="m152.026,28.777l0,0c1.696,0 3.07,-1.375 3.07,-3.07l0,-8.061c0,-0.203 -0.165,-0.367 -0.367,-0.367l-5.406,0c-0.203,0 -0.367,0.164 -0.367,0.367l0,8.061c-0.001,1.695 1.374,3.07 3.07,3.07z" fill="#EFAFC0" id="svg_412"/>
|
||||
<path d="m155.096,19.621c-0.813,2.108 -2.586,5.356 -6.141,6.155l0,-8.499l5.55,0l0.59,0.259l0,2.085l0.001,0z" fill="#D98E9D" id="svg_413" opacity="0.5"/>
|
||||
</g>
|
||||
<path d="m147.55,8.2l1.413,0c2.809,0 5.086,2.277 5.086,5.086l0,4.812c0,2.84 -2.302,5.143 -5.143,5.143l-2.507,0c-2.173,0 -3.934,-1.761 -3.934,-3.934l0,-6.021c0,-2.809 2.277,-5.086 5.085,-5.086z" fill="#EFAFC0" id="svg_414"/>
|
||||
<circle cx="154.682" cy="16.984" fill="#EFAFC0" id="svg_415" r="2.736"/>
|
||||
<path d="m153.984,14.586c-0.213,0.985 -0.35,1.986 -0.41,2.991c-0.114,-0.107 -0.325,-0.155 -0.47,-0.099" fill="none" id="svg_416"/>
|
||||
<path d="m145.848,19.027l2.962,-0.753c0,0 -0.472,1.61 -1.418,1.773c-0.947,0.163 -1.544,-1.02 -1.544,-1.02z" fill="#FFFFFF" id="svg_417"/>
|
||||
<path d="m146.767,14.676c0,0.305 0.247,0.552 0.552,0.552c0.305,0 0.552,-0.247 0.552,-0.552c0,-0.305 -0.247,-0.552 -0.552,-0.552c-0.305,0 -0.552,0.247 -0.552,0.552z" fill="#2D294C" id="svg_418"/>
|
||||
<path d="m142.905,14.676c0,0.305 0.247,0.552 0.552,0.552c0.305,0 0.552,-0.247 0.552,-0.552c0,-0.305 -0.247,-0.552 -0.552,-0.552c-0.305,0 -0.552,0.247 -0.552,0.552z" fill="#2D294C" id="svg_419"/>
|
||||
<path d="m151.485,8.973c0,0 -1.112,2.88 0.312,4.687c0.434,0.551 0.784,2.299 0.931,2.984c0.153,0 0.321,-1.654 1.2,-2.207c0.304,-0.195 1.535,-0.114 1.535,-0.114s0.673,-1.691 0.818,-2.126c0.185,-0.554 0.492,-1.063 0.666,-1.62c0.581,-1.859 -0.652,-4.078 -2.537,-4.567c-0.551,-0.143 -1.122,-0.216 -1.691,-0.236c-12.074,-0.429 -10.65,5.234 -10.605,6.527c5.007,0.668 9.371,-3.328 9.371,-3.328z" fill="#2D294C" id="svg_420"/>
|
||||
</g>
|
||||
<path d="m163.38,57.685c0.802,-0.333 1.613,-0.804 1.996,-1.584c0.383,-0.78 -1.006,-1.349 -1.831,-1.621c-0.785,0.718 -1.993,0.582 -3.035,0.797c-0.632,0.13 -1.225,0.402 -1.799,0.696c-0.736,0.377 -1.46,0.802 -2.046,1.386c-0.586,0.584 -1.028,1.344 -1.095,2.169c-0.016,0.193 0.131,0.325 0.316,0.269c0.185,-0.057 0.299,-0.237 0.407,-0.398c0.337,-0.505 0.771,-0.945 1.27,-1.29c0.091,-0.063 0.221,0.08 0.141,0.155c-1.171,1.105 -1.214,2.089 -1.164,2.16c0.055,0.077 0.171,0.074 0.26,0.044c0.161,-0.056 0.349,-0.387 0.455,-0.52c0.45,-0.566 0.944,-0.804 0.944,-0.804s0.6,-0.157 -0.607,1.04c-0.148,0.147 -0.293,0.329 -0.291,0.538c0,0.037 0.006,0.074 0.024,0.106c0.021,0.038 0.059,0.066 0.098,0.085c0.122,0.058 0.268,0.043 0.392,-0.009c0.124,-0.052 0.231,-0.139 0.335,-0.223c0.514,-0.417 1.027,-0.834 1.541,-1.251c-0.123,0.149 -1.014,1.026 -1.082,1.207c-0.026,0.07 -0.047,0.153 -0.009,0.218c0.056,0.096 0.195,0.09 0.303,0.066c0.73,-0.16 2.161,-1.274 2.74,-1.746c0.58,-0.473 1.047,-1.203 1.737,-1.49z" fill="#EFAFC0" id="svg_421"/>
|
||||
<path d="m163.052,29.686c2.636,2.75 5.273,5.5 7.909,8.25c2.443,2.548 5.045,5.473 5.038,9.003c-0.005,2.685 -1.557,5.122 -3.318,7.148c-1.249,1.437 -2.708,2.804 -4.535,3.338c-1.827,0.535 -4.086,-0.005 -5.022,-1.663c-1.015,-1.796 -0.176,-4.044 0.782,-5.871c0.958,-1.827 2.065,-3.848 1.519,-5.837c-0.388,-1.416 -1.538,-2.469 -2.585,-3.498c-2.957,-2.908 -5.618,-6.529 -6.459,-10.668c-0.357,-1.759 -0.916,-3.975 1.325,-3.962c1.979,0.012 4.113,2.473 5.346,3.76z" fill="#6F9EDE" id="svg_422"/>
|
||||
<path d="m148.515,121.53l-1.375,3.425l-3.653,1.661c-1.819,0.738 -1.155,1.634 -1.155,1.634l11.019,-0.762l-1.007,-6.275l-3.829,0.317z" fill="#2D294C" id="svg_423"/>
|
||||
<path d="m152.805,125.823l-3.653,1.661c-1.819,0.738 -1.155,1.634 -1.155,1.634l10.017,-0.755l0.776,-6.717l-3.943,-0.403l0.317,1.9l-2.359,2.68z" fill="#2D294C" id="svg_424"/>
|
||||
</g>
|
||||
<g id="svg_425">
|
||||
<path d="m24.854,55.079c4.549,0.076 9.099,0.153 13.648,0.229c1.546,0.026 3.103,0.051 4.624,-0.227c3.221,-0.588 11.197,-8.695 13.435,-10.265c0.838,0.743 1.646,1.432 2.484,2.174c-2.347,4.501 -3.586,7.027 -6.704,11.032c-0.932,1.197 -1.938,2.402 -3.293,3.082c-1.054,0.528 -2.246,0.7 -3.417,0.839c-4.995,0.59 -10.047,0.696 -15.063,0.317c-0.661,-0.05 -1.359,-0.122 -1.888,-0.522c-0.461,-0.348 -0.723,-0.894 -0.967,-1.418c-1.162,-2.485 -1.699,-2.754 -2.859,-5.241z" fill="#EDA956" id="svg_426"/>
|
||||
<path d="m27.581,35.701c0,0 -7.203,0.647 -9.146,7.365c-1.943,6.718 1.781,9.308 1.781,9.308l1.376,0.081l0.648,-1.295l0.486,1.295l8.579,0.971l0.567,-0.648l0.405,0.648c0,0 4.209,-1.295 3.966,-4.694c-0.243,-3.399 -0.081,-5.018 -1.133,-7.041c-1.052,-2.023 -2.914,-4.452 -2.914,-4.452l-4.615,-1.538z" fill="#563238" id="svg_427"/>
|
||||
<g id="svg_428">
|
||||
<path d="m35.733,93.744l4.374,34.965l0.712,0l-2.287,-36.243c0.001,0.001 -2.799,1.917 -2.799,1.278z" fill="#2D294C" id="svg_429"/>
|
||||
<path d="m45.446,92.449l16.677,36.26l0.712,0l-14.589,-37.538c0,0.001 -2.8,1.917 -2.8,1.278z" fill="#2D294C" id="svg_430"/>
|
||||
<path d="m23.661,92.449l-16.677,36.26l-0.712,0l14.589,-37.538c0.001,0.001 2.8,1.917 2.8,1.278z" fill="#2D294C" id="svg_431"/>
|
||||
<path d="m31.755,93.744l-4.374,34.965l-0.712,0l2.287,-36.243c-0.001,0.001 2.799,1.917 2.799,1.278z" fill="#2D294C" id="svg_432"/>
|
||||
<g id="svg_433">
|
||||
<rect fill="#2D294C" height="0.485" id="svg_434" transform="matrix(0.4675 -0.884 0.884 0.4675 -9.1243 344.263)" width="19.406" x="230.048508" y="-67.858469"/>
|
||||
</g>
|
||||
<g id="svg_435">
|
||||
<rect fill="#2D294C" height="22.897" id="svg_436" transform="matrix(0.6928 -0.7211 0.7211 0.6928 -43.4672 259.313)" width="0.485" x="174.469854" y="-57.134714"/>
|
||||
</g>
|
||||
<g id="svg_437">
|
||||
<rect fill="#2D294C" height="0.486" id="svg_438" transform="matrix(0.6874 -0.7263 0.7263 0.6874 -50.5056 245.956)" width="24.505" x="141.987717" y="-45.752078"/>
|
||||
</g>
|
||||
<g id="svg_439">
|
||||
<polygon fill="#2D294C" id="svg_440" points="30.022003173828125,107.79200744628906 20.957000732421875,92.89999389648438 21.37298583984375,92.64700317382812 30.43798828125,107.53900146484375 "/>
|
||||
</g>
|
||||
<path d="m18.899,55.868c0,0 10.684,-2.59 14.245,19.101l0.56,2.427c0.816,3.538 3.897,6.093 7.525,6.241l4.676,0.191c3.044,0.124 5.701,2.099 6.697,4.978l1.262,3.645l2.586,0c0,0 -0.971,5.18 -6.151,5.18c-5.18,0 -17.155,0 -17.155,0l-11.007,0c0,0 -9.065,0.648 -10.684,-11.331c-1.619,-11.979 -7.447,-31.08 7.446,-30.432z" fill="#5A8ECC" id="svg_441"/>
|
||||
<path d="m59.2,124.768l2.354,2.58l3.653,1.66c1.819,0.738 1.155,1.635 1.155,1.635l-10.017,-0.755l-0.757,-3.442l3.612,-1.678z" fill="#2D294C" id="svg_442"/>
|
||||
<path d="m55.342,116.368l3.858,8.4l2.354,2.58c0,0 -1.511,0.448 -3.066,0.122c-1.555,-0.326 -2.9,-1.025 -2.9,-1.025l-4.08,-6.569l-0.47,-3.327l4.304,-0.181z" fill="#EFAFC0" id="svg_443"/>
|
||||
<polygon fill="#EFAFC0" id="svg_444" points="46.364990234375,119.7550048828125 43.110992431640625,122.447998046875 43.89599609375,126.93699645996094 46.701995849609375,128.0590057373047 41.989013671875,127.94700622558594 39.519989013671875,121.55099487304688 42.217010498046875,118.19599914550781 "/>
|
||||
<path d="m46.702,128.059c0,0 0.991,0.013 1.331,0.391c0.34,0.377 0,0.944 0,0.944l-7.615,-0.213l-2.581,-7.406l1.683,-0.224l2.469,6.396l4.713,0.112z" fill="#2D294C" id="svg_445"/>
|
||||
<g id="svg_446">
|
||||
<path d="m47.267,120.77l-7.793,-3.96l1.98,-3.896c3.337,-6.565 6.933,-14.445 8.429,-18.673c-4.337,-0.627 -12.462,-1.279 -19.433,-1.635l-4.365,-0.222l0.446,-8.73l4.365,0.222c23.576,1.202 25.569,2.862 26.639,3.754c3.192,2.661 2.048,6.952 -2.774,17.706c-2.604,5.804 -5.396,11.306 -5.513,11.538l-1.981,3.896zm5.396,-25.969l0,0l0,0z" fill="#9D3542" id="svg_447"/>
|
||||
</g>
|
||||
<path d="m38.418,93.753l-8.349,0.463c-4.148,0 -9.259,-3.453 -9.259,-7.601l-0.124,-2.204l16.226,-1.472l1.506,10.814z" fill="#DC614B" id="svg_448"/>
|
||||
<g id="svg_449">
|
||||
<path d="m49.849,123.164l-0.339,-4.357c-0.68,-8.739 -2.282,-19.42 -3.618,-21.755c-1.457,-1.115 -7.727,-2.828 -13.42,-3.641l-4.326,-0.618l1.236,-8.653l4.327,0.618c4.303,0.615 14.717,2.449 18.469,6.2c3.528,3.527 5.236,16.723 6.047,27.171l0.339,4.357l-8.715,0.678zm-4.032,-26.233l0,0l0,0z" fill="#DC614B" id="svg_450"/>
|
||||
</g>
|
||||
<path d="m30.622,55.47c0,0 -6.79,-0.266 -7.257,-0.224c-1.32,0.117 -3.098,0.479 -3.965,0.973c-1.958,1.114 -2.138,2.935 -2.305,4.379c-0.226,1.95 4.137,11.453 4.137,11.453l-1.692,8.038c0,0 -0.26,3.501 0,5.711l17.557,-2.327l-1.423,-6.738l-1.457,-8.822c0,0 2.514,-4.672 0.89,-6.394c-3.922,-4.16 -4.485,-6.049 -4.485,-6.049z" fill="#FFCD8B" id="svg_451"/>
|
||||
<g id="svg_452">
|
||||
<path d="m26.036,58.4l0,0c-1.314,0 -2.378,-1.065 -2.378,-2.379l0,-10.132c0,-0.157 0.127,-0.285 0.285,-0.285l4.188,0c0.157,0 0.285,0.127 0.285,0.285l0,10.132c-0.001,1.314 -1.066,2.379 -2.38,2.379z" fill="#EFAFC0" id="svg_453"/>
|
||||
<path d="m23.658,51.307c0.63,1.633 2.003,4.149 4.757,4.768l0,-10.471l-4.3,0l-0.457,0.201l0,5.502z" fill="#D98E9D" id="svg_454" opacity="0.5"/>
|
||||
</g>
|
||||
<path d="m30.232,37.823l-1.413,0c-2.809,0 -5.086,2.277 -5.086,5.086l0,4.812c0,2.84 2.302,5.143 5.143,5.143l0.659,0c3.193,0 5.782,-2.589 5.782,-5.782l0,-4.173c0,-2.809 -2.277,-5.086 -5.085,-5.086z" fill="#EFAFC0" id="svg_455"/>
|
||||
<path d="m25.836,46.607c0,1.511 -1.225,2.736 -2.736,2.736s-2.736,-1.225 -2.736,-2.736c0,-1.511 1.225,-2.736 2.736,-2.736s2.736,1.225 2.736,2.736z" fill="#EFAFC0" id="svg_456"/>
|
||||
<path d="m23.798,42.265c0.213,0.985 0.35,1.986 0.41,2.991c0.114,-0.106 0.325,-0.155 0.47,-0.099" fill="none" id="svg_457"/>
|
||||
<path d="m31.935,48.65l-2.962,-0.753c0,0 0.472,1.61 1.418,1.773c0.946,0.163 1.544,-1.02 1.544,-1.02z" fill="#FFFFFF" id="svg_458"/>
|
||||
<circle cx="30.464" cy="44.299" fill="#2D294C" id="svg_459" r="0.552"/>
|
||||
<path d="m34.877,44.299c0,0.305 -0.247,0.552 -0.552,0.552c-0.305,0 -0.552,-0.247 -0.552,-0.552c0,-0.305 0.247,-0.552 0.552,-0.552c0.305,0 0.552,0.247 0.552,0.552z" fill="#2D294C" id="svg_460"/>
|
||||
<path d="m11.452,86.3c-0.717,-5.305 -2.256,-12.005 -2.459,-17.77l0,0l5.055,14.542c1.072,3.084 3.979,5.151 7.244,5.151l3.238,0c2.861,0 5.44,1.725 6.532,4.37l2.081,5.039l-11.007,0c0,-0.001 -9.065,0.647 -10.684,-11.332z" fill="#6F9EDE" id="svg_461"/>
|
||||
<path d="m23.69,89.615c-0.835,-0.239 -1.695,-0.614 -2.165,-1.344c-0.47,-0.73 0.845,-1.456 1.633,-1.82c0.862,0.623 2.047,0.35 3.107,0.443c0.643,0.057 1.263,0.258 1.867,0.485c0.774,0.29 1.542,0.63 2.191,1.142c0.649,0.513 1.176,1.218 1.337,2.029c0.038,0.19 -0.093,0.338 -0.283,0.303c-0.19,-0.035 -0.324,-0.201 -0.449,-0.349c-0.393,-0.463 -0.874,-0.851 -1.41,-1.136c-0.097,-0.052 -0.21,0.105 -0.122,0.17c1.29,0.964 1.446,1.936 1.404,2.013c-0.046,0.083 -0.161,0.093 -0.254,0.073c-0.167,-0.037 -0.391,-0.344 -0.512,-0.464c-0.512,-0.51 -1.03,-0.69 -1.03,-0.69s-0.614,-0.087 0.722,0.964c0.164,0.129 0.329,0.293 0.351,0.5c0.004,0.037 0.003,0.075 -0.012,0.109c-0.017,0.041 -0.051,0.072 -0.088,0.096c-0.114,0.072 -0.262,0.074 -0.391,0.036c-0.129,-0.038 -0.245,-0.111 -0.359,-0.184c-0.558,-0.355 -1.116,-0.71 -1.675,-1.066c0.14,0.133 1.125,0.903 1.214,1.075c0.034,0.067 0.065,0.147 0.034,0.216c-0.044,0.102 -0.183,0.112 -0.294,0.101c-0.743,-0.075 -2.293,-1.017 -2.922,-1.421c-0.628,-0.404 -1.176,-1.076 -1.894,-1.281z" fill="#EFAFC0" id="svg_462"/>
|
||||
<path d="m19.326,56.368c0,0 -7.823,5.529 -9.727,14.837c-1.904,9.307 4.138,16.479 11.694,17.018l2.888,-1.432c0,0 -9.08,-9.595 -8.445,-12.768c0.635,-3.173 5.921,-10.222 5.921,-10.222s-0.521,-8.173 -2.331,-7.433z" fill="#FFCD8B" id="svg_463"/>
|
||||
</g>
|
||||
<path d="m24.036,44.593c0,0 7.272,-1.157 8.422,-4.988c0,0 0.668,3.521 2.86,3.303c0,0 1.247,-7.694 -6.037,-7.37c-6.637,0.295 -7.568,3.739 -7.313,8.592c0,0.001 1.62,-0.817 2.068,0.463z" fill="#563238" id="svg_464"/>
|
||||
<path d="m23.442,47.902c0,0.278 -0.226,0.504 -0.504,0.504c-0.278,0 -0.504,-0.226 -0.504,-0.504c0,-0.278 0.226,-0.504 0.504,-0.504c0.278,0 0.504,0.225 0.504,0.504z" fill="#FFFFFF" id="svg_465"/>
|
||||
<path d="m56.561,44.817l2.687,-2.855c0,0 -0.068,-2.583 0.408,-4.01c0.476,-1.427 0.476,-1.427 0.476,-1.427s0.748,-0.136 0.68,0.952c-0.068,1.088 0.068,2.039 0.068,2.039s3.195,-5.709 4.01,-5.981c0.816,-0.272 -0.748,3.127 -0.952,3.602c-0.204,0.476 2.651,-3.058 2.991,-2.923c0.34,0.136 -0.884,2.515 -2.039,3.806c0,0 2.787,-2.447 3.127,-2.243c0.34,0.204 -1.555,2.214 -1.555,2.214s2.708,-2.37 0.671,0.776c-0.866,1.337 -4.962,4.554 -5.437,4.758c-0.476,0.204 -2.651,3.466 -2.651,3.466l-2.484,-2.174z" fill="#EFAFC0" id="svg_466"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_467"/>
|
||||
<g id="svg_480">
|
||||
<g id="svg_481"/>
|
||||
<g id="svg_500">
|
||||
<g id="svg_502"/>
|
||||
<g id="svg_517"/>
|
||||
<g id="svg_532"/>
|
||||
</g>
|
||||
<g id="svg_545">
|
||||
<g id="svg_553"/>
|
||||
<g id="svg_555"/>
|
||||
</g>
|
||||
<g id="svg_570">
|
||||
<g id="svg_572"/>
|
||||
</g>
|
||||
<g id="svg_591">
|
||||
<g id="svg_592"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_596">
|
||||
<g id="svg_597">
|
||||
<g id="svg_600"/>
|
||||
<g id="svg_612"/>
|
||||
<g id="svg_621">
|
||||
<g id="svg_625"/>
|
||||
<g id="svg_627"/>
|
||||
<g id="svg_629"/>
|
||||
<g id="svg_631"/>
|
||||
</g>
|
||||
<g id="svg_633">
|
||||
<g id="svg_637"/>
|
||||
<g id="svg_639"/>
|
||||
<g id="svg_641"/>
|
||||
<g id="svg_643"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_645">
|
||||
<g id="svg_646">
|
||||
<g id="svg_647"/>
|
||||
<g id="svg_650"/>
|
||||
</g>
|
||||
<g id="svg_656">
|
||||
<g id="svg_657"/>
|
||||
<g id="svg_660"/>
|
||||
</g>
|
||||
<g id="svg_666">
|
||||
<g id="svg_671"/>
|
||||
<g id="svg_677"/>
|
||||
<g id="svg_680"/>
|
||||
<g id="svg_691"/>
|
||||
</g>
|
||||
<g id="svg_700">
|
||||
<g id="svg_704"/>
|
||||
<g id="svg_706"/>
|
||||
<g id="svg_709"/>
|
||||
</g>
|
||||
<g id="svg_730"/>
|
||||
</g>
|
||||
<g id="svg_735">
|
||||
<g id="svg_736"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_740">
|
||||
<g id="svg_742">
|
||||
<g id="svg_743"/>
|
||||
<g id="svg_746"/>
|
||||
<g id="svg_749"/>
|
||||
<g id="svg_752"/>
|
||||
<g id="svg_755"/>
|
||||
</g>
|
||||
<g id="svg_758"/>
|
||||
</g>
|
||||
<g id="svg_761">
|
||||
<g id="svg_762"/>
|
||||
<g id="svg_765"/>
|
||||
</g>
|
||||
<g id="svg_768">
|
||||
<g id="svg_771">
|
||||
<g id="svg_772"/>
|
||||
<g id="svg_774"/>
|
||||
</g>
|
||||
<g id="svg_776">
|
||||
<g id="svg_777"/>
|
||||
<g id="svg_779"/>
|
||||
</g>
|
||||
<g id="svg_781">
|
||||
<g id="svg_782"/>
|
||||
<g id="svg_784"/>
|
||||
</g>
|
||||
<g id="svg_786">
|
||||
<g id="svg_787"/>
|
||||
<g id="svg_789"/>
|
||||
</g>
|
||||
<g id="svg_791">
|
||||
<g id="svg_792"/>
|
||||
<g id="svg_794"/>
|
||||
</g>
|
||||
<g id="svg_796">
|
||||
<g id="svg_797"/>
|
||||
<g id="svg_799"/>
|
||||
</g>
|
||||
<g id="svg_801">
|
||||
<g id="svg_802"/>
|
||||
<g id="svg_804"/>
|
||||
</g>
|
||||
<g id="svg_806">
|
||||
<g id="svg_807"/>
|
||||
<g id="svg_809"/>
|
||||
</g>
|
||||
<g id="svg_811"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="DESIGNED_BY_FREEPIK">
|
||||
<g id="svg_814">
|
||||
<g id="XMLID_319_">
|
||||
<g id="XMLID_338_"/>
|
||||
</g>
|
||||
<g id="svg_815"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 35 KiB |
@@ -0,0 +1,287 @@
|
||||
<?xml version="1.0"?>
|
||||
<svg width="120" height="240" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<g class="layer">
|
||||
<title>Layer 1</title>
|
||||
<g id="OBJECTS">
|
||||
<g id="svg_2">
|
||||
<g id="svg_3">
|
||||
<g id="svg_4">
|
||||
<g id="svg_5">
|
||||
<g id="svg_6"/>
|
||||
</g>
|
||||
<g id="svg_13">
|
||||
<g id="svg_14">
|
||||
<g id="svg_15">
|
||||
<g id="svg_17">
|
||||
<g id="svg_18"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_22">
|
||||
<g id="svg_23">
|
||||
<g id="svg_25">
|
||||
<g id="svg_26"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_29">
|
||||
<g id="svg_31"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_33">
|
||||
<g id="svg_34">
|
||||
<g id="svg_35">
|
||||
<g id="svg_37">
|
||||
<g id="svg_38"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_42"/>
|
||||
<g id="svg_45">
|
||||
<g id="svg_46"/>
|
||||
<g id="svg_49">
|
||||
<g id="svg_50"/>
|
||||
<g id="svg_53"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_57">
|
||||
<g id="svg_59"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_63">
|
||||
<g id="svg_69"/>
|
||||
<g id="svg_72">
|
||||
<g id="svg_78"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_87">
|
||||
<g id="svg_89">
|
||||
<g id="svg_90">
|
||||
<g id="svg_91">
|
||||
<g id="svg_94"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_98">
|
||||
<g id="svg_99"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_105">
|
||||
<g id="svg_106">
|
||||
<g id="svg_107">
|
||||
<g id="svg_108">
|
||||
<path d="m61.3,141.244l-2.689,64.307l-4.824,-0.362c0,0 -5.422,-36.369 -4.115,-54.575l11.628,-9.37z" fill="#EDBC9C" id="svg_109"/>
|
||||
<g id="svg_110">
|
||||
<path d="m63.882,214.201c0,0 6.033,2.309 6.64,4.92l-13.121,0l-2.064,-4.069l-1.397,4.069l-1.412,0c0,0 -1.324,-10.555 1.259,-13.931l10.095,9.011z" fill="#20202A" id="svg_111"/>
|
||||
<path d="m58.7,203.276l-0.089,2.275l5.837,9.767c0,0 -6.44,1.208 -8.852,-3.375c-2.412,-4.583 -1.809,-6.754 -1.809,-6.754l-0.33,-2.194l5.243,0.281z" fill="#EDBC9C" id="svg_112"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_113">
|
||||
<path d="m47.796,141.244l-2.689,64.307l-4.824,-0.362c0,0 -5.422,-36.369 -4.114,-54.575l11.627,-9.37z" fill="#EDBC9C" id="svg_114"/>
|
||||
<g id="svg_115">
|
||||
<path d="m50.378,214.201c0,0 6.033,2.309 6.64,4.92l-13.121,0l-2.065,-4.069l-1.397,4.069l-1.412,0c0,0 -1.324,-10.555 1.259,-13.931l10.096,9.011z" fill="#20202A" id="svg_116"/>
|
||||
<path d="m45.196,203.276l-0.089,2.275l5.837,9.767c0,0 -6.44,1.208 -8.852,-3.375c-2.412,-4.583 -1.809,-6.754 -1.809,-6.754l-0.33,-2.194l5.243,0.281z" fill="#EDBC9C" id="svg_117"/>
|
||||
</g>
|
||||
</g>
|
||||
<path d="m37.759,96.171c0,0 -6.725,1.185 -10.812,43.664l-1.451,16.827c0,0 5.437,-1.917 14.885,2.343c9.449,4.26 30.029,-4.686 30.029,-4.686s-1.349,-39.087 -10.452,-59.906l-22.199,1.758z" fill="#C4B6A7" id="svg_118"/>
|
||||
</g>
|
||||
<g id="svg_119">
|
||||
<path d="m36.29,33.629c0,0 0.423,6.627 -1.553,10.145c-1.976,3.518 -3.479,4.967 -4.006,8.788c0,0 16.576,4.945 33.177,-0.558l-6.522,-18.376l-21.096,0l0,0.001z" fill="#412424" id="svg_120"/>
|
||||
<path d="m57.354,26.041c0,0 -15.531,-4.207 -19.923,3.58c-3.187,5.65 0.578,15.881 7.856,18.764c6.932,2.746 4.965,-19.842 4.965,-19.842l7.102,-2.502z" fill="#412424" id="svg_121"/>
|
||||
<path d="m55.874,40.087c-0.157,1.865 1.227,3.504 3.092,3.661c1.865,0.157 3.504,-1.227 3.661,-3.092c0.157,-1.865 -1.227,-3.504 -3.092,-3.661c-1.865,-0.158 -3.504,1.227 -3.661,3.092z" fill="#DEA888" id="svg_122"/>
|
||||
<path d="m54.352,49.556l-0.576,7.363c-0.197,2.586 -1.896,3.847 -4.44,3.633l0,0c-2.172,-0.183 -4.117,-1.476 -4.205,-3.653l1.464,-11.544l7.757,4.201z" fill="#EDBC9C" id="svg_123"/>
|
||||
<polygon fill="#DEA888" id="svg_124" points="53.94700622558594,54.875 45.44700622558594,47.135986328125 54.425994873046875,48.803009033203125 "/>
|
||||
<path d="m52.092,50.715l0,0c4.354,0.077 8,-3.3 8.182,-6.853l0.379,-8.387c0.228,-4.454 -3.118,-8.286 -7.562,-8.66l0,0c-4.549,-0.383 -9.346,2.927 -9.729,7.476l-0.231,6.749c-0.18,5.218 3.756,9.236 8.961,9.675z" fill="#EDBC9C" id="svg_125"/>
|
||||
<path d="m48.166,30.423c1.575,3.798 -3.21,9.62 -3.21,9.62l-5.761,-6.925c-0.001,0 7.856,-5.383 8.971,-2.695z" fill="#412424" id="svg_126"/>
|
||||
<g id="svg_127">
|
||||
<path d="m39.079,40.019c-0.157,1.865 1.227,3.504 3.092,3.662c1.865,0.157 3.504,-1.227 3.661,-3.092c0.157,-1.865 -1.227,-3.504 -3.092,-3.661c-1.864,-0.159 -3.503,1.226 -3.661,3.091z" fill="#EDBC9C" id="svg_128"/>
|
||||
<path d="m43.552,41.407c0,0 0.206,-2.448 -2.167,-2.26" fill="none" id="svg_129" stroke="#1F2326" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="0.5387"/>
|
||||
</g>
|
||||
<g id="svg_130">
|
||||
<polyline fill="none" id="svg_131" points="55.81500244140625,41.614990234375 55.968994140625,43.125 54.83299255371094,43.170989990234375 " stroke="#1F2326" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="0.5387"/>
|
||||
<path d="m50.628,35.968c0,0 1.881,-0.164 1.902,0.627c0,0 0,0.535 -1.641,0.646c-1.641,0.11 -2.068,-0.013 -2.219,-0.516c-0.152,-0.503 1.076,-0.75 1.958,-0.757z" fill="#1F2326" id="svg_132"/>
|
||||
<path d="m58.494,36.176c0,0 -1.553,-0.162 -1.678,0.581c0,0 -0.073,0.503 1.287,0.613c1.36,0.11 1.802,-0.069 1.929,-0.475c0.144,-0.46 -0.8,-0.709 -1.538,-0.719z" fill="#1F2326" id="svg_133"/>
|
||||
<path d="m57.385,39.954c-0.013,0.45 0.33,0.826 0.766,0.84c0.436,0.014 0.8,-0.34 0.813,-0.79c0.013,-0.45 -0.329,-0.826 -0.766,-0.84c-0.436,-0.014 -0.8,0.34 -0.813,0.79z" fill="#1F2326" id="svg_134"/>
|
||||
<path d="m50.465,39.732c-0.013,0.45 0.329,0.826 0.766,0.84c0.436,0.014 0.8,-0.34 0.813,-0.79c0.013,-0.45 -0.329,-0.826 -0.766,-0.84c-0.435,-0.014 -0.799,0.34 -0.813,0.79z" fill="#1F2326" id="svg_135"/>
|
||||
<path d="m56.815,44.735l-5.726,0c0,0 0.905,1.788 3.263,1.779c2.148,-0.007 2.463,-1.779 2.463,-1.779z" fill="#FFFFFF" id="svg_136"/>
|
||||
</g>
|
||||
<path d="m47.205,30.423c0,0 3.424,5.851 14.408,3.798c0,0 2.67,-5.993 -4.259,-8.18c-6.93,-2.188 -14.898,1.508 -14.898,1.508l0,4.927l4.749,-2.053z" fill="#412424" id="svg_137"/>
|
||||
</g>
|
||||
<g id="svg_138">
|
||||
<g id="svg_139">
|
||||
<path d="m56.454,58.018c0,0 9.821,2.329 11.553,15.317l-6.3,5.391l-5.253,-20.708z" fill="#808DFF" id="svg_140"/>
|
||||
<g id="svg_141">
|
||||
<path d="m72.006,94.803c-0.177,-0.291 -0.064,2.104 -0.172,1.78c0,0 -2.925,-4.972 -7.25,-15.385c-1.106,-2.664 0.385,-10.814 3.26,-11.041c2.111,-0.167 4.618,3.77 5.121,5.827l5.243,13.186l-6.202,5.633z" fill="#EDBC9C" id="svg_142"/>
|
||||
<path d="m73.62,90.323l20.437,-11.019l1.882,2.079l-16.512,16.377c-2.398,2.406 -6.473,1.593 -7.765,-1.548l0,0c-0.895,-2.177 -0.063,-4.681 1.958,-5.889z" fill="#EDBC9C" id="svg_143"/>
|
||||
<path d="m101.709,78.454c1.374,1.815 0.663,4.668 -1.588,6.371c-2.251,1.703 -5.189,1.612 -6.563,-0.203c-1.374,-1.815 -0.663,-4.668 1.588,-6.371c2.251,-1.703 5.189,-1.612 6.563,0.203z" fill="#EDBC9C" id="svg_144"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_145">
|
||||
<g id="svg_146">
|
||||
<g id="svg_147">
|
||||
<path d="m36.26,97.764c0.829,0.016 25.04,-0.28 25.04,-0.28l-1.802,-35.76l-17.901,3.048l-5.337,32.992z" fill="#FFFFFF" id="svg_148"/>
|
||||
<path d="m41.597,57.649l7.183,-1.259l5.305,0.957l4.102,8.332c0,0 1.227,3.814 -5.311,4.419c-6.538,0.605 -11.078,-3.995 -12.167,-6.84c-1.09,-2.846 0.888,-5.609 0.888,-5.609z" fill="#EDBC9C" id="svg_149"/>
|
||||
</g>
|
||||
<g id="svg_150">
|
||||
<path d="m54.085,57.347c0,0 5.376,11.324 4.043,23.017c-1.332,11.694 5.31,24.05 5.31,24.05l0,-32.265c0,0 -0.07,-13.466 -9.353,-14.802z" fill="#808DFF" id="svg_151"/>
|
||||
<path d="m62.766,67.107c0,0 2.192,5.383 0.673,14.431" fill="none" id="svg_152" stroke="#5F73E8" stroke-miterlimit="10" stroke-width="0.764"/>
|
||||
</g>
|
||||
<path d="m41.597,57.649c0,0 16.376,20.229 -9.038,48.331l2.684,-46.65l6.354,-1.681z" fill="#808DFF" id="svg_153"/>
|
||||
</g>
|
||||
<g id="svg_154">
|
||||
<g id="svg_155">
|
||||
<g id="svg_156">
|
||||
<path d="m35.286,59.315c0,0 -12.081,2.553 -12.675,26.91l14.457,-1.365l1.98,-19.807l-3.762,-5.738z" fill="#808DFF" id="svg_157"/>
|
||||
<path d="m36.679,90.803c0,0 3.15,-3.086 0.389,-5.944c0,0 -10.297,-1.601 -16.298,2.167c0,0 -2.512,4.286 0.698,6.608c0,0.001 4.326,-2.831 15.211,-2.831z" fill="#9CABFF" id="svg_158"/>
|
||||
<line fill="none" id="svg_159" stroke="#5F73E8" stroke-miterlimit="10" stroke-width="0.764" x1="37.068" x2="39.46" y1="84.86" y2="70.101"/>
|
||||
</g>
|
||||
<g id="svg_160">
|
||||
<path d="m36.679,90.803l22.472,2.831l-1.249,4.419l-30.375,0.75c-3.043,0.075 -5.654,-2.153 -6.059,-5.169l0,0c0,0.001 6.728,-4.835 15.211,-2.831z" fill="#EDBC9C" id="svg_161"/>
|
||||
<path d="m68.003,95.927c-0.117,2.805 -3.031,4.961 -6.508,4.816c-3.478,-0.145 -6.202,-2.536 -6.085,-5.341c0.117,-2.805 3.031,-4.961 6.508,-4.816c3.478,0.145 6.202,2.536 6.085,5.341z" fill="#EDBC9C" id="svg_162"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_163">
|
||||
<path d="m37.285,93.313l23.99,0l0,-2.707l-23.99,0c-0.747,0 -1.353,0.606 -1.353,1.354l0,0c0,0.747 0.605,1.353 1.353,1.353z" fill="#1C2123" id="svg_164"/>
|
||||
<g id="svg_165">
|
||||
<path d="m58.117,93.313l34.442,0c1.831,0 3.438,-1.221 3.928,-2.984l6.199,-22.27c0.548,-1.969 -0.933,-3.918 -2.976,-3.918l-31.85,0c-1.222,0 -2.291,0.824 -2.601,2.006l-7.142,27.166z" fill="#2E353A" id="svg_166"/>
|
||||
<path d="m78.798,79.973c0,1.661 1.347,3.007 3.008,3.007s3.007,-1.346 3.007,-3.007c0,-1.661 -1.346,-3.007 -3.007,-3.007s-3.008,1.346 -3.008,3.007z" fill="#FFFFFF" id="svg_167"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_168">
|
||||
<g id="svg_170">
|
||||
<g id="svg_171">
|
||||
<g id="svg_172"/>
|
||||
<g id="svg_174"/>
|
||||
</g>
|
||||
<g id="svg_178">
|
||||
<g id="svg_179">
|
||||
<g id="svg_180"/>
|
||||
</g>
|
||||
<g id="svg_183"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_186">
|
||||
<g id="svg_187">
|
||||
<g id="svg_188">
|
||||
<g id="svg_189">
|
||||
<g id="svg_191">
|
||||
<g id="svg_192"/>
|
||||
<g id="svg_195"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_199">
|
||||
<g id="svg_201">
|
||||
<g id="svg_202"/>
|
||||
<g id="svg_205"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_209"/>
|
||||
</g>
|
||||
<g id="svg_212">
|
||||
<g id="svg_213"/>
|
||||
<g id="svg_216"/>
|
||||
<g id="svg_219">
|
||||
<g id="svg_220">
|
||||
<g id="svg_222"/>
|
||||
</g>
|
||||
<g id="svg_225">
|
||||
<g id="svg_226"/>
|
||||
<g id="svg_229"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_232">
|
||||
<g id="svg_233"/>
|
||||
</g>
|
||||
<g id="svg_239">
|
||||
<g id="svg_246"/>
|
||||
<g id="svg_254">
|
||||
<g id="svg_255"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_259">
|
||||
<g id="svg_261">
|
||||
<g id="svg_262">
|
||||
<g id="svg_264"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_268">
|
||||
<g id="svg_269">
|
||||
<g id="svg_270">
|
||||
<g id="svg_271">
|
||||
<g id="svg_275"/>
|
||||
</g>
|
||||
<g id="svg_281">
|
||||
<g id="svg_282">
|
||||
<g id="svg_283">
|
||||
<g id="svg_284">
|
||||
<g id="svg_286"/>
|
||||
</g>
|
||||
<g id="svg_292"/>
|
||||
</g>
|
||||
<g id="svg_295">
|
||||
<g id="svg_296">
|
||||
<g id="svg_298"/>
|
||||
</g>
|
||||
<g id="svg_304"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_307">
|
||||
<g id="svg_308">
|
||||
<g id="svg_309">
|
||||
<g id="svg_310"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_315"/>
|
||||
<g id="svg_318">
|
||||
<g id="svg_319"/>
|
||||
<g id="svg_322">
|
||||
<g id="svg_323"/>
|
||||
<g id="svg_326"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_329">
|
||||
<g id="svg_336"/>
|
||||
<g id="svg_343"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_350">
|
||||
<g id="svg_351">
|
||||
<g id="svg_352"/>
|
||||
<g id="svg_355"/>
|
||||
<g id="svg_358">
|
||||
<g id="svg_360"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_363"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_368">
|
||||
<g id="svg_370"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="DESIGNED_BY_FREEPIK">
|
||||
<g id="svg_373">
|
||||
<g id="XMLID_319_">
|
||||
<g id="XMLID_338_"/>
|
||||
</g>
|
||||
<g id="svg_374"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,297 @@
|
||||
<?xml version="1.0"?>
|
||||
<svg width="240" height="240" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<g class="layer">
|
||||
<title>Layer 1</title>
|
||||
<g id="BACKGROUND"/>
|
||||
<g id="OBJECTS">
|
||||
<g id="svg_2">
|
||||
<g id="svg_3">
|
||||
<g id="svg_4">
|
||||
<g id="svg_5">
|
||||
<g id="svg_6"/>
|
||||
</g>
|
||||
<g id="svg_13">
|
||||
<g id="svg_14">
|
||||
<g id="svg_15">
|
||||
<g id="svg_17">
|
||||
<g id="svg_18"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_22">
|
||||
<g id="svg_23">
|
||||
<g id="svg_25">
|
||||
<g id="svg_26"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_29">
|
||||
<g id="svg_31"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_33">
|
||||
<g id="svg_34">
|
||||
<g id="svg_35">
|
||||
<g id="svg_37">
|
||||
<g id="svg_38"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_42"/>
|
||||
<g id="svg_45">
|
||||
<g id="svg_46"/>
|
||||
<g id="svg_49">
|
||||
<g id="svg_50"/>
|
||||
<g id="svg_53"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_57">
|
||||
<g id="svg_59"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_63">
|
||||
<g id="svg_69"/>
|
||||
<g id="svg_72">
|
||||
<g id="svg_78"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_87">
|
||||
<g id="svg_89">
|
||||
<g id="svg_90">
|
||||
<g id="svg_91">
|
||||
<g id="svg_94"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_98">
|
||||
<g id="svg_99"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_105">
|
||||
<g id="svg_106">
|
||||
<g id="svg_107">
|
||||
<g id="svg_108">
|
||||
<g id="svg_110"/>
|
||||
</g>
|
||||
<g id="svg_113">
|
||||
<g id="svg_115"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_119">
|
||||
<g id="svg_127"/>
|
||||
<g id="svg_130"/>
|
||||
</g>
|
||||
<g id="svg_138">
|
||||
<g id="svg_139">
|
||||
<g id="svg_141"/>
|
||||
</g>
|
||||
<g id="svg_145">
|
||||
<g id="svg_146">
|
||||
<g id="svg_147"/>
|
||||
<g id="svg_150"/>
|
||||
</g>
|
||||
<g id="svg_154">
|
||||
<g id="svg_155">
|
||||
<g id="svg_156"/>
|
||||
<g id="svg_160"/>
|
||||
</g>
|
||||
<g id="svg_163">
|
||||
<g id="svg_165"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_168">
|
||||
<g id="svg_170">
|
||||
<g id="svg_171">
|
||||
<g id="svg_172"/>
|
||||
<g id="svg_174"/>
|
||||
</g>
|
||||
<g id="svg_178">
|
||||
<g id="svg_179">
|
||||
<g id="svg_180"/>
|
||||
</g>
|
||||
<g id="svg_183"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_186">
|
||||
<g id="svg_187">
|
||||
<g id="svg_188">
|
||||
<g id="svg_189">
|
||||
<polygon fill="#EDBC9C" id="svg_190" points="105.89300537109375,190.30499267578125 107.11898803710938,208.34300231933594 115.42999267578125,207.98500061035156 115.1409912109375,189.718994140625 "/>
|
||||
<g id="svg_191">
|
||||
<g id="svg_192">
|
||||
<path d="m106.501,206.34l-15.149,6.908c-0.992,0.498 -1.514,1.611 -1.261,2.692l0,0l26.72,0l-0.993,-9.711l-9.317,0.111z" id="svg_193"/>
|
||||
<rect fill="#FFFFFF" height="2.874" id="svg_194" width="27.532" x="89.751" y="215.94"/>
|
||||
</g>
|
||||
<g id="svg_195">
|
||||
<line fill="none" id="svg_196" stroke="#FFFFFF" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.0231" x1="101.835" x2="103.478" y1="208.069" y2="209.465"/>
|
||||
<line fill="none" id="svg_197" stroke="#FFFFFF" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.0231" x1="99.631" x2="101.286" y1="209.129" y2="210.537"/>
|
||||
<line fill="none" id="svg_198" stroke="#FFFFFF" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.0231" x1="97.419" x2="99.168" y1="210.239" y2="211.73"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_199">
|
||||
<polygon fill="#EDBC9C" id="svg_200" points="132.95599365234375,190.30499267578125 134.1820068359375,208.34300231933594 142.49301147460938,207.98500061035156 142.20401000976562,189.718994140625 "/>
|
||||
<g id="svg_201">
|
||||
<g id="svg_202">
|
||||
<path d="m133.563,206.34l-15.149,6.908c-0.992,0.498 -1.514,1.611 -1.261,2.692l0,0l26.72,0l-0.993,-9.711l-9.317,0.111z" id="svg_203"/>
|
||||
<rect fill="#FFFFFF" height="2.874" id="svg_204" width="27.532" x="116.814" y="215.94"/>
|
||||
</g>
|
||||
<g id="svg_205">
|
||||
<line fill="none" id="svg_206" stroke="#FFFFFF" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.0231" x1="128.898" x2="130.54" y1="208.069" y2="209.465"/>
|
||||
<line fill="none" id="svg_207" stroke="#FFFFFF" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.0231" x1="126.694" x2="128.349" y1="209.129" y2="210.537"/>
|
||||
<line fill="none" id="svg_208" stroke="#FFFFFF" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.0231" x1="124.482" x2="126.231" y1="210.239" y2="211.73"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_209">
|
||||
<polygon fill="#C4B6A7" id="svg_210" points="110.69400024414062,105.5979995727539 142.58099365234375,108.02799987792969 145.22799682617188,197.60800170898438 129.34298706054688,197.60800170898438 123.68499755859375,130.04200744628906 116.10400390625,158.91400146484375 118.68499755859375,198.6999969482422 103.80899047851562,198.7429962158203 98.67599487304688,154.75100708007812 "/>
|
||||
<line fill="none" id="svg_211" stroke="#A69382" stroke-miterlimit="10" stroke-width="0.75" x1="116.554" x2="123.685" y1="126.149" y2="130.042"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_212">
|
||||
<g id="svg_213">
|
||||
<polygon fill="#EBAB9D" id="svg_214" points="111.10598754882812,78.26899719238281 111.31298828125,87.06099700927734 106.32199096679688,87.125 106.69000244140625,77.66100311279297 "/>
|
||||
<path d="m110.799,63.347c0,0 -6.674,4.388 -5.975,15.518l6.489,0.73l-0.514,-16.248z" fill="#808DFF" id="svg_215"/>
|
||||
</g>
|
||||
<g id="svg_216">
|
||||
<path d="m126.881,58.394c8.026,0 17.123,2.774 17.123,2.774l-0.346,51.05l-33.9,-2.604l0.359,-44.65c0.007,-0.79 0.442,-1.514 1.136,-1.891l9.375,-4.011c0,0.001 4.615,-0.668 6.253,-0.668z" fill="#808DFF" id="svg_217"/>
|
||||
<line fill="none" id="svg_218" stroke="#667AF1" stroke-miterlimit="10" stroke-width="0.764" x1="109.995" x2="109.995" y1="79.446" y2="68.69"/>
|
||||
</g>
|
||||
<g id="svg_219">
|
||||
<g id="svg_220">
|
||||
<path d="m139.987,78.372l2.181,25.251c0,0 2.408,5.078 6.667,-0.204c4.259,-5.282 0.614,-25.916 0.614,-25.916l-9.462,0.869z" fill="#EDBC9C" id="svg_221"/>
|
||||
<g id="svg_222">
|
||||
<path d="m137.918,80.866c0,0 -1.819,-18.64 4.081,-19.735c6.117,-1.136 9.67,10.396 10.236,18.659l-14.317,1.076z" fill="#808DFF" id="svg_223"/>
|
||||
<path d="m140.187,80.695l-2.269,0.171c0,0 -0.664,-6.379 -0.265,-10.813" fill="none" id="svg_224" stroke="#667AF1" stroke-miterlimit="10" stroke-width="0.764"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_225">
|
||||
<g id="svg_226">
|
||||
<path d="m125.509,79.979c0,0 -1.228,-4.452 -5.684,-4.886c-4.456,-0.434 -5.767,3.064 -4.928,5.368c0.839,2.304 3.738,6.054 8.13,4.136c4.392,-1.918 2.482,-4.618 2.482,-4.618z" fill="#EDBC9C" id="svg_227"/>
|
||||
<path d="m118.346,75.099c0,0 -4.376,0.502 -7.633,2.089c-0.331,0.161 -0.512,0.525 -0.464,0.89l0,0c0.065,0.493 0.531,0.831 1.02,0.74l6.039,-1.129l1.038,-2.59z" fill="#EDBC9C" id="svg_228"/>
|
||||
</g>
|
||||
<g id="svg_229">
|
||||
<polygon fill="#EDBC9C" id="svg_230" points="143.11099243164062,104.90899658203125 123.0260009765625,84.59700012207031 125.50900268554688,79.97899627685547 145.04400634765625,92.39099884033203 "/>
|
||||
<line fill="none" id="svg_231" stroke="#DEA888" stroke-miterlimit="10" stroke-width="0.764" x1="140.974" x2="145.044" y1="89.805" y2="92.391"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_232">
|
||||
<g id="svg_233">
|
||||
<path d="m98.081,85.09l8.447,25.074c0.29,0.862 1.151,1.399 2.053,1.28l20.53,-2.697c1.196,-0.157 1.947,-1.373 1.551,-2.513l-8.695,-25.042c-0.296,-0.853 -1.151,-1.381 -2.047,-1.264l-20.282,2.664c-1.19,0.157 -1.94,1.361 -1.557,2.498z" fill="#121314" id="svg_234"/>
|
||||
<polygon fill="#121314" id="svg_235" points="98.44100952148438,83.23100280761719 97.36801147460938,84.6240005493164 129.35501098632812,109.32099914550781 130.50201416015625,107.82599639892578 "/>
|
||||
<path d="m97.114,86.335l8.447,25.074c0.29,0.862 1.15,1.399 2.053,1.28l20.53,-2.697c1.196,-0.157 1.947,-1.373 1.551,-2.513l-8.695,-25.04c-0.296,-0.853 -1.151,-1.381 -2.047,-1.263l-20.282,2.664c-1.19,0.155 -1.94,1.359 -1.557,2.495z" fill="#455157" id="svg_236"/>
|
||||
<path d="m109.496,94.445c0.009,1.221 1.005,2.204 2.226,2.195c1.221,-0.009 2.204,-1.005 2.195,-2.226c-0.009,-1.221 -1.005,-2.204 -2.226,-2.195c-1.221,0.008 -2.204,1.005 -2.195,2.226z" fill="#86969C" id="svg_237"/>
|
||||
</g>
|
||||
<path d="m98.071,93.622l1.923,5.188c0.235,0.633 0.876,1.019 1.545,0.931l0,0c1.97,-0.259 3.232,-2.23 2.642,-4.128l-0.541,-1.74c-0.433,-1.394 -1.754,-2.319 -3.212,-2.25l-1.07,0.051c-0.977,0.047 -1.627,1.03 -1.287,1.948z" fill="#EDBC9C" id="svg_238"/>
|
||||
</g>
|
||||
<g id="svg_239">
|
||||
<path d="m119.195,26.137c0,0 19.114,-5.53 12.146,14.151l-3.095,-6.884l-9.051,-7.267z" fill="#412424" id="svg_240"/>
|
||||
<path d="m111.261,33.648c0,0 -4.703,2.124 0.548,8.693l1.677,-9.011l-2.225,0.318z" fill="#412424" id="svg_241"/>
|
||||
<path d="m116.028,43.543c0.332,1.727 -0.755,3.394 -2.427,3.723c-1.672,0.329 -3.296,-0.805 -3.628,-2.532c-0.332,-1.727 0.755,-3.394 2.426,-3.723c1.673,-0.33 3.298,0.804 3.629,2.532z" fill="#DEA888" id="svg_242"/>
|
||||
<path d="m119.478,50.804l1.02,8.228c0.352,2.89 2.265,4.218 5.017,3.848l0,0c2.349,-0.315 4.79,-1.776 4.371,-4.31l-1.213,-12.919l-9.195,5.153z" fill="#EDBC9C" id="svg_243"/>
|
||||
<path d="m120.34,57.913c0,0 9.269,-3.364 8.49,-11.668c-0.232,-2.474 -9.473,3.717 -9.473,3.717l0.983,7.951z" fill="#DEA888" id="svg_244"/>
|
||||
<path d="m122.243,53.961l0,0c-5.105,0.903 -8.527,-3.188 -8.757,-5.275l-2.19,-9.681c-0.788,-4.945 2.364,-9.612 7.123,-10.548l0,0c4.872,-0.958 10.458,2.177 11.425,7.21l0.911,6.449c0.828,5.806 -2.939,10.749 -8.512,11.845z" fill="#EDBC9C" id="svg_245"/>
|
||||
<g id="svg_246">
|
||||
<polyline fill="none" id="svg_247" points="117.26400756835938,45.770999908447266 117.29000854492188,47.65800094604492 118.43499755859375,47.56100082397461 " stroke="#1F2326" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="0.5281"/>
|
||||
<path d="m121.502,40.326c0,0 -1.907,0.072 -1.828,0.868c0,0 0.068,0.537 1.727,0.441c1.659,-0.096 2.072,-0.273 2.16,-0.796c0.089,-0.524 -1.173,-0.617 -2.059,-0.513z" fill="#1F2326" id="svg_248"/>
|
||||
<path d="m113.909,41.295c0,0 1.537,-0.358 1.756,0.372c0,0 0.136,0.495 -1.214,0.777c-1.35,0.282 -1.816,0.157 -1.995,-0.234c-0.201,-0.444 0.715,-0.812 1.453,-0.915z" fill="#1F2326" id="svg_249"/>
|
||||
<path d="m115.497,44.944c0.07,0.45 -0.227,0.87 -0.662,0.939c-0.436,0.069 -0.845,-0.24 -0.915,-0.69c-0.07,-0.45 0.226,-0.87 0.662,-0.939c0.435,-0.069 0.845,0.24 0.915,0.69z" fill="#1F2326" id="svg_250"/>
|
||||
<path d="m122.407,43.851c0.07,0.45 -0.226,0.87 -0.662,0.939c-0.435,0.068 -0.845,-0.241 -0.915,-0.69c-0.07,-0.45 0.226,-0.87 0.662,-0.939c0.436,-0.069 0.845,0.24 0.915,0.69z" fill="#1F2326" id="svg_251"/>
|
||||
<path d="m117.256,49.092l4.875,-0.54c0,0 -0.146,1.713 -2.692,1.714c-1.653,0.001 -2.183,-1.174 -2.183,-1.174z" fill="#FFFFFF" id="svg_252"/>
|
||||
</g>
|
||||
<path d="m131.754,33.022l-0.413,7.266l-1.408,1.572l0,0c-1.085,-0.37 -2.49,-0.348 -3.389,-1.572c-0.665,-0.904 -1.326,-2.593 -1.349,-3.715l-0.032,-1.564l-3.829,2.827c-0.6,-1.4 -1.279,-3.264 -1.279,-3.264s-0.913,0.065 -2.157,3.101c0,0 -9.875,2.985 -9.87,-3.036c0.005,-6.355 11.168,-8.498 11.168,-8.498l9.609,3.278l2.949,3.605z" fill="#412424" id="svg_253"/>
|
||||
<g id="svg_254">
|
||||
<g id="svg_255">
|
||||
<path d="m135.054,42.129c0.358,1.864 -0.814,3.662 -2.618,4.017c-1.804,0.355 -3.557,-0.868 -3.915,-2.732c-0.358,-1.864 0.814,-3.662 2.618,-4.017c1.805,-0.356 3.557,0.868 3.915,2.732z" fill="#EDBC9C" id="svg_256"/>
|
||||
<path d="m130.839,43.999c0,0 -0.47,-2.447 1.868,-2.507" fill="none" id="svg_257" stroke="#1F2326" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="0.5387"/>
|
||||
</g>
|
||||
</g>
|
||||
<path d="m118.435,60.001l4,-1.86l7.054,0.202l4.137,0.578c0,0 1.542,6.197 -8.92,6.542c-8.365,0.276 -6.271,-5.462 -6.271,-5.462z" fill="#EDBC9C" id="svg_258"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_259">
|
||||
<g id="svg_261">
|
||||
<g id="svg_262">
|
||||
<g id="svg_264"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_268">
|
||||
<g id="svg_269">
|
||||
<g id="svg_270">
|
||||
<g id="svg_271">
|
||||
<g id="svg_275"/>
|
||||
</g>
|
||||
<g id="svg_281">
|
||||
<g id="svg_282">
|
||||
<g id="svg_283">
|
||||
<g id="svg_284">
|
||||
<g id="svg_286"/>
|
||||
</g>
|
||||
<g id="svg_292"/>
|
||||
</g>
|
||||
<g id="svg_295">
|
||||
<g id="svg_296">
|
||||
<g id="svg_298"/>
|
||||
</g>
|
||||
<g id="svg_304"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_307">
|
||||
<g id="svg_308">
|
||||
<g id="svg_309">
|
||||
<g id="svg_310"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_315"/>
|
||||
<g id="svg_318">
|
||||
<g id="svg_319"/>
|
||||
<g id="svg_322">
|
||||
<g id="svg_323"/>
|
||||
<g id="svg_326"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_329">
|
||||
<g id="svg_336"/>
|
||||
<g id="svg_343"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_350">
|
||||
<g id="svg_351">
|
||||
<g id="svg_352"/>
|
||||
<g id="svg_355"/>
|
||||
<g id="svg_358">
|
||||
<g id="svg_360"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_363"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_368">
|
||||
<g id="svg_370"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="DESIGNED_BY_FREEPIK">
|
||||
<g id="svg_373">
|
||||
<g id="XMLID_319_">
|
||||
<g id="XMLID_338_"/>
|
||||
</g>
|
||||
<g id="svg_374"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,302 @@
|
||||
<?xml version="1.0"?>
|
||||
<svg width="240" height="240" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<g class="layer">
|
||||
<title>Layer 1</title>
|
||||
<g id="BACKGROUND"/>
|
||||
<g id="OBJECTS">
|
||||
<g id="svg_2">
|
||||
<g id="svg_3">
|
||||
<g id="svg_4">
|
||||
<g id="svg_5">
|
||||
<g id="svg_6">
|
||||
<rect fill="#F1EEEB" height="35.866" id="svg_7" width="6.506" x="140.712" y="150.904"/>
|
||||
<polygon fill="#F1EEEB" id="svg_8" points="142.39498901367188,182.20001220703125 123.70401000976562,190.64801025390625 123.97000122070312,193.04299926757812 143.45999145507812,186.76901245117188 "/>
|
||||
<polygon fill="#F1EEEB" id="svg_9" points="145.56900024414062,182.20001220703125 164.260986328125,190.64801025390625 163.9949951171875,193.04299926757812 144.5050048828125,186.76901245117188 "/>
|
||||
</g>
|
||||
<circle cx="124.552" cy="197.321" fill="#02060B" id="svg_10" r="4.467"/>
|
||||
<circle cx="164.261" cy="197.321" fill="#02060B" id="svg_11" r="4.467"/>
|
||||
<path d="m162.323,153.401l-37.707,0c-2.083,0 -3.771,-1.688 -3.771,-3.771l0,0c0,-2.083 1.688,-3.771 3.771,-3.771l37.707,0c2.083,0 3.771,1.688 3.771,3.771l0,0c0,2.082 -1.688,3.771 -3.771,3.771z" fill="#8292FD" id="svg_12"/>
|
||||
</g>
|
||||
<g id="svg_13">
|
||||
<g id="svg_14">
|
||||
<g id="svg_15">
|
||||
<polygon fill="#B87F58" id="svg_16" points="80.69500732421875,173.71701049804688 77.38699340820312,190.06500244140625 85.1820068359375,191.42098999023438 89.40701293945312,175.04998779296875 "/>
|
||||
<g id="svg_17">
|
||||
<g id="svg_18">
|
||||
<path d="m77.22,188.078l-15.477,3.341c-1.023,0.261 -1.734,1.189 -1.718,2.245l0,0l24.825,5.427l1.05,-9.224l-8.68,-1.789z" fill="#121314" id="svg_19"/>
|
||||
<rect fill="#455157" height="2.733" id="svg_20" transform="matrix(0.9769 0.2136 -0.2136 0.9769 101.605 -57.1239)" width="26.183" x="12.597898" y="253.892476"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<path d="m99.456,129.387l-3.183,0.787c-6.628,1.639 -11.887,6.674 -13.814,13.224l-9.379,39.247l17.477,2.871l9.923,-37.495l10.947,-3.624l-11.971,-15.01z" fill="#A69382" id="svg_21"/>
|
||||
</g>
|
||||
<g id="svg_22">
|
||||
<g id="svg_23">
|
||||
<polygon fill="#B87F58" id="svg_24" points="115.427001953125,170.66900634765625 123.0260009765625,185.51699829101562 129.96701049804688,181.72000122070312 123.07199096679688,166.28399658203125 "/>
|
||||
<g id="svg_25">
|
||||
<g id="svg_26">
|
||||
<path d="m121.657,184.067l-10.024,12.257c-0.638,0.841 -0.615,2.01 0.055,2.826l0,0l22.799,-11.223l-4.926,-7.868l-7.904,4.008z" fill="#121314" id="svg_27"/>
|
||||
<rect fill="#455157" height="2.733" id="svg_28" transform="matrix(0.8972 -0.4417 0.4417 0.8972 -156.063 207.084)" width="26.183" x="243.41755" y="111.141864"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_29">
|
||||
<path d="m119.363,123.294l-18.21,0.757l9.689,20.303l25.192,1.938c0,0 26.326,2.739 24.026,-14.856l-40.697,-8.142z" fill="#C4B6A7" id="svg_30"/>
|
||||
<g id="svg_31">
|
||||
<path d="m91.17,136.499l24.499,45.673l16.026,-7.099l-23.601,-51.311l-9.274,0.338c-6.254,0.201 -10.276,6.719 -7.65,12.399z" fill="#C4B6A7" id="svg_32"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_33">
|
||||
<g id="svg_34">
|
||||
<g id="svg_35">
|
||||
<path d="m119.164,74.785c0,0 -6.882,4.837 -6.882,20.054s2.497,27.105 2.497,27.105l8.72,1.961l-4.335,-49.12z" fill="#FFFFFF" id="svg_36"/>
|
||||
<g id="svg_37">
|
||||
<g id="svg_38">
|
||||
<path d="m103.84,110.305c0,0 -6.252,-1.651 -9.101,2.793c-1.881,2.934 -1.174,4.003 -0.345,5.388c0.457,0.764 1.732,1.74 2.619,1.812c1.948,0.158 5.366,0.815 7.866,-3.309l-1.039,-6.684z" fill="#B87F58" id="svg_39"/>
|
||||
<path d="m93.889,114.774l-1.751,6.004c-0.146,0.499 0.18,1.014 0.693,1.096l0,0c0.342,0.055 0.685,-0.1 0.869,-0.394l3.313,-5.279l-3.124,-1.427z" fill="#B87F58" id="svg_40"/>
|
||||
</g>
|
||||
<polygon fill="#FFFFFF" id="svg_41" points="116.23199462890625,111.90301513671875 103.35299682617188,109.93301391601562 103.8070068359375,118.72100830078125 123.29800415039062,123.92001342773438 "/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_42">
|
||||
<path d="m119.363,78.31c0,0 -10.616,11.177 0,44.983l40.697,8.141c0,0 -1.242,-31.461 -11.867,-45.121c-10.625,-13.66 -21.59,-13.724 -21.59,-13.724l-7.24,5.721z" fill="#FFFFFF" id="svg_43"/>
|
||||
<path d="m114.711,94.142c0,0 0.89,16.214 4.652,29.152" fill="none" id="svg_44" stroke="#DDDADA" stroke-miterlimit="10" stroke-width="0.9284"/>
|
||||
</g>
|
||||
<g id="svg_45">
|
||||
<g id="svg_46">
|
||||
<path d="m127.91,87.542c4.895,7.336 22.622,33.284 22.622,33.284s6.482,7.397 12.188,3.261c8.103,-5.874 -3.09,-21.8 -3.09,-21.8s-12.918,-20.839 -24.921,-27.37c-3.451,-1.877 -5.255,0.443 -5.982,3.129l-1.474,5.448c-0.375,1.384 -0.139,2.854 0.657,4.048z" fill="#FFFFFF" id="svg_47"/>
|
||||
<line fill="none" id="svg_48" stroke="#DDDADA" stroke-miterlimit="10" stroke-width="0.9284" x1="132.403" x2="147.218" y1="94.142" y2="115.569"/>
|
||||
</g>
|
||||
<g id="svg_49">
|
||||
<g id="svg_50">
|
||||
<path d="m120.241,110.656c0,0 -6.252,-1.651 -9.101,2.793c-1.881,2.934 -1.174,4.003 -0.345,5.388c0.457,0.764 1.732,1.74 2.619,1.812c1.948,0.158 5.366,0.815 7.866,-3.309l-1.039,-6.684z" fill="#B87F58" id="svg_51"/>
|
||||
<path d="m110.291,115.124l-1.751,6.004c-0.146,0.499 0.18,1.014 0.693,1.096l0,0c0.342,0.055 0.685,-0.1 0.869,-0.394l3.313,-5.279l-3.124,-1.427z" fill="#B87F58" id="svg_52"/>
|
||||
</g>
|
||||
<g id="svg_53">
|
||||
<path d="m150.84,112.518c-0.645,0.161 -30.585,-1.879 -30.585,-1.879l1.129,7.257c0,0 16.813,7.16 36.492,7.289l-7.036,-12.667z" fill="#FFFFFF" id="svg_54"/>
|
||||
<line fill="none" id="svg_55" stroke="#DDDADA" stroke-miterlimit="10" stroke-width="0.9284" x1="150.206" x2="120.255" y1="112.585" y2="110.639"/>
|
||||
<path d="m159.607,125.308c-15.861,1.237 -37.891,-5.783 -37.891,-5.783l-0.435,-2.186" fill="none" id="svg_56" stroke="#DDDADA" stroke-miterlimit="10" stroke-width="0.9284"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_57">
|
||||
<path d="m127.609,121.745l-27.943,0l0,2.017l27.943,0c0.557,0 1.009,-0.452 1.009,-1.008l0,0c0,-0.557 -0.452,-1.009 -1.009,-1.009z" fill="#121314" id="svg_58"/>
|
||||
<g id="svg_59">
|
||||
<path d="m63.84,89.169l17.028,0l17.173,34.593l-17.061,0c-1.656,0 -3.168,-0.943 -3.896,-2.431l-14.641,-29.923c-0.505,-1.033 0.247,-2.239 1.397,-2.239z" fill="#455157" id="svg_60"/>
|
||||
<path d="m80.869,89.169l0.795,0c1.405,0 2.69,0.794 3.319,2.051l16.271,32.542l-3.212,0l-17.173,-34.593z" fill="#121314" id="svg_61"/>
|
||||
<path d="m79.763,105.69c0.708,1.691 0.451,3.409 -0.574,3.837c-1.024,0.429 -2.428,-0.595 -3.135,-2.285c-0.707,-1.691 -0.451,-3.409 0.574,-3.837c1.024,-0.429 2.427,0.594 3.135,2.285z" fill="#86969C" id="svg_62"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_63">
|
||||
<path d="m107.855,63.708c1.012,1.758 0.408,4.003 -1.35,5.015c-1.758,1.012 -4.003,0.408 -5.015,-1.35c-1.012,-1.758 -0.408,-4.003 1.35,-5.015c1.758,-1.013 4.003,-0.408 5.015,1.35z" fill="#A06945" id="svg_64"/>
|
||||
<path d="m110.405,67.704l5.565,9.936c1.811,3.205 4.138,2.184 7.279,0.376l0,0c2.681,-1.544 4.523,-3.65 3.324,-6.501l-6.221,-12.162l-9.947,8.351z" fill="#B87F58" id="svg_65"/>
|
||||
<path d="m115.492,76.787c0,0 4.764,-3.52 5.57,-12.784c0.211,-2.425 -9.144,6.441 -9.144,6.441l3.574,6.343z" fill="#A56C4B" id="svg_66"/>
|
||||
<path d="m117.339,70.892l-3.032,1.746c-3.342,1.924 -7.613,0.699 -9.426,-2.705l-5.009,-9.402c-2.273,-4.266 -0.752,-9.565 3.436,-11.976l0,0c4.288,-2.469 10.517,-1.427 12.986,2.861l4.314,8.438c2.026,3.963 0.588,8.818 -3.269,11.038z" fill="#B87F58" id="svg_67"/>
|
||||
<path d="m117.671,59.352l-5.752,-7.429c0,0 -6.768,5.181 -12.864,6.437l-0.948,-1.194c-1.432,-2.487 -0.477,-5.667 2.089,-6.953l10.379,-5.192c7.407,-5.069 8.61,8.96 8.61,8.96l-1.514,5.371z" fill="#1F2326" id="svg_68"/>
|
||||
<g id="svg_69">
|
||||
<path d="m123.323,54.801c1.012,1.758 0.408,4.003 -1.35,5.015c-1.758,1.012 -4.003,0.408 -5.015,-1.35c-1.012,-1.758 -0.408,-4.003 1.35,-5.015c1.758,-1.012 4.003,-0.408 5.015,1.35z" fill="#B87F58" id="svg_70"/>
|
||||
<path d="m119.676,58.548c0,0 -1.329,-2.308 1.087,-3.215" fill="none" id="svg_71" stroke="#1F2326" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="0.4844"/>
|
||||
</g>
|
||||
<g id="svg_72">
|
||||
<polyline fill="none" id="svg_73" points="107.80099487304688,66.94198608398438 108.35000610351562,68.36099243164062 109.3699951171875,67.77301025390625 " stroke="#1F2326" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="0.4844"/>
|
||||
<path d="m109.488,59.892c0,0 -1.867,0.729 -1.377,1.672c0,0 0.345,0.632 1.953,-0.043c1.608,-0.675 1.928,-1.03 1.746,-1.697c-0.182,-0.667 -1.491,-0.356 -2.322,0.068z" fill="#1F2326" id="svg_74"/>
|
||||
<path d="m102.882,63.629c0,0 1.35,-0.953 1.946,-0.137c0,0 0.392,0.558 -0.811,1.355c-1.202,0.797 -1.732,0.802 -2.113,0.385c-0.431,-0.472 0.293,-1.229 0.978,-1.603z" fill="#1F2326" id="svg_75"/>
|
||||
<path d="m105.237,65.74c0.173,0.337 0.04,0.75 -0.297,0.923c-0.337,0.173 -0.75,0.04 -0.923,-0.297c-0.173,-0.337 -0.04,-0.75 0.297,-0.923c0.336,-0.173 0.75,-0.04 0.923,0.297z" fill="#1F2326" id="svg_76"/>
|
||||
<path d="m111.555,62.703c0.173,0.337 0.04,0.75 -0.297,0.923c-0.337,0.173 -0.75,0.04 -0.923,-0.297c-0.173,-0.337 -0.04,-0.75 0.297,-0.923c0.337,-0.173 0.75,-0.04 0.923,0.297z" fill="#1F2326" id="svg_77"/>
|
||||
<g id="svg_78">
|
||||
<path d="m104.782,63.321c1.772,1.22 2.347,3.639 1.284,5.404c-1.064,1.765 -3.363,2.206 -5.135,0.987c-1.772,-1.22 -2.347,-3.639 -1.284,-5.404s3.363,-2.207 5.135,-0.987z" fill="none" id="svg_79" stroke="#FFFFFF" stroke-miterlimit="10" stroke-width="0.5379"/>
|
||||
<path d="m115.137,60.479c1.06,2.067 0.244,4.603 -1.823,5.663s-4.603,0.244 -5.663,-1.823c-1.06,-2.067 -0.244,-4.602 1.823,-5.663c2.067,-1.06 4.602,-0.244 5.663,1.823z" fill="none" id="svg_80" stroke="#FFFFFF" stroke-miterlimit="10" stroke-width="0.5583"/>
|
||||
<line fill="none" id="svg_81" stroke="#FFFFFF" stroke-miterlimit="10" stroke-width="0.5583" x1="106.288" x2="107.728" y1="65.207" y2="64.469"/>
|
||||
<line fill="none" id="svg_82" stroke="#FFFFFF" stroke-miterlimit="10" stroke-width="0.5583" x1="115.094" x2="116.467" y1="60.254" y2="57.798"/>
|
||||
</g>
|
||||
<path d="m108.712,69.837l3.285,-1.825c0,0 0.557,1.217 -0.95,2.076c-1.507,0.859 -2.335,-0.251 -2.335,-0.251z" fill="#FFFFFF" id="svg_83"/>
|
||||
</g>
|
||||
<path d="m109.13,51.9c0,0 1.992,4.043 5.689,2.133c4.311,-2.228 -1.543,-4.731 -1.543,-4.731l-4.146,2.598z" fill="#1F2326" id="svg_84"/>
|
||||
<path d="m99.054,58.361c0,0 -3.777,0.019 -4.245,-3.688c-0.467,-3.707 3.542,-4.175 3.542,-4.175s-0.946,-2.081 0.951,-3.418c1.897,-1.337 4.607,0.405 4.607,0.405s7.908,-5.598 10.438,-3.469c2.53,2.129 -8.964,8.538 -8.964,8.538l-6.329,5.807z" fill="#1F2326" id="svg_85"/>
|
||||
<path d="m126.697,74.575c0,0 -2.129,4.731 -7.66,9.867c0,0 -2.312,-3.402 -2.767,-7.287l10.427,-2.58z" fill="#B87F58" id="svg_86"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_87">
|
||||
<g id="svg_89">
|
||||
<g id="svg_90">
|
||||
<g id="svg_91">
|
||||
<g id="svg_94"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_98">
|
||||
<g id="svg_99"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_105">
|
||||
<g id="svg_106">
|
||||
<g id="svg_107">
|
||||
<g id="svg_108">
|
||||
<g id="svg_110"/>
|
||||
</g>
|
||||
<g id="svg_113">
|
||||
<g id="svg_115"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_119">
|
||||
<g id="svg_127"/>
|
||||
<g id="svg_130"/>
|
||||
</g>
|
||||
<g id="svg_138">
|
||||
<g id="svg_139">
|
||||
<g id="svg_141"/>
|
||||
</g>
|
||||
<g id="svg_145">
|
||||
<g id="svg_146">
|
||||
<g id="svg_147"/>
|
||||
<g id="svg_150"/>
|
||||
</g>
|
||||
<g id="svg_154">
|
||||
<g id="svg_155">
|
||||
<g id="svg_156"/>
|
||||
<g id="svg_160"/>
|
||||
</g>
|
||||
<g id="svg_163">
|
||||
<g id="svg_165"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_168">
|
||||
<g id="svg_170">
|
||||
<g id="svg_171">
|
||||
<g id="svg_172"/>
|
||||
<g id="svg_174"/>
|
||||
</g>
|
||||
<g id="svg_178">
|
||||
<g id="svg_179">
|
||||
<g id="svg_180"/>
|
||||
</g>
|
||||
<g id="svg_183"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_186">
|
||||
<g id="svg_187">
|
||||
<g id="svg_188">
|
||||
<g id="svg_189">
|
||||
<g id="svg_191">
|
||||
<g id="svg_192"/>
|
||||
<g id="svg_195"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_199">
|
||||
<g id="svg_201">
|
||||
<g id="svg_202"/>
|
||||
<g id="svg_205"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_209"/>
|
||||
</g>
|
||||
<g id="svg_212">
|
||||
<g id="svg_213"/>
|
||||
<g id="svg_216"/>
|
||||
<g id="svg_219">
|
||||
<g id="svg_220">
|
||||
<g id="svg_222"/>
|
||||
</g>
|
||||
<g id="svg_225">
|
||||
<g id="svg_226"/>
|
||||
<g id="svg_229"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_232">
|
||||
<g id="svg_233"/>
|
||||
</g>
|
||||
<g id="svg_239">
|
||||
<g id="svg_246"/>
|
||||
<g id="svg_254">
|
||||
<g id="svg_255"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_259">
|
||||
<g id="svg_261">
|
||||
<g id="svg_262">
|
||||
<g id="svg_264"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_268">
|
||||
<g id="svg_269">
|
||||
<g id="svg_270">
|
||||
<g id="svg_271">
|
||||
<g id="svg_275"/>
|
||||
</g>
|
||||
<g id="svg_281">
|
||||
<g id="svg_282">
|
||||
<g id="svg_283">
|
||||
<g id="svg_284">
|
||||
<g id="svg_286"/>
|
||||
</g>
|
||||
<g id="svg_292"/>
|
||||
</g>
|
||||
<g id="svg_295">
|
||||
<g id="svg_296">
|
||||
<g id="svg_298"/>
|
||||
</g>
|
||||
<g id="svg_304"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_307">
|
||||
<g id="svg_308">
|
||||
<g id="svg_309">
|
||||
<g id="svg_310"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_315"/>
|
||||
<g id="svg_318">
|
||||
<g id="svg_319"/>
|
||||
<g id="svg_322">
|
||||
<g id="svg_323"/>
|
||||
<g id="svg_326"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_329">
|
||||
<g id="svg_336"/>
|
||||
<g id="svg_343"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_350">
|
||||
<g id="svg_351">
|
||||
<g id="svg_352"/>
|
||||
<g id="svg_355"/>
|
||||
<g id="svg_358">
|
||||
<g id="svg_360"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_363"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_368">
|
||||
<g id="svg_370"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="DESIGNED_BY_FREEPIK">
|
||||
<g id="svg_373">
|
||||
<g id="XMLID_319_">
|
||||
<g id="XMLID_338_"/>
|
||||
</g>
|
||||
<g id="svg_374"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 14 KiB |
@@ -1,15 +1,89 @@
|
||||
<template>
|
||||
<div></div>
|
||||
<img style="display: none;" :src="devCookiePath" v-if="!isProduction" />
|
||||
<v-app>
|
||||
<router-view />
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useTheme } from 'vuetify';
|
||||
import { register } from 'register-service-worker';
|
||||
|
||||
import { mapStores } from 'pinia';
|
||||
import { useSettingsStore } from '@/stores/setting.js';
|
||||
import { useUserStore } from '@/stores/user.js';
|
||||
import { useTokensStore } from '@/stores/token.js';
|
||||
import { useExchangeRatesStore } from '@/stores/exchangeRates.js';
|
||||
|
||||
import { isProduction } from '@/lib/version.js';
|
||||
import { loadMapAssets } from '@/lib/map/index.js';
|
||||
import { getSystemTheme } from '@/lib/ui.js';
|
||||
|
||||
export default {
|
||||
created() {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
window.location.replace('../mobile/');
|
||||
} else {
|
||||
window.location.replace('../mobile.html');
|
||||
data() {
|
||||
return {
|
||||
isProduction: isProduction(),
|
||||
devCookiePath: isProduction() ? '' : '/dev/cookies'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapStores(useSettingsStore, useUserStore, useTokensStore, useExchangeRatesStore),
|
||||
},
|
||||
created() {
|
||||
const self = this;
|
||||
const theme = useTheme();
|
||||
|
||||
if (self.settingsStore.appSettings.theme === 'light') {
|
||||
theme.global.name.value = 'light';
|
||||
} else if (self.settingsStore.appSettings.theme === 'dark') {
|
||||
theme.global.name.value = 'dark';
|
||||
} else {
|
||||
theme.global.name.value = getSystemTheme();
|
||||
}
|
||||
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function (e) {
|
||||
if (self.settingsStore.appSettings.theme === 'auto') {
|
||||
if (e.matches) {
|
||||
theme.global.name.value = 'dark';
|
||||
} else {
|
||||
theme.global.name.value = 'light';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let localeDefaultSettings = self.$locale.initLocale(self.userStore.currentUserLanguage, self.settingsStore.appSettings.timeZone);
|
||||
self.settingsStore.updateLocalizedDefaultSettings(localeDefaultSettings);
|
||||
|
||||
if (self.$user.isUserLogined()) {
|
||||
if (!self.settingsStore.appSettings.applicationLock) {
|
||||
// refresh token if user is logined
|
||||
self.tokensStore.refreshTokenAndRevokeOldToken().then(response => {
|
||||
if (response.user) {
|
||||
localeDefaultSettings = self.$locale.setLanguage(response.user.language);
|
||||
self.settingsStore.updateLocalizedDefaultSettings(localeDefaultSettings);
|
||||
}
|
||||
});
|
||||
|
||||
// auto refresh exchange rates data
|
||||
if (self.settingsStore.appSettings.autoUpdateExchangeRatesData) {
|
||||
self.exchangeRatesStore.getLatestExchangeRates({ silent: true, force: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isProduction()) {
|
||||
register('./sw.js', {
|
||||
registrationOptions: {
|
||||
scope: './'
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const languageInfo = this.$locale.getCurrentLanguageInfo();
|
||||
loadMapAssets(languageInfo ? languageInfo.code : null);
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -9,20 +9,31 @@
|
||||
import { f7ready } from 'framework7-vue';
|
||||
import routes from './router/mobile.js';
|
||||
|
||||
import { mapStores } from 'pinia';
|
||||
import { useSettingsStore } from '@/stores/setting.js';
|
||||
import { useUserStore } from '@/stores/user.js';
|
||||
import { useTokensStore } from '@/stores/token.js';
|
||||
import { useExchangeRatesStore } from '@/stores/exchangeRates.js';
|
||||
|
||||
import { isProduction } from '@/lib/version.js';
|
||||
import { getTheme, isEnableAnimate } from '@/lib/settings.js';
|
||||
import { loadMapAssets } from '@/lib/map/index.js';
|
||||
import { isModalShowing, setAppFontSize } from '@/lib/ui.mobile.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
const self = this;
|
||||
let darkMode = 'auto';
|
||||
|
||||
if (self.$settings.getTheme() === 'light') {
|
||||
if (getTheme() === 'light') {
|
||||
darkMode = false;
|
||||
} else if (self.$settings.getTheme() === 'dark') {
|
||||
} else if (getTheme() === 'dark') {
|
||||
darkMode = true;
|
||||
}
|
||||
|
||||
return {
|
||||
isProduction: self.$settings.isProduction(),
|
||||
devCookiePath: self.$settings.isProduction() ? '' : '/dev/cookies',
|
||||
isProduction: isProduction(),
|
||||
devCookiePath: isProduction() ? '' : '/dev/cookies',
|
||||
f7params: {
|
||||
name: 'ezBookkeeping',
|
||||
theme: 'ios',
|
||||
@@ -36,31 +47,31 @@ export default {
|
||||
tapHold: true
|
||||
},
|
||||
serviceWorker: {
|
||||
path: self.$settings.isProduction() ? './sw.js' : undefined,
|
||||
path: isProduction() ? './sw.js' : undefined,
|
||||
scope: './',
|
||||
},
|
||||
actions: {
|
||||
animate: self.$settings.isEnableAnimate(),
|
||||
animate: isEnableAnimate(),
|
||||
backdrop: true,
|
||||
closeOnEscape: true
|
||||
},
|
||||
dialog: {
|
||||
animate: self.$settings.isEnableAnimate(),
|
||||
animate: isEnableAnimate(),
|
||||
backdrop: true
|
||||
},
|
||||
popover: {
|
||||
animate: self.$settings.isEnableAnimate(),
|
||||
animate: isEnableAnimate(),
|
||||
backdrop: true,
|
||||
closeOnEscape: true
|
||||
},
|
||||
popup: {
|
||||
animate: self.$settings.isEnableAnimate(),
|
||||
animate: isEnableAnimate(),
|
||||
backdrop: true,
|
||||
closeOnEscape: true,
|
||||
swipeToClose: true
|
||||
},
|
||||
sheet: {
|
||||
animate: self.$settings.isEnableAnimate(),
|
||||
animate: isEnableAnimate(),
|
||||
backdrop: true,
|
||||
closeOnEscape: true
|
||||
},
|
||||
@@ -68,7 +79,7 @@ export default {
|
||||
routableModals: false
|
||||
},
|
||||
view: {
|
||||
animate: self.$settings.isEnableAnimate(),
|
||||
animate: isEnableAnimate(),
|
||||
browserHistory: !self.isiOSHomeScreenMode(),
|
||||
browserHistoryInitialMatch: true,
|
||||
browserHistoryAnimate: false,
|
||||
@@ -81,26 +92,35 @@ export default {
|
||||
hasBackdrop: undefined
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapStores(useSettingsStore, useUserStore, useTokensStore, useExchangeRatesStore),
|
||||
},
|
||||
created() {
|
||||
const self = this;
|
||||
|
||||
let localeDefaultSettings = self.$locale.initLocale(self.userStore.currentUserLanguage, self.settingsStore.appSettings.timeZone);
|
||||
self.settingsStore.updateLocalizedDefaultSettings(localeDefaultSettings);
|
||||
|
||||
if (self.$user.isUserLogined()) {
|
||||
if (!self.$settings.isEnableApplicationLock()) {
|
||||
if (!self.settingsStore.appSettings.applicationLock) {
|
||||
// refresh token if user is logined
|
||||
self.$store.dispatch('refreshTokenAndRevokeOldToken').then(response => {
|
||||
if (response.user && response.user.language) {
|
||||
self.$locale.setLanguage(response.user.language);
|
||||
self.tokensStore.refreshTokenAndRevokeOldToken().then(response => {
|
||||
if (response.user) {
|
||||
localeDefaultSettings = self.$locale.setLanguage(response.user.language);
|
||||
self.settingsStore.updateLocalizedDefaultSettings(localeDefaultSettings);
|
||||
}
|
||||
});
|
||||
|
||||
// auto refresh exchange rates data
|
||||
if (self.$settings.isAutoUpdateExchangeRatesData()) {
|
||||
self.$store.dispatch('getLatestExchangeRates', { silent: true, force: false });
|
||||
if (self.settingsStore.appSettings.autoUpdateExchangeRatesData) {
|
||||
self.exchangeRatesStore.getLatestExchangeRates({ silent: true, force: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
setAppFontSize(this.settingsStore.appSettings.fontSize);
|
||||
|
||||
f7ready((f7) => {
|
||||
this.isDarkMode = f7.darkMode;
|
||||
this.setThemeColorMeta(f7.darkMode);
|
||||
@@ -117,7 +137,7 @@ export default {
|
||||
f7.on('sheetClose', (event) => this.onBackdropChanged(event));
|
||||
|
||||
f7.on('pageBeforeOut', () => {
|
||||
if (this.$ui.isModalShowing()) {
|
||||
if (isModalShowing()) {
|
||||
f7.actions.close('.actions-modal.modal-in', false);
|
||||
f7.dialog.close('.dialog.modal-in', false);
|
||||
f7.popover.close('.popover.modal-in', false);
|
||||
@@ -131,6 +151,11 @@ export default {
|
||||
this.setThemeColorMeta(isDarkMode);
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const languageInfo = this.$locale.getCurrentLanguageInfo();
|
||||
loadMapAssets(languageInfo ? languageInfo.code : null);
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
isiOSHomeScreenMode() {
|
||||
@@ -174,511 +199,3 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/** Global style **/
|
||||
html, body {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
body {
|
||||
-ms-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/** Common class **/
|
||||
.no-right-border {
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.no-bottom-border {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.work-break-all {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.full-line {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.smaller {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.readonly {
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
.skeleton-text {
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
.segmented.readonly .button:not(.button-active) > span,
|
||||
.list.readonly .item-content .item-title.item-label,
|
||||
.list.readonly .item-content .item-title > .item-header {
|
||||
opacity: 0.55 !important;
|
||||
}
|
||||
|
||||
/** Replacing the default style of framework7 **/
|
||||
:root {
|
||||
--f7-popup-push-offset: 5px;
|
||||
--f7-color-gray: #8e8e93;
|
||||
--f7-color-gray-rgb: 142, 142, 147;
|
||||
--f7-color-gray-shade: #79797f;
|
||||
--f7-color-gray-tint: #a3a3a7;
|
||||
--default-icon-color: var(--f7-text-color);
|
||||
}
|
||||
|
||||
:root .dark {
|
||||
--default-icon-color: var(--f7-text-color);
|
||||
}
|
||||
|
||||
.color-gray {
|
||||
--f7-theme-color: var(--f7-color-gray);
|
||||
--f7-theme-color-rgb: var(--f7-color-gray-rgb);
|
||||
--f7-theme-color-shade: var(--f7-color-gray-shade);
|
||||
--f7-theme-color-tint: var(--f7-color-gray-tint);
|
||||
}
|
||||
|
||||
.ios .dark, .ios.dark {
|
||||
--f7-list-item-header-text-color: inherit !important;
|
||||
}
|
||||
|
||||
i.icon.la, i.icon.las, i.icon.lab {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.chip.chip-placeholder {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/** Replacing the default style of @vuepic/vue-datepicker **/
|
||||
.dp__theme_light {
|
||||
--dp-primary-color: #c67e48;
|
||||
}
|
||||
|
||||
.dp__theme_dark {
|
||||
--dp-primary-color: #c67e48;
|
||||
}
|
||||
|
||||
/** Common class for replacing the default style of framework7 **/
|
||||
.navbar .navbar-compact-icons.right a + a {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.toolbar-item-auto-size .toolbar-inner {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
.toolbar-item-auto-size .toolbar-inner > .link {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.tabbar-primary-link,
|
||||
.tabbar-item-changed {
|
||||
color: var(--f7-theme-color);
|
||||
}
|
||||
|
||||
.tabbar-text-with-ellipsis > span {
|
||||
display: block;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-all;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.block-title .accordion-item-toggle .icon {
|
||||
color: var(--f7-list-chevron-icon-color);
|
||||
font-size: var(--f7-list-chevron-icon-font-size);
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
.list-item-media-valign-middle .item-media {
|
||||
align-self: normal !important;
|
||||
}
|
||||
|
||||
.list-item-no-item-after .item-after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.list-item-with-header-and-title .item-content .item-header {
|
||||
margin-bottom: 11px;
|
||||
}
|
||||
|
||||
.list-item-with-header-and-title.list-item-title-hide-overflow .item-content .list-item-custom-title {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.list .item-content .input.list-title-input {
|
||||
margin-top: calc(-1 * var(--f7-list-item-padding-vertical));
|
||||
margin-bottom: calc(-1 * var(--f7-list-item-padding-vertical));
|
||||
}
|
||||
|
||||
.list .item-content .list-item-valign-middle {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.list .item-content .list-item-showing {
|
||||
color: rgba(0, 0, 0, 0.2);
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.dark .list .item-content .list-item-showing {
|
||||
color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.accordion-item.list-item-checked > .item-link > .item-content .item-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.list.list-dividers li.has-child-list-item .item-inner:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
background-color: var(--f7-list-item-border-color);
|
||||
display: block !important;
|
||||
z-index: 15;
|
||||
top: auto;
|
||||
right: auto;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
transform-origin: 50% 100%;
|
||||
transform: scaleY(calc(1 / var(--f7-device-pixel-ratio)));
|
||||
}
|
||||
|
||||
.list.list-dividers li.list-group-title:first-child,
|
||||
.list.list-dividers li.list-group-title.actual-first-child {
|
||||
border-radius: var(--f7-list-inset-border-radius) var(--f7-list-inset-border-radius) 0 0;
|
||||
}
|
||||
|
||||
.list.list-dividers li.list-group-title:first-child:before,
|
||||
.list.list-dividers li.list-group-title.actual-first-child:before {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.list.list-dividers li:last-child > .swipeout-content > .item-content > .item-inner:after,
|
||||
.list.list-dividers li:last-child > .swipeout-content > .item-link > .item-content > .item-inner:after,
|
||||
.list.list-dividers li.actual-last-child > .swipeout-content > .item-content > .item-inner:after,
|
||||
.list.list-dividers li.actual-last-child > .swipeout-content > .item-link > .item-content > .item-inner:after {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.list.inset li.list-group-title:first-child > a.button {
|
||||
border-radius: var(--f7-button-border-radius);
|
||||
}
|
||||
|
||||
.list.inset li.swipeout.actual-first-child,
|
||||
.list.inset li.actual-first-child > a {
|
||||
border-radius: var(--f7-list-inset-border-radius) var(--f7-list-inset-border-radius) 0 0;
|
||||
}
|
||||
|
||||
.list.inset li.swipeout.actual-last-child,
|
||||
.list.inset li.actual-last-child > a {
|
||||
border-radius: 0 0 var(--f7-list-inset-border-radius) var(--f7-list-inset-border-radius);
|
||||
}
|
||||
|
||||
.list.inset li.swipeout.actual-first-child.actual-last-child,
|
||||
.list.inset li.actual-first-child.actual-last-child > a {
|
||||
border-radius: var(--f7-list-inset-border-radius);
|
||||
}
|
||||
|
||||
.list.inset.list-has-group-title li.swipeout.actual-first-child,
|
||||
.list.inset.list-has-group-title li.actual-first-child > a {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.list.inset.list-has-group-title li.swipeout:first-child:last-child,
|
||||
.list.inset.list-has-group-title li:first-child:last-child > a,
|
||||
.list.inset.list-has-group-title li.swipeout.actual-first-child.actual-last-child,
|
||||
.list.inset.list-has-group-title li.actual-first-child.actual-last-child > a {
|
||||
border-radius: 0 0 var(--f7-list-inset-border-radius) var(--f7-list-inset-border-radius);
|
||||
}
|
||||
|
||||
.accordion-item .block-title+.accordion-item-content .list.inset li.swipeout:first-child:not(:last-child),
|
||||
.accordion-item .block-title+.accordion-item-content .list.inset li:first-child:not(:last-child) > a {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.accordion-item .block-title+.accordion-item-content .list.inset li.swipeout:first-child:last-child,
|
||||
.accordion-item .block-title+.accordion-item-content .list.inset li:first-child:last-child > a,
|
||||
.accordion-item .block-title+.accordion-item-content .list.inset li.swipeout.actual-last-child:first-child,
|
||||
.accordion-item .block-title+.accordion-item-content .list.inset li.actual-last-child:first-child > a {
|
||||
border-radius: 0 0 var(--f7-list-inset-border-radius) var(--f7-list-inset-border-radius);
|
||||
}
|
||||
|
||||
.list .item-content .list-item-checked-icon {
|
||||
font-size: 20px;
|
||||
color: var(--f7-radio-active-color, var(--f7-theme-color));
|
||||
margin-right: calc(var(--f7-list-item-media-margin) + var(--f7-checkbox-extra-margin));
|
||||
}
|
||||
|
||||
.list .item-content > .item-inner > .item-after .list-item-checked-icon {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.list li.no-margin .item-content.item-input {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ebk-list-item-error-info div.item-footer {
|
||||
color: var(--f7-input-error-text-color)
|
||||
}
|
||||
|
||||
.skeleton-text .list-item-toggle .item-after {
|
||||
height: var(--f7-toggle-height);
|
||||
}
|
||||
|
||||
.skeleton-text .list-item-toggle .item-after > span {
|
||||
line-height: var(--f7-toggle-height);
|
||||
font-size: var(--f7-toggle-height);
|
||||
}
|
||||
|
||||
.no-sortable > .sortable-handler {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.card-header-content {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.icon-after-text {
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
.badge.right-bottom-icon {
|
||||
margin-left: -12px;
|
||||
margin-top: 14px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.badge.right-bottom-icon > .icon {
|
||||
font-size: 13px;
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
}
|
||||
|
||||
/** Swipe handler **/
|
||||
.swipe-handler {
|
||||
height: 26px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.swipe-handler:after {
|
||||
content: "";
|
||||
width: 36px;
|
||||
height: 6px;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
margin-left: -18px;
|
||||
margin-top: -8px;
|
||||
border-radius: 3px;
|
||||
background: #666
|
||||
}
|
||||
|
||||
/** list-item-with-multi-item for framework7 **/
|
||||
.list-item-with-multi-item .item-content,
|
||||
.list-item-with-multi-item .item-inner {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.list-item-with-multi-item .item-inner > div {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.list-item-with-multi-item > .item-content > .item-inner:after {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.list-item-with-multi-item .list-item-subitem:first-child .item-content {
|
||||
padding-left: calc(var(--f7-list-item-padding-horizontal) + var(--f7-safe-area-left));
|
||||
}
|
||||
|
||||
.list-item-with-multi-item .list-item-subitem .item-inner {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding-left: calc(var(--f7-list-item-padding-horizontal) + var(--f7-safe-area-left));
|
||||
padding-top: var(--f7-list-item-padding-vertical);
|
||||
padding-bottom: var(--f7-list-item-padding-vertical);
|
||||
}
|
||||
|
||||
.list-item-with-multi-item .list-item-subitem:first-child .item-inner {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
/** Combination list for framework7 **/
|
||||
.combination-list-wrapper {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.combination-list-wrapper .block-title {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.combination-list-wrapper .list.combination-list-header {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.combination-list-wrapper .list.combination-list-header .item-title {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.combination-list-wrapper .list.combination-list-header > ul {
|
||||
background-color: var(--f7-list-group-title-bg-color);
|
||||
}
|
||||
|
||||
.combination-list-wrapper .list.combination-list-header.combination-list-opened > ul {
|
||||
border-radius: var(--f7-list-inset-border-radius) var(--f7-list-inset-border-radius) 0 0;
|
||||
}
|
||||
|
||||
.combination-list-wrapper .list.combination-list-header.combination-list-closed > ul {
|
||||
border-radius: var(--f7-list-inset-border-radius);
|
||||
}
|
||||
|
||||
.combination-list-wrapper .list.combination-list-header .combination-list-chevron-icon {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.combination-list-wrapper .list.combination-list-content.inset > ul {
|
||||
border-radius: 0 0 var(--f7-list-inset-border-radius) var(--f7-list-inset-border-radius);
|
||||
}
|
||||
|
||||
/** Nested List item for framework7 **/
|
||||
.nested-list-item .item-title {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nested-list-item.has-child-list-item > .swipeout-content > .item-content > .item-inner,
|
||||
.nested-list-item.has-child-list-item > .swipeout-content > .item-link > .item-content > .item-inner {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.nested-list-item.has-child-list-item .nested-list-item-child .item-inner {
|
||||
padding-bottom: var(--f7-list-item-padding-vertical);
|
||||
}
|
||||
|
||||
.nested-list-item.has-child-list-item .item-link.active-state {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.nested-list-item.has-child-list-item .item-link .item-inner {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.nested-list-item.has-child-list-item .item-link .item-inner:before {
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.nested-list-item.has-child-list-item .item-link.active-state .item-inner .nested-list-item-child .item-link.active-state {
|
||||
background-color: var(--f7-list-link-pressed-bg-color);
|
||||
}
|
||||
|
||||
.nested-list-item.has-child-list-item .item-link .item-inner .nested-list-item-child .item-link .item-inner {
|
||||
padding-right: calc(var(--f7-list-chevron-icon-area) + var(--f7-list-item-padding-horizontal) + var(--f7-safe-area-right));
|
||||
}
|
||||
|
||||
.nested-list-item.has-child-list-item .item-link .item-inner .nested-list-item-child .item-link .item-inner:before {
|
||||
color: var(--f7-list-chevron-icon-color);
|
||||
}
|
||||
|
||||
.nested-list-item .nested-list-item-title {
|
||||
align-self: center;
|
||||
margin-right: var(--f7-list-item-media-margin);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.nested-list-item.has-child-list-item .nested-list-item-title {
|
||||
margin-left: var(--f7-list-item-media-margin);
|
||||
}
|
||||
|
||||
.nested-list-item.has-child-list-item > .swipeout-content > .item-content > .item-inner:after,
|
||||
.nested-list-item.has-child-list-item > .swipeout-content > .item-link > .item-content > .item-inner:after {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.list.inset li.nested-list-item.has-child-list-item .item-inner li.nested-list-item-child,
|
||||
.list.inset li.nested-list-item.has-child-list-item .item-inner li.nested-list-item-child > a {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.list.list-dividers li.nested-list-item.has-child-list-item .item-inner .nested-list-item-child .item-inner:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
background-color: var(--f7-list-item-border-color);
|
||||
display: block !important;
|
||||
z-index: 15;
|
||||
top: auto;
|
||||
right: auto;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
transform-origin: 50% 100%;
|
||||
transform: scaleY(calc(1 / var(--f7-device-pixel-ratio)));
|
||||
}
|
||||
|
||||
.list.list-dividers li.nested-list-item.has-child-list-item:last-child .item-inner .nested-list-item-child:last-child .item-inner:after,
|
||||
.list.list-dividers li.nested-list-item.has-child-list-item.actual-last-child .item-inner .nested-list-item-child.actual-last-child .item-inner:after {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.sortable-enabled .nested-list-item .nested-list-item-child .item-inner {
|
||||
padding-right: var(--f7-safe-area-right) !important;
|
||||
}
|
||||
|
||||
/** Fix @vuepic/vue-datepicker style issue **/
|
||||
.dp__main.dp__flex_display {
|
||||
flex-direction: column
|
||||
}
|
||||
|
||||
.dp__main .dp__preset_range {
|
||||
white-space: inherit;
|
||||
}
|
||||
|
||||
.dp__main .dp__menu_inner {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.dp__main .dp__menu_inner .dp__month_year_row > button {
|
||||
width: inherit;
|
||||
}
|
||||
|
||||
.dp__main .dp__menu_inner .dp__month_year_row > button.dp__button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dp__main .dp__menu_inner .dp__month_year_row .dp__month_year_wrap > button {
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
.dp__main .dp__calendar .dp__calendar_item {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.dp__main .dp__calendar .dp__calendar_item > .dp__cell_inner {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<div ref="mapContainer" style="width: 100%" :class="mapClass" :style="finalMapStyle"></div>
|
||||
<slot name="error-title"
|
||||
:mapSupported="mapSupported" :mapDependencyLoaded="mapDependencyLoaded"
|
||||
v-if="!mapSupported || !mapDependencyLoaded"></slot>
|
||||
<slot name="error-content"
|
||||
:mapSupported="mapSupported" :mapDependencyLoaded="mapDependencyLoaded"
|
||||
v-if="!mapSupported || !mapDependencyLoaded"></slot>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
copyObjectTo
|
||||
} from '@/lib/common.js';
|
||||
import {
|
||||
createMapHolder,
|
||||
initMapInstance,
|
||||
setMapCenterTo,
|
||||
setMapCenterMarker,
|
||||
removeMapCenterMarker
|
||||
} from '@/lib/map/index.js';
|
||||
|
||||
export default {
|
||||
props: [
|
||||
'height',
|
||||
'mapClass',
|
||||
'mapStyle',
|
||||
'geoLocation'
|
||||
],
|
||||
expose: [
|
||||
'init'
|
||||
],
|
||||
data() {
|
||||
this.mapHolder = createMapHolder();
|
||||
|
||||
return {
|
||||
mapSupported: !!this.mapHolder,
|
||||
mapDependencyLoaded: this.mapHolder && this.mapHolder.dependencyLoaded,
|
||||
mapInited: false,
|
||||
initCenter: {
|
||||
latitude: 0,
|
||||
longitude: 0,
|
||||
},
|
||||
zoomLevel: 1
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
finalMapStyle() {
|
||||
const styles = copyObjectTo(this.mapStyle, {});
|
||||
|
||||
if (this.height) {
|
||||
styles.height = this.height;
|
||||
}
|
||||
|
||||
if (!this.mapSupported || !this.mapDependencyLoaded) {
|
||||
styles.height = '0';
|
||||
}
|
||||
|
||||
return styles;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
let isFirstInit = false;
|
||||
let centerChanged = false;
|
||||
|
||||
if (!this.mapSupported || !this.mapDependencyLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.geoLocation && (this.geoLocation.longitude || this.geoLocation.latitude)) {
|
||||
if (this.initCenter.latitude !== this.geoLocation.latitude || this.initCenter.longitude !== this.geoLocation.longitude) {
|
||||
this.initCenter.latitude = this.geoLocation.latitude;
|
||||
this.initCenter.longitude = this.geoLocation.longitude;
|
||||
this.zoomLevel = this.mapHolder.defaultZoomLevel;
|
||||
|
||||
centerChanged = true;
|
||||
}
|
||||
} else if (!this.geoLocation || (!this.geoLocation.longitude && !this.geoLocation.latitude)) {
|
||||
if (this.initCenter.latitude || this.initCenter.longitude) {
|
||||
this.initCenter.latitude = 0;
|
||||
this.initCenter.longitude = 0;
|
||||
this.zoomLevel = this.mapHolder.minZoomLevel;
|
||||
|
||||
centerChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.mapHolder.inited) {
|
||||
const languageInfo = this.$locale.getCurrentLanguageInfo();
|
||||
|
||||
initMapInstance(this.mapHolder, this.$refs.mapContainer, {
|
||||
language: languageInfo ? languageInfo.code : null,
|
||||
initCenter: this.initCenter,
|
||||
zoomLevel: this.zoomLevel,
|
||||
text: {
|
||||
zoomIn: this.$t('Zoom in'),
|
||||
zoomOut: this.$t('Zoom out'),
|
||||
}
|
||||
});
|
||||
|
||||
if (this.mapHolder.inited) {
|
||||
isFirstInit = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isFirstInit || centerChanged) {
|
||||
setMapCenterTo(this.mapHolder, this.initCenter, this.zoomLevel);
|
||||
}
|
||||
|
||||
if (centerChanged && this.zoomLevel > this.mapHolder.minZoomLevel) {
|
||||
setMapCenterMarker(this.mapHolder, this.initCenter);
|
||||
} else if (centerChanged && this.zoomLevel <= this.mapHolder.minZoomLevel) {
|
||||
removeMapCenterMarker(this.mapHolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||