Skip to content

Commit

Permalink
Annotation editing
Browse files Browse the repository at this point in the history
  • Loading branch information
melmothx committed Oct 2, 2023
1 parent 4ed0aeb commit fbf02a8
Show file tree
Hide file tree
Showing 6 changed files with 214 additions and 16 deletions.
44 changes: 38 additions & 6 deletions lib/AmuseWikiFarm/Controller/Annotate.pm
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,25 @@ sub title :Chained('root') :Args(1) {
if (my $title = $site->titles->find($title_id)) {
# get the uploads
my %updates;
ANNOTATION:
foreach my $ann ($site->annotations) {
my $aid = $ann->annotation_id;
if ($params->{"passed-$aid"}) {
my %single = (value => $params->{"value-$aid"});
my ($upload) = $c->request->upload("file-$aid");
if ($ann->annotation_type eq 'file' and $upload) {
$single{file} = $upload->tempname;
$single{value} = $upload->basename;
}
if ($params->{"wipe-$aid"}) {
$single{remove} = 1;
}
elsif ($ann->annotation_type eq 'file') {
my ($upload) = $c->request->upload("file-$aid");
if ($upload) {
$single{file} = $upload->tempname;
$single{value} = $upload->basename;
}
else {
# skip if there's nothing to do do.
next ANNOTATION;
}
}
$updates{$aid} = \%single;
}
}
Expand All @@ -51,7 +58,7 @@ sub title :Chained('root') :Args(1) {
$c->flash(status_msg => $c->loc("Thanks!"));
}
else {
$c->flash(error_msg => $c->loc($res->{error} || 'Errors'));
$c->flash(error_msg => $c->loc($res->{errors} || 'Errors'));
}
}
return $c->response->redirect($c->uri_for($title->full_uri));
Expand All @@ -60,6 +67,31 @@ sub title :Chained('root') :Args(1) {
$c->detach('/bad_parameters');
}

# this is open to anybody, so we chain from /site
sub annotation :Chained('/site') :PathPart('annotation') :CaptureArgs(0) {
my ($self, $c) = @_;
}

sub download :Chained('annotation') :Args(3) {
my ($self, $c, $title_id, $annotation_id, $filename) = @_;
my $site = $c->stash->{site};
if ($title_id =~ m/\A\d+\z/a and $annotation_id =~ m/\A\d+\z/a) {
log_debug { "Here: $title_id $annotation_id" };
if (my $title = $site->titles->find($title_id)) {
if (my $title_annotation = $title->title_annotations->find({ annotation_id => $annotation_id })) {
if (my $file = $title_annotation->validate_file) {
$c->stash(serve_static_file => $file);
$c->detach($c->view('StaticFile'));
return;
}
}
}
}
$c->detach('/not_found');
}



=encoding utf8
=head1 AUTHOR
Expand Down
10 changes: 9 additions & 1 deletion lib/AmuseWikiFarm/Role/Controller/Text.pm
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ with 'AmuseWikiFarm::Role::Controller::HumanLoginScreen';
use AmuseWikiFarm::Utils::Amuse qw//;
use HTML::Entities qw//;
use AmuseWikiFarm::Log::Contextual;
use Try::Tiny;

sub match :Chained('base') PathPart('') :CaptureArgs(1) {
my ($self, $c, $arg) = @_;
Expand Down Expand Up @@ -191,17 +192,24 @@ sub populate_preamble :Chained('match') :PathPart('') :CaptureArgs(0) {
if (@annotations) {
my %vals;
foreach my $ann ($text->title_annotations) {
$vals{$ann->annotation_id} = $ann->annotation_value;
$vals{$ann->annotation_id} = $ann->valid_value;
}
Dlog_debug { "Values are $_" } \%vals;
foreach my $ann (@annotations) {
if (my $value = $vals{$ann->{id}}) {
$ann->{value} = $value;
if ($ann->{type} eq 'file') {
log_debug { "File annotation is $value" };
$ann->{url} = $c->uri_for($value)
}
my $html = HTML::Entities::encode_entities($value, q{<>&"'});
$html =~ s/\r?\n/<br>/g;
$ann->{html} = $html;
}
}
}

Dlog_debug { "Annotations are $_" } \@annotations;
$c->stash(annotations => \@annotations) if @annotations;
}

Expand Down
73 changes: 69 additions & 4 deletions lib/AmuseWikiFarm/Schema/Result/Title.pm
Original file line number Diff line number Diff line change
Expand Up @@ -716,12 +716,13 @@ use AmuseWikiFarm::Log::Contextual;
use Text::Amuse;
use HTML::Entities qw/decode_entities encode_entities/;
use AmuseWikiFarm::Utils::Amuse qw/cover_filename_is_valid to_json from_json build_full_uri/;
use Path::Tiny qw//;
use Path::Tiny ();
use HTML::LinkExtor; # from HTML::Parser
use HTML::TreeBuilder;
use URI;
use Data::Dumper::Concise;
use AmuseWikiFarm::Utils::Paths;
use File::MimeInfo::Magic qw/mimetype/;
use constant { PAPER_PAGE_SIZE => 2000 };

has selected_formats => (is => 'ro',
Expand Down Expand Up @@ -2029,22 +2030,86 @@ MUSE

sub annotate {
my ($self, $params) = @_;
my $site = $self->site;
my @errors;
ANNOTATION:
foreach my $ann ($self->site->annotations) {
if (my $update = $params->{$ann->annotation_id}) {
my $title_annotation = $self->title_annotations->find_or_create({ annotation => $ann });
my @path = ($site->repo_root, 'annotations');

# create directory and add gitingore
my $gitignore = Path::Tiny::path(@path, '.gitignore');
$gitignore->parent->mkpath;
unless ($gitignore->exists) {
$gitignore->spew_utf8("*\n*/\n");
}

# annotation name
if ($ann->annotation_name =~ m/\A([a-z0-9-]+)\z/) {
push @path, $1;
}
else {
push @errors, "Bad annotation name " . $ann->annotation_name;
next ANNOTATION;
}

# class, path, uri
push @path, $self->f_class;
if (my $relpath = $self->f_archive_rel_path) {
push @path, grep { /\A[a-z0-9-]+\z/ } split(/\//, $relpath);
}
if ($ann->annotation_name eq 'file' and my $file = $update->{file}) {
delete $update->{value};
if (-f $file) {
my $mime = mimetype($file);
my $all_mime = AmuseWikiFarm::Utils::Paths::served_mime_types();
my %mimes = reverse %$all_mime;
if (my $ext = $mimes{$mime}) {
my $storage = Path::Tiny::path(@path, $self->uri . ".$ext");
$storage->parent->mkpath;
if (Path::Tiny::path($file)->copy($storage)) {
log_debug { "File copied in $storage" };
$update->{value} = $storage->relative($path[0]);
}
else {
push @errors, "Could not copy $update->{file}";
next ANNOTATION;
}
}
else {
push @errors, "$update->{file} has invalid mime $mime";
next ANNOTATION;
}
}
else {
push @errors, "$update->{file} not found, cannot add to annotation";
next ANNOTATION;
}
}
my $value = $update->{value};
my $destination = Path::Tiny::path(@path, $self->uri);
$destination->parent->mkpath;
log_debug { "Saving update in $destination" };
if ($update->{remove}) {
$title_annotation->delete;
$destination->remove if $destination->exists;
}
elsif ($update->{value}) {
$title_annotation->update({ annotation_value => $update->{value} });
elsif (defined $value) {
# save the content in a file, so we can reconstruct the tree.
my $destination = Path::Tiny::path(@path, $self->uri);
$destination->spew_utf8($value);
$title_annotation->update({ annotation_value => $value });
}
else {
push @errors, $ann->annotation_name . " was not passed!";
}
}
}
return {
success => !@errors,
errors => join(" / ", @errors),
}
};
}

__PACKAGE__->meta->make_immutable;
Expand Down
58 changes: 57 additions & 1 deletion lib/AmuseWikiFarm/Schema/Result/TitleAnnotation.pm
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,64 @@ __PACKAGE__->belongs_to(

# Created by DBIx::Class::Schema::Loader v0.07049 @ 2023-10-01 08:37:40
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ZK2MrOhm7Wq1pasOD8hBgA
use Path::Tiny ();
use Try::Tiny;
use AmuseWikiFarm::Log::Contextual;

sub valid_value {
my ($self) = @_;
my $annotation = $self->annotation;
my $value = $self->annotation_value;
if ($value and $annotation->annotation_type eq 'file') {
$value = undef;
if (my $file = $self->validate_file) {
log_debug { "Value is $file" };
$value = join("/",
"/annotation/download",
$self->title->id,
$annotation->annotation_id,
$file->basename);
}
}
return $value;
}

sub validate_file {
my ($self) = @_;
my $annotation = $self->annotation;
my $value = $self->annotation_value;
if ($value and $annotation->annotation_type eq 'file') {
my $raw = $value;
log_debug { "File is $raw (not validated)" };
$value = undef;
try {
my $repo_root = Path::Tiny::path($annotation->site->repo_root)->realpath;
my $file = Path::Tiny::path($repo_root, $raw)->realpath;
if ($file->exists) {
# realpaths already resolved above
if ($repo_root->subsumes($file)) {
log_debug { "OK, returning $file" };
$value = $file;
}
else {
log_warn { "$file not under $repo_root" };
}
}
else {
log_warn { "$file does not exists in $repo_root" };
}
}
catch {
my $err = $_;
log_warn { "Error in handling file $raw annotation $err" };
};
return $value;
}
return;
}




# You can replace this text with custom code or comments, and it will be preserved on regeneration
__PACKAGE__->meta->make_immutable;
1;
3 changes: 2 additions & 1 deletion root/src/include/preamble.tt
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,11 @@
<strong>[% lh.site_loc_html(annotation.label) %]</strong>
[% IF annotation.url %]
<a href="[% annotation.url %]">[% loc('Download') %]</a>
[% END %]
[% ELSE %]
<span>
[% annotation.html %]
</span>
[% END %]
</div>
[% END %]
[% END %]
Expand Down
42 changes: 39 additions & 3 deletions t/annotations.t
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ BEGIN {
};

use Data::Dumper;
use Test::More tests => 23;
use Test::More tests => 39;
use AmuseWikiFarm::Schema;
use File::Spec::Functions qw/catfile catdir/;
use lib catdir(qw/t lib/);
Expand Down Expand Up @@ -66,13 +66,17 @@ foreach my $title ($site->titles->all) {
}
ok $site->annotations->count;
ok $annotation->title_annotations->count;
$annotation->delete;

foreach my $type (qw/identifier text/) {
my %annotation_types;

foreach my $type (qw/identifier text file/) {
my $ann = $site->annotations->create({
label => ucfirst($type),
annotation_name => $type,
annotation_type => $type,
});
$annotation_types{$type} = $ann;
}

foreach my $title ($site->titles->all) {
Expand All @@ -81,8 +85,16 @@ foreach my $title ($site->titles->all) {
$update{$ann->annotation_id} = {
value => $ann->label . " Test",
};
if ($ann->annotation_type eq 'file') {
$update{$ann->annotation_id}{file} = 't/files/shot.pdf';
}
}
my $res = $title->annotate(\%update);

# check if the file was written
ok -f "repo/" . $site->id . "/annotations/file/text/" .
$title->f_archive_rel_path . "/" . $title->uri . '.pdf';

ok $res->{success} or diag Dumper($res);
is $title->title_annotations->count, 3;

Expand All @@ -91,12 +103,36 @@ foreach my $title ($site->titles->all) {
}
$res = $title->annotate(\%update);
is $title->title_annotations->count, 3;
ok $res->{success} or diag Dumper($res);

$mech->get_ok('/annotation/download/' . $title->id . '/' .
$annotation_types{file}->annotation_id . '/whatever');

# this is for librarians only
$mech->get('/annotate/title/' . $title->id);
is $mech->status, 401;

# if we have a bad value in the db: do not serve it.
$title->title_annotations->update({ annotation_value => '../../../../../../../../../../../etc/passwd' });
$mech->get('/annotation/download/' . $title->id . '/' .
$annotation_types{file}->annotation_id . '/whatever');
is $mech->status, 404;

ok $res->{success} or diag Dumper($res);
foreach my $up (values %update) {
$up->{remove} = 1;
}
$res = $title->annotate(\%update);
ok $res->{success} or diag Dumper($res);

$res = $title->annotate({
$annotation_types{file}->annotation_id => {
value => 'dummy',
file => 'dummy',
},
});
ok $res->{errors};
diag Dumper($res);
}

ok path($site->repo_root, 'annotations', '.gitignore')->exists;

0 comments on commit fbf02a8

Please sign in to comment.