-
Notifications
You must be signed in to change notification settings - Fork 38
/
Main.hs
169 lines (145 loc) · 5.05 KB
/
Main.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
module Main where
import Control.Monad.Trans.Maybe
import Data.List (intersperse)
import Data.Text (Text, pack)
import Data.Text.Lazy (toStrict)
import Database.PostgreSQL.Simple.SqlQQ (sql)
import Lucid
import Network.HTTP.Types.Status
import Web.Spock.Simple
import qualified Data.Vector as Vector
import qualified Database.PostgreSQL.Simple as Pg
import qualified Database.PostgreSQL.Simple.FromField as Pg
import qualified Database.PostgreSQL.Simple.FromRow as Pg
import qualified Database.PostgreSQL.Simple.ToRow as Pg
-- This SpockAction is parameterized to work with /any/ database,
-- session and application state.
helloSpock :: SpockAction database session state ()
helloSpock = do lucid helloSpockHtml
app :: SpockM Pg.Connection session state ()
app =
do get "/" getProjects
post "/projects" postProject
get "/add-project" addProjectForm
main :: IO ()
main =
do runSpock
8000
(spock sessionConfig dbConn initialState app)
sessionConfig :: SessionCfg ()
sessionConfig =
SessionCfg "zurihac" (60 * 60) 0 True () Nothing
dbConn :: PoolOrConn Pg.Connection
dbConn =
PCConn (ConnBuilder
(Pg.connect
Pg.defaultConnectInfo {Pg.connectUser = "zurihac"
,Pg.connectDatabase = "zurihac"})
Pg.close
(PoolCfg 5 5 60))
initialState :: ()
initialState = ()
pageTemplate :: Html () -> Html ()
pageTemplate contents =
do html_ (do head_ (title_ "Hello!")
body_ contents)
link :: Text -> Html () -> Html ()
link url caption = a_ [href_ url] caption
helloSpockHtml :: Html ()
helloSpockHtml =
pageTemplate
(do h1_ "Hello!"
p_ "Hello, Lucid!"
p_ (do "I love "
link "http://haskell.org" "Haskell!"))
lucid :: Html () -> SpockAction database session state ()
lucid document = html (toStrict (renderText document))
data Project =
Project {projectName :: Text
,projectDescription :: Text
,projectAuthors :: [Text]}
instance Pg.FromRow Project where
fromRow = do
name <- Pg.field
description <- Pg.field
authors <- fmap Vector.toList Pg.field
return (Project name description authors)
sqlListAllProjects :: Pg.Query
sqlListAllProjects =
[sql| SELECT name, description, authors
FROM projects
ORDER BY name |]
fetchAllProjects :: Pg.Connection -> IO [Project]
fetchAllProjects dbConn = Pg.query_ dbConn sqlListAllProjects
projectToRow :: Project -> Html ()
projectToRow project =
tr_ (do td_ (toHtml (projectName project))
td_ (toHtml (projectDescription project))
td_ (commaSeparate (map toHtml (projectAuthors project))))
where
commaSeparate :: [Html ()] -> Html ()
commaSeparate = mconcat . intersperse ", "
renderProjects :: [Project] -> Html ()
renderProjects projects =
table_ (do thead_ (tr_ (do th_ "Name"
th_ "Description"
th_ "Authors"))
tbody_ (foldMap projectToRow projects))
getProjects :: SpockAction Pg.Connection session state ()
getProjects =
do allProjects <- runQuery fetchAllProjects
lucid (pageTemplate
(do h1_ "Projects"
renderProjects allProjects
link "/add-project" "Add Your Project!"))
projectFromPOST :: SpockAction database session state (Maybe Project)
projectFromPOST =
runMaybeT
(do name <-
MaybeT (param "name")
description <-
MaybeT (param "description")
authors <-
sequence
(map (\i -> MaybeT (param (pack ("author-" ++ show i))))
[0 .. 5])
return (Project name description authors))
sqlAddProject :: Pg.Query
sqlAddProject =
[sql| INSERT INTO projects (name, description, authors)
VALUES (?, ?, ?) |]
instance Pg.ToRow Project where
toRow (Project name description authors) =
Pg.toRow (name, description, Vector.fromList authors)
insertProject :: Project -> Pg.Connection -> IO ()
insertProject project dbConn =
do Pg.execute dbConn sqlAddProject project
return ()
postProject :: SpockAction Pg.Connection session state ()
postProject =
do maybeProject <- projectFromPOST
case maybeProject of
Nothing ->
do lucid (p_ "Invalid submission")
setStatus badRequest400
Just project ->
do runQuery (insertProject project)
redirect "/"
addProjectForm :: SpockAction database session state ()
addProjectForm =
do lucid
(pageTemplate
(do form_
[method_ "post",action_ "/projects"]
(do p_ (do label_ "Project"
input_ [name_ "name"])
p_ (do label_ "Description"
input_ [name_ "description"])
mapM_ authorRow [0 .. 5]
input_ [type_ "submit" ,value_ "Add Project"])))
where authorRow i =
do p_ (do label_ (toHtml ("Author #" ++ show i))
input_ [name_ (pack ("author-" ++ show i))])