added room invites in backend
This commit is contained in:
@@ -61,7 +61,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
// )
|
||||
.layer(cors);
|
||||
|
||||
let port = var("CHATAPP_SERVER_PORT").unwrap_or_else(|_| "8081".to_string());
|
||||
let port = var("CHATAPP_SERVER_PORT").unwrap_or_else(|_| "8080".to_string());
|
||||
let addr = format!("0.0.0.0:{port}");
|
||||
|
||||
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
|
||||
|
||||
@@ -77,8 +77,7 @@ async fn list_messages(
|
||||
.bind(room_id)
|
||||
.fetch_all(&db)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("failed to list messages: {e}");
|
||||
.map_err(|_| {
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"Failed to list messages".into(),
|
||||
@@ -128,12 +127,7 @@ async fn create_message(
|
||||
.bind(&payload.content)
|
||||
.fetch_one(&db)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
(
|
||||
StatusCode::BAD_REQUEST,
|
||||
format!("Could not create message: {e}"),
|
||||
)
|
||||
})?;
|
||||
.map_err(|_| (StatusCode::BAD_REQUEST, "Could not create message".into()))?;
|
||||
|
||||
let sender_name = username_from_uuid(&db, claims.sub).await?;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use axum::{
|
||||
use sqlx::{PgPool, Pool, Postgres};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::db::user_id_from_uuid;
|
||||
use crate::db::{id_from_username, user_id_from_uuid, username_from_id};
|
||||
use crate::{auth::verify_jwt, db::room_id_from_uuid};
|
||||
|
||||
#[derive(sqlx::FromRow, serde::Serialize)]
|
||||
@@ -24,11 +24,33 @@ pub struct NewRoomPayload {
|
||||
pub global: bool,
|
||||
}
|
||||
|
||||
#[derive(sqlx::FromRow, serde::Serialize)]
|
||||
pub struct RoomInvite {
|
||||
pub room_uuid: Uuid,
|
||||
pub sender_uuid: Uuid,
|
||||
pub sender_username: String,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct SendRoomInvitePayload {
|
||||
pub room_uuid: Uuid,
|
||||
pub receiver_username: String,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct AcceptRoomInvitePayload {
|
||||
pub room_uuid: Uuid,
|
||||
pub sender_uuid: Uuid,
|
||||
}
|
||||
|
||||
pub fn routes() -> Router {
|
||||
Router::new()
|
||||
.route("/rooms/{user_uuid}", get(list_rooms))
|
||||
.route("/rooms", get(list_rooms))
|
||||
.route("/rooms", post(create_room))
|
||||
// .route("/rooms/{user_uuid}/{room_id}", get(get_room))
|
||||
.route("/rooms/{room_id}", get(get_room))
|
||||
.route("/rooms/invites", get(list_invites))
|
||||
.route("/rooms/invite", post(send_invite))
|
||||
.route("/rooms/join", post(accept_request))
|
||||
}
|
||||
|
||||
pub async fn is_member(user_id: i32, room_id: i32, db: &Pool<Postgres>) -> bool {
|
||||
@@ -52,12 +74,11 @@ pub async fn is_member(user_id: i32, room_id: i32, db: &Pool<Postgres>) -> bool
|
||||
}
|
||||
|
||||
async fn list_rooms(
|
||||
Path(user_uuid): Path<Uuid>,
|
||||
headers: HeaderMap,
|
||||
Extension(db): Extension<PgPool>,
|
||||
) -> Result<Json<Vec<Room>>, (StatusCode, String)> {
|
||||
let claims = verify_jwt(headers)?;
|
||||
if claims.sub != user_uuid {
|
||||
if claims.sub != claims.sub {
|
||||
return Err((StatusCode::FORBIDDEN, "Forbidden".to_string()));
|
||||
}
|
||||
|
||||
@@ -136,29 +157,200 @@ async fn create_room(
|
||||
))
|
||||
}
|
||||
|
||||
// async fn get_room(
|
||||
// Path((user_uuid, room_uuid)): Path<(Uuid, Uuid)>,
|
||||
// headers: HeaderMap,
|
||||
// Extension(db): Extension<PgPool>,
|
||||
// ) -> Result<Json<Room>, (StatusCode, String)> {
|
||||
// let claims = verify_jwt(headers)?;
|
||||
// if claims.sub != user_uuid {
|
||||
// return Err((StatusCode::FORBIDDEN, "Forbidden".to_string()));
|
||||
// }
|
||||
//
|
||||
// let user_id = user_id_from_uuid(&db, user_uuid).await?;
|
||||
//
|
||||
// let room: Room =
|
||||
// sqlx::query_as("SELECT uuid, owner, name FROM room_ WHERE uuid = $1 AND owner = $2")
|
||||
// .bind(room_uuid)
|
||||
// .bind(user_id)
|
||||
// .fetch_one(&db)
|
||||
// .await
|
||||
// .map_err(|_| (StatusCode::NOT_FOUND, "Room not found".to_string()))?;
|
||||
//
|
||||
// Ok(Json(Room {
|
||||
// uuid: room_uuid,
|
||||
// owner: room.owner,
|
||||
// name: room.name,
|
||||
// }))
|
||||
// }
|
||||
async fn get_room(
|
||||
Path(room_uuid): Path<Uuid>,
|
||||
headers: HeaderMap,
|
||||
Extension(db): Extension<PgPool>,
|
||||
) -> Result<Json<Room>, (StatusCode, String)> {
|
||||
let claims = verify_jwt(headers)?;
|
||||
|
||||
let user_id = user_id_from_uuid(&db, claims.sub).await?;
|
||||
|
||||
let room: Room = sqlx::query_as(
|
||||
r#"
|
||||
SELECT uuid, u.name AS owner_name, r.name, r.global
|
||||
FROM room_ r
|
||||
JOIN user u ON u.id = r.owner
|
||||
WHERE uuid = $1 AND owner = $2
|
||||
"#,
|
||||
)
|
||||
.bind(room_uuid)
|
||||
.bind(user_id)
|
||||
.fetch_one(&db)
|
||||
.await
|
||||
.map_err(|_| (StatusCode::NOT_FOUND, "Room not found".to_string()))?;
|
||||
|
||||
Ok(Json(Room {
|
||||
uuid: room_uuid,
|
||||
owner_name: room.owner_name,
|
||||
name: room.name,
|
||||
global: room.global,
|
||||
}))
|
||||
}
|
||||
|
||||
async fn list_invites(
|
||||
headers: HeaderMap,
|
||||
Extension(db): Extension<PgPool>,
|
||||
) -> Result<Json<Vec<RoomInvite>>, (StatusCode, String)> {
|
||||
let claims = verify_jwt(headers)?;
|
||||
let user_id = user_id_from_uuid(&db, claims.sub).await?;
|
||||
|
||||
let requests = sqlx::query_as::<_, RoomInvite>(
|
||||
r#"
|
||||
SELECT r.uuid AS room_uuid, u.uuid AS sender_uuid, u.username AS sender_username
|
||||
FROM room_invite_ AS i
|
||||
JOIN user_ u ON u.id = i.sender
|
||||
JOIN room_ r ON r.id = i.room
|
||||
WHERE i.receiver = $1
|
||||
"#,
|
||||
)
|
||||
.bind(user_id)
|
||||
.fetch_all(&db)
|
||||
.await
|
||||
.map_err(|_| {
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"Could not list room invites".into(),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(Json(requests))
|
||||
}
|
||||
|
||||
async fn send_invite(
|
||||
headers: HeaderMap,
|
||||
Extension(db): Extension<PgPool>,
|
||||
Json(payload): Json<SendRoomInvitePayload>,
|
||||
) -> Result<(StatusCode, Json<RoomInvite>), (StatusCode, String)> {
|
||||
let claims = verify_jwt(headers)?;
|
||||
|
||||
let sender_id = user_id_from_uuid(&db, claims.sub).await?;
|
||||
let receiver_id = id_from_username(&db, payload.receiver_username).await?;
|
||||
let room_id = room_id_from_uuid(&db, payload.room_uuid).await?;
|
||||
|
||||
if sender_id == receiver_id {
|
||||
return Err((
|
||||
StatusCode::BAD_REQUEST,
|
||||
"Cannot send a room invite to yourself".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let is_already_member = sqlx::query_scalar::<_, bool>(
|
||||
r#"
|
||||
SELECT EXISTS (
|
||||
SELECT 1 FROM membership_
|
||||
WHERE user_id = $1
|
||||
AND room = $2
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.bind(receiver_id)
|
||||
.bind(room_id)
|
||||
.fetch_one(&db)
|
||||
.await
|
||||
.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Database error".into()))?;
|
||||
|
||||
if is_already_member {
|
||||
return Err((
|
||||
StatusCode::CONFLICT,
|
||||
"This user is already a member of this room".into(),
|
||||
));
|
||||
}
|
||||
|
||||
sqlx::query("INSERT INTO room_invite_ (sender, receiver, room) VALUES ($1, $2, $3)")
|
||||
.bind(sender_id)
|
||||
.bind(receiver_id)
|
||||
.bind(room_id)
|
||||
.execute(&db)
|
||||
.await
|
||||
.map_err(|_| (StatusCode::CONFLICT, "Request already exists".into()))?;
|
||||
|
||||
tracing::info!("bro");
|
||||
|
||||
Ok((
|
||||
StatusCode::CREATED,
|
||||
Json(RoomInvite {
|
||||
room_uuid: payload.room_uuid,
|
||||
sender_uuid: claims.sub,
|
||||
sender_username: username_from_id(&db, receiver_id).await?,
|
||||
}),
|
||||
))
|
||||
}
|
||||
|
||||
async fn accept_request(
|
||||
headers: HeaderMap,
|
||||
Extension(db): Extension<PgPool>,
|
||||
Json(payload): Json<AcceptRoomInvitePayload>,
|
||||
) -> Result<(StatusCode, Json<Room>), (StatusCode, String)> {
|
||||
let claims = verify_jwt(headers)?;
|
||||
|
||||
let receiver_id = user_id_from_uuid(&db, claims.sub).await?;
|
||||
let sender_id = user_id_from_uuid(&db, payload.sender_uuid).await?;
|
||||
|
||||
let mut tx = db
|
||||
.begin()
|
||||
.await
|
||||
.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "DB error".into()))?;
|
||||
|
||||
let rows = sqlx::query(
|
||||
r#"
|
||||
DELETE FROM room_invite_
|
||||
WHERE sender = $1 AND receiver = $2
|
||||
"#,
|
||||
)
|
||||
.bind(sender_id)
|
||||
.bind(receiver_id)
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "DB error".into()))?
|
||||
.rows_affected();
|
||||
|
||||
if rows == 0 {
|
||||
return Err((StatusCode::NOT_FOUND, "No such invite".into()));
|
||||
}
|
||||
|
||||
let room_id = room_id_from_uuid(&db, payload.room_uuid).await?;
|
||||
|
||||
sqlx::query("INSERT INTO membership_ (user_id, room) VALUES ($1, $2)")
|
||||
.bind(receiver_id)
|
||||
.bind(room_id)
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.map_err(|_| {
|
||||
(
|
||||
StatusCode::CONFLICT,
|
||||
"Error creating room membership".into(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let room: Room = sqlx::query_as(
|
||||
r#"
|
||||
SELECT r.uuid, u.username AS owner_name, r.name, r.global
|
||||
FROM room_ r
|
||||
JOIN user_ u ON u.id = r.owner
|
||||
WHERE r.id = $1 AND r.owner = $2
|
||||
"#,
|
||||
)
|
||||
.bind(room_id)
|
||||
.bind(sender_id)
|
||||
.fetch_one(&db)
|
||||
.await
|
||||
.map_err(|_| (StatusCode::NOT_FOUND, "Room not found".into()))?;
|
||||
|
||||
tx.commit().await.map_err(|_| {
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"Could not accept room invite".into(),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok((
|
||||
StatusCode::CREATED,
|
||||
Json(Room {
|
||||
uuid: payload.room_uuid,
|
||||
owner_name: room.owner_name,
|
||||
name: room.name,
|
||||
global: room.global,
|
||||
}),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -112,8 +112,12 @@ pub async fn register_user(
|
||||
));
|
||||
}
|
||||
|
||||
let password_hash =
|
||||
hash_password(&payload.password).map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e))?;
|
||||
let password_hash = hash_password(&payload.password).map_err(|_| {
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"Failed to hash password".into(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let user_uuid = uuid::Uuid::now_v7();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user