From 2d00b9102f83273e5e384cb647a3b30d0ec83237 Mon Sep 17 00:00:00 2001 From: Lorenzo Balugani Date: Fri, 7 May 2021 19:15:14 +0200 Subject: [PATCH 1/2] Add alert creation --- code/backend/nest_backend/__main__.py | 2 + code/backend/nest_backend/api_schemas.py | 33 ++++ code/backend/nest_backend/api_spec.py | 5 + .../nest_backend/database/tables/Alert.py | 15 +- .../database/tables/BoolOperation.py | 12 +- .../database/tables/Notification.py | 9 +- .../routes/repository/__init__.py | 3 +- .../routes/repository/alerts/__init__.py | 1 + .../routes/repository/alerts/alert.py | 183 ++++++++++++++++++ .../repository/alerts/repository_alerts.py | 92 +++++++++ 10 files changed, 351 insertions(+), 4 deletions(-) create mode 100644 code/backend/nest_backend/routes/repository/alerts/__init__.py create mode 100644 code/backend/nest_backend/routes/repository/alerts/alert.py create mode 100644 code/backend/nest_backend/routes/repository/alerts/repository_alerts.py diff --git a/code/backend/nest_backend/__main__.py b/code/backend/nest_backend/__main__.py index c473990..70b0549 100644 --- a/code/backend/nest_backend/__main__.py +++ b/code/backend/nest_backend/__main__.py @@ -26,6 +26,8 @@ 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", "PUT"]) app.add_url_rule("/api/v1/repositories//conditions", view_func=page_repository_conditions, methods=["GET", "POST"]) +app.add_url_rule("/api/v1/repositories//alerts", view_func=page_repository_alerts, + 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) diff --git a/code/backend/nest_backend/api_schemas.py b/code/backend/nest_backend/api_schemas.py index 4a100d5..3d84528 100644 --- a/code/backend/nest_backend/api_schemas.py +++ b/code/backend/nest_backend/api_schemas.py @@ -75,5 +75,38 @@ class CreateCondition(Schema): content = fields.String(description="The condition content. Meaning may change according to type.") +class CreateAlert(Schema): + name = fields.String(description="The name of the alert.") + limit = fields.Integer(description="The number of tweets in a time window.") + window_size = fields.Integer(description="The size of the time window.") + + +class Operations(Schema): + id = fields.Integer(description="The operation id.") + operation = fields.Integer(description="The type of the operation.") + is_root = fields.Boolean(description="If true, the operation is the root of the operation tree.") + node_1 = fields.Nested('self') + node_2 = fields.Nested('self') + condition = fields.Nested(ConditionSchema) + alert_id = fields.Integer(description="The id of the related alert.") + + +class Notification(Schema): + id = fields.Integer(description="The notification id.") + ora = fields.DateTime(description="Muda muda muda.") + repository_id = fields.Integer(description="The id of the related repository.") + + +class Alert(Schema): + id = fields.Integer(description="The alert id.") + name = fields.String(description="The name of the alert.") + limit = fields.Integer(description="The number of tweets in a time window.") + window_size = fields.Integer(description="The size of the time window.") + repository_id = fields.Integer(description="The id of the related repository.") + operations = fields.Nested(Operations, many=True) + root_operation = fields.Nested(Operations, many=False) + notifications = fields.Nested(Notification, many=True) + + class ConditionParameterSchema(Schema): cid = fields.Integer(description="The condition id.") diff --git a/code/backend/nest_backend/api_spec.py b/code/backend/nest_backend/api_spec.py index 8d353ce..2d98960 100644 --- a/code/backend/nest_backend/api_spec.py +++ b/code/backend/nest_backend/api_spec.py @@ -29,6 +29,8 @@ spec.components.schema("RepositoryUpdate", schema=RepositoryUpdate) spec.components.schema("CreateRepository", schema=CreateRepository) spec.components.schema("CreateCondition", schema=CreateCondition) spec.components.schema("ConditionParameter", schema=ConditionParameterSchema) +spec.components.schema("Alert", schema=Alert) +spec.components.schema("CreateAlert", schema=CreateAlert) spec.components.security_scheme("jwt", {"type": "http", "scheme": "bearer", "bearerFormat": "JWT"}) # add swagger tags that are used for endpoint annotation @@ -42,6 +44,9 @@ tags = [ {'name': 'condition-related', 'description': 'Condition related calls of the API.' }, + {'name': 'alert-related', + 'description': 'Alert 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 fccfb75..f292eef 100644 --- a/code/backend/nest_backend/database/tables/Alert.py +++ b/code/backend/nest_backend/database/tables/Alert.py @@ -16,4 +16,17 @@ class Alert(Base.Model): # Relationships repository = Base.relationship("Repository", back_populates="alerts") notifications = Base.relationship("Notification", back_populates="alert", cascade="all, delete") - operations = Base.relationship("BoolOperation", back_populates="alert", cascade="all, delete") \ No newline at end of file + operations = Base.relationship("BoolOperation", back_populates="alert", cascade="all, delete") + + def to_json(self): + return { + 'id': self.id, + 'name': self.name, + 'window_size': self.window_size, + 'limit': self.limit, + 'repository_id': self.repository_id, + 'notifications': [notification.to_json() for notification in self.notifications], + 'operations': [operation.to_json() for operation in self.operations], + 'root_operation': [operation.to_json() for operation in self.operations if operation.is_root == True][ + 0] if self.operations else None + } diff --git a/code/backend/nest_backend/database/tables/BoolOperation.py b/code/backend/nest_backend/database/tables/BoolOperation.py index 927feae..e6e40d1 100644 --- a/code/backend/nest_backend/database/tables/BoolOperation.py +++ b/code/backend/nest_backend/database/tables/BoolOperation.py @@ -10,7 +10,7 @@ class BoolOperation(Base.Model): __tablename__ = "bool_operation" id = Base.Column(Base.Integer, primary_key=True) operation = Base.Column(Base.Enum(OperationType), nullable=False) - isRoot = Base.Column(Base.Boolean, default=False, nullable=False) + is_root = Base.Column(Base.Boolean, default=False, nullable=False) # Foreign Keys condition_id = Base.Column(Base.Integer, Base.ForeignKey("condition.id")) node_1_id = Base.Column(Base.Integer, Base.ForeignKey("bool_operation.id")) @@ -23,3 +23,13 @@ class BoolOperation(Base.Model): node_2 = Base.relationship("BoolOperation", primaryjoin=("bool_operation.c.node_2_id==bool_operation.c.id"), remote_side="BoolOperation.id", backref=backref("father_2", uselist=False)) alert = Base.relationship("Alert", back_populates="operations") + + def to_json(self): + return {"id": self.id, + "operation": self.operation, + "is_root": self.is_root, + "alert_id": self.alert_id, + "condition": self.condition.to_json() if self.condition else None, + "node_1": self.node_1.to_json() if self.node_1 else None, + "node_2": self.node_2.to_json() if self.node_2 else None + } diff --git a/code/backend/nest_backend/database/tables/Notification.py b/code/backend/nest_backend/database/tables/Notification.py index ae751b4..b682485 100644 --- a/code/backend/nest_backend/database/tables/Notification.py +++ b/code/backend/nest_backend/database/tables/Notification.py @@ -11,4 +11,11 @@ class Notification(Base.Model): # Foreign Key alert_id = Base.Column(Base.Integer, Base.ForeignKey("alert.id"), nullable=False) # Relationships - alert = Base.relationship("Alert", back_populates="notifications") \ No newline at end of file + alert = Base.relationship("Alert", back_populates="notifications") + + def to_json(self): + return { + "id": self.id, + "ora": self.ora.isoformat(), + "alert_id": self.alert_id + } \ 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 0c8ad2d..f5a9ca5 100644 --- a/code/backend/nest_backend/routes/repository/__init__.py +++ b/code/backend/nest_backend/routes/repository/__init__.py @@ -1,4 +1,5 @@ from .conditions import page_repository_conditions from .repository import page_repository from .repositories import page_repositories -from .conditions import * \ No newline at end of file +from .conditions import * +from .alerts import * \ No newline at end of file diff --git a/code/backend/nest_backend/routes/repository/alerts/__init__.py b/code/backend/nest_backend/routes/repository/alerts/__init__.py new file mode 100644 index 0000000..2763c14 --- /dev/null +++ b/code/backend/nest_backend/routes/repository/alerts/__init__.py @@ -0,0 +1 @@ +from .repository_alerts import page_repository_alerts \ No newline at end of file diff --git a/code/backend/nest_backend/routes/repository/alerts/alert.py b/code/backend/nest_backend/routes/repository/alerts/alert.py new file mode 100644 index 0000000..4b0262f --- /dev/null +++ b/code/backend/nest_backend/routes/repository/alerts/alert.py @@ -0,0 +1,183 @@ +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 +import datetime + + +@cross_origin() +@jwt_required() +@repository_auth +def page_repository(aid): + """ + --- + get: + summary: Get details about a repository. + parameters: + - in: path + schema: IntegerParameterSchema + security: + - jwt: [] + responses: + '200': + description: The details about the requested schema. The schema is incapsulated in Success. + content: + application/json: + schema: Repository + '404': + description: Could not find the requested repository. + content: + application/json: + schema: Error + '403': + description: The user is not authorized. + content: + application/json: + schema: Error + '401': + description: The user is not logged in. + content: + application/json: + schema: Error + tags: + - repository-related + delete: + summary: Deletes a repository. + parameters: + - in: path + schema: IntegerParameterSchema + security: + - jwt: [] + responses: + '200': + description: The repository has been deleted successfully. + '404': + description: Could not find the requested repository. + content: + application/json: + schema: Error + '403': + description: The user is not authorized. + content: + application/json: + schema: Error + '401': + description: The user is not logged in. + content: + application/json: + schema: Error + '500': + description: Could not delete the repository. + content: + application/json: + schema: Error + tags: + - repository-related + patch: + summary: Updates a repository. + security: + - jwt: [] + requestBody: + required: true + content: + application/json: + schema: RepositoryUpdate + parameters: + - in: path + schema: IntegerParameterSchema + + responses: + '200': + description: The repository has been updated successfully. + content: + application/json: + schema: Repository + '404': + description: Could not find the requested repository. + content: + application/json: + schema: Error + '403': + description: The user is not authorized. + content: + application/json: + schema: Error + '401': + description: The user is not logged in. + content: + application/json: + schema: Error + tags: + - repository-related + put: + summary: Overwrites a repository. + security: + - jwt: [] + requestBody: + required: true + content: + application/json: + schema: Repository + parameters: + - in: path + schema: IntegerParameterSchema + + responses: + '200': + description: The repository has been updated successfully. + content: + application/json: + schema: Repository + '404': + description: Could not find the requested repository. + content: + application/json: + schema: Error + '403': + description: The user is not authorized. + content: + application/json: + schema: Error + '401': + description: The user is not logged in. + content: + application/json: + schema: Error + '400': + description: The request was malformed. + content: + application/json: + schema: Error + tags: + - repository-related + """ + user = find_user(get_jwt_identity()) + alert = Alert.query.filter_by(id=aid).first() + if not alert: + return json_error("Could not find alert."), 404 + if alert.repository not in [a.repository for a in user.authorizations] + user.owner_of: + json_error("You are not authorized to proceed."), 403 + if request.method == "GET": + return json_success(alert.to_json()), 200 + if alert.repository not in user.owner_of: + json_error("You are not authorized to proceed."), 403 + if request.method == "PATCH": + if 'name' in request.json: + alert.name = request.json['name'] + if 'limit' in request.json: + alert.limit = request.json['limit'] + if 'window_size' in request.json: + alert.window_size = request.json['window_size'] + Base.session.commit() + return json_success(alert.to_json()), 200 + elif request.method == "DELETE": + try: + Base.session.delete(alert) + Base.session.commit() + except Exception: + return json_error("Something went wrong while deleting alert."), 500 + return json_success("Deletion completed."), 200 + elif request.method == "PUT": + pass + diff --git a/code/backend/nest_backend/routes/repository/alerts/repository_alerts.py b/code/backend/nest_backend/routes/repository/alerts/repository_alerts.py new file mode 100644 index 0000000..fa30d96 --- /dev/null +++ b/code/backend/nest_backend/routes/repository/alerts/repository_alerts.py @@ -0,0 +1,92 @@ +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() +@repository_auth +def page_repository_alerts(rid): + """ + --- + get: + summary: Get a list of a repository alerts. + parameters: + - in: path + schema: IntegerParameterSchema + security: + - jwt: [] + responses: + '200': + description: List of Alert schemas, incapsulated in Success. + '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: + - repository-related + post: + summary: Creates an alert and attaches it to the repository. + security: + - jwt: [] + requestBody: + required: true + content: + application/json: + schema: CreateAlert + responses: + '200': + description: The alert has been created successfully. + content: + application/json: + schema: Alert + '403': + description: The user is not authorized. + content: + application/json: + schema: Error + '401': + description: The user is not logged in. + content: + application/json: + schema: Error + tags: + - alert-related + """ + + repository = Repository.query.filter_by(id=rid).first() + if not repository: + return json_error("Could not find repository"), 404 + user = find_user(get_jwt_identity()) + if user.email != repository.owner_id: + return json_error("You are not authorized."), 403 + + if request.method == "GET": + return json_success([alert.to_json() for alert in repository.alerts]) + + if request.method == "POST": + if 'name' not in request.json: + json_error("Missing name."), 400 + if 'limit' not in request.json: + json_error('Missing limit'), 400 + if 'window_size' not in request.json: + json_error('Missing window size'), 400 + alert = Alert(name=request.json['name'], limit=request.json['limit'], window_size=request.json['window_size'], + repository_id=rid) + Base.session.add(alert) + Base.session.commit() + + return json_success(alert.to_json()), 200 From 0fe508071c73add1162b12eec9e030cdf34d8647 Mon Sep 17 00:00:00 2001 From: Lorenzo Balugani Date: Fri, 7 May 2021 19:31:57 +0200 Subject: [PATCH 2/2] Add docs --- code/backend/nest_backend/__main__.py | 3 +- code/backend/nest_backend/api_schemas.py | 4 ++ code/backend/nest_backend/api_spec.py | 1 + .../routes/repository/alerts/__init__.py | 3 +- .../routes/repository/alerts/alert.py | 69 ++++--------------- 5 files changed, 23 insertions(+), 57 deletions(-) diff --git a/code/backend/nest_backend/__main__.py b/code/backend/nest_backend/__main__.py index 70b0549..fa5d6e7 100644 --- a/code/backend/nest_backend/__main__.py +++ b/code/backend/nest_backend/__main__.py @@ -28,7 +28,8 @@ app.add_url_rule("/api/v1/repositories//conditions", view_func=page_rep methods=["GET", "POST"]) app.add_url_rule("/api/v1/repositories//alerts", view_func=page_repository_alerts, methods=["GET", "POST"]) -app.add_url_rule("/api/v1/conditions/", view_func=page_condition, methods=["GET", "PATCH", "DELETE"]) +app.add_url_rule("/api/v1/conditions/", view_func=page_condition, methods=["GET", "PATCH", "DELETE"]) +app.add_url_rule("/api/v1/alerts/", view_func=page_alert, methods=["GET", "PATCH", "DELETE"]) app.register_error_handler(Exception, error_handler) app.register_blueprint(swagger_ui_blueprint, url_prefix=SWAGGER_URL) diff --git a/code/backend/nest_backend/api_schemas.py b/code/backend/nest_backend/api_schemas.py index 3d84528..9a7848f 100644 --- a/code/backend/nest_backend/api_schemas.py +++ b/code/backend/nest_backend/api_schemas.py @@ -36,6 +36,10 @@ class IntegerParameterSchema(Schema): rid = fields.Integer(description="The target numeric id.") +class AlertParameterSchema(Schema): + rid = fields.Integer(description="The target numeric id.") + + class CreateUser(Schema): email = fields.String(description="The new user's email.") username = fields.String(description="The new user's username.") diff --git a/code/backend/nest_backend/api_spec.py b/code/backend/nest_backend/api_spec.py index 2d98960..aed57af 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("ConditionParameter", schema=ConditionParameterSchema) +spec.components.schema("AlertParameter", schema=AlertParameterSchema) spec.components.schema("Alert", schema=Alert) spec.components.schema("CreateAlert", schema=CreateAlert) spec.components.security_scheme("jwt", {"type": "http", "scheme": "bearer", "bearerFormat": "JWT"}) diff --git a/code/backend/nest_backend/routes/repository/alerts/__init__.py b/code/backend/nest_backend/routes/repository/alerts/__init__.py index 2763c14..3858608 100644 --- a/code/backend/nest_backend/routes/repository/alerts/__init__.py +++ b/code/backend/nest_backend/routes/repository/alerts/__init__.py @@ -1 +1,2 @@ -from .repository_alerts import page_repository_alerts \ No newline at end of file +from .repository_alerts import page_repository_alerts +from .alert import page_alert \ No newline at end of file diff --git a/code/backend/nest_backend/routes/repository/alerts/alert.py b/code/backend/nest_backend/routes/repository/alerts/alert.py index 4b0262f..87ab122 100644 --- a/code/backend/nest_backend/routes/repository/alerts/alert.py +++ b/code/backend/nest_backend/routes/repository/alerts/alert.py @@ -9,22 +9,22 @@ import datetime @cross_origin() @jwt_required() @repository_auth -def page_repository(aid): +def page_alert(aid): """ --- get: - summary: Get details about a repository. + summary: Get details about an alert. parameters: - in: path - schema: IntegerParameterSchema + schema: AlertParameterSchema security: - jwt: [] responses: '200': - description: The details about the requested schema. The schema is incapsulated in Success. + description: The details about the requested alert. The schema is incapsulated in Success. content: application/json: - schema: Repository + schema: Alert '404': description: Could not find the requested repository. content: @@ -41,12 +41,12 @@ def page_repository(aid): application/json: schema: Error tags: - - repository-related + - alert-related delete: - summary: Deletes a repository. + summary: Deletes an alert. parameters: - in: path - schema: IntegerParameterSchema + schema: AlertParameterSchema security: - jwt: [] responses: @@ -73,26 +73,26 @@ def page_repository(aid): application/json: schema: Error tags: - - repository-related + - alert-related patch: - summary: Updates a repository. + summary: Updates an alert. security: - jwt: [] requestBody: required: true content: application/json: - schema: RepositoryUpdate + schema: CreateAlert parameters: - in: path - schema: IntegerParameterSchema + schema: AlertParameterSchema responses: '200': description: The repository has been updated successfully. content: application/json: - schema: Repository + schema: Alert '404': description: Could not find the requested repository. content: @@ -109,48 +109,7 @@ def page_repository(aid): application/json: schema: Error tags: - - repository-related - put: - summary: Overwrites a repository. - security: - - jwt: [] - requestBody: - required: true - content: - application/json: - schema: Repository - parameters: - - in: path - schema: IntegerParameterSchema - - responses: - '200': - description: The repository has been updated successfully. - content: - application/json: - schema: Repository - '404': - description: Could not find the requested repository. - content: - application/json: - schema: Error - '403': - description: The user is not authorized. - content: - application/json: - schema: Error - '401': - description: The user is not logged in. - content: - application/json: - schema: Error - '400': - description: The request was malformed. - content: - application/json: - schema: Error - tags: - - repository-related + - alert-related """ user = find_user(get_jwt_identity()) alert = Alert.query.filter_by(id=aid).first()