Skip to content

Commit 77f0967

Browse files
authored
Merge pull request #18 from paywteam/feature/notifier
Complete implementation for fragment notifier
2 parents 9823efe + 25ebb43 commit 77f0967

9 files changed

+196
-7
lines changed

.env.example

+6
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ DB_DATABASE=database-name
88
DB_USERNAME=username
99
DB_PASSWORD=password
1010

11+
MAIL_SERVICE=Zoho
12+
MAIL_HOST=smtp.zoho.com
13+
MAIL_PORT=465
14+
MAIL_USERNAME=username
15+
MAIL_PASSWORD=password
16+
1117
SESSION_SECRET=session-secret
1218
SESSION_NAME=session-name
1319

package.json

+4
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242
"moment": "^2.24.0",
4343
"mongoose": "^5.7.3",
4444
"nanoid": "^2.1.3",
45+
"node-schedule": "^1.3.2",
46+
"nodemailer": "^6.3.1",
4547
"p-limit": "^2.2.1",
4648
"passport": "^0.4.0",
4749
"passport-google-oauth20": "^2.0.0",
@@ -64,6 +66,8 @@
6466
"@types/module-alias": "^2.0.0",
6567
"@types/mongoose": "^5.5.18",
6668
"@types/nanoid": "^2.1.0",
69+
"@types/node-schedule": "^1.2.4",
70+
"@types/nodemailer": "^6.2.2",
6771
"@types/passport": "^1.0.1",
6872
"@types/passport-google-oauth20": "^2.0.2",
6973
"@types/puppeteer": "^2.0.0",

src/app/modules/Mailer.ts

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import nodeMailer, { Transporter } from 'nodemailer'
2+
import LogHelper from '@/modules/LogHelper'
3+
4+
export default class Mailer {
5+
private static _instance: Mailer
6+
7+
/**
8+
* Mail transporter
9+
*/
10+
private readonly transporter: Transporter
11+
12+
/**
13+
* Get singleton instance
14+
*
15+
* @constructor
16+
*/
17+
public static get Instance(): Mailer {
18+
if (this._instance === undefined) {
19+
this._instance = new this()
20+
}
21+
22+
return this._instance
23+
}
24+
25+
/**
26+
* Private constructor for singleton pattern
27+
*/
28+
// eslint-disable-next-line no-useless-constructor
29+
private constructor() {
30+
this.transporter = nodeMailer.createTransport({
31+
service: process.env.MAIL_SERVICE,
32+
host: process.env.MAIL_HOST,
33+
port: parseInt(process.env.MAIL_PORT, 10),
34+
secure: true,
35+
auth: {
36+
user: process.env.MAIL_USERNAME,
37+
pass: process.env.MAIL_PASSWORD
38+
}
39+
})
40+
}
41+
42+
public sendMail(mailOptions: object): void {
43+
this.transporter.sendMail(mailOptions, err => {
44+
if (err) {
45+
LogHelper.Instance.log('error', 'Mailer error: ' + err.stack)
46+
}
47+
})
48+
}
49+
}

src/app/modules/webglue-api/FragmentNotifier.ts

+28-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import BrowserHandler from '@/modules/BrowserHandler'
2+
import { UserDoc } from '@@/migrate/schemas/user'
3+
import Mailer from '@/modules/Mailer'
24

35
interface Selector {
46
name: string
@@ -8,9 +10,16 @@ interface Selector {
810
export default class FragmentNotifier {
911
private static readonly VIEWPORT = { width: 1280, height: 800 }
1012

11-
// public notify(): void {}
13+
public async notify(
14+
user: UserDoc,
15+
url: string,
16+
selector: Selector
17+
): Promise<void> {
18+
const captureImg = await this.capture(url, selector)
19+
this.sendCaptureToUser(user, url, captureImg)
20+
}
1221

13-
public async capture(url: string, selector: Selector): Promise<string> {
22+
private async capture(url: string, selector: Selector): Promise<string> {
1423
const page = await BrowserHandler.Instance.browser.newPage()
1524
await page.setViewport(FragmentNotifier.VIEWPORT)
1625
await page.goto(url, { waitUntil: 'networkidle2' })
@@ -20,7 +29,7 @@ export default class FragmentNotifier {
2029
return null
2130
}
2231

23-
const capture = element.screenshot({
32+
const capture = await element.screenshot({
2433
encoding: 'base64'
2534
})
2635

@@ -29,5 +38,20 @@ export default class FragmentNotifier {
2938
return capture
3039
}
3140

32-
// public sendCaptureToUser(): void {}
41+
private sendCaptureToUser(user: UserDoc, url: string, capture: string): void {
42+
Mailer.Instance.sendMail({
43+
from: '"webglue notifier" <[email protected]>',
44+
to: user.email,
45+
subject: 'webglue fragment 변화 알림',
46+
html: `<a href="${url}"><img src="cid:[email protected]" /></a>`,
47+
attachments: [
48+
{
49+
filename: 'fragment.png',
50+
content: capture,
51+
encoding: 'base64',
52+
cid: '[email protected]' // same cid value as in the html img src
53+
}
54+
]
55+
})
56+
}
3357
}

src/app/modules/webglue-api/FragmentWatcher.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import moment from 'moment'
44
import pLimit from 'p-limit'
55
import Snappy from '@/modules/webglue-api/Snappy'
66
import { JSDOM } from 'jsdom'
7-
import EventEmitter from 'events'
7+
import { EventEmitter } from 'events'
88
import { GlueBoardDoc } from '@@/migrate/schemas/glue-board'
99
import { UserDoc } from '@@/migrate/schemas/user'
1010

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import FragmentWatcher from '@/modules/webglue-api/FragmentWatcher'
2+
import FragmentNotifier from '@/modules/webglue-api/FragmentNotifier'
3+
import { UserDoc } from '@@/migrate/schemas/user'
4+
5+
export default class EventServiceProvider {
6+
public static boot(): void {
7+
this.registerFragmentEvent()
8+
}
9+
10+
private static registerFragmentEvent(): void {
11+
FragmentWatcher.Instance.on(
12+
FragmentWatcher.CHANGE_EVENT,
13+
(
14+
user: UserDoc,
15+
url: string,
16+
selector: { name: string; offset: number }
17+
) => {
18+
const notifier = new FragmentNotifier()
19+
notifier.notify(user, url, selector).then()
20+
}
21+
)
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import schedule from 'node-schedule'
2+
import FragmentWatcher from '@/modules/webglue-api/FragmentWatcher'
3+
4+
export default class ScheduleServiceProvider {
5+
public static boot(): void {
6+
this.scheduleWatcher()
7+
}
8+
9+
private static scheduleWatcher(): void {
10+
// watch fragment every 5 minutes
11+
schedule.scheduleJob('*/5 * * * *', () => {
12+
FragmentWatcher.Instance.watch().then()
13+
})
14+
}
15+
}

src/main.ts

+8
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,17 @@ import 'module-alias/register'
22
import dotenv from 'dotenv'
33
import DBServiceProvider from '@/providers/DBServiceProvider'
44
import RouteServiceProvider from '@/providers/RouteServiceProvider'
5+
import EventServiceProvider from '@/providers/EventServiceProvider'
6+
import ScheduleServiceProvider from '@/providers/ScheduleServiceProvider'
7+
import BrowserHandler from '@/modules/BrowserHandler'
58

69
async function bootApp(): Promise<void> {
710
await DBServiceProvider.boot()
11+
EventServiceProvider.boot()
12+
13+
await BrowserHandler.Instance.turnOn()
14+
15+
ScheduleServiceProvider.boot()
816
const app = RouteServiceProvider.boot()
917
app.listen(process.env.APP_PORT)
1018
}

yarn.lock

+62-2
Original file line numberDiff line numberDiff line change
@@ -190,11 +190,25 @@
190190
dependencies:
191191
"@types/node" "*"
192192

193+
"@types/node-schedule@^1.2.4":
194+
version "1.2.4"
195+
resolved "https://registry.yarnpkg.com/@types/node-schedule/-/node-schedule-1.2.4.tgz#2dce38dd7d87e77cd293113ac72f795fb9f6dfe0"
196+
integrity sha512-s8ie8rUwAtX0Si75SiKH14akE/Ofw/Hx4Exbulv4wOQJEDerI7zeOaDODr/a1UEaHN19VkmZCt9Q0rFEKrVE5g==
197+
dependencies:
198+
"@types/node" "*"
199+
193200
"@types/node@*":
194201
version "12.12.12"
195202
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.12.tgz#529bc3e73dbb35dd9e90b0a1c83606a9d3264bdb"
196203
integrity sha512-MGuvYJrPU0HUwqF7LqvIj50RZUX23Z+m583KBygKYUZLlZ88n6w28XRNJRJgsHukLEnLz6w6SvxZoLgbr5wLqQ==
197204

205+
"@types/nodemailer@^6.2.2":
206+
version "6.2.2"
207+
resolved "https://registry.yarnpkg.com/@types/nodemailer/-/nodemailer-6.2.2.tgz#08df85c22546e9f3f3c86e5635571c5229ab43a3"
208+
integrity sha512-vDbSSe3+bBXYgibKs8duOrH7bhAv1hMHl3Vtdr3KmXZWLfi5WtIg3X6D/+K4SMK1RbNlm5QZBQCOldST/sVjjg==
209+
dependencies:
210+
"@types/node" "*"
211+
198212
"@types/oauth@*":
199213
version "0.9.1"
200214
resolved "https://registry.yarnpkg.com/@types/oauth/-/oauth-0.9.1.tgz#e17221e7f7936b0459ae7d006255dff61adca305"
@@ -742,6 +756,14 @@ cors@^2.8.5:
742756
object-assign "^4"
743757
vary "^1"
744758

759+
cron-parser@^2.7.3:
760+
version "2.13.0"
761+
resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-2.13.0.tgz#6f930bb6f2931790d2a9eec83b3ec276e27a6725"
762+
integrity sha512-UWeIpnRb0eyoWPVk+pD3TDpNx3KCFQeezO224oJIkktBrcW6RoAPOx5zIKprZGfk6vcYSmA8yQXItejSaDBhbQ==
763+
dependencies:
764+
is-nan "^1.2.1"
765+
moment-timezone "^0.5.25"
766+
745767
cross-spawn@^6.0.5:
746768
version "6.0.5"
747769
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
@@ -824,7 +846,7 @@ deep-is@~0.1.3:
824846
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
825847
integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=
826848

827-
define-properties@^1.1.2, define-properties@^1.1.3:
849+
define-properties@^1.1.1, define-properties@^1.1.2, define-properties@^1.1.3:
828850
version "1.1.3"
829851
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
830852
integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==
@@ -1758,6 +1780,13 @@ is-glob@^4.0.0, is-glob@^4.0.1:
17581780
dependencies:
17591781
is-extglob "^2.1.1"
17601782

1783+
is-nan@^1.2.1:
1784+
version "1.2.1"
1785+
resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.2.1.tgz#9faf65b6fb6db24b7f5c0628475ea71f988401e2"
1786+
integrity sha1-n69ltvttskt/XAYoR16nH5iEAeI=
1787+
dependencies:
1788+
define-properties "^1.1.1"
1789+
17611790
is-promise@^2.1.0:
17621791
version "2.1.0"
17631792
resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa"
@@ -1960,6 +1989,11 @@ logform@^2.1.1:
19601989
ms "^2.1.1"
19611990
triple-beam "^1.3.0"
19621991

1992+
1993+
version "0.1.1"
1994+
resolved "https://registry.yarnpkg.com/long-timeout/-/long-timeout-0.1.1.tgz#9721d788b47e0bcb5a24c2e2bee1a0da55dab514"
1995+
integrity sha1-lyHXiLR+C8taJMLivuGg2lXatRQ=
1996+
19631997
lru-cache@^5.1.1:
19641998
version "5.1.1"
19651999
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
@@ -2048,7 +2082,14 @@ module-alias@^2.2.2:
20482082
resolved "https://registry.yarnpkg.com/module-alias/-/module-alias-2.2.2.tgz#151cdcecc24e25739ff0aa6e51e1c5716974c0e0"
20492083
integrity sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==
20502084

2051-
moment@^2.24.0:
2085+
moment-timezone@^0.5.25:
2086+
version "0.5.27"
2087+
resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.27.tgz#73adec8139b6fe30452e78f210f27b1f346b8877"
2088+
integrity sha512-EIKQs7h5sAsjhPCqN6ggx6cEbs94GK050254TIJySD1bzoM5JTYDwAU1IoVOeTOL6Gm27kYJ51/uuvq1kIlrbw==
2089+
dependencies:
2090+
moment ">= 2.9.0"
2091+
2092+
"moment@>= 2.9.0", moment@^2.24.0:
20522093
version "2.24.0"
20532094
resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
20542095
integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
@@ -2147,6 +2188,20 @@ [email protected]:
21472188
resolved "https://registry.yarnpkg.com/nocache/-/nocache-2.1.0.tgz#120c9ffec43b5729b1d5de88cd71aa75a0ba491f"
21482189
integrity sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q==
21492190

2191+
node-schedule@^1.3.2:
2192+
version "1.3.2"
2193+
resolved "https://registry.yarnpkg.com/node-schedule/-/node-schedule-1.3.2.tgz#d774b383e2a6f6ade59eecc62254aea07cd758cb"
2194+
integrity sha512-GIND2pHMHiReSZSvS6dpZcDH7pGPGFfWBIEud6S00Q8zEIzAs9ommdyRK1ZbQt8y1LyZsJYZgPnyi7gpU2lcdw==
2195+
dependencies:
2196+
cron-parser "^2.7.3"
2197+
long-timeout "0.1.1"
2198+
sorted-array-functions "^1.0.0"
2199+
2200+
nodemailer@^6.3.1:
2201+
version "6.3.1"
2202+
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.3.1.tgz#2784beebac6b9f014c424c54dbdcc5c4d1221346"
2203+
integrity sha512-j0BsSyaMlyadEDEypK/F+xlne2K5m6wzPYMXS/yxKI0s7jmT1kBx6GEKRVbZmyYfKOsjkeC/TiMVDJBI/w5gMQ==
2204+
21502205
normalize-package-data@^2.3.2:
21512206
version "2.5.0"
21522207
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
@@ -2777,6 +2832,11 @@ [email protected]:
27772832
resolved "https://registry.yarnpkg.com/sliced/-/sliced-1.0.1.tgz#0b3a662b5d04c3177b1926bea82b03f837a2ef41"
27782833
integrity sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=
27792834

2835+
sorted-array-functions@^1.0.0:
2836+
version "1.2.0"
2837+
resolved "https://registry.yarnpkg.com/sorted-array-functions/-/sorted-array-functions-1.2.0.tgz#43265b21d6e985b7df31621b1c11cc68d8efc7c3"
2838+
integrity sha512-sWpjPhIZJtqO77GN+LD8dDsDKcWZ9GCOJNqKzi1tvtjGIzwfoyuRH8S0psunmc6Z5P+qfDqztSbwYR5X/e1UTg==
2839+
27802840
source-map-support@^0.5.6:
27812841
version "0.5.16"
27822842
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042"

0 commit comments

Comments
 (0)