@@ -4,46 +4,76 @@
44 OverloadedLabels,
55 OverloadedStrings ,
66 TypeApplications ,
7− TypeOperators #-}
7+ TypeOperators,
8+ PartialTypeSignatures #-}
9+
10+{-# OPTIONS_GHC -Wno-partial-type-signatures #-}
11+
812
913module Data.Query (
1014 Schema,
1115 createTables,
1216 getPost,
1317 getUser,
14− createDraft,
15− updateDraft,
16− publishDraft,
17− getDrafts,
1818 getLastPosts,
19− getPostsForUser
19+ getPostsForUser,
20+ Limit,
21+ publishPost
2022) where
2123
2224import Squeal.PostgreSQL
2325import Data.User
2426import Data.Post
2527import Data.Schema
26−-- import Data.Function (on)
27−import qualified Data.Map.Lazy as M
28+import Data.Function (on)
2829import Data.Connection
2930import Data.PostElement
3031import qualified Servant as S
3132import Control.Monad.Morph
32−import Data.Maybe
3333import Data.Word (Word64)
34+import qualified Data.List as L
35+
36+
37+-- MARK: Generalized Post Queries
38+
39+type TableEndo schema params from grouping = TableExpression schema params from grouping ->
40+ TableExpression schema params from grouping
41+
42+postsQ :: TableEndo Schema params _ 'Ungrouped -> Query Schema params _
43+postsQ te = selectStar $ te (from $ table #posts)
3444
45+postViewT :: TableEndo Schema params _ 'Ungrouped -> TableExpression Schema params _ 'Ungrouped
46+postViewT te = from $ subquery (postsQ te `As` #posts) &
47+ innerJoin (table #postElements)
48+ (#posts ! #postRowId .== #postElements ! #rowElementId)
3549
36−getQuoteRowsQ :: Query Schema (TuplePG (Only QuoteId)) (RowPG (ElementRow Post))
37−getQuoteRowsQ = UnsafeQuery "WITH RECURSIVE getQuote AS ( \
38− \SELECT * FROM \"quoteElements\" quote WHERE \"rowElementId\" = $1 \
39− \UNION ALL \
40− \SELECT newQuote.* FROM \"quoteElements\" newQuote \
41− \INNER JOIN getQuote q ON q.\"rowElementQuote\" = newQuote.\"rowElementId\" \
42− \) \
43− \SELECT * FROM getQuote"
50+getPostQ :: TableEndo Schema params _ 'Ungrouped -> TableEndo Schema params _ 'Ungrouped -> Query Schema params (RowPG PostRowResponse)
51+getPostQ oTe te = select (
52+ #posts ! #postRowId :*
53+ #posts ! #postRowAuthorId :*
54+ -- #rowElementId :*
55+ #postElements ! #rowElementOrd :*
56+ #postElements ! #rowElementMarkdown :*
57+ #postElements ! #rowElementLatex :*
58+ #postElements ! #rowElementImage :*
59+ #postElements ! #rowElementQuote :*
60+ #postElements ! #rowElementAttachment)
61+ (oTe $ postViewT te)
4462
45−getQuoteQ :: Query Schema (TuplePG (Only QuoteId)) (RowPG PostQuoteRow)
46−getQuoteQ = selectStar $ from (table #quotes) & where_ (#quoteRowId .== param @1)
63+
64+-- MARK: Concrete Queries
65+
66+-- getQuoteRowsQ :: Query Schema (TuplePG (Only QuoteId)) (RowPG (ElementRow Post))
67+-- getQuoteRowsQ = UnsafeQuery "WITH RECURSIVE getQuote AS ( \
68+-- \SELECT * FROM \"quoteElements\" quote WHERE \"rowElementId\" = $1 \
69+-- \UNION ALL \
70+-- \SELECT newQuote.* FROM \"quoteElements\" newQuote \
71+-- \INNER JOIN getQuote q ON q.\"rowElementQuote\" = newQuote.\"rowElementId\" \
72+-- \) \
73+-- \SELECT * FROM getQuote"
74+
75+-- getQuoteQ :: Query Schema (TuplePG (Only QuoteId)) (RowPG PostQuoteRow)
76+-- getQuoteQ = selectStar $ from (table #quotes) & where_ (#quoteRowId .== param @1)
4777
4878-- getQuote :: QuoteId -> PQ Quote
4979-- getQuote qId = do
@@ -55,62 +85,27 @@ getQuoteQ = selectStar $ from (table #quotes) & where_ (#quoteRowId .== param @1
5585getUserQ :: Query Schema (TuplePG (Only UserId)) (RowPG User)
5686getUserQ = selectStar $ from (table #users) & where_ (#userId .== param @1)
5787
58−getPostRowQ :: Query Schema (TuplePG (Only PostId)) (RowPG PostRow)
59−getPostRowQ = selectStar $ from (table #posts) & where_ (#postRowId .== param @1)
60−
61−getDraftRowQ :: Query Schema (TuplePG (Only DraftId)) (RowPG PostRow)
62−getDraftRowQ = selectStar $ from (table #drafts) & where_ (#postRowId .== param @1)
63−
64−getPostElementsQ :: Query Schema (TuplePG (Only PostId)) (RowPG (ElementRow Post))
65−getPostElementsQ = selectStar $ from (table #postElements) & where_ (#rowElementId .== param @1)
88+getPostByIdQ :: Query Schema (TuplePG (Only PostId)) (RowPG PostRowResponse)
89+getPostByIdQ = getPostQ id $ where_ $ #postRowId .== param @1
6690
67−getDraftElementsQ :: Query Schema (TuplePG (Only DraftId)) (RowPG (ElementRow Draft))
68−getDraftElementsQ = selectStar $ from (table #draftElements) & where_ (#rowElementId .== param @1)
91+type Limit = Word64
6992
70−getPost :: PostId -> StaticPQ (Maybe Post)
71−getPost pId = do
72− postRow <- runQueryParams getPostRowQ (Only pId) >>= getRow 0
73− postRowElements <- runQueryParams getPostElementsQ (Only pId) >>= getRows
74− return $ rowsToPost M.empty M.empty postRow postRowElements
75−
76−getLastPostRowsQ :: Word64 -> Query Schema '[] (RowPG (Only PostId))
77−getLastPostRowsQ n = select (#postRowId `as` #fromOnly) $
78− (from $ table #posts) &
79− orderBy [#postRowId & Desc] &
80− limit n
81−
82−getLastPosts :: Word64 -> StaticPQ [Post]
83−getLastPosts n = do
84− postIds <- runQuery (getLastPostRowsQ n) >>= getRows
85− catMaybes <$> traverse getPost (map fromOnly postIds)
86−
87−getUser :: UserId -> StaticPQ User
88−getUser uId = runQueryParams getUserQ (Only uId) >>= getRow 0
89−
90−
91−createDraftRowQ :: Manipulation Schema (TuplePG (Only UserId)) (RowPG (Only DraftId))
92−createDraftRowQ = insertRow #drafts
93− (Default `as` #postRowId :* Set (param @1) `as` #postRowAuthorId)
94− OnConflictDoRaise
95− (Returning $ #postRowId `as` #fromOnly)
93+getLastNPostsQ :: Limit -> Query Schema params (RowPG PostRowResponse)
94+getLastNPostsQ lim = getPostQ
95+ (limit lim . orderBy [#posts ! #postRowId & Desc]) id
9696
97+getLastNPostsForUserQ :: Limit -> Query Schema (TuplePG (Only UserId)) (RowPG PostRowResponse)
98+getLastNPostsForUserQ lim = getPostQ
99+ (limit lim . orderBy [#posts ! #postRowId & Desc])
100+ (where_ $ #postRowAuthorId .== param @1)
101+
97102createPostRowQ :: Manipulation Schema (TuplePG (Only UserId)) (RowPG (Only PostId))
98103createPostRowQ = insertRow #posts
99104 (Default `as` #postRowId :* Set (param @1) `as` #postRowAuthorId)
100105 OnConflictDoRaise
101106 (Returning $ #postRowId `as` #fromOnly)
102107
103−createDraftElementRowsQ :: Manipulation Schema (TuplePG (ElementRow Draft)) '[]
104−createDraftElementRowsQ = insertRow_ #draftElements
105− (Set (param @1) `as` #rowElementId :*
106− Set (param @2) `as` #rowElementOrd :*
107− Set (param @3) `as` #rowElementMarkdown :*
108− Set (param @4) `as` #rowElementLatex :*
109− Set (param @5) `as` #rowElementImage :*
110− Set (param @6) `as` #rowElementQuote :*
111− Set (param @7) `as` #rowElementAttachment)
112−
113−createPostElementRowsQ :: Manipulation Schema (TuplePG (ElementRow Post)) '[]
108+createPostElementRowsQ :: Manipulation Schema (TuplePG (PostElementRow Post)) '[]
114109createPostElementRowsQ = insertRow_ #postElements
115110 (Set (param @1) `as` #rowElementId :*
116111 Set (param @2) `as` #rowElementOrd :*
@@ -120,84 +115,38 @@ createPostElementRowsQ = insertRow_ #postElements
120115 Set (param @6) `as` #rowElementQuote :*
121116 Set (param @7) `as` #rowElementAttachment)
122117
123−createDraft :: UserId -> [PostElement Draft] -> StaticPQ Draft
124−createDraft uId els = transactionally_ $ do
125− dId' <- manipulateParams createDraftRowQ (Only uId)
126− dId <- lift $ fromOnly <$> getRow 0 dId'
127− traversePrepared_ createDraftElementRowsQ $ elementsToRows dId els
128− return $ mkDraft dId uId els
129−
130−getDraftsForUserQ :: Query Schema (TuplePG (Only UserId)) (RowPG (Only DraftId))
131−getDraftsForUserQ = select (#postRowId `as` #fromOnly) $
132− (from $ table #drafts) &
133− where_ (#postRowAuthorId .== param @1)
134−
135−getPostsForUserLimitingQ :: Word64 -> Query Schema (TuplePG (Only UserId)) (RowPG (Only DraftId))
136−getPostsForUserLimitingQ lim = select (#postRowId `as` #fromOnly) $
137− (from $ table #posts) &
138− where_ (#postRowAuthorId .== param @1) &
139− orderBy [#postRowId & Desc] &
140− limit lim
141−
142−getPostsForUser :: Word64 -> UserId -> StaticPQ [Post]
118+-- MARK: FrontEnd functions
119+
120+getPostsForUser :: Word64 -> UserId -> StaticPQ (Maybe [Post])
143121getPostsForUser lim uId = do
144− postIds <- runQueryParams (getPostsForUserLimitingQ lim) (Only uId) >>= getRows
145− catMaybes <$> traverse getPost (map fromOnly postIds)
146−
122+ postRows <- runQueryParams (getLastNPostsForUserQ lim) (Only uId) >>= getRows
123+ return . foldMaybe . map rowsToPost . L.groupBy ((==) `on` postRowId) $ postRows
124+
125+getPost :: PostId -> StaticPQ (Maybe Post)
126+getPost pId = do
127+ postRow <- runQueryParams getPostByIdQ (Only pId) >>= getRows
128+ return $ rowsToPost postRow
147129
148−getDraft :: DraftId -> StaticPQ (Maybe Draft)
149−getDraft dId = do
150− draftRow <- runQueryParams getDraftRowQ (Only dId) >>= getRow 0
151− draftRowElements <- runQueryParams getDraftElementsQ (Only dId) >>= getRows
152− return $ rowsToPost M.empty M.empty draftRow draftRowElements
153−
154−getDrafts :: UserId -> StaticPQ [Draft]
155−getDrafts uId = do
156− draftRow <- runQueryParams getDraftsForUserQ (Only uId) >>= getRows
157− catMaybes <$> traverse getDraft (map fromOnly draftRow)
158−
159−getDraftForUserWithIdQ :: Query Schema (TuplePG (UserId, DraftId)) (RowPG PostRow)
160−getDraftForUserWithIdQ = selectStar $
161− (from $ table #drafts) &
162− where_ (#postRowAuthorId .== param @1 .&& #postRowId .== param @2)
163−
164−deleteElementsForIdQ :: Manipulation Schema (TuplePG (Only DraftId)) '[]
165−deleteElementsForIdQ = deleteFrom_ #draftElements (#rowElementId .== param @1)
166−
167−deleteDraftQ :: Manipulation Schema (TuplePG (Only DraftId)) '[]
168−deleteDraftQ = deleteFrom_ #drafts (#postRowId .== param @1)
169−
170−updateDraft :: UserId -> DraftId -> [PostElement Draft] -> StaticPQ ()
171−updateDraft uId dId els = transactionally_ $ do
172− rs <- runQueryParams getDraftForUserWithIdQ (uId, dId) >>= getRows
173− let c = length (rs :: [PostRow])
174− if c /= 1 then
175− lift $ S.throwError S.err404
176− else do
177− _ <- manipulateParams deleteElementsForIdQ (Only dId)
178− traversePrepared_ createDraftElementRowsQ $ elementsToRows dId els
179−
180−getDraftElementRowsQ :: Query Schema (TuplePG (Only DraftId)) (RowPG (ElementRow Draft))
181−getDraftElementRowsQ = selectStar $
182− (from $ table #draftElements) &
183− where_ (#rowElementId .== param @1) &
184− orderBy [#rowElementOrd & Asc]
185−
186−publishDraft :: UserId -> DraftId -> StaticPQ ()
187−publishDraft uId dId = transactionally_ $ do
188− rs <- runQueryParams getDraftForUserWithIdQ (uId, dId) >>= getRows
189− let c = length (rs :: [PostRow])
190− if c /= 1 then
191− lift $ S.throwError S.err404
192− else do
193− els <- runQueryParams getDraftElementRowsQ (Only dId) >>= getRows
194− pId' <- manipulateParams createPostRowQ (Only uId)
195− pId <- lift $ fromOnly <$> getRow 0 pId'
196− traversePrepared_ createPostElementRowsQ $ map (fromDraft pId) els
197− _ <- manipulateParams deleteDraftQ (Only dId)
198− return ()
199−
200−
201−-- tupleFirst :: (a -> b) -> a -> (b, a)
202−-- tupleFirst f x = (f x, x)
130+getLastPosts :: Word64 -> StaticPQ (Maybe [Post])
131+getLastPosts n = do
132+ postRows <- runQuery (getLastNPostsQ n) >>= getRows
133+ return . foldMaybe . map rowsToPost . L.groupBy ((==) `on` postRowId) $ postRows
203134
135+getUser :: UserId -> StaticPQ User
136+getUser uId = runQueryParams getUserQ (Only uId) >>= getRow 0
137+
138+publishPost :: UserId -> [PostElement Post] -> StaticPQ PostId
139+publishPost uId els = transactionally_ $ do
140+ pId' <- manipulateParams createPostRowQ (Only uId) >>= firstRow
141+ case pId' of
142+ (Just (Only pId)) -> do
143+ traversePrepared_ createPostElementRowsQ $ elementsToRows pId els
144+ return pId
145+ _ -> lift $ S.throwError S.err404
146+
147+-- MARK: Utils
148+
149+foldMaybe :: [Maybe a] -> Maybe [a]
150+foldMaybe [] = Just []
151+foldMaybe (Nothing : _) = Nothing
152+foldMaybe (Just x : xs) = (x :) <$> foldMaybe xs