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/authenticate
|
||||
- [X] GET /trainer/{trainerId}
|
||||
- [ ] GET /trainer/{trainerId}/pokemon
|
||||
- [ ] POST /trainer/{trainerId}/pokemon
|
||||
- [X] GET /trainer/{trainerId}/pokemon
|
||||
- [X] POST /trainer/{trainerId}/pokemon
|
||||
- [ ] GET /trainer/{trainerId}/pokemon/{pokemonId}
|
||||
- [ ] DELETE /trainer/{trainerId}/pokemon/{pokemonId}
|
||||
|
|
|
@ -9,4 +9,5 @@ db = SQLAlchemy(app)
|
|||
ma = Marshmallow(app)
|
||||
|
||||
import api.models.trainer
|
||||
import api.models.pokemon_owned
|
||||
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):
|
||||
__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)
|
||||
first_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)
|
||||
team = db.Column(db.String(10), nullable=False)
|
||||
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):
|
||||
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 sqlite3 import ProgrammingError, IntegrityError
|
||||
from api.app import app, db
|
||||
from api.views import trainer
|
||||
import asyncio
|
||||
|
||||
@app.route('/trainer/<int:trainerId>', methods=['GET'])
|
||||
@app.route("/trainer/<int:trainerId>/", methods=["GET"])
|
||||
def route_get_trainer(trainerId):
|
||||
return trainer.get_trainer(trainerId)
|
||||
|
||||
@app.route('/trainer', methods=['GET'])
|
||||
@app.route("/trainer", methods=["GET"])
|
||||
def route_get_trainers():
|
||||
return trainer.get_trainers()
|
||||
|
||||
@app.route('/trainer', methods=['POST'])
|
||||
@app.route("/trainer", methods=["POST"])
|
||||
def route_create_trainer():
|
||||
return trainer.post_trainer()
|
||||
|
||||
@app.route("/trainer/authenticate", methods=['POST'])
|
||||
@app.route("/trainer/authenticate", methods=["POST"])
|
||||
def route_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):
|
||||
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 app
|
||||
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
|
||||
import datetime
|
||||
import jwt
|
||||
|
@ -12,19 +13,16 @@ def get_trainer(id):
|
|||
try:
|
||||
return trainer_schema.dump(Trainer.query.get(id))
|
||||
except:
|
||||
return jsonify({})
|
||||
return ("", 404)
|
||||
|
||||
def get_trainers():
|
||||
args = request.args
|
||||
|
||||
try:
|
||||
limit = int(args.get("limit", -1))
|
||||
offset = int(args.get("offset", 0))
|
||||
except ValueError:
|
||||
return ParsingError("Couldn't parse parameter as integer")
|
||||
|
||||
if limit < -1 or offset < 0:
|
||||
return ParsingError("Expected positive integer as parameter")
|
||||
limit = parse_limit()
|
||||
offset = parse_offset()
|
||||
except ParsingException as e:
|
||||
return ParsingError(e.message)
|
||||
|
||||
nickname = args.get("nickname", "")
|
||||
nickname_contains = args.get("nickname_contains", "")
|
||||
|
@ -53,14 +51,8 @@ def get_trainer_by_email(email):
|
|||
|
||||
def post_trainer():
|
||||
try:
|
||||
json = request.get_json()
|
||||
except:
|
||||
return ParsingError("Failed to parse JSON body")
|
||||
json = parse_json_obj()
|
||||
|
||||
if type(json) is not dict:
|
||||
return ParsingError("Expected JSON object as body")
|
||||
|
||||
try:
|
||||
nickname = json["nickname"]
|
||||
first_name = json["first_name"]
|
||||
last_name = json["last_name"]
|
||||
|
@ -81,25 +73,22 @@ def post_trainer():
|
|||
db.session.commit()
|
||||
|
||||
return trainer_schema.dump(trainer)
|
||||
except ParsingException as e:
|
||||
return ParsingError(e.message)
|
||||
except InvalidTeam:
|
||||
return ParsingError("Field team is invalid")
|
||||
except KeyError:
|
||||
return ParsingError("Missing JSON object fields")
|
||||
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():
|
||||
try:
|
||||
auth = request.get_json()
|
||||
except:
|
||||
return ParsingError("Failed to parse JSON body")
|
||||
|
||||
if type(auth) is not dict:
|
||||
return ParsingError("Expected JSON object as body")
|
||||
|
||||
try:
|
||||
auth = parse_json_obj()
|
||||
email = auth["email"]
|
||||
password = auth["password"]
|
||||
except ParsingException as e:
|
||||
return ParsingError(e.message)
|
||||
except KeyError:
|
||||
return AuthenticationFailure("Login required")
|
||||
|
||||
|
@ -113,7 +102,7 @@ def auth_trainer():
|
|||
"username": trainer.nickname,
|
||||
"exp": datetime.datetime.now() + datetime.timedelta(hours=12)
|
||||
},
|
||||
app.config["SECRET_KEY"])
|
||||
app.config["SECRET_KEY"], algorithm="HS256")
|
||||
return jsonify(
|
||||
{
|
||||
"id": trainer.id,
|
||||
|
|
Loading…
Reference in New Issue