source: bookmarks/trunk/BookmarkApp.pm @ 23

Last change on this file since 23 was 22, checked in by peter, 12 years ago
  • construct the link attributes and hrefs in the BookmarkApp instead of in the template
  • render the @links array as a list instead of hardcoded paths using the tag_query value
  • display the links whether or not there are any tag parameters
File size: 8.7 KB
Line 
1package BookmarkApp;
2use strict;
3use warnings;
4use base qw{CGI::Application};
5
6use CGI::Application::Plugin::TT;
7
8use Encode;
9use HTTP::Date qw{time2isoz};
10use JSON;
11use Bookmarks;
12
13use URI;
14my $base_uri = URI->new;
15$base_uri->scheme('http');
16$base_uri->host($ENV{HTTP_X_FORWARDED_HOST} || $ENV{SERVER_NAME});
17$base_uri->port($ENV{SERVER_PORT});
18$base_uri->path($ENV{SCRIPT_NAME} . '/');
19
20my $dbname = 'fk.db';
21my $bookmarks = Bookmarks->new({
22    dbname   => $dbname,
23    base_uri => $base_uri->canonical,
24});
25
26sub setup {
27    my $self = shift;
28    $self->mode_param(path_info => 1);
29    $self->run_modes([qw{
30        list
31        feed
32        view
33        edit
34    }]);
35}
36
37sub list {
38    my $self = shift;
39    my $q = $self->query;
40
41    # check for a uri param, and if there is one present,
42    # see if a bookmark for that URI already exists
43    if (defined(my $uri = $q->param('uri'))) {
44        my $bookmark = $bookmarks->get_bookmark({ uri => $uri });
45        if ($bookmark) {
46            # redirect to the view of the existing bookmark
47            $self->header_type('redirect');
48            $self->header_props(
49                -uri => $q->url . '/' . $bookmark->{id},
50            );
51            return;
52        } else {
53            # bookmark was not found; show the form to create a new bookmark
54            $bookmark->{uri} = $uri;
55            $bookmark->{title} = $q->param('title');
56            $self->header_props(
57                -type    => 'text/html',
58                -charset => 'UTF-8',
59                -status  => 404,
60            );
61            return $self->tt_process(
62                'bookmark.tt',
63                $bookmark,
64            );
65        }
66    }
67
68    # list all the bookmarks
69    my $format = $q->param('format') || 'html';
70    my $tag = $q->param('tag');
71    my @tags = $q->param('tag');
72    # special case: handle the empty tag
73    if (@tags == 1 && $tags[0] eq '') {
74        @tags = ();
75    }
76    my $limit = $q->param('limit');
77    my $offset = $q->param('offset');
78    my @resources = $bookmarks->get_resources({
79        tag    => \@tags,
80        limit  => $limit,
81        offset => $offset,
82    });
83    my @all_tags = $bookmarks->get_tags({ selected => $tag });
84    my @cotags = $bookmarks->get_cotags({ tag => \@tags });
85
86    if ($format eq 'json') {
87        $self->header_props(
88            -type    => 'application/json',
89            -charset => 'UTF-8',
90        );
91        return decode_utf8(
92            encode_json({
93                bookmarks => \@resources,
94            })
95        );
96    } elsif ($format eq 'xbel') {
97        require XML::XBEL;
98        #TODO: conditional support; if XML::XBEL is not present, return a 5xx response
99
100        my $xbel = XML::XBEL->new;
101
102        $xbel->new_document({
103            title => 'Bookmarks' . ($tag ? " tagged as $tag" : ''),
104        });
105
106        for my $bookmark (@resources) {
107            my $cdatetime = time2isoz $bookmark->{ctime};
108            my $mdatetime = time2isoz $bookmark->{mtime};
109            # make the timestamps W3C-correct
110            s/ /T/ foreach ($cdatetime, $mdatetime);
111
112            $xbel->add_bookmark({
113                href     => $bookmark->{uri},
114                title    => $bookmark->{title},
115                desc     => 'Tags: ' . join(', ', @{ $bookmark->{tags} }),
116                added    => $cdatetime,
117                #XXX: are we sure that modified is the mtime of the bookmark or the resource?
118                modified => $mdatetime,
119            });
120        }
121
122        $self->header_props(
123            -type    => 'application/xml',
124            -charset => 'UTF-8',
125        );
126
127        return $xbel->toString;
128    } else {
129        $self->header_props(
130            -type    => 'text/html',
131            -charset => 'UTF-8',
132        );
133
134        # set the base URL, adding a trailing slash if needed
135        my $base_url = $q->url;
136        $base_url .= '/' if $base_url =~ m{/bookmarks$};
137
138        my @links = (
139            {
140                text => 'Link',
141                type => 'text/html',
142                rel  => 'self',
143                query => {
144                    tag => \@tags,
145                },
146            },
147            {
148                text => 'JSON',
149                type => 'application/json',
150                rel  => 'alternate',
151                query => {
152                    tag => \@tags,
153                    format => 'json',
154                },
155            },
156            {
157                text => 'XBEL',
158                type => 'application/xml',
159                rel  => 'alternate',
160                query => {
161                    tag => \@tags,
162                    format => 'xbel',
163                },
164            },
165            {
166                text => 'Atom',
167                type => 'application/atom+xml',
168                rel  => 'alternate',
169                path => 'feed',
170                query => {
171                    tag => \@tags,
172                },
173            },
174        );
175        for my $link (@links) {
176            $link->{href} = URI->new_abs($link->{path} || '', $base_uri);
177            $link->{href}->query_form($link->{query});
178        }
179       
180        return $self->tt_process(
181            'list.tt',
182            {
183                base_url     => $base_url,
184                selected_tag => $tag,
185                search_tags  => \@tags,
186                links        => \@links,
187                all_tags     => \@all_tags,
188                cotags       => \@cotags,
189                resources    => \@resources,
190            },
191        );
192    }
193}
194
195sub feed {
196    my $self = shift;
197    my $q = $self->query;
198
199    my $tag = $q->param('tag');
200
201    require XML::Atom::Feed;
202    require XML::Atom::Entry;
203    require XML::Atom::Link;
204
205    my $feed = XML::Atom::Feed->new;
206    $feed->title('Bookmarks' . ($tag ? " tagged as $tag" : ''));
207    $feed->id($base_uri->canonical . 'feed');
208
209    # construct a feed from the most recent 12 bookmarks
210    for my $bookmark ($bookmarks->get_resources({ tag => $tag, limit => 12 })) {
211        my $entry = XML::Atom::Entry->new;
212        $entry->id($bookmark->{bookmark_uri});
213        $entry->title($bookmark->{title});
214        my $link = XML::Atom::Link->new;
215        $link->href($bookmark->{uri});
216        $entry->add_link($link);
217        $entry->summary('Tags: ' . join(', ', @{ $bookmark->{tags} }));
218        $feed->add_entry($entry);
219    }
220
221    $self->header_props(
222        -type => 'application/atom+xml',
223        -charset => 'UTF-8',
224    );
225    return $feed->as_xml;
226}
227
228sub view {
229    my $self = shift;
230    my $id = $self->param('id');
231    my $field = $self->param('field');
232    my $format = $self->query->param('format') || 'html';
233
234    my $bookmark = $bookmarks->get_bookmark({ id => $id });
235    if ($bookmark) {
236        if ($field) {
237            # respond with just the requested field as plain text
238            $self->header_props(
239                -type    => 'text/plain',
240                -charset => 'UTF-8',
241            );
242            my $value = $bookmark->{$field};
243            return ref $value eq 'ARRAY' ? join(',', @{ $value }) : $value;
244        } else {
245            if ($format eq 'json') {
246                $self->header_props(
247                    -type    => 'application/json',
248                    -charset => 'UTF-8',
249                );
250                return decode_utf8(encode_json($bookmark));
251            } else {
252                # display the bookmark form for this bookmark
253                $bookmark->{exists} = 1;
254                $bookmark->{created} = "Created " . localtime($bookmark->{ctime});
255                $bookmark->{created} .= '; Updated ' . localtime($bookmark->{mtime}) unless $bookmark->{ctime} == $bookmark->{mtime};
256                $self->header_props(
257                    -type    => 'text/html',
258                    -charset => 'UTF-8',
259                );
260                return $self->tt_process(
261                    'bookmark.tt',
262                    $bookmark,
263                );
264            }
265        }
266    } else {
267        $self->header_props(
268            -type    => 'text/html',
269            -charset => 'UTF-8',
270            -status  => 404,
271        );
272        return "Bookmark $id Not Found";
273    }
274}
275
276#TODO: split this into edit and create methods
277sub edit {
278    my $self = shift;
279    my $q = $self->query;
280    #TODO: get the bookmark based on the id and edit it directly?
281    #TODO: deal with changing URIs
282    my $uri = $q->param('uri');
283    my $title = $q->param('title');
284    my @tags = split ' ', $q->param('tags');
285    $bookmarks->add({
286        uri   => $uri,
287        title => $title,
288        tags  => \@tags,
289    });
290
291=begin
292
293    my $location = URI->new($q->url);
294    $location->query_form(uri => $uri) if defined $q->url_param('uri');
295    $location->fragment('updated');
296
297=cut
298
299    # return to the form
300    $self->header_type('redirect');
301    $self->header_props(
302        -uri => $ENV{REQUEST_URI},
303        -status => 303,
304    );
305}
306
3071;
Note: See TracBrowser for help on using the repository browser.