diff --git a/.DS_Store b/.DS_Store index 2914ecce4bd8e8af33e2fa9741bc2e8b49933c6b..d641b36b43ddf62d1a438754e703c2cc6e9bef70 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/django_project/django_project/settings.py b/django_project/django_project/settings.py index a189ffa6f201b4d076bfadf80817b19c329ad1c6..b76dde59c48a1b3a2fb233f295f26d9827172fbe 100644 --- a/django_project/django_project/settings.py +++ b/django_project/django_project/settings.py @@ -17,6 +17,8 @@ BASE_DIR = Path(__file__).resolve().parent.parent DATABASE_URL = os.environ.get('DATABASE_URL', 'postgres://postgres:postgres@localhost:5432/postgres') +MEDIA_URL = '/media/' +MEDIA_ROOT = '/app/media' # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/ diff --git a/django_project/django_project/urls.py b/django_project/django_project/urls.py index 9fd572e0f973ad31be58a7453ca092289691c9f6..5702f0f11a63a9edfdf6d78296591165ff802c65 100644 --- a/django_project/django_project/urls.py +++ b/django_project/django_project/urls.py @@ -17,6 +17,8 @@ Including another URLconf from django.contrib import admin from django.urls import path, include from myapp import views +from django.conf import settings +from django.conf.urls.static import static urlpatterns = [ path('admin/', admin.site.urls), @@ -33,3 +35,6 @@ urlpatterns = [ path('post/create/', views.create_post_view, name='create_post'), path('feed/', views.feed_view, name='feed'), ] + +if settings.DEBUG: + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/django_project/myapp/templates/myapp/account.html b/django_project/myapp/templates/myapp/account.html index aab98147dea2c8fe93ee2db170b032e35ef52b90..e741f80d43504b0a68717f3599038abd48be5b90 100644 --- a/django_project/myapp/templates/myapp/account.html +++ b/django_project/myapp/templates/myapp/account.html @@ -7,6 +7,11 @@ <p>Username: {{ user.username }}</p> <p>Email: {{ user.email }}</p> <p>StudentId: {{user.studentid}}</p> + {% if user.profilepicture %} + <img src="{{ user.profilepicture }}" alt="Profile Picture"> + {% else %} + <p>No profile picture</p> + {% endif %} <p><a href="{% url 'delete_account' %}">Delete Account</a></p> <p><a href="{% url 'logout' %}">Logout</a></p> {% endblock %} diff --git a/django_project/myapp/templates/myapp/register.html b/django_project/myapp/templates/myapp/register.html index e9501296f9ca45b4ca09bebea03131f44e81ad1d..26f5e4e4eef38d3639ea19921b007cb1c5ccceac 100644 --- a/django_project/myapp/templates/myapp/register.html +++ b/django_project/myapp/templates/myapp/register.html @@ -13,7 +13,7 @@ {% if error %} <p class="error">{{ error }}</p> {% endif %} - <form method="post"> + <form method="POST" enctype="multipart/form-data"> {% csrf_token %} <label for="id_name">Name:</label> <input type="text" name="name" id="id_name" required><br><br> @@ -21,7 +21,7 @@ <label for="id_username">Username:</label> <input type="text" name="username" id="id_username" required><br><br> - <label for="id_email">Email:</label> + <label for="id_email">Email:</label> <input type="email" name="email" id="id_email" required><br><br> <label for="id_password">Password:</label> @@ -36,6 +36,9 @@ <label for="id_endyear">Ending Year:</label> <input type="endyear" name="endyear" id="id_endyear" required><br><br> + <label>Profile Picture:</label> + <input type="file" name="profilepicture"> + <button type="submit">Register</button> </form> <p> diff --git a/django_project/myapp/views.py b/django_project/myapp/views.py index 5fdb30c990431c898d37ff381c9f02db5024429a..a3353c6643c6b4e9e9a601b51216c5a8486eb953 100644 --- a/django_project/myapp/views.py +++ b/django_project/myapp/views.py @@ -8,6 +8,8 @@ import rust_crud_api # Custom Rust Pyo3 Library 🦀 # Dont delete this db_url = settings.DATABASE_URL + + def init_db_view(request): db_url = os.environ.get("DATABASE_URL") try: @@ -18,57 +20,97 @@ def init_db_view(request): def register_view(request): """ - Handles user registration. Expects POST with - 'name', 'email', 'password', 'username', and 'studentid'. + Handles user registration. Expects POST with + 'name', 'email', 'password', 'username', 'studentid', 'startyear', 'endyear', + and optionally 'profilepicture'. """ - db_url = settings.DATABASE_URL # Defined in settings.py + db_url = settings.DATABASE_URL # Make sure DATABASE_URL is defined in your Django settings context = {} - + if request.method == 'POST': name = request.POST.get('name', '').strip() email = request.POST.get('email', '').strip() password = request.POST.get('password', '').strip() username = request.POST.get('username', '').strip() studentid_str = request.POST.get('studentid', '').strip() - startyear = request.POST.get('startyear', '').strip() - endyear = request.POST.get('endyear', '').strip() + startyear_str = request.POST.get('startyear', '').strip() + endyear_str = request.POST.get('endyear', '').strip() + # 1. Check required fields - if not all([name, email, password, username, studentid_str, startyear, endyear]): - context['error'] = "Name, email, username, student ID, and password are required." + if not all([name, email, password, username, studentid_str, startyear_str, endyear_str]): + context['error'] = ( + "Name, email, username, student ID, start year, and end year are required." + ) return render(request, 'myapp/register.html', context) - # 2. Try converting studentid to int + # 2. Try converting studentid, startyear, endyear to int try: studentid = int(studentid_str) except ValueError: - context['error'] = "Student ID must be an number." + context['error'] = "Student ID must be a number." return render(request, 'myapp/register.html', context) - + try: - startyear = int(startyear) + startyear = int(startyear_str) except ValueError: - context['error'] = "Start year must be an number." + context['error'] = "Start year must be a number." return render(request, 'myapp/register.html', context) - + try: - endyear = int(endyear) + endyear = int(endyear_str) except ValueError: - context['error'] = "End year must be an number." + context['error'] = "End year must be a number." return render(request, 'myapp/register.html', context) - # 3. If the parse succeeded, call create_user in Rust + + # 3. Handle profile picture upload (if any) + profilepicture_file = request.FILES.get('profilepicture') + saved_path = None # Default to None if no file is uploaded + + if profilepicture_file: + # Make sure your form <input> matches name="profilepicture" + filename = profilepicture_file.name + + # Decide a directory inside MEDIA_ROOT for profile pics + profile_dir = os.path.join(settings.MEDIA_ROOT, 'profile_pics') + os.makedirs(profile_dir, exist_ok=True) # Ensure the folder exists + + # Build the full filesystem path where we’ll store the file + save_path = os.path.join(profile_dir, filename) + + # Write the file to disk in chunks + with open(save_path, 'wb+') as destination: + for chunk in profilepicture_file.chunks(): + destination.write(chunk) + + # The path we'll store in the DB (so we can serve it at /media/profile_pics/<filename>) + saved_path = f"/media/profile_pics/{filename}" + + # Debug prints to confirm we have the file and path + print("DEBUG: profilepicture_file is:", profilepicture_file) + print("DEBUG: saved_path is:", saved_path) + + # 4. Call Rust to create the user try: - rust_crud_api.create_user(db_url, name, email, password, username, studentid, startyear, endyear) - # 4. Redirect on success - return redirect('') + rust_crud_api.create_user( + db_url, + name, + email, + password, + username, + studentid, + startyear, + endyear, + saved_path + ) + # 5. After successful registration, redirect to login or some other page + return redirect('login') except Exception as e: context['error'] = f"An error occurred: {str(e)}" return render(request, 'myapp/register.html', context) - # If GET or anything else, just render the registration page. + # If GET or anything else, just render the registration form return render(request, 'myapp/register.html', context) - - def login_view(request): """ Handles user login by verifying credentials using the Rust extension. @@ -105,7 +147,7 @@ def login_view(request): request.session['user_name'] = user.name request.session['user_email'] = user.email - return redirect('account') + return redirect('feed') except Exception as e: context['error'] = f"An error occurred: {str(e)}" return render(request, 'myapp/login.html', context) @@ -259,7 +301,7 @@ def group_detail_view(request, group_id): context['error'] = f"An error occurred: {e}" return render(request, 'myapp/group_detail.html', context) -def account_view(request): +'''def account_view(request): """ Display the account page with user posts. """ @@ -278,6 +320,7 @@ def account_view(request): return render(request, 'myapp/account.html', {'error': str(e)}) return render(request, 'myapp/account.html', {'user': user, 'posts': posts}) +''' def create_post_view(request): """ @@ -325,3 +368,5 @@ def feed_view(request): return render(request, 'myapp/feed.html', {'error': str(e)}) return render(request, 'myapp/feed.html', {'posts': posts}) + + diff --git a/docker-compose.yml b/docker-compose.yml index c54a5b7cb4a92fa2ee5b1cb5a9183013c95da3f5..98072c1ae5f07639b790c65512568563e5d8aaed 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,6 +23,8 @@ services: depends_on: db: condition: service_healthy + volumes: + - ./media:/app/media ports: - "8000:8000" environment: diff --git a/rust_crud_api/src/db/groups.rs b/rust_crud_api/src/db/groups.rs index c6d5d7713a422f4f70bafeeddc7c6550deaa4f91..6b0c264a2d8d5095ed37972d1515c44292c51ed0 100644 --- a/rust_crud_api/src/db/groups.rs +++ b/rust_crud_api/src/db/groups.rs @@ -85,6 +85,7 @@ pub fn get_group_members(db_url: &str, group_id: i32) -> PyResult<Vec<User>> { studentid: row.get(4), startyear: row.get(5), endyear: row.get(6), + profilepicture: row.get(7) }).collect(); Ok(users) diff --git a/rust_crud_api/src/db/init.rs b/rust_crud_api/src/db/init.rs index 73abf8369720de3ead2891a5fd5a40819e1f9114..aa85fa75af34fc6b938f17d38c49aaf0dc5e23d6 100644 --- a/rust_crud_api/src/db/init.rs +++ b/rust_crud_api/src/db/init.rs @@ -19,7 +19,8 @@ pub fn init_db(db_url: &str) -> PyResult<()> { username VARCHAR NOT NULL, studentid INT NOT NULL UNIQUE, startyear INT NOT NULL, - endyear INT NOT NULL + endyear INT NOT NULL, + profilepicture VARCHAR ); CREATE TABLE IF NOT EXISTS groups ( id SERIAL PRIMARY KEY, diff --git a/rust_crud_api/src/db/users.rs b/rust_crud_api/src/db/users.rs index edb56a483c050d0b30c2ab7e7c403756df210311..79cf3467b02afc7bd3c502f80a7fece5cef961e6 100644 --- a/rust_crud_api/src/db/users.rs +++ b/rust_crud_api/src/db/users.rs @@ -11,13 +11,13 @@ fn pg_err(e: postgres::Error) -> PyErr { #[pyfunction] pub fn create_user( - db_url: &str, name: &str, email: &str, password: &str, username: &str, studentid: i32, startyear: i32, endyear: i32) -> PyResult<()> { + db_url: &str, name: &str, email: &str, password: &str, username: &str, studentid: i32, startyear: i32, endyear: i32, profilepicture: Option<&str>,) -> PyResult<()> { let password_hash = hash_password(password)?; let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?; let result = client.execute( - "INSERT INTO users (name, email, password_hash, username, studentid, startyear, endyear) - VALUES ($1, $2, $3, $4, $5, $6, $7)", - &[&name, &email, &password_hash, &username, &studentid, &startyear, &endyear], + "INSERT INTO users (name, email, password_hash, username, studentid, startyear, endyear, profilepicture) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", + &[&name, &email, &password_hash, &username, &studentid, &startyear, &endyear, &profilepicture], ); match result { @@ -42,7 +42,7 @@ pub fn create_user( 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, username, studentid, startyear, endyear FROM users WHERE id = $1", + "SELECT id, name, email, username, studentid, startyear, endyear, profilepicture FROM users WHERE id = $1", &[&user_id] ).map_err(pg_err)?; @@ -55,7 +55,8 @@ pub fn get_user(db_url: &str, user_id: i32) -> PyResult<Option<User>> { studentid: row.get(4), startyear: row.get(5), endyear: row.get(6), - + profilepicture: row.get(7), + }; Ok(Some(user)) } else { @@ -67,7 +68,7 @@ pub fn get_user(db_url: &str, user_id: i32) -> PyResult<Option<User>> { #[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, username, studentid, startyear, endyear FROM users", &[]) + let rows = client.query("SELECT id, name, email, username, studentid, startyear, endyear, profilepicture FROM users", &[]) .map_err(pg_err)?; let mut users = Vec::new(); @@ -80,6 +81,7 @@ pub fn get_all_users(db_url: &str) -> PyResult<Vec<User>> { studentid: row.get(4), startyear: row.get(5), endyear: row.get(6), + profilepicture: row.get(7), }); } @@ -128,3 +130,18 @@ pub fn verify_user(db_url: &str, email: &str, password: & str) -> PyResult<bool> Ok(false) } } + +#[pyfunction] +pub fn update_user_profile_picture(db_url: &str, user_id: i32, path: &str) -> PyResult<()> { + let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?; + let updated = client.execute( + "UPDATE users SET profilepicture = $1 WHERE id = $2", + &[&path, &user_id], + ).map_err(pg_err)?; + if updated == 0 { + Err(PyRuntimeError::new_err("User not found")) + } else { + Ok(()) + } +} + diff --git a/rust_crud_api/src/models/user.rs b/rust_crud_api/src/models/user.rs index ef54873266322a7b5b1026f21d87eeafc7205e8e..b1530f9e3ad6e461028651977b7d942b1963b33f 100644 --- a/rust_crud_api/src/models/user.rs +++ b/rust_crud_api/src/models/user.rs @@ -18,5 +18,7 @@ pub struct User { pub startyear: i32, #[pyo3(get,set)] pub endyear: i32, + #[pyo3(get,set)] + pub profilepicture: Option<String>, }