Skip to content

Commit 5808496

Browse files
committed
Added simple rooms api
1 parent fb0bf10 commit 5808496

File tree

11 files changed

+364
-59
lines changed

11 files changed

+364
-59
lines changed

backend/src/main.rs

+10-5
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,13 @@ use web::{
2222
routes_login,
2323
};
2424

25-
use crate::web::{
26-
webtransport::{self, Certs},
27-
SESSION_COOKIE_KEY,
25+
use crate::{
26+
service::room,
27+
web::{
28+
routes_room,
29+
webtransport::{self, Certs},
30+
SESSION_COOKIE_KEY,
31+
},
2832
};
2933

3034
#[tokio::main]
@@ -50,6 +54,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
5054

5155
let auth_service = auth::Service::new(db.clone(), email_service);
5256
let session_service = session::Service::new(db.clone());
57+
let room_service = room::Service::new(db.clone());
5358

5459
let key = general_purpose::STANDARD
5560
.decode("mN1GR7dsQ+Bj8NFIA+n/uvSbBcdyvHnVdFuJSJrQJ3g2/8gGYaATt3Wv7j3xKpD07652no/eddRdD7sJTVjg4w==")
@@ -72,9 +77,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
7277
},
7378
};
7479

75-
let app = Router::new().route("/test", get(|| async { "Hello world" }));
7680

