chat: add Id<T> type

This commit is contained in:
sam 2024-01-20 23:09:19 +01:00
parent e57bff00c2
commit ce543e7ee1
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
8 changed files with 146 additions and 43 deletions

View file

@ -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<ChannelType>,
pub guild_id: Id<GuildType>,
pub name: String,
pub topic: Option<String>,
}
pub async fn create_channel(
executor: impl PgExecutor<'_>,
guild_id: &str,
guild_id: &Id<GuildType>,
name: &str,
topic: Option<String>,
) -> Result<Channel> {
@ -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<Channel, FoxError> {
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<ChannelType>) -> Result<Channel, FoxError> {
let channel = sqlx::query_as!(Channel, "select * from channels where id = $1", channel_id.0)
.fetch_one(executor)
.await
.map_err(|e| match e {

View file

@ -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<GuildType>,
pub owner_id: Id<UserType>,
pub name: String,
}
pub async fn create_guild(
executor: impl PgExecutor<'_>,
owner_id: &str,
owner_id: &Id<UserType>,
name: &str,
) -> Result<Guild> {
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<GuildType>,
user_id: &Id<UserType>,
) -> 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<GuildType>,
user_id: &Id<UserType>,
) -> Result<Guild, FoxError> {
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

View file

@ -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<MessageType>,
pub channel_id: Id<ChannelType>,
pub author_id: Id<UserType>,
pub updated_at: DateTime<Utc>,
pub content: String,
}
pub async fn create_message(
executor: impl PgExecutor<'_>,
channel_id: &str,
user_id: &str,
channel_id: &Id<ChannelType>,
user_id: &Id<UserType>,
params: CreateMessageParams,
) -> Result<Message> {
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)

View file

@ -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<Arc<AppState>>,
Path(channel_id): Path<Id<ChannelType>>,
request: FoxRequestData,
Path(channel_id): Path<String>,
Json(params): Json<CreateMessageParams>,
) -> Result<Json<Message>, 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),
}))
}

View file

@ -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,
}
}))

View file

@ -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<UserType>,
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(),

103
foxchat/src/id.rs Normal file
View file

@ -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<T>(pub String, PhantomData<T>)
where
T: IdType;
impl<T: IdType> Id<T> {
pub fn new(value: String) -> Self {
Self(value, PhantomData)
}
pub fn from_str(value: &str) -> Self {
value.into()
}
}
impl<T: IdType> From<&str> for Id<T> {
fn from(value: &str) -> Self {
Self::new(value.to_string())
}
}
impl<T: IdType> From<String> for Id<T> {
fn from(value: String) -> Self {
Self::new(value)
}
}
impl<T: IdType> From<Id<T>> for String {
fn from(value: Id<T>) -> 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<T: IdType> Type<Postgres> for Id<T> {
fn type_info() -> PgTypeInfo {
<&str as Type<Postgres>>::type_info()
}
}
impl<'r, T: IdType> Decode<'r, Postgres> for Id<T> {
fn decode(value: <Postgres as HasValueRef<'r>>::ValueRef) -> Result<Self, BoxDynError> {
let value = <&str as Decode<Postgres>>::decode(value)?;
Ok(Id::<T>::new(value.parse()?))
}
}
impl<'q, T: IdType> Encode<'q, Postgres> for Id<T> {
fn encode_by_ref(&self, buf: &mut <Postgres as HasArguments<'q>>::ArgumentBuffer) -> IsNull {
<&str as Encode<Postgres>>::encode(&self.0, buf)
}
}
impl<T: IdType> Serialize for Id<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.0)
}
}
impl<'de, T: IdType> Deserialize<'de> for Id<T> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Ok(Self::new(String::deserialize(deserializer)?))
}
}

View file

@ -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;