Paste: extra\webapps\blogs
Author: | jim |
Mode: | factor |
Date: | Mon, 8 Nov 2010 07:09:09 |
Plain Text |
USING: accessors calendar db db.sqlite db.tuples db.types fry
furnace furnace.actions furnace.alloy furnace.auth
furnace.auth.login furnace.boilerplate furnace.redirection
furnace.syndication html.components html.forms http.server
http.server.dispatchers io.directories io.pathnames kernel
logging math.order math.parser namespaces present sequences
sorting threads urls validators ;
IN: webapps.blogs
TUPLE: blogs < dispatcher ;
SYMBOL: can-administer-blogs?
can-administer-blogs? define-capability
: view-post-url ( id -- url )
present "$blogs/post/" prepend >url ;
: view-comment-url ( parent id -- url )
[ view-post-url ] dip >>anchor ;
: list-posts-url ( -- url )
"$blogs/" >url ;
: posts-by-url ( author -- url )
"$blogs/by/" prepend >url ;
TUPLE: entity id author date content ;
GENERIC: entity-url ( entity -- url )
M: entity feed-entry-url entity-url ;
entity "entity" {
{ "id" "ID" INTEGER +db-assigned-id+ }
{ "author" "AUTHOR" { VARCHAR 256 } +not-null+ }
{ "date" "DATE" TIMESTAMP +not-null+ }
{ "content" "CONTENT" TEXT +not-null+ }
} define-persistent
M: entity feed-entry-date date>> ;
TUPLE: post < entity title comments ;
M: post feed-entry-title
[ author>> ] [ title>> ] bi ": " glue ;
M: post entity-url
id>> view-post-url ;
\ post "BLOG_POSTS" {
{ "title" "TITLE" { VARCHAR 256 } +not-null+ }
} define-persistent
: <post> ( id -- post ) \ post new swap >>id ;
TUPLE: comment < entity parent ;
comment "COMMENTS" {
{ "parent" "PARENT" INTEGER +not-null+ }
} define-persistent
M: comment feed-entry-title
author>> "Comment by " prepend ;
M: comment entity-url
[ parent>> ] [ id>> ] bi view-comment-url ;
: <comment> ( parent id -- post )
comment new
swap >>id
swap >>parent ;
: post ( id -- post )
[ <post> select-tuple ] [ f <comment> select-tuples ] bi
>>comments ;
: reverse-chronological-order ( seq -- sorted )
[ date>> ] inv-sort-with ;
: validate-author ( -- )
{ { "author" [ v-username ] } } validate-params ;
: list-posts ( -- posts )
f <post> "author" value >>author
select-tuples [ dup id>> f <comment> count-tuples >>comments ] map
reverse-chronological-order ;
: <list-posts-action> ( -- action )
<page-action>
[ list-posts "posts" set-value ] >>init
{ blogs "list-posts" } >>template ;
: <list-posts-feed-action> ( -- action )
<feed-action>
[ "Recent Posts" ] >>title
[ list-posts ] >>entries
[ list-posts-url ] >>url ;
: <posts-by-action> ( -- action )
<page-action>
"author" >>rest
[
validate-author
list-posts "posts" set-value
] >>init
{ blogs "posts-by" } >>template ;
: <posts-by-feed-action> ( -- action )
<feed-action>
"author" >>rest
[ validate-author ] >>init
[ "Recent Posts by " "author" value append ] >>title
[ list-posts ] >>entries
[ "author" value posts-by-url ] >>url ;
: <post-feed-action> ( -- action )
<feed-action>
"id" >>rest
[ validate-integer-id "id" value post "post" set-value ] >>init
[ "post" value feed-entry-title ] >>title
[ "post" value entity-url ] >>url
[ "post" value comments>> ] >>entries ;
: <view-post-action> ( -- action )
<page-action>
"id" >>rest
[
validate-integer-id
"id" value post from-object
"id" value
"new-comment" [
"parent" set-value
] nest-form
] >>init
{ blogs "view-post" } >>template ;
: validate-post ( -- )
{
{ "title" [ v-one-line ] }
{ "content" [ v-required ] }
} validate-params ;
: <new-post-action> ( -- action )
<page-action>
[
validate-post
username "author" set-value
] >>validate
[
f <post>
dup { "title" "content" } to-object
username >>author
now >>date
[ insert-tuple ] [ entity-url <redirect> ] bi
] >>submit
{ blogs "new-post" } >>template
<protected>
"make a new blog post" >>description ;
: authorize-author ( author -- )
username =
{ can-administer-blogs? } have-capabilities? or
[ "edit a blog post" f login-required ] unless ;
: do-post-action ( -- )
validate-integer-id
"id" value <post> select-tuple from-object ;
: <edit-post-action> ( -- action )
<page-action>
"id" >>rest
[ do-post-action ] >>init
[ do-post-action validate-post ] >>validate
[ "author" value authorize-author ] >>authorize
[
"id" value <post>
dup { "title" "author" "date" "content" } to-object
[ update-tuple ] [ entity-url <redirect> ] bi
] >>submit
{ blogs "edit-post" } >>template
<protected>
"edit a blog post" >>description ;
: delete-post ( id -- )
[ <post> delete-tuples ] [ f <comment> delete-tuples ] bi ;
: <delete-post-action> ( -- action )
<action>
[ do-post-action ] >>validate
[ "author" value authorize-author ] >>authorize
[
[ "id" value delete-post ] with-transaction
"author" value posts-by-url <redirect>
] >>submit
<protected>
"delete a blog post" >>description ;
: <delete-author-action> ( -- action )
<action>
[ validate-author ] >>validate
[ "author" value authorize-author ] >>authorize
[
[
f <post> "author" value >>author select-tuples [ id>> delete-post ] each
f f <comment> "author" value >>author delete-tuples
] with-transaction
"author" value posts-by-url <redirect>
] >>submit
<protected>
"delete a blog post" >>description ;
: validate-comment ( -- )
{
{ "parent" [ v-integer ] }
{ "content" [ v-required ] }
} validate-params ;
: <new-comment-action> ( -- action )
<action>
[
validate-comment
username "author" set-value
] >>validate
[
"parent" value f <comment>
"content" value >>content
username >>author
now >>date
[ insert-tuple ] [ entity-url <redirect> ] bi
] >>submit
<protected>
"make a comment" >>description ;
: <delete-comment-action> ( -- action )
<action>
[
validate-integer-id
{ { "parent" [ v-integer ] } } validate-params
] >>validate
[
"parent" value <post> select-tuple
author>> authorize-author
] >>authorize
[
f "id" value <comment> delete-tuples
"parent" value view-post-url <redirect>
] >>submit
<protected>
"delete a comment" >>description ;
: <blogs> ( -- dispatcher )
blogs new-dispatcher
<list-posts-action> "" add-responder
<list-posts-feed-action> "posts.atom" add-responder
<posts-by-action> "by" add-responder
<posts-by-feed-action> "by.atom" add-responder
<view-post-action> "post" add-responder
<post-feed-action> "post.atom" add-responder
<new-post-action> "new-post" add-responder
<edit-post-action> "edit-post" add-responder
<delete-post-action> "delete-post" add-responder
<new-comment-action> "new-comment" add-responder
<delete-comment-action> "delete-comment" add-responder
<boilerplate>
{ blogs "blogs-common" } >>template ;
: <blogs-responder> ( -- responder )
blogs new-dispatcher
<blogs> "blogs" add-responder
<blogs> >>default ;
: blog-db ( -- db ) "blogs" home prepend-path [ make-directories ] [ "blogs.sqlite" append-path ] bi <sqlite-db> ;
: with-blog-data ( quot -- ) '[ blog-db _ with-db ] call ; inline
: init-db ( -- ) [ init-furnace-tables
entity ensure-table
\ post ensure-table
comment ensure-table
] with-blog-data ;
: blog-server ( -- ) "blogs" [ <blogs-responder>
blog-db <alloy>
main-responder set-global ] with-logging ;
: launch ( -- ) [ 80 httpd ] in-thread ;
New Annotation