refactor(API): ✨ Relspect integration
This commit is contained in:
246
pkg/storage/db.go
Normal file
246
pkg/storage/db.go
Normal file
@@ -0,0 +1,246 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.warky.dev/wdevs/whatshooked/pkg/config"
|
||||
"git.warky.dev/wdevs/whatshooked/pkg/models"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/dialect/pgdialect"
|
||||
"github.com/uptrace/bun/dialect/sqlitedialect"
|
||||
"github.com/uptrace/bun/driver/pgdriver"
|
||||
"github.com/uptrace/bun/driver/sqliteshim"
|
||||
"github.com/uptrace/bun/extra/bundebug"
|
||||
)
|
||||
|
||||
// DB is the global database instance
|
||||
var DB *bun.DB
|
||||
var dbType string // Store the database type for later use
|
||||
|
||||
// Initialize sets up the database connection based on configuration
|
||||
func Initialize(cfg *config.DatabaseConfig) error {
|
||||
var sqldb *sql.DB
|
||||
var err error
|
||||
|
||||
dbType = cfg.Type
|
||||
switch cfg.Type {
|
||||
case "postgres", "postgresql":
|
||||
dsn := fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=disable",
|
||||
cfg.Username, cfg.Password, cfg.Host, cfg.Port, cfg.Database)
|
||||
sqldb = sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(dsn)))
|
||||
DB = bun.NewDB(sqldb, pgdialect.New())
|
||||
|
||||
case "sqlite":
|
||||
sqldb, err = sql.Open(sqliteshim.ShimName, cfg.SQLitePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open sqlite database: %w", err)
|
||||
}
|
||||
DB = bun.NewDB(sqldb, sqlitedialect.New())
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unsupported database type: %s", cfg.Type)
|
||||
}
|
||||
|
||||
// Add query hook for debugging (optional, can be removed in production)
|
||||
DB.AddQueryHook(bundebug.NewQueryHook(
|
||||
bundebug.WithVerbose(true),
|
||||
bundebug.FromEnv("BUNDEBUG"),
|
||||
))
|
||||
|
||||
// Set connection pool settings
|
||||
sqldb.SetMaxIdleConns(10)
|
||||
sqldb.SetMaxOpenConns(100)
|
||||
sqldb.SetConnMaxLifetime(time.Hour)
|
||||
|
||||
// Test the connection
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
if err := sqldb.PingContext(ctx); err != nil {
|
||||
return fmt.Errorf("failed to ping database: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateTables creates database tables based on BUN models
|
||||
func CreateTables(ctx context.Context) error {
|
||||
if DB == nil {
|
||||
return fmt.Errorf("database not initialized")
|
||||
}
|
||||
|
||||
// For SQLite, use raw SQL with compatible syntax
|
||||
if dbType == "sqlite" {
|
||||
return createTablesSQLite(ctx)
|
||||
}
|
||||
|
||||
// For PostgreSQL, use BUN's auto-generation
|
||||
models := []interface{}{
|
||||
(*models.ModelPublicUser)(nil),
|
||||
(*models.ModelPublicAPIKey)(nil),
|
||||
(*models.ModelPublicHook)(nil),
|
||||
(*models.ModelPublicWhatsappAccount)(nil),
|
||||
(*models.ModelPublicEventLog)(nil),
|
||||
(*models.ModelPublicSession)(nil),
|
||||
(*models.ModelPublicMessageCache)(nil),
|
||||
}
|
||||
|
||||
for _, model := range models {
|
||||
_, err := DB.NewCreateTable().Model(model).IfNotExists().Exec(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create table: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// createTablesSQLite creates tables using SQLite-compatible SQL
|
||||
func createTablesSQLite(ctx context.Context) error {
|
||||
tables := []string{
|
||||
// Users table
|
||||
`CREATE TABLE IF NOT EXISTS users (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
username VARCHAR(255) NOT NULL UNIQUE,
|
||||
email VARCHAR(255) NOT NULL UNIQUE,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
full_name VARCHAR(255),
|
||||
role VARCHAR(50) NOT NULL DEFAULT 'user',
|
||||
active BOOLEAN NOT NULL DEFAULT 1,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
)`,
|
||||
|
||||
// API Keys table
|
||||
`CREATE TABLE IF NOT EXISTS api_keys (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
user_id VARCHAR(36) NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
key VARCHAR(255) NOT NULL UNIQUE,
|
||||
key_prefix VARCHAR(20),
|
||||
permissions TEXT,
|
||||
active BOOLEAN NOT NULL DEFAULT 1,
|
||||
last_used_at TIMESTAMP,
|
||||
expires_at TIMESTAMP,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
)`,
|
||||
|
||||
// Hooks table
|
||||
`CREATE TABLE IF NOT EXISTS hooks (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
user_id VARCHAR(36) NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
url TEXT NOT NULL,
|
||||
method VARCHAR(10) NOT NULL DEFAULT 'POST',
|
||||
headers TEXT,
|
||||
events TEXT,
|
||||
active BOOLEAN NOT NULL DEFAULT 1,
|
||||
retry_count INTEGER NOT NULL DEFAULT 3,
|
||||
timeout_seconds INTEGER NOT NULL DEFAULT 30,
|
||||
description TEXT,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
)`,
|
||||
|
||||
// WhatsApp Accounts table
|
||||
`CREATE TABLE IF NOT EXISTS whatsapp_accounts (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
user_id VARCHAR(36) NOT NULL,
|
||||
phone_number VARCHAR(20) NOT NULL UNIQUE,
|
||||
account_type VARCHAR(50) NOT NULL DEFAULT 'whatsmeow',
|
||||
business_api_config TEXT,
|
||||
active BOOLEAN NOT NULL DEFAULT 1,
|
||||
connected BOOLEAN NOT NULL DEFAULT 0,
|
||||
last_connected_at TIMESTAMP,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
)`,
|
||||
|
||||
// Event Logs table
|
||||
`CREATE TABLE IF NOT EXISTS event_logs (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
user_id VARCHAR(36),
|
||||
account_id VARCHAR(36),
|
||||
event_type VARCHAR(100) NOT NULL,
|
||||
event_data TEXT,
|
||||
from_number VARCHAR(20),
|
||||
to_number VARCHAR(20),
|
||||
message_id VARCHAR(255),
|
||||
status VARCHAR(50),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL,
|
||||
FOREIGN KEY (account_id) REFERENCES whatsapp_accounts(id) ON DELETE SET NULL
|
||||
)`,
|
||||
|
||||
// Sessions table
|
||||
`CREATE TABLE IF NOT EXISTS sessions (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
user_id VARCHAR(36) NOT NULL,
|
||||
token VARCHAR(500) NOT NULL UNIQUE,
|
||||
expires_at TIMESTAMP NOT NULL,
|
||||
ip_address VARCHAR(45),
|
||||
user_agent TEXT,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
)`,
|
||||
|
||||
// Message Cache table
|
||||
`CREATE TABLE IF NOT EXISTS message_cache (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
account_id VARCHAR(36),
|
||||
event_type VARCHAR(100) NOT NULL,
|
||||
event_data TEXT NOT NULL,
|
||||
message_id VARCHAR(255),
|
||||
from_number VARCHAR(20),
|
||||
to_number VARCHAR(20),
|
||||
processed BOOLEAN NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
processed_at TIMESTAMP,
|
||||
FOREIGN KEY (account_id) REFERENCES whatsapp_accounts(id) ON DELETE SET NULL
|
||||
)`,
|
||||
}
|
||||
|
||||
for _, sql := range tables {
|
||||
if _, err := DB.ExecContext(ctx, sql); err != nil {
|
||||
return fmt.Errorf("failed to create table: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the database connection
|
||||
func Close() error {
|
||||
if DB == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return DB.Close()
|
||||
}
|
||||
|
||||
// GetDB returns the database instance
|
||||
func GetDB() *bun.DB {
|
||||
return DB
|
||||
}
|
||||
|
||||
// HealthCheck checks if the database connection is healthy
|
||||
func HealthCheck() error {
|
||||
if DB == nil {
|
||||
return fmt.Errorf("database not initialized")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
return DB.PingContext(ctx)
|
||||
}
|
||||
Reference in New Issue
Block a user