Skip to content

Commit ed866e6

Browse files
alsakhaevLisofffa
andauthored
feat: show outgoing notifications (DAP-4770, DAP-4771, DAP-4772) (#35)
Co-authored-by: Lisofffa <lisofffa@gmail.com>
1 parent ef30c0a commit ed866e6

20 files changed

+484
-245
lines changed

libs/backend/src/config.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ export const NearConfigs: { [networkId: string]: NearConfig } = {
3333
ear: 'bos.dapplets.near/widget/ContextActionsGroup',
3434
},
3535
timeReference: {
36-
timestamp: 1731150806896,
37-
height: 132297337,
38-
avgBlockTime: 1122, // https://nearblocks.io/
36+
timestamp: 1733844432230,
37+
height: 134671239,
38+
avgBlockTime: 1091, // https://nearblocks.io/
3939
},
4040
},
4141
testnet: {
@@ -50,8 +50,8 @@ export const NearConfigs: { [networkId: string]: NearConfig } = {
5050
ear: 'bos.dapplets.testnet/widget/ContextActionsGroup',
5151
},
5252
timeReference: {
53-
timestamp: 1731150806896,
54-
height: 179057513,
53+
timestamp: 1733844492601,
54+
height: 181770025,
5555
avgBlockTime: 1000, // https://testnet.nearblocks.io/
5656
},
5757
},

libs/backend/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ export {
3535
PullRequestResult,
3636
PullRequestStatus,
3737
} from './services/notification/types/pull-request'
38+
export { PullRequestAcceptedPayload } from './services/notification/types/pull-request-accepted'
39+
export { PullRequestRejectedPayload } from './services/notification/types/pull-request-rejected'
3840
export { RegularPayload } from './services/notification/types/regular'
3941

4042
// ToDo: replace with DTO

libs/backend/src/services/base/base.repository.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,9 +270,12 @@ export class BaseRepository<T extends Base> implements IRepository<T> {
270270
throw new Error('User is not logged in')
271271
}
272272

