diff --git a/backend/pyproject.toml b/backend/pyproject.toml index f9d7142..d1ed66a 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -64,5 +64,6 @@ start = {cmd = "uvicorn --workers 2 neo_neo_todo.main:app", env_file = "developm dev = {cmd = "uvicorn neo_neo_todo.main:app --reload", env_file = "development.env"} recreate-db-base = "python3 src/neo_neo_todo/utils/database.py" -recreate-db = {composite = ["recreate-db-base"], env_file = "development.env"} +migration-base = "python3 src/neo_neo_todo/migrations/0.py" +recreate-db = {composite = ["recreate-db-base", "migration-base"], env_file = "development.env"} test = {composite = ["recreate-db-base", "pytest tests/"], env_file = "testing.env"} diff --git a/backend/src/neo_neo_todo/migrations/0.py b/backend/src/neo_neo_todo/migrations/0.py new file mode 100644 index 0000000..a75cab6 --- /dev/null +++ b/backend/src/neo_neo_todo/migrations/0.py @@ -0,0 +1,71 @@ +import os +from urllib.parse import urlparse + +import psycopg +from psycopg import sql + + +def migrate() -> None: + """ + Migrate the PostgreSQL database + + Watch for PostgreSQL version changes + """ + url_parts = urlparse(os.environ["TODO_DB_DATABASE_URL"]) + + # Extract connection parameters + dbname = url_parts.path[1:] + user_name = url_parts.username + password = url_parts.password + host = url_parts.hostname + port = url_parts.port + + print( + f"The database name is {dbname}, the username is {user_name}, the password is {password}, the host is {host}, the port is {port}" + ) + + with psycopg.connect( + dbname=dbname, user=user_name, password=password, host=host, port=port + ) as conn: + with conn.cursor() as cur: + # create the members table + cur.execute( + sql.SQL( + """CREATE TABLE members ( + id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + created TIMESTAMP NOT NULL DEFAULT now(), + email TEXT NOT NULL, + email_verified TIMESTAMP, + password_hash TEXT NOT NULL + )""" + ) + ) + + cur.execute( + sql.SQL( + """CREATE UNIQUE INDEX members_unique_email_idx + ON members (LOWER(email) + )""" + ) + ) + + cur.execute( + sql.SQL( + """CREATE TABLE todos ( + id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + complete BOOLEAN NOT NULL DEFAULT FALSE, + due TIMESTAMPTZ, + member_id INT NOT NULL REFERENCES members(id), + task TEXT NOT NULL + )""" + ) + ) + + # Commit the changes + conn.commit() + + print("Finished PostgreSQL migration number 0") + + +if __name__ == "__main__": + migrate() diff --git a/backend/src/neo_neo_todo/models/member.py b/backend/src/neo_neo_todo/models/member.py new file mode 100644 index 0000000..a869519 --- /dev/null +++ b/backend/src/neo_neo_todo/models/member.py @@ -0,0 +1,11 @@ +from dataclasses import dataclass +from datetime import datetime + + +@dataclass +class Member: + id: int + email: str + password_hash: str + created: datetime + email_verified: datetime | None diff --git a/backend/src/neo_neo_todo/utils/database.py b/backend/src/neo_neo_todo/utils/database.py index 2330f2d..601f08f 100644 --- a/backend/src/neo_neo_todo/utils/database.py +++ b/backend/src/neo_neo_todo/utils/database.py @@ -58,7 +58,7 @@ def recreate_db() -> None: # Create a new user cur.execute( - sql.SQL("CREATE USER {} WITH PASSWORD {}").format( + sql.SQL("CREATE USER {} WITH PASSWORD {} CREATEDB").format( sql.Identifier(user_name), password # type: ignore ) ) @@ -73,6 +73,13 @@ def recreate_db() -> None: ) ) + cur.execute( + sql.SQL("ALTER DATABASE {} OWNER TO {}").format( + sql.Identifier(dbname), + sql.Identifier(user_name), # type: ignore + ) + ) + # Commit the changes conn.commit()