67 Commits

Author SHA1 Message Date
988479dc5d Big Refacotr - part2 2025-10-27 18:54:31 +01:00
5a304f11ac Big Refacotr -part1 2025-10-27 17:22:32 +01:00
44edb080d8 Refacotor: Added repository interface to cover UserRepository dependency
Some checks are pending
continuous-integration/drone/push Build is pending
2025-10-24 10:26:15 +02:00
5540e02644 Changed permissions cache save interval to 30 minutes
Some checks reported errors
continuous-integration/drone Build encountered an error
2025-10-23 09:11:31 +02:00
956fd3ee84 Moved infra from internal/ to src/ dir 2025-10-23 08:03:29 +02:00
1ef015cd05 Moved infra/ to internal/ sub-dir 2025-10-23 08:00:36 +02:00
ed325c7faa Added Chronos to schedule tasks, restructured config as common pkg, updated server pkg 2025-10-23 07:30:05 +02:00
5279e34b2c Added function to SaveAll URL Access records into cache - tmp solution before worker gonna be working 2025-10-22 19:39:53 +02:00
ad647fd1b0 Renamed service Guard to GuardService 2025-10-22 17:42:23 +02:00
ee0b512bfe Removed coupling with useless services in actions 2025-10-22 17:10:54 +02:00
e546d16222 Moved logic from Handlers to UI Actions 2025-10-22 16:01:19 +02:00
40a7d841b8 Moved lgoin from AccessHandler into Guard Service 2025-10-22 14:10:38 +02:00
e97b232207 Addded refactor note to Router 2025-10-22 13:48:25 +02:00
1e1cce9dc2 Update packages 2025-10-22 13:41:54 +02:00
e022d60b61 Added GetTokenFromAuthorizationHeader in AuthService 2025-10-22 13:41:44 +02:00
07dbe290f1 Added FindByURLAndServiceForRole func to URLAccessRepository 2025-10-22 13:41:04 +02:00
0321f7b767 Added endpoint to verify permission for requested url and service 2025-10-22 13:39:30 +02:00
bd0929748c cleaned up middlewares.go 2025-10-22 10:56:06 +02:00
662a9b7ffd Added access checking middleware
Added URLAccessRepository
Refactor
2025-10-22 10:53:20 +02:00
89b665c3d9 Refactor replace db with UserRepository 2025-10-21 20:39:34 +02:00
9ceea35b08 Cleaned up plugins.go 2025-10-21 18:47:21 +02:00
a6b70410f5 Fixed exit signal from migrate command 2025-10-21 18:46:39 +02:00
060b6af9b3 Fixes in building Docker image 2025-10-21 18:45:59 +02:00
748459c788 Added repo for dealing with DB and Hashing password to register/login endpoints 2025-10-21 18:45:07 +02:00
8469bd3f33 Removed useless pkg/database package 2025-10-21 13:33:51 +02:00
cb6004f230 Added /refresh endpoint and make some refactor again 2025-10-21 10:48:36 +02:00
ab961a8608 Update packages 2025-10-20 20:59:10 +02:00
b58b07f0ac Added JWT Refresh token 2025-10-20 20:59:00 +02:00
50b2127bd7 Refactor added saving auth token to redis when successfully logged in 2025-10-20 19:58:14 +02:00
52fc66e7a9 Refactored database pkg - added function to detect duplicated rows error
Fixed Register method for detect duplicated username/email
2025-10-20 18:24:44 +02:00
bff728234b Added /register handler 2025-10-20 17:58:25 +02:00
7adf6f1699 Changes in srv middlewares 2025-10-20 17:16:53 +02:00
6a9c3e3d3c Updated .gitignore 2025-10-20 17:15:17 +02:00
8e6b85541e health handler fix 2025-10-20 17:15:05 +02:00
7daeb3e38e internal entities package update 2025-10-20 17:12:42 +02:00
f8f390e425 Removed useless health cmd 2025-10-20 17:12:20 +02:00
f978db83ee Refactor 2025-10-20 17:11:36 +02:00
ddbb8bea25 Removed useless files from domain/ 2025-10-20 17:10:05 +02:00
ce20b2cc18 Remvoed useless binary 2025-10-20 17:09:22 +02:00
178ac59dca Fixes in Docker build 2025-10-20 17:09:01 +02:00
d8e14eefe2 Fixed README.md 2025-10-13 19:36:53 +02:00
4bed682b5f Fixed README.md 2025-10-13 19:35:03 +02:00
8bd73873b9 Fixed README.md 2025-10-13 19:34:31 +02:00
3f341790c3 Fixed README.md 2025-10-13 19:34:16 +02:00
3518099b43 Fixed README.md 2025-10-13 19:34:04 +02:00
7f951cd42d Migrate to K8S stack and fixes before big refactoring 2025-10-13 19:32:31 +02:00
ac19e766cf Update 2024-12-05 16:59:08 +01:00
13073daa38 Refactgor, TLS support, v0.5 2024-07-20 19:18:49 +02:00
85f03049d9 added src/.gitignore 2024-07-19 21:51:37 +02:00
a73f2b2921 Huge refactoring, resolved tight coupling 2024-07-19 21:51:16 +02:00
71a43d17a1 Fixes 2023-10-01 22:57:26 +02:00
1909744c79 Fixes 2023-10-01 21:35:21 +02:00
380fe41d1a Update 2023-05-22 21:58:38 +02:00
7adf3b9512 Refactor
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-16 17:28:33 +02:00
cd9bbdfd75 Added LICENSE
Some checks failed
continuous-integration/drone/push Build is failing
2023-03-30 22:16:05 +02:00
90765beb35 update
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-20 17:11:37 +01:00
167d067aed Refactoring
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-25 23:19:48 +01:00
0a684d45f9 Added migrations
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-17 06:31:35 +01:00
78418b230d fixes
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-16 01:01:54 +01:00
cff0644ccb Added wait-for-it.sh script
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-07 02:42:58 +01:00
653224943b KV fixes
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-05 22:34:35 +01:00
09ecea4d1a Added KV Config support and updated build scripts
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-05 21:34:45 +01:00
38d7d1ab86 build fix
Some checks reported errors
continuous-integration/drone/pr Build was killed
continuous-integration/drone/push Build is passing
2022-12-05 05:30:46 +01:00
2e67274cc2 build fix and pkg update
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2022-12-05 05:26:24 +01:00
93e4cdaa74 fixes
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2022-12-03 23:44:04 +01:00
ec4164a9e2 Skip logging of health check requests
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-02 21:51:51 +01:00
6f7fdad944 Fixes, refactor, a lot of things happen
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-02 21:03:08 +01:00
58 changed files with 2088 additions and 761 deletions

View File

@@ -1,2 +0,0 @@
SERVER_IP=0.0.0.0
DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/egommerce

25
.gitignore vendored
View File

