Initial commit
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/casbin/casbin/v2"
|
||||
gormadapter "github.com/casbin/gorm-adapter/v3"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type AuthConfig struct {
|
||||
*gormadapter.Adapter
|
||||
Type string `yaml:"type" env:"AUTH_TYPE" env-default:"simple"`
|
||||
Casbin *Casbin
|
||||
Enforcer *casbin.Enforcer
|
||||
}
|
||||
|
||||
func (d *AuthConfig) Setup(db *gorm.DB, file string) {
|
||||
adapter, err := gormadapter.NewAdapterByDB(db)
|
||||
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to initialize casbin adapter: %v", err))
|
||||
}
|
||||
d.Adapter = adapter
|
||||
enforcer, err := casbin.NewEnforcer(file)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
enforcer.SetAdapter(adapter)
|
||||
err = enforcer.LoadPolicy()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
d.Enforcer = enforcer
|
||||
authConf := CasbinAuthConfig{
|
||||
Enforcer: d.Enforcer,
|
||||
PolicyAdapter: d.Adapter,
|
||||
Lookup: func(ctx *fiber.Ctx) string {
|
||||
userId := ctx.Locals("user_id")
|
||||
if userId != nil {
|
||||
return userId.(string)
|
||||
}
|
||||
|
||||
return ""
|
||||
},
|
||||
Unauthorized: func(c *fiber.Ctx) error {
|
||||
var err fiber.Error
|
||||
err.Code = fiber.StatusUnauthorized
|
||||
return CustomErrorHandler(c, &err)
|
||||
},
|
||||
Forbidden: func(c *fiber.Ctx) error {
|
||||
var err fiber.Error
|
||||
err.Code = fiber.StatusForbidden
|
||||
return CustomErrorHandler(c, &err)
|
||||
},
|
||||
}
|
||||
d.Casbin = CasbinAuth(authConf)
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package config
|
||||
|
||||
// CacheConfig es la configuración para el almacenamiento en caché
|
||||
type CacheConfig struct {
|
||||
Driver string `yaml:"driver" env:"CACHE_DRIVER"`
|
||||
Name string `yaml:"name" env:"CACHE_NAME"`
|
||||
}
|
||||
|
||||
// Setup inicializa el almacenamiento en caché
|
||||
func (c *CacheConfig) Setup() {
|
||||
switch c.Driver {
|
||||
case "memory":
|
||||
// Aquí puedes inicializar un almacenamiento en memoria si es necesario
|
||||
// Por ejemplo, utilizando un mapa o una biblioteca específica para el almacenamiento en memoria.
|
||||
default:
|
||||
// Otras configuraciones de almacenamiento en caché
|
||||
// Puedes agregar lógica para otros controladores aquí si los usas
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/casbin/casbin/v2"
|
||||
gormadapter "github.com/casbin/gorm-adapter/v3"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
// Config holds the configuration for the middleware
|
||||
type CasbinAuthConfig struct {
|
||||
// ModelFilePath is path to model file for Casbin.
|
||||
// Optional. Default: "./model.conf".
|
||||
Enforcer *casbin.Enforcer
|
||||
|
||||
// PolicyAdapter is an interface for different persistent providers.
|
||||
// Optional. Default: fileadapter.NewAdapter("./policy.csv").
|
||||
PolicyAdapter *gormadapter.Adapter
|
||||
|
||||
// Lookup is a function that is used to look up current subject.
|
||||
// An empty string is considered as unauthenticated user.
|
||||
// Optional. Default: func(c *fiber.Ctx) string { return "" }
|
||||
Lookup func(*fiber.Ctx) string
|
||||
|
||||
// Unauthorized defines the response body for unauthorized responses.
|
||||
// Optional. Default: func(c *fiber.Ctx) error { return c.SendStatus(401) }
|
||||
Unauthorized fiber.Handler
|
||||
|
||||
// Forbidden defines the response body for forbidden responses.
|
||||
// Optional. Default: func(c *fiber.Ctx) error { return c.SendStatus(403) }
|
||||
Forbidden fiber.Handler
|
||||
}
|
||||
|
||||
// Casbin ...
|
||||
type Casbin struct {
|
||||
config CasbinAuthConfig
|
||||
enforcer *casbin.Enforcer
|
||||
}
|
||||
|
||||
// New creates an authorization middleware for use in Fiber
|
||||
func CasbinAuth(config ...CasbinAuthConfig) *Casbin {
|
||||
|
||||
var cfg CasbinAuthConfig
|
||||
if len(config) > 0 {
|
||||
cfg = config[0]
|
||||
}
|
||||
|
||||
if cfg.Lookup == nil {
|
||||
cfg.Lookup = func(c *fiber.Ctx) string { return "" }
|
||||
}
|
||||
|
||||
if cfg.Unauthorized == nil {
|
||||
cfg.Unauthorized = func(c *fiber.Ctx) error {
|
||||
return c.SendStatus(fiber.StatusUnauthorized)
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.Forbidden == nil {
|
||||
cfg.Forbidden = func(c *fiber.Ctx) error {
|
||||
return c.SendStatus(fiber.StatusForbidden)
|
||||
}
|
||||
}
|
||||
|
||||
return &Casbin{
|
||||
config: cfg,
|
||||
enforcer: cfg.Enforcer,
|
||||
}
|
||||
}
|
||||
|
||||
type validationRule int
|
||||
|
||||
const (
|
||||
matchAll validationRule = iota
|
||||
atLeastOne
|
||||
)
|
||||
|
||||
// MatchAll is an option that defines all permissions
|
||||
// or roles should match the user.
|
||||
var MatchAll = func(o *Options) {
|
||||
o.ValidationRule = matchAll
|
||||
}
|
||||
|
||||
// AtLeastOne is an option that defines at least on of
|
||||
// permissions or roles should match to pass.
|
||||
var AtLeastOne = func(o *Options) {
|
||||
o.ValidationRule = atLeastOne
|
||||
}
|
||||
|
||||
// PermissionParserFunc is used for parsing the permission
|
||||
// to extract object and action usually
|
||||
type PermissionParserFunc func(str string) []string
|
||||
|
||||
func permissionParserWithSeperator(sep string) PermissionParserFunc {
|
||||
return func(str string) []string {
|
||||
return strings.Split(str, sep)
|
||||
}
|
||||
}
|
||||
|
||||
// PermissionParserWithSeperator is an option that parses permission
|
||||
// with seperators
|
||||
func PermissionParserWithSeperator(sep string) func(o *Options) {
|
||||
return func(o *Options) {
|
||||
o.PermissionParser = permissionParserWithSeperator(sep)
|
||||
}
|
||||
}
|
||||
|
||||
// Options holds options of middleware
|
||||
type Options struct {
|
||||
ValidationRule validationRule
|
||||
PermissionParser PermissionParserFunc
|
||||
}
|
||||
|
||||
// RequiresPermissions tries to find the current subject and determine if the
|
||||
// subject has the required permissions according to predefined Casbin policies.
|
||||
func (cm *Casbin) RequiresPermissions(permissions []string, opts ...func(o *Options)) fiber.Handler {
|
||||
|
||||
options := &Options{
|
||||
ValidationRule: matchAll,
|
||||
PermissionParser: permissionParserWithSeperator(":"),
|
||||
}
|
||||
|
||||
for _, o := range opts {
|
||||
o(options)
|
||||
}
|
||||
|
||||
return func(c *fiber.Ctx) error {
|
||||
if len(permissions) == 0 {
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
sub := cm.config.Lookup(c)
|
||||
if sub == "" {
|
||||
return cm.config.Unauthorized(c)
|
||||
}
|
||||
|
||||
if options.ValidationRule == matchAll {
|
||||
for _, permission := range permissions {
|
||||
vals := append([]string{sub}, options.PermissionParser(permission)...)
|
||||
if ok, err := cm.enforcer.Enforce(convertToInterface(vals)...); err != nil {
|
||||
return c.SendStatus(fiber.StatusInternalServerError)
|
||||
} else if !ok {
|
||||
return cm.config.Forbidden(c)
|
||||
}
|
||||
}
|
||||
return c.Next()
|
||||
} else if options.ValidationRule == atLeastOne {
|
||||
for _, permission := range permissions {
|
||||
vals := append([]string{sub}, options.PermissionParser(permission)...)
|
||||
if ok, err := cm.enforcer.Enforce(convertToInterface(vals)...); err != nil {
|
||||
return c.SendStatus(fiber.StatusInternalServerError)
|
||||
} else if ok {
|
||||
return c.Next()
|
||||
}
|
||||
}
|
||||
return cm.config.Forbidden(c)
|
||||
}
|
||||
|
||||
return c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// RequiresPermissions tries to find the current subject and determine if the
|
||||
// subject has the required permissions according to predefined Casbin policies.
|
||||
func (cm *Casbin) Can(sub string, perm string, opts ...func(o *Options)) bool {
|
||||
permissions := []string{perm}
|
||||
options := &Options{
|
||||
ValidationRule: matchAll,
|
||||
PermissionParser: permissionParserWithSeperator(":"),
|
||||
}
|
||||
|
||||
for _, o := range opts {
|
||||
o(options)
|
||||
}
|
||||
if len(permissions) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if sub == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
if options.ValidationRule == matchAll {
|
||||
for _, permission := range permissions {
|
||||
vals := append([]string{sub}, options.PermissionParser(permission)...)
|
||||
if ok, err := cm.enforcer.Enforce(convertToInterface(vals)...); err != nil {
|
||||
return false
|
||||
} else if !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
} else if options.ValidationRule == atLeastOne {
|
||||
for _, permission := range permissions {
|
||||
vals := append([]string{sub}, options.PermissionParser(permission)...)
|
||||
if ok, err := cm.enforcer.Enforce(convertToInterface(vals)...); err != nil {
|
||||
return false
|
||||
} else if ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// RoutePermission tries to find the current subject and determine if the
|
||||
// subject has the required permissions according to predefined Casbin policies.
|
||||
// This method uses http Path and Method as object and action.
|
||||
func (cm *Casbin) RoutePermission() fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
sub := cm.config.Lookup(c)
|
||||
if sub == "" {
|
||||
return cm.config.Unauthorized(c)
|
||||
}
|
||||
|
||||
if ok, err := cm.enforcer.Enforce(sub, c.Path(), c.Method()); err != nil {
|
||||
return c.SendStatus(fiber.StatusInternalServerError)
|
||||
} else if !ok {
|
||||
return cm.config.Forbidden(c)
|
||||
}
|
||||
|
||||
return c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// RequiresRoles tries to find the current subject and determine if the
|
||||
// subject has the required roles according to predefined Casbin policies.
|
||||
func (cm *Casbin) RequiresRoles(roles []string, opts ...func(o *Options)) fiber.Handler {
|
||||
options := &Options{
|
||||
ValidationRule: matchAll,
|
||||
PermissionParser: permissionParserWithSeperator(":"),
|
||||
}
|
||||
|
||||
for _, o := range opts {
|
||||
o(options)
|
||||
}
|
||||
|
||||
return func(c *fiber.Ctx) error {
|
||||
if len(roles) == 0 {
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
sub := cm.config.Lookup(c)
|
||||
if sub == "" {
|
||||
return cm.config.Unauthorized(c)
|
||||
}
|
||||
|
||||
userRoles, err := cm.enforcer.GetRolesForUser(sub)
|
||||
if err != nil {
|
||||
return c.SendStatus(fiber.StatusInternalServerError)
|
||||
}
|
||||
if options.ValidationRule == matchAll {
|
||||
for _, role := range roles {
|
||||
if !contains(userRoles, role) {
|
||||
return cm.config.Forbidden(c)
|
||||
}
|
||||
}
|
||||
return c.Next()
|
||||
} else if options.ValidationRule == atLeastOne {
|
||||
for _, role := range roles {
|
||||
if contains(userRoles, role) {
|
||||
return c.Next()
|
||||
}
|
||||
}
|
||||
return cm.config.Forbidden(c)
|
||||
}
|
||||
|
||||
return c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func contains(s []string, v string) bool {
|
||||
for _, vv := range s {
|
||||
if vv == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func convertToInterface(arr []string) []interface{} {
|
||||
in := make([]interface{}, 0)
|
||||
for _, a := range arr {
|
||||
in = append(in, a)
|
||||
}
|
||||
return in
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/ilyakaznacheev/cleanenv"
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/oarkflow/log"
|
||||
"github.com/sujit-baniya/flash"
|
||||
"github.com/sujit-baniya/ip"
|
||||
)
|
||||
|
||||
// Config is a application configuration structure
|
||||
type AppConfig struct {
|
||||
Auth AuthConfig `yaml:"auth"`
|
||||
Mail Mail `yaml:"mail"`
|
||||
Hash Hash
|
||||
View ViewConfig `yaml:"view"`
|
||||
Cache CacheConfig `yaml:"cache"`
|
||||
Database DatabaseConfig `yaml:"database"`
|
||||
Session SessionConfig `yaml:"session"`
|
||||
Queue QueueConfig `yaml:"queue"`
|
||||
JwtSecrets JwtSecrets `yaml:"jwt"`
|
||||
Storage StorageConfig `yaml:"storage"`
|
||||
Server ServerConfig `yaml:"server"`
|
||||
Log LogConfig `yaml:"log"`
|
||||
Token Token `yaml:"token"`
|
||||
Profiler ProfilerConfig `yaml:"profiler"`
|
||||
Flash *flash.Flash
|
||||
GeoIP *ip.GeoIpDB
|
||||
ConfigFile string
|
||||
}
|
||||
|
||||
func (cfg *AppConfig) Setup() {
|
||||
err := godotenv.Load()
|
||||
// read configuration from the file and environment variables
|
||||
if err = cleanenv.ReadConfig(cfg.ConfigFile, cfg); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(2)
|
||||
}
|
||||
cfg.Server.LoadPath()
|
||||
cfg.View.Load(cfg.Server.Path)
|
||||
cfg.Mail.View = &cfg.View
|
||||
cfg.Server.TemplateEngine = cfg.View.Template.TemplateEngine
|
||||
cfg.Server.Setup()
|
||||
cfg.LoadComponents()
|
||||
if cfg.Auth.Type == "casbin" {
|
||||
modelFile := filepath.Join(cfg.Server.AssetPath, "rbac_model.conf")
|
||||
cfg.Auth.Setup(cfg.Database.DB, modelFile)
|
||||
}
|
||||
path := MakeDir(filepath.Join(cfg.Server.AssetPath, "GeoLite2-City.mmdb"))
|
||||
cfg.GeoIP = ip.NewGeoIpDB(path)
|
||||
}
|
||||
|
||||
func (cfg *AppConfig) PrepareLog() {
|
||||
writer := &log.MultiWriter{}
|
||||
path := MakeDir(filepath.Join(cfg.Server.Path, cfg.Log.InfoLevel.Path))
|
||||
writer.InfoWriter = &log.FileWriter{Filename: filepath.Join(path, "INFO.log"), EnsureFolder: true, TimeFormat: cfg.Log.InfoLevel.TimeFormat}
|
||||
|
||||
path = MakeDir(filepath.Join(cfg.Server.Path, cfg.Log.WarnLevel.Path))
|
||||
writer.WarnWriter = &log.FileWriter{Filename: filepath.Join(cfg.Server.Path, cfg.Log.WarnLevel.Path, "WARN.log"), EnsureFolder: true, TimeFormat: cfg.Log.WarnLevel.TimeFormat}
|
||||
|
||||
path = MakeDir(filepath.Join(cfg.Server.Path, cfg.Log.ErrorLevel.Path))
|
||||
writer.ErrorWriter = &log.FileWriter{Filename: filepath.Join(cfg.Server.Path, cfg.Log.ErrorLevel.Path, "ERROR.log"), EnsureFolder: true, TimeFormat: cfg.Log.ErrorLevel.TimeFormat}
|
||||
if cfg.Log.ConsoleLog.Show {
|
||||
writer.ConsoleWriter = &log.IOWriter{Writer: os.Stderr}
|
||||
writer.ConsoleLevel = log.InfoLevel
|
||||
}
|
||||
log.DefaultLogger = log.Logger{
|
||||
TimeField: cfg.Log.TimeField,
|
||||
TimeFormat: cfg.Log.TimeFormat,
|
||||
Writer: writer,
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg *AppConfig) Route404() {
|
||||
cfg.Server.Use(func(c *fiber.Ctx) error {
|
||||
return c.Status(fiber.StatusNotFound).SendString("Page not found")
|
||||
})
|
||||
}
|
||||
|
||||
func (cfg *AppConfig) LoadComponents() {
|
||||
cfg.Flash = flash.New(flash.Config{
|
||||
Name: "fiber",
|
||||
HTTPOnly: true,
|
||||
})
|
||||
cfg.LoadStatic()
|
||||
cfg.PrepareLog()
|
||||
_ = cfg.Database.Setup()
|
||||
_ = cfg.Session.Setup()
|
||||
cfg.Cache.Setup()
|
||||
cfg.Storage.Setup()
|
||||
}
|
||||
|
||||
func (cfg *AppConfig) LoadStatic() {
|
||||
cfg.Server.Static("/websocket", "./resources/views/websocket.html")
|
||||
cfg.Server.Static("/", filepath.Join(cfg.Server.Path, cfg.Server.PublicPath), fiber.Static{
|
||||
Compress: true,
|
||||
ByteRange: true,
|
||||
CacheDuration: 24 * time.Hour,
|
||||
})
|
||||
}
|
||||
|
||||
func (cfg *AppConfig) LoadSpamDetectionEngine() {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/oarkflow/log"
|
||||
"gorm.io/gorm/logger"
|
||||
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/driver/sqlserver" // SQL Server driver
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/plugin/dbresolver"
|
||||
)
|
||||
|
||||
type DatabaseDriver struct {
|
||||
Driver string `yaml:"driver" env:"DB_DRIVER"`
|
||||
Host string `yaml:"host" env:"DB_HOST"`
|
||||
Username string `yaml:"username" env:"DB_USER"`
|
||||
Password string `yaml:"password" env:"DB_PASS"`
|
||||
DBName string `yaml:"db_name" env:"DB_NAME"`
|
||||
Port int `yaml:"port" env:"DB_PORT"`
|
||||
Connections int `yaml:"connections" env:"DB_CONNECTIONS"`
|
||||
}
|
||||
|
||||
type DatabaseConfig struct {
|
||||
*gorm.DB
|
||||
Drivers map[string]DatabaseDriver `yaml:"drivers"`
|
||||
Default DatabaseDriver `yaml:"default" env:"DEFAULT_DB_DRIVER"`
|
||||
}
|
||||
|
||||
func (d *DatabaseConfig) Setup() error {
|
||||
var err error
|
||||
connectionString := ""
|
||||
if d.DB != nil {
|
||||
return nil
|
||||
}
|
||||
gormLogger := New(&log.DefaultLogger, logger.Config{
|
||||
LogLevel: 0,
|
||||
}, false)
|
||||
newLogger := gormLogger.LogMode(logger.Info)
|
||||
|
||||
switch d.Default.Driver {
|
||||
case "postgres":
|
||||
connectionString = fmt.Sprintf("host=%s port=%d user=%s dbname=%s password=%s", d.Default.Host, d.Default.Port, d.Default.Username, d.Default.DBName, d.Default.Password)
|
||||
d.DB, err = gorm.Open(postgres.Open(connectionString), &gorm.Config{
|
||||
DisableForeignKeyConstraintWhenMigrating: true,
|
||||
Logger: newLogger,
|
||||
})
|
||||
|
||||
case "mysql":
|
||||
connectionString = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=True&loc=Local", d.Default.Username, d.Default.Password, d.Default.Host, d.Default.Port, d.Default.DBName)
|
||||
d.DB, err = gorm.Open(mysql.Open(connectionString), &gorm.Config{
|
||||
DisableForeignKeyConstraintWhenMigrating: true,
|
||||
Logger: newLogger,
|
||||
})
|
||||
|
||||
case "sqlserver":
|
||||
connectionString = fmt.Sprintf(
|
||||
"sqlserver://%s:%s@%s:%d?database=%s&charset=utf8mb4",
|
||||
d.Default.Username,
|
||||
d.Default.Password,
|
||||
d.Default.Host,
|
||||
d.Default.Port,
|
||||
d.Default.DBName,
|
||||
)
|
||||
d.DB, err = gorm.Open(sqlserver.Open(connectionString), &gorm.Config{
|
||||
DisableForeignKeyConstraintWhenMigrating: true,
|
||||
Logger: newLogger,
|
||||
})
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unsupported database driver: %s", d.Default.Driver)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(d.Default)
|
||||
panic(err)
|
||||
}
|
||||
d.DB.Use(
|
||||
dbresolver.Register(dbresolver.Config{}).
|
||||
SetConnMaxLifetime(24 * time.Hour).
|
||||
SetMaxIdleConns(100).
|
||||
SetMaxOpenConns(100),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
func New(logger *log.Logger, config logger.Config, slient bool) logger.Interface {
|
||||
return &gormLogger{
|
||||
Log: logger,
|
||||
Config: config,
|
||||
Slient: slient,
|
||||
}
|
||||
}
|
||||
|
||||
type gormLogger struct {
|
||||
Log *log.Logger
|
||||
Config logger.Config
|
||||
Slient bool
|
||||
}
|
||||
|
||||
func (l *gormLogger) LogMode(level logger.LogLevel) logger.Interface {
|
||||
var newLogger = gormLogger{Log: l.Log}
|
||||
switch level {
|
||||
case logger.Silent:
|
||||
newLogger.Slient = true
|
||||
case logger.Error:
|
||||
newLogger.Log.SetLevel(log.ErrorLevel)
|
||||
case logger.Warn:
|
||||
newLogger.Log.SetLevel(log.WarnLevel)
|
||||
case logger.Info:
|
||||
newLogger.Log.SetLevel(log.InfoLevel)
|
||||
}
|
||||
|
||||
return &newLogger
|
||||
}
|
||||
|
||||
func (l *gormLogger) Info(ctx context.Context, format string, args ...interface{}) {
|
||||
l.Log.Info().Msgf(format, args...)
|
||||
}
|
||||
|
||||
func (l *gormLogger) Warn(ctx context.Context, format string, args ...interface{}) {
|
||||
l.Log.Warn().Msgf(format, args...)
|
||||
}
|
||||
|
||||
func (l *gormLogger) Error(ctx context.Context, format string, args ...interface{}) {
|
||||
l.Log.Error().Msgf(format, args...)
|
||||
}
|
||||
|
||||
func (l *gormLogger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
|
||||
if l.Slient {
|
||||
return
|
||||
}
|
||||
elapsed := time.Since(begin)
|
||||
switch {
|
||||
case err != nil && l.Log.Level >= log.ErrorLevel:
|
||||
sql, rows := fc()
|
||||
if rows == -1 {
|
||||
l.Log.Error().Caller(1).Err(err).Dur("elapsed", elapsed).Str("sql", sql).Msg("")
|
||||
} else {
|
||||
l.Log.Error().Caller(1).Err(err).Dur("elapsed", elapsed).Str("sql", sql).Int64("rows", rows).Msg("")
|
||||
}
|
||||
case elapsed > l.Config.SlowThreshold && l.Config.SlowThreshold != 0 && l.Log.Level >= log.WarnLevel:
|
||||
sql, rows := fc()
|
||||
if rows == -1 {
|
||||
l.Log.Warn().Caller(1).Err(err).Dur("elapsed", elapsed).Str("sql", sql).Msgf("SLOW SQL >= %v", l.Config.SlowThreshold)
|
||||
} else {
|
||||
l.Log.Warn().Caller(1).Err(err).Dur("elapsed", elapsed).Str("sql", sql).Int64("rows", rows).Msgf("SLOW SQL >= %v", l.Config.SlowThreshold)
|
||||
}
|
||||
case l.Log.Level == log.InfoLevel:
|
||||
sql, rows := fc()
|
||||
if rows == -1 {
|
||||
l.Log.Info().Caller(1).Err(err).Dur("elapsed", elapsed).Str("sql", sql).Msg("")
|
||||
} else {
|
||||
l.Log.Info().Caller(1).Err(err).Dur("elapsed", elapsed).Str("sql", sql).Int64("rows", rows).Msg("")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/alexedwards/argon2id"
|
||||
)
|
||||
|
||||
type Hash struct {
|
||||
// Argon2id configuration
|
||||
Params *argon2id.Params
|
||||
}
|
||||
|
||||
func (d *Hash) Create(password string) (hash string, err error) {
|
||||
if d.Params == nil {
|
||||
d.Params = argon2id.DefaultParams
|
||||
}
|
||||
return argon2id.CreateHash(password, d.Params)
|
||||
}
|
||||
|
||||
func (d *Hash) Match(password string, hash string) (match bool, err error) {
|
||||
if d.Params == nil {
|
||||
d.Params = argon2id.DefaultParams
|
||||
}
|
||||
return argon2id.ComparePasswordAndHash(password, hash)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package config
|
||||
|
||||
type JwtConfig struct {
|
||||
Secret string `yaml:"secret"`
|
||||
Expire string `yaml:"expire"`
|
||||
}
|
||||
|
||||
type JwtSecrets struct {
|
||||
App JwtConfig `yaml:"app"`
|
||||
Api JwtConfig `yaml:"api"`
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package config
|
||||
|
||||
type Graylog struct {
|
||||
Host string `mapstructure:"GRAYLOG_HOST" yaml:"host" env:"GRAYLOG_HOST" env-default:"localhost"`
|
||||
Port string `mapstructure:"GRAYLOG_PORT" yaml:"port" env:"GRAYLOG_PORT" env-default:"12201"`
|
||||
}
|
||||
|
||||
type FileLog struct {
|
||||
Path string `mapstructure:"LOG_PATH" yaml:"path" env-default:"storage/logs"`
|
||||
TimeFormat string `mapstructure:"LOG_TIME_FORMAT" yaml:"timeformat" env-default:"2006-01-02"`
|
||||
}
|
||||
|
||||
type ConsoleLog struct {
|
||||
Level string `mapstructure:"CONSOLE_LOG_LEVEL" yaml:"level" env-default:"info"`
|
||||
Show bool `mapstructure:"CONSOLE_LOG_SHOW" yaml:"show" env-default:"false"`
|
||||
}
|
||||
|
||||
type LogConfig struct {
|
||||
TimeField string `yaml:"timefield"`
|
||||
TimeFormat string `yaml:"timeformat"`
|
||||
ConsoleLog ConsoleLog `yaml:"console"`
|
||||
Monitor Graylog `yaml:"monitor"`
|
||||
InfoLevel FileLog `yaml:"info"`
|
||||
WarnLevel FileLog `yaml:"warn"`
|
||||
ErrorLevel FileLog `yaml:"error"`
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/valyala/bytebufferpool"
|
||||
mail "github.com/xhit/go-simple-mail/v2"
|
||||
)
|
||||
|
||||
type Mail struct {
|
||||
*mail.SMTPServer
|
||||
*mail.SMTPClient
|
||||
Host string `mapstructure:"MAIL_HOST" yaml:"host" env:"MAIL_HOST" env-default:"smtp-mail.outlook.com"`
|
||||
Username string `mapstructure:"MAIL_USERNAME" yaml:"username" env:"MAIL_USERNAME" env-default:"ovirtual@gasesdeloriente.com.co"`
|
||||
Password string `mapstructure:"MAIL_PASSWORD" yaml:"password" env:"MAIL_PASSWORD" env-default:"Gases2023**"`
|
||||
Encryption string `mapstructure:"MAIL_ENCRYPTION" yaml:"encryption" env:"MAIL_ENCRYPTION" env-default:"tls"`
|
||||
FromAddress string `mapstructure:"MAIL_FROM_ADDRESS" yaml:"from_address" env:"MAIL_FROM_ADDRESS" env-default:"ovirtual@gasesdeloriente.com.co"`
|
||||
FromName string `mapstructure:"MAIL_FROM_NAME" yaml:"from_name" env:"MAIL_FROM_NAME" env-default:"Gases"`
|
||||
View *ViewConfig
|
||||
Port int `mapstructure:"MAIL_PORT" yaml:"port" env:"MAIL_PORT" env-default:"587"` // Cambié a 465 para SSL
|
||||
}
|
||||
|
||||
// Enviar correo
|
||||
func (m *Mail) Send(to string, subject string, body string, ccEmails ...string) error {
|
||||
// Configurar el cliente SMTP antes de enviar cada correo
|
||||
if m.SMTPServer == nil {
|
||||
m.SetupMailer()
|
||||
}
|
||||
|
||||
// Obtener el remitente desde el archivo .env
|
||||
from := os.Getenv("MAIL_FROM_ADDRESS")
|
||||
if from == "" {
|
||||
log.Println("Error: MAIL_FROM_ADDRESS no está configurado en el archivo .env")
|
||||
return fmt.Errorf("MAIL_FROM_ADDRESS no está configurado")
|
||||
}
|
||||
|
||||
// Crear nuevo mensaje de correo
|
||||
email := mail.NewMSG()
|
||||
email.SetFrom(from).
|
||||
AddTo(to).
|
||||
SetSubject(subject).
|
||||
SetBody(mail.TextHTML, body)
|
||||
|
||||
// Crear un nuevo cliente SMTP
|
||||
client, err := m.SMTPServer.Connect()
|
||||
if err != nil {
|
||||
log.Printf("Error conectando al servidor SMTP: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Enviar el correo usando el cliente SMTP nuevo
|
||||
err = email.Send(client)
|
||||
if err != nil {
|
||||
log.Printf("Error enviando el correo: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Println("Correo enviado con éxito")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Preparar HTML para el cuerpo del correo
|
||||
func (m *Mail) PrepareHtml(view string, body fiber.Map) string {
|
||||
buf := bytebufferpool.Get()
|
||||
defer bytebufferpool.Put(buf)
|
||||
// app.Settings.Views.Render (asegúrate de que el motor de plantillas esté configurado)
|
||||
if err := m.View.Template.TemplateEngine.Render(buf, view, body, "layouts/email"); err != nil {
|
||||
log.Printf("Error rendering HTML: %s", err)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Configuración inicial del cliente SMTP
|
||||
func (m *Mail) SetupMailer() {
|
||||
var err error
|
||||
m.SMTPServer = mail.NewSMTPClient()
|
||||
m.SMTPServer.Host = m.Host
|
||||
m.SMTPServer.Port = m.Port
|
||||
m.SMTPServer.Username = m.Username
|
||||
m.SMTPServer.Password = m.Password
|
||||
if m.Encryption == "tls" {
|
||||
m.SMTPServer.Encryption = mail.EncryptionTLS
|
||||
} else {
|
||||
m.SMTPServer.Encryption = mail.EncryptionSSL
|
||||
}
|
||||
|
||||
// Configuración de tiempo y conexión
|
||||
m.SMTPServer.KeepAlive = false
|
||||
m.SMTPServer.ConnectTimeout = 10 * time.Second
|
||||
m.SMTPServer.SendTimeout = 10 * time.Second
|
||||
|
||||
// Conectar al servidor SMTP
|
||||
m.SMTPClient, err = m.SMTPServer.Connect()
|
||||
if err != nil {
|
||||
log.Printf("Error conectando al servidor SMTP: %s", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package config
|
||||
|
||||
type ProfilerConfig struct {
|
||||
Server string `yaml:"server" env:"PROFILER_SERVER" env-default:"http://localhost:4040"`
|
||||
Enabled bool `yaml:"enabled" env:"PROFILER_ENABLED" env-default:"false"`
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package config
|
||||
|
||||
type QueueConfig struct {
|
||||
Driver string `yaml:"driver" env:"CACHE_DRIVER"`
|
||||
Name string `yaml:"name" env:"CACHE_NAME"`
|
||||
Host string `yaml:"host" env:"CACHE_HOST"`
|
||||
Port string `yaml:"port" env:"CACHE_PORT"`
|
||||
DB string `yaml:"db" env:"CACHE_DB"`
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package config
|
||||
@@ -0,0 +1,349 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/template/html"
|
||||
"github.com/mattn/go-colorable"
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/oarkflow/log"
|
||||
)
|
||||
|
||||
type ServerConfig struct {
|
||||
*fiber.App
|
||||
TemplateEngine *html.Engine
|
||||
Name string `mapstructure:"APP_NAME" yaml:"name" env:"APP_NAME" env-default:"iSend.to"`
|
||||
Version string `mapstructure:"APP_VERSION" yaml:"version" env:"APP_VERSION" env-default:"dev"`
|
||||
Mode string `mapstructure:"APP_MODE" yaml:"mode" env:"APP_MODE" env-default:"app"`
|
||||
Env string `mapstructure:"APP_ENV" yaml:"env" env:"APP_ENV" env-default:"dev"`
|
||||
Key string `mapstructure:"APP_KEY" yaml:"key" env:"APP_KEY" env-default:"1894cde6c936a294a478cff0a9227fd276d86df6573b51af5dc59c9064edf426"`
|
||||
Url string `mapstructure:"APP_URL" yaml:"url" env:"APP_URL" env-default:"http://localhost"`
|
||||
Host string `mapstructure:"APP_HOST" yaml:"host" env:"APP_HOST" env-default:"localhost"`
|
||||
Port string `mapstructure:"APP_PORT" yaml:"port" env:"APP_PORT" env-default:"8080"`
|
||||
Path string `mapstructure:"APP_PATH" yaml:"path" env:"APP_PATH"`
|
||||
ProxyHeader string `mapstructure:"PROXY_HEADER" yaml:"PROXY_HEADER" env:"PROXY_HEADER" env-default:"*"`
|
||||
AssetPath string `mapstructure:"ASSET_PATH" yaml:"asset_path" env:"ASSET_PATH" env-default:"assets"`
|
||||
PublicPath string `mapstructure:"PUBLIC_PATH" yaml:"public_path" env:"PUBLIC_PATH" env-default:"public"`
|
||||
UploadPath string `mapstructure:"UPLOAD_PATH" yaml:"upload_path" env:"UPLOAD_PATH" env-default:"uploads"`
|
||||
StoragePath string `mapstructure:"STORAGE_PATH" yaml:"storage_path" env:"STORAGE_PATH" env-default:"storage"`
|
||||
LogPath string `mapstructure:"LOG_PATH" yaml:"log_path" env:"LOG_PATH" env-default:"storage/logs"`
|
||||
ExecPath bool `mapstructure:"EXEC_PATH" yaml:"exec_path" env:"EXEC_PATH" env-default:"false"`
|
||||
Debug bool `mapstructure:"APP_DEBUG" yaml:"debug" env:"APP_DEBUG" env-default:"true"`
|
||||
UploadSize int `mapstructure:"UPLOAD_SIZE" yaml:"upload_size" env:"UPLOAD_SIZE" env-default:"400"`
|
||||
}
|
||||
|
||||
func (s *ServerConfig) LoadPath() {
|
||||
if s.Url == "" {
|
||||
s.Url = fmt.Sprintf("http://localhost:%s", s.Port)
|
||||
}
|
||||
path, _ := os.Getwd()
|
||||
if s.ExecPath {
|
||||
path = getPath()
|
||||
}
|
||||
s.Path = path
|
||||
s.UploadPath = MakeDir(filepath.Join(path, s.UploadPath))
|
||||
s.AssetPath = MakeDir(filepath.Join(path, s.AssetPath))
|
||||
s.StoragePath = MakeDir(filepath.Join(path, s.StoragePath))
|
||||
s.LogPath = MakeDir(filepath.Join(path, s.LogPath))
|
||||
s.UploadSize = s.UploadSize * 1024 * 1024
|
||||
}
|
||||
|
||||
func (s *ServerConfig) Setup() {
|
||||
|
||||
s.App = fiber.New(fiber.Config{
|
||||
Views: s.TemplateEngine,
|
||||
Concurrency: 256 * 1024 * 1024,
|
||||
ServerHeader: s.Name,
|
||||
BodyLimit: s.UploadSize,
|
||||
ReduceMemoryUsage: true,
|
||||
ErrorHandler: CustomErrorHandler,
|
||||
DisableStartupMessage: true,
|
||||
ProxyHeader: s.ProxyHeader,
|
||||
Prefork: true, // Cambia a 'true' para habilitar Preforking
|
||||
})
|
||||
}
|
||||
|
||||
func (s *ServerConfig) Serve(addr ...string) error {
|
||||
a := s.Host + ":" + s.Port
|
||||
if len(addr) != 0 {
|
||||
a = addr[0]
|
||||
}
|
||||
s.startupMessage(a, false, "")
|
||||
return s.Listen(a)
|
||||
}
|
||||
|
||||
func (s *ServerConfig) ServeWithGraceFullShutdown(addr ...string) error {
|
||||
a := s.Host + ":" + s.Port
|
||||
if len(addr) != 0 {
|
||||
a = addr[0]
|
||||
}
|
||||
s.startupMessage(a, false, "")
|
||||
// Listen from a different goroutine
|
||||
go func() {
|
||||
if err := s.Listen(a); err != nil {
|
||||
log.Fatal().Err(err)
|
||||
}
|
||||
}()
|
||||
|
||||
c := make(chan os.Signal, 1) // Create channel to signify a signal being sent
|
||||
signal.Notify(c,
|
||||
syscall.SIGINT,
|
||||
syscall.SIGTERM,
|
||||
syscall.SIGABRT,
|
||||
syscall.SIGQUIT,
|
||||
) // When an interrupt is sent, notify the channel
|
||||
<-c // This blocks the main thread until an interrupt is received
|
||||
fmt.Println("I'm shutting down")
|
||||
return s.Shutdown()
|
||||
}
|
||||
|
||||
func (s *ServerConfig) startupMessage(addr string, tls bool, processIds string) {
|
||||
// ignore child processes
|
||||
if fiber.IsChild() {
|
||||
return
|
||||
}
|
||||
|
||||
var logo string
|
||||
logo += "%s"
|
||||
logo += " ┌─────────────────────────────────────────────────────┐\n"
|
||||
logo += " │ %s │\n"
|
||||
logo += " │ %s │\n"
|
||||
logo += " │ │\n"
|
||||
logo += " │ Handlers %s Processes %s │\n"
|
||||
logo += " │ Prefork .%s PID ....%s │\n"
|
||||
logo += " └─────────────────────────────────────────────────────┘"
|
||||
logo += "%s"
|
||||
|
||||
const (
|
||||
cBlack = "\u001b[90m"
|
||||
// cRed = "\u001b[91m"
|
||||
cCyan = "\u001b[96m"
|
||||
// cGreen = "\u001b[92m"
|
||||
// cYellow = "\u001b[93m"
|
||||
// cBlue = "\u001b[94m"
|
||||
// cMagenta = "\u001b[95m"
|
||||
// cWhite = "\u001b[97m"
|
||||
cReset = "\u001b[0m"
|
||||
)
|
||||
|
||||
value := func(s string, width int) string {
|
||||
pad := width - len(s)
|
||||
str := ""
|
||||
for i := 0; i < pad; i++ {
|
||||
str += "."
|
||||
}
|
||||
if s == "Disabled" {
|
||||
str += " " + s
|
||||
} else {
|
||||
str += fmt.Sprintf(" %s%s%s", cCyan, s, cBlack)
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
center := func(s string, width int) string {
|
||||
pad := strconv.Itoa((width - len(s)) / 2)
|
||||
str := fmt.Sprintf("%"+pad+"s", " ")
|
||||
str += s
|
||||
str += fmt.Sprintf("%"+pad+"s", " ")
|
||||
if len(str) < width {
|
||||
str += " "
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
centerValue := func(s string, width int) string {
|
||||
pad := strconv.Itoa((width - len(s)) / 2)
|
||||
str := fmt.Sprintf("%"+pad+"s", " ")
|
||||
str += fmt.Sprintf("%s%s%s", cCyan, s, cBlack)
|
||||
str += fmt.Sprintf("%"+pad+"s", " ")
|
||||
if len(str)-10 < width {
|
||||
str += " "
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
pad := func(s string, width int) (str string) {
|
||||
toAdd := width - len(s)
|
||||
str += s
|
||||
for i := 0; i < toAdd; i++ {
|
||||
str += " "
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
host, port := parseAddr(addr)
|
||||
if host == "" || host == "0.0.0.0" {
|
||||
host = "127.0.0.1"
|
||||
}
|
||||
addr = "http://" + host + ":" + port
|
||||
if tls {
|
||||
addr = "https://" + host + ":" + port
|
||||
}
|
||||
|
||||
isPrefork := "Disabled"
|
||||
if s.Config().Prefork {
|
||||
isPrefork = "Enabled"
|
||||
}
|
||||
|
||||
procs := strconv.Itoa(runtime.GOMAXPROCS(0))
|
||||
if !s.Config().Prefork {
|
||||
procs = "1"
|
||||
}
|
||||
routeCount := 0
|
||||
for _, route := range s.Stack() {
|
||||
routeCount += len(route)
|
||||
}
|
||||
mainLogo := fmt.Sprintf(logo,
|
||||
cBlack,
|
||||
centerValue(s.Name+" "+s.Version, 49),
|
||||
center(addr, 49),
|
||||
value(strconv.Itoa(routeCount), 14), value(procs, 12),
|
||||
value(isPrefork, 14), value(strconv.Itoa(os.Getpid()), 14),
|
||||
cReset,
|
||||
)
|
||||
|
||||
var childPidsLogo string
|
||||
if s.Config().Prefork {
|
||||
var childPidsTemplate string
|
||||
childPidsTemplate += "%s"
|
||||
childPidsTemplate += " ┌───────────────────────────────────────────────────┐\n%s"
|
||||
childPidsTemplate += " └───────────────────────────────────────────────────┘"
|
||||
childPidsTemplate += "%s"
|
||||
|
||||
newLine := " │ %s%s%s │"
|
||||
|
||||
// Turn the `processIds` variable (in the form ",a,b,c,d,e,f,etc") into a slice of PIDs
|
||||
var pidSlice []string
|
||||
for _, v := range strings.Split(processIds, ",") {
|
||||
if v != "" {
|
||||
pidSlice = append(pidSlice, v)
|
||||
}
|
||||
}
|
||||
|
||||
var lines []string
|
||||
thisLine := "Child PIDs ... "
|
||||
var itemsOnThisLine []string
|
||||
|
||||
addLine := func() {
|
||||
lines = append(lines,
|
||||
fmt.Sprintf(
|
||||
newLine,
|
||||
cBlack,
|
||||
thisLine+cCyan+pad(strings.Join(itemsOnThisLine, ", "), 49-len(thisLine)),
|
||||
cBlack,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
for _, pid := range pidSlice {
|
||||
if len(thisLine+strings.Join(append(itemsOnThisLine, pid), ", ")) > 49 {
|
||||
addLine()
|
||||
thisLine = ""
|
||||
itemsOnThisLine = []string{pid}
|
||||
} else {
|
||||
itemsOnThisLine = append(itemsOnThisLine, pid)
|
||||
}
|
||||
}
|
||||
|
||||
// Add left over items to their own line
|
||||
if len(itemsOnThisLine) != 0 {
|
||||
addLine()
|
||||
}
|
||||
|
||||
// Form logo
|
||||
childPidsLogo = fmt.Sprintf(childPidsTemplate,
|
||||
cBlack,
|
||||
strings.Join(lines, "\n")+"\n",
|
||||
cReset,
|
||||
)
|
||||
}
|
||||
|
||||
// Combine both the child PID logo and the main Fiber logo
|
||||
|
||||
// Pad the shorter logo to the length of the longer one
|
||||
splitMainLogo := strings.Split(mainLogo, "\n")
|
||||
splitChildPidsLogo := strings.Split(childPidsLogo, "\n")
|
||||
|
||||
mainLen := len(splitMainLogo)
|
||||
childLen := len(splitChildPidsLogo)
|
||||
|
||||
if mainLen > childLen {
|
||||
diff := mainLen - childLen
|
||||
for i := 0; i < diff; i++ {
|
||||
splitChildPidsLogo = append(splitChildPidsLogo, "")
|
||||
}
|
||||
} else {
|
||||
diff := childLen - mainLen
|
||||
for i := 0; i < diff; i++ {
|
||||
splitMainLogo = append(splitMainLogo, "")
|
||||
}
|
||||
}
|
||||
|
||||
// Combine the two logos, line by line
|
||||
output := "\n"
|
||||
for i := range splitMainLogo {
|
||||
output += cBlack + splitMainLogo[i] + " " + splitChildPidsLogo[i] + "\n"
|
||||
}
|
||||
|
||||
out := colorable.NewColorableStdout()
|
||||
if os.Getenv("TERM") == "dumb" || (!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd())) {
|
||||
out = colorable.NewNonColorable(os.Stdout)
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintln(out, output)
|
||||
}
|
||||
|
||||
func (s *ServerConfig) Stop() {
|
||||
_ = s.Shutdown()
|
||||
}
|
||||
|
||||
func getPath() string {
|
||||
ex, err := os.Executable()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
exPath := filepath.Dir(ex)
|
||||
return exPath
|
||||
}
|
||||
|
||||
func MakeDir(path string) string {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
_ = os.MkdirAll(path, os.ModePerm)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func CustomErrorHandler(c *fiber.Ctx, err error) error {
|
||||
// StatusCode defaults to 500
|
||||
code := fiber.StatusInternalServerError
|
||||
//nolint:misspell // Retrieve the custom statuscode if it's an fiber.*Error
|
||||
if e, ok := err.(*fiber.Error); ok {
|
||||
code = e.Code
|
||||
} //nolint:gofmt,wsl
|
||||
er := errors.WithStack(err)
|
||||
fmt.Printf("%+v", er)
|
||||
fmt.Printf("%+v", err)
|
||||
if c.Is("json") {
|
||||
return c.Status(code).JSON(err)
|
||||
}
|
||||
return c.Status(code).Render(fmt.Sprintf("errors/%d", code), fiber.Map{ //nolint:nolintlint,errcheck
|
||||
"error": err,
|
||||
})
|
||||
}
|
||||
|
||||
func parseAddr(raw string) (host, port string) {
|
||||
if i := strings.LastIndex(raw, ":"); i != -1 {
|
||||
return raw[:i], raw[i+1:]
|
||||
}
|
||||
return raw, ""
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/session/v2"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type SessionConfig struct {
|
||||
Database string `yaml:"database" env:"SESSION_DATABASE"` // Ruta de la base de datos SQLite
|
||||
*session.Session
|
||||
}
|
||||
|
||||
func (s *SessionConfig) Setup() error {
|
||||
// Abrir conexión a la base de datos SQLite
|
||||
db, err := gorm.Open(sqlite.Open(s.Database), &gorm.Config{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to connect to the database: %v", err)
|
||||
}
|
||||
|
||||
// Migrar la estructura de la tabla para sesiones
|
||||
err = db.AutoMigrate(&Session{}) // Define la estructura de la sesión según sea necesario
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to migrate database: %v", err)
|
||||
}
|
||||
|
||||
// Configurar el proveedor de sesiones en memoria
|
||||
store := session.New() // Usar el proveedor de sesiones en memoria
|
||||
|
||||
// Crear una nueva sesión
|
||||
s.Session = store
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Estructura para las sesiones (ajusta según tus necesidades)
|
||||
type Session struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
SessionID string `gorm:"unique"`
|
||||
Data string `gorm:"type:text"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
ExpireAt time.Time
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3" // Importar el controlador SQLite
|
||||
)
|
||||
|
||||
type StorageConfig struct {
|
||||
DBFile string `yaml:"db_file" env:"STORAGE_DB_FILE"`
|
||||
}
|
||||
|
||||
// Setup inicializa el almacenamiento utilizando SQLite
|
||||
func (c *StorageConfig) Setup() (*sql.DB, error) {
|
||||
db, err := sql.Open("sqlite3", c.DBFile)
|
||||
if err != nil {
|
||||
log.Fatalf("Error al conectar a la base de datos SQLite: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Crear la tabla si no existe
|
||||
createTableQuery := `
|
||||
CREATE TABLE IF NOT EXISTS storage (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT,
|
||||
expire_at INTEGER
|
||||
);
|
||||
`
|
||||
_, err = db.Exec(createTableQuery)
|
||||
if err != nil {
|
||||
log.Fatalf("Error al crear la tabla de almacenamiento: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// Función para guardar un valor en el almacenamiento
|
||||
func (c *StorageConfig) Set(db *sql.DB, key string, value string, expireAt int64) error {
|
||||
_, err := db.Exec("INSERT OR REPLACE INTO storage (key, value, expire_at) VALUES (?, ?, ?)", key, value, expireAt)
|
||||
return err
|
||||
}
|
||||
|
||||
// Función para obtener un valor del almacenamiento
|
||||
func (c *StorageConfig) Get(db *sql.DB, key string) (string, error) {
|
||||
var value string
|
||||
err := db.QueryRow("SELECT value FROM storage WHERE key = ? AND (expire_at IS NULL OR expire_at > ?)", key, time.Now().Unix()).Scan(&value)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/form3tech-oss/jwt-go"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
type Token struct {
|
||||
Hash string `json:"token"`
|
||||
Expire int64 `mapstructure:"JWT_EXPIRE" json:"expires_in" yaml:"expires_in"`
|
||||
AppJwtSecret string `mapstructure:"APP_JWT_SECRET" yaml:"app_jwt_secret"`
|
||||
ApiJwtSecret string `mapstructure:"API_JWT_SECRET" yaml:"api_jwt_secret"`
|
||||
}
|
||||
|
||||
//CreateToken authenticates the user
|
||||
func (t *Token) CreateToken(c *fiber.Ctx, userID uint, secret string, expire ...int64) (*Token, error) {
|
||||
token := jwt.New(jwt.SigningMethodHS256)
|
||||
|
||||
claims := token.Claims.(jwt.MapClaims)
|
||||
claims["id"] = userID
|
||||
if len(expire) > 0 {
|
||||
t.Expire = expire[0]
|
||||
} else {
|
||||
t.Expire = 3600
|
||||
}
|
||||
expiresIn := time.Now().Add(time.Duration(t.Expire) * time.Second).Unix()
|
||||
claims["exp"] = expiresIn
|
||||
|
||||
tokenHash, err := token.SignedString([]byte(secret))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Cookie(&fiber.Cookie{
|
||||
Name: "Verify-Rest-Token",
|
||||
Value: tokenHash,
|
||||
Secure: false,
|
||||
HTTPOnly: true,
|
||||
})
|
||||
t.Hash = tokenHash
|
||||
t.Expire = expiresIn
|
||||
return t, nil
|
||||
}
|
||||
|
||||
//ParseToken returns the users id or error
|
||||
func (t *Token) ParseToken(c *fiber.Ctx, secret string) (uint, error) {
|
||||
tokenString := c.Cookies("Verify-Rest-Token")
|
||||
|
||||
if tokenString == "" {
|
||||
return 0, errors.New("Empty auth cookie")
|
||||
}
|
||||
|
||||
claims := jwt.MapClaims{}
|
||||
_, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(secret), nil
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
//Checks if the token is valid if it is not then it deletes it
|
||||
err2 := claims.Valid()
|
||||
|
||||
if err2 != nil {
|
||||
t.DeleteToken(c)
|
||||
return 0, err2
|
||||
}
|
||||
|
||||
return uint(claims["id"].(float64)), nil
|
||||
}
|
||||
|
||||
//DeleteToken deletes the jwt token
|
||||
func (t *Token) DeleteToken(c *fiber.Ctx) {
|
||||
c.ClearCookie("Verify-Rest-Token")
|
||||
}
|
||||
|
||||
//RefreshToken refreshes the token
|
||||
func (t *Token) RefreshToken(c *fiber.Ctx, secret string) (*Token, error) {
|
||||
u, err := t.ParseToken(c, secret)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return t.CreateToken(c, u, secret)
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/gofiber/template/html"
|
||||
"github.com/markbates/pkger"
|
||||
)
|
||||
|
||||
// TemplateConfig para la configuración del motor de plantillas
|
||||
type TemplateConfig struct {
|
||||
TemplateEngine *html.Engine
|
||||
Path string `yaml:"path" env-default:"resources/views"`
|
||||
Extension string `yaml:"extension" env-default:".html"`
|
||||
}
|
||||
|
||||
// ViewConfig para la configuración de vistas
|
||||
type ViewConfig struct {
|
||||
Template TemplateConfig `yaml:"template"`
|
||||
}
|
||||
|
||||
// Load carga la configuración de las vistas
|
||||
func (v *ViewConfig) Load(path string) {
|
||||
path = MakeDir(filepath.Join(path, v.Template.Path))
|
||||
v.Template.TemplateEngine = html.NewFileSystem(pkger.Dir(path), v.Template.Extension)
|
||||
}
|
||||
|
||||
// NewDict crea un mapa para usar en las plantillas
|
||||
func NewDict(data ...interface{}) map[string]interface{} {
|
||||
dict := make(map[string]interface{})
|
||||
if len(data)%2 != 0 {
|
||||
return dict // Retorna un mapa vacío si hay un número impar de argumentos
|
||||
}
|
||||
for i := 0; i < len(data); i += 2 {
|
||||
key, ok := data[i].(string)
|
||||
if !ok {
|
||||
continue // Salta si la clave no es un string
|
||||
}
|
||||
dict[key] = data[i+1]
|
||||
}
|
||||
return dict
|
||||
}
|
||||
Reference in New Issue
Block a user