From ce543e7ee1fffc2dae2c788ba325b4a0abe78789 Mon Sep 17 00:00:00 2001 From: sam Date: Sat, 20 Jan 2024 23:09:19 +0100 Subject: [PATCH] chat: add Id type --- chat/src/db/channel.rs | 14 +-- chat/src/db/guild.rs | 28 +++--- chat/src/db/message.rs | 16 ++-- chat/src/http/api/channels/messages.rs | 12 +-- chat/src/http/api/guilds/create_guild.rs | 6 +- chat/src/model/user.rs | 8 +- foxchat/src/id.rs | 103 +++++++++++++++++++++++ foxchat/src/lib.rs | 2 + 8 files changed, 146 insertions(+), 43 deletions(-) create mode 100644 foxchat/src/id.rs diff --git a/chat/src/db/channel.rs b/chat/src/db/channel.rs index ded4508..b2181e3 100644 --- a/chat/src/db/channel.rs +++ b/chat/src/db/channel.rs @@ -1,18 +1,18 @@ use eyre::Result; -use foxchat::FoxError; +use foxchat::{FoxError, Id, id::{ChannelType, GuildType}}; use sqlx::PgExecutor; use ulid::Ulid; pub struct Channel { - pub id: String, - pub guild_id: String, + pub id: Id, + pub guild_id: Id, pub name: String, pub topic: Option, } pub async fn create_channel( executor: impl PgExecutor<'_>, - guild_id: &str, + guild_id: &Id, name: &str, topic: Option, ) -> Result { @@ -20,7 +20,7 @@ pub async fn create_channel( Channel, "insert into channels (id, guild_id, name, topic) values ($1, $2, $3, $4) returning *", Ulid::new().to_string(), - guild_id, + guild_id.0, name, topic ) @@ -30,8 +30,8 @@ pub async fn create_channel( Ok(channel) } -pub async fn get_channel(executor: impl PgExecutor<'_>, channel_id: &str) -> Result { - let channel = sqlx::query_as!(Channel, "select * from channels where id = $1", channel_id) +pub async fn get_channel(executor: impl PgExecutor<'_>, channel_id: &Id) -> Result { + let channel = sqlx::query_as!(Channel, "select * from channels where id = $1", channel_id.0) .fetch_one(executor) .await .map_err(|e| match e { diff --git a/chat/src/db/guild.rs b/chat/src/db/guild.rs index ff56223..56518de 100644 --- a/chat/src/db/guild.rs +++ b/chat/src/db/guild.rs @@ -1,24 +1,24 @@ use eyre::Result; -use foxchat::FoxError; +use foxchat::{FoxError, Id, id::{GuildType, UserType}}; use sqlx::PgExecutor; use ulid::Ulid; pub struct Guild { - pub id: String, - pub owner_id: String, + pub id: Id, + pub owner_id: Id, pub name: String, } pub async fn create_guild( executor: impl PgExecutor<'_>, - owner_id: &str, + owner_id: &Id, name: &str, ) -> Result { let guild = sqlx::query_as!( Guild, "insert into guilds (id, owner_id, name) values ($1, $2, $3) returning *", Ulid::new().to_string(), - owner_id, + owner_id.0, name ) .fetch_one(executor) @@ -29,13 +29,13 @@ pub async fn create_guild( pub async fn join_guild( executor: impl PgExecutor<'_>, - guild_id: &str, - user_id: &str, + guild_id: &Id, + user_id: &Id, ) -> Result<()> { sqlx::query!( "insert into guilds_users (guild_id, user_id) values ($1, $2) on conflict (guild_id, user_id) do nothing", - guild_id, - user_id + guild_id.0, + user_id.0 ) .execute(executor) .await?; @@ -45,16 +45,14 @@ pub async fn join_guild( pub async fn get_guild( executor: impl PgExecutor<'_>, - guild_id: &str, - user_id: &str, + guild_id: &Id, + user_id: &Id, ) -> Result { - println!("guild id: {}, user id: {}", guild_id, user_id); - let guild = sqlx::query_as!( Guild, "select g.* from guilds_users u join guilds g on u.guild_id = g.id where u.guild_id = $1 and u.user_id = $2", - guild_id, - user_id + guild_id.0, + user_id.0 ) .fetch_one(executor) .await diff --git a/chat/src/db/message.rs b/chat/src/db/message.rs index 66b6aac..c93f208 100644 --- a/chat/src/db/message.rs +++ b/chat/src/db/message.rs @@ -1,29 +1,29 @@ use chrono::{DateTime, Utc}; use eyre::Result; -use foxchat::model::http::channel::CreateMessageParams; +use foxchat::{Id, model::http::channel::CreateMessageParams, id::{ChannelType, UserType, MessageType}}; use sqlx::PgExecutor; use ulid::Ulid; pub struct Message { - pub id: String, - pub channel_id: String, - pub author_id: String, + pub id: Id, + pub channel_id: Id, + pub author_id: Id, pub updated_at: DateTime, pub content: String, } pub async fn create_message( executor: impl PgExecutor<'_>, - channel_id: &str, - user_id: &str, + channel_id: &Id, + user_id: &Id, params: CreateMessageParams, ) -> Result { let message = sqlx::query_as!( Message, "insert into messages (id, channel_id, author_id, content) values ($1, $2, $3, $4) returning *", Ulid::new().to_string(), - channel_id, - user_id, + channel_id.0, + user_id.0, params.content, ) .fetch_one(executor) diff --git a/chat/src/http/api/channels/messages.rs b/chat/src/http/api/channels/messages.rs index 3574dc1..03de706 100644 --- a/chat/src/http/api/channels/messages.rs +++ b/chat/src/http/api/channels/messages.rs @@ -5,7 +5,7 @@ use eyre::Result; use foxchat::{ http::ApiError, model::{http::channel::CreateMessageParams, Message, user::PartialUser}, - FoxError, ulid_timestamp, + FoxError, ulid_timestamp, id::ChannelType, Id, }; use crate::{ @@ -17,8 +17,8 @@ use crate::{ pub async fn post_messages( Extension(state): Extension>, + Path(channel_id): Path>, request: FoxRequestData, - Path(channel_id): Path, Json(params): Json, ) -> Result, ApiError> { let user_id = request.user_id.ok_or(FoxError::MissingUser)?; @@ -35,14 +35,14 @@ pub async fn post_messages( // TODO: dispatch message create event Ok(Json(Message { - id: message.id.clone(), - channel_id: channel.id, + id: message.id.0.clone(), + channel_id: channel.id.0, author: PartialUser { - id: user.id, + id: user.id.0, username: user.username, instance: request.instance.domain, }, content: Some(message.content), - created_at: ulid_timestamp(&message.id), + created_at: ulid_timestamp(&message.id.0), })) } diff --git a/chat/src/http/api/guilds/create_guild.rs b/chat/src/http/api/guilds/create_guild.rs index 1e0995c..d506707 100644 --- a/chat/src/http/api/guilds/create_guild.rs +++ b/chat/src/http/api/guilds/create_guild.rs @@ -31,15 +31,15 @@ pub async fn post_guilds( tx.commit().await?; Ok(Json(Guild { - id: guild.id, + id: guild.id.0, name: guild.name, owner: PartialUser { - id: user.id, + id: user.id.0, username: user.username, instance: user.instance.domain, }, default_channel: PartialChannel { - id: channel.id, + id: channel.id.0, name: channel.name, } })) diff --git a/chat/src/model/user.rs b/chat/src/model/user.rs index 34566e0..71ca79e 100644 --- a/chat/src/model/user.rs +++ b/chat/src/model/user.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use eyre::Result; -use foxchat::{fed, model::User as HttpUser}; +use foxchat::{fed, model::User as HttpUser, Id, id::UserType}; use ulid::Ulid; use crate::app_state::AppState; @@ -9,7 +9,7 @@ use crate::app_state::AppState; use super::identity_instance::IdentityInstance; pub struct User { - pub id: String, + pub id: Id, pub instance_id: String, pub instance: IdentityInstance, pub remote_user_id: String, @@ -32,7 +32,7 @@ impl User { .await? { return Ok(User { - id: user.id, + id: user.id.into(), username: user.username, instance_id: user.instance_id, instance: instance.to_owned(), @@ -62,7 +62,7 @@ impl User { .await?; Ok(User { - id: user.id, + id: user.id.into(), username: user.username, instance_id: user.instance_id, instance: instance.to_owned(), diff --git a/foxchat/src/id.rs b/foxchat/src/id.rs new file mode 100644 index 0000000..9eefac7 --- /dev/null +++ b/foxchat/src/id.rs @@ -0,0 +1,103 @@ +use std::marker::PhantomData; + +use serde::{Deserialize, Serialize}; +use sqlx::{ + database::{HasArguments, HasValueRef}, + encode::IsNull, + error::BoxDynError, + postgres::PgTypeInfo, + Decode, Encode, Postgres, Type, +}; + +pub struct Id(pub String, PhantomData) +where + T: IdType; + +impl Id { + pub fn new(value: String) -> Self { + Self(value, PhantomData) + } + + pub fn from_str(value: &str) -> Self { + value.into() + } +} + +impl From<&str> for Id { + fn from(value: &str) -> Self { + Self::new(value.to_string()) + } +} + +impl From for Id { + fn from(value: String) -> Self { + Self::new(value) + } +} + +impl From> for String { + fn from(value: Id) -> Self { + value.0 + } +} + +pub trait IdType: private::Sealed {} + +pub struct GuildType; +pub struct ChannelType; +pub struct MessageType; +pub struct UserType; + +impl IdType for GuildType {} +impl IdType for ChannelType {} +impl IdType for MessageType {} +impl IdType for UserType {} + +mod private { + use super::*; + + pub trait Sealed {} + + impl Sealed for GuildType {} + impl Sealed for ChannelType {} + impl Sealed for MessageType {} + impl Sealed for UserType {} +} + +impl Type for Id { + fn type_info() -> PgTypeInfo { + <&str as Type>::type_info() + } +} + +impl<'r, T: IdType> Decode<'r, Postgres> for Id { + fn decode(value: >::ValueRef) -> Result { + let value = <&str as Decode>::decode(value)?; + + Ok(Id::::new(value.parse()?)) + } +} + +impl<'q, T: IdType> Encode<'q, Postgres> for Id { + fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { + <&str as Encode>::encode(&self.0, buf) + } +} + +impl Serialize for Id { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.0) + } +} + +impl<'de, T: IdType> Deserialize<'de> for Id { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Ok(Self::new(String::deserialize(deserializer)?)) + } +} diff --git a/foxchat/src/lib.rs b/foxchat/src/lib.rs index 915cb89..03ad1c1 100644 --- a/foxchat/src/lib.rs +++ b/foxchat/src/lib.rs @@ -3,9 +3,11 @@ pub mod fed; pub mod http; pub mod model; pub mod s2s; +pub mod id; pub use error::FoxError; pub use fed::signature; +pub use id::Id; use chrono::{DateTime, Utc}; use ulid::Ulid;