194 lines
5.6 KiB
Rust
Executable file
194 lines
5.6 KiB
Rust
Executable file
use crate::db::{db_get_latest_posts, db_get_post, db_new_post};
|
|
use crate::jwt::Authentication;
|
|
use crate::types::{NewPost, PostQueryParams};
|
|
use crate::ServerState;
|
|
|
|
use actix_web::web::{Data, Path, Query};
|
|
use actix_web::{delete, HttpRequest};
|
|
use actix_web::{get, post, web::Json, HttpResponse, Responder, Result};
|
|
use log::info;
|
|
|
|
/// Gets all posts from the database, query parameters are optional
|
|
/// If limit is not specified, it defaults to a sane value
|
|
#[get("/posts")]
|
|
pub async fn get_posts(
|
|
query: Query<PostQueryParams>,
|
|
state: Data<ServerState>,
|
|
) -> Result<impl Responder> {
|
|
if let (Some(lim), Some(ofs)) = (query.limit, query.offset) {
|
|
return Ok(HttpResponse::Ok()
|
|
.json(db_get_latest_posts(&state.pool, std::cmp::min(lim, 30), ofs).await));
|
|
}
|
|
Ok(HttpResponse::Ok().json(db_get_latest_posts(&state.pool, 30, 0).await))
|
|
}
|
|
|
|
/// Creates a new post, requires a token in release mode
|
|
#[post("/posts")]
|
|
pub async fn new_post(
|
|
new_post: Json<NewPost>,
|
|
state: Data<ServerState>,
|
|
auth: Data<Authentication>,
|
|
) -> Result<impl Responder> {
|
|
let user_claims = auth.decode(&new_post.token);
|
|
|
|
if let Err(e) = user_claims {
|
|
info!("Error validating token: {}", e);
|
|
return Ok(HttpResponse::BadRequest().json("Error"));
|
|
}
|
|
|
|
// Bail if the token is invalid
|
|
let claims = user_claims.unwrap();
|
|
info!("User {:?} created a new post", &claims.sub);
|
|
|
|
let content = new_post.content.clone();
|
|
let username = claims.sub.clone();
|
|
|
|
// This one is avoidable if we just store the user id in the token
|
|
let userid = sqlx::query!("SELECT id FROM users WHERE username = $1", username)
|
|
.fetch_one(&state.pool)
|
|
.await
|
|
.unwrap()
|
|
.id;
|
|
|
|
// By now we know that the token is valid, so we can create the post
|
|
return match db_new_post(userid, &content, &state.pool).await {
|
|
Some(post) => {
|
|
info!("Created post {:?}", post.id);
|
|
Ok(HttpResponse::Ok().json(post))
|
|
}
|
|
None => Ok(HttpResponse::InternalServerError().json("Error")),
|
|
};
|
|
}
|
|
|
|
#[post("/posts/{id}/engage")]
|
|
pub async fn engage_post(
|
|
path: Path<i64>,
|
|
state: Data<ServerState>,
|
|
auth: Data<Authentication>,
|
|
req: HttpRequest,
|
|
) -> Result<impl Responder> {
|
|
// Token from header
|
|
let token = req
|
|
.headers()
|
|
.get("Authorization")
|
|
.unwrap()
|
|
.to_str()
|
|
.unwrap();
|
|
|
|
// Remove the Bearer prefix
|
|
let token = token.replace("Bearer ", "");
|
|
let claims = auth.decode(&token);
|
|
|
|
if let Err(e) = claims {
|
|
info!("Error validating token: {}", e);
|
|
return Ok(HttpResponse::BadRequest().json("Error"));
|
|
}
|
|
|
|
let post_id = path.into_inner();
|
|
let username = claims.unwrap().sub;
|
|
|
|
let q = sqlx::query!(
|
|
"INSERT INTO engagements (post_id, user_id) VALUES ($1, (SELECT id FROM users WHERE username = $2))",
|
|
post_id,
|
|
username
|
|
).execute(&state.pool).await;
|
|
|
|
match q {
|
|
Ok(_) => (),
|
|
Err(e) => {
|
|
info!("Error engaging post: {}", e);
|
|
return Ok(HttpResponse::InternalServerError().json("Error"));
|
|
}
|
|
}
|
|
|
|
// Get engagement count
|
|
let q = sqlx::query!(
|
|
"SELECT COUNT(*) FROM engagements WHERE post_id = $1",
|
|
post_id
|
|
)
|
|
.fetch_one(&state.pool)
|
|
.await;
|
|
|
|
match q {
|
|
Ok(count) => Ok(HttpResponse::Ok().json(count.count)),
|
|
Err(e) => {
|
|
info!("Error getting engagements: {}", e);
|
|
Ok(HttpResponse::InternalServerError().json("Error"))
|
|
}
|
|
}
|
|
}
|
|
|
|
#[delete("/posts/{id}")]
|
|
pub async fn delete_post(
|
|
path: Path<i64>,
|
|
state: Data<ServerState>,
|
|
auth: Data<Authentication>,
|
|
req: HttpRequest,
|
|
) -> Result<impl Responder> {
|
|
let post_id = path.into_inner();
|
|
let token = req
|
|
.headers()
|
|
.get("Authorization")
|
|
.unwrap()
|
|
.to_str()
|
|
.unwrap();
|
|
|
|
// Remove the Bearer prefix
|
|
let token = token.replace("Bearer ", "");
|
|
let claims = auth.decode(&token);
|
|
|
|
if let Err(e) = claims {
|
|
info!("Error validating token: {}", e);
|
|
return Ok(HttpResponse::BadRequest().json("Error"));
|
|
}
|
|
|
|
let username = claims.unwrap().sub;
|
|
|
|
let q = sqlx::query!(
|
|
"DELETE FROM posts WHERE id = $1 AND user_id = (SELECT id FROM users WHERE username = $2)",
|
|
post_id,
|
|
username
|
|
)
|
|
.execute(&state.pool)
|
|
.await;
|
|
|
|
match q {
|
|
Ok(q) => {
|
|
// Does this include cascading deletes?
|
|
if q.rows_affected() == 1 {
|
|
Ok(HttpResponse::Ok().json("Deleted"))
|
|
} else {
|
|
Ok(HttpResponse::Forbidden().json("Forbidden"))
|
|
}
|
|
}
|
|
Err(e) => {
|
|
info!("Error deleting post: {}", e);
|
|
Ok(HttpResponse::InternalServerError().json("Error"))
|
|
}
|
|
}
|
|
}
|
|
|
|
#[get("/posts/{id}/engage")]
|
|
pub async fn get_engagements(path: Path<i64>, state: Data<ServerState>) -> Result<impl Responder> {
|
|
let id = path.into_inner();
|
|
let q = sqlx::query!("SELECT COUNT(*) FROM engagements WHERE post_id = $1", id)
|
|
.fetch_one(&state.pool)
|
|
.await;
|
|
|
|
match q {
|
|
Ok(count) => Ok(HttpResponse::Ok().json(count.count)),
|
|
Err(e) => {
|
|
info!("Error getting engagements: {}", e);
|
|
Ok(HttpResponse::InternalServerError().json("Error"))
|
|
}
|
|
}
|
|
}
|
|
|
|
#[get("posts/{id}")]
|
|
pub async fn post_by_id(path: Path<i64>, state: Data<ServerState>) -> Result<impl Responder> {
|
|
let id = path.into_inner();
|
|
match db_get_post(id, &state.pool).await {
|
|
Some(post) => Ok(HttpResponse::Ok().json(post)),
|
|
None => Ok(HttpResponse::NotFound().json("Error")),
|
|
}
|
|
}
|