From 6ef93e751becec7f5aa4d5e07787e11ef51e9ff4 Mon Sep 17 00:00:00 2001 From: 0x01FE Date: Tue, 26 Aug 2025 16:31:29 -0500 Subject: [PATCH] added get_thread_messages and create_forum_thread endpoints --- .gitignore | 2 +- main.db | Bin 53248 -> 57344 bytes main2.db | Bin 57344 -> 0 bytes schema.sql | 9 +++++ server/api/api.py | 2 -- server/api/forum_api.py | 58 +++++++++++++++++++++++++++++--- server/api/helpers.py | 8 +++++ server/api/thread_api.py | 24 ------------- server/database.py | 32 ++++++++++++++++++ server/sql/get_forum_owners.sql | 3 ++ server/sql/insert_thread.sql | 2 ++ 11 files changed, 109 insertions(+), 31 deletions(-) delete mode 100644 main2.db delete mode 100644 server/api/thread_api.py create mode 100644 server/sql/get_forum_owners.sql create mode 100644 server/sql/insert_thread.sql diff --git a/.gitignore b/.gitignore index 582d4db..53b8843 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ __pycache__ -*.db \ No newline at end of file +*.db diff --git a/main.db b/main.db index 6c13ba7e6f114eb26e655e41fdb9a5b360ed8e65..8d6ad280558d106a9dd707eb93c66fb9042a3c98 100644 GIT binary patch delta 149 zcmZozz}#?vd4jYcI|Bm)9}vR;`$QdM9(D%3cqv}~9}K+g4HL5^nb;dPPW;7Y)a1^~ zF0QZ7*kW6fn3R)RTAW%GpO#-#nj4>Ao|jrwj3K}`Igwj}4I(?alG|~z5Wmo5e;)VA bRy=}}4{)(g{>1IU#HG3U6o=XIMFs)@UU@7G delta 55 zcmZoTz}&Ead4jYc8v_FaFA&23+e95>9ySKOcqv}~9}GMkrzU1gZWa`9B>sC9(57_V4lYc^)Tu z>?XZ*ak1h#=CyjGTJp?Er^~SqLD200Id7H-Y4Kbi^1R zSMOf;N-OJ*dwqSy_MGa5?LW0=+7cpj1uI*$%wqQRqGhJq@>Aw$LQ7T3DQzKFw9Z-u zGnX%#xuwNLb1AoQamgAJ*_*D@XkS6*cxw%(w8zFW?`pl~IW;e(6|E~GWHYS4Ri*0e z8Sg@2;e58RY@V~0N99hX#oF`vg0*lq7o_=nk~Rz0yj8GrbJiu(-#IrGtZwFW=F){T zvckE%nVl;xhy-yvV=Y=c({_h#R--j=kk}z~a$Eh@Z-_u%xZaKp#*LvN^=^<-bzHZ! z>bT*2S3`u!_{*k#djF11n@Decnur@mkE*xN`v*m{8Jn~I*5e^;V{5Ch~!|L6BA4?uzc2M|^MXeQWM~>*e62MOp z$LpR1K?Utj*)2k0pfU5&R%z+D2bqlczo@mx4|k(AyiEO9Z8D|BjD_J&ZQ>UenA@2h zPZD3m;>O^hy0z>#O|VAr7;kNmAhFpNdpU=%<+80hbmEp~kg}pLy%~=g?+kX@?{Jrc zdEbFAmAGN3>DOY$tkG#3pU^(;&km#yKY3Lj#Eoqoc~t~A)LVxhc2n06(%UNGr|s)C z)n{mZYYLSF(niU3*GleM`YJO?FQ z*Q%fWX{a*$jh^?M_4WFgd86J~FTXA_ewE{YanWDVzt?|zt}DpSA%Fk^2q1s}0tg_0 z00Iag@bU?C9M21igW^1Z^6)&s6HW{?>Ez$?M_)chYK#B^2q1s}0tg_000IagfWV6^ z&>L0NNF*B7GnjH=T@&}H$J&Me{tpH$xO{XQ(R4mda3%~in#T5 zt%?+c00IagfB*srAbQV>7@0R#|0009ILKmY**5I~@-0*|i$KTz}sT`iiz5I_I{ z1Q0*~0R#|0009ILK%hGUQC0l@UvT}O@qc&7DFp!p5I_I{1Q0*~0R#|00D-Ox^z>-q z@BiOZ^m|<|p8^p;009ILKmY**5I_I{1Q0;r*$C)rk20*uFa96%)*4Q!%=iDF4Kz^^ zKmY**5I_I{1Q0*~0R#|uUIgU#|K<2UqW>lS|DSkZLI42-5I_I{1Q0*~0R#|00D%`s ypf{?jkw`fHSLOJh(f tuple[str, int]: @@ -42,16 +42,16 @@ def send_message() -> tuple[str, int]: return '', 201 -@forum.route('/forum/get_threads') +@forum.route('/get_threads') @flask_login.login_required def get_forum_threads() -> tuple[flask.Response, int]: """ Get all threads from forums the current user has access to. """ - user_id: str = flask_login.current_user.get_id() + user_id: int = flask_login.current_user.get_id() # Get all forums the user has access to - user_forums: list[mgU.Forum] = database.get_user_forums(int(user_id)) + user_forums: list[mgU.Forum] = database.get_user_forums(user_id) if not user_forums: return flask.jsonify({"threads": []}), 200 @@ -68,3 +68,53 @@ def get_forum_threads() -> tuple[flask.Response, int]: return flask.jsonify(threads_dict), 200 +@forum.route('/get_messages') +@flask_login.login_required +def get_thread_messages() -> tuple[flask.Response, int]: + thread_id = flask.request.args.get("thread", type=int) + if thread_id is None: + return flask.jsonify({"error": "Missing thread parameter"}), 400 + + messages = database.get_messages_by_thread(thread_id) + messages_dicts = {"messages" : [dataclasses.asdict(msg) for msg in messages]} + return flask.jsonify(messages_dicts), 200 + +@forum.route('/create_thread', methods=['POST']) +@flask_login.login_required +def create_forum_thread() -> tuple[flask.Response, int] | tuple[str, int]: + + data = flask.request.json + + if not data: + return '', 400 + + if any([not key in data for key in ['forum_id', 'name']]): + return '', 400 + + if type(data['forum_id']) != int: + return '', 400 + + forum_id: int = data.get('forum_id') + thread_name: str = data.get('name') + + if not thread_name or not thread_name.strip(): + return flask.jsonify({"error": "Thread name cannot be empty"}), 400 + + user_id: int = flask_login.current_user.get_id() + + user_owns_forum: bool = helpers.user_owns_forum(user_id, forum_id) + + if not user_owns_forum: + return flask.jsonify({"error": "You don't have permission to create threads in this forum"}), 403 + + # Create the thread + new_thread = database.create_thread(thread_name.strip(), forum_id) + + if new_thread is None: + return flask.jsonify({"error": "Failed to create thread"}), 500 + + # Return the created thread + thread_dict = dataclasses.asdict(new_thread) + return flask.jsonify({"thread": thread_dict, "message": "Thread created successfully"}), 201 + + diff --git a/server/api/helpers.py b/server/api/helpers.py index 8b19138..193904b 100644 --- a/server/api/helpers.py +++ b/server/api/helpers.py @@ -24,3 +24,11 @@ def user_in_thread(user_id: int, thread_id: int) -> bool: return True return False + +def user_owns_forum(user_id: int, forum_id: int) -> bool: + forum_owner_ids: list[int] = database.get_forum_owners(forum_id) + + if not forum_owner_ids: + return False + + return user_id in forum_owner_ids diff --git a/server/api/thread_api.py b/server/api/thread_api.py deleted file mode 100644 index 7a1f8b5..0000000 --- a/server/api/thread_api.py +++ /dev/null @@ -1,24 +0,0 @@ -import dataclasses - -import flask -import flask_login - -import database - - -thread = flask.Blueprint( - name='thread_api', - import_name=__name__, - url_prefix='/thread/' -) - -@thread.route('/get_messages') -@flask_login.login_required -def get_thread_messages() -> tuple[flask.Response, int]: - thread_id = flask.request.args.get("thread", type=int) - if thread_id is None: - return flask.jsonify({"error": "Missing thread parameter"}), 400 - - messages = database.get_messages_by_thread(thread_id) - messages_dicts = {"messages" : [dataclasses.asdict(msg) for msg in messages]} - return flask.jsonify(messages_dicts), 200 diff --git a/server/database.py b/server/database.py index efc0b53..8fe90ca 100644 --- a/server/database.py +++ b/server/database.py @@ -165,3 +165,35 @@ def get_user(username: str, api_key: str) -> User | None: return User(str(user_id), str(user_name)) +# Returns a list of user_ids that are owners of a forum +def get_forum_owners(forum_id: int) -> list[int]: + + q = load_query("get_forum_owners") + with Opener() as (conn, cur): + cur.execute(q) + results = cur.fetchall() + + if not results: + return [] + + user_ids: list[int] = [row[0] for row in results] + + return user_ids + +def create_thread(name: str, forum_id: int) -> mgU.Thread | None: + """ + Create a new thread in the specified forum. + Returns the created Thread object with id, or None if creation fails. + """ + try: + with Opener() as (conn, cur): + q = load_query("insert_thread") + cur.execute(q, [name, forum_id]) + thread_id = cur.lastrowid + + return mgU.Thread(name=name, forum=forum_id, id=thread_id) + except sqlite3.IntegrityError: + # Forum doesn't exist or other constraint violation + return None + + diff --git a/server/sql/get_forum_owners.sql b/server/sql/get_forum_owners.sql new file mode 100644 index 0000000..f5c218e --- /dev/null +++ b/server/sql/get_forum_owners.sql @@ -0,0 +1,3 @@ +SELECT user_id +FROM user_forum_owners +WHERE forum_id = ?; diff --git a/server/sql/insert_thread.sql b/server/sql/insert_thread.sql new file mode 100644 index 0000000..4b0d651 --- /dev/null +++ b/server/sql/insert_thread.sql @@ -0,0 +1,2 @@ +INSERT INTO threads (name, forum) +VALUES (?, ?);