From cac2986b59344292c5d0c4118d66c37be7dde423 Mon Sep 17 00:00:00 2001 From: Zeni Kim Date: Sat, 14 Dec 2024 22:38:06 -0500 Subject: [PATCH 01/21] authority system start --- models/permission.go | 16 ++ models/role-permissions.go | 16 ++ models/roles.go | 16 ++ models/user-roles.go | 16 ++ run-auto-migrations.go | 4 + utils/authority.go | 408 +++++++++++++++++++++++++++++++++++++ 6 files changed, 476 insertions(+) create mode 100644 models/permission.go create mode 100644 models/role-permissions.go create mode 100644 models/roles.go create mode 100644 models/user-roles.go create mode 100644 utils/authority.go diff --git a/models/permission.go b/models/permission.go new file mode 100644 index 0000000..933b16d --- /dev/null +++ b/models/permission.go @@ -0,0 +1,16 @@ +// Copyright (c) 2025 Zeni Kim +// Use of this source code is governed by MIT-style +// license that can be found in the LICENSE file. + +package models + +type Permission struct { + BaseModel + Name string + Slug string +} + +// TableName sets the table name +func (Permission) TableName() string { + return "permissions" +} diff --git a/models/role-permissions.go b/models/role-permissions.go new file mode 100644 index 0000000..99cf3fe --- /dev/null +++ b/models/role-permissions.go @@ -0,0 +1,16 @@ +// Copyright (c) 2025 Zeni Kim +// Use of this source code is governed by MIT-style +// license that can be found in the LICENSE file. + +package models + +type RolePermission struct { + BaseModel + RoleID uint // Role id + PermissionID uint // Permission id +} + +// TableName sets the table name +func (RolePermission) TableName() string { + return "role_permissions" +} diff --git a/models/roles.go b/models/roles.go new file mode 100644 index 0000000..decd675 --- /dev/null +++ b/models/roles.go @@ -0,0 +1,16 @@ +// Copyright (c) 2025 Zeni Kim +// Use of this source code is governed by MIT-style +// license that can be found in the LICENSE file. + +package models + +type Role struct { + BaseModel + Name string // The name of the role + Slug string // String based unique identifier of the role, (use hyphen seperated role name '-', instead of space) +} + +// TableName sets the table name +func (Role) TableName() string { + return "roles" +} diff --git a/models/user-roles.go b/models/user-roles.go new file mode 100644 index 0000000..9c69ba7 --- /dev/null +++ b/models/user-roles.go @@ -0,0 +1,16 @@ +// Copyright (c) 2025 Zeni Kim +// Use of this source code is governed by MIT-style +// license that can be found in the LICENSE file. + +package models + +type UserRole struct { + BaseModel + UserID string // The user id + RoleID uint // The role id +} + +// TableName sets the table name +func (UserRole) TableName() string { + return "user_roles" +} diff --git a/run-auto-migrations.go b/run-auto-migrations.go index 8410637..119a44c 100644 --- a/run-auto-migrations.go +++ b/run-auto-migrations.go @@ -18,4 +18,8 @@ func RunAutoMigrations() { // Add auto migrations for your models here... db.AutoMigrate(&models.User{}) + db.AutoMigrate(&models.UserRole{}) + db.AutoMigrate(&models.Role{}) + db.AutoMigrate(&models.RolePermission{}) + db.AutoMigrate(&models.Permission{}) } diff --git a/utils/authority.go b/utils/authority.go new file mode 100644 index 0000000..401f36b --- /dev/null +++ b/utils/authority.go @@ -0,0 +1,408 @@ +// Copyright (c) 2025 Zeni Kim +// Use of this source code is governed by MIT-style +// license that can be found in the LICENSE file. + +package utils + +import ( + "errors" + "fmt" + + "git.smarteching.com/goffee/core" + "git.smarteching.com/goffee/cup/models" + "gorm.io/gorm" +) + +type Authority struct{} + +var auth *Authority + +var ( + ErrPermissionInUse = errors.New("cannot delete assigned permission") + ErrPermissionNotFound = errors.New("permission not found") + ErrRoleInUse = errors.New("cannot delete assigned role") + ErrRoleNotFound = errors.New("role not found") +) + +// Add a new role to the database +func (a *Authority) CreateRole(c *core.Context, r models.Role) error { + roleSlug := r.Slug + var dbRole models.Role + res := c.GetGorm().Where("slug = ?", roleSlug).First(&dbRole) + if res.Error != nil { + if errors.Is(res.Error, gorm.ErrRecordNotFound) { + // create + createRes := c.GetGorm().Create(&r) + if createRes.Error != nil { + return createRes.Error + } + return nil + } + return res.Error + } + + return errors.New(fmt.Sprintf("role '%v' already exists", roleSlug)) +} + +// Add a new permission to the database +func (a *Authority) CreatePermission(c *core.Context, p models.Permission) error { + permSlug := p.Slug + var dbPerm models.Permission + res := c.GetGorm().Where("slug = ?", permSlug).First(&dbPerm) + if res.Error != nil { + if errors.Is(res.Error, gorm.ErrRecordNotFound) { + // create + createRes := c.GetGorm().Create(&p) + if createRes.Error != nil { + return createRes.Error + } + return nil + } + return res.Error + } + + return errors.New(fmt.Sprintf("permission '%v' already exists", permSlug)) +} + +// Assigns a group of permissions to a given role +func (a *Authority) AssignPermissionsToRole(c *core.Context, roleSlug string, permSlugs []string) error { + var role models.Role + rRes := c.GetGorm().Where("slug = ?", roleSlug).First(&role) + if rRes.Error != nil { + if errors.Is(rRes.Error, gorm.ErrRecordNotFound) { + return ErrRoleNotFound + } + return rRes.Error + } + var perms []models.Permission + for _, permSlug := range permSlugs { + var perm models.Permission + pRes := c.GetGorm().Where("slug = ?", permSlug).First(&perm) + if pRes.Error != nil { + if errors.Is(pRes.Error, gorm.ErrRecordNotFound) { + return ErrPermissionNotFound + } + return pRes.Error + } + perms = append(perms, perm) + } + tx := c.GetGorm().Begin() + for _, perm := range perms { + var rolePerm models.RolePermission + res := c.GetGorm().Where("role_id = ?", role.ID).Where("permission_id =?", perm.ID).First(&rolePerm) + if res.Error != nil && errors.Is(res.Error, gorm.ErrRecordNotFound) { + cRes := tx.Create(&models.RolePermission{RoleID: role.ID, PermissionID: perm.ID}) + if cRes.Error != nil { + tx.Rollback() + return cRes.Error + } + } + if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) { + tx.Rollback() + return res.Error + } + if rolePerm != (models.RolePermission{}) { + tx.Rollback() + return errors.New(fmt.Sprintf("permission '%v' is aleady assigned to the role '%v'", perm.Name, role.Name)) + } + rolePerm = models.RolePermission{} + } + return tx.Commit().Error +} + +// Assigns a role to a given user +func (a *Authority) AssignRoleToUser(c *core.Context, userID interface{}, roleSlug string) error { + userIDStr := fmt.Sprintf("%v", userID) + var role models.Role + res := c.GetGorm().Where("slug = ?", roleSlug).First(&role) + if res.Error != nil { + if errors.Is(res.Error, gorm.ErrRecordNotFound) { + return ErrRoleNotFound + } + return res.Error + } + var userRole models.UserRole + res = c.GetGorm().Where("user_id = ?", userIDStr).Where("role_id = ?", role.ID).First(&userRole) + if res.Error != nil && errors.Is(res.Error, gorm.ErrRecordNotFound) { + c.GetGorm().Create(&models.UserRole{UserID: userIDStr, RoleID: role.ID}) + return nil + } + if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) { + return res.Error + } + + return errors.New(fmt.Sprintf("this role '%v' is aleady assigned to the user", roleSlug)) +} + +// Checks if a role is assigned to a user +func (a *Authority) CheckUserRole(c *core.Context, userID interface{}, roleSlug string) (bool, error) { + userIDStr := fmt.Sprintf("%v", userID) + // find the role + var role models.Role + res := c.GetGorm().Where("slug = ?", roleSlug).First(&role) + if res.Error != nil { + if errors.Is(res.Error, gorm.ErrRecordNotFound) { + return false, ErrRoleNotFound + } + return false, res.Error + } + + // check if the role is a assigned + var userRole models.UserRole + res = c.GetGorm().Where("user_id = ?", userIDStr).Where("role_id = ?", role.ID).First(&userRole) + if res.Error != nil { + if errors.Is(res.Error, gorm.ErrRecordNotFound) { + return false, nil + } + return false, res.Error + } + + return true, nil +} + +// Checks if a permission is assigned to a user +func (a *Authority) CheckUserPermission(c *core.Context, userID interface{}, permSlug string) (bool, error) { + userIDStr := fmt.Sprintf("%v", userID) + // the user role + var userRoles []models.UserRole + res := c.GetGorm().Where("user_id = ?", userIDStr).Find(&userRoles) + if res.Error != nil { + if errors.Is(res.Error, gorm.ErrRecordNotFound) { + return false, nil + } + return false, res.Error + } + + //prepare an array of role ids + var roleIDs []interface{} + for _, r := range userRoles { + roleIDs = append(roleIDs, r.RoleID) + } + + // find the permission + var perm models.Permission + res = c.GetGorm().Where("slug = ?", permSlug).First(&perm) + if res.Error != nil { + if errors.Is(res.Error, gorm.ErrRecordNotFound) { + return false, ErrPermissionNotFound + } + return false, res.Error + } + + // find the role permission + var rolePermission models.RolePermission + res = c.GetGorm().Where("role_id IN (?)", roleIDs).Where("permission_id = ?", perm.ID).First(&rolePermission) + if res.Error != nil && errors.Is(res.Error, gorm.ErrRecordNotFound) { + return false, nil + } + if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) { + return false, res.Error + } + + return true, nil +} + +// Checks if a permission is assigned to a role +func (a *Authority) CheckRolePermission(c *core.Context, roleSlug string, permSlug string) (bool, error) { + // find the role + var role models.Role + res := c.GetGorm().Where("slug = ?", roleSlug).First(&role) + if res.Error != nil { + if errors.Is(res.Error, gorm.ErrRecordNotFound) { + return false, ErrRoleNotFound + } + return false, res.Error + } + + // find the permission + var perm models.Permission + res = c.GetGorm().Where("slug = ?", permSlug).First(&perm) + if res.Error != nil { + if errors.Is(res.Error, gorm.ErrRecordNotFound) { + return false, ErrPermissionNotFound + } + return false, res.Error + } + + // find the rolePermission + var rolePermission models.RolePermission + res = c.GetGorm().Where("role_id = ?", role.ID).Where("permission_id = ?", perm.ID).First(&rolePermission) + if res.Error != nil { + if errors.Is(res.Error, gorm.ErrRecordNotFound) { + return false, nil + } + return false, res.Error + } + + return true, nil +} + +// Revokes a roles's permission +func (a *Authority) RevokeRolePermission(c *core.Context, roleSlug string, permSlug string) error { + // find the role + var role models.Role + res := c.GetGorm().Where("slug = ?", roleSlug).First(&role) + if res.Error != nil { + if errors.Is(res.Error, gorm.ErrRecordNotFound) { + return ErrRoleNotFound + } + return res.Error + } + + // find the permission + var perm models.Permission + res = c.GetGorm().Where("slug = ?", permSlug).First(&perm) + if res.Error != nil { + if errors.Is(res.Error, gorm.ErrRecordNotFound) { + return ErrPermissionNotFound + } + return res.Error + } + + // revoke the permission + rRes := c.GetGorm().Where("role_id = ?", role.ID).Where("permission_id = ?", perm.ID).Delete(models.RolePermission{}) + if rRes.Error != nil { + return rRes.Error + } + + return nil +} + +// Returns all stored roles +func (a *Authority) GetAllRoles(c *core.Context) ([]models.Role, error) { + var roles []models.Role + res := c.GetGorm().Find(&roles) + if res.Error != nil { + return nil, res.Error + } + + return roles, nil +} + +// Returns all user assigned roles +func (a *Authority) GetUserRoles(c *core.Context, userID interface{}) ([]models.Role, error) { + userIDStr := fmt.Sprintf("%v", userID) + var userRoles []models.UserRole + res := c.GetGorm().Where("user_id = ?", userIDStr).Find(&userRoles) + if res.Error != nil { + return nil, res.Error + } + + var roleIDs []interface{} + for _, r := range userRoles { + roleIDs = append(roleIDs, r.RoleID) + } + + var roles []models.Role + res = c.GetGorm().Where("id IN (?)", roleIDs).Find(&roles) + if res.Error != nil { + return nil, res.Error + } + + return roles, nil +} + +// Returns all role assigned permissions +func (a *Authority) GetRolePermissions(c *core.Context, roleSlug string) ([]models.Permission, error) { + var role models.Role + res := c.GetGorm().Where("slug = ?", roleSlug).Find(&role) + if res.Error != nil { + return nil, res.Error + } + + var rolePerms []models.RolePermission + res = c.GetGorm().Where("role_id = ?", role.ID).Find(&rolePerms) + if res.Error != nil { + return nil, res.Error + } + var permIDs []interface{} + for _, rolePerm := range rolePerms { + permIDs = append(permIDs, rolePerm.PermissionID) + } + + var perms []models.Permission + res = c.GetGorm().Where("id IN (?)", permIDs).Find(&perms) + if res.Error != nil { + return nil, res.Error + } + + return perms, nil +} + +// Returns all stored permissions +func (a *Authority) GetAllPermissions(c *core.Context) ([]models.Permission, error) { + var perms []models.Permission + res := c.GetGorm().Find(&perms) + if res.Error != nil { + return nil, res.Error + } + + return perms, nil +} + +// Deletes a given role even if it's has assigned permissions +func (a *Authority) DeleteRole(c *core.Context, roleSlug string) error { + // find the role + var role models.Role + res := c.GetGorm().Where("slug = ?", roleSlug).First(&role) + if res.Error != nil { + return res.Error + } + + // check if the role is assigned to a user + var ca int64 + res = c.GetGorm().Model(models.UserRole{}).Where("role_id = ?", role.ID).Count(&ca) + if res.Error != nil { + return res.Error + } + + if ca != 0 { + // role is assigned + return ErrRoleInUse + } + tx := c.GetGorm().Begin() + // revoke the assignment of permissions before deleting the role + dRes := tx.Where("role_id = ?", role.ID).Delete(models.RolePermission{}) + if dRes.Error != nil { + tx.Rollback() + return dRes.Error + } + + // delete the role + dRes = c.GetGorm().Where("slug = ?", roleSlug).Delete(models.Role{}) + if dRes.Error != nil { + tx.Rollback() + return dRes.Error + } + + return tx.Commit().Error +} + +// Deletes a given permission +func (a *Authority) DeletePermission(c *core.Context, permSlug string) error { + // find the permission + var perm models.Permission + res := c.GetGorm().Where("slug = ?", permSlug).First(&perm) + if res.Error != nil { + return res.Error + } + + // check if the permission is assigned to a role + var rolePermission models.RolePermission + res = c.GetGorm().Where("permission_id = ?", perm.ID).First(&rolePermission) + if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) { + return res.Error + } + + if res.Error == nil { + return ErrPermissionInUse + } + + // delete the permission + dRes := c.GetGorm().Where("slug = ?", permSlug).Delete(models.Permission{}) + if dRes.Error != nil { + return dRes.Error + } + + return nil +} From 43f3ad986e7492b54b8d87ca38dd9596c2003ab7 Mon Sep 17 00:00:00 2001 From: Zeni Kim Date: Mon, 16 Dec 2024 20:05:37 -0500 Subject: [PATCH 02/21] fix user id, seed data autority --- models/user-roles.go | 4 +-- run-auto-migrations.go | 15 ++++++++++- utils/authority.go | 22 +++++++--------- utils/helpers.go | 60 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 16 deletions(-) diff --git a/models/user-roles.go b/models/user-roles.go index 9c69ba7..1dfcc18 100644 --- a/models/user-roles.go +++ b/models/user-roles.go @@ -6,8 +6,8 @@ package models type UserRole struct { BaseModel - UserID string // The user id - RoleID uint // The role id + UserID uint // The user id + RoleID uint // The role id } // TableName sets the table name diff --git a/run-auto-migrations.go b/run-auto-migrations.go index 119a44c..996ae15 100644 --- a/run-auto-migrations.go +++ b/run-auto-migrations.go @@ -6,8 +6,12 @@ package main import ( + "errors" + "git.smarteching.com/goffee/core" "git.smarteching.com/goffee/cup/models" + "git.smarteching.com/goffee/cup/utils" + "gorm.io/gorm" ) func RunAutoMigrations() { @@ -17,9 +21,18 @@ func RunAutoMigrations() { //############################## // Add auto migrations for your models here... - db.AutoMigrate(&models.User{}) db.AutoMigrate(&models.UserRole{}) db.AutoMigrate(&models.Role{}) db.AutoMigrate(&models.RolePermission{}) db.AutoMigrate(&models.Permission{}) + + // End your auto migrations + + // Create seed data data, DO NOT TOUCH + if err := db.AutoMigrate(&models.User{}); err == nil && db.Migrator().HasTable(&models.User{}) { + if err := db.First(&models.User{}).Error; errors.Is(err, gorm.ErrRecordNotFound) { + utils.CreateSeedData() + } + } + } diff --git a/utils/authority.go b/utils/authority.go index 401f36b..33124e8 100644 --- a/utils/authority.go +++ b/utils/authority.go @@ -111,8 +111,7 @@ func (a *Authority) AssignPermissionsToRole(c *core.Context, roleSlug string, pe } // Assigns a role to a given user -func (a *Authority) AssignRoleToUser(c *core.Context, userID interface{}, roleSlug string) error { - userIDStr := fmt.Sprintf("%v", userID) +func (a *Authority) AssignRoleToUser(c *core.Context, userID uint, roleSlug string) error { var role models.Role res := c.GetGorm().Where("slug = ?", roleSlug).First(&role) if res.Error != nil { @@ -122,9 +121,9 @@ func (a *Authority) AssignRoleToUser(c *core.Context, userID interface{}, roleSl return res.Error } var userRole models.UserRole - res = c.GetGorm().Where("user_id = ?", userIDStr).Where("role_id = ?", role.ID).First(&userRole) + res = c.GetGorm().Where("user_id = ?", userID).Where("role_id = ?", role.ID).First(&userRole) if res.Error != nil && errors.Is(res.Error, gorm.ErrRecordNotFound) { - c.GetGorm().Create(&models.UserRole{UserID: userIDStr, RoleID: role.ID}) + c.GetGorm().Create(&models.UserRole{UserID: userID, RoleID: role.ID}) return nil } if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) { @@ -135,8 +134,7 @@ func (a *Authority) AssignRoleToUser(c *core.Context, userID interface{}, roleSl } // Checks if a role is assigned to a user -func (a *Authority) CheckUserRole(c *core.Context, userID interface{}, roleSlug string) (bool, error) { - userIDStr := fmt.Sprintf("%v", userID) +func (a *Authority) CheckUserRole(c *core.Context, userID uint, roleSlug string) (bool, error) { // find the role var role models.Role res := c.GetGorm().Where("slug = ?", roleSlug).First(&role) @@ -149,7 +147,7 @@ func (a *Authority) CheckUserRole(c *core.Context, userID interface{}, roleSlug // check if the role is a assigned var userRole models.UserRole - res = c.GetGorm().Where("user_id = ?", userIDStr).Where("role_id = ?", role.ID).First(&userRole) + res = c.GetGorm().Where("user_id = ?", userID).Where("role_id = ?", role.ID).First(&userRole) if res.Error != nil { if errors.Is(res.Error, gorm.ErrRecordNotFound) { return false, nil @@ -161,11 +159,10 @@ func (a *Authority) CheckUserRole(c *core.Context, userID interface{}, roleSlug } // Checks if a permission is assigned to a user -func (a *Authority) CheckUserPermission(c *core.Context, userID interface{}, permSlug string) (bool, error) { - userIDStr := fmt.Sprintf("%v", userID) +func (a *Authority) CheckUserPermission(c *core.Context, userID uint, permSlug string) (bool, error) { // the user role var userRoles []models.UserRole - res := c.GetGorm().Where("user_id = ?", userIDStr).Find(&userRoles) + res := c.GetGorm().Where("user_id = ?", userID).Find(&userRoles) if res.Error != nil { if errors.Is(res.Error, gorm.ErrRecordNotFound) { return false, nil @@ -280,10 +277,9 @@ func (a *Authority) GetAllRoles(c *core.Context) ([]models.Role, error) { } // Returns all user assigned roles -func (a *Authority) GetUserRoles(c *core.Context, userID interface{}) ([]models.Role, error) { - userIDStr := fmt.Sprintf("%v", userID) +func (a *Authority) GetUserRoles(c *core.Context, userID uint) ([]models.Role, error) { var userRoles []models.UserRole - res := c.GetGorm().Where("user_id = ?", userIDStr).Find(&userRoles) + res := c.GetGorm().Where("user_id = ?", userID).Find(&userRoles) if res.Error != nil { return nil, res.Error } diff --git a/utils/helpers.go b/utils/helpers.go index 36ac76b..8ca7dd8 100644 --- a/utils/helpers.go +++ b/utils/helpers.go @@ -8,9 +8,69 @@ package utils import ( "crypto/md5" "fmt" + "log" "time" + + "git.smarteching.com/goffee/core" + "git.smarteching.com/goffee/cup/models" ) +func CreateSeedData() { + + db := core.ResolveGorm() + var hashing = new(core.Hashing) + var role models.Role + + // seed user + password := "goffee" + name := "admin" + fullname := "Goffee administrator" + email := "change@me.com" + passwordHashed, _ := hashing.HashPassword(password) + + user := models.User{ + Name: name, + Fullname: fullname, + Email: email, + Password: passwordHashed, + } + result := db.Create(&user) + if result.Error != nil { + log.Fatal("Can't create seed user in database") + } + // seed roles + roles := []models.Role{ + {Name: "Administrator", Slug: "admin"}, + {Name: "Authenticated", Slug: "authenticated"}, + } + + for _, role := range roles { + result = db.Create(&role) + if result.Error != nil { + log.Fatal("Can't create seed role in database") + } + } + + // seed permission + permission := models.Permission{Name: "Users administration", Slug: "admin-users"} + result = db.Create(&permission) + if result.Error != nil { + log.Fatal("Can't create seed permission in database") + } + result = db.Where("slug = ?", "admin").First(&role) + if result.Error != nil { + log.Fatal("Can't find user admin in database") + } + result = db.Create(&models.RolePermission{RoleID: role.ID, PermissionID: permission.ID}) + if result.Error != nil { + log.Fatal("Can't register permission role in database") + } + result = db.Create(&models.UserRole{UserID: user.ID, RoleID: role.ID}) + if result.Error != nil { + log.Fatal("Can't assign role administrator to user in database") + } +} + // generate a hashed string to be used as key for caching auth jwt token func CreateAuthTokenHashedCacheKey(userID uint, userAgent string) string { cacheKey := fmt.Sprintf("userid:_%v_useragent:_%v_jwt_token", userID, userAgent) From d43196318178d1684b5dee618e516c2924ba8ebc Mon Sep 17 00:00:00 2001 From: Zeni Kim Date: Tue, 17 Dec 2024 14:23:07 -0500 Subject: [PATCH 03/21] Update sample user admin to user authority, add revoke role to auth --- controllers/adminusers.go | 128 +++++++++++++++++++++++++- storage/templates/admin_useradd.html | 3 +- storage/templates/admin_useredit.html | 3 +- storage/templates/nopermission.html | 12 +++ utils/authority.go | 32 +++++++ 5 files changed, 175 insertions(+), 3 deletions(-) create mode 100644 storage/templates/nopermission.html diff --git a/controllers/adminusers.go b/controllers/adminusers.go index b051308..9ffb762 100644 --- a/controllers/adminusers.go +++ b/controllers/adminusers.go @@ -19,6 +19,29 @@ import ( func AdminUsersList(c *core.Context) *core.Response { + // initiate authority + auth := new(utils.Authority) + var session = new(utils.SessionUser) + // true if session is active + hassession := session.Init(c) + + if !hassession { + type emptytemplate struct{} + emptyData := emptytemplate{} + return c.Response.Template("nopermission.html", emptyData) + } + + session_uid := session.GetUserID() + // check if user has role admin + is_admin, _ := auth.CheckUserRole(c, session_uid, "admin") + + if !is_admin { + type emptytemplate struct{} + emptyData := emptytemplate{} + return c.Response.Template("nopermission.html", emptyData) + } + + // continue if has session and is admin var users []models.User db := c.GetGorm() db.Find(&users) @@ -46,6 +69,10 @@ func AdminUsersList(c *core.Context) *core.Response { Value: "Email", ValueType: "string", }, + { + Value: "Roles", + ValueType: "string", + }, { Value: "Created", }, @@ -57,13 +84,26 @@ func AdminUsersList(c *core.Context) *core.Response { }, } + var listroles string rows := make([][]components.ContentTableTD, len(users)) for i, u := range users { + + roles, _ := auth.GetUserRoles(c, u.ID) + listroles = "" + + for _, role := range roles { + if listroles != "" { + listroles += ", " + } + listroles += role.Name + } + row := []components.ContentTableTD{ {Value: strconv.Itoa(int(u.ID))}, {Value: u.Name}, {Value: u.Fullname}, {Value: u.Email}, + {Value: listroles}, {Value: utils.FormatUnix(u.Created)}, {Value: utils.FormatUnix(u.Updated)}, {Value: components.ContentHref{ @@ -93,10 +133,44 @@ func AdminUsersList(c *core.Context) *core.Response { func AdminUsersAdd(c *core.Context) *core.Response { + // initiate authority + auth := new(utils.Authority) + var session = new(utils.SessionUser) + // true if session is active + hassession := session.Init(c) + + if !hassession { + type emptytemplate struct{} + emptyData := emptytemplate{} + return c.Response.Template("nopermission.html", emptyData) + } + + session_uid := session.GetUserID() + // check if user has role admin + is_admin, _ := auth.CheckUserRole(c, session_uid, "admin") + + if !is_admin { + type emptytemplate struct{} + emptyData := emptytemplate{} + return c.Response.Template("nopermission.html", emptyData) + } + // check if is submit submit := c.GetRequestParam("submit").(string) - errormessages := make([]string, 0) + var listroles []components.FormCheckboxItem + systemroles, _ := auth.GetAllRoles(c) + + for _, systemrole := range systemroles { + var userrole components.FormCheckboxItem + userrole.Label = systemrole.Name + userrole.Name = "roles" + userrole.Value = systemrole.Slug + if systemrole.Slug == "authenticated" { + userrole.IsChecked = true + } + listroles = append(listroles, userrole) + } name := "" fullname := "" @@ -109,6 +183,7 @@ func AdminUsersAdd(c *core.Context) *core.Response { fullname = c.GetRequestParam("fullname").(string) email = c.GetRequestParam("email").(string) password = c.GetRequestParam("password").(string) + roles := c.GetRequesForm("roles").([]string) // check if email exists var user models.User @@ -161,6 +236,11 @@ func AdminUsersAdd(c *core.Context) *core.Response { errormessages = append(errormessages, res.Error.Error()) } else { + // assign roles + for _, role := range roles { + auth.AssignRoleToUser(c, user.ID, role) + } + // fire user registered event err := c.GetEventsManager().Fire(&core.Event{Name: events.USER_REGISTERED, Payload: map[string]interface{}{ "user": user, @@ -180,6 +260,7 @@ func AdminUsersAdd(c *core.Context) *core.Response { FieldName components.FormInput FieldFullname components.FormInput FieldEmail components.FormInput + FieldRoles components.FormCheckbox FieldPassword components.FormInput ErrorMessages []string SubmitButton components.FormButton @@ -208,6 +289,10 @@ func AdminUsersAdd(c *core.Context) *core.Response { //Autocomplete: true, IsRequired: true, }, + FieldRoles: components.FormCheckbox{ + Label: "Roles", + AllCheckbox: listroles, + }, FieldPassword: components.FormInput{ ID: "password", Label: "Password", @@ -236,6 +321,29 @@ func AdminUsersEdit(c *core.Context) *core.Response { user_id := c.GetPathParam("id") errormessages := make([]string, 0) + // initiate authority + auth := new(utils.Authority) + + var listroles []components.FormCheckboxItem + + systemroles, _ := auth.GetAllRoles(c) + user_id_uint, _ := strconv.ParseUint(user_id.(string), 10, 32) + + userroles, _ := auth.GetUserRoles(c, uint(user_id_uint)) + + for _, systemrole := range systemroles { + var userrole components.FormCheckboxItem + userrole.Label = systemrole.Name + userrole.Name = "roles" + userrole.Value = systemrole.Slug + for _, ur := range userroles { + if ur.Slug == systemrole.Slug { + userrole.IsChecked = true + break + } + } + listroles = append(listroles, userrole) + } var origin_user models.User @@ -259,6 +367,7 @@ func AdminUsersEdit(c *core.Context) *core.Response { fullname = c.GetRequestParam("fullname").(string) email = c.GetRequestParam("email").(string) password = c.GetRequestParam("password").(string) + roles := c.GetRequesForm("roles").([]string) key := c.GetRequestParam("key") // check if email exists @@ -316,6 +425,14 @@ func AdminUsersEdit(c *core.Context) *core.Response { c.GetLogger().Error("Admin user: error updating") errormessages = append(errormessages, fmt.Sprintf("Error updating user %s:", user_id_string)) } else { + + // delete roles + auth.RevokeAllUserRole(c, origin_user.ID) + // assign roles + for _, role := range roles { + auth.AssignRoleToUser(c, origin_user.ID, role) + } + return c.Response.Redirect("/admin/users") } } @@ -325,6 +442,7 @@ func AdminUsersEdit(c *core.Context) *core.Response { FieldName components.FormInput FieldFullname components.FormInput FieldEmail components.FormInput + FieldRoles components.FormCheckbox FieldPassword components.FormInput FieldKey components.FormInput ErrorMessages []string @@ -355,6 +473,10 @@ func AdminUsersEdit(c *core.Context) *core.Response { //Autocomplete: true, IsRequired: true, }, + FieldRoles: components.FormCheckbox{ + Label: "Roles", + AllCheckbox: listroles, + }, FieldPassword: components.FormInput{ ID: "password", Label: "Password", @@ -461,6 +583,9 @@ func AdminUsersDelConfirm(c *core.Context) *core.Response { // check if is the seed user seed := "1" if user_id != seed { + + // initiate authority + auth := new(utils.Authority) // Delete the user // fire user delete event err := c.GetEventsManager().Fire(&core.Event{Name: events.USER_DELETED, Payload: map[string]interface{}{ @@ -469,6 +594,7 @@ func AdminUsersDelConfirm(c *core.Context) *core.Response { if err != nil { c.GetLogger().Error(err.Error()) } + auth.RevokeAllUserRole(c, origin_user.ID) result_db.Unscoped().Delete(&origin_user) } } diff --git a/storage/templates/admin_useradd.html b/storage/templates/admin_useradd.html index ccdb363..bc5d780 100644 --- a/storage/templates/admin_useradd.html +++ b/storage/templates/admin_useradd.html @@ -15,7 +15,8 @@
{{template "form_input" .FieldName}} {{template "form_input" .FieldFullname}} - {{template "form_input" .FieldEmail}} + {{template "form_input" .FieldEmail}} + {{template "form_checkbox" .FieldRoles}} {{template "form_input" .FieldPassword}}
{{template "form_button" .SubmitButton}} diff --git a/storage/templates/admin_useredit.html b/storage/templates/admin_useredit.html index 36ebfdf..3c49e08 100644 --- a/storage/templates/admin_useredit.html +++ b/storage/templates/admin_useredit.html @@ -15,7 +15,8 @@ {{template "form_input" .FieldName}} {{template "form_input" .FieldFullname}} - {{template "form_input" .FieldEmail}} + {{template "form_input" .FieldEmail}} + {{template "form_checkbox" .FieldRoles}} {{template "form_input" .FieldPassword}} {{template "form_input" .FieldKey}} {{template "form_button" .SubmitButton}} diff --git a/storage/templates/nopermission.html b/storage/templates/nopermission.html new file mode 100644 index 0000000..bec8f39 --- /dev/null +++ b/storage/templates/nopermission.html @@ -0,0 +1,12 @@ + + + {{template "page_head" "No permission"}} + +
+
+ You do not have permission to visit this page. +
+
+ {{template "page_footer"}} + + \ No newline at end of file diff --git a/utils/authority.go b/utils/authority.go index 33124e8..823f845 100644 --- a/utils/authority.go +++ b/utils/authority.go @@ -265,6 +265,38 @@ func (a *Authority) RevokeRolePermission(c *core.Context, roleSlug string, permS return nil } +// Revokes a user's role +func (a *Authority) RevokeUserRole(c *core.Context, userID uint, roleSlug string) error { + // find the role + var role models.Role + res := c.GetGorm().Where("slug = ?", roleSlug).First(&role) + if res.Error != nil { + if errors.Is(res.Error, gorm.ErrRecordNotFound) { + return ErrRoleNotFound + } + return res.Error + } + + // revoke the role + rRes := c.GetGorm().Where("user_id = ?", userID).Where("role_id = ?", role.ID).Delete(models.UserRole{}) + if rRes.Error != nil { + return rRes.Error + } + + return nil +} + +// Revokes all user's role +func (a *Authority) RevokeAllUserRole(c *core.Context, userID uint) error { + // revoke the role + rRes := c.GetGorm().Where("user_id = ?", userID).Delete(models.UserRole{}) + if rRes.Error != nil { + return rRes.Error + } + + return nil +} + // Returns all stored roles func (a *Authority) GetAllRoles(c *core.Context) ([]models.Role, error) { var roles []models.Role From d413d7def4dcad8e500e194ab6c787aa04fb5d1e Mon Sep 17 00:00:00 2001 From: Zeni Kim Date: Mon, 23 Dec 2024 23:19:22 -0500 Subject: [PATCH 04/21] add base queue system --- controllers/queuesample.go | 52 ++++++++++++++++++++++++++++++++++++++ go.mod | 6 +++++ go.sum | 12 +++++++++ main.go | 1 + register-queues.go | 30 ++++++++++++++++++++++ routes.go | 1 + workers/workers.go | 39 ++++++++++++++++++++++++++++ 7 files changed, 141 insertions(+) create mode 100644 controllers/queuesample.go create mode 100644 register-queues.go create mode 100644 workers/workers.go diff --git a/controllers/queuesample.go b/controllers/queuesample.go new file mode 100644 index 0000000..d289aee --- /dev/null +++ b/controllers/queuesample.go @@ -0,0 +1,52 @@ +// Copyright (c) 2025 Zeni Kim +// Use of this source code is governed by MIT-style +// license that can be found in the LICENSE file. + +package controllers + +import ( + "encoding/json" + "log" + "time" + + "git.smarteching.com/goffee/core" + "git.smarteching.com/goffee/cup/workers" + "github.com/hibiken/asynq" +) + +// Make samples queues +func Queuesample(c *core.Context) *core.Response { + + // Get client queue asynq + client := c.GetQueueClient() + + // Create a task with typename and payload. + payload, err := json.Marshal(workers.EmailTaskPayload{UserID: 42}) + if err != nil { + log.Fatal(err) + } + + t1 := asynq.NewTask(workers.TypeWelcomeEmail, payload) + + t2 := asynq.NewTask(workers.TypeReminderEmail, payload) + + // Process the task immediately. + info, err := client.Enqueue(t1) + if err != nil { + log.Fatal(err) + } + log.Printf(" [*] Successfully enqueued task: %+v", info) + + // Process 2 task 1 min later. + for i := 1; i < 3; i++ { + info, err = client.Enqueue(t2, asynq.ProcessIn(1*time.Minute)) + if err != nil { + log.Fatal(err) + } + log.Printf(" [*] Successfully enqueued task: %+v", info) + } + + message := "{\"message\": \"Task queued\"}" + return c.Response.Json(message) + +} diff --git a/go.mod b/go.mod index e70b070..2e7078c 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ go 1.23.1 require ( git.smarteching.com/goffee/core v1.8.4 github.com/google/uuid v1.6.0 + github.com/hibiken/asynq v0.25.1 github.com/joho/godotenv v1.5.1 github.com/julienschmidt/httprouter v1.3.0 gorm.io/gorm v1.25.12 @@ -44,14 +45,19 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/redis/go-redis/v9 v9.7.0 // indirect + github.com/robfig/cron/v3 v3.0.1 // indirect github.com/sendgrid/rest v2.6.9+incompatible // indirect github.com/sendgrid/sendgrid-go v3.16.0+incompatible // indirect github.com/sirupsen/logrus v1.9.3 // indirect + github.com/spf13/cast v1.7.0 // indirect golang.org/x/crypto v0.28.0 // indirect golang.org/x/image v0.21.0 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.27.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 gorm.io/driver/mysql v1.5.7 // indirect gorm.io/driver/postgres v1.5.9 // indirect gorm.io/driver/sqlite v1.5.6 // indirect diff --git a/go.sum b/go.sum index be301b7..ca95d88 100644 --- a/go.sum +++ b/go.sum @@ -47,6 +47,8 @@ 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/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/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= @@ -89,6 +91,8 @@ 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/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/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0= github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE= @@ -96,6 +100,8 @@ github.com/sendgrid/sendgrid-go v3.16.0+incompatible h1:i8eE6IMkiCy7vusSdacHHSBU github.com/sendgrid/sendgrid-go v3.16.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -116,11 +122,17 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= 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= +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/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/main.go b/main.go index 4b9c956..8e7c815 100644 --- a/main.go +++ b/main.go @@ -62,6 +62,7 @@ func main() { registerGlobalHooks() registerRoutes() registerEvents() + registerQueues() if config.GetGormConfig().EnableGorm == true { RunAutoMigrations() } diff --git a/register-queues.go b/register-queues.go new file mode 100644 index 0000000..a1cf059 --- /dev/null +++ b/register-queues.go @@ -0,0 +1,30 @@ +// Copyright (c) 2025 Zeni Kim +// Use of this source code is governed by MIT-style +// license that can be found in the LICENSE file. + +package main + +import ( + "git.smarteching.com/goffee/core" + "git.smarteching.com/goffee/cup/workers" +) + +// Register queues +func registerQueues() { + + var queque = new(core.Queuemux) + queque.QueueInit() + //######################################## + //# quques registration ##### + //######################################## + + // register your queues here ... + + queque.AddWork(workers.TypeWelcomeEmail, workers.HandleWelcomeEmailTask) + queque.AddWork(workers.TypeReminderEmail, workers.HandleReminderEmailTask) + + //######################################## + // Start queue server, DO NOT TOUCH + //######################################## + go queque.RunQueueserver() +} diff --git a/routes.go b/routes.go index 785c59e..f8d7fec 100644 --- a/routes.go +++ b/routes.go @@ -26,6 +26,7 @@ func registerRoutes() { controller.Get("/themecontent", controllers.Themecontent) controller.Get("/themepanel", controllers.Themedemo) controller.Get("/themeelements", controllers.ThemeElements) + controller.Get("/queuesample", controllers.Queuesample) // Uncomment the lines below to enable authentication controller.Post("/signup", controllers.Signup) diff --git a/workers/workers.go b/workers/workers.go new file mode 100644 index 0000000..e7a9cad --- /dev/null +++ b/workers/workers.go @@ -0,0 +1,39 @@ +package workers + +import ( + "context" + "encoding/json" + "log" + + "github.com/hibiken/asynq" +) + +// A list of task types. +const ( + TypeWelcomeEmail = "email:welcome" + TypeReminderEmail = "email:reminder" +) + +// Task payload for any email related tasks. +type EmailTaskPayload struct { + // ID for the email recipient. + UserID int +} + +func HandleWelcomeEmailTask(ctx context.Context, t *asynq.Task) error { + var p EmailTaskPayload + if err := json.Unmarshal(t.Payload(), &p); err != nil { + return err + } + log.Printf(" [*] Send Welcome Email to User %d", p.UserID) + return nil +} + +func HandleReminderEmailTask(ctx context.Context, t *asynq.Task) error { + var p EmailTaskPayload + if err := json.Unmarshal(t.Payload(), &p); err != nil { + return err + } + log.Printf(" [*] Send Reminder Email to User %d", p.UserID) + return nil +} From 273b43f5e0c4f102405a29a2fea60f0f30f8f865 Mon Sep 17 00:00:00 2001 From: Zeni Kim Date: Mon, 23 Dec 2024 23:40:22 -0500 Subject: [PATCH 05/21] add config option to disable queues --- config/queue.go | 20 ++++++++++++++++++++ main.go | 4 +++- 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 config/queue.go diff --git a/config/queue.go b/config/queue.go new file mode 100644 index 0000000..8060572 --- /dev/null +++ b/config/queue.go @@ -0,0 +1,20 @@ +// Copyright (c) 2025 Zeni Kim +// Use of this source code is governed by MIT-style +// license that can be found in the LICENSE file. + +package config + +import "git.smarteching.com/goffee/core" + +// Retrieve the main config for the Queue +func GetQueueConfig() core.QueueConfig { + //##################################### + //# Main configuration for Queue ##### + //##################################### + + return core.QueueConfig{ + // For enabling and disabling the queue system + // set to true to enable it, set to false to disable + EnableQueue: false, + } +} diff --git a/main.go b/main.go index 8e7c815..5fac06e 100644 --- a/main.go +++ b/main.go @@ -62,7 +62,9 @@ func main() { registerGlobalHooks() registerRoutes() registerEvents() - registerQueues() + if config.GetQueueConfig().EnableQueue == true { + registerQueues() + } if config.GetGormConfig().EnableGorm == true { RunAutoMigrations() } From d5725093511d0b146e5fa00c495195859fc5ef9b Mon Sep 17 00:00:00 2001 From: Zeni Kim Date: Mon, 23 Dec 2024 23:58:48 -0500 Subject: [PATCH 06/21] fix core version --- go.mod | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 2e7078c..e6be3df 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,7 @@ module git.smarteching.com/goffee/cup replace ( + git.smarteching.com/goffee/core => ../core git.smarteching.com/goffee/cup/config => ./config git.smarteching.com/goffee/cup/handlers => ./handlers git.smarteching.com/goffee/cup/middlewares => ./middlewares @@ -10,7 +11,7 @@ replace ( go 1.23.1 require ( - git.smarteching.com/goffee/core v1.8.4 + git.smarteching.com/goffee/core v1.9.0 github.com/google/uuid v1.6.0 github.com/hibiken/asynq v0.25.1 github.com/joho/godotenv v1.5.1 From 7a32380274b97b8f073d1f0a4be2763fbaa4086f Mon Sep 17 00:00:00 2001 From: Zeni Kim Date: Tue, 24 Dec 2024 00:01:42 -0500 Subject: [PATCH 07/21] fix core version --- go.mod | 1 - 1 file changed, 1 deletion(-) diff --git a/go.mod b/go.mod index e6be3df..8b0cfa0 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,6 @@ module git.smarteching.com/goffee/cup replace ( - git.smarteching.com/goffee/core => ../core git.smarteching.com/goffee/cup/config => ./config git.smarteching.com/goffee/cup/handlers => ./handlers git.smarteching.com/goffee/cup/middlewares => ./middlewares From f92910abfd8305659bfabfafda107030553ea120 Mon Sep 17 00:00:00 2001 From: Zeni Kim Date: Sat, 22 Feb 2025 20:59:16 -0500 Subject: [PATCH 08/21] update go1.24.0 --- go.mod | 32 ++++++++++++++++---------------- go.sum | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index 8b0cfa0..a7cfe99 100644 --- a/go.mod +++ b/go.mod @@ -25,40 +25,40 @@ require ( github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/go-chi/chi/v5 v5.1.0 // indirect + github.com/go-chi/chi/v5 v5.2.1 // indirect github.com/go-ozzo/ozzo-validation v3.6.0+incompatible // indirect - github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/go-sql-driver/mysql v1.9.0 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/harranali/mailing v1.2.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/pgx/v5 v5.7.1 // indirect + github.com/jackc/pgx/v5 v5.7.2 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailgun/errors v0.4.0 // indirect - github.com/mailgun/mailgun-go/v4 v4.17.3 // indirect + github.com/mailgun/mailgun-go/v4 v4.22.1 // indirect github.com/mattn/go-sqlite3 v1.14.24 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/redis/go-redis/v9 v9.7.0 // indirect + github.com/redis/go-redis/v9 v9.7.1 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/sendgrid/rest v2.6.9+incompatible // indirect github.com/sendgrid/sendgrid-go v3.16.0+incompatible // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/spf13/cast v1.7.0 // indirect - golang.org/x/crypto v0.28.0 // indirect - golang.org/x/image v0.21.0 // indirect - golang.org/x/net v0.30.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.27.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 + github.com/spf13/cast v1.7.1 // indirect + golang.org/x/crypto v0.34.0 // indirect + golang.org/x/image v0.24.0 // indirect + golang.org/x/net v0.35.0 // indirect + golang.org/x/sync v0.11.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect + golang.org/x/time v0.10.0 // indirect + google.golang.org/protobuf v1.36.5 // indirect gorm.io/driver/mysql v1.5.7 // indirect - gorm.io/driver/postgres v1.5.9 // indirect - gorm.io/driver/sqlite v1.5.6 // indirect + gorm.io/driver/postgres v1.5.11 // indirect + gorm.io/driver/sqlite v1.5.7 // indirect ) diff --git a/go.sum b/go.sum index ca95d88..c25cd73 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= git.smarteching.com/goffee/core v1.8.4 h1:XB+vpe7e8muiDChRVDaJ1TG7H+/FBxDQcMfWp4zloPs= git.smarteching.com/goffee/core v1.8.4/go.mod h1:JxXDvTQU2shKYY6c9aS3s6sFh7mEDzgmjzdc85HhBV8= +git.smarteching.com/goffee/core v1.9.0 h1:aND9YkkDXEqsgEoDA04D9pmum2D3/nNljO1ojE1nSM4= +git.smarteching.com/goffee/core v1.9.0/go.mod h1:L9a+kL1RVHRHzp+DzCS1apwVLyZAvGE6B94UlyIMhIg= git.smarteching.com/zeni/go-chart/v2 v2.1.4 h1:pF06+F6eqJLIG8uMiTVPR5TygPGMjM/FHMzTxmu5V/Q= git.smarteching.com/zeni/go-chart/v2 v2.1.4/go.mod h1:b3ueW9h3pGGXyhkormZAvilHaG4+mQti+bMNPdQBeOQ= github.com/SparkPost/gosparkpost v0.2.0 h1:yzhHQT7cE+rqzd5tANNC74j+2x3lrPznqPJrxC1yR8s= @@ -31,11 +33,15 @@ github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQD github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8= +github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= github.com/go-ozzo/ozzo-validation v3.6.0+incompatible h1:msy24VGS42fKO9K1vLz82/GeYW1cILu7Nuuj1N3BBkE= github.com/go-ozzo/ozzo-validation v3.6.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/go-sql-driver/mysql v1.9.0 h1:Y0zIbQXhQKmQgTp44Y1dp3wTXcn804QoTptLZT1vtvo= +github.com/go-sql-driver/mysql v1.9.0/go.mod h1:pDetrLJeA3oMujJuvXc8RJoasr589B6A9fwzD3QMrqw= github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= @@ -55,6 +61,8 @@ github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7Ulw github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs= github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA= +github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI= +github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= @@ -75,6 +83,8 @@ github.com/mailgun/errors v0.4.0 h1:6LFBvod6VIW83CMIOT9sYNp28TCX0NejFPP4dSX++i8= github.com/mailgun/errors v0.4.0/go.mod h1:xGBaaKdEdQT0/FhwvoXv4oBaqqmVZz9P1XEnvD/onc0= github.com/mailgun/mailgun-go/v4 v4.17.3 h1:WoO48/VeXgAVSzjgzyeLvF08AoPzWU2EBz79INN8rEA= github.com/mailgun/mailgun-go/v4 v4.17.3/go.mod h1:0ood70bQR/SffQ9NxIsAY06H+HG0hrvMVApfUp9TihI= +github.com/mailgun/mailgun-go/v4 v4.22.1 h1:yMvPeo9m5XPVVg3XF0aPiJiiGt/n/cayBa4eQBDYqtc= +github.com/mailgun/mailgun-go/v4 v4.22.1/go.mod h1:JA2xbLTkEWrX2TO+RUiJQALZus6WLLoXym2i8a8F5sE= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= @@ -91,6 +101,8 @@ 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/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/redis/go-redis/v9 v9.7.1 h1:4LhKRCIduqXqtvCUlaq9c8bdHOkICjDMrr1+Zb3osAc= +github.com/redis/go-redis/v9 v9.7.1/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= @@ -102,37 +114,56 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= 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/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.34.0 h1:+/C6tk6rf/+t5DhUketUbD1aNGqiSX3j15Z6xuIDlBA= +golang.org/x/crypto v0.34.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s= golang.org/x/image v0.21.0/go.mod h1:vUbsLavqK/W303ZroQQVKQ+Af3Yl6Uz1Ppu5J/cLz78= +golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ= +golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/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/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 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.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= 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/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= +golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 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= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= @@ -141,8 +172,12 @@ gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8= gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= +gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314= +gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= gorm.io/driver/sqlite v1.5.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE= gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4= +gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I= +gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4= gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= From bbadb4e3f869d29839ebda4bdfa5f16a335f398d Mon Sep 17 00:00:00 2001 From: Zeni Kim Date: Mon, 24 Feb 2025 09:58:53 -0500 Subject: [PATCH 09/21] update gitignore --- .gitignore | 2 ++ README.md | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 84146d5..90a928b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ tls/* !tls/.gitkeep .DS_Store storage/sqlite/* +.idea +cup diff --git a/README.md b/README.md index 7397849..4ae95df 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![gocondor logo](https://git.smarteching.com/avatars/cd7cd5b690adc8e5ec6d6cdb117f1bf5a9e9353dae111bfbb394d2c3d4497537?size=200) +![goffee logo](https://git.smarteching.com/avatars/cd7cd5b690adc8e5ec6d6cdb117f1bf5a9e9353dae111bfbb394d2c3d4497537?size=200) # Cup of Goffee ## What is Goffee? From 5aee1d1508753d0608cffd77c1b007bef01b63ca Mon Sep 17 00:00:00 2001 From: Zeni Kim Date: Mon, 24 Feb 2025 10:12:51 -0500 Subject: [PATCH 10/21] update core and tested on go1.24.0 --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index a7cfe99..b0f8a68 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ replace ( go 1.23.1 require ( - git.smarteching.com/goffee/core v1.9.0 + git.smarteching.com/goffee/core v1.9.1 github.com/google/uuid v1.6.0 github.com/hibiken/asynq v0.25.1 github.com/joho/godotenv v1.5.1 diff --git a/go.sum b/go.sum index c25cd73..d2b6fd3 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ git.smarteching.com/goffee/core v1.8.4 h1:XB+vpe7e8muiDChRVDaJ1TG7H+/FBxDQcMfWp4 git.smarteching.com/goffee/core v1.8.4/go.mod h1:JxXDvTQU2shKYY6c9aS3s6sFh7mEDzgmjzdc85HhBV8= git.smarteching.com/goffee/core v1.9.0 h1:aND9YkkDXEqsgEoDA04D9pmum2D3/nNljO1ojE1nSM4= git.smarteching.com/goffee/core v1.9.0/go.mod h1:L9a+kL1RVHRHzp+DzCS1apwVLyZAvGE6B94UlyIMhIg= +git.smarteching.com/goffee/core v1.9.1 h1:/WkIHkLJuNGf72tUJx7s/jt4gIA0EMkR1wemr+gnKg0= +git.smarteching.com/goffee/core v1.9.1/go.mod h1:L9a+kL1RVHRHzp+DzCS1apwVLyZAvGE6B94UlyIMhIg= git.smarteching.com/zeni/go-chart/v2 v2.1.4 h1:pF06+F6eqJLIG8uMiTVPR5TygPGMjM/FHMzTxmu5V/Q= git.smarteching.com/zeni/go-chart/v2 v2.1.4/go.mod h1:b3ueW9h3pGGXyhkormZAvilHaG4+mQti+bMNPdQBeOQ= github.com/SparkPost/gosparkpost v0.2.0 h1:yzhHQT7cE+rqzd5tANNC74j+2x3lrPznqPJrxC1yR8s= From 44228d87e8544d8c54ed42c2e60550840b8ab54b Mon Sep 17 00:00:00 2001 From: Zeni Kim Date: Wed, 19 Mar 2025 00:07:46 -0500 Subject: [PATCH 11/21] update core 1.9.2 and go1.24.1 --- go.mod | 18 +++++++++--------- go.sum | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index b0f8a68..4fc453a 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ replace ( go 1.23.1 require ( - git.smarteching.com/goffee/core v1.9.1 + git.smarteching.com/goffee/core v1.9.2 github.com/google/uuid v1.6.0 github.com/hibiken/asynq v0.25.1 github.com/joho/godotenv v1.5.1 @@ -39,7 +39,7 @@ require ( github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailgun/errors v0.4.0 // indirect - github.com/mailgun/mailgun-go/v4 v4.22.1 // indirect + github.com/mailgun/mailgun-go/v4 v4.23.0 // indirect github.com/mattn/go-sqlite3 v1.14.24 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect @@ -50,13 +50,13 @@ require ( github.com/sendgrid/sendgrid-go v3.16.0+incompatible // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/cast v1.7.1 // indirect - golang.org/x/crypto v0.34.0 // indirect - golang.org/x/image v0.24.0 // indirect - golang.org/x/net v0.35.0 // indirect - golang.org/x/sync v0.11.0 // indirect - golang.org/x/sys v0.30.0 // indirect - golang.org/x/text v0.22.0 // indirect - golang.org/x/time v0.10.0 // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/image v0.25.0 // indirect + golang.org/x/net v0.37.0 // indirect + golang.org/x/sync v0.12.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect + golang.org/x/time v0.11.0 // indirect google.golang.org/protobuf v1.36.5 // indirect gorm.io/driver/mysql v1.5.7 // indirect gorm.io/driver/postgres v1.5.11 // indirect diff --git a/go.sum b/go.sum index d2b6fd3..2954a37 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ git.smarteching.com/goffee/core v1.9.0 h1:aND9YkkDXEqsgEoDA04D9pmum2D3/nNljO1ojE git.smarteching.com/goffee/core v1.9.0/go.mod h1:L9a+kL1RVHRHzp+DzCS1apwVLyZAvGE6B94UlyIMhIg= git.smarteching.com/goffee/core v1.9.1 h1:/WkIHkLJuNGf72tUJx7s/jt4gIA0EMkR1wemr+gnKg0= git.smarteching.com/goffee/core v1.9.1/go.mod h1:L9a+kL1RVHRHzp+DzCS1apwVLyZAvGE6B94UlyIMhIg= +git.smarteching.com/goffee/core v1.9.2 h1:SpLAhsTxssPItgkBYLN3UxMH5s+q8qVtbvmRom3WKh8= +git.smarteching.com/goffee/core v1.9.2/go.mod h1:L9a+kL1RVHRHzp+DzCS1apwVLyZAvGE6B94UlyIMhIg= git.smarteching.com/zeni/go-chart/v2 v2.1.4 h1:pF06+F6eqJLIG8uMiTVPR5TygPGMjM/FHMzTxmu5V/Q= git.smarteching.com/zeni/go-chart/v2 v2.1.4/go.mod h1:b3ueW9h3pGGXyhkormZAvilHaG4+mQti+bMNPdQBeOQ= github.com/SparkPost/gosparkpost v0.2.0 h1:yzhHQT7cE+rqzd5tANNC74j+2x3lrPznqPJrxC1yR8s= @@ -87,6 +89,8 @@ github.com/mailgun/mailgun-go/v4 v4.17.3 h1:WoO48/VeXgAVSzjgzyeLvF08AoPzWU2EBz79 github.com/mailgun/mailgun-go/v4 v4.17.3/go.mod h1:0ood70bQR/SffQ9NxIsAY06H+HG0hrvMVApfUp9TihI= github.com/mailgun/mailgun-go/v4 v4.22.1 h1:yMvPeo9m5XPVVg3XF0aPiJiiGt/n/cayBa4eQBDYqtc= github.com/mailgun/mailgun-go/v4 v4.22.1/go.mod h1:JA2xbLTkEWrX2TO+RUiJQALZus6WLLoXym2i8a8F5sE= +github.com/mailgun/mailgun-go/v4 v4.23.0 h1:jPEMJzzin2s7lvehcfv/0UkyBu18GvcURPr2+xtZRbk= +github.com/mailgun/mailgun-go/v4 v4.23.0/go.mod h1:imTtizoFtpfZqPqGP8vltVBB6q9yWcv6llBhfFeElZU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= @@ -130,19 +134,27 @@ golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/crypto v0.34.0 h1:+/C6tk6rf/+t5DhUketUbD1aNGqiSX3j15Z6xuIDlBA= golang.org/x/crypto v0.34.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s= golang.org/x/image v0.21.0/go.mod h1:vUbsLavqK/W303ZroQQVKQ+Af3Yl6Uz1Ppu5J/cLz78= golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ= golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8= +golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ= +golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= @@ -151,16 +163,22 @@ 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/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 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.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= 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/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= 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= From 368d6c8e6b526f6f19a603551852c8f80de1ed90 Mon Sep 17 00:00:00 2001 From: Zeni Kim Date: Wed, 16 Apr 2025 22:50:56 -0500 Subject: [PATCH 12/21] sample code for select multiple and custom attributes in form --- controllers/themedemo.go | 44 +++++++++++++++++++ .../templates/custom_theme_contentpage.html | 3 ++ 2 files changed, 47 insertions(+) diff --git a/controllers/themedemo.go b/controllers/themedemo.go index bbde3c6..696862b 100644 --- a/controllers/themedemo.go +++ b/controllers/themedemo.go @@ -646,8 +646,37 @@ func Themecontent(c *core.Context) *core.Response { ContentTable components.ContentTable ContentTabledetail components.ContentTabledetail ContentGraph components.ContentGraph + FieldText components.FormInput + FormSelectCityM components.FormSelect } + // for select options + var allOptions []components.FormSelectOption + var option components.FormSelectOption + option.Value = "ch" + option.Caption = "China" + allOptions = append(allOptions, option) + option.Value = "ba" + option.Caption = "Buenos Aires" + allOptions = append(allOptions, option) + option.Value = "fr" + option.Caption = "France" + selectedOption := option + allOptions = append(allOptions, option) + option.Value = "kr" + option.Caption = "Korea" + allOptions = append(allOptions, option) + + // for custom attributes in form + var customAtt []components.CustomAtt + var attribute components.CustomAtt + attribute.AttName = "code" + attribute.AttValue = "five" + customAtt = append(customAtt, attribute) + attribute.AttName = "mytag" + attribute.AttValue = "My value" + customAtt = append(customAtt, attribute) + // TABLES // for th head var allTh []components.ContentTableTH @@ -725,6 +754,21 @@ func Themecontent(c *core.Context) *core.Response { // now fill data of the components tmplData := templateData{ + FormSelectCityM: components.FormSelect{ + ID: "city", + Label: "Select city", + AllOptions: allOptions, + SelectedOption: selectedOption, + IsMultiple: true, + }, + FieldText: components.FormInput{ + ID: "text", + Label: "Name", + Type: "text", + Hint: "This is sample hint", + Placeholder: "Enter your name", + CustomAtt: customAtt, + }, ContentTable: components.ContentTable{ ID: "table_demo", AllTH: allTh, diff --git a/storage/templates/custom_theme_contentpage.html b/storage/templates/custom_theme_contentpage.html index 67b35f9..1024f56 100644 --- a/storage/templates/custom_theme_contentpage.html +++ b/storage/templates/custom_theme_contentpage.html @@ -12,6 +12,9 @@

Pie chart

{{template "content_graph" .ContentGraph}} +
+ {{template "form_input" .FieldText}} + {{template "form_select" .FormSelectCityM}}
{{template "content_tabledetail" .ContentTabledetail}} From 51d76c2a94471368122ed3cfbcf2cc0aa20b9d77 Mon Sep 17 00:00:00 2001 From: Zeni Kim Date: Thu, 17 Apr 2025 01:51:08 -0500 Subject: [PATCH 13/21] add sample element paginator --- controllers/themedemo.go | 78 +++++++++++++++++-- .../templates/custom_theme_contentpage.html | 3 + 2 files changed, 74 insertions(+), 7 deletions(-) diff --git a/controllers/themedemo.go b/controllers/themedemo.go index 696862b..03c55c1 100644 --- a/controllers/themedemo.go +++ b/controllers/themedemo.go @@ -643,11 +643,13 @@ func Themecontent(c *core.Context) *core.Response { // first, include all compoments type templateData struct { - ContentTable components.ContentTable - ContentTabledetail components.ContentTabledetail - ContentGraph components.ContentGraph - FieldText components.FormInput - FormSelectCityM components.FormSelect + ContentTable components.ContentTable + ContentTabledetail components.ContentTabledetail + ContentGraph components.ContentGraph + FieldText components.FormInput + FormSelectCityM components.FormSelect + Pagination components.ContentPagination + ShouldShowPagination bool } // for select options @@ -699,7 +701,7 @@ func Themecontent(c *core.Context) *core.Response { var allTd [][]components.ContentTableTD //var vals []components.ContentTableTD // rows - for i := 1; i <= 10; i++ { + for i := 1; i <= 28; i++ { vals := make([]components.ContentTableTD, len(allTh)) for b := 0; b < len(allTh)-2; b++ { vals[b].Value = fmt.Sprintf("%s%d%d", "TD data: ", i, b) @@ -718,6 +720,46 @@ func Themecontent(c *core.Context) *core.Response { allTd = append(allTd, vals) } + // Pagination demo + // start config + limit := 10 + shouldShowPagination := false + pageViewTableOffset := 0 + // Get the length of AllOptions + totalrecords := len(allTd) + // get current table offset + tpage := c.RequestParamExists("tpage") + if tpage { + pageViewTableOffset, _ = strconv.Atoi(c.GetRequestParam("tpage").(string)) + } + + // start default option paginator + var pagination components.ContentPagination + pagination.PageStartRecord = pageViewTableOffset + 1 + pagination.PageEndRecord = 0 + pagination.TotalRecords = 0 + pagination.PrevLink = "" + pagination.NextLink = "" + + // check current page + // fake function to emulate a query offset + newTd := getPaginatedPageViews(allTd, limit, pageViewTableOffset) + + if len(newTd) > 0 { + pagination.TotalRecords = totalrecords + pagination.PageStartRecord = pageViewTableOffset + 1 + pagination.PageEndRecord = pageViewTableOffset + len(newTd) + shouldShowPagination = totalrecords > limit + } + + if shouldShowPagination && pageViewTableOffset != 0 { + pagination.PrevLink = fmt.Sprintf("/themecontent?tpage=%d", pageViewTableOffset-limit) + } + + if shouldShowPagination && pageViewTableOffset+limit < totalrecords { + pagination.NextLink = fmt.Sprintf("/themecontent?tpage=%d", pageViewTableOffset+limit) + } + // for td items in table detail var allTdetail []components.ContentTabledetailTD // table detail @@ -772,7 +814,7 @@ func Themecontent(c *core.Context) *core.Response { ContentTable: components.ContentTable{ ID: "table_demo", AllTH: allTh, - AllTD: allTd, + AllTD: newTd, }, ContentTabledetail: components.ContentTabledetail{ ID: "table_demodetail", @@ -785,6 +827,8 @@ func Themecontent(c *core.Context) *core.Response { Labels: "Berlin|Paris|Venecia", Values: valuesgraph, }, + Pagination: pagination, + ShouldShowPagination: shouldShowPagination, } return c.Response.Template("custom_theme_contentpage.html", tmplData) @@ -797,3 +841,23 @@ func Themecontent(c *core.Context) *core.Response { } } + +func getPaginatedPageViews(values [][]components.ContentTableTD, limit int, offset int) [][]components.ContentTableTD { + + // Validate the offset and adjust if necessary + if offset < 0 { + offset = 0 // Ensure offset is not negative + } else if offset >= len(values) { + var emptytd [][]components.ContentTableTD + return emptytd + } + + // Calculate the end index (limit the slice to the size of the array) + end := offset + limit + if end > len(values) { + end = len(values) // Ensure end doesn't exceed the length of the array + } + + return values[offset:end] // Slice the array + +} diff --git a/storage/templates/custom_theme_contentpage.html b/storage/templates/custom_theme_contentpage.html index 1024f56..422c253 100644 --- a/storage/templates/custom_theme_contentpage.html +++ b/storage/templates/custom_theme_contentpage.html @@ -7,6 +7,9 @@ Content demos
{{template "content_table" .ContentTable}} + {{if .ShouldShowPagination}} + {{template "content_pagination" .Pagination}} + {{end}}
From ffd3c89c1e4ccf1b8b27038f105fda5e0de68b9a Mon Sep 17 00:00:00 2001 From: Zeni Kim Date: Tue, 15 Jul 2025 11:27:52 -0500 Subject: [PATCH 14/21] add folders to cdn --- go.mod | 4 +++- go.sum | 24 ++++++++++++++++++------ storage/app/img/goffee.png | Bin 0 -> 25928 bytes storage/app/index.html | 9 +++++++++ 4 files changed, 30 insertions(+), 7 deletions(-) create mode 100644 storage/app/img/goffee.png create mode 100644 storage/app/index.html diff --git a/go.mod b/go.mod index 8b0cfa0..a72bbeb 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ replace ( git.smarteching.com/goffee/cup/models => ./models ) -go 1.23.1 +go 1.24.1 require ( git.smarteching.com/goffee/core v1.9.0 @@ -21,10 +21,12 @@ require ( require ( filippo.io/edwards25519 v1.1.0 // indirect git.smarteching.com/zeni/go-chart/v2 v2.1.4 // indirect + git.smarteching.com/zeni/go-charts/v2 v2.6.11 // indirect github.com/SparkPost/gosparkpost v0.2.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/dustin/go-humanize v1.0.1 // indirect github.com/go-chi/chi/v5 v5.1.0 // indirect github.com/go-ozzo/ozzo-validation v3.6.0+incompatible // indirect github.com/go-sql-driver/mysql v1.8.1 // indirect diff --git a/go.sum b/go.sum index ca95d88..95137c3 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,9 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -git.smarteching.com/goffee/core v1.8.4 h1:XB+vpe7e8muiDChRVDaJ1TG7H+/FBxDQcMfWp4zloPs= -git.smarteching.com/goffee/core v1.8.4/go.mod h1:JxXDvTQU2shKYY6c9aS3s6sFh7mEDzgmjzdc85HhBV8= git.smarteching.com/zeni/go-chart/v2 v2.1.4 h1:pF06+F6eqJLIG8uMiTVPR5TygPGMjM/FHMzTxmu5V/Q= git.smarteching.com/zeni/go-chart/v2 v2.1.4/go.mod h1:b3ueW9h3pGGXyhkormZAvilHaG4+mQti+bMNPdQBeOQ= +git.smarteching.com/zeni/go-charts/v2 v2.6.11 h1:9udzlv3uxGXszpplfkL5IaTUrgkNj++KwhbaN1vVEqI= +git.smarteching.com/zeni/go-charts/v2 v2.6.11/go.mod h1:3OpRPSXg7Qx4zcgsmwsC9ZFB9/wAkGSbnXf1wIbHYCg= github.com/SparkPost/gosparkpost v0.2.0 h1:yzhHQT7cE+rqzd5tANNC74j+2x3lrPznqPJrxC1yR8s= github.com/SparkPost/gosparkpost v0.2.0/go.mod h1:S9WKcGeou7cbPpx0kTIgo8Q69WZvUmVeVzbD+djalJ4= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= @@ -23,12 +23,16 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ= github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y= github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-ozzo/ozzo-validation v3.6.0+incompatible h1:msy24VGS42fKO9K1vLz82/GeYW1cILu7Nuuj1N3BBkE= @@ -42,6 +46,8 @@ github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17w github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -69,6 +75,10 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mailgun/errors v0.4.0 h1:6LFBvod6VIW83CMIOT9sYNp28TCX0NejFPP4dSX++i8= @@ -93,6 +103,8 @@ github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa 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/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 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/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE= @@ -106,8 +118,10 @@ github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02n 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.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= @@ -120,8 +134,6 @@ golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/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= diff --git a/storage/app/img/goffee.png b/storage/app/img/goffee.png new file mode 100644 index 0000000000000000000000000000000000000000..d0491fce12ada6be01434cd41b956a32d65d901a GIT binary patch literal 25928 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 literal 0 HcmV?d00001 diff --git a/storage/app/index.html b/storage/app/index.html new file mode 100644 index 0000000..326fcb0 --- /dev/null +++ b/storage/app/index.html @@ -0,0 +1,9 @@ + + + + Title! + + +

This is an example conten.

+ + From 2a3d5486e9a4e4d204b62412902985604221f714 Mon Sep 17 00:00:00 2001 From: Zeni Kim Date: Tue, 15 Jul 2025 11:31:16 -0500 Subject: [PATCH 15/21] add env vars --- .env-dev | 4 +++- .env-example | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.env-dev b/.env-dev index ded2eab..c88f67a 100644 --- a/.env-dev +++ b/.env-dev @@ -8,7 +8,7 @@ App_HTTP_HOST=localhost App_HTTP_PORT=8080 App_USE_HTTPS=false App_USE_LETSENCRYPT=false -App_USE_CORESERVICES=false +App_USE_CORESERVICES=true APP_LETSENCRYPT_EMAIL=mail@example.com App_HTTPS_HOSTS=example.com, www.example.com App_REDIRECT_HTTP_TO_HTTPS=false @@ -19,6 +19,8 @@ App_KEY_FILE_PATH=tls/server.key ###### TEMPLATES ###### ####################################### TEMPLATE_ENABLE=true +APP_ENABLE=true +CDNEnable=false COOKIE_SECRET=13d6b4dff8f84a10851021ec8608f814570d562c92fe6b5ec4c9f595bcb3234b ####################################### diff --git a/.env-example b/.env-example index ded2eab..d0780b5 100644 --- a/.env-example +++ b/.env-example @@ -20,6 +20,9 @@ App_KEY_FILE_PATH=tls/server.key ####################################### TEMPLATE_ENABLE=true COOKIE_SECRET=13d6b4dff8f84a10851021ec8608f814570d562c92fe6b5ec4c9f595bcb3234b +APP_ENABLE=true +CDNEnable=false + ####################################### ###### JWT ###### From 7bf28afdbfc85ea0f97cc087f3a6321bab9617ce Mon Sep 17 00:00:00 2001 From: Zeni Kim Date: Sat, 2 May 2026 01:15:22 -0500 Subject: [PATCH 16/21] go update --- go.mod | 45 +++++++++--------- go.sum | 141 ++++++++++++++++++++------------------------------------- 2 files changed, 72 insertions(+), 114 deletions(-) diff --git a/go.mod b/go.mod index bdafa0f..c51f815 100644 --- a/go.mod +++ b/go.mod @@ -7,57 +7,56 @@ replace ( git.smarteching.com/goffee/cup/models => ./models ) -go 1.24.1 +go 1.25.0 require ( - git.smarteching.com/goffee/core v1.9.5 + git.smarteching.com/goffee/core v1.9.6 github.com/google/uuid v1.6.0 - github.com/hibiken/asynq v0.25.1 + github.com/hibiken/asynq v0.26.0 github.com/joho/godotenv v1.5.1 github.com/julienschmidt/httprouter v1.3.0 - gorm.io/gorm v1.30.0 + gorm.io/gorm v1.31.1 ) require ( - filippo.io/edwards25519 v1.1.0 // indirect + filippo.io/edwards25519 v1.2.0 // indirect git.smarteching.com/zeni/go-chart/v2 v2.1.4 // indirect github.com/SparkPost/gosparkpost v0.2.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/go-chi/chi/v5 v5.2.2 // indirect + github.com/go-chi/chi/v5 v5.2.5 // indirect github.com/go-ozzo/ozzo-validation v3.6.0+incompatible // indirect - github.com/go-sql-driver/mysql v1.9.3 // indirect - github.com/golang-jwt/jwt/v5 v5.2.3 // indirect + github.com/go-sql-driver/mysql v1.10.0 // indirect + github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/harranali/mailing v1.2.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/pgx/v5 v5.7.5 // indirect + github.com/jackc/pgx/v5 v5.9.2 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/mailgun/errors v0.4.0 // indirect + github.com/mailgun/errors v0.5.0 // indirect github.com/mailgun/mailgun-go/v4 v4.23.0 // indirect - github.com/mattn/go-sqlite3 v1.14.28 // indirect + github.com/mattn/go-sqlite3 v1.14.44 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/redis/go-redis/v9 v9.11.0 // indirect + github.com/redis/go-redis/v9 v9.19.0 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/sendgrid/rest v2.6.9+incompatible // indirect github.com/sendgrid/sendgrid-go v3.16.1+incompatible // indirect - github.com/sirupsen/logrus v1.9.3 // indirect - github.com/spf13/cast v1.9.2 // indirect - golang.org/x/crypto v0.40.0 // indirect - golang.org/x/image v0.29.0 // indirect - golang.org/x/net v0.42.0 // indirect - golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.34.0 // indirect - golang.org/x/text v0.27.0 // indirect - golang.org/x/time v0.12.0 // indirect - google.golang.org/protobuf v1.36.6 // indirect + github.com/spf13/cast v1.10.0 // indirect + go.uber.org/atomic v1.11.0 // indirect + golang.org/x/crypto v0.50.0 // indirect + golang.org/x/image v0.39.0 // indirect + golang.org/x/net v0.53.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.43.0 // indirect + golang.org/x/text v0.36.0 // indirect + golang.org/x/time v0.15.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect gorm.io/driver/mysql v1.6.0 // indirect gorm.io/driver/postgres v1.6.0 // indirect gorm.io/driver/sqlite v1.6.0 // indirect diff --git a/go.sum b/go.sum index 0ea3916..76d68f0 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,7 @@ -filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= -filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -git.smarteching.com/goffee/core v1.9.2 h1:SpLAhsTxssPItgkBYLN3UxMH5s+q8qVtbvmRom3WKh8= -git.smarteching.com/goffee/core v1.9.2/go.mod h1:L9a+kL1RVHRHzp+DzCS1apwVLyZAvGE6B94UlyIMhIg= -git.smarteching.com/goffee/core v1.9.5 h1:rq6vI4WSUMGQNzJvhNWmtY2ycC7UszEvXpQ7uUR8sZY= -git.smarteching.com/goffee/core v1.9.5/go.mod h1:ifiBgTOR4zCMzdGsabNrEO792EHny2o149NGe3TSlms= +filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= +filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= +git.smarteching.com/goffee/core v1.9.6 h1:GY1EXqbmBEWZAVrl3q22Izb6aXhQzFVQBv2hWhK/So8= +git.smarteching.com/goffee/core v1.9.6/go.mod h1:ifiBgTOR4zCMzdGsabNrEO792EHny2o149NGe3TSlms= git.smarteching.com/zeni/go-chart/v2 v2.1.4 h1:pF06+F6eqJLIG8uMiTVPR5TygPGMjM/FHMzTxmu5V/Q= git.smarteching.com/zeni/go-chart/v2 v2.1.4/go.mod h1:b3ueW9h3pGGXyhkormZAvilHaG4+mQti+bMNPdQBeOQ= github.com/SparkPost/gosparkpost v0.2.0 h1:yzhHQT7cE+rqzd5tANNC74j+2x3lrPznqPJrxC1yR8s= @@ -23,46 +21,35 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8= -github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= -github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618= -github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= +github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug= +github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= github.com/go-ozzo/ozzo-validation v3.6.0+incompatible h1:msy24VGS42fKO9K1vLz82/GeYW1cILu7Nuuj1N3BBkE= github.com/go-ozzo/ozzo-validation v3.6.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU= -github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= -github.com/go-sql-driver/mysql v1.9.0 h1:Y0zIbQXhQKmQgTp44Y1dp3wTXcn804QoTptLZT1vtvo= -github.com/go-sql-driver/mysql v1.9.0/go.mod h1:pDetrLJeA3oMujJuvXc8RJoasr589B6A9fwzD3QMrqw= -github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= -github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= +github.com/go-sql-driver/mysql v1.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxoWCWfw= +github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk= github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14= -github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= -github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0= -github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/harranali/mailing v1.2.0 h1:ihIyJwB8hyRVcdk+v465wk1PHMrSrgJqo/kMd+gZClY= 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/hibiken/asynq v0.26.0 h1:1Zxr92MlDnb1Zt/QR5g2vSCqUS03i95lUfqx5X7/wrw= +github.com/hibiken/asynq v0.26.0/go.mod h1:Qk4e57bTnWDoyJ67VkchuV6VzSM9IQW2nPvAGuDyw58= 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/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI= -github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= -github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs= -github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= +github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw= +github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= @@ -77,21 +64,21 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= +github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailgun/errors v0.4.0 h1:6LFBvod6VIW83CMIOT9sYNp28TCX0NejFPP4dSX++i8= -github.com/mailgun/errors v0.4.0/go.mod h1:xGBaaKdEdQT0/FhwvoXv4oBaqqmVZz9P1XEnvD/onc0= +github.com/mailgun/errors v0.5.0 h1:pLQo8uhAdORsjN69mGixSr0pGs46z/BW/FQXd8HG1VM= +github.com/mailgun/errors v0.5.0/go.mod h1:+2nrgY77E0vDkG4ErehpcpbSkMLkseJzKbrva89WeSs= github.com/mailgun/mailgun-go/v4 v4.23.0 h1:jPEMJzzin2s7lvehcfv/0UkyBu18GvcURPr2+xtZRbk= github.com/mailgun/mailgun-go/v4 v4.23.0/go.mod h1:imTtizoFtpfZqPqGP8vltVBB6q9yWcv6llBhfFeElZU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= -github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= -github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.44 h1:3VSe+xafpbzsLbdr2AWlAZk9yRHiBhTBakioXaCKTF8= +github.com/mattn/go-sqlite3 v1.14.44/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -103,10 +90,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/redis/go-redis/v9 v9.7.1 h1:4LhKRCIduqXqtvCUlaq9c8bdHOkICjDMrr1+Zb3osAc= -github.com/redis/go-redis/v9 v9.7.1/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= -github.com/redis/go-redis/v9 v9.11.0 h1:E3S08Gl/nJNn5vkxd2i78wZxWAPNZgUNTp8WIJUAiIs= -github.com/redis/go-redis/v9 v9.11.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= +github.com/redis/go-redis/v9 v9.19.0 h1:XPVaaPSnG6RhYf7p+rmSa9zZfeVAnWsH5h3lxthOm/k= +github.com/redis/go-redis/v9 v9.19.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA= 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/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= @@ -114,81 +99,55 @@ github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/f 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/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE= -github.com/sendgrid/sendgrid-go v3.16.0+incompatible h1:i8eE6IMkiCy7vusSdacHHSBUpXyTcTXy/Rl9N9aZ/Qw= -github.com/sendgrid/sendgrid-go v3.16.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8= github.com/sendgrid/sendgrid-go v3.16.1+incompatible h1:zWhTmB0Y8XCDzeWIm2/BIt1GjJohAA0p6hVEaDtHWWs= github.com/sendgrid/sendgrid-go v3.16.1+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= -github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE= -github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= 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/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= +github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= -golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= -golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= -golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ= -golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs= -golang.org/x/image v0.29.0 h1:HcdsyR4Gsuys/Axh0rDEmlBmB68rW1U9BUdB3UVHsas= -golang.org/x/image v0.29.0/go.mod h1:RVJROnf3SLK8d26OW91j4FrIHGbsJ8QnbEocVTOWQDA= +golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= +golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= +golang.org/x/image v0.39.0 h1:skVYidAEVKgn8lZ602XO75asgXBgLj9G/FE3RbuPFww= +golang.org/x/image v0.39.0/go.mod h1:sIbmppfU+xFLPIG0FoVUTvyBMmgng1/XAMhQ2ft0hpA= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= +golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= 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.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= -golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= -golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= -golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= -golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= -google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= 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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= -gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= -gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314= -gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= -gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I= -gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4= gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= -gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= -gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= -gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= -gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs= -gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= +gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= +gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= From a7310b24d576803cca0a92e55e4f1d5853335307 Mon Sep 17 00:00:00 2001 From: Zeni Kim Date: Sat, 2 May 2026 03:03:36 -0500 Subject: [PATCH 17/21] Custom `html/template` FuncMap extensions that bring Liquid-like expressiveness to Go server-side templates. Add template demo. --- controllers/themedemo.go | 131 +++++ go.mod | 2 + go.sum | 4 + routes.go | 2 + .../templates/custom_templates_functions.html | 471 ++++++++++++++++++ 5 files changed, 610 insertions(+) create mode 100644 storage/templates/custom_templates_functions.html diff --git a/controllers/themedemo.go b/controllers/themedemo.go index 03c55c1..85446e6 100644 --- a/controllers/themedemo.go +++ b/controllers/themedemo.go @@ -9,6 +9,7 @@ import ( "math/rand/v2" "os" "strconv" + "time" "git.smarteching.com/goffee/core" "git.smarteching.com/goffee/core/template/components" @@ -861,3 +862,133 @@ func getPaginatedPageViews(values [][]components.ContentTableTD, limit int, offs return values[offset:end] // Slice the array } + +// Custom Templates functions +func TemplatesFunctions(c *core.Context) *core.Response { + + // check if template engine is enabled + TemplateEnableStr := os.Getenv("TEMPLATE_ENABLE") + if TemplateEnableStr == "" { + TemplateEnableStr = "false" + } + TemplateEnable, _ := strconv.ParseBool(TemplateEnableStr) + + if TemplateEnable { + + tmplData := SamplePageData() + return c.Response.Template("custom_templates_functions.html", tmplData) + + } else { + + message := "{\"message\": \"Error, template not enabled\"}" + return c.Response.Json(message) + + } + +} + +// Author represents the writer of an article to show the custom functions +type Author struct { + Name string + Avatar string + Bio string +} + +// Article represents a blog post or news entry used to test template helpers to show the custom functions +type Article struct { + Title string + Slug string + Excerpt string + Body string + Tags []string + PublishedAt time.Time + UpdatedAt time.Time + Author Author + Views int + Price float64 + Featured bool + Subtitle string // intentionally left empty on some entries to test coalesce/defaultVal +} + +// PageData is the top-level context passed to the HTML template to show the custom functions +type PageData struct { + SiteTitle string + Articles []Article +} + +// SamplePageData returns a populated PageData ready to be rendered by the template. +func SamplePageData() PageData { + return PageData{ + SiteTitle: "go/template lab", + Articles: []Article{ + { + Title: "getting started with go templates", + Slug: "getting-started-go-templates", + Excerpt: "Go's html/template package is both powerful and safe by default. In this article we explore how to extend it with custom FuncMap helpers that bring it closer to the expressiveness of Liquid or Twig, without sacrificing any of the security guarantees.", + Tags: []string{"go", "templates", "web", "backend"}, + PublishedAt: time.Now().Add(-3 * 24 * time.Hour), + UpdatedAt: time.Now().Add(-1 * 24 * time.Hour), + Views: 14200, + Price: 0, + Featured: true, + Subtitle: "", + Author: Author{ + Name: "marina voss", + Avatar: "MV", + Bio: "Senior backend engineer focused on developer tooling and observability.", + }, + }, + { + Title: "building a blog engine in go", + Slug: "blog-engine-go", + Excerpt: "We walk through building a minimal but complete blog engine using only the Go standard library: routing with net/http, persistence with database/sql, and rendering with html/template.", + Tags: []string{"go", "blog", "sqlite"}, + PublishedAt: time.Now().Add(-10 * 24 * time.Hour), + UpdatedAt: time.Now().Add(-10 * 24 * time.Hour), + Views: 8750, + Price: 9.99, + Featured: false, + Subtitle: "A zero-dependency approach", + Author: Author{ + Name: "rafael okonkwo", + Avatar: "RO", + Bio: "Full-stack engineer and open-source contributor. Writes about Go and distributed systems.", + }, + }, + { + Title: "concurrency patterns you should know", + Slug: "concurrency-patterns-go", + Excerpt: "Goroutines are cheap, but misusing them is expensive. This deep-dive covers fan-out/fan-in, pipelines, semaphores, and error group patterns with real production examples.", + Tags: []string{"go", "concurrency", "goroutines", "advanced"}, + PublishedAt: time.Now().Add(-45 * 24 * time.Hour), + UpdatedAt: time.Now().Add(-40 * 24 * time.Hour), + Views: 31400, + Price: 14.99, + Featured: true, + Subtitle: "", + Author: Author{ + Name: "selin çelik", + Avatar: "SÇ", + Bio: "Systems programmer. Previously at Cloudflare. Loves writing about the internals of things.", + }, + }, + { + Title: "understanding go interfaces", + Slug: "understanding-go-interfaces", + Excerpt: "Interfaces in Go are implicit and structural, which makes them both elegant and occasionally surprising. We look at how the runtime dispatches method calls and how to design composable interfaces.", + Tags: []string{"go", "interfaces", "design"}, + PublishedAt: time.Now().Add(-2 * time.Hour), + UpdatedAt: time.Now().Add(-1 * time.Hour), + Views: 420, + Price: 0, + Featured: false, + Subtitle: "Implicit, structural, and powerful", + Author: Author{ + Name: "marina voss", + Avatar: "MV", + Bio: "Senior backend engineer focused on developer tooling and observability.", + }, + }, + }, + } +} \ No newline at end of file diff --git a/go.mod b/go.mod index c51f815..21103fe 100644 --- a/go.mod +++ b/go.mod @@ -21,9 +21,11 @@ require ( require ( filippo.io/edwards25519 v1.2.0 // indirect git.smarteching.com/zeni/go-chart/v2 v2.1.4 // indirect + git.smarteching.com/zeni/go-charts/v2 v2.6.11 // indirect github.com/SparkPost/gosparkpost v0.2.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect github.com/go-chi/chi/v5 v5.2.5 // indirect github.com/go-ozzo/ozzo-validation v3.6.0+incompatible // indirect github.com/go-sql-driver/mysql v1.10.0 // indirect diff --git a/go.sum b/go.sum index 76d68f0..2bf5036 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ git.smarteching.com/goffee/core v1.9.6 h1:GY1EXqbmBEWZAVrl3q22Izb6aXhQzFVQBv2hWh git.smarteching.com/goffee/core v1.9.6/go.mod h1:ifiBgTOR4zCMzdGsabNrEO792EHny2o149NGe3TSlms= git.smarteching.com/zeni/go-chart/v2 v2.1.4 h1:pF06+F6eqJLIG8uMiTVPR5TygPGMjM/FHMzTxmu5V/Q= git.smarteching.com/zeni/go-chart/v2 v2.1.4/go.mod h1:b3ueW9h3pGGXyhkormZAvilHaG4+mQti+bMNPdQBeOQ= +git.smarteching.com/zeni/go-charts/v2 v2.6.11 h1:9udzlv3uxGXszpplfkL5IaTUrgkNj++KwhbaN1vVEqI= +git.smarteching.com/zeni/go-charts/v2 v2.6.11/go.mod h1:3OpRPSXg7Qx4zcgsmwsC9ZFB9/wAkGSbnXf1wIbHYCg= github.com/SparkPost/gosparkpost v0.2.0 h1:yzhHQT7cE+rqzd5tANNC74j+2x3lrPznqPJrxC1yR8s= github.com/SparkPost/gosparkpost v0.2.0/go.mod h1:S9WKcGeou7cbPpx0kTIgo8Q69WZvUmVeVzbD+djalJ4= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= @@ -21,6 +23,8 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug= diff --git a/routes.go b/routes.go index f8d7fec..dc1799c 100644 --- a/routes.go +++ b/routes.go @@ -60,4 +60,6 @@ func registerRoutes() { controller.Get("/appsession", controllers.AppSession) controller.Post("/appsession", controllers.AppSession) + + controller.Get("/templatesfunc", controllers.TemplatesFunctions) } diff --git a/storage/templates/custom_templates_functions.html b/storage/templates/custom_templates_functions.html new file mode 100644 index 0000000..6e0c357 --- /dev/null +++ b/storage/templates/custom_templates_functions.html @@ -0,0 +1,471 @@ + + + + + + {{.SiteTitle}} — template lab + + + + + + + +
+
+

{{.SiteTitle}}

+

Template helpers
in action

+
+

22 helpers registered
across 6 groups

+
+ + + + + +{{$first := first .Articles}} + +
+ +
+

capitalize capitalize

+

{{capitalize $first.Title}}

+

input: "{{$first.Title}}"

+
+ +
+

truncate truncate

+

{{$first.Excerpt | truncate 80}}

+

capped at 80 chars

+
+ +
+

prepend prepend

+

{{prepend $first.Slug "/articles/"}}

+

prepended "/articles/" to slug

+
+ +
+

strAppend strAppend

+

{{strAppend $first.Slug ".html"}}

+

appended ".html" to slug

+
+ +
+

split → join split join

+ {{$parts := split "go,templates,funcmap" ","}} +

{{join $parts " · "}}

+

split on "," then joined with " · "

+
+ +
+ + + + + +
+ +
+

fmtNumber — int fmtNumber

+

{{fmtNumber $first.Views}}

+

raw value: {{$first.Views}}

+
+ + {{$paid := index .Articles 2}} +
+

fmtNumber — float fmtNumber

+

$ {{fmtNumber $paid.Price}}

+

raw value: {{$paid.Price}}

+
+ +
+

fmtDate "short" fmtDate

+

{{fmtDate $first.PublishedAt "short"}}

+

layout: "02 Jan 2006"

+
+ +
+

fmtDate "long" fmtDate

+

{{fmtDate $first.PublishedAt "long"}}

+

layout: "02 January 2006"

+
+ +
+

fmtDate "iso" fmtDate

+

{{fmtDate $first.PublishedAt "iso"}}

+

layout: "2006-01-02"

+
+ +
+

timeAgo timeAgo

+ {{range .Articles}} +

{{timeAgo .PublishedAt}} — {{fmtDate .PublishedAt "short"}}

+ {{end}} +
+ +
+ + + + + +
+ +
+

first first

+ {{with first .Articles}} +

{{capitalize .Title}}

+

first article in the list

+ {{end}} +
+ +
+

last last

+ {{with last .Articles}} +

{{capitalize .Title}}

+

last article in the list

+ {{end}} +
+ +
+

sliceOf 0–2 sliceOf

+ {{range sliceOf .Articles 0 2}} +

— {{capitalize .Title}}

+ {{end}} +

only first 2 of {{len .Articles}} articles

+
+ +
+

contains — slice contains

+ {{if contains $first.Tags "go"}} +

✓ has tag "go"

+ {{else}} +

✗ missing tag "go"

+ {{end}} +

tags: {{join $first.Tags ", "}}

+
+ +
+

contains — string contains

+ {{if contains $first.Excerpt "FuncMap"}} +

✓ excerpt mentions "FuncMap"

+ {{else}} +

✗ not found

+ {{end}} +

substring search on .Excerpt

+
+ +
+

join join

+

{{join $first.Tags " / "}}

+

tags joined with " / "

+
+ +
+ + + + + + + + + + + + + + + + {{range .Articles}} + + + + + + + + + + + + + + + + + + + {{end}} + +
HelperArticleInputOutput
defaultVal{{.Title | capitalize | truncate 30}}.Subtitle ({{if .Subtitle}}"{{.Subtitle}}"{{else}}empty{{end}}){{defaultVal "No subtitle" .Subtitle}}
ternary{{capitalize .Title | truncate 30}}.Featured = {{.Featured}}{{ternary "⭐ featured" "regular" .Featured}}
coalesce{{capitalize .Title | truncate 30}}.Subtitle → .Title{{coalesce .Subtitle .Title}}
+ + + + + +
+ {{range .Articles}} +
+
+
+ {{if .Featured}}featured{{end}} + {{range .Tags}}{{.}}{{end}} +
+ +

{{capitalize .Title}}

+ + {{with coalesce .Subtitle ""}} +

{{.}}

+ {{end}} + +

{{.Excerpt | truncate 160}}

+ + + {{prepend .Slug "/articles/"}} → + + +
+
{{.Author.Avatar}}
+
+
{{capitalize .Author.Name}}
+ +
+
+
+ +
+
+ {{fmtNumber .Views}} + views +
+ {{if gt .Price 0.0}} + $ {{fmtNumber .Price}} + {{else}} + free + {{end}} +
+
+ {{end}} +
+ + + +
+ {{.SiteTitle}} / template sandbox + {{len .Articles}} articles · {{fmtDate (first .Articles).PublishedAt "iso"}} → {{fmtDate (last .Articles).PublishedAt "iso"}} +
+ + + \ No newline at end of file From f320c1d238678958e0ec571faf621ae13d1d9fe5 Mon Sep 17 00:00:00 2001 From: Zeni Kim Date: Wed, 6 May 2026 16:07:55 -0500 Subject: [PATCH 18/21] support to new log system --- .env-dev | 2 ++ controllers/themedemo.go | 7 +++++++ main.go | 13 +++++++++++++ 3 files changed, 22 insertions(+) diff --git a/.env-dev b/.env-dev index c88f67a..800ca98 100644 --- a/.env-dev +++ b/.env-dev @@ -14,6 +14,8 @@ App_HTTPS_HOSTS=example.com, www.example.com App_REDIRECT_HTTP_TO_HTTPS=false App_CERT_FILE_PATH=tls/server.crt App_KEY_FILE_PATH=tls/server.key +LOG_STDOUT_ENABLE=true +LOG_LEVEL=debug ####################################### ###### TEMPLATES ###### diff --git a/controllers/themedemo.go b/controllers/themedemo.go index 85446e6..a3e22a3 100644 --- a/controllers/themedemo.go +++ b/controllers/themedemo.go @@ -874,6 +874,13 @@ func TemplatesFunctions(c *core.Context) *core.Response { TemplateEnable, _ := strconv.ParseBool(TemplateEnableStr) if TemplateEnable { + loggr := c.GetLogger() + + loggr.Debug("D e b u g") + loggr.Info("I n f o") + loggr.Warning("W a r n i n g") + loggr.Error("E R R O R") + tmplData := SamplePageData() return c.Response.Template("custom_templates_functions.html", tmplData) diff --git a/main.go b/main.go index 5fac06e..dc5750b 100644 --- a/main.go +++ b/main.go @@ -58,6 +58,19 @@ func main() { app.SetGormConfig(config.GetGormConfig()) app.SetCacheConfig(config.GetCacheConfig()) app.Bootstrap() + // Set log level from environment variable (debug, info, warning, error) + if levelStr := env.GetVar("LOG_LEVEL"); levelStr != "" { + switch levelStr { + case "debug": + logger.ResolveLogger().SetLevel(logger.DEBUG) + case "info": + logger.ResolveLogger().SetLevel(logger.INFO) + case "warning": + logger.ResolveLogger().SetLevel(logger.WARNING) + case "error": + logger.ResolveLogger().SetLevel(logger.ERROR) + } + } app.RegisterTemplates(resources) registerGlobalHooks() registerRoutes() From d80be2713facdba636a4355901233321c3bd8b32 Mon Sep 17 00:00:00 2001 From: Zeni Kim Date: Wed, 6 May 2026 21:10:06 -0500 Subject: [PATCH 19/21] empty database --- storage/sqlite/sqlite.db | Bin 90112 -> 4096 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/storage/sqlite/sqlite.db b/storage/sqlite/sqlite.db index 97751beb57aa829a9420e53efc55e7c533b572c2..57a82d2db1a7536fb820719d2dbb8ad2ea724ed1 100644 GIT binary patch delta 36 qcmZoTz}lcNL7J73fq{W>qJjRz2?E@V40_3oyg&g4fz5&n)A#|A!Up{S literal 90112 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 Date: Wed, 6 May 2026 21:21:57 -0500 Subject: [PATCH 20/21] clean samples --- controllers/queuesample.go | 52 - controllers/sample.go | 149 --- controllers/themedemo.go | 1001 ----------------- routes.go | 28 +- storage/templates/app.html | 17 - storage/templates/appsession.html | 12 - .../templates/custom_templates_functions.html | 471 -------- storage/templates/custom_theme_base.html | 10 - .../templates/custom_theme_contentpage.html | 30 - storage/templates/custom_theme_elements.html | 66 -- storage/templates/custom_theme_formpage.html | 23 - 11 files changed, 2 insertions(+), 1857 deletions(-) delete mode 100644 controllers/queuesample.go delete mode 100644 controllers/sample.go delete mode 100644 controllers/themedemo.go delete mode 100644 storage/templates/app.html delete mode 100644 storage/templates/appsession.html delete mode 100644 storage/templates/custom_templates_functions.html delete mode 100644 storage/templates/custom_theme_base.html delete mode 100644 storage/templates/custom_theme_contentpage.html delete mode 100644 storage/templates/custom_theme_elements.html delete mode 100644 storage/templates/custom_theme_formpage.html diff --git a/controllers/queuesample.go b/controllers/queuesample.go deleted file mode 100644 index d289aee..0000000 --- a/controllers/queuesample.go +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) 2025 Zeni Kim -// Use of this source code is governed by MIT-style -// license that can be found in the LICENSE file. - -package controllers - -import ( - "encoding/json" - "log" - "time" - - "git.smarteching.com/goffee/core" - "git.smarteching.com/goffee/cup/workers" - "github.com/hibiken/asynq" -) - -// Make samples queues -func Queuesample(c *core.Context) *core.Response { - - // Get client queue asynq - client := c.GetQueueClient() - - // Create a task with typename and payload. - payload, err := json.Marshal(workers.EmailTaskPayload{UserID: 42}) - if err != nil { - log.Fatal(err) - } - - t1 := asynq.NewTask(workers.TypeWelcomeEmail, payload) - - t2 := asynq.NewTask(workers.TypeReminderEmail, payload) - - // Process the task immediately. - info, err := client.Enqueue(t1) - if err != nil { - log.Fatal(err) - } - log.Printf(" [*] Successfully enqueued task: %+v", info) - - // Process 2 task 1 min later. - for i := 1; i < 3; i++ { - info, err = client.Enqueue(t2, asynq.ProcessIn(1*time.Minute)) - if err != nil { - log.Fatal(err) - } - log.Printf(" [*] Successfully enqueued task: %+v", info) - } - - message := "{\"message\": \"Task queued\"}" - return c.Response.Json(message) - -} diff --git a/controllers/sample.go b/controllers/sample.go deleted file mode 100644 index 9f5687b..0000000 --- a/controllers/sample.go +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright (c) 2024 Zeni Kim -// Use of this source code is governed by MIT-style -// license that can be found in the LICENSE file. - -package controllers - -import ( - "fmt" - - "git.smarteching.com/goffee/core" - "git.smarteching.com/goffee/core/template/components" - "git.smarteching.com/goffee/cup/utils" -) - -// Show basic template -func Sample(c *core.Context) *core.Response { - - // first, include all compoments - type templateData struct { - PageCard components.PageCard - } - - // now fill data of the components - tmplData := templateData{ - PageCard: components.PageCard{ - CardTitle: "Framework Goffee", - CardBody: "Powered by Golang", - }, - } - - return c.Response.Template("basic.html", tmplData) - -} - -// Show basic app login -func AppLogin(c *core.Context) *core.Response { - - // first, include all compoments - // first, include all compoments - type templateData struct { - PageCard components.PageCard - } - - // now fill data of the components - tmplData := templateData{ - PageCard: components.PageCard{ - CardTitle: "Card title", - CardBody: "Loerm ipsum at deim", - }, - } - return c.Response.Template("login.html", tmplData) -} - -// Show basic app login -func AppSession(c *core.Context) *core.Response { - - var session = new(utils.SessionUser) - - // true if session is active - hassession := session.Init(c) - - //session.Set("numberdos", 66) - - type templateData struct { - PageCard components.PageCard - } - - // now fill data of the components - tmplData := templateData{} - - if hassession { - - sesiondata := "" - cardtitle := fmt.Sprintf("Session user id: %v", session.GetUserID()) - numberdos, ok := session.Get("numberdos") - - if numberdos != nil { - numberdos = numberdos.(float64) + 10 - } else { - numberdos = 10 - } - - session.Set("numberdos", numberdos) - - if ok { - sesiondata = fmt.Sprintf("OK, Session numberdos has %v", numberdos) - } else { - sesiondata = fmt.Sprintf("No ok, session numberdos has %v", numberdos) - } - - // delete single - //session.Delete("numberdos") - - // delete all data - //session.Flush() - - tmplData = templateData{ - PageCard: components.PageCard{ - CardTitle: cardtitle, - CardBody: sesiondata, - }, - } - - return c.Response.Template("appsession.html", tmplData) - - } else { - - return c.Response.Template("login.html", tmplData) - - } - -} - -// Show basic app sample -func AppSample(c *core.Context) *core.Response { - - // first, include all compoments - type templateData struct { - PageCard components.PageCard - ContentDropdown components.ContentDropdown - } - - // now fill data of the components - tmplData := templateData{ - PageCard: components.PageCard{ - CardTitle: "Protected page", - CardBody: "If you can see this page, your are loggedin", - }, - ContentDropdown: components.ContentDropdown{ - Label: "dropdown", - Items: []components.ContentDropdownItem{ - { - Text: "Signout", - Link: "#", - ID: "signout", - }, - { - Text: "item disabled", - Link: "#", - IsDisabled: true, - }, - }, - }, - } - //fmt.Printf("Outside cookie user is: %s", user.Email) - - return c.Response.Template("app.html", tmplData) - -} diff --git a/controllers/themedemo.go b/controllers/themedemo.go deleted file mode 100644 index a3e22a3..0000000 --- a/controllers/themedemo.go +++ /dev/null @@ -1,1001 +0,0 @@ -// Copyright (c) 2024 Zeni Kim -// Use of this source code is governed by MIT-style -// license that can be found in the LICENSE file. - -package controllers - -import ( - "fmt" - "math/rand/v2" - "os" - "strconv" - "time" - - "git.smarteching.com/goffee/core" - "git.smarteching.com/goffee/core/template/components" -) - -// Show home page -func Themedemo(c *core.Context) *core.Response { - - // check if template engine is enabled - TemplateEnableStr := os.Getenv("TEMPLATE_ENABLE") - if TemplateEnableStr == "" { - TemplateEnableStr = "false" - } - TemplateEnable, _ := strconv.ParseBool(TemplateEnableStr) - - if TemplateEnable { - - // first, include all compoments - type templateData struct { - PageCard components.PageCard - } - - // now fill data of the components - tmplData := templateData{ - PageCard: components.PageCard{ - CardTitle: "Card title", - CardBody: "Loerm ipsum at deim", - }, - } - - return c.Response.Template("custom_theme_base.html", tmplData) - - } else { - - message := "{\"message\": \"Error, template not enabled\"}" - return c.Response.Json(message) - - } - -} - -// Show form element page -func Themeform(c *core.Context) *core.Response { - - // check if template engine is enabled - TemplateEnableStr := os.Getenv("TEMPLATE_ENABLE") - if TemplateEnableStr == "" { - TemplateEnableStr = "false" - } - TemplateEnable, _ := strconv.ParseBool(TemplateEnableStr) - - if TemplateEnable { - - // first, include all compoments - type templateData struct { - FormText components.FormInput - FormEmail components.FormInput - FormButton components.FormButton - FormSelectCity components.FormSelect - FormTextarea components.FormTextarea - FormRadio components.FormRadio - FormCheckbox components.FormCheckbox - } - - // for select options - var allOptions []components.FormSelectOption - var option components.FormSelectOption - option.Value = "ch" - option.Caption = "China" - allOptions = append(allOptions, option) - option.Value = "ba" - option.Caption = "Buenos Aires" - allOptions = append(allOptions, option) - option.Value = "fr" - option.Caption = "France" - selectedOption := option - allOptions = append(allOptions, option) - - // for radio options - var allOptionsr []components.FormRadioItem - var optionr components.FormRadioItem - optionr.ID = "citysch" - optionr.Name = "citys" - optionr.Value = "china" - optionr.Label = "China" - allOptionsr = append(allOptionsr, optionr) - optionr.ID = "citysba" - optionr.Name = "citys" - optionr.Value = "buenosaires" - optionr.Label = "Buenos Aires" - //optionr.IsDisabled = true - allOptionsr = append(allOptionsr, optionr) - - // for radio options - var allOptionsc []components.FormCheckboxItem - var optionc components.FormCheckboxItem - optionc.ID = "citysch" - optionc.Name = "citys" - optionc.Value = "china" - optionc.Label = "China" - allOptionsc = append(allOptionsc, optionc) - optionc.ID = "citysba" - optionc.Name = "citys" - optionc.Value = "buenosaires" - optionc.Label = "Buenos Aires" - allOptionsc = append(allOptionsc, optionc) - optionc.ID = "london" - optionc.Name = "london" - optionc.Value = "london" - optionc.Label = "London" - //optionc.IsChecked = true - allOptionsc = append(allOptionsc, optionc) - - // now fill data of the components - tmplData := templateData{ - FormText: components.FormInput{ - ID: "text", - Label: "Name", - Type: "text", - Hint: "This is sample hint", - Placeholder: "Enter your name", - }, - FormEmail: components.FormInput{ - ID: "email", - Label: "Email", - Type: "email", - IsRequired: true, - Placeholder: "Enter your email address", - }, - FormButton: components.FormButton{ - Text: "Login", - IsSubmit: true, - TypeClass: "primary", - }, - FormSelectCity: components.FormSelect{ - ID: "city", - Label: "Select city", - AllOptions: allOptions, - SelectedOption: selectedOption, - }, - FormTextarea: components.FormTextarea{ - ID: "text", - Label: "Example textarea", - }, - FormRadio: components.FormRadio{ - Label: "Radio buttons", - AllRadios: allOptionsr, - }, - FormCheckbox: components.FormCheckbox{ - Label: "Checkbox options", - AllCheckbox: allOptionsc, - }, - } - return c.Response.Template("custom_theme_formpage.html", tmplData) - - } else { - - message := "{\"message\": \"Error, template not enabled\"}" - return c.Response.Json(message) - - } - -} - -func ThemeElements(c *core.Context) *core.Response { - // check if template engine is enabled - TemplateEnableStr := os.Getenv("TEMPLATE_ENABLE") - if TemplateEnableStr == "" { - TemplateEnableStr = "false" - } - TemplateEnable, _ := strconv.ParseBool(TemplateEnableStr) - - if TemplateEnable { - type templateData struct { - Buttons []components.FormButton - Hrefs []components.ContentHref - Badges []components.ContentBadge - Dropdowns []components.ContentDropdown - Lists []components.ContentList - Menus []components.PageNav - } - buttons := []components.FormButton{ - { - Text: "primary", - TypeClass: "primary", - }, - { - Text: "secondary", - TypeClass: "secondary", - }, - { - Text: "success", - TypeClass: "success", - }, - { - Text: "danger", - TypeClass: "danger", - }, - { - Text: "warning", - TypeClass: "warning", - }, - { - Text: "info", - TypeClass: "info", - }, - { - Text: "light", - TypeClass: "light", - }, - { - Text: "dark", - TypeClass: "dark", - }, - { - Text: "link", - TypeClass: "link", - }, - { - Text: "disabled", - TypeClass: "primary", - IsDisabled: true, - }, - { - Text: "outline-primary", - TypeClass: "outline-primary", - }, - { - Text: "outline-secondary", - TypeClass: "outline-secondary", - }, - { - Text: "outline-success", - TypeClass: "outline-success", - }, - { - Text: "outline-danger", - TypeClass: "outline-danger", - }, - { - Text: "outline-warning", - TypeClass: "outline-warning", - }, - { - Text: "outline-info", - TypeClass: "outline-info", - }, - { - Text: "outline-light", - TypeClass: "outline-light", - }, - { - Text: "outline-dark", - TypeClass: "outline-dark", - }, - } - hrefs := []components.ContentHref{ - { - Text: "href", - Link: "#", - IsButton: false, - }, - { - Text: "link", - Link: "#", - IsButton: false, - TypeClass: "link", - }, - { - Text: "button", - Link: "#", - IsButton: true, - TypeClass: "primary", - }, - { - Text: "href disabled", - Link: "#", - IsButton: false, - IsDisabled: true, - }, - { - Text: "link disabled", - Link: "#", - TypeClass: "link", - IsDisabled: true, - }, - { - Text: "button disabled", - Link: "#", - IsButton: true, - TypeClass: "primary", - IsDisabled: true, - }, - } - badges := []components.ContentBadge{ - { - Text: "primary", - TypeClass: "primary", - }, - { - Text: "secondary", - TypeClass: "secondary", - }, - { - Text: "success", - TypeClass: "success", - }, - { - Text: "danger", - TypeClass: "danger", - }, - { - Text: "warning", - TypeClass: "warning", - }, - { - Text: "info", - TypeClass: "info", - }, - { - Text: "light", - TypeClass: "light", - }, - { - Text: "dark", - TypeClass: "dark", - }, - { - Text: "outline", - TypeClass: "primary", - IsOutline: true, - }, - { - Text: "outline", - TypeClass: "success", - IsOutline: true, - }, - { - Text: "outline", - TypeClass: "danger", - IsOutline: true, - }, - { - Text: "outline", - TypeClass: "warning", - IsOutline: true, - }, - } - dropdowns := []components.ContentDropdown{ - // dropdown - { - Label: "dropdown", - Items: []components.ContentDropdownItem{ - { - Text: "item ", - Link: "#", - }, - { - Text: "item disabled", - Link: "#", - IsDisabled: true, - }, - }, - }, - // dropdown - { - Label: "primary", - TypeClass: "primary", - Items: []components.ContentDropdownItem{ - { - Text: "item ", - Link: "#", - }, - { - Text: "item ", - Link: "#", - IsActive: true, - }, - { - Text: "item disabled", - Link: "#", - IsDisabled: true, - }, - }, - }, - // dropdown - { - Label: "outline", - TypeClass: "outline-primary", - Items: []components.ContentDropdownItem{ - { - Text: "item ", - Link: "#", - }, - }, - }, - // dropdown - { - Label: "disabled", - TypeClass: "primary", - IsDisabled: true, - // items - }, - } - list := []components.ContentList{ - // basic list - { - Items: []components.ContentListItem{ - { - Text: "item 1", - }, - { - Text: "item 2", - EndElement: "end text", - }, - { - Text: "item disabled", - IsDisabled: true, - }, - }, - }, - // description list - { - Items: []components.ContentListItem{ - { - Text: "item 1", - Description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ", - }, - { - Text: "item 2", - Description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", - }, - { - Text: "item disabled", - Description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", - IsDisabled: true, - }, - }, - }, - // list with class - { - Items: []components.ContentListItem{ - { - Text: "class primary", - TypeClass: "primary", - }, - { - Text: "class success", - TypeClass: "success", - }, - { - Text: "class danger", - TypeClass: "danger", - }, - }, - }, - } - menus := []components.PageNav{ - // nav - { - NavClass: "nav-pills", - NavItems: []components.PageNavItem{ - { - Text: "item active", - Link: "#", - IsActive: true, - }, - { - Text: "item", - Link: "#", - IsActive: false, - }, - { - Text: "item", - Link: "#", - IsActive: false, - ChildItems: []components.PageNavItem{ - { - Text: "item ", - Link: "#", - }, - { - Text: "item disabled", - Link: "#", - IsDisabled: true, - }, - }, - }, - { - Text: "item disabled", - Link: "#", - IsDisabled: true, - }, - }, - }, - // nav - { - NavClass: "", - NavItems: []components.PageNavItem{ - { - Text: "item active", - Link: "#", - IsActive: true, - }, - { - Text: "item", - Link: "#", - IsActive: false, - }, - { - Text: "item", - Link: "#", - IsActive: false, - }, - { - Text: "item disabled", - Link: "#", - IsDisabled: true, - }, - }, - }, - // nav underline - { - NavClass: "nav-underline", - NavItems: []components.PageNavItem{ - { - Text: "item active", - Link: "#", - IsActive: true, - }, - { - Text: "item", - Link: "#", - IsActive: false, - }, - { - Text: "item", - Link: "#", - IsActive: false, - }, - { - Text: "item disabled", - Link: "#", - IsDisabled: true, - }, - }, - }, - // nav tabs - { - NavClass: "", - IsTab: true, - NavItems: []components.PageNavItem{ - { - Text: "tab active", - Link: "#", - IsActive: true, - }, - { - Text: "tab", - Link: "#", - IsActive: false, - }, - { - Text: "tab", - Link: "#", - IsActive: false, - }, - { - Text: "tab disabled", - Link: "#", - IsDisabled: true, - }, - }, - }, - // nav vertical - { - NavClass: "", - IsVertical: true, - NavItems: []components.PageNavItem{ - { - Text: "item active", - Link: "#", - IsActive: true, - }, - { - Text: "item", - Link: "#", - IsActive: false, - }, - { - Text: "item", - Link: "#", - IsActive: false, - }, - { - Text: "item disabled", - Link: "#", - IsDisabled: true, - }, - }, - }, - } - - tmplData := templateData{ - Buttons: buttons, - Hrefs: hrefs, - Badges: badges, - Dropdowns: dropdowns, - Lists: list, - Menus: menus, - } - return c.Response.Template("custom_theme_elements.html", tmplData) - - } else { - - message := "{\"message\": \"Error, template not enabled\"}" - return c.Response.Json(message) - } -} - -// Show form element page -func Themecontent(c *core.Context) *core.Response { - - // check if template engine is enabled - TemplateEnableStr := os.Getenv("TEMPLATE_ENABLE") - if TemplateEnableStr == "" { - TemplateEnableStr = "false" - } - TemplateEnable, _ := strconv.ParseBool(TemplateEnableStr) - - if TemplateEnable { - - // first, include all compoments - type templateData struct { - ContentTable components.ContentTable - ContentTabledetail components.ContentTabledetail - ContentGraph components.ContentGraph - FieldText components.FormInput - FormSelectCityM components.FormSelect - Pagination components.ContentPagination - ShouldShowPagination bool - } - - // for select options - var allOptions []components.FormSelectOption - var option components.FormSelectOption - option.Value = "ch" - option.Caption = "China" - allOptions = append(allOptions, option) - option.Value = "ba" - option.Caption = "Buenos Aires" - allOptions = append(allOptions, option) - option.Value = "fr" - option.Caption = "France" - selectedOption := option - allOptions = append(allOptions, option) - option.Value = "kr" - option.Caption = "Korea" - allOptions = append(allOptions, option) - - // for custom attributes in form - var customAtt []components.CustomAtt - var attribute components.CustomAtt - attribute.AttName = "code" - attribute.AttValue = "five" - customAtt = append(customAtt, attribute) - attribute.AttName = "mytag" - attribute.AttValue = "My value" - customAtt = append(customAtt, attribute) - - // TABLES - // for th head - var allTh []components.ContentTableTH - var th components.ContentTableTH - th.Value = "Column heading 1" - allTh = append(allTh, th) - th.Value = "Column heading 2" - allTh = append(allTh, th) - th.ID = "ba" - th.Value = "Column heading 3" - allTh = append(allTh, th) - th.Value = "Column badge" - th.ValueType = "badge" // column type badge - allTh = append(allTh, th) - th.Value = "Column action" - th.ValueType = "href" // column type href - allTh = append(allTh, th) - - // for td items - var allTd [][]components.ContentTableTD - //var vals []components.ContentTableTD - // rows - for i := 1; i <= 28; i++ { - vals := make([]components.ContentTableTD, len(allTh)) - for b := 0; b < len(allTh)-2; b++ { - vals[b].Value = fmt.Sprintf("%s%d%d", "TD data: ", i, b) - vals[b].ID = fmt.Sprintf("%s%d%d", "idtd_", i, b) - } - // column badge - vals[len(allTh)-2].Value = components.ContentBadge{ - Text: "success", - TypeClass: "success", - } - // last column href - vals[len(allTh)-1].Value = components.ContentHref{ - Text: "edit", - Link: "#", - } - allTd = append(allTd, vals) - } - - // Pagination demo - // start config - limit := 10 - shouldShowPagination := false - pageViewTableOffset := 0 - // Get the length of AllOptions - totalrecords := len(allTd) - // get current table offset - tpage := c.RequestParamExists("tpage") - if tpage { - pageViewTableOffset, _ = strconv.Atoi(c.GetRequestParam("tpage").(string)) - } - - // start default option paginator - var pagination components.ContentPagination - pagination.PageStartRecord = pageViewTableOffset + 1 - pagination.PageEndRecord = 0 - pagination.TotalRecords = 0 - pagination.PrevLink = "" - pagination.NextLink = "" - - // check current page - // fake function to emulate a query offset - newTd := getPaginatedPageViews(allTd, limit, pageViewTableOffset) - - if len(newTd) > 0 { - pagination.TotalRecords = totalrecords - pagination.PageStartRecord = pageViewTableOffset + 1 - pagination.PageEndRecord = pageViewTableOffset + len(newTd) - shouldShowPagination = totalrecords > limit - } - - if shouldShowPagination && pageViewTableOffset != 0 { - pagination.PrevLink = fmt.Sprintf("/themecontent?tpage=%d", pageViewTableOffset-limit) - } - - if shouldShowPagination && pageViewTableOffset+limit < totalrecords { - pagination.NextLink = fmt.Sprintf("/themecontent?tpage=%d", pageViewTableOffset+limit) - } - - // for td items in table detail - var allTdetail []components.ContentTabledetailTD - // table detail - var thd components.ContentTabledetailTD - thd.Caption = "Continent" - thd.Value = "Asia" - allTdetail = append(allTdetail, thd) - thd.Caption = "Country" - thd.Value = "South Korea" - allTdetail = append(allTdetail, thd) - thd.Caption = "Capital" - thd.Value = "Seoul" - allTdetail = append(allTdetail, thd) - thd.Caption = "Details" - thd.ValueType = "href" // column type href - thd.Value = components.ContentHref{ - Text: "edit", - Link: "#", - } - allTdetail = append(allTdetail, thd) - thd.Caption = "Notifications" - thd.ValueType = "badge" // column type href - thd.Value = components.ContentBadge{ - Text: "success", - TypeClass: "success", - } - allTdetail = append(allTdetail, thd) - - // random values for pie - one := rand.IntN(50) - two := rand.IntN(50) - three := rand.IntN(50) - valuesgraph := fmt.Sprintf("%d|%d|%d", one, two, three) - - // now fill data of the components - tmplData := templateData{ - FormSelectCityM: components.FormSelect{ - ID: "city", - Label: "Select city", - AllOptions: allOptions, - SelectedOption: selectedOption, - IsMultiple: true, - }, - FieldText: components.FormInput{ - ID: "text", - Label: "Name", - Type: "text", - Hint: "This is sample hint", - Placeholder: "Enter your name", - CustomAtt: customAtt, - }, - ContentTable: components.ContentTable{ - ID: "table_demo", - AllTH: allTh, - AllTD: newTd, - }, - ContentTabledetail: components.ContentTabledetail{ - ID: "table_demodetail", - Title: "Sample table detail", - HeadClass: "table-warning", - AllTD: allTdetail, - }, - ContentGraph: components.ContentGraph{ - Graph: "pie", - Labels: "Berlin|Paris|Venecia", - Values: valuesgraph, - }, - Pagination: pagination, - ShouldShowPagination: shouldShowPagination, - } - - return c.Response.Template("custom_theme_contentpage.html", tmplData) - - } else { - - message := "{\"message\": \"Error, template not enabled\"}" - return c.Response.Json(message) - - } - -} - -func getPaginatedPageViews(values [][]components.ContentTableTD, limit int, offset int) [][]components.ContentTableTD { - - // Validate the offset and adjust if necessary - if offset < 0 { - offset = 0 // Ensure offset is not negative - } else if offset >= len(values) { - var emptytd [][]components.ContentTableTD - return emptytd - } - - // Calculate the end index (limit the slice to the size of the array) - end := offset + limit - if end > len(values) { - end = len(values) // Ensure end doesn't exceed the length of the array - } - - return values[offset:end] // Slice the array - -} - -// Custom Templates functions -func TemplatesFunctions(c *core.Context) *core.Response { - - // check if template engine is enabled - TemplateEnableStr := os.Getenv("TEMPLATE_ENABLE") - if TemplateEnableStr == "" { - TemplateEnableStr = "false" - } - TemplateEnable, _ := strconv.ParseBool(TemplateEnableStr) - - if TemplateEnable { - loggr := c.GetLogger() - - loggr.Debug("D e b u g") - loggr.Info("I n f o") - loggr.Warning("W a r n i n g") - loggr.Error("E R R O R") - - - tmplData := SamplePageData() - return c.Response.Template("custom_templates_functions.html", tmplData) - - } else { - - message := "{\"message\": \"Error, template not enabled\"}" - return c.Response.Json(message) - - } - -} - -// Author represents the writer of an article to show the custom functions -type Author struct { - Name string - Avatar string - Bio string -} - -// Article represents a blog post or news entry used to test template helpers to show the custom functions -type Article struct { - Title string - Slug string - Excerpt string - Body string - Tags []string - PublishedAt time.Time - UpdatedAt time.Time - Author Author - Views int - Price float64 - Featured bool - Subtitle string // intentionally left empty on some entries to test coalesce/defaultVal -} - -// PageData is the top-level context passed to the HTML template to show the custom functions -type PageData struct { - SiteTitle string - Articles []Article -} - -// SamplePageData returns a populated PageData ready to be rendered by the template. -func SamplePageData() PageData { - return PageData{ - SiteTitle: "go/template lab", - Articles: []Article{ - { - Title: "getting started with go templates", - Slug: "getting-started-go-templates", - Excerpt: "Go's html/template package is both powerful and safe by default. In this article we explore how to extend it with custom FuncMap helpers that bring it closer to the expressiveness of Liquid or Twig, without sacrificing any of the security guarantees.", - Tags: []string{"go", "templates", "web", "backend"}, - PublishedAt: time.Now().Add(-3 * 24 * time.Hour), - UpdatedAt: time.Now().Add(-1 * 24 * time.Hour), - Views: 14200, - Price: 0, - Featured: true, - Subtitle: "", - Author: Author{ - Name: "marina voss", - Avatar: "MV", - Bio: "Senior backend engineer focused on developer tooling and observability.", - }, - }, - { - Title: "building a blog engine in go", - Slug: "blog-engine-go", - Excerpt: "We walk through building a minimal but complete blog engine using only the Go standard library: routing with net/http, persistence with database/sql, and rendering with html/template.", - Tags: []string{"go", "blog", "sqlite"}, - PublishedAt: time.Now().Add(-10 * 24 * time.Hour), - UpdatedAt: time.Now().Add(-10 * 24 * time.Hour), - Views: 8750, - Price: 9.99, - Featured: false, - Subtitle: "A zero-dependency approach", - Author: Author{ - Name: "rafael okonkwo", - Avatar: "RO", - Bio: "Full-stack engineer and open-source contributor. Writes about Go and distributed systems.", - }, - }, - { - Title: "concurrency patterns you should know", - Slug: "concurrency-patterns-go", - Excerpt: "Goroutines are cheap, but misusing them is expensive. This deep-dive covers fan-out/fan-in, pipelines, semaphores, and error group patterns with real production examples.", - Tags: []string{"go", "concurrency", "goroutines", "advanced"}, - PublishedAt: time.Now().Add(-45 * 24 * time.Hour), - UpdatedAt: time.Now().Add(-40 * 24 * time.Hour), - Views: 31400, - Price: 14.99, - Featured: true, - Subtitle: "", - Author: Author{ - Name: "selin çelik", - Avatar: "SÇ", - Bio: "Systems programmer. Previously at Cloudflare. Loves writing about the internals of things.", - }, - }, - { - Title: "understanding go interfaces", - Slug: "understanding-go-interfaces", - Excerpt: "Interfaces in Go are implicit and structural, which makes them both elegant and occasionally surprising. We look at how the runtime dispatches method calls and how to design composable interfaces.", - Tags: []string{"go", "interfaces", "design"}, - PublishedAt: time.Now().Add(-2 * time.Hour), - UpdatedAt: time.Now().Add(-1 * time.Hour), - Views: 420, - Price: 0, - Featured: false, - Subtitle: "Implicit, structural, and powerful", - Author: Author{ - Name: "marina voss", - Avatar: "MV", - Bio: "Senior backend engineer focused on developer tooling and observability.", - }, - }, - }, - } -} \ No newline at end of file diff --git a/routes.go b/routes.go index dc1799c..193da2d 100644 --- a/routes.go +++ b/routes.go @@ -8,7 +8,6 @@ package main import ( "git.smarteching.com/goffee/core" "git.smarteching.com/goffee/cup/controllers" - "git.smarteching.com/goffee/cup/hooks" ) // Register the app controllers @@ -20,15 +19,8 @@ func registerRoutes() { // Define your routes here... controller.Get("/", controllers.WelcomeHome) - // Uncomment the lines below to enable theme demo - controller.Get("/themebase", controllers.Themedemo) - controller.Get("/themeform", controllers.Themeform) - controller.Get("/themecontent", controllers.Themecontent) - controller.Get("/themepanel", controllers.Themedemo) - controller.Get("/themeelements", controllers.ThemeElements) - controller.Get("/queuesample", controllers.Queuesample) - - // Uncomment the lines below to enable authentication + + // Uncomment the lines below to enable authentication API controller.Post("/signup", controllers.Signup) controller.Post("/signin", controllers.Signin) controller.Post("/signout", controllers.Signout) @@ -44,22 +36,6 @@ func registerRoutes() { controller.Post("/admin/users/edit/:id", controllers.AdminUsersEdit) controller.Post("/admin/users/delete", controllers.AdminUsersDelete) controller.Post("/admin/users/deleteconfirm", controllers.AdminUsersDelConfirm) - //controller.Get("/admin/users/roles", controllers.Signout) - //controller.Get("/admin/users/permissions", controllers.ResetPasswordRequest) - controller.Get("/dashboard", controllers.WelcomeToDashboard, hooks.AuthCheck) - // templates demos - controller.Get("/signout", controllers.Signout) - - controller.Get("/appsample", controllers.AppSample, hooks.AuthCheck) - controller.Post("/appsample", controllers.AppSample, hooks.AuthCheck) - - controller.Get("/applogin", controllers.AppLogin, hooks.CheckSessionCookie) - controller.Post("/applogin", controllers.AppLogin, hooks.CheckSessionCookie) - - controller.Get("/appsession", controllers.AppSession) - controller.Post("/appsession", controllers.AppSession) - - controller.Get("/templatesfunc", controllers.TemplatesFunctions) } diff --git a/storage/templates/app.html b/storage/templates/app.html deleted file mode 100644 index 2bc5f93..0000000 --- a/storage/templates/app.html +++ /dev/null @@ -1,17 +0,0 @@ - - - {{template "page_head" "Sample page"}} - -
-
- {{template "content_dropdown" .ContentDropdown}} - {{template "page_card" .PageCard}} - {{ define "page_card_content" }} - - - {{ end }} -
-
- {{template "page_footer"}} - - \ No newline at end of file diff --git a/storage/templates/appsession.html b/storage/templates/appsession.html deleted file mode 100644 index 32364f0..0000000 --- a/storage/templates/appsession.html +++ /dev/null @@ -1,12 +0,0 @@ - - - {{template "page_head" "Sample page test session vars"}} - -
-
- {{template "page_card" .PageCard}} -
-
- {{template "page_footer"}} - - \ No newline at end of file diff --git a/storage/templates/custom_templates_functions.html b/storage/templates/custom_templates_functions.html deleted file mode 100644 index 6e0c357..0000000 --- a/storage/templates/custom_templates_functions.html +++ /dev/null @@ -1,471 +0,0 @@ - - - - - - {{.SiteTitle}} — template lab - - - - - - - -
-
-

{{.SiteTitle}}

-

Template helpers
in action

-
-

22 helpers registered
across 6 groups

-
- - - - - -{{$first := first .Articles}} - -
- -
-

capitalize capitalize

-

{{capitalize $first.Title}}

-

input: "{{$first.Title}}"

-
- -
-

truncate truncate

-

{{$first.Excerpt | truncate 80}}

-

capped at 80 chars

-
- -
-

prepend prepend

-

{{prepend $first.Slug "/articles/"}}

-

prepended "/articles/" to slug

-
- -
-

strAppend strAppend

-

{{strAppend $first.Slug ".html"}}

-

appended ".html" to slug

-
- -
-

split → join split join

- {{$parts := split "go,templates,funcmap" ","}} -

{{join $parts " · "}}

-

split on "," then joined with " · "

-
- -
- - - - - -
- -
-

fmtNumber — int fmtNumber

-

{{fmtNumber $first.Views}}

-

raw value: {{$first.Views}}

-
- - {{$paid := index .Articles 2}} -
-

fmtNumber — float fmtNumber

-

$ {{fmtNumber $paid.Price}}

-

raw value: {{$paid.Price}}

-
- -
-

fmtDate "short" fmtDate

-

{{fmtDate $first.PublishedAt "short"}}

-

layout: "02 Jan 2006"

-
- -
-

fmtDate "long" fmtDate

-

{{fmtDate $first.PublishedAt "long"}}

-

layout: "02 January 2006"

-
- -
-

fmtDate "iso" fmtDate

-

{{fmtDate $first.PublishedAt "iso"}}

-

layout: "2006-01-02"

-
- -
-

timeAgo timeAgo

- {{range .Articles}} -

{{timeAgo .PublishedAt}} — {{fmtDate .PublishedAt "short"}}

- {{end}} -
- -
- - - - - -
- -
-

first first

- {{with first .Articles}} -

{{capitalize .Title}}

-

first article in the list

- {{end}} -
- -
-

last last

- {{with last .Articles}} -

{{capitalize .Title}}

-

last article in the list

- {{end}} -
- -
-

sliceOf 0–2 sliceOf

- {{range sliceOf .Articles 0 2}} -

— {{capitalize .Title}}

- {{end}} -

only first 2 of {{len .Articles}} articles

-
- -
-

contains — slice contains

- {{if contains $first.Tags "go"}} -

✓ has tag "go"

- {{else}} -

✗ missing tag "go"

- {{end}} -

tags: {{join $first.Tags ", "}}

-
- -
-

contains — string contains

- {{if contains $first.Excerpt "FuncMap"}} -

✓ excerpt mentions "FuncMap"

- {{else}} -

✗ not found

- {{end}} -

substring search on .Excerpt

-
- -
-

join join

-

{{join $first.Tags " / "}}

-

tags joined with " / "

-
- -
- - - - - - - - - - - - - - - - {{range .Articles}} - - - - - - - - - - - - - - - - - - - {{end}} - -
HelperArticleInputOutput
defaultVal{{.Title | capitalize | truncate 30}}.Subtitle ({{if .Subtitle}}"{{.Subtitle}}"{{else}}empty{{end}}){{defaultVal "No subtitle" .Subtitle}}
ternary{{capitalize .Title | truncate 30}}.Featured = {{.Featured}}{{ternary "⭐ featured" "regular" .Featured}}
coalesce{{capitalize .Title | truncate 30}}.Subtitle → .Title{{coalesce .Subtitle .Title}}
- - - - - -
- {{range .Articles}} -
-
-
- {{if .Featured}}featured{{end}} - {{range .Tags}}{{.}}{{end}} -
- -

{{capitalize .Title}}

- - {{with coalesce .Subtitle ""}} -

{{.}}

- {{end}} - -

{{.Excerpt | truncate 160}}

- - - {{prepend .Slug "/articles/"}} → - - -
-
{{.Author.Avatar}}
-
-
{{capitalize .Author.Name}}
- -
-
-
- -
-
- {{fmtNumber .Views}} - views -
- {{if gt .Price 0.0}} - $ {{fmtNumber .Price}} - {{else}} - free - {{end}} -
-
- {{end}} -
- - - -
- {{.SiteTitle}} / template sandbox - {{len .Articles}} articles · {{fmtDate (first .Articles).PublishedAt "iso"}} → {{fmtDate (last .Articles).PublishedAt "iso"}} -
- - - \ No newline at end of file diff --git a/storage/templates/custom_theme_base.html b/storage/templates/custom_theme_base.html deleted file mode 100644 index 9c28a1e..0000000 --- a/storage/templates/custom_theme_base.html +++ /dev/null @@ -1,10 +0,0 @@ - - - {{template "page_head" "Goffee"}} - -
- {{template "page_card" .PageCard}} -
- {{template "page_footer"}} - - \ No newline at end of file diff --git a/storage/templates/custom_theme_contentpage.html b/storage/templates/custom_theme_contentpage.html deleted file mode 100644 index 422c253..0000000 --- a/storage/templates/custom_theme_contentpage.html +++ /dev/null @@ -1,30 +0,0 @@ - - - {{template "page_head" "Goffee"}} - -
-
- Content demos -
- {{template "content_table" .ContentTable}} - {{if .ShouldShowPagination}} - {{template "content_pagination" .Pagination}} - {{end}} -
-
-
-

Pie chart

- {{template "content_graph" .ContentGraph}} -
- {{template "form_input" .FieldText}} - {{template "form_select" .FormSelectCityM}} -
-
- {{template "content_tabledetail" .ContentTabledetail}} -
-
-
-
- {{template "page_footer"}} - - diff --git a/storage/templates/custom_theme_elements.html b/storage/templates/custom_theme_elements.html deleted file mode 100644 index fbe2d52..0000000 --- a/storage/templates/custom_theme_elements.html +++ /dev/null @@ -1,66 +0,0 @@ - - - {{template "page_head" "Goffee"}} - -
-
- Demos buttons -
- {{range .Buttons}} - {{template "form_button" .}} - {{end}} -
-
- -
- Demos href -
- {{range .Hrefs}} - {{template "content_href" .}} - {{end}} -
-
- -
- Demos Badges -
- {{range .Badges}} - {{template "content_badge" .}} - {{end}} -
-
- - -
- Demos dropdown -
- {{range .Dropdowns}} - {{template "content_dropdown" .}} - {{end}} -
-
- - -
- Demos List -
- {{range .Lists}} - {{template "content_list" .}} - {{end}} -
-
- -
- Demos nav -
- {{range .Menus}} -
- {{template "page_nav" .}} -
- {{end}} -
-
-
- {{template "page_footer"}} - - diff --git a/storage/templates/custom_theme_formpage.html b/storage/templates/custom_theme_formpage.html deleted file mode 100644 index f45a558..0000000 --- a/storage/templates/custom_theme_formpage.html +++ /dev/null @@ -1,23 +0,0 @@ - - - {{template "page_head" "Goffee"}} - -
- -
- form demos -
- {{template "form_input" .FormText}} - {{template "form_input" .FormEmail}} - {{template "form_select" .FormSelectCity}} - {{template "form_textarea" .FormTextarea}} - {{template "form_radio" .FormRadio}} - {{template "form_checkbox" .FormCheckbox}} -
-
- {{template "form_button" .FormButton}} - -
- {{template "page_footer"}} - - From 5af4371961a33a936a84c18691d096c12ec4090d Mon Sep 17 00:00:00 2001 From: Zeni Kim Date: Thu, 7 May 2026 23:24:02 -0500 Subject: [PATCH 21/21] remove set level --- main.go | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/main.go b/main.go index dc5750b..5fac06e 100644 --- a/main.go +++ b/main.go @@ -58,19 +58,6 @@ func main() { app.SetGormConfig(config.GetGormConfig()) app.SetCacheConfig(config.GetCacheConfig()) app.Bootstrap() - // Set log level from environment variable (debug, info, warning, error) - if levelStr := env.GetVar("LOG_LEVEL"); levelStr != "" { - switch levelStr { - case "debug": - logger.ResolveLogger().SetLevel(logger.DEBUG) - case "info": - logger.ResolveLogger().SetLevel(logger.INFO) - case "warning": - logger.ResolveLogger().SetLevel(logger.WARNING) - case "error": - logger.ResolveLogger().SetLevel(logger.ERROR) - } - } app.RegisterTemplates(resources) registerGlobalHooks() registerRoutes()