feat: added server and db config
This commit is contained in:
207
server/config/database/config.go
Normal file
207
server/config/database/config.go
Normal file
@@ -0,0 +1,207 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user