Skip to content
Clone
a06673b
227 lines 7.6 KB Rust
use db::conn::sea_orm::{
ActiveModelTrait, EntityTrait, ExprTrait, QueryFilter, QueryOrder, QuerySelect,
TransactionTrait,
};
use db::user::user_blocks as blocks;
use db::user::user_followers as followers;
use db::user::users as user_entity;
use uuid::Uuid;
use crate::error::{UserError, UserResult};
use crate::features::UserService;
use super::types::{
BlockedSummary, FollowerSummary, ListBlocksQuery, ListFollowersQuery, ListFollowingQuery,
};
pub async fn follow(svc: &UserService, follower: Uuid, target: Uuid) -> UserResult<()> {
if follower == target {
return Err(UserError::BadRequest("cannot follow self".into()));
}
let writer = svc.db.writer();
if user_entity::Entity::find_by_id(target)
.one(&writer)
.await?
.is_none()
{
return Err(UserError::NotFound(format!("user {target}")));
}
let existing = followers::Entity::find()
.filter(followers::COLUMN.follower_id.eq(follower))
.filter(followers::COLUMN.followed_id.eq(target))
.one(&writer)
.await?;
if existing.is_some() {
return Ok(());
}
let active: followers::ActiveModel = followers::ActiveModel::builder()
.set_id(Uuid::new_v4())
.set_follower_id(follower)
.set_followed_id(target)
.set_created_at(time::OffsetDateTime::now_utc())
.into();
active.insert(&writer).await?;
Ok(())
}
pub async fn unfollow(svc: &UserService, follower: Uuid, target: Uuid) -> UserResult<()> {
let writer = svc.db.writer();
followers::Entity::delete_many()
.filter(followers::COLUMN.follower_id.eq(follower))
.filter(followers::COLUMN.followed_id.eq(target))
.exec(&writer)
.await?;
Ok(())
}
pub async fn list_followers(
svc: &UserService,
q: ListFollowersQuery,
) -> UserResult<Vec<FollowerSummary>> {
let limit = q.limit.min(svc.config.max_profile_pagination).max(1) as u64;
let writer = svc.db.writer();
let mut sel = followers::Entity::find()
.filter(followers::COLUMN.followed_id.eq(q.target_uid))
.order_by_asc(followers::COLUMN.id)
.limit(Some(limit));
if let Some(cursor) = q.cursor {
sel = sel.filter(followers::COLUMN.id.gt(cursor));
}
let edges: Vec<followers::Model> = sel.all(&writer).await?;
if edges.is_empty() {
return Ok(Vec::new());
}
let follower_ids: Vec<Uuid> = edges.iter().map(|e| e.follower_id).collect();
let users: Vec<user_entity::Model> = user_entity::Entity::find()
.filter(user_entity::COLUMN.uid.is_in(follower_ids))
.all(&writer)
.await?;
let map: std::collections::HashMap<Uuid, user_entity::Model> =
users.into_iter().map(|u| (u.uid, u)).collect();
let out = edges
.into_iter()
.filter_map(|e| {
map.get(&e.follower_id).map(|u| FollowerSummary {
user: super::super::profile::UserPublic::from(u.clone()),
since: e
.created_at
.format(&time::format_description::well_known::Rfc3339)
.unwrap_or_default(),
})
})
.collect();
Ok(out)
}
pub async fn list_following(
svc: &UserService,
q: ListFollowingQuery,
) -> UserResult<Vec<FollowerSummary>> {
let limit = q.limit.min(svc.config.max_profile_pagination).max(1) as u64;
let writer = svc.db.writer();
let mut sel = followers::Entity::find()
.filter(followers::COLUMN.follower_id.eq(q.user_uid))
.order_by_asc(followers::COLUMN.id)
.limit(Some(limit));
if let Some(cursor) = q.cursor {
sel = sel.filter(followers::COLUMN.id.gt(cursor));
}
let edges: Vec<followers::Model> = sel.all(&writer).await?;
if edges.is_empty() {
return Ok(Vec::new());
}
let followed_ids: Vec<Uuid> = edges.iter().map(|e| e.followed_id).collect();
let users: Vec<user_entity::Model> = user_entity::Entity::find()
.filter(user_entity::COLUMN.uid.is_in(followed_ids))
.all(&writer)
.await?;
let map: std::collections::HashMap<Uuid, user_entity::Model> =
users.into_iter().map(|u| (u.uid, u)).collect();
let out = edges
.into_iter()
.filter_map(|e| {
map.get(&e.followed_id).map(|u| FollowerSummary {
user: super::super::profile::UserPublic::from(u.clone()),
since: e
.created_at
.format(&time::format_description::well_known::Rfc3339)
.unwrap_or_default(),
})
})
.collect();
Ok(out)
}
pub async fn block(svc: &UserService, blocker: Uuid, target: Uuid) -> UserResult<()> {
if blocker == target {
return Err(UserError::BadRequest("cannot block self".into()));
}
let writer = svc.db.writer();
if user_entity::Entity::find_by_id(target)
.one(&writer)
.await?
.is_none()
{
return Err(UserError::NotFound(format!("user {target}")));
}
let txn = writer.begin().await?;
let existing = blocks::Entity::find()
.filter(blocks::COLUMN.blocker_id.eq(blocker))
.filter(blocks::COLUMN.blocked_id.eq(target))
.one(&txn)
.await?;
if existing.is_none() {
let active: blocks::ActiveModel = blocks::ActiveModel::builder()
.set_id(Uuid::new_v4())
.set_blocker_id(blocker)
.set_blocked_id(target)
.set_created_at(time::OffsetDateTime::now_utc())
.into();
active.insert(&txn).await?;
}
let a = followers::COLUMN.follower_id
.eq(blocker)
.and(followers::COLUMN.followed_id.eq(target));
let b = followers::COLUMN.follower_id
.eq(target)
.and(followers::COLUMN.followed_id.eq(blocker));
followers::Entity::delete_many()
.filter(a.or(b))
.exec(&txn)
.await?;
txn.commit().await?;
Ok(())
}
pub async fn unblock(svc: &UserService, blocker: Uuid, target: Uuid) -> UserResult<()> {
let writer = svc.db.writer();
blocks::Entity::delete_many()
.filter(blocks::COLUMN.blocker_id.eq(blocker))
.filter(blocks::COLUMN.blocked_id.eq(target))
.exec(&writer)
.await?;
Ok(())
}
pub async fn list_blocks(
svc: &UserService,
caller: Uuid,
q: ListBlocksQuery,
) -> UserResult<Vec<BlockedSummary>> {
let limit = q.limit.min(svc.config.max_profile_pagination).max(1) as u64;
let writer = svc.db.writer();
let mut sel = blocks::Entity::find()
.filter(blocks::COLUMN.blocker_id.eq(caller))
.order_by_asc(blocks::COLUMN.id)
.limit(Some(limit));
if let Some(cursor) = q.cursor {
sel = sel.filter(blocks::COLUMN.id.gt(cursor));
}
let edges: Vec<blocks::Model> = sel.all(&writer).await?;
if edges.is_empty() {
return Ok(Vec::new());
}
let blocked_ids: Vec<Uuid> = edges.iter().map(|e| e.blocked_id).collect();
let users: Vec<user_entity::Model> = user_entity::Entity::find()
.filter(user_entity::COLUMN.uid.is_in(blocked_ids))
.all(&writer)
.await?;
let map: std::collections::HashMap<Uuid, user_entity::Model> =
users.into_iter().map(|u| (u.uid, u)).collect();
let out = edges
.into_iter()
.filter_map(|e| {
map.get(&e.blocked_id).map(|u| BlockedSummary {
user: super::super::profile::UserPublic::from(u.clone()),
since: e
.created_at
.format(&time::format_description::well_known::Rfc3339)
.unwrap_or_default(),
})
})
.collect();
Ok(out)
}