- Timestamp:
- 08/23/13 15:35:17 (11 years ago)
- 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; 1 package BookmarkController; 2 use Moose; 7 3 8 4 use Encode; … … 11 7 use Bookmarks; 12 8 use 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; 9 use Template; 10 11 has bookmarks => ( 12 is => 'rw', 13 handles => [qw{get_bookmark}], 14 ); 15 has base_uri => ( 16 is => 'ro', 17 builder => '_build_base_uri', 18 lazy => 1, 19 ); 20 has request => ( 21 is => 'ro', 22 ); 23 24 sub _build_base_uri { 25 my $self = shift; 26 my $url = $self->request->base; 27 27 28 $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 } 42 31 43 32 sub _get_list_links { … … 96 85 for my $link (@links) { 97 86 $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); 99 88 $link->{href}->query_form($link->{query}); 100 89 } … … 103 92 } 104 93 94 sub 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 105 116 sub list { 106 117 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 exists111 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 bookmark115 $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 bookmark122 $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 }135 118 136 119 # 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({ 146 129 query => $query, 147 130 tag => \@tags, … … 149 132 offset => $offset, 150 133 }); 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({ 153 136 query => $query, 154 137 tag => \@tags, … … 158 141 159 142 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( 165 144 JSON->new->utf8->convert_blessed->encode({ 166 145 bookmarks => \@resources, 167 146 }) 168 147 ); 148 return [200, ['Content-Type' => 'application/json; charset=UTF-8'], [$json]]; 169 149 } elsif ($format eq 'xbel') { 170 150 require XML::XBEL; … … 193 173 } 194 174 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]]; 201 176 } 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 '', 207 178 map { 208 179 sprintf "# %s\n# Tags: %s\n%s\n", … … 211 182 $_->uri 212 183 } @resources; 184 return [200, ['Content-Type' => 'text/uri-list; charset=UTF-8'], [$text]]; 213 185 } elsif ($format eq 'csv') { 214 186 require Text::CSV::Encoded; 215 my $csv = Text::CSV::Encoded->new({ encoding => 'utf8' });187 my $csv = Text::CSV::Encoded->new({ encoding_out => 'utf8' }); 216 188 my $text = qq{id,uri,title,tags,ctime,mtime\n}; 217 189 for my $bookmark (@resources) { … … 231 203 $dt =~ s/[^\d]//g; 232 204 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, 237 209 ); 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( 250 216 'list.tt', 251 217 { 252 base_url => $ base_url,218 base_url => $self->base_uri, 253 219 title => $title, 254 220 query => $query, … … 260 226 resources => \@resources, 261 227 }, 228 \my $output, 262 229 ); 230 return [200, ['Content-Type' => 'text/html; charset=UTF-8'], [$output]]; 263 231 } 264 232 } … … 266 234 sub feed { 267 235 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'); 272 239 my $title = 'Bookmarks' . (@tags ? " tagged as " . join(' & ', @tags) : ''); 273 240 … … 283 250 $feed->title($title); 284 251 285 my $feed_uri = URI->new_abs('feed', $self-> param('base_uri'));252 my $feed_uri = URI->new_abs('feed', $self->base_uri); 286 253 $feed_uri->query_form(tag => \@tags); 287 254 $feed->id($feed_uri->canonical); … … 296 263 297 264 # 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 })) { 299 266 my $entry = XML::Atom::Entry->new; 300 267 $entry->id($bookmark->bookmark_uri->canonical); … … 323 290 } 324 291 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]]; 330 293 } 331 294 332 295 sub 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 }); 338 301 if ($bookmark) { 339 302 # 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}); 342 305 if ($bookmark->mtime <= $cache_time) { 343 $self->header_type('redirect'); 344 $self->header_props( 345 -status => 304, 346 ); 347 return; 306 return [304, [], []]; 348 307 } 349 308 } 309 my $last_modified = time2str($bookmark->mtime); 350 310 351 311 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]]; 357 314 } else { 358 315 # display the bookmark form for this bookmark … … 360 317 $bookmark->{created} = "Created " . localtime($bookmark->ctime); 361 318 $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( 368 321 'bookmark.tt', 369 322 $bookmark, 323 \my $output, 370 324 ); 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"]]; 379 329 } 380 330 } 381 331 382 332 sub 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 }); 388 336 if ($bookmark) { 389 337 # respond with just the requested field as plain text … … 391 339 if ($@) { 392 340 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}]]; 399 342 } else { 400 343 die $@; 401 344 } 402 345 } 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"]]; 415 349 } 416 350 } … … 418 352 sub create { 419 353 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({ 425 360 uri => $uri, 426 361 title => $title, … … 428 363 }); 429 364 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], []]; 444 367 } 445 368 446 369 sub edit { 447 370 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 }); 452 374 if ($bookmark) { 453 375 # 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') || '' ]); 457 379 458 380 # 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"]]; 474 387 } 475 388 } -
trunk/app.psgi
r55 r59 4 4 use YAML; 5 5 use Plack::Builder; 6 use Plack::Request; 7 use Router::Resource; 6 8 7 use BookmarkApp::Dispatch; 9 use BookmarkController; 10 use Bookmarks; 11 12 #TODO: allow a different config file on the command line, or set options from the command line 8 13 9 14 -e 'conf.yml' or die "Missing required conf.yml config file\n"; 10 15 11 16 my $config = YAML::LoadFile('conf.yml'); 12 my $app = BookmarkApp::Dispatch->as_psgi( 13 args_to_new => { 14 PARAMS => { dbname => $config->{dbname} }, 15 }, 16 ); 17 18 sub 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 32 my $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 #}; 17 113 18 114 builder { … … 24 120 authenticator => sub { $config->{digest_password} } 25 121 ); 26 $app;122 sub { $router->dispatch(shift); }; 27 123 }; -
trunk/start
r54 r59 1 1 #!/bin/sh 2 2 3 starman --listen :5000 --daemonize --pid pid --error-log errors 3 starman --listen :5000 --daemonize --pid pid --error-log errors --access-log access
Note: See TracChangeset
for help on using the changeset viewer.