Skip to content

Commit 3354491

Browse files
committed
Hydrate events in transactions
1 parent 3e65a9b commit 3354491

7 files changed

Lines changed: 32 additions & 17 deletions

File tree

src/Share/BackgroundJobs/Webhooks/Worker.hs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import Share.IDs qualified as IDs
3434
import Share.JWT (JWTParam (..))
3535
import Share.JWT qualified as JWT
3636
import Share.Metrics qualified as Metrics
37+
import Share.Notifications.Ops qualified as NotOps
3738
import Share.Notifications.Queries qualified as NQ
3839
import Share.Notifications.Types
3940
import Share.Notifications.Webhooks.Secrets (WebhookConfig (..), WebhookSecretError)
@@ -308,7 +309,7 @@ buildWebhookRequest webhookId uri event defaultPayload = do
308309
actorAvatarUrl = event.eventActor ^. DisplayInfo.avatarUrl_
309310
actorLink <- Links.userProfilePage (event.eventActor ^. DisplayInfo.handle_)
310311
let mainLink = event.eventData.hydratedEventLink
311-
messageContent :: MessageContent provider <- case event.eventData.payload of
312+
messageContent :: MessageContent provider <- case event.eventData.hydratedEventPayload of
312313
HydratedProjectBranchUpdatedPayload payload -> do
313314
let pbShorthand = (projectBranchShortHandFromParts payload.projectInfo.projectShortHand payload.branchInfo.branchShortHand)
314315
title = "Branch " <> IDs.toText pbShorthand <> " was just updated."
@@ -369,7 +370,8 @@ attemptWebhookSend ::
369370
PG.Transaction e (Maybe WebhookSendFailure)
370371
attemptWebhookSend _authZReceipt tryWebhookIO eventId webhookId = do
371372
event <- NQ.expectEvent eventId
372-
hydratedEvent <- forOf eventData_ event NQ.hydrateEventPayload
373+
hydratedEventPayload <- forOf eventData_ event NQ.hydrateEventPayload
374+
hydratedEvent <- for hydratedEventPayload NotOps.hydrateEvent
373375
populatedEvent <- hydratedEvent & DisplayInfoQ.unifiedDisplayInfoForUserOf eventUserInfo_
374376
PG.transactionUnsafeIO (tryWebhookIO populatedEvent webhookId) >>= \case
375377
Just err -> do

src/Share/Notifications/API.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import Data.Text qualified as Text
3939
import Data.Time (UTCTime)
4040
import Servant
4141
import Share.IDs
42-
import Share.Notifications.Types (DeliveryMethodId, HydratedEventPayload, NotificationDeliveryMethod, NotificationHubEntry, NotificationStatus, NotificationSubscription, NotificationTopic, SubscriptionFilter)
42+
import Share.Notifications.Types (DeliveryMethodId, HydratedEvent, NotificationDeliveryMethod, NotificationHubEntry, NotificationStatus, NotificationSubscription, NotificationTopic, SubscriptionFilter)
4343
import Share.OAuth.Session (AuthenticatedUserId)
4444
import Share.Prelude
4545
import Share.Utils.URI (URIParam)
@@ -213,7 +213,7 @@ type GetHubEntriesEndpoint =
213213
:> Get '[JSON] GetHubEntriesResponse
214214

