-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathWiki.hs
324 lines (296 loc) · 12.5 KB
/
Wiki.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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
{-# LANGUAGE QuasiQuotes, TemplateHaskell, TypeFamilies #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-}
module Wiki
( Wiki (..)
, WikiRoute (..)
, resourcesWiki
, Handler
, Widget
, maybeAuth
, maybeAuthId
, requireAuth
, requireAuthId
, module Yesod.Core
, module Yesod.Form
, module Yesod.Persist
, module Settings
, module Model
, module StaticFiles
, StaticRoute (..)
, AuthRoute (..)
, WikiMessage (..)
, lift
, liftIO
, getCurrentTime
, UTCTime
, (<$>)
, (<*>)
, Text
, addNewsItem
, mappend
, fromLabel
, getBlogPost
, getBook
, getMapNode
) where
import Data.Time
import Yesod.Core hiding (YesodBreadcrumbs (..), breadcrumbs, setMessage)
import Yesod.Form hiding (Field, MsgDelete)
import Yesod.Persist
import Yesod.Static
import Yesod.Auth
import Yesod.Auth.OpenId
import Yesod.AtomFeed
import qualified Settings
import System.Directory
import qualified Data.ByteString.Lazy as L
import Database.Persist.GenericSql
import Settings (hamletFile, cassiusFile, juliusFile, widgetFile, luciusFile)
import Model
import StaticFiles
import Control.Monad (unless)
import Text.Jasmine (minifym)
import qualified Data.Text as T
import Control.Monad.Trans.Class (lift)
import Control.Monad.IO.Class (liftIO)
import Yesod.Message
import Data.Text (Text, pack)
import Control.Applicative ((<$>), (<*>))
import Text.Hamlet (Html, ihamletFile)
import Data.Monoid (mappend, mempty)
import qualified Yesod.Auth.OpenId as OpenId
import qualified Yesod.Auth.Message as Msg
mkMessage "Wiki" "messages" "en"
-- | The site argument for your application. This can be a good place to
-- keep settings and values requiring initialization before your application
-- starts running, such as database connections. Every handler will have
-- access to the data present here.
data Wiki = Wiki
{ getStatic :: Static -- ^ Settings for static file serving.
, connPool :: Settings.ConnectionPool -- ^ Database connection pool.
, myApproot :: Text
}
-- This is where we define all of the routes in our application. For a full
-- explanation of the syntax, please see:
-- http://docs.yesodweb.com/book/web-routes-quasi/
--
-- This function does three things:
--
-- * Creates the route datatype WikiRoute. Every valid URL in your
-- application can be represented as a value of this type.
-- * Creates the associated type:
-- type instance Route Wiki = WikiRoute
-- * Creates the value resourcesWiki which contains information on the
-- resources declared below. This is used in Controller.hs by the call to
-- mkYesodDispatch
--
-- What this function does *not* do is create a YesodSite instance for
-- Wiki. Creating that instance requires all of the handler functions
-- for our application to be in scope. However, the handler functions
-- usually require access to the WikiRoute datatype. Therefore, we
-- split these actions into two functions and place them in separate files.
mkYesodData "Wiki" $(parseRoutesFile "config/routes")
-- Please see the documentation for the Yesod typeclass. There are a number
-- of settings which can be configured by overriding methods here.
instance Yesod Wiki where
approot = myApproot
defaultLayout widget = do
mmsg <- getMessage
(title, bcs) <- breadcrumbs
muser <- fmap (fmap snd) maybeAuth
pc <- widgetToPageContent $ do
setTitleI title
widget
atomLink FeedR "Site activity"
atomLink BlogFeedR "Blog posts"
tm <- getRouteToMaster
mcurr <- getCurrentRoute
let isHome = fmap tm mcurr == Just RootR
ihamletToRepHtml $(ihamletFile "hamlet/default-layout.hamlet")
-- This is done to provide an optimization for serving static files from
-- a separate domain. Please see the staticroot setting in Settings.hs
urlRenderOverride a (StaticR s) =
Just $ uncurry (joinPath a $ Settings.staticroot $ myApproot a) $ renderRoute s
urlRenderOverride _ _ = Nothing
-- The page to be redirected to when authentication is required.
authRoute _ = Just $ AuthR LoginR
-- This function creates static content files in the static folder
-- and names them based on a hash of their content. This allows
-- expiration dates to be set far in the future without worry of
-- users receiving stale content.
addStaticContent ext' _ content = do
let fn = base64md5 content ++ '.' : T.unpack ext'
let content' =
if ext' == "js"
then case minifym content of
Left _ -> content
Right y -> y
else content
let statictmp = Settings.staticdir ++ "/tmp/"
liftIO $ createDirectoryIfMissing True statictmp
let fn' = statictmp ++ fn
exists <- liftIO $ doesFileExist fn'
unless exists $ liftIO $ L.writeFile fn' content'
return $ Just $ Right (StaticR $ StaticRoute ["tmp", T.pack fn] [], [])
clientSessionDuration _ = 60 * 24 * 7 * 2 -- 2 weeks
maximumContentLength _ (Just UploadDitamapR) = 1000 * 1000 * 5
maximumContentLength _ _ = 1000 * 1000 * 1
-- How to run database actions.
instance YesodPersist Wiki where
type YesodPersistBackend Wiki = SqlPersist
runDB db = liftIOHandler
$ fmap connPool getYesod >>= Settings.runConnectionPool db
instance RenderMessage Wiki FormMessage where
renderMessage _ _ = defaultFormMessage
instance YesodAuth Wiki where
type AuthId Wiki = UserId
-- Where to send a user after successful login
loginDest _ = SettingsR
-- Where to send a user after logout
logoutDest _ = RootR
getAuthId creds = runDB $ do
x <- getBy $ UniqueUser $ credsIdent creds
case x of
Just (uid, _) -> return $ Just uid
Nothing -> do
handle <- getUniqueHandle (1 :: Int)
fmap Just $ insert $ User (credsIdent creds) "Unnamed User" False handle Nothing Nothing Nothing
where
getUniqueHandle i = do
let h = UserHandleT $ pack $ "anon" ++ show i
x <- getBy $ UniqueHandle h
case x of
Nothing -> return h
Just _ -> getUniqueHandle $ i + 1
authPlugins = [authOpenId]
loginHandler = do
ident <- newIdent
let name = "openid_identifier" :: Text
defaultLayout $(widgetFile "login")
instance YesodBreadcrumbs Wiki where
breadcrumb RootR = return (MsgHomepageTitle, Nothing)
breadcrumb (PageR p) = do
t <- runDB $ getBy404 (UniquePage p) >>= get404 . pageTopic . snd
return (MsgTopicTitle $ topicTitle t, Just RootR)
breadcrumb CreateTopicR = return (MsgCreateTopicTitle, Just SettingsR)
breadcrumb (TopicR tid) = do
t <- runDB $ get404 tid
return (MsgTopicTitle $ topicTitle t, Just RootR)
breadcrumb SettingsR = return (MsgSettingsTitle, Just RootR)
breadcrumb CreateMapR = return (MsgCreateMapTitle, Just RootR)
breadcrumb (EditMapR i) = do
m <- runDB $ get404 i
return (MsgEditMapTitle $ tMapTitle m, Just SettingsR)
breadcrumb LabelsR = return (MsgLabelsTitle, Just SettingsR)
breadcrumb BrowseR = return (MsgBrowseTitle, Just RootR)
breadcrumb (ShowMapR tmid) = do
tm <- runDB $ get404 tmid
return (MsgShowMapTitle $ tMapTitle tm, Just RootR)
breadcrumb (AuthR LoginR) = return (MsgLoginTitle, Just RootR)
breadcrumb (BlogPostR year month slug) = do
blog <- getBlogPost year month slug
tm <- runDB $ get404 $ blogMap blog
return (MsgBlogPostTitle $ tMapTitle tm, Just RootR)
breadcrumb BookR = do
book <- runDB getBook
tm <- runDB $ get404 $ bookMap book
return (MsgBookTitle $ tMapTitle tm, Just RootR)
breadcrumb (BookChapterR mnslug mnslugs) = do
(_, mn) <- runDB $ getMapNode mnslug mnslugs
title <-
case (tMapNodeCtopic mn, tMapNodeCmap mn) of
(Just tid, _) -> runDB $ topicTitle <$> get404 tid
(Nothing, Just tmid) -> runDB $ tMapTitle <$> get404 tmid
(Nothing, Nothing) -> return "" -- FIXME
return (MsgBookChapterTitle title, Just BookR)
breadcrumb SearchR = return (MsgSearchTitle, Just RootR)
breadcrumb (WikiR ps) = do
let title = if null ps then MsgWikiHomeTitle else MsgWikiTitle (last ps)
let parent = if null ps then RootR else WikiR (init ps)
return (title, Just parent)
breadcrumb StaticR{} = return (MsgNotFound, Nothing)
breadcrumb FaviconR{} = return (MsgNotFound, Nothing)
breadcrumb RobotsR{} = return (MsgNotFound, Nothing)
breadcrumb FeedR{} = return (MsgNotFound, Nothing)
breadcrumb BlogFeedR{} = return (MsgNotFound, Nothing)
breadcrumb FeedItemR{} = return (MsgNotFound, Nothing)
breadcrumb EditPageR{} = return (MsgNotFound, Nothing)
breadcrumb NewLabelR{} = return (MsgNotFound, Nothing)
breadcrumb TopicLabelsR{} = return (MsgNotFound, Nothing)
breadcrumb MapLabelsR{} = return (MsgNotFound, Nothing)
breadcrumb AuthR{} = return (MsgNotFound, Nothing)
breadcrumb AddBlogMapR{} = return (MsgNotFound, Nothing)
breadcrumb SetBookR{} = return (MsgNotFound, Nothing)
breadcrumb RebuildSearchR{} = return (MsgNotFound, Nothing)
breadcrumb BlogR{} = return (MsgNotFound, Nothing)
breadcrumb StaticContentR{} = return (MsgNotFound, Nothing)
breadcrumb UploadDitamapR{} = return (MsgNotFound, Nothing)
breadcrumb DownloadDitamapR{} = return (MsgNotFound, Nothing)
breadcrumb UploadDitamapUrlR{} = return (MsgNotFound, Nothing)
breadcrumb BlogPostNoDateR{} = return (MsgNotFound, Nothing)
breadcrumb UploadBlogsR{} = return (MsgNotFound, Nothing)
breadcrumb CommentCountR{} = return (MsgNotFound, Nothing)
breadcrumb CommentsR{} = return (MsgNotFound, Nothing)
breadcrumb TopicWorldWriteableR{} = return (MsgNotFound, Nothing)
breadcrumb TopicNotWorldWriteableR{} = return (MsgNotFound, Nothing)
breadcrumb DeleteTopicR{} = return (MsgNotFound, Nothing)
breadcrumb DeleteMapR{} = return (MsgNotFound, Nothing)
breadcrumb EditMapNameR{} = return (MsgNotFound, Nothing)
breadcrumb MakeSubMapR{} = return (MsgNotFound, Nothing)
breadcrumb BestTopicR{} = return (MsgNotFound, Nothing)
breadcrumb UnlinkWikiR{} = return (MsgNotFound, Nothing)
class YesodBreadcrumbs y where
-- | Returns the title and the parent resource, if available. If you return
-- a 'Nothing', then this is considered a top-level page.
breadcrumb :: Route y -> GHandler sub y (WikiMessage, Maybe (Route y))
-- | Gets the title of the current page and the hierarchy of parent pages,
-- along with their respective titles.
breadcrumbs :: YesodBreadcrumbs y => GHandler sub y (WikiMessage, [(Route y, WikiMessage)])
breadcrumbs = do
x' <- getCurrentRoute
tm <- getRouteToMaster
let x = fmap tm x'
case x of
Nothing -> return (MsgNotFound, [])
Just y -> do
(title, next) <- breadcrumb y
z <- go [] next
return (title, z)
where
go back Nothing = return back
go back (Just this) = do
(title, next) <- breadcrumb this
go ((this, title) : back) next
addNewsItem :: Text -> WikiRoute -> Maybe Text -> Html -> SqlPersist (GGHandler s Wiki IO) ()
addNewsItem title url mhash content = do
now <- liftIO getCurrentTime
render <- lift getUrlRender
let hash = maybe "" ("#" `T.append`) mhash
_ <- insert $ NewsItem now title (render url `T.append` hash) content
return ()
fromLabel :: WikiMessage -> FieldSettings WikiMessage
fromLabel x = FieldSettings x Nothing Nothing Nothing
getBlogPost :: Int -> Month -> BlogSlugT -> GHandler sub Wiki Blog
getBlogPost year month slug =
runDB $ fmap snd $ getBy404 $ UniqueBlogSlug year month slug
getBook :: SqlPersist (GGHandler s Wiki IO) Book
getBook = do
x <- selectList [] [LimitTo 1]
case x of
[] -> lift notFound
(_, y):_ -> return y
getMapNode :: MapNodeSlug -> MapNodeSlugs -> SqlPersist (GGHandler s Wiki IO) (TMapNodeId, TMapNode)
getMapNode mnslug mnslugs = do
book <- getBook
(mnid, mn) <- go' (bookMap book) mnslug
go mnid mn mnslugs
where
go' tmid slug = getBy404 $ UniqueMapNode tmid slug
go mnid mn [] = return (mnid, mn)
go _ mn (x:xs) =
case tMapNodeCmap mn of
Nothing -> lift notFound -- FIXME more debug info?
Just tmid -> do
(mnid', mn') <- go' tmid x
go mnid' mn' xs