@@ -5,7 +5,8 @@
55 OverloadedStrings ,
66 TypeApplications ,
77 TypeOperators,
8− PartialTypeSignatures #-}
8+ PartialTypeSignatures,
9+ FlexibleInstances #-}
910
1011{-# OPTIONS_GHC -Wno-partial-type-signatures #-}
1112
@@ -21,13 +22,15 @@ module Data.Query (
2122 getPostsForUser,
2223 Limit,
2324 publishPost,
24− PostResponse(..),
2525 getChannelPosts,
2626 updateChannel,
2727 createNewChannel,
2828 getAllChannels,
2929 getAnonymousChannelPosts,
30− deleteNamedChannel
30+ deleteNamedChannel,
31+ HasUsers(..),
32+ wrapIntoUsers,
33+ ResponseWithUsers(..)
3134) where
3235
3336import Squeal.PostgreSQL
@@ -57,12 +60,11 @@ import Data.Text (Text)
5760import Data.Channel.NamedChannel
5861import Data.Channel.AnonymousChannel
5962import Data.Maybe (fromMaybe)
60−import Data.Monoid (mempty)
6163
6264-- MARK: Documentation
6365
64−instance ToSample PostResponse where
65− toSamples _ = samples $ PostResponse <$> (map snd $ toSamples Proxy) <*> [M.fromList $ map (tupleFirst userId . snd) (toSamples Proxy)]
66+instance (ToSample r) => ToSample (ResponseWithUsers r) where
67+ toSamples _ = samples $ ResponseWithUsers <$> (map snd $ toSamples Proxy) <*> [M.fromList $ map (tupleFirst userId . snd) (toSamples Proxy)]
6668
6769
6870-- MARK: Generalized Post Queries
@@ -74,7 +76,7 @@ postsQ :: TableEndo Schema params _ 'Ungrouped -> Query Schema params _
7476postsQ te = selectStar $ te (from $ table #posts)
7577
7678postViewT :: TableEndo Schema params _ 'Ungrouped -> TableExpression Schema params _ 'Ungrouped
77−postViewT te = from $ subquery (postsQ te `As` #posts) &
79+postViewT te = from $ subquery (postsQ te `As` #posts) &
7880 innerJoin (table #postElements)
7981 (#posts ! #postRowId .== #postElements ! #rowElementId)
8082
@@ -90,7 +92,7 @@ getPostQ oTe te = select (
9092 #postElements ! #rowElementLatex :*
9193 #postElements ! #rowElementImage :*
9294 #postElements ! #rowElementQuote :*
93− #postElements ! #rowElementAttachment)
95+ #postElements ! #rowElementAttachment)
9496 (oTe $ postViewT te)
9597
9698
@@ -113,14 +115,14 @@ getPostQ oTe te = select (
113115-- rows' <- runQuery getQuoteQ (Only qId) >>= getRows
114116-- qRow <- runQuery getQuoteQ (Only qId) >>= getRow 0
115117-- let rows = M.fromList $ map (tupleBy $ quoteRowId . head) . groupBy ((==) `on` quoteRowId) . sortBy (compare `on` quoteRowId) $ rows'
116−-- return $ M.lookup qId rows >>= rowsToQuote rows
118+-- return $ M.lookup qId rows >>= rowsToQuote rows
117119
118120getUserQ :: Query Schema (TuplePG (Only UserId)) (RowPG User)
119121getUserQ = selectStar $ from (table #users) & where_ (#userId .== param @1)
120122
121123verifiedUserIdsQ :: [UserId] -> Maybe (Query Schema '[] (RowPG (Only UserId)))
122124verifiedUserIdsQ uIds = case idsToColumn uIds of
123− Just idQ -> Just $ select (#users ! #userId `as` #fromOnly) $ from (subquery (idQ `As` #ids) &
125+ Just idQ -> Just $ select (#users ! #userId `as` #fromOnly) $ from (subquery (idQ `As` #ids) &
124126 innerJoin (table #users)
125127 (#ids ! #id .== #users ! #userId))
126128 Nothing -> Nothing
@@ -128,7 +130,7 @@ verifiedUserIdsQ uIds = case idsToColumn uIds of
128130
129131getUsersQ :: [UserId] -> Maybe (Query Schema '[] (RowPG User))
130132getUsersQ uIds = case idsToColumn uIds of
131− Just idQ -> Just $ selectDotStar #users $ from (subquery (idQ `As` #ids) &
133+ Just idQ -> Just $ selectDotStar #users $ from (subquery (idQ `As` #ids) &
132134 innerJoin (table #users)
133135 (#ids ! #id .== #users ! #userId))
134136 Nothing -> Nothing
@@ -138,7 +140,7 @@ getAllUsersQ = selectStar $ from $ table #users
138140
139141getPostsByIdQ :: [PostId] -> Maybe (Query Schema '[] (RowPG PostRowResponse))
140142getPostsByIdQ pIds = case idsToColumn pIds of
141− Just idQ -> Just $ selectDotStar #posts $ from (subquery (idQ `As` #ids) &
143+ Just idQ -> Just $ selectDotStar #posts $ from (subquery (idQ `As` #ids) &
142144 innerJoin (subquery $ getPostQ id id `As` #posts)
143145 (#ids ! #id .== #posts ! #postRowId))
144146 Nothing -> Nothing
@@ -147,25 +149,25 @@ getPostsByIdQ pIds = case idsToColumn pIds of
147149type Limit = Word64
148150
149151getLastNPostsQ :: Limit -> Query Schema params (RowPG PostRowResponse)
150−getLastNPostsQ lim = getPostQ
152+getLastNPostsQ lim = getPostQ
151153 (limit lim . orderBy [#posts ! #postRowUpdateTime & Desc]) id
152154
153155getLastNPostsForUserQ :: Limit -> Query Schema (TuplePG (Only UserId)) (RowPG PostRowResponse)
154−getLastNPostsForUserQ lim = getPostQ
155− (limit lim . orderBy [#posts ! #postRowUpdateTime & Desc])
156+getLastNPostsForUserQ lim = getPostQ
157+ (limit lim . orderBy [#posts ! #postRowUpdateTime & Desc])
156158 (where_ $ #postRowAuthorId .== param @1)
157−
159+
158160createPostRowQ :: Manipulation Schema (TuplePG (UserId, Vector Text)) (RowPG (Only PostId))
159−createPostRowQ = insertRow #posts
161+createPostRowQ = insertRow #posts
160162 (Default `as` #postRowId :*
161163 Set (param @1) `as` #postRowAuthorId :*
162164 Set currentTimestamp `as` #postRowUpdateTime :*
163− Set (param @2) `as` #postRowTags)
164− OnConflictDoRaise
165+ Set (param @2) `as` #postRowTags)
166+ OnConflictDoRaise
165167 (Returning $ #postRowId `as` #fromOnly)
166168
167169createPostElementRowsQ :: Manipulation Schema (TuplePG (PostElementRow Post)) '[]
168−createPostElementRowsQ = insertRow_ #postElements
170+createPostElementRowsQ = insertRow_ #postElements
169171 (Set (param @1) `as` #rowElementId :*
170172 Set (param @2) `as` #rowElementOrd :*
171173 Set (param @3) `as` #rowElementMarkdown :*
@@ -175,7 +177,7 @@ createPostElementRowsQ = insertRow_ #postElements
175177 Set (param @7) `as` #rowElementAttachment)
176178
177179getChannelForUserQ :: Query Schema (TuplePG (UserId, NamedChannelId)) (RowPG NamedChannelFull)
178−getChannelForUserQ = selectStar $
180+getChannelForUserQ = selectStar $
179181 from (table #channels) &
180182 where_ (#namedChannelFullOwner .== param @1 .&& #namedChannelFullId .== param @2)
181183
@@ -193,7 +195,7 @@ createNamedChannelQ = insertRow #channels
193195 Set (param @2) `as` #namedChannelFullName :*
194196 Set (param @3) `as` #namedChannelFullTags :*
195197 Set (param @4) `as` #namedChannelFullPeopleIds
196− )
198+ )
197199 OnConflictDoRaise
198200 (Returning $ #namedChannelFullId `as` #fromOnly)
199201
@@ -208,31 +210,24 @@ updateNamedChannelQ = update_ #channels
208210 Set (param @3) `as` #namedChannelFullName :*
209211 Set (param @4) `as` #namedChannelFullTags :*
210212 Set (param @5) `as` #namedChannelFullPeopleIds
211− )
213+ )
212214 (#namedChannelFullOwner .== param @2 .&& #namedChannelFullId .== param @1)
213−
215+
214216
215217getChannelPostsQ :: Limit -> [UserId] -> Vector Text -> Query Schema '[] (RowPG PostRowResponse)
216−getChannelPostsQ lim uIds tags = selectStar $ from (subquery $ postsQ' `As` #bar) &
218+getChannelPostsQ lim uIds tags = selectStar $ from (subquery $ postsQ' `As` #bar) &
217219 orderBy [#postRowUpdateTime & Desc] & limit lim
218− where
220+ where
219221 unionWithUsers = case idsToColumn uIds of
220− Just uIdsQ -> selectDotStar #posts' (from $ subquery (uIdsQ `As` #uIds) &
222+ Just uIdsQ -> selectDotStar #posts' (from $ subquery (uIdsQ `As` #uIds) &
221223 innerJoin (subquery $ getPostQ id id `As` #posts')
222224 (#uIds ! #id .== #posts' ! #postRowAuthorId)) &
223225 union
224226 Nothing -> id
225227 postsQ' = unionWithUsers $ getPostQ id $ where_ $ jsonbLit tags .?| #postRowTags
226−
227228
228−-- MARK: FrontEnd Data Structures
229229
230−data PostResponse = PostResponse {
231− posts :: [Post],
232− users :: M.Map UserId User
233−} deriving (Generic)
234−
235−instance ToJSON PostResponse
230+-- MARK: FrontEnd Data Structures
236231
237232data NamedChannelCreation = NamedChannelCreation {
238233 namedChannelCreationOwner :: UserId,
@@ -274,31 +269,31 @@ getChannelForUser uId cId = do
274269
275270-- TODO: Reject bad requests. Should be binary.
276271verifiedUsers :: [UserId] -> StaticPQ [UserId]
277−verifiedUsers uIds =
272+verifiedUsers uIds =
278273 case verifiedUserIdsQ uIds of
279274 Just uIdsQ -> fmap fromOnly <$> (runQuery uIdsQ >>= getRows)
280275 Nothing -> return []
281276
282277-- MARK: FrontEnd functions
283278
284−getPostsForUser :: Word64 -> UserId -> StaticPQ (Maybe PostResponse)
285−getPostsForUser lim uId = do
279+getPostsForUser :: Word64 -> UserId -> StaticPQ (Maybe (ResponseWithUsers [Post]))
280+getPostsForUser lim uId = do
286281 postRows <- runQueryParams (getLastNPostsForUserQ lim) (Only uId) >>= getRows
287− rowsToPosts postRows `liftMaybe` wrapIntoPostResponse
282+ rowsToPosts postRows `liftMaybe` wrapIntoResponse
288283
289−getPosts :: [PostId] -> StaticPQ (Maybe PostResponse)
290−getPosts pId =
284+getPosts :: [PostId] -> StaticPQ (Maybe (ResponseWithUsers [Post]))
285+getPosts pId =
291286 case getPostsByIdQ pId of
292287 Just psQ -> do
293288 postRows <- runQuery psQ >>= getRows
294− rowsToPosts postRows `liftMaybe` wrapIntoPostResponse
289+ rowsToPosts postRows `liftMaybe` wrapIntoResponse
295290 Nothing -> return Nothing
296291
297−getLastPosts :: Word64 -> StaticPQ (Maybe PostResponse)
298−getLastPosts n = do
292+getLastPosts :: Word64 -> StaticPQ (Maybe (ResponseWithUsers [Post]))
293+getLastPosts n = do
299294 postRows <- runQuery (getLastNPostsQ n) >>= getRows
300− rowsToPosts postRows `liftMaybe` wrapIntoPostResponse
301−
295+ rowsToPosts postRows `liftMaybe` wrapIntoResponse
296+
302297getUser :: UserId -> StaticPQ User
303298getUser uId = runQueryParams getUserQ (Only uId) >>= getRow 0
304299
@@ -307,7 +302,7 @@ getUsers ids = getUsersQ ids `liftMaybe` (fmap Just . (runQuery >=> getRows))
307302
308303getAllUsers :: StaticPQ [User]
309304getAllUsers = runQuery getAllUsersQ >>= getRows
310−
305+
311306publishPost :: UserId -> PostCreation -> StaticPQ PostId
312307publishPost uId pc = commitedTransactionallyUpdate $ do
313308 pId' <- manipulateParams createPostRowQ (uId, postCreationTags pc) >>= firstRow
@@ -330,20 +325,20 @@ updateChannel uId req = commitedTransactionallyUpdate $ do
330325 _ <- manipulateParams updateNamedChannelQ $ addUserToChannelUpdate uId (req {namedChannelPeopleIds = uIds})
331326 return ()
332327
333−getChannelPosts :: UserId -> Limit -> NamedChannelId -> StaticPQ PostResponse
328+getChannelPosts :: UserId -> Limit -> NamedChannelId -> StaticPQ (ResponseWithUsers [Post])
334329getChannelPosts uId lim cId = do
335330 channel <- getChannelForUser uId cId
336− postRows <- runQuery (getChannelPostsQ lim (toList $ namedChannelFullPeopleIds channel) (namedChannelFullTags channel))
331+ postRows <- runQuery (getChannelPostsQ lim (toList $ namedChannelFullPeopleIds channel) (namedChannelFullTags channel))
337332 >>= getRows
338− fmap (fromMaybe $ PostResponse mempty mempty) $ rowsToPosts postRows `liftMaybe` wrapIntoPostResponse
333+ fmap (fromMaybe $ ResponseWithUsers mempty mempty) $ rowsToPosts postRows `liftMaybe` wrapIntoResponse
339334
340−getAnonymousChannelPosts :: Limit -> AnonymousChannel -> StaticPQ PostResponse
335+getAnonymousChannelPosts :: Limit -> AnonymousChannel -> StaticPQ (ResponseWithUsers [Post])
341336getAnonymousChannelPosts lim (AnonymousChannel tags people) = do
342337 postRows <- runQuery (getChannelPostsQ lim people tags) >>= getRows
343− fmap (fromMaybe $ PostResponse mempty mempty) $ rowsToPosts postRows `liftMaybe` wrapIntoPostResponse
338+ fmap (fromMaybe $ ResponseWithUsers mempty mempty) $ rowsToPosts postRows `liftMaybe` wrapIntoResponse
344339
345340getAllChannels :: UserId -> StaticPQ [NamedChannel UserId]
346−getAllChannels uId = fmap (map removeUserFromChannel) $
341+getAllChannels uId = fmap (map removeUserFromChannel) $
347342 runQueryParams getAllChannelsForUserQ (Only uId) >>= getRows
348343
349344deleteNamedChannel :: UserId -> NamedChannelId -> StaticPQ ()
@@ -352,7 +347,7 @@ deleteNamedChannel uId cId = commitedTransactionallyUpdate $ do
352347 _ <- manipulateParams deleteNamedChannelQ (Only cId)
353348 return ()
354349
355−
350+
356351-- MARK: Utils
357352
358353foldMaybe :: [Maybe a] -> Maybe [a]
@@ -360,9 +355,6 @@ foldMaybe [] = Just []
360355foldMaybe (Nothing : _) = Nothing
361356foldMaybe (Just x : xs) = (x :) <$> foldMaybe xs
362357
363−userIdsFromPostResponse :: [Post] -> [UserId]
364−userIdsFromPostResponse = Set.toList . Set.fromList . map postAuthorId
365−
366358rowsToPosts :: [PostRowResponse] -> Maybe [Post]
367359rowsToPosts = foldMaybe . map rowsToPost . L.groupBy ((==) `on` postRowId)
368360
@@ -379,10 +371,31 @@ idsToColumn (i:ii) = Just $ values (unsafeIntToExpr i `as` #id) $ map ((`as` #id
379371unsafeIntToExpr :: Int64 -> Expression schema from grouping params ty
380372unsafeIntToExpr = UnsafeExpression . pack . show
381373
382−wrapIntoPostResponse :: [Post] -> StaticPQ (Maybe PostResponse)
383−wrapIntoPostResponse ps = do
384− pUsers <- getUsers $ userIdsFromPostResponse ps
385− return $ PostResponse ps . M.fromList . mapFirstBy userId <$> pUsers
374+data ResponseWithUsers r = ResponseWithUsers {
375+ response :: r,
376+ users :: M.Map UserId User
377+} deriving (Generic)
378+
379+instance (ToJSON r) => ToJSON (ResponseWithUsers r)
380+
381+instance (FromJSON r) => FromJSON (ResponseWithUsers r)
382+
383+class HasUsers r where
384+ userSet :: r -> Set.Set UserId
385+
386+ wrapIntoResponse :: r -> StaticPQ (Maybe (ResponseWithUsers r))
387+ wrapIntoResponse r = wrapIntoUsers r $ userSet r
388+
389+instance (Foldable t, HasUsers r) => HasUsers (t r) where
390+ userSet = foldMap userSet
391+
392+instance HasUsers Post where
393+ userSet = Set.singleton . postAuthorId
394+
395+wrapIntoUsers :: r -> Set.Set UserId -> StaticPQ (Maybe (ResponseWithUsers r))
396+wrapIntoUsers r us = do
397+ pUsers <- getUsers $ Set.toList us
398+ return $ ResponseWithUsers r . M.fromList . mapFirstBy userId <$> pUsers
386399
387400liftMaybe :: Applicative f => Maybe a -> (a -> f (Maybe b)) -> f (Maybe b)
388401liftMaybe Nothing _ = pure Nothing