Concierge-Users

 view release on metacpan or  search on metacpan

lib/Concierge/Users/Database.pm  view on Meta::CPAN

package Concierge::Users::Database v0.8.0;
use v5.36;
use Carp qw/ croak /;
use DBI;
use parent qw/ Concierge::Users::Meta /;

# ABSTRACT: Database backend for Concierge::Users

# ==============================================================================
# Configure Class Method - One-time setup (called by Users->setup)
# ==============================================================================

sub configure {
    my ($class, $setup_config) = @_;

    # Extract storage_dir
    my $storage_dir = $setup_config->{storage_dir};

    # Build SQLite DSN and file path
    my $db_file = "$storage_dir/users.db";
    my $dsn = "dbi:SQLite:$db_file";

    # Connect to database
    my $dbh = DBI->connect($dsn, '', '', {
        RaiseError => 0,
        AutoCommit => 1,
        PrintError => 0,
        sqlite_unicode => 1,
    });

    unless ($dbh) {
        return {
            success => 0,
            message => sprintf(
                "Database backend connection failed:\n" .
                "  - Database file: %s\n" .
                "  - Error: %s",
                $db_file,
                $DBI::errstr || 'Unknown error'
            ),
        };
    }

    # Create temporary object for ensure_storage
    my $temp_backend = bless {
        dbh         => $dbh,
        table_name  => 'users',
        storage_dir => $storage_dir,
        db_file     => $db_file,
        field_definitions => $setup_config->{field_definitions},
        fields      => $setup_config->{fields} || [],
    }, $class;

    # Check for existing data and archive if present
    my $check_sql = "SELECT name FROM sqlite_master WHERE type='table' AND name=?";
    my $sth = $dbh->prepare($check_sql);
    if ($sth) {
        $sth->execute('users');
        my ($table_exists) = $sth->fetchrow_array();
        $sth->finish();

        if ($table_exists) {
            # Check if table has data
            my $count_sql = "SELECT COUNT(*) FROM users";
            my $count_sth = $dbh->prepare($count_sql);
            if ($count_sth) {
                $count_sth->execute();
                my ($user_count) = $count_sth->fetchrow_array();
                $count_sth->finish();

                # Archive if table has data
                if ($user_count > 0) {
                    my $archive_result = $temp_backend->_archive_user_data();
                    unless ($archive_result->{success}) {
                        $temp_backend->disconnect();
                        return {
                            success => 0,
                            message => $archive_result->{message},
                        };
                    }
                } else {
                    # Drop empty table
                    $dbh->do("DROP TABLE users");
                }
            }
        }
    }

    # Ensure storage (table) exists
    my $storage_ok = $temp_backend->ensure_storage();
    unless ($storage_ok) {
        return {
            success => 0,
            message => "Failed to initialize storage for database backend",
        };
    }

    # Disconnect temp object
    $temp_backend->disconnect();

    # Return success with config
    return {
        success => 1,
        message => "Database backend configured successfully",
        config => {
            storage_dir       => $storage_dir,
            db_file           => 'users.db',
            db_full_path      => $db_file,
            table_name        => 'users',
            fields            => $setup_config->{fields} || [],
            field_definitions => $setup_config->{field_definitions},
        },
    };
}

# ==============================================================================
# Constructor - Runtime instantiation (called by Users->new)
# ==============================================================================
sub new {
    my ($class, $runtime_config) = @_;

    # Extract parameters from saved config (no validation needed)
    my $storage_dir = $runtime_config->{storage_dir};
    my $db_file     = $runtime_config->{db_full_path};
    my $table_name  = $runtime_config->{table_name} || 'users';

    # Build SQLite DSN
    my $dsn = "dbi:SQLite:$db_file";

    # Connect to database
    my $dbh = DBI->connect($dsn, '', '', {
        RaiseError => 0,
        AutoCommit => 1,
        PrintError => 0,
        sqlite_unicode => 1,
    });

    unless ($dbh) {
        croak sprintf(
            "Database backend connection failed:\n" .
            "  - Database file: %s\n" .
            "  - Error: %s",
            $db_file,
            $DBI::errstr || 'Unknown error'
        );
    }

    return bless {
        dbh              => $dbh,
        table_name       => $table_name,
        storage_dir      => $storage_dir,
        db_file          => $db_file,
        fields           => $runtime_config->{fields} || [],
        field_definitions => $runtime_config->{field_definitions} || {},
    }, $class;
}

# Report backend configuration (for debugging/info)
sub config {
    my ($self) = @_;

    return {
        storage_dir       => $self->{storage_dir},
        db_file           => $self->{db_file},
        db_full_path      => $self->{db_file},
        table_name        => $self->{table_name},
        fields	       	  => $self->{fields},
        field_definitions => $self->{field_definitions},
    };
}

# Ensure storage (table) exists
sub ensure_storage {
    my ($self) = @_;

    # Check if table exists
    my $check_sql = "SELECT name FROM sqlite_master WHERE type='table' AND name=?";
    my $sth = $self->{dbh}->prepare($check_sql);
    return 0 unless $sth;
    $sth->execute($self->{table_name});
    my ($exists) = $sth->fetchrow_array();

    return 1 if $exists;  # Table already exists

    # Build CREATE TABLE SQL
    my @field_defs;
    my @indexes;

    foreach my $field (@{$self->{fields}}) {
        # All fields are TEXT in our schema
        my $field_def = "$field TEXT";

        # Check if field is required
        my $field_def_info = $self->{field_definitions}{$field};
        if ($field_def_info && $field_def_info->{required}) {



( run in 1.805 second using v1.01-cache-2.11-cpan-39bf76dae61 )