Skip to content

Commit

Permalink
finish sendy integration
Browse files Browse the repository at this point in the history
  • Loading branch information
pkage committed Oct 20, 2020
1 parent 3d1aed2 commit b360755
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 10 deletions.
31 changes: 27 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
"author": "patrick kage",
"license": "MIT",
"dependencies": {
"axios": "^0.20.0",
"ejs": "^3.1.5",
"express": "^4.17.1",
"express-session": "^1.17.1",
"form-data": "^3.0.0",
"luxon": "^1.19.3",
"nightmare": "^3.0.2",
"passport": "^0.4.1",
Expand Down
44 changes: 44 additions & 0 deletions sendy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const axios = require('axios')
const FormData = require('form-data')

/*
* Send a single email
*/
const add_email = async (member, sendy_info) => {
const form = new FormData()
form.append('api_key', sendy_info.api_key)
form.append('name', member.name.full)
form.append('email', `s${member.student}@ed.ac.uk`)
form.append('list', sendy_info.target_list)
form.append('boolean', 'true')

await axios.post(sendy_info.sendy_url + 'subscribe', form, { headers: form.getHeaders() })
}

const sendy_sync = async (members, sendy_info) => {
// Got to send these in small batches and sequentially, because sendy is kinda shit.

const partial_add_email = m => add_email(m, sendy_info)

// split into batches of 10
const BATCH_SIZE = 10
const members_batched = members
.reduce((a, c, i) => {
// if we're at BATCH_SIZE or starting off, add a sub array
if (0 == i % BATCH_SIZE) {
a.push([])
}

// add to the last batch
a[a.length - 1].push(c)

return a
}, [])

for (let batch of members_batched) {
// resolve all these guys
await Promise.all(batch.map(partial_add_email))
}
}

module.exports = sendy_sync
28 changes: 24 additions & 4 deletions server.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const express = require('express')
const scrape_members = require('./scrape.js')
const sendy_sync = require('./sendy')
const fs = require('fs')
const fsAsync = require('fs').promises
const { DateTime } = require('luxon')
Expand Down Expand Up @@ -81,7 +82,12 @@ api_app.get('/refresh', async (req, res) => {
})

api_app.get('/sendy_sync', async (req, res) => {

try {
const subscribed = await sendy_sync((await readScrape()).members, secrets.sendy)
res.json({ success: true })
} catch (e) {
res.json({ success: false, status: 'An error occurred.' })
}
})

/* --- FRONTEND --- */
Expand Down Expand Up @@ -169,11 +175,16 @@ app.get('/dashboard',
latest,
csv: makecsv(latest.members),
render_time: new Date().toISOString(),
sendy: {
url: secrets.sendy.sendy_url,
list: secrets.sendy.target_list
},
apikey: apikey,
user: req.user
})
}
)

app.get('/dashboard/rescrape',
login_guard,
(req, res) => {
Expand All @@ -184,6 +195,16 @@ app.get('/dashboard/rescrape',
}
)

app.get('/dashboard/sendy_sync',
login_guard,
(req, res) => {
res.render('sendy_sync', {
apikey,
user: req.user
})
}
)



// mount the api application
Expand All @@ -193,7 +214,6 @@ app.use('/api', api_app)

console.log('getting initial scrape...')

const dummy = async () => {}
//writeScrape()
dummy()
//const dummy = async () => {} // useful for debugging
writeScrape()
.then(() => app.listen(port, () => console.log(`EUSA members api listening on port ${port}!`)))
6 changes: 4 additions & 2 deletions views/dash.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
<main>
<h1> Dashboard </h1>

<p> General information about the API status.</p>
<p> General information about the Members API.</p>

<ul>
<li> Login email: <strong><%= user.email %></strong> (<%= user.email_verified ? 'verified' : 'nnot verified' %>) </li>
Expand All @@ -39,14 +39,16 @@
<summary>Developer</summary>
<ul>
<li>Current API key: <span class="mono"><%= apikey %></span></li>
<li>Sendy installation: <a href="<%= sendy.url %>"><%= sendy.url %></a></li>
<li> Sendy list: <span class="mono"><%= sendy.list %></span></li>
</ul>
</details>

<h1> Synchronization </h1>

<p>
<a href="/dashboard/rescrape">Rescrape &rarr;</a> <br/>
<a href="/dashboard/synchronize">Sync with Sendy &rarr;</a>
<a href="/dashboard/sendy_sync">Sync with Sendy &rarr;</a>
</p>

<h1> Exports </h1>
Expand Down
70 changes: 70 additions & 0 deletions views/sendy_sync.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<!DOCTYPE html>

<html>
<head>
<title>EUSA Members API</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/static/index.css" rel="stylesheet">
<link href="/static/compsoc-icon.png" rel="icon">
</head>

<body>
<nav>
<div class="nav__brand">
<img src="/static/compsoc-icon.png" alt="compsoc icon"/>
<span class="nav__title">EUSA Members API</span>
</div>
<div class="nav__login">
<span class="nav__name"><%= user.name %></span>
<img src="<%= user.photo %>" alt="<%= user.name %> photo"/>

<a class="nav__logout" href="/auth/logout">
<span>logout</span>
</a>
</div>
</nav>
<main>
<h1> Synchronizing... </h1>
<p id="status">
Synchronizing the cached member list with Sendy. This may take 5-30 seconds.
Please do not refresh the page!
</p>

<div class="spinner" id="spinner"></div>

<a href="/dashboard" id="return" style="display: none;">
Return to the dashboard &rarr;
</a>
</main>

<script type="text/javascript">
window.apikey = '<%- apikey %>'
const statusP = document.querySelector('#status')
const returnLink = document.querySelector('#return')
const spinner = document.querySelector('#spinner')
fetch('/api/sendy_sync', {
headers: {
'Authorization': `Bearer ${window.apikey}`
}
})
.then(r => r.json())
.then(r => {
returnLink.style.display = 'inline'
spinner.style.display = 'none';
if (r.success) {
location.href = '/dashboard'
statusP.innerText = 'Scrape successful!'
} else {
statusP.innerHTML = `Something went wrong! Here's what we know: <br/> <code> ${r.status} </code>`
}
})
.catch(() => {
returnLink.style.display = 'inline'
spinner.style.display = 'none';
statusP.innerText = `Something went wrong! And we don't know what it is, either.`
})
</script>
</body>
</html>

0 comments on commit b360755

Please sign in to comment.