diff --git a/django_project/django_project/settings.py b/django_project/django_project/settings.py index b8db6642a7907e9256490f9a282e6cb9af22b896..d5fc7b68b0718809e275b527d5064ebe4e986848 100644 --- a/django_project/django_project/settings.py +++ b/django_project/django_project/settings.py @@ -144,3 +144,5 @@ STATIC_URL = 'static/' # https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +LOGIN_URL = '/login/' \ No newline at end of file diff --git a/django_project/django_project/urls.py b/django_project/django_project/urls.py index 8ba75c7ea9d72e34ae35b854f0cce285b85c94c4..41b80a8eb9a4a7e77eae13f13603db5cd7f032c2 100644 --- a/django_project/django_project/urls.py +++ b/django_project/django_project/urls.py @@ -24,7 +24,7 @@ from myapp.views.userviews import register_view, account_view, delete_account_vi from myapp.views.groupviews import group_list_view, group_create_view, group_update_view, group_delete_view, group_detail_view, group_search_view from myapp.views.postviews import create_post_view, feed_view from myapp.views.initview import init_db_view -from myapp.views.events_views import event_list_view +from myapp.views.events_views import event_list_view, register_event_view, event_create_view, event_update_view, event_delete_view urlpatterns = [ @@ -44,7 +44,11 @@ urlpatterns = [ path('welcome/', welcome_view, name='welcome'), path('edit_account/', edit_account_view, name='edit_account'), path('group_search', group_search_view, name='group_search'), - path('events/', event_list_view, name='event_list'), + path('events/', event_list_view, name='events_list'), + path('events/create/', event_create_view, name='event_create') + + + ] if settings.DEBUG: diff --git a/django_project/myapp/templates/myapp/base.html b/django_project/myapp/templates/myapp/base.html index e5e79458d8d1251d0ed78889d315f960fda86da5..19eaa927569af0df26b203d5cc3ae9b5e73a59c0 100644 --- a/django_project/myapp/templates/myapp/base.html +++ b/django_project/myapp/templates/myapp/base.html @@ -50,6 +50,7 @@ <li><a href="{% url 'create_post' %}">Post</a></li> <li><a href="{% url 'account' %}">Account</a></li> <li><a href="{% url 'group_list' %}">Groups</a></li> + <li><a href="{% url 'events_list' %}">Events</a></li> <li><a href="{% url 'logout' %}">Logout</a></li> <form method="GET" action="{% url 'group_search' %}"> <input type="text" name="q" placeholder="Search for groups..." value="{{ search_query }}"> diff --git a/django_project/myapp/templates/myapp/event_create.html b/django_project/myapp/templates/myapp/event_create.html new file mode 100644 index 0000000000000000000000000000000000000000..29c008e92e19f262d10b2c1b0cf69bd8fa8b6e9a --- /dev/null +++ b/django_project/myapp/templates/myapp/event_create.html @@ -0,0 +1,21 @@ +{% extends "base.html" %} + +{% block content %} +<h1>Create New Event</h1> + +{% if error %} + <p style="color: red;">{{ error }}</p> +{% endif %} + +<form method="post"> + {% csrf_token %} + <label>Title: <input type="text" name="title" required></label><br> + <label>Description: <textarea name="description"></textarea></label><br> + <label>Location: <input type="text" name="location" required></label><br> + <label>Date: <input type="date" name="date" required></label><br> + <label>Time: <input type="time" name="time" required></label><br> + <button type="submit">Create Event</button> + </form> + +<a href="{% url 'events_list' %}">← Back to Events</a> +{% endblock %} diff --git a/django_project/myapp/templates/myapp/events_list.html b/django_project/myapp/templates/myapp/events_list.html new file mode 100644 index 0000000000000000000000000000000000000000..44f0d8f5568e9498a5d8286bbad1eb7882053c3f --- /dev/null +++ b/django_project/myapp/templates/myapp/events_list.html @@ -0,0 +1,55 @@ +{% extends "base.html" %} + +{% block content %} +<h1>Upcoming Events</h1> + +{% if error %} + <p style="color:red">{{ error }}</p> +{% endif %} + +{% if events %} + <ul> + {% for event in events %} + <li> + <strong>{{ event.title }}</strong><br> + <em>{{ event.date }} at {{ event.time }}</em><br> + {{ event.location }}<br> + {% if event.description %} + <p>{{ event.description }}</p> + {% endif %} + + {% if user.is_authenticated %} + <form method="post" action="{% url 'register_event' event.id %}"> + {% csrf_token %} + <button type="submit">Register</button> + </form> + {% else %} + <p><em>Login to register for this event.</em></p> + {% endif %} + </li> + {% endfor %} + </ul> +{% else %} + <p>No events found.</p> +{% endif %} +<a href="{% url 'event_create' %}">Create New Event</a> + +{% if user.is_authenticated %} + <h2>Your Registered Events</h2> + <ul> + {% for event in registered_events %} + <li> + <strong>{{ event.title }}</strong><br> + <em>{{ event.date }} at {{ event.time }}</em><br> + {{ event.location }}<br> + {% if event.description %} + <p>{{ event.description }}</p> + {% endif %} + </li> + {% endfor %} + </ul> +{% else %} + <p><em>Login to see your registered events.</em></p> +{% endif %} + +{% endblock %} diff --git a/django_project/myapp/templates/myapp/events_page.html b/django_project/myapp/templates/myapp/events_page.html deleted file mode 100644 index 715f6e0127e2e489d2d57d076a19fbc656aaa8dc..0000000000000000000000000000000000000000 --- a/django_project/myapp/templates/myapp/events_page.html +++ /dev/null @@ -1,26 +0,0 @@ -{% extends "base.html" %} - -{% block content %} -<h1>Upcoming Events</h1> - -{% if error %} - <p style="color:red">{{ error }}</p> -{% endif %} - -{% if events %} - <ul> - {% for event in events %} - <li> - <strong>{{ event.title }}</strong><br> - <em>{{ event.date }} at {{ event.time }}</em><br> - {{ event.location }}<br> - {% if event.description %} - <p>{{ event.description }}</p> - {% endif %} - </li> - {% endfor %} - </ul> -{% else %} - <p>No events found.</p> -{% endif %} -{% endblock %} \ No newline at end of file diff --git a/django_project/myapp/templates/myapp/register.html b/django_project/myapp/templates/myapp/register.html index 26f5e4e4eef38d3639ea19921b007cb1c5ccceac..691df4144633b62d77f50db731904bd9386e0b32 100644 --- a/django_project/myapp/templates/myapp/register.html +++ b/django_project/myapp/templates/myapp/register.html @@ -36,6 +36,12 @@ <label for="id_endyear">Ending Year:</label> <input type="endyear" name="endyear" id="id_endyear" required><br><br> + <label for="id_bio">Bio:</label> + <textarea name="bio" id="id_bio" rows="4" cols="50"></textarea><br><br> + + <label for="id_interests">Interests:</label> + <input type="text" name="interests" id="id_interests"><br><br> + <label>Profile Picture:</label> <input type="file" name="profilepicture"> diff --git a/django_project/myapp/views/events_views.py b/django_project/myapp/views/events_views.py index dc6b4ee7a210f2f1a30a67987e5c778ce92f229b..965d6b59e960259c71a46f2c398d0e77eb20b5ad 100644 --- a/django_project/myapp/views/events_views.py +++ b/django_project/myapp/views/events_views.py @@ -1,13 +1,128 @@ from django.shortcuts import render from django.conf import settings +from django.contrib.auth.decorators import login_required +from django.shortcuts import redirect +from django.urls import reverse import rust_crud_api # Custom Rust Pyo3 Library 🦀 + def event_list_view(request): db_url = settings.DATABASE_URL try: events = rust_crud_api.get_all_events(db_url) - return render(request, 'myapp/event_list.html', {'events': events}) + return render(request, 'myapp/events_list.html', {'events': events}) except Exception as e: - return render(request, 'myapp/event_list.html', { + return render(request, 'myapp/events_list.html', { 'error': str(e), 'events': [] - }) \ No newline at end of file + }) + +@login_required +def register_event_view(request, event_id): + if request.method == "POST": + db_url = settings.DATABASE_URL + user_id = request.user.id # Assumes your Django user ID maps to Rust user ID + try: + rust_crud_api.register_for_event(db_url, user_id, event_id) + return redirect(reverse('events_page')) + except Exception as e: + return render(request, 'myapp/events_page.html', { + 'error': f"Could not register: {str(e)}", + 'events': rust_crud_api.get_all_events(db_url) + }) + else: + return redirect(reverse('events_page')) +def event_create_view(request): + if request.method == "POST": + db_url = settings.DATABASE_URL + title = request.POST.get("title") + description = request.POST.get("description") or None + location = request.POST.get("location") + date = request.POST.get("date") # format: YYYY-MM-DD + time = request.POST.get("time", "").strip() + + # If time is in HH:MM format, append :00 to make it HH:MM:SS + if time and len(time.split(":")) == 2: + time += ":00" + # format: HH:MM:SS + group_id = None # Update if needed + registered_users = [] # Initially empty + + # Validate input + if not all([title, location, date, time]): + return render(request, "myapp/event_create.html", { + "error": "All fields except description are required.", + }) + + try: + user_id = request.session.get("user_id") # Replace this with however you store user ID in session + if user_id is None: + raise Exception("You must be logged in to create an event.") + + rust_crud_api.create_event( + db_url, + title, + description, + location, + date, + time, + int(user_id), + group_id, + registered_users + ) + return redirect(reverse('events_list')) + + + except Exception as e: + return render(request, "myapp/event_create.html", { + "error": f"Could not create event: {str(e)}" + }) + + return render(request, "myapp/event_create.html") + +def event_update_view(request, event_id): + db_url = settings.DATABASE_URL + if request.method == "POST": + event_data = { + 'name': request.POST.get('name'), + 'date': request.POST.get('date'), + 'location': request.POST.get('location'), + 'description': request.POST.get('description') + } + try: + rust_crud_api.update_event(db_url, event_id, event_data) + return redirect(reverse('events_page')) + except Exception as e: + return render(request, 'myapp/event_update.html', { + 'error': f"Could not update event: {str(e)}", + 'event_id': event_id + }) + else: + try: + event = rust_crud_api.get_event_by_id(db_url, event_id) + return render(request, 'myapp/event_update.html', {'event': event}) + except Exception as e: + return render(request, 'myapp/event_update.html', { + 'error': f"Could not retrieve event: {str(e)}", + 'event_id': event_id + }) +@login_required +def event_delete_view(request, event_id): + db_url = settings.DATABASE_URL + if request.method == "POST": + try: + rust_crud_api.delete_event(db_url, event_id) + return redirect(reverse('events_page')) + except Exception as e: + return render(request, 'myapp/event_delete.html', { + 'error': f"Could not delete event: {str(e)}", + 'event_id': event_id + }) + else: + try: + event = rust_crud_api.get_event_by_id(db_url, event_id) + return render(request, 'myapp/event_delete.html', {'event': event}) + except Exception as e: + return render(request, 'myapp/event_delete.html', { + 'error': f"Could not retrieve event: {str(e)}", + 'event_id': event_id + }) \ No newline at end of file diff --git a/django_project/myapp/views/userviews.py b/django_project/myapp/views/userviews.py index 422f9894a3efe6ed0983194603b9a261c03d7ff7..26d802a7c1e1afac55d134a97fe4926123a893b9 100644 --- a/django_project/myapp/views/userviews.py +++ b/django_project/myapp/views/userviews.py @@ -27,6 +27,8 @@ def register_view(request): "studentid": request.POST.get("studentid", "").strip(), "startyear": request.POST.get("startyear", "").strip(), "endyear": request.POST.get("endyear", "").strip(), + "bio": request.POST.get("bio", "").strip(), + "interests": request.POST.get("interests", "").strip(), } # Preserve form data for repopulation @@ -84,7 +86,9 @@ def register_view(request): form_data["startyear"], form_data["endyear"], saved_path, - True, # First-time login flag + True, + form_data["bio"], + form_data["interests"].split(",") if form_data["interests"] else None, ) return redirect("login") # Redirect to login page after success except Exception as e: @@ -159,6 +163,8 @@ def edit_account_view(request): new_studentid_str = request.POST.get('studentid', '').strip() new_startyear_str = request.POST.get('startyear', '').strip() new_endyear_str = request.POST.get('endyear', '').strip() + new_bio = request.POST.get('bio', '').strip() + new_interests = request.POST.get('interests', '').strip() # Handle profile picture upload, if any. profilepicture_file = request.FILES.get('profilepicture') diff --git a/rust_crud_api/Cargo.toml b/rust_crud_api/Cargo.toml index 081d59b898b154e4badc682278acd9b87a7dbdc7..b0566918ad43ecd71218b43c1252e989360cb9de 100644 --- a/rust_crud_api/Cargo.toml +++ b/rust_crud_api/Cargo.toml @@ -9,7 +9,7 @@ crate-type = ["cdylib"] [dependencies] pyo3 = { version = "0.18", features = ["extension-module"] } -postgres = "0.19" +postgres = { version = "0.19", features = ["with-chrono-0_4"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" jsonwebtoken = "8.2.0" diff --git a/rust_crud_api/src/auth/mod.rs b/rust_crud_api/src/auth/mod.rs index da8716c6ae6217ceaf7738e542cf8cc6f44217ef..7c9992c21a33417bd3b0cf06fe629b279189b514 100644 --- a/rust_crud_api/src/auth/mod.rs +++ b/rust_crud_api/src/auth/mod.rs @@ -1,6 +1,7 @@ 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/db/events.rs b/rust_crud_api/src/db/events.rs index 38c5ce57cacfcbe12313a53ed5985d4dda23eb67..1fd7dd2077e385ccaf5b3b73bfc3cf087dc8d159 100644 --- a/rust_crud_api/src/db/events.rs +++ b/rust_crud_api/src/db/events.rs @@ -1,8 +1,10 @@ use crate::models::Event; + use pyo3::prelude::*; use pyo3::exceptions::PyRuntimeError; use postgres::{Client, NoTls}; -use chrono::{NaiveDate, NaiveTime, NaiveDateTime}; +use chrono::{NaiveDate, NaiveTime, DateTime, Utc}; +use postgres::types::ToSql; fn pg_err(e: postgres::Error) -> PyErr { PyRuntimeError::new_err(e.to_string()) @@ -18,11 +20,11 @@ pub fn create_event( date: &str, // expected format: "YYYY-MM-DD" time: &str, // expected format: "HH:MM:SS" created_by: i32, - group_id: Option<i32> + group_id: Option<i32>, ) -> PyResult<()> { let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?; - let date_parsed = NaiveDate::parse_from_str(date, "%Y-%m-%d") // Naive is raw date without timezone + let date_parsed = NaiveDate::parse_from_str(date, "%Y-%m-%d") .map_err(|e| PyRuntimeError::new_err(format!("Invalid date: {}", e)))?; let time_parsed = NaiveTime::parse_from_str(time, "%H:%M:%S") .map_err(|e| PyRuntimeError::new_err(format!("Invalid time: {}", e)))?; @@ -36,24 +38,41 @@ pub fn create_event( ).map_err(pg_err)?; Ok(()) -} +} // Make sure DateTime and Utc are imported #[pyfunction] -pub fn get_all_events(db_url: &str) -> PyResult<Vec<PyEvent>> { +pub fn get_all_events(db_url: &str) -> PyResult<Vec<Event>> { let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?; let mut events = Vec::new(); - for row in client.query("SELECT * FROM events ORDER BY date, time", &[]).map_err(pg_err)? { - let event = PyEvent { - id: row.get("id"), + for row in client + .query("SELECT * FROM events ORDER BY date, time", &[]) + .map_err(pg_err)? + { + let created_at: DateTime<Utc> = row.get("created_at"); + let event_id: i32 = row.get("id"); + + // Fetch registered users from the event_attendees table + let reg_rows = client + .query( + "SELECT user_id FROM event_attendees WHERE event_id = $1", + &[&event_id], + ) + .map_err(pg_err)?; + + let registered_users = reg_rows.into_iter().map(|r| r.get(0)).collect(); + + let event = Event { + id: event_id, title: row.get("title"), description: row.get("description"), location: row.get("location"), - date: row.get::<_, chrono::NaiveDate>("date").to_string(), - time: row.get::<_, chrono::NaiveTime>("time").to_string(), + date: row.get::<_, NaiveDate>("date").to_string(), + time: row.get::<_, NaiveTime>("time").to_string(), created_by: row.get("created_by"), group_id: row.get("group_id"), - created_at: row.get::<_, chrono::NaiveDateTime>("created_at").to_string(), + created_at: created_at.format("%Y-%m-%d %H:%M:%S").to_string(), + registered_users, }; events.push(event); } @@ -61,8 +80,10 @@ pub fn get_all_events(db_url: &str) -> PyResult<Vec<PyEvent>> { Ok(events) } + + #[pyfunction] -pub fn get_event(db_url: &str, event_id: i32) -> PyResult<Option<PyEvent>> { +pub fn get_event(db_url: &str, event_id: i32) -> PyResult<Option<Event>> { let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?; let row_opt = client.query_opt( @@ -71,7 +92,7 @@ pub fn get_event(db_url: &str, event_id: i32) -> PyResult<Option<PyEvent>> { ).map_err(pg_err)?; if let Some(row) = row_opt { - let event = PyEvent { + let event = Event { id: row.get("id"), title: row.get("title"), description: row.get("description"), @@ -81,9 +102,106 @@ pub fn get_event(db_url: &str, event_id: i32) -> PyResult<Option<PyEvent>> { created_by: row.get("created_by"), group_id: row.get("group_id"), created_at: row.get::<_, chrono::NaiveDateTime>("created_at").to_string(), + registered_users: row.get::<_, Vec<i32>>("registered_users"), }; Ok(Some(event)) } else { Ok(None) } } + +#[pyfunction] +pub fn update_event( + db_url: &str, + event_id: i32, + title: Option<&str>, + description: Option<&str>, + location: Option<&str>, + date: Option<&str>, // "YYYY-MM-DD" + time: Option<&str>, // "HH:MM:SS" +) -> PyResult<()> { + let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?; + + let mut query = String::from("UPDATE events SET "); + let mut params: Vec<Box<dyn ToSql + Sync>> = Vec::new(); + let mut param_refs: Vec<&(dyn ToSql + Sync)> = Vec::new(); + let mut param_count = 1; + + if let Some(val) = title { + query.push_str(&format!("title = ${}, ", param_count)); + params.push(Box::new(val.to_string())); + param_count += 1; + } + if let Some(val) = description { + query.push_str(&format!("description = ${}, ", param_count)); + params.push(Box::new(val.to_string())); + param_count += 1; + } + if let Some(val) = location { + query.push_str(&format!("location = ${}, ", param_count)); + params.push(Box::new(val.to_string())); + param_count += 1; + } + if let Some(date_str) = date { + let date_parsed = NaiveDate::parse_from_str(date_str, "%Y-%m-%d") + .map_err(|e| PyRuntimeError::new_err(format!("Invalid date: {}", e)))?; + query.push_str(&format!("date = ${}, ", param_count)); + params.push(Box::new(date_parsed)); + param_count += 1; + } + if let Some(time_str) = time { + let time_parsed = NaiveTime::parse_from_str(time_str, "%H:%M:%S") + .map_err(|e| PyRuntimeError::new_err(format!("Invalid time: {}", e)))?; + query.push_str(&format!("time = ${}, ", param_count)); + params.push(Box::new(time_parsed)); + param_count += 1; + } + + if params.is_empty() { + return Err(PyRuntimeError::new_err("No fields provided to update.")); + } + + // Remove trailing comma + if query.ends_with(", ") { + query.truncate(query.len() - 2); + } + + query.push_str(&format!(" WHERE id = ${}", param_count)); + params.push(Box::new(event_id)); + + // Convert to slice of references + for p in ¶ms { + param_refs.push(p.as_ref()); + } + + client.execute(&query, ¶m_refs[..]).map_err(pg_err)?; + + Ok(()) +} + +#[pyfunction] +pub fn delete_event(db_url: &str, event_id: i32) -> PyResult<()> { + let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?; + + client.execute( + "DELETE FROM events WHERE id = $1", + &[&event_id] + ).map_err(pg_err)?; + + Ok(()) +} +#[pyfunction] +pub fn register_for_event( + db_url: &str, + event_id: i32, + user_id: i32, +) -> PyResult<()> { + let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?; + + client.execute( + "INSERT INTO event_registrations (event_id, user_id) VALUES ($1, $2)", + &[&event_id, &user_id] + ).map_err(pg_err)?; + + Ok(()) +} \ No newline at end of file diff --git a/rust_crud_api/src/db/groups.rs b/rust_crud_api/src/db/groups.rs index 914919b52e4e52645ad1b1947ccd9bd6ffc713d8..784540205660d96fd6e7eeb6a32b79a1cea6c7ad 100644 --- a/rust_crud_api/src/db/groups.rs +++ b/rust_crud_api/src/db/groups.rs @@ -82,7 +82,7 @@ pub fn get_group_members(db_url: &str, group_id: i32) -> PyResult<(Vec<User>, i3 let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?; let rows = client.query( - "SELECT u.id, u.name, u.email, u.username, u.studentid, u.startyear, u.endyear, u.profilepicture, u.firstlogin + "SELECT u.id, u.name, u.email, u.username, u.studentid, u.startyear, u.endyear, u.profilepicture, u.firstlogin, u.bio, u.interests FROM users u JOIN group_members gm ON u.id = gm.user_id WHERE gm.group_id = $1", @@ -99,6 +99,8 @@ pub fn get_group_members(db_url: &str, group_id: i32) -> PyResult<(Vec<User>, i3 endyear: row.get(6), profilepicture: row.get(7), firstlogin: row.get(8), + bio: None, + interests: None, }).collect(); let count: i32 = client.query_one( diff --git a/rust_crud_api/src/db/init.rs b/rust_crud_api/src/db/init.rs index f02e6dd4f23340186677d100bf9f241d4e57c0e4..dc8fc28c096e2f03801dd3d8f1a01518e556bf23 100644 --- a/rust_crud_api/src/db/init.rs +++ b/rust_crud_api/src/db/init.rs @@ -23,20 +23,24 @@ pub fn init_db(db_url: &str) -> PyResult<()> { profilepicture VARCHAR, firstlogin BOOLEAN NOT NULL DEFAULT TRUE ); - + + -- Ensure columns exist even if table already did + ALTER TABLE users ADD COLUMN IF NOT EXISTS bio TEXT; + ALTER TABLE users ADD COLUMN IF NOT EXISTS interests TEXT[]; + CREATE TABLE IF NOT EXISTS groups ( id SERIAL PRIMARY KEY, name VARCHAR NOT NULL, member_count INTEGER NOT NULL DEFAULT 0, tags TEXT[] ); - + 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) ); - + CREATE TABLE IF NOT EXISTS posts ( id SERIAL PRIMARY KEY, user_id INTEGER REFERENCES users(id), @@ -44,7 +48,7 @@ pub fn init_db(db_url: &str) -> PyResult<()> { content TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); - + CREATE TABLE IF NOT EXISTS events ( id SERIAL PRIMARY KEY, title TEXT NOT NULL, @@ -56,7 +60,15 @@ pub fn init_db(db_url: &str) -> PyResult<()> { group_id INTEGER REFERENCES groups(id), created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); + + CREATE TABLE IF NOT EXISTS event_attendees ( + user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, + event_id INTEGER REFERENCES events(id) ON DELETE CASCADE, + registered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (user_id, event_id) + ); " ).map_err(pg_err)?; Ok(()) -} \ No newline at end of file +} +// \ No newline at end of file diff --git a/rust_crud_api/src/db/mod.rs b/rust_crud_api/src/db/mod.rs index 5d9ae31adeeb81a290ef7f34d2a49b695f0fedb2..add75a8715429b6bd77c637802ebf80e2ae2dea5 100644 --- a/rust_crud_api/src/db/mod.rs +++ b/rust_crud_api/src/db/mod.rs @@ -2,8 +2,10 @@ pub mod users; pub mod groups; pub mod init; pub mod posts; +pub mod events; pub use init::init_db; pub use users::{create_user, get_user, get_all_users, update_user, delete_user, update_first_login}; pub use groups::{create_group, get_group, get_all_groups, add_user_to_group, get_group_members, delete_group, search_groups}; pub use posts::{create_post, get_user_posts, get_group_posts, get_feed}; +pub use events::{create_event, get_all_events, get_event, delete_event, update_event, register_for_event}; \ No newline at end of file diff --git a/rust_crud_api/src/db/posts.rs b/rust_crud_api/src/db/posts.rs index a36a29bbcde33db25d34b014823d4474680e6e14..360a3128839aab8936e0fa784fc150637dbf60d6 100644 --- a/rust_crud_api/src/db/posts.rs +++ b/rust_crud_api/src/db/posts.rs @@ -68,7 +68,8 @@ pub fn get_feed(db_url: &str) -> PyResult<Vec<Post>> { let user_name: Option<String> = Some(row.get(2)); let content: String = row.get(3); let created_at: SystemTime = row.get(4); - /// Convert SystemTime to DateTime<Utc> + + // Convert SystemTime to DateTime<Utc> let created_at = DateTime::<Utc>::from(created_at) .format("%H:%M:%S/%d/%m/%Y") .to_string(); @@ -84,3 +85,4 @@ pub fn get_feed(db_url: &str) -> PyResult<Vec<Post>> { Ok(posts) } + diff --git a/rust_crud_api/src/db/users.rs b/rust_crud_api/src/db/users.rs index ee95e1dc7c46b76a89d85ffa279520583c932ab7..6aacd76136ef34fe409fefd4cf0179933f369934 100644 --- a/rust_crud_api/src/db/users.rs +++ b/rust_crud_api/src/db/users.rs @@ -14,13 +14,13 @@ fn pg_err(e: postgres::Error) -> PyErr { /// Create a user #[pyfunction] pub fn create_user( - db_url: &str, name: &str, email: &str, password: &str, username: &str, studentid: i32, startyear: i32, endyear: i32, profilepicture: Option<&str>, firstlogin: bool) -> PyResult<()> { + db_url: &str, name: &str, email: &str, password: &str, username: &str, studentid: i32, startyear: i32, endyear: i32, profilepicture: Option<&str>, firstlogin: bool, bio: Option<&str>, interests: Option<Vec<String>>) -> 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, profilepicture, firstlogin) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", - &[&name, &email, &password_hash, &username, &studentid, &startyear, &endyear, &profilepicture, &firstlogin], + "INSERT INTO users (name, email, password_hash, username, studentid, startyear, endyear, profilepicture, firstlogin, bio, interests) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", + &[&name, &email, &password_hash, &username, &studentid, &startyear, &endyear, &profilepicture, &firstlogin, &bio, &interests], ); match result { @@ -45,7 +45,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, profilepicture, firstlogin FROM users WHERE id = $1", + "SELECT id, name, email, username, studentid, startyear, endyear, profilepicture, firstlogin, bio, interests FROM users WHERE id = $1", &[&user_id] ).map_err(pg_err)?; @@ -60,6 +60,8 @@ pub fn get_user(db_url: &str, user_id: i32) -> PyResult<Option<User>> { endyear: row.get(6), profilepicture: row.get(7), firstlogin: row.get(8), + bio: row.get(9), + interests: row.get(10), }; Ok(Some(user)) @@ -72,7 +74,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, profilepicture, firstlogin FROM users", &[]) + let rows = client.query("SELECT id, name, email, username, studentid, startyear, endyear, profilepicture, firstlogin, bio, interests FROM users", &[]) .map_err(pg_err)?; let mut users = Vec::new(); @@ -87,6 +89,8 @@ pub fn get_all_users(db_url: &str) -> PyResult<Vec<User>> { endyear: row.get(6), profilepicture: row.get(7), firstlogin: row.get(8), + bio: row.get(9), + interests: row.get(10), }); } @@ -104,7 +108,9 @@ pub fn get_all_users(db_url: &str) -> PyResult<Vec<User>> { new_studentid = None, new_startyear = None, new_endyear = None, - new_profilepicture = None + new_profilepicture = None, + new_bio = None, + new_interests = None, ))] pub fn update_user( @@ -118,6 +124,8 @@ pub fn update_user( new_startyear: Option<i32>, new_endyear: Option<i32>, new_profilepicture: Option<&str>, + new_bio: Option<&str>, + new_interests: Option<Vec<String>>, ) -> PyResult<()> { let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?; let mut query = String::from("UPDATE users SET "); @@ -150,6 +158,11 @@ pub fn update_user( push_field!("startyear", new_startyear); push_field!("endyear", new_endyear); push_field!("profilepicture", new_profilepicture); + push_field!("bio", new_bio); + if let Some(interests) = new_interests { + updates.push(format!("interests = ${}", owned_params.len() + 1)); + owned_params.push(Box::new(interests)); + } if updates.is_empty() { return Err(PyRuntimeError::new_err("No fields to update.")); @@ -224,3 +237,25 @@ pub fn update_first_login(db_url: &str, user_id: i32, first_login: bool) -> PyRe Ok(()) } } + +#[pyfunction] +pub fn register_for_event(db_url: &str, user_id: i32, event_id: i32) -> PyResult<()> { + let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?; + + // Optional: check if already registered + let exists = client.query_opt( + "SELECT 1 FROM event_attendees WHERE user_id = $1 AND event_id = $2", + &[&user_id, &event_id] + ).map_err(pg_err)?; + + if exists.is_some() { + return Err(PyRuntimeError::new_err("User is already registered for this event")); + } + + client.execute( + "INSERT INTO event_attendees (user_id, event_id) VALUES ($1, $2)", + &[&user_id, &event_id] + ).map_err(pg_err)?; + + Ok(()) +} diff --git a/rust_crud_api/src/lib.rs b/rust_crud_api/src/lib.rs index 82821a1bd6d5d91a6b7f21265b72b5de2990cbfc..baabeeebd18ec9b0ffa955da0a9ca52bb2e3cd02 100644 --- a/rust_crud_api/src/lib.rs +++ b/rust_crud_api/src/lib.rs @@ -11,6 +11,7 @@ fn rust_crud_api(_py: Python, m: &PyModule) -> PyResult<()> { m.add_class::<models::User>()?; m.add_class::<models::Group>()?; m.add_class::<models::Post>()?; + m.add_class::<models::Event>()?; // Authentication functions m.add_function(wrap_pyfunction!(auth::generate_jwt, m)?)?; m.add_function(wrap_pyfunction!(auth::verify_jwt, m)?)?; @@ -44,6 +45,12 @@ fn rust_crud_api(_py: Python, m: &PyModule) -> PyResult<()> { m.add_function(wrap_pyfunction!(db::posts::get_feed, m)?)?; // Events + m.add_function(wrap_pyfunction!(db::events::register_for_event, m)?)?; + m.add_function(wrap_pyfunction!(db::events::create_event, m)?)?; + m.add_function(wrap_pyfunction!(db::events::get_all_events, m)?)?; + m.add_function(wrap_pyfunction!(db::events::get_event, m)?)?; + m.add_function(wrap_pyfunction!(db::events::delete_event, m)?)?; + m.add_function(wrap_pyfunction!(db::events::update_event, m)?)?; // Placeholder Ok(()) } diff --git a/rust_crud_api/src/models/event.rs b/rust_crud_api/src/models/event.rs index 2a7082bd142a919f9acad76feb5fe6c5fe1983d0..6d2f887cc3b7ae18c969fc99648b7a9e397e6796 100644 --- a/rust_crud_api/src/models/event.rs +++ b/rust_crud_api/src/models/event.rs @@ -22,4 +22,6 @@ pub struct Event { pub group_id: Option<i32>, #[pyo3(get, set)] pub created_at: String, + #[pyo3(get, set)] + pub registered_users: Vec<i32>, } \ No newline at end of file diff --git a/rust_crud_api/src/models/mod.rs b/rust_crud_api/src/models/mod.rs index 4c1fdd414302a354147f7f01a3dfd968c9f25011..620d8215f8de37e6a16df2fd5d6ab46c5106e733 100644 --- a/rust_crud_api/src/models/mod.rs +++ b/rust_crud_api/src/models/mod.rs @@ -1,7 +1,9 @@ pub mod user; pub mod group; pub mod post; +pub mod event; pub use user::User; pub use group::Group; pub use post::Post; +pub use event::Event; diff --git a/rust_crud_api/src/models/user.rs b/rust_crud_api/src/models/user.rs index 65fbdb9e7f59362111b60c6680178f38479406b6..703eb009c3d6700865d94098c36e7eda67f1dd7f 100644 --- a/rust_crud_api/src/models/user.rs +++ b/rust_crud_api/src/models/user.rs @@ -22,5 +22,9 @@ pub struct User { pub profilepicture: Option<String>, #[pyo3(get,set)] pub firstlogin: bool, + #[pyo3(get,set)] + pub bio: Option<String>, + #[pyo3(get,set)] + pub interests: Option<Vec<String>>, }