From 77526dde011e83ecfc2d21b9fef0096f9f2bb853 Mon Sep 17 00:00:00 2001
From: robban64 <carl@schonfelder.se>
Date: Mon, 5 Apr 2021 21:19:43 +0200
Subject: [PATCH 1/7] add: marshmallow for users

---
 server/app/__init__.py            |   3 +-
 server/app/apis/__init__.py       |  24 ++++-----
 server/app/apis/users.py          |  31 ++++++-----
 server/app/core/__init__.py       |   2 +
 server/app/core/controller/get.py |   8 +--
 server/app/core/dto.py            |  22 +++-----
 server/app/core/rich_schemas.py   |  38 ++++++++++++++
 server/app/core/schemas.py        |  82 ++++++++++++++++++++++++++++++
 server/requirements.txt           | Bin 2106 -> 2170 bytes
 9 files changed, 162 insertions(+), 48 deletions(-)
 create mode 100644 server/app/core/rich_schemas.py
 create mode 100644 server/app/core/schemas.py

diff --git a/server/app/__init__.py b/server/app/__init__.py
index 5365f636..d1bbd3af 100644
--- a/server/app/__init__.py
+++ b/server/app/__init__.py
@@ -1,7 +1,7 @@
 from flask import Flask, redirect, request
 
 import app.core.models as models
-from app.core import bcrypt, db, jwt
+from app.core import bcrypt, db, jwt, ma
 
 
 def create_app(config_name="configmodule.DevelopmentConfig"):
@@ -13,6 +13,7 @@ def create_app(config_name="configmodule.DevelopmentConfig"):
         bcrypt.init_app(app)
         jwt.init_app(app)
         db.init_app(app)
+        ma.init_app(app)
 
         from app.apis import flask_api
 
diff --git a/server/app/apis/__init__.py b/server/app/apis/__init__.py
index c9ff2f51..e53acab1 100644
--- a/server/app/apis/__init__.py
+++ b/server/app/apis/__init__.py
@@ -1,7 +1,9 @@
 from functools import wraps
 
+import app.core.http_codes as codes
 from flask_jwt_extended import verify_jwt_in_request
 from flask_jwt_extended.utils import get_jwt_claims
+from flask_restx.errors import abort
 
 
 def admin_required():
@@ -13,27 +15,23 @@ def admin_required():
             if claims["role"] == "Admin":
                 return fn(*args, **kwargs)
             else:
-                return {"message:": "Admins only"}, 403
+                return {"message:": "Admins only"}, codes.FORBIDDEN
 
         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 list_response(items, total, code=200):
+    if type(items) is not list:
+        abort(codes.INTERNAL_SERVER_ERROR)
+    return {"items": items, "count": len(items), "total_count": total}, code
 
 
-def object_response(items, code=200):
-    if type(items) is not list:
-        items = [items]
-    return {"result": items}, code
+def item_response(item, code=200):
+    if isinstance(item, list):
+        abort(codes.INTERNAL_SERVER_ERROR)
+    return {"items": [item], "count": 1, "total_count": 1}, code
 
 
 from flask_restx import Api
diff --git a/server/app/apis/users.py b/server/app/apis/users.py
index 43e304c1..4071474f 100644
--- a/server/app/apis/users.py
+++ b/server/app/apis/users.py
@@ -1,6 +1,6 @@
 import app.core.controller as dbc
 import app.core.http_codes as codes
-from app.apis import admin_required
+from app.apis import admin_required, item_response, list_response
 from app.core.dto import UserDTO
 from app.core.models import User
 from app.core.parsers import user_parser, user_search_parser
@@ -8,8 +8,8 @@ from flask_jwt_extended import get_jwt_identity, jwt_required
 from flask_restx import Namespace, Resource
 
 api = UserDTO.api
-user_model = UserDTO.model
-user_list_model = UserDTO.user_list_model
+schema = UserDTO.schema
+list_schema = UserDTO.list_schema
 
 
 def edit_user(item_user, args):
@@ -28,38 +28,37 @@ def edit_user(item_user, args):
 @api.route("/")
 class UsersList(Resource):
     @jwt_required
-    @api.marshal_list_with(user_model)
     def get(self):
-        return User.query.filter(User.id == get_jwt_identity()).first()
+        item = User.query.filter(User.id == get_jwt_identity()).first()
+        return item_response(UserDTO.schema.dump(item))
 
     @jwt_required
-    @api.marshal_with(user_model)
     def put(self):
         args = user_parser.parse_args(strict=True)
-        item_user = User.query.filter(User.id == get_jwt_identity()).first()
-        return edit_user(item_user, args)
+        item = User.query.filter(User.id == get_jwt_identity()).first()
+        item = edit_user(item, args)
+        return item_response(UserDTO.schema.dump(item))
 
 
 @api.route("/<ID>")
 @api.param("ID")
 class Users(Resource):
     @jwt_required
-    @api.marshal_with(user_model)
     def get(self, ID):
-        return User.query.filter(User.id == ID).first()
+        item = User.query.filter(User.id == ID).first()
+        return item_response(UserDTO.schema.dump(item))
 
     @jwt_required
-    @api.marshal_with(user_model)
     def put(self, ID):
         args = user_parser.parse_args(strict=True)
-        item_user = User.query.filter(User.id == ID).first()
-        return edit_user(item_user, args)
+        item = User.query.filter(User.id == ID).first()
+        item = edit_user(item, args)
+        return item_response(UserDTO.schema.dump(item))
 
 
 @api.route("/search")
 class UserSearch(Resource):
     @jwt_required
-    @api.marshal_list_with(user_list_model)
     def get(self):
         args = user_search_parser.parse_args(strict=True)
         name = args.get("name")
@@ -69,5 +68,5 @@ class UserSearch(Resource):
         page = args.get("page", 0)
         page_size = args.get("page_size", 15)
 
-        result, total = dbc.get.search_user(email, name, city_id, role_id, page, page_size)
-        return {"users": result, "count": len(result), "total": total}
+        items, total = dbc.get.search_user(email, name, city_id, role_id, page, page_size)
+        return list_response(list_schema.dump(items), total)
diff --git a/server/app/core/__init__.py b/server/app/core/__init__.py
index 1f6cad48..e9d132cb 100644
--- a/server/app/core/__init__.py
+++ b/server/app/core/__init__.py
@@ -1,6 +1,7 @@
 import sqlalchemy as sa
 from flask_bcrypt import Bcrypt
 from flask_jwt_extended.jwt_manager import JWTManager
+from flask_marshmallow import Marshmallow
 from flask_sqlalchemy import SQLAlchemy
 from flask_sqlalchemy.model import Model
 from sqlalchemy.sql import func
@@ -15,3 +16,4 @@ class Base(Model):
 db = SQLAlchemy(model_class=Base)
 bcrypt = Bcrypt()
 jwt = JWTManager()
+ma = Marshmallow()
diff --git a/server/app/core/controller/get.py b/server/app/core/controller/get.py
index ef86ef6b..d849c772 100644
--- a/server/app/core/controller/get.py
+++ b/server/app/core/controller/get.py
@@ -26,9 +26,9 @@ def search_user(email=None, name=None, city_id=None, role_id=None, page=0, page_
 
     total = query.count()
     query = query.limit(page_size).offset(page * page_size)
-    result = query.all()
+    items = query.all()
 
-    return result, total
+    return items, total
 
 
 def search_competitions(name=None, year=None, city_id=None, style_id=None, page=0, page_size=15):
@@ -44,6 +44,6 @@ def search_competitions(name=None, year=None, city_id=None, style_id=None, page=
 
     total = query.count()
     query = query.limit(page_size).offset(page * page_size)
-    result = query.all()
+    items = query.all()
 
-    return result, total
+    return items, total
diff --git a/server/app/core/dto.py b/server/app/core/dto.py
index 6c47877c..7df2d767 100644
--- a/server/app/core/dto.py
+++ b/server/app/core/dto.py
@@ -1,4 +1,10 @@
+import app.core.rich_schemas as rich_schemas
+import app.core.schemas as schemas
+import marshmallow as ma
 from flask_restx import Namespace, fields
+from sqlalchemy.sql.expression import false
+
+res_fields = {"*": fields.Wildcard(fields.String)}
 
 
 class AuthDTO:
@@ -8,20 +14,8 @@ class AuthDTO:
 
 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(),
-        },
-    )
-    user_list_model = api.model(
-        "UserList",
-        {"users": fields.List(fields.Nested(model)), "count": fields.Integer(), "total": fields.Integer()},
-    )
+    schema = rich_schemas.UserSchemaRich(many=False)
+    list_schema = rich_schemas.UserSchemaRich(many=True)
 
 
 class CompetitionDTO:
diff --git a/server/app/core/rich_schemas.py b/server/app/core/rich_schemas.py
new file mode 100644
index 00000000..af52b293
--- /dev/null
+++ b/server/app/core/rich_schemas.py
@@ -0,0 +1,38 @@
+import app.core.models as models
+import app.core.schemas as schemas
+from app.core import ma
+from marshmallow import fields as fields2
+from marshmallow_sqlalchemy import fields
+
+
+class RichSchema(ma.SQLAlchemySchema):
+    class Meta:
+        strict = True
+        load_instance = True
+        include_relationships = True
+
+
+class UserSchemaRich(RichSchema):
+    class Meta(RichSchema.Meta):
+        model = models.User
+
+    id = ma.auto_field()
+    name = ma.auto_field()
+    email = ma.auto_field()
+    role = fields.Nested(schemas.RoleSchema, many=False)
+    city = fields.Nested(schemas.CitySchema, many=False)
+
+
+class CompetitionSchemaRich(RichSchema):
+    class Meta(RichSchema.Meta):
+        model = models.Competition
+
+    id = ma.auto_field()
+    name = ma.auto_field()
+    year = ma.auto_field()
+    style = fields.Nested(schemas.RoleSchema, many=False)
+    city = fields.Nested(schemas.CitySchema, many=False)
+
+
+class UserListSchema(ma.Schema):
+    users = fields2.Nested(UserSchemaRich, many=False)
diff --git a/server/app/core/schemas.py b/server/app/core/schemas.py
new file mode 100644
index 00000000..9962479f
--- /dev/null
+++ b/server/app/core/schemas.py
@@ -0,0 +1,82 @@
+import app.core.models as models
+from app.core import ma
+from marshmallow_sqlalchemy import fields
+
+
+class BaseSchema(ma.SQLAlchemySchema):
+    class Meta:
+        strict = True
+        load_instance = False
+        include_relationships = False
+
+
+class QuestionTypeSchema(BaseSchema):
+    class Meta(BaseSchema.Meta):
+        model = models.QuestionType
+
+    id = ma.auto_field()
+    name = ma.auto_field()
+
+
+class MediaTypeSchema(BaseSchema):
+    class Meta(BaseSchema.Meta):
+        model = models.MediaType
+
+    id = ma.auto_field()
+    name = ma.auto_field()
+
+
+class RoleSchema(BaseSchema):
+    class Meta(BaseSchema.Meta):
+        model = models.Role
+
+    id = ma.auto_field()
+    name = ma.auto_field()
+
+
+class CitySchema(BaseSchema):
+    class Meta(BaseSchema.Meta):
+        model = models.City
+
+    id = ma.auto_field()
+    name = ma.auto_field()
+
+
+class MediaSchema(BaseSchema):
+    class Meta(BaseSchema.Meta):
+        model = models.Media
+
+    id = ma.auto_field()
+    filename = ma.auto_field()
+    type_id = ma.auto_field()
+    upload_by_id = ma.auto_field()
+
+
+class StyleSchema(BaseSchema):
+    class Meta(BaseSchema.Meta):
+        model = models.Style
+
+    id = ma.auto_field()
+    name = ma.auto_field()
+    css = ma.auto_field()
+    bg_image = fields.Nested(MediaSchema, many=False)
+
+
+class SlideSchema(BaseSchema):
+    class Meta(BaseSchema.Meta):
+        model = models.Slide
+
+    id = ma.auto_field()
+    order = ma.auto_field()
+    title = ma.auto_field()
+    timer = ma.auto_field()
+    competition_id = ma.auto_field()
+
+
+class TeamSchema(BaseSchema):
+    class Meta(BaseSchema.Meta):
+        model = models.Team
+
+    id = ma.auto_field()
+    name = ma.auto_field()
+    competition_id = ma.auto_field()
diff --git a/server/requirements.txt b/server/requirements.txt
index 463ef4fe07f14b03b7771a25842170c8903f73f7..172f152510c3ccd6245f19e5cb53ac702a6b4370 100644
GIT binary patch
delta 54
zcmdlb@JnFBB9_S&EF!A948;tE3^@#m3^@$R3>gfm47m)I47Lom3<eB(3`PtlAae6;
Hmif#8ge44R

delta 12
Ucmew*uuEXWB9_gMSY|K-049_LUjP6A

-- 
GitLab


From de67012656b997ac4e3b720af71d71e944897035 Mon Sep 17 00:00:00 2001
From: robban64 <carl@schonfelder.se>
Date: Mon, 5 Apr 2021 21:45:51 +0200
Subject: [PATCH 2/7] add: marshmallow for competition, teams and slides

---
 server/app/apis/__init__.py            |  4 +++-
 server/app/apis/auth.py                | 11 ++++++---
 server/app/apis/competitions.py        | 33 ++++++++++++--------------
 server/app/apis/slides.py              | 25 ++++++++++---------
 server/app/apis/teams.py               | 16 ++++++-------
 server/app/apis/users.py               |  8 +++----
 server/app/core/controller/__init__.py |  4 ++++
 server/app/core/controller/add.py      |  6 ++---
 server/app/core/dto.py                 | 31 +++++-------------------
 server/app/core/rich_schemas.py        |  1 +
 server/tests/test_app.py               | 14 +++++------
 11 files changed, 70 insertions(+), 83 deletions(-)

diff --git a/server/app/apis/__init__.py b/server/app/apis/__init__.py
index e53acab1..19f8a8d9 100644
--- a/server/app/apis/__init__.py
+++ b/server/app/apis/__init__.py
@@ -22,9 +22,11 @@ def admin_required():
     return wrapper
 
 
-def list_response(items, total, code=200):
+def list_response(items, total=None, code=200):
     if type(items) is not list:
         abort(codes.INTERNAL_SERVER_ERROR)
+    if not total:
+        total = len(items)
     return {"items": items, "count": len(items), "total_count": total}, code
 
 
diff --git a/server/app/apis/auth.py b/server/app/apis/auth.py
index 8fe25b6b..061b00d9 100644
--- a/server/app/apis/auth.py
+++ b/server/app/apis/auth.py
@@ -4,9 +4,14 @@ 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 (create_access_token, create_refresh_token,
-                                get_jwt_identity, get_raw_jwt,
-                                jwt_refresh_token_required, jwt_required)
+from flask_jwt_extended import (
+    create_access_token,
+    create_refresh_token,
+    get_jwt_identity,
+    get_raw_jwt,
+    jwt_refresh_token_required,
+    jwt_required,
+)
 from flask_restx import Namespace, Resource, cors
 
 api = AuthDTO.api
diff --git a/server/app/apis/competitions.py b/server/app/apis/competitions.py
index ac7384df..1353988e 100644
--- a/server/app/apis/competitions.py
+++ b/server/app/apis/competitions.py
@@ -1,15 +1,14 @@
 import app.core.controller as dbc
-import app.core.http_codes as codes
-from app.apis import admin_required
+from app.apis import admin_required, item_response, list_response
 from app.core.dto import CompetitionDTO
-from app.core.models import Competition, Slide, Team
+from app.core.models import Competition
 from app.core.parsers import competition_parser, competition_search_parser
-from flask_jwt_extended import get_jwt_identity, jwt_required
-from flask_restx import Resource, reqparse
+from flask_jwt_extended import jwt_required
+from flask_restx import Resource
 
 api = CompetitionDTO.api
-competition_model = CompetitionDTO.model
-competition_list_model = CompetitionDTO.user_list_model
+schema = CompetitionDTO.schema
+list_schema = CompetitionDTO.list_schema
 
 
 def get_comp(CID):
@@ -19,7 +18,6 @@ def get_comp(CID):
 @api.route("/")
 class CompetitionsList(Resource):
     @jwt_required
-    @api.marshal_with(competition_model)
     def post(self):
         args = competition_parser.parse_args(strict=True)
 
@@ -29,25 +27,24 @@ class CompetitionsList(Resource):
         style_id = args.get("style_id")
 
         # Add competition
-        item_competition = dbc.add.default(Competition(name, year, style_id, city_id))
+        item = dbc.add.default(Competition(name, year, style_id, city_id))
 
         # Add default slide
-        item_slide = dbc.add.slide(item_competition.id)
+        item_slide = dbc.add.slide(item)
 
-        return item_competition
+        dbc.refresh(item)
+        return item_response(schema.dump(item))
 
 
 @api.route("/<ID>")
 @api.param("ID")
 class Competitions(Resource):
     @jwt_required
-    @api.marshal_with(competition_model)
     def get(self, ID):
         item = get_comp(ID)
-        return item
+        return item_response(schema.dump(item))
 
     @jwt_required
-    @api.marshal_with(competition_model)
     def put(self, ID):
         args = competition_parser.parse_args(strict=True)
 
@@ -56,7 +53,8 @@ class Competitions(Resource):
         year = args.get("year")
         city_id = args.get("city_id")
         style_id = args.get("style_id")
-        return dbc.edit.competition(item, name, year, city_id, style_id)
+        item = dbc.edit.competition(item, name, year, city_id, style_id)
+        return item_response(schema.dump(item))
 
     @jwt_required
     def delete(self, ID):
@@ -68,7 +66,6 @@ class Competitions(Resource):
 @api.route("/search")
 class CompetitionSearch(Resource):
     @jwt_required
-    @api.marshal_with(competition_list_model)
     def get(self):
         args = competition_search_parser.parse_args(strict=True)
         name = args.get("name")
@@ -77,6 +74,6 @@ class CompetitionSearch(Resource):
         style_id = args.get("style_id")
         page = args.get("page", 0)
         page_size = args.get("page_size", 15)
-        result, total = dbc.get.search_competitions(name, year, city_id, style_id, page, page_size)
+        items, total = dbc.get.search_competitions(name, year, city_id, style_id, page, page_size)
 
-        return {"competitions": result, "count": len(result), "total": total}
+        return list_response(list_schema.dump(items), total)
diff --git a/server/app/apis/slides.py b/server/app/apis/slides.py
index 6399ebc7..6a2b1714 100644
--- a/server/app/apis/slides.py
+++ b/server/app/apis/slides.py
@@ -1,6 +1,7 @@
 import app.core.controller as dbc
 import app.core.http_codes as codes
-from app.apis import admin_required
+from app.apis import admin_required, item_response, list_response
+from app.core import schemas
 from app.core.dto import SlideDTO
 from app.core.models import Competition, Slide
 from app.core.parsers import slide_parser
@@ -8,7 +9,8 @@ from flask_jwt_extended import get_jwt_identity, jwt_required
 from flask_restx import Namespace, Resource, reqparse
 
 api = SlideDTO.api
-model = SlideDTO.model
+schema = SlideDTO.schema
+list_schema = SlideDTO.list_schema
 
 
 def get_comp(CID):
@@ -19,38 +21,36 @@ def get_comp(CID):
 @api.param("CID")
 class SlidesList(Resource):
     @jwt_required
-    @api.marshal_with(model)
     def get(self, CID):
         item_comp = get_comp(CID)
-        return item_comp.slides
+        return list_response(list_schema.dump(item_comp.slides))
 
     @jwt_required
-    @api.marshal_with(model)
     def post(self, CID):
-        dbc.add.slide(CID)
         item_comp = get_comp(CID)
-        return item_comp.slides
+        dbc.add.slide(item_comp)
+        dbc.refresh(item_comp)
+        return list_response(list_schema.dump(item_comp.slides))
 
 
 @api.route("/<SID>")
 @api.param("CID,SID")
 class Slides(Resource):
     @jwt_required
-    @api.marshal_with(model)
     def get(self, CID, SID):
         item_slide = dbc.get.slide(CID, SID)
-        return item_slide
+        return item_response(schema.dump(item_slide))
 
     @jwt_required
-    @api.marshal_with(model)
     def put(self, CID, SID):
         args = slide_parser.parse_args(strict=True)
         title = args.get("title")
         timer = args.get("timer")
 
         item_slide = dbc.get.slide(CID, SID)
+        item_slide = dbc.edit.slide(item_slide, title, timer)
 
-        return dbc.edit.slide(item_slide, title, timer)
+        return item_response(schema.dump(item_slide))
 
     @jwt_required
     def delete(self, CID, SID):
@@ -63,7 +63,6 @@ class Slides(Resource):
 @api.param("CID,SID")
 class SlidesOrder(Resource):
     @jwt_required
-    @api.marshal_with(model)
     def put(self, CID, SID):
         args = slide_parser.parse_args(strict=True)
         order = args.get("order")
@@ -86,4 +85,4 @@ class SlidesOrder(Resource):
         # switch place between them
         item_slide = dbc.edit.switch_order(item_slide, item_slide_order)
 
-        return item_slide
+        return item_response(schema.dump(item_slide))
diff --git a/server/app/apis/teams.py b/server/app/apis/teams.py
index 02c730bb..3f183bf5 100644
--- a/server/app/apis/teams.py
+++ b/server/app/apis/teams.py
@@ -1,13 +1,14 @@
 import app.core.controller as dbc
 import app.core.http_codes as codes
-from app.apis import admin_required
+from app.apis import admin_required, item_response, list_response
 from app.core.dto import TeamDTO
 from app.core.models import Competition, Team
 from flask_jwt_extended import get_jwt_identity, jwt_required
 from flask_restx import Namespace, Resource, reqparse
 
 api = TeamDTO.api
-model = TeamDTO.model
+schema = TeamDTO.schema
+list_schema = TeamDTO.list_schema
 
 
 def get_comp(CID):
@@ -18,13 +19,11 @@ def get_comp(CID):
 @api.param("CID")
 class TeamsList(Resource):
     @jwt_required
-    @api.marshal_with(model)
     def get(self, CID):
         item_comp = get_comp(CID)
-        return item_comp.teams
+        return list_response(list_schema.dump(item_comp.teams))
 
     @jwt_required
-    @api.marshal_with(model)
     def post(self, CID):
         parser = reqparse.RequestParser()
         parser.add_argument("name", type=str, location="json")
@@ -32,17 +31,16 @@ class TeamsList(Resource):
 
         dbc.add.default(Team(args["name"], CID))
         item_comp = get_comp(CID)
-        return item_comp.teams
+        return list_response(list_schema.dump(item_comp.teams))
 
 
 @api.route("/<TID>")
 @api.param("CID,TID")
 class Teams(Resource):
     @jwt_required
-    @api.marshal_with(model)
     def get(self, CID, TID):
-        item_team = dbc.get.team(CID, TID)
-        return item_team
+        item = dbc.get.team(CID, TID)
+        return item_response(schema.dump(item))
 
     @jwt_required
     def delete(self, CID, TID):
diff --git a/server/app/apis/users.py b/server/app/apis/users.py
index 4071474f..d144e7be 100644
--- a/server/app/apis/users.py
+++ b/server/app/apis/users.py
@@ -30,14 +30,14 @@ class UsersList(Resource):
     @jwt_required
     def get(self):
         item = User.query.filter(User.id == get_jwt_identity()).first()
-        return item_response(UserDTO.schema.dump(item))
+        return item_response(schema.dump(item))
 
     @jwt_required
     def put(self):
         args = user_parser.parse_args(strict=True)
         item = User.query.filter(User.id == get_jwt_identity()).first()
         item = edit_user(item, args)
-        return item_response(UserDTO.schema.dump(item))
+        return item_response(schema.dump(item))
 
 
 @api.route("/<ID>")
@@ -46,14 +46,14 @@ class Users(Resource):
     @jwt_required
     def get(self, ID):
         item = User.query.filter(User.id == ID).first()
-        return item_response(UserDTO.schema.dump(item))
+        return item_response(schema.dump(item))
 
     @jwt_required
     def put(self, ID):
         args = user_parser.parse_args(strict=True)
         item = User.query.filter(User.id == ID).first()
         item = edit_user(item, args)
-        return item_response(UserDTO.schema.dump(item))
+        return item_response(schema.dump(item))
 
 
 @api.route("/search")
diff --git a/server/app/core/controller/__init__.py b/server/app/core/controller/__init__.py
index ec746b74..a8f0d8d2 100644
--- a/server/app/core/controller/__init__.py
+++ b/server/app/core/controller/__init__.py
@@ -1,3 +1,7 @@
 # import add, get
 from app.core import db
 from app.core.controller import add, delete, edit, get
+
+
+def refresh(item):
+    db.session.refresh(item)
diff --git a/server/app/core/controller/add.py b/server/app/core/controller/add.py
index 958cf766..14c5eecf 100644
--- a/server/app/core/controller/add.py
+++ b/server/app/core/controller/add.py
@@ -14,9 +14,9 @@ def blacklist(jti):
     db.session.commit()
 
 
-def slide(competition_id):
-    order = Slide.query.filter(Slide.competition_id == competition_id).count()
-    item = Slide(order, competition_id)
+def slide(item_competition):
+    order = Slide.query.filter(Slide.competition_id == item_competition.id).count()
+    item = Slide(order, item_competition.id)
     db.session.add(item)
     db.session.commit()
     db.session.refresh(item)
diff --git a/server/app/core/dto.py b/server/app/core/dto.py
index 7df2d767..c80cbe08 100644
--- a/server/app/core/dto.py
+++ b/server/app/core/dto.py
@@ -20,39 +20,20 @@ class UserDTO:
 
 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(),
-        },
-    )
-    user_list_model = api.model(
-        "CompetitionList",
-        {"competitions": fields.List(fields.Nested(model)), "count": fields.Integer(), "total": fields.Integer()},
-    )
+    schema = rich_schemas.CompetitionSchemaRich(many=False)
+    list_schema = rich_schemas.CompetitionSchemaRich(many=True)
 
 
 class SlideDTO:
     api = Namespace("slides")
-    model = api.model(
-        "Slide",
-        {
-            "id": fields.Integer(),
-            "order": fields.Integer(),
-            "title": fields.String(),
-            "timer": fields.Integer(),
-            "competition_id": fields.Integer(),
-        },
-    )
+    schema = schemas.SlideSchema(many=False)
+    list_schema = schemas.SlideSchema(many=True)
 
 
 class TeamDTO:
     api = Namespace("teams")
-    model = api.model("Team", {"id": fields.Integer(), "name": fields.String(), "competition_id": fields.Integer()})
+    schema = schemas.TeamSchema(many=False)
+    list_schema = schemas.TeamSchema(many=True)
 
 
 class MiscDTO:
diff --git a/server/app/core/rich_schemas.py b/server/app/core/rich_schemas.py
index af52b293..56aec85e 100644
--- a/server/app/core/rich_schemas.py
+++ b/server/app/core/rich_schemas.py
@@ -30,6 +30,7 @@ class CompetitionSchemaRich(RichSchema):
     id = ma.auto_field()
     name = ma.auto_field()
     year = ma.auto_field()
+    slides = fields.Nested(schemas.SlideSchema, many=True)
     style = fields.Nested(schemas.RoleSchema, many=False)
     city = fields.Nested(schemas.CitySchema, many=False)
 
diff --git a/server/tests/test_app.py b/server/tests/test_app.py
index ce897883..b4308dce 100644
--- a/server/tests/test_app.py
+++ b/server/tests/test_app.py
@@ -14,19 +14,19 @@ def test_competition(client):
     data = {"name": "c1", "year": 2020, "city_id": 1, "style_id": 1}
     response, body = post(client, "/api/competitions", data, headers=headers)
     assert response.status_code == 200
-    assert body["name"] == "c1"
+    assert body["items"][0]["name"] == "c1"
 
     # Get competition
     response, body = get(client, "/api/competitions/1", headers=headers)
     assert response.status_code == 200
-    assert body["name"] == "c1"
+    assert body["items"][0]["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
+    assert len(body["items"]) == 2
 
     response, body = put(client, "/api/competitions/1/slides/1/order", {"order": 1}, headers=headers)
     assert response.status_code == 200
@@ -36,8 +36,8 @@ def test_competition(client):
 
     response, body = get(client, "/api/competitions/1/teams", headers=headers)
     assert response.status_code == 200
-    assert len(body) == 1
-    assert body[0]["name"] == "t1"
+    assert len(body["items"]) == 1
+    assert body["items"][0]["name"] == "t1"
 
     response, body = delete(client, "/api/competitions/1", {}, headers=headers)
     assert response.status_code == 200
@@ -74,12 +74,12 @@ def test_app(client):
     # Get the current user
     response, body = get(client, "/api/users", headers=headers)
     assert response.status_code == 200
-    assert body["email"] == "test1@test.se"
+    assert body["items"][0]["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"
+    assert body["items"][0]["name"] == "Carl Carlsson"
 
     # Delete created user
     response, body = delete(client, "/api/auth/delete/1", {}, headers=headers)
-- 
GitLab


From 908b00611cb9cc23a26ca613fb3b6560fe2d4742 Mon Sep 17 00:00:00 2001
From: robban64 <carl@schonfelder.se>
Date: Mon, 5 Apr 2021 21:51:34 +0200
Subject: [PATCH 3/7] add: marshmallow for miscs

---
 server/app/apis/misc.py | 37 +++++++++++++++++++------------------
 server/app/core/dto.py  |  8 ++++----
 2 files changed, 23 insertions(+), 22 deletions(-)

diff --git a/server/app/apis/misc.py b/server/app/apis/misc.py
index d44c26ee..ed25ac11 100644
--- a/server/app/apis/misc.py
+++ b/server/app/apis/misc.py
@@ -1,4 +1,5 @@
 import app.core.controller as dbc
+from app.apis import admin_required, item_response, list_response
 from app.core.dto import MiscDTO
 from app.core.models import City, MediaType, QuestionType, Role
 from flask_jwt_extended import jwt_required
@@ -6,10 +7,10 @@ from flask_restx import Resource, reqparse
 
 api = MiscDTO.api
 
-question_type_model = MiscDTO.question_type_model
-media_type_model = MiscDTO.media_type_model
-role_model = MiscDTO.role_model
-city_model = MiscDTO.city_model
+question_type_schema = MiscDTO.question_type_schema
+media_type_schema = MiscDTO.media_type_schema
+role_schema = MiscDTO.role_schema
+city_schema = MiscDTO.city_schema
 
 
 name_parser = reqparse.RequestParser()
@@ -19,57 +20,57 @@ name_parser.add_argument("name", type=str, required=True, location="json")
 @api.route("/media_types")
 class MediaTypeList(Resource):
     @jwt_required
-    @api.marshal_with(media_type_model)
     def get(self):
-        return MediaType.query.all()
+        items = MediaType.query.all()
+        return list_response(media_type_schema.dump(items))
 
 
 @api.route("/question_types")
 class QuestionTypeList(Resource):
     @jwt_required
-    @api.marshal_with(question_type_model)
     def get(self):
-        return QuestionType.query.all()
+        items = QuestionType.query.all()
+        return list_response(question_type_schema.dump(items))
 
 
 @api.route("/roles")
 class RoleList(Resource):
     @jwt_required
-    @api.marshal_with(role_model)
     def get(self):
-        return Role.query.all()
+        items = Role.query.all()
+        return list_response(role_schema.dump(items))
 
 
 @api.route("/cities")
 class CitiesList(Resource):
     @jwt_required
-    @api.marshal_with(city_model)
     def get(self):
-        return City.query.all()
+        items = City.query.all()
+        return list_response(city_schema.dump(items))
 
     @jwt_required
-    @api.marshal_with(role_model)
     def post(self):
         args = name_parser.parse_args(strict=True)
         dbc.add.default(City(args["name"]))
-        return City.query.all()
+        items = City.query.all()
+        return list_response(city_schema.dump(items))
 
 
 @api.route("/cities/<ID>")
 @api.param("ID")
 class Cities(Resource):
     @jwt_required
-    @api.marshal_with(city_model)
     def put(self, ID):
         item = City.query.filter(City.id == ID).first()
         args = name_parser.parse_args(strict=True)
         item.name = args["name"]
         dbc.edit.default(item)
-        return City.query.all()
+        items = City.query.all()
+        return list_response(city_schema.dump(items))
 
     @jwt_required
-    @api.marshal_with(city_model)
     def delete(self, ID):
         item = City.query.filter(City.id == ID).first()
         dbc.delete.default(item)
-        return City.query.all()
+        items = City.query.all()
+        return list_response(city_schema.dump(items))
diff --git a/server/app/core/dto.py b/server/app/core/dto.py
index c80cbe08..e66814c4 100644
--- a/server/app/core/dto.py
+++ b/server/app/core/dto.py
@@ -38,7 +38,7 @@ class TeamDTO:
 
 class MiscDTO:
     api = Namespace("misc")
-    role_model = api.model("Role", {"id": fields.Integer(), "name": fields.String()})
-    question_type_model = api.model("QuestionType", {"id": fields.Integer(), "name": fields.String()})
-    media_type_model = api.model("MediaType", {"id": fields.Integer(), "name": fields.String()})
-    city_model = api.model("City", {"id": fields.Integer(), "name": fields.String()})
+    role_schema = schemas.RoleSchema(many=True)
+    question_type_schema = schemas.QuestionTypeSchema(many=True)
+    media_type_schema = schemas.MediaTypeSchema(many=True)
+    city_schema = schemas.CitySchema(many=True)
-- 
GitLab


From 5070feeff1948fd1162a840b10e08c05cf076af8 Mon Sep 17 00:00:00 2001
From: robban64 <carl@schonfelder.se>
Date: Mon, 5 Apr 2021 21:57:32 +0200
Subject: [PATCH 4/7] add: tests for miscs

---
 server/tests/test_app.py | 32 ++++++++++++++++++++++++++++++++
 1 file changed, 32 insertions(+)

diff --git a/server/tests/test_app.py b/server/tests/test_app.py
index b4308dce..96cbe9c0 100644
--- a/server/tests/test_app.py
+++ b/server/tests/test_app.py
@@ -2,6 +2,38 @@ from tests import app, client, db
 from tests.test_helpers import add_default_values, delete, get, post, put
 
 
+def test_misc(client):
+    add_default_values()
+
+    # Login in with default user
+    response, body = post(client, "/api/auth/login", {"email": "test@test.se", "password": "password"})
+    assert response.status_code == 200
+    headers = {"Authorization": "Bearer " + body["access_token"]}
+
+    ## Get misc
+    response, body = get(client, "/api/misc/roles", headers=headers)
+    assert body["count"] >= 2
+
+    response, body = get(client, "/api/misc/cities", headers=headers)
+    assert body["count"] >= 1
+    assert body["items"][0]["name"] == "Linköping"
+
+    response, body = get(client, "/api/misc/media_types", headers=headers)
+    assert body["count"] >= 2
+
+    response, body = get(client, "/api/misc/question_types", headers=headers)
+    assert body["count"] >= 3
+
+    ## Cities
+    response, body = post(client, "/api/misc/cities", {"name": "Göteborg"}, headers=headers)
+    assert body["count"] >= 2
+    assert body["items"][1]["name"] == "Göteborg"
+
+    response, body = put(client, "/api/misc/cities/2", {"name": "Gbg"}, headers=headers)
+    assert body["count"] >= 2
+    assert body["items"][1]["name"] == "Gbg"
+
+
 def test_competition(client):
     add_default_values()
 
-- 
GitLab


From 223598dab91cef953c26cba2ca13efe2e7f6aea5 Mon Sep 17 00:00:00 2001
From: robban64 <carl@schonfelder.se>
Date: Mon, 5 Apr 2021 22:21:14 +0200
Subject: [PATCH 5/7] add: order and order_by for searching

---
 server/app/apis/competitions.py   |  4 +++-
 server/app/apis/users.py          |  4 +++-
 server/app/core/controller/get.py | 24 ++++++++++++++++++++++--
 server/app/core/parsers.py        |  6 ++++--
 4 files changed, 32 insertions(+), 6 deletions(-)

diff --git a/server/app/apis/competitions.py b/server/app/apis/competitions.py
index 1353988e..b2c07999 100644
--- a/server/app/apis/competitions.py
+++ b/server/app/apis/competitions.py
@@ -74,6 +74,8 @@ class CompetitionSearch(Resource):
         style_id = args.get("style_id")
         page = args.get("page", 0)
         page_size = args.get("page_size", 15)
-        items, total = dbc.get.search_competitions(name, year, city_id, style_id, page, page_size)
+        order = args.get("order", 1)
+        order_by = args.get("order_by")
 
+        items, total = dbc.get.search_competitions(name, year, city_id, style_id, page, page_size, order, order_by)
         return list_response(list_schema.dump(items), total)
diff --git a/server/app/apis/users.py b/server/app/apis/users.py
index d144e7be..4aaf372b 100644
--- a/server/app/apis/users.py
+++ b/server/app/apis/users.py
@@ -67,6 +67,8 @@ class UserSearch(Resource):
         city_id = args.get("city_id")
         page = args.get("page", 0)
         page_size = args.get("page_size", 15)
+        order = args.get("order", 1)
+        order_by = args.get("order_by")
 
-        items, total = dbc.get.search_user(email, name, city_id, role_id, page, page_size)
+        items, total = dbc.get.search_user(email, name, city_id, role_id, page, page_size, order, order_by)
         return list_response(list_schema.dump(items), total)
diff --git a/server/app/core/controller/get.py b/server/app/core/controller/get.py
index d849c772..1b281570 100644
--- a/server/app/core/controller/get.py
+++ b/server/app/core/controller/get.py
@@ -13,7 +13,7 @@ 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=0, page_size=15):
+def search_user(email=None, name=None, city_id=None, role_id=None, page=0, page_size=15, order=1, order_by=None):
     query = User.query
     if name:
         query = query.filter(User.name.like(f"%{name}%"))
@@ -24,6 +24,15 @@ def search_user(email=None, name=None, city_id=None, role_id=None, page=0, page_
     if role_id:
         query = query.filter(User.role_id == role_id)
 
+    order_column = User.id  # Default order_by
+    if order_by:
+        order_column = getattr(User.__table__.c, order_by)
+
+    if order == 1:
+        query = query.order_by(order_column)
+    else:
+        query = query.order_by(order_column.desc())
+
     total = query.count()
     query = query.limit(page_size).offset(page * page_size)
     items = query.all()
@@ -31,7 +40,9 @@ def search_user(email=None, name=None, city_id=None, role_id=None, page=0, page_
     return items, total
 
 
-def search_competitions(name=None, year=None, city_id=None, style_id=None, page=0, page_size=15):
+def search_competitions(
+    name=None, year=None, city_id=None, style_id=None, page=0, page_size=15, order=1, order_by=None
+):
     query = Competition.query
     if name:
         query = query.filter(Competition.name.like(f"%{name}%"))
@@ -42,6 +53,15 @@ def search_competitions(name=None, year=None, city_id=None, style_id=None, page=
     if style_id:
         query = query.filter(Competition.style_id == style_id)
 
+    order_column = Competition.year  # Default order_by
+    if order_by:
+        order_column = getattr(Competition.columns, order_by)
+
+    if order == 1:
+        query = query.order_by(order_column)
+    else:
+        query = query.order_by(order_column.desc())
+
     total = query.count()
     query = query.limit(page_size).offset(page * page_size)
     items = query.all()
diff --git a/server/app/core/parsers.py b/server/app/core/parsers.py
index 3fc63f1a..6f287cb2 100644
--- a/server/app/core/parsers.py
+++ b/server/app/core/parsers.py
@@ -2,8 +2,10 @@ from flask_restx import inputs, reqparse
 
 ###SEARCH####
 search_parser = reqparse.RequestParser()
-search_parser.add_argument("page", type=int, default=0)
-search_parser.add_argument("page_size", type=int, default=15)
+search_parser.add_argument("page", type=int, default=0, location="args")
+search_parser.add_argument("page_size", type=int, default=15, location="args")
+search_parser.add_argument("order", type=int, default=1, location="args")
+search_parser.add_argument("order_by", type=str, location="args")
 
 ###LOGIN####
 login_parser = reqparse.RequestParser()
-- 
GitLab


From f08baf888477ff91b0390ac68b13583b8c05c006 Mon Sep 17 00:00:00 2001
From: robban64 <carl@schonfelder.se>
Date: Mon, 5 Apr 2021 23:30:11 +0200
Subject: [PATCH 6/7] add: auth schemas

---
 server/app/apis/__init__.py            |  4 +++
 server/app/apis/auth.py                | 16 +++++-------
 server/app/apis/misc.py                |  2 +-
 server/app/core/controller/__init__.py |  9 +++++++
 server/app/core/controller/edit.py     |  6 -----
 server/app/core/controller/get.py      | 34 +++++++++++---------------
 server/app/core/dto.py                 |  6 ++---
 server/tests/test_app.py               |  5 ++--
 8 files changed, 39 insertions(+), 43 deletions(-)

diff --git a/server/app/apis/__init__.py b/server/app/apis/__init__.py
index 19f8a8d9..f009581d 100644
--- a/server/app/apis/__init__.py
+++ b/server/app/apis/__init__.py
@@ -22,6 +22,10 @@ def admin_required():
     return wrapper
 
 
+def text_response(message, code=200):
+    return {"message": message}, 200
+
+
 def list_response(items, total=None, code=200):
     if type(items) is not list:
         abort(codes.INTERNAL_SERVER_ERROR)
diff --git a/server/app/apis/auth.py b/server/app/apis/auth.py
index 061b00d9..920d2d65 100644
--- a/server/app/apis/auth.py
+++ b/server/app/apis/auth.py
@@ -1,6 +1,6 @@
 import app.core.controller as dbc
 import app.core.http_codes as codes
-from app.apis import admin_required
+from app.apis import admin_required, item_response, text_response
 from app.core.dto import AuthDTO
 from app.core.models import User
 from app.core.parsers import create_user_parser, login_parser
@@ -15,7 +15,8 @@ from flask_jwt_extended import (
 from flask_restx import Namespace, Resource, cors
 
 api = AuthDTO.api
-user_model = AuthDTO.model
+schema = AuthDTO.schema
+list_schema = AuthDTO.list_schema
 
 
 def get_user_claims(item_user):
@@ -25,7 +26,6 @@ def get_user_claims(item_user):
 @api.route("/signup")
 class AuthSignup(Resource):
     @jwt_required
-    @cors.crossdomain(origin="*")
     def post(self):
         args = create_user_parser.parse_args(strict=True)
         email = args.get("email")
@@ -40,26 +40,24 @@ class AuthSignup(Resource):
         if not item_user:
             api.abort(codes.BAD_REQUEST, "User could not be created")
 
-        return {"id": item_user.id}
+        return item_response(schema.dump(item_user))
 
 
 @api.route("/delete/<ID>")
 @api.param("ID")
 class AuthDelete(Resource):
     @jwt_required
-    @cors.crossdomain(origin="*")
     def delete(self, ID):
         item_user = User.query.filter(User.id == ID).first()
         dbc.delete.default(item_user)
         if ID == get_jwt_identity():
             jti = get_raw_jwt()["jti"]
             dbc.add.blacklist(jti)
-        return "deleted"
+        return text_response(f"User {ID} deleted")
 
 
 @api.route("/login")
 class AuthLogin(Resource):
-    @cors.crossdomain(origin="*")
     def post(self):
         args = login_parser.parse_args(strict=True)
         email = args.get("email")
@@ -79,18 +77,16 @@ class AuthLogin(Resource):
 @api.route("/logout")
 class AuthLogout(Resource):
     @jwt_required
-    @cors.crossdomain(origin="*")
     def post(self):
         jti = get_raw_jwt()["jti"]
         dbc.add.blacklist(jti)
-        return "logout"
+        return text_response("User logout")
 
 
 @api.route("/refresh")
 class AuthRefresh(Resource):
     @jwt_required
     @jwt_refresh_token_required
-    @cors.crossdomain(origin="*")
     def post(self):
         old_jti = get_raw_jwt()["jti"]
 
diff --git a/server/app/apis/misc.py b/server/app/apis/misc.py
index ed25ac11..13055f21 100644
--- a/server/app/apis/misc.py
+++ b/server/app/apis/misc.py
@@ -64,7 +64,7 @@ class Cities(Resource):
         item = City.query.filter(City.id == ID).first()
         args = name_parser.parse_args(strict=True)
         item.name = args["name"]
-        dbc.edit.default(item)
+        dbc.commit_and_refresh(item)
         items = City.query.all()
         return list_response(city_schema.dump(items))
 
diff --git a/server/app/core/controller/__init__.py b/server/app/core/controller/__init__.py
index a8f0d8d2..57f9b429 100644
--- a/server/app/core/controller/__init__.py
+++ b/server/app/core/controller/__init__.py
@@ -3,5 +3,14 @@ from app.core import db
 from app.core.controller import add, delete, edit, get
 
 
+def commit_and_refresh(item):
+    db.session.commit()
+    db.session.refresh(item)
+
+
 def refresh(item):
     db.session.refresh(item)
+
+
+def commit(item):
+    db.session.commit()
diff --git a/server/app/core/controller/edit.py b/server/app/core/controller/edit.py
index 77c726a7..9373ebc7 100644
--- a/server/app/core/controller/edit.py
+++ b/server/app/core/controller/edit.py
@@ -1,12 +1,6 @@
 from app.core import db
 
 
-def default(item):
-    db.session.commit()
-    db.session.refresh(item)
-    return item
-
-
 def switch_order(item1, item2):
     old_order = item1.order
     new_order = item2.order
diff --git a/server/app/core/controller/get.py b/server/app/core/controller/get.py
index 1b281570..d5b5978a 100644
--- a/server/app/core/controller/get.py
+++ b/server/app/core/controller/get.py
@@ -13,6 +13,18 @@ def team(CID, TID):
     return Team.query.filter((Team.competition_id == CID) & (Team.id == TID)).first()
 
 
+def _search(query, order_column, page=0, page_size=15, order=1):
+    if order == 1:
+        query = query.order_by(order_column)
+    else:
+        query = query.order_by(order_column.desc())
+
+    total = query.count()
+    query = query.limit(page_size).offset(page * page_size)
+    items = query.all()
+    return items, total
+
+
 def search_user(email=None, name=None, city_id=None, role_id=None, page=0, page_size=15, order=1, order_by=None):
     query = User.query
     if name:
@@ -28,16 +40,7 @@ def search_user(email=None, name=None, city_id=None, role_id=None, page=0, page_
     if order_by:
         order_column = getattr(User.__table__.c, order_by)
 
-    if order == 1:
-        query = query.order_by(order_column)
-    else:
-        query = query.order_by(order_column.desc())
-
-    total = query.count()
-    query = query.limit(page_size).offset(page * page_size)
-    items = query.all()
-
-    return items, total
+    return _search(query, order_column, page, page_size, order)
 
 
 def search_competitions(
@@ -57,13 +60,4 @@ def search_competitions(
     if order_by:
         order_column = getattr(Competition.columns, order_by)
 
-    if order == 1:
-        query = query.order_by(order_column)
-    else:
-        query = query.order_by(order_column.desc())
-
-    total = query.count()
-    query = query.limit(page_size).offset(page * page_size)
-    items = query.all()
-
-    return items, total
+    return _search(query, order_column, page, page_size, order)
diff --git a/server/app/core/dto.py b/server/app/core/dto.py
index e66814c4..3378a17b 100644
--- a/server/app/core/dto.py
+++ b/server/app/core/dto.py
@@ -2,14 +2,12 @@ import app.core.rich_schemas as rich_schemas
 import app.core.schemas as schemas
 import marshmallow as ma
 from flask_restx import Namespace, fields
-from sqlalchemy.sql.expression import false
-
-res_fields = {"*": fields.Wildcard(fields.String)}
 
 
 class AuthDTO:
     api = Namespace("auth")
-    model = api.model("Auth", {"id": fields.Integer()})
+    schema = rich_schemas.UserSchemaRich(many=False)
+    list_schema = rich_schemas.UserSchemaRich(many=True)
 
 
 class UserDTO:
diff --git a/server/tests/test_app.py b/server/tests/test_app.py
index 96cbe9c0..b60529cf 100644
--- a/server/tests/test_app.py
+++ b/server/tests/test_app.py
@@ -87,8 +87,9 @@ def test_app(client):
     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
+    assert body["items"][0]["id"] == 2
+    assert "password" not in body["items"][0]
+    assert "_password" not in body["items"][0]
 
     # Try loggin with wrong PASSWORD
     response, body = post(client, "/api/auth/login", {"email": "test1@test.se", "password": "abc1234"})
-- 
GitLab


From 944205d4d0a9ffa04cc5196067d79fffe1fb3be8 Mon Sep 17 00:00:00 2001
From: robban64 <carl@schonfelder.se>
Date: Tue, 6 Apr 2021 08:23:33 +0200
Subject: [PATCH 7/7] fix: single items response

---
 server/app/apis/__init__.py |  2 +-
 server/tests/test_app.py    | 14 +++++++-------
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/server/app/apis/__init__.py b/server/app/apis/__init__.py
index f009581d..493a1a94 100644
--- a/server/app/apis/__init__.py
+++ b/server/app/apis/__init__.py
@@ -37,7 +37,7 @@ def list_response(items, total=None, code=200):
 def item_response(item, code=200):
     if isinstance(item, list):
         abort(codes.INTERNAL_SERVER_ERROR)
-    return {"items": [item], "count": 1, "total_count": 1}, code
+    return item, code
 
 
 from flask_restx import Api
diff --git a/server/tests/test_app.py b/server/tests/test_app.py
index b60529cf..383cb574 100644
--- a/server/tests/test_app.py
+++ b/server/tests/test_app.py
@@ -46,12 +46,12 @@ def test_competition(client):
     data = {"name": "c1", "year": 2020, "city_id": 1, "style_id": 1}
     response, body = post(client, "/api/competitions", data, headers=headers)
     assert response.status_code == 200
-    assert body["items"][0]["name"] == "c1"
+    assert body["name"] == "c1"
 
     # Get competition
     response, body = get(client, "/api/competitions/1", headers=headers)
     assert response.status_code == 200
-    assert body["items"][0]["name"] == "c1"
+    assert body["name"] == "c1"
 
     response, body = post(client, "/api/competitions/1/slides", {}, headers=headers)
     assert response.status_code == 200
@@ -87,9 +87,9 @@ def test_app(client):
     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["items"][0]["id"] == 2
-    assert "password" not in body["items"][0]
-    assert "_password" not in body["items"][0]
+    assert body["id"] == 2
+    assert "password" not in body
+    assert "_password" not in body
 
     # Try loggin with wrong PASSWORD
     response, body = post(client, "/api/auth/login", {"email": "test1@test.se", "password": "abc1234"})
@@ -107,12 +107,12 @@ def test_app(client):
     # Get the current user
     response, body = get(client, "/api/users", headers=headers)
     assert response.status_code == 200
-    assert body["items"][0]["email"] == "test1@test.se"
+    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["items"][0]["name"] == "Carl Carlsson"
+    assert body["name"] == "Carl Carlsson"
 
     # Delete created user
     response, body = delete(client, "/api/auth/delete/1", {}, headers=headers)
-- 
GitLab