Adicionar 2 rotas
This commit is contained in:
parent
b905a812b9
commit
5fc179d389
|
@ -20,7 +20,7 @@ Os endpoints que já estão funcionando marcados:
|
||||||
- [X] POST /trainer
|
- [X] POST /trainer
|
||||||
- [X] POST /trainer/authenticate
|
- [X] POST /trainer/authenticate
|
||||||
- [X] GET /trainer/{trainerId}
|
- [X] GET /trainer/{trainerId}
|
||||||
- [ ] GET /trainer/{trainerId}/pokemon
|
- [X] GET /trainer/{trainerId}/pokemon
|
||||||
- [ ] POST /trainer/{trainerId}/pokemon
|
- [X] POST /trainer/{trainerId}/pokemon
|
||||||
- [ ] GET /trainer/{trainerId}/pokemon/{pokemonId}
|
- [ ] GET /trainer/{trainerId}/pokemon/{pokemonId}
|
||||||
- [ ] DELETE /trainer/{trainerId}/pokemon/{pokemonId}
|
- [ ] DELETE /trainer/{trainerId}/pokemon/{pokemonId}
|
||||||
|
|
|
@ -9,4 +9,5 @@ db = SQLAlchemy(app)
|
||||||
ma = Marshmallow(app)
|
ma = Marshmallow(app)
|
||||||
|
|
||||||
import api.models.trainer
|
import api.models.trainer
|
||||||
|
import api.models.pokemon_owned
|
||||||
import api.routes.routes
|
import api.routes.routes
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
from api.app import db, ma
|
||||||
|
from .trainer import *
|
||||||
|
|
||||||
|
class PokemonOwned(db.Model):
|
||||||
|
__tablename__ = "pokemons_owned"
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||||
|
name = db.Column(db.Text(50), unique=True, index=True, nullable=False)
|
||||||
|
level = db.Column(db.Integer)
|
||||||
|
pokemon_id = db.Column(db.Integer)
|
||||||
|
trainer_id = db.Column(db.Integer, db.ForeignKey("trainers.id"), index=True)
|
||||||
|
|
||||||
|
def __init__(self, name, level, pokemon_id, trainer_id):
|
||||||
|
self.name = name
|
||||||
|
self.level = level
|
||||||
|
self.pokemon_id = pokemon_id
|
||||||
|
self.trainer_id = trainer_id
|
||||||
|
|
||||||
|
class PokemonOwnedSchema(ma.Schema):
|
||||||
|
class Meta:
|
||||||
|
fields = ('id', 'name', 'level', 'pokemon_data')
|
||||||
|
|
||||||
|
pokemon_owned_schema = PokemonOwnedSchema()
|
||||||
|
pokemon_owned_schemas = PokemonOwnedSchema(many=True)
|
|
@ -14,7 +14,7 @@ class InvalidTeam(Exception):
|
||||||
class Trainer(db.Model):
|
class Trainer(db.Model):
|
||||||
__tablename__ = "trainers"
|
__tablename__ = "trainers"
|
||||||
|
|
||||||
id = db.Column(db.Integer, primary_key=True, unique=True, autoincrement=True)
|
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||||
nickname = db.Column(db.String(20), unique=True, nullable=False, index=True)
|
nickname = db.Column(db.String(20), unique=True, nullable=False, index=True)
|
||||||
first_name = db.Column(db.String(30), nullable=False)
|
first_name = db.Column(db.String(30), nullable=False)
|
||||||
last_name = db.Column(db.String(30), nullable=False)
|
last_name = db.Column(db.String(30), nullable=False)
|
||||||
|
@ -22,6 +22,7 @@ class Trainer(db.Model):
|
||||||
password = db.Column(db.String(200), nullable=False)
|
password = db.Column(db.String(200), nullable=False)
|
||||||
team = db.Column(db.String(10), nullable=False)
|
team = db.Column(db.String(10), nullable=False)
|
||||||
pokemons_owned = db.Column(db.Integer, default=0)
|
pokemons_owned = db.Column(db.Integer, default=0)
|
||||||
|
pokemons_list = db.relationship("PokemonOwned", lazy="dynamic")
|
||||||
|
|
||||||
def __init__(self, nickname, first_name, last_name, email, password, team):
|
def __init__(self, nickname, first_name, last_name, email, password, team):
|
||||||
if team not in teams:
|
if team not in teams:
|
||||||
|
|
|
@ -1,20 +1,31 @@
|
||||||
|
from api.app import app
|
||||||
|
from api.views import trainer, pokemon_owned, helper, errors
|
||||||
from flask import request
|
from flask import request
|
||||||
from sqlite3 import ProgrammingError, IntegrityError
|
import asyncio
|
||||||
from api.app import app, db
|
|
||||||
from api.views import trainer
|
|
||||||
|
|
||||||
@app.route('/trainer/<int:trainerId>', methods=['GET'])
|
@app.route("/trainer/<int:trainerId>/", methods=["GET"])
|
||||||
def route_get_trainer(trainerId):
|
def route_get_trainer(trainerId):
|
||||||
return trainer.get_trainer(trainerId)
|
return trainer.get_trainer(trainerId)
|
||||||
|
|
||||||
@app.route('/trainer', methods=['GET'])
|
@app.route("/trainer", methods=["GET"])
|
||||||
def route_get_trainers():
|
def route_get_trainers():
|
||||||
return trainer.get_trainers()
|
return trainer.get_trainers()
|
||||||
|
|
||||||
@app.route('/trainer', methods=['POST'])
|
@app.route("/trainer", methods=["POST"])
|
||||||
def route_create_trainer():
|
def route_create_trainer():
|
||||||
return trainer.post_trainer()
|
return trainer.post_trainer()
|
||||||
|
|
||||||
@app.route("/trainer/authenticate", methods=['POST'])
|
@app.route("/trainer/authenticate", methods=["POST"])
|
||||||
def route_auth_trainer():
|
def route_auth_trainer():
|
||||||
return trainer.auth_trainer()
|
return trainer.auth_trainer()
|
||||||
|
|
||||||
|
@app.route("/trainer/<int:trainerId>/pokemon", methods=["GET"])
|
||||||
|
def route_get_pokemons_owned(trainerId):
|
||||||
|
return asyncio.run(pokemon_owned.get_pokemons_owned(trainerId))
|
||||||
|
|
||||||
|
@app.route("/trainer/<int:trainerId>/pokemon", methods=["POST"])
|
||||||
|
@helper.token_required
|
||||||
|
def route_post_pokemons_owned(trainer, trainerId):
|
||||||
|
if trainer.id != trainerId:
|
||||||
|
return errors.ForbiddenError("Trainer id mismatch")
|
||||||
|
return pokemon_owned.post_pokemon_owned(trainerId)
|
||||||
|
|
|
@ -16,3 +16,9 @@ def ConflictingResources(message):
|
||||||
|
|
||||||
def AuthenticationFailure(message):
|
def AuthenticationFailure(message):
|
||||||
return error(4, "AuthenticationFailure", message, http_code=401)
|
return error(4, "AuthenticationFailure", message, http_code=401)
|
||||||
|
|
||||||
|
def ForbiddenError(message):
|
||||||
|
return error(5, "ForbiddenError", message, http_code=403)
|
||||||
|
|
||||||
|
def FetchError(message):
|
||||||
|
return error(6, "FetchError", message, http_code=500)
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
from functools import wraps
|
||||||
|
from flask import request
|
||||||
|
from api.models.trainer import Trainer
|
||||||
|
from .errors import AuthenticationFailure
|
||||||
|
from api.app import app
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import jwt
|
||||||
|
|
||||||
|
class HTTPError(Exception):
|
||||||
|
def __init__(self, message):
|
||||||
|
self.message = message
|
||||||
|
|
||||||
|
class TrainerNotFound(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_trainer_fail(id):
|
||||||
|
try:
|
||||||
|
trainer = Trainer.query.get(id)
|
||||||
|
if trainer is None:
|
||||||
|
raise TrainerNotFound()
|
||||||
|
return trainer
|
||||||
|
except:
|
||||||
|
raise TrainerNotFound()
|
||||||
|
|
||||||
|
def get_trainer_by_nick_fail(nickname):
|
||||||
|
try:
|
||||||
|
trainer = Trainer.query.filter_by(nickname=nickname).one()
|
||||||
|
if trainer is None:
|
||||||
|
raise TrainerNotFound()
|
||||||
|
return trainer
|
||||||
|
except:
|
||||||
|
raise TrainerNotFound()
|
||||||
|
|
||||||
|
# authenticação do trainer (decorator)
|
||||||
|
def token_required(f):
|
||||||
|
@wraps(f)
|
||||||
|
def decorated(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
token = request.headers["authorization"]
|
||||||
|
print(token)
|
||||||
|
print(app.config["SECRET_KEY"])
|
||||||
|
data = jwt.decode(token, app.config["SECRET_KEY"], algorithms=["HS256"])
|
||||||
|
print(data["username"])
|
||||||
|
trainer = get_trainer_by_nick_fail(data["username"])
|
||||||
|
except (TypeError, KeyError):
|
||||||
|
return AuthenticationFailure("JWT token required")
|
||||||
|
except:
|
||||||
|
return AuthenticationFailure("JWT token is invalid or expired")
|
||||||
|
|
||||||
|
return f(trainer, *args, **kwargs)
|
||||||
|
return decorated
|
||||||
|
|
||||||
|
# seguintes funções puxam informações da pokeapi
|
||||||
|
def set_pokemon_data(pokemon):
|
||||||
|
response = requests.get("https://pokeapi.co/api/v2/pokemon/{}".format(pokemon.pokemon_id))
|
||||||
|
if response.status_code != 200:
|
||||||
|
raise HTTPError("Could not fetch pokemon with id {}".format(pokemon.pokemon_id))
|
||||||
|
pokemon.pokemon_data = json.loads(response.text)
|
||||||
|
|
||||||
|
async def async_set_pokemon_data(session, pokemon):
|
||||||
|
response = await session.get("https://pokeapi.co/api/v2/pokemon/{}".format(pokemon.pokemon_id))
|
||||||
|
if response.status != 200:
|
||||||
|
raise HTTPError("Could not fetch pokemon with id {}".format(pokemon.pokemon_id))
|
||||||
|
pokemon.pokemon_data = json.loads(await response.text())
|
|
@ -0,0 +1,33 @@
|
||||||
|
from flask import request
|
||||||
|
|
||||||
|
class ParsingException(Exception):
|
||||||
|
def __init__(self, message):
|
||||||
|
self.message = message
|
||||||
|
|
||||||
|
def parse_int(name, minimum):
|
||||||
|
try:
|
||||||
|
result = int(request.args.get(name, minimum))
|
||||||
|
except ValueError:
|
||||||
|
raise ParsingException("Couldn't parse {} as integer".format(name))
|
||||||
|
|
||||||
|
if result < minimum:
|
||||||
|
raise ParsingException("{} must be greater than {}".format(name, minimum))
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def parse_limit():
|
||||||
|
return parse_int("limit", -1)
|
||||||
|
|
||||||
|
def parse_offset():
|
||||||
|
return parse_int("offset", 0)
|
||||||
|
|
||||||
|
def parse_json_obj():
|
||||||
|
try:
|
||||||
|
json = request.get_json()
|
||||||
|
except:
|
||||||
|
raise ParsingException("Failed to parse JSON body")
|
||||||
|
|
||||||
|
if type(json) is not dict:
|
||||||
|
raise ParsingException("Expected JSON object as body")
|
||||||
|
|
||||||
|
return json
|
|
@ -0,0 +1,64 @@
|
||||||
|
from api.models.pokemon_owned import pokemon_owned_schema, pokemon_owned_schemas, PokemonOwned
|
||||||
|
from api.app import db
|
||||||
|
from .parse_args import parse_limit, parse_offset, ParsingException, parse_json_obj
|
||||||
|
from .errors import ParsingError, FetchError, ConflictingResources
|
||||||
|
from .helper import TrainerNotFound, HTTPError, get_trainer_fail, set_pokemon_data, async_set_pokemon_data
|
||||||
|
from aiohttp import ClientSession
|
||||||
|
from asyncio import create_task, gather
|
||||||
|
from sqlalchemy.exc import IntegrityError
|
||||||
|
|
||||||
|
def post_pokemon_owned(trainer_id):
|
||||||
|
try:
|
||||||
|
json = parse_json_obj()
|
||||||
|
pokemon_id = json["pokemon_id"]
|
||||||
|
name = json["name"]
|
||||||
|
level = json["level"]
|
||||||
|
except ParsingException as e:
|
||||||
|
return ParsingError(e.message)
|
||||||
|
except KeyError:
|
||||||
|
return ParsingError("Missing JSON object fields")
|
||||||
|
|
||||||
|
pokemon = PokemonOwned(
|
||||||
|
name=name,
|
||||||
|
level=level,
|
||||||
|
pokemon_id=pokemon_id,
|
||||||
|
trainer_id=trainer_id
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
set_pokemon_data(pokemon)
|
||||||
|
|
||||||
|
db.session.add(pokemon)
|
||||||
|
db.session.commit()
|
||||||
|
except HTTPError as e:
|
||||||
|
return FetchError(e.message)
|
||||||
|
except IntegrityError:
|
||||||
|
return ConflictingResources("Trainer already has another pokemon with the same name")
|
||||||
|
|
||||||
|
return (pokemon_owned_schema.dump(pokemon), 201)
|
||||||
|
|
||||||
|
async def get_pokemons_owned(trainer_id):
|
||||||
|
try:
|
||||||
|
limit = parse_limit()
|
||||||
|
offset = parse_offset()
|
||||||
|
except ParsingException as e:
|
||||||
|
return ParsingError(e.message)
|
||||||
|
|
||||||
|
try:
|
||||||
|
trainer = get_trainer_fail(trainer_id)
|
||||||
|
except TrainerNotFound:
|
||||||
|
return "", 404
|
||||||
|
|
||||||
|
pokemons = trainer.pokemons_list.limit(limit).offset(offset).all()
|
||||||
|
|
||||||
|
async with ClientSession() as session:
|
||||||
|
tasks = []
|
||||||
|
for pokemon in pokemons:
|
||||||
|
task = create_task(async_set_pokemon_data(session, pokemon))
|
||||||
|
tasks.append(task)
|
||||||
|
try:
|
||||||
|
await gather(*tasks)
|
||||||
|
except HTTPError as e:
|
||||||
|
return FetchError(e.message)
|
||||||
|
|
||||||
|
return pokemon_owned_schemas.dumps(pokemons)
|
|
@ -3,7 +3,8 @@ from api.models.trainer import Trainer, trainer_schema, trainer_schemas, Invalid
|
||||||
from api.app import db
|
from api.app import db
|
||||||
from api.app import app
|
from api.app import app
|
||||||
from flask import request, jsonify
|
from flask import request, jsonify
|
||||||
from .errors import ParsingError, ConflictingParameters, ConflictingResources, AuthenticationFailure
|
from .errors import *
|
||||||
|
from .parse_args import parse_limit, parse_offset, ParsingException, parse_json_obj
|
||||||
from werkzeug.security import check_password_hash
|
from werkzeug.security import check_password_hash
|
||||||
import datetime
|
import datetime
|
||||||
import jwt
|
import jwt
|
||||||
|
@ -12,19 +13,16 @@ def get_trainer(id):
|
||||||
try:
|
try:
|
||||||
return trainer_schema.dump(Trainer.query.get(id))
|
return trainer_schema.dump(Trainer.query.get(id))
|
||||||
except:
|
except:
|
||||||
return jsonify({})
|
return ("", 404)
|
||||||
|
|
||||||
def get_trainers():
|
def get_trainers():
|
||||||
args = request.args
|
args = request.args
|
||||||
|
|
||||||
try:
|
try:
|
||||||
limit = int(args.get("limit", -1))
|
limit = parse_limit()
|
||||||
offset = int(args.get("offset", 0))
|
offset = parse_offset()
|
||||||
except ValueError:
|
except ParsingException as e:
|
||||||
return ParsingError("Couldn't parse parameter as integer")
|
return ParsingError(e.message)
|
||||||
|
|
||||||
if limit < -1 or offset < 0:
|
|
||||||
return ParsingError("Expected positive integer as parameter")
|
|
||||||
|
|
||||||
nickname = args.get("nickname", "")
|
nickname = args.get("nickname", "")
|
||||||
nickname_contains = args.get("nickname_contains", "")
|
nickname_contains = args.get("nickname_contains", "")
|
||||||
|
@ -53,14 +51,8 @@ def get_trainer_by_email(email):
|
||||||
|
|
||||||
def post_trainer():
|
def post_trainer():
|
||||||
try:
|
try:
|
||||||
json = request.get_json()
|
json = parse_json_obj()
|
||||||
except:
|
|
||||||
return ParsingError("Failed to parse JSON body")
|
|
||||||
|
|
||||||
if type(json) is not dict:
|
|
||||||
return ParsingError("Expected JSON object as body")
|
|
||||||
|
|
||||||
try:
|
|
||||||
nickname = json["nickname"]
|
nickname = json["nickname"]
|
||||||
first_name = json["first_name"]
|
first_name = json["first_name"]
|
||||||
last_name = json["last_name"]
|
last_name = json["last_name"]
|
||||||
|
@ -81,25 +73,22 @@ def post_trainer():
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
return trainer_schema.dump(trainer)
|
return trainer_schema.dump(trainer)
|
||||||
|
except ParsingException as e:
|
||||||
|
return ParsingError(e.message)
|
||||||
except InvalidTeam:
|
except InvalidTeam:
|
||||||
return ParsingError("Field team is invalid")
|
return ParsingError("Field team is invalid")
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return ParsingError("Missing JSON object fields")
|
return ParsingError("Missing JSON object fields")
|
||||||
except IntegrityError:
|
except IntegrityError:
|
||||||
return ConflictingResources("Trainer with the same nickname or email already exists", http_code=500)
|
return ConflictingResources("Trainer with the same nickname or email already exists")
|
||||||
|
|
||||||
def auth_trainer():
|
def auth_trainer():
|
||||||
try:
|
try:
|
||||||
auth = request.get_json()
|
auth = parse_json_obj()
|
||||||
except:
|
|
||||||
return ParsingError("Failed to parse JSON body")
|
|
||||||
|
|
||||||
if type(auth) is not dict:
|
|
||||||
return ParsingError("Expected JSON object as body")
|
|
||||||
|
|
||||||
try:
|
|
||||||
email = auth["email"]
|
email = auth["email"]
|
||||||
password = auth["password"]
|
password = auth["password"]
|
||||||
|
except ParsingException as e:
|
||||||
|
return ParsingError(e.message)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return AuthenticationFailure("Login required")
|
return AuthenticationFailure("Login required")
|
||||||
|
|
||||||
|
@ -113,7 +102,7 @@ def auth_trainer():
|
||||||
"username": trainer.nickname,
|
"username": trainer.nickname,
|
||||||
"exp": datetime.datetime.now() + datetime.timedelta(hours=12)
|
"exp": datetime.datetime.now() + datetime.timedelta(hours=12)
|
||||||
},
|
},
|
||||||
app.config["SECRET_KEY"])
|
app.config["SECRET_KEY"], algorithm="HS256")
|
||||||
return jsonify(
|
return jsonify(
|
||||||
{
|
{
|
||||||
"id": trainer.id,
|
"id": trainer.id,
|
||||||
|
|
Loading…
Reference in New Issue