feat: add @nocobase/plugin-notifications
This commit is contained in:
parent
23bdada69c
commit
4ab7c71f49
@ -56,6 +56,7 @@
|
|||||||
"koa-bodyparser": "^4.3.0",
|
"koa-bodyparser": "^4.3.0",
|
||||||
"lerna": "^3.22.0",
|
"lerna": "^3.22.0",
|
||||||
"mockjs": "^1.1.0",
|
"mockjs": "^1.1.0",
|
||||||
|
"nodemailer-mock": "^1.5.11",
|
||||||
"nodemon": "^2.0.12",
|
"nodemon": "^2.0.12",
|
||||||
"pg": "^8.6.0",
|
"pg": "^8.6.0",
|
||||||
"pg-hstore": "^2.3.3",
|
"pg-hstore": "^2.3.3",
|
||||||
|
7
packages/plugin-notifications/.npmignore
Normal file
7
packages/plugin-notifications/.npmignore
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
node_modules
|
||||||
|
*.log
|
||||||
|
docs
|
||||||
|
__tests__
|
||||||
|
tsconfig.json
|
||||||
|
src
|
||||||
|
.fatherrc.ts
|
11
packages/plugin-notifications/package.json
Normal file
11
packages/plugin-notifications/package.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "@nocobase/plugin-notifications",
|
||||||
|
"version": "0.5.0-alpha.15",
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@nocobase/server": "^0.5.0-alpha.15",
|
||||||
|
"nodemailer": "^6.6.1"
|
||||||
|
},
|
||||||
|
"gitHead": "f0b335ac30f29f25c95d7d137655fa64d8d67f1e"
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
import Database, { Model, ModelCtor } from '@nocobase/database';
|
||||||
|
import { Notification, NotificationService } from '../models';
|
||||||
|
import nodemailerMock from 'nodemailer-mock';
|
||||||
|
import { mockServer } from '@nocobase/test';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import plugin from '../server';
|
||||||
|
|
||||||
|
jest.setTimeout(300000);
|
||||||
|
|
||||||
|
describe('notifications', () => {
|
||||||
|
let db: Database;
|
||||||
|
let NotificationModel: ModelCtor<Notification>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const app = mockServer();
|
||||||
|
app.plugin(plugin);
|
||||||
|
await app.load();
|
||||||
|
db = app.db;
|
||||||
|
await db.sync();
|
||||||
|
NotificationService.createTransport = nodemailerMock.createTransport;
|
||||||
|
NotificationModel = db.getModel('notifications') as ModelCtor<Notification>;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => db.close());
|
||||||
|
|
||||||
|
it('create', async () => {
|
||||||
|
const notification = await NotificationModel.create({
|
||||||
|
subject: 'Subject',
|
||||||
|
body: 'hell world',
|
||||||
|
receiver_options: {
|
||||||
|
data: 'to@nocobase.com',
|
||||||
|
fromTable: 'users',
|
||||||
|
filter: {},
|
||||||
|
dataField: 'email',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await notification.updateAssociations({
|
||||||
|
service: {
|
||||||
|
type: 'email',
|
||||||
|
title: '阿里云邮件推送',
|
||||||
|
options: {
|
||||||
|
host: 'smtpdm.aliyun.com',
|
||||||
|
port: 465,
|
||||||
|
secure: true,
|
||||||
|
auth: {
|
||||||
|
user: 'from@nocobase.com',
|
||||||
|
pass: 'pass',
|
||||||
|
},
|
||||||
|
from: 'NocoBase<from@nocobase.com>',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await notification.send();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,24 @@
|
|||||||
|
import { TableOptions } from '@nocobase/database';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'notification_logs',
|
||||||
|
model: 'NotificationLog',
|
||||||
|
title: '通知日志',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
title: '接收人',
|
||||||
|
type: 'json',
|
||||||
|
name: 'receiver',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
type: 'string',
|
||||||
|
name: 'state',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '详情',
|
||||||
|
type: 'json',
|
||||||
|
name: 'response',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
} as TableOptions;
|
@ -0,0 +1,24 @@
|
|||||||
|
import { TableOptions } from '@nocobase/database';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'notification_services',
|
||||||
|
model: 'NotificationService',
|
||||||
|
title: '通知服务',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
title: '类型',
|
||||||
|
type: 'string',
|
||||||
|
name: 'type',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '服务名称',
|
||||||
|
type: 'string',
|
||||||
|
name: 'title',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '配置信息',
|
||||||
|
type: 'json',
|
||||||
|
name: 'options',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
} as TableOptions;
|
@ -0,0 +1,41 @@
|
|||||||
|
import { TableOptions } from '@nocobase/database';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'notifications',
|
||||||
|
model: 'Notification',
|
||||||
|
title: '通知',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'uid',
|
||||||
|
name: 'name',
|
||||||
|
prefix: 'n_',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '主题',
|
||||||
|
type: 'string',
|
||||||
|
name: 'subject',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '内容',
|
||||||
|
type: 'text',
|
||||||
|
name: 'body',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '接收人配置',
|
||||||
|
type: 'json',
|
||||||
|
name: 'receiver_options',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '发送服务',
|
||||||
|
type: 'belongsTo',
|
||||||
|
name: 'service',
|
||||||
|
target: 'notification_services',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '日志',
|
||||||
|
type: 'hasMany',
|
||||||
|
name: 'logs',
|
||||||
|
target: 'notification_logs',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
} as TableOptions;
|
68
packages/plugin-notifications/src/models/Notification.ts
Normal file
68
packages/plugin-notifications/src/models/Notification.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { Model } from '@nocobase/database';
|
||||||
|
import { NotificationService } from './NotificationService';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
export class Notification extends Model {
|
||||||
|
|
||||||
|
async getReceiversByOptions(): Promise<any[]> {
|
||||||
|
const { data, fromTable, filter, dataField } = this.receiver_options;
|
||||||
|
let receivers = [];
|
||||||
|
if (data) {
|
||||||
|
receivers = Array.isArray(data) ? data : [data];
|
||||||
|
} else if (fromTable) {
|
||||||
|
const M = this.database.getModel(fromTable);
|
||||||
|
const rows = await M.findAll(M.parseApiJson2({
|
||||||
|
filter,
|
||||||
|
}));
|
||||||
|
receivers = rows.map(row => row[dataField]);
|
||||||
|
}
|
||||||
|
return receivers;
|
||||||
|
}
|
||||||
|
|
||||||
|
async send(options: any = {}) {
|
||||||
|
if (!this.service) {
|
||||||
|
this.service = await this.getService();
|
||||||
|
}
|
||||||
|
const receivers = await this.getReceiversByOptions();
|
||||||
|
let { to } = options;
|
||||||
|
if (to) {
|
||||||
|
to = Array.isArray(to) ? to : [to];
|
||||||
|
receivers.push(...to);
|
||||||
|
}
|
||||||
|
console.log(receivers)
|
||||||
|
for (const receiver of receivers) {
|
||||||
|
try {
|
||||||
|
const response = await (this.service as NotificationService).send({
|
||||||
|
to: receiver,
|
||||||
|
subject: this.getSubject(),
|
||||||
|
html: this.getBody(options),
|
||||||
|
});
|
||||||
|
await this.createLog({
|
||||||
|
receiver,
|
||||||
|
state: 'success',
|
||||||
|
response,
|
||||||
|
});
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, 100);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
await this.createLog({
|
||||||
|
receiver,
|
||||||
|
state: 'fail',
|
||||||
|
response: {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getSubject() {
|
||||||
|
return this.subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
getBody(data) {
|
||||||
|
const compiled = _.template(this.body)
|
||||||
|
const body = compiled(data);
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
import { Model } from '@nocobase/database';
|
||||||
|
|
||||||
|
export class NotificationLog extends Model {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
import { Model } from '@nocobase/database';
|
||||||
|
import nodemailer from 'nodemailer';
|
||||||
|
|
||||||
|
export class NotificationService extends Model {
|
||||||
|
|
||||||
|
static createTransport = nodemailer.createTransport;
|
||||||
|
|
||||||
|
get transporter() {
|
||||||
|
if (this._transporter) {
|
||||||
|
return this._transporter;
|
||||||
|
}
|
||||||
|
return this._transporter = NotificationService.createTransport(this.options);
|
||||||
|
}
|
||||||
|
|
||||||
|
async send(options) {
|
||||||
|
const { from } = this.options;
|
||||||
|
const mailOptions = {
|
||||||
|
from,
|
||||||
|
...options,
|
||||||
|
};
|
||||||
|
console.log({ mailOptions });
|
||||||
|
return this.transporter.sendMail(mailOptions);
|
||||||
|
}
|
||||||
|
}
|
3
packages/plugin-notifications/src/models/index.ts
Normal file
3
packages/plugin-notifications/src/models/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './Notification';
|
||||||
|
export * from './NotificationLog';
|
||||||
|
export * from './NotificationService';
|
15
packages/plugin-notifications/src/server.ts
Normal file
15
packages/plugin-notifications/src/server.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import path from 'path';
|
||||||
|
import Database, { registerModels } from '@nocobase/database';
|
||||||
|
import { PluginOptions } from '@nocobase/server';
|
||||||
|
import * as models from './models';
|
||||||
|
|
||||||
|
registerModels(models);
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'notifications',
|
||||||
|
async load() {
|
||||||
|
this.app.db.import({
|
||||||
|
directory: path.resolve(__dirname, 'collections'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} as PluginOptions
|
15
yarn.lock
15
yarn.lock
@ -7590,7 +7590,7 @@ debug@3.X, debug@^3.1.0, debug@^3.2.6, debug@^3.2.7:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms "^2.1.1"
|
ms "^2.1.1"
|
||||||
|
|
||||||
debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@~4.3.1:
|
debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@~4.3.1:
|
||||||
version "4.3.2"
|
version "4.3.2"
|
||||||
resolved "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
|
resolved "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
|
||||||
integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==
|
integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==
|
||||||
@ -13894,6 +13894,19 @@ node-xlsx@^0.16.1:
|
|||||||
buffer-from "^1.1.1"
|
buffer-from "^1.1.1"
|
||||||
xlsx "^0.17.0"
|
xlsx "^0.17.0"
|
||||||
|
|
||||||
|
nodemailer-mock@^1.5.11:
|
||||||
|
version "1.5.11"
|
||||||
|
resolved "https://registry.npmjs.org/nodemailer-mock/-/nodemailer-mock-1.5.11.tgz#1bb6b9af44e9007380191d32e33555ea136a819f"
|
||||||
|
integrity sha512-RbqkppKkptTgSzry3q8u/YD8pxulln/hYQ30O5brFRki8gEKv7KoAYGwfErZM2VsI7nGNBYRo5IOLnMW3NXBcw==
|
||||||
|
dependencies:
|
||||||
|
debug "^4.3.2"
|
||||||
|
nodemailer "^6.x"
|
||||||
|
|
||||||
|
nodemailer@^6.6.1, nodemailer@^6.x:
|
||||||
|
version "6.6.5"
|
||||||
|
resolved "https://registry.npmjs.org/nodemailer/-/nodemailer-6.6.5.tgz#f9f6953cee5cfe82cbea152eeddacf7a0442049a"
|
||||||
|
integrity sha512-C/v856DBijUzHcHIgGpQoTrfsH3suKIRAGliIzCstatM2cAa+MYX3LuyCrABiO/cdJTxgBBHXxV1ztiqUwst5A==
|
||||||
|
|
||||||
nodemon@^2.0.12:
|
nodemon@^2.0.12:
|
||||||
version "2.0.12"
|
version "2.0.12"
|
||||||
resolved "https://registry.npmjs.org/nodemon/-/nodemon-2.0.12.tgz#5dae4e162b617b91f1873b3bfea215dd71e144d5"
|
resolved "https://registry.npmjs.org/nodemon/-/nodemon-2.0.12.tgz#5dae4e162b617b91f1873b3bfea215dd71e144d5"
|
||||||
|
Loading…
Reference in New Issue
Block a user