From 0414ee9e4e31593b72aabff9dc3b5f043589f2e4 Mon Sep 17 00:00:00 2001 From: bmondream Date: Sat, 2 May 2026 23:54:01 +0200 Subject: [PATCH] feat: add endpoints for sending and polling messages --- app.py | 36 ++++++++++++++++++++++++++++++++++++ db.py | 27 ++++++++++++++++++++++++++- dto.py | 22 ++++++++++++++++++++++ init-db.py | 1 + 4 files changed, 85 insertions(+), 1 deletion(-) diff --git a/app.py b/app.py index dddd081..aae2324 100644 --- a/app.py +++ b/app.py @@ -25,6 +25,7 @@ app.logger.addHandler(file_handler) app.logger.setLevel(logging.INFO) msg_403_not_primary = "Only a PRIMARY device can perform this action" +msg_403_not_secondary = "Only a SECONDARY device can perform this action" @app.route("/api/v1/login", methods=["POST"]) @@ -95,5 +96,40 @@ def get_sms_messages_by_local_phone_number(): return make_response(jsonify([n.to_dict() for n in db.get_sms_messages_by_local_phone_number(cur, local_phone_number)]), 200) +@app.route("/api/v1/send-message", methods=["POST"]) +@jwt_required() +def send_sms_message(): + if not is_primary(get_jwt()): + return make_response(jsonify(msg=msg_403_not_primary), 403) + + content = request.json.get("content", None) + access_key = get_jwt_identity() + local_phone_number = request.json.get("local_phone_number", None) + remote_phone_number = request.json.get("remote_phone_number", None) + + app.logger.debug(f"sending msg: content='{content}', access_key='{access_key}', local_num='{local_phone_number}', remote_num='{remote_phone_number}'") + + if db.send_sms_message(cur, content, access_key, local_phone_number, remote_phone_number): + return make_response(jsonify(msg="Message successfully sent"), 200) + else: + return make_response(jsonify(msg="Failed to send message"), 400) + + +@app.route("/api/v1/get-queued-sms-messages", methods=["GET"]) +@jwt_required() +def get_queued_sms_messages(): + if not is_secondary(get_jwt()): + return make_response(jsonify(msg=msg_403_not_secondary), 403) + + access_key = get_jwt_identity() + local_phone_number = request.json.get("local_phone_number", None) + + return make_response(jsonify([m.to_dict() for m in db.get_queued_sms_messages(cur, local_phone_number)]), 200) + + def is_primary(jwt): return jwt["typ"] == "PRIMARY" + + +def is_secondary(jwt): + return jwt["typ"] == "SECONDARY" diff --git a/db.py b/db.py index a99e294..fae80a6 100644 --- a/db.py +++ b/db.py @@ -1,4 +1,4 @@ -from dto import Device, SimCard, SmsMessage +from dto import Device, SimCard, SmsMessage, QueuedSmsMessage def get_all_devices(cur) -> list[Device]: @@ -31,3 +31,28 @@ def get_sms_messages_by_local_phone_number(cur, local_phone_number: str) -> list for item in msgs_from_db: msgs.append(SmsMessage.convert(item)) return msgs + + +def send_sms_message(cur, content: str, sender_access_key: str, local_phone_number: str, remote_phone_number: str) -> bool: + if content is None or not content \ + or sender_access_key is None or not sender_access_key \ + or local_phone_number is None or not local_phone_number \ + or remote_phone_number is None or not remote_phone_number: + return False + + cur.execute("INSERT INTO message_queue VALUES (?, ?, ?, ?)", \ + (content, sender_access_key, local_phone_number, remote_phone_number)) + cur.commit() + return True + + +def get_queued_sms_messages(cur, local_phone_number) -> list[QueuedSmsMessage]: + if local_phone_number is None or not local_phone_number: + return [] + + msgs_from_db = cur.execute("DELETE FROM message_queue WHERE local_phone_number = ? RETURNING *", (local_phone_number,)).fetchall() + cur.commit() + msgs = [] + for item in msgs_from_db: + msgs.append(QueuedSmsMessage.convert(item)) + return msgs diff --git a/dto.py b/dto.py index 4c91e2e..8bac173 100644 --- a/dto.py +++ b/dto.py @@ -18,6 +18,7 @@ class Device: def convert(device_from_db) -> Device: return Device(*device_from_db) + @dataclass class SimCard: phone_number: str @@ -34,6 +35,7 @@ class SimCard: def convert(sim_from_db) -> SimCard: return SimCard(*sim_from_db) + @dataclass class SmsMessage: content: str @@ -56,3 +58,23 @@ class SmsMessage: def convert(sms_from_db) -> SmsMessage: return SmsMessage(*sms_from_db) + + +@dataclass +class QueuedSmsMessage: + content: str + sender_access_key: str + local_phone_number: str + remote_phone_number: str + + def to_dict(self): + return { + 'content': self.content, + 'sender_access_key': self.sender_access_key, + 'local_phone_number': self.local_phone_number, + 'remote_phone_number': self.remote_phone_number + } + + + def convert(sms_from_db) -> QueuedSmsMessage: + return QueuedSmsMessage(*sms_from_db) diff --git a/init-db.py b/init-db.py index 24e7415..b591a44 100644 --- a/init-db.py +++ b/init-db.py @@ -19,6 +19,7 @@ cur.execute("CREATE TABLE devices(access_key, secret_key_hash, type, name)") cur.execute("CREATE TABLE messages(content, ts_received, ts_sent, type, local_phone_number, remote_phone_number)") cur.execute("CREATE TABLE sim_events(sim_id, ts, note, cost, currency)") cur.execute("CREATE TABLE sim_cards(phone_number, device_access_key)") +cur.execute("CREATE TABLE message_queue(content, sender_access_key, local_phone_number, remote_phone_number)") pw1_hash = flask_bcrypt.generate_password_hash('pw1').decode('utf-8') pw2_hash = flask_bcrypt.generate_password_hash('pw2').decode('utf-8')