add leftside/rightside template and database singleton

This commit is contained in:
Sky Johnson 2025-08-09 17:30:04 -05:00
parent 0534da09a1
commit 69e0ab5645
6 changed files with 281 additions and 24 deletions

View File

@ -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()
}

View File

@ -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)
}

View File

@ -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()

View File

@ -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"

46
templates/leftside.html Normal file
View File

@ -0,0 +1,46 @@
<table width="100%">
<tr>
<td class="title"><img src="/assets/images/button_location.gif" alt="Location" title="Location"></td>
</tr>
<tr>
<td>
Currently: {user.currentaction}<br>
Longitude: {user.longitude}<br>
Latitude: {user.latitude}<br>
<a href="javascript:openmappopup()">View Map</a>
<br><br>
<form action="/move" method="post">
<center>
<input name="north" type="submit" value="North"><br>
<input name="west" type="submit" value="West"><input name="east" type="submit" value="East"><br>
<input name="south" type="submit" value="South">
</center>
</form>
</td>
</tr>
</table>
<br>
<table width="100%">
<tr><td class="title"><img src="/assets/images/button_towns.gif" alt="Towns" title="Towns"></td></tr>
<tr>
<td>
{townname}
{townlist}
</td>
</tr>
</table>
<br>
<table width="100%">
<tr><td class="title"><img src="/assets/images/button_functions.gif" alt="Functions" title="Functions"></td></tr>
<tr><td>
<a href="/">Home</a><br>
<a href="/forum">Forum</a>
<a href="/change-password">Change Password</a><br>
<a href="#">Log Out</a><br>
<a href="/help">Help</a>
</td></tr>
</table>

58
templates/rightside.html Normal file
View File

@ -0,0 +1,58 @@
<table width="100%">
<tr>
<td class="title"><img src="/assets/images/button_character.gif" alt="Character" title="Character"></td>
</tr>
<tr>
<td>
<b>{user.username}</b><br>
Level: {user.level}<br>
Exp: {user.experience}<br>
Gold: {user.gold}<br>
HP: {user.currenthp}<br>
MP: {user.currentmp}<br>
TP: {user.currenttp}<br>
{statbars}<br>
<a href="javascript:opencharpopup()">Extended Stats</a>
</td>
</tr>
</table>
<br>
<table width="100%">
<tr>
<td class="title"><img src="/assets/images/button_inventory.gif" alt="Inventory" title="Inventory"></td>
</tr>
<tr>
<td>
<table width="100%">
<tr>
<td><img src="/assets/images/icon_weapon.gif" alt="Weapon" title="Weapon"></td>
<td width="100%">{weaponname}</td>
</tr>
<tr>
<td><img src="/assets/images/icon_armor.gif" alt="Armor" title="Armor"></td>
<td width="100%">{armorname}</td>
</tr>
<tr>
<td><img src="/assets/images/icon_shield.gif" alt="Shield" title="Shield"></td>
<td width="100%">{shieldname}</td>
</tr>
</table>
{slot1name}<br>
{slot2name}<br>
{slot3name}
</td>
</tr>
</table>
<br>
<table width="100%">
<tr>
<td class="title"><img src="/assets/images/button_fastspells.gif" alt="Fast Spells" title="Fast Spells"></td>
</tr>
<tr>
<td>{magiclist}</td>
</tr>
</table>