ContactBridge

Log | Files | Refs | README

commit c345242a6a9519ce3b3e87f0c1226a321fdaca1f
parent 19eaff6d1cc7d0556b3dfd2acf59a879fd1d66b9
Author: William Lindholm <william_lindholm@outlook.com>
Date:   Sat, 11 Nov 2023 01:54:05 +0100

Implemented separate db table for storing spam.

Diffstat:
MWebAPI/__init__.py | 8++++++--
AWebAPI/namespaces/contact.py | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MWebAPI/namespaces/messages.py | 57++-------------------------------------------------------
AWebAPI/namespaces/spam.py | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MWebInterface/routes.py | 2+-
Mdatabase/db.sql | 10++++++++++
Adatabase/models/spam.py | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 258 insertions(+), 58 deletions(-)

diff --git a/WebAPI/__init__.py b/WebAPI/__init__.py @@ -1,8 +1,13 @@ from flask import Blueprint from flask_restx import Api + +from .namespaces.contact import contact_ns from .namespaces.messages import messages_ns +from .namespaces.spam import spam_ns blueprint = Blueprint('api', __name__) api = Api(blueprint) -api.add_namespace(messages_ns) -\ No newline at end of file +api.add_namespace(messages_ns) +api.add_namespace(contact_ns) +api.add_namespace(spam_ns) diff --git a/WebAPI/namespaces/contact.py b/WebAPI/namespaces/contact.py @@ -0,0 +1,72 @@ +from datetime import datetime + +from flask import request +from flask_restx import Namespace, Resource, fields + +from MessageTagging.spam_detector import SpamDetector +from WebAPI.utils.mailer import Mailer +from database.models.messages import MessageModel +from database.models.spam import SpamModel +from database.repository import DbRepository + +contact_ns = Namespace('contact', description='Contact namespace, used for integration with external sites.') + +db_repository = DbRepository() +message_model = MessageModel() +spam_model = SpamModel() +mailer = Mailer() +sd = SpamDetector() + +message_create_model = contact_ns.model('Message', { + 'name': fields.String(required=True, description='The senders name'), + 'email': fields.String(required=True, description='The email address of the sender'), + 'content': fields.String(required=True, description='The content of the message'), +}) + + +@contact_ns.route('/contact') +class ContactResource(Resource): + + @contact_ns.expect(message_create_model) + def post(self): + """Contact form endpoint""" + try: + data, subject, content = prepare_data(request.form) + inverted_relevance = calculate_relevance(data['content']) + + parameters = ( + data['name'], + data['email'], + data.get('subject', 'No subject'), + data['content'], + inverted_relevance + ) + + if inverted_relevance > 0.5: + message_model.create(parameters) + mailer.send_notification(subject, content) + else: + spam_model.create(parameters) + + return {'message': 'Message saved successfully'}, 201 + except Exception as e: + print(f"An error occurred: {e}") + return {'message': 'An error occurred while processing your request'}, 500 + + +def calculate_relevance(content): + try: + relevance = float(sd.detect_spam(content)) + return round(1 - relevance, 2) + except Exception as e: + print(f"An error occurred in relevance calculation: {e}") + return 1 + + +def prepare_data(form_data): + name = form_data['name'] + email = form_data['email'] + content = form_data['content'] + + subject = f"{name} Has contacted you from {email} at {datetime.now().time()}" + return form_data, subject, content +\ No newline at end of file diff --git a/WebAPI/namespaces/messages.py b/WebAPI/namespaces/messages.py @@ -1,5 +1,3 @@ -from datetime import datetime - from flask import request, render_template, make_response from flask_restx import Namespace, Resource, fields @@ -25,20 +23,6 @@ message_read_model = messages_ns.model('Message', { 'relevance': fields.Float(readOnly=True, description='relevance, tries to set to low values for spam') }) -message_create_model = messages_ns.model('Message', { - 'name': fields.String(required=True, description='The senders name'), - 'email': fields.String(required=True, description='The email address of the sender'), - 'content': fields.String(required=True, description='The content of the message'), -}) - -paginated_message_model = messages_ns.model('MessageList', { - 'messages': fields.List(fields.Nested(message_read_model), description='List of message objects'), - 'total_pages': fields.Integer(description='Total number of pages'), - 'current_page': fields.Integer(description='The current page'), - 'page_size': fields.Integer(description='The number of messages per page') -}) - - @messages_ns.route('/') class MessageList(Resource): @@ -82,55 +66,18 @@ class MessageList(Resource): 'page_size': page_size } - @messages_ns.expect(message_create_model) - def post(self): - """Create new message""" - data = request.json - - relevance = float(sd.detect_spam(data['content'])) - inverted_relevance = round(1 - relevance, 2) - - if inverted_relevance > 0.5: - subject = f""" - {data['name']} Has contacted you from {data['email']} - At {datetime.now().time()} - """ - content = data['content'] - mailer.send_notification(subject, content) - - parameters = ( - data['name'], - data['email'], - data.get('subject', 'No subject'), - data['content'], - inverted_relevance - ) - - message_model.create(parameters) - return {'message': 'Message saved successfully'}, 201 - @messages_ns.route('/<int:id>') @messages_ns.response(404, 'Message not found') class Message(Resource): @messages_ns.marshal_with(message_read_model) def get(self, id): - query = "SELECT * FROM messages WHERE id = ?" - message = db_repository.execute_query(query, (id,), expect_result=True) - if message: - return message[0] + return message_model.get(id) messages_ns.abort(404, "Message not found") - @messages_ns.expect(message_read_model) - def put(self, id): - """Update a message given its ID""" - data = request.json - query = "UPDATE messages SET email = ?, content = ? WHERE id = ?" - db_repository.execute_query(query, (data['email'], data['content'], id)) - return {'message': 'Message updated successfully'}, 200 @messages_ns.response(204, 'Message deleted') def delete(self, id): """Delete a message by id""" message_model.delete(id) - return '', 204 + return 'Deleted message', 204 diff --git a/WebAPI/namespaces/spam.py b/WebAPI/namespaces/spam.py @@ -0,0 +1,83 @@ +from flask import request, render_template, make_response +from flask_restx import Namespace, Resource, fields + +from MessageTagging.spam_detector import SpamDetector +from WebAPI.utils.mailer import Mailer +from database.models.spam import SpamModel +from database.repository import DbRepository + +spam_ns = Namespace('spam', description='Spam operations') + +db_repository = DbRepository() +spam_model = SpamModel() +mailer = Mailer() +sd = SpamDetector() + +message_read_model = spam_ns.model('Message', { + 'id': fields.Integer(readOnly=True, description='The message unique identifier'), + 'name': fields.String(required=True, description='The senders name'), + 'email': fields.String(required=True, description='The email address of the sender'), + 'subject': fields.String(required=False, description='The subject of the message'), + 'content': fields.String(required=True, description='The content of the message'), + 'timestamp': fields.String(required=True, description='The time at which the message was sent'), + 'relevance': fields.Float(readOnly=True, description='relevance, tries to set to low values for spam') +}) + +@spam_ns.route('/') +class MessageList(Resource): + + @spam_ns.doc('get spam') + @spam_ns.param('query', 'search query') + @spam_ns.param('sort_by', 'Field to sort the messages by') + @spam_ns.param('sort_order', 'Order to sort the messages (ASC or DESC)') + @spam_ns.param('page', 'The page to retrieve') + @spam_ns.param('page_size', 'The number of messages to retrieve per page') + @spam_ns.produces(['application/json', 'text/html']) + def get(self): + """Get (paginated) spam (HTML or JSON)""" + args = request.args + query = args.get('query', '', type=str) + page = args.get('page', 1, type=int) + page_size = args.get('page_size', 10, type=int) + sort_by = args.get('sort_by', 'id') # Default sort by 'id' + sort_order = args.get('sort_order', 'ASC').upper() # Default sort order 'ASC' + + if sort_order not in ['ASC', 'DESC']: + spam_ns.abort(400, "sort_order must be either 'ASC' or 'DESC'") + + valid_sort_fields = ['id', 'name', 'email', 'subject', 'timestamp', 'relevance', 'content'] + if sort_by not in valid_sort_fields: + spam_ns.abort(400, f"Invalid sort_by field. Must be one of {valid_sort_fields}") + + messages, total_pages = spam_model.get_page(page, page_size, sort=sort_by, sort_order=sort_order, + search=query) + + accept_header = request.headers.get('Accept', '') + if 'text/html' in accept_header: + response = make_response(render_template('component/messages_template.html', messages=messages)) + response.headers['X-Total-Pages'] = total_pages + response.headers['X-Current-Page'] = page + return response + else: + return { + 'messages': messages, + 'total_pages': total_pages, + 'current_page': page, + 'page_size': page_size + } + + +@spam_ns.route('/<int:id>') +@spam_ns.response(404, 'Message not found') +class Message(Resource): + @spam_ns.marshal_with(message_read_model) + def get(self, id): + return spam_ns.get(id) + messages_ns.abort(404, "Message not found") + + + @spam_ns.response(204, 'Message deleted') + def delete(self, id): + """Delete a message by id""" + spam_ns.delete(id) + return 'Deleted message', 204 diff --git a/WebInterface/routes.py b/WebInterface/routes.py @@ -17,7 +17,7 @@ def home(): } spam_context = { - "api_endpoint": "messages", + "api_endpoint": "spam", "id_prefix": "spam-", "headers": [("ID", "10%"), ("Relevance", "10%"), ("Name", "30%"), ("Content", "50%")] } diff --git a/database/db.sql b/database/db.sql @@ -8,6 +8,16 @@ CREATE TABLE IF NOT EXISTS messages ( relevance NUMERIC ); +CREATE TABLE IF NOT EXISTS spam ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + email TEXT NOT NULL, + subject TEXT, + content TEXT NOT NULL, + timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, + relevance NUMERIC +); + CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, diff --git a/database/models/spam.py b/database/models/spam.py @@ -0,0 +1,83 @@ +import math + +from database.repository import DbRepository + + +class SpamModel: + def __init__(self): + self.db_repository = DbRepository() + + def get_page(self, page, page_size, sort="id", sort_order="ASC", search=''): + search_term = f"%{search}%" + + common_sql_query_base = f""" + FROM spam + WHERE (name LIKE ? OR email LIKE ? OR subject LIKE ? OR content LIKE ?) + """ + + total_messages_query = f""" + SELECT COUNT(*) + {common_sql_query_base} + """ + + total_messages = self.db_repository.execute_query( + total_messages_query, + (search_term, search_term, search_term, search_term), + expect_result=True + ) + + total_messages_count = total_messages[0][0] if total_messages else 0 + offset = (page - 1) * page_size + total_pages = math.ceil(total_messages_count / page_size) + + paginated_messages_query = f""" + SELECT id, name, email, subject, content, timestamp, relevance + {common_sql_query_base} + ORDER BY {sort} {sort_order} + LIMIT ? OFFSET ? + """ + + paginated_messages = self.db_repository.execute_query( + paginated_messages_query, + (search_term, search_term, search_term, search_term, page_size, offset), + expect_result=True + ) + + labeled_messages = [ + { + 'id': message[0], + 'name': message[1], + 'email': message[2], + 'subject': message[3], + 'content': message[4], + 'timestamp': message[5], + 'relevance': message[6] + } + for message in paginated_messages + ] + + return labeled_messages, total_pages + + def get(self, message_id): + query = 'SELECT * FROM spam WHERE id = ?' + result = self.db_repository.execute_query(query, (message_id,), expect_result=True) + return result[0] if result else None + + def create(self, parameters): + create_query = """ + INSERT INTO spam (name, email, subject, content, relevance) + VALUES (?, ?, ?, ?, ?) + """ + self.db_repository.execute_query(create_query, parameters) + + def update(self, parameters): + update_query = """ + UPDATE spam + SET name = ?, email = ?, subject = ?, content = ?, relevance = ? + WHERE id = ? + """ + self.db_repository.execute_query(update_query, parameters) + + def delete(self, message_id): + delete_query = "DELETE FROM spam WHERE id = ?" + self.db_repository.execute_query(delete_query, (message_id,))