By | Chris Done;Chris Done |
At | 2013-03-01; 2013-03-02 |
Title | Generalize model/controller. |
Description |
Edit file src/Main.hs 33188 → 33188
6 6 module Main (main) where
7 7 8 8 import Hpaste.Config
9 9 import Hpaste.Controller.Activity as Activity
10 10 import Hpaste.Controller.Browse as Browse
- 11 import Hpaste.Controller.Cache (newCache)
12 11 import Hpaste.Controller.Diff as Diff
13 12 import Hpaste.Controller.Home as Home
14 13 import Hpaste.Controller.New as New
15 14 import Hpaste.Controller.Paste as Paste
16 15 import Hpaste.Controller.Raw as Raw
17 16 import Hpaste.Controller.Report as Report
18 17 import Hpaste.Controller.Reported as Reported
19 18 import Hpaste.Controller.Style as Style
20 19 import Hpaste.Model.Announcer (newAnnouncer)
21 20 import Hpaste.Types
- 22 import Hpaste.Types.Cache
23 21 24 22 import Snap.App.Controller
25 23 import Snap.Core
26 24 import Snap.Http.Server hiding (Config)
27 25 import Snap.Util.FileServe
… … … … 37 35 main = do
38 36 cpath:_ <- getArgs
39 37 config <- getConfig cpath
40 38 announces <- newAnnouncer (configAnnounce config)
41 39 pool <- newPool (configPostgres config)
- 42 cache <- newCache
43 40 setUnicodeLocale "en_US"
- 44 httpServe server (serve config pool cache announces)
+ 41 httpServe server (serve config pool announces)
45 42 where server = setPort 10000 defaultConfig
46 43 47 44 -- | Serve the controllers.
- 48 serve :: Config -> Pool -> Cache -> Chan Text -> Snap ()
- 49 serve conf p cache ans = route routes where
+ 45 serve :: Config -> Pool -> Chan Text -> Snap ()
+ 46 serve conf p ans = route routes where
50 47 routes = [("/css/amelie.css", run Style.handle)
51 48 ,("/js/",serveDirectory "static/js")
52 49 ,("/css/",serveDirectory "static/css")
53 50 ,("/js/",serveDirectory "static/js")
54 51 ,("/hs/",serveDirectory "static/hs")
… … … … 64 61 ,("/new/:channel",run (New.handle New.NewPaste))
65 62 ,("/browse",run Browse.handle)
66 63 ,("/activity",run Activity.handle)
67 64 ,("/diff/:this/:that",run Diff.handle)
68 65 ]
- 69 run = runHandler conf p cache ans
+ 66 run = runHandler ans conf p
… … … … Edit file src/Snap/App/Controller.hs 33188 → 33188
15 15 ,getStringMaybe
16 16 ,getPagination
17 17 ,getMyURI)
18 18 where
19 19 - 20 import Hpaste.Types
- 21 import Hpaste.Types.Cache
+ 20 import Snap.Core
+ 21 import Snap.App.Types
22 22 23 23 import Control.Applicative
- 24 import Control.Concurrent.Chan (Chan)
25 24 import Control.Monad.Env
26 25 import Control.Monad.Reader (runReaderT)
27 26 import Data.ByteString (ByteString)
28 27 import Data.ByteString.UTF8 (toString)
29 28 import Data.Maybe
30 29 import Network.URI
31 30 import Data.Text.Lazy (Text,toStrict)
32 31 import Database.PostgreSQL.Base (withPoolConnection)
33 32 import Database.PostgreSQL.Simple (Pool)
34 33 import Safe (readMay)
- 35 import Snap.Core
36 34 import Text.Blaze (Html)
37 35 import Text.Blaze.Renderer.Text (renderHtml)
38 36 39 37 -- | Run a controller handler.
- 40 runHandler :: Config -> Pool -> Cache -> Chan Text -> Controller () -> Snap ()
- 41 runHandler conf pool cache anns ctrl = do
+ 38 runHandler :: s -> c -> Pool -> Controller c s () -> Snap ()
+ 39 runHandler st conf pool ctrl = do
42 40 withPoolConnection pool $ \conn -> do
- 43 let state = ControllerState conf conn cache anns
+ 41 let state = ControllerState conf conn st
44 42 -- Default to HTML, can be overridden.
45 43 modifyResponse $ setContentType "text/html"
46 44 runReaderT (runController ctrl) state
47 45 48 46 -- | Strictly renders HTML to Text before outputting it via Snap.
49 47 -- This ensures that any lazy exceptions are caught by the Snap
50 48 -- handler.
- 51 output :: Html -> Controller ()
+ 49 output :: Html -> Controller c s ()
52 50 output html = outputText $ renderHtml $ html
53 51 54 52 -- | Strictly renders text before outputting it via Snap.
55 53 -- This ensures that any lazy exceptions are caught by the Snap
56 54 -- handler.
- 57 outputText :: Text -> Controller ()
+ 55 outputText :: Text -> Controller c s ()
58 56 outputText text = do
59 57 let !x = toStrict $ text
60 58 writeText x
61 59 62 60 -- | Generic redirect to home page.
- 63 goHome :: Controller ()
+ 61 goHome :: Controller c s ()
64 62 goHome = redirect "/"
65 63 66 64 -- | Extract a Just value or go home.
- 67 justOrGoHome :: Maybe a -> (a -> Controller ()) -> Controller ()
+ 65 justOrGoHome :: Maybe a -> (a -> Controller c s ()) -> Controller c s ()
68 66 justOrGoHome x m = maybe goHome m x
69 67 70 68 -- | Get integer parmater.
- 71 getInteger :: ByteString -> Integer -> Controller Integer
+ 69 getInteger :: ByteString -> Integer -> Controller c s Integer
72 70 getInteger name def = do
73 71 pid <- (>>= readMay . toString) <$> getParam name
74 72 maybe (return def) return pid
75 73 76 74 -- | Get string.
- 77 getString :: ByteString -> String -> Controller String
+ 75 getString :: ByteString -> String -> Controller c s String
78 76 getString name def = do
79 77 pid <- (>>= return . toString) <$> getParam name
80 78 maybe (return def) return pid
81 79 82 80 -- | Get string (maybe).
- 83 getStringMaybe :: ByteString -> Controller (Maybe String)
+ 81 getStringMaybe :: ByteString -> Controller c s (Maybe String)
84 82 getStringMaybe name = do
85 83 pid <- (>>= return . toString) <$> getParam name
86 84 return pid
87 85 88 86 -- | Get pagination data.
- 89 getPagination :: Controller Pagination
+ 87 getPagination :: AppConfig c => Controller c s Pagination
90 88 getPagination = do
91 89 p <- getInteger "page" 1
92 90 limit <- getInteger "limit" 35
- 93 i <- fmap rqURI getRequest
94 91 uri <- getMyURI
95 92 return Pagination { pnPage = max 1 p
96 93 , pnLimit = max 1 (min 100 limit)
97 94 , pnURI = uri
98 95 , pnResults = 0
99 96 , pnTotal = 0
100 97 }
101 98 - 102 getMyURI :: Controller URI
+ 99 getMyURI :: AppConfig c => Controller c s URI
103 100 getMyURI = do
- 104 domain <- env (configDomain . controllerStateConfig)
+ 101 domain <- env (getConfigDomain . controllerStateConfig)
105 102 fmap (fromJust .
106 103 parseURI .
107 104 (("http://" ++ domain) ++) .
108 105 toString .
109 106 rqURI)
… … … … Remove file src/Hpaste/Controller.hs 33188
- 1 {-# LANGUAGE BangPatterns #-} - 2 {-# OPTIONS -Wall #-} - 3 {-# LANGUAGE OverloadedStrings #-} - 4 - 5 -- | Controller routing/handling. - 6 - 7 module Hpaste.Controller - 8 (runHandler - 9 ,output - 10 ,outputText - 11 ,goHome - 12 ,justOrGoHome - 13 ,getInteger - 14 ,getString - 15 ,getStringMaybe - 16 ,getPagination - 17 ,getMyURI) - 18 where - 19 - 20 import Hpaste.Types - 21 import Hpaste.Types.Cache - 22 - 23 import Control.Applicative - 24 import Control.Concurrent.Chan (Chan) - 25 import Control.Monad.Env - 26 import Control.Monad.Reader (runReaderT) - 27 import Data.ByteString (ByteString) - 28 import Data.ByteString.UTF8 (toString) - 29 import Data.Maybe - 30 import Network.URI - 31 import Data.Text.Lazy (Text,toStrict) - 32 import Database.PostgreSQL.Base (withPoolConnection) - 33 import Database.PostgreSQL.Simple (Pool) - 34 import Safe (readMay) - 35 import Snap.Core - 36 import Text.Blaze (Html) - 37 import Text.Blaze.Renderer.Text (renderHtml) - 38 - 39 -- | Run a controller handler. - 40 runHandler :: Config -> Pool -> Cache -> Chan Text -> Controller () -> Snap () - 41 runHandler conf pool cache anns ctrl = do - 42 withPoolConnection pool $ \conn -> do - 43 let state = ControllerState conf conn cache anns - 44 -- Default to HTML, can be overridden. - 45 modifyResponse $ setContentType "text/html" - 46 runReaderT (runController ctrl) state - 47 - 48 -- | Strictly renders HTML to Text before outputting it via Snap. - 49 -- This ensures that any lazy exceptions are caught by the Snap - 50 -- handler. - 51 output :: Html -> Controller () - 52 output html = outputText $ renderHtml $ html - 53 - 54 -- | Strictly renders text before outputting it via Snap. - 55 -- This ensures that any lazy exceptions are caught by the Snap - 56 -- handler. - 57 outputText :: Text -> Controller () - 58 outputText text = do - 59 let !x = toStrict $ text - 60 writeText x - 61 - 62 -- | Generic redirect to home page. - 63 goHome :: Controller () - 64 goHome = redirect "/" - 65 - 66 -- | Extract a Just value or go home. - 67 justOrGoHome :: Maybe a -> (a -> Controller ()) -> Controller () - 68 justOrGoHome x m = maybe goHome m x - 69 - 70 -- | Get integer parmater. - 71 getInteger :: ByteString -> Integer -> Controller Integer - 72 getInteger name def = do - 73 pid <- (>>= readMay . toString) <$> getParam name - 74 maybe (return def) return pid - 75 - 76 -- | Get string. - 77 getString :: ByteString -> String -> Controller String - 78 getString name def = do - 79 pid <- (>>= return . toString) <$> getParam name - 80 maybe (return def) return pid - 81 - 82 -- | Get string (maybe). - 83 getStringMaybe :: ByteString -> Controller (Maybe String) - 84 getStringMaybe name = do - 85 pid <- (>>= return . toString) <$> getParam name - 86 return pid - 87 - 88 -- | Get pagination data. - 89 getPagination :: Controller Pagination - 90 getPagination = do - 91 p <- getInteger "page" 1 - 92 limit <- getInteger "limit" 35 - 93 uri <- getMyURI - 94 return Pagination { pnPage = max 1 p - 95 , pnLimit = max 1 (min 100 limit) - 96 , pnURI = uri - 97 , pnResults = 0 - 98 , pnTotal = 0 - 99 } - 100 - 101 getMyURI :: Controller URI - 102 getMyURI = do - 103 domain <- env (configDomain . controllerStateConfig) - 104 fmap (fromJust . - 105 parseURI . - 106 (("http://" ++ domain) ++) . - 107 toString . - 108 rqURI) - 109 getRequest Remove file src/Hpaste/Model.hs 33188
- 1 {-# OPTIONS -Wall #-} - 2 {-# LANGUAGE OverloadedStrings #-} - 3 {-# LANGUAGE FlexibleContexts #-} - 4 - 5 -- | Model running. - 6 - 7 module Hpaste.Model - 8 (model - 9 ,query - 10 ,single - 11 ,singleNoParams - 12 ,queryNoParams - 13 ,exec - 14 ,module Hpaste.Types - 15 ,DB.Only(..)) - 16 where - 17 - 18 import Hpaste.Types - 19 - 20 import Control.Monad.Env (env) - 21 import Control.Monad.IO (io) - 22 import Control.Monad.Reader - 23 import Data.String - 24 import Database.PostgreSQL.Simple (Only(..)) - 25 import qualified Database.PostgreSQL.Simple as DB - 26 import Database.PostgreSQL.Simple.QueryParams - 27 import Database.PostgreSQL.Simple.QueryResults - 28 - 29 -- | Run a model action. - 30 model :: Model a -> Controller a - 31 model action = do - 32 conn <- env controllerStateConn - 33 anns <- env controllerStateAnns - 34 conf <- env controllerStateConfig - 35 let state = ModelState conn anns conf - 36 io $ runReaderT (runModel action) state - 37 - 38 -- | Query with some parameters. - 39 query :: (QueryParams ps,QueryResults r) => [String] -> ps -> Model [r] - 40 query q ps = do - 41 conn <- env modelStateConn - 42 Model $ ReaderT (\_ -> DB.query conn (fromString (unlines q)) ps) - 43 - 44 -- | Query a single field from a single result. - 45 single :: (QueryParams ps,QueryResults (Only r)) => [String] -> ps -> Model (Maybe r) - 46 single q ps = do - 47 rows <- query q ps - 48 case rows of - 49 [(Only r)] -> return (Just r) - 50 _ -> return Nothing - 51 - 52 -- | Query a single field from a single result (no params). - 53 singleNoParams :: (QueryResults (Only r)) => [String] -> Model (Maybe r) - 54 singleNoParams q = do - 55 rows <- queryNoParams q - 56 case rows of - 57 [(Only r)] -> return (Just r) - 58 _ -> return Nothing - 59 - 60 -- | Query with no parameters. - 61 queryNoParams :: (QueryResults r) => [String] -> Model [r] - 62 queryNoParams q = do - 63 conn <- env modelStateConn - 64 Model $ ReaderT (\_ -> DB.query_ conn (fromString (unlines q))) - 65 - 66 -- | Execute some SQL returning the rows affected. - 67 exec :: (QueryParams ps) => [String] -> ps -> Model Integer - 68 exec q ps = do - 69 conn <- env modelStateConn - 70 Model $ ReaderT (\_ -> DB.execute conn (fromString (unlines q)) ps) Edit file src/Hpaste/Types.hs 33188 → 33188
- 1 {-# OPTIONS -Wall #-}
+ 1 {-# LANGUAGE FlexibleInstances #-}
+ 2 {-# LANGUAGE TypeSynonymInstances #-}
+ 3 {-# LANGUAGE MultiParamTypeClasses #-}
+ 4 {-# OPTIONS -Wall -fno-warn-orphans #-}
2 5 3 6 -- | All types.
4 7 5 8 module Hpaste.Types
- 6 (module Hpaste.Types.MVC
- 7 ,module Hpaste.Types.Paste
+ 9 (module Hpaste.Types.Paste
8 10 ,module Hpaste.Types.Channel
9 11 ,module Hpaste.Types.Language
10 12 ,module Hpaste.Types.Page
11 13 ,module Hpaste.Types.Newtypes
- 12 ,module Hpaste.Types.View
13 14 ,module Hpaste.Types.Config
14 15 ,module Hpaste.Types.Activity
15 16 ,module Hpaste.Types.Stepeval
- 16 ,module Hpaste.Types.Report)
+ 17 ,module Hpaste.Types.Report
+ 18 ,HPState
+ 19 ,HPCtrl
+ 20 ,HPModel)
17 21 where
18 22 - 19 import Hpaste.Types.MVC
20 23 import Hpaste.Types.Paste
21 24 import Hpaste.Types.Channel
22 25 import Hpaste.Types.Language
23 26 import Hpaste.Types.Page
24 27 import Hpaste.Types.Newtypes
- 25 import Hpaste.Types.View
26 28 import Hpaste.Types.Config
27 29 import Hpaste.Types.Activity
28 30 import Hpaste.Types.Stepeval
29 31 import Hpaste.Types.Report
+ 32 + 33 import Control.Concurrent (Chan)
+ 34 import Control.Monad.Env
+ 35 import Control.Monad.IO
+ 36 import Control.Monad.Reader
+ 37 import Data.Text.Lazy (Text)
+ 38 import Snap.App.Types
+ 39 + 40 type HPState = Chan Text
+ 41 type HPCtrl = Controller Config HPState
+ 42 type HPModel = Model Config HPState
+ 43 + 44 instance AppLiftModel Config HPState where
+ 45 liftModel action = do
+ 46 conn <- env controllerStateConn
+ 47 anns <- env controllerState
+ 48 conf <- env controllerStateConfig
+ 49 let state = ModelState conn anns conf
+ 50 io $ runReaderT (runModel action) state
… … … … Edit file src/Hpaste/View/Browse.hs 33188 → 33188
6 6 7 7 module Hpaste.View.Browse
8 8 (page)
9 9 where
10 10 - 11 import Hpaste.Types
- 12 import Hpaste.View.Html
- 13 import Hpaste.View.Layout
- 14 import Hpaste.View.Paste (pasteLink)
- 15 - 16 - 17 import Control.Monad
- 18 import Data.Maybe
- 19 import Data.Time.Show (showDateTime)
- 20 import Prelude hiding ((++))
- 21 import Data.Monoid.Operator
- 22 import Text.Blaze.Html5 as H hiding (map)
+ 11 import Hpaste.Types
+ 12 import Hpaste.View.Html
+ 13 import Hpaste.View.Layout
+ 14 import Hpaste.View.Paste (pasteLink)
+ 15 + 16 + 17 import Control.Monad
+ 18 import Data.Maybe
+ 19 import Data.Monoid.Operator
23 20 import qualified Data.Text as T
24 21 import qualified Data.Text.Lazy as LT
- 25 import Text.Blaze.Extra
- 26 import Network.URI.Params
+ 22 import Data.Time.Show (showDateTime)
+ 23 import Network.URI.Params
+ 24 import Prelude hiding ((++))
+ 25 import Snap.App.Types
+ 26 import Text.Blaze.Extra
+ 27 import Text.Blaze.Html5 as H hiding (map)
27 28 28 29 -- | Render the browse page.
29 30 page :: Pagination -> [Channel] -> [Language] -> [Paste] -> Maybe String -> Html
30 31 page pn chans langs ps mauthor =
31 32 layoutPage $ Page {
… … … … Edit file src/Hpaste/View/Html.hs 33188 → 33188
32 32 import Network.URI.Params
33 33 import Prelude hiding ((++))
34 34 import Text.Blaze.Html5 as H hiding (map,nav)
35 35 import qualified Text.Blaze.Html5.Attributes as A
36 36 import Text.Blaze.Extra
+ 37 import Snap.App.Types
37 38 38 39 -- | A class prefixed with amelie-.
39 40 aClass :: AttributeValue -> Attribute
40 41 aClass name = A.class_ ("amelie-" ++ name)
41 42 … … … … Edit file src/Hpaste/View/Reported.hs 33188 → 33188
14 14 15 15 import Data.Monoid.Operator ((++))
16 16 import Data.Time.Show (showDateTime)
17 17 import Prelude hiding ((++))
18 18 import Text.Blaze.Html5 as H hiding (map)
+ 19 import Snap.App.Types
19 20 20 21 -- | Render the reported page.
21 22 page :: Pagination -> [Report] -> Html
22 23 page pn rs =
23 24 layoutPage $ Page {
… … … … Edit file src/Hpaste/Types/Config.hs 33188 → 33188
5 5 ,Announcer(..))
6 6 where
7 7 8 8 import Database.PostgreSQL.Simple (ConnectInfo)
9 9 import Network.Mail.Mime (Address)
+ 10 import Snap.App.Types
10 11 11 12 -- | Site-wide configuration.
12 13 data Config = Config {
13 14 configAnnounce :: Announcer
14 15 , configPostgres :: ConnectInfo
… … … … 18 19 , configIrcDir :: FilePath
19 20 , configAdmin :: Address
20 21 , configSiteAddy :: Address
21 22 , configCacheDir :: FilePath
22 23 }
+ 24 + 25 instance AppConfig Config where
+ 26 getConfigDomain = configDomain
23 27 24 28 -- | Announcer configuration.
25 29 data Announcer = Announcer {
26 30 announceUser :: String
27 31 , announcePass :: String
… … … … Remove file src/Hpaste/Types/MVC.hs 33188
- 1 {-# OPTIONS -Wall #-} - 2 {-# LANGUAGE RecordWildCards #-} - 3 {-# LANGUAGE GeneralizedNewtypeDeriving #-} - 4 - 5 -- | Model-view-controller types. - 6 - 7 module Hpaste.Types.MVC - 8 (Controller(..) - 9 ,Model(..) - 10 ,ControllerState(..) - 11 ,ModelState(..)) - 12 where - 13 - 14 import Hpaste.Types.Cache - 15 import Hpaste.Types.Config - 16 - 17 import Control.Applicative (Applicative,Alternative) - 18 import Control.Concurrent.Chan (Chan) - 19 import Control.Monad (MonadPlus) - 20 import Control.Monad.Catch (MonadCatchIO) - 21 import Control.Monad.Reader (ReaderT,MonadReader) - 22 import Control.Monad.Trans (MonadIO) - 23 import Data.Text.Lazy (Text) - 24 import Database.PostgreSQL.Simple (Connection) - 25 import Snap.Core (Snap,MonadSnap) - 26 - 27 -- | The state accessible to the controller (DB/session stuff). - 28 data ControllerState = ControllerState { - 29 controllerStateConfig :: Config - 30 , controllerStateConn :: Connection - 31 , controllerStateCache :: Cache - 32 , controllerStateAnns :: Chan Text - 33 } - 34 - 35 -- | The controller monad. - 36 newtype Controller a = Controller { - 37 runController :: ReaderT ControllerState Snap a - 38 } deriving (Monad - 39 ,Functor - 40 ,Applicative - 41 ,Alternative - 42 ,MonadReader ControllerState - 43 ,MonadSnap - 44 ,MonadIO - 45 ,MonadPlus - 46 ,MonadCatchIO) - 47 - 48 -- | The state accessible to the model (just DB connection). - 49 data ModelState = ModelState { - 50 modelStateConn :: Connection - 51 , modelStateAnns :: Chan Text - 52 , modelStateConfig :: Config - 53 } - 54 - 55 -- | The model monad (limited access to IO, only DB access). - 56 newtype Model a = Model { - 57 runModel :: ReaderT ModelState IO a - 58 } deriving (Monad,Functor,Applicative,MonadReader ModelState,MonadIO) Remove file src/Hpaste/Types/View.hs 33188
- 1 module Hpaste.Types.View - 2 (Pagination(..)) - 3 where - 4 - 5 - 6 - 7 import Network.URI (URI) - 8 - 9 -- | Pagination data. - 10 data Pagination = Pagination { - 11 pnPage :: Integer - 12 , pnLimit :: Integer - 13 , pnURI :: URI - 14 , pnResults :: Integer - 15 , pnTotal :: Integer - 16 } deriving Show Edit file src/Hpaste/Model/Activity.hs 33188 → 33188
12 12 import Control.Monad.IO (io)
13 13 import Data.Maybe (mapMaybe)
14 14 import Data.Text.Lazy (pack)
15 15 import Data.Time
16 16 import Network.Curl.Download
+ 17 import Snap.App.Types
17 18 import System.Locale
18 19 import Text.Feed.Query
19 20 20 21 -- | Get commits of this project from a commit feed.
- 21 getCommits :: String -> Model [Commit]
+ 22 getCommits :: String -> Model c s [Commit]
22 23 getCommits uri = io $ do
23 24 result <- openAsFeed uri
24 25 case result of
25 26 Left _ -> return []
26 27 Right feed -> return $
27 28 let items = getFeedItems feed
28 29 in mapMaybe makeCommit items
- 29 + 30 30 31 where makeCommit item = do
31 32 title <- getItemTitle item
32 33 datestr <- getItemDate item
33 34 date <- parseDateString datestr
34 35 link <- getItemLink item
… … … … Edit file src/Hpaste/Model/Announcer.hs 33188 → 33188
22 22 import Data.Text.Lazy (Text,pack)
23 23 import Data.Text.Lazy.Encoding
24 24 import qualified Data.Text.Lazy.IO as T
25 25 import Network
26 26 import Prelude hiding ((++))
+ 27 import Snap.App.Types
27 28 import System.IO
28 29 29 30 -- | Start a thread and return a channel to it.
30 31 newAnnouncer :: Announcer -> IO (Chan Text)
31 32 newAnnouncer config = do
… … … … 48 49 cont h
49 50 lines <- getChanContents ans
50 51 forM_ lines $ \line -> send h line
51 52 52 53 -- | Announce something to the IRC.
- 53 announce :: Text -> Text -> Model ()
+ 54 announce :: Text -> Text -> Model c HPState ()
54 55 announce channel line = do
55 56 chan <- env modelStateAnns
56 57 io $ writeChan chan $ "PRIVMSG " ++ channel ++ " :" ++ line
… … … … Edit file src/Hpaste/Model/Channel.hs 33188 → 33188
7 7 module Hpaste.Model.Channel
8 8 (getChannels)
9 9 where
10 10 11 11 import Hpaste.Types
- 12 import Hpaste.Model
+ 12 + 13 import Snap.App
13 14 14 15 -- | Get the channels.
- 15 getChannels :: Model [Channel]
+ 16 getChannels :: Model c s [Channel]
16 17 getChannels =
17 18 queryNoParams ["SELECT *"
18 19 ,"FROM channel"]
… … … … Edit file src/Hpaste/Model/Language.hs 33188 → 33188
7 7 module Hpaste.Model.Language
8 8 (getLanguages)
9 9 where
10 10 11 11 import Hpaste.Types
- 12 import Hpaste.Model
+ 12 + 13 import Snap.App
13 14 14 15 -- | Get the languages.
- 15 getLanguages :: Model [Language]
+ 16 getLanguages :: Model c s [Language]
16 17 getLanguages =
17 18 queryNoParams ["SELECT id,name,title"
18 19 ,"FROM language"
19 20 ,"WHERE visible"
20 21 ,"ORDER BY ordinal,title ASC"]
… … … … Edit file src/Hpaste/Model/Paste.hs 33188 → 33188
20 20 ,getHints
21 21 ,validNick)
22 22 where
23 23 24 24 import Hpaste.Types
- 25 import Hpaste.Model
26 25 import Hpaste.Model.Announcer
27 26 28 27 import Control.Applicative ((<$>),(<|>))
29 28 import Control.Exception as E
30 29 import Control.Monad
… … … … 37 36 import Data.Text (Text,unpack,pack)
38 37 import Data.Text.IO as T (writeFile)
39 38 import Data.Text.Lazy (fromStrict)
40 39 import Language.Haskell.HLint
41 40 import Prelude hiding ((++))
+ 41 import Snap.App
42 42 import System.Directory
43 43 import System.FilePath
44 44 45 45 -- | Count public pastes.
- 46 countPublicPastes :: Maybe String -> Model Integer
+ 46 countPublicPastes :: Maybe String -> HPModel Integer
47 47 countPublicPastes mauthor = do
48 48 rows <- single ["SELECT COUNT(*)"
49 49 ,"FROM public_toplevel_paste"
50 50 ,"WHERE (? IS NULL) OR (author = ?)"]
51 51 (mauthor,mauthor)
52 52 return $ fromMaybe 0 rows
53 53 54 54 -- | Get the latest pastes.
- 55 getLatestPastes :: Model [Paste]
+ 55 getLatestPastes :: HPModel [Paste]
56 56 getLatestPastes =
57 57 queryNoParams ["SELECT *"
58 58 ,"FROM public_toplevel_paste"
59 59 ,"ORDER BY id DESC"
60 60 ,"LIMIT 20"]
61 61 62 62 -- | Get some paginated pastes.
- 63 getSomePastes :: Maybe String -> Pagination -> Model [Paste]
+ 63 getSomePastes :: Maybe String -> Pagination -> HPModel [Paste]
64 64 getSomePastes mauthor Pagination{..} =
65 65 query ["SELECT *"
66 66 ,"FROM public_toplevel_paste"
67 67 ,"WHERE (? IS NULL) OR (author = ?)"
68 68 ,"ORDER BY id DESC"
69 69 ,"OFFSET " ++ show (max 0 (pnPage - 1) * pnLimit)
70 70 ,"LIMIT " ++ show pnLimit]
71 71 (mauthor,mauthor)
72 72 73 73 -- | Get a paste by its id.
- 74 getPasteById :: PasteId -> Model (Maybe Paste)
+ 74 getPasteById :: PasteId -> HPModel (Maybe Paste)
75 75 getPasteById pid =
76 76 listToMaybe <$> query ["SELECT *"
77 77 ,"FROM public_paste"
78 78 ,"WHERE id = ?"]
79 79 (Only pid)
80 80 81 81 -- | Get annotations of a paste.
- 82 getAnnotations :: PasteId -> Model [Paste]
+ 82 getAnnotations :: PasteId -> HPModel [Paste]
83 83 getAnnotations pid =
84 84 query ["SELECT *"
85 85 ,"FROM public_paste"
86 86 ,"WHERE annotation_of = ?"
87 87 ,"ORDER BY id ASC"]
88 88 (Only pid)
89 89 90 90 -- | Get revisions of a paste.
- 91 getRevisions :: PasteId -> Model [Paste]
+ 91 getRevisions :: PasteId -> HPModel [Paste]
92 92 getRevisions pid = do
93 93 query ["SELECT *"
94 94 ,"FROM public_paste"
95 95 ,"WHERE revision_of = ? or id = ?"
96 96 ,"ORDER BY id DESC"]
97 97 (pid,pid)
98 98 99 99 -- | Create a paste, or update an existing one.
- 100 createOrUpdate :: [Language] -> [Channel] -> PasteSubmit -> Model (Maybe PasteId)
+ 100 createOrUpdate :: [Language] -> [Channel] -> PasteSubmit -> HPModel (Maybe PasteId)
101 101 createOrUpdate langs chans paste@PasteSubmit{..} = do
102 102 case pasteSubmitId of
103 103 Nothing -> createPaste langs chans paste
104 104 Just pid -> do updatePaste pid paste
105 105 return $ Just pid
106 106 107 107 -- | Create a new paste (possibly annotating an existing one).
- 108 createPaste :: [Language] -> [Channel] -> PasteSubmit -> Model (Maybe PasteId)
+ 108 createPaste :: [Language] -> [Channel] -> PasteSubmit -> HPModel (Maybe PasteId)
109 109 createPaste langs chans ps@PasteSubmit{..} = do
110 110 res <- single ["INSERT INTO paste"
111 111 ,"(title,author,content,channel,language,annotation_of,revision_of)"
112 112 ,"VALUES"
113 113 ,"(?,?,?,?,?,?,?)"
… … … … 126 126 just j m = maybe (return ()) m j
127 127 ann_pid = case pasteSubmitType of AnnotationOf pid -> Just pid; _ -> Nothing
128 128 rev_pid = case pasteSubmitType of RevisionOf pid -> Just pid; _ -> Nothing
129 129 130 130 -- | Create the hints for a paste.
- 131 createHints :: PasteSubmit -> PasteId -> Model ()
+ 131 createHints :: PasteSubmit -> PasteId -> HPModel ()
132 132 createHints ps pid = do
133 133 hints <- generateHintsForPaste ps pid
134 134 forM_ hints $ \hint ->
135 135 exec ["INSERT INTO hint"
136 136 ,"(paste,type,content)"
… … … … 139 139 (pid
140 140 ,suggestionSeverity hint
141 141 ,show hint)
142 142 143 143 -- | Announce the paste.
- 144 announcePaste :: PasteType -> Text -> PasteSubmit -> PasteId -> Model ()
+ 144 announcePaste :: PasteType -> Text -> PasteSubmit -> PasteId -> HPModel ()
145 145 announcePaste ptype channel PasteSubmit{..} pid = do
146 146 conf <- env modelStateConfig
147 147 verb <- getVerb
148 148 announce (fromStrict channel) $ fromStrict $ do
149 149 nick ++ " " ++ verb ++ " “" ++ pasteSubmitTitle ++ "” at " ++ link conf
… … … … 173 173 validNick s = first && all ok s && length s > 0 where
174 174 ok c = isDigit c || isLetter c || elem c "-_/\\;()[]{}?`'"
175 175 first = all (\c -> isDigit c || isLetter c) $ take 1 s
176 176 177 177 -- | Get hints for a Haskell paste from hlint.
- 178 generateHintsForPaste :: PasteSubmit -> PasteId -> Model [Suggestion]
+ 178 generateHintsForPaste :: PasteSubmit -> PasteId -> HPModel [Suggestion]
179 179 generateHintsForPaste PasteSubmit{..} (fromIntegral -> pid :: Integer) = io $
180 180 E.catch (generateHints (show pid) pasteSubmitPaste)
181 181 (\SomeException{} -> return [])
182 182 183 183 -- | Get hints for a Haskell paste from hlint.
… … … … 188 188 exists <- doesFileExist tmp
189 189 unless exists $ T.writeFile tmp $ contents
190 190 !hints <- hlint [tmp,"--quiet","--ignore=Parse error"]
191 191 return hints
192 192 - 193 getHints :: PasteId -> Model [Hint]
+ 193 getHints :: PasteId -> HPModel [Hint]
194 194 getHints pid =
195 195 query ["SELECT type,content"
196 196 ,"FROM hint"
197 197 ,"WHERE paste = ?"]
198 198 (Only pid)
199 199 200 200 -- | Update an existing paste.
- 201 updatePaste :: PasteId -> PasteSubmit -> Model ()
+ 201 updatePaste :: PasteId -> PasteSubmit -> HPModel ()
202 202 updatePaste pid PasteSubmit{..} = do
203 203 _ <- exec (["UPDATE paste"
204 204 ,"SET"]
205 205 ++
206 206 [intercalate ", " (map set (words fields))]
… … … … Edit file src/Hpaste/Model/Report.hs 33188 → 33188
8 8 9 9 module Hpaste.Model.Report
10 10 (getSomeReports,createReport,countReports)
11 11 where
12 12 - 13 import Hpaste.Types
- 14 import Hpaste.Model
- 15 import Hpaste.Controller.Cache
- 16 import Hpaste.Types.Cache as Key
- 17 - 18 import Control.Monad
- 19 - 20 import Control.Monad.Env
- 21 import Control.Monad.IO
- 22 import Data.Maybe
- 23 import Data.Monoid.Operator ((++))
+ 13 import Hpaste.Types
+ 14 import Hpaste.Controller.Cache
+ 15 import Hpaste.Types.Cache as Key
+ 16 + 17 import Control.Monad
+ 18 + 19 import Control.Monad.Env
+ 20 import Control.Monad.IO
+ 21 import Data.Maybe
+ 22 import Data.Monoid.Operator ((++))
+ 23 import qualified Data.Text as T
24 24 import qualified Data.Text.Lazy as LT
- 25 import qualified Data.Text as T
- 26 import Prelude hiding ((++))
- 27 import Network.Mail.Mime
+ 25 import Network.Mail.Mime
+ 26 import Prelude hiding ((++))
+ 27 import Snap.App
28 28 29 29 -- | Get some paginated reports.
- 30 getSomeReports :: Pagination -> Model [Report]
+ 30 getSomeReports :: Pagination -> Model c s [Report]
31 31 getSomeReports Pagination{..} =
32 32 queryNoParams ["SELECT created,paste,comments"
33 33 ,"FROM report"
34 34 ,"ORDER BY id DESC"
35 35 ,"OFFSET " ++ show (max 0 (pnPage - 1) * pnLimit)
36 36 ,"LIMIT " ++ show pnLimit]
37 37 38 38 -- | Count reports.
- 39 countReports :: Model Integer
+ 39 countReports :: Model c s Integer
40 40 countReports = do
41 41 rows <- singleNoParams ["SELECT COUNT(*)"
42 42 ,"FROM report"]
43 43 return $ fromMaybe 0 rows
44 44 45 45 -- | Create a new report.
- 46 createReport :: ReportSubmit -> Model (Maybe ReportId)
+ 46 createReport :: ReportSubmit -> Model Config s (Maybe ReportId)
47 47 createReport rs@ReportSubmit{..} = do
48 48 res <- single ["INSERT INTO report"
49 49 ,"(paste,comments)"
50 50 ,"VALUES"
51 51 ,"(?,?)"
… … … … 60 60 resetCacheModel (Key.Revision (fromIntegral pid))
61 61 reset rsPaste
62 62 sendReport rs
63 63 return res
64 64 - 65 sendReport :: ReportSubmit -> Model ()
+ 65 sendReport :: ReportSubmit -> Model Config s ()
66 66 sendReport ReportSubmit{..} = do
67 67 conf <- env modelStateConfig
68 68 _ <- io $ simpleMail (configAdmin conf)
69 69 (configSiteAddy conf)
70 70 (T.pack ("Paste reported: #" ++ show rsPaste))
… … … … Edit file src/Hpaste/Controller/Activity.hs 33188 → 33188
5 5 6 6 module Hpaste.Controller.Activity
7 7 (handle)
8 8 where
9 9 - 10 import Hpaste.Controller (outputText)
11 10 import Hpaste.Controller.Cache (cache)
- 12 import Hpaste.Model
13 11 import Hpaste.Model.Activity (getCommits)
14 12 import Hpaste.Types.Cache as Key
15 13 import Hpaste.View.Activity (page)
16 14 17 15 import Control.Monad.Env (env)
+ 16 import Snap.App
18 17 19 18 -- | Display commit history.
- 20 handle :: Controller ()
+ 19 handle :: HPCtrl ()
21 20 handle = do
22 21 html <- cache Key.Activity $ do
23 22 uri <- env $ configCommits . controllerStateConfig
24 23 repourl <- env $ configRepoURL . controllerStateConfig
25 24 commits <- model $ getCommits uri
… … … … Edit file src/Hpaste/Controller/Browse.hs 33188 → 33188
5 5 6 6 module Hpaste.Controller.Browse
7 7 (handle)
8 8 where
9 9 - 10 import Hpaste.Controller (output,getPagination,getStringMaybe)
- 11 import Hpaste.Model
12 10 import Hpaste.Model.Channel (getChannels)
13 11 import Hpaste.Model.Language (getLanguages)
14 12 import Hpaste.Model.Paste (getSomePastes,countPublicPastes)
15 13 import Hpaste.View.Browse (page)
16 14 + 15 import Snap.App
+ 16 17 17 -- | Browse all pastes.
- 18 handle :: Controller ()
+ 18 handle :: HPCtrl ()
19 19 handle = do
20 20 pn <- getPagination
21 21 author <- getStringMaybe "author"
22 22 total <- model $ countPublicPastes author
23 23 pastes <- model $ getSomePastes author pn
… … … … Edit file src/Hpaste/Controller/Cache.hs 33188 → 33188
11 11 where
12 12 13 13 14 14 import Hpaste.Types.Cache
15 15 import Hpaste.Types.Config
- 16 import Hpaste.Types.MVC
17 16 18 17 import Control.Concurrent
+ 18 import Control.Monad
19 19 import Control.Monad.IO (io)
- 20 import Control.Monad
21 20 import Control.Monad.Reader (asks)
22 21 import qualified Data.Map as M
23 22 import Data.Text.Lazy (Text)
24 23 import qualified Data.Text.Lazy.IO as T
+ 24 import Snap.App.Types
25 25 import System.Directory
26 26 import Text.Blaze.Html5 (Html)
27 27 import Text.Blaze.Renderer.Text (renderHtml)
28 28 29 29 -- | Create a new cache.
… … … … 31 31 newCache = do
32 32 var <- newMVar M.empty
33 33 return $ Cache var
34 34 35 35 -- | Cache conditionally.
- 36 cacheIf :: Bool -> Key -> Controller (Maybe Html) -> Controller (Maybe Text)
+ 36 cacheIf :: Bool -> Key -> Controller Config s (Maybe Html) -> Controller Config s (Maybe Text)
37 37 cacheIf pred key generate =
38 38 if pred
39 39 then cache key generate
40 40 else fmap (fmap renderHtml) generate
41 41 42 42 -- | Generate and save into the cache, or retrieve existing from the
43 43 -- | cache.
- 44 cache :: Key -> Controller (Maybe Html) -> Controller (Maybe Text)
+ 44 cache :: Key -> Controller Config s (Maybe Html) -> Controller Config s (Maybe Text)
45 45 cache key generate = do
46 46 tmpdir <- asks (configCacheDir . controllerStateConfig)
47 47 let cachePath = tmpdir ++ "/" ++ keyToString key
48 48 exists <- io $ doesFileExist cachePath
49 49 if exists
… … … … 54 54 Just text' -> do io $ T.writeFile cachePath text'
55 55 return text
56 56 Nothing -> return text
57 57 58 58 -- | Reset an item in the cache.
- 59 resetCache :: Key -> Controller ()
+ 59 resetCache :: Key -> Controller Config s ()
60 60 resetCache key = do
61 61 tmpdir <- asks (configCacheDir . controllerStateConfig)
62 62 io $ do
63 63 let cachePath = tmpdir ++ "/" ++ keyToString key
64 64 exists <- io $ doesFileExist cachePath
65 65 when exists $ removeFile cachePath
66 66 67 67 -- | Reset an item in the cache.
- 68 resetCacheModel :: Key -> Model ()
+ 68 resetCacheModel :: Key -> Model Config s ()
69 69 resetCacheModel key = do
70 70 tmpdir <- asks (configCacheDir . modelStateConfig)
71 71 io $ do
72 72 let cachePath = tmpdir ++ "/" ++ keyToString key
73 73 exists <- io $ doesFileExist cachePath
… … … … Edit file src/Hpaste/Controller/Diff.hs 33188 → 33188
6 6 7 7 module Hpaste.Controller.Diff
8 8 (handle)
9 9 where
10 10 - 11 import Hpaste.Controller
12 11 import Hpaste.Controller.Paste (withPasteKey)
- 13 import Hpaste.Model
14 12 import Hpaste.View.Diff (page)
15 13 + 14 import Snap.App
+ 15 16 16 -- | Diff one paste with another.
- 17 handle :: Controller ()
+ 17 handle :: HPCtrl ()
18 18 handle = do
19 19 withPasteKey "this" $ \this ->
20 20 withPasteKey "that" $ \that ->
21 21 output $ page this that
… … … … Edit file src/Hpaste/Controller/Home.hs 33188 → 33188
5 5 6 6 module Hpaste.Controller.Home
7 7 (handle)
8 8 where
9 9 - 10 import Hpaste.Controller (outputText,getMyURI)
11 10 import Hpaste.Controller.Cache (cache)
12 11 import Hpaste.Controller.Paste (pasteForm)
- 13 import Hpaste.Model
14 12 import Hpaste.Model.Channel (getChannels)
15 13 import Hpaste.Model.Language (getLanguages)
16 14 import Hpaste.Model.Paste (getLatestPastes)
17 15 import Hpaste.Types.Cache as Key
18 16 import Hpaste.View.Home (page)
19 17 + 18 import Snap.App
+ 19 20 20 -- | Handle the home page, display a simple list and paste form.
- 21 handle :: Controller ()
+ 21 handle :: HPCtrl ()
22 22 handle = do
23 23 html <- cache Key.Home $ do
24 24 pastes <- model $ getLatestPastes
25 25 chans <- model $ getChannels
26 26 langs <- model $ getLanguages
… … … … Edit file src/Hpaste/Controller/New.hs 33188 → 33188
5 5 6 6 module Hpaste.Controller.New
7 7 (handle,NewStyle(..))
8 8 where
9 9 - 10 import Hpaste.Controller
11 10 import Hpaste.Controller.Paste (pasteForm,getPasteId)
- 12 import Hpaste.Model
13 11 import Hpaste.Model.Channel (getChannels)
14 12 import Hpaste.Model.Language (getLanguages)
15 13 import Hpaste.Model.Paste (getPasteById)
16 14 import Hpaste.View.Annotate as Annotate (page)
17 15 import Hpaste.View.Edit as Edit (page)
18 16 import Hpaste.View.New as New (page)
19 17 20 18 import Control.Applicative
21 19 import Data.Text.Encoding (decodeUtf8)
- 22 import Snap.Core
+ 20 import Snap.App
23 21 24 22 data NewStyle = NewPaste | AnnotatePaste | EditPaste
25 23 deriving Eq
26 24 27 25 -- | Make a new paste.
- 28 handle :: NewStyle -> Controller ()
+ 26 handle :: NewStyle -> HPCtrl ()
29 27 handle style = do
30 28 chans <- model $ getChannels
31 29 langs <- model $ getLanguages
32 30 defChan <- fmap decodeUtf8 <$> getParam "channel"
33 31 pid <- if style == NewPaste then return Nothing else getPasteId
… … … … Edit file src/Hpaste/Controller/Paste.hs 33188 → 33188
11 11 ,getPasteIdKey
12 12 ,withPasteKey)
13 13 where
14 14 15 15 import Hpaste.Types
- 16 - 17 import Hpaste.Controller
18 16 import Hpaste.Controller.Cache (cache,resetCache)
- 19 import Hpaste.Model
20 17 import Hpaste.Model.Channel (getChannels)
21 18 import Hpaste.Model.Language (getLanguages)
22 19 import Hpaste.Model.Paste
23 20 import Hpaste.Types.Cache as Key
24 21 import Hpaste.View.Paste (pasteFormlet,page)
… … … … 31 28 import Data.Monoid.Operator ((++))
32 29 import Data.String (fromString)
33 30 import Data.Text (Text)
34 31 import Prelude hiding ((++))
35 32 import Safe
- 36 import Snap.Core
+ 33 import Snap.App
37 34 import Text.Blaze.Html5 as H hiding (output)
38 35 import Text.Formlet
39 36 40 37 -- | Handle the paste page.
- 41 handle :: Bool -> Controller ()
+ 38 handle :: Bool -> HPCtrl ()
42 39 handle revision = do
43 40 pid <- getPasteId
44 41 justOrGoHome pid $ \(pid :: Integer) -> do
45 42 html <- cache (if revision then Key.Revision pid else Key.Paste pid) $ do
46 43 paste <- model $ getPasteById (fromIntegral pid)
… … … … 66 63 , ppRevision = revision
67 64 }
68 65 justOrGoHome html outputText
69 66 70 67 -- | Control paste annotating / submission.
- 71 pasteForm :: [Channel] -> [Language] -> Maybe Text -> Maybe Paste -> Maybe Paste -> Controller Html
+ 68 pasteForm :: [Channel] -> [Language] -> Maybe Text -> Maybe Paste -> Maybe Paste -> HPCtrl Html
72 69 pasteForm channels languages defChan annotatePaste editPaste = do
73 70 params <- getParams
74 71 submitted <- isJust <$> getParam "submit"
75 72 revisions <- maybe (return []) (model . getRevisions) (fmap pasteId (annotatePaste <|> editPaste))
76 73 let formlet = PasteFormlet {
… … … … 98 95 pid <- model $ createPaste languages channels paste
99 96 maybe (return ()) redirectToPaste pid
100 97 return html
101 98 102 99 -- | Redirect to the paste's page.
- 103 redirectToPaste :: PasteId -> Controller ()
+ 100 redirectToPaste :: PasteId -> HPCtrl ()
104 101 redirectToPaste (PasteId pid) =
105 102 redirect $ "/" ++ fromString (show pid)
106 103 107 104 -- | Get the paste id.
- 108 getPasteId :: Controller (Maybe Integer)
+ 105 getPasteId :: HPCtrl (Maybe Integer)
109 106 getPasteId = (fmap toString >=> readMay) <$> getParam "id"
110 107 111 108 -- | Get the paste id by a key.
- 112 getPasteIdKey :: ByteString -> Controller (Maybe Integer)
+ 109 getPasteIdKey :: ByteString -> HPCtrl (Maybe Integer)
113 110 getPasteIdKey key = (fmap toString >=> readMay) <$> getParam key
114 111 115 112 -- | With the
- 116 withPasteKey :: ByteString -> (Paste -> Controller a) -> Controller ()
+ 113 withPasteKey :: ByteString -> (Paste -> HPCtrl a) -> HPCtrl ()
117 114 withPasteKey key with = do
118 115 pid <- getPasteIdKey key
119 116 justOrGoHome pid $ \(pid :: Integer) -> do
120 117 paste <- model $ getPasteById (fromIntegral pid)
121 118 justOrGoHome paste $ \paste -> do
… … … … Edit file src/Hpaste/Controller/Raw.hs 33188 → 33188
6 6 7 7 module Hpaste.Controller.Raw
8 8 (handle)
9 9 where
10 10 + 11 import Hpaste.Model.Paste (getPasteById)
11 12 import Hpaste.Types
12 13 - 13 import Hpaste.Controller
- 14 import Hpaste.Model
- 15 import Hpaste.Model.Paste (getPasteById)
- 16 17 14 import Control.Applicative
18 15 import Data.ByteString.UTF8 (toString)
19 16 import Data.Maybe
20 17 import Data.Text.Lazy (fromStrict)
21 18 import Prelude hiding ((++))
22 19 import Safe
- 23 import Snap.Core
+ 20 import Snap.App
24 21 25 22 -- | Handle the paste page.
- 26 handle :: Controller ()
+ 23 handle :: HPCtrl ()
27 24 handle = do
28 25 pid <- (>>= readMay) . fmap (toString) <$> getParam "id"
29 26 case pid of
30 27 Nothing -> goHome
31 28 Just (pid :: Integer) -> do
… … … … Edit file src/Hpaste/Controller/Report.hs 33188 → 33188
7 7 8 8 module Hpaste.Controller.Report
9 9 (handle)
10 10 where
11 11 - 12 import Hpaste.Controller
- 13 import Hpaste.Model
+ 12 import Hpaste.Controller.Cache (resetCache)
14 13 import Hpaste.Model.Paste (getPasteById)
15 14 import Hpaste.Model.Report
+ 15 import Hpaste.Types
+ 16 import Hpaste.Types.Cache as Key
16 17 import Hpaste.View.Report
17 18 import qualified Hpaste.View.Thanks as Thanks
- 18 import Hpaste.Types.Cache as Key
- 19 import Hpaste.Controller.Cache (resetCache)
20 19 21 20 import Control.Applicative
22 21 import Data.ByteString.UTF8 (toString)
23 22 import Data.Maybe
24 23 import Data.Monoid.Operator ((++))
25 24 import Data.Text (unpack)
26 25 import Prelude hiding ((++))
27 26 import Safe
- 28 import Snap.Core
+ 27 import Snap.App
29 28 import Text.Blaze.Html5 as H hiding (output,map,body)
30 29 import Text.Formlet
31 30 32 31 -- | Handle the report/delete page.
- 33 handle :: Controller ()
+ 32 handle :: HPCtrl ()
34 33 handle = do
35 34 pid <- (>>= readMay) . fmap (toString) <$> getParam "id"
36 35 case pid of
37 36 Nothing -> goHome
38 37 Just (pid :: Integer) -> do
… … … … 47 46 "Thanks, your comments have " ++
48 47 "been reported to the administrator."
49 48 Nothing -> maybe goHome (output . page frm) paste
50 49 51 50 -- | Report form.
- 52 exprForm :: Controller (Html,Maybe String)
+ 51 exprForm :: HPCtrl (Html,Maybe String)
53 52 exprForm = do
54 53 params <- getParams
55 54 submitted <- isJust <$> getParam "submit"
56 55 let formlet = ReportFormlet {
57 56 rfSubmitted = submitted
… … … … Edit file src/Hpaste/Controller/Reported.hs 33188 → 33188
5 5 6 6 module Hpaste.Controller.Reported
7 7 (handle)
8 8 where
9 9 - 10 import Hpaste.Controller (output,getPagination)
- 11 import Hpaste.Model
12 10 import Hpaste.Model.Report (getSomeReports,countReports)
+ 11 import Hpaste.Types
13 12 import Hpaste.View.Reported (page)
14 13 + 14 import Snap.App
+ 15 15 16 -- | List the reported pastes.
- 16 handle :: Controller ()
+ 17 handle :: HPCtrl ()
17 18 handle = do
18 19 pn <- getPagination
19 20 total <- model countReports
20 21 reports <- model $ getSomeReports pn
21 22 let pn' = pn { pnResults = fromIntegral (length reports)
… … … … Edit file src/Hpaste/Controller/Style.hs 33188 → 33188
5 5 6 6 module Hpaste.Controller.Style
7 7 (handle)
8 8 where
9 9 - 10 import Hpaste.Controller (outputText)
- 11 import Hpaste.Model
+ 10 import Hpaste.Types
12 11 import Hpaste.View.Style (style)
13 12 - 14 import Snap.Core (modifyResponse,setContentType)
+ 13 import Snap.App
15 14 - 16 handle :: Controller ()
+ 15 handle :: HPCtrl ()
17 16 handle = do
18 17 modifyResponse $ setContentType "text/css"
19 18 outputText $ style
… … … … Add file src/Snap/App.hs 33188
+ 1 module Snap.App + 2 (module Snap.Core + 3 ,module Snap.App.Types + 4 ,module Snap.App.Controller + 5 ,module Snap.App.Model) + 6 where + 7 + 8 import Snap.Core + 9 import Snap.App.Types + 10 import Snap.App.Controller + 11 import Snap.App.Model Add file src/Snap/App/Model.hs 33188
+ 1 {-# OPTIONS -Wall #-} + 2 {-# LANGUAGE OverloadedStrings #-} + 3 {-# LANGUAGE FlexibleContexts #-} + 4 + 5 -- | Model running. + 6 + 7 module Snap.App.Model + 8 (model + 9 ,query + 10 ,single + 11 ,singleNoParams + 12 ,queryNoParams + 13 ,exec + 14 ,module Hpaste.Types + 15 ,DB.Only(..)) + 16 where + 17 + 18 import Hpaste.Types + 19 + 20 import Control.Monad.Env (env) + 21 + 22 import Control.Monad.Reader + 23 import Data.String + 24 import Database.PostgreSQL.Simple (Only(..)) + 25 import qualified Database.PostgreSQL.Simple as DB + 26 import Database.PostgreSQL.Simple.QueryParams + 27 import Database.PostgreSQL.Simple.QueryResults + 28 import Snap.App.Types + 29 + 30 -- | Run a model action. + 31 model :: AppLiftModel c s => Model c s a -> Controller c s a + 32 model = liftModel + 33 + 34 -- | Query with some parameters. + 35 query :: (QueryParams ps,QueryResults r) => [String] -> ps -> Model c s [r] + 36 query q ps = do + 37 conn <- env modelStateConn + 38 Model $ ReaderT (\_ -> DB.query conn (fromString (unlines q)) ps) + 39 + 40 -- | Query a single field from a single result. + 41 single :: (QueryParams ps,QueryResults (Only r)) => [String] -> ps -> Model c s (Maybe r) + 42 single q ps = do + 43 rows <- query q ps + 44 case rows of + 45 [(Only r)] -> return (Just r) + 46 _ -> return Nothing + 47 + 48 -- | Query a single field from a single result (no params). + 49 singleNoParams :: (QueryResults (Only r)) => [String] -> Model c s (Maybe r) + 50 singleNoParams q = do + 51 rows <- queryNoParams q + 52 case rows of + 53 [(Only r)] -> return (Just r) + 54 _ -> return Nothing + 55 + 56 -- | Query with no parameters. + 57 queryNoParams :: (QueryResults r) => [String] -> Model c s [r] + 58 queryNoParams q = do + 59 conn <- env modelStateConn + 60 Model $ ReaderT (\_ -> DB.query_ conn (fromString (unlines q))) + 61 + 62 -- | Execute some SQL returning the rows affected. + 63 exec :: (QueryParams ps) => [String] -> ps -> Model c s Integer + 64 exec q ps = do + 65 conn <- env modelStateConn + 66 Model $ ReaderT (\_ -> DB.execute conn (fromString (unlines q)) ps) Add file src/Snap/App/Types.hs 33188
+ 1 {-# LANGUAGE FunctionalDependencies #-} + 2 {-# LANGUAGE MultiParamTypeClasses #-} + 3 {-# OPTIONS -Wall #-} + 4 {-# LANGUAGE RecordWildCards #-} + 5 {-# LANGUAGE GeneralizedNewtypeDeriving #-} + 6 + 7 -- | Model-view-controller app types. + 8 + 9 module Snap.App.Types + 10 (Controller(..) + 11 ,Model(..) + 12 ,ControllerState(..) + 13 ,ModelState(..) + 14 ,AppConfig(..) + 15 ,AppLiftModel(..) + 16 ,Pagination(..)) + 17 where + 18 + 19 import Control.Applicative (Applicative,Alternative) + 20 import Control.Monad (MonadPlus) + 21 import Control.Monad.Catch (MonadCatchIO) + 22 import Control.Monad.Reader (ReaderT,MonadReader) + 23 import Control.Monad.Trans (MonadIO) + 24 import Database.PostgreSQL.Simple (Connection) + 25 import Network.URI (URI) + 26 import Snap.Core (Snap,MonadSnap) + 27 + 28 -- | The state accessible to the controller (DB/session stuff). + 29 data ControllerState config state = ControllerState { + 30 controllerStateConfig :: config + 31 , controllerStateConn :: Connection + 32 , controllerState :: state + 33 } + 34 + 35 -- | The controller monad. + 36 newtype Controller config state a = Controller { + 37 runController :: ReaderT (ControllerState config state) Snap a + 38 } deriving (Monad + 39 ,Functor + 40 ,Applicative + 41 ,Alternative + 42 ,MonadReader (ControllerState config state) + 43 ,MonadSnap + 44 ,MonadIO + 45 ,MonadPlus + 46 ,MonadCatchIO) + 47 + 48 -- | The state accessible to the model (just DB connection). + 49 data ModelState config state = ModelState { + 50 modelStateConn :: Connection + 51 , modelStateAnns :: state + 52 , modelStateConfig :: config + 53 } + 54 + 55 -- | The model monad (limited access to IO, only DB access). + 56 newtype Model config state a = Model { + 57 runModel :: ReaderT (ModelState config state) IO a + 58 } deriving (Monad,Functor,Applicative,MonadReader (ModelState config state),MonadIO) + 59 + 60 -- | Pagination data. + 61 data Pagination = Pagination { + 62 pnPage :: Integer + 63 , pnLimit :: Integer + 64 , pnURI :: URI + 65 , pnResults :: Integer + 66 , pnTotal :: Integer + 67 } deriving Show + 68 + 69 class AppConfig config where + 70 getConfigDomain :: config -> String + 71 + 72 class AppLiftModel c s where + 73 liftModel :: Model c s a -> Controller c s a