commit 1afb54fae7b798e23032b33974b141ecd375a8f4
parent 0461ea099954eadf33e9f08aea116a8c39697166
Author: William Lindholm <william_lindholm@outlook.com>
Date: Tue, 7 Nov 2023 14:48:20 +0100
Implemented better API.
Diffstat:
14 files changed, 266 insertions(+), 30 deletions(-)
diff --git a/WebAPI/__init__.py b/WebAPI/__init__.py
@@ -1,7 +1,10 @@
from flask import Blueprint
from flask_restx import Api
+from .namespaces.messages import messages_ns
blueprint = Blueprint('api', __name__)
api = Api(blueprint)
+api.add_namespace(messages_ns)
+
from . import routes
diff --git a/WebAPI/namespaces/messages.py b/WebAPI/namespaces/messages.py
@@ -0,0 +1,93 @@
+from flask import request
+from flask_restx import Namespace, Resource, fields
+
+from database.models.messages import MessageModel
+from database.repository import DbRepository
+
+messages_ns = Namespace('messages', description='Message operations')
+
+db_repository = DbRepository()
+message_model = MessageModel()
+
+message_read_model = messages_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')
+})
+
+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):
+
+ @messages_ns.param('sort_by', 'Field to sort the messages by')
+ @messages_ns.param('sort_order', 'Order to sort the messages (ASC or DESC)')
+ @messages_ns.param('page', 'The page to retrieve')
+ @messages_ns.param('page_size', 'The number of messages to retrieve per page')
+ @messages_ns.marshal_list_with(paginated_message_model)
+ def get(self):
+ args = request.args
+ 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']:
+ messages_ns.abort(400, "sort_order must be either 'ASC' or 'DESC'")
+
+ valid_sort_fields = ['id', 'name', 'email', 'subject', 'timestamp', 'relevance']
+ if sort_by not in valid_sort_fields:
+ messages_ns.abort(400, f"Invalid sort_by field. Must be one of {valid_sort_fields}")
+
+ messages, total_pages = message_model.get_page(page, page_size, sort=sort_by, sort_order=sort_order)
+
+ return {
+ 'messages': messages,
+ 'total_pages': total_pages,
+ 'current_page': page,
+ 'page_size': page_size
+ }
+
+ # Create message
+ @messages_ns.expect(message_create_model)
+ def post(self):
+ data = request.json
+ query = "INSERT INTO messages (name, email, content) VALUES (?, ?, ?)"
+ db_repository.execute_query(query, (data['name'], data['email'], data['content']))
+ 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]
+ 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
diff --git a/WebAPI/routes.py b/WebAPI/routes.py
@@ -1,3 +1,5 @@
+from flask import request, jsonify
+
from MessageTagging.spam_detector import SpamDetector
from . import api
from flask_restx import Resource, fields
@@ -15,6 +17,7 @@ contact_fields = api.model('Contact', {
'message_content': fields.String(required=True)
})
+
@api.route('/contact')
class contact(Resource):
@api.expect(contact_fields)
@@ -39,21 +42,3 @@ class contact(Resource):
db_repo.save_message(name, email, formatted_subject, message_content, rounded_prediction)
return {"message": "Message sent and saved successfully"}, 200
-
-
-@api.route('/messages')
-class Messages(Resource):
- def get(self):
- messages = db_repo.get_all_messages()
- formatted_messages = [
- {
- "id": message[0],
- "name": message[1],
- "email": message[2],
- "subject": message[3],
- "message_content": message[4],
- "timestamp": message[5]
- }
- for message in messages
- ]
- return formatted_messages, 200
-\ No newline at end of file
diff --git a/WebInterface/routes.py b/WebInterface/routes.py
@@ -67,3 +67,8 @@ def login():
def logout():
session.pop('logged_in', None)
return redirect(url_for('web_interface.login'))
+
+
+@web_interface.route('/test')
+def test():
+ return redirect(url_for('static', filename='html/tabletest.html'))
diff --git a/database/database.db b/database/database.db
Binary files differ.
diff --git a/database/db.sql b/database/db.sql
@@ -2,8 +2,8 @@ CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL,
- subject TEXT NOT NULL,
- message_content TEXT NOT NULL,
+ subject TEXT,
+ content TEXT NOT NULL,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
relevance NUMERIC
);
diff --git a/database/models/messages.py b/database/models/messages.py
@@ -0,0 +1,63 @@
+import math
+
+from database.repository import DbRepository
+
+
+class MessageModel:
+ def __init__(self):
+ self.db_repository = DbRepository()
+
+ def get_page(self, page, page_size, sort="id", sort_order="ASC"):
+ total_messages_query = 'SELECT COUNT(*) FROM messages'
+ total_messages = self.db_repository.execute_query(total_messages_query, expect_result=True)[0][0]
+
+ offset = (page - 1) * page_size
+ total_pages = math.ceil(total_messages / page_size)
+
+ paginated_messages_query = f"""
+ SELECT id, name, email, subject, content, timestamp, relevance
+ FROM messages
+ ORDER BY {sort} {sort_order}
+ LIMIT ? OFFSET ?
+ """
+
+ paginated_messages = self.db_repository.execute_query(
+ paginated_messages_query,
+ (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 messages 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 messages (name, email, subject, content, relevance)
+ VALUES (?, ?, ?, ?, ?)
+ """
+ self.db_repository.execute_query(create_query, parameters)
+
+ def update(self, parameters):
+ update_query = """
+ UPDATE messages
+ SET name = ?, email = ?, subject = ?, content = ?, relevance = ?
+ WHERE id = ?
+ """
+ self.db_repository.execute_query(update_query, parameters)
diff --git a/database/repository.py b/database/repository.py
@@ -0,0 +1,29 @@
+import os
+import sqlite3
+
+
+class DbRepository:
+ CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
+ DB_PATH = os.path.join(CURRENT_DIR, 'database.db')
+
+ def __init__(self):
+ self.connection = sqlite3.connect(self.DB_PATH, check_same_thread=False)
+ self.cursor = self.connection.cursor()
+ self.setup_database()
+
+ def execute_query(self, query, parameters=(), expect_result=False):
+ self.cursor.execute(query, parameters)
+
+ if expect_result:
+ return self.cursor.fetchall()
+ else:
+ self.connection.commit()
+
+ def setup_database(self):
+ with open(os.path.join(self.CURRENT_DIR, 'db.sql'), 'r') as sql_file:
+ sql_script = sql_file.read()
+ self.connection.executescript(sql_script)
+ self.connection.commit()
+
+ def close_connection(self):
+ self.connection.close()
diff --git a/main.py b/main.py
@@ -1,10 +1,10 @@
from flask import Flask
-from WebAPI import blueprint as api_blueprint
+from WebAPI import blueprint as web_api
from WebInterface import web_interface
import os
app = Flask(__name__)
-app.register_blueprint(api_blueprint, url_prefix='/api')
+app.register_blueprint(web_api, url_prefix='/api')
app.register_blueprint(web_interface)
app.secret_key = os.urandom(24)
diff --git a/static/css/main.css b/static/css/main.css
@@ -111,3 +111,11 @@ table tr, table td {
padding: 0;
margin: 0 !important;
}
+
+.pagination li a, pagination li a i {
+ transition: background-color 0.2s ease, color 0.2s ease;
+}
+
+.pagination .active {
+ background-color: var(--primary-color) !important;
+}
diff --git a/static/html/tabletest.html b/static/html/tabletest.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Messages Table with HTMX</title>
+ <script src="https://unpkg.com/htmx.org"></script>
+ <style>
+ table {
+ width: 100%;
+ border-collapse: collapse;
+ }
+ table, th, td {
+ border: 1px solid black;
+ }
+ th, td {
+ padding: 10px;
+ text-align: left;
+ }
+ .pagination {
+ padding: 10px;
+ text-align: center;
+ }
+ .pagination a {
+ padding: 5px 10px;
+ margin: 5px;
+ border: 1px solid #ddd;
+ text-decoration: none;
+ }
+ .pagination a.active {
+ background-color: #007bff;
+ color: white;
+ border: 1px solid #007bff;
+ }
+ .pagination a:hover:not(.active) {
+ background-color: #ddd;
+ }
+ </style>
+</head>
+<body>
+
+<div id="messages-table">
+ <!-- This is where our messages table will be loaded -->
+ <div hx-get="/api/messages?page=1&page_size=10" hx-trigger="load" hx-target="#messages-table">
+ Loading messages...
+ </div>
+</div>
+
+<script>
+ // Function to handle the pagination click
+ function loadPage(page) {
+ htmx.ajax('GET', '/api/messages?page=' + page + '&page_size=10', {target: '#messages-table'});
+ }
+</script>
+
+</body>
+</html>
diff --git a/static/js/pagination.js b/static/js/pagination.js
@@ -10,11 +10,6 @@ document.addEventListener('DOMContentLoaded', function () {
});
function changePage(direction) {
- if (typeof currentPage === 'undefined' || typeof totalPages === 'undefined') {
- console.error('currentPage or totalPages is undefined');
- return;
- }
-
var newPage = currentPage;
if (direction === 'next' && currentPage < totalPages) {
newPage = currentPage + 1;
diff --git a/templates/nav.html b/templates/nav.html
@@ -3,7 +3,7 @@
<li>
<div class="user-view">
<i class="material-icons medium" style="color: white;">account_circle</i>
- <a href="#name"><span class="white-text name">John Doe</span></a>
+ <a href="#name"><span class="white-text name" style="display: block; margin: 0; padding: 5px 0; line-height: 1.2;">John Doe</span></a>
<a href="#email"><span class="white-text email">jdandturk@gmail.com</span></a>
</div>
</li>
diff --git a/templates/table.html b/templates/table.html
@@ -36,7 +36,7 @@
<a href="?page={{ i }}">{{ i }}</a>
</li>
{% endfor %}
- <li class="{% if table.current_page == table.total_pages %}disabled{% else %}waves-effect{% endif %}">
+ <li class="{% if table.current_page == table.total_pages %}disabled{% else %}waves-effect no-hover-effect{% endif %}">
<a href="javascript:void(0);" onclick="changePage('next')"><i class="material-icons">chevron_right</i></a>
</li>
</ul>