diff options
Diffstat (limited to 'utils')
| -rw-r--r-- | utils/auth/auth.go | 17 | ||||
| -rw-r--r-- | utils/crypto/crypto.go | 71 | ||||
| -rw-r--r-- | utils/meta/request.go | 47 | ||||
| -rw-r--r-- | utils/meta/title.go | 13 | ||||
| -rw-r--r-- | utils/shortcuts/helpers.go | 50 | ||||
| -rw-r--r-- | utils/shortcuts/redirect.go | 23 | ||||
| -rw-r--r-- | utils/shortcuts/render.go | 38 | ||||
| -rw-r--r-- | utils/urls/attach.go | 38 | ||||
| -rw-r--r-- | utils/urls/namespace.go | 7 | ||||
| -rw-r--r-- | utils/urls/path.go | 51 | ||||
| -rw-r--r-- | utils/urls/registery.go | 28 |
11 files changed, 383 insertions, 0 deletions
diff --git a/utils/auth/auth.go b/utils/auth/auth.go new file mode 100644 index 0000000..3a45ac3 --- /dev/null +++ b/utils/auth/auth.go @@ -0,0 +1,17 @@ +package auth + +import ( + session "lain/sessions" + + "github.com/gofiber/fiber/v2" +) + +func IsAuthenticated(context *fiber.Ctx) bool { + session, err := session.Store.Get(context) + if err != nil { + return false + } + + email := session.Get("email") + return email != nil +} diff --git a/utils/crypto/crypto.go b/utils/crypto/crypto.go new file mode 100644 index 0000000..54d2eec --- /dev/null +++ b/utils/crypto/crypto.go @@ -0,0 +1,71 @@ +package crypto + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha256" + "encoding/base64" + "fmt" + "io" + "lain/config" +) + +func getKey() []byte { + hash := sha256.Sum256([]byte(config.Server.AppSecret)) + return hash[:] +} + +func Encrypt(plaintext string) (string, error) { + key := getKey() + + block, err := aes.NewCipher(key) + if err != nil { + return "", err + } + + gcm, err := cipher.NewGCM(block) + if err != nil { + return "", err + } + + nonce := make([]byte, gcm.NonceSize()) + if _, err := io.ReadFull(rand.Reader, nonce); err != nil { + return "", err + } + + ciphertext := gcm.Seal(nonce, nonce, []byte(plaintext), nil) + return base64.URLEncoding.EncodeToString(ciphertext), nil +} + +func Decrypt(ciphertext string) (string, error) { + key := getKey() + + ciphertextBytes, err := base64.URLEncoding.DecodeString(ciphertext) + if err != nil { + return "", err + } + + block, err := aes.NewCipher(key) + if err != nil { + return "", err + } + + gcm, err := cipher.NewGCM(block) + if err != nil { + return "", err + } + + nonceSize := gcm.NonceSize() + if len(ciphertextBytes) < nonceSize { + return "", fmt.Errorf("ciphertext too short") + } + + nonce, ciphertextBytes := ciphertextBytes[:nonceSize], ciphertextBytes[nonceSize:] + plaintextBytes, err := gcm.Open(nil, nonce, ciphertextBytes, nil) + if err != nil { + return "", err + } + + return string(plaintextBytes), nil +} diff --git a/utils/meta/request.go b/utils/meta/request.go new file mode 100644 index 0000000..e1db77c --- /dev/null +++ b/utils/meta/request.go @@ -0,0 +1,47 @@ +package meta + +import ( + "lain/types" + + "github.com/gofiber/fiber/v2" +) + +func BuildRequest(context *fiber.Ctx) types.HTTPRequest { + return types.HTTPRequest{ + Path: context.Path(), + Method: context.Method(), + Query: buildQueryParams(context), + Params: buildRouteParams(context), + QueryString: string(context.Request().URI().QueryString()), + IP: context.IP(), + URL: context.OriginalURL(), + } +} + +func buildQueryParams(context *fiber.Ctx) []types.HTTPQueryParam { + params := make([]types.HTTPQueryParam, 0) + args := context.Request().URI().QueryArgs() + + args.VisitAll(transformQueryParam(¶ms)) + return params +} + +func buildRouteParams(context *fiber.Ctx) []types.HTTPQueryParam { + params := make([]types.HTTPQueryParam, 0) + for key, value := range context.AllParams() { + params = append(params, types.HTTPQueryParam{ + Key: key, + Value: value, + }) + } + return params +} + +func transformQueryParam(params *[]types.HTTPQueryParam) func(key, value []byte) { + return func(key, value []byte) { + *params = append(*params, types.HTTPQueryParam{ + Key: string(key), + Value: string(value), + }) + } +} diff --git a/utils/meta/title.go b/utils/meta/title.go new file mode 100644 index 0000000..27ee2ed --- /dev/null +++ b/utils/meta/title.go @@ -0,0 +1,13 @@ +package meta + +import ( + "fmt" + "lain/config" + + "github.com/gofiber/fiber/v2" +) + +func SetPageTitle(context *fiber.Ctx, title string) { + title = fmt.Sprintf("%s | %s", title, config.Server.AppName) + context.Locals("Title", title) +} diff --git a/utils/shortcuts/helpers.go b/utils/shortcuts/helpers.go new file mode 100644 index 0000000..8503a2d --- /dev/null +++ b/utils/shortcuts/helpers.go @@ -0,0 +1,50 @@ +package shortcuts + +import ( + "fmt" + "reflect" + "strings" +) + +func structValue(data any) (reflect.Value, error) { + v := reflect.ValueOf(data) + if v.Kind() == reflect.Pointer { + v = v.Elem() + } + + if v.Kind() != reflect.Struct { + return reflect.Value{}, fmt.Errorf( + "Render: unsupported bind type %T; must be struct or *struct", + data, + ) + } + + return v, nil +} + +func mapStruct(v reflect.Value) map[string]any { + t := v.Type() + result := make(map[string]any, v.NumField()) + + for i := 0; i < v.NumField(); i++ { + fieldType := t.Field(i) + if !fieldType.IsExported() { + continue + } + + key := fieldType.Name + if tag := fieldType.Tag.Get("json"); tag != "" && tag != "-" { + if idx := strings.IndexByte(tag, ','); idx >= 0 { + if idx > 0 { + key = tag[:idx] + } + } else { + key = tag + } + } + + result[key] = v.Field(i).Interface() + } + + return result +} diff --git a/utils/shortcuts/redirect.go b/utils/shortcuts/redirect.go new file mode 100644 index 0000000..aa760dc --- /dev/null +++ b/utils/shortcuts/redirect.go @@ -0,0 +1,23 @@ +package shortcuts + +import ( + "lain/utils/urls" + + "github.com/gofiber/fiber/v2" +) + +func Redirect(ctx *fiber.Ctx, routeName string) error { + path, ok := urls.GetFullPath(routeName) + if !ok { + return fiber.ErrNotFound + } + return ctx.Redirect(path) +} + +func RedirectWithStatus(ctx *fiber.Ctx, routeName string, statusCode int) error { + path, ok := urls.GetFullPath(routeName) + if !ok { + return fiber.ErrNotFound + } + return ctx.Redirect(path, statusCode) +} diff --git a/utils/shortcuts/render.go b/utils/shortcuts/render.go new file mode 100644 index 0000000..1efeb61 --- /dev/null +++ b/utils/shortcuts/render.go @@ -0,0 +1,38 @@ +package shortcuts + +import ( + "maps" + + "github.com/gofiber/fiber/v2" +) + +func Render(ctx *fiber.Ctx, template string, data any) error { + bind := make(fiber.Map) + + ctx.Context().VisitUserValues(func(key []byte, value any) { + bind[string(key)] = value + }) + + if data != nil { + switch v := data.(type) { + case map[string]any: + maps.Copy(bind, v) + case fiber.Map: + maps.Copy(bind, v) + default: + rv, err := structValue(data) + if err != nil { + return err + } + + maps.Copy(bind, mapStruct(rv)) + } + } + + return ctx.Render(template, bind) +} + +func RenderWithStatus(ctx *fiber.Ctx, template string, data any, statusCode int) error { + ctx.Status(statusCode) + return Render(ctx, template, data) +} diff --git a/utils/urls/attach.go b/utils/urls/attach.go new file mode 100644 index 0000000..70c6db9 --- /dev/null +++ b/utils/urls/attach.go @@ -0,0 +1,38 @@ +package urls + +import ( + "lain/types" + "log" + + "github.com/gofiber/fiber/v2" +) + +var methodBinders = map[types.HTTPMethod]func(fiber.Router, string, fiber.Handler) fiber.Router{ + types.GET: func(r fiber.Router, path string, h fiber.Handler) fiber.Router { return r.Get(path, h) }, + types.POST: func(r fiber.Router, path string, h fiber.Handler) fiber.Router { return r.Post(path, h) }, + types.PUT: func(r fiber.Router, path string, h fiber.Handler) fiber.Router { return r.Put(path, h) }, + types.PATCH: func(r fiber.Router, path string, h fiber.Handler) fiber.Router { return r.Patch(path, h) }, + types.DELETE: func(r fiber.Router, path string, h fiber.Handler) fiber.Router { return r.Delete(path, h) }, + types.OPTIONS: func(r fiber.Router, path string, h fiber.Handler) fiber.Router { return r.Options(path, h) }, + types.HEAD: func(r fiber.Router, path string, h fiber.Handler) fiber.Router { return r.Head(path, h) }, +} + +func Attach(app *fiber.App) { + namespaceGroups := make(map[string]fiber.Router) + + for fullName, route := range registry.routes { + group, exists := namespaceGroups[route.namespace] + if !exists { + group = app.Group("/" + route.namespace) + namespaceGroups[route.namespace] = group + } + + binder, ok := methodBinders[route.method] + if !ok { + log.Fatalf("%s", "unsupported HTTP method: "+string(route.method)) + } + + fiberRoute := binder(group, route.path, route.handler) + fiberRoute.Name(fullName) + } +} diff --git a/utils/urls/namespace.go b/utils/urls/namespace.go new file mode 100644 index 0000000..7bb5311 --- /dev/null +++ b/utils/urls/namespace.go @@ -0,0 +1,7 @@ +package urls + +func SetNamespace(namespace string) { + registry.mutex.Lock() + defer registry.mutex.Unlock() + registry.currentNamespace = namespace +} diff --git a/utils/urls/path.go b/utils/urls/path.go new file mode 100644 index 0000000..e6d4ed7 --- /dev/null +++ b/utils/urls/path.go @@ -0,0 +1,51 @@ +package urls + +import ( + "lain/types" + "strings" + + "github.com/gofiber/fiber/v2" +) + +func Path(method types.HTTPMethod, path string, handler fiber.Handler, name string) { + registry.mutex.Lock() + defer registry.mutex.Unlock() + + namespace := registry.currentNamespace + fullName := name + fullPath := path + + if namespace != "" { + if !strings.HasPrefix(path, "/") { + path = "/" + path + } + + fullName = namespace + "." + name + fullPath = "/" + namespace + path + } else { + if !strings.HasPrefix(fullPath, "/") { + fullPath = "/" + fullPath + } + } + + registry.routes[fullName] = registeredRoute{ + method: method, + path: path, + handler: handler, + namespace: namespace, + name: name, + fullPath: fullPath, + } +} + +func GetFullPath(routeName string) (string, bool) { + registry.mutex.Lock() + defer registry.mutex.Unlock() + + route, ok := registry.routes[routeName] + if !ok { + return "", false + } + + return route.fullPath, true +} diff --git a/utils/urls/registery.go b/utils/urls/registery.go new file mode 100644 index 0000000..d1bff93 --- /dev/null +++ b/utils/urls/registery.go @@ -0,0 +1,28 @@ +package urls + +import ( + "sync" + + "lain/types" + + "github.com/gofiber/fiber/v2" +) + +type registeredRoute struct { + method types.HTTPMethod + path string + handler fiber.Handler + namespace string + name string + fullPath string +} + +type routeRegistry struct { + mutex sync.Mutex + currentNamespace string + routes map[string]registeredRoute +} + +var registry = &routeRegistry{ + routes: make(map[string]registeredRoute), +} |
