module Handler.Blog where
import Data.Time
import Import
import Control.Monad
import Yesod.Auth
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import qualified Data.Text.Lazy as LT
import qualified Data.Text.Lazy.Encoding as LT
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as LBS
import Data.List (sortBy, isPrefixOf)
import Data.Function
import qualified Data.Map as M
import qualified Data.Conduit.Binary as BC
import Data.Conduit
import Text.Hamlet.XML
import Text.XML
import Text.XML.Cursor
import Blaze.ByteString.Builder
import Data.Maybe
import Network.HTTP.Conduit hiding (def)
import Network.HTTP.Types
import qualified Network.Wai as W
import System.Directory
import Control.Monad.Trans.Resource
import System.FilePath
import System.IO.Temp
import Network.URI
postCreateR :: Handler RepHtml
postCreateR = do
((result, widget), enctype) <- runFormPost articleForm
case result of
FormSuccess (article, tags, tbs) -> do
usr <- requireAuthId
when (articleAuthor article /= usr) $ redirect RootR
success <- runDB $ do
ans <- insertBy article
case ans of
Right key -> do
mapM_ (insertBy . Tag key) tags
return True
Left _ -> return False
if success
then do
errs <- catMaybes <$> mapM (pingTrackback article) tbs
unless (null errs) $ setMessageI $ T.unlines errs
redirect $ ArticleR (toEnum $ articleCreatedDate article) (articleIdent article)
else do
setMessageI $ MsgAlreadyExists $ articleTitle article
defaultLayout $(widgetFile "post-article")
_ -> do
setMessageI MsgInvalidInput
defaultLayout $(widgetFile "post-article")
getCreateR :: Handler RepHtml
getCreateR = do
(widget, enctype) <- generateFormPost articleForm
defaultLayout $ do
$(widgetFile "post-article")
getArticleR :: YablogDay -> Text -> Handler RepHtml
getArticleR = withArticle $ \(Entity key article) -> do
let date = toEnum $ articleCreatedDate article
ident = articleIdent article
musr <- maybeAuthId
(comments, trackbacks, mprev, mnext) <- runDB $ do
cs <- map entityVal <$> selectList [CommentArticle ==. key] []
ts <- map entityVal <$> selectList [TrackbackArticle ==. key] []
mnext <- selectFirst [ ArticleCreatedDate >=. articleCreatedDate article
, FilterOr [ ArticleCreatedDate >. articleCreatedDate article
, ArticleCreatedTime >. articleCreatedTime article
]
]
[ Asc ArticleCreatedDate, Asc ArticleCreatedTime]
mprev <- selectFirst [ ArticleCreatedDate <=. articleCreatedDate article
, FilterOr [ ArticleCreatedDate <. articleCreatedDate article
, ArticleCreatedTime <. articleCreatedTime article
]
]
[ Desc ArticleCreatedDate, Desc ArticleCreatedTime ]
return (cs, ts, entityVal <$> mprev, entityVal <$> mnext)
(cWidget, cEnctype) <- generateFormPost $ commentForm Nothing article
let mCommentForm = Just (cWidget, cEnctype)
blogTitle <- getBlogTitle
render <- getUrlRender
defaultLayout $ do
when (isJust mprev) $ do
let prev = fromJust mprev
toWidgetHead
[hamlet|
|]
when (isJust mnext) $ do
let next = fromJust mnext
toWidgetHead
[hamlet|
|]
setTitle $ toHtml $ T.concat [articleTitle article, " - ", blogTitle]
$(widgetFile "article")
pingTrackback :: Article -> String -> Handler (Maybe T.Text)
pingTrackback article tb = do
renderUrl <- getUrlRender
master <- getYesod
blogName <- getBlogTitle
let meta = [("title", T.encodeUtf8 $ articleTitle article)
,("excerpt", T.encodeUtf8 $ T.take 255 $ T.pack $ articleBody article)
,("url", T.encodeUtf8 $ renderUrl $ articleLink article)
,("blog_name", T.encodeUtf8 blogName)
]
man = httpManager master
rsp <- lift $ flip httpLbs man . urlEncodedBody meta =<< parseUrl tb
if responseStatus rsp /= status200
then return $ Just $ T.concat [ T.pack tb
, ": HTTP Error: "
, T.concat (LT.toChunks $ LT.decodeUtf8 $ responseBody rsp)
]
else do
case fromDocument <$> parseLBS def (responseBody rsp) of
Right root -> do
let code = T.concat $ root $// checkName (== "error") >=> descendant >=> content
msgs = T.concat $ root $// checkName (== "message") >=> descendant >=> content
if code == "0"
then return Nothing
else return $ Just $ T.concat [T.pack tb, ": ", msgs]
Left _ -> return $ Just $ T.concat [T.pack tb, ": malformed response"]
putArticleR :: YablogDay -> Text -> Handler RepHtml
putArticleR (YablogDay day) ident = do
((result, _), _) <- runFormPost articleForm
usrId <- requireAuthId
time <- liftIO getCurrentTime
case result of
FormSuccess (article, tags, tbs) -> do
suc <- runDB $ do
Entity key old <- getBy404 $ UniqueArticle (fromEnum day) ident
if articleAuthor old == usrId
then do
replace key article { articleModifiedAt = Just time }
mapM_ (delete . entityKey) =<< selectList [TagArticle ==. key] []
mapM_ (insert . Tag key) tags
return True
else return False
if suc
then do
errs <- catMaybes <$> mapM (pingTrackback article) tbs
unless (null errs) $ setMessageI $ T.unlines errs
redirect $ ArticleR (YablogDay day) $ articleIdent article
else permissionDenied "You are not allowed to edit this article."
_ -> do
setMessageI MsgInvalidInput
redirect $ ModifyR (YablogDay day) ident
getDeleteR :: YablogDay -> Text -> Handler RepHtml
getDeleteR = deleteArticleR
getModifyR :: YablogDay -> Text -> Handler RepHtml
getModifyR = withArticleAuth $ \(Entity artId art) -> do
tags <- runDB $ map (tagName . entityVal) <$> selectList [TagArticle ==. artId] []
(widget, enctype) <- generateFormPost $ articleForm' (Just art) (Just tags)
(cWidget, cEnctype) <- generateFormPost $ commentDeleteForm artId
(tWidget, tEnctype) <- generateFormPost $ trackbackDeleteForm artId
let mCommentTrackbackForm = Just (cWidget, cEnctype, tWidget, tEnctype)
day = toEnum $ articleCreatedDate art
ident = articleIdent art
mAttachments <- articleAttachments art
defaultLayout $ do
setTitleI $ MsgEdit $ articleTitle art
addScript $ StaticR js_jquery_upload_1_0_2_min_js
$(widgetFile "edit-article")
articleAttachments :: Article -> Handler (Maybe [FilePath])
articleAttachments art = liftIO $ do
let dir = attachmentDir art
exists <- doesDirectoryExist dir
if exists
then do
children <- filter (not . ("." `isPrefixOf`)) <$> getDirectoryContents dir
if null children
then return Nothing
else return $ Just $ map (("/" > dir) >) children
else return Nothing
getDeleteAttachmentR :: YablogDay -> Text -> FilePath -> Handler ()
getDeleteAttachmentR d@(YablogDay day) ident fp = withArticleAuth act d ident
where
act (Entity artId art) = do
usr <- requireAuthId
tags <- runDB $ map (tagName . entityVal) <$> selectList [TagArticle ==. artId] []
exc <- liftIO $ doesFileExist $ attachmentDir art > fp
unless exc $ do
setMessageI $ MsgAttachmentNotFound fp
redirect $ ModifyR (YablogDay day) ident
liftIO $ removeFile $ attachmentDir art > fp
render <- getUrlRender
redirect $ render (ModifyR (YablogDay day) ident) `T.append` "#attachments"
postDeleteCommentR :: YablogDay -> Text -> Handler ()
postDeleteCommentR = deleteCommentR
postModifyR :: YablogDay -> Text -> Handler RepHtml
postModifyR = putArticleR
deleteArticleR :: YablogDay -> Text -> Handler RepHtml
deleteArticleR = withArticleAuth $ \(Entity key _) -> do
runDB $ do
mapM_ (delete . entityKey) =<< selectList [TagArticle ==. key] []
delete key
redirect RootR
postCommentR :: YablogDay -> Text -> Handler RepHtml
postCommentR = withArticle $ \(Entity key article) -> do
addr <- getIPAddrProxy
isBanned <- isJust <$> runDB (selectFirst [BannedIp ==. Just addr] [])
when isBanned $ permissionDenied "YOU ARE BANNED TO COMMENT"
((result, _), _) <- runFormPost $ commentForm' Nothing key
case result of
FormSuccess comment -> do
ans <- runDB $ insertBy comment
case ans of
Right _ -> do
render <- getUrlRender
let anchor = commentAnchor comment
let url = T.concat [render $ ArticleR (toEnum $ articleCreatedDate article) (articleIdent article)
, "#", anchor
]
msgRender <- getMessageRender
notice (articleAuthor article) (msgRender MsgNewComment) $
T.unlines [ msgRender (MsgYouHaveNewCommentFor url)
, ""
, "\"" `T.append` commentBody comment `T.append` "\""
, "\nby " `T.append` commentAuthor comment
]
redirect url
Left _ -> do
setMessageI $ MsgAlreadyExists $ articleTitle article
redirect $ ArticleR (toEnum $ articleCreatedDate article) (articleIdent article)
_ -> do
setMessageI MsgInvalidInput
redirect $ ArticleR (toEnum $ articleCreatedDate article) (articleIdent article)
putCommentR :: YablogDay -> Text -> Handler ()
putCommentR = undefined
deleteTrackbackR :: YablogDay -> Text -> Handler ()
deleteTrackbackR = withArticleAuth $ \(Entity aid art) -> do
let day = toEnum $ articleCreatedDate art
ident = articleIdent art
Entity uid _ <- requireAuth
Entity aid art <- runDB $ getBy404 $ UniqueArticle (fromEnum day) ident
((result, _), _) <- runFormPost $ trackbackDeleteForm aid
when (uid /= articleAuthor art) $ do
permissionDenied "You are not allowed to delete those comment(s)."
case result of
FormSuccess cs -> do
when (any ((/= aid) . trackbackArticle) cs) $ permissionDenied "You can't delete that comment."
runDB $ mapM_ (\c -> deleteBy $ UniqueTrackback aid (trackbackUrl c)) cs
redirect $ ArticleR (YablogDay day) (articleIdent art)
_ -> do
setMessageI MsgInvalidInput
redirect $ ModifyR (YablogDay day) (articleIdent art)
postDeleteTrackbackR :: YablogDay -> Text -> Handler ()
postDeleteTrackbackR = deleteTrackbackR
deleteCommentR :: YablogDay -> Text -> Handler ()
deleteCommentR = withArticleAuth $ \(Entity aid art) -> do
let day = toEnum $ articleCreatedDate art
((result, _), _) <- runFormPost $ commentDeleteForm aid
case result of
FormSuccess (cs, ban) -> do
when (any ((/= aid) . commentArticle) cs) $ permissionDenied "You can't delete that comment."
runDB $ forM_ cs $ \c -> do
deleteBy $ UniqueComment aid (commentAuthor c) (commentCreatedAt c)
when ban $ void $ insert $ Banned (Just $ commentIpAddress c) Nothing
redirect $ ArticleR (YablogDay day) (articleIdent art)
_ -> do
setMessageI MsgInvalidInput
redirect $ ModifyR (YablogDay day) (articleIdent art)
postAttachR :: YablogDay -> Text -> Handler ()
postAttachR = withArticleAuth $ \(Entity _ article) -> do
(_, files) <- runRequestBody
case lookup "file" files of
Just finfo -> do
let dir = attachmentDir article
liftIO $ do
createDirectoryIfMissing True dir
lift $ fileSource finfo $$ BC.sinkFile (dir > T.unpack (fileName finfo))
Nothing -> notFound
postPreviewR :: Handler RepHtml
postPreviewR = do
author <- userScreenName . entityVal <$> requireAuth
((result, _), _) <- runFormPost articleForm
case result of
FormSuccess (article, tags, tbs) -> do
let editable = False
comments = []
mCommentTrackbackForm = Nothing :: Maybe (Widget, Text, Widget, Text)
title = articleTitle article
posted = show $ UTCTime (toEnum $ articleCreatedDate article) (toEnum $ articleCreatedTime article)
date = toEnum $ articleCreatedDate article :: Day
route = Nothing :: Maybe Text
ident = articleIdent article
mnext = Nothing
mprev = Nothing
blogTitle <- getBlogTitle
body <- markupRender Nothing article
defaultLayout $ do
$(widgetFile "article-view")
_ -> notFound
getTagR :: Text -> Handler RepHtml
getTagR tag = do
articles <- runDB $ do
mapM (get404 . tagArticle . entityVal) =<< selectList [TagName ==. tag] []
when (null articles) notFound
defaultLayout $ do
setTitleI $ MsgArticlesForTag tag
$(widgetFile "tag")
postTrackbackR :: YablogDay -> Text -> Handler RepXml
postTrackbackR = withArticle $ \(Entity aid article) -> do
let date = toEnum $ articleCreatedDate article
ident = articleIdent article
trackback <- runInputPost $
Trackback aid <$> iopt textField "title"
<*> (liftM unTextarea <$> iopt textareaField "excerpt")
<*> ireq textField "url"
<*> iopt textField "blog_name"
man <- httpManager <$> getYesod
curUrl <- getUrlRender <*> pure (ArticleR (YablogDay date) ident)
alert <- lift $ do
rsp <- flip httpLbs man =<< parseUrl (T.unpack $ trackbackUrl trackback)
if responseStatus rsp == status200
then
if T.encodeUtf8 curUrl `BS.isInfixOf` BS.concat (LBS.toChunks (responseBody rsp))
then return Nothing
else return $ Just "Your page does not include link to my page."
else return $ Just "HTTP Error"
case alert of
Nothing -> do
runDB $ do
ans <- insertBy trackback
case ans of
Right _ -> return ()
Left (Entity k _) -> replace k trackback
return $ mkXmlResponse [xml|0|]
Just err ->
return $ mkXmlResponse [xml|1
#{err}
|]
getTrackbackR :: YablogDay -> Text -> Handler RepXml
getTrackbackR (YablogDay date) ident = do
(art, ts) <- runDB $ do
Entity aid art <- getBy404 $ UniqueArticle (fromEnum date) ident
ts <- map entityVal <$> selectList [TrackbackArticle ==. aid] []
return (art, ts)
render <- getUrlRender
bTitle <- getBlogTitle
return $ mkXmlResponse [xml|
0
#{articleTitle art} - #{bTitle}
#{render $ ArticleR (YablogDay date) ident}
#{articleTitle art} - #{bTitle}
ja
$forall t <- ts
-
#{fromMaybe (trackbackUrl t) $ trackbackTitle t}
#{trackbackUrl t}
$maybe e <- trackbackExcerpt t
#{e}
|]
mkXmlResponse :: [Node] -> RepXml
mkXmlResponse nodes = RepXml $
ContentBuilder (fromLazyByteString $ renderLBS def $
Document (Prologue [] Nothing []) body [])
Nothing
where
body = Element "response" M.empty nodes