From 4b8db71df76fabcef33498d66beab3a55bc4a8d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20P=C3=A9rez?= Date: Fri, 22 Nov 2024 10:44:21 +0100 Subject: [PATCH] simplified gopher-toolbox --- .env.example | 3 + binding/binding.go | 83 --------------- binding/binding_test.go | 64 ------------ config/config.go | 100 +++++++++++------- db/driver.go | 13 +-- go.mod | 41 +------- go.sum | 95 +---------------- gorender/README.md | 107 -------------------- gorender/form.go | 95 ----------------- gorender/functions.go | 71 ------------- gorender/pages.go | 153 ---------------------------- gorender/render.go | 185 ---------------------------------- handlers/handlers.go | 14 --- middleware/middelware_test.go | 42 -------- middleware/middleware.go | 77 -------------- testutils/testutils.go | 55 ---------- token/token.go | 67 ------------ token/token_test.go | 62 ------------ utils/utils.go | 34 +------ 19 files changed, 83 insertions(+), 1278 deletions(-) create mode 100644 .env.example delete mode 100644 binding/binding.go delete mode 100644 binding/binding_test.go delete mode 100644 gorender/README.md delete mode 100644 gorender/form.go delete mode 100644 gorender/functions.go delete mode 100644 gorender/pages.go delete mode 100644 gorender/render.go delete mode 100644 middleware/middelware_test.go delete mode 100644 middleware/middleware.go delete mode 100644 testutils/testutils.go delete mode 100644 token/token.go delete mode 100644 token/token_test.go diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..5e241f6 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +DATASOURCE= +ASYMMETRICKEY= +DURATION= diff --git a/binding/binding.go b/binding/binding.go deleted file mode 100644 index 744a082..0000000 --- a/binding/binding.go +++ /dev/null @@ -1,83 +0,0 @@ -package binding - -import ( - "errors" - "net/http" - "reflect" - "strconv" -) - -type FormBinding struct{} - -func (FormBinding) Bind(r *http.Request, obj any) error { - if r == nil { - return errors.New("request is nil") - } - - if err := r.ParseForm(); err != nil { - return err - } - - if r.Form == nil { - return errors.New("form is nil") - } - - return mapForm(obj, r.Form) -} - -func mapForm(obj any, form map[string][]string) error { - val := reflect.ValueOf(obj) - if val.Kind() != reflect.Ptr || val.IsNil() { - return errors.New("obj must be a non-nil pointer") - } - val = val.Elem() - if val.Kind() != reflect.Struct { - return errors.New("obj must be a pointer to a struct") - } - - typ := val.Type() - for i := 0; i < val.NumField(); i++ { - field := val.Field(i) - fieldType := typ.Field(i) - formTag := fieldType.Tag.Get("form") - - if formTag == "" { - formTag = fieldType.Name - } - - if values, ok := form[formTag]; ok && len(values) > 0 { - value := values[0] - switch field.Kind() { - case reflect.String: - field.SetString(value) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - intValue, err := strconv.ParseInt(value, 10, 64) - if err != nil { - return err - } - field.SetInt(intValue) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - uintValue, err := strconv.ParseUint(value, 10, 64) - if err != nil { - return err - } - field.SetUint(uintValue) - case reflect.Float32, reflect.Float64: - floatValue, err := strconv.ParseFloat(value, 64) - if err != nil { - return err - } - field.SetFloat(floatValue) - case reflect.Bool: - boolValue, err := strconv.ParseBool(value) - if err != nil { - return err - } - field.SetBool(boolValue) - default: - return errors.New("unsupported field type") - } - } - } - return nil -} diff --git a/binding/binding_test.go b/binding/binding_test.go deleted file mode 100644 index aca7f00..0000000 --- a/binding/binding_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package binding - -import ( - "github.com/stretchr/testify/require" - "testing" -) - -func Test_mapForm(t *testing.T) { - var someStruct struct { - StringType string `form:"stringtype"` - IntType int `form:"inttype"` - Int8Type int8 `form:"int8type"` - Int16Type int16 `form:"int16type"` - Int32Type int32 `form:"int32type"` - Int64Type int64 `form:"int64type"` - UintType uint `form:"uinttype"` - Uint8Type uint8 `form:"uint8type"` - Uint16Type uint16 `form:"uint16type"` - Uint32Type uint32 `form:"uint32type"` - Uint64Type uint64 `form:"uint64type"` - Float32Type float32 `form:"float32type"` - Float64Type float64 `form:"float64type"` - BoolType bool `form:"booltype"` - } - - formData := map[string][]string{ - "stringtype": {"stringType"}, - "inttype": {"-2147483647"}, - "int8type": {"-127"}, - "int16type": {"-32767"}, - "int32type": {"-2147483647"}, - "int64type": {"-9223372036854775807"}, - "uinttype": {"4294967295"}, - "uint8type": {"255"}, - "uint16type": {"65535"}, - "uint32type": {"4294967295"}, - "uint64type": {"18446744073709551615"}, - "float32type": { - "3.1415927", - }, - "float64type": { - "3.141592653589793", - }, - "booltype": {"true"}, - } - - err := mapForm(&someStruct, formData) - require.NoError(t, err) - require.Equal(t, "stringType", someStruct.StringType) - require.Equal(t, int(-2147483647), someStruct.IntType) - require.Equal(t, int8(-127), someStruct.Int8Type) - require.Equal(t, int16(-32767), someStruct.Int16Type) - require.Equal(t, int32(-2147483647), someStruct.Int32Type) - require.Equal(t, int64(-9223372036854775807), someStruct.Int64Type) - require.Equal(t, uint(4294967295), someStruct.UintType) - require.Equal(t, uint8(255), someStruct.Uint8Type) - require.Equal(t, uint16(65535), someStruct.Uint16Type) - require.Equal(t, uint32(4294967295), someStruct.Uint32Type) - require.Equal(t, uint64(18446744073709551615), someStruct.Uint64Type) - require.Equal(t, float32(3.1415927), someStruct.Float32Type) - require.Equal(t, float64(3.141592653589793), someStruct.Float64Type) - require.Equal(t, true, someStruct.BoolType) - t.Log(someStruct) -} diff --git a/config/config.go b/config/config.go index a137635..bbedbab 100644 --- a/config/config.go +++ b/config/config.go @@ -1,60 +1,90 @@ package config import ( - "fmt" - "gopher-toolbox/token" - "io" + "bufio" "log/slog" "os" + "strings" "time" + + "aidanwoods.dev/go-paseto" ) type App struct { DataSource string - UseCache bool - // TODO: Extract Security field and use as ExtendedApp (or think a strategy for better library management) - Security Security - AppInfo AppInfo + Security Security + AppInfo AppInfo } type AppInfo struct { - GinMode string Version string } type Security struct { - Token *token.Paseto - StripeKey string - Duration time.Duration + AsymmetricKey paseto.V4AsymmetricSecretKey + PublicKey paseto.V4AsymmetricPublicKey + Duration time.Duration } -func NewLogger(level slog.Level) { - now := time.Now().Format("2006-01-02") - if _, err := os.Stat("logs"); os.IsNotExist(err) { - os.Mkdir("logs", 0755) +func New(version string) *App { + var err error + + err = loadEnvFile() + if err != nil { + slog.Error("error loading env file", "error", err) + panic(err) } - f, _ := os.OpenFile(fmt.Sprintf("logs/log%s.log", now), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) - mw := io.MultiWriter(os.Stdout, f) - logger := slog.New(slog.NewTextHandler(mw, &slog.HandlerOptions{ - AddSource: true, - Level: level, - })) + var durationTime time.Duration + var ak paseto.V4AsymmetricSecretKey - slog.SetDefault(logger) -} + ak, err = paseto.NewV4AsymmetricSecretKeyFromHex(os.Getenv("ASYMMETRICKEY")) + if err != nil { + ak = paseto.NewV4AsymmetricSecretKey() + } + pk := ak.Public() -func LogLevel(level string) slog.Level { - switch level { - case "debug": - return slog.LevelDebug - case "info": - return slog.LevelInfo - case "warn": - return slog.LevelWarn - case "error": - return slog.LevelError - default: - return slog.LevelDebug + duration := os.Getenv("DURATION") + if duration != "" { + durationTime, err = time.ParseDuration(duration) + if err != nil { + durationTime = time.Hour * 24 * 7 + } + } + + return &App{ + DataSource: os.Getenv("DATASOURCE"), + Security: Security{ + AsymmetricKey: ak, + PublicKey: pk, + Duration: durationTime, + }, + AppInfo: AppInfo{ + Version: version, + }, } } + +func loadEnvFile() error { + file, err := os.Open(".env") + if err != nil { + return err + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + if len(line) == 0 || strings.HasPrefix(line, "#") { + continue + } + parts := strings.SplitN(line, "=", 2) + if len(parts) != 2 { + continue + } + key := strings.TrimSpace(parts[0]) + value := strings.TrimSpace(parts[1]) + os.Setenv(key, value) + } + return scanner.Err() +} diff --git a/db/driver.go b/db/driver.go index 5caed19..fc36d80 100644 --- a/db/driver.go +++ b/db/driver.go @@ -12,7 +12,7 @@ import ( _ "github.com/jackc/pgx/v5/stdlib" ) -func NewPostgresPool(dataSource string) *pgxpool.Pool { +func NewPGXPool(dataSource string) *pgxpool.Pool { dbPool, err := pgxpool.New(context.Background(), dataSource) if err != nil { slog.Error("error connecting to database", "error", err) @@ -42,19 +42,10 @@ func NewMySQL(dataSource string) (*sql.DB, error) { d.SetMaxIdleConns(maxIdleDbConn) d.SetConnMaxLifetime(maxDbLifetime) - err = testDB(d) - if err != nil { + if err := d.Ping(); err != nil { slog.Error("error pinging database", "error", err) return nil, err } return d, nil } - -func testDB(d *sql.DB) error { - err := d.Ping() - if err != nil { - return err - } - return nil -} diff --git a/go.mod b/go.mod index d1a0528..2bbd629 100644 --- a/go.mod +++ b/go.mod @@ -3,65 +3,32 @@ module gopher-toolbox go 1.23.2 require ( - github.com/go-playground/locales v0.14.1 - github.com/go-playground/universal-translator v0.18.1 - github.com/go-playground/validator/v10 v10.22.1 - github.com/google/uuid v1.6.0 github.com/jackc/pgconn v1.14.3 github.com/jackc/pgx/v5 v5.7.1 - github.com/justinas/nosurf v1.1.1 - github.com/o1egl/paseto v1.0.0 - github.com/stretchr/testify v1.9.0 github.com/xuri/excelize/v2 v2.9.0 - github.com/zepyrshut/esfaker v0.0.0-20241017072233-b4a5efb1f24d ) require ( - github.com/bytedance/sonic v1.11.6 // indirect - github.com/bytedance/sonic/loader v0.1.1 // indirect - github.com/cloudwego/base64x v0.1.4 // indirect - github.com/cloudwego/iasm v0.2.0 // indirect - github.com/gin-contrib/sse v0.1.0 // indirect - github.com/goccy/go-json v0.10.2 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.2.7 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.2.2 // indirect - github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.2.12 // indirect - golang.org/x/arch v0.8.0 // indirect - google.golang.org/protobuf v1.34.1 // indirect + aidanwoods.dev/go-result v0.1.0 // indirect + github.com/stretchr/testify v1.9.0 // indirect ) require ( - github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect - github.com/aead/chacha20poly1305 v0.0.0-20170617001512-233f39982aeb // indirect - github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/gabriel-vasile/mimetype v1.4.3 // indirect - github.com/gin-gonic/gin v1.10.0 + aidanwoods.dev/go-paseto v1.5.2 github.com/jackc/chunkreader/v2 v2.0.1 // indirect github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgproto3/v2 v2.3.3 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect - github.com/kr/text v0.2.0 // indirect - github.com/leodido/go-urn v1.4.0 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect - github.com/pkg/errors v0.8.0 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect github.com/richardlehane/mscfb v1.0.4 // indirect github.com/richardlehane/msoleps v1.0.4 // indirect - github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d // indirect github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 // indirect golang.org/x/crypto v0.28.0 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.26.0 // indirect - golang.org/x/text v0.19.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect + golang.org/x/text v0.19.0 ) diff --git a/go.sum b/go.sum index c481ad3..a195734 100644 --- a/go.sum +++ b/go.sum @@ -1,40 +1,10 @@ -github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= -github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= -github.com/aead/chacha20poly1305 v0.0.0-20170617001512-233f39982aeb h1:6Z/wqhPFZ7y5ksCEV/V5MXOazLaeu/EW97CU5rz8NWk= -github.com/aead/chacha20poly1305 v0.0.0-20170617001512-233f39982aeb/go.mod h1:UzH9IX1MMqOcwhoNOIjmTQeAxrFgzs50j4golQtXXxU= -github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw= -github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us= -github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= -github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= -github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= -github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= -github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= -github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= -github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= -github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +aidanwoods.dev/go-paseto v1.5.2 h1:9aKbCQQUeHCqis9Y6WPpJpM9MhEOEI5XBmfTkFMSF/o= +aidanwoods.dev/go-paseto v1.5.2/go.mod h1:7eEJZ98h2wFi5mavCcbKfv9h86oQwut4fLVeL/UBFnw= +aidanwoods.dev/go-result v0.1.0 h1:y/BMIRX6q3HwaorX1Wzrjo3WUdiYeyWbvGe18hKS3K8= +aidanwoods.dev/go-result v0.1.0/go.mod h1:yridkWghM7AXSFA6wzx0IbsurIm1Lhuro3rYef8FBHM= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -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/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= -github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -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/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= -github.com/go-playground/validator/v10 v10.22.1/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/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -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/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= @@ -54,35 +24,8 @@ github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs= github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/justinas/nosurf v1.1.1 h1:92Aw44hjSK4MxJeMSyDa7jwuI9GR2J/JCQiaKvXXSlk= -github.com/justinas/nosurf v1.1.1/go.mod h1:ALpWdSbuNGy2lZWtyXdjkYv4edL23oSEgfBT1gPJ5BQ= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= -github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -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/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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= -github.com/o1egl/paseto v1.0.0 h1:bwpvPu2au176w4IBlhbyUv/S5VPptERIA99Oap5qUd0= -github.com/o1egl/paseto v1.0.0/go.mod h1:5HxsZPmw/3RI2pAwGo1HhOOwSdvBpcuVzO7uDkm+CLU= -github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= -github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= -github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM= @@ -90,39 +33,18 @@ github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7 github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= github.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM/9/g00= github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 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/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= -github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= -github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d h1:llb0neMWDQe87IzJLS4Ci7psK/lVsjIS2otl+1WyRyY= github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= github.com/xuri/excelize/v2 v2.9.0 h1:1tgOaEq92IOEumR1/JfYS/eR0KHOCsRv/rYXXh6YJQE= github.com/xuri/excelize/v2 v2.9.0/go.mod h1:uqey4QBZ9gdMeWApPLdhm9x+9o2lq4iVmjiLfBS5hdE= github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 h1:hPVCafDV85blFTabnqKgNhDCkJX25eik94Si9cTER4A= github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= -github.com/zepyrshut/esfaker v0.0.0-20241017072233-b4a5efb1f24d h1:o52tUkQBIDD6s2v2OHmXIsZQIKTEiVMPeov2SmvXJWk= -github.com/zepyrshut/esfaker v0.0.0-20241017072233-b4a5efb1f24d/go.mod h1:HgsPkO8n/XumWNHKfMZNV9UgC9/sUghpxVFuQcPJd2o= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= -golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= -golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= @@ -131,21 +53,12 @@ golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= -google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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= -nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/gorender/README.md b/gorender/README.md deleted file mode 100644 index 43017bb..0000000 --- a/gorender/README.md +++ /dev/null @@ -1,107 +0,0 @@ -# gorender - -Simple y minimalista librería para procesar plantillas utilizando la librería -estándar de Go `html/template`. - -## Características - -- Procesamiento de plantillas utilizando `html/template`. -- Soporte para caché de plantillas. -- Soporte para paginación de elementos como tablas o múltiples blogs. -- Posibilidad de añadir funciones personalizadas a las plantillas. -- Configuración sencilla con opciones por defecto que se pueden sobreescribir. -Inspirado en `Gin`. - -## Instalación - -```bash -go get github.com/zepyrshut/gorender -``` - -## Uso mínimo - -Las plantillas deben tener la siguiente estructura, observa que las páginas a -procesar están dentro de `pages`. Los demás componentes como bases y fragmentos -pueden estar en el directorio raíz o dentro de un directorio. - -Puedes cambiar el nombre del directorio `template` y `pages`. Ejemplo en la -siguiente sección. - -``` -template/ -├── pages/ -│ └── page.html -├── base.html -└── fragment.html -``` - -```go -import ( - "github.com/zepyrshut/gorender" -) - -func main() { - ren := gorender.New() - - // ... - - td := &gorender.TemplateData{} - ren.Template(w, r, "index.html", td) - - // ... -} -``` - -## Personalización - -> Recuerda que si habilitas el caché, no podrás ver los cambios que realices -> durante el desarrollo. - -```go -func dummyFunc() string { - return "dummy" -} - -func main() { - - customFuncs := template.FuncMap{ - "dummyFunc": dummyFunc, - } - - renderOpts := &gorender.Render{ - EnableCache: true, - TemplatesPath: "template/path", - PageTemplatesPath: "template/path/pages", - Functions: customFuncs, - } - - ren := gorender.New(gorender.WithRenderOptions(renderOpts)) - - // ... - - td := &gorender.TemplateData{} - ren.Template(w, r, "index.html", td) - - // ... -} -``` -## Agradecimientos - -- [Protección CSRF justinas/nosurf](https://github.com/justinas/nosurf) -- [Valicación go-playground/validator](https://github.com/go-playground/validator) - -## Descargo de responsabilidad - -Esta librería fue creada para usar las plantillas en mis proyectos privados, es -posible que también solucione tu problema. Sin embargo, no ofrezco ninguna -garantía de que funcione para todos los casos de uso, tenga el máximo -rendimiento o esté libre de errores. - -Si decides integrarla en tu proyecto, te recomiendo que la pruebes para -asegurarte de que cumple con tus expectativas y requisitos. - -Si encuentras problemas o tienes sugerencias de mejora, puedes colocar tus -aportaciones a través de _issues_ o _pull requests_ en el repositorio. Estaré -encantado de ayudarte. - - diff --git a/gorender/form.go b/gorender/form.go deleted file mode 100644 index 0a2f46e..0000000 --- a/gorender/form.go +++ /dev/null @@ -1,95 +0,0 @@ -package gorender - -import ( - "strings" - - spanish "github.com/go-playground/locales/es" - ut "github.com/go-playground/universal-translator" - "github.com/go-playground/validator/v10" - esTranslations "github.com/go-playground/validator/v10/translations/es" -) - -type FormData struct { - HasErrors bool - Errors map[string]string - Values map[string]string -} - -func NewForm() FormData { - return FormData{ - HasErrors: false, - Errors: map[string]string{}, - Values: map[string]string{}, - } -} - -// AddError añade errores a la estructura FormData, es un mapa cuya clave es una -// cadena de carecteres. Hay que tener en cuenta que cuando se hace una -// validación, se llama a esta función cuya clave es el nombre del campo con lo -// cual si hay más de un error de validación se sobreescriben el anterior y sólo -// se muestra el último error. -func (fd *FormData) AddError(field, message string) { - fd.HasErrors = true - fd.Errors[field] = message -} - -func (fd *FormData) AddValue(field, value string) { - fd.Values[field] = value -} - -type ValidationError struct { - Field string - Reason string -} - -func (fd *FormData) ValidateStruct(s interface{}) (map[string]string, error) { - spanishTranslator := spanish.New() - uni := ut.New(spanishTranslator, spanishTranslator) - trans, _ := uni.GetTranslator("es") - validate := validator.New() - _ = esTranslations.RegisterDefaultTranslations(validate, trans) - errors := make(map[string]string) - var validationErrors []ValidationError - - err := validate.Struct(s) - if err != nil { - if _, ok := err.(*validator.InvalidValidationError); ok { - fd.AddError("form-error", "Error de validación de datos.") - return errors, err - } - - for _, err := range err.(validator.ValidationErrors) { - fieldName, _ := trans.T(err.Field()) - message := strings.Replace(err.Translate(trans), err.Field(), fieldName, -1) - - validationErrors = append(validationErrors, ValidationError{ - Field: strings.ToLower(err.Field()), - Reason: correctMessage(message), - }) - } - - for _, err := range validationErrors { - errors[err.Field] = err.Reason - } - - if len(errors) > 0 { - fd.Errors = errors - fd.HasErrors = true - } - - return errors, err - } - - return errors, nil -} - -func correctMessage(s string) string { - s = strings.TrimSpace(s) - runes := []rune(s) - runes[0] = []rune(strings.ToUpper(string(runes[0])))[0] - if runes[len(runes)-1] != '.' { - runes = append(runes, '.') - } - - return string(runes) -} diff --git a/gorender/functions.go b/gorender/functions.go deleted file mode 100644 index d00def8..0000000 --- a/gorender/functions.go +++ /dev/null @@ -1,71 +0,0 @@ -package gorender - -import ( - "bufio" - "fmt" - "os" - "strings" -) - -func or(a, b string) bool { - if a == "" && b == "" { - return false - } - return true -} - -// containsErrors hace una función similar a "{{ with index ... }}" con el -// añadido de que puede pasarle más de un argumento y comprobar si alguno de -// ellos está en el mapa de errores. -// -// Ejemplo: -// -// {{ if containsErrors .FormData.Errors "name" "email" }} -// {{index .FormData.Errors "name" }} -// {{index .FormData.Errors "email" }} -// {{ end }} -func containsErrors(errors map[string]string, names ...string) bool { - for _, name := range names { - if _, ok := errors[name]; ok { - return true - } - } - return false -} - -func loadTranslations(language string) map[string]string { - translations := make(map[string]string) - filePath := fmt.Sprintf("%s.translate", language) - file, err := os.Open(filePath) - if err != nil { - fmt.Println("Error opening translation file:", err) - return translations - } - defer file.Close() - - scanner := bufio.NewScanner(file) - for scanner.Scan() { - line := scanner.Text() - parts := strings.Split(line, "=") - if len(parts) == 2 { - key := strings.TrimSpace(parts[0]) - value := strings.TrimSpace(parts[1]) - translations[key] = value - } - } - - if err := scanner.Err(); err != nil { - fmt.Println("Error reading translation file:", err) - } - - return translations -} - -func translateKey(key string) string { - translations := loadTranslations("es_ES") - translated := translations[key] - if translated != "" { - return translated - } - return key -} diff --git a/gorender/pages.go b/gorender/pages.go deleted file mode 100644 index 555a5c6..0000000 --- a/gorender/pages.go +++ /dev/null @@ -1,153 +0,0 @@ -package gorender - -import ( - "net/http" - "strconv" -) - -// Pages contiene la información de paginación. -type Pages struct { - // totalElements son la cantidad de elementos totales a paginar. Pueden ser - // total de filas o total de páginas de blog. - totalElements int - // showElements muestra la cantidad máxima de elementos a mostrar en una - // página. - showElements int - // currentPage es la página actual, utilizado como ayuda para mostrar la - // página activa. - currentPage int -} - -// Page contiene la información de una página. -type Page struct { - // number es el número de página. - number int - // active es un dato lógico que indica si la página es la actual. - active bool -} - -// NewPages crea un nuevo objeto para paginación. -func NewPages(totalElements, showElements, currentPage int) Pages { - if showElements <= 0 { - showElements = 1 - } - if currentPage <= 0 { - currentPage = 1 - } - p := Pages{totalElements, showElements, currentPage} - if p.currentPage > p.TotalPages() { - p.currentPage = p.TotalPages() - } - - return p -} - -// Limit devuelve la cantidad de elementos máximos a mostrar por página. -func (p *Pages) Limit() int { - return p.showElements -} - -// TotalPages devuelve la cantidad total de páginas. -func (p *Pages) TotalPages() int { - return (p.totalElements + p.showElements - 1) / p.showElements -} - -// IsFirst indica si la página actual es la primera. -func (p *Pages) IsFirst() bool { - return p.currentPage == 1 -} - -// IsLast indica si la página actual es la última. -func (p *Pages) IsLast() bool { - return p.currentPage == p.TotalPages() -} - -// HasPrevious indica si hay una página anterior. -func (p *Pages) HasPrevious() bool { - return p.currentPage > 1 -} - -// HasNext indica si hay una página siguiente. -func (p *Pages) HasNext() bool { - return p.currentPage < p.TotalPages() -} - -// Previous devuelve el número de la página anterior. -func (p *Pages) Previous() int { - return p.currentPage - 1 -} - -// Next devuelve el número de la página siguiente. -func (p *Pages) Next() int { - return p.currentPage + 1 -} - -func (p *Page) NumberOfPage() int { - return p.number -} - -// IsActive indica si la página es la actual. -func (p *Page) IsActive() bool { - return p.active -} - -// Pages devuelve un arreglo de páginas para mostrar en la paginación. El -// parametro pagesShow indica la cantidad de páginas a mostrar, asignable desde -// la plantilla. -func (p *Pages) Pages(pagesShow int) []*Page { - var pages []*Page - startPage := p.currentPage - (pagesShow / 2) - endPage := p.currentPage + (pagesShow/2 - 1) - - if startPage < 1 { - startPage = 1 - endPage = pagesShow - } - - if endPage > p.TotalPages() { - endPage = p.TotalPages() - startPage = p.TotalPages() - pagesShow + 1 - if startPage < 1 { - startPage = 1 - } - } - - for i := startPage; i <= endPage; i++ { - pages = append(pages, &Page{i, i == p.currentPage}) - } - return pages -} - -func PaginateArray[T any](items []T, currentPage, itemsPerPage int) []T { - totalItems := len(items) - - startIndex := (currentPage - 1) * itemsPerPage - endIndex := startIndex + itemsPerPage - - if startIndex > totalItems { - startIndex = totalItems - } - if endIndex > totalItems { - endIndex = totalItems - } - - return items[startIndex:endIndex] -} - -func PaginationParams(r *http.Request) (int, int, int) { - limit := r.FormValue("limit") - if limit == "" { - limit = "50" - } - page := r.FormValue("page") - if page == "" || page == "0" { - page = "1" - } - - limitInt, _ := strconv.Atoi(limit) - pageInt, _ := strconv.Atoi(page) - offset := (pageInt - 1) * limitInt - actualPage := offset/limitInt + 1 - - return limitInt, offset, actualPage -} diff --git a/gorender/render.go b/gorender/render.go deleted file mode 100644 index f4126be..0000000 --- a/gorender/render.go +++ /dev/null @@ -1,185 +0,0 @@ -package gorender - -import ( - "bytes" - "errors" - "html/template" - "io/fs" - "log/slog" - "net/http" - "path/filepath" - - "github.com/justinas/nosurf" -) - -type templateCache map[string]*template.Template - -type Render struct { - EnableCache bool - // TemplatesPath es la ruta donde se encuentran las plantillas de la - // aplicación, pueden ser bases, fragmentos o ambos. Lo que quieras. - TemplatesPath string - // PageTemplatesPath es la ruta donde se encuentran las plantillas de las - // páginas de la aplicación. Estas son las que van a ser llamadas para - // mostrar en pantalla. - PageTemplatesPath string - Functions template.FuncMap - templateCache templateCache -} - -type OptionFunc func(*Render) -type Data map[string]interface{} -type FeedbackData map[string]string - -type TemplateData struct { - Data Data - // SessionData contiene los datos de la sesión del usuario. - SessionData interface{} - // FeedbackData tiene como función mostrar los mensajes habituales de - // información, advertencia, éxito y error. No va implícitamente relacionado - // con los errores de validación de formularios pero pueden ser usados para - // ello. - FeedbackData FeedbackData - // FormData es una estructura que contiene los errores de validación de los - // formularios además de los valores que se han introducido en los campos. - FormData FormData - CSRFToken string - Page Pages -} - -func WithRenderOptions(opts *Render) OptionFunc { - return func(re *Render) { - re.TemplatesPath = opts.TemplatesPath - re.PageTemplatesPath = opts.PageTemplatesPath - - if opts.Functions != nil { - for k, v := range opts.Functions { - re.Functions[k] = v - } - } - - if opts.EnableCache { - re.EnableCache = opts.EnableCache - re.templateCache, _ = re.createTemplateCache() - } - } -} - -func New(opts ...OptionFunc) *Render { - functions := template.FuncMap{ - "translateKey": translateKey, - "or": or, - "containsErrors": containsErrors, - } - - config := &Render{ - EnableCache: false, - TemplatesPath: "templates", - PageTemplatesPath: "templates/pages", - Functions: functions, - templateCache: templateCache{}, - } - - return config.apply(opts...) -} - -func (re *Render) apply(opts ...OptionFunc) *Render { - for _, opt := range opts { - opt(re) - } - - return re -} - -func addDefaultData(td *TemplateData, r *http.Request) *TemplateData { - td.CSRFToken = nosurf.Token(r) - return td -} - -func (re *Render) Template(w http.ResponseWriter, r *http.Request, tmpl string, td *TemplateData) error { - var tc templateCache - var err error - - if td == nil { - td = &TemplateData{} - } - - if re.EnableCache { - tc = re.templateCache - } else { - slog.Info("creating template cache") - tc, err = re.createTemplateCache() - if err != nil { - slog.Error("error creating template cache:", "error", err) - return err - } - } - - t, ok := tc[tmpl] - if !ok { - return errors.New("can't get template from cache") - } - - buf := new(bytes.Buffer) - td = addDefaultData(td, r) - err = t.Execute(buf, td) - if err != nil { - slog.Error("error executing template:", "error", err) - return err - } - - _, err = buf.WriteTo(w) - if err != nil { - slog.Error("error writing template to browser:", "error", err) - } - - return nil -} - -func findHTMLFiles(root string) ([]string, error) { - var files []string - - err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - - if !d.IsDir() && filepath.Ext(path) == ".gohtml" { - files = append(files, path) - } - - return nil - }) - - if err != nil { - return nil, err - } - - return files, nil -} - -func (re *Render) createTemplateCache() (templateCache, error) { - myCache := templateCache{} - - pagesTemplates, err := findHTMLFiles(re.PageTemplatesPath) - if err != nil { - return myCache, err - } - - files, err := findHTMLFiles(re.TemplatesPath) - if err != nil { - return myCache, err - } - - for _, file := range pagesTemplates { - name := filepath.Base(file) - ts, err := template.New(name).Funcs(re.Functions).ParseFiles(append(files, file)...) - if err != nil { - return myCache, err - } - - myCache[name] = ts - } - - return myCache, nil -} diff --git a/handlers/handlers.go b/handlers/handlers.go index 3743241..a232cf9 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -19,18 +19,4 @@ const ( ErrorGettingAll string = "error_getting_all" InvalidEntityID string = "invalid_entity_id" NotImplemented string = "not_implemented" - - UserUsernameKey string = "user_username_key" - UserEmailKey string = "user_email_key" - UsernameAlReadyExists string = "username_already_exists" - EmailAlreadyExists string = "email_already_exists" - IncorrectPassword string = "incorrect_password" - ErrorGeneratingToken string = "error_generating_token" - LoggedIn string = "logged_in" - - CategoryNameKey string = "category_name_key" - CategoryAlreadyExists string = "category_already_exists" - - ItemsNameKey string = "items_name_key" - NameAlreadyExists string = "name_already_exists" ) diff --git a/middleware/middelware_test.go b/middleware/middelware_test.go deleted file mode 100644 index 84a2a26..0000000 --- a/middleware/middelware_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package middleware - -import ( - "gopher-toolbox/token" - "net/http/httptest" - "testing" - - "github.com/gin-gonic/gin" - - "github.com/stretchr/testify/require" -) - -func Test_authMiddleware(t *testing.T) { - - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - - paseto := token.New() - user := token.UserPayload{ - Username: "test", - // Permissions: map[string]bool{ - // "view_customer": true, - // }, - } - publicToken, error := paseto.Create(user) - require.NoError(t, error) - payload, error := paseto.Verify(publicToken) - require.NoError(t, error) - - c.Request = httptest.NewRequest("GET", "/", nil) - c.Request.Header.Set("Authorization", "Bearer "+publicToken) - - authorizationHeader := c.GetHeader("Authorization") - require.Equal(t, "Bearer "+publicToken, authorizationHeader) - - authMW := AuthMiddleware(paseto) - authMW(c) - - payloadFromMW, exists := c.Get("payload") - require.True(t, exists) - require.Equal(t, payload, payloadFromMW) -} diff --git a/middleware/middleware.go b/middleware/middleware.go deleted file mode 100644 index 2b1f7cf..0000000 --- a/middleware/middleware.go +++ /dev/null @@ -1,77 +0,0 @@ -package middleware - -import ( - "context" - "gopher-toolbox/token" - "log/slog" - "net/http" - "strings" - - "github.com/gin-gonic/gin" - "github.com/google/uuid" -) - -func AuthMiddleware(token *token.Paseto) gin.HandlerFunc { - return func(c *gin.Context) { - authorizationHeader := c.GetHeader("Authorization") - if len(authorizationHeader) == 0 { - slog.Error("authorization header is required") - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "authorization_header_required"}) - return - } - - fields := strings.Fields(authorizationHeader) - if len(fields) != 2 || fields[0] != "Bearer" { - slog.Error("invalid authorization header") - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid_authorization_header"}) - return - } - - accessToken := fields[1] - payload, err := token.Verify(accessToken) - if err != nil { - slog.Error("error verifying token", "error", err.Error()) - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid_signature"}) - return - } - - c.Set("payload", payload) - c.Next() - } -} - -func PermissionMiddleware(requiredPermissions ...string) gin.HandlerFunc { - return func(c *gin.Context) { - // payloadInterface, exists := c.Get("payload") - // if !exists { - // c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "authentication required"}) - // return - // } - - // payload, ok := payloadInterface.(*token.Payload) - // if !ok { - // c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "invalid payload type"}) - // return - // } - - // for _, requiredPermission := range requiredPermissions { - // hasPermission, exists := payload.User.Permissions[requiredPermission] - // if !exists || !hasPermission { - // c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": fmt.Sprintf("Permission '%s' required", requiredPermission)}) - // return - // } - // } - - c.Next() - } -} - -func RequestIDMiddleware() gin.HandlerFunc { - return func(c *gin.Context) { - requestID := uuid.New().String() - ctx := context.WithValue(c.Request.Context(), "request_id", requestID) - c.Request = c.Request.WithContext(ctx) - c.Writer.Header().Set("X-Request-ID", requestID) - c.Next() - } -} diff --git a/testutils/testutils.go b/testutils/testutils.go deleted file mode 100644 index 8d5768d..0000000 --- a/testutils/testutils.go +++ /dev/null @@ -1,55 +0,0 @@ -package testutils - -import ( - "bytes" - "encoding/json" - "net/http" -) - -func NewHTTPRequestJSON(method, url string, body interface{}, queryParams map[string]string) (*http.Request, error) { - bodyBytes, err := json.Marshal(body) - if err != nil { - return nil, err - } - - request, err := http.NewRequest(method, url, bytes.NewReader(bodyBytes)) - if err != nil { - return nil, err - } - request.Header.Set("Content-Type", "application/json") - - if len(queryParams) > 0 { - query := request.URL.Query() - for k, v := range queryParams { - query.Add(k, v) - } - request.URL.RawQuery = query.Encode() - } - - return request, nil -} - -func NewHTTPRequest(method, url string, params map[string]string) (*http.Request, error) { - request, err := http.NewRequest(method, url, nil) - if err != nil { - return nil, err - } - - if len(params) > 0 { - query := request.URL.Query() - for k, v := range params { - query.Add(k, v) - } - request.URL.RawQuery = query.Encode() - } - - return request, nil -} - -func Decode(input interface{}, output interface{}) error { - bytes, err := json.Marshal(input) - if err != nil { - return err - } - return json.Unmarshal(bytes, output) -} diff --git a/token/token.go b/token/token.go deleted file mode 100644 index 2189be5..0000000 --- a/token/token.go +++ /dev/null @@ -1,67 +0,0 @@ -package token - -import ( - "crypto/ed25519" - "time" - - "github.com/google/uuid" - "github.com/o1egl/paseto" -) - -type UserPayload struct { - Username string `json:"username"` - // TODO: Add permissions -} - -type Payload struct { - UUID uuid.UUID `json:"token_uuid"` - User UserPayload `json:"user"` - IssuedAt time.Time `json:"issued_at"` - ExpiredAt time.Time `json:"expired_at"` -} - -type Paseto struct { - paseto *paseto.V2 - publicKey ed25519.PublicKey - privateKey ed25519.PrivateKey -} - -func New() *Paseto { - publicKey, privateKey, _ := ed25519.GenerateKey(nil) - return &Paseto{ - paseto: paseto.NewV2(), - publicKey: publicKey, - privateKey: privateKey, - } -} - -func NewPayload(user UserPayload) *Payload { - // TODO: add documentation and advert to developers: tokenID != user.UUID - tokenID, err := uuid.NewRandom() - if err != nil { - return NewPayload(user) - } - - payload := &Payload{ - UUID: tokenID, - User: user, - IssuedAt: time.Now(), - ExpiredAt: time.Now().Add(time.Hour * 24 * 7), - } - - return payload -} - -func (m *Paseto) Create(user UserPayload) (string, error) { - return m.paseto.Sign(m.privateKey, NewPayload(user), nil) -} - -func (m *Paseto) Verify(token string) (*Payload, error) { - var payload Payload - err := m.paseto.Verify(token, m.publicKey, &payload, nil) - if err != nil { - return nil, err - } - - return &payload, nil -} diff --git a/token/token_test.go b/token/token_test.go deleted file mode 100644 index ecbfcc9..0000000 --- a/token/token_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package token - -import ( - "testing" - - "github.com/google/uuid" - "github.com/stretchr/testify/require" - "github.com/zepyrshut/esfaker" -) - -func Test_New(t *testing.T) { - paseto := New() - - require.NotNil(t, paseto.paseto) - require.NotNil(t, paseto.privateKey) - require.NotNil(t, paseto.publicKey) -} - -func Test_NewPayload(t *testing.T) { - user := createRandomUser() - payload := NewPayload(user) - - require.True(t, isValidUUID(payload.UUID)) - require.Equal(t, user, payload.User) -} - -func Test_CreateToken(t *testing.T) { - token := New() - user := createRandomUser() - signature, err := token.Create(user) - - require.Nil(t, err) - require.NotEmpty(t, signature) -} - -func Test_VerifyToken(t *testing.T) { - token := New() - user := createRandomUser() - signature, _ := token.Create(user) - payload, err := token.Verify(signature) - - require.Nil(t, err) - require.Equal(t, user, payload.User) -} - -func Test_VerifyToken_InvalidToken(t *testing.T) { - token := New() - _, err := token.Verify("invalid-token") - - require.NotNil(t, err) -} - -func isValidUUID(u uuid.UUID) bool { - _, err := uuid.Parse(u.String()) - return err == nil -} - -func createRandomUser() UserPayload { - return UserPayload{ - Username: esfaker.Chars(5, 10), - } -} diff --git a/utils/utils.go b/utils/utils.go index 5f9b6ce..a591042 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1,16 +1,16 @@ package utils import ( - "bufio" "fmt" - "golang.org/x/text/transform" - "golang.org/x/text/unicode/norm" "log/slog" - "os" "regexp" "strings" "time" "unicode" + + "golang.org/x/text/runes" + "golang.org/x/text/transform" + "golang.org/x/text/unicode/norm" ) func CorrectTimezone(timeStamp time.Time) time.Time { @@ -34,34 +34,10 @@ func GetBoolFromString(s string) bool { return false } -func LoadEnvFile(filename string) error { - file, err := os.Open(filename) - if err != nil { - return err - } - defer file.Close() - - scanner := bufio.NewScanner(file) - for scanner.Scan() { - line := scanner.Text() - if len(line) == 0 || strings.HasPrefix(line, "#") { - continue - } - parts := strings.SplitN(line, "=", 2) - if len(parts) != 2 { - continue - } - key := strings.TrimSpace(parts[0]) - value := strings.TrimSpace(parts[1]) - os.Setenv(key, value) - } - return scanner.Err() -} - func Slugify(s string) string { s = strings.ToLower(s) - t := transform.Chain(norm.NFD, transform.RemoveFunc(isMn), norm.NFC) + t := transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC) s, _, _ = transform.String(t, s) s = strings.ReplaceAll(s, " ", "-")