chore(tazblog): Replace BlogDB with stubs for DNS-based storage

Removes acid-state specific code and the former BlogDB module, in its
stead the new BlogStorage module contains stubs for the functions that
will be filled in with DNS-based storage.

This code is unformatted and will not currently serve a working blog.
This commit is contained in:
Vincent Ambo 2019-08-20 00:17:23 +01:00
parent 1d5b53abf8
commit 11fcf62297
10 changed files with 116 additions and 561 deletions

View file

@ -1,11 +1,8 @@
-- | Main module for the blog's web server -- | Main module for the blog's web server
module Main where module Main where
import BlogDB (initialBlogState) import Control.Applicative (pure, (<*>))
import Control.Applicative (pure, (<$>), (<*>))
import Control.Exception (bracket) import Control.Exception (bracket)
import Data.Acid
import Data.Acid.Remote
import Data.Word (Word16) import Data.Word (Word16)
import Locales (version) import Locales (version)
import Network (HostName, PortID (..)) import Network (HostName, PortID (..))
@ -13,18 +10,12 @@ import Options
import Server import Server
data MainOptions = MainOptions { data MainOptions = MainOptions {
dbHost :: String,
dbPort :: Word16,
blogPort :: Int, blogPort :: Int,
resourceDir :: String resourceDir :: String
} }
instance Options MainOptions where instance Options MainOptions where
defineOptions = pure MainOptions defineOptions = pure MainOptions
<*> simpleOption "dbHost" "localhost"
"Remote acid-state database host. Default is localhost"
<*> simpleOption "dbPort" 8070
"Remote acid-state database port. Default is 8070"
<*> simpleOption "blogPort" 8000 <*> simpleOption "blogPort" 8000
"Port to serve the blog on. Default is 8000." "Port to serve the blog on. Default is 8000."
<*> simpleOption "resourceDir" "/opt/tazblog/static" <*> simpleOption "resourceDir" "/opt/tazblog/static"
@ -34,8 +25,4 @@ main :: IO()
main = do main = do
putStrLn ("TazBlog " ++ version ++ " in Haskell starting") putStrLn ("TazBlog " ++ version ++ " in Haskell starting")
runCommand $ \opts _ -> runCommand $ \opts _ ->
let port = PortNumber $ fromIntegral $ dbPort opts runBlog (blogPort opts) (resourceDir opts)
in openRemoteState skipAuthenticationPerform (dbHost opts) port >>=
(\acid -> runBlog acid (blogPort opts) (resourceDir opts))

View file

@ -1,34 +0,0 @@
-- | Main module for the database server
module Main where
import BlogDB (initialBlogState)
import Control.Applicative (pure, (<$>), (<*>))
import Control.Exception (bracket)
import Data.Acid
import Data.Acid.Local (createCheckpointAndClose)
import Data.Acid.Remote
import Data.Word
import Network (PortID (..))
import Options
data DBOptions = DBOptions {
dbPort :: Word16,
stateDirectory :: String
}
instance Options DBOptions where
defineOptions = pure DBOptions
<*> simpleOption "dbport" 8070
"Port to serve acid-state on remotely."
<*> simpleOption "state" "/var/tazblog/state"
"Directory in which the acid-state is located."
main :: IO ()
main = do
putStrLn ("Launching TazBlog database server ...")
runCommand $ \opts args ->
bracket (openState opts) createCheckpointAndClose
(acidServer skipAuthenticationCheck $ getPort opts)
where
openState o = openLocalStateFrom (stateDirectory o) initialBlogState
getPort = PortNumber . fromIntegral . dbPort

View file

@ -11,9 +11,9 @@
module Blog where module Blog where
import BlogDB import BlogStore
import Data.Maybe (fromJust) import Data.Maybe (fromJust)
import Data.Text (Text, append, empty, pack) import Data.Text (Text, empty, pack)
import Data.Text.Lazy (fromStrict) import Data.Text.Lazy (fromStrict)
import Data.Time import Data.Time
import Locales import Locales
@ -75,9 +75,9 @@ isEntryMarkdown e = edate e > markdownCutoff
renderEntryMarkdown :: Text -> Html renderEntryMarkdown :: Text -> Html
renderEntryMarkdown = markdown def {msXssProtect = False} . fromStrict renderEntryMarkdown = markdown def {msXssProtect = False} . fromStrict
renderEntries :: Bool -> [Entry] -> Maybe Html -> Html renderEntries :: [Entry] -> Maybe Html -> Html
renderEntries showAll entries pageLinks = [shamlet| renderEntries entries pageLinks = [shamlet|
$forall entry <- toDisplay $forall entry <- entries
<article> <article>
<h2 .inline> <h2 .inline>
<a href=#{linkElems entry} .unstyled-link> <a href=#{linkElems entry} .unstyled-link>
@ -97,10 +97,9 @@ $maybe links <- pageLinks
^{links} ^{links}
|] |]
where where
toDisplay = if showAll then entries else (take 6 entries)
linkElems Entry{..} = concat $ ["/", show lang, "/", show entryId] linkElems Entry{..} = concat $ ["/", show lang, "/", show entryId]
showLinks :: Maybe Int -> BlogLang -> Html showLinks :: Maybe Integer -> BlogLang -> Html
showLinks (Just i) lang = [shamlet| showLinks (Just i) lang = [shamlet|
$if ((>) i 1) $if ((>) i 1)
<div .navigation> <div .navigation>
@ -135,103 +134,6 @@ renderEntry e@Entry{..} = [shamlet|
<hr> <hr>
|] |]
{- Administration pages -}
adminTemplate :: Text -> Html -> Html
adminTemplate title body = [shamlet|
$doctype 5
<head>
<link rel="stylesheet" type="text/css" href="/static/admin.css" media="all">
<meta http-equiv="content-type" content="text/html;charset=UTF-8">
<title>#{append "TazBlog Admin: " title}
<body>
^{body}
|]
adminLogin :: Html
adminLogin = adminTemplate "Login" $ [shamlet|
<div class="loginBox">
<div class="loginBoxTop">TazBlog Admin: Login
<div class="loginBoxMiddle">
<form action="/admin" method="POST">
<p>Account ID
<p><input type="text" style="font-size:2;" name="account" value="tazjin" readonly="1">
<p>Passwort
<p><input type="password" style="font-size:2;" name="password">
<p><input alt="Anmelden" type="image" src="/static/signin.gif">
|]
adminIndex :: Text -> Html
adminIndex sUser = adminTemplate "Index" $ [shamlet|
<div style="float:center;">
<form action="/admin/entry" method="POST">
<table>
<tr>
<thead><td>Title:
<td><input type="text" name="title">
<tr>
<thead><td>Language:
<td><select name="lang">
<option value="en">English
<option value="de">Deutsch
<tr>
<thead><td>Text:
<td>
<textarea name="btext" cols="100" rows="15">
<tr>
<thead>
<td style="vertical-align:top;">Read more:
<td>
<textarea name="mtext" cols="100" rows="15">
<input type="hidden" name="author" value=#{sUser}>
<input style="margin-left:20px;" type="submit" value="Submit">
^{adminFooter}
|]
adminFooter :: Html
adminFooter = [shamlet|
<a href="/">Front page
\ -- #
<a href="/admin">New article
\ -- Entry list: #
<a href="/admin/entrylist/en">EN
\ & #
<a href="/admin/entrylist/de">DE
|]
adminEntryList :: [Entry] -> Html
adminEntryList entries = adminTemplate "EntryList" $ [shamlet|
<div style="float: center;">
<table>
$forall entry <- entries
<tr>
<td><a href=#{append "/admin/entry/" (show' $ entryId entry)}>#{title entry}
<td>#{formatPostDate $ edate entry}
|]
where
formatPostDate = formatTime defaultTimeLocale "[On %D at %H:%M]"
editPage :: Entry -> Html
editPage (Entry{..}) = adminTemplate "Index" $ [shamlet|
<div style="float:center;">
<form action=#{append "/admin/entry/" (show' entryId)} method="POST">
<table>
<tr>
<td>Title:
<td>
<input type="text" name="title" value=#{title}>
<tr>
<td style="vertical-align:top;">Text:
<td>
<textarea name="btext" cols="100" rows="15">#{btext}
<tr>
<td style="vertical-align:top;">Read more:
<td>
<textarea name="mtext" cols="100" rows="15">#{mtext}
<input type="submit" style="margin-left:20px;" value="Submit">
<p>^{adminFooter}
|]
showError :: BlogError -> BlogLang -> Html showError :: BlogError -> BlogLang -> Html
showError NotFound l = blogTemplate l (T.append ": " $ notFoundTitle l) $ [shamlet| showError NotFound l = blogTemplate l (T.append ": " $ notFoundTitle l) $ [shamlet|
<p>:( <p>:(

View file

@ -1,241 +0,0 @@
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
module BlogDB where
import Control.Monad.Reader (ask)
import Control.Monad.State (get, put)
import Data.Acid
import Data.Acid.Advanced
import Data.Acid.Remote
import Data.ByteString (ByteString)
import Data.Data (Data, Typeable)
import Data.IxSet (Indexable (..), IxSet, Proxy (..),
getOne, ixFun, ixSet, (@=))
import Data.SafeCopy (base, deriveSafeCopy)
import Data.Text (Text, pack)
import Data.Time
import Network (PortID (..))
import System.Environment (getEnv)
import qualified Crypto.Hash.SHA512 as SHA (hash)
import qualified Data.ByteString.Base64 as B64 (encode)
import qualified Data.ByteString.Char8 as B
import qualified Data.IxSet as IxSet
newtype EntryId = EntryId { unEntryId :: Integer }
deriving (Eq, Ord, Data, Enum, Typeable)
$(deriveSafeCopy 2 'base ''EntryId)
instance Show EntryId where
show = show . unEntryId
data BlogLang = EN | DE
deriving (Eq, Ord, Data, Typeable)
instance Show BlogLang where
show DE = "de"
show EN = "en"
$(deriveSafeCopy 0 'base ''BlogLang)
data Entry = Entry {
entryId :: EntryId,
lang :: BlogLang,
author :: Text,
title :: Text,
btext :: Text,
mtext :: Text,
edate :: UTCTime
} deriving (Eq, Ord, Show, Data, Typeable)
$(deriveSafeCopy 2 'base ''Entry)
-- ixSet requires different datatypes for field indexes, so let's define some
newtype Author = Author Text deriving (Eq, Ord, Data, Typeable)
newtype Title = Title Text deriving (Eq, Ord, Data, Typeable)
newtype BText = BText Text deriving (Eq, Ord, Data, Typeable) -- standard text
newtype MText = MText Text deriving (Eq, Ord, Data, Typeable) -- "read more" text
newtype Tag = Tag Text deriving (Eq, Ord, Data, Typeable)
newtype EDate = EDate UTCTime deriving (Eq, Ord, Data, Typeable)
newtype SDate = SDate UTCTime deriving (Eq, Ord, Data, Typeable)
newtype Username = Username Text deriving (Eq, Ord, Data, Typeable)
newtype SessionID = SessionID Text deriving (Eq, Ord, Data, Typeable)
$(deriveSafeCopy 2 'base ''Author)
$(deriveSafeCopy 2 'base ''Title)
$(deriveSafeCopy 2 'base ''BText)
$(deriveSafeCopy 2 'base ''MText)
$(deriveSafeCopy 2 'base ''Tag)
$(deriveSafeCopy 2 'base ''EDate)
$(deriveSafeCopy 2 'base ''SDate)
$(deriveSafeCopy 2 'base ''Username)
$(deriveSafeCopy 2 'base ''SessionID)
instance Indexable Entry where
empty = ixSet [ ixFun $ \e -> [ entryId e]
, ixFun $ (:[]) . lang
, ixFun $ \e -> [ Author $ author e ]
, ixFun $ \e -> [ Title $ title e]
, ixFun $ \e -> [ BText $ btext e]
, ixFun $ \e -> [ MText $ mtext e]
, ixFun $ \e -> [ EDate $ edate e]
]
data User = User {
username :: Text,
password :: ByteString
} deriving (Eq, Ord, Data, Typeable)
$(deriveSafeCopy 0 'base ''User)
data Session = Session {
sessionID :: Text,
user :: User,
sdate :: UTCTime
} deriving (Eq, Ord, Data, Typeable)
$(deriveSafeCopy 0 'base ''Session)
instance Indexable User where
empty = ixSet [ ixFun $ \u -> [Username $ username u]
, ixFun $ (:[]) . password
]
instance Indexable Session where
empty = ixSet [ ixFun $ \s -> [SessionID $ sessionID s]
, ixFun $ (:[]) . user
, ixFun $ \s -> [SDate $ sdate s]
]
data Blog = Blog {
blogSessions :: IxSet Session,
blogUsers :: IxSet User,
blogEntries :: IxSet Entry
} deriving (Data, Typeable)
$(deriveSafeCopy 0 'base ''Blog)
initialBlogState :: Blog
initialBlogState =
Blog { blogSessions = empty
, blogUsers = empty
, blogEntries = empty }
-- acid-state database functions (purity is necessary!)
insertEntry :: Entry -> Update Blog Entry
insertEntry e =
do b@Blog{..} <- get
put $ b { blogEntries = IxSet.insert e blogEntries }
return e
updateEntry :: Entry -> Update Blog Entry
updateEntry e =
do b@Blog{..} <- get
put $ b { blogEntries = IxSet.updateIx (entryId e) e blogEntries }
return e
deleteEntry :: EntryId -> Update Blog EntryId
deleteEntry entry =
do b@Blog{..} <- get
put $ b { blogEntries = IxSet.deleteIx entry blogEntries }
return entry
getEntry :: EntryId -> Query Blog (Maybe Entry)
getEntry eId =
do Blog{..} <- ask
return $ getOne $ blogEntries @= eId
latestEntries :: BlogLang -> Query Blog [Entry]
latestEntries lang =
do Blog{..} <- ask
return $ IxSet.toDescList (Proxy :: Proxy EDate) $ blogEntries @= lang
addSession :: Session -> Update Blog Session
addSession nSession =
do b@Blog{..} <- get
put $ b { blogSessions = IxSet.insert nSession blogSessions}
return nSession
getSession :: SessionID -> Query Blog (Maybe Session)
getSession sId =
do Blog{..} <- ask
return $ getOne $ blogSessions @= sId
clearSessions :: Update Blog [Session]
clearSessions =
do b@Blog{..} <- get
put $ b { blogSessions = empty }
return []
addUser :: Text -> String -> Update Blog User
addUser un pw =
do b@Blog{..} <- get
let u = User un $ hashString pw
put $ b { blogUsers = IxSet.insert u blogUsers}
return u
getUser :: Username -> Query Blog (Maybe User)
getUser uN =
do Blog{..} <- ask
return $ getOne $ blogUsers @= uN
checkUser :: Username -> String -> Query Blog Bool
checkUser uN pw =
do Blog{..} <- ask
let user = getOne $ blogUsers @= uN
case user of
Nothing -> return False
(Just u) -> return $ password u == hashString pw
-- various functions
hashString :: String -> ByteString
hashString = B64.encode . SHA.hash . B.pack
$(makeAcidic ''Blog
[ 'insertEntry
, 'updateEntry
, 'deleteEntry
, 'getEntry
, 'latestEntries
, 'addSession
, 'getSession
, 'addUser
, 'getUser
, 'checkUser
, 'clearSessions
])
interactiveUserAdd :: String -> IO ()
interactiveUserAdd dbHost = do
acid <- openRemoteState skipAuthenticationPerform dbHost (PortNumber 8070)
putStrLn "Username:"
un <- getLine
putStrLn "Password:"
pw <- getLine
update' acid (AddUser (pack un) pw)
closeAcidState acid
flushSessions :: IO ()
flushSessions = do
tbDir <- getEnv "TAZBLOG"
acid <- openLocalStateFrom (tbDir ++ "/BlogState") initialBlogState
update' acid ClearSessions
closeAcidState acid
archiveState :: IO ()
archiveState = do
tbDir <- getEnv "TAZBLOG"
acid <- openLocalStateFrom (tbDir ++ "/BlogState") initialBlogState
createArchive acid
closeAcidState acid

View file

@ -0,0 +1,54 @@
-- |This module implements fetching of individual blog entries from
-- DNS. Yes, you read that correctly.
--
-- Each blog post is stored as a set of records in a designated DNS
-- zone. For the production blog, this zone is `blog.tazj.in.`.
--
-- A top-level record at `_posts` contains a list of all published
-- post IDs.
--
-- For each of these post IDs, there is a record at `_meta.$postID`
-- that contains the title and number of post chunks.
--
-- For each post chunk, there is a record at `_$chunkID.$postID` that
-- contains a base64-encoded post fragment.
--
-- This module implements logic for assembling a post out of these
-- fragments and caching it based on the TTL of its `_meta` record.
module BlogStore where
import Data.Text (Text)
import Locales (BlogLang(..))
import Data.Time (UTCTime)
import Control.Monad.IO.Class (MonadIO)
newtype EntryId = EntryId { unEntryId :: Integer }
deriving (Eq, Ord)
instance Show EntryId where
show = show . unEntryId
data Entry = Entry {
entryId :: EntryId,
lang :: BlogLang,
author :: Text,
title :: Text,
btext :: Text,
mtext :: Text,
edate :: UTCTime
} deriving (Eq, Ord, Show)
data BlogCache
type Offset = Integer
type Count = Integer
newCache :: String -> IO BlogCache
newCache zone = undefined
listEntries :: MonadIO m => BlogCache -> Offset -> Count -> m [Entry]
listEntries cache offset count = undefined
getEntry :: MonadIO m => BlogCache -> EntryId -> m (Maybe Entry)
getEntry cache eId = undefined

View file

@ -1,15 +1,21 @@
{-# LANGUAGE ScopedTypeVariables, OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables, OverloadedStrings #-}
module Locales where module Locales where
import BlogDB (BlogLang (..))
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
import Data.Text (Text) import Data.Text (Text)
import qualified Data.Text as T import qualified Data.Text as T
import Network.URI import Network.URI
data BlogLang = EN | DE
deriving (Eq, Ord)
instance Show BlogLang where
show DE = "de"
show EN = "en"
data BlogError = NotFound | UnknownError data BlogError = NotFound | UnknownError
version = "5.1.2" version = "6.0.0"
blogTitle :: BlogLang -> Text -> Text blogTitle :: BlogLang -> Text -> Text
blogTitle DE s = T.concat ["Tazjins blog", s] blogTitle DE s = T.concat ["Tazjins blog", s]

View file

@ -9,7 +9,7 @@ import Data.Time (UTCTime, getCurrentTime)
import Network.URI import Network.URI
import Text.RSS import Text.RSS
import BlogDB hiding (Title) import BlogStore
import Locales import Locales
createChannel :: BlogLang -> UTCTime -> [ChannelElem] createChannel :: BlogLang -> UTCTime -> [ChannelElem]

View file

@ -2,20 +2,15 @@
module Server where module Server where
import Control.Applicative (optional) import Control.Applicative (optional)
import Control.Monad (msum, mzero, unless) import Control.Monad (msum)
import Control.Monad.IO.Class (liftIO) import Control.Monad.IO.Class (liftIO)
import Data.Acid
import Data.Acid.Advanced
import Data.ByteString.Char8 (unpack)
import Data.Char (toLower) import Data.Char (toLower)
import Data.Text (Text)
import qualified Data.Text as T import qualified Data.Text as T
import Data.Time
import Happstack.Server hiding (Session) import Happstack.Server hiding (Session)
import Data.Maybe (fromJust) import Data.Maybe (maybe)
import Blog import Blog
import BlogDB hiding (updateEntry) import BlogStore
import Locales import Locales
import RSS import RSS
@ -26,32 +21,32 @@ instance FromReqURI BlogLang where
"en" -> Just EN "en" -> Just EN
_ -> Nothing _ -> Nothing
pageSize :: Integer
pageSize = 3
tmpPolicy :: BodyPolicy tmpPolicy :: BodyPolicy
tmpPolicy = defaultBodyPolicy "/tmp" 0 200000 1000 tmpPolicy = defaultBodyPolicy "/tmp" 0 200000 1000
runBlog :: AcidState Blog -> Int -> String -> IO () runBlog :: Int -> String -> IO ()
runBlog acid port respath = runBlog port respath = do
simpleHTTP nullConf {port = port} $ tazBlog acid respath cache <- newCache "blog.tazj.in."
simpleHTTP nullConf {port = port} $ tazBlog cache respath
tazBlog :: AcidState Blog -> String -> ServerPart Response tazBlog :: BlogCache -> String -> ServerPart Response
tazBlog acid resDir = do tazBlog cache resDir = do
msum [ path $ \(lang :: BlogLang) -> blogHandler acid lang msum [ path $ \(lang :: BlogLang) -> blogHandler cache lang
, dir "admin" $ msum [
adminHandler acid -- this checks auth
, method GET >> (ok $ toResponse adminLogin)
, method POST >> processLogin acid ]
, dir "static" $ staticHandler resDir , dir "static" $ staticHandler resDir
, blogHandler acid EN , blogHandler cache EN
, staticHandler resDir , staticHandler resDir
, notFound $ toResponse $ showError NotFound DE , notFound $ toResponse $ showError NotFound DE
] ]
blogHandler :: AcidState Blog -> BlogLang -> ServerPart Response blogHandler :: BlogCache -> BlogLang -> ServerPart Response
blogHandler acid lang = blogHandler cache lang =
msum [ path $ \(eId :: Integer) -> showEntry acid lang $ EntryId eId msum [ path $ \(eId :: Integer) -> showEntry cache lang $ EntryId eId
, nullDir >> showIndex acid lang , nullDir >> showIndex cache lang
, dir "rss" $ nullDir >> showRSS acid lang , dir "rss" $ nullDir >> showRSS cache lang
, dir "rss.xml" $ nullDir >> showRSS acid lang , dir "rss.xml" $ nullDir >> showRSS cache lang
, notFound $ toResponse $ showError NotFound lang , notFound $ toResponse $ showError NotFound lang
] ]
@ -61,20 +56,9 @@ staticHandler resDir = do
setHeaderM "expires" "Tue, 20 Jan 2037 04:20:42 GMT" setHeaderM "expires" "Tue, 20 Jan 2037 04:20:42 GMT"
serveDirectory DisableBrowsing [] resDir serveDirectory DisableBrowsing [] resDir
adminHandler :: AcidState Blog -> ServerPart Response showEntry :: BlogCache -> BlogLang -> EntryId -> ServerPart Response
adminHandler acid = do showEntry cache lang eId = do
guardSession acid entry <- getEntry cache eId
msum [ dir "entry" $ method POST >> postEntry acid
, dir "entry" $ path $ \(entry :: Integer) -> msum [
method GET >> editEntry acid entry
, method POST >> updateEntry acid entry ]
, dir "entrylist" $ path $ \(lang :: BlogLang) -> entryList acid lang
, ok $ toResponse $ adminIndex "tazjin"
]
showEntry :: AcidState Blog -> BlogLang -> EntryId -> ServerPart Response
showEntry acid lang eId = do
entry <- query' acid (GetEntry eId)
tryEntry entry lang tryEntry entry lang
tryEntry :: Maybe Entry -> BlogLang -> ServerPart Response tryEntry :: Maybe Entry -> BlogLang -> ServerPart Response
@ -84,107 +68,19 @@ tryEntry (Just entry) _ = ok $ toResponse $ blogTemplate eLang eTitle $ renderEn
eTitle = T.append ": " (title entry) eTitle = T.append ": " (title entry)
eLang = lang entry eLang = lang entry
showIndex :: AcidState Blog -> BlogLang -> ServerPart Response offset :: Maybe Integer -> Integer
showIndex acid lang = do offset = maybe 0 ((*) pageSize)
entries <- query' acid (LatestEntries lang)
(page :: Maybe Int) <- optional $ lookRead "page"
ok $ toResponse $ blogTemplate lang "" $
renderEntries False (eDrop page entries) (Just $ showLinks page lang)
where
eDrop :: Maybe Int -> [a] -> [a]
eDrop (Just i) = drop ((i-1) * 6)
eDrop Nothing = drop 0
showRSS :: AcidState Blog -> BlogLang -> ServerPart Response showIndex :: BlogCache -> BlogLang -> ServerPart Response
showRSS acid lang = do showIndex cache lang = do
entries <- query' acid (LatestEntries lang) (page :: Maybe Integer) <- optional $ lookRead "page"
feed <- liftIO $ renderFeed lang $ take 6 entries entries <- listEntries cache (offset page) pageSize
ok $ toResponse $ blogTemplate lang "" $
renderEntries entries (Just $ showLinks page lang)
showRSS :: BlogCache -> BlogLang -> ServerPart Response
showRSS cache lang = do
entries <- listEntries cache 0 4
feed <- liftIO $ renderFeed lang entries
setHeaderM "content-type" "text/xml" setHeaderM "content-type" "text/xml"
ok $ toResponse feed ok $ toResponse feed
{- ADMIN stuff -}
postEntry :: AcidState Blog -> ServerPart Response
postEntry acid = do
nullDir
decodeBody tmpPolicy
now <- liftIO getCurrentTime
let eId = timeToId now
lang <- look "lang"
nBtext <- lookText' "btext"
nMtext <- lookText' "mtext"
nEntry <- Entry <$> pure eId
<*> getLang lang
<*> readCookieValue "sUser"
<*> lookText' "title"
<*> pure nBtext
<*> pure nMtext
<*> pure now
update' acid (InsertEntry nEntry)
seeOther ("/" ++ lang ++ "/" ++ show eId) (toResponse())
where
timeToId :: UTCTime -> EntryId
timeToId t = EntryId . read $ formatTime defaultTimeLocale "%s" t
getLang :: String -> ServerPart BlogLang
getLang "de" = return DE
getLang _ = return EN -- English is default
entryList :: AcidState Blog -> BlogLang -> ServerPart Response
entryList acid lang = do
entries <- query' acid (LatestEntries lang)
ok $ toResponse $ adminEntryList entries
editEntry :: AcidState Blog -> Integer -> ServerPart Response
editEntry acid entryId = do
entry <- query' acid (GetEntry $ EntryId entryId)
ok $ toResponse $ editPage $ fromJust entry
updateEntry :: AcidState Blog -> Integer -> ServerPart Response
updateEntry acid entryId = do
decodeBody tmpPolicy
entry <- query' acid (GetEntry $ EntryId entryId)
nTitle <- lookText' "title"
nBtext <- lookText' "btext"
nMtext <- lookText' "mtext"
let newEntry = (fromJust entry)
{ title = nTitle
, btext = nBtext
, mtext = nMtext}
update' acid (UpdateEntry newEntry)
seeOther (concat $ ["/", show $ lang newEntry, "/", show entryId])
(toResponse ())
guardSession :: AcidState Blog -> ServerPartT IO ()
guardSession acid = do
(sId :: Text) <- readCookieValue "session"
(uName :: Text) <- readCookieValue "sUser"
now <- liftIO getCurrentTime
mS <- query' acid (GetSession $ SessionID sId)
case mS of
Nothing -> mzero
(Just Session{..}) -> unless ((uName == username user) && sessionTimeDiff now sdate)
mzero
where
sessionTimeDiff :: UTCTime -> UTCTime -> Bool
sessionTimeDiff now sdate = diffUTCTime now sdate < 43200
processLogin :: AcidState Blog -> ServerPart Response
processLogin acid = do
decodeBody tmpPolicy
account <- lookText' "account"
password <- look "password"
login <- query' acid (CheckUser (Username account) password)
if login
then createSession account
else unauthorized $ toResponse adminLogin
where
createSession account = do
now <- liftIO getCurrentTime
let sId = hashString $ show now
addCookie (MaxAge 43200) (mkCookie "session" $ unpack sId)
addCookie (MaxAge 43200) (mkCookie "sUser" $ T.unpack account)
user <- query' acid (GetUser $ Username account)
let nSession = Session (T.pack $ unpack sId) (fromJust user) now
update' acid (AddSession nSession)
seeOther ("/admin?do=login" :: Text) (toResponse())

View file

@ -1,9 +1,9 @@
Name: tazblog Name: tazblog
Version: 5.1.3 Version: 6.0.0
Synopsis: Tazjin's Blog Synopsis: Tazjin's Blog
License: MIT License: MIT
Author: Vincent Ambo Author: Vincent Ambo
Maintainer: tazjin@gmail.com Maintainer: mail@tazj.in
Category: Web blog Category: Web blog
Build-type: Simple Build-type: Simple
cabal-version: >= 1.10 cabal-version: >= 1.10
@ -12,7 +12,7 @@ library
hs-source-dirs: src hs-source-dirs: src
default-language: Haskell2010 default-language: Haskell2010
ghc-options: -W ghc-options: -W
exposed-modules: Blog, BlogDB, Locales, Server, RSS exposed-modules: Blog, BlogStore, Locales, Server, RSS
build-depends: base, build-depends: base,
bytestring, bytestring,
happstack-server, happstack-server,
@ -24,9 +24,6 @@ library
old-locale, old-locale,
time, time,
base64-bytestring, base64-bytestring,
acid-state,
ixset,
safecopy,
mtl, mtl,
transformers, transformers,
network, network,
@ -53,18 +50,6 @@ executable tazblog
default-language: Haskell2010 default-language: Haskell2010
ghc-options: -threaded -rtsopts -with-rtsopts=-N ghc-options: -threaded -rtsopts -with-rtsopts=-N
build-depends: base, build-depends: base,
acid-state,
tazblog,
options,
network
executable tazblog-db
hs-source-dirs: db
main-is: Main.hs
default-language: Haskell2010
ghc-options: -threaded -rtsopts -with-rtsopts=-N
build-depends: base,
acid-state,
tazblog, tazblog,
options, options,
network network

View file

@ -6,17 +6,17 @@
}: }:
mkDerivation { mkDerivation {
pname = "tazblog"; pname = "tazblog";
version = "5.1.3"; version = "6.0.0";
src = ./.; src = ./.;
isLibrary = true; isLibrary = true;
isExecutable = true; isExecutable = true;
libraryHaskellDepends = [ libraryHaskellDepends = [
acid-state base base64-bytestring blaze-html blaze-markup base base64-bytestring blaze-html blaze-markup bytestring
bytestring crypto-api cryptohash hamlet happstack-server ixset crypto-api cryptohash hamlet happstack-server markdown mtl
markdown mtl network network-uri old-locale rss safecopy network network-uri old-locale rss shakespeare text time
shakespeare text time transformers transformers
]; ];
executableHaskellDepends = [ acid-state base network options ]; executableHaskellDepends = [ base network options ];
description = "Tazjin's Blog"; description = "Tazjin's Blog";
license = stdenv.lib.licenses.mit; license = stdenv.lib.licenses.mit;
} }