Compare commits
No commits in common. "main" and "main" have entirely different histories.
37 changed files with 1337 additions and 1042 deletions
8
.env-dev
8
.env-dev
|
|
@ -8,28 +8,24 @@ App_HTTP_HOST=localhost
|
||||||
App_HTTP_PORT=8080
|
App_HTTP_PORT=8080
|
||||||
App_USE_HTTPS=false
|
App_USE_HTTPS=false
|
||||||
App_USE_LETSENCRYPT=false
|
App_USE_LETSENCRYPT=false
|
||||||
App_USE_CORESERVICES=true
|
App_USE_CORESERVICES=false
|
||||||
APP_LETSENCRYPT_EMAIL=mail@example.com
|
APP_LETSENCRYPT_EMAIL=mail@example.com
|
||||||
App_HTTPS_HOSTS=example.com, www.example.com
|
App_HTTPS_HOSTS=example.com, www.example.com
|
||||||
App_REDIRECT_HTTP_TO_HTTPS=false
|
App_REDIRECT_HTTP_TO_HTTPS=false
|
||||||
App_CERT_FILE_PATH=tls/server.crt
|
App_CERT_FILE_PATH=tls/server.crt
|
||||||
App_KEY_FILE_PATH=tls/server.key
|
App_KEY_FILE_PATH=tls/server.key
|
||||||
LOG_STDOUT_ENABLE=true
|
|
||||||
LOG_LEVEL=debug
|
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
###### TEMPLATES ######
|
###### TEMPLATES ######
|
||||||
#######################################
|
#######################################
|
||||||
TEMPLATE_ENABLE=true
|
TEMPLATE_ENABLE=true
|
||||||
APP_ENABLE=true
|
|
||||||
CDNEnable=false
|
|
||||||
COOKIE_SECRET=13d6b4dff8f84a10851021ec8608f814570d562c92fe6b5ec4c9f595bcb3234b
|
COOKIE_SECRET=13d6b4dff8f84a10851021ec8608f814570d562c92fe6b5ec4c9f595bcb3234b
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
###### JWT ######
|
###### JWT ######
|
||||||
#######################################
|
#######################################
|
||||||
JWT_SECRET=dkfTgonmgaAdlgkw
|
JWT_SECRET=dkfTgonmgaAdlgkw
|
||||||
JWT_LIFESPAN_MINUTES=1440 # expires after 1 day
|
JWT_LIFESPAN_MINUTES=4320 # expires after 3 days
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
###### DATABASE ######
|
###### DATABASE ######
|
||||||
|
|
|
||||||
|
|
@ -20,15 +20,12 @@ App_KEY_FILE_PATH=tls/server.key
|
||||||
#######################################
|
#######################################
|
||||||
TEMPLATE_ENABLE=true
|
TEMPLATE_ENABLE=true
|
||||||
COOKIE_SECRET=13d6b4dff8f84a10851021ec8608f814570d562c92fe6b5ec4c9f595bcb3234b
|
COOKIE_SECRET=13d6b4dff8f84a10851021ec8608f814570d562c92fe6b5ec4c9f595bcb3234b
|
||||||
APP_ENABLE=true
|
|
||||||
CDNEnable=false
|
|
||||||
|
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
###### JWT ######
|
###### JWT ######
|
||||||
#######################################
|
#######################################
|
||||||
JWT_SECRET=dkfTgonmgaAdlgkw
|
JWT_SECRET=dkfTgonmgaAdlgkw
|
||||||
JWT_LIFESPAN_MINUTES=1440 # expires after 1 day
|
JWT_LIFESPAN_MINUTES=4320 # expires after 3 days
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
###### DATABASE ######
|
###### DATABASE ######
|
||||||
|
|
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -7,5 +7,3 @@ tls/*
|
||||||
!tls/.gitkeep
|
!tls/.gitkeep
|
||||||
.DS_Store
|
.DS_Store
|
||||||
storage/sqlite/*
|
storage/sqlite/*
|
||||||
.idea
|
|
||||||
cup
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||

|

|
||||||
# Cup of Goffee
|
# Cup of Goffee
|
||||||
|
|
||||||
## What is Goffee?
|
## What is Goffee?
|
||||||
|
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
// Copyright (c) 2025 Zeni Kim <zenik@smarteching.com>
|
|
||||||
// Use of this source code is governed by MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package 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,
|
|
||||||
|
|
||||||
// Number of concurrent workers processing tasks
|
|
||||||
Concurrency: 10,
|
|
||||||
|
|
||||||
// Queue names with priority weights (higher number = higher priority)
|
|
||||||
Queues: map[string]int{
|
|
||||||
"critical": 6,
|
|
||||||
"default": 3,
|
|
||||||
"low": 1,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -19,29 +19,6 @@ import (
|
||||||
|
|
||||||
func AdminUsersList(c *core.Context) *core.Response {
|
func AdminUsersList(c *core.Context) *core.Response {
|
||||||
|
|
||||||
// initiate authority
|
|
||||||
auth := new(utils.Authority)
|
|
||||||
session := c.GetSession()
|
|
||||||
// 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
|
var users []models.User
|
||||||
db := c.GetGorm()
|
db := c.GetGorm()
|
||||||
db.Find(&users)
|
db.Find(&users)
|
||||||
|
|
@ -69,10 +46,6 @@ func AdminUsersList(c *core.Context) *core.Response {
|
||||||
Value: "Email",
|
Value: "Email",
|
||||||
ValueType: "string",
|
ValueType: "string",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Value: "Roles",
|
|
||||||
ValueType: "string",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Value: "Created",
|
Value: "Created",
|
||||||
},
|
},
|
||||||
|
|
@ -84,26 +57,13 @@ func AdminUsersList(c *core.Context) *core.Response {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var listroles string
|
|
||||||
rows := make([][]components.ContentTableTD, len(users))
|
rows := make([][]components.ContentTableTD, len(users))
|
||||||
for i, u := range 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{
|
row := []components.ContentTableTD{
|
||||||
{Value: strconv.Itoa(int(u.ID))},
|
{Value: strconv.Itoa(int(u.ID))},
|
||||||
{Value: u.Name},
|
{Value: u.Name},
|
||||||
{Value: u.Fullname},
|
{Value: u.Fullname},
|
||||||
{Value: u.Email},
|
{Value: u.Email},
|
||||||
{Value: listroles},
|
|
||||||
{Value: utils.FormatUnix(u.Created)},
|
{Value: utils.FormatUnix(u.Created)},
|
||||||
{Value: utils.FormatUnix(u.Updated)},
|
{Value: utils.FormatUnix(u.Updated)},
|
||||||
{Value: components.ContentHref{
|
{Value: components.ContentHref{
|
||||||
|
|
@ -133,44 +93,10 @@ func AdminUsersList(c *core.Context) *core.Response {
|
||||||
|
|
||||||
func AdminUsersAdd(c *core.Context) *core.Response {
|
func AdminUsersAdd(c *core.Context) *core.Response {
|
||||||
|
|
||||||
// initiate authority
|
|
||||||
auth := new(utils.Authority)
|
|
||||||
session := c.GetSession()
|
|
||||||
// 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
|
// check if is submit
|
||||||
submit := c.GetRequestParam("submit").(string)
|
submit := c.GetRequestParam("submit").(string)
|
||||||
errormessages := make([]string, 0)
|
|
||||||
var listroles []components.FormCheckboxItem
|
|
||||||
systemroles, _ := auth.GetAllRoles(c)
|
|
||||||
|
|
||||||
for _, systemrole := range systemroles {
|
errormessages := make([]string, 0)
|
||||||
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 := ""
|
name := ""
|
||||||
fullname := ""
|
fullname := ""
|
||||||
|
|
@ -183,7 +109,6 @@ func AdminUsersAdd(c *core.Context) *core.Response {
|
||||||
fullname = c.GetRequestParam("fullname").(string)
|
fullname = c.GetRequestParam("fullname").(string)
|
||||||
email = c.GetRequestParam("email").(string)
|
email = c.GetRequestParam("email").(string)
|
||||||
password = c.GetRequestParam("password").(string)
|
password = c.GetRequestParam("password").(string)
|
||||||
roles := c.GetRequesForm("roles").([]string)
|
|
||||||
|
|
||||||
// check if email exists
|
// check if email exists
|
||||||
var user models.User
|
var user models.User
|
||||||
|
|
@ -236,11 +161,6 @@ func AdminUsersAdd(c *core.Context) *core.Response {
|
||||||
errormessages = append(errormessages, res.Error.Error())
|
errormessages = append(errormessages, res.Error.Error())
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// assign roles
|
|
||||||
for _, role := range roles {
|
|
||||||
auth.AssignRoleToUser(c, user.ID, role)
|
|
||||||
}
|
|
||||||
|
|
||||||
// fire user registered event
|
// fire user registered event
|
||||||
err := c.GetEventsManager().Fire(&core.Event{Name: events.USER_REGISTERED, Payload: map[string]interface{}{
|
err := c.GetEventsManager().Fire(&core.Event{Name: events.USER_REGISTERED, Payload: map[string]interface{}{
|
||||||
"user": user,
|
"user": user,
|
||||||
|
|
@ -260,7 +180,6 @@ func AdminUsersAdd(c *core.Context) *core.Response {
|
||||||
FieldName components.FormInput
|
FieldName components.FormInput
|
||||||
FieldFullname components.FormInput
|
FieldFullname components.FormInput
|
||||||
FieldEmail components.FormInput
|
FieldEmail components.FormInput
|
||||||
FieldRoles components.FormCheckbox
|
|
||||||
FieldPassword components.FormInput
|
FieldPassword components.FormInput
|
||||||
ErrorMessages []string
|
ErrorMessages []string
|
||||||
SubmitButton components.FormButton
|
SubmitButton components.FormButton
|
||||||
|
|
@ -289,10 +208,6 @@ func AdminUsersAdd(c *core.Context) *core.Response {
|
||||||
//Autocomplete: true,
|
//Autocomplete: true,
|
||||||
IsRequired: true,
|
IsRequired: true,
|
||||||
},
|
},
|
||||||
FieldRoles: components.FormCheckbox{
|
|
||||||
Label: "Roles",
|
|
||||||
AllCheckbox: listroles,
|
|
||||||
},
|
|
||||||
FieldPassword: components.FormInput{
|
FieldPassword: components.FormInput{
|
||||||
ID: "password",
|
ID: "password",
|
||||||
Label: "Password",
|
Label: "Password",
|
||||||
|
|
@ -321,29 +236,6 @@ func AdminUsersEdit(c *core.Context) *core.Response {
|
||||||
user_id := c.GetPathParam("id")
|
user_id := c.GetPathParam("id")
|
||||||
|
|
||||||
errormessages := make([]string, 0)
|
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
|
var origin_user models.User
|
||||||
|
|
||||||
|
|
@ -367,7 +259,6 @@ func AdminUsersEdit(c *core.Context) *core.Response {
|
||||||
fullname = c.GetRequestParam("fullname").(string)
|
fullname = c.GetRequestParam("fullname").(string)
|
||||||
email = c.GetRequestParam("email").(string)
|
email = c.GetRequestParam("email").(string)
|
||||||
password = c.GetRequestParam("password").(string)
|
password = c.GetRequestParam("password").(string)
|
||||||
roles := c.GetRequesForm("roles").([]string)
|
|
||||||
key := c.GetRequestParam("key")
|
key := c.GetRequestParam("key")
|
||||||
|
|
||||||
// check if email exists
|
// check if email exists
|
||||||
|
|
@ -425,14 +316,6 @@ func AdminUsersEdit(c *core.Context) *core.Response {
|
||||||
c.GetLogger().Error("Admin user: error updating")
|
c.GetLogger().Error("Admin user: error updating")
|
||||||
errormessages = append(errormessages, fmt.Sprintf("Error updating user %s:", user_id_string))
|
errormessages = append(errormessages, fmt.Sprintf("Error updating user %s:", user_id_string))
|
||||||
} else {
|
} 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")
|
return c.Response.Redirect("/admin/users")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -442,7 +325,6 @@ func AdminUsersEdit(c *core.Context) *core.Response {
|
||||||
FieldName components.FormInput
|
FieldName components.FormInput
|
||||||
FieldFullname components.FormInput
|
FieldFullname components.FormInput
|
||||||
FieldEmail components.FormInput
|
FieldEmail components.FormInput
|
||||||
FieldRoles components.FormCheckbox
|
|
||||||
FieldPassword components.FormInput
|
FieldPassword components.FormInput
|
||||||
FieldKey components.FormInput
|
FieldKey components.FormInput
|
||||||
ErrorMessages []string
|
ErrorMessages []string
|
||||||
|
|
@ -473,10 +355,6 @@ func AdminUsersEdit(c *core.Context) *core.Response {
|
||||||
//Autocomplete: true,
|
//Autocomplete: true,
|
||||||
IsRequired: true,
|
IsRequired: true,
|
||||||
},
|
},
|
||||||
FieldRoles: components.FormCheckbox{
|
|
||||||
Label: "Roles",
|
|
||||||
AllCheckbox: listroles,
|
|
||||||
},
|
|
||||||
FieldPassword: components.FormInput{
|
FieldPassword: components.FormInput{
|
||||||
ID: "password",
|
ID: "password",
|
||||||
Label: "Password",
|
Label: "Password",
|
||||||
|
|
@ -583,9 +461,6 @@ func AdminUsersDelConfirm(c *core.Context) *core.Response {
|
||||||
// check if is the seed user
|
// check if is the seed user
|
||||||
seed := "1"
|
seed := "1"
|
||||||
if user_id != seed {
|
if user_id != seed {
|
||||||
|
|
||||||
// initiate authority
|
|
||||||
auth := new(utils.Authority)
|
|
||||||
// Delete the user
|
// Delete the user
|
||||||
// fire user delete event
|
// fire user delete event
|
||||||
err := c.GetEventsManager().Fire(&core.Event{Name: events.USER_DELETED, Payload: map[string]interface{}{
|
err := c.GetEventsManager().Fire(&core.Event{Name: events.USER_DELETED, Payload: map[string]interface{}{
|
||||||
|
|
@ -594,7 +469,6 @@ func AdminUsersDelConfirm(c *core.Context) *core.Response {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.GetLogger().Error(err.Error())
|
c.GetLogger().Error(err.Error())
|
||||||
}
|
}
|
||||||
auth.RevokeAllUserRole(c, origin_user.ID)
|
|
||||||
result_db.Unscoped().Delete(&origin_user)
|
result_db.Unscoped().Delete(&origin_user)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// Copyright 2023 Harran Ali <harran.m@gmail.com>. All rights reserved.
|
// Copyright 2023 Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||||
// Copyright (c) 2026 Zeni Kim <zenik@smarteching.com>
|
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
|
||||||
// Use of this source code is governed by MIT-style
|
// Use of this source code is governed by MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"git.smarteching.com/goffee/core"
|
"git.smarteching.com/goffee/core"
|
||||||
"git.smarteching.com/goffee/cup/events"
|
"git.smarteching.com/goffee/cup/events"
|
||||||
"git.smarteching.com/goffee/cup/models"
|
"git.smarteching.com/goffee/cup/models"
|
||||||
|
"git.smarteching.com/goffee/cup/utils"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
@ -100,7 +101,7 @@ func Signup(c *core.Context) *core.Response {
|
||||||
|
|
||||||
// cache the token
|
// cache the token
|
||||||
userAgent := c.GetUserAgent()
|
userAgent := c.GetUserAgent()
|
||||||
hashedCacheKey := core.CreateAuthTokenHashedCacheKey(user.ID, userAgent)
|
hashedCacheKey := utils.CreateAuthTokenHashedCacheKey(user.ID, userAgent)
|
||||||
err = c.GetCache().Set(hashedCacheKey, token)
|
err = c.GetCache().Set(hashedCacheKey, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.GetLogger().Error(err.Error())
|
c.GetLogger().Error(err.Error())
|
||||||
|
|
@ -222,12 +223,12 @@ func Signin(c *core.Context) *core.Response {
|
||||||
}
|
}
|
||||||
// cache the token
|
// cache the token
|
||||||
userAgent := c.GetUserAgent()
|
userAgent := c.GetUserAgent()
|
||||||
hashedCacheKey := core.CreateAuthTokenHashedCacheKey(user.ID, userAgent)
|
hashedCacheKey := utils.CreateAuthTokenHashedCacheKey(user.ID, userAgent)
|
||||||
err = c.GetCache().Set(hashedCacheKey, token)
|
err = c.GetCache().Set(hashedCacheKey, token)
|
||||||
|
|
||||||
// delete data from old sessions
|
// delete data from old sessions
|
||||||
sessionKey := fmt.Sprintf("sess_%v", userAgent)
|
sessionKey := fmt.Sprintf("sess_%v", userAgent)
|
||||||
hashedSessionKey := core.CreateAuthTokenHashedCacheKey(user.ID, sessionKey)
|
hashedSessionKey := utils.CreateAuthTokenHashedCacheKey(user.ID, sessionKey)
|
||||||
_ = c.GetCache().Delete(hashedSessionKey)
|
_ = c.GetCache().Delete(hashedSessionKey)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -462,7 +463,7 @@ func Signout(c *core.Context) *core.Response {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
userAgent := c.GetUserAgent()
|
userAgent := c.GetUserAgent()
|
||||||
hashedCacheKey := core.CreateAuthTokenHashedCacheKey(uint(c.CastToInt(payload["userID"])), userAgent)
|
hashedCacheKey := utils.CreateAuthTokenHashedCacheKey(uint(c.CastToInt(payload["userID"])), userAgent)
|
||||||
|
|
||||||
err = c.GetCache().Delete(hashedCacheKey)
|
err = c.GetCache().Delete(hashedCacheKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -47,27 +47,8 @@ func WelcomeHome(c *core.Context) *core.Response {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show dashboard
|
||||||
func WelcomeToDashboard(c *core.Context) *core.Response {
|
func WelcomeToDashboard(c *core.Context) *core.Response {
|
||||||
message := "{\"message\": \"Welcome to Dashboard\"}"
|
message := "{\"message\": \"Welcome to Dashboard\"}"
|
||||||
return c.Response.Json(message)
|
return c.Response.Json(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
// Copyright (c) 2025 Zeni Kim <zenik@smarteching.com>
|
|
||||||
// Use of this source code is governed by MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package controllers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"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 {
|
|
||||||
c.GetLogger().Error(err.Error())
|
|
||||||
return c.Response.SetStatusCode(500).Json(`{"message": "internal error"}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
t1 := asynq.NewTask(workers.TypeWelcomeEmail, payload)
|
|
||||||
|
|
||||||
t2 := asynq.NewTask(workers.TypeReminderEmail, payload)
|
|
||||||
|
|
||||||
// Process the task immediately.
|
|
||||||
info, err := client.Enqueue(t1)
|
|
||||||
if err != nil {
|
|
||||||
c.GetLogger().Error(err.Error())
|
|
||||||
return c.Response.SetStatusCode(500).Json(`{"message": "internal error"}`)
|
|
||||||
}
|
|
||||||
c.GetLogger().Info(fmt.Sprintf(" [*] 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 {
|
|
||||||
c.GetLogger().Error(err.Error())
|
|
||||||
return c.Response.SetStatusCode(500).Json(`{"message": "internal error"}`)
|
|
||||||
}
|
|
||||||
c.GetLogger().Info(fmt.Sprintf(" [*] Successfully enqueued task: %+v", info))
|
|
||||||
}
|
|
||||||
|
|
||||||
message := "{\"message\": \"Task queued\"}"
|
|
||||||
return c.Response.Json(message)
|
|
||||||
|
|
||||||
}
|
|
||||||
149
controllers/sample.go
Normal file
149
controllers/sample.go
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
|
||||||
|
// Use of this source code is governed by MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package 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)
|
||||||
|
|
||||||
|
}
|
||||||
755
controllers/themedemo.go
Normal file
755
controllers/themedemo.go
Normal file
|
|
@ -0,0 +1,755 @@
|
||||||
|
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
|
||||||
|
// Use of this source code is governed by MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand/v2"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 <= 10; 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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{
|
||||||
|
ContentTable: components.ContentTable{
|
||||||
|
ID: "table_demo",
|
||||||
|
AllTH: allTh,
|
||||||
|
AllTD: allTd,
|
||||||
|
},
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Response.Template("custom_theme_contentpage.html", tmplData)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
message := "{\"message\": \"Error, template not enabled\"}"
|
||||||
|
return c.Response.Json(message)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
53
go.mod
53
go.mod
|
|
@ -7,59 +7,52 @@ replace (
|
||||||
git.smarteching.com/goffee/cup/models => ./models
|
git.smarteching.com/goffee/cup/models => ./models
|
||||||
)
|
)
|
||||||
|
|
||||||
go 1.25.0
|
go 1.23.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.smarteching.com/goffee/core v1.9.8
|
git.smarteching.com/goffee/core v1.8.4
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/hibiken/asynq v0.26.0
|
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/julienschmidt/httprouter v1.3.0
|
github.com/julienschmidt/httprouter v1.3.0
|
||||||
gorm.io/gorm v1.31.1
|
gorm.io/gorm v1.25.12
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
filippo.io/edwards25519 v1.2.0 // indirect
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
git.smarteching.com/zeni/go-chart/v2 v2.1.4 // 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/SparkPost/gosparkpost v0.2.0 // indirect
|
||||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/go-chi/chi/v5 v5.2.5 // 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-ozzo/ozzo-validation v3.6.0+incompatible // indirect
|
||||||
github.com/go-sql-driver/mysql v1.10.0 // indirect
|
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
|
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||||
github.com/harranali/mailing v1.2.0 // indirect
|
github.com/harranali/mailing v1.2.0 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
github.com/jackc/pgx/v5 v5.9.2 // indirect
|
github.com/jackc/pgx/v5 v5.7.1 // indirect
|
||||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/mailgun/errors v0.6.0 // indirect
|
github.com/mailgun/errors v0.4.0 // indirect
|
||||||
github.com/mailgun/mailgun-go/v4 v4.23.0 // indirect
|
github.com/mailgun/mailgun-go/v4 v4.17.3 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.44 // 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/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/redis/go-redis/v9 v9.19.0 // 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/rest v2.6.9+incompatible // indirect
|
||||||
github.com/sendgrid/sendgrid-go v3.16.1+incompatible // indirect
|
github.com/sendgrid/sendgrid-go v3.16.0+incompatible // indirect
|
||||||
github.com/spf13/cast v1.10.0 // indirect
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
go.uber.org/atomic v1.11.0 // indirect
|
golang.org/x/crypto v0.28.0 // indirect
|
||||||
golang.org/x/crypto v0.51.0 // indirect
|
golang.org/x/image v0.21.0 // indirect
|
||||||
golang.org/x/image v0.40.0 // indirect
|
golang.org/x/net v0.30.0 // indirect
|
||||||
golang.org/x/net v0.54.0 // indirect
|
golang.org/x/sync v0.8.0 // indirect
|
||||||
golang.org/x/sync v0.20.0 // indirect
|
golang.org/x/text v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.44.0 // indirect
|
gorm.io/driver/mysql v1.5.7 // indirect
|
||||||
golang.org/x/text v0.37.0 // indirect
|
gorm.io/driver/postgres v1.5.9 // indirect
|
||||||
golang.org/x/time v0.15.0 // indirect
|
gorm.io/driver/sqlite v1.5.6 // 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
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
145
go.sum
145
go.sum
|
|
@ -1,13 +1,9 @@
|
||||||
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
git.smarteching.com/goffee/core v1.9.6 h1:GY1EXqbmBEWZAVrl3q22Izb6aXhQzFVQBv2hWhK/So8=
|
git.smarteching.com/goffee/core v1.8.4 h1:XB+vpe7e8muiDChRVDaJ1TG7H+/FBxDQcMfWp4zloPs=
|
||||||
git.smarteching.com/goffee/core v1.9.6/go.mod h1:ifiBgTOR4zCMzdGsabNrEO792EHny2o149NGe3TSlms=
|
git.smarteching.com/goffee/core v1.8.4/go.mod h1:JxXDvTQU2shKYY6c9aS3s6sFh7mEDzgmjzdc85HhBV8=
|
||||||
git.smarteching.com/goffee/core v1.9.8 h1:FWlUZ9gaPO5J+ti/YXL/Bvs11JYJ1mrmcGHlFtU7C5I=
|
|
||||||
git.smarteching.com/goffee/core v1.9.8/go.mod h1:yTpKuV9/aseMvM/HIpcEVCeSdmZajiXja8gsLxYM9co=
|
|
||||||
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 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-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 h1:yzhHQT7cE+rqzd5tANNC74j+2x3lrPznqPJrxC1yR8s=
|
||||||
github.com/SparkPost/gosparkpost v0.2.0/go.mod h1:S9WKcGeou7cbPpx0kTIgo8Q69WZvUmVeVzbD+djalJ4=
|
github.com/SparkPost/gosparkpost v0.2.0/go.mod h1:S9WKcGeou7cbPpx0kTIgo8Q69WZvUmVeVzbD+djalJ4=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
||||||
|
|
@ -25,37 +21,38 @@ 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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
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/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ=
|
||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64=
|
||||||
github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
|
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A=
|
||||||
github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
|
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/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=
|
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-ozzo/ozzo-validation v3.6.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU=
|
||||||
github.com/go-sql-driver/mysql v1.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxoWCWfw=
|
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||||
github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk=
|
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-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
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/gogs/chardet v0.0.0-20150115103509-2404f7772561/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
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 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||||
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/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 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/harranali/mailing v1.2.0 h1:ihIyJwB8hyRVcdk+v465wk1PHMrSrgJqo/kMd+gZClY=
|
github.com/harranali/mailing v1.2.0 h1:ihIyJwB8hyRVcdk+v465wk1PHMrSrgJqo/kMd+gZClY=
|
||||||
github.com/harranali/mailing v1.2.0/go.mod h1:4a5N3yG98pZKluMpmcYlTtll7bisvOfGQEMIng3VQk4=
|
github.com/harranali/mailing v1.2.0/go.mod h1:4a5N3yG98pZKluMpmcYlTtll7bisvOfGQEMIng3VQk4=
|
||||||
github.com/hibiken/asynq v0.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 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
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/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw=
|
github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs=
|
||||||
github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA=
|
||||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
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/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=
|
github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
|
||||||
|
|
@ -70,23 +67,15 @@ 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/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 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
||||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
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 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
github.com/mailgun/errors v0.5.0 h1:pLQo8uhAdORsjN69mGixSr0pGs46z/BW/FQXd8HG1VM=
|
github.com/mailgun/errors v0.4.0 h1:6LFBvod6VIW83CMIOT9sYNp28TCX0NejFPP4dSX++i8=
|
||||||
github.com/mailgun/errors v0.5.0/go.mod h1:+2nrgY77E0vDkG4ErehpcpbSkMLkseJzKbrva89WeSs=
|
github.com/mailgun/errors v0.4.0/go.mod h1:xGBaaKdEdQT0/FhwvoXv4oBaqqmVZz9P1XEnvD/onc0=
|
||||||
github.com/mailgun/errors v0.6.0 h1:IWmzIGwXCSN/Q60JT/lXvam3xRAgTUJSX88KwKJ7hss=
|
github.com/mailgun/mailgun-go/v4 v4.17.3 h1:WoO48/VeXgAVSzjgzyeLvF08AoPzWU2EBz79INN8rEA=
|
||||||
github.com/mailgun/errors v0.6.0/go.mod h1:+2nrgY77E0vDkG4ErehpcpbSkMLkseJzKbrva89WeSs=
|
github.com/mailgun/mailgun-go/v4 v4.17.3/go.mod h1:0ood70bQR/SffQ9NxIsAY06H+HG0hrvMVApfUp9TihI=
|
||||||
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-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.44 h1:3VSe+xafpbzsLbdr2AWlAZk9yRHiBhTBakioXaCKTF8=
|
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
||||||
github.com/mattn/go-sqlite3 v1.14.44/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
|
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
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 h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
|
@ -98,74 +87,50 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/redis/go-redis/v9 v9.19.0 h1:XPVaaPSnG6RhYf7p+rmSa9zZfeVAnWsH5h3lxthOm/k=
|
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
|
||||||
github.com/redis/go-redis/v9 v9.19.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA=
|
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/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
|
||||||
github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0=
|
github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0=
|
||||||
github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE=
|
github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE=
|
||||||
github.com/sendgrid/sendgrid-go v3.16.1+incompatible h1:zWhTmB0Y8XCDzeWIm2/BIt1GjJohAA0p6hVEaDtHWWs=
|
github.com/sendgrid/sendgrid-go v3.16.0+incompatible h1:i8eE6IMkiCy7vusSdacHHSBUpXyTcTXy/Rl9N9aZ/Qw=
|
||||||
github.com/sendgrid/sendgrid-go v3.16.1+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
|
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 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
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/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
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.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||||
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||||
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
|
golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s=
|
||||||
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
|
golang.org/x/image v0.21.0/go.mod h1:vUbsLavqK/W303ZroQQVKQ+Af3Yl6Uz1Ppu5J/cLz78=
|
||||||
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/image v0.40.0 h1:Tw4GyDXMo+daZN1znreBRC3VayR1aLFUyUEOLUdW1a8=
|
|
||||||
golang.org/x/image v0.40.0/go.mod h1:uIc348UZMSvS5Z65CVZ7iDPaNobNFEPeJ4kbqTOszmA=
|
|
||||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
||||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
||||||
golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
|
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||||
golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
|
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||||
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.44.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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
|
||||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
|
||||||
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=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
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/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
|
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
|
||||||
gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
|
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
|
||||||
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
|
gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8=
|
||||||
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
|
gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
|
||||||
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
|
gorm.io/driver/sqlite v1.5.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE=
|
||||||
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
|
gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
||||||
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||||
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||||
|
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
|
|
||||||
"git.smarteching.com/goffee/core"
|
"git.smarteching.com/goffee/core"
|
||||||
"git.smarteching.com/goffee/cup/models"
|
"git.smarteching.com/goffee/cup/models"
|
||||||
|
"git.smarteching.com/goffee/cup/utils"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -30,7 +31,7 @@ var CheckSessionCookie core.Hook = func(c *core.Context) {
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
userAgent := c.GetUserAgent()
|
userAgent := c.GetUserAgent()
|
||||||
hashedCacheKey := core.CreateAuthTokenHashedCacheKey(uint(c.CastToInt(payload["userID"])), userAgent)
|
hashedCacheKey := utils.CreateAuthTokenHashedCacheKey(uint(c.CastToInt(payload["userID"])), userAgent)
|
||||||
|
|
||||||
cachedToken, err := c.GetCache().Get(hashedCacheKey)
|
cachedToken, err := c.GetCache().Get(hashedCacheKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -101,7 +102,7 @@ var AuthCheck core.Hook = func(c *core.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
userAgent := c.GetUserAgent()
|
userAgent := c.GetUserAgent()
|
||||||
hashedCacheKey := core.CreateAuthTokenHashedCacheKey(uint(c.CastToInt(payload["userID"])), userAgent)
|
hashedCacheKey := utils.CreateAuthTokenHashedCacheKey(uint(c.CastToInt(payload["userID"])), userAgent)
|
||||||
|
|
||||||
cachedToken, err := c.GetCache().Get(hashedCacheKey)
|
cachedToken, err := c.GetCache().Get(hashedCacheKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
3
main.go
3
main.go
|
|
@ -62,9 +62,6 @@ func main() {
|
||||||
registerGlobalHooks()
|
registerGlobalHooks()
|
||||||
registerRoutes()
|
registerRoutes()
|
||||||
registerEvents()
|
registerEvents()
|
||||||
if config.GetQueueConfig().EnableQueue == true {
|
|
||||||
registerQueues()
|
|
||||||
}
|
|
||||||
if config.GetGormConfig().EnableGorm == true {
|
if config.GetGormConfig().EnableGorm == true {
|
||||||
RunAutoMigrations()
|
RunAutoMigrations()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
// Copyright (c) 2025 Zeni Kim <zenik@smarteching.com>
|
|
||||||
// Use of this source code is governed by MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package models
|
|
||||||
|
|
||||||
type Permission struct {
|
|
||||||
BaseModel
|
|
||||||
Name string
|
|
||||||
Slug string
|
|
||||||
}
|
|
||||||
|
|
||||||
// TableName sets the table name
|
|
||||||
func (Permission) TableName() string {
|
|
||||||
return "permissions"
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
// Copyright (c) 2025 Zeni Kim <zenik@smarteching.com>
|
|
||||||
// Use of this source code is governed by MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package 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"
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
// Copyright (c) 2025 Zeni Kim <zenik@smarteching.com>
|
|
||||||
// Use of this source code is governed by MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package 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"
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
// Copyright (c) 2025 Zeni Kim <zenik@smarteching.com>
|
|
||||||
// Use of this source code is governed by MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package models
|
|
||||||
|
|
||||||
type UserRole struct {
|
|
||||||
BaseModel
|
|
||||||
UserID uint // The user id
|
|
||||||
RoleID uint // The role id
|
|
||||||
}
|
|
||||||
|
|
||||||
// TableName sets the table name
|
|
||||||
func (UserRole) TableName() string {
|
|
||||||
return "user_roles"
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
// Copyright (c) 2025 Zeni Kim <zenik@smarteching.com>
|
|
||||||
// Use of this source code is governed by MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.smarteching.com/goffee/core"
|
|
||||||
"git.smarteching.com/goffee/cup/config"
|
|
||||||
"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(config.GetQueueConfig())
|
|
||||||
}
|
|
||||||
22
routes.go
22
routes.go
|
|
@ -20,17 +20,20 @@ func registerRoutes() {
|
||||||
|
|
||||||
// Define your routes here...
|
// Define your routes here...
|
||||||
controller.Get("/", controllers.WelcomeHome)
|
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)
|
||||||
|
|
||||||
// Uncomment the lines below to enable authentication API
|
// Uncomment the lines below to enable authentication
|
||||||
controller.Post("/signup", controllers.Signup)
|
controller.Post("/signup", controllers.Signup)
|
||||||
controller.Post("/signin", controllers.Signin)
|
controller.Post("/signin", controllers.Signin)
|
||||||
controller.Post("/signout", controllers.Signout)
|
controller.Post("/signout", controllers.Signout)
|
||||||
controller.Post("/reset-password", controllers.ResetPasswordRequest)
|
controller.Post("/reset-password", controllers.ResetPasswordRequest)
|
||||||
controller.Post("/reset-password/code/:code", controllers.SetNewPassword)
|
controller.Post("/reset-password/code/:code", controllers.SetNewPassword)
|
||||||
|
|
||||||
// queue sample route
|
|
||||||
controller.Get("/queuesample", controllers.Queuesample)
|
|
||||||
|
|
||||||
// Uncomment the lines below to enable user administration
|
// Uncomment the lines below to enable user administration
|
||||||
controller.Get("/admin/users", controllers.AdminUsersList)
|
controller.Get("/admin/users", controllers.AdminUsersList)
|
||||||
controller.Post("/admin/users", controllers.AdminUsersList)
|
controller.Post("/admin/users", controllers.AdminUsersList)
|
||||||
|
|
@ -40,9 +43,20 @@ func registerRoutes() {
|
||||||
controller.Post("/admin/users/edit/:id", controllers.AdminUsersEdit)
|
controller.Post("/admin/users/edit/:id", controllers.AdminUsersEdit)
|
||||||
controller.Post("/admin/users/delete", controllers.AdminUsersDelete)
|
controller.Post("/admin/users/delete", controllers.AdminUsersDelete)
|
||||||
controller.Post("/admin/users/deleteconfirm", controllers.AdminUsersDelConfirm)
|
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)
|
controller.Get("/dashboard", controllers.WelcomeToDashboard, hooks.AuthCheck)
|
||||||
|
|
||||||
|
// templates demos
|
||||||
controller.Get("/signout", controllers.Signout)
|
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.Get("/applogin", controllers.AppLogin, hooks.CheckSessionCookie)
|
||||||
controller.Post("/applogin", controllers.AppLogin, hooks.CheckSessionCookie)
|
controller.Post("/applogin", controllers.AppLogin, hooks.CheckSessionCookie)
|
||||||
|
|
||||||
|
controller.Get("/appsession", controllers.AppSession)
|
||||||
|
controller.Post("/appsession", controllers.AppSession)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,8 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
|
|
||||||
"git.smarteching.com/goffee/core"
|
"git.smarteching.com/goffee/core"
|
||||||
"git.smarteching.com/goffee/cup/models"
|
"git.smarteching.com/goffee/cup/models"
|
||||||
"git.smarteching.com/goffee/cup/utils"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func RunAutoMigrations() {
|
func RunAutoMigrations() {
|
||||||
|
|
@ -21,18 +17,5 @@ func RunAutoMigrations() {
|
||||||
//##############################
|
//##############################
|
||||||
|
|
||||||
// Add auto migrations for your models here...
|
// Add auto migrations for your models here...
|
||||||
db.AutoMigrate(&models.UserRole{})
|
db.AutoMigrate(&models.User{})
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB |
|
|
@ -1,9 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Title!</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<p>This is an example <strong>conten</strong>.</p>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Binary file not shown.
|
|
@ -16,7 +16,6 @@
|
||||||
{{template "form_input" .FieldName}}
|
{{template "form_input" .FieldName}}
|
||||||
{{template "form_input" .FieldFullname}}
|
{{template "form_input" .FieldFullname}}
|
||||||
{{template "form_input" .FieldEmail}}
|
{{template "form_input" .FieldEmail}}
|
||||||
{{template "form_checkbox" .FieldRoles}}
|
|
||||||
{{template "form_input" .FieldPassword}}
|
{{template "form_input" .FieldPassword}}
|
||||||
<hr>
|
<hr>
|
||||||
{{template "form_button" .SubmitButton}}
|
{{template "form_button" .SubmitButton}}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@
|
||||||
{{template "form_input" .FieldName}}
|
{{template "form_input" .FieldName}}
|
||||||
{{template "form_input" .FieldFullname}}
|
{{template "form_input" .FieldFullname}}
|
||||||
{{template "form_input" .FieldEmail}}
|
{{template "form_input" .FieldEmail}}
|
||||||
{{template "form_checkbox" .FieldRoles}}
|
|
||||||
{{template "form_input" .FieldPassword}}
|
{{template "form_input" .FieldPassword}}
|
||||||
{{template "form_input" .FieldKey}}
|
{{template "form_input" .FieldKey}}
|
||||||
{{template "form_button" .SubmitButton}}
|
{{template "form_button" .SubmitButton}}
|
||||||
|
|
|
||||||
17
storage/templates/app.html
Normal file
17
storage/templates/app.html
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
{{template "page_head" "Sample page"}}
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
{{template "content_dropdown" .ContentDropdown}}
|
||||||
|
{{template "page_card" .PageCard}}
|
||||||
|
{{ define "page_card_content" }}
|
||||||
|
|
||||||
|
<img class="goffeelogo"src="/public/img/goffee.png" alt="Goffee logo" />
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{template "page_footer"}}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
{{template "page_head" "No permission"}}
|
{{template "page_head" "Sample page test session vars"}}
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
You do not have permission to visit this page.
|
{{template "page_card" .PageCard}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{template "page_footer"}}
|
{{template "page_footer"}}
|
||||||
10
storage/templates/custom_theme_base.html
Normal file
10
storage/templates/custom_theme_base.html
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
{{template "page_head" "Goffee"}}
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
{{template "page_card" .PageCard}}
|
||||||
|
</div>
|
||||||
|
{{template "page_footer"}}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
24
storage/templates/custom_theme_contentpage.html
Normal file
24
storage/templates/custom_theme_contentpage.html
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
{{template "page_head" "Goffee"}}
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<fieldset>
|
||||||
|
<legend>Content demos</legend>
|
||||||
|
<div class="row">
|
||||||
|
{{template "content_table" .ContentTable}}
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<h2>Pie chart</h2>
|
||||||
|
{{template "content_graph" .ContentGraph}}
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-6">
|
||||||
|
{{template "content_tabledetail" .ContentTabledetail}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
{{template "page_footer"}}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
66
storage/templates/custom_theme_elements.html
Normal file
66
storage/templates/custom_theme_elements.html
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
{{template "page_head" "Goffee"}}
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<fieldset class="mb-3">
|
||||||
|
<legend>Demos buttons</legend>
|
||||||
|
<div class="container border rounded-3 p-2">
|
||||||
|
{{range .Buttons}}
|
||||||
|
{{template "form_button" .}}
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset class="mb-3">
|
||||||
|
<legend>Demos href</legend>
|
||||||
|
<div class="container d-flex justify-content-between border rounded-3 p-2">
|
||||||
|
{{range .Hrefs}}
|
||||||
|
{{template "content_href" .}}
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset class="mb-3">
|
||||||
|
<legend>Demos Badges</legend>
|
||||||
|
<div class="container d-flex justify-content-between border rounded-3 p-2">
|
||||||
|
{{range .Badges}}
|
||||||
|
{{template "content_badge" .}}
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
|
||||||
|
<fieldset class="mb-3">
|
||||||
|
<legend>Demos dropdown</legend>
|
||||||
|
<div class="container d-flex justify-content-between border rounded-3 p-2">
|
||||||
|
{{range .Dropdowns}}
|
||||||
|
{{template "content_dropdown" .}}
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
|
||||||
|
<fieldset class="mb-3">
|
||||||
|
<legend>Demos List</legend>
|
||||||
|
<div class="container d-flex justify-content-between border rounded-3 p-2">
|
||||||
|
{{range .Lists}}
|
||||||
|
{{template "content_list" .}}
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset class="mb-3">
|
||||||
|
<legend>Demos nav</legend>
|
||||||
|
<div class="container border rounded-3 p-2 ">
|
||||||
|
{{range .Menus}}
|
||||||
|
<div class="container border rounded-3 p-2 mb-2">
|
||||||
|
{{template "page_nav" .}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
{{template "page_footer"}}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
23
storage/templates/custom_theme_formpage.html
Normal file
23
storage/templates/custom_theme_formpage.html
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
{{template "page_head" "Goffee"}}
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<form>
|
||||||
|
<fieldset>
|
||||||
|
<legend>form demos</legend>
|
||||||
|
<div class="row">
|
||||||
|
{{template "form_input" .FormText}}
|
||||||
|
{{template "form_input" .FormEmail}}
|
||||||
|
{{template "form_select" .FormSelectCity}}
|
||||||
|
{{template "form_textarea" .FormTextarea}}
|
||||||
|
{{template "form_radio" .FormRadio}}
|
||||||
|
{{template "form_checkbox" .FormCheckbox}}
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
{{template "form_button" .FormButton}}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{{template "page_footer"}}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -1,436 +0,0 @@
|
||||||
// Copyright (c) 2025 Zeni Kim <zenik@smarteching.com>
|
|
||||||
// Use of this source code is governed by MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package 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 uint, roleSlug string) error {
|
|
||||||
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 = ?", userID).Where("role_id = ?", role.ID).First(&userRole)
|
|
||||||
if res.Error != nil && errors.Is(res.Error, gorm.ErrRecordNotFound) {
|
|
||||||
c.GetGorm().Create(&models.UserRole{UserID: userID, 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 uint, roleSlug 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if the role is a assigned
|
|
||||||
var userRole models.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
|
|
||||||
}
|
|
||||||
return false, res.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks if a permission is assigned to a user
|
|
||||||
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 = ?", userID).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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
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 uint) ([]models.Role, error) {
|
|
||||||
var userRoles []models.UserRole
|
|
||||||
res := c.GetGorm().Where("user_id = ?", userID).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
|
|
||||||
}
|
|
||||||
|
|
@ -6,71 +6,19 @@
|
||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"crypto/md5"
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.smarteching.com/goffee/core"
|
|
||||||
"git.smarteching.com/goffee/cup/models"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func CreateSeedData() {
|
// 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)
|
||||||
|
hashedCacheKey := fmt.Sprintf("%v", fmt.Sprintf("%x", md5.Sum([]byte(cacheKey))))
|
||||||
|
|
||||||
db := core.ResolveGorm()
|
return hashedCacheKey
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func FormatUnix(value int64) string {
|
func FormatUnix(value int64) string {
|
||||||
return time.Unix(value, 0).Format("2006-01-02 15:04:05")
|
return time.Unix(value, 0).Format("2006-01-02 15:04:05")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
170
utils/session.go
Normal file
170
utils/session.go
Normal file
|
|
@ -0,0 +1,170 @@
|
||||||
|
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
|
||||||
|
// Use of this source code is governed by MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"git.smarteching.com/goffee/core"
|
||||||
|
"git.smarteching.com/goffee/cup/models"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SessionUser struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
context *core.Context
|
||||||
|
userID uint
|
||||||
|
hashedSessionKey string
|
||||||
|
authenticated bool
|
||||||
|
sessionStart time.Time
|
||||||
|
values map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// start the struct
|
||||||
|
func (s *SessionUser) Init(c *core.Context) bool {
|
||||||
|
|
||||||
|
// check session cookie
|
||||||
|
pass := true
|
||||||
|
token := ""
|
||||||
|
s.context = c
|
||||||
|
|
||||||
|
payload := make(map[string]interface{})
|
||||||
|
// get cookie
|
||||||
|
usercookie, err := c.GetCookie()
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
token = usercookie.Token
|
||||||
|
|
||||||
|
if token == "" {
|
||||||
|
|
||||||
|
pass = false
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
payload, err = c.GetJWT().DecodeToken(token)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
pass = false
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
userID := uint(c.CastToInt(payload["userID"]))
|
||||||
|
userAgent := c.GetUserAgent()
|
||||||
|
|
||||||
|
// get data from redis
|
||||||
|
hashedCacheKey := CreateAuthTokenHashedCacheKey(userID, userAgent)
|
||||||
|
cachedToken, err := c.GetCache().Get(hashedCacheKey)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
pass = false
|
||||||
|
} else if cachedToken != token {
|
||||||
|
pass = false
|
||||||
|
} else {
|
||||||
|
var user models.User
|
||||||
|
res := c.GetGorm().Where("id = ?", userID).First(&user)
|
||||||
|
if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) {
|
||||||
|
pass = false
|
||||||
|
}
|
||||||
|
// if have session start the struct
|
||||||
|
if pass {
|
||||||
|
userAgent := c.GetUserAgent()
|
||||||
|
sessionKey := fmt.Sprintf("sess_%v", userAgent)
|
||||||
|
s.hashedSessionKey = CreateAuthTokenHashedCacheKey(userID, sessionKey)
|
||||||
|
|
||||||
|
s.values = make(map[string]interface{})
|
||||||
|
s.authenticated = true
|
||||||
|
s.userID = userID
|
||||||
|
value, _ := c.GetCache().Get(s.hashedSessionKey)
|
||||||
|
|
||||||
|
if len(value) > 0 {
|
||||||
|
_ = json.Unmarshal([]byte(value), &s.values)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
s.hashedSessionKey = ""
|
||||||
|
s.authenticated = false
|
||||||
|
s.userID = 0
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SessionUser) Set(key string, value interface{}) error {
|
||||||
|
s.mu.Lock()
|
||||||
|
s.values[key] = value
|
||||||
|
s.mu.Unlock()
|
||||||
|
return s.Save()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SessionUser) Get(key string) (interface{}, bool) {
|
||||||
|
s.mu.RLock()
|
||||||
|
defer s.mu.RUnlock()
|
||||||
|
val, ok := s.values[key]
|
||||||
|
return val, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SessionUser) Delete(key string) interface{} {
|
||||||
|
s.mu.RLock()
|
||||||
|
v, ok := s.values[key]
|
||||||
|
s.mu.RUnlock()
|
||||||
|
if ok {
|
||||||
|
s.mu.Lock()
|
||||||
|
delete(s.values, key)
|
||||||
|
s.mu.Unlock()
|
||||||
|
}
|
||||||
|
s.Save()
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SessionUser) Flush() error {
|
||||||
|
s.mu.Lock()
|
||||||
|
s.context.GetCache().Delete(s.hashedSessionKey)
|
||||||
|
s.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SessionUser) Save() error {
|
||||||
|
|
||||||
|
var value string
|
||||||
|
|
||||||
|
s.mu.RLock()
|
||||||
|
|
||||||
|
if len(s.values) > 0 {
|
||||||
|
buf, err := json.Marshal(&s.values)
|
||||||
|
if err != nil {
|
||||||
|
s.mu.RUnlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
value = string(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(value) > 0 {
|
||||||
|
s.context.GetCache().Set(s.hashedSessionKey, value)
|
||||||
|
} else {
|
||||||
|
s.context.GetCache().Delete(s.hashedSessionKey)
|
||||||
|
}
|
||||||
|
s.mu.RUnlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SessionUser) GetUserID() uint {
|
||||||
|
|
||||||
|
return s.userID
|
||||||
|
}
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
package workers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"git.smarteching.com/goffee/core/logger"
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
logger.ResolveLogger().Info(fmt.Sprintf(" [*] 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
|
|
||||||
}
|
|
||||||
logger.ResolveLogger().Info(fmt.Sprintf(" [*] Send Reminder Email to User %d", p.UserID))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue