source: bookmarks/trunk/BookmarkApp.pm @ 21

Last change on this file since 21 was 21, checked in by peter, 11 years ago
  • added links to the JSON and XBEL formats of the current list
  • all links to the current list take into account the full list of tags in the query
  • minor style tweaks to the cotags list
File size: 7.5 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        return $self->tt_process(
139            'list.tt',
140            {
141                base_url     => $base_url,
142                selected_tag => $tag,
143                search_tags  => \@tags,
144                tag_query    => join('&', map { "tag=$_" } @tags),
145                all_tags     => \@all_tags,
146                cotags       => \@cotags,
147                resources    => \@resources,
148            },
149        );
150    }
151}
152
153sub feed {
154    my $self = shift;
155    my $q = $self->query;
156
157    my $tag = $q->param('tag');
158
159    require XML::Atom::Feed;
160    require XML::Atom::Entry;
161    require XML::Atom::Link;
162
163    my $feed = XML::Atom::Feed->new;
164    $feed->title('Bookmarks' . ($tag ? " tagged as $tag" : ''));
165    $feed->id($base_uri->canonical . 'feed');
166
167    # construct a feed from the most recent 12 bookmarks
168    for my $bookmark ($bookmarks->get_resources({ tag => $tag, limit => 12 })) {
169        my $entry = XML::Atom::Entry->new;
170        $entry->id($bookmark->{bookmark_uri});
171        $entry->title($bookmark->{title});
172        my $link = XML::Atom::Link->new;
173        $link->href($bookmark->{uri});
174        $entry->add_link($link);
175        $entry->summary('Tags: ' . join(', ', @{ $bookmark->{tags} }));
176        $feed->add_entry($entry);
177    }
178
179    $self->header_props(
180        -type => 'application/atom+xml',
181        -charset => 'UTF-8',
182    );
183    return $feed->as_xml;
184}
185
186sub view {
187    my $self = shift;
188    my $id = $self->param('id');
189    my $field = $self->param('field');
190    my $format = $self->query->param('format') || 'html';
191
192    my $bookmark = $bookmarks->get_bookmark({ id => $id });
193    if ($bookmark) {
194        if ($field) {
195            # respond with just the requested field as plain text
196            $self->header_props(
197                -type    => 'text/plain',
198                -charset => 'UTF-8',
199            );
200            my $value = $bookmark->{$field};
201            return ref $value eq 'ARRAY' ? join(',', @{ $value }) : $value;
202        } else {
203            if ($format eq 'json') {
204                $self->header_props(
205                    -type    => 'application/json',
206                    -charset => 'UTF-8',
207                );
208                return decode_utf8(encode_json($bookmark));
209            } else {
210                # display the bookmark form for this bookmark
211                $bookmark->{exists} = 1;
212                $bookmark->{created} = "Created " . localtime($bookmark->{ctime});
213                $bookmark->{created} .= '; Updated ' . localtime($bookmark->{mtime}) unless $bookmark->{ctime} == $bookmark->{mtime};
214                $self->header_props(
215                    -type    => 'text/html',
216                    -charset => 'UTF-8',
217                );
218                return $self->tt_process(
219                    'bookmark.tt',
220                    $bookmark,
221                );
222            }
223        }
224    } else {
225        $self->header_props(
226            -type    => 'text/html',
227            -charset => 'UTF-8',
228            -status  => 404,
229        );
230        return "Bookmark $id Not Found";
231    }
232}
233
234#TODO: split this into edit and create methods
235sub edit {
236    my $self = shift;
237    my $q = $self->query;
238    #TODO: get the bookmark based on the id and edit it directly?
239    #TODO: deal with changing URIs
240    my $uri = $q->param('uri');
241    my $title = $q->param('title');
242    my @tags = split ' ', $q->param('tags');
243    $bookmarks->add({
244        uri   => $uri,
245        title => $title,
246        tags  => \@tags,
247    });
248
249=begin
250
251    my $location = URI->new($q->url);
252    $location->query_form(uri => $uri) if defined $q->url_param('uri');
253    $location->fragment('updated');
254
255=cut
256
257    # return to the form
258    $self->header_type('redirect');
259    $self->header_props(
260        -uri => $ENV{REQUEST_URI},
261        -status => 303,
262    );
263}
264
2651;
Note: See TracBrowser for help on using the repository browser.