Commit | Line | Data |
e30ed59d |
1 | package App::IdiotBox; |
2 | |
625f105e |
3 | use App::IdiotBox::Common qw(@SupportedFormats); |
e30ed59d |
4 | use Web::Simple __PACKAGE__; |
d7497a23 |
5 | use FindBin; |
6 | use HTML::Zoom; |
998cc52c |
7 | use HTML::Zoom::FilterBuilder::Template; |
02ea620e |
8 | use List::Util qw(first); |
d7497a23 |
9 | |
f29e9b6f |
10 | use App::IdiotBox::Announcement; |
11 | use App::IdiotBox::Bucket; |
12 | use App::IdiotBox::Video; |
d7497a23 |
13 | |
362a8766 |
14 | has $_ => (is => 'ro') for qw(recent_announcements buckets); |
15 | |
8b9d3d54 |
16 | sub default_config { |
71a02d85 |
17 | template_dir => 'share/html', |
18 | store => 'SQLite', |
19 | db_file => 'var/lib/idiotbox.db', |
998cc52c |
20 | base_url => 'http://localhost:3000/', |
ebba317f |
21 | base_dir => do { use FindBin; $FindBin::Bin }, |
8b9d3d54 |
22 | } |
e30ed59d |
23 | |
71a02d85 |
24 | sub BUILD { |
25 | my $self = shift; |
26 | my $store; |
27 | ($store = $self->config->{store}) =~ /^(\w+)$/ |
28 | or die "Store config should be just a name, got ${store} instead"; |
29 | my $store_class = "App::IdiotBox::Store::${store}"; |
30 | eval "require ${store_class}; 1" |
31 | or die "Couldn't load ${store} store: $@"; |
32 | $store_class->bind($self); |
33 | } |
34 | |
8b9d3d54 |
35 | sub dispatch_request { |
36 | my $self = shift; |
e30ed59d |
37 | sub (/) { $self->show_front_page }, |
f29e9b6f |
38 | |
2da7f57f |
39 | # Admin |
26b4958e |
40 | sub (/admin/) { |
41 | sub (%new_name=&new_slug=) { |
42 | my ($self, $name, $slug) = @_; |
43 | |
f29e9b6f |
44 | my ($nb, $err) = App::IdiotBox::Bucket->create( |
26b4958e |
45 | slug => $slug, |
46 | name => $name, |
f29e9b6f |
47 | ); |
48 | |
2da7f57f |
49 | return $self->show_admin_page(message => $err) if $err; |
f29e9b6f |
50 | |
51 | my $nb = $self->buckets->add($nb); |
26b4958e |
52 | |
2da7f57f |
53 | $self->show_admin_page(message => "New bucket created"); |
26b4958e |
54 | }, |
55 | }, |
56 | sub (/admin/) { $self->show_admin_page }, |
57 | |
2da7f57f |
58 | sub (/admin/bucket/*/...) { |
26b4958e |
59 | my $bucket = $self->buckets->get({ slug => $_[1] }); |
f29e9b6f |
60 | |
2da7f57f |
61 | # Admin, video |
62 | sub (/video/*/...) { |
63 | my $video = $bucket->videos->get({ slug => $_[1] }); |
64 | |
65 | sub (POST + %edit_video=&*) { |
66 | my ($self, undef, $args) = @_; |
67 | delete $args->{edit_video}; |
68 | |
69 | my ($uv, $err) = $video->update(%$args); |
70 | |
71 | return $self->show_edit_video_page($video, message => $err) if $err; |
72 | |
56640bce |
73 | $bucket->videos->replace($video, $uv); |
2da7f57f |
74 | |
75 | $self->show_edit_video_page($video, message => "Video updated"); |
76 | }, |
77 | |
78 | sub (/) { $self->show_edit_video_page($video); } |
79 | }, |
80 | |
81 | sub (POST + %edit_bucket=&*) { |
56640bce |
82 | my ($self, undef, $args) = @_; |
83 | delete $args->{edit_bucket}; |
84 | |
85 | my ($ub, $err) = $bucket->update(%$args); |
f29e9b6f |
86 | |
2da7f57f |
87 | return $self->show_admin_page(message => $err) if $err; |
f29e9b6f |
88 | |
89 | $self->buckets->replace($bucket, $ub); |
90 | |
26b4958e |
91 | $self->show_bucket_edited_page($bucket); |
92 | }, |
93 | sub (/) { |
94 | $self->show_edit_bucket_page($bucket); |
95 | }, |
96 | sub (/delete/) { |
97 | $self->show_confirm_delete_bucket_page($bucket) |
98 | }, |
99 | sub (/delete/yes/) { |
100 | $self->buckets->remove({ slug => $bucket->slug }); |
101 | $self->show_bucket_deleted_page($bucket->slug); |
102 | }, |
103 | }, |
2da7f57f |
104 | |
105 | # Normal site |
8b9d3d54 |
106 | sub (/*/...) { |
e30ed59d |
107 | my $bucket = $self->buckets->get({ slug => $_[1] }); |
8b9d3d54 |
108 | sub (/) { |
109 | $self->show_bucket($bucket) |
110 | }, |
111 | sub (/*/) { |
112 | $self->show_video($bucket->videos->get({ slug => $_[1] })); |
113 | } |
e30ed59d |
114 | } |
8b9d3d54 |
115 | } |
e30ed59d |
116 | |
847de56a |
117 | sub show_front_page { |
118 | my $self = shift; |
e30ed59d |
119 | my $ann = $self->recent_announcements; |
120 | $self->html_response( |
1a1c4f78 |
121 | front_page => sub { |
122 | $_->select('#announcement-list') |
123 | ->repeat_content($ann->map(sub { |
124 | my $obj = $_; |
125 | sub { |
126 | $_->select('.bucket-name')->replace_content($obj->bucket->name) |
02ea620e |
127 | ->select('.made-at')->replace_content($obj->made_at) |
fb836c4b |
128 | ->select('.bucket-link')->set_attribute( |
129 | 'href' => $obj->bucket->slug.'/' |
130 | ) |
1a1c4f78 |
131 | ->select('.new-videos')->replace_content($obj->video_count) |
132 | ->select('.total-videos')->replace_content( |
133 | $obj->bucket->video_count |
134 | ) |
135 | } |
136 | })) |
137 | } |
e30ed59d |
138 | ); |
139 | } |
140 | |
26b4958e |
141 | sub show_admin_page { |
142 | my $self = shift; |
143 | my %opts = @_; |
2da7f57f |
144 | my $message = $opts{message} || ''; |
26b4958e |
145 | |
146 | my $bucket = $self->buckets; |
147 | $self->html_response( |
148 | admin => sub { |
149 | $_->select('#bucket-list') |
150 | ->repeat_content($bucket->map(sub { |
151 | my $obj = $_; |
152 | sub { |
153 | $_->select('.bucket-slug')->replace_content($obj->slug) |
154 | ->select('.bucket-name')->replace_content($obj->name) |
155 | ->select('.edit-link')->set_attribute( |
2da7f57f |
156 | 'href' => 'bucket/'.$obj->slug.'/' |
26b4958e |
157 | ) |
158 | ->select('.delete-link')->set_attribute( |
2da7f57f |
159 | 'href' => 'bucket/'.$obj->slug.'/delete/' |
26b4958e |
160 | ) |
161 | } |
162 | })) |
2da7f57f |
163 | ->select('.message-text')->replace_content($message) |
26b4958e |
164 | |
165 | } |
166 | |
167 | ); |
168 | } |
169 | |
170 | sub show_confirm_delete_bucket_page { |
171 | my ($self, $bucket) = @_; |
2da7f57f |
172 | $self->html_response('delete_bucket' => sub { |
26b4958e |
173 | $_->select('.bucket-name')->replace_content($bucket->name) |
174 | ->select('.confirm-yes')->set_attribute( |
175 | 'href' => 'yes/' |
176 | ) |
177 | }); |
178 | } |
179 | |
180 | sub show_edit_bucket_page { |
181 | my ($self, $bucket, %opt) = @_; |
2da7f57f |
182 | my $message = $opt{message} || ''; |
183 | $self->html_response('edit_bucket' => sub { |
26b4958e |
184 | $_->select('.bucket-name')->replace_content($bucket->name) |
2da7f57f |
185 | ->select('.bucket-slug')->replace_content($bucket->slug) |
186 | ->select('.message-text')->replace_content($message) |
187 | ->select('#video-list')->repeat_content($bucket->videos->map(sub { |
188 | my $video = $_; |
189 | sub { |
190 | $_->select('.video-name')->replace_content($video->name) |
191 | ->select('.video-author')->replace_content($video->author) |
192 | ->select('.video-link')->set_attribute( |
193 | href => 'video/'.$video->slug.'/' |
194 | ) |
195 | } |
196 | })) |
197 | |
198 | }); |
199 | } |
200 | |
201 | sub show_edit_video_page { |
202 | my ($self, $video, %opt) = @_; |
203 | my $message = $opt{message} || ''; |
204 | $self->html_response('edit_video' => sub { |
205 | $_->select('.video-name')->set_attribute(value => $video->name) |
206 | ->select('.author-name')->set_attribute(value => $video->author) |
207 | ->select('.bucket-slug')->replace_content($video->bucket->slug) |
208 | ->select('.slug-name')->replace_content($video->slug) |
209 | ->select('.message-text')->replace_content($message) |
26b4958e |
210 | }); |
211 | } |
212 | |
213 | sub show_bucket_deleted_page { |
214 | my ($self, $name) = @_; |
2da7f57f |
215 | $self->html_response('deleted_bucket' => sub { |
26b4958e |
216 | $_->select('.bucket-name')->replace_content($name) |
217 | }); |
218 | } |
219 | |
220 | sub show_bucket_edited_page { |
221 | my ($self, $name) = @_; |
2da7f57f |
222 | $self->html_response('edited_bucket' => sub { |
26b4958e |
223 | $_->select('.bucket-name')->replace_content($name) |
224 | }); |
225 | } |
226 | |
847de56a |
227 | sub show_bucket { |
228 | my ($self, $bucket) = @_; |
1a1c4f78 |
229 | $self->html_response(bucket => sub { |
230 | $_->select('.bucket-name')->replace_content($bucket->name) |
231 | ->select('#video-list')->repeat_content($bucket->videos->map(sub { |
232 | my $video = $_; |
233 | sub { |
234 | $_->select('.video-name')->replace_content($video->name) |
235 | ->select('.video-author')->replace_content($video->author) |
236 | ->select('.video-link')->set_attribute( |
fb836c4b |
237 | href => $video->slug.'/' |
1a1c4f78 |
238 | ) |
239 | } |
240 | })) |
241 | }); |
d7497a23 |
242 | } |
243 | |
847de56a |
244 | sub show_video { |
245 | my ($self, $video) = @_; |
02ea620e |
246 | my $video_file = first { |
247 | -e join('/', $self->config->{base_dir}, $_) |
248 | } map { |
249 | join('/', $video->bucket->slug, $video->slug, $video->file_name.".$_") |
625f105e |
250 | } @SupportedFormats; |
1a1c4f78 |
251 | $self->html_response(video => sub { |
998cc52c |
252 | my $video_url = |
253 | $self->base_url |
6df05090 |
254 | .($video_file||'NO FILE FOUND SORRY'); |
998cc52c |
255 | |
1a1c4f78 |
256 | $_->select('.video-name')->replace_content($video->name) |
257 | ->select('.author-name')->replace_content($video->author) |
258 | ->select('.bucket-link')->set_attribute( |
fb836c4b |
259 | href => '../' |
1a1c4f78 |
260 | ) |
261 | ->select('.bucket-name')->replace_content($video->bucket->name) |
262 | ->select('.video-details')->replace_content($video->details) |
998cc52c |
263 | ->select('script')->template_text_raw({ video_url => $video_url }); |
1a1c4f78 |
264 | }); |
d7497a23 |
265 | } |
266 | |
847de56a |
267 | sub html_response { |
268 | my ($self, $template_name, $selectors) = @_; |
1a1c4f78 |
269 | my $io = $self->_zoom_for($template_name => $selectors)->to_fh; |
e30ed59d |
270 | return [ 200, [ 'Content-Type' => 'text/html' ], $io ] |
271 | } |
272 | |
847de56a |
273 | sub _template_filename_for { |
274 | my ($self, $name) = @_; |
d7497a23 |
275 | $self->{config}{template_dir}.'/'.$name.'.html'; |
276 | } |
277 | |
847de56a |
278 | sub _layout_zoom { |
279 | my $self = shift; |
d7497a23 |
280 | $self->{layout_zoom} ||= HTML::Zoom->from_file( |
281 | $self->_template_filename_for('layout') |
e30ed59d |
282 | ) |
283 | } |
284 | |
847de56a |
285 | sub _zoom_for { |
286 | my ($self, $template_name, $selectors) = @_; |
e30ed59d |
287 | ($self->{zoom_for_template}{$template_name} ||= do { |
288 | my @body; |
d7497a23 |
289 | HTML::Zoom->from_file( |
290 | $self->_template_filename_for($template_name) |
e30ed59d |
291 | ) |
1a1c4f78 |
292 | ->select('#main-content')->collect_content({ into => \@body }) |
d7497a23 |
293 | ->run; |
1a1c4f78 |
294 | $self->_layout_zoom |
295 | ->select('#main-content')->replace_content(\@body) |
296 | ->memoize; |
297 | })->apply($selectors); |
e30ed59d |
298 | } |
299 | |
847de56a |
300 | sub base_url { |
301 | my $self = shift; |
998cc52c |
302 | $self->{base_url} ||= do { |
303 | (my $u = $self->config->{base_url}) =~ s/\/$//; |
304 | "${u}/"; |
305 | } |
306 | } |
307 | |
847de56a |
308 | sub _run_cli { |
309 | my $self = shift; |
ebba317f |
310 | unless (@ARGV == 1 && $ARGV[0] eq 'import') { |
311 | return $self->SUPER::_run_cli(@_); |
312 | } |
313 | $self->cli_import; |
314 | } |
315 | |
847de56a |
316 | sub _cli_usage { |
317 | my $self = shift; |
ebba317f |
318 | "To import data into your idiotbox install, chdir into a directory\n". |
319 | "containing video files and run:\n". |
320 | "\n". |
321 | " $0 import\n". |
322 | "\n". |
323 | $self->SUPER::_cli_usage(@_); |
324 | } |
325 | |
847de56a |
326 | sub cli_import { |
327 | my $self = shift; |
ebba317f |
328 | require App::IdiotBox::Importer; |
329 | App::IdiotBox::Importer->run($self); |
330 | } |
331 | |
e30ed59d |
332 | 1; |