From 5607b74e87a7f92f4db3788ceb1273b98a021d9d Mon Sep 17 00:00:00 2001 From: Lorenzo Balugani Date: Mon, 17 May 2021 15:29:08 +0200 Subject: [PATCH] New alert conditions --- nest_backend/api_schemas.py | 7 +- nest_backend/database/tables/Alert.py | 9 +- nest_backend/database/tables/BoolOperation.py | 45 -------- nest_backend/database/tables/Condition.py | 2 +- nest_backend/database/tables/MadeOf.py | 14 +++ nest_backend/database/tables/__init__.py | 2 +- .../routes/repository/alerts/alert.py | 100 +++++------------- .../repository/alerts/repository_alerts.py | 21 +++- 8 files changed, 70 insertions(+), 130 deletions(-) delete mode 100644 nest_backend/database/tables/BoolOperation.py create mode 100644 nest_backend/database/tables/MadeOf.py diff --git a/nest_backend/api_schemas.py b/nest_backend/api_schemas.py index 754f390..8b39f0e 100644 --- a/nest_backend/api_schemas.py +++ b/nest_backend/api_schemas.py @@ -93,6 +93,9 @@ 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.") + repository_id = fields.Integer(description="The id of the related repository.") + evaluation_mode = fields.Integer(description="How the conditions have to be evaluated.") + conditions = fields.Nested(ConditionSchema, many=True) class Operations(Schema): @@ -117,8 +120,8 @@ class Alert(Schema): 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) + evaluation_mode = fields.Integer(description="How the conditions have to be evaluated.") + conditions = fields.Nested(ConditionSchema, many=True) notifications = fields.Nested(Notification, many=True) diff --git a/nest_backend/database/tables/Alert.py b/nest_backend/database/tables/Alert.py index 6afe6b1..f569bad 100644 --- a/nest_backend/database/tables/Alert.py +++ b/nest_backend/database/tables/Alert.py @@ -3,6 +3,7 @@ This module defines the Alert database class. """ from ..base import ext +from .Enums import ConditionMode class Alert(ext.Model): @@ -11,12 +12,13 @@ class Alert(ext.Model): name = ext.Column(ext.String, nullable=False) limit = ext.Column(ext.Integer, nullable=False) window_size = ext.Column(ext.Integer, nullable=False) + evaluation_mode = ext.Column(ext.Enum(ConditionMode), nullable=False, default=ConditionMode.all_or) # Foreign Keys repository_id = ext.Column(ext.Integer, ext.ForeignKey("repository.id", ondelete="CASCADE"), nullable=False) # Relationships repository = ext.relationship("Repository", back_populates="alerts") notifications = ext.relationship("Notification", back_populates="alert") - operations = ext.relationship("BoolOperation", back_populates="alert") + conditions = ext.relationship("MadeOf", back_populates="alert") def to_json(self): return { @@ -25,8 +27,7 @@ class Alert(ext.Model): 'window_size': self.window_size, 'limit': self.limit, 'repository_id': self.repository_id, + 'evaluation_mode': self.evaluation_mode.value, '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 + 'conditions': [c.condition.to_json() for c in self.conditions] } diff --git a/nest_backend/database/tables/BoolOperation.py b/nest_backend/database/tables/BoolOperation.py deleted file mode 100644 index dede0dd..0000000 --- a/nest_backend/database/tables/BoolOperation.py +++ /dev/null @@ -1,45 +0,0 @@ -""" -This module defines the BoolOperation database class. -""" - -from ..base import ext -from .Enums import OperationType -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) - is_root = ext.Column(ext.Boolean, default=False, nullable=False) - # Foreign Keys - condition_id = ext.Column(ext.Integer, ext.ForeignKey("condition.id")) - node_1_id = ext.Column(ext.Integer, ext.ForeignKey("bool_operation.id", ondelete="SET NULL")) - node_2_id = ext.Column(ext.Integer, ext.ForeignKey("bool_operation.id", ondelete="SET NULL")) - alert_id = ext.Column(ext.Integer, ext.ForeignKey("alert.id", ondelete="CASCADE")) - # Relationships - condition = ext.relationship("Condition", back_populates="operations") - node_1 = ext.relationship("BoolOperation", primaryjoin=("bool_operation.c.node_1_id==bool_operation.c.id"), - remote_side="BoolOperation.id", backref=backref("father_1", uselist=False)) - 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 - } - - def get_chain_ids(self, lista): - if self.id in lista: - # Loop detected! - return -1 - lista.append(self.id) - self.node_1.get_chain_ids(lista) - self.node_1.get_chain_ids(lista) diff --git a/nest_backend/database/tables/Condition.py b/nest_backend/database/tables/Condition.py index 4871c03..f07b3ef 100644 --- a/nest_backend/database/tables/Condition.py +++ b/nest_backend/database/tables/Condition.py @@ -16,7 +16,7 @@ class Condition(ext.Model): # Relationships repository = ext.relationship("Repository", back_populates="conditions") tweets = ext.relationship("Contains", back_populates="condition") - operations = ext.relationship("BoolOperation", back_populates="condition") + alerts = ext.relationship("MadeOf", back_populates="condition") def to_json(self): return { diff --git a/nest_backend/database/tables/MadeOf.py b/nest_backend/database/tables/MadeOf.py new file mode 100644 index 0000000..d768426 --- /dev/null +++ b/nest_backend/database/tables/MadeOf.py @@ -0,0 +1,14 @@ +""" +This module defines the MadeOf database class. +""" + +from ..base import ext + + +class MadeOf(ext.Model): + __tablename__ = "made_of" + aid = ext.Column(ext.Integer, ext.ForeignKey("alert.id", ondelete="CASCADE"), primary_key=True) + cid = ext.Column(ext.Integer, ext.ForeignKey("condition.id"), primary_key=True) + # Relationships + alert = ext.relationship("Alert", back_populates="conditions") + condition = ext.relationship("Condition", back_populates="alerts") \ No newline at end of file diff --git a/nest_backend/database/tables/__init__.py b/nest_backend/database/tables/__init__.py index c273139..656e428 100644 --- a/nest_backend/database/tables/__init__.py +++ b/nest_backend/database/tables/__init__.py @@ -4,7 +4,6 @@ This module contains all database classes. from .Alert import Alert from .Authorization import Authorization -from .BoolOperation import BoolOperation from .Composed import Composed from .Condition import Condition from .Contains import Contains @@ -12,4 +11,5 @@ from .Notification import Notification from .Repository import Repository from .Tweet import Tweet from .User import User +from .MadeOf import MadeOf from .Enums import ConditionType, OperationType, ConditionMode \ No newline at end of file diff --git a/nest_backend/routes/repository/alerts/alert.py b/nest_backend/routes/repository/alerts/alert.py index 6ad44d7..1e1ce31 100644 --- a/nest_backend/routes/repository/alerts/alert.py +++ b/nest_backend/routes/repository/alerts/alert.py @@ -145,80 +145,30 @@ def page_alert(aid): alert.limit = request.json['limit'] alert.name = request.json['name'] alert.window_size = request.json['window_size'] - root_id = alert.to_json()['root_operation']['id'] - root = BoolOperation.filter_by(id=root_id).first() - if not root: - return json_error("Could not find original root element."), 404 - # No longer used chain element deletion - l = [] - bool_list = root.get_chains_ids(l) - for element in alert.operations: - if element.id not in bool_list: - if element.id == root.id: - root = None - ext.session.delete(element) + if (mode := request.json.get("evaluation_mode")) is not None: + try: + alert.evaluation_mode = ConditionMode(mode) + except KeyError: + return json_error("Unknown `type` specified."), 400 + except Exception as e: + return json_error("Unknown error:" + str(e)), 400 + if request.json['conditions'] is not None: + # Possibile vulnearabilità! Un utente potrebbe aggiungere conditions non del suo repo! + for c in request.json['conditions']: + if c['id'] not in alert.repository.conditions: + return json_error("Stop! You violated the law!"), 403 + # Wow very pythonic so much wow + # Obtain list of no longer needed connections + to_be_deleted = [c.cid for c in alert.conditions if + c.cid not in [json['id'] for json in request.json['conditions']]] + # RIP AND TEAR UNTIL ITS DONE + for elem in to_be_deleted: + conn = MadeOf.query.filter_by(cid=elem, aid=alert.id).first() + if conn: + ext.session.delete(conn) + ext.session.commit() + for c in request.json['conditions']: + conn = MadeOf(cid=c['id'], aid=alert.id) + ext.session.add(conn) ext.session.commit() - if request.json['root-operation'].get('id'): # If an alternative root is already present. - new_root_test = BoolOperation.filter_by(id=request.json['root_operation']['id']).first() - if new_root_test and new_root_test != root: - new_root_test.is_root = True - ext.session.commit() - else: # If the alternative root needs to be brand-new. - condition_id = None - if request.json['root_operation'].get('condition'): # Is the new alternative connected to a condition? - if not Condition.query.filter_by(id=request.json['root_operation']['condition']['id'], - repository_id=alert.repository_id).first(): - return json_error("One of the provided IDs is incorrect."), 404 - condition_id = request.json['root_operation']['condition']['id'] - if (type_ := request.json['root-operation']['operation']) is not None: - try: - type_ = OperationType(type_) - except KeyError: - return json_error("Unknown `operation` specified."), 400 - root = BoolOperation(operation=type_, is_root=True, alert_id=aid, condition_id=condition_id) - ext.session.add(root) - ext.session.commit() - root = BoolOperation.filter_by(id=request.json['root_operation']['id']).first() - try: - recursion(root, ext, request.json['root_operation']) - except FileNotFoundError: - return json_error("One of the provided IDs is incorrect"), 404 - except KeyError: - return json_error("Unknown field specified."), 400 return json_success(alert.to_json()), 200 - - -def create_node(node, ext, json, id): - # Check if the node already exists - id_1 = json[f'node_{id}']['id'] - if id_1: - node_1 = BoolOperation.query.filter_by(id=id_1).first() - if not node_1: - raise FileNotFoundError - else: - # The node is new. - condition_id = None - if json[f'node_{id}'].get('condition'): - condition = Condition.query.filter_by( - id=json[f'node_{id}']['condition']['id']).filter( - Condition.repository_id == node.alert.repository_id).first() - if not condition: - raise FileNotFoundError - condition_id = condition.id - if (type_ := json[f'node_{id}']['operation']) is not None: - type_ = OperationType(type_) - else: - raise FileNotFoundError - # Create new node - node_1 = BoolOperation(operation=type_, is_root=False, condition_id=condition_id, alert_id=node.alert_id) - ext.session.add(node_1) - ext.session.commit() - # Recursion goes brr - recursion(node_1, ext, json['node_1']) - - -def recursion(node, ext, json): - if json.get('node_1'): # Create node 1 - create_node(node, ext, json, 1) - if json.get('node_2'): # Create node 2 - create_node(node, ext, json, 2) \ No newline at end of file diff --git a/nest_backend/routes/repository/alerts/repository_alerts.py b/nest_backend/routes/repository/alerts/repository_alerts.py index a47e094..ec066fe 100644 --- a/nest_backend/routes/repository/alerts/repository_alerts.py +++ b/nest_backend/routes/repository/alerts/repository_alerts.py @@ -84,9 +84,26 @@ def page_repository_alerts(rid): return json_error('Missing limit'), 400 if 'window_size' not in request.json: return json_error('Missing window size'), 400 + if (mode := request.json.get("evaluation_mode")) is not None: + try: + mode = ConditionMode(mode) + except KeyError: + return json_error("Unknown `type` specified."), 400 + except Exception as e: + return json_error("Unknown error:" + str(e)), 400 + else: + return json_error("Evaluation mode was not provided."), 400 + alert = Alert(name=request.json['name'], limit=request.json['limit'], window_size=request.json['window_size'], - repository_id=rid) + repository_id=rid, evaluation_mode=mode) ext.session.add(alert) ext.session.commit() - + if request.json['conditions'] is not None: + for condition in request.json['conditions']: + c = Condition.query.filter_by(id=condition['id']).first() + if not c: + return json_error("Could not locate condition."), 404 + conn = MadeOf(aid=alert.id, cid=c.id) + ext.session.add(conn) + ext.session.commit() return json_success(alert.to_json()), 201