add: channels, messages
This commit is contained in:
parent
67492cf73f
commit
7774124bd0
40 changed files with 2238 additions and 115 deletions
|
@ -1,5 +1,6 @@
|
|||
use super::*;
|
||||
use crate::cache::Cache;
|
||||
use crate::model::moderation::AuditLogEntry;
|
||||
use crate::model::{
|
||||
Error, Result, auth::User, permissions::FinePermission,
|
||||
communities_permissions::CommunityPermission, channels::Channel,
|
||||
|
@ -26,12 +27,14 @@ impl DataManager {
|
|||
minimum_role_read: get!(x->4(i32)) as u32,
|
||||
minimum_role_write: get!(x->5(i32)) as u32,
|
||||
position: get!(x->6(i32)) as usize,
|
||||
members: serde_json::from_str(&get!(x->7(String))).unwrap(),
|
||||
title: get!(x->8(String)),
|
||||
}
|
||||
}
|
||||
|
||||
auto_method!(get_channel_by_id(usize)@get_channel_from_row -> "SELECT * FROM channels WHERE id = $1" --name="channel" --returns=Channel --cache-key-tmpl="atto.channel:{}");
|
||||
auto_method!(get_channel_by_id(usize as i64)@get_channel_from_row -> "SELECT * FROM channels WHERE id = $1" --name="channel" --returns=Channel --cache-key-tmpl="atto.channel:{}");
|
||||
|
||||
/// Get all channels by user.
|
||||
/// Get all channels by community.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `community` - the ID of the community to fetch channels for
|
||||
|
@ -43,7 +46,7 @@ impl DataManager {
|
|||
|
||||
let res = query_rows!(
|
||||
&conn,
|
||||
"SELECT * FROM channels WHERE community = $1 ORDER BY position DESC",
|
||||
"SELECT * FROM channels WHERE community = $1 ORDER BY position ASC",
|
||||
&[&(community as i64)],
|
||||
|x| { Self::get_channel_from_row(x) }
|
||||
);
|
||||
|
@ -55,6 +58,59 @@ impl DataManager {
|
|||
Ok(res.unwrap())
|
||||
}
|
||||
|
||||
/// Get all channels by user.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `user` - the ID of the user to fetch channels for
|
||||
pub async fn get_channels_by_user(&self, user: usize) -> Result<Vec<Channel>> {
|
||||
let conn = match self.connect().await {
|
||||
Ok(c) => c,
|
||||
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||
};
|
||||
|
||||
let res = query_rows!(
|
||||
&conn,
|
||||
"SELECT * FROM channels WHERE (owner = $1 OR members LIKE $2) AND community = 0 ORDER BY created DESC",
|
||||
params![&(user as i64), &format!("%{user}%")],
|
||||
|x| { Self::get_channel_from_row(x) }
|
||||
);
|
||||
|
||||
if res.is_err() {
|
||||
return Err(Error::GeneralNotFound("channel".to_string()));
|
||||
}
|
||||
|
||||
Ok(res.unwrap())
|
||||
}
|
||||
|
||||
/// Get a channel given its `owner` and a member.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `owner` - the ID of the owner
|
||||
/// * `member` - the ID of the member
|
||||
pub async fn get_channel_by_owner_member(
|
||||
&self,
|
||||
owner: usize,
|
||||
member: usize,
|
||||
) -> Result<Channel> {
|
||||
let conn = match self.connect().await {
|
||||
Ok(c) => c,
|
||||
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||
};
|
||||
|
||||
let res = query_row!(
|
||||
&conn,
|
||||
"SELECT * FROM channels WHERE owner = $1 AND members = $2 AND community = 0 ORDER BY created DESC",
|
||||
params![&(owner as i64), &format!("[{member}]")],
|
||||
|x| { Ok(Self::get_channel_from_row(x)) }
|
||||
);
|
||||
|
||||
if res.is_err() {
|
||||
return Err(Error::GeneralNotFound("channel".to_string()));
|
||||
}
|
||||
|
||||
Ok(res.unwrap())
|
||||
}
|
||||
|
||||
/// Create a new channel in the database.
|
||||
///
|
||||
/// # Arguments
|
||||
|
@ -63,14 +119,16 @@ impl DataManager {
|
|||
let user = self.get_user_by_id(data.owner).await?;
|
||||
|
||||
// check user permission in community
|
||||
let membership = self
|
||||
.get_membership_by_owner_community(user.id, data.community)
|
||||
.await?;
|
||||
if data.community != 0 {
|
||||
let membership = self
|
||||
.get_membership_by_owner_community(user.id, data.community)
|
||||
.await?;
|
||||
|
||||
if !membership.role.check(CommunityPermission::MANAGE_CHANNELS)
|
||||
&& !user.permissions.check(FinePermission::MANAGE_CHANNELS)
|
||||
{
|
||||
return Err(Error::NotAllowed);
|
||||
if !membership.role.check(CommunityPermission::MANAGE_CHANNELS)
|
||||
&& !user.permissions.check(FinePermission::MANAGE_CHANNELS)
|
||||
{
|
||||
return Err(Error::NotAllowed);
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
|
@ -81,7 +139,7 @@ impl DataManager {
|
|||
|
||||
let res = execute!(
|
||||
&conn,
|
||||
"INSERT INTO channels VALUES ($1, $2, $3, $4, $5, $6, $7)",
|
||||
"INSERT INTO channels VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
params![
|
||||
&(data.id as i64),
|
||||
&(data.community as i64),
|
||||
|
@ -89,7 +147,9 @@ impl DataManager {
|
|||
&(data.created as i64),
|
||||
&(data.minimum_role_read as i32),
|
||||
&(data.minimum_role_write as i32),
|
||||
&(data.position as i32)
|
||||
&(data.position as i32),
|
||||
&serde_json::to_string(&data.members).unwrap(),
|
||||
&data.title
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -100,16 +160,18 @@ impl DataManager {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_channel(&self, id: usize, user: User) -> Result<()> {
|
||||
pub async fn delete_channel(&self, id: usize, user: &User) -> Result<()> {
|
||||
let channel = self.get_channel_by_id(id).await?;
|
||||
|
||||
// check user permission in community
|
||||
let membership = self
|
||||
.get_membership_by_owner_community(user.id, channel.community)
|
||||
.await?;
|
||||
if user.id != channel.owner {
|
||||
let membership = self
|
||||
.get_membership_by_owner_community(user.id, channel.community)
|
||||
.await?;
|
||||
|
||||
if !membership.role.check(CommunityPermission::MANAGE_CHANNELS) {
|
||||
return Err(Error::NotAllowed);
|
||||
if !membership.role.check(CommunityPermission::MANAGE_CHANNELS) {
|
||||
return Err(Error::NotAllowed);
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
|
@ -124,11 +186,63 @@ impl DataManager {
|
|||
return Err(Error::DatabaseError(e.to_string()));
|
||||
}
|
||||
|
||||
// delete messages
|
||||
let res = execute!(
|
||||
&conn,
|
||||
"DELETE FROM messages WHERE channel = $1",
|
||||
&[&(id as i64)]
|
||||
);
|
||||
|
||||
if let Err(e) = res {
|
||||
return Err(Error::DatabaseError(e.to_string()));
|
||||
}
|
||||
|
||||
// ...
|
||||
self.2.remove(format!("atto.channel:{}", id)).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
auto_method!(update_channel_position(i32)@get_channel_by_id:MANAGE_COMMUNITIES -> "UPDATE channels SET position = $1 WHERE id = $2" --cache-key-tmpl="atto.channel:{}");
|
||||
auto_method!(update_channel_minimum_role_read(i32)@get_channel_by_id:MANAGE_COMMUNITIES -> "UPDATE channels SET minimum_role_read = $1 WHERE id = $2" --cache-key-tmpl="atto.channel:{}");
|
||||
auto_method!(update_channel_minimum_role_write(i32)@get_channel_by_id:MANAGE_COMMUNITIES -> "UPDATE channels SET minimum_role_write = $1 WHERE id = $2" --cache-key-tmpl="atto.channel:{}");
|
||||
pub async fn remove_channel_member(&self, id: usize, user: User, member: usize) -> Result<()> {
|
||||
let mut y = self.get_channel_by_id(id).await?;
|
||||
|
||||
if user.id != y.owner && member != user.id {
|
||||
if !user.permissions.check(FinePermission::MANAGE_CHANNELS) {
|
||||
return Err(Error::NotAllowed);
|
||||
} else {
|
||||
self.create_audit_log_entry(AuditLogEntry::new(
|
||||
user.id,
|
||||
format!("invoked `remove_channel_member` with x value `{member}`"),
|
||||
))
|
||||
.await?
|
||||
}
|
||||
}
|
||||
|
||||
y.members
|
||||
.remove(y.members.iter().position(|x| *x == member).unwrap());
|
||||
|
||||
let conn = match self.connect().await {
|
||||
Ok(c) => c,
|
||||
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||
};
|
||||
|
||||
let res = execute!(
|
||||
&conn,
|
||||
"UPDATE channels SET members = $1 WHERE id = $2",
|
||||
params![&serde_json::to_string(&y.members).unwrap(), &(id as i64)]
|
||||
);
|
||||
|
||||
if let Err(e) = res {
|
||||
return Err(Error::DatabaseError(e.to_string()));
|
||||
}
|
||||
|
||||
self.2.remove(format!("atto.channel:{}", id)).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
auto_method!(update_channel_title(&str)@get_channel_by_id:MANAGE_CHANNELS -> "UPDATE channels SET title = $1 WHERE id = $2" --cache-key-tmpl="atto.channel:{}");
|
||||
auto_method!(update_channel_position(i32)@get_channel_by_id:MANAGE_CHANNELS -> "UPDATE channels SET position = $1 WHERE id = $2" --cache-key-tmpl="atto.channel:{}");
|
||||
auto_method!(update_channel_minimum_role_read(i32)@get_channel_by_id:MANAGE_CHANNELS -> "UPDATE channels SET minimum_role_read = $1 WHERE id = $2" --cache-key-tmpl="atto.channel:{}");
|
||||
auto_method!(update_channel_minimum_role_write(i32)@get_channel_by_id:MANAGE_CHANNELS -> "UPDATE channels SET minimum_role_write = $1 WHERE id = $2" --cache-key-tmpl="atto.channel:{}");
|
||||
auto_method!(update_channel_members(Vec<usize>)@get_channel_by_id:MANAGE_CHANNELS -> "UPDATE channels SET members = $1 WHERE id = $2" --serde --cache-key-tmpl="atto.channel:{}");
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ use crate::{
|
|||
execute,
|
||||
model::{Error, Result},
|
||||
};
|
||||
|
||||
use super::DataManager;
|
||||
|
||||
impl DataManager {
|
||||
|
@ -357,7 +356,7 @@ macro_rules! auto_method {
|
|||
let res = execute!(
|
||||
&conn,
|
||||
$query,
|
||||
&[&serde_json::to_string(&x).unwrap(), &(id as i64)]
|
||||
params![&serde_json::to_string(&x).unwrap(), &(id as i64)]
|
||||
);
|
||||
|
||||
if let Err(e) = res {
|
||||
|
|
|
@ -330,6 +330,11 @@ impl DataManager {
|
|||
return Err(Error::DatabaseError(e.to_string()));
|
||||
}
|
||||
|
||||
// remove channels
|
||||
for channel in self.get_channels_by_community(id).await? {
|
||||
self.delete_channel(channel.id, &user).await?;
|
||||
}
|
||||
|
||||
// remove images
|
||||
let avatar = PathBufD::current().extend(&[
|
||||
self.0.dirs.media.as_str(),
|
||||
|
|
|
@ -5,5 +5,7 @@ CREATE TABLE IF NOT EXISTS channels (
|
|||
created BIGINT NOT NULL,
|
||||
minimum_role_read INT NOT NULL,
|
||||
minimum_role_write INT NOT NULL,
|
||||
position INT NOT NULL
|
||||
position INT NOT NULL,
|
||||
members TEXT NOT NULL,
|
||||
title TEXT NOT NULL
|
||||
)
|
||||
|
|
|
@ -1,11 +1,23 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use super::*;
|
||||
use crate::cache::Cache;
|
||||
use crate::model::moderation::AuditLogEntry;
|
||||
use crate::model::socket::{SocketMessage, SocketMethod};
|
||||
use crate::model::{
|
||||
Error, Result, auth::User, permissions::FinePermission,
|
||||
communities_permissions::CommunityPermission, channels::Message,
|
||||
};
|
||||
use crate::{auto_method, execute, get, query_row, query_rows, params};
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct DeleteMessageEvent {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
#[cfg(feature = "redis")]
|
||||
use redis::Commands;
|
||||
|
||||
#[cfg(feature = "sqlite")]
|
||||
use rusqlite::Row;
|
||||
|
@ -31,7 +43,35 @@ impl DataManager {
|
|||
}
|
||||
}
|
||||
|
||||
auto_method!(get_message_by_id(usize)@get_message_from_row -> "SELECT * FROM messages WHERE id = $1" --name="message" --returns=Message --cache-key-tmpl="atto.message:{}");
|
||||
auto_method!(get_message_by_id(usize as i64)@get_message_from_row -> "SELECT * FROM messages WHERE id = $1" --name="message" --returns=Message --cache-key-tmpl="atto.message:{}");
|
||||
|
||||
/// Complete a vector of just messages with their owner as well.
|
||||
pub async fn fill_messages(
|
||||
&self,
|
||||
messages: Vec<Message>,
|
||||
ignore_users: &Vec<usize>,
|
||||
) -> Result<Vec<(Message, User)>> {
|
||||
let mut out: Vec<(Message, User)> = Vec::new();
|
||||
|
||||
let mut users: HashMap<usize, User> = HashMap::new();
|
||||
for message in messages {
|
||||
let owner = message.owner;
|
||||
|
||||
if ignore_users.contains(&owner) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(user) = users.get(&owner) {
|
||||
out.push((message, user.clone()));
|
||||
} else {
|
||||
let user = self.get_user_by_id(owner).await?;
|
||||
users.insert(owner, user.clone());
|
||||
out.push((message, user));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// Get all messages by channel (paginated).
|
||||
///
|
||||
|
@ -52,7 +92,7 @@ impl DataManager {
|
|||
|
||||
let res = query_rows!(
|
||||
&conn,
|
||||
"SELECT * FROM messages WHERE channel = $1 ORDER BY created DESC LIMIT $1 OFFSET $2",
|
||||
"SELECT * FROM messages WHERE channel = $1 ORDER BY created DESC LIMIT $2 OFFSET $3",
|
||||
&[&(channel as i64), &(batch as i64), &((page * batch) as i64)],
|
||||
|x| { Self::get_message_from_row(x) }
|
||||
);
|
||||
|
@ -69,6 +109,14 @@ impl DataManager {
|
|||
/// # Arguments
|
||||
/// * `data` - a mock [`Message`] object to insert
|
||||
pub async fn create_message(&self, data: Message) -> Result<()> {
|
||||
if data.content.len() < 2 {
|
||||
return Err(Error::DataTooLong("content".to_string()));
|
||||
}
|
||||
|
||||
if data.content.len() > 2048 {
|
||||
return Err(Error::DataTooLong("content".to_string()));
|
||||
}
|
||||
|
||||
let user = self.get_user_by_id(data.owner).await?;
|
||||
let channel = self.get_channel_by_id(data.channel).await?;
|
||||
|
||||
|
@ -77,14 +125,8 @@ impl DataManager {
|
|||
.get_membership_by_owner_community(user.id, channel.community)
|
||||
.await?;
|
||||
|
||||
if !membership.role.check_member() {
|
||||
return Err(Error::NotAllowed);
|
||||
}
|
||||
|
||||
// check user permission to post in channel
|
||||
let role = membership.role.bits();
|
||||
|
||||
if role < channel.minimum_role_write {
|
||||
if !channel.check_post(user.id, Some(membership.role)) {
|
||||
return Err(Error::NotAllowed);
|
||||
}
|
||||
|
||||
|
@ -112,6 +154,21 @@ impl DataManager {
|
|||
return Err(Error::DatabaseError(e.to_string()));
|
||||
}
|
||||
|
||||
// post event
|
||||
let mut con = self.2.get_con().await;
|
||||
|
||||
if let Err(e) = con.publish::<usize, String, ()>(
|
||||
data.channel,
|
||||
serde_json::to_string(&SocketMessage {
|
||||
method: SocketMethod::Message,
|
||||
data: serde_json::to_string(&data).unwrap(),
|
||||
})
|
||||
.unwrap(),
|
||||
) {
|
||||
return Err(Error::MiscError(e.to_string()));
|
||||
}
|
||||
|
||||
// ...
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -149,6 +206,22 @@ impl DataManager {
|
|||
}
|
||||
|
||||
self.2.remove(format!("atto.message:{}", id)).await;
|
||||
|
||||
// post event
|
||||
let mut con = self.2.get_con().await;
|
||||
|
||||
if let Err(e) = con.publish::<usize, String, ()>(
|
||||
message.channel,
|
||||
serde_json::to_string(&SocketMessage {
|
||||
method: SocketMethod::Delete,
|
||||
data: serde_json::to_string(&DeleteMessageEvent { id: id.to_string() }).unwrap(),
|
||||
})
|
||||
.unwrap(),
|
||||
) {
|
||||
return Err(Error::MiscError(e.to_string()));
|
||||
}
|
||||
|
||||
// ...
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -190,9 +190,12 @@ pub struct UserSettings {
|
|||
/// If dislikes are hidden for the user.
|
||||
#[serde(default)]
|
||||
pub hide_dislikes: bool,
|
||||
/// The timeline that the "Home" button takes you to
|
||||
/// The timeline that the "Home" button takes you to.
|
||||
#[serde(default)]
|
||||
pub default_timeline: DefaultTimelineChoice,
|
||||
/// If other users that you aren't following can add you to chats.
|
||||
#[serde(default)]
|
||||
pub private_chats: bool,
|
||||
}
|
||||
|
||||
impl Default for User {
|
||||
|
@ -352,10 +355,12 @@ pub enum ConnectionService {
|
|||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum ConnectionType {
|
||||
/// A connection through a token with an expiration time.
|
||||
/// A connection through a token which never expires.
|
||||
Token,
|
||||
/// <https://www.rfc-editor.org/rfc/rfc7636>
|
||||
PKCE,
|
||||
/// A connection with no stored authentication.
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
|
|
|
@ -18,11 +18,17 @@ pub struct Channel {
|
|||
///
|
||||
/// Top (0) to bottom.
|
||||
pub position: usize,
|
||||
/// The members of the chat (ids). Should be empty if `community > 0`.
|
||||
///
|
||||
/// The owner should not be a member of the channel since any member can update members.
|
||||
pub members: Vec<usize>,
|
||||
/// The title of the channel.
|
||||
pub title: String,
|
||||
}
|
||||
|
||||
impl Channel {
|
||||
/// Create a new [`Channel`].
|
||||
pub fn new(community: usize, owner: usize, position: usize) -> Self {
|
||||
pub fn new(community: usize, owner: usize, position: usize, title: String) -> Self {
|
||||
Self {
|
||||
id: AlmostSnowflake::new(1234567890)
|
||||
.to_string()
|
||||
|
@ -34,8 +40,32 @@ impl Channel {
|
|||
minimum_role_read: (CommunityPermission::DEFAULT | CommunityPermission::MEMBER).bits(),
|
||||
minimum_role_write: (CommunityPermission::DEFAULT | CommunityPermission::MEMBER).bits(),
|
||||
position,
|
||||
members: Vec::new(),
|
||||
title,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the given `uid` can post in the channel.
|
||||
pub fn check_post(&self, uid: usize, membership: Option<CommunityPermission>) -> bool {
|
||||
let mut is_member = false;
|
||||
|
||||
if let Some(membership) = membership {
|
||||
is_member = membership.bits() >= self.minimum_role_write
|
||||
}
|
||||
|
||||
(uid == self.owner) | is_member | self.members.contains(&uid)
|
||||
}
|
||||
|
||||
/// Check if the given `uid` can post in the channel.
|
||||
pub fn check_read(&self, uid: usize, membership: Option<CommunityPermission>) -> bool {
|
||||
let mut is_member = false;
|
||||
|
||||
if let Some(membership) = membership {
|
||||
is_member = membership.bits() >= self.minimum_role_read
|
||||
}
|
||||
|
||||
(uid == self.owner) | is_member | self.members.contains(&uid)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
|
|
|
@ -2,6 +2,7 @@ pub mod auth;
|
|||
pub mod communities;
|
||||
pub mod communities_permissions;
|
||||
pub mod moderation;
|
||||
pub mod oauth;
|
||||
pub mod permissions;
|
||||
pub mod reactions;
|
||||
pub mod requests;
|
||||
|
@ -9,6 +10,9 @@ pub mod requests;
|
|||
#[cfg(feature = "redis")]
|
||||
pub mod channels;
|
||||
|
||||
#[cfg(feature = "redis")]
|
||||
pub mod socket;
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
36
crates/core/src/model/oauth.rs
Normal file
36
crates/core/src/model/oauth.rs
Normal file
|
@ -0,0 +1,36 @@
|
|||
use base64::{engine::general_purpose::URL_SAFE as base64url, Engine};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use tetratto_shared::hash::hash;
|
||||
use super::{Result, Error};
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum PkceChallengeMethod {
|
||||
S256,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum AppScope {
|
||||
#[serde(alias = "user-read-profile")]
|
||||
UserReadProfile,
|
||||
}
|
||||
|
||||
/// Check a verifier against the stored challenge (using the given [`PkceChallengeMethod`]).
|
||||
pub fn check_verifier(verifier: &str, challenge: &str, method: PkceChallengeMethod) -> Result<()> {
|
||||
if method != PkceChallengeMethod::S256 {
|
||||
return Err(Error::MiscError("only S256 is supported".to_string()));
|
||||
}
|
||||
|
||||
let decoded = match base64url.decode(challenge.as_bytes()) {
|
||||
Ok(hash) => hash,
|
||||
Err(e) => return Err(Error::MiscError(e.to_string())),
|
||||
};
|
||||
|
||||
let hash = hash(verifier.to_string());
|
||||
|
||||
if hash.as_bytes() != decoded {
|
||||
// the verifier we received does not match the verifier from the stored challenge
|
||||
return Err(Error::NotAllowed);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
23
crates/core/src/model/socket.rs
Normal file
23
crates/core/src/model/socket.rs
Normal file
|
@ -0,0 +1,23 @@
|
|||
use serde::{Serialize, Deserialize, de::DeserializeOwned};
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum SocketMethod {
|
||||
/// Authentication and channel identification.
|
||||
Headers,
|
||||
/// A message was sent in the channel.
|
||||
Message,
|
||||
/// A message was deleted in the channel.
|
||||
Delete,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct SocketMessage {
|
||||
pub method: SocketMethod,
|
||||
pub data: String,
|
||||
}
|
||||
|
||||
impl SocketMessage {
|
||||
pub fn data<T: DeserializeOwned>(&self) -> T {
|
||||
serde_json::from_str(&self.data).unwrap()
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue