forked from lavina/lavina
				
			persistence for rooms
This commit is contained in:
		
							parent
							
								
									ef5c0dbbf6
								
							
						
					
					
						commit
						1b5ac1491a
					
				|  | @ -11,5 +11,21 @@ create table challenges_plain_password( | ||||||
| 
 | 
 | ||||||
| create table rooms( | create table rooms( | ||||||
|     id integer primary key autoincrement not null, |     id integer primary key autoincrement not null, | ||||||
|     name string unique not null |     name string unique not null, | ||||||
|  |     topic string not null, | ||||||
|  |     message_count integer not null default 0 | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | create table messages( | ||||||
|  |     room_id integer not null, | ||||||
|  |     id integer not null,  -- unique per room, sequential in one room | ||||||
|  |     content string not null, | ||||||
|  |     primary key (room_id, id) | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | create table memberships( | ||||||
|  |     user_id integer not null, | ||||||
|  |     room_id integer not null, | ||||||
|  |     status integer not null, -- 0 for not-joined, 1 for joined, 2 for banned | ||||||
|  |     primary key (user_id, room_id) | ||||||
| ); | ); | ||||||
|  |  | ||||||
|  | @ -354,7 +354,13 @@ impl Player { | ||||||
|                     return; |                     return; | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 let room = self.rooms.get_or_create_room(room_id.clone()); |                 let room = match self.rooms.get_or_create_room(room_id.clone()).await { | ||||||
|  |                     Ok(room) => room, | ||||||
|  |                     Err(e) => { | ||||||
|  |                         log::error!("Failed to get or create room: {e}"); | ||||||
|  |                         return; | ||||||
|  |                     } | ||||||
|  |                 }; | ||||||
|                 room.subscribe(self.player_id.clone(), self.handle.clone()) |                 room.subscribe(self.player_id.clone(), self.handle.clone()) | ||||||
|                     .await; |                     .await; | ||||||
|                 self.my_rooms.insert(room_id.clone(), room.clone()); |                 self.my_rooms.insert(room_id.clone(), room.clone()); | ||||||
|  | @ -384,7 +390,7 @@ impl Player { | ||||||
|                 body, |                 body, | ||||||
|                 promise, |                 promise, | ||||||
|             } => { |             } => { | ||||||
|                 let room = self.rooms.get_room(&room_id); |                 let room = self.rooms.get_room(&room_id).await; | ||||||
|                 if let Some(room) = room { |                 if let Some(room) = room { | ||||||
|                     room.send_message(self.player_id.clone(), body.clone()) |                     room.send_message(self.player_id.clone(), body.clone()) | ||||||
|                         .await; |                         .await; | ||||||
|  | @ -404,7 +410,7 @@ impl Player { | ||||||
|                 new_topic, |                 new_topic, | ||||||
|                 promise, |                 promise, | ||||||
|             } => { |             } => { | ||||||
|                 let room = self.rooms.get_room(&room_id); |                 let room = self.rooms.get_room(&room_id).await; | ||||||
|                 if let Some(mut room) = room { |                 if let Some(mut room) = room { | ||||||
|                     room.set_topic(self.player_id.clone(), new_topic.clone()) |                     room.set_topic(self.player_id.clone(), new_topic.clone()) | ||||||
|                         .await; |                         .await; | ||||||
|  |  | ||||||
|  | @ -47,10 +47,39 @@ impl Storage { | ||||||
|         Ok(res) |         Ok(res) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     pub async fn retrieve_room_by_name(&mut 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) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub async fn create_new_room(&mut self, name: &str, topic: &str) -> Result<()> { | ||||||
|  |         let mut executor = self.conn.lock().await; | ||||||
|  |         let _: (u32,) = sqlx::query_as( | ||||||
|  |             "insert into rooms(name, topic)
 | ||||||
|  |             values (?, ?) | ||||||
|  |             returning id;",
 | ||||||
|  |         ) | ||||||
|  |         .bind(name) | ||||||
|  |         .bind(topic) | ||||||
|  |         .fetch_one(&mut *executor) | ||||||
|  |         .await?; | ||||||
|  | 
 | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     pub async fn close(mut self) -> Result<()> { |     pub async fn close(mut self) -> Result<()> { | ||||||
|         let res = match Arc::try_unwrap(self.conn) { |         let res = match Arc::try_unwrap(self.conn) { | ||||||
|             Ok(e) => e, |             Ok(e) => e, | ||||||
|             Err(_) => return Err(fail("failed to acquire DB ownership on shutdown")), |             Err(e) => return Err(fail("failed to acquire DB ownership on shutdown")), | ||||||
|         }; |         }; | ||||||
|         let res = res.into_inner(); |         let res = res.into_inner(); | ||||||
|         res.close().await?; |         res.close().await?; | ||||||
|  | @ -64,3 +93,11 @@ pub struct StoredUser { | ||||||
|     pub name: String, |     pub name: String, | ||||||
|     pub password: Option<String>, |     pub password: Option<String>, | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | #[derive(FromRow)] | ||||||
|  | pub struct StoredRoom { | ||||||
|  |     pub id: u32, | ||||||
|  |     pub name: String, | ||||||
|  |     pub topic: String, | ||||||
|  |     pub message_count: u32, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -2,17 +2,16 @@ | ||||||
| use std::{ | use std::{ | ||||||
|     collections::HashMap, |     collections::HashMap, | ||||||
|     hash::Hash, |     hash::Hash, | ||||||
|     sync::{Arc, RwLock}, |     sync::Arc, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| use prometheus::{IntGauge, Registry as MetricRegistry}; | use prometheus::{IntGauge, Registry as MetricRegistry}; | ||||||
| use serde::Serialize; | use serde::Serialize; | ||||||
| use tokio::sync::RwLock as AsyncRwLock; | use tokio::sync::RwLock as AsyncRwLock; | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::core::player::{PlayerHandle, PlayerId, Updates}; | ||||||
|     core::player::{PlayerHandle, PlayerId, Updates}, | use crate::core::repo::Storage; | ||||||
|     prelude::*, | use crate::prelude::*; | ||||||
| }; |  | ||||||
| 
 | 
 | ||||||
| /// Opaque room id
 | /// Opaque room id
 | ||||||
| #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)] | #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)] | ||||||
|  | @ -40,45 +39,66 @@ impl RoomId { | ||||||
| 
 | 
 | ||||||
| /// Shared datastructure for storing metadata about rooms.
 | /// Shared datastructure for storing metadata about rooms.
 | ||||||
| #[derive(Clone)] | #[derive(Clone)] | ||||||
| pub struct RoomRegistry(Arc<RwLock<RoomRegistryInner>>); | pub struct RoomRegistry(Arc<AsyncRwLock<RoomRegistryInner>>); | ||||||
| impl RoomRegistry { | impl RoomRegistry { | ||||||
|     pub fn empty(metrics: &mut MetricRegistry) -> Result<RoomRegistry> { |     pub fn new(metrics: &mut MetricRegistry, storage: Storage) -> Result<RoomRegistry> { | ||||||
|         let metric_active_rooms = |         let metric_active_rooms = | ||||||
|             IntGauge::new("chat_rooms_active", "Number of alive room actors")?; |             IntGauge::new("chat_rooms_active", "Number of alive room actors")?; | ||||||
|         metrics.register(Box::new(metric_active_rooms.clone()))?; |         metrics.register(Box::new(metric_active_rooms.clone()))?; | ||||||
|         let inner = RoomRegistryInner { |         let inner = RoomRegistryInner { | ||||||
|             rooms: HashMap::new(), |             rooms: HashMap::new(), | ||||||
|             metric_active_rooms, |             metric_active_rooms, | ||||||
|  |             storage, | ||||||
|         }; |         }; | ||||||
|         Ok(RoomRegistry(Arc::new(RwLock::new(inner)))) |         Ok(RoomRegistry(Arc::new(AsyncRwLock::new(inner)))) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn get_or_create_room(&mut self, room_id: RoomId) -> RoomHandle { |     pub async fn get_or_create_room(&mut self, room_id: RoomId) -> Result<RoomHandle> { | ||||||
|         let mut inner = self.0.write().unwrap(); |         let mut inner = self.0.write().await; | ||||||
|         if let Some(room_handle) = inner.rooms.get(&room_id) { |         if let Some(room_handle) = inner.rooms.get(&room_id) { | ||||||
|             room_handle.clone() |             // room was already loaded into memory
 | ||||||
|         } else { |             log::debug!("Room {} was loaded already", &room_id.0); | ||||||
|  |             Ok(room_handle.clone()) | ||||||
|  |         } else if let Some(stored_room) = inner.storage.retrieve_room_by_name(&*room_id.0).await? { | ||||||
|  |             // room exists, but was not loaded
 | ||||||
|  |             log::debug!("Loading room {}...", &room_id.0); | ||||||
|             let room = Room { |             let room = Room { | ||||||
|                 room_id: room_id.clone(), |                 room_id: room_id.clone(), | ||||||
|                 subscriptions: HashMap::new(), |                 subscriptions: HashMap::new(), | ||||||
|                 topic: "New room".into(), |                 topic: stored_room.topic.into(), | ||||||
|  |                 message_count: stored_room.message_count, | ||||||
|             }; |             }; | ||||||
|             let room_handle = RoomHandle(Arc::new(AsyncRwLock::new(room))); |             let room_handle = RoomHandle(Arc::new(AsyncRwLock::new(room))); | ||||||
|             inner.rooms.insert(room_id, room_handle.clone()); |             inner.rooms.insert(room_id, room_handle.clone()); | ||||||
|             inner.metric_active_rooms.inc(); |             inner.metric_active_rooms.inc(); | ||||||
|             room_handle |             Ok(room_handle) | ||||||
|  |         } else { | ||||||
|  |             // room does not exist, create it and load
 | ||||||
|  |             log::debug!("Creating room {}...", &room_id.0); | ||||||
|  |             let topic = "New room"; | ||||||
|  |             let _ = inner.storage.create_new_room(&*room_id.0, &*topic).await?; | ||||||
|  |             let room = Room { | ||||||
|  |                 room_id: room_id.clone(), | ||||||
|  |                 subscriptions: HashMap::new(), | ||||||
|  |                 topic: topic.into(), | ||||||
|  |                 message_count: 0, | ||||||
|  |             }; | ||||||
|  |             let room_handle = RoomHandle(Arc::new(AsyncRwLock::new(room))); | ||||||
|  |             inner.rooms.insert(room_id, room_handle.clone()); | ||||||
|  |             inner.metric_active_rooms.inc(); | ||||||
|  |             Ok(room_handle) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn get_room(&self, room_id: &RoomId) -> Option<RoomHandle> { |     pub async fn get_room(&self, room_id: &RoomId) -> Option<RoomHandle> { | ||||||
|         let inner = self.0.read().unwrap(); |         let inner = self.0.read().await; | ||||||
|         let res = inner.rooms.get(room_id); |         let res = inner.rooms.get(room_id); | ||||||
|         res.map(|r| r.clone()) |         res.map(|r| r.clone()) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub async fn get_all_rooms(&self) -> Vec<RoomInfo> { |     pub async fn get_all_rooms(&self) -> Vec<RoomInfo> { | ||||||
|         let handles = { |         let handles = { | ||||||
|             let inner = self.0.read().unwrap(); |             let inner = self.0.read().await; | ||||||
|             let handles = inner.rooms.values().cloned().collect::<Vec<_>>(); |             let handles = inner.rooms.values().cloned().collect::<Vec<_>>(); | ||||||
|             handles |             handles | ||||||
|         }; |         }; | ||||||
|  | @ -93,6 +113,7 @@ impl RoomRegistry { | ||||||
| struct RoomRegistryInner { | struct RoomRegistryInner { | ||||||
|     rooms: HashMap<RoomId, RoomHandle>, |     rooms: HashMap<RoomId, RoomHandle>, | ||||||
|     metric_active_rooms: IntGauge, |     metric_active_rooms: IntGauge, | ||||||
|  |     storage: Storage, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Clone)] | #[derive(Clone)] | ||||||
|  | @ -145,6 +166,7 @@ impl RoomHandle { | ||||||
| struct Room { | struct Room { | ||||||
|     room_id: RoomId, |     room_id: RoomId, | ||||||
|     subscriptions: HashMap<PlayerId, PlayerHandle>, |     subscriptions: HashMap<PlayerId, PlayerHandle>, | ||||||
|  |     message_count: u32, | ||||||
|     topic: Str, |     topic: Str, | ||||||
| } | } | ||||||
| impl Room { | impl Room { | ||||||
|  |  | ||||||
|  | @ -53,7 +53,7 @@ async fn main() -> Result<()> { | ||||||
|     } = config; |     } = config; | ||||||
|     let mut metrics = MetricsRegistry::new(); |     let mut metrics = MetricsRegistry::new(); | ||||||
|     let storage = Storage::open(storage_config).await?; |     let storage = Storage::open(storage_config).await?; | ||||||
|     let rooms = RoomRegistry::empty(&mut metrics)?; |     let rooms = RoomRegistry::new(&mut metrics, storage.clone())?; | ||||||
|     let players = PlayerRegistry::empty(rooms.clone(), &mut metrics)?; |     let players = PlayerRegistry::empty(rooms.clone(), &mut metrics)?; | ||||||
|     let telemetry_terminator = |     let telemetry_terminator = | ||||||
|         util::telemetry::launch(telemetry_config, metrics.clone(), rooms.clone()).await?; |         util::telemetry::launch(telemetry_config, metrics.clone(), rooms.clone()).await?; | ||||||
|  |  | ||||||
|  | @ -311,7 +311,7 @@ async fn handle_update( | ||||||
|             room_id, |             room_id, | ||||||
|         } => { |         } => { | ||||||
|             if player_id == &new_member_id { |             if player_id == &new_member_id { | ||||||
|                 if let Some(room) = rooms.get_room(&room_id) { |                 if let Some(room) = rooms.get_room(&room_id).await { | ||||||
|                     let room_info = room.get_room_info().await; |                     let room_info = room.get_room_info().await; | ||||||
|                     let chan = Chan::Global(room_id.as_inner().clone()); |                     let chan = Chan::Global(room_id.as_inner().clone()); | ||||||
|                     produce_on_join_cmd_messages(&config, &user, &chan, &room_info, writer).await?; |                     produce_on_join_cmd_messages(&config, &user, &chan, &room_info, writer).await?; | ||||||
|  | @ -482,7 +482,7 @@ async fn handle_incoming_message( | ||||||
|                     writer.flush().await?; |                     writer.flush().await?; | ||||||
|                 } |                 } | ||||||
|                 Recipient::Chan(Chan::Global(chan)) => { |                 Recipient::Chan(Chan::Global(chan)) => { | ||||||
|                     let room = rooms.get_room(&RoomId::from(chan.clone())?); |                     let room = rooms.get_room(&RoomId::from(chan.clone())?).await; | ||||||
|                     if let Some(room) = room { |                     if let Some(room) = room { | ||||||
|                         let room_info = room.get_room_info().await; |                         let room_info = room.get_room_info().await; | ||||||
|                         for member in room_info.members { |                         for member in room_info.members { | ||||||
|  |  | ||||||
|  | @ -0,0 +1,6 @@ | ||||||
|  | insert into users(name) | ||||||
|  | values ('kek'), ('shrek') | ||||||
|  | returning id; | ||||||
|  | 
 | ||||||
|  | insert into challenges_plain_password(user_id, password) | ||||||
|  | values (1, 'parolchik1'), (2, 'qwerty123'); | ||||||
		Loading…
	
		Reference in New Issue