source: bookmarks/trunk/lib/Bookmarks/Controller.pm @ 108

Last change on this file since 108 was 108, checked in by peter, 8 years ago

Added template for the tag tree display.

File size: 7.9 KB
Line 
1package Bookmarks::Controller;
2
3use Moose;
4
5use Encode;
6use HTTP::Date qw{time2str str2time};
7use JSON;
8use Bookmarks;
9use Bookmarks::List;
10use URI;
11use Template;
12
13has dbname => (
14    is => 'ro',
15    required => 1,
16);
17has bookmarks => (
18    is => 'ro',
19    handles => [qw{get_bookmark}],
20    builder => '_build_bookmarks',
21    lazy => 1,
22);
23has base_uri => (
24    is => 'ro',
25    builder => '_build_base_uri',
26    lazy => 1,
27);
28has request => (
29    is => 'rw',
30);
31
32sub _build_bookmarks {
33    my $self = shift;
34    return Bookmarks->new({
35        dbname   => $self->dbname,
36        base_uri => $self->base_uri,
37    });
38}
39
40sub _build_base_uri {
41    my $self = shift;
42    my $url = $self->request->base;
43
44    $url .= '/' unless $url =~ m{/$};
45    return URI->new($url);
46}
47
48sub find_or_new {
49    my $self = shift;
50
51    my $bookmark = $self->bookmarks->get_bookmark({ uri => $self->request->param('uri') });
52    if ($bookmark) {
53        # redirect to the view of the existing bookmark
54        return [301, [Location => $bookmark->bookmark_uri], []];
55    } else {
56        # bookmark was not found; show the form to create a new bookmark
57        require File::Basename;
58        my $template = Template->new({ INCLUDE_PATH => File::Basename::dirname($INC{'Bookmarks/Controller.pm'}) });
59        $template->process(
60            'bookmark.tt',
61            { 
62                bookmark => {
63                    uri   => $self->request->param('uri'),
64                    title => $self->request->param('title') || '',
65                },
66            },
67            \my $output,
68        );
69        return [404, ['Content-Type' => 'text/html; charset=UTF-8'], [$output]];
70    }
71}
72
73sub list {
74    my $self = shift;
75
76    # list all the bookmarks
77    my $mtime = $self->bookmarks->get_last_modified_time;
78
79    my $format = $self->request->param('format') || 'html';
80
81    my @tags = grep { $_ ne '' } $self->request->param('tag');
82    my $query = $self->request->param('q');
83    my $limit = $self->request->param('limit');
84    my $offset = $self->request->param('offset');
85    my $page = $self->request->param('page');
86
87    my $list = Bookmarks::List->new({
88        bookmarks => $self->bookmarks,
89        search    => $self->bookmarks->search({
90            query  => $query,
91            tags   => \@tags,
92            limit  => $limit,
93            offset => $offset,
94            page   => $page,
95        }),
96    });
97
98    my $as_format = "as_$format";
99    if (!$list->meta->has_method($as_format)) {
100        return [406, ['Content-Type' => 'text/plain; charset=UTF-8'], [qq{"$format" is not a supported format}]];
101    }
102    return $list->$as_format;
103}
104
105sub feed {
106    my $self = shift;
107
108    my $query = $self->request->param('q');
109    my @tags = grep { $_ ne '' } $self->request->param('tag');
110
111    # construct a feed from the most recent 12 bookmarks
112    my $list = Bookmarks::List->new({
113        bookmarks => $self->bookmarks,
114        search    => $self->bookmarks->search({ query => $query, tags => \@tags, limit => 12 }),
115    });
116    return $list->as_atom;
117}
118
119# returns 1 if there is an If-Modified-Since header and it is newer than the given $mtime
120# returns 0 if there is an If-Modified-Since header but the $mtime is newer
121# returns undef if there is no If-Modified-Since header
122sub _request_is_newer_than {
123    my $self = shift;
124    my $mtime = shift;
125
126    # check If-Modified-Since header to return cache response
127    if ($self->request->env->{HTTP_IF_MODIFIED_SINCE}) {
128        my $cache_time = str2time($self->request->env->{HTTP_IF_MODIFIED_SINCE});
129        return $mtime <= $cache_time ? 1 : 0;
130    } else {
131        return;
132    }
133}
134
135sub view {
136    my ($self, $id) = @_;
137
138    my $format = $self->request->param('format') || 'html';
139
140    my $bookmark = $self->get_bookmark({ id => $id });
141    if ($bookmark) {
142        return [304, [], []] if $self->_request_is_newer_than($bookmark->mtime);
143
144        my $last_modified = time2str($bookmark->mtime);
145       
146        if ($format eq 'json') {
147            my $json = decode_utf8(JSON->new->utf8->convert_blessed->encode($bookmark->to_hashref));
148            return [200, ['Content-Type' => 'application/json; charset=UTF-8', 'Last-Modified' => $last_modified], [$json]];
149        } else {
150            # display the bookmark form for this bookmark
151            require File::Basename;
152            my $template = Template->new({ INCLUDE_PATH => File::Basename::dirname($INC{'Bookmarks/Controller.pm'}) });
153            $template->process(
154                'bookmark.tt',
155                { bookmark => $bookmark },
156                \my $output,
157            );
158            return [200, ['Content-Type' => 'text/html; charset=UTF-8', 'Last-Modified' => $last_modified], [$output]];
159        }
160    } else {
161        return [404, ['Content-Type' => 'text/plain; charset=UTF-8'], ["Bookmark $id not found"]];
162    }
163}
164
165sub view_field {
166    my ($self, $id, $field) = @_;
167
168    my $bookmark = $self->get_bookmark({ id => $id });
169    if ($bookmark) {
170        return [304, [], []] if $self->_request_is_newer_than($bookmark->mtime);
171
172        # check whether the requested field is part of the bookmark
173        if (!$bookmark->meta->has_attribute($field)) {
174            return [404, ['Content-Type' => 'text/plain; charset=UTF-8'], [qq{"$field" is not a valid bookmark data field}]];
175        }
176
177        # respond with just the requested field as plain text
178        my $value = $bookmark->$field;
179        my $last_modified = time2str($bookmark->mtime);
180        return [200, ['Content-Type' => 'text/plain; charset=UTF-8', 'Last-Modified' => $last_modified], [ref $value eq 'ARRAY' ? join(' ', @{ $value }) : $value]];
181    } else {
182        return [404, ['Content-Type' => 'text/plain; charset=UTF-8'], ["Bookmark $id not found"]];
183    }
184}
185
186sub create_and_redirect {
187    my $self = shift;
188
189    my $uri   = $self->request->param('uri');
190    my $title = $self->request->param('title');
191    my @tags  = split ' ', $self->request->param('tags');
192
193    my $bookmark = $self->bookmarks->add({
194        uri   => $uri,
195        title => $title,
196        tags  => \@tags,
197    });
198
199    #TODO: not RESTful; the proper RESTful response would be a 201
200    return [303, ['Location' => $bookmark->bookmark_uri->canonical], []];
201}
202
203sub update_and_redirect {
204    my $self = shift;
205    my $id = shift;
206
207    my $bookmark = $self->bookmarks->get_bookmark({ id => $id });
208    if ($bookmark) {
209        # update the URI, title, and tags
210        $bookmark->uri($self->request->param('uri'));
211        $bookmark->title($self->request->param('title'));
212        $bookmark->tags([ split ' ', $self->request->param('tags') || '' ]);
213
214        # write to the database
215        $bookmark->update;
216
217        #TODO: not RESTful; proper response would be a 200
218        return [303, ['Location' => $bookmark->bookmark_uri->canonical], []];
219    } else {
220        return [404, ['Content-Type' => 'text/plain; charset=UTF-8'], ["Bookmark $id not found"]];
221    }
222}
223
224sub tag_tree {
225    my $self = shift;
226    my $tags = shift || [];
227
228    my $list = Bookmarks::List->new({
229        bookmarks => $self->bookmarks,
230        search    => $self->bookmarks->search({
231            tags   => $tags,
232        }),
233    });
234
235    my @tags = map {
236        {
237            path => join('/', @$tags[0 .. $_ - 1]),
238            tag  => $tags->[$_ - 1],
239        }
240    } 1 .. scalar(@{ $tags });
241
242    require Template;
243    require File::Basename;
244    my @cotags = $self->bookmarks->get_cotags({ search => $list->search });
245
246    #TODO: get the actual request URI and makre sure it ends with a /
247    my $base_url = join '/', ($self->base_uri . 'tags'), @$tags;
248    $base_url .= '/';
249    my $template = Template->new({ INCLUDE_PATH => File::Basename::dirname($INC{'Bookmarks/Controller.pm'}) });
250
251    $template->process(
252        'tagtree.tt',
253        {
254            base_url => $self->base_uri,
255            request_url => $base_url,
256            tags      => \@tags,
257            cotags    => \@cotags,
258            bookmarks => $list->results,
259        },
260        \my $output,
261    );
262
263
264    return [200, ['Content-Type' => 'text/html; charset=UTF-8'], [$output]];
265}
266
2671;
Note: See TracBrowser for help on using the repository browser.