This commit is contained in:
pavel 2026-02-13 19:52:42 +01:00
commit d1a68c635c
5 changed files with 384 additions and 94 deletions

126
src/db.rs
View file

@ -1,15 +1,18 @@
use anyhow::{Result, anyhow};
use chrono::{Duration, Utc};
use sea_orm::{
ActiveModelTrait, ActiveValue::Set, ColumnTrait, DatabaseConnection,
Condition, DatabaseTransaction, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect,
ActiveModelTrait, ActiveValue::Set, ColumnTrait, Condition, DatabaseConnection,
DatabaseTransaction, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect,
TransactionTrait, sea_query::OnConflict,
};
use uuid::Uuid;
use crate::{
entity::{channels, direct_messages, guild_members, guilds, invites, messages, users},
models::{BasicUser, Channel, DmConversation, DmMessageWithAuthor, Guild, Invite, MessageWithAuthor, User},
models::{
BasicUser, Channel, DmConversation, DmMessageWithAuthor, Guild, Invite, MessageWithAuthor,
User,
},
};
pub const CHANNEL_KIND_TEXT: &str = "text";
@ -42,7 +45,10 @@ pub async fn upsert_user_from_oidc(
users::Column::DisplayName,
users::Column::AvatarUrl,
])
.value(users::Column::UpdatedAt, sea_orm::sea_query::Expr::current_timestamp())
.value(
users::Column::UpdatedAt,
sea_orm::sea_query::Expr::current_timestamp(),
)
.to_owned(),
)
.exec_with_returning(db)
@ -75,6 +81,15 @@ pub async fn list_guild_members(db: &DatabaseConnection, guild_id: Uuid) -> Resu
.collect())
}
pub async fn list_guild_member_ids(db: &DatabaseConnection, guild_id: Uuid) -> Result<Vec<Uuid>> {
let rows = guild_members::Entity::find()
.filter(guild_members::Column::GuildId.eq(guild_id))
.all(db)
.await?;
Ok(rows.into_iter().map(|m| m.user_id).collect())
}
pub async fn list_guilds_for_user(db: &DatabaseConnection, user_id: Uuid) -> Result<Vec<Guild>> {
let rows = guild_members::Entity::find()
.filter(guild_members::Column::UserId.eq(user_id))
@ -88,7 +103,11 @@ pub async fn list_guilds_for_user(db: &DatabaseConnection, user_id: Uuid) -> Res
.collect())
}
pub async fn create_guild(db: &DatabaseConnection, owner_user_id: Uuid, name: &str) -> Result<Guild> {
pub async fn create_guild(
db: &DatabaseConnection,
owner_user_id: Uuid,
name: &str,
) -> Result<Guild> {
let guild = guilds::Entity::insert(guilds::ActiveModel {
id: Set(Uuid::new_v4()),
name: Set(name.to_string()),
@ -104,9 +123,12 @@ pub async fn create_guild(db: &DatabaseConnection, owner_user_id: Uuid, name: &s
..Default::default()
})
.on_conflict(
OnConflict::columns([guild_members::Column::GuildId, guild_members::Column::UserId])
.do_nothing()
.to_owned(),
OnConflict::columns([
guild_members::Column::GuildId,
guild_members::Column::UserId,
])
.do_nothing()
.to_owned(),
)
.exec(db)
.await?;
@ -176,9 +198,12 @@ pub async fn join_invite(db: &DatabaseConnection, code: &str, user_id: Uuid) ->
..Default::default()
})
.on_conflict(
OnConflict::columns([guild_members::Column::GuildId, guild_members::Column::UserId])
.do_nothing()
.to_owned(),
OnConflict::columns([
guild_members::Column::GuildId,
guild_members::Column::UserId,
])
.do_nothing()
.to_owned(),
)
.exec(&txn)
.await?;
@ -215,12 +240,18 @@ pub async fn create_channel(
Ok(map_channel(channel))
}
pub async fn get_channel_by_id(db: &DatabaseConnection, channel_id: Uuid) -> Result<Option<Channel>> {
pub async fn get_channel_by_id(
db: &DatabaseConnection,
channel_id: Uuid,
) -> Result<Option<Channel>> {
let row = channels::Entity::find_by_id(channel_id).one(db).await?;
Ok(row.map(map_channel))
}
pub async fn list_channels_for_guild(db: &DatabaseConnection, guild_id: Uuid) -> Result<Vec<Channel>> {
pub async fn list_channels_for_guild(
db: &DatabaseConnection,
guild_id: Uuid,
) -> Result<Vec<Channel>> {
let channels = channels::Entity::find()
.filter(channels::Column::GuildId.eq(guild_id))
.order_by_asc(channels::Column::CreatedAt)
@ -230,7 +261,10 @@ pub async fn list_channels_for_guild(db: &DatabaseConnection, guild_id: Uuid) ->
Ok(channels.into_iter().map(map_channel).collect())
}
pub async fn list_voice_channels_for_guild(db: &DatabaseConnection, guild_id: Uuid) -> Result<Vec<Channel>> {
pub async fn list_voice_channels_for_guild(
db: &DatabaseConnection,
guild_id: Uuid,
) -> Result<Vec<Channel>> {
let channels = channels::Entity::find()
.filter(channels::Column::GuildId.eq(guild_id))
.filter(channels::Column::Kind.eq(CHANNEL_KIND_VOICE))
@ -241,7 +275,11 @@ pub async fn list_voice_channels_for_guild(db: &DatabaseConnection, guild_id: Uu
Ok(channels.into_iter().map(map_channel).collect())
}
pub async fn is_member_of_guild(db: &DatabaseConnection, guild_id: Uuid, user_id: Uuid) -> Result<bool> {
pub async fn is_member_of_guild(
db: &DatabaseConnection,
guild_id: Uuid,
user_id: Uuid,
) -> Result<bool> {
let count = guild_members::Entity::find()
.filter(guild_members::Column::GuildId.eq(guild_id))
.filter(guild_members::Column::UserId.eq(user_id))
@ -251,7 +289,10 @@ pub async fn is_member_of_guild(db: &DatabaseConnection, guild_id: Uuid, user_id
Ok(count > 0)
}
pub async fn guild_id_for_channel(db: &DatabaseConnection, channel_id: Uuid) -> Result<Option<Uuid>> {
pub async fn guild_id_for_channel(
db: &DatabaseConnection,
channel_id: Uuid,
) -> Result<Option<Uuid>> {
let guild_id = channels::Entity::find_by_id(channel_id)
.select_only()
.column(channels::Column::GuildId)
@ -267,18 +308,30 @@ pub async fn create_message(
channel_id: Uuid,
author_user_id: Uuid,
body: &str,
) -> Result<()> {
messages::Entity::insert(messages::ActiveModel {
) -> Result<MessageWithAuthor> {
let model = messages::Entity::insert(messages::ActiveModel {
id: Set(Uuid::new_v4()),
channel_id: Set(channel_id),
author_user_id: Set(author_user_id),
body: Set(body.to_string()),
..Default::default()
})
.exec(db)
.exec_with_returning(db)
.await?;
Ok(())
let user = users::Entity::find_by_id(author_user_id)
.one(db)
.await?
.ok_or_else(|| anyhow!("author not found"))?;
Ok(MessageWithAuthor {
id: model.id,
channel_id: model.channel_id,
author_user_id: model.author_user_id,
author_display_name: user.display_name,
body: model.body,
created_at: model.created_at,
})
}
pub async fn list_messages(
@ -317,18 +370,30 @@ pub async fn create_direct_message(
sender_user_id: Uuid,
recipient_user_id: Uuid,
body: &str,
) -> Result<()> {
direct_messages::Entity::insert(direct_messages::ActiveModel {
) -> Result<DmMessageWithAuthor> {
let model = direct_messages::Entity::insert(direct_messages::ActiveModel {
id: Set(Uuid::new_v4()),
sender_user_id: Set(sender_user_id),
recipient_user_id: Set(recipient_user_id),
body: Set(body.to_string()),
..Default::default()
})
.exec(db)
.exec_with_returning(db)
.await?;
Ok(())
let user = users::Entity::find_by_id(sender_user_id)
.one(db)
.await?
.ok_or_else(|| anyhow!("sender not found"))?;
Ok(DmMessageWithAuthor {
id: model.id,
author_user_id: model.sender_user_id,
recipient_user_id: model.recipient_user_id,
author_display_name: user.display_name,
body: model.body,
created_at: model.created_at,
})
}
pub async fn list_direct_messages(
@ -385,7 +450,10 @@ pub async fn list_direct_messages(
.collect())
}
pub async fn list_dm_conversations(db: &DatabaseConnection, current_user_id: Uuid) -> Result<Vec<DmConversation>> {
pub async fn list_dm_conversations(
db: &DatabaseConnection,
current_user_id: Uuid,
) -> Result<Vec<DmConversation>> {
let rows = direct_messages::Entity::find()
.filter(
Condition::any()
@ -396,7 +464,8 @@ pub async fn list_dm_conversations(db: &DatabaseConnection, current_user_id: Uui
.all(db)
.await?;
let mut latest_by_peer = std::collections::HashMap::<Uuid, chrono::DateTime<chrono::FixedOffset>>::new();
let mut latest_by_peer =
std::collections::HashMap::<Uuid, chrono::DateTime<chrono::FixedOffset>>::new();
for row in rows {
let peer_id = if row.sender_user_id == current_user_id {
row.recipient_user_id
@ -496,7 +565,10 @@ fn validate_invite(invite: &invites::Model) -> Result<()> {
Ok(())
}
async fn increment_invite_use_count(txn: &DatabaseTransaction, invite: invites::Model) -> Result<()> {
async fn increment_invite_use_count(
txn: &DatabaseTransaction,
invite: invites::Model,
) -> Result<()> {
let next_count = invite.use_count + 1;
let mut active: invites::ActiveModel = invite.into();
active.use_count = Set(next_count);