Separate out App::IdiotBox::* DB objects and put create/update
[catagits/App-IdiotBox.git] / lib / App / IdiotBox.pm
CommitLineData
e30ed59d 1package App::IdiotBox;
2
625f105e 3use App::IdiotBox::Common qw(@SupportedFormats);
e30ed59d 4use Web::Simple __PACKAGE__;
d7497a23 5use FindBin;
6use HTML::Zoom;
998cc52c 7use HTML::Zoom::FilterBuilder::Template;
02ea620e 8use List::Util qw(first);
d7497a23 9
f29e9b6f 10use App::IdiotBox::Announcement;
11use App::IdiotBox::Bucket;
12use App::IdiotBox::Video;
d7497a23 13
362a8766 14has $_ => (is => 'ro') for qw(recent_announcements buckets);
15
8b9d3d54 16sub 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 24sub 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 35sub dispatch_request {
36 my $self = shift;
e30ed59d 37 sub (/) { $self->show_front_page },
f29e9b6f 38
26b4958e 39 sub (/admin/) {
40 sub (%new_name=&new_slug=) {
41 my ($self, $name, $slug) = @_;
42
f29e9b6f 43 my ($nb, $err) = App::IdiotBox::Bucket->create(
26b4958e 44 slug => $slug,
45 name => $name,
f29e9b6f 46 );
47
48 return $self->show_admin_page(error => $err) if $err;
49
50 my $nb = $self->buckets->add($nb);
26b4958e 51
52 $self->show_admin_page;
53 },
54 },
55 sub (/admin/) { $self->show_admin_page },
56
57 sub (/admin/*/...) {
58 my $bucket = $self->buckets->get({ slug => $_[1] });
59 sub (%new_name=) {
60 my ($self, $new_name) = @_;
f29e9b6f 61
62 my ($ub, $err) = $bucket->update(
63 name => $new_name,
64 );
65
66 return $self->show_admin_page(error => $err) if $err;
67
68 $self->buckets->replace($bucket, $ub);
69
26b4958e 70 $self->show_bucket_edited_page($bucket);
71 },
72 sub (/) {
73 $self->show_edit_bucket_page($bucket);
74 },
75 sub (/delete/) {
76 $self->show_confirm_delete_bucket_page($bucket)
77 },
78 sub (/delete/yes/) {
79 $self->buckets->remove({ slug => $bucket->slug });
80 $self->show_bucket_deleted_page($bucket->slug);
81 },
82 },
8b9d3d54 83 sub (/*/...) {
e30ed59d 84 my $bucket = $self->buckets->get({ slug => $_[1] });
8b9d3d54 85 sub (/) {
86 $self->show_bucket($bucket)
87 },
88 sub (/*/) {
89 $self->show_video($bucket->videos->get({ slug => $_[1] }));
90 }
e30ed59d 91 }
8b9d3d54 92}
e30ed59d 93
847de56a 94sub show_front_page {
95 my $self = shift;
e30ed59d 96 my $ann = $self->recent_announcements;
97 $self->html_response(
1a1c4f78 98 front_page => sub {
99 $_->select('#announcement-list')
100 ->repeat_content($ann->map(sub {
101 my $obj = $_;
102 sub {
103 $_->select('.bucket-name')->replace_content($obj->bucket->name)
02ea620e 104 ->select('.made-at')->replace_content($obj->made_at)
fb836c4b 105 ->select('.bucket-link')->set_attribute(
106 'href' => $obj->bucket->slug.'/'
107 )
1a1c4f78 108 ->select('.new-videos')->replace_content($obj->video_count)
109 ->select('.total-videos')->replace_content(
110 $obj->bucket->video_count
111 )
112 }
113 }))
114 }
e30ed59d 115 );
116}
117
26b4958e 118sub show_admin_page {
119 my $self = shift;
120 my %opts = @_;
121 my $error = $opts{error} || '';
122
123 my $bucket = $self->buckets;
124 $self->html_response(
125 admin => sub {
126 $_->select('#bucket-list')
127 ->repeat_content($bucket->map(sub {
128 my $obj = $_;
129 sub {
130 $_->select('.bucket-slug')->replace_content($obj->slug)
131 ->select('.bucket-name')->replace_content($obj->name)
132 ->select('.edit-link')->set_attribute(
133 'href' => $obj->slug.'/'
134 )
135 ->select('.delete-link')->set_attribute(
136 'href' => $obj->slug.'/delete/'
137 )
138 }
139 }))
140 ->select('.error-text')->replace_content($error)
141
142 }
143
144 );
145}
146
147sub show_confirm_delete_bucket_page {
148 my ($self, $bucket) = @_;
149 $self->html_response('delete' => sub {
150 $_->select('.bucket-name')->replace_content($bucket->name)
151 ->select('.confirm-yes')->set_attribute(
152 'href' => 'yes/'
153 )
154 });
155}
156
157sub show_edit_bucket_page {
158 my ($self, $bucket, %opt) = @_;
159 my $error = $opt{error} || '';
160 $self->html_response('edit' => sub {
161 $_->select('.bucket-name')->replace_content($bucket->name)
162 ->select('.error-text')->replace_content($error);
163 });
164}
165
166sub show_bucket_deleted_page {
167 my ($self, $name) = @_;
168 $self->html_response('deleted' => sub {
169 $_->select('.bucket-name')->replace_content($name)
170 });
171}
172
173sub show_bucket_edited_page {
174 my ($self, $name) = @_;
175 $self->html_response('edited' => sub {
176 $_->select('.bucket-name')->replace_content($name)
177 });
178}
179
847de56a 180sub show_bucket {
181 my ($self, $bucket) = @_;
1a1c4f78 182 $self->html_response(bucket => sub {
183 $_->select('.bucket-name')->replace_content($bucket->name)
184 ->select('#video-list')->repeat_content($bucket->videos->map(sub {
185 my $video = $_;
186 sub {
187 $_->select('.video-name')->replace_content($video->name)
188 ->select('.video-author')->replace_content($video->author)
189 ->select('.video-link')->set_attribute(
fb836c4b 190 href => $video->slug.'/'
1a1c4f78 191 )
192 }
193 }))
194 });
d7497a23 195}
196
847de56a 197sub show_video {
198 my ($self, $video) = @_;
02ea620e 199 my $video_file = first {
200 -e join('/', $self->config->{base_dir}, $_)
201 } map {
202 join('/', $video->bucket->slug, $video->slug, $video->file_name.".$_")
625f105e 203 } @SupportedFormats;
1a1c4f78 204 $self->html_response(video => sub {
998cc52c 205 my $video_url =
206 $self->base_url
6df05090 207 .($video_file||'NO FILE FOUND SORRY');
998cc52c 208
1a1c4f78 209 $_->select('.video-name')->replace_content($video->name)
210 ->select('.author-name')->replace_content($video->author)
211 ->select('.bucket-link')->set_attribute(
fb836c4b 212 href => '../'
1a1c4f78 213 )
214 ->select('.bucket-name')->replace_content($video->bucket->name)
215 ->select('.video-details')->replace_content($video->details)
998cc52c 216 ->select('script')->template_text_raw({ video_url => $video_url });
1a1c4f78 217 });
d7497a23 218}
219
847de56a 220sub html_response {
221 my ($self, $template_name, $selectors) = @_;
1a1c4f78 222 my $io = $self->_zoom_for($template_name => $selectors)->to_fh;
e30ed59d 223 return [ 200, [ 'Content-Type' => 'text/html' ], $io ]
224}
225
847de56a 226sub _template_filename_for {
227 my ($self, $name) = @_;
d7497a23 228 $self->{config}{template_dir}.'/'.$name.'.html';
229}
230
847de56a 231sub _layout_zoom {
232 my $self = shift;
d7497a23 233 $self->{layout_zoom} ||= HTML::Zoom->from_file(
234 $self->_template_filename_for('layout')
e30ed59d 235 )
236}
237
847de56a 238sub _zoom_for {
239 my ($self, $template_name, $selectors) = @_;
e30ed59d 240 ($self->{zoom_for_template}{$template_name} ||= do {
241 my @body;
d7497a23 242 HTML::Zoom->from_file(
243 $self->_template_filename_for($template_name)
e30ed59d 244 )
1a1c4f78 245 ->select('#main-content')->collect_content({ into => \@body })
d7497a23 246 ->run;
1a1c4f78 247 $self->_layout_zoom
248 ->select('#main-content')->replace_content(\@body)
249 ->memoize;
250 })->apply($selectors);
e30ed59d 251}
252
847de56a 253sub base_url {
254 my $self = shift;
998cc52c 255 $self->{base_url} ||= do {
256 (my $u = $self->config->{base_url}) =~ s/\/$//;
257 "${u}/";
258 }
259}
260
847de56a 261sub _run_cli {
262 my $self = shift;
ebba317f 263 unless (@ARGV == 1 && $ARGV[0] eq 'import') {
264 return $self->SUPER::_run_cli(@_);
265 }
266 $self->cli_import;
267}
268
847de56a 269sub _cli_usage {
270 my $self = shift;
ebba317f 271 "To import data into your idiotbox install, chdir into a directory\n".
272 "containing video files and run:\n".
273 "\n".
274 " $0 import\n".
275 "\n".
276 $self->SUPER::_cli_usage(@_);
277}
278
847de56a 279sub cli_import {
280 my $self = shift;
ebba317f 281 require App::IdiotBox::Importer;
282 App::IdiotBox::Importer->run($self);
283}
284
e30ed59d 2851;