Changeset 59 in bookmarks


Ignore:
Timestamp:
08/23/13 15:35:17 (11 years ago)
Author:
peter
Message:
  • converted app.psgi from using a CGI::Application::Dispatch dispatcher to a Path::Router dispatcher
  • created a more pure PSGI BookmarkController to replace the CGI::Application-based BookmarkApp
  • added an access log to the start script
Location:
trunk
Files:
2 edited
1 copied

Legend:

Unmodified
Added
Removed
  • trunk/BookmarkController.pm

    r58 r59  
    1 package BookmarkApp; 
    2 use strict; 
    3 use warnings; 
    4 use base qw{CGI::Application}; 
    5  
    6 use CGI::Application::Plugin::TT; 
     1package BookmarkController; 
     2use Moose; 
    73 
    84use Encode; 
     
    117use Bookmarks; 
    128use URI; 
    13  
    14 sub setup { 
    15     my $self = shift; 
    16     $self->mode_param(path_info => 1); 
    17     $self->run_modes([qw{ 
    18         list 
    19         feed 
    20         view 
    21         view_field 
    22         create 
    23         edit 
    24     }]); 
    25  
    26     my $url = $self->query->url; 
     9use Template; 
     10 
     11has bookmarks => ( 
     12    is => 'rw', 
     13    handles => [qw{get_bookmark}], 
     14); 
     15has base_uri => ( 
     16    is => 'ro', 
     17    builder => '_build_base_uri', 
     18    lazy => 1, 
     19); 
     20has request => ( 
     21    is => 'ro', 
     22); 
     23 
     24sub _build_base_uri { 
     25    my $self = shift; 
     26    my $url = $self->request->base; 
     27 
    2728    $url .= '/' unless $url =~ m{/$}; 
    28     my $base_uri = URI->new($url); 
    29  
    30     my $bookmarks = Bookmarks->new({ 
    31         dbname   => $self->param('dbname'), 
    32         base_uri => $base_uri, 
    33     }); 
    34  
    35     $self->param( 
    36         base_uri  => $base_uri, 
    37         bookmarks => $bookmarks, 
    38     ); 
    39 } 
    40  
    41 sub _bookmarks { $_[0]->param('bookmarks') } 
     29    return URI->new($url); 
     30} 
    4231 
    4332sub _get_list_links { 
     
    9685    for my $link (@links) { 
    9786        $link->{rel}  = $link->{type} eq $self_type ? 'self' : 'alternate'; 
    98         $link->{href} = URI->new_abs($link->{path} || '', $self->param('base_uri')); 
     87        $link->{href} = URI->new_abs($link->{path} || '', $self->base_uri); 
    9988        $link->{href}->query_form($link->{query}); 
    10089    } 
     
    10392} 
    10493 
     94sub find_or_new { 
     95    my $self = shift; 
     96 
     97    my $bookmark = $self->bookmarks->get_bookmark({ uri => $self->request->param('uri') }); 
     98    if ($bookmark) { 
     99        # redirect to the view of the existing bookmark 
     100        return [301, [Location => $bookmark->bookmark_uri], []]; 
     101    } else { 
     102        # bookmark was not found; show the form to create a new bookmark 
     103        my $template = Template->new; 
     104        $template->process( 
     105            'bookmark.tt', 
     106            { 
     107                uri   => $self->request->param('uri'), 
     108                title => $self->request->param('title') || '', 
     109            }, 
     110            \my $output, 
     111        ); 
     112        return [404, ['Content-Type' => 'text/html; charset=UTF-8'], [$output]]; 
     113    } 
     114} 
     115 
    105116sub list { 
    106117    my $self = shift; 
    107     my $q = $self->query; 
    108  
    109     # check for a uri param, and if there is one present, 
    110     # see if a bookmark for that URI already exists 
    111     if (defined(my $uri = $q->param('uri'))) { 
    112         my $bookmark = $self->_bookmarks->get_bookmark({ uri => $uri }); 
    113         if ($bookmark) { 
    114             # redirect to the view of the existing bookmark 
    115             $self->header_type('redirect'); 
    116             $self->header_props( 
    117                 -uri => $q->url . '/' . $bookmark->{id}, 
    118             ); 
    119             return; 
    120         } else { 
    121             # bookmark was not found; show the form to create a new bookmark 
    122             $bookmark->{uri} = $uri; 
    123             $bookmark->{title} = $q->param('title'); 
    124             $self->header_props( 
    125                 -type    => 'text/html', 
    126                 -charset => 'UTF-8', 
    127                 -status  => 404, 
    128             ); 
    129             return $self->tt_process( 
    130                 'bookmark.tt', 
    131                 $bookmark, 
    132             ); 
    133         } 
    134     } 
    135118 
    136119    # list all the bookmarks  
    137     my $mtime = $self->_bookmarks->get_last_modified_time; 
    138  
    139     my $format = $q->param('format') || 'html'; 
    140  
    141     my @tags = grep { $_ ne '' } $q->param('tag'); 
    142     my $query = $q->param('q'); 
    143     my $limit = $q->param('limit'); 
    144     my $offset = $q->param('offset'); 
    145     my @resources = $self->_bookmarks->get_bookmarks({ 
     120    my $mtime = $self->bookmarks->get_last_modified_time; 
     121 
     122    my $format = $self->request->param('format') || 'html'; 
     123 
     124    my @tags = grep { $_ ne '' } $self->request->param('tag'); 
     125    my $query = $self->request->param('q'); 
     126    my $limit = $self->request->param('limit'); 
     127    my $offset = $self->request->param('offset'); 
     128    my @resources = $self->bookmarks->get_bookmarks({ 
    146129        query  => $query, 
    147130        tag    => \@tags, 
     
    149132        offset => $offset, 
    150133    }); 
    151     my @all_tags = $self->_bookmarks->get_tags({ selected => $tags[0] }); 
    152     my @cotags = $self->_bookmarks->get_cotags({ 
     134    my @all_tags = $self->bookmarks->get_tags({ selected => $tags[0] }); 
     135    my @cotags = $self->bookmarks->get_cotags({ 
    153136        query  => $query, 
    154137        tag    => \@tags, 
     
    158141 
    159142    if ($format eq 'json') { 
    160         $self->header_props( 
    161             -type    => 'application/json', 
    162             -charset => 'UTF-8', 
    163         ); 
    164         return decode_utf8( 
     143        my $json = decode_utf8( 
    165144            JSON->new->utf8->convert_blessed->encode({ 
    166145                bookmarks => \@resources, 
    167146            }) 
    168147        ); 
     148        return [200, ['Content-Type' => 'application/json; charset=UTF-8'], [$json]]; 
    169149    } elsif ($format eq 'xbel') { 
    170150        require XML::XBEL; 
     
    193173        } 
    194174 
    195         $self->header_props( 
    196             -type    => 'application/xml', 
    197             -charset => 'UTF-8', 
    198         ); 
    199  
    200         return $xbel->toString; 
     175        return [200, ['Content-Type' => 'application/xml; charset=UTF-8'], [$xbel->toString]]; 
    201176    } elsif ($format eq 'text') { 
    202         $self->header_props( 
    203             -type    => 'text/uri-list', 
    204             -charset => 'UTF-8', 
    205         ); 
    206         return join '',  
     177        my $text = join '',  
    207178            map { 
    208179                sprintf "# %s\n# Tags: %s\n%s\n", 
     
    211182                $_->uri 
    212183            } @resources; 
     184        return [200, ['Content-Type' => 'text/uri-list; charset=UTF-8'], [$text]]; 
    213185    } elsif ($format eq 'csv') { 
    214186        require Text::CSV::Encoded; 
    215         my $csv = Text::CSV::Encoded->new({ encoding => 'utf8' }); 
     187        my $csv = Text::CSV::Encoded->new({ encoding_out => 'utf8' }); 
    216188        my $text = qq{id,uri,title,tags,ctime,mtime\n}; 
    217189        for my $bookmark (@resources) { 
     
    231203        $dt =~ s/[^\d]//g; 
    232204 
    233         $self->header_props( 
    234             -type       => 'text/csv', 
    235             -charset    => 'UTF-8', 
    236             -attachment => 'bookmarks-' . join('_', @tags) . "-$dt.csv", 
     205        my $filename = sprintf( 
     206            'bookmarks-%s-%s.csv', 
     207            join('_', @tags), 
     208            $dt, 
    237209        ); 
    238         return $text; 
    239     } else { 
    240         $self->header_props( 
    241             -type    => 'text/html', 
    242             -charset => 'UTF-8', 
    243         ); 
    244  
    245         # set the base URL, adding a trailing slash if needed 
    246         my $base_url = $q->url; 
    247         $base_url .= '/' if $base_url =~ m{/bookmarks$}; 
    248  
    249         return $self->tt_process( 
     210 
     211        return [200, ['Content-Type' => 'text/csv; charset=UTF-8', 'Content-Disposition' => sprintf('attachement; filename="%s"', $filename)], [$text]]; 
     212    } else { 
     213        my $template = Template->new; 
     214 
     215        $template->process( 
    250216            'list.tt', 
    251217            { 
    252                 base_url     => $base_url, 
     218                base_url     => $self->base_uri, 
    253219                title        => $title, 
    254220                query        => $query, 
     
    260226                resources    => \@resources, 
    261227            }, 
     228            \my $output, 
    262229        ); 
     230        return [200, ['Content-Type' => 'text/html; charset=UTF-8'], [$output]]; 
    263231    } 
    264232} 
     
    266234sub feed { 
    267235    my $self = shift; 
    268     my $q = $self->query; 
    269  
    270     my $query = $q->param('q'); 
    271     my @tags = grep { $_ ne '' } $q->param('tag'); 
     236 
     237    my $query = $self->request->param('q'); 
     238    my @tags = grep { $_ ne '' } $self->request->param('tag'); 
    272239    my $title = 'Bookmarks' . (@tags ? " tagged as " . join(' & ', @tags) : ''); 
    273240 
     
    283250    $feed->title($title); 
    284251 
    285     my $feed_uri = URI->new_abs('feed', $self->param('base_uri')); 
     252    my $feed_uri = URI->new_abs('feed', $self->base_uri); 
    286253    $feed_uri->query_form(tag => \@tags); 
    287254    $feed->id($feed_uri->canonical); 
     
    296263 
    297264    # construct a feed from the most recent 12 bookmarks 
    298     for my $bookmark ($self->_bookmarks->get_bookmarks({ query => $query, tag => \@tags, limit => 12 })) { 
     265    for my $bookmark ($self->bookmarks->get_bookmarks({ query => $query, tag => \@tags, limit => 12 })) { 
    299266        my $entry = XML::Atom::Entry->new; 
    300267        $entry->id($bookmark->bookmark_uri->canonical); 
     
    323290    } 
    324291 
    325     $self->header_props( 
    326         -type => 'application/atom+xml', 
    327         -charset => 'UTF-8', 
    328     ); 
    329     return $feed->as_xml; 
     292    return [200, ['Content-Type' => 'application/atom+xml; charset=UTF-8'], [$feed->as_xml]]; 
    330293} 
    331294 
    332295sub view { 
    333     my $self = shift; 
    334     my $id = $self->param('id'); 
    335     my $format = $self->query->param('format') || 'html'; 
    336  
    337     my $bookmark = $self->_bookmarks->get_bookmark({ id => $id }); 
     296    my ($self, $id) = @_; 
     297 
     298    my $format = $self->request->param('format') || 'html'; 
     299 
     300    my $bookmark = $self->get_bookmark({ id => $id }); 
    338301    if ($bookmark) { 
    339302        # check If-Modified-Since header to return cache response 
    340         if ($self->query->env->{HTTP_IF_MODIFIED_SINCE}) { 
    341             my $cache_time = str2time($self->query->env->{HTTP_IF_MODIFIED_SINCE}); 
     303        if ($self->request->env->{HTTP_IF_MODIFIED_SINCE}) { 
     304            my $cache_time = str2time($self->request->env->{HTTP_IF_MODIFIED_SINCE}); 
    342305            if ($bookmark->mtime <= $cache_time) { 
    343                 $self->header_type('redirect'); 
    344                 $self->header_props( 
    345                     -status => 304, 
    346                 ); 
    347                 return; 
     306                return [304, [], []]; 
    348307            } 
    349308        } 
     309        my $last_modified = time2str($bookmark->mtime); 
    350310         
    351311        if ($format eq 'json') { 
    352             $self->header_props( 
    353                 -type    => 'application/json', 
    354                 -charset => 'UTF-8', 
    355             ); 
    356             return decode_utf8(JSON->new->utf8->convert_blessed->encode($bookmark)); 
     312            my $json = decode_utf8(JSON->new->utf8->convert_blessed->encode($bookmark)); 
     313            return [200, ['Content-Type' => 'application/json; charset=UTF-8', 'Last-Modified' => $last_modified], [$json]]; 
    357314        } else { 
    358315            # display the bookmark form for this bookmark 
     
    360317            $bookmark->{created} = "Created " . localtime($bookmark->ctime); 
    361318            $bookmark->{created} .= '; Updated ' . localtime($bookmark->mtime) unless $bookmark->ctime == $bookmark->mtime; 
    362             $self->header_props( 
    363                 -type    => 'text/html', 
    364                 -charset => 'UTF-8', 
    365                 -Last_Modified => time2str($bookmark->mtime), 
    366             ); 
    367             return $self->tt_process( 
     319            my $template = Template->new; 
     320            $template->process( 
    368321                'bookmark.tt', 
    369322                $bookmark, 
     323                \my $output, 
    370324            ); 
    371         } 
    372     } else { 
    373         $self->header_props( 
    374             -type    => 'text/html', 
    375             -charset => 'UTF-8', 
    376             -status  => 404, 
    377         ); 
    378         return "Bookmark $id Not Found"; 
     325            return [200, ['Content-Type' => 'text/html; charset=UTF-8', 'Last-Modified' => $last_modified], [$output]]; 
     326        } 
     327    } else { 
     328        return [404, ['Content-Type' => 'text/plain; charset=UTF-8'], ["Boomark $id not found"]]; 
    379329    } 
    380330} 
    381331 
    382332sub view_field { 
    383     my $self = shift; 
    384     my $id = $self->param('id'); 
    385     my $field = $self->param('field'); 
    386  
    387     my $bookmark = $self->_bookmarks->get_bookmark({ id => $id }); 
     333    my ($self, $id, $field) = @_; 
     334 
     335    my $bookmark = $self->bookmarks->get_bookmark({ id => $id }); 
    388336    if ($bookmark) { 
    389337        # respond with just the requested field as plain text 
     
    391339        if ($@) { 
    392340            if ($@ =~ /Can't locate object method/) { 
    393                 $self->header_props( 
    394                     -type    => 'text/plain', 
    395                     -charset => 'UTF-8', 
    396                     -status  => 404, 
    397                 ); 
    398                 return qq{"$field" is not a valid bookmark data field}; 
     341                return [404, ['Content-Type' => 'text/plain; charset=UTF-8'], [qq{"$field" is not a valid bookmark data field}]]; 
    399342            } else { 
    400343                die $@; 
    401344            } 
    402345        } 
    403         $self->header_props( 
    404             -type    => 'text/plain', 
    405             -charset => 'UTF-8', 
    406         ); 
    407         return ref $value eq 'ARRAY' ? join(' ', @{ $value }) : $value; 
    408     } else { 
    409         $self->header_props( 
    410             -type    => 'text/html', 
    411             -charset => 'UTF-8', 
    412             -status  => 404, 
    413         ); 
    414         return "Bookmark $id Not Found"; 
     346        return [200, ['Content-Type' => 'text/plain; charset=UTF-8'], [ref $value eq 'ARRAY' ? join(' ', @{ $value }) : $value]]; 
     347    } else { 
     348        return [404, ['Content-Type' => 'text/plain; charset=UTF-8'], ["Boomark $id not found"]]; 
    415349    } 
    416350} 
     
    418352sub create { 
    419353    my $self = shift; 
    420     my $q = $self->query; 
    421     my $uri = $q->param('uri'); 
    422     my $title = $q->param('title'); 
    423     my @tags = split ' ', $q->param('tags'); 
    424     my $bookmark = $self->_bookmarks->add({ 
     354 
     355    my $uri   = $self->request->param('uri'); 
     356    my $title = $self->request->param('title'); 
     357    my @tags  = split ' ', $self->request->param('tags'); 
     358 
     359    my $bookmark = $self->bookmarks->add({ 
    425360        uri   => $uri, 
    426361        title => $title, 
     
    428363    }); 
    429364 
    430 =begin 
    431  
    432     my $location = URI->new($q->url); 
    433     $location->query_form(uri => $uri) if defined $q->url_param('uri'); 
    434     $location->fragment('updated'); 
    435  
    436 =cut 
    437  
    438     # return to the form 
    439     $self->header_type('redirect'); 
    440     $self->header_props( 
    441         -uri => $bookmark->bookmark_uri->canonical, 
    442         -status => 303, 
    443     ); 
     365    #TODO: not RESTful; the proper RESTful response would be a 201 
     366    return [303, ['Location' => $bookmark->bookmark_uri->canonical], []]; 
    444367} 
    445368 
    446369sub edit { 
    447370    my $self = shift; 
    448     my $q = $self->query; 
    449     my $id = $self->param('id'); 
    450  
    451     my $bookmark = $self->_bookmarks->get_bookmark({ id => $id }); 
     371    my $id = shift; 
     372 
     373    my $bookmark = $self->bookmarks->get_bookmark({ id => $id }); 
    452374    if ($bookmark) { 
    453375        # update the URI, title, and tags 
    454         $bookmark->uri($q->param('uri')); 
    455         $bookmark->title($q->param('title')); 
    456         $bookmark->tags([ split ' ', $q->param('tags') || '' ]); 
     376        $bookmark->uri($self->request->param('uri')); 
     377        $bookmark->title($self->request->param('title')); 
     378        $bookmark->tags([ split ' ', $self->request->param('tags') || '' ]); 
    457379 
    458380        # write to the database 
    459         $self->_bookmarks->update($bookmark); 
    460  
    461         # return to the form 
    462         $self->header_type('redirect'); 
    463         $self->header_props( 
    464             -uri => $bookmark->bookmark_uri->canonical, 
    465             -status => 303, 
    466         ); 
    467     } else { 
    468         $self->header_props( 
    469             -type    => 'text/html', 
    470             -charset => 'UTF-8', 
    471             -status  => 404, 
    472         ); 
    473         return "Bookmark $id Not Found"; 
     381        $self->bookmarks->update($bookmark); 
     382 
     383        #TODO: not RESTful; proper response would be a 200 
     384        return [303, ['Location' => $bookmark->bookmark_uri->canonical], []]; 
     385    } else { 
     386        return [404, ['Content-Type' => 'text/plain; charset=UTF-8'], ["Boomark $id not found"]]; 
    474387    } 
    475388} 
  • trunk/app.psgi

    r55 r59  
    44use YAML; 
    55use Plack::Builder; 
     6use Plack::Request; 
     7use Router::Resource; 
    68 
    7 use BookmarkApp::Dispatch; 
     9use BookmarkController; 
     10use Bookmarks; 
     11 
     12#TODO: allow a different config file on the command line, or set options from the command line 
    813 
    914-e 'conf.yml' or die "Missing required conf.yml config file\n"; 
    1015 
    1116my $config = YAML::LoadFile('conf.yml'); 
    12 my $app = BookmarkApp::Dispatch->as_psgi( 
    13     args_to_new => { 
    14         PARAMS => { dbname => $config->{dbname} }, 
    15     }, 
    16 ); 
     17 
     18sub get_controller { 
     19    my $req = shift; 
     20 
     21    my $controller = BookmarkController->new({ 
     22        request => $req, 
     23    }); 
     24    my $bookmarks = Bookmarks->new({ 
     25        dbname   => $config->{dbname}, 
     26        base_uri => $controller->base_uri, 
     27    }); 
     28    $controller->bookmarks($bookmarks); 
     29    return $controller; 
     30} 
     31 
     32my $router = router { 
     33    resource '/' => sub { 
     34        GET { 
     35            my ($env) = @_; 
     36            my $req = Plack::Request->new($env); 
     37            my $controller = get_controller($req); 
     38 
     39            # check for a uri param, and if there is one present, 
     40            # see if a bookmark for that URI already exists; if so 
     41            # redirect to that bookmark, and if not, show the form 
     42            # to create a new bookmark 
     43            if (defined $req->param('uri')) { 
     44                return $controller->find_or_new; 
     45            } 
     46 
     47            # otherwise return a list 
     48            return $controller->list; 
     49        }; 
     50        POST { 
     51            my ($env) = @_; 
     52            my $req = Plack::Request->new($env); 
     53            my $controller = get_controller($req); 
     54 
     55            # create the bookmark and redirect to the new bookmark's edit form 
     56            return $controller->create; 
     57        }; 
     58    }; 
     59 
     60    resource '/list' => sub { 
     61        GET { 
     62            my ($env) = @_; 
     63            my $req = Plack::Request->new($env); 
     64            my $controller = get_controller($req); 
     65 
     66            return $controller->list; 
     67        }; 
     68    }; 
     69 
     70    resource '/feed' => sub { 
     71        GET { 
     72            my ($env) = @_; 
     73            my $req = Plack::Request->new($env); 
     74            my $controller = get_controller($req); 
     75 
     76            return $controller->feed; 
     77        }; 
     78    }; 
     79 
     80    resource '/{id}' => sub { 
     81        GET { 
     82            my ($env, $params) = @_; 
     83            my $req = Plack::Request->new($env); 
     84            my $controller = get_controller($req); 
     85 
     86            return $controller->view($params->{id}); 
     87        }; 
     88        POST { 
     89            my ($env, $params) = @_; 
     90            my $req = Plack::Request->new($env); 
     91            my $controller = get_controller($req); 
     92 
     93            return $controller->edit($params->{id}); 
     94 
     95            return [200, ['Content-Type' => 'text/plain'], ['update ', $params->{id}]]; 
     96        }; 
     97    }; 
     98 
     99    resource '/{id}/{field}' => sub { 
     100        GET { 
     101            my ($env, $params) = @_; 
     102            my $req = Plack::Request->new($env); 
     103            my $controller = get_controller($req); 
     104 
     105            return $controller->view_field($params->{id}, $params->{field}); 
     106        }; 
     107    }; 
     108}; 
     109 
     110#builder { 
     111#    sub { $router->dispatch(shift); }; 
     112#}; 
    17113 
    18114builder { 
     
    24120        authenticator   => sub { $config->{digest_password} } 
    25121    ); 
    26     $app; 
     122    sub { $router->dispatch(shift); }; 
    27123}; 
  • trunk/start

    r54 r59  
    11#!/bin/sh 
    22 
    3 starman --listen :5000 --daemonize --pid pid --error-log errors 
     3starman --listen :5000 --daemonize --pid pid --error-log errors --access-log access 
Note: See TracChangeset for help on using the changeset viewer.