develop #9
12 changed files with 626 additions and 7 deletions
|
@ -73,9 +73,9 @@ then `Goffee` locates the matching [handler](https://git.smarteching.com/goffee/
|
||||||
│ ├── config/ --------------------------> main configs
|
│ ├── config/ --------------------------> main configs
|
||||||
│ ├── events/ --------------------------> contains events
|
│ ├── events/ --------------------------> contains events
|
||||||
│ │ ├── jobs/ ------------------------> contains the event jobs
|
│ │ ├── jobs/ ------------------------> contains the event jobs
|
||||||
│ ├── controllers/ ------------------------> route's controllers
|
│ ├── controllers/ ---------------------> route's controllers
|
||||||
│ ├── logs/ ----------------------------> app log files
|
│ ├── logs/ ----------------------------> app log files
|
||||||
│ ├── hooks/ ---------------------> app hooks
|
│ ├── hooks/ ---------------------------> app hooks
|
||||||
│ ├── models/ --------------------------> database models
|
│ ├── models/ --------------------------> database models
|
||||||
│ ├── storage/ -------------------------> a place to store files
|
│ ├── storage/ -------------------------> a place to store files
|
||||||
│ ├── tls/ -----------------------------> tls certificates
|
│ ├── tls/ -----------------------------> tls certificates
|
||||||
|
@ -86,7 +86,7 @@ then `Goffee` locates the matching [handler](https://git.smarteching.com/goffee/
|
||||||
│ ├── main.go --------------------------> go main file
|
│ ├── main.go --------------------------> go main file
|
||||||
│ ├── README.md ------------------------> readme file
|
│ ├── README.md ------------------------> readme file
|
||||||
│ ├── register-events.go ---------------> register events and jobs
|
│ ├── 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
|
│ ├── routes.go ------------------------> app routes
|
||||||
│ ├── run-auto-migrations.go -----------> database migrations
|
│ ├── run-auto-migrations.go -----------> database migrations
|
||||||
```
|
```
|
478
controllers/adminusers.go
Normal file
478
controllers/adminusers.go
Normal file
|
@ -0,0 +1,478 @@
|
||||||
|
// 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 {
|
||||||
|
|
||||||
|
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: "Created",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: "Updated",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ValueType: "href",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
rows := make([][]components.ContentTableTD, len(users))
|
||||||
|
for i, u := range users {
|
||||||
|
row := []components.ContentTableTD{
|
||||||
|
{Value: strconv.Itoa(int(u.ID))},
|
||||||
|
{Value: u.Name},
|
||||||
|
{Value: u.Fullname},
|
||||||
|
{Value: u.Email},
|
||||||
|
{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 {
|
||||||
|
|
||||||
|
// check if is submit
|
||||||
|
submit := c.GetRequestParam("submit").(string)
|
||||||
|
|
||||||
|
errormessages := make([]string, 0)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
|
||||||
|
// 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
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
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 {
|
||||||
|
return c.Response.Redirect("/admin/users")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// -- response template
|
||||||
|
type templateData struct {
|
||||||
|
FieldName components.FormInput
|
||||||
|
FieldFullname components.FormInput
|
||||||
|
FieldEmail components.FormInput
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
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 {
|
||||||
|
// 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())
|
||||||
|
}
|
||||||
|
result_db.Unscoped().Delete(&origin_user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Response.Redirect("/admin/users")
|
||||||
|
|
||||||
|
}
|
|
@ -26,6 +26,7 @@ import (
|
||||||
|
|
||||||
func Signup(c *core.Context) *core.Response {
|
func Signup(c *core.Context) *core.Response {
|
||||||
name := c.GetRequestParam("name")
|
name := c.GetRequestParam("name")
|
||||||
|
fullname := c.GetRequestParam("fullname")
|
||||||
email := c.GetRequestParam("email")
|
email := c.GetRequestParam("email")
|
||||||
password := c.GetRequestParam("password")
|
password := c.GetRequestParam("password")
|
||||||
// check if email exists
|
// check if email exists
|
||||||
|
@ -46,14 +47,16 @@ func Signup(c *core.Context) *core.Response {
|
||||||
// validation data
|
// validation data
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"name": name,
|
"name": name,
|
||||||
|
"fullname": fullname,
|
||||||
"email": email,
|
"email": email,
|
||||||
"password": password,
|
"password": password,
|
||||||
}
|
}
|
||||||
// validation rules
|
// validation rules
|
||||||
rules := map[string]interface{}{
|
rules := map[string]interface{}{
|
||||||
"name": "required|alphaNumeric",
|
"name": "required|alphaNumeric",
|
||||||
|
"fullname": "required",
|
||||||
"email": "required|email",
|
"email": "required|email",
|
||||||
"password": "required|length:6,10",
|
"password": "required|length:6,20",
|
||||||
}
|
}
|
||||||
// validate
|
// validate
|
||||||
v := c.GetValidator().Validate(data, rules)
|
v := c.GetValidator().Validate(data, rules)
|
||||||
|
@ -73,6 +76,7 @@ func Signup(c *core.Context) *core.Response {
|
||||||
// store the record in db
|
// store the record in db
|
||||||
user = models.User{
|
user = models.User{
|
||||||
Name: c.CastToString(name),
|
Name: c.CastToString(name),
|
||||||
|
Fullname: c.CastToString(fullname),
|
||||||
Email: c.CastToString(email),
|
Email: c.CastToString(email),
|
||||||
Password: passwordHashed,
|
Password: passwordHashed,
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,5 +2,6 @@ package events
|
||||||
|
|
||||||
// event names
|
// event names
|
||||||
const USER_REGISTERED = "user-registered"
|
const USER_REGISTERED = "user-registered"
|
||||||
|
const USER_DELETED = "user-deleted"
|
||||||
const USER_PASSWORD_RESET_REQUESTED = "user-password-reset-requested"
|
const USER_PASSWORD_RESET_REQUESTED = "user-password-reset-requested"
|
||||||
const PASSWORD_CHANGED = "password-changed"
|
const PASSWORD_CHANGED = "password-changed"
|
||||||
|
|
11
models/base.go
Normal file
11
models/base.go
Normal 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"`
|
||||||
|
}
|
|
@ -5,11 +5,10 @@
|
||||||
|
|
||||||
package models
|
package models
|
||||||
|
|
||||||
import "gorm.io/gorm"
|
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
gorm.Model
|
BaseModel
|
||||||
Name string
|
Name string
|
||||||
|
Fullname string
|
||||||
Email string
|
Email string
|
||||||
Password string
|
Password string
|
||||||
}
|
}
|
||||||
|
|
12
routes.go
12
routes.go
|
@ -34,6 +34,18 @@ func registerRoutes() {
|
||||||
controller.Post("/reset-password", controllers.ResetPasswordRequest)
|
controller.Post("/reset-password", controllers.ResetPasswordRequest)
|
||||||
controller.Post("/reset-password/code/:code", controllers.SetNewPassword)
|
controller.Post("/reset-password/code/:code", controllers.SetNewPassword)
|
||||||
|
|
||||||
|
// 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)
|
controller.Get("/dashboard", controllers.WelcomeToDashboard, hooks.AuthCheck)
|
||||||
|
|
||||||
// templates demos
|
// templates demos
|
||||||
|
|
34
storage/templates/admin_confirmuserdel.html
Normal file
34
storage/templates/admin_confirmuserdel.html
Normal 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>
|
26
storage/templates/admin_useradd.html
Normal file
26
storage/templates/admin_useradd.html
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<!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_input" .FieldPassword}}
|
||||||
|
<hr>
|
||||||
|
{{template "form_button" .SubmitButton}}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{{template "page_footer"}}
|
||||||
|
</body>
|
||||||
|
</html>
|
31
storage/templates/admin_useredit.html
Normal file
31
storage/templates/admin_useredit.html
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
<!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_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>
|
18
storage/templates/admin_userlist.html
Normal file
18
storage/templates/admin_userlist.html
Normal 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>
|
|
@ -8,6 +8,7 @@ package utils
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -17,3 +18,7 @@ func CreateAuthTokenHashedCacheKey(userID uint, userAgent string) string {
|
||||||
|
|
||||||
return hashedCacheKey
|
return hashedCacheKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func FormatUnix(value int64) string {
|
||||||
|
return time.Unix(value, 0).Format("2006-01-02 15:04:05")
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue