feat: add friends!
This commit is contained in:
parent
ac3e7e242b
commit
e8b8b49643
13 changed files with 439 additions and 39 deletions
4
migrations/2025-07-07-131320_add_friends/down.sql
Normal file
4
migrations/2025-07-07-131320_add_friends/down.sql
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
-- This file should undo anything in `up.sql`
|
||||||
|
DROP TABLE friend_requests;
|
||||||
|
DROP FUNCTION check_friend_request;
|
||||||
|
DROP TABLE friends;
|
35
migrations/2025-07-07-131320_add_friends/up.sql
Normal file
35
migrations/2025-07-07-131320_add_friends/up.sql
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
-- Your SQL goes here
|
||||||
|
CREATE TABLE friends (
|
||||||
|
uuid1 UUID REFERENCES users(uuid),
|
||||||
|
uuid2 UUID REFERENCES users(uuid),
|
||||||
|
accepted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
PRIMARY KEY (uuid1, uuid2),
|
||||||
|
CHECK (uuid1 < uuid2)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE friend_requests (
|
||||||
|
sender UUID REFERENCES users(uuid),
|
||||||
|
receiver UUID REFERENCES users(uuid),
|
||||||
|
requested_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
PRIMARY KEY (sender, receiver),
|
||||||
|
CHECK (sender <> receiver)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Create a function to check for existing friendships
|
||||||
|
CREATE FUNCTION check_friend_request()
|
||||||
|
RETURNS TRIGGER AS $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1 FROM friends
|
||||||
|
WHERE (uuid1, uuid2) = (LEAST(NEW.sender, NEW.receiver), GREATEST(NEW.sender, NEW.receiver))
|
||||||
|
) THEN
|
||||||
|
RAISE EXCEPTION 'Users are already friends';
|
||||||
|
END IF;
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
-- Create the trigger
|
||||||
|
CREATE TRIGGER prevent_friend_request_conflict
|
||||||
|
BEFORE INSERT OR UPDATE ON friend_requests
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION check_friend_request();
|
|
@ -1,9 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
Data,
|
api::v1::auth::check_access_token, error::Error, objects::{Me, Member}, utils::{get_auth_header, global_checks}, Data
|
||||||
api::v1::auth::check_access_token,
|
|
||||||
error::Error,
|
|
||||||
objects::Member,
|
|
||||||
utils::{get_auth_header, global_checks},
|
|
||||||
};
|
};
|
||||||
use ::uuid::Uuid;
|
use ::uuid::Uuid;
|
||||||
use actix_web::{HttpRequest, HttpResponse, get, web};
|
use actix_web::{HttpRequest, HttpResponse, get, web};
|
||||||
|
@ -28,7 +24,9 @@ pub async fn get(
|
||||||
|
|
||||||
Member::check_membership(&mut conn, uuid, guild_uuid).await?;
|
Member::check_membership(&mut conn, uuid, guild_uuid).await?;
|
||||||
|
|
||||||
let members = Member::fetch_all(&data, guild_uuid).await?;
|
let me = Me::get(&mut conn, uuid).await?;
|
||||||
|
|
||||||
|
let members = Member::fetch_all(&data, &me, guild_uuid).await?;
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(members))
|
Ok(HttpResponse::Ok().json(members))
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
use actix_web::{HttpRequest, HttpResponse, get, post, web};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use ::uuid::Uuid;
|
||||||
|
|
||||||
|
pub mod uuid;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
Data,
|
||||||
|
api::v1::auth::check_access_token,
|
||||||
|
error::Error,
|
||||||
|
objects::Me,
|
||||||
|
utils::{get_auth_header, global_checks},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Returns a list of users that are your friends
|
||||||
|
#[get("/friends")]
|
||||||
|
pub async fn get(req: HttpRequest, data: web::Data<Data>) -> Result<HttpResponse, Error> {
|
||||||
|
let headers = req.headers();
|
||||||
|
|
||||||
|
let auth_header = get_auth_header(headers)?;
|
||||||
|
|
||||||
|
let mut conn = data.pool.get().await?;
|
||||||
|
|
||||||
|
let uuid = check_access_token(auth_header, &mut conn).await?;
|
||||||
|
|
||||||
|
global_checks(&data, uuid).await?;
|
||||||
|
|
||||||
|
let me = Me::get(&mut conn, uuid).await?;
|
||||||
|
|
||||||
|
let friends = me.get_friends(&data).await?;
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().json(friends))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct UserReq {
|
||||||
|
uuid: Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `POST /api/v1/me/friends` Send friend request
|
||||||
|
///
|
||||||
|
/// requires auth? yes
|
||||||
|
///
|
||||||
|
/// ### Request Example:
|
||||||
|
/// ```
|
||||||
|
/// json!({
|
||||||
|
/// "uuid": "155d2291-fb23-46bd-a656-ae7c5d8218e6",
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
/// NOTE: UUIDs in this response are made using `uuidgen`, UUIDs made by the actual backend will be UUIDv7 and have extractable timestamps
|
||||||
|
///
|
||||||
|
/// ### Responses
|
||||||
|
/// 200 Success
|
||||||
|
///
|
||||||
|
/// 404 Not Found
|
||||||
|
///
|
||||||
|
/// 400 Bad Request (usually means users are already friends)
|
||||||
|
///
|
||||||
|
#[post("/friends")]
|
||||||
|
pub async fn post(req: HttpRequest, json: web::Json<UserReq>, data: web::Data<Data>) -> Result<HttpResponse, Error> {
|
||||||
|
let headers = req.headers();
|
||||||
|
|
||||||
|
let auth_header = get_auth_header(headers)?;
|
||||||
|
|
||||||
|
let mut conn = data.pool.get().await?;
|
||||||
|
|
||||||
|
let uuid = check_access_token(auth_header, &mut conn).await?;
|
||||||
|
|
||||||
|
global_checks(&data, uuid).await?;
|
||||||
|
|
||||||
|
let me = Me::get(&mut conn, uuid).await?;
|
||||||
|
|
||||||
|
me.add_friend(&mut conn, json.uuid).await?;
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().finish())
|
||||||
|
}
|
29
src/api/v1/me/friends/uuid.rs
Normal file
29
src/api/v1/me/friends/uuid.rs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
use actix_web::{HttpRequest, HttpResponse, delete, web};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
Data,
|
||||||
|
api::v1::auth::check_access_token,
|
||||||
|
error::Error,
|
||||||
|
objects::Me,
|
||||||
|
utils::{get_auth_header, global_checks},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[delete("/friends/{uuid}")]
|
||||||
|
pub async fn delete(req: HttpRequest, path: web::Path<(Uuid,)>, data: web::Data<Data>) -> Result<HttpResponse, Error> {
|
||||||
|
let headers = req.headers();
|
||||||
|
|
||||||
|
let auth_header = get_auth_header(headers)?;
|
||||||
|
|
||||||
|
let mut conn = data.pool.get().await?;
|
||||||
|
|
||||||
|
let uuid = check_access_token(auth_header, &mut conn).await?;
|
||||||
|
|
||||||
|
global_checks(&data, uuid).await?;
|
||||||
|
|
||||||
|
let me = Me::get(&mut conn, uuid).await?;
|
||||||
|
|
||||||
|
me.remove_friend(&mut conn, path.0).await?;
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().finish())
|
||||||
|
}
|
|
@ -11,12 +11,16 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
mod guilds;
|
mod guilds;
|
||||||
|
mod friends;
|
||||||
|
|
||||||
pub fn web() -> Scope {
|
pub fn web() -> Scope {
|
||||||
web::scope("/me")
|
web::scope("/me")
|
||||||
.service(get)
|
.service(get)
|
||||||
.service(update)
|
.service(update)
|
||||||
.service(guilds::get)
|
.service(guilds::get)
|
||||||
|
.service(friends::get)
|
||||||
|
.service(friends::post)
|
||||||
|
.service(friends::uuid::delete)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("")]
|
#[get("")]
|
||||||
|
|
|
@ -4,11 +4,7 @@ use actix_web::{HttpRequest, HttpResponse, get, web};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Data,
|
api::v1::auth::check_access_token, error::Error, objects::{Me, User}, utils::{get_auth_header, global_checks}, Data
|
||||||
api::v1::auth::check_access_token,
|
|
||||||
error::Error,
|
|
||||||
objects::User,
|
|
||||||
utils::{get_auth_header, global_checks},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// `GET /api/v1/users/{uuid}` Returns user with the given UUID
|
/// `GET /api/v1/users/{uuid}` Returns user with the given UUID
|
||||||
|
@ -45,7 +41,9 @@ pub async fn get(
|
||||||
|
|
||||||
global_checks(&data, uuid).await?;
|
global_checks(&data, uuid).await?;
|
||||||
|
|
||||||
let user = User::fetch_one(&data, user_uuid).await?;
|
let me = Me::get(&mut conn, uuid).await?;
|
||||||
|
|
||||||
|
let user = User::fetch_one_with_friendship(&data, &me, user_uuid).await?;
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(user))
|
Ok(HttpResponse::Ok().json(user))
|
||||||
}
|
}
|
||||||
|
|
24
src/objects/friends.rs
Normal file
24
src/objects/friends.rs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use diesel::{Queryable, Selectable};
|
||||||
|
use serde::Serialize;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::schema::{friend_requests, friends};
|
||||||
|
|
||||||
|
#[derive(Serialize, Queryable, Selectable, Clone)]
|
||||||
|
#[diesel(table_name = friends)]
|
||||||
|
#[diesel(check_for_backend(diesel::pg::Pg))]
|
||||||
|
pub struct Friend {
|
||||||
|
pub uuid1: Uuid,
|
||||||
|
pub uuid2: Uuid,
|
||||||
|
pub accepted_at: DateTime<Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Queryable, Selectable, Clone)]
|
||||||
|
#[diesel(table_name = friend_requests)]
|
||||||
|
#[diesel(check_for_backend(diesel::pg::Pg))]
|
||||||
|
pub struct FriendRequest {
|
||||||
|
pub sender: Uuid,
|
||||||
|
pub receiver: Uuid,
|
||||||
|
pub requested_at: DateTime<Utc>,
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
use actix_web::web::BytesMut;
|
use actix_web::web::BytesMut;
|
||||||
use diesel::{ExpressionMethods, QueryDsl, Queryable, Selectable, SelectableHelper, update};
|
use diesel::{delete, insert_into, update, ExpressionMethods, QueryDsl, Queryable, Selectable, SelectableHelper};
|
||||||
use diesel_async::RunQueryDsl;
|
use diesel_async::RunQueryDsl;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use tokio::task;
|
use tokio::task;
|
||||||
|
@ -7,10 +7,7 @@ use url::Url;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Conn, Data,
|
error::Error, objects::{FriendRequest, Friend, User}, schema::{friend_requests, friends, guild_members, guilds, users}, utils::{image_check, EMAIL_REGEX, USERNAME_REGEX}, Conn, Data
|
||||||
error::Error,
|
|
||||||
schema::{guild_members, guilds, users},
|
|
||||||
utils::{EMAIL_REGEX, USERNAME_REGEX, image_check},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{Guild, guild::GuildBuilder, load_or_empty, member::MemberBuilder};
|
use super::{Guild, guild::GuildBuilder, load_or_empty, member::MemberBuilder};
|
||||||
|
@ -153,13 +150,11 @@ impl Me {
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mut conn = data.pool.get().await?;
|
let mut conn = data.pool.get().await?;
|
||||||
|
|
||||||
let new_display_name_option;
|
let new_display_name_option = if new_display_name.is_empty() {
|
||||||
|
None
|
||||||
if new_display_name.is_empty() {
|
|
||||||
new_display_name_option = None;
|
|
||||||
} else {
|
} else {
|
||||||
new_display_name_option = Some(new_display_name)
|
Some(new_display_name)
|
||||||
}
|
};
|
||||||
|
|
||||||
use users::dsl;
|
use users::dsl;
|
||||||
update(users::table)
|
update(users::table)
|
||||||
|
@ -236,4 +231,175 @@ impl Me {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn friends_with(&self, conn: &mut Conn, user_uuid: Uuid) -> Result<Option<Friend>, Error> {
|
||||||
|
use friends::dsl;
|
||||||
|
|
||||||
|
let friends: Vec<Friend> = if self.uuid < user_uuid {
|
||||||
|
load_or_empty(
|
||||||
|
dsl::friends
|
||||||
|
.filter(dsl::uuid1.eq(self.uuid))
|
||||||
|
.filter(dsl::uuid2.eq(user_uuid))
|
||||||
|
.load(conn)
|
||||||
|
.await
|
||||||
|
)?
|
||||||
|
} else {
|
||||||
|
load_or_empty(
|
||||||
|
dsl::friends
|
||||||
|
.filter(dsl::uuid1.eq(user_uuid))
|
||||||
|
.filter(dsl::uuid2.eq(self.uuid))
|
||||||
|
.load(conn)
|
||||||
|
.await
|
||||||
|
)?
|
||||||
|
};
|
||||||
|
|
||||||
|
if friends.is_empty() {
|
||||||
|
return Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(friends[0].clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn add_friend(&self, conn: &mut Conn, user_uuid: Uuid) -> Result<(), Error> {
|
||||||
|
if self.friends_with(conn, user_uuid).await?.is_some() {
|
||||||
|
// TODO: Check if another error should be used
|
||||||
|
return Err(Error::BadRequest("Already friends with user".to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
use friend_requests::dsl;
|
||||||
|
|
||||||
|
let friend_request: Vec<FriendRequest> = load_or_empty(
|
||||||
|
dsl::friend_requests
|
||||||
|
.filter(dsl::sender.eq(user_uuid))
|
||||||
|
.filter(dsl::receiver.eq(self.uuid))
|
||||||
|
.load(conn)
|
||||||
|
.await
|
||||||
|
)?;
|
||||||
|
|
||||||
|
#[allow(clippy::get_first)]
|
||||||
|
if let Some(friend_request) = friend_request.get(0) {
|
||||||
|
use friends::dsl;
|
||||||
|
|
||||||
|
if self.uuid < user_uuid {
|
||||||
|
insert_into(friends::table)
|
||||||
|
.values((dsl::uuid1.eq(self.uuid), dsl::uuid2.eq(user_uuid)))
|
||||||
|
.execute(conn)
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
insert_into(friends::table)
|
||||||
|
.values((dsl::uuid1.eq(user_uuid), dsl::uuid2.eq(self.uuid)))
|
||||||
|
.execute(conn)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
use friend_requests::dsl as frdsl;
|
||||||
|
|
||||||
|
delete(friend_requests::table)
|
||||||
|
.filter(frdsl::sender.eq(friend_request.sender))
|
||||||
|
.filter(frdsl::receiver.eq(friend_request.receiver))
|
||||||
|
.execute(conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
use friend_requests::dsl;
|
||||||
|
|
||||||
|
insert_into(friend_requests::table)
|
||||||
|
.values((dsl::sender.eq(self.uuid), dsl::receiver.eq(user_uuid)))
|
||||||
|
.execute(conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn remove_friend(&self, conn: &mut Conn, user_uuid: Uuid) -> Result<(), Error> {
|
||||||
|
if self.friends_with(conn, user_uuid).await?.is_none() {
|
||||||
|
// TODO: Check if another error should be used
|
||||||
|
return Err(Error::BadRequest("Not friends with user".to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
use friends::dsl;
|
||||||
|
|
||||||
|
if self.uuid < user_uuid {
|
||||||
|
delete(friends::table)
|
||||||
|
.filter(dsl::uuid1.eq(self.uuid))
|
||||||
|
.filter(dsl::uuid2.eq(user_uuid))
|
||||||
|
.execute(conn)
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
delete(friends::table)
|
||||||
|
.filter(dsl::uuid1.eq(user_uuid))
|
||||||
|
.filter(dsl::uuid2.eq(self.uuid))
|
||||||
|
.execute(conn)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_friends(&self, data: &Data) -> Result<Vec<User>, Error> {
|
||||||
|
use friends::dsl;
|
||||||
|
|
||||||
|
let mut conn = data.pool.get().await?;
|
||||||
|
|
||||||
|
let friends1 = load_or_empty(
|
||||||
|
dsl::friends
|
||||||
|
.filter(dsl::uuid1.eq(self.uuid))
|
||||||
|
.select(Friend::as_select())
|
||||||
|
.load(&mut conn)
|
||||||
|
.await
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let friends2 = load_or_empty(
|
||||||
|
dsl::friends
|
||||||
|
.filter(dsl::uuid2.eq(self.uuid))
|
||||||
|
.select(Friend::as_select())
|
||||||
|
.load(&mut conn)
|
||||||
|
.await
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let friend_futures = friends1.iter().map(async move |friend| {
|
||||||
|
User::fetch_one_with_friendship(data, self, friend.uuid2).await
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut friends = futures::future::try_join_all(friend_futures).await?;
|
||||||
|
|
||||||
|
let friend_futures = friends2.iter().map(async move |friend| {
|
||||||
|
User::fetch_one_with_friendship(data, self, friend.uuid1).await
|
||||||
|
});
|
||||||
|
|
||||||
|
friends.append(&mut futures::future::try_join_all(friend_futures).await?);
|
||||||
|
|
||||||
|
Ok(friends)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO
|
||||||
|
pub async fn get_friend_requests(&self, conn: &mut Conn) -> Result<Vec<FriendRequest>, Error> {
|
||||||
|
use friend_requests::dsl;
|
||||||
|
|
||||||
|
let friend_request: Vec<FriendRequest> = load_or_empty(
|
||||||
|
dsl::friend_requests
|
||||||
|
.filter(dsl::receiver.eq(self.uuid))
|
||||||
|
.load(conn)
|
||||||
|
.await
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_friend_request(&self, conn: &mut Conn, user_uuid: Uuid) -> Result<Vec<FriendRequest>, Error> {
|
||||||
|
use friend_requests::dsl;
|
||||||
|
|
||||||
|
let friend_request: Vec<FriendRequest> = load_or_empty(
|
||||||
|
dsl::friend_requests
|
||||||
|
.filter(dsl::sender.eq(user_uuid))
|
||||||
|
.filter(dsl::receiver.eq(self.uuid))
|
||||||
|
.load(conn)
|
||||||
|
.await
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok()
|
||||||
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Conn, Data,
|
error::Error, objects::{Me, Permissions, Role}, schema::guild_members, Conn, Data
|
||||||
error::Error,
|
|
||||||
objects::{Permissions, Role},
|
|
||||||
schema::guild_members,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{User, load_or_empty};
|
use super::{User, load_or_empty};
|
||||||
|
@ -26,8 +23,14 @@ pub struct MemberBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MemberBuilder {
|
impl MemberBuilder {
|
||||||
pub async fn build(&self, data: &Data) -> Result<Member, Error> {
|
pub async fn build(&self, data: &Data, me: Option<&Me>) -> Result<Member, Error> {
|
||||||
let user = User::fetch_one(data, self.user_uuid).await?;
|
let user;
|
||||||
|
|
||||||
|
if let Some(me) = me {
|
||||||
|
user = User::fetch_one_with_friendship(data, me, self.user_uuid).await?;
|
||||||
|
} else {
|
||||||
|
user = User::fetch_one(data, self.user_uuid).await?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Member {
|
Ok(Member {
|
||||||
uuid: self.uuid,
|
uuid: self.uuid,
|
||||||
|
@ -94,7 +97,7 @@ impl Member {
|
||||||
Ok(member_builder)
|
Ok(member_builder)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn fetch_one(data: &Data, user_uuid: Uuid, guild_uuid: Uuid) -> Result<Self, Error> {
|
pub async fn fetch_one(data: &Data, me: &Me, user_uuid: Uuid, guild_uuid: Uuid) -> Result<Self, Error> {
|
||||||
let mut conn = data.pool.get().await?;
|
let mut conn = data.pool.get().await?;
|
||||||
|
|
||||||
use guild_members::dsl;
|
use guild_members::dsl;
|
||||||
|
@ -105,10 +108,10 @@ impl Member {
|
||||||
.get_result(&mut conn)
|
.get_result(&mut conn)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
member.build(data).await
|
member.build(data, Some(me)).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn fetch_all(data: &Data, guild_uuid: Uuid) -> Result<Vec<Self>, Error> {
|
pub async fn fetch_all(data: &Data, me: &Me, guild_uuid: Uuid) -> Result<Vec<Self>, Error> {
|
||||||
let mut conn = data.pool.get().await?;
|
let mut conn = data.pool.get().await?;
|
||||||
|
|
||||||
use guild_members::dsl;
|
use guild_members::dsl;
|
||||||
|
@ -122,7 +125,7 @@ impl Member {
|
||||||
|
|
||||||
let member_futures = member_builders
|
let member_futures = member_builders
|
||||||
.iter()
|
.iter()
|
||||||
.map(async move |m| m.build(data).await);
|
.map(async move |m| m.build(data, Some(me)).await);
|
||||||
|
|
||||||
futures::future::try_join_all(member_futures).await
|
futures::future::try_join_all(member_futures).await
|
||||||
}
|
}
|
||||||
|
@ -145,6 +148,6 @@ impl Member {
|
||||||
.execute(&mut conn)
|
.execute(&mut conn)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
member.build(data).await
|
member.build(data, None).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ mod message;
|
||||||
mod password_reset_token;
|
mod password_reset_token;
|
||||||
mod role;
|
mod role;
|
||||||
mod user;
|
mod user;
|
||||||
|
mod friends;
|
||||||
|
|
||||||
pub use channel::Channel;
|
pub use channel::Channel;
|
||||||
pub use email_token::EmailToken;
|
pub use email_token::EmailToken;
|
||||||
|
@ -29,6 +30,8 @@ pub use password_reset_token::PasswordResetToken;
|
||||||
pub use role::Permissions;
|
pub use role::Permissions;
|
||||||
pub use role::Role;
|
pub use role::Role;
|
||||||
pub use user::User;
|
pub use user::User;
|
||||||
|
pub use friends::Friend;
|
||||||
|
pub use friends::FriendRequest;
|
||||||
|
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,40 @@
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
use diesel::{ExpressionMethods, QueryDsl, Queryable, Selectable, SelectableHelper};
|
use diesel::{ExpressionMethods, QueryDsl, Queryable, Selectable, SelectableHelper};
|
||||||
use diesel_async::RunQueryDsl;
|
use diesel_async::RunQueryDsl;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{Conn, Data, error::Error, schema::users};
|
use crate::{error::Error, objects::Me, schema::users, Conn, Data};
|
||||||
|
|
||||||
use super::load_or_empty;
|
use super::load_or_empty;
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Queryable, Selectable)]
|
#[derive(Deserialize, Serialize, Clone, Queryable, Selectable)]
|
||||||
#[diesel(table_name = users)]
|
#[diesel(table_name = users)]
|
||||||
#[diesel(check_for_backend(diesel::pg::Pg))]
|
#[diesel(check_for_backend(diesel::pg::Pg))]
|
||||||
|
pub struct UserBuilder {
|
||||||
|
uuid: Uuid,
|
||||||
|
username: String,
|
||||||
|
display_name: Option<String>,
|
||||||
|
avatar: Option<String>,
|
||||||
|
pronouns: Option<String>,
|
||||||
|
about: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UserBuilder {
|
||||||
|
fn build(self) -> User {
|
||||||
|
User {
|
||||||
|
uuid: self.uuid,
|
||||||
|
username: self.username,
|
||||||
|
display_name: self.display_name,
|
||||||
|
avatar: self.avatar,
|
||||||
|
pronouns: self.pronouns,
|
||||||
|
about: self.about,
|
||||||
|
friends_since: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone)]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
username: String,
|
username: String,
|
||||||
|
@ -17,6 +42,7 @@ pub struct User {
|
||||||
avatar: Option<String>,
|
avatar: Option<String>,
|
||||||
pronouns: Option<String>,
|
pronouns: Option<String>,
|
||||||
about: Option<String>,
|
about: Option<String>,
|
||||||
|
friends_since: Option<DateTime<Utc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl User {
|
impl User {
|
||||||
|
@ -28,33 +54,49 @@ impl User {
|
||||||
}
|
}
|
||||||
|
|
||||||
use users::dsl;
|
use users::dsl;
|
||||||
let user: User = dsl::users
|
let user_builder: UserBuilder = dsl::users
|
||||||
.filter(dsl::uuid.eq(user_uuid))
|
.filter(dsl::uuid.eq(user_uuid))
|
||||||
.select(User::as_select())
|
.select(UserBuilder::as_select())
|
||||||
.get_result(&mut conn)
|
.get_result(&mut conn)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
let user = user_builder.build();
|
||||||
|
|
||||||
data.set_cache_key(user_uuid.to_string(), user.clone(), 1800)
|
data.set_cache_key(user_uuid.to_string(), user.clone(), 1800)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(user)
|
Ok(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn fetch_one_with_friendship(data: &Data, me: &Me, user_uuid: Uuid) -> Result<Self, Error> {
|
||||||
|
let mut conn = data.pool.get().await?;
|
||||||
|
|
||||||
|
let mut user = Self::fetch_one(data, user_uuid).await?;
|
||||||
|
|
||||||
|
if let Some(friend) = me.friends_with(&mut conn, user_uuid).await? {
|
||||||
|
user.friends_since = Some(friend.accepted_at);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(user)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn fetch_amount(
|
pub async fn fetch_amount(
|
||||||
conn: &mut Conn,
|
conn: &mut Conn,
|
||||||
offset: i64,
|
offset: i64,
|
||||||
amount: i64,
|
amount: i64,
|
||||||
) -> Result<Vec<Self>, Error> {
|
) -> Result<Vec<Self>, Error> {
|
||||||
use users::dsl;
|
use users::dsl;
|
||||||
let users: Vec<User> = load_or_empty(
|
let user_builders: Vec<UserBuilder> = load_or_empty(
|
||||||
dsl::users
|
dsl::users
|
||||||
.limit(amount)
|
.limit(amount)
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
.select(User::as_select())
|
.select(UserBuilder::as_select())
|
||||||
.load(conn)
|
.load(conn)
|
||||||
.await,
|
.await,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
let users: Vec<User> = user_builders.iter().map(|u| u.clone().build()).collect();
|
||||||
|
|
||||||
Ok(users)
|
Ok(users)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,22 @@ diesel::table! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
friend_requests (sender, receiver) {
|
||||||
|
sender -> Uuid,
|
||||||
|
receiver -> Uuid,
|
||||||
|
requested_at -> Timestamptz,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
friends (uuid1, uuid2) {
|
||||||
|
uuid1 -> Uuid,
|
||||||
|
uuid2 -> Uuid,
|
||||||
|
accepted_at -> Timestamptz,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
diesel::table! {
|
diesel::table! {
|
||||||
guild_members (uuid) {
|
guild_members (uuid) {
|
||||||
uuid -> Uuid,
|
uuid -> Uuid,
|
||||||
|
@ -153,6 +169,8 @@ diesel::allow_tables_to_appear_in_same_query!(
|
||||||
access_tokens,
|
access_tokens,
|
||||||
channel_permissions,
|
channel_permissions,
|
||||||
channels,
|
channels,
|
||||||
|
friend_requests,
|
||||||
|
friends,
|
||||||
guild_members,
|
guild_members,
|
||||||
guilds,
|
guilds,
|
||||||
instance_permissions,
|
instance_permissions,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue