diff --git a/server/app/apis/__init__.py b/server/app/apis/__init__.py index dbff66c786e573fa5acb9c8a63ce187e344f745d..3c0e7020a2d738a8f29c993fb40e92e88cf612a7 100644 --- a/server/app/apis/__init__.py +++ b/server/app/apis/__init__.py @@ -38,13 +38,11 @@ def object_response(items, code=200): from flask_restx import Api -from .auth import api as ns2 -from .competitions import api as ns4 -from .slides import api as ns3 -from .users import api as ns1 +from .auth import api as auth_ns +from .competitions import api as comp_ns +from .users import api as user_ns flask_api = Api() -flask_api.add_namespace(ns1, path="/api/users") -flask_api.add_namespace(ns3, path="/api/slides") -flask_api.add_namespace(ns2, path="/api/auth") -flask_api.add_namespace(ns4, path="/api/competitions") +flask_api.add_namespace(user_ns, path="/api/users") +flask_api.add_namespace(auth_ns, path="/api/auth") +flask_api.add_namespace(comp_ns, path="/api/competitions") diff --git a/server/app/apis/auth.py b/server/app/apis/auth.py index a425f904b5e41a71a8bc6356b3a3f0701720fc1f..306392ff64c6cbf1af7723bc1cdeeb9f4e5084df 100644 --- a/server/app/apis/auth.py +++ b/server/app/apis/auth.py @@ -1,6 +1,7 @@ import app.core.controller as dbc import app.core.utils.http_codes as codes from app.apis import admin_required +from app.core.dto import AuthDTO from app.core.models import User from app.core.parsers import create_user_parser, login_parser from flask_jwt_extended import ( @@ -13,7 +14,8 @@ from flask_jwt_extended import ( ) from flask_restx import Namespace, Resource, cors -api = Namespace("auth") +api = AuthDTO.api +user_model = AuthDTO.model def get_user_claims(item_user): @@ -28,13 +30,13 @@ class AuthSignup(Resource): args = create_user_parser.parse_args(strict=True) email = args.get("email") password = args.get("password") - role = args.get("role") - city = args.get("city") + role_id = args.get("role_id") + city_id = args.get("city_id") if User.query.filter(User.email == email).count() > 0: api.abort(codes.BAD_REQUEST, "User already exists") - item_user = dbc.add.user(email, password, role, city) + item_user = dbc.add.default(User(email, password, role_id, city_id)) if not item_user: api.abort(codes.BAD_REQUEST, "User could not be created") @@ -48,7 +50,7 @@ class AuthDelete(Resource): @cors.crossdomain(origin="*") def delete(self, ID): item_user = User.query.filter(User.id == ID).first() - dbc.delete(item_user) + dbc.delete.default(item_user) if ID == get_jwt_identity(): jti = get_raw_jwt()["jti"] dbc.add.blacklist(jti) diff --git a/server/app/apis/competitions.py b/server/app/apis/competitions.py index 46c0e88f99f6976d49817be0a7dd6eb36ef01385..a60a49d595b3d0d1a3a9ba3669410ca983d2e3fb 100644 --- a/server/app/apis/competitions.py +++ b/server/app/apis/competitions.py @@ -1,18 +1,24 @@ import app.core.controller as dbc import app.core.utils.http_codes as codes from app.apis import admin_required -from app.core.models import Competition, User +from app.core.dto import CompetitionDTO +from app.core.models import Competition, Slide, Team from app.core.parsers import competition_parser, competition_search_parser -from app.core.schemas import competition_schema from flask_jwt_extended import get_jwt_identity, jwt_required -from flask_restx import Namespace, Resource +from flask_restx import Resource, reqparse -api = Namespace("competitions") -competition_model = api.model(*competition_schema) +api = CompetitionDTO.api +competition_model = CompetitionDTO.model +slide_model = CompetitionDTO.slide_model +team_model = CompetitionDTO.team_model + + +def get_comp(CID): + return Competition.query.filter(Competition.id == CID).first() @api.route("/") -class CompetitionBase(Resource): +class CompetitionsList(Resource): @jwt_required @api.marshal_with(competition_model) def post(self): @@ -24,7 +30,7 @@ class CompetitionBase(Resource): style_id = args.get("style_id") # Add competition - item_competition = dbc.add.competition(name, year, style_id, city_id) + item_competition = dbc.add.default(Competition(name, year, style_id, city_id)) # Add default slide item_slide = dbc.add.slide(item_competition.id) @@ -34,11 +40,11 @@ class CompetitionBase(Resource): @api.route("/<ID>") @api.param("ID") -class CompetitionByID(Resource): +class Competitions(Resource): @jwt_required @api.marshal_with(competition_model) def get(self, ID): - item = Competition.query.filter(Competition.id == ID).first() + item = get_comp(ID) return item @jwt_required @@ -46,7 +52,7 @@ class CompetitionByID(Resource): def put(self, ID): args = competition_parser.parse_args(strict=True) - item = Competition.query.filter(Competition.id == ID).first() + item = get_comp(ID) name = args.get("name") year = args.get("year") city_id = args.get("city_id") @@ -55,8 +61,8 @@ class CompetitionByID(Resource): @jwt_required def delete(self, ID): - item = Competition.query.filter(Competition.id == ID).first() - dbc.delete(item) + item = get_comp(ID) + dbc.delete.competition(item) return "deleted" @@ -73,3 +79,73 @@ class CompetitionSearch(Resource): page = args.get("page") page_size = args.get("page_size") return dbc.get.search_competitions(name, year, city_id, style_id, page, page_size) + + +@api.route("/<CID>/slides") +@api.param("CID") +class SlidesList(Resource): + @jwt_required + @api.marshal_with(slide_model) + def get(self, CID): + item_comp = get_comp(CID) + return item_comp.slides + + @jwt_required + @api.marshal_with(slide_model) + def post(self, CID): + dbc.add.slide(CID) + item_comp = get_comp(CID) + return item_comp.slides + + +@api.route("/<CID>/slides/<SID>") +@api.param("CID,SID") +class Slides(Resource): + @jwt_required + @api.marshal_with(slide_model) + def get(self, CID, SID): + item_slide = dbc.get.slide(CID, SID) + return item_slide + + @jwt_required + def delete(self, CID, SID): + item_slide = dbc.get.slide(CID, SID) + dbc.delete.slide(item_slide) + return "deleted" + + +@api.route("/<CID>/teams") +@api.param("CID") +class TeamsList(Resource): + @jwt_required + @api.marshal_with(team_model) + def get(self, CID): + item_comp = get_comp(CID) + return item_comp.teams + + @jwt_required + @api.marshal_with(team_model) + def post(self, CID): + parser = reqparse.RequestParser() + parser.add_argument("name", type=str, location="json") + args = competition_parser.parse_args(strict=True) + + dbc.add.default(Team(args["name"], CID)) + item_comp = get_comp(CID) + return item_comp.teams + + +@api.route("/<CID>/teams/<TID>") +@api.param("CID,TID") +class Teams(Resource): + @jwt_required + @api.marshal_with(team_model) + def get(self, CID, TID): + item_team = dbc.get.team(CID, TID) + return item_team + + @jwt_required + def delete(self, CID, TID): + item_team = dbc.get.team(CID, TID) + dbc.delete.team(item_team) + return "deleted" diff --git a/server/app/apis/slides.py b/server/app/apis/slides.py index bc6dfa3d748a569d154a2179e4cdf25b7b9416ed..6b0ee73517771f9f8994344ffbcd74a847b5c2fa 100644 --- a/server/app/apis/slides.py +++ b/server/app/apis/slides.py @@ -1,3 +1,8 @@ -from flask_restx import Namespace, Resource, fields - -api = Namespace("slides") +import app.core.controller as dbc +import app.core.utils.http_codes as codes +from app.apis import admin_required +from app.core.models import Competition, Slide, User +from app.core.parsers import competition_parser, competition_search_parser +from app.core.schemas import competition_schema +from flask_jwt_extended import get_jwt_identity, jwt_required +from flask_restx import Namespace, Resource diff --git a/server/app/apis/users.py b/server/app/apis/users.py index 424e608e824ec7202580aaba32b1e1d0451f4879..9b7d266e074f2ec7dc9c75383eef2cd66102d5cc 100644 --- a/server/app/apis/users.py +++ b/server/app/apis/users.py @@ -1,31 +1,32 @@ import app.core.controller as dbc import app.core.utils.http_codes as codes from app.apis import admin_required +from app.core.dto import UserDTO from app.core.models import User from app.core.parsers import user_parser, user_search_parser from app.core.schemas import user_schema from flask_jwt_extended import get_jwt_identity, jwt_required from flask_restx import Namespace, Resource -api = Namespace("users") -user_model = api.model(*user_schema) +api = UserDTO.api +user_model = UserDTO.model def edit_user(item_user, args): email = args.get("email") name = args.get("name") - city = args.get("city") - role = args.get("role") + city_id = args.get("city_id") + role_id = args.get("role_id") if email: if User.query.filter(User.email == args["email"]).count() > 0: api.abort(codes.BAD_REQUEST, "Email is already in use") - return dbc.edit.user(item_user, name, email, city, role) + return dbc.edit.user(item_user, name, email, city_id, role_id) @api.route("/") -class UserBase(Resource): +class UsersList(Resource): @jwt_required @api.marshal_list_with(user_model) def get(self): @@ -41,7 +42,7 @@ class UserBase(Resource): @api.route("/<ID>") @api.param("ID") -class UserByID(Resource): +class Users(Resource): @jwt_required @api.marshal_with(user_model) def get(self, ID): @@ -63,9 +64,9 @@ class UserSearch(Resource): args = user_search_parser.parse_args(strict=True) name = args.get("name") email = args.get("email") - role = args.get("role") - city = args.get("city") + role_id = args.get("role_id") + city_id = args.get("city_id") page = args.get("page", 1) page_size = args.get("page_size", 15) - return dbc.get.search_user(email, name, city, role, page, page_size) + return dbc.get.search_user(email, name, city_id, role_id, page, page_size) diff --git a/server/app/core/controller/__init__.py b/server/app/core/controller/__init__.py index 05f51a94602e790ffc2034f0807218f806fb6709..ec746b748474a23bcfeff64ecda5161b2c2425b6 100644 --- a/server/app/core/controller/__init__.py +++ b/server/app/core/controller/__init__.py @@ -1,8 +1,3 @@ # import add, get from app.core import db -from app.core.controller import add, edit, get - - -def delete(item): - db.session.delete(item) - db.session.commit() +from app.core.controller import add, delete, edit, get diff --git a/server/app/core/controller/add.py b/server/app/core/controller/add.py index 48f9c949238f8a175590d876f9499c12b22b5087..958cf766d06cb2b30cab7c0c8fa9fd347df501c9 100644 --- a/server/app/core/controller/add.py +++ b/server/app/core/controller/add.py @@ -2,35 +2,22 @@ from app.core import db from app.core.models import Blacklist, City, Competition, Role, Slide, User -def blacklist(jti): - db.session.add(Blacklist(jti)) - db.session.commit() - - -def user(email, plaintext_password, role, city): - item_role = Role.query.filter(Role.name == role).first() - item_city = City.query.filter(City.name == city).first() - - new_user = User(email, plaintext_password, item_role.id, item_city.id) - db.session.add(new_user) +def default(item): + db.session.add(item) db.session.commit() + db.session.refresh(item) + return item - return User.query.filter(User.email == email).first() - -def competition(name, year, style_id, city_id): - db.session.add(Competition(name, year, style_id, city_id)) +def blacklist(jti): + db.session.add(Blacklist(jti)) db.session.commit() - filters = (Competition.name == name) & (Competition.city_id == city_id) - return Competition.query.filter(filters).first() - def slide(competition_id): - # item_slides = Slide.query.filter(Slide.competition_id == competition_id).order_by(Slide.order).all() order = Slide.query.filter(Slide.competition_id == competition_id).count() - db.session.add(Slide(order, competition_id)) + item = Slide(order, competition_id) + db.session.add(item) db.session.commit() - - filters = (Slide.order == order) & (Slide.competition_id == competition_id) - return Slide.query.filter(filters).first() + db.session.refresh(item) + return item diff --git a/server/app/core/controller/delete.py b/server/app/core/controller/delete.py new file mode 100644 index 0000000000000000000000000000000000000000..037373e433dd191c2ce1e6546075c413caf8502c --- /dev/null +++ b/server/app/core/controller/delete.py @@ -0,0 +1,26 @@ +from app.core import db +from app.core.models import Blacklist, City, Competition, Role, Slide, User + + +def default(item): + db.session.delete(item) + db.session.commit() + + +def slide(item): + default(item) + + +def team(item): + default(item) + + +def competition(item): + # Remove all slides from competition + for item_slide in item.slides: + slide(item_slide) + # Remove all teams from competition + for item_team in item.teams: + team(item_team) + + default(item) diff --git a/server/app/core/controller/edit.py b/server/app/core/controller/edit.py index da5340812343310a5e92b3205db804157eb10393..a92779042743b70c0d0f06a97a36afa8f23eb250 100644 --- a/server/app/core/controller/edit.py +++ b/server/app/core/controller/edit.py @@ -17,7 +17,7 @@ def competition(item, name=None, year=None, city_id=None, style_id=None): return item -def user(item, name=None, email=None, city=None, role=None): +def user(item, name=None, email=None, city_id=None, role_id=None): if name: item.name = name.title() @@ -25,13 +25,11 @@ def user(item, name=None, email=None, city=None, role=None): if email: item.email = email - if city: - item_city = City.query.filter(City.name == city).first() - item.city_id = item_city.id + if city_id: + item.city_id = city_id - if role: - item_role = Role.query.filter(Role.name == role).first() - item.role_id = item_role.id + if role_id: + item.role_id = role_id db.session.commit() db.session.refresh(item) diff --git a/server/app/core/controller/get.py b/server/app/core/controller/get.py index 6f4fae14078a9d5ac6d0e1e9b44a61ad492553a7..b1a19e04dcce342d013e9f647573bf87ade49198 100644 --- a/server/app/core/controller/get.py +++ b/server/app/core/controller/get.py @@ -1,16 +1,24 @@ -from app.core.models import Competition, User +from app.core.models import Competition, Slide, Team, User -def search_user(email=None, name=None, city=None, role=None, page=1, page_size=15): +def slide(CID, SID): + return Slide.query.filter((Slide.competition_id == CID) & (Slide.id == SID)).first() + + +def team(CID, TID): + return Team.query.filter((Team.competition_id == CID) & (Team.id == TID)).first() + + +def search_user(email=None, name=None, city_id=None, role_id=None, page=1, page_size=15): query = User.query if name: query = query.filter(User.name.like(f"%{name}%")) if email: query = query.filter(User.email.like(f"%{email}%")) - if city: - query = query.filter(User.city.name == city) - if role: - query = query.filter(User.role.name == role) + if city_id: + query = query.filter(User.city_id == city_id) + if role_id: + query = query.filter(User.role_id == role_id) query = query.limit(page_size).offset(page * page_size) diff --git a/server/app/core/dto.py b/server/app/core/dto.py new file mode 100644 index 0000000000000000000000000000000000000000..1ef2f51bd352188e3e8030ba678adbed6db4c71f --- /dev/null +++ b/server/app/core/dto.py @@ -0,0 +1,47 @@ +from app.apis import slides +from flask_restx import Namespace, fields + + +class AuthDTO: + api = Namespace("auth") + model = api.model("Auth", {"id": fields.Integer()}) + + +class UserDTO: + api = Namespace("users") + model = api.model( + "User", + { + "id": fields.Integer(), + "name": fields.String(), + "email": fields.String(), + "role_id": fields.Integer(), + "city_id": fields.Integer(), + }, + ) + + +class CompetitionDTO: + api = Namespace("competitions") + model = api.model( + "Competition", + { + "id": fields.Integer(), + "name": fields.String(), + "year": fields.Integer(), + "style_id": fields.Integer(), + "city_id": fields.Integer(), + }, + ) + slide_model = api.model( + "Slide", + { + "id": fields.Integer(), + "competition_id": fields.Integer(), + "order": fields.Integer(), + }, + ) + + team_model = api.model( + "Team", {"id": fields.Integer(), "name": fields.String(), "competition_id": fields.Integer()} + ) diff --git a/server/app/core/parsers.py b/server/app/core/parsers.py index 5c354d9079da45fd9187968003d06e5e9ebb9d35..9106c5d8d08bba4a3d392bf62239fe485417a458 100644 --- a/server/app/core/parsers.py +++ b/server/app/core/parsers.py @@ -13,27 +13,27 @@ login_parser.add_argument("password", required=True, location="json") ###CREATE_USER#### create_user_parser = login_parser.copy() create_user_parser.add_argument("email", type=inputs.email(), required=True, location="json") -create_user_parser.add_argument("city", required=True, location="json") -create_user_parser.add_argument("role", required=True, location="json") +create_user_parser.add_argument("city_id", type=int, required=True, location="json") +create_user_parser.add_argument("role_id", type=int, required=True, location="json") ###USER#### user_parser = reqparse.RequestParser() user_parser.add_argument("email", type=inputs.email(), location="json") -user_parser.add_argument("name", location="json") -user_parser.add_argument("city", location="json") -user_parser.add_argument("role", location="json") +user_parser.add_argument("name", type=str, location="json") +user_parser.add_argument("city_id", type=int, location="json") +user_parser.add_argument("role_id", type=int, location="json") ###SEARCH_USER#### user_search_parser = search_parser.copy() -user_search_parser.add_argument("name", default=None, location="args") -user_search_parser.add_argument("email", default=None, location="args") -user_search_parser.add_argument("city", default=None, location="args") -user_search_parser.add_argument("role", default=None, location="args") +user_search_parser.add_argument("name", type=str, default=None, location="args") +user_search_parser.add_argument("email", type=str, default=None, location="args") +user_search_parser.add_argument("city_id", type=int, default=None, location="args") +user_search_parser.add_argument("role_id", type=int, default=None, location="args") ###COMPETIION#### competition_parser = reqparse.RequestParser() -competition_parser.add_argument("name", location="json") +competition_parser.add_argument("name", type=str, location="json") competition_parser.add_argument("year", type=int, location="json") competition_parser.add_argument("city_id", type=int, location="json") competition_parser.add_argument("style_id", type=int, location="json") @@ -41,7 +41,7 @@ competition_parser.add_argument("style_id", type=int, location="json") ###SEARCH_COMPETITOIN#### competition_search_parser = search_parser.copy() -competition_search_parser.add_argument("name", default=None, location="args") -competition_search_parser.add_argument("year", default=None, location="args") -competition_search_parser.add_argument("city_id", default=None, location="args") -competition_search_parser.add_argument("style_id", default=None, location="args") +competition_search_parser.add_argument("name", type=str, default=None, location="args") +competition_search_parser.add_argument("year", type=str, default=None, location="args") +competition_search_parser.add_argument("city_id", type=int, default=None, location="args") +competition_search_parser.add_argument("style_id", type=int, default=None, location="args") diff --git a/server/app/core/schemas.py b/server/app/core/schemas.py index fb2681ba5b39d4fb2f34f89f0c2910fb8fcc8088..afc1911be0dcdf67aab6af4344f609bf525968ae 100644 --- a/server/app/core/schemas.py +++ b/server/app/core/schemas.py @@ -6,8 +6,8 @@ user_schema = ( "id": fields.Integer(), "name": fields.String(), "email": fields.String(), - "role": fields.String(attribute=lambda x: x.role.name), - "city": fields.String(attribute=lambda x: x.city.name), + "role_id": fields.Integer(), + "city_id": fields.Integer(), }, ) diff --git a/server/app/core/utils/test_helpers.py b/server/app/core/utils/test_helpers.py index 968281d5af22c91a117aa4ebf58b0fe21ace11eb..648bc27a41aba569f3bd61a36d02a24929703558 100644 --- a/server/app/core/utils/test_helpers.py +++ b/server/app/core/utils/test_helpers.py @@ -3,7 +3,7 @@ import json import app.core.controller as dbc import pytest from app.core import db -from app.core.models import City, MediaType, QuestionType, Role, Style +from app.core.models import City, MediaType, QuestionType, Role, Style, User def add_default_values(): @@ -35,7 +35,15 @@ def add_default_values(): db.session.commit() # Add user with role and city - dbc.add.user("test@test.se", "password", "Admin", "Linköping") + dbc.add.default(User("test@test.se", "password", 1, 1)) + + +def get_body(response): + try: + body = json.loads(response.data.decode()) + except: + body = None + return body def post(client, url, data, headers=None): @@ -43,13 +51,13 @@ def post(client, url, data, headers=None): headers = {} headers["Content-Type"] = "application/json" response = client.post(url, data=json.dumps(data), headers=headers) - body = json.loads(response.data.decode()) + body = get_body(response) 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()) + body = get_body(response) return response, body @@ -59,13 +67,13 @@ def put(client, url, data, headers=None): headers["Content-Type"] = "application/json" response = client.put(url, data=json.dumps(data), headers=headers) - body = json.loads(response.data.decode()) + body = get_body(response) 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()) + body = get_body(response) return response, body diff --git a/server/populate.py b/server/populate.py index 452769918189a1b6c4937578a0714ad777b281b6..71a5fc2df530cde0c5fdd7b0053ab267d8770284 100644 --- a/server/populate.py +++ b/server/populate.py @@ -1,6 +1,6 @@ import app.core.controller as dbc from app import create_app, db -from app.core.models import City, MediaType, QuestionType, Role, Style +from app.core.models import City, MediaType, QuestionType, Role, Style, User user = {"email": "test@test.se", "password": "password", "role": "Admin", "city": "Linköping"} media_types = ["Image", "Video"] @@ -38,7 +38,7 @@ def _add_items(): db.session.commit() # Add user with role and city - dbc.add.user("test@test.se", "password", "Admin", "Linköping") + dbc.add.default(User("test@test.se", "password", 1, 1)) db.session.flush() diff --git a/server/tests/test_app.py b/server/tests/test_app.py index 6d6e16dcbd5a5c8128e78a832e94cb6321120497..bba8b481e6a7a81e7c15979b93912f3370e6047e 100644 --- a/server/tests/test_app.py +++ b/server/tests/test_app.py @@ -1,6 +1,6 @@ import json -from app.core.utils.test_helpers import add_default_values, get, post, put +from app.core.utils.test_helpers import add_default_values, delete, get, post, put from tests import app, client, db @@ -24,6 +24,24 @@ def test_competition(client): assert response.status_code == 200 assert body["name"] == "c1" + response, body = post(client, "/api/competitions/1/slides", {}, headers=headers) + assert response.status_code == 200 + + response, body = get(client, "/api/competitions/1/slides", headers=headers) + assert response.status_code == 200 + assert len(body) == 2 + + response, body = post(client, "/api/competitions/1/teams", {"name": "t1"}, headers=headers) + assert response.status_code == 200 + + response, body = get(client, "/api/competitions/1/teams", headers=headers) + assert response.status_code == 200 + assert len(body) == 1 + assert body[0]["name"] == "t1" + + response, body = delete(client, "/api/competitions/1", {}, headers=headers) + assert response.status_code == 200 + def test_app(client): add_default_values() @@ -34,9 +52,8 @@ def test_app(client): headers = {"Authorization": "Bearer " + body["access_token"]} # Create user - register_data = {"email": "test1@test.se", "password": "abc123", "role": "Admin", "city": "Linköping"} + register_data = {"email": "test1@test.se", "password": "abc123", "role_id": 2, "city_id": 1} response, body = post(client, "/api/auth/signup", register_data, headers) - assert response.status_code == 200 assert body["id"] == 2 assert "password" not in body @@ -59,6 +76,11 @@ def test_app(client): assert response.status_code == 200 assert body["email"] == "test1@test.se" + # Edit current user name response, body = put(client, "/api/users", {"name": "carl carlsson"}, headers=headers) assert response.status_code == 200 assert body["name"] == "Carl Carlsson" + + # Delete created user + response, body = delete(client, "/api/auth/delete/1", {}, headers=headers) + assert response.status_code == 200