215215
data GetHubEntriesResponse = GetHubEntriesResponse
216-
{ notifications :: [NotificationHubEntry UnifiedDisplayInfo HydratedEventPayload]
216+
{ notifications :: [NotificationHubEntry UnifiedDisplayInfo HydratedEvent]
217217
}
218218

219219
instance ToJSON GetHubEntriesResponse where

src/Share/Notifications/Impl.hs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
module Share.Notifications.Impl (server) where
22

3+
import Control.Lens (forOf, traversed)
34
import Data.Time
45
import Servant
56
import Servant.Server.Generic (AsServerT)
@@ -80,7 +81,9 @@ getHubEntriesEndpoint :: UserHandle -> UserId -> Maybe Int -> Maybe UTCTime -> M
8081
getHubEntriesEndpoint userHandle callerUserId limit afterTime mayStatusFilter = do
8182
User {user_id = notificationUserId} <- UserQ.expectUserByHandle userHandle
8283
_authZReceipt <- AuthZ.permissionGuard $ AuthZ.checkNotificationsGet callerUserId notificationUserId
83-
notifications <- PG.runTransaction $ NotificationQ.listNotificationHubEntries notificationUserId limit afterTime (API.getStatusFilter <$> mayStatusFilter)
84+
notifications <- PG.runTransaction do
85+
notifs <- NotificationQ.listNotificationHubEntryPayloads notificationUserId limit afterTime (API.getStatusFilter <$> mayStatusFilter)
86+
forOf (traversed . traversed) notifs NotifOps.hydrateEvent
8487
pure $ API.GetHubEntriesResponse {notifications}
8588

8689
updateHubEntriesEndpoint :: UserHandle -> UserId -> API.UpdateHubEntriesRequest -> WebApp ()

src/Share/Notifications/Ops.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ deleteWebhookDeliveryMethod notificationUser webhookDeliveryMethodId = do
7878
PG.runTransaction $ do
7979
NotifQ.deleteWebhookDeliveryMethod notificationUser webhookDeliveryMethodId
8080

81-
hydrateEvent :: HydratedEventPayload -> WebApp HydratedEvent
81+
hydrateEvent :: HydratedEventPayload -> PG.Transaction e HydratedEvent
8282
hydrateEvent hydratedEventPayload = do
8383
hydratedEventLink <- Links.notificationLink hydratedEventPayload
8484
pure $ HydratedEvent {hydratedEventPayload, hydratedEventLink}

src/Share/Notifications/Queries.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ listNotificationHubEntryPayloads notificationUserId mayLimit afterTime statusFil
7070
ORDER BY hub.created_at DESC
7171
LIMIT #{limit}
7272
|]
73-
hydratedPayloads <- PG.pipelined $ forOf (traversed . traversed) dbNotifications hydrateEventData
74-
hydrated & DisplayInfoQ.unifiedDisplayInfoForUserOf (traversed . hubEntryUserInfo_)
73+
hydratedPayloads <- PG.pipelined $ forOf (traversed . traversed) dbNotifications hydrateEventPayload
74+
hydratedPayloads & DisplayInfoQ.unifiedDisplayInfoForUserOf (traversed . hubEntryUserInfo_)
7575

7676
updateNotificationHubEntries :: (QueryA m) => NESet NotificationHubEntryId -> NotificationStatus -> m ()
7777
updateNotificationHubEntries hubEntryIds status = do

src/Share/Notifications/Types.hs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,16 @@ instance ToJSON HydratedEvent where
548548
"kind" .= kind
549549
]
550550

551+
instance FromJSON HydratedEvent where
552+
parseJSON = Aeson.withObject "HydratedEvent" \o -> do
553+
kind <- o .: "kind"
554+
hydratedEventLink <- o .: "link"
555+
hydratedEventPayload <- case kind of
556+
"projectBranchUpdated" -> HydratedProjectBranchUpdatedPayload <$> o .: "payload"
557+
"projectContributionCreated" -> HydratedProjectContributionCreatedPayload <$> o .: "payload"
558+
_ -> fail $ "Unknown event kind: " <> Text.unpack kind
559+
pure HydratedEvent {hydratedEventPayload, hydratedEventLink}
560+
551561
data HydratedEventPayload
552562
= HydratedProjectBranchUpdatedPayload ProjectBranchUpdatedPayload
553563
| HydratedProjectContributionCreatedPayload ProjectContributionCreatedPayload

src/Share/Web/UI/Links.hs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ homePage mayEvent = do
9393
LogOut -> Map.singleton "event" "log-out"
9494

