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.
| | | | 4 | h1 { border-bottom: 2px solid #eee; } | 4 | h1 { border-bottom: 2px solid #eee; } |
---|
5 | h2 { font-size: 1.2em; } | 5 | h2 { font-size: 1.2em; } |
---|
6 | | 6 | |
---|
7 | .page { margin: 2em auto; width: 35em; border: 5px solid #ccc; | | .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; } | | .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; } |
---|
| | 19 | small { font-size: 0.9em; } |
---|
| | | | 16 | from urllib import unquote_plus | 16 | from urllib import unquote_plus |
---|
17 | import json | 17 | import json |
---|
18 | import conf | 18 | import conf |
---|
| | 19 | import requests |
---|
19 | | 20 | |
---|
20 | # TODO: | 21 | # TODO: |
---|
21 | # restify | 22 | # restify |
---|
… | | … | |
---|
29 | # classes! | 29 | # classes! |
---|
30 | # sqlAlchemy | 30 | # sqlAlchemy |
---|
31 | # Postgres | 31 | # Postgres |
---|
| | 32 | # Persona, Auth in API endpoints |
---|
32 | | 33 | |
---|
33 | # TODO: move this in a config file | 34 | # TODO: move this in a config file |
---|
34 | # configuration | 35 | # configuration |
---|
35 | DATABASE = 'alipiBlog' | | DATABASE = 'alipiBlog' |
---|
| | 36 | | | | 37 | DATABASE = 'sweets_production' |
---|
36 | COLLECTION_NAME = 'posts' | 38 | COLLECTION_NAME = 'posts' |
---|
37 | DEBUG = True | 39 | DEBUG = True |
---|
38 | SECRET_KEY = conf.SECRET_KEY | 40 | SECRET_KEY = conf.SECRET_KEY |
---|
… | | … | |
---|
42 | PASSWORD = 'default' | 42 | PASSWORD = 'default' |
---|
43 | DB_PORT = 27017 | 43 | DB_PORT = 27017 |
---|
44 | DB_HOST = 'localhost' | 44 | DB_HOST = 'localhost' |
---|
45 | URL = "http://localhost:5001" | | URL = "http://localhost:5001" |
---|
| | 45 | URL = 'http://localhost:5001' | | | 46 | MOZ_PERSONA_VERIFIER = 'https://verifier.login.persona.org/verify' |
---|
| | 47 | MOZ_PERSONA_AUDIENCE = 'http://localhost:5000' |
---|
46 | | 48 | |
---|
| | 49 | appURL_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 :P | 55 | # ^ ... It's going to be big now :P |
---|
49 | app = Flask(__name__) | 56 | app = Flask(__name__) |
---|
… | | … | |
---|
73 | return False | 73 | return False |
---|
74 | return True | 74 | return True |
---|
75 | | 75 | |
---|
| | 76 | |
---|
76 | def getUsers(): | 77 | def 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 users | 83 | return users |
---|
84 | | 84 | |
---|
| | 85 | def gatherStats(coll): |
---|
| | 86 | stats = {} |
---|
| | 87 | stats['total_sweets'] = coll.count() |
---|
| | 88 | return stats |
---|
| | 89 | |
---|
85 | @app.before_request | 90 | @app.before_request |
---|
86 | def init_db(): | 91 | def 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) |
---|
90 | | 96 | |
---|
91 | | 97 | |
---|
92 | @app.teardown_request | 98 | @app.teardown_request |
---|
… | | … | |
---|
111 | | 111 | |
---|
112 | @app.route('/') | 112 | @app.route('/') |
---|
113 | def show_entries(): | 113 | def show_entries(): |
---|
114 | print 'request:' | | print 'request:' |
---|
115 | print request.method | | print request.method |
---|
116 | res = g.collection.find().sort('_id',direction=-1) | | 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) | | 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) |
---|
119 | | 118 | |
---|
120 | | 119 | |
---|
121 | # TODO: understand if we really need the OPTIONS | 120 | # 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 = 200 | 128 | response.status_code = 200 |
---|
129 | response.headers['Access-Control-Allow-Origin'] = '*' | | 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 response | 134 | return response |
---|
134 | | 135 | |
---|
135 | response = make_response() | 136 | response = make_response() |
---|
136 | response.headers['Access-Control-Allow-Origin'] = '*' | | 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 sweets | 149 | # 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']) |
---|
… | | … | |
---|
200 | def searchSweets(): | 200 | def searchSweets(): |
---|
201 | response = make_response() | 201 | response = make_response() |
---|
202 | response.status_code = 200 | 202 | response.status_code = 200 |
---|
203 | response.headers['Access-Control-Allow-Origin'] = '*' | | 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 = 400 | 211 | reponse.status_code = 400 |
---|
212 | return response | 212 | return response |
---|
213 | | 213 | |
---|
214 | if args['where'] is None: | | if args['where'] is None: |
---|
215 | reponse.status_code = 400 | | reponse.status_code = 400 |
---|
216 | return response | | return response |
---|
| | 214 | #if args['where'] is None: | | | 215 | # reponse.status_code = 400 |
---|
| | 216 | # return response |
---|
217 | | 217 | |
---|
218 | params = {} | 218 | params = {} |
---|
219 | | 219 | |
---|
220 | params['where'] = args.get('where') | | 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') |
---|
228 | | 228 | |
---|
| | 229 | |
---|
| | 230 | print params |
---|
229 | res = g.collection.find(params) | 231 | res = g.collection.find(params) |
---|
230 | | 232 | |
---|
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") |
---|
362 | | 362 | |
---|
| | 363 | @app.route('/auth/login', methods=['POST']) |
---|
| | 364 | def 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']) |
---|
| | 395 | def 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 |
---|
363 | | 410 | |
---|
364 | def make_list(res): | 411 | def make_list(res): |
---|
365 | entries = [] | 412 | entries = [] |
---|
| | | | 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') }}"> | | <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> | | <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> |
---|
| | | | 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 %} |
---|
18 | | 18 | |
---|
19 | <b>#{{ entry.what }}</b> /{{ entry.where }} {{ entry.how|safe }} | | <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> | | <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> |
---|