App-WHMCSUtils

 view release on metacpan or  search on metacpan

README  view on Meta::CPAN

    information, much like how HTTP response headers provide additional
    metadata.

    Return value: (any)

  restore_whmcs_client
    Usage:

     restore_whmcs_client(%args) -> [$status_code, $reason, $payload, \%result_meta]

    Restore a missing client from SQL database backup.

    This function is not exported.

    This function supports dry-run operation.

    Arguments ('*' denotes required arguments):

    *   client_email => *str*

    *   client_id => *posint*

    *   restore_domains => *bool* (default: 1)

    *   restore_hostings => *bool* (default: 1)

    *   restore_invoices => *bool* (default: 1)

    *   sql_backup_dir => *dirname*

        Directory containing per-table SQL files.

    *   sql_backup_file => *filename*

        Can accept either ".sql" or ".sql.gz".

        Will be converted first to a directory where the SQL file will be
        extracted to separate files on a per-table basis.

    Special arguments:

    *   -dry_run => *bool*

lib/App/WHMCSUtils.pm  view on Meta::CPAN

    );

    DBIx::Connect::MySQL->connect(
        $dsn, $args{db_user}, $args{db_pass},
        {RaiseError => 1},
    );
}

$SPEC{restore_whmcs_client} = {
    v => 1.1,
    summary => "Restore a missing client from SQL database backup",
    args => {
        sql_backup_file => {
            schema => 'filename*',
            description => <<'_',

Can accept either `.sql` or `.sql.gz`.

Will be converted first to a directory where the SQL file will be extracted to
separate files on a per-table basis.

_
        },
        sql_backup_dir => {
            summary => 'Directory containing per-table SQL files',
            schema => 'dirname*',
            description => <<'_',


_
        },
        client_email => {
            schema => 'str*',
        },

lib/App/WHMCSUtils.pm  view on Meta::CPAN

            schema => 'bool*',
            default => 1,
        },
        restore_domains => {
            schema => 'bool*',
            default => 1,
        },
    },
    args_rels => {
        'req_one&' => [
            ['sql_backup_file', 'sql_backup_dir'],
            ['client_email', 'client_id'],
        ],
    },
    deps => {
        prog => "mysql-sql-dump-extract-tables",
    },
    features => {
        dry_run => 1,
    },
};
sub restore_whmcs_client {
    my %args = @_;

    local $CWD;

    my $sql_backup_dir;
    my $decompress = 0;
    if ($args{sql_backup_file}) {
        return [404, "No such file: $args{sql_backup_file}"]
            unless -f $args{sql_backup_file};
        my $pt = path($args{sql_backup_file});
        my $basename = $pt->basename;
        if ($basename =~ /(.+)\.sql\z/i) {
            $sql_backup_dir = $1;
        } elsif ($basename =~ /(.+)\.sql\.gz\z/i) {
            $sql_backup_dir = $1;
            $decompress = 1;
        } else {
            return [412, "SQL backup file should be named *.sql or *.sql.gz: ".
                        "$args{sql_backup_file}"];
        }
        if (-d $sql_backup_dir) {
            log_info "SQL backup dir '$sql_backup_dir' already exists, ".
                "skipped extracting";
        } else {
            mkdir $sql_backup_dir, 0755
                or return [500, "Can't mkdir '$sql_backup_dir': $!"];
            $CWD = $sql_backup_dir;
            my @cmd;
            if ($decompress) {
                push @cmd, "zcat", $pt->absolute->stringify, \"|";
            } else {
                push @cmd, "cat", $pt->absolute->stringify, \"|";
            }
            push @cmd, "mysql-sql-dump-extract-tables",
                "--include-table-pattern", '^(tblclients|tblinvoices|tblinvoiceitems|tblorders)$';
            system({shell=>1, die=>1, log=>1}, @cmd);
        }
    } elsif ($args{sql_backup_dir}) {
        $sql_backup_dir = $args{sql_backup_dir};
        return [404, "No such dir: $sql_backup_dir"]
            unless -d $sql_backup_dir;
        $CWD = $sql_backup_dir;
    }

    my @sql;

    my $clientid = $args{client_id};
  FIND_CLIENT:
    {
        open my $fh, "<", "tblclients"
            or return [500, "Can't open $sql_backup_dir/tblclients: $!"];
        my $clientemail;
        $clientemail = lc $args{client_email} if defined $args{client_email};
        while (<$fh>) {
            next unless /^INSERT INTO `tblclients` \(`id`, `firstname`, `lastname`, `companyname`, `email`, [^)]+\) VALUES \((\d+),'(.*?)','(.*?)','(.*?)','(.*?)',/;
            my ($rid, $rfirstname, $rlastname, $rcompanyname, $remail) = ($1, $2, $3, $4, $5);
            if (defined $clientid) {
                # find by ID
                if ($rid == $clientid) {
                    $clientemail = $remail;
                    push @sql, $_;
                    log_info "Found client ID=%s in backup", $clientid;
                    last FIND_CLIENT;
                }
            } else {
                # find by email
                if (lc $remail eq $clientemail) {
                    $clientid = $rid;
                    push @sql, $_;
                    log_info "Found client email=%s in backup: ID=%s", $clientemail, $clientid;
                    last FIND_CLIENT;
                }
            }
        }
        return [404, "Couldn't find client email=$clientemail in database backup, please check the email or try another backup"];
    }

    my @invoiceids;
  FIND_INVOICES:
    {
        last unless $args{restore_invoices};
        open my $fh, "<", "tblinvoices"
            or return [500, "Can't open $sql_backup_dir/tblinvoices: $!"];
        while (<$fh>) {
            next unless /^INSERT INTO `tblinvoices` \(`id`, `userid`, [^)]+\) VALUES \((\d+),(\d+),/;
            my ($rid, $ruserid) = ($1, $2);
            if ($ruserid == $clientid) {
                push @invoiceids, $rid;
                push @sql, $_;
                log_info "Found client invoice in backup: ID=%s", $rid;
            }
        }
        log_info "Number of invoices found for client in backup: %d", ~~@invoiceids if @invoiceids;
    }

  FIND_INVOICEITEMS:
    {
        last unless @invoiceids;
        open my $fh, "<", "tblinvoiceitems"
            or return [500, "Can't open $sql_backup_dir/tblinvoiceitems: $!"];
        while (<$fh>) {
            next unless /^INSERT INTO `tblinvoiceitems` \(`id`, `invoiceid`, `userid`, [^)]+\) VALUES \((\d+),(\d+),(\d+)/;
            my ($rid, $rinvoiceid, $ruserid) = ($1, $2, $3);
            if (grep {$rinvoiceid == $_} @invoiceids) {
                log_trace "Adding invoice item %s for invoice #%s", $rid, $rinvoiceid;
                push @sql, $_;
            }
        }
    }

  FIND_HOSTINGS:
    {
        last unless $args{restore_hostings};
        open my $fh, "<", "tblhosting"
            or return [500, "Can't open $sql_backup_dir/tblhosting: $!"];
        while (<$fh>) {
            next unless /^INSERT INTO `tblhosting` \(`id`, `userid`, [^)]+\) VALUES \((\d+),(\d+),(\d+)/;
            my ($rid, $ruserid) = ($1, $2, $3);
            if ($ruserid == $clientid) {
                log_trace "Found hosting for client in backup: ID=%d", $rid;
                push @sql, $_;
            }
        }
    }

  FIND_DOMAINS:
    {
        last unless $args{restore_domains};
        open my $fh, "<", "tbldomains"
            or return [500, "Can't open $sql_backup_dir/tbldomains: $!"];
        while (<$fh>) {
            next unless /^INSERT INTO `tbldomains` \(`id`, `userid`, [^)]+\) VALUES \((\d+),(\d+),(\d+)/;
            my ($rid, $ruserid) = ($1, $2, $3);
            if ($ruserid == $clientid) {
                log_trace "Found domain for client in backup: ID=%d", $rid;
                push @sql, $_;
            }
        }
    }

    # TODO: tickets?

    # records in tblaccounts (transactions) are not deleted when client is
    # deleted

lib/App/WHMCSUtils.pm  view on Meta::CPAN

Return value:  (any)



=head2 restore_whmcs_client

Usage:

 restore_whmcs_client(%args) -> [$status_code, $reason, $payload, \%result_meta]

Restore a missing client from SQL database backup.

This function is not exported.

This function supports dry-run operation.


Arguments ('*' denotes required arguments):

=over 4

=item * B<client_email> => I<str>

=item * B<client_id> => I<posint>

=item * B<restore_domains> => I<bool> (default: 1)

=item * B<restore_hostings> => I<bool> (default: 1)

=item * B<restore_invoices> => I<bool> (default: 1)

=item * B<sql_backup_dir> => I<dirname>

Directory containing per-table SQL files.

=item * B<sql_backup_file> => I<filename>

Can accept either C<.sql> or C<.sql.gz>.

Will be converted first to a directory where the SQL file will be extracted to
separate files on a per-table basis.


=back

Special arguments:

script/restore-whmcs-client  view on Meta::CPAN

our $VERSION = '0.012'; # VERSION

my $cmdline = Perinci::CmdLine::Any->new(
    url => "/App/WHMCSUtils/restore_whmcs_client",
    program_name => "restore-whmcs-client",
    log => 1,
);

$cmdline->run;

# ABSTRACT: Restore a missing client from SQL database backup
# PODNAME: restore-whmcs-client

__END__

=pod

=encoding UTF-8

=head1 NAME

restore-whmcs-client - Restore a missing client from SQL database backup

=head1 VERSION

This document describes version 0.012 of restore-whmcs-client (from Perl distribution App-WHMCSUtils), released on 2021-11-30.

=head1 SYNOPSIS

Usage:

% B<restore-whmcs-client> [B<--client-email>=I<str>] [B<--client-id>=I<posint>] [B<--config-path>=I<path>|B<-c>|B<--no-config>|B<-C>] [B<--config-profile>=I<profile>|B<-P>] [B<--debug>|B<--log-level>=I<level>|B<--quiet>|B<--trace>|B<--verbose>] [B<--...

=head1 OPTIONS

C<*> marks required options.

=head2 Main options

=over

=item B<--client-email>=I<s>

=item B<--client-id>=I<s>

=item B<--no-restore-domains>

=item B<--no-restore-hostings>

=item B<--no-restore-invoices>

=item B<--sql-backup-dir>=I<s>

Directory containing per-table SQL files.

=item B<--sql-backup-file>=I<s>

Can accept either `.sql` or `.sql.gz`.

Will be converted first to a directory where the SQL file will be extracted to
separate files on a per-table basis.


=back

=head2 Configuration options

script/restore-whmcs-client  view on Meta::CPAN

List of available configuration parameters:

 client_email (see --client-email)
 client_id (see --client-id)
 format (see --format)
 log_level (see --log-level)
 naked_res (see --naked-res)
 restore_domains (see --no-restore-domains)
 restore_hostings (see --no-restore-hostings)
 restore_invoices (see --no-restore-invoices)
 sql_backup_dir (see --sql-backup-dir)
 sql_backup_file (see --sql-backup-file)

=head1 ENVIRONMENT

=head2 RESTORE_WHMCS_CLIENT_OPT => str

Specify additional command-line options.

=head1 FILES

F<~/.config/restore-whmcs-client.conf>



( run in 0.693 second using v1.01-cache-2.11-cpan-49f99fa48dc )