Skip to content
This repository has been archived by the owner on Jun 29, 2019. It is now read-only.

Commit

Permalink
Merge cef651f into 31a96b7
Browse files Browse the repository at this point in the history
  • Loading branch information
duk3luk3 committed Mar 22, 2017
2 parents 31a96b7 + cef651f commit d4d65c0
Show file tree
Hide file tree
Showing 8 changed files with 596 additions and 21 deletions.
2 changes: 1 addition & 1 deletion api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from api.deployment.deployment_manager import DeploymentManager
from api.error import ApiException
from api.jwt_user import JwtUser
from api.user import User
from api.user import User, UserGroup

__version__ = '0.7.0'
__author__ = 'Chris Kitching, Michael Søndergaard, Vytautas Mickus, Michel Jung'
Expand Down
334 changes: 314 additions & 20 deletions api/avatars.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,255 @@
import json
import os
from peewee import IntegrityError
from werkzeug.utils import secure_filename
from api import *

import faf.db as db
from api.error import ApiException, Error, ErrorCode

from flask import request

@app.route("/avatar", methods=['GET', 'POST'])
import logging
logger = logging.getLogger(__name__)

class Avatar:
"""
Avatar model/db class
The implementation of this class is a little tricky since we have to handle
the url and filename.
It also does not ensure 100% consistency. E.g. it allows for avatars to be
inserted without uploading the corresponding avatar file.
"""

def __init__(self, id=None, filename=None, url=None, tooltip=None, **kwargs):
"""
Constructor
:param int id: database id
:param string filename: filename / url / whatever
:param string tooltip: tooltip
"""
self.URLBASE = app.config.get('AVATAR_URL', 'http://content.faforever.com/faf/avatars/')
self.FILEBASE = app.config.get('AVATAR_FOLDER', '/content/faf/avatars/')

self.id = id
# FIXME: Dirty hack, done dirt cheap
filename = filename or url
if '/' in filename:
filename = filename.split('/')[-1]
self.filename = filename
self.tooltip = tooltip

def dict(self):
return {
'id': self.id,
'url': self._url(),
'tooltip': self.tooltip
}

def _url(self):
if self.filename is not None:
return self.URLBASE + self.filename
else:
return None

def _path(self):
if self.filename is not None:
return self.FILEBASE + self.filename
else:
return None

@classmethod
def get_by_id(cls, avatar_id):
"""
Find avatar by id.
:param str avatar_id: The avatar database id
:returns:
:class: `Avatar` if avatar is found, None otherwise
"""
with db.connection:
cursor = db.connection.cursor(db.pymysql.cursors.DictCursor)
cursor.execute("SELECT id, url, tooltip FROM avatars_list WHERE id = %s", avatar_id)

avatar = cursor.fetchone()
if avatar is not None:
return Avatar(**avatar)
else:
return None

@classmethod
def get_all(cls):
"""
Get all avatars from db - as dict, because you probably don't need instances.
"""
with db.connection:
cursor = db.connection.cursor(db.pymysql.cursors.DictCursor)
cursor.execute("SELECT * FROM avatars_list")

return cursor.fetchall()

@classmethod
def get_user_avatars(cls, user):
"""
Find a user's avatars
pass user id or User instance
Get back list of avatars
"""
if isinstance(user, User):
user = user.id

with db.connection:
cursor = db.connection.cursor(db.pymysql.cursors.DictCursor)
cursor.execute('select al.* from avatars_list as al JOIN avatars as a on (al.id = a.idAvatar) where a.idUser = %s', user)
avatars = cursor.fetchall()
return avatars

@classmethod
def remove_user_avatars(cls, user, avatars):
"""
Remove avatars from user
"""

if isinstance(user, User):
user_id = user.id
else:
user_id = int(user)

with db.connection:
cursor = db.connection.cursor(db.pymysql.cursors.DictCursor)
#FIXME: make "where idAvatar in (set)" query?
for avatar in avatars:
if isinstance(avatar, cls):
avatar_id = avatar.id
else:
avatar_id = int(avatar)
cursor.execute('delete from avatars where idUser=%s and idAvatar=%s', [user_id, avatar_id])

@classmethod
def add_user_avatars(cls, user, avatars):
"""
Add avatars to user
"""

if isinstance(user, User):
user_id = user.id
else:
user_id = int(user)

with db.connection:
cursor = db.connection.cursor(db.pymysql.cursors.DictCursor)
#FIXME: make "where idAvatar in (set)" query?
for avatar in avatars:
if isinstance(avatar, cls):
avatar_id = avatar.id
else:
avatar_id = int(avatar)
cursor.execute('insert into avatars (idUser, idAvatar) values (%s, %s)', [user_id, avatar_id])

def get_avatar_users(self, attrs = ['id', 'login']):
"""
Get all users that have this avatar
Returns None if avatar instance does not have an id, otherwise list of user id's
"""
if self.id is not None:
with db.connection:
cursor = db.connection.cursor(db.pymysql.cursors.DictCursor)
cursor.execute('SELECT l.* from login as l join avatars as a on (a.idUser = l.id) WHERE idAvatar = %s', self.id)

return [{key: rec[key] for key in attrs} for rec in cursor.fetchall()]
else:
return None

def insert(self):
"""
Inserts avatar into db
"""

# if an avatar has an id, it must already be in the database.
if self.id is not None:
raise Exception('Avatar already has an id - refusing to insert.')
with db.connection:
cursor = db.connection.cursor(db.pymysql.cursors.DictCursor)
cursor.execute("INSERT INTO avatars_list (url, tooltip) VALUES (%s, %s)", [self._url(), self.tooltip])
self.id = cursor.lastrowid

def update(self):
"""
Updates avatar tooltip
"""
with db.connection:
cursor = db.connection.cursor(db.pymysql.cursors.DictCursor)
cursor.execute("UPDATE avatars_list SET tooltip=%s WHERE id=%s", [self.tooltip, self.id])


def upload(self, avatar_file, overwrite=False):
"""
Copies avatar file to filesystem
:param avatar_file flo: file-like object of the avatar file
:param overwrite bool: Only overwrites existing file if true
"""
dest = self._path()
if os.path.exists(dest) and not overwrite:
raise ApiException([Error(ErrorCode.AVATAR_FILE_EXISTS)])
with open(dest, 'wb') as fh:
fh.write(avatar_file.read())

def delete(self):
"""
Deletes avatar
WARNING: Does not check if avatar is still in use!
"""
with db.connection:
cursor = db.connection.cursor(db.pymysql.cursors.DictCursor)
cursor.execute("DELETE FROM avatars_list WHERE id=%s", self.id)

@app.route("/user_avatars", methods=['GET', 'POST', 'DELETE'])
def user_avatars():
if request.method != 'GET':
valid, req = oauth.verify_request([])
if not valid:
raise ApiException([Error(ErrorCode.AUTHENTICATION_NEEDED)])
else:
current_user = User.get_by_id(req.user.id)
print("user:", current_user.id, current_user.username)
print("checking usergroup:", current_user.usergroup())
if not current_user.usergroup() >= UserGroup.MODERATOR:
raise ApiException([Error(ErrorCode.FORBIDDEN)])

if request.method == 'POST':
user_id = request.form.get('user_id', type=int)
avatar_ids = request.form.getlist('avatar_id', type=int)
Avatar.add_user_avatars(user_id, avatar_ids)
return 'ok'
elif request.method == 'DELETE':
user_id = request.form.get('user_id', type=int)
if user_id is not None:
avatar_ids = request.form.getlist('avatar_id', type=int)
Avatar.remove_user_avatars(user_id, avatar_ids)
return json.dumps(dict(status='Removed avatar from user')), 204
else:
raise ApiException([Error(ErrorCode.PARAMETER_MISSING, 'id')])
elif request.method == 'GET':
user_id = request.args.get('id', type=int)
if user_id is not None:
avatars = Avatar.get_user_avatars(user_id)
return json.dumps(avatars)
else:
raise ApiException([Error(ErrorCode.PARAMETER_MISSING, 'id')])


