forked from lavina/lavina
208 lines
6.2 KiB
Rust
208 lines
6.2 KiB
Rust
use anyhow::{anyhow, Result};
|
|
use chrono::{DateTime, Utc};
|
|
use sqlx::FromRow;
|
|
|
|
use crate::repo::Storage;
|
|
use crate::room::{RoomId, StoredMessage};
|
|
|
|
#[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<Option<StoredRoom>> {
|
|
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), name = "Storage::retrieve_room_message_history")]
|
|
pub async fn get_room_message_history(&self, room_id: u32) -> Result<Vec<StoredMessage>> {
|
|
let mut executor = self.conn.lock().await;
|
|
let res = sqlx::query_as(
|
|
"
|
|
select
|
|
messages.id as id,
|
|
content,
|
|
created_at,
|
|
users.name as author_name
|
|
from
|
|
messages
|
|
join
|
|
users
|
|
on messages.author_id = users.id
|
|
where
|
|
room_id = ?
|
|
order by
|
|
messages.id;
|
|
",
|
|
)
|
|
.bind(room_id)
|
|
.fetch_all(&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<u32> {
|
|
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<Utc>,
|
|
) -> 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)
|
|
.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<bool> {
|
|
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<u32> {
|
|
// 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<Vec<RoomId>> {
|
|
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::try_from(room_id)).collect()
|
|
}
|
|
}
|