]> ruin.nu Git - ndwebbie.git/blob - lib/NDWeb/Controller/Forum.pm
Possible to mark threads unread from a specific post
[ndwebbie.git] / lib / NDWeb / Controller / Forum.pm
1 package NDWeb::Controller::Forum;
2
3 use strict;
4 use warnings;
5 use parent 'Catalyst::Controller';
6
7 use NDWeb::Include;
8
9 =head1 NAME
10
11 NDWeb::Controller::Forum - Catalyst Controller
12
13 =head1 DESCRIPTION
14
15 Catalyst Controller.
16
17 =head1 METHODS
18
19 =cut
20
21 =head2 index 
22
23 =cut
24
25 sub index :Path :Args(0) {
26         my ( $self, $c ) = @_;
27         my $dbh = $c->model;
28
29         my $boards = $dbh->prepare(q{
30 SELECT fcid,category,fb.fbid,fb.board
31         ,SUM((SELECT count(*) FROM forum_posts WHERE ftid = ft.ftid
32                 AND COALESCE(time > ftv.time,TRUE))) AS unread
33         ,date_trunc('seconds',max(ft.mtime)::timestamp ) AS last_post
34 FROM forum_categories fc
35         JOIN forum_boards fb USING (fcid)
36         LEFT OUTER JOIN forum_threads ft USING (fbid)
37         LEFT OUTER JOIN (SELECT * FROM forum_thread_visits WHERE uid = $1)
38                 ftv USING (ftid)
39 WHERE fbid IN (SELECT fbid FROM forum_access
40                 WHERE gid IN (SELECT groups($1)))
41         OR ftid IN (SELECT ftid FROM forum_priv_access
42                 WHERE uid = $1)
43 GROUP BY fcid,category,fb.fbid, fb.board
44 ORDER BY fcid,fb.fbid
45                 });
46                 $boards->execute($c->stash->{UID});
47
48         my @categories;
49         my $category = {fcid => 0};
50         while (my $board = $boards->fetchrow_hashref){
51                 if ($category->{fcid} != $board->{fcid}){
52                         $category = {fcid => $board->{fcid}, category => $board->{category}};
53                         push @categories,$category;
54                 }
55                 push @{$category->{boards}},$board;
56         }
57         $c->stash(categories => \@categories);
58 }
59
60 sub allUnread : Local {
61         my ( $self, $c ) = @_;
62         my $dbh = $c->model;
63
64         my $threads = $dbh->prepare(q{
65 SELECT fcid,category,fbid,board,ft.ftid,u.username,ft.subject,ft.sticky
66         ,(SELECT count(*) FROM forum_posts WHERE ftid = ft.ftid
67                 AND COALESCE(time > ftv.time,TRUE)) AS unread
68         ,ft.posts,date_trunc('seconds',ft.mtime::timestamp) as last_post
69         ,ft.ctime::DATE as posting_date
70 FROM forum_categories fc
71         JOIN forum_boards fb USING (fcid)
72         JOIN forum_threads ft USING (fbid)
73         JOIN users u ON u.uid = ft.uid
74         LEFT OUTER JOIN (SELECT * FROM forum_thread_visits WHERE uid = $1)
75                 ftv ON ftv.ftid = ft.ftid
76 WHERE COALESCE(ft.mtime > ftv.time,TRUE)
77         AND ft.ftid IN (SELECT ftid FROM forum_posts WHERE ftid = ft.ftid)
78         AND ((fbid > 0 AND
79                         fb.fbid IN (SELECT fbid FROM forum_access WHERE gid IN (SELECT groups($1))))
80                 OR ft.ftid IN (SELECT ftid FROM forum_priv_access WHERE uid = $1))
81 ORDER BY fcid,fbid,sticky DESC,last_post DESC
82                 });
83
84         $threads->execute($c->stash->{UID});
85         my @categories;
86         my $category = {fcid => 0};
87         my $board = {fbid => 0};
88         while (my $thread = $threads->fetchrow_hashref){
89                 if ($category->{fcid} != $thread->{fcid}){
90                         $category = {fcid => $thread->{fcid}, category => $thread->{category}};
91                         push @categories,$category;
92                 }
93                 if ($board->{fbid} != $thread->{fbid}){
94                         $board = {fbid => $thread->{fbid}, board => $thread->{board}};
95                         push @{$category->{boards}},$board;
96                 }
97                 delete $thread->{fcid};
98                 delete $thread->{fbid};
99                 delete $thread->{category};
100                 delete $thread->{board};
101                 push @{$board->{threads}},$thread;
102         }
103         $c->stash(categories => \@categories);
104         $c->stash(time => $dbh->selectrow_array('SELECT now()::timestamp',undef));
105 }
106
107
108 sub search : Local {
109         my ( $self, $c ) = @_;
110
111         my $dbh = $c->model;
112
113         my @queries;
114         if ($c->req->param('search')){
115                 push @queries,'('.$c->req->param('search').')';
116         }
117         my %cat = (body => 'D', topic => 'A', author => 'B');
118         for ('body','topic','author'){
119                 if ($c->req->param($_)){
120                         my @words = split /\W+/,$c->req->param($_);
121                         my $op = $c->req->param('all'.$_) ? '&' : '|';
122                         my $cat = $cat{$_};
123                         my $query = join " $op ", map {"$_:$cat"} @words;
124                         push @queries,"($query)";
125                 }
126         }
127         my $search = join ' & ', @queries;
128
129         if ($search){
130                 my $posts = $dbh->prepare(q{SELECT fp.ftid,u.username,ft.subject
131                         ,ts_headline(fp.message,to_tsquery($2)) AS headline
132                         ,ts_rank_cd(fp.textsearch, to_tsquery($2),32) AS rank
133                         FROM forum_boards fb 
134                                 JOIN forum_threads ft USING (fbid)
135                                 JOIN forum_posts fp USING (ftid)
136                                 JOIN users u ON fp.uid = u.uid
137                         WHERE (fb.fbid IN (SELECT fbid FROM forum_access
138                                                 WHERE gid IN (SELECT groups($1)))
139                                         OR ft.ftid IN (SELECT ftid FROM forum_priv_access WHERE uid = $1)
140                                 ) AND fp.textsearch @@@ to_tsquery($2)
141                         ORDER BY rank DESC
142                 });
143                 eval {
144                         $posts->execute($c->stash->{UID},$search);
145                         my @posts;
146                         while (my $post = $posts->fetchrow_hashref){
147                                 push @posts,$post;
148                         }
149                         $c->stash(searchresults => \@posts);
150                 };
151                 if ($@){
152                         $c->stash( searcherror => $dbh->errstr);
153                 }
154         }
155
156 }
157
158
159 sub board : Local {
160         my ( $self, $c, $board ) = @_;
161         my $dbh = $c->model;
162
163         $c->stash(time => $dbh->selectrow_array('SELECT now()::timestamp',undef));
164
165         $c->forward('findBoard');
166         $board = $c->stash->{board};
167         if ( !defined $board->{fbid}){
168                 $c->detach('/default');
169         }
170
171         my $threads = $dbh->prepare(q{
172 SELECT ft.ftid,u.username,ft.subject,ft.posts, ft.sticky
173         ,(SELECT count(*) FROM forum_posts WHERE ftid = ft.ftid
174                 AND COALESCE(time > ftv.time,TRUE)) AS unread
175         ,ft.ctime::DATE as posting_date
176         ,date_trunc('seconds',ft.mtime::timestamp) as last_post
177 FROM forum_threads ft
178         JOIN users u USING(uid)
179         LEFT OUTER JOIN (SELECT * FROM forum_thread_visits WHERE uid = $2)
180                 ftv ON ftv.ftid = ft.ftid
181 WHERE ft.posts > 0 AND ft.fbid = $1 AND (
182                 ft.fbid IN (SELECT fbid FROM forum_access WHERE gid IN (SELECT groups($2)))
183                 OR ft.ftid IN (SELECT ftid FROM forum_priv_access WHERE uid = $2)
184         )
185 GROUP BY ft.ftid, ft.subject,ft.sticky,u.username,ft.ctime,ft.mtime,ft.posts,ftv.time
186 ORDER BY sticky DESC,last_post DESC
187         });
188         $threads->execute($board->{fbid},$c->stash->{UID});
189         my @threads;
190         while (my $thread = $threads->fetchrow_hashref){
191                 push @threads,$thread;
192         }
193
194         if ( !(defined $board->{post}) && @threads == 0){
195                 $c->acl_access_denied('test',$c->action,'No access to board')
196         }
197         $c->stash(threads => \@threads);
198
199         $c->stash(title => "$board->{board} ($board->{category})");
200
201         $c->forward('listModeratorBoards', [$board->{fbid}]) if $board->{moderate};
202         
203 }
204
205 sub thread : Local {
206         my ( $self, $c, $thread ) = @_;
207         my $dbh = $c->model;
208
209         $c->forward('findThread');
210         $thread = $c->stash->{thread};
211         unless ($thread){
212                 $c->stash(template => 'default.tt2');
213                 $c->res->status(404);
214                 return;
215         }
216         my $query = $dbh->prepare(q{SELECT uid,username FROM users u
217                 JOIN forum_priv_access fta USING (uid) WHERE fta.ftid = $1});
218         $query->execute($thread->{ftid});
219         $c->stash(access => $query->fetchall_arrayref({}) );
220         $c->stash(title => $thread->{subject}
221                 . " ($thread->{category} - $thread->{board})");
222         $c->forward('findPosts');
223         $c->forward('markThreadAsRead') if $c->user_exists;
224         if ($c->stash->{thread}->{moderate}) {
225                 $c->forward('findUsers');
226                 $c->forward('listModeratorBoards', [$c->stash->{thread}->{fbid}]);
227         }
228 }
229
230 sub findPosts :Private {
231         my ( $self, $c, $thread ) = @_;
232         my $dbh = $c->model;
233
234         my $posts = $dbh->prepare(q{
235                 SELECT fpid,u.uid,u.username,date_trunc('seconds',fp.time::timestamp) AS time
236                         ,fp.message,COALESCE(fp.time > ftv.time,TRUE) AS unread
237                 FROM forum_threads ft
238                         JOIN forum_posts fp USING (ftid)
239                         JOIN users u ON u.uid = fp.uid
240                         LEFT OUTER JOIN 
241                                 (SELECT * FROM forum_thread_visits WHERE uid = $2) ftv ON ftv.ftid = ft.ftid
242                 WHERE ft.ftid = $1
243                 ORDER BY fp.time ASC
244                 });
245         $posts->execute($thread,$c->stash->{UID});
246
247         my @posts;
248         while (my $post = $posts->fetchrow_hashref){
249                 $post->{message} = parseMarkup($post->{message});
250                 push @posts,$post;
251         }
252
253         $c->stash(posts => \@posts);
254 }
255
256
257 sub markBoardAsRead : Local {
258         my ( $self, $c, $board, $time ) = @_;
259         my $dbh = $c->model;
260
261         $c->forward('findBoard');
262         $board = $c->stash->{board};
263
264         my $threads = $dbh->prepare(q{SELECT ft.ftid,ft.subject
265                         ,count(NULLIF(COALESCE(fp.time > ftv.time,TRUE),FALSE)) AS unread
266                         ,count(fp.fpid) AS posts, max(fp.time)::timestamp as last_post
267                         FROM forum_threads ft 
268                                 JOIN forum_posts fp USING (ftid) 
269                                 LEFT OUTER JOIN (SELECT * FROM forum_thread_visits WHERE uid = $2) ftv ON ftv.ftid = ft.ftid
270                         WHERE ft.fbid = $1 AND fp.time <= $3
271                         GROUP BY ft.ftid, ft.subject
272                         HAVING count(NULLIF(COALESCE(fp.time > ftv.time,TRUE),FALSE)) >= 1
273                 });
274         $threads->execute($board->{fbid},$c->user->id,$time);
275         $dbh->begin_work;
276         while (my $thread = $threads->fetchrow_hashref){
277                 $c->forward('markThreadAsRead',[$thread->{ftid}]);
278         }
279         $dbh->commit;
280         $c->forward('/redirect');
281 }
282
283 sub markThreadAsRead : Private {
284         my ( $self, $c, $thread ) = @_;
285         my $dbh = $c->model;
286
287         my $rows = $dbh->do(q{UPDATE forum_thread_visits SET time = now() 
288                 WHERE uid =     $1 AND ftid = $2
289                 },undef,$c->user->id,$thread);
290         if ($rows == 0){
291                 $dbh->do(q{INSERT INTO forum_thread_visits (uid,ftid)
292                         VALUES ($1,$2)}
293                         ,undef,$c->user->id,$thread);
294         }
295 }
296
297 sub markThreadAsUnread : Local {
298         my ( $self, $c, $thread ) = @_;
299         my $dbh = $c->model;
300
301         my ($fbid) = $dbh->selectrow_array(q{
302 SELECT fbid FROM forum_threads WHERE ftid = $1
303                 },undef, $thread);
304
305         $dbh->do(q{
306 DELETE FROM forum_thread_visits WHERE uid = $1 AND ftid = $2
307                 }, undef, $c->user->id, $thread);
308         $c->res->redirect($c->uri_for('board',$fbid));
309 }
310
311 sub markPostAsUnread : Local {
312         my ( $self, $c, $post ) = @_;
313         my $dbh = $c->model;
314
315         my ($fbid) = $dbh->selectrow_array(q{
316 SELECT fbid FROM forum_threads JOIN forum_posts USING (ftid) WHERE fpid = $1
317                 },undef, $post);
318
319         $dbh->do(q{
320 UPDATE forum_thread_visits ftv SET time = (fp.time - interval '1 second')
321 FROM forum_posts fp
322 WHERE ftv.uid = $1 AND fp.fpid = $2 AND fp.ftid = ftv.ftid
323                 }, undef, $c->user->id, $post);
324         $c->res->redirect($c->uri_for('board',$fbid));
325 }
326
327 sub moveThreads : Local {
328         my ( $self, $c, $board ) = @_;
329         my $dbh = $c->model;
330
331         $c->forward('findBoard',[$c->req->param('board')]);
332         my $toboard = $c->stash->{board};
333         unless ($toboard->{moderate}){
334                 $c->acl_access_denied('test',$c->action,'No moderator access for target board.')
335         }
336
337         $c->forward('findBoard');
338         $board = $c->stash->{board};
339         unless ($board->{moderate}){
340                 $c->acl_access_denied('test',$c->action,'No moderator access for source board.')
341         }
342
343         my $log = "Moved these threads:\n\n";
344         $dbh->begin_work;
345         my $moveThread = $dbh->prepare(q{UPDATE forum_threads SET fbid = $1 WHERE ftid = $2 AND fbid = $3});
346         for my $param ($c->req->param){
347                 if ($param =~ /t:(\d+)/){
348                         $moveThread->execute($toboard->{fbid},$1,$board->{fbid});
349                         if ($moveThread->rows > 0){
350                                 $log .= "$1\n";
351                         }
352                 }
353         }
354
355         $log .= "\nFrom board: $board->{board} ($board->{fbid})";
356         $log .= "\nTo board: $toboard->{board} ($toboard->{fbid})";
357         $dbh->do(q{INSERT INTO forum_posts (ftid,uid,message)
358                 VALUES((SELECT ftid FROM users WHERE uid = $1),$1,$2)
359                 }, undef, $c->user->id, $log);
360         $dbh->commit;
361         
362         $c->res->redirect($c->uri_for('board',$board->{fbid}));
363 }
364
365 sub newThread : Local {
366         my ( $self, $c, $board ) = @_;
367
368         $c->forward('findBoard');
369         $board = $c->stash->{board};
370
371         unless ($c->stash->{board}->{post}){
372                 $c->acl_access_denied('test',$c->action,'No post access to board.')
373         }
374
375         $c->forward('insertThread');
376         $c->forward('addPost',[$c->stash->{thread}]);
377 }
378
379 sub insertThread : Private {
380         my ( $self, $c, $board ) = @_;
381         my $dbh = $c->model;
382
383         my $insert = $dbh->prepare(q{INSERT INTO forum_threads (ftid,fbid,subject,uid)
384                 VALUES(DEFAULT,$1,$2,$3) RETURNING (ftid);
385                 });
386         $insert->execute($board,html_escape($c->req->param('subject')),$c->stash->{UID});
387         $c->stash(thread => $insert->fetchrow);
388         $insert->finish;
389 }
390
391 sub addPost : Local {
392         my ( $self, $c, $thread ) = @_;
393         my $dbh = $c->model;
394
395         if ($c->req->param('cmd') eq 'Submit'){
396                 $c->forward('findThread');
397                 unless ($c->stash->{thread}->{post}){
398                         $c->acl_access_denied('test',$c->action,'No post access to board.')
399                 }
400                 $c->forward('insertPost');
401                 $c->res->redirect($c->uri_for('thread',$thread));
402         }elsif ($c->req->param('cmd') eq 'Preview'){
403                 $c->forward('thread');
404                 $c->forward('previewPost');
405                 $c->stash(template => 'forum/thread.tt2');
406         }
407 }
408
409 sub setSticky : Local {
410         my ( $self, $c, $thread, $sticky ) = @_;
411         my $dbh = $c->model;
412
413         $c->forward('findThread');
414         unless ($c->stash->{thread}->{moderate}){
415                 $c->acl_access_denied('test',$c->action,'No moderator access to board.')
416         }
417
418         $dbh->do(q{UPDATE forum_threads SET sticky = $2 WHERE ftid = $1}
419                 , undef,$thread, $sticky);
420         $c->res->redirect($c->uri_for('thread',$thread));
421 }
422
423 sub postthreadaccess : Local {
424         my ( $self, $c, $thread) = @_;
425         my $dbh = $c->model;
426
427         $c->forward('findThread');
428         $dbh->begin_work;
429         unless ($c->stash->{thread}->{moderate}){
430                 $c->acl_access_denied('test',$c->action,'No moderator access to board.')
431         }
432         if ($c->req->param('access')){
433                 $c->req->parameters->{access} = [$c->req->parameters->{access}]
434                         unless ref $c->req->parameters->{access} eq 'ARRAY';
435                 my $query = $dbh->prepare(q{DELETE From forum_priv_access
436                         WHERE ftid = $1 AND uid = ANY ($2)});
437                 $query->execute($thread,$c->req->parameters->{access});
438                 $dbh->do(q{INSERT INTO forum_posts (ftid,uid,message)
439                         VALUES((SELECT ftid FROM users WHERE uid = $1),$1,$2)
440                         }, undef, $c->user->id
441                         ,"Removed access on thread $thread for : @{$c->req->parameters->{access}}");
442         }
443         if ($c->req->param('uid')){
444                 $c->forward('addaccess');
445         }
446         $dbh->commit;
447         $c->res->redirect($c->uri_for('thread',$thread));
448 }
449
450 sub removeownthreadaccess : Local {
451         my ( $self, $c, $thread) = @_;
452         my $dbh = $c->model;
453         $dbh->do(q{DELETE FROM forum_priv_access WHERE uid = $1 AND ftid = $2}
454                 ,undef,$c->user->id,$thread);
455         $c->res->redirect($c->uri_for('allUnread'));
456 }
457
458 sub privmsg : Local {
459         my ( $self, $c, $uid ) = @_;
460
461         $uid ||= 0;
462         $c->stash(uid => $uid);
463
464         $c->forward('findUsers');
465 }
466
467 sub postprivmsg : Local {
468         my ( $self, $c ) = @_;
469         my $dbh = $c->model;
470
471         $dbh->begin_work;
472         $c->forward('insertThread',[-1999]);
473
474         $c->req->parameters->{uid} = [$c->req->parameters->{uid}]
475                 unless ref $c->req->parameters->{uid} eq 'ARRAY';
476         push @{$c->req->parameters->{uid}}, $c->user->id;
477         $c->forward('addaccess',[$c->stash->{thread}]);
478
479         $c->forward('addPost',[$c->stash->{thread}]);
480         $dbh->commit;
481 }
482
483 sub addaccess : Private {
484         my ( $self, $c, $thread) = @_;
485         my $dbh = $c->model;
486
487         $c->req->parameters->{uid} = [$c->req->parameters->{uid}]
488                 unless ref $c->req->parameters->{uid} eq 'ARRAY';
489         my $query = $dbh->prepare(q{INSERT INTO forum_priv_access (ftid,uid)
490                 (SELECT $1,uid FROM users u WHERE uid = ANY ($2) AND NOT uid
491                         IN (SELECT uid FROM forum_priv_access WHERE ftid = $1))});
492         $query->execute($thread,$c->req->parameters->{uid});
493         $dbh->do(q{INSERT INTO forum_posts (ftid,uid,message)
494                 VALUES((SELECT ftid FROM users WHERE uid = $1),$1,$2)
495                 }, undef, $c->user->id
496                 ,"Gave access on thread $thread to : @{$c->req->parameters->{uid}}");
497 }
498
499 sub findUsers : Private {
500         my ( $self, $c ) = @_;
501         my $dbh = $c->model;
502
503         my $query = $dbh->prepare(q{SELECT uid,username FROM users
504                 WHERE uid > 0 AND uid IN (SELECT uid FROM groupmembers)
505                 ORDER BY username});
506         $query->execute;
507
508         $c->stash(users => $query->fetchall_arrayref({}) );
509 }
510
511 sub findThread : Private {
512         my ( $self, $c, $thread ) = @_;
513         my $dbh = $c->model;
514         my $findThread = $dbh->prepare(q{SELECT ft.ftid,ft.subject
515                 ,COALESCE(bool_or(fa.post),true) AS post, bool_or(fa.moderate) AS moderate
516                 ,ft.fbid,fb.board,fb.fcid,ft.sticky,fc.category
517                 FROM forum_boards fb
518                         NATURAL JOIN forum_threads ft
519                         NATURAL JOIN forum_categories fc
520                         LEFT OUTER JOIN (SELECT fa.* FROM forum_access fa
521                                 JOIN (SELECT groups($2) AS gid) g USING (gid)
522                         ) fa USING (fbid)
523                 WHERE ft.ftid = $1 AND (fa.post IS NOT NULL
524                         OR ft.ftid IN (SELECT ftid FROM forum_priv_access WHERE uid = $2))
525                 GROUP BY ft.ftid,ft.subject,ft.fbid,fb.board,fb.fcid,ft.sticky,fc.category
526         });
527         $thread = $dbh->selectrow_hashref($findThread,undef,$thread,$c->stash->{UID});
528         $c->stash(thread => $thread);
529 }
530
531 sub findBoard : Private {
532         my ( $self, $c, $board ) = @_;
533         my $dbh = $c->model;
534
535         my $boards = $dbh->prepare(q{SELECT fb.fbid,fb.board, bool_or(fa.post) AS post, bool_or(fa.moderate) AS moderate,fb.fcid, fc.category
536                         FROM forum_boards fb
537                                 NATURAL JOIN forum_categories fc
538                                 LEFT OUTER JOIN (SELECT * FROM forum_access
539                                         WHERE fbid = $1 AND gid IN (SELECT groups($2))
540                                 ) fa USING (fbid)
541                         WHERE fb.fbid = $1
542                         GROUP BY fb.fbid,fb.board,fb.fcid,fc.category
543                 });
544         $board = $dbh->selectrow_hashref($boards,undef,$board,$c->stash->{UID});
545
546         $c->stash(board => $board);
547 }
548
549 sub previewPost : Private {
550         my ( $self, $c) = @_;
551         push @{$c->stash->{posts}}, {
552                 unread => 1,
553                 username => 'PREVIEW',
554                 message => parseMarkup(html_escape $c->req->param('message')),
555         };
556         $c->stash(previewMessage => html_escape $c->req->param('message'));
557 }
558
559 sub insertPost : Private {
560         my ( $self, $c, $thread ) = @_;
561         my $dbh = $c->model;
562
563         my $insert = $dbh->prepare(q{INSERT INTO forum_posts (ftid,message,uid)
564                 VALUES($1,$2,$3)});
565         $insert->execute($thread,html_escape($c->req->param('message')),$c->stash->{UID});
566 }
567
568 sub listModeratorBoards : Private {
569         my ( $self, $c, $fbid ) = @_;
570         my $dbh = $c->model;
571
572         my $categories = $dbh->prepare(q{SELECT fcid,category FROM forum_categories ORDER BY fcid});
573         my $boards = $dbh->prepare(q{SELECT fb.fbid,fb.board, bool_or(fa.post) AS post
574                 FROM forum_boards fb NATURAL JOIN forum_access fa
575                 WHERE fb.fcid = $1
576                         AND gid IN (SELECT groups($2))
577                         AND moderate
578                 GROUP BY fb.fbid,fb.board
579                 ORDER BY fb.fbid
580                 });
581         $categories->execute;
582         my @categories;
583         while (my $category = $categories->fetchrow_hashref){
584                 $boards->execute($category->{fcid},$c->stash->{UID});
585
586                 my @boards;
587                 while (my $b = $boards->fetchrow_hashref){
588                         next if ($b->{fbid} == $fbid);
589                         push @boards,$b;
590                 }
591                 $category->{boards} = \@boards;
592                 push @categories,$category if @boards;
593         }
594         $c->stash(categories => \@categories);
595 }
596
597 =head1 AUTHOR
598
599 Michael Andreen (harv@ruin.nu)
600
601 =head1 LICENSE
602
603 GPL 2.0, or later.
604
605 =cut
606
607 1;