Friends system for app (#2958)

* Friends system for app

* Fix impl issues

* move friends to in-memory store
This commit is contained in:
Geometrically
2024-11-26 18:23:29 -07:00
committed by GitHub
parent 7184c5f5c7
commit 47b0ccdf78
46 changed files with 1078 additions and 539 deletions

View File

@@ -18,7 +18,7 @@ const FLOWS_NAMESPACE: &str = "flows";
pub enum Flow {
OAuth {
user_id: Option<UserId>,
url: Option<String>,
url: String,
provider: AuthProvider,
},
Login2FA {

View File

@@ -0,0 +1,132 @@
use crate::database::models::UserId;
use chrono::{DateTime, Utc};
pub struct FriendItem {
pub user_id: UserId,
pub friend_id: UserId,
pub created: DateTime<Utc>,
pub accepted: bool,
}
impl FriendItem {
pub async fn insert(
&self,
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<(), sqlx::Error> {
sqlx::query!(
"
INSERT INTO friends (user_id, friend_id, created, accepted)
VALUES ($1, $2, $3, $4)
",
self.user_id.0,
self.friend_id.0,
self.created,
self.accepted,
)
.execute(&mut **transaction)
.await?;
Ok(())
}
pub async fn get_friend<'a, E>(
user_id: UserId,
friend_id: UserId,
exec: E,
) -> Result<Option<FriendItem>, sqlx::Error>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
{
let friend = sqlx::query!(
"
SELECT f.user_id, f.friend_id, f.created, f.accepted
FROM friends f
WHERE (f.user_id = $1 AND f.friend_id = $2) OR (f.user_id = $2 AND f.friend_id = $1)
",
user_id.0,
friend_id.0,
)
.fetch_optional(exec)
.await?
.map(|row| FriendItem {
user_id: UserId(row.user_id),
friend_id: UserId(row.friend_id),
created: row.created,
accepted: row.accepted,
});
Ok(friend)
}
pub async fn update_friend(
user_id: UserId,
friend_id: UserId,
accepted: bool,
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<(), sqlx::Error> {
sqlx::query!(
"
UPDATE friends
SET accepted = $3
WHERE (user_id = $1 AND friend_id = $2) OR (user_id = $2 AND friend_id = $1)
",
user_id.0,
friend_id.0,
accepted,
)
.execute(&mut **transaction)
.await?;
Ok(())
}
pub async fn get_user_friends<'a, E>(
user_id: UserId,
accepted: Option<bool>,
exec: E,
) -> Result<Vec<FriendItem>, sqlx::Error>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
{
let friends = sqlx::query!(
"
SELECT f.user_id, f.friend_id, f.created, f.accepted
FROM friends f
WHERE f.user_id = $1 OR f.friend_id = $1
",
user_id.0,
)
.fetch_all(exec)
.await?
.into_iter()
.map(|row| FriendItem {
user_id: UserId(row.user_id),
friend_id: UserId(row.friend_id),
created: row.created,
accepted: row.accepted,
})
.filter(|x| accepted.map(|y| y == x.accepted).unwrap_or(true))
.collect::<Vec<_>>();
Ok(friends)
}
pub async fn remove(
user_id: UserId,
friend_id: UserId,
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<(), sqlx::Error> {
sqlx::query!(
"
DELETE FROM friends
WHERE (user_id = $1 AND friend_id = $2) OR (user_id = $2 AND friend_id = $1)
",
user_id.0 as i64,
friend_id.0 as i64,
)
.execute(&mut **transaction)
.await?;
Ok(())
}
}

View File

@@ -4,6 +4,7 @@ pub mod categories;
pub mod charge_item;
pub mod collection_item;
pub mod flow_item;
pub mod friend_item;
pub mod ids;
pub mod image_item;
pub mod legacy_loader_fields;

View File

@@ -44,6 +44,8 @@ pub struct User {
pub created: DateTime<Utc>,
pub role: String,
pub badges: Badges,
pub allow_friend_requests: bool,
}
impl User {
@@ -58,13 +60,13 @@ impl User {
avatar_url, raw_avatar_url, bio, created,
github_id, discord_id, gitlab_id, google_id, steam_id, microsoft_id,
email_verified, password, paypal_id, paypal_country, paypal_email,
venmo_handle, stripe_customer_id
venmo_handle, stripe_customer_id, allow_friend_requests
)
VALUES (
$1, $2, $3, $4, $5,
$6, $7,
$8, $9, $10, $11, $12, $13,
$14, $15, $16, $17, $18, $19, $20
$14, $15, $16, $17, $18, $19, $20, $21
)
",
self.id as UserId,
@@ -86,7 +88,8 @@ impl User {
self.paypal_country,
self.paypal_email,
self.venmo_handle,
self.stripe_customer_id
self.stripe_customer_id,
self.allow_friend_requests,
)
.execute(&mut **transaction)
.await?;
@@ -172,7 +175,7 @@ impl User {
created, role, badges,
github_id, discord_id, gitlab_id, google_id, steam_id, microsoft_id,
email_verified, password, totp_secret, paypal_id, paypal_country, paypal_email,
venmo_handle, stripe_customer_id
venmo_handle, stripe_customer_id, allow_friend_requests
FROM users
WHERE id = ANY($1) OR LOWER(username) = ANY($2)
",
@@ -205,6 +208,7 @@ impl User {
venmo_handle: u.venmo_handle,
stripe_customer_id: u.stripe_customer_id,
totp_secret: u.totp_secret,
allow_friend_requests: u.allow_friend_requests,
};
acc.insert(u.id, (Some(u.username), user));
@@ -643,6 +647,16 @@ impl User {
.execute(&mut **transaction)
.await?;
sqlx::query!(
"
DELETE FROM friends
WHERE user_id = $1 OR friend_id = $1
",
id as UserId,
)
.execute(&mut **transaction)
.await?;
sqlx::query!(
"
DELETE FROM user_backup_codes

View File

@@ -547,6 +547,23 @@ impl RedisConnection {
Ok(res)
}
pub async fn get_many(
&mut self,
namespace: &str,
ids: &[String],
) -> Result<Vec<Option<String>>, DatabaseError> {
let mut cmd = cmd("MGET");
redis_args(
&mut cmd,
ids.iter()
.map(|x| format!("{}_{}:{}", self.meta_namespace, namespace, x))
.collect::<Vec<_>>()
.as_slice(),
);
let res = redis_execute(&mut cmd, &mut self.connection).await?;
Ok(res)
}
pub async fn get_deserialized_from_json<R>(
&mut self,
namespace: &str,
@@ -561,6 +578,22 @@ impl RedisConnection {
.and_then(|x| serde_json::from_str(&x).ok()))
}
pub async fn get_many_deserialized_from_json<R>(
&mut self,
namespace: &str,
ids: &[String],
) -> Result<Vec<Option<R>>, DatabaseError>
where
R: for<'a> serde::Deserialize<'a>,
{
Ok(self
.get_many(namespace, ids)
.await?
.into_iter()
.map(|x| x.and_then(|val| serde_json::from_str::<R>(&val).ok()))
.collect::<Vec<_>>())
}
pub async fn delete<T1>(
&mut self,
namespace: &str,