summaryrefslogtreecommitdiff
path: root/utils
diff options
context:
space:
mode:
authorBobby <[email protected]>2025-12-19 18:01:24 +0530
committerBobby <[email protected]>2025-12-19 18:01:24 +0530
commitb1bfec1ce2987d9fe0cc52e5ae9115977fdf8c24 (patch)
tree7080b7dc97522ffe0837a1e0b2965489d7e67664 /utils
parent767297e28d47ee9cf3722054e41caa837f0e68d2 (diff)
downloadlain-b1bfec1ce2987d9fe0cc52e5ae9115977fdf8c24.tar.xz
lain-b1bfec1ce2987d9fe0cc52e5ae9115977fdf8c24.zip
added utils, templates, routes, types, middleware, processors and a whole lot of things for a basic login page
Diffstat (limited to 'utils')
-rw-r--r--utils/auth/auth.go17
-rw-r--r--utils/crypto/crypto.go71
-rw-r--r--utils/meta/request.go47
-rw-r--r--utils/meta/title.go13
-rw-r--r--utils/shortcuts/helpers.go50
-rw-r--r--utils/shortcuts/redirect.go23
-rw-r--r--utils/shortcuts/render.go38
-rw-r--r--utils/urls/attach.go38
-rw-r--r--utils/urls/namespace.go7
-rw-r--r--utils/urls/path.go51
-rw-r--r--utils/urls/registery.go28
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(&params))
+ 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),
+}