Skip to content
Snippets Groups Projects
Commit 41aa617b authored by Carl Schönfelder's avatar Carl Schönfelder
Browse files

Resolve "Add api methods"

parent 5c4eae2f
No related branches found
No related tags found
1 merge request!34Resolve "Add api methods"
Pipeline #38260 passed
Showing with 324 additions and 95 deletions
...@@ -29,7 +29,6 @@ ...@@ -29,7 +29,6 @@
"options": { "options": {
"cwd": "${workspaceFolder}/client" "cwd": "${workspaceFolder}/client"
}, },
}, },
{ {
"label": "Start server", "label": "Start server",
...@@ -54,6 +53,16 @@ ...@@ -54,6 +53,16 @@
"cwd": "${workspaceFolder}/server" "cwd": "${workspaceFolder}/server"
}, },
}, },
{
"label": "Populate server",
"type": "shell",
"group": "build",
"command": "env/Scripts/python populate.py",
"problemMatcher": [],
"options": {
"cwd": "${workspaceFolder}/server"
},
},
{ {
"label": "Open server coverage", "label": "Open server coverage",
"type": "shell", "type": "shell",
...@@ -63,7 +72,6 @@ ...@@ -63,7 +72,6 @@
"options": { "options": {
"cwd": "${workspaceFolder}/server" "cwd": "${workspaceFolder}/server"
}, },
}, },
{ {
"label": "Start client and server", "label": "Start client and server",
......
...@@ -25,7 +25,6 @@ def create_app(config_name="configmodule.DevelopmentConfig"): ...@@ -25,7 +25,6 @@ def create_app(config_name="configmodule.DevelopmentConfig"):
from app.api import api_blueprint from app.api import api_blueprint
app.register_blueprint(api_blueprint, url_prefix="/api") app.register_blueprint(api_blueprint, url_prefix="/api")
return app return app
......
from functools import wraps
from flask import Blueprint from flask import Blueprint
from flask_jwt_extended import verify_jwt_in_request
from flask_jwt_extended.utils import get_jwt_claims
def admin_required():
def wrapper(fn):
@wraps(fn)
def decorator(*args, **kwargs):
verify_jwt_in_request()
claims = get_jwt_claims()
if claims["role"] == "Admin":
return fn(*args, **kwargs)
else:
return {"message:": "Admins only"}, 403
return decorator
return wrapper
def text_response(text, code=200):
return {"message": text}, code
def query_response(db_items, code=200):
if type(db_items) is not list:
db_items = [db_items]
return {"result": [i.get_dict() for i in db_items]}, code
def object_response(items, code=200):
if type(items) is not list:
items = [items]
return {"result": items}, code
api_blueprint = Blueprint("api", __name__) api_blueprint = Blueprint("api", __name__)
# Import the rest of the routes. # Import the rest of the routes.
from app.api import users, admin from app.api import admin, users
###
# Admin stuff placed here for later use
# No need to implement this before the application is somewhat done
###
import app.database.controller as dbc
import app.utils.http_codes as codes
from app import db
from app.api import admin_required, api_blueprint, object_response, query_response, text_response
from app.database.models import Blacklist, City, Competition, Role, User
from app.utils.validator import edit_user_schema, login_schema, register_schema, validate_object
from flask import request
from flask_jwt_extended import (
create_access_token,
create_refresh_token,
get_jwt_identity,
get_raw_jwt,
jwt_refresh_token_required,
jwt_required,
)
@api_blueprint.route("/competitions/<int:id>", methods=["GET"])
@jwt_required
def get(id):
item = Competition.query.filter(Competition.id == id).first()
if not item:
return text_response("Competition not found", codes.NOT_FOUND)
return query_response(item)
@api_blueprint.route("/competitions", methods=["POST"])
@jwt_required
def create():
json_dict = request.get_json(force=True)
name = json_dict.get("name")
city_id = json_dict.get("city_id")
year = json_dict.get("year", 2020)
style_id = json_dict.get("style_id", 0)
dbc.add.competition(name, year, style_id, city_id)
item = Competition.query.filter(Competition.name == name).first()
return query_response(item)
import datetime import datetime
import app.database.controller as dbc import app.database.controller as dbc
import app.utils.http_codes as codes
from app import db from app import db
from app.api import api_blueprint from app.api import admin_required, api_blueprint, object_response, query_response, text_response
from app.database.models import Blacklist, User from app.database.models import Blacklist, City, Role, User
from app.utils.validator import edit_user_schema, login_schema, register_schema, validate_object from app.utils.validator import edit_user_schema, login_schema, register_schema, validate_object
from flask import request from flask import request
from flask_jwt_extended import ( from flask_jwt_extended import (
...@@ -16,19 +17,34 @@ from flask_jwt_extended import ( ...@@ -16,19 +17,34 @@ from flask_jwt_extended import (
) )
def get_current_user(): ##Helpers
def _get_current_user():
return User.query.filter_by(id=get_jwt_identity()).first() return User.query.filter_by(id=get_jwt_identity()).first()
def _create_token(user):
expires = datetime.timedelta(days=7)
claims = {"role": user.role.name}
return create_access_token(identity=user.id, expires_delta=expires, user_claims=claims)
@api_blueprint.route("/users/test", methods=["GET"]) @api_blueprint.route("/users/test", methods=["GET"])
def test(): def test():
return {"message": "hello teknik8"}, 200 return text_response("hello teknik8")
@api_blueprint.route("/users/test_auth", methods=["GET"]) @api_blueprint.route("/users/test_auth", methods=["GET"])
@jwt_required @jwt_required
@admin_required()
def test_auth(): def test_auth():
return {"message": "you are authenticated"}, 200 return text_response("you are authenticated")
@api_blueprint.route("/roles", methods=["GET"])
@jwt_required
def get_roles():
return query_response(Role.query.all())
@api_blueprint.route("/users/login", methods=["POST"]) @api_blueprint.route("/users/login", methods=["POST"])
...@@ -37,26 +53,20 @@ def login(): ...@@ -37,26 +53,20 @@ def login():
validate_msg = validate_object(login_schema, json_dict) validate_msg = validate_object(login_schema, json_dict)
if validate_msg is not None: if validate_msg is not None:
return {"message": validate_msg}, 400 return text_response(validate_msg, codes.BAD_REQUEST)
email = json_dict["email"] email = json_dict.get("email")
password = json_dict["password"] password = json_dict.get("password")
user = User.query.filter_by(email=email).first() user = User.query.filter_by(email=email).first()
# Don't show the user that the email was correct unless the password was also correct if not user or not user.is_correct_password(password):
if not user: return text_response("Invalid email or password", codes.UNAUTHORIZED)
return {"message": "The email or password you entered is incorrect."}, 401
if not user.is_correct_password(password):
return {"message": "The email or password you entered is incorrect."}, 401
expires = datetime.timedelta(days=7) access_token = _create_token(user)
access_token = create_access_token(identity=user.id, expires_delta=expires)
refresh_token = create_refresh_token(identity=user.id) refresh_token = create_refresh_token(identity=user.id)
return (
{"id": user.id, "access_token": access_token, "refresh_token": refresh_token}, response = {"id": user.id, "access_token": access_token, "refresh_token": refresh_token}
200, return object_response(response)
)
@api_blueprint.route("/users/logout", methods=["POST"]) @api_blueprint.route("/users/logout", methods=["POST"])
...@@ -66,15 +76,16 @@ def logout(): ...@@ -66,15 +76,16 @@ def logout():
db.session.add(Blacklist(jti)) db.session.add(Blacklist(jti))
db.session.commit() db.session.commit()
return {"message": "message fully logged out"}, 200 return text_response("Logged out")
@api_blueprint.route("/users/refresh", methods=["POST"]) @api_blueprint.route("/users/refresh", methods=["POST"])
@jwt_refresh_token_required @jwt_refresh_token_required
def refresh(): def refresh():
current_user = get_jwt_identity() current_user = get_jwt_identity()
ret = {"access_token": create_access_token(identity=current_user)} response = {"access_token": _create_token(current_user)}
return ret, 200
return object_response(response)
@api_blueprint.route("/users/", methods=["POST"]) @api_blueprint.route("/users/", methods=["POST"])
...@@ -83,68 +94,101 @@ def create(): ...@@ -83,68 +94,101 @@ def create():
validate_msg = validate_object(register_schema, json_dict) validate_msg = validate_object(register_schema, json_dict)
if validate_msg is not None: if validate_msg is not None:
return {"message": validate_msg}, 400 return text_response(validate_msg, codes.BAD_REQUEST)
email = json_dict.get("email")
password = json_dict.get("password")
role = json_dict.get("role")
city = json_dict.get("city")
existing_user = User.query.filter_by(email=json_dict["email"]).first() existing_user = User.query.filter(User.email == email).first()
if existing_user is not None: if existing_user is not None:
return {"message": "User already exists"}, 400 return text_response("User already exists", codes.BAD_REQUEST)
dbc.add.user(json_dict["email"], json_dict["password"], json_dict["role"], json_dict["city"]) dbc.add.user(email, password, role, city)
item_user = User.query.filter_by(email=json_dict["email"]).first() item_user = User.query.filter(User.email == email).first()
return item_user.get_dict(), 200 return query_response(item_user)
@api_blueprint.route("/users/", methods=["PUT"]) @api_blueprint.route("/users/", defaults={"user_id": None}, methods=["PUT"])
@api_blueprint.route("/users/<int:user_id>", methods=["PUT"])
@jwt_required @jwt_required
def edit(): def edit(user_id):
json_dict = request.get_json(force=True) json_dict = request.get_json(force=True)
validate_msg = validate_object(edit_user_schema, json_dict) validate_msg = validate_object(edit_user_schema, json_dict)
if validate_msg is not None: if validate_msg is not None:
return {"message": validate_msg}, 400 return text_response(validate_msg, codes.BAD_REQUEST)
if user_id:
item_user = User.query.filter(User.id == user_id).first()
else:
item_user = _get_current_user()
name = json_dict.get("name")
role = json_dict.get("city")
city = json_dict.get("password")
if name:
item_user.name = name.title()
user = get_current_user() if city:
user.name = json_dict["name"].title() if City.query.filter(City.name == city).first() is None:
return text_response(f"City {city} does not exist", codes.BAD_REQUEST)
item_user.city = city
if role:
if Role.query.filter(Role.name == role).first() is None:
return text_response(f"Role {role} does not exist", codes.BAD_REQUEST)
item_user.role = role
db.session.commit() db.session.commit()
return user.get_dict(), 200 db.session.refresh(item_user)
return query_response(item_user)
@api_blueprint.route("/users/", methods=["DELETE"]) @api_blueprint.route("/users/", defaults={"user_id": None}, methods=["DELETE"])
@api_blueprint.route("/users/<int:user_id>", methods=["DELETE"])
@jwt_required @jwt_required
def delete(): def delete(user_id):
user = get_current_user() if user_id:
db.session.delete(user) item_user = User.query.filter(User.id == user_id).first()
else:
item_user = _get_current_user()
db.session.delete(item_user)
jti = get_raw_jwt()["jti"] jti = get_raw_jwt()["jti"]
db.session.add(Blacklist(jti)) db.session.add(Blacklist(jti))
db.session.commit() db.session.commit()
return {"message": "User was deleted"}, 200
return text_response("User deleted")
### ###
# Getters # Getters
### ###
@api_blueprint.route("/users/", defaults={"UserID": None}, methods=["GET"]) @api_blueprint.route("/users/", defaults={"user_id": None}, methods=["GET"])
@api_blueprint.route("/users/<int:UserID>", methods=["GET"]) @api_blueprint.route("/users/<int:user_id>", methods=["GET"])
@jwt_required @jwt_required
def get(UserID): def get(user_id):
if UserID: if user_id:
user = User.query.filter_by(id=UserID).first() user = User.query.filter(User.id == user_id).first()
else: else:
user = get_current_user() user = _get_current_user()
if not user: if not user:
return {"message": "User not found"}, 404 return text_response("User not found", codes.NOT_FOUND)
return user.get_dict(), 200 return query_response(user)
# Searchable, returns 10 max at default # Searchable, returns 15 max at default
@api_blueprint.route("/users/search", methods=["GET"]) @api_blueprint.route("/users/search", methods=["GET"])
@jwt_required
def search(): def search():
arguments = request.args arguments = request.args
query = User.query query = User.query
...@@ -164,4 +208,4 @@ def search(): ...@@ -164,4 +208,4 @@ def search():
else: else:
query = query.limit(15) query = query.limit(15)
return [i.get_dict() for i in query.all()], 200 return query_response(query.all())
...@@ -5,5 +5,5 @@ from sqlalchemy.sql import func ...@@ -5,5 +5,5 @@ from sqlalchemy.sql import func
class Base(Model): class Base(Model):
__abstract__ = True __abstract__ = True
created = sa.Column(sa.DateTime(timezone=True), server_default=func.now()) _created = sa.Column(sa.DateTime(timezone=True), server_default=func.now())
updated = sa.Column(sa.DateTime(timezone=True), onupdate=func.now()) _updated = sa.Column(sa.DateTime(timezone=True), onupdate=func.now())
...@@ -13,8 +13,8 @@ def user(email, plaintext_password, role, city): ...@@ -13,8 +13,8 @@ def user(email, plaintext_password, role, city):
return User.query.filter(User.email == email).first() return User.query.filter(User.email == email).first()
def competition(name, style_id, city_id): def competition(name, year, style_id, city_id):
db.session.add(Competition(name, style_id, city_id)) db.session.add(Competition(name, year, style_id, city_id))
db.session.commit() db.session.commit()
filters = (Competition.name == name) & (Competition.city_id == city_id) filters = (Competition.name == name) & (Competition.city_id == city_id)
......
from app import db from app import db
from app.database.models import City, Competition, Role, Slide, Style, User from app.database.models import City, Competition, Role, Slide, Style, User
from sqlalchemy import and_, or_ from sqlalchemy import and_, or_
def user():
return
...@@ -12,6 +12,9 @@ class Blacklist(db.Model): ...@@ -12,6 +12,9 @@ class Blacklist(db.Model):
def __init__(self, jti): def __init__(self, jti):
self.jti = jti self.jti = jti
def get_dict(self):
return {"id": self.id, "jti": self.jti, "expire_date": self.expire_date}
class Role(db.Model): class Role(db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
...@@ -22,6 +25,9 @@ class Role(db.Model): ...@@ -22,6 +25,9 @@ class Role(db.Model):
def __init__(self, name): def __init__(self, name):
self.name = name self.name = name
def get_dict(self):
return {"id": self.id, "name": self.name}
# TODO Region? # TODO Region?
class City(db.Model): class City(db.Model):
...@@ -59,7 +65,13 @@ class User(db.Model): ...@@ -59,7 +65,13 @@ class User(db.Model):
self.authenticated = False self.authenticated = False
def get_dict(self): def get_dict(self):
return {"id": self.id, "email": self.email, "name": self.name} return {
"id": self.id,
"email": self.email,
"name": self.name,
"role_id": self.role_id,
"city_id": self.city_id,
}
@hybrid_property @hybrid_property
def password(self): def password(self):
...@@ -105,14 +117,17 @@ class Style(db.Model): ...@@ -105,14 +117,17 @@ class Style(db.Model):
class Competition(db.Model): class Competition(db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(STRING_SIZE), unique=True) name = db.Column(db.String(STRING_SIZE), unique=True)
year = db.Column(db.Integer, nullable=False, default=2020)
style_id = db.Column(db.Integer, db.ForeignKey("style.id"), nullable=False) style_id = db.Column(db.Integer, db.ForeignKey("style.id"), nullable=False)
city_id = db.Column(db.Integer, db.ForeignKey("city.id"), nullable=False) city_id = db.Column(db.Integer, db.ForeignKey("city.id"), nullable=False)
slides = db.relationship("Slide", backref="competition") slides = db.relationship("Slide", backref="competition")
teams = db.relationship("Team", backref="competition") teams = db.relationship("Team", backref="competition")
def __init__(self, name, style_id, city_id): def __init__(self, name, year, style_id, city_id):
self.name = name self.name = name
self.year = year
self.style_id = style_id self.style_id = style_id
self.city_id = city_id self.city_id = city_id
......
OK = 200
BAD_REQUEST = 400
UNAUTHORIZED = 401
FORBIDDEN = 403
NOT_FOUND = 404
GONE = 410
INTERNAL_SERVER_ERROR = 500
SERVICE_UNAVAILABLE = 503
import json
from app import db from app import db
def post(client, url, data, headers=None):
response = client.post(url, data=json.dumps(data), headers=headers)
body = json.loads(response.data.decode())
return response, body
def get(client, url, query_string=None, headers=None):
response = client.get(url, query_string=query_string, headers=headers)
body = json.loads(response.data.decode())
return response, body
def put(client, url, data, headers=None):
response = client.put(url, data=json.dumps(data), headers=headers)
body = json.loads(response.data.decode())
return response, body
def delete(client, url, data, headers=None):
response = client.delete(url, data=json.dumps(data), headers=headers)
body = json.loads(response.data.decode())
return response, body
# Try insert invalid row. If it fails then the test is passed # Try insert invalid row. If it fails then the test is passed
def assert_insert_fail(db_type, *args): def assert_insert_fail(db_type, *args):
try: try:
......
...@@ -23,4 +23,6 @@ register_schema = { ...@@ -23,4 +23,6 @@ register_schema = {
edit_user_schema = { edit_user_schema = {
"name": {"type": "string", "required": False, "minlength": 1, "maxlength": 50}, "name": {"type": "string", "required": False, "minlength": 1, "maxlength": 50},
"role": {"type": "string", "required": False},
"city": {"type": "string", "required": False},
} }
...@@ -9,6 +9,7 @@ class Config: ...@@ -9,6 +9,7 @@ class Config:
class DevelopmentConfig(Config): class DevelopmentConfig(Config):
DEBUG = True DEBUG = True
SQLALCHEMY_DATABASE_URI = "sqlite:///database.db"
class TestingConfig(Config): class TestingConfig(Config):
......
import app.database.controller as dbc
from app import create_app, db
from app.database.models import City, MediaType, QuestionType, Role
user = {"email": "test@test.se", "password": "password", "role": "Admin", "city": "Linköping"}
media_types = ["Image", "Video"]
question_types = ["Boolean", "Multiple", "Text"]
roles = ["Admin", "Editor"]
cities = ["Linköping"]
def _add_items():
# Add media types
for item in media_types:
db.session.add(MediaType(item))
db.session.commit()
# Add question types
for item in question_types:
db.session.add(QuestionType(item))
db.session.commit()
# Add roles
for item in roles:
db.session.add(Role(item))
db.session.commit()
# Add cities
for item in cities:
db.session.add(City(item))
db.session.commit()
# Add user with role and city
dbc.add.user("test@test.se", "password", "Admin", "Linköping")
db.session.flush()
app = create_app("configmodule.DevelopmentConfig")
with app.app_context():
db.create_all()
_add_items()
import json import json
from app.database.populate import add_default_values from app.database.populate import add_default_values
from app.utils.test_helpers import delete, get, post, put
from tests import app, client from tests import app, client
...@@ -8,40 +9,42 @@ from tests import app, client ...@@ -8,40 +9,42 @@ from tests import app, client
def test_app(client): def test_app(client):
add_default_values() add_default_values()
register_data = {"email": "test1@test.se", "password": "abc123", "role": "Admin", "city": "Linköping"} # Login in with default user
response, body = post(client, "/api/users/login", {"email": "test@test.se", "password": "password"})
item = body["result"][0]
headers = {"Authorization": "Bearer " + item["access_token"]}
# Create user # Create user
rv = client.post( register_data = {"email": "test1@test.se", "password": "abc123", "role": "Admin", "city": "Linköping"}
"/api/users/", response, body = post(client, "/api/users/", register_data, headers)
data=json.dumps(register_data), item = body["result"][0]
)
rv_dict = json.loads(rv.data.decode())
assert rv.status_code == 200 assert response.status_code == 200
assert rv_dict["id"] == 2 assert item["id"] == 2
assert "password" not in rv_dict assert "password" not in item
assert rv_dict["email"] == "test1@test.se" assert item["email"] == "test1@test.se"
# Try loggin with wrong PASSWORD # Try loggin with wrong PASSWORD
rv = client.post("/api/users/login", data=json.dumps({"email": "test1@test.se", "password": "abc1234"})) response, body = post(client, "/api/users/login", {"email": "test1@test.se", "password": "abc1234"})
assert rv.status_code == 401 assert response.status_code == 401
# Try loggin with wrong Email # Try loggin with wrong Email
rv = client.post("/api/users/login", data=json.dumps({"email": "testx@test.se", "password": "abc1234"})) response, body = post(client, "/api/users/login", {"email": "testx@test.se", "password": "abc1234"})
assert rv.status_code == 401 assert response.status_code == 401
# Try loggin with right PASSWORD # Try loggin with right PASSWORD
rv = client.post("/api/users/login", data=json.dumps({"email": "test1@test.se", "password": "abc123"})) response, body = post(client, "/api/users/login", {"email": "test1@test.se", "password": "abc123"})
rv_dict = json.loads(rv.data.decode()) item = body["result"][0]
assert rv.status_code == 200 headers = {"Authorization": "Bearer " + item["access_token"]}
headers = {"Authorization": "Bearer " + rv_dict["access_token"]} assert response.status_code == 200
# Get the current user # Get the current user
rv = client.get("/api/users/", headers=headers) response, body = get(client, "/api/users/", headers=headers)
rv_dict = json.loads(rv.data.decode()) item = body["result"][0]
assert rv.status_code == 200 assert response.status_code == 200
assert rv_dict["email"] == "test1@test.se" assert item["email"] == "test1@test.se"
rv = client.put("/api/users/", data=json.dumps({"name": "carl carlsson"}), headers=headers) response, body = put(client, "/api/users/", {"name": "carl carlsson"}, headers=headers)
rv_dict = json.loads(rv.data.decode()) item = body["result"][0]
assert rv.status_code == 200 assert response.status_code == 200
assert rv_dict["name"] == "Carl Carlsson" assert item["name"] == "Carl Carlsson"
import app.database.controller as dbc
import pytest import pytest
from app.database.models import ( from app.database.models import (
City, City,
...@@ -13,9 +14,8 @@ from app.database.models import ( ...@@ -13,9 +14,8 @@ from app.database.models import (
Team, Team,
User, User,
) )
import app.database.controller as dbc
from app.database.populate import add_default_values from app.database.populate import add_default_values
from app.utils.test_helpers import * from app.utils.test_helpers import assert_exists, assert_insert_fail, assert_object_values
from tests import app, client, db from tests import app, client, db
...@@ -89,8 +89,8 @@ def test_question(client): ...@@ -89,8 +89,8 @@ def test_question(client):
# Add competition # Add competition
item_city = City.query.filter_by(name="Linköping").first() item_city = City.query.filter_by(name="Linköping").first()
db.session.add(Competition("teknik8", item_style.id, item_city.id)) db.session.add(Competition("teknik8", 2020, item_style.id, item_city.id))
db.session.add(Competition("teknik9", item_style.id, item_city.id)) db.session.add(Competition("teknik9", 2020, item_style.id, item_city.id))
db.session.commit() db.session.commit()
item_competition = Competition.query.filter_by(name="teknik8").first() item_competition = Competition.query.filter_by(name="teknik8").first()
item_competition_2 = Competition.query.filter_by(name="teknik9").first() item_competition_2 = Competition.query.filter_by(name="teknik9").first()
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment