source: bookmarks/trunk/BookmarkApp.pm @ 20

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