added cargo files

This commit is contained in:
2026-03-03 10:57:43 -05:00
parent 478a90e01b
commit 169df46bc2
813 changed files with 227273 additions and 9 deletions

View File

@@ -0,0 +1,290 @@
package db
import (
"context"
"database/sql"
"fmt"
"log"
"net/url"
"os"
"pinepods/gpodder-api/config"
"regexp"
"strings"
"time"
_ "github.com/go-sql-driver/mysql" // MySQL driver
_ "github.com/lib/pq" // PostgreSQL driver
)
// Database represents a database connection that can be either PostgreSQL or MySQL
type Database struct {
*sql.DB
Type string // "postgresql" or "mysql"
}
// NewDatabase creates a new database connection based on the DB_TYPE environment variable
func NewDatabase(cfg config.DatabaseConfig) (*Database, error) {
// Print connection details for debugging (hide password for security)
fmt.Printf("Connecting to %s database: host=%s port=%d user=%s dbname=%s\n",
cfg.Type, cfg.Host, cfg.Port, cfg.User, cfg.DBName)
var db *sql.DB
var err error
switch cfg.Type {
case "postgresql":
db, err = connectPostgreSQL(cfg)
case "mysql", "mariadb":
db, err = connectMySQL(cfg)
default:
return nil, fmt.Errorf("unsupported database type: %s", cfg.Type)
}
if err != nil {
return nil, err
}
// Test the connection
if err := db.Ping(); err != nil {
db.Close()
if strings.Contains(err.Error(), "password authentication failed") {
// Print environment variables (hide password)
fmt.Println("Password authentication failed. Environment variables:")
fmt.Printf("DB_HOST=%s\n", os.Getenv("DB_HOST"))
fmt.Printf("DB_PORT=%s\n", os.Getenv("DB_PORT"))
fmt.Printf("DB_USER=%s\n", os.Getenv("DB_USER"))
fmt.Printf("DB_NAME=%s\n", os.Getenv("DB_NAME"))
fmt.Printf("DB_PASSWORD=*** (length: %d)\n", len(os.Getenv("DB_PASSWORD")))
}
return nil, fmt.Errorf("failed to ping database: %w", err)
}
fmt.Println("Successfully connected to the database")
// Migrations are now handled by the Python migration system
// Skip Go migrations to avoid conflicts
log.Println("Skipping Go migrations - now handled by Python migration system")
return &Database{DB: db, Type: cfg.Type}, nil
}
// runMigrationsWithRetry - DISABLED: migrations now handled by Python system
// func runMigrationsWithRetry(db *sql.DB, dbType string) error {
// All migration logic has been moved to the Python migration system
// to ensure consistency and centralized management
// This function is kept for reference but is no longer used
// }
// connectPostgreSQL connects to a PostgreSQL database
func connectPostgreSQL(cfg config.DatabaseConfig) (*sql.DB, error) {
// Escape special characters in password
escapedPassword := url.QueryEscape(cfg.Password)
// Use a connection string without password for logging
logConnStr := fmt.Sprintf(
"host=%s port=%d user=%s dbname=%s sslmode=%s",
cfg.Host, cfg.Port, cfg.User, cfg.DBName, cfg.SSLMode,
)
fmt.Printf("PostgreSQL connection string (without password): %s\n", logConnStr)
// Build the actual connection string with password
connStr := fmt.Sprintf(
"host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
cfg.Host, cfg.Port, cfg.User, cfg.Password, cfg.DBName, cfg.SSLMode,
)
// Try standard connection string first
db, err := sql.Open("postgres", connStr)
if err != nil {
// Try URL format connection string
urlConnStr := fmt.Sprintf(
"postgres://%s:%s@%s:%d/%s?sslmode=%s",
cfg.User, escapedPassword, cfg.Host, cfg.Port, cfg.DBName, cfg.SSLMode,
)
fmt.Println("First connection attempt failed, trying URL format...")
db, err = sql.Open("postgres", urlConnStr)
}
return db, err
}
// Replace the existing connectMySQL function with this version
func connectMySQL(cfg config.DatabaseConfig) (*sql.DB, error) {
// Add needed parameters for MySQL authentication
connStr := fmt.Sprintf(
"%s:%s@tcp(%s:%d)/%s?parseTime=true&allowNativePasswords=true&multiStatements=true",
cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.DBName,
)
fmt.Printf("Attempting MySQL connection to %s:%d as user '%s'\n",
cfg.Host, cfg.Port, cfg.User)
// Open the connection
db, err := sql.Open("mysql", connStr)
if err != nil {
return nil, fmt.Errorf("failed to open MySQL connection: %w", err)
}
// Configure connection pool
db.SetConnMaxLifetime(time.Minute * 3)
db.SetMaxOpenConns(10)
db.SetMaxIdleConns(5)
// Explicitly test the connection
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
fmt.Println("Testing MySQL connection with ping...")
if err := db.PingContext(ctx); err != nil {
db.Close()
fmt.Printf("MySQL connection failed: %v\n", err)
return nil, fmt.Errorf("failed to ping MySQL database: %w", err)
}
fmt.Println("MySQL connection successful!")
return db, nil
}
// Close closes the database connection
func (db *Database) Close() error {
return db.DB.Close()
}
// IsMySQLDB returns true if the database is MySQL/MariaDB
func (db *Database) IsMySQLDB() bool {
return db.Type == "mysql"
}
// IsPostgreSQLDB returns true if the database is PostgreSQL
func (db *Database) IsPostgreSQLDB() bool {
return db.Type == "postgresql"
}
// FormatQuery formats a query for the specific database type
func (db *Database) FormatQuery(query string) string {
if db.Type == "postgresql" {
return query // PostgreSQL queries already have correct format
}
// For MySQL:
result := query
// First, replace quoted table names
knownTables := []string{
"Users", "GpodderDevices", "GpodderSyncSettings",
"GpodderSyncSubscriptions", "GpodderSyncEpisodeActions",
"GpodderSyncPodcastLists", "GpodderSyncState", "GpodderSessions",
"GpodderSyncMigrations", "Podcasts", "Episodes", "SavedEpisodes",
"UserEpisodeHistory", "UserSettings", "APIKeys",
}
for _, table := range knownTables {
quoted := fmt.Sprintf("\"%s\"", table)
result = strings.ReplaceAll(result, quoted, table)
}
// Replace column quotes (double quotes to backticks)
re := regexp.MustCompile(`"([^"]+)"`)
result = re.ReplaceAllString(result, "`$1`")
// Then replace placeholders
for i := 10; i > 0; i-- {
old := fmt.Sprintf("$%d", i)
result = strings.ReplaceAll(result, old, "?")
}
return result
}
// Exec executes a query with the correct formatting for the database type
func (db *Database) Exec(query string, args ...interface{}) (sql.Result, error) {
formattedQuery := db.FormatQuery(query)
return db.DB.Exec(formattedQuery, args...)
}
// Query executes a query with the correct formatting for the database type
func (db *Database) Query(query string, args ...interface{}) (*sql.Rows, error) {
formattedQuery := db.FormatQuery(query)
return db.DB.Query(formattedQuery, args...)
}
// QueryRow executes a query with the correct formatting for the database type
func (db *Database) QueryRow(query string, args ...interface{}) *sql.Row {
formattedQuery := db.FormatQuery(query)
return db.DB.QueryRow(formattedQuery, args...)
}
// Begin starts a transaction with the correct formatting for the database type
func (db *Database) Begin() (*Transaction, error) {
tx, err := db.DB.Begin()
if err != nil {
return nil, err
}
return &Transaction{tx: tx, dbType: db.Type}, nil
}
// Transaction is a wrapper around sql.Tx that formats queries correctly
type Transaction struct {
tx *sql.Tx
dbType string
}
// Commit commits the transaction
func (tx *Transaction) Commit() error {
return tx.tx.Commit()
}
// Rollback rolls back the transaction
func (tx *Transaction) Rollback() error {
return tx.tx.Rollback()
}
// Exec executes a query in the transaction with correct formatting
func (tx *Transaction) Exec(query string, args ...interface{}) (sql.Result, error) {
formattedQuery := formatQuery(query, tx.dbType)
return tx.tx.Exec(formattedQuery, args...)
}
// Query executes a query in the transaction with correct formatting
func (tx *Transaction) Query(query string, args ...interface{}) (*sql.Rows, error) {
formattedQuery := formatQuery(query, tx.dbType)
return tx.tx.Query(formattedQuery, args...)
}
// QueryRow executes a query in the transaction with correct formatting
func (tx *Transaction) QueryRow(query string, args ...interface{}) *sql.Row {
formattedQuery := formatQuery(query, tx.dbType)
return tx.tx.QueryRow(formattedQuery, args...)
}
// Helper function to format queries
func formatQuery(query string, dbType string) string {
if dbType == "postgresql" {
return query
}
// For MySQL:
// Same logic as FormatQuery method
result := query
knownTables := []string{
"Users", "GpodderDevices", "GpodderSyncSettings",
"GpodderSyncSubscriptions", "GpodderSyncEpisodeActions",
"GpodderSyncPodcastLists", "GpodderSyncState", "GpodderSessions",
"GpodderSyncMigrations", "Podcasts", "Episodes", "SavedEpisodes",
"UserEpisodeHistory", "UserSettings", "APIKeys",
}
for _, table := range knownTables {
quoted := fmt.Sprintf("\"%s\"", table)
result = strings.ReplaceAll(result, quoted, table)
}
for i := 10; i > 0; i-- {
old := fmt.Sprintf("$%d", i)
result = strings.ReplaceAll(result, old, "?")
}
return result
}