feat: account crud

This commit is contained in:
kroyzen 2023-05-17 23:27:09 +03:00
parent 8bfd447d46
commit 7060ec31f7
49 changed files with 1128 additions and 970 deletions

@ -2,42 +2,35 @@ package main
import (
"context"
"fmt"
"log"
"os/signal"
"path"
"runtime"
"strconv"
"syscall"
formatter "github.com/antonfisher/nested-logrus-formatter"
"github.com/sirupsen/logrus"
"go.uber.org/zap"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/app"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/initialize"
)
func main() {
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
logger := logrus.New()
logger, err := zap.NewProduction(zap.AddCaller())
if err != nil {
log.Fatalf("failed to init zap logger: %v", err)
}
defer cancel()
logger.SetReportCaller(true)
logger.SetFormatter(&formatter.Formatter{
TimestampFormat: "02-01-2006 15:04:05",
HideKeys: true,
NoColors: false,
NoFieldsSpace: true,
CustomCallerFormatter: func(frame *runtime.Frame) string {
return fmt.Sprintf(" (%s:%s)", path.Base(frame.File), strconv.Itoa(frame.Line))
},
})
defer logger.Sync()
config, err := initialize.Configuration(".env.test")
if err != nil {
logger.Fatalf("failed to init config: %v", err)
logger.Fatal("failed to init config: %v",
zap.Error(err),
)
}
if err := app.Run(ctx, config, logger); err != nil {
logger.Fatalf("failed to run app: %v", err)
logger.Fatal("failed to run app: %v",
zap.Error(err),
)
}
}

12
go.mod

@ -20,6 +20,7 @@ require (
require (
cloud.google.com/go/compute/metadata v0.2.0 // indirect
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/brpaz/echozap v1.1.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/swag v0.21.1 // indirect
@ -36,7 +37,7 @@ require (
github.com/labstack/gommon v0.4.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
github.com/perimeterx/marshmallow v1.1.4 // indirect
@ -48,10 +49,13 @@ require (
github.com/xdg-go/scram v1.1.1 // indirect
github.com/xdg-go/stringprep v1.0.3 // indirect
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
golang.org/x/crypto v0.8.0 // indirect
golang.org/x/net v0.9.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.9.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/time v0.3.0 // indirect
google.golang.org/appengine v1.6.7 // indirect

34
go.sum

@ -6,12 +6,15 @@ github.com/antonfisher/nested-logrus-formatter v1.3.1/go.mod h1:6WTfyWFkBc9+zyBa
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
github.com/brpaz/echozap v1.1.3 h1:6cmi4m8/XwUckFH+cfsvX9eRomVOOs01AWDakEcDRCk=
github.com/brpaz/echozap v1.1.3/go.mod h1:5NJmhB1VsJbB8cyks5qft57uvgJwgls3t5tJbThIM4Y=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deepmap/oapi-codegen v1.12.4 h1:pPmn6qI9MuOtCz82WY2Xaw46EQjgvxednXXrP7g5Q2s=
github.com/deepmap/oapi-codegen v1.12.4/go.mod h1:3lgHGMu6myQ2vqbbTXH2H1o4eXFTGnFiDaOaKKl5yas=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/getkin/kin-openapi v0.116.0 h1:o986hwgMzR972JzOG5j6+WTwWqllZLs1EJKMKCivs2E=
github.com/getkin/kin-openapi v0.116.0/go.mod h1:l5e9PaFUo9fyLJCPGQeXI2ML8c3P8BHOEV2VaAVf/pc=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
@ -60,8 +63,10 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo/v4 v4.1.10/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M=
github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
@ -69,13 +74,18 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
@ -84,6 +94,7 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA
github.com/perimeterx/marshmallow v1.1.4 h1:pZLDH9RjlLGGorbXhcaQLhfuV0pFMNfPO55FuFkxqLw=
github.com/perimeterx/marshmallow v1.1.4/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@ -99,6 +110,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/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=
@ -114,6 +126,7 @@ github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
@ -127,21 +140,39 @@ github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
go.mongodb.org/mongo-driver v1.11.4 h1:4ayjakA013OdpGyL2K3ZqylTac/rMjrJOMZ1EHizXas=
go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/multierr v1.2.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw=
golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -150,8 +181,11 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=

@ -5,10 +5,12 @@ import (
"fmt"
"time"
"github.com/sirupsen/logrus"
"go.uber.org/zap"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/initialize"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/server"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/swagger"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/utils"
"penahub.gitlab.yandexcloud.net/pena-services/customer/pkg/closer"
"penahub.gitlab.yandexcloud.net/pena-services/customer/pkg/mongo"
)
@ -17,7 +19,7 @@ const (
shutdownTimeout = 5 * time.Second
)
func Run(ctx context.Context, config *models.Config, logger *logrus.Logger) error {
func Run(ctx context.Context, config *models.Config, logger *zap.Logger) error {
mongoDB, err := mongo.Connect(ctx, &mongo.ConnectDeps{
Configuration: &config.Database,
Timeout: 10 * time.Second,
@ -27,11 +29,8 @@ func Run(ctx context.Context, config *models.Config, logger *logrus.Logger) erro
}
clients := initialize.NewClients(&initialize.ClientsDeps{
Logger: logger,
GoogleURL: &config.Service.Google.URL,
AmocrmURL: &config.Service.Amocrm.URL,
AuthURL: &config.Service.AuthMicroservice.URL,
AmocrmOAuthConfiguration: &config.Service.Amocrm.OAuthConfig,
Logger: logger,
AuthURL: &config.Service.AuthMicroservice.URL,
})
repositories := initialize.NewRepositories(&initialize.RepositoriesDeps{
@ -51,8 +50,21 @@ func Run(ctx context.Context, config *models.Config, logger *logrus.Logger) erro
Services: services,
})
httpServer := server.New(logger).Register(controllers)
closer := closer.New(logger)
openAPI, err := swagger.GetSwagger()
if err != nil {
return fmt.Errorf("failed to loading openapi spec: %w", err)
}
swagger := initialize.NewSwagger(controllers)
closer := closer.New()
httpServer := server.New(&server.Deps{
Logger: logger,
Swagger: openAPI,
JWTUtil: utils.NewJWT[models.AuthJWTDecoded](&config.Service.JWT),
})
httpServer.Register(swagger)
go httpServer.Run(&config.HTTP)
@ -61,7 +73,7 @@ func Run(ctx context.Context, config *models.Config, logger *logrus.Logger) erro
<-ctx.Done()
logger.Infoln("shutting down app gracefully")
logger.Info("shutting down app gracefully")
shutdownCtx, cancel := context.WithTimeout(context.Background(), shutdownTimeout)

@ -2,9 +2,10 @@ package client
import (
"context"
"fmt"
"net/url"
"github.com/sirupsen/logrus"
"go.uber.org/zap"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/errors"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/utils"
@ -12,12 +13,12 @@ import (
)
type AuthClientDeps struct {
Logger *logrus.Logger
Logger *zap.Logger
URLs *models.AuthMicroServiceURL
}
type AuthClient struct {
logger *logrus.Logger
logger *zap.Logger
urls *models.AuthMicroServiceURL
}
@ -28,23 +29,40 @@ func NewAuthClient(deps *AuthClientDeps) *AuthClient {
}
}
func (receiver *AuthClient) GetUser(ctx context.Context, userID string) (*models.AuthUser, error) {
func (receiver *AuthClient) GetUser(ctx context.Context, userID string) (*models.User, errors.Error) {
userURL, err := url.JoinPath(receiver.urls.User, userID)
if err != nil {
return nil, errors.ErrInvalidReturnValue
return nil, errors.New(
fmt.Errorf("failed to join path on <GetUser> of <AuthClient>: %w", err),
errors.ErrInternalError,
)
}
response, err := client.Get[models.AuthUser, models.FastifyError](ctx, &client.RequestSettings{
response, err := client.Get[models.User, models.FastifyError](ctx, &client.RequestSettings{
URL: userURL,
Headers: map[string]string{"Content-Type": "application/json"},
})
if err != nil {
receiver.logger.Errorf("failed to get user on <GetUser> of <AuthClient>: %v", err)
return nil, err
receiver.logger.Error("failed to request get user on <GetUser> of <AuthClient>",
zap.Error(err),
zap.String("userID", userID),
)
return nil, errors.New(
fmt.Errorf("failed to request get user with <%s> on <GetUser> of <AuthClient>: %w", userID, err),
errors.ErrInternalError,
)
}
if response.Error != nil {
receiver.logger.Errorf("failed request on <GetUser> of <AuthClient>: %s", response.Error.Message)
return nil, utils.DetermineClientErrorResponse(response.StatusCode)
receiver.logger.Error("failed request on <GetUser> of <AuthClient>",
zap.String("error", response.Error.Message),
zap.String("userID", userID),
)
return nil, errors.New(
fmt.Errorf("failed request with <%s> on <GetUser> of <AuthClient>: %w", userID, err),
utils.DetermineClientErrorResponse(response.StatusCode),
)
}
return response.Body, nil

@ -0,0 +1,121 @@
package account
import (
"context"
"fmt"
"net/http"
"github.com/labstack/echo/v4"
"go.uber.org/zap"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/errors"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/swagger"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/utils"
)
type accountService interface {
GetAccountByUserID(ctx context.Context, userID string) (*models.Account, errors.Error)
GetAccountsList(ctx context.Context, pagination *models.Pagination) ([]models.Account, errors.Error)
CreateAccount(ctx context.Context, account *models.Account) (*models.Account, errors.Error)
CreateAccountByUserID(ctx context.Context, userID string) (*models.Account, errors.Error)
RemoveAccount(ctx context.Context, userID string) (*models.Account, errors.Error)
DeleteAccount(ctx context.Context, userID string) (*models.Account, errors.Error)
}
type Deps struct {
Logger *zap.Logger
Service accountService
}
type Controller struct {
logger *zap.Logger
service accountService
}
func New(deps *Deps) *Controller {
return &Controller{
logger: deps.Logger,
service: deps.Service,
}
}
func (receiver *Controller) GetAccount(ctx echo.Context) error {
userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
if !ok {
return utils.DetermineEchoErrorResponse(ctx, errors.New(
fmt.Errorf("failed to convert jwt payload to string: %s", userID),
errors.ErrInvalidArgs,
))
}
account, err := receiver.service.GetAccountByUserID(ctx.Request().Context(), userID)
if err != nil {
return utils.DetermineEchoErrorResponse(ctx, err)
}
return ctx.JSON(http.StatusOK, account)
}
func (receiver *Controller) GetDirectAccount(ctx echo.Context, userId string) error {
account, err := receiver.service.GetAccountByUserID(ctx.Request().Context(), userId)
if err != nil {
return utils.DetermineEchoErrorResponse(ctx, err)
}
return ctx.JSON(http.StatusOK, account)
}
func (receiver *Controller) GetAccounts(ctx echo.Context, params swagger.PaginationAccountsParams) error {
accounts, err := receiver.service.GetAccountsList(
ctx.Request().Context(),
utils.DeterminePagination(params.Page, params.Limit),
)
if err != nil {
return utils.DetermineEchoErrorResponse(ctx, err)
}
return ctx.JSON(http.StatusOK, accounts)
}
func (receiver *Controller) CreateAccount(ctx echo.Context) error {
userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
if !ok {
return utils.DetermineEchoErrorResponse(ctx, errors.New(
fmt.Errorf("failed to convert jwt payload to string: %s", userID),
errors.ErrInvalidArgs,
))
}
account, err := receiver.service.CreateAccountByUserID(ctx.Request().Context(), userID)
if err != nil {
return utils.DetermineEchoErrorResponse(ctx, err)
}
return ctx.JSON(http.StatusOK, account)
}
func (receiver *Controller) RemoveAccount(ctx echo.Context) error {
userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
if !ok {
return utils.DetermineEchoErrorResponse(ctx, errors.New(
fmt.Errorf("failed to convert jwt payload to string: %s", userID),
errors.ErrInvalidArgs,
))
}
account, err := receiver.service.RemoveAccount(ctx.Request().Context(), userID)
if err != nil {
return utils.DetermineEchoErrorResponse(ctx, err)
}
return ctx.JSON(http.StatusOK, account)
}
func (receiver *Controller) RemoveDirectAccount(ctx echo.Context, userId string) error {
account, err := receiver.service.RemoveAccount(ctx.Request().Context(), userId)
if err != nil {
return utils.DetermineEchoErrorResponse(ctx, err)
}
return ctx.JSON(http.StatusOK, account)
}

@ -1,7 +0,0 @@
package errors
import "errors"
var (
ErrNoServerItem = errors.New("microservice/service has no such item")
)

@ -1,10 +0,0 @@
package errors
import "errors"
var (
ErrInvalidReturnValue = errors.New("method of function returned invalid value")
ErrEmptyArgs = errors.New("empty arguments or nil argument")
ErrInvalidArgs = errors.New("invalid arguments")
ErrMethodNotImplemented = errors.New("method is not implemented")
)

56
internal/errors/errors.go Normal file

@ -0,0 +1,56 @@
package errors
import (
"errors"
"fmt"
)
type ErrorType error
var (
ErrInternalError ErrorType = errors.New("internal error")
ErrInvalidArgs ErrorType = errors.New("invalid arguments")
ErrMethodNotImplemented ErrorType = errors.New("method is not implemented")
ErrNotFound ErrorType = errors.New("record not found")
ErrNoAccess ErrorType = errors.New("no access")
)
type Error interface {
Error() string
Extract() error
Type() ErrorType
Wrap(err error)
SetType(errorType ErrorType)
}
type customError struct {
errorType ErrorType
err error
}
func New(err error, errorType ErrorType) *customError {
return &customError{
errorType: errorType,
err: err,
}
}
func (receiver *customError) Error() string {
return receiver.err.Error()
}
func (receiver *customError) Type() ErrorType {
return receiver.errorType
}
func (receiver *customError) Wrap(err error) {
receiver.err = fmt.Errorf("%w: %w", receiver.err, err)
}
func (receiver *customError) SetType(errorType ErrorType) {
receiver.errorType = errorType
}
func (receiver *customError) Extract() error {
return receiver.err
}

@ -1,15 +0,0 @@
package errors
import "errors"
var (
ErrNoRecord = errors.New("no record in db")
ErrInsertRecord = errors.New("failed to insert record")
ErrReadRecord = errors.New("failed to read record")
ErrFindRecord = errors.New("failed to find record")
ErrDecodeRecord = errors.New("failed to decode structure")
ErrTransaction = errors.New("failed transaction")
ErrTransactionSessionStart = errors.New("failed to start transaction session")
ErrUpdateRecord = errors.New("failed to update record")
ErrRecordAlreadyExist = errors.New("record already exist")
)

@ -0,0 +1,21 @@
package fields
var Account = struct {
ID string
UserID string
Cart string
Wallet string
Deleted string
CreatedAt string
UpdatedAt string
DeletedAt string
}{
ID: "_id",
UserID: "userId",
Cart: "cart",
Wallet: "wallet",
Deleted: "deleted",
CreatedAt: "createdAt",
UpdatedAt: "updatedAt",
DeletedAt: "deletedAt",
}

@ -1,15 +0,0 @@
package fields
var AmocrmUser = struct {
ID string
AmocrmID string
UserID string
Information string
Audit string
}{
ID: "id",
AmocrmID: "amocrmId",
UserID: "userId",
Information: "information",
Audit: auditFieldName,
}

@ -1,17 +0,0 @@
package fields
import "fmt"
var auditFieldName = "audit"
var Audit = struct {
UpdatedAt string
DeletedAt string
CreatedAt string
Deleted string
}{
UpdatedAt: fmt.Sprintf("%s.updatedAt", auditFieldName),
DeletedAt: fmt.Sprintf("%s.deletedAt", auditFieldName),
CreatedAt: fmt.Sprintf("%s.createdAt", auditFieldName),
Deleted: fmt.Sprintf("%s.deleted", auditFieldName),
}

@ -1,25 +0,0 @@
package fields
import "fmt"
// TODO: актуализировать поля для google user'а
var GoogleFields = struct {
Subject string
Fullname string
GivenName string
FamilyName string
PictureURL string
Email string
EmailVerified string
Locale string
}{
Subject: fmt.Sprintf("%s.sub", UserFields.Google),
Fullname: fmt.Sprintf("%s.name", UserFields.Google),
GivenName: fmt.Sprintf("%s.given_name", UserFields.Google),
FamilyName: fmt.Sprintf("%s.family_name", UserFields.Google),
PictureURL: fmt.Sprintf("%s.picture", UserFields.Google),
Email: fmt.Sprintf("%s.email", UserFields.Google),
EmailVerified: fmt.Sprintf("%s.email_verified", UserFields.Google),
Locale: fmt.Sprintf("%s.locale", UserFields.Google),
}

@ -1,19 +0,0 @@
package fields
var UserFields = struct {
ID string
UserID string
GoogleID string
VKID string
Google string
VK string
Amocrm string
}{
ID: "_id",
UserID: "userId",
GoogleID: "googleId",
VKID: "vkId",
Google: "googleInformation",
VK: "vkInformation",
Amocrm: "amocrmInformation",
}

@ -1,37 +0,0 @@
package fields
import "fmt"
// TODO: актуализировать поля для vk user'а
var VKFields = struct {
ID string
FirstName string
LastName string
Photo string
Sex string
Domain string
ScreenName string
Birthday string
PhotoID string
FollowersCount string
HomeTown string
Timezone string
MobilePhone string
Email string
}{
ID: fmt.Sprintf("%s.id", UserFields.VK),
FirstName: fmt.Sprintf("%s.firstname", UserFields.VK),
LastName: fmt.Sprintf("%s.lastname", UserFields.VK),
Photo: fmt.Sprintf("%s.avatar", UserFields.VK),
Sex: fmt.Sprintf("%s.sex", UserFields.VK),
Domain: fmt.Sprintf("%s.domain", UserFields.VK),
ScreenName: fmt.Sprintf("%s.screen_name", UserFields.VK),
Birthday: fmt.Sprintf("%s.birthday", UserFields.VK),
PhotoID: fmt.Sprintf("%s.photo_id", UserFields.VK),
FollowersCount: fmt.Sprintf("%s.followers_count", UserFields.VK),
HomeTown: fmt.Sprintf("%s.home_town", UserFields.VK),
Timezone: fmt.Sprintf("%s.timezone", UserFields.VK),
MobilePhone: fmt.Sprintf("%s.mobile_phone", UserFields.VK),
Email: fmt.Sprintf("%s.email", UserFields.VK),
}

@ -1,18 +1,14 @@
package initialize
import (
"github.com/sirupsen/logrus"
"golang.org/x/oauth2"
"go.uber.org/zap"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/client"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models"
)
type ClientsDeps struct {
Logger *logrus.Logger
GoogleURL *models.GoogleURL
AmocrmURL *models.AmocrmURL
AuthURL *models.AuthMicroServiceURL
AmocrmOAuthConfiguration *oauth2.Config
Logger *zap.Logger
AuthURL *models.AuthMicroServiceURL
}
type Clients struct {

@ -4,9 +4,6 @@ import (
"time"
"github.com/golang-jwt/jwt/v5"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"golang.org/x/oauth2/vk"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/utils"
"penahub.gitlab.yandexcloud.net/pena-services/customer/pkg/env"
@ -22,46 +19,11 @@ func Configuration(path string) (*models.Config, error) {
return nil, err
}
initOAuth2Configuration(&config.Service)
iniJWTConfiguration(&config.Service.JWT)
return config, nil
}
func initOAuth2Configuration(config *models.ServiceConfiguration) {
config.Google.OAuthConfig = oauth2.Config{
RedirectURL: config.Google.URL.Redirect,
ClientID: config.Google.ClientID,
ClientSecret: config.Google.ClientSecret,
Scopes: []string{
"openid",
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile",
},
Endpoint: google.Endpoint,
}
config.VK.OAuthConfig = oauth2.Config{
RedirectURL: config.VK.URL.Redirect,
ClientID: config.VK.ClientID,
ClientSecret: config.VK.ClientSecret,
Scopes: []string{"email"},
Endpoint: vk.Endpoint,
}
config.Amocrm.OAuthConfig = oauth2.Config{
RedirectURL: config.Amocrm.URL.Redirect,
ClientID: config.Amocrm.ClientID,
ClientSecret: config.Amocrm.ClientSecret,
Scopes: nil,
Endpoint: oauth2.Endpoint{
AuthURL: config.Amocrm.URL.OAuthHost,
TokenURL: config.Amocrm.URL.AccessToken,
AuthStyle: models.BodyAuthStyle,
},
}
}
func iniJWTConfiguration(config *models.JWTConfiguration) {
config.Algorithm = *jwt.SigningMethodRS256
config.ExpiresIn = 15 * time.Minute

@ -6,9 +6,6 @@ import (
"github.com/golang-jwt/jwt/v5"
"github.com/stretchr/testify/assert"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"golang.org/x/oauth2/vk"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/initialize"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/customer/pkg/mongo"
@ -17,58 +14,8 @@ import (
func setDefaultTestingENV(t *testing.T) *models.Config {
t.Helper()
defaultGoogleURL := models.GoogleURL{
Redirect: "http://www.google.com/callback",
OAuthHost: "http://www.google.com/oauth",
}
defaultVKURL := models.VKURL{
Redirect: "http://www.vk.ru/callback",
}
defaultAmocrmURL := models.AmocrmURL{
Redirect: "http://www.amocrm.ru/callback",
OAuthHost: "http://www.amocrm.ru/oauth",
UserInfo: "http://www.amocrm.ru/user",
AccessToken: "http://www.amocrm.ru/token",
}
defaultAuthURL := models.AuthMicroServiceURL{
Exchange: "http://www.auth.ru/callback",
Register: "http://www.auth.ru/register",
User: "http://www.auth.ru/user",
}
defaultGoogleOAuthConfiguration := oauth2.Config{
RedirectURL: defaultGoogleURL.Redirect,
ClientID: "google_client_id",
ClientSecret: "google_client_secret",
Scopes: []string{
"openid",
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile",
},
Endpoint: google.Endpoint,
}
defaultVKOAuthConfiguration := oauth2.Config{
RedirectURL: defaultVKURL.Redirect,
ClientID: "vk_client_id",
ClientSecret: "vk_client_secret",
Scopes: []string{"email"},
Endpoint: vk.Endpoint,
}
defaultAmocrmOAuthConfiguration := oauth2.Config{
RedirectURL: defaultAmocrmURL.Redirect,
ClientID: "amocrm_client_id",
ClientSecret: "amocrm_client_secret",
Scopes: nil,
Endpoint: oauth2.Endpoint{
AuthURL: defaultAmocrmURL.OAuthHost,
TokenURL: defaultAmocrmURL.AccessToken,
AuthStyle: models.BodyAuthStyle,
},
User: "http://www.auth.ru/user",
}
defaultConfiguration := models.Config{
@ -77,28 +24,8 @@ func setDefaultTestingENV(t *testing.T) *models.Config {
Port: "8080",
},
Service: models.ServiceConfiguration{
Google: models.GoogleConfiguration{
ClientID: defaultGoogleOAuthConfiguration.ClientID,
ClientSecret: defaultGoogleOAuthConfiguration.ClientSecret,
OAuthConfig: defaultGoogleOAuthConfiguration,
URL: defaultGoogleURL,
},
VK: models.VKConfiguration{
ClientID: defaultVKOAuthConfiguration.ClientID,
ClientSecret: defaultVKOAuthConfiguration.ClientSecret,
OAuthConfig: defaultVKOAuthConfiguration,
URL: defaultVKURL,
},
Amocrm: models.AmocrmConfiguration{
ClientID: defaultAmocrmOAuthConfiguration.ClientID,
ClientSecret: defaultAmocrmOAuthConfiguration.ClientSecret,
OAuthConfig: defaultAmocrmOAuthConfiguration,
URL: defaultAmocrmURL,
},
AuthMicroservice: models.AuthMicroserviceConfiguration{
AuthGroup: "group",
PrivateSignKey: "key",
URL: defaultAuthURL,
URL: defaultAuthURL,
},
JWT: models.JWTConfiguration{
PrivateKey: "jwt private key",
@ -124,26 +51,6 @@ func setDefaultTestingENV(t *testing.T) *models.Config {
t.Setenv("JWT_ISSUER", defaultConfiguration.Service.JWT.Issuer)
t.Setenv("JWT_AUDIENCE", defaultConfiguration.Service.JWT.Audience)
t.Setenv("GOOGLE_CLIENT_ID", defaultConfiguration.Service.Google.ClientID)
t.Setenv("GOOGLE_CLIENT_SECRET", defaultConfiguration.Service.Google.ClientSecret)
t.Setenv("GOOGLE_REDIRECT_URL", defaultConfiguration.Service.Google.URL.Redirect)
t.Setenv("GOOGLE_OAUTH_HOST", defaultConfiguration.Service.Google.URL.OAuthHost)
t.Setenv("VK_CLIENT_ID", defaultConfiguration.Service.VK.ClientID)
t.Setenv("VK_CLIENT_SECRET", defaultConfiguration.Service.VK.ClientSecret)
t.Setenv("VK_REDIRECT_URL", defaultConfiguration.Service.VK.URL.Redirect)
t.Setenv("AMOCRM_CLIENT_ID", defaultConfiguration.Service.Amocrm.ClientID)
t.Setenv("AMOCRM_CLIENT_SECRET", defaultConfiguration.Service.Amocrm.ClientSecret)
t.Setenv("AMOCRM_REDIRECT_URL", defaultConfiguration.Service.Amocrm.URL.Redirect)
t.Setenv("AMOCRM_OAUTH_HOST", defaultConfiguration.Service.Amocrm.URL.OAuthHost)
t.Setenv("AMOCRM_USER_INFO_URL", defaultConfiguration.Service.Amocrm.URL.UserInfo)
t.Setenv("AMOCRM_ACCESS_TOKEN_URL", defaultConfiguration.Service.Amocrm.URL.AccessToken)
t.Setenv("AUTH_MICROSERVICE_GROUP", defaultConfiguration.Service.AuthMicroservice.AuthGroup)
t.Setenv("AUTH_MICROSERVICE_PRIVATE_SIGN_KEY", defaultConfiguration.Service.AuthMicroservice.PrivateSignKey)
t.Setenv("AUTH_MICROSERVICE_EXHANGE_URL", defaultConfiguration.Service.AuthMicroservice.URL.Exchange)
t.Setenv("AUTH_MICROSERVICE_REGISTER_URL", defaultConfiguration.Service.AuthMicroservice.URL.Register)
t.Setenv("AUTH_MICROSERVICE_USER_URL", defaultConfiguration.Service.AuthMicroservice.URL.User)
t.Setenv("MONGO_HOST", defaultConfiguration.Database.Host)

@ -1,17 +1,24 @@
package initialize
import (
"github.com/sirupsen/logrus"
"go.uber.org/zap"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/controller/account"
)
type ControllersDeps struct {
Logger *logrus.Logger
Logger *zap.Logger
Services *Services
}
type Controllers struct {
AccountController *account.Controller
}
func NewControllers(deps *ControllersDeps) *Controllers {
return &Controllers{}
return &Controllers{
AccountController: account.New(&account.Deps{
Logger: deps.Logger,
Service: deps.Services.AccountService,
}),
}
}

@ -1,32 +1,27 @@
package initialize
import (
"github.com/sirupsen/logrus"
"go.mongodb.org/mongo-driver/mongo"
"go.uber.org/zap"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/repository"
)
type RepositoriesDeps struct {
MongoDB *mongo.Database
Logger *logrus.Logger
Logger *zap.Logger
}
type Repositories struct {
HealthRepository *repository.HealthRepository
GoogleRepository *repository.GoogleRepository
AmocrmRepository *repository.AmocrmRepository
HealthRepository *repository.HealthRepository
AccountRepository *repository.AccountRepository
}
func NewRepositories(deps *RepositoriesDeps) *Repositories {
return &Repositories{
HealthRepository: repository.NewHealthRepository(deps.MongoDB),
AmocrmRepository: repository.NewAmocrmRepository(
deps.MongoDB.Collection("amocrm"),
deps.Logger,
),
GoogleRepository: repository.NewGoogleRepository(
deps.MongoDB.Collection("google"),
deps.Logger,
),
AccountRepository: repository.NewAccountRepository(&repository.AccountRepositoryDeps{
MongoDB: deps.MongoDB.Collection("accounts"),
Logger: deps.Logger,
}),
}
}

@ -18,8 +18,7 @@ func TestNewRepositories(t *testing.T) {
})
assert.NotNil(t, repositories)
assert.NotNil(t, repositories.AmocrmRepository)
assert.NotNil(t, repositories.GoogleRepository)
assert.NotNil(t, repositories.AccountRepository)
assert.NotNil(t, repositories.HealthRepository)
})
})

@ -1,20 +1,27 @@
package initialize
import (
"github.com/sirupsen/logrus"
"go.uber.org/zap"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/service/account"
)
type ServicesDeps struct {
Logger *logrus.Logger
Logger *zap.Logger
Config *models.ServiceConfiguration
Repositories *Repositories
Clients *Clients
}
type Services struct {
AccountService *account.Service
}
func NewServices(deps *ServicesDeps) *Services {
return &Services{}
return &Services{
AccountService: account.New(&account.Deps{
Logger: deps.Logger,
Repository: deps.Repositories.AccountRepository,
}),
}
}

@ -0,0 +1,7 @@
package initialize
import "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/swagger"
func NewSwagger(controllers *Controllers) *swagger.API {
return swagger.New(&swagger.Deps{})
}

@ -0,0 +1,25 @@
package models
import "time"
type Account struct {
ID string `json:"id" bson:"_id"`
UserID string `json:"userId" bson:"userId"`
Cart []string `json:"cart" bson:"cart"`
Wallet Wallet `json:"wallet" bson:"wallet"`
Deleted bool `json:"deleted" bson:"deleted"`
CreatedAt time.Time `json:"createdAt" bson:"createdAt"`
UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt"`
DeletedAt *time.Time `json:"deletedAt,omitempty" bson:"deletedAt,omitempty"`
}
type Wallet struct {
Cash int64 `json:"cash"`
Currency string `json:"currency"`
/*
Money деньги на счету в копейках. Чтобы при перессчётах не возникало денег из ни откуда.
Фиксируемся к одной валюте, она будет внутренней, никому её не покажем.
*/
Money int64 `json:"money"`
}

@ -1,103 +0,0 @@
package models
type AmocrmUser struct {
ID string `json:"id" bson:"_id,omitempty"`
AmocrmID string `json:"amocrmId" bson:"amocrmId"`
UserID string `json:"userId,omitempty" bson:"userId,omitempty"`
Information AmocrmUserInformation `json:"information" bson:"information"`
Audit Audit `json:"audit" bson:"audit"`
}
type AmocrmUserInformation struct {
ID int64 `json:"id" bson:"id"`
Name string `json:"name" bson:"name"`
Subdomain string `json:"subdomain" bson:"subdomain"`
CreatedAt int `json:"created_at" bson:"created_at"`
CreatedBy int `json:"created_by" bson:"created_by"`
UpdatedAt int `json:"updated_at" bson:"updated_at"`
UpdatedBy int `json:"updated_by" bson:"updated_by"`
CurrentUserID int `json:"current_user_id" bson:"current_user_id"`
Country string `json:"country" bson:"country"`
CustomersMode string `json:"customers_mode" bson:"customers_mode"`
IsUnsortedOn bool `json:"is_unsorted_on" bson:"is_unsorted_on"`
IsLossReasonEnabled bool `json:"is_loss_reason_enabled" bson:"is_loss_reason_enabled"`
IsHelpbotEnabled bool `json:"is_helpbot_enabled" bson:"is_helpbot_enabled"`
IsTechnicalAccount bool `json:"is_technical_account" bson:"is_technical_account"`
ContactNameDisplayOrder int `json:"contact_name_display_order" bson:"contact_name_display_order"`
AmojoID string `json:"amojo_id" bson:"amojo_id"`
UUID string `json:"uuid" bson:"uuid"`
Version int `json:"version" bson:"version"`
Links struct {
Self struct {
Href string `json:"href" bson:"href"`
} `json:"self" bson:"self"`
} `json:"_links" bson:"_links"`
Embedded struct {
AmojoRights struct {
CanDirect bool `json:"can_direct" bson:"can_direct"`
CanCreateGroups bool `json:"can_create_groups" bson:"can_create_groups"`
} `json:"amojo_rights" bson:"amojo_rights"`
UsersGroups []struct {
ID int `json:"id" bson:"id"`
Name string `json:"name" bson:"name"`
UUID interface{} `json:"uuid" bson:"uuid"`
} `json:"users_groups" bson:"users_groups"`
TaskTypes []struct {
ID int `json:"id" bson:"id"`
Name string `json:"name" bson:"name"`
Color interface{} `json:"color" bson:"color"`
IconID interface{} `json:"icon_id" bson:"icon_id"`
Code string `json:"code" bson:"code"`
} `json:"task_types" bson:"task_types"`
EntityNames struct {
Leads struct {
Ru struct {
Gender string `json:"gender" bson:"gender"`
PluralForm struct {
Dative string `json:"dative" bson:"dative"`
Default string `json:"default" bson:"default"`
Genitive string `json:"genitive" bson:"genitive"`
Accusative string `json:"accusative" bson:"accusative"`
Instrumental string `json:"instrumental" bson:"instrumental"`
Prepositional string `json:"prepositional" bson:"prepositional"`
} `json:"plural_form" bson:"plural_form"`
SingularForm struct {
Dative string `json:"dative" bson:"dative"`
Default string `json:"default" bson:"default"`
Genitive string `json:"genitive" bson:"genitive"`
Accusative string `json:"accusative" bson:"accusative"`
Instrumental string `json:"instrumental" bson:"instrumental"`
Prepositional string `json:"prepositional" bson:"prepositional"`
} `json:"singular_form" bson:"singular_form"`
} `json:"ru" bson:"ru"`
En struct {
SingularForm struct {
Default string `json:"default" bson:"default"`
} `json:"singular_form" bson:"singular_form"`
PluralForm struct {
Default string `json:"default" bson:"default"`
} `json:"plural_form" bson:"plural_form"`
Gender string `json:"gender" bson:"gender"`
} `json:"en" bson:"en"`
Es struct {
SingularForm struct {
Default string `json:"default" bson:"default"`
} `json:"singular_form" bson:"singular_form"`
PluralForm struct {
Default string `json:"default" bson:"default"`
} `json:"plural_form" bson:"plural_form"`
Gender string `json:"gender" bson:"gender"`
} `json:"es" bson:"es"`
} `json:"leads" bson:"leads"`
} `json:"entity_names" bson:"entity_names"`
DatetimeSettings struct {
DatePattern string `json:"date_pattern" bson:"date_pattern"`
ShortDatePattern string `json:"short_date_pattern" bson:"short_date_pattern"`
ShortTimePattern string `json:"short_time_pattern" bson:"short_time_pattern"`
DateFormant string `json:"date_formant" bson:"date_formant"`
TimeFormat string `json:"time_format" bson:"time_format"`
Timezone string `json:"timezone" bson:"timezone"`
TimezoneOffset string `json:"timezone_offset" bson:"timezone_offset"`
} `json:"datetime_settings" bson:"datetime_settings"`
} `json:"_embedded" bson:"_embedded"`
}

@ -2,39 +2,19 @@ package models
import "time"
type AuthUserInformation struct {
Email string `json:"email"`
PhoneNumber string `json:"phoneNumber"`
}
type JWTAuthUser struct {
ID string `json:"id"`
}
type AuthUser struct {
type User struct {
ID string `json:"_id"`
Login string `json:"login"`
Email string `json:"email"`
PhoneNumber string `json:"phoneNumber"`
IsDeleted bool `json:"isDeleted"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
DeletedAt *time.Time `json:"deletedAt,omitempty"`
IsDeleted bool `json:"isDeleted"`
}
type Tokens struct {
AccessToken string `json:"accessToken"`
RefreshToken string `json:"refreshToken"`
type AuthJWTDecoded struct {
UserID string `json:"id"`
}
type RegisterRequest struct {
Login string `json:"login"`
PhoneNumber string `json:"phoneNumber"`
Email string `json:"email"`
Password string `json:"password"`
}
type ExchangeRequest struct {
UserID string `json:"userId"`
Signature string `json:"signature"`
}
const AuthJWTDecodedUserIDKey = "userID"

@ -1,22 +1,5 @@
package models
import (
"time"
"golang.org/x/oauth2"
)
type Audit struct {
UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt"`
DeletedAt *time.Time `json:"deletedAt,omitempty" bson:"deletedAt,omitempty"`
CreatedAt time.Time `json:"createdAt" bson:"createdAt"`
Deleted bool `json:"deleted" bson:"deleted"`
}
type GenerateURLResponse struct {
URL string `json:"url"`
}
type FastifyError struct {
StatusCode int `json:"statusCode"`
Error string `json:"error"`
@ -28,6 +11,12 @@ type ResponseErrorHTTP struct {
Message string `json:"message"`
}
type Pagination struct {
Page int64
Limit int64
}
const (
BodyAuthStyle oauth2.AuthStyle = 4
DefaultPageNumber int64 = 1
DefaultLimit int64 = 100
)

@ -4,7 +4,6 @@ import (
"time"
"github.com/golang-jwt/jwt/v5"
"golang.org/x/oauth2"
"penahub.gitlab.yandexcloud.net/pena-services/customer/pkg/mongo"
)
@ -20,9 +19,6 @@ type HTTPConfiguration struct {
}
type ServiceConfiguration struct {
Google GoogleConfiguration
VK VKConfiguration
Amocrm AmocrmConfiguration
AuthMicroservice AuthMicroserviceConfiguration
JWT JWTConfiguration
}
@ -36,52 +32,10 @@ type JWTConfiguration struct {
ExpiresIn time.Duration
}
type GoogleConfiguration struct {
ClientID string `env:"GOOGLE_CLIENT_ID,required"`
ClientSecret string `env:"GOOGLE_CLIENT_SECRET,required"`
OAuthConfig oauth2.Config
URL GoogleURL
}
type VKConfiguration struct {
ClientID string `env:"VK_CLIENT_ID,required"`
ClientSecret string `env:"VK_CLIENT_SECRET,required"`
OAuthConfig oauth2.Config
URL VKURL
}
type AmocrmConfiguration struct {
ClientID string `env:"AMOCRM_CLIENT_ID,required"`
ClientSecret string `env:"AMOCRM_CLIENT_SECRET,required"`
OAuthConfig oauth2.Config
URL AmocrmURL
}
type AuthMicroserviceConfiguration struct {
AuthGroup string `env:"AUTH_MICROSERVICE_GROUP,required"`
PrivateSignKey string `env:"AUTH_MICROSERVICE_PRIVATE_SIGN_KEY,required"`
URL AuthMicroServiceURL
}
type GoogleURL struct {
Redirect string `env:"GOOGLE_REDIRECT_URL,required"`
OAuthHost string `env:"GOOGLE_OAUTH_HOST,required"`
UserInfo string
}
type AmocrmURL struct {
Redirect string `env:"AMOCRM_REDIRECT_URL,required"`
OAuthHost string `env:"AMOCRM_OAUTH_HOST,required"`
UserInfo string `env:"AMOCRM_USER_INFO_URL,required"`
AccessToken string `env:"AMOCRM_ACCESS_TOKEN_URL,required"`
URL AuthMicroServiceURL
}
type AuthMicroServiceURL struct {
Exchange string `env:"AUTH_MICROSERVICE_EXHANGE_URL,required"`
Register string `env:"AUTH_MICROSERVICE_REGISTER_URL,required"`
User string `env:"AUTH_MICROSERVICE_USER_URL,required"`
}
type VKURL struct {
Redirect string `env:"VK_REDIRECT_URL,required"`
User string `env:"AUTH_MICROSERVICE_USER_URL,required"`
}

@ -1,13 +0,0 @@
package models
type GoogleUserInformation struct {
// The subject property contains the unique user identifier of the user who signed in
Subject string `json:"sub" bson:"Subject"`
Fullname string `json:"name" bson:"Fullname"`
GivenName string `json:"given_name" bson:"GivenName"`
FamilyName string `json:"family_name" bson:"FamilyName"`
AvatarURL string `json:"picture" bson:"AvatarURL"`
Email string `json:"email" bson:"Email"`
EmailVerified bool `json:"email_verified" bson:"EmailVerified"`
Locale string `json:"locale" bson:"Locale"`
}

@ -1,18 +0,0 @@
package models
type VKUserInformation struct {
ID int64 `json:"id" bson:"id"`
FirstName string `json:"first_name" bson:"firstname"`
LastName string `json:"last_name" bson:"lastname"`
Photo string `json:"photo_400_orig" bson:"avatar"`
Sex int `json:"sex" bson:"sex"`
Domain string `json:"domain" bson:"domain"`
ScreenName string `json:"screen_name" bson:"screen_name"`
Birthday string `json:"bdate" bson:"birthday"`
PhotoID string `json:"photo_id" bson:"photo_id"`
FollowersCount int `json:"followers_count" bson:"followers_count"`
HomeTown string `json:"home_town" bson:"home_town"`
Timezone float64 `json:"timezone" bson:"timezone"`
MobilePhone string `json:"mobile_phone" bson:"mobile_phone"`
Email string `json:"email" bson:"email"`
}

@ -0,0 +1,179 @@
package repository
import (
"context"
"fmt"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.uber.org/zap"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/errors"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/fields"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models"
mongoWrapper "penahub.gitlab.yandexcloud.net/pena-services/customer/pkg/mongo"
)
type AccountRepositoryDeps struct {
MongoDB *mongo.Collection
Logger *zap.Logger
}
type AccountRepository struct {
mongoDB *mongo.Collection
logger *zap.Logger
}
func NewAccountRepository(deps *AccountRepositoryDeps) *AccountRepository {
return &AccountRepository{
mongoDB: deps.MongoDB,
logger: deps.Logger,
}
}
func (receiver *AccountRepository) FindByUserID(ctx context.Context, id string) (*models.Account, errors.Error) {
filter := bson.M{
fields.Account.UserID: id,
fields.Account.Deleted: false,
}
account, err := mongoWrapper.FindOne[models.Account](ctx, &mongoWrapper.RequestSettings{
Driver: receiver.mongoDB,
Filter: filter,
})
if err != nil {
receiver.logger.Error("failed to find account by userID on <FindByUserID> of <AccountRepository>",
zap.String("id", id),
zap.Error(err),
)
findError := errors.New(
fmt.Errorf("failed to find account with <%s> on <FindByUserID> of <AccountRepository>: %w", id, err),
errors.ErrInternalError,
)
if err == mongo.ErrNoDocuments {
findError.SetType(errors.ErrNotFound)
}
return nil, findError
}
return account, nil
}
func (receiver *AccountRepository) FindMany(ctx context.Context, page, limit int64) ([]models.Account, errors.Error) {
filter := bson.M{fields.Account.Deleted: false}
findOptions := options.Find()
skip := (page - 1) * limit
findOptions.SetSkip(skip)
findOptions.SetLimit(limit)
accounts, err := mongoWrapper.Find[models.Account](ctx, &mongoWrapper.RequestSettings{
Driver: receiver.mongoDB,
Options: findOptions,
Filter: filter,
})
if err != nil {
receiver.logger.Error("failed to find many accounts on <FindMany> of <AccountRepository>",
zap.Int64("page", page),
zap.Int64("limit", limit),
zap.Int64("skip", skip),
zap.Error(err),
)
return nil, errors.New(
fmt.Errorf("failed to find many accounts on <FindMany> of <AccountRepository>: %w", err),
errors.ErrInternalError,
)
}
return accounts, nil
}
func (receiver *AccountRepository) Insert(ctx context.Context, account *models.Account) (*models.Account, errors.Error) {
account.CreatedAt = time.Now()
account.UpdatedAt = time.Now()
account.Deleted = false
result, err := receiver.mongoDB.InsertOne(ctx, account)
if err != nil {
receiver.logger.Error("failed to insert account on <Insert> of <AccountRepository>",
zap.Any("account", account),
zap.Error(err),
)
return nil, errors.New(
fmt.Errorf("failed to insert account on <Insert> of <AccountRepository>: %w", err),
errors.ErrInternalError,
)
}
insertedID := result.InsertedID.(primitive.ObjectID).Hex()
account.ID = insertedID
return account, nil
}
func (receiver *AccountRepository) Remove(ctx context.Context, id string) (*models.Account, errors.Error) {
account := models.Account{}
update := bson.M{"$set": bson.M{fields.Account.Deleted: true}}
filter := bson.M{
fields.Account.UserID: id,
fields.Account.Deleted: false,
}
if err := receiver.mongoDB.FindOneAndUpdate(ctx, filter, update).Decode(&account); err != nil {
receiver.logger.Error("failed to set 'deleted=true' on <Delete> of <AccountRepository>",
zap.String("id", id),
zap.Error(err),
)
removeErr := errors.New(
fmt.Errorf("failed to remove account with <%s> on <Remove> of <AccountRepository>: %w", id, err),
errors.ErrInternalError,
)
if err == mongo.ErrNoDocuments {
removeErr.SetType(errors.ErrNotFound)
}
return nil, removeErr
}
return &account, nil
}
func (receiver *AccountRepository) Delete(ctx context.Context, id string) (*models.Account, errors.Error) {
account := models.Account{}
filter := bson.M{
fields.Account.UserID: id,
fields.Account.Deleted: false,
}
if err := receiver.mongoDB.FindOneAndDelete(ctx, filter).Decode(&account); err != nil {
receiver.logger.Error("failed delete account on <Delete> of <AccountRepository>",
zap.String("id", id),
zap.Error(err),
)
removeErr := errors.New(
fmt.Errorf("failed to remove account with <%s> on <Delete> of <AccountRepository>: %w", id, err),
errors.ErrInternalError,
)
if err == mongo.ErrNoDocuments {
removeErr.SetType(errors.ErrNotFound)
}
return nil, removeErr
}
return &account, nil
}

@ -1,142 +0,0 @@
package repository
import (
"context"
"time"
"github.com/sirupsen/logrus"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/errors"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/fields"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models"
mongoWrapper "penahub.gitlab.yandexcloud.net/pena-services/customer/pkg/mongo"
)
type AmocrmRepository struct {
mongoDB *mongo.Collection
logger *logrus.Logger
}
func NewAmocrmRepository(mongoDB *mongo.Collection, logger *logrus.Logger) *AmocrmRepository {
return &AmocrmRepository{
mongoDB: mongoDB,
logger: logger,
}
}
func (receiver *AmocrmRepository) FindByID(ctx context.Context, amocrmID string) (*models.AmocrmUser, error) {
filter := bson.M{
fields.AmocrmUser.AmocrmID: amocrmID,
fields.Audit.Deleted: false,
}
user, err := mongoWrapper.FindOne[models.AmocrmUser](ctx, &mongoWrapper.RequestSettings{
Driver: receiver.mongoDB,
Filter: filter,
})
if err != nil {
receiver.logger.Errorf("failed to find amocrm user <%s> on <FindByID> of <AmocrmRepository>: %v", amocrmID, err)
if err == mongo.ErrNoDocuments {
return nil, errors.ErrNoRecord
}
return nil, errors.ErrFindRecord
}
return user, nil
}
func (receiver *AmocrmRepository) FindByUserID(ctx context.Context, userID string) (*models.AmocrmUser, error) {
filter := bson.M{
fields.AmocrmUser.UserID: userID,
fields.Audit.Deleted: false,
}
user, err := mongoWrapper.FindOne[models.AmocrmUser](ctx, &mongoWrapper.RequestSettings{
Driver: receiver.mongoDB,
Filter: filter,
})
if err != nil {
receiver.logger.Errorf("failed to find amocrm user <%s> on <FindByUserID> of <AmocrmRepository>: %v", userID, err)
if err == mongo.ErrNoDocuments {
return nil, errors.ErrNoRecord
}
return nil, errors.ErrFindRecord
}
return user, nil
}
func (receiver *AmocrmRepository) Insert(ctx context.Context, user *models.AmocrmUser) (*models.AmocrmUser, error) {
user.Audit = models.Audit{
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
Deleted: false,
}
result, err := receiver.mongoDB.InsertOne(ctx, user)
if err != nil {
receiver.logger.Errorf("failed to insert record on <Insert> of <AmocrmRepository>: %v", err)
return nil, errors.ErrInsertRecord
}
insertedID := result.InsertedID.(primitive.ObjectID).Hex()
userCopy := *user
userCopy.ID = insertedID
return &userCopy, nil
}
func (receiver *AmocrmRepository) Delete(ctx context.Context, amocrmID string) (*models.AmocrmUser, error) {
user := models.AmocrmUser{}
update := bson.M{"$set": bson.M{fields.Audit.Deleted: true}}
filter := bson.M{
fields.AmocrmUser.AmocrmID: amocrmID,
fields.Audit.Deleted: false,
}
if err := receiver.mongoDB.FindOneAndUpdate(ctx, filter, update).Decode(&user); err != nil {
receiver.logger.Errorf("failed to set 'deleted=true' with id <%s> on <Delete> of <AmocrmRepository>: %v", amocrmID, err)
if err == mongo.ErrNoDocuments {
return nil, errors.ErrNoRecord
}
return nil, errors.ErrUpdateRecord
}
return &user, nil
}
func (receiver *AmocrmRepository) Remove(ctx context.Context, id string) (*models.AmocrmUser, error) {
objectID, err := primitive.ObjectIDFromHex(id)
if err != nil {
receiver.logger.Errorf("failed to parse ObjectID <%s> on <FindByID> of <DiscountRepository>: %v", id, err)
return nil, errors.ErrInvalidArgs
}
user := models.AmocrmUser{}
filter := bson.M{
fields.AmocrmUser.ID: objectID,
fields.Audit.Deleted: false,
}
if err := receiver.mongoDB.FindOneAndDelete(ctx, filter).Decode(&user); err != nil {
receiver.logger.Errorf("failed remove user with _id <%s> on <Remove> of <AmocrmRepository>: %v", id, err)
if err == mongo.ErrNoDocuments {
return nil, errors.ErrNoRecord
}
return nil, errors.ErrUpdateRecord
}
return &user, nil
}

@ -1,48 +0,0 @@
package repository
import (
"context"
"github.com/sirupsen/logrus"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/errors"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models"
)
type GoogleRepository struct {
mongoDB *mongo.Collection
logger *logrus.Logger
}
func NewGoogleRepository(mongoDB *mongo.Collection, logger *logrus.Logger) *GoogleRepository {
return &GoogleRepository{
mongoDB: mongoDB,
logger: logger,
}
}
func (receiver *GoogleRepository) UpdateUser(ctx context.Context, user *models.GoogleUserInformation) (*models.GoogleUserInformation, error) {
var googleUserInformation models.GoogleUserInformation
updateOptions := options.FindOneAndUpdate().SetUpsert(true).SetReturnDocument(options.After)
filter := bson.D{{Key: "sub", Value: user.Subject}}
update := bson.D{{Key: "$set", Value: user}}
if err := receiver.mongoDB.FindOneAndUpdate(ctx, filter, update, updateOptions).Decode(&googleUserInformation); err != nil {
receiver.logger.Errorf("failed decode google user information: %v", err)
return nil, errors.ErrDecodeRecord
}
return &googleUserInformation, nil
}
func (receiver *GoogleRepository) FindUserBySubject(_ context.Context, _ string) (*models.GoogleUserInformation, error) {
return nil, errors.ErrMethodNotImplemented
}
func (receiver *GoogleRepository) InsertUser(_ context.Context, _ *models.GoogleUserInformation) (*models.GoogleUserInformation, error) {
return nil, errors.ErrMethodNotImplemented
}

@ -6,32 +6,43 @@ import (
"net/http"
"time"
"github.com/brpaz/echozap"
oapiMiddleware "github.com/deepmap/oapi-codegen/pkg/middleware"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/openapi3filter"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/sirupsen/logrus"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/initialize"
"go.uber.org/zap"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/swagger"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/utils"
)
type Deps struct {
Logger *zap.Logger
Swagger *openapi3.T
JWTUtil *utils.JWT[models.AuthJWTDecoded]
}
type HTTP struct {
logger *logrus.Entry
logger *zap.Logger
server *http.Server
echo *echo.Echo
}
func New(logger *logrus.Logger, swagger *openapi3.T) *HTTP {
func New(deps *Deps) *HTTP {
echo := echo.New()
echo.Use(middleware.Logger())
echo.Use(echozap.ZapLogger(deps.Logger))
echo.Use(middleware.Recover())
echo.Use(oapiMiddleware.OapiRequestValidator(swagger))
echo.Use(oapiMiddleware.OapiRequestValidator(deps.Swagger))
echo.Use(oapiMiddleware.OapiRequestValidatorWithOptions(deps.Swagger, &oapiMiddleware.Options{
Options: openapi3filter.Options{AuthenticationFunc: utils.NewAuthenticator(deps.JWTUtil)},
}))
return &HTTP{
echo: echo,
logger: logrus.NewEntry(logger),
logger: deps.Logger,
server: &http.Server{
Handler: echo,
MaxHeaderBytes: 1 << 20,
@ -51,15 +62,15 @@ func (receiver *HTTP) Run(config *models.HTTPConfiguration) {
connectionString := fmt.Sprintf("%s:%s", config.Host, config.Port)
startServerMessage := fmt.Sprintf("starting http server on %s", connectionString)
receiver.logger.Infoln(startServerMessage)
receiver.logger.Info(startServerMessage)
if err := receiver.Listen(connectionString); err != nil && err != http.ErrServerClosed {
receiver.logger.Infoln("http listen error: ", err)
receiver.logger.Error("http listen error: ", zap.Error(err))
}
}
func (receiver *HTTP) Stop(ctx context.Context) error {
receiver.logger.Infoln("shutting down server ...")
receiver.logger.Info("shutting down server...")
if err := receiver.server.Shutdown(ctx); err != nil {
return fmt.Errorf("failed to shutdown server: %w", err)
@ -68,8 +79,8 @@ func (receiver *HTTP) Stop(ctx context.Context) error {
return nil
}
func (receiver *HTTP) Register(controllers *initialize.Controllers) *HTTP {
swagger.RegisterHandlers(receiver.echo, nil)
func (receiver *HTTP) Register(api *swagger.API) *HTTP {
swagger.RegisterHandlers(receiver.echo, api)
return receiver
}

@ -0,0 +1,157 @@
package account
import (
"context"
"fmt"
"go.uber.org/zap"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/errors"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models"
)
type accountRepository interface {
FindByUserID(ctx context.Context, id string) (*models.Account, errors.Error)
FindMany(ctx context.Context, page, limit int64) ([]models.Account, errors.Error)
Insert(ctx context.Context, account *models.Account) (*models.Account, errors.Error)
Remove(ctx context.Context, id string) (*models.Account, errors.Error)
Delete(ctx context.Context, id string) (*models.Account, errors.Error)
}
type authClient interface {
GetUser(ctx context.Context, userID string) (*models.User, errors.Error)
}
type Deps struct {
Logger *zap.Logger
Repository accountRepository
AuthClient authClient
}
type Service struct {
logger *zap.Logger
repository accountRepository
authClient authClient
}
func New(deps *Deps) *Service {
return &Service{
logger: deps.Logger,
repository: deps.Repository,
authClient: deps.AuthClient,
}
}
func (receiver *Service) GetAccountByUserID(ctx context.Context, userID string) (*models.Account, errors.Error) {
account, err := receiver.repository.FindByUserID(ctx, userID)
if err != nil {
receiver.logger.Error("failed to get account by id on <GetAccountByUserID> of <AccountService>",
zap.Error(err.Extract()),
zap.String("userID", userID),
)
return nil, err
}
return account, nil
}
func (receiver *Service) GetAccountsList(ctx context.Context, pagination *models.Pagination) ([]models.Account, errors.Error) {
if pagination == nil {
return nil, errors.New(
fmt.Errorf("pagination is nil on <GetAccountsList> of <AccountService>: %w", errors.ErrInternalError),
errors.ErrInternalError,
)
}
accounts, err := receiver.repository.FindMany(ctx, pagination.Page, pagination.Limit)
if err != nil {
receiver.logger.Error("failed to get accounts list on <GetAccountsList> of <AccountService>",
zap.Error(err.Extract()),
zap.Int64("page", pagination.Page),
zap.Int64("limit", pagination.Limit),
)
return nil, err
}
return accounts, nil
}
func (receiver *Service) CreateAccount(ctx context.Context, account *models.Account) (*models.Account, errors.Error) {
accounts, err := receiver.repository.Insert(ctx, account)
if err != nil {
receiver.logger.Error("failed to create account on <CreateAccount> of <AccountService>",
zap.Error(err.Extract()),
zap.Any("account", account),
)
return nil, err
}
return accounts, nil
}
/*
CreateAccountByUserID
TODO: Дополнить проверку на дефолтное значение Currency
*/
func (receiver *Service) CreateAccountByUserID(ctx context.Context, userID string) (*models.Account, errors.Error) {
user, err := receiver.authClient.GetUser(ctx, userID)
if err != nil {
receiver.logger.Error("failed to get user on <CreateAccountByUserID> of <AccountService>",
zap.Error(err.Extract()),
zap.String("userID", userID),
)
return nil, err
}
account, err := receiver.repository.Insert(ctx, &models.Account{
UserID: user.ID,
Cart: make([]string, 0),
Wallet: models.Wallet{
Cash: 0,
Money: 0,
Currency: "",
},
})
if err != nil {
receiver.logger.Error("failed to create account on <CreateAccountByUserID> of <AccountService>",
zap.Error(err.Extract()),
zap.String("userID", userID),
)
return nil, err
}
return account, nil
}
func (receiver *Service) RemoveAccount(ctx context.Context, userID string) (*models.Account, errors.Error) {
account, err := receiver.repository.Remove(ctx, userID)
if err != nil {
receiver.logger.Error("failed to remove account on <RemoveAccount> of <AccountService>",
zap.Error(err.Extract()),
zap.String("userID", userID),
)
return nil, err
}
return account, nil
}
func (receiver *Service) DeleteAccount(ctx context.Context, userID string) (*models.Account, errors.Error) {
account, err := receiver.repository.Delete(ctx, userID)
if err != nil {
receiver.logger.Error("failed to delete account on <DeleteAccount> of <AccountService>",
zap.Error(err.Extract()),
zap.String("userID", userID),
)
return nil, err
}
return account, nil
}

@ -30,11 +30,11 @@ type ServerInterface interface {
// (POST /account)
AddAccount(ctx echo.Context) error
// Удалить аккаунт по айди
// (DELETE /account/{accountId})
DeleteDirectAccount(ctx echo.Context, accountId string) error
// Получить аккаунт по ID
// (GET /account/{accountId})
GetDirectAccount(ctx echo.Context, accountId string) error
// (DELETE /account/{userId})
DeleteDirectAccount(ctx echo.Context, userId string) error
// Получить аккаунт по ID пользователя системы единой авторизации
// (GET /account/{userId})
GetDirectAccount(ctx echo.Context, userId string) error
// списко аккаунтов с пагинацией
// (GET /accounts)
PaginationAccounts(ctx echo.Context, params PaginationAccountsParams) error
@ -111,36 +111,36 @@ func (w *ServerInterfaceWrapper) AddAccount(ctx echo.Context) error {
// DeleteDirectAccount converts echo context to params.
func (w *ServerInterfaceWrapper) DeleteDirectAccount(ctx echo.Context) error {
var err error
// ------------- Path parameter "accountId" -------------
var accountId string
// ------------- Path parameter "userId" -------------
var userId string
err = runtime.BindStyledParameterWithLocation("simple", false, "accountId", runtime.ParamLocationPath, ctx.Param("accountId"), &accountId)
err = runtime.BindStyledParameterWithLocation("simple", false, "userId", runtime.ParamLocationPath, ctx.Param("userId"), &userId)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter accountId: %s", err))
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter userId: %s", err))
}
ctx.Set(BearerScopes, []string{""})
// Invoke the callback with all the unmarshalled arguments
err = w.Handler.DeleteDirectAccount(ctx, accountId)
err = w.Handler.DeleteDirectAccount(ctx, userId)
return err
}
// GetDirectAccount converts echo context to params.
func (w *ServerInterfaceWrapper) GetDirectAccount(ctx echo.Context) error {
var err error
// ------------- Path parameter "accountId" -------------
var accountId string
// ------------- Path parameter "userId" -------------
var userId string
err = runtime.BindStyledParameterWithLocation("simple", false, "accountId", runtime.ParamLocationPath, ctx.Param("accountId"), &accountId)
err = runtime.BindStyledParameterWithLocation("simple", false, "userId", runtime.ParamLocationPath, ctx.Param("userId"), &userId)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter accountId: %s", err))
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter userId: %s", err))
}
ctx.Set(BearerScopes, []string{""})
// Invoke the callback with all the unmarshalled arguments
err = w.Handler.GetDirectAccount(ctx, accountId)
err = w.Handler.GetDirectAccount(ctx, userId)
return err
}
@ -224,6 +224,8 @@ func (w *ServerInterfaceWrapper) GetCurrencies(ctx echo.Context) error {
func (w *ServerInterfaceWrapper) UpdateCurrencies(ctx echo.Context) error {
var err error
ctx.Set(BearerScopes, []string{""})
// Invoke the callback with all the unmarshalled arguments
err = w.Handler.UpdateCurrencies(ctx)
return err
@ -237,18 +239,18 @@ func (w *ServerInterfaceWrapper) GetHistory(ctx echo.Context) error {
// Parameter object where we will unmarshal all parameters from the context
var params GetHistoryParams
// ------------- Optional query parameter "p" -------------
// ------------- Optional query parameter "page" -------------
err = runtime.BindQueryParameter("form", false, false, "p", ctx.QueryParams(), &params.P)
err = runtime.BindQueryParameter("form", false, false, "page", ctx.QueryParams(), &params.Page)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter p: %s", err))
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter page: %s", err))
}
// ------------- Optional query parameter "s" -------------
// ------------- Optional query parameter "limit" -------------
err = runtime.BindQueryParameter("form", false, false, "s", ctx.QueryParams(), &params.S)
err = runtime.BindQueryParameter("form", false, false, "limit", ctx.QueryParams(), &params.Limit)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter s: %s", err))
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter limit: %s", err))
}
// Invoke the callback with all the unmarshalled arguments
@ -340,8 +342,8 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
router.DELETE(baseURL+"/account", wrapper.DeleteAccount)
router.GET(baseURL+"/account", wrapper.GetAccount)
router.POST(baseURL+"/account", wrapper.AddAccount)
router.DELETE(baseURL+"/account/:accountId", wrapper.DeleteDirectAccount)
router.GET(baseURL+"/account/:accountId", wrapper.GetDirectAccount)
router.DELETE(baseURL+"/account/:userId", wrapper.DeleteDirectAccount)
router.GET(baseURL+"/account/:userId", wrapper.GetDirectAccount)
router.GET(baseURL+"/accounts", wrapper.PaginationAccounts)
router.DELETE(baseURL+"/cart", wrapper.RemoveFromCart)
router.PATCH(baseURL+"/cart", wrapper.Add2cart)
@ -359,64 +361,66 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
// Base64 encoded, gzipped, json marshaled Swagger object
var swaggerSpec = []string{
"H4sIAAAAAAAC/+xbW28bxxX+K4NtHxKUpkhHiRO+OXbSukCBIKmRB0doVuRQ2oTcZXaXSVSDgCi6dgIp",
"Vly0SB5qu2kK9LE0JVor3vQXzvyj4pyZ3Z29UKYuseUmLzZFzs6cOZfvXPe2UXWaLcfmtu8ZlduGV13n",
"TZM+Xq1Wnbbt48eW67S461ucfviTVcP/+Jdms9XgRsV4s3SlXq5fubJarb9ZrtauvPXW8mtvlcplo2D4",
"Gy1c4fmuZa8ZnYJRNV0/8fSt4x6f89NlY6VgWD5vEj2ZM9QXpuuaG3Smy02f167SwXXHbZq+UTFqps8v",
"+VaT55FZ4w3u8+Q962bD49HaVcdpcNPWFp9k/3ardlKS2h53b5yB81+YjQan837t8rpRMX61FMt+SQl+",
"6UO5qtMpGC7/rG25yIRbJPOIBCXFaMuYXTqv9UuuRPQ4q5/wqo/0vOO6jpvVrib3PHONJ++JBzPb8Vnd",
"adu1vNt5vum3vWtOLfnkcmm5EPPXsv03luOnLdvna9zNXLaKuxQiSvKI/53l+Y67kSW/6jSb3E6quCF2",
"mfhGbMGMiS7swxDG0IcxgyOY4bcwET0m7tGC5cu5VnPxNPgsIOC1JSMrt40a96qu1fItxzYqBvyXmAIj",
"6DOYwpDBBGawJ3oMjsQmDGGffh7DDJ5CILbEToHhYhhBgKufiB7si57YYuIOBDAWO2JLbIpt2pVBILrI",
"ZLEJAQQFyf9vIglER6B09qAv7jMYhIJT5w7lqpncCM8eQx//EDuM/ttE6lGcXfaJ59hFBgE8wZX7MBa7",
"ktinsI+3QnXYggCO8LIH0IcjJBACBjOYQh91ZQAzsVs0CjGfbxu+6Vr1unc65CwbK53CPMS8bXC73cSN",
"vXa1yj2v3m68Z26QPqOOVBuWzWvxN6gaTtuPv1htb1yTyOC1Vz0l1nfsGppQrCl5m794gOzkWPmHEWSm",
"jNz01hPnlEtXSuUFgKZgVNuuy+3qRpLM92++nXejpmPzjRwrQQiZih3YQ2WRqiLuwVBsoRYPUMlmcARD",
"OER1E38pMvgPafkTsgRUf9TnIaq76OKz4oHYwpXK6AYwgwOYQkDqOpbqO8TfYA8VWv6G2DVCg4N+Udrb",
"CNVXbIoeDGEiuqTvqM37MIUZHOLGfRiL+2ILhoVQzaXRIvX4+xStl+xwSucdFpgiRBnpUDxQVCqggKd4",
"mm4jJI3iG4sBf0roiE682nYtf+MDdIlS4G9z0+XkrFbp07vhxr//8I9kGrp43rF97jJ/nTPf+ZTb7AvL",
"X6c/P5bbVNjHrOXyuvVlgfHiWpF9pPZn5mq1xsuXX1t+/SMDrZ6cMkG1PD+idt33W0YHibXsupOjIY/g",
"iYZMJKoBKUhXfiLuShQLEGhQFwYEP312KUZCuaIPA4Q+koPi+1jswAHMUKAoTBQqE3dED6YwEndROVC2",
"SAJpo9iEAwhgivoXyG++oqd2UIJFwhKfLOFa2/OdJnfZpQRZIX6KHtGmUSRBdYykwZSInUBgFIzPuetJ",
"ZpSLpWIJzclpcdtsWUbFeK1YKiIGtEx/nQS8ZMbRrnSGOUzFi09gKO5BX6or+h20sB6ezKQpwFg8QO6K",
"bVJKhA0TN0BkMq7T1mFkjVGH13JsTyrZ5VJJRhC2ryIIs9VqWFV6fAm9SRyhPyuMC48gHUleQ/REl6z/",
"K7LKYUR3LOHEvaCPvFsulc+NOBn55ZAGD2GIso2ctFIxmCYM06jcik3y1kpnBf1Ns2liPGZEtwmUUybc",
"09RebCMSJa6I+meukUsN9QDd5BrPC1G+J/YpE0CoI6vajYD1AIGUQG/IxH04ICXuUyjRVREIomOvyOBb",
"OIR9CEgSAYwSy+URvVjZAjhghOIjuke/IO2IWCXvpCAd74+ALqkayNhiT4VNA3ycmW1/3XGtP5P0Mlr6",
"W+5fMBXVuKGU9ELoJJKw/BxI+BepqwwZ58DvLjlFsbW4ocDjNE8Z7TUSPfE1eno8LIUEmobOsZmW4+UZ",
"zQ8UUSjFZCTVQY4hRjAfHVNAj99Fc2YE7hEbhuLrKBJ4IrZRuTOKfLVWu2hY2w0Z8f+AtZpYKcyYJ9Zc",
"XekUIq+7dFt9uFHrHOuB/4EqjthKgidkl1AbwESeO8BgpyspoX8nEjRlqDHDhFIGsvsYN1IoMSlo0YXm",
"CTORxRx3ft1yeVVDzJbpmk3uc9cj5iWvcON6VuQYxRkVCkaMgmGbFPJFHDH04oTvtnlBE306iVl5MXoO",
"P76UMcXzwu+H5MGT+DUHxk9gfD8mAp00kB5J+KYA40TxzU9sY2lXjl9JOJyKbcxAZ0mrG+aFJycyOav2",
"szC5bIz0i9Gdv9HFQdN8s7tx/VkOjxQl3/zScT9JOaC4YZQW6AxNkeo/PaqEHEg7olqLqkZSnCHuwpD9",
"hjEyZozthrJGhNhxj2pAmB3NcrbPGN975ppl059Xw6s8w/zgoUqbN1M0iW2q7fSRl/T/LhNdVqZKTqtB",
"lXxVsCZb/azN3Y3YWFvmGjd0u6zxutlu+EalnFfnSVNFZBzMoWtBEhpW0/Ln0FAq5VBxVqhIViF1XYqa",
"YQuhSbZF5ju+2XjPXJM7x5W08kI1s9MkcBmm52kf2WWc2oemMMrVVdQeOKJcV+rTXXIih3ONMexFxuFm",
"Utff503nc/6u6zRVVTul53k6YR3vPE5VmX5hHkaL4WayAJEs5v3c/AnBFYVUQ5jGruWQPiJd4s7J4zex",
"C0OY5LFX3zbWYdJaSrVNv7qe40EepqM28nzUmRpQOKYaZ6rjmHsNtE4qA0jfIMtcefn1ZdUKRpXnnv+2",
"U9s4A6RZ59i+6ci27guwm78h+7Ua9SxsykSi7f1iOWexHI3BofWkGXys7YTgv9QyZQs/t2h1icEj8lcD",
"CGQLi+HO1G+aUpNMbMKIAggIqEIFMwqnqAYrdl6h6EI1iveoHkzt7KdyK2r+k1XuhfUTdGd3YCR6r+rF",
"XYqMtC4ZlcPUE9Q2Vg3p7aj3N6UmxVRsy4pT1EXBJSoL20/paBBVsOm6YyoABirby8a4eVH0R/YlBn8l",
"8sL4NS7e6Q3EQboDNGRxr4nufI9YdEk2rsKGjwq4BwuwXjJmX8aWKkSWAd9M557k1Yj6oFuyZZNqhqFq",
"ZVgVTgGkWEX3/3vOfad6Ly2+X0GPisJuBQoJxjRCILujMzhSBPcYzRUkenXRjw/gKYzw3IAplJed1B3q",
"mBZV+0ETTLzvtiIkwgjFZ7EtJaxGIGQGNQmrA9Cn+z4i2qmPGPFUg7ls+hBOCJyPw4h7/tkym6rpKdJD",
"f0dN8DhfCptEsY300rwZxWClytLaClQAvff8crqsRLxOcBNfsH8Rg76FXYUmqNjmY+3McwxyQkMpmErS",
"MxWoa/GqM4pMm4SUYyA3P7huFIxrV6+fZMrxtDlYlFH1temMVN6VA1N6TWImETIc2pBVvHgvjcnh7AtF",
"r+0cxt6kqZ8Ub0+HE+fF1s5LI170UrImO362gCMbTu5607RVa5jX0lqg7Y9RV6Y2dRo9QINbj2cp51lb",
"OG75gkpN+TWe0iJ1Jvjn2etM3ourMbm86ri1xUtMoaB++hLTIvVvNfyxTW768OVyXNnuPM1v7MnBUO1a",
"NCcqdsPiL5md6GbrYxPNAkOT0zv22dR+PbK604LwQqry00Y92jHHqlBfJkA9GpyjwUdxV2ZFiu2oPq+f",
"I2Hz1eeR+Iqmhgmzw3HNNGUQnESZjrkXZVSJyehcRUGkjl8hyO1dXGLwb9pjEk+BJlJUBqPjY21cktd0",
"nMiJVZlCPRHfUEw+gYBSkMd6xwRPjKYd6WMmZdJOxAPjnEvOqKazrsScVU/cl6WMfZnCxwNXFFzOGY3M",
"pEHvS3v6A833nr9Ty3MiNLN8XGla9yvPwbM0LPvTZLFv3fdbXmVpac1x1hq86LYXS5mOs2rZr05oYJSX",
"Pz97/kGfY9W1L87UYcyoIkJmL3onMO3v9MtNk5ODWqslVPEgq+CauSsDP666/DiacgyPldW9TM25ouoD",
"WzIGpBpaoKrQ/bwKNCFwPCHeT0eU/ZS1q4QuGh5AOmhIMmnv1VW3/gpVM/ZlPScqDUU1lqkad8OvYkM+",
"oKLfEIavqoOlsQ9UUS/QiMWdUkPNTPZmBzCToKGtLeqLH2DoPKVaZVj3EnfpFaGZLK2oaJ50OVHESMy+",
"qZltQpwpJbiHhLZRtSma9VY1I5LCfeoGH4bLCAHT78VIkFEFL4lDJJYx6c9mNPIVvkcgX0YISKjDeAqV",
"LjWVrxlk8PDaummvhXnfxrlVh/JftpAJ2AXuH6TDEtFVEzV6CocKkFI5GeCWnttAatZgpzIPTVjtz6rV",
"8Tg3ANhRnNF6ICdA+O9l/khOX1Zmj4GdOWCeP5VLWJ7upCTdh8qtDlSgEeUiI4YBBZOh6aZ0KxkXEOJk",
"WCxXP9BbQdqrQHmXSJWN21GsdE7IkH2DKzfoKZwEQ87/FbSLURCeyXfCptTMiTQg0eDop1zaRYai54ED",
"3yY7Zmew/u9kgC/bSmHEk3r9L8X7HAgg3ZJfZlAgPaqdnIY0cmpaqpklenAUBl6aX9JyjtB2cvbQegpk",
"8FGa4vrzlus3VMvVBXMe0DJasRs/ECa0nZXO/wIAAP//vkWxS/xAAAA=",
"H4sIAAAAAAAC/+xbW28bxxX+K4NtHxKUpihHiRO9OXbSpkCBIKmRB1toVuRQ2oTcZXaXSVRDgCg6VgIp",
"UlykSFDUdpMU6GMpirRWvOkvnPlHxTkzuzt7oe6R5SYvNkXOzpw5l+9c975RduoNx+a27xnz9w2vvMzr",
"Jn28WS47TdvHjw3XaXDXtzj98Bergv/xz816o8aNeeP10o3qbPXGjcVy9fXZcuXGG2/MvfJGaXbWKBj+",
"SgNXeL5r2UvGasEom66fePruUY9P+em6sVAwLJ/XiZ7MGeoL03XNFTrT5abPKzfp4Krj1k3fmDcqps+v",
"+Vad55FZ4TXu8+Q9q2bN49HaRcepcdPWFp9m/2ajclqSmh533zkH5z8zazVO5/3W5VVj3vjNTCz7GSX4",
"mQ/kqtXVguHyT5qWi0y4SzKPSFBSjLaM2aXzWr/kQkSPs/gRL/tIz1uu67hZ7apzzzOXePKeeDCzHZ9V",
"naZdybud55t+07vlVJJPzpXmCjF/Ldt/bS5+2rJ9vsTdzGXLuEshoiSP+D9Ynu+4K1nyy069zu2kihti",
"h4mvxTpMmGhBD/owhA4MGRzCBL+FkWgzsUEL5q7nWs3V0+DzgIDXlIycv29UuFd2rYZvObYxb8B/iSkw",
"gA6DMfQZjGACe6LN4FCsQR969PMQJvAMArEutgoMF8MAAly9K9rQE22xzsQDCGAotsS6WBObtCuDQLSQ",
"yWINAggKkv9fRxKIjkDp7EFHbDPohoJj6uC+XDaRO+HhQ+jgH2KL0X9rSD7Ks8U+8hy7yBh8D7u4tAdD",
"sSPJfQY9vBcqxDoEcIjX3YcOHCKJEDCYwBg6qC1dmIidolGIOX3f8E3Xqla9s2HnrLGwWpiGmfcNbjfr",
"uLHXLJe551WbtXfNFdJo1JJyzbJ5Jf4GlcNp+vEXi82VWxIbvOaipwT7ll1BI4p1JW/z5w+RKRRIIh6t",
"PR7oChEAxFqehx8fRGCcgg/TW84xix9EG0Ywgg6rOzZfQWXsk7J2UWHFIxijvqB2kdqsQx91UHwFARww",
"+rgmWroSzZZulGZPgIwFo9x0XW6XV3Ko+jH3GAZdRDexLdbFpn6m8d6dN/OkRlfK2f5b6MNYbMEeGoQ0",
"B7EBfbGOttpFQ5ogH+AATUp8UWTwH7LlXbJ3NPKIT6KFz4pHYh1XKmjpwgT2YQwBmeRQmmgff4M9BoH6",
"DRF6gLACnaJElQGaqFgTbejDSLTIptFiezCGCTIiuj/0C6EpS2hC6vH3MWIUCXBM5x0UmCJEQVFfPFJU",
"KjiEZ3haRoTF187g3kKJFqS6hRLI6ilCNS83XctfeR/jA6mjb3LT5eS5F+nT2+H5f/zgz2QiuhTfsn3u",
"Mn+ZM9/5mNvsM8tfpj8/lNvMsw9Zw+VV6/MC48WlIrun9mfmYrnCZ6+/MvfqPQMBkCIU8lvy/IjaZd9v",
"GKtIrGVXnRxFegK7GkqTRLukRy35iYQgET1AzEWV6RISd9i12C3IFR3oohsgcSnxDMUW7MME5U6mNxTb",
"TDwQbRjDQDxEHUIVQBJIacUa7EMAY1TTQH7zJT21hYIuEqz6ZDC3mp7v1LnLriXICl2JaBNtGkXSvwyR",
"NBgTsSMIjILxKXc9yYzZYqlYQqtzGtw2G5Yxb7xSLBURDhumv0wCnjHj0F8CXg5T8eIj6IsN6EitRieM",
"htjGk5m0GBhKbBKbpLuIdCZugCBt3KatwzQDddRrOLYnlex6qSTDKdtX4ZTZaNSsMj0+g541TleOi2nD",
"I0hHktcQbdEikPiSjLcf0R1LOHEv6CDv5kqzF0acDINzSIPH0EfZRhGLUjEYJwzTmL8bm+TdhdUFdDz1",
"uonBqRHdJlABCsGjpvZiEwErcUXUP3OJootQDzBiWOJ58dr3xD5lAoiIZFU7Ef7uI94SNvaZ2IZ9UuIO",
"xVUtFY4hiLaLDL6BA+hBQJIIYJBYLo9ox8oWwD4jsB/QPToFaUfEKnmn0EP2oIO4L6nqyjBrT8WQXXyc",
"mU1/2XGtv5L0Mlr6e+5fMRXVuKGU9EroJJIwdwkk/EjqKqPnKfBLcVBfrJ/cUOBpmqd6FNWXh6WQQNPQ",
"KTbTcLw8o/mBAg+lmIyk2s0xxAjmo2MKGBi00JwZgXvEhr74KgoYdsUmKndGkW9WKlcNa1shI/4fsFYT",
"K4UZ08SaqyurhcjrztyXScfqke73n6jfCKwkdYJ1ibMBjOShXYx0WpIM+nckEVPGGRNMrWWw28PYkuKI",
"UUELLTQ3mAkrpvjy25bLyxpcNkzXrHOfux5xLnmFd25n5Y0hnDFPkYhRMGyzHhZ/KAeLg1jfbfKCJvR0",
"JrfwfDQcfnoho4nLQu7H5LuTyDUFwE9hdj8lQpw0hB5K4KbQ4lSRzc9sYGknjl9JIByLTUxRJ0mT6+cF",
"Jr/a27Gh0a8Wd/EWF8dK020OtW1acJYI/TcZWUgQFk+SHOtQ/hwc5zNJ6fLtOJ06kMYEFHoM0soxQZum",
"SlObai770iCJMFXcpVBFPIQ++x1jhAoYHvZlNQpBaIOqTZhgTXK2z1jxu+aSZdOfN8OrHGPH8Fhl3msp",
"msQmVZE6KBdVDhQtNks1o0aNOiOqAUBG/0mTuyux1TfMJW7oNl7hVbNZ84352byKUpoqImN/Cl0nJKFm",
"1S1/Cg2lUg4V54WdZO1V16WouXgiZMq2HH3HN2vvmkty57hml8vLTNntLDlghul52kc2HlcHQlMY5Ooq",
"ag8cUros9ekheaODqcYY9nbjoDWp6+/xuvMpf9t16qpHkNLzPJ2wjnZEZ6rzPzdvpQWDE1nDSNYDf2m+",
"ieCKYrM+jGM3dUAfkS7x4PSBoNhBv5LHXn3bWIdJaylbN/1yXvPlcTr8Iy9Kjb4uxXWqEak6uLnXQOuk",
"SoL0DbJSlpeiX1etdVR57vlvOpWVc0CadYHNsJz2gFz1HCzpWxSIVviehA2hSNjtX23pPLakMTi0pzSD",
"j7Sm0B3MNEw5JJFbCbvG4Al5sC4Esn3GcGfqdY2pQSfWYEAhBQRU9oIJBVhU2BVb7CUKOFQrfo+qzDQx",
"8EzuRfMVZKh7YVUGPdwDGIj2y3rJWPZO4xYdFdnUE9SXVy3/zajxOKbWx1hsyjpW1JvBJSrD66WUNIjq",
"4nTfIZUVA5VJZkPovND5nn2Nwd+IvDCkjUuCeveym+4r9VncwaI7bxCLrsl2WNhGUvF89yS8J8b0ZLip",
"omYZA0507kleDagJuy4bQakWG+pWhlXhnEWKVXT/v+fcd6x36OL7FZiCaNlwpebflxDALo1oQKAF+vjz",
"PZtNCeST+2+LryAQXyCoS27QOsW3qPd7zyZyn2T5q6mU5FzF8gjsEqxRT2+oZCmIGKqBXDadCOcvLsaB",
"xBMV2WKCqhSuy8Qy9H/Ufk+yNWkgbUZ9e6khm9H8DkGVqnRrK1D6iSGCM7swdZUr5MYSUT0hUHztzlUM",
"DU/sPjTxxTAQ62yes5BDCUrtVCqfKXjdiledU2Ta/KkcS7nz/m2jYNy6efs0s6VnzdSivKujIUYqO9Me",
"jLq3WuViIkEzHCKRRcN4L43J4bgHxbjNHMbeofGlFG/Phh4XxdbVF0a86LhkCXh4vIAvyYbvmLbqafPK",
"qaw2ugoGfZli2VlUDm17OR6WnWbY4TztC1n7gn+98LUvl5cdt3Ly0lcor5+/9HWSGr+aa9mkcOHgxXKV",
"2cEDGk3Zk+O/2rVoGljshEVpsj6MH9N1u5FmiKHl6cMI2ZLDcmR8Z4X9E6nKzxtnacccqUIdmYW1aSaQ",
"Rj/FQ5maKbaj+rx6gYRNV58nYS5CI05qYDVNGQSnUaYj7kXpSWICPldRELDjV0VyeyrXGPyb9hjFc7Cp",
"pGZwdMwv855sV3UkZ3ZlnrUrvqbcYAQBpUJP9U4OnhgNctLH9OSmfiIeSDPAmzBUuYacyo3XtBMjZG2x",
"LQsqPVlHiGfJKJydMvWZScfek/b0J5qvPc63/SMv8YxyXCPfb6gJ3ukVct2NXIIjqVn2x8ma47LvN7z5",
"mZklx1mq8aLbzM3UTuUHZP89oXAxny7NfH/QJ3J1ZYtrDjBMVBxOYcnfpUsE+R2fUKODrD5r1q3s+agi",
"99NoXjM8VpYUM6Xv+aio0ZVjFzS0uRYaQrYQToAbl0U66TiykzJulTFGwxBIB417Js27vOhW2Us0YdWT",
"RaSoHoVwM1ZvEMmXbDbC8s+hmtHCX/ovq5OjVynUCLj2AkM7M5/NZI94Al0JEtraIkMbVosfYcQ8pgpp",
"WGwTD+nVr4ks6ah8gZQ5UTxJjPGp8XNCmDGl0AeErlGJKxpbZ1L0JIZt6kofhMsI8dLvO8mAWVXZZOxK",
"chmSAq1F02vhmxPy9YuApNqPB2rpUmP5YkUG/24tm/ZSmFmuXFhVSn8nJQYameIdXQSKM5Mr1M1Ihyei",
"pUaHOol3aTKqKAPd0qXN3GYteSwz4IQ5/6IaL09zA4EtxRmtI3MK6P9e5pKUQMpy/BFwNAXl8wePCeTT",
"fZ2kX1E51r5KpqOcZMAw0mAyRF2T/ibjG0L8DCv36gd6P0orjOddIlXGbkYx0wUhhnqvTnt7KjcaKpwG",
"Wy74hUMVymmvZ13xkvVEvkU3pg5UpCmJrkxHl/UjGFxlyLoMvPgm2eY7B0p8J4tdshcWhkypFyZTvM+B",
"Cgq95ZcZtEhPrauMw4ymnjJPqA6ZaMNhGLkl3wUNk5ZQxXP20LoeBAxRnuP605brN1TL1QVzHtAyYLET",
"PxAmwKsLq/8LAAD//9ctKcoUQwAA",
}
// GetSwagger returns the content of the embedded swagger specification file

@ -1,4 +1,97 @@
package swagger
import (
"net/http"
"github.com/labstack/echo/v4"
)
//go:generate oapi-codegen --config api.yaml ../../openapi.yaml
//go:generate oapi-codegen --config models.yaml ../../openapi.yaml
type AccountController interface {
RemoveAccount(ctx echo.Context) error
GetAccount(ctx echo.Context) error
CreateAccount(ctx echo.Context) error
RemoveDirectAccount(ctx echo.Context, userId string) error
GetDirectAccount(ctx echo.Context, userId string) error
GetAccounts(ctx echo.Context, params PaginationAccountsParams) error
}
type Deps struct {
AccountController AccountController
}
type API struct {
accountController AccountController
}
func New(deps *Deps) *API {
return &API{
accountController: deps.AccountController,
}
}
func (receiver *API) DeleteAccount(ctx echo.Context) error {
return receiver.accountController.RemoveAccount(ctx)
}
func (receiver *API) GetAccount(ctx echo.Context) error {
return receiver.accountController.GetAccount(ctx)
}
func (receiver *API) AddAccount(ctx echo.Context) error {
return receiver.accountController.CreateAccount(ctx)
}
func (receiver *API) DeleteDirectAccount(ctx echo.Context, accountId string) error {
return receiver.accountController.RemoveDirectAccount(ctx, accountId)
}
func (receiver *API) GetDirectAccount(ctx echo.Context, accountId string) error {
return receiver.accountController.GetDirectAccount(ctx, accountId)
}
func (receiver *API) PaginationAccounts(ctx echo.Context, params PaginationAccountsParams) error {
return receiver.accountController.GetAccounts(ctx, params)
}
func (receiver *API) RemoveFromCart(ctx echo.Context, params RemoveFromCartParams) error {
return ctx.String(http.StatusNotImplemented, "method not implemented")
}
func (receiver *API) Add2cart(ctx echo.Context) error {
return ctx.String(http.StatusNotImplemented, "method not implemented")
}
func (receiver *API) PayCart(ctx echo.Context) error {
return ctx.String(http.StatusNotImplemented, "method not implemented")
}
func (receiver *API) GetCurrencies(ctx echo.Context) error {
return ctx.String(http.StatusNotImplemented, "method not implemented")
}
func (receiver *API) UpdateCurrencies(ctx echo.Context) error {
return ctx.String(http.StatusNotImplemented, "method not implemented")
}
func (receiver *API) GetHistory(ctx echo.Context, params GetHistoryParams) error {
return ctx.String(http.StatusNotImplemented, "method not implemented")
}
func (receiver *API) Add2history(ctx echo.Context) error {
return ctx.String(http.StatusNotImplemented, "method not implemented")
}
func (receiver *API) RequestMoney(ctx echo.Context, params RequestMoneyParams) error {
return ctx.String(http.StatusNotImplemented, "method not implemented")
}
func (receiver *API) ChangeCurrency(ctx echo.Context) error {
return ctx.String(http.StatusNotImplemented, "method not implemented")
}
func (receiver *API) PutMoney(ctx echo.Context) error {
return ctx.String(http.StatusNotImplemented, "method not implemented")
}

@ -40,17 +40,17 @@ type Error struct {
// History defines model for History.
type History struct {
Comment *string `json:"comment,omitempty"`
CreatedAt *time.Time `json:"createdAt,omitempty"`
Deleted *bool `json:"deleted,omitempty"`
Comment string `json:"comment"`
CreatedAt time.Time `json:"createdAt"`
Deleted bool `json:"deleted"`
DeletedAt *time.Time `json:"deletedAt,omitempty"`
Id *string `json:"id,omitempty"`
Id string `json:"id"`
// Subject Я пока не могу предположить, какие будут фильтры по истории, поэтому предлагаю в это поле просто класть строку с json. ибо для каждого типа записи она своя.
Subject *string `json:"subject,omitempty"`
Type *HistoryType `json:"type,omitempty"`
UpdatedAt *time.Time `json:"updatedAt,omitempty"`
UserId *string `json:"userId,omitempty"`
// Subject Я пока не могу предположить, какие будут фильтры по истории, поэтому предлагаю в это поле просто класть строку с json. Ибо для каждого типа записи она своя.
Subject string `json:"subject"`
Type HistoryType `json:"type"`
UpdatedAt time.Time `json:"updatedAt"`
UserId string `json:"userId"`
}
// HistoryType defines model for History.Type.
@ -58,11 +58,14 @@ type HistoryType string
// Wallet defines model for Wallet.
type Wallet struct {
Cash *int64 `json:"cash,omitempty"`
Currency *string `json:"currency,omitempty"`
// Cash Сумма money переведённая на текущий курс
Cash int64 `json:"cash"`
// Money деньги на счету в копейках. Чтобы при перессчётах не возникало денег изниоткуда. фиксируемся к одной валюте, она будет внутренней, никому её не покажем
Money *int64 `json:"money,omitempty"`
// Currency Текущий курс валюты
Currency string `json:"currency"`
// Money Деньги на счету в копейках. Чтобы при перессчётах не возникало денег изниоткуда. фиксируемся к одной валюте, она будет внутренней, никому её не покажем
Money int64 `json:"money"`
}
// PaginationAccountsParams defines parameters for PaginationAccounts.
@ -81,13 +84,13 @@ type RemoveFromCartParams struct {
// Add2cartJSONBody defines parameters for Add2cart.
type Add2cartJSONBody struct {
Id *string `json:"id,omitempty"`
Id string `json:"id"`
}
// PayCartJSONBody defines parameters for PayCart.
type PayCartJSONBody struct {
// UserId ID для того, чтобы указать сервису оплаты, какой юзер оплатил
UserId *string `json:"userId,omitempty"`
UserId string `json:"userId"`
}
// UpdateCurrenciesJSONBody defines parameters for UpdateCurrencies.
@ -95,29 +98,29 @@ type UpdateCurrenciesJSONBody = []string
// GetHistoryParams defines parameters for GetHistory.
type GetHistoryParams struct {
// P Номер страницы, начиная с 1
P *int `form:"p,omitempty" json:"p,omitempty"`
// Page Номер страницы, начиная с 1
Page *int `form:"page,omitempty" json:"page,omitempty"`
// S Размер страницы
S *int `form:"s,omitempty" json:"s,omitempty"`
// Limit Размер страницы
Limit *int `form:"limit,omitempty" json:"limit,omitempty"`
}
// RequestMoneyParams defines parameters for RequestMoney.
type RequestMoneyParams struct {
// Cash Номер страницы, начиная с 1
// Cash Количество денег
Cash int `form:"cash" json:"cash"`
}
// ChangeCurrencyJSONBody defines parameters for ChangeCurrency.
type ChangeCurrencyJSONBody struct {
Currency *string `json:"currency,omitempty"`
Currency string `json:"currency"`
}
// PutMoneyJSONBody defines parameters for PutMoney.
type PutMoneyJSONBody struct {
Cash *int `json:"cash,omitempty"`
Currency *string `json:"currency,omitempty"`
UserId *string `json:"userId,omitempty"`
Cash int `json:"cash"`
Currency string `json:"currency"`
UserId string `json:"userId"`
}
// Add2cartJSONRequestBody defines body for Add2cart for application/json ContentType.

@ -0,0 +1,68 @@
package utils
import (
"context"
"fmt"
"net/http"
"strings"
"github.com/deepmap/oapi-codegen/pkg/middleware"
"github.com/getkin/kin-openapi/openapi3filter"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/errors"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models"
)
const (
prefix = "Bearer "
)
func NewAuthenticator(jwtUtil *JWT[models.AuthJWTDecoded]) openapi3filter.AuthenticationFunc {
return func(ctx context.Context, input *openapi3filter.AuthenticationInput) error {
if jwtUtil == nil {
return errors.New(fmt.Errorf("jwt util is nil"), errors.ErrInvalidArgs).Extract()
}
return authenticate(jwtUtil, ctx, input)
}
}
func authenticate(jwtUtil *JWT[models.AuthJWTDecoded], ctx context.Context, input *openapi3filter.AuthenticationInput) error {
if input.SecuritySchemeName != "BearerAuth" {
return fmt.Errorf("security scheme %s != 'BearerAuth'", input.SecuritySchemeName)
}
// Now, we need to get the JWS from the request, to match the request expectations
// against request contents.
jws, err := parseJWSFromRequest(input.RequestValidationInput.Request)
if err != nil {
return err.Extract()
}
// if the JWS is valid, we have a JWT, which will contain a bunch of claims.
token, validateErr := jwtUtil.Validate(jws)
if validateErr != nil {
return validateErr
}
// Set the property on the echo context so the handler is able to
// access the claims data we generate in here.
echoCtx := middleware.GetEchoContext(ctx)
echoCtx.Set(models.AuthJWTDecodedUserIDKey, token.UserID)
return nil
}
// extracts a JWS string from an Authorization: Bearer <jws> header
func parseJWSFromRequest(request *http.Request) (string, errors.Error) {
header := request.Header.Get("Authorization")
if header == "" || !strings.HasPrefix(header, prefix) {
return "", errors.New(
fmt.Errorf("failed to parse jws from request header: %s", header),
errors.ErrNoAccess,
)
}
return strings.TrimPrefix(header, prefix), nil
}

13
internal/utils/bind.go Normal file

@ -0,0 +1,13 @@
package utils
import "github.com/labstack/echo/v4"
func EchoBind[T any](ctx echo.Context) (*T, error) {
item := new(T)
if err := ctx.Bind(item); err != nil {
return nil, err
}
return item, nil
}

@ -7,16 +7,17 @@ import (
)
var clientErrors = map[int]error{
http.StatusInternalServerError: errors.ErrInvalidReturnValue,
http.StatusInternalServerError: errors.ErrInternalError,
http.StatusBadRequest: errors.ErrInvalidArgs,
http.StatusNotImplemented: errors.ErrMethodNotImplemented,
http.StatusNotFound: errors.ErrNoServerItem,
http.StatusNotFound: errors.ErrNotFound,
http.StatusForbidden: errors.ErrNoAccess,
}
func DetermineClientErrorResponse(statusCode int) error {
err, ok := clientErrors[statusCode]
if !ok {
return errors.ErrInvalidReturnValue
return errors.ErrInternalError
}
return err

@ -8,31 +8,25 @@ import (
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models"
)
var httpStatuses = map[error]int{
errors.ErrEmptyArgs: http.StatusInternalServerError,
errors.ErrInvalidArgs: http.StatusBadRequest,
errors.ErrFindRecord: http.StatusInternalServerError,
errors.ErrInsertRecord: http.StatusInternalServerError,
errors.ErrMethodNotImplemented: http.StatusNotImplemented,
errors.ErrNoRecord: http.StatusNotFound,
errors.ErrNoServerItem: http.StatusNotFound,
errors.ErrTransaction: http.StatusInternalServerError,
errors.ErrTransactionSessionStart: http.StatusInternalServerError,
errors.ErrUpdateRecord: http.StatusInternalServerError,
errors.ErrInvalidReturnValue: http.StatusInternalServerError,
var httpStatuses = map[errors.ErrorType]int{
errors.ErrInternalError: http.StatusInternalServerError,
errors.ErrInvalidArgs: http.StatusBadRequest,
errors.ErrNoAccess: http.StatusForbidden,
errors.ErrNotFound: http.StatusNotFound,
errors.ErrMethodNotImplemented: http.StatusNotImplemented,
}
func DetermineEchoErrorResponse(ctx echo.Context, err error, message string) error {
status, ok := httpStatuses[err]
func DetermineEchoErrorResponse(ctx echo.Context, err errors.Error) error {
status, ok := httpStatuses[err.Type()]
if !ok {
return ctx.JSON(http.StatusInternalServerError, models.ResponseErrorHTTP{
StatusCode: http.StatusInternalServerError,
Message: message,
Message: err.Error(),
})
}
return ctx.JSON(status, models.ResponseErrorHTTP{
StatusCode: status,
Message: message,
Message: err.Error(),
})
}

@ -0,0 +1,38 @@
package utils
import "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models"
func DeterminePagination(page, limit interface{}) *models.Pagination {
determinePage := func() int64 {
if page == nil {
return models.DefaultPageNumber
}
pageNumber, isPageNumberOK := page.(int64)
if !isPageNumberOK || pageNumber < 1 {
return models.DefaultPageNumber
}
return pageNumber
}
determineLimit := func() int64 {
if limit == nil {
return models.DefaultLimit
}
limitNumber, isLimitNumberOK := limit.(int64)
if !isLimitNumberOK || limitNumber > models.DefaultLimit {
return models.DefaultLimit
}
return limitNumber
}
return &models.Pagination{
Page: determinePage(),
Limit: determineLimit(),
}
}

@ -8,50 +8,10 @@ import (
)
func ValidateConfigurationURLs(config *models.ServiceConfiguration) error {
if err := validateGoogleURLs(&config.Google.URL); err != nil {
return err
}
if err := validateAmocrmURLs(&config.Amocrm.URL); err != nil {
return err
}
return validateAuthMicroserviceURLs(&config.AuthMicroservice.URL)
}
func validateGoogleURLs(urls *models.GoogleURL) error {
if !validate.URL(urls.OAuthHost) {
return fmt.Errorf("invalid google oauth host: %s", urls.OAuthHost)
}
return nil
}
func validateAmocrmURLs(urls *models.AmocrmURL) error {
if !validate.URL(urls.OAuthHost) {
return fmt.Errorf("invalid amocrm oauth host: %s", urls.OAuthHost)
}
if !validate.URL(urls.UserInfo) {
return fmt.Errorf("invalid amocrm user info url: %s", urls.UserInfo)
}
if !validate.URL(urls.AccessToken) {
return fmt.Errorf("invalid amocrm access token url: %s", urls.AccessToken)
}
return nil
}
func validateAuthMicroserviceURLs(urls *models.AuthMicroServiceURL) error {
if !validate.URL(urls.Exchange) {
return fmt.Errorf("invalid auth microservice exchange url: %s", urls.Exchange)
}
if !validate.URL(urls.Register) {
return fmt.Errorf("invalid auth register url: %s", urls.Register)
}
if !validate.URL(urls.User) {
return fmt.Errorf("invalid auth user url: %s", urls.User)
}

@ -129,19 +129,19 @@ paths:
items:
$ref: "#/components/schemas/Account"
/account/{accountId}:
/account/{userId}:
get:
tags:
- account
summary: Получить аккаунт по ID
summary: Получить аккаунт по ID пользователя системы единой авторизации
description: Метод необходимый в основном только менеджерам, для получения данных о клиенте
security:
- Bearer: []
operationId: getDirectAccount
parameters:
- name: accountId
- name: userId
in: path
description: id аккаунта
description: ID аккаунта
required: true
schema:
type: string
@ -173,7 +173,7 @@ paths:
security:
- Bearer: []
parameters:
- name: accountId
- name: userId
in: path
description: ID аккаунта
required: true
@ -223,6 +223,8 @@ paths:
- currency
summary: обновляет список одобренных валют
operationId: updateCurrencies
security:
- Bearer: []
requestBody:
content:
application/json:
@ -249,6 +251,10 @@ paths:
type: string
'401':
description: Uanuthorized
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/cart:
patch:
@ -264,6 +270,7 @@ paths:
application/json:
schema:
type: object
required: [id]
properties:
id:
type: string
@ -330,11 +337,14 @@ paths:
security:
- Bearer: []
description: >-
- Очевидно нужен воркер или очередь(место где можно потрогать кафку), которая будет слать запросы в конечные сервисы для добавления привилегий в аккаунт пользователя
- Очевидно нужен воркер или очередь (место где можно потрогать кафку), которая будет слать запросы в конечные сервисы для добавления привилегий в аккаунт пользователя
- В случае если денег в кошельке достаточно - отправить в воркер или очередь задачи на разослать конкретным сервисам добавление привилегий
- Если денег недостаточно, получить ссылку на оплату у сервиса платёжки и вернуть её. в случае оплаты, повторить вызов этого метода
- Если денег недостаточно, вернуть ошибку и указать
количество недостающих средств в валюте
- Отправить запрос на discount сервис
- Очистить корзину
@ -343,6 +353,7 @@ paths:
application/json:
schema:
type: object
required: [userId]
properties:
userId:
type: string
@ -373,9 +384,9 @@ paths:
- Отвалидировать, что такая валюта одобрена
- Получить данные из сервиса cbrf(выдам задачу на него чуть позднее)
- Получить данные из сервиса cbrf (выдам задачу на него чуть позднее)
- Перевести валюту кошелька в нвоую валюту. кошелёк нарочно целочисленный, чтобы не было претензий к точности с плавающей точкой, поэтому, например долларовый счёт считается в центах
- Перевести валюту кошелька в новую валюту. Кошелёк нарочно целочисленный, чтобы не было претензий к точности с плавающей точкой, поэтому, например долларовый счёт считается в центах
security:
- Bearer: []
@ -384,6 +395,7 @@ paths:
application/json:
schema:
type: object
required: [currency]
properties:
currency:
type: string
@ -426,6 +438,7 @@ paths:
application/json:
schema:
type: object
required: [cash, currency, userId]
properties:
cash:
type: integer
@ -470,7 +483,7 @@ paths:
parameters:
- name: cash
in: query
description: Номер страницы, начиная с 1
description: Количество денег
required: true
schema:
type: integer
@ -502,15 +515,15 @@ paths:
security:
- Bearer: []
parameters:
- name: p
- name: page
in: query
description: Номер страницы, начиная с 1
required: false
explode: false
schema:
type: integer
default: 0
- name: s
default: 1
- name: limit
in: query
description: Размер страницы
required: false
@ -603,22 +616,29 @@ components:
Wallet:
type: object
required: [currency, cash, money]
properties:
currency:
description: Текущий курс валюты
type: string
example: "RUB"
cash:
description: Сумма money переведённая на текущий курс
type: integer
format: int64
example: 10701
money:
type: integer
format: int64
description: деньги на счету в копейках. Чтобы при перессчётах не возникало денег изниоткуда. фиксируемся к одной валюте, она будет внутренней, никому её не покажем
description: >-
Деньги на счету в копейках. Чтобы при перессчётах не
возникало денег изниоткуда. фиксируемся к одной валюте,
она будет внутренней, никому её не покажем
example: 10701.60
History:
type: object
required: [id, userId, type, deleted, createdAt, updatedAt, comment, subject]
properties:
id:
type: string
@ -648,16 +668,15 @@ components:
subject:
type: string
description: >-
Я пока не могу предположить, какие будут фильтры по истории, поэтому
предлагаю в это поле просто класть строку с json. ибо для каждого типа
записи она своя.
Я пока не могу предположить, какие будут фильтры по
истории, поэтому предлагаю в это
поле просто класть строку с json.
Ибо для каждого типа записи она своя.
example: {"tariffs":["807f1f77bcf81cd799439011","807f1f77bcf81cd799439011"]}
Error:
type: object
required:
- code
- message
required: [code, message]
properties:
statusCode:
type: integer

@ -3,9 +3,8 @@ package closer
import (
"context"
"fmt"
"log"
"sync"
"github.com/sirupsen/logrus"
)
type Callback func(ctx context.Context) error
@ -13,11 +12,10 @@ type Callback func(ctx context.Context) error
type Closer struct {
mutex sync.Mutex
callbacks []Callback
logger *logrus.Logger
}
func New(logger *logrus.Logger) *Closer {
return &Closer{logger: logger}
func New() *Closer {
return &Closer{}
}
func (receiver *Closer) Add(callback Callback) {
@ -36,7 +34,7 @@ func (receiver *Closer) Close(ctx context.Context) error {
go func() {
for index, callback := range receiver.callbacks {
if err := callback(ctx); err != nil {
receiver.logger.Errorf("[! (%d)] %v", index, err)
log.Printf("[! (%d)] %v", index, err)
}
}

@ -3,6 +3,7 @@ package mongo
import (
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type Configuration struct {
@ -15,6 +16,7 @@ type Configuration struct {
}
type RequestSettings struct {
Driver *mongo.Collection
Filter primitive.M
Driver *mongo.Collection
Options *options.FindOptions
Filter primitive.M
}

@ -12,7 +12,7 @@ func Find[T any](ctx context.Context, settings *RequestSettings) ([]T, error) {
results := make([]T, 0)
cursor, err := settings.Driver.Find(ctx, settings.Filter)
cursor, err := settings.Driver.Find(ctx, settings.Filter, settings.Options)
if err != nil {
return []T{}, err
}