diff --git a/src/packetserver/common/__init__.py b/src/packetserver/common/__init__.py index e3f05bf..89cdd0a 100644 --- a/src/packetserver/common/__init__.py +++ b/src/packetserver/common/__init__.py @@ -193,6 +193,9 @@ class Request(Message): if ('p' in msg.data) and (type(msg.data['p']) is not str): raise ValueError("Path of Request must be a string.") + if 'p' in self.data: + self.data['p'] = str(self.data['p']).strip().lower() + if 'm' in msg.data: if type(msg.data['m']) is not bytes: raise ValueError("Method of Request must be bytes.") @@ -207,7 +210,7 @@ class Request(Message): @path.setter def path(self, path: str): - self.data['p'] = str(path.strip()) + self.data['p'] = str(path).strip().lower() @property def method(self) -> Method: diff --git a/src/packetserver/server/__init__.py b/src/packetserver/server/__init__.py index 2d2cc47..a299e57 100644 --- a/src/packetserver/server/__init__.py +++ b/src/packetserver/server/__init__.py @@ -1,6 +1,7 @@ import pe.app import packetserver.common from packetserver.server.constants import default_server_config +from packetserver.server.bulletin import init_bulletins from copy import deepcopy import ax25 from pathlib import Path @@ -13,6 +14,7 @@ from packetserver.server.requests import standard_handlers import logging import signal import time +from typing import Callable class Server: @@ -46,6 +48,7 @@ class Server: conn.root.config['blacklist'] = PersistentList() if 'users' not in conn.root(): conn.root.users = OOBTree() + init_bulletins(conn.root()) self.app = pe.app.Application() packetserver.common.PacketServerConnection.receive_subscribers.append(lambda x: self.server_receiver(x)) packetserver.common.PacketServerConnection.connection_subscribers.append(lambda x: self.server_connection_bouncer(x)) @@ -83,6 +86,9 @@ class Server: logging.debug("running server receiver") process_incoming_data(conn, self) + def register_path_handler(self, path_root: str, fn: Callable): + self.handlers[path_root.strip().lower()] = fn + def start(self): self.app.start(self.pe_server, self.pe_port) self.app.register_callsigns(self.callsign) diff --git a/src/packetserver/server/bulletin.py b/src/packetserver/server/bulletin.py new file mode 100644 index 0000000..0a256c2 --- /dev/null +++ b/src/packetserver/server/bulletin.py @@ -0,0 +1,101 @@ +import persistent +import persistent.list +from persistent.mapping import PersistentMapping +import datetime +from typing import Self,Union,Optional +from packetserver.common import PacketServerConnection, Request, Response, Message +from packetserver.server import Server +from packetserver.server.requests import send_404 +import ZODB +import logging + +def init_bulletins(root: PersistentMapping): + if 'bulletins' not in root: + root['bulletins'] = persistent.list.PersistentList() + if 'bulletin_counter' not in root: + root['bulletin_counter'] = 0 + +def get_new_bulletin_id(root: PersistentMapping) -> int: + if 'bulletin_counter' not in root: + root['bulletin_counter'] = 1 + return 0 + else: + current = root['bulletin_counter'] + root['bulletin_counter'] = current + 1 + return current + +class Bulletin(persistent.Persistent): + @classmethod + def get_bulletin_by_id(cls, bid: int, db_root: PersistentMapping) -> Optional[Self]: + for bull in db_root['bulletins']: + if bull.id == bid: + return bull + return None + + def __init__(self, author: str, subject: str, text: str): + self.author = author + self.subject = subject + self.body = text + self.created_at = None + self.updated_at = None + self.id = None + + @classmethod + def from_dict(cls, bulletin_dict: dict) -> Self: + return Bulletin(bulletin_dict['author'], bulletin_dict['subject'], bulletin_dict['body']) + + def write_new(self, db_root: PersistentMapping): + if self.id is None: + self.id = get_new_bulletin_id(db_root) + self.created_at = datetime.datetime.now(datetime.UTC) + self.updated_at = datetime.datetime.now(datetime.UTC) + db_root['bulletins'].append(self) + + def update_subject(self, new_text: str): + self.subject = new_text + self.updated_at = datetime.datetime.now(datetime.UTC) + + def update_body(self, new_text: str): + self.body = new_text + self.updated_at = datetime.datetime.now(datetime.UTC) + + def to_dict(self): + return { + "id": self.id, + "author": self.author, + "subject": self.subject, + "body": self.body, + "created_at": self.created_at.isoformat(), + "updated_at": self.updated_at.isoformat() + } + +def handle_bulletin_get(req: Request, conn: PacketServerConnection, server: Server): + response = Response.blank() + with server.db.transaction() as db: + pass + return response + +def handle_bulletin_post(req: Request, conn: PacketServerConnection, server: Server): + response = Response.blank() + with server.db.transaction() as db: + pass + return response + +def handle_bulletin_update(req: Request, conn: PacketServerConnection, server: Server): + response = Response.blank() + with server.db.transaction() as db: + pass + return response + +def handle_bulletin_delete(req: Request, conn: PacketServerConnection, server: Server): + response = Response.blank() + with server.db.transaction() as db: + pass + return response + +def bulletin_root_handler(req: Request, conn: PacketServerConnection, server: Server): + logging.debug(f"{req} being processed by bulletin_root_handler") + if req.method is Request.Method.GET: + handle_bulletin_get(req, conn, server) + else: + send_404(conn) diff --git a/src/packetserver/server/requests.py b/src/packetserver/server/requests.py index b74f0b8..b632e29 100644 --- a/src/packetserver/server/requests.py +++ b/src/packetserver/server/requests.py @@ -2,7 +2,16 @@ from msgpack.exceptions import OutOfData from packetserver.common import Message, Request, Response, PacketServerConnection +from .bulletin import bulletin_root_handler import logging +from typing import Union + +def send_404(conn: PacketServerConnection, payload: Union[bytes, bytearray, str, dict] = ""): + response_404 = Response.blank() + response_404.status_code = 404 + response_404.payload = payload + if conn.state.name == "CONNECTED": + conn.send_data(response_404.pack()) def handle_root_get(req: Request, conn: PacketServerConnection, server: 'packetserver.server.Server'): @@ -22,15 +31,27 @@ def handle_root_get(req: Request, conn: PacketServerConnection, 'motd': motd } - if conn.state.name == "CONNECTED": + if conn.state.name == "CONNECTED" and not conn.closing: logging.debug(f"sending response: {response}, {response.compression}, {response.payload}") conn.send_data(response.pack()) logging.debug("response sent successfully") +def root_root_handler(req: Request, conn: PacketServerConnection, + server: 'packetserver.server.Server'): + logging.debug(f"{req} got to root_root_handler") + if req.method is Request.Method.GET: + handle_root_get(req, conn, server) + else: + logging.warning(f"unhandled request found: {req}") + response_404 = Response.blank() + response_404.status_code = 404 + if (conn.state.name == "CONNECTED") and not conn.closing: + conn.send_data(response_404.pack()) + logging.debug(f"Sent 404 in response to {req}") + standard_handlers = { - "": { - "GET": handle_root_get - } + "": root_root_handler, + "bulletin": bulletin_root_handler } def handle_request(req: Request, conn: PacketServerConnection, @@ -40,11 +61,11 @@ def handle_request(req: Request, conn: PacketServerConnection, if conn.closing: logging.debug("Connection marked as closing. Ignoring it.") return - if req.path in server.handlers: - if req.method.name in server.handlers[req.path]: - logging.debug(f"found handler for req {req}") - server.handlers[req.path][req.method.name](req, conn, server) - return + req_root_path = req.path.split("/")[0] + if req_root_path in server.handlers: + logging.debug(f"found handler for req {req}") + server.handlers[req_root_path](req, conn, server) + return logging.warning(f"unhandled request found: {req}") response_404 = Response.blank() response_404.status_code = 404