diff --git a/main.db b/main.db new file mode 100644 index 0000000..6c13ba7 Binary files /dev/null and b/main.db differ diff --git a/schema.sql b/schema.sql index 65c36d9..9b9bad5 100644 --- a/schema.sql +++ b/schema.sql @@ -3,6 +3,7 @@ CREATE TABLE IF NOT EXISTS "messages" ( "user" INTEGER NOT NULL, "thread" INTEGER NOT NULL, "content" TEXT NOT NULL, + "time" INTEGER NOT NULL, PRIMARY KEY("id"), FOREIGN KEY ("user") REFERENCES "users"("id") ON UPDATE NO ACTION ON DELETE NO ACTION, @@ -13,6 +14,7 @@ CREATE TABLE IF NOT EXISTS "messages" ( CREATE TABLE IF NOT EXISTS "users" ( "id" INTEGER NOT NULL UNIQUE, "name" TEXT NOT NULL, + "passhash" TEXT NOT NULL, PRIMARY KEY("id") ); @@ -41,3 +43,11 @@ CREATE TABLE IF NOT EXISTS "threads" ( ON UPDATE NO ACTION ON DELETE NO ACTION ); +CREATE TABLE IF NOT EXISTS "attachments" ( + "id" INTEGER NOT NULL UNIQUE, + "message_id" INTEGER NOT NULL, + "file_path" TEXT NOT NULL, + PRIMARY KEY("id"), + FOREIGN KEY ("message_id") REFERENCES "messages"("id") + ON UPDATE NO ACTION ON DELETE NO ACTION +); diff --git a/server/api/forum_api.py b/server/api/forum_api.py index 8d1a594..359dffe 100644 --- a/server/api/forum_api.py +++ b/server/api/forum_api.py @@ -26,10 +26,10 @@ def send_message() -> tuple[str, int]: if any([not p in data for p in ['content', 'thread']]): return '', 400 - new_message = database.MessageORM( - user=flask_login.current_user.get_id(), + new_message = database.MessageDTO( + user_id=flask_login.current_user.get_id(), content=data['content'], - thread=data['thread'], + thread_id=data['thread'], time=int(time.time()) ) diff --git a/server/api/user_api.py b/server/api/user_api.py index c1bf8dc..05b246d 100644 --- a/server/api/user_api.py +++ b/server/api/user_api.py @@ -23,3 +23,43 @@ def get_user_forums() -> tuple[flask.Response, int]: forums_dict = {"forums" : [dataclasses.asdict(forum) for forum in forums]} return flask.jsonify(forums_dict), 200 + + +@user.route('/create', methods=['POST']) +def create_user() -> tuple[flask.Response, int]: + """ + Create a new user account. + Expects JSON with 'username' and 'password' fields. + """ + data = flask.request.get_json() + + if not data: + return flask.jsonify({"error": "No JSON data provided"}), 400 + + username = data.get('username') + password = data.get('password') + + if not username or not password: + return flask.jsonify({"error": "Username and password are required"}), 400 + + # Validate username length and characters + if len(username.strip()) == 0: + return flask.jsonify({"error": "Username cannot be empty"}), 400 + + if len(password) < 6: + return flask.jsonify({"error": "Password must be at least 6 characters long"}), 400 + + # Attempt to create the user + new_user = database.create_user(username.strip(), password) + + if new_user is None: + return flask.jsonify({"error": "Username already exists or creation failed"}), 409 + + # Return the created user (without sensitive data) + user_dict = { + "id": new_user.id, + "name": new_user.name, + "message": "User created successfully" + } + + return flask.jsonify(user_dict), 201 diff --git a/server/database.py b/server/database.py index cfe492d..efc0b53 100644 --- a/server/database.py +++ b/server/database.py @@ -7,7 +7,7 @@ import flask_login import mgU -DATABASE = "./main2.db" +DATABASE = "./main.db" ph = argon2.PasswordHasher() @@ -46,21 +46,48 @@ class UserDTO: @dataclasses.dataclass class MessageDTO: - user: UserDTO + user_id: UserDTO content: str time: int thread_id: int id: int | None = None -def insert_message(message: mgU.Message) -> None: +def create_user(username: str, password: str) -> mgU.User | None: + """ + Create a new user with the given username and password. + Returns the created User object with id, or None if creation fails. + """ + # Hash the password + passhash = ph.hash(password) + + try: + with Opener() as (conn, cur): + q = load_query("insert_user") + cur.execute(q, [username, passhash]) + user_id = cur.lastrowid + + return mgU.User(name=username, id=user_id) + except sqlite3.IntegrityError: + # Username already exists or other constraint violation + return None + +def insert_message(message: mgU.Message | MessageDTO) -> None: """ Insert a new message (from mgU.Message dataclass) and return a new mgU.Message with id, or None on failure. """ + if type(message) == mgU.Message: + thread_id = message.thread + user_id = message.user.id + elif type(message) == MessageDTO: + thread_id = message.thread_id + user_id = message.user_id + + with Opener() as (conn, cur): q = load_query("insert_message") cur.execute(q, [ - message.user, - message.thread, + user_id, + thread_id, message.content, message.time ]) diff --git a/server/example_jsons/create_user.json b/server/example_jsons/create_user.json new file mode 100644 index 0000000..2cf1a51 --- /dev/null +++ b/server/example_jsons/create_user.json @@ -0,0 +1,4 @@ +{ + "username" : "johndoe", + "password" : "securepassword123" +} diff --git a/server/mgU.py b/server/mgU.py index faa07d2..39159a4 100644 --- a/server/mgU.py +++ b/server/mgU.py @@ -1,4 +1,3 @@ -import typing import dataclasses @dataclasses.dataclass(kw_only=True) diff --git a/server/sql/insert_user.sql b/server/sql/insert_user.sql new file mode 100644 index 0000000..3833497 --- /dev/null +++ b/server/sql/insert_user.sql @@ -0,0 +1,2 @@ +INSERT INTO users (name, passhash) +VALUES (?, ?);