diff --git a/init.sh b/init.sh index edeb122..4cb6065 100755 --- a/init.sh +++ b/init.sh @@ -7,7 +7,7 @@ install(){ } start(){ - sandbox/bin/python src/api/index.py --path $PWD/config.json --port 8084 --context store & > log + sandbox/bin/python src/api/index.py --path $PWD/config.json --port 8084 --context store & > log } stop(){ diff --git a/src/api/User.py b/src/api/User.py index 9ec72a1..a6ec545 100644 --- a/src/api/User.py +++ b/src/api/User.py @@ -14,8 +14,8 @@ class User : self.stripe = args["stripe"] self.store = args['store'] - self.store['dbname'] = args['product'] - self.product = args['product'] + self.store['dbname'] = str(args['product']) + self.product = str(args['product']) self.me = {} self.init() @@ -25,10 +25,13 @@ class User : lproducts = [item for item in lproducts.auto_paging_iter() if item.name == self.product ] if lproducts : self.me['info'] = {"active":lproducts[0].active,"id":lproducts[0].id,"description":lproducts[0].statement_descriptor,"images":lproducts[0].images} + self.plans = self.stripe.Plan.list(product=lproducts[0].id) + self.plans = [item for item in self.plans.auto_paging_iter() ] - def init_customer(self,uid): + def init_customer(self,uid,pid): """ - This function copies a customer to the internal database + This function copies a customer to the internal database. + This would allow the object to be in sync with what is available in stripe and we will save the content later on ... """ customer = self.stripe.Customer.list(email=uid) if customer : @@ -36,20 +39,40 @@ class User : customer = customer[0] # # Insure the product is the one we are looking for ... - args = dict(self.store) - + args = dict(self.store) + args['uid']=uid reader = CouchdbReader(**args) key = reader.view('users/uid_map',key=uid) if not key : self.store['uid'] = customer.id self.update(_id=customer.id,emails=[uid]) + # + # We need to update the user's subscription + # + product_id = self.me['info']['id'] + info = {} + lsub = customer.subscriptions ; + + info = {} + found = False + for sub in lsub.data : + if sub.plan.id == pid or sub.plan.product == product_id: + info[sub.plan.nickname] = sub.plan + self.update(subscriptions=info) self.post() #-- housekeeping work - - return customer + else: + # + # The key exists we need to just read the user info in me + reader.uid = key + document = reader.read() + self.me = dict(self.me,**document) + self.user_key = customer.id + return customer else: return None + def update(self,**args): for key in args : @@ -66,19 +89,34 @@ class User : self.me[key] = value def post(self,**args): - + document = dict(self.me,**{}) args = dict(self.store) args['create'] = True writer = CouchdbWriter(**args) - writer.set(self.me) + writer.set(document) # writer.close() + def get_key(self,uid): + reader = CouchdbReader(**self.store) + key = reader.view('users/uid_map',key=uid) + + if key : + if isinstance(key,list) : + key = key[0]['value'] + elif 'value' in key : + key = key['value'] + else: + key = None + return key def get(self,uid,key): args = dict(self.store) - args['uid'] = 'logs' - couchdb = CouchdbReader(**args) - document = couchdb.view('users/uid_map',key=uid) - return document + args['uid'] = self.get_key(uid) + if key not in self.me : + couchdb = CouchdbReader(**args) + document = couchdb.basic_read() + return document[key] if key in document else {} + else: + return self.me[key] # def plans(self,free=False): # lproducts = self.stripe.Product.list() # plans = [item for item in lproducts.auto_paging_iter() if item.name == self.product ] @@ -91,22 +129,14 @@ class User : @param pid plan id @param stripeToken stripe token to process payments """ - # customer = self.stripe.Customer.list(email=uid).data - # if customer : - # customer = [item for item in customer if item.email == uid] - # customer = customer[0] - # - customer = self.init_customer(uid) + customer = self.init_customer(uid,pid) + product_id = self.me['info']['id'] if pid is None : # # In this block we try to find the free plan if one isn't specified # The informtion about the product is already known (constructor) # - # lproducts = self.stripe.Product.list() - # lproducts = [item for item in lproducts.auto_paging_iter() if item.name == self.product ] - product_id = self.me['info']['id'] - plans = self.stripe.Plan.list(product=product_id) - plans = [item for item in plans.auto_paging_iter() if item.amount == 0 ] + plans = [item for item in self.plans if item.amount == 0] pid = None if not plans else plans[0].id if not customer : @@ -120,29 +150,14 @@ class User : if stripeToken: args['source'] = stripeToken - if customer.subscriptions.data : - lsub = customer.subscriptions ; - found = False - for sub in lsub.data : - if sub.plan.id == pid : - found = True - break - - # for sub in lsub : - # found = [ for item in lsub.d - # found = [plan for plan in customer.subscriptions.data if plan.id == pid and plan.active == True] - # print " found ",len(found) > 0 - - else: - found = False - + sub = self.me['subscriptions'] + found = len([1 for plan_key in sub if sub[plan_key]['id'] == pid]) > 0 if found is False : - sub = self.stripe.Subscription.create(**args) + sub = self.stripe.Subscription.create(**args) info = {sub.plan.nickname:sub.plan} self.update(subscriptions=info) - self.post() - else: - pass + + self.post() # # keep a copy of this on our servers ... # @@ -153,23 +168,29 @@ class User : customer = parent.stripe.Customer.retrieve(customer_id) parent.store['uid'] = customer.id - lsub = customer.subscriptions - reader = CouchdbReader(**parent.store) - parent.me = reader.read() + lsub = customer.subscriptions + reader = CouchdbReader(**parent.store) + _me = reader.read() + if 'emails' not in parent.me : + parent.me['emails'] = [] + emails = list(set(parent.me['emails']) | set(_me['emails'])) + parent.me = dict(parent.me,**_me) + parent.me['emails'] = emails product_id = parent.me['info']['id'] [parent.update(subscriptions={sub.plan.nickname:sub.plan}) for sub in lsub.auto_paging_iter() if sub.plan.product == product_id and sub.plan.active == True] parent.post() - reader = CouchdbReader(**self.store) - key = reader.view('users/uid_map',key=uid) + key = self.get_key(uid) + # reader = CouchdbReader(**self.store) + # key = reader.view('users/uid_map',key=uid) + # if key : + # if isinstance(key,list) : + # key = key[0]['value'] + # elif 'value' in key : + # key = key['value'] if key : - if isinstance(key,list) : - key = key[0]['value'] - elif 'value' in key : - key = key['value'] - thread = Thread(target=_update,args=(key,)) thread.start() diff --git a/src/api/index.py b/src/api/index.py index 2e2ec9c..7d4ed79 100755 --- a/src/api/index.py +++ b/src/api/index.py @@ -12,6 +12,7 @@ from __future__ import division from flask import Flask, request, session, render_template,Response from flask_cors import CORS import Domain +from User import User from couchdbkit import Server, Document import stripe import json @@ -37,6 +38,7 @@ stripe.api_key = stripe_keys['secret_key'].strip() app = Flask(__name__) CORS(app) COUCHDB = Server(uri=CONFIG['couchdb']['uri']) ; +SYS_STORE = CONFIG['couchdb'] """ This function will set the user information to the session and update the information @header uid user email address @@ -49,18 +51,30 @@ def init(product): @param uid user's email (primary) @param pid plan identifier """ - email = request.headers['uid'] - plan_id = request.headers['pid'] + uid = request.headers['uid'] + plan_id = request.headers['pid'] if 'pid' in request.headers else None user = User(stripe=stripe,store=CONFIG['couchdb'],product=product) - user.susbscribe(uid,plan_id) + user.subscribe(uid,plan_id) + + sub = None if 'auid' in request.headers : auid = request.header['auid'] user.update(emails=auid) user.post() - features = json.loads(users.me['subscriptions'][pid][0]['metadata']) - else: - features = user.get(uid,key) - user.refresh() + + + store = dict(CONFIG['couchdb'],**{}) + store['dbname'] = product + store['uid'] = 'logs' + + sub = user.get(uid,'subscriptions') + features = {} + for id in sub : + if sub[id]['active'] is True : + features[id] = sub[id] + user.refresh(uid) + session['key'] = user.user_key + session['uid'] = uid return json.dumps(features),200 # @app.route("/init/",methods=['POST']) @@ -109,16 +123,16 @@ def init(product): """ This function will update the user's email """ -@app.route('/init/',methods=['PUT']) -def init_update(app_name): - uid = request.headers['uid'] - id = session['customer.id'] - couchdb = Couchdb(uri=CONFIG['couchdb']['uri'],dbname=app_name,uid=None) - DB = couchdb.dbase - handler = Domain.User(DB,stripe) - handler.update_user(uid=uid,id=id) +# @app.route('/init/',methods=['PUT']) +# def init_update(app_name): +# uid = request.headers['uid'] +# id = session['customer.id'] +# couchdb = Couchdb(uri=CONFIG['couchdb']['uri'],dbname=app_name,uid=None) +# DB = couchdb.dbase +# handler = Domain.User(DB,stripe) +# handler.update_user(uid=uid,id=id) - return ('',204) +# return ('',204) """ @@ -127,12 +141,13 @@ def init_update(app_name): @resource name name of the application {cloud-music} @header key service/plan """ -@app.route('/subscribe/',methods=['POST']) -def subscribe(app_name): +@app.route('/subscribe/',methods=['POST']) +def subscribe(product): # # The name is the full name of the service # resp = "0" + user = User(stripe=stripe,store=CONFIG['couchdb'],product=product) if 'stripeToken' in request.form : stripeToken = request.form['stripeToken'] uid = request.form['stripeEmail'] @@ -141,43 +156,73 @@ def subscribe(app_name): pid = request.form['plan'] else: - pid = request.headers['pid'] + pid = request.headers['pid'] if 'pid' in request.headers else None uid = request.headers['uid'] stripeToken = None + user.subscribe(uid,pid,stripeToken) + if 'auid' in request.headers : + + if request.headers['auid'].startswith('[') or request.headers['auid'].startswith("{") : + auid = json.loads(request.headers['auid']) + else: + auid = [request.headers['auid']] + user.update(emails=auid) + user.refresh(uid) - plans = stripe.Plan.list().data - plan = [item for item in plans if item.id == pid] - resp = "0" + reader = CouchdbReader(uri=SYS_STORE['uri'],dbname=product,uid=uid,create=False) + plans = reader.view('users/active_plan',key=user.user_key) #me['_id']) + #session['plans'] = plans + session['key'] = user.user_key + session['uid'] = uid - couch_handler = Couchdb(uri=CONFIG['couchdb']['uri'],dbname=app_name,uid=uid) - DB = couch_handler.dbase - handler = Domain.User(DB,stripe,stripeToken) ; + return (json.dumps(plans),200) - if plan : - handler.init(uid,plan) - resp = plan[0].id +def get_plans(product) : + lproducts = stripe.Product.list() + lproducts = [item for item in lproducts.auto_paging_iter() if item.name == product ] + if lproducts : + product_id = lproducts[0].id + alias = lproducts[0].statement_descriptor + plans = stripe.Plan.list(product=product_id) + i = 0 + for plan in plans : + plan['product_alias'] = alias if alias is not None else '' + if 'features' in plan['metadata']: + plan['metadata']['features'] = json.loads(plan['metadata']['features']) + plans[i] = plan + i += 1 + if plans : + plans = plans.data + plans.sort(key = lambda x:x.amount) + + return plans + else: + return [] - return ('',204) +@app.route('/features/') +def features(product): + """ + This function returns the plan/features of a user for a given application if provided a uid (email) + if no uid are provided then the function will return all the plans/features associated with the given product + @header uid user's email + """ -""" - This function returns the features of a user for a given application - The features are returned provided the plan is still valid i.e (end_date,status={active|trailing}) - @header uid user's email -""" -@app.route('/features/') -def features(app_name): plans = [] if 'uid' in request.headers : uid = request.headers['uid'] - - couchdb = CouchdbReader(uri=CONFIG['couchdb']['uri'],dbname=app_name,uid=uid,create=False) - handler = Domain.User(couchdb.dbase,stripe) - handler.initialize(uid) - lsub = handler.subscriptions() - - plans = [ sub['plan'] for sub in lsub if sub['ended_at'] is None ] - plans = [item['metadata'] for item in plans if item['id']] - + couchdb = CouchdbReader(uri=CONFIG['couchdb']['uri'],dbname=product,uid=uid,create=False) + key = couchdb.view("users/uid_map",key=uid) + if key : + key = key[0]['value'] + plans = couchdb.view('users/active_plan',key=key) + plans = [plan['value'] for plan in plans if 'value' in plan] + else: + plans = [] + else: + plans = get_plans(product) + # + # formatting plans for the output + # return json.dumps(plans) """ This function returns a user's plans/status for an application @@ -199,48 +244,31 @@ def status(app_name): It will allow user's to be able to signup for various plans @pre 'uid' in request.headers """ -@app.route('/signup/') -def get_plans(app_name) : +@app.route('/signup/') +def signup(product) : apikey = CONFIG['stripe']['pub'].strip() + # # This function returns the plans for a given application # We assume the application name is the prefix of the plan identifier in stripe # - uid = session['uid'] if 'uid' in session else None + if 'user-info' in session : + _info = session['user-info'] + session['uid'] = _info['uid'] + uid = session['uid'] if 'uid' in session else '' can_purchase = True if uid is None : uid = request.args.get('uid') if 'uid' in request.args else None - plans = stripe.Plan.list(limit=10) + plans = get_plans(product) - # plans = handler.cast(plans.data) - plans = json.loads(json.dumps(plans.data, sort_keys=True, indent=2)) - - plans = [item for item in plans if re.match(app_name,item['name'])] - if uid is not None: - couchdb = Couchdb(uri=CONFIG['couchdb']['uri'],dbname=app_name,uid=uid,create=False) - handler = Domain.User(couchdb.dbase,stripe) - if handler.getId(uid) is not None: - handler.init(uid,[]) - subs = handler.subscriptions().data - ids = [item.id for item in subs] - for item in plans : - if item['id'] in ids : - item['active'] = True - # - # let's determine if this user can make a purchase - # - - else: - # # - # This should address any case of the database of customers being out of sync - # @TODO: Have a log suggesting a utility imports the customer - # - return json.dumps(plans) - # # @TODO: Mark the plans the current user is signed up for # - return render_template('subscribe.html',context=CONTEXT,uid=uid,app_name=app_name,plans=plans,apikey=apikey) + platform='web' if 'platform' not in request.args else request.args['platform'] + alias = plans[0]['product_alias'] + active_plan = session['plans'] if 'plans' in session else [] + print [' ** ',uid,active_plan] + return render_template('subscribe.html',context=CONTEXT,uid=uid,alias=alias,platform=platform,app_name=product,plans=plans,apikey=apikey) @app.route('/subscribe/',methods=['DELETE']) def cancel_subscribe(name) : @@ -279,4 +307,5 @@ def is_customer (app_name): if __name__ == '__main__' : app.debug = True ; app.secret_key = '360-8y-[0v@t10n]+kr81v17y' - app.run(port=PORT,threaded=True) + app.config['MAX_CONTENT_LENGTH'] = 1600 * 1024 * 1024 + app.run(port=PORT,threaded=True,host='0.0.0.0') diff --git a/src/api/static/js/jx b/src/api/static/js/jx new file mode 160000 index 0000000..df17531 --- /dev/null +++ b/src/api/static/js/jx @@ -0,0 +1 @@ +Subproject commit df17531499104b155e5ac97ee2755a8bf10bf8ff diff --git a/src/api/templates/subscribe.html b/src/api/templates/subscribe.html index 96a6b0a..1eca04a 100755 --- a/src/api/templates/subscribe.html +++ b/src/api/templates/subscribe.html @@ -8,9 +8,13 @@ --> + + + {{app_name.replace('-',' ')}} - + +
+{{active_plan}}

-
- +
- -
-
- -
The Phi Technology -
{{ app_name }}
-
+
+
+ +
+
The Phi Technology
+
{{ alias }}
+
- -
Call : 615-866-0454
-
Email: support@the-phi.com
+
+ {{uid}}
@@ -116,76 +157,72 @@ -
{{app_name.replace('-',' ') }}
+ {% if alias %} +
{{alias.replace('-',' ') }}
+ {% else %} +
{{app_name.replace('-',' ') }}
+ {% endif %}
Available Plans
+
{% for item in plans%} -
-
{{ item.metadata.info|safe }}
- {% if 'note' in item.metadata %} -
{{item.metadata.note|safe}}
- {% endif %} - -
-
- {% if item.amount > 0 %} -
{{ item.amount / 100 }}
-
{{item.currency.upper() }}/{{ item.interval.replace('month','Mo') }}
- {% else %} -
Free
- {% endif %} -
-
- {% if item.amount > 0 or item.active == True %} - + + -
Your first subscription should be enabled Online
-
https://the-phi.com/store/subscribe
+ +
support@the-phi.com
-
- -
diff --git a/src/utils/transport.py b/src/utils/transport.py index 7bf9c17..5b49c3d 100755 --- a/src/utils/transport.py +++ b/src/utils/transport.py @@ -261,6 +261,7 @@ class MessageQueue: self.close() return resp def close(self): + if self.connection.is_closed == False : self.channel.close() self.connection.close() """ @@ -351,7 +352,6 @@ class QueueReader(MessageQueue,Reader): self.durable = False self.size = -1 self.data = {} - def init(self,qid): properties = pika.ConnectionParameters(host=self.host) @@ -368,6 +368,7 @@ class QueueReader(MessageQueue,Reader): """ def callback(self,channel,method,header,stream): + r = [] if re.match("^\{|\[",stream) is not None: r = json.loads(stream) @@ -399,9 +400,12 @@ class QueueReader(MessageQueue,Reader): # We enabled the reader to be able to read from several queues (sequentially for now) # The qid parameter will be an array of queues the reader will be reading from # + if isinstance(self.qid,basestring) : + self.qid = [self.qid] for qid in self.qid: self.init(qid) # r[qid] = [] + if self.info.method.message_count > 0: self.channel.basic_consume(self.callback,queue=qid,no_ack=False); @@ -420,7 +424,7 @@ class QueueListener(QueueReader): self.channel = self.connection.channel() self.channel.exchange_declare(exchange=self.uid,type='direct',durable=True ) - self.info = self.channel.queue_declare(exclusive=True,queue=qid) + self.info = self.channel.queue_declare(passive=True,exclusive=True,queue=qid) self.channel.queue_bind(exchange=self.uid,queue=self.info.method.queue,routing_key=qid) #self.callback = callback @@ -470,8 +474,6 @@ class Couchdb: create = args['create'] if 'create' in args else False if self.dbase.doc_exist(self.uid) == False and create == True: self.dbase.save_doc({"_id":self.uid}) - def view(self,id,**args): - return self.dbase.view(id,**args).all() """ Insuring the preconditions are met for processing """ @@ -484,9 +486,14 @@ class Couchdb: # We are also sure that the database actually exists # q = self.dbase.doc_exist(self.uid) - return p and q + if q == False: + return False + return True + def view(self,id,**args): + r =self.dbase.view(id,**args) + r = r.all() + return r[0]['value'] if len(r) > 0 else [] - """ This function will read an attachment from couchdb and return it to calling code. The attachment must have been placed before hand (otherwise oops) @T: Account for security & access control @@ -518,13 +525,14 @@ class CouchdbReader(Couchdb,Reader): # doc = self.dbase.get(self.uid) - q = doc is not None + if '_attachments' in doc: r = self.filename in doc['_attachments'].keys() + else: - r = True + r = False - return p and q and r + return r def stream(self): content = self.dbase.fetch_attachment(self.uid,self.filename).split('\n') ; i = 1 @@ -555,7 +563,8 @@ class CouchdbWriter(Couchdb,Writer): @param dbname database name (target) """ def __init__(self,**args): - Couchdb.__init__(self,**args) + + Couchdb.__init__(self,**args) uri = args['uri'] self.uid = args['uid'] if 'filename' in args: @@ -571,27 +580,53 @@ class CouchdbWriter(Couchdb,Writer): def set(self,document): _document = self.dbase.get(self.uid) - if _document : - document['_id'] = _document['_id'] - document['_rev']= _document['_rev'] + document = dict(document,**_document) + # if _document : + # document['_id'] = _document['_id'] + # document['_rev']= _document['_rev'] + self.dbase.save_doc(document) - """ - write a given attribute to a document database - @param label scope of the row repair|broken|fixed|stats - @param row row to be written - """ + def write(self,**params): + """ + write a given attribute to a document database + @param label scope of the row repair|broken|fixed|stats + @param row row to be written + """ document = self.dbase.get(self.uid) label = params['label'] - row = params['row'] - if label not in document : - document[label] = [] - document[label].append(row) + + + if 'row' in params : + row = params['row'] + row_is_list = isinstance(row,list) + if label not in document : + document[label] = row if row_is_list else [row] + + elif isinstance(document[label][0],list) : + document[label].append(row) + else: + document[label] += row + else : + if label not in document : + document[label] = {} + if isinstance(params['data'],list) == False : + + document[label] = dict(document[label],**params['data']) + else: + document[label] = params['data'] + + # if label not in document : + # document[label] = [] if isinstance(row,list) else {} + # if isinstance(document[label],list): + # document[label].append(row) + # else : + # document[label] = dict(document[label],**row) self.dbase.save_doc(document) def flush(self,**params) : - size = params['size'] + size = params['size'] if 'size' in params else 0 has_changed = False document = self.dbase.get(self.uid) for key in document: @@ -599,7 +634,7 @@ class CouchdbWriter(Couchdb,Writer): content = document[key] else: continue - if isinstance(content,list): + if isinstance(content,list) and size > 0: index = len(content) - size content = content[index:] document[key] = content @@ -607,8 +642,8 @@ class CouchdbWriter(Couchdb,Writer): else: document[key] = {} has_changed = True - if has_changed: - self.dbase.save_doc(document) + + self.dbase.save_doc(document) def archive(self,params=None): document = self.dbase.get(self.uid)