Compare commits
2 commits
ac3e7e242b
...
e8a9857e19
Author | SHA1 | Date | |
---|---|---|---|
e8a9857e19 | |||
e8b8b49643 |
16 changed files with 484 additions and 38 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();
|
|
@ -13,11 +13,11 @@ use crate::{
|
|||
/// requires auth: kinda, needs refresh token set but no access token is technically required
|
||||
///
|
||||
/// ### Responses
|
||||
///
|
||||
///
|
||||
/// 200 Logged out
|
||||
///
|
||||
///
|
||||
/// 404 Refresh token is invalid
|
||||
///
|
||||
///
|
||||
/// 401 Unauthorized (no refresh token found)
|
||||
///
|
||||
#[get("/logout")]
|
||||
|
@ -38,7 +38,9 @@ pub async fn res(req: HttpRequest, data: web::Data<Data>) -> Result<HttpResponse
|
|||
refresh_token_cookie.make_removal();
|
||||
|
||||
if deleted == 0 {
|
||||
return Ok(HttpResponse::NotFound().cookie(refresh_token_cookie).finish())
|
||||
return Ok(HttpResponse::NotFound()
|
||||
.cookie(refresh_token_cookie)
|
||||
.finish());
|
||||
}
|
||||
|
||||
Ok(HttpResponse::Ok().cookie(refresh_token_cookie).finish())
|
||||
|
|
|
@ -26,13 +26,13 @@ struct Query {
|
|||
///
|
||||
/// ### Responses
|
||||
/// 200 Success
|
||||
///
|
||||
///
|
||||
/// 204 Already verified
|
||||
///
|
||||
///
|
||||
/// 410 Token Expired
|
||||
///
|
||||
///
|
||||
/// 404 Not Found
|
||||
///
|
||||
///
|
||||
/// 401 Unauthorized
|
||||
///
|
||||
#[get("/verify-email")]
|
||||
|
@ -74,11 +74,11 @@ pub async fn get(
|
|||
///
|
||||
/// ### Responses
|
||||
/// 200 Email sent
|
||||
///
|
||||
///
|
||||
/// 204 Already verified
|
||||
///
|
||||
///
|
||||
/// 429 Too Many Requests
|
||||
///
|
||||
///
|
||||
/// 401 Unauthorized
|
||||
///
|
||||
#[post("/verify-email")]
|
||||
|
|
|
@ -83,7 +83,9 @@ pub async fn ws(
|
|||
|
||||
let message_body: MessageBody = serde_json::from_str(&text)?;
|
||||
|
||||
let message = channel.new_message(&data, uuid, message_body.message, message_body.reply_to).await?;
|
||||
let message = channel
|
||||
.new_message(&data, uuid, message_body.message, message_body.reply_to)
|
||||
.await?;
|
||||
|
||||
redis::cmd("PUBLISH")
|
||||
.arg(&[channel_uuid.to_string(), serde_json::to_string(&message)?])
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::{
|
|||
Data,
|
||||
api::v1::auth::check_access_token,
|
||||
error::Error,
|
||||
objects::Member,
|
||||
objects::{Me, Member},
|
||||
utils::{get_auth_header, global_checks},
|
||||
};
|
||||
use ::uuid::Uuid;
|
||||
|
@ -28,7 +28,9 @@ pub async fn get(
|
|||
|
||||
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))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
use ::uuid::Uuid;
|
||||
use actix_web::{HttpRequest, HttpResponse, get, post, web};
|
||||
use serde::Deserialize;
|
||||
|
||||
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())
|
||||
}
|
33
src/api/v1/me/friends/uuid.rs
Normal file
33
src/api/v1/me/friends/uuid.rs
Normal file
|
@ -0,0 +1,33 @@
|
|||
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())
|
||||
}
|
|
@ -10,6 +10,7 @@ use crate::{
|
|||
utils::{get_auth_header, global_checks},
|
||||
};
|
||||
|
||||
mod friends;
|
||||
mod guilds;
|
||||
|
||||
pub fn web() -> Scope {
|
||||
|
@ -17,6 +18,9 @@ pub fn web() -> Scope {
|
|||
.service(get)
|
||||
.service(update)
|
||||
.service(guilds::get)
|
||||
.service(friends::get)
|
||||
.service(friends::post)
|
||||
.service(friends::uuid::delete)
|
||||
}
|
||||
|
||||
#[get("")]
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::{
|
|||
Data,
|
||||
api::v1::auth::check_access_token,
|
||||
error::Error,
|
||||
objects::User,
|
||||
objects::{Me, User},
|
||||
utils::{get_auth_header, global_checks},
|
||||
};
|
||||
|
||||
|
@ -45,7 +45,9 @@ pub async fn get(
|
|||
|
||||
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))
|
||||
}
|
||||
|
|
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,8 @@
|
|||
use actix_web::web::BytesMut;
|
||||
use diesel::{ExpressionMethods, QueryDsl, Queryable, Selectable, SelectableHelper, update};
|
||||
use diesel::{
|
||||
ExpressionMethods, QueryDsl, Queryable, Selectable, SelectableHelper, delete, insert_into,
|
||||
update,
|
||||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use serde::Serialize;
|
||||
use tokio::task;
|
||||
|
@ -9,7 +12,8 @@ use uuid::Uuid;
|
|||
use crate::{
|
||||
Conn, Data,
|
||||
error::Error,
|
||||
schema::{guild_members, guilds, users},
|
||||
objects::{Friend, FriendRequest, User},
|
||||
schema::{friend_requests, friends, guild_members, guilds, users},
|
||||
utils::{EMAIL_REGEX, USERNAME_REGEX, image_check},
|
||||
};
|
||||
|
||||
|
@ -124,7 +128,10 @@ impl Me {
|
|||
}
|
||||
|
||||
pub async fn set_username(&mut self, data: &Data, new_username: String) -> Result<(), Error> {
|
||||
if !USERNAME_REGEX.is_match(&new_username) || new_username.len() < 3 || new_username.len() > 32 {
|
||||
if !USERNAME_REGEX.is_match(&new_username)
|
||||
|| new_username.len() < 3
|
||||
|| new_username.len() > 32
|
||||
{
|
||||
return Err(Error::BadRequest("Invalid username".to_string()));
|
||||
}
|
||||
|
||||
|
@ -153,13 +160,11 @@ impl Me {
|
|||
) -> Result<(), Error> {
|
||||
let mut conn = data.pool.get().await?;
|
||||
|
||||
let new_display_name_option;
|
||||
|
||||
if new_display_name.is_empty() {
|
||||
new_display_name_option = None;
|
||||
let new_display_name_option = if new_display_name.is_empty() {
|
||||
None
|
||||
} else {
|
||||
new_display_name_option = Some(new_display_name)
|
||||
}
|
||||
Some(new_display_name)
|
||||
};
|
||||
|
||||
use users::dsl;
|
||||
update(users::table)
|
||||
|
@ -236,4 +241,179 @@ impl Me {
|
|||
|
||||
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()
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ use uuid::Uuid;
|
|||
use crate::{
|
||||
Conn, Data,
|
||||
error::Error,
|
||||
objects::{Permissions, Role},
|
||||
objects::{Me, Permissions, Role},
|
||||
schema::guild_members,
|
||||
};
|
||||
|
||||
|
@ -26,8 +26,14 @@ pub struct MemberBuilder {
|
|||
}
|
||||
|
||||
impl MemberBuilder {
|
||||
pub async fn build(&self, data: &Data) -> Result<Member, Error> {
|
||||
let user = User::fetch_one(data, self.user_uuid).await?;
|
||||
pub async fn build(&self, data: &Data, me: Option<&Me>) -> Result<Member, Error> {
|
||||
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 {
|
||||
uuid: self.uuid,
|
||||
|
@ -94,7 +100,12 @@ impl Member {
|
|||
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?;
|
||||
|
||||
use guild_members::dsl;
|
||||
|
@ -105,10 +116,10 @@ impl Member {
|
|||
.get_result(&mut conn)
|
||||
.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?;
|
||||
|
||||
use guild_members::dsl;
|
||||
|
@ -122,7 +133,7 @@ impl Member {
|
|||
|
||||
let member_futures = member_builders
|
||||
.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
|
||||
}
|
||||
|
@ -145,6 +156,6 @@ impl Member {
|
|||
.execute(&mut conn)
|
||||
.await?;
|
||||
|
||||
member.build(data).await
|
||||
member.build(data, None).await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ use uuid::Uuid;
|
|||
|
||||
mod channel;
|
||||
mod email_token;
|
||||
mod friends;
|
||||
mod guild;
|
||||
mod invite;
|
||||
mod me;
|
||||
|
@ -20,6 +21,8 @@ mod user;
|
|||
|
||||
pub use channel::Channel;
|
||||
pub use email_token::EmailToken;
|
||||
pub use friends::Friend;
|
||||
pub use friends::FriendRequest;
|
||||
pub use guild::Guild;
|
||||
pub use invite::Invite;
|
||||
pub use me::Me;
|
||||
|
|
|
@ -1,15 +1,40 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use diesel::{ExpressionMethods, QueryDsl, Queryable, Selectable, SelectableHelper};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{Conn, Data, error::Error, schema::users};
|
||||
use crate::{Conn, Data, error::Error, objects::Me, schema::users};
|
||||
|
||||
use super::load_or_empty;
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Queryable, Selectable)]
|
||||
#[diesel(table_name = users)]
|
||||
#[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 {
|
||||
uuid: Uuid,
|
||||
username: String,
|
||||
|
@ -17,6 +42,7 @@ pub struct User {
|
|||
avatar: Option<String>,
|
||||
pronouns: Option<String>,
|
||||
about: Option<String>,
|
||||
friends_since: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
impl User {
|
||||
|
@ -28,33 +54,53 @@ impl User {
|
|||
}
|
||||
|
||||
use users::dsl;
|
||||
let user: User = dsl::users
|
||||
let user_builder: UserBuilder = dsl::users
|
||||
.filter(dsl::uuid.eq(user_uuid))
|
||||
.select(User::as_select())
|
||||
.select(UserBuilder::as_select())
|
||||
.get_result(&mut conn)
|
||||
.await?;
|
||||
|
||||
let user = user_builder.build();
|
||||
|
||||
data.set_cache_key(user_uuid.to_string(), user.clone(), 1800)
|
||||
.await?;
|
||||
|
||||
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(
|
||||
conn: &mut Conn,
|
||||
offset: i64,
|
||||
amount: i64,
|
||||
) -> Result<Vec<Self>, Error> {
|
||||
use users::dsl;
|
||||
let users: Vec<User> = load_or_empty(
|
||||
let user_builders: Vec<UserBuilder> = load_or_empty(
|
||||
dsl::users
|
||||
.limit(amount)
|
||||
.offset(offset)
|
||||
.select(User::as_select())
|
||||
.select(UserBuilder::as_select())
|
||||
.load(conn)
|
||||
.await,
|
||||
)?;
|
||||
|
||||
let users: Vec<User> = user_builders.iter().map(|u| u.clone().build()).collect();
|
||||
|
||||
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! {
|
||||
guild_members (uuid) {
|
||||
uuid -> Uuid,
|
||||
|
@ -153,6 +169,8 @@ diesel::allow_tables_to_appear_in_same_query!(
|
|||
access_tokens,
|
||||
channel_permissions,
|
||||
channels,
|
||||
friend_requests,
|
||||
friends,
|
||||
guild_members,
|
||||
guilds,
|
||||
instance_permissions,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue