Commit daaf4fdcb352bc56e95949765d4700fb36faa971

Multiple experiments on current sweet store

  - Add Persona login, logout APIs and verfication with Persona code.
  - Added a small stats part to show the total number of sweets
  - Changed template styles to have a better UI
  - Initial version of opening each sweet in a relevant app.
The /where part of displayed sweet is a hyperlink to a relevant swtr app
(defined by the context) with the corresponding sweet as the argument.
  • Diff rendering mode:
  • inline
  • side by side

static/css/style.css

4h1 { border-bottom: 2px solid #eee; }4h1 { border-bottom: 2px solid #eee; }
5h2 { font-size: 1.2em; }5h2 { font-size: 1.2em; }
66
7.page { margin: 2em auto; width: 35em; border: 5px solid #ccc;
7.page { margin: 2em auto; width: 45em; border: 5px solid #ccc;
8 padding: 0.8em; background: white; }8 padding: 0.8em; background: white; }
9.entries li { margin: 0.8em 1.2em; word-wrap: break-word; }
9.entries li { margin: 0.8em 1.2em; word-wrap: break-word;
10 border: 1px solid #CCC; padding: 10px;
11 border-radius: 3px; }
10.add-entry { font-size: 0.9em; border-bottom: 1px solid #ccc; }12.add-entry { font-size: 0.9em; border-bottom: 1px solid #ccc; }
11.add-entry dl { font-weight: bold; }13.add-entry dl { font-weight: bold; }
12.metanav { text-align: right; font-size: 0.8em; padding: 0.3em;14.metanav { text-align: right; font-size: 0.8em; padding: 0.3em;
16.flash { background: #CEE5F5; padding: 0.5em;16.flash { background: #CEE5F5; padding: 0.5em;
17 border: 1px solid #AACBE2; }17 border: 1px solid #AACBE2; }
18.error { background: #F0D6D6; padding: 0.5em; }18.error { background: #F0D6D6; padding: 0.5em; }
19small { font-size: 0.9em; }

swtr.py

16from urllib import unquote_plus16from urllib import unquote_plus
17import json17import json
18import conf18import conf
19import requests
1920
20# TODO:21# TODO:
21# restify22# restify
29# classes!29# classes!
30# sqlAlchemy30# sqlAlchemy
31# Postgres31# Postgres
32# Persona, Auth in API endpoints
3233
33# TODO: move this in a config file34# TODO: move this in a config file
34# configuration35# configuration
35DATABASE = 'alipiBlog'
36
37DATABASE = 'sweets_production'
36COLLECTION_NAME = 'posts'38COLLECTION_NAME = 'posts'
37DEBUG = True39DEBUG = True
38SECRET_KEY = conf.SECRET_KEY40SECRET_KEY = conf.SECRET_KEY
42PASSWORD = 'default'42PASSWORD = 'default'
43DB_PORT = 2701743DB_PORT = 27017
44DB_HOST = 'localhost'44DB_HOST = 'localhost'
45URL = "http://localhost:5001"
45URL = 'http://localhost:5001'
46MOZ_PERSONA_VERIFIER = 'https://verifier.login.persona.org/verify'
47MOZ_PERSONA_AUDIENCE = 'http://localhost:5000'
4648
49appURL_map = {'img-anno': 'http://localhost:5000/?where=',
50 're-narration': 'http://y.a11y.in/web?foruri=',
51 'idh-mowl': 'http://app.swtr.us/?where=',
52 'testFromAPI': 'http://app.swtr.us/?where='}
53
47# create our little application :)54# create our little application :)
48# ^ ... It's going to be big now :P55# ^ ... It's going to be big now :P
49app = Flask(__name__)56app = Flask(__name__)
73 return False73 return False
74 return True74 return True
7575
76
76def getUsers():77def getUsers():
77 db = g.connection[app.config['DATABASE']]78 db = g.connection[app.config['DATABASE']]
78 coll = db['sweet_users']79 coll = db['sweet_users']
82 users.append(i['user'])82 users.append(i['user'])
83 return users83 return users
8484
85def gatherStats(coll):
86 stats = {}
87 stats['total_sweets'] = coll.count()
88 return stats
89
85@app.before_request90@app.before_request
86def init_db():91def init_db():
87 g.connection = Connection(app.config['DB_HOST'], app.config['DB_PORT'])92 g.connection = Connection(app.config['DB_HOST'], app.config['DB_PORT'])
88 db = g.connection[app.config['DATABASE']]93 db = g.connection[app.config['DATABASE']]
89 g.collection = db[app.config["COLLECTION_NAME"]]94 g.collection = db[app.config["COLLECTION_NAME"]]
95 g.stats = gatherStats(g.collection)
9096
9197
92@app.teardown_request98@app.teardown_request
111111
112@app.route('/')112@app.route('/')
113def show_entries():113def show_entries():
114 print 'request:'
115 print request.method
116 res = g.collection.find().sort('_id',direction=-1)
114 res = g.collection.find().sort('_id',direction=-1).limit(100)
117 entries = make_list(res)115 entries = make_list(res)
118 return render_template('show_entries.html', entries=entries)
116 return render_template('show_entries.html', entries=entries,
117 appURL_map=appURL_map, stats=g.stats)
119118
120119
121# TODO: understand if we really need the OPTIONS120# TODO: understand if we really need the OPTIONS
126 if request.method == 'OPTIONS':126 if request.method == 'OPTIONS':
127 response = make_response()127 response = make_response()
128 response.status_code = 200128 response.status_code = 200
129 response.headers['Access-Control-Allow-Origin'] = '*'
129 response.headers['Access-Control-Allow-Origin'] =\
130 'http://localhost:5000'
130 response.headers['Access-Control-Max-Age'] = '20days'131 response.headers['Access-Control-Max-Age'] = '20days'
131 response.headers['Access-Control-Allow-Headers'] = 'Origin,\132 response.headers['Access-Control-Allow-Headers'] = 'Origin,\
132 X-Requested-With, Content-Type, Accept'133 X-Requested-With, Content-Type, Accept'
133 return response134 return response
134135
135 response = make_response()136 response = make_response()
136 response.headers['Access-Control-Allow-Origin'] = '*'
137 response.headers['Access-Control-Allow-Origin'] = 'http://localhost:5000'
137 response.headers['Access-Control-Allow-Headers'] = 'Origin,\138 response.headers['Access-Control-Allow-Headers'] = 'Origin,\
138 X-Requested-With, Content-Type, Accept'139 X-Requested-With, Content-Type, Accept'
139 data = {}140 data = {}
140 data_list = []141 data_list = []
142
143 if 'email' in session:
144 print 'identifed user'
145 print session['email']
146 else:
147 print 'unidentified user'
148
141 # TODO: find a better way of handling reqeust sweets149 # TODO: find a better way of handling reqeust sweets
142 try:150 try:
143 payload = json.loads(request.form['data'])151 payload = json.loads(request.form['data'])
200def searchSweets():200def searchSweets():
201 response = make_response()201 response = make_response()
202 response.status_code = 200202 response.status_code = 200
203 response.headers['Access-Control-Allow-Origin'] = '*'
203 response.headers['Access-Control-Allow-Origin'] = 'http://localhost:5000'
204 response.headers['Access-Control-Max-Age'] = '20days'204 response.headers['Access-Control-Max-Age'] = '20days'
205 response.headers['Access-Control-Allow-Headers'] = 'Origin,\205 response.headers['Access-Control-Allow-Headers'] = 'Origin,\
206 X-Requested-With, Content-Type, Accept'206 X-Requested-With, Content-Type, Accept'
211 reponse.status_code = 400211 reponse.status_code = 400
212 return response212 return response
213213
214 if args['where'] is None:
215 reponse.status_code = 400
216 return response
214 #if args['where'] is None:
215 # reponse.status_code = 400
216 # return response
217217
218 params = {}218 params = {}
219219
220 params['where'] = args.get('where')
220 if args.get('where'):
221 params['where'] = args.get('where')
221 if args.get('who'):222 if args.get('who'):
222 params['who'] = args.get('who')223 params['who'] = args.get('who')
223 if args.get('what'):224 if args.get('what'):
226 if args.get('how'):226 if args.get('how'):
227 params['how'] = args.get('how')227 params['how'] = args.get('how')
228228
229
230 print params
229 res = g.collection.find(params)231 res = g.collection.find(params)
230232
231 if res.count() < 1:233 if res.count() < 1:
360 elif request.method == "GET":360 elif request.method == "GET":
361 return app.send_static_file("sweet-authenticate.js")361 return app.send_static_file("sweet-authenticate.js")
362362
363@app.route('/auth/login', methods=['POST'])
364def authLogin():
365 response = make_response()
366 response.headers['Access-Control-Allow-Origin'] = 'http://localhost:5000'
367 response.headers['Access-Control-Allow-Credentials'] = 'true'
368 response.headers['Access-Control-Max-Age'] = '20days'
369 response.headers['Access-Control-Allow-Headers'] = 'Origin,\
370 X-Requested-With, Content-Type, Accept'
371
372 if 'assertion' not in request.form:
373 response.status_code = 400
374 return response
375
376 data = {'assertion': request.form['assertion'], 'audience':
377 MOZ_PERSONA_AUDIENCE}
378 resp = requests.post(MOZ_PERSONA_VERIFIER, data=data, verify=True)
379 print resp.status_code
380 print resp.json()
381
382 if resp.ok:
383 verified_data = json.loads(resp.content)
384 if verified_data['status'] == 'okay':
385 #session.update({'email': verified_data['email']})
386 session['email'] = verified_data['email']
387 response.status_code = 200
388 response.data = {'email': verified_data['email']}
389 return response
390
391 response.status_code = 500
392 return response
393
394@app.route('/auth/logout', methods=['POST'])
395def authLogout():
396 response = make_response()
397 response.headers['Access-Control-Allow-Origin'] = 'http://localhost:5000'
398 response.headers['Access-Control-Allow-Credentials'] = 'true'
399 response.headers['Access-Control-Max-Age'] = '20days'
400 response.headers['Access-Control-Allow-Headers'] = 'Origin,\
401 X-Requested-With, Content-Type, Accept'
402
403 if 'email' in session:
404 print 'logging out '
405 print session['email']
406 session.pop('email')
407
408 response.status_code = 200
409 return response
363410
364def make_list(res):411def make_list(res):
365 entries = []412 entries = []

templates/layout.html

2<html>2<html>
3 <head>3 <head>
4 <title>SWeeT Store</title>4 <title>SWeeT Store</title>
5 <link rel=stylesheet type=text/css href="{{ url_for('static', filename='css/style.css') }}">
6 <link rel=stylesheet type=text/css href="{{ url_for('static', filename='css/bootstrap.min.css') }}">5 <link rel=stylesheet type=text/css href="{{ url_for('static', filename='css/bootstrap.min.css') }}">
6 <link rel=stylesheet type=text/css href="{{ url_for('static', filename='css/style.css') }}">
7 {% block head %}{% endblock %}7 {% block head %}{% endblock %}
8 </head>8 </head>
9 <body>9 <body>
21 {% endif %}21 {% endif %}
22 </div>22 </div>
23 {% for message in get_flashed_messages() %}23 {% for message in get_flashed_messages() %}
24 <div class="flash">{{ message }}</div>
24 <div class="flash">{{ message }}</div>
25 {% endfor %}25 {% endfor %}
26 {% block body %}{% endblock %}26 {% block body %}{% endblock %}
27 </div>27 </div>

templates/show_entries.html

1{% extends "layout.html" %}1{% extends "layout.html" %}
2{% block body %}2{% block body %}
3 <div id="store-stats">
4 <span><b>Total Sweets:</b> {{ stats.total_sweets }}</span>
5 </div>
3 <ul class="entries unstyled">6 <ul class="entries unstyled">
4 {% if entries|len > 0 %}7 {% if entries|len > 0 %}
5 {% for entry in entries %}8 {% for entry in entries %}
16 <b>@{{ entry.who }}</b>16 <b>@{{ entry.who }}</b>
17 {% endif %}17 {% endif %}
1818
19 <b>#{{ entry.what }}</b> /{{ entry.where }} {{ entry.how|safe }}
19 <b>#{{ entry.what }}</b>
20 <a href="{{ appURL_map[entry.what] + entry.where }}" target="tab">
21 {{ entry.where }}
22 </a>
23 <p></p>
24 <p> {{ entry.how|safe }} </p>
20 {% if entry.created|len > 0 %}25 {% if entry.created|len > 0 %}
21 <small>created: {{entry.created }} UTC</small>
26 <small><i> created: {{entry.created }} UTC </i></small>
22 {% endif %}27 {% endif %}
23 <a class="pull-right" href={{ "/posts/" + entry.id }}>28 <a class="pull-right" href={{ "/posts/" + entry.id }}>
24 <i class="icon-share-alt"></i>29 <i class="icon-share-alt"></i>