//! Friends: mutual, by request and accept. A `friendships` row exists per
//! pair in the direction it was asked; friends are the accepted rows with
//! you on either side.
use std::collections::HashSet;
use chrono::Utc;
use sea_orm::sea_query::{Condition, Expr};
use sea_orm::*;
use serde::Serialize;
use uuid::Uuid;
use crate::entities::friendships::{self, FriendStatus};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Link {
None,
Friends,
/// You asked them.
Requested,
/// They asked you.
Incoming,
}
fn between(a: Uuid, b: Uuid) -> Condition {
Condition::any()
.add(
Condition::all()
.add(friendships::Column::RequesterId.eq(a))
.add(friendships::Column::AddresseeId.eq(b)),
)
.add(
Condition::all()
.add(friendships::Column::RequesterId.eq(b))
.add(friendships::Column::AddresseeId.eq(a)),
)
}
pub async fn link(db: &impl ConnectionTrait, me: Uuid, other: Uuid) -> Result<Link, DbErr> {
Ok(match friendships::Entity::find().filter(between(me, other)).one(db).await? {
None => Link::None,
Some(f) if f.status == FriendStatus::Accepted => Link::Friends,
Some(f) if f.requester_id == me => Link::Requested,
Some(_) => Link::Incoming,
})
}
/// Everyone `me` has any link with, and what it is.
pub async fn links(db: &impl ConnectionTrait, me: Uuid) -> Result<Vec<(Uuid, Link)>, DbErr> {
Ok(friendships::Entity::find()
.filter(
Condition::any()
.add(friendships::Column::RequesterId.eq(me))
.add(friendships::Column::AddresseeId.eq(me)),
)
.all(db)
.await?
.into_iter()
.map(|f| {
let (other, link) = if f.status == FriendStatus::Accepted {
(if f.requester_id == me { f.addressee_id } else { f.requester_id }, Link::Friends)
} else if f.requester_id == me {
(f.addressee_id, Link::Requested)
} else {
(f.requester_id, Link::Incoming)
};
(other, link)
})
.collect())
}
pub async fn friend_ids(db: &impl ConnectionTrait, me: Uuid) -> Result<HashSet<Uuid>, DbErr> {
Ok(links(db, me).await?.into_iter().filter(|(_, l)| *l == Link::Friends).map(|(u, _)| u).collect())
}
/// Ask to be friends. If they'd already asked you, this accepts instead.
pub async fn request(db: &impl ConnectionTrait, me: Uuid, other: Uuid) -> Result<Link, DbErr> {
if me == other {
return Ok(Link::None);
}
match link(db, me, other).await? {
Link::None => {
friendships::ActiveModel {
requester_id: Set(me),
addressee_id: Set(other),
status: Set(FriendStatus::Pending),
created_at: Set(Utc::now().into()),
accepted_at: Set(None),
}
.insert(db)
.await?;
Ok(Link::Requested)
}
Link::Incoming => accept(db, me, other).await,
l => Ok(l),
}
}
/// Accept `requester`'s request to `me`.
pub async fn accept(db: &impl ConnectionTrait, me: Uuid, requester: Uuid) -> Result<Link, DbErr> {
let updated = friendships::Entity::update_many()
.col_expr(friendships::Column::Status, Expr::value(FriendStatus::Accepted.to_value()))
.col_expr(friendships::Column::AcceptedAt, Expr::value(Utc::now()))
.filter(friendships::Column::RequesterId.eq(requester))
.filter(friendships::Column::AddresseeId.eq(me))
.filter(friendships::Column::Status.eq(FriendStatus::Pending))
.exec(db)
.await?;
if updated.rows_affected == 0 { link(db, me, requester).await } else { Ok(Link::Friends) }
}
/// Decline, cancel, or unfriend — whichever applies, the link is gone.
pub async fn remove(db: &impl ConnectionTrait, me: Uuid, other: Uuid) -> Result<(), DbErr> {
friendships::Entity::delete_many().filter(between(me, other)).exec(db).await?;
Ok(())
}
neiam
/fieri-forever
the flavortown lunch engine
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
Log in to mark this repository.
4.0 KiB · text
History
aecd85b