api/v0: switch the API to the new model
This commit is contained in:
parent
0635e37e13
commit
230c595e80
5 changed files with 99 additions and 137 deletions
|
@ -5,6 +5,18 @@ use DBD::SQLite::Constants ":dbd_sqlite_string_mode";
|
||||||
use DBIx::Migration;
|
use DBIx::Migration;
|
||||||
use Pooru::Schema;
|
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 ()
|
sub _search_opts ()
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
|
@ -63,6 +75,32 @@ sub startup ($self)
|
||||||
dir => $self->app->home->child("migrations")->to_string,
|
dir => $self->app->home->child("migrations")->to_string,
|
||||||
})->migrate;
|
})->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;
|
my $r = $self->routes;
|
||||||
|
|
||||||
$r->get("/meta")->to("meta#index")->name("meta");
|
$r->get("/meta")->to("meta#index")->name("meta");
|
||||||
|
|
|
@ -7,52 +7,26 @@ my %search_opts = Pooru::API::V0::_search_opts->%*;
|
||||||
|
|
||||||
sub _list_no_tags ($self, $page)
|
sub _list_no_tags ($self, $page)
|
||||||
{
|
{
|
||||||
my $paged_media = $self->schema->resultset("Media")
|
my $paged_media = $self->media_model->get->page($page);
|
||||||
->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;
|
|
||||||
|
|
||||||
return $self->render(json => {
|
return $self->render(json => {
|
||||||
media => [@media],
|
media => $paged_media->fetchall,
|
||||||
pager => {$self->pager($paged_media)},
|
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)
|
sub _list_with_tags ($self, $page, @tags)
|
||||||
{
|
{
|
||||||
my $attrs = {
|
my $paged_media_ids = $self->media_model->with_all_tags(@tags)
|
||||||
$search_opts{TaggedMediaView_media}->%*,
|
->page($page);
|
||||||
|
my @media_ids = map {$_->{media_id}} $paged_media_ids->fetchall->@*;
|
||||||
# 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;
|
|
||||||
|
|
||||||
return $self->render(json => {
|
return $self->render(json => {
|
||||||
media => [@media],
|
media => $self->media_model->get(@media_ids)->page($page)
|
||||||
pager => {$self->pager($paged_media)},
|
->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,
|
status => 400,
|
||||||
) if $v->has_error;
|
) 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 ?
|
return @tags == 0 ?
|
||||||
$self->_list_no_tags($page) :
|
$self->_list_no_tags($page) :
|
||||||
$self->_list_with_tags($page, @tags);
|
$self->_list_with_tags($page, @tag_ids);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub show ($self)
|
sub show ($self)
|
||||||
|
@ -88,85 +70,45 @@ sub show ($self)
|
||||||
status => 400,
|
status => 400,
|
||||||
) if $v->has_error;
|
) if $v->has_error;
|
||||||
|
|
||||||
my $media_id = $self->stash("media_id");
|
my $media = $self->media_model->get($self->stash("media_id"))->single;
|
||||||
my $media = $self->schema->resultset("Media")
|
|
||||||
->single({id => $media_id});
|
|
||||||
|
|
||||||
return $self->render(
|
return $self->render(
|
||||||
json => {error => "Media not found"},
|
json => {error => "Media not found"},
|
||||||
status => 404,
|
status => 404,
|
||||||
) if !defined($media);
|
) if !defined($media);
|
||||||
|
|
||||||
my $paged_tags = $self->schema->resultset("TaggedMediaView")
|
my $paged_tags = $self->tags_model
|
||||||
->search({media_id => $media_id},
|
->ranked_for_media($media->{id})->page($page);
|
||||||
$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;
|
|
||||||
|
|
||||||
return $self->render(json => {
|
return $self->render(json => {
|
||||||
id => $media->id,
|
$media->%*,
|
||||||
storage_id => $media->storage_id,
|
tags => $paged_tags->fetchall,
|
||||||
filename => $media->filename,
|
pager => $paged_tags->pager,
|
||||||
content_type => $media->content_type,
|
|
||||||
upload_datetime => $media->upload_datetime,
|
|
||||||
tags => [@tags],
|
|
||||||
pager => {$self->pager($paged_tags)},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
# 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)
|
sub similar ($self)
|
||||||
{
|
{
|
||||||
my $media_id = $self->stash("media_id");
|
my $media = $self->media_model->get($self->stash("media_id"))->single;
|
||||||
my $media = $self->schema->resultset("Media")
|
|
||||||
->single({id => $media_id});
|
|
||||||
|
|
||||||
return $self->render(
|
return $self->render(
|
||||||
json => {error => "Media not found"},
|
json => {error => "Media not found"},
|
||||||
status => 404,
|
status => 404,
|
||||||
) if !defined($media);
|
) if !defined($media);
|
||||||
|
|
||||||
my %attrs = (
|
my @similar_media = $self->media_model
|
||||||
select => ["tag_id"],
|
->similar($media->{id})->fetchall->@*;
|
||||||
rows => 100,
|
my %media = map +(
|
||||||
);
|
$_->{id} => $_
|
||||||
my $tags = $self->schema->resultset("MediaTag")
|
), $self->media_model
|
||||||
->search({media_id => $media_id}, \%attrs)->as_query;
|
->get(map {$_->{media_id}} @similar_media)->fetchall->@*;
|
||||||
|
|
||||||
my %search = (
|
foreach my $sm (@similar_media) {
|
||||||
media_id => {"!=", $media_id},
|
$sm = {
|
||||||
tag_id => {"-in", $tags},
|
score => $sm->{score},
|
||||||
);
|
$media{$sm->{media_id}}->%*,
|
||||||
%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;
|
|
||||||
|
|
||||||
return $self->render(json => {
|
return $self->render(json => {media => [@similar_media]});
|
||||||
media => [@media],
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
|
|
@ -3,27 +3,27 @@ use Mojo::Base "Mojolicious::Controller", -signatures;
|
||||||
|
|
||||||
sub media ($self)
|
sub media ($self)
|
||||||
{
|
{
|
||||||
my $media = $self->schema->resultset("Media")->random->single;
|
my $media_id = $self->media_model->random_id;
|
||||||
|
|
||||||
return $self->render(
|
return $self->render(
|
||||||
json => {error => "Media not found."},
|
json => {error => "Media not found."},
|
||||||
status => 404,
|
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)
|
sub tag ($self)
|
||||||
{
|
{
|
||||||
my $tag = $self->schema->resultset("Tag")->random->single;
|
my $tag_id = $self->tags_model->random_id;
|
||||||
|
|
||||||
return $self->render(
|
return $self->render(
|
||||||
json => {error => "Tag not found."},
|
json => {error => "Tag not found."},
|
||||||
status => 404,
|
status => 404,
|
||||||
) if !defined($tag);
|
) if !defined($tag_id);
|
||||||
|
|
||||||
return $self->redirect_to(
|
return $self->redirect_to(
|
||||||
$self->url_for("show_tag")->query(id => $tag->id));
|
$self->url_for("show_tag")->query(id => $tag_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
|
|
@ -13,19 +13,11 @@ sub list ($self)
|
||||||
status => 400,
|
status => 400,
|
||||||
) if $v->has_error;
|
) if $v->has_error;
|
||||||
|
|
||||||
my $paged_tags = $self->schema->resultset("TagCountView")
|
my $paged_tags = $self->tags_model->ranked->page($page);
|
||||||
->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;
|
|
||||||
|
|
||||||
return $self->render(json => {
|
return $self->render(json => {
|
||||||
tags => [@tags],
|
tags => $paged_tags->fetchall,
|
||||||
pager => {$self->pager($paged_tags)},
|
pager => $paged_tags->pager,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +33,7 @@ sub show ($self)
|
||||||
status => 400,
|
status => 400,
|
||||||
) if $v->has_error;
|
) 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;
|
my $kind_id = $v->optional("kind_id")->param;
|
||||||
return $self->render(
|
return $self->render(
|
||||||
|
@ -50,13 +42,9 @@ sub show ($self)
|
||||||
) if $v->has_error;
|
) if $v->has_error;
|
||||||
$search{kind_id} = $kind_id if defined($kind_id);
|
$search{kind_id} = $kind_id if defined($kind_id);
|
||||||
|
|
||||||
my @tags = map +{
|
my @tag_ids = map {$_->{id}} $self->tags_model
|
||||||
id => $_->id,
|
->get(%search)->fetchall->@*;
|
||||||
name => $_->name,
|
my @tags = $self->tags_model->ranked_for_tags(@tag_ids)->fetchall->@*;
|
||||||
kind_id => $_->kind_id,
|
|
||||||
display => $_->display,
|
|
||||||
count => $_->count,
|
|
||||||
}, $self->schema->resultset("TagCountView")->search(\%search)->all;
|
|
||||||
|
|
||||||
return $self->render(
|
return $self->render(
|
||||||
json => {error => "Tag not found."},
|
json => {error => "Tag not found."},
|
||||||
|
@ -83,19 +71,7 @@ sub search ($self)
|
||||||
|
|
||||||
$q =~ s/[%\\_]/\\$&/g;
|
$q =~ s/[%\\_]/\\$&/g;
|
||||||
$q .= '%';
|
$q .= '%';
|
||||||
my %attrs = (
|
my @tags = $self->tags_model->search($q)->fetchall->@*;
|
||||||
$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);
|
|
||||||
|
|
||||||
return $self->render(json => {tags => [@tags]});
|
return $self->render(json => {tags => [@tags]});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
|
use DBD::SQLite::Constants ":dbd_sqlite_string_mode";
|
||||||
|
|
||||||
{
|
{
|
||||||
dsn => "dbi:SQLite:db/pooru.db",
|
dsn => "dbi:SQLite:db/pooru.db",
|
||||||
|
dbi_connect_args => {
|
||||||
|
# Highly recommended to keep this.
|
||||||
|
sqlite_string_mode => DBD_SQLITE_STRING_MODE_UNICODE_STRICT,
|
||||||
|
},
|
||||||
|
|
||||||
secrets => [
|
secrets => [
|
||||||
# Generate with "openssl rand -hex 32".
|
# Generate with "openssl rand -hex 32".
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue