diff --git a/.dockerignore b/.dockerignore index 71abae4..484a1de 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,20 +1,32 @@ -# git and github +# Git and Github /.github README.md -# vscode +# VScode /.vscode -# proxies +# Proxies /nginx /traefik /haproxy -# compose +# Compose docker-compose.yaml -# make +# Make Makefile -# postman +# Postman /postman + +# Fresh +runner.conf + +# Testing +/tests +/postman +coverage.out +endpoints_test.sh + +# Temporary +/tmp diff --git a/.env b/.env new file mode 100644 index 0000000..573b65d --- /dev/null +++ b/.env @@ -0,0 +1,8 @@ +# TebakAja Proxy +TEBAKAJA_PROXY_HOST=0.0.0.0 +TEBAKAJA_PROXY_PORT=7860 + +# TebakAja CORS +TEBAKAJA_CORS_ALLOW_ORIGINS=https://huggingface.co,https://qywok-tebakaja-proxy-space-0.hf.space,https://qywok-tebakaja-proxy-space-1.hf.space,https://qywok-tebakaja-proxy-space-2.hf.space +TEBAKAJA_CORS_ALLOW_HEADERS=* +TEBAKAJA_CORS_ALLOW_METHODS=GET,POST \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6c81d79 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# temporary file +/tmp/tebakaja_proxy.exe diff --git a/Makefile b/Makefile index 9f9a776..2e69cad 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,9 @@ svc: swag init go run main.go +unittest: + go test -v ./tests -coverprofile=coverage.out + swag: swag init @@ -27,6 +30,9 @@ traefik-test: --api.dashboard=true \ --api.insecure=false +hot-reload: + freeze -c runner.conf + endpoints_check: bash -c 'chmod +x endpoints_test.sh && ./endpoints_test.sh' diff --git a/docs/docs.go b/docs/docs.go index 0626afc..88b4952 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -203,12 +203,19 @@ const docTemplate = `{ }, "crypto.PredictionRequest": { "type": "object", + "required": [ + "currency" + ], "properties": { "currency": { - "type": "string" + "type": "string", + "maxLength": 16, + "minLength": 4 }, "days": { - "type": "integer" + "type": "integer", + "maximum": 31, + "minimum": 1 } } }, @@ -278,12 +285,19 @@ const docTemplate = `{ }, "national_currency.PredictionRequest": { "type": "object", + "required": [ + "currency" + ], "properties": { "currency": { - "type": "string" + "type": "string", + "maxLength": 16, + "minLength": 4 }, "days": { - "type": "integer" + "type": "integer", + "maximum": 31, + "minimum": 1 } } }, @@ -353,12 +367,19 @@ const docTemplate = `{ }, "stock.PredictionRequest": { "type": "object", + "required": [ + "currency" + ], "properties": { "currency": { - "type": "string" + "type": "string", + "maxLength": 16, + "minLength": 4 }, "days": { - "type": "integer" + "type": "integer", + "maximum": 31, + "minimum": 1 } } }, diff --git a/docs/swagger.json b/docs/swagger.json index 6abf26c..af78f98 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -195,12 +195,19 @@ }, "crypto.PredictionRequest": { "type": "object", + "required": [ + "currency" + ], "properties": { "currency": { - "type": "string" + "type": "string", + "maxLength": 16, + "minLength": 4 }, "days": { - "type": "integer" + "type": "integer", + "maximum": 31, + "minimum": 1 } } }, @@ -270,12 +277,19 @@ }, "national_currency.PredictionRequest": { "type": "object", + "required": [ + "currency" + ], "properties": { "currency": { - "type": "string" + "type": "string", + "maxLength": 16, + "minLength": 4 }, "days": { - "type": "integer" + "type": "integer", + "maximum": 31, + "minimum": 1 } } }, @@ -345,12 +359,19 @@ }, "stock.PredictionRequest": { "type": "object", + "required": [ + "currency" + ], "properties": { "currency": { - "type": "string" + "type": "string", + "maxLength": 16, + "minLength": 4 }, "days": { - "type": "integer" + "type": "integer", + "maximum": 31, + "minimum": 1 } } }, diff --git a/docs/swagger.yaml b/docs/swagger.yaml index f1d4d21..d7c759b 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -10,9 +10,15 @@ definitions: crypto.PredictionRequest: properties: currency: + maxLength: 16 + minLength: 4 type: string days: + maximum: 31 + minimum: 1 type: integer + required: + - currency type: object crypto.PredictionResponse: properties: @@ -58,9 +64,15 @@ definitions: national_currency.PredictionRequest: properties: currency: + maxLength: 16 + minLength: 4 type: string days: + maximum: 31 + minimum: 1 type: integer + required: + - currency type: object national_currency.PredictionResponse: properties: @@ -106,9 +118,15 @@ definitions: stock.PredictionRequest: properties: currency: + maxLength: 16 + minLength: 4 type: string days: + maximum: 31 + minimum: 1 type: integer + required: + - currency type: object stock.PredictionResponse: properties: diff --git a/go.mod b/go.mod index 1bff463..67affb1 100644 --- a/go.mod +++ b/go.mod @@ -8,14 +8,20 @@ require ( github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/andybalholm/brotli v1.1.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/spec v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.22.0 // indirect github.com/gofiber/fiber/v2 v2.52.5 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/joho/godotenv v1.5.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/klauspost/compress v1.17.9 // indirect + github.com/leodido/go-urn v1.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.20 // indirect @@ -32,6 +38,7 @@ require ( github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.55.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect + golang.org/x/crypto v0.25.0 // indirect golang.org/x/net v0.27.0 // indirect golang.org/x/sys v0.22.0 // indirect golang.org/x/text v0.16.0 // indirect diff --git a/go.sum b/go.sum index 8779b60..7925c1e 100644 --- a/go.sum +++ b/go.sum @@ -16,6 +16,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma 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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= @@ -35,12 +37,20 @@ github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyr github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= +github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/gofiber/fiber/v2 v2.32.0/go.mod h1:CMy5ZLiXkn6qwthrl03YMyW1NLfj0rhxz2LKl4t7ZTY= github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo= github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= @@ -51,6 +61,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN 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/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= @@ -112,6 +124,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= diff --git a/main.go b/main.go index 72730b3..9814336 100644 --- a/main.go +++ b/main.go @@ -1,18 +1,24 @@ package main import ( + "os" "fmt" "log" - "github.com/gofiber/fiber/v2" + "github.com/joho/godotenv" - proxy "tebakaja_lb_proxy/proxy" + // Fiber + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/cors" + "github.com/gofiber/fiber/v2/middleware/helmet" // Main Features - stock_proxy "tebakaja_lb_proxy/proxy/stock" - crypto_proxy "tebakaja_lb_proxy/proxy/crypto" + stock_proxy "tebakaja_lb_proxy/proxy/stock" + crypto_proxy "tebakaja_lb_proxy/proxy/crypto" national_currency_proxy "tebakaja_lb_proxy/proxy/national_currency" + middlewares "tebakaja_lb_proxy/proxy/middlewares" + // Swagger _ "tebakaja_lb_proxy/docs" swagger "github.com/swaggo/fiber-swagger" @@ -22,6 +28,7 @@ import ( ) + // @title TebakAja // @version 1.0 // @description TebakAja REST API Service @@ -36,9 +43,32 @@ import ( // @host 192.168.137.1:7860 func main() { + err := godotenv.Load() + if err != nil { + log.Fatalf("Error loading .env file") + } + proxyService := fiber.New() - proxyService.Use(proxy.LoggingMiddleware) - proxyService.Use(proxy.RateLimiterMiddleware()) + proxyService.Use(helmet.New()) + proxyService.Use(middlewares.LoggingMiddleware) + proxyService.Use(middlewares.RateLimiterMiddleware()) + + proxyService.Use(cors.New(cors.Config{ + AllowOrigins: os.Getenv("TEBAKAJA_CORS_ALLOW_ORIGINS"), + AllowHeaders: os.Getenv("TEBAKAJA_CORS_ALLOW_HEADERS"), + AllowMethods: os.Getenv("TEBAKAJA_CORS_ALLOW_METHODS"), + AllowCredentials: true, + })) + + proxyService.Use(func(c *fiber.Ctx) error { + c.Set("Content-Security-Policy", fmt.Sprintf("frame-ancestors 'self' %s %s %s %s", + "https://huggingface.co", + "https://qywok-tebakaja-proxy-space-0.hf.space", + "https://qywok-tebakaja-proxy-space-1.hf.space", + "https://qywok-tebakaja-proxy-space-2.hf.space", + )) + return c.Next() + }) stockGroup := proxyService.Group("/stock") stockGroup.Get("/lists", @@ -69,7 +99,7 @@ func main() { return c.Redirect("/swagger/index.html", fiber.StatusMovedPermanently) }) - host := "0.0.0.0" - port := 7860 - log.Fatal(proxyService.Listen(fmt.Sprintf("%s:%d", host, port))) + HOST := os.Getenv("TEBAKAJA_PROXY_HOST") + PORT := os.Getenv("TEBAKAJA_PROXY_PORT") + log.Fatal(proxyService.Listen(fmt.Sprintf("%s:%s", HOST, PORT))) } diff --git a/proxy/crypto/list_service.go b/proxy/crypto/list_service.go index 68ae328..093de15 100644 --- a/proxy/crypto/list_service.go +++ b/proxy/crypto/list_service.go @@ -6,7 +6,7 @@ import ( "net/http" "encoding/json" - proxy "tebakaja_lb_proxy/proxy" + helpers "tebakaja_lb_proxy/proxy/helpers" ) @@ -14,7 +14,7 @@ import ( * --- Cryptocurrency Prediction Model Lists Service --- */ func (s *CryptoServiceImpl) CryptoListsService(ctx context.Context) (ApiResponse, error) { - endpoint := fmt.Sprintf("%s/lists", proxy.GetEndpointByRestService("crypto")) + endpoint := fmt.Sprintf("%s/lists", helpers.GetEndpointService("crypto")) req, err := http.NewRequestWithContext(ctx, "GET", endpoint, nil) if err != nil { return ApiResponse{ diff --git a/proxy/crypto/prediction_handler.go b/proxy/crypto/prediction_handler.go index 19cb475..87a8d92 100644 --- a/proxy/crypto/prediction_handler.go +++ b/proxy/crypto/prediction_handler.go @@ -9,6 +9,9 @@ import ( "net/http" "github.com/gofiber/fiber/v2" + "github.com/go-playground/validator/v10" + + helpers "tebakaja_lb_proxy/proxy/helpers" ) @@ -46,6 +49,16 @@ func CryptoPredictionHandler(service CryptoService) fiber.Handler { return } + if err := helpers.ValidateStruct(predictionReq); err != nil { + errors := err.(validator.ValidationErrors) + + ch <- ApiResponse{ + Message: fmt.Sprintf("%v", errors), + StatusCode: http.StatusBadRequest, + } + return + } + apiResponse, err := service.CryptoPredictionService(ctx, predictionReq) if err != nil { log.Printf("[%s] %v", time.Now().Format("2006-01-02 15:04:05"), err) diff --git a/proxy/crypto/prediction_service.go b/proxy/crypto/prediction_service.go index 7034b2b..de28af4 100644 --- a/proxy/crypto/prediction_service.go +++ b/proxy/crypto/prediction_service.go @@ -6,7 +6,8 @@ import ( "context" "net/http" "encoding/json" - proxy "tebakaja_lb_proxy/proxy" + + helpers "tebakaja_lb_proxy/proxy/helpers" ) @@ -22,7 +23,7 @@ func (s *CryptoServiceImpl) CryptoPredictionService(ctx context.Context, req Pre }, err } - endpoint := fmt.Sprintf("%s/prediction", proxy.GetEndpointByRestService("crypto")) + endpoint := fmt.Sprintf("%s/prediction", helpers.GetEndpointService("crypto")) httpReq, err := http.NewRequestWithContext(ctx, "POST", endpoint, bytes.NewBuffer(reqBody)) if err != nil { return ApiResponse{ diff --git a/proxy/crypto/structs.go b/proxy/crypto/structs.go index 3849eab..b3ee9d3 100644 --- a/proxy/crypto/structs.go +++ b/proxy/crypto/structs.go @@ -8,8 +8,8 @@ type ApiResponse struct { type PredictionRequest struct { - Days int `json:"days"` - Currency string `json:"currency"` + Days int `json:"days" validate:"gte=1,lte=31"` + Currency string `json:"currency" validate:"required,min=4,max=16"` } diff --git a/proxy/utils.go b/proxy/helpers/get_service.go similarity index 94% rename from proxy/utils.go rename to proxy/helpers/get_service.go index 1315503..5d41689 100644 --- a/proxy/utils.go +++ b/proxy/helpers/get_service.go @@ -1,4 +1,4 @@ -package proxy +package helpers import "math/rand" @@ -26,7 +26,7 @@ var serviceUrls = map[string][]string{ }, } -func GetEndpointByRestService(svc_name string) string { +func GetEndpointService(svc_name string) string { var selectedUrls []string switch svc_name { case "crypto": diff --git a/proxy/helpers/validator.go b/proxy/helpers/validator.go new file mode 100644 index 0000000..7f74e58 --- /dev/null +++ b/proxy/helpers/validator.go @@ -0,0 +1,9 @@ +package helpers + +import "github.com/go-playground/validator/v10" + +var validate *validator.Validate + +func init() { validate = validator.New() } + +func ValidateStruct(data interface{}) error { return validate.Struct(data) } diff --git a/proxy/middlewares/logging.go b/proxy/middlewares/logging.go new file mode 100644 index 0000000..898c98d --- /dev/null +++ b/proxy/middlewares/logging.go @@ -0,0 +1,26 @@ +package middlewares + +import ( + "log" + "time" + "net/http" + + "github.com/gofiber/fiber/v2" +) + + +/* + * --- Logging Middleware --- +*/ +func LoggingMiddleware(c *fiber.Ctx) error { + start_time := time.Now() + + log.Printf("[%s] %s %s - %d %s in %v", + time.Now().Format("2006-01-02 15:04:05"), + c.Method(), c.Path(), c.Response().StatusCode(), + http.StatusText(c.Response().StatusCode()), time.Since(start_time)) + + + return c.Next() +} + diff --git a/proxy/middlewares.go b/proxy/middlewares/rate_limiter.go similarity index 59% rename from proxy/middlewares.go rename to proxy/middlewares/rate_limiter.go index 7a10843..6c95437 100644 --- a/proxy/middlewares.go +++ b/proxy/middlewares/rate_limiter.go @@ -1,30 +1,14 @@ -package proxy +package middlewares import ( - "log" - "net/http" "time" + "net/http" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/limiter" ) -/* - * --- Logging Middleware --- -*/ -func LoggingMiddleware(c *fiber.Ctx) error { - start := time.Now() - err := c.Next() - - log.Printf("[%s] %s %s - %d %s in %v", time.Now().Format("2006-01-02 15:04:05"), - c.Method(), c.Path(), c.Response().StatusCode(), - http.StatusText(c.Response().StatusCode()), time.Since(start)) - - return err -} - - /* * --- Rate Limiter Middleware --- */ diff --git a/proxy/national_currency/list_service.go b/proxy/national_currency/list_service.go index f82d90f..ab34b94 100644 --- a/proxy/national_currency/list_service.go +++ b/proxy/national_currency/list_service.go @@ -5,7 +5,8 @@ import ( "context" "net/http" "encoding/json" - proxy "tebakaja_lb_proxy/proxy" + + helpers "tebakaja_lb_proxy/proxy/helpers" ) @@ -13,7 +14,7 @@ import ( * --- National Currency Prediction Model Lists Service --- */ func (s *NationalCurrencyServiceImpl) NationalCurrencyListsService(ctx context.Context) (ApiResponse, error) { - endpoint := fmt.Sprintf("%s/lists", proxy.GetEndpointByRestService("national")) + endpoint := fmt.Sprintf("%s/lists", helpers.GetEndpointService("national")) req, err := http.NewRequestWithContext(ctx, "GET", endpoint, nil) if err != nil { return ApiResponse{ diff --git a/proxy/national_currency/prediction_handler.go b/proxy/national_currency/prediction_handler.go index 0454bb4..25f4d65 100644 --- a/proxy/national_currency/prediction_handler.go +++ b/proxy/national_currency/prediction_handler.go @@ -9,6 +9,9 @@ import ( "net/http" "github.com/gofiber/fiber/v2" + "github.com/go-playground/validator/v10" + + helpers "tebakaja_lb_proxy/proxy/helpers" ) @@ -46,6 +49,16 @@ func NationalCurrencyPredictionHandler(service NationalCurrencyService) fiber.Ha return } + if err := helpers.ValidateStruct(predictionReq); err != nil { + errors := err.(validator.ValidationErrors) + + ch <- ApiResponse{ + Message: fmt.Sprintf("%v", errors), + StatusCode: http.StatusBadRequest, + } + return + } + apiResponse, err := service.NationalCurrencyPredictionService(ctx, predictionReq) if err != nil { log.Printf("[%s] %v", time.Now().Format("2006-01-02 15:04:05"), err) diff --git a/proxy/national_currency/prediction_service.go b/proxy/national_currency/prediction_service.go index a23909b..50054f8 100644 --- a/proxy/national_currency/prediction_service.go +++ b/proxy/national_currency/prediction_service.go @@ -6,7 +6,8 @@ import ( "context" "net/http" "encoding/json" - proxy "tebakaja_lb_proxy/proxy" + + helpers "tebakaja_lb_proxy/proxy/helpers" ) @@ -22,7 +23,7 @@ func (s *NationalCurrencyServiceImpl) NationalCurrencyPredictionService(ctx cont }, err } - endpoint := fmt.Sprintf("%s/prediction", proxy.GetEndpointByRestService("national")) + endpoint := fmt.Sprintf("%s/prediction", helpers.GetEndpointService("national")) httpReq, err := http.NewRequestWithContext(ctx, "POST", endpoint, bytes.NewBuffer(reqBody)) if err != nil { return ApiResponse{ diff --git a/proxy/national_currency/structs.go b/proxy/national_currency/structs.go index b1110d2..4a4d0de 100644 --- a/proxy/national_currency/structs.go +++ b/proxy/national_currency/structs.go @@ -7,8 +7,8 @@ type ApiResponse struct { } type PredictionRequest struct { - Days int `json:"days"` - Currency string `json:"currency"` + Days int `json:"days" validate:"gte=1,lte=31"` + Currency string `json:"currency" validate:"required,min=4,max=16"` } type PredictionResponse struct { diff --git a/proxy/stock/list_service.go b/proxy/stock/list_service.go index c55a303..84f7c5f 100644 --- a/proxy/stock/list_service.go +++ b/proxy/stock/list_service.go @@ -6,7 +6,7 @@ import ( "net/http" "encoding/json" - proxy "tebakaja_lb_proxy/proxy" + helpers "tebakaja_lb_proxy/proxy/helpers" ) @@ -14,7 +14,7 @@ import ( * --- Stock Prediction Model Lists Service --- */ func (s *StockServiceImpl) StockListsService(ctx context.Context) (ApiResponse, error) { - endpoint := fmt.Sprintf("%s/lists", proxy.GetEndpointByRestService("stock")) + endpoint := fmt.Sprintf("%s/lists", helpers.GetEndpointService("stock")) req, err := http.NewRequestWithContext(ctx, "GET", endpoint, nil) if err != nil { return ApiResponse{ diff --git a/proxy/stock/prediction_handler.go b/proxy/stock/prediction_handler.go index 3d436b9..1069ce9 100644 --- a/proxy/stock/prediction_handler.go +++ b/proxy/stock/prediction_handler.go @@ -9,6 +9,9 @@ import ( "net/http" "github.com/gofiber/fiber/v2" + "github.com/go-playground/validator/v10" + + helpers "tebakaja_lb_proxy/proxy/helpers" ) @@ -46,6 +49,16 @@ func StockPredictionHandler(service StockService) fiber.Handler { return } + if err := helpers.ValidateStruct(predictionReq); err != nil { + errors := err.(validator.ValidationErrors) + + ch <- ApiResponse{ + Message: fmt.Sprintf("%v", errors), + StatusCode: http.StatusBadRequest, + } + return + } + apiResponse, err := service.StockPredictionService(ctx, predictionReq) if err != nil { log.Printf("[%s] %v", time.Now().Format("2006-01-02 15:04:05"), err) diff --git a/proxy/stock/prediction_service.go b/proxy/stock/prediction_service.go index 0e3fd2c..0a7dcf7 100644 --- a/proxy/stock/prediction_service.go +++ b/proxy/stock/prediction_service.go @@ -7,7 +7,7 @@ import ( "net/http" "encoding/json" - proxy "tebakaja_lb_proxy/proxy" + helpers "tebakaja_lb_proxy/proxy/helpers" ) @@ -23,7 +23,7 @@ func (s *StockServiceImpl) StockPredictionService(ctx context.Context, req Predi }, err } - endpoint := fmt.Sprintf("%s/prediction", proxy.GetEndpointByRestService("stock")) + endpoint := fmt.Sprintf("%s/prediction", helpers.GetEndpointService("stock")) httpReq, err := http.NewRequestWithContext(ctx, "POST", endpoint, bytes.NewBuffer(reqBody)) if err != nil { return ApiResponse{ diff --git a/proxy/stock/structs.go b/proxy/stock/structs.go index ac3411f..143d694 100644 --- a/proxy/stock/structs.go +++ b/proxy/stock/structs.go @@ -7,8 +7,8 @@ type ApiResponse struct { } type PredictionRequest struct { - Days int `json:"days"` - Currency string `json:"currency"` + Days int `json:"days" validate:"gte=1,lte=31"` + Currency string `json:"currency" validate:"required,min=4,max=16"` } type PredictionResponse struct { diff --git a/runner.conf b/runner.conf new file mode 100644 index 0000000..9af37c8 --- /dev/null +++ b/runner.conf @@ -0,0 +1,54 @@ +# root directory for your project +root: . + +# temporary path where Fresh will store built binaries +tmp_path: ./tmp + +# name of the build file +build_name: tebakaja_proxy + +# directories to watch for changes (can include multiple directories) +watch_dirs: ["."] +# watch_dirs: ["./src", "./views"] + +# file extensions to watch and trigger a rebuild +valid_ext: [".go", ".tpl", ".tmpl", ".html", ".css", ".js"] + +# file extensions that should not trigger a rebuild (but reloads) +no_rebuild_ext: [".tpl", ".tmpl", ".html", ".css", ".js"] + +# directories and files to ignore when watching for changes +ignore: ["assets", "tmp", "vendor", ".git", "node_modules"] + +# log path for build output +build_log: ./tmp/build.log + +# command to run after the build is complete +build_cmd: go build -o ./tmp/tebakaja_proxy . + +# command to run when starting the server +run_cmd: ./tmp/tebakaja_proxy + +# environment variables to set when running the server +envs: [ + "HOST=0.0.0.0", + "ENV=development" +] + +# delay before restarting the server after a change (milliseconds) +restart_delay: 200 + +# enables or disables color in the terminal output +colors: true + +# specify custom commands to run on file change +commands: { + "go": { + "run": "go run .", + "build": "go build -o ./tmp/tebakaja_proxy ." + }, + # "html": { + # "run": "echo HTML file changed", + # "build": "" + # } +} diff --git a/tests/utils_test.go b/tests/utils_test.go index 73cb26b..fa4dd59 100644 --- a/tests/utils_test.go +++ b/tests/utils_test.go @@ -4,7 +4,7 @@ import ( "reflect" "testing" - proxy "tebakaja_lb_proxy/proxy" + helpers "tebakaja_lb_proxy/proxy/helpers" ) func IsStringReflect(x interface{}) bool { @@ -28,7 +28,7 @@ func TestGetEndpointByRestService(t *testing.T) { for _, tt := range tests { t.Run(tt.service, func(t *testing.T) { - endpoint := proxy.GetEndpointByRestService(tt.service) + endpoint := helpers.GetEndpointService(tt.service) if got := IsStringReflect(endpoint); got != tt.want { t.Errorf("IsStringReflect(%v) = %v, want %v", endpoint, got, tt.want) diff --git a/tmp/.gitkeep b/tmp/.gitkeep new file mode 100644 index 0000000..e69de29