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:
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,))