From d5705e1eb6a7b5250426de3ee09d2cb9013ee241 Mon Sep 17 00:00:00 2001 From: Steve Nyemba Date: Tue, 8 Oct 2019 11:50:26 -0500 Subject: [PATCH] store front signup with account @TODO: add simple payments like donation --- src/api/index.py | 82 ++++++-- src/api/static/assets/google/normal.png | Bin 0 -> 3983 bytes src/api/static/css/default.css | 218 ++++++++++++++++++++++ src/api/static/img/accounts/box.png | Bin 0 -> 926 bytes src/api/static/img/accounts/dropbox.png | Bin 0 -> 1334 bytes src/api/static/img/accounts/microsoft.png | Bin 0 -> 1678 bytes src/api/static/img/accounts/one-drive.png | Bin 0 -> 1220 bytes src/api/templates/plans.html | 123 ++++++++++++ src/api/templates/signup.html | 141 ++++++++++++++ 9 files changed, 551 insertions(+), 13 deletions(-) create mode 100644 src/api/static/assets/google/normal.png create mode 100644 src/api/static/css/default.css create mode 100644 src/api/static/img/accounts/box.png create mode 100644 src/api/static/img/accounts/dropbox.png create mode 100644 src/api/static/img/accounts/microsoft.png create mode 100644 src/api/static/img/accounts/one-drive.png create mode 100755 src/api/templates/plans.html create mode 100644 src/api/templates/signup.html diff --git a/src/api/index.py b/src/api/index.py index a74f99e..a73498f 100755 --- a/src/api/index.py +++ b/src/api/index.py @@ -7,20 +7,25 @@ - Having a free product insures there is no excuse not to signup users - If a product doesn't fall under this model we will find a way to fix it. - + The store must be prefixed with an authentication module, because customers must be signed in before making a purchase. """ 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 Domain +# from User import User +# from couchdbkit import Server, Document import stripe import json from StringIO import StringIO import re import os from utils.params import PARAMS -from utils.transport import Couchdb, CouchdbReader +from transport import factory +# from store import Store +import store +from datetime import datetime +# from utils.transport import Couchdb, CouchdbReader PORT = 8100 if 'port' not in PARAMS else int(PARAMS['port']) ; path = PARAMS['path'] #os.environ['CONFIG'] f = open(path) @@ -36,23 +41,71 @@ 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 -""" +# CORS(app) +# COUCHDB = Server(uri=CONFIG['couchdb']['uri']) ; +# SYS_STORE = CONFIG['couchdb'] +SYS_STORE = CONFIG['store'] +@app.route("/") +def index(): + mystore = store.factory.instance(name='music') + products = mystore.get.products() + args = {"context":CONTEXT,"products":products} + return json.dumps(products),"content-type:application/json" + # return render_template("index.html",**args) +@app.route("/json/") +def get_plans_json(id): + """ + This function returns the plans for a given product + """ + HEADER="Content-type:application/json" + try: + mystore = store.factory.instance(name=id) + return json.dumps(mystore.get.plans()),HEADER + except Exception as e: + # + # Log this shit or not + pass + return "[]",HEADER +@app.route("/ui/signup/") +def signup(id): + """ + """ + mystore = store.factory.instance(name=id) + plans = mystore.get.plans() + index = int(request.args['index']) + plan = plans[index] + + args = {"product":id,"label":mystore.product['statement_descriptor'],"plan":plan,"context":CONTEXT,"now":datetime.now().year} + args['theme'] = 'theme-clouds' + return render_template("signup.html",**args) +@app.route("/ui/") +def get_plans_ui(id): + mystore = store.factory.instance(name=id) + # + # sort plans by + + description = mystore.product['metadata']['about'] if 'about' in mystore.product['metadata'] else '' + label = mystore.product['statement_descriptor'] + args = {"product":id,"label":label,"description":description,"context":CONTEXT,"plans":mystore.get.plans(),"now":datetime.now().year} + args['theme'] = 'theme-clouds' + print (mystore.product.keys()) + return render_template('plans.html',**args) @app.route("/init/",methods=['POST']) def init(product): """ - This function initializes a product to a given user, + This function initializes/logs a product to a given user, i.e it will create a session for users/product/plans if the user has provided a user identifier it will be used as her primary email. The understanding is that a product may have multiple plans under it but always a free one @param uid user's email (primary) @param pid plan identifier """ + # + # get product and plans + # uid = request.headers['uid'] plan_id = request.headers['pid'] if 'pid' in request.headers else None + # + # We should just pull the factory method and get a storage handler to handle the logs + # store = dict(CONFIG['couchdb']) store['dbname'] = product user = User(stripe=stripe,store=store,product=product) @@ -193,7 +246,7 @@ def status(app_name): @pre 'uid' in request.headers """ @app.route('/signup/') -def signup(product) : +def _signup(product) : apikey = CONFIG['stripe']['pub'].strip() # @@ -263,7 +316,10 @@ def is_customer (app_name): return render_template('bill.html',context=CONTEXT,apikey=apikey,app_name=app_name.replace('-',' '),plans=plans,total_amount=amount) if __name__ == '__main__' : + # + # setup mongodb session management (not sure why) app.debug = True ; app.secret_key = '360-8y-[0v@t10n]+kr81v17y' 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/assets/google/normal.png b/src/api/static/assets/google/normal.png new file mode 100644 index 0000000000000000000000000000000000000000..b1327b4f7b47c04368da6c4b066645453398eca5 GIT binary patch literal 3983 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 A0RR91 literal 0 HcmV?d00001 diff --git a/src/api/static/img/accounts/dropbox.png b/src/api/static/img/accounts/dropbox.png new file mode 100644 index 0000000000000000000000000000000000000000..e8f05ae31a58b5eb294ea378d52071f53e7bcb30 GIT binary patch literal 1334 zcmV-61`q_V_?t+X@mUVn6eVV2G`m>3h!&->kb&bi!=e;L%<;H>iYk;>Ma z0-O(;hU<%b$M4CIPOWag$ri9Y4D=5&7q=8-9qW^UysZb)HE1hQ)fLQ~0ssP>Am}2m zD|=6G;<+B*GzGoOz_6Z(bU}GNY=L!6&rZ15y8%_ot@6z!=w4}+EwF^WVWnjh@-)MtrGYHdq>oA0)O0vLPn6n=MW|sRVGN=ZO zQAyOcSe6#=UYvat0BA~?&z_P_t4Og73^NG=0Dy>`1KB3_v=Q9T;QoE6?Plf;T^IB< zPD!UqBD~lqabO3VrlYwi>u_^X)?qfxu>$N!wm!73N>c6Qp$AI$+HM$f6#&bRX$pmZ$ke%3uzJBiHAFHmcfm!ALISPCL#=scarva=?%Q*60{rve> zZ?yC3>Wqu0G8ZYZmKhmQ34*_rvC!*sHUJU3V0zV$BW#xS8Hk*rP*8x)W|zFOYiZ7( ziMzUrIh)!B1ygA?fffU>^)#*ipkX`~elA3v1CMX`Htk|sdIgCNHJFz+xSU7&A{>dl zt%t^Fz}iT}9sMp{aDTe&zRv66`^OPA1Er=!OO+1}_2ol(J%9lKppflW z!2Wbg@#wB70}pRKFtDFx?ga2)6r0FCX3kG`JRPdE$bw1KkeDB#S z24-(LI4o%JZU&EwM=B!SO#E`%V56*lzN0%<&b+$zl+M%E^P;l)@+B4%s{Vixn6ane z?hcym@n;IK2XQW+XeVeF=rhgk?C-P>07*qoM6N<$f+Xf&Bme*a literal 0 HcmV?d00001 diff --git a/src/api/static/img/accounts/microsoft.png b/src/api/static/img/accounts/microsoft.png new file mode 100644 index 0000000000000000000000000000000000000000..a9411e72d32b376131c4a6f50b71f16cad0771de GIT binary patch literal 1678 zcmV;9266d`P) zaB^>EX>4U6ba`-PAZ2)IW&i+q+O1Y$a_cM%{I65IBY*@*90v@{yc>MSe~V4hG_9Ms z^Vw5_Wg(>9MJe_F{to(si(O_4Q7z?^9IlW<=7Nd2<2pv~!v5SZUZ3#($xinV43l7# z<2Ci=Uto_P3p6Zy{cI=QRvcS_)`wd_nLE$j+DdW-5K?BL{KjZDMn9@6Arat8tEgKk`wWg1aLT?c;a2=b z%yqvrZga(L*sP({O0ME1dj_(@2j6`0!}opM{Du?*cUFwd8_RApQO>;Wq=b3*CbZJVBBFg6j)q6Le%I#v?V*w;v&H{xI^+@+X?`Puy;f3I=?n$PHO?5J?~hrvZCojr!e}BOhk87Z4F8iGms&AdnS8 z3<>AWAfZB0Vu&%OsL^7MB}ob?rj#_zK2=mSXw;;trdf-L1&fwU%`98VoCR$xQchX3 z<(x~wrX>`_D)28bRIXA(jWt!RR&y;IZfVoaw!D?bO&VGNwbZm(%dK?ZA*CLA z?5S(Fo_iV4+K>@O9BJsVkw>|+wpjhl+B0)Mv*s6Tyc0G3^n*2+Rc|5uY$tJY2F5se zFs_pU2%0Bn&U^?wnVX!U_X~+cMwW4s)BI!%42JGG4u9wFgSk`Q2}xo-fm?lXr4k~iVi6_esJhk*vkA}`qfCkUHCN+GM+Q8J%rKta-b?9 z`VdK*wfjJ~&|i2D ze|jJyrK2#ME;Qdug|U&7u6iJ@(p$LWcu`(-qOZ&Hq7!{xmKUAq>$1G)L|>QXMdx%} zlNX)n>x;ZbsJFPRTefGk>D4K3DIV+1{3vE;{&djjdcU{+3>tkw?U$etU+-hzf<~{U_8w^T zT59isMz5vz-bk;d_8w^TT55K=`M-NU&jOuF?b1kB7yJ}R@jqCmf7ZIRuHs87E&u=l zglR)VP)S2WAaHVTW@&6?001bFeUUv#!$2IxUsI)}R21wW;*g;_Sr8S`O{-9Z3ZYhL z)xqS_FKE(`w7575t_24_7OM^}&bm6d3WDGVh>NR}qKlOHzogJ2#)IR2yu0_fdj|-O zGSjTCF+kI+W-1XEGuc%!@QM(E=!1wEnOVl1BqiZFzV6}U>s^dzd7t}p^r$(D0X~s< zmKkQ1c!PLqvubeOCyub9tP-CSkD7Er;zzD4F28XuI4tnYh?!2!6Gw=}LI*1y%!;N) zJV_i?HJ$Q>jLRzLEzVlG##;B}FAV4Om1VBe96|z%Sb{_yDX5`@GOCEts*_?NMf-6N z|DfZS$fc001V)YpRG>k2{NR7^yIU(iG43XX<3RU|ZGQ{{fnA_kx9#s^+iso!{%7Dy zZ~H3^VEU8vdRvPg0lnM6#dTX#_JGSBVBpD+P1%wBG=+Q~ct4|W$^!kjKxobFt+kKS z2Ov#dC2xR(Ltvyp+3Ozf4tDnT@0nJAKbogy{D4^ z000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2jdC|5F!$rEoa&Q0044HL_t(o!((6= z>3|W&_!zGIpA7xN&hg<43=9l!N!579tBsL?fq{W(phIBP0izBWb-<_tMjbHV4q!p9 zSjf=-o&vq74oC>!MwS7mc>O2}dgKszONKeZ6zCnTjz=9Z>VQ!Pj5=V{0i%F{fq{Vm Y01es;)toA+MgRZ+07*qoM6N<$f|=hTegFUf literal 0 HcmV?d00001 diff --git a/src/api/static/img/accounts/one-drive.png b/src/api/static/img/accounts/one-drive.png new file mode 100644 index 0000000000000000000000000000000000000000..085944124ed86610542ae49501814661a1110dbf GIT binary patch literal 1220 zcmV;#1UvhQP)2->@LgdaM!T)bqmY-~{jx=f~FjuJPnW5Q4v_e-JePAI38 zmR`>1kG6*%XbHm*|L`Wwd%iE<$#Z$%_d5kvV>SNgAphlMK@ju`h2jVyWGf+LEv3{( zDgA?G*-L~F--;2SloF@Yb)xB>_2?yYPh7_UkNbiLNgRX1>{PsF%K0Z{4SR@X*_IU{ zaDQ^@=Mz;e`)h9XL!=ax0-{Jk5zGmU1|J>D_nDsDSfN^2SiLZB3)2vADr$WRJODb5`$nl8vT<}eI1^gsmMZuj2a zvB`@kFSi&v26;(gA{&SNECa4I4+g;{usP63N7D2xsz2CYyFOL_?2-xaJkMDivpq*n z*BLxs|DphksSmF|D=P>b*qLtNIF64-Krw%VVVIMjUT!ta`uqr1H3$&Qh`vTR&p!vW z_1Tq#kZrLXP|e@4Pr0A(4B0OILZB2>+S`_{$IDM=K^L!q&+ms?t;B%UiL+NbFv?Gd z-lLdy`#>pWmrS5{d@4n*W}*6bU)YX#jSA)G8zIbiCCfLbJcQ$=JMn#8H|p9)P_laq z4)4nXA*9shav8K*?YD#wF%oY<4>Wa+PLzHAyCGJ6U)+-cN+~L@cgsdEc%DM3P~glP zh2XStsJ_{U+O}bU0(){F#(O1Cw523!O*);x^GL~x-;y?%yq!RQ`2)jq@(@|&p z)n{^Wt#ud;UE@;2#l8z@3{d&{PCt)^F3YciAiT|S+;^%;r$@iQLKkYv+X){IPc7#f7@Q^9Cj$|f+xsU6j6|!iuz8Aj9j$te$dkF zdu%_Cj-Yqk0a1+dX%6RzD_OQ5ao&)q zg(+_njut=qTh`_crX~F)8yXra(r7eAgpe!%O34U$mY%~-5JG6=vnYxngjhTtk2yU( i{lZePS7S9+JpKjV1ignrbewJg0000 + + + + + + + + + + + + +{{product.replace('-',' ')}} + + + +
+ +
+ +
+
+ +

