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