Skip to content

Commit

Permalink
Merge pull request #2 from tyr-rust-bootcamp/feature/v10-12-send-message
Browse files Browse the repository at this point in the history
feature: provide better openapi spec
  • Loading branch information
tyrchen authored Aug 18, 2024
2 parents c83f829 + 4378c49 commit 3cdc655
Show file tree
Hide file tree
Showing 14 changed files with 62 additions and 11 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 12 additions & 3 deletions chat_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,33 +43,42 @@ pub struct ChatUser {

#[derive(Debug, Clone, ToSchema, Serialize, Deserialize, PartialEq, PartialOrd, sqlx::Type)]
#[sqlx(type_name = "chat_type", rename_all = "snake_case")]
#[serde(rename_all = "camelCase")]
#[serde(rename_all(serialize = "camelCase"))]
pub enum ChatType {
#[serde(alias = "single", alias = "Single")]
Single,
#[serde(alias = "group", alias = "Group")]
Group,
#[serde(alias = "private_channel", alias = "privateChannel")]
PrivateChannel,
#[serde(alias = "public_channel", alias = "publicChannel")]
PublicChannel,
}

#[derive(Debug, Clone, FromRow, ToSchema, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(rename_all(serialize = "camelCase"))]
pub struct Chat {
pub id: i64,
#[serde(alias = "wsId")]
pub ws_id: i64,
pub name: Option<String>,
pub r#type: ChatType,
pub members: Vec<i64>,
#[serde(alias = "createdAt")]
pub created_at: DateTime<Utc>,
}

#[derive(Debug, Clone, FromRow, ToSchema, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(rename_all(serialize = "camelCase"))]
pub struct Message {
pub id: i64,
#[serde(alias = "chatId")]
pub chat_id: i64,
#[serde(alias = "senderId")]
pub sender_id: i64,
pub content: String,
pub files: Vec<String>,
#[serde(alias = "createdAt")]
pub created_at: DateTime<Utc>,
}

Expand Down
8 changes: 4 additions & 4 deletions chat_core/src/middlewares/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use tracing::warn;

#[derive(Debug, Deserialize)]
struct Params {
access_token: String,
token: String,
}

pub async fn verify_token<T>(State(state): State<T>, req: Request, next: Next) -> Response
Expand All @@ -28,7 +28,7 @@ where
Err(e) => {
if e.is_missing() {
match Query::<Params>::from_request_parts(&mut parts, &state).await {
Ok(params) => params.access_token.clone(),
Ok(params) => params.token.clone(),
Err(e) => {
let msg = format!("parse query params failed: {}", e);
warn!(msg);
Expand Down Expand Up @@ -114,7 +114,7 @@ mod tests {

// good token in query params
let req = Request::builder()
.uri(format!("/?access_token={}", token))
.uri(format!("/?token={}", token))
.body(Body::empty())?;
let res = app.clone().oneshot(req).await?;
assert_eq!(res.status(), StatusCode::OK);
Expand All @@ -134,7 +134,7 @@ mod tests {

// bad token in query params
let req = Request::builder()
.uri("/?access_token=bad-token")
.uri("/?token=bad-token")
.body(Body::empty())?;
let res = app.oneshot(req).await?;
assert_eq!(res.status(), StatusCode::FORBIDDEN);
Expand Down
3 changes: 2 additions & 1 deletion chat_server/src/handlers/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub struct AuthOutput {
(status = 200, description = "User created", body = AuthOutput),
)
)]
/// Create a new user in the chat system with email and password.
/// Create a new user in the chat system with email, password workspace and full name.
///
/// - If the email already exists, it will return 409.
/// - Otherwise, it will return 201 with a token.
Expand All @@ -33,6 +33,7 @@ pub(crate) async fn signup_handler(
Ok((StatusCode::CREATED, body))
}

/// Sign in a user with email and password.
#[utoipa::path(
post,
path = "/api/signin",
Expand Down
3 changes: 3 additions & 0 deletions chat_server/src/handlers/chat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use axum::{
};
use chat_core::User;

/// List all chats in the workspace of the user.
#[utoipa::path(
get,
path = "/api/chats",
Expand All @@ -25,6 +26,7 @@ pub(crate) async fn list_chat_handler(
Ok((StatusCode::OK, Json(chat)))
}

/// Create a new chat in the workspace of the user.
#[utoipa::path(
post,
path = "/api/chats",
Expand All @@ -46,6 +48,7 @@ pub(crate) async fn create_chat_handler(
Ok((StatusCode::CREATED, Json(chat)))
}

/// Get the chat info by id.
#[utoipa::path(
get,
path = "/api/chats/{id}",
Expand Down
16 changes: 16 additions & 0 deletions chat_server/src/handlers/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,21 @@ use tracing::{info, warn};
use crate::{AppError, AppState, ChatFile, CreateMessage, ListMessages};
use chat_core::User;

/// Send a new message in the chat.
#[utoipa::path(
post,
path = "/api/chats/{id}",
params(
("id" = u64, Path, description = "Chat id")
),
responses(
(status = 200, description = "List of messages", body = Message),
(status = 400, description = "Invalid input", body = ErrorOutput),
),
security(
("token" = [])
)
)]
pub(crate) async fn send_message_handler(
Extension(user): Extension<User>,
State(state): State<AppState>,
Expand All @@ -21,6 +36,7 @@ pub(crate) async fn send_message_handler(
Ok((StatusCode::CREATED, Json(msg)))
}

/// List all messages in the chat.
#[utoipa::path(
get,
path = "/api/chats/{id}/messages",
Expand Down
1 change: 1 addition & 0 deletions chat_server/src/handlers/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::{AppError, AppState};
use axum::{extract::State, response::IntoResponse, Extension, Json};
use chat_core::User;

/// List all users in the workspace.
#[utoipa::path(
get,
path = "/api/users",
Expand Down
1 change: 1 addition & 0 deletions chat_server/src/models/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use utoipa::{IntoParams, ToSchema};
#[derive(Debug, Clone, ToSchema, Serialize, Deserialize)]
pub struct CreateMessage {
pub content: String,
#[serde(default)]
pub files: Vec<String>,
}

Expand Down
1 change: 1 addition & 0 deletions chat_server/src/openapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub(crate) trait OpenApiRouter {
create_chat_handler,
get_chat_handler,
list_message_handler,
send_message_handler,
list_chat_users_handler,
),
components(
Expand Down
2 changes: 1 addition & 1 deletion chat_test/tests/chat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ impl NotifyServer {
.unwrap();
});

let mut es = EventSource::get(format!("http://{}/events?access_token={}", addr, token));
let mut es = EventSource::get(format!("http://{}/events?token={}", addr, token));

tokio::spawn(async move {
while let Some(event) = es.next().await {
Expand Down
1 change: 1 addition & 0 deletions notify_server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ sqlx = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true }
tokio-stream = { version = "0.1.15", features = ["sync"] }
tower-http = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
2 changes: 1 addition & 1 deletion notify_server/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ <h1>Server Sent Events</h1>

<script lang="javascript">
let token = 'eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3MTQ4NDU0MjEsImV4cCI6MTcxNTQ1MDIyMSwibmJmIjoxNzE0ODQ1NDIxLCJpc3MiOiJjaGF0X3NlcnZlciIsImF1ZCI6ImNoYXRfd2ViIiwiaWQiOjEsIndzX2lkIjoxLCJmdWxsbmFtZSI6IlR5ciBDaGVuIiwiZW1haWwiOiJ0Y2hlbkBhY21lLm9yZyIsImNyZWF0ZWRfYXQiOiIyMDI0LTA1LTA0VDE3OjE4OjQ3LjAwNDI3N1oifQ.8dDhp6o2mPWvOC9TnaEIVr6jeu3idKjGTBQ8OSou_LCA2t6C9Mw6HOxO1SZuLmFtDiJaVXPBO3hIuw2Slfk7BQ';
let source = new EventSource(`/events?access_token=${token}`);
let source = new EventSource(`/events?token=${token}`);
source.onmessage = function(event) {
console.log("Got:", event.data);
};
Expand Down
16 changes: 16 additions & 0 deletions notify_server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod notif;
mod sse;

use axum::{
http::Method,
middleware::from_fn_with_state,
response::{Html, IntoResponse},
routing::get,
Expand All @@ -17,6 +18,7 @@ use dashmap::DashMap;
use sse::sse_handler;
use std::{ops::Deref, sync::Arc};
use tokio::sync::broadcast;
use tower_http::cors::{self, CorsLayer};

pub use config::AppConfig;
pub use error::AppError;
Expand All @@ -38,9 +40,23 @@ const INDEX_HTML: &str = include_str!("../index.html");
pub async fn get_router(config: AppConfig) -> anyhow::Result<Router> {
let state = AppState::new(config);
notif::setup_pg_listener(state.clone()).await?;

let cors = CorsLayer::new()
// allow `GET` and `POST` when accessing the resource
.allow_methods([
Method::GET,
Method::POST,
Method::PATCH,
Method::DELETE,
Method::PUT,
])
.allow_origin(cors::Any)
.allow_headers(cors::Any);

let app = Router::new()
.route("/events", get(sse_handler))
.layer(from_fn_with_state(state.clone(), verify_token::<AppState>))
.layer(cors)
.route("/", get(index_handler))
.with_state(state);

Expand Down
3 changes: 2 additions & 1 deletion notify_server/src/sse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use futures::Stream;
use std::{convert::Infallible, time::Duration};
use tokio::sync::broadcast;
use tokio_stream::{wrappers::BroadcastStream, StreamExt};
use tracing::info;
use tracing::{debug, info};

const CHANNEL_CAPACITY: usize = 256;

Expand Down Expand Up @@ -37,6 +37,7 @@ pub(crate) async fn sse_handler(
AppEvent::NewMessage(_) => "NewMessage",
};
let v = serde_json::to_string(&v).expect("Failed to serialize event");
debug!("Sending event {}: {:?}", name, v);
Ok(Event::default().data(v).event(name))
});

Expand Down

0 comments on commit 3cdc655

Please sign in to comment.