add basic guild create + message create endpoints
This commit is contained in:
parent
5b23095520
commit
e57bff00c2
27 changed files with 367 additions and 36 deletions
|
@ -31,6 +31,8 @@ create table guilds_users (
|
|||
guild_id text not null references guilds (id) on delete cascade,
|
||||
user_id text not null references users (id) on delete cascade,
|
||||
|
||||
joined_at timestamptz not null default now(),
|
||||
|
||||
primary key (guild_id, user_id)
|
||||
);
|
||||
|
||||
|
|
47
chat/src/db/channel.rs
Normal file
47
chat/src/db/channel.rs
Normal file
|
@ -0,0 +1,47 @@
|
|||
use eyre::Result;
|
||||
use foxchat::FoxError;
|
||||
use sqlx::PgExecutor;
|
||||
use ulid::Ulid;
|
||||
|
||||
pub struct Channel {
|
||||
pub id: String,
|
||||
pub guild_id: String,
|
||||
pub name: String,
|
||||
pub topic: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn create_channel(
|
||||
executor: impl PgExecutor<'_>,
|
||||
guild_id: &str,
|
||||
name: &str,
|
||||
topic: Option<String>,
|
||||
) -> Result<Channel> {
|
||||
let channel = sqlx::query_as!(
|
||||
Channel,
|
||||
"insert into channels (id, guild_id, name, topic) values ($1, $2, $3, $4) returning *",
|
||||
Ulid::new().to_string(),
|
||||
guild_id,
|
||||
name,
|
||||
topic
|
||||
)
|
||||
.fetch_one(executor)
|
||||
.await?;
|
||||
|
||||
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)
|
||||
.fetch_one(executor)
|
||||
.await
|
||||
.map_err(|e| match e {
|
||||
sqlx::Error::RowNotFound => FoxError::NotInGuild,
|
||||
_ => {
|
||||
tracing::error!("database error: {}", e);
|
||||
|
||||
return FoxError::DatabaseError;
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(channel)
|
||||
}
|
71
chat/src/db/guild.rs
Normal file
71
chat/src/db/guild.rs
Normal file
|
@ -0,0 +1,71 @@
|
|||
use eyre::Result;
|
||||
use foxchat::FoxError;
|
||||
use sqlx::PgExecutor;
|
||||
use ulid::Ulid;
|
||||
|
||||
pub struct Guild {
|
||||
pub id: String,
|
||||
pub owner_id: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
pub async fn create_guild(
|
||||
executor: impl PgExecutor<'_>,
|
||||
owner_id: &str,
|
||||
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,
|
||||
name
|
||||
)
|
||||
.fetch_one(executor)
|
||||
.await?;
|
||||
|
||||
Ok(guild)
|
||||
}
|
||||
|
||||
pub async fn join_guild(
|
||||
executor: impl PgExecutor<'_>,
|
||||
guild_id: &str,
|
||||
user_id: &str,
|
||||
) -> 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
|
||||
)
|
||||
.execute(executor)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_guild(
|
||||
executor: impl PgExecutor<'_>,
|
||||
guild_id: &str,
|
||||
user_id: &str,
|
||||
) -> 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
|
||||
)
|
||||
.fetch_one(executor)
|
||||
.await
|
||||
.map_err(|e| match e {
|
||||
sqlx::Error::RowNotFound => FoxError::NotInGuild,
|
||||
_ => {
|
||||
tracing::error!("database error: {}", e);
|
||||
|
||||
return FoxError::DatabaseError;
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(guild)
|
||||
}
|
33
chat/src/db/message.rs
Normal file
33
chat/src/db/message.rs
Normal file
|
@ -0,0 +1,33 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use eyre::Result;
|
||||
use foxchat::model::http::channel::CreateMessageParams;
|
||||
use sqlx::PgExecutor;
|
||||
use ulid::Ulid;
|
||||
|
||||
pub struct Message {
|
||||
pub id: String,
|
||||
pub channel_id: String,
|
||||
pub author_id: String,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
pub async fn create_message(
|
||||
executor: impl PgExecutor<'_>,
|
||||
channel_id: &str,
|
||||
user_id: &str,
|
||||
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,
|
||||
params.content,
|
||||
)
|
||||
.fetch_one(executor)
|
||||
.await?;
|
||||
|
||||
Ok(message)
|
||||
}
|
|
@ -1,3 +1,7 @@
|
|||
pub mod guild;
|
||||
pub mod channel;
|
||||
pub mod message;
|
||||
|
||||
use eyre::{OptionExt, Result};
|
||||
use rsa::pkcs1::{EncodeRsaPrivateKey, EncodeRsaPublicKey, LineEnding};
|
||||
use rsa::{RsaPrivateKey, RsaPublicKey};
|
||||
|
|
48
chat/src/http/api/channels/messages.rs
Normal file
48
chat/src/http/api/channels/messages.rs
Normal file
|
@ -0,0 +1,48 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use axum::{extract::Path, Extension, Json};
|
||||
use eyre::Result;
|
||||
use foxchat::{
|
||||
http::ApiError,
|
||||
model::{http::channel::CreateMessageParams, Message, user::PartialUser},
|
||||
FoxError, ulid_timestamp,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
app_state::AppState,
|
||||
db::{channel::get_channel, guild::get_guild, message::create_message},
|
||||
fed::FoxRequestData,
|
||||
model::user::User,
|
||||
};
|
||||
|
||||
pub async fn post_messages(
|
||||
Extension(state): Extension<Arc<AppState>>,
|
||||
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)?;
|
||||
let user = User::get(&state, &request.instance, &user_id).await?;
|
||||
|
||||
let mut tx = state.pool.begin().await?;
|
||||
let channel = get_channel(&mut *tx, &channel_id).await?;
|
||||
let _guild = get_guild(&mut *tx, &channel.guild_id, &user.id).await?;
|
||||
|
||||
let message = create_message(&mut *tx, &channel.id, &user.id, params).await?;
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
// TODO: dispatch message create event
|
||||
|
||||
Ok(Json(Message {
|
||||
id: message.id.clone(),
|
||||
channel_id: channel.id,
|
||||
author: PartialUser {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
instance: request.instance.domain,
|
||||
},
|
||||
content: Some(message.content),
|
||||
created_at: ulid_timestamp(&message.id),
|
||||
}))
|
||||
}
|
7
chat/src/http/api/channels/mod.rs
Normal file
7
chat/src/http/api/channels/mod.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
mod messages;
|
||||
|
||||
use axum::{routing::post, Router};
|
||||
|
||||
pub fn router() -> Router {
|
||||
Router::new().route("/_fox/chat/channels/:id/messages", post(messages::post_messages))
|
||||
}
|
|
@ -3,30 +3,32 @@ use std::sync::Arc;
|
|||
use axum::{Extension, Json};
|
||||
use foxchat::{
|
||||
http::ApiError,
|
||||
model::{http::guild::CreateGuildParams, Guild, user::PartialUser},
|
||||
model::{http::guild::CreateGuildParams, user::PartialUser, Guild, channel::PartialChannel},
|
||||
FoxError,
|
||||
};
|
||||
use ulid::Ulid;
|
||||
|
||||
use crate::{app_state::AppState, fed::FoxRequestData, model::user::User};
|
||||
use crate::{
|
||||
app_state::AppState,
|
||||
db::{channel::create_channel, guild::{create_guild, join_guild}},
|
||||
fed::FoxRequestData,
|
||||
model::user::User,
|
||||
};
|
||||
|
||||
pub async fn create_guild(
|
||||
pub async fn post_guilds(
|
||||
Extension(state): Extension<Arc<AppState>>,
|
||||
request: FoxRequestData,
|
||||
Json(params): Json<CreateGuildParams>,
|
||||
) -> Result<Json<Guild>, ApiError> {
|
||||
let user_id = request.user_id.ok_or(FoxError::MissingUser)?;
|
||||
let user = User::get(&state, &request.instance, &user_id).await?;
|
||||
|
||||
let user = User::get(&state, &request.instance, user_id).await?;
|
||||
let mut tx = state.pool.begin().await?;
|
||||
let guild = create_guild(&mut *tx, &user.id, ¶ms.name).await?;
|
||||
let channel = create_channel(&mut *tx, &guild.id, "general", None).await?;
|
||||
|
||||
let guild = sqlx::query!(
|
||||
"insert into guilds (id, owner_id, name) values ($1, $2, $3) returning *",
|
||||
Ulid::new().to_string(),
|
||||
user.id,
|
||||
params.name
|
||||
)
|
||||
.fetch_one(&state.pool)
|
||||
.await?;
|
||||
join_guild(&mut *tx, &guild.id, &user.id).await?;
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
Ok(Json(Guild {
|
||||
id: guild.id,
|
||||
|
@ -34,7 +36,11 @@ pub async fn create_guild(
|
|||
owner: PartialUser {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
instance: request.instance.domain,
|
||||
instance: user.instance.domain,
|
||||
},
|
||||
default_channel: PartialChannel {
|
||||
id: channel.id,
|
||||
name: channel.name,
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -4,5 +4,5 @@ use axum::{Router, routing::post};
|
|||
|
||||
pub fn router() -> Router {
|
||||
Router::new()
|
||||
.route("/_fox/chat/guilds", post(create_guild::create_guild))
|
||||
.route("/_fox/chat/guilds", post(create_guild::post_guilds))
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
use axum::Router;
|
||||
|
||||
pub mod channels;
|
||||
pub mod guilds;
|
||||
|
||||
pub fn router() -> Router {
|
||||
Router::new().merge(guilds::router())
|
||||
Router::new()
|
||||
.merge(guilds::router())
|
||||
.merge(channels::router())
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use crate::app_state::AppState;
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Serialize, Clone, Debug)]
|
||||
pub struct IdentityInstance {
|
||||
pub id: String,
|
||||
pub domain: String,
|
||||
|
@ -17,7 +17,7 @@ pub struct IdentityInstance {
|
|||
pub reason: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, sqlx::Type)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, sqlx::Type)]
|
||||
#[sqlx(type_name = "instance_status", rename_all = "lowercase")]
|
||||
pub enum InstanceStatus {
|
||||
Active,
|
||||
|
|
|
@ -11,6 +11,7 @@ use super::identity_instance::IdentityInstance;
|
|||
pub struct User {
|
||||
pub id: String,
|
||||
pub instance_id: String,
|
||||
pub instance: IdentityInstance,
|
||||
pub remote_user_id: String,
|
||||
pub username: String,
|
||||
pub avatar: Option<String>,
|
||||
|
@ -20,10 +21,9 @@ impl User {
|
|||
pub async fn get(
|
||||
state: &Arc<AppState>,
|
||||
instance: &IdentityInstance,
|
||||
remote_id: String,
|
||||
remote_id: &str,
|
||||
) -> Result<User> {
|
||||
if let Some(user) = sqlx::query_as!(
|
||||
User,
|
||||
if let Some(user) = sqlx::query!(
|
||||
"select * from users where instance_id = $1 and remote_user_id = $2",
|
||||
instance.id,
|
||||
remote_id
|
||||
|
@ -31,7 +31,14 @@ impl User {
|
|||
.fetch_optional(&state.pool)
|
||||
.await?
|
||||
{
|
||||
return Ok(user);
|
||||
return Ok(User {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
instance_id: user.instance_id,
|
||||
instance: instance.to_owned(),
|
||||
remote_user_id: user.remote_user_id,
|
||||
avatar: user.avatar,
|
||||
});
|
||||
}
|
||||
|
||||
let http_user = fed::get::<HttpUser>(
|
||||
|
@ -43,8 +50,7 @@ impl User {
|
|||
)
|
||||
.await?;
|
||||
|
||||
let user = sqlx::query_as!(
|
||||
User,
|
||||
let user = sqlx::query!(
|
||||
"insert into users (id, instance_id, remote_user_id, username, avatar) values ($1, $2, $3, $4, $5) returning *",
|
||||
Ulid::new().to_string(),
|
||||
instance.id,
|
||||
|
@ -55,6 +61,13 @@ impl User {
|
|||
.fetch_one(&state.pool)
|
||||
.await?;
|
||||
|
||||
Ok(user)
|
||||
Ok(User {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
instance_id: user.instance_id,
|
||||
instance: instance.to_owned(),
|
||||
remote_user_id: user.remote_user_id,
|
||||
avatar: user.avatar,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue