mirror of
https://github.com/netscrawler/changeAPI.git
synced 2025-05-06 15:29:53 +00:00
v0.1
This commit is contained in:
parent
c99f477e35
commit
5b11756af6
1
.idea/changeAPI.iml
generated
1
.idea/changeAPI.iml
generated
@ -5,6 +5,5 @@
|
|||||||
<content url="file://$MODULE_DIR$" />
|
<content url="file://$MODULE_DIR$" />
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
<orderEntry type="module" module-name="protos" />
|
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
1
.idea/modules.xml
generated
1
.idea/modules.xml
generated
@ -3,7 +3,6 @@
|
|||||||
<component name="ProjectModuleManager">
|
<component name="ProjectModuleManager">
|
||||||
<modules>
|
<modules>
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/changeAPI.iml" filepath="$PROJECT_DIR$/.idea/changeAPI.iml" />
|
<module fileurl="file://$PROJECT_DIR$/.idea/changeAPI.iml" filepath="$PROJECT_DIR$/.idea/changeAPI.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/../protos/.idea/protos.iml" filepath="$PROJECT_DIR$/../protos/.idea/protos.iml" />
|
|
||||||
</modules>
|
</modules>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
1
.idea/vcs.xml
generated
1
.idea/vcs.xml
generated
@ -2,6 +2,5 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
<mapping directory="" vcs="Git" />
|
<mapping directory="" vcs="Git" />
|
||||||
<mapping directory="$PROJECT_DIR$/../protos" vcs="Git" />
|
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
9
Taskfile.yaml
Normal file
9
Taskfile.yaml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
version: "3"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
runServer:
|
||||||
|
aliases:
|
||||||
|
- run
|
||||||
|
desc: "Run grpc service"
|
||||||
|
cmds:
|
||||||
|
- go run cmd/converter/main.go --config .\config\local.yaml
|
@ -1,12 +1,16 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"converter/internal/app"
|
"converter/internal/app"
|
||||||
"converter/internal/config"
|
"converter/internal/config"
|
||||||
"converter/internal/lib/logger/handlers/slogpretty"
|
"converter/internal/lib/logger/handlers/slogpretty"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -24,10 +28,40 @@ func main() {
|
|||||||
log := setupLogger(cfg.Env)
|
log := setupLogger(cfg.Env)
|
||||||
log.Info("starting application", slog.Any("cfg", cfg))
|
log.Info("starting application", slog.Any("cfg", cfg))
|
||||||
applicaton := app.New(log, cfg.GRPC.Port, cfg.TokenTTL)
|
applicaton := app.New(log, cfg.GRPC.Port, cfg.TokenTTL)
|
||||||
applicaton.GRPCSrv.MustRun()
|
go applicaton.GRPCSrv.MustRun()
|
||||||
|
|
||||||
// TODO: инициализировать приложение app
|
// TODO: инициализировать приложение app
|
||||||
|
|
||||||
|
//TODO:redis
|
||||||
|
client := redis.NewClient(&redis.Options{
|
||||||
|
Addr: cfg.Redis.Addr,
|
||||||
|
Password: cfg.Redis.Password,
|
||||||
|
DB: cfg.Redis.DB,
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
err := client.Set(ctx, "key", "value1", 0).Err()
|
||||||
|
if err != nil {
|
||||||
|
log.Info("err")
|
||||||
|
}
|
||||||
|
|
||||||
|
val, err := client.Get(ctx, "key").Result()
|
||||||
|
if err != nil {
|
||||||
|
log.Info("err")
|
||||||
|
}
|
||||||
|
log.Info("key", slog.Any("val", val))
|
||||||
|
|
||||||
// TODO: запустить gRPC сервер приложения
|
// TODO: запустить gRPC сервер приложения
|
||||||
|
|
||||||
|
// Graceful shutdown
|
||||||
|
stop := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(stop, syscall.SIGTERM, syscall.SIGINT)
|
||||||
|
|
||||||
|
sign := <-stop
|
||||||
|
log.Info("stoping application", slog.String("signal", sign.String()))
|
||||||
|
applicaton.GRPCSrv.Stop()
|
||||||
|
log.Info("application stopped")
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupLogger(env string) *slog.Logger {
|
func setupLogger(env string) *slog.Logger {
|
||||||
|
@ -2,4 +2,8 @@ env: "local"
|
|||||||
token_ttl: 1h
|
token_ttl: 1h
|
||||||
grpc:
|
grpc:
|
||||||
port: 44044
|
port: 44044
|
||||||
timeout: 10h
|
timeout: 10h
|
||||||
|
redis:
|
||||||
|
addr: "localhost:6379"
|
||||||
|
password: "1234"
|
||||||
|
db: 0
|
13
go.mod
13
go.mod
@ -3,23 +3,26 @@ module converter
|
|||||||
go 1.22
|
go 1.22
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/netscrawler/protos v0.0.5
|
github.com/fatih/color v1.17.0
|
||||||
|
github.com/ilyakaznacheev/cleanenv v1.5.0
|
||||||
|
github.com/netscrawler/protoss v0.0.0-20240630182512-36e5b935e6b4
|
||||||
|
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
|
||||||
google.golang.org/grpc v1.64.0
|
google.golang.org/grpc v1.64.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v1.2.1 // indirect
|
github.com/BurntSushi/toml v1.2.1 // indirect
|
||||||
github.com/fatih/color v1.17.0 // indirect
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
github.com/ilyakaznacheev/cleanenv v1.5.0 // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/joho/godotenv v1.5.1 // indirect
|
github.com/joho/godotenv v1.5.1 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
|
github.com/redis/go-redis/v9 v9.5.3 // indirect
|
||||||
golang.org/x/net v0.22.0 // indirect
|
golang.org/x/net v0.22.0 // indirect
|
||||||
golang.org/x/sys v0.18.0 // indirect
|
golang.org/x/sys v0.18.0 // indirect
|
||||||
golang.org/x/text v0.14.0 // indirect
|
golang.org/x/text v0.14.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect
|
||||||
google.golang.org/protobuf v1.33.0 // indirect
|
google.golang.org/protobuf v1.34.2 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect
|
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect
|
||||||
)
|
)
|
||||||
|
19
go.sum
19
go.sum
@ -1,7 +1,13 @@
|
|||||||
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
|
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
|
||||||
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
||||||
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/ilyakaznacheev/cleanenv v1.5.0 h1:0VNZXggJE2OYdXE87bfSSwGxeiGt9moSR2lOrsHHvr4=
|
github.com/ilyakaznacheev/cleanenv v1.5.0 h1:0VNZXggJE2OYdXE87bfSSwGxeiGt9moSR2lOrsHHvr4=
|
||||||
github.com/ilyakaznacheev/cleanenv v1.5.0/go.mod h1:a5aDzaJrLCQZsazHol1w8InnDcOX0OColm64SlIi6gk=
|
github.com/ilyakaznacheev/cleanenv v1.5.0/go.mod h1:a5aDzaJrLCQZsazHol1w8InnDcOX0OColm64SlIi6gk=
|
||||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
@ -11,10 +17,10 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
|
|||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/netscrawler/protos v0.0.0-20240629165054-1454eca14db0 h1:YKByh7dtXFBif3xemj/OFyWbAcw3j/M8tD4U4XF229A=
|
github.com/netscrawler/protoss v0.0.0-20240630182512-36e5b935e6b4 h1:V/zWems1vjc1Ob0p0APeL+tKiSHeOW4Kc1yo3xP0IMQ=
|
||||||
github.com/netscrawler/protos v0.0.0-20240629165054-1454eca14db0/go.mod h1:jgfFUikzJpivreejTHY7s1BWvVpM9/rJdvTntTzcPzc=
|
github.com/netscrawler/protoss v0.0.0-20240630182512-36e5b935e6b4/go.mod h1:+Ms2tgnXO0rzzVXLn61kltLnEOA/CuSKSv62L+cacdQ=
|
||||||
github.com/netscrawler/protos v0.0.4 h1:D+Cb4cELT9YSIyUsyXL233Zb90ClzOR290aE4i+KI8Y=
|
github.com/redis/go-redis/v9 v9.5.3 h1:fOAp1/uJG+ZtcITgZOfYFmTKPE7n4Vclj1wZFgRciUU=
|
||||||
github.com/netscrawler/protos v0.0.4/go.mod h1:jgfFUikzJpivreejTHY7s1BWvVpM9/rJdvTntTzcPzc=
|
github.com/redis/go-redis/v9 v9.5.3/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
||||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
|
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
|
||||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
|
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
|
||||||
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
|
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
|
||||||
@ -29,8 +35,9 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:
|
|||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||||
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
|
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
|
||||||
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
|
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
|
||||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
@ -16,6 +16,7 @@ func New(
|
|||||||
tokenTTl time.Duration) *App {
|
tokenTTl time.Duration) *App {
|
||||||
//TODO: инициализировать хранилище
|
//TODO: инициализировать хранилище
|
||||||
//TODO: init convert service
|
//TODO: init convert service
|
||||||
|
|
||||||
grpcApp := grpcapp.New(log, grpcPort)
|
grpcApp := grpcapp.New(log, grpcPort)
|
||||||
return &App{
|
return &App{
|
||||||
GRPCSrv: grpcApp,
|
GRPCSrv: grpcApp,
|
||||||
|
@ -2,6 +2,7 @@ package grpcapp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
convertgrpc "converter/internal/grpc/cnvrt"
|
convertgrpc "converter/internal/grpc/cnvrt"
|
||||||
|
"converter/internal/services/converter"
|
||||||
"fmt"
|
"fmt"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
@ -18,8 +19,8 @@ func New(
|
|||||||
log *slog.Logger,
|
log *slog.Logger,
|
||||||
port int) *App {
|
port int) *App {
|
||||||
gRPCServer := grpc.NewServer()
|
gRPCServer := grpc.NewServer()
|
||||||
|
convert := converter.New(log)
|
||||||
convertgrpc.Register(gRPCServer)
|
convertgrpc.Register(gRPCServer, convert)
|
||||||
|
|
||||||
return &App{
|
return &App{
|
||||||
log: log,
|
log: log,
|
||||||
|
@ -11,6 +11,7 @@ type Config struct {
|
|||||||
Env string `yaml:"env" env-default:"local"`
|
Env string `yaml:"env" env-default:"local"`
|
||||||
TokenTTL time.Duration `yaml:"token_ttl" env-required:"true"`
|
TokenTTL time.Duration `yaml:"token_ttl" env-required:"true"`
|
||||||
GRPC GRPCConfig `yaml:"grpc"`
|
GRPC GRPCConfig `yaml:"grpc"`
|
||||||
|
Redis RedisConfig `yaml:"redis"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GRPCConfig struct {
|
type GRPCConfig struct {
|
||||||
@ -18,6 +19,12 @@ type GRPCConfig struct {
|
|||||||
Timeout time.Duration `yaml:"timeout"`
|
Timeout time.Duration `yaml:"timeout"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RedisConfig struct {
|
||||||
|
Addr string `yaml:"addr"`
|
||||||
|
Password string `yaml:"password"`
|
||||||
|
DB int `yaml:"db"`
|
||||||
|
}
|
||||||
|
|
||||||
func MustLoad() *Config {
|
func MustLoad() *Config {
|
||||||
path := fetchConfigPath()
|
path := fetchConfigPath()
|
||||||
if path == "" {
|
if path == "" {
|
||||||
|
6
internal/domain/models/vunitRate.go
Normal file
6
internal/domain/models/vunitRate.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
type VunitRate struct {
|
||||||
|
Currency string
|
||||||
|
Rate float32
|
||||||
|
}
|
@ -2,21 +2,58 @@ package cnvrt
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
cnvrtv1 "github.com/netscrawler/protos/gen/go/changeAPI"
|
cnvrtv1 "github.com/netscrawler/protoss/gen/go/changeAPI"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Converter interface {
|
||||||
|
Convert(ctx context.Context,
|
||||||
|
amount uint32,
|
||||||
|
targetCurrency string,
|
||||||
|
) (convertedAmount uint32, rate float32, err error)
|
||||||
|
}
|
||||||
|
|
||||||
type serverAPI struct {
|
type serverAPI struct {
|
||||||
cnvrtv1.UnimplementedConverterServer
|
cnvrtv1.UnimplementedConverterServer
|
||||||
|
convert Converter
|
||||||
}
|
}
|
||||||
|
|
||||||
func Register(gRPC *grpc.Server) {
|
func Register(gRPC *grpc.Server, convert Converter) {
|
||||||
cnvrtv1.RegisterConverterServer(gRPC, &serverAPI{})
|
cnvrtv1.RegisterConverterServer(gRPC, &serverAPI{convert: convert})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s serverAPI) Convert(
|
func (s *serverAPI) Convert(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *cnvrtv1.ConvertRequest) (
|
req *cnvrtv1.ConvertRequest) (
|
||||||
*cnvrtv1.ConvertResponse, error) {
|
*cnvrtv1.ConvertResponse, error) {
|
||||||
panic("implement me")
|
if !isValidCurrency(req.GetTargetCurrency()) {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, "Invalid target currency")
|
||||||
|
}
|
||||||
|
|
||||||
|
convertedAmount, rate, err := s.convert.Convert(ctx, req.GetAmount(), req.GetTargetCurrency())
|
||||||
|
if err != nil {
|
||||||
|
//todo error handler
|
||||||
|
return nil, status.Error(codes.Internal, "Internal error")
|
||||||
|
}
|
||||||
|
return &cnvrtv1.ConvertResponse{
|
||||||
|
BaseAmount: req.GetAmount(),
|
||||||
|
ConvertedAmount: convertedAmount,
|
||||||
|
ConvertedCurrency: req.GetTargetCurrency(),
|
||||||
|
Rate: rate,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValidCurrency(currency string) bool {
|
||||||
|
|
||||||
|
currencies := map[string]bool{
|
||||||
|
"AUD": true, "GBP": true, "BYR": true, "DKK": true, "USD": true, "EUR": true,
|
||||||
|
"ISK": true, "KZT": true, "CAD": true, "NOK": true, "XDR": true, "SGD": true,
|
||||||
|
"TRL": true, "UAH": true, "SEK": true, "CHF": true, "JPY": true,
|
||||||
|
}
|
||||||
|
if currencies[currency] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
177
internal/lib/rateExtract/rateExtract.go
Normal file
177
internal/lib/rateExtract/rateExtract.go
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
package rateExtract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"compress/gzip"
|
||||||
|
"converter/internal/domain/models"
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/text/encoding/charmap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type resultType struct {
|
||||||
|
XMLName xml.Name `xml:"ValCurs"`
|
||||||
|
Valute []struct {
|
||||||
|
NumCode string `xml:"NumCode"`
|
||||||
|
CharCode string `xml:"CharCode"`
|
||||||
|
Nominal string `xml:"Nominal"`
|
||||||
|
Name string `xml:"Name"`
|
||||||
|
Value string `xml:"Value"`
|
||||||
|
} `xml:"Valute"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type cacheKeyType struct {
|
||||||
|
CurrencyId string
|
||||||
|
Date string
|
||||||
|
}
|
||||||
|
|
||||||
|
type cacheResultType struct {
|
||||||
|
Rate float64
|
||||||
|
}
|
||||||
|
|
||||||
|
var urlTemplate string = "https://www.cbr.ru/scripts/XML_daily.asp?date_req=%s"
|
||||||
|
|
||||||
|
var cache map[cacheKeyType]*cacheResultType
|
||||||
|
|
||||||
|
func GetExchangeRate(currencyId string) (models.VunitRate, error) {
|
||||||
|
date := time.Now()
|
||||||
|
if cache == nil {
|
||||||
|
cache = map[cacheKeyType]*cacheResultType{}
|
||||||
|
}
|
||||||
|
|
||||||
|
reqDate := fmt.Sprintf("%02d/%02d/%d", date.Day(), date.Month(), date.Year())
|
||||||
|
|
||||||
|
cacheKey := cacheKeyType{CurrencyId: currencyId, Date: reqDate}
|
||||||
|
|
||||||
|
cacheResult, exists := cache[cacheKey]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
|
||||||
|
url := fmt.Sprintf(urlTemplate, reqDate)
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return models.VunitRate{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Add("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7")
|
||||||
|
req.Header.Add("accept-encoding", "gzip, deflate, br")
|
||||||
|
req.Header.Add("accept-language", "en-US,en;q=0.9,ru;q=0.8")
|
||||||
|
req.Header.Add("cache-control", "max-age=0")
|
||||||
|
req.Header.Add("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36")
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return models.VunitRate{Currency: currencyId, Rate: 0}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var reader io.ReadCloser
|
||||||
|
|
||||||
|
switch resp.Header.Get("Content-Encoding") {
|
||||||
|
case "gzip":
|
||||||
|
reader, err = gzip.NewReader(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return models.VunitRate{Currency: currencyId, Rate: 0}, err
|
||||||
|
}
|
||||||
|
defer reader.Close()
|
||||||
|
default:
|
||||||
|
reader = resp.Body
|
||||||
|
}
|
||||||
|
|
||||||
|
xml := xml.NewDecoder(reader)
|
||||||
|
|
||||||
|
xml.CharsetReader = func(charset string, input io.Reader) (io.Reader, error) {
|
||||||
|
switch charset {
|
||||||
|
case "windows-1251":
|
||||||
|
return charmap.Windows1251.NewDecoder().Reader(input), nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown charset: %s", charset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result := &resultType{}
|
||||||
|
|
||||||
|
err = xml.Decode(result)
|
||||||
|
if err != nil {
|
||||||
|
return models.VunitRate{Currency: currencyId, Rate: 0}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, resultRow := range result.Valute {
|
||||||
|
|
||||||
|
if resultRow.CharCode != currencyId {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
resultRow.Value = strings.Replace(resultRow.Value, ",", ".", 1)
|
||||||
|
|
||||||
|
rate, err := strconv.ParseFloat(resultRow.Value, 64)
|
||||||
|
if err != nil {
|
||||||
|
return models.VunitRate{Currency: currencyId, Rate: 0}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nominal, err := strconv.ParseInt(resultRow.Nominal, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return models.VunitRate{Currency: currencyId, Rate: 0}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheResult = &cacheResultType{Rate: rate / float64(nominal)}
|
||||||
|
|
||||||
|
cache[cacheKey] = cacheResult
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return models.VunitRate{Currency: currencyId, Rate: float32(cacheResult.Rate)}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//func Convert(from string, to string, value float64, date time.Time) (float64, error) {
|
||||||
|
//
|
||||||
|
// if from == to {
|
||||||
|
// return value, nil
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if value == 0 {
|
||||||
|
// return 0, nil
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// result := value
|
||||||
|
//
|
||||||
|
// if from != CurrencyRUB {
|
||||||
|
//
|
||||||
|
// exchangeRate, err := GetExchangeRate(from, date)
|
||||||
|
// if err != nil {
|
||||||
|
// return 0, err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// result = result * exchangeRate
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if to != CurrencyRUB {
|
||||||
|
//
|
||||||
|
// exchangeRate, err := GetExchangeRate(to, date)
|
||||||
|
// if err != nil {
|
||||||
|
// return 0, err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// result = result / exchangeRate
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return (math.Floor(result*100) / 100), nil
|
||||||
|
//
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func main() {
|
||||||
|
// fmt.Println(GetExchangeRate(CurrencyUSD, time.Now()))
|
||||||
|
//}
|
48
internal/services/converter/converter.go
Normal file
48
internal/services/converter/converter.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package converter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"converter/internal/lib/logger/sl"
|
||||||
|
"converter/internal/lib/rateExtract"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Converter struct {
|
||||||
|
log *slog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new instance of the Converter service.
|
||||||
|
func New(
|
||||||
|
log *slog.Logger,
|
||||||
|
|
||||||
|
) *Converter {
|
||||||
|
return &Converter{
|
||||||
|
log: log,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Converter) Convert(
|
||||||
|
ctx context.Context,
|
||||||
|
amount uint32,
|
||||||
|
currency string,
|
||||||
|
) (uint32, float32, error) {
|
||||||
|
const op = "converter.convert"
|
||||||
|
log := c.log.With(
|
||||||
|
slog.String("op", op),
|
||||||
|
slog.String("currency", currency),
|
||||||
|
slog.Any("amount", amount),
|
||||||
|
)
|
||||||
|
log.Info("convertation")
|
||||||
|
var convertedAmount uint32
|
||||||
|
//TODO extract rate
|
||||||
|
rate, err := rateExtract.GetExchangeRate(currency)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error extracting rate", sl.Err(err))
|
||||||
|
return 0, 0, fmt.Errorf("%s: %w", op, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
convertedAmount = uint32(float32(amount) * rate.Rate)
|
||||||
|
|
||||||
|
return convertedAmount, rate.Rate, nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user