{-# LANGUAGE OverloadedStrings, RecordWildCards #-}
module Main where
import Application
import Templates
import Types
import Control.Applicative ((<|>))
import Control.Monad.IO.Class (liftIO)
import Data.ByteString.Char8 (ByteString, readInt, unpack)
import Data.Default (def)
import qualified Data.Text as T
import Data.Text.Encoding (encodeUtf8, decodeUtf8)
import Snap.Core
import Snap.Util.FileServe
import Snap.Http.Server
import Snap.Snaplet
import Snap.Snaplet.Auth
import Snap.Snaplet.Auth.Backends.SqliteSimple
import Snap.Snaplet.Session.Backends.CookieSession
import Snap.Snaplet.SqliteSimple
import Text.Blaze (toMarkup)
import Text.Blaze.Html.Renderer.Utf8 (renderHtmlBuilder)
basicError :: Response
basicError = setResponseCode 400 emptyResponse
main :: IO ()
main = do
(_, s, _) <- runSnaplet Nothing app
quickHttpServe s
handleLogin :: Maybe T.Text -> Handler App (AuthManager App) ()
handleLogin _ = writeBuilder (renderHtmlBuilder (errorPage err msg))
where err = "Authentication failed!"
msg = "Unknown user or password"
handleLoginSubmit :: Handler App (AuthManager App) ()
handleLoginSubmit = loginUser "user" "passwd" Nothing (\_ -> handleLogin err) (redirect "/")
where err = Just "Unknown user or password"
handleLogout :: Handler App (AuthManager App) ()
handleLogout = logout >> redirect "/"
app :: SnapletInit App App
app = makeSnaplet "khuzd" "Strike the earth!" Nothing $ do
s <- nestSnaplet "sess" sess $ initCookieSessionManager "site_key.txt" "sess" (Just 3600)
d <- nestSnaplet "db" db sqliteInit
a <- nestSnaplet "auth" auth $ initSqliteAuth sess d
addRoutes routes
return $ App s d a
routes :: [(ByteString, AppHandler ())]
routes = [ ("/", method POST doAddPost <|>
(doIndex >>= doPage))
, ("/auth", doLoginForm >>= doPage)
, ("/archive", doArchive >>= doPage)
, ("/create", doCreate >>= doPage)
, ("/:year/:month/:slug", doPost >>= doPage)
, ("/:year/:month/:slug/edit", editPost >>= doPage)
, ("/login", with auth handleLoginSubmit)
, ("/logout", with auth handleLogout)
, ("/newest", doNewestRedirect)
, ("/oldest", doOldestRedirect)
, ("/change", method POST doPasswdChange <|>
(doPasswdForm >>= doPage))
, ("/static", serveDirectory "static")
, ("/newuser", method POST doNewUser)
]
doPasswdForm :: AppHandler Page
doPasswdForm = do
user <- fmap userLogin `fmap` with auth currentUser
case user of
Just u -> return (PasswdForm u)
Nothing -> redirect "/"
doPasswdChange :: AppHandler ()
doPasswdChange = do
Just op <- getParam "oldpasswd"
Just p1 <- getParam "p1"
Just p2 <- getParam "p2"
user <- with auth currentUser
case user of
Nothing -> finishWith basicError
Just u ->
case authenticatePassword u (ClearText op) of
Just _ -> finishWith basicError
Nothing ->
if p1 /= p2
then finishWith basicError
else do a <- liftIO (setPassword u p1)
with auth $ saveUser a
redirect "/"
doLoginForm :: AppHandler Page
doLoginForm = do
user <- fmap userLogin `fmap` with auth currentUser
return (LoginForm user)
doNewUser :: AppHandler ()
doNewUser = do
Just user <- getParam "user"
Just passwd <- getParam "pass"
with auth $ createUser (T.pack (unpack user)) passwd
redirect "/"
doCreate :: AppHandler Page
doCreate = do
user <- fmap userLogin `fmap` with auth currentUser
case user of
Just u -> return (Edit u def)
_ -> redirect "/"
doIndex :: AppHandler Page
doIndex = do
newest <- withSqlite newestPost
user <- fmap userLogin `fmap` with auth currentUser
case newest of
Just pg -> return (Index user pg)
Nothing -> finishWith basicError
doAddPost :: AppHandler ()
doAddPost = do
allowed <- with auth isLoggedIn
if allowed then do
rp <- getRawPost
Just uname <- fmap userLogin `fmap` with auth currentUser
success <- withSqlite (submitPost uname rp)
if success
then redirect "/"
else finishWith basicError
else
redirect "/"
doArchive :: AppHandler Page
doArchive = do
user <- fmap userLogin `fmap` with auth currentUser
posts <- withSqlite listPosts
return (List user posts)
doOldestRedirect :: AppHandler ()
doOldestRedirect = do
oldest <- withSqlite oldestPostRef
case oldest of
Nothing -> finishWith basicError
Just pr -> do
let url = urlFor pr
redirect (encodeUtf8 url)
doNewestRedirect :: AppHandler ()
doNewestRedirect = do
newest <- withSqlite newestPostRef
case newest of
Nothing -> finishWith basicError
Just pr -> do
let url = urlFor pr
redirect (encodeUtf8 url)
toInt :: ByteString -> AppHandler Int
toInt bs = case readInt bs of
Just (n, "") -> return n
_ -> finishWith basicError
getPost :: AppHandler (Maybe Post)
getPost = do
Just year <- getParam "year"
Just month <- getParam "month"
Just slug <- getParam "slug"
year' <- toInt year
month' <- toInt month
let slug' = unpack slug
withSqlite (postByDateAndSlug year' month' slug')
getRawPost :: AppHandler RawPost
getRawPost = do
Just rpTitle <- fmap decodeUtf8 `fmap` getParam "title"
Just rpAuthor <- fmap decodeUtf8 `fmap` getParam "author"
Just rpContents <- fmap decodeUtf8 `fmap` getParam "contents"
idNum <- getParam "id"
let rpId = case idNum of
Just "none" -> Nothing
_ -> maybe Nothing (fmap fst . readInt) idNum
return RawPost { .. }
doPost :: AppHandler Page
doPost = do
post <- getPost
user <- fmap userLogin `fmap` with auth currentUser
case post of
Just pg -> return (Entry user pg)
Nothing -> finishWith basicError
editPost :: AppHandler Page
editPost = do
post <- getPost
user <- fmap userLogin `fmap` with auth currentUser
case (post, user) of
(Just pg, Just u)
| u == postAuthor pg -> return (Edit u (toRaw pg))
_ -> finishWith basicError
doPage :: Page -> AppHandler ()
doPage = writeBuilder . renderHtmlBuilder . toMarkup