77-
let app = app
81+
let app = Router::new()
82+
.nest("/api/rooms", routes_room::router(room_service))
7883
.layer(middleware::from_fn(mw_ctx_require))
7984
.nest(
8085
"/api/auth",

backend/src/service/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
pub mod email;
22
pub mod error;
3+
pub mod room;
34
pub mod user;

backend/src/service/room.rs

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
use std::{collections::HashMap, default, sync::Arc};
2+
3+
use axum::extract::FromRef;
4+
use sqlx::{prelude::FromRow, PgPool};
5+
use time::PrimitiveDateTime;
6+
use tokio::sync::Mutex;
7+
use uuid::Uuid;
8+
9+
use super::{error::Error};
10+
11+
struct Session {
12+
current_people_listening: i32,
13+
current_people_playing: i32,
14+
max_people_playing: i32,
15+
}
16+
17+
#[derive(Clone, FromRef)]
18+
pub struct Service {
19+
db: PgPool,
20+
sessions: Arc<Mutex<HashMap<Uuid, Session>>>,
21+
}
22+
23+
#[derive(FromRow)]
24+
pub struct Room {
25+
pub id: Uuid,
26+
pub name: String,
27+
pub description: Option<String>,
28+
pub owner: String,
29+
pub private: bool,
30+
pub open: bool,
31+
pub max_people_playing: i32,
32+
pub created_at: PrimitiveDateTime,
33+
pub updated_at: PrimitiveDateTime,
34+
}
35+
36+
37+
impl Service {
38+
pub fn new(db: PgPool) -> Self {
39+
Self {
40+
db,
41+
sessions: Arc::new(Mutex::new(HashMap::new())),
42+
}
43+
}
44+
}
45+
46+
// crud operations
47+
impl Service {
48+
pub async fn create(
49+
&self,
50+
owner: String,
51+
name: String,
52+
description: Option<String>,
53+
private: bool,
54+
open: bool,
55+
max_people_playing: i32,
56+
) -> Result<Room, Error> {
57+
let id = Uuid::new_v4();
58+
let room = sqlx::query_as!(
59+
Room,
60+
r#"INSERT INTO rooms (id, owner, name, description, private, open, max_people_playing)
61+
VALUES ($1, $2, $3, $4, $5, $6, $7)
62+
RETURNING *
63+
"#,
64+
id,
65+
owner,
66+
name,
67+
description,
68+
private,
69+
open,
70+
max_people_playing
71+
)
72+
.fetch_one(&self.db)
73+
.await?;
74+
75+
self.sessions.lock().await.insert(
76+
id,
77+
Session {
78+
current_people_playing: 0,
79+
current_people_listening: 0,
80+
max_people_playing,
81+
},
82+
);
83+
84+
Ok(room)
85+
}
86+
87+
pub async fn get_by_id(&self, id: Uuid) -> Result<Option<Room>, Error> {
88+
let room = sqlx::query_as!(Room, "SELECT * FROM rooms WHERE id = $1", id)
89+
.fetch_optional(&self.db)
90+
.await?;
91+
92+
Ok(room)
93+
}
94+
95+
pub async fn delete(&self, id: Uuid) -> Result<(), Error> {
96+
sqlx::query!(r#"DELETE FROM rooms WHERE id = $1"#, id)
97+
.execute(&self.db)
98+
.await?;
99+
100+
Ok(())
101+
}
102+
}

backend/src/web/error.rs

+9
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ pub enum Error {
3838
#[error(transparent)]
3939
#[serde(skip)]
4040
AxumJsonRejection(#[from] JsonRejection),
41+
42+
NotAllowed,
43+
NotFound
4144
}
4245

4346
// region: --- Axum IntoResponse
@@ -83,6 +86,10 @@ impl Error {
8386

8487
// -- Auth
8588
CtxExt(_) => (StatusCode::FORBIDDEN, ClientError::NO_AUTH),
89+
NotAllowed => (StatusCode::FORBIDDEN, ClientError::NOT_ALLOWED),
90+
91+
// -- Model
92+
NotFound => (StatusCode::NOT_FOUND, ClientError::NOT_FOUND),
8693

8794
// -- Fallback.
8895
_ => (
@@ -100,5 +107,7 @@ pub enum ClientError {
100107
LOGIN_FAIL,
101108
NO_AUTH,
102109
SERVICE_ERROR,
110+
NOT_ALLOWED,
111+
NOT_FOUND,
103112
}
104113
// endregion: --- Client Error

backend/src/web/mod.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ pub mod context;
55
pub mod error;
66
pub mod json;
77
pub mod mw_auth;
8-
pub mod mw_res_map;
98
pub mod mw_req_stamp;
9+
pub mod mw_res_map;
1010
pub mod routes_login;
11+
pub mod routes_room;
1112
pub mod signed_cookies;
1213
pub mod webtransport;
1314

1415
pub const SESSION_COOKIE_NAME: &str = "session-id";
15-
pub static SESSION_COOKIE_KEY: OnceCell<Key> = OnceCell::const_new();
16+
pub static SESSION_COOKIE_KEY: OnceCell<Key> = OnceCell::const_new();

backend/src/web/routes_room.rs

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
use crate::service::room::{self, Room};
2+
use axum::{
3+
extract::{Path, State},
4+
http::StatusCode,
5+
response::IntoResponse,
6+
routing::{delete, post},
7+
Json as AJson, Router,
8+
};
9+
use common::types::{CreateRoomRequest, RoomResponse};
10+
11+
use super::{
12+
error::{Error, Result},
13+
json::Json,
14+
mw_auth::CtxW,
15+
};
16+
17+
#[derive(Clone)]
18+
struct AppState {
19+
room_service: room::Service,
20+
}
21+
22+
pub fn router(room_service: room::Service) -> Router {
23+
Router::new()
24+
.route("/", post(create))
25+
.route("/:id", delete(delete_room).get(get_by_id))
26+
.with_state(AppState { room_service })
27+
}
28+
29+
async fn create(
30+
context: CtxW,
31+
State(AppState { room_service }): State<AppState>,
32+
Json(CreateRoomRequest { name }): Json<CreateRoomRequest>,
33+
) -> Result<impl IntoResponse> {
34+
let username = context.0.get_session().username;
35+
36+
let room = room_service
37+
.create(username, name, None, false, true, 5)
38+
.await?;
39+
40+
Ok((StatusCode::CREATED, AJson(RoomResponse::from(room))))
41+
}
42+
43+
async fn delete_room(
44+
Path(id): Path<uuid::Uuid>,
45+
State(AppState { room_service, .. }): State<AppState>,
46+
context: CtxW,
47+
) -> Result<impl IntoResponse> {
48+
let room = room_service.get_by_id(id).await?;
49+
if let Some(room) = room {
50+
if room.owner != context.0.get_session().username {
51+
return Err(Error::NotAllowed);
52+
}
53+
54+
room_service.delete(id).await?;
55+
}
56+
Ok(StatusCode::NO_CONTENT)
57+
}
58+
59+
async fn get_by_id(
60+
Path(id): Path<uuid::Uuid>,
61+
State(AppState { room_service }): State<AppState>,
62+
) -> Result<impl IntoResponse> {
63+
let room = room_service.get_by_id(id).await?;
64+
65+
if let Some(room) = room {
66+
Ok(AJson(RoomResponse::from(room)))
67+
} else {
68+
Err(Error::NotFound)
69+
}
70+
71+
}
72+
73+
impl From<Room> for RoomResponse {
74+
fn from(
75+
Room {
76+
id, name, owner, ..
77+
}: Room,
78+
) -> Self {
79+
Self { id, name, owner }
80+
}
81+
}

common/src/types.rs

+11
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,14 @@ pub struct StartResetRequest{
6363
#[validate(email(message = "Invalid email"))]
6464
pub email: String,
6565
}
66+
67+
#[derive(Serialize, Deserialize, Clone, Validate)]
68+
pub struct CreateRoomRequest {
69+
pub name: String,
70+
}
71+
#[derive(Serialize, Deserialize, Clone)]
72+
pub struct RoomResponse {
73+
pub id: Uuid,
74+
pub name: String,
75+
pub owner: String,
76+
}

frontend/src/components/pages/classes.rs

-2
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@ pub fn main_div_classes() -> Classes {
99
"items-center",
1010
"justify-center",
1111
"px-6",
12-
"py-8",
1312
"mx-auto",
14-
"lg:py-0"
1513
)
1614
}
1715

0 commit comments

Comments
 (0)