! Copyright (C) 2008 Slava Pestov ! See http://factorcode.org/license.txt for BSD license. USING: kernel accessors sequences sorting math.order math.parser urls validators db db.types db.tuples calendar present namespaces html.forms html.components http.server.dispatchers furnace furnace.actions furnace.redirection furnace.auth furnace.auth.login furnace.boilerplate furnace.syndication ; 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 f { { "id" "ID" INTEGER +db-assigned-id+ } { "author" "AUTHOR" { VARCHAR 256 } +not-null+ } ! uid { "date" "DATE" TIMESTAMP +not-null+ } { "content" "CONTENT" TEXT +not-null+ } } define-persistent M: entity feed-entry-date date>> ; TUPLE: post-state < entity title comments ; M: post-state feed-entry-title [ author>> ] [ title>> ] bi ": " glue ; M: post-state entity-url id>> view-post-url ; \ post-state "BLOG_POSTS" { { "title" "TITLE" { VARCHAR 256 } +not-null+ } } define-persistent : ( id -- post ) \ post-state new swap >>id ; TUPLE: comment < entity parent ; comment "COMMENTS" { { "parent" "PARENT" INTEGER +not-null+ } ! post id } define-persistent M: comment feed-entry-title author>> "Comment by " prepend ; M: comment entity-url [ parent>> ] [ id>> ] bi view-comment-url ; : ( parent id -- post ) comment new swap >>id swap >>parent ; : post ( id -- post ) [ select-tuple ] [ f select-tuples ] bi >>comments ; : reverse-chronological-order ( seq -- sorted ) [ date>> ] inv-sort-with ; : validate-author ( -- ) { { "author" [ v-username ] } } validate-params ; : list-posts ( -- posts ) f "author" value >>author select-tuples [ dup id>> f count-tuples >>comments ] map reverse-chronological-order ; : ( -- action ) [ list-posts "posts" set-value ] >>init { blogs "list-posts" } >>template ; : ( -- action ) [ "Recent Posts" ] >>title [ list-posts ] >>entries [ list-posts-url ] >>url ; : ( -- action ) "author" >>rest [ validate-author list-posts "posts" set-value ] >>init { blogs "posts-by" } >>template ; : ( -- action ) "author" >>rest [ validate-author ] >>init [ "Recent Posts by " "author" value append ] >>title [ list-posts ] >>entries [ "author" value posts-by-url ] >>url ; : ( -- 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 ; : ( -- 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 ; : ( -- action ) [ validate-post username "author" set-value ] >>validate [ f dup { "title" "content" } to-object username >>author now >>date [ insert-tuple ] [ entity-url ] bi ] >>submit { blogs "new-post" } >>template "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 select-tuple from-object ; : ( -- action ) "id" >>rest [ do-post-action ] >>init [ do-post-action validate-post ] >>validate [ "author" value authorize-author ] >>authorize [ "id" value dup { "title" "author" "date" "content" } to-object [ update-tuple ] [ entity-url ] bi ] >>submit { blogs "edit-post" } >>template "edit a blog post" >>description ; : delete-post ( id -- ) [ delete-tuples ] [ f delete-tuples ] bi ; : ( -- action ) [ do-post-action ] >>validate [ "author" value authorize-author ] >>authorize [ [ "id" value delete-post ] with-transaction "author" value posts-by-url ] >>submit "delete a blog post" >>description ; : ( -- action ) [ validate-author ] >>validate [ "author" value authorize-author ] >>authorize [ [ f "author" value >>author select-tuples [ id>> delete-post ] each f f "author" value >>author delete-tuples ] with-transaction "author" value posts-by-url ] >>submit "delete a blog post" >>description ; : validate-comment ( -- ) { { "parent" [ v-integer ] } { "content" [ v-required ] } } validate-params ; : ( -- action ) [ validate-comment username "author" set-value ] >>validate [ "parent" value f "content" value >>content username >>author now >>date [ insert-tuple ] [ entity-url ] bi ] >>submit "make a comment" >>description ; : ( -- action ) [ validate-integer-id { { "parent" [ v-integer ] } } validate-params ] >>validate [ "parent" value select-tuple author>> authorize-author ] >>authorize [ f "id" value delete-tuples "parent" value view-post-url ] >>submit "delete a comment" >>description ; : ( -- dispatcher ) blogs new-dispatcher "" add-responder "posts.atom" add-responder "by" add-responder "by.atom" add-responder "post" add-responder "post.atom" add-responder "new-post" add-responder "edit-post" add-responder "delete-post" add-responder "new-comment" add-responder "delete-comment" add-responder { blogs "blogs-common" } >>template ;