1
0
Fork 0
forked from goffee/cup

Compare commits

...

32 commits

Author SHA1 Message Date
461c776bb7 Merge pull request 'develop' (#13) from develop into main
Reviewed-on: goffee/cup#13
2024-12-24 00:02:26 -05:00
7a32380274 fix core version 2024-12-24 00:01:42 -05:00
d572509351 fix core version 2024-12-23 23:58:48 -05:00
964c3c6089 Merge pull request 'develop' (#12) from develop into main
Reviewed-on: goffee/cup#12
2024-12-23 23:46:47 -05:00
273b43f5e0 add config option to disable queues 2024-12-23 23:40:22 -05:00
d413d7def4 add base queue system 2024-12-23 23:19:22 -05:00
d4d4abb751 Merge pull request 'develop' (#11) from develop into main
Reviewed-on: goffee/cup#11
2024-12-18 08:22:38 -05:00
d431963181 Update sample user admin to user authority, add revoke role to auth 2024-12-17 14:23:07 -05:00
43f3ad986e fix user id, seed data autority 2024-12-16 20:05:37 -05:00
cac2986b59 authority system start 2024-12-14 22:38:06 -05:00
ce78691f33 Merge pull request 'add gopher_read.png image, remove .env-dev from gitignore' (#10) from develop into main
Reviewed-on: goffee/cup#10
2024-12-08 08:55:21 -05:00
e5dee10221 add gopher_read.png image, remove .env-dev from gitignore 2024-12-08 08:51:50 -05:00
f22e940279 Merge pull request 'develop' (#9) from develop into main
Reviewed-on: goffee/cup#9
2024-12-06 05:02:05 -05:00
cf699e2084 sample admin users 2024-12-06 04:48:10 -05:00
0f520e67af sample users admin 2024-12-04 08:39:08 -05:00
e85dea89b5 Merge pull request 'js destroy session' (#8) from develop into main
Reviewed-on: goffee/cup#8
2024-10-29 13:46:14 -04:00
19c24593fe js destroy session 2024-10-29 12:44:51 -05:00
7a44f2ede7 Merge pull request 'add prod mode' (#7) from develop into main
Reviewed-on: goffee/cup#7
2024-10-29 07:51:59 -04:00
0b2abac628 add prod mode 2024-10-29 06:48:11 -05:00
b5f6b2f413 Merge pull request 'develop' (#6) from develop into main
Reviewed-on: goffee/cup#6
2024-10-28 13:21:41 -04:00
801c5c107f add sample graph 2024-10-28 11:34:54 -05:00
80520260c9 detail table 2024-10-27 12:55:12 -05:00
91455fc972 session cookie destroy 2024-10-27 10:27:54 -05:00
8141a9eba0 add sqlite to ignore watcher 2024-10-24 17:06:52 -05:00
baeec24df2 Merge pull request 'develop' (#5) from develop into main
Reviewed-on: goffee/cup#5
2024-10-23 08:12:37 -04:00
ba49afe04a reorder files 2024-10-23 07:09:27 -05:00
3c1170bd87 sessions review 2024-10-21 20:44:04 -05:00
23fd877a98 start simple session manager 2024-10-21 16:04:58 -05:00
0e2803df9e Merge pull request 'develop' (#4) from develop into main
Reviewed-on: goffee/cup#4
2024-10-16 00:00:20 -04:00
384f25d220 Merge pull request 'develop' (#3) from diana/cup:develop into develop
Reviewed-on: goffee/cup#3
2024-10-15 23:03:08 -04:00
04666cf1ab add cookie secret as env 2024-10-15 21:59:03 -05:00
2f35c6e792 Merge pull request 'add theme system' (#1) from develop into main
Reviewed-on: goffee/cup#1
2024-09-27 10:49:30 -04:00
39 changed files with 2118 additions and 125 deletions

87
.env-dev Normal file
View file

@ -0,0 +1,87 @@
#######################################
###### App ######
#######################################
APP_NAME=Cup
APP_ENV=local # local | testing | production
APP_DEBUG_MODE=true
App_HTTP_HOST=localhost
App_HTTP_PORT=8080
App_USE_HTTPS=false
App_USE_LETSENCRYPT=false
App_USE_CORESERVICES=false
APP_LETSENCRYPT_EMAIL=mail@example.com
App_HTTPS_HOSTS=example.com, www.example.com
App_REDIRECT_HTTP_TO_HTTPS=false
App_CERT_FILE_PATH=tls/server.crt
App_KEY_FILE_PATH=tls/server.key
#######################################
###### TEMPLATES ######
#######################################
TEMPLATE_ENABLE=true
COOKIE_SECRET=13d6b4dff8f84a10851021ec8608f814570d562c92fe6b5ec4c9f595bcb3234b
#######################################
###### JWT ######
#######################################
JWT_SECRET=dkfTgonmgaAdlgkw
JWT_LIFESPAN_MINUTES=4320 # expires after 3 days
#######################################
###### DATABASE ######
#######################################
DB_DRIVER=sqlite # mysql | postgres | sqlite
#_____ MYSQL _____#
MYSQL_HOST=db-host-here
MYSQL_DB_NAME=db-name-here
MYSQL_PORT=3306
MYSQL_USERNAME=db-user-here
MYSQL_PASSWORD=db-password-here
MYSQL_CHARSET=utf8mb4
#_____ postgres _____#
POSTGRES_HOST=localhost
POSTGRES_USER=user
POSTGRES_PASSWORD=secret
POSTGRES_DB_NAME=db_test
POSTGRES_PORT=5432
POSTGRES_SSL_MODE=disable
POSTGRES_TIMEZONE=America/Argentina/Buenos_Aires
#_____ SQLITE _____#
SQLITE_DB_PATH=storage/sqlite/sqlite.db
#######################################
###### CACHE ######
#######################################
CACHE_DRIVER=redis
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_DB=0
#######################################
###### Emails ######
#######################################
EMAILS_DRIVER=smtp # smtp | sparkpost | sendgrid | mailgun
#_____ SMTP _____#
SMTP_HOST=
SMTP_PORT=25
SMTP_USERNAME=
SMTP_PASSWORD=
SMTP_TLS_SKIP_VERIFY_HOST=true # (set true for development only!)
#_____ sparkpost _____#
SPARKPOST_BASE_URL=https://api.sparkpost.com
SPARKPOST_API_VERSION=1
SPARKPOST_API_KEY=sparkpost-api-key-here # the api key
#_____ sendgrid _____#
SENDGRID_HOST=https://api.sendgrid.com
SENDGRID_ENDPOINT=/v3/mail/send
SENDGRID_API_KEY=sendgrid-api-key-here # the api key
#_____ mailgun _____#
MAILGUN_DOMAIN=your-domain.com # your domain
MAILGUN_API_KEY=mailgun-api-key-here # the api key
MAILGUN_TLS_SKIP_VERIFY_HOST=true # (set true for development only!)

View file

@ -8,6 +8,7 @@ App_HTTP_HOST=localhost
App_HTTP_PORT=8080
App_USE_HTTPS=false
App_USE_LETSENCRYPT=false
App_USE_CORESERVICES=false
APP_LETSENCRYPT_EMAIL=mail@example.com
App_HTTPS_HOSTS=example.com, www.example.com
App_REDIRECT_HTTP_TO_HTTPS=false
@ -18,6 +19,7 @@ App_KEY_FILE_PATH=tls/server.key
###### TEMPLATES ######
#######################################
TEMPLATE_ENABLE=true
COOKIE_SECRET=13d6b4dff8f84a10851021ec8608f814570d562c92fe6b5ec4c9f595bcb3234b
#######################################
###### JWT ######
@ -47,7 +49,7 @@ POSTGRES_SSL_MODE=disable
POSTGRES_TIMEZONE=America/Argentina/Buenos_Aires
#_____ SQLITE _____#
SQLITE_DB_PATH=storage/sqlite.db
SQLITE_DB_PATH=storage/sqlite/sqlite.db
#######################################
###### CACHE ######

1
.gitignore vendored
View file

@ -6,3 +6,4 @@ logs/*
tls/*
!tls/.gitkeep
.DS_Store
storage/sqlite/*

View file

@ -73,9 +73,9 @@ then `Goffee` locates the matching [handler](https://git.smarteching.com/goffee/
│ ├── config/ --------------------------> main configs
│ ├── events/ --------------------------> contains events
│ │ ├── jobs/ ------------------------> contains the event jobs
│ ├── controllers/ ------------------------> route's controllers
│ ├── controllers/ ---------------------> route's controllers
│ ├── logs/ ----------------------------> app log files
│ ├── hooks/ ---------------------> app hooks
│ ├── hooks/ ---------------------------> app hooks
│ ├── models/ --------------------------> database models
│ ├── storage/ -------------------------> a place to store files
│ ├── tls/ -----------------------------> tls certificates
@ -86,7 +86,7 @@ then `Goffee` locates the matching [handler](https://git.smarteching.com/goffee/
│ ├── main.go --------------------------> go main file
│ ├── README.md ------------------------> readme file
│ ├── register-events.go ---------------> register events and jobs
│ ├── register-global-hooks.go ---> register global middlewares
│ ├── register-global-hooks.go ---------> register global middlewares
│ ├── routes.go ------------------------> app routes
│ ├── run-auto-migrations.go -----------> database migrations
```

20
config/queue.go Normal file
View file

@ -0,0 +1,20 @@
// 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,
}
}

604
controllers/adminusers.go Normal file
View file

@ -0,0 +1,604 @@
// 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 (
"errors"
"fmt"
"strconv"
"git.smarteching.com/goffee/core"
"git.smarteching.com/goffee/core/template/components"
"git.smarteching.com/goffee/cup/events"
"git.smarteching.com/goffee/cup/models"
"git.smarteching.com/goffee/cup/utils"
"gorm.io/gorm"
)
func AdminUsersList(c *core.Context) *core.Response {
// initiate authority
auth := new(utils.Authority)
var session = new(utils.SessionUser)
// true if session is active
hassession := session.Init(c)
if !hassession {
type emptytemplate struct{}
emptyData := emptytemplate{}
return c.Response.Template("nopermission.html", emptyData)
}
session_uid := session.GetUserID()
// check if user has role admin
is_admin, _ := auth.CheckUserRole(c, session_uid, "admin")
if !is_admin {
type emptytemplate struct{}
emptyData := emptytemplate{}
return c.Response.Template("nopermission.html", emptyData)
}
// continue if has session and is admin
var users []models.User
db := c.GetGorm()
db.Find(&users)
// -- response template
type templateData struct {
TableUsers components.ContentTable
AddButton components.ContentHref
}
cols := []components.ContentTableTH{
{
Value: "UID",
ValueType: "number",
},
{
Value: "Name",
ValueType: "string",
},
{
Value: "Fullname",
ValueType: "string",
},
{
Value: "Email",
ValueType: "string",
},
{
Value: "Roles",
ValueType: "string",
},
{
Value: "Created",
},
{
Value: "Updated",
},
{
ValueType: "href",
},
}
var listroles string
rows := make([][]components.ContentTableTD, len(users))
for i, u := range users {
roles, _ := auth.GetUserRoles(c, u.ID)
listroles = ""
for _, role := range roles {
if listroles != "" {
listroles += ", "
}
listroles += role.Name
}
row := []components.ContentTableTD{
{Value: strconv.Itoa(int(u.ID))},
{Value: u.Name},
{Value: u.Fullname},
{Value: u.Email},
{Value: listroles},
{Value: utils.FormatUnix(u.Created)},
{Value: utils.FormatUnix(u.Updated)},
{Value: components.ContentHref{
Text: "edit",
Link: "/admin/users/edit/" + strconv.Itoa(int(u.ID)),
TypeClass: "outline-secondary",
}},
}
rows[i] = row
}
tmplData := templateData{
TableUsers: components.ContentTable{
ID: "table_demo",
AllTH: cols,
AllTD: rows,
},
AddButton: components.ContentHref{
Text: "Register",
Link: "/admin/users/add",
IsButton: true,
TypeClass: "outline-primary",
},
}
return c.Response.Template("admin_userlist.html", tmplData)
}
func AdminUsersAdd(c *core.Context) *core.Response {
// initiate authority
auth := new(utils.Authority)
var session = new(utils.SessionUser)
// true if session is active
hassession := session.Init(c)
if !hassession {
type emptytemplate struct{}
emptyData := emptytemplate{}
return c.Response.Template("nopermission.html", emptyData)
}
session_uid := session.GetUserID()
// check if user has role admin
is_admin, _ := auth.CheckUserRole(c, session_uid, "admin")
if !is_admin {
type emptytemplate struct{}
emptyData := emptytemplate{}
return c.Response.Template("nopermission.html", emptyData)
}
// check if is submit
submit := c.GetRequestParam("submit").(string)
errormessages := make([]string, 0)
var listroles []components.FormCheckboxItem
systemroles, _ := auth.GetAllRoles(c)
for _, systemrole := range systemroles {
var userrole components.FormCheckboxItem
userrole.Label = systemrole.Name
userrole.Name = "roles"
userrole.Value = systemrole.Slug
if systemrole.Slug == "authenticated" {
userrole.IsChecked = true
}
listroles = append(listroles, userrole)
}
name := ""
fullname := ""
email := ""
password := ""
if submit != "" {
name = c.GetRequestParam("name").(string)
fullname = c.GetRequestParam("fullname").(string)
email = c.GetRequestParam("email").(string)
password = c.GetRequestParam("password").(string)
roles := c.GetRequesForm("roles").([]string)
// check if email exists
var user models.User
res := c.GetGorm().Where("email = ?", c.CastToString(email)).First(&user)
if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) {
c.GetLogger().Error(res.Error.Error())
errormessages = append(errormessages, "Error")
}
if res.Error == nil {
errormessages = append(errormessages, "Error, email already exists in the database")
}
// validation data
data := map[string]interface{}{
"name": name,
"fullname": fullname,
"email": email,
"password": password,
}
// validation rules
rules := map[string]interface{}{
"name": "required|alphaNumeric",
"fullname": "required",
"email": "required|email",
"password": "required|length:6,20",
}
// validate
v := c.GetValidator().Validate(data, rules)
if v.Failed() {
c.GetLogger().Error(v.GetErrorMessagesJson())
for _, v := range v.GetErrorMessagesMap() {
errormessages = append(errormessages, v)
}
}
if len(errormessages) == 0 {
//hash the password
passwordHashed, _ := c.GetHashing().HashPassword(c.CastToString(password))
// store the record in db
user = models.User{
Name: c.CastToString(name),
Fullname: c.CastToString(fullname),
Email: c.CastToString(email),
Password: passwordHashed,
}
res = c.GetGorm().Create(&user)
if res.Error != nil {
c.GetLogger().Error(res.Error.Error())
errormessages = append(errormessages, res.Error.Error())
} else {
// assign roles
for _, role := range roles {
auth.AssignRoleToUser(c, user.ID, role)
}
// fire user registered event
err := c.GetEventsManager().Fire(&core.Event{Name: events.USER_REGISTERED, Payload: map[string]interface{}{
"user": user,
}})
if err != nil {
c.GetLogger().Error(err.Error())
errormessages = append(errormessages, "Internal server error")
} else {
// redirect to list
return c.Response.Redirect("/admin/users")
}
}
}
}
// -- response template
type templateData struct {
FieldName components.FormInput
FieldFullname components.FormInput
FieldEmail components.FormInput
FieldRoles components.FormCheckbox
FieldPassword components.FormInput
ErrorMessages []string
SubmitButton components.FormButton
}
tmplData := templateData{
FieldName: components.FormInput{
ID: "name",
Label: "Name",
Type: "text",
Value: name,
IsRequired: true,
},
FieldFullname: components.FormInput{
ID: "fullname",
Label: "Full name",
Type: "text",
Value: fullname,
IsRequired: true,
},
FieldEmail: components.FormInput{
ID: "email",
Label: "e-mail",
Type: "text",
Value: email,
//Autocomplete: true,
IsRequired: true,
},
FieldRoles: components.FormCheckbox{
Label: "Roles",
AllCheckbox: listroles,
},
FieldPassword: components.FormInput{
ID: "password",
Label: "Password",
Type: "password",
Value: password,
IsRequired: true,
},
SubmitButton: components.FormButton{
ID: "submit",
Text: "Add user",
Value: "submit",
IsSubmit: true,
TypeClass: "primary",
},
ErrorMessages: errormessages,
}
return c.Response.Template("admin_useradd.html", tmplData)
}
func AdminUsersEdit(c *core.Context) *core.Response {
// check if is submit
submit := c.GetRequestParam("submit").(string)
user_id := c.GetPathParam("id")
errormessages := make([]string, 0)
// initiate authority
auth := new(utils.Authority)
var listroles []components.FormCheckboxItem
systemroles, _ := auth.GetAllRoles(c)
user_id_uint, _ := strconv.ParseUint(user_id.(string), 10, 32)
userroles, _ := auth.GetUserRoles(c, uint(user_id_uint))
for _, systemrole := range systemroles {
var userrole components.FormCheckboxItem
userrole.Label = systemrole.Name
userrole.Name = "roles"
userrole.Value = systemrole.Slug
for _, ur := range userroles {
if ur.Slug == systemrole.Slug {
userrole.IsChecked = true
break
}
}
listroles = append(listroles, userrole)
}
var origin_user models.User
db := c.GetGorm()
// check if existes
result_db := db.First(&origin_user, user_id)
if result_db.RowsAffected == 0 {
c.GetLogger().Error("User ID not found")
return c.Response.Redirect("/admin/users")
}
name := origin_user.Name
fullname := origin_user.Fullname
email := origin_user.Email
password := ""
user_id_string := c.GetPathParam("id").(string)
if submit != "" {
name = c.GetRequestParam("name").(string)
fullname = c.GetRequestParam("fullname").(string)
email = c.GetRequestParam("email").(string)
password = c.GetRequestParam("password").(string)
roles := c.GetRequesForm("roles").([]string)
key := c.GetRequestParam("key")
// check if email exists
var user models.User
res := c.GetGorm().Where("email = ? AND id != ?", c.CastToString(email), key).First(&user)
if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) {
c.GetLogger().Error(res.Error.Error())
errormessages = append(errormessages, "Error")
}
if res.Error == nil {
errormessages = append(errormessages, "Error, email already exists to another user")
}
// validation data
data := map[string]interface{}{
"name": name,
"fullname": fullname,
"email": email,
}
// validation rules
rules := map[string]interface{}{
"name": "required|alphaNumeric",
"fullname": "required",
"email": "required|email",
}
// nee update password
if password != "" {
data["password"] = password
rules["password"] = "required|length:6,20"
}
// validate
v := c.GetValidator().Validate(data, rules)
if v.Failed() {
c.GetLogger().Error(v.GetErrorMessagesJson())
for _, v := range v.GetErrorMessagesMap() {
errormessages = append(errormessages, v)
}
}
if len(errormessages) == 0 {
//hash the password
if password != "" {
passwordHashed, _ := c.GetHashing().HashPassword(c.CastToString(password))
origin_user.Password = passwordHashed
}
// store the record in db
origin_user.Name = name
origin_user.Fullname = fullname
origin_user.Email = email
result_db = db.Save(&origin_user)
if result_db.RowsAffected == 0 {
c.GetLogger().Error("Admin user: error updating")
errormessages = append(errormessages, fmt.Sprintf("Error updating user %s:", user_id_string))
} else {
// delete roles
auth.RevokeAllUserRole(c, origin_user.ID)
// assign roles
for _, role := range roles {
auth.AssignRoleToUser(c, origin_user.ID, role)
}
return c.Response.Redirect("/admin/users")
}
}
}
// -- response template
type templateData struct {
FieldName components.FormInput
FieldFullname components.FormInput
FieldEmail components.FormInput
FieldRoles components.FormCheckbox
FieldPassword components.FormInput
FieldKey components.FormInput
ErrorMessages []string
SubmitButton components.FormButton
DeleteButton components.FormButton
}
tmplData := templateData{
FieldName: components.FormInput{
ID: "name",
Label: "Name",
Type: "text",
Value: name,
IsRequired: true,
},
FieldFullname: components.FormInput{
ID: "fullname",
Label: "Full name",
Type: "text",
Value: fullname,
IsRequired: true,
},
FieldEmail: components.FormInput{
ID: "email",
Label: "e-mail",
Type: "text",
Value: email,
//Autocomplete: true,
IsRequired: true,
},
FieldRoles: components.FormCheckbox{
Label: "Roles",
AllCheckbox: listroles,
},
FieldPassword: components.FormInput{
ID: "password",
Label: "Password",
Type: "password",
Hint: "Leave blank if you don't want to change it",
Value: password,
IsRequired: false,
},
FieldKey: components.FormInput{
ID: "key",
Type: "hidden",
Value: user_id_string,
},
SubmitButton: components.FormButton{
ID: "submit",
Text: "Update user",
Value: "submit",
IsSubmit: true,
TypeClass: "primary",
},
DeleteButton: components.FormButton{
ID: "submit",
Text: "Delete user",
Value: "submit",
IsSubmit: true,
TypeClass: "warning",
},
ErrorMessages: errormessages,
}
return c.Response.Template("admin_useredit.html", tmplData)
}
func AdminUsersDelete(c *core.Context) *core.Response {
user_id := c.GetRequestParam("key").(string)
errormessages := make([]string, 0)
warningmessages := make([]string, 0)
var origin_user models.User
db := c.GetGorm()
// check if existes
result_db := db.First(&origin_user, user_id)
if result_db.RowsAffected == 0 {
errormessages = append(errormessages, "User ID not found")
} else {
// check if is the seed user
seed := "1"
if user_id == seed {
errormessages = append(errormessages, "You can't delete the seed user")
}
}
// sample warning message
warningmessages = append(warningmessages, fmt.Sprintf("Are you sure you want to cancel the account %s?", origin_user.Name))
// -- response template
type templateData struct {
ErrorMessages []string
WarningMessages []string
FieldKey components.FormInput
ConfirmButton components.FormButton
BackButton components.ContentHref
}
tmplData := templateData{
FieldKey: components.FormInput{
ID: "key",
Type: "hidden",
Value: user_id,
},
ConfirmButton: components.FormButton{
ID: "submit",
Text: "Confirm",
Value: "submit",
IsSubmit: true,
TypeClass: "primary",
},
BackButton: components.ContentHref{
Link: "/admin/users",
Text: "Cancel",
IsButton: true,
},
ErrorMessages: errormessages,
WarningMessages: warningmessages,
}
return c.Response.Template("admin_confirmuserdel.html", tmplData)
}
func AdminUsersDelConfirm(c *core.Context) *core.Response {
user_id := c.GetRequestParam("key").(string)
var origin_user models.User
db := c.GetGorm()
// check if existes
result_db := db.First(&origin_user, user_id)
if result_db.RowsAffected != 0 {
// check if is the seed user
seed := "1"
if user_id != seed {
// initiate authority
auth := new(utils.Authority)
// Delete the user
// fire user delete event
err := c.GetEventsManager().Fire(&core.Event{Name: events.USER_DELETED, Payload: map[string]interface{}{
"user": origin_user,
}})
if err != nil {
c.GetLogger().Error(err.Error())
}
auth.RevokeAllUserRole(c, origin_user.ID)
result_db.Unscoped().Delete(&origin_user)
}
}
return c.Response.Redirect("/admin/users")
}

View file

@ -22,12 +22,11 @@ import (
"git.smarteching.com/goffee/cup/utils"
"github.com/google/uuid"
"gorm.io/gorm"
"git.smarteching.com/goffee/core/template/components"
)
func Signup(c *core.Context) *core.Response {
name := c.GetRequestParam("name")
fullname := c.GetRequestParam("fullname")
email := c.GetRequestParam("email")
password := c.GetRequestParam("password")
// check if email exists
@ -48,14 +47,16 @@ func Signup(c *core.Context) *core.Response {
// validation data
data := map[string]interface{}{
"name": name,
"fullname": fullname,
"email": email,
"password": password,
}
// validation rules
rules := map[string]interface{}{
"name": "required|alphaNumeric",
"fullname": "required",
"email": "required|email",
"password": "required|length:6,10",
"password": "required|length:6,20",
}
// validate
v := c.GetValidator().Validate(data, rules)
@ -75,6 +76,7 @@ func Signup(c *core.Context) *core.Response {
// store the record in db
user = models.User{
Name: c.CastToString(name),
Fullname: c.CastToString(fullname),
Email: c.CastToString(email),
Password: passwordHashed,
}
@ -223,6 +225,12 @@ func Signin(c *core.Context) *core.Response {
userAgent := c.GetUserAgent()
hashedCacheKey := utils.CreateAuthTokenHashedCacheKey(user.ID, userAgent)
err = c.GetCache().Set(hashedCacheKey, token)
// delete data from old sessions
sessionKey := fmt.Sprintf("sess_%v", userAgent)
hashedSessionKey := utils.CreateAuthTokenHashedCacheKey(user.ID, sessionKey)
_ = c.GetCache().Delete(hashedSessionKey)
if err != nil {
c.GetLogger().Error(err.Error())
if TemplateEnable {
@ -421,8 +429,28 @@ func SetNewPassword(c *core.Context) *core.Response {
}
func Signout(c *core.Context) *core.Response {
tokenRaw := c.GetHeader("Authorization")
token := strings.TrimSpace(strings.Replace(tokenRaw, "Bearer", "", 1))
// check if template engine is enable
TemplateEnableStr := os.Getenv("TEMPLATE_ENABLE")
if TemplateEnableStr == "" {
TemplateEnableStr = "false"
}
TemplateEnable, _ := strconv.ParseBool(TemplateEnableStr)
token := ""
if TemplateEnable {
// get cookie
usercookie, err := c.GetCookie()
if err != nil {
}
token = usercookie.Token
} else {
tokenRaw := c.GetHeader("Authorization")
token = strings.TrimSpace(strings.Replace(tokenRaw, "Bearer", "", 1))
}
if token == "" {
return c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{
"message": "unauthorized",
@ -448,43 +476,3 @@ func Signout(c *core.Context) *core.Response {
"message": "signed out successfully",
}))
}
// 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 sample
func AppSample(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: "Protected page",
CardBody: "If you can see this page, your are loggedin",
},
}
//fmt.Printf("Outside cookie user is: %s", user.Email)
return c.Response.Template("app.html", tmplData)
}

View file

@ -0,0 +1,52 @@
// 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"
"log"
"time"
"git.smarteching.com/goffee/core"
"git.smarteching.com/goffee/cup/workers"
"github.com/hibiken/asynq"
)
// Make samples queues
func Queuesample(c *core.Context) *core.Response {
// Get client queue asynq
client := c.GetQueueClient()
// Create a task with typename and payload.
payload, err := json.Marshal(workers.EmailTaskPayload{UserID: 42})
if err != nil {
log.Fatal(err)
}
t1 := asynq.NewTask(workers.TypeWelcomeEmail, payload)
t2 := asynq.NewTask(workers.TypeReminderEmail, payload)
// Process the task immediately.
info, err := client.Enqueue(t1)
if err != nil {
log.Fatal(err)
}
log.Printf(" [*] Successfully enqueued task: %+v", info)
// Process 2 task 1 min later.
for i := 1; i < 3; i++ {
info, err = client.Enqueue(t2, asynq.ProcessIn(1*time.Minute))
if err != nil {
log.Fatal(err)
}
log.Printf(" [*] Successfully enqueued task: %+v", info)
}
message := "{\"message\": \"Task queued\"}"
return c.Response.Json(message)
}

View file

@ -5,8 +5,11 @@
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
@ -28,3 +31,119 @@ func Sample(c *core.Context) *core.Response {
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)
}

View file

@ -6,6 +6,7 @@ package controllers
import (
"fmt"
"math/rand/v2"
"os"
"strconv"
@ -114,10 +115,10 @@ func Themeform(c *core.Context) *core.Response {
optionc.Value = "buenosaires"
optionc.Label = "Buenos Aires"
allOptionsc = append(allOptionsc, optionc)
optionc.ID = "sogas"
optionc.Name = "sogas"
optionc.Value = "Sogamoso"
optionc.Label = "Sogamoso"
optionc.ID = "london"
optionc.Name = "london"
optionc.Value = "london"
optionc.Label = "London"
//optionc.IsChecked = true
allOptionsc = append(allOptionsc, optionc)
@ -642,7 +643,9 @@ func Themecontent(c *core.Context) *core.Response {
// first, include all compoments
type templateData struct {
ContentTable components.ContentTable
ContentTable components.ContentTable
ContentTabledetail components.ContentTabledetail
ContentGraph components.ContentGraph
}
// TABLES
@ -686,6 +689,40 @@ func Themecontent(c *core.Context) *core.Response {
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{
@ -693,7 +730,19 @@ func Themecontent(c *core.Context) *core.Response {
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 {

View file

@ -2,5 +2,6 @@ package events
// event names
const USER_REGISTERED = "user-registered"
const USER_DELETED = "user-deleted"
const USER_PASSWORD_RESET_REQUESTED = "user-password-reset-requested"
const PASSWORD_CHANGED = "password-changed"

54
go.mod
View file

@ -10,41 +10,55 @@ replace (
go 1.23.1
require (
git.smarteching.com/goffee/core v1.7.3
git.smarteching.com/goffee/core v1.9.0
github.com/google/uuid v1.6.0
github.com/hibiken/asynq v0.25.1
github.com/joho/godotenv v1.5.1
github.com/julienschmidt/httprouter v1.3.0
gorm.io/gorm v1.25.12
)
require (
filippo.io/edwards25519 v1.1.0 // indirect
git.smarteching.com/zeni/go-chart/v2 v2.1.4 // indirect
github.com/SparkPost/gosparkpost v0.2.0 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/go-chi/chi/v5 v5.0.8 // indirect
github.com/go-chi/chi/v5 v5.1.0 // indirect
github.com/go-ozzo/ozzo-validation v3.6.0+incompatible // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/golang-jwt/jwt/v5 v5.0.0 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/harranali/mailing v1.2.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.3.1 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.7.1 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.10 // indirect
github.com/mailgun/mailgun-go/v4 v4.10.0 // indirect
github.com/mattn/go-sqlite3 v1.14.17 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailgun/errors v0.4.0 // indirect
github.com/mailgun/mailgun-go/v4 v4.17.3 // indirect
github.com/mattn/go-sqlite3 v1.14.24 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/redis/go-redis/v9 v9.0.5 // indirect
github.com/redis/go-redis/v9 v9.7.0 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/sendgrid/rest v2.6.9+incompatible // indirect
github.com/sendgrid/sendgrid-go v3.12.0+incompatible // indirect
golang.org/x/crypto v0.11.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/text v0.14.0 // indirect
gorm.io/driver/mysql v1.5.1 // indirect
gorm.io/driver/postgres v1.5.2 // indirect
gorm.io/driver/sqlite v1.5.2 // indirect
github.com/sendgrid/sendgrid-go v3.16.0+incompatible // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spf13/cast v1.7.0 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/image v0.21.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.27.0 // indirect
golang.org/x/text v0.19.0 // indirect
golang.org/x/time v0.8.0 // indirect
google.golang.org/protobuf v1.35.2 // indirect
gorm.io/driver/mysql v1.5.7 // indirect
gorm.io/driver/postgres v1.5.9 // indirect
gorm.io/driver/sqlite v1.5.6 // indirect
)

123
go.sum
View file

@ -1,19 +1,23 @@
git.smarteching.com/goffee/core v1.7.3 h1:GlZ7B/QwAQ6eSQcYBtqlglZoqA7tFYiXqvV2z27xuQY=
git.smarteching.com/goffee/core v1.7.3/go.mod h1:QQNIHVN6qjJBtq42WCQMrLYN9oFE3wm26SLU8ZxNTec=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
git.smarteching.com/goffee/core v1.8.4 h1:XB+vpe7e8muiDChRVDaJ1TG7H+/FBxDQcMfWp4zloPs=
git.smarteching.com/goffee/core v1.8.4/go.mod h1:JxXDvTQU2shKYY6c9aS3s6sFh7mEDzgmjzdc85HhBV8=
git.smarteching.com/zeni/go-chart/v2 v2.1.4 h1:pF06+F6eqJLIG8uMiTVPR5TygPGMjM/FHMzTxmu5V/Q=
git.smarteching.com/zeni/go-chart/v2 v2.1.4/go.mod h1:b3ueW9h3pGGXyhkormZAvilHaG4+mQti+bMNPdQBeOQ=
github.com/SparkPost/gosparkpost v0.2.0 h1:yzhHQT7cE+rqzd5tANNC74j+2x3lrPznqPJrxC1yR8s=
github.com/SparkPost/gosparkpost v0.2.0/go.mod h1:S9WKcGeou7cbPpx0kTIgo8Q69WZvUmVeVzbD+djalJ4=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/brianvoe/gofakeit/v6 v6.21.0 h1:tNkm9yxEbpuPK8Bx39tT4sSc5i9SUGiciLdNix+VDQY=
github.com/brianvoe/gofakeit/v6 v6.21.0/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8=
github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w=
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/buger/jsonparser v1.0.0/go.mod h1:tgcrVJ81GPSF0mz+0nu1Xaz0fazGPrmmJfJtxjbHhUQ=
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a/go.mod h1:2GxOXOlEPAMFPfp014mK1SWq8G8BN8o7/dfYqJrVGn8=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -25,27 +29,34 @@ github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojt
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.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
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/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/harranali/mailing v1.2.0 h1:ihIyJwB8hyRVcdk+v465wk1PHMrSrgJqo/kMd+gZClY=
github.com/harranali/mailing v1.2.0/go.mod h1:4a5N3yG98pZKluMpmcYlTtll7bisvOfGQEMIng3VQk4=
github.com/hibiken/asynq v0.25.1 h1:phj028N0nm15n8O2ims+IvJ2gz4k2auvermngh9JhTw=
github.com/hibiken/asynq v0.25.1/go.mod h1:pazWNOLBu0FEynQRBvHA26qdIKRSmfdIfUm4HdsLmXg=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU=
github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs=
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/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
github.com/jhillyerd/enmime v0.8.0/go.mod h1:MBHs3ugk03NGjMM6PuRynlKf+HA5eSillZ+TRCm73AE=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
@ -54,62 +65,84 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/mailgun/mailgun-go/v4 v4.10.0 h1:e5LVsxpqjOYRyaOWifrJORoLQZTYDP+g4ljfmf9G2zE=
github.com/mailgun/mailgun-go/v4 v4.10.0/go.mod h1:L9s941Lgk7iB3TgywTPz074pK2Ekkg4kgbnAaAyJ2z8=
github.com/mailgun/errors v0.4.0 h1:6LFBvod6VIW83CMIOT9sYNp28TCX0NejFPP4dSX++i8=
github.com/mailgun/errors v0.4.0/go.mod h1:xGBaaKdEdQT0/FhwvoXv4oBaqqmVZz9P1XEnvD/onc0=
github.com/mailgun/mailgun-go/v4 v4.17.3 h1:WoO48/VeXgAVSzjgzyeLvF08AoPzWU2EBz79INN8rEA=
github.com/mailgun/mailgun-go/v4 v4.17.3/go.mod h1:0ood70bQR/SffQ9NxIsAY06H+HG0hrvMVApfUp9TihI=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
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/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/redis/go-redis/v9 v9.0.5 h1:CuQcn5HIEeK7BgElubPP8CGtE0KakrnbBSTLjathl5o=
github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0=
github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE=
github.com/sendgrid/sendgrid-go v3.12.0+incompatible h1:/N2vx18Fg1KmQOh6zESc5FJB8pYwt5QFBDflYPh1KVg=
github.com/sendgrid/sendgrid-go v3.12.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
github.com/sendgrid/sendgrid-go v3.16.0+incompatible h1:i8eE6IMkiCy7vusSdacHHSBUpXyTcTXy/Rl9N9aZ/Qw=
github.com/sendgrid/sendgrid-go v3.16.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s=
golang.org/x/image v0.21.0/go.mod h1:vUbsLavqK/W303ZroQQVKQ+Af3Yl6Uz1Ppu5J/cLz78=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.5.1 h1:WUEH5VF9obL/lTtzjmML/5e6VfFR/788coz2uaVCAZw=
gorm.io/driver/mysql v1.5.1/go.mod h1:Jo3Xu7mMhCyj8dlrb3WoCaRd1FhsVh+yMXb1jUInf5o=
gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0=
gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8=
gorm.io/driver/sqlite v1.5.2 h1:TpQ+/dqCY4uCigCFyrfnrJnrW9zjpelWVoEVNy5qJkc=
gorm.io/driver/sqlite v1.5.2/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8=
gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
gorm.io/driver/sqlite v1.5.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE=
gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=

16
main.go
View file

@ -26,14 +26,25 @@ var resources embed.FS
func main() {
app := core.New()
basePath, err := os.Getwd()
runMode := "dev"
if len(os.Args) > 1 {
if os.Args[1] == "prod" || os.Args[1] == "dev" {
runMode = os.Args[1]
}
}
if err != nil {
log.Fatal("error getting current working dir")
}
app.SetBasePath(basePath)
app.SetRunMode(runMode)
app.MakeDirs("logs", "storage", "storage/sqlite", "tls")
// Handle the reading of the .env file
if config.GetEnvFileConfig().UseDotEnvFile {
envVars, err := godotenv.Read(".env")
envfile := ".env-dev"
if runMode == "prod" {
envfile = ".env"
}
envVars, err := godotenv.Read(envfile)
if err != nil {
log.Fatal("Error loading .env file")
}
@ -51,6 +62,9 @@ func main() {
registerGlobalHooks()
registerRoutes()
registerEvents()
if config.GetQueueConfig().EnableQueue == true {
registerQueues()
}
if config.GetGormConfig().EnableGorm == true {
RunAutoMigrations()
}

11
models/base.go Normal file
View file

@ -0,0 +1,11 @@
// 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 models
type BaseModel struct {
ID uint `json:"id" gorm:"primaryKey; column:id"`
Created int64 `gorm:"autoCreateTime"`
Updated int64 `gorm:"autoUpdateTime"`
}

16
models/permission.go Normal file
View file

@ -0,0 +1,16 @@
// 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"
}

View file

@ -0,0 +1,16 @@
// 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"
}

16
models/roles.go Normal file
View file

@ -0,0 +1,16 @@
// 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"
}

16
models/user-roles.go Normal file
View file

@ -0,0 +1,16 @@
// 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"
}

View file

@ -5,11 +5,10 @@
package models
import "gorm.io/gorm"
type User struct {
gorm.Model
BaseModel
Name string
Fullname string
Email string
Password string
}

30
register-queues.go Normal file
View file

@ -0,0 +1,30 @@
// 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/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()
}

View file

@ -26,6 +26,7 @@ func registerRoutes() {
controller.Get("/themecontent", controllers.Themecontent)
controller.Get("/themepanel", controllers.Themedemo)
controller.Get("/themeelements", controllers.ThemeElements)
controller.Get("/queuesample", controllers.Queuesample)
// Uncomment the lines below to enable authentication
controller.Post("/signup", controllers.Signup)
@ -34,11 +35,29 @@ func registerRoutes() {
controller.Post("/reset-password", controllers.ResetPasswordRequest)
controller.Post("/reset-password/code/:code", controllers.SetNewPassword)
// Uncomment the lines below to enable user administration
controller.Get("/admin/users", controllers.AdminUsersList)
controller.Post("/admin/users", controllers.AdminUsersList)
controller.Get("/admin/users/add", controllers.AdminUsersAdd)
controller.Post("/admin/users/add", controllers.AdminUsersAdd)
controller.Get("/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/deleteconfirm", controllers.AdminUsersDelConfirm)
//controller.Get("/admin/users/roles", controllers.Signout)
//controller.Get("/admin/users/permissions", controllers.ResetPasswordRequest)
controller.Get("/dashboard", controllers.WelcomeToDashboard, hooks.AuthCheck)
// templates demos
controller.Get("/signout", controllers.Signout)
controller.Get("/appsample", controllers.AppSample, hooks.AuthCheck)
controller.Post("/appsample", controllers.AppSample, hooks.AuthCheck)
controller.Get("/applogin", controllers.AppLogin, hooks.CheckSessionCookie)
controller.Post("/applogin", controllers.AppLogin, hooks.CheckSessionCookie)
controller.Get("/appsession", controllers.AppSession)
controller.Post("/appsession", controllers.AppSession)
}

View file

@ -6,8 +6,12 @@
package main
import (
"errors"
"git.smarteching.com/goffee/core"
"git.smarteching.com/goffee/cup/models"
"git.smarteching.com/goffee/cup/utils"
"gorm.io/gorm"
)
func RunAutoMigrations() {
@ -17,5 +21,18 @@ func RunAutoMigrations() {
//##############################
// Add auto migrations for your models here...
db.AutoMigrate(&models.User{})
db.AutoMigrate(&models.UserRole{})
db.AutoMigrate(&models.Role{})
db.AutoMigrate(&models.RolePermission{})
db.AutoMigrate(&models.Permission{})
// End your auto migrations
// Create seed data data, DO NOT TOUCH
if err := db.AutoMigrate(&models.User{}); err == nil && db.Migrator().HasTable(&models.User{}) {
if err := db.First(&models.User{}).Error; errors.Is(err, gorm.ErrRecordNotFound) {
utils.CreateSeedData()
}
}
}

View file

@ -1 +1,14 @@
console.log("Start Goffee app");
console.log("Start Goffee app");
let elem = document.querySelector('#signout');
if (elem) {
document.getElementById("signout").onclick = (_event) => {
fetch('/signout').then(response => response.json())
.then(data => {
if (data['message'] == "signed out successfully") {
// Refresh the page
location.reload();
}
})
};
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

BIN
storage/sqlite/sqlite.db Normal file

Binary file not shown.

View file

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="en">
{{template "page_head" "Goffee"}}
<body>
<div class="container">
<h2>User delete confirmation</h2>
{{if .ErrorMessages }}
<div class="alert alert-dismissible alert-warning">
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
<ul>
{{range $i, $a := .ErrorMessages}}
<li>{{$a}}</li>
{{end}}
</ul>
</div>
{{else}}
{{if .WarningMessages }}
<ul class="warningmessages">
{{range $o, $u := .WarningMessages}}
<li>{{$u}}</li>
{{end}}
</ul>
{{end}}
<form method="POST" id="confirmdelete_user" action="/admin/users/deleteconfirm">
{{template "form_input" .FieldKey}}
{{template "form_button" .ConfirmButton}}
</form>
{{end}}
<hr>
{{template "content_href" .BackButton}}
</div>
{{template "page_footer"}}
</body>
</html>

View file

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en">
{{template "page_head" "Goffee"}}
<body>
<div class="container">
<h2>Add user form</h2>
{{if .ErrorMessages }}<div class="alert alert-dismissible alert-warning">
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
<ul>
{{range $i, $a := .ErrorMessages}}
<li>{{$a}}</li>
{{end}}
</ul>
</div>{{end}}
<form method="POST" id="add_user" action="/admin/users/add">
{{template "form_input" .FieldName}}
{{template "form_input" .FieldFullname}}
{{template "form_input" .FieldEmail}}
{{template "form_checkbox" .FieldRoles}}
{{template "form_input" .FieldPassword}}
<hr>
{{template "form_button" .SubmitButton}}
</form>
</div>
{{template "page_footer"}}
</body>
</html>

View file

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="en">
{{template "page_head" "Goffee"}}
<body>
<div class="container">
<h2>Edit user form</h2>
{{if .ErrorMessages }}<div class="alert alert-dismissible alert-warning">
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
<ul>
{{range $i, $a := .ErrorMessages}}
<li>{{$a}}</li>
{{end}}
</ul>
</div>{{end}}
<form method="POST" id="add_user" action="/admin/users/edit/{{.FieldKey.Value}}">
{{template "form_input" .FieldName}}
{{template "form_input" .FieldFullname}}
{{template "form_input" .FieldEmail}}
{{template "form_checkbox" .FieldRoles}}
{{template "form_input" .FieldPassword}}
{{template "form_input" .FieldKey}}
{{template "form_button" .SubmitButton}}
</form>
<hr>
<form method="POST" id="del_user" action="/admin/users/delete">
{{template "form_input" .FieldKey}}
{{template "form_button" .DeleteButton}}
</form>
</div>
{{template "page_footer"}}
</body>
</html>

View file

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
{{template "page_head" "Goffee"}}
<body>
<div class="container">
<div class="d-flex justify-content-between mb-2">
<h2>Users</h2>
<div class=" align-self-end ">
{{template "content_href" .AddButton}}
</div>
</div>
<div class="section table-container">
{{template "content_table" .TableUsers}}
</div>
</div>
{{template "page_footer"}}
</body>
</html>

View file

@ -4,8 +4,10 @@
<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>

View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
{{template "page_head" "Sample page test session vars"}}
<body>
<div class="container">
<div class="row">
{{template "page_card" .PageCard}}
</div>
</div>
{{template "page_footer"}}
</body>
</html>

View file

@ -8,6 +8,15 @@
<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"}}

View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
{{template "page_head" "No permission"}}
<body>
<div class="container">
<div class="row">
You do not have permission to visit this page.
</div>
</div>
{{template "page_footer"}}
</body>
</html>

436
utils/authority.go Normal file
View file

@ -0,0 +1,436 @@
// 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
}

View file

@ -8,8 +8,69 @@ package utils
import (
"crypto/md5"
"fmt"
"log"
"time"
"git.smarteching.com/goffee/core"
"git.smarteching.com/goffee/cup/models"
)
func CreateSeedData() {
db := core.ResolveGorm()
var hashing = new(core.Hashing)
var role models.Role
// seed user
password := "goffee"
name := "admin"
fullname := "Goffee administrator"
email := "change@me.com"
passwordHashed, _ := hashing.HashPassword(password)
user := models.User{
Name: name,
Fullname: fullname,
Email: email,
Password: passwordHashed,
}
result := db.Create(&user)
if result.Error != nil {
log.Fatal("Can't create seed user in database")
}
// seed roles
roles := []models.Role{
{Name: "Administrator", Slug: "admin"},
{Name: "Authenticated", Slug: "authenticated"},
}
for _, role := range roles {
result = db.Create(&role)
if result.Error != nil {
log.Fatal("Can't create seed role in database")
}
}
// seed permission
permission := models.Permission{Name: "Users administration", Slug: "admin-users"}
result = db.Create(&permission)
if result.Error != nil {
log.Fatal("Can't create seed permission in database")
}
result = db.Where("slug = ?", "admin").First(&role)
if result.Error != nil {
log.Fatal("Can't find user admin in database")
}
result = db.Create(&models.RolePermission{RoleID: role.ID, PermissionID: permission.ID})
if result.Error != nil {
log.Fatal("Can't register permission role in database")
}
result = db.Create(&models.UserRole{UserID: user.ID, RoleID: role.ID})
if result.Error != nil {
log.Fatal("Can't assign role administrator to user in database")
}
}
// generate a hashed string to be used as key for caching auth jwt token
func CreateAuthTokenHashedCacheKey(userID uint, userAgent string) string {
cacheKey := fmt.Sprintf("userid:_%v_useragent:_%v_jwt_token", userID, userAgent)
@ -17,3 +78,7 @@ func CreateAuthTokenHashedCacheKey(userID uint, userAgent string) string {
return hashedCacheKey
}
func FormatUnix(value int64) string {
return time.Unix(value, 0).Format("2006-01-02 15:04:05")
}

170
utils/session.go Normal file
View 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
}

39
workers/workers.go Normal file
View file

@ -0,0 +1,39 @@
package workers
import (
"context"
"encoding/json"
"log"
"github.com/hibiken/asynq"
)
// A list of task types.
const (
TypeWelcomeEmail = "email:welcome"
TypeReminderEmail = "email:reminder"
)
// Task payload for any email related tasks.
type EmailTaskPayload struct {
// ID for the email recipient.
UserID int
}
func HandleWelcomeEmailTask(ctx context.Context, t *asynq.Task) error {
var p EmailTaskPayload
if err := json.Unmarshal(t.Payload(), &p); err != nil {
return err
}
log.Printf(" [*] Send Welcome Email to User %d", p.UserID)
return nil
}
func HandleReminderEmailTask(ctx context.Context, t *asynq.Task) error {
var p EmailTaskPayload
if err := json.Unmarshal(t.Payload(), &p); err != nil {
return err
}
log.Printf(" [*] Send Reminder Email to User %d", p.UserID)
return nil
}