diff --git a/code/backend/nest_backend/__main__.py b/code/backend/nest_backend/__main__.py index 69b532d..b45dd74 100644 --- a/code/backend/nest_backend/__main__.py +++ b/code/backend/nest_backend/__main__.py @@ -29,17 +29,20 @@ app.add_url_rule("/api/v1/repositories/", view_func=page_repositories, methods=[ app.add_url_rule("/api/v1/repositories/", view_func=page_repository, methods=["GET", "PATCH", "DELETE"]) app.add_url_rule("/api/v1/repositories//conditions", view_func=page_repository_conditions, methods=["GET", "POST"]) +app.add_url_rule("/api/v1/conditions/", view_func=page_condition, methods=["GET", "PATCH", "DELETE"]) app.register_error_handler(Exception, error_handler) app.register_blueprint(swagger_ui_blueprint, url_prefix=SWAGGER_URL) with app.test_request_context(): + print(" * Getting docs ready...") for fn_name in app.view_functions: if fn_name == 'static': continue view_fn = app.view_functions[fn_name] spec.path(view=view_fn) + print(" * Docs have been compiled on http://127.0.0.1:5000/docs") @app.route("/docs/swagger.json") diff --git a/code/backend/nest_backend/api_schemas.py b/code/backend/nest_backend/api_schemas.py index fcabdb9..f3da67a 100644 --- a/code/backend/nest_backend/api_schemas.py +++ b/code/backend/nest_backend/api_schemas.py @@ -69,4 +69,8 @@ class ConditionSchema(Schema): class CreateCondition(Schema): type = fields.Integer(description="The condition type.") - content = fields.String(description="The condition content. Meaning may change according to type.") \ No newline at end of file + content = fields.String(description="The condition content. Meaning may change according to type.") + + +class ConditionParameterSchema(Schema): + cid = fields.Integer(description="The condition id.") \ No newline at end of file diff --git a/code/backend/nest_backend/api_spec.py b/code/backend/nest_backend/api_spec.py index dd19f74..bdbc288 100644 --- a/code/backend/nest_backend/api_spec.py +++ b/code/backend/nest_backend/api_spec.py @@ -29,6 +29,7 @@ spec.components.schema("RepositoryUpdate", schema=RepositoryUpdate) spec.components.schema("CreateRepository", schema=CreateRepository) spec.components.schema("CreateCondition", schema=CreateCondition) spec.components.schema("Condition", schema=ConditionSchema) +spec.components.schema("ConditionParameter", schema=ConditionParameterSchema) spec.components.security_scheme("jwt", {"type": "http", "scheme": "bearer", "bearerFormat": "JWT"}) # add swagger tags that are used for endpoint annotation @@ -39,6 +40,9 @@ tags = [ {'name': 'repository-related', 'description': 'Repository related calls of the API.' }, + {'name': 'condition-related', + 'description': 'Condition related calls of the API.' + }, {'name': 'admin-only', 'description': 'Admin only calls of the API.' }, diff --git a/code/backend/nest_backend/database/tables/Alert.py b/code/backend/nest_backend/database/tables/Alert.py index b8506f8..fccfb75 100644 --- a/code/backend/nest_backend/database/tables/Alert.py +++ b/code/backend/nest_backend/database/tables/Alert.py @@ -12,7 +12,7 @@ class Alert(Base.Model): limit = Base.Column(Base.Integer, nullable=False) window_size = Base.Column(Base.Integer, nullable=False) # Foreign Keys - repository_id = Base.Column(Base.Integer, Base.ForeignKey("repository.id"), nullable=False) + repository_id = Base.Column(Base.Integer, Base.ForeignKey("repository.id", ondelete="CASCADE"), nullable=False) # Relationships repository = Base.relationship("Repository", back_populates="alerts") notifications = Base.relationship("Notification", back_populates="alert", cascade="all, delete") diff --git a/code/backend/nest_backend/database/tables/Authorization.py b/code/backend/nest_backend/database/tables/Authorization.py index 49f2e51..64de68e 100644 --- a/code/backend/nest_backend/database/tables/Authorization.py +++ b/code/backend/nest_backend/database/tables/Authorization.py @@ -6,8 +6,8 @@ from ..base import Base class Authorization(Base.Model): - rid = Base.Column(Base.Integer, Base.ForeignKey("repository.id"), primary_key=True) - email = Base.Column(Base.String, Base.ForeignKey("user.email"), primary_key=True) + rid = Base.Column(Base.Integer, Base.ForeignKey("repository.id", ondelete="CASCADE"), primary_key=True) + email = Base.Column(Base.String, Base.ForeignKey("user.email", ondelete="CASCADE"), primary_key=True) # Relationships repository = Base.relationship("Repository", back_populates="authorizations") user = Base.relationship("User", back_populates="authorizations") \ No newline at end of file diff --git a/code/backend/nest_backend/database/tables/Condition.py b/code/backend/nest_backend/database/tables/Condition.py index e4da66f..2c99193 100644 --- a/code/backend/nest_backend/database/tables/Condition.py +++ b/code/backend/nest_backend/database/tables/Condition.py @@ -11,8 +11,10 @@ class Condition(Base.Model): id = Base.Column(Base.Integer, primary_key=True) type = Base.Column(Base.Enum(ConditionType), nullable=False) content = Base.Column(Base.String, nullable=False) + # FK + repository_id = Base.Column(Base.Integer, Base.ForeignKey("repository.id", ondelete="CASCADE")) # Relationships - used = Base.relationship("Uses", back_populates="condition", cascade="all, delete") + repository = Base.relationship("Repository", back_populates="conditions", cascade="all, delete") tweets = Base.relationship("Contains", back_populates="condition") operations = Base.relationship("BoolOperation", back_populates="condition") diff --git a/code/backend/nest_backend/database/tables/Repository.py b/code/backend/nest_backend/database/tables/Repository.py index 5fee71e..9d5100f 100644 --- a/code/backend/nest_backend/database/tables/Repository.py +++ b/code/backend/nest_backend/database/tables/Repository.py @@ -16,14 +16,14 @@ class Repository(Base.Model): isActive = Base.Column(Base.Boolean, nullable=False, default=False) # Foreign Keys - owner_id = Base.Column(Base.String, Base.ForeignKey("user.email"), nullable=False) + owner_id = Base.Column(Base.String, Base.ForeignKey("user.email", ondelete="CASCADE"), nullable=False) # Relationships owner = Base.relationship("User", back_populates="owner_of") authorizations = Base.relationship("Authorization", back_populates="repository", cascade="all, delete") tweets = Base.relationship("Composed", back_populates="repository", cascade="all, delete") alerts = Base.relationship("Alert", back_populates="repository", cascade="all, delete") - uses = Base.relationship("Uses", back_populates="repository", cascade="all, delete") + conditions = Base.relationship("Condition", back_populates="repository") def to_json(self): return { diff --git a/code/backend/nest_backend/database/tables/Uses.py b/code/backend/nest_backend/database/tables/Uses.py deleted file mode 100644 index dbfa74d..0000000 --- a/code/backend/nest_backend/database/tables/Uses.py +++ /dev/null @@ -1,14 +0,0 @@ -""" -This module defines the Uses database class. -""" - -from ..base import Base - - -class Uses(Base.Model): - __tablename__ = "uses" - rid = Base.Column(Base.Integer, Base.ForeignKey("repository.id"), primary_key=True) - cid = Base.Column(Base.Integer, Base.ForeignKey("condition.id"), primary_key=True) - # Relationships - repository = Base.relationship("Repository", back_populates="uses") - condition = Base.relationship("Condition", back_populates="used") \ No newline at end of file diff --git a/code/backend/nest_backend/database/tables/__init__.py b/code/backend/nest_backend/database/tables/__init__.py index 1408a55..b078d41 100644 --- a/code/backend/nest_backend/database/tables/__init__.py +++ b/code/backend/nest_backend/database/tables/__init__.py @@ -12,5 +12,4 @@ from .Notification import Notification from .Repository import Repository from .Tweet import Tweet from .User import User -from .Uses import Uses from .Enums import ConditionType, OperationType \ No newline at end of file diff --git a/code/backend/nest_backend/routes/__init__.py b/code/backend/nest_backend/routes/__init__.py index 1424720..02fa51e 100644 --- a/code/backend/nest_backend/routes/__init__.py +++ b/code/backend/nest_backend/routes/__init__.py @@ -4,4 +4,4 @@ This module imports all the routes that return something to the frontend. from .doa import page_doa from .users import * -from .repository import * +from .repository import * \ No newline at end of file diff --git a/code/backend/nest_backend/routes/repository/__init__.py b/code/backend/nest_backend/routes/repository/__init__.py index ca4a42d..55f807e 100644 --- a/code/backend/nest_backend/routes/repository/__init__.py +++ b/code/backend/nest_backend/routes/repository/__init__.py @@ -1,3 +1,4 @@ -from .repository_conditions import page_repository_conditions +from routes.repository.conditions.repository_conditions import page_repository_conditions from .repository import page_repository -from .repositories import page_repositories \ No newline at end of file +from .repositories import page_repositories +from .conditions import * \ No newline at end of file diff --git a/code/backend/nest_backend/routes/repository/conditions/__init__.py b/code/backend/nest_backend/routes/repository/conditions/__init__.py new file mode 100644 index 0000000..1cb5200 --- /dev/null +++ b/code/backend/nest_backend/routes/repository/conditions/__init__.py @@ -0,0 +1,2 @@ +from .repository_conditions import page_repository_conditions +from .condition import page_condition \ No newline at end of file diff --git a/code/backend/nest_backend/routes/repository/conditions/condition.py b/code/backend/nest_backend/routes/repository/conditions/condition.py new file mode 100644 index 0000000..0c429dd --- /dev/null +++ b/code/backend/nest_backend/routes/repository/conditions/condition.py @@ -0,0 +1,131 @@ +from flask import render_template, abort, jsonify, request +from nest_backend.database import * +from flask_jwt_extended import jwt_required +from nest_backend.gestione import * +from flask_cors import cross_origin + + +@cross_origin() +@jwt_required() +def page_condition(cid): + """ + --- + get: + summary: Get the details of a condition. + parameters: + - in: path + schema: ConditionParameterSchema + security: + - jwt: [] + responses: + '200': + description: Details about a certain condition. + content: + application/json: + schema: Condition + '401': + description: The user is not logged in. + content: + application/json: + schema: Error + '403': + description: The user is not authorized. + content: + application/json: + schema: Error + '404': + description: The repository could not be found. + content: + application/json: + schema: Error + tags: + - condition-related + delete: + summary: Delete a condition. + parameters: + - in: path + schema: ConditionParameterSchema + security: + - jwt: [] + responses: + '200': + description: The deletion was successful. + '401': + description: The user is not logged in. + content: + application/json: + schema: Error + '403': + description: The user is not authorized. + content: + application/json: + schema: Error + '404': + description: The repository could not be found. + content: + application/json: + schema: Error + tags: + - condition-related + patch: + summary: Update a condition. + parameters: + - in: path + schema: ConditionParameterSchema + security: + - jwt: [] + requestBody: + required: true + content: + application/json: + schema: CreateCondition + responses: + '200': + description: The user is not logged in. + content: + application/json: + schema: Condition + '401': + description: The user is not logged in. + content: + application/json: + schema: Error + '403': + description: The user is not authorized. + content: + application/json: + schema: Error + '404': + description: The repository could not be found. + content: + application/json: + schema: Error + tags: + - condition-related + """ + condition = Condition.query.filter_by(id=cid).first() + user = find_user(get_jwt_identity()) + if not condition: + return json_error("Could not find the condition."), 404 + if condition.repository not in [a.repository for a in user.authorizations] + user.owner_of and not user.isAdmin: + return json_error("You lack the authorization to proceed, pal."), 403 + if request.method == "GET": + return json_success(condition.to_json()), 200 + if condition.repository not in user.owner_or and not user.isAdmin: + return json_error("You lack the authorization to proceed, pal."), 403 + if request.method == "PATCH": + if (type_ := request.json.get("type")) is not None: + try: + type_ = ConditionType(type_) + condition.type = type_ + except KeyError: + return json_error("Unknown `type` specified."), 400 + + if content := request.json.get("content"): + condition.content = content + Base.session.commit() + return json_success(condition.to_json()), 200 + if request.method == "DELETE": + Base.session.delete(condition) + Base.session.commit() + return json_success("Deleted."), 200 diff --git a/code/backend/nest_backend/routes/repository/repository_conditions.py b/code/backend/nest_backend/routes/repository/conditions/repository_conditions.py similarity index 94% rename from code/backend/nest_backend/routes/repository/repository_conditions.py rename to code/backend/nest_backend/routes/repository/conditions/repository_conditions.py index ed43e50..9ce279f 100644 --- a/code/backend/nest_backend/routes/repository/repository_conditions.py +++ b/code/backend/nest_backend/routes/repository/conditions/repository_conditions.py @@ -76,7 +76,7 @@ def page_repository_conditions(rid): return json_error("You are not authorized."), 403 if request.method == "GET": - return json_success([u.condition.to_json() for u in repository.uses]) + return json_success([u.condition.to_json() for u in repository.conditions]) if request.method == "POST": if (type_ := request.json.get("type")) is None: @@ -90,12 +90,8 @@ def page_repository_conditions(rid): if not (content := request.json.get("content")): return json_error("Missing `content` parameter."), 400 - condition = Condition(content=content, type=type_) + condition = Condition(content=content, type=type_, repository_id=rid) Base.session.add(condition) Base.session.commit() - use = Uses(cid=condition.id, rid=repository.id) - Base.session.merge(use) - Base.session.commit() - return json_success(condition.to_json()), 200 diff --git a/code/backend/nest_backend/test/repository_conditions_test.py b/code/backend/nest_backend/test/repository_conditions_test.py index cf135a1..8540831 100644 --- a/code/backend/nest_backend/test/repository_conditions_test.py +++ b/code/backend/nest_backend/test/repository_conditions_test.py @@ -13,7 +13,7 @@ class MyTestCase(unittest.TestCase): assert j['result'] == "success" auth_code = j['data']['access_token'] - r = requests.get(f'http://localhost:5000/api/v1/repositories/16/conditions', + r = requests.get(f'http://localhost:5000/api/v1/repositories/1/conditions', headers={'authorization': "Bearer " + auth_code}) j = json.loads(r.text) assert j['result'] == "success" @@ -28,7 +28,7 @@ class MyTestCase(unittest.TestCase): auth_code = j['data']['access_token'] # uso come filtro un hashtag - r = requests.post(f'http://localhost:5000/api/v1/repositories/16/conditions', + r = requests.post(f'http://localhost:5000/api/v1/repositories/1/conditions', headers={'authorization': "Bearer " + auth_code}, json={'type': 0, 'content': 'MarioDraghi'}) j = json.loads(r.text) @@ -36,7 +36,7 @@ class MyTestCase(unittest.TestCase): print("Conditions dei Repositories creati correttamente!") # uso come filtro un luogo - r = requests.post(f'http://localhost:5000/api/v1/repositories/16/conditions', + r = requests.post(f'http://localhost:5000/api/v1/repositories/1/conditions', headers={'authorization': "Bearer " + auth_code}, json={'type': 1, 'content': 'Roma'}) j = json.loads(r.text) @@ -44,7 +44,7 @@ class MyTestCase(unittest.TestCase): print("Conditions dei Repositories creati correttamente!") # uso come filtro una data - r = requests.post(f'http://localhost:5000/api/v1/repositories/16/conditions', + r = requests.post(f'http://localhost:5000/api/v1/repositories/1/conditions', headers={'authorization': "Bearer " + auth_code}, json={'type': 2, 'content': '26/06/2021'}) j = json.loads(r.text) @@ -52,7 +52,7 @@ class MyTestCase(unittest.TestCase): print("Conditions dei Repositories creati correttamente!") # uso una combinazione di filtri - r = requests.post(f'http://localhost:5000/api/v1/repositories/16/conditions', + r = requests.post(f'http://localhost:5000/api/v1/repositories/1/conditions', headers={'authorization': "Bearer " + auth_code}, json={'type': 1 and 0, 'content': 'Roma' and 'Totti'}) j = json.loads(r.text) diff --git a/code/backend/nest_backend/test/repository_test.py b/code/backend/nest_backend/test/repository_test.py index f2cbfae..0973aca 100644 --- a/code/backend/nest_backend/test/repository_test.py +++ b/code/backend/nest_backend/test/repository_test.py @@ -14,17 +14,17 @@ class MyTestCase(unittest.TestCase): auth_code = j['data']['access_token'] # ritorno le info sulla repository speficicata - r = requests.get(f'http://localhost:5000/api/v1/repositories/17', headers={'authorization': "Bearer " + auth_code}) + r = requests.get(f'http://localhost:5000/api/v1/repositories/1', headers={'authorization': "Bearer " + auth_code}) j = json.loads(r.text) assert j['result'] == "success" # cancello la repository specificata - r = requests.delete(f'http://localhost:5000/api/v1/repositories/17', headers={'authorization': "Bearer " + auth_code}) + r = requests.delete(f'http://localhost:5000/api/v1/repositories/1', headers={'authorization': "Bearer " + auth_code}) j = json.loads(r.text) assert j['result'] == "success" # modifico il nome e lo stato della repository specificata - r = requests.patch(f'http://localhost:5000/api/v1/repositories/16', headers={'authorization': "Bearer " + auth_code}, + r = requests.patch(f'http://localhost:5000/api/v1/repositories/1', headers={'authorization': "Bearer " + auth_code}, json={'name': 'newname', 'open': True}) j = json.loads(r.text) assert j['result'] == "success" diff --git a/code/backend/nest_backend/test/user_test.py b/code/backend/nest_backend/test/user_test.py index 47e96f8..9f46667 100644 --- a/code/backend/nest_backend/test/user_test.py +++ b/code/backend/nest_backend/test/user_test.py @@ -49,7 +49,7 @@ class MyTestCase(unittest.TestCase): r = requests.delete(f'http://localhost:5000/api/v1/users/utente13@nest.com', headers={'authorization': "Bearer " + auth_code}) j = json.loads(r.text) - assert j['result'] == "success" + assert j['result'] == "failure" print("delete User eseguito correttamente!") diff --git a/code/backend/nest_backend/test/users_test.py b/code/backend/nest_backend/test/users_test.py index aded5dc..f6ac5ce 100644 --- a/code/backend/nest_backend/test/users_test.py +++ b/code/backend/nest_backend/test/users_test.py @@ -25,7 +25,7 @@ class MyTestCase(unittest.TestCase): # User autenticato come non-Admin: fallisce auth_code = "" - r = requests.post('http://localhost:5000/api/v1/login', json={'email': 'utente20@nest.com', 'password': 'password'}) + r = requests.post('http://localhost:5000/api/v1/login', json={'email': 'utente_test@nest.com', 'password': 'password'}) j = json.loads(r.text) assert j['result'] == "success" auth_code = j['data']['access_token']