Initial commit

This commit is contained in:
Vitaliy Pavlov 2024-05-10 13:17:18 +07:00
commit c4912af8fb
31 changed files with 1169 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.env

7
app/constants/errors.go Normal file
View File

@ -0,0 +1,7 @@
package constants
const (
AUTH_FAILED = "User with specified email and password not found"
NOT_FOUND = "Wrong request method or path"
UNAUTHORIZED = "Unauthorized"
)

5
app/constants/misc.go Normal file
View File

@ -0,0 +1,5 @@
package constants
const (
JWT_APP_ISS = "stc"
)

15
app/controllers/auth.go Normal file
View File

@ -0,0 +1,15 @@
package controllers
import (
"system-trace/core/auth"
"github.com/gofiber/fiber/v2"
)
func InitAuth(app *fiber.App, version *fiber.Router) {
v := *version
g := v.Group("/auth")
{
g.Post("/login", auth.ReqTokens)
}
}

17
app/router/router.go Normal file
View File

@ -0,0 +1,17 @@
package router
import (
"system-trace/core/app/controllers"
_ "system-trace/core/docs"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/swagger"
)
func Init(app *fiber.App) {
app.Get("/swagger/*", swagger.HandlerDefault)
v1 := app.Group("/v1")
{
controllers.InitAuth(app, &v1)
}
}

51
app/server.go Normal file
View File

@ -0,0 +1,51 @@
package app
import (
"os"
"system-trace/core/app/constants"
"system-trace/core/app/router"
"system-trace/core/auth/middlewares"
"system-trace/core/environment"
"github.com/goccy/go-json"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/recover"
)
var Instance *fiber.App
func Serve() {
Instance = initServer()
Instance.Use(recover.New())
Instance.Use(cors.New())
Instance.Use(middlewares.ValidateSession)
router.Init(Instance)
Instance.Use(func(c *fiber.Ctx) error {
return c.
Status(fiber.StatusNotFound).
JSON(fiber.Map{
"error": constants.NOT_FOUND,
})
})
Instance.Listen(":" + os.Getenv("APP_PORT"))
}
func initServer() *fiber.App {
debug := environment.IsDebug()
return fiber.New(fiber.Config{
JSONEncoder: json.Marshal,
JSONDecoder: json.Unmarshal,
CaseSensitive: true,
EnablePrintRoutes: debug,
Prefork: true,
AppName: "System Trace API",
ErrorHandler: func(c *fiber.Ctx, err error) error {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": err.Error(),
})
},
})
}

11
auth/README.md Normal file
View File

@ -0,0 +1,11 @@
## Password requirements:
- Minimum 1 uppercase character
- Minimum 1 lowercase character
- Minimum 1 digit
- Minimum 1 special character
- Minimal length 8 characters
### Auth request return Set-Cookie headers to set server-cookie on client
### AccessToken lifetime is 1 hour
### RefreshToken lifetime is 24 hours
### When AccessToken expires, RefreshToken requests a new one. Old pair becomes revoked and creates new one

77
auth/auth.go Normal file
View File

