diff --git a/.env-dev b/.env-dev deleted file mode 100644 index 800ca98..0000000 --- a/.env-dev +++ /dev/null @@ -1,91 +0,0 @@ -####################################### -###### 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=true -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 -LOG_STDOUT_ENABLE=true -LOG_LEVEL=debug - -####################################### -###### TEMPLATES ###### -####################################### -TEMPLATE_ENABLE=true -APP_ENABLE=true -CDNEnable=false -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!) diff --git a/.env-example b/.env-example index d0780b5..b83c72d 100644 --- a/.env-example +++ b/.env-example @@ -8,7 +8,6 @@ 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 @@ -19,10 +18,6 @@ App_KEY_FILE_PATH=tls/server.key ###### TEMPLATES ###### ####################################### TEMPLATE_ENABLE=true -COOKIE_SECRET=13d6b4dff8f84a10851021ec8608f814570d562c92fe6b5ec4c9f595bcb3234b -APP_ENABLE=true -CDNEnable=false - ####################################### ###### JWT ###### @@ -52,7 +47,7 @@ POSTGRES_SSL_MODE=disable POSTGRES_TIMEZONE=America/Argentina/Buenos_Aires #_____ SQLITE _____# -SQLITE_DB_PATH=storage/sqlite/sqlite.db +SQLITE_DB_PATH=storage/sqlite.db ####################################### ###### CACHE ###### diff --git a/.gitignore b/.gitignore index 90a928b..8d4682e 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,3 @@ logs/* tls/* !tls/.gitkeep .DS_Store -storage/sqlite/* -.idea -cup diff --git a/README.md b/README.md index 4ae95df..0eee0df 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![goffee logo](https://git.smarteching.com/avatars/cd7cd5b690adc8e5ec6d6cdb117f1bf5a9e9353dae111bfbb394d2c3d4497537?size=200) +![gocondor logo](https://git.smarteching.com/avatars/cd7cd5b690adc8e5ec6d6cdb117f1bf5a9e9353dae111bfbb394d2c3d4497537?size=200) # Cup of Goffee ## What is Goffee? @@ -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 ``` \ No newline at end of file diff --git a/config/queue.go b/config/queue.go deleted file mode 100644 index 8060572..0000000 --- a/config/queue.go +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) 2025 Zeni Kim -// Use of this source code is governed by MIT-style -// license that can be found in the LICENSE file. - -package config - -import "git.smarteching.com/goffee/core" - -// Retrieve the main config for the Queue -func GetQueueConfig() core.QueueConfig { - //##################################### - //# Main configuration for Queue ##### - //##################################### - - return core.QueueConfig{ - // For enabling and disabling the queue system - // set to true to enable it, set to false to disable - EnableQueue: false, - } -} diff --git a/controllers/adminusers.go b/controllers/adminusers.go deleted file mode 100644 index 9ffb762..0000000 --- a/controllers/adminusers.go +++ /dev/null @@ -1,604 +0,0 @@ -// Copyright (c) 2024 Zeni Kim -// Use of this source code is governed by MIT-style -// license that can be found in the LICENSE file. - -package controllers - -import ( - "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") - -} diff --git a/controllers/authentication.go b/controllers/authentication.go index 9e3eff9..a58b71d 100644 --- a/controllers/authentication.go +++ b/controllers/authentication.go @@ -22,11 +22,12 @@ 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 @@ -47,16 +48,14 @@ 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,20", + "password": "required|length:6,10", } // validate v := c.GetValidator().Validate(data, rules) @@ -76,7 +75,6 @@ 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, } @@ -225,12 +223,6 @@ 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 { @@ -429,28 +421,8 @@ func SetNewPassword(c *core.Context) *core.Response { } func Signout(c *core.Context) *core.Response { - - // 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)) - } - + 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", @@ -476,3 +448,43 @@ 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) + +} diff --git a/controllers/sample.go b/controllers/sample.go new file mode 100644 index 0000000..1243d5d --- /dev/null +++ b/controllers/sample.go @@ -0,0 +1,30 @@ +// Copyright (c) 2024 Zeni Kim +// Use of this source code is governed by MIT-style +// license that can be found in the LICENSE file. + +package controllers + +import ( + "git.smarteching.com/goffee/core" + "git.smarteching.com/goffee/core/template/components" +) + +// Show basic template +func Sample(c *core.Context) *core.Response { + + // first, include all compoments + type templateData struct { + PageCard components.PageCard + } + + // now fill data of the components + tmplData := templateData{ + PageCard: components.PageCard{ + CardTitle: "Framework Goffee", + CardBody: "Powered by Golang", + }, + } + + return c.Response.Template("basic.html", tmplData) + +} diff --git a/controllers/themedemo.go b/controllers/themedemo.go new file mode 100644 index 0000000..1373052 --- /dev/null +++ b/controllers/themedemo.go @@ -0,0 +1,706 @@ +// Copyright (c) 2024 Zeni Kim +// Use of this source code is governed by MIT-style +// license that can be found in the LICENSE file. + +package controllers + +import ( + "fmt" + "os" + "strconv" + + "git.smarteching.com/goffee/core" + "git.smarteching.com/goffee/core/template/components" +) + +// Show home page +func Themedemo(c *core.Context) *core.Response { + + // check if template engine is enabled + TemplateEnableStr := os.Getenv("TEMPLATE_ENABLE") + if TemplateEnableStr == "" { + TemplateEnableStr = "false" + } + TemplateEnable, _ := strconv.ParseBool(TemplateEnableStr) + + if TemplateEnable { + + // first, include all compoments + type templateData struct { + PageCard components.PageCard + } + + // now fill data of the components + tmplData := templateData{ + PageCard: components.PageCard{ + CardTitle: "Card title", + CardBody: "Loerm ipsum at deim", + }, + } + + return c.Response.Template("custom_theme_base.html", tmplData) + + } else { + + message := "{\"message\": \"Error, template not enabled\"}" + return c.Response.Json(message) + + } + +} + +// Show form element page +func Themeform(c *core.Context) *core.Response { + + // check if template engine is enabled + TemplateEnableStr := os.Getenv("TEMPLATE_ENABLE") + if TemplateEnableStr == "" { + TemplateEnableStr = "false" + } + TemplateEnable, _ := strconv.ParseBool(TemplateEnableStr) + + if TemplateEnable { + + // first, include all compoments + type templateData struct { + FormText components.FormInput + FormEmail components.FormInput + FormButton components.FormButton + FormSelectCity components.FormSelect + FormTextarea components.FormTextarea + FormRadio components.FormRadio + FormCheckbox components.FormCheckbox + } + + // for select options + var allOptions []components.FormSelectOption + var option components.FormSelectOption + option.Value = "ch" + option.Caption = "China" + allOptions = append(allOptions, option) + option.Value = "ba" + option.Caption = "Buenos Aires" + allOptions = append(allOptions, option) + option.Value = "fr" + option.Caption = "France" + selectedOption := option + allOptions = append(allOptions, option) + + // for radio options + var allOptionsr []components.FormRadioItem + var optionr components.FormRadioItem + optionr.ID = "citysch" + optionr.Name = "citys" + optionr.Value = "china" + optionr.Label = "China" + allOptionsr = append(allOptionsr, optionr) + optionr.ID = "citysba" + optionr.Name = "citys" + optionr.Value = "buenosaires" + optionr.Label = "Buenos Aires" + //optionr.IsDisabled = true + allOptionsr = append(allOptionsr, optionr) + + // for radio options + var allOptionsc []components.FormCheckboxItem + var optionc components.FormCheckboxItem + optionc.ID = "citysch" + optionc.Name = "citys" + optionc.Value = "china" + optionc.Label = "China" + allOptionsc = append(allOptionsc, optionc) + optionc.ID = "citysba" + optionc.Name = "citys" + optionc.Value = "buenosaires" + optionc.Label = "Buenos Aires" + allOptionsc = append(allOptionsc, optionc) + optionc.ID = "sogas" + optionc.Name = "sogas" + optionc.Value = "Sogamoso" + optionc.Label = "Sogamoso" + //optionc.IsChecked = true + allOptionsc = append(allOptionsc, optionc) + + // now fill data of the components + tmplData := templateData{ + FormText: components.FormInput{ + ID: "text", + Label: "Name", + Type: "text", + Hint: "This is sample hint", + Placeholder: "Enter your name", + }, + FormEmail: components.FormInput{ + ID: "email", + Label: "Email", + Type: "email", + IsRequired: true, + Placeholder: "Enter your email address", + }, + FormButton: components.FormButton{ + Text: "Login", + IsSubmit: true, + TypeClass: "primary", + }, + FormSelectCity: components.FormSelect{ + ID: "city", + Label: "Select city", + AllOptions: allOptions, + SelectedOption: selectedOption, + }, + FormTextarea: components.FormTextarea{ + ID: "text", + Label: "Example textarea", + }, + FormRadio: components.FormRadio{ + Label: "Radio buttons", + AllRadios: allOptionsr, + }, + FormCheckbox: components.FormCheckbox{ + Label: "Checkbox options", + AllCheckbox: allOptionsc, + }, + } + return c.Response.Template("custom_theme_formpage.html", tmplData) + + } else { + + message := "{\"message\": \"Error, template not enabled\"}" + return c.Response.Json(message) + + } + +} + +func ThemeElements(c *core.Context) *core.Response { + // check if template engine is enabled + TemplateEnableStr := os.Getenv("TEMPLATE_ENABLE") + if TemplateEnableStr == "" { + TemplateEnableStr = "false" + } + TemplateEnable, _ := strconv.ParseBool(TemplateEnableStr) + + if TemplateEnable { + type templateData struct { + Buttons []components.FormButton + Hrefs []components.ContentHref + Badges []components.ContentBadge + Dropdowns []components.ContentDropdown + Lists []components.ContentList + Menus []components.PageNav + } + buttons := []components.FormButton{ + { + Text: "primary", + TypeClass: "primary", + }, + { + Text: "secondary", + TypeClass: "secondary", + }, + { + Text: "success", + TypeClass: "success", + }, + { + Text: "danger", + TypeClass: "danger", + }, + { + Text: "warning", + TypeClass: "warning", + }, + { + Text: "info", + TypeClass: "info", + }, + { + Text: "light", + TypeClass: "light", + }, + { + Text: "dark", + TypeClass: "dark", + }, + { + Text: "link", + TypeClass: "link", + }, + { + Text: "disabled", + TypeClass: "primary", + IsDisabled: true, + }, + { + Text: "outline-primary", + TypeClass: "outline-primary", + }, + { + Text: "outline-secondary", + TypeClass: "outline-secondary", + }, + { + Text: "outline-success", + TypeClass: "outline-success", + }, + { + Text: "outline-danger", + TypeClass: "outline-danger", + }, + { + Text: "outline-warning", + TypeClass: "outline-warning", + }, + { + Text: "outline-info", + TypeClass: "outline-info", + }, + { + Text: "outline-light", + TypeClass: "outline-light", + }, + { + Text: "outline-dark", + TypeClass: "outline-dark", + }, + } + hrefs := []components.ContentHref{ + { + Text: "href", + Link: "#", + IsButton: false, + }, + { + Text: "link", + Link: "#", + IsButton: false, + TypeClass: "link", + }, + { + Text: "button", + Link: "#", + IsButton: true, + TypeClass: "primary", + }, + { + Text: "href disabled", + Link: "#", + IsButton: false, + IsDisabled: true, + }, + { + Text: "link disabled", + Link: "#", + TypeClass: "link", + IsDisabled: true, + }, + { + Text: "button disabled", + Link: "#", + IsButton: true, + TypeClass: "primary", + IsDisabled: true, + }, + } + badges := []components.ContentBadge{ + { + Text: "primary", + TypeClass: "primary", + }, + { + Text: "secondary", + TypeClass: "secondary", + }, + { + Text: "success", + TypeClass: "success", + }, + { + Text: "danger", + TypeClass: "danger", + }, + { + Text: "warning", + TypeClass: "warning", + }, + { + Text: "info", + TypeClass: "info", + }, + { + Text: "light", + TypeClass: "light", + }, + { + Text: "dark", + TypeClass: "dark", + }, + { + Text: "outline", + TypeClass: "primary", + IsOutline: true, + }, + { + Text: "outline", + TypeClass: "success", + IsOutline: true, + }, + { + Text: "outline", + TypeClass: "danger", + IsOutline: true, + }, + { + Text: "outline", + TypeClass: "warning", + IsOutline: true, + }, + } + dropdowns := []components.ContentDropdown{ + // dropdown + { + Label: "dropdown", + Items: []components.ContentDropdownItem{ + { + Text: "item ", + Link: "#", + }, + { + Text: "item disabled", + Link: "#", + IsDisabled: true, + }, + }, + }, + // dropdown + { + Label: "primary", + TypeClass: "primary", + Items: []components.ContentDropdownItem{ + { + Text: "item ", + Link: "#", + }, + { + Text: "item ", + Link: "#", + IsActive: true, + }, + { + Text: "item disabled", + Link: "#", + IsDisabled: true, + }, + }, + }, + // dropdown + { + Label: "outline", + TypeClass: "outline-primary", + Items: []components.ContentDropdownItem{ + { + Text: "item ", + Link: "#", + }, + }, + }, + // dropdown + { + Label: "disabled", + TypeClass: "primary", + IsDisabled: true, + // items + }, + } + list := []components.ContentList{ + // basic list + { + Items: []components.ContentListItem{ + { + Text: "item 1", + }, + { + Text: "item 2", + EndElement: "end text", + }, + { + Text: "item disabled", + IsDisabled: true, + }, + }, + }, + // description list + { + Items: []components.ContentListItem{ + { + Text: "item 1", + Description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ", + }, + { + Text: "item 2", + Description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", + }, + { + Text: "item disabled", + Description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", + IsDisabled: true, + }, + }, + }, + // list with class + { + Items: []components.ContentListItem{ + { + Text: "class primary", + TypeClass: "primary", + }, + { + Text: "class success", + TypeClass: "success", + }, + { + Text: "class danger", + TypeClass: "danger", + }, + }, + }, + } + menus := []components.PageNav{ + // nav + { + NavClass: "nav-pills", + NavItems: []components.PageNavItem{ + { + Text: "item active", + Link: "#", + IsActive: true, + }, + { + Text: "item", + Link: "#", + IsActive: false, + }, + { + Text: "item", + Link: "#", + IsActive: false, + ChildItems: []components.PageNavItem{ + { + Text: "item ", + Link: "#", + }, + { + Text: "item disabled", + Link: "#", + IsDisabled: true, + }, + }, + }, + { + Text: "item disabled", + Link: "#", + IsDisabled: true, + }, + }, + }, + // nav + { + NavClass: "", + NavItems: []components.PageNavItem{ + { + Text: "item active", + Link: "#", + IsActive: true, + }, + { + Text: "item", + Link: "#", + IsActive: false, + }, + { + Text: "item", + Link: "#", + IsActive: false, + }, + { + Text: "item disabled", + Link: "#", + IsDisabled: true, + }, + }, + }, + // nav underline + { + NavClass: "nav-underline", + NavItems: []components.PageNavItem{ + { + Text: "item active", + Link: "#", + IsActive: true, + }, + { + Text: "item", + Link: "#", + IsActive: false, + }, + { + Text: "item", + Link: "#", + IsActive: false, + }, + { + Text: "item disabled", + Link: "#", + IsDisabled: true, + }, + }, + }, + // nav tabs + { + NavClass: "", + IsTab: true, + NavItems: []components.PageNavItem{ + { + Text: "tab active", + Link: "#", + IsActive: true, + }, + { + Text: "tab", + Link: "#", + IsActive: false, + }, + { + Text: "tab", + Link: "#", + IsActive: false, + }, + { + Text: "tab disabled", + Link: "#", + IsDisabled: true, + }, + }, + }, + // nav vertical + { + NavClass: "", + IsVertical: true, + NavItems: []components.PageNavItem{ + { + Text: "item active", + Link: "#", + IsActive: true, + }, + { + Text: "item", + Link: "#", + IsActive: false, + }, + { + Text: "item", + Link: "#", + IsActive: false, + }, + { + Text: "item disabled", + Link: "#", + IsDisabled: true, + }, + }, + }, + } + + tmplData := templateData{ + Buttons: buttons, + Hrefs: hrefs, + Badges: badges, + Dropdowns: dropdowns, + Lists: list, + Menus: menus, + } + return c.Response.Template("custom_theme_elements.html", tmplData) + + } else { + + message := "{\"message\": \"Error, template not enabled\"}" + return c.Response.Json(message) + } +} + +// Show form element page +func Themecontent(c *core.Context) *core.Response { + + // check if template engine is enabled + TemplateEnableStr := os.Getenv("TEMPLATE_ENABLE") + if TemplateEnableStr == "" { + TemplateEnableStr = "false" + } + TemplateEnable, _ := strconv.ParseBool(TemplateEnableStr) + + if TemplateEnable { + + // first, include all compoments + type templateData struct { + ContentTable components.ContentTable + } + + // TABLES + // for th head + var allTh []components.ContentTableTH + var th components.ContentTableTH + th.Value = "Column heading 1" + allTh = append(allTh, th) + th.Value = "Column heading 2" + allTh = append(allTh, th) + th.ID = "ba" + th.Value = "Column heading 3" + allTh = append(allTh, th) + th.Value = "Column badge" + th.ValueType = "badge" // column type badge + allTh = append(allTh, th) + th.Value = "Column action" + th.ValueType = "href" // column type href + allTh = append(allTh, th) + + // for td items + var allTd [][]components.ContentTableTD + //var vals []components.ContentTableTD + // rows + for i := 1; i <= 10; i++ { + vals := make([]components.ContentTableTD, len(allTh)) + for b := 0; b < len(allTh)-2; b++ { + vals[b].Value = fmt.Sprintf("%s%d%d", "TD data: ", i, b) + vals[b].ID = fmt.Sprintf("%s%d%d", "idtd_", i, b) + } + // column badge + vals[len(allTh)-2].Value = components.ContentBadge{ + Text: "success", + TypeClass: "success", + } + // last column href + vals[len(allTh)-1].Value = components.ContentHref{ + Text: "edit", + Link: "#", + } + allTd = append(allTd, vals) + } + + // now fill data of the components + tmplData := templateData{ + ContentTable: components.ContentTable{ + ID: "table_demo", + AllTH: allTh, + AllTD: allTd, + }, + } + return c.Response.Template("custom_theme_contentpage.html", tmplData) + + } else { + + message := "{\"message\": \"Error, template not enabled\"}" + return c.Response.Json(message) + + } + +} diff --git a/events/event-names.go b/events/event-names.go index 14afec8..0e5f431 100644 --- a/events/event-names.go +++ b/events/event-names.go @@ -2,6 +2,5 @@ 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" diff --git a/go.mod b/go.mod index 21103fe..2243d4d 100644 --- a/go.mod +++ b/go.mod @@ -7,59 +7,44 @@ replace ( git.smarteching.com/goffee/cup/models => ./models ) -go 1.25.0 +go 1.23.1 require ( - git.smarteching.com/goffee/core v1.9.6 + git.smarteching.com/goffee/core v1.7.3 github.com/google/uuid v1.6.0 - github.com/hibiken/asynq v0.26.0 github.com/joho/godotenv v1.5.1 github.com/julienschmidt/httprouter v1.3.0 - gorm.io/gorm v1.31.1 + gorm.io/gorm v1.25.12 ) require ( - filippo.io/edwards25519 v1.2.0 // indirect - git.smarteching.com/zeni/go-chart/v2 v2.1.4 // indirect - git.smarteching.com/zeni/go-charts/v2 v2.6.11 // indirect github.com/SparkPost/gosparkpost v0.2.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect - github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/dustin/go-humanize v1.0.1 // indirect - github.com/go-chi/chi/v5 v5.2.5 // indirect + github.com/cespare/xxhash/v2 v2.2.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-ozzo/ozzo-validation v3.6.0+incompatible // indirect - github.com/go-sql-driver/mysql v1.10.0 // indirect - github.com/golang-jwt/jwt/v5 v5.3.1 // indirect - github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect + github.com/go-sql-driver/mysql v1.7.0 // indirect + github.com/golang-jwt/jwt/v5 v5.0.0 // indirect github.com/harranali/mailing v1.2.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/pgx/v5 v5.9.2 // indirect - github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.3.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/mailgun/errors v0.5.0 // indirect - github.com/mailgun/mailgun-go/v4 v4.23.0 // indirect - github.com/mattn/go-sqlite3 v1.14.44 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/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/pkg/errors v0.9.1 // indirect - github.com/redis/go-redis/v9 v9.19.0 // indirect - github.com/robfig/cron/v3 v3.0.1 // indirect + github.com/redis/go-redis/v9 v9.0.5 // indirect github.com/sendgrid/rest v2.6.9+incompatible // indirect - github.com/sendgrid/sendgrid-go v3.16.1+incompatible // indirect - github.com/spf13/cast v1.10.0 // indirect - go.uber.org/atomic v1.11.0 // indirect - golang.org/x/crypto v0.50.0 // indirect - golang.org/x/image v0.39.0 // indirect - golang.org/x/net v0.53.0 // indirect - golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.43.0 // indirect - golang.org/x/text v0.36.0 // indirect - golang.org/x/time v0.15.0 // indirect - google.golang.org/protobuf v1.36.11 // indirect - gorm.io/driver/mysql v1.6.0 // indirect - gorm.io/driver/postgres v1.6.0 // indirect - gorm.io/driver/sqlite v1.6.0 // indirect + 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 ) diff --git a/go.sum b/go.sum index 2bf5036..e052570 100644 --- a/go.sum +++ b/go.sum @@ -1,61 +1,51 @@ -filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= -filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= -git.smarteching.com/goffee/core v1.9.6 h1:GY1EXqbmBEWZAVrl3q22Izb6aXhQzFVQBv2hWhK/So8= -git.smarteching.com/goffee/core v1.9.6/go.mod h1:ifiBgTOR4zCMzdGsabNrEO792EHny2o149NGe3TSlms= -git.smarteching.com/zeni/go-chart/v2 v2.1.4 h1:pF06+F6eqJLIG8uMiTVPR5TygPGMjM/FHMzTxmu5V/Q= -git.smarteching.com/zeni/go-chart/v2 v2.1.4/go.mod h1:b3ueW9h3pGGXyhkormZAvilHaG4+mQti+bMNPdQBeOQ= -git.smarteching.com/zeni/go-charts/v2 v2.6.11 h1:9udzlv3uxGXszpplfkL5IaTUrgkNj++KwhbaN1vVEqI= -git.smarteching.com/zeni/go-charts/v2 v2.6.11/go.mod h1:3OpRPSXg7Qx4zcgsmwsC9ZFB9/wAkGSbnXf1wIbHYCg= +git.smarteching.com/goffee/core v1.7.3 h1:GlZ7B/QwAQ6eSQcYBtqlglZoqA7tFYiXqvV2z27xuQY= +git.smarteching.com/goffee/core v1.7.3/go.mod h1:QQNIHVN6qjJBtq42WCQMrLYN9oFE3wm26SLU8ZxNTec= 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.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/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/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.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= -github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug= -github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ= +github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= +github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= +github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= +github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y= +github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= +github.com/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-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.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxoWCWfw= -github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk= +github.com/go-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-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.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= -github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= -github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= -github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +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/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.26.0 h1:1Zxr92MlDnb1Zt/QR5g2vSCqUS03i95lUfqx5X7/wrw= -github.com/hibiken/asynq v0.26.0/go.mod h1:Qk4e57bTnWDoyJ67VkchuV6VzSM9IQW2nPvAGuDyw58= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw= -github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= -github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= -github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/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/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= @@ -64,94 +54,62 @@ 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.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +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/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= -github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailgun/errors v0.5.0 h1:pLQo8uhAdORsjN69mGixSr0pGs46z/BW/FQXd8HG1VM= -github.com/mailgun/errors v0.5.0/go.mod h1:+2nrgY77E0vDkG4ErehpcpbSkMLkseJzKbrva89WeSs= -github.com/mailgun/mailgun-go/v4 v4.23.0 h1:jPEMJzzin2s7lvehcfv/0UkyBu18GvcURPr2+xtZRbk= -github.com/mailgun/mailgun-go/v4 v4.23.0/go.mod h1:imTtizoFtpfZqPqGP8vltVBB6q9yWcv6llBhfFeElZU= +github.com/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/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-sqlite3 v1.14.44 h1:3VSe+xafpbzsLbdr2AWlAZk9yRHiBhTBakioXaCKTF8= -github.com/mattn/go-sqlite3 v1.14.44/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ= +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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -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/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/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.19.0 h1:XPVaaPSnG6RhYf7p+rmSa9zZfeVAnWsH5h3lxthOm/k= -github.com/redis/go-redis/v9 v9.19.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA= -github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= -github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +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/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0= github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE= -github.com/sendgrid/sendgrid-go v3.16.1+incompatible h1:zWhTmB0Y8XCDzeWIm2/BIt1GjJohAA0p6hVEaDtHWWs= -github.com/sendgrid/sendgrid-go v3.16.1+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= -github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +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/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.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= -github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= -golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= -golang.org/x/image v0.39.0 h1:skVYidAEVKgn8lZ602XO75asgXBgLj9G/FE3RbuPFww= -golang.org/x/image v0.39.0/go.mod h1:sIbmppfU+xFLPIG0FoVUTvyBMmgng1/XAMhQ2ft0hpA= +golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= -golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= -golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= -golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= -golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= -golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= -google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/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.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= -gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= -gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= -gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= -gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= -gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= -gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= -gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= +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/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= +gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= diff --git a/main.go b/main.go index 5fac06e..fd1f7b4 100644 --- a/main.go +++ b/main.go @@ -26,25 +26,14 @@ 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 { - envfile := ".env-dev" - if runMode == "prod" { - envfile = ".env" - } - envVars, err := godotenv.Read(envfile) + envVars, err := godotenv.Read(".env") if err != nil { log.Fatal("Error loading .env file") } @@ -62,9 +51,6 @@ func main() { registerGlobalHooks() registerRoutes() registerEvents() - if config.GetQueueConfig().EnableQueue == true { - registerQueues() - } if config.GetGormConfig().EnableGorm == true { RunAutoMigrations() } diff --git a/models/base.go b/models/base.go deleted file mode 100644 index 5350de2..0000000 --- a/models/base.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) 2024 Zeni Kim -// Use of this source code is governed by MIT-style -// license that can be found in the LICENSE file. - -package models - -type BaseModel struct { - ID uint `json:"id" gorm:"primaryKey; column:id"` - Created int64 `gorm:"autoCreateTime"` - Updated int64 `gorm:"autoUpdateTime"` -} diff --git a/models/permission.go b/models/permission.go deleted file mode 100644 index 933b16d..0000000 --- a/models/permission.go +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2025 Zeni Kim -// Use of this source code is governed by MIT-style -// license that can be found in the LICENSE file. - -package models - -type Permission struct { - BaseModel - Name string - Slug string -} - -// TableName sets the table name -func (Permission) TableName() string { - return "permissions" -} diff --git a/models/role-permissions.go b/models/role-permissions.go deleted file mode 100644 index 99cf3fe..0000000 --- a/models/role-permissions.go +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2025 Zeni Kim -// Use of this source code is governed by MIT-style -// license that can be found in the LICENSE file. - -package models - -type RolePermission struct { - BaseModel - RoleID uint // Role id - PermissionID uint // Permission id -} - -// TableName sets the table name -func (RolePermission) TableName() string { - return "role_permissions" -} diff --git a/models/roles.go b/models/roles.go deleted file mode 100644 index decd675..0000000 --- a/models/roles.go +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2025 Zeni Kim -// Use of this source code is governed by MIT-style -// license that can be found in the LICENSE file. - -package models - -type Role struct { - BaseModel - Name string // The name of the role - Slug string // String based unique identifier of the role, (use hyphen seperated role name '-', instead of space) -} - -// TableName sets the table name -func (Role) TableName() string { - return "roles" -} diff --git a/models/user-roles.go b/models/user-roles.go deleted file mode 100644 index 1dfcc18..0000000 --- a/models/user-roles.go +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2025 Zeni Kim -// Use of this source code is governed by MIT-style -// license that can be found in the LICENSE file. - -package 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" -} diff --git a/models/user.go b/models/user.go index 3a229d9..c85a386 100644 --- a/models/user.go +++ b/models/user.go @@ -5,10 +5,11 @@ package models +import "gorm.io/gorm" + type User struct { - BaseModel + gorm.Model Name string - Fullname string Email string Password string } diff --git a/register-queues.go b/register-queues.go deleted file mode 100644 index a1cf059..0000000 --- a/register-queues.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) 2025 Zeni Kim -// Use of this source code is governed by MIT-style -// license that can be found in the LICENSE file. - -package main - -import ( - "git.smarteching.com/goffee/core" - "git.smarteching.com/goffee/cup/workers" -) - -// Register queues -func registerQueues() { - - var queque = new(core.Queuemux) - queque.QueueInit() - //######################################## - //# quques registration ##### - //######################################## - - // register your queues here ... - - queque.AddWork(workers.TypeWelcomeEmail, workers.HandleWelcomeEmailTask) - queque.AddWork(workers.TypeReminderEmail, workers.HandleReminderEmailTask) - - //######################################## - // Start queue server, DO NOT TOUCH - //######################################## - go queque.RunQueueserver() -} diff --git a/routes.go b/routes.go index 193da2d..18c72be 100644 --- a/routes.go +++ b/routes.go @@ -8,6 +8,7 @@ package main import ( "git.smarteching.com/goffee/core" "git.smarteching.com/goffee/cup/controllers" + "git.smarteching.com/goffee/cup/hooks" ) // Register the app controllers @@ -19,23 +20,25 @@ func registerRoutes() { // Define your routes here... controller.Get("/", controllers.WelcomeHome) - - // Uncomment the lines below to enable authentication API + // Uncomment the lines below to enable theme demo + controller.Get("/themebase", controllers.Themedemo) + controller.Get("/themeform", controllers.Themeform) + controller.Get("/themecontent", controllers.Themecontent) + controller.Get("/themepanel", controllers.Themedemo) + controller.Get("/themeelements", controllers.ThemeElements) + + // Uncomment the lines below to enable authentication controller.Post("/signup", controllers.Signup) controller.Post("/signin", controllers.Signin) controller.Post("/signout", controllers.Signout) 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("/dashboard", controllers.WelcomeToDashboard, hooks.AuthCheck) + 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) } diff --git a/run-auto-migrations.go b/run-auto-migrations.go index 996ae15..8410637 100644 --- a/run-auto-migrations.go +++ b/run-auto-migrations.go @@ -6,12 +6,8 @@ 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() { @@ -21,18 +17,5 @@ func RunAutoMigrations() { //############################## // Add auto migrations for your models here... - 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() - } - } - + db.AutoMigrate(&models.User{}) } diff --git a/storage/app/img/goffee.png b/storage/app/img/goffee.png deleted file mode 100644 index d0491fc..0000000 Binary files a/storage/app/img/goffee.png and /dev/null differ diff --git a/storage/app/index.html b/storage/app/index.html deleted file mode 100644 index 326fcb0..0000000 --- a/storage/app/index.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - Title! - - -