@@ -1,19 +1,12 @@
# ---> Go deploy/.env
# Binaries for programs and plugins !deploy/.env.dist
*.exe deploy/.env.*
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c` deploy/server
*.test deploy/scheduler
# Output of the go coverage tool, specifically when used with LiteIDE !deploy/certs/.gitkeep
*.out deploy/certs/*
.env
# Dependency directories (remove the comment below to include it)
vendor/
.vscode/
__debug_bin

View File

@@ -1,25 +0,0 @@
# Builder
FROM golang:alpine AS builder
WORKDIR /go/src/app
ARG MAIN_GO=cmd/main.go
COPY src ./
RUN go mod download && \
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o /go/bin/app $MAIN_GO
# Destination image
FROM gcr.io/distroless/base-debian10
LABEL author="Piotr Biernat"
LABEL service="identity"
LABEL vendor="Egommerce"
LABEL version="1.0"
COPY --from=builder /go/bin/app /app
COPY .env.dist /.env
EXPOSE 8080
ENTRYPOINT ["/app"]

15
Dockerfile.builder Normal file
View File

@@ -0,0 +1,15 @@
# Builder
FROM golang:alpine
ARG BIN_OUTPUT=/go/bin
ARG GO_SERVER=cmd/server/main.go
ARG GO_CHRONOS=cmd/scheduler/main.go
ARG GO_MIGRATE=cmd/migrate/main.go
WORKDIR /go/src/app
COPY src/ ./
RUN export CGO_ENABLED=0 ; export GOOS=linux ; export GOARCH=amd64 && \
go build -ldflags="-w -s" -o "$BIN_OUTPUT/server" $GO_SERVER && \
go build -ldflags="-w -s" -o "$BIN_OUTPUT/scheduler" $GO_CHRONOS && \
go build -ldflags="-w -s" -o "$BIN_OUTPUT/migrate" $GO_MIGRATE

33
Dockerfile.target Normal file
View File

@@ -0,0 +1,33 @@
# Builder
ARG BUILDER_IMAGE
FROM ${BUILDER_IMAGE} AS builder
# Destination image - server
# FROM gcr.io/distroless/base-debian10
FROM alpine:3.22
ARG SVC_NAME
ARG SVC_VER
ARG BUILD_TIME
LABEL dev.egommerce.image.author="Piotr Biernat"
LABEL dev.egommerce.image.vendor="Egommerce"
LABEL dev.egommerce.image.service=${SVC_NAME}
LABEL dev.egommerce.image.version=${SVC_VER}
LABEL dev.egommerce.image.build_time=${BUILD_TIME}
WORKDIR /
COPY --from=builder /go/bin/server /usr/local/bin/server
COPY --from=builder /go/bin/migrate /usr/local/bin/migrate
COPY --from=builder /go/bin/scheduler /usr/local/bin/scheduler
COPY deploy/.env.docker /.env
COPY ./bin/* /usr/local/bin/
RUN chmod 755 /usr/local/bin/entrypoint.sh /usr/local/bin/migrate.sh
EXPOSE 443
ENTRYPOINT ["entrypoint.sh"]
CMD ["sh", "-c", "/usr/local/bin/server"]

173
LICENSE.md Normal file
View File

@@ -0,0 +1,173 @@
# Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible.
**Using Creative Commons Public Licenses**
Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses.
* __Considerations for licensors:__ Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. [More considerations for licensors](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensors).
* __Considerations for the public:__ By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensors permission is not necessary for any reasonfor example, because of any applicable exception or limitation to copyrightthen that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. [More considerations for the public](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensees).
## Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public License
By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions.
### Section 1 Definitions.
a. __Adapted Material__ means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image.
b. __Adapter's License__ means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License.
c. __BY-NC-SA Compatible License__ means a license listed at [creativecommons.org/compatiblelicenses](http://creativecommons.org/compatiblelicenses), approved by Creative Commons as essentially the equivalent of this Public License.
d. __Copyright and Similar Rights__ means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights.
e. __Effective Technological Measures__ means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements.
f. __Exceptions and Limitations__ means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material.
g. __License Elements__ means the license attributes listed in the name of a Creative Commons Public License. The License Elements of this Public License are Attribution, NonCommercial, and ShareAlike.
h. __Licensed Material__ means the artistic or literary work, database, or other material to which the Licensor applied this Public License.
i. __Licensed Rights__ means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license.
j. __Licensor__ means the individual(s) or entity(ies) granting rights under this Public License.
k. __NonCommercial__ means not primarily intended for or directed towards commercial advantage or monetary compensation. For purposes of this Public License, the exchange of the Licensed Material for other material subject to Copyright and Similar Rights by digital file-sharing or similar means is NonCommercial provided there is no payment of monetary compensation in connection with the exchange.
l. __Share__ means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them.
m. __Sui Generis Database Rights__ means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world.
n. __You__ means the individual or entity exercising the Licensed Rights under this Public License. __Your__ has a corresponding meaning.
### Section 2 Scope.
a. ___License grant.___
1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to:
A. reproduce and Share the Licensed Material, in whole or in part, for NonCommercial purposes only; and
B. produce, reproduce, and Share Adapted Material for NonCommercial purposes only.
2. __Exceptions and Limitations.__ For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions.
3. __Term.__ The term of this Public License is specified in Section 6(a).
4. __Media and formats; technical modifications allowed.__ The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material.
5. __Downstream recipients.__
A. __Offer from the Licensor Licensed Material.__ Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License.
B. __Additional offer from the Licensor Adapted Material.__ Every recipient of Adapted Material from You automatically receives an offer from the Licensor to exercise the Licensed Rights in the Adapted Material under the conditions of the Adapters License You apply.
C. __No downstream restrictions.__ You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material.
6. __No endorsement.__ Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i).
b. ___Other rights.___
1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise.
2. Patent and trademark rights are not licensed under this Public License.
3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties, including when the Licensed Material is used other than for NonCommercial purposes.
### Section 3 License Conditions.
Your exercise of the Licensed Rights is expressly made subject to the following conditions.
a. ___Attribution.___
1. If You Share the Licensed Material (including in modified form), You must:
A. retain the following if it is supplied by the Licensor with the Licensed Material:
i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated);
ii. a copyright notice;
iii. a notice that refers to this Public License;
iv. a notice that refers to the disclaimer of warranties;
v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable;
B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and
C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License.
2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information.
3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable.
b. ___ShareAlike.___
In addition to the conditions in Section 3(a), if You Share Adapted Material You produce, the following conditions also apply.
1. The Adapters License You apply must be a Creative Commons license with the same License Elements, this version or later, or a BY-NC-SA Compatible License.
2. You must include the text of, or the URI or hyperlink to, the Adapter's License You apply. You may satisfy this condition in any reasonable manner based on the medium, means, and context in which You Share Adapted Material.
3. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, Adapted Material that restrict exercise of the rights granted under the Adapter's License You apply.
### Section 4 Sui Generis Database Rights.
Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material:
a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database for NonCommercial purposes only;
b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material, including for purposes of Section 3(b); and
c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database.
For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights.
### Section 5 Disclaimer of Warranties and Limitation of Liability.
a. __Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You.__
b. __To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You.__
c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability.
### Section 6 Term and Termination.
a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically.
b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates:
1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or
2. upon express reinstatement by the Licensor.
For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License.
c. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License.
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public License.
### Section 7 Other Terms and Conditions.
a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed.
b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License.
### Section 8 Interpretation.
a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License.
b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions.
c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor.
d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority.
> Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at [creativecommons.org/policies](http://creativecommons.org/policies), Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses.
>
> Creative Commons may be contacted at creativecommons.org

26
Makefile Normal file
View File

@@ -0,0 +1,26 @@
DEPLOY_DIR := ./deploy
SRC_DIR := ./src
build-image-dev:
- sh ${DEPLOY_DIR}/image-build.sh dev
build-image-prod:
- sh ${DEPLOY_DIR}/image-build.sh
push-image-dev:
- sh ${DEPLOY_DIR}/image-push.sh dev
push-image-prod:
- sh ${DEPLOY_DIR}/image-push.sh
build-local-server:
- go build -C ${SRC_DIR} -o ../deploy/server cmd/server/main.go
run-local-server:
- cd deploy/ && ./server
build-local-scheduler:
- go build -C ${SRC_DIR} -o ../deploy/scheduler cmd/scheduler/main.go
run-local-scheduler:
- cd deploy/ && ./scheduler

View File

@@ -2,8 +2,30 @@
Authentication & Authorization service Authentication & Authorization service
Budowanie obrazu: Prepare local dev environment
$ sh deploy/image-build.sh
Opublikowanie obrazu: 1. Add following entry to the /etc/hosts file
$ sh deploy/image-push.sh
```127.0.0.1 egommerce.local```
2. Modify and copy (or link) ```deploy/.env.local``` to the ```deploy/.env``` file
3. Link the cert and key files from stack (./deploy/certs/identity-svc/) to the local ./deploy/certs dir:
```ln -s PATH_TO_THE_STACK_DIR/deploy/certs/identity-svc/identity-svc.crt deploy/certs/identity-svc.crt```
```ln -s PATH_TO_THE_STACK_DIR/deploy/certs/identity-svc/identity-svc.key deploy/certs/identity-svc.key```
4. Build server app binary:
```make build-local```
5. Run server app:
```make run-local```
Build prod image:
$ make build-image-prod
Push prod image:
$ make push-image-prod
Build dev image:
$ make build-image-dev
Push dev image:
$ make push-image-dev

6
bin/entrypoint.sh Executable file
View File

@@ -0,0 +1,6 @@
#!/usr/bin/env sh
# run migrations
migrate.sh
exec "$@"

26
bin/migrate.sh Normal file
View File

@@ -0,0 +1,26 @@
#!/usr/bin/env sh
# ensure migrate env is initialized
$(migrate version >/dev/null 2>&1)
version=$?
if [ $version != "0" ]
then
echo "Creating base table..."
$(migrate init >/dev/null 2>&1)
init=$?
fi
# check again
$(migrate version >/dev/null 2>&1)
version=$?
if [ $version != "0" ]
then
echo "Unable to run migrations."
exit 1
fi
# run migrations
migrate up
echo "Done."
exit $version

16
deploy/.env.dist Normal file
View File

@@ -0,0 +1,16 @@
SERVER_ADDR=:443
APP_NAME=identity-svc
APP_DOMAIN=identity.service.ego.io
API_DATABASE_URL=postgres://postgres:12345678@db-postgres:5432/egommerce
API_CACHE_ADDR=api-cache:6379
API_CACHE_USERNAME=default
API_CACHE_PASSWORD=12345678
API_MONGODB_URL=mongodb://mongodb:12345678@mongo-db:27017
JWT_ACCESS_TOKEN_SECRET_KEY=SomeFancySecretAndRandomString
JWT_ACCESS_TOKEN_EXPIRE_TIME=1 # hours
JWT_REFRESH_TOKEN_SECRET_KEY=SomeFancySecretAndRandomStringAgain
JWT_REFRESH_TOKEN_EXPIRE_TIME=7 # days

0
deploy/certs/.gitkeep Normal file
View File

View File

@@ -1,6 +1,38 @@
#!/bin/sh #!/bin/sh
# RUN IN REPO ROOT DIR !! # RUN IN REPO ROOT DIR !!
export IMAGE_NAME="git.pbiernat.dev/egommerce/identity-svc" export IMAGE_PREFIX="git.ego.freeddns.org/egommerce/identity"
export BUILDER_IMAGE="egommerce-builder:identity"
export BUILD_TIME=$(date +"%Y%m%d%H%M%S")
export SERVER_IMAGE="$IMAGE_PREFIX-svc"
docker build --rm --cache-from "$IMAGE_NAME:latest" -t "$IMAGE_NAME:latest" . TARGET=${1:-latest}
[ ! -d "src/vendor" ] && sh -c "cd src; go mod vendor"
echo "Building target $IMAGE_PREFIX images..."
docker build --rm -t $BUILDER_IMAGE -f Dockerfile.builder .
if [ $TARGET = "latest" ]
then
# PROD
docker build \
--build-arg SVC_NAME=identity-service \
--build-arg SVC_VER="1.0" \
--build-arg BUILDER_IMAGE=$BUILDER_IMAGE \
--build-arg BUILD_TIME \
--rm --cache-from $SERVER_IMAGE:$TARGET \
-t $SERVER_IMAGE:$TARGET \
-f Dockerfile.target . > /dev/null 2>&1 && echo "Successfully tagged $SERVER_IMAGE:$TARGET"
else
# DEV
docker build \
--build-arg SVC_NAME=identity-service \
--build-arg SVC_VER="dev" \
--build-arg BUILDER_IMAGE=$BUILDER_IMAGE \
--build-arg BUILD_TIME \
--rm --no-cache -t $SERVER_IMAGE:$TARGET \
-f Dockerfile.target . > /dev/null 2>&1 && echo "Successfully tagged $SERVER_IMAGE:$TARGET"
fi
echo "Done."

View File

@@ -1,7 +1,14 @@
#!/bin/sh #!/bin/sh
# RUN IN REPO ROOT DIR !! # RUN IN REPO ROOT DIR !!
export IMAGE_NAME="git.pbiernat.dev/egommerce/identity-svc" export IMAGE_PREFIX="git.ego.freeddns.org/egommerce/identity"
export SERVER_IMAGE="$IMAGE_PREFIX-svc"
echo $DOCKER_PASSWORD | docker login git.pbiernat.dev -u $DOCKER_USERNAME --password-stdin TARGET=${1:-latest}
docker push "$IMAGE_NAME:latest"
echo $DOCKER_PASSWORD | docker login git.ego.freeddns.org -u $DOCKER_USERNAME --password-stdin
docker push "$SERVER_IMAGE:$TARGET"
# Restart container
# TODO

18
src/.gitignore vendored Normal file
View File

@@ -0,0 +1,18 @@
# ---> Go
# Binaries for programs and plugins
*.exe
*.exe~
*.dlli
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
.env
# Dependency directories (remove the comment below to include it)
vendor/

40
src/app/app.go Normal file
View File

@@ -0,0 +1,40 @@
package app
import (
"log"
"os"
"os/signal"
"syscall"
)
type App struct {
worker WorkerInterface
}
func NewApp(worker WorkerInterface) *App {
return &App{
worker: worker,
}
}
func (app *App) Start(while chan struct{}) error {
go func(while chan struct{}) {
sigint := make(chan os.Signal, 1)
signal.Notify(sigint, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
<-sigint
log.Println("Received signal:", sigint)
app.Shutdown()
close(while)
}(while)
return app.worker.Start()
}
func (app *App) Shutdown() {
app.worker.OnShutdown()
}
func (app *App) RegisterPlugin(plugin Plugin) {
app.worker.addPlugin(plugin.name, plugin.fn)
}

57
src/app/config.go Normal file
View File

@@ -0,0 +1,57 @@
package app
import (
"fmt"
"os"
"time"
cnf "git.ego.freeddns.org/egommerce/go-api-pkg/config"
)
const (
defName = "identity-svc"
defNetAddr = ":443"
defDomain = "identity-svc"
defCacheAddr = "api-cache:6379"
defCacheUsername = "default"
defCachePassword = "12345678"
defDbURL = "postgres://postgres:12345678@db-postgres:5432/egommerce"
defMongoDbURL = "mongodb://mongodb:12345678@db-mongo:27017"
)
type Config struct {
ID string
Name string
Domain string
NetAddr string
IdleTimeout time.Duration // miliseconds
ReadTimeout time.Duration // miliseconds
WriteTimeout time.Duration // miliseconds
DbURL string `json:"db_url"`
CacheAddr string `json:"cache_addr"`
CacheUsername string `json:"cache_username"`
CachePassword string `json:"cache_password"`
MongoDbUrl string `json:"mongodb_url"`
}
func NewConfig(name string) *Config {
c := new(Config)
c.ID, _ = os.Hostname()
c.Name = name
c.Domain = cnf.GetEnv("APP_DOMAIN", defDomain)
c.NetAddr = cnf.GetEnv("SERVER_ADDR", defNetAddr)
c.CacheAddr = cnf.GetEnv("API_CACHE_ADDR", defCacheAddr)
c.CacheUsername = cnf.GetEnv("API_CACHE_USERNAME", defCacheUsername)
c.CachePassword = cnf.GetEnv("API_CACHE_PASSWORD", defCachePassword)
c.DbURL = cnf.GetEnv("API_DATABASE_URL", defDbURL)
c.MongoDbUrl = cnf.GetEnv("API_MONGO_URL", defMongoDbURL)
return c
}
func (c *Config) getAppFullName() string {
return fmt.Sprintf("%s_%s", c.Name, c.ID)
}

23
src/app/interface.go Normal file
View File

@@ -0,0 +1,23 @@
package app
type (
Application interface {
Start()
RegisterPlugin(Plugin)
Shutdown()
}
WorkerInterface interface {
Start() error
OnShutdown()
addPlugin(name string, fn PluginFn)
}
Plugin struct {
name string
fn PluginFn
}
PluginFn func() any
)

64
src/app/plugins.go Normal file
View File

@@ -0,0 +1,64 @@
package app
import (
"context"
"log"
"os"
"time"
redis "github.com/go-redis/redis/v8"
"github.com/jackc/pgx/v5/pgxpool"
db "github.com/jackc/pgx/v5/pgxpool"
)
type PluginManager struct {
plugins map[string]any
}
func NewPluginManager() *PluginManager {
return &PluginManager{
plugins: make(map[string]any),
}
}
func (pm *PluginManager) addPlugin(name string, fn PluginFn) {
pm.plugins[name] = fn()
}
func (pm *PluginManager) getCache() *redis.Client {
return (pm.plugins["cache"]).(*redis.Client)
}
func (pm *PluginManager) getDatabase() *pgxpool.Pool {
return (pm.plugins["database"]).(*pgxpool.Pool)
}
func CachePlugin(cnf *Config) Plugin {
return Plugin{
name: "cache",
fn: func() any {
return redis.NewClient(&redis.Options{
Addr: cnf.CacheAddr,
Username: cnf.CacheUsername,
Password: cnf.CachePassword,
DB: 0, // TODO
DialTimeout: 100 * time.Millisecond, // TODO
})
},
}
}
func DatabasePlugin(cnf *Config) Plugin {
return Plugin{
name: "database",
fn: func() any {
dbConn, err := db.New(context.Background(), cnf.DbURL)
if err != nil {
log.Fatalf("Failed to connect to the Database: %s. Err: %v\n", cnf.DbURL, err)
os.Exit(1)
}
return dbConn
},
}
}

44
src/app/scheduler.go Normal file
View File

@@ -0,0 +1,44 @@
package app
import (
"log"
"time"
"github.com/onatm/clockwerk"
"git.ego.freeddns.org/egommerce/identity-service/infra/repository"
"git.ego.freeddns.org/egommerce/identity-service/internal/cli/scheduler"
"git.ego.freeddns.org/egommerce/identity-service/internal/service"
)
type Scheduler struct {
*PluginManager
}
func NewScheduler(c *Config) *Scheduler {
return &Scheduler{
PluginManager: NewPluginManager(),
}
}
func (c *Scheduler) Start() error { // STILL NEEDS REFACTORING ;)
userRepo := repository.NewUserRepository(c.getDatabase())
roleRepo := repository.NewRoleRepository(c.getDatabase())
urlRepo := repository.NewURLAccessRepository(c.getDatabase())
authSrv := service.NewAuthService(userRepo, c.getCache())
guardSrv := service.NewGuardService(authSrv, c.getCache(), userRepo, roleRepo, urlRepo)
job := scheduler.NewCachePermissionsJob(guardSrv)
sch := clockwerk.New()
sch.Every(30 * time.Minute).Do(job)
sch.Start()
return nil
}
func (c *Scheduler) OnShutdown() {
log.Println("Scheduler is going down...")
c.getDatabase().Close()
c.getCache().Close()
}

131
src/app/server.go Normal file
View File

@@ -0,0 +1,131 @@
package app
import (
"crypto/tls"
"log"
"net"
"time"
jwt "github.com/gofiber/contrib/jwt"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/google/uuid"
commonDTO "git.ego.freeddns.org/egommerce/api-entities/common/dto"
cnf "git.ego.freeddns.org/egommerce/go-api-pkg/config"
"git.ego.freeddns.org/egommerce/identity-service/internal/http"
)
var defaultCORS = cors.New(
cors.Config{
// DEV CONFIG
AllowOrigins: "*",
AllowMethods: "GET, POST, PATCH, PUT, DELETE, OPTIONS",
AllowHeaders: "Accept, Authorization, Content-Type, Vary, X-Request-Id",
// PROD CONFIG
// AllowOrigins: "http://egommerce.io:3001", // client(reactjs) app url
// AllowCredentials: true,
// AllowMethods: "GET, POST, PATCH, PUT, DELETE",
},
)
type (
Server struct {
*fiber.App
*PluginManager
ID string
addr string // e.g. "127.0.0.1:443"
}
)
func NewServer(c *Config) *Server {
return &Server{
ID: c.ID,
App: fiber.New(fiber.Config{
AppName: c.ID,
ServerHeader: c.getAppFullName(),
ReadTimeout: c.ReadTimeout * time.Millisecond,
WriteTimeout: c.WriteTimeout * time.Millisecond,
IdleTimeout: c.IdleTimeout * time.Millisecond,
}),
PluginManager: NewPluginManager(),
addr: c.NetAddr,
}
}
func (s *Server) Start() error {
s.setupMiddleware()
s.setupRouter()
crt, err := tls.LoadX509KeyPair("certs/identity-svc.crt", "certs/identity-svc.key")
if err != nil {
log.Fatal(err)
}
tlsCnf := &tls.Config{Certificates: []tls.Certificate{crt}}
ln, _ := net.Listen("tcp", s.addr)
ln = tls.NewListener(ln, tlsCnf)
return s.Listener(ln)
}
func (s *Server) OnShutdown() {
log.Printf("Server %s is going down...", s.ID)
s.getDatabase().Close()
s.getCache().Close()
s.Shutdown()
}
func (s *Server) setupRouter() {
s.Options("*", defaultCORS)
s.Use(defaultCORS)
s.Get("/health", http.HealthHandlerFn(s.getDatabase(), s.getCache()))
s.Group("/v1").
Post("/login", http.LoginHandlerFn(s.getDatabase(), s.getCache())).
Post("/refresh", http.RefreshHandlerFn(s.getDatabase(), s.getCache())). // add JWTProtected() and get token from Auth Bearer header not from the body?
Post("/register", http.RegisterHandlerFn(s.getDatabase(), s.getCache())).
Get("/access", JWTProtected(), http.AccessHandlerFn(s.getDatabase(), s.getCache()))
}
func (s *Server) setupMiddleware() {
s.Use(LoggingMiddleware())
s.Use(XRequestIDMiddleware())
}
func LoggingMiddleware() func(c *fiber.Ctx) error {
return func(c *fiber.Ctx) error {
log.Printf("Request: %s, remote: %s, via: %s",
c.Request().URI().String(),
c.Context().RemoteIP().String(),
string(c.Context().UserAgent()),
)
return c.Next()
}
}
func XRequestIDMiddleware() func(c *fiber.Ctx) error {
return func(c *fiber.Ctx) error {
c.Set("X-Request-ID", uuid.New().String())
return c.Next()
}
}
func JWTProtected() func(c *fiber.Ctx) error {
secret := []byte(cnf.GetEnv("JWT_ACCESS_TOKEN_SECRET_KEY", "FallbackAccessTokenSecret"))
return jwt.New(jwt.Config{
SigningKey: jwt.SigningKey{Key: secret},
ContextKey: "jwt",
ErrorHandler: func(c *fiber.Ctx, err error) error {
return c.Status(fiber.StatusUnauthorized).JSON(commonDTO.ErrorResponseDTO{Error: "unauthorized"})
},
})
}

View File

@@ -1,49 +0,0 @@
package main
import (
"context"
"net"
"os"
"os/signal"
"time"
"git.pbiernat.dev/egommerce/identity-service/internal/app"
"git.pbiernat.dev/egommerce/identity-service/internal/app/config"
"git.pbiernat.dev/egommerce/identity-service/internal/app/database"
"git.pbiernat.dev/egommerce/identity-service/internal/app/handler"
)
const (
defHTTPIP = "0.0.0.0"
defHTTPPort = "8080"
defDbURL = "postgres://postgres:12345678@postgres_svc:5432/egommerce"
)
func main() {
if config.ErrLoadingEnvs != nil {
app.Panicf("Error loading .env file")
}
httpAddr := net.JoinHostPort(config.GetEnv("SERVER_IP", defHTTPIP), defHTTPPort)
dbConnStr := config.GetEnv("DATABASE_URL", defDbURL)
dbc, err := database.Connect(dbConnStr)
if err != nil {
app.Panicf("Unable to connect to database: %v\n", err)
}
env := &handler.Env{Addr: httpAddr, DB: dbc}
srv := app.NewServer(env)
go srv.Start()
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
<-c
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
srv.Shutdown(ctx)
os.Exit(0)
}

74
src/cmd/migrate/main.go Normal file
View File

@@ -0,0 +1,74 @@
package main
import (
"flag"
"fmt"
"log"
"os"
"github.com/go-pg/migrations/v8"
"github.com/go-pg/pg/v10"
cnf "git.ego.freeddns.org/egommerce/go-api-pkg/config"
)
const (
defMigrationsTableName = "identity.migrations"
)
const usageText = `This program runs command on the db. Supported commands are:
- init - creates version info table in the database
- up - runs all available migrations.
- up [target] - runs available migrations up to the target one.
- down - reverts last migration.
- reset - reverts all migrations.
- version - prints current db version.
- set_version [version] - sets db version without running migrations.
Usage:
go run cmd/migrate/main.go <command> [args]
`
func main() {
flag.Usage = func() {
fmt.Print(usageText)
flag.PrintDefaults()
os.Exit(2)
}
flag.Parse()
if cnf.ErrLoadingEnvs != nil {
log.Panicln(cnf.ErrLoadingEnvs)
}
// c := cnf.NewConfig("migrator")
// dbURL := baseCnf.GetEnv("DATABASE_URL", defDbURL)
db := pg.Connect(&pg.Options{ // FIXME
Addr: "db-postgres:5432",
User: "postgres",
Password: "12345678",
Database: "egommerce",
})
defer db.Close()
mTbl := cnf.GetEnv("MIGRATIONS_TABLE_NAME", defMigrationsTableName)
mig := migrations.NewCollection()
mig.SetTableName(mTbl)
if err := mig.DiscoverSQLMigrations("./migrations"); err != nil {
log.Printf("migration dicovery error: %#v\n", err)
}
oldVersion, newVersion, err := mig.Run(db, flag.Args()...)
if err != nil {
log.Printf("migration runner error: %#v", err)
os.Exit(1)
}
if newVersion != oldVersion {
log.Printf("migrated from version %d to %d\n", oldVersion, newVersion)
} else {
log.Printf("version is %d\n", oldVersion)
}
os.Exit(0)
}

33
src/cmd/scheduler/main.go Normal file
View File

@@ -0,0 +1,33 @@
package main
import (
"log"
"os"
cnf "git.ego.freeddns.org/egommerce/go-api-pkg/config"
"git.ego.freeddns.org/egommerce/identity-service/app"
)
func main() {
if cnf.ErrLoadingEnvs != nil {
log.Panicln(cnf.ErrLoadingEnvs)
}
cnf := app.NewConfig("identity-scheduler")
w := app.NewScheduler(cnf)
a := app.NewApp(w)
a.RegisterPlugin(app.CachePlugin(cnf))
a.RegisterPlugin(app.DatabasePlugin(cnf))
while := make(chan struct{})
err := a.Start(while)
<-while
if err != nil {
log.Fatalf("Failed to scheduler. Reason: %v\n", err)
os.Exit(1)
}
os.Exit(0)
}

33
src/cmd/server/main.go Normal file
View File

@@ -0,0 +1,33 @@
package main
import (
"log"
"os"
cnf "git.ego.freeddns.org/egommerce/go-api-pkg/config"
"git.ego.freeddns.org/egommerce/identity-service/app"
)
func main() {
if cnf.ErrLoadingEnvs != nil {
log.Panicln(cnf.ErrLoadingEnvs.Error())
}
cnf := app.NewConfig("identity-svc")
w := app.NewServer(cnf)
a := app.NewApp(w)
a.RegisterPlugin(app.CachePlugin(cnf))
a.RegisterPlugin(app.DatabasePlugin(cnf))
while := make(chan struct{})
err := a.Start(while)
<-while
if err != nil {
log.Fatalf("Failed to start server. Reason: %v\n", err)
os.Exit(1)
}
os.Exit(0)
}

View File

@@ -1,23 +1,54 @@
module git.pbiernat.dev/egommerce/identity-service module git.ego.freeddns.org/egommerce/identity-service
go 1.18 go 1.24.0
toolchain go1.24.1
require ( require (
git.ego.freeddns.org/egommerce/api-entities v0.3.34
git.ego.freeddns.org/egommerce/go-api-pkg v0.5.3
github.com/georgysavva/scany/v2 v2.1.4
github.com/go-pg/migrations/v8 v8.1.0
github.com/go-pg/pg/v10 v10.15.0
github.com/go-redis/redis/v8 v8.11.5
github.com/gofiber/contrib/jwt v1.1.2
github.com/gofiber/fiber/v2 v2.52.9
github.com/golang-jwt/jwt v3.2.2+incompatible github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/gorilla/mux v1.8.0 github.com/google/uuid v1.6.0
github.com/jackc/pgx/v4 v4.17.2 github.com/jackc/pgx/v5 v5.7.6
github.com/joho/godotenv v1.4.0 github.com/onatm/clockwerk v1.1.0
golang.org/x/crypto v0.43.0
) )
require ( require (
github.com/jackc/chunkreader/v2 v2.0.1 // indirect github.com/MicahParks/keyfunc/v2 v2.1.0 // indirect
github.com/jackc/pgconn v1.13.0 // indirect github.com/andybalholm/brotli v1.2.0 // indirect
github.com/jackc/pgio v1.0.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-pg/zerochecker v0.2.0 // indirect
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgproto3/v2 v2.3.1 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jackc/pgtype v1.12.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jackc/puddle v1.3.0 // indirect github.com/joho/godotenv v1.5.1 // indirect
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect github.com/klauspost/compress v1.18.1 // indirect
golang.org/x/text v0.3.7 // indirect github.com/kr/pretty v0.3.1 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.67.0 // indirect
github.com/vmihailenco/bufpool v0.1.11 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser v0.1.2 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/sys v0.37.0 // indirect
golang.org/x/text v0.30.0 // indirect
mellium.im/sasl v0.3.2 // indirect
) )

View File

@@ -1,190 +1,257 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
git.ego.freeddns.org/egommerce/api-entities v0.3.34 h1:WftM9cvV3JmbS1DlHCIiV3tsYIpALj9IXo90mkgZNWQ=
git.ego.freeddns.org/egommerce/api-entities v0.3.34/go.mod h1:IqynARw+06GOm4eZGZuepmbi7bUxWBnOB4jd5cI7jf8=
git.ego.freeddns.org/egommerce/go-api-pkg v0.5.3 h1:so+OWWVJEg6JZ5XOSmCpfW7Pd7IL6ETH0QsC6zCwndo=
git.ego.freeddns.org/egommerce/go-api-pkg v0.5.3/go.mod h1:T3ia8iprzlTRznPVXYCgEzQb/1UvIcdn9FHabE58vy0=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/MicahParks/keyfunc/v2 v2.1.0 h1:6ZXKb9Rp6qp1bDbJefnG7cTH8yMN1IC/4nf+GVjO99k=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/MicahParks/keyfunc/v2 v2.1.0/go.mod h1:rW42fi+xgLJ2FRRXAfNx9ZA8WpD4OeE/yHVMteCkw9k=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=
github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/cockroachdb/cockroach-go/v2 v2.2.0 h1:/5znzg5n373N/3ESjHF5SMLxiW4RKB05Ql//KWfeTFs=
github.com/cockroachdb/cockroach-go/v2 v2.2.0/go.mod h1:u3MiKYGupPPjkn3ozknpMUpxPaNLTFWAya419/zv6eI=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/georgysavva/scany/v2 v2.1.4 h1:nrzHEJ4oQVRoiKmocRqA1IyGOmM/GQOEsg9UjMR5Ip4=
github.com/georgysavva/scany/v2 v2.1.4/go.mod h1:fqp9yHZzM/PFVa3/rYEC57VmDx+KDch0LoqrJzkvtos=
github.com/go-pg/migrations/v8 v8.1.0 h1:bc1wQwFoWRKvLdluXCRFRkeaw9xDU4qJ63uCAagh66w=
github.com/go-pg/migrations/v8 v8.1.0/go.mod h1:o+CN1u572XHphEHZyK6tqyg2GDkRvL2bIoLNyGIewus=
github.com/go-pg/pg/v10 v10.4.0/go.mod h1:BfgPoQnD2wXNd986RYEHzikqv9iE875PrFaZ9vXvtNM=
github.com/go-pg/pg/v10 v10.15.0 h1:6DQwbaxJz/e4wvgzbxBkBLiL/Uuk87MGgHhkURtzx24=
github.com/go-pg/pg/v10 v10.15.0/go.mod h1:FIn/x04hahOf9ywQ1p68rXqaDVbTRLYlu4MQR0lhoB8=
github.com/go-pg/zerochecker v0.2.0 h1:pp7f72c3DobMWOb2ErtZsnrPaSvHd2W4o9//8HtF4mU=
github.com/go-pg/zerochecker v0.2.0/go.mod h1:NJZ4wKL0NmTtz0GKCoJ8kym6Xn/EQzXRl2OnAe7MmDo=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/gofiber/contrib/jwt v1.1.2 h1:GmWnOqT4A15EkA8IPXwSpvNUXZR4u5SMj+geBmyLAjs=
github.com/gofiber/contrib/jwt v1.1.2/go.mod h1:CpIwrkUQ3Q6IP8y9n3f0wP9bOnSKx39EDp2fBVgMFVk=
github.com/gofiber/fiber/v2 v2.52.9 h1:YjKl5DOiyP3j0mO61u3NTmK7or8GzzWzCFzkboyP5cw=
github.com/gofiber/fiber/v2 v2.52.9/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w=
github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
github.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E=
github.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0=
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
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/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 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.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E=
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onatm/clockwerk v1.1.0 h1:Ig2tTdZGtYWM1n5sDcf/LZ9zKe5l569G8jIPejHS0DM=
github.com/onatm/clockwerk v1.1.0/go.mod h1:9R0U2KkwobOCqRvPtIAt0qHgaCWbo3kjvySmmMth2Ao=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= github.com/valyala/fasthttp v1.67.0 h1:tqKlJMUP6iuNG8hGjK/s9J4kadH7HLV4ijEcPGsezac=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= github.com/valyala/fasthttp v1.67.0/go.mod h1:qYSIpqt/0XNmShgo/8Aq8E3UYWVVwNS2QYmzd8WIEPM=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= github.com/vmihailenco/bufpool v0.1.11 h1:gOq2WmBrq0i2yW5QJ16ykccQ4wH9UyEsgLm6czKAd94=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= github.com/vmihailenco/bufpool v0.1.11/go.mod h1:AFf/MOy3l2CFTKbxwt0mp2MwnqjNEs5H/UxrkA5jxTQ=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= github.com/vmihailenco/msgpack/v4 v4.3.11/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= github.com/vmihailenco/msgpack/v5 v5.0.0-beta.1/go.mod h1:xlngVLeyQ/Qi05oQxhQ+oTuqa03RjMwMfk/7/TCs+QI=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc=
github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
go.opentelemetry.io/otel v0.13.0/go.mod h1:dlSNewoRYikTkotEnxdmuBHgzT+k/idJSfDv/FxEnOY=
golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c= golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=
golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20201017003518-b09fb700fbb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= 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/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
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/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 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=
mellium.im/sasl v0.2.1/go.mod h1:ROaEDLQNuf9vjKqE1SrAfnsobm2YKXT1gnN1uDp1PjQ=
mellium.im/sasl v0.3.2 h1:PT6Xp7ccn9XaXAnJ03FcEjmAn7kK1x7aoXV6F+Vmrl0=
mellium.im/sasl v0.3.2/go.mod h1:NKXDi1zkr+BlMHLQjY3ofYuU4KSPFxknb8mfEu6SveY=

View File

@@ -0,0 +1,12 @@
package repository
import entity "git.ego.freeddns.org/egommerce/api-entities/identity/entity"
type UserRepositoryInterface interface {
FindAll() ([]entity.User, error)
FindByID(id string) (*entity.User, error)
FindByUsername(login string) (*entity.User, error)
Create(user *entity.User) (string, error) // FIXME types
Update(user *entity.User) (*entity.User, error) // FIXME types
Delete(id int64) (bool, error)
}

View File

@@ -0,0 +1,64 @@
package repository
import (
"context"
"errors"
entity "git.ego.freeddns.org/egommerce/api-entities/identity/entity"
"github.com/jackc/pgx/v5/pgxpool"
)
type RoleRepository struct {
db *pgxpool.Pool
}
func NewRoleRepository(db *pgxpool.Pool) *RoleRepository {
return &RoleRepository{db}
}
func (r *RoleRepository) FindByID(id string) (*entity.Role, error) {
var role entity.Role
sql := `SELECT id, name, display_name FROM identity.roles WHERE id=$1 LIMIT 1`
err := r.db.QueryRow(context.Background(), sql, id).
Scan(&role.ID, &role.Name, &role.DisplayName)
if err != nil {
return nil, errors.New("failed to fetch role from DB: " + err.Error())
}
return &role, nil
}
func (r *RoleRepository) Create(role *entity.Role) (string, error) {
var id string
// sql := `INSERT INTO identity.users(email, username, password) VALUES($1, $2, $3) LIMIT 1 RETURNING id`
// err := r.db.QueryRow(context.Background(), sql, user.Email, user.Username, user.Password).Scan(&id)
// if err != nil {
// if err = db.IsDuplicatedRow(err); err != nil {
// return "", errors.New("username/email is already taken")
// }
// return "", errors.New("db error: " + err.Error())
// }
return id, nil
}
func (r *RoleRepository) Update(role *entity.Role) (*entity.Role, error) {
return &entity.Role{}, nil
}
func (r *RoleRepository) Delete(id int64) (bool, error) {
return true, nil
}
func (r *RoleRepository) GetUserRole(user *entity.User) *entity.Role {
role := new(entity.Role)
sql := `SELECT r.id, r.name, r.display_name FROM identity.roles r JOIN identity.users_roles ur ON r.id = ur.role_id WHERE ur.user_id=$1 LIMIT 1`
r.db.QueryRow(context.Background(), sql, user.ID).Scan(&role.ID, &role.Name, &role.DisplayName)
return role
}

View File

@@ -0,0 +1,102 @@
package repository
import (
"context"
"errors"
"fmt"
entity "git.ego.freeddns.org/egommerce/api-entities/identity/entity"
database "git.ego.freeddns.org/egommerce/go-api-pkg/client/postgresql"
"github.com/jackc/pgx/v5/pgxpool"
)
type URLAccessRepository struct {
db *pgxpool.Pool
}
func NewURLAccessRepository(db *pgxpool.Pool) *URLAccessRepository {
return &URLAccessRepository{db}
}
func (r *URLAccessRepository) FindByID(id string) (*entity.URLAccess, error) {
var urlAccess entity.URLAccess
sql := `SELECT id, roles, url, service FROM identity.url_access WHERE id=$1 LIMIT 1`
err := r.db.QueryRow(context.Background(), sql, id).
Scan(&urlAccess.ID, &urlAccess.Roles, &urlAccess.URL, &urlAccess.Service)
if err != nil {
return nil, errors.New("failed to fetch url_access from DB: " + err.Error())
}
return &urlAccess, nil
}
func (r *URLAccessRepository) FindAll() ([]entity.URLAccess, error) {
sql := "SELECT id, roles, url, service FROM identity.url_access ORDER BY service"
rows, err := r.db.Query(context.Background(), sql)
if err != nil {
return nil, err
}
defer rows.Close()
var results []entity.URLAccess
for rows.Next() {
var url entity.URLAccess
if err := rows.Scan(&url.ID, &url.Roles, &url.URL, &url.Service); err != nil {
return nil, err
}
results = append(results, url)
}
return results, nil
}
func (r *URLAccessRepository) Create(role *entity.URLAccess) (string, error) {
var id string
return id, nil
}
func (r *URLAccessRepository) Update(role *entity.URLAccess) (entity.URLAccess, error) {
return entity.URLAccess{}, nil
}
func (r *URLAccessRepository) Delete(id int64) (bool, error) {
return true, nil
}
func (r *URLAccessRepository) FindByURLAndService(url, service string) (*entity.URLAccess, error) {
var urlAccess entity.URLAccess
sql := `SELECT id, roles, url FROM identity.url_access WHERE url=$1 AND service=$2 LIMIT 1`
err := r.db.QueryRow(context.Background(), sql, url, service).
Scan(&urlAccess.ID, &urlAccess.Roles, &urlAccess.URL)
if err != nil {
return nil, errors.New("failed to fetch url_access from DB: " + err.Error())
}
return &urlAccess, nil
}
func (r *URLAccessRepository) FindByURLAndServiceForRole(url, service, role string) (*entity.URLAccess, error) {
var entity entity.URLAccess
sql := fmt.Sprintf("SELECT id, roles, url, service FROM identity.url_access WHERE url=$1 AND service=$2 AND roles::jsonb @> '[\"%s\"]'::jsonb LIMIT 1", role)
err := r.db.QueryRow(context.Background(), sql, url, service).
Scan(&entity.ID, &entity.Roles, &entity.URL, &entity.Service)
if err != nil {
if err = database.NoRowsInQuerySet(err); err != nil {
return nil, errors.New("no url found for: " + url + " and role: " + role)
}
return nil, errors.New("failed to fetch url_access from DB: " + err.Error())
}
return &entity, nil
}
func (r *URLAccessRepository) FindForUser(user *entity.User) {
}

View File

@@ -0,0 +1,85 @@
package repository
import (
"context"
"errors"
entity "git.ego.freeddns.org/egommerce/api-entities/identity/entity"
db "git.ego.freeddns.org/egommerce/go-api-pkg/client/postgresql"
"github.com/georgysavva/scany/v2/pgxscan"
"github.com/jackc/pgx/v5/pgxpool"
)
type UserRepository struct {
db *pgxpool.Pool
}
func NewUserRepository(db *pgxpool.Pool) *UserRepository {
return &UserRepository{db}
}
func (r *UserRepository) FindAll() ([]entity.User, error) {
users := []entity.User{}
sql := `SELECT id, username, password, email, created_at, updated_at FROM identity.users`
rows, err := r.db.Query(context.Background(), sql)
if err != nil {
return nil, err
}
if err := pgxscan.ScanAll(&users, rows); err != nil {
return nil, err
}
return users, nil
}
func (r *UserRepository) FindByID(id string) (*entity.User, error) {
user := new(entity.User)
sql := `SELECT id, username, password, email, created_at, updated_at FROM identity.users WHERE id=$1 LIMIT 1`
err := pgxscan.Get(context.Background(), r.db, user, sql, id)
if err != nil {
return nil, errors.New("failed to fetch user from DB: " + err.Error())
}
return user, nil
}
func (r *UserRepository) FindByUsername(login string) (*entity.User, error) {
user := new(entity.User)
sql := `SELECT id, username, password, email, created_at, updated_at FROM identity.users WHERE username=$1 LIMIT 1`
err := pgxscan.Get(context.Background(), r.db, user, sql, login)
if err != nil {
return nil, errors.New("failed to fetch user from DB: " + err.Error())
}
return user, nil
}
func (r *UserRepository) Create(user *entity.User) (string, error) {
var id string
sql := `INSERT INTO identity.users(email, username, password) VALUES($1, $2, $3) LIMIT 1 RETURNING id`
err := r.db.QueryRow(context.Background(), sql, user.Email, user.Username, user.Password).Scan(&id)
if err != nil {
if err = db.IsDuplicatedRow(err); err != nil {
return "", errors.New("username/email is already taken")
}
return "", errors.New("db error: " + err.Error())
}
return id, nil
}
func (r *UserRepository) Update(user *entity.User) (*entity.User, error) {
return &entity.User{}, nil
}
func (r *UserRepository) Delete(id int64) (bool, error) {
return true, nil
}

View File

@@ -1,22 +0,0 @@
package config
import (
"os"
"github.com/joho/godotenv"
)
var ErrLoadingEnvs error
func init() {
ErrLoadingEnvs = godotenv.Load()
}
func GetEnv(name, defVal string) string {
env := os.Getenv(name)
if env == "" {
return defVal
}
return env
}

View File

@@ -1,16 +0,0 @@
package database
import (
"context"
"github.com/jackc/pgx/v4/pgxpool"
)
func Connect(connStr string) (*pgxpool.Pool, error) {
conn, err := pgxpool.Connect(context.Background(), connStr)
if err != nil {
return nil, err
}
return conn, nil
}

View File

@@ -1,9 +0,0 @@
package definition
type AuthLoginRequest struct {
Username string `json:"username"`
Password string `json:"password"`
}
type AuthLoginResponse struct {
}

View File

@@ -1,9 +0,0 @@
package definition
type ErrorResponse struct {
Error string `json:"error"`
}
func Error(err string) *ErrorResponse {
return &ErrorResponse{err}
}

View File

@@ -1,18 +0,0 @@
package entity
import "time"
type User struct {
ID int `json:"id"`
Username string `json:"username"`
Password string `json:"password"`
CreateDate time.Time `json:"create_date"`
ModifyDate time.Time `json:"modify_date"` // FIXME: zero-value issue
}
var TestUser = &User{
ID: 1,
Username: "test",
Password: "test",
CreateDate: time.Now(),
}

View File

@@ -1,35 +0,0 @@
package handler
import (
"net/http"
def "git.pbiernat.dev/egommerce/identity-service/internal/app/definition"
"git.pbiernat.dev/egommerce/identity-service/internal/app/service"
)
var AuthLoginHandler *Handler
func init() {
AuthLoginHandler = &Handler{
Handle: AuthLoginHandlerFunc,
Request: &def.AuthLoginRequest{},
Response: &def.AuthLoginResponse{},
}
}
func AuthLoginHandlerFunc(h *Handler, w http.ResponseWriter) (interface{}, int, error) {
var req = h.Request.(*def.AuthLoginRequest)
// u := entity.TestUser
token, err := service.AuthService.Login(req)
if err != nil {
return nil, http.StatusForbidden, err
}
service.AuthService.SetCookie(w, service.AuthService.TokenCookieName, token)
// service.AuthService.SetCookie(w, service.AuthService.RefreshTokenCookieName, refreshTtoken)
// log.Println("user:", u, "req:", token, "err:", err)
return nil, http.StatusOK, nil
}

View File

@@ -1,18 +0,0 @@
package handler
import (
"errors"
"net/http"
)
type NotFoundHandler struct{}
func (NotFoundHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
encodeResponse(w, &response{http.StatusNotFound, ""}, errors.New("Path "+r.RequestURI+" not found"))
}
type MethodNotAllowedHandler struct{}
func (MethodNotAllowedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
encodeResponse(w, &response{http.StatusMethodNotAllowed, ""}, errors.New("Method Not Allowed: "+r.Method))
}

View File

@@ -1,83 +0,0 @@
package handler
import (
"bytes"
"encoding/json"
"io"
"log"
"net/http"
def "git.pbiernat.dev/egommerce/identity-service/internal/app/definition"
"github.com/gorilla/mux"
"github.com/jackc/pgx/v4/pgxpool"
)
type Env struct {
Addr string
DB *pgxpool.Pool
}
type Handler struct {
*Env
Handle HandlerFunc
Request interface{}
Response interface{}
Params Set
}
type HandlerFunc func(h *Handler, w http.ResponseWriter) (interface{}, int, error)
type Set map[string]string
type response struct {
Status int
Data interface{}
}
func Init(e *Env, h *Handler) *Handler {
// return &Handler{e, h.Handle, h.Request, h.Response, Set{}}
h.Env = e
return h
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err := decodeRequestData(r, h.Request); err != nil {
log.Println("Decode request data error:", err.Error())
w.WriteHeader(http.StatusInternalServerError)
}
h.Params = mux.Vars(r)
res, code, err := h.Handle(h, w)
encodeResponse(w, &response{code, res}, err)
}
func decodeRequestData(r *http.Request, v interface{}) error {
buf, _ := io.ReadAll(r.Body)
rdr := io.NopCloser(bytes.NewReader(buf))
r.Body = io.NopCloser(bytes.NewReader(buf))
json.NewDecoder(rdr).Decode(&v)
return nil
}
func encodeResponse(w http.ResponseWriter, res *response, e error) {
if e != nil {
encodeError(w, res.Status, e)
return
}
w.WriteHeader(res.Status)
if res.Data != nil {
json.NewEncoder(w).Encode(res.Data)
}
}
func encodeError(w http.ResponseWriter, status int, e error) {
w.WriteHeader(status)
json.NewEncoder(w).Encode(def.Error(e.Error()))
}

View File

@@ -1,42 +0,0 @@
package handler
import (
"net/http"
)
var HealthCheckHandler *Handler
func init() {
HealthCheckHandler = &Handler{
Handle: HealthCheckHandlerFunc,
Request: &HealthCheckRequest{},
Response: &HealthCheckResponse{},
}
}
type HealthCheckRequest struct {
}
type HealthCheckResponse struct {
Status string `json:"status"`
Data *HealthCheckResponseBody `json:"data"`
}
type HealthCheckResponseBody struct {
Message string `json:"message,omitempty"`
Status string `json:"status,omitempty"`
}
func HealthCheckHandlerFunc(_ *Handler, w http.ResponseWriter) (interface{}, int, error) {
return &HealthCheckResponseBody{
Message: "This is welcome health message. Everything seems to be alright ;)",
Status: "OK",
}, http.StatusOK, nil
// return &HealthCheckResponse{
// Status: http.StatusText(http.StatusOK),
// Data: &HealthCheckResponseBody{
// Message: "This is welcome health message. Everything seems to be alright ;)",
// },
// }, http.StatusOK, nil
}

View File

@@ -1,16 +0,0 @@
package app
import "log"
func Panic(v ...any) {
log.Panicln(Name+":", v)
}
func Panicf(format string, v ...any) {
log.Panicf(Name+": "+format, v...)
}
func Panicln(v ...any) {
v = append([]any{Name + ":"}, v...)
log.Panicln(v...)
}

View File

@@ -1,23 +0,0 @@
package app
import (
"net/http"
"git.pbiernat.dev/egommerce/identity-service/internal/app/handler"
"github.com/gorilla/mux"
)
func SetupRouter(env *handler.Env) *mux.Router {
r := mux.NewRouter()
r.NotFoundHandler = &handler.NotFoundHandler{}
r.MethodNotAllowedHandler = &handler.MethodNotAllowedHandler{}
r.Use(PrepareHeadersMiddleware)
r.Use(ValidateJsonBodyMiddleware) // probably not needed
r.Use(LoggingMiddleware)
r.Handle("/health", handler.Init(env, handler.HealthCheckHandler)).Methods(http.MethodGet)
r.Handle("/login", handler.Init(env, handler.AuthLoginHandler)).Methods(http.MethodPost)
return r
}

View File

@@ -1,85 +0,0 @@
package app
import (
"bytes"
"encoding/json"
"io"
"log"
"net"
"net/http"
"os"
"strconv"
"time"
def "git.pbiernat.dev/egommerce/identity-service/internal/app/definition"
"git.pbiernat.dev/egommerce/identity-service/internal/app/handler"
)
const Name = "REST API Service"
type Server struct {
*http.Server
}
func NewServer(env *handler.Env) *Server {
return &Server{
&http.Server{
Handler: SetupRouter(env),
Addr: env.Addr,
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
},
}
}
func (s *Server) Start() {
if os.Getenv("LISTEN_PID") == strconv.Itoa(os.Getpid()) {
// systemd run
f := os.NewFile(3, "from systemd")
l, err := net.FileListener(f)
if err != nil {
log.Fatalln(err)
}
log.Println("Server listening on " + l.Addr().String())
s.Serve(l)
} else {
log.Println("Server listening on " + s.Addr)
log.Fatalln(s.ListenAndServe())
}
}
func PrepareHeadersMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.Header().Set("Connection", "keep-alive")
w.Header().Set("Keep-Alive", "timeout=5")
next.ServeHTTP(w, r)
})
}
func ValidateJsonBodyMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
buf, _ := io.ReadAll(r.Body)
r.Body = io.NopCloser(bytes.NewReader(buf)) // rollack *Request to original state
if len(buf) > 0 && !json.Valid(buf) {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(def.Error("Unable to parse JSON: " + string(buf)))
return
}
next.ServeHTTP(w, r)
})
}
func LoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println("Request: " + r.RequestURI + " remote: " + r.RemoteAddr + " via: " + r.UserAgent())
next.ServeHTTP(w, r)
})
}

View File

@@ -1,113 +0,0 @@
package service
import (
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"strconv"
"time"
"git.pbiernat.dev/egommerce/identity-service/internal/app/config"
def "git.pbiernat.dev/egommerce/identity-service/internal/app/definition"
"github.com/golang-jwt/jwt"
)
var (
AuthService *Auth
ErrUserNotFound = errors.New("user not found")
ErrTokenError = errors.New("failed to generate JWT token")
)
func init() {
expire, _ := strconv.Atoi(config.GetEnv("AUTH_TOKEN_EXPIRE_TIME", "5"))
secret := []byte(config.GetEnv("AUTH_SECRET_HMAC", "B413IlIv9nKQfsMCXTE0Cteo4yHgUEfqaLfjg73sNlh"))
AuthService = &Auth{expire, "jwt_token", "jwt_token_refresh", secret}
}
type Auth struct {
ExpireTime int // token expire time in minutes
TokenCookieName string
RefreshTokenCookieName string
secret []byte // signing key
}
func (a *Auth) Login(r *def.AuthLoginRequest) (string, error) {
if r.Username == "admin" && r.Password == "secret" {
token, err := a.createToken()
if err != nil {
return "", ErrTokenError
}
return token, nil
}
return "", ErrUserNotFound
}
// SetCookie appends cookie header to response
func (a *Auth) SetCookie(w http.ResponseWriter, name, token string) {
c := &http.Cookie{
Name: name,
Value: token,
MaxAge: a.ExpireTime * 60,
Path: "/",
}
http.SetCookie(w, c)
}
func (a Auth) createToken() (string, error) {
// log.Println("now:", time.Now().Unix())
// log.Println("expire at:", time.Now().Add(time.Duration(a.ExpireTime)*time.Minute).Unix())
claims := &jwt.StandardClaims{
ExpiresAt: time.Now().Add(time.Duration(a.ExpireTime) * time.Minute).Unix(),
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(a.secret)
}
func (a *Auth) validateToken(tokenStr string) error {
token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
// Don't forget to validate the alg is what you expect:
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
// hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key")
return a.secret, nil
})
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
log.Println(claims)
} else {
return err
}
return nil
}
func (a Auth) ValidateUserTokenMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
cToken, err := r.Cookie(a.TokenCookieName)
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode(def.Error("Missing JWT Token cookie"))
return
}
if err := a.validateToken(cToken.Value); err != nil {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode(def.Error(err.Error()))
return
}
next.ServeHTTP(w, r)
})
}

View File

@@ -0,0 +1,22 @@
package scheduler
import (
"fmt"
"time"
"git.ego.freeddns.org/egommerce/identity-service/internal/service"
)
type CachePermissionsJob struct {
guard *service.GuardService
}
func NewCachePermissionsJob(guard *service.GuardService) *CachePermissionsJob {
return &CachePermissionsJob{guard: guard}
}
func (j CachePermissionsJob) Run() {
j.guard.CacheAllPermissions()
fmt.Println(time.Now().String() + " permission successfully cached")
}

View File

@@ -0,0 +1,34 @@
package http
import (
commonDTO "git.ego.freeddns.org/egommerce/api-entities/common/dto"
identityDTO "git.ego.freeddns.org/egommerce/api-entities/identity/dto"
"git.ego.freeddns.org/egommerce/identity-service/infra/repository"
"git.ego.freeddns.org/egommerce/identity-service/internal/service"
"git.ego.freeddns.org/egommerce/identity-service/internal/ui"
"github.com/go-redis/redis/v8"
"github.com/gofiber/fiber/v2"
"github.com/jackc/pgx/v5/pgxpool"
)
func AccessHandlerFn(db *pgxpool.Pool, cache *redis.Client) fiber.Handler {
return func(c *fiber.Ctx) error {
userRepo := repository.NewUserRepository(db)
roleRepo := repository.NewRoleRepository(db)
urlRepo := repository.NewURLAccessRepository(db)
authSrv := service.NewAuthService(userRepo, cache)
guardSrv := service.NewGuardService(authSrv, cache, userRepo, roleRepo, urlRepo)
url, srvName := c.Query("q"), c.Query("srv")
header := new(identityDTO.AuthorizationHeaderDTO)
c.ReqHeaderParser(header)
if err := ui.NewAccessActionUI(guardSrv).Execute(header, url, srvName); err != nil {
return c.Status(fiber.StatusNotFound).JSON(&commonDTO.ErrorResponseDTO{Error: err.Error()})
}
return c.SendStatus(fiber.StatusNoContent)
}
}

View File

@@ -0,0 +1,33 @@
package http
import (
"context"
"net/http"
"github.com/go-redis/redis/v8"
"github.com/gofiber/fiber/v2"
"github.com/jackc/pgx/v5/pgxpool"
)
type HealthResponse struct {
Status string `json:"status,omitempty"`
}
func HealthHandlerFn(db *pgxpool.Pool, cache *redis.Client) fiber.Handler {
return func(c *fiber.Ctx) error {
// Only 404 indicate service as not-healthy
err := db.Ping(context.Background())
if err != nil {
return c.SendStatus(http.StatusNotFound)
}
err = cache.Ping(context.Background()).Err()
if err != nil {
return c.SendStatus(http.StatusNotFound)
}
return c.JSON(&HealthResponse{
Status: "OK",
})
}
}

View File

@@ -0,0 +1,35 @@
package http
import (
"github.com/go-redis/redis/v8"
"github.com/gofiber/fiber/v2"
"github.com/jackc/pgx/v5/pgxpool"
commonDTO "git.ego.freeddns.org/egommerce/api-entities/common/dto"
identityDTO "git.ego.freeddns.org/egommerce/api-entities/identity/dto"
"git.ego.freeddns.org/egommerce/identity-service/infra/repository"
"git.ego.freeddns.org/egommerce/identity-service/internal/service"
"git.ego.freeddns.org/egommerce/identity-service/internal/ui"
)
func LoginHandlerFn(db *pgxpool.Pool, cache *redis.Client) fiber.Handler {
return func(c *fiber.Ctx) error {
req := new(identityDTO.AuthLoginRequestDTO)
if err := c.BodyParser(req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(&commonDTO.ErrorResponseDTO{Error: "error parsing input"})
// return srv.Error(c, fiber.StatusBadRequest, "error parsing input")
}
userRepo := repository.NewUserRepository(db)
authSrv := service.NewAuthService(userRepo, cache)
token, err := ui.NewLoginActionUI(authSrv).Execute(req)
if err != nil { // TODO: handle other response status codes -- add struct to decorate error with code and message
return c.Status(fiber.StatusBadRequest).JSON(commonDTO.ErrorResponseDTO{Error: err.Error()})
// return srv.Error(c, fiber.StatusBadRequest, err.Error())
}
return c.JSON(&identityDTO.AuthLoginResponseDTO{Token: token})
}
}

View File

@@ -0,0 +1,35 @@
package http
import (
commonDTO "git.ego.freeddns.org/egommerce/api-entities/common/dto"
identityDTO "git.ego.freeddns.org/egommerce/api-entities/identity/dto"
"git.ego.freeddns.org/egommerce/identity-service/infra/repository"
"git.ego.freeddns.org/egommerce/identity-service/internal/service"
"git.ego.freeddns.org/egommerce/identity-service/internal/ui"
"github.com/go-redis/redis/v8"
"github.com/gofiber/fiber/v2"
"github.com/jackc/pgx/v5/pgxpool"
)
func RefreshHandlerFn(db *pgxpool.Pool, cache *redis.Client) fiber.Handler {
return func(c *fiber.Ctx) error {
header := new(identityDTO.AuthorizationHeaderDTO)
if err := c.ReqHeaderParser(header); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(commonDTO.ErrorResponseDTO{Error: "Error parsing headers"})
// return srv.Error(c, fiber.StatusBadRequest, "Error parsing headers")
}
repo := repository.NewUserRepository(db)
authSrv := service.NewAuthService(repo, cache)
token, err := ui.NewRefreshTokenActionUI(authSrv).Execute(header)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(&commonDTO.ErrorResponseDTO{Error: err.Error()})
// return srv.Error(c, fiber.StatusBadRequest, err.Error())
}
return c.JSON(&identityDTO.AuthRefreshTokenResponseDTO{Token: token})
}
}

View File

@@ -0,0 +1,33 @@
package http
import (
commonDTO "git.ego.freeddns.org/egommerce/api-entities/common/dto"
identityDTO "git.ego.freeddns.org/egommerce/api-entities/identity/dto"
"git.ego.freeddns.org/egommerce/identity-service/infra/repository"
"git.ego.freeddns.org/egommerce/identity-service/internal/ui"
"github.com/go-redis/redis/v8"
"github.com/gofiber/fiber/v2"
"github.com/jackc/pgx/v5/pgxpool"
)
func RegisterHandlerFn(db *pgxpool.Pool, cache *redis.Client) fiber.Handler {
return func(c *fiber.Ctx) error {
data := new(identityDTO.AuthRegisterRequestDTO)
if err := c.BodyParser(data); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(commonDTO.ErrorResponseDTO{Error: "Error parsing input"})
// return srv.Error(c, fiber.StatusBadRequest, "Error parsing input")
}
repo := repository.NewUserRepository(db)
id, err := ui.NewRegisterActionUI(repo, cache).Execute(data)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(commonDTO.ErrorResponseDTO{Error: err.Error()})
// return srv.Error(c, fiber.StatusBadRequest, err.Error())
}
return c.JSON(&identityDTO.AuthRegisterResponseDTO{ID: id})
}
}

View File

@@ -0,0 +1,157 @@
package service
import (
"context"
"errors"
"fmt"
"strings"
dto "git.ego.freeddns.org/egommerce/api-entities/identity/dto"
entity "git.ego.freeddns.org/egommerce/api-entities/identity/entity"
"git.ego.freeddns.org/egommerce/identity-service/infra/repository"
"github.com/go-redis/redis/v8"
)
var (
passSrv *PaswordService
ErrLoginIncorrect = errors.New("login incorrect")
ErrUnableToCacheToken = errors.New("unable to save tokens in cache")
ErrInvalidAccessToken = errors.New("invalid access token")
ErrParsingAccessToken = errors.New("error while parsing access token")
ErrUnableToCacheUserID = errors.New("unable to save User ID in cache")
)
func init() {
passSrv = NewPasswordService()
}
type AuthService struct {
userRepo repository.UserRepositoryInterface
cache *redis.Client
}
func NewAuthService(userRepo repository.UserRepositoryInterface, cache *redis.Client) *AuthService {
return &AuthService{
userRepo: userRepo,
cache: cache,
}
}
func (a *AuthService) Login(login, passwd string) (string, error) {
user, err := a.userRepo.FindByUsername(login)
if err != nil {
// TODO place code below in better place...
// if err = database.NoRowsInQuerySet(err); err != nil {
// return "", errors.New("no user found")
// }
return "", ErrLoginIncorrect
}
if err = passSrv.Verify(passwd, user.Password); err != nil {
return "", ErrLoginIncorrect
}
accessToken, _ := jwtSrv.CreateAccessToken(user.ID)
refreshToken, _ := jwtSrv.CreateRefreshToken(user.ID)
if err = a.saveTokensToCache(user.ID, accessToken, refreshToken); err != nil {
return "", ErrUnableToCacheToken
}
// REFACTOR: save uid in cache under "user:$ACCES_TOKEN" key
res := a.cache.Set(context.Background(), "user:"+accessToken, user.ID, accessTokenExpireTime)
if err := res.Err(); err != nil {
fmt.Println("failed to save user:$ACCESS_TOKEN in cache: ", err.Error())
return "", ErrUnableToCacheUserID
}
return accessToken, nil
}
func (a *AuthService) RefreshToken(accessToken string) (string, error) {
// POSSIBLE BIG SECURITY ISSUE- WHEN REFRESH WITH ABANDONED (or EXPIRED)
// ACCESS TOKEN WE GET NEW ACCESS TOKEN
token, claims, err := jwtSrv.ValidateAccessToken(accessToken)
if err != nil || !token.Valid {
return "", ErrInvalidAccessToken
}
userID := claims["sub"]
newAccessToken, _ := jwtSrv.CreateAccessToken(userID.(string))
newRefreshToken, _ := jwtSrv.CreateRefreshToken(userID.(string))
if err = a.saveTokensToCache(userID.(string), newAccessToken, newRefreshToken); err != nil {
return "", ErrUnableToCacheToken
}
// REFACTOR
del := a.cache.Del(context.Background(), "user:"+accessToken)
if err := del.Err(); err != nil {
fmt.Println("failed to invalidate user:$ACCESS_TOKEN from cache: ", err.Error())
}
// REFACTOR: save uid in cache under user:$ACCES_TOKEN key
res := a.cache.Set(context.Background(), "user:"+newAccessToken, userID, accessTokenExpireTime)
if err := res.Err(); err != nil {
fmt.Println("failed to save user:$ACCESS_TOKEN in cache: ", err.Error())
}
return newAccessToken, nil
}
func (a *AuthService) Register(email, login, passwd string) (string, error) {
passwd, _ = passSrv.Hash(passwd)
id, err := a.userRepo.Create(&entity.User{
Email: email,
Username: login,
Password: passwd,
})
if err != nil {
return "", err
}
return id, nil
}
func (a *AuthService) GetTokenFromAuthorizationHeader(header *dto.AuthorizationHeaderDTO) (string, error) {
split := strings.Split(header.Authorization, " ")
if len(split) != 2 {
return "", ErrParsingAccessToken
}
return split[1], nil
}
func (a *AuthService) getUIDByAccesssToken(aToken string) (string, error) {
res := a.cache.Get(context.Background(), "user:"+aToken)
if err := res.Err(); err != nil {
return "", err
}
uid, _ := res.Result()
return uid, nil
}
func (a *AuthService) saveTokensToCache(id, aToken, rToken string) error {
res := a.cache.Set(context.Background(), "auth:access_token:"+id, aToken, accessTokenExpireTime)
if err := res.Err(); err != nil {
fmt.Println("failed to save access token in cache: ", err.Error())
return err
}
res = a.cache.Set(context.Background(), "auth:refresh_token:"+id, rToken, refreshTokenExpireTime)
if err := res.Err(); err != nil {
fmt.Println("failed to save refresh token in cache: ", err.Error())
return err
}
return nil
}

View File

@@ -0,0 +1,82 @@
package service
import (
"context"
"encoding/json"
"errors"
"time"
dto "git.ego.freeddns.org/egommerce/api-entities/identity/dto"
entity "git.ego.freeddns.org/egommerce/api-entities/identity/entity"
"git.ego.freeddns.org/egommerce/identity-service/infra/repository"
"github.com/go-redis/redis/v8"
)
type GuardService struct {
authSrv *AuthService
cache *redis.Client
userRepo *repository.UserRepository
roleRepo *repository.RoleRepository
urlRepo *repository.URLAccessRepository
}
func NewGuardService(authSrv *AuthService, cache *redis.Client, userRepo *repository.UserRepository, roleRepo *repository.RoleRepository, urlRepo *repository.URLAccessRepository) *GuardService {
return &GuardService{
authSrv: authSrv,
cache: cache,
userRepo: userRepo,
roleRepo: roleRepo,
urlRepo: urlRepo,
}
}
func (g *GuardService) CheckUserPermissions(authHeader *dto.AuthorizationHeaderDTO, url, srvName string) error {
token, _ := g.authSrv.GetTokenFromAuthorizationHeader(authHeader)
uid, _ := g.authSrv.getUIDByAccesssToken(token)
user, err := g.userRepo.FindByID(uid)
if err != nil {
return errors.New("user not found")
}
role := g.roleRepo.GetUserRole(user)
if _, err := g.urlRepo.FindByURLAndServiceForRole(url, srvName, role.Name); err != nil {
return errors.New("user doesn't have required permission")
}
return nil
}
func (g *GuardService) CacheAllPermissions() error {
urls, err := g.urlRepo.FindAll()
if err != nil {
return err
}
var urlsArr = make(map[string][]entity.URLAccess)
for _, url := range urls {
urlsArr[url.Service] = append(urlsArr[url.Service], url)
}
for service, url := range urlsArr {
json, err := json.Marshal(url)
if err != nil {
return err
}
jsonUrl := string(json)
if err := g.cache.HSet(context.Background(), "urls_access", service, jsonUrl).Err(); err != nil {
return err
}
if err := g.cache.Expire(context.Background(), "urls_access", time.Duration(time.Hour)).Err(); err != nil {
return err
}
}
return nil
}
// func (g *GuardService) fetchURLAccessFromCache() {}

100
src/internal/service/jwt.go Normal file
View File

@@ -0,0 +1,100 @@
package service
import (
"errors"
"fmt"
"strconv"
"time"
cnf "git.ego.freeddns.org/egommerce/go-api-pkg/config"
"github.com/golang-jwt/jwt"
)
var (
ErrorTokenExpired = errors.New("token has expired")
ErrInvalidToken = errors.New("invalid token")
accessTokenExpireTime time.Duration
refreshTokenExpireTime time.Duration
)
var jwtSrv *JWT
func init() {
expAccessTokenTime, _ := strconv.Atoi(cnf.GetEnv("JWT_ACCESS_TOKEN_EXPIRE_TIME", "1"))
accessTokenExpireTime = time.Duration(int(time.Hour) * expAccessTokenTime) // hours
expRefreshTokenTime, _ := strconv.Atoi(cnf.GetEnv("JWT_REFRESH_TOKEN_EXPIRE_TIME", "7"))
refreshTokenExpireTime = time.Duration(int(time.Hour*24) * expRefreshTokenTime) // days
jwtSrv = NewJWTService(
accessTokenExpireTime,
[]byte(cnf.GetEnv("JWT_ACCESS_TOKEN_SECRET_KEY", "FallbackAccessTokenSecret")),
refreshTokenExpireTime,
[]byte(cnf.GetEnv("JWT_REFRESH_TOKEN_SECRET_KEY", "FallbackRefreshTokenSecret")),
)
}
func NewJWTService(aTokenExp time.Duration, aTokenSecret []byte, rTokenExp time.Duration, rTokenSecret []byte) *JWT {
return &JWT{aTokenExp, aTokenSecret, rTokenExp, rTokenSecret}
}
type JWT struct {
accessTokenExpireTime time.Duration
accessTokenSecret []byte
refreshTokenExpireTime time.Duration
refreshTokenSecret []byte
}
func (s *JWT) CreateAccessToken(id string) (string, error) {
claims := &jwt.StandardClaims{
Subject: id,
IssuedAt: time.Now().Unix(),
ExpiresAt: time.Now().Add(s.accessTokenExpireTime).Unix(),
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(s.accessTokenSecret)
}
func (s *JWT) CreateRefreshToken(id string) (string, error) {
claims := &jwt.StandardClaims{
Subject: id,
IssuedAt: time.Now().Unix(),
ExpiresAt: time.Now().Add(s.refreshTokenExpireTime).Unix(),
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(s.accessTokenSecret)
}
func (s *JWT) ValidateAccessToken(tokenStr string) (*jwt.Token, jwt.MapClaims, error) {
token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
// Don't forget to validate the alg is what you expect:
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return s.accessTokenSecret, nil
})
if err != nil {
return nil, nil, err
}
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
if exp, ok := claims["exp"].(float64); ok {
if int64(exp) < time.Now().Unix() {
return nil, nil, ErrorTokenExpired
}
return token, claims, nil
}
}
return nil, nil, ErrInvalidToken
}

View File

@@ -0,0 +1,22 @@
package service
import "golang.org/x/crypto/bcrypt"
type PaswordService struct{}
func NewPasswordService() *PaswordService {
return &PaswordService{}
}
func (p *PaswordService) Hash(pass string) (string, error) {
hash, err := bcrypt.GenerateFromPassword([]byte(pass), bcrypt.DefaultCost)
if err != nil {
return "", err
}
return string(hash), nil
}
func (p *PaswordService) Verify(pass, hashedPass string) error {
return bcrypt.CompareHashAndPassword([]byte(hashedPass), []byte(pass))
}

View File

@@ -0,0 +1,24 @@
package ui
import (
identityDTO "git.ego.freeddns.org/egommerce/api-entities/identity/dto"
"git.ego.freeddns.org/egommerce/identity-service/internal/service"
)
type CheckUserPermissionsUI struct {
guard *service.GuardService
}
func NewAccessActionUI(guard *service.GuardService) *CheckUserPermissionsUI {
return &CheckUserPermissionsUI{guard: guard}
}
func (ui *CheckUserPermissionsUI) Execute(data *identityDTO.AuthorizationHeaderDTO, url, srvName string) error { // TODO: rename to CheckAccess
err := ui.guard.CheckUserPermissions(data, url, srvName)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,30 @@
package ui
import (
identityDTO "git.ego.freeddns.org/egommerce/api-entities/identity/dto"
"git.ego.freeddns.org/egommerce/identity-service/internal/service"
)
type LoginActionUI struct {
authSrv *service.AuthService
}
func NewLoginActionUI(authSrv *service.AuthService) *LoginActionUI {
return &LoginActionUI{
authSrv: authSrv,
}
}
func (ui *LoginActionUI) Execute(data *identityDTO.AuthLoginRequestDTO) (string, error) {
token, err := ui.authSrv.Login(data.Username, data.Password)
if err != nil {
// TODO: handle other response status codes -- add struct to decorate error with code and message
if err == service.ErrUnableToCacheToken {
return "", err
}
return "", err
}
return token, nil
}

View File

@@ -0,0 +1,30 @@
package ui
import (
identityDTO "git.ego.freeddns.org/egommerce/api-entities/identity/dto"
"git.ego.freeddns.org/egommerce/identity-service/internal/service"
)
type RefreshTokenActionUI struct {
auth *service.AuthService
}
func NewRefreshTokenActionUI(auth *service.AuthService) *RefreshTokenActionUI {
return &RefreshTokenActionUI{
auth: auth,
}
}
func (ui *RefreshTokenActionUI) Execute(header *identityDTO.AuthorizationHeaderDTO) (string, error) {
token, _ := ui.auth.GetTokenFromAuthorizationHeader(header)
newToken, err := ui.auth.RefreshToken(token)
if err != nil {
if err == service.ErrUnableToCacheToken { // FIXME: Move to RefreshHandlerFn
return "", err
}
return "", err
}
return newToken, nil
}

View File

@@ -0,0 +1,28 @@
package ui
import (
identityDTO "git.ego.freeddns.org/egommerce/api-entities/identity/dto"
"git.ego.freeddns.org/egommerce/identity-service/infra/repository"
"git.ego.freeddns.org/egommerce/identity-service/internal/service"
"github.com/go-redis/redis/v8"
)
type RegisterActionUI struct {
authSrv *service.AuthService
}
func NewRegisterActionUI(repo *repository.UserRepository, cache *redis.Client) *RegisterActionUI {
return &RegisterActionUI{
authSrv: service.NewAuthService(repo, cache),
}
}
func (ui *RegisterActionUI) Execute(data *identityDTO.AuthRegisterRequestDTO) (string, error) {
id, err := ui.authSrv.Register(data.Email, data.Username, data.Password)
if err != nil {
return "", err
}
return id, nil
}