ContactBridge

Log | Files | Refs | README

commit 1afb54fae7b798e23032b33974b141ecd375a8f4
parent 0461ea099954eadf33e9f08aea116a8c39697166
Author: William Lindholm <william_lindholm@outlook.com>
Date:   Tue,  7 Nov 2023 14:48:20 +0100

Implemented better API.

Diffstat:
MWebAPI/__init__.py | 3+++
AWebAPI/namespaces/messages.py | 93+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MWebAPI/routes.py | 22+++-------------------
MWebInterface/routes.py | 5+++++
Adatabase/database.db | 0
Mdatabase/db.sql | 4++--
Adatabase/models/messages.py | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adatabase/repository.py | 29+++++++++++++++++++++++++++++
Mmain.py | 4++--
Mstatic/css/main.css | 8++++++++
Astatic/html/tabletest.html | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mstatic/js/pagination.js | 5-----
Mtemplates/nav.html | 2+-
Mtemplates/table.html | 2+-
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>