Skip to content
Snippets Groups Projects
Commit 3e175e2f authored by James2Tulloch's avatar James2Tulloch
Browse files

First pass of posts, working but basic

parent 108bc6cf
No related branches found
No related tags found
No related merge requests found
......@@ -30,4 +30,6 @@ urlpatterns = [
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'),
path('groups/<int:group_id>/', views.group_detail_view, name='group_detail'),
path('post/create/', views.create_post_view, name='create_post'),
path('feed/', views.feed_view, name='feed'),
]
{% extends 'myapp/base.html' %}
{% block title %}Create Post{% endblock %}
{% block content %}
<h1>Create a New Post</h1>
{% if error %}
<p style="color: red;">{{ error }}</p>
{% endif %}
<form method="post">
{% csrf_token %}
<label for="id_content">Content:</label><br>
<textarea name="content" id="id_content" rows="4" cols="50" required></textarea><br><br>
<!-- Optional: for posting to a specific group -->
<label for="id_group_id">Group ID (optional):</label>
<input type="number" name="group_id" id="id_group_id"><br><br>
<button type="submit">Post</button>
</form>
<p><a href="{% url 'account' %}">Back to Account</a></p>
{% endblock %}
{% extends 'myapp/base.html' %}
{% block title %}Feed{% endblock %}
{% block content %}
<h1>Global Feed</h1>
{% if error %}
<p style="color:red;">{{ error }}</p>
{% endif %}
<ul>
{% for post in posts %}
<li>
<strong>{{ post.user_name }}</strong> posted at {{ post.created_at }}:
<p>{{ post.content }}</p>
</li>
{% empty %}
<li>No posts available.</li>
{% endfor %}
</ul>
{% endblock %}
......@@ -233,3 +233,70 @@ def group_detail_view(request, group_id):
except Exception as e:
context['error'] = f"An error occurred: {e}"
return render(request, 'myapp/group_detail.html', context)
def account_view(request):
"""
Display the account page with user posts.
"""
db_url = settings.DATABASE_URL
user_id = request.session.get('user_id')
if not user_id:
return redirect('login')
try:
# Retrieve user info, e.g., using get_user (or session data)
users = rust_crud_api.get_all_users(db_url)
user = next((u for u in users if u.id == user_id), None)
# Retrieve posts for this user (public posts)
posts = rust_crud_api.get_user_posts(db_url, user_id)
except Exception as e:
return render(request, 'myapp/account.html', {'error': str(e)})
return render(request, 'myapp/account.html', {'user': user, 'posts': posts})
def create_post_view(request):
"""
Allow a logged-in user to create a new post.
If a group is specified in the POST data, the post is associated with that group.
"""
db_url = settings.DATABASE_URL
user_id = request.session.get('user_id')
if not user_id:
return redirect('login')
context = {}
if request.method == 'POST':
content = request.POST.get('content', '').strip()
# Optionally, get a group_id if the post is for a group:
group_id = request.POST.get('group_id')
if group_id:
try:
group_id = int(group_id)
except ValueError:
group_id = None
else:
group_id = None
if not content:
context['error'] = "Post content cannot be empty."
return render(request, 'myapp/create_post.html', context)
try:
rust_crud_api.create_post(db_url, user_id, group_id, content)
return redirect('account')
except Exception as e:
context['error'] = f"Error creating post: {str(e)}"
return render(request, 'myapp/create_post.html', context)
def feed_view(request):
"""
Display a global feed of posts.
"""
db_url = settings.DATABASE_URL
try:
posts = rust_crud_api.get_feed(db_url)
except Exception as e:
return render(request, 'myapp/feed.html', {'error': str(e)})
return render(request, 'myapp/feed.html', {'posts': posts})
......@@ -15,3 +15,4 @@ serde_json = "1.0"
jsonwebtoken = "8.2.0"
argon2 = "0.4"
rand = "0.8"
chrono = { version = "0.4", features = ["serde"]}
......@@ -27,6 +27,13 @@ pub fn init_db(db_url: &str) -> PyResult<()> {
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),
group_id INTEGER REFERENCES groups(id),
content TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
"
).map_err(pg_err)?;
Ok(())
......
pub mod users;
pub mod groups;
pub mod init;
pub mod posts;
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};
pub use posts::{create_post, get_user_posts, get_group_posts, get_feed};
use crate::models::Post;
use pyo3::prelude::*;
use pyo3::exceptions::PyRuntimeError;
use postgres::{Client, NoTls};
use chrono::{DateTime, Utc};
use std::time::SystemTime;
fn pg_err(e: postgres::Error) -> PyErr {
PyRuntimeError::new_err(e.to_string())
}
/// Create a new post. If group_id is None, the post is for the user’s account or public feed.
/// Otherwise, it is posted to the specified group.
#[pyfunction(signature = (db_url, user_id, group_id=None, content=""))]
pub fn create_post(db_url: &str, user_id: i32, group_id: Option<i32>, content: &str) -> PyResult<()> {
let mut client = Client::connect(db_url, NoTls).map_err(|e| PyRuntimeError::new_err(e.to_string()))?;
client.execute(
"INSERT INTO posts (user_id, group_id, content) VALUES ($1, $2, $3)",
&[&user_id, &group_id.as_ref(), &content]
).map_err(|e| PyRuntimeError::new_err(e.to_string()))?;
Ok(())
}
/// Retrieve posts for a given user. If group_id is None, returns posts that are not tied to a group,
/// which could be used for a public feed or the user’s own posts.
#[pyfunction]
pub fn get_user_posts(db_url: &str, user_id: i32) -> PyResult<Vec<String>> {
let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?;
let rows = client.query(
"SELECT content FROM posts WHERE user_id = $1 AND group_id IS NULL ORDER BY created_at DESC",
&[&user_id]
).map_err(pg_err)?;
let posts = rows.iter().map(|row| row.get(0)).collect();
Ok(posts)
}
/// Retrieve posts for a given group.
#[pyfunction]
pub fn get_group_posts(db_url: &str, group_id: i32) -> PyResult<Vec<String>> {
let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?;
let rows = client.query(
"SELECT content FROM posts WHERE group_id = $1 ORDER BY created_at DESC",
&[&group_id]
).map_err(pg_err)?;
let posts = rows.iter().map(|row| row.get(0)).collect();
Ok(posts)
}
/// Retrieve a global feed of posts (optionally filtering by user_id if needed).
#[pyfunction]
pub fn get_feed(db_url: &str) -> PyResult<Vec<Post>> {
let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?;
let rows = client.query(
"SELECT p.id, p.user_id, u.name, p.content, p.created_at
FROM posts p
JOIN users u ON p.user_id = u.id
WHERE p.group_id IS NULL
ORDER BY p.created_at DESC",
&[]
).map_err(pg_err)?;
let mut posts = Vec::new();
for row in rows {
let id: Option<i32> = row.get(0);
let user_id: i32 = row.get(1);
let user_name: Option<String> = Some(row.get(2));
let content: String = row.get(3);
let created_at: SystemTime = row.get(4); // Retrieves timestamp from DB
let created_at = DateTime::<Utc>::from(created_at).to_rfc3339(); // Convert to String
posts.push(Post {
id,
user_id,
user_name,
content,
created_at, // Now a properly formatted String
});
}
Ok(posts)
}
......@@ -8,7 +8,7 @@ use pyo3::prelude::*;
fn rust_crud_api(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<models::User>()?;
m.add_class::<models::Group>()?;
m.add_class::<models::Post>()?;
// Authentication functions
m.add_function(wrap_pyfunction!(auth::generate_jwt, m)?)?;
m.add_function(wrap_pyfunction!(auth::verify_jwt, m)?)?;
......@@ -17,19 +17,25 @@ fn rust_crud_api(_py: Python, m: &PyModule) -> PyResult<()> {
// Database functions
m.add_function(wrap_pyfunction!(db::init::init_db, m)?)?;
// Users
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::users::verify_user,m)?)?;
// Groups
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)?)?;
// Posts
m.add_function(wrap_pyfunction!(db::posts::create_post, m)?)?;
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)?)?;
Ok(())
}
pub mod user;
pub mod group;
pub mod post;
pub use user::User;
pub use group::Group;
pub use post::Post;
use pyo3::prelude::*;
use serde::{Serialize, Deserialize};
#[pyclass]
#[derive(Serialize, Deserialize, Debug)]
pub struct Post {
#[pyo3(get, set)]
pub id: Option<i32>,
#[pyo3(get,set)]
pub user_id: i32,
#[pyo3(get,set)]
pub user_name: Option<String>,
#[pyo3(get, set)]
pub content: String,
#[pyo3(get,set)]
pub created_at: String,
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment