You've already forked AstralRinth
forked from didirus/AstralRinth
60
Cargo.lock
generated
60
Cargo.lock
generated
@@ -1280,6 +1280,22 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "email-encoding"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dbfb21b9878cf7a348dcb8559109aabc0ec40d69924bd706fa5149846c4fef75"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.21.2",
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "email_address"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2153bd83ebc09db15bcbdc3e2194d901804952e3dc96967e1cd3b0c5c32d112"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
version = "0.8.32"
|
version = "0.8.32"
|
||||||
@@ -1874,6 +1890,16 @@ version = "1.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-bidi",
|
||||||
|
"unicode-normalization",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@@ -2093,6 +2119,7 @@ dependencies = [
|
|||||||
"image",
|
"image",
|
||||||
"itertools 0.11.0",
|
"itertools 0.11.0",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
"lettre",
|
||||||
"log",
|
"log",
|
||||||
"meilisearch-sdk",
|
"meilisearch-sdk",
|
||||||
"rand",
|
"rand",
|
||||||
@@ -2144,6 +2171,29 @@ version = "0.5.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
|
checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lettre"
|
||||||
|
version = "0.10.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "76bd09637ae3ec7bd605b8e135e757980b3968430ff2b1a4a94fb7769e50166d"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.21.2",
|
||||||
|
"email-encoding",
|
||||||
|
"email_address",
|
||||||
|
"fastrand",
|
||||||
|
"futures-util",
|
||||||
|
"hostname",
|
||||||
|
"httpdate",
|
||||||
|
"idna 0.3.0",
|
||||||
|
"mime",
|
||||||
|
"native-tls",
|
||||||
|
"nom 7.1.3",
|
||||||
|
"once_cell",
|
||||||
|
"quoted_printable",
|
||||||
|
"socket2",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lexical-core"
|
name = "lexical-core"
|
||||||
version = "0.7.6"
|
version = "0.7.6"
|
||||||
@@ -2978,6 +3028,12 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quoted_printable"
|
||||||
|
version = "0.4.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a3866219251662ec3b26fc217e3e05bf9c4f84325234dfb96bf0bf840889e49"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "r2d2"
|
name = "r2d2"
|
||||||
version = "0.8.10"
|
version = "0.8.10"
|
||||||
@@ -4408,7 +4464,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb"
|
checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"form_urlencoded",
|
"form_urlencoded",
|
||||||
"idna",
|
"idna 0.4.0",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
@@ -4441,7 +4497,7 @@ version = "0.16.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b92f40481c04ff1f4f61f304d61793c7b56ff76ac1469f1beb199b1445b253bd"
|
checksum = "b92f40481c04ff1f4f61f304d61793c7b56ff76ac1469f1beb199b1445b253bd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"idna",
|
"idna 0.4.0",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"phonenumber",
|
"phonenumber",
|
||||||
"regex",
|
"regex",
|
||||||
|
|||||||
@@ -79,4 +79,6 @@ sentry-actix = "0.31.5"
|
|||||||
image = "0.24.6"
|
image = "0.24.6"
|
||||||
color-thief = "0.2.2"
|
color-thief = "0.2.2"
|
||||||
|
|
||||||
woothee = "0.13.0"
|
woothee = "0.13.0"
|
||||||
|
|
||||||
|
lettre = "0.10.4"
|
||||||
44
migrations/20230710034250_flows.sql
Normal file
44
migrations/20230710034250_flows.sql
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
CREATE INDEX sessions_session
|
||||||
|
ON sessions (session);
|
||||||
|
|
||||||
|
CREATE TABLE flows (
|
||||||
|
id bigint NOT NULL PRIMARY KEY,
|
||||||
|
flow varchar(64) NOT NULL UNIQUE,
|
||||||
|
user_id BIGINT NOT NULL REFERENCES users(id),
|
||||||
|
expires timestamptz NOT NULL,
|
||||||
|
flow_type varchar(64) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX flows_flow
|
||||||
|
ON flows (flow);
|
||||||
|
|
||||||
|
DROP TABLE pats;
|
||||||
|
|
||||||
|
CREATE TABLE pats (
|
||||||
|
id BIGINT PRIMARY KEY,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
user_id BIGINT NOT NULL REFERENCES users(id),
|
||||||
|
access_token VARCHAR(64) NOT NULL UNIQUE,
|
||||||
|
scopes BIGINT NOT NULL,
|
||||||
|
created timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
expires timestamptz NOT NULL,
|
||||||
|
last_used timestamptz NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX pats_user_id
|
||||||
|
ON pats (user_id);
|
||||||
|
|
||||||
|
CREATE INDEX pats_access_token
|
||||||
|
ON pats (access_token);
|
||||||
|
|
||||||
|
ALTER TABLE mods DROP COLUMN thread_id;
|
||||||
|
ALTER TABLE reports DROP COLUMN thread_id;
|
||||||
|
|
||||||
|
DELETE FROM threads_members;
|
||||||
|
DELETE FROM threads_messages;
|
||||||
|
DELETE FROM threads;
|
||||||
|
|
||||||
|
ALTER TABLE threads
|
||||||
|
ADD COLUMN report_id bigint references reports ON UPDATE CASCADE NULL;
|
||||||
|
ALTER TABLE threads
|
||||||
|
ADD COLUMN mod_id bigint references mods ON UPDATE CASCADE NULL;
|
||||||
1285
sqlx-data.json
1285
sqlx-data.json
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
pub mod checks;
|
pub mod checks;
|
||||||
pub mod flows;
|
pub mod flows;
|
||||||
pub mod pat;
|
pub mod pats;
|
||||||
pub mod session;
|
pub mod session;
|
||||||
pub mod validate;
|
pub mod validate;
|
||||||
|
|
||||||
|
|||||||
115
src/auth/pat.rs
115
src/auth/pat.rs
@@ -1,115 +0,0 @@
|
|||||||
// use crate::auth::AuthenticationError;
|
|
||||||
// use crate::database;
|
|
||||||
// use crate::database::models::{DatabaseError, UserId};
|
|
||||||
// use crate::models::users::{self, Badges, RecipientType, RecipientWallet};
|
|
||||||
// use censor::Censor;
|
|
||||||
// use chrono::{NaiveDateTime, Utc};
|
|
||||||
// use rand::Rng;
|
|
||||||
// use serde::{Deserialize, Serialize};
|
|
||||||
//
|
|
||||||
// #[derive(Serialize, Deserialize)]
|
|
||||||
// pub struct PersonalAccessToken {
|
|
||||||
// pub id: String,
|
|
||||||
// pub name: Option<String>,
|
|
||||||
// pub access_token: Option<String>,
|
|
||||||
// pub scope: i64,
|
|
||||||
// pub user_id: users::UserId,
|
|
||||||
// pub expires_at: NaiveDateTime,
|
|
||||||
// }
|
|
||||||
// // Find database user from PAT token
|
|
||||||
// // Separate to user_items as it may yet include further behaviour.
|
|
||||||
// pub async fn get_user_from_pat<'a, E>(
|
|
||||||
// access_token: &str,
|
|
||||||
// executor: E,
|
|
||||||
// ) -> Result<Option<database::models::User>, AuthenticationError>
|
|
||||||
// where
|
|
||||||
// E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
|
||||||
// {
|
|
||||||
// let row = sqlx::query!(
|
|
||||||
// "
|
|
||||||
// SELECT pats.expires_at,
|
|
||||||
// u.id, u.name, u.email,
|
|
||||||
// u.avatar_url, u.username, u.bio,
|
|
||||||
// u.created, u.role, u.badges,
|
|
||||||
// u.balance, u.payout_wallet, u.payout_wallet_type, u.payout_address,
|
|
||||||
// github_id, discord_id, gitlab_id, google_id, steam_id, microsoft_id
|
|
||||||
// FROM pats LEFT OUTER JOIN users u ON pats.user_id = u.id
|
|
||||||
// WHERE access_token = $1
|
|
||||||
// ",
|
|
||||||
// access_token
|
|
||||||
// )
|
|
||||||
// .fetch_optional(executor)
|
|
||||||
// .await?;
|
|
||||||
// if let Some(row) = row {
|
|
||||||
// if row.expires_at < Utc::now().naive_utc() {
|
|
||||||
// return Ok(None);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return Ok(Some(database::models::User {
|
|
||||||
// id: UserId(row.id),
|
|
||||||
// name: row.name,
|
|
||||||
// github_id: row.github_id,
|
|
||||||
// discord_id: row.discord_id,
|
|
||||||
// gitlab_id: row.gitlab_id,
|
|
||||||
// google_id: row.google_id,
|
|
||||||
// steam_id: row.steam_id,
|
|
||||||
// microsoft_id: row.microsoft_id,
|
|
||||||
// email: row.email,
|
|
||||||
// avatar_url: row.avatar_url,
|
|
||||||
// username: row.username,
|
|
||||||
// bio: row.bio,
|
|
||||||
// created: row.created,
|
|
||||||
// role: row.role,
|
|
||||||
// badges: Badges::from_bits(row.badges as u64).unwrap_or_default(),
|
|
||||||
// balance: row.balance,
|
|
||||||
// payout_wallet: row.payout_wallet.map(|x| RecipientWallet::from_string(&x)),
|
|
||||||
// payout_wallet_type: row
|
|
||||||
// .payout_wallet_type
|
|
||||||
// .map(|x| RecipientType::from_string(&x)),
|
|
||||||
// payout_address: row.payout_address,
|
|
||||||
// }));
|
|
||||||
// }
|
|
||||||
// Ok(None)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Generate a new 128 char PAT token starting with 'modrinth_pat_'
|
|
||||||
// pub async fn generate_pat(
|
|
||||||
// con: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
|
||||||
// ) -> Result<String, DatabaseError> {
|
|
||||||
// let mut rng = rand::thread_rng();
|
|
||||||
// let mut retry_count = 0;
|
|
||||||
// let censor = Censor::Standard + Censor::Sex;
|
|
||||||
//
|
|
||||||
// // First generate the PAT token as a random 128 char string. This may include uppercase and lowercase and numbers only.
|
|
||||||
// loop {
|
|
||||||
// let mut access_token = String::with_capacity(63);
|
|
||||||
// access_token.push_str("modrinth_pat_");
|
|
||||||
// for _ in 0..51 {
|
|
||||||
// let c = rng.gen_range(0..62);
|
|
||||||
// if c < 10 {
|
|
||||||
// access_token.push(char::from_u32(c + 48).unwrap()); // 0-9
|
|
||||||
// } else if c < 36 {
|
|
||||||
// access_token.push(char::from_u32(c + 55).unwrap()); // A-Z
|
|
||||||
// } else {
|
|
||||||
// access_token.push(char::from_u32(c + 61).unwrap()); // a-z
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// let results = sqlx::query!(
|
|
||||||
// "
|
|
||||||
// SELECT EXISTS(SELECT 1 FROM pats WHERE access_token=$1)
|
|
||||||
// ",
|
|
||||||
// access_token
|
|
||||||
// )
|
|
||||||
// .fetch_one(&mut *con)
|
|
||||||
// .await?;
|
|
||||||
//
|
|
||||||
// if !results.exists.unwrap_or(true) && !censor.check(&access_token) {
|
|
||||||
// break Ok(access_token);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// retry_count += 1;
|
|
||||||
// if retry_count > 15 {
|
|
||||||
// return Err(DatabaseError::RandomId);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
269
src/auth/pats.rs
Normal file
269
src/auth/pats.rs
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
use crate::database;
|
||||||
|
use crate::database::models::generate_pat_id;
|
||||||
|
|
||||||
|
use crate::auth::get_user_from_headers;
|
||||||
|
use crate::routes::ApiError;
|
||||||
|
|
||||||
|
use actix_web::web::{self, Data};
|
||||||
|
use actix_web::{delete, get, patch, post, HttpRequest, HttpResponse};
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use rand::distributions::Alphanumeric;
|
||||||
|
use rand::Rng;
|
||||||
|
use rand_chacha::rand_core::SeedableRng;
|
||||||
|
use rand_chacha::ChaCha20Rng;
|
||||||
|
|
||||||
|
use crate::models::pats::{PersonalAccessToken, Scopes};
|
||||||
|
use crate::queue::session::AuthQueue;
|
||||||
|
use crate::util::validate::validation_errors_to_string;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use sqlx::postgres::PgPool;
|
||||||
|
use validator::Validate;
|
||||||
|
|
||||||
|
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||||
|
cfg.service(get_pats);
|
||||||
|
cfg.service(create_pat);
|
||||||
|
cfg.service(edit_pat);
|
||||||
|
cfg.service(delete_pat);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("pat")]
|
||||||
|
pub async fn get_pats(
|
||||||
|
req: HttpRequest,
|
||||||
|
pool: Data<PgPool>,
|
||||||
|
redis: Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: Data<AuthQueue>,
|
||||||
|
) -> Result<HttpResponse, ApiError> {
|
||||||
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::PAT_READ]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
|
|
||||||
|
let pat_ids = database::models::pat_item::PersonalAccessToken::get_user_pats(
|
||||||
|
user.id.into(),
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let pats =
|
||||||
|
database::models::pat_item::PersonalAccessToken::get_many_ids(&pat_ids, &**pool, &redis)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().json(
|
||||||
|
pats.into_iter()
|
||||||
|
.map(|x| PersonalAccessToken::from(x, false))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Validate)]
|
||||||
|
pub struct NewPersonalAccessToken {
|
||||||
|
pub scopes: Scopes,
|
||||||
|
#[validate(length(min = 3, max = 255))]
|
||||||
|
pub name: String,
|
||||||
|
pub expires: DateTime<Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("pat")]
|
||||||
|
pub async fn create_pat(
|
||||||
|
req: HttpRequest,
|
||||||
|
info: web::Json<NewPersonalAccessToken>,
|
||||||
|
pool: Data<PgPool>,
|
||||||
|
redis: Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: Data<AuthQueue>,
|
||||||
|
) -> Result<HttpResponse, ApiError> {
|
||||||
|
info.0
|
||||||
|
.validate()
|
||||||
|
.map_err(|err| ApiError::InvalidInput(validation_errors_to_string(err, None)))?;
|
||||||
|
|
||||||
|
if info.scopes.restricted() {
|
||||||
|
return Err(ApiError::InvalidInput(
|
||||||
|
"Invalid scopes requested!".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if info.expires < Utc::now() {
|
||||||
|
return Err(ApiError::InvalidInput(
|
||||||
|
"Expire date must be in the future!".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::PAT_CREATE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
|
|
||||||
|
let mut transaction = pool.begin().await?;
|
||||||
|
|
||||||
|
let id = generate_pat_id(&mut transaction).await?;
|
||||||
|
|
||||||
|
let token = ChaCha20Rng::from_entropy()
|
||||||
|
.sample_iter(&Alphanumeric)
|
||||||
|
.take(60)
|
||||||
|
.map(char::from)
|
||||||
|
.collect::<String>();
|
||||||
|
let token = format!("mrp_{}", token);
|
||||||
|
|
||||||
|
let name = info.name.clone();
|
||||||
|
database::models::pat_item::PersonalAccessToken {
|
||||||
|
id,
|
||||||
|
name: name.clone(),
|
||||||
|
access_token: token.clone(),
|
||||||
|
scopes: info.scopes,
|
||||||
|
user_id: user.id.into(),
|
||||||
|
created: Utc::now(),
|
||||||
|
expires: info.expires,
|
||||||
|
last_used: None,
|
||||||
|
}
|
||||||
|
.insert(&mut transaction)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
database::models::pat_item::PersonalAccessToken::clear_cache(
|
||||||
|
vec![(None, None, Some(user.id.into()))],
|
||||||
|
&redis,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
transaction.commit().await?;
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().json(PersonalAccessToken {
|
||||||
|
id: id.into(),
|
||||||
|
name,
|
||||||
|
access_token: Some(token),
|
||||||
|
scopes: info.scopes,
|
||||||
|
user_id: user.id,
|
||||||
|
created: Utc::now(),
|
||||||
|
expires: info.expires,
|
||||||
|
last_used: None,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Validate)]
|
||||||
|
pub struct ModifyPersonalAccessToken {
|
||||||
|
pub scopes: Option<Scopes>,
|
||||||
|
#[validate(length(min = 3, max = 255))]
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub expires: Option<DateTime<Utc>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[patch("pat/{id}")]
|
||||||
|
pub async fn edit_pat(
|
||||||
|
req: HttpRequest,
|
||||||
|
id: web::Path<(String,)>,
|
||||||
|
info: web::Json<ModifyPersonalAccessToken>,
|
||||||
|
pool: Data<PgPool>,
|
||||||
|
redis: Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: Data<AuthQueue>,
|
||||||
|
) -> Result<HttpResponse, ApiError> {
|
||||||
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::PAT_WRITE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
|
|
||||||
|
let id = id.into_inner().0;
|
||||||
|
let pat = database::models::pat_item::PersonalAccessToken::get(&id, &**pool, &redis).await?;
|
||||||
|
|
||||||
|
if let Some(pat) = pat {
|
||||||
|
if pat.user_id == user.id.into() {
|
||||||
|
let mut transaction = pool.begin().await?;
|
||||||
|
|
||||||
|
if let Some(scopes) = &info.scopes {
|
||||||
|
sqlx::query!(
|
||||||
|
"
|
||||||
|
UPDATE pats
|
||||||
|
SET scopes = $1
|
||||||
|
WHERE id = $2
|
||||||
|
",
|
||||||
|
scopes.bits() as i64,
|
||||||
|
pat.id.0
|
||||||
|
)
|
||||||
|
.execute(&mut *transaction)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
if let Some(name) = &info.name {
|
||||||
|
sqlx::query!(
|
||||||
|
"
|
||||||
|
UPDATE pats
|
||||||
|
SET name = $1
|
||||||
|
WHERE id = $2
|
||||||
|
",
|
||||||
|
name,
|
||||||
|
pat.id.0
|
||||||
|
)
|
||||||
|
.execute(&mut *transaction)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
if let Some(expires) = &info.expires {
|
||||||
|
sqlx::query!(
|
||||||
|
"
|
||||||
|
UPDATE pats
|
||||||
|
SET expires = $1
|
||||||
|
WHERE id = $2
|
||||||
|
",
|
||||||
|
expires,
|
||||||
|
pat.id.0
|
||||||
|
)
|
||||||
|
.execute(&mut *transaction)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
database::models::pat_item::PersonalAccessToken::clear_cache(
|
||||||
|
vec![(Some(pat.id), Some(pat.access_token), Some(pat.user_id))],
|
||||||
|
&redis,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
transaction.commit().await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(HttpResponse::NoContent().finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[delete("pat/{id}")]
|
||||||
|
pub async fn delete_pat(
|
||||||
|
req: HttpRequest,
|
||||||
|
id: web::Path<(String,)>,
|
||||||
|
pool: Data<PgPool>,
|
||||||
|
redis: Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: Data<AuthQueue>,
|
||||||
|
) -> Result<HttpResponse, ApiError> {
|
||||||
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::PAT_DELETE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
|
let id = id.into_inner().0;
|
||||||
|
let pat = database::models::pat_item::PersonalAccessToken::get(&id, &**pool, &redis).await?;
|
||||||
|
|
||||||
|
if let Some(pat) = pat {
|
||||||
|
if pat.user_id == user.id.into() {
|
||||||
|
let mut transaction = pool.begin().await?;
|
||||||
|
database::models::pat_item::PersonalAccessToken::remove(pat.id, &mut transaction)
|
||||||
|
.await?;
|
||||||
|
database::models::pat_item::PersonalAccessToken::clear_cache(
|
||||||
|
vec![(Some(pat.id), Some(pat.access_token), Some(pat.user_id))],
|
||||||
|
&redis,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
transaction.commit().await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(HttpResponse::NoContent().finish())
|
||||||
|
}
|
||||||
@@ -2,8 +2,9 @@ use crate::auth::{get_user_from_headers, AuthenticationError};
|
|||||||
use crate::database::models::session_item::Session as DBSession;
|
use crate::database::models::session_item::Session as DBSession;
|
||||||
use crate::database::models::session_item::SessionBuilder;
|
use crate::database::models::session_item::SessionBuilder;
|
||||||
use crate::database::models::UserId;
|
use crate::database::models::UserId;
|
||||||
|
use crate::models::pats::Scopes;
|
||||||
use crate::models::sessions::Session;
|
use crate::models::sessions::Session;
|
||||||
use crate::queue::session::SessionQueue;
|
use crate::queue::session::AuthQueue;
|
||||||
use crate::routes::ApiError;
|
use crate::routes::ApiError;
|
||||||
use crate::util::env::parse_var;
|
use crate::util::env::parse_var;
|
||||||
use actix_web::http::header::AUTHORIZATION;
|
use actix_web::http::header::AUTHORIZATION;
|
||||||
@@ -122,9 +123,17 @@ pub async fn list(
|
|||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
pool: Data<PgPool>,
|
pool: Data<PgPool>,
|
||||||
redis: Data<deadpool_redis::Pool>,
|
redis: Data<deadpool_redis::Pool>,
|
||||||
session_queue: Data<SessionQueue>,
|
session_queue: Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let current_user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let current_user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::SESSION_READ]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
|
|
||||||
let session_ids = DBSession::get_user_sessions(current_user.id.into(), &**pool, &redis).await?;
|
let session_ids = DBSession::get_user_sessions(current_user.id.into(), &**pool, &redis).await?;
|
||||||
let sessions = DBSession::get_many_ids(&session_ids, &**pool, &redis)
|
let sessions = DBSession::get_many_ids(&session_ids, &**pool, &redis)
|
||||||
@@ -143,9 +152,17 @@ pub async fn delete(
|
|||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
pool: Data<PgPool>,
|
pool: Data<PgPool>,
|
||||||
redis: Data<deadpool_redis::Pool>,
|
redis: Data<deadpool_redis::Pool>,
|
||||||
session_queue: Data<SessionQueue>,
|
session_queue: Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let current_user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let current_user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::SESSION_DELETE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
|
|
||||||
let session = DBSession::get(info.into_inner().0, &**pool, &redis).await?;
|
let session = DBSession::get(info.into_inner().0, &**pool, &redis).await?;
|
||||||
|
|
||||||
@@ -174,9 +191,11 @@ pub async fn refresh(
|
|||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
pool: Data<PgPool>,
|
pool: Data<PgPool>,
|
||||||
redis: Data<deadpool_redis::Pool>,
|
redis: Data<deadpool_redis::Pool>,
|
||||||
session_queue: Data<SessionQueue>,
|
session_queue: Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let current_user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let current_user = get_user_from_headers(&req, &**pool, &redis, &session_queue, None)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
let session = req
|
let session = req
|
||||||
.headers()
|
.headers()
|
||||||
.get(AUTHORIZATION)
|
.get(AUTHORIZATION)
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ use crate::auth::flows::AuthProvider;
|
|||||||
use crate::auth::session::get_session_metadata;
|
use crate::auth::session::get_session_metadata;
|
||||||
use crate::auth::AuthenticationError;
|
use crate::auth::AuthenticationError;
|
||||||
use crate::database::models::user_item;
|
use crate::database::models::user_item;
|
||||||
|
use crate::models::pats::Scopes;
|
||||||
use crate::models::users::{Role, User, UserId, UserPayoutData};
|
use crate::models::users::{Role, User, UserId, UserPayoutData};
|
||||||
use crate::queue::session::SessionQueue;
|
use crate::queue::session::AuthQueue;
|
||||||
use actix_web::HttpRequest;
|
use actix_web::HttpRequest;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use reqwest::header::{HeaderValue, AUTHORIZATION};
|
use reqwest::header::{HeaderValue, AUTHORIZATION};
|
||||||
@@ -12,8 +13,9 @@ pub async fn get_user_from_headers<'a, E>(
|
|||||||
req: &HttpRequest,
|
req: &HttpRequest,
|
||||||
executor: E,
|
executor: E,
|
||||||
redis: &deadpool_redis::Pool,
|
redis: &deadpool_redis::Pool,
|
||||||
session_queue: &SessionQueue,
|
session_queue: &AuthQueue,
|
||||||
) -> Result<User, AuthenticationError>
|
required_scopes: Option<&[Scopes]>,
|
||||||
|
) -> Result<(Scopes, User), AuthenticationError>
|
||||||
where
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||||
{
|
{
|
||||||
@@ -21,7 +23,7 @@ where
|
|||||||
let token: Option<&HeaderValue> = headers.get(AUTHORIZATION);
|
let token: Option<&HeaderValue> = headers.get(AUTHORIZATION);
|
||||||
|
|
||||||
// Fetch DB user record and minos user from headers
|
// Fetch DB user record and minos user from headers
|
||||||
let db_user = get_user_record_from_bearer_token(
|
let (scopes, db_user) = get_user_record_from_bearer_token(
|
||||||
req,
|
req,
|
||||||
token
|
token
|
||||||
.ok_or_else(|| AuthenticationError::InvalidAuthMethod)?
|
.ok_or_else(|| AuthenticationError::InvalidAuthMethod)?
|
||||||
@@ -57,7 +59,16 @@ where
|
|||||||
payout_address: db_user.payout_address,
|
payout_address: db_user.payout_address,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
Ok(user)
|
|
||||||
|
if let Some(required_scopes) = required_scopes {
|
||||||
|
for scope in required_scopes {
|
||||||
|
if !scopes.contains(*scope) {
|
||||||
|
return Err(AuthenticationError::InvalidCredentials);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((scopes, user))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_user_record_from_bearer_token<'a, 'b, E>(
|
pub async fn get_user_record_from_bearer_token<'a, 'b, E>(
|
||||||
@@ -65,13 +76,28 @@ pub async fn get_user_record_from_bearer_token<'a, 'b, E>(
|
|||||||
token: &str,
|
token: &str,
|
||||||
executor: E,
|
executor: E,
|
||||||
redis: &deadpool_redis::Pool,
|
redis: &deadpool_redis::Pool,
|
||||||
session_queue: &SessionQueue,
|
session_queue: &AuthQueue,
|
||||||
) -> Result<Option<user_item::User>, AuthenticationError>
|
) -> Result<Option<(Scopes, user_item::User)>, AuthenticationError>
|
||||||
where
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||||
{
|
{
|
||||||
let possible_user = match token.split_once('_') {
|
let possible_user = match token.split_once('_') {
|
||||||
//Some(("modrinth", _)) => get_user_from_pat(token, executor).await?,
|
Some(("mrp", _)) => {
|
||||||
|
let pat =
|
||||||
|
crate::database::models::pat_item::PersonalAccessToken::get(token, executor, redis)
|
||||||
|
.await?
|
||||||
|
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
||||||
|
|
||||||
|
if pat.expires < Utc::now() {
|
||||||
|
return Err(AuthenticationError::InvalidCredentials);
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = user_item::User::get_id(pat.user_id, executor, redis).await?;
|
||||||
|
|
||||||
|
session_queue.add_pat(pat.id).await;
|
||||||
|
|
||||||
|
user.map(|x| (pat.scopes, x))
|
||||||
|
}
|
||||||
Some(("mra", _)) => {
|
Some(("mra", _)) => {
|
||||||
let session =
|
let session =
|
||||||
crate::database::models::session_item::Session::get(token, executor, redis)
|
crate::database::models::session_item::Session::get(token, executor, redis)
|
||||||
@@ -85,23 +111,31 @@ where
|
|||||||
let user = user_item::User::get_id(session.user_id, executor, redis).await?;
|
let user = user_item::User::get_id(session.user_id, executor, redis).await?;
|
||||||
|
|
||||||
let rate_limit_ignore = dotenvy::var("RATE_LIMIT_IGNORE_KEY")?;
|
let rate_limit_ignore = dotenvy::var("RATE_LIMIT_IGNORE_KEY")?;
|
||||||
if !req.headers().get("x-ratelimit-key").and_then(|x| x.to_str().ok()).map(|x| x == rate_limit_ignore).unwrap_or(false) {
|
if !req
|
||||||
|
.headers()
|
||||||
|
.get("x-ratelimit-key")
|
||||||
|
.and_then(|x| x.to_str().ok())
|
||||||
|
.map(|x| x == rate_limit_ignore)
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
let metadata = get_session_metadata(req).await?;
|
let metadata = get_session_metadata(req).await?;
|
||||||
session_queue.add(session.id, metadata).await;
|
session_queue.add_session(session.id, metadata).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
user
|
user.map(|x| (Scopes::ALL, x))
|
||||||
}
|
}
|
||||||
Some(("github", _)) | Some(("gho", _)) | Some(("ghp", _)) => {
|
Some(("github", _)) | Some(("gho", _)) | Some(("ghp", _)) => {
|
||||||
let user = AuthProvider::GitHub.get_user(token).await?;
|
let user = AuthProvider::GitHub.get_user(token).await?;
|
||||||
let id = AuthProvider::GitHub.get_user_id(&user.id, executor).await?;
|
let id = AuthProvider::GitHub.get_user_id(&user.id, executor).await?;
|
||||||
|
|
||||||
user_item::User::get_id(
|
let user = user_item::User::get_id(
|
||||||
id.ok_or_else(|| AuthenticationError::InvalidCredentials)?,
|
id.ok_or_else(|| AuthenticationError::InvalidCredentials)?,
|
||||||
executor,
|
executor,
|
||||||
redis,
|
redis,
|
||||||
)
|
)
|
||||||
.await?
|
.await?;
|
||||||
|
|
||||||
|
user.map(|x| (Scopes::ALL, x))
|
||||||
}
|
}
|
||||||
_ => return Err(AuthenticationError::InvalidAuthMethod),
|
_ => return Err(AuthenticationError::InvalidAuthMethod),
|
||||||
};
|
};
|
||||||
@@ -112,12 +146,14 @@ pub async fn check_is_moderator_from_headers<'a, 'b, E>(
|
|||||||
req: &HttpRequest,
|
req: &HttpRequest,
|
||||||
executor: E,
|
executor: E,
|
||||||
redis: &deadpool_redis::Pool,
|
redis: &deadpool_redis::Pool,
|
||||||
session_queue: &SessionQueue,
|
session_queue: &AuthQueue,
|
||||||
) -> Result<User, AuthenticationError>
|
) -> Result<User, AuthenticationError>
|
||||||
where
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||||
{
|
{
|
||||||
let user = get_user_from_headers(req, executor, redis, session_queue).await?;
|
let user = get_user_from_headers(req, executor, redis, session_queue, None)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
|
|
||||||
if user.role.is_mod() {
|
if user.role.is_mod() {
|
||||||
Ok(user)
|
Ok(user)
|
||||||
|
|||||||
@@ -83,13 +83,13 @@ generate_ids!(
|
|||||||
"SELECT EXISTS(SELECT 1 FROM states WHERE id=$1)",
|
"SELECT EXISTS(SELECT 1 FROM states WHERE id=$1)",
|
||||||
StateId
|
StateId
|
||||||
);
|
);
|
||||||
// generate_ids!(
|
generate_ids!(
|
||||||
// pub generate_pat_id,
|
pub generate_pat_id,
|
||||||
// PatId,
|
PatId,
|
||||||
// 8,
|
8,
|
||||||
// "SELECT EXISTS(SELECT 1 FROM pats WHERE id=$1)",
|
"SELECT EXISTS(SELECT 1 FROM pats WHERE id=$1)",
|
||||||
// PatId
|
PatId
|
||||||
// );
|
);
|
||||||
|
|
||||||
generate_ids!(
|
generate_ids!(
|
||||||
pub generate_user_id,
|
pub generate_user_id,
|
||||||
@@ -193,7 +193,7 @@ pub struct FileId(pub i64);
|
|||||||
#[sqlx(transparent)]
|
#[sqlx(transparent)]
|
||||||
pub struct StateId(pub i64);
|
pub struct StateId(pub i64);
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Type)]
|
#[derive(Copy, Clone, Debug, Type, Deserialize, Serialize)]
|
||||||
#[sqlx(transparent)]
|
#[sqlx(transparent)]
|
||||||
pub struct PatId(pub i64);
|
pub struct PatId(pub i64);
|
||||||
|
|
||||||
@@ -302,3 +302,8 @@ impl From<SessionId> for ids::SessionId {
|
|||||||
ids::SessionId(id.0 as u64)
|
ids::SessionId(id.0 as u64)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl From<PatId> for ids::PatId {
|
||||||
|
fn from(id: PatId) -> Self {
|
||||||
|
ids::PatId(id.0 as u64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use thiserror::Error;
|
|||||||
pub mod categories;
|
pub mod categories;
|
||||||
pub mod ids;
|
pub mod ids;
|
||||||
pub mod notification_item;
|
pub mod notification_item;
|
||||||
|
pub mod pat_item;
|
||||||
pub mod project_item;
|
pub mod project_item;
|
||||||
pub mod report_item;
|
pub mod report_item;
|
||||||
pub mod session_item;
|
pub mod session_item;
|
||||||
|
|||||||
289
src/database/models/pat_item.rs
Normal file
289
src/database/models/pat_item.rs
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
use super::ids::*;
|
||||||
|
use crate::database::models::DatabaseError;
|
||||||
|
use crate::models::ids::base62_impl::{parse_base62, to_base62};
|
||||||
|
use crate::models::pats::Scopes;
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use redis::cmd;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
const PATS_NAMESPACE: &str = "pats";
|
||||||
|
const PATS_TOKENS_NAMESPACE: &str = "pats_tokens";
|
||||||
|
const PATS_USERS_NAMESPACE: &str = "pats_users";
|
||||||
|
const DEFAULT_EXPIRY: i64 = 1800; // 30 minutes
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
|
pub struct PersonalAccessToken {
|
||||||
|
pub id: PatId,
|
||||||
|
pub name: String,
|
||||||
|
pub access_token: String,
|
||||||
|
pub scopes: Scopes,
|
||||||
|
pub user_id: UserId,
|
||||||
|
pub created: DateTime<Utc>,
|
||||||
|
pub expires: DateTime<Utc>,
|
||||||
|
pub last_used: Option<DateTime<Utc>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PersonalAccessToken {
|
||||||
|
pub async fn insert(
|
||||||
|
&self,
|
||||||
|
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
|
) -> Result<(), DatabaseError> {
|
||||||
|
sqlx::query!(
|
||||||
|
"
|
||||||
|
INSERT INTO pats (
|
||||||
|
id, name, access_token, scopes, user_id,
|
||||||
|
expires
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
$1, $2, $3, $4, $5,
|
||||||
|
$6
|
||||||
|
)
|
||||||
|
",
|
||||||
|
self.id as PatId,
|
||||||
|
self.name,
|
||||||
|
self.access_token,
|
||||||
|
self.scopes.bits() as i64,
|
||||||
|
self.user_id as UserId,
|
||||||
|
self.expires
|
||||||
|
)
|
||||||
|
.execute(&mut *transaction)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get<'a, E, T: ToString>(
|
||||||
|
id: T,
|
||||||
|
exec: E,
|
||||||
|
redis: &deadpool_redis::Pool,
|
||||||
|
) -> Result<Option<PersonalAccessToken>, DatabaseError>
|
||||||
|
where
|
||||||
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
|
{
|
||||||
|
Self::get_many(&[id], exec, redis)
|
||||||
|
.await
|
||||||
|
.map(|x| x.into_iter().next())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_many_ids<'a, E>(
|
||||||
|
pat_ids: &[PatId],
|
||||||
|
exec: E,
|
||||||
|
redis: &deadpool_redis::Pool,
|
||||||
|
) -> Result<Vec<PersonalAccessToken>, DatabaseError>
|
||||||
|
where
|
||||||
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
|
{
|
||||||
|
let ids = pat_ids
|
||||||
|
.iter()
|
||||||
|
.map(|x| crate::models::ids::PatId::from(*x))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
PersonalAccessToken::get_many(&ids, exec, redis).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_many<'a, E, T: ToString>(
|
||||||
|
pat_strings: &[T],
|
||||||
|
exec: E,
|
||||||
|
redis: &deadpool_redis::Pool,
|
||||||
|
) -> Result<Vec<PersonalAccessToken>, DatabaseError>
|
||||||
|
where
|
||||||
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
|
{
|
||||||
|
use futures::TryStreamExt;
|
||||||
|
|
||||||
|
if pat_strings.is_empty() {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut redis = redis.get().await?;
|
||||||
|
|
||||||
|
let mut found_pats = Vec::new();
|
||||||
|
let mut remaining_strings = pat_strings
|
||||||
|
.iter()
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let mut pat_ids = pat_strings
|
||||||
|
.iter()
|
||||||
|
.flat_map(|x| parse_base62(&x.to_string()).map(|x| x as i64))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
pat_ids.append(
|
||||||
|
&mut cmd("MGET")
|
||||||
|
.arg(
|
||||||
|
pat_strings
|
||||||
|
.iter()
|
||||||
|
.map(|x| format!("{}:{}", PATS_TOKENS_NAMESPACE, x.to_string()))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
.query_async::<_, Vec<Option<i64>>>(&mut redis)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if !pat_ids.is_empty() {
|
||||||
|
let pats = cmd("MGET")
|
||||||
|
.arg(
|
||||||
|
pat_ids
|
||||||
|
.iter()
|
||||||
|
.map(|x| format!("{}:{}", PATS_NAMESPACE, x))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
.query_async::<_, Vec<Option<String>>>(&mut redis)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
for pat in pats {
|
||||||
|
if let Some(pat) =
|
||||||
|
pat.and_then(|x| serde_json::from_str::<PersonalAccessToken>(&x).ok())
|
||||||
|
{
|
||||||
|
remaining_strings
|
||||||
|
.retain(|x| &to_base62(pat.id.0 as u64) != x && &pat.access_token != x);
|
||||||
|
found_pats.push(pat);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !remaining_strings.is_empty() {
|
||||||
|
let pat_ids_parsed: Vec<i64> = pat_strings
|
||||||
|
.iter()
|
||||||
|
.flat_map(|x| parse_base62(&x.to_string()).ok())
|
||||||
|
.map(|x| x as i64)
|
||||||
|
.collect();
|
||||||
|
let db_pats: Vec<PersonalAccessToken> = sqlx::query!(
|
||||||
|
"
|
||||||
|
SELECT id, name, access_token, scopes, user_id, created, expires, last_used
|
||||||
|
FROM pats
|
||||||
|
WHERE id = ANY($1) OR access_token = ANY($2)
|
||||||
|
ORDER BY created DESC
|
||||||
|
",
|
||||||
|
&pat_ids_parsed,
|
||||||
|
&pat_strings
|
||||||
|
.into_iter()
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
.fetch_many(exec)
|
||||||
|
.try_filter_map(|e| async {
|
||||||
|
Ok(e.right().map(|x| PersonalAccessToken {
|
||||||
|
id: PatId(x.id),
|
||||||
|
name: x.name,
|
||||||
|
access_token: x.access_token,
|
||||||
|
scopes: Scopes::from_bits(x.scopes as u64).unwrap_or(Scopes::NONE),
|
||||||
|
user_id: UserId(x.user_id),
|
||||||
|
created: x.created,
|
||||||
|
expires: x.expires,
|
||||||
|
last_used: x.last_used,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
.try_collect::<Vec<PersonalAccessToken>>()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
for pat in db_pats {
|
||||||
|
cmd("SET")
|
||||||
|
.arg(format!("{}:{}", PATS_NAMESPACE, pat.id.0))
|
||||||
|
.arg(serde_json::to_string(&pat)?)
|
||||||
|
.arg("EX")
|
||||||
|
.arg(DEFAULT_EXPIRY)
|
||||||
|
.query_async::<_, ()>(&mut redis)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
cmd("SET")
|
||||||
|
.arg(format!("{}:{}", PATS_TOKENS_NAMESPACE, pat.access_token))
|
||||||
|
.arg(pat.id.0)
|
||||||
|
.arg("EX")
|
||||||
|
.arg(DEFAULT_EXPIRY)
|
||||||
|
.query_async::<_, ()>(&mut redis)
|
||||||
|
.await?;
|
||||||
|
found_pats.push(pat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(found_pats)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_user_pats<'a, E>(
|
||||||
|
user_id: UserId,
|
||||||
|
exec: E,
|
||||||
|
redis: &deadpool_redis::Pool,
|
||||||
|
) -> Result<Vec<PatId>, DatabaseError>
|
||||||
|
where
|
||||||
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
|
{
|
||||||
|
let mut redis = redis.get().await?;
|
||||||
|
let res = cmd("GET")
|
||||||
|
.arg(format!("{}:{}", PATS_USERS_NAMESPACE, user_id.0))
|
||||||
|
.query_async::<_, Option<Vec<i64>>>(&mut redis)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if let Some(res) = res {
|
||||||
|
return Ok(res.into_iter().map(PatId).collect());
|
||||||
|
}
|
||||||
|
|
||||||
|
use futures::TryStreamExt;
|
||||||
|
let db_pats: Vec<PatId> = sqlx::query!(
|
||||||
|
"
|
||||||
|
SELECT id
|
||||||
|
FROM pats
|
||||||
|
WHERE user_id = $1
|
||||||
|
ORDER BY created DESC
|
||||||
|
",
|
||||||
|
user_id.0,
|
||||||
|
)
|
||||||
|
.fetch_many(exec)
|
||||||
|
.try_filter_map(|e| async { Ok(e.right().map(|x| PatId(x.id))) })
|
||||||
|
.try_collect::<Vec<PatId>>()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
cmd("SET")
|
||||||
|
.arg(format!("{}:{}", PATS_USERS_NAMESPACE, user_id.0))
|
||||||
|
.arg(serde_json::to_string(&db_pats)?)
|
||||||
|
.arg("EX")
|
||||||
|
.arg(DEFAULT_EXPIRY)
|
||||||
|
.query_async::<_, ()>(&mut redis)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(db_pats)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn clear_cache(
|
||||||
|
clear_pats: Vec<(Option<PatId>, Option<String>, Option<UserId>)>,
|
||||||
|
redis: &deadpool_redis::Pool,
|
||||||
|
) -> Result<(), DatabaseError> {
|
||||||
|
let mut redis = redis.get().await?;
|
||||||
|
let mut cmd = cmd("DEL");
|
||||||
|
|
||||||
|
for (id, token, user_id) in clear_pats {
|
||||||
|
if let Some(id) = id {
|
||||||
|
cmd.arg(format!("{}:{}", PATS_NAMESPACE, id.0));
|
||||||
|
}
|
||||||
|
if let Some(token) = token {
|
||||||
|
cmd.arg(format!("{}:{}", PATS_TOKENS_NAMESPACE, token));
|
||||||
|
}
|
||||||
|
if let Some(user_id) = user_id {
|
||||||
|
cmd.arg(format!("{}:{}", PATS_USERS_NAMESPACE, user_id.0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.query_async::<_, ()>(&mut redis).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn remove(
|
||||||
|
id: PatId,
|
||||||
|
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
|
) -> Result<Option<()>, sqlx::error::Error> {
|
||||||
|
sqlx::query!(
|
||||||
|
"
|
||||||
|
DELETE FROM pats WHERE id = $1
|
||||||
|
",
|
||||||
|
id as PatId,
|
||||||
|
)
|
||||||
|
.execute(&mut *transaction)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Some(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -85,6 +85,7 @@ impl GalleryItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct ProjectBuilder {
|
pub struct ProjectBuilder {
|
||||||
pub project_id: ProjectId,
|
pub project_id: ProjectId,
|
||||||
pub project_type_id: ProjectTypeId,
|
pub project_type_id: ProjectTypeId,
|
||||||
@@ -110,7 +111,6 @@ pub struct ProjectBuilder {
|
|||||||
pub donation_urls: Vec<DonationUrl>,
|
pub donation_urls: Vec<DonationUrl>,
|
||||||
pub gallery_items: Vec<GalleryItem>,
|
pub gallery_items: Vec<GalleryItem>,
|
||||||
pub color: Option<u32>,
|
pub color: Option<u32>,
|
||||||
pub thread_id: ThreadId,
|
|
||||||
pub monetization_status: MonetizationStatus,
|
pub monetization_status: MonetizationStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +153,6 @@ impl ProjectBuilder {
|
|||||||
moderation_message_body: None,
|
moderation_message_body: None,
|
||||||
webhook_sent: false,
|
webhook_sent: false,
|
||||||
color: self.color,
|
color: self.color,
|
||||||
thread_id: Some(self.thread_id),
|
|
||||||
monetization_status: self.monetization_status,
|
monetization_status: self.monetization_status,
|
||||||
};
|
};
|
||||||
project_struct.insert(&mut *transaction).await?;
|
project_struct.insert(&mut *transaction).await?;
|
||||||
@@ -231,7 +230,6 @@ pub struct Project {
|
|||||||
pub moderation_message_body: Option<String>,
|
pub moderation_message_body: Option<String>,
|
||||||
pub webhook_sent: bool,
|
pub webhook_sent: bool,
|
||||||
pub color: Option<u32>,
|
pub color: Option<u32>,
|
||||||
pub thread_id: Option<ThreadId>,
|
|
||||||
pub monetization_status: MonetizationStatus,
|
pub monetization_status: MonetizationStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,14 +245,14 @@ impl Project {
|
|||||||
published, downloads, icon_url, issues_url,
|
published, downloads, icon_url, issues_url,
|
||||||
source_url, wiki_url, status, requested_status, discord_url,
|
source_url, wiki_url, status, requested_status, discord_url,
|
||||||
client_side, server_side, license_url, license,
|
client_side, server_side, license_url, license,
|
||||||
slug, project_type, color, thread_id, monetization_status
|
slug, project_type, color, monetization_status
|
||||||
)
|
)
|
||||||
VALUES (
|
VALUES (
|
||||||
$1, $2, $3, $4, $5,
|
$1, $2, $3, $4, $5,
|
||||||
$6, $7, $8, $9,
|
$6, $7, $8, $9,
|
||||||
$10, $11, $12, $13, $14,
|
$10, $11, $12, $13, $14,
|
||||||
$15, $16, $17, $18,
|
$15, $16, $17, $18,
|
||||||
LOWER($19), $20, $21, $22, $23
|
LOWER($19), $20, $21, $22
|
||||||
)
|
)
|
||||||
",
|
",
|
||||||
self.id as ProjectId,
|
self.id as ProjectId,
|
||||||
@@ -278,7 +276,6 @@ impl Project {
|
|||||||
self.slug.as_ref(),
|
self.slug.as_ref(),
|
||||||
self.project_type as ProjectTypeId,
|
self.project_type as ProjectTypeId,
|
||||||
self.color.map(|x| x as i32),
|
self.color.map(|x| x as i32),
|
||||||
self.thread_id.map(|x| x.0),
|
|
||||||
self.monetization_status.as_str(),
|
self.monetization_status.as_str(),
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
@@ -381,6 +378,8 @@ impl Project {
|
|||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
models::Thread::remove_full(project.thread_id, transaction).await?;
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
DELETE FROM mods
|
DELETE FROM mods
|
||||||
@@ -413,10 +412,6 @@ impl Project {
|
|||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if let Some(thread_id) = project.inner.thread_id {
|
|
||||||
models::Thread::remove_full(thread_id, transaction).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Some(()))
|
Ok(Some(()))
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
@@ -551,7 +546,7 @@ impl Project {
|
|||||||
m.issues_url issues_url, m.source_url source_url, m.wiki_url wiki_url, m.discord_url discord_url, m.license_url license_url,
|
m.issues_url issues_url, m.source_url source_url, m.wiki_url wiki_url, m.discord_url discord_url, m.license_url license_url,
|
||||||
m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.moderation_message moderation_message, m.moderation_message_body moderation_message_body,
|
m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.moderation_message moderation_message, m.moderation_message_body moderation_message_body,
|
||||||
cs.name client_side_type, ss.name server_side_type, pt.name project_type_name, m.webhook_sent, m.color,
|
cs.name client_side_type, ss.name server_side_type, pt.name project_type_name, m.webhook_sent, m.color,
|
||||||
m.thread_id thread_id, m.monetization_status monetization_status,
|
t.id thread_id, m.monetization_status monetization_status,
|
||||||
ARRAY_AGG(DISTINCT l.loader) filter (where l.loader is not null) loaders,
|
ARRAY_AGG(DISTINCT l.loader) filter (where l.loader is not null) loaders,
|
||||||
JSONB_AGG(DISTINCT jsonb_build_object('id', gv.version, 'created', gv.created)) filter (where gv.version is not null) game_versions,
|
JSONB_AGG(DISTINCT jsonb_build_object('id', gv.version, 'created', gv.created)) filter (where gv.version is not null) game_versions,
|
||||||
ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is false) categories,
|
ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is false) categories,
|
||||||
@@ -563,6 +558,7 @@ impl Project {
|
|||||||
INNER JOIN project_types pt ON pt.id = m.project_type
|
INNER JOIN project_types pt ON pt.id = m.project_type
|
||||||
INNER JOIN side_types cs ON m.client_side = cs.id
|
INNER JOIN side_types cs ON m.client_side = cs.id
|
||||||
INNER JOIN side_types ss ON m.server_side = ss.id
|
INNER JOIN side_types ss ON m.server_side = ss.id
|
||||||
|
INNER JOIN threads t ON t.mod_id = m.id
|
||||||
LEFT JOIN mods_gallery mg ON mg.mod_id = m.id
|
LEFT JOIN mods_gallery mg ON mg.mod_id = m.id
|
||||||
LEFT JOIN mods_donations md ON md.joining_mod_id = m.id
|
LEFT JOIN mods_donations md ON md.joining_mod_id = m.id
|
||||||
LEFT JOIN donation_platforms dp ON md.joining_platform_id = dp.id
|
LEFT JOIN donation_platforms dp ON md.joining_platform_id = dp.id
|
||||||
@@ -574,7 +570,7 @@ impl Project {
|
|||||||
LEFT JOIN game_versions_versions gvv ON v.id = gvv.joining_version_id
|
LEFT JOIN game_versions_versions gvv ON v.id = gvv.joining_version_id
|
||||||
LEFT JOIN game_versions gv ON gvv.game_version_id = gv.id
|
LEFT JOIN game_versions gv ON gvv.game_version_id = gv.id
|
||||||
WHERE m.id = ANY($1) OR m.slug = ANY($2)
|
WHERE m.id = ANY($1) OR m.slug = ANY($2)
|
||||||
GROUP BY pt.id, cs.id, ss.id, m.id;
|
GROUP BY pt.id, cs.id, ss.id, t.id, m.id;
|
||||||
",
|
",
|
||||||
&project_ids_parsed,
|
&project_ids_parsed,
|
||||||
&remaining_strings.into_iter().map(|x| x.to_string().to_lowercase()).collect::<Vec<_>>(),
|
&remaining_strings.into_iter().map(|x| x.to_string().to_lowercase()).collect::<Vec<_>>(),
|
||||||
@@ -620,7 +616,6 @@ impl Project {
|
|||||||
webhook_sent: m.webhook_sent,
|
webhook_sent: m.webhook_sent,
|
||||||
color: m.color.map(|x| x as u32),
|
color: m.color.map(|x| x as u32),
|
||||||
queued: m.queued,
|
queued: m.queued,
|
||||||
thread_id: m.thread_id.map(ThreadId),
|
|
||||||
monetization_status: MonetizationStatus::from_str(
|
monetization_status: MonetizationStatus::from_str(
|
||||||
&m.monetization_status,
|
&m.monetization_status,
|
||||||
),
|
),
|
||||||
@@ -676,8 +671,9 @@ impl Project {
|
|||||||
game_versions.sort_by(|a, b| a.created.cmp(&b.created));
|
game_versions.sort_by(|a, b| a.created.cmp(&b.created));
|
||||||
|
|
||||||
game_versions.into_iter().map(|x| x.id).collect()
|
game_versions.into_iter().map(|x| x.id).collect()
|
||||||
}
|
},
|
||||||
}}))
|
thread_id: ThreadId(m.thread_id),
|
||||||
|
}}))
|
||||||
})
|
})
|
||||||
.try_collect::<Vec<QueryProject>>()
|
.try_collect::<Vec<QueryProject>>()
|
||||||
.await?;
|
.await?;
|
||||||
@@ -814,4 +810,5 @@ pub struct QueryProject {
|
|||||||
pub server_side: crate::models::projects::SideType,
|
pub server_side: crate::models::projects::SideType,
|
||||||
pub loaders: Vec<String>,
|
pub loaders: Vec<String>,
|
||||||
pub game_versions: Vec<String>,
|
pub game_versions: Vec<String>,
|
||||||
|
pub thread_id: ThreadId,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ pub struct Report {
|
|||||||
pub reporter: UserId,
|
pub reporter: UserId,
|
||||||
pub created: DateTime<Utc>,
|
pub created: DateTime<Utc>,
|
||||||
pub closed: bool,
|
pub closed: bool,
|
||||||
pub thread_id: ThreadId,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct QueryReport {
|
pub struct QueryReport {
|
||||||
@@ -24,7 +23,7 @@ pub struct QueryReport {
|
|||||||
pub reporter: UserId,
|
pub reporter: UserId,
|
||||||
pub created: DateTime<Utc>,
|
pub created: DateTime<Utc>,
|
||||||
pub closed: bool,
|
pub closed: bool,
|
||||||
pub thread_id: Option<ThreadId>,
|
pub thread_id: ThreadId,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Report {
|
impl Report {
|
||||||
@@ -36,11 +35,11 @@ impl Report {
|
|||||||
"
|
"
|
||||||
INSERT INTO reports (
|
INSERT INTO reports (
|
||||||
id, report_type_id, mod_id, version_id, user_id,
|
id, report_type_id, mod_id, version_id, user_id,
|
||||||
body, reporter, thread_id
|
body, reporter
|
||||||
)
|
)
|
||||||
VALUES (
|
VALUES (
|
||||||
$1, $2, $3, $4, $5,
|
$1, $2, $3, $4, $5,
|
||||||
$6, $7, $8
|
$6, $7
|
||||||
)
|
)
|
||||||
",
|
",
|
||||||
self.id as ReportId,
|
self.id as ReportId,
|
||||||
@@ -49,8 +48,7 @@ impl Report {
|
|||||||
self.version_id.map(|x| x.0 as i64),
|
self.version_id.map(|x| x.0 as i64),
|
||||||
self.user_id.map(|x| x.0 as i64),
|
self.user_id.map(|x| x.0 as i64),
|
||||||
self.body,
|
self.body,
|
||||||
self.reporter as UserId,
|
self.reporter as UserId
|
||||||
self.thread_id as ThreadId,
|
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -79,9 +77,10 @@ impl Report {
|
|||||||
let report_ids_parsed: Vec<i64> = report_ids.iter().map(|x| x.0).collect();
|
let report_ids_parsed: Vec<i64> = report_ids.iter().map(|x| x.0).collect();
|
||||||
let reports = sqlx::query!(
|
let reports = sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT r.id, rt.name, r.mod_id, r.version_id, r.user_id, r.body, r.reporter, r.created, r.thread_id, r.closed
|
SELECT r.id, rt.name, r.mod_id, r.version_id, r.user_id, r.body, r.reporter, r.created, t.id thread_id, r.closed
|
||||||
FROM reports r
|
FROM reports r
|
||||||
INNER JOIN report_types rt ON rt.id = r.report_type_id
|
INNER JOIN report_types rt ON rt.id = r.report_type_id
|
||||||
|
INNER JOIN threads t ON t.report_id = r.id
|
||||||
WHERE r.id = ANY($1)
|
WHERE r.id = ANY($1)
|
||||||
ORDER BY r.created DESC
|
ORDER BY r.created DESC
|
||||||
",
|
",
|
||||||
@@ -99,7 +98,7 @@ impl Report {
|
|||||||
reporter: UserId(x.reporter),
|
reporter: UserId(x.reporter),
|
||||||
created: x.created,
|
created: x.created,
|
||||||
closed: x.closed,
|
closed: x.closed,
|
||||||
thread_id: x.thread_id.map(ThreadId),
|
thread_id: ThreadId(x.thread_id)
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
.try_collect::<Vec<QueryReport>>()
|
.try_collect::<Vec<QueryReport>>()
|
||||||
@@ -127,14 +126,18 @@ impl Report {
|
|||||||
|
|
||||||
let thread_id = sqlx::query!(
|
let thread_id = sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT thread_id FROM reports
|
SELECT id FROM threads
|
||||||
WHERE id = $1
|
WHERE report_id = $1
|
||||||
",
|
",
|
||||||
id as ReportId
|
id as ReportId
|
||||||
)
|
)
|
||||||
.fetch_optional(&mut *transaction)
|
.fetch_optional(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
if let Some(thread_id) = thread_id {
|
||||||
|
crate::database::models::Thread::remove_full(ThreadId(thread_id.id), transaction).await?;
|
||||||
|
}
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
DELETE FROM reports WHERE id = $1
|
DELETE FROM reports WHERE id = $1
|
||||||
@@ -144,12 +147,6 @@ impl Report {
|
|||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if let Some(thread_id) = thread_id {
|
|
||||||
if let Some(id) = thread_id.thread_id {
|
|
||||||
crate::database::models::Thread::remove_full(ThreadId(id), transaction).await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Some(()))
|
Ok(Some(()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,14 +107,14 @@ impl Session {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_many_ids<'a, E>(
|
pub async fn get_many_ids<'a, E>(
|
||||||
user_ids: &[SessionId],
|
session_ids: &[SessionId],
|
||||||
exec: E,
|
exec: E,
|
||||||
redis: &deadpool_redis::Pool,
|
redis: &deadpool_redis::Pool,
|
||||||
) -> Result<Vec<Session>, DatabaseError>
|
) -> Result<Vec<Session>, DatabaseError>
|
||||||
where
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
let ids = user_ids
|
let ids = session_ids
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| crate::models::ids::SessionId::from(*x))
|
.map(|x| crate::models::ids::SessionId::from(*x))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|||||||
@@ -7,12 +7,18 @@ use serde::Deserialize;
|
|||||||
pub struct ThreadBuilder {
|
pub struct ThreadBuilder {
|
||||||
pub type_: ThreadType,
|
pub type_: ThreadType,
|
||||||
pub members: Vec<UserId>,
|
pub members: Vec<UserId>,
|
||||||
|
pub project_id: Option<ProjectId>,
|
||||||
|
pub report_id: Option<ReportId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Thread {
|
pub struct Thread {
|
||||||
pub id: ThreadId,
|
pub id: ThreadId,
|
||||||
|
|
||||||
|
pub project_id: Option<ProjectId>,
|
||||||
|
pub report_id: Option<ReportId>,
|
||||||
pub type_: ThreadType,
|
pub type_: ThreadType,
|
||||||
|
|
||||||
pub messages: Vec<ThreadMessage>,
|
pub messages: Vec<ThreadMessage>,
|
||||||
pub members: Vec<UserId>,
|
pub members: Vec<UserId>,
|
||||||
pub show_in_mod_inbox: bool,
|
pub show_in_mod_inbox: bool,
|
||||||
@@ -70,14 +76,16 @@ impl ThreadBuilder {
|
|||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
INSERT INTO threads (
|
INSERT INTO threads (
|
||||||
id, thread_type
|
id, thread_type, mod_id, report_id
|
||||||
)
|
)
|
||||||
VALUES (
|
VALUES (
|
||||||
$1, $2
|
$1, $2, $3, $4
|
||||||
)
|
)
|
||||||
",
|
",
|
||||||
thread_id as ThreadId,
|
thread_id as ThreadId,
|
||||||
self.type_.as_str()
|
self.type_.as_str(),
|
||||||
|
self.project_id.map(|x| x.0),
|
||||||
|
self.report_id.map(|x| x.0),
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -125,7 +133,7 @@ impl Thread {
|
|||||||
let thread_ids_parsed: Vec<i64> = thread_ids.iter().map(|x| x.0).collect();
|
let thread_ids_parsed: Vec<i64> = thread_ids.iter().map(|x| x.0).collect();
|
||||||
let threads = sqlx::query!(
|
let threads = sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT t.id, t.thread_type, t.show_in_mod_inbox,
|
SELECT t.id, t.thread_type, t.mod_id, t.report_id, t.show_in_mod_inbox,
|
||||||
ARRAY_AGG(DISTINCT tm.user_id) filter (where tm.user_id is not null) members,
|
ARRAY_AGG(DISTINCT tm.user_id) filter (where tm.user_id is not null) members,
|
||||||
JSONB_AGG(DISTINCT jsonb_build_object('id', tmsg.id, 'author_id', tmsg.author_id, 'thread_id', tmsg.thread_id, 'body', tmsg.body, 'created', tmsg.created)) filter (where tmsg.id is not null) messages
|
JSONB_AGG(DISTINCT jsonb_build_object('id', tmsg.id, 'author_id', tmsg.author_id, 'thread_id', tmsg.thread_id, 'body', tmsg.body, 'created', tmsg.created)) filter (where tmsg.id is not null) messages
|
||||||
FROM threads t
|
FROM threads t
|
||||||
@@ -140,6 +148,8 @@ impl Thread {
|
|||||||
.try_filter_map(|e| async {
|
.try_filter_map(|e| async {
|
||||||
Ok(e.right().map(|x| Thread {
|
Ok(e.right().map(|x| Thread {
|
||||||
id: ThreadId(x.id),
|
id: ThreadId(x.id),
|
||||||
|
project_id: x.mod_id.map(ProjectId),
|
||||||
|
report_id: x.report_id.map(ReportId),
|
||||||
type_: ThreadType::from_str(&x.thread_type),
|
type_: ThreadType::from_str(&x.thread_type),
|
||||||
messages: {
|
messages: {
|
||||||
let mut messages: Vec<ThreadMessage> = serde_json::from_value(
|
let mut messages: Vec<ThreadMessage> = serde_json::from_value(
|
||||||
|
|||||||
@@ -10,11 +10,10 @@ use std::cmp::Ordering;
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
const VERSIONS_NAMESPACE: &str = "versions";
|
const VERSIONS_NAMESPACE: &str = "versions";
|
||||||
// TODO: Cache version slugs call
|
|
||||||
// const VERSIONS_SLUGS_NAMESPACE: &str = "versions_slugs";
|
|
||||||
const VERSION_FILES_NAMESPACE: &str = "versions_files";
|
const VERSION_FILES_NAMESPACE: &str = "versions_files";
|
||||||
const DEFAULT_EXPIRY: i64 = 1800; // 30 minutes
|
const DEFAULT_EXPIRY: i64 = 1800; // 30 minutes
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct VersionBuilder {
|
pub struct VersionBuilder {
|
||||||
pub version_id: VersionId,
|
pub version_id: VersionId,
|
||||||
pub project_id: ProjectId,
|
pub project_id: ProjectId,
|
||||||
@@ -32,6 +31,7 @@ pub struct VersionBuilder {
|
|||||||
pub requested_status: Option<VersionStatus>,
|
pub requested_status: Option<VersionStatus>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct DependencyBuilder {
|
pub struct DependencyBuilder {
|
||||||
pub project_id: Option<ProjectId>,
|
pub project_id: Option<ProjectId>,
|
||||||
pub version_id: Option<VersionId>,
|
pub version_id: Option<VersionId>,
|
||||||
@@ -79,6 +79,7 @@ impl DependencyBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct VersionFileBuilder {
|
pub struct VersionFileBuilder {
|
||||||
pub url: String,
|
pub url: String,
|
||||||
pub filename: String,
|
pub filename: String,
|
||||||
@@ -130,6 +131,7 @@ impl VersionFileBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct HashBuilder {
|
pub struct HashBuilder {
|
||||||
pub algorithm: String,
|
pub algorithm: String,
|
||||||
pub hash: Vec<u8>,
|
pub hash: Vec<u8>,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::file_hosting::S3Host;
|
use crate::file_hosting::S3Host;
|
||||||
use crate::queue::download::DownloadQueue;
|
use crate::queue::download::DownloadQueue;
|
||||||
use crate::queue::payouts::PayoutsQueue;
|
use crate::queue::payouts::PayoutsQueue;
|
||||||
use crate::queue::session::SessionQueue;
|
use crate::queue::session::AuthQueue;
|
||||||
use crate::ratelimit::errors::ARError;
|
use crate::ratelimit::errors::ARError;
|
||||||
use crate::ratelimit::memory::{MemoryStore, MemoryStoreActor};
|
use crate::ratelimit::memory::{MemoryStore, MemoryStoreActor};
|
||||||
use crate::ratelimit::middleware::RateLimiter;
|
use crate::ratelimit::middleware::RateLimiter;
|
||||||
@@ -288,7 +288,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let session_queue = web::Data::new(SessionQueue::new());
|
let session_queue = web::Data::new(AuthQueue::new());
|
||||||
|
|
||||||
let pool_ref = pool.clone();
|
let pool_ref = pool.clone();
|
||||||
let redis_ref = redis_pool.clone();
|
let redis_ref = redis_pool.clone();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
pub use super::notifications::NotificationId;
|
pub use super::notifications::NotificationId;
|
||||||
|
pub use super::pats::PatId;
|
||||||
pub use super::projects::{ProjectId, VersionId};
|
pub use super::projects::{ProjectId, VersionId};
|
||||||
pub use super::reports::ReportId;
|
pub use super::reports::ReportId;
|
||||||
pub use super::sessions::SessionId;
|
pub use super::sessions::SessionId;
|
||||||
@@ -115,6 +116,7 @@ base62_id_impl!(NotificationId, NotificationId);
|
|||||||
base62_id_impl!(ThreadId, ThreadId);
|
base62_id_impl!(ThreadId, ThreadId);
|
||||||
base62_id_impl!(ThreadMessageId, ThreadMessageId);
|
base62_id_impl!(ThreadMessageId, ThreadMessageId);
|
||||||
base62_id_impl!(SessionId, SessionId);
|
base62_id_impl!(SessionId, SessionId);
|
||||||
|
base62_id_impl!(PatId, PatId);
|
||||||
|
|
||||||
pub mod base62_impl {
|
pub mod base62_impl {
|
||||||
use serde::de::{self, Deserializer, Visitor};
|
use serde::de::{self, Deserializer, Visitor};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ pub mod error;
|
|||||||
pub mod ids;
|
pub mod ids;
|
||||||
pub mod notifications;
|
pub mod notifications;
|
||||||
pub mod pack;
|
pub mod pack;
|
||||||
|
pub mod pats;
|
||||||
pub mod projects;
|
pub mod projects;
|
||||||
pub mod reports;
|
pub mod reports;
|
||||||
pub mod sessions;
|
pub mod sessions;
|
||||||
|
|||||||
137
src/models/pats.rs
Normal file
137
src/models/pats.rs
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
use super::ids::Base62Id;
|
||||||
|
use crate::models::ids::UserId;
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// The ID of a team
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(from = "Base62Id")]
|
||||||
|
#[serde(into = "Base62Id")]
|
||||||
|
pub struct PatId(pub u64);
|
||||||
|
|
||||||
|
bitflags::bitflags! {
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(transparent)]
|
||||||
|
pub struct Scopes: u64 {
|
||||||
|
// read a user's email
|
||||||
|
const USER_READ_EMAIL = 1 << 0;
|
||||||
|
// read a user's data
|
||||||
|
const USER_READ = 1 << 1;
|
||||||
|
// write to a user's profile (edit username, email, avatar, follows, etc)
|
||||||
|
const USER_WRITE = 1 << 2;
|
||||||
|
// delete a user
|
||||||
|
const USER_DELETE = 1 << 3;
|
||||||
|
// modify a user's authentication data
|
||||||
|
const USER_AUTH_WRITE = 1 << 4;
|
||||||
|
|
||||||
|
// read a user's notifications
|
||||||
|
const NOTIFICATION_READ = 1 << 5;
|
||||||
|
// delete or read a notification
|
||||||
|
const NOTIFICATION_WRITE = 1 << 6;
|
||||||
|
|
||||||
|
// read a user's payouts data
|
||||||
|
const PAYOUTS_READ = 1 << 7;
|
||||||
|
// withdraw money from a user's account
|
||||||
|
const PAYOUTS_WRITE = 1<< 8;
|
||||||
|
// access user analytics (payout analytics at the moment)
|
||||||
|
const ANALYTICS = 1 << 9;
|
||||||
|
|
||||||
|
// create a project
|
||||||
|
const PROJECT_CREATE = 1 << 10;
|
||||||
|
// read a user's projects (including private)
|
||||||
|
const PROJECT_READ = 1 << 11;
|
||||||
|
// write to a project's data (metadata, title, team members, etc)
|
||||||
|
const PROJECT_WRITE = 1 << 12;
|
||||||
|
// delete a project
|
||||||
|
const PROJECT_DELETE = 1 << 13;
|
||||||
|
|
||||||
|
// create a version
|
||||||
|
const VERSION_CREATE = 1 << 14;
|
||||||
|
// read a user's versions (including private)
|
||||||
|
const VERSION_READ = 1 << 15;
|
||||||
|
// write to a version's data (metadata, files, etc)
|
||||||
|
const VERSION_WRITE = 1 << 16;
|
||||||
|
// delete a project
|
||||||
|
const VERSION_DELETE = 1 << 17;
|
||||||
|
|
||||||
|
// create a report
|
||||||
|
const REPORT_CREATE = 1 << 18;
|
||||||
|
// read a user's reports
|
||||||
|
const REPORT_READ = 1 << 19;
|
||||||
|
// edit a report
|
||||||
|
const REPORT_WRITE = 1 << 20;
|
||||||
|
// delete a report
|
||||||
|
const REPORT_DELETE = 1 << 21;
|
||||||
|
|
||||||
|
// read a thread
|
||||||
|
const THREAD_READ = 1 << 22;
|
||||||
|
// write to a thread (send a message, delete a message)
|
||||||
|
const THREAD_WRITE = 1 << 23;
|
||||||
|
|
||||||
|
// create a pat
|
||||||
|
const PAT_CREATE = 1 << 24;
|
||||||
|
// read a user's pats
|
||||||
|
const PAT_READ = 1 << 25;
|
||||||
|
// edit a pat
|
||||||
|
const PAT_WRITE = 1 << 26;
|
||||||
|
// delete a pat
|
||||||
|
const PAT_DELETE = 1 << 27;
|
||||||
|
|
||||||
|
// read a user's sessions
|
||||||
|
const SESSION_READ = 1 << 28;
|
||||||
|
// delete a session22
|
||||||
|
const SESSION_DELETE = 1 << 29;
|
||||||
|
|
||||||
|
const ALL = 0b11111111111111111111111111;
|
||||||
|
const NONE = 0b0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Scopes {
|
||||||
|
// these scopes cannot be specified in a personal access token
|
||||||
|
pub fn restricted(&self) -> bool {
|
||||||
|
self.contains(
|
||||||
|
Scopes::PAT_CREATE
|
||||||
|
| Scopes::PAT_READ
|
||||||
|
| Scopes::PAT_WRITE
|
||||||
|
| Scopes::PAT_DELETE
|
||||||
|
| Scopes::SESSION_READ
|
||||||
|
| Scopes::SESSION_DELETE
|
||||||
|
| Scopes::USER_AUTH_WRITE,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct PersonalAccessToken {
|
||||||
|
pub id: PatId,
|
||||||
|
pub name: String,
|
||||||
|
pub access_token: Option<String>,
|
||||||
|
pub scopes: Scopes,
|
||||||
|
pub user_id: UserId,
|
||||||
|
pub created: DateTime<Utc>,
|
||||||
|
pub expires: DateTime<Utc>,
|
||||||
|
pub last_used: Option<DateTime<Utc>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PersonalAccessToken {
|
||||||
|
pub fn from(
|
||||||
|
data: crate::database::models::pat_item::PersonalAccessToken,
|
||||||
|
include_token: bool,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
id: data.id.into(),
|
||||||
|
name: data.name,
|
||||||
|
access_token: if include_token {
|
||||||
|
Some(data.access_token)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
scopes: data.scopes,
|
||||||
|
user_id: data.user_id.into(),
|
||||||
|
created: data.created,
|
||||||
|
expires: data.expires,
|
||||||
|
last_used: data.last_used,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -106,7 +106,7 @@ pub struct Project {
|
|||||||
pub color: Option<u32>,
|
pub color: Option<u32>,
|
||||||
|
|
||||||
/// The thread of the moderation messages of the project
|
/// The thread of the moderation messages of the project
|
||||||
pub thread_id: Option<ThreadId>,
|
pub thread_id: ThreadId,
|
||||||
|
|
||||||
/// The monetization status of this project
|
/// The monetization status of this project
|
||||||
pub monetization_status: MonetizationStatus,
|
pub monetization_status: MonetizationStatus,
|
||||||
@@ -196,7 +196,7 @@ impl From<QueryProject> for Project {
|
|||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
color: m.color,
|
color: m.color,
|
||||||
thread_id: m.thread_id.map(|x| x.into()),
|
thread_id: data.thread_id.into(),
|
||||||
monetization_status: m.monetization_status,
|
monetization_status: m.monetization_status,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ pub struct Report {
|
|||||||
pub body: String,
|
pub body: String,
|
||||||
pub created: DateTime<Utc>,
|
pub created: DateTime<Utc>,
|
||||||
pub closed: bool,
|
pub closed: bool,
|
||||||
pub thread_id: Option<ThreadId>,
|
pub thread_id: ThreadId,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
@@ -67,7 +67,7 @@ impl From<DBReport> for Report {
|
|||||||
body: x.body,
|
body: x.body,
|
||||||
created: x.created,
|
created: x.created,
|
||||||
closed: x.closed,
|
closed: x.closed,
|
||||||
thread_id: x.thread_id.map(|x| x.into()),
|
thread_id: x.thread_id.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ pub struct TeamId(pub u64);
|
|||||||
pub const OWNER_ROLE: &str = "Owner";
|
pub const OWNER_ROLE: &str = "Owner";
|
||||||
pub const DEFAULT_ROLE: &str = "Member";
|
pub const DEFAULT_ROLE: &str = "Member";
|
||||||
|
|
||||||
// TODO: permissions, role names, etc
|
|
||||||
/// A team of users who control a project
|
/// A team of users who control a project
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct Team {
|
pub struct Team {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use crate::models::projects::ProjectStatus;
|
|||||||
use crate::models::users::{User, UserId};
|
use crate::models::users::{User, UserId};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use crate::models::ids::{ProjectId, ReportId};
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(from = "Base62Id")]
|
#[serde(from = "Base62Id")]
|
||||||
@@ -19,6 +20,8 @@ pub struct Thread {
|
|||||||
pub id: ThreadId,
|
pub id: ThreadId,
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub type_: ThreadType,
|
pub type_: ThreadType,
|
||||||
|
pub project_id: Option<ProjectId>,
|
||||||
|
pub report_id: Option<ReportId>,
|
||||||
pub messages: Vec<ThreadMessage>,
|
pub messages: Vec<ThreadMessage>,
|
||||||
pub members: Vec<User>,
|
pub members: Vec<User>,
|
||||||
}
|
}
|
||||||
@@ -90,6 +93,8 @@ impl Thread {
|
|||||||
Thread {
|
Thread {
|
||||||
id: data.id.into(),
|
id: data.id.into(),
|
||||||
type_: thread_type,
|
type_: thread_type,
|
||||||
|
project_id: data.project_id.map(|x| x.into()),
|
||||||
|
report_id: data.report_id.map(|x| x.into()),
|
||||||
messages: data
|
messages: data
|
||||||
.messages
|
.messages
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|||||||
@@ -1,27 +1,41 @@
|
|||||||
use crate::auth::session::SessionMetadata;
|
use crate::auth::session::SessionMetadata;
|
||||||
|
use crate::database::models::pat_item::PersonalAccessToken;
|
||||||
use crate::database::models::session_item::Session;
|
use crate::database::models::session_item::Session;
|
||||||
use crate::database::models::{DatabaseError, SessionId, UserId};
|
use crate::database::models::{DatabaseError, PatId, SessionId, UserId};
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
pub struct SessionQueue {
|
pub struct AuthQueue {
|
||||||
queue: Mutex<Vec<(SessionId, SessionMetadata)>>,
|
session_queue: Mutex<Vec<(SessionId, SessionMetadata)>>,
|
||||||
|
pat_queue: Mutex<Vec<PatId>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Batches session accessing transactions every 30 seconds
|
// Batches session accessing transactions every 30 seconds
|
||||||
impl SessionQueue {
|
impl AuthQueue {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
SessionQueue {
|
AuthQueue {
|
||||||
queue: Mutex::new(Vec::with_capacity(1000)),
|
session_queue: Mutex::new(Vec::with_capacity(1000)),
|
||||||
|
pat_queue: Mutex::new(Vec::with_capacity(1000)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub async fn add(&self, id: SessionId, metadata: SessionMetadata) {
|
pub async fn add_session(&self, id: SessionId, metadata: SessionMetadata) {
|
||||||
self.queue.lock().await.push((id, metadata));
|
self.session_queue.lock().await.push((id, metadata));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn take(&self) -> Vec<(SessionId, SessionMetadata)> {
|
pub async fn add_pat(&self, id: PatId) {
|
||||||
let mut queue = self.queue.lock().await;
|
self.pat_queue.lock().await.push(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn take_sessions(&self) -> Vec<(SessionId, SessionMetadata)> {
|
||||||
|
let mut queue = self.session_queue.lock().await;
|
||||||
|
let len = queue.len();
|
||||||
|
|
||||||
|
std::mem::replace(&mut queue, Vec::with_capacity(len))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn take_pats(&self) -> Vec<PatId> {
|
||||||
|
let mut queue = self.pat_queue.lock().await;
|
||||||
let len = queue.len();
|
let len = queue.len();
|
||||||
|
|
||||||
std::mem::replace(&mut queue, Vec::with_capacity(len))
|
std::mem::replace(&mut queue, Vec::with_capacity(len))
|
||||||
@@ -32,13 +46,14 @@ impl SessionQueue {
|
|||||||
pool: &PgPool,
|
pool: &PgPool,
|
||||||
redis: &deadpool_redis::Pool,
|
redis: &deadpool_redis::Pool,
|
||||||
) -> Result<(), DatabaseError> {
|
) -> Result<(), DatabaseError> {
|
||||||
let queue = self.take().await;
|
let session_queue = self.take_sessions().await;
|
||||||
|
let pat_queue = self.take_pats().await;
|
||||||
|
|
||||||
if !queue.is_empty() {
|
if !session_queue.is_empty() || !pat_queue.is_empty() {
|
||||||
let mut transaction = pool.begin().await?;
|
let mut transaction = pool.begin().await?;
|
||||||
let mut clear_cache_sessions = Vec::new();
|
let mut clear_cache_sessions = Vec::new();
|
||||||
|
|
||||||
for (id, metadata) in queue {
|
for (id, metadata) in session_queue {
|
||||||
clear_cache_sessions.push((Some(id), None, None));
|
clear_cache_sessions.push((Some(id), None, None));
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
@@ -83,6 +98,26 @@ impl SessionQueue {
|
|||||||
|
|
||||||
Session::clear_cache(clear_cache_sessions, redis).await?;
|
Session::clear_cache(clear_cache_sessions, redis).await?;
|
||||||
|
|
||||||
|
let mut clear_cache_pats = Vec::new();
|
||||||
|
|
||||||
|
for id in pat_queue {
|
||||||
|
clear_cache_pats.push((Some(id), None, None));
|
||||||
|
|
||||||
|
sqlx::query!(
|
||||||
|
"
|
||||||
|
UPDATE pats
|
||||||
|
SET last_used = $2
|
||||||
|
WHERE (id = $1)
|
||||||
|
",
|
||||||
|
id as PatId,
|
||||||
|
Utc::now(),
|
||||||
|
)
|
||||||
|
.execute(&mut *transaction)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
PersonalAccessToken::clear_cache(clear_cache_pats, redis).await?;
|
||||||
|
|
||||||
transaction.commit().await?;
|
transaction.commit().await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
use crate::auth::{get_user_from_headers, is_authorized_version};
|
use crate::auth::{get_user_from_headers, is_authorized_version};
|
||||||
use crate::database::models::project_item::QueryProject;
|
use crate::database::models::project_item::QueryProject;
|
||||||
use crate::database::models::version_item::{QueryFile, QueryVersion};
|
use crate::database::models::version_item::{QueryFile, QueryVersion};
|
||||||
|
use crate::models::pats::Scopes;
|
||||||
use crate::models::projects::{ProjectId, VersionId};
|
use crate::models::projects::{ProjectId, VersionId};
|
||||||
use crate::queue::session::SessionQueue;
|
use crate::queue::session::AuthQueue;
|
||||||
use crate::routes::ApiError;
|
use crate::routes::ApiError;
|
||||||
use crate::{auth::is_authorized, database};
|
use crate::{auth::is_authorized, database};
|
||||||
use actix_web::{get, route, web, HttpRequest, HttpResponse};
|
use actix_web::{get, route, web, HttpRequest, HttpResponse};
|
||||||
@@ -68,7 +69,7 @@ pub async fn maven_metadata(
|
|||||||
params: web::Path<(String,)>,
|
params: web::Path<(String,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let project_id = params.into_inner().0;
|
let project_id = params.into_inner().0;
|
||||||
let project_data = database::models::Project::get(&project_id, &**pool, &redis).await?;
|
let project_data = database::models::Project::get(&project_id, &**pool, &redis).await?;
|
||||||
@@ -79,9 +80,16 @@ pub async fn maven_metadata(
|
|||||||
return Ok(HttpResponse::NotFound().body(""));
|
return Ok(HttpResponse::NotFound().body(""));
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
let user_option = get_user_from_headers(
|
||||||
.await
|
&req,
|
||||||
.ok();
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::PROJECT_READ]),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|x| x.1)
|
||||||
|
.ok();
|
||||||
|
|
||||||
if !is_authorized(&data.inner, &user_option, &pool).await? {
|
if !is_authorized(&data.inner, &user_option, &pool).await? {
|
||||||
return Ok(HttpResponse::NotFound().body(""));
|
return Ok(HttpResponse::NotFound().body(""));
|
||||||
@@ -190,7 +198,7 @@ pub async fn version_file(
|
|||||||
params: web::Path<(String, String, String)>,
|
params: web::Path<(String, String, String)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let (project_id, vnum, file) = params.into_inner();
|
let (project_id, vnum, file) = params.into_inner();
|
||||||
let project_data = database::models::Project::get(&project_id, &**pool, &redis).await?;
|
let project_data = database::models::Project::get(&project_id, &**pool, &redis).await?;
|
||||||
@@ -201,9 +209,16 @@ pub async fn version_file(
|
|||||||
return Ok(HttpResponse::NotFound().body(""));
|
return Ok(HttpResponse::NotFound().body(""));
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
let user_option = get_user_from_headers(
|
||||||
.await
|
&req,
|
||||||
.ok();
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::PROJECT_READ]),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|x| x.1)
|
||||||
|
.ok();
|
||||||
|
|
||||||
if !is_authorized(&project.inner, &user_option, &pool).await? {
|
if !is_authorized(&project.inner, &user_option, &pool).await? {
|
||||||
return Ok(HttpResponse::NotFound().body(""));
|
return Ok(HttpResponse::NotFound().body(""));
|
||||||
@@ -274,7 +289,7 @@ pub async fn version_file_sha1(
|
|||||||
params: web::Path<(String, String, String)>,
|
params: web::Path<(String, String, String)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let (project_id, vnum, file) = params.into_inner();
|
let (project_id, vnum, file) = params.into_inner();
|
||||||
let project_data = database::models::Project::get(&project_id, &**pool, &redis).await?;
|
let project_data = database::models::Project::get(&project_id, &**pool, &redis).await?;
|
||||||
@@ -285,9 +300,16 @@ pub async fn version_file_sha1(
|
|||||||
return Ok(HttpResponse::NotFound().body(""));
|
return Ok(HttpResponse::NotFound().body(""));
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
let user_option = get_user_from_headers(
|
||||||
.await
|
&req,
|
||||||
.ok();
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::PROJECT_READ]),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|x| x.1)
|
||||||
|
.ok();
|
||||||
|
|
||||||
if !is_authorized(&project.inner, &user_option, &pool).await? {
|
if !is_authorized(&project.inner, &user_option, &pool).await? {
|
||||||
return Ok(HttpResponse::NotFound().body(""));
|
return Ok(HttpResponse::NotFound().body(""));
|
||||||
@@ -332,7 +354,7 @@ pub async fn version_file_sha512(
|
|||||||
params: web::Path<(String, String, String)>,
|
params: web::Path<(String, String, String)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let (project_id, vnum, file) = params.into_inner();
|
let (project_id, vnum, file) = params.into_inner();
|
||||||
let project_data = database::models::Project::get(&project_id, &**pool, &redis).await?;
|
let project_data = database::models::Project::get(&project_id, &**pool, &redis).await?;
|
||||||
@@ -343,9 +365,16 @@ pub async fn version_file_sha512(
|
|||||||
return Ok(HttpResponse::NotFound().body(""));
|
return Ok(HttpResponse::NotFound().body(""));
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
let user_option = get_user_from_headers(
|
||||||
.await
|
&req,
|
||||||
.ok();
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::PROJECT_READ]),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|x| x.1)
|
||||||
|
.ok();
|
||||||
|
|
||||||
if !is_authorized(&project.inner, &user_option, &pool).await? {
|
if !is_authorized(&project.inner, &user_option, &pool).await? {
|
||||||
return Ok(HttpResponse::NotFound().body(""));
|
return Ok(HttpResponse::NotFound().body(""));
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ use sqlx::PgPool;
|
|||||||
|
|
||||||
use crate::auth::{filter_authorized_versions, get_user_from_headers, is_authorized};
|
use crate::auth::{filter_authorized_versions, get_user_from_headers, is_authorized};
|
||||||
use crate::database;
|
use crate::database;
|
||||||
|
use crate::models::pats::Scopes;
|
||||||
use crate::models::projects::VersionType;
|
use crate::models::projects::VersionType;
|
||||||
use crate::queue::session::SessionQueue;
|
use crate::queue::session::AuthQueue;
|
||||||
|
|
||||||
use super::ApiError;
|
use super::ApiError;
|
||||||
|
|
||||||
@@ -21,7 +22,7 @@ pub async fn forge_updates(
|
|||||||
info: web::Path<(String,)>,
|
info: web::Path<(String,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
const ERROR: &str = "The specified project does not exist!";
|
const ERROR: &str = "The specified project does not exist!";
|
||||||
|
|
||||||
@@ -31,9 +32,16 @@ pub async fn forge_updates(
|
|||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| ApiError::InvalidInput(ERROR.to_string()))?;
|
.ok_or_else(|| ApiError::InvalidInput(ERROR.to_string()))?;
|
||||||
|
|
||||||
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
let user_option = get_user_from_headers(
|
||||||
.await
|
&req,
|
||||||
.ok();
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::PROJECT_READ]),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|x| x.1)
|
||||||
|
.ok();
|
||||||
|
|
||||||
if !is_authorized(&project.inner, &user_option, &pool).await? {
|
if !is_authorized(&project.inner, &user_option, &pool).await? {
|
||||||
return Err(ApiError::InvalidInput(ERROR.to_string()));
|
return Err(ApiError::InvalidInput(ERROR.to_string()));
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
mod admin;
|
mod admin;
|
||||||
mod moderation;
|
mod moderation;
|
||||||
mod notifications;
|
mod notifications;
|
||||||
mod pats;
|
|
||||||
pub(crate) mod project_creation;
|
pub(crate) mod project_creation;
|
||||||
mod projects;
|
mod projects;
|
||||||
mod reports;
|
mod reports;
|
||||||
@@ -22,6 +21,7 @@ pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
|||||||
.configure(admin::config)
|
.configure(admin::config)
|
||||||
.configure(crate::auth::session::config)
|
.configure(crate::auth::session::config)
|
||||||
.configure(crate::auth::flows::config)
|
.configure(crate::auth::flows::config)
|
||||||
|
.configure(crate::auth::pats::config)
|
||||||
.configure(moderation::config)
|
.configure(moderation::config)
|
||||||
.configure(notifications::config)
|
.configure(notifications::config)
|
||||||
//.configure(pats::config)
|
//.configure(pats::config)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use super::ApiError;
|
|||||||
use crate::auth::check_is_moderator_from_headers;
|
use crate::auth::check_is_moderator_from_headers;
|
||||||
use crate::database;
|
use crate::database;
|
||||||
use crate::models::projects::ProjectStatus;
|
use crate::models::projects::ProjectStatus;
|
||||||
use crate::queue::session::SessionQueue;
|
use crate::queue::session::AuthQueue;
|
||||||
use actix_web::{get, web, HttpRequest, HttpResponse};
|
use actix_web::{get, web, HttpRequest, HttpResponse};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
@@ -27,7 +27,7 @@ pub async fn get_projects(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
count: web::Query<ResultCount>,
|
count: web::Query<ResultCount>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
check_is_moderator_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
check_is_moderator_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ use crate::auth::get_user_from_headers;
|
|||||||
use crate::database;
|
use crate::database;
|
||||||
use crate::models::ids::NotificationId;
|
use crate::models::ids::NotificationId;
|
||||||
use crate::models::notifications::Notification;
|
use crate::models::notifications::Notification;
|
||||||
use crate::queue::session::SessionQueue;
|
use crate::models::pats::Scopes;
|
||||||
|
use crate::queue::session::AuthQueue;
|
||||||
use crate::routes::ApiError;
|
use crate::routes::ApiError;
|
||||||
use actix_web::{delete, get, patch, web, HttpRequest, HttpResponse};
|
use actix_web::{delete, get, patch, web, HttpRequest, HttpResponse};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -32,9 +33,17 @@ pub async fn notifications_get(
|
|||||||
web::Query(ids): web::Query<NotificationIds>,
|
web::Query(ids): web::Query<NotificationIds>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::NOTIFICATION_READ]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
|
|
||||||
use database::models::notification_item::Notification as DBNotification;
|
use database::models::notification_item::Notification as DBNotification;
|
||||||
use database::models::NotificationId as DBNotificationId;
|
use database::models::NotificationId as DBNotificationId;
|
||||||
@@ -64,9 +73,17 @@ pub async fn notification_get(
|
|||||||
info: web::Path<(NotificationId,)>,
|
info: web::Path<(NotificationId,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::NOTIFICATION_READ]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
|
|
||||||
let id = info.into_inner().0;
|
let id = info.into_inner().0;
|
||||||
|
|
||||||
@@ -90,9 +107,17 @@ pub async fn notification_read(
|
|||||||
info: web::Path<(NotificationId,)>,
|
info: web::Path<(NotificationId,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::NOTIFICATION_WRITE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
|
|
||||||
let id = info.into_inner().0;
|
let id = info.into_inner().0;
|
||||||
|
|
||||||
@@ -125,9 +150,17 @@ pub async fn notification_delete(
|
|||||||
info: web::Path<(NotificationId,)>,
|
info: web::Path<(NotificationId,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::NOTIFICATION_WRITE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
|
|
||||||
let id = info.into_inner().0;
|
let id = info.into_inner().0;
|
||||||
|
|
||||||
@@ -160,9 +193,17 @@ pub async fn notifications_read(
|
|||||||
web::Query(ids): web::Query<NotificationIds>,
|
web::Query(ids): web::Query<NotificationIds>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::NOTIFICATION_WRITE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
|
|
||||||
let notification_ids = serde_json::from_str::<Vec<NotificationId>>(&ids.ids)?
|
let notification_ids = serde_json::from_str::<Vec<NotificationId>>(&ids.ids)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -197,9 +238,17 @@ pub async fn notifications_delete(
|
|||||||
web::Query(ids): web::Query<NotificationIds>,
|
web::Query(ids): web::Query<NotificationIds>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::NOTIFICATION_WRITE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
|
|
||||||
let notification_ids = serde_json::from_str::<Vec<NotificationId>>(&ids.ids)?
|
let notification_ids = serde_json::from_str::<Vec<NotificationId>>(&ids.ids)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|||||||
@@ -1,249 +0,0 @@
|
|||||||
// /*!
|
|
||||||
// Current edition of Ory kratos does not support PAT access of data, so this module is how we allow for PAT authentication.
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Just as a summary: Don't implement this flow in your application!
|
|
||||||
// */
|
|
||||||
//
|
|
||||||
// use crate::database;
|
|
||||||
// use crate::database::models::generate_pat_id;
|
|
||||||
// use crate::models::ids::base62_impl::{parse_base62, to_base62};
|
|
||||||
//
|
|
||||||
// use crate::auth::get_user_from_headers;
|
|
||||||
// use crate::auth::{generate_pat, PersonalAccessToken};
|
|
||||||
// use crate::models::users::UserId;
|
|
||||||
// use crate::routes::ApiError;
|
|
||||||
//
|
|
||||||
// use actix_web::web::{self, Data, Query};
|
|
||||||
// use actix_web::{delete, get, patch, post, HttpRequest, HttpResponse};
|
|
||||||
// use chrono::{Duration, Utc};
|
|
||||||
//
|
|
||||||
// use crate::queue::session::SessionQueue;
|
|
||||||
// use serde::Deserialize;
|
|
||||||
// use sqlx::postgres::PgPool;
|
|
||||||
//
|
|
||||||
// pub fn config(cfg: &mut web::ServiceConfig) {
|
|
||||||
// cfg.service(get_pats);
|
|
||||||
// cfg.service(create_pat);
|
|
||||||
// cfg.service(edit_pat);
|
|
||||||
// cfg.service(delete_pat);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// #[derive(Deserialize)]
|
|
||||||
// pub struct CreatePersonalAccessToken {
|
|
||||||
// pub scope: i64, // todo: should be a vec of enum
|
|
||||||
// pub name: Option<String>,
|
|
||||||
// pub expire_in_days: i64, // resets expiry to expire_in_days days from now
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// #[derive(Deserialize)]
|
|
||||||
// pub struct ModifyPersonalAccessToken {
|
|
||||||
// #[serde(default, with = "::serde_with::rust::double_option")]
|
|
||||||
// pub name: Option<Option<String>>,
|
|
||||||
// pub expire_in_days: Option<i64>, // resets expiry to expire_in_days days from now
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // GET /pat
|
|
||||||
// // Get all personal access tokens for the given user. Minos/Kratos cookie must be attached for it to work.
|
|
||||||
// // Does not return the actual access token, only the ID + metadata.
|
|
||||||
// #[get("pat")]
|
|
||||||
// pub async fn get_pats(
|
|
||||||
// req: HttpRequest,
|
|
||||||
// pool: Data<PgPool>,
|
|
||||||
// redis: Data<deadpool_redis::Pool>,
|
|
||||||
// session_queue: web::Data<SessionQueue>,
|
|
||||||
// ) -> Result<HttpResponse, ApiError> {
|
|
||||||
// let user: crate::models::users::User =
|
|
||||||
// get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
|
||||||
// let db_user_id: database::models::UserId = database::models::UserId::from(user.id);
|
|
||||||
//
|
|
||||||
// let pats = sqlx::query!(
|
|
||||||
// "
|
|
||||||
// SELECT id, name, user_id, scope, expires_at
|
|
||||||
// FROM pats
|
|
||||||
// WHERE user_id = $1
|
|
||||||
// ",
|
|
||||||
// db_user_id.0
|
|
||||||
// )
|
|
||||||
// .fetch_all(&**pool)
|
|
||||||
// .await?;
|
|
||||||
//
|
|
||||||
// let pats = pats
|
|
||||||
// .into_iter()
|
|
||||||
// .map(|pat| PersonalAccessToken {
|
|
||||||
// id: to_base62(pat.id as u64),
|
|
||||||
// scope: pat.scope,
|
|
||||||
// name: pat.name,
|
|
||||||
// expires_at: pat.expires_at,
|
|
||||||
// access_token: None,
|
|
||||||
// user_id: UserId(pat.user_id as u64),
|
|
||||||
// })
|
|
||||||
// .collect::<Vec<_>>();
|
|
||||||
//
|
|
||||||
// Ok(HttpResponse::Ok().json(pats))
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // POST /pat
|
|
||||||
// // Create a new personal access token for the given user. Minos/Kratos cookie must be attached for it to work.
|
|
||||||
// // All PAT tokens are base62 encoded, and are prefixed with "modrinth_pat_"
|
|
||||||
// #[post("pat")]
|
|
||||||
// pub async fn create_pat(
|
|
||||||
// req: HttpRequest,
|
|
||||||
// Query(info): Query<CreatePersonalAccessToken>, // callback url
|
|
||||||
// pool: Data<PgPool>,
|
|
||||||
// redis: web::Data<deadpool_redis::Pool>,
|
|
||||||
// session_queue: web::Data<SessionQueue>,
|
|
||||||
// ) -> Result<HttpResponse, ApiError> {
|
|
||||||
// let user: crate::models::users::User =
|
|
||||||
// get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
|
||||||
// let db_user_id: database::models::UserId = database::models::UserId::from(user.id);
|
|
||||||
//
|
|
||||||
// let mut transaction: sqlx::Transaction<sqlx::Postgres> = pool.begin().await?;
|
|
||||||
//
|
|
||||||
// let pat = generate_pat_id(&mut transaction).await?;
|
|
||||||
// let access_token = generate_pat(&mut transaction).await?;
|
|
||||||
// let expiry = Utc::now().naive_utc() + Duration::days(info.expire_in_days);
|
|
||||||
// if info.expire_in_days <= 0 {
|
|
||||||
// return Err(ApiError::InvalidInput(
|
|
||||||
// "'expire_in_days' must be greater than 0".to_string(),
|
|
||||||
// ));
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// sqlx::query!(
|
|
||||||
// "
|
|
||||||
// INSERT INTO pats (id, name, access_token, user_id, scope, expires_at)
|
|
||||||
// VALUES ($1, $2, $3, $4, $5, $6)
|
|
||||||
// ",
|
|
||||||
// pat.0,
|
|
||||||
// info.name,
|
|
||||||
// access_token,
|
|
||||||
// db_user_id.0,
|
|
||||||
// info.scope,
|
|
||||||
// expiry
|
|
||||||
// )
|
|
||||||
// .execute(&mut *transaction)
|
|
||||||
// .await?;
|
|
||||||
//
|
|
||||||
// transaction.commit().await?;
|
|
||||||
//
|
|
||||||
// Ok(HttpResponse::Ok().json(PersonalAccessToken {
|
|
||||||
// id: to_base62(pat.0 as u64),
|
|
||||||
// access_token: Some(access_token),
|
|
||||||
// name: info.name,
|
|
||||||
// scope: info.scope,
|
|
||||||
// user_id: user.id,
|
|
||||||
// expires_at: expiry,
|
|
||||||
// }))
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // PATCH /pat/(id)
|
|
||||||
// // Edit an access token of id "id" for the given user.
|
|
||||||
// // 'None' will mean not edited. Minos/Kratos cookie or PAT must be attached for it to work.
|
|
||||||
// #[patch("pat/{id}")]
|
|
||||||
// pub async fn edit_pat(
|
|
||||||
// req: HttpRequest,
|
|
||||||
// id: web::Path<String>,
|
|
||||||
// Query(info): Query<ModifyPersonalAccessToken>, // callback url
|
|
||||||
// pool: Data<PgPool>,
|
|
||||||
// redis: web::Data<deadpool_redis::Pool>,
|
|
||||||
// session_queue: web::Data<SessionQueue>,
|
|
||||||
// ) -> Result<HttpResponse, ApiError> {
|
|
||||||
// let user: crate::models::users::User =
|
|
||||||
// get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
|
||||||
// let pat_id = database::models::PatId(parse_base62(&id)? as i64);
|
|
||||||
// let db_user_id: database::models::UserId = database::models::UserId::from(user.id);
|
|
||||||
//
|
|
||||||
// if let Some(expire_in_days) = info.expire_in_days {
|
|
||||||
// if expire_in_days <= 0 {
|
|
||||||
// return Err(ApiError::InvalidInput(
|
|
||||||
// "'expire_in_days' must be greater than 0".to_string(),
|
|
||||||
// ));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Get the singular PAT and user combination (failing immediately if it doesn't exist)
|
|
||||||
// let mut transaction = pool.begin().await?;
|
|
||||||
// let row = sqlx::query!(
|
|
||||||
// "
|
|
||||||
// SELECT id, name, scope, user_id, expires_at FROM pats
|
|
||||||
// WHERE id = $1 AND user_id = $2
|
|
||||||
// ",
|
|
||||||
// pat_id.0,
|
|
||||||
// db_user_id.0 // included for safety
|
|
||||||
// )
|
|
||||||
// .fetch_one(&**pool)
|
|
||||||
// .await?;
|
|
||||||
//
|
|
||||||
// let pat = PersonalAccessToken {
|
|
||||||
// id: to_base62(row.id as u64),
|
|
||||||
// access_token: None,
|
|
||||||
// user_id: UserId::from(db_user_id),
|
|
||||||
// name: info.name.unwrap_or(row.name),
|
|
||||||
// scope: row.scope,
|
|
||||||
// expires_at: info
|
|
||||||
// .expire_in_days
|
|
||||||
// .map(|d| Utc::now().naive_utc() + Duration::days(d))
|
|
||||||
// .unwrap_or(row.expires_at),
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// sqlx::query!(
|
|
||||||
// "
|
|
||||||
// UPDATE pats SET
|
|
||||||
// name = $1,
|
|
||||||
// expires_at = $2
|
|
||||||
// WHERE id = $3
|
|
||||||
// ",
|
|
||||||
// pat.name,
|
|
||||||
// pat.expires_at,
|
|
||||||
// parse_base62(&pat.id)? as i64
|
|
||||||
// )
|
|
||||||
// .execute(&mut *transaction)
|
|
||||||
// .await?;
|
|
||||||
// transaction.commit().await?;
|
|
||||||
//
|
|
||||||
// Ok(HttpResponse::Ok().json(pat))
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // DELETE /pat
|
|
||||||
// // Delete a personal access token for the given user. Minos/Kratos cookie must be attached for it to work.
|
|
||||||
// #[delete("pat/{id}")]
|
|
||||||
// pub async fn delete_pat(
|
|
||||||
// req: HttpRequest,
|
|
||||||
// id: web::Path<String>,
|
|
||||||
// pool: Data<PgPool>,
|
|
||||||
// redis: web::Data<deadpool_redis::Pool>,
|
|
||||||
// session_queue: web::Data<SessionQueue>,
|
|
||||||
// ) -> Result<HttpResponse, ApiError> {
|
|
||||||
// let user: crate::models::users::User =
|
|
||||||
// get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
|
||||||
// let pat_id = database::models::PatId(parse_base62(&id)? as i64);
|
|
||||||
// let db_user_id: database::models::UserId = database::models::UserId::from(user.id);
|
|
||||||
//
|
|
||||||
// // Get the singular PAT and user combination (failing immediately if it doesn't exist)
|
|
||||||
// // This is to prevent users from deleting other users' PATs
|
|
||||||
// let pat_id = sqlx::query!(
|
|
||||||
// "
|
|
||||||
// SELECT id FROM pats
|
|
||||||
// WHERE id = $1 AND user_id = $2
|
|
||||||
// ",
|
|
||||||
// pat_id.0,
|
|
||||||
// db_user_id.0
|
|
||||||
// )
|
|
||||||
// .fetch_one(&**pool)
|
|
||||||
// .await?
|
|
||||||
// .id;
|
|
||||||
//
|
|
||||||
// let mut transaction = pool.begin().await?;
|
|
||||||
// sqlx::query!(
|
|
||||||
// "
|
|
||||||
// DELETE FROM pats
|
|
||||||
// WHERE id = $1
|
|
||||||
// ",
|
|
||||||
// pat_id,
|
|
||||||
// )
|
|
||||||
// .execute(&mut *transaction)
|
|
||||||
// .await?;
|
|
||||||
// transaction.commit().await?;
|
|
||||||
//
|
|
||||||
// Ok(HttpResponse::Ok().finish())
|
|
||||||
// }
|
|
||||||
@@ -4,13 +4,14 @@ use crate::database::models;
|
|||||||
use crate::database::models::thread_item::ThreadBuilder;
|
use crate::database::models::thread_item::ThreadBuilder;
|
||||||
use crate::file_hosting::{FileHost, FileHostingError};
|
use crate::file_hosting::{FileHost, FileHostingError};
|
||||||
use crate::models::error::ApiError;
|
use crate::models::error::ApiError;
|
||||||
|
use crate::models::pats::Scopes;
|
||||||
use crate::models::projects::{
|
use crate::models::projects::{
|
||||||
DonationLink, License, MonetizationStatus, ProjectId, ProjectStatus, SideType, VersionId,
|
DonationLink, License, MonetizationStatus, ProjectId, ProjectStatus, SideType, VersionId,
|
||||||
VersionStatus,
|
VersionStatus,
|
||||||
};
|
};
|
||||||
use crate::models::threads::ThreadType;
|
use crate::models::threads::ThreadType;
|
||||||
use crate::models::users::UserId;
|
use crate::models::users::UserId;
|
||||||
use crate::queue::session::SessionQueue;
|
use crate::queue::session::AuthQueue;
|
||||||
use crate::search::indexing::IndexingError;
|
use crate::search::indexing::IndexingError;
|
||||||
use crate::util::routes::read_from_field;
|
use crate::util::routes::read_from_field;
|
||||||
use crate::util::validate::validation_errors_to_string;
|
use crate::util::validate::validation_errors_to_string;
|
||||||
@@ -273,7 +274,7 @@ pub async fn project_create(
|
|||||||
client: Data<PgPool>,
|
client: Data<PgPool>,
|
||||||
redis: Data<deadpool_redis::Pool>,
|
redis: Data<deadpool_redis::Pool>,
|
||||||
file_host: Data<Arc<dyn FileHost + Send + Sync>>,
|
file_host: Data<Arc<dyn FileHost + Send + Sync>>,
|
||||||
session_queue: Data<SessionQueue>,
|
session_queue: Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, CreateError> {
|
) -> Result<HttpResponse, CreateError> {
|
||||||
let mut transaction = client.begin().await?;
|
let mut transaction = client.begin().await?;
|
||||||
let mut uploaded_files = Vec::new();
|
let mut uploaded_files = Vec::new();
|
||||||
@@ -343,13 +344,21 @@ async fn project_create_inner(
|
|||||||
uploaded_files: &mut Vec<UploadedFile>,
|
uploaded_files: &mut Vec<UploadedFile>,
|
||||||
pool: &PgPool,
|
pool: &PgPool,
|
||||||
redis: &deadpool_redis::Pool,
|
redis: &deadpool_redis::Pool,
|
||||||
session_queue: &SessionQueue,
|
session_queue: &AuthQueue,
|
||||||
) -> Result<HttpResponse, CreateError> {
|
) -> Result<HttpResponse, CreateError> {
|
||||||
// The base URL for files uploaded to backblaze
|
// The base URL for files uploaded to backblaze
|
||||||
let cdn_url = dotenvy::var("CDN_URL")?;
|
let cdn_url = dotenvy::var("CDN_URL")?;
|
||||||
|
|
||||||
// The currently logged in user
|
// The currently logged in user
|
||||||
let current_user = get_user_from_headers(&req, pool, redis, session_queue).await?;
|
let current_user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
pool,
|
||||||
|
redis,
|
||||||
|
session_queue,
|
||||||
|
Some(&[Scopes::PROJECT_CREATE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
|
|
||||||
let project_id: ProjectId = models::generate_project_id(transaction).await?.into();
|
let project_id: ProjectId = models::generate_project_id(transaction).await?.into();
|
||||||
|
|
||||||
@@ -726,14 +735,7 @@ async fn project_create_inner(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let thread_id = ThreadBuilder {
|
let project_builder_actual = models::project_item::ProjectBuilder {
|
||||||
type_: ThreadType::Project,
|
|
||||||
members: vec![],
|
|
||||||
}
|
|
||||||
.insert(&mut *transaction)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let project_builder = models::project_item::ProjectBuilder {
|
|
||||||
project_id: project_id.into(),
|
project_id: project_id.into(),
|
||||||
project_type_id,
|
project_type_id,
|
||||||
team_id,
|
team_id,
|
||||||
@@ -769,12 +771,23 @@ async fn project_create_inner(
|
|||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
color: icon_data.and_then(|x| x.1),
|
color: icon_data.and_then(|x| x.1),
|
||||||
thread_id,
|
|
||||||
monetization_status: MonetizationStatus::Monetized,
|
monetization_status: MonetizationStatus::Monetized,
|
||||||
};
|
};
|
||||||
|
let project_builder = project_builder_actual.clone();
|
||||||
|
|
||||||
let now = Utc::now();
|
let now = Utc::now();
|
||||||
|
|
||||||
|
let id = project_builder_actual.insert(&mut *transaction).await?;
|
||||||
|
|
||||||
|
let thread_id = ThreadBuilder {
|
||||||
|
type_: ThreadType::Project,
|
||||||
|
members: vec![],
|
||||||
|
project_id: Some(id),
|
||||||
|
report_id: None,
|
||||||
|
}
|
||||||
|
.insert(&mut *transaction)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let response = crate::models::projects::Project {
|
let response = crate::models::projects::Project {
|
||||||
id: project_id,
|
id: project_id,
|
||||||
slug: project_builder.slug.clone(),
|
slug: project_builder.slug.clone(),
|
||||||
@@ -817,12 +830,10 @@ async fn project_create_inner(
|
|||||||
donation_urls: project_create_data.donation_urls.clone(),
|
donation_urls: project_create_data.donation_urls.clone(),
|
||||||
gallery: gallery_urls,
|
gallery: gallery_urls,
|
||||||
color: project_builder.color,
|
color: project_builder.color,
|
||||||
thread_id: Some(project_builder.thread_id.into()),
|
thread_id: thread_id.into(),
|
||||||
monetization_status: project_builder.monetization_status,
|
monetization_status: MonetizationStatus::Monetized,
|
||||||
};
|
};
|
||||||
|
|
||||||
let _project_id = project_builder.insert(&mut *transaction).await?;
|
|
||||||
|
|
||||||
if status == ProjectStatus::Processing {
|
if status == ProjectStatus::Processing {
|
||||||
if let Ok(webhook_url) = dotenvy::var("MODERATION_DISCORD_WEBHOOK") {
|
if let Ok(webhook_url) = dotenvy::var("MODERATION_DISCORD_WEBHOOK") {
|
||||||
crate::util::webhook::send_discord_webhook(response.id, pool, webhook_url, None)
|
crate::util::webhook::send_discord_webhook(response.id, pool, webhook_url, None)
|
||||||
|
|||||||
@@ -6,12 +6,13 @@ use crate::file_hosting::FileHost;
|
|||||||
use crate::models;
|
use crate::models;
|
||||||
use crate::models::ids::base62_impl::parse_base62;
|
use crate::models::ids::base62_impl::parse_base62;
|
||||||
use crate::models::notifications::NotificationBody;
|
use crate::models::notifications::NotificationBody;
|
||||||
|
use crate::models::pats::Scopes;
|
||||||
use crate::models::projects::{
|
use crate::models::projects::{
|
||||||
DonationLink, MonetizationStatus, Project, ProjectId, ProjectStatus, SearchRequest, SideType,
|
DonationLink, MonetizationStatus, Project, ProjectId, ProjectStatus, SearchRequest, SideType,
|
||||||
};
|
};
|
||||||
use crate::models::teams::Permissions;
|
use crate::models::teams::Permissions;
|
||||||
use crate::models::threads::MessageBody;
|
use crate::models::threads::MessageBody;
|
||||||
use crate::queue::session::SessionQueue;
|
use crate::queue::session::AuthQueue;
|
||||||
use crate::routes::ApiError;
|
use crate::routes::ApiError;
|
||||||
use crate::search::{search_for_project, SearchConfig, SearchError};
|
use crate::search::{search_for_project, SearchConfig, SearchError};
|
||||||
use crate::util::routes::read_from_payload;
|
use crate::util::routes::read_from_payload;
|
||||||
@@ -116,14 +117,21 @@ pub async fn projects_get(
|
|||||||
web::Query(ids): web::Query<ProjectIds>,
|
web::Query(ids): web::Query<ProjectIds>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let ids = serde_json::from_str::<Vec<&str>>(&ids.ids)?;
|
let ids = serde_json::from_str::<Vec<&str>>(&ids.ids)?;
|
||||||
let projects_data = database::models::Project::get_many(&ids, &**pool, &redis).await?;
|
let projects_data = database::models::Project::get_many(&ids, &**pool, &redis).await?;
|
||||||
|
|
||||||
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
let user_option = get_user_from_headers(
|
||||||
.await
|
&req,
|
||||||
.ok();
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::PROJECT_READ]),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|x| x.1)
|
||||||
|
.ok();
|
||||||
|
|
||||||
let projects = filter_authorized_projects(projects_data, &user_option, &pool).await?;
|
let projects = filter_authorized_projects(projects_data, &user_option, &pool).await?;
|
||||||
|
|
||||||
@@ -136,15 +144,22 @@ pub async fn project_get(
|
|||||||
info: web::Path<(String,)>,
|
info: web::Path<(String,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let project_data = database::models::Project::get(&string, &**pool, &redis).await?;
|
let project_data = database::models::Project::get(&string, &**pool, &redis).await?;
|
||||||
|
|
||||||
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
let user_option = get_user_from_headers(
|
||||||
.await
|
&req,
|
||||||
.ok();
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::PROJECT_READ]),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|x| x.1)
|
||||||
|
.ok();
|
||||||
|
|
||||||
if let Some(data) = project_data {
|
if let Some(data) = project_data {
|
||||||
if is_authorized(&data.inner, &user_option, &pool).await? {
|
if is_authorized(&data.inner, &user_option, &pool).await? {
|
||||||
@@ -186,15 +201,22 @@ pub async fn dependency_list(
|
|||||||
info: web::Path<(String,)>,
|
info: web::Path<(String,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let result = database::models::Project::get(&string, &**pool, &redis).await?;
|
let result = database::models::Project::get(&string, &**pool, &redis).await?;
|
||||||
|
|
||||||
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
let user_option = get_user_from_headers(
|
||||||
.await
|
&req,
|
||||||
.ok();
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::PROJECT_READ]),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|x| x.1)
|
||||||
|
.ok();
|
||||||
|
|
||||||
if let Some(project) = result {
|
if let Some(project) = result {
|
||||||
if !is_authorized(&project.inner, &user_option, &pool).await? {
|
if !is_authorized(&project.inner, &user_option, &pool).await? {
|
||||||
@@ -357,9 +379,17 @@ pub async fn project_edit(
|
|||||||
config: web::Data<SearchConfig>,
|
config: web::Data<SearchConfig>,
|
||||||
new_project: web::Json<EditProject>,
|
new_project: web::Json<EditProject>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::PROJECT_WRITE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
|
|
||||||
new_project
|
new_project
|
||||||
.validate()
|
.validate()
|
||||||
@@ -546,18 +576,16 @@ pub async fn project_edit(
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(thread) = project_item.inner.thread_id {
|
ThreadMessageBuilder {
|
||||||
ThreadMessageBuilder {
|
author_id: Some(user.id.into()),
|
||||||
author_id: Some(user.id.into()),
|
body: MessageBody::StatusChange {
|
||||||
body: MessageBody::StatusChange {
|
new_status: *status,
|
||||||
new_status: *status,
|
old_status: project_item.inner.status,
|
||||||
old_status: project_item.inner.status,
|
},
|
||||||
},
|
thread_id: project_item.thread_id,
|
||||||
thread_id: thread,
|
}
|
||||||
}
|
|
||||||
.insert(&mut transaction)
|
.insert(&mut transaction)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
@@ -1181,9 +1209,17 @@ pub async fn projects_edit(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
bulk_edit_project: web::Json<BulkEditProject>,
|
bulk_edit_project: web::Json<BulkEditProject>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::PROJECT_WRITE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
|
|
||||||
bulk_edit_project
|
bulk_edit_project
|
||||||
.validate()
|
.validate()
|
||||||
@@ -1511,10 +1547,18 @@ pub async fn project_schedule(
|
|||||||
info: web::Path<(String,)>,
|
info: web::Path<(String,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
scheduling_data: web::Json<SchedulingData>,
|
scheduling_data: web::Json<SchedulingData>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::PROJECT_WRITE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
|
|
||||||
if scheduling_data.time < Utc::now() {
|
if scheduling_data.time < Utc::now() {
|
||||||
return Err(ApiError::InvalidInput(
|
return Err(ApiError::InvalidInput(
|
||||||
@@ -1591,11 +1635,19 @@ pub async fn project_icon_edit(
|
|||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||||
mut payload: web::Payload,
|
mut payload: web::Payload,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
if let Some(content_type) = crate::util::ext::get_image_content_type(&ext.ext) {
|
if let Some(content_type) = crate::util::ext::get_image_content_type(&ext.ext) {
|
||||||
let cdn_url = dotenvy::var("CDN_URL")?;
|
let cdn_url = dotenvy::var("CDN_URL")?;
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::PROJECT_WRITE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let project_item = database::models::Project::get(&string, &**pool, &redis)
|
let project_item = database::models::Project::get(&string, &**pool, &redis)
|
||||||
@@ -1687,9 +1739,17 @@ pub async fn delete_project_icon(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::PROJECT_WRITE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let project_item = database::models::Project::get(&string, &**pool, &redis)
|
let project_item = database::models::Project::get(&string, &**pool, &redis)
|
||||||
@@ -1773,14 +1833,22 @@ pub async fn add_gallery_item(
|
|||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||||
mut payload: web::Payload,
|
mut payload: web::Payload,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
if let Some(content_type) = crate::util::ext::get_image_content_type(&ext.ext) {
|
if let Some(content_type) = crate::util::ext::get_image_content_type(&ext.ext) {
|
||||||
item.validate()
|
item.validate()
|
||||||
.map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?;
|
.map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?;
|
||||||
|
|
||||||
let cdn_url = dotenvy::var("CDN_URL")?;
|
let cdn_url = dotenvy::var("CDN_URL")?;
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::PROJECT_WRITE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let project_item = database::models::Project::get(&string, &**pool, &redis)
|
let project_item = database::models::Project::get(&string, &**pool, &redis)
|
||||||
@@ -1915,9 +1983,17 @@ pub async fn edit_gallery_item(
|
|||||||
info: web::Path<(String,)>,
|
info: web::Path<(String,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::PROJECT_WRITE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
item.validate()
|
item.validate()
|
||||||
@@ -2061,9 +2137,17 @@ pub async fn delete_gallery_item(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::PROJECT_WRITE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let project_item = database::models::Project::get(&string, &**pool, &redis)
|
let project_item = database::models::Project::get(&string, &**pool, &redis)
|
||||||
@@ -2148,9 +2232,17 @@ pub async fn project_delete(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
config: web::Data<SearchConfig>,
|
config: web::Data<SearchConfig>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::PROJECT_DELETE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let project = database::models::Project::get(&string, &**pool, &redis)
|
let project = database::models::Project::get(&string, &**pool, &redis)
|
||||||
@@ -2203,9 +2295,17 @@ pub async fn project_follow(
|
|||||||
info: web::Path<(String,)>,
|
info: web::Path<(String,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::USER_WRITE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let result = database::models::Project::get(&string, &**pool, &redis)
|
let result = database::models::Project::get(&string, &**pool, &redis)
|
||||||
@@ -2274,9 +2374,17 @@ pub async fn project_unfollow(
|
|||||||
info: web::Path<(String,)>,
|
info: web::Path<(String,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::USER_WRITE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let result = database::models::Project::get(&string, &**pool, &redis)
|
let result = database::models::Project::get(&string, &**pool, &redis)
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
use crate::auth::{check_is_moderator_from_headers, get_user_from_headers};
|
use crate::auth::{check_is_moderator_from_headers, get_user_from_headers};
|
||||||
use crate::database::models::thread_item::{ThreadBuilder, ThreadMessageBuilder};
|
use crate::database::models::thread_item::{ThreadBuilder, ThreadMessageBuilder};
|
||||||
use crate::models::ids::{base62_impl::parse_base62, ProjectId, UserId, VersionId};
|
use crate::models::ids::{base62_impl::parse_base62, ProjectId, UserId, VersionId};
|
||||||
|
use crate::models::pats::Scopes;
|
||||||
use crate::models::reports::{ItemType, Report};
|
use crate::models::reports::{ItemType, Report};
|
||||||
use crate::models::threads::{MessageBody, ThreadType};
|
use crate::models::threads::{MessageBody, ThreadType};
|
||||||
use crate::queue::session::SessionQueue;
|
use crate::queue::session::AuthQueue;
|
||||||
use crate::routes::ApiError;
|
use crate::routes::ApiError;
|
||||||
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
|
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
@@ -35,11 +36,19 @@ pub async fn report_create(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
mut body: web::Payload,
|
mut body: web::Payload,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let mut transaction = pool.begin().await?;
|
let mut transaction = pool.begin().await?;
|
||||||
|
|
||||||
let current_user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let current_user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::REPORT_CREATE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
|
|
||||||
let mut bytes = web::BytesMut::new();
|
let mut bytes = web::BytesMut::new();
|
||||||
while let Some(item) = body.next().await {
|
while let Some(item) = body.next().await {
|
||||||
@@ -59,13 +68,6 @@ pub async fn report_create(
|
|||||||
ApiError::InvalidInput(format!("Invalid report type: {}", new_report.report_type))
|
ApiError::InvalidInput(format!("Invalid report type: {}", new_report.report_type))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let thread_id = ThreadBuilder {
|
|
||||||
type_: ThreadType::Report,
|
|
||||||
members: vec![],
|
|
||||||
}
|
|
||||||
.insert(&mut transaction)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut report = crate::database::models::report_item::Report {
|
let mut report = crate::database::models::report_item::Report {
|
||||||
id,
|
id,
|
||||||
report_type_id: report_type,
|
report_type_id: report_type,
|
||||||
@@ -76,7 +78,6 @@ pub async fn report_create(
|
|||||||
reporter: current_user.id.into(),
|
reporter: current_user.id.into(),
|
||||||
created: Utc::now(),
|
created: Utc::now(),
|
||||||
closed: false,
|
closed: false,
|
||||||
thread_id,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
match new_report.item_type {
|
match new_report.item_type {
|
||||||
@@ -146,6 +147,15 @@ pub async fn report_create(
|
|||||||
}
|
}
|
||||||
|
|
||||||
report.insert(&mut transaction).await?;
|
report.insert(&mut transaction).await?;
|
||||||
|
let thread_id = ThreadBuilder {
|
||||||
|
type_: ThreadType::Report,
|
||||||
|
members: vec![],
|
||||||
|
project_id: None,
|
||||||
|
report_id: Some(report.id),
|
||||||
|
}
|
||||||
|
.insert(&mut transaction)
|
||||||
|
.await?;
|
||||||
|
|
||||||
transaction.commit().await?;
|
transaction.commit().await?;
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(Report {
|
Ok(HttpResponse::Ok().json(Report {
|
||||||
@@ -157,7 +167,7 @@ pub async fn report_create(
|
|||||||
body: new_report.body.clone(),
|
body: new_report.body.clone(),
|
||||||
created: Utc::now(),
|
created: Utc::now(),
|
||||||
closed: false,
|
closed: false,
|
||||||
thread_id: Some(report.thread_id.into()),
|
thread_id: thread_id.into(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,9 +192,17 @@ pub async fn reports(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
count: web::Query<ReportsRequestOptions>,
|
count: web::Query<ReportsRequestOptions>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::REPORT_READ]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
|
|
||||||
use futures::stream::TryStreamExt;
|
use futures::stream::TryStreamExt;
|
||||||
|
|
||||||
@@ -248,7 +266,7 @@ pub async fn reports_get(
|
|||||||
web::Query(ids): web::Query<ReportIds>,
|
web::Query(ids): web::Query<ReportIds>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let report_ids: Vec<crate::database::models::ids::ReportId> =
|
let report_ids: Vec<crate::database::models::ids::ReportId> =
|
||||||
serde_json::from_str::<Vec<crate::models::ids::ReportId>>(&ids.ids)?
|
serde_json::from_str::<Vec<crate::models::ids::ReportId>>(&ids.ids)?
|
||||||
@@ -259,7 +277,15 @@ pub async fn reports_get(
|
|||||||
let reports_data =
|
let reports_data =
|
||||||
crate::database::models::report_item::Report::get_many(&report_ids, &**pool).await?;
|
crate::database::models::report_item::Report::get_many(&report_ids, &**pool).await?;
|
||||||
|
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::REPORT_READ]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
|
|
||||||
let all_reports = reports_data
|
let all_reports = reports_data
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -276,9 +302,17 @@ pub async fn report_get(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
info: web::Path<(crate::models::reports::ReportId,)>,
|
info: web::Path<(crate::models::reports::ReportId,)>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::REPORT_READ]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
let id = info.into_inner().0.into();
|
let id = info.into_inner().0.into();
|
||||||
|
|
||||||
let report = crate::database::models::report_item::Report::get(id, &**pool).await?;
|
let report = crate::database::models::report_item::Report::get(id, &**pool).await?;
|
||||||
@@ -308,10 +342,18 @@ pub async fn report_edit(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
info: web::Path<(crate::models::reports::ReportId,)>,
|
info: web::Path<(crate::models::reports::ReportId,)>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
edit_report: web::Json<EditReport>,
|
edit_report: web::Json<EditReport>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::REPORT_WRITE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
let id = info.into_inner().0.into();
|
let id = info.into_inner().0.into();
|
||||||
|
|
||||||
let report = crate::database::models::report_item::Report::get(id, &**pool).await?;
|
let report = crate::database::models::report_item::Report::get(id, &**pool).await?;
|
||||||
@@ -344,19 +386,17 @@ pub async fn report_edit(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(thread) = report.thread_id {
|
ThreadMessageBuilder {
|
||||||
ThreadMessageBuilder {
|
author_id: Some(user.id.into()),
|
||||||
author_id: Some(user.id.into()),
|
body: if !edit_closed && report.closed {
|
||||||
body: if !edit_closed && report.closed {
|
MessageBody::ThreadReopen
|
||||||
MessageBody::ThreadReopen
|
} else {
|
||||||
} else {
|
MessageBody::ThreadClosure
|
||||||
MessageBody::ThreadClosure
|
},
|
||||||
},
|
thread_id: report.thread_id,
|
||||||
thread_id: thread,
|
}
|
||||||
}
|
|
||||||
.insert(&mut transaction)
|
.insert(&mut transaction)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
@@ -385,7 +425,7 @@ pub async fn report_delete(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
info: web::Path<(crate::models::reports::ReportId,)>,
|
info: web::Path<(crate::models::reports::ReportId,)>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
check_is_moderator_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
check_is_moderator_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
|
|
||||||
|
|||||||
@@ -29,8 +29,6 @@ pub struct CategoryData {
|
|||||||
header: String,
|
header: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: searching / filtering? Could be used to implement a live
|
|
||||||
// searching category list
|
|
||||||
#[get("category")]
|
#[get("category")]
|
||||||
pub async fn category_list(pool: web::Data<PgPool>) -> Result<HttpResponse, ApiError> {
|
pub async fn category_list(pool: web::Data<PgPool>) -> Result<HttpResponse, ApiError> {
|
||||||
let results = Category::list(&**pool)
|
let results = Category::list(&**pool)
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ use crate::database::models::notification_item::NotificationBuilder;
|
|||||||
use crate::database::models::TeamMember;
|
use crate::database::models::TeamMember;
|
||||||
use crate::models::ids::ProjectId;
|
use crate::models::ids::ProjectId;
|
||||||
use crate::models::notifications::NotificationBody;
|
use crate::models::notifications::NotificationBody;
|
||||||
|
use crate::models::pats::Scopes;
|
||||||
use crate::models::teams::{Permissions, TeamId};
|
use crate::models::teams::{Permissions, TeamId};
|
||||||
use crate::models::users::UserId;
|
use crate::models::users::UserId;
|
||||||
use crate::queue::session::SessionQueue;
|
use crate::queue::session::AuthQueue;
|
||||||
use crate::routes::ApiError;
|
use crate::routes::ApiError;
|
||||||
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
|
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
|
||||||
use rust_decimal::Decimal;
|
use rust_decimal::Decimal;
|
||||||
@@ -32,15 +33,22 @@ pub async fn team_members_get_project(
|
|||||||
info: web::Path<(String,)>,
|
info: web::Path<(String,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
let project_data = crate::database::models::Project::get(&string, &**pool, &redis).await?;
|
let project_data = crate::database::models::Project::get(&string, &**pool, &redis).await?;
|
||||||
|
|
||||||
if let Some(project) = project_data {
|
if let Some(project) = project_data {
|
||||||
let current_user = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
let current_user = get_user_from_headers(
|
||||||
.await
|
&req,
|
||||||
.ok();
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::PROJECT_READ]),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|x| x.1)
|
||||||
|
.ok();
|
||||||
|
|
||||||
if !is_authorized(&project.inner, ¤t_user, &pool).await? {
|
if !is_authorized(&project.inner, ¤t_user, &pool).await? {
|
||||||
return Ok(HttpResponse::NotFound().body(""));
|
return Ok(HttpResponse::NotFound().body(""));
|
||||||
@@ -93,7 +101,7 @@ pub async fn team_members_get(
|
|||||||
info: web::Path<(TeamId,)>,
|
info: web::Path<(TeamId,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let id = info.into_inner().0;
|
let id = info.into_inner().0;
|
||||||
let members_data = TeamMember::get_from_team_full(id.into(), &**pool, &redis).await?;
|
let members_data = TeamMember::get_from_team_full(id.into(), &**pool, &redis).await?;
|
||||||
@@ -104,9 +112,16 @@ pub async fn team_members_get(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let current_user = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
let current_user = get_user_from_headers(
|
||||||
.await
|
&req,
|
||||||
.ok();
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::PROJECT_READ]),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|x| x.1)
|
||||||
|
.ok();
|
||||||
let user_id = current_user.as_ref().map(|x| x.id.into());
|
let user_id = current_user.as_ref().map(|x| x.id.into());
|
||||||
|
|
||||||
let logged_in = current_user
|
let logged_in = current_user
|
||||||
@@ -148,7 +163,7 @@ pub async fn teams_get(
|
|||||||
web::Query(ids): web::Query<TeamIds>,
|
web::Query(ids): web::Query<TeamIds>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
|
||||||
@@ -165,9 +180,16 @@ pub async fn teams_get(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let current_user = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
let current_user = get_user_from_headers(
|
||||||
.await
|
&req,
|
||||||
.ok();
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::PROJECT_READ]),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|x| x.1)
|
||||||
|
.ok();
|
||||||
|
|
||||||
let teams_groups = teams_data.into_iter().group_by(|data| data.team_id.0);
|
let teams_groups = teams_data.into_iter().group_by(|data| data.team_id.0);
|
||||||
|
|
||||||
@@ -206,10 +228,18 @@ pub async fn join_team(
|
|||||||
info: web::Path<(TeamId,)>,
|
info: web::Path<(TeamId,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let team_id = info.into_inner().0.into();
|
let team_id = info.into_inner().0.into();
|
||||||
let current_user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let current_user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::PROJECT_WRITE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
|
|
||||||
let member =
|
let member =
|
||||||
TeamMember::get_from_user_id_pending(team_id, current_user.id.into(), &**pool).await?;
|
TeamMember::get_from_user_id_pending(team_id, current_user.id.into(), &**pool).await?;
|
||||||
@@ -275,13 +305,21 @@ pub async fn add_team_member(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
new_member: web::Json<NewTeamMember>,
|
new_member: web::Json<NewTeamMember>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let team_id = info.into_inner().0.into();
|
let team_id = info.into_inner().0.into();
|
||||||
|
|
||||||
let mut transaction = pool.begin().await?;
|
let mut transaction = pool.begin().await?;
|
||||||
|
|
||||||
let current_user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let current_user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::PROJECT_WRITE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
let member = TeamMember::get_from_user_id(team_id, current_user.id.into(), &**pool)
|
let member = TeamMember::get_from_user_id(team_id, current_user.id.into(), &**pool)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
@@ -390,13 +428,21 @@ pub async fn edit_team_member(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
edit_member: web::Json<EditTeamMember>,
|
edit_member: web::Json<EditTeamMember>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let ids = info.into_inner();
|
let ids = info.into_inner();
|
||||||
let id = ids.0.into();
|
let id = ids.0.into();
|
||||||
let user_id = ids.1.into();
|
let user_id = ids.1.into();
|
||||||
|
|
||||||
let current_user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let current_user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::PROJECT_WRITE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
let member = TeamMember::get_from_user_id(id, current_user.id.into(), &**pool)
|
let member = TeamMember::get_from_user_id(id, current_user.id.into(), &**pool)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
@@ -481,11 +527,19 @@ pub async fn transfer_ownership(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
new_owner: web::Json<TransferOwnership>,
|
new_owner: web::Json<TransferOwnership>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let id = info.into_inner().0;
|
let id = info.into_inner().0;
|
||||||
|
|
||||||
let current_user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let current_user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::PROJECT_WRITE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
|
|
||||||
if !current_user.role.is_admin() {
|
if !current_user.role.is_admin() {
|
||||||
let member = TeamMember::get_from_user_id(id.into(), current_user.id.into(), &**pool)
|
let member = TeamMember::get_from_user_id(id.into(), current_user.id.into(), &**pool)
|
||||||
@@ -554,13 +608,21 @@ pub async fn remove_team_member(
|
|||||||
info: web::Path<(TeamId, UserId)>,
|
info: web::Path<(TeamId, UserId)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let ids = info.into_inner();
|
let ids = info.into_inner();
|
||||||
let id = ids.0.into();
|
let id = ids.0.into();
|
||||||
let user_id = ids.1.into();
|
let user_id = ids.1.into();
|
||||||
|
|
||||||
let current_user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let current_user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::PROJECT_WRITE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
let member = TeamMember::get_from_user_id(id, current_user.id.into(), &**pool)
|
let member = TeamMember::get_from_user_id(id, current_user.id.into(), &**pool)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ use crate::database::models::notification_item::NotificationBuilder;
|
|||||||
use crate::database::models::thread_item::ThreadMessageBuilder;
|
use crate::database::models::thread_item::ThreadMessageBuilder;
|
||||||
use crate::models::ids::ThreadMessageId;
|
use crate::models::ids::ThreadMessageId;
|
||||||
use crate::models::notifications::NotificationBody;
|
use crate::models::notifications::NotificationBody;
|
||||||
|
use crate::models::pats::Scopes;
|
||||||
use crate::models::projects::ProjectStatus;
|
use crate::models::projects::ProjectStatus;
|
||||||
use crate::models::threads::{MessageBody, Thread, ThreadId, ThreadType};
|
use crate::models::threads::{MessageBody, Thread, ThreadId, ThreadType};
|
||||||
use crate::models::users::User;
|
use crate::models::users::User;
|
||||||
use crate::queue::session::SessionQueue;
|
use crate::queue::session::AuthQueue;
|
||||||
use crate::routes::ApiError;
|
use crate::routes::ApiError;
|
||||||
use actix_web::{delete, get, post, web, HttpRequest, HttpResponse};
|
use actix_web::{delete, get, post, web, HttpRequest, HttpResponse};
|
||||||
use futures::TryStreamExt;
|
use futures::TryStreamExt;
|
||||||
@@ -38,28 +39,34 @@ pub async fn is_authorized_thread(
|
|||||||
let user_id: database::models::UserId = user.id.into();
|
let user_id: database::models::UserId = user.id.into();
|
||||||
Ok(match thread.type_ {
|
Ok(match thread.type_ {
|
||||||
ThreadType::Report => {
|
ThreadType::Report => {
|
||||||
let report_exists = sqlx::query!(
|
if let Some(report_id) = thread.report_id {
|
||||||
"SELECT EXISTS(SELECT 1 FROM reports WHERE thread_id = $1 AND reporter = $2)",
|
let report_exists = sqlx::query!(
|
||||||
thread.id as database::models::ids::ThreadId,
|
"SELECT EXISTS(SELECT 1 FROM reports WHERE id = $1 AND reporter = $2)",
|
||||||
user_id as database::models::ids::UserId,
|
report_id as database::models::ids::ReportId,
|
||||||
)
|
user_id as database::models::ids::UserId,
|
||||||
.fetch_one(pool)
|
)
|
||||||
.await?
|
.fetch_one(pool)
|
||||||
.exists;
|
.await?
|
||||||
|
.exists;
|
||||||
|
|
||||||
report_exists.unwrap_or(false)
|
report_exists.unwrap_or(false)
|
||||||
|
} else { false }
|
||||||
}
|
}
|
||||||
ThreadType::Project => {
|
ThreadType::Project => {
|
||||||
let project_exists = sqlx::query!(
|
if let Some(project_id) = thread.project_id {
|
||||||
"SELECT EXISTS(SELECT 1 FROM mods m INNER JOIN team_members tm ON tm.team_id = m.team_id AND tm.user_id = $2 WHERE thread_id = $1)",
|
let project_exists = sqlx::query!(
|
||||||
thread.id as database::models::ids::ThreadId,
|
"SELECT EXISTS(SELECT 1 FROM mods m INNER JOIN team_members tm ON tm.team_id = m.team_id AND tm.user_id = $2 WHERE m.id = $1)",
|
||||||
user_id as database::models::ids::UserId,
|
project_id as database::models::ids::ProjectId,
|
||||||
)
|
user_id as database::models::ids::UserId,
|
||||||
.fetch_one(pool)
|
)
|
||||||
.await?
|
.fetch_one(pool)
|
||||||
.exists;
|
.await?
|
||||||
|
.exists;
|
||||||
|
|
||||||
project_exists.unwrap_or(false)
|
project_exists.unwrap_or(false)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ThreadType::DirectMessage => thread.members.contains(&user_id),
|
ThreadType::DirectMessage => thread.members.contains(&user_id),
|
||||||
})
|
})
|
||||||
@@ -90,15 +97,15 @@ pub async fn filter_authorized_threads(
|
|||||||
let project_thread_ids = check_threads
|
let project_thread_ids = check_threads
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|x| x.type_ == ThreadType::Project)
|
.filter(|x| x.type_ == ThreadType::Project)
|
||||||
.map(|x| x.id.0)
|
.flat_map(|x| x.project_id.map(|x| x.0))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
if !project_thread_ids.is_empty() {
|
if !project_thread_ids.is_empty() {
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT m.thread_id FROM mods m
|
SELECT m.id FROM mods m
|
||||||
INNER JOIN team_members tm ON tm.team_id = m.team_id AND user_id = $2
|
INNER JOIN team_members tm ON tm.team_id = m.team_id AND user_id = $2
|
||||||
WHERE m.thread_id = ANY($1)
|
WHERE m.id = ANY($1)
|
||||||
",
|
",
|
||||||
&*project_thread_ids,
|
&*project_thread_ids,
|
||||||
user_id as database::models::ids::UserId,
|
user_id as database::models::ids::UserId,
|
||||||
@@ -107,7 +114,7 @@ pub async fn filter_authorized_threads(
|
|||||||
.try_for_each(|e| {
|
.try_for_each(|e| {
|
||||||
if let Some(row) = e.right() {
|
if let Some(row) = e.right() {
|
||||||
check_threads.retain(|x| {
|
check_threads.retain(|x| {
|
||||||
let bool = Some(x.id.0) == row.thread_id;
|
let bool = x.project_id.map(|x| x.0) == Some(row.id);
|
||||||
|
|
||||||
if bool {
|
if bool {
|
||||||
return_threads.push(x.clone());
|
return_threads.push(x.clone());
|
||||||
@@ -125,14 +132,14 @@ pub async fn filter_authorized_threads(
|
|||||||
let report_thread_ids = check_threads
|
let report_thread_ids = check_threads
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|x| x.type_ == ThreadType::Report)
|
.filter(|x| x.type_ == ThreadType::Report)
|
||||||
.map(|x| x.id.0)
|
.flat_map(|x| x.report_id.map(|x| x.0))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
if !report_thread_ids.is_empty() {
|
if !report_thread_ids.is_empty() {
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT thread_id FROM reports
|
SELECT id FROM reports
|
||||||
WHERE thread_id = ANY($1) AND reporter = $2
|
WHERE id = ANY($1) AND reporter = $2
|
||||||
",
|
",
|
||||||
&*report_thread_ids,
|
&*report_thread_ids,
|
||||||
user_id as database::models::ids::UserId,
|
user_id as database::models::ids::UserId,
|
||||||
@@ -141,7 +148,7 @@ pub async fn filter_authorized_threads(
|
|||||||
.try_for_each(|e| {
|
.try_for_each(|e| {
|
||||||
if let Some(row) = e.right() {
|
if let Some(row) = e.right() {
|
||||||
check_threads.retain(|x| {
|
check_threads.retain(|x| {
|
||||||
let bool = Some(x.id.0) == row.thread_id;
|
let bool = x.report_id.map(|x| x.0) == Some(row.id);
|
||||||
|
|
||||||
if bool {
|
if bool {
|
||||||
return_threads.push(x.clone());
|
return_threads.push(x.clone());
|
||||||
@@ -212,13 +219,21 @@ pub async fn thread_get(
|
|||||||
info: web::Path<(ThreadId,)>,
|
info: web::Path<(ThreadId,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let string = info.into_inner().0.into();
|
let string = info.into_inner().0.into();
|
||||||
|
|
||||||
let thread_data = database::models::Thread::get(string, &**pool).await?;
|
let thread_data = database::models::Thread::get(string, &**pool).await?;
|
||||||
|
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::THREAD_READ]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
|
|
||||||
if let Some(mut data) = thread_data {
|
if let Some(mut data) = thread_data {
|
||||||
if is_authorized_thread(&data, &user, &pool).await? {
|
if is_authorized_thread(&data, &user, &pool).await? {
|
||||||
@@ -255,9 +270,17 @@ pub async fn threads_get(
|
|||||||
web::Query(ids): web::Query<ThreadIds>,
|
web::Query(ids): web::Query<ThreadIds>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::THREAD_READ]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
|
|
||||||
let thread_ids: Vec<database::models::ids::ThreadId> =
|
let thread_ids: Vec<database::models::ids::ThreadId> =
|
||||||
serde_json::from_str::<Vec<ThreadId>>(&ids.ids)?
|
serde_json::from_str::<Vec<ThreadId>>(&ids.ids)?
|
||||||
@@ -284,9 +307,17 @@ pub async fn thread_send_message(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
new_message: web::Json<NewThreadMessage>,
|
new_message: web::Json<NewThreadMessage>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::THREAD_WRITE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
|
|
||||||
let string: database::models::ThreadId = info.into_inner().0.into();
|
let string: database::models::ThreadId = info.into_inner().0.into();
|
||||||
|
|
||||||
@@ -347,50 +378,45 @@ pub async fn thread_send_message(
|
|||||||
.insert(&mut transaction)
|
.insert(&mut transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mod_notif = if thread.type_ == ThreadType::Project {
|
let mod_notif = if let Some(project_id) = thread.project_id {
|
||||||
let record = sqlx::query!(
|
let project = database::models::Project::get_id(
|
||||||
"SELECT m.id, m.status, m.team_id FROM mods m WHERE thread_id = $1",
|
project_id,
|
||||||
thread.id as database::models::ids::ThreadId,
|
&**pool,
|
||||||
|
&redis,
|
||||||
)
|
)
|
||||||
.fetch_one(&**pool)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let status = ProjectStatus::from_str(&record.status);
|
|
||||||
|
|
||||||
if status != ProjectStatus::Processing && user.role.is_mod() {
|
|
||||||
let members = database::models::TeamMember::get_from_team_full(
|
|
||||||
database::models::TeamId(record.team_id),
|
|
||||||
&**pool,
|
|
||||||
&redis,
|
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
NotificationBuilder {
|
if let Some(project) = project {
|
||||||
body: NotificationBody::ModeratorMessage {
|
if project.inner.status != ProjectStatus::Processing && user.role.is_mod() {
|
||||||
thread_id: thread.id.into(),
|
let members = database::models::TeamMember::get_from_team_full(
|
||||||
message_id: id.into(),
|
project.inner.team_id,
|
||||||
project_id: Some(database::models::ProjectId(record.id).into()),
|
&**pool,
|
||||||
report_id: None,
|
&redis,
|
||||||
},
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
NotificationBuilder {
|
||||||
|
body: NotificationBody::ModeratorMessage {
|
||||||
|
thread_id: thread.id.into(),
|
||||||
|
message_id: id.into(),
|
||||||
|
project_id: Some(project.inner.id.into()),
|
||||||
|
report_id: None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
.insert_many(
|
||||||
|
members.into_iter().map(|x| x.user_id).collect(),
|
||||||
|
&mut transaction,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
.insert_many(
|
|
||||||
members.into_iter().map(|x| x.user_id).collect(),
|
project.inner.status == ProjectStatus::Processing && !user.role.is_mod()
|
||||||
&mut transaction,
|
} else {
|
||||||
)
|
!user.role.is_mod()
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
|
} else if let Some(report_id) = thread.report_id {
|
||||||
status == ProjectStatus::Processing && !user.role.is_mod()
|
|
||||||
} else if thread.type_ == ThreadType::Report {
|
|
||||||
let record = sqlx::query!(
|
|
||||||
"SELECT r.id FROM reports r WHERE thread_id = $1",
|
|
||||||
thread.id as database::models::ids::ThreadId,
|
|
||||||
)
|
|
||||||
.fetch_one(&**pool)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let report = database::models::report_item::Report::get(
|
let report = database::models::report_item::Report::get(
|
||||||
database::models::ReportId(record.id),
|
report_id,
|
||||||
&**pool,
|
&**pool,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -446,7 +472,7 @@ pub async fn moderation_inbox(
|
|||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = check_is_moderator_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let user = check_is_moderator_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
|
|
||||||
@@ -474,7 +500,7 @@ pub async fn thread_read(
|
|||||||
info: web::Path<(ThreadId,)>,
|
info: web::Path<(ThreadId,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
check_is_moderator_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
check_is_moderator_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
|
|
||||||
@@ -503,9 +529,17 @@ pub async fn message_delete(
|
|||||||
info: web::Path<(ThreadMessageId,)>,
|
info: web::Path<(ThreadMessageId,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::THREAD_WRITE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
|
|
||||||
let result = database::models::ThreadMessage::get(info.into_inner().0.into(), &**pool).await?;
|
let result = database::models::ThreadMessage::get(info.into_inner().0.into(), &**pool).await?;
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
use crate::auth::get_user_from_headers;
|
use crate::auth::{get_user_from_headers, AuthenticationError};
|
||||||
use crate::database::models::User;
|
use crate::database::models::User;
|
||||||
use crate::file_hosting::FileHost;
|
use crate::file_hosting::FileHost;
|
||||||
use crate::models::notifications::Notification;
|
use crate::models::notifications::Notification;
|
||||||
|
use crate::models::pats::Scopes;
|
||||||
use crate::models::projects::Project;
|
use crate::models::projects::Project;
|
||||||
use crate::models::users::{Badges, RecipientType, RecipientWallet, Role, UserId};
|
use crate::models::users::{Badges, RecipientType, RecipientWallet, Role, UserId};
|
||||||
use crate::queue::payouts::{PayoutAmount, PayoutItem, PayoutsQueue};
|
use crate::queue::payouts::{PayoutAmount, PayoutItem, PayoutsQueue};
|
||||||
use crate::queue::session::SessionQueue;
|
use crate::queue::session::AuthQueue;
|
||||||
use crate::routes::ApiError;
|
use crate::routes::ApiError;
|
||||||
use crate::util::routes::read_from_payload;
|
use crate::util::routes::read_from_payload;
|
||||||
use crate::util::validate::validation_errors_to_string;
|
use crate::util::validate::validation_errors_to_string;
|
||||||
@@ -27,7 +28,6 @@ use validator::Validate;
|
|||||||
|
|
||||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||||
cfg.service(user_auth_get);
|
cfg.service(user_auth_get);
|
||||||
cfg.service(user_data_get);
|
|
||||||
cfg.service(users_get);
|
cfg.service(users_get);
|
||||||
|
|
||||||
cfg.service(
|
cfg.service(
|
||||||
@@ -49,51 +49,36 @@ pub async fn user_auth_get(
|
|||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
Ok(
|
let (scopes, mut user) = get_user_from_headers(
|
||||||
HttpResponse::Ok()
|
&req,
|
||||||
.json(get_user_from_headers(&req, &**pool, &redis, &session_queue).await?),
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::USER_READ]),
|
||||||
)
|
)
|
||||||
}
|
.await?;
|
||||||
|
|
||||||
#[derive(Serialize)]
|
if !scopes.contains(Scopes::USER_READ_EMAIL) {
|
||||||
pub struct UserData {
|
user.email = None;
|
||||||
pub notifs_count: u64,
|
|
||||||
pub followed_projects: Vec<crate::models::ids::ProjectId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("user_data")]
|
|
||||||
pub async fn user_data_get(
|
|
||||||
req: HttpRequest,
|
|
||||||
pool: web::Data<PgPool>,
|
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
|
||||||
session_queue: web::Data<SessionQueue>,
|
|
||||||
) -> Result<HttpResponse, ApiError> {
|
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
|
||||||
|
|
||||||
let data = sqlx::query!(
|
|
||||||
"
|
|
||||||
SELECT COUNT(DISTINCT n.id) notifs_count, ARRAY_AGG(mf.mod_id) followed_projects FROM notifications n
|
|
||||||
LEFT OUTER JOIN mod_follows mf ON mf.follower_id = $1
|
|
||||||
WHERE user_id = $1 AND read = FALSE
|
|
||||||
",
|
|
||||||
user.id.0 as i64
|
|
||||||
).fetch_optional(&**pool).await?;
|
|
||||||
|
|
||||||
if let Some(data) = data {
|
|
||||||
Ok(HttpResponse::Ok().json(UserData {
|
|
||||||
notifs_count: data.notifs_count.map(|x| x as u64).unwrap_or(0),
|
|
||||||
followed_projects: data
|
|
||||||
.followed_projects
|
|
||||||
.unwrap_or_default()
|
|
||||||
.into_iter()
|
|
||||||
.map(|x| crate::models::ids::ProjectId(x as u64))
|
|
||||||
.collect(),
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
Ok(HttpResponse::NoContent().body(""))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !scopes.contains(Scopes::PAYOUTS_READ) {
|
||||||
|
user.payout_data = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().json(
|
||||||
|
get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::USER_READ]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@@ -138,11 +123,18 @@ pub async fn projects_list(
|
|||||||
info: web::Path<(String,)>,
|
info: web::Path<(String,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
let user = get_user_from_headers(
|
||||||
.await
|
&req,
|
||||||
.ok();
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::PROJECT_READ]),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|x| x.1)
|
||||||
|
.ok();
|
||||||
|
|
||||||
let id_option = User::get(&info.into_inner().0, &**pool, &redis).await?;
|
let id_option = User::get(&info.into_inner().0, &**pool, &redis).await?;
|
||||||
|
|
||||||
@@ -218,9 +210,16 @@ pub async fn user_edit(
|
|||||||
new_user: web::Json<EditUser>,
|
new_user: web::Json<EditUser>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let (scopes, user) = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::USER_WRITE]),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
new_user
|
new_user
|
||||||
.validate()
|
.validate()
|
||||||
@@ -343,6 +342,12 @@ pub async fn user_edit(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !scopes.contains(Scopes::PAYOUTS_WRITE) {
|
||||||
|
return Err(ApiError::Authentication(
|
||||||
|
AuthenticationError::InvalidCredentials,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
if !match payout_data.payout_wallet_type {
|
if !match payout_data.payout_wallet_type {
|
||||||
RecipientType::Email => {
|
RecipientType::Email => {
|
||||||
validator::validate_email(&payout_data.payout_address)
|
validator::validate_email(&payout_data.payout_address)
|
||||||
@@ -401,6 +406,12 @@ pub async fn user_edit(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some((old_password, new_password)) = &new_user.password {
|
if let Some((old_password, new_password)) = &new_user.password {
|
||||||
|
if !scopes.contains(Scopes::USER_AUTH_WRITE) {
|
||||||
|
return Err(ApiError::Authentication(
|
||||||
|
AuthenticationError::InvalidCredentials,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(pass) = actual_user.password {
|
if let Some(pass) = actual_user.password {
|
||||||
let old_password = old_password.as_ref().ok_or_else(|| {
|
let old_password = old_password.as_ref().ok_or_else(|| {
|
||||||
ApiError::CustomAuthentication(
|
ApiError::CustomAuthentication(
|
||||||
@@ -500,11 +511,19 @@ pub async fn user_icon_edit(
|
|||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||||
mut payload: web::Payload,
|
mut payload: web::Payload,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
if let Some(content_type) = crate::util::ext::get_image_content_type(&ext.ext) {
|
if let Some(content_type) = crate::util::ext::get_image_content_type(&ext.ext) {
|
||||||
let cdn_url = dotenvy::var("CDN_URL")?;
|
let cdn_url = dotenvy::var("CDN_URL")?;
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::USER_WRITE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
let id_option = User::get(&info.into_inner().0, &**pool, &redis).await?;
|
let id_option = User::get(&info.into_inner().0, &**pool, &redis).await?;
|
||||||
|
|
||||||
if let Some(actual_user) = id_option {
|
if let Some(actual_user) = id_option {
|
||||||
@@ -579,9 +598,17 @@ pub async fn user_delete(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
removal_type: web::Query<RemovalType>,
|
removal_type: web::Query<RemovalType>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::USER_DELETE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
let id_option = User::get(&info.into_inner().0, &**pool, &redis).await?;
|
let id_option = User::get(&info.into_inner().0, &**pool, &redis).await?;
|
||||||
|
|
||||||
if let Some(id) = id_option.map(|x| x.id) {
|
if let Some(id) = id_option.map(|x| x.id) {
|
||||||
@@ -619,9 +646,17 @@ pub async fn user_follows(
|
|||||||
info: web::Path<(String,)>,
|
info: web::Path<(String,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::USER_READ]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
let id_option = User::get(&info.into_inner().0, &**pool, &redis).await?;
|
let id_option = User::get(&info.into_inner().0, &**pool, &redis).await?;
|
||||||
|
|
||||||
if let Some(id) = id_option.map(|x| x.id) {
|
if let Some(id) = id_option.map(|x| x.id) {
|
||||||
@@ -667,9 +702,17 @@ pub async fn user_notifications(
|
|||||||
info: web::Path<(String,)>,
|
info: web::Path<(String,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::NOTIFICATION_READ]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
let id_option = User::get(&info.into_inner().0, &**pool, &redis).await?;
|
let id_option = User::get(&info.into_inner().0, &**pool, &redis).await?;
|
||||||
|
|
||||||
if let Some(id) = id_option.map(|x| x.id) {
|
if let Some(id) = id_option.map(|x| x.id) {
|
||||||
@@ -707,9 +750,17 @@ pub async fn user_payouts(
|
|||||||
info: web::Path<(String,)>,
|
info: web::Path<(String,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::PAYOUTS_READ]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
let id_option = User::get(&info.into_inner().0, &**pool, &redis).await?;
|
let id_option = User::get(&info.into_inner().0, &**pool, &redis).await?;
|
||||||
|
|
||||||
if let Some(id) = id_option.map(|x| x.id) {
|
if let Some(id) = id_option.map(|x| x.id) {
|
||||||
@@ -784,11 +835,19 @@ pub async fn user_payouts_request(
|
|||||||
data: web::Json<PayoutData>,
|
data: web::Json<PayoutData>,
|
||||||
payouts_queue: web::Data<Mutex<PayoutsQueue>>,
|
payouts_queue: web::Data<Mutex<PayoutsQueue>>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let mut payouts_queue = payouts_queue.lock().await;
|
let mut payouts_queue = payouts_queue.lock().await;
|
||||||
|
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::PAYOUTS_WRITE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
let id_option = User::get(&info.into_inner().0, &**pool, &redis).await?;
|
let id_option = User::get(&info.into_inner().0, &**pool, &redis).await?;
|
||||||
|
|
||||||
if let Some(id) = id_option.map(|x| x.id) {
|
if let Some(id) = id_option.map(|x| x.id) {
|
||||||
|
|||||||
@@ -8,12 +8,13 @@ use crate::database::models::version_item::{
|
|||||||
use crate::file_hosting::FileHost;
|
use crate::file_hosting::FileHost;
|
||||||
use crate::models::notifications::NotificationBody;
|
use crate::models::notifications::NotificationBody;
|
||||||
use crate::models::pack::PackFileHash;
|
use crate::models::pack::PackFileHash;
|
||||||
|
use crate::models::pats::Scopes;
|
||||||
use crate::models::projects::{
|
use crate::models::projects::{
|
||||||
Dependency, DependencyType, FileType, GameVersion, Loader, ProjectId, Version, VersionFile,
|
Dependency, DependencyType, FileType, GameVersion, Loader, ProjectId, Version, VersionFile,
|
||||||
VersionId, VersionStatus, VersionType,
|
VersionId, VersionStatus, VersionType,
|
||||||
};
|
};
|
||||||
use crate::models::teams::Permissions;
|
use crate::models::teams::Permissions;
|
||||||
use crate::queue::session::SessionQueue;
|
use crate::queue::session::AuthQueue;
|
||||||
use crate::util::routes::read_from_field;
|
use crate::util::routes::read_from_field;
|
||||||
use crate::util::validate::validation_errors_to_string;
|
use crate::util::validate::validation_errors_to_string;
|
||||||
use crate::validate::{validate_file, ValidationResult};
|
use crate::validate::{validate_file, ValidationResult};
|
||||||
@@ -85,7 +86,7 @@ pub async fn version_create(
|
|||||||
client: Data<PgPool>,
|
client: Data<PgPool>,
|
||||||
redis: Data<deadpool_redis::Pool>,
|
redis: Data<deadpool_redis::Pool>,
|
||||||
file_host: Data<Arc<dyn FileHost + Send + Sync>>,
|
file_host: Data<Arc<dyn FileHost + Send + Sync>>,
|
||||||
session_queue: Data<SessionQueue>,
|
session_queue: Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, CreateError> {
|
) -> Result<HttpResponse, CreateError> {
|
||||||
let mut transaction = client.begin().await?;
|
let mut transaction = client.begin().await?;
|
||||||
let mut uploaded_files = Vec::new();
|
let mut uploaded_files = Vec::new();
|
||||||
@@ -127,7 +128,7 @@ async fn version_create_inner(
|
|||||||
file_host: &dyn FileHost,
|
file_host: &dyn FileHost,
|
||||||
uploaded_files: &mut Vec<UploadedFile>,
|
uploaded_files: &mut Vec<UploadedFile>,
|
||||||
pool: &PgPool,
|
pool: &PgPool,
|
||||||
session_queue: &SessionQueue,
|
session_queue: &AuthQueue,
|
||||||
) -> Result<HttpResponse, CreateError> {
|
) -> Result<HttpResponse, CreateError> {
|
||||||
let cdn_url = dotenvy::var("CDN_URL")?;
|
let cdn_url = dotenvy::var("CDN_URL")?;
|
||||||
|
|
||||||
@@ -137,7 +138,15 @@ async fn version_create_inner(
|
|||||||
let all_game_versions = models::categories::GameVersion::list(&mut *transaction).await?;
|
let all_game_versions = models::categories::GameVersion::list(&mut *transaction).await?;
|
||||||
let all_loaders = models::categories::Loader::list(&mut *transaction).await?;
|
let all_loaders = models::categories::Loader::list(&mut *transaction).await?;
|
||||||
|
|
||||||
let user = get_user_from_headers(&req, pool, redis, session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
pool,
|
||||||
|
redis,
|
||||||
|
session_queue,
|
||||||
|
Some(&[Scopes::VERSION_CREATE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
|
|
||||||
let mut error = None;
|
let mut error = None;
|
||||||
while let Some(item) = payload.next().await {
|
while let Some(item) = payload.next().await {
|
||||||
@@ -441,7 +450,7 @@ pub async fn upload_file_to_version(
|
|||||||
client: Data<PgPool>,
|
client: Data<PgPool>,
|
||||||
redis: Data<deadpool_redis::Pool>,
|
redis: Data<deadpool_redis::Pool>,
|
||||||
file_host: Data<Arc<dyn FileHost + Send + Sync>>,
|
file_host: Data<Arc<dyn FileHost + Send + Sync>>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, CreateError> {
|
) -> Result<HttpResponse, CreateError> {
|
||||||
let mut transaction = client.begin().await?;
|
let mut transaction = client.begin().await?;
|
||||||
let mut uploaded_files = Vec::new();
|
let mut uploaded_files = Vec::new();
|
||||||
@@ -487,14 +496,22 @@ async fn upload_file_to_version_inner(
|
|||||||
file_host: &dyn FileHost,
|
file_host: &dyn FileHost,
|
||||||
uploaded_files: &mut Vec<UploadedFile>,
|
uploaded_files: &mut Vec<UploadedFile>,
|
||||||
version_id: models::VersionId,
|
version_id: models::VersionId,
|
||||||
session_queue: &SessionQueue,
|
session_queue: &AuthQueue,
|
||||||
) -> Result<HttpResponse, CreateError> {
|
) -> Result<HttpResponse, CreateError> {
|
||||||
let cdn_url = dotenvy::var("CDN_URL")?;
|
let cdn_url = dotenvy::var("CDN_URL")?;
|
||||||
|
|
||||||
let mut initial_file_data: Option<InitialFileData> = None;
|
let mut initial_file_data: Option<InitialFileData> = None;
|
||||||
let mut file_builders: Vec<VersionFileBuilder> = Vec::new();
|
let mut file_builders: Vec<VersionFileBuilder> = Vec::new();
|
||||||
|
|
||||||
let user = get_user_from_headers(&req, &**client, &redis, session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**client,
|
||||||
|
&redis,
|
||||||
|
session_queue,
|
||||||
|
Some(&[Scopes::VERSION_WRITE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
|
|
||||||
let result = models::Version::get(version_id, &**client, &redis).await?;
|
let result = models::Version::get(version_id, &**client, &redis).await?;
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ use crate::auth::{
|
|||||||
is_authorized_version,
|
is_authorized_version,
|
||||||
};
|
};
|
||||||
use crate::models::ids::VersionId;
|
use crate::models::ids::VersionId;
|
||||||
|
use crate::models::pats::Scopes;
|
||||||
use crate::models::projects::VersionType;
|
use crate::models::projects::VersionType;
|
||||||
use crate::models::teams::Permissions;
|
use crate::models::teams::Permissions;
|
||||||
use crate::queue::session::SessionQueue;
|
use crate::queue::session::AuthQueue;
|
||||||
use crate::{database, models};
|
use crate::{database, models};
|
||||||
use actix_web::{delete, get, post, web, HttpRequest, HttpResponse};
|
use actix_web::{delete, get, post, web, HttpRequest, HttpResponse};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
@@ -49,11 +50,18 @@ pub async fn get_version_from_hash(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
hash_query: web::Query<HashQuery>,
|
hash_query: web::Query<HashQuery>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
let user_option = get_user_from_headers(
|
||||||
.await
|
&req,
|
||||||
.ok();
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::VERSION_READ]),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|x| x.1)
|
||||||
|
.ok();
|
||||||
|
|
||||||
let hash = info.into_inner().0.to_lowercase();
|
let hash = info.into_inner().0.to_lowercase();
|
||||||
let file = database::models::Version::get_file_from_hash(
|
let file = database::models::Version::get_file_from_hash(
|
||||||
@@ -95,11 +103,18 @@ pub async fn download_version(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
hash_query: web::Query<HashQuery>,
|
hash_query: web::Query<HashQuery>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
let user_option = get_user_from_headers(
|
||||||
.await
|
&req,
|
||||||
.ok();
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::VERSION_READ]),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|x| x.1)
|
||||||
|
.ok();
|
||||||
|
|
||||||
let hash = info.into_inner().0.to_lowercase();
|
let hash = info.into_inner().0.to_lowercase();
|
||||||
let file = database::models::Version::get_file_from_hash(
|
let file = database::models::Version::get_file_from_hash(
|
||||||
@@ -138,9 +153,17 @@ pub async fn delete_file(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
hash_query: web::Query<HashQuery>,
|
hash_query: web::Query<HashQuery>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::VERSION_WRITE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
|
|
||||||
let hash = info.into_inner().0.to_lowercase();
|
let hash = info.into_inner().0.to_lowercase();
|
||||||
|
|
||||||
@@ -234,11 +257,18 @@ pub async fn get_update_from_hash(
|
|||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
hash_query: web::Query<HashQuery>,
|
hash_query: web::Query<HashQuery>,
|
||||||
update_data: web::Json<UpdateData>,
|
update_data: web::Json<UpdateData>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
let user_option = get_user_from_headers(
|
||||||
.await
|
&req,
|
||||||
.ok();
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::VERSION_READ]),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|x| x.1)
|
||||||
|
.ok();
|
||||||
let hash = info.into_inner().0.to_lowercase();
|
let hash = info.into_inner().0.to_lowercase();
|
||||||
|
|
||||||
if let Some(file) = database::models::Version::get_file_from_hash(
|
if let Some(file) = database::models::Version::get_file_from_hash(
|
||||||
@@ -304,11 +334,18 @@ pub async fn get_versions_from_hashes(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
file_data: web::Json<FileHashes>,
|
file_data: web::Json<FileHashes>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
let user_option = get_user_from_headers(
|
||||||
.await
|
&req,
|
||||||
.ok();
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::VERSION_READ]),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|x| x.1)
|
||||||
|
.ok();
|
||||||
|
|
||||||
let files = database::models::Version::get_files_from_hash(
|
let files = database::models::Version::get_files_from_hash(
|
||||||
file_data.algorithm.clone(),
|
file_data.algorithm.clone(),
|
||||||
@@ -345,11 +382,18 @@ pub async fn get_projects_from_hashes(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
file_data: web::Json<FileHashes>,
|
file_data: web::Json<FileHashes>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
let user_option = get_user_from_headers(
|
||||||
.await
|
&req,
|
||||||
.ok();
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::VERSION_READ]),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|x| x.1)
|
||||||
|
.ok();
|
||||||
|
|
||||||
let files = database::models::Version::get_files_from_hash(
|
let files = database::models::Version::get_files_from_hash(
|
||||||
file_data.algorithm.clone(),
|
file_data.algorithm.clone(),
|
||||||
@@ -396,11 +440,18 @@ pub async fn update_files(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
update_data: web::Json<ManyUpdateData>,
|
update_data: web::Json<ManyUpdateData>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
let user_option = get_user_from_headers(
|
||||||
.await
|
&req,
|
||||||
.ok();
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::VERSION_READ]),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|x| x.1)
|
||||||
|
.ok();
|
||||||
|
|
||||||
let files = database::models::Version::get_files_from_hash(
|
let files = database::models::Version::get_files_from_hash(
|
||||||
update_data.algorithm.clone(),
|
update_data.algorithm.clone(),
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ use crate::auth::{
|
|||||||
};
|
};
|
||||||
use crate::database;
|
use crate::database;
|
||||||
use crate::models;
|
use crate::models;
|
||||||
|
use crate::models::pats::Scopes;
|
||||||
use crate::models::projects::{Dependency, FileType, VersionStatus, VersionType};
|
use crate::models::projects::{Dependency, FileType, VersionStatus, VersionType};
|
||||||
use crate::models::teams::Permissions;
|
use crate::models::teams::Permissions;
|
||||||
use crate::queue::session::SessionQueue;
|
use crate::queue::session::AuthQueue;
|
||||||
use crate::util::validate::validation_errors_to_string;
|
use crate::util::validate::validation_errors_to_string;
|
||||||
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
|
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
@@ -45,15 +46,22 @@ pub async fn version_list(
|
|||||||
web::Query(filters): web::Query<VersionListFilters>,
|
web::Query(filters): web::Query<VersionListFilters>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let result = database::models::Project::get(&string, &**pool, &redis).await?;
|
let result = database::models::Project::get(&string, &**pool, &redis).await?;
|
||||||
|
|
||||||
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
let user_option = get_user_from_headers(
|
||||||
.await
|
&req,
|
||||||
.ok();
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::PROJECT_READ, Scopes::VERSION_READ]),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|x| x.1)
|
||||||
|
.ok();
|
||||||
|
|
||||||
if let Some(project) = result {
|
if let Some(project) = result {
|
||||||
if !is_authorized(&project.inner, &user_option, &pool).await? {
|
if !is_authorized(&project.inner, &user_option, &pool).await? {
|
||||||
@@ -154,15 +162,22 @@ pub async fn version_project_get(
|
|||||||
info: web::Path<(String, String)>,
|
info: web::Path<(String, String)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let id = info.into_inner();
|
let id = info.into_inner();
|
||||||
let version_data =
|
let version_data =
|
||||||
database::models::Version::get_full_from_id_slug(&id.0, &id.1, &**pool, &redis).await?;
|
database::models::Version::get_full_from_id_slug(&id.0, &id.1, &**pool, &redis).await?;
|
||||||
|
|
||||||
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
let user_option = get_user_from_headers(
|
||||||
.await
|
&req,
|
||||||
.ok();
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::PROJECT_READ, Scopes::VERSION_READ]),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|x| x.1)
|
||||||
|
.ok();
|
||||||
|
|
||||||
if let Some(data) = version_data {
|
if let Some(data) = version_data {
|
||||||
if is_authorized_version(&data.inner, &user_option, &pool).await? {
|
if is_authorized_version(&data.inner, &user_option, &pool).await? {
|
||||||
@@ -184,7 +199,7 @@ pub async fn versions_get(
|
|||||||
web::Query(ids): web::Query<VersionIds>,
|
web::Query(ids): web::Query<VersionIds>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let version_ids = serde_json::from_str::<Vec<models::ids::VersionId>>(&ids.ids)?
|
let version_ids = serde_json::from_str::<Vec<models::ids::VersionId>>(&ids.ids)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -192,9 +207,16 @@ pub async fn versions_get(
|
|||||||
.collect::<Vec<database::models::VersionId>>();
|
.collect::<Vec<database::models::VersionId>>();
|
||||||
let versions_data = database::models::Version::get_many(&version_ids, &**pool, &redis).await?;
|
let versions_data = database::models::Version::get_many(&version_ids, &**pool, &redis).await?;
|
||||||
|
|
||||||
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
let user_option = get_user_from_headers(
|
||||||
.await
|
&req,
|
||||||
.ok();
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::VERSION_READ]),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|x| x.1)
|
||||||
|
.ok();
|
||||||
|
|
||||||
let versions = filter_authorized_versions(versions_data, &user_option, &pool).await?;
|
let versions = filter_authorized_versions(versions_data, &user_option, &pool).await?;
|
||||||
|
|
||||||
@@ -207,14 +229,21 @@ pub async fn version_get(
|
|||||||
info: web::Path<(models::ids::VersionId,)>,
|
info: web::Path<(models::ids::VersionId,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let id = info.into_inner().0;
|
let id = info.into_inner().0;
|
||||||
let version_data = database::models::Version::get(id.into(), &**pool, &redis).await?;
|
let version_data = database::models::Version::get(id.into(), &**pool, &redis).await?;
|
||||||
|
|
||||||
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
let user_option = get_user_from_headers(
|
||||||
.await
|
&req,
|
||||||
.ok();
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::VERSION_READ]),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|x| x.1)
|
||||||
|
.ok();
|
||||||
|
|
||||||
if let Some(data) = version_data {
|
if let Some(data) = version_data {
|
||||||
if is_authorized_version(&data.inner, &user_option, &pool).await? {
|
if is_authorized_version(&data.inner, &user_option, &pool).await? {
|
||||||
@@ -268,9 +297,17 @@ pub async fn version_edit(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
new_version: web::Json<EditVersion>,
|
new_version: web::Json<EditVersion>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::VERSION_WRITE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
|
|
||||||
new_version
|
new_version
|
||||||
.validate()
|
.validate()
|
||||||
@@ -645,9 +682,17 @@ pub async fn version_schedule(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
scheduling_data: web::Json<SchedulingData>,
|
scheduling_data: web::Json<SchedulingData>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::VERSION_WRITE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
|
|
||||||
if scheduling_data.time < Utc::now() {
|
if scheduling_data.time < Utc::now() {
|
||||||
return Err(ApiError::InvalidInput(
|
return Err(ApiError::InvalidInput(
|
||||||
@@ -711,9 +756,17 @@ pub async fn version_delete(
|
|||||||
info: web::Path<(models::ids::VersionId,)>,
|
info: web::Path<(models::ids::VersionId,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
session_queue: web::Data<SessionQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
let user = get_user_from_headers(
|
||||||
|
&req,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
&session_queue,
|
||||||
|
Some(&[Scopes::VERSION_DELETE]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.1;
|
||||||
let id = info.into_inner().0;
|
let id = info.into_inner().0;
|
||||||
|
|
||||||
let version = database::models::Version::get(id.into(), &**pool, &redis)
|
let version = database::models::Version::get(id.into(), &**pool, &redis)
|
||||||
|
|||||||
Reference in New Issue
Block a user