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

Adding initial support for email addresses. #2303

Merged
merged 4 commits into from
Apr 21, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions branch.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
BEGIN;
CREATE TYPE email_address_with_confirmation AS
(
address text,
confirmed boolean
);

ALTER TABLE participants ADD email email_address_with_confirmation
DEFAULT NULL;

CREATE TABLE emails
( id serial PRIMARY KEY
, email email_address_with_confirmation NOT NULL
, ctime timestamp with time zone NOT NULL
DEFAULT CURRENT_TIMESTAMP
, participant text NOT NULL
REFERENCES participants
ON UPDATE CASCADE
ON DELETE RESTRICT
);


CREATE RULE log_email_changes AS ON UPDATE
TO participants WHERE (OLD.email IS NULL AND NOT NEW.email IS NULL)
OR (NEW.email IS NULL AND NOT OLD.email IS NULL)
OR NEW.email <> OLD.email
DO INSERT INTO emails (email, participant)
VALUES (NEW.email, OLD.username);

END;
6 changes: 6 additions & 0 deletions gittip/models/email_address_with_confirmation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from postgres.orm import Model


class EmailAddressWithConfirmation(Model):

typname = "email_address_with_confirmation"
7 changes: 7 additions & 0 deletions gittip/models/participant.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,13 @@ def change_username(self, suggested):

return suggested

def update_email(self, email, confirmed=False):
with self.db.get_cursor() as c:
add_event(c, 'participant', dict(id=self.id, action='set', values=dict(current_email=email)))
c.one("UPDATE participants SET email = ROW(%s, %s) WHERE username=%s RETURNING id"
, (email, confirmed, self.username)
)
self.set_attributes(email=(email, confirmed))

def update_goal(self, goal):
typecheck(goal, (Decimal, None))
Expand Down
2 changes: 2 additions & 0 deletions gittip/wireup.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from gittip.models.account_elsewhere import AccountElsewhere
from gittip.models.community import Community
from gittip.models.participant import Participant
from gittip.models.email_address_with_confirmation import EmailAddressWithConfirmation
from gittip.models import GittipDB


Expand All @@ -35,6 +36,7 @@ def db(env):
db.register_model(Community)
db.register_model(AccountElsewhere)
db.register_model(Participant)
db.register_model(EmailAddressWithConfirmation)

return db

Expand Down
53 changes: 53 additions & 0 deletions js/gittip/profile.js
Original file line number Diff line number Diff line change
Expand Up @@ -356,4 +356,57 @@ Gittip.profile.init = function() {

return false;
});

// Wire up email address input.
// ============================
$('.email').on("click", ".toggle-email", function()
{
$('.email').toggle();
$('input.email').focus();
});