273+
const id = [authorId, this._entityKey, localId].join(KeyDelimiter)
274+
273275
// @ts-ignore
274276
const entity: T = this.EntityType.create({
275277
...item,
278+
id,
276279
localId,
277280
authorId,
278281
source: EntitySourceType.Origin,

libs/backend/src/services/mutation/mutation.service.ts

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,14 +257,34 @@ export class MutationService {
257257
Promise.all([
258258
this._applyChangesToOrigin(sourceMutation, tx),
259259
this.notificationService.acceptNotification(notificationId, tx),
260+
this._notifyAboutAcceptedPullRequest(sourceMutationId, targetMutationId, tx),
260261
])
261262
)
262263

263264
return freshNotification
264265
}
265266

266267
async rejectPullRequest(notificationId: EntityId): Promise<NotificationDto> {
267-
return this.notificationService.rejectNotification(notificationId)
268+
const notification = await this.notificationService.getNotification(notificationId)
269+
270+
if (!notification) {
271+
throw new Error('Notification not found')
272+
}
273+
274+
if (notification.type !== NotificationType.PullRequest) {
275+
throw new Error('Notification is not a pull request')
276+
}
277+
278+
const { sourceMutationId, targetMutationId } = notification.payload as PullRequestPayload
279+
280+
const [freshNotification] = await this.unitOfWorkService.runInTransaction((tx) =>
281+
Promise.all([
282+
this.notificationService.rejectNotification(notificationId, tx),
283+
this._notifyAboutRejectedPullRequest(sourceMutationId, targetMutationId, tx),
284+
])
285+
)
286+
287+
return freshNotification
268288
}
269289

270290
async removeMutationFromRecents(mutationId: MutationId): Promise<void> {
@@ -380,4 +400,44 @@ export class MutationService {
380400

381401
return mutation
382402
}
403+
404+
private async _notifyAboutAcceptedPullRequest(
405+
sourceMutationId: string,
406+
targetMutationId: string,
407+
tx?: Transaction
408+
) {
409+
const [sourceAuthorId] = sourceMutationId.split('/')
410+
411+
const notification: NotificationCreateDto = {
412+
source: EntitySourceType.Origin,
413+
type: NotificationType.PullRequestAccepted,
414+
recipients: [sourceAuthorId],
415+
payload: {
416+
sourceMutationId: sourceMutationId,
417+
targetMutationId: targetMutationId,
418+
},
419+
}
420+
421+
await this.notificationService.createNotification(notification, tx)
422+
}
423+
424+
private async _notifyAboutRejectedPullRequest(
425+
sourceMutationId: string,
426+
targetMutationId: string,
427+
tx?: Transaction
428+
) {
429+
const [sourceAuthorId] = sourceMutationId.split('/')
430+
431+
const notification: NotificationCreateDto = {
432+
source: EntitySourceType.Origin,
433+
type: NotificationType.PullRequestRejected,
434+
recipients: [sourceAuthorId],
435+
payload: {
436+
sourceMutationId: sourceMutationId,
437+
targetMutationId: targetMutationId,
438+
},
439+
}
440+
441+
await this.notificationService.createNotification(notification, tx)
442+
}
383443
}

libs/backend/src/services/notification/dtos/notification-create.dto.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,15 @@ import { BaseCreateDto } from '../../base/base-create.dto'
22
import { NotificationType } from '../notification.entity'
33
import { RegularPayload } from '../types/regular'
44
import { PullRequestPayload } from '../types/pull-request'
5+
import { PullRequestAcceptedPayload } from '../types/pull-request-accepted'
6+
import { PullRequestRejectedPayload } from '../types/pull-request-rejected'
57

68
export type NotificationCreateDto = BaseCreateDto & {
79
type: NotificationType
8-
payload: RegularPayload | PullRequestPayload
10+
payload:
11+
| RegularPayload
12+
| PullRequestPayload
13+
| PullRequestAcceptedPayload
14+
| PullRequestRejectedPayload
915
recipients: string[]
1016
}

libs/backend/src/services/notification/dtos/notification.dto.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,18 @@ import { BaseDto } from '../../base/base.dto'
22
import { NotificationType } from '../notification.entity'
33
import { NotificationStatus } from '../resolution.entity'
44
import { PullRequestPayload, PullRequestResult } from '../types/pull-request'
5+
import { PullRequestAcceptedPayload } from '../types/pull-request-accepted'
6+
import { PullRequestRejectedPayload } from '../types/pull-request-rejected'
57
import { RegularPayload } from '../types/regular'
68

79
export type NotificationDto = BaseDto & {
810
type: NotificationType
9-
payload: RegularPayload | PullRequestPayload | null
11+
payload:
12+
| RegularPayload
13+
| PullRequestPayload
14+
| PullRequestAcceptedPayload
15+
| PullRequestRejectedPayload
16+
| null
1017
recipients: string[]
1118
status: NotificationStatus
1219
result: PullRequestResult | null

libs/backend/src/services/notification/notification.entity.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@ import { Entity } from '../base/decorators/entity'
33
import { Column, ColumnType } from '../base/decorators/column'
44
import { RegularPayload } from './types/regular'
55
import { PullRequestPayload } from './types/pull-request'
6+
import { PullRequestAcceptedPayload } from './types/pull-request-accepted'
7+
import { PullRequestRejectedPayload } from './types/pull-request-rejected'
68

79
export enum NotificationType {
810
Regular = 'regular',
911
PullRequest = 'pull-request',
12+
PullRequestAccepted = 'pull-request-accepted',
13+
PullRequestRejected = 'pull-request-rejected',
1014
Unknown = 'unknown', // ToDo: workaround to construct Notification
1115
}
1216

@@ -16,7 +20,12 @@ export class Notification extends Base {
1620
type: NotificationType = NotificationType.Unknown
1721

1822
@Column({ type: ColumnType.Json })
19-
payload: RegularPayload | PullRequestPayload | null = null
23+
payload:
24+
| RegularPayload
25+
| PullRequestPayload
26+
| PullRequestAcceptedPayload
27+
| PullRequestRejectedPayload
28+
| null = null
2029

2130
@Column({ type: ColumnType.Set })
2231
recipients: string[] = []

libs/backend/src/services/notification/notification.service.ts

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { EntityId } from '../base/base.entity'
1+
import { EntityId, EntitySourceType } from '../base/base.entity'
22
import { NearSigner } from '../near-signer/near-signer.service'
33
import { Transaction } from '../unit-of-work/transaction'
44
import { UserLinkService } from '../user-link/user-link.service'
@@ -11,6 +11,8 @@ import { UnitOfWorkService } from '../unit-of-work/unit-of-work.service'
1111
import { generateGuid } from '../../common/generate-guid'
1212
import { IRepository } from '../base/repository.interface'
1313

14+
const KeyDelimiter = '/'
15+
1416
export class NotificationService {
1517
constructor(
1618
private notificationRepository: IRepository<Notification>,
@@ -38,20 +40,32 @@ export class NotificationService {
3840

3941
// ToDo: move DTOs to controllers?
4042

41-
async getNotificationsByRecipient(recipientId: string): Promise<NotificationDto[]> {
42-
if (!recipientId) return []
43+
async getMyNotifications(accountId: string): Promise<NotificationDto[]> {
44+
if (!accountId) return []
45+
46+
const incomingNotifications = await this.notificationRepository.getItemsByIndex({
47+
recipients: [accountId],
48+
})
4349

44-
const notifications = await this.notificationRepository.getItemsByIndex({
45-
recipients: [recipientId],
50+
const outgoingNotifications = await this.notificationRepository.getItems({
51+
authorId: accountId,
4652
})
4753

54+
const allNotifications = incomingNotifications
55+
56+
for (const outgoingNotification of outgoingNotifications) {
57+
if (!allNotifications.some((n) => n.id === outgoingNotification.id)) {
58+
allNotifications.push(outgoingNotification)
59+
}
60+
}
61+
4862
const resolutions = await Promise.all(
49-
notifications.map((notification) =>
50-
this._getResolutionForNotification(notification.id, notification.type, recipientId)
63+
allNotifications.map((notification) =>
64+
this._getResolutionForNotification(notification.id, notification.type, accountId)
5165
)
5266
)
5367

54-
return notifications.map((notification, i) => {
68+
return allNotifications.map((notification, i) => {
5569
const resolution = resolutions[i]
5670
return this._toDto(notification, resolution)
5771
})
@@ -72,7 +86,7 @@ export class NotificationService {
7286
}
7387

7488
async viewAllNotifcations(recipientId: string): Promise<NotificationDto[]> {
75-
const notifications = await this.getNotificationsByRecipient(recipientId)
89+
const notifications = await this.getMyNotifications(recipientId)
7690

7791
const notificationsToBeViewed = notifications.filter(
7892
(notification) => notification.status === NotificationStatus.New
@@ -184,9 +198,10 @@ export class NotificationService {
184198
throw new Error('Not logged in')
185199
}
186200

187-
if (!notification.recipients.includes(accountId)) {
188-
throw new Error('You are not a recipient of this notification')
189-
}
201+
//todo: commented, but not resolved
202+
// if (!notification.recipients.includes(accountId)) {
203+
// throw new Error('You are not a recipient of this notification')
204+
// }
190205

191206
const resolution = await this._getResolutionForNotification(
192207
notification.id,
@@ -205,10 +220,11 @@ export class NotificationService {
205220
private async _getResolutionForNotification(
206221
notificationId: EntityId,
207222
notificationType: NotificationType,
208-
accountId: string
223+
recipientId: string
209224
): Promise<Resolution> {
225+
const [senderId] = notificationId.split(KeyDelimiter)
210226
const hash = UserLinkService._hashString(notificationId)
211-
const resolutionId = `${accountId}/resolution/${hash}`
227+
const resolutionId = `${recipientId}/resolution/${hash}`
212228

213229
const resolution = await this.resolutionRepository.getItem({ id: resolutionId })
214230

@@ -221,15 +237,19 @@ export class NotificationService {
221237
case NotificationType.PullRequest:
222238
return Resolution.create({
223239
id: resolutionId,
224-
status: NotificationStatus.New,
240+
source: EntitySourceType.Origin,
241+
// ToDo: outgoing notifications are viewed by default
242+
status: recipientId === senderId ? NotificationStatus.Viewed : NotificationStatus.New,
225243
result: { status: PullRequestStatus.Open },
226244
})
227245

228246
case NotificationType.Regular:
229247
default:
230248
return Resolution.create({
231249
id: resolutionId,
232-
status: NotificationStatus.New,
250+
source: EntitySourceType.Origin,
251+
// ToDo: outgoing notifications are viewed by default
252+
status: recipientId === senderId ? NotificationStatus.Viewed : NotificationStatus.New,
233253
})
234254
}
235255
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export type PullRequestAcceptedPayload = {
2+
sourceMutationId: string // nikter.near/mutation/Sandbox
3+
targetMutationId: string // dapplets.near/mutation/Sandbox
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export type PullRequestRejectedPayload = {
2+
sourceMutationId: string // nikter.near/mutation/Sandbox
3+
targetMutationId: string // dapplets.near/mutation/Sandbox
4+
}

0 commit comments

Comments
 (0)