diff --git a/server/app/apis/__init__.py b/server/app/apis/__init__.py index 109252e64bf7bb7f52af20a6ba4236d7bdf15ba0..31d1be3df8d545c7c8e7ac658e545e6dc970dfd8 100644 --- a/server/app/apis/__init__.py +++ b/server/app/apis/__init__.py @@ -139,7 +139,9 @@ flask_api = Api() def init_api(): from .auth import blp as auth_blp + from .misc import blp as misc_blp from .users import blp as user_blp flask_api.register_blueprint(user_blp) flask_api.register_blueprint(auth_blp) + flask_api.register_blueprint(misc_blp) diff --git a/server/app/apis/auth.py b/server/app/apis/auth.py index dc5af792ec96f3e26bbd2f938ee2e8e0cc393fac..cb40f58349886183263253b25955509b15015008 100644 --- a/server/app/apis/auth.py +++ b/server/app/apis/auth.py @@ -45,13 +45,13 @@ if has_app_context(): def get_user_claims(item_user): - """ Gets user details for jwt-token. """ + """ Gets user details for jwt. """ return {"role": item_user.role.name, "city_id": item_user.city_id} def get_code_claims(item_code): - """ Gets code details for jwt-token. """ + """ Gets code details for jwt. """ return { "view": item_code.view_type.name, @@ -111,7 +111,7 @@ class AuthLogin(MethodView): @blp.arguments(UserLoginArgsSchema) @blp.response(http_codes.OK, UserLoginResponseSchema) def post(self, args): - """ Logs in the specified user and creates a jwt-token. """ + """ Logs in the specified user and creates a jwt. """ email = args.get("email") password = args.get("password") diff --git a/server/app/apis/misc.py b/server/app/apis/misc.py index 552a0ad0c4848c33a08bcb63ccc3e870acd6af48..21054b200c11cd5c3a37b514550513de04cfd7e5 100644 --- a/server/app/apis/misc.py +++ b/server/app/apis/misc.py @@ -4,100 +4,114 @@ Default route: /api/misc """ import app.database.controller as dbc -from app.apis import list_response, protect_route -from app.core import http_codes -from app.core.dto import MiscDTO +import marshmallow as ma +from app.apis import protect_route +from app.core.schemas import ( + BaseSchema, + CitySchema, + ComponentTypeSchema, + MediaTypeSchema, + QuestionTypeSchema, + RoleSchema, + ViewTypeSchema, +) +from app.database import models +from app.database.controller.add import competition from app.database.models import City, Competition, ComponentType, MediaType, QuestionType, Role, User, ViewType -from flask_restx import Resource, reqparse +from flask.views import MethodView +from flask_smorest import Blueprint, response +from marshmallow_sqlalchemy import auto_field -api = MiscDTO.api +from . import http_codes -question_type_schema = MiscDTO.question_type_schema -media_type_schema = MiscDTO.media_type_schema -component_type_schema = MiscDTO.component_type_schema -view_type_schema = MiscDTO.view_type_schema +blp = Blueprint("misc", "misc", url_prefix="/api/misc", description="Miscellaneous operations") -role_schema = MiscDTO.role_schema -city_schema = MiscDTO.city_schema +class TypesResponseSchema(BaseSchema): + media_types = ma.fields.Nested(MediaTypeSchema, many=True) + component_types = ma.fields.Nested(ComponentTypeSchema, many=True) + question_types = ma.fields.Nested(QuestionTypeSchema, many=True) + view_types = ma.fields.Nested(ViewTypeSchema, many=True) -name_parser = reqparse.RequestParser() -name_parser.add_argument("name", type=str, required=True, location="json") - -@api.route("/types") -class TypesList(Resource): +@blp.route("/types") +class Types(MethodView): + @blp.response(http_codes.OK, TypesResponseSchema) def get(self): - """ Gets a list of all types. """ - - result = {} - result["media_types"] = media_type_schema.dump(dbc.get.all(MediaType)) - result["component_types"] = component_type_schema.dump(dbc.get.all(ComponentType)) - result["question_types"] = question_type_schema.dump(dbc.get.all(QuestionType)) - result["view_types"] = view_type_schema.dump(dbc.get.all(ViewType)) - return result + """ Gets a list of all types """ + return dict( + media_types=dbc.get.all(MediaType), + component_types=dbc.get.all(ComponentType), + question_types=dbc.get.all(QuestionType), + view_types=dbc.get.all(ViewType), + ) -@api.route("/roles") -class RoleList(Resource): +@blp.route("/roles") +class RoleList(MethodView): @protect_route(allowed_roles=["*"]) + @blp.response(http_codes.OK, RoleSchema(many=True)) def get(self): """ Gets a list of all roles. """ + return dbc.get.all(Role) + - items = dbc.get.all(Role) - return list_response(role_schema.dump(items)) +class CityAddArgsSchema(BaseSchema): + class Meta(BaseSchema.Meta): + model = models.City + name = auto_field() -@api.route("/cities") -class CitiesList(Resource): + +@blp.route("/cities") +class CitiesList(MethodView): @protect_route(allowed_roles=["*"]) + @blp.response(http_codes.OK, CitySchema(many=True)) def get(self): """ Gets a list of all cities. """ - - items = dbc.get.all(City) - return list_response(city_schema.dump(items)) + return dbc.get.all(City) @protect_route(allowed_roles=["Admin"]) - def post(self): + @blp.arguments(CitySchema) + @blp.response(http_codes.OK, CitySchema(many=True)) + def post(self, args): """ Posts the specified city. """ + dbc.add.city(**args) + return dbc.get.all(City) - args = name_parser.parse_args(strict=True) - dbc.add.city(args["name"]) - items = dbc.get.all(City) - return list_response(city_schema.dump(items)) - -@api.route("/cities/<ID>") -@api.param("ID") -class Cities(Resource): +@blp.route("/cities/<city_id>") +class Cities(MethodView): @protect_route(allowed_roles=["Admin"]) - def put(self, ID): + @blp.arguments(CitySchema) + @blp.response(http_codes.OK, CitySchema(many=True)) + @blp.alt_response(http_codes.NOT_FOUND, None, description="City not found") + @blp.alt_response(http_codes.CONFLICT, None, description="The city can't be updated with the provided values") + def put(self, args, city_id): """ Edits the specified city with the provided arguments. """ - - item = dbc.get.one(City, ID) - args = name_parser.parse_args(strict=True) - item.name = args["name"] - dbc.utils.commit_and_refresh(item) - items = dbc.get.all(City) - return list_response(city_schema.dump(items)) + dbc.edit.default(dbc.get.one(City, city_id), **args) + return dbc.get.all(City) @protect_route(allowed_roles=["Admin"]) - def delete(self, ID): + @blp.response(http_codes.OK, CitySchema(many=True)) + @blp.alt_response(http_codes.NOT_FOUND, None, description="City not found") + @blp.alt_response(http_codes.CONFLICT, None, description="The city can't be updated with the provided values") + def delete(self, city_id): """ Deletes the specified city. """ + dbc.delete.default(dbc.get.one(City, city_id)) + return dbc.get.all(City) - item = dbc.get.one(City, ID) - dbc.delete.default(item) - items = dbc.get.all(City) - return list_response(city_schema.dump(items)) +class StatisticsResponseSchema(BaseSchema): + users = ma.fields.Int() + competitions = ma.fields.Int() + regions = ma.fields.Int() -@api.route("/statistics") -class Statistics(Resource): + +@blp.route("/statistics") +class Statistics(MethodView): @protect_route(allowed_roles=["*"]) + @blp.response(http_codes.OK, StatisticsResponseSchema) def get(self): """ Gets statistics. """ - - user_count = User.query.count() - competition_count = Competition.query.count() - region_count = City.query.count() - return {"users": user_count, "competitions": competition_count, "regions": region_count}, http_codes.OK + return {"users": User.query.count(), "competitions": Competition.query.count(), "regions": City.query.count()} diff --git a/server/app/database/__init__.py b/server/app/database/__init__.py index 828c4f962e73d6ceffa4867e93a66aeb0cb3bc9e..594894480efea039c5cdf0c544e964ad4d554b3f 100644 --- a/server/app/database/__init__.py +++ b/server/app/database/__init__.py @@ -3,6 +3,7 @@ The database submodule contaisn all functionality that has to do with the database. It can add, get, delete, edit, search and copy items. """ +from app.apis import http_codes from flask_smorest import abort # from flask_restx import abort @@ -27,7 +28,7 @@ class ExtendedQuery(BaseQuery): Extensions to a regular query which makes using the database more convenient. """ - def first_api(self, required=True, error_message=None, error_code=404): + def first_api(self, required=True, error_message=None, error_code=http_codes.NOT_FOUND): """ Extensions of the first() functions otherwise used on queries. Abort if no item was found and it was required. diff --git a/server/app/database/controller/delete.py b/server/app/database/controller/delete.py index 9e4f8b3d7d08abeb3f0fa641aea6f4378d3700b5..bd2ff7469e7856f2a874cb1eeeaa47e7f201e110 100644 --- a/server/app/database/controller/delete.py +++ b/server/app/database/controller/delete.py @@ -3,8 +3,10 @@ This file contains functionality to delete data to the database. """ import app.database.controller as dbc +from app.apis import http_codes from app.core import db from app.database.models import QuestionAlternativeAnswer, QuestionScore, Whitelist +from flask_smorest import abort # from flask_restx import abort from sqlalchemy.exc import IntegrityError @@ -18,15 +20,10 @@ def default(item): db.session.commit() except IntegrityError: db.session.rollback() - pass - # abort(codes.CONFLICT, f"Item of type {type(item)} cannot be deleted due to an Integrity Constraint") + abort(http_codes.CONFLICT, f"Kunde inte ta bort objektet") except: db.session.rollback() - pass - # abort( - # codes.INTERNAL_SERVER_ERROR, - # f"Item of type {type(item)} could not be deleted", - # ) + abort(http_codes.INTERNAL_SERVER_ERROR, f"Kunde inte ta bort objektet") def whitelist_to_blacklist(filters): diff --git a/server/app/database/controller/edit.py b/server/app/database/controller/edit.py index 3cd544628616225ad34e53d765946698137c84fa..209c753b5c84c99dff15a0131db227a1486a65b7 100644 --- a/server/app/database/controller/edit.py +++ b/server/app/database/controller/edit.py @@ -2,7 +2,9 @@ This file contains functionality to get data from the database. """ +from app.apis import http_codes from app.core import db +from flask_smorest import abort # from flask_restx.errors import abort from sqlalchemy import exc @@ -32,8 +34,7 @@ def default(item, **kwargs): try: db.session.commit() except exc.IntegrityError: - pass - # abort(codes.CONFLICT, f"Item of type {type(item)} cannot be edited due to an Integrity Constraint") + abort(http_codes.CONFLICT, f"Kunde inte utföra ändringen") db.session.refresh(item) return item