-
Notifications
You must be signed in to change notification settings - Fork 308
Link venmo #1857
Link venmo #1857
Changes from 26 commits
ba597f6
ff9ab95
dcb34fd
f167db9
c3f780b
880663d
8df9b9c
2d6bc4b
3ed0f6e
6359aea
fbdb2ac
e475555
2a677c6
32b7d70
2d7ee98
277ca54
b763a21
ff7298e
bcdaa14
8f5915f
c0029ac
427ae5e
cac31ca
2897cad
30988e8
5262b2c
257647a
8a8cffa
9780f0b
1d2ce2f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
ALTER TABLE elsewhere ADD COLUMN access_token text DEFAULT NULL; | ||
ALTER TABLE elsewhere ADD COLUMN refresh_token text DEFAULT NULL; | ||
ALTER TABLE elsewhere ADD COLUMN expires timestamp with time zone DEFAULT NULL; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
from __future__ import division | ||
|
||
from importlib import import_module | ||
import os | ||
import threading | ||
import time | ||
|
@@ -10,6 +11,9 @@ | |
from gittip import canonize, configure_payments | ||
from gittip.security import authentication, csrf, x_frame_options | ||
from gittip.utils import cache_static, timer | ||
from gittip.elsewhere import platforms_ordered | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is unused in this file. |
||
from gittip.elsewhere import platform_classes | ||
|
||
|
||
from aspen import log_dammit | ||
|
||
|
@@ -44,6 +48,11 @@ | |
gittip.wireup.envvars(website) | ||
tell_sentry = gittip.wireup.make_sentry_teller(website) | ||
|
||
# this serves two purposes: | ||
# 1) ensure all platform classes are created (and thus added to platform_classes) | ||
# 2) keep the platform modules around to be added to the context below | ||
platform_modules = {platform: import_module("gittip.elsewhere.%s" % platform) | ||
for platform in platform_classes} | ||
|
||
# The homepage wants expensive queries. Let's periodically select into an | ||
# intermediate table. | ||
|
@@ -117,14 +126,8 @@ def log_busy_threads(): | |
# ================= | ||
|
||
def add_stuff_to_context(request): | ||
from gittip.elsewhere import bitbucket, github, twitter, bountysource, openstreetmap | ||
request.context['username'] = None | ||
request.context['bitbucket'] = bitbucket | ||
request.context['github'] = github | ||
request.context['twitter'] = twitter | ||
request.context['bountysource'] = bountysource | ||
request.context['openstreetmap'] = openstreetmap | ||
|
||
request.context.update(platform_modules) | ||
|
||
algorithm = website.algorithm | ||
algorithm.functions = [ timer.start | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,38 +1,53 @@ | ||
DATABASE_URL=postgres://gittip:gittip@localhost:5432/gittip | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. *.env reorg looks good, thanks. :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One thing to note, swaddle now displays 11 warnings in a row about skipping blank lines. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, we must have missed that because we didn't regenerate our environments. Would running after a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't sweat it, we're porting away from swaddle (#468). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unless @clone1018 wants to sweat it, and add line breaks under #468. :-) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Much lazy. Lack effort. |
||
|
||
PYTHONDONTWRITEBYTECODE=true | ||
CANONICAL_HOST=localhost:8537 | ||
CANONICAL_SCHEME=http | ||
MIN_THREADS=10 | ||
DATABASE_URL=postgres://gittip:gittip@localhost:5432/gittip | ||
DATABASE_MAXCONN=10 | ||
|
||
GITTIP_CSS_HREF=/assets/-/gittip.css | ||
GITTIP_JS_SRC=/assets/-/gittip.js | ||
GITTIP_CACHE_STATIC=no | ||
GITTIP_COMPRESS_ASSETS=no | ||
|
||
STRIPE_SECRET_API_KEY=1 | ||
STRIPE_PUBLISHABLE_API_KEY=1 | ||
|
||
BALANCED_API_SECRET=90bb3648ca0a11e1a977026ba7e239a9 | ||
|
||
GITHUB_CLIENT_ID=3785a9ac30df99feeef5 | ||
GITHUB_CLIENT_SECRET=e69825fafa163a0b0b6d2424c107a49333d46985 | ||
GITHUB_CALLBACK=http://127.0.0.1:8537/on/github/associate | ||
|
||
BITBUCKET_CONSUMER_KEY=b8yzpsurhsmJufUqzh | ||
BITBUCKET_CONSUMER_SECRET=WF3q2g7naRHeeGUjnxyRwPLVhMBU4dmP | ||
BITBUCKET_CALLBACK=http://127.0.0.1:8537/on/bitbucket/associate | ||
|
||
TWITTER_CONSUMER_KEY=QBB9vEhxO4DFiieRF68zTA | ||
TWITTER_CONSUMER_SECRET=mUymh1hVMiQdMQbduQFYRi79EYYVeOZGrhj27H59H78 | ||
TWITTER_ACCESS_TOKEN=34175404-G6W8Hh19GWuUhIMEXK0LyZsy7N9aCMcy1bYJ9rI | ||
TWITTER_ACCESS_TOKEN_SECRET=K6wxV1OCsihZAkEPkWtoLYDiRJnWajBBWn4UgliTRQ | ||
TWITTER_CALLBACK=http://127.0.0.1:8537/on/twitter/associate | ||
NANSWERS_THRESHOLD=2 | ||
NMEMBERS_THRESHOLD=50 | ||
UPDATE_HOMEPAGE_EVERY=10 | ||
|
||
BOUNTYSOURCE_API_SECRET=e2BbqjNY60kC7V-Uq1dv2oHgGavbWm9pUJmiRHCApFZHDiY9aZyAspInhZaZ94x9 | ||
BOUNTYSOURCE_API_HOST=https://staging-qa.bountysource.com | ||
BOUNTYSOURCE_WWW_HOST=https://staging.bountysource.com | ||
BOUNTYSOURCE_CALLBACK=http://127.0.0.1:8537/on/bountysource/associate | ||
|
||
VENMO_CLIENT_ID=1534 | ||
VENMO_CLIENT_SECRET=55ckgsguYC3cj7xWW5c95PHvUzrwgZMA | ||
VENMO_CALLBACK=http://127.0.0.1:8537/on/venmo/associate | ||
|
||
OPENSTREETMAP_API=http://master.apis.dev.openstreetmap.org | ||
OPENSTREETMAP_CONSUMER_KEY=J2SS5GM0A7tM1CIBjAHXUTMeCEkRBMYsTJzGONxe | ||
OPENSTREETMAP_CONSUMER_SECRET=hgvZkbtWVOEoaJV5AzQPcBI9m8f7BylkpT0cP7wS | ||
OPENSTREETMAP_CALLBACK=http://127.0.0.1:8537/on/openstreetmap/associate | ||
|
||
NANSWERS_THRESHOLD=2 | ||
NMEMBERS_THRESHOLD=50 | ||
|
||
UPDATE_HOMEPAGE_EVERY=10 | ||
GOOGLE_ANALYTICS_ID= | ||
SENTRY_DSN= | ||
LOG_BUSY_THREADS_EVERY=0 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,25 +1,81 @@ | ||
"""This subpackage contains functionality for working with accounts elsewhere. | ||
""" | ||
from __future__ import print_function, unicode_literals | ||
from collections import OrderedDict | ||
|
||
from aspen.utils import typecheck | ||
from aspen import json | ||
from psycopg2 import IntegrityError | ||
|
||
import gittip | ||
from gittip.exceptions import ProblemChangingUsername | ||
from gittip.security.user import User | ||
from gittip.models.participant import Participant, reserve_a_random_username | ||
from gittip.exceptions import ProblemChangingUsername, UnknownPlatform | ||
from gittip.utils.username import reserve_a_random_username | ||
|
||
|
||
ACTIONS = [u'opt-in', u'connect', u'lock', u'unlock'] | ||
|
||
|
||
# to add a new elsewhere/platform: | ||
# 1) add its name (also the name of its module) to this list. | ||
# it's best to append it; this ordering is used in templates. | ||
# 2) inherit from AccountElsewhere in the platform class | ||
# | ||
# platform_modules will populate the platform class automatically in configure-aspen. | ||
platforms_ordered = ( | ||
'twitter', | ||
'github', | ||
'bitbucket', | ||
'bountysource', | ||
'venmo', | ||
'openstreetmap' | ||
) | ||
|
||
# init-time key setup ensures the future ordering of platform_classes will match | ||
# platforms_ordered, since overwriting entries will maintain their order. | ||
platform_classes = OrderedDict([(platform, None) for platform in platforms_ordered]) | ||
|
||
|
||
class _RegisterPlatformMeta(type): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This reminds me of something I was doing on the elsewhere refactor branch. Does this relate to that? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is what I was thinking of: There I'm using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup, it accomplishes the same thing as the PlatformRegistry, but doesn't require the manual call to register. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, I misunderstood. Yeah, I think hooking |
||
"""Tied to AccountElsewhere to enable registration by the platform field. | ||
""" | ||
|
||
def __new__(cls, name, bases, dct): | ||
c = super(_RegisterPlatformMeta, cls).__new__(cls, name, bases, dct) | ||
|
||
# * register the platform | ||
# * verify it was added at init-time | ||
# * register the subclass's json encoder with aspen | ||
c_platform = getattr(c, 'platform') | ||
if name == 'AccountElsewhere': | ||
pass | ||
elif c_platform not in platform_classes: | ||
raise UnknownPlatform(c_platform) # has it been added to platform_classes init? | ||
else: | ||
platform_classes[c_platform] = c | ||
|
||
# aspen's json encoder registry does not take class hierarchies into account, | ||
# so we need to register the subclasses explicitly. | ||
json.register_encoder(c, c.to_json_compatible_object) | ||
|
||
return c | ||
|
||
class AccountElsewhere(object): | ||
|
||
__metaclass__ = _RegisterPlatformMeta | ||
|
||
platform = None # set in subclass | ||
|
||
def __init__(self, db, user_id, user_info=None): | ||
"""Takes a user_id and user_info, and updates the database. | ||
# only fields in this set will be encoded | ||
json_encode_field_whitelist = set([ | ||
'id', 'is_locked', 'participant', 'platform', 'user_id', 'user_info', | ||
]) | ||
|
||
def __init__(self, db, user_id, user_info=None, existing_record=None): | ||
"""Either: | ||
- Takes a user_id and user_info, and updates the database. | ||
|
||
Or: | ||
- Takes a user_id and existing_record, and constructs a "model" object out of the record | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup, that's exactly what we were going for. We did it this way to avoid changing all the existing callsites, though hydrating with |
||
""" | ||
typecheck(user_id, (int, unicode), user_info, (None, dict)) | ||
self.user_id = unicode(user_id) | ||
|
@@ -33,10 +89,26 @@ def __init__(self, db, user_id, user_info=None): | |
self.is_locked = c | ||
self.balance = d | ||
|
||
self.user_info = user_info | ||
|
||
# hack to make this into a weird pseudo-model that can share convenience methods | ||
elif existing_record is not None: | ||
self.participant = existing_record.participant | ||
self.is_claimed, self.is_locked, self.balance = self.get_misc_info(self.participant) | ||
self.user_info = existing_record.user_info | ||
self.record = existing_record | ||
|
||
def to_json_compatible_object(self): | ||
""" | ||
This is registered as an aspen.json encoder in configure-aspen | ||
for all subclasses of this class. | ||
|
||
def get_participant(self): | ||
return Participant.query.get(username=self.participant) | ||
It only exports fields in the whitelist. | ||
""" | ||
output = {k: v for (k,v) in self.record._asdict().items() | ||
if k in self.json_encode_field_whitelist} | ||
|
||
return output | ||
|
||
def set_is_locked(self, is_locked): | ||
self.db.run(""" | ||
|
@@ -51,6 +123,8 @@ def set_is_locked(self, is_locked): | |
def opt_in(self, desired_username): | ||
"""Given a desired username, return a User object. | ||
""" | ||
from gittip.security.user import User | ||
|
||
self.set_is_locked(False) | ||
user = User.from_username(self.participant) | ||
user.sign_in() | ||
|
@@ -128,10 +202,9 @@ def upsert(self, user_info): | |
|
||
""", (user_info, self.platform, self.user_id)) | ||
|
||
return (username,) + self.get_misc_info(username) | ||
|
||
# Get a little more info to return. | ||
# ================================= | ||
|
||
def get_misc_info(self, username): | ||
rec = self.db.one(""" | ||
|
||
SELECT claimed_time, balance, is_locked | ||
|
@@ -145,9 +218,19 @@ def upsert(self, user_info): | |
|
||
assert rec is not None # sanity check | ||
|
||
|
||
return ( username | ||
, rec.claimed_time is not None | ||
return ( rec.claimed_time is not None | ||
, rec.is_locked | ||
, rec.balance | ||
) | ||
|
||
def set_oauth_tokens(self, access_token, refresh_token, expires): | ||
""" | ||
Updates the elsewhere row with the given access token, refresh token, and Python datetime | ||
""" | ||
|
||
self.db.run(""" | ||
UPDATE elsewhere | ||
SET (access_token, refresh_token, expires) | ||
= (%s, %s, %s) | ||
WHERE platform=%s AND user_id=%s | ||
""", (access_token, refresh_token, expires, self.platform, self.user_id)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's wrap these in a single BEGIN/END transaction block (see schema.sql for reference).