-
-
Notifications
You must be signed in to change notification settings - Fork 637
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(server): support notification (#1683)
* feat(server): support notification * add third-party notification center
- Loading branch information
Showing
13 changed files
with
315 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"InsufficientBalance": { | ||
"title": "Laf account is in arrears", | ||
"content": "Your account is in arrears, the app {appid} will stop running, please recharge your account promptly!" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"InsufficientBalance": { | ||
"title": "laf 账户欠费", | ||
"content": "你的账户已欠费,应用 {appid} 将停止运行,请及时充值" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"InsufficientBalance": { | ||
"title": "laf 账户欠费", | ||
"content": "你的账户已欠费,应用 {appid} 将停止运行,请及时充值" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { ApiProperty } from '@nestjs/swagger' | ||
import { ObjectId } from 'mongodb' | ||
import { NotificationType } from '../notification-type' | ||
|
||
export enum NotificationState { | ||
Pending = 'Pending', | ||
Done = 'Done', | ||
} | ||
|
||
export class Notification { | ||
@ApiProperty({ type: String }) | ||
_id?: ObjectId | ||
|
||
@ApiProperty({ enum: NotificationType }) | ||
type: NotificationType | ||
|
||
@ApiProperty() | ||
title: string | ||
|
||
@ApiProperty() | ||
content: string | ||
|
||
@ApiProperty({ enum: NotificationState }) | ||
state: NotificationState | ||
|
||
@ApiProperty({ type: String }) | ||
target: ObjectId | ||
|
||
lockedAt: Date | ||
|
||
@ApiProperty() | ||
createdAt: Date | ||
|
||
@ApiProperty() | ||
updatedAt: Date | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import { Cron, CronExpression } from '@nestjs/schedule' | ||
import { SystemDatabase } from 'src/system-database' | ||
import { Notification, NotificationState } from './entities/notification' | ||
import { HttpService } from '@nestjs/axios' | ||
import { Injectable, Logger } from '@nestjs/common' | ||
import { ServerConfig } from 'src/constants' | ||
import { UserService } from 'src/user/user.service' | ||
|
||
@Injectable() | ||
export class NotificationTaskService { | ||
private lockTimeout = 15 // in seconds | ||
private readonly logger = new Logger(NotificationTaskService.name) | ||
|
||
constructor( | ||
private readonly httpService: HttpService, | ||
private readonly userService: UserService, | ||
) {} | ||
|
||
@Cron(CronExpression.EVERY_30_SECONDS) | ||
tick() { | ||
if (ServerConfig.DISABLE_NOTIFICATION_TASK) return | ||
|
||
this.handlePendingNotifications() | ||
} | ||
|
||
async handlePendingNotifications() { | ||
const db = SystemDatabase.db | ||
|
||
const res = await db | ||
.collection<Notification>('Notification') | ||
.findOneAndUpdate( | ||
{ | ||
state: NotificationState.Pending, | ||
lockedAt: { | ||
$lt: new Date(Date.now() - this.lockTimeout * 1000), | ||
}, | ||
}, | ||
{ | ||
$set: { | ||
lockedAt: new Date(), | ||
}, | ||
}, | ||
) | ||
|
||
if (!res.value) return | ||
const notification = res.value | ||
|
||
const ok = await this.sendNotification(notification) | ||
if (!ok) return | ||
|
||
await db.collection<Notification>('Notification').updateOne( | ||
{ | ||
_id: notification._id, | ||
}, | ||
{ | ||
$set: { | ||
state: NotificationState.Done, | ||
updatedAt: new Date(), | ||
}, | ||
}, | ||
) | ||
} | ||
|
||
async sendNotification(data: Notification) { | ||
if (!ServerConfig.NOTIFICATION_CENTER_URL) { | ||
return true | ||
} | ||
|
||
const user = await this.userService.findOneById(data.target) | ||
if (!user) { | ||
this.logger.error(`failed to send notification ${data._id}, no user`) | ||
return true | ||
} | ||
|
||
if (!user.email) { | ||
this.logger.log(`skip to send notification ${data._id} due to no channel`) | ||
return true | ||
} | ||
|
||
const to = [user.email].join('|') | ||
|
||
const res = await this.httpService.axiosRef.post( | ||
ServerConfig.NOTIFICATION_CENTER_URL, | ||
{ | ||
title: data.title, | ||
description: data.content, | ||
token: ServerConfig.NOTIFICATION_CENTER_TOKEN, | ||
channel: 'notification', | ||
async: true, | ||
to: to, | ||
}, | ||
) | ||
if (!res.data.success) { | ||
this.logger.error(`failed to send notification ${data._id}`, res.data) | ||
return false | ||
} | ||
return true | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export enum NotificationType { | ||
InsufficientBalance = 'InsufficientBalance', | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { Controller, Get, Query, UseGuards } from '@nestjs/common' | ||
import { ApiResponsePagination, ResponseUtil } from 'src/utils/response' | ||
import { NotificationService } from './notification.service' | ||
import { InjectUser } from 'src/utils/decorator' | ||
import { User } from 'src/user/entities/user' | ||
import { JwtAuthGuard } from 'src/authentication/jwt.auth.guard' | ||
import { ApiOperation } from '@nestjs/swagger' | ||
import { Notification } from './entities/notification' | ||
|
||
@Controller('notification') | ||
export class NotificationController { | ||
constructor(private readonly notificationService: NotificationService) {} | ||
|
||
@ApiOperation({ summary: 'Get notification list' }) | ||
@ApiResponsePagination(Notification) | ||
@UseGuards(JwtAuthGuard) | ||
@Get('list') | ||
async findAll( | ||
@Query('page') page: number, | ||
@Query('pageSize') pageSize: number, | ||
@InjectUser() user: User, | ||
) { | ||
page = page ? Number(page) : 1 | ||
pageSize = pageSize ? Number(pageSize) : 10 | ||
|
||
const res = await this.notificationService.findAll(user, page, pageSize) | ||
|
||
return ResponseUtil.ok(res) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { Global, Module } from '@nestjs/common' | ||
import { NotificationService } from './notification.service' | ||
import { NotificationTaskService } from './notification-task.service' | ||
import { NotificationController } from './notification.controller' | ||
import { HttpModule } from '@nestjs/axios' | ||
import { UserModule } from 'src/user/user.module' | ||
|
||
@Global() | ||
@Module({ | ||
providers: [NotificationService, NotificationTaskService], | ||
exports: [NotificationService], | ||
controllers: [NotificationController], | ||
imports: [HttpModule, UserModule], | ||
}) | ||
export class NotificationModule {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import { Injectable } from '@nestjs/common' | ||
import { SystemDatabase } from 'src/system-database' | ||
import type { User } from 'src/user/entities/user' | ||
import { Notification, NotificationState } from './entities/notification' | ||
import { TASK_LOCK_INIT_TIME } from 'src/constants' | ||
import { I18nService } from 'nestjs-i18n' | ||
import { NotificationType } from './notification-type' | ||
import { ObjectId } from 'mongodb' | ||
|
||
@Injectable() | ||
export class NotificationService { | ||
constructor(private readonly i18n: I18nService) {} | ||
|
||
async notify(params: { | ||
type: NotificationType | ||
payload?: object | ||
uid?: ObjectId | ||
}) { | ||
const { type, payload, uid } = params | ||
|
||
const db = SystemDatabase.db | ||
await db.collection<Notification>('Notification').insertOne({ | ||
title: this.getNotificationTitle(type, payload), | ||
content: this.genNotificationContent(type, payload), | ||
type, | ||
target: uid, | ||
state: NotificationState.Pending, | ||
lockedAt: TASK_LOCK_INIT_TIME, | ||
createdAt: new Date(), | ||
updatedAt: new Date(), | ||
}) | ||
} | ||
|
||
getNotificationTitle(type: NotificationType, payload?: object) { | ||
const title = this.i18n.t(`notification.${type}.title`, { | ||
args: payload, | ||
}) | ||
return title | ||
} | ||
|
||
genNotificationContent(type: NotificationType, payload?: object) { | ||
const content = this.i18n.t(`notification.${type}.content`, { | ||
args: payload, | ||
}) | ||
return content | ||
} | ||
|
||
async findAll(user: User, page: number, pageSize: number) { | ||
const db = SystemDatabase.db | ||
const list = db | ||
.collection<Notification>('Notification') | ||
.find( | ||
{ target: user._id }, | ||
{ | ||
skip: (page - 1) * pageSize, | ||
limit: pageSize, | ||
sort: { createdAt: -1 }, | ||
}, | ||
) | ||
.toArray() | ||
|
||
const total = await db | ||
.collection<Notification>('Notification') | ||
.countDocuments({ target: user._id }) | ||
|
||
return { | ||
list, | ||
total, | ||
page, | ||
pageSize, | ||
} | ||
} | ||
} |