added vibecoded swagger documentation with utoipa

This commit is contained in:
2026-07-06 12:34:46 +02:00
parent 19fffc8b30
commit f31eec26d0
12 changed files with 822 additions and 151 deletions
+176 -6
View File
@@ -10,10 +10,11 @@ use uuid::Uuid;
use crate::{MAX_ROOM_NAME_LENGTH, auth::verify_jwt, db::room_id_from_uuid, errors::APIError};
use crate::{
db::{id_from_username, room_name_from_uuid, user_id_from_uuid, username_from_id},
errors::ErrorResponse,
routes::users::UserProfile,
};
#[derive(sqlx::FromRow, serde::Serialize, serde::Deserialize)]
#[derive(sqlx::FromRow, serde::Serialize, serde::Deserialize, utoipa::ToSchema)]
pub struct Room {
pub uuid: Uuid,
pub name: String,
@@ -23,13 +24,13 @@ pub struct Room {
pub unread_count: i64,
}
#[derive(serde::Deserialize)]
#[derive(serde::Deserialize, utoipa::ToSchema)]
pub struct NewRoomPayload {
pub name: String,
pub global: bool,
}
#[derive(sqlx::FromRow, serde::Serialize)]
#[derive(sqlx::FromRow, serde::Serialize, utoipa::ToSchema)]
pub struct RoomInvite {
pub room_uuid: Uuid,
pub room_name: String,
@@ -37,19 +38,19 @@ pub struct RoomInvite {
pub sender_username: String,
}
#[derive(serde::Deserialize)]
#[derive(serde::Deserialize, utoipa::ToSchema)]
pub struct SendRoomInvitePayload {
pub room_uuid: Uuid,
pub receiver_username: String,
}
#[derive(serde::Deserialize)]
#[derive(serde::Deserialize, utoipa::ToSchema)]
pub struct AcceptRoomInvitePayload {
pub room_uuid: Uuid,
pub sender_uuid: Uuid,
}
#[derive(serde::Deserialize)]
#[derive(serde::Deserialize, utoipa::ToSchema)]
pub struct TransferOwnershipPayload {
pub room_uuid: Uuid,
pub new_owner_uuid: Uuid,
@@ -90,6 +91,19 @@ pub async fn is_member(user_id: i32, room_id: i32, db: &Pool<Postgres>) -> bool
.unwrap_or(false)
}
#[utoipa::path(
get,
path = "/rooms",
responses(
(status = 200, description = "Successfully listed rooms", body = [Room]),
(status = 401, description = "Missing authentication header or Invalid or expired token", body = ErrorResponse),
(status = 404, description = "User not found", body = ErrorResponse),
(status = 500, description = "Database error", body = ErrorResponse)
),
security(
("bearer_auth" = [])
)
)]
async fn list_rooms(
headers: HeaderMap,
Extension(db): Extension<PgPool>,
@@ -130,6 +144,21 @@ async fn list_rooms(
Ok(Json(rooms))
}
#[utoipa::path(
post,
path = "/rooms",
request_body = NewRoomPayload,
responses(
(status = 201, description = "Room successfully created", body = Room),
(status = 400, description = "Room name must be 0-35 characters long", body = ErrorResponse),
(status = 401, description = "Missing authentication header or Invalid or expired token", body = ErrorResponse),
(status = 404, description = "User not found or Room not found", body = ErrorResponse),
(status = 500, description = "Database error", body = ErrorResponse)
),
security(
("bearer_auth" = [])
)
)]
async fn create_room(
Extension(db): Extension<PgPool>,
headers: HeaderMap,
@@ -184,6 +213,23 @@ async fn create_room(
))
}
#[utoipa::path(
get,
path = "/rooms/{room_uuid}",
params(
("room_uuid" = Uuid, Path, description = "UUID of the room to retrieve")
),
responses(
(status = 200, description = "Room successfully retrieved", body = Room),
(status = 401, description = "Missing authentication header or Invalid or expired token", body = ErrorResponse),
(status = 403, description = "You are not a member of this room", body = ErrorResponse),
(status = 404, description = "User not found or Room not found", body = ErrorResponse),
(status = 500, description = "Database error", body = ErrorResponse)
),
security(
("bearer_auth" = [])
)
)]
async fn get_room(
Path(room_uuid): Path<Uuid>,
headers: HeaderMap,
@@ -246,6 +292,19 @@ async fn get_room(
}))
}
#[utoipa::path(
get,
path = "/rooms/invites",
responses(
(status = 200, description = "List of room invites successfully retrieved", body = [RoomInvite]),
(status = 401, description = "Missing authentication header or Invalid or expired token", body = ErrorResponse),
(status = 404, description = "User not found", body = ErrorResponse),
(status = 500, description = "Database error", body = ErrorResponse)
),
security(
("bearer_auth" = [])
)
)]
async fn list_invites(
headers: HeaderMap,
Extension(db): Extension<PgPool>,
@@ -273,6 +332,22 @@ async fn list_invites(
Ok(Json(requests))
}
#[utoipa::path(
post,
path = "/rooms/invite",
request_body = SendRoomInvitePayload,
responses(
(status = 201, description = "Room invite successfully sent", body = RoomInvite),
(status = 400, description = "Cannot invite yourself", body = ErrorResponse),
(status = 401, description = "Missing authentication header or Invalid or expired token", body = ErrorResponse),
(status = 404, description = "User not found or Room not found", body = ErrorResponse),
(status = 409, description = "User is already a member or Invite already sent", body = ErrorResponse),
(status = 500, description = "Database error", body = ErrorResponse)
),
security(
("bearer_auth" = [])
)
)]
async fn send_invite(
headers: HeaderMap,
Extension(db): Extension<PgPool>,
@@ -327,6 +402,21 @@ async fn send_invite(
))
}
#[utoipa::path(
post,
path = "/rooms/join",
request_body = AcceptRoomInvitePayload,
responses(
(status = 201, description = "Room invite successfully accepted", body = Room),
(status = 401, description = "Missing authentication header or Invalid or expired token", body = ErrorResponse),
(status = 404, description = "User not found, Room not found, or Invite not found", body = ErrorResponse),
(status = 409, description = "User is already a member", body = ErrorResponse),
(status = 500, description = "Database error", body = ErrorResponse)
),
security(
("bearer_auth" = [])
)
)]
async fn accept_request(
headers: HeaderMap,
Extension(db): Extension<PgPool>,
@@ -399,6 +489,20 @@ async fn accept_request(
))
}
#[utoipa::path(
post,
path = "/rooms/decline",
request_body = AcceptRoomInvitePayload,
responses(
(status = 201, description = "Room invite successfully declined"),
(status = 401, description = "Missing authentication header or Invalid or expired token", body = ErrorResponse),
(status = 404, description = "User not found or Invite not found", body = ErrorResponse),
(status = 500, description = "Database error", body = ErrorResponse)
),
security(
("bearer_auth" = [])
)
)]
async fn decline_request(
headers: HeaderMap,
Extension(db): Extension<PgPool>,
@@ -428,6 +532,23 @@ async fn decline_request(
Ok(StatusCode::CREATED)
}
#[utoipa::path(
post,
path = "/rooms/{room_uuid}/leave",
params(
("room_uuid" = Uuid, Path, description = "UUID of the room to leave")
),
responses(
(status = 200, description = "Successfully left the room"),
(status = 401, description = "Missing authentication header or Invalid or expired token", body = ErrorResponse),
(status = 403, description = "You are not a member of this room or Owner cannot leave the room without transferring ownership", body = ErrorResponse),
(status = 404, description = "User not found or Room not found", body = ErrorResponse),
(status = 500, description = "Database error", body = ErrorResponse)
),
security(
("bearer_auth" = [])
)
)]
async fn leave_room(
headers: HeaderMap,
Path(room_uuid): Path<Uuid>,
@@ -469,6 +590,21 @@ async fn leave_room(
Ok(StatusCode::OK)
}
#[utoipa::path(
post,
path = "/rooms/transfer-ownership",
request_body = TransferOwnershipPayload,
responses(
(status = 200, description = "Room ownership successfully transferred"),
(status = 401, description = "Missing authentication header or Invalid or expired token", body = ErrorResponse),
(status = 403, description = "You are not a member of this room", body = ErrorResponse),
(status = 404, description = "User not found or Room not found", body = ErrorResponse),
(status = 500, description = "Database error", body = ErrorResponse)
),
security(
("bearer_auth" = [])
)
)]
async fn transfer_ownership(
headers: HeaderMap,
Extension(db): Extension<PgPool>,
@@ -515,6 +651,23 @@ async fn transfer_ownership(
Ok(StatusCode::OK)
}
#[utoipa::path(
get,
path = "/rooms/{room_uuid}/members",
params(
("room_uuid" = Uuid, Path, description = "UUID of the room to list members of")
),
responses(
(status = 200, description = "Room members successfully retrieved", body = [UserProfile]),
(status = 401, description = "Missing authentication header or Invalid or expired token", body = ErrorResponse),
(status = 403, description = "You are not a member of this room or Cannot list members for global rooms", body = ErrorResponse),
(status = 404, description = "User not found or Room not found", body = ErrorResponse),
(status = 500, description = "Database error", body = ErrorResponse)
),
security(
("bearer_auth" = [])
)
)]
async fn list_members(
headers: HeaderMap,
Path(room_uuid): Path<Uuid>,
@@ -554,6 +707,23 @@ async fn list_members(
Ok((StatusCode::OK, Json(members)))
}
#[utoipa::path(
delete,
path = "/rooms/{room_uuid}/delete",
params(
("room_uuid" = Uuid, Path, description = "UUID of the room to delete")
),
responses(
(status = 200, description = "Room successfully deleted"),
(status = 401, description = "Missing authentication header or Invalid or expired token", body = ErrorResponse),
(status = 403, description = "You are not a member of this room", body = ErrorResponse),
(status = 404, description = "User not found or Room not found", body = ErrorResponse),
(status = 500, description = "Database error", body = ErrorResponse)
),
security(
("bearer_auth" = [])
)
)]
async fn delete_room(
headers: HeaderMap,
Path(room_uuid): Path<Uuid>,