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 6c13ba7..8d6ad28 100644 Binary files a/main.db and b/main.db differ diff --git a/main2.db b/main2.db deleted file mode 100644 index 4144ec0..0000000 Binary files a/main2.db and /dev/null differ diff --git a/schema.sql b/schema.sql index 9b9bad5..966f2be 100644 --- a/schema.sql +++ b/schema.sql @@ -51,3 +51,12 @@ CREATE TABLE IF NOT EXISTS "attachments" ( FOREIGN KEY ("message_id") REFERENCES "messages"("id") ON UPDATE NO ACTION ON DELETE NO ACTION ); + +CREATE TABLE IF NOT EXISTS "user_forum_owners" ( + "user_id" INTEGER NOT NULL, + "forum_id" INTEGER NOT NULL, + FOREIGN KEY ("user_id") REFERENCES "users"("id") + ON UPDATE NO ACTION ON DELETE NO ACTION, + FOREIGN KEY ("forum_id") REFERENCES "forums"("id") + ON UPDATE NO ACTION ON DELETE NO ACTION +); diff --git a/server/api/api.py b/server/api/api.py index f5775f4..f4dadad 100644 --- a/server/api/api.py +++ b/server/api/api.py @@ -2,7 +2,6 @@ import flask from . import user_api from . import forum_api -from . import thread_api api = flask.Blueprint( name='api', @@ -11,4 +10,3 @@ api = flask.Blueprint( ) api.register_blueprint(user_api.user) api.register_blueprint(forum_api.forum) -api.register_blueprint(thread_api.thread) diff --git a/server/api/forum_api.py b/server/api/forum_api.py index 05b7abe..d8d7026 100644 --- a/server/api/forum_api.py +++ b/server/api/forum_api.py @@ -16,7 +16,7 @@ forum = flask.Blueprint( ) -@forum.route('/forum/send_message', methods=['POST']) +@forum.route('/send_message', methods=['POST']) @flask_login.login_required def send_message() -> 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 (?, ?);