! Copyright (C) 2008 Slava Pestov ! See http://factorcode.org/license.txt for BSD license. 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+ } ! uid { "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 : ( id -- post ) \ post 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 ; : ( -- responder ) blogs new-dispatcher "blogs" add-responder >>default ; : blog-db ( -- db ) "blogs" home prepend-path [ make-directories ] [ "blogs.sqlite" append-path ] bi ; : 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" [ blog-db main-responder set-global ] with-logging ; : launch ( -- ) [ 80 httpd ] in-thread ; ! make sure sqlite dlls in factor folder: sqlite3.exe, .dll, .def ! init-db ! only once needed ! blog-server launch ! browse to localhost:8080/blogs ! look for log in C:\factor\logs\http.server ! look for sqlite in C:\Documents and Settings\you\blogs ! I can see the menu, but can't add a blog - seems to be ssl security problem, but it may help you see how things work.