task2 backend

This commit is contained in:
2020-01-04 12:37:56 +01:00
parent ecf2a0a2dd
commit 6cfe7d338a
9 changed files with 565 additions and 0 deletions
+180
View File
@@ -0,0 +1,180 @@
package lib
import (
"crypto/ecdsa"
"encoding/json"
"errors"
"fmt"
"net/http"
"sort"
"strings"
"time"
jwtgo "github.com/dgrijalva/jwt-go"
"github.com/go-redis/redis/v7"
routing "github.com/jackwhelpton/fasthttp-routing/v2"
"github.com/jackwhelpton/fasthttp-routing/v2/access"
"github.com/jackwhelpton/fasthttp-routing/v2/cors"
"github.com/jackwhelpton/fasthttp-routing/v2/fault"
log "github.com/sirupsen/logrus"
"github.com/valyala/fasthttp"
)
const (
URL = "/check"
URLHealthz = "/healthz"
)
type Api struct {
Base string
Alg string
Pub *ecdsa.PublicKey
Priv *ecdsa.PrivateKey
ScoresKey string
WinnersKey string
Redis *redis.Client
}
func (a *Api) GetHandler() fasthttp.RequestHandler {
crs := cors.Options{
AllowOrigins: "*",
AllowHeaders: "*",
AllowMethods: "*",
AllowCredentials: true,
}
router := routing.New()
router.Use(
access.Logger(log.Debugf),
cors.Handler(crs),
fault.PanicHandler(log.Warnf),
)
base := strings.TrimSuffix(a.Base, "/")
api := router.Group(base)
api.Post(URL, a.checkScore)
api.Get(URLHealthz, a.healthCheck)
return router.HandleRequest
}
func (a *Api) healthCheck(ctx *routing.Context) (err error) {
_, err = a.Redis.Ping().Result()
if err != nil {
return routing.NewHTTPError(http.StatusInternalServerError, err.Error())
}
ctx.Response.Header.Set("Content-Type", "application/json")
_, err = ctx.WriteString("{\"health\":\"ok\"}")
if err != nil {
return routing.NewHTTPError(http.StatusInternalServerError, err.Error())
}
return
}
func (a *Api) checkScore(ctx *routing.Context) (err error) {
type Score struct {
Key string `json:"key"`
Score int `json:"score"`
}
type Response struct {
Top []redis.Z `json:"top"`
Msg []string `json:"msg"`
}
now := time.Now()
body := ctx.Request.Body()
claims := &jwtgo.StandardClaims{}
score := &Score{}
err = json.Unmarshal(body, score)
if err != nil {
return routing.NewHTTPError(http.StatusBadRequest, err.Error())
}
_, err = jwtgo.ParseWithClaims(score.Key, claims, a.getKey)
if err != nil {
return routing.NewHTTPError(http.StatusForbidden, err.Error())
}
nowunix := fmt.Sprintf("%d", now.UnixNano()) // 1578057345996508551
member := fmt.Sprintf("%s:%s", nowunix, claims.Subject)
if len(nowunix) != 19 {
panic(errors.New("o rly"))
}
_, err = a.Redis.ZAddNX(a.ScoresKey, &redis.Z{Score: float64(score.Score), Member: member}).Result()
if err != nil {
return routing.NewHTTPError(http.StatusInternalServerError, err.Error())
}
zz, err := a.Redis.ZRevRangeWithScores(a.ScoresKey, 0, 2).Result()
if err != nil {
return routing.NewHTTPError(http.StatusInternalServerError, err.Error())
}
zz = append(zz, redis.Z{
Score: 201920192019,
Member: fmt.Sprintf("%d:%s", time.Unix(claims.IssuedAt, now.Unix()).UnixNano(), claims.Issuer),
})
sort.Slice(zz, func(i, j int) bool {
return zz[i].Score > zz[j].Score
})
resp := &Response{Top: zz}
if int(zz[0].Score) > score.Score {
resp.Msg = []string{
`ух, хорошая игра, но нужно стараться еще!`,
}
} else {
resp.Msg = []string{
`поздравляю с победой`,
`следующий уровень ищи здесь:`,
`nc 1451e73305328869824eda4c81a75cb5.mehrweg.ga 31337`,
`отправь туда ключ чтобы открыть`,
}
_, err = a.Redis.SAdd(a.WinnersKey, claims.Id).Result()
if err != nil {
return routing.NewHTTPError(http.StatusInternalServerError, err.Error())
}
}
body, err = json.Marshal(resp)
if err != nil {
return routing.NewHTTPError(http.StatusInternalServerError, err.Error())
}
ctx.Response.Header.Set("Content-Type", "application/json")
err = ctx.Write(body)
if err != nil {
return routing.NewHTTPError(http.StatusInternalServerError, err.Error())
}
return
}
func (a *Api) getPrivKey(t *jwtgo.Token) (interface{}, error) {
if _, ok := t.Method.(*jwtgo.SigningMethodECDSA); !ok {
return nil, errors.New("unexpected signing method")
}
return a.Priv, nil
}
func (a *Api) getKey(t *jwtgo.Token) (interface{}, error) {
if _, ok := t.Method.(*jwtgo.SigningMethodECDSA); !ok {
return nil, errors.New("unexpected signing method")
}
return a.Pub, nil
}
+18
View File
@@ -0,0 +1,18 @@
package lib
var (
ConfPath = "config"
ConfName = "task2"
)
type AppConfig struct {
HttpAPI struct {
Base string `mapstructure:"base"`
Addr string `mapstructure:"addr"`
} `mapstructure:"httpApi"`
Redis struct {
Addr string `mapstructure:"addr"`
ScoreKey string `mapstructure:"scoreKey"`
WinnersKey string `mapstructure:"winnersKey"`
} `mapstructure:"redis"`
}
+38
View File
@@ -0,0 +1,38 @@
package configure
import (
"strings"
"github.com/sirupsen/logrus"
"github.com/spf13/viper"
"github.com/mainnika/a-quest/task2-backend/lib"
"github.com/mainnika/a-quest/task2-backend/lib/env"
)
var Config lib.AppConfig
func init() {
path := env.ConfigPath
name := env.ConfigName
if len(path) == 0 {
path = lib.ConfPath
}
if len(name) == 0 {
name = lib.ConfName
}
viper.AddConfigPath(path)
viper.SetConfigName(name)
viper.SetEnvPrefix(env.Prefix)
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.AutomaticEnv()
err, _ := viper.ReadInConfig(), viper.Unmarshal(&Config)
if err != nil {
logrus.Fatal(err)
}
}
+13
View File
@@ -0,0 +1,13 @@
package env
import (
"fmt"
"os"
)
var (
Prefix = "CFG"
IsDevelopment = len(os.Getenv("DEBUG")) > 0
ConfigPath = os.Getenv(fmt.Sprintf("%s_PATH", Prefix))
ConfigName = os.Getenv(fmt.Sprintf("%s_NAME", Prefix))
)