diff --git a/lib/Pooru/API/V0/Model/Media.pm b/lib/Pooru/API/V0/Model/Media.pm new file mode 100644 index 0000000..d8247a6 --- /dev/null +++ b/lib/Pooru/API/V0/Model/Media.pm @@ -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; diff --git a/lib/Pooru/API/V0/Model/PagedResults.pm b/lib/Pooru/API/V0/Model/PagedResults.pm new file mode 100644 index 0000000..2a95f98 --- /dev/null +++ b/lib/Pooru/API/V0/Model/PagedResults.pm @@ -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; diff --git a/lib/Pooru/API/V0/Model/Results.pm b/lib/Pooru/API/V0/Model/Results.pm new file mode 100644 index 0000000..6527404 --- /dev/null +++ b/lib/Pooru/API/V0/Model/Results.pm @@ -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; diff --git a/lib/Pooru/API/V0/Model/Tags.pm b/lib/Pooru/API/V0/Model/Tags.pm new file mode 100644 index 0000000..f6d2395 --- /dev/null +++ b/lib/Pooru/API/V0/Model/Tags.pm @@ -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;