use anyhow::{anyhow, Result}; use chrono::{DateTime, Utc}; use sqlx::FromRow; use crate::repo::Storage; use crate::room::RoomId; #[derive(FromRow)] pub struct StoredRoom { pub id: u32, pub name: String, pub topic: String, pub message_count: u32, } impl Storage { #[tracing::instrument(skip(self), name = "Storage::retrieve_room_by_name")] pub async fn retrieve_room_by_name(&self, name: &str) -> Result> { let mut executor = self.conn.lock().await; let res = sqlx::query_as( "select id, name, topic, message_count from rooms where name = ?;", ) .bind(name) .fetch_optional(&mut *executor) .await?; Ok(res) } #[tracing::instrument(skip(self, topic), name = "Storage::create_new_room")] pub async fn create_new_room(&self, name: &str, topic: &str) -> Result { let mut executor = self.conn.lock().await; let (id,): (u32,) = sqlx::query_as( "insert into rooms(name, topic) values (?, ?) returning id;", ) .bind(name) .bind(topic) .fetch_one(&mut *executor) .await?; Ok(id) } #[tracing::instrument(skip(self, content, created_at), name = "Storage::insert_room_message")] pub async fn insert_room_message( &self, room_id: u32, id: u32, content: &str, author_id: &str, created_at: &DateTime, ) -> Result<()> { let mut executor = self.conn.lock().await; let res: Option<(u32,)> = sqlx::query_as("select id from users where name = ?;") .bind(author_id) .fetch_optional(&mut *executor) .await?; let Some((author_id,)) = res else { return Err(anyhow!("No such user")); }; sqlx::query( "insert into messages(room_id, id, content, author_id, created_at) values (?, ?, ?, ?, ?); update rooms set message_count = message_count + 1 where id = ?;", ) .bind(room_id) .bind(id) .bind(content) .bind(author_id) .bind(created_at.to_string()) .bind(room_id) .execute(&mut *executor) .await?; Ok(()) } #[tracing::instrument(skip(self), name = "Storage::is_room_member")] pub async fn is_room_member(&self, room_id: u32, player_id: u32) -> Result { let mut executor = self.conn.lock().await; let res: (u32,) = sqlx::query_as( " select count(*) from memberships where user_id = ? and room_id = ?; ", ) .bind(player_id) .bind(room_id) .fetch_one(&mut *executor) .await?; Ok(res.0 > 0) } #[tracing::instrument(skip(self), name = "Storage::add_room_member")] pub async fn add_room_member(&self, room_id: u32, player_id: u32) -> Result<()> { let mut executor = self.conn.lock().await; sqlx::query( "insert into memberships(user_id, room_id, status) values (?, ?, 1);", ) .bind(player_id) .bind(room_id) .execute(&mut *executor) .await?; Ok(()) } #[tracing::instrument(skip(self), name = "Storage::remove_room_member")] pub async fn remove_room_member(&self, room_id: u32, player_id: u32) -> Result<()> { let mut executor = self.conn.lock().await; sqlx::query( "delete from memberships where user_id = ? and room_id = ?;", ) .bind(player_id) .bind(room_id) .execute(&mut *executor) .await?; Ok(()) } #[tracing::instrument(skip(self, topic), name = "Storage::set_room_topic")] pub async fn set_room_topic(&self, id: u32, topic: &str) -> Result<()> { let mut executor = self.conn.lock().await; sqlx::query( "update rooms set topic = ? where id = ?;", ) .bind(topic) .bind(id) .fetch_optional(&mut *executor) .await?; Ok(()) } #[tracing::instrument(skip(self), name = "Storage::create_or_retrieve_room_id_by_name")] pub async fn create_or_retrieve_room_id_by_name(&self, name: &str) -> Result { // TODO we don't need any info except the name on non-owning nodes, should remove stubs here let mut executor = self.conn.lock().await; let res: (u32,) = sqlx::query_as( "insert into rooms(name, topic) values (?, '') on conflict(name) do update set name = excluded.name returning id;", ) .bind(name) .fetch_one(&mut *executor) .await?; Ok(res.0) } #[tracing::instrument(skip(self), name = "Storage::get_rooms_of_a_user")] pub async fn get_rooms_of_a_user(&self, user_id: u32) -> Result> { let mut executor = self.conn.lock().await; let res: Vec<(String,)> = sqlx::query_as( "select r.name from memberships m inner join rooms r on m.room_id = r.id where m.user_id = ?;", ) .bind(user_id) .fetch_all(&mut *executor) .await?; res.into_iter().map(|(room_id,)| RoomId::from(room_id)).collect() } }