diff --git a/code/backend/nest_backend/api_schemas.py b/code/backend/nest_backend/api_schemas.py index 4a100d5..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.") @@ -75,5 +79,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..aed57af 100644 --- a/code/backend/nest_backend/api_spec.py +++ b/code/backend/nest_backend/api_spec.py @@ -29,6 +29,9 @@ 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"}) # add swagger tags that are used for endpoint annotation @@ -42,6 +45,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/app.py b/code/backend/nest_backend/app.py index 3573d79..ef0d726 100644 --- a/code/backend/nest_backend/app.py +++ b/code/backend/nest_backend/app.py @@ -62,6 +62,11 @@ app.add_url_rule( view_func=routes.page_repository_conditions, methods=["GET", "POST"], ) +app.add_url_rule( + "/api/v1/repositories//alerts", + view_func=routes.page_repository_alerts, + methods=["GET", "POST"] +) app.add_url_rule( "/api/v1/conditions/", view_func=routes.page_condition, diff --git a/code/backend/nest_backend/database/tables/Alert.py b/code/backend/nest_backend/database/tables/Alert.py index 188ea14..1c573dc 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(ext.Model): # Relationships repository = ext.relationship("Repository", back_populates="alerts") notifications = ext.relationship("Notification", back_populates="alert", cascade="all, delete") - operations = ext.relationship("BoolOperation", back_populates="alert", cascade="all, delete") \ No newline at end of file + operations = ext.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 52d4df5..0038f18 100644 --- a/code/backend/nest_backend/database/tables/BoolOperation.py +++ b/code/backend/nest_backend/database/tables/BoolOperation.py @@ -9,6 +9,7 @@ from sqlalchemy.orm import backref class BoolOperation(ext.Model): __tablename__ = "bool_operation" + id = ext.Column(ext.Integer, primary_key=True) operation = ext.Column(ext.Enum(OperationType), nullable=False) isRoot = ext.Column(ext.Boolean, default=False, nullable=False) @@ -24,3 +25,13 @@ class BoolOperation(ext.Model): node_2 = ext.relationship("BoolOperation", primaryjoin=("bool_operation.c.node_2_id==bool_operation.c.id"), remote_side="BoolOperation.id", backref=backref("father_2", uselist=False)) alert = ext.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 881be58..823eafd 100644 --- a/code/backend/nest_backend/database/tables/Notification.py +++ b/code/backend/nest_backend/database/tables/Notification.py @@ -13,3 +13,10 @@ class Notification(ext.Model): alert_id = ext.Column(ext.Integer, ext.ForeignKey("alert.id"), nullable=False) # Relationships alert = ext.relationship("Alert", back_populates="notifications") + + def to_json(self): + return { + "id": self.id, + "ora": self.ora.isoformat(), + "alert_id": self.alert_id + } 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..3858608 --- /dev/null +++ b/code/backend/nest_backend/routes/repository/alerts/__init__.py @@ -0,0 +1,2 @@ +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 new file mode 100644 index 0000000..f991b81 --- /dev/null +++ b/code/backend/nest_backend/routes/repository/alerts/alert.py @@ -0,0 +1,142 @@ +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_alert(aid): + """ + --- + get: + summary: Get details about an alert. + parameters: + - in: path + schema: AlertParameterSchema + security: + - jwt: [] + responses: + '200': + description: The details about the requested alert. The schema is incapsulated in Success. + content: + application/json: + schema: Alert + '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: + - alert-related + delete: + summary: Deletes an alert. + parameters: + - in: path + schema: AlertParameterSchema + 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: + - alert-related + patch: + summary: Updates an alert. + security: + - jwt: [] + requestBody: + required: true + content: + application/json: + schema: CreateAlert + parameters: + - in: path + schema: AlertParameterSchema + + responses: + '200': + description: The repository has been updated successfully. + content: + application/json: + schema: Alert + '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: + - alert-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'] + ext.session.commit() + return json_success(alert.to_json()), 200 + elif request.method == "DELETE": + try: + ext.session.delete(alert) + ext.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..bff0a20 --- /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) + ext.session.add(alert) + ext.session.commit() + + return json_success(alert.to_json()), 200