Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 142 additions & 0 deletions lib/LANraragi/Controller/Api/Stamp.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package LANraragi::Controller::Api::Stamp;
use Mojo::Base 'Mojolicious::Controller';

use Redis;
use Encode;

use LANraragi::Model::Stamp;
use LANraragi::Utils::Generic qw(render_api_response exec_with_lock);


sub get_stamp {

my $self = shift->openapi->valid_input or return;
my $id = $self->stash('id');
my $stamp_id = $self->req->param('stamp_id');

my ( $stamp, $err ) = LANraragi::Model::Stamp::get_stamp($id, $stamp_id);

unless ($stamp) {
render_api_response($self, "get_stamp", "The given stamp does not exist.");
return;
}

$self->render( openapi => { result => $stamp } );
}

sub get_stamps_by_page {

my $self = shift->openapi->valid_input or return;
my $id = $self->stash('id');
my $index = $self->stash('index');

my ( $stamps, $err ) = LANraragi::Model::Stamp::get_stamps_by_page($id, $index);

$self->render( openapi => { result => $stamps } );
}

sub get_stamped_pages {

my $self = shift->openapi->valid_input or return;
my $id = $self->stash('id');

my ( $indexes, $err ) = LANraragi::Model::Stamp::get_stamped_pages( $id );

$self->render( openapi => { result => $indexes } );
}

sub add_stamp {

my $self = shift->openapi->valid_input or return;
my $id = $self->stash('id');
my $index = $self->stash('index');
my $content = $self->req->param('content') || "";
my $position = $self->req->param('position') || "";

unless ( defined $index ) {
return render_api_response( $self, "add_stamp", "Archive page." );
}

return unless exec_with_lock(
$self,
"stamp-write:$id",
"create_stamp",
$id,
sub {
my ( $created_id, $err ) = LANraragi::Model::Stamp::add_stamp( $id, $index, $content, $position );

if ($created_id) {
$self->render(
openapi => {
operation => "add_stamp",
stamp_id => $created_id,
success => 1
}
);
} else {
$self->render(
openapi => {
operation => "add_stamp",
stamp_id => $created_id,
success => 0
}
);
}
}
);
}

sub update_stamp {

my $self = shift->openapi->valid_input or return;
my $id = $self->stash('id');
my $stamp_id = $self->req->param('stamp_id');
my $position = $self->req->param('position') || undef;
my $content = $self->req->param('content') || undef;

return unless exec_with_lock(
$self,
"stamp-write:$stamp_id",
"update_stamp",
$stamp_id,
sub {
my ( $result, $err ) = LANraragi::Model::Stamp::update_stamp( $id, $stamp_id, $content, $position );

if ($result) {
my %stamp = LANraragi::Model::Stamp::get_stamp( $id, $stamp_id );
my $successMessage = "Updated stamp \"$stamp_id\"!";

render_api_response( $self, "update_stamp", undef, $successMessage );
} else {
render_api_response( $self, "update_stamp", $err );
}
}
);

}

sub delete_stamp {

my $self = shift->openapi->valid_input or return;
my $id = $self->stash('id');
my $stamp_id = $self->req->param('stamp_id');

return unless exec_with_lock(
$self,
"stamp-write:$stamp_id",
"delete_stamp",
$stamp_id,
sub {
my ( $result, $err ) = LANraragi::Model::Stamp::remove_stamp($id, $stamp_id);

if ($result) {
render_api_response( $self, "delete_stamp" );
} else {
render_api_response( $self, "delete_stamp", "The given stamp does not exist." );
}
}
);
}

1;

254 changes: 254 additions & 0 deletions lib/LANraragi/Model/Stamp.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
package LANraragi::Model::Stamp;

use v5.36;
use experimental 'try';

use strict;
use warnings;
use utf8;

use Redis;

use LANraragi::Utils::Logging qw(get_logger);
use LANraragi::Utils::Redis qw(redis_encode);


# get_stamp(id, stamp_id)
# Gets the requested stamp.
# Returns the stamp object.
sub get_stamp {
my ( $id, $stamp_id ) = @_;

my $redis = LANraragi::Model::Config->get_redis;
my $logger = get_logger( "Stamps", "lanraragi" );
my $faves_id = "FAVES_" . $id;
my $err = "";

if ( $redis->hexists($faves_id => $stamp_id) ) {
my $content = $redis->hget($faves_id => $stamp_id);
my %stamp = convert_stamp_to_object($stamp_id, $content);

return ( \%stamp, $err );
}

return ();
}

# get_stamps_by_page(id)
# Gets the list of pages that have at least one stamp.
# Returns an array of stamps objects.
# TODO Pagination
sub get_stamps_by_page {
my ( $id, $index ) = @_;

my $redis = LANraragi::Model::Config->get_redis;
my $logger = get_logger( "Stamps", "lanraragi" );
my $faves_id = "FAVES_" . $id;
my $err = "";

my $data = get_stamps_data($redis, $faves_id, $index);
my @stamps = convert_stamps_to_object(%$data);

return ( \@stamps, $err );
}

# get_stamped_pages(id)
# Gets the list of pages that have at least one stamp.
# Returns an array of page numbers.
sub get_stamped_pages {
my ( $id ) = @_;

my $redis = LANraragi::Model::Config->get_redis;
my $logger = get_logger( "Stamps", "lanraragi" );
my $faves_id = "FAVES_" . $id;
my $err = "";

my $fields = $redis->hkeys($faves_id);

my %indexes;

foreach my $field (@$fields) {
# Extract the page number
my ($index) = split(/:/, $field, 2);
$indexes{$index} = 1;
}

my @keys = keys %indexes;

return ( \@keys, $err );
}

# add_stamp(id, key, content, position)
# Add the stamp to the page.
# Returns the stamp key.
sub add_stamp {
my ( $id, $index, $content, $position ) = @_;

my $redis = LANraragi::Model::Config->get_redis;
my $logger = get_logger( "Stamps", "lanraragi" );
my $faves_id = "FAVES_" . $id;
my $err = "";

unless ( $redis->exists($id) ) {
$err = "$id does not exist in the database.";
$logger->error($err);
$redis->quit;
return ( 0, $err );
}

$content = remove_separator($content, "|");
$position = remove_separator($position, "|");

# page:timestamp
# Not sure if this is the right way to make a timestamp in milliseconds, is the only thing that came to my mind
my $key = $index . ":" . int(time() * 1000);

$redis->hset( $faves_id, $key, redis_encode("${position}|${content}") );

$redis->quit;

return ( $key, $err );
}

# update_stamp(id, key, content, position)
# Removes the stamp from the page.
# Returns 1 on success, 0 on failure alongside an error message.
sub update_stamp {
my ( $id, $key, $content, $position ) = @_;

my $logger = get_logger( "Stamps", "lanraragi" );
my $redis = LANraragi::Model::Config->get_redis;
my $err = "";
my $faves_id = "FAVES_" . $id;

my $current = $redis->hget($faves_id => $key);
my @c_content = split(/\|/, $current);

if ( defined $position ) {
$position = remove_separator($position, "|");
} else {
$position = $c_content[0]
}

if ( defined $content ) {
$content = remove_separator($content, "|");
} else {
$content = $c_content[1]
}

if ( $redis->exists($faves_id) ) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exist check comes too late. It should come before $redis->hget($faves_id => $key); and gate updates against nonexistent stamps.

See

if ( $redis->exists($tank_id) ) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This review should probably be moved to #1493 since this PR should be exclusively for the Frontend files.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, just wondered why it was in draft and realized myb

$redis->hset( $faves_id, $key, redis_encode("${position}|${content}") );
$redis->quit;
return ( 1, $err );
}

$err = "$faves_id doesn't exist in the database!";
$logger->warn($err);
$redis->quit;
return ( 0, $err );
}

# remove_stamp(id, key)
# Removes the stamp from the page.
# Returns 1 on success, 0 on failure alongside an error message.
sub remove_stamp {
my ( $id, $key ) = @_;

my $logger = get_logger( "Stamps", "lanraragi" );
my $redis = LANraragi::Model::Config->get_redis;
my $err = "";
my $faves_id = "FAVES_" . $id;

if ( $redis->exists($faves_id) ) {
$redis->hdel($faves_id, $key);
$redis->quit;
return ( 1, $err );
}

$err = "$faves_id doesn't exist in the database!";
$logger->warn($err);
$redis->quit;
return ( 0, $err );
}

# Replaces | for " " in the given string
sub remove_separator {
my ($string, $char) = @_;

# Escape special regex characters in $char
my $escaped_char = quotemeta($char);

# Replace all occurrences with a space
$string =~ s/$escaped_char/ /g;

return $string;
}

# Extracts the stamps related to a page using HSCAN
sub get_stamps_data {
my ($redis, $faves_id, $index) = @_;

my $cursor = 0;
my %result;
my $pattern = "$index:*";
my $logger = get_logger( "Stamps", "lanraragi" );

# Use a Do While until the cursor goes back to 0
do {
my ($next_cursor, $data) = $redis->hscan($faves_id, $cursor, 'MATCH', $pattern);

# Append data to the dictionary
for (my $i = 0; $i < @$data; $i += 2) {
my $field = $data->[$i];
my $value = $data->[$i + 1];

$result{$field} = $value;
}

$cursor = $next_cursor;

} while ($cursor != 0);

return \%result;
}

# Gets the number of stamps in the page
sub size_stamps_by_page {
my ($redis, $faves_id, $index) = @_;

my $data = get_stamps_data($redis, $faves_id, $index);

return scalar keys %$data;
}

# Converts a stamp register to object
sub convert_stamp_to_object {
my ( $stamp_id, $content ) = @_;

# Separate the string and classify the fields
my @x = split(/\|/, $content);
my %stamp = (
id => $stamp_id,
position => $x[0],
content => $x[1],
);

return %stamp;
}

# Converts an array of stamp registers to an array ob objects
sub convert_stamps_to_object {
my (%stamps_raw) = @_;

my @stamps;

# Convert stamp registers to objects
foreach my $i (keys %stamps_raw) {
my %stamp = convert_stamp_to_object($i, $stamps_raw{$i});
push @stamps, \%stamp;
}

return @stamps;
}

1;
Loading
Loading