This is an example conten.

- - diff --git a/storage/public/app.js b/storage/public/app.js index 57fdf3f..75654fb 100644 --- a/storage/public/app.js +++ b/storage/public/app.js @@ -1,14 +1 @@ -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(); - } - }) - }; -} \ No newline at end of file +console.log("Start Goffee app"); \ No newline at end of file diff --git a/storage/public/img/gopher_read.png b/storage/public/img/gopher_read.png deleted file mode 100644 index 6714ef4..0000000 Binary files a/storage/public/img/gopher_read.png and /dev/null differ diff --git a/storage/sqlite.db b/storage/sqlite.db new file mode 100644 index 0000000..b10cc17 Binary files /dev/null and b/storage/sqlite.db differ diff --git a/storage/sqlite/sqlite.db b/storage/sqlite/sqlite.db deleted file mode 100644 index 57a82d2..0000000 Binary files a/storage/sqlite/sqlite.db and /dev/null differ diff --git a/storage/templates/admin_confirmuserdel.html b/storage/templates/admin_confirmuserdel.html deleted file mode 100644 index 5fd0d71..0000000 --- a/storage/templates/admin_confirmuserdel.html +++ /dev/null @@ -1,34 +0,0 @@ - - - {{template "page_head" "Goffee"}} - -
-

