api/v0: add a new model implementation

Use DBI exclusively to have a fine control of the produced queries. dbic
requires a lot of symbols permutation to produce the desired ones.
This commit is contained in:
Lucas Gabriel Vuotto 2025-05-03 13:30:27 +00:00
parent b052526f4e
commit 0635e37e13
4 changed files with 318 additions and 0 deletions

View file

@ -0,0 +1,84 @@
package Pooru::API::V0::Model::Media;
use v5.40;
use Pooru::API::V0::Model::PagedResults;
my $slice = {};
sub where_in ($k, @v) { "$k IN (" . join(",", split(//, "?" x @v)) . ")" }
sub new ($class, $dbh) { bless {_dbh => $dbh}, $class }
sub count ($self)
{
return $self->{_dbh}->selectrow_arrayref(q{
SELECT COUNT(*) FROM media
})->[0];
}
sub random_id ($self)
{
my $row = $self->{_dbh}->selectrow_arrayref(q{
SELECT id FROM media ORDER BY random() LIMIT 1
});
return defined($row) ? $row->[0] : undef;
}
sub get ($self, @ids)
{
my $where_clause = "";
$where_clause = "WHERE " . where_in("id", @ids) if @ids > 0;
my $sth = $self->{_dbh}->prepare(qq{
SELECT * FROM media $where_clause ORDER BY id DESC
});
return Pooru::API::V0::Model::PagedResults->new($sth, $self->ROWS,
$slice, @ids);
}
sub with_all_tags ($self, @tag_ids)
{
my $where_clause = where_in("tag_id", @tag_ids);
# "0 + ?" forces a numeric cast. Otherwise, it's interpreted as TEXT.
my $stmt = qq{
SELECT
media_id
FROM media_tag
WHERE $where_clause
GROUP BY media_id
HAVING COUNT(media_id) = 0 + ?
ORDER BY media_id DESC
};
my $sth = $self->{_dbh}->prepare($stmt);
return Pooru::API::V0::Model::PagedResults->new($sth, $self->ROWS,
$slice, (@tag_ids, scalar @tag_ids));
}
sub similar ($self, $id)
{
my $sth = $self->{_dbh}->prepare(qq{
SELECT
media_id,
COUNT(tag_id) AS score
FROM media_tag
WHERE tag_id IN (
SELECT
tag_id
FROM media_tag
WHERE media_id = ?
) AND media_id != ?
GROUP BY media_id
ORDER BY score DESC, media_id DESC
LIMIT ?
});
return Pooru::API::V0::Model::Results->new($sth, $slice,
($id, $id, $self->SIMILAR_ROWS));
}
1;

View file

@ -0,0 +1,59 @@
package Pooru::API::V0::Model::PagedResults;
use v5.40;
use parent "Pooru::API::V0::Model::Results";
sub new ($class, $sth, $pgsz, $slice, @bind_values)
{
my $self = $class->SUPER::new($sth, $slice, (@bind_values, $pgsz, 0));
$self->{_current} = 1;
$self->{_pgsz} = $pgsz;
$self->{_offset} = @bind_values + 1;
my $dbh = $sth->{Database};
$self->{_rows} = $dbh->selectrow_arrayref(qq{
SELECT COUNT(*) FROM ($sth->{Statement})
}, undef, @bind_values)->[0];
$self->{_sth} = $dbh->prepare(qq{$sth->{Statement} LIMIT ? OFFSET ?});
{
use integer;
$self->{_npages} = $self->{_rows} / $self->{_pgsz} + 1;
}
return $self;
}
sub page_size ($self) { $self->{_pgsz} }
sub rows ($self) { $self->{_rows} }
sub page ($self, $n)
{
$n = $n < 1 ? 1 : $n > $self->{_npages} ? $self->{_npages} : $n;
$self->{_current} = $n;
return $self;
}
sub pager ($self)
{
return {
first_page => 1,
previous_page => $self->{_current} > 1 ?
$self->{_current} - 1 : undef,
current_page => $self->{_current},
next_page => $self->{_current} < $self->{_npages} ?
$self->{_current} + 1 : undef,
last_page => $self->{_npages},
};
}
sub fetchall ($self, $slice = $self->{_slice}) {
$self->{_bind_values}->[$self->{_offset}] =
$self->{_pgsz} * ($self->{_current} - 1);
return $self->SUPER::fetchall($slice);
}
1;

View file

@ -0,0 +1,30 @@
package Pooru::API::V0::Model::Results;
use v5.40;
sub new ($class, $sth, $slice, @bind_values)
{
my $self = {
_sth => $sth,
_slice => $slice,
_bind_values => [@bind_values],
};
return bless $self, $class;
}
sub fetchall ($self, $slice = $self->{_slice})
{
$self->{_sth}->execute($self->{_bind_values}->@*);
return $self->{_sth}->fetchall_arrayref($slice);
}
sub single ($self, $slice = $self->{_slice})
{
my $rows = $self->fetchall($slice);
return $rows->@* == 1 ? $rows->[0] : undef;
}
1;

View file

@ -0,0 +1,145 @@
package Pooru::API::V0::Model::Tags;
use v5.40;
use Pooru::API::V0::Model::PagedResults;
my $slice = {};
sub where_in ($k, @v) { "$k IN (" . join(",", split(//, "?" x @v)) . ")" }
sub new ($class, $dbh) { bless {_dbh => $dbh}, $class }
sub count ($self)
{
return $self->{_dbh}->selectrow_arrayref(q{
SELECT COUNT(*) FROM tag
})->[0];
}
sub random_id ($self)
{
my $row = $self->{_dbh}->selectrow_arrayref(q{
SELECT id FROM tag ORDER BY random() LIMIT 1
});
return defined($row) ? $row->[0] : undef;
}
sub get ($self, %search)
{
my $key = exists($search{id}) ?
"id" : exists($search{display}) ?
"display" : "name";
my $where_clause = where_in($key, $search{$key}->@*);
my @bind_values = $search{$key}->@*;
if (exists($search{kind_id})) {
$where_clause .= " AND kind_id = ?";
push(@bind_values, $search{kind_id});
}
my $sth = $self->{_dbh}->prepare(qq{
SELECT *
FROM tag
WHERE $where_clause
LIMIT ?
});
return Pooru::API::V0::Model::Results->new($sth, $slice,
(@bind_values, $self->ROWS));
}
sub _ranked ($self, $stmt, @bind_values)
{
my $sth = $self->{_dbh}->prepare($stmt);
return Pooru::API::V0::Model::PagedResults->new($sth, $self->ROWS,
$slice, @bind_values);
}
sub ranked ($self) {
my $stmt = q{
SELECT
tag.id,
tag.name,
tag.kind_id,
tag.display,
COUNT(*) AS count
FROM media_tag, tag
WHERE media_tag.tag_id = tag.id
GROUP BY tag.id
ORDER BY count DESC, kind_id DESC, id ASC
};
return $self->_ranked($stmt);
}
sub ranked_for_tags ($self, @ids)
{
my $where_clause = where_in("tag.id", @ids);
my $stmt = qq{
SELECT
tag.id,
tag.name,
tag.kind_id,
tag.display,
COUNT(*) AS count
FROM media_tag, tag
WHERE media_tag.tag_id = tag.id AND $where_clause
GROUP BY tag.id
ORDER BY count DESC, kind_id DESC, id ASC
};
return $self->_ranked($stmt, @ids);
}
sub ranked_for_media ($self, @ids)
{
my $where_clause = where_in("media_tag.media_id", @ids);
my $stmt = qq{
SELECT
tag_count.id,
tag_count.name,
tag_count.kind_id,
tag_count.display,
tag_count.count
FROM
media_tag,
(SELECT
tag.id,
tag.name,
tag.kind_id,
tag.display,
COUNT(*) AS count
FROM media_tag, tag
WHERE media_tag.tag_id = tag.id
GROUP BY tag.id) tag_count
WHERE media_tag.tag_id = tag_count.id AND $where_clause
ORDER BY count DESC, kind_id DESC, id ASC
};
return $self->_ranked($stmt, @ids);
}
sub search ($self, $q)
{
my $sth = $self->{_dbh}->prepare(qq{
SELECT
tag.id,
tag.name,
tag.kind_id,
tag.display,
COUNT(*) AS count
FROM media_tag, tag
WHERE media_tag.tag_id = tag.id AND tag.name LIKE ? ESCAPE '\\'
GROUP BY tag.id
ORDER BY count DESC, tag.kind_id DESC, tag.id ASC
LIMIT ?
});
return Pooru::API::V0::Model::Results->new($sth, $slice,
($q, $self->SEARCH_ROWS));
}
1;