mirror of
https://codeberg.org/likwid/likwid.git
synced 2026-03-27 03:03:09 +00:00
192 lines
5.4 KiB
Rust
192 lines
5.4 KiB
Rust
use axum::{
|
|
extract::{Path, State},
|
|
http::StatusCode,
|
|
routing::{get, put},
|
|
Json, Router,
|
|
};
|
|
use chrono::{DateTime, Utc};
|
|
use serde::{Deserialize, Serialize};
|
|
use sqlx::PgPool;
|
|
use uuid::Uuid;
|
|
|
|
use crate::auth::AuthUser;
|
|
use crate::models::user::UserResponse;
|
|
|
|
pub fn router(pool: PgPool) -> Router {
|
|
Router::new()
|
|
.route("/api/users", get(list_users))
|
|
.route("/api/users/{username}", get(get_user_profile))
|
|
.route("/api/users/{username}/votes", get(get_user_votes))
|
|
.route("/api/users/me/profile", put(update_profile))
|
|
.with_state(pool)
|
|
}
|
|
|
|
async fn list_users(State(pool): State<PgPool>) -> Result<Json<Vec<UserResponse>>, String> {
|
|
let users = sqlx::query_as!(
|
|
crate::models::User,
|
|
"SELECT * FROM users WHERE is_active = true ORDER BY created_at DESC LIMIT 100"
|
|
)
|
|
.fetch_all(&pool)
|
|
.await
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
Ok(Json(users.into_iter().map(UserResponse::from).collect()))
|
|
}
|
|
|
|
#[derive(Debug, Serialize)]
|
|
pub struct UserProfile {
|
|
pub id: Uuid,
|
|
pub username: String,
|
|
pub display_name: Option<String>,
|
|
pub created_at: DateTime<Utc>,
|
|
pub communities: Vec<CommunityMembership>,
|
|
pub proposal_count: i64,
|
|
pub comment_count: i64,
|
|
}
|
|
|
|
#[derive(Debug, Serialize)]
|
|
pub struct CommunityMembership {
|
|
pub id: Uuid,
|
|
pub name: String,
|
|
pub slug: String,
|
|
pub role: String,
|
|
}
|
|
|
|
async fn get_user_profile(
|
|
Path(username): Path<String>,
|
|
State(pool): State<PgPool>,
|
|
) -> Result<Json<UserProfile>, (StatusCode, String)> {
|
|
let user = sqlx::query!(
|
|
"SELECT id, username, display_name, created_at FROM users WHERE username = $1 AND is_active = true",
|
|
username
|
|
)
|
|
.fetch_optional(&pool)
|
|
.await
|
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
|
|
.ok_or((StatusCode::NOT_FOUND, "User not found".to_string()))?;
|
|
|
|
let communities = sqlx::query!(
|
|
r#"
|
|
SELECT c.id, c.name, c.slug, cm.role
|
|
FROM communities c
|
|
JOIN community_members cm ON c.id = cm.community_id
|
|
WHERE cm.user_id = $1 AND c.is_active = true
|
|
ORDER BY cm.joined_at DESC
|
|
"#,
|
|
user.id
|
|
)
|
|
.fetch_all(&pool)
|
|
.await
|
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
|
|
|
let proposal_count = sqlx::query_scalar!(
|
|
"SELECT COUNT(*) FROM proposals WHERE author_id = $1",
|
|
user.id
|
|
)
|
|
.fetch_one(&pool)
|
|
.await
|
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
|
|
.unwrap_or(0);
|
|
|
|
let comment_count = sqlx::query_scalar!(
|
|
"SELECT COUNT(*) FROM comments WHERE author_id = $1",
|
|
user.id
|
|
)
|
|
.fetch_one(&pool)
|
|
.await
|
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
|
|
.unwrap_or(0);
|
|
|
|
Ok(Json(UserProfile {
|
|
id: user.id,
|
|
username: user.username,
|
|
display_name: user.display_name,
|
|
created_at: user.created_at,
|
|
communities: communities
|
|
.into_iter()
|
|
.map(|c| CommunityMembership {
|
|
id: c.id,
|
|
name: c.name,
|
|
slug: c.slug,
|
|
role: c.role,
|
|
})
|
|
.collect(),
|
|
proposal_count,
|
|
comment_count,
|
|
}))
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct UpdateProfile {
|
|
pub display_name: Option<String>,
|
|
}
|
|
|
|
async fn update_profile(
|
|
auth: AuthUser,
|
|
State(pool): State<PgPool>,
|
|
Json(payload): Json<UpdateProfile>,
|
|
) -> Result<Json<serde_json::Value>, (StatusCode, String)> {
|
|
sqlx::query!(
|
|
"UPDATE users SET display_name = $1 WHERE id = $2",
|
|
payload.display_name,
|
|
auth.user_id
|
|
)
|
|
.execute(&pool)
|
|
.await
|
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
|
|
|
Ok(Json(serde_json::json!({ "status": "updated" })))
|
|
}
|
|
|
|
#[derive(Debug, Serialize)]
|
|
pub struct UserVote {
|
|
pub proposal_id: Uuid,
|
|
pub proposal_title: String,
|
|
pub community_name: String,
|
|
pub option_label: String,
|
|
pub voted_at: DateTime<Utc>,
|
|
}
|
|
|
|
async fn get_user_votes(
|
|
Path(username): Path<String>,
|
|
State(pool): State<PgPool>,
|
|
) -> Result<Json<Vec<UserVote>>, (StatusCode, String)> {
|
|
let user = sqlx::query!("SELECT id FROM users WHERE username = $1", username)
|
|
.fetch_optional(&pool)
|
|
.await
|
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
|
|
.ok_or((StatusCode::NOT_FOUND, "User not found".to_string()))?;
|
|
|
|
let votes = sqlx::query!(
|
|
r#"
|
|
SELECT v.created_at as voted_at, po.label as option_label,
|
|
p.id as proposal_id, p.title as proposal_title,
|
|
c.name as community_name
|
|
FROM votes v
|
|
JOIN voting_identities vi ON v.voter_id = vi.id
|
|
JOIN proposal_options po ON v.option_id = po.id
|
|
JOIN proposals p ON po.proposal_id = p.id
|
|
JOIN communities c ON p.community_id = c.id
|
|
WHERE vi.user_id = $1
|
|
ORDER BY v.created_at DESC
|
|
LIMIT 20
|
|
"#,
|
|
user.id
|
|
)
|
|
.fetch_all(&pool)
|
|
.await
|
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
|
|
|
let result = votes
|
|
.into_iter()
|
|
.map(|v| UserVote {
|
|
proposal_id: v.proposal_id,
|
|
proposal_title: v.proposal_title,
|
|
community_name: v.community_name,
|
|
option_label: v.option_label,
|
|
voted_at: v.voted_at,
|
|
})
|
|
.collect();
|
|
|
|
Ok(Json(result))
|
|
}
|