Compare commits

..

2 commits

Author SHA1 Message Date
6233298feb id form button, redirect 2024-11-02 20:23:48 -05:00
e7cc41672d Merge pull request 'develop' (#5) from goffee/core:develop into develop
Reviewed-on: diana/core#5
2024-11-02 21:10:40 -04:00
20 changed files with 44 additions and 274 deletions

3
.gitignore vendored
View file

@ -1,3 +1,2 @@
tmp tmp
.DS_STore .DS_STore
.idea

View file

@ -1,3 +1,4 @@
# Goffee Core # GoCondor Core
![Build Status](https://github.com/gocondor/core/actions/workflows/build-main.yml/badge.svg) ![Test Status](https://github.com/gocondor/core/actions/workflows/test-main.yml/badge.svg) [![Coverage Status](https://coveralls.io/repos/github/gocondor/core/badge.svg?branch=main)](https://coveralls.io/github/gocondor/core?branch=main&cache=false) [![GoDoc](https://godoc.org/github.com/gocondor/core?status.svg)](https://godoc.org/github.com/gocondor/core) [![Go Report Card](https://goreportcard.com/badge/github.com/gocondor/core)](https://goreportcard.com/report/github.com/gocondor/core)
The core packages of Goffee framework The core packages of GoCondor framework

View file

@ -16,9 +16,6 @@ type Cache struct {
redis *redis.Client redis *redis.Client
} }
// NewCache initializes a new Cache instance with the provided configuration and connects to a Redis database.
// If caching is enabled in the provided configuration but the connection to Redis fails, it causes a panic.
// Returns a pointer to the initialized Cache structure.
func NewCache(cacheConfig CacheConfig) *Cache { func NewCache(cacheConfig CacheConfig) *Cache {
ctx = context.Background() ctx = context.Background()
dbStr := os.Getenv("REDIS_DB") dbStr := os.Getenv("REDIS_DB")
@ -43,7 +40,6 @@ func NewCache(cacheConfig CacheConfig) *Cache {
} }
} }
// Set stores a key-value pair in the Redis cache with no expiration. Returns an error if the operation fails.
func (c *Cache) Set(key string, value string) error { func (c *Cache) Set(key string, value string) error {
err := c.redis.Set(ctx, key, value, 0).Err() err := c.redis.Set(ctx, key, value, 0).Err()
if err != nil { if err != nil {
@ -52,8 +48,6 @@ func (c *Cache) Set(key string, value string) error {
return nil return nil
} }
// SetWithExpiration stores a key-value pair in the cache with a specified expiration duration.
// Returns an error if the operation fails.
func (c *Cache) SetWithExpiration(key string, value string, expiration time.Duration) error { func (c *Cache) SetWithExpiration(key string, value string, expiration time.Duration) error {
err := c.redis.Set(ctx, key, value, expiration).Err() err := c.redis.Set(ctx, key, value, expiration).Err()
if err != nil { if err != nil {
@ -62,7 +56,6 @@ func (c *Cache) SetWithExpiration(key string, value string, expiration time.Dura
return nil return nil
} }
// Get retrieves the value associated with the provided key from the cache. Returns an error if the operation fails.
func (c *Cache) Get(key string) (string, error) { func (c *Cache) Get(key string) (string, error) {
result, err := c.redis.Get(ctx, key).Result() result, err := c.redis.Get(ctx, key).Result()
if err != nil { if err != nil {
@ -71,7 +64,6 @@ func (c *Cache) Get(key string) (string, error) {
return result, nil return result, nil
} }
// Delete removes the specified key from the cache and returns an error if the operation fails.
func (c *Cache) Delete(key string) error { func (c *Cache) Delete(key string) error {
err := c.redis.Del(ctx, key).Err() err := c.redis.Del(ctx, key).Err()
if err != nil { if err != nil {

View file

@ -22,10 +22,6 @@ type GormConfig struct {
EnableGorm bool EnableGorm bool
} }
type QueueConfig struct {
EnableQueue bool
}
type CacheConfig struct { type CacheConfig struct {
EnableCache bool EnableCache bool
} }

View file

@ -19,7 +19,6 @@ import (
"syscall" "syscall"
"git.smarteching.com/goffee/core/logger" "git.smarteching.com/goffee/core/logger"
"github.com/hibiken/asynq"
"gorm.io/gorm" "gorm.io/gorm"
) )
@ -69,9 +68,8 @@ func (c *Context) RequestParamExists(key string) bool {
return c.Request.httpRequest.Form.Has(key) return c.Request.httpRequest.Form.Has(key)
} }
func (c *Context) GetRequesForm(key string) interface{} { func (c *Context) GetRequesForm() interface{} {
c.Request.httpRequest.ParseForm() return c.Request.httpRequest.Form
return c.Request.httpRequest.Form[key]
} }
func (c *Context) GetRequesBodyMap() map[string]interface{} { func (c *Context) GetRequesBodyMap() map[string]interface{} {
@ -113,51 +111,34 @@ func (c *Context) GetCookie() (UserCookie, error) {
return user, nil return user, nil
} }
func (c *Context) GetQueueClient() *asynq.Client { func (c *Context) GetUploadedFile(name string) *UploadedFileInfo {
redisAddr := fmt.Sprintf("%v:%v", os.Getenv("REDIS_HOST"), os.Getenv("REDIS_PORT"))
client := asynq.NewClient(asynq.RedisClientOpt{Addr: redisAddr})
return client
}
func (c *Context) GetUploadedFile(name string) (*UploadedFileInfo, error) {
file, fileHeader, err := c.Request.httpRequest.FormFile(name) file, fileHeader, err := c.Request.httpRequest.FormFile(name)
if err != nil { if err != nil {
return nil, fmt.Errorf("error retrieving file: %v", err) panic(fmt.Sprintf("error with file,[%v]", err.Error()))
} }
defer file.Close() defer file.Close()
// Extract the file extension
ext := strings.TrimPrefix(path.Ext(fileHeader.Filename), ".") ext := strings.TrimPrefix(path.Ext(fileHeader.Filename), ".")
tmpFilePath := filepath.Join(os.TempDir(), fileHeader.Filename) tmpFilePath := filepath.Join(os.TempDir(), fileHeader.Filename)
tmpFile, err := os.Create(tmpFilePath) tmpFile, err := os.Create(tmpFilePath)
if err != nil { if err != nil {
return nil, fmt.Errorf("error creating temporary file: %v", err) panic(fmt.Sprintf("error with file,[%v]", err.Error()))
} }
defer tmpFile.Close()
// Copy the uploaded file content to the temporary file
buff := make([]byte, 100) buff := make([]byte, 100)
for { for {
n, err := file.Read(buff) n, err := file.Read(buff)
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
return nil, fmt.Errorf("error reading uploaded file: %v", err) panic("error with uploaded file")
} }
if n == 0 { if n == 0 {
break break
} }
_, err = tmpFile.Write(buff[:n]) n, _ = tmpFile.Write(buff[:n])
if err != nil {
return nil, fmt.Errorf("error writing to temporary file: %v", err)
}
} }
// Get file info for the temporary file
tmpFileInfo, err := os.Stat(tmpFilePath) tmpFileInfo, err := os.Stat(tmpFilePath)
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting file info: %v", err) panic(fmt.Sprintf("error with file,[%v]", err.Error()))
} }
defer tmpFile.Close()
uploadedFileInfo := &UploadedFileInfo{ uploadedFileInfo := &UploadedFileInfo{
FullPath: tmpFilePath, FullPath: tmpFilePath,
Name: fileHeader.Filename, Name: fileHeader.Filename,
@ -165,7 +146,7 @@ func (c *Context) GetUploadedFile(name string) (*UploadedFileInfo, error) {
Extension: ext, Extension: ext,
Size: int(tmpFileInfo.Size()), Size: int(tmpFileInfo.Size()),
} }
return uploadedFileInfo, nil return uploadedFileInfo
} }
func (c *Context) MoveFile(sourceFilePath string, destFolderPath string) error { func (c *Context) MoveFile(sourceFilePath string, destFolderPath string) error {
@ -194,7 +175,7 @@ func (c *Context) MoveFile(sourceFilePath string, destFolderPath string) error {
for { for {
n, err := srcFile.Read(buff) n, err := srcFile.Read(buff)
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
return fmt.Errorf("error moving file: %v", sourceFilePath) panic(fmt.Sprintf("error moving file %v", sourceFilePath))
} }
if n == 0 { if n == 0 {
break break
@ -238,7 +219,7 @@ func (c *Context) CopyFile(sourceFilePath string, destFolderPath string) error {
for { for {
n, err := srcFile.Read(buff) n, err := srcFile.Read(buff)
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
return fmt.Errorf("error moving file: %v", sourceFilePath) panic(fmt.Sprintf("error moving file %v", sourceFilePath))
} }
if n == 0 { if n == 0 {
break break

View file

@ -21,24 +21,19 @@ import (
"strings" "strings"
) )
// ErrValueTooLong indicates that the cookie value exceeds the allowed length limit.
// ErrInvalidValue signifies that the cookie value is in an invalid format.
var ( var (
ErrValueTooLong = errors.New("cookie value too long") ErrValueTooLong = errors.New("cookie value too long")
ErrInvalidValue = errors.New("invalid cookie value") ErrInvalidValue = errors.New("invalid cookie value")
) )
// UserCookie represents a structure to hold user-specific data stored in a cookie, including Email and Token fields. // Declare the User type.
type UserCookie struct { type UserCookie struct {
Email string Email string
Token string Token string
} }
// secretcookie is a global variable used to store the decoded secret key for encrypting and decrypting cookies.
var secretcookie []byte var secretcookie []byte
// GetCookie retrieves and decrypts the user cookie from the provided HTTP request and returns it as a UserCookie.
// Returns an error if the cookie retrieval, decryption, or decoding process fails.
func GetCookie(r *http.Request) (UserCookie, error) { func GetCookie(r *http.Request) (UserCookie, error) {
var err error var err error
@ -83,7 +78,6 @@ func GetCookie(r *http.Request) (UserCookie, error) {
} }
// SetCookie sets an encrypted cookie with a user's email and token, using gob encoding for data serialization.
func SetCookie(w http.ResponseWriter, email string, token string) error { func SetCookie(w http.ResponseWriter, email string, token string) error {
// Initialize a User struct containing the data that we want to store in the // Initialize a User struct containing the data that we want to store in the
// cookie. // cookie.
@ -144,8 +138,6 @@ func SetCookie(w http.ResponseWriter, email string, token string) error {
return nil return nil
} }
// CookieWrite writes a secure HTTP cookie to the response writer after base64 encoding its value.
// Returns ErrValueTooLong if the cookie string exceeds the 4096-byte size limit.
func CookieWrite(w http.ResponseWriter, cookie http.Cookie) error { func CookieWrite(w http.ResponseWriter, cookie http.Cookie) error {
// Encode the cookie value using base64. // Encode the cookie value using base64.
cookie.Value = base64.URLEncoding.EncodeToString([]byte(cookie.Value)) cookie.Value = base64.URLEncoding.EncodeToString([]byte(cookie.Value))
@ -162,8 +154,6 @@ func CookieWrite(w http.ResponseWriter, cookie http.Cookie) error {
return nil return nil
} }
// CookieRead retrieves a base64-encoded cookie value by name from the HTTP request and decodes it.
// Returns the decoded value as a string or an error if the cookie is not found or the value is invalid.
func CookieRead(r *http.Request, name string) (string, error) { func CookieRead(r *http.Request, name string) (string, error) {
// Read the cookie as normal. // Read the cookie as normal.
cookie, err := r.Cookie(name) cookie, err := r.Cookie(name)
@ -183,9 +173,6 @@ func CookieRead(r *http.Request, name string) (string, error) {
return string(value), nil return string(value), nil
} }
// CookieWriteEncrypted encrypts the cookie's value using AES-GCM and writes it to the HTTP response writer.
// The cookie name is authenticated along with its value.
// It returns an error if encryption fails or the cookie exceeds the maximum allowed length.
func CookieWriteEncrypted(w http.ResponseWriter, cookie http.Cookie, secretKey []byte) error { func CookieWriteEncrypted(w http.ResponseWriter, cookie http.Cookie, secretKey []byte) error {
// Create a new AES cipher block from the secret key. // Create a new AES cipher block from the secret key.
block, err := aes.NewCipher(secretKey) block, err := aes.NewCipher(secretKey)
@ -226,8 +213,6 @@ func CookieWriteEncrypted(w http.ResponseWriter, cookie http.Cookie, secretKey [
return CookieWrite(w, cookie) return CookieWrite(w, cookie)
} }
// CookieReadEncrypted reads an encrypted cookie, decrypts its value using AES-GCM, and validates its name before returning.
// Returns the plaintext cookie value or an error if decryption or validation fails.
func CookieReadEncrypted(r *http.Request, name string, secretKey []byte) (string, error) { func CookieReadEncrypted(r *http.Request, name string, secretKey []byte) (string, error) {
// Read the encrypted value from the cookie as normal. // Read the encrypted value from the cookie as normal.
encryptedValue, err := CookieRead(r, name) encryptedValue, err := CookieRead(r, name)

61
core.go
View file

@ -39,17 +39,13 @@ var basePath string
var runMode string var runMode string
var disableEvents bool = false var disableEvents bool = false
// components_resources holds embedded file system data, typically for templates or other embedded resources.
//
//go:embed all:template //go:embed all:template
var components_resources embed.FS var components_resources embed.FS
// configContainer is a configuration structure that holds request-specific configurations for the application.
type configContainer struct { type configContainer struct {
Request RequestConfig Request RequestConfig
} }
// App is a struct representing the main application container and its core components.
type App struct { type App struct {
t int // for tracking hooks t int // for tracking hooks
chain *chain chain *chain
@ -57,10 +53,8 @@ type App struct {
Config *configContainer Config *configContainer
} }
// app is a global instance of the App structure used to manage application configuration and lifecycle.
var app *App var app *App
// New initializes and returns a new instance of the App structure with default configurations and chain.
func New() *App { func New() *App {
app = &App{ app = &App{
chain: &chain{}, chain: &chain{},
@ -72,29 +66,24 @@ func New() *App {
return app return app
} }
// ResolveApp returns the global instance of the App, providing access to its methods and configuration components.
func ResolveApp() *App { func ResolveApp() *App {
return app return app
} }
// SetLogsDriver sets the logging driver to be used by the application.
func (app *App) SetLogsDriver(d logger.LogsDriver) { func (app *App) SetLogsDriver(d logger.LogsDriver) {
logsDriver = &d logsDriver = &d
} }
// Bootstrap initializes the application by setting up the logger, router, and events manager.
func (app *App) Bootstrap() { func (app *App) Bootstrap() {
loggr = logger.NewLogger(*logsDriver) loggr = logger.NewLogger(*logsDriver)
NewRouter() NewRouter()
NewEventsManager() NewEventsManager()
} }
// RegisterTemplates initializes the application template system by embedding provided templates for rendering views.
func (app *App) RegisterTemplates(templates_resources embed.FS) { func (app *App) RegisterTemplates(templates_resources embed.FS) {
NewTemplates(components_resources, templates_resources) NewTemplates(components_resources, templates_resources)
} }
// Run initializes the application's router, configures HTTPS if enabled, and starts the HTTP/HTTPS server.
func (app *App) Run(router *httprouter.Router) { func (app *App) Run(router *httprouter.Router) {
portNumber := os.Getenv("App_HTTP_PORT") portNumber := os.Getenv("App_HTTP_PORT")
if portNumber == "" { if portNumber == "" {
@ -104,7 +93,7 @@ func (app *App) Run(router *httprouter.Router) {
// check if template engine is enable // check if template engine is enable
TemplateEnableStr := os.Getenv("TEMPLATE_ENABLE") TemplateEnableStr := os.Getenv("TEMPLATE_ENABLE")
if "" == TemplateEnableStr { if TemplateEnableStr == "" {
TemplateEnableStr = "false" TemplateEnableStr = "false"
} }
TemplateEnable, _ := strconv.ParseBool(TemplateEnableStr) TemplateEnable, _ := strconv.ParseBool(TemplateEnableStr)
@ -119,7 +108,7 @@ func (app *App) Run(router *httprouter.Router) {
} }
useHttps, _ := strconv.ParseBool(useHttpsStr) useHttps, _ := strconv.ParseBool(useHttpsStr)
if "dev" == runMode { if runMode == "dev" {
fmt.Printf("Welcome to Goffee\n") fmt.Printf("Welcome to Goffee\n")
if useHttps { if useHttps {
fmt.Printf("Listening on https \nWaiting for requests...\n") fmt.Printf("Listening on https \nWaiting for requests...\n")
@ -167,9 +156,6 @@ func (app *App) Run(router *httprouter.Router) {
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", portNumber), router)) log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", portNumber), router))
} }
// RegisterRoutes sets up and registers HTTP routes and their handlers to the provided router, enabling request processing.
// It assigns appropriate methods, controllers, and hooks, while handling core services, errors, and fallback behaviors.
// Returns the configured router instance.
func (app *App) RegisterRoutes(routes []Route, router *httprouter.Router) *httprouter.Router { func (app *App) RegisterRoutes(routes []Route, router *httprouter.Router) *httprouter.Router {
router.PanicHandler = panicHandler router.PanicHandler = panicHandler
router.NotFound = notFoundHandler{} router.NotFound = notFoundHandler{}
@ -208,7 +194,6 @@ func (app *App) RegisterRoutes(routes []Route, router *httprouter.Router) *httpr
return router return router
} }
// makeHTTPRouterHandlerFunc creates an httprouter.Handle that handles HTTP requests with the specified controller and hooks.
func (app *App) makeHTTPRouterHandlerFunc(h Controller, ms []Hook) httprouter.Handle { func (app *App) makeHTTPRouterHandlerFunc(h Controller, ms []Hook) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
@ -275,13 +260,9 @@ func (app *App) makeHTTPRouterHandlerFunc(h Controller, ms []Hook) httprouter.Ha
} }
} }
// notFoundHandler is a type that implements the http.Handler interface to handle HTTP 404 Not Found errors.
type notFoundHandler struct{} type notFoundHandler struct{}
// methodNotAllowed is a type that implements http.Handler to handle HTTP 405 Method Not Allowed errors.
type methodNotAllowed struct{} type methodNotAllowed struct{}
// ServeHTTP handles HTTP requests by responding with a 404 status code and a JSON-formatted "Not Found" message.
func (n notFoundHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (n notFoundHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
res := "{\"message\": \"Not Found\"}" res := "{\"message\": \"Not Found\"}"
@ -291,7 +272,6 @@ func (n notFoundHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(res)) w.Write([]byte(res))
} }
// ServeHTTP handles HTTP requests with method not allowed status and logs the occurrence and stack trace.
func (n methodNotAllowed) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (n methodNotAllowed) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusMethodNotAllowed) w.WriteHeader(http.StatusMethodNotAllowed)
res := "{\"message\": \"Method not allowed\"}" res := "{\"message\": \"Method not allowed\"}"
@ -301,7 +281,6 @@ func (n methodNotAllowed) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(res)) w.Write([]byte(res))
} }
// panicHandler handles panics during HTTP request processing, logs errors, and formats responses based on environment settings.
var panicHandler = func(w http.ResponseWriter, r *http.Request, e interface{}) { var panicHandler = func(w http.ResponseWriter, r *http.Request, e interface{}) {
isDebugModeStr := os.Getenv("APP_DEBUG_MODE") isDebugModeStr := os.Getenv("APP_DEBUG_MODE")
isDebugMode, err := strconv.ParseBool(isDebugModeStr) isDebugMode, err := strconv.ParseBool(isDebugModeStr)
@ -336,12 +315,10 @@ var panicHandler = func(w http.ResponseWriter, r *http.Request, e interface{}) {
w.Write([]byte(res)) w.Write([]byte(res))
} }
// UseHook registers a Hook middleware function by attaching it to the global Hooks instance.
func UseHook(mw Hook) { func UseHook(mw Hook) {
ResolveHooks().Attach(mw) ResolveHooks().Attach(mw)
} }
// Next advances to the next middleware or controller in the chain and invokes it with the given context if available.
func (app *App) Next(c *Context) { func (app *App) Next(c *Context) {
app.t = app.t + 1 app.t = app.t + 1
n := app.chain.getByIndex(app.t) n := app.chain.getByIndex(app.t)
@ -358,17 +335,14 @@ func (app *App) Next(c *Context) {
} }
} }
// chain represents a sequence of nodes, where each node is an interface, used for executing a series of operations.
type chain struct { type chain struct {
nodes []interface{} nodes []interface{}
} }
// reset clears all nodes in the chain, resetting it to an empty state.
func (cn *chain) reset() { func (cn *chain) reset() {
cn.nodes = []interface{}{} cn.nodes = []interface{}{}
} }
// getByIndex retrieves an element from the chain's nodes slice by its index. Returns nil if the index is out of range.
func (c *chain) getByIndex(i int) interface{} { func (c *chain) getByIndex(i int) interface{} {
for k := range c.nodes { for k := range c.nodes {
if k == i { if k == i {
@ -379,7 +353,6 @@ func (c *chain) getByIndex(i int) interface{} {
return nil return nil
} }
// prepareChain appends all hooks from the provided list and the application's Hooks to the chain's nodes.
func (app *App) prepareChain(hs []interface{}) { func (app *App) prepareChain(hs []interface{}) {
mw := app.hooks.GetHooks() mw := app.hooks.GetHooks()
for _, v := range mw { for _, v := range mw {
@ -390,7 +363,6 @@ func (app *App) prepareChain(hs []interface{}) {
} }
} }
// execute executes the first node in the chain, invoking it as either a Hook or Controller if applicable.
func (cn *chain) execute(ctx *Context) { func (cn *chain) execute(ctx *Context) {
i := cn.getByIndex(0) i := cn.getByIndex(0)
if i != nil { if i != nil {
@ -406,7 +378,6 @@ func (cn *chain) execute(ctx *Context) {
} }
} }
// combHandlers combines a controller and a slice of hooks into a single slice of interfaces in reversed order.
func (app *App) combHandlers(h Controller, mw []Hook) []interface{} { func (app *App) combHandlers(h Controller, mw []Hook) []interface{} {
var rev []interface{} var rev []interface{}
for _, k := range mw { for _, k := range mw {
@ -416,8 +387,6 @@ func (app *App) combHandlers(h Controller, mw []Hook) []interface{} {
return rev return rev
} }
// getGormFunc returns a function that provides a *gorm.DB instance, ensuring gorm is enabled in the configuration.
// If gorm is not enabled, it panics with an appropriate message.
func getGormFunc() func() *gorm.DB { func getGormFunc() func() *gorm.DB {
f := func() *gorm.DB { f := func() *gorm.DB {
if !gormC.EnableGorm { if !gormC.EnableGorm {
@ -428,9 +397,6 @@ func getGormFunc() func() *gorm.DB {
return f return f
} }
// NewGorm initializes and returns a new gorm.DB instance based on the selected database driver specified in environment variables.
// Supported drivers include MySQL, PostgreSQL, and SQLite.
// Panics if the database driver is not specified or if a connection error occurs.
func NewGorm() *gorm.DB { func NewGorm() *gorm.DB {
var err error var err error
switch os.Getenv("DB_DRIVER") { switch os.Getenv("DB_DRIVER") {
@ -455,7 +421,6 @@ func NewGorm() *gorm.DB {
return db return db
} }
// ResolveGorm initializes and returns a singleton instance of *gorm.DB, creating it if it doesn't already exist.
func ResolveGorm() *gorm.DB { func ResolveGorm() *gorm.DB {
if db != nil { if db != nil {
return db return db
@ -464,8 +429,6 @@ func ResolveGorm() *gorm.DB {
return db return db
} }
// resolveCache returns a function that provides a *Cache instance when invoked.
// Panics if caching is not enabled in the configuration.
func resolveCache() func() *Cache { func resolveCache() func() *Cache {
f := func() *Cache { f := func() *Cache {
if !cacheC.EnableCache { if !cacheC.EnableCache {
@ -476,8 +439,6 @@ func resolveCache() func() *Cache {
return f return f
} }
// postgresConnect establishes a connection to a PostgreSQL database using environment variables for configuration.
// It returns a pointer to a gorm.DB instance or an error if the connection fails.
func postgresConnect() (*gorm.DB, error) { func postgresConnect() (*gorm.DB, error) {
dsn := fmt.Sprintf("host=%v user=%v password=%v dbname=%v port=%v sslmode=%v TimeZone=%v", dsn := fmt.Sprintf("host=%v user=%v password=%v dbname=%v port=%v sslmode=%v TimeZone=%v",
os.Getenv("POSTGRES_HOST"), os.Getenv("POSTGRES_HOST"),
@ -491,8 +452,6 @@ func postgresConnect() (*gorm.DB, error) {
return gorm.Open(postgres.Open(dsn), &gorm.Config{}) return gorm.Open(postgres.Open(dsn), &gorm.Config{})
} }
// mysqlConnect establishes a connection to a MySQL database using credentials and configurations from environment variables.
// Returns a *gorm.DB instance on success or an error if the connection fails.
func mysqlConnect() (*gorm.DB, error) { func mysqlConnect() (*gorm.DB, error) {
dsn := fmt.Sprintf("%v:%v@tcp(%v:%v)/%v?charset=%v&parseTime=True&loc=Local", dsn := fmt.Sprintf("%v:%v@tcp(%v:%v)/%v?charset=%v&parseTime=True&loc=Local",
os.Getenv("MYSQL_USERNAME"), os.Getenv("MYSQL_USERNAME"),
@ -512,9 +471,6 @@ func mysqlConnect() (*gorm.DB, error) {
}), &gorm.Config{}) }), &gorm.Config{})
} }
// getJWT returns a function that initializes and provides a *JWT instance configured with environment variables.
// The JWT instance is created using a secret signing key and a lifespan derived from the environment.
// Panics if the signing key is not set or if the lifespan cannot be parsed.
func getJWT() func() *JWT { func getJWT() func() *JWT {
f := func() *JWT { f := func() *JWT {
secret := os.Getenv("JWT_SECRET") secret := os.Getenv("JWT_SECRET")
@ -538,7 +494,6 @@ func getJWT() func() *JWT {
return f return f
} }
// getValidator returns a closure that instantiates and provides a new instance of Validator.
func getValidator() func() *Validator { func getValidator() func() *Validator {
f := func() *Validator { f := func() *Validator {
return &Validator{} return &Validator{}
@ -546,7 +501,6 @@ func getValidator() func() *Validator {
return f return f
} }
// resloveHashing returns a function that initializes and provides a pointer to a new instance of the Hashing struct.
func resloveHashing() func() *Hashing { func resloveHashing() func() *Hashing {
f := func() *Hashing { f := func() *Hashing {
return &Hashing{} return &Hashing{}
@ -554,7 +508,6 @@ func resloveHashing() func() *Hashing {
return f return f
} }
// resolveMailer initializes and returns a function that provides a singleton Mailer instance based on the configured driver.
func resolveMailer() func() *Mailer { func resolveMailer() func() *Mailer {
f := func() *Mailer { f := func() *Mailer {
if mailer != nil { if mailer != nil {
@ -583,7 +536,6 @@ func resolveMailer() func() *Mailer {
return f return f
} }
// resolveEventsManager creates and returns a function that resolves the singleton instance of EventsManager.
func resolveEventsManager() func() *EventsManager { func resolveEventsManager() func() *EventsManager {
f := func() *EventsManager { f := func() *EventsManager {
return ResolveEventsManager() return ResolveEventsManager()
@ -591,7 +543,6 @@ func resolveEventsManager() func() *EventsManager {
return f return f
} }
// resolveLogger returns a function that provides access to the initialized logger instance when invoked.
func resolveLogger() func() *logger.Logger { func resolveLogger() func() *logger.Logger {
f := func() *logger.Logger { f := func() *logger.Logger {
return loggr return loggr
@ -599,7 +550,6 @@ func resolveLogger() func() *logger.Logger {
return f return f
} }
// MakeDirs creates the specified directories under the base path with the provided permissions (0766).
func (app *App) MakeDirs(dirs ...string) { func (app *App) MakeDirs(dirs ...string) {
o := syscall.Umask(0) o := syscall.Umask(0)
defer syscall.Umask(o) defer syscall.Umask(o)
@ -608,37 +558,30 @@ func (app *App) MakeDirs(dirs ...string) {
} }
} }
// SetRequestConfig sets the configuration for the request in the application instance.
func (app *App) SetRequestConfig(r RequestConfig) { func (app *App) SetRequestConfig(r RequestConfig) {
requestC = r requestC = r
} }
// SetGormConfig sets the GormConfig for the application, overriding the current configuration with the provided value.
func (app *App) SetGormConfig(g GormConfig) { func (app *App) SetGormConfig(g GormConfig) {
gormC = g gormC = g
} }
// SetCacheConfig sets the cache configuration for the application using the provided CacheConfig parameter.
func (app *App) SetCacheConfig(c CacheConfig) { func (app *App) SetCacheConfig(c CacheConfig) {
cacheC = c cacheC = c
} }
// SetBasePath sets the base path for the application, which is used as a prefix for file and directory operations.
func (app *App) SetBasePath(path string) { func (app *App) SetBasePath(path string) {
basePath = path basePath = path
} }
// SetRunMode sets the application run mode to the specified value.
func (app *App) SetRunMode(runmode string) { func (app *App) SetRunMode(runmode string) {
runMode = runmode runMode = runmode
} }
// DisableEvents sets the global variable `disableEvents` to true, disabling the handling or triggering of events.
func DisableEvents() { func DisableEvents() {
disableEvents = true disableEvents = true
} }
// EnableEvents sets the global variable `disableEvents` to false, effectively enabling event handling.
func EnableEvents() { func EnableEvents() {
disableEvents = false disableEvents = false
} }

10
go.mod
View file

@ -10,11 +10,11 @@ require (
git.smarteching.com/zeni/go-chart/v2 v2.1.4 git.smarteching.com/zeni/go-chart/v2 v2.1.4
github.com/brianvoe/gofakeit/v6 v6.21.0 github.com/brianvoe/gofakeit/v6 v6.21.0
github.com/golang-jwt/jwt/v5 v5.0.0 github.com/golang-jwt/jwt/v5 v5.0.0
github.com/google/uuid v1.6.0 github.com/google/uuid v1.3.0
github.com/harranali/mailing v1.2.0 github.com/harranali/mailing v1.2.0
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
github.com/julienschmidt/httprouter v1.3.0 github.com/julienschmidt/httprouter v1.3.0
github.com/redis/go-redis/v9 v9.7.0 github.com/redis/go-redis/v9 v9.0.5
golang.org/x/crypto v0.11.0 golang.org/x/crypto v0.11.0
gorm.io/driver/mysql v1.5.1 gorm.io/driver/mysql v1.5.1
gorm.io/driver/postgres v1.5.2 gorm.io/driver/postgres v1.5.2
@ -28,7 +28,6 @@ require (
github.com/go-chi/chi/v5 v5.0.8 // indirect github.com/go-chi/chi/v5 v5.0.8 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/hibiken/asynq v0.25.1 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.3.1 // indirect github.com/jackc/pgx/v5 v5.3.1 // indirect
@ -38,16 +37,11 @@ require (
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/sendgrid/rest v2.6.9+incompatible // indirect github.com/sendgrid/rest v2.6.9+incompatible // indirect
github.com/sendgrid/sendgrid-go v3.12.0+incompatible // indirect github.com/sendgrid/sendgrid-go v3.12.0+incompatible // indirect
github.com/spf13/cast v1.7.0 // indirect
golang.org/x/image v0.21.0 // indirect golang.org/x/image v0.21.0 // indirect
golang.org/x/net v0.10.0 // indirect golang.org/x/net v0.10.0 // indirect
golang.org/x/sys v0.27.0 // indirect
golang.org/x/text v0.19.0 // indirect golang.org/x/text v0.19.0 // indirect
golang.org/x/time v0.8.0 // indirect
google.golang.org/protobuf v1.35.2 // indirect
) )
require ( require (

16
go.sum
View file

@ -40,12 +40,8 @@ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGw
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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/harranali/mailing v1.2.0 h1:ihIyJwB8hyRVcdk+v465wk1PHMrSrgJqo/kMd+gZClY= github.com/harranali/mailing v1.2.0 h1:ihIyJwB8hyRVcdk+v465wk1PHMrSrgJqo/kMd+gZClY=
github.com/harranali/mailing v1.2.0/go.mod h1:4a5N3yG98pZKluMpmcYlTtll7bisvOfGQEMIng3VQk4= github.com/harranali/mailing v1.2.0/go.mod h1:4a5N3yG98pZKluMpmcYlTtll7bisvOfGQEMIng3VQk4=
github.com/hibiken/asynq v0.25.1 h1:phj028N0nm15n8O2ims+IvJ2gz4k2auvermngh9JhTw=
github.com/hibiken/asynq v0.25.1/go.mod h1:pazWNOLBu0FEynQRBvHA26qdIKRSmfdIfUm4HdsLmXg=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
@ -83,17 +79,11 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/redis/go-redis/v9 v9.0.5 h1:CuQcn5HIEeK7BgElubPP8CGtE0KakrnbBSTLjathl5o= github.com/redis/go-redis/v9 v9.0.5 h1:CuQcn5HIEeK7BgElubPP8CGtE0KakrnbBSTLjathl5o=
github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk= github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0= github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0=
github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE= github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE=
github.com/sendgrid/sendgrid-go v3.12.0+incompatible h1:/N2vx18Fg1KmQOh6zESc5FJB8pYwt5QFBDflYPh1KVg= github.com/sendgrid/sendgrid-go v3.12.0+incompatible h1:/N2vx18Fg1KmQOh6zESc5FJB8pYwt5QFBDflYPh1KVg=
github.com/sendgrid/sendgrid-go v3.12.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8= github.com/sendgrid/sendgrid-go v3.12.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM= github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
@ -109,17 +99,11 @@ golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View file

@ -1,52 +0,0 @@
// Copyright (c) 2025 Zeni Kim <zenik@smarteching.com>
// Use of this source code is governed by MIT-style
// license that can be found in the LICENSE file.
package core
import (
"context"
"fmt"
"log"
"os"
"github.com/hibiken/asynq"
)
type Asynqtask func(context.Context, *asynq.Task) error
type Queuemux struct {
themux *asynq.ServeMux
}
func (q *Queuemux) QueueInit() {
q.themux = asynq.NewServeMux()
}
func (q *Queuemux) AddWork(pattern string, work Asynqtask) {
q.themux.HandleFunc(pattern, work)
}
// RunQueueserver starts the queue server with predefined configurations and handles tasks using the assigned ServeMux.
// Configures the queue server with concurrency limits and priority-based queue management.
// Logs and terminates the program if the server fails to run.
func (q *Queuemux) RunQueueserver() {
redisAddr := fmt.Sprintf("%v:%v", os.Getenv("REDIS_HOST"), os.Getenv("REDIS_PORT"))
srv := asynq.NewServer(
asynq.RedisClientOpt{Addr: redisAddr},
asynq.Config{Concurrency: 10,
// Optionally specify multiple queues with different priority.
Queues: map[string]int{
"critical": 6,
"default": 3,
"low": 1,
},
},
)
if err := srv.Run(q.themux); err != nil {
log.Fatal(err)
}
}

View file

@ -11,7 +11,6 @@ import (
"net/http" "net/http"
) )
// Response represents an HTTP response to be sent to the client, including headers, body, status code, and metadata.
type Response struct { type Response struct {
headers []header headers []header
body []byte body []byte
@ -23,34 +22,12 @@ type Response struct {
HttpResponseWriter http.ResponseWriter HttpResponseWriter http.ResponseWriter
} }
// header represents a single HTTP header with a key-value pair.
type header struct { type header struct {
key string key string
val string val string
} }
// writes the contents of a buffer to the HTTP response with specified file name and type if not terminated. // TODO add doc
func (rs *Response) BufferFile(name string, filetype string, b bytes.Buffer) *Response {
if rs.isTerminated == false {
rs.HttpResponseWriter.Header().Add("Content-Type", filetype)
rs.HttpResponseWriter.Header().Add("Content-Disposition", "attachment; filename="+name)
b.WriteTo(rs.HttpResponseWriter)
}
return rs
}
// writes the contents of a buffer to the HTTP response with specified file name and type if not terminated.
func (rs *Response) BufferInline(name string, filetype string, b bytes.Buffer) *Response {
if rs.isTerminated == false {
rs.HttpResponseWriter.Header().Add("Content-Type", filetype)
b.WriteTo(rs.HttpResponseWriter)
}
return rs
}
// sets the response's content type to HTML and assigns the provided body as the response body if not terminated.
func (rs *Response) Any(body any) *Response { func (rs *Response) Any(body any) *Response {
if rs.isTerminated == false { if rs.isTerminated == false {
rs.contentType = CONTENT_TYPE_HTML rs.contentType = CONTENT_TYPE_HTML
@ -60,7 +37,7 @@ func (rs *Response) Any(body any) *Response {
return rs return rs
} }
// sets the response's content type to JSON and assigns the provided string as the body if the response is not terminated. // TODO add doc
func (rs *Response) Json(body string) *Response { func (rs *Response) Json(body string) *Response {
if rs.isTerminated == false { if rs.isTerminated == false {
rs.contentType = CONTENT_TYPE_JSON rs.contentType = CONTENT_TYPE_JSON
@ -69,7 +46,7 @@ func (rs *Response) Json(body string) *Response {
return rs return rs
} }
// sets the response's content type to plain text and assigns the provided string as the body if not terminated. // TODO add doc
func (rs *Response) Text(body string) *Response { func (rs *Response) Text(body string) *Response {
if rs.isTerminated == false { if rs.isTerminated == false {
rs.contentType = CONTENT_TYPE_TEXT rs.contentType = CONTENT_TYPE_TEXT
@ -78,7 +55,7 @@ func (rs *Response) Text(body string) *Response {
return rs return rs
} }
// sets the response's content type to HTML and assigns the provided string as the body if the response is not terminated. // TODO add doc
func (rs *Response) HTML(body string) *Response { func (rs *Response) HTML(body string) *Response {
if rs.isTerminated == false { if rs.isTerminated == false {
rs.contentType = CONTENT_TYPE_HTML rs.contentType = CONTENT_TYPE_HTML
@ -87,7 +64,7 @@ func (rs *Response) HTML(body string) *Response {
return rs return rs
} }
// renders the specified template with the provided data and writes the result to the HTTP response if not terminated. // TODO add doc
func (rs *Response) Template(name string, data interface{}) *Response { func (rs *Response) Template(name string, data interface{}) *Response {
var buffer bytes.Buffer var buffer bytes.Buffer
if rs.isTerminated == false { if rs.isTerminated == false {
@ -101,7 +78,7 @@ func (rs *Response) Template(name string, data interface{}) *Response {
return rs return rs
} }
// sets the response status code if the response is not yet terminated. Returns the updated Response object. // TODO add doc
func (rs *Response) SetStatusCode(code int) *Response { func (rs *Response) SetStatusCode(code int) *Response {
if rs.isTerminated == false { if rs.isTerminated == false {
rs.statusCode = code rs.statusCode = code
@ -110,7 +87,7 @@ func (rs *Response) SetStatusCode(code int) *Response {
return rs return rs
} }
// sets a custom content type for the response if it is not terminated. Returns the updated Response object. // TODO add doc
func (rs *Response) SetContentType(c string) *Response { func (rs *Response) SetContentType(c string) *Response {
if rs.isTerminated == false { if rs.isTerminated == false {
rs.overrideContentType = c rs.overrideContentType = c
@ -119,7 +96,7 @@ func (rs *Response) SetContentType(c string) *Response {
return rs return rs
} }
// adds or updates a header to the response if it is not terminated. Returns the updated Response object. // TODO add doc
func (rs *Response) SetHeader(key string, val string) *Response { func (rs *Response) SetHeader(key string, val string) *Response {
if rs.isTerminated == false { if rs.isTerminated == false {
h := header{ h := header{
@ -131,12 +108,10 @@ func (rs *Response) SetHeader(key string, val string) *Response {
return rs return rs
} }
// terminates the response processing, preventing any further modifications or actions.
func (rs *Response) ForceSendResponse() { func (rs *Response) ForceSendResponse() {
rs.isTerminated = true rs.isTerminated = true
} }
// updates the redirect URL for the response and returns the modified Response. Validates the URL before setting it.
func (rs *Response) Redirect(url string) *Response { func (rs *Response) Redirect(url string) *Response {
validator := resolveValidator() validator := resolveValidator()
v := validator.Validate(map[string]interface{}{ v := validator.Validate(map[string]interface{}{
@ -156,7 +131,6 @@ func (rs *Response) Redirect(url string) *Response {
return rs return rs
} }
// converts various primitive data types to their string representation or triggers a panic for unsupported types.
func (rs *Response) castBasicVarsToString(data interface{}) string { func (rs *Response) castBasicVarsToString(data interface{}) string {
switch dataType := data.(type) { switch dataType := data.(type) {
case string: case string:
@ -214,7 +188,6 @@ func (rs *Response) castBasicVarsToString(data interface{}) string {
} }
} }
// reinitializes the Response object to its default state, clearing its body, headers, and resetting other properties.
func (rs *Response) reset() { func (rs *Response) reset() {
rs.body = nil rs.body = nil
rs.statusCode = http.StatusOK rs.statusCode = http.StatusOK

View file

@ -5,7 +5,6 @@
package core package core
// Route defines an HTTP route with a method, path, controller function, and optional hooks for customization.
type Route struct { type Route struct {
Method string Method string
Path string Path string
@ -13,15 +12,12 @@ type Route struct {
Hooks []Hook Hooks []Hook
} }
// Router represents a structure for storing and managing routes in the application.
type Router struct { type Router struct {
Routes []Route Routes []Route
} }
// router is a global instance of the Router struct used to manage and resolve application routes.
var router *Router var router *Router
// NewRouter initializes and returns a new Router instance with an empty slice of routes.
func NewRouter() *Router { func NewRouter() *Router {
router = &Router{ router = &Router{
[]Route{}, []Route{},
@ -29,12 +25,10 @@ func NewRouter() *Router {
return router return router
} }
// ResolveRouter returns the singleton instance of the Router. It initializes and manages HTTP routes configuration.
func ResolveRouter() *Router { func ResolveRouter() *Router {
return router return router
} }
// Get registers a GET route with the specified path, controller, and optional hooks, returning the updated Router.
func (r *Router) Get(path string, controller Controller, hooks ...Hook) *Router { func (r *Router) Get(path string, controller Controller, hooks ...Hook) *Router {
r.Routes = append(r.Routes, Route{ r.Routes = append(r.Routes, Route{
Method: GET, Method: GET,
@ -45,7 +39,6 @@ func (r *Router) Get(path string, controller Controller, hooks ...Hook) *Router
return r return r
} }
// Post registers a route with the HTTP POST method, associating it with the given path, controller, and optional hooks.
func (r *Router) Post(path string, controller Controller, hooks ...Hook) *Router { func (r *Router) Post(path string, controller Controller, hooks ...Hook) *Router {
r.Routes = append(r.Routes, Route{ r.Routes = append(r.Routes, Route{
Method: POST, Method: POST,
@ -56,7 +49,6 @@ func (r *Router) Post(path string, controller Controller, hooks ...Hook) *Router
return r return r
} }
// Delete registers a DELETE HTTP method route with the specified path, controller, and optional hooks.
func (r *Router) Delete(path string, controller Controller, hooks ...Hook) *Router { func (r *Router) Delete(path string, controller Controller, hooks ...Hook) *Router {
r.Routes = append(r.Routes, Route{ r.Routes = append(r.Routes, Route{
Method: DELETE, Method: DELETE,
@ -67,7 +59,6 @@ func (r *Router) Delete(path string, controller Controller, hooks ...Hook) *Rout
return r return r
} }
// Patch registers a new route with the HTTP PATCH method, a specified path, a controller, and optional hooks.
func (r *Router) Patch(path string, controller Controller, hooks ...Hook) *Router { func (r *Router) Patch(path string, controller Controller, hooks ...Hook) *Router {
r.Routes = append(r.Routes, Route{ r.Routes = append(r.Routes, Route{
Method: PATCH, Method: PATCH,
@ -78,7 +69,6 @@ func (r *Router) Patch(path string, controller Controller, hooks ...Hook) *Route
return r return r
} }
// Put registers a new route with the HTTP PUT method, associating it to the given path, controller, and optional hooks.
func (r *Router) Put(path string, controller Controller, hooks ...Hook) *Router { func (r *Router) Put(path string, controller Controller, hooks ...Hook) *Router {
r.Routes = append(r.Routes, Route{ r.Routes = append(r.Routes, Route{
Method: PUT, Method: PUT,
@ -89,7 +79,6 @@ func (r *Router) Put(path string, controller Controller, hooks ...Hook) *Router
return r return r
} }
// Options registers a new route with the OPTIONS HTTP method, a specified path, controller, and optional hooks.
func (r *Router) Options(path string, controller Controller, hooks ...Hook) *Router { func (r *Router) Options(path string, controller Controller, hooks ...Hook) *Router {
r.Routes = append(r.Routes, Route{ r.Routes = append(r.Routes, Route{
Method: OPTIONS, Method: OPTIONS,
@ -100,7 +89,6 @@ func (r *Router) Options(path string, controller Controller, hooks ...Hook) *Rou
return r return r
} }
// Head registers a route with the HTTP HEAD method, associates it with a path, controller, and optional hooks.
func (r *Router) Head(path string, controller Controller, hooks ...Hook) *Router { func (r *Router) Head(path string, controller Controller, hooks ...Hook) *Router {
r.Routes = append(r.Routes, Route{ r.Routes = append(r.Routes, Route{
Method: HEAD, Method: HEAD,
@ -111,7 +99,6 @@ func (r *Router) Head(path string, controller Controller, hooks ...Hook) *Router
return r return r
} }
// GetRoutes returns a slice of Route objects currently registered within the Router.
func (r *Router) GetRoutes() []Route { func (r *Router) GetRoutes() []Route {
return r.Routes return r.Routes
} }

View file

@ -1,7 +1,6 @@
package components package components
type ContentDropdown struct { type ContentDropdown struct {
ID string
Label string Label string
TypeClass string // type primary, secondary, success, danger, warning, info, light, dark, link, outline-primary TypeClass string // type primary, secondary, success, danger, warning, info, light, dark, link, outline-primary
IsDisabled bool IsDisabled bool

View file

@ -10,12 +10,11 @@ type ContentTable struct {
type ContentTableTH struct { type ContentTableTH struct {
ID string ID string
ValueType string // -> default string, href, badge, list, checkbox ValueType string // -> default string, href, badge
Value string Value string
} }
type ContentTableTD struct { type ContentTableTD struct {
ID string ID string
Value interface{} // string or component struct according ValueType Value interface{} // string or component struct according ValueType
ValueClass string
} }

View file

@ -7,18 +7,12 @@
</thead> </thead>
<tbody> <tbody>
{{- range .AllTD}}<tr scope="row"> {{- range .AllTD}}<tr scope="row">
{{range $index, $item := .}}<td {{ if $item.ID }}id="{{$item.ID}}"{{end}}{{ if $item.ValueClass }} class="{{$item.ValueClass}}"{{end}}> {{range $index, $item := .}}<td {{ if $item.ID }}id="{{$item.ID}}"{{end}}>
{{ with $x := index $.AllTH $index }} {{ with $x := index $.AllTH $index }}
{{ if eq $x.ValueType "href"}} {{ if eq $x.ValueType "href"}}
{{template "content_href" $item.Value}} {{template "content_href" $item.Value}}
{{ else if eq $x.ValueType "badge"}} {{ else if eq $x.ValueType "badge"}}
{{template "content_badge" $item.Value}} {{template "content_badge" $item.Value}}
{{ else if eq $x.ValueType "list"}}
{{template "content_list" $item.Value}}
{{ else if eq $x.ValueType "checkbox"}}
{{template "form_checkbox" $item.Value}}
{{ else if eq $x.ValueType "image"}}
<img src="{{ $item.Value }}">
{{ else }} {{ else }}
{{ $item.Value }} {{ $item.Value }}
{{end}} {{end}}

View file

@ -2,7 +2,6 @@ package components
type FormButton struct { type FormButton struct {
ID string ID string
Value string
Text string Text string
Icon string Icon string
IsSubmit bool IsSubmit bool

View file

@ -1,5 +1,5 @@
{{define "form_button"}} {{define "form_button"}}
<button {{if .ID }}id="{{.ID}}" name="{{.ID}}"{{end}} {{if .Value }}value="{{.Value}}" name="{{.ID}}"{{end}} class="btn btn-{{.TypeClass}}" {{if eq .IsSubmit true}}type="submit"{{else}}type="button"{{end}} {{if .IsDisabled}}disabled{{end}}> <button class="btn btn-{{.TypeClass}}" {{ if .ID }}id="{{.ID}}"{{end}} {{if eq .IsSubmit true}}type="submit"{{else}}type="button"{{end}} {{if .IsDisabled}}disabled{{end}}>
{{.Text}} {{.Text}}
</button> </button>
<!-- tailwind heroicons --> <!-- tailwind heroicons -->

View file

@ -1,6 +1,6 @@
{{define "form_checkbox"}} {{define "form_checkbox"}}
<div class="input-container"> <div class="input-container">
{{ if .Label }}<label class="form-label">{{.Label}}</label>{{end}} <label class="form-label">{{.Label}}</label>
{{range $options := .AllCheckbox}} {{range $options := .AllCheckbox}}
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" name="{{$options.Name}}" id="{{$options.ID}}" value="{{$options.Value}}"{{if eq $options.IsChecked true}} checked{{end}}> <input class="form-check-input" type="checkbox" name="{{$options.Name}}" id="{{$options.ID}}" value="{{$options.Value}}"{{if eq $options.IsChecked true}} checked{{end}}>

View file

@ -1,14 +1,13 @@
package components package components
type FormInput struct { type FormInput struct {
ID string ID string
Label string Label string
Type string Type string
Placeholder string Placeholder string
Value string Value string
Hint string Hint string
Error string Error string
IsDisabled bool IsDisabled bool
Autocomplete bool IsRequired bool
IsRequired bool
} }

View file

@ -8,9 +8,6 @@
{{if eq .IsRequired true}} {{if eq .IsRequired true}}
required required
{{end}} {{end}}
{{if eq .Autocomplete false}}
autocomplete="off"
{{end}}
{{if ne .Value ""}} {{if ne .Value ""}}
value="{{.Value}}" value="{{.Value}}"
{{end}} {{end}}