Initial commit

This commit is contained in:
Lizandro Guarnizo
2025-02-06 14:22:29 -05:00
commit 6d8f1bcd6f
452 changed files with 250835 additions and 0 deletions
+58
View File
@@ -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)
}
+19
View File
@@ -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
}
}
+288
View File
@@ -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
}
+110
View File
@@ -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() {
}
+161
View File
@@ -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("")
}
}
}
+24
View File
@@ -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)
}
+11
View File
@@ -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"`
}
+26
View File
@@ -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
View File
@@ -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)
}
}
+6
View File
@@ -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"`
}
+9
View File
@@ -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"`
}
+1
View File
@@ -0,0 +1 @@
package config
+349
View File
@@ -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, ""
}
+47
View File
@@ -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
}
+54
View File
@@ -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
}
+89
View File
@@ -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)
}
+42
View File
@@ -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
}