208 lines
5.9 KiB
Go
208 lines
5.9 KiB
Go
package database
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"time"
|
|
"ccoin/utils"
|
|
"github.com/charmbracelet/log"
|
|
_ "github.com/jackc/pgx/v5/stdlib"
|
|
"os"
|
|
)
|
|
|
|
type DatabaseConfig struct {
|
|
DB *sql.DB
|
|
logger *log.Logger
|
|
}
|
|
|
|
// Holds database configuration
|
|
type DatabaseSettings struct {
|
|
URL string
|
|
User string
|
|
Password string
|
|
MaxPoolSize int
|
|
MinIdle int
|
|
ConnTimeout time.Duration
|
|
IdleTimeout time.Duration
|
|
MaxLifetime time.Duration
|
|
ValidationQuery string
|
|
}
|
|
|
|
// Creates and initializes a new database configuration
|
|
func NewDatabaseConfig() *DatabaseConfig {
|
|
return &DatabaseConfig{
|
|
logger: log.New(os.Stdout),
|
|
}
|
|
}
|
|
|
|
// Initializes the database connection
|
|
func (dc *DatabaseConfig) Init() error {
|
|
dc.logger.Info("Initializing database connection...")
|
|
|
|
settings := dc.loadSettings()
|
|
|
|
// Create connection string
|
|
connStr := fmt.Sprintf("%s?user=%s&password=%s", settings.URL, settings.User, settings.Password)
|
|
|
|
// Open database connection
|
|
db, err := sql.Open("pgx", connStr)
|
|
if err != nil {
|
|
dc.logger.Error("Failed to open database connection", "error", err)
|
|
return fmt.Errorf("failed to open database: %w", err)
|
|
}
|
|
|
|
// Configure database connection pool
|
|
db.SetMaxOpenConns(settings.MaxPoolSize)
|
|
db.SetMaxIdleConns(settings.MinIdle)
|
|
db.SetConnMaxLifetime(settings.MaxLifetime)
|
|
db.SetConnMaxIdleTime(settings.IdleTimeout)
|
|
|
|
// Test the connection
|
|
ctx, cancel := context.WithTimeout(context.Background(), settings.ConnTimeout)
|
|
defer cancel()
|
|
|
|
if err := db.PingContext(ctx); err != nil {
|
|
dc.logger.Error("Failed to ping database", "error", err)
|
|
return fmt.Errorf("failed to ping database: %w", err)
|
|
}
|
|
|
|
dc.DB = db
|
|
dc.logger.Info("Database connection established successfully")
|
|
|
|
// Create tables if they don't exist
|
|
if err := dc.createTables(); err != nil {
|
|
return fmt.Errorf("failed to create tables: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Loads database settings from environment variables
|
|
func (dc *DatabaseConfig) loadSettings() DatabaseSettings {
|
|
return DatabaseSettings{
|
|
URL: utils.GetEnvOrDefault("DATABASE_URL", "postgres://localhost:5432/ccoin"),
|
|
User: utils.GetEnvOrDefault("DATABASE_USER", "ccoin"),
|
|
Password: utils.GetEnvOrDefault("DATABASE_PASSWORD", "ccoin"),
|
|
MaxPoolSize: utils.GetEnvOrDefault("DATABASE_POOL_SIZE", 20),
|
|
ConnTimeout: 30 * time.Second,
|
|
IdleTimeout: 10 * time.Minute,
|
|
MaxLifetime: 30 * time.Minute,
|
|
ValidationQuery: "SELECT 1",
|
|
}
|
|
}
|
|
|
|
// Creates database tables if they don't exist
|
|
func (dc *DatabaseConfig) createTables() error {
|
|
dc.logger.Info("Creating database tables if they don't exist...")
|
|
|
|
// Define tables
|
|
tables := []string{
|
|
// Wallets table
|
|
`CREATE TABLE IF NOT EXISTS wallets (
|
|
address VARCHAR(64) PRIMARY KEY,
|
|
balance DECIMAL(20,8) DEFAULT 0,
|
|
label VARCHAR(255),
|
|
password_hash VARCHAR(64),
|
|
created_at BIGINT NOT NULL,
|
|
last_activity BIGINT
|
|
)`,
|
|
|
|
// Transactions table
|
|
`CREATE TABLE IF NOT EXISTS transactions (
|
|
hash VARCHAR(64) PRIMARY KEY,
|
|
from_address VARCHAR(64),
|
|
to_address VARCHAR(64) NOT NULL,
|
|
amount DECIMAL(20,8) NOT NULL,
|
|
fee DECIMAL(20,8) DEFAULT 0,
|
|
memo TEXT,
|
|
block_hash VARCHAR(64),
|
|
timestamp BIGINT NOT NULL,
|
|
status VARCHAR(20) DEFAULT 'pending',
|
|
confirmations INTEGER DEFAULT 0
|
|
)`,
|
|
|
|
// Blocks table
|
|
`CREATE TABLE IF NOT EXISTS blocks (
|
|
hash VARCHAR(64) PRIMARY KEY,
|
|
previous_hash VARCHAR(64),
|
|
merkle_root VARCHAR(64) NOT NULL,
|
|
timestamp BIGINT NOT NULL,
|
|
difficulty INTEGER NOT NULL,
|
|
nonce BIGINT NOT NULL,
|
|
miner_address VARCHAR(64) NOT NULL,
|
|
reward DECIMAL(20,8) NOT NULL,
|
|
height SERIAL,
|
|
transaction_count INTEGER DEFAULT 0,
|
|
confirmations INTEGER DEFAULT 0
|
|
)`,
|
|
}
|
|
|
|
// Create tables
|
|
for _, tableSQL := range tables {
|
|
if _, err := dc.DB.Exec(tableSQL); err != nil {
|
|
dc.logger.Error("Failed to create table", "error", err, "sql", tableSQL)
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Create indexes
|
|
indexes := []string{
|
|
// Wallets indexes
|
|
"CREATE INDEX IF NOT EXISTS idx_wallets_created_at ON wallets(created_at)",
|
|
"CREATE INDEX IF NOT EXISTS idx_wallets_last_activity ON wallets(last_activity)",
|
|
|
|
// Transactions indexes
|
|
"CREATE INDEX IF NOT EXISTS idx_transactions_from_address ON transactions(from_address)",
|
|
"CREATE INDEX IF NOT EXISTS idx_transactions_to_address ON transactions(to_address)",
|
|
"CREATE INDEX IF NOT EXISTS idx_transactions_block_hash ON transactions(block_hash)",
|
|
"CREATE INDEX IF NOT EXISTS idx_transactions_timestamp ON transactions(timestamp)",
|
|
"CREATE INDEX IF NOT EXISTS idx_transactions_status ON transactions(status)",
|
|
|
|
// Blocks indexes
|
|
"CREATE INDEX IF NOT EXISTS idx_blocks_height ON blocks(height)",
|
|
"CREATE INDEX IF NOT EXISTS idx_blocks_miner_address ON blocks(miner_address)",
|
|
"CREATE INDEX IF NOT EXISTS idx_blocks_timestamp ON blocks(timestamp)",
|
|
"CREATE INDEX IF NOT EXISTS idx_blocks_previous_hash ON blocks(previous_hash)",
|
|
|
|
// Foreign key-like indexes for referential integrity
|
|
"CREATE INDEX IF NOT EXISTS idx_transactions_from_wallet ON transactions(from_address)",
|
|
"CREATE INDEX IF NOT EXISTS idx_transactions_to_wallet ON transactions(to_address)",
|
|
"CREATE INDEX IF NOT EXISTS idx_blocks_miner_wallet ON blocks(miner_address)",
|
|
}
|
|
|
|
// Create indexes
|
|
for _, indexSQL := range indexes {
|
|
if _, err := dc.DB.Exec(indexSQL); err != nil {
|
|
dc.logger.Error("Failed to create index", "error", err, "sql", indexSQL)
|
|
return err
|
|
}
|
|
}
|
|
|
|
dc.logger.Info("Database tables and indexes created/verified successfully")
|
|
return nil
|
|
}
|
|
|
|
// Returns database connection information
|
|
func (dc *DatabaseConfig) GetConnectionInfo() map[string]interface{} {
|
|
settings := dc.loadSettings()
|
|
return map[string]interface{}{
|
|
"url": settings.URL,
|
|
"user": settings.User,
|
|
"poolSize": settings.MaxPoolSize,
|
|
}
|
|
}
|
|
|
|
// Closes the database connection
|
|
func (dc *DatabaseConfig) Close() error {
|
|
if dc.DB != nil {
|
|
return dc.DB.Close()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Returns the database connection
|
|
func (dc *DatabaseConfig) GetDB() *sql.DB {
|
|
return dc.DB
|
|
}
|