diff --git a/django_project/myapp/forms.py b/django_project/myapp/forms.py new file mode 100644 index 0000000000000000000000000000000000000000..16718dce960dc395262f958b88cebbd24d438248 --- /dev/null +++ b/django_project/myapp/forms.py @@ -0,0 +1,25 @@ +from django import forms +from django.contrib.auth import authenticate + +class EmailLoginForm(forms.Form): + email = forms.EmailField(label="Email") + password = forms.CharField(widget=forms.PasswordInput) + + def clean(self): + cleaned_data = super().clean() + email = cleaned_data.get("email") + password = cleaned_data.get("password") + + from django.contrib.auth import get_user_model + User = get_user_model() + + try: + user = User.objects.get(email=email) + except User.DoesNotExist: + raise forms.ValidationError("No user with that email.") + + self.user = authenticate(username=user.username, password=password) + if self.user is None: + raise forms.ValidationError("Invalid password.") + + return cleaned_data diff --git a/django_project/myapp/templates/myapp/group_create.html b/django_project/myapp/templates/myapp/group_create.html index c55d83fb084668692bf576d55df4f2698e3aa2b2..8ce3d22f8120261ef2dd8a841bd570856060623f 100644 --- a/django_project/myapp/templates/myapp/group_create.html +++ b/django_project/myapp/templates/myapp/group_create.html @@ -14,6 +14,9 @@ <label for="id_name">Group Name:</label> <input type="text" name="name" id="id_name" required> <br><br> + <label>Tags (comma-separated):</label> + <input type="text" name="tags"><br> + <button type="submit">Create Group</button> </form> diff --git a/django_project/myapp/templates/myapp/group_detail.html b/django_project/myapp/templates/myapp/group_detail.html index d34906382b2dfe0a6b1634b862d46e1bbacdb291..8f24e08eecc86b3822385ef841305d155d46dd80 100644 --- a/django_project/myapp/templates/myapp/group_detail.html +++ b/django_project/myapp/templates/myapp/group_detail.html @@ -3,23 +3,52 @@ {% block title %}Group Details{% endblock %} {% block content %} - <h1>Group Details</h1> - {% if error %} - <p style="color: red;">{{ error }}</p> +<h1>Group Details</h1> + +{% if error %} + <p style="color: red;">{{ error }}</p> +{% elif group %} + <p><strong>Group Name:</strong> {{ group.name }}</p> + <p><strong>Group ID:</strong> {{ group.id }}</p> + <p><strong>Member Count:</strong> {{ group.member_count }}</p> + + {% if group.tags %} + <p><strong>Tags:</strong> + {% for tag in group.tags %} + <span style="display: inline-block; background-color: #eee; padding: 4px 8px; margin-right: 5px; border-radius: 4px;"> + {{ tag }} + </span> + {% endfor %} + </p> {% else %} - <p><strong>Group Name:</strong> {{ group.name }}</p> - <p><strong>Group ID:</strong> {{ group.id }}</p> - <p><strong>Member Count:</strong> {{ group.member_count }}</p> - {% if request.user in group.members.all %} - <p>You’re already a member of this group.</p> + <p><strong>Tags:</strong> <em>No tags assigned.</em></p> + {% endif %} + + {% if user_id %} + {% if is_member %} + <p>You’re already a member of this group.</p> {% else %} - <form method="post"> - {% csrf_token %} - <input type="hidden" name="user_id" value="{{ request.user.id }}"> - <button type="submit">Join Group</button> - </form> + <form method="post"> + {% csrf_token %} + <input type="hidden" name="user_id" value="{{ user_id }}"> + <button type="submit">Join Group</button> + </form> + {% endif %} + {% else %} + <p><a href="{% url 'login' %}">Log in</a> to join this group.</p> {% endif %} + + <h2>Members</h2> + {% if members %} + <ul> + {% for member in members %} + <li>{{ member.name }} ({{ member.email }})</li> + {% endfor %} + </ul> + {% else %} + <p>No members yet.</p> {% endif %} - <p><a href="{% url 'group_list' %}">Back to Groups</a></p> -</body> +{% endif %} + +<p><a href="{% url 'group_list' %}">Back to Groups</a></p> {% endblock %} diff --git a/django_project/myapp/templates/myapp/login.html b/django_project/myapp/templates/myapp/login.html index dd14de3d34faeaeac9ebd4eead55eaf033955ab5..ce831fde2cfed5f7c676a77f7caa8633749fca50 100644 --- a/django_project/myapp/templates/myapp/login.html +++ b/django_project/myapp/templates/myapp/login.html @@ -56,19 +56,21 @@ <div class="left"> <div> <h1>Login</h1> - {% if error %} - <p class="error">{{ error }}</p> + {% if form.errors %} + <p class="error">Invalid login. Please try again.</p> {% endif %} <form method="post"> {% csrf_token %} - <label for="id_email">Email:</label> - <input type="email" name="email" id="id_email" required><br> + <label for="id_email">Email:</label> + <input type="email" name="email" id="id_email" value="{{ form.email.value|default_if_none:'' }}" required><br> + <label for="id_password">Password:</label> <input type="password" name="password" id="id_password" required><br> - + <button type="submit">Login</button> </form> + <p> Don't have an account? <a href="{% url 'register' %}">Register here</a>. @@ -79,7 +81,7 @@ <h1>UniHub</h1> <p>Where university happens.</p> </div> - </div> + </div> </body> </html> diff --git a/django_project/myapp/views/events_views.py b/django_project/myapp/views/events_views.py index b9a0c2c3311a2dd2e1c60816a74956bfb5305720..dc6b4ee7a210f2f1a30a67987e5c778ce92f229b 100644 --- a/django_project/myapp/views/events_views.py +++ b/django_project/myapp/views/events_views.py @@ -1,7 +1,6 @@ from django.shortcuts import render from django.conf import settings -import rust_crud_api # Make sure this matches your actual module name - +import rust_crud_api # Custom Rust Pyo3 Library 🦀 def event_list_view(request): db_url = settings.DATABASE_URL try: diff --git a/django_project/myapp/views/groupviews.py b/django_project/myapp/views/groupviews.py index a4f28c956a6a2aae4b5bccb14c01eaca74b0076c..c5e274933ea387b00f842ecf81749e1fd7a528ca 100644 --- a/django_project/myapp/views/groupviews.py +++ b/django_project/myapp/views/groupviews.py @@ -5,6 +5,9 @@ import time import os import rust_crud_api # Custom Rust Pyo3 Library 🦀 +from django.contrib.auth.decorators import login_required + + db_url = settings.DATABASE_URL def group_list_view(request): @@ -20,6 +23,25 @@ def group_list_view(request): context['error'] = f"An error occurred: {e}" return render(request, 'myapp/group_list.html', context) +@login_required +def group_join_view(request, group_id): + """ + Join a group. + """ + db_url = settings.DATABASE_URL + context = {} + if request.method == 'POST': + user_id = request.POST.get('user_id') + if not user_id or user_id == 'None': + context['error'] = "Invalid user_id: None" + else: + try: + rust_crud_api.add_user_to_group(db_url, int(group_id), int(user_id)) + return redirect('group_list') + except Exception as e: + context['error'] = f"An error occurred: {e}" + return render(request, 'myapp/group_join.html', context) + def group_create_view(request): """ Create a new group. @@ -28,11 +50,13 @@ def group_create_view(request): context = {} if request.method == 'POST': group_name = request.POST.get('name', '').strip() + tags_raw = request.POST.get('tags', '').strip() + tags = [tag.strip() for tag in tags_raw.split(',') if tag.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) + rust_crud_api.create_group(db_url, group_name, tags) return redirect('group_list') except Exception as e: context['error'] = f"An error occurred: {e}" @@ -98,24 +122,34 @@ def group_detail_view(request, group_id): db_url = settings.DATABASE_URL context = {} - if request.method == 'POST': - user_id = request.POST.get('user_id') # might be None or 'None' - - # Check for None *before* calling Rust - if not user_id or user_id == 'None': - context['error'] = "Invalid user_id: None" - return render(request, 'myapp/group_detail.html', context) + user_id = request.session.get('user_id') + is_member = False + members = [] + if request.method == 'POST': + if not user_id: + return redirect('login') try: - # Now we safely do int(user_id) because we checked it's not None - result = rust_crud_api.add_user_to_group(db_url, int(group_id), int(user_id)) + rust_crud_api.add_user_to_group(db_url, int(group_id), int(user_id)) except Exception as e: - context['error'] = f"An error occurred: {e}" - return render(request, 'myapp/group_detail.html', context) + context['error'] = f"An error occurred while joining: {e}" - # Normal GET behavior... - return render(request, 'myapp/group_detail.html', context) + try: + group = rust_crud_api.get_group(db_url, int(group_id)) + if not group: + context['error'] = "Group not found." + else: + context['group'] = group + context['user_id'] = user_id + + members, _ = rust_crud_api.get_group_members(db_url, int(group_id)) + context['members'] = members + is_member = any(m.id == int(user_id) for m in members) if user_id else False + context['is_member'] = is_member + except Exception as e: + context['error'] = f"An error occurred: {e}" + return render(request, 'myapp/group_detail.html', context) diff --git a/django_project/myapp/views/userviews.py b/django_project/myapp/views/userviews.py index f9ad56e0a2b56a2f342a1c9d1d6d3e32a862012d..422f9894a3efe6ed0983194603b9a261c03d7ff7 100644 --- a/django_project/myapp/views/userviews.py +++ b/django_project/myapp/views/userviews.py @@ -1,6 +1,7 @@ from django.shortcuts import render, redirect from django.http import JsonResponse from django.conf import settings +from myapp.forms import EmailLoginForm import time import os import rust_crud_api # Custom Rust Pyo3 Library 🦀 @@ -226,3 +227,17 @@ def edit_account_view(request): context['user'] = user return render(request, 'myapp/edit_account.html', context) + +def email_login_view(request): + form = EmailLoginForm(request.POST or None) + context = {'form': form} + + if request.method == 'POST': + if form.is_valid(): + user = form.user + request.session['user_id'] = user.id # ✅ store manually + return redirect('group_list') # or 'account', etc. + else: + context['form'] = form # errors included + + return render(request, 'myapp/login.html', context) \ No newline at end of file diff --git a/rust_crud_api/src/db/events.rs b/rust_crud_api/src/db/events.rs index 635c79882cf450a27e5572b25b8e8fa123078b6a..38c5ce57cacfcbe12313a53ed5985d4dda23eb67 100644 --- a/rust_crud_api/src/db/events.rs +++ b/rust_crud_api/src/db/events.rs @@ -22,7 +22,7 @@ pub fn create_event( ) -> PyResult<()> { let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?; - let date_parsed = NaiveDate::parse_from_str(date, "%Y-%m-%d") + let date_parsed = NaiveDate::parse_from_str(date, "%Y-%m-%d") // Naive is raw date without timezone .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)))?; @@ -59,4 +59,31 @@ pub fn get_all_events(db_url: &str) -> PyResult<Vec<PyEvent>> { } Ok(events) -} \ No newline at end of file +} + +#[pyfunction] +pub fn get_event(db_url: &str, event_id: i32) -> PyResult<Option<PyEvent>> { + let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?; + + let row_opt = client.query_opt( + "SELECT * FROM events WHERE id = $1", + &[&event_id] + ).map_err(pg_err)?; + + if let Some(row) = row_opt { + let event = PyEvent { + id: row.get("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(), + created_by: row.get("created_by"), + group_id: row.get("group_id"), + created_at: row.get::<_, chrono::NaiveDateTime>("created_at").to_string(), + }; + Ok(Some(event)) + } else { + Ok(None) + } +} diff --git a/rust_crud_api/src/db/groups.rs b/rust_crud_api/src/db/groups.rs index 27f6d68a5ba95eb590dd5cdea644023b0dff313f..914919b52e4e52645ad1b1947ccd9bd6ffc713d8 100644 --- a/rust_crud_api/src/db/groups.rs +++ b/rust_crud_api/src/db/groups.rs @@ -10,11 +10,11 @@ fn pg_err(e: postgres::Error) -> PyErr { /// Create a group #[pyfunction] -pub fn create_group(db_url: &str, name: &str) -> PyResult<()> { +pub fn create_group(db_url: &str, name: &str, tags: Vec<String>) -> PyResult<()> { let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?; client.execute( - "INSERT INTO groups (name, member_count) VALUES ($1, $2)", - &[&name, &0] + "INSERT INTO groups (name, member_count, tags) VALUES ($1, $2, $3)", + &[&name, &0, &tags] ).map_err(pg_err)?; Ok(()) } @@ -23,16 +23,18 @@ pub fn create_group(db_url: &str, name: &str) -> PyResult<()> { #[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, member_count FROM groups WHERE id = $1", + "SELECT id, name, member_count, tags 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), member_count: row.get(2), + tags: Some(row.get::<_, Option<Vec<String>>>(3).unwrap_or_default()), }; Ok(Some(group)) } else { @@ -44,13 +46,14 @@ pub fn get_group(db_url: &str, group_id: i32) -> PyResult<Option<Group>> { #[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, member_count FROM groups", &[]) + let rows = client.query("SELECT id, name, member_count, tags FROM groups", &[]) .map_err(pg_err)?; let groups = rows.into_iter().map(|row| Group { id: row.get(0), name: row.get(1), member_count: row.get(2), + tags: row.get(3), }).collect(); Ok(groups) @@ -122,7 +125,7 @@ pub fn delete_group(db_url: &str, group_id: i32) -> PyResult<bool> { pub fn search_groups(db_url: &str, query: &str) -> PyResult<Vec<Group>> { let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?; let rows = client.query( - "SELECT id, name, member_count FROM groups WHERE name ILIKE $1", + "SELECT id, name, member_count, tags FROM groups WHERE name ILIKE $1", &[&format!("%{}%", query)] ).map_err(pg_err)?; @@ -131,12 +134,14 @@ pub fn search_groups(db_url: &str, query: &str) -> PyResult<Vec<Group>> { id: Some(row.get(0)), name: row.get(1), member_count: row.get(2), + tags: row.get(3), }) .collect(); Ok(groups) } +///Get the count of members in a group #[pyfunction] pub fn get_group_member_count(db_url: &str, group_id: i32) -> PyResult<i64> { let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?; diff --git a/rust_crud_api/src/db/init.rs b/rust_crud_api/src/db/init.rs index d6c4ae201836cf601efd6cc593a66f3f660755e6..f02e6dd4f23340186677d100bf9f241d4e57c0e4 100644 --- a/rust_crud_api/src/db/init.rs +++ b/rust_crud_api/src/db/init.rs @@ -27,7 +27,8 @@ pub fn init_db(db_url: &str) -> PyResult<()> { CREATE TABLE IF NOT EXISTS groups ( id SERIAL PRIMARY KEY, name VARCHAR NOT NULL, - member_count INTEGER NOT NULL DEFAULT 0 + member_count INTEGER NOT NULL DEFAULT 0, + tags TEXT[] ); CREATE TABLE IF NOT EXISTS group_members ( diff --git a/rust_crud_api/src/db/posts.rs b/rust_crud_api/src/db/posts.rs index 08219f8e37d37c87cc53eb12af3fa329e9dc19b4..a36a29bbcde33db25d34b014823d4474680e6e14 100644 --- a/rust_crud_api/src/db/posts.rs +++ b/rust_crud_api/src/db/posts.rs @@ -68,7 +68,11 @@ 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); - let created_at = DateTime::<Utc>::from(created_at).to_rfc3339(); + /// Convert SystemTime to DateTime<Utc> + let created_at = DateTime::<Utc>::from(created_at) + .format("%H:%M:%S/%d/%m/%Y") + .to_string(); + posts.push(Post { id, user_id, @@ -79,3 +83,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 2e34a3472dac121d078ea30edb97fd031215d526..ee95e1dc7c46b76a89d85ffa279520583c932ab7 100644 --- a/rust_crud_api/src/db/users.rs +++ b/rust_crud_api/src/db/users.rs @@ -6,10 +6,12 @@ use crate::auth::hash_password; use crate::auth::verify_password; use postgres::types::ToSql; + +/// Dont Delete this! fn pg_err(e: postgres::Error) -> PyErr { PyRuntimeError::new_err(e.to_string()) } - +/// 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<()> { @@ -104,6 +106,7 @@ pub fn get_all_users(db_url: &str) -> PyResult<Vec<User>> { new_endyear = None, new_profilepicture = None ))] + pub fn update_user( db_url: &str, user_id: i32, diff --git a/rust_crud_api/src/lib.rs b/rust_crud_api/src/lib.rs index 05e21a9104ffb375f94f24b6166ed266b08cf65f..82821a1bd6d5d91a6b7f21265b72b5de2990cbfc 100644 --- a/rust_crud_api/src/lib.rs +++ b/rust_crud_api/src/lib.rs @@ -42,6 +42,9 @@ fn rust_crud_api(_py: Python, m: &PyModule) -> PyResult<()> { m.add_function(wrap_pyfunction!(db::posts::get_user_posts, m)?)?; m.add_function(wrap_pyfunction!(db::posts::get_group_posts, m)?)?; m.add_function(wrap_pyfunction!(db::posts::get_feed, m)?)?; + + // Events + // Placeholder Ok(()) } diff --git a/rust_crud_api/src/models/event.rs b/rust_crud_api/src/models/event.rs index d7ab003147cbe52a84f33f8e96ad0b4bd4ee525f..2a7082bd142a919f9acad76feb5fe6c5fe1983d0 100644 --- a/rust_crud_api/src/models/event.rs +++ b/rust_crud_api/src/models/event.rs @@ -13,9 +13,9 @@ pub struct Event { #[pyo3(get, set)] pub location: String, #[pyo3(get, set)] - pub date: String, // or NaiveDate + pub date: String, // or NaiveDate - !Might not match up because Naive is being used elsewhere #[pyo3(get, set)] - pub time: String, // or NaiveTime + pub time: String, // or NaiveTime - !"" #[pyo3(get, set)] pub created_by: i32, #[pyo3(get, set)] diff --git a/rust_crud_api/src/models/group.rs b/rust_crud_api/src/models/group.rs index c95423a4216085ec19154c84ce83eaf9140e1a0b..4c292689c29059f3f45160799d00eaaf0f9ede32 100644 --- a/rust_crud_api/src/models/group.rs +++ b/rust_crud_api/src/models/group.rs @@ -10,6 +10,8 @@ pub struct Group { pub name: String, #[pyo3(get, set)] pub member_count: i32, + #[pyo3(get, set)] + pub tags : Option<Vec<String>>, }