feat: add @nocobase/plugin-notifications
This commit is contained in:
parent
23bdada69c
commit
4ab7c71f49
@ -56,6 +56,7 @@
|
||||
"koa-bodyparser": "^4.3.0",
|
||||
"lerna": "^3.22.0",
|
||||
"mockjs": "^1.1.0",
|
||||
"nodemailer-mock": "^1.5.11",
|
||||
"nodemon": "^2.0.12",
|
||||
"pg": "^8.6.0",
|
||||
"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:
|
||||
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"
|
||||
resolved "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
|
||||
integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==
|
||||
@ -13894,6 +13894,19 @@ node-xlsx@^0.16.1:
|
||||
buffer-from "^1.1.1"
|
||||
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:
|
||||
version "2.0.12"
|
||||
resolved "https://registry.npmjs.org/nodemon/-/nodemon-2.0.12.tgz#5dae4e162b617b91f1873b3bfea215dd71e144d5"
|
||||
|
Loading…
Reference in New Issue
Block a user