@app.route("/avatar", methods=['GET', 'POST', 'PUT', 'DELETE'])
def avatars():
"""
Displays avatars
.. warning:: Not working currently. Broken.
**Example Request**:
.. sourcecode:: http
Expand All @@ -34,34 +271,76 @@ def avatars():
.. todo:: Probably would be better to isolate methods GET and POST methods...
"""
if request.method != 'GET':
valid, req = oauth.verify_request([])
if not valid:
raise ApiException([Error(ErrorCode.AUTHENTICATION_NEEDED)])
else:
current_user = User.get_by_id(req.user.id)
if not current_user.usergroup() >= UserGroup.MODERATOR:
raise ApiException([Error(ErrorCode.FORBIDDEN)])

if request.method == 'POST':
logger.debug('Handling POST')
avatar_id = request.form.get('id')
avatar_tooltip = request.form.get('tooltip')
avatar_file = request.files.get('file')
logger.debug('Fetched params: id={} tooltip={} file={}'.format(repr(avatar_id), repr(avatar_tooltip), repr(avatar_file)))

file = request.files['file']
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['AVATAR_FOLDER'], filename))
avatar = Avatar.get_by_id(avatar_id)

try:
avatar = Avatar.create(url=app.config['AVATAR_URL'], tooltip=request.form['tooltip'])
except IntegrityError:
return json.dumps(dict(error="Avatar already exists")), 400
if avatar is not None:
if avatar_tooltip != avatar.tooltip:
avatar.tooltip = avatar_tooltip
avatar.update()
if avatar_file is not None:
avatar.upload(avatar_file, overwrite=True)
return avatar.dict()
else:
raise ApiException([Error(ErrorCode.AVATAR_NOT_FOUND)])
elif request.method == 'PUT':
avatar_file = request.files['file']
avatar_filename = secure_filename(avatar_file.filename)
avatar_tooltip = request.form['tooltip']

avatar = Avatar(filename=avatar_filename, tooltip=avatar_tooltip)
avatar.upload(avatar_file)
avatar.insert()

return avatar.dict()
elif request.method == 'DELETE':
avatar_id = request.form.get('id')
if id is not None:
avatar = Avatar.get_by_id(avatar_id)
if avatar is not None:
avatar.delete()
return json.dumps(dict(status='Deleted avatar')), 204
else:
raise ApiException([Error(ErrorCode.AVATAR_NOT_FOUND)])
else:
raise ApiException([Error(ErrorCode.AVATAR_ID_MISSING)])
elif request.method == 'GET':
avatar_id = request.args.get('id')
if avatar_id is not None:
avatar = Avatar.get_by_id(avatar_id)
if avatar is not None:
return avatar.dict()
else:
raise ApiException([Error(ErrorCode.AVATAR_NOT_FOUND)])
else:
return json.dumps(Avatar.get_all())

else:
return [avatar.dict() for avatar in Avatar.select()]

@app.route("/avatar/<int:id>", methods=['GET', 'PUT'])
@app.route("/avatar/<int:id>", methods=['GET'])
def avatar(id):
"""
Displays individual avatars
.. warning:: Not working currently. Broken.
**Example Request**:
.. sourcecode:: http
GET, PUT /avatar/781
GET /avatar/781
**Example Response**:
Expand All @@ -79,6 +358,21 @@ def avatar(id):
.. todo:: Probably would be better to isolate methods GET and PUT methods...
"""
if request.method == 'GET':
return Avatar.get(Avatar.id == id).dict()
elif request.method == 'PUT':
raise NotImplemented('')
avatar = Avatar.get_by_id(id)
if avatar is not None:
return avatar.dict()
else:
raise ApiException([Error(ErrorCode.AVATAR_NOT_FOUND)])

@app.route("/avatar/<int:id>/users", methods=['GET'])
def avatar_users(id):
if request.method == 'GET':
avatar = Avatar.get_by_id(id)
attrs = request.args.getlist('attr')
if len(attrs) < 1:
attrs = ['id', 'login']
if avatar is not None:
return json.dumps(avatar.get_avatar_users(attrs))
else:
return json.dumps(dict(error='Not found')), 404

Loading

0 comments on commit d4d65c0

Please sign in to comment.