@ -0,0 +1,77 @@
package auth
import (
"errors"
"system-trace/core/app/constants"
"system-trace/core/users"
"system-trace/core/validators"
"time"
"github.com/gofiber/fiber/v2"
)
// ReqTokens godoc
// @Summary Request pair of tokens
// @Description Returns pair of access and refresh tokens
// @Tags auth
// @Produce json
// @Param request body AuthBody true "Request body"
// @Header 200 {string} Token "accessToken=..."
// @Header 200 {string} Set-Cookie "refreshToken=..."
// @Success 200
// @Router /auth/login [post]
func ReqTokens(c *fiber.Ctx) error {
ab := new(AuthBody)
if err := c.BodyParser(ab); err != nil {
return err
}
if err := validators.Validate(c, ab); err != nil {
return err
}
u, err := users.FindByEmailAndPassword(ab.Email, ab.Password)
if err != nil {
e := err.Error()
if e == "sql: no rows in result set" {
e = constants.AUTH_FAILED
}
return c.
Status(fiber.StatusBadRequest).
JSON(fiber.Map{
"error": e,
})
}
if u != nil {
p, err := genPair(u)
if err != nil {
return c.
Status(fiber.StatusBadRequest).
JSON(fiber.Map{
"error": err.Error(),
})
}
setCookie(c, p)
return c.SendStatus(fiber.StatusOK)
}
return errors.New(constants.AUTH_FAILED)
}
func setCookie(c *fiber.Ctx, p *PairTokens) {
// Access token
atc := new(fiber.Cookie)
atc.Name = "accessToken"
atc.Value = p.AccessToken
atc.Expires = time.Now().Add(time.Duration(AccessTokenLifetime) * time.Hour)
atc.Secure = true
atc.HTTPOnly = true
c.Cookie(atc)
// Refresh token
rtc := new(fiber.Cookie)
rtc.Name = "refreshToken"
rtc.Value = p.RefreshToken
rtc.Expires = time.Now().Add(time.Duration(RefreshTokenLifetime) * time.Hour)
rtc.Secure = true
rtc.HTTPOnly = true
c.Cookie(rtc)
}

View File

@ -0,0 +1,40 @@
package middlewares
import (
"fmt"
"net/http"
"strings"
"system-trace/core/app/constants"
"system-trace/core/utils"
"github.com/gofiber/fiber/v2"
)
func ValidateSession(c *fiber.Ctx) error {
header := c.GetReqHeaders()[http.CanonicalHeaderKey("Authorization")]
if len(header) <= 0 || len(header[0]) <= 0 || !validateToken(c, header[0]) {
return c.Status(http.StatusForbidden).JSON(fiber.Map{
"error": constants.UNAUTHORIZED,
})
}
return c.Next()
}
func validateToken(c *fiber.Ctx, hash string) bool {
splitted := strings.Split(hash, " ")
if len(splitted) <= 1 {
return false
}
claims, err := utils.ValidateJWT(splitted[1])
fmt.Println(claims, err)
// id, ok := claims["ID"].(string)
// TODO validate date and check refresh token
if err != nil || claims["iss"] != constants.JWT_APP_ISS {
return false
}
// c.Locals("userId", id)
return true
}

76
auth/tokens.go Normal file
View File

@ -0,0 +1,76 @@
package auth
import (
"context"
"fmt"
"system-trace/core/app/constants"
"system-trace/core/database"
"system-trace/core/database/entities"
"system-trace/core/utils"
"time"
"github.com/golang-jwt/jwt/v5"
)
const (
AccessTokenLifetime int8 = 3
RefreshTokenLifetime int8 = 24
)
func genPair(u *entities.User) (*PairTokens, error) {
at, rt, err := genTokens(u)
err = insertPair(u.ID, at, rt)
if err != nil {
return nil, err
}
p := PairTokens{
AccessToken: at,
RefreshToken: rt,
}
return &p, nil
}
func genTokens(u *entities.User) (string, string, error) {
at, err := genToken(fmt.Sprintf("%d", u.ID), AccessTokenLifetime)
if err != nil {
return "", "", err
}
rt, err := genToken(at, RefreshTokenLifetime)
if err != nil {
return "", "", err
}
return at, rt, nil
}
func genToken(sub string, hours int8) (string, error) {
fmt.Println(sub, hours)
c := jwt.MapClaims{
"iss": constants.JWT_APP_ISS,
"sub": sub,
"iat": time.Now().Unix(),
"exp": time.Now().Add(time.Duration(hours) * time.Hour).Unix(),
}
a, err := utils.SignJWT(c)
return a, err
}
func insertPair(id int32, at, rt string) error {
aut := entities.AuthToken{
UserID: id,
AccessToken: at,
RefreshToken: rt,
}
ctx := context.Background()
_, err := database.PG.NewInsert().
Model(&aut).
Exec(ctx)
return err
}

