From 976d244cb01fcb197cf792eb8fae8d71e4d04044 Mon Sep 17 00:00:00 2001
From: James2Tulloch <146088090+James2Tulloch@users.noreply.github.com>
Date: Sun, 23 Feb 2025 13:28:41 +0000
Subject: [PATCH] Cleaned up rust_crud_api library

---
 django_project/django_project/urls.py         |   4 +
 .../myapp/templates/myapp/group_create.html   |  22 ++
 .../myapp/templates/myapp/group_delete.html   |  24 ++
 .../myapp/templates/myapp/group_list.html     |  26 ++
 .../myapp/templates/myapp/group_update.html   |  26 ++
 django_project/myapp/views.py                 |  86 +++++
 rust_crud_api/src/auth/jwt.rs                 |  38 ++
 rust_crud_api/src/auth/mod.rs                 |   6 +
 rust_crud_api/src/auth/password.rs            |  25 ++
 rust_crud_api/src/db/groups.rs                |  98 +++++
 rust_crud_api/src/db/init.rs                  |  33 ++
 rust_crud_api/src/db/mod.rs                   |   8 +
 rust_crud_api/src/db/users.rs                 | 105 ++++++
 rust_crud_api/src/lib.rs                      | 348 ++----------------
 rust_crud_api/src/models/group.rs             |  12 +
 rust_crud_api/src/models/mod.rs               |   6 +
 rust_crud_api/src/models/user.rs              |  14 +
 17 files changed, 560 insertions(+), 321 deletions(-)
 create mode 100644 django_project/myapp/templates/myapp/group_create.html
 create mode 100644 django_project/myapp/templates/myapp/group_delete.html
 create mode 100644 django_project/myapp/templates/myapp/group_list.html
 create mode 100644 django_project/myapp/templates/myapp/group_update.html
 create mode 100644 rust_crud_api/src/auth/jwt.rs
 create mode 100644 rust_crud_api/src/auth/mod.rs
 create mode 100644 rust_crud_api/src/auth/password.rs
 create mode 100644 rust_crud_api/src/db/groups.rs
 create mode 100644 rust_crud_api/src/db/init.rs
 create mode 100644 rust_crud_api/src/db/mod.rs
 create mode 100644 rust_crud_api/src/db/users.rs
 create mode 100644 rust_crud_api/src/models/group.rs
 create mode 100644 rust_crud_api/src/models/mod.rs
 create mode 100644 rust_crud_api/src/models/user.rs

diff --git a/django_project/django_project/urls.py b/django_project/django_project/urls.py
index 55db621..03e8563 100644
--- a/django_project/django_project/urls.py
+++ b/django_project/django_project/urls.py
@@ -25,4 +25,8 @@ urlpatterns = [
     path('account/', views.account_view, name='account'),
     path('delete/', views.delete_account_view, name='delete_account'),
     path('logout/', views.logout_view, name='logout'),
+    path('groups/', views.group_list_view, name='group_list'),
+    path('groups/create/', views.group_create_view, name='group_create'),
+    path('groups/<int:group_id>/update/', views.group_update_view, name='group_update'),
+    path('groups/<int:group_id>/delete/', views.group_delete_view, name='group_delete'),
 ]
diff --git a/django_project/myapp/templates/myapp/group_create.html b/django_project/myapp/templates/myapp/group_create.html
new file mode 100644
index 0000000..d1a52b6
--- /dev/null
+++ b/django_project/myapp/templates/myapp/group_create.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Create Group</title>
+</head>
+<body>
+    <h1>Create Group</h1>
+    {% if error %}
+      <p style="color: red;">{{ error }}</p>
+    {% endif %}
+    <form method="post">
+        {% csrf_token %}
+        <label for="id_name">Group Name:</label>
+        <input type="text" name="name" id="id_name" required>
+        <br><br>
+        <button type="submit">Create Group</button>
+    </form>
+    <p><a href="{% url 'group_list' %}">Back to Group List</a></p>
+</body>
+</html>
+
diff --git a/django_project/myapp/templates/myapp/group_delete.html b/django_project/myapp/templates/myapp/group_delete.html
new file mode 100644
index 0000000..2d6dfee
--- /dev/null
+++ b/django_project/myapp/templates/myapp/group_delete.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Delete Group</title>
+</head>
+<body>
+    <h1>Delete Group</h1>
+    {% if error %}
+      <p style="color: red;">{{ error }}</p>
+    {% endif %}
+    {% if group %}
+      <p>Are you sure you want to delete the group "{{ group.name }}"?</p>
+      <form method="post">
+          {% csrf_token %}
+          <button type="submit">Yes, delete group</button>
+      </form>
+    {% else %}
+      <p>Group not found.</p>
+    {% endif %}
+    <p><a href="{% url 'group_list' %}">Back to Group List</a></p>
+</body>
+</html>
+
diff --git a/django_project/myapp/templates/myapp/group_list.html b/django_project/myapp/templates/myapp/group_list.html
new file mode 100644
index 0000000..1c5ee84
--- /dev/null
+++ b/django_project/myapp/templates/myapp/group_list.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Group List</title>
+</head>
+<body>
+    <h1>Groups</h1>
+    {% if error %}
+      <p style="color: red;">{{ error }}</p>
+    {% endif %}
+    <ul>
+        {% for group in groups %}
+          <li>
+              {{ group.name }}
+              <a href="{% url 'group_update' group.id %}">Edit</a>
+              <a href="{% url 'group_delete' group.id %}">Delete</a>
+          </li>
+        {% empty %}
+          <li>No groups available.</li>
+        {% endfor %}
+    </ul>
+    <p><a href="{% url 'group_create' %}">Create New Group</a></p>
+</body>
+</html>
+
diff --git a/django_project/myapp/templates/myapp/group_update.html b/django_project/myapp/templates/myapp/group_update.html
new file mode 100644
index 0000000..1e9945a
--- /dev/null
+++ b/django_project/myapp/templates/myapp/group_update.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Update Group</title>
+</head>
+<body>
+    <h1>Update Group</h1>
+    {% if error %}
+      <p style="color: red;">{{ error }}</p>
+    {% endif %}
+    {% if group %}
+    <form method="post">
+        {% csrf_token %}
+        <label for="id_name">New Group Name:</label>
+        <input type="text" name="name" id="id_name" value="{{ group.name }}" required>
+        <br><br>
+        <button type="submit">Update Group</button>
+    </form>
+    {% else %}
+      <p>Group not found.</p>
+    {% endif %}
+    <p><a href="{% url 'group_list' %}">Back to Group List</a></p>
+</body>
+</html>
+
diff --git a/django_project/myapp/views.py b/django_project/myapp/views.py
index 24e97ca..5bf7b28 100644
--- a/django_project/myapp/views.py
+++ b/django_project/myapp/views.py
@@ -130,3 +130,89 @@ def logout_view(request):
     # Optionally, render a confirmation page or redirect to login.
     return render(request, 'myapp/logout.html')
 
+def group_list_view(request):
+    """
+    List all groups.
+    """
+    db_url = settings.DATABASE_URL
+    try:
+        groups = rust_crud_api.get_all_groups(db_url)
+    except Exception as e:
+        return render(request, 'myapp/group_list.html', {'error': str(e)})
+    return render(request, 'myapp/group_list.html', {'groups': groups})
+
+def group_create_view(request):
+    """
+    Create a new group.
+    """
+    db_url = settings.DATABASE_URL
+    context = {}
+    if request.method == 'POST':
+        group_name = request.POST.get('name', '').strip()
+        if not group_name:
+            context['error'] = "Group name is required."
+            return render(request, 'myapp/group_create.html', context)
+        try:
+            rust_crud_api.create_group(db_url, group_name)
+            return redirect('group_list')
+        except Exception as e:
+            context['error'] = f"An error occurred: {e}"
+            return render(request, 'myapp/group_create.html', context)
+    return render(request, 'myapp/group_create.html', context)
+
+def group_update_view(request, group_id):
+    """
+    Update an existing group's name.
+    """
+    db_url = settings.DATABASE_URL
+    context = {}
+    try:
+        group = rust_crud_api.get_group(db_url, int(group_id))
+        if group is None:
+            context['error'] = "Group not found."
+            return render(request, 'myapp/group_update.html', context)
+    except Exception as e:
+        context['error'] = f"Error retrieving group: {e}"
+        return render(request, 'myapp/group_update.html', context)
+    
+    if request.method == 'POST':
+        new_name = request.POST.get('name', '').strip()
+        if not new_name:
+            context['error'] = "New group name is required."
+            context['group'] = group
+            return render(request, 'myapp/group_update.html', context)
+        try:
+            # This assumes you've implemented an `update_group` function in your Rust library.
+            rust_crud_api.update_group(db_url, int(group_id), new_name)
+            return redirect('group_list')
+        except Exception as e:
+            context['error'] = f"Error updating group: {e}"
+            context['group'] = group
+            return render(request, 'myapp/group_update.html', context)
+    
+    context['group'] = group
+    return render(request, 'myapp/group_update.html', context)
+
+def group_delete_view(request, group_id):
+    """
+    Delete a group.
+    """
+    db_url = settings.DATABASE_URL
+    context = {}
+    if request.method == 'POST':
+        try:
+            # This assumes you have a `delete_group` function in your Rust library.
+            success = rust_crud_api.delete_group(db_url, int(group_id))
+            if success:
+                return redirect('group_list')
+            else:
+                context['error'] = "Deletion failed."
+        except Exception as e:
+            context['error'] = f"Error deleting group: {e}"
+    else:
+        try:
+            group = rust_crud_api.get_group(db_url, int(group_id))
+            context['group'] = group
+        except Exception as e:
+            context['error'] = f"Error retrieving group: {e}"
+    return render(request, 'myapp/group_delete.html', context)
diff --git a/rust_crud_api/src/auth/jwt.rs b/rust_crud_api/src/auth/jwt.rs
new file mode 100644
index 0000000..e6ce0c7
--- /dev/null
+++ b/rust_crud_api/src/auth/jwt.rs
@@ -0,0 +1,38 @@
+use pyo3::prelude::*;
+use pyo3::exceptions::PyRuntimeError;
+use jsonwebtoken::{encode, decode, Header, Validation, EncodingKey, DecodingKey};
+use serde::{Serialize, Deserialize};
+use pyo3::types::PyDict;
+
+#[derive(Debug, Serialize, Deserialize)]
+struct Claims {
+    sub: String,
+    exp: usize,
+}
+
+#[pyfunction]
+pub fn generate_jwt(user_email: String, secret: String, exp: usize) -> PyResult<String> {
+    let claims = Claims { sub: user_email, exp };
+    encode(&Header::default(), &claims, &EncodingKey::from_secret(secret.as_ref()))
+        .map_err(|e| PyRuntimeError::new_err(e.to_string()))
+}
+
+#[pyfunction]
+pub fn verify_jwt(token: String, secret: String) -> PyResult<PyObject> {
+    let token_data = decode::<Claims>(
+        &token,
+        &DecodingKey::from_secret(secret.as_ref()),
+        &Validation::default(),
+    ).map_err(|e| PyRuntimeError::new_err(e.to_string()))?;
+
+    let claims = token_data.claims; // Move claims inside the function scope
+
+    Python::with_gil(|py| {
+        let dict = PyDict::new(py);
+        dict.set_item("sub", claims.sub)?;
+        dict.set_item("exp", claims.exp)?;
+        Ok(dict.to_object(py))
+    })
+}
+
+
diff --git a/rust_crud_api/src/auth/mod.rs b/rust_crud_api/src/auth/mod.rs
new file mode 100644
index 0000000..da8716c
--- /dev/null
+++ b/rust_crud_api/src/auth/mod.rs
@@ -0,0 +1,6 @@
+pub mod jwt;
+pub mod password;
+
+pub use jwt::{generate_jwt, verify_jwt};
+pub use password::{hash_password, verify_password};
+
diff --git a/rust_crud_api/src/auth/password.rs b/rust_crud_api/src/auth/password.rs
new file mode 100644
index 0000000..01a94e0
--- /dev/null
+++ b/rust_crud_api/src/auth/password.rs
@@ -0,0 +1,25 @@
+use pyo3::prelude::*;
+use pyo3::exceptions::PyRuntimeError;
+use argon2::{Argon2, PasswordHasher, PasswordVerifier};
+use argon2::password_hash::{SaltString, PasswordHash, rand_core::OsRng};
+
+#[pyfunction]
+pub fn hash_password(password: &str) -> PyResult<String> {
+    let salt = SaltString::generate(&mut OsRng);
+    let argon2 = Argon2::default();
+    let password_hash = argon2.hash_password(password.as_bytes(), &salt)
+        .map_err(|e| PyRuntimeError::new_err(e.to_string()))?
+        .to_string();
+    Ok(password_hash)
+}
+
+#[pyfunction]
+pub fn verify_password(hash: &str, password: &str) -> PyResult<bool> {
+    let argon2 = Argon2::default();
+    let parsed_hash = PasswordHash::new(hash)
+        .map_err(|e| PyRuntimeError::new_err(e.to_string()))?;
+    argon2.verify_password(password.as_bytes(), &parsed_hash)
+        .map_err(|e| PyRuntimeError::new_err(e.to_string()))
+        .map(|_| true)
+}
+
diff --git a/rust_crud_api/src/db/groups.rs b/rust_crud_api/src/db/groups.rs
new file mode 100644
index 0000000..deea586
--- /dev/null
+++ b/rust_crud_api/src/db/groups.rs
@@ -0,0 +1,98 @@
+use crate::models::Group;
+use pyo3::prelude::*;
+use pyo3::exceptions::PyRuntimeError;
+use postgres::{Client, NoTls};
+use crate::models::User;
+
+fn pg_err(e: postgres::Error) -> PyErr {
+    PyRuntimeError::new_err(e.to_string())
+}
+
+/// Create a group
+#[pyfunction]
+pub fn create_group(db_url: &str, name: &str) -> PyResult<()> {
+    let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?;
+    client.execute(
+        "INSERT INTO groups (name) VALUES ($1)",
+        &[&name]
+    ).map_err(pg_err)?;
+    Ok(())
+}
+
+/// Retrieve a group by ID.
+#[pyfunction]
+pub fn get_group(db_url: &str, group_id: i32) -> PyResult<Option<Group>> {
+    let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?;
+    let row_opt = client.query_opt(
+        "SELECT id, name FROM groups WHERE id = $1",
+        &[&group_id]
+    ).map_err(pg_err)?;
+    
+    if let Some(row) = row_opt {
+        let group = Group {
+            id: row.get(0),
+            name: row.get(1),
+        };
+        Ok(Some(group))
+    } else {
+        Ok(None)
+    }
+}
+
+/// Retrieve all groups.
+#[pyfunction]
+pub fn get_all_groups(db_url: &str) -> PyResult<Vec<Group>> {
+    let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?;
+    let rows = client.query("SELECT id, name FROM groups", &[])
+        .map_err(pg_err)?;
+    
+    let groups = rows.into_iter().map(|row| Group {
+        id: row.get(0),
+        name: row.get(1),
+    }).collect();
+    
+    Ok(groups)
+}
+
+/// Add a user to a group.
+#[pyfunction]
+pub fn add_user_to_group(db_url: &str, group_id: i32, user_id: i32) -> PyResult<()> {
+    let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?;
+    client.execute(
+        "INSERT INTO group_members (group_id, user_id) VALUES ($1, $2)",
+        &[&group_id, &user_id]
+    ).map_err(pg_err)?;
+    Ok(())
+}
+
+/// Get all members of a group.
+#[pyfunction]
+pub fn get_group_members(db_url: &str, group_id: i32) -> PyResult<Vec<User>> {
+    let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?;
+    let rows = client.query(
+        "SELECT u.id, u.name, u.email
+         FROM users u
+         JOIN group_members gm ON u.id = gm.user_id
+         WHERE gm.group_id = $1",
+         &[&group_id]
+    ).map_err(pg_err)?;
+    
+    let users = rows.into_iter().map(|row| User {
+        id: row.get(0),
+        name: row.get(1),
+        email: row.get(2),
+    }).collect();
+    
+    Ok(users)
+}
+
+/// Group Delete Function
+#[pyfunction]
+pub fn delete_group(db_url: &str, group_id: i32) -> PyResult<bool> {
+    let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?;
+    let deleted = client.execute(
+        "DELETE FROM groups WHERE group_id = $1",
+        &[&group_id]
+    ).map_err(pg_err)?;
+    Ok(deleted > 0)
+}
diff --git a/rust_crud_api/src/db/init.rs b/rust_crud_api/src/db/init.rs
new file mode 100644
index 0000000..e0ecf0e
--- /dev/null
+++ b/rust_crud_api/src/db/init.rs
@@ -0,0 +1,33 @@
+use postgres::{Client, NoTls};
+use pyo3::prelude::*;
+use pyo3::exceptions::PyRuntimeError;
+
+fn pg_err(e: postgres::Error) -> PyErr {
+    PyRuntimeError::new_err(e.to_string())
+}
+
+#[pyfunction]
+pub fn init_db(db_url: &str) -> PyResult<()> {
+    let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?;
+    client.batch_execute(
+        "
+        CREATE TABLE IF NOT EXISTS users (
+            id SERIAL PRIMARY KEY,
+            name VARCHAR NOT NULL,
+            email VARCHAR NOT NULL UNIQUE,
+            password_hash VARCHAR NOT NULL
+        );
+        CREATE TABLE IF NOT EXISTS groups (
+            id SERIAL PRIMARY KEY,
+            name VARCHAR NOT NULL
+        );
+        CREATE TABLE IF NOT EXISTS group_members (
+            group_id INTEGER REFERENCES groups(id),
+            user_id INTEGER REFERENCES users(id),
+            PRIMARY KEY (group_id, user_id)
+        );
+        "
+    ).map_err(pg_err)?;
+    Ok(())
+}
+
diff --git a/rust_crud_api/src/db/mod.rs b/rust_crud_api/src/db/mod.rs
new file mode 100644
index 0000000..3ed49ca
--- /dev/null
+++ b/rust_crud_api/src/db/mod.rs
@@ -0,0 +1,8 @@
+pub mod users;
+pub mod groups;
+pub mod init;
+
+pub use init::init_db;
+pub use users::{create_user, get_user, get_all_users, update_user, delete_user};
+pub use groups::{create_group, get_group, get_all_groups, add_user_to_group, get_group_members, delete_group};
+
diff --git a/rust_crud_api/src/db/users.rs b/rust_crud_api/src/db/users.rs
new file mode 100644
index 0000000..3549b00
--- /dev/null
+++ b/rust_crud_api/src/db/users.rs
@@ -0,0 +1,105 @@
+use crate::models::User;
+use pyo3::prelude::*;
+use pyo3::exceptions::PyRuntimeError;
+use postgres::{Client, NoTls};
+use crate::auth::hash_password;
+use crate::auth::verify_password;
+
+fn pg_err(e: postgres::Error) -> PyErr {
+    PyRuntimeError::new_err(e.to_string())
+}
+
+
+/// Create a new user by inserting into the database.
+#[pyfunction]
+pub fn create_user(db_url: &str, name: &str, email: &str, password: &str) -> PyResult<()> {
+    let password_hash = hash_password(password)?;
+    let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?;
+    client.execute(
+        "INSERT INTO users (name, email, password_hash) VALUES ($1, $2, $3)",
+        &[&name, &email, &password_hash]
+    ).map_err(pg_err)?;
+    Ok(())
+}
+
+/// Retrieve a user by ID. Returns None if the user is not found.
+#[pyfunction]
+pub fn get_user(db_url: &str, user_id: i32) -> PyResult<Option<User>> {
+    let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?;
+    let row_opt = client.query_opt(
+        "SELECT id, name, email FROM users WHERE id = $1",
+        &[&user_id]
+    ).map_err(pg_err)?;
+    
+    if let Some(row) = row_opt {
+        let user = User {
+            id: row.get(0),
+            name: row.get(1),
+            email: row.get(2),
+        };
+        Ok(Some(user))
+    } else {
+        Ok(None)
+    }
+}
+
+/// Retrieve all users from the database.
+#[pyfunction]
+pub fn get_all_users(db_url: &str) -> PyResult<Vec<User>> {
+    let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?;
+    let rows = client.query("SELECT id, name, email FROM users", &[])
+        .map_err(pg_err)?;
+    
+    let mut users = Vec::new();
+    for row in rows {
+        users.push(User {
+            id: row.get(0),
+            name: row.get(1),
+            email: row.get(2),
+        });
+    }
+    Ok(users)
+}
+
+/// Update an existing user with a new name and email.
+#[pyfunction]
+pub fn update_user(db_url: &str, user_id: i32, new_name: &str, new_email: &str) -> PyResult<()> {
+    let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?;
+    let updated = client.execute(
+        "UPDATE users SET name = $1, email = $2 WHERE id = $3",
+        &[&new_name, &new_email, &user_id]
+    ).map_err(pg_err)?;
+    if updated == 0 {
+        Err(PyRuntimeError::new_err("User not found"))
+    } else {
+        Ok(())
+    }
+}
+
+/// Delete a user by ID. Returns true if a user was deleted.
+#[pyfunction]
+pub fn delete_user(db_url: &str, user_id: i32) -> PyResult<bool> {
+    let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?;
+    let deleted = client.execute(
+        "DELETE FROM users WHERE id = $1",
+        &[&user_id]
+    ).map_err(pg_err)?;
+    Ok(deleted > 0)
+}
+
+///Verify credentials
+///Retrieve a stored password hash for the email and compare.
+#[pyfunction]
+pub fn verify_user(db_url: &str, email: &str, password: & str) -> PyResult<bool> {
+    let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?;
+    let row_opt = client.query_opt(
+        "SELECT password_hash FROM users WHERE email = $1",
+        &[&email]
+        ).map_err(pg_err)?;
+        if let Some(row) = row_opt {
+            let stored_hash: String = row.get(0);
+            verify_password(&stored_hash, password)
+            } else {
+                Ok(false)
+        }
+}
diff --git a/rust_crud_api/src/lib.rs b/rust_crud_api/src/lib.rs
index bc4f7b4..b8885a9 100644
--- a/rust_crud_api/src/lib.rs
+++ b/rust_crud_api/src/lib.rs
@@ -1,329 +1,35 @@
-/// lib.rs
+mod models;
+mod auth;
+mod db;
 
 use pyo3::prelude::*;
-use pyo3::exceptions::PyRuntimeError;
-use postgres::{Client, NoTls};
-use pyo3::types::PyDict;
-use serde::{Serialize, Deserialize};  /// Import derive macros from serde_derive
-use jsonwebtoken::{encode, decode, Header, Validation, EncodingKey, DecodingKey};
 
-
-/// Define our User model, which will be exposed to Python.
-#[pyclass]
-#[derive(Serialize, Deserialize, Debug)]
-struct User {
-    #[pyo3(get, set)]
-    id: Option<i32>,
-    #[pyo3(get, set)]
-    name: String,
-    #[pyo3(get, set)]
-    email: String,
-}
-/// Group Model
-#[pyclass]
-#[derive(Serialize, Deserialize, Debug)]
-struct Group {
-    #[pyo3(get, set)]
-    id: Option<i32>,
-    #[pyo3(get, set)]
-    name: String,
-}
-
-//JWT Claims for Token Payload
-
-#[derive(Debug,Serialize,Deserialize)]
-struct Claims {
-    sub: String, // Subject 
-    exp: usize, // Expiration time
-}
-
-/// Generate a JWT token for a given user.
-/// Returns the token as a String.
-#[pyfunction]
-fn generate_jwt(user_email: String, secret: String, exp: usize) -> PyResult<String> {
-    let claims = Claims { sub: user_email, exp };
-    encode(&Header::default(), &claims, &EncodingKey::from_secret(secret.as_ref()))
-        .map_err(|e| PyRuntimeError::new_err(e.to_string()))
-}
-
-/// Verify a JWT token and return its claims as a Python dictionary.
-#[pyfunction]
-fn verify_jwt(token: String, secret: String) -> PyResult<PyObject> {
-    let gil = Python::acquire_gil();
-    let py = gil.python();
-    
-    let token_data = decode::<Claims>(
-        &token,
-        &DecodingKey::from_secret(secret.as_ref()),
-        &Validation::default()
-    ).map_err(|e| PyRuntimeError::new_err(e.to_string()))?;
-    
-    // Extract the claims.
-    let claims = token_data.claims;
-    // Create a Python dictionary and insert the claim fields.
-    let dict = PyDict::new(py);
-    dict.set_item("sub", claims.sub)?;
-    dict.set_item("exp", claims.exp)?;
-    Ok(dict.to_object(py))
-}
-
-// Pasword Hashing with Argon2
-
-#[pyfunction]
-fn hash_password(password: &str) -> PyResult<String> {
-    use argon2::{Argon2, PasswordHasher};
-    use argon2::password_hash::{SaltString, rand_core::OsRng};
-    
-    // Generate a random salt using OsRng.
-    let salt = SaltString::generate(&mut OsRng);
-    // Create a default Argon2 instance.
-    let argon2 = Argon2::default();
-    // Hash the password using the salt.
-    let password_hash = argon2.hash_password(password.as_bytes(), &salt)
-        .map_err(|e| PyRuntimeError::new_err(e.to_string()))?
-        .to_string();
-    Ok(password_hash)
-}
-
-#[pyfunction]
-fn verify_password(hash: &str, password: &str) -> PyResult<bool> {
-    use argon2::{Argon2, PasswordVerifier};
-    use argon2::password_hash::PasswordHash;
-    
-    let argon2 = Argon2::default();
-    let parsed_hash = PasswordHash::new(hash)
-        .map_err(|e| PyRuntimeError::new_err(e.to_string()))?;
-    argon2.verify_password(password.as_bytes(), &parsed_hash)
-        .map_err(|e| PyRuntimeError::new_err(e.to_string()))
-        .map(|_| true)
-}
-
-/// A helper function to convert postgres::Error into a Python RuntimeError.
-fn pg_err(e: postgres::Error) -> PyErr {
-    PyRuntimeError::new_err(e.to_string())
-}
-
-/// Initialize the database by creating the users table if it does not exist.
-#[pyfunction]
-fn init_db(db_url: &str) -> PyResult<()> {
-    let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?;
-    client.batch_execute(
-        "
-        CREATE TABLE IF NOT EXISTS users (
-            id SERIAL PRIMARY KEY,
-            name VARCHAR NOT NULL,
-            email VARCHAR NOT NULL UNIQUE,
-            password_hash VARCHAR NOT NULL
-        );
-        CREATE TABLE IF NOT EXISTS groups (
-            id SERIAL PRIMARY KEY,
-            name VARCHAR NOT NULL
-        );
-        CREATE TABLE IF NOT EXISTS group_members (
-            group_id INTEGER REFERENCES groups(id),
-            user_id INTEGER REFERENCES users(id),
-            PRIMARY KEY (group_id, user_id)
-        );
-        "
-    ).map_err(pg_err)?;
-    Ok(())
-}
-
-/// Create a new user by inserting into the database.
-#[pyfunction]
-fn create_user(db_url: &str, name: &str, email: &str, password: &str) -> PyResult<()> {
-    let password_hash = hash_password(password)?;
-    let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?;
-    client.execute(
-        "INSERT INTO users (name, email, password_hash) VALUES ($1, $2, $3)",
-        &[&name, &email, &password_hash]
-    ).map_err(pg_err)?;
-    Ok(())
-}
-
-/// Retrieve a user by ID. Returns None if the user is not found.
-#[pyfunction]
-fn get_user(db_url: &str, user_id: i32) -> PyResult<Option<User>> {
-    let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?;
-    let row_opt = client.query_opt(
-        "SELECT id, name, email FROM users WHERE id = $1",
-        &[&user_id]
-    ).map_err(pg_err)?;
-    
-    if let Some(row) = row_opt {
-        let user = User {
-            id: row.get(0),
-            name: row.get(1),
-            email: row.get(2),
-        };
-        Ok(Some(user))
-    } else {
-        Ok(None)
-    }
-}
-
-/// Retrieve all users from the database.
-#[pyfunction]
-fn get_all_users(db_url: &str) -> PyResult<Vec<User>> {
-    let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?;
-    let rows = client.query("SELECT id, name, email FROM users", &[])
-        .map_err(pg_err)?;
-    
-    let mut users = Vec::new();
-    for row in rows {
-        users.push(User {
-            id: row.get(0),
-            name: row.get(1),
-            email: row.get(2),
-        });
-    }
-    Ok(users)
-}
-
-/// Update an existing user with a new name and email.
-#[pyfunction]
-fn update_user(db_url: &str, user_id: i32, new_name: &str, new_email: &str) -> PyResult<()> {
-    let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?;
-    let updated = client.execute(
-        "UPDATE users SET name = $1, email = $2 WHERE id = $3",
-        &[&new_name, &new_email, &user_id]
-    ).map_err(pg_err)?;
-    if updated == 0 {
-        Err(PyRuntimeError::new_err("User not found"))
-    } else {
-        Ok(())
-    }
-}
-
-/// Delete a user by ID. Returns true if a user was deleted.
-#[pyfunction]
-fn delete_user(db_url: &str, user_id: i32) -> PyResult<bool> {
-    let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?;
-    let deleted = client.execute(
-        "DELETE FROM users WHERE id = $1",
-        &[&user_id]
-    ).map_err(pg_err)?;
-    Ok(deleted > 0)
-}
-
-///Verify credentials
-///Retrieve a stored password hash for the email and compare.
-#[pyfunction]
-fn verify_user(db_url: &str, email: &str, password: & str) -> PyResult<bool> {
-    let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?;
-    let row_opt = client.query_opt(
-        "SELECT password_hash FROM users WHERE email = $1",
-        &[&email]
-        ).map_err(pg_err)?;
-        if let Some(row) = row_opt {
-            let stored_hash: String = row.get(0);
-            verify_password(&stored_hash, password)
-            } else {
-                Ok(false)
-        }
-}
-
-
-/// Create a group
-#[pyfunction]
-fn create_group(db_url: &str, name: &str) -> PyResult<()> {
-    let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?;
-    client.execute(
-        "INSERT INTO groups (name) VALUES ($1)",
-        &[&name]
-    ).map_err(pg_err)?;
-    Ok(())
-}
-
-/// Retrieve a group by ID.
-#[pyfunction]
-fn get_group(db_url: &str, group_id: i32) -> PyResult<Option<Group>> {
-    let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?;
-    let row_opt = client.query_opt(
-        "SELECT id, name FROM groups WHERE id = $1",
-        &[&group_id]
-    ).map_err(pg_err)?;
-    
-    if let Some(row) = row_opt {
-        let group = Group {
-            id: row.get(0),
-            name: row.get(1),
-        };
-        Ok(Some(group))
-    } else {
-        Ok(None)
-    }
-}
-
-/// Retrieve all groups.
-#[pyfunction]
-fn get_all_groups(db_url: &str) -> PyResult<Vec<Group>> {
-    let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?;
-    let rows = client.query("SELECT id, name FROM groups", &[])
-        .map_err(pg_err)?;
-    
-    let groups = rows.into_iter().map(|row| Group {
-        id: row.get(0),
-        name: row.get(1),
-    }).collect();
-    
-    Ok(groups)
-}
-
-/// Add a user to a group.
-#[pyfunction]
-fn add_user_to_group(db_url: &str, group_id: i32, user_id: i32) -> PyResult<()> {
-    let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?;
-    client.execute(
-        "INSERT INTO group_members (group_id, user_id) VALUES ($1, $2)",
-        &[&group_id, &user_id]
-    ).map_err(pg_err)?;
-    Ok(())
-}
-
-/// Get all members of a group.
-#[pyfunction]
-fn get_group_members(db_url: &str, group_id: i32) -> PyResult<Vec<User>> {
-    let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?;
-    let rows = client.query(
-        "SELECT u.id, u.name, u.email
-         FROM users u
-         JOIN group_members gm ON u.id = gm.user_id
-         WHERE gm.group_id = $1",
-         &[&group_id]
-    ).map_err(pg_err)?;
-    
-    let users = rows.into_iter().map(|row| User {
-        id: row.get(0),
-        name: row.get(1),
-        email: row.get(2),
-    }).collect();
-    
-    Ok(users)
-}
-
-
-/// This is the Python module initializer. The name "rust_crud_api" here should match the name used in your Cargo.toml.
 #[pymodule]
 fn rust_crud_api(_py: Python, m: &PyModule) -> PyResult<()> {
-    m.add_class::<User>()?;
-    m.add_class::<Group>()?;
-    m.add_function(wrap_pyfunction!(init_db, m)?)?;
-    m.add_function(wrap_pyfunction!(create_user, m)?)?;
-    m.add_function(wrap_pyfunction!(get_user, m)?)?;
-    m.add_function(wrap_pyfunction!(get_all_users, m)?)?;
-    m.add_function(wrap_pyfunction!(update_user, m)?)?;
-    m.add_function(wrap_pyfunction!(delete_user, m)?)?;
-    m.add_function(wrap_pyfunction!(create_group, m)?)?;
-    m.add_function(wrap_pyfunction!(get_group, m)?)?;
-    m.add_function(wrap_pyfunction!(get_all_groups, m)?)?;
-    m.add_function(wrap_pyfunction!(add_user_to_group, m)?)?;
-    m.add_function(wrap_pyfunction!(get_group_members, m)?)?;
-    m.add_function(wrap_pyfunction!(generate_jwt, m)?)?;
-    m.add_function(wrap_pyfunction!(verify_jwt, m)?)?;
-    m.add_function(wrap_pyfunction!(hash_password, m)?)?;
-    m.add_function(wrap_pyfunction!(verify_password, m)?)?;
-    m.add_function(wrap_pyfunction!(verify_user, m)?)?;
+    m.add_class::<models::User>()?;
+    m.add_class::<models::Group>()?;
+
+    // Authentication functions
+    m.add_function(wrap_pyfunction!(auth::generate_jwt, m)?)?;
+    m.add_function(wrap_pyfunction!(auth::verify_jwt, m)?)?;
+    m.add_function(wrap_pyfunction!(auth::hash_password, m)?)?;
+    m.add_function(wrap_pyfunction!(auth::verify_password, m)?)?;
+
+    // Database functions
+    m.add_function(wrap_pyfunction!(db::init::init_db, m)?)?;
+    m.add_function(wrap_pyfunction!(db::users::create_user, m)?)?;
+    m.add_function(wrap_pyfunction!(db::users::get_user, m)?)?;
+    m.add_function(wrap_pyfunction!(db::users::get_all_users, m)?)?;
+    m.add_function(wrap_pyfunction!(db::users::update_user, m)?)?;
+    m.add_function(wrap_pyfunction!(db::users::delete_user, m)?)?;
+
+    m.add_function(wrap_pyfunction!(db::groups::create_group, m)?)?;
+    m.add_function(wrap_pyfunction!(db::groups::get_group, m)?)?;
+    m.add_function(wrap_pyfunction!(db::groups::get_all_groups, m)?)?;
+    m.add_function(wrap_pyfunction!(db::groups::add_user_to_group, m)?)?;
+    m.add_function(wrap_pyfunction!(db::groups::get_group_members, m)?)?;
+    m.add_function(wrap_pyfunction!(db::groups::delete_group, m)?)?;
+
     Ok(())
 }
 