// Wire up email form.
$('.email-submit')
.on('click', '[type=submit]', function () {
var $this = $(this);

$this.text('Saving...');

function success(data) {
$('.email-address').text(data.email);
$('.email').toggle();
if (data.email === '') {
html = "<span class=\"none\">None</span>"
html += "<button class=\"toggle-email\">+ Add</button>";
} else {
html = "<a class=\"email-address\" href=\"javascript:;\">" + data.email + "</a>";
html += "<button class=\"toggle-email\">Edit</button>";
}
$('div.email').html(html);
$this.text('Save');
}

$.ajax({
url: "email.json",
type: "POST",
dataType: 'json',
success: success,
error: function (data) {
$this.text('Save');
Gittip.notification('Failed to save your email address. '
+ 'Please try again.', 'error');
},
data: {
email: $('input.email').val()
}
}
)

return false;
})
.on('click', '[type=cancel]', function () {
$('.email').toggle();

return false;
});
};
2 changes: 1 addition & 1 deletion scss/modules.scss
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ a.mini-user:hover {
float: right;
margin-top: -13px;
}
.auth-button, .account-delete, .toggle-bitcoin {
.auth-button, .account-delete, .toggle-bitcoin, .toggle-email {
float: right;
margin-top: -13px;
}
Expand Down
2 changes: 1 addition & 1 deletion scss/profile-edit.scss
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
}
}
}
.bitcoin-submit {
.email-submit, .bitcoin-submit {
.address input {
width: 100%;
margin: 5px 0;
Expand Down
42 changes: 42 additions & 0 deletions templates/connected-accounts.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,46 @@ <h2>Social Profiles</h2>
{{ account_row(platform, accounts, auth_button) }}
{% endif %}
{% endfor %}

{% if user.participant == participant or user.ADMIN %}
<tr>
<td class="account-type">
<img src="{{ website.asset_url }}/email.png" /><!-- This needs to change -->
</td>
<td class="account-details">
<div class="account-type">Email Address (Private)</div>
{% if not user.ANON and user.participant == participant %}
<div class="email">
{% else %}
<div>
{% endif %}
{% if participant.email %}
<a class="address email-address" href="javascript:;">
{{ participant.email.address }}
</a>
<button class="toggle-email">Edit</button>
{% else %}
<span class="none">None</span>
<button class="toggle-email">+ Add</button>
{% endif %}
</div>

<form class="email-submit">
<div class="address">
<input type="email" class="email hidden"
{% if participant.email %}
value="{{ participant.email.address }}"
{% endif %}
>
</div>
<div class="buttons">
<button type="submit" class="email hidden">Save</button>
<button type="cancel" class="email cancel hidden">Cancel</button>
</div>
</form>
</td>
</tr>
{% endif %}
</table>

{% if not user.ANON and user.participant == participant %}
Expand Down Expand Up @@ -104,3 +144,5 @@ <h2>Moving Money Every Week</h2>
</td>
</tr>
</table>


39 changes: 39 additions & 0 deletions tests/py/test_email_json.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from __future__ import unicode_literals

import json

from gittip.testing import Harness

class TestMembernameJson(Harness):

def change_email_address(self, address, user='alice', should_fail=True):
self.make_participant("alice")

if should_fail:
response = self.client.PxST("/alice/email.json"
, {'email': address,}
, auth_as=user
)
else:
response = self.client.POST("/alice/email.json"
, {'email': address,}
, auth_as=user
)
return response

def test_participant_can_change_email(self):
response = self.change_email_address('[email protected]', should_fail=False)
actual = json.loads(response.body)['email']
assert actual == '[email protected]', actual

def test_post_anon_returns_404(self):
response = self.change_email_address('[email protected]', user=None)
assert response.code == 404, response.code

def test_post_with_no_at_symbol_is_400(self):
response = self.change_email_address('gittip.com')
assert response.code == 400, response.code

def test_post_with_no_period_symbol_is_400(self):
response = self.change_email_address('test@gittip')
assert response.code == 400, response.code
11 changes: 11 additions & 0 deletions tests/py/test_participant.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,17 @@ def test_john_is_plural(self):
actual = Participant.from_username('john').IS_PLURAL
assert actual == expected

def test_can_change_email(self):
Participant.from_username('alice').update_email('[email protected]')
expected = '[email protected]'
actual = Participant.from_username('alice').email.address
assert actual == expected

def test_can_confirm_email(self):
Participant.from_username('alice').update_email('[email protected]', True)
actual = Participant.from_username('alice').email.confirmed
assert actual == True

def test_cant_take_over_claimed_participant_without_confirmation(self):
bob_twitter = self.make_elsewhere('twitter', '2', 'bob')
with self.assertRaises(NeedConfirmation):
Expand Down
26 changes: 26 additions & 0 deletions www/%username/email.json.spt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""
Change the currently authenticated user's email address.
This will need to send a confirmation email in the future.
"""
import json
import re

from aspen import Response

[-----------------------------------------]

if user.ANON:
raise Response(404)
request.allow("POST")

address = request.body['email']

# This checks for exactly one @ and at least one . after @
# The real validation will happen when we send the email
if not re.match(r"[^@]+@[^@]+\.[^@]+", address):
raise Response(400)
else:
# Woohoo! valid request, store it!
user.participant.update_email(address)

response.body = {'email': address}
Binary file added www/assets/%version/email.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.