11
auth/types.go Normal file
View File

@ -0,0 +1,11 @@
package auth
type PairTokens struct {
AccessToken string `json:"accessToken"`
RefreshToken string `json:"refreshToken"`
}
type AuthBody struct {
Email string `json:"email" validate:"required,email" example:"john@proton.mail"`
Password string `json:"password" validate:"required,password" example:"Aasdfg1!"`
}

80
database/database.go Normal file
View File

@ -0,0 +1,80 @@
package database
import (
"context"
"crypto/tls"
"database/sql"
"fmt"
"os"
"system-trace/core/database/entities"
"system-trace/core/environment"
"time"
"github.com/gofiber/fiber/v2"
"github.com/uptrace/bun"
"github.com/uptrace/bun/dialect/pgdialect"
"github.com/uptrace/bun/driver/pgdriver"
"github.com/uptrace/bun/extra/bundebug"
)
var PG *bun.DB
func Connect() {
pgconn := pgdriver.NewConnector(
pgdriver.WithNetwork("tcp"),
pgdriver.WithAddr(fmt.Sprintf("%s:%s", os.Getenv("DB_HOST"), os.Getenv("DB_PORT"))),
pgdriver.WithTLSConfig(&tls.Config{InsecureSkipVerify: true}),
pgdriver.WithInsecure(true),
pgdriver.WithUser(os.Getenv("DB_USER")),
pgdriver.WithPassword(os.Getenv("DB_PASS")),
pgdriver.WithDatabase(os.Getenv("DB_NAME")),
pgdriver.WithApplicationName("st-controller"),
pgdriver.WithTimeout(5*time.Second),
pgdriver.WithDialTimeout(5*time.Second),
pgdriver.WithReadTimeout(5*time.Second),
pgdriver.WithWriteTimeout(5*time.Second),
)
sqldb := sql.OpenDB(pgconn)
db := bun.NewDB(sqldb, pgdialect.New())
if environment.IsDebug() {
db.AddQueryHook(bundebug.NewQueryHook(
bundebug.WithVerbose(true),
bundebug.FromEnv("BUNDEBUG"),
))
}
if !fiber.IsChild() {
err := createSchema(db)
if err != nil {
panic(err)
}
}
PG = db
}
func createSchema(db *bun.DB) error {
models := []interface{}{
(*entities.Group)(nil),
(*entities.GroupPermission)(nil),
(*entities.User)(nil),
(*entities.Session)(nil),
(*entities.Server)(nil),
(*entities.AuthToken)(nil),
}
ctx := context.Background()
for _, model := range models {
_, err := db.
NewCreateTable().
Model(model).
IfNotExists().
Exec(ctx)
if err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,8 @@
package entities
type AuthToken struct {
UserID int32
AccessToken string `bun:"type:varchar"`
RefreshToken string `bun:"type:varchar"`
IsRevoked bool `bun:",default:false"`
}

View File

@ -0,0 +1,16 @@
package entities
import "time"
type Group struct {
ID int32 `bun:",pk,autoincrement"`
IssuerID int32 `bun:",notnull"`
Issuer *User `bun:"rel:belongs-to,join:issuer_id=id"`
Name string `bun:",notnull,unique"`
GroupID int32 `bun:",notnull"`
Users []*User `bun:"rel:has-many,join:id=group_id"`
Permissions []*GroupPermission `bun:"rel:has-many,join:id=group_id"`
CreatedAt time.Time `bun:",notnull,default:current_timestamp"`
UpdatedAt time.Time
DeletedAt time.Time `bun:",soft_delete,nullzero"`
}

View File

@ -0,0 +1,6 @@
package entities
type GroupPermission struct {
GroupID int32 `bun:",notnull" json:"-"`
Value int8 `bun:",notnull"`
}

View File

@ -0,0 +1,30 @@
package entities
import (
"context"
"time"
"github.com/uptrace/bun"
)
type Server struct {
ID int64 `bun:",pk,autoincrement"`
Hostname string `bun:",notnull"`
IP string `bun:",notnull"`
IsOnline bool `bun:",notnull,default:true"`
LastOnline time.Time
CreatedAt time.Time `bun:",notnull,default:current_timestamp"`
UpdatedAt time.Time
}
var _ bun.BeforeAppendModelHook = (*Server)(nil)
func (s *Server) BeforeAppendModel(ctx context.Context, query bun.Query) error {
switch query.(type) {
case *bun.InsertQuery:
s.LastOnline = time.Now()
case *bun.UpdateQuery:
s.UpdatedAt = time.Now()
}
return nil
}

View File

@ -0,0 +1,29 @@
package entities
import (
"context"
"time"
"github.com/uptrace/bun"
)
type Session struct {
ID int64 `bun:",pk,autoincrement"`
IssuerID int32 `bun:",notnull"`
Issuer *User `bun:"rel:belongs-to,join:issuer_id=id"`
Configuration map[string]interface{} `bun:"type:jsonb"`
CreatedAt time.Time `bun:",notnull,default:current_timestamp"`
FinishedAt time.Time
UpdatedAt time.Time
DeletedAt time.Time `bun:",soft_delete,nullzero"`
}
var _ bun.BeforeAppendModelHook = (*Session)(nil)
func (s *Session) BeforeAppendModel(ctx context.Context, query bun.Query) error {
switch query.(type) {
case *bun.UpdateQuery:
s.UpdatedAt = time.Now()
}
return nil
}

34
database/entities/user.go Normal file
View File

@ -0,0 +1,34 @@
package entities
import (
"context"
"time"
"github.com/uptrace/bun"
)
type User struct {
ID int32 `bun:",pk,autoincrement"`
Email string `bun:",notnull,unique"`
PasswordHash string `bun:",notnull,type:varchar(64)"`
PasswordLength int8 `bun:",notnull"`
RealName string `bun:",notnull"`
GroupID int32 `bun:",notnull"`
Group *Group `bun:"rel:belongs-to,join:group_id=id"`
IsRequiredToSetPassword bool `bun:",notnull,default:true"`
IsActive bool `bun:",notnull,default:true"`
LastLogin time.Time
CreatedAt time.Time `bun:",notnull,default:current_timestamp"`
UpdatedAt time.Time
DeletedAt time.Time `bun:",soft_delete,nullzero"`
}
var _ bun.BeforeAppendModelHook = (*User)(nil)
func (u *User) BeforeAppendModel(ctx context.Context, query bun.Query) error {
switch query.(type) {
case *bun.UpdateQuery:
u.UpdatedAt = time.Now()
}
return nil
}

93
docs/docs.go Normal file
View File

@ -0,0 +1,93 @@
// Package docs Code generated by swaggo/swag. DO NOT EDIT
package docs
import "github.com/swaggo/swag"
const docTemplate = `{
"schemes": {{ marshal .Schemes }},
"swagger": "2.0",
"info": {
"description": "{{escape .Description}}",
"title": "{{.Title}}",
"contact": {
"name": "https://peresvet.it"
},
"version": "{{.Version}}"
},
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/auth/login": {
"post": {
"description": "Returns pair of access and refresh tokens",
"produces": [
"application/json"
],
"tags": [
"auth"
],
"summary": "Request pair of tokens",
"parameters": [
{
"description": "Request body",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/auth.AuthBody"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
}
},
"definitions": {
"auth.AuthBody": {
"type": "object",
"required": [
"email",
"password"
],
"properties": {
"email": {
"type": "string",
"example": "john@proton.mail"
},
"password": {
"type": "string",
"example": "Aasdfgh1!"
}
}
}
},
"securityDefinitions": {
"Bearer": {
"type": "apiKey",
"name": "Authorization",
"in": "header"
}
}
}`
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{
Version: "1.0",
Host: "",
BasePath: "/v1",
Schemes: []string{},
Title: "System Trace API",
Description: "",
InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate,
LeftDelim: "{{",
RightDelim: "}}",
}
func init() {
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
}

67
docs/swagger.json Normal file
View File

@ -0,0 +1,67 @@
{
"swagger": "2.0",
"info": {
"title": "System Trace API",
"contact": {
"name": "https://peresvet.it"
},
"version": "1.0"
},
"basePath": "/v1",
"paths": {
"/auth/login": {
"post": {
"description": "Returns pair of access and refresh tokens",
"produces": [
"application/json"
],
"tags": [
"auth"
],
"summary": "Request pair of tokens",
"parameters": [
{
"description": "Request body",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/auth.AuthBody"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
}
},
"definitions": {
"auth.AuthBody": {
"type": "object",
"required": [
"email",
"password"
],
"properties": {
"email": {
"type": "string",
"example": "john@proton.mail"
},
"password": {
"type": "string",
"example": "Aasdfgh1!"
}
}
}
},
"securityDefinitions": {
"Bearer": {
"type": "apiKey",
"name": "Authorization",
"in": "header"
}
}
}

44
docs/swagger.yaml Normal file
View File

@ -0,0 +1,44 @@
basePath: /v1
definitions:
auth.AuthBody:
properties:
email:
example: john@proton.mail
type: string
password:
example: Aasdfgh1!
type: string
required:
- email
- password
type: object
info:
contact:
name: https://peresvet.it
title: System Trace API
version: "1.0"
paths:
/auth/login:
post:
description: Returns pair of access and refresh tokens
parameters:
- description: Request body
in: body
name: request
required: true
schema:
$ref: '#/definitions/auth.AuthBody'
produces:
- application/json
responses:
"200":
description: OK
summary: Request pair of tokens
tags:
- auth
securityDefinitions:
Bearer:
in: header
name: Authorization
type: apiKey
swagger: "2.0"

View File

@ -0,0 +1,19 @@
package environment
import (
"log"
"os"
"github.com/joho/godotenv"
)
func Load() {
err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file")
}
}
func IsDebug() bool {
return os.Getenv("DEBUG") == "true"
}

67
go.mod Normal file
View File

@ -0,0 +1,67 @@
module system-trace/core
go 1.22.3
require (
github.com/go-playground/validator v9.31.0+incompatible
github.com/go-playground/validator/v10 v10.20.0
github.com/goccy/go-json v0.10.2
github.com/gofiber/fiber v1.14.6
github.com/gofiber/fiber/v2 v2.52.4
github.com/gofiber/swagger v1.0.0
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/joho/godotenv v1.5.1
github.com/swaggo/swag v1.16.3
github.com/uptrace/bun v1.2.1
github.com/uptrace/bun/dialect/pgdialect v1.2.1
github.com/uptrace/bun/driver/pgdriver v1.2.1
github.com/uptrace/bun/extra/bundebug v1.2.1
github.com/wasilibs/go-re2 v1.5.2
)
require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.6 // indirect
github.com/go-openapi/spec v0.20.4 // indirect
github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/gofiber/utils v0.0.10 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/schema v1.1.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/klauspost/compress v1.17.8 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/magefile/mage v1.14.0 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/stretchr/testify v1.9.0 // indirect
github.com/swaggo/files/v2 v2.0.0 // indirect
github.com/tetratelabs/wazero v1.7.0 // indirect
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.52.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/tools v0.7.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
mellium.im/sasl v0.3.1 // indirect
)

174
go.sum Normal file
View File

@ -0,0 +1,174 @@
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
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/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
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 v9.31.0+incompatible h1:UA72EPEogEnq76ehGdEDp4Mit+3FDh548oRqwVgNsHA=
github.com/go-playground/validator v9.31.0+incompatible/go.mod h1:yrEkQXlcI+PugkyDjY2bRrL/UBU4f3rvrgkN3V8JEig=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
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 v1.14.6 h1:QRUPvPmr8ijQuGo1MgupHBn8E+wW0IKqiOvIZPtV70o=
github.com/gofiber/fiber v1.14.6/go.mod h1:Yw2ekF1YDPreO9V6TMYjynu94xRxZBdaa8X5HhHsjCM=
github.com/gofiber/fiber/v2 v2.52.4 h1:P+T+4iK7VaqUsq2PALYEfBBo6bJZ4q3FP8cZ84EggTM=
github.com/gofiber/fiber/v2 v2.52.4/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
github.com/gofiber/swagger v1.0.0 h1:BzUzDS9ZT6fDUa692kxmfOjc1DZiloLiPK/W5z1H1tc=
github.com/gofiber/swagger v1.0.0/go.mod h1:QrYNF1Yrc7ggGK6ATsJ6yfH/8Zi5bu9lA7wB8TmCecg=
github.com/gofiber/utils v0.0.10 h1:3Mr7X7JdCUo7CWf/i5sajSaDmArEDtti8bM1JUVso2U=
github.com/gofiber/utils v0.0.10/go.mod h1:9J5aHFUIjq0XfknT4+hdSMG6/jzfaAgCu4HEbWDeBlo=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
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/gorilla/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY=
github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
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/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
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/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
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=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
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.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
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/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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw=
github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM=
github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg=
github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk=
github.com/tetratelabs/wazero v1.7.0 h1:jg5qPydno59wqjpGrHph81lbtHzTrWzwwtD4cD88+hQ=
github.com/tetratelabs/wazero v1.7.0/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y=
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
github.com/uptrace/bun v1.2.1 h1:2ENAcfeCfaY5+2e7z5pXrzFKy3vS8VXvkCag6N2Yzfk=
github.com/uptrace/bun v1.2.1/go.mod h1:cNg+pWBUMmJ8rHnETgf65CEvn3aIKErrwOD6IA8e+Ec=
github.com/uptrace/bun/dialect/pgdialect v1.2.1 h1:ceP99r03u+s8ylaDE/RzgcajwGiC76Jz3nS2ZgyPQ4M=
github.com/uptrace/bun/dialect/pgdialect v1.2.1/go.mod h1:mv6B12cisvSc6bwKm9q9wcrr26awkZK8QXM+nso9n2U=
github.com/uptrace/bun/driver/pgdriver v1.2.1 h1:Cp6c1tKzbTIyL8o0cGT6cOhTsmQZdsUNhgcV51dsmLU=
github.com/uptrace/bun/driver/pgdriver v1.2.1/go.mod h1:jEd3WGx74hWLat3/IkesOoWNjrFNUDADK3nkyOFOOJM=
github.com/uptrace/bun/extra/bundebug v1.2.1 h1:85MYpX3QESYI02YerKxUi1CD9mHuLrc2BXs1eOCtQus=
github.com/uptrace/bun/extra/bundebug v1.2.1/go.mod h1:sfGKIi0HSGxsTC/sgIHGwpnYduHHYhdMeOIwurgSY+Y=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
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.16.0/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA=
github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7gU0=
github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/wasilibs/go-re2 v1.5.2 h1:fDO2TJrRzRrv3jD0gzOvmZ2UM4Yt9YXOEdLrlNc/Ies=
github.com/wasilibs/go-re2 v1.5.2/go.mod h1:UqqxQ1O99boQUm1r61H/IYGiGQOS/P88K7hU5nLNkEg=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo=
mellium.im/sasl v0.3.1/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=

23
main.go Normal file
View File

@ -0,0 +1,23 @@
package main
import (
"system-trace/core/app"
"system-trace/core/database"
"system-trace/core/environment"
"system-trace/core/validators"
)
// @title System Trace API
// @version 1.0
// @contact.name https://peresvet.it
// @BasePath /v1
// @securityDefinitions.apikey Bearer
// @in header
// @name Authorization
// @externalDocs.description OpenAPI
func main() {
environment.Load()
validators.RegisterValidators()
database.Connect()
app.Serve()
}

22
users/sql.go Normal file
View File

@ -0,0 +1,22 @@
package users
import (
"context"
"system-trace/core/database"
"system-trace/core/database/entities"
"system-trace/core/utils"
)
func FindByEmailAndPassword(email, password string) (*entities.User, error) {
passwordHash := utils.SHA256(password)
ctx := context.Background()
u := new(entities.User)
err := database.PG.NewSelect().
Model(u).
Where("email = ?", email).
Where("password_hash = ?", passwordHash).
Column("id").
Scan(ctx)
return u, err
}

9
users/users.go Normal file
View File

@ -0,0 +1,9 @@
package users
import (
"github.com/gofiber/fiber/v2"
)
func Get(c *fiber.Ctx) error {
return nil
}

13
utils/crypto.go Normal file
View File

@ -0,0 +1,13 @@
package utils
import (
"crypto/sha256"
"fmt"
)
func SHA256(s string) string {
h := sha256.New()
h.Write([]byte(s))
bs := h.Sum(nil)
return fmt.Sprintf("%x", bs)
}

28
utils/jwt.go Normal file
View File

@ -0,0 +1,28 @@
package utils
import (
"fmt"
"github.com/golang-jwt/jwt/v5"
)
func SignJWT(c jwt.Claims) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
ts, err := token.SignedString([]byte("123"))
return ts, err
}
func ValidateJWT(h string) (jwt.MapClaims, error) {
token, err := jwt.Parse(h, func(token *jwt.Token) (interface{}, error) {
return []byte("123"), nil
})
if err != nil {
fmt.Println(err)
return nil, err
}
claims := token.Claims.(jwt.MapClaims)
return claims, err
}

28
validators/password.go Normal file
View File

@ -0,0 +1,28 @@
package validators
import (
regexp "github.com/wasilibs/go-re2"
"github.com/go-playground/validator/v10"
)
func regPassword(v *XValidator) {
v.validator.RegisterValidation("password", func(fl validator.FieldLevel) bool {
tests := []string{
`[a-z]`,
`[A-Z]`,
`[\d]`,
`[@$!%*?&]`,
`[A-Za-z\d@$!%*?&]`,
`.{8,}`,
}
password := fl.Field().String()
for _, test := range tests {
t, _ := regexp.MatchString(test, password)
if !t {
return false
}
}
return true
})
}

68
validators/validator.go Normal file
View File

@ -0,0 +1,68 @@
package validators
import (
"errors"
"fmt"
"strings"
"github.com/go-playground/validator/v10"
"github.com/gofiber/fiber/v2"
)
type (
ErrorResponse struct {
Error bool
FailedField string
Tag string
}
XValidator struct {
validator *validator.Validate
}
)
var Validator *XValidator
var validate = validator.New()
func RegisterValidators() {
Validator = &XValidator{
validator: validate,
}
regPassword(Validator)
}
func Validate(c *fiber.Ctx, data interface{}) error {
if errs := Validator.validateFields(data); len(errs) > 0 && errs[0].Error {
errMsgs := make([]string, 0)
for _, err := range errs {
errMsgs = append(errMsgs, fmt.Sprintf(
"field '%s' needs to implement '%s' rule",
err.FailedField,
err.Tag,
))
}
return errors.New(strings.Join(errMsgs, " || "))
}
return nil
}
func (v XValidator) validateFields(data interface{}) []ErrorResponse {
validationErrors := []ErrorResponse{}
errs := validate.Struct(data)
if errs != nil {
for _, err := range errs.(validator.ValidationErrors) {
elem := ErrorResponse{
FailedField: err.Field(),
Tag: err.ActualTag(),
Error: true,
}
validationErrors = append(validationErrors, elem)
}
}
return validationErrors
}