-
Notifications
You must be signed in to change notification settings - Fork 1
/
server.py
467 lines (376 loc) · 18 KB
/
server.py
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
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
"""LTU SE M7011E Project Music Mood """
from flask import *
from werkzeug import secure_filename
import os
import DbFunct
import hashlib
from flask.ext.cors import cross_origin
from time import gmtime, strftime
import config
app = Flask(__name__)
"""The key for the application"""
app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RTTEaSDFQ'
UPLOAD_FOLDER = './static/images/userprofile'
ALLOWED_EXTENSIONS = set(['png', 'jpg', 'jpeg', 'gif'])
"""app configuration*****"""
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
# ========================================
# NAVIGATION
# ========================================
@app.route('/')
def nav_root_to_home():
""" starting route """
session['playlist'] = []
return redirect(url_for('nav_home'))
@app.route('/api', methods=['GET'])
def nav_apidoc():
return render_template('api.html', username=session['username'])
# ----------------------------------------
# User handling
# ----------------------------------------
@app.route('/register', methods=['GET'])
def nav_register():
"""Get the Registration page """
return render_template('register.html')
@app.route('/register', methods=['POST'])
def nav_register_post():
"""Route to Post the user Information in the DB (making call to the user_user_insert function ) and redirect to the authentication page"""
first_name = escape(request.form['username'])
email = escape(request.form['email'])
password = escape(request.form['password'])
# Encrypt password.
password = hashlib.sha224(password).hexdigest()
user = DbFunct.user_user_get(email, None)
if user is None:
DbFunct.user_user_insert(first_name, email, password)
return redirect('/login')
else:
return render_template('register.html')
@app.route('/submit_password', methods=['POST'])
def submit_password():
"""route to submit the new password """
if not request.json:
abort(300)
email = session['email']
password = request.json['psswd']
# TODO hashing
DbFunct.user_password_update(password, email)
return jsonify({'succes': 1})
@app.route('/login')
def nav_login():
"""authentication route for all users """
if 'username' in session:
return redirect(url_for('nav_home'))
else:
return render_template('login.html')
@app.route('/login', methods=['POST'])
def nav_login_post():
"""login route to the application """
# Encrypt password.
password = hashlib.sha224(escape(request.form['password'])).hexdigest()
user = DbFunct.user_user_get(escape(request.form['email']), password)
if user is None:
return redirect(url_for('nav_login'))
else:
session['username'] = user['username']
session['email'] = user['email']
session['password'] = user['password']
session['playlist'] = []
return redirect(url_for('nav_home'))
@app.route('/logout')
def logout():
"""route to logout from the application """
session.clear()
return redirect(url_for('nav_login'))
@app.route('/profile', methods=['GET', 'POST'])
def nav_profile():
""" route to the user profile """
list = []
list = DbFunct.song_songs_get_top_personal(session['email'])
img = DbFunct.user_image_get(session['email'])
print img
# GET
if request.method == 'GET':
return render_template('profile.html', email=session['email'], username=session['username'], list_music=list,
image=img)
# POST
file = request.files['file']
if file and allowed_file(file.filename.lower()):
filename = secure_filename(file.filename.lower())
extension = filename.rsplit('.', 1)[1]
filename = session['email'] + '_' + strftime("%Y-%m-%d-%H-%M-%S", gmtime()) + '.' + extension
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
filename = app.config['UPLOAD_FOLDER'].split(".")[1] + '/' + filename
DbFunct.user_image_update(filename, session['email'])
# Delete previous image
os.remove(os.path.dirname(os.path.realpath(__file__)) + img)
return render_template('profile.html', email=session['email'], username=session['username'], list_music=list,
image=filename)
# ---------------------------------------
# Music Pages
# ----------------------------------------
@app.route('/home', methods=['GET'])
def nav_home():
"""
Main Home page route, shows the user playlist songs.
If user has no playlist, show no songs nor initial songs.
If there is a 'random' key in the querystring, makes call to the either gets 10 random songs with 'song_songs_get_all()',
Then, If user selected one song (that is, a song data is on the query string), add it to the playlist.
"""
if 'username' in session:
# If there is a 'random' query string key, get the 10 random songs.
if request.args.has_key('random'):
session['playlist'] = []
for music in DbFunct.song_songs_get_all(random=True, limit=config.random_songs_number):
session['playlist'].append({'artist': music.artist , 'album': music.album, 'title': music.title})
session.modified = True
list = session['playlist']
# If a song was passed by arguments, add it at the beginning of the playlist.
args_has_song = request.args.has_key('artist') and request.args.has_key('album') and request.args.has_key('title')
if args_has_song:
# Todo: check for SQL injection.
# Check if the song in arguments in on DB.
args_song = DbFunct.song_data_get(request.args.get('artist'), request.args.get('album'), request.args.get('title'))
if args_song is not None:
# this change is not picked up because a mutable object (here a list) is changed.
list.insert(0, {
'artist': args_song.artist, 'album': args_song.album, 'title': args_song.title
})
# so mark it as modified yourself
session.modified = True
print "home list: {list}".format(list=list)
# Check if there is a 1st song.
if not session['playlist']:
initial_song = None
else:
# Get the first song from the list.
initial_song = DbFunct.song_data_get(list[0]['artist'], list[0]['album'], list[0]['title'])
return render_template('home.html', music=initial_song, music_list=list, username=session['username'])
else:
return redirect(url_for('nav_login'))
@app.route('/home', methods=['POST'])
def nav_home_search():
"""
Route for Search and music playlist recreation in the Home page.
Used when a Search is done in the nav bar, or a Mood Search is done.
"""
if 'username' in session:
# Check if there was any search.
if request.form['search'] is not None:
search = escape(request.form['search']).encode("utf-8")
print "Search: " + search
listKeyword = search.split()
session['playlist'] = []
# Check if search was empty, redirect to home.return
if not listKeyword:
return redirect("/home")
# Search music by Moods (set by the same user), or as a common search.
if listKeyword[0] == "mood:":
# Check the type of mood search.
if listKeyword[1] == 'mood:':
listMusic = DbFunct.song_songs_get_with_mood(listKeyword[2:], session['email'])
elif listKeyword[1] == 'genre:':
listMusic = DbFunct.song_songs_get_with_mood_genres(listKeyword[2:], session['email'])
else:
listMusic = DbFunct.song_songs_get_with_search(listKeyword)
# Cerate playlist.
for music in listMusic:
session['playlist'].append({'artist': music.artist , 'album': music.album, 'title': music.title})
print "nav_home_search() POST - session['playlist']: " + str(session['playlist'])
# Check if there is a 1st song.
listMusic = session['playlist']
if not listMusic:
initial_song = None
else:
# Get the 1st song from the DB for the client "music" variable (for metadata and src load).
initial_song = DbFunct.song_data_get(listMusic[0]['artist'], listMusic[0]['album'], listMusic[0]['title'])
return render_template('home.html', music=initial_song, music_list=listMusic, username=session['username'])
else:
return redirect("/home")
else:
return redirect(url_for('nav_login'))
@app.route('/top_music')
def top_music():
"""Route to Get the list of Top Music for a user according to the email , return top.html page"""
email = session['email']
list = DbFunct.song_songs_get_top_global()
return render_template('top.html', list_music=list, username=session['username'])
@app.route('/last_music')
def last_music():
"""Route to Get the last music list, return last.html"""
list = DbFunct.song_songs_get_latest()
return render_template('last.html', list_music=list, username=session['username'])
# ========================================
# AJAX API
# ========================================
@app.route('/api/getMusic', methods=['GET'])
@cross_origin(allow_headers=['Content-Type'])
def api_get_music():
"""
@api {get} /api/getMusic Get song data
@apiName api_get_music
@apiGroup Song
@apiParam {string} artist The exact song's artist name on the system.
@apiParam {string} album The exact song's album name on the system.
@apiParam {string} title The exact song's title on the system.
@apiSuccess {string} artist The song artist name on the system.
@apiSuccess {string} album The song album name on the system.
@apiSuccess {string} title The song title on the system.
@apiSuccess {string} year The song year of release on the system.
@apiSuccess {string} label The song label on the system.
@apiSuccess {string} music_path The song's YouTube video ID.
@apiSuccess {string} image_path The song cover image URL (currently not used).
@apiError {number} result The result of the GET, being
<code>-1</code> for incomplete/incorrect parameters, or
<code>0</code> for a song not found.
@apiSuccessExample {json} Success-Response for an example song:
{
"album":"Motion",
"artist":"Calvin Harris",
"image_path":null,
"label":"Columbia Records; Syco; Syco Music UK",
"music_path":"ebXbLfLACGM",
"title":"Summer",
"year":2014
}
@apiErrorExample {json} Error-Response:
{ "result": 0 }
@apiDescription
API getMusic route makes a GET to get the Music from the DB.
Returns JSON object.
"""
#@apiParam {String="info","id"} [method=id] Especifies the information used to query the song, either with id or info.
if request.args.get('artist') == None or request.args.get('album') == None or request.args.get('title') == None:
return jsonify({'result': -1})
if request.args.get('artist') == '' or request.args.get('album') == '' or request.args.get('title') == '':
return jsonify({'result': -1})
music = DbFunct.song_data_get(request.args.get('artist'), request.args.get('album'), request.args.get('title'))
print "api_get_music() result: " + str(music)
# Check if there was any returned song from DB.
if music == None:
return jsonify({'result': 0})
return jsonify({'artist': music.artist, 'album': music.album, 'title': music.title, 'year': music.year,
'label': music.label, 'music_path': music.musicPath, 'image_path': music.imagePath})
@app.route('/api/rating', methods=['POST'])
@cross_origin(allow_headers=['Content-Type'])
def api_post_rating():
"""
@api {post} /api/rating Post a song rating
@apiName api_post_rating
@apiGroup Song
@apiParam {string} artist The exact song's artist name on the system.
@apiParam {string} album The exact song's album name on the system.
@apiParam {string} title The exact song's title on the system.
@apiParam {number{1}=1,2,3,4,5} rating The rating given to the song.
@apiParam {string} email The email of the user that post the rating.
Note: If a valid cookie that identifies an user is sent, then this parameter is optional.
@apiSuccess {number} result The result of the POST, being the sent Rating from <code>1</code> to <code>5</code> for Success.
@apiError {number} result The result of the POST, being
<code>-1</code> for incomplete/incorrect parameters, or
<code>0</code> for a song not found.
@apiSuccessExample {json} Success-Response for a 4 start Rating:
{ "result": 4 }
@apiErrorExample {json} Error-Response:
{ "result": 0 }
@apiDescription
API note to set a note for a song.
Returns JSON object.
"""
if not request.json:
return jsonify({'result': -1}) #abort(300)
if request.json['artist'] == None or request.json['album'] == None or request.json['title'] == None or request.json['rating'] == None:
return jsonify({'result': -1})
if request.json['artist'] == '' or request.json['album'] == '' or request.json['title'] == '' or request.json['rating'] == '':
return jsonify({'result': -1})
if not request.json['rating'].isdigit():
return jsonify({'result': -1})
# Check for an email in JSON.
email = None
if request.json.has_key('email') and request.json['email'] != '':
email = request.json['email']
# Check if email on Session, and overwrite the one in JSON.
elif session.has_key('email'):
email = session['email']
else:
return jsonify({'result': -1})
rating = request.json['rating']
# Check if rating range is Not valid.
if int(rating) < 1 or int(rating) > 5:
return jsonify({'result': -1})
music = DbFunct.song_data_get(request.json['artist'], request.json['album'], request.json['title'])
# Check if there was any returned song from DB.
if music == None:
return jsonify({'result': 0})
result = DbFunct.song_rate_set_rating(email, music, rating)
if result == True:
return jsonify({'result': int(rating)})
else:
return jsonify({'result': -1})
@app.route('/api/mood', methods=['POST'])
@cross_origin(allow_headers=['Content-Type'])
def api_post_mood():
"""
@api {post} /api/mood Post song mood
@apiName api_post_mood
@apiGroup Song
@apiParam {string} artist The exact song's artist name on the system.
@apiParam {string} album The exact song's album name on the system.
@apiParam {string} title The exact song's title on the system.
@apiParam {string=Chill,Sad,Nostalgic,Gaming,Travel,Motivated,Enthusiastic,Upset,Inspired,Festive,Hard,Geek,Instrumental,Creative,Tropical,Studious,Aggressive,Calm,Adventurous,Humorous} mood
The mood classification given to the song.
@apiParam {string} email The email of the user that post the mood classification.
Note: If a valid cookie that identifies an user is sent, then this parameter is optional.
@apiSuccess {number} result The result of the POST, being <code>1</code> for Success.
@apiError {number} result The result of the POST, being
<code>-1</code> for incomplete/incorrect parameters, or
<code>0</code> for a song not found.
@apiSuccessExample {json} Success-Response:
{ "result": 1 }
@apiErrorExample {json} Error-Response:
{ "result": 0 }
@apiDescription
Route to post a mood to song.
"""
if not request.json:
return jsonify({'result': -1}) #abort(300)
if request.json['artist'] == None or request.json['album'] == None or request.json['title'] == None or request.json['mood'] == None:
return jsonify({'result': -1})
if request.json['artist'] == '' or request.json['album'] == '' or request.json['title'] == '' or request.json['mood'] == '':
return jsonify({'result': -1})
# Check for an email in JSON.
email = None
if request.json.has_key('email') and request.json['email'] != '':
email = request.json['email']
# Check if email on Session, and overwrite the one in JSON.
elif session.has_key('email'):
email = session['email']
else:
return jsonify({'result': -1})
# Check for the moods.
moods_hardcoded = ('Chill', 'Sad', 'Nostalgic', 'Gaming', 'Travel', 'Motivated', 'Enthusiastic', 'Upset', 'Inspired', 'Festive', 'Hard', 'Geek', 'Instrumental', 'Creative', 'Tropical', 'Studious', 'Aggressive', 'Calm', 'Adventurous', 'Humorous')
if request.json['mood'] not in moods_hardcoded:
return jsonify({'result': -1})
mood = request.json['mood']
music = DbFunct.song_data_get(request.json['artist'], request.json['album'], request.json['title'])
# Check if there was any returned song from DB.
if music == None:
return jsonify({'result': 0})
result = DbFunct.song_rate_set_mood(email, music, mood)
if result == True:
return jsonify({'result': 1})
else:
return jsonify({'result': -1})
# Enable HTTPS
import ssl
appPath = os.path.dirname(os.path.realpath(__file__))
context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
context.load_cert_chain(appPath + '/ssl.crt', appPath + '/ssl.key')
use_https = config.use_https
if __name__ == '__main__':
if use_https:
app.run(debug=True, ssl_context=context, port=443)
else:
app.run(debug=True, port=80)