9595
-- E.g. https://share.unison-lang.org/@unison/base/code/@ceedubs/each-first/latest
96-
projectBranchBrowseLink :: ProjectBranchShortHand -> AppM reqCtx URI
96+
projectBranchBrowseLink :: (MonadReader (Env.Env ctx) m) => ProjectBranchShortHand -> m URI
9797
projectBranchBrowseLink (ProjectBranchShortHand {userHandle, projectSlug, contributorHandle, branchName}) = do
9898
let branchPath = case contributorHandle of
9999
Just contributor -> [IDs.toText contributor, IDs.toText branchName]
@@ -102,13 +102,13 @@ projectBranchBrowseLink (ProjectBranchShortHand {userHandle, projectSlug, contri
102102
shareUIPath path
103103

104104
-- E.g. https://share.unison-lang.org/@unison/base/contributions/100
105-
contributionLink :: ProjectShortHand -> ContributionNumber -> AppM reqCtx URI
105+
contributionLink :: (MonadReader (Env.Env ctx) m) => ProjectShortHand -> ContributionNumber -> m URI
106106
contributionLink (ProjectShortHand {userHandle, projectSlug}) contributionNumber = do
107107
let path = [IDs.toText (PrefixedID @"@" userHandle), IDs.toText projectSlug, "contributions", IDs.toText contributionNumber]
108108
shareUIPath path
109109

110110
-- | Where the user should go when clicking on a notification
111-
notificationLink :: HydratedEventPayload -> AppM reqCtx URI
111+
notificationLink :: (MonadReader (Env.Env ctx) m) => HydratedEventPayload -> m URI
112112
notificationLink = \case
113113
HydratedProjectBranchUpdatedPayload payload ->
114114
projectBranchBrowseLink payload.branchInfo.projectBranchShortHand
@@ -123,19 +123,19 @@ unisonLogoImage =
123123
----------- Utilities -----------
124124

125125
-- | Construct a full URI to a path within share, with provided query params.
126-
sharePathQ :: [Text] -> Map Text Text -> AppM reqCtx URI
126+
sharePathQ :: (MonadReader (Env.Env ctx) m) => [Text] -> Map Text Text -> m URI
127127
sharePathQ pathSegments queryParams = do
128128
uri <- asks Env.apiOrigin
129129
pure . setPathAndQueryParams pathSegments queryParams $ uri
130130

131131
-- | Construct a full URI to a path within share.
132-
sharePath :: [Text] -> AppM reqCtx URI
132+
sharePath :: (MonadReader (Env.Env ctx) m) => [Text] -> m URI
133133
sharePath path = sharePathQ path mempty
134134

135135
-- | Check if a URI is a the Share UI, the Cloud UI, the main website, or the
136136
-- Cloud website. This is useful for preventing attackers from generating
137137
-- arbitrary redirections in things like login redirects.
138-
isTrustedURI :: URI -> AppM reqCtx Bool
138+
isTrustedURI :: (MonadReader (Env.Env ctx) m) => URI -> m Bool
139139
isTrustedURI uri = do
140140
shareUiURI <- asks Env.shareUiOrigin
141141
websiteURI <- asks Env.websiteOrigin
@@ -146,12 +146,12 @@ isTrustedURI uri = do
146146
pure $ any (\uri -> uriAuthority uri == requestedAuthority) trustedURIs
147147

148148
-- | Construct a full URI to a path within the share UI, with the provided query params.
149-
shareUIPathQ :: [Text] -> Map Text Text -> AppM reqCtx URI
149+
shareUIPathQ :: (MonadReader (Env.Env ctx) m) => [Text] -> Map Text Text -> m URI
150150
shareUIPathQ pathSegments queryParams = do
151151
shareUiURI <- asks Env.shareUiOrigin
152152
pure . setPathAndQueryParams pathSegments queryParams $ shareUiURI
153153

154-
shareUIPath :: [Text] -> AppM reqCtx URI
154+
shareUIPath :: (MonadReader (Env.Env ctx) m) => [Text] -> m URI
155155
shareUIPath pathSegments = shareUIPathQ pathSegments mempty
156156

157157
-- | Various Error types that the Share UI knows how to interpret
@@ -172,7 +172,7 @@ shareUIErrorToUIText e =
172172
AccountCreationInvalidHandle {} ->
173173
"AccountCreationInvalidHandle"
174174

175-
errorRedirectLink :: ShareUIError -> AppM reqCtx URI
175+
errorRedirectLink :: (MonadReader (Env.Env ctx) m) => ShareUIError -> m URI
176176
errorRedirectLink shareUIError = shareUIPathQ ["error"] (Map.fromList [("appError", shareUIErrorToUIText shareUIError)])
177177

178178
-- | Redirect the user to the Share UI and show an error message.

0 commit comments

Comments
 (0)