Skip to content
This repository has been archived by the owner on Feb 8, 2018. It is now read-only.

Commit

Permalink
Merge pull request #2752 from gratipay/email-step-1
Browse files Browse the repository at this point in the history
Email - Part 1
  • Loading branch information
chadwhitacre committed Dec 9, 2014
2 parents 8e56914 + 835de34 commit ba26bbe
Show file tree
Hide file tree
Showing 30 changed files with 936 additions and 225 deletions.
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,16 +223,23 @@ The site works without this, except for the credit card page. Visit the
[Balanced Documentation](https://www.balancedpayments.com/docs) if you want to
know more about creating marketplaces.

The GITHUB_* keys are for a gratipay-dev application in the Gratipay organization
on Github. It points back to localhost:8537, which is where Gratipay will be
running if you start it locally with `make run`. Similarly with the TWITTER_*
keys, but there they required us to spell it `127.0.0.1`.
The `GITHUB_*` keys are for a gratipay-dev application in the Gratipay
organization on Github. It points back to localhost:8537, which is where
Gratipay will be running if you start it locally with `make run`. Similarly
with the `TWITTER_*` keys, but there they required us to spell it `127.0.0.1`.

If you wish to use a different username or database name for the database, you
should override the `DATABASE_URL` in `local.env` using the following format:

DATABASE_URL=postgres://<username>@localhost/<database name>

The `MANDRILL_KEY` value in `defaults.env` is for a test mail server, which
won't actually send email to you. If you need to receive email during
development then sign up for an account of your own at
[Mandrill](http://mandrill.com/) and override `MANDRILL_KEY` in your
`local.env`.


Vagrant
-------
If you have Vagrant installed, you can run Gratipay by running `vagrant up` from the project directory. Please note that if you ever switch between running Gratipay on your own machine to Vagrant or vice versa, you will need to run `make clean`.
Expand Down
63 changes: 63 additions & 0 deletions branch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""
This is a one-off script to populate the new `emails` table using the addresses
we have in `participants`.
"""
from __future__ import division, print_function, unicode_literals

from urllib import quote
import uuid

import gratipay
from aspen.utils import utcnow
from postgres.orm import Model

import gratipay.wireup

env = gratipay.wireup.env()
tell_sentry = gratipay.wireup.make_sentry_teller(env)
db = gratipay.wireup.db(env)
gratipay.wireup.mail(env)
gratipay.wireup.load_i18n('.', tell_sentry)
gratipay.wireup.canonical(env)


class EmailAddressWithConfirmation(Model):
typname = "email_address_with_confirmation"

db.register_model(EmailAddressWithConfirmation)


def add_email(self, email):
nonce = str(uuid.uuid4())
verification_start = utcnow()

scheme = gratipay.canonical_scheme
host = gratipay.canonical_host
username = self.username_lower
quoted_email = quote(email)
link = "{scheme}://{host}/{username}/emails/verify.html?email={quoted_email}&nonce={nonce}"
self.send_email('initial',
email=email,
link=link.format(**locals()),
username=self.username,
include_unsubscribe=False)

db.run("""
INSERT INTO emails
(address, nonce, verification_start, participant)
VALUES (%s, %s, %s, %s)
""", (email, nonce, verification_start, self.username))


participants = db.all("""
SELECT p.*::participants
FROM participants p
WHERE email IS NOT NULL
AND NOT is_closed
AND is_suspicious IS NOT true
AND claimed_time IS NOT NULL;
""")
total = len(participants)
for i, p in enumerate(participants, 1):
print('sending email to %s (%i/%i)' % (p.username, i, total))
add_email(p, p.email.address)
46 changes: 46 additions & 0 deletions branch.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
BEGIN;
CREATE TABLE emails
( id serial PRIMARY KEY
, address text NOT NULL
, verified boolean DEFAULT NULL
CONSTRAINT verified_cant_be_false
-- Only use TRUE and NULL, so that the
-- unique constraint below functions
-- properly.
CHECK (verified IS NOT FALSE)
, nonce text
, verification_start timestamp with time zone NOT NULL
DEFAULT CURRENT_TIMESTAMP
, verification_end timestamp with time zone
, participant text NOT NULL
REFERENCES participants
ON UPDATE CASCADE
ON DELETE RESTRICT

, UNIQUE (address, verified) -- A verified email address can't be linked to multiple
-- participants. However, an *un*verified address *can*
-- be linked to multiple participants. We implement this
-- by using NULL instead of FALSE for the unverified
-- state, hence the check constraint on verified.
, UNIQUE (participant, address)
);

-- The participants table currently has an `email` attribute of type
-- email_address_with confirmation. This should be deleted in the future,
-- once the emails are migrated. The column we're going to replace it with
-- is named `email_address`. This is only for **verified** emails. All
-- unverified email stuff happens in the emails table and won't touch this
-- attribute.

ALTER TABLE participants ADD COLUMN email_address text UNIQUE,
ADD COLUMN email_lang text;

UPDATE events
SET payload = replace(replace( payload::text, '"set"', '"add"')
, '"current_email"'
, '"email"'
)::json
WHERE payload->>'action' = 'set'
AND (payload->'values'->'current_email') IS NOT NULL;

END;
10 changes: 6 additions & 4 deletions configure-aspen.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import division

from decimal import Decimal as D
import base64
import threading
import time
import traceback
Expand Down Expand Up @@ -55,19 +56,20 @@ def _set_cookie(response, *args, **kw):
'len': len,
'float': float,
'type': type,
'str': str
'str': str,
'b64encode': base64.b64encode
}


env = website.env = gratipay.wireup.env()
tell_sentry = website.tell_sentry = gratipay.wireup.make_sentry_teller(env)
gratipay.wireup.canonical(env)
website.db = gratipay.wireup.db(env)
website.mail = gratipay.wireup.mail(env)
website.mailer = gratipay.wireup.mail(env, website.project_root)
gratipay.wireup.billing(env)
gratipay.wireup.username_restrictions(website)
gratipay.wireup.nanswers(env)
gratipay.wireup.load_i18n(website)
gratipay.wireup.load_i18n(website.project_root, tell_sentry)
gratipay.wireup.other_stuff(website, env)
gratipay.wireup.accounts_elsewhere(website, env)

Expand Down Expand Up @@ -158,7 +160,7 @@ def add_stuff_to_context(request):
, authentication.get_auth_from_request
, csrf.get_csrf_token_from_request
, add_stuff_to_context
, i18n.add_helpers_to_context
, i18n.set_up_i18n

, algorithm['dispatch_request_to_filesystem']

Expand Down
2 changes: 1 addition & 1 deletion defaults.env
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,6 @@ ASPEN_WWW_ROOT=www/
# https://github.com/benoitc/gunicorn/issues/186
GUNICORN_OPTS="--workers=1 --timeout=99999999"

MANDRILL_KEY=
MANDRILL_KEY=Phh_Lm3RdPT5blqOPY4dVQ

RAISE_CARD_EXPIRATION=no
29 changes: 29 additions & 0 deletions emails/base.spt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[---] text/html
<div style="text-align: center; padding: 20px 0; margin: 0;">
<img src="https://downloads.gratipay.com/email/gratipay.png" alt="Gratipay">
</div>

<div style="text-align: center; font: normal 14px/21px Arial, sans-serif; color: #333; padding: 0 20%;">
$body
</div>

<div style="text-align: center; color: #999; padding: 21px 0 0;">
<div style="font: normal 14px/21px Arial, sans-serif;">
{{ _("Something not right? Reply to this email for help.") }}
</div>
<div style="font: normal 10px/21px Arial, sans-serif;">
Sent by <a href="https://gratipay.com/" style="color: #999; text-decoration: underline;">Gratipay, LLC</a> | 716 Park Road, Ambridge, PA, 15003, USA
</div>
</div>

[---] text/plain
{{ _("Greetings, program!") }}

$body

{{ _("Something not right? Reply to this email for help.") }}

----

Sent by Gratipay, LLC, https://gratipay.com/
716 Park Road, Ambridge, PA, 15003, USA
21 changes: 21 additions & 0 deletions emails/initial.spt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{{ _("Connect to {0} on Gratipay?", username) }}

[---] text/html
A while ago we received a request to connect <b>{{ escape(email) }}</b> to the
{{ '<b><a href="https://gratipay.com/{0}/">{0}</a></b>'.format(username) }}
account on Gratipay (<a
href="https://medium.com/gratipay-blog/gratitude-gratipay-ef24ad5e41f9">formerly</a>
Gittip). Now we're finally sending a verification email! Ring a bell?
<br>
<br>
<a href="{{ link }}" style="color: #fff; text-decoration:none; display:inline-block; padding: 0 15px; background: #396; font: normal 14px/40px Arial, sans-serif; white-space: nowrap; border-radius: 3px">Yes, proceed!</a>

[---] text/plain

A while ago we received a request to connect `{{ email }}`
to the `{{ username }}` account on Gratipay (formerly Gittip).
Now we're finally sending a verification email!

Ring a bell? Follow this link to finish connecting your email:

{{ link }}
17 changes: 17 additions & 0 deletions emails/verification.spt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{{ _("Connect to {0} on Gratipay?", username) }}

[---] text/html
{{ _("We've received a request to connect {0} to the {1} account on Gratipay. Sound familiar?",
'<b>%s</b>' % escape(email),
'<b><a href="https://gratipay.com/{0}">{0}</a></b>'.format(username)) }}
<br>
<br>
<a href="{{ link }}" style="color: #fff; text-decoration:none; display:inline-block; padding: 0 15px; background: #396; font: normal 14px/40px Arial, sans-serif; white-space: nowrap; border-radius: 3px">{{ _("Yes, proceed!") }}</a>

[---] text/plain
{{ _("We've received a request to connect {0} to the {1} account on Gratipay. Sound familiar?",
email, username) }}

{{ _("Follow this link to finish connecting your email:") }}

{{ link }}
13 changes: 13 additions & 0 deletions emails/verification_notice.spt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{{ _("Connecting {0} to {1} on Gratipay.", new_email, username) }}

[---] text/html
{{ _("We are connecting {0} to the {1} account on Gratipay. This is a notification "
"sent to {2} because that is the primary email address we have on file.",
'<b>%s</b>' % escape(new_email),
'<b><a href="https://gratipay.com/{0}">{0}</a></b>'.format(username),
'<b>%s</b>' % escape(email)) }}

[---] text/plain
{{ _("We are connecting {0} to the {1} account on Gratipay. This is a notification "
"sent to {2} because that is the primary email address we have on file.",
new_email, username, email) }}
19 changes: 19 additions & 0 deletions gratipay/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

from __future__ import print_function, unicode_literals

from aspen import Response


class ProblemChangingUsername(Exception):
def __str__(self):
Expand All @@ -25,6 +27,23 @@ class UsernameAlreadyTaken(ProblemChangingUsername):
msg = "The username '{}' is already taken."


class ProblemChangingEmail(Response):
def __init__(self, *args):
Response.__init__(self, 400, self.msg.format(*args))

class EmailAlreadyTaken(ProblemChangingEmail):
msg = "{} is already connected to a different Gratipay account."

class CannotRemovePrimaryEmail(ProblemChangingEmail):
msg = "You cannot remove your primary email address."

class EmailNotVerified(ProblemChangingEmail):
msg = "The email address '{}' is not verified."

class TooManyEmailAddresses(ProblemChangingEmail):
msg = "You've reached the maximum number of email addresses we allow."


class ProblemChangingNumber(Exception):
def __str__(self):
return self.msg
Expand Down
6 changes: 0 additions & 6 deletions gratipay/models/email_address_with_confirmation.py

This file was deleted.

Loading

0 comments on commit ba26bbe

Please sign in to comment.