• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

iftechfoundation / ifcomp / 10332112767

10 Aug 2024 01:55PM UTC coverage: 65.551% (+0.3%) from 65.299%
10332112767

push

github

web-flow
Ask authors for details about computer-generated content (#408)

1254 of 1913 relevant lines covered (65.55%)

45.42 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

71.59
IFComp/lib/IFComp/Controller/Entry.pm
1
package IFComp::Controller::Entry;
2
use Moose;
15✔
3
use namespace::autoclean;
15✔
4

5
BEGIN { extends 'Catalyst::Controller'; }
15✔
6

7
=head1 NAME
8

9
IFComp::Controller::Entry - Catalyst Controller
10

11
=head1 DESCRIPTION
12

13
Catalyst Controller.
14

15
=head1 METHODS
16

17
=cut
18

19
use IFComp::Form::Coauthorship;
15✔
20
use IFComp::Form::Entry;
15✔
21
use IFComp::Form::WithdrawEntry;
15✔
22

23
use MIME::Types;
15✔
24
use Imager;
15✔
25
use DateTime;
15✔
26

27
use Readonly;
15✔
28
Readonly my $MAX_ENTRIES => 3;
29

30
has 'form' => (
31
    is         => 'ro',
32
    isa        => 'IFComp::Form::Entry',
33
    lazy_build => 1,
34
);
35

36
has 'coauthorship_form' => (
37
    is         => 'ro',
38
    isa        => 'IFComp::Form::Coauthorship',
39
    lazy_build => 1,
40
);
41

42
has 'withdrawal_form' => (
43
    is         => 'ro',
44
    isa        => 'IFComp::Form::WithdrawEntry',
45
    lazy_build => 1,
46
);
47

48
has 'entry' => (
49
    is  => 'rw',
50
    isa => 'Maybe[IFComp::Schema::Result::Entry]',
51
);
52

53
sub root : Chained('/') : PathPart('entry') : CaptureArgs(0) {
54
    my ( $self, $c ) = @_;
25✔
55

56
    unless ( $c->user ) {
25✔
57
        $c->res->redirect('/auth/login');
×
58
        $c->detach();
×
59
    }
60

61
    my @entries = $c->user->get_object->current_comp_entries;
25✔
62

63
    my $current_comp = $c->model('IFCompDB::Comp')->current_comp;
25✔
64

65
    $c->stash(
25✔
66
        entries      => \@entries,
67
        current_comp => $current_comp,
68
    );
69

70
}
15✔
71

72
sub fetch_entry : Chained('root') : PathPart('') : CaptureArgs(1) {
73
    my ( $self, $c, $id ) = @_;
10✔
74

75
    my $entry = $c->model('IFCompDB::Entry')->find($id);
10✔
76
    if ( $entry && $entry->author->id eq $c->user->get_object->id ) {
10✔
77
        $c->stash->{entry} = $entry;
10✔
78
        $self->entry($entry);
10✔
79
    }
80
    else {
81
        $c->res->redirect('/');
×
82
    }
83
}
15✔
84

85
sub list : Chained('root') : PathPart('') : Args(0) {
86
    my ( $self, $c ) = @_;
8✔
87

88
    $self->_process_coauthorship_form($c);
8✔
89

90
    $c->stash( coauthorship_form => $self->coauthorship_form );
7✔
91
}
15✔
92

93
sub preview : Chained('root') : PathPart('preview') : Args(0) {
94
    my ( $self, $c ) = @_;
×
95
}
15✔
96

97
sub create : Chained('root') : PathPart('create') : Args(0) {
98
    my ( $self, $c ) = @_;
5✔
99

100
    unless ( $c->stash->{current_comp}->status eq 'accepting_intents' ) {
5✔
101
        $c->res->redirect( $c->uri_for_action('/entry/list') );
×
102
    }
103

104
    my %new_result_args = (
105
        comp   => $c->stash->{current_comp},
106
        author => $c->user->get_object->id,
5✔
107
    );
108

109
    $c->stash( entry =>
5✔
110
            $c->model('IFCompDB::Entry')->new_result( \%new_result_args ) );
111
    if ( $self->_process_form($c) ) {
5✔
112
        $c->user->send_author_reminder_email;
2✔
113
        $c->res->redirect( $c->uri_for_action('/entry/list') );
2✔
114
    }
115
}
15✔
116

117
sub update : Chained('fetch_entry') : PathPart('update') : Args(0) {
118
    my ( $self, $c ) = @_;
10✔
119

120
    my $status = $c->stash->{current_comp}->status;
10✔
121
    unless ( ( $status eq 'accepting_intents' )
10✔
122
        || ( $status eq 'closed_to_intents' )
123
        || ( $status eq 'open_for_judging' ) )
124
    {
125
        $c->res->redirect( $c->uri_for_action('/entry/list') );
×
126
    }
127

128
    $self->_process_form($c);
10✔
129

130
    $self->_process_withdrawal_form($c);
10✔
131

132
    $c->stash( collabs => [ $c->stash->{'entry'}->entry_coauthors()->all ] );
10✔
133
}
15✔
134

135
sub cover : Chained('fetch_entry') : PathPart('cover') : Args(0) {
136
    my ( $self, $c ) = @_;
×
137

138
    my $file = $c->stash->{entry}->cover_file;
×
139
    if ( -e $file ) {
×
140
        my $image_data = $file->slurp;
×
141
        $c->res->headers->header( 'Cache-Control' => 'max-age=86400' );
×
142
        if ( $file->basename =~ /png$/ ) {
×
143
            $c->res->content_type('image/png');
×
144
        }
145
        else {
146
            $c->res->content_type('image/jpeg');
×
147
        }
148
        $c->res->body($image_data);
×
149
    }
150
    else {
151
        $c->detach('/error_404');
×
152
    }
153
}
15✔
154

155
sub current_comp_test : Chained('fetch_entry') : PathPart('test') : Args(0) {
156
    my ( $self, $c ) = @_;
×
157
}
15✔
158

159
sub transcript_list : Chained('fetch_entry') : PathPart('transcript') :
160
    Args(0) {
161
    my ( $self, $c ) = @_;
×
162

163
    my $session_rs = $c->model('IFCompDB::Transcript')->search(
164
        { entry => $c->stash->{entry}->id, },
165
        {   select => [
×
166
                'session',
167
                { max => 'inputcount', -as => 'command_count' },
168
                { min => 'timestamp',  -as => 'start_time' },
169
            ],
170
            as       => [qw( session_id command_count start_time )],
171
            group_by => 'session',
172
            order_by => 'start_time',
173
        },
174
    );
175

176
    $c->stash->{session_rs} = $session_rs;
×
177

178
}
15✔
179

180
sub transcript : Chained('fetch_entry') : PathPart('transcript') : Args(1) {
181
    my ( $self, $c, $session_id ) = @_;
×
182

183
    my @inputs;
×
184
    my @output_sets;
185

186
    my $transcript_rs = $c->model('IFCompDB::Transcript')->search(
187
        {   session => $session_id,
188
            entry   => $c->stash->{entry}->id,
189
            window  => 0,
×
190
        },
191
        { order_by => 'timestamp', },
192
    );
193

194
    my $current_input_count = 0;
×
195
    @output_sets = ( [] );
×
196
    while ( my $transcript = $transcript_rs->next ) {
×
197
        if ( $current_input_count != $transcript->inputcount ) {
×
198
            $current_input_count = $transcript->inputcount;
×
199
            push @inputs,      $transcript->input;
×
200
            push @output_sets, [];
×
201
        }
202
        push @{ $output_sets[-1] }, $transcript->output;
×
203
    }
204

205
    $c->stash(
206
        inputs      => \@inputs,
×
207
        output_sets => \@output_sets,
208
    );
209

210
}
15✔
211

212
sub feedback : Chained('root') : PathPart('feedback') : Args(0) {
213
    my ( $self, $c ) = @_;
2✔
214

215
    my $author_id = $c->user->get_object->id;
2✔
216

217
    # Fetch a resultset of feedback for all the current user's entries,
218
    # limited to competitions that have closed,
219
    # ordered by comp-year (descending) and entry title.
220
    my $dtf         = $c->model('IFCompDB')->storage->datetime_parser;
2✔
221
    my $feedback_rs = $c->model('IFCompDB::Feedback')->search(
2✔
222
        {   author      => $author_id,
223
            comp_closes => { '<',  $dtf->format_datetime( DateTime->now ) },
224
            text        => { '!=', undef },
225
        },
226
        {   join     => { entry => 'comp' },
227
            order_by => [ 'comp.year desc', 'entry.title' ],
228
        }
229
    );
230

231
    $c->stash( feedback_rs => $feedback_rs );
2✔
232
}
15✔
233

234
sub _build_form {
235
    return IFComp::Form::Entry->new;
2✔
236
}
237

238
sub _build_coauthorship_form {
239
    return IFComp::Form::Coauthorship->new;
2✔
240
}
241

242
sub _build_withdrawal_form {
243
    return IFComp::Form::WithdrawEntry->new;
1✔
244
}
245

246
sub _process_form {
247
    my ( $self, $c ) = @_;
15✔
248

249
    $c->stash(
15✔
250
        form     => $self->form,
251
        template => 'entry/update.tt',
252
    );
253

254
    my $params_ref = $c->req->parameters;
15✔
255
    foreach (qw( main_upload walkthrough_upload cover_upload )) {
15✔
256
        my $param = "entry.$_";
45✔
257
        if ( $params_ref->{$param} ) {
45✔
258
            $params_ref->{$param} = $c->req->upload($param);
6✔
259
        }
260
    }
261

262
    my $entry = $c->stash->{entry};
15✔
263
    if ( $self->form->process( item => $entry, params => $params_ref, ) ) {
15✔
264

265
        # Handle files
266
        for my $upload_type (qw( main walkthrough cover )) {
5✔
267
            my $deletion_param = "entry.${upload_type}_delete";
15✔
268
            if ( $params_ref->{$deletion_param} ) {
15✔
269
                my $method = "${upload_type}_file";
1✔
270
                if ( my $file = $entry->$method ) {
1✔
271
                    $entry->$method->remove;
1✔
272
                    my $clearer = "clear_${upload_type}_file";
1✔
273
                    $entry->$clearer;
1✔
274
                }
275
                if ( $upload_type eq 'cover' ) {
1✔
276

277
                    # If clearing cover art, also clear the web-cover.
278
                    $entry->web_cover_file->remove;
1✔
279
                    $entry->clear_web_cover_file;
1✔
280
                }
281
            }
282

283
            my $upload_param = "entry.${upload_type}_upload";
15✔
284
            if ( my $upload = $params_ref->{$upload_param} ) {
15✔
285
                my $file_method = "${upload_type}_file";
5✔
286
                if ( my $file = $entry->$file_method ) {
5✔
287
                    $file->remove;
1✔
288
                }
289
                my $directory_method = "${upload_type}_directory";
5✔
290
                my $result =
5✔
291
                    $upload->copy_to(
292
                    $entry->$directory_method->file( $upload->basename ) );
293
                unless ($result) {
5✔
294
                    die "Failed to write the $upload_type file from "
×
295
                        . $upload->tempname . " to "
296
                        . $entry->$directory_method->file( $upload->basename )
297
                        . ": $!";
298
                }
299
                my $clearer = "clear_${upload_type}_file";
5✔
300
                $entry->$clearer;
5✔
301

302
                # Now do extra work specific to uploaded file types.
303
                if ( $upload_type eq 'main' ) {
5✔
304

305
                    # This is the main game file! Process it into the entry's
306
                    # content directory.
307
                    $entry->update_content_directory;
2✔
308
                    if ( my $note = $self->form->field('note')->value ) {
2✔
309
                        $entry->add_to_entry_updates(
×
310
                            {   note => $note,
311
                                time => DateTime->now( time_zone => 'UTC' ),
312
                            }
313
                        );
314
                    }
315
                }
316
                elsif ( $upload_type eq 'cover' ) {
317

318
                    # Cover art! Preserve as-is, but also make a possibly
319
                    # scaled-down web copy.
320
                    $entry->create_web_cover_file;
2✔
321
                }
322
            }
323
        }
324

325
        my $genai_data = $params_ref->{"entry.genai"};
5✔
326
        my $value      = 0;
5✔
327
        if ( ref($genai_data) eq 'ARRAY' ) {
5✔
328
            foreach my $flag (@$genai_data) {
×
329
                $value += $entry->convert_genai_to_value($flag);
×
330
            }
331
        }
332
        else {
333
            $value = $entry->convert_genai_to_value($genai_data);
5✔
334
        }
335

336
        if ( $entry->genai_state != $value ) {
5✔
337
            $entry->genai_state($value);
2✔
338
            $entry->update();
2✔
339
        }
340

341
        if ( $params_ref->{'regenerate_coauthor_code'} ) {
5✔
342
            $entry->reset_coauthor_code();
×
343
        }
344

345
        my @removals = $params_ref->{"remove_coauthor"};
5✔
346
        for my $coauthor_id (@removals) {
5✔
347
            $entry->entry_coauthors->search( { coauthor_id => $coauthor_id } )
5✔
348
                ->delete();
349
        }
350

351
        $c->flash->{entry_updated} = 1;
5✔
352
        return 1;
5✔
353
    }
354
    else {
355
        return 0;
10✔
356
    }
357
}
358

359
sub _process_coauthorship_form {
360
    my ( $self, $c ) = @_;
8✔
361

362
    my $user   = $c->user->get_object;
8✔
363
    my $params = $c->req->parameters;
8✔
364
    my $code   = $params->{"coauthorship.add_coauthor_code"};
8✔
365
    my $ec_rs  = $c->model('IFCompDB::EntryCoauthor');
8✔
366

367
    if ( defined($code) && $code ne "" ) {
8✔
368
        if ( $self->coauthorship_form->process( params => $params ) ) {
1✔
369

370
            my $entry =
1✔
371
                $c->model('IFCompDB::Entry')
372
                ->find( { coauthor_code => $code } );
373

374
            unless ( defined($entry) ) {
1✔
375
                $c->stash( coauthor_error =>
×
376
                        "The code '$code' does not belong to any game", );
377
                return;
×
378
            }
379

380
            if ( $entry->author->id == $user->id ) {
1✔
381
                $c->stash( coauthor_error =>
×
382
                        "You can't be a coauthor of your own game", );
383
                return;
×
384
            }
385

386
            if ( $ec_rs->find( { entry => $entry, coauthor => $user } ) ) {
1✔
387
                $c->stash( coauthor_error =>
×
388
                        "You are already a coauthor for that game", );
389
                return;
×
390
            }
391

392
            my $settings = { coauthor => $user, };
1✔
393
            if ( $params->{"coauthorship.pseudonym"} ne "" ) {
1✔
394
                $settings->{"pseudonym"} =
395
                    $params->{"coauthorship.pseudonym"};
×
396
            }
397
            if (   $params->{"coauthorship.reveal_pseudonym"}
1✔
398
                && $params->{"coauthorship.reveal_pseudonym"} eq "on" )
399
            {
400
                $settings->{"reveal_pseudonym"} = 1;
×
401
            }
402
            $entry->add_to_entry_coauthors($settings);
1✔
403

404
            $c->res->redirect( $c->uri_for_action('/entry/list') );
1✔
405
            $c->detach();
1✔
406
        }
407
        else {
408
            $c->stash( coauthor_error =>
×
409
                    join( "\n", $self->coauthorship_form->errors ) );
410
        }
411
    }
412
    elsif ( $params->{"coauthorship.remove.submit"} ) {
413
        my $entry_id = $params->{"coauthorship.remove"};
×
414
        $user->entry_coauthors->search( { entry_id => $entry_id } )->delete();
×
415
        $c->res->redirect( $c->uri_for_action('/entry/list') );
×
416
        $c->detach();
×
417
    }
418
}
419

420
sub _process_withdrawal_form {
421
    my ( $self, $c ) = @_;
10✔
422

423
    if ( $self->withdrawal_form->process( params => $c->req->parameters, ) ) {
10✔
424
        $c->stash->{entry}->delete;
1✔
425
        $c->flash->{entry_withdrawn} = $c->stash->{entry}->title;
1✔
426
        $c->res->redirect( $c->uri_for_action('/entry/list') );
1✔
427
    }
428

429
    $c->stash->{withdrawal_form} = $self->withdrawal_form;
10✔
430

431
}
432

433
=encoding utf8
434

435
=head1 AUTHOR
436

437
Jason McIntosh
438

439

440

441
=cut
442

443
__PACKAGE__->meta->make_immutable;
444

445
1;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc