mirror of
https://github.com/modrinth/code.git
synced 2026-07-31 13:16:38 +00:00
feat(labrinth): user blocking (#6837)
* feat(labrinth): user blocking still wip, nothing is hooked up * style(labrinth): cargo fmt * refactor(labrinth): move block/unblock routes to public api * feat(labrinth): block friend requests if blocked * feat(labrinth): remove friend/request on block * feat(labrinth): get blocked users endpoint
This commit is contained in:
Generated
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO blocked_users (user_id, blocked_id)\n VALUES ($1, $2)\n ON CONFLICT (user_id, blocked_id) DO NOTHING\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "57895dd22cb3f6d954ed742a730edece682a771d504ef209cbece81c46e07efb"
|
||||
}
|
||||
Generated
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n DELETE FROM blocked_users\n WHERE user_id = $1 AND blocked_id = $2\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "c08a594796f4af4b7cc536c672731756440e8d1165e88dc644f898a9e40abc48"
|
||||
}
|
||||
Generated
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT 1 FROM blocked_users\n WHERE user_id = $1 AND blocked_id = $2\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "?column?",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "c88172a879d9c2e9cb7fa1480e274e650cd29bc04083708a5fc8580c25be7747"
|
||||
}
|
||||
Generated
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT blocked_id FROM blocked_users\n WHERE user_id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "blocked_id",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "d446d0fe439ea6d8ea7df14419955d93660afa7b634cb7b5edb77c277d89e986"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
CREATE TABLE blocked_users (
|
||||
user_id bigint NOT NULL REFERENCES users (id) ON DELETE CASCADE,
|
||||
blocked_id bigint NOT NULL REFERENCES users (id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (user_id, blocked_id)
|
||||
);
|
||||
@@ -0,0 +1,94 @@
|
||||
use crate::database::models::DBUserId;
|
||||
|
||||
pub struct DBBlockedUser {
|
||||
pub user_id: DBUserId,
|
||||
pub blocked_id: DBUserId,
|
||||
}
|
||||
|
||||
impl DBBlockedUser {
|
||||
pub async fn insert<'a, E>(&self, exec: E) -> Result<(), sqlx::Error>
|
||||
where
|
||||
E: crate::database::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
sqlx::query!(
|
||||
"
|
||||
INSERT INTO blocked_users (user_id, blocked_id)
|
||||
VALUES ($1, $2)
|
||||
ON CONFLICT (user_id, blocked_id) DO NOTHING
|
||||
",
|
||||
self.user_id.0,
|
||||
self.blocked_id.0,
|
||||
)
|
||||
.execute(exec)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn is_blocked<'a, E>(
|
||||
user_id: DBUserId,
|
||||
blocked_id: DBUserId,
|
||||
exec: E,
|
||||
) -> Result<bool, sqlx::Error>
|
||||
where
|
||||
E: crate::database::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
let blocked = sqlx::query_scalar!(
|
||||
"
|
||||
SELECT 1 FROM blocked_users
|
||||
WHERE user_id = $1 AND blocked_id = $2
|
||||
",
|
||||
user_id.0,
|
||||
blocked_id.0,
|
||||
)
|
||||
.fetch_optional(exec)
|
||||
.await?;
|
||||
|
||||
Ok(blocked.is_some())
|
||||
}
|
||||
|
||||
pub async fn get_blocked_for_user<'a, E>(
|
||||
user_id: DBUserId,
|
||||
exec: E,
|
||||
) -> Result<Vec<DBUserId>, sqlx::Error>
|
||||
where
|
||||
E: crate::database::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
let blocked = sqlx::query_scalar!(
|
||||
"
|
||||
SELECT blocked_id FROM blocked_users
|
||||
WHERE user_id = $1
|
||||
",
|
||||
user_id.0,
|
||||
)
|
||||
.fetch_all(exec)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(DBUserId)
|
||||
.collect();
|
||||
|
||||
Ok(blocked)
|
||||
}
|
||||
|
||||
pub async fn remove<'a, E>(
|
||||
user_id: DBUserId,
|
||||
blocked_id: DBUserId,
|
||||
exec: E,
|
||||
) -> Result<(), sqlx::Error>
|
||||
where
|
||||
E: crate::database::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
sqlx::query!(
|
||||
"
|
||||
DELETE FROM blocked_users
|
||||
WHERE user_id = $1 AND blocked_id = $2
|
||||
",
|
||||
user_id.0,
|
||||
blocked_id.0,
|
||||
)
|
||||
.execute(exec)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ use thiserror::Error;
|
||||
|
||||
pub mod affiliate_code_item;
|
||||
pub mod analytics_event_item;
|
||||
pub mod blocked_user_item;
|
||||
pub mod categories;
|
||||
pub mod charge_item;
|
||||
pub mod collection_item;
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
use crate::database::PgPool;
|
||||
use crate::database::models::DBUserId;
|
||||
use crate::database::models::blocked_user_item::DBBlockedUser;
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::guards::admin_key_guard;
|
||||
use actix_web::{get, web};
|
||||
use ariadne::ids::base62_impl::parse_base62;
|
||||
use serde::Serialize;
|
||||
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(block_status);
|
||||
}
|
||||
|
||||
#[derive(Serialize, utoipa::ToSchema)]
|
||||
pub struct BlockStatus {
|
||||
pub blocked: bool,
|
||||
}
|
||||
|
||||
/// Check whether one user has blocked another.
|
||||
#[utoipa::path(tag = "blocked_users", responses((status = OK, body = BlockStatus)))]
|
||||
#[get("/block/{user_id}/{target_id}", guard = "admin_key_guard")]
|
||||
pub async fn block_status(
|
||||
info: web::Path<(String, String)>,
|
||||
pool: web::Data<PgPool>,
|
||||
) -> Result<web::Json<BlockStatus>, ApiError> {
|
||||
let (user_id, target_id) = info.into_inner();
|
||||
|
||||
let user_id =
|
||||
DBUserId(parse_base62(&user_id).map_err(|_| {
|
||||
ApiError::InvalidInput("invalid user_id".to_string())
|
||||
})? as i64);
|
||||
let target_id =
|
||||
DBUserId(parse_base62(&target_id).map_err(|_| {
|
||||
ApiError::InvalidInput("invalid target_id".to_string())
|
||||
})? as i64);
|
||||
|
||||
let blocked =
|
||||
DBBlockedUser::is_blocked(user_id, target_id, &**pool).await?;
|
||||
|
||||
Ok(web::Json(BlockStatus { blocked }))
|
||||
}
|
||||
@@ -2,6 +2,7 @@ pub mod admin;
|
||||
pub mod affiliate;
|
||||
pub mod attribution;
|
||||
pub mod billing;
|
||||
pub mod blocked_users;
|
||||
pub mod campaign;
|
||||
pub mod delphi;
|
||||
pub mod external_notifications;
|
||||
@@ -29,6 +30,7 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
web::scope("/_internal")
|
||||
.wrap(default_cors())
|
||||
.configure(admin::config)
|
||||
.configure(blocked_users::config)
|
||||
.configure(session::config)
|
||||
.configure(flows::config)
|
||||
.configure(pats::config)
|
||||
@@ -65,6 +67,7 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
),
|
||||
paths(
|
||||
admin::count_download,
|
||||
blocked_users::block_status,
|
||||
admin::force_reindex,
|
||||
admin::force_reindex_project,
|
||||
session::list,
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
use crate::auth::get_user_from_headers;
|
||||
use crate::database::PgPool;
|
||||
use crate::database::models::DBUser;
|
||||
use crate::database::models::blocked_user_item::DBBlockedUser;
|
||||
use crate::database::models::friend_item::DBFriend;
|
||||
use crate::models::pats::Scopes;
|
||||
use crate::queue::session::AuthQueue;
|
||||
use crate::routes::ApiError;
|
||||
use actix_web::{HttpRequest, delete, get, post, web};
|
||||
use ariadne::ids::UserId;
|
||||
use eyre::eyre;
|
||||
use xredis::RedisPool;
|
||||
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(block_user);
|
||||
cfg.service(unblock_user);
|
||||
cfg.service(get_blocked_users);
|
||||
}
|
||||
|
||||
/// Block a user.
|
||||
#[utoipa::path(tag = "blocked_users", responses((status = NO_CONTENT)))]
|
||||
#[post("/block/{id}")]
|
||||
pub async fn block_user(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<(), ApiError> {
|
||||
let user = get_user_from_headers(
|
||||
&req,
|
||||
&**pool,
|
||||
&redis,
|
||||
&session_queue,
|
||||
Scopes::USER_WRITE,
|
||||
)
|
||||
.await?
|
||||
.1;
|
||||
|
||||
let user_id = info.into_inner().0;
|
||||
let Some(blocked) = DBUser::get(&user_id, &**pool, &redis).await? else {
|
||||
return Err(ApiError::NotFound);
|
||||
};
|
||||
|
||||
if blocked.id == user.id.into() {
|
||||
return Err(ApiError::Request(eyre!("you cannot block yourself")));
|
||||
}
|
||||
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
DBFriend::remove(user.id.into(), blocked.id, &mut transaction).await?;
|
||||
|
||||
DBBlockedUser {
|
||||
user_id: user.id.into(),
|
||||
blocked_id: blocked.id,
|
||||
}
|
||||
.insert(&mut transaction)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Unblock a user.
|
||||
#[utoipa::path(tag = "blocked_users", responses((status = NO_CONTENT)))]
|
||||
#[delete("/block/{id}")]
|
||||
pub async fn unblock_user(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<(), ApiError> {
|
||||
let user = get_user_from_headers(
|
||||
&req,
|
||||
&**pool,
|
||||
&redis,
|
||||
&session_queue,
|
||||
Scopes::USER_WRITE,
|
||||
)
|
||||
.await?
|
||||
.1;
|
||||
|
||||
let user_id = info.into_inner().0;
|
||||
let Some(blocked) = DBUser::get(&user_id, &**pool, &redis).await? else {
|
||||
return Err(ApiError::NotFound);
|
||||
};
|
||||
|
||||
DBBlockedUser::remove(user.id.into(), blocked.id, &**pool).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// List the users blocked by the current user.
|
||||
#[utoipa::path(tag = "blocked_users", responses((status = OK, body = Vec<UserId>)))]
|
||||
#[get("/blocks")]
|
||||
pub async fn get_blocked_users(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<web::Json<Vec<UserId>>, ApiError> {
|
||||
let user = get_user_from_headers(
|
||||
&req,
|
||||
&**pool,
|
||||
&redis,
|
||||
&session_queue,
|
||||
Scopes::USER_READ,
|
||||
)
|
||||
.await?
|
||||
.1;
|
||||
|
||||
let blocked = DBBlockedUser::get_blocked_for_user(user.id.into(), &**pool)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(UserId::from)
|
||||
.collect();
|
||||
|
||||
Ok(web::Json(blocked))
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::auth::get_user_from_headers;
|
||||
use crate::database::PgPool;
|
||||
use crate::database::models::blocked_user_item::DBBlockedUser;
|
||||
use crate::database::models::friend_item::DBFriend;
|
||||
use crate::database::models::{DBUser, DBUserId};
|
||||
use crate::models::pats::Scopes;
|
||||
@@ -49,6 +50,18 @@ pub async fn add_friend(
|
||||
return Err(ApiError::NotFound);
|
||||
};
|
||||
|
||||
if DBBlockedUser::is_blocked(friend.id, user.id.into(), &**pool).await? {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"You've been blocked the other user!".to_string(),
|
||||
));
|
||||
} else if DBBlockedUser::is_blocked(user.id.into(), friend.id, &**pool)
|
||||
.await?
|
||||
{
|
||||
return Err(ApiError::InvalidInput(
|
||||
"You've blocked the other user!".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
if let Some(friend) =
|
||||
|
||||
@@ -6,6 +6,7 @@ use serde_json::json;
|
||||
|
||||
pub mod analytics_event;
|
||||
pub mod analytics_get;
|
||||
pub mod blocked_users;
|
||||
pub mod collections;
|
||||
pub mod content;
|
||||
pub mod friends;
|
||||
@@ -66,6 +67,7 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
.configure(version_file::config)
|
||||
.configure(versions::config)
|
||||
.configure(friends::config)
|
||||
.configure(blocked_users::config)
|
||||
.configure(content::config),
|
||||
);
|
||||
}
|
||||
@@ -227,6 +229,9 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
friends::add_friend,
|
||||
friends::remove_friend,
|
||||
friends::friends,
|
||||
blocked_users::block_user,
|
||||
blocked_users::unblock_user,
|
||||
blocked_users::get_blocked_users,
|
||||
content::resolve_content,
|
||||
),
|
||||
modifiers(&V3PathModifier, &SecurityAddon)
|
||||
|
||||
Reference in New Issue
Block a user