Commit f867edb11e372a8dc06e4236e6829f76f62bf818

Implementing OAuth 2.0 enabled APIs

  - APIs: GET /api/sweets/q, POST /api/sweets, GET /api/users/me are now OAuth
    2.0 enabled. That means registered clients can use these APIs to sweet to
swtstore and retrieve swts from the swtstore.
  - Implement CORS headers in APIs and Persona login URLS, so that swtstore can
    support cross-domain, cross-origin requests.
  - README changes to better explain how to install and run this application.
README.md
(61 / 25)
  
88------------
99
1010This is the sweet store application.
11
1211The store for the decentralized, semantic, social web tweets a.k.a SWeeTs!
1312
13This README is about installing, configuring and running/deploying this
14application. If you want to know more about SWeeTs, please go to
15[wiki.janastu.org/Sweet_Web](http://janastu.org/technoscience/index.php/Sweet_Web) and
16[trac.pantoto.org/sweet-web](http://trac.pantoto.org/sweet-web).
17
1418This application acts as the repository store for all the SWeeTs that are
15generated from the clients registered with the sweet store. It also provides
19generated from the clients registered with the sweet store. It provides
1620query APIs to query the SWeeTs.
1721
22It also provides user management to manage the users of the swtstore.
23
24All the APIs of the swtstore are accessed by using an access token which is
25generated by the swtstore when an user authorizes a third-party application
26through OAuth.
27
1828Sweet store provides the following APIs:
1929
2030 - [GET] /api/sweets/<id>: Get a specific SWeeT by its id.
2131
32 - [GET] /api/sweets/q?who=<username>&what=<contextname>&where=<URL> :
33 This API is for querying sweet based on the who, what and where
34 parameters.
35 This API do not support querying based on parameters mentioned in how,
36 but will be supported in future. Right now, the client can get sweets
37 based on the above mentioned three parameters, and as the 'how' part is
38 a JSON, it is trivial to do further filtering based on parameters of
39 'how' by the client.
40
2241 - [POST] /api/sweets : Post a SWeeT to this swtstore with the data in the
23 body of the request. Only registered applications can sweet to sweet store.
42 body of the request. The data or payload is a list of sweets. Even if you
43 are sending one sweet, make sure that the sweet is in a list.
2444
25 - [POST] /api/context : Create a new context on the swtstore.
45 - [GET] /api/users/me : Get a JSON details of the current user logged in.
2646
2747
28Any client side application can communicate with the sweet store using these
29APIs.
48Any third-party client side application can communicate with the swtstore
49using these APIs, provided they have a valid access token.
3050
3151
3252Installing
6262For more information on supported databases see
6363[here](http://docs.sqlalchemy.org/en/rel_0_9/dialects/index.html).
6464
65_Important:_
66__So once you are sure you have Python and a relational database (like
67MySQL/Postgresql etc.) installed. You can go ahead and follow these steps:__
65___ Once you are sure you have Python and a relational database (like
66MySQL/Postgresql etc.) installed. You can go ahead and follow these steps:___
6867
69* Clone the repository from [https://git.pantoto.org/sweet-web/sweet-web-engine]
70 (https://git.pantoto.org/sweet-web/sweet-web-engine) OR you can download the
68* Clone the repository from [https://git.pantoto.org/sweet-web/sweet-web-engine](https://git.pantoto.org/sweet-web/sweet-web-engine)
69 OR you can download the
7170 code from the same link.
7271
73* Initialize a python virtual environment using virtualenv in the same place
74 where you cloned the reposiory in the above step. Now, activate the
75 environment ``$ source <path/to/your/current-virtual-env>/bin/activate ``
72* It is strongly recommended to do the installation inside a virtual environment.
73 If you think, you know what you are doing and don't need the virtual
74 environment, you can skip to the next step.
75 Initialize a python virtual environment using `virtualenv` in the same directory
76 where you cloned the repository in the above step. Now, activate the
77 environment
7678
77 See
78 [http://www.virtualenv.org/en/latest/virtualenv.html]
79 (http://www.virtualenv.org/en/latest/virtualenv.html) for more details.
79 > ``$ source <path/to/your/current-virtual-env>/bin/activate ``
8080
81 See [http://www.virtualenv.org/en/latest/virtualenv.html](http://www.virtualenv.org/en/latest/virtualenv.html) for more details about `virtualenv`.
82
8183* Run the setup.py script to install `` python setup.py install ``
8284
8385You're done installing swtstore. Now you need to configure it to run.
9191* Copy the contents of ``sample_config.py`` inside the ``swtstore`` directory
9292 into ``config.py`` inside ``swtstore`` directory itself.
9393
94 Assuming you are using a Unix based system, and you are in the root directory
94 Assuming you are using a *-nix based system, and you are in the root directory
9595 of the codebase,
9696
9797 `` $ cp swtstore/sample_config.py swtstore/config.py``
9898
9999* Edit the config.py file, and change the values accordingly.
100100
101* Now, you have to setup the database for the swtstore application. But
102 fortunately, the creation of database and tables have also been scripted, so
103 all you need to do is run the ``dbsetup.py`` script.
104 `` $ python dbsetup.py ``
101105
106**NOTE:** Please remember that all these configuration step is necessary and is
107required wether you are running the application locally or deploying it on a
108server.
102109
110
111
103112Running the server locally
104113--------------------------
105114
106115Run the runserver.py script to run the server locally,
107116
108`` python runserver.py ``
117> `` $ python runserver.py ``
109118
110This runs the application locally, on port 5001
119This runs the application locally, on port 5001. So you can direct your browser
120to http://localhost:5001
111121
112122
113123
114124Deploying the application
115125-------------------------
116126
117The wsgi script to deploy the application is present.
118Point your webserver like Apache, or Nginx to point to the swtstore.wsgi
127SwtStore is deployed as a WSGI application server.
128
129The wsgi script to deploy the application is provided (its called
130`swtstore.wsgi`).
131Point your webserver like Apache, or Nginx to point to the `swtstore.wsgi`
119132script.
120133
121134See Apache WSGI configuration here:
122[http://modwsgi.readthedocs.org/en/latest/configuration-directives/WSGIScriptAlias.html]
123(http://modwsgi.readthedocs.org/en/latest/configuration-directives/WSGIScriptAlias.html)
135[http://modwsgi.readthedocs.org/en/latest/configuration-directives/WSGIScriptAlias.html](http://modwsgi.readthedocs.org/en/latest/configuration-directives/WSGIScriptAlias.html)
124136
137TODO: [insert Nginx WSGI config link]
125138
139
126140Help / Feedback
127141---------------
128142
129143If you need any help, or have any questions, comments or feedback, you can contact at
130rayanon or arvind or bhanu @servelots.com
144rayanon or arvind or bhanu at servelots.com
131145
132146You can also join channel #servelots on freenode network, using your favourite
133147IRC client. We usually hang out at #servelots.
dbsetup.py
(10 / 3)
  
1# coding utf-8
1# -*- coding: utf-8 -*-
2# swtstore->dbsetup.py
23
4# Create and setup databases for the first time run of the application
5
36import sys, os
47
8# Get the path to the base directory of the app
59BASE_DIR = os.path.join(os.path.dirname(__file__))
610
11# append the path to the WSGI env path
712sys.path.insert(0, BASE_DIR)
813
14# Import and create the app; also get the db instance from the current app
915from swtstore import create_app, getDBInstance
1016
1117app = create_app()
1218
1319db = getDBInstance()
1420
15# Import all modules which represents a SQLAlchemy model
16# They have correspondin tables that are needed to be created
21# Import all modules which represents a SQLAlchemy model;
22# they have corresponding tables that are needed to be created
1723from swtstore.classes.models import Sweet, Context, Client
1824from swtstore.classes.models.um import User, Group, Membership
1925
26# Create them!
2027db.create_all()
  
11# -*- coding: utf-8 -*-
2# swtstore->runserver.py
3
24# Script to run the application server in development mode
35
46import sys, os
5from swtstore import create_app, getDBInstance
67
78# Get the path to the base directory of the app
89BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__)))
1111# append the path to the WSGI env path
1212sys.path.insert(0, BASE_DIR)
1313
14# Import and create the app
15from swtstore import create_app
16
1417app = create_app()
1518
19# Run the server if this script is directly executed
20# Presumably, this is development mode
1621if __name__ == '__main__':
1722 app.run(debug=True, host='0.0.0.0', port=5001)
setup.py
(3 / 3)
  
1111swtstore
1212--------
1313
14The sweet store for decentralized, semantic, social web tweets a.k.a SWeeTs!!
14The store for decentralized, semantic, social web tweets a.k.a SWeeTs!!
1515
1616"""
1717from setuptools import setup
3030 version='0.1 - alpha',
3131 url='https://git.pantoto.org/sweet-web',
3232 license='BSD',
33 author='Halwai',
34 author_email='rayanon@servelots.com',
33 author='swtstore authors',
34 author_email='rayanon004@gmail.com',
3535 description='Server-side store for decentralized, semantic, social, web\
3636 tweets',
3737 long_description=__doc__,
  
99from classes.database import db
1010from config import DefaultConfig
1111from classes import views
12#from something import oauth
13
1412#from classes import models
13from classes import oauth
1514
1615__all__ = ['create_app', 'getDBInstance']
1716
2121 (views.api, '/api'),
2222 (views.user, '/users'),
2323 (views.context, '/contexts'),
24 (views.app, '/apps')
24 (views.app, '/apps'),
25 (views.Oauth, '/oauth')
2526)
2627
2728
6565def configure_extensions(app):
6666 db.init_app(app)
6767 db.app = app
68 #oauth.init_app(app)
68 oauth.init_app(app)
6969
7070# return the current db instance
7171# TODO: is this needed so much?
  
2929
3030 _is_private = db.Column(db.Boolean)
3131
32 _host_url = db.Column(db.String(60))
33
3234 _redirect_uris = db.Column(db.Text)
3335 _default_scopes = db.Column(db.Text)
3436
3537
3638 @property
39 def client_id(self):
40 return self.id
41
42 @property
3743 def client_type(self):
3844 if self._is_private:
3945 return 'private'
4046 return 'public'
4147
4248 @property
49 def host_url(self):
50 return self._host_url
51
52 @property
4353 def redirect_uris(self):
4454 if self._redirect_uris:
4555 return self._redirect_uris.split()
6666 return []
6767
6868 def __repr__(self):
69 return '[Client: <%s> : <%s>]' % (self.id, self.name)
69 return '<Client: %s :: ID: %s>' % (self.name, self.id)
7070
7171 def __str__(self):
72 return '[Client: <%s> : <%s>]' % (self.id, self.name)
72 return '<Client: %s :: ID: %s>' % (self.name, self.id)
7373
7474
7575 # create and persist the client to the database
124124 """
125125 The final token to be used by a client
126126 """
127
128 __tablename__ = 'tokens'
129
127130 id = db.Column(db.Integer, primary_key=True)
128131
129132 client_id = db.Column(db.String(40), db.ForeignKey('clients.id'),
156156
157157@oauth.clientgetter
158158def loadClient(client_id):
159 return Client.query.filter_by(id=client_id).first()
159 print '@oauth.clientgetter'
160 #return Client.query.filter_by(id=client_id).first()
161 return Client.query.get(client_id)
160162
161163@oauth.grantgetter
162164def loadGrant(client_id, code):
165 print '@oauth.grantgetter'
163166 return Grant.query.filter_by(client_id=client_id, code=code).first()
164167
165168@oauth.grantsetter
166169def saveGrant(client_id, code, request, *args, **kwargs):
170 print '@oauth.grantsetter'
167171 expires = datetime.utcnow() + timedelta(seconds=100)
168172 grant = Grant(
169173 client_id = client_id,
183183
184184@oauth.tokengetter
185185def loadToken(access_token=None, refresh_token=None):
186 print '@oauth.tokengetter'
186187 if access_token:
187188 return Token.query.filter_by(access_token=access_token).first()
188189 elif refresh_token:
191191
192192@oauth.tokensetter
193193def saveToken(token, request, *args, **kwargs):
194 print '@oauth.tokensetter'
195
194196 toks = Token.query.filter_by(client_id=request.client.id,
195197 user_id=request.user.id)
196198 # make sure that every client has only one token connected to a user
197199 for t in toks:
198200 db.session.delete(t)
201
199202 expires_in = token.pop('expires_in')
200203 expires = datetime.utcnow() + timedelta(seconds=expires_in)
201204
206206 access_token = token['access_token'],
207207 refresh_token = token['refresh_token'],
208208 token_type = token['token_type'],
209 _scopes = token['scopes'],
209 _scopes = token['scope'],
210210 expires = expires,
211211 client_id = request.client.id,
212 user = request.user.id
212 user = request.user
213213 )
214214 db.session.add(tok)
215215 db.session.commit()
216216 return tok
217
218@oauth.usergetter
219def getUser():
220 return User.getCurrentUser()
  
5050 print self.created
5151 return {
5252 'id': self.id,
53 'who': self.who,
53 'who': self.who.username,
54 'user_id': self.user_id,
5455 'what': self.what.name,
5556 'context_id': self.context_id,
5657 'where': self.where,
  
11
22
3def make_cross_origin_headers(response):
4 response.headers['Access-Control-Allow-Origin'] = 'http://localhost:5000'
5 response.headers['Access-Control-Allow-Origin'] = 'http://localhost'
6 response.headers['Access-Control-Max-Age'] = '20days'
7 response.headers['Access-Control-Allow-Headers'] = 'Origin,\
8 X-Requested-With, Content-Type, Accept'
3def make_cross_origin_headers(response, host_url):
4 print 'client\'s host_url: %s' % host_url
5 response.headers['Access-Control-Allow-Origin'] = host_url
6 response.headers['Access-Control-Max-Age'] = '3600'
7 response.headers['Access-Control-Allow-Credentials'] = 'true'
8 response.headers['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept'
9
10 print 'Updated headers'
11 print response.headers
912
1013 return response
  
33from .user import user
44from .context import context
55from .app import app
6from .oauth import Oauth
  
1from flask import Module, jsonify, request, make_response, abort, g
2import json
1from flask import Module, jsonify, request, make_response, abort, g, json
2#import json
33from sqlalchemy.exc import IntegrityError
44
55from swtstore.classes.models import Context
77from swtstore.classes.exceptions import AlreadyExistsError
88from swtstore.classes.utils import urlnorm # normalize URLs
99from swtstore.classes.utils.httputils import make_cross_origin_headers
10from swtstore.classes import oauth
1011
1112
1213api = Module(__name__)
1314
1415
15
1616# Get a specific sweet
1717@api.route('/sweets/<int:id>', methods=['GET'])
1818def getSweetById(id):
3030
3131# Post a sweet to the sweet store
3232@api.route('/sweets', methods=['OPTIONS', 'POST'])
33def createSweet():
33@oauth.require_oauth('email', 'sweet')
34def createSweet(oauth_request):
3435
3536 response = make_response()
3637
38 client = oauth_request.client
39
3740 #TODO: check if response is coming from a registered client
38 response = make_cross_origin_headers(response)
41 response = make_cross_origin_headers(response, client.host_url)
3942
4043 if request.method == 'OPTIONS':
4144 response.status_code = 200
5656 print 'new sweet payload recvd..'
5757 print payload
5858
59 if 'who' not in payload and 'what' not in payload and 'where' not in\
60 payload and 'how' not in payload:
59 # the payload has to be a list; a list of swts
60 for each in payload:
61 if 'what' not in each and 'where' not in\
62 each and 'how' not in each:
6163
62 print 'Invalid Request..'
63 abort(400)
64 print 'Invalid Request..'
65 abort(400)
6466
65 #who = User.getUserByName(payload['who'])
67 # all ok. create swts from the list now
6668
67 what = Context.query.filter_by(name=payload['what']).first()
69 swts = []
70 for each in payload:
6871
69 if what is None:
70 print 'Context doesn\'t exist'
71 g.error = 'Context doesn\'t exist'
72 abort(400) # this context doesn't exist!
72 what = Context.query.filter_by(name=each['what']).first()
7373
74 print 'SWEET DATA'
75 print '------------'
76 print payload['who']
77 print what
78 print payload['where']
79 print payload['how']
80 print '-------------'
74 if what is None:
75 print 'Context doesn\'t exist'
76 g.error = 'Context doesn\'t exist'
77 abort(400) # this context doesn't exist!
8178
82 new_sweet = Sweet(payload['who'], what, payload['where'], payload['how'])
79 # Get the authenticated user from the oauth request object.
80 # Older swtr clients sending `who` in string will be ignored.
81 who = oauth_request.user
8382
84 print new_sweet
85 new_sweet.persist()
83 print 'SWEET DATA'
84 print '------------'
85 print who
86 print what
87 print each['where']
88 print each['how']
89 print '-------------'
8690
91 new_sweet = Sweet(who, what, each['where'], each['how'])
92
93 new_sweet.persist()
94 print new_sweet
95 swts.append(new_sweet.id)
96
8797 response.status_code = 200
98 response.data = json.dumps(swts)
8899 return response
89100
90101
91
92102# The Sweet query API: /sweets/q?who=<>&what=<>&where=<>
93103# args: who, what, where
94@api.route('/sweets/q', methods=['GET'])
95def querySweets():
104@api.route('/sweets/q', methods=['GET', 'OPTIONS'])
105@oauth.require_oauth('sweet')
106def querySweets(oauth_request):
96107
108 response = make_response()
109 response = make_cross_origin_headers(response,
110 oauth_request.client.host_url)
111
112 if request.method == 'OPTIONS':
113 reponse.status_code = 200
114 return response
115
97116 args = request.args
98117
99118 # if no arguments are passed, its an invalid request
135135 print 'recvd params'
136136 print params
137137
138 response = make_response()
139138
140139 sweets = Sweet.query.filter_by(**params).all()
141140
142141 if len(sweets) == 0:
142 print 'No sweets found to satisfy query..'
143143 abort(404)
144144
145145 swts = [i.to_dict() for i in sweets]
176176
177177
178178# Create a new Sweet Context
179@oauth.require_oauth('email', 'context')
179180@api.route('/contexts', methods=['POST'])
180181def createContext():
181182
213213 res = new_context.persist()
214214
215215 response.status_code = 200
216 return response
217
218
219# Send back logged in user data
220@api.route('/users/me', methods=['GET', 'OPTIONS'])
221@oauth.require_oauth('email')
222def getCurrentUser(oauth_request):
223 response = make_response()
224 response = make_cross_origin_headers(response,
225 oauth_request.client.host_url)
226 response.status_code = 200
227
228 if request.method == 'OPTIONS':
229 return response
230
231 response.data = json.dumps(oauth_request.user.to_dict())
216232 return response
  
33# classes/views/apps.py
44
55
6from flask import Module, jsonify, request, render_template, redirect, url_for
6from flask import Module, jsonify, request, render_template, redirect,\
7 url_for, flash, abort
8
79from hashlib import md5
810from werkzeug.security import gen_salt
911
1012from swtstore.classes.models import Client
1113from swtstore.classes.models.um import User
14from swtstore.classes.utils import urlnorm
1215
1316
1417app = Module(__name__)
2121def list():
2222 current_user = User.getCurrentUser()
2323 if current_user is None:
24 return redirect(url_for('index'))
24 return redirect(url_for('frontend.index'))
2525
2626 her_apps = Client.getClientsByCreator(current_user.id)
2727 print 'her_apps'
3434def register():
3535 current_user = User.getCurrentUser()
3636 if current_user is None:
37 return redirect(url_for('index'))
37 return redirect(url_for('frontend.index'))
3838
3939 user = current_user.to_dict()
4040
4242 return render_template('register_app.html', user=user)
4343
4444 elif request.method == 'POST':
45 if not request.form.get('name'):
46 abort(400)
45 req_fields = ['name', 'host_url', 'redirect_uris', 'scopes']
46 for field in req_fields:
47 if not request.form.get(field):
48 abort(404)
4749
4850 new_app = Client(
4951 id = gen_salt(40),
5353 name = request.form.get('name'),
5454 description = request.form.get('desc'),
5555 user_id = current_user.id,
56 _redirect_uris = request.form.get('redirect_uris'),
57 _default_scopes = request.form.get('scopes'),
56 _host_url = request.form.get('host_url'),
57 _redirect_uris = urlnorm(request.form.get('redirect_uris')),
58 _default_scopes = ' '.join(request.form.get('scopes').split(',')),
5859 _is_private = False
5960 )
6061 new_app.persist()
  
1# -*- coding utf-8 -*-
2# classes/views/oauth.py
3
4from flask import Module, jsonify, request, render_template, redirect, url_for
5import json
6
7from swtstore.classes import oauth
8from swtstore.classes.models.um import User
9from swtstore.classes.models import Client
10
11
12Oauth = Module(__name__)
13
14@Oauth.route('/authorize', methods=['GET', 'POST'])
15@oauth.authorize_handler
16def authorize(*args, **kwargs):
17 current_user = User.getCurrentUser()
18 if current_user is None:
19 return render_template('oauth_login.html')
20
21 if request.method == 'GET':
22 client_id = kwargs.get('client_id')
23 client = Client.query.get(client_id)
24 print '/authorize: '
25 print client
26 kwargs['client'] = client
27 kwargs['user'] = current_user
28 print kwargs
29 return render_template('authorize.html', **kwargs)
30
31 confirm = request.form.get('confirm', 'no')
32 print confirm
33 return confirm == 'yes'
34
35@Oauth.route('/token', methods=['GET', 'POST'])
36@oauth.token_handler
37def access_token():
38 #print request.form
39 print 'access token touched..'
40 return None
41
42@Oauth.route('/errors')
43def error():
44 return jsonify(error=request.args.get('error'))
  
2121def login():
2222
2323 response = make_response()
24 response = make_cross_origin_headers(response)
24 #response = make_cross_origin_headers(response)
2525
2626 if 'assertion' not in request.form:
2727 response.status_code = 400
2929
3030 print request.remote_addr
3131 data = {'assertion': request.form['assertion'], 'audience':
32 'http://localhost:5001'}
32 config.SWTSTORE_URL}
3333
3434 resp = requests.post(config.MOZ_PERSONA_VERIFIER, data=data, verify=True)
3535 print resp.status_code
6464def logout():
6565
6666 response = make_response()
67 response = make_cross_origin_headers(response)
67 #response = make_cross_origin_headers(response)
6868
6969 if 'email' in session:
7070 print 'logging out '
  
2929
3030 # The Mozilla Persona Verifier Host. Leave it as it is.
3131 MOZ_PERSONA_VERIFIER = 'https://verifier.login.persona.org/verify'
32
33 # The URL at which this app, swtstore, is deployed.
34 SWTSTORE_URL = 'http://demo.swtr.us'
35
36 # Bearer token expiry
37 OAUTH2_PROVIDER_TOKEN_EXPIRES_IN = 3600
  
1{% extends "layout.html" %}
2{% block body %}
3<h4> Allow Access ?</h4>
4<p class="text-muted">
5 The following application wants to get permission to do stuff(?) on the <i>swt
6 web</i> platform on your behalf.
7</p>
8
9<form role="form" method="POST" action="{{ url_for('oauth.authorize') }}">
10 <div class="form-group">
11 <p class="form-control-static text-primary">
12 {{ client.name }} at {{ client.host_url }} wants
13 to get permission to post data to swt store
14 </p>
15 </div>
16 <div class="form-group">
17 <label class="control-label">Scopes</label>
18 <div class="">
19 <p class="form-control-static">{{ scopes|join(',') }}</p>
20 </div>
21 </div>
22 <input type="hidden" name="client_id" value="{{ client.id }}">
23 <input type="hidden" name="scope" value="{{ scopes|join(' ') }}">
24 <input type="hidden" name="response_type" value="{{ response_type }}">
25 {% if state %}
26 <input type="hidden" name="state" value="{{ state }}">
27 {% endif %}
28 <button type="submit" name="confirm" value="yes" class="btn btn-primary">Allow</button>
29 <button type="submit" name="confirm" value="no" class="btn btn-default">Deny</button>
30</form>
31
32{% endblock %}
33
34{% block scripts %}
35
36<script>
37 var created = new Date("{{ user.created }}");
38 window.onload = function() {
39 $('#user-created').html(created.toString());
40 };
41</script>
42
43{% endblock %}
  
1<!doctype html>
2<body>
3 <em> Oops! Looks like you are not authorized to make this request!!</em>
4</body>
  
1010 {% for sweet in sweets %}
1111 <li>
1212 <span class="who">
13 <a href="#"> @{{ sweet.who }} </a>
13 <a href="#">
14 @{{ sweet.who.username }}
15 </a>
1416 </span>
1517 <span class="what">
16 #{{ sweet.what.name }}
18 <b> #{{ sweet.what.name }} </b>
1719 </span>
1820 <span class="where">
1921 {{ sweet.where }}
  
44 <meta charset="utf-8">
55 <meta http-equiv="X-UA-Compatible" content="IE=edge">
66 <meta name="viewport" content="width=device-width, initial-scale=1">
7 <title>SWeeT Store</title>
7 <title>SWT Store</title>
88 <link rel=stylesheet type=text/css href="{{ url_for('.static', filename='css/bootstrap.min.css') }}">
99 <link rel=stylesheet type=text/css href="{{ url_for('.static', filename='css/bootstrap-theme.min.css') }}">
1010 <link rel=stylesheet type=text/css href="{{ url_for('.static', filename='css/style.css') }}">
5252 </small></a>
5353 </li>
5454 <li class="divider"></li>
55 <li> <a href="#" id="logout">Logout</a> </li>
55 <li> <a href="#" id="logout"> Logout </a> </li>
5656 </ul>
5757 </li>
5858 {% endif %}
6161 </div>
6262 <div class="notif">
6363 {% for message in get_flashed_messages() %}
64 <div class="flash">{{ message }}</div>
64 <div class="alert alert-danger">{{ message }}</div>
6565 {% endfor %}
6666 </div>
6767 <div class="content">
  
88 <div class="pull-right">
99 <span class="glyphicon glyphicon-trash"></span>
1010 </div>
11 <div> {{ app.name }} </div>
12 <div> {{ app.description }} </div>
11 <div><h4> {{ app.name }} </h4></div>
12 <div><h5> {{ app.description }} </h5></div>
1313 <div>APP ID: {{ app.id }} </div>
1414 <div> APP Secret: {{ app.client_secret }} </div>
15 <div>Host URL: {{ app.host_url }} </div>
16 <div>Redirect URL: {{ app.redirect_uris|join(', ') }} </div>
17 <div>Scopes: {{ app.default_scopes|join(', ') }} </div>
1518 </div>
1619 {% endfor %}
1720 </ul>
  
1515 </textarea>
1616 </div>
1717 <div class="form-group">
18 <label for="host_url">Host URL</label>
19 <p class="help-block">URL where your app is deployed</p>
20 <input type="text" class="form-control" name="host_url" id="host_url" placeholder="host URL">
21 </div>
22 <div class="form-group">
1823 <label for="redirect_uris">Redirect URLs</label>
1924 <p class="help-block">Your app URL where it should be redirected once the user is authorized.</p>
2025 <input type="text" class="form-control" name="redirect_uris" id="redirect_uris" placeholder="redirect URLs">
3030 </div>
3131 <button type="submit" class="btn btn-primary">Register</button>
3232</form>
33{% endblock %}
34{% block scripts %}
35<script>
36 // form validation
37 /*$('form').on('submit', function(event) {
38 console.log('here..');
39 if(!$('#name').val()) {
40 event.preventDefault();
41 $('#name').parent().addClass('has-feedback');
42 $('#name').parent().addClass('has-error');
43 return false;
44 }
45 if(!$('#host_url').val()) {
46 event.preventDefault();
47 $('#host_url').parent().addClass('has-feedback');
48 $('#name').parent().addClass('has-error');
49 return false;
50 }
51 if(!$('#redirect_uris').val()) {
52 event.preventDefault();
53 $('#redirect_uris').parent().addClass('has-feedback');
54 $('#name').parent().addClass('has-error');
55 return false;
56 }
57 if(!$('#scopes').val()) {
58 event.preventDefault();
59 $('#scopes').parent().addClass('has-feedback');
60 $('#name').parent().addClass('has-error');
61 return false;
62 }
63 });*/
64</script>
3365{% endblock %}