File Coverage

blib/lib/Story/Interact/WWW.pm
Criterion Covered Total %
statement 26 99 26.2
branch 0 2 0.0
condition 0 15 0.0
subroutine 9 15 60.0
pod 1 1 100.0
total 36 132 27.2


line stmt bran cond sub pod time code
1 2     2   449673 use 5.024000;
  2         19  
2 2     2   11 use strict;
  2         4  
  2         39  
3 2     2   9 use warnings;
  2         3  
  2         119  
4              
5             package Story::Interact::WWW;
6              
7             our $AUTHORITY = 'cpan:TOBYINK';
8             our $VERSION = '0.001004';
9              
10 2     2   12 use constant DISTRIBUTION => 'Story-Interact-WWW';
  2         2  
  2         125  
11              
12 2     2   970 use Mojo::ShareDir;
  2         411920  
  2         126  
13 2     2   20 use Mojo::Base 'Mojolicious', -signatures;
  2         4  
  2         10  
14 2     2   632993 use Mojo::Util qw( xml_escape );
  2         5  
  2         95  
15 2     2   942 use Story::Interact::State ();
  2         705334  
  2         79  
16 2     2   978 use Text::Markdown::Hoedown;
  2         2337  
  2         2688  
17              
18 0     0 1   sub startup ( $self ) {
  0            
  0            
19              
20 0           $self->log->info( 'Story::Interact::State->VERSION = ' . Story::Interact::State->VERSION );
21              
22 0           $self->secrets( [ __PACKAGE__ . '/' . $VERSION ] );
23              
24             # Setup app config, paths, etc.
25 0           $self->plugin( 'Config', { file => 'si_www.conf' } );
26 0           unshift(
27             $self->static->paths->@*,
28             $self->home->rel_file( 'local/public' ),
29             Mojo::ShareDir->new( DISTRIBUTION, 'public' ),
30             );
31 0           unshift(
32             $self->renderer->paths->@*,
33             $self->home->rel_file( 'local/templates' ),
34             Mojo::ShareDir->new( DISTRIBUTION, 'templates' ),
35             );
36              
37             # Story list
38             {
39 0           $self->routes->get( '/' )->to(
40 0     0     cb => sub ($c) {
  0            
41 0           my $stories = $self->config( 'story' );
42             my @keys = sort {
43 0   0       ( $stories->{$a}{title} // 'Story' ) cmp ( $stories->{$b}{title} // 'Story' )
  0   0        
44             } keys %$stories;
45 0           my $html = '
    ';
46 0           for my $k ( @keys ) {
47             $html .= sprintf(
48             '
  • %s
  • ',
    49             xml_escape( $c->url_for( "/story/$k" ) ),
    50 0           xml_escape( $stories->{$k}{title} ),
    51             );
    52             }
    53 0           $html .= '';
    54 0           $c->stash->{title} = 'Stories';
    55 0           $c->stash->{story_list} = $html;
    56 0           $c->render( template => 'index' );
    57             },
    58 0           )->name( 'index' );
    59             }
    60              
    61             # HTML + JavaScript story harness
    62             {
    63 0           $self->routes->get( '/story/:story' )->to(
      0            
    64 0     0     cb => sub ($c) {
      0            
    65 0           my $story_id = $c->stash( 'story' );
    66 0           my $story_config = $self->config( 'story' )->{$story_id};
    67 0           $c->stash->{api} = $c->url_for('/api');
    68 0           $c->stash->{story_id} = $story_id;
    69 0   0       $c->stash->{title} = $story_config->{title} // 'Story';
    70 0   0       $c->stash->{storage_key} = $story_config->{storage_key} // $story_id;
    71 0   0       $c->render( template => $story_config->{template} // 'story' );
    72             },
    73 0           )->name( 'story' );
    74             }
    75              
    76             # API endpoint to get a blank slate state
    77             {
    78 0           $self->routes->get( '/api/state/init' )->to(
      0            
    79 0     0     cb => sub ($c) {
      0            
    80 0           my $blank = Story::Interact::State->new;
    81 0           $c->render( json => { state => $blank->dump } );
    82             },
    83 0           )->name( 'api-state-init' );
    84             }
    85              
    86             # API endpoint to read a page
    87             {
    88 0     0     my $render_html = sub ( $page ) {
      0            
      0            
      0            
      0            
    89 0           my $markdown = join "\n\n", @{ $page->text };
      0            
    90 0           return markdown( $markdown );
    91 0           };
    92 0           $self->routes->post( '/api/story/:story/page/:page' )->to(
    93 0     0     cb => sub ($c) {
      0            
    94 0           my $story_id = $c->stash( 'story' );
    95 0           my $page_id = $c->stash( 'page' );
    96 0           $c->log->info("Request for page `$page_id` from story `$story_id`");
    97 0           my $story_config = $self->config( 'story' )->{$story_id};
    98 0           my $page_source = $story_config->{page_source};
    99 0   0       my $munge_state = $story_config->{state_munge} // sub {};
    100 0   0       my $munge = $story_config->{data_munge} // sub {};
    101 0           my $state = Story::Interact::State->load( $c->req->json( '/state' ) );
    102 0           $munge_state->( $c, $state );
    103            
    104 0 0         if ( $page_id =~ /\A(.+)\?(.+)\z/ms ) {
    105 0           $page_id = $1;
    106 0           require URI::Query;
    107 0           my $params = URI::Query->new( $2 )->hash;
    108 0           $state->params( $params );
    109             }
    110             else {
    111 0           $state->params( {} );
    112             }
    113            
    114 0           my $page = $page_source->get_page( $state, $page_id );
    115 0           my %data = (
    116             %$page,
    117             state => $state->dump,
    118             html => $render_html->( $page ),
    119             );
    120 0           $munge->( \%data, $page, $state );
    121 0           $c->render( json => \%data );
    122             },
    123 0           )->name( 'api-story-page' );
    124             }
    125              
    126             # Done!
    127             }
    128              
    129             1;
    130              
    131             __END__