mirror of
https://codeberg.org/likwid/likwid.git
synced 2026-06-25 15:37:42 +00:00
181 lines
4.9 KiB
Rust
181 lines
4.9 KiB
Rust
|
|
use axum::{
|
||
|
|
extract::{Path, State},
|
||
|
|
http::StatusCode,
|
||
|
|
routing::get,
|
||
|
|
Extension,
|
||
|
|
Json, Router,
|
||
|
|
};
|
||
|
|
use chrono::{DateTime, Utc};
|
||
|
|
use serde::{Deserialize, Serialize};
|
||
|
|
use serde_json::json;
|
||
|
|
use sqlx::PgPool;
|
||
|
|
use std::sync::Arc;
|
||
|
|
use uuid::Uuid;
|
||
|
|
|
||
|
|
use crate::auth::AuthUser;
|
||
|
|
use crate::plugins::HookContext;
|
||
|
|
use crate::plugins::PluginManager;
|
||
|
|
use crate::plugins::PluginError;
|
||
|
|
|
||
|
|
#[derive(Debug, Serialize)]
|
||
|
|
pub struct Comment {
|
||
|
|
pub id: Uuid,
|
||
|
|
pub proposal_id: Uuid,
|
||
|
|
pub author_id: Uuid,
|
||
|
|
pub author_name: String,
|
||
|
|
pub content: String,
|
||
|
|
pub parent_id: Option<Uuid>,
|
||
|
|
pub created_at: DateTime<Utc>,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Deserialize)]
|
||
|
|
pub struct CreateComment {
|
||
|
|
pub content: String,
|
||
|
|
pub parent_id: Option<Uuid>,
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn router(pool: PgPool) -> Router {
|
||
|
|
Router::new()
|
||
|
|
.route("/api/proposals/{proposal_id}/comments", get(list_comments).post(create_comment))
|
||
|
|
.with_state(pool)
|
||
|
|
}
|
||
|
|
|
||
|
|
async fn list_comments(
|
||
|
|
Path(proposal_id): Path<Uuid>,
|
||
|
|
State(pool): State<PgPool>,
|
||
|
|
) -> Result<Json<Vec<Comment>>, (StatusCode, String)> {
|
||
|
|
let comments = sqlx::query!(
|
||
|
|
r#"
|
||
|
|
SELECT c.id, c.proposal_id, c.author_id, c.content, c.parent_id, c.created_at,
|
||
|
|
u.username as author_name
|
||
|
|
FROM comments c
|
||
|
|
JOIN users u ON c.author_id = u.id
|
||
|
|
WHERE c.proposal_id = $1
|
||
|
|
ORDER BY c.created_at ASC
|
||
|
|
"#,
|
||
|
|
proposal_id
|
||
|
|
)
|
||
|
|
.fetch_all(&pool)
|
||
|
|
.await
|
||
|
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||
|
|
|
||
|
|
let result = comments
|
||
|
|
.into_iter()
|
||
|
|
.map(|c| Comment {
|
||
|
|
id: c.id,
|
||
|
|
proposal_id: c.proposal_id,
|
||
|
|
author_id: c.author_id,
|
||
|
|
author_name: c.author_name,
|
||
|
|
content: c.content,
|
||
|
|
parent_id: c.parent_id,
|
||
|
|
created_at: c.created_at,
|
||
|
|
})
|
||
|
|
.collect();
|
||
|
|
|
||
|
|
Ok(Json(result))
|
||
|
|
}
|
||
|
|
|
||
|
|
async fn create_comment(
|
||
|
|
auth: AuthUser,
|
||
|
|
Path(proposal_id): Path<Uuid>,
|
||
|
|
State(pool): State<PgPool>,
|
||
|
|
Extension(plugins): Extension<Arc<PluginManager>>,
|
||
|
|
Json(req): Json<CreateComment>,
|
||
|
|
) -> Result<Json<Comment>, (StatusCode, String)> {
|
||
|
|
// Get proposal author for notification
|
||
|
|
let proposal = sqlx::query!(
|
||
|
|
"SELECT author_id, community_id, title FROM proposals WHERE id = $1",
|
||
|
|
proposal_id
|
||
|
|
)
|
||
|
|
.fetch_optional(&pool)
|
||
|
|
.await
|
||
|
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
|
||
|
|
.ok_or((StatusCode::NOT_FOUND, "Proposal not found".to_string()))?;
|
||
|
|
|
||
|
|
let filtered = plugins
|
||
|
|
.apply_filters(
|
||
|
|
"comment.create",
|
||
|
|
HookContext {
|
||
|
|
pool: pool.clone(),
|
||
|
|
community_id: Some(proposal.community_id),
|
||
|
|
actor_user_id: Some(auth.user_id),
|
||
|
|
},
|
||
|
|
json!({
|
||
|
|
"proposal_id": proposal_id,
|
||
|
|
"content": req.content,
|
||
|
|
"parent_id": req.parent_id,
|
||
|
|
}),
|
||
|
|
)
|
||
|
|
.await
|
||
|
|
.map_err(|e| match e {
|
||
|
|
PluginError::Message(m) => (StatusCode::BAD_REQUEST, m),
|
||
|
|
PluginError::Sqlx(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()),
|
||
|
|
})?;
|
||
|
|
|
||
|
|
let content = filtered
|
||
|
|
.get("content")
|
||
|
|
.and_then(|v| v.as_str())
|
||
|
|
.ok_or((
|
||
|
|
StatusCode::BAD_REQUEST,
|
||
|
|
"Invalid comment.create filter output".to_string(),
|
||
|
|
))?
|
||
|
|
.to_string();
|
||
|
|
|
||
|
|
let parent_id = match filtered.get("parent_id") {
|
||
|
|
Some(v) if v.is_null() => None,
|
||
|
|
Some(v) => v
|
||
|
|
.as_str()
|
||
|
|
.and_then(|s| Uuid::parse_str(s).ok())
|
||
|
|
.ok_or((
|
||
|
|
StatusCode::BAD_REQUEST,
|
||
|
|
"Invalid comment.create filter output".to_string(),
|
||
|
|
))
|
||
|
|
.map(Some)?,
|
||
|
|
None => None,
|
||
|
|
};
|
||
|
|
|
||
|
|
let comment = sqlx::query!(
|
||
|
|
r#"
|
||
|
|
INSERT INTO comments (proposal_id, author_id, content, parent_id)
|
||
|
|
VALUES ($1, $2, $3, $4)
|
||
|
|
RETURNING id, created_at
|
||
|
|
"#,
|
||
|
|
proposal_id,
|
||
|
|
auth.user_id,
|
||
|
|
content,
|
||
|
|
parent_id
|
||
|
|
)
|
||
|
|
.fetch_one(&pool)
|
||
|
|
.await
|
||
|
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||
|
|
|
||
|
|
plugins
|
||
|
|
.do_action(
|
||
|
|
"comment.created",
|
||
|
|
HookContext {
|
||
|
|
pool: pool.clone(),
|
||
|
|
community_id: Some(proposal.community_id),
|
||
|
|
actor_user_id: Some(auth.user_id),
|
||
|
|
},
|
||
|
|
serde_json::json!({
|
||
|
|
"proposal_id": proposal_id,
|
||
|
|
"proposal_title": proposal.title,
|
||
|
|
"proposal_author_id": proposal.author_id,
|
||
|
|
"commenter_id": auth.user_id,
|
||
|
|
"commenter_name": auth.username,
|
||
|
|
"content": content,
|
||
|
|
}),
|
||
|
|
)
|
||
|
|
.await;
|
||
|
|
|
||
|
|
Ok(Json(Comment {
|
||
|
|
id: comment.id,
|
||
|
|
proposal_id,
|
||
|
|
author_id: auth.user_id,
|
||
|
|
author_name: auth.username.clone(),
|
||
|
|
content,
|
||
|
|
parent_id,
|
||
|
|
created_at: comment.created_at,
|
||
|
|
}))
|
||
|
|
}
|