Compare commits
10 commits
Author | SHA1 | Date | |
---|---|---|---|
461c776bb7 | |||
7a32380274 | |||
d572509351 | |||
964c3c6089 | |||
273b43f5e0 | |||
d413d7def4 | |||
d4d4abb751 | |||
d431963181 | |||
43f3ad986e | |||
cac2986b59 |
19 changed files with 885 additions and 5 deletions
20
config/queue.go
Normal file
20
config/queue.go
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,29 @@ import (
|
||||||
|
|
||||||
func AdminUsersList(c *core.Context) *core.Response {
|
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
|
var users []models.User
|
||||||
db := c.GetGorm()
|
db := c.GetGorm()
|
||||||
db.Find(&users)
|
db.Find(&users)
|
||||||
|
@ -46,6 +69,10 @@ func AdminUsersList(c *core.Context) *core.Response {
|
||||||
Value: "Email",
|
Value: "Email",
|
||||||
ValueType: "string",
|
ValueType: "string",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Value: "Roles",
|
||||||
|
ValueType: "string",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Value: "Created",
|
Value: "Created",
|
||||||
},
|
},
|
||||||
|
@ -57,13 +84,26 @@ 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{
|
||||||
|
@ -93,10 +133,44 @@ 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)
|
||||||
|
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
|
// check if is submit
|
||||||
submit := c.GetRequestParam("submit").(string)
|
submit := c.GetRequestParam("submit").(string)
|
||||||
|
|
||||||
errormessages := make([]string, 0)
|
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 := ""
|
name := ""
|
||||||
fullname := ""
|
fullname := ""
|
||||||
|
@ -109,6 +183,7 @@ 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
|
||||||
|
@ -161,6 +236,11 @@ 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,
|
||||||
|
@ -180,6 +260,7 @@ 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
|
||||||
|
@ -208,6 +289,10 @@ 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",
|
||||||
|
@ -236,6 +321,29 @@ 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
|
||||||
|
|
||||||
|
@ -259,6 +367,7 @@ 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
|
||||||
|
@ -316,6 +425,14 @@ 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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -325,6 +442,7 @@ 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
|
||||||
|
@ -355,6 +473,10 @@ 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",
|
||||||
|
@ -461,6 +583,9 @@ 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{}{
|
||||||
|
@ -469,6 +594,7 @@ 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
52
controllers/queuesample.go
Normal file
52
controllers/queuesample.go
Normal 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)
|
||||||
|
|
||||||
|
}
|
8
go.mod
8
go.mod
|
@ -10,8 +10,9 @@ replace (
|
||||||
go 1.23.1
|
go 1.23.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.smarteching.com/goffee/core v1.8.4
|
git.smarteching.com/goffee/core v1.9.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
|
github.com/hibiken/asynq v0.25.1
|
||||||
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.25.12
|
gorm.io/gorm v1.25.12
|
||||||
|
@ -44,14 +45,19 @@ require (
|
||||||
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.7.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.0+incompatible // indirect
|
github.com/sendgrid/sendgrid-go v3.16.0+incompatible // indirect
|
||||||
github.com/sirupsen/logrus v1.9.3 // 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/crypto v0.28.0 // indirect
|
||||||
golang.org/x/image v0.21.0 // indirect
|
golang.org/x/image v0.21.0 // indirect
|
||||||
golang.org/x/net v0.30.0 // indirect
|
golang.org/x/net v0.30.0 // indirect
|
||||||
golang.org/x/sync v0.8.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/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/mysql v1.5.7 // indirect
|
||||||
gorm.io/driver/postgres v1.5.9 // indirect
|
gorm.io/driver/postgres v1.5.9 // indirect
|
||||||
gorm.io/driver/sqlite v1.5.6 // indirect
|
gorm.io/driver/sqlite v1.5.6 // indirect
|
||||||
|
|
12
go.sum
12
go.sum
|
@ -47,6 +47,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/harranali/mailing v1.2.0 h1:ihIyJwB8hyRVcdk+v465wk1PHMrSrgJqo/kMd+gZClY=
|
github.com/harranali/mailing v1.2.0 h1:ihIyJwB8hyRVcdk+v465wk1PHMrSrgJqo/kMd+gZClY=
|
||||||
github.com/harranali/mailing v1.2.0/go.mod h1:4a5N3yG98pZKluMpmcYlTtll7bisvOfGQEMIng3VQk4=
|
github.com/harranali/mailing v1.2.0/go.mod h1:4a5N3yG98pZKluMpmcYlTtll7bisvOfGQEMIng3VQk4=
|
||||||
|
github.com/hibiken/asynq v0.25.1 h1:phj028N0nm15n8O2ims+IvJ2gz4k2auvermngh9JhTw=
|
||||||
|
github.com/hibiken/asynq v0.25.1/go.mod h1:pazWNOLBu0FEynQRBvHA26qdIKRSmfdIfUm4HdsLmXg=
|
||||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
|
@ -89,6 +91,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
|
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
|
||||||
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
|
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
|
||||||
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
|
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
|
||||||
github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0=
|
github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0=
|
||||||
github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE=
|
github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE=
|
||||||
|
@ -96,6 +100,8 @@ github.com/sendgrid/sendgrid-go v3.16.0+incompatible h1:i8eE6IMkiCy7vusSdacHHSBU
|
||||||
github.com/sendgrid/sendgrid-go v3.16.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
|
github.com/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.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
|
||||||
|
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
|
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
@ -116,11 +122,17 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.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 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||||
|
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
|
||||||
|
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
|
||||||
|
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
|
3
main.go
3
main.go
|
@ -62,6 +62,9 @@ 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()
|
||||||
}
|
}
|
||||||
|
|
16
models/permission.go
Normal file
16
models/permission.go
Normal 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"
|
||||||
|
}
|
16
models/role-permissions.go
Normal file
16
models/role-permissions.go
Normal 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
16
models/roles.go
Normal 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
16
models/user-roles.go
Normal 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"
|
||||||
|
}
|
30
register-queues.go
Normal file
30
register-queues.go
Normal 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()
|
||||||
|
}
|
|
@ -26,6 +26,7 @@ func registerRoutes() {
|
||||||
controller.Get("/themecontent", controllers.Themecontent)
|
controller.Get("/themecontent", controllers.Themecontent)
|
||||||
controller.Get("/themepanel", controllers.Themedemo)
|
controller.Get("/themepanel", controllers.Themedemo)
|
||||||
controller.Get("/themeelements", controllers.ThemeElements)
|
controller.Get("/themeelements", controllers.ThemeElements)
|
||||||
|
controller.Get("/queuesample", controllers.Queuesample)
|
||||||
|
|
||||||
// Uncomment the lines below to enable authentication
|
// Uncomment the lines below to enable authentication
|
||||||
controller.Post("/signup", controllers.Signup)
|
controller.Post("/signup", controllers.Signup)
|
||||||
|
|
|
@ -6,8 +6,12 @@
|
||||||
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() {
|
||||||
|
@ -17,5 +21,18 @@ func RunAutoMigrations() {
|
||||||
//##############################
|
//##############################
|
||||||
|
|
||||||
// Add auto migrations for your models here...
|
// 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,8 @@
|
||||||
<form method="POST" id="add_user" action="/admin/users/add">
|
<form method="POST" id="add_user" action="/admin/users/add">
|
||||||
{{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}}
|
||||||
|
|
|
@ -15,7 +15,8 @@
|
||||||
<form method="POST" id="add_user" action="/admin/users/edit/{{.FieldKey.Value}}">
|
<form method="POST" id="add_user" action="/admin/users/edit/{{.FieldKey.Value}}">
|
||||||
{{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}}
|
||||||
|
|
12
storage/templates/nopermission.html
Normal file
12
storage/templates/nopermission.html
Normal 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
436
utils/authority.go
Normal 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
|
||||||
|
}
|
|
@ -8,9 +8,69 @@ package utils
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"time"
|
"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
|
// generate a hashed string to be used as key for caching auth jwt token
|
||||||
func CreateAuthTokenHashedCacheKey(userID uint, userAgent string) string {
|
func CreateAuthTokenHashedCacheKey(userID uint, userAgent string) string {
|
||||||
cacheKey := fmt.Sprintf("userid:_%v_useragent:_%v_jwt_token", userID, userAgent)
|
cacheKey := fmt.Sprintf("userid:_%v_useragent:_%v_jwt_token", userID, userAgent)
|
||||||
|
|
39
workers/workers.go
Normal file
39
workers/workers.go
Normal 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
|
||||||
|
}
|
Loading…
Reference in a new issue