diff --git a/rust_crud_api/src/models/group.rs b/rust_crud_api/src/models/group.rs
new file mode 100644
index 0000000..ab2ebe3
--- /dev/null
+++ b/rust_crud_api/src/models/group.rs
@@ -0,0 +1,12 @@
+use pyo3::prelude::*;
+use serde::{Serialize, Deserialize};
+
+#[pyclass]
+#[derive(Serialize, Deserialize, Debug)]
+pub struct Group {
+    #[pyo3(get, set)]
+    pub id: Option<i32>,
+    #[pyo3(get, set)]
+    pub name: String,
+}
+
diff --git a/rust_crud_api/src/models/mod.rs b/rust_crud_api/src/models/mod.rs
new file mode 100644
index 0000000..e2723a9
--- /dev/null
+++ b/rust_crud_api/src/models/mod.rs
@@ -0,0 +1,6 @@
+pub mod user;
+pub mod group;
+
+pub use user::User;
+pub use group::Group;
+
diff --git a/rust_crud_api/src/models/user.rs b/rust_crud_api/src/models/user.rs
new file mode 100644
index 0000000..042512c
--- /dev/null
+++ b/rust_crud_api/src/models/user.rs
@@ -0,0 +1,14 @@
+use pyo3::prelude::*;
+use serde::{Serialize, Deserialize};
+
+#[pyclass]
+#[derive(Serialize, Deserialize, Debug)]
+pub struct User {
+    #[pyo3(get, set)]
+    pub id: Option<i32>,
+    #[pyo3(get, set)]
+    pub name: String,
+    #[pyo3(get, set)]
+    pub email: String,
+}
+
-- 
GitLab