api/v0: switch the API to the new model

This commit is contained in:
Lucas Gabriel Vuotto 2025-05-03 13:34:26 +00:00
parent 0635e37e13
commit 230c595e80
5 changed files with 99 additions and 137 deletions

View file

@ -5,6 +5,18 @@ use DBD::SQLite::Constants ":dbd_sqlite_string_mode";
use DBIx::Migration;
use Pooru::Schema;
use Pooru::API::V0::Model::Media;
use Pooru::API::V0::Model::Tags;
use DBI;
use constant {
"Pooru::API::V0::Model::Media::ROWS" => 12,
"Pooru::API::V0::Model::Media::SIMILAR_ROWS" => 6,
"Pooru::API::V0::Model::Tags::ROWS" => 100,
"Pooru::API::V0::Model::Tags::SEARCH_ROWS" => 15,
};
sub _search_opts ()
{
return {
@ -63,6 +75,32 @@ sub startup ($self)
dir => $self->app->home->child("migrations")->to_string,
})->migrate;
$self->helper(dbh => sub {
state $dbh = DBI->connect(
$config->{dsn},
$config->{db_username},
$config->{db_password},
{
PrintError => 0,
RaiseError => 1,
AutoCommit => 1,
($config->{dbi_connect_args} // {})->%*,
}
)
});
$self->dbh->{FetchHashKeyName} = "NAME_lc";
if ($self->dbh->{Driver}->{Name} eq "SQLite") {
$self->dbh->do("PRAGMA foreign_keys = ON");
}
$self->helper(tags_model => sub {
state $model = Pooru::API::V0::Model::Tags->new($self->dbh)
});
$self->helper(media_model => sub {
state $model = Pooru::API::V0::Model::Media->new($self->dbh)
});
my $r = $self->routes;
$r->get("/meta")->to("meta#index")->name("meta");

View file

@ -7,52 +7,26 @@ my %search_opts = Pooru::API::V0::_search_opts->%*;
sub _list_no_tags ($self, $page)
{
my $paged_media = $self->schema->resultset("Media")
->search(undef, $search_opts{Media})
->page($page);
my @media = map +{
id => $_->id,
storage_id => $_->storage_id,
filename => $_->filename,
content_type => $_->content_type,
upload_datetime => $_->upload_datetime,
}, $paged_media->all;
my $paged_media = $self->media_model->get->page($page);
return $self->render(json => {
media => [@media],
pager => {$self->pager($paged_media)},
media => $paged_media->fetchall,
pager => $paged_media->pager,
});
}
# Executes
#
# SELECT * FROM tagged_media_view
# WHERE (tag_display = t_1 OR ... OR tag_display = t_N)
# GROUP BY media_id HAVING COUNT(media_id) = N
sub _list_with_tags ($self, $page, @tags)
{
my $attrs = {
$search_opts{TaggedMediaView_media}->%*,
# Use "0 + ?" as otherwise a bare "?" interprets the value as
# TEXT, breaking the functionality.
having => \["COUNT(media_id) = 0 + ?", scalar @tags],
group_by => "media_id",
};
my $paged_media = $self->schema->resultset("TaggedMediaView")
->search({tag_display => [@tags]}, $attrs)->page($page);
my @media = map +{
id => $_->media_id,
storage_id => $_->media_storage_id,
filename => $_->media_filename,
content_type => $_->media_content_type,
upload_datetime => $_->media_upload_datetime,
}, $paged_media->all;
my $paged_media_ids = $self->media_model->with_all_tags(@tags)
->page($page);
my @media_ids = map {$_->{media_id}} $paged_media_ids->fetchall->@*;
return $self->render(json => {
media => [@media],
pager => {$self->pager($paged_media)},
media => $self->media_model->get(@media_ids)->page($page)
->fetchall,
# Use the original pager, as the one for the media is already
# capped at the size of a single page.
pager => $paged_media_ids->pager,
});
}
@ -72,10 +46,18 @@ sub list ($self)
status => 400,
) if $v->has_error;
return $self->render(
json => {error => "Too many tags."},
status => 400,
) if @tags > Pooru::API::V0::Model::Tags::ROWS;
my @tag_ids;
@tag_ids = map {$_->{id}} $self->tags_model
->get(display => [@tags])->fetchall->@* if @tags > 0;
return @tags == 0 ?
$self->_list_no_tags($page) :
$self->_list_with_tags($page, @tags);
$self->_list_with_tags($page, @tag_ids);
}
sub show ($self)
@ -88,85 +70,45 @@ sub show ($self)
status => 400,
) if $v->has_error;
my $media_id = $self->stash("media_id");
my $media = $self->schema->resultset("Media")
->single({id => $media_id});
my $media = $self->media_model->get($self->stash("media_id"))->single;
return $self->render(
json => {error => "Media not found"},
status => 404,
) if !defined($media);
my $paged_tags = $self->schema->resultset("TaggedMediaView")
->search({media_id => $media_id},
$search_opts{TaggedMediaView_tags})->page($page);
my @tags = map +{
id => $_->tag_id,
name => $_->tag_name,
kind_id => $_->tag_kind_id,
display => $_->tag_display,
count => $_->tag_count,
}, $paged_tags->all;
my $paged_tags = $self->tags_model
->ranked_for_media($media->{id})->page($page);
return $self->render(json => {
id => $media->id,
storage_id => $media->storage_id,
filename => $media->filename,
content_type => $media->content_type,
upload_datetime => $media->upload_datetime,
tags => [@tags],
pager => {$self->pager($paged_tags)},
$media->%*,
tags => $paged_tags->fetchall,
pager => $paged_tags->pager,
});
}
# Executes
# SELECT *, COUNT(tag_id) AS similarity_score FROM tagged_media_view
# WHERE tag_id IN (
# SELECT tag_id FROM tagged_media_view WHERE media_id = m
# ) AND media_id != m
# GROUP BY media_id ORDER BY similarity_score
sub similar ($self)
{
my $media_id = $self->stash("media_id");
my $media = $self->schema->resultset("Media")
->single({id => $media_id});
my $media = $self->media_model->get($self->stash("media_id"))->single;
return $self->render(
json => {error => "Media not found"},
status => 404,
) if !defined($media);
my %attrs = (
select => ["tag_id"],
rows => 100,
);
my $tags = $self->schema->resultset("MediaTag")
->search({media_id => $media_id}, \%attrs)->as_query;
my @similar_media = $self->media_model
->similar($media->{id})->fetchall->@*;
my %media = map +(
$_->{id} => $_
), $self->media_model
->get(map {$_->{media_id}} @similar_media)->fetchall->@*;
my %search = (
media_id => {"!=", $media_id},
tag_id => {"-in", $tags},
);
%attrs = (
"+select" => [{count => "tag_id", -as => "similarity_score"}],
"+as" => ["similarity_score"],
group_by => "media_id",
order_by => {-desc => "similarity_score"},
rows => 6,
);
my @media = map +{
id => $_->media_id,
storage_id => $_->media_storage_id,
filename => $_->media_filename,
content_type => $_->media_content_type,
upload_datetime => $_->media_upload_datetime,
similarity_score => $_->get_column("similarity_score"),
}, $self->schema->resultset("TaggedMediaView")
->search(\%search, \%attrs)->all;
foreach my $sm (@similar_media) {
$sm = {
score => $sm->{score},
$media{$sm->{media_id}}->%*,
}
}
return $self->render(json => {
media => [@media],
});
return $self->render(json => {media => [@similar_media]});
}
1;

View file

@ -3,27 +3,27 @@ use Mojo::Base "Mojolicious::Controller", -signatures;
sub media ($self)
{
my $media = $self->schema->resultset("Media")->random->single;
my $media_id = $self->media_model->random_id;
return $self->render(
json => {error => "Media not found."},
status => 404,
) if !defined($media);
) if !defined($media_id);
return $self->redirect_to("show_media", media_id => $media->id);
return $self->redirect_to("show_media", media_id => $media_id);
}
sub tag ($self)
{
my $tag = $self->schema->resultset("Tag")->random->single;
my $tag_id = $self->tags_model->random_id;
return $self->render(
json => {error => "Tag not found."},
status => 404,
) if !defined($tag);
) if !defined($tag_id);
return $self->redirect_to(
$self->url_for("show_tag")->query(id => $tag->id));
$self->url_for("show_tag")->query(id => $tag_id));
}
1;

View file

@ -13,19 +13,11 @@ sub list ($self)
status => 400,
) if $v->has_error;
my $paged_tags = $self->schema->resultset("TagCountView")
->search(undef, $search_opts{TagCountView})->page($page);
my @tags = map +{
id => $_->id,
name => $_->name,
kind_id => $_->kind_id,
display => $_->display,
count => $_->count,
}, $paged_tags->all;
my $paged_tags = $self->tags_model->ranked->page($page);
return $self->render(json => {
tags => [@tags],
pager => {$self->pager($paged_tags)},
tags => $paged_tags->fetchall,
pager => $paged_tags->pager,
});
}
@ -41,7 +33,7 @@ sub show ($self)
status => 400,
) if $v->has_error;
my %search = ($v->topic => $tag_id_or_name);
my %search = ($v->topic => [$tag_id_or_name]);
my $kind_id = $v->optional("kind_id")->param;
return $self->render(
@ -50,13 +42,9 @@ sub show ($self)
) if $v->has_error;
$search{kind_id} = $kind_id if defined($kind_id);
my @tags = map +{
id => $_->id,
name => $_->name,
kind_id => $_->kind_id,
display => $_->display,
count => $_->count,
}, $self->schema->resultset("TagCountView")->search(\%search)->all;
my @tag_ids = map {$_->{id}} $self->tags_model
->get(%search)->fetchall->@*;
my @tags = $self->tags_model->ranked_for_tags(@tag_ids)->fetchall->@*;
return $self->render(
json => {error => "Tag not found."},
@ -83,19 +71,7 @@ sub search ($self)
$q =~ s/[%\\_]/\\$&/g;
$q .= '%';
my %attrs = (
$search_opts{TagCountView}->%*,
rows => 10,
);
my @tags = map +{
id => $_->id,
name => $_->name,
kind_id => $_->kind_id,
display => $_->display,
count => $_->count,
}, $self->schema->resultset("TagCountView")
->search({name => \["LIKE ? ESCAPE '\\'", $q]}, \%attrs);
my @tags = $self->tags_model->search($q)->fetchall->@*;
return $self->render(json => {tags => [@tags]});
}

View file

@ -1,5 +1,11 @@
use DBD::SQLite::Constants ":dbd_sqlite_string_mode";
{
dsn => "dbi:SQLite:db/pooru.db",
dbi_connect_args => {
# Highly recommended to keep this.
sqlite_string_mode => DBD_SQLITE_STRING_MODE_UNICODE_STRICT,
},
secrets => [
# Generate with "openssl rand -hex 32".