aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBobby <[email protected]>2026-03-07 14:25:54 +0530
committerBobby <[email protected]>2026-03-07 14:25:54 +0530
commit41926c10ea2e8496ce4b528262f5047ccbe6f155 (patch)
tree67ff5a27bb50faec368cf7ef38a1b1f2866459dd
parented2a033d7c08e448f5c6fd035e2de8f51431b597 (diff)
downloaddove-41926c10ea2e8496ce4b528262f5047ccbe6f155.tar.xz
dove-41926c10ea2e8496ce4b528262f5047ccbe6f155.zip
Implement authentication system with login/logout functionality and session management
-rw-r--r--config/config.go7
-rw-r--r--config/functions.go4
-rw-r--r--controllers/auth.go33
-rw-r--r--enums/error.go12
-rw-r--r--go.mod2
-rw-r--r--messages/auth.go7
-rw-r--r--messages/config.go10
-rw-r--r--messages/database.go6
-rw-r--r--messages/logger.go2
-rw-r--r--messages/session.go5
-rw-r--r--messages/shortcuts.go5
-rw-r--r--messages/toml.go2
-rw-r--r--middleware/auth.go17
-rw-r--r--middleware/middleware.go14
-rw-r--r--pages/dashboard.go11
-rw-r--r--pages/home.go17
-rw-r--r--router/auth.go14
-rw-r--r--router/base.go13
-rw-r--r--router/dashboard.go14
-rw-r--r--router/router.go33
-rw-r--r--services/auth.go37
-rw-r--r--session/constants.go10
-rw-r--r--session/session.go23
-rw-r--r--types/auth.go6
-rw-r--r--types/errors.go12
-rw-r--r--types/response.go5
-rw-r--r--utils/auth/auth.go46
-rw-r--r--utils/auth/constants.go5
-rw-r--r--utils/meta/body.go12
-rw-r--r--utils/shortcuts/error.go65
-rw-r--r--utils/shortcuts/functions.go97
-rw-r--r--utils/shortcuts/redirect.go25
-rw-r--r--utils/shortcuts/render.go22
33 files changed, 580 insertions, 13 deletions
diff --git a/config/config.go b/config/config.go
index 78eb6a8..ed9dab3 100644
--- a/config/config.go
+++ b/config/config.go
@@ -14,8 +14,9 @@ var (
)
var (
- DataDir string
- DevMode bool
+ AuthEnabled bool
+ DataDir string
+ DevMode bool
)
func init() {
@@ -34,5 +35,7 @@ func init() {
}
}
+ AuthEnabled = isAuthEnabled()
+
logger.Successf(LOG_PREFIX, messages.ConfigLoaded)
}
diff --git a/config/functions.go b/config/functions.go
index 58ab717..1a49226 100644
--- a/config/functions.go
+++ b/config/functions.go
@@ -56,3 +56,7 @@ func loadConfigFile(configFilePath string) error {
func parseSection(target any) error {
return toml.Parse(target)
}
+
+func isAuthEnabled() bool {
+ return Server.Username != "" && Server.Password != ""
+}
diff --git a/controllers/auth.go b/controllers/auth.go
new file mode 100644
index 0000000..444026a
--- /dev/null
+++ b/controllers/auth.go
@@ -0,0 +1,33 @@
+package controllers
+
+import (
+ "dove/services"
+ "dove/types"
+ "dove/utils/meta"
+ "dove/utils/shortcuts"
+
+ "github.com/gofiber/fiber/v2"
+)
+
+func Login(context *fiber.Ctx) error {
+ body, parseError := meta.Body[types.LoginRequest](context)
+ if parseError != nil {
+ return shortcuts.BadRequest(context, parseError)
+ }
+
+ _, serviceError := services.Authenticate(context, body)
+ if serviceError != nil {
+ return shortcuts.HandleError(context, serviceError)
+ }
+
+ return shortcuts.Redirect(context, "dashboard.index")
+}
+
+func Logout(context *fiber.Ctx) error {
+ _, serviceError := services.Deauthenticate(context)
+ if serviceError != nil {
+ return shortcuts.HandleError(context, serviceError)
+ }
+
+ return shortcuts.Redirect(context, "home")
+} \ No newline at end of file
diff --git a/enums/error.go b/enums/error.go
new file mode 100644
index 0000000..b3dd10a
--- /dev/null
+++ b/enums/error.go
@@ -0,0 +1,12 @@
+package enums
+
+type ErrorKind string
+
+const (
+ BadRequest ErrorKind = "bad_request"
+ Forbidden ErrorKind = "forbidden"
+ Internal ErrorKind = "internal"
+ NotFound ErrorKind = "not_found"
+ Unauthorized ErrorKind = "unauthorized"
+ Unprocessable ErrorKind = "unprocessable"
+)
diff --git a/go.mod b/go.mod
index dd4d734..d8956de 100644
--- a/go.mod
+++ b/go.mod
@@ -3,6 +3,7 @@ module dove
go 1.25.0
require (
+ github.com/gofiber/fiber/v2 v2.52.12
github.com/pelletier/go-toml/v2 v2.2.4
go.uber.org/zap v1.27.1
gorm.io/driver/sqlite v1.6.0
@@ -11,7 +12,6 @@ require (
require (
github.com/andybalholm/brotli v1.1.0 // indirect
- github.com/gofiber/fiber/v2 v2.52.12 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
diff --git a/messages/auth.go b/messages/auth.go
new file mode 100644
index 0000000..0cad598
--- /dev/null
+++ b/messages/auth.go
@@ -0,0 +1,7 @@
+package messages
+
+const (
+ AuthAuthenticated = "Authenticated successfully."
+ AuthInvalidCredentials = "Invalid username or password."
+ AuthLoggedOut = "Logged out successfully."
+)
diff --git a/messages/config.go b/messages/config.go
index fcb42c3..43c9d7f 100644
--- a/messages/config.go
+++ b/messages/config.go
@@ -1,9 +1,9 @@
package messages
const (
- ConfigFileLoadFailed = "failed to load config: %v"
- ConfigFileReadFailed = "failed to read config file %s: %s"
- ConfigLoaded = "configuration loaded successfully"
- ConfigSectionInvalid = "config section '%s' has invalid data"
- ConfigSectionParseFailed = "failed to parse config section: %v"
+ ConfigFileLoadFailed = "Failed to load config: %v"
+ ConfigFileReadFailed = "Failed to read config file %s: %s."
+ ConfigLoaded = "Configuration loaded successfully."
+ ConfigSectionInvalid = "Config section '%s' has invalid data."
+ ConfigSectionParseFailed = "Failed to parse config section: %v"
)
diff --git a/messages/database.go b/messages/database.go
index 119bdef..57bc27f 100644
--- a/messages/database.go
+++ b/messages/database.go
@@ -1,7 +1,7 @@
package messages
const (
- DatabaseConnected = "connected to %s"
- DatabaseConnectionFailed = "failed to connect to database: %v"
- DatabasePoolConfigFailed = "failed to configure connection pool: %v"
+ DatabaseConnected = "Connected to %s."
+ DatabaseConnectionFailed = "Failed to connect to database: %v"
+ DatabasePoolConfigFailed = "Failed to configure connection pool: %v"
)
diff --git a/messages/logger.go b/messages/logger.go
index 46c12e6..755e259 100644
--- a/messages/logger.go
+++ b/messages/logger.go
@@ -1,5 +1,5 @@
package messages
const (
- LoggerNotInitialized = "logger.Init() was not called"
+ LoggerNotInitialized = "logger.Init() was not called."
)
diff --git a/messages/session.go b/messages/session.go
new file mode 100644
index 0000000..b35b85f
--- /dev/null
+++ b/messages/session.go
@@ -0,0 +1,5 @@
+package messages
+
+const (
+ SessionInitialized = "Session store initialized."
+)
diff --git a/messages/shortcuts.go b/messages/shortcuts.go
new file mode 100644
index 0000000..b0c0d91
--- /dev/null
+++ b/messages/shortcuts.go
@@ -0,0 +1,5 @@
+package messages
+
+const (
+ ShortcutUnsupportedBindType = "Bind data must be a struct, *struct, fiber.Map, or map[string]any."
+) \ No newline at end of file
diff --git a/messages/toml.go b/messages/toml.go
index ee4cbfd..9ecc069 100644
--- a/messages/toml.go
+++ b/messages/toml.go
@@ -1,5 +1,5 @@
package messages
const (
- ParseTargetMustBeStructPointer = "parse target must be a pointer to a struct"
+ ParseTargetMustBeStructPointer = "Parse target must be a pointer to a struct."
)
diff --git a/middleware/auth.go b/middleware/auth.go
new file mode 100644
index 0000000..008aa2c
--- /dev/null
+++ b/middleware/auth.go
@@ -0,0 +1,17 @@
+package middleware
+
+import (
+ "dove/utils/auth"
+ "dove/utils/shortcuts"
+
+ "github.com/gofiber/fiber/v2"
+)
+
+func authentication(context *fiber.Ctx) error {
+ switch auth.IsAuthenticated(context) {
+ case true:
+ return context.Next()
+ default:
+ return shortcuts.Redirect(context, "auth.login")
+ }
+}
diff --git a/middleware/middleware.go b/middleware/middleware.go
new file mode 100644
index 0000000..dffc109
--- /dev/null
+++ b/middleware/middleware.go
@@ -0,0 +1,14 @@
+package middleware
+
+import (
+ "dove/config"
+
+ "github.com/gofiber/fiber/v2"
+)
+
+func Initialize(application *fiber.App) {
+ switch config.AuthEnabled {
+ case true:
+ application.Use(authentication)
+ }
+}
diff --git a/pages/dashboard.go b/pages/dashboard.go
new file mode 100644
index 0000000..323cd1f
--- /dev/null
+++ b/pages/dashboard.go
@@ -0,0 +1,11 @@
+package pages
+
+import (
+ "dove/utils/shortcuts"
+
+ "github.com/gofiber/fiber/v2"
+)
+
+func Dashboard(context *fiber.Ctx) error {
+ return shortcuts.Render(context, "dashboard", nil)
+}
diff --git a/pages/home.go b/pages/home.go
new file mode 100644
index 0000000..cbf5a5d
--- /dev/null
+++ b/pages/home.go
@@ -0,0 +1,17 @@
+package pages
+
+import (
+ "dove/config"
+ "dove/utils/shortcuts"
+
+ "github.com/gofiber/fiber/v2"
+)
+
+func Home(context *fiber.Ctx) error {
+ switch config.AuthEnabled {
+ case true:
+ return shortcuts.Render(context, "auth/login", nil)
+ default:
+ return shortcuts.Render(context, "dashboard", nil)
+ }
+}
diff --git a/router/auth.go b/router/auth.go
new file mode 100644
index 0000000..f8f41fc
--- /dev/null
+++ b/router/auth.go
@@ -0,0 +1,14 @@
+package router
+
+import (
+ "dove/controllers"
+ "dove/enums"
+ "dove/utils/urls"
+)
+
+func init() {
+ urls.SetNamespace("auth")
+
+ urls.Path(enums.Post, "/login", controllers.Login, "login")
+ urls.Path(enums.Get, "/logout", controllers.Logout, "logout")
+}
diff --git a/router/base.go b/router/base.go
new file mode 100644
index 0000000..3a5e7ee
--- /dev/null
+++ b/router/base.go
@@ -0,0 +1,13 @@
+package router
+
+import (
+ "dove/enums"
+ "dove/pages"
+ "dove/utils/urls"
+)
+
+func init() {
+ urls.SetNamespace("")
+
+ urls.Path(enums.Get, "/", pages.Home, "home")
+}
diff --git a/router/dashboard.go b/router/dashboard.go
new file mode 100644
index 0000000..1ab4dda
--- /dev/null
+++ b/router/dashboard.go
@@ -0,0 +1,14 @@
+package router
+
+import (
+ "dove/enums"
+ "dove/pages"
+ "dove/utils/auth"
+ "dove/utils/urls"
+)
+
+func init() {
+ urls.SetNamespace("dashboard")
+
+ urls.Path(enums.Get, "/", auth.RequireAuthentication(pages.Dashboard), "index")
+}
diff --git a/router/router.go b/router/router.go
new file mode 100644
index 0000000..68ecad9
--- /dev/null
+++ b/router/router.go
@@ -0,0 +1,33 @@
+package router
+
+import (
+ "dove/utils/shortcuts"
+ "dove/utils/urls"
+
+ "github.com/gofiber/fiber/v2"
+)
+
+func Initialize(application *fiber.App) {
+ application.Static("/static", "./static")
+ urls.Attach(application)
+}
+
+func ErrorHandler(context *fiber.Ctx, err error) error {
+ statusCode := fiber.StatusInternalServerError
+ if fiberError, ok := err.(*fiber.Error); ok {
+ statusCode = fiberError.Code
+ }
+
+ switch statusCode {
+ case fiber.StatusBadRequest:
+ return shortcuts.BadRequest(context, err)
+ case fiber.StatusForbidden:
+ return shortcuts.Forbidden(context, err)
+ case fiber.StatusNotFound:
+ return shortcuts.NotFound(context, err)
+ case fiber.StatusUnauthorized:
+ return shortcuts.Unauthorized(context, err)
+ default:
+ return shortcuts.InternalServerError(context, err)
+ }
+} \ No newline at end of file
diff --git a/services/auth.go b/services/auth.go
new file mode 100644
index 0000000..ce62d10
--- /dev/null
+++ b/services/auth.go
@@ -0,0 +1,37 @@
+package services
+
+import (
+ "dove/config"
+ "dove/enums"
+ "dove/messages"
+ "dove/types"
+ "dove/utils/auth"
+ "dove/utils/shortcuts"
+
+ "github.com/gofiber/fiber/v2"
+)
+
+func Authenticate(context *fiber.Ctx, request types.LoginRequest) (*types.MessageResponse, *types.ServiceError) {
+ switch request.Username == config.Server.Username && request.Password == config.Server.Password {
+ case true:
+ if sessionError := auth.Authenticate(context); sessionError != nil {
+ return nil, shortcuts.ServiceError(enums.Internal, sessionError.Error())
+ }
+
+ return &types.MessageResponse{
+ Message: messages.AuthAuthenticated,
+ }, nil
+ default:
+ return nil, shortcuts.ServiceError(enums.Unauthorized, messages.AuthInvalidCredentials)
+ }
+}
+
+func Deauthenticate(context *fiber.Ctx) (*types.MessageResponse, *types.ServiceError) {
+ if sessionError := auth.Deauthenticate(context); sessionError != nil {
+ return nil, shortcuts.ServiceError(enums.Internal, sessionError.Error())
+ }
+
+ return &types.MessageResponse{
+ Message: messages.AuthLoggedOut,
+ }, nil
+} \ No newline at end of file
diff --git a/session/constants.go b/session/constants.go
new file mode 100644
index 0000000..d6a7f69
--- /dev/null
+++ b/session/constants.go
@@ -0,0 +1,10 @@
+package session
+
+import "time"
+
+const (
+ LOG_PREFIX = "Session"
+ SESSION_COOKIE_NAME = "dove_session"
+ SESSION_COOKIE_SAME_SITE = "Lax"
+ SESSION_EXPIRATION = 24 * time.Hour
+)
diff --git a/session/session.go b/session/session.go
new file mode 100644
index 0000000..89b7ef8
--- /dev/null
+++ b/session/session.go
@@ -0,0 +1,23 @@
+package session
+
+import (
+ "fmt"
+
+ "dove/messages"
+ "dove/utils/logger"
+
+ "github.com/gofiber/fiber/v2/middleware/session"
+)
+
+var Store *session.Store
+
+func init() {
+ Store = session.New(session.Config{
+ KeyLookup: fmt.Sprintf("cookie:%s", SESSION_COOKIE_NAME),
+ CookieHTTPOnly: true,
+ CookieSameSite: SESSION_COOKIE_SAME_SITE,
+ Expiration: SESSION_EXPIRATION,
+ })
+
+ logger.Successf(LOG_PREFIX, messages.SessionInitialized)
+}
diff --git a/types/auth.go b/types/auth.go
new file mode 100644
index 0000000..1792011
--- /dev/null
+++ b/types/auth.go
@@ -0,0 +1,6 @@
+package types
+
+type LoginRequest struct {
+ Username string `form:"username"`
+ Password string `form:"password"`
+}
diff --git a/types/errors.go b/types/errors.go
new file mode 100644
index 0000000..1e5a562
--- /dev/null
+++ b/types/errors.go
@@ -0,0 +1,12 @@
+package types
+
+import "dove/enums"
+
+type ServiceError struct {
+ Kind enums.ErrorKind
+ Message string
+}
+
+func (self *ServiceError) Error() string {
+ return self.Message
+}
diff --git a/types/response.go b/types/response.go
new file mode 100644
index 0000000..d38051d
--- /dev/null
+++ b/types/response.go
@@ -0,0 +1,5 @@
+package types
+
+type MessageResponse struct {
+ Message string
+}
diff --git a/utils/auth/auth.go b/utils/auth/auth.go
new file mode 100644
index 0000000..5abb496
--- /dev/null
+++ b/utils/auth/auth.go
@@ -0,0 +1,46 @@
+package auth
+
+import (
+ "dove/session"
+
+ "github.com/gofiber/fiber/v2"
+)
+
+func IsAuthenticated(context *fiber.Ctx) bool {
+ activeSession, sessionError := session.Store.Get(context)
+ if sessionError != nil {
+ return false
+ }
+
+ return activeSession.Get(SESSION_AUTHENTICATED_KEY) != nil
+}
+
+func RequireAuthentication(handler fiber.Handler) fiber.Handler {
+ return func(context *fiber.Ctx) error {
+ switch IsAuthenticated(context) {
+ case true:
+ return handler(context)
+ default:
+ return fiber.ErrUnauthorized
+ }
+ }
+}
+
+func Authenticate(context *fiber.Ctx) error {
+ activeSession, sessionError := session.Store.Get(context)
+ if sessionError != nil {
+ return sessionError
+ }
+
+ activeSession.Set(SESSION_AUTHENTICATED_KEY, true)
+ return activeSession.Save()
+}
+
+func Deauthenticate(context *fiber.Ctx) error {
+ activeSession, sessionError := session.Store.Get(context)
+ if sessionError != nil {
+ return sessionError
+ }
+
+ return activeSession.Destroy()
+}
diff --git a/utils/auth/constants.go b/utils/auth/constants.go
new file mode 100644
index 0000000..99f5a85
--- /dev/null
+++ b/utils/auth/constants.go
@@ -0,0 +1,5 @@
+package auth
+
+const (
+ SESSION_AUTHENTICATED_KEY = "authenticated"
+)
diff --git a/utils/meta/body.go b/utils/meta/body.go
new file mode 100644
index 0000000..ce10bde
--- /dev/null
+++ b/utils/meta/body.go
@@ -0,0 +1,12 @@
+package meta
+
+import "github.com/gofiber/fiber/v2"
+
+func Body[T any](context *fiber.Ctx) (T, error) {
+ var body T
+ if parseError := context.BodyParser(&body); parseError != nil {
+ return body, parseError
+ }
+
+ return body, nil
+}
diff --git a/utils/shortcuts/error.go b/utils/shortcuts/error.go
new file mode 100644
index 0000000..71fa4bd
--- /dev/null
+++ b/utils/shortcuts/error.go
@@ -0,0 +1,65 @@
+package shortcuts
+
+import (
+ "dove/enums"
+ "dove/types"
+
+ "github.com/gofiber/fiber/v2"
+)
+
+var statusMap = map[enums.ErrorKind]int{
+ enums.BadRequest: fiber.StatusBadRequest,
+ enums.Forbidden: fiber.StatusForbidden,
+ enums.Internal: fiber.StatusInternalServerError,
+ enums.NotFound: fiber.StatusNotFound,
+ enums.Unauthorized: fiber.StatusUnauthorized,
+ enums.Unprocessable: fiber.StatusUnprocessableEntity,
+}
+
+func ServiceError(kind enums.ErrorKind, message string) *types.ServiceError {
+ return &types.ServiceError{
+ Kind: kind,
+ Message: message,
+ }
+}
+
+func HandleError(context *fiber.Ctx, serviceError *types.ServiceError) error {
+ statusCode, exists := statusMap[serviceError.Kind]
+ if !exists {
+ statusCode = fiber.StatusInternalServerError
+ }
+
+ return RenderWithStatus(context, "error", fiber.Map{
+ "ErrorMessage": serviceError.Message,
+ }, statusCode)
+}
+
+func BadRequest(context *fiber.Ctx, err error) error {
+ return RenderWithStatus(context, "error", fiber.Map{
+ "ErrorMessage": err.Error(),
+ }, fiber.StatusBadRequest)
+}
+
+func Forbidden(context *fiber.Ctx, err error) error {
+ return RenderWithStatus(context, "error", fiber.Map{
+ "ErrorMessage": err.Error(),
+ }, fiber.StatusForbidden)
+}
+
+func InternalServerError(context *fiber.Ctx, err error) error {
+ return RenderWithStatus(context, "error", fiber.Map{
+ "ErrorMessage": err.Error(),
+ }, fiber.StatusInternalServerError)
+}
+
+func NotFound(context *fiber.Ctx, err error) error {
+ return RenderWithStatus(context, "error", fiber.Map{
+ "ErrorMessage": err.Error(),
+ }, fiber.StatusNotFound)
+}
+
+func Unauthorized(context *fiber.Ctx, err error) error {
+ return RenderWithStatus(context, "error", fiber.Map{
+ "ErrorMessage": err.Error(),
+ }, fiber.StatusUnauthorized)
+}
diff --git a/utils/shortcuts/functions.go b/utils/shortcuts/functions.go
new file mode 100644
index 0000000..df00dcf
--- /dev/null
+++ b/utils/shortcuts/functions.go
@@ -0,0 +1,97 @@
+package shortcuts
+
+import (
+ "maps"
+ "reflect"
+ "strings"
+
+ "dove/messages"
+ "dove/utils/errors"
+
+ "github.com/gofiber/fiber/v2"
+)
+
+func mergeContextValues(context *fiber.Ctx, targetMap fiber.Map) {
+ context.Context().VisitUserValues(func(key []byte, value any) {
+ targetMap[string(key)] = value
+ })
+}
+
+func mergeBindData(targetMap fiber.Map, data any) error {
+ normalizedData, normalizeError := normalizeToMap(data)
+ if normalizeError != nil {
+ return normalizeError
+ }
+
+ maps.Copy(targetMap, normalizedData)
+ return nil
+}
+
+func normalizeToMap(data any) (fiber.Map, error) {
+ switch typedData := data.(type) {
+ case fiber.Map:
+ return typedData, nil
+ case map[string]any:
+ return fiber.Map(typedData), nil
+ default:
+ return convertStructToMap(data)
+ }
+}
+
+func convertStructToMap(data any) (fiber.Map, error) {
+ structValue := reflect.ValueOf(data)
+
+ switch structValue.Kind() {
+ case reflect.Pointer:
+ structValue = structValue.Elem()
+ }
+
+ switch structValue.Kind() {
+ case reflect.Struct:
+ return extractStructFields(structValue), nil
+ default:
+ return nil, errors.Error(messages.ShortcutUnsupportedBindType)
+ }
+}
+
+func extractStructFields(structValue reflect.Value) fiber.Map {
+ structType := structValue.Type()
+ fieldMap := make(fiber.Map, structValue.NumField())
+
+ for fieldIndex := range structType.NumField() {
+ fieldDescriptor := structType.Field(fieldIndex)
+
+ if !fieldDescriptor.IsExported() {
+ continue
+ }
+
+ fieldKey := resolveFieldKey(fieldDescriptor)
+ fieldMap[fieldKey] = structValue.Field(fieldIndex).Interface()
+ }
+
+ return fieldMap
+}
+
+func resolveFieldKey(fieldDescriptor reflect.StructField) string {
+ jsonTag := fieldDescriptor.Tag.Get("json")
+
+ switch {
+ case jsonTag == "" || jsonTag == "-":
+ return fieldDescriptor.Name
+ default:
+ return extractTagName(jsonTag, fieldDescriptor.Name)
+ }
+}
+
+func extractTagName(jsonTag string, fallbackName string) string {
+ separatorIndex := strings.IndexByte(jsonTag, ',')
+
+ switch {
+ case separatorIndex < 0:
+ return jsonTag
+ case separatorIndex > 0:
+ return jsonTag[:separatorIndex]
+ default:
+ return fallbackName
+ }
+} \ No newline at end of file
diff --git a/utils/shortcuts/redirect.go b/utils/shortcuts/redirect.go
new file mode 100644
index 0000000..627538d
--- /dev/null
+++ b/utils/shortcuts/redirect.go
@@ -0,0 +1,25 @@
+package shortcuts
+
+import (
+ "dove/utils/urls"
+
+ "github.com/gofiber/fiber/v2"
+)
+
+func Redirect(context *fiber.Ctx, routeName string) error {
+ fullPath, exists := urls.GetFullPath(routeName)
+ if !exists {
+ return fiber.ErrNotFound
+ }
+
+ return context.Redirect(fullPath)
+}
+
+func RedirectWithStatus(context *fiber.Ctx, routeName string, statusCode int) error {
+ fullPath, exists := urls.GetFullPath(routeName)
+ if !exists {
+ return fiber.ErrNotFound
+ }
+
+ return context.Redirect(fullPath, statusCode)
+} \ No newline at end of file
diff --git a/utils/shortcuts/render.go b/utils/shortcuts/render.go
new file mode 100644
index 0000000..29c7a8f
--- /dev/null
+++ b/utils/shortcuts/render.go
@@ -0,0 +1,22 @@
+package shortcuts
+
+import "github.com/gofiber/fiber/v2"
+
+func Render(context *fiber.Ctx, templateName string, data any) error {
+ templateData := make(fiber.Map)
+
+ mergeContextValues(context, templateData)
+
+ if data != nil {
+ if mergeError := mergeBindData(templateData, data); mergeError != nil {
+ return mergeError
+ }
+ }
+
+ return context.Render(templateName, templateData)
+}
+
+func RenderWithStatus(context *fiber.Ctx, templateName string, data any, statusCode int) error {
+ context.Status(statusCode)
+ return Render(context, templateName, data)
+}