Using pino as a log writer + Append the Generics to make Routing more scalable
This commit is contained in:
parent
d1f1165136
commit
01ca99c4e7
4455
package-lock.json
generated
4455
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -29,6 +29,7 @@
|
|||
"@types/nock": "^11.1.0",
|
||||
"@types/node": "^15.3.0",
|
||||
"@types/node-fetch": "^2.5.10",
|
||||
"@types/pino": "^6.3.8",
|
||||
"@types/sinon-chai": "^3.2.5",
|
||||
"@typescript-eslint/eslint-plugin": "^4.23.0",
|
||||
"@typescript-eslint/parser": "^4.23.0",
|
||||
|
@ -44,6 +45,7 @@
|
|||
"mocha": "^8.4.0",
|
||||
"nock": "^13.0.11",
|
||||
"node-fetch": "^2.6.1",
|
||||
"pino": "^6.11.3",
|
||||
"prettier": "^2.3.0",
|
||||
"sinon-chai": "^3.6.0",
|
||||
"tmp-promise": "^3.0.2",
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { Client, Message, TextChannel } from 'discord.js';
|
||||
import dedent from 'ts-dedent';
|
||||
import { ImplementableApi } from './implementableApi';
|
||||
|
||||
import { once as eventsOnce } from 'events';
|
||||
|
@ -29,7 +28,12 @@ type ChannelsType = {
|
|||
ChannelPeerTube: TextChannel;
|
||||
};
|
||||
|
||||
class DiscordParser extends ImplementableApi {
|
||||
type TBaseDiscordMessageType = ('addListener' | 'newEntriesNotify') &
|
||||
ImplementableApi.TBaseMessageType;
|
||||
|
||||
class DiscordParser<
|
||||
T extends TBaseDiscordMessageType
|
||||
> extends ImplementableApi<T> {
|
||||
readonly botPrefix: string;
|
||||
readonly channels: {
|
||||
[key: string]: TextChannel;
|
||||
|
@ -46,9 +50,9 @@ class DiscordParser extends ImplementableApi {
|
|||
this.settingEvents();
|
||||
}
|
||||
|
||||
static async instanciate(
|
||||
static async instanciate<T extends TBaseDiscordMessageType>(
|
||||
config: DiscordParser.Config
|
||||
): Promise<DiscordParser.ConstructorPattern> {
|
||||
): Promise<DiscordParser<T>> {
|
||||
const client = new Client();
|
||||
client.login(config.token);
|
||||
await eventsOnce(client, 'ready');
|
||||
|
@ -63,12 +67,12 @@ class DiscordParser extends ImplementableApi {
|
|||
),
|
||||
};
|
||||
|
||||
return {
|
||||
return new DiscordParser<T>({
|
||||
name: config.name,
|
||||
channels: channels,
|
||||
client: client,
|
||||
botPrefix: config.keyWord,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private static getTextChannel(client: Client, id: string): TextChannel {
|
||||
|
@ -78,7 +82,7 @@ class DiscordParser extends ImplementableApi {
|
|||
return channel;
|
||||
}
|
||||
|
||||
public receivedMessage(message: ImplementableApi.Message) {
|
||||
public receivedMessage(message: ImplementableApi.Message<T>) {
|
||||
switch (message.type) {
|
||||
case 'newEntriesNotify':
|
||||
this.sendMsgYtb(
|
||||
|
@ -114,13 +118,14 @@ class DiscordParser extends ImplementableApi {
|
|||
if (msg_splitted[0] === this.botPrefix) {
|
||||
switch (msg_splitted[1]) {
|
||||
case 'add':
|
||||
const send_message: ImplementableApi.Message = {
|
||||
const send_message: ImplementableApi.Message<TBaseDiscordMessageType> =
|
||||
{
|
||||
rawContent: {
|
||||
address: msg_splitted[2],
|
||||
user: message.author.toString(),
|
||||
date: message.createdAt.toUTCString(),
|
||||
},
|
||||
type: 'newListener',
|
||||
type: 'addListener',
|
||||
};
|
||||
message.channel.send('Ceci est un feedback');
|
||||
this.emit('addListener', send_message);
|
||||
|
|
|
@ -10,14 +10,17 @@ namespace ImplementableApi {
|
|||
* the type field permit to know which type of message it is
|
||||
* rawContent field is the string content of the message
|
||||
*/
|
||||
export type Message = {
|
||||
export type Message<T extends TBaseMessageType> = {
|
||||
idListener?: number;
|
||||
type: 'newEntriesNotify' | 'newListener';
|
||||
type: T | 'logging';
|
||||
rawContent: any;
|
||||
};
|
||||
export type TBaseMessageType = string;
|
||||
}
|
||||
|
||||
abstract class ImplementableApi extends EventEmitter {
|
||||
abstract class ImplementableApi<
|
||||
T extends ImplementableApi.TBaseMessageType
|
||||
> extends EventEmitter {
|
||||
readonly name: string;
|
||||
|
||||
constructor(readonly config: ImplementableApi.Config) {
|
||||
|
@ -25,7 +28,7 @@ abstract class ImplementableApi extends EventEmitter {
|
|||
this.name = config.name;
|
||||
}
|
||||
|
||||
public abstract receivedMessage(message: ImplementableApi.Message): void;
|
||||
public abstract receivedMessage(message: ImplementableApi.Message<T>): void;
|
||||
}
|
||||
|
||||
export { ImplementableApi };
|
||||
|
|
|
@ -1,11 +1,6 @@
|
|||
import { ImplementableApi } from './implementableApi';
|
||||
import {
|
||||
appendFileSync,
|
||||
closeSync,
|
||||
openSync,
|
||||
writeFile,
|
||||
writeFileSync,
|
||||
} from 'fs';
|
||||
|
||||
import pino, { Logger } from 'pino';
|
||||
|
||||
namespace LogWriter {
|
||||
export type Config = ImplementableApi.Config & {
|
||||
|
@ -17,33 +12,30 @@ namespace LogWriter {
|
|||
*/
|
||||
class LogWriter extends ImplementableApi {
|
||||
readonly path: string;
|
||||
readonly logger: Logger;
|
||||
|
||||
constructor(readonly config: LogWriter.Config) {
|
||||
super(config);
|
||||
this.path = config.path;
|
||||
|
||||
this.firstWrite();
|
||||
}
|
||||
|
||||
private firstWrite() {
|
||||
this.writeMsg('LogWriter is running');
|
||||
this.logger = pino(pino.destination({ dest: config.path }));
|
||||
}
|
||||
|
||||
private writeMsg(message: string): void;
|
||||
private writeMsg(message: ImplementableApi.Message): void;
|
||||
private writeMsg(message: ImplementableApi.Message | string) {
|
||||
if (typeof message !== 'string')
|
||||
message = `[${message.type} :: ${new Date().toISOString()}] ${
|
||||
message.rawContent
|
||||
message = `[${message.type}] ${
|
||||
typeof message.rawContent === 'string'
|
||||
? message.rawContent
|
||||
: JSON.stringify(message.rawContent)
|
||||
} ${
|
||||
message.idListener ??
|
||||
`( listener_id : ${message.idListener} )\n`
|
||||
}`;
|
||||
|
||||
const fd = openSync(this.path, 'a');
|
||||
const str = `[${new Date().toISOString()}] ${message}\n`;
|
||||
appendFileSync(fd, str);
|
||||
closeSync(fd);
|
||||
// const str = `[${new Date().toISOString()}] ${message}\n`;
|
||||
this.logger.info(message);
|
||||
}
|
||||
|
||||
public receivedMessage(message: ImplementableApi.Message) {
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
import { ImplementableApi } from './implementableApi';
|
||||
|
||||
import { DiscordParser } from './discordParser';
|
||||
import { LogWriter } from './logWriter';
|
||||
import { PeerTubeRequester } from './peertubeRequester';
|
||||
import EventEmitter from 'events';
|
||||
|
||||
namespace Router {
|
||||
|
@ -12,44 +9,57 @@ namespace Router {
|
|||
};
|
||||
export type InternalConfig = {
|
||||
events: EventsType[];
|
||||
apis: {
|
||||
apiName: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
// export type GlobalConfig = {
|
||||
// router: InternalConfig;
|
||||
// };
|
||||
}
|
||||
|
||||
class Router extends EventEmitter {
|
||||
api_array: { [key: string]: ImplementableApi } = {};
|
||||
routes: Router.InternalConfig;
|
||||
class Router<
|
||||
MessageType extends ImplementableApi.TBaseMessageType
|
||||
> extends EventEmitter {
|
||||
api_array: { [key: string]: ImplementableApi<MessageType> } = {};
|
||||
events: Router.EventsType[];
|
||||
|
||||
constructor(readonly config: Router.EventsType[]) {
|
||||
super();
|
||||
this.routes = { events: config, apis: [] };
|
||||
this.events = config;
|
||||
}
|
||||
|
||||
public injectDependency(api: ImplementableApi): void {
|
||||
public injectDependency(api: ImplementableApi<MessageType>): void {
|
||||
if (api.name in this.api_array)
|
||||
throw `The api name '${api.name}' is already take`;
|
||||
this.routes.apis.push({ apiName: api.name });
|
||||
this.api_array[api.name] = api;
|
||||
|
||||
this.setEvents(api);
|
||||
|
||||
this.receivedMessage({
|
||||
rawContent: `The dependency \`${api.name}\` was well injected into the router`,
|
||||
type: 'logging',
|
||||
});
|
||||
}
|
||||
|
||||
private setEvents(api: ImplementableApi) {
|
||||
for (const eventName in this.routes.events.map(
|
||||
private setEvents(api: ImplementableApi<MessageType>) {
|
||||
for (const event of this.events.filter(
|
||||
(ev) => ev.type === 'received'
|
||||
))
|
||||
api.on(eventName, () => this.emit(eventName));
|
||||
)) {
|
||||
api.on(event.name, (obj: ImplementableApi.Message<MessageType>) => {
|
||||
this.receivedMessage({
|
||||
type: 'logging',
|
||||
rawContent: `A message of type \`${obj.type}\` was emited by \`${api.name}\` with the event \`${event.name}\``,
|
||||
});
|
||||
this.emit(event.name, obj);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public receivedMessage(message: ImplementableApi.Message) {
|
||||
for (const apiName in this.routes.apis)
|
||||
this.api_array[apiName].receivedMessage(message);
|
||||
public receivedMessage(message: ImplementableApi.Message<MessageType>) {
|
||||
this.redirectMessage({
|
||||
rawContent: `A message of type \`${message.type}\` was received`,
|
||||
type: 'logging',
|
||||
});
|
||||
this.redirectMessage(message);
|
||||
}
|
||||
private redirectMessage(message: ImplementableApi.Message<MessageType>) {
|
||||
for (const api in this.api_array)
|
||||
this.api_array[api].receivedMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
import chai, { expect } from 'chai';
|
||||
import sinon from 'ts-sinon';
|
||||
import sinonChai from 'sinon-chai';
|
||||
|
||||
chai.use(sinonChai);
|
||||
|
||||
import { DiscordParser } from '../src/discordParser';
|
||||
|
||||
//data
|
||||
const config: DiscordParser.Config = {
|
||||
channelsId: {
|
||||
idChannelPeerTube: 'peertubeID',
|
||||
idChannelYtb: 'ytbChannel',
|
||||
},
|
||||
keyWord: 'myDiscordKeyword',
|
||||
name: 'DiscordTestedInterface',
|
||||
token: 'mySecretDiscordToken',
|
||||
};
|
||||
|
||||
//mockeded imports
|
||||
import { Channel, ChannelManager, Client } from 'discord.js';
|
||||
|
||||
describe.skip('test DiscordParser', function () {
|
||||
let clientMockOn: sinon.SinonStub,
|
||||
clientMockLogin: sinon.SinonStub,
|
||||
channelStub: sinon.SinonStubbedInstance<Channel>,
|
||||
channelManagerMockResolve: sinon.SinonStub;
|
||||
|
||||
before(() => {
|
||||
clientMockOn = sinon.stub(Client.prototype, 'on');
|
||||
clientMockOn.withArgs('message', sinon.match.func).onFirstCall();
|
||||
clientMockOn.throws('Error, bad parameter or too much call');
|
||||
|
||||
clientMockLogin = sinon.stub(Client.prototype, 'login');
|
||||
clientMockLogin
|
||||
.withArgs(config.token)
|
||||
.onFirstCall()
|
||||
.resolves('ok')
|
||||
.returnsThis();
|
||||
clientMockLogin.throws('Error, bad parameter or too much call');
|
||||
|
||||
channelStub = sinon.createStubInstance(Channel);
|
||||
|
||||
channelManagerMockResolve = sinon.stub(
|
||||
ChannelManager.prototype,
|
||||
'resolve'
|
||||
);
|
||||
channelManagerMockResolve
|
||||
.withArgs(config.channelsId.idChannelPeerTube)
|
||||
.onFirstCall()
|
||||
.returns(channelStub);
|
||||
channelManagerMockResolve
|
||||
.withArgs(config.channelsId.idChannelYtb)
|
||||
.onFirstCall()
|
||||
.returns(channelStub);
|
||||
channelManagerMockResolve.throws("Error Bad id's or too much call");
|
||||
});
|
||||
|
||||
after(() => {
|
||||
clientMockOn.restore();
|
||||
clientMockLogin.restore();
|
||||
channelManagerMockResolve.restore();
|
||||
});
|
||||
|
||||
it('it will test the DiscordParser constructor', async function () {
|
||||
//when
|
||||
const discordParser = new DiscordParser(config);
|
||||
|
||||
// expect
|
||||
expect(discordParser.token).to.be.eql(config.token);
|
||||
await discordParser.channels.then((channels) => {
|
||||
expect(channels.ChannelYtb.id).to.be.eql(
|
||||
config.channelsId.idChannelYtb
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -5,42 +5,49 @@ import sinonChai from 'sinon-chai';
|
|||
chai.use(sinonChai);
|
||||
const expect = chai.expect;
|
||||
|
||||
import { withFile } from 'tmp-promise';
|
||||
import { writeFileSync } from 'fs';
|
||||
import { ImplementableApi, Router } from '../src/index';
|
||||
|
||||
import { Router } from '../src/index';
|
||||
import { DiscordParser } from '../src/discordParser';
|
||||
import { LogWriter } from '../src/logWriter';
|
||||
import { PeerTubeRequester } from '../src/peertubeRequester';
|
||||
// const well_build_routing_file: Router.GlobalConfig = require('./rsrc/wellBuildedRoutingFile.json');
|
||||
|
||||
const well_build_routing_file: Router.GlobalConfig = require('./rsrc/wellBuildedRoutingFile.json');
|
||||
|
||||
describe('testing the routing part', function () {
|
||||
describe('testing the building part', function () {
|
||||
it('it will test a normal building', async function () {
|
||||
await withFile(
|
||||
async (file) => {
|
||||
const edit_config = {
|
||||
...well_build_routing_file,
|
||||
logWriter: {
|
||||
...well_build_routing_file.logWriter,
|
||||
...{ path: file.path },
|
||||
const events: Router.EventsType[] = [
|
||||
{
|
||||
name: 'newEntries',
|
||||
type: 'emit',
|
||||
},
|
||||
};
|
||||
const r = new Router(edit_config);
|
||||
|
||||
// expect(r.api_array['Discord']).to.be.instanceOf(
|
||||
// DiscordParser
|
||||
// );
|
||||
expect(r.api_array['logWriter']).to.be.instanceOf(
|
||||
LogWriter
|
||||
);
|
||||
expect(r.api_array['peertubeRequester']).to.be.instanceOf(
|
||||
PeerTubeRequester
|
||||
);
|
||||
{
|
||||
name: 'addListener',
|
||||
type: 'received',
|
||||
},
|
||||
{ postfix: '.log' }
|
||||
);
|
||||
];
|
||||
type AllowedEvents = 'newEntries' | 'addListener'; // need to match with the EventsType's names
|
||||
|
||||
class FakeApi extends ImplementableApi<AllowedEvents> {
|
||||
constructor(conf: ImplementableApi.Config) {
|
||||
super(conf);
|
||||
}
|
||||
|
||||
receivedMessage(msg: ImplementableApi.Message<AllowedEvents>) {
|
||||
// don't do anything
|
||||
}
|
||||
}
|
||||
|
||||
describe.only('testing the routing part', function () {
|
||||
describe('testing the injection', function () {
|
||||
it('will test the dependency injection', function () {
|
||||
// given
|
||||
const myRouter = new Router<AllowedEvents>(events);
|
||||
const myObj = new FakeApi({ name: 'myFakeApi' });
|
||||
// first expect
|
||||
expect(myRouter.api_array).to.be.empty;
|
||||
expect(myRouter.events).to.be.empty;
|
||||
|
||||
// when
|
||||
myRouter.injectDependency(myObj);
|
||||
|
||||
// expect
|
||||
expect(myRouter.api_array).to.have.keys(['myFakeApi']);
|
||||
expect(myRouter.api_array['myFakeApi']).to.be.instanceOf(FakeApi);
|
||||
expect(myRouter.events).to.be.empty;
|
||||
});
|
||||
});
|
||||
describe('testing the data transmission', function () {
|
||||
|
@ -48,6 +55,31 @@ describe('testing the routing part', function () {
|
|||
it('it will emit a new listener request', function () {});
|
||||
});
|
||||
describe('testing the data reception', function () {
|
||||
it('it will received a new entries notification', function () {});
|
||||
it.only('it will received a new entries notification', function () {
|
||||
//given
|
||||
const myMessageWhoWouldBeSend: ImplementableApi.Message<AllowedEvents> =
|
||||
{
|
||||
rawContent: 'My content',
|
||||
type: 'logging',
|
||||
idListener: 5,
|
||||
};
|
||||
const myStub = sinon.stub(FakeApi.prototype, 'receivedMessage');
|
||||
const myRouter = new Router([]);
|
||||
const myObj = new FakeApi({ name: 'myFakeApi' });
|
||||
myRouter.injectDependency(myObj);
|
||||
|
||||
// when
|
||||
myRouter.receivedMessage(myMessageWhoWouldBeSend);
|
||||
|
||||
// expect
|
||||
expect(myStub.firstCall).to.have.been.calledWith(
|
||||
myMessageWhoWouldBeSend
|
||||
);
|
||||
expect(myStub.secondCall).to.have.been.calledWith({
|
||||
rawContent:
|
||||
'The dependency `myFakeApi` was well injected into the router',
|
||||
type: 'logging',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
import chai, { expect } from 'chai';
|
||||
import sinon from 'ts-sinon';
|
||||
import sinonChai from 'sinon-chai';
|
||||
|
||||
chai.use(sinonChai);
|
||||
|
||||
import { withDir } from 'tmp-promise';
|
||||
import { existsSync, readFileSync } from 'fs';
|
||||
|
||||
import { LogWriter } from '../src/logWriter';
|
||||
|
||||
const config: LogWriter.Config = {
|
||||
name: 'logWirterTested',
|
||||
path: 'it will be set by tmp-promise',
|
||||
};
|
||||
|
||||
describe('test logWriter', function () {
|
||||
describe('constructor', function () {
|
||||
it('will test the constructor with a new file', async function () {
|
||||
await withDir(
|
||||
async (dir) => {
|
||||
const log_writer = new LogWriter({
|
||||
...config,
|
||||
...{ path: dir.path + '/toto.log' },
|
||||
});
|
||||
expect(existsSync(dir.path + '/toto.log')).to.be.true;
|
||||
expect(
|
||||
readFileSync(dir.path + '/toto.log', {
|
||||
encoding: 'utf-8',
|
||||
})
|
||||
).to.match(/LogWriter is running\n+$/g);
|
||||
},
|
||||
{ unsafeCleanup: true }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('received message', function () {
|
||||
it('will test the print function', function () {});
|
||||
});
|
||||
});
|
||||
|
||||
//presenter le projet . listener -> import sous forme de plugin
|
Loading…
Reference in New Issue
Block a user