Compare commits

..

No commits in common. "main" and "main" have entirely different histories.
main ... main

73 changed files with 139 additions and 61268 deletions

View file

@ -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,7 +18,8 @@ App_KEY_FILE_PATH=tls/server.key
###### TEMPLATES ######
#######################################
TEMPLATE_ENABLE=true
COOKIE_SECRET=13d6b4dff8f84a10851021ec8608f814570d562c92fe6b5ec4c9f595bcb3234b
TEMPLATE_PUBLIC=storage/public
TEMPLATE_COMPONENTS=true
#######################################
###### JWT ######
@ -46,10 +46,10 @@ POSTGRES_PASSWORD=secret
POSTGRES_DB_NAME=db_test
POSTGRES_PORT=5432
POSTGRES_SSL_MODE=disable
POSTGRES_TIMEZONE=America/Argentina/Buenos_Aires
POSTGRES_TIMEZONE=Asia/Dubai
#_____ SQLITE _____#
SQLITE_DB_PATH=storage/sqlite/sqlite.db
SQLITE_DB_PATH=storage/sqlite.db
#######################################
###### CACHE ######

3
.gitignore vendored
View file

@ -1,10 +1,9 @@
.air.toml
.env
.env-dev
tmp/*
logs/*
!logs/.gitkeep
tls/*
!tls/.gitkeep
.DS_Store
storage/sqlite/*
storage/public/*

View file

@ -8,10 +8,7 @@ package controllers
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"os"
"strconv"
"strings"
"time"
@ -126,13 +123,6 @@ func Signin(c *core.Context) *core.Response {
email := c.GetRequestParam("email")
password := c.GetRequestParam("password")
// check if template engine is enable
TemplateEnableStr := os.Getenv("TEMPLATE_ENABLE")
if TemplateEnableStr == "" {
TemplateEnableStr = "false"
}
TemplateEnable, _ := strconv.ParseBool(TemplateEnableStr)
data := map[string]interface{}{
"email": email,
"password": password,
@ -145,61 +135,36 @@ func Signin(c *core.Context) *core.Response {
if v.Failed() {
c.GetLogger().Error(v.GetErrorMessagesJson())
if TemplateEnable {
// TODO set error in session
return c.Response.Redirect("/applogin")
} else {
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(v.GetErrorMessagesJson())
}
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(v.GetErrorMessagesJson())
}
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())
if TemplateEnable {
// TODO set error in session
return c.Response.Redirect("/applogin")
} else {
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{
"message": "internal server error",
}))
}
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{
"message": "internal server error",
}))
}
if res.Error != nil && errors.Is(res.Error, gorm.ErrRecordNotFound) {
if TemplateEnable {
// TODO set error in session
return c.Response.Redirect("/applogin")
} else {
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{
"message": "invalid email or password",
}))
}
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{
"message": "invalid email or password",
}))
}
ok, err := c.GetHashing().CheckPasswordHash(user.Password, c.CastToString(password))
if err != nil {
c.GetLogger().Error(err.Error())
if TemplateEnable {
// TODO set error in session
return c.Response.Redirect("/applogin")
} else {
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{
"message": err.Error(),
}))
}
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{
"message": err.Error(),
}))
}
if !ok {
if TemplateEnable {
// TODO set error in session
return c.Response.Redirect("/applogin")
} else {
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{
"message": "invalid email or password",
}))
}
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{
"message": "invalid email or password",
}))
}
token, err := c.GetJWT().GenerateToken(map[string]interface{}{
@ -208,52 +173,24 @@ func Signin(c *core.Context) *core.Response {
if err != nil {
c.GetLogger().Error(err.Error())
// TODO set error in session
if TemplateEnable {
return c.Response.Redirect("/applogin")
} else {
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{
"message": "internal server error",
}))
}
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{
"message": "internal server error",
}))
}
// cache the token
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 {
// TODO set error in session
return c.Response.Redirect("/applogin")
} else {
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]interface{}{
"message": "internal server error",
}))
}
}
if TemplateEnable {
// create cookie
err = core.SetCookie(c.Response.HttpResponseWriter, email.(string), token)
if err != nil {
panic(fmt.Sprintf("Error write encrypted cookie: %v", err))
return c.Response.SetStatusCode(http.StatusInternalServerError)
}
// redirecto to app
return c.Response.Redirect("/appsample")
} else {
return c.Response.Json(c.MapToJson(map[string]string{
"token": token,
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]interface{}{
"message": "internal server error",
}))
}
return c.Response.Json(c.MapToJson(map[string]string{
"token": token,
}))
}
func ResetPasswordRequest(c *core.Context) *core.Response {
@ -425,28 +362,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",

View file

@ -6,45 +6,13 @@
package controllers
import (
"os"
"strconv"
"git.smarteching.com/goffee/core"
"git.smarteching.com/goffee/core/template/components"
)
// Show home page
func WelcomeHome(c *core.Context) *core.Response {
// check if template engine is enable
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: "Golang Framework",
CardBody: "Welcome to Goffee",
},
}
return c.Response.Template("welcome.html", tmplData)
} else {
message := "{\"message\": \"Welcome to Goffee\"}"
return c.Response.Json(message)
}
message := "{\"message\": \"Welcome to Goffee\"}"
return c.Response.Json(message)
}
// Show dashboard

View file

@ -1,149 +0,0 @@
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
// Use of this source code is governed by MIT-style
// license that can be found in the LICENSE file.
package controllers
import (
"fmt"
"git.smarteching.com/goffee/core"
"git.smarteching.com/goffee/core/template/components"
"git.smarteching.com/goffee/cup/utils"
)
// Show basic template
func Sample(c *core.Context) *core.Response {
// first, include all compoments
type templateData struct {
PageCard components.PageCard
}
// now fill data of the components
tmplData := templateData{
PageCard: components.PageCard{
CardTitle: "Framework Goffee",
CardBody: "Powered by Golang",
},
}
return c.Response.Template("basic.html", tmplData)
}
// Show basic app login
func AppLogin(c *core.Context) *core.Response {
// first, include all compoments
// first, include all compoments
type templateData struct {
PageCard components.PageCard
}
// now fill data of the components
tmplData := templateData{
PageCard: components.PageCard{
CardTitle: "Card title",
CardBody: "Loerm ipsum at deim",
},
}
return c.Response.Template("login.html", tmplData)
}
// Show basic app login
func AppSession(c *core.Context) *core.Response {
var session = new(utils.SessionUser)
// true if session is active
hassession := session.Init(c)
//session.Set("numberdos", 66)
type templateData struct {
PageCard components.PageCard
}
// now fill data of the components
tmplData := templateData{}
if hassession {
sesiondata := ""
cardtitle := fmt.Sprintf("Session user id: %v", session.GetUserID())
numberdos, ok := session.Get("numberdos")
if numberdos != nil {
numberdos = numberdos.(float64) + 10
} else {
numberdos = 10
}
session.Set("numberdos", numberdos)
if ok {
sesiondata = fmt.Sprintf("OK, Session numberdos has %v", numberdos)
} else {
sesiondata = fmt.Sprintf("No ok, session numberdos has %v", numberdos)
}
// delete single
//session.Delete("numberdos")
// delete all data
//session.Flush()
tmplData = templateData{
PageCard: components.PageCard{
CardTitle: cardtitle,
CardBody: sesiondata,
},
}
return c.Response.Template("appsession.html", tmplData)
} else {
return c.Response.Template("login.html", tmplData)
}
}
// Show basic app sample
func AppSample(c *core.Context) *core.Response {
// first, include all compoments
type templateData struct {
PageCard components.PageCard
ContentDropdown components.ContentDropdown
}
// now fill data of the components
tmplData := templateData{
PageCard: components.PageCard{
CardTitle: "Protected page",
CardBody: "If you can see this page, your are loggedin",
},
ContentDropdown: components.ContentDropdown{
Label: "dropdown",
Items: []components.ContentDropdownItem{
{
Text: "Signout",
Link: "#",
ID: "signout",
},
{
Text: "item disabled",
Link: "#",
IsDisabled: true,
},
},
},
}
//fmt.Printf("Outside cookie user is: %s", user.Email)
return c.Response.Template("app.html", tmplData)
}

View file

@ -1,755 +0,0 @@
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
// Use of this source code is governed by MIT-style
// license that can be found in the LICENSE file.
package controllers
import (
"fmt"
"math/rand/v2"
"os"
"strconv"
"git.smarteching.com/goffee/core"
"git.smarteching.com/goffee/core/template/components"
)
// Show home page
func Themedemo(c *core.Context) *core.Response {
// check if template engine is enabled
TemplateEnableStr := os.Getenv("TEMPLATE_ENABLE")
if TemplateEnableStr == "" {
TemplateEnableStr = "false"
}
TemplateEnable, _ := strconv.ParseBool(TemplateEnableStr)
if TemplateEnable {
// first, include all compoments
type templateData struct {
PageCard components.PageCard
}
// now fill data of the components
tmplData := templateData{
PageCard: components.PageCard{
CardTitle: "Card title",
CardBody: "Loerm ipsum at deim",
},
}
return c.Response.Template("custom_theme_base.html", tmplData)
} else {
message := "{\"message\": \"Error, template not enabled\"}"
return c.Response.Json(message)
}
}
// Show form element page
func Themeform(c *core.Context) *core.Response {
// check if template engine is enabled
TemplateEnableStr := os.Getenv("TEMPLATE_ENABLE")
if TemplateEnableStr == "" {
TemplateEnableStr = "false"
}
TemplateEnable, _ := strconv.ParseBool(TemplateEnableStr)
if TemplateEnable {
// first, include all compoments
type templateData struct {
FormText components.FormInput
FormEmail components.FormInput
FormButton components.FormButton
FormSelectCity components.FormSelect
FormTextarea components.FormTextarea
FormRadio components.FormRadio
FormCheckbox components.FormCheckbox
}
// for select options
var allOptions []components.FormSelectOption
var option components.FormSelectOption
option.Value = "ch"
option.Caption = "China"
allOptions = append(allOptions, option)
option.Value = "ba"
option.Caption = "Buenos Aires"
allOptions = append(allOptions, option)
option.Value = "fr"
option.Caption = "France"
selectedOption := option
allOptions = append(allOptions, option)
// for radio options
var allOptionsr []components.FormRadioItem
var optionr components.FormRadioItem
optionr.ID = "citysch"
optionr.Name = "citys"
optionr.Value = "china"
optionr.Label = "China"
allOptionsr = append(allOptionsr, optionr)
optionr.ID = "citysba"
optionr.Name = "citys"
optionr.Value = "buenosaires"
optionr.Label = "Buenos Aires"
//optionr.IsDisabled = true
allOptionsr = append(allOptionsr, optionr)
// for radio options
var allOptionsc []components.FormCheckboxItem
var optionc components.FormCheckboxItem
optionc.ID = "citysch"
optionc.Name = "citys"
optionc.Value = "china"
optionc.Label = "China"
allOptionsc = append(allOptionsc, optionc)
optionc.ID = "citysba"
optionc.Name = "citys"
optionc.Value = "buenosaires"
optionc.Label = "Buenos Aires"
allOptionsc = append(allOptionsc, optionc)
optionc.ID = "london"
optionc.Name = "london"
optionc.Value = "london"
optionc.Label = "London"
//optionc.IsChecked = true
allOptionsc = append(allOptionsc, optionc)
// now fill data of the components
tmplData := templateData{
FormText: components.FormInput{
ID: "text",
Label: "Name",
Type: "text",
Hint: "This is sample hint",
Placeholder: "Enter your name",
},
FormEmail: components.FormInput{
ID: "email",
Label: "Email",
Type: "email",
IsRequired: true,
Placeholder: "Enter your email address",
},
FormButton: components.FormButton{
Text: "Login",
IsSubmit: true,
TypeClass: "primary",
},
FormSelectCity: components.FormSelect{
ID: "city",
Label: "Select city",
AllOptions: allOptions,
SelectedOption: selectedOption,
},
FormTextarea: components.FormTextarea{
ID: "text",
Label: "Example textarea",
},
FormRadio: components.FormRadio{
Label: "Radio buttons",
AllRadios: allOptionsr,
},
FormCheckbox: components.FormCheckbox{
Label: "Checkbox options",
AllCheckbox: allOptionsc,
},
}
return c.Response.Template("custom_theme_formpage.html", tmplData)
} else {
message := "{\"message\": \"Error, template not enabled\"}"
return c.Response.Json(message)
}
}
func ThemeElements(c *core.Context) *core.Response {
// check if template engine is enabled
TemplateEnableStr := os.Getenv("TEMPLATE_ENABLE")
if TemplateEnableStr == "" {
TemplateEnableStr = "false"
}
TemplateEnable, _ := strconv.ParseBool(TemplateEnableStr)
if TemplateEnable {
type templateData struct {
Buttons []components.FormButton
Hrefs []components.ContentHref
Badges []components.ContentBadge
Dropdowns []components.ContentDropdown
Lists []components.ContentList
Menus []components.PageNav
}
buttons := []components.FormButton{
{
Text: "primary",
TypeClass: "primary",
},
{
Text: "secondary",
TypeClass: "secondary",
},
{
Text: "success",
TypeClass: "success",
},
{
Text: "danger",
TypeClass: "danger",
},
{
Text: "warning",
TypeClass: "warning",
},
{
Text: "info",
TypeClass: "info",
},
{
Text: "light",
TypeClass: "light",
},
{
Text: "dark",
TypeClass: "dark",
},
{
Text: "link",
TypeClass: "link",
},
{
Text: "disabled",
TypeClass: "primary",
IsDisabled: true,
},
{
Text: "outline-primary",
TypeClass: "outline-primary",
},
{
Text: "outline-secondary",
TypeClass: "outline-secondary",
},
{
Text: "outline-success",
TypeClass: "outline-success",
},
{
Text: "outline-danger",
TypeClass: "outline-danger",
},
{
Text: "outline-warning",
TypeClass: "outline-warning",
},
{
Text: "outline-info",
TypeClass: "outline-info",
},
{
Text: "outline-light",
TypeClass: "outline-light",
},
{
Text: "outline-dark",
TypeClass: "outline-dark",
},
}
hrefs := []components.ContentHref{
{
Text: "href",
Link: "#",
IsButton: false,
},
{
Text: "link",
Link: "#",
IsButton: false,
TypeClass: "link",
},
{
Text: "button",
Link: "#",
IsButton: true,
TypeClass: "primary",
},
{
Text: "href disabled",
Link: "#",
IsButton: false,
IsDisabled: true,
},
{
Text: "link disabled",
Link: "#",
TypeClass: "link",
IsDisabled: true,
},
{
Text: "button disabled",
Link: "#",
IsButton: true,
TypeClass: "primary",
IsDisabled: true,
},
}
badges := []components.ContentBadge{
{
Text: "primary",
TypeClass: "primary",
},
{
Text: "secondary",
TypeClass: "secondary",
},
{
Text: "success",
TypeClass: "success",
},
{
Text: "danger",
TypeClass: "danger",
},
{
Text: "warning",
TypeClass: "warning",
},
{
Text: "info",
TypeClass: "info",
},
{
Text: "light",
TypeClass: "light",
},
{
Text: "dark",
TypeClass: "dark",
},
{
Text: "outline",
TypeClass: "primary",
IsOutline: true,
},
{
Text: "outline",
TypeClass: "success",
IsOutline: true,
},
{
Text: "outline",
TypeClass: "danger",
IsOutline: true,
},
{
Text: "outline",
TypeClass: "warning",
IsOutline: true,
},
}
dropdowns := []components.ContentDropdown{
// dropdown
{
Label: "dropdown",
Items: []components.ContentDropdownItem{
{
Text: "item ",
Link: "#",
},
{
Text: "item disabled",
Link: "#",
IsDisabled: true,
},
},
},
// dropdown
{
Label: "primary",
TypeClass: "primary",
Items: []components.ContentDropdownItem{
{
Text: "item ",
Link: "#",
},
{
Text: "item ",
Link: "#",
IsActive: true,
},
{
Text: "item disabled",
Link: "#",
IsDisabled: true,
},
},
},
// dropdown
{
Label: "outline",
TypeClass: "outline-primary",
Items: []components.ContentDropdownItem{
{
Text: "item ",
Link: "#",
},
},
},
// dropdown
{
Label: "disabled",
TypeClass: "primary",
IsDisabled: true,
// items
},
}
list := []components.ContentList{
// basic list
{
Items: []components.ContentListItem{
{
Text: "item 1",
},
{
Text: "item 2",
EndElement: "end text",
},
{
Text: "item disabled",
IsDisabled: true,
},
},
},
// description list
{
Items: []components.ContentListItem{
{
Text: "item 1",
Description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ",
},
{
Text: "item 2",
Description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
},
{
Text: "item disabled",
Description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
IsDisabled: true,
},
},
},
// list with class
{
Items: []components.ContentListItem{
{
Text: "class primary",
TypeClass: "primary",
},
{
Text: "class success",
TypeClass: "success",
},
{
Text: "class danger",
TypeClass: "danger",
},
},
},
}
menus := []components.PageNav{
// nav
{
NavClass: "nav-pills",
NavItems: []components.PageNavItem{
{
Text: "item active",
Link: "#",
IsActive: true,
},
{
Text: "item",
Link: "#",
IsActive: false,
},
{
Text: "item",
Link: "#",
IsActive: false,
ChildItems: []components.PageNavItem{
{
Text: "item ",
Link: "#",
},
{
Text: "item disabled",
Link: "#",
IsDisabled: true,
},
},
},
{
Text: "item disabled",
Link: "#",
IsDisabled: true,
},
},
},
// nav
{
NavClass: "",
NavItems: []components.PageNavItem{
{
Text: "item active",
Link: "#",
IsActive: true,
},
{
Text: "item",
Link: "#",
IsActive: false,
},
{
Text: "item",
Link: "#",
IsActive: false,
},
{
Text: "item disabled",
Link: "#",
IsDisabled: true,
},
},
},
// nav underline
{
NavClass: "nav-underline",
NavItems: []components.PageNavItem{
{
Text: "item active",
Link: "#",
IsActive: true,
},
{
Text: "item",
Link: "#",
IsActive: false,
},
{
Text: "item",
Link: "#",
IsActive: false,
},
{
Text: "item disabled",
Link: "#",
IsDisabled: true,
},
},
},
// nav tabs
{
NavClass: "",
IsTab: true,
NavItems: []components.PageNavItem{
{
Text: "tab active",
Link: "#",
IsActive: true,
},
{
Text: "tab",
Link: "#",
IsActive: false,
},
{
Text: "tab",
Link: "#",
IsActive: false,
},
{
Text: "tab disabled",
Link: "#",
IsDisabled: true,
},
},
},
// nav vertical
{
NavClass: "",
IsVertical: true,
NavItems: []components.PageNavItem{
{
Text: "item active",
Link: "#",
IsActive: true,
},
{
Text: "item",
Link: "#",
IsActive: false,
},
{
Text: "item",
Link: "#",
IsActive: false,
},
{
Text: "item disabled",
Link: "#",
IsDisabled: true,
},
},
},
}
tmplData := templateData{
Buttons: buttons,
Hrefs: hrefs,
Badges: badges,
Dropdowns: dropdowns,
Lists: list,
Menus: menus,
}
return c.Response.Template("custom_theme_elements.html", tmplData)
} else {
message := "{\"message\": \"Error, template not enabled\"}"
return c.Response.Json(message)
}
}
// Show form element page
func Themecontent(c *core.Context) *core.Response {
// check if template engine is enabled
TemplateEnableStr := os.Getenv("TEMPLATE_ENABLE")
if TemplateEnableStr == "" {
TemplateEnableStr = "false"
}
TemplateEnable, _ := strconv.ParseBool(TemplateEnableStr)
if TemplateEnable {
// first, include all compoments
type templateData struct {
ContentTable components.ContentTable
ContentTabledetail components.ContentTabledetail
ContentGraph components.ContentGraph
}
// TABLES
// for th head
var allTh []components.ContentTableTH
var th components.ContentTableTH
th.Value = "Column heading 1"
allTh = append(allTh, th)
th.Value = "Column heading 2"
allTh = append(allTh, th)
th.ID = "ba"
th.Value = "Column heading 3"
allTh = append(allTh, th)
th.Value = "Column badge"
th.ValueType = "badge" // column type badge
allTh = append(allTh, th)
th.Value = "Column action"
th.ValueType = "href" // column type href
allTh = append(allTh, th)
// for td items
var allTd [][]components.ContentTableTD
//var vals []components.ContentTableTD
// rows
for i := 1; i <= 10; i++ {
vals := make([]components.ContentTableTD, len(allTh))
for b := 0; b < len(allTh)-2; b++ {
vals[b].Value = fmt.Sprintf("%s%d%d", "TD data: ", i, b)
vals[b].ID = fmt.Sprintf("%s%d%d", "idtd_", i, b)
}
// column badge
vals[len(allTh)-2].Value = components.ContentBadge{
Text: "success",
TypeClass: "success",
}
// last column href
vals[len(allTh)-1].Value = components.ContentHref{
Text: "edit",
Link: "#",
}
allTd = append(allTd, vals)
}
// for td items in table detail
var allTdetail []components.ContentTabledetailTD
// table detail
var thd components.ContentTabledetailTD
thd.Caption = "Continent"
thd.Value = "Asia"
allTdetail = append(allTdetail, thd)
thd.Caption = "Country"
thd.Value = "South Korea"
allTdetail = append(allTdetail, thd)
thd.Caption = "Capital"
thd.Value = "Seoul"
allTdetail = append(allTdetail, thd)
thd.Caption = "Details"
thd.ValueType = "href" // column type href
thd.Value = components.ContentHref{
Text: "edit",
Link: "#",
}
allTdetail = append(allTdetail, thd)
thd.Caption = "Notifications"
thd.ValueType = "badge" // column type href
thd.Value = components.ContentBadge{
Text: "success",
TypeClass: "success",
}
allTdetail = append(allTdetail, thd)
// random values for pie
one := rand.IntN(50)
two := rand.IntN(50)
three := rand.IntN(50)
valuesgraph := fmt.Sprintf("%d|%d|%d", one, two, three)
// now fill data of the components
tmplData := templateData{
ContentTable: components.ContentTable{
ID: "table_demo",
AllTH: allTh,
AllTD: allTd,
},
ContentTabledetail: components.ContentTabledetail{
ID: "table_demodetail",
Title: "Sample table detail",
HeadClass: "table-warning",
AllTD: allTdetail,
},
ContentGraph: components.ContentGraph{
Graph: "pie",
Labels: "Berlin|Paris|Venecia",
Values: valuesgraph,
},
}
return c.Response.Template("custom_theme_contentpage.html", tmplData)
} else {
message := "{\"message\": \"Error, template not enabled\"}"
return c.Response.Json(message)
}
}

48
go.mod
View file

@ -10,7 +10,7 @@ replace (
go 1.23.1
require (
git.smarteching.com/goffee/core v1.8.4
git.smarteching.com/goffee/core v1.7.3
github.com/google/uuid v1.6.0
github.com/joho/godotenv v1.5.1
github.com/julienschmidt/httprouter v1.3.0
@ -18,41 +18,33 @@ require (
)
require (
filippo.io/edwards25519 v1.1.0 // indirect
git.smarteching.com/zeni/go-chart/v2 v2.1.4 // indirect
github.com/SparkPost/gosparkpost v0.2.0 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // 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.1.0 // 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.8.1 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/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.7.1 // 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.4.0 // indirect
github.com/mailgun/mailgun-go/v4 v4.17.3 // indirect
github.com/mattn/go-sqlite3 v1.14.24 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/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.7.0 // 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.0+incompatible // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/image v0.21.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/text v0.19.0 // indirect
gorm.io/driver/mysql v1.5.7 // indirect
gorm.io/driver/postgres v1.5.9 // indirect
gorm.io/driver/sqlite v1.5.6 // indirect
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
)

111
go.sum
View file

@ -1,23 +1,19 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
git.smarteching.com/goffee/core v1.8.4 h1:XB+vpe7e8muiDChRVDaJ1TG7H+/FBxDQcMfWp4zloPs=
git.smarteching.com/goffee/core v1.8.4/go.mod h1:JxXDvTQU2shKYY6c9aS3s6sFh7mEDzgmjzdc85HhBV8=
git.smarteching.com/zeni/go-chart/v2 v2.1.4 h1:pF06+F6eqJLIG8uMiTVPR5TygPGMjM/FHMzTxmu5V/Q=
git.smarteching.com/zeni/go-chart/v2 v2.1.4/go.mod h1:b3ueW9h3pGGXyhkormZAvilHaG4+mQti+bMNPdQBeOQ=
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=
@ -29,19 +25,16 @@ github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojt
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y=
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
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.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/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=
@ -49,12 +42,10 @@ github.com/harranali/mailing v1.2.0 h1:ihIyJwB8hyRVcdk+v465wk1PHMrSrgJqo/kMd+gZC
github.com/harranali/mailing v1.2.0/go.mod h1:4a5N3yG98pZKluMpmcYlTtll7bisvOfGQEMIng3VQk4=
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.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs=
github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/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=
@ -63,74 +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/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.4.0 h1:6LFBvod6VIW83CMIOT9sYNp28TCX0NejFPP4dSX++i8=
github.com/mailgun/errors v0.4.0/go.mod h1:xGBaaKdEdQT0/FhwvoXv4oBaqqmVZz9P1XEnvD/onc0=
github.com/mailgun/mailgun-go/v4 v4.17.3 h1:WoO48/VeXgAVSzjgzyeLvF08AoPzWU2EBz79INN8rEA=
github.com/mailgun/mailgun-go/v4 v4.17.3/go.mod h1:0ood70bQR/SffQ9NxIsAY06H+HG0hrvMVApfUp9TihI=
github.com/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.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
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.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
github.com/redis/go-redis/v9 v9.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.0+incompatible h1:i8eE6IMkiCy7vusSdacHHSBUpXyTcTXy/Rl9N9aZ/Qw=
github.com/sendgrid/sendgrid-go v3.16.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s=
golang.org/x/image v0.21.0/go.mod h1:vUbsLavqK/W303ZroQQVKQ+Af3Yl6Uz1Ppu5J/cLz78=
golang.org/x/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.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/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.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/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.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
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=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8=
gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
gorm.io/driver/sqlite v1.5.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE=
gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/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=

View file

@ -3,8 +3,6 @@ package hooks
import (
"errors"
"net/http"
"os"
"strconv"
"strings"
"git.smarteching.com/goffee/core"
@ -13,92 +11,20 @@ import (
"gorm.io/gorm"
)
var CheckSessionCookie core.Hook = func(c *core.Context) {
pass := true
token := ""
usercookie, err := c.GetCookie()
if err != nil {
}
token = usercookie.Token
var AuthCheck core.Hook = func(c *core.Context) {
tokenRaw := c.GetHeader("Authorization")
token := strings.TrimSpace(strings.Replace(tokenRaw, "Bearer", "", 1))
if token == "" {
pass = false
} else {
payload, err := c.GetJWT().DecodeToken(token)
if err != nil {
pass = false
} else {
userAgent := c.GetUserAgent()
hashedCacheKey := utils.CreateAuthTokenHashedCacheKey(uint(c.CastToInt(payload["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 = ?", payload["userID"]).First(&user)
if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) {
pass = false
}
}
}
}
// if have session redirect protected page
if pass {
c.Response.Redirect("/appsample").ForceSendResponse()
c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{
"message": "unauthorized",
})).ForceSendResponse()
return
}
c.Next()
}
var AuthCheck core.Hook = func(c *core.Context) {
// check if template engine is enable
TemplateEnableStr := os.Getenv("TEMPLATE_ENABLE")
if TemplateEnableStr == "" {
TemplateEnableStr = "false"
}
TemplateEnable, _ := strconv.ParseBool(TemplateEnableStr)
token := ""
if TemplateEnable {
usercookie, err := c.GetCookie()
if err != nil {
}
token = usercookie.Token
if token == "" {
c.Response.Redirect("/applogin").ForceSendResponse()
return
}
} else {
tokenRaw := c.GetHeader("Authorization")
token = strings.TrimSpace(strings.Replace(tokenRaw, "Bearer", "", 1))
if token == "" {
c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{
"message": "unauthorized",
})).ForceSendResponse()
return
}
}
payload, err := c.GetJWT().DecodeToken(token)
if err != nil {
if TemplateEnable {
c.Response.Redirect("/applogin").ForceSendResponse()
} else {
c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{
"message": "unauthorized",
})).ForceSendResponse()
}
c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{
"message": "unauthorized",
})).ForceSendResponse()
return
}
userAgent := c.GetUserAgent()
@ -107,24 +33,16 @@ var AuthCheck core.Hook = func(c *core.Context) {
cachedToken, err := c.GetCache().Get(hashedCacheKey)
if err != nil {
// user signed out
if TemplateEnable {
c.Response.Redirect("/applogin").ForceSendResponse()
} else {
c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{
"message": "unauthorized",
})).ForceSendResponse()
}
c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{
"message": "unauthorized",
})).ForceSendResponse()
return
}
if cachedToken != token {
// using old token replaced with new one after recent signin
if TemplateEnable {
c.Response.Redirect("/applogin").ForceSendResponse()
} else {
c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{
"message": "unauthorized",
})).ForceSendResponse()
}
c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{
"message": "unauthorized",
})).ForceSendResponse()
return
}
@ -133,13 +51,17 @@ var AuthCheck core.Hook = func(c *core.Context) {
if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) {
// error with the database
c.GetLogger().Error(res.Error.Error())
if TemplateEnable {
c.Response.Redirect("/applogin").ForceSendResponse()
} else {
c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]interface{}{
"message": "internal error",
})).ForceSendResponse()
}
c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]interface{}{
"message": "internal error",
})).ForceSendResponse()
return
}
if res.Error != nil && errors.Is(res.Error, gorm.ErrRecordNotFound) {
// user record is not found (deleted)
c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{
"message": "unauthorized",
})).ForceSendResponse()
return
}

15
main.go
View file

@ -19,32 +19,21 @@ import (
"github.com/julienschmidt/httprouter"
)
//go:embed all:storage/templates
//go:embed all:templates
var resources embed.FS
// The main function
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")
}

View file

@ -20,31 +20,11 @@ func registerRoutes() {
// Define your routes here...
controller.Get("/", controllers.WelcomeHome)
// Uncomment the lines below to enable theme demo
controller.Get("/themebase", controllers.Themedemo)
controller.Get("/themeform", controllers.Themeform)
controller.Get("/themecontent", controllers.Themecontent)
controller.Get("/themepanel", controllers.Themedemo)
controller.Get("/themeelements", controllers.ThemeElements)
// Uncomment the lines below to enable authentication
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)
// 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)
controller.Get("/dashboard", controllers.WelcomeToDashboard, hooks.AuthCheck)
// templates demos
controller.Get("/signout", controllers.Signout)
controller.Get("/appsample", controllers.AppSample, hooks.AuthCheck)
controller.Post("/appsample", controllers.AppSample, hooks.AuthCheck)
controller.Get("/applogin", controllers.AppLogin, hooks.CheckSessionCookie)
controller.Post("/applogin", controllers.AppLogin, hooks.CheckSessionCookie)
controller.Get("/appsession", controllers.AppSession)
controller.Post("/appsession", controllers.AppSession)
}

View file

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

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,597 +0,0 @@
/*!
* Bootstrap Reboot v5.3.3 (https://getbootstrap.com/)
* Copyright 2011-2024 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
:root,
[data-bs-theme=light] {
--bs-blue: #0d6efd;
--bs-indigo: #6610f2;
--bs-purple: #6f42c1;
--bs-pink: #d63384;
--bs-red: #dc3545;
--bs-orange: #fd7e14;
--bs-yellow: #ffc107;
--bs-green: #198754;
--bs-teal: #20c997;
--bs-cyan: #0dcaf0;
--bs-black: #000;
--bs-white: #fff;
--bs-gray: #6c757d;
--bs-gray-dark: #343a40;
--bs-gray-100: #f8f9fa;
--bs-gray-200: #e9ecef;
--bs-gray-300: #dee2e6;
--bs-gray-400: #ced4da;
--bs-gray-500: #adb5bd;
--bs-gray-600: #6c757d;
--bs-gray-700: #495057;
--bs-gray-800: #343a40;
--bs-gray-900: #212529;
--bs-primary: #0d6efd;
--bs-secondary: #6c757d;
--bs-success: #198754;
--bs-info: #0dcaf0;
--bs-warning: #ffc107;
--bs-danger: #dc3545;
--bs-light: #f8f9fa;
--bs-dark: #212529;
--bs-primary-rgb: 13, 110, 253;
--bs-secondary-rgb: 108, 117, 125;
--bs-success-rgb: 25, 135, 84;
--bs-info-rgb: 13, 202, 240;
--bs-warning-rgb: 255, 193, 7;
--bs-danger-rgb: 220, 53, 69;
--bs-light-rgb: 248, 249, 250;
--bs-dark-rgb: 33, 37, 41;
--bs-primary-text-emphasis: #052c65;
--bs-secondary-text-emphasis: #2b2f32;
--bs-success-text-emphasis: #0a3622;
--bs-info-text-emphasis: #055160;
--bs-warning-text-emphasis: #664d03;
--bs-danger-text-emphasis: #58151c;
--bs-light-text-emphasis: #495057;
--bs-dark-text-emphasis: #495057;
--bs-primary-bg-subtle: #cfe2ff;
--bs-secondary-bg-subtle: #e2e3e5;
--bs-success-bg-subtle: #d1e7dd;
--bs-info-bg-subtle: #cff4fc;
--bs-warning-bg-subtle: #fff3cd;
--bs-danger-bg-subtle: #f8d7da;
--bs-light-bg-subtle: #fcfcfd;
--bs-dark-bg-subtle: #ced4da;
--bs-primary-border-subtle: #9ec5fe;
--bs-secondary-border-subtle: #c4c8cb;
--bs-success-border-subtle: #a3cfbb;
--bs-info-border-subtle: #9eeaf9;
--bs-warning-border-subtle: #ffe69c;
--bs-danger-border-subtle: #f1aeb5;
--bs-light-border-subtle: #e9ecef;
--bs-dark-border-subtle: #adb5bd;
--bs-white-rgb: 255, 255, 255;
--bs-black-rgb: 0, 0, 0;
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
--bs-body-font-family: var(--bs-font-sans-serif);
--bs-body-font-size: 1rem;
--bs-body-font-weight: 400;
--bs-body-line-height: 1.5;
--bs-body-color: #212529;
--bs-body-color-rgb: 33, 37, 41;
--bs-body-bg: #fff;
--bs-body-bg-rgb: 255, 255, 255;
--bs-emphasis-color: #000;
--bs-emphasis-color-rgb: 0, 0, 0;
--bs-secondary-color: rgba(33, 37, 41, 0.75);
--bs-secondary-color-rgb: 33, 37, 41;
--bs-secondary-bg: #e9ecef;
--bs-secondary-bg-rgb: 233, 236, 239;
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
--bs-tertiary-color-rgb: 33, 37, 41;
--bs-tertiary-bg: #f8f9fa;
--bs-tertiary-bg-rgb: 248, 249, 250;
--bs-heading-color: inherit;
--bs-link-color: #0d6efd;
--bs-link-color-rgb: 13, 110, 253;
--bs-link-decoration: underline;
--bs-link-hover-color: #0a58ca;
--bs-link-hover-color-rgb: 10, 88, 202;
--bs-code-color: #d63384;
--bs-highlight-color: #212529;
--bs-highlight-bg: #fff3cd;
--bs-border-width: 1px;
--bs-border-style: solid;
--bs-border-color: #dee2e6;
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
--bs-border-radius: 0.375rem;
--bs-border-radius-sm: 0.25rem;
--bs-border-radius-lg: 0.5rem;
--bs-border-radius-xl: 1rem;
--bs-border-radius-xxl: 2rem;
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
--bs-border-radius-pill: 50rem;
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
--bs-focus-ring-width: 0.25rem;
--bs-focus-ring-opacity: 0.25;
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
--bs-form-valid-color: #198754;
--bs-form-valid-border-color: #198754;
--bs-form-invalid-color: #dc3545;
--bs-form-invalid-border-color: #dc3545;
}
[data-bs-theme=dark] {
color-scheme: dark;
--bs-body-color: #dee2e6;
--bs-body-color-rgb: 222, 226, 230;
--bs-body-bg: #212529;
--bs-body-bg-rgb: 33, 37, 41;
--bs-emphasis-color: #fff;
--bs-emphasis-color-rgb: 255, 255, 255;
--bs-secondary-color: rgba(222, 226, 230, 0.75);
--bs-secondary-color-rgb: 222, 226, 230;
--bs-secondary-bg: #343a40;
--bs-secondary-bg-rgb: 52, 58, 64;
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
--bs-tertiary-color-rgb: 222, 226, 230;
--bs-tertiary-bg: #2b3035;
--bs-tertiary-bg-rgb: 43, 48, 53;
--bs-primary-text-emphasis: #6ea8fe;
--bs-secondary-text-emphasis: #a7acb1;
--bs-success-text-emphasis: #75b798;
--bs-info-text-emphasis: #6edff6;
--bs-warning-text-emphasis: #ffda6a;
--bs-danger-text-emphasis: #ea868f;
--bs-light-text-emphasis: #f8f9fa;
--bs-dark-text-emphasis: #dee2e6;
--bs-primary-bg-subtle: #031633;
--bs-secondary-bg-subtle: #161719;
--bs-success-bg-subtle: #051b11;
--bs-info-bg-subtle: #032830;
--bs-warning-bg-subtle: #332701;
--bs-danger-bg-subtle: #2c0b0e;
--bs-light-bg-subtle: #343a40;
--bs-dark-bg-subtle: #1a1d20;
--bs-primary-border-subtle: #084298;
--bs-secondary-border-subtle: #41464b;
--bs-success-border-subtle: #0f5132;
--bs-info-border-subtle: #087990;
--bs-warning-border-subtle: #997404;
--bs-danger-border-subtle: #842029;
--bs-light-border-subtle: #495057;
--bs-dark-border-subtle: #343a40;
--bs-heading-color: inherit;
--bs-link-color: #6ea8fe;
--bs-link-hover-color: #8bb9fe;
--bs-link-color-rgb: 110, 168, 254;
--bs-link-hover-color-rgb: 139, 185, 254;
--bs-code-color: #e685b5;
--bs-highlight-color: #dee2e6;
--bs-highlight-bg: #664d03;
--bs-border-color: #495057;
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
--bs-form-valid-color: #75b798;
--bs-form-valid-border-color: #75b798;
--bs-form-invalid-color: #ea868f;
--bs-form-invalid-border-color: #ea868f;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
border: 0;
border-top: var(--bs-border-width) solid;
opacity: 0.25;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
color: var(--bs-heading-color);
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-left: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-left: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 0.875em;
}
mark {
padding: 0.1875em;
color: var(--bs-highlight-color);
background-color: var(--bs-highlight-bg);
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
a {
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
text-decoration: underline;
}
a:hover {
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: var(--bs-font-monospace);
font-size: 1em;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: var(--bs-code-color);
word-wrap: break-word;
}
a > code {
color: inherit;
}
kbd {
padding: 0.1875rem 0.375rem;
font-size: 0.875em;
color: var(--bs-body-bg);
background-color: var(--bs-body-color);
border-radius: 0.25rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
}
figure {
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: var(--bs-secondary-color);
text-align: left;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
}
[role=button] {
cursor: pointer;
}
select {
word-wrap: normal;
}
select:disabled {
opacity: 1;
}
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
display: none !important;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: left;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
font-size: calc(1.275rem + 0.3vw);
line-height: inherit;
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: left;
}
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-minute,
::-webkit-datetime-edit-hour-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
}
[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px;
}
/* rtl:raw:
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
}
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
::file-selector-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
iframe {
border: 0;
}
summary {
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,594 +0,0 @@
/*!
* Bootstrap Reboot v5.3.3 (https://getbootstrap.com/)
* Copyright 2011-2024 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
:root,
[data-bs-theme=light] {
--bs-blue: #0d6efd;
--bs-indigo: #6610f2;
--bs-purple: #6f42c1;
--bs-pink: #d63384;
--bs-red: #dc3545;
--bs-orange: #fd7e14;
--bs-yellow: #ffc107;
--bs-green: #198754;
--bs-teal: #20c997;
--bs-cyan: #0dcaf0;
--bs-black: #000;
--bs-white: #fff;
--bs-gray: #6c757d;
--bs-gray-dark: #343a40;
--bs-gray-100: #f8f9fa;
--bs-gray-200: #e9ecef;
--bs-gray-300: #dee2e6;
--bs-gray-400: #ced4da;
--bs-gray-500: #adb5bd;
--bs-gray-600: #6c757d;
--bs-gray-700: #495057;
--bs-gray-800: #343a40;
--bs-gray-900: #212529;
--bs-primary: #0d6efd;
--bs-secondary: #6c757d;
--bs-success: #198754;
--bs-info: #0dcaf0;
--bs-warning: #ffc107;
--bs-danger: #dc3545;
--bs-light: #f8f9fa;
--bs-dark: #212529;
--bs-primary-rgb: 13, 110, 253;
--bs-secondary-rgb: 108, 117, 125;
--bs-success-rgb: 25, 135, 84;
--bs-info-rgb: 13, 202, 240;
--bs-warning-rgb: 255, 193, 7;
--bs-danger-rgb: 220, 53, 69;
--bs-light-rgb: 248, 249, 250;
--bs-dark-rgb: 33, 37, 41;
--bs-primary-text-emphasis: #052c65;
--bs-secondary-text-emphasis: #2b2f32;
--bs-success-text-emphasis: #0a3622;
--bs-info-text-emphasis: #055160;
--bs-warning-text-emphasis: #664d03;
--bs-danger-text-emphasis: #58151c;
--bs-light-text-emphasis: #495057;
--bs-dark-text-emphasis: #495057;
--bs-primary-bg-subtle: #cfe2ff;
--bs-secondary-bg-subtle: #e2e3e5;
--bs-success-bg-subtle: #d1e7dd;
--bs-info-bg-subtle: #cff4fc;
--bs-warning-bg-subtle: #fff3cd;
--bs-danger-bg-subtle: #f8d7da;
--bs-light-bg-subtle: #fcfcfd;
--bs-dark-bg-subtle: #ced4da;
--bs-primary-border-subtle: #9ec5fe;
--bs-secondary-border-subtle: #c4c8cb;
--bs-success-border-subtle: #a3cfbb;
--bs-info-border-subtle: #9eeaf9;
--bs-warning-border-subtle: #ffe69c;
--bs-danger-border-subtle: #f1aeb5;
--bs-light-border-subtle: #e9ecef;
--bs-dark-border-subtle: #adb5bd;
--bs-white-rgb: 255, 255, 255;
--bs-black-rgb: 0, 0, 0;
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
--bs-body-font-family: var(--bs-font-sans-serif);
--bs-body-font-size: 1rem;
--bs-body-font-weight: 400;
--bs-body-line-height: 1.5;
--bs-body-color: #212529;
--bs-body-color-rgb: 33, 37, 41;
--bs-body-bg: #fff;
--bs-body-bg-rgb: 255, 255, 255;
--bs-emphasis-color: #000;
--bs-emphasis-color-rgb: 0, 0, 0;
--bs-secondary-color: rgba(33, 37, 41, 0.75);
--bs-secondary-color-rgb: 33, 37, 41;
--bs-secondary-bg: #e9ecef;
--bs-secondary-bg-rgb: 233, 236, 239;
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
--bs-tertiary-color-rgb: 33, 37, 41;
--bs-tertiary-bg: #f8f9fa;
--bs-tertiary-bg-rgb: 248, 249, 250;
--bs-heading-color: inherit;
--bs-link-color: #0d6efd;
--bs-link-color-rgb: 13, 110, 253;
--bs-link-decoration: underline;
--bs-link-hover-color: #0a58ca;
--bs-link-hover-color-rgb: 10, 88, 202;
--bs-code-color: #d63384;
--bs-highlight-color: #212529;
--bs-highlight-bg: #fff3cd;
--bs-border-width: 1px;
--bs-border-style: solid;
--bs-border-color: #dee2e6;
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
--bs-border-radius: 0.375rem;
--bs-border-radius-sm: 0.25rem;
--bs-border-radius-lg: 0.5rem;
--bs-border-radius-xl: 1rem;
--bs-border-radius-xxl: 2rem;
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
--bs-border-radius-pill: 50rem;
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
--bs-focus-ring-width: 0.25rem;
--bs-focus-ring-opacity: 0.25;
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
--bs-form-valid-color: #198754;
--bs-form-valid-border-color: #198754;
--bs-form-invalid-color: #dc3545;
--bs-form-invalid-border-color: #dc3545;
}
[data-bs-theme=dark] {
color-scheme: dark;
--bs-body-color: #dee2e6;
--bs-body-color-rgb: 222, 226, 230;
--bs-body-bg: #212529;
--bs-body-bg-rgb: 33, 37, 41;
--bs-emphasis-color: #fff;
--bs-emphasis-color-rgb: 255, 255, 255;
--bs-secondary-color: rgba(222, 226, 230, 0.75);
--bs-secondary-color-rgb: 222, 226, 230;
--bs-secondary-bg: #343a40;
--bs-secondary-bg-rgb: 52, 58, 64;
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
--bs-tertiary-color-rgb: 222, 226, 230;
--bs-tertiary-bg: #2b3035;
--bs-tertiary-bg-rgb: 43, 48, 53;
--bs-primary-text-emphasis: #6ea8fe;
--bs-secondary-text-emphasis: #a7acb1;
--bs-success-text-emphasis: #75b798;
--bs-info-text-emphasis: #6edff6;
--bs-warning-text-emphasis: #ffda6a;
--bs-danger-text-emphasis: #ea868f;
--bs-light-text-emphasis: #f8f9fa;
--bs-dark-text-emphasis: #dee2e6;
--bs-primary-bg-subtle: #031633;
--bs-secondary-bg-subtle: #161719;
--bs-success-bg-subtle: #051b11;
--bs-info-bg-subtle: #032830;
--bs-warning-bg-subtle: #332701;
--bs-danger-bg-subtle: #2c0b0e;
--bs-light-bg-subtle: #343a40;
--bs-dark-bg-subtle: #1a1d20;
--bs-primary-border-subtle: #084298;
--bs-secondary-border-subtle: #41464b;
--bs-success-border-subtle: #0f5132;
--bs-info-border-subtle: #087990;
--bs-warning-border-subtle: #997404;
--bs-danger-border-subtle: #842029;
--bs-light-border-subtle: #495057;
--bs-dark-border-subtle: #343a40;
--bs-heading-color: inherit;
--bs-link-color: #6ea8fe;
--bs-link-hover-color: #8bb9fe;
--bs-link-color-rgb: 110, 168, 254;
--bs-link-hover-color-rgb: 139, 185, 254;
--bs-code-color: #e685b5;
--bs-highlight-color: #dee2e6;
--bs-highlight-bg: #664d03;
--bs-border-color: #495057;
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
--bs-form-valid-color: #75b798;
--bs-form-valid-border-color: #75b798;
--bs-form-invalid-color: #ea868f;
--bs-form-invalid-border-color: #ea868f;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
border: 0;
border-top: var(--bs-border-width) solid;
opacity: 0.25;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
color: var(--bs-heading-color);
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-right: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-right: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 0.875em;
}
mark {
padding: 0.1875em;
color: var(--bs-highlight-color);
background-color: var(--bs-highlight-bg);
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
a {
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
text-decoration: underline;
}
a:hover {
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: var(--bs-font-monospace);
font-size: 1em;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: var(--bs-code-color);
word-wrap: break-word;
}
a > code {
color: inherit;
}
kbd {
padding: 0.1875rem 0.375rem;
font-size: 0.875em;
color: var(--bs-body-bg);
background-color: var(--bs-body-color);
border-radius: 0.25rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
}
figure {
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: var(--bs-secondary-color);
text-align: right;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
}
[role=button] {
cursor: pointer;
}
select {
word-wrap: normal;
}
select:disabled {
opacity: 1;
}
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
display: none !important;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: right;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
font-size: calc(1.275rem + 0.3vw);
line-height: inherit;
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: right;
}
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-minute,
::-webkit-datetime-edit-hour-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
}
[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px;
}
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
}
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
::file-selector-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
iframe {
border: 0;
}
summary {
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

View file

@ -1 +0,0 @@

View file

@ -1,11 +0,0 @@
## styles
body {
background-color: #f2f2f2;
font-family: "Helvetica Neue";
}
.goffeelogo {
display: block;
margin: 0 auto;
}

BIN
storage/sqlite.db Normal file

Binary file not shown.

Binary file not shown.

View file

@ -1,17 +0,0 @@
<!DOCTYPE html>
<html lang="en">
{{template "page_head" "Sample page"}}
<body>
<div class="container">
<div class="row">
{{template "content_dropdown" .ContentDropdown}}
{{template "page_card" .PageCard}}
{{ define "page_card_content" }}
<img class="goffeelogo"src="/public/img/goffee.png" alt="Goffee logo" />
{{ end }}
</div>
</div>
{{template "page_footer"}}
</body>
</html>

View file

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

View file

@ -1,10 +0,0 @@
<!DOCTYPE html>
<html lang="en">
{{template "page_head" "Goffee"}}
<body>
<div class="container">
{{template "page_card" .PageCard}}
</div>
{{template "page_footer"}}
</body>
</html>

View file

@ -1,24 +0,0 @@
<!DOCTYPE html>
<html lang="en">
{{template "page_head" "Goffee"}}
<body>
<div class="container">
<fieldset>
<legend>Content demos</legend>
<div class="row">
{{template "content_table" .ContentTable}}
</div>
<div class="row">
<div class="col-lg-6">
<h2>Pie chart</h2>
{{template "content_graph" .ContentGraph}}
</div>
<div class="col-lg-6">
{{template "content_tabledetail" .ContentTabledetail}}
</div>
</div>
</fieldset>
</div>
{{template "page_footer"}}
</body>
</html>

View file

@ -1,66 +0,0 @@
<!DOCTYPE html>
<html lang="en">
{{template "page_head" "Goffee"}}
<body>
<div class="container">
<fieldset class="mb-3">
<legend>Demos buttons</legend>
<div class="container border rounded-3 p-2">
{{range .Buttons}}
{{template "form_button" .}}
{{end}}
</div>
</fieldset>
<fieldset class="mb-3">
<legend>Demos href</legend>
<div class="container d-flex justify-content-between border rounded-3 p-2">
{{range .Hrefs}}
{{template "content_href" .}}
{{end}}
</div>
</fieldset>
<fieldset class="mb-3">
<legend>Demos Badges</legend>
<div class="container d-flex justify-content-between border rounded-3 p-2">
{{range .Badges}}
{{template "content_badge" .}}
{{end}}
</div>
</fieldset>
<fieldset class="mb-3">
<legend>Demos dropdown</legend>
<div class="container d-flex justify-content-between border rounded-3 p-2">
{{range .Dropdowns}}
{{template "content_dropdown" .}}
{{end}}
</div>
</fieldset>
<fieldset class="mb-3">
<legend>Demos List</legend>
<div class="container d-flex justify-content-between border rounded-3 p-2">
{{range .Lists}}
{{template "content_list" .}}
{{end}}
</div>
</fieldset>
<fieldset class="mb-3">
<legend>Demos nav</legend>
<div class="container border rounded-3 p-2 ">
{{range .Menus}}
<div class="container border rounded-3 p-2 mb-2">
{{template "page_nav" .}}
</div>
{{end}}
</div>
</fieldset>
</div>
{{template "page_footer"}}
</body>
</html>

View file

@ -1,23 +0,0 @@
<!DOCTYPE html>
<html lang="en">
{{template "page_head" "Goffee"}}
<body>
<div class="container">
<form>
<fieldset>
<legend>form demos</legend>
<div class="row">
{{template "form_input" .FormText}}
{{template "form_input" .FormEmail}}
{{template "form_select" .FormSelectCity}}
{{template "form_textarea" .FormTextarea}}
{{template "form_radio" .FormRadio}}
{{template "form_checkbox" .FormCheckbox}}
</div>
</fieldset>
{{template "form_button" .FormButton}}
</form>
</div>
{{template "page_footer"}}
</body>
</html>

View file

@ -1,24 +0,0 @@
<!DOCTYPE html>
<html lang="en">
{{template "page_head" "Goffee"}}
<body>
<main>
<form method="POST" action="/signin">
<div>
<label for="email">Email:</label>
<input type="text" id="email" name="email" required>
</div>
<div>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
</div>
<div>
<button type="submit" name="login">Login</button>
</div>
</form>
</main>
<script src="/public/app.js"></script>
</body>
</html>

View file

@ -1,15 +0,0 @@
<!DOCTYPE html>
<html lang="en">
{{template "page_head" "Sample page"}}
<body>
<div class="container">
<div class="row">
{{template "page_card" .PageCard}}
{{ define "page_card_content" }}
<img class="goffeelogo"src="/public/img/goffee.png" alt="Goffee logo" />
{{ end }}
</div>
</div>
{{template "page_footer"}}
</body>
</html>

10
templates/sample.html Normal file
View file

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
{{template "head" "Sample"}}
<body>
{{template "title" .TheTitle}}
<div class="section">
Sample page!
</div>
</body>
</html>

View file

@ -1,170 +0,0 @@
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
// Use of this source code is governed by MIT-style
// license that can be found in the LICENSE file.
package utils
import (
"errors"
"fmt"
"sync"
"time"
"encoding/json"
"git.smarteching.com/goffee/core"
"git.smarteching.com/goffee/cup/models"
"gorm.io/gorm"
)
type SessionUser struct {
mu sync.RWMutex
context *core.Context
userID uint
hashedSessionKey string
authenticated bool
sessionStart time.Time
values map[string]interface{}
}
// start the struct
func (s *SessionUser) Init(c *core.Context) bool {
// check session cookie
pass := true
token := ""
s.context = c
payload := make(map[string]interface{})
// get cookie
usercookie, err := c.GetCookie()
if err != nil {
}
token = usercookie.Token
if token == "" {
pass = false
} else {
payload, err = c.GetJWT().DecodeToken(token)
if err != nil {
pass = false
} else {
userID := uint(c.CastToInt(payload["userID"]))
userAgent := c.GetUserAgent()
// get data from redis
hashedCacheKey := CreateAuthTokenHashedCacheKey(userID, userAgent)
cachedToken, err := c.GetCache().Get(hashedCacheKey)
if err != nil {
pass = false
} else if cachedToken != token {
pass = false
} else {
var user models.User
res := c.GetGorm().Where("id = ?", userID).First(&user)
if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) {
pass = false
}
// if have session start the struct
if pass {
userAgent := c.GetUserAgent()
sessionKey := fmt.Sprintf("sess_%v", userAgent)
s.hashedSessionKey = CreateAuthTokenHashedCacheKey(userID, sessionKey)
s.values = make(map[string]interface{})
s.authenticated = true
s.userID = userID
value, _ := c.GetCache().Get(s.hashedSessionKey)
if len(value) > 0 {
_ = json.Unmarshal([]byte(value), &s.values)
}
return true
} else {
s.hashedSessionKey = ""
s.authenticated = false
s.userID = 0
return false
}
}
}
}
return false
}
func (s *SessionUser) Set(key string, value interface{}) error {
s.mu.Lock()
s.values[key] = value
s.mu.Unlock()
return s.Save()
}
func (s *SessionUser) Get(key string) (interface{}, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
val, ok := s.values[key]
return val, ok
}
func (s *SessionUser) Delete(key string) interface{} {
s.mu.RLock()
v, ok := s.values[key]
s.mu.RUnlock()
if ok {
s.mu.Lock()
delete(s.values, key)
s.mu.Unlock()
}
s.Save()
return v
}
func (s *SessionUser) Flush() error {
s.mu.Lock()
s.context.GetCache().Delete(s.hashedSessionKey)
s.mu.Unlock()
return nil
}
func (s *SessionUser) Save() error {
var value string
s.mu.RLock()
if len(s.values) > 0 {
buf, err := json.Marshal(&s.values)
if err != nil {
s.mu.RUnlock()
return err
}
value = string(buf)
}
if len(value) > 0 {
s.context.GetCache().Set(s.hashedSessionKey, value)
} else {
s.context.GetCache().Delete(s.hashedSessionKey)
}
s.mu.RUnlock()
return nil
}
func (s *SessionUser) GetUserID() uint {
return s.userID
}