{{label}}
+
{{description}}
+ +

+ +
+
+ + {% for item in plans%} +
+
+
{{ item.nickname }}
+ {% if item.id in active_plans %} +
+ {% else %} +
+ {% endif %} + {% if item.amount == 0%} +
+ Free +
+ + {%else%} +
+ $ {{ item.amount/100 }} + / {{item.interval[:]}} + +
+ {% endif %} +
+ +
+ {% for key,value in item.metadata['features'].iteritems() %} +
+
{{ key }}
+ {% if value == True %} +
+ {% elif value == False %} +
+ {%else %} +
{{value}}
+ {% endif %} +
+ + {% endfor %} +
+ +
+

+

+ Signup Now +
+

+ + +
+ + +
+ {% endfor %} + +
+ +
+ + + + diff --git a/src/api/templates/signup.html b/src/api/templates/signup.html new file mode 100644 index 0000000..6bf3635 --- /dev/null +++ b/src/api/templates/signup.html @@ -0,0 +1,141 @@ + + + + + + + + + + + + +Signup {{product.replace('-',' ')}} + + +

+
+ +
+
+ +
+ +
+
+
{{label}}
+
+ + {{plan.nickname}} Plan, + + {{plan.amount / 100}} / {{plan.interval}} +
+
+
+

+

+ + {% for key,value in plan.metadata.features.iteritems() %} +
+
+ {{key}} +
+
+ {% if value == 1%} + + {% elif value == 0%} + + {% else %} + {{ value}} + {% endif %} +
+
+ {% endfor%} +
+

+
+
+ +
Signup using a cloud account
+ + +

+

+ + + + + +
+

+
+ +
+ + \ No newline at end of file