refactor(pr): Fixed some PR issues

This commit is contained in:
Cipher Vance
2025-11-15 13:18:36 -06:00
parent b0301599c9
commit 268df9886e
5 changed files with 41 additions and 15 deletions

View File

@@ -1,6 +1,8 @@
package database package database
import ( import (
"context"
"time"
"database/sql" "database/sql"
"fmt" "fmt"
"log" "log"
@@ -27,7 +29,7 @@ type Admin struct {
// cfg provides PostgreSQL connection parameters and the default admin credentials used to create the // cfg provides PostgreSQL connection parameters and the default admin credentials used to create the
// default admin user when missing. // default admin user when missing.
func Init(cfg *config.Config) { func Init(cfg *config.Config) {
password := url.QueryEscape(cfg.PGPassword) password := url.PathEscape(cfg.PGPassword)
psqlInfo := fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=require", psqlInfo := fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=require",
cfg.PGUser, password, cfg.PGHost, cfg.PGPort, cfg.PGDatabase, cfg.PGUser, password, cfg.PGHost, cfg.PGPort, cfg.PGDatabase,
@@ -45,7 +47,9 @@ func Init(cfg *config.Config) {
db.SetMaxOpenConns(25) db.SetMaxOpenConns(25)
db.SetMaxIdleConns(5) db.SetMaxIdleConns(5)
if err = db.Ping(); err != nil { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel
if err = db.PingContext(ctx); err != nil {
log.Fatalf("Failed to ping database: %v", err) log.Fatalf("Failed to ping database: %v", err)
} }
@@ -58,7 +62,9 @@ func Init(cfg *config.Config) {
// It is safe to call multiple times; if no connection exists, the call is a no-op. // It is safe to call multiple times; if no connection exists, the call is a no-op.
func Close() { func Close() {
if db != nil { if db != nil {
db.Close() if err := db.Close(); err != nil {
log.Printf("Error closing database connection: %v", err)
}
} }
} }

View File

@@ -3,6 +3,7 @@ package email
import ( import (
"fmt" "fmt"
"log" "log"
"net/url"
"time" "time"
"github.com/rideaware/admin-panel/internal/config" "github.com/rideaware/admin-panel/internal/config"
@@ -23,22 +24,24 @@ func SendUpdate(subject, body string) (string, error) {
if err != nil { if err != nil {
return "Failed to retrieve subscribers", err return "Failed to retrieve subscribers", err
} }
if len(subscribers) == 0 { if len(subscribers) == 0 {
return "No subscribers found.", nil return "No subscribers found.", nil
} }
var succeeded, failed int
for _, email := range subscribers { for _, email := range subscribers {
if !send(subject, body, email) { if send(subject, body, email) {
return fmt.Sprintf("Failed to send to %s", email), nil succeeded++
} else {
failed++
} }
} }
if err := database.LogNewsletter(subject, body); err != nil { if err := database.LogNewsletter(subject, body); err != nil {
log.Printf("Error logging newsletter: %v", err) log.Printf("Error logging newsletter: %v", err)
} }
if failed == 0 {
return "Email has been sent to all subscribers.", nil return fmt.Sprintf("Email sent to all %d subscribers.", succeeded), nil
}
return fmt.Sprintf("Sent to %d/%d subscribers; %d failed.", succeeded, succeeded+failed, failed), nil
} }
// send constructs and sends an HTML newsletter update to the specified recipient using the current SMTP configuration. // send constructs and sends an HTML newsletter update to the specified recipient using the current SMTP configuration.
@@ -72,7 +75,7 @@ func send(subject, body, recipient string) bool {
m.Subject(subject) m.Subject(subject)
unsubLink := fmt.Sprintf("https://%s/unsubscribe?email=%s", unsubLink := fmt.Sprintf("https://%s/unsubscribe?email=%s",
cfg.BaseURL, recipient) cfg.BaseURL, url.QueryEscape(recipient))
htmlBody := fmt.Sprintf( htmlBody := fmt.Sprintf(
"%s<br><br>If you ever wish to unsubscribe, "+ "%s<br><br>If you ever wish to unsubscribe, "+

View File

@@ -21,9 +21,16 @@ func SendUpdatePost(c *gin.Context) {
subject := c.PostForm("subject") subject := c.PostForm("subject")
body := c.PostForm("body") body := c.PostForm("body")
// validate inputs
if strings.TrimSpace(subject) == "" || strings.TrimSpace(body) == {
c.HTML(http,StatusBadRequest, "send_update.html",
gin.H{"error": "Subject and message cannot be empty"})
return
}
message, err := email.SendUpdate(subject, body) message, err := email.SendUpdate(subject, body)
if err != nil { if err != nil {
c.HTML(http.StatusOK, "send_update.html", c.HTML(http.StatusInternalServerError, "send_update.html",
gin.H{"error": message}) gin.H{"error": message})
return return
} }

View File

@@ -14,7 +14,8 @@ import (
func IndexGet(c *gin.Context) { func IndexGet(c *gin.Context) {
emails, err := database.GetAllEmails() emails, err := database.GetAllEmails()
if err != nil { if err != nil {
c.AbortWithError(http.StatusInternalServerError, err) c.HTML(http.StatusInternalServerError, "admin_index.html",
gin.H{"error": "Failed to retrieve subscribers"})
return return
} }
c.HTML(http.StatusOK, "admin_index.html", c.HTML(http.StatusOK, "admin_index.html",

View File

@@ -15,6 +15,10 @@ var store *sessions.CookieStore
// It panics if config.Current.SecretKey is empty. // It panics if config.Current.SecretKey is empty.
// The created store is configured with Path "/", MaxAge one week, HttpOnly true, Secure false, and SameSite 0. // The created store is configured with Path "/", MaxAge one week, HttpOnly true, Secure false, and SameSite 0.
func Init() { func Init() {
if config.Current == nil {
panic("config was not loaded; call config.Load() before middleware.Init()")
}
if config.Current.SecretKey == "" { if config.Current.SecretKey == "" {
panic("SECRET_KEY not set") panic("SECRET_KEY not set")
} }
@@ -23,8 +27,8 @@ func Init() {
Path: "/", Path: "/",
MaxAge: 86400 * 7, MaxAge: 86400 * 7,
HttpOnly: true, HttpOnly: true,
Secure: false, Secure: true,
SameSite: 0, SameSite: http.SameSiteStrictMode,
} }
} }
@@ -40,6 +44,11 @@ func GetStore() *sessions.CookieStore {
// Otherwise the middleware calls the next handler in the chain. // Otherwise the middleware calls the next handler in the chain.
func Auth() gin.HandlerFunc { func Auth() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
if store == nil {
c.String(http.StatusInternalServerError, "Session store not initialized.")
c.Abort()
return
}
session, err := store.Get(c.Request, "session") session, err := store.Get(c.Request, "session")
if err != nil || session.Values["username"] == nil { if err != nil || session.Values["username"] == nil {
c.Redirect(http.StatusFound, "/login") c.Redirect(http.StatusFound, "/login")