-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathindex.js
144 lines (126 loc) · 4.75 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
import nodemailer from 'nodemailer';
import express from 'express';
import dotenv from 'dotenv';
import crypto from 'crypto';
import fetch from 'node-fetch';
import cors from 'cors';
dotenv.config(process.env.ENV_PATH ? {path:process.env.ENV_PATH} : undefined);
import { exec } from 'child_process';
import { runInThisContext } from 'vm';
import { create } from 'domain';
import { readdirSync } from 'fs';
const config = {
accountName: 'Hack OC',
accountEmail: '[email protected]',
accountPassword: process.env.EMAIL_PASSWORD,
mailServer: {
host: 'smtp.gmail.com',
post: 465,
secure: true
},
replyTo: '[email protected]',
messages: {}
};
const transporter = nodemailer.createTransport({
auth: {
user: config.accountEmail,
pass: config.accountPassword
},
...config.mailServer
});
async function send (data, { template, subject, text }) {
let info = await transporter.sendMail({
from: `"${config.accountName}" <${config.accountEmail}>`,
to: data.email, // list of receivers
subject: subject(data),
text: text(data),
html: template(data),
replyTo: config.replyTo
});
return info;
}
const app = express();
app.use(express.json({ extended: true }));
app.use(cors());
app.get('/', (req, res) => {
res.send('Hi there! I\'m the Hack OC Mailroom. I power all of our email services, like our mailing list, registrations, vaccine verification, and test verification. I\'m also open source! Check out https://github.com/hackoc/mail.');
});
app.use('/v1/authed', (req, res, next) => {
const auth = req.header('Authorization');
const valid = auth?.startsWith('Bearer hoc-m-') && ('HOC-M-' + crypto.createHash('sha512').update(auth.substring(13)).digest('hex')).toUpperCase() == process.env.API_KEY_HASH;
if (!valid) return res.status(401).send('Unauthorized');
next();
});
app.get('/v1/authed', (req, res) => {
res.send('Authed');
});
app.get('/v1/authed/templates', (req, res) => {
res.json(Object.keys(config.messages));
});
app.get('/v1/authed/templates/:template', (req, res) => {
const { template } = req.params;
if (!config.messages[template]) return res.json({ error: "Template not found" });
return res.json({ required: config.messages[template].required });
});
app.post('/v1/authed/deliver/:message', async (req, res) => {
const { message } = req.params;
if (!config.messages[message]) return res.status(404).send('Message not found');
const template = config.messages[message];
const { data } = req.body;
if (!data) return res.status(400).send('Missing "data" property on JSON');
const results = await send(data, template);
res.json(results);
});
app.get('/v1/unauthed', (req, res) => {
res.send('No authentication needed.');
});
app.get('/v1/unauthed/subscribe/webhook', async (req, res) => {
const response = await fetch('https://api.airtable.com/v0/appYlvRWZObGXXGOh/Emails?maxRecords=3&view=Grid%20view&sort%5B0%5D%5Bfield%5D=ID&sort%5B0%5D%5Bdirection%5D=desc&maxRecords=100&pageSize=100', {
headers: {
Authorization: 'Bearer ' + process.env.AIRTABLE_KEY
}
});
const { records } = await response.json();
let emailsSent = 0;
for (const record of records.filter(record => !record?.fields?.Notification)) {
const email = record?.fields?.Email;
const id = record?.id;
try {
await send({ email }, config.messages.subscribe);
emailsSent += 1;
const output = await fetch('https://api.airtable.com/v0/appYlvRWZObGXXGOh/Emails', {
method: 'PATCH',
headers: {
Authorization: 'Bearer ' + process.env.AIRTABLE_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({
records: [
{
id,
fields: {
Notification: true
}
}
]
})
});
await output.json();
} catch (err) {
console.error(err);
}
}
res.send(`${emailsSent} email(s) sent out.`);
});
app.get('/v1/webhook', (req, res) => {
res.redirect(`http://mail2.hackoc.org:8081/v1/unauthed/subscribe/webhook`);
});
(async () => {
const templates = readdirSync('./templates').filter(template => template.endsWith('.js'));
for (const template of templates) {
config.messages[template.substring(0, template.length - 3)] = await import('./templates/' + template);
}
app.listen(process.env.PORT ?? 8081, () => {
console.log(`Listening on *:${process.env.PORT ?? 8081}`);
});
})();