From 59eadf29d0ca88e1f793a481204ada7b65f6c551 Mon Sep 17 00:00:00 2001 From: Zeni Kim Date: Fri, 8 May 2026 02:30:34 -0500 Subject: [PATCH 1/7] fix bug Template Template compliance --- response.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/response.go b/response.go index d6202c7..7e92b2f 100644 --- a/response.go +++ b/response.go @@ -96,7 +96,7 @@ func (rs *Response) Template(name string, data interface{}) *Response { panic(fmt.Sprintf("error executing template: %v", err)) } rs.contentType = CONTENT_TYPE_HTML - buffer.WriteTo(rs.HttpResponseWriter) + rs.body = buffer.Bytes() } return rs } From 19dba8f5041fe65e5e88af8ddb982eacc0de1050 Mon Sep 17 00:00:00 2001 From: Zeni Kim Date: Mon, 11 May 2026 20:17:30 -0500 Subject: [PATCH 2/7] - Add function RenderNamedTemplate executes a named template from the registered set with the given data and returns the rendered HTML. - gormlogger.Silent is the log level that suppresses all Gorm log messages, including the "record not found" warnings. --- core.go | 16 +++++++++++++--- templates.go | 20 +++++++++++++++++++- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/core.go b/core.go index 3846432..cf99f3e 100644 --- a/core.go +++ b/core.go @@ -26,6 +26,7 @@ import ( "gorm.io/driver/postgres" "gorm.io/driver/sqlite" "gorm.io/gorm" + gormlogger "gorm.io/gorm/logger" ) var loggr *logger.Logger @@ -517,13 +518,18 @@ func NewGorm() *gorm.DB { if err != nil { panic(fmt.Sprintf("error locating sqlite file: %v", err.Error())) } - db, err = gorm.Open(sqlite.Open(fullSqlitePath), &gorm.Config{}) + db, err = gorm.Open(sqlite.Open(fullSqlitePath), &gorm.Config{ + Logger: gormlogger.Default.LogMode(gormlogger.Silent), + }) default: panic("database driver not selected") } if gormC.EnableGorm && err != nil { panic(fmt.Sprintf("gorm has problem connecting to %v, (if it's not needed you can disable it in config/gorm.go): %v", os.Getenv("DB_DRIVER"), err)) } + if db != nil { + db.Logger = db.Logger.LogMode(gormlogger.Silent) + } return db } @@ -560,7 +566,9 @@ func postgresConnect() (*gorm.DB, error) { os.Getenv("POSTGRES_SSL_MODE"), os.Getenv("POSTGRES_TIMEZONE"), ) - return gorm.Open(postgres.Open(dsn), &gorm.Config{}) + return gorm.Open(postgres.Open(dsn), &gorm.Config{ + Logger: gormlogger.Default.LogMode(gormlogger.Silent), + }) } // mysqlConnect establishes a connection to a MySQL database using credentials and configurations from environment variables. @@ -581,7 +589,9 @@ func mysqlConnect() (*gorm.DB, error) { DontSupportRenameIndex: true, // drop & create when rename index, rename index not supported before MySQL 5.7, MariaDB DontSupportRenameColumn: true, // `change` when rename column, rename column not supported before MySQL 8, MariaDB SkipInitializeWithVersion: false, // auto configure based on currently MySQL version - }), &gorm.Config{}) + }), &gorm.Config{ + Logger: gormlogger.Default.LogMode(gormlogger.Silent), + }) } // getJWT returns a function that initializes and provides a *JWT instance configured with environment variables. diff --git a/templates.go b/templates.go index 4ee0050..cf6692e 100644 --- a/templates.go +++ b/templates.go @@ -359,4 +359,22 @@ func NewTemplates(components embed.FS, templates embed.FS) { ParseFS(components, paths...), ) tmpl = template.Must(tmpl.ParseFS(templates, pathst...)) -} \ No newline at end of file +} + + +// RenderNamedTemplate executes a named template from the registered set with +// the given data and returns the rendered HTML. +// Usage: +// +// html, err := core.RenderNamedTemplate("tabler_table", data) +// if err != nil { +// // handle error +// } +// return c.Response.HTML(string(html)) +func RenderNamedTemplate(name string, data interface{}) (template.HTML, error) { + var buf strings.Builder + if err := tmpl.ExecuteTemplate(&buf, name, data); err != nil { + return "", fmt.Errorf("failed to execute template %q: %w", name, err) + } + return template.HTML(buf.String()), nil +} From 262c5befd9c6fabb37d7106d8d303d8c856d8958 Mon Sep 17 00:00:00 2001 From: Zeni Kim Date: Mon, 11 May 2026 21:35:00 -0500 Subject: [PATCH 3/7] - COOKIE_SECURE environment variable (defaults to true, set to false for local HTTP development) - Cookie time and JWT share the same time --- cookies.go | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/cookies.go b/cookies.go index 4e51470..9830099 100644 --- a/cookies.go +++ b/cookies.go @@ -19,6 +19,7 @@ import ( "os" "strconv" "strings" + "time" ) // ErrValueTooLong indicates that the cookie value exceeds the allowed length limit. @@ -84,9 +85,8 @@ func GetCookie(r *http.Request) (UserCookie, error) { } // SetCookie sets an encrypted cookie with a user's email and token, using gob encoding for data serialization. +// The Secure flag is controlled by the COOKIE_SECURE environment variable (defaults to true, set to false for local HTTP development). func SetCookie(w http.ResponseWriter, email string, token string) error { - // Initialize a User struct containing the data that we want to store in the - // cookie. var err error // check if template engine is enable @@ -124,15 +124,36 @@ func SetCookie(w http.ResponseWriter, email string, token string) error { return err } + // Derive cookie MaxAge from JWT_LIFESPAN_MINUTES (default: 1440 min = 1 day) + maxAge := 1440 * 60 // default 1 day in seconds + lifetimeStr := os.Getenv("JWT_LIFESPAN_MINUTES") + if lifetimeStr != "" { + lifetime, parseErr := strconv.Atoi(lifetimeStr) + if parseErr == nil { + maxAge = lifetime * 60 // convert minutes to seconds + } + } + + // Determine if the cookie should have the Secure flag. + // Set COOKIE_SECURE=false (or "0", "f") in your .env for local development over HTTP. + // Defaults to true for production safety. + cookieSecureStr := os.Getenv("COOKIE_SECURE") + if cookieSecureStr == "" { + cookieSecureStr = "true" + } + cookieSecure, _ := strconv.ParseBool(cookieSecureStr) + // Call buf.String() to get the gob-encoded value as a string and set it as // the cookie value. cookie := http.Cookie{ Name: "goffee", Value: buf.String(), Path: "/", - MaxAge: 3600, + MaxAge: maxAge, + Expires: time.Now().Add(time.Duration(maxAge) * time.Second), HttpOnly: true, SameSite: http.SameSiteLaxMode, + Secure: cookieSecure, } // Write an encrypted cookie containing the gob-encoded data as normal. From 8446f6fb2a251be90bd65d15fb83872ed089340b Mon Sep 17 00:00:00 2001 From: Zeni Kim Date: Tue, 12 May 2026 23:21:03 -0500 Subject: [PATCH 4/7] In the core's response.go, add an optional RedirectCodeSetter field or variadic use303 ...bool parameter to Redirect() and use the stored code + http.StatusSeeOther in core.go instead of the hardcoded http.StatusTemporaryRedirect, so that a POST handler can redirect to a GET route by calling c.Response.Redirect("/url", true). --- core.go | 6 +++++- response.go | 14 ++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/core.go b/core.go index cf99f3e..4163e2d 100644 --- a/core.go +++ b/core.go @@ -308,7 +308,11 @@ func (app *App) makeHTTPRouterHandlerFunc(h Controller, ms []Hook) httprouter.Ha w.WriteHeader(ctx.Response.statusCode) } if ctx.Response.redirectTo != "" { - http.Redirect(w, r, ctx.Response.redirectTo, http.StatusTemporaryRedirect) + statusCode := ctx.Response.redirectStatusCode + if statusCode == 0 { + statusCode = http.StatusTemporaryRedirect // default to 307 + } + http.Redirect(w, r, ctx.Response.redirectTo, statusCode) } else { w.Write(ctx.Response.body) } diff --git a/response.go b/response.go index 7e92b2f..716eb05 100644 --- a/response.go +++ b/response.go @@ -20,6 +20,7 @@ type Response struct { overrideContentType string isTerminated bool redirectTo string + redirectStatusCode int HttpResponseWriter http.ResponseWriter } @@ -136,8 +137,10 @@ func (rs *Response) ForceSendResponse() { rs.isTerminated = true } -// updates the redirect URL for the response and returns the modified Response. Validates the URL before setting it. -func (rs *Response) Redirect(url string) *Response { +// Redirect sends a redirect response to the given URL. +// By default it uses 307 (Temporary Redirect) to preserve the HTTP method. +// Pass true as the second argument to use 303 (See Other), which changes POST to GET. +func (rs *Response) Redirect(url string, use303 ...bool) *Response { validator := resolveValidator() v := validator.Validate(map[string]interface{}{ "url": url, @@ -153,6 +156,13 @@ func (rs *Response) Redirect(url string) *Response { return rs } rs.redirectTo = url + + if len(use303) > 0 && use303[0] { + rs.redirectStatusCode = http.StatusSeeOther // 303 + } else { + rs.redirectStatusCode = http.StatusTemporaryRedirect // 307 (default) + } + return rs } From 8604ce94ff8741180abdf3ed4751135aea38825e Mon Sep 17 00:00:00 2001 From: Zeni Kim Date: Wed, 13 May 2026 00:08:36 -0500 Subject: [PATCH 5/7] fix order --- response.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/response.go b/response.go index 716eb05..7934f2f 100644 --- a/response.go +++ b/response.go @@ -147,6 +147,13 @@ func (rs *Response) Redirect(url string, use303 ...bool) *Response { }, map[string]interface{}{ "url": "url", }) + + if len(use303) > 0 && use303[0] { + rvi res= http.StatusSeeOther // 303 + } else { + rs.redirectStatusCode = http.StatusTemporaryRedirect // 307 (default) + } + if v.Failed() { if url[0:1] != "/" { rs.redirectTo = "/" + url @@ -157,12 +164,6 @@ func (rs *Response) Redirect(url string, use303 ...bool) *Response { } rs.redirectTo = url - if len(use303) > 0 && use303[0] { - rs.redirectStatusCode = http.StatusSeeOther // 303 - } else { - rs.redirectStatusCode = http.StatusTemporaryRedirect // 307 (default) - } - return rs } From d6dafa5ab653928c5d2b1158756426d000a7ee8e Mon Sep 17 00:00:00 2001 From: Zeni Kim Date: Wed, 13 May 2026 00:10:30 -0500 Subject: [PATCH 6/7] fix typo --- response.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/response.go b/response.go index 7934f2f..1059b4b 100644 --- a/response.go +++ b/response.go @@ -149,7 +149,7 @@ func (rs *Response) Redirect(url string, use303 ...bool) *Response { }) if len(use303) > 0 && use303[0] { - rvi res= http.StatusSeeOther // 303 + rs= http.StatusSeeOther // 303 } else { rs.redirectStatusCode = http.StatusTemporaryRedirect // 307 (default) } From 7c164ef58f59ff435d8dbdeac419ead2c59e8275 Mon Sep 17 00:00:00 2001 From: Zeni Kim Date: Wed, 13 May 2026 00:14:02 -0500 Subject: [PATCH 7/7] fix typo --- response.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/response.go b/response.go index 1059b4b..12b6406 100644 --- a/response.go +++ b/response.go @@ -149,7 +149,7 @@ func (rs *Response) Redirect(url string, use303 ...bool) *Response { }) if len(use303) > 0 && use303[0] { - rs= http.StatusSeeOther // 303 + rs.redirectStatusCode = http.StatusSeeOther // 303 } else { rs.redirectStatusCode = http.StatusTemporaryRedirect // 307 (default) }