feat: add @nocobase/plugin-notifications

This commit is contained in:
chenos 2021-10-08 23:12:39 +08:00
parent 23bdada69c
commit 4ab7c71f49
13 changed files with 292 additions and 1 deletions

View File

@ -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",

View File

@ -0,0 +1,7 @@
node_modules
*.log
docs
__tests__
tsconfig.json
src
.fatherrc.ts

View 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"
}

View File

@ -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();
});
});

View File

@ -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;

View File

@ -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;

View File

@ -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;

View 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;
}
}

View File

@ -0,0 +1,5 @@
import { Model } from '@nocobase/database';
export class NotificationLog extends Model {
}

View File

@ -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);
}
}

View File

@ -0,0 +1,3 @@
export * from './Notification';
export * from './NotificationLog';
export * from './NotificationService';

View 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

View File

@ -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"