-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmatrix.py
More file actions
308 lines (257 loc) · 10.9 KB
/
matrix.py
File metadata and controls
308 lines (257 loc) · 10.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
import json
from os import urandom, environ as env
from functools import wraps
from flask import Flask, jsonify, redirect, render_template, session, url_for, request, flash, abort
from authlib.flask.client import OAuth
from six.moves.urllib.parse import urlencode
from pymongo import MongoClient
from datetime import datetime
from flaskext.markdown import Markdown
from bson.objectid import ObjectId
import re
app = Flask(__name__)
app.secret_key = urandom(24)
# Flask Utility to interpret Markdown [Used in html pages with jinja as "{text|markdown}" filter]
Markdown(app)
# OAuth utility Auth0 Used -> https://auth0.com Check it out
oauth = OAuth(app)
######## DB Connections
connection = MongoClient()
# MongoDB DB name: matrix
db = connection['matrix']
# YearBook Collection
# Schema:
# {name, roll_no(int), batch(int), course, testimonials [only .md file ids], titles [not using right now]}
yearbook = db.yearbook
# Testimonials Collection
# Schema:
# {roll_no(int), author_id [LinkedIn Retrieved], author_name, datetime}
testimonials = db.testimonials
# Profiles Collection
# Schema:
# {roll_no(int), id [LinkedIn Retrieved], name, batch(int)}
profiles = db.profiles
######## Setting up Environment Variables -
# We do this so that our code doesn't contain sensitive info and we can easily share it,
# although right now we have exposed it on github,
# which in principle is dangerous and can be exploited by someone else ;P
# CALLBACK_URL = env(['CALLBACK_URL'])
# AUTH0_ID = env(['CLIENT_ID_AUTH0'])
# AUTH0_SECRET = env(['CLIENT_SECRET_AUTH0'])
AUTH0_ID = 'ZY0nrFukhFZYENGZlKus01I8IlhsCzCa'
AUTH0_SECRET = 'ejH_iKscvD91y9l9r_IfiPVkMdNDzW9eGYwFxXK-hVUstceeJSlyL856ERuB1LMY'
CALLBACK_URL = 'http://localhost:5000/callback/'
# AUTH0 API Object, This has all methods required for necessary Auth0 operations
auth0 = oauth.register(
'auth0',
client_id=AUTH0_ID,
client_secret=AUTH0_SECRET,
api_base_url='https://matrix-iitg.auth0.com',
access_token_url='https://matrix-iitg.auth0.com/oauth/token',
authorize_url='https://matrix-iitg.auth0.com/authorize',
client_kwargs={
'scope': 'openid profile',
},
)
# Function to check if a url function is accessed without login, if it is then redirect to main page
# FOR TESTING PURPOSES IF AUTH0 ISN'T WORKING REMOVE THIS FROM THE CORRESPONDING VIEW AND USE IT DIRECTLY
def requires_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
if 'profile' not in session:
# Redirect to Login page here
flash('You need to be logged in before accessing this page.')
return redirect('/')
return f(*args, **kwargs)
return decorated
######## Views - Functions that renders html pages
# Basic matrix intro page
@app.route('/')
def index():
return render_template('index.html')
# Dimension page
@app.route('/dimension/')
def dimension():
return render_template('dimension.html')
# Team Page
@app.route('/team/')
def team():
return render_template('team.html')
# Login page
@app.route('/login')
def login():
return auth0.authorize_redirect(redirect_uri=CALLBACK_URL,
audience='https://matrix-iitg.auth0.com/userinfo')
# Callback Page -> After login Auth0 service redirects us here
# as is mentioned in authorize_redirect function in login view
@app.route('/callback/')
def callback_handling():
# Handles response from token endpoint
auth0.authorize_access_token()
response = auth0.get('userinfo')
userinfo = response.json()
# Store the user information in flask session.
session['jwt_payload'] = userinfo
session['profile'] = {
'user_id': userinfo['sub'],
'name': userinfo['name'],
'picture': userinfo['picture']
}
return redirect(url_for('verification'))
# First time login redirects to verification page where people enter their roll_no and batch
# There's one thing getting both roll_no and batch is actually redundant, roll_no itself is sufficient
@app.route('/verify/', methods=['GET', 'POST'])
def verification():
if request.method == 'GET':
try:
# If this fails it means somebody is accessing this page without getting redirected from login
user_id = session['profile']['user_id']
except:
# So if fails redirect to error page you are at wrong place
return abort(404)
# If this person is logging in not for the first time then his user_id must be there in profiles collection
if profiles.count_documents({'id': user_id}):
return redirect(url_for('dashboard'))
# If not then redirect to fill up the form
else:
return render_template('verification.html')
# Request.method is POST hence information is to be submitted
else:
# Validate the request parameters
roll_no = request.form['roll_no']
batch = request.form['batch']
# Validation of roll_no & batch by regex match
if re.match(r'^[0-9][0-9][0-2]1230[0-9][0-9]$', str(roll_no)) and re.match(r'^20[0-9][0-9]$', str(batch)):
profiles.insert_one({
"roll_no": int(request.form['roll_no']),
"id": session['profile']['user_id'],
"name": session['profile']['name'],
"batch": int(request.form['batch'])
})
return redirect(url_for('dashboard'))
else:
flash('Enter Correct information')
return render_template('verification.html')
# Page wherein one can add his/her profile parameters like BIO etc.
# Now observe since our choice of DB was MongoDB we can actually extend the DB dynamically in future
# Hence can add fields even afterwards without data redundancy/duplication.
@app.route('/update_profile/', methods=['GET', 'POST'])
def update_profile():
pass
## TODO: HTML page + GET POST requests handle karni h
# Final Landing Page after successful login/verification
@app.route('/dashboard/')
@requires_auth
def dashboard():
return render_template('dashboard.html',
userinfo=session['profile'],
userinfo_pretty=json.dumps(session['jwt_payload'], indent=4))
## TODO: HTML page banana h
# Logout view - redirects to index view
@app.route('/logout')
@requires_auth
def logout():
# Clear session stored data
session.clear()
# Redirect user to logout endpoint
params = {'returnTo': url_for('index', _external=True), 'client_id': AUTH0_ID}
return redirect(auth0.api_base_url + '/v2/logout?' + urlencode(params))
# Yearbook Pages
@app.route('/yearbook/<batch>/')
@requires_auth
def yearbook_url(batch):
if batch_validation(batch):
students = yearbook.find({"batch": int(batch)})
return render_template('yearbook.html', students=students, batch=batch)
else:
# If Batch no is not correct than redirect to error page saying you are at wrong place
return abort(404)
# Already written Testimonials for a particular person
@app.route('/testimonials/<roll_no>/')
@requires_auth
def testimonials_url(roll_no):
# fetch testimonials
if roll_no_validation(roll_no):
# Finding student record in yearbook collection using roll_no
student = yearbook.find_one({"roll_no": int(roll_no)})
testimonial_array = [] # Empty Array
for testimonial_id in student['testimonials']:
# Search testimonial id in testimonials collection
# Append the markdown file content and fetched information to the empty array we declared above
result = testimonials.find_one({'_id': ObjectId(testimonial_id)})
# Opening markdown file
with open('markdowns/' + str(testimonial_id) + '.md', 'r') as md:
testimonial_array.append({
"author_name": result['author_name'],
"author_id": result['author_id'],
"markdown": md.read(),
"date": result['datetime']
})
return render_template('testimonials.html', testimonies=testimonial_array, student=student)
## TODO: Change markdown renderer from markdown lib filter to javascript marked.js as used in add_testimonials
else:
return abort(404)
# Add testimonial for a particular person
@app.route('/add_testimonial/<roll_no>/', methods=['GET', 'POST'])
@requires_auth
def add_testimonial(roll_no):
roll_no = int(roll_no)
if roll_no_validation(roll_no):
if request.method == 'GET':
return render_template('add_testimonial.html', roll_no=roll_no)
else:
# Insert testimonial in testimonials collection
result = testimonials.insert_one({
"roll_no": int(roll_no),
"author_id": session['profile']['user_id'],
"author_name": session['profile']['name'],
"datetime": datetime.now()
})
# Retrieve Id of inserted record in mongodb collection,
# each time we add new entry it is automatically assigned new id
# Also since Database Technology is smart enough to handle concurrency issues
# Using this id serves the problem of markdown filesystem concurrent write operations on same file
testimonial_id = str(result.inserted_id)
# Write in md file
with open('markdowns/' + str(testimonial_id) + '.md', 'w') as g:
g.write(request.form['text'])
# Add testimonial id to yearbook testimonials array field
yearbook.update({"roll_no": roll_no}, {"$push": {"testimonials": testimonial_id}})
# Redirect to that person's testimonials
return redirect(url_for('testimonials_url', roll_no=roll_no))
## TODO: Fix HTML Page UI & Give decent tutorial on how to use markdown
else:
return abort(404)
# 404 error handler - abort(404) redirects us here
@app.errorhandler(404)
def page_not_found(e):
# note that we set the 404 status explicitly
return render_template('404.html'), 404
######## Helper Functions
def roll_no_validation(roll_no):
# check if roll no is an integer
try:
roll_no = int(roll_no)
except:
return False # invalid roll no
# The method below is really nice since yearbook is manually updated collection
# So chances of adding testimonial for incorrect roll_no is eliminated
if yearbook.count_documents({'roll_no': roll_no}) != 0:
return True
else:
return False
def batch_validation(batch):
# check if roll no is an integer
try:
batch = int(batch)
except:
pass # invalid roll no
# Similarly, The method below is really nice since yearbook is manually updated collection
# So chances of adding testimonial for incorrect batch is eliminated
if yearbook.count_documents({'batch': batch}) != 0:
return True
else:
return False
# Turn debug = False in production
if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True)