@@ -1,13 +1,16 @@
11{-# LANGUAGE
2− DataKinds ,
3− DeriveGeneric ,
2+ DataKinds,
3+ DeriveGeneric,
44 OverloadedLabels,
5− OverloadedStrings ,
6− TypeApplications ,
5+ OverloadedStrings,
6+ TypeApplications,
77 TypeOperators,
88 PartialTypeSignatures,
99 FlexibleInstances,
10− KindSignatures #-}
10+ KindSignatures,
11+ DeriveAnyClass,
12+ RecordWildCards,
13+ ScopedTypeVariables #-}
1114
1215{-# OPTIONS_GHC -Wno-partial-type-signatures #-}
1316
@@ -16,7 +19,7 @@ module Data.Query (
1619 Schema,
1720 createTables,
1821 getPosts,
19− getUser,
22+ -- getUser,
2023 getUsers,
2124 getAllUsers,
2225 getLastPosts,
@@ -40,8 +43,31 @@ module Data.Query (
4043 createGroupChat,
4144 updateGroupChat,
4245 leaveGroupChat,
43− getGroupChatForUser
44−) where
46+ getGroupChatForUser,
47+ getFaculty,
48+ updateFaculty,
49+ getFacultyFromQuery,
50+ getUserWithEmail,
51+ createToken,
52+ validateToken,
53+ invalidateToken,
54+ tryVerifyToken,
55+ getVerifiedToken,
56+ UserCreation(..),
57+ createUser,
58+ tryActivateToken,
59+ deactivateToken,
60+ searchUsers,
61+ searchTags,
62+ updateUser,
63+ UserUpdateRow(..),
64+ killAllTokens,
65+ channelCountForUser,
66+ pruneAuth,
67+ searchChannels,
68+ isValidFaculty,
69+ groupChatIsValidForUser
70+ ) where
4571
4672import Squeal.PostgreSQL
4773import Data.User
@@ -67,14 +93,23 @@ import Servant.Docs
6793import Data.Proxy
6894import Data.Vector (Vector, toList, fromList)
6995import Data.Text (Text)
96+import qualified Data.Text as T
7097import Data.Channel.NamedChannel
7198import Data.Channel.AnonymousChannel
72−import Data.Maybe (fromMaybe)
99+import Data.Maybe (fromMaybe, catMaybes, isJust)
73100import Squeal.PostgreSQL.Render
74101import Data.GroupChat
75102import Data.Message
76103import Server.Query.Dialog
77104import qualified Server.Query.Pagination as P
105+import Data.Faculty
106+import Data.ByteString (ByteString)
107+import Data.Foldable (fold)
108+import Data.Char
109+import qualified Data.Vector as V
110+import Server.Auth.Token
111+import Server.Error
112+import Data.Text.Validator
78113
79114-- MARK: Documentation
80115
@@ -137,31 +172,76 @@ getPostQ oTe te = select (
137172-- let rows = M.fromList $ map (tupleBy $ quoteRowId . head) . groupBy ((==) `on` quoteRowId) . sortBy (compare `on` quoteRowId) $ rows'
138173-- return $ M.lookup qId rows >>= rowsToQuote rows
139174
140−getUserQ :: Query Schema (TuplePG (Only UserId)) (RowPG User)
141−getUserQ = selectStar $ from (table #users) & where_ (#userId .== param @1)
175+getUserWithEmailQ :: Query Schema (TuplePG (Only UserEmail)) (RowPG (User FacultyUrl))
176+getUserWithEmailQ = select (
177+ #userRowId `as` #userId :*
178+ #userRowFirstName `as` #firstName :*
179+ #userRowMiddleName `as` #middleName :*
180+ #userRowLastName `as` #lastName :*
181+ #userRowFacultyUrl `as` #userFaculty :*
182+ #userRowEmail `as` #userEmail
183+ ) (from (table #users) &
184+ where_ (#userRowEmail .== param @1) &
185+ where_ (notNull #userRowIsValidated))
142186
143187verifiedUserIdsQ :: [UserId] -> Maybe (Query Schema '[] (RowPG (Only UserId)))
144188verifiedUserIdsQ uIds = case idsToColumn uIds of
145− Just idQ -> Just $ select (#users ! #userId `as` #fromOnly) $ from (subquery (idQ `As` #ids) &
189+ Just idQ -> Just $ select (#users ! #userRowId `as` #fromOnly) $ from (subquery (idQ `As` #ids) &
146190 innerJoin (table #users)
147− (#ids ! #id .== #users ! #userId))
191+ (#ids ! #id .== #users ! #userRowId))
148192 Nothing -> Nothing
149193
150−
151−getUsersQ :: [UserId] -> Maybe (Query Schema '[] (RowPG User))
194+getUsersQ :: [UserId] -> Maybe (Query Schema '[] (RowPG UserRow))
152195getUsersQ uIds = case idsToColumn uIds of
153− Just idQ -> Just $ selectDotStar #users $ from (subquery (idQ `As` #ids) &
196+ Just idQ -> Just $ select (
197+ #users ! #userRowId :*
198+ #users ! #userRowFirstName :*
199+ #users ! #userRowMiddleName :*
200+ #users ! #userRowLastName :*
201+ #users ! #userRowFacultyUrl :*
202+ #users ! #userRowEmail :*
203+ #users ! #userRowIsValidated :*
204+ #faculties ! #facultyName `as` #userRowFacultyName :*
205+ #faculties ! #facultyUrl `as` #userRowFacultyURL :*
206+ #faculties ! #facultyPath `as` #userRowFacultyPath :*
207+ #faculties ! #facultyCampusName `as` #userRowFacultyCampusName :*
208+ #faculties ! #facultyCampusCode `as` #userRowFacultyCampusCode :*
209+ #faculties ! #facultyTags `as` #userRowFacultyTags :*
210+ #faculties ! #facultyAddress `as` #userRowFacultyAddress
211+ ) (from (subquery (idQ `As` #ids) &
154212 innerJoin (table #users)
155− (#ids ! #id .== #users ! #userId))
213+ (#ids ! #id .== #users ! #userRowId) &
214+ innerJoin (table #faculties)
215+ (#users ! #userRowFacultyUrl .== #faculties ! #facultyUrl)) &
216+ where_ (notNull $ #users ! #userRowIsValidated))
156217 Nothing -> Nothing
157218
158−getAllUsersQ :: Query Schema '[] (RowPG User)
159−getAllUsersQ = selectStar $ from $ table #users
219+getAllUsersQ :: Query Schema '[] (RowPG UserRow)
220+getAllUsersQ = select (
221+ #users ! #userRowId :*
222+ #users ! #userRowFirstName :*
223+ #users ! #userRowMiddleName :*
224+ #users ! #userRowLastName :*
225+ #users ! #userRowFacultyUrl :*
226+ #users ! #userRowEmail :*
227+ #users ! #userRowIsValidated :*
228+ #faculties ! #facultyName `as` #userRowFacultyName :*
229+ #faculties ! #facultyUrl `as` #userRowFacultyURL :*
230+ #faculties ! #facultyPath `as` #userRowFacultyPath :*
231+ #faculties ! #facultyCampusName `as` #userRowFacultyCampusName :*
232+ #faculties ! #facultyCampusCode `as` #userRowFacultyCampusCode :*
233+ #faculties ! #facultyTags `as` #userRowFacultyTags :*
234+ #faculties ! #facultyAddress `as` #userRowFacultyAddress
235+ ) (from ((table #users) &
236+ innerJoin (table #faculties)
237+ (#users ! #userRowFacultyUrl .== #faculties ! #facultyUrl))&
238+ where_ (notNull $ #users ! #userRowIsValidated))
160239
161240getPostsByIdQ :: [PostId] -> Maybe (Query Schema '[] (RowPG PostRowResponse))
162241getPostsByIdQ pIds = case idsToColumn pIds of
163242 Just idQ -> Just $ selectDotStar #posts $ from (subquery (idQ `As` #ids) &
164− innerJoin (subquery $ getPostQ (orderBy [#postElements ! #rowElementOrd & Asc] . orderBy [#posts ! #postRowId & Desc]) id `As` #posts)
243+ innerJoin (subquery $ getPostQ
244+ (orderBy [#postElements ! #rowElementId & Desc, #postElements ! #rowElementOrd & Asc]) id `As` #posts)
165245 (#ids ! #id .== #posts ! #postRowId))
166246 Nothing -> Nothing
167247
@@ -176,15 +256,20 @@ isNotFalse x = UnsafeExpression $ renderExpression x <+> "IS NOT FALSE"
176256getLastPostsQ
177257 :: TableEndo _ _ _ _
178258 -> Limit
179− -> ConditionOp Schema _ _ _ _ _ _
259+ -> P.PaginationDirection
180260 -> Query Schema ('Null (PG Int64) ': params) (RowPG PostRowResponse)
181−getLastPostsQ te lim (.^) = getPostQ
182− (orderBy [#postElements ! #rowElementOrd & Asc] . orderBy [#posts ! #postRowId & Desc])
183− (limit lim . te . where_ (isNotFalse $ #posts ! #postRowId .^ param @1) . orderBy [#posts ! #postRowId & Desc])
261+getLastPostsQ te lim dir = getPostQ
262+ (orderBy [#postElements ! #rowElementId & Desc, #postElements ! #rowElementOrd & Asc])
263+ (limit lim .
264+ te .
265+ where_ (ifThenElse (isNull $ param @1) true $ #posts ! #postRowId .^ param @1) .
266+ orderBy [#posts ! #postRowId & order])
267+ where
268+ ((.^), order) = directionToOperator dir
184269
185270getLastNPostsForUserQ
186271 :: Limit
187− -> ConditionOp Schema _ _ _ _ _ _
272+ -> P.PaginationDirection
188273 -> Query Schema (TuplePG (Maybe PostId, UserId)) (RowPG PostRowResponse)
189274getLastNPostsForUserQ = getLastPostsQ $ where_ (#posts ! #postRowAuthorId .== param @2)
190275
@@ -212,12 +297,58 @@ getChannelForUserQ = selectStar $
212297 from (table #channels) &
213298 where_ (#namedChannelFullOwner .== param @1 .&& #namedChannelFullId .== param @2)
214299
300+updateUserQ :: Manipulation Schema (TuplePG (UserUpdateRow)) (RowPG (Only UserId))
301+updateUserQ = update #users (
302+ Same `as` #userRowId :*
303+ Set (param @2) `as` #userRowFirstName :*
304+ Set (param @3) `as` #userRowMiddleName :*
305+ Set (param @4) `as` #userRowLastName :*
306+ Set (param @5) `as` #userRowFacultyUrl :*
307+ Same `as` #userRowEmail :*
308+ Same `as` #userRowIsValidated)
309+ (#userRowId .== param @1)
310+ (Returning $ #userRowId `as` #fromOnly)
311+
215312getAllChannelsForUserQ :: Query Schema (TuplePG (Only UserId)) (RowPG NamedChannelFull)
216313getAllChannelsForUserQ = selectStar $
217314 from (table #channels) &
218315 where_ (#namedChannelFullOwner .== param @1) &
219316 orderBy [#namedChannelFullId & Desc]
220317
318+searchChannelsForUserQ :: Query Schema (TuplePG (UserId, Text)) (RowPG NamedChannelFull)
319+searchChannelsForUserQ = selectStar $
320+ from (table #channels) &
321+ where_ (#namedChannelFullOwner .== param @1) &
322+ orderBy [
323+ tsRankCd vector1 query & Desc,
324+ tsRankCd vector query & Desc
325+ ]
326+ where
327+ vector = tsVector' (textArrayToText [
328+ #namedChannelFullName,
329+ unsafeFunction "unnest" #namedChannelFullTags
330+ ])
331+ vector1 = tsVector' #namedChannelFullName
332+ query = tsQuery' (param @2)
333+
334+updateFacultyQ :: Manipulation Schema (TuplePG Faculty) '[]
335+updateFacultyQ = insertRow #faculties
336+ (
337+ Set (param @1) `as` #facultyName :*
338+ Set (param @2) `as` #facultyUrl :*
339+ Set (param @3) `as` #facultyPath :*
340+ Set (param @4) `as` #facultyCampusName :*
341+ Set (param @5) `as` #facultyCampusCode :*
342+ Set (param @6) `as` #facultyTags :*
343+ Set (param @7) `as` #facultyAddress
344+ )
345+ (OnConflictDoNothing)
346+ (Returning Nil)
347+
348+getFacultyQ :: Query Schema (TuplePG (Only Text)) (RowPG Faculty)
349+getFacultyQ = selectStar (from (table #faculties) &
350+ where_ (#facultyUrl .== param @1))
351+
221352createNamedChannelQ :: Manipulation Schema (TuplePG NamedChannelCreation) (RowPG (Only NamedChannelId))
222353createNamedChannelQ = insertRow #channels
223354 (
@@ -230,6 +361,11 @@ createNamedChannelQ = insertRow #channels
230361 OnConflictDoRaise
231362 (Returning $ #namedChannelFullId `as` #fromOnly)
232363
364+countChannelsQ :: Query Schema (TuplePG (Only UserId)) (RowPG (Only Int64))
365+countChannelsQ = select (countStar `as` #fromOnly) (from (table #channels) &
366+ where_ (#namedChannelFullOwner .== param @1) &
367+ groupBy #namedChannelFullOwner)
368+
233369deleteNamedChannelQ :: Manipulation Schema (TuplePG (Only NamedChannelId)) '[]
234370deleteNamedChannelQ = deleteFrom_ #channels (#namedChannelFullId .== param @1)
235371
@@ -246,26 +382,23 @@ updateNamedChannelQ = update_ #channels
246382
247383getChannelPostsQ
248384 :: Limit
249− -> ConditionOp Schema _ _ _ _ _ _
385+ -> P.PaginationDirection
250386 -> [UserId]
251387 -> Vector Text
252388 -> Query
253389 Schema
254− (TuplePG (Only (Maybe PostId))) -- ^ post id from which to start
390+ (TuplePG (Only (Maybe PostId))) -- post id from which to start
255391 (RowPG PostRowResponse)
256−getChannelPostsQ lim (.^) uIds tags = selectStar $ from (subquery $ postsQ' `As` #bar) &
257− where_ (isNotFalse $ #postRowId .^ param @1) &
258− orderBy [#rowElementOrd & Asc] & limit lim . orderBy [#postRowId & Desc]
392+getChannelPostsQ lim dir uIds tags = getLastPostsQ
393+ (where_ $ jsonbLit tags .?| #postRowTags .|| isUser #postRowAuthorId)
394+ lim
395+ dir
259396 where
260− unionWithUsers = case idsToColumn uIds of
261− Just uIdsQ -> selectDotStar #posts' (from $ subquery (uIdsQ `As` #uIds) &
262− innerJoin (subquery $ getPostQ id id `As` #posts')
263− (#uIds ! #id .== #posts' ! #postRowAuthorId)) &
264− union
265− Nothing -> id
266− postsQ' = unionWithUsers $ getPostQ id $ where_ $ jsonbLit tags .?| #postRowTags
267−
268−getTagsQ :: Query Schema '[] (RowPG (Only String))
397+ isUser = case idsToColumn uIds of
398+ Just uIdsQ -> (`in_` uIdsQ)
399+ Nothing -> const $ false
400+
401+getTagsQ :: Query Schema params (RowPG (Only String))
269402getTagsQ = selectDistinct (unsafeFunction "unnest" #postRowTags `as` #fromOnly) $
270403 from (table #posts)
271404
@@ -276,15 +409,26 @@ createMessageQ :: Manipulation
276409createMessageQ = insertRow #messages
277410 (
278411 Default `as` #messageStorageId :*
279− Set (param @1) `as` #messageStorageAuthorId :*
412+ Set (notNull $ param @1) `as` #messageStorageAuthorId :*
280413 Set (param @2) `as` #messageStorageDestinationGroupId :*
281414 Set (param @3) `as` #messageStorageDestinationUserId :*
282− Set (param @4) `as` #messageStorageBody :*
415+ Set (notNull $ param @4) `as` #messageStorageBody :*
283416 Set currentTimestamp `as` #messageStorageTime
284417 )
285418 OnConflictDoRaise
286419 (Returning $ #messageStorageId `as` #fromOnly)
287420
421+createPhantomMessageQ :: Manipulation Schema (TuplePG (Only GroupChatId)) '[]
422+createPhantomMessageQ = insertRow_ #messages
423+ (
424+ Default `as` #messageStorageId :*
425+ Set null_ `as` #messageStorageAuthorId :*
426+ Set (notNull $ param @1) `as` #messageStorageDestinationGroupId :*
427+ Set null_ `as` #messageStorageDestinationUserId :*
428+ Set null_ `as` #messageStorageBody :*
429+ Set currentTimestamp `as` #messageStorageTime
430+ )
431+
288432createGroupChatQ :: Manipulation Schema (TuplePG GroupChatCreation) (RowPG (Only GroupChatId))
289433createGroupChatQ = insertRow #groupChats
290434 (
@@ -317,15 +461,15 @@ type ConditionOp schema from grouping params nullity0 nullity1 (ty :: PGType)
317461
318462type ChatQueryParams = TuplePG
319463 ( UserId
320− , Maybe String -- ^ Just UserId as String
464+ , Maybe String -- Just UserId as String
321465 , Maybe MessageId
322466 )
323467
324468getChatsQ
325469 :: Limit
326− -> ConditionOp Schema _ _ _ _ _ _
470+ -> P.PaginationDirection
327471 -> Query Schema ChatQueryParams (RowPG ChatsResponse)
328−getChatsQ lim (.^) = select (
472+getChatsQ lim dir = select (
329473 #chats ! #messageId `as` #chatResponseMessageId :*
330474 #messages ! #messageStorageAuthorId `as` #chatResponseMessageAuthorId :*
331475 #messages ! #messageStorageDestinationGroupId `as` #chatResponseMessageDestinationGroupId :*
@@ -345,9 +489,11 @@ getChatsQ lim (.^) = select (
345489 where_ (isNull (#groupChats ! #groupChatUsers) .||
346490 (#groupChats ! #groupChatUsers .? param @2)) &
347491 where_ (isNotFalse $ #chats ! #messageId .^ param @3) &
348− orderBy [#chats ! #messageId & Desc] &
349− limit lim
492+ orderBy [#chats ! #messageId & order] &
493+ limit lim &
494+ orderBy [#chats ! #messageId & Desc]
350495 where
496+ ((.^), order) = directionToOperator dir
351497 chatsQ :: Query Schema _ ChatsQueryResponse
352498 chatsQ = unionAll groupChats userChats
353499 where
@@ -358,7 +504,7 @@ getChatsQ lim (.^) = select (
358504 (ifThenElse
359505 (#messageStorageAuthorId .== param @1)
360506 #messageStorageDestinationUserId
361− (notNull #messageStorageAuthorId)) `as` #messageUserId) $
507+ #messageStorageAuthorId) `as` #messageUserId) $
362508 from (table #messages) &
363509 where_ (isNotNull #messageStorageDestinationUserId .&&
364510 (#messageStorageAuthorId .== param @1 .||
@@ -381,33 +527,39 @@ getChatsQ lim (.^) = select (
381527
382528getMessagesQ
383529 :: Limit
384− -> ConditionOp Schema _ _ _ _ _ _
530+ -> P.PaginationDirection
385531 -> Query Schema (TuplePG
386532 ( UserId
387533 , UserId
388534 , Maybe MessageId
389535 )) (RowPG MessageStorage)
390−getMessagesQ lim (.^) = selectStar (from (table #messages) &
536+getMessagesQ lim dir = selectStar (from (table #messages) &
391537 where_ (isNotNull #messageStorageDestinationUserId .&&
392538 ((#messageStorageAuthorId .== param @1 .&& #messageStorageDestinationUserId .== param @2) .||
393539 (#messageStorageDestinationUserId .== param @1 .&& #messageStorageAuthorId .== param @2))) &
394540 where_ (isNotFalse $ #messageStorageId .^ param @3) &
395− orderBy [#messageStorageId & Desc] &
396− limit lim)
541+ orderBy [#messageStorageId & order] &
542+ limit lim &
543+ orderBy [#messageStorageId & Desc])
544+ where
545+ ((.^), order) = directionToOperator dir
397546
398547getGroupMessagesQ
399548 :: Limit
400− -> ConditionOp Schema _ _ _ _ _ _
549+ -> P.PaginationDirection
401550 -> Query Schema (TuplePG
402551 ( GroupChatId
403552 , Maybe MessageId
404553 )) (RowPG MessageStorage)
405−getGroupMessagesQ lim (.^) = selectStar (from (table #messages) &
554+getGroupMessagesQ lim dir = selectStar (from (table #messages) &
406555 where_ (isNotNull #messageStorageDestinationGroupId .&&
407556 (#messageStorageDestinationGroupId .== param @1)) &
408557 where_ (isNotFalse $ #messageStorageId .^ param @2) &
409− orderBy [#messageStorageId & Desc] &
410− limit lim)
558+ orderBy [#messageStorageId & order] &
559+ limit lim &
560+ orderBy [#messageStorageId & Desc])
561+ where
562+ ((.^), order) = directionToOperator dir
411563
412564-- String -- user id as String
413565getGroupChatForUserQ :: Query Schema (TuplePG (String, GroupChatId)) (RowPG GroupChat)
@@ -416,18 +568,272 @@ getGroupChatForUserQ = selectStar $
416568 where_ (#groupChatId .== param @2) &
417569 where_ (#groupChatUsers .? param @1)
418570
571+getFacultySearchResultQ :: Query Schema (TuplePG (Only Text)) (RowPG Faculty)
572+getFacultySearchResultQ = selectStar (from (table #faculties) &
573+ where_ (vector @@ query) &
574+ orderBy [
575+ tsRankCd vector1 query & Desc,
576+ tsRankCd vector2 query & Desc,
577+ tsRankCd vector query & Desc
578+ ])
579+ where
580+ vector = tsVector "russian" (textArrayToText [
581+ #facultyName,
582+ #facultyPath,
583+ #facultyCampusName,
584+ (arrayToText #facultyTags)])
585+ vector1 = tsVector "russian" #facultyName
586+ vector2 = tsVector "russian" (textArrayToText [
587+ #facultyName,
588+ #facultyPath])
589+ query = tsQuery "russian" (param @1)
590+
591+searchUsersQ :: Query Schema (TuplePG (Only Text)) (RowPG UserRow)
592+searchUsersQ = select (
593+ #users ! #userRowId :*
594+ #users ! #userRowFirstName :*
595+ #users ! #userRowMiddleName :*
596+ #users ! #userRowLastName :*
597+ #users ! #userRowFacultyUrl :*
598+ #users ! #userRowEmail :*
599+ #users ! #userRowIsValidated :*
600+ #faculties ! #facultyName `as` #userRowFacultyName :*
601+ #faculties ! #facultyUrl `as` #userRowFacultyURL :*
602+ #faculties ! #facultyPath `as` #userRowFacultyPath :*
603+ #faculties ! #facultyCampusName `as` #userRowFacultyCampusName :*
604+ #faculties ! #facultyCampusCode `as` #userRowFacultyCampusCode :*
605+ #faculties ! #facultyTags `as` #userRowFacultyTags :*
606+ #faculties ! #facultyAddress `as` #userRowFacultyAddress
607+ ) (from ((table #users) &
608+ innerJoin (table #faculties)
609+ (#users ! #userRowFacultyUrl .== #faculties ! #facultyUrl)) &
610+ where_ (notNull $ #users ! #userRowIsValidated) &
611+ where_ (vector @@ query) &
612+ orderBy [tsRankCd vector query & Desc] &
613+ limit 100)
614+ where
615+ vector = tsVector' (textArrayToText [
616+ #users ! #userRowFirstName,
617+ #users ! #userRowMiddleName,
618+ #users ! #userRowLastName
619+ ])
620+ query = tsQuery' (param @1)
621+
622+tagsByFrquencyQ :: Query Schema params '["tag" ::: 'NotNull (PG Text), "count" ::: 'NotNull 'PGint8]
623+tagsByFrquencyQ = select
624+ (#fromOnly `as` #tag :* count #fromOnly `as` #count)
625+ (from (subquery $ getTagsQ `As` #tt) &
626+ groupBy #fromOnly)
627+
628+getNpopularTagsQ :: Limit -> Query Schema param (RowPG (Only Text))
629+getNpopularTagsQ lim = select (#tag `as` #fromOnly)
630+ (from (subquery $ tagsByFrquencyQ `As` #tt) &
631+ orderBy [#count & Desc] &
632+ limit lim)
633+
634+
635+searchTagsQ :: Query Schema (TuplePG (Only Text)) (RowPG (Only Text))
636+searchTagsQ = select (#tag `as` #fromOnly) $
637+ from (subquery (tagsByFrquencyQ `As` #tt)) &
638+ where_ (vector @@ query) &
639+ orderBy [#count & Desc] &
640+ limit 100
641+ where
642+ query = tsQuery' (param @1)
643+ vector = tsVector' #tag
644+
645+tsRankCd
646+ :: Expression schema from grouping params ('NotNull 'PGtext)
647+ -> Expression schema from grouping params ('NotNull 'PGtext)
648+ -> Expression schema from grouping params ('NotNull 'PGfloat4)
649+tsRankCd vector query = UnsafeExpression $
650+ "ts_rank_cd" <> parenthesized (commaSeparated ["ARRAY[1,1,1,1]", renderExpression vector, renderExpression query, "2|4|8|16|32"])
651+
652+tsVector
653+ :: ByteString -- ^ Language
654+ -> Expression schema from grouping params ('NotNull 'PGtext)
655+ -> Expression schema from grouping params ('NotNull 'PGtext)
656+tsVector lang x = UnsafeExpression $
657+ "to_tsvector" <> parenthesized (commaSeparated [singleQuotedUtf8 lang, renderExpression x])
658+
659+tsVector'
660+ :: Expression schema from grouping params ('NotNull 'PGtext)
661+ -> Expression schema from grouping params ('NotNull 'PGtext)
662+tsVector' x = UnsafeExpression $
663+ "to_tsvector" <> parenthesized (renderExpression x)
664+
665+tsQuery
666+ :: ByteString -- ^ Language
667+ -> Expression schema from grouping params ('NotNull 'PGtext)
668+ -> Expression schema from grouping params ('NotNull 'PGtext)
669+tsQuery lang x = UnsafeExpression $
670+ "to_tsquery" <> parenthesized (commaSeparated [singleQuotedUtf8 lang, renderExpression x])
671+
672+tsQuery'
673+ :: Expression schema from grouping params ('NotNull 'PGtext)
674+ -> Expression schema from grouping params ('NotNull 'PGtext)
675+tsQuery' x = UnsafeExpression $
676+ "to_tsquery" <> parenthesized (renderExpression x)
677+
678+arrayToText
679+ :: Expression schema from grouping params ('NotNull ('PGvararray ('NotNull 'PGtext)))
680+ -> Expression schema from grouping params ('NotNull 'PGtext)
681+arrayToText x = UnsafeExpression $
682+ "array_to_string" <> parenthesized (commaSeparated [renderExpression x, "' '"])
683+
684+(@@)
685+ :: Expression schema from grouping params (nullity0 'PGtext)
686+ -> Expression schema from grouping params (nullity1 'PGtext)
687+ -> Expression schema from grouping params (nullity2 'PGbool)
688+(@@) = unsafeBinaryOp "@@"
689+
690+space :: Expression schema from grouping params ('NotNull 'PGtext)
691+space = UnsafeExpression "' '"
692+
693+textArrayToText
694+ :: [Expression schema from grouping params ('NotNull 'PGtext)]
695+ -> Expression schema from grouping params ('NotNull 'PGtext)
696+textArrayToText = fold . L.intersperse space
697+
698+createUserQ :: Manipulation Schema (TuplePG UserCreation) (RowPG (Only UserId))
699+createUserQ = insertRow #users (
700+ Default `as` #userRowId :*
701+ Set (param @1) `as` #userRowFirstName :*
702+ Set (param @2) `as` #userRowMiddleName :*
703+ Set (param @3) `as` #userRowLastName :*
704+ Set (param @4) `as` #userRowFacultyUrl :*
705+ Set (param @5) `as` #userRowEmail :*
706+ Set false `as` #userRowIsValidated)
707+ OnConflictDoRaise
708+ (Returning (#userRowId `as` #fromOnly))
709+
710+removeUnverifiedUserQ :: Manipulation Schema (TuplePG (Only UserEmail)) '[]
711+removeUnverifiedUserQ = deleteFrom_ #users
712+ (#userRowEmail .== param @1 .&&
713+ (not_ . notNull $ #userRowIsValidated))
714+
715+
716+createTokenQ :: Manipulation Schema (TuplePG Token) '[]
717+createTokenQ = insertRow_ #tokens (
718+ Set (param @1) `as` #tokenUserId :*
719+ Set (param @2) `as` #tokenValue :*
720+ Set (param @3) `as` #tokenExpiryDate :*
721+ Set (param @4) `as` #tokenVerificationCode :*
722+ Set (param @5) `as` #tokenActivationTriesLeft :*
723+ Set (param @6) `as` #tokenUserAgent :*
724+ Set (param @7) `as` #tokenActivationCode :*
725+ Set (param @8) `as` #tokenDeactivationCode
726+ )
727+
728+updateTokenQ :: Manipulation Schema (TuplePG Token) '[]
729+updateTokenQ = update_ #tokens (
730+ Same `as` #tokenUserId :*
731+ Same `as` #tokenValue :*
732+ Set (param @3) `as` #tokenExpiryDate :*
733+ Set (param @4) `as` #tokenVerificationCode :*
734+ Set (param @5) `as` #tokenActivationTriesLeft :*
735+ Set (param @6) `as` #tokenUserAgent :*
736+ Set (param @7) `as` #tokenActivationCode :*
737+ Set (param @8) `as` #tokenDeactivationCode)
738+ (#tokenUserId .== param @1 .&& #tokenValue .== param @2)
739+
740+deleteTokenQ :: Manipulation Schema (TuplePG (UserId, ByteString)) '[]
741+deleteTokenQ = deleteFrom_ #tokens
742+ (#tokenUserId .== param @1 .&& #tokenValue .== param @2)
743+
744+validateUserQ :: Manipulation Schema (TuplePG (Only UserId)) '[]
745+validateUserQ = update_ #users (
746+ Same `as` #userRowId :*
747+ Same `as` #userRowFirstName :*
748+ Same `as` #userRowMiddleName :*
749+ Same `as` #userRowLastName :*
750+ Same `as` #userRowFacultyUrl :*
751+ Same `as` #userRowEmail :*
752+ Set true `as` #userRowIsValidated)
753+ (#userRowId .== param @1)
754+
755+getTokenQ :: Query Schema (TuplePG (UserId, ByteString)) (RowPG Token)
756+getTokenQ = selectStar (from (table #tokens) &
757+ where_ (#tokenUserId .== param @1 .&&
758+ #tokenValue .== param @2))
759+
760+getTokenActivationQ :: Query Schema (TuplePG (UserId, ByteString)) (RowPG Token)
761+getTokenActivationQ = selectStar (from (table #tokens) &
762+ where_ (#tokenUserId .== param @1 .&&
763+ #tokenActivationCode .== param @2))
764+
765+deactivateTokenQ :: Manipulation Schema (TuplePG (UserId, ByteString)) (RowPG (Only Text))
766+deactivateTokenQ = deleteFrom #tokens
767+ (#tokenUserId .== param @1 .&&
768+ #tokenDeactivationCode .== param @2)
769+ (Returning (#tokenUserAgent `as` #fromOnly))
770+
771+getVerifiedTokenQ :: Query Schema (TuplePG (UserId, ByteString)) (RowPG Token)
772+getVerifiedTokenQ = selectStar (from (table #tokens) &
773+ where_ (#tokenUserId .== param @1 .&&
774+ #tokenValue .== param @2 .&&
775+ isNull #tokenVerificationCode .&&
776+ currentTimestamp .< #tokenExpiryDate))
777+
778+killAllTokensQ :: Manipulation Schema (TuplePG (Only UserId)) '[]
779+killAllTokensQ = deleteFrom_ #tokens
780+ (#tokenUserId .== param @1)
781+
782+pruneDeadTokensQ :: Manipulation Schema '[] '[]
783+pruneDeadTokensQ = deleteFrom_ #tokens
784+ (#tokenExpiryDate .< currentTimestamp)
785+
786+pruneGhostUsersQ :: Manipulation Schema '[] '[]
787+pruneGhostUsersQ = deleteFrom_ #users
788+ ((not_ . in_ #userRowId $ select (#tokens ! #tokenUserId `as` #id) (from $ table #tokens)) .&&
789+ (not_ . notNull $ #userRowIsValidated))
419790
420791-- MARK: FrontEnd Data Structures
421792
793+instance HasValidatableText UserCreation where
794+ validateText UserCreation{..} = all validateText [
795+ userCreationFirstName,
796+ userCreationMiddleName,
797+ userCreationLastName,
798+ userCreationUserFaculty,
799+ userCreationUserEmail]
800+
801+data UserCreation = UserCreation {
802+ userCreationFirstName :: Text,
803+ userCreationMiddleName :: Text,
804+ userCreationLastName :: Text,
805+ userCreationUserFaculty :: FacultyUrl,
806+ userCreationUserEmail :: Text
807+} deriving (GHC.Generic, SOP.Generic, SOP.HasDatatypeInfo)
808+
809+instance ToJSON UserCreation where
810+ toJSON (UserCreation fName mName lName faculty email) = object [
811+ "firstName" .= fName,
812+ "middleName" .= mName,
813+ "lastName" .= lName,
814+ "faculty" .= faculty,
815+ "email" .= email
816+ ]
817+
818+instance FromJSON UserCreation where
819+ parseJSON = withObject "user creation" $ \e ->
820+ UserCreation <$> e .: "firstName" <*> e .: "middleName" <*> e .: "lastName" <*> e .: "faculty" <*> e .: "email"
821+
822+data UserUpdateRow = UserUpdateRow {
823+ userUpdateRowId :: UserId,
824+ userUpdateRowFirstName :: Text,
825+ userUpdateRowMiddleName :: Text,
826+ userUpdateRowLastName :: Text,
827+ userUpdateRowUserFaculty :: FacultyUrl
828+} deriving (GHC.Generic, SOP.Generic, SOP.HasDatatypeInfo)
829+
830+
422831data NamedChannelCreation = NamedChannelCreation {
423832 namedChannelCreationOwner :: UserId,
424833 namedChannelCreationName :: Text,
425834 namedChannelCreationTags :: Vector Text,
426835 namedChannelCreationPeopleIds :: Vector UserId
427−} deriving GHC.Generic
428−
429−instance SOP.Generic NamedChannelCreation
430−instance SOP.HasDatatypeInfo NamedChannelCreation
836+} deriving (GHC.Generic, SOP.Generic, SOP.HasDatatypeInfo)
431837
432838addUserToChannelCreate :: UserId -> NamedChannelCreationRequest -> NamedChannelCreation
433839addUserToChannelCreate uId (NamedChannelCreationRequest cName cTags cPeople) = NamedChannelCreation uId cName cTags cPeople
@@ -457,11 +863,33 @@ getChannelForUser uId cId = do
457863 Nothing -> lift $ S.throwError S.err404
458864
459865-- TODO: Reject bad requests. Should be binary.
460−verifiedUsers :: [UserId] -> StaticPQ [UserId]
461−verifiedUsers uIds =
866+verifyUsers :: [UserId] -> StaticPQ ()
867+verifyUsers uIds =
462868 case verifiedUserIdsQ uIds of
463− Just uIdsQ -> fmap fromOnly <$> (runQuery uIdsQ >>= getRows)
464− Nothing -> return []
869+ Just uIdsQ -> do
870+ (us :: [UserId]) <- fmap fromOnly <$> (runQuery uIdsQ >>= getRows)
871+ when (length us /= length uIds) $ lift $ S.throwError impossibleContent
872+ Nothing -> return ()
873+
874+deleteGroupChat :: GroupChatId -> StaticPQ ()
875+deleteGroupChat gId = do
876+ _ <- manipulateParams deleteGroupChatQ (Only gId)
877+ _ <- manipulateParams deleteGroupChatMessagesQ (Only gId)
878+ return ()
879+ where
880+ deleteGroupChatQ :: Manipulation Schema (TuplePG (Only GroupChatId)) '[]
881+ deleteGroupChatQ = deleteFrom_ #groupChats
882+ (#groupChatId .== param @1)
883+ deleteGroupChatMessagesQ :: Manipulation Schema (TuplePG (Only GroupChatId)) '[]
884+ deleteGroupChatMessagesQ = deleteFrom_ #messages
885+ (#messageStorageDestinationGroupId .== param @1)
886+
887+-- NOTE: Not transactional
888+createUser :: UserCreation -> StaticPQ UserId
889+createUser creation = do
890+ _ <- manipulateParams removeUnverifiedUserQ (Only $ userCreationUserEmail creation)
891+ rowThing <- manipulateParams createUserQ creation >>= firstRow >>= fromMaybe500
892+ return (fromOnly rowThing)
465893
466894-- MARK: FrontEnd functions
467895
@@ -473,7 +901,7 @@ getPostsForUser
473901 -> UserId
474902 -> StaticPQ (Maybe (ResponseWithUsers [Post]))
475903getPostsForUser pId lim dir uId = do
476− postRows <- runQueryParams (getLastNPostsForUserQ lim $ directionToOperator dir) (pId, uId) >>= getRows
904+ postRows <- runQueryParams (getLastNPostsForUserQ lim dir) (pId, uId) >>= getRows
477905 wrapIntoResponse (fromMaybe [] . rowsToPosts $ postRows)
478906
479907getPosts :: [PostId] -> StaticPQ (Maybe (ResponseWithUsers [Post]))
@@ -490,40 +918,56 @@ getLastPosts
490918 -> P.PaginationDirection
491919 -> StaticPQ (Maybe (ResponseWithUsers [Post]))
492920getLastPosts pId lim dir = do
493− postRows <- runQueryParams (getLastPostsQ id lim $ directionToOperator dir) (Only pId) >>= getRows
921+ postRows <- runQueryParams (getLastPostsQ id lim dir) (Only pId) >>= getRows
494922 rowsToPosts postRows `liftMaybe` wrapIntoResponse
495923
496−getUser :: UserId -> StaticPQ User
497−getUser uId = runQueryParams getUserQ (Only uId) >>= getRow 0
924+getUserWithEmail :: UserEmail -> StaticPQ (Maybe (User FacultyUrl))
925+getUserWithEmail email = runQueryParams getUserWithEmailQ (Only email) >>= firstRow
498926
499−getUsers :: [UserId] -> StaticPQ (Maybe [User])
500−getUsers ids = getUsersQ ids `liftMaybe` (fmap Just . (runQuery >=> getRows))
927+getUsers :: [UserId] -> StaticPQ (Maybe [User Faculty])
928+getUsers ids = getUsersQ ids `liftMaybe` (fmap (Just . map rowToUser) . (runQuery >=> getRows))
501929
502−getAllUsers :: StaticPQ [User]
503−getAllUsers = runQuery getAllUsersQ >>= getRows
930+getAllUsers :: StaticPQ [User Faculty]
931+getAllUsers = fmap rowToUser <$> (runQuery getAllUsersQ >>= getRows)
504932
505933publishPost :: UserId -> PostCreation -> StaticPQ PostId
506−publishPost uId pc = commitedTransactionallyUpdate $ do
934+publishPost uId pc = commitedTransactionallyUpdate $ do
507935 pId' <- manipulateParams createPostRowQ (uId, postCreationTags pc) >>= firstRow
508936 case pId' of
509937 (Just (Only pId)) -> do
510− traversePrepared_ createPostElementRowsQ $ elementsToRows pId (postCreationBody pc)
938+ traversePrepared_ createPostElementRowsQ $ elementsToRows pId (map stripPostElement $ postCreationBody pc)
511939 return pId
512940 _ -> lift $ S.throwError S.err404
513941
514942createNewChannel :: UserId -> NamedChannelCreationRequest -> StaticPQ (Maybe NamedChannelId)
515943createNewChannel uId req = commitedTransactionallyUpdate $ do
516− uIds <- verifiedUsers . toList $ namedChannelCreationRequestPeopleIds req
944+ let uIds = toList $ namedChannelCreationRequestPeopleIds req
945+ verifyUsers uIds
517946 cId <- manipulateParams createNamedChannelQ (addUserToChannelCreate uId (req {namedChannelCreationRequestPeopleIds = fromList uIds})) >>= firstRow
518947 return $ fmap fromOnly cId
519948
949+channelCountForUser :: UserId -> StaticPQ Int64
950+channelCountForUser uId = (fromMaybe 0 . fmap fromOnly) <$> (runQueryParams countChannelsQ (Only uId) >>= firstRow)
951+
520952updateChannel :: UserId -> NamedChannel UserId -> StaticPQ ()
521−updateChannel uId req = commitedTransactionallyUpdate $ do
522− uIds <- fmap fromList . verifiedUsers . toList $ namedChannelPeopleIds req
953+updateChannel uId req = do
954+ let uIds = namedChannelPeopleIds req
955+ verifyUsers (toList uIds)
523956 _ <- getChannelForUser uId $ namedChannelId req
524− _ <- manipulateParams updateNamedChannelQ $ addUserToChannelUpdate uId (req {namedChannelPeopleIds = uIds})
957+ _ <- commitedTransactionallyUpdate $ manipulateParams updateNamedChannelQ $ addUserToChannelUpdate uId (req {namedChannelPeopleIds = uIds})
525958 return ()
526959
960+getFaculty :: Text -> StaticPQ Faculty
961+getFaculty t = runQueryParams getFacultyQ (Only t) >>= firstRow >>= fromMaybe404
962+
963+isValidFaculty :: FacultyUrl -> StaticPQ Bool
964+isValidFaculty url = do
965+ (faculty :: Maybe Faculty) <- runQueryParams getFacultyQ (Only url) >>= firstRow
966+ return $ isJust faculty
967+
968+updateFaculty :: Faculty -> StaticPQ ()
969+updateFaculty fac = void $ manipulateParams updateFacultyQ fac
970+
527971getChannelPosts
528972 :: UserId
529973 -> Maybe PostId
@@ -535,7 +979,7 @@ getChannelPosts uId pId lim dir cId = do
535979 channel <- getChannelForUser uId cId
536980 postRows <- runQueryParams (getChannelPostsQ
537981 lim
538− (directionToOperator dir)
982+ dir
539983 (toList $ namedChannelFullPeopleIds channel)
540984 (namedChannelFullTags channel))
541985 (Only pId)
@@ -549,7 +993,7 @@ getAnonymousChannelPosts
549993 -> AnonymousChannel
550994 -> StaticPQ (ResponseWithUsers [Post])
551995getAnonymousChannelPosts pId lim dir (AnonymousChannel tags people) = do
552− postRows <- runQueryParams (getChannelPostsQ lim (directionToOperator dir) people tags) (Only pId) >>= getRows
996+ postRows <- runQueryParams (getChannelPostsQ lim dir people tags) (Only pId) >>= getRows
553997 fmap (fromMaybe $ ResponseWithUsers mempty mempty) $ rowsToPosts postRows `liftMaybe` wrapIntoResponse
554998
555999getAllChannels :: UserId -> StaticPQ [NamedChannel UserId]
@@ -557,13 +1001,13 @@ getAllChannels uId = fmap (map removeUserFromChannel) $
5571001 runQueryParams getAllChannelsForUserQ (Only uId) >>= getRows
5581002
5591003deleteNamedChannel :: UserId -> NamedChannelId -> StaticPQ ()
560−deleteNamedChannel uId cId = commitedTransactionallyUpdate $ do
1004+deleteNamedChannel uId cId = do
5611005 _ <- getChannelForUser uId cId
562− _ <- manipulateParams deleteNamedChannelQ (Only cId)
1006+ _ <- commitedTransactionallyUpdate $ manipulateParams deleteNamedChannelQ (Only cId)
5631007 return ()
5641008
565−getTags :: StaticPQ [String]
566−getTags = map fromOnly <$> (runQuery getTagsQ >>= getRows)
1009+getTags :: Limit -> StaticPQ [String]
1010+getTags lim = map fromOnly <$> (runQuery (getNpopularTagsQ lim) >>= getRows)
5671011
5681012getDialogs
5691013 :: UserId
@@ -572,7 +1016,7 @@ getDialogs
5721016 -> P.PaginationDirection
5731017 -> StaticPQ (ResponseWithUsers [Dialog])
5741018getDialogs uId mId lim dir = do
575− resp <- runQueryParams (getChatsQ lim $ directionToOperator dir) (uId, Just $ show uId, mId) >>= getRows
1019+ resp <- runQueryParams (getChatsQ lim dir) (uId, Just $ show uId, mId) >>= getRows
5761020 (>>= fromMaybe500) $ (responseToDialogs uId resp `liftMaybe` wrapIntoResponse)
5771021
5781022getMessagesInUserChat
@@ -583,7 +1027,7 @@ getMessagesInUserChat
5831027 -> P.PaginationDirection
5841028 -> StaticPQ [Message]
5851029getMessagesInUserChat selfId uId mId lim dir = do
586− resp <- runQueryParams (getMessagesQ lim $ directionToOperator dir) (selfId, uId, mId) >>= getRows
1030+ resp <- runQueryParams (getMessagesQ lim dir) (selfId, uId, mId) >>= getRows
5871031 fromMaybe500 . sequence . map restoreMessage $ resp
5881032
5891033getMessagesInGroupChat
@@ -595,44 +1039,49 @@ getMessagesInGroupChat
5951039 -> StaticPQ [Message]
5961040getMessagesInGroupChat selfId gId mId lim dir = do
5971041 _ <- getGroupChatForUser selfId gId
598− resp <- runQueryParams (getGroupMessagesQ lim $ directionToOperator dir) (gId, mId) >>= getRows
599− (return . sequence . map restoreMessage $ resp) >>= fromMaybe500
1042+ resp <- runQueryParams (getGroupMessagesQ lim dir) (gId, mId) >>= getRows
1043+ return . catMaybes . map restoreMessage $ resp
6001044
6011045sendMessage :: UserId -> MessageCreation -> StaticPQ MessageId
602−sendMessage selfId (MessageCreation (Just gId) uId@Nothing body) = do
1046+sendMessage selfId (MessageCreation (Just gId) uId@Nothing (Jsonb body)) = do
6031047 _ <- getGroupChatForUser selfId gId
604− (Only mId) <- manipulateParams createMessageQ (selfId, Just gId, uId, body)
1048+ (Only mId) <- manipulateParams createMessageQ (selfId, Just gId, uId, Jsonb $ stripPostElement body)
6051049 >>= firstRow >>= fromMaybe500
6061050 return mId
607−sendMessage selfId (MessageCreation gId@Nothing (Just uId) body) = do
608− (Only mId) <- manipulateParams createMessageQ (selfId, gId, Just uId, body)
1051+sendMessage selfId (MessageCreation gId@Nothing (Just uId) (Jsonb body)) = do
1052+ verifyUsers [uId]
1053+ (Only mId) <- manipulateParams createMessageQ (selfId, gId, Just uId, Jsonb $ stripPostElement body)
6091054 >>= firstRow >>= fromMaybe500
6101055 return mId
6111056sendMessage _ _ = lift $ S.throwError S.err400
6121057
6131058createGroupChat :: UserId -> GroupChatCreation -> StaticPQ GroupChatId
6141059createGroupChat uId (GroupChatCreation (Jsonb chatUsers) name) = do
615− let chatUsers' = M.insert uId maxChatPermissions chatUsers
616− (Only chatId) <- manipulateParams createGroupChatQ (GroupChatCreation (Jsonb chatUsers') name)
617− >>= firstRow >>= fromMaybe500
618− -- TODO: Do somehting with this
619− _ <- sendMessage uId $ MessageCreation (Just chatId) Nothing (Jsonb $ Markdown "henlo")
620− return chatId
1060+ verifyUsers . M.keys $ chatUsers
1061+ commitedTransactionallyUpdate $ do
1062+ let chatUsers' = M.insert uId maxChatPermissions chatUsers
1063+ (Only chatId) <- manipulateParams createGroupChatQ (GroupChatCreation (Jsonb chatUsers') name)
1064+ >>= firstRow >>= fromMaybe500
1065+ _ <- manipulateParams createPhantomMessageQ (Only chatId)
1066+ return chatId
6211067
6221068updateGroupChat :: UserId -> GroupChat -> StaticPQ ()
623−updateGroupChat uId (GroupChat gId (Jsonb perms) name) = commitedTransactionallyUpdate $ do
624− (GroupChat _ (Jsonb perms') _) <- getGroupChatForUser uId gId
625− let userPerm = fromMaybe minChatPermissions $ M.lookup uId perms'
626− when (isAdmin userPerm) $
627− lift $ S.throwError $ S.err401
628− let newPerms = M.adjust (max userPerm) uId perms
629− void $ manipulateParams updateGroupChatQ (GroupChat gId (Jsonb newPerms) name)
1069+updateGroupChat uId (GroupChat gId (Jsonb perms) name) = do
1070+ verifyUsers . M.keys $ perms
1071+ commitedTransactionallyUpdate $ do
1072+ (GroupChat _ (Jsonb perms') _) <- getGroupChatForUser uId gId
1073+ let userPerm = fromMaybe minChatPermissions $ M.lookup uId perms'
1074+ unless (isAdmin userPerm) $ lift $ S.throwError $ S.err401
1075+ let newPerms = M.insert uId userPerm perms
1076+ void $ manipulateParams updateGroupChatQ (GroupChat gId (Jsonb newPerms) name)
6301077
6311078leaveGroupChat :: UserId -> GroupChatId -> StaticPQ ()
6321079leaveGroupChat uId gId = commitedTransactionallyUpdate $ do
6331080 gChat@(GroupChat _ (Jsonb perms) _) <- getGroupChatForUser uId gId
6341081 let newPerms = M.delete uId perms
635− void $ manipulateParams updateGroupChatQ gChat{groupChatUsers = Jsonb newPerms}
1082+ if M.null newPerms
1083+ then deleteGroupChat gId
1084+ else void $ manipulateParams updateGroupChatQ gChat{groupChatUsers = Jsonb newPerms}
6361085
6371086getGroupChatForUser :: UserId -> GroupChatId -> StaticPQ GroupChat
6381087getGroupChatForUser uId gId = do
@@ -641,13 +1090,158 @@ getGroupChatForUser uId gId = do
6411090 (Just c) -> return c
6421091 Nothing -> lift $ S.throwError S.err404
6431092
1093+groupChatIsValidForUser :: UserId -> GroupChatId -> StaticPQ Bool
1094+groupChatIsValidForUser uId gId = do
1095+ (chat :: Maybe GroupChat) <- runQueryParams getGroupChatForUserQ (show uId, gId) >>= firstRow
1096+ return $ isJust chat
1097+
1098+getFacultyFromQuery :: T.Text -> StaticPQ [Faculty]
1099+getFacultyFromQuery query =
1100+ runQueryParams getFacultySearchResultQ (Only . queryFromTextAbbr $ query) >>= getRows
1101+
1102+searchUsers :: T.Text -> StaticPQ [User Faculty]
1103+searchUsers query = do
1104+ unless (validateText query) $ lift $ throwError lengthExceeded
1105+ if (T.null . T.filter isAlpha $ query)
1106+ then return []
1107+ else map rowToUser <$> (runQueryParams searchUsersQ (Only . queryFromText $ query) >>= getRows)
1108+
1109+searchTags :: T.Text -> StaticPQ [Text]
1110+searchTags "" = return []
1111+searchTags query = map fromOnly <$>
1112+ (runQueryParams searchTagsQ
1113+ (Only . (<> ":*") . T.strip $ query) >>= getRows)
1114+
1115+createToken :: Token -> StaticPQ ()
1116+createToken token = void $ manipulateParams createTokenQ token
1117+
1118+killAllTokens :: UserId -> StaticPQ ()
1119+killAllTokens = void . manipulateParams killAllTokensQ . Only
1120+
1121+pruneAuth :: StaticPQ ()
1122+pruneAuth = do
1123+ _ <- commitedTransactionallyUpdate . manipulate $ pruneDeadTokensQ
1124+ _ <- commitedTransactionallyUpdate . manipulate $ pruneGhostUsersQ
1125+ return ()
1126+
1127+searchChannels :: UserId -> Text -> StaticPQ [NamedChannel UserId]
1128+searchChannels uId query = fmap (map removeUserFromChannel) $
1129+ runQueryParams searchChannelsForUserQ (uId, queryFromText query) >>= getRows
1130+
1131+-- NOTE: Not transactional
1132+validateToken :: Token -> StaticPQ ()
1133+validateToken token = do
1134+ void $ manipulateParams updateTokenQ token{tokenVerificationCode = Nothing, tokenActivationCode = Nothing}
1135+ void $ manipulateParams validateUserQ $ Only $ tokenUserId token
1136+
1137+invalidateToken :: UserId -> ByteString -> StaticPQ ()
1138+invalidateToken uId bs = void . manipulateParams deleteTokenQ $ (uId, bs)
1139+
1140+tryVerifyToken :: UserId -> ByteString -> Int -> StaticPQ (Maybe Token)
1141+tryVerifyToken uId tokenData code = commitedTransactionallyUpdate $ do
1142+ token' <- runQueryParams getTokenQ (uId, hash tokenData) >>= firstRow
1143+ case token' of
1144+ Nothing -> return Nothing
1145+ Just token -> do
1146+ if (tokenVerificationCode token == Just (hashVerificationCode code))
1147+ then validateToken token >> (return $ Just token)
1148+ else do
1149+ let triesLeft = tokenActivationTriesLeft token - 1
1150+ if triesLeft <= 0
1151+ then invalidateToken (tokenUserId token) (tokenValue token) >> return Nothing
1152+ else do
1153+ let newToken = token{tokenActivationTriesLeft = triesLeft}
1154+ _ <- manipulateParams updateTokenQ newToken
1155+ return Nothing
1156+
1157+tryActivateToken :: UserId -> ByteString -> StaticPQ (Maybe Token)
1158+tryActivateToken uId activationData = do
1159+ let hData = hash activationData
1160+ token' <- runQueryParams getTokenActivationQ (uId, hData) >>= firstRow
1161+ case token' of
1162+ Nothing -> return Nothing
1163+ Just token -> do
1164+ if (tokenActivationCode token == Just hData)
1165+ then commitedTransactionallyUpdate (validateToken token) >> (return $ Just token)
1166+ else return Nothing
1167+
1168+deactivateToken :: UserId -> ByteString -> StaticPQ (Maybe Text)
1169+deactivateToken uId dData = fmap fromOnly <$> (manipulateParams deactivateTokenQ (uId, hash dData) >>= firstRow)
1170+
1171+getVerifiedToken :: UserId -> ByteString -> StaticPQ (Maybe Token)
1172+getVerifiedToken uId tokenData = runQueryParams getVerifiedTokenQ (uId, hash tokenData) >>= firstRow
1173+
1174+updateUser :: (UserUpdateRow) -> StaticPQ (Maybe UserId)
1175+updateUser user = fmap fromOnly <$> (manipulateParams updateUserQ (normalizeUser user) >>= firstRow)
1176+
6441177-- MARK: Utils
6451178
1179+normalizeUser :: UserUpdateRow -> UserUpdateRow
1180+normalizeUser UserUpdateRow{..} = UserUpdateRow {
1181+ userUpdateRowId = userUpdateRowId,
1182+ userUpdateRowFirstName = T.strip $ userUpdateRowFirstName,
1183+ userUpdateRowMiddleName = T.strip $ userUpdateRowMiddleName,
1184+ userUpdateRowLastName = T.strip $ userUpdateRowLastName,
1185+ userUpdateRowUserFaculty = T.toLower . T.strip $ userUpdateRowUserFaculty
1186+}
1187+
1188+data UserRow = UserRow {
1189+ userRowId :: UserId,
1190+ userRowFirstName :: Text,
1191+ userRowMiddleName :: Text,
1192+ userRowLastName :: Text,
1193+ userRowFacultyUrl :: FacultyUrl,
1194+ userRowEmail :: Text,
1195+ userRowIsValidated :: Bool,
1196+ userRowFacultyName :: T.Text,
1197+ userRowFacultyURL :: FacultyUrl,
1198+ userRowFacultyPath :: T.Text,
1199+ userRowFacultyCampusName :: T.Text,
1200+ userRowFacultyCampusCode :: T.Text,
1201+ userRowFacultyTags :: V.Vector T.Text,
1202+ userRowFacultyAddress :: T.Text
1203+} deriving (GHC.Generic, SOP.Generic, SOP.HasDatatypeInfo)
1204+
1205+rowToUser :: UserRow -> User Faculty
1206+rowToUser UserRow{..} = User {
1207+ userId = userRowId,
1208+ firstName = userRowFirstName,
1209+ middleName = userRowMiddleName,
1210+ lastName = userRowLastName,
1211+ userFaculty = Faculty {
1212+ facultyName = userRowFacultyName,
1213+ facultyUrl = userRowFacultyURL,
1214+ facultyPath = userRowFacultyPath,
1215+ facultyCampusName = userRowFacultyCampusName,
1216+ facultyCampusCode = userRowFacultyCampusCode,
1217+ facultyTags = userRowFacultyTags,
1218+ facultyAddress = userRowFacultyAddress
1219+ },
1220+ userEmail = userRowEmail
1221+ }
1222+
1223+queryFromTextAbbr :: Text -> Text
1224+queryFromTextAbbr = T.unwords . L.intersperse "&" . map processTerm . filter (not . T.null) . T.split (not . isAlphaNum)
1225+ where
1226+ processTerm :: Text -> Text
1227+ processTerm t = "(" <> t <> ":*" <> (
1228+ if T.length t <= 5
1229+ then (" | (" <>) . (<> ")") . T.unwords . L.intersperse "<->" . map (<> ":*") . map T.singleton . T.unpack $ t
1230+ else mempty
1231+ ) <> ")"
1232+
1233+queryFromText :: Text -> Text
1234+queryFromText = T.unwords . L.intersperse "&" . map processTerm . filter (not . T.null) . T.split (not . isPrint)
1235+ where
1236+ processTerm :: Text -> Text
1237+ processTerm t = t <> ":*"
1238+
6461239directionToOperator
6471240 :: P.PaginationDirection
648− -> ConditionOp schema from grouping params nullity0 nullity1 (ty :: PGType)
649−directionToOperator P.BackPagination = (.<)
650−directionToOperator P.ForwardPagination = (.>)
1241+ -> (ConditionOp schema0 from0 grouping0 params0 nullity0 nullity1 (ty :: PGType),
1242+ (Expression schema2 from2 grouping2 params2 _ -> SortExpression schema2 from2 grouping2 params2))
1243+directionToOperator P.BackPagination = ((.<), Desc)
1244+directionToOperator P.ForwardPagination = ((.>), Asc)
6511245
6521246foldMaybe :: [Maybe a] -> Maybe [a]
6531247foldMaybe [] = Just []
@@ -672,7 +1266,7 @@ unsafeIntToExpr = UnsafeExpression . pack . show
6721266
6731267data ResponseWithUsers r = ResponseWithUsers {
6741268 response :: r,
675− users :: M.Map UserId User
1269+ users :: M.Map UserId (User Faculty)
6761270} deriving (Generic)
6771271
6781272instance (ToJSON r) => ToJSON (ResponseWithUsers r)
@@ -714,5 +1308,9 @@ liftMaybe Nothing _ = pure Nothing
7141308liftMaybe (Just a) f = f a
7151309
7161310fromMaybe500 :: Maybe a -> StaticPQ a
717−fromMaybe500 Nothing = lift $ S.throwError S.err404
718−fromMaybe500 (Just a) = return a
1311+fromMaybe500 Nothing = lift $ S.throwError S.err500
1312+fromMaybe500 (Just a) = return a
1313+
1314+fromMaybe404 :: Maybe a -> StaticPQ a
1315+fromMaybe404 Nothing = lift $ S.throwError S.err404
1316+fromMaybe404 (Just a) = return a