Initial import

This commit is contained in:
Lucas Gabriel Vuotto 2025-04-26 09:41:50 +00:00
commit dd492db92e
37 changed files with 1953 additions and 0 deletions

80
lib/Pooru/API/V0.pm Normal file
View file

@ -0,0 +1,80 @@
package Pooru::API::V0;
use Mojo::Base "Mojolicious", -signatures;
use DBD::SQLite::Constants ":dbd_sqlite_string_mode";
use DBIx::Migration;
use Pooru::Schema;
sub _search_opts ()
{
return {
Media => {
order_by => {-desc => "id"},
rows => 12,
},
TagCountView => {
order_by => [
{-desc => [qw(count kind_id)]},
{-asc => "name"},
],
rows => 1000,
},
TaggedMediaView_media => {
order_by => {-desc => "media_id"},
rows => 12,
},
TaggedMediaView_tags => {
order_by => [
{-desc => "tag_kind_id"},
{-asc => "tag_name"},
],
rows => 1000,
},
};
}
sub startup ($self)
{
$self->moniker("pooru-api-v0");
my $config = $self->plugin("Config");
$self->secrets($config->{secrets});
$self->helper(schema => sub {
state $schema = Pooru::Schema->connect(
$config->{dsn},
undef,
undef,
{
sqlite_string_mode =>
DBD_SQLITE_STRING_MODE_UNICODE_STRICT,
on_connect_call => "use_foreign_keys",
}
);
});
$self->helper(pager => sub ($, $rs) {
return map +($_ => ($rs->pager->$_ and int($rs->pager->$_))),
qw(first_page previous_page current_page next_page
last_page);
});
DBIx::Migration->new({
dsn => $config->{dsn},
dir => $self->app->home->child("migrations")->to_string,
})->migrate;
my $r = $self->routes;
$r->get("/meta")->to("meta#index")->name("meta");
$r->get("/media")->to("media#list")->name("list_media");
$r->get("/media/<media_id:num>")->to("media#show")->name("show_media");
$r->get("/tag")->to("tags#show")->name("show_tag");
$r->get("/tags")->to("tags#list")->name("list_tags");
$r->get("/random/media")->to("random#media")->name("random_media");
$r->get("/random/tag")->to("random#tag")->name("random_tag");
}
1;

View file

@ -0,0 +1,120 @@
package Pooru::API::V0::Controller::Media;
use Mojo::Base "Mojolicious::Controller", -signatures;
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;
return $self->render(json => {
media => [@media],
pager => {$self->pager($paged_media)},
});
}
# 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;
return $self->render(json => {
media => [@media],
pager => {$self->pager($paged_media)},
});
}
sub list ($self)
{
my $v = $self->validation;
my $page = $v->optional("page")->num(1, undef)->param // 1;
return $self->render(
json => {error => "Invalid page number."},
status => 400,
) if $v->has_error;
my @tags = split(" ", $v->optional("tags")->param // "");
return $self->render(
json => {error => "Invalid tags."},
status => 400,
) if $v->has_error;
return @tags == 0 ?
$self->_list_no_tags($page) :
$self->_list_with_tags($page, @tags);
}
sub show ($self)
{
my $v = $self->validation;
my $page = $v->optional("page")->num(1, undef)->param // 1;
return $self->render(
json => {error => "Invalid page number."},
status => 400,
) if $v->has_error;
my $media_id = $self->stash("media_id");
my $media = $self->schema->resultset("Media")
->single({id => $media_id});
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;
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)},
});
}
1;

View file

@ -0,0 +1,22 @@
package Pooru::API::V0::Controller::Meta;
use Mojo::Base "Mojolicious::Controller", -signatures;
sub index ($self)
{
return $self->render(json => {
routes => [
{ path => "/meta", verb => "GET" },
{ path => "/media", verb => "GET" },
{ path => "/media/<media_id:num>", verb => "GET" },
{ path => "/tag", verb => "GET" },
{ path => "/tags", verb => "GET" },
{ path => "/random/media", verb => "GET" },
{ path => "/random/tag", verb => "GET" },
],
});
}
1;

View file

@ -0,0 +1,35 @@
package Pooru::API::V0::Controller::Random;
use Mojo::Base "Mojolicious::Controller", -signatures;
sub _random_entry ($self, $rs)
{
return $self->schema->resultset($rs)
->search(undef, {order_by => 'random()', rows => 1})->single;
}
sub media ($self)
{
my $media = $self->_random_entry("Media");
return $self->render(
json => {error => "Media not found."},
status => 404,
) if !defined($media);
return $self->redirect_to("show_media", media_id => $media->id);
}
sub tag ($self)
{
my $tag = $self->_random_entry("Tag");
return $self->render(
json => {error => "Tag not found."},
status => 404,
) if !defined($tag);
return $self->redirect_to(
$self->url_for("show_tag")->query(id => $tag->id));
}
1;

View file

@ -0,0 +1,69 @@
package Pooru::API::V0::Controller::Tags;
use Mojo::Base "Mojolicious::Controller", -signatures;
my %search_opts = Pooru::API::V0::_search_opts->%*;
sub list ($self)
{
my $v = $self->validation;
my $page = $v->optional("page")->num(1, undef)->param // 1;
return $self->render(
json => {error => "Invalid page number."},
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;
return $self->render(json => {
tags => [@tags],
pager => {$self->pager($paged_tags)},
});
}
sub show ($self)
{
my $v = $self->validation;
my $tag_id_or_name = $v->optional("id")->num(1, undef)->param //
$v->optional("display")->param //
$v->required("name")->param;
return $self->render(
json => {error => "Invalid tag ID or name."},
status => 400,
) if $v->has_error;
my %search = ($v->topic => $tag_id_or_name);
my $kind_id = $v->optional("kind_id")->param;
return $self->render(
json => {error => "Invalid kind ID."},
status => 400,
) 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;
return $self->render(
json => {error => "Tag not found."},
status => 404,
) if @tags == 0;
return $self->render(json => {tags => [@tags]});
}
1;