package Peace::DB; use v5.30.0; use strict; use warnings; use Params::ValidationCompiler qw/validation_for/; use Types::Standard qw/HashRef/; use DBI; use Const::Fast; my @migrations = ( 'CREATE TABLE options ( key TEXT PRIMARY KEY, value INTEGER );', 'CREATE EXTENSION "pgcrypto";', 'CREATE TABLE customers ( uuid UUID NOT NULL DEFAULT gen_random_uuid(), date_creation timestamp DEFAULT NOW(), secret_bcrypt TEXT NOT NULL, stripe_id TEXT, PRIMARY KEY (uuid) );', 'CREATE TABLE developers ( uuid UUID NOT NULL DEFAULT gen_random_uuid(), date_creation timestamp DEFAULT NOW(), secret_bcrypt TEXT NOT NULL, name TEXT NOT NULL, surname TEXT NOT NULL, email TEXT NOT NULL UNIQUE, stripe_id TEXT, country TEXT NOT NULL, verified BOOL DEFAULT false, PRIMARY KEY (uuid) );', 'CREATE TABLE applications ( uuid UUID NOT NULL DEFAULT gen_random_uuid(), date_creation timestamp DEFAULT NOW(), name TEXT NOT NULL, description TEXT NOT NULL, url TEXT NOT NULL, developer UUID NOT NULL, price INTEGER NOT NULL, git_repo TEXT NOT NULL, app_id TEXT NOT NULL UNIQUE, flatpak_builder_file TEXT NOT NULL, flatpak_repo TEXT NOT NULL, verified BOOL DEFAULT false, PRIMARY KEY (uuid), FOREIGN KEY (developer) REFERENCES developers (uuid) );', 'CREATE TABLE releases ( uuid UUID NOT NULL DEFAULT gen_random_uuid(), date_creation timestamp DEFAULT NOW(), application UUID NOT NULL, tag TEXT NOT NULL, name TEXT NOT NULL, PRIMARY KEY (uuid), FOREIGN KEY (application) REFERENCES applications (uuid) );', 'CREATE TABLE builds ( uuid UUID NOT NULL DEFAULT gen_random_uuid(), date_creation timestamp DEFAULT NOW(), release UUID NOT NULL, success BOOLEAN NOT NULL, log TEXT NOT NULL, arch TEXT NOT NULL, PRIMARY KEY (uuid), FOREIGN KEY (release) REFERENCES releases (uuid) );', 'CREATE TABLE purchases ( customer UUID NOT NULL, application UUID NOT NULL, date_purchase timestamp DEFAULT NOW(), PRIMARY KEY (customer, application), FOREIGN KEY (application) REFERENCES applications (uuid), FOREIGN KEY (customer) REFERENCES customers (uuid) );', ); { my $validator = validation_for( params => { config => { type => HashRef } } ); sub dbh { my $self = shift; my %params = $validator->(@_); my $config = $params{config}; my $db_config = $config->{db_config}; my $dbname = $db_config->{dbname} or die 'No dbname in db_config'; my $host = $db_config->{host} or die 'No host in db_config'; my $username = $db_config->{username} or die 'No username in db_config'; my $password = $db_config->{password} or die 'No password in db_config'; my $port = $db_config->{port}; my $dsn = "dbi:Pg:" . ( defined $dbname ? "dbname=$dbname;" : "" ) . ( defined $host ? "host=$host;" : "" ) . ( defined $port ? "port=$port;" : "" ); my $dbh = DBI->connect_cached( $dsn, $username, $password, { AutoCommit => 1, RaiseError => 1, sqlite_unicode => 1, } ); state $migrations_run = 0; if ( !$migrations_run ) { run_migrations($dbh); $migrations_run = 1; } return $dbh; } } sub run_migrations { my $dbh = shift; my $current_migration = _get_current_migration_number($dbh); local $dbh->{RaiseError} = 0; if ( $current_migration < scalar @migrations ) { my @needed_migrations = @migrations[ $current_migration .. $#migrations ]; for my $migration (@needed_migrations) { $dbh->do($migration); my $update_result = $dbh->do( <<'EOF', undef, ++$current_migration ); UPDATE options SET value = ? WHERE key = 'migration'; EOF unless ( defined $update_result && 0+$update_result ) { say $dbh->do( <<'EOF', undef, $current_migration); INSERT INTO options (key, value) VALUES ('migration', ?); EOF } } } } sub _get_current_migration_number { my $dbh = shift; local $dbh->{RaiseError} = 0; my $migration = $dbh->selectrow_hashref( <<'EOF', {} ); SELECT value FROM options WHERE key = 'migration' EOF my $value = 0; if ( defined $migration ) { $value = $migration->{value}; } return $value; } 1; =encoding utf8 =head1 NAME Peace::DB - Database handler generator for the Peace shop. =head1 SYNOPSIS my $peace = Peace->new; my $home = $ENV{HOME}; my $config = $peace->plugin( JSONConfig => { file => "$home/.config/peace/peace.conf" } ); my $dbh = Peace::DB->dbh( config => $config ); =head1 DESCRIPTION This module helps to recover a database handle and runs the migrations automatically when doing so. =head1 FUNCTIONS Peace::DB implements the following functions: =head2 dbh Recovers a database handle, requires the config of the app as its parameter. my $dbh = Peace::DB->dbh( config => $config ); =head2 run_migrations Runs the migrations manually. Peace::DB->run_migrations($dbh); =head1 SEE ALSO L, L, L