User delete confirmation

- {{if .ErrorMessages }} -
- -
    - {{range $i, $a := .ErrorMessages}} -
  • {{$a}}
  • - {{end}} -
-
- {{else}} - {{if .WarningMessages }} -
    - {{range $o, $u := .WarningMessages}} -
  • {{$u}}
  • - {{end}} -
- {{end}} -
- {{template "form_input" .FieldKey}} - {{template "form_button" .ConfirmButton}} -
- {{end}} -
- {{template "content_href" .BackButton}} -
- {{template "page_footer"}} - - diff --git a/storage/templates/admin_useradd.html b/storage/templates/admin_useradd.html deleted file mode 100644 index bc5d780..0000000 --- a/storage/templates/admin_useradd.html +++ /dev/null @@ -1,27 +0,0 @@ - - - {{template "page_head" "Goffee"}} - -
-

Add user form

- {{if .ErrorMessages }}
- -
    - {{range $i, $a := .ErrorMessages}} -
  • {{$a}}
  • - {{end}} -
-
{{end}} -
- {{template "form_input" .FieldName}} - {{template "form_input" .FieldFullname}} - {{template "form_input" .FieldEmail}} - {{template "form_checkbox" .FieldRoles}} - {{template "form_input" .FieldPassword}} -
- {{template "form_button" .SubmitButton}} -
-
- {{template "page_footer"}} - - diff --git a/storage/templates/admin_useredit.html b/storage/templates/admin_useredit.html deleted file mode 100644 index 3c49e08..0000000 --- a/storage/templates/admin_useredit.html +++ /dev/null @@ -1,32 +0,0 @@ - - - {{template "page_head" "Goffee"}} - -
-

Edit user form

- {{if .ErrorMessages }}
- -
    - {{range $i, $a := .ErrorMessages}} -
  • {{$a}}
  • - {{end}} -
-
{{end}} -
- {{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}} -
-
-
- {{template "form_input" .FieldKey}} - {{template "form_button" .DeleteButton}} -
-
- {{template "page_footer"}} - - diff --git a/storage/templates/admin_userlist.html b/storage/templates/admin_userlist.html deleted file mode 100644 index 9981d46..0000000 --- a/storage/templates/admin_userlist.html +++ /dev/null @@ -1,18 +0,0 @@ - - - {{template "page_head" "Goffee"}} - -
-
-

Users

-
- {{template "content_href" .AddButton}} -
-
-
- {{template "content_table" .TableUsers}} -
-
- {{template "page_footer"}} - - diff --git a/storage/templates/app.html b/storage/templates/app.html new file mode 100644 index 0000000..21831b7 --- /dev/null +++ b/storage/templates/app.html @@ -0,0 +1,15 @@ + + + {{template "page_head" "Sample page"}} + +
+
+ {{template "page_card" .PageCard}} + {{ define "page_card_content" }} + + {{ end }} +
+
+ {{template "page_footer"}} + + \ No newline at end of file diff --git a/storage/templates/custom_theme_base.html b/storage/templates/custom_theme_base.html new file mode 100644 index 0000000..9c28a1e --- /dev/null +++ b/storage/templates/custom_theme_base.html @@ -0,0 +1,10 @@ + + + {{template "page_head" "Goffee"}} + +
+ {{template "page_card" .PageCard}} +
+ {{template "page_footer"}} + + \ No newline at end of file diff --git a/storage/templates/custom_theme_contentpage.html b/storage/templates/custom_theme_contentpage.html new file mode 100644 index 0000000..e1d71e9 --- /dev/null +++ b/storage/templates/custom_theme_contentpage.html @@ -0,0 +1,15 @@ + + + {{template "page_head" "Goffee"}} + +
+
+ Content demos +
+ {{template "content_table" .ContentTable}} +
+
+
+ {{template "page_footer"}} + + diff --git a/storage/templates/custom_theme_elements.html b/storage/templates/custom_theme_elements.html new file mode 100644 index 0000000..fbe2d52 --- /dev/null +++ b/storage/templates/custom_theme_elements.html @@ -0,0 +1,66 @@ + + + {{template "page_head" "Goffee"}} + +
+
+ Demos buttons +
+ {{range .Buttons}} + {{template "form_button" .}} + {{end}} +
+
+ +
+ Demos href +
+ {{range .Hrefs}} + {{template "content_href" .}} + {{end}} +
+
+ +
+ Demos Badges +
+ {{range .Badges}} + {{template "content_badge" .}} + {{end}} +
+
+ + +
+ Demos dropdown +
+ {{range .Dropdowns}} + {{template "content_dropdown" .}} + {{end}} +
+
+ + +
+ Demos List +
+ {{range .Lists}} + {{template "content_list" .}} + {{end}} +
+
+ +
+ Demos nav +
+ {{range .Menus}} +
+ {{template "page_nav" .}} +
+ {{end}} +
+
+
+ {{template "page_footer"}} + + diff --git a/storage/templates/custom_theme_formpage.html b/storage/templates/custom_theme_formpage.html new file mode 100644 index 0000000..f45a558 --- /dev/null +++ b/storage/templates/custom_theme_formpage.html @@ -0,0 +1,23 @@ + + + {{template "page_head" "Goffee"}} + +
+
+
+ form demos +
+ {{template "form_input" .FormText}} + {{template "form_input" .FormEmail}} + {{template "form_select" .FormSelectCity}} + {{template "form_textarea" .FormTextarea}} + {{template "form_radio" .FormRadio}} + {{template "form_checkbox" .FormCheckbox}} +
+
+ {{template "form_button" .FormButton}} +
+
+ {{template "page_footer"}} + + diff --git a/storage/templates/nopermission.html b/storage/templates/nopermission.html deleted file mode 100644 index bec8f39..0000000 --- a/storage/templates/nopermission.html +++ /dev/null @@ -1,12 +0,0 @@ - - - {{template "page_head" "No permission"}} - -
-
- You do not have permission to visit this page. -
-
- {{template "page_footer"}} - - \ No newline at end of file diff --git a/utils/authority.go b/utils/authority.go deleted file mode 100644 index 823f845..0000000 --- a/utils/authority.go +++ /dev/null @@ -1,436 +0,0 @@ -// Copyright (c) 2025 Zeni Kim -// Use of this source code is governed by MIT-style -// license that can be found in the LICENSE file. - -package 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 -} diff --git a/utils/helpers.go b/utils/helpers.go index 8ca7dd8..00525b7 100644 --- a/utils/helpers.go +++ b/utils/helpers.go @@ -8,69 +8,8 @@ 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) @@ -78,7 +17,3 @@ 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") -} diff --git a/utils/session.go b/utils/session.go deleted file mode 100644 index 27e202d..0000000 --- a/utils/session.go +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright (c) 2024 Zeni Kim -// Use of this source code is governed by MIT-style -// license that can be found in the LICENSE file. - -package 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 -} diff --git a/workers/workers.go b/workers/workers.go deleted file mode 100644 index e7a9cad..0000000 --- a/workers/workers.go +++ /dev/null @@ -1,39 +0,0 @@ -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 -}