mirror of
				https://github.com/netscrawler/changeAPI.git
				synced 2025-10-31 04:23:13 +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 { | ||||||
|  | |||||||
| @ -3,3 +3,7 @@ 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