-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
197 changed files
with
14,365 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
node_modules | ||
dist | ||
pc | ||
data |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
# Start from golang base image | ||
FROM golang:alpine as builder | ||
|
||
# Install git. Git is required for fetching the dependencies. | ||
RUN apk update && apk add --no-cache git | ||
|
||
# Set the current working directory inside the container | ||
WORKDIR /app | ||
|
||
# Copy go mod and sum files | ||
COPY go.mod go.sum ./ | ||
|
||
# Download all dependencies. Dependencies will be cached if the go.mod and the go.sum files are not changed | ||
RUN go mod download | ||
|
||
COPY . . | ||
|
||
# Build the Go app | ||
RUN go build ./cmd/main.go | ||
|
||
# Start a new stage from scratch | ||
FROM alpine:latest | ||
RUN apk --no-cache add ca-certificates | ||
|
||
WORKDIR /opt/gofiber_server | ||
|
||
# Copy the Pre-built binary file from the previous stage + .env file | ||
COPY --from=builder /app/main . | ||
|
||
|
||
#Command to run the executable | ||
CMD ["./main"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package main | ||
|
||
import ( | ||
a "currency-telegram-webapp-backend/internal/app" | ||
"currency-telegram-webapp-backend/internal/config" | ||
"currency-telegram-webapp-backend/pkg/logger" | ||
"fmt" | ||
"os" | ||
"os/signal" | ||
"syscall" | ||
) | ||
|
||
func main() { | ||
c, err := config.GetConfig() | ||
if err != nil { | ||
fmt.Printf("Get config: %v\n", err) | ||
return | ||
} | ||
|
||
log, err := logger.NewLogger(logger.Config{ | ||
Type: c.LoggerType, | ||
}) | ||
if err != nil { | ||
fmt.Printf("Create logger: %v\n", err) | ||
return | ||
} | ||
|
||
app := a.CreateApp(c, log) | ||
|
||
done := make(chan struct{}, 1) | ||
sigs := make(chan os.Signal, 1) | ||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) | ||
|
||
go func() { | ||
<-sigs | ||
err = app.Shutdown() | ||
if err != nil { | ||
log.Fatalw("Shutdown", "error", err) | ||
} | ||
|
||
done <- struct{}{} | ||
}() | ||
|
||
app.Run() | ||
|
||
fmt.Println("Started") | ||
|
||
<-done | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
module currency-telegram-webapp-backend | ||
|
||
go 1.20 | ||
|
||
require ( | ||
github.com/goccy/go-json v0.10.2 | ||
github.com/gofiber/fiber/v2 v2.49.2 | ||
github.com/redis/go-redis/v9 v9.2.1 | ||
github.com/sethvargo/go-envconfig v0.9.0 | ||
github.com/valyala/fasthttp v1.50.0 | ||
go.uber.org/zap v1.26.0 | ||
) | ||
|
||
require ( | ||
github.com/andybalholm/brotli v1.0.5 // indirect | ||
github.com/cespare/xxhash/v2 v2.2.0 // indirect | ||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect | ||
github.com/google/uuid v1.3.1 // indirect | ||
github.com/klauspost/compress v1.16.7 // indirect | ||
github.com/mattn/go-colorable v0.1.13 // indirect | ||
github.com/mattn/go-isatty v0.0.19 // indirect | ||
github.com/mattn/go-runewidth v0.0.15 // indirect | ||
github.com/rivo/uniseg v0.2.0 // indirect | ||
github.com/valyala/bytebufferpool v1.0.0 // indirect | ||
github.com/valyala/tcplisten v1.0.0 // indirect | ||
go.uber.org/multierr v1.10.0 // indirect | ||
golang.org/x/sys v0.12.0 // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= | ||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= | ||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= | ||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= | ||
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/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
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/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= | ||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= | ||
github.com/gofiber/fiber/v2 v2.49.2 h1:ONEN3/Vc+dUCxxDgZZwpqvhISgHqb+bu+isBiEyKEQs= | ||
github.com/gofiber/fiber/v2 v2.49.2/go.mod h1:gNsKnyrmfEWFpJxQAV0qvW6l70K1dZGno12oLtukcts= | ||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= | ||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= | ||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= | ||
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= | ||
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.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= | ||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= | ||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= | ||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= | ||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= | ||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
github.com/redis/go-redis/v9 v9.2.1 h1:WlYJg71ODF0dVspZZCpYmoF1+U1Jjk9Rwd7pq6QmlCg= | ||
github.com/redis/go-redis/v9 v9.2.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= | ||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= | ||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= | ||
github.com/sethvargo/go-envconfig v0.9.0 h1:Q6FQ6hVEeTECULvkJZakq3dZMeBQ3JUpcKMfPQbKMDE= | ||
github.com/sethvargo/go-envconfig v0.9.0/go.mod h1:Iz1Gy1Sf3T64TQlJSvee81qDhf7YIlt8GMUX6yyNFs0= | ||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= | ||
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/fasthttp v1.50.0 h1:H7fweIlBm0rXLs2q0XbalvJ6r0CUPFWK3/bB4N13e9M= | ||
github.com/valyala/fasthttp v1.50.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= | ||
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= | ||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= | ||
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= | ||
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= | ||
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= | ||
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= | ||
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= | ||
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.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= | ||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
package app | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
|
||
"github.com/goccy/go-json" | ||
"github.com/valyala/fasthttp" | ||
"go.uber.org/zap" | ||
|
||
"fmt" | ||
|
||
resp "currency-telegram-webapp-backend/pkg/api_response" | ||
|
||
"currency-telegram-webapp-backend/internal/client" | ||
"currency-telegram-webapp-backend/internal/config" | ||
"currency-telegram-webapp-backend/internal/provider" | ||
"currency-telegram-webapp-backend/internal/store" | ||
|
||
"github.com/gofiber/fiber/v2" | ||
"github.com/gofiber/fiber/v2/middleware/recover" | ||
"github.com/gofiber/fiber/v2/utils" | ||
) | ||
|
||
type App struct { | ||
server *fiber.App | ||
conf *config.ServiceConfig | ||
log *zap.SugaredLogger | ||
store store.Store | ||
baseCurrency provider.Currency | ||
p provider.Provider | ||
api *client.Caller | ||
} | ||
|
||
func CreateApp(c *config.ServiceConfig, logger *zap.SugaredLogger) *App { | ||
clientApi := &client.Caller{ | ||
Client: &fasthttp.Client{}, | ||
} | ||
|
||
app := &App{ | ||
conf: c, | ||
log: logger, | ||
server: fiber.New(fiber.Config{ | ||
AppName: "backend currency telegram webapp", | ||
DisableStartupMessage: false, | ||
JSONEncoder: json.Marshal, | ||
JSONDecoder: json.Unmarshal, | ||
ErrorHandler: errorHandler(logger), | ||
EnableSplittingOnParsers: true, | ||
}), | ||
store: store.NewRedisStore(&c.Redis), | ||
baseCurrency: "", | ||
p: provider.NewFixedProvider(clientApi, c.FixerAccessToken), | ||
} | ||
|
||
// auth middleware | ||
app.server.Use(app.AuthMw) | ||
|
||
app.server.Get("/api/rates", app.getRate) | ||
|
||
// panic recover | ||
app.server.Use(recover.New()) | ||
|
||
// custom 404 handler | ||
app.server.Use(notFoundHandler) | ||
|
||
if err := app.initBaseCurrency(); err != nil { | ||
app.log.Fatalw("Init base currency", "error", err) | ||
} | ||
|
||
return app | ||
} | ||
|
||
func (app *App) Run() { | ||
go func() { | ||
if err := app.server.Listen(app.conf.ListenAddress); err != nil { | ||
app.log.Fatalw("Start server", "error", err) | ||
} | ||
}() | ||
} | ||
|
||
func (app *App) Shutdown() error { | ||
return app.server.ShutdownWithTimeout(config.ShutdownTimeout) | ||
} | ||
|
||
func errorHandler(log *zap.SugaredLogger) func(ctx *fiber.Ctx, err error) error { | ||
return func(ctx *fiber.Ctx, err error) error { | ||
// Status code defaults to 500 | ||
code := fiber.StatusInternalServerError | ||
errData := utils.StatusMessage(code) | ||
|
||
// Retrieve the custom status code if it's a *fiber.Error | ||
var fiberErr *fiber.Error | ||
if errors.As(err, &fiberErr) { | ||
code = fiberErr.Code | ||
errData = fiberErr.Message | ||
} | ||
|
||
log.Errorf("API panic recovered: %v", err) | ||
|
||
return ctx.Status(code).JSON(resp.New(false, errData)) | ||
} | ||
} | ||
|
||
func (app *App) initBaseCurrency() error { | ||
base, err := app.store.GetBase() | ||
if err != nil { | ||
if !errors.Is(err, store.ErrNotFound) { | ||
return fmt.Errorf("failed get base currency %w", err) | ||
} | ||
|
||
data, err := app.p.Latest(context.Background()) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if err := app.store.SetRates(data.Base, data.Timestamp, &data.Rates); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
app.baseCurrency = base | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package app | ||
|
||
import ( | ||
resp "currency-telegram-webapp-backend/pkg/api_response" | ||
"encoding/hex" | ||
"io" | ||
"net/url" | ||
"sort" | ||
|
||
"github.com/gofiber/fiber/v2" | ||
"github.com/gofiber/fiber/v2/utils" | ||
|
||
"crypto/hmac" | ||
"crypto/sha256" | ||
) | ||
|
||
// Auth middleware | ||
func (a *App) AuthMw(ctx *fiber.Ctx) error { | ||
q := ctx.Query("auth") | ||
if q == "" { | ||
return ctx.Status(fiber.StatusUnauthorized).JSON(resp.New(false, utils.StatusMessage(fiber.StatusUnauthorized))) | ||
} | ||
|
||
params, err := url.ParseQuery(q) | ||
if err != nil { | ||
a.log.Errorw("failed ParseQuery auth", "error", err) | ||
return ctx.Status(fiber.StatusInternalServerError).JSON(resp.New(false, utils.StatusMessage(fiber.StatusInternalServerError))) | ||
} | ||
|
||
data := make([]string, 0, len(params)-1) | ||
for k, v := range params { | ||
if k != "hash" { | ||
data = append(data, k+"="+v[0]) | ||
} | ||
} | ||
|
||
sort.Strings(data) | ||
|
||
res := "" | ||
for _, v := range data { | ||
if res != "" { | ||
res += "\n" | ||
} | ||
|
||
res += v | ||
} | ||
|
||
secretKey := hmac.New(sha256.New, []byte("WebAppData")) | ||
if _, err = io.WriteString(secretKey, a.conf.BotToken); err != nil { | ||
a.log.Errorw("failed create hmac", "error", err) | ||
return ctx.Status(fiber.StatusInternalServerError).JSON(resp.New(false, utils.StatusMessage(fiber.StatusInternalServerError))) | ||
} | ||
|
||
hash := hmac.New(sha256.New, secretKey.Sum(nil)) | ||
if _, err = io.WriteString(hash, res); err != nil { | ||
a.log.Errorw("failed create hmac", "error", err) | ||
return ctx.Status(fiber.StatusInternalServerError).JSON(resp.New(false, utils.StatusMessage(fiber.StatusInternalServerError))) | ||
} | ||
|
||
if hex.EncodeToString(hash.Sum(nil)) != params.Get("hash") { | ||
return ctx.Status(fiber.StatusUnauthorized).JSON(resp.New(false, utils.StatusMessage(fiber.StatusUnauthorized))) | ||
} | ||
|
||
return ctx.Next() | ||
} |
Oops, something went wrong.