Hippo
8226f67f3d
Now, the app (server) only runs when run as a standalone script, not when it's imported elsewhere!
863 lines
24 KiB
JavaScript
863 lines
24 KiB
JavaScript
const express = require('express')
|
|
const nodemailer = require('nodemailer')
|
|
const bodyParser = require('body-parser')
|
|
const path = require('path')
|
|
const { URLSafeTimedSerializer } = require("@constemi/itsdangerjs")
|
|
|
|
require('dotenv').config()
|
|
|
|
// set up debug
|
|
let DEBUG
|
|
|
|
if (process.env.DEBUG || process.env.CROWDFUNDING_SITE_DEBUG) {
|
|
DEBUG = true
|
|
} else {
|
|
DEBUG = false
|
|
}
|
|
|
|
if (DEBUG) console.log('Loading website in debug mode')
|
|
|
|
// set up secret key
|
|
let secretKey
|
|
if (process.env.CROWDFUNDING_SITE_SECRET_KEY) {
|
|
secretKey = process.env.CROWDFUNDING_SITE_SECRET_KEY
|
|
} else {
|
|
if (DEBUG) {
|
|
secretKey = 'NotReallyASecret'
|
|
console.warn("Secret key is not set! We're falling back to the default one which is INSECURE and NOT TO BE USED IN PRODUCTION")
|
|
} else {
|
|
console.error('No secret key is set. You cannot run this in production without setting a secret key because that would be very insecure. Sorry.')
|
|
process.exit()
|
|
}
|
|
}
|
|
|
|
// helper function to make the serialiser
|
|
function makeSerialiser(value) {
|
|
return URLSafeTimedSerializer(secretKey, {salt: value})
|
|
}
|
|
|
|
// set up nodemailer (if configured)
|
|
let mailer
|
|
let emailFrom
|
|
if (
|
|
!!process.env.CROWDFUNDING_SITE_EMAIL_HOST &&
|
|
!!process.env.CROWDFUNDING_SITE_EMAIL_USER &&
|
|
!!process.env.CROWDFUNDING_SITE_EMAIL_PASSWORD
|
|
) {
|
|
mailer = nodemailer.createTransport({
|
|
host: process.env.CROWDFUNDING_SITE_EMAIL_HOST,
|
|
port: process.env.CROWDFUNDING_SITE_EMAIL_PORT || 587,
|
|
secure: process.env.CROWDFUNDING_SITE_EMAIL_SECURE == 'false' ? false : true,
|
|
auth: {
|
|
user: process.env.CROWDFUNDING_SITE_EMAIL_USER,
|
|
pass: process.env.CROWDFUNDING_SITE_EMAIL_PASSWORD,
|
|
}
|
|
})
|
|
|
|
if (!!process.env.CROWDFUNDING_SITE_EMAIL_FROM) {
|
|
emailFrom = process.env.CROWDFUNDING_SITE_EMAIL_FROM
|
|
} else {
|
|
emailFrom = process.env.CROWDFUNDING_SITE_EMAIL_USER
|
|
console.warn(`No "From" address set! Using ${emailFrom} as a default.`)
|
|
}
|
|
} else {
|
|
console.log("Email has not been configured! Some features won't work properly until this is set up :/")
|
|
}
|
|
|
|
async function sendMail(message) {
|
|
if (DEBUG || !mailer) {
|
|
console.log(`\n\n-------------------------- BEGIN EMAIL --------------------------\n`)
|
|
console.log(`From: ${message.from}\nTo:${message.to}\nSubject:${message.subject||'(no subject)'}\n\n${message.text}`)
|
|
console.log(`\n--------------------------- END EMAIL ---------------------------`)
|
|
return
|
|
} else {
|
|
let receipt = await mailer.sendMail({
|
|
from: message.from || emailFrom,
|
|
to: message.to,
|
|
subject: message.subject,
|
|
text: message.text,
|
|
html: message.html,
|
|
})
|
|
|
|
return receipt
|
|
}
|
|
}
|
|
|
|
// get goal details
|
|
const goalPeople = Number(process.env.CROWDFUNDING_SITE_GOAL_PEOPLE) || 750
|
|
const goalRupees = Number(process.env.CROWDFUNDING_SITE_GOAL_RUPEES) || 500000
|
|
|
|
// set up knex (for database)
|
|
const knex = require('knex')({
|
|
client:'sqlite3',
|
|
connection:{
|
|
filename:'./donors.sqlite'
|
|
},
|
|
useNullAsDefault: true
|
|
})
|
|
|
|
// schema to save pledges
|
|
let pledgeSchema = function(t) {
|
|
t.increments('id').primary()
|
|
t.timestamp('created_at').defaultTo(knex.fn.now()).notNullable()
|
|
t.boolean('was_robot').defaultTo(true)
|
|
|
|
t.integer('amount').notNullable()
|
|
t.boolean('overseas')
|
|
t.string('name', 128)
|
|
t.boolean('anonymous')
|
|
t.string('email', 128)
|
|
t.string('phone', 32)
|
|
t.integer('retry_times').defaultTo(10)
|
|
t.boolean('get_newsletter').defaultTo(false)
|
|
t.text('other_message')
|
|
|
|
t.string('referral_code', 256)
|
|
t.string('referrer', 128)
|
|
}
|
|
|
|
// function to make sure referral columns exist
|
|
// we need this because they were added later in the code, so
|
|
// if there are old databases they'd need to be updated.
|
|
|
|
function ensureReferralColumns(table) {
|
|
knex.schema.hasColumn(table, 'referral_code').then(function(exists) {
|
|
if (!exists) {
|
|
console.debug(`No referral_code column in ${table}! Adding now...`)
|
|
knex.schema.alterTable(table, t => {
|
|
t.string('referral_code', 256)
|
|
}).then(r=> console.debug(`referral_code column added to ${table}:`, r))
|
|
}
|
|
})
|
|
|
|
knex.schema.hasColumn(table, 'referrer').then(function(exists) {
|
|
if (!exists) {
|
|
console.debug(`No referrer column in ${table}! Adding it now...`)
|
|
knex.schema.alterTable(table, t => {
|
|
t.string('referrer', 128)
|
|
}).then(r=> console.debug(`referrer column added to ${table}:`, r))
|
|
}
|
|
})
|
|
}
|
|
|
|
// make sure pledges table exists
|
|
knex.schema.hasTable('pledges').then(function(exists) {
|
|
if (!exists) {
|
|
if (DEBUG) console.debug('No pledge table exists! Creating one now...')
|
|
return knex.schema.createTable('pledges', pledgeSchema)
|
|
} else {
|
|
ensureReferralColumns('pledges')
|
|
}
|
|
})
|
|
|
|
// make sure unverified pledge table exists
|
|
knex.schema.hasTable('unverified_pledges').then(function(exists) {
|
|
if (!exists) {
|
|
if (DEBUG) console.debug('No unverified pledge table exists! Creating one now...')
|
|
return knex.schema.createTable('unverified_pledges', pledgeSchema)
|
|
} else {
|
|
ensureReferralColumns('unverified_pledges')
|
|
}
|
|
})
|
|
|
|
// set up bookshelf (for easy interface for database)
|
|
bookshelf = require('bookshelf')(knex)
|
|
|
|
const Pledge = bookshelf.model('Pledge', {
|
|
tableName: 'pledges',
|
|
})
|
|
|
|
const UnverifiedPledge = bookshelf.model('UnverifiedPledge', {
|
|
tableName: 'unverified_pledges',
|
|
})
|
|
|
|
// decide base url and port for app (can be configured)
|
|
const baseUrl = process.env.CROWDFUNDING_SITE_BASE_URL || '/'
|
|
const port = process.env.CROWDFUNDING_SITE_PORT || 5000
|
|
|
|
// set up twing
|
|
const {TwingEnvironment, TwingLoaderFilesystem} = require('twing')
|
|
let loader = new TwingLoaderFilesystem([
|
|
path.resolve(__dirname, '..', 'src'),
|
|
])
|
|
let twing = new TwingEnvironment(loader)
|
|
|
|
// set up express
|
|
const app = express()
|
|
|
|
const router = express.Router()
|
|
app.use(baseUrl, router)
|
|
|
|
router.use(bodyParser.urlencoded({
|
|
extended: true,
|
|
}))
|
|
|
|
// main views
|
|
|
|
// the home view is used in a couple of places, so we've moved it out
|
|
let homeView = async (req, res) => {
|
|
if (DEBUG) console.debug('Returning home page')
|
|
|
|
// count people
|
|
let { total_people, total_rupees } = (await knex('pledges')
|
|
.sum('amount as total_rupees')
|
|
.count('* as total_people'))[0]
|
|
|
|
|
|
// get latest pledges
|
|
let recentPledges = (
|
|
await Pledge
|
|
.forge()
|
|
.orderBy('created_at', 'DESC')
|
|
.fetchPage({ limit: 5 })
|
|
).models
|
|
|
|
if (DEBUG) console.log(`Listing ${recentPledges.length} pledges`)
|
|
if (DEBUG && req.params.referralCode) console.log(`Referral code: ${req.params.referralCode}`)
|
|
|
|
let referrer
|
|
if (req.params.referralCode) {
|
|
try {
|
|
let pledge = await (Pledge
|
|
.query({
|
|
where: {
|
|
referral_code: req.params.referralCode,
|
|
}
|
|
})
|
|
.fetch())
|
|
|
|
referrer = pledge.get('anonymous') ? 'Anonymous' : pledge.get('name')
|
|
} catch(e) {
|
|
if (DEBUG) console.debug(`Skipping referral ${req.params.referralCode} because not found: ${e}`)
|
|
|
|
// if the referrer doesn't exist, send people to the normal homepage!
|
|
res.redirect('/')
|
|
return
|
|
}
|
|
}
|
|
|
|
|
|
twing.render('index.htm.twig', {
|
|
'goal_rupees': Number(goalRupees).toLocaleString('en-IN'),
|
|
'goal_people': Number(goalPeople).toLocaleString('en-IN'),
|
|
'progress_rupees': Number(total_rupees).toLocaleString('en-IN'),
|
|
'progress_people': Number(total_people).toLocaleString('en-IN'),
|
|
'percent_rupees': `style="width: ${total_rupees/goalRupees*100}%"`,
|
|
'percent_people': `style="width: ${total_people/goalPeople*100}%"`,
|
|
'recent_pledges': recentPledges,
|
|
'referral_code': req.params.referralCode || null, // if it's there
|
|
'referrer': referrer,
|
|
}).then((output) => {
|
|
res.end(output)
|
|
})
|
|
}
|
|
|
|
router.get('/', homeView)
|
|
router.get('/r/:referralCode', homeView)
|
|
|
|
// helpers to generate and decode referrals
|
|
function randomCode() {
|
|
let chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'
|
|
|
|
let code = ''
|
|
|
|
for (let i=0; i<10; i++) {
|
|
code += chars.charAt(Math.floor(Math.random() * chars.length))
|
|
}
|
|
|
|
return code
|
|
}
|
|
async function findReferralCode(email) {
|
|
let referral_code
|
|
|
|
// fetch existing referral code, if exists
|
|
try {
|
|
let pledge = await (Pledge
|
|
.query({
|
|
where: {
|
|
email: email,
|
|
}
|
|
})
|
|
.fetch())
|
|
|
|
referral_code = pledge.get('referral_code')
|
|
if (DEBUG) console.log(`Using existing referral code: ${referral_code}`)
|
|
} catch(e) {
|
|
if (e.message == 'EmptyResponse' && DEBUG) {
|
|
console.log('No matching referrals found')
|
|
}
|
|
}
|
|
|
|
if (!referral_code) {
|
|
// no code found; create a new one and ensure uniqueness
|
|
console.log(`Generating new referral code for: ${email}`)
|
|
async function checkUniqueness(referral_code) {
|
|
let a = await UnverifiedPledge.query({where: {referral_code: referral_code}}).count()
|
|
let b = await Pledge.query({where: {referral_code: referral_code}}).count()
|
|
|
|
return a == 0 && b == 0
|
|
}
|
|
do {
|
|
referral_code = randomCode()
|
|
} while (!checkUniqueness(referral_code))
|
|
}
|
|
|
|
return referral_code
|
|
}
|
|
|
|
async function findReferrer(code) {
|
|
let email
|
|
|
|
// first, check for an entry with this referral code
|
|
try {
|
|
let pledge = await (Pledge
|
|
.query({
|
|
where: {
|
|
referral_code: code,
|
|
}
|
|
})
|
|
.fetch())
|
|
|
|
email = pledge.get('email')
|
|
console.log(`Found: ${email}`)
|
|
} catch(e) {
|
|
if (e.message == 'EmptyResponse' && DEBUG) {
|
|
console.log('No matching referrals found')
|
|
} else {
|
|
console.error(e)
|
|
}
|
|
|
|
// next, check for an entry with this email
|
|
// (maybe it's already been converted from code to email)
|
|
try {
|
|
let pledge = await (Pledge
|
|
.query({
|
|
where: {
|
|
email: code,
|
|
}
|
|
})
|
|
.fetch())
|
|
|
|
email = pledge.get('email')
|
|
console.log('it seems to be')
|
|
} catch(e) {
|
|
if (e.message == 'EmptyResponse' && DEBUG) {
|
|
console.debug('No matching emails found either')
|
|
} else {
|
|
console.error(e)
|
|
}
|
|
|
|
return null
|
|
}
|
|
}
|
|
|
|
return email
|
|
}
|
|
|
|
// function to validate pledges before saving
|
|
async function validatePledge(body, PledgeModel = Pledge) {
|
|
// errors get saved here
|
|
let errors = []
|
|
|
|
let was_robot = body.was_robot
|
|
if(was_robot != 'no') {
|
|
errors.push('Only humans are allowed to donate money. Robots are too digital 🤖')
|
|
}
|
|
|
|
let amount = body.amount
|
|
if (!amount || amount == 'custom') {
|
|
amount = body['amount-custom']
|
|
}
|
|
if (!amount || amount <= 0) {
|
|
errors.push('Pledge amount too small. Please choose at least a rupee!')
|
|
}
|
|
try {
|
|
amount = Number(amount)
|
|
} catch (err) {
|
|
errors.push('Invalid amount. Please choose a positive number!')
|
|
}
|
|
|
|
let name = body.name
|
|
if (name.length <=0) {
|
|
errors.push('What is your name? You can be anonymous to the world but at least we should know...')
|
|
}
|
|
|
|
let anonymous = body.anonymous == 'on' ? true : false
|
|
|
|
let email = body.email
|
|
if (email.length < 5) {
|
|
errors.push('Please enter a valid email address')
|
|
}
|
|
|
|
let phone = body.phone
|
|
|
|
let retry_times
|
|
try {
|
|
retry_times = body.retry_times
|
|
} catch (err) {
|
|
errors.push('Invalid retry count. Please choose a positive number!')
|
|
}
|
|
|
|
let get_newsletter = body.get_newsletter == 'yes' ? true : false
|
|
let overseas = body.overseas == 'yes' ? true : false
|
|
let other_message = body.other_message
|
|
|
|
// manage referrals
|
|
let referral_code = await findReferralCode(email)
|
|
let referrer = null
|
|
|
|
console.log('ref co', body.referral_code)
|
|
if (body.referral_code) {
|
|
try {
|
|
referrer = await findReferrer(body.referral_code)
|
|
} catch (e) {
|
|
console.debug(`Error loading referrer: ${e}`)
|
|
}
|
|
}
|
|
|
|
// enter the info
|
|
let pledge = new PledgeModel() // may be Pledge or UnverifiedPledge
|
|
pledge.set('was_robot', was_robot)
|
|
pledge.set('amount', amount)
|
|
pledge.set('overseas', overseas)
|
|
pledge.set('name', name)
|
|
pledge.set('anonymous', anonymous)
|
|
pledge.set('email', email)
|
|
pledge.set('phone', phone)
|
|
pledge.set('retry_times', retry_times)
|
|
pledge.set('get_newsletter', get_newsletter)
|
|
pledge.set('other_message', other_message)
|
|
|
|
pledge.set('referral_code', referral_code)
|
|
pledge.set('referrer', referrer)
|
|
|
|
// return it all!
|
|
return {
|
|
pledge: pledge,
|
|
errors: errors,
|
|
}
|
|
}
|
|
|
|
router.post('/pledge', async (req, res) => {
|
|
if (DEBUG) console.debug('New pledge:', req.body)
|
|
|
|
// check that the right submit button was pressed
|
|
let submit = req.body.submit
|
|
if (submit != 'Save Pledge') {
|
|
errors.push("This request seems to have been tampered with. Are you sure it wasn't you doing the tampering?")
|
|
}
|
|
|
|
// process the pledge with our handy function
|
|
let {pledge, errors} = await validatePledge(req.body, UnverifiedPledge)
|
|
|
|
// fail if there were errors
|
|
if (!!errors.length) {
|
|
let output = await twing.render('form-errors.htm.twig', {
|
|
errors: errors,
|
|
})
|
|
|
|
res.send(output)
|
|
return
|
|
}
|
|
|
|
// save if there weren't
|
|
if (DEBUG) console.debug (`Saving pledge: ${JSON.stringify(pledge)}`)
|
|
try {
|
|
await pledge.save()
|
|
} catch (err) {
|
|
|
|
let output = await twing.render('error.htm.twig', {
|
|
error: "Sorry, something went wrong while saving your pledge and we don't know what in was. This website is still in beta so we do have a glitches once in a while. Apologies and please try again...🙁",
|
|
})
|
|
|
|
res.send(output)
|
|
return
|
|
}
|
|
|
|
// check for existing pledge
|
|
let existingPledge
|
|
try {
|
|
existingPledge = await (Pledge
|
|
.query({
|
|
where: {
|
|
'email': pledge.get('email'),
|
|
'amount': pledge.get('amount'),
|
|
},
|
|
})
|
|
.orderBy('created_at', 'DESC')
|
|
.fetch())
|
|
} catch(e) {
|
|
if (e.message == 'EmptyResponse' && DEBUG) {
|
|
console.debug('No existing pledge')
|
|
} else {
|
|
console.warn(`Weird error happened: ${e}`)
|
|
}
|
|
}
|
|
|
|
// generate verification link
|
|
let serialiser = makeSerialiser(pledge.get('email'))
|
|
let verificationLink = `${req.protocol}://${req.hostname}/verify?email=${encodeURIComponent(pledge.get('email'))}&key=${encodeURIComponent(serialiser.dumps(pledge.get('amount')))}`
|
|
|
|
// send out the email, along with existing pledge deets
|
|
console.debug(`Verification link generated: ${verificationLink}`)
|
|
|
|
let text = `Hi ${pledge.get('name')},
|
|
Thank you so much for your pledge of ₹${Number(pledge.get('amount')).toLocaleString('en-IN')} to Snipette!
|
|
To finish the pledge, please verify your email by clicking the link below:
|
|
|
|
${verificationLink}
|
|
|
|
If you have any questions, don't hesitate to reach out: you can drop a line
|
|
anytime to editors@snipettemag.com.
|
|
|
|
Thanks,
|
|
The Snipette Team`
|
|
|
|
// to make things snappier, we don't `await` for the sending to finish.
|
|
let receipt = sendMail({
|
|
to: req.body.email,
|
|
subject: `Finish ${existingPledge ? 'updating' : 'saving'} your pledge to Snipette`,
|
|
text: text,
|
|
})
|
|
|
|
// return the success page
|
|
twing.render('pledge.htm.twig', {
|
|
'email': pledge.get('email'),
|
|
}).then((output) => {
|
|
res.send(output)
|
|
})
|
|
})
|
|
|
|
// save pledge after verification complete
|
|
router.get('/verify', async (req, res) => {
|
|
if (DEBUG) console.debug('Validating pledge:', req.query)
|
|
|
|
// unpack verification link (unless it's expired)
|
|
let serialiser = makeSerialiser(req.query.email)
|
|
let amount
|
|
|
|
try {
|
|
amount = serialiser.loads(req.query.key, 300) // number in seconds
|
|
} catch(e) {
|
|
if (e.name == 'SignatureExpired') {
|
|
|
|
let output = await twing.render('error.htm.twig', {
|
|
error: "Sorry, looks like your link has expired. Please go back and try pledging again. Hopefully you'll manage it quicker this time 😛",
|
|
})
|
|
|
|
res.send(output)
|
|
return
|
|
} else {
|
|
|
|
let output = await twing.render('error.htm.twig', {
|
|
error: "An unknown error occurred. Please generate a new link and try again. Sorry for the inconvenience 🤦",
|
|
})
|
|
|
|
res.send(output)
|
|
return
|
|
}
|
|
}
|
|
|
|
// check against database
|
|
let pledge
|
|
try {
|
|
pledge = await (UnverifiedPledge
|
|
.query({
|
|
where: {
|
|
'email': req.query.email,
|
|
'amount': amount,
|
|
},
|
|
})
|
|
.orderBy('created_at', 'DESC')
|
|
.fetch())
|
|
} catch(e) {
|
|
if (e.name == 'EmptyResponse') {
|
|
|
|
let output = await twing.render('error.htm.twig', {
|
|
error: "That pledge was not found in our records. Are you sure you made it? 🔎",
|
|
})
|
|
|
|
res.send(output)
|
|
return
|
|
} else {
|
|
|
|
let output = await twing.render('error.htm.twig', {
|
|
error: "An unknown error occurred. Please generate a new link and try again. Sorry for the inconvenience 🤦",
|
|
})
|
|
|
|
res.send(output)
|
|
return
|
|
}
|
|
}
|
|
|
|
// prepare our new pledge
|
|
let newPledge = new Pledge()
|
|
|
|
newPledge.set('was_robot', pledge.get('was_robot'))
|
|
newPledge.set('amount', pledge.get('amount'))
|
|
newPledge.set('overseas', pledge.get('overseas'))
|
|
newPledge.set('name', pledge.get('name'))
|
|
newPledge.set('anonymous', pledge.get('anonymous'))
|
|
newPledge.set('email', pledge.get('email'))
|
|
newPledge.set('phone', pledge.get('phone'))
|
|
newPledge.set('retry_times', pledge.get('retry_times'))
|
|
newPledge.set('get_newsletter', pledge.get('get_newsletter'))
|
|
newPledge.set('other_message', pledge.get('other_message'))
|
|
|
|
newPledge.set('referral_code', await findReferralCode(pledge.get('email')))
|
|
|
|
// set referrer in priority: old pledge > new pledge
|
|
try {
|
|
let oldPledge = await (Pledge
|
|
.query({
|
|
where: {
|
|
email: req.query.email,
|
|
}
|
|
})
|
|
.fetch())
|
|
|
|
newPledge.set('referrer',
|
|
oldPledge.get('referrer') || pledge.get('referrer') || null)
|
|
} catch(e) {
|
|
if (e.message == 'EmptyResponse' && DEBUG) {
|
|
console.log('No matching referrals found')
|
|
} else {
|
|
console.error(e)
|
|
}
|
|
|
|
newPledge.set('referrer', pledge.get('referrer') || null)
|
|
}
|
|
|
|
console.log(`Saved referrer: ${newPledge.get('referrer')}`)
|
|
|
|
// destroy previous pledges by that user
|
|
try {
|
|
await (Pledge
|
|
.query({
|
|
where: {
|
|
email: req.query.email,
|
|
}
|
|
})
|
|
.destroy())
|
|
} catch(e) {
|
|
if (e.message != 'No Rows Deleted') throw e
|
|
}
|
|
|
|
// save the new pledge
|
|
await newPledge.save()
|
|
|
|
// send the confirmation email
|
|
let referralLink = `${req.protocol}://${req.hostname}/?ref=${newPledge.get('referral_code')}`
|
|
let text = `Hi ${pledge.get('name')},
|
|
Your pledge of ₹${Number(pledge.get('amount')).toLocaleString('en-IN')} to Snipette Analog has been saved!
|
|
|
|
Thank you so much for your support. Once we hit our goals, we will contact
|
|
you to make the final payment. Meanwhile, here are the details of your
|
|
pledge for reference:
|
|
|
|
Name: ${pledge.get('name')}
|
|
Pledge amount: ${Number(pledge.get('amount')).toLocaleString('en-IN')}${pledge.overseas ? ' (plus $25 for shipping)' : ''}
|
|
Email: ${pledge.get('email')}
|
|
Phone: ${pledge.get('phone') || 'not provided'}
|
|
|
|
Extra message:
|
|
${pledge.get('other_message') || '[no message sent]'}
|
|
|
|
We will attempt to contact you ${pledge.get('retry_times') <= 1 ? 'only once' : String(pledge.get('retry_times')) + ' times'} before giving up.
|
|
|
|
You can update your pledge by going to the crowdfunding homepage and saving
|
|
a pledge with the same email address as you used before.
|
|
|
|
If you have any questions, don't hesitate to reach out: you can drop a line
|
|
anytime to editors@snipettemag.com.
|
|
|
|
### Share for Stickers!
|
|
|
|
Money helps, but we need people too—and that's where you can help! Here's your
|
|
unique referral link:
|
|
|
|
${referralLink}
|
|
|
|
Get 5 or more of your friends to sign up using that link, and, once we collect
|
|
the money and start printing, we'll send you a free pack of Snipette stickers!
|
|
|
|
Thanks,
|
|
The Snipette Team`
|
|
|
|
// to make things snappier, we don't `await` for the sending to finish
|
|
let receipt = sendMail({
|
|
to: pledge.get('email'),
|
|
subject: `Your pledge has been saved!`,
|
|
text: text,
|
|
})
|
|
|
|
// Send confirmation message
|
|
res.redirect(`/thanks?ref=${newPledge.get('referral_code')}`)
|
|
})
|
|
|
|
router.get('/thanks', async (req, res) => {
|
|
// validate referral code
|
|
if (!!req.query.ref && !await findReferrer(req.query.ref)) {
|
|
if (DEBUG) console.debug(`Invalid referral: redirecting to normal page`)
|
|
res.redirect('/thanks')
|
|
return
|
|
}
|
|
|
|
let output = await twing.render('thanks.htm.twig', {
|
|
referral_code: req.query.ref,
|
|
})
|
|
|
|
res.send(output)
|
|
return
|
|
})
|
|
|
|
router.get('/pledges', async (req, res) => {
|
|
if (DEBUG) console.debug('Returning pledges list')
|
|
|
|
// count people
|
|
let { total_people, total_rupees } = (await knex('pledges')
|
|
.sum('amount as total_rupees')
|
|
.count('* as total_people'))[0]
|
|
|
|
|
|
// get latest pledges
|
|
let recentPledges = (
|
|
await Pledge
|
|
.forge()
|
|
.orderBy('created_at', 'DESC')
|
|
.fetchAll()
|
|
).models
|
|
|
|
if (DEBUG) console.log(`Listing ${recentPledges.length} pledges`)
|
|
|
|
twing.render('pledges.htm.twig', {
|
|
'recent_pledges': recentPledges,
|
|
}).then((output) => {
|
|
res.send(output)
|
|
})
|
|
})
|
|
|
|
router.get('/back-out', async (req, res) => {
|
|
if (DEBUG) console.debug('Backing out:', req.query)
|
|
|
|
// unpack verification link (unless it's expired)
|
|
let serialiser = makeSerialiser(req.query.email)
|
|
let staySubscribed
|
|
|
|
try {
|
|
// we allow them a month to do this
|
|
staySubscribed = serialiser.loads(req.query.key, 60 * 60 * 24 * 30)
|
|
|
|
// clean up the variable
|
|
if (staySubscribed == 'yes' || staySubscribed == 'true') {
|
|
staySubscribed = true
|
|
} else {
|
|
staySubscribed = false
|
|
}
|
|
} catch(e) {
|
|
if (e.name == 'SignatureExpired') {
|
|
|
|
let output = await twing.render('error.htm.twig', {
|
|
error: "It looks like you've clicked on the link of a very old email! Nothing lasts forever, including verification links, so if you're having trouble please contact <a href='mailto:editors@snipettemag.com'>editors@snipettemag.com</a>. Sorry for the inconvenience 🙁",
|
|
})
|
|
|
|
} else {
|
|
|
|
let output = await twing.render('error.htm.twig', {
|
|
error: "An unknown error occurred, and our robots can't figure out what it is. Please contact <a href='mailto:editors@snipettemag.com'>editors@snipettemag.com</a> for help. Sorry for the inconvenience 🤦",
|
|
})
|
|
|
|
}
|
|
|
|
res.send(output)
|
|
return
|
|
}
|
|
|
|
// now that we've got past that check...
|
|
let pledge
|
|
try {
|
|
pledge = await (Pledge
|
|
.query({
|
|
where: {
|
|
'email': req.query.email,
|
|
},
|
|
})
|
|
.orderBy('created_at', 'DESC')
|
|
.fetch())
|
|
} catch(e) {
|
|
if (e.name == 'EmptyResponse') {
|
|
let output = await twing.render('error.htm.twig', {
|
|
error: "That pledge was not found in our records. Are you sure you made it? 🔎",
|
|
})
|
|
|
|
res.send(output)
|
|
return
|
|
} else {
|
|
|
|
let output = await twing.render('error.htm.twig', {
|
|
error: "An unknown error occurred, and we can't figure out what it is. Please contact <a href='mailto:editors@snipettemag.com'>editors@snipettemag.com</a> for help (we're guessing you're trying to unsubscribe, in which case we'll handle it immediately upon receiving your email). Sorry for the inconvenience 🤦",
|
|
})
|
|
|
|
res.send(output)
|
|
return
|
|
}
|
|
}
|
|
|
|
// update settings for our new pledge
|
|
pledge.set('backed_out', true)
|
|
pledge.set('unsubscribed', !staySubscribed)
|
|
|
|
// save the new pledge
|
|
|
|
await pledge.save()
|
|
|
|
// send the confirmation email
|
|
let text = `Hi ${pledge.get('name')},
|
|
We have received your back-out request for your pledge of ${Number(pledge.get('amount')).toLocaleString('en-IN')}${pledge.get('overseas') ? ' (plus $25 for shipping)' : ''}. We have removed you from our list and you will receive no further emails regarding collecting your contribution.${staySubscribed ? ' However, you will still receive general crowdfunding updates and are welcome to join back again later.' : ''}
|
|
|
|
If you think this was a mistake, please reply to this email and one of us will get back to you.
|
|
|
|
We realise that some people may want to continue supporting Snipette even if it doesn't make sense for them to go with the original pledge. You are always welcome to subscribe directly at: https://snipettemag.com/subscribe/
|
|
|
|
Thank you for your support so far, and apologies for the fact that it didn't work out quite as expected this time.
|
|
|
|
Thanks,
|
|
The Snipette Team`
|
|
|
|
// to make things snappier, we won't `await` for the sending to finish
|
|
let receipt = sendMail({
|
|
to: pledge.get('email'),
|
|
subject: 'Pledge withdrawn from Snipette crowdfunding',
|
|
text: text,
|
|
})
|
|
|
|
// Send confirmation message
|
|
res.redirect('/bye')
|
|
})
|
|
|
|
router.get('/bye', async (req, res) => {
|
|
|
|
let output = await twing.render('bye.htm.twig', {})
|
|
|
|
res.send(output)
|
|
return
|
|
})
|
|
|
|
router.use(express.static('src/assets'))
|
|
|
|
// start the listener!
|
|
if (!module.parent) {
|
|
app.listen(port, () => {
|
|
console.log(`Server is up at port ${port}`)
|
|
})
|
|
}
|
|
|
|
// end note: in case we want to import this somewhere for testing
|
|
module.exports = {
|
|
knex,
|
|
bookshelf,
|
|
Pledge,
|
|
UnverifiedPledge,
|
|
router,
|
|
makeSerialiser,
|
|
}
|