From 69e0ab564522a26cb780e2755d24be1c1dc25fb5 Mon Sep 17 00:00:00 2001 From: Sky Johnson Date: Sat, 9 Aug 2025 17:30:04 -0500 Subject: [PATCH] add leftside/rightside template and database singleton --- internal/database/database.go | 100 ++++++++++++++++++--- internal/routes/auth.go | 11 ++- internal/server/server.go | 10 +-- internal/template/components/components.go | 80 ++++++++++++++++- templates/leftside.html | 46 ++++++++++ templates/rightside.html | 58 ++++++++++++ 6 files changed, 281 insertions(+), 24 deletions(-) create mode 100644 templates/leftside.html create mode 100644 templates/rightside.html diff --git a/internal/database/database.go b/internal/database/database.go index 66bc318..3a737f9 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -11,13 +11,19 @@ import ( const DefaultPath = "dk.db" -// DB wraps a SQLite connection pool with simplified methods -type DB struct { +// database wraps a SQLite connection pool with simplified methods +type database struct { pool *sqlitex.Pool } +// DB is a backward-compatible type alias +type DB = database + +// instance is the global singleton instance +var instance *database + // Open creates a new database connection pool -func Open(path string) (*DB, error) { +func Open(path string) (*database, error) { if path == "" { path = DefaultPath } @@ -49,26 +55,26 @@ func Open(path string) (*DB, error) { return nil, fmt.Errorf("failed to set synchronous mode: %w", err) } - return &DB{pool: pool}, nil + return &database{pool: pool}, nil } // Close closes the database connection pool -func (db *DB) Close() error { +func (db *database) Close() error { return db.pool.Close() } // GetConn gets a connection from the pool - caller must call Put when done -func (db *DB) GetConn(ctx context.Context) (*sqlite.Conn, error) { +func (db *database) GetConn(ctx context.Context) (*sqlite.Conn, error) { return db.pool.Take(ctx) } // PutConn returns a connection to the pool -func (db *DB) PutConn(conn *sqlite.Conn) { +func (db *database) PutConn(conn *sqlite.Conn) { db.pool.Put(conn) } // Exec executes a SQL statement without returning results -func (db *DB) Exec(query string, args ...any) error { +func (db *database) Exec(query string, args ...any) error { conn, err := db.pool.Take(context.Background()) if err != nil { return fmt.Errorf("failed to get connection from pool: %w", err) @@ -85,7 +91,7 @@ func (db *DB) Exec(query string, args ...any) error { } // Query executes a SQL query and calls fn for each row -func (db *DB) Query(query string, fn func(*sqlite.Stmt) error, args ...any) error { +func (db *database) Query(query string, fn func(*sqlite.Stmt) error, args ...any) error { conn, err := db.pool.Take(context.Background()) if err != nil { return fmt.Errorf("failed to get connection from pool: %w", err) @@ -105,7 +111,7 @@ func (db *DB) Query(query string, fn func(*sqlite.Stmt) error, args ...any) erro } // Begin starts a new transaction -func (db *DB) Begin() (*Tx, error) { +func (db *database) Begin() (*Tx, error) { conn, err := db.pool.Take(context.Background()) if err != nil { return nil, fmt.Errorf("failed to get connection from pool: %w", err) @@ -120,7 +126,7 @@ func (db *DB) Begin() (*Tx, error) { } // Transaction runs a function within a transaction -func (db *DB) Transaction(fn func(*Tx) error) error { +func (db *database) Transaction(fn func(*Tx) error) error { tx, err := db.Begin() if err != nil { return err @@ -176,3 +182,75 @@ func (tx *Tx) Rollback() error { defer tx.pool.Put(tx.conn) return sqlitex.ExecuteTransient(tx.conn, "ROLLBACK", nil) } + +// InitializeDB initializes the global DB singleton +func InitializeDB(path string) error { + db, err := Open(path) + if err != nil { + return err + } + instance = db + return nil +} + +// GetDB returns the global database instance +func GetDB() *DB { + return instance +} + +// Global convenience functions that use the singleton + +// Exec executes a SQL statement without returning results using the global DB +func Exec(query string, args ...any) error { + if instance == nil { + return fmt.Errorf("database not initialized") + } + return instance.Exec(query, args...) +} + +// Query executes a SQL query and calls fn for each row using the global DB +func Query(query string, fn func(*sqlite.Stmt) error, args ...any) error { + if instance == nil { + return fmt.Errorf("database not initialized") + } + return instance.Query(query, fn, args...) +} + +// Begin starts a new transaction using the global DB +func Begin() (*Tx, error) { + if instance == nil { + return nil, fmt.Errorf("database not initialized") + } + return instance.Begin() +} + +// Transaction runs a function within a transaction using the global DB +func Transaction(fn func(*Tx) error) error { + if instance == nil { + return fmt.Errorf("database not initialized") + } + return instance.Transaction(fn) +} + +// GetConn gets a connection from the pool using the global DB +func GetConn(ctx context.Context) (*sqlite.Conn, error) { + if instance == nil { + return nil, fmt.Errorf("database not initialized") + } + return instance.GetConn(ctx) +} + +// PutConn returns a connection to the pool using the global DB +func PutConn(conn *sqlite.Conn) { + if instance != nil { + instance.PutConn(conn) + } +} + +// Close closes the global database connection pool +func Close() error { + if instance == nil { + return nil + } + return instance.Close() +} diff --git a/internal/routes/auth.go b/internal/routes/auth.go index 5b10116..9a345cf 100644 --- a/internal/routes/auth.go +++ b/internal/routes/auth.go @@ -6,6 +6,7 @@ import ( "dk/internal/auth" "dk/internal/csrf" + "dk/internal/database" "dk/internal/middleware" "dk/internal/password" "dk/internal/router" @@ -155,13 +156,13 @@ func processRegister() router.Handler { } // Check if username already exists - if _, err := users.GetByUsername(auth.Manager.DB(), username); err == nil { + if _, err := users.GetByUsername(database.GetDB(), username); err == nil { showRegisterError(ctx, "Username already exists", username, email) return } // Check if email already exists - if _, err := users.GetByEmail(auth.Manager.DB(), email); err == nil { + if _, err := users.GetByEmail(database.GetDB(), email); err == nil { showRegisterError(ctx, "Email already registered", username, email) return } @@ -315,17 +316,15 @@ func validateRegistration(username, email, password, confirmPassword string) err // createUser inserts a new user into the database // This is a simplified version - in a real app you'd have a proper users.Create function func createUser(user *users.User) error { - db := auth.Manager.DB() - query := `INSERT INTO users (username, password, email, verified, auth) VALUES (?, ?, ?, ?, ?)` - err := db.Exec(query, user.Username, user.Password, user.Email, user.Verified, user.Auth) + err := database.Exec(query, user.Username, user.Password, user.Email, user.Verified, user.Auth) if err != nil { return fmt.Errorf("failed to insert user: %w", err) } // Get the user ID (simplified - in real app you'd return it from insert) - createdUser, err := users.GetByUsername(db, user.Username) + createdUser, err := users.GetByUsername(database.GetDB(), user.Username) if err != nil { return fmt.Errorf("failed to get created user: %w", err) } diff --git a/internal/server/server.go b/internal/server/server.go index beaba3a..53c1c52 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -27,14 +27,14 @@ func Start(port string) error { // Initialize template singleton template.InitializeCache(cwd) - db, err := database.Open("dk.db") - if err != nil { - return fmt.Errorf("failed to open database: %w", err) + // Initialize database singleton + if err := database.InitializeDB("dk.db"); err != nil { + return fmt.Errorf("failed to initialize database: %w", err) } - defer db.Close() + defer database.Close() // Initialize auth singleton - auth.InitializeManager(db, "sessions.json") + auth.InitializeManager(database.GetDB(), "sessions.json") // Initialize router r := router.New() diff --git a/internal/template/components/components.go b/internal/template/components/components.go index 08be0e6..8291d90 100644 --- a/internal/template/components/components.go +++ b/internal/template/components/components.go @@ -6,9 +6,11 @@ import ( "dk/internal/auth" "dk/internal/csrf" + "dk/internal/database" "dk/internal/middleware" "dk/internal/router" "dk/internal/template" + "dk/internal/users" ) // GenerateTopNav generates the top navigation HTML based on authentication status @@ -27,6 +29,80 @@ func GenerateTopNav(ctx router.Ctx) string { } } +// GenerateLeftSide generates the left sidebar content for authenticated users +func GenerateLeftSide(ctx router.Ctx) string { + if !middleware.IsAuthenticated(ctx) { + return "" + } + + // Load and render the leftside template with user data + leftSideTmpl, err := template.Cache.Load("leftside.html") + if err != nil { + return "" // Silently fail - sidebar is optional + } + + // Get the current user from session + currentUser := middleware.GetCurrentUser(ctx) + if currentUser == nil { + return "" + } + + // Get the full user object from database + db := database.GetDB() + if db == nil { + return "" + } + + user, err := users.Find(db, currentUser.ID) + if err != nil { + return "" + } + + // Pass the user object directly to the template + leftSideData := map[string]any{ + "user": user, + } + + return leftSideTmpl.RenderNamed(leftSideData) +} + +// GenerateRightSide generates the right sidebar content for authenticated users +func GenerateRightSide(ctx router.Ctx) string { + if !middleware.IsAuthenticated(ctx) { + return "" + } + + // Load and render the rightside template with user data + rightSideTmpl, err := template.Cache.Load("rightside.html") + if err != nil { + return "" // Silently fail - sidebar is optional + } + + // Get the current user from session + currentUser := middleware.GetCurrentUser(ctx) + if currentUser == nil { + return "" + } + + // Get the full user object from database + db := database.GetDB() + if db == nil { + return "" + } + + user, err := users.Find(db, currentUser.ID) + if err != nil { + return "" + } + + // Pass the user object directly to the template + rightSideData := map[string]any{ + "user": user, + } + + return rightSideTmpl.RenderNamed(rightSideData) +} + // PageData holds common page template data type PageData struct { Title string @@ -69,10 +145,10 @@ func RenderPage(ctx router.Ctx, pageData PageData, additionalData map[string]any // Set defaults for empty fields if data["leftside"] == "" { - data["leftside"] = "" + data["leftside"] = GenerateLeftSide(ctx) } if data["rightside"] == "" { - data["rightside"] = "" + data["rightside"] = GenerateRightSide(ctx) } if data["numqueries"] == "" { data["numqueries"] = "0" diff --git a/templates/leftside.html b/templates/leftside.html new file mode 100644 index 0000000..55fae29 --- /dev/null +++ b/templates/leftside.html @@ -0,0 +1,46 @@ + + + + + + + +
Location
+ Currently: {user.currentaction}
+ Longitude: {user.longitude}
+ Latitude: {user.latitude}
+ View Map +

+
+
+
+
+ +
+
+
+ +
+ + + + + + +
Towns
+ {townname} + {townlist} +
+ +
+ + + + +
Functions
+ Home
+ Forum + Change Password
+ Log Out
+ Help +
diff --git a/templates/rightside.html b/templates/rightside.html new file mode 100644 index 0000000..b75721f --- /dev/null +++ b/templates/rightside.html @@ -0,0 +1,58 @@ + + + + + + + +
Character
+ {user.username}
+ Level: {user.level}
+ Exp: {user.experience}
+ Gold: {user.gold}
+ HP: {user.currenthp}
+ MP: {user.currentmp}
+ TP: {user.currenttp}
+ {statbars}
+ Extended Stats +
+ +
+ + + + + + + + +
Inventory
+ + + + + + + + + + + + + +
Weapon{weaponname}
Armor{armorname}
Shield{shieldname}
+ {slot1name}
+ {slot2name}
+ {slot3name} +
+ +
+ + + + + + + + +
Fast Spells
{magiclist}