You've already forked AstralRinth
* Hopefully fix collection visibility once and for all Follow up to #3408 and #3864 * Use same unlisted approach for collections as is used for projects
796 lines
22 KiB
Rust
796 lines
22 KiB
Rust
use std::{collections::HashMap, sync::Arc};
|
|
|
|
use super::{ApiError, oauth_clients::get_user_clients};
|
|
use crate::{
|
|
auth::{
|
|
filter_visible_collections, filter_visible_projects,
|
|
get_user_from_headers,
|
|
},
|
|
database::{models::DBUser, redis::RedisPool},
|
|
file_hosting::{FileHost, FileHostPublicity},
|
|
models::{
|
|
notifications::Notification,
|
|
pats::Scopes,
|
|
projects::Project,
|
|
users::{Badges, Role},
|
|
},
|
|
queue::session::AuthQueue,
|
|
util::{
|
|
img::delete_old_images, routes::read_limited_from_payload,
|
|
validate::validation_errors_to_string,
|
|
},
|
|
};
|
|
use actix_web::{HttpRequest, HttpResponse, web};
|
|
use ariadne::ids::UserId;
|
|
use serde::{Deserialize, Serialize};
|
|
use sqlx::PgPool;
|
|
use validator::Validate;
|
|
|
|
pub fn config(cfg: &mut web::ServiceConfig) {
|
|
cfg.route("user", web::get().to(user_auth_get));
|
|
cfg.route("users", web::get().to(users_get));
|
|
cfg.route("user_email", web::get().to(admin_user_email));
|
|
|
|
cfg.service(
|
|
web::scope("user")
|
|
.route("{user_id}/projects", web::get().to(projects_list))
|
|
.route("{id}", web::get().to(user_get))
|
|
.route("{user_id}/collections", web::get().to(collections_list))
|
|
.route("{user_id}/organizations", web::get().to(orgs_list))
|
|
.route("{id}", web::patch().to(user_edit))
|
|
.route("{id}/icon", web::patch().to(user_icon_edit))
|
|
.route("{id}/icon", web::delete().to(user_icon_delete))
|
|
.route("{id}", web::delete().to(user_delete))
|
|
.route("{id}/follows", web::get().to(user_follows))
|
|
.route("{id}/notifications", web::get().to(user_notifications))
|
|
.route("{id}/oauth_apps", web::get().to(get_user_clients)),
|
|
);
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
pub struct UserEmailQuery {
|
|
pub email: String,
|
|
}
|
|
|
|
pub async fn admin_user_email(
|
|
req: HttpRequest,
|
|
pool: web::Data<PgPool>,
|
|
redis: web::Data<RedisPool>,
|
|
session_queue: web::Data<AuthQueue>,
|
|
email: web::Query<UserEmailQuery>,
|
|
) -> Result<HttpResponse, ApiError> {
|
|
let user = get_user_from_headers(
|
|
&req,
|
|
&**pool,
|
|
&redis,
|
|
&session_queue,
|
|
Scopes::SESSION_ACCESS,
|
|
)
|
|
.await
|
|
.map(|x| x.1)?;
|
|
|
|
if !user.role.is_admin() {
|
|
return Err(ApiError::CustomAuthentication(
|
|
"You do not have permission to get a user from their email!"
|
|
.to_string(),
|
|
));
|
|
}
|
|
|
|
let user_id = sqlx::query!(
|
|
"
|
|
SELECT id FROM users
|
|
WHERE LOWER(email) = LOWER($1)
|
|
",
|
|
email.email
|
|
)
|
|
.fetch_optional(&**pool)
|
|
.await?
|
|
.map(|x| x.id)
|
|
.ok_or_else(|| {
|
|
ApiError::InvalidInput(
|
|
"The email provided is not associated with a user!".to_string(),
|
|
)
|
|
})?;
|
|
|
|
let user = DBUser::get_id(
|
|
crate::database::models::DBUserId(user_id),
|
|
&**pool,
|
|
&redis,
|
|
)
|
|
.await?;
|
|
|
|
if let Some(user) = user {
|
|
Ok(HttpResponse::Ok().json(user))
|
|
} else {
|
|
Err(ApiError::NotFound)
|
|
}
|
|
}
|
|
|
|
pub async fn projects_list(
|
|
req: HttpRequest,
|
|
info: web::Path<(String,)>,
|
|
pool: web::Data<PgPool>,
|
|
redis: web::Data<RedisPool>,
|
|
session_queue: web::Data<AuthQueue>,
|
|
) -> Result<HttpResponse, ApiError> {
|
|
let user = get_user_from_headers(
|
|
&req,
|
|
&**pool,
|
|
&redis,
|
|
&session_queue,
|
|
Scopes::PROJECT_READ,
|
|
)
|
|
.await
|
|
.map(|x| x.1)
|
|
.ok();
|
|
|
|
let id_option = DBUser::get(&info.into_inner().0, &**pool, &redis).await?;
|
|
|
|
if let Some(id) = id_option.map(|x| x.id) {
|
|
let project_data = DBUser::get_projects(id, &**pool, &redis).await?;
|
|
|
|
let projects: Vec<_> = crate::database::DBProject::get_many_ids(
|
|
&project_data,
|
|
&**pool,
|
|
&redis,
|
|
)
|
|
.await?;
|
|
let projects =
|
|
filter_visible_projects(projects, &user, &pool, true).await?;
|
|
Ok(HttpResponse::Ok().json(projects))
|
|
} else {
|
|
Err(ApiError::NotFound)
|
|
}
|
|
}
|
|
|
|
pub async fn user_auth_get(
|
|
req: HttpRequest,
|
|
pool: web::Data<PgPool>,
|
|
redis: web::Data<RedisPool>,
|
|
session_queue: web::Data<AuthQueue>,
|
|
) -> Result<HttpResponse, ApiError> {
|
|
let (scopes, mut user) = get_user_from_headers(
|
|
&req,
|
|
&**pool,
|
|
&redis,
|
|
&session_queue,
|
|
Scopes::USER_READ,
|
|
)
|
|
.await?;
|
|
|
|
if !scopes.contains(Scopes::USER_READ_EMAIL) {
|
|
user.email = None;
|
|
}
|
|
|
|
if !scopes.contains(Scopes::PAYOUTS_READ) {
|
|
user.payout_data = None;
|
|
}
|
|
|
|
Ok(HttpResponse::Ok().json(user))
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
pub struct UserIds {
|
|
pub ids: String,
|
|
}
|
|
|
|
pub async fn users_get(
|
|
web::Query(ids): web::Query<UserIds>,
|
|
pool: web::Data<PgPool>,
|
|
redis: web::Data<RedisPool>,
|
|
) -> Result<HttpResponse, ApiError> {
|
|
let user_ids = serde_json::from_str::<Vec<String>>(&ids.ids)?;
|
|
|
|
let users_data = DBUser::get_many(&user_ids, &**pool, &redis).await?;
|
|
|
|
let users: Vec<crate::models::users::User> =
|
|
users_data.into_iter().map(From::from).collect();
|
|
|
|
Ok(HttpResponse::Ok().json(users))
|
|
}
|
|
|
|
pub async fn user_get(
|
|
req: HttpRequest,
|
|
info: web::Path<(String,)>,
|
|
pool: web::Data<PgPool>,
|
|
redis: web::Data<RedisPool>,
|
|
session_queue: web::Data<AuthQueue>,
|
|
) -> Result<HttpResponse, ApiError> {
|
|
let user_data = DBUser::get(&info.into_inner().0, &**pool, &redis).await?;
|
|
|
|
if let Some(data) = user_data {
|
|
let auth_user = get_user_from_headers(
|
|
&req,
|
|
&**pool,
|
|
&redis,
|
|
&session_queue,
|
|
Scopes::SESSION_ACCESS,
|
|
)
|
|
.await
|
|
.map(|x| x.1)
|
|
.ok();
|
|
|
|
let response: crate::models::users::User =
|
|
if auth_user.is_some_and(|x| x.role.is_admin()) {
|
|
crate::models::users::User::from_full(data)
|
|
} else {
|
|
data.into()
|
|
};
|
|
|
|
Ok(HttpResponse::Ok().json(response))
|
|
} else {
|
|
Err(ApiError::NotFound)
|
|
}
|
|
}
|
|
|
|
pub async fn collections_list(
|
|
req: HttpRequest,
|
|
info: web::Path<(String,)>,
|
|
pool: web::Data<PgPool>,
|
|
redis: web::Data<RedisPool>,
|
|
session_queue: web::Data<AuthQueue>,
|
|
) -> Result<HttpResponse, ApiError> {
|
|
let user = get_user_from_headers(
|
|
&req,
|
|
&**pool,
|
|
&redis,
|
|
&session_queue,
|
|
Scopes::COLLECTION_READ,
|
|
)
|
|
.await
|
|
.map(|x| x.1)
|
|
.ok();
|
|
|
|
let id_option = DBUser::get(&info.into_inner().0, &**pool, &redis).await?;
|
|
|
|
if let Some(id) = id_option.map(|x| x.id) {
|
|
let collection_data = DBUser::get_collections(id, &**pool).await?;
|
|
|
|
let response: Vec<_> = crate::database::models::DBCollection::get_many(
|
|
&collection_data,
|
|
&**pool,
|
|
&redis,
|
|
)
|
|
.await?;
|
|
|
|
let collections =
|
|
filter_visible_collections(response, &user, true).await?;
|
|
|
|
Ok(HttpResponse::Ok().json(collections))
|
|
} else {
|
|
Err(ApiError::NotFound)
|
|
}
|
|
}
|
|
|
|
pub async fn orgs_list(
|
|
req: HttpRequest,
|
|
info: web::Path<(String,)>,
|
|
pool: web::Data<PgPool>,
|
|
redis: web::Data<RedisPool>,
|
|
session_queue: web::Data<AuthQueue>,
|
|
) -> Result<HttpResponse, ApiError> {
|
|
let user = get_user_from_headers(
|
|
&req,
|
|
&**pool,
|
|
&redis,
|
|
&session_queue,
|
|
Scopes::PROJECT_READ,
|
|
)
|
|
.await
|
|
.map(|x| x.1)
|
|
.ok();
|
|
|
|
let id_option = DBUser::get(&info.into_inner().0, &**pool, &redis).await?;
|
|
|
|
if let Some(id) = id_option.map(|x| x.id) {
|
|
let org_data = DBUser::get_organizations(id, &**pool).await?;
|
|
|
|
let organizations_data =
|
|
crate::database::models::organization_item::DBOrganization::get_many_ids(
|
|
&org_data, &**pool, &redis,
|
|
)
|
|
.await?;
|
|
|
|
let team_ids = organizations_data
|
|
.iter()
|
|
.map(|x| x.team_id)
|
|
.collect::<Vec<_>>();
|
|
|
|
let teams_data =
|
|
crate::database::models::DBTeamMember::get_from_team_full_many(
|
|
&team_ids, &**pool, &redis,
|
|
)
|
|
.await?;
|
|
let users = DBUser::get_many_ids(
|
|
&teams_data.iter().map(|x| x.user_id).collect::<Vec<_>>(),
|
|
&**pool,
|
|
&redis,
|
|
)
|
|
.await?;
|
|
|
|
let mut organizations = vec![];
|
|
let mut team_groups = HashMap::new();
|
|
for item in teams_data {
|
|
team_groups.entry(item.team_id).or_insert(vec![]).push(item);
|
|
}
|
|
|
|
for data in organizations_data {
|
|
let members_data =
|
|
team_groups.remove(&data.team_id).unwrap_or(vec![]);
|
|
let logged_in = user
|
|
.as_ref()
|
|
.and_then(|user| {
|
|
members_data
|
|
.iter()
|
|
.find(|x| x.user_id == user.id.into() && x.accepted)
|
|
})
|
|
.is_some();
|
|
|
|
let team_members: Vec<_> = members_data
|
|
.into_iter()
|
|
.filter(|x| logged_in || x.accepted || id == x.user_id)
|
|
.filter_map(|data| {
|
|
users.iter().find(|x| x.id == data.user_id).map(|user| {
|
|
crate::models::teams::TeamMember::from(
|
|
data,
|
|
user.clone(),
|
|
!logged_in,
|
|
)
|
|
})
|
|
})
|
|
.collect();
|
|
|
|
let organization = crate::models::organizations::Organization::from(
|
|
data,
|
|
team_members,
|
|
);
|
|
organizations.push(organization);
|
|
}
|
|
|
|
Ok(HttpResponse::Ok().json(organizations))
|
|
} else {
|
|
Err(ApiError::NotFound)
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Validate)]
|
|
pub struct EditUser {
|
|
#[validate(length(min = 1, max = 39), regex(path = *crate::util::validate::RE_USERNAME))]
|
|
pub username: Option<String>,
|
|
#[serde(
|
|
default,
|
|
skip_serializing_if = "Option::is_none",
|
|
with = "::serde_with::rust::double_option"
|
|
)]
|
|
#[validate(length(max = 160))]
|
|
pub bio: Option<Option<String>>,
|
|
pub role: Option<Role>,
|
|
pub badges: Option<Badges>,
|
|
#[validate(length(max = 160))]
|
|
pub venmo_handle: Option<String>,
|
|
pub allow_friend_requests: Option<bool>,
|
|
}
|
|
|
|
pub async fn user_edit(
|
|
req: HttpRequest,
|
|
info: web::Path<(String,)>,
|
|
new_user: web::Json<EditUser>,
|
|
pool: web::Data<PgPool>,
|
|
redis: web::Data<RedisPool>,
|
|
session_queue: web::Data<AuthQueue>,
|
|
) -> Result<HttpResponse, ApiError> {
|
|
let (scopes, user) = get_user_from_headers(
|
|
&req,
|
|
&**pool,
|
|
&redis,
|
|
&session_queue,
|
|
Scopes::USER_WRITE,
|
|
)
|
|
.await?;
|
|
|
|
new_user.validate().map_err(|err| {
|
|
ApiError::Validation(validation_errors_to_string(err, None))
|
|
})?;
|
|
|
|
let id_option = DBUser::get(&info.into_inner().0, &**pool, &redis).await?;
|
|
|
|
if let Some(actual_user) = id_option {
|
|
let id = actual_user.id;
|
|
let user_id: UserId = id.into();
|
|
|
|
if user.id == user_id || user.role.is_mod() {
|
|
let mut transaction = pool.begin().await?;
|
|
|
|
if let Some(username) = &new_user.username {
|
|
let existing_user_id_option =
|
|
DBUser::get(username, &**pool, &redis).await?;
|
|
|
|
if existing_user_id_option
|
|
.map(|x| UserId::from(x.id))
|
|
.is_none_or(|id| id == user.id)
|
|
{
|
|
sqlx::query!(
|
|
"
|
|
UPDATE users
|
|
SET username = $1
|
|
WHERE (id = $2)
|
|
",
|
|
username,
|
|
id as crate::database::models::ids::DBUserId,
|
|
)
|
|
.execute(&mut *transaction)
|
|
.await?;
|
|
} else {
|
|
return Err(ApiError::InvalidInput(format!(
|
|
"Username {username} is taken!"
|
|
)));
|
|
}
|
|
}
|
|
|
|
if let Some(bio) = &new_user.bio {
|
|
sqlx::query!(
|
|
"
|
|
UPDATE users
|
|
SET bio = $1
|
|
WHERE (id = $2)
|
|
",
|
|
bio.as_deref(),
|
|
id as crate::database::models::ids::DBUserId,
|
|
)
|
|
.execute(&mut *transaction)
|
|
.await?;
|
|
}
|
|
|
|
if let Some(role) = &new_user.role {
|
|
if !user.role.is_admin() {
|
|
return Err(ApiError::CustomAuthentication(
|
|
"You do not have the permissions to edit the role of this user!"
|
|
.to_string(),
|
|
));
|
|
}
|
|
|
|
let role = role.to_string();
|
|
|
|
sqlx::query!(
|
|
"
|
|
UPDATE users
|
|
SET role = $1
|
|
WHERE (id = $2)
|
|
",
|
|
role,
|
|
id as crate::database::models::ids::DBUserId,
|
|
)
|
|
.execute(&mut *transaction)
|
|
.await?;
|
|
}
|
|
|
|
if let Some(badges) = &new_user.badges {
|
|
if !user.role.is_admin() {
|
|
return Err(ApiError::CustomAuthentication(
|
|
"You do not have the permissions to edit the badges of this user!"
|
|
.to_string(),
|
|
));
|
|
}
|
|
|
|
sqlx::query!(
|
|
"
|
|
UPDATE users
|
|
SET badges = $1
|
|
WHERE (id = $2)
|
|
",
|
|
badges.bits() as i64,
|
|
id as crate::database::models::ids::DBUserId,
|
|
)
|
|
.execute(&mut *transaction)
|
|
.await?;
|
|
}
|
|
|
|
if let Some(venmo_handle) = &new_user.venmo_handle {
|
|
if !scopes.contains(Scopes::PAYOUTS_WRITE) {
|
|
return Err(ApiError::CustomAuthentication(
|
|
"You do not have the permissions to edit the venmo handle of this user!"
|
|
.to_string(),
|
|
));
|
|
}
|
|
|
|
sqlx::query!(
|
|
"
|
|
UPDATE users
|
|
SET venmo_handle = $1
|
|
WHERE (id = $2)
|
|
",
|
|
venmo_handle,
|
|
id as crate::database::models::ids::DBUserId,
|
|
)
|
|
.execute(&mut *transaction)
|
|
.await?;
|
|
}
|
|
|
|
if let Some(allow_friend_requests) = &user.allow_friend_requests {
|
|
sqlx::query!(
|
|
"
|
|
UPDATE users
|
|
SET allow_friend_requests = $1
|
|
WHERE (id = $2)
|
|
",
|
|
allow_friend_requests,
|
|
id as crate::database::models::ids::DBUserId,
|
|
)
|
|
.execute(&mut *transaction)
|
|
.await?;
|
|
}
|
|
|
|
transaction.commit().await?;
|
|
DBUser::clear_caches(&[(id, Some(actual_user.username))], &redis)
|
|
.await?;
|
|
Ok(HttpResponse::NoContent().body(""))
|
|
} else {
|
|
Err(ApiError::CustomAuthentication(
|
|
"You do not have permission to edit this user!".to_string(),
|
|
))
|
|
}
|
|
} else {
|
|
Err(ApiError::NotFound)
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
pub struct Extension {
|
|
pub ext: String,
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub async fn user_icon_edit(
|
|
web::Query(ext): web::Query<Extension>,
|
|
req: HttpRequest,
|
|
info: web::Path<(String,)>,
|
|
pool: web::Data<PgPool>,
|
|
redis: web::Data<RedisPool>,
|
|
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
|
mut payload: web::Payload,
|
|
session_queue: web::Data<AuthQueue>,
|
|
) -> Result<HttpResponse, ApiError> {
|
|
let user = get_user_from_headers(
|
|
&req,
|
|
&**pool,
|
|
&redis,
|
|
&session_queue,
|
|
Scopes::USER_WRITE,
|
|
)
|
|
.await?
|
|
.1;
|
|
let id_option = DBUser::get(&info.into_inner().0, &**pool, &redis).await?;
|
|
|
|
if let Some(actual_user) = id_option {
|
|
if user.id != actual_user.id.into() && !user.role.is_mod() {
|
|
return Err(ApiError::CustomAuthentication(
|
|
"You don't have permission to edit this user's icon."
|
|
.to_string(),
|
|
));
|
|
}
|
|
|
|
delete_old_images(
|
|
actual_user.avatar_url,
|
|
actual_user.raw_avatar_url,
|
|
FileHostPublicity::Public,
|
|
&***file_host,
|
|
)
|
|
.await?;
|
|
|
|
let bytes = read_limited_from_payload(
|
|
&mut payload,
|
|
262144,
|
|
"Icons must be smaller than 256KiB",
|
|
)
|
|
.await?;
|
|
|
|
let user_id: UserId = actual_user.id.into();
|
|
let upload_result = crate::util::img::upload_image_optimized(
|
|
&format!("data/{user_id}"),
|
|
FileHostPublicity::Public,
|
|
bytes.freeze(),
|
|
&ext.ext,
|
|
Some(96),
|
|
Some(1.0),
|
|
&***file_host,
|
|
)
|
|
.await?;
|
|
|
|
sqlx::query!(
|
|
"
|
|
UPDATE users
|
|
SET avatar_url = $1, raw_avatar_url = $2
|
|
WHERE (id = $3)
|
|
",
|
|
upload_result.url,
|
|
upload_result.raw_url,
|
|
actual_user.id as crate::database::models::ids::DBUserId,
|
|
)
|
|
.execute(&**pool)
|
|
.await?;
|
|
DBUser::clear_caches(&[(actual_user.id, None)], &redis).await?;
|
|
|
|
Ok(HttpResponse::NoContent().body(""))
|
|
} else {
|
|
Err(ApiError::NotFound)
|
|
}
|
|
}
|
|
|
|
pub async fn user_icon_delete(
|
|
req: HttpRequest,
|
|
info: web::Path<(String,)>,
|
|
pool: web::Data<PgPool>,
|
|
redis: web::Data<RedisPool>,
|
|
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
|
session_queue: web::Data<AuthQueue>,
|
|
) -> Result<HttpResponse, ApiError> {
|
|
let user = get_user_from_headers(
|
|
&req,
|
|
&**pool,
|
|
&redis,
|
|
&session_queue,
|
|
Scopes::USER_WRITE,
|
|
)
|
|
.await?
|
|
.1;
|
|
let id_option = DBUser::get(&info.into_inner().0, &**pool, &redis).await?;
|
|
|
|
if let Some(actual_user) = id_option {
|
|
if user.id != actual_user.id.into() && !user.role.is_mod() {
|
|
return Err(ApiError::CustomAuthentication(
|
|
"You don't have permission to edit this user's icon."
|
|
.to_string(),
|
|
));
|
|
}
|
|
|
|
delete_old_images(
|
|
actual_user.avatar_url,
|
|
actual_user.raw_avatar_url,
|
|
FileHostPublicity::Public,
|
|
&***file_host,
|
|
)
|
|
.await?;
|
|
|
|
sqlx::query!(
|
|
"
|
|
UPDATE users
|
|
SET avatar_url = NULL, raw_avatar_url = NULL
|
|
WHERE (id = $1)
|
|
",
|
|
actual_user.id as crate::database::models::ids::DBUserId,
|
|
)
|
|
.execute(&**pool)
|
|
.await?;
|
|
|
|
DBUser::clear_caches(&[(actual_user.id, None)], &redis).await?;
|
|
|
|
Ok(HttpResponse::NoContent().body(""))
|
|
} else {
|
|
Err(ApiError::NotFound)
|
|
}
|
|
}
|
|
|
|
pub async fn user_delete(
|
|
req: HttpRequest,
|
|
info: web::Path<(String,)>,
|
|
pool: web::Data<PgPool>,
|
|
redis: web::Data<RedisPool>,
|
|
session_queue: web::Data<AuthQueue>,
|
|
) -> Result<HttpResponse, ApiError> {
|
|
let user = get_user_from_headers(
|
|
&req,
|
|
&**pool,
|
|
&redis,
|
|
&session_queue,
|
|
Scopes::USER_DELETE,
|
|
)
|
|
.await?
|
|
.1;
|
|
let id_option = DBUser::get(&info.into_inner().0, &**pool, &redis).await?;
|
|
|
|
if let Some(id) = id_option.map(|x| x.id) {
|
|
if !user.role.is_admin() && user.id != id.into() {
|
|
return Err(ApiError::CustomAuthentication(
|
|
"You do not have permission to delete this user!".to_string(),
|
|
));
|
|
}
|
|
|
|
let mut transaction = pool.begin().await?;
|
|
|
|
let result = DBUser::remove(id, &mut transaction, &redis).await?;
|
|
|
|
transaction.commit().await?;
|
|
|
|
if result.is_some() {
|
|
Ok(HttpResponse::NoContent().body(""))
|
|
} else {
|
|
Err(ApiError::NotFound)
|
|
}
|
|
} else {
|
|
Err(ApiError::NotFound)
|
|
}
|
|
}
|
|
|
|
pub async fn user_follows(
|
|
req: HttpRequest,
|
|
info: web::Path<(String,)>,
|
|
pool: web::Data<PgPool>,
|
|
redis: web::Data<RedisPool>,
|
|
session_queue: web::Data<AuthQueue>,
|
|
) -> Result<HttpResponse, ApiError> {
|
|
let user = get_user_from_headers(
|
|
&req,
|
|
&**pool,
|
|
&redis,
|
|
&session_queue,
|
|
Scopes::USER_READ,
|
|
)
|
|
.await?
|
|
.1;
|
|
let id_option = DBUser::get(&info.into_inner().0, &**pool, &redis).await?;
|
|
|
|
if let Some(id) = id_option.map(|x| x.id) {
|
|
if !user.role.is_admin() && user.id != id.into() {
|
|
return Err(ApiError::CustomAuthentication(
|
|
"You do not have permission to see the projects this user follows!".to_string(),
|
|
));
|
|
}
|
|
|
|
let project_ids = DBUser::get_follows(id, &**pool).await?;
|
|
let projects: Vec<_> = crate::database::DBProject::get_many_ids(
|
|
&project_ids,
|
|
&**pool,
|
|
&redis,
|
|
)
|
|
.await?
|
|
.into_iter()
|
|
.map(Project::from)
|
|
.collect();
|
|
|
|
Ok(HttpResponse::Ok().json(projects))
|
|
} else {
|
|
Err(ApiError::NotFound)
|
|
}
|
|
}
|
|
|
|
pub async fn user_notifications(
|
|
req: HttpRequest,
|
|
info: web::Path<(String,)>,
|
|
pool: web::Data<PgPool>,
|
|
redis: web::Data<RedisPool>,
|
|
session_queue: web::Data<AuthQueue>,
|
|
) -> Result<HttpResponse, ApiError> {
|
|
let user = get_user_from_headers(
|
|
&req,
|
|
&**pool,
|
|
&redis,
|
|
&session_queue,
|
|
Scopes::NOTIFICATION_READ,
|
|
)
|
|
.await?
|
|
.1;
|
|
let id_option = DBUser::get(&info.into_inner().0, &**pool, &redis).await?;
|
|
|
|
if let Some(id) = id_option.map(|x| x.id) {
|
|
if !user.role.is_admin() && user.id != id.into() {
|
|
return Err(ApiError::CustomAuthentication(
|
|
"You do not have permission to see the notifications of this user!".to_string(),
|
|
));
|
|
}
|
|
|
|
let mut notifications: Vec<Notification> =
|
|
crate::database::models::notification_item::DBNotification::get_many_user(
|
|
id, &**pool, &redis,
|
|
)
|
|
.await?
|
|
.into_iter()
|
|
.map(Into::into)
|
|
.collect();
|
|
|
|
notifications.sort_by(|a, b| b.created.cmp(&a.created));
|
|
Ok(HttpResponse::Ok().json(notifications))
|
|
} else {
|
|
Err(ApiError::NotFound)
|
|
}
|
|
}
|