You've already forked AstralRinth
forked from didirus/AstralRinth
Tests (#719)
* computer switch * some fixes; github action * added pr to master * sqlx database setup * switched intial GHA test db * removed sqlx database setup * unfinished patch route * bug fixes + tests * more tests, more fixes, cargo fmt * merge fixes * more tests, full reorganization * fmt, clippy * sqlx-data * revs * removed comments * delete revs
This commit is contained in:
@@ -3,16 +3,17 @@ use crate::auth::session::issue_session;
|
||||
use crate::auth::validate::get_user_record_from_bearer_token;
|
||||
use crate::auth::{get_user_from_headers, AuthenticationError};
|
||||
use crate::database::models::flow_item::Flow;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::file_hosting::FileHost;
|
||||
use crate::models::ids::base62_impl::{parse_base62, to_base62};
|
||||
use crate::models::ids::random_base62_rng;
|
||||
use crate::models::pats::Scopes;
|
||||
use crate::models::users::{Badges, Role};
|
||||
use crate::parse_strings_from_var;
|
||||
use crate::queue::session::AuthQueue;
|
||||
use crate::queue::socket::ActiveSockets;
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::captcha::check_turnstile_captcha;
|
||||
use crate::util::env::parse_strings_from_var;
|
||||
use crate::util::ext::{get_image_content_type, get_image_ext};
|
||||
use crate::util::validate::{validation_errors_to_string, RE_URL_SAFE};
|
||||
use actix_web::web::{scope, Data, Payload, Query, ServiceConfig};
|
||||
@@ -54,7 +55,7 @@ pub fn config(cfg: &mut ServiceConfig) {
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Eq, PartialEq, Clone, Copy)]
|
||||
#[derive(Serialize, Deserialize, Default, Eq, PartialEq, Clone, Copy, Debug)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum AuthProvider {
|
||||
#[default]
|
||||
@@ -84,7 +85,7 @@ impl TempUser {
|
||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
client: &PgPool,
|
||||
file_host: &Arc<dyn FileHost + Send + Sync>,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<crate::database::models::UserId, AuthenticationError> {
|
||||
if let Some(email) = &self.email {
|
||||
if crate::database::models::User::get_email(email, client)
|
||||
@@ -907,7 +908,7 @@ pub async fn init(
|
||||
req: HttpRequest,
|
||||
Query(info): Query<AuthorizationInit>, // callback url
|
||||
client: Data<PgPool>,
|
||||
redis: Data<deadpool_redis::Pool>,
|
||||
redis: Data<RedisPool>,
|
||||
session_queue: Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, AuthenticationError> {
|
||||
let url = url::Url::parse(&info.url).map_err(|_| AuthenticationError::Url)?;
|
||||
@@ -959,7 +960,7 @@ pub async fn ws_init(
|
||||
Query(info): Query<WsInit>,
|
||||
body: Payload,
|
||||
db: Data<RwLock<ActiveSockets>>,
|
||||
redis: Data<deadpool_redis::Pool>,
|
||||
redis: Data<RedisPool>,
|
||||
) -> Result<HttpResponse, actix_web::Error> {
|
||||
let (res, session, _msg_stream) = actix_ws::handle(&req, body)?;
|
||||
|
||||
@@ -967,7 +968,7 @@ pub async fn ws_init(
|
||||
mut ws_stream: actix_ws::Session,
|
||||
info: WsInit,
|
||||
db: Data<RwLock<ActiveSockets>>,
|
||||
redis: Data<deadpool_redis::Pool>,
|
||||
redis: Data<RedisPool>,
|
||||
) -> Result<(), Closed> {
|
||||
let flow = Flow::OAuth {
|
||||
user_id: None,
|
||||
@@ -1003,7 +1004,7 @@ pub async fn auth_callback(
|
||||
active_sockets: Data<RwLock<ActiveSockets>>,
|
||||
client: Data<PgPool>,
|
||||
file_host: Data<Arc<dyn FileHost + Send + Sync>>,
|
||||
redis: Data<deadpool_redis::Pool>,
|
||||
redis: Data<RedisPool>,
|
||||
) -> Result<HttpResponse, super::templates::ErrorPage> {
|
||||
let state_string = query
|
||||
.get("state")
|
||||
@@ -1210,7 +1211,7 @@ pub struct DeleteAuthProvider {
|
||||
pub async fn delete_auth_provider(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
redis: Data<deadpool_redis::Pool>,
|
||||
redis: Data<RedisPool>,
|
||||
delete_provider: web::Json<DeleteAuthProvider>,
|
||||
session_queue: Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
@@ -1297,7 +1298,7 @@ pub struct NewAccount {
|
||||
pub async fn create_account_with_password(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
redis: Data<deadpool_redis::Pool>,
|
||||
redis: Data<RedisPool>,
|
||||
new_account: web::Json<NewAccount>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
new_account
|
||||
@@ -1414,7 +1415,7 @@ pub struct Login {
|
||||
pub async fn login_password(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
redis: Data<deadpool_redis::Pool>,
|
||||
redis: Data<RedisPool>,
|
||||
login: web::Json<Login>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
if !check_turnstile_captcha(&req, &login.challenge).await? {
|
||||
@@ -1478,7 +1479,7 @@ async fn validate_2fa_code(
|
||||
secret: String,
|
||||
allow_backup: bool,
|
||||
user_id: crate::database::models::UserId,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
pool: &PgPool,
|
||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<bool, AuthenticationError> {
|
||||
@@ -1530,7 +1531,7 @@ async fn validate_2fa_code(
|
||||
pub async fn login_2fa(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
redis: Data<deadpool_redis::Pool>,
|
||||
redis: Data<RedisPool>,
|
||||
login: web::Json<Login2FA>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let flow = Flow::get(&login.flow, &redis)
|
||||
@@ -1577,7 +1578,7 @@ pub async fn login_2fa(
|
||||
pub async fn begin_2fa_flow(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
redis: Data<deadpool_redis::Pool>,
|
||||
redis: Data<RedisPool>,
|
||||
session_queue: Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(
|
||||
@@ -1616,7 +1617,7 @@ pub async fn begin_2fa_flow(
|
||||
pub async fn finish_2fa_flow(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
redis: Data<deadpool_redis::Pool>,
|
||||
redis: Data<RedisPool>,
|
||||
login: web::Json<Login2FA>,
|
||||
session_queue: Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
@@ -1739,7 +1740,7 @@ pub struct Remove2FA {
|
||||
pub async fn remove_2fa(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
redis: Data<deadpool_redis::Pool>,
|
||||
redis: Data<RedisPool>,
|
||||
login: web::Json<Remove2FA>,
|
||||
session_queue: Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
@@ -1821,7 +1822,7 @@ pub struct ResetPassword {
|
||||
pub async fn reset_password_begin(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
redis: Data<deadpool_redis::Pool>,
|
||||
redis: Data<RedisPool>,
|
||||
reset_password: web::Json<ResetPassword>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
if !check_turnstile_captcha(&req, &reset_password.challenge).await? {
|
||||
@@ -1866,7 +1867,7 @@ pub struct ChangePassword {
|
||||
pub async fn change_password(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
redis: Data<deadpool_redis::Pool>,
|
||||
redis: Data<RedisPool>,
|
||||
change_password: web::Json<ChangePassword>,
|
||||
session_queue: Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
@@ -2007,7 +2008,7 @@ pub struct SetEmail {
|
||||
pub async fn set_email(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
redis: Data<deadpool_redis::Pool>,
|
||||
redis: Data<RedisPool>,
|
||||
email: web::Json<SetEmail>,
|
||||
session_queue: Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
@@ -2073,7 +2074,7 @@ pub async fn set_email(
|
||||
pub async fn resend_verify_email(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
redis: Data<deadpool_redis::Pool>,
|
||||
redis: Data<RedisPool>,
|
||||
session_queue: Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(
|
||||
@@ -2118,7 +2119,7 @@ pub struct VerifyEmail {
|
||||
#[post("email/verify")]
|
||||
pub async fn verify_email(
|
||||
pool: Data<PgPool>,
|
||||
redis: Data<deadpool_redis::Pool>,
|
||||
redis: Data<RedisPool>,
|
||||
email: web::Json<VerifyEmail>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let flow = Flow::get(&email.flow, &redis).await?;
|
||||
@@ -2168,7 +2169,7 @@ pub async fn verify_email(
|
||||
pub async fn subscribe_newsletter(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
redis: Data<deadpool_redis::Pool>,
|
||||
redis: Data<RedisPool>,
|
||||
session_queue: Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::database::models::generate_pat_id;
|
||||
use crate::auth::get_user_from_headers;
|
||||
use crate::routes::ApiError;
|
||||
|
||||
use crate::database::redis::RedisPool;
|
||||
use actix_web::web::{self, Data};
|
||||
use actix_web::{delete, get, patch, post, HttpRequest, HttpResponse};
|
||||
use chrono::{DateTime, Utc};
|
||||
@@ -30,7 +31,7 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
pub async fn get_pats(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
redis: Data<deadpool_redis::Pool>,
|
||||
redis: Data<RedisPool>,
|
||||
session_queue: Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(
|
||||
@@ -73,14 +74,14 @@ pub async fn create_pat(
|
||||
req: HttpRequest,
|
||||
info: web::Json<NewPersonalAccessToken>,
|
||||
pool: Data<PgPool>,
|
||||
redis: Data<deadpool_redis::Pool>,
|
||||
redis: Data<RedisPool>,
|
||||
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() {
|
||||
if info.scopes.is_restricted() {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"Invalid scopes requested!".to_string(),
|
||||
));
|
||||
@@ -159,7 +160,7 @@ pub async fn edit_pat(
|
||||
id: web::Path<(String,)>,
|
||||
info: web::Json<ModifyPersonalAccessToken>,
|
||||
pool: Data<PgPool>,
|
||||
redis: Data<deadpool_redis::Pool>,
|
||||
redis: Data<RedisPool>,
|
||||
session_queue: Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(
|
||||
@@ -180,7 +181,7 @@ pub async fn edit_pat(
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
if let Some(scopes) = &info.scopes {
|
||||
if scopes.restricted() {
|
||||
if scopes.is_restricted() {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"Invalid scopes requested!".to_string(),
|
||||
));
|
||||
@@ -248,7 +249,7 @@ pub async fn delete_pat(
|
||||
req: HttpRequest,
|
||||
id: web::Path<(String,)>,
|
||||
pool: Data<PgPool>,
|
||||
redis: Data<deadpool_redis::Pool>,
|
||||
redis: Data<RedisPool>,
|
||||
session_queue: Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::auth::{get_user_from_headers, AuthenticationError};
|
||||
use crate::database::models::session_item::Session as DBSession;
|
||||
use crate::database::models::session_item::SessionBuilder;
|
||||
use crate::database::models::UserId;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::pats::Scopes;
|
||||
use crate::models::sessions::Session;
|
||||
use crate::queue::session::AuthQueue;
|
||||
@@ -86,7 +87,7 @@ pub async fn issue_session(
|
||||
req: HttpRequest,
|
||||
user_id: UserId,
|
||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<DBSession, AuthenticationError> {
|
||||
let metadata = get_session_metadata(&req).await?;
|
||||
|
||||
@@ -132,7 +133,7 @@ pub async fn issue_session(
|
||||
pub async fn list(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
redis: Data<deadpool_redis::Pool>,
|
||||
redis: Data<RedisPool>,
|
||||
session_queue: Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let current_user = get_user_from_headers(
|
||||
@@ -167,7 +168,7 @@ pub async fn delete(
|
||||
info: web::Path<(String,)>,
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
redis: Data<deadpool_redis::Pool>,
|
||||
redis: Data<RedisPool>,
|
||||
session_queue: Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let current_user = get_user_from_headers(
|
||||
@@ -206,7 +207,7 @@ pub async fn delete(
|
||||
pub async fn refresh(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
redis: Data<deadpool_redis::Pool>,
|
||||
redis: Data<RedisPool>,
|
||||
session_queue: Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let current_user = get_user_from_headers(&req, &**pool, &redis, &session_queue, None)
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::auth::flows::AuthProvider;
|
||||
use crate::auth::session::get_session_metadata;
|
||||
use crate::auth::AuthenticationError;
|
||||
use crate::database::models::user_item;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::pats::Scopes;
|
||||
use crate::models::users::{Role, User, UserId, UserPayoutData};
|
||||
use crate::queue::session::AuthQueue;
|
||||
@@ -12,7 +13,7 @@ use reqwest::header::{HeaderValue, AUTHORIZATION};
|
||||
pub async fn get_user_from_headers<'a, E>(
|
||||
req: &HttpRequest,
|
||||
executor: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
session_queue: &AuthQueue,
|
||||
required_scopes: Option<&[Scopes]>,
|
||||
) -> Result<(Scopes, User), AuthenticationError>
|
||||
@@ -82,7 +83,7 @@ pub async fn get_user_record_from_bearer_token<'a, 'b, E>(
|
||||
req: &HttpRequest,
|
||||
token: Option<&str>,
|
||||
executor: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
session_queue: &AuthQueue,
|
||||
) -> Result<Option<(Scopes, user_item::User)>, AuthenticationError>
|
||||
where
|
||||
@@ -140,7 +141,7 @@ where
|
||||
session_queue.add_session(session.id, metadata).await;
|
||||
}
|
||||
|
||||
user.map(|x| (Scopes::ALL, x))
|
||||
user.map(|x| (Scopes::all(), x))
|
||||
}
|
||||
Some(("github", _)) | Some(("gho", _)) | Some(("ghp", _)) => {
|
||||
let user = AuthProvider::GitHub.get_user(token).await?;
|
||||
@@ -153,7 +154,7 @@ where
|
||||
)
|
||||
.await?;
|
||||
|
||||
user.map(|x| (Scopes::NOT_RESTRICTED, x))
|
||||
user.map(|x| ((Scopes::all() ^ Scopes::restricted()), x))
|
||||
}
|
||||
_ => return Err(AuthenticationError::InvalidAuthMethod),
|
||||
};
|
||||
@@ -163,13 +164,14 @@ where
|
||||
pub async fn check_is_moderator_from_headers<'a, 'b, E>(
|
||||
req: &HttpRequest,
|
||||
executor: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
session_queue: &AuthQueue,
|
||||
required_scopes: Option<&[Scopes]>,
|
||||
) -> Result<User, AuthenticationError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||
{
|
||||
let user = get_user_from_headers(req, executor, redis, session_queue, None)
|
||||
let user = get_user_from_headers(req, executor, redis, session_queue, required_scopes)
|
||||
.await?
|
||||
.1;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pub mod models;
|
||||
mod postgres_database;
|
||||
pub mod redis;
|
||||
pub use models::Image;
|
||||
pub use models::Project;
|
||||
pub use models::Version;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use crate::database::redis::RedisPool;
|
||||
|
||||
use super::ids::*;
|
||||
use super::DatabaseError;
|
||||
use chrono::DateTime;
|
||||
use chrono::Utc;
|
||||
use futures::TryStreamExt;
|
||||
use redis::cmd;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const TAGS_NAMESPACE: &str = "tags";
|
||||
const DEFAULT_EXPIRY: i64 = 1800; // 30 minutes
|
||||
|
||||
pub struct ProjectType {
|
||||
pub id: ProjectTypeId,
|
||||
@@ -98,17 +98,12 @@ impl Category {
|
||||
Ok(result.map(|r| CategoryId(r.id)))
|
||||
}
|
||||
|
||||
pub async fn list<'a, E>(
|
||||
exec: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
) -> Result<Vec<Category>, DatabaseError>
|
||||
pub async fn list<'a, E>(exec: E, redis: &RedisPool) -> Result<Vec<Category>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
let mut redis = redis.get().await?;
|
||||
let res = cmd("GET")
|
||||
.arg(format!("{}:category", TAGS_NAMESPACE))
|
||||
.query_async::<_, Option<String>>(&mut redis)
|
||||
let res = redis
|
||||
.get::<String, _>(TAGS_NAMESPACE, "category")
|
||||
.await?
|
||||
.and_then(|x| serde_json::from_str::<Vec<Category>>(&x).ok());
|
||||
|
||||
@@ -137,12 +132,13 @@ impl Category {
|
||||
.try_collect::<Vec<Category>>()
|
||||
.await?;
|
||||
|
||||
cmd("SET")
|
||||
.arg(format!("{}:category", TAGS_NAMESPACE))
|
||||
.arg(serde_json::to_string(&result)?)
|
||||
.arg("EX")
|
||||
.arg(DEFAULT_EXPIRY)
|
||||
.query_async::<_, ()>(&mut redis)
|
||||
redis
|
||||
.set(
|
||||
TAGS_NAMESPACE,
|
||||
"category",
|
||||
serde_json::to_string(&result)?,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(result)
|
||||
@@ -167,17 +163,12 @@ impl Loader {
|
||||
Ok(result.map(|r| LoaderId(r.id)))
|
||||
}
|
||||
|
||||
pub async fn list<'a, E>(
|
||||
exec: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
) -> Result<Vec<Loader>, DatabaseError>
|
||||
pub async fn list<'a, E>(exec: E, redis: &RedisPool) -> Result<Vec<Loader>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
let mut redis = redis.get().await?;
|
||||
let res = cmd("GET")
|
||||
.arg(format!("{}:loader", TAGS_NAMESPACE))
|
||||
.query_async::<_, Option<String>>(&mut redis)
|
||||
let res = redis
|
||||
.get::<String, _>(TAGS_NAMESPACE, "loader")
|
||||
.await?
|
||||
.and_then(|x| serde_json::from_str::<Vec<Loader>>(&x).ok());
|
||||
|
||||
@@ -212,12 +203,13 @@ impl Loader {
|
||||
.try_collect::<Vec<_>>()
|
||||
.await?;
|
||||
|
||||
cmd("SET")
|
||||
.arg(format!("{}:loader", TAGS_NAMESPACE))
|
||||
.arg(serde_json::to_string(&result)?)
|
||||
.arg("EX")
|
||||
.arg(DEFAULT_EXPIRY)
|
||||
.query_async::<_, ()>(&mut redis)
|
||||
redis
|
||||
.set(
|
||||
TAGS_NAMESPACE,
|
||||
"loader",
|
||||
serde_json::to_string(&result)?,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(result)
|
||||
@@ -256,17 +248,12 @@ impl GameVersion {
|
||||
Ok(result.map(|r| GameVersionId(r.id)))
|
||||
}
|
||||
|
||||
pub async fn list<'a, E>(
|
||||
exec: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
) -> Result<Vec<GameVersion>, DatabaseError>
|
||||
pub async fn list<'a, E>(exec: E, redis: &RedisPool) -> Result<Vec<GameVersion>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
let mut redis = redis.get().await?;
|
||||
let res = cmd("GET")
|
||||
.arg(format!("{}:game_version", TAGS_NAMESPACE))
|
||||
.query_async::<_, Option<String>>(&mut redis)
|
||||
let res = redis
|
||||
.get::<String, _>(TAGS_NAMESPACE, "game_version")
|
||||
.await?
|
||||
.and_then(|x| serde_json::from_str::<Vec<GameVersion>>(&x).ok());
|
||||
|
||||
@@ -291,14 +278,14 @@ impl GameVersion {
|
||||
.try_collect::<Vec<GameVersion>>()
|
||||
.await?;
|
||||
|
||||
cmd("SET")
|
||||
.arg(format!("{}:game_version", TAGS_NAMESPACE))
|
||||
.arg(serde_json::to_string(&result)?)
|
||||
.arg("EX")
|
||||
.arg(DEFAULT_EXPIRY)
|
||||
.query_async::<_, ()>(&mut redis)
|
||||
redis
|
||||
.set(
|
||||
TAGS_NAMESPACE,
|
||||
"game_version",
|
||||
serde_json::to_string(&result)?,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
@@ -306,7 +293,7 @@ impl GameVersion {
|
||||
version_type_option: Option<&str>,
|
||||
major_option: Option<bool>,
|
||||
exec: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Vec<GameVersion>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
@@ -408,15 +395,13 @@ impl DonationPlatform {
|
||||
|
||||
pub async fn list<'a, E>(
|
||||
exec: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Vec<DonationPlatform>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
let mut redis = redis.get().await?;
|
||||
let res = cmd("GET")
|
||||
.arg(format!("{}:donation_platform", TAGS_NAMESPACE))
|
||||
.query_async::<_, Option<String>>(&mut redis)
|
||||
let res = redis
|
||||
.get::<String, _>(TAGS_NAMESPACE, "donation_platform")
|
||||
.await?
|
||||
.and_then(|x| serde_json::from_str::<Vec<DonationPlatform>>(&x).ok());
|
||||
|
||||
@@ -440,12 +425,13 @@ impl DonationPlatform {
|
||||
.try_collect::<Vec<DonationPlatform>>()
|
||||
.await?;
|
||||
|
||||
cmd("SET")
|
||||
.arg(format!("{}:donation_platform", TAGS_NAMESPACE))
|
||||
.arg(serde_json::to_string(&result)?)
|
||||
.arg("EX")
|
||||
.arg(DEFAULT_EXPIRY)
|
||||
.query_async::<_, ()>(&mut redis)
|
||||
redis
|
||||
.set(
|
||||
TAGS_NAMESPACE,
|
||||
"donation_platform",
|
||||
serde_json::to_string(&result)?,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(result)
|
||||
@@ -470,17 +456,12 @@ impl ReportType {
|
||||
Ok(result.map(|r| ReportTypeId(r.id)))
|
||||
}
|
||||
|
||||
pub async fn list<'a, E>(
|
||||
exec: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
) -> Result<Vec<String>, DatabaseError>
|
||||
pub async fn list<'a, E>(exec: E, redis: &RedisPool) -> Result<Vec<String>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
let mut redis = redis.get().await?;
|
||||
let res = cmd("GET")
|
||||
.arg(format!("{}:report_type", TAGS_NAMESPACE))
|
||||
.query_async::<_, Option<String>>(&mut redis)
|
||||
let res = redis
|
||||
.get::<String, _>(TAGS_NAMESPACE, "report_type")
|
||||
.await?
|
||||
.and_then(|x| serde_json::from_str::<Vec<String>>(&x).ok());
|
||||
|
||||
@@ -498,12 +479,13 @@ impl ReportType {
|
||||
.try_collect::<Vec<String>>()
|
||||
.await?;
|
||||
|
||||
cmd("SET")
|
||||
.arg(format!("{}:report_type", TAGS_NAMESPACE))
|
||||
.arg(serde_json::to_string(&result)?)
|
||||
.arg("EX")
|
||||
.arg(DEFAULT_EXPIRY)
|
||||
.query_async::<_, ()>(&mut redis)
|
||||
redis
|
||||
.set(
|
||||
TAGS_NAMESPACE,
|
||||
"report_type",
|
||||
serde_json::to_string(&result)?,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(result)
|
||||
@@ -528,17 +510,12 @@ impl ProjectType {
|
||||
Ok(result.map(|r| ProjectTypeId(r.id)))
|
||||
}
|
||||
|
||||
pub async fn list<'a, E>(
|
||||
exec: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
) -> Result<Vec<String>, DatabaseError>
|
||||
pub async fn list<'a, E>(exec: E, redis: &RedisPool) -> Result<Vec<String>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
let mut redis = redis.get().await?;
|
||||
let res = cmd("GET")
|
||||
.arg(format!("{}:project_type", TAGS_NAMESPACE))
|
||||
.query_async::<_, Option<String>>(&mut redis)
|
||||
let res = redis
|
||||
.get::<String, _>(TAGS_NAMESPACE, "project_type")
|
||||
.await?
|
||||
.and_then(|x| serde_json::from_str::<Vec<String>>(&x).ok());
|
||||
|
||||
@@ -556,12 +533,13 @@ impl ProjectType {
|
||||
.try_collect::<Vec<String>>()
|
||||
.await?;
|
||||
|
||||
cmd("SET")
|
||||
.arg(format!("{}:project_type", TAGS_NAMESPACE))
|
||||
.arg(serde_json::to_string(&result)?)
|
||||
.arg("EX")
|
||||
.arg(DEFAULT_EXPIRY)
|
||||
.query_async::<_, ()>(&mut redis)
|
||||
redis
|
||||
.set(
|
||||
TAGS_NAMESPACE,
|
||||
"project_type",
|
||||
serde_json::to_string(&result)?,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(result)
|
||||
@@ -586,17 +564,12 @@ impl SideType {
|
||||
Ok(result.map(|r| SideTypeId(r.id)))
|
||||
}
|
||||
|
||||
pub async fn list<'a, E>(
|
||||
exec: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
) -> Result<Vec<String>, DatabaseError>
|
||||
pub async fn list<'a, E>(exec: E, redis: &RedisPool) -> Result<Vec<String>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
let mut redis = redis.get().await?;
|
||||
let res = cmd("GET")
|
||||
.arg(format!("{}:side_type", TAGS_NAMESPACE))
|
||||
.query_async::<_, Option<String>>(&mut redis)
|
||||
let res = redis
|
||||
.get::<String, _>(TAGS_NAMESPACE, "side_type")
|
||||
.await?
|
||||
.and_then(|x| serde_json::from_str::<Vec<String>>(&x).ok());
|
||||
|
||||
@@ -614,12 +587,13 @@ impl SideType {
|
||||
.try_collect::<Vec<String>>()
|
||||
.await?;
|
||||
|
||||
cmd("SET")
|
||||
.arg(format!("{}:side_type", TAGS_NAMESPACE))
|
||||
.arg(serde_json::to_string(&result)?)
|
||||
.arg("EX")
|
||||
.arg(DEFAULT_EXPIRY)
|
||||
.query_async::<_, ()>(&mut redis)
|
||||
redis
|
||||
.set(
|
||||
TAGS_NAMESPACE,
|
||||
"side_type",
|
||||
serde_json::to_string(&result)?,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(result)
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
use super::ids::*;
|
||||
use crate::database::models;
|
||||
use crate::database::models::DatabaseError;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::collections::CollectionStatus;
|
||||
use chrono::{DateTime, Utc};
|
||||
use redis::cmd;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const COLLECTIONS_NAMESPACE: &str = "collections";
|
||||
const DEFAULT_EXPIRY: i64 = 1800; // 30 minutes
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CollectionBuilder {
|
||||
@@ -102,7 +101,7 @@ impl Collection {
|
||||
pub async fn remove(
|
||||
id: CollectionId,
|
||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Option<()>, DatabaseError> {
|
||||
let collection = Self::get(id, &mut *transaction, redis).await?;
|
||||
|
||||
@@ -138,7 +137,7 @@ impl Collection {
|
||||
pub async fn get<'a, 'b, E>(
|
||||
id: CollectionId,
|
||||
executor: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Option<Collection>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
@@ -151,7 +150,7 @@ impl Collection {
|
||||
pub async fn get_many<'a, E>(
|
||||
collection_ids: &[CollectionId],
|
||||
exec: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Vec<Collection>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
@@ -162,20 +161,12 @@ impl Collection {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let mut redis = redis.get().await?;
|
||||
|
||||
let mut found_collections = Vec::new();
|
||||
let mut remaining_collections: Vec<CollectionId> = collection_ids.to_vec();
|
||||
|
||||
if !collection_ids.is_empty() {
|
||||
let collections = cmd("MGET")
|
||||
.arg(
|
||||
collection_ids
|
||||
.iter()
|
||||
.map(|x| format!("{}:{}", COLLECTIONS_NAMESPACE, x.0))
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.query_async::<_, Vec<Option<String>>>(&mut redis)
|
||||
let collections = redis
|
||||
.multi_get::<String, _>(COLLECTIONS_NAMESPACE, collection_ids.iter().map(|x| x.0))
|
||||
.await?;
|
||||
|
||||
for collection in collections {
|
||||
@@ -233,14 +224,14 @@ impl Collection {
|
||||
.await?;
|
||||
|
||||
for collection in db_collections {
|
||||
cmd("SET")
|
||||
.arg(format!("{}:{}", COLLECTIONS_NAMESPACE, collection.id.0))
|
||||
.arg(serde_json::to_string(&collection)?)
|
||||
.arg("EX")
|
||||
.arg(DEFAULT_EXPIRY)
|
||||
.query_async::<_, ()>(&mut redis)
|
||||
redis
|
||||
.set(
|
||||
COLLECTIONS_NAMESPACE,
|
||||
collection.id.0,
|
||||
serde_json::to_string(&collection)?,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
found_collections.push(collection);
|
||||
}
|
||||
}
|
||||
@@ -248,16 +239,8 @@ impl Collection {
|
||||
Ok(found_collections)
|
||||
}
|
||||
|
||||
pub async fn clear_cache(
|
||||
id: CollectionId,
|
||||
redis: &deadpool_redis::Pool,
|
||||
) -> Result<(), DatabaseError> {
|
||||
let mut redis = redis.get().await?;
|
||||
let mut cmd = cmd("DEL");
|
||||
|
||||
cmd.arg(format!("{}:{}", COLLECTIONS_NAMESPACE, id.0));
|
||||
cmd.query_async::<_, ()>(&mut redis).await?;
|
||||
|
||||
pub async fn clear_cache(id: CollectionId, redis: &RedisPool) -> Result<(), DatabaseError> {
|
||||
redis.delete(COLLECTIONS_NAMESPACE, id.0).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use super::ids::*;
|
||||
use crate::auth::flows::AuthProvider;
|
||||
use crate::database::models::DatabaseError;
|
||||
use crate::database::redis::RedisPool;
|
||||
use chrono::Duration;
|
||||
use rand::distributions::Alphanumeric;
|
||||
use rand::Rng;
|
||||
use rand_chacha::rand_core::SeedableRng;
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
use redis::cmd;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const FLOWS_NAMESPACE: &str = "flows";
|
||||
@@ -40,50 +40,32 @@ impl Flow {
|
||||
pub async fn insert(
|
||||
&self,
|
||||
expires: Duration,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<String, DatabaseError> {
|
||||
let mut redis = redis.get().await?;
|
||||
|
||||
let flow = ChaCha20Rng::from_entropy()
|
||||
.sample_iter(&Alphanumeric)
|
||||
.take(32)
|
||||
.map(char::from)
|
||||
.collect::<String>();
|
||||
|
||||
cmd("SET")
|
||||
.arg(format!("{}:{}", FLOWS_NAMESPACE, flow))
|
||||
.arg(serde_json::to_string(&self)?)
|
||||
.arg("EX")
|
||||
.arg(expires.num_seconds())
|
||||
.query_async::<_, ()>(&mut redis)
|
||||
redis
|
||||
.set(
|
||||
FLOWS_NAMESPACE,
|
||||
&flow,
|
||||
serde_json::to_string(&self)?,
|
||||
Some(expires.num_seconds()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(flow)
|
||||
}
|
||||
|
||||
pub async fn get(
|
||||
id: &str,
|
||||
redis: &deadpool_redis::Pool,
|
||||
) -> Result<Option<Flow>, DatabaseError> {
|
||||
let mut redis = redis.get().await?;
|
||||
|
||||
let res = cmd("GET")
|
||||
.arg(format!("{}:{}", FLOWS_NAMESPACE, id))
|
||||
.query_async::<_, Option<String>>(&mut redis)
|
||||
.await?;
|
||||
|
||||
pub async fn get(id: &str, redis: &RedisPool) -> Result<Option<Flow>, DatabaseError> {
|
||||
let res = redis.get::<String, _>(FLOWS_NAMESPACE, id).await?;
|
||||
Ok(res.and_then(|x| serde_json::from_str(&x).ok()))
|
||||
}
|
||||
|
||||
pub async fn remove(
|
||||
id: &str,
|
||||
redis: &deadpool_redis::Pool,
|
||||
) -> Result<Option<()>, DatabaseError> {
|
||||
let mut redis = redis.get().await?;
|
||||
let mut cmd = cmd("DEL");
|
||||
cmd.arg(format!("{}:{}", FLOWS_NAMESPACE, id));
|
||||
cmd.query_async::<_, ()>(&mut redis).await?;
|
||||
|
||||
pub async fn remove(id: &str, redis: &RedisPool) -> Result<Option<()>, DatabaseError> {
|
||||
redis.delete(FLOWS_NAMESPACE, id).await?;
|
||||
Ok(Some(()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
use super::ids::*;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::{database::models::DatabaseError, models::images::ImageContext};
|
||||
use chrono::{DateTime, Utc};
|
||||
use redis::cmd;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const IMAGES_NAMESPACE: &str = "images";
|
||||
const DEFAULT_EXPIRY: i64 = 1800; // 30 minutes
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Image {
|
||||
@@ -58,7 +57,7 @@ impl Image {
|
||||
pub async fn remove(
|
||||
id: ImageId,
|
||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Option<()>, DatabaseError> {
|
||||
let image = Self::get(id, &mut *transaction, redis).await?;
|
||||
|
||||
@@ -161,7 +160,7 @@ impl Image {
|
||||
pub async fn get<'a, 'b, E>(
|
||||
id: ImageId,
|
||||
executor: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Option<Image>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
@@ -174,7 +173,7 @@ impl Image {
|
||||
pub async fn get_many<'a, E>(
|
||||
image_ids: &[ImageId],
|
||||
exec: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Vec<Image>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
@@ -185,24 +184,15 @@ impl Image {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let mut redis = redis.get().await?;
|
||||
|
||||
let mut found_images = Vec::new();
|
||||
let mut remaining_ids = image_ids.to_vec();
|
||||
|
||||
let image_ids = image_ids.iter().map(|x| x.0).collect::<Vec<_>>();
|
||||
|
||||
if !image_ids.is_empty() {
|
||||
let images = cmd("MGET")
|
||||
.arg(
|
||||
image_ids
|
||||
.iter()
|
||||
.map(|x| format!("{}:{}", IMAGES_NAMESPACE, x))
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.query_async::<_, Vec<Option<String>>>(&mut redis)
|
||||
let images = redis
|
||||
.multi_get::<String, _>(IMAGES_NAMESPACE, image_ids)
|
||||
.await?;
|
||||
|
||||
for image in images {
|
||||
if let Some(image) = image.and_then(|x| serde_json::from_str::<Image>(&x).ok()) {
|
||||
remaining_ids.retain(|x| image.id.0 != x.0);
|
||||
@@ -245,14 +235,14 @@ impl Image {
|
||||
.await?;
|
||||
|
||||
for image in db_images {
|
||||
cmd("SET")
|
||||
.arg(format!("{}:{}", IMAGES_NAMESPACE, image.id.0))
|
||||
.arg(serde_json::to_string(&image)?)
|
||||
.arg("EX")
|
||||
.arg(DEFAULT_EXPIRY)
|
||||
.query_async::<_, ()>(&mut redis)
|
||||
redis
|
||||
.set(
|
||||
IMAGES_NAMESPACE,
|
||||
image.id.0,
|
||||
serde_json::to_string(&image)?,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
found_images.push(image);
|
||||
}
|
||||
}
|
||||
@@ -260,16 +250,8 @@ impl Image {
|
||||
Ok(found_images)
|
||||
}
|
||||
|
||||
pub async fn clear_cache(
|
||||
id: ImageId,
|
||||
redis: &deadpool_redis::Pool,
|
||||
) -> Result<(), DatabaseError> {
|
||||
let mut redis = redis.get().await?;
|
||||
let mut cmd = cmd("DEL");
|
||||
|
||||
cmd.arg(format!("{}:{}", IMAGES_NAMESPACE, id.0));
|
||||
cmd.query_async::<_, ()>(&mut redis).await?;
|
||||
|
||||
pub async fn clear_cache(id: ImageId, redis: &RedisPool) -> Result<(), DatabaseError> {
|
||||
redis.delete(IMAGES_NAMESPACE, id.0).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
use crate::models::ids::base62_impl::{parse_base62, to_base62};
|
||||
use crate::{
|
||||
database::redis::RedisPool,
|
||||
models::ids::base62_impl::{parse_base62, to_base62},
|
||||
};
|
||||
|
||||
use super::{ids::*, TeamMember};
|
||||
use redis::cmd;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const ORGANIZATIONS_NAMESPACE: &str = "organizations";
|
||||
const ORGANIZATIONS_TITLES_NAMESPACE: &str = "organizations_titles";
|
||||
|
||||
const DEFAULT_EXPIRY: i64 = 1800;
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
/// An organization of users who together control one or more projects and organizations.
|
||||
pub struct Organization {
|
||||
@@ -55,7 +55,7 @@ impl Organization {
|
||||
pub async fn get<'a, E>(
|
||||
string: &str,
|
||||
exec: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Option<Self>, super::DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
@@ -68,7 +68,7 @@ impl Organization {
|
||||
pub async fn get_id<'a, 'b, E>(
|
||||
id: OrganizationId,
|
||||
exec: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Option<Self>, super::DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
@@ -81,7 +81,7 @@ impl Organization {
|
||||
pub async fn get_many_ids<'a, 'b, E>(
|
||||
organization_ids: &[OrganizationId],
|
||||
exec: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Vec<Self>, super::DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
@@ -96,7 +96,7 @@ impl Organization {
|
||||
pub async fn get_many<'a, E, T: ToString>(
|
||||
organization_strings: &[T],
|
||||
exec: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Vec<Self>, super::DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
@@ -107,8 +107,6 @@ impl Organization {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let mut redis = redis.get().await?;
|
||||
|
||||
let mut found_organizations = Vec::new();
|
||||
let mut remaining_strings = organization_strings
|
||||
.iter()
|
||||
@@ -121,20 +119,13 @@ impl Organization {
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
organization_ids.append(
|
||||
&mut cmd("MGET")
|
||||
.arg(
|
||||
&mut redis
|
||||
.multi_get::<i64, _>(
|
||||
ORGANIZATIONS_TITLES_NAMESPACE,
|
||||
organization_strings
|
||||
.iter()
|
||||
.map(|x| {
|
||||
format!(
|
||||
"{}:{}",
|
||||
ORGANIZATIONS_TITLES_NAMESPACE,
|
||||
x.to_string().to_lowercase()
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
.map(|x| x.to_string().to_lowercase()),
|
||||
)
|
||||
.query_async::<_, Vec<Option<i64>>>(&mut redis)
|
||||
.await?
|
||||
.into_iter()
|
||||
.flatten()
|
||||
@@ -142,14 +133,8 @@ impl Organization {
|
||||
);
|
||||
|
||||
if !organization_ids.is_empty() {
|
||||
let organizations = cmd("MGET")
|
||||
.arg(
|
||||
organization_ids
|
||||
.iter()
|
||||
.map(|x| format!("{}:{}", ORGANIZATIONS_NAMESPACE, x))
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.query_async::<_, Vec<Option<String>>>(&mut redis)
|
||||
let organizations = redis
|
||||
.multi_get::<String, _>(ORGANIZATIONS_NAMESPACE, organization_ids)
|
||||
.await?;
|
||||
|
||||
for organization in organizations {
|
||||
@@ -201,25 +186,23 @@ impl Organization {
|
||||
.await?;
|
||||
|
||||
for organization in organizations {
|
||||
cmd("SET")
|
||||
.arg(format!("{}:{}", ORGANIZATIONS_NAMESPACE, organization.id.0))
|
||||
.arg(serde_json::to_string(&organization)?)
|
||||
.arg("EX")
|
||||
.arg(DEFAULT_EXPIRY)
|
||||
.query_async::<_, ()>(&mut redis)
|
||||
redis
|
||||
.set(
|
||||
ORGANIZATIONS_NAMESPACE,
|
||||
organization.id.0,
|
||||
serde_json::to_string(&organization)?,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
redis
|
||||
.set(
|
||||
ORGANIZATIONS_TITLES_NAMESPACE,
|
||||
organization.title.to_lowercase(),
|
||||
organization.id.0,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
cmd("SET")
|
||||
.arg(format!(
|
||||
"{}:{}",
|
||||
ORGANIZATIONS_TITLES_NAMESPACE,
|
||||
organization.title.to_lowercase()
|
||||
))
|
||||
.arg(organization.id.0)
|
||||
.arg("EX")
|
||||
.arg(DEFAULT_EXPIRY)
|
||||
.query_async::<_, ()>(&mut redis)
|
||||
.await?;
|
||||
found_organizations.push(organization);
|
||||
}
|
||||
}
|
||||
@@ -265,7 +248,7 @@ impl Organization {
|
||||
pub async fn remove(
|
||||
id: OrganizationId,
|
||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Option<()>, super::DatabaseError> {
|
||||
use futures::TryStreamExt;
|
||||
|
||||
@@ -333,20 +316,17 @@ impl Organization {
|
||||
pub async fn clear_cache(
|
||||
id: OrganizationId,
|
||||
title: Option<String>,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<(), super::DatabaseError> {
|
||||
let mut redis = redis.get().await?;
|
||||
let mut cmd = cmd("DEL");
|
||||
cmd.arg(format!("{}:{}", ORGANIZATIONS_NAMESPACE, id.0));
|
||||
if let Some(title) = title {
|
||||
cmd.arg(format!(
|
||||
"{}:{}",
|
||||
ORGANIZATIONS_TITLES_NAMESPACE,
|
||||
title.to_lowercase()
|
||||
));
|
||||
}
|
||||
cmd.query_async::<_, ()>(&mut redis).await?;
|
||||
|
||||
redis
|
||||
.delete_many([
|
||||
(ORGANIZATIONS_NAMESPACE, Some(id.0.to_string())),
|
||||
(
|
||||
ORGANIZATIONS_TITLES_NAMESPACE,
|
||||
title.map(|x| x.to_lowercase()),
|
||||
),
|
||||
])
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
use super::ids::*;
|
||||
use crate::database::models::DatabaseError;
|
||||
use crate::database::redis::RedisPool;
|
||||
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)]
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
pub struct PersonalAccessToken {
|
||||
pub id: PatId,
|
||||
pub name: String,
|
||||
@@ -55,7 +54,7 @@ impl PersonalAccessToken {
|
||||
pub async fn get<'a, E, T: ToString>(
|
||||
id: T,
|
||||
exec: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Option<PersonalAccessToken>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
@@ -68,7 +67,7 @@ impl PersonalAccessToken {
|
||||
pub async fn get_many_ids<'a, E>(
|
||||
pat_ids: &[PatId],
|
||||
exec: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Vec<PersonalAccessToken>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
@@ -83,7 +82,7 @@ impl PersonalAccessToken {
|
||||
pub async fn get_many<'a, E, T: ToString>(
|
||||
pat_strings: &[T],
|
||||
exec: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Vec<PersonalAccessToken>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
@@ -94,8 +93,6 @@ impl PersonalAccessToken {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let mut redis = redis.get().await?;
|
||||
|
||||
let mut found_pats = Vec::new();
|
||||
let mut remaining_strings = pat_strings
|
||||
.iter()
|
||||
@@ -108,14 +105,11 @@ impl PersonalAccessToken {
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
pat_ids.append(
|
||||
&mut cmd("MGET")
|
||||
.arg(
|
||||
pat_strings
|
||||
.iter()
|
||||
.map(|x| format!("{}:{}", PATS_TOKENS_NAMESPACE, x.to_string()))
|
||||
.collect::<Vec<_>>(),
|
||||
&mut redis
|
||||
.multi_get::<i64, _>(
|
||||
PATS_TOKENS_NAMESPACE,
|
||||
pat_strings.iter().map(|x| x.to_string()),
|
||||
)
|
||||
.query_async::<_, Vec<Option<i64>>>(&mut redis)
|
||||
.await?
|
||||
.into_iter()
|
||||
.flatten()
|
||||
@@ -123,16 +117,9 @@ impl PersonalAccessToken {
|
||||
);
|
||||
|
||||
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)
|
||||
let pats = redis
|
||||
.multi_get::<String, _>(PATS_NAMESPACE, pat_ids)
|
||||
.await?;
|
||||
|
||||
for pat in pats {
|
||||
if let Some(pat) =
|
||||
pat.and_then(|x| serde_json::from_str::<PersonalAccessToken>(&x).ok())
|
||||
@@ -181,20 +168,16 @@ impl 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)
|
||||
redis
|
||||
.set(PATS_NAMESPACE, pat.id.0, serde_json::to_string(&pat)?, None)
|
||||
.await?;
|
||||
|
||||
cmd("SET")
|
||||
.arg(format!("{}:{}", PATS_TOKENS_NAMESPACE, pat.access_token))
|
||||
.arg(pat.id.0)
|
||||
.arg("EX")
|
||||
.arg(DEFAULT_EXPIRY)
|
||||
.query_async::<_, ()>(&mut redis)
|
||||
redis
|
||||
.set(
|
||||
PATS_TOKENS_NAMESPACE,
|
||||
pat.access_token.clone(),
|
||||
pat.id.0,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
found_pats.push(pat);
|
||||
}
|
||||
@@ -206,15 +189,13 @@ impl PersonalAccessToken {
|
||||
pub async fn get_user_pats<'a, E>(
|
||||
user_id: UserId,
|
||||
exec: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> 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<String>>(&mut redis)
|
||||
let res = redis
|
||||
.get::<String, _>(PATS_USERS_NAMESPACE, user_id.0)
|
||||
.await?
|
||||
.and_then(|x| serde_json::from_str::<Vec<i64>>(&x).ok());
|
||||
|
||||
@@ -237,41 +218,34 @@ impl PersonalAccessToken {
|
||||
.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)
|
||||
redis
|
||||
.set(
|
||||
PATS_USERS_NAMESPACE,
|
||||
user_id.0,
|
||||
serde_json::to_string(&db_pats)?,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(db_pats)
|
||||
}
|
||||
|
||||
pub async fn clear_cache(
|
||||
clear_pats: Vec<(Option<PatId>, Option<String>, Option<UserId>)>,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<(), DatabaseError> {
|
||||
if clear_pats.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
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?;
|
||||
redis
|
||||
.delete_many(clear_pats.into_iter().flat_map(|(id, token, user_id)| {
|
||||
[
|
||||
(PATS_NAMESPACE, id.map(|i| i.0.to_string())),
|
||||
(PATS_TOKENS_NAMESPACE, token),
|
||||
(PATS_USERS_NAMESPACE, user_id.map(|i| i.0.to_string())),
|
||||
]
|
||||
}))
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
use super::ids::*;
|
||||
use crate::database::models;
|
||||
use crate::database::models::DatabaseError;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::ids::base62_impl::{parse_base62, to_base62};
|
||||
use crate::models::projects::{MonetizationStatus, ProjectStatus};
|
||||
use chrono::{DateTime, Utc};
|
||||
use redis::cmd;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const PROJECTS_NAMESPACE: &str = "projects";
|
||||
const PROJECTS_SLUGS_NAMESPACE: &str = "projects_slugs";
|
||||
pub const PROJECTS_NAMESPACE: &str = "projects";
|
||||
pub const PROJECTS_SLUGS_NAMESPACE: &str = "projects_slugs";
|
||||
const PROJECTS_DEPENDENCIES_NAMESPACE: &str = "projects_dependencies";
|
||||
const DEFAULT_EXPIRY: i64 = 1800; // 30 minutes
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DonationUrl {
|
||||
@@ -299,7 +298,7 @@ impl Project {
|
||||
pub async fn remove(
|
||||
id: ProjectId,
|
||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Option<()>, DatabaseError> {
|
||||
let project = Self::get_id(id, &mut *transaction, redis).await?;
|
||||
|
||||
@@ -433,7 +432,7 @@ impl Project {
|
||||
pub async fn get<'a, 'b, E>(
|
||||
string: &str,
|
||||
executor: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Option<QueryProject>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
@@ -446,7 +445,7 @@ impl Project {
|
||||
pub async fn get_id<'a, 'b, E>(
|
||||
id: ProjectId,
|
||||
executor: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Option<QueryProject>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
@@ -459,7 +458,7 @@ impl Project {
|
||||
pub async fn get_many_ids<'a, E>(
|
||||
project_ids: &[ProjectId],
|
||||
exec: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Vec<QueryProject>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
@@ -474,7 +473,7 @@ impl Project {
|
||||
pub async fn get_many<'a, E, T: ToString>(
|
||||
project_strings: &[T],
|
||||
exec: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Vec<QueryProject>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
@@ -485,8 +484,6 @@ impl Project {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let mut redis = redis.get().await?;
|
||||
|
||||
let mut found_projects = Vec::new();
|
||||
let mut remaining_strings = project_strings
|
||||
.iter()
|
||||
@@ -499,20 +496,11 @@ impl Project {
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
project_ids.append(
|
||||
&mut cmd("MGET")
|
||||
.arg(
|
||||
project_strings
|
||||
.iter()
|
||||
.map(|x| {
|
||||
format!(
|
||||
"{}:{}",
|
||||
PROJECTS_SLUGS_NAMESPACE,
|
||||
x.to_string().to_lowercase()
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
&mut redis
|
||||
.multi_get::<i64, _>(
|
||||
PROJECTS_SLUGS_NAMESPACE,
|
||||
project_strings.iter().map(|x| x.to_string().to_lowercase()),
|
||||
)
|
||||
.query_async::<_, Vec<Option<i64>>>(&mut redis)
|
||||
.await?
|
||||
.into_iter()
|
||||
.flatten()
|
||||
@@ -520,16 +508,9 @@ impl Project {
|
||||
);
|
||||
|
||||
if !project_ids.is_empty() {
|
||||
let projects = cmd("MGET")
|
||||
.arg(
|
||||
project_ids
|
||||
.iter()
|
||||
.map(|x| format!("{}:{}", PROJECTS_NAMESPACE, x))
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.query_async::<_, Vec<Option<String>>>(&mut redis)
|
||||
let projects = redis
|
||||
.multi_get::<String, _>(PROJECTS_NAMESPACE, project_ids)
|
||||
.await?;
|
||||
|
||||
for project in projects {
|
||||
if let Some(project) =
|
||||
project.and_then(|x| serde_json::from_str::<QueryProject>(&x).ok())
|
||||
@@ -551,7 +532,6 @@ impl Project {
|
||||
.flat_map(|x| parse_base62(&x.to_string()).ok())
|
||||
.map(|x| x as i64)
|
||||
.collect();
|
||||
|
||||
let db_projects: Vec<QueryProject> = sqlx::query!(
|
||||
"
|
||||
SELECT m.id id, m.project_type project_type, m.title title, m.description description, m.downloads downloads, m.follows follows,
|
||||
@@ -672,25 +652,22 @@ impl Project {
|
||||
.await?;
|
||||
|
||||
for project in db_projects {
|
||||
cmd("SET")
|
||||
.arg(format!("{}:{}", PROJECTS_NAMESPACE, project.inner.id.0))
|
||||
.arg(serde_json::to_string(&project)?)
|
||||
.arg("EX")
|
||||
.arg(DEFAULT_EXPIRY)
|
||||
.query_async::<_, ()>(&mut redis)
|
||||
redis
|
||||
.set(
|
||||
PROJECTS_NAMESPACE,
|
||||
project.inner.id.0,
|
||||
serde_json::to_string(&project)?,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(slug) = &project.inner.slug {
|
||||
cmd("SET")
|
||||
.arg(format!(
|
||||
"{}:{}",
|
||||
redis
|
||||
.set(
|
||||
PROJECTS_SLUGS_NAMESPACE,
|
||||
slug.to_lowercase()
|
||||
))
|
||||
.arg(project.inner.id.0)
|
||||
.arg("EX")
|
||||
.arg(DEFAULT_EXPIRY)
|
||||
.query_async::<_, ()>(&mut redis)
|
||||
slug.to_lowercase(),
|
||||
project.inner.id.0,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
found_projects.push(project);
|
||||
@@ -703,7 +680,7 @@ impl Project {
|
||||
pub async fn get_dependencies<'a, E>(
|
||||
id: ProjectId,
|
||||
exec: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Vec<(Option<VersionId>, Option<ProjectId>, Option<ProjectId>)>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
@@ -712,13 +689,9 @@ impl Project {
|
||||
|
||||
use futures::stream::TryStreamExt;
|
||||
|
||||
let mut redis = redis.get().await?;
|
||||
|
||||
let dependencies = cmd("GET")
|
||||
.arg(format!("{}:{}", PROJECTS_DEPENDENCIES_NAMESPACE, id.0))
|
||||
.query_async::<_, Option<String>>(&mut redis)
|
||||
let dependencies = redis
|
||||
.get::<String, _>(PROJECTS_DEPENDENCIES_NAMESPACE, id.0)
|
||||
.await?;
|
||||
|
||||
if let Some(dependencies) =
|
||||
dependencies.and_then(|x| serde_json::from_str::<Dependencies>(&x).ok())
|
||||
{
|
||||
@@ -752,14 +725,14 @@ impl Project {
|
||||
.try_collect::<Dependencies>()
|
||||
.await?;
|
||||
|
||||
cmd("SET")
|
||||
.arg(format!("{}:{}", PROJECTS_DEPENDENCIES_NAMESPACE, id.0))
|
||||
.arg(serde_json::to_string(&dependencies)?)
|
||||
.arg("EX")
|
||||
.arg(DEFAULT_EXPIRY)
|
||||
.query_async::<_, ()>(&mut redis)
|
||||
redis
|
||||
.set(
|
||||
PROJECTS_DEPENDENCIES_NAMESPACE,
|
||||
id.0,
|
||||
serde_json::to_string(&dependencies)?,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(dependencies)
|
||||
}
|
||||
|
||||
@@ -817,25 +790,22 @@ impl Project {
|
||||
id: ProjectId,
|
||||
slug: Option<String>,
|
||||
clear_dependencies: Option<bool>,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<(), DatabaseError> {
|
||||
let mut redis = redis.get().await?;
|
||||
let mut cmd = cmd("DEL");
|
||||
|
||||
cmd.arg(format!("{}:{}", PROJECTS_NAMESPACE, id.0));
|
||||
if let Some(slug) = slug {
|
||||
cmd.arg(format!(
|
||||
"{}:{}",
|
||||
PROJECTS_SLUGS_NAMESPACE,
|
||||
slug.to_lowercase()
|
||||
));
|
||||
}
|
||||
if clear_dependencies.unwrap_or(false) {
|
||||
cmd.arg(format!("{}:{}", PROJECTS_DEPENDENCIES_NAMESPACE, id.0));
|
||||
}
|
||||
|
||||
cmd.query_async::<_, ()>(&mut redis).await?;
|
||||
|
||||
redis
|
||||
.delete_many([
|
||||
(PROJECTS_NAMESPACE, Some(id.0.to_string())),
|
||||
(PROJECTS_SLUGS_NAMESPACE, slug.map(|x| x.to_lowercase())),
|
||||
(
|
||||
PROJECTS_DEPENDENCIES_NAMESPACE,
|
||||
if clear_dependencies.unwrap_or(false) {
|
||||
Some(id.0.to_string())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
),
|
||||
])
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
use super::ids::*;
|
||||
use crate::database::models::DatabaseError;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::ids::base62_impl::{parse_base62, to_base62};
|
||||
use chrono::{DateTime, Utc};
|
||||
use redis::cmd;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const SESSIONS_NAMESPACE: &str = "sessions";
|
||||
const SESSIONS_IDS_NAMESPACE: &str = "sessions_ids";
|
||||
const SESSIONS_USERS_NAMESPACE: &str = "sessions_users";
|
||||
const DEFAULT_EXPIRY: i64 = 1800; // 30 minutes
|
||||
|
||||
pub struct SessionBuilder {
|
||||
pub session: String,
|
||||
@@ -83,7 +82,7 @@ impl Session {
|
||||
pub async fn get<'a, E, T: ToString>(
|
||||
id: T,
|
||||
exec: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Option<Session>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
@@ -96,7 +95,7 @@ impl Session {
|
||||
pub async fn get_id<'a, 'b, E>(
|
||||
id: SessionId,
|
||||
executor: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Option<Session>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
@@ -109,7 +108,7 @@ impl Session {
|
||||
pub async fn get_many_ids<'a, E>(
|
||||
session_ids: &[SessionId],
|
||||
exec: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Vec<Session>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
@@ -124,7 +123,7 @@ impl Session {
|
||||
pub async fn get_many<'a, E, T: ToString>(
|
||||
session_strings: &[T],
|
||||
exec: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Vec<Session>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
@@ -135,8 +134,6 @@ impl Session {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let mut redis = redis.get().await?;
|
||||
|
||||
let mut found_sessions = Vec::new();
|
||||
let mut remaining_strings = session_strings
|
||||
.iter()
|
||||
@@ -149,14 +146,11 @@ impl Session {
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
session_ids.append(
|
||||
&mut cmd("MGET")
|
||||
.arg(
|
||||
session_strings
|
||||
.iter()
|
||||
.map(|x| format!("{}:{}", SESSIONS_IDS_NAMESPACE, x.to_string()))
|
||||
.collect::<Vec<_>>(),
|
||||
&mut redis
|
||||
.multi_get::<i64, _>(
|
||||
SESSIONS_IDS_NAMESPACE,
|
||||
session_strings.iter().map(|x| x.to_string()),
|
||||
)
|
||||
.query_async::<_, Vec<Option<i64>>>(&mut redis)
|
||||
.await?
|
||||
.into_iter()
|
||||
.flatten()
|
||||
@@ -164,16 +158,9 @@ impl Session {
|
||||
);
|
||||
|
||||
if !session_ids.is_empty() {
|
||||
let sessions = cmd("MGET")
|
||||
.arg(
|
||||
session_ids
|
||||
.iter()
|
||||
.map(|x| format!("{}:{}", SESSIONS_NAMESPACE, x))
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.query_async::<_, Vec<Option<String>>>(&mut redis)
|
||||
let sessions = redis
|
||||
.multi_get::<String, _>(SESSIONS_NAMESPACE, session_ids)
|
||||
.await?;
|
||||
|
||||
for session in sessions {
|
||||
if let Some(session) =
|
||||
session.and_then(|x| serde_json::from_str::<Session>(&x).ok())
|
||||
@@ -225,20 +212,21 @@ impl Session {
|
||||
.await?;
|
||||
|
||||
for session in db_sessions {
|
||||
cmd("SET")
|
||||
.arg(format!("{}:{}", SESSIONS_NAMESPACE, session.id.0))
|
||||
.arg(serde_json::to_string(&session)?)
|
||||
.arg("EX")
|
||||
.arg(DEFAULT_EXPIRY)
|
||||
.query_async::<_, ()>(&mut redis)
|
||||
redis
|
||||
.set(
|
||||
SESSIONS_NAMESPACE,
|
||||
session.id.0,
|
||||
serde_json::to_string(&session)?,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
cmd("SET")
|
||||
.arg(format!("{}:{}", SESSIONS_IDS_NAMESPACE, session.session))
|
||||
.arg(session.id.0)
|
||||
.arg("EX")
|
||||
.arg(DEFAULT_EXPIRY)
|
||||
.query_async::<_, ()>(&mut redis)
|
||||
redis
|
||||
.set(
|
||||
SESSIONS_IDS_NAMESPACE,
|
||||
session.session.clone(),
|
||||
session.id.0,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
found_sessions.push(session);
|
||||
}
|
||||
@@ -250,15 +238,13 @@ impl Session {
|
||||
pub async fn get_user_sessions<'a, E>(
|
||||
user_id: UserId,
|
||||
exec: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Vec<SessionId>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
let mut redis = redis.get().await?;
|
||||
let res = cmd("GET")
|
||||
.arg(format!("{}:{}", SESSIONS_USERS_NAMESPACE, user_id.0))
|
||||
.query_async::<_, Option<String>>(&mut redis)
|
||||
let res = redis
|
||||
.get::<String, _>(SESSIONS_USERS_NAMESPACE, user_id.0)
|
||||
.await?
|
||||
.and_then(|x| serde_json::from_str::<Vec<i64>>(&x).ok());
|
||||
|
||||
@@ -281,12 +267,13 @@ impl Session {
|
||||
.try_collect::<Vec<SessionId>>()
|
||||
.await?;
|
||||
|
||||
cmd("SET")
|
||||
.arg(format!("{}:{}", SESSIONS_USERS_NAMESPACE, user_id.0))
|
||||
.arg(serde_json::to_string(&db_sessions)?)
|
||||
.arg("EX")
|
||||
.arg(DEFAULT_EXPIRY)
|
||||
.query_async::<_, ()>(&mut redis)
|
||||
redis
|
||||
.set(
|
||||
SESSIONS_USERS_NAMESPACE,
|
||||
user_id.0,
|
||||
serde_json::to_string(&db_sessions)?,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(db_sessions)
|
||||
@@ -294,29 +281,25 @@ impl Session {
|
||||
|
||||
pub async fn clear_cache(
|
||||
clear_sessions: Vec<(Option<SessionId>, Option<String>, Option<UserId>)>,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<(), DatabaseError> {
|
||||
if clear_sessions.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut redis = redis.get().await?;
|
||||
let mut cmd = cmd("DEL");
|
||||
|
||||
for (id, session, user_id) in clear_sessions {
|
||||
if let Some(id) = id {
|
||||
cmd.arg(format!("{}:{}", SESSIONS_NAMESPACE, id.0));
|
||||
}
|
||||
if let Some(session) = session {
|
||||
cmd.arg(format!("{}:{}", SESSIONS_IDS_NAMESPACE, session));
|
||||
}
|
||||
if let Some(user_id) = user_id {
|
||||
cmd.arg(format!("{}:{}", SESSIONS_USERS_NAMESPACE, user_id.0));
|
||||
}
|
||||
}
|
||||
|
||||
cmd.query_async::<_, ()>(&mut redis).await?;
|
||||
|
||||
redis
|
||||
.delete_many(
|
||||
clear_sessions
|
||||
.into_iter()
|
||||
.flat_map(|(id, session, user_id)| {
|
||||
[
|
||||
(SESSIONS_NAMESPACE, id.map(|i| i.0.to_string())),
|
||||
(SESSIONS_IDS_NAMESPACE, session),
|
||||
(SESSIONS_USERS_NAMESPACE, user_id.map(|i| i.0.to_string())),
|
||||
]
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use super::{ids::*, Organization, Project};
|
||||
use crate::models::teams::{OrganizationPermissions, ProjectPermissions};
|
||||
use crate::{
|
||||
database::redis::RedisPool,
|
||||
models::teams::{OrganizationPermissions, ProjectPermissions},
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use redis::cmd;
|
||||
use rust_decimal::Decimal;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const TEAMS_NAMESPACE: &str = "teams";
|
||||
const DEFAULT_EXPIRY: i64 = 1800;
|
||||
|
||||
pub struct TeamBuilder {
|
||||
pub members: Vec<TeamMemberBuilder>,
|
||||
@@ -145,7 +146,7 @@ impl TeamMember {
|
||||
pub async fn get_from_team_full<'a, 'b, E>(
|
||||
id: TeamId,
|
||||
executor: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Vec<TeamMember>, super::DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||
@@ -156,7 +157,7 @@ impl TeamMember {
|
||||
pub async fn get_from_team_full_many<'a, E>(
|
||||
team_ids: &[TeamId],
|
||||
exec: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Vec<TeamMember>, super::DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||
@@ -169,18 +170,10 @@ impl TeamMember {
|
||||
|
||||
let mut team_ids_parsed: Vec<i64> = team_ids.iter().map(|x| x.0).collect();
|
||||
|
||||
let mut redis = redis.get().await?;
|
||||
|
||||
let mut found_teams = Vec::new();
|
||||
|
||||
let teams = cmd("MGET")
|
||||
.arg(
|
||||
team_ids_parsed
|
||||
.iter()
|
||||
.map(|x| format!("{}:{}", TEAMS_NAMESPACE, x))
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.query_async::<_, Vec<Option<String>>>(&mut redis)
|
||||
let teams = redis
|
||||
.multi_get::<String, _>(TEAMS_NAMESPACE, team_ids_parsed.clone())
|
||||
.await?;
|
||||
|
||||
for team_raw in teams {
|
||||
@@ -232,14 +225,14 @@ impl TeamMember {
|
||||
for (id, members) in &teams.into_iter().group_by(|x| x.team_id) {
|
||||
let mut members = members.collect::<Vec<_>>();
|
||||
|
||||
cmd("SET")
|
||||
.arg(format!("{}:{}", TEAMS_NAMESPACE, id.0))
|
||||
.arg(serde_json::to_string(&members)?)
|
||||
.arg("EX")
|
||||
.arg(DEFAULT_EXPIRY)
|
||||
.query_async::<_, ()>(&mut redis)
|
||||
redis
|
||||
.set(
|
||||
TEAMS_NAMESPACE,
|
||||
id.0,
|
||||
serde_json::to_string(&members)?,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
found_teams.append(&mut members);
|
||||
}
|
||||
}
|
||||
@@ -247,16 +240,8 @@ impl TeamMember {
|
||||
Ok(found_teams)
|
||||
}
|
||||
|
||||
pub async fn clear_cache(
|
||||
id: TeamId,
|
||||
redis: &deadpool_redis::Pool,
|
||||
) -> Result<(), super::DatabaseError> {
|
||||
let mut redis = redis.get().await?;
|
||||
cmd("DEL")
|
||||
.arg(format!("{}:{}", TEAMS_NAMESPACE, id.0))
|
||||
.query_async::<_, ()>(&mut redis)
|
||||
.await?;
|
||||
|
||||
pub async fn clear_cache(id: TeamId, redis: &RedisPool) -> Result<(), super::DatabaseError> {
|
||||
redis.delete(TEAMS_NAMESPACE, id.0).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::ids::*;
|
||||
use crate::database::models::DatabaseError;
|
||||
use crate::models::threads::{MessageBody, ThreadType};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub struct ThreadBuilder {
|
||||
pub type_: ThreadType,
|
||||
@@ -11,7 +11,7 @@ pub struct ThreadBuilder {
|
||||
pub report_id: Option<ReportId>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct Thread {
|
||||
pub id: ThreadId,
|
||||
|
||||
@@ -30,7 +30,7 @@ pub struct ThreadMessageBuilder {
|
||||
pub thread_id: ThreadId,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct ThreadMessage {
|
||||
pub id: ThreadMessageId,
|
||||
pub thread_id: ThreadId,
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
use super::ids::{ProjectId, UserId};
|
||||
use super::CollectionId;
|
||||
use crate::database::models::DatabaseError;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::ids::base62_impl::{parse_base62, to_base62};
|
||||
use crate::models::users::{Badges, RecipientType, RecipientWallet};
|
||||
use chrono::{DateTime, Utc};
|
||||
use redis::cmd;
|
||||
use rust_decimal::Decimal;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const USERS_NAMESPACE: &str = "users";
|
||||
const USER_USERNAMES_NAMESPACE: &str = "users_usernames";
|
||||
// const USERS_PROJECTS_NAMESPACE: &str = "users_projects";
|
||||
const DEFAULT_EXPIRY: i64 = 1800; // 30 minutes
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
pub struct User {
|
||||
@@ -87,7 +86,7 @@ impl User {
|
||||
pub async fn get<'a, 'b, E>(
|
||||
string: &str,
|
||||
executor: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Option<User>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
@@ -100,7 +99,7 @@ impl User {
|
||||
pub async fn get_id<'a, 'b, E>(
|
||||
id: UserId,
|
||||
executor: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Option<User>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
@@ -113,7 +112,7 @@ impl User {
|
||||
pub async fn get_many_ids<'a, E>(
|
||||
user_ids: &[UserId],
|
||||
exec: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Vec<User>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
@@ -128,7 +127,7 @@ impl User {
|
||||
pub async fn get_many<'a, E, T: ToString>(
|
||||
users_strings: &[T],
|
||||
exec: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Vec<User>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
@@ -139,8 +138,6 @@ impl User {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let mut redis = redis.get().await?;
|
||||
|
||||
let mut found_users = Vec::new();
|
||||
let mut remaining_strings = users_strings
|
||||
.iter()
|
||||
@@ -153,20 +150,11 @@ impl User {
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
user_ids.append(
|
||||
&mut cmd("MGET")
|
||||
.arg(
|
||||
users_strings
|
||||
.iter()
|
||||
.map(|x| {
|
||||
format!(
|
||||
"{}:{}",
|
||||
USER_USERNAMES_NAMESPACE,
|
||||
x.to_string().to_lowercase()
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
&mut redis
|
||||
.multi_get::<i64, _>(
|
||||
USER_USERNAMES_NAMESPACE,
|
||||
users_strings.iter().map(|x| x.to_string().to_lowercase()),
|
||||
)
|
||||
.query_async::<_, Vec<Option<i64>>>(&mut redis)
|
||||
.await?
|
||||
.into_iter()
|
||||
.flatten()
|
||||
@@ -174,16 +162,9 @@ impl User {
|
||||
);
|
||||
|
||||
if !user_ids.is_empty() {
|
||||
let users = cmd("MGET")
|
||||
.arg(
|
||||
user_ids
|
||||
.iter()
|
||||
.map(|x| format!("{}:{}", USERS_NAMESPACE, x))
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.query_async::<_, Vec<Option<String>>>(&mut redis)
|
||||
let users = redis
|
||||
.multi_get::<String, _>(USERS_NAMESPACE, user_ids)
|
||||
.await?;
|
||||
|
||||
for user in users {
|
||||
if let Some(user) = user.and_then(|x| serde_json::from_str::<User>(&x).ok()) {
|
||||
remaining_strings.retain(|x| {
|
||||
@@ -252,24 +233,21 @@ impl User {
|
||||
.await?;
|
||||
|
||||
for user in db_users {
|
||||
cmd("SET")
|
||||
.arg(format!("{}:{}", USERS_NAMESPACE, user.id.0))
|
||||
.arg(serde_json::to_string(&user)?)
|
||||
.arg("EX")
|
||||
.arg(DEFAULT_EXPIRY)
|
||||
.query_async::<_, ()>(&mut redis)
|
||||
redis
|
||||
.set(
|
||||
USERS_NAMESPACE,
|
||||
user.id.0,
|
||||
serde_json::to_string(&user)?,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
cmd("SET")
|
||||
.arg(format!(
|
||||
"{}:{}",
|
||||
redis
|
||||
.set(
|
||||
USER_USERNAMES_NAMESPACE,
|
||||
user.username.to_lowercase()
|
||||
))
|
||||
.arg(user.id.0)
|
||||
.arg("EX")
|
||||
.arg(DEFAULT_EXPIRY)
|
||||
.query_async::<_, ()>(&mut redis)
|
||||
user.username.to_lowercase(),
|
||||
user.id.0,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
found_users.push(user);
|
||||
}
|
||||
@@ -371,24 +349,19 @@ impl User {
|
||||
|
||||
pub async fn clear_caches(
|
||||
user_ids: &[(UserId, Option<String>)],
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<(), DatabaseError> {
|
||||
let mut redis = redis.get().await?;
|
||||
let mut cmd = cmd("DEL");
|
||||
|
||||
for (id, username) in user_ids {
|
||||
cmd.arg(format!("{}:{}", USERS_NAMESPACE, id.0));
|
||||
if let Some(username) = username {
|
||||
cmd.arg(format!(
|
||||
"{}:{}",
|
||||
USER_USERNAMES_NAMESPACE,
|
||||
username.to_lowercase()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
cmd.query_async::<_, ()>(&mut redis).await?;
|
||||
|
||||
redis
|
||||
.delete_many(user_ids.into_iter().flat_map(|(id, username)| {
|
||||
[
|
||||
(USERS_NAMESPACE, Some(id.0.to_string())),
|
||||
(
|
||||
USER_USERNAMES_NAMESPACE,
|
||||
username.clone().map(|i| i.to_lowercase()),
|
||||
),
|
||||
]
|
||||
}))
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -396,7 +369,7 @@ impl User {
|
||||
id: UserId,
|
||||
full: bool,
|
||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Option<()>, DatabaseError> {
|
||||
let user = Self::get_id(id, &mut *transaction, redis).await?;
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
use super::ids::*;
|
||||
use super::DatabaseError;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::projects::{FileType, VersionStatus};
|
||||
use chrono::{DateTime, Utc};
|
||||
use itertools::Itertools;
|
||||
use redis::cmd;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::HashMap;
|
||||
use std::iter;
|
||||
|
||||
const VERSIONS_NAMESPACE: &str = "versions";
|
||||
const VERSION_FILES_NAMESPACE: &str = "versions_files";
|
||||
const DEFAULT_EXPIRY: i64 = 1800; // 30 minutes
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VersionBuilder {
|
||||
@@ -78,7 +78,7 @@ impl DependencyBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct VersionFileBuilder {
|
||||
pub url: String,
|
||||
pub filename: String,
|
||||
@@ -130,7 +130,7 @@ impl VersionFileBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct HashBuilder {
|
||||
pub algorithm: String,
|
||||
pub hash: Vec<u8>,
|
||||
@@ -263,7 +263,7 @@ impl Version {
|
||||
|
||||
pub async fn remove_full(
|
||||
id: VersionId,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<Option<()>, DatabaseError> {
|
||||
let result = Self::get(id, &mut *transaction, redis).await?;
|
||||
@@ -398,7 +398,7 @@ impl Version {
|
||||
pub async fn get<'a, 'b, E>(
|
||||
id: VersionId,
|
||||
executor: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Option<QueryVersion>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
@@ -411,7 +411,7 @@ impl Version {
|
||||
pub async fn get_many<'a, E>(
|
||||
version_ids: &[VersionId],
|
||||
exec: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Vec<QueryVersion>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
@@ -424,18 +424,10 @@ impl Version {
|
||||
|
||||
let mut version_ids_parsed: Vec<i64> = version_ids.iter().map(|x| x.0).collect();
|
||||
|
||||
let mut redis = redis.get().await?;
|
||||
|
||||
let mut found_versions = Vec::new();
|
||||
|
||||
let versions = cmd("MGET")
|
||||
.arg(
|
||||
version_ids_parsed
|
||||
.iter()
|
||||
.map(|x| format!("{}:{}", VERSIONS_NAMESPACE, x))
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.query_async::<_, Vec<Option<String>>>(&mut redis)
|
||||
let versions = redis
|
||||
.multi_get::<String, _>(VERSIONS_NAMESPACE, version_ids_parsed.clone())
|
||||
.await?;
|
||||
|
||||
for version in versions {
|
||||
@@ -588,12 +580,13 @@ impl Version {
|
||||
.await?;
|
||||
|
||||
for version in db_versions {
|
||||
cmd("SET")
|
||||
.arg(format!("{}:{}", VERSIONS_NAMESPACE, version.inner.id.0))
|
||||
.arg(serde_json::to_string(&version)?)
|
||||
.arg("EX")
|
||||
.arg(DEFAULT_EXPIRY)
|
||||
.query_async::<_, ()>(&mut redis)
|
||||
redis
|
||||
.set(
|
||||
VERSIONS_NAMESPACE,
|
||||
version.inner.id.0,
|
||||
serde_json::to_string(&version)?,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
found_versions.push(version);
|
||||
@@ -608,7 +601,7 @@ impl Version {
|
||||
hash: String,
|
||||
version_id: Option<VersionId>,
|
||||
executor: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Option<SingleFile>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||
@@ -625,7 +618,7 @@ impl Version {
|
||||
algorithm: String,
|
||||
hashes: &[String],
|
||||
executor: E,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Vec<SingleFile>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||
@@ -638,18 +631,16 @@ impl Version {
|
||||
|
||||
let mut file_ids_parsed = hashes.to_vec();
|
||||
|
||||
let mut redis = redis.get().await?;
|
||||
|
||||
let mut found_files = Vec::new();
|
||||
|
||||
let files = cmd("MGET")
|
||||
.arg(
|
||||
let files = redis
|
||||
.multi_get::<String, _>(
|
||||
VERSION_FILES_NAMESPACE,
|
||||
file_ids_parsed
|
||||
.iter()
|
||||
.map(|hash| format!("{}:{}_{}", VERSION_FILES_NAMESPACE, algorithm, hash))
|
||||
.map(|hash| format!("{}_{}", algorithm, hash))
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.query_async::<_, Vec<Option<String>>>(&mut redis)
|
||||
.await?;
|
||||
|
||||
for file in files {
|
||||
@@ -726,12 +717,13 @@ impl Version {
|
||||
}
|
||||
|
||||
for (key, mut files) in save_files {
|
||||
cmd("SET")
|
||||
.arg(format!("{}:{}", VERSION_FILES_NAMESPACE, key))
|
||||
.arg(serde_json::to_string(&files)?)
|
||||
.arg("EX")
|
||||
.arg(DEFAULT_EXPIRY)
|
||||
.query_async::<_, ()>(&mut redis)
|
||||
redis
|
||||
.set(
|
||||
VERSION_FILES_NAMESPACE,
|
||||
key,
|
||||
serde_json::to_string(&files)?,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
found_files.append(&mut files);
|
||||
@@ -743,22 +735,19 @@ impl Version {
|
||||
|
||||
pub async fn clear_cache(
|
||||
version: &QueryVersion,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<(), DatabaseError> {
|
||||
let mut redis = redis.get().await?;
|
||||
|
||||
let mut cmd = cmd("DEL");
|
||||
|
||||
cmd.arg(format!("{}:{}", VERSIONS_NAMESPACE, version.inner.id.0));
|
||||
|
||||
for file in &version.files {
|
||||
for (algo, hash) in &file.hashes {
|
||||
cmd.arg(format!("{}:{}_{}", VERSION_FILES_NAMESPACE, algo, hash));
|
||||
}
|
||||
}
|
||||
|
||||
cmd.query_async::<_, ()>(&mut redis).await?;
|
||||
|
||||
redis
|
||||
.delete_many(
|
||||
iter::once((VERSIONS_NAMESPACE, Some(version.inner.id.0.to_string()))).chain(
|
||||
version.files.iter().flat_map(|file| {
|
||||
file.hashes.iter().map(|(algo, hash)| {
|
||||
(VERSION_FILES_NAMESPACE, Some(format!("{}_{}", algo, hash)))
|
||||
})
|
||||
}),
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
128
src/database/redis.rs
Normal file
128
src/database/redis.rs
Normal file
@@ -0,0 +1,128 @@
|
||||
use super::models::DatabaseError;
|
||||
use deadpool_redis::{Config, Runtime};
|
||||
use redis::{cmd, FromRedisValue, ToRedisArgs};
|
||||
use std::fmt::Display;
|
||||
|
||||
const DEFAULT_EXPIRY: i64 = 1800; // 30 minutes
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RedisPool {
|
||||
pool: deadpool_redis::Pool,
|
||||
meta_namespace: String,
|
||||
}
|
||||
|
||||
impl RedisPool {
|
||||
// initiate a new redis pool
|
||||
// testing pool uses a hashmap to mimic redis behaviour for very small data sizes (ie: tests)
|
||||
// PANICS: production pool will panic if redis url is not set
|
||||
pub fn new(meta_namespace: Option<String>) -> Self {
|
||||
let redis_pool = Config::from_url(dotenvy::var("REDIS_URL").expect("Redis URL not set"))
|
||||
.builder()
|
||||
.expect("Error building Redis pool")
|
||||
.max_size(
|
||||
dotenvy::var("DATABASE_MAX_CONNECTIONS")
|
||||
.ok()
|
||||
.and_then(|x| x.parse().ok())
|
||||
.unwrap_or(10000),
|
||||
)
|
||||
.runtime(Runtime::Tokio1)
|
||||
.build()
|
||||
.expect("Redis connection failed");
|
||||
|
||||
RedisPool {
|
||||
pool: redis_pool,
|
||||
meta_namespace: meta_namespace.unwrap_or("".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn set<T1, T2>(
|
||||
&self,
|
||||
namespace: &str,
|
||||
id: T1,
|
||||
data: T2,
|
||||
expiry: Option<i64>,
|
||||
) -> Result<(), DatabaseError>
|
||||
where
|
||||
T1: Display,
|
||||
T2: ToRedisArgs,
|
||||
{
|
||||
let mut redis_connection = self.pool.get().await?;
|
||||
|
||||
cmd("SET")
|
||||
.arg(format!("{}_{}:{}", self.meta_namespace, namespace, id))
|
||||
.arg(data)
|
||||
.arg("EX")
|
||||
.arg(expiry.unwrap_or(DEFAULT_EXPIRY))
|
||||
.query_async::<_, ()>(&mut redis_connection)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get<R, T1>(&self, namespace: &str, id: T1) -> Result<Option<R>, DatabaseError>
|
||||
where
|
||||
T1: Display,
|
||||
R: FromRedisValue,
|
||||
{
|
||||
let mut redis_connection = self.pool.get().await?;
|
||||
|
||||
let res = cmd("GET")
|
||||
.arg(format!("{}_{}:{}", self.meta_namespace, namespace, id))
|
||||
.query_async::<_, Option<R>>(&mut redis_connection)
|
||||
.await?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub async fn multi_get<R, T1>(
|
||||
&self,
|
||||
namespace: &str,
|
||||
ids: impl IntoIterator<Item = T1>,
|
||||
) -> Result<Vec<Option<R>>, DatabaseError>
|
||||
where
|
||||
T1: Display,
|
||||
R: FromRedisValue,
|
||||
{
|
||||
let mut redis_connection = self.pool.get().await?;
|
||||
let res = cmd("MGET")
|
||||
.arg(
|
||||
ids.into_iter()
|
||||
.map(|x| format!("{}_{}:{}", self.meta_namespace, namespace, x))
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.query_async::<_, Vec<Option<R>>>(&mut redis_connection)
|
||||
.await?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub async fn delete<T1>(&self, namespace: &str, id: T1) -> Result<(), DatabaseError>
|
||||
where
|
||||
T1: Display,
|
||||
{
|
||||
let mut redis_connection = self.pool.get().await?;
|
||||
|
||||
cmd("DEL")
|
||||
.arg(format!("{}_{}:{}", self.meta_namespace, namespace, id))
|
||||
.query_async::<_, ()>(&mut redis_connection)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_many(
|
||||
&self,
|
||||
iter: impl IntoIterator<Item = (&str, Option<String>)>,
|
||||
) -> Result<(), DatabaseError>
|
||||
where {
|
||||
let mut redis_connection = self.pool.get().await?;
|
||||
|
||||
let mut cmd = cmd("DEL");
|
||||
for (namespace, id) in iter {
|
||||
if let Some(id) = id {
|
||||
cmd.arg(format!("{}_{}:{}", self.meta_namespace, namespace, id));
|
||||
}
|
||||
}
|
||||
cmd.query_async::<_, ()>(&mut redis_connection).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
413
src/lib.rs
Normal file
413
src/lib.rs
Normal file
@@ -0,0 +1,413 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use actix_web::web;
|
||||
use database::redis::RedisPool;
|
||||
use log::{info, warn};
|
||||
use queue::{
|
||||
analytics::AnalyticsQueue, download::DownloadQueue, payouts::PayoutsQueue, session::AuthQueue,
|
||||
socket::ActiveSockets,
|
||||
};
|
||||
use scheduler::Scheduler;
|
||||
use sqlx::Postgres;
|
||||
use tokio::sync::{Mutex, RwLock};
|
||||
|
||||
extern crate clickhouse as clickhouse_crate;
|
||||
use clickhouse_crate::Client;
|
||||
use util::cors::default_cors;
|
||||
|
||||
use crate::{
|
||||
queue::payouts::process_payout,
|
||||
search::indexing::index_projects,
|
||||
util::env::{parse_strings_from_var, parse_var},
|
||||
};
|
||||
|
||||
pub mod auth;
|
||||
pub mod clickhouse;
|
||||
pub mod database;
|
||||
pub mod file_hosting;
|
||||
pub mod models;
|
||||
pub mod queue;
|
||||
pub mod ratelimit;
|
||||
pub mod routes;
|
||||
pub mod scheduler;
|
||||
pub mod search;
|
||||
pub mod util;
|
||||
pub mod validate;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Pepper {
|
||||
pub pepper: String,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LabrinthConfig {
|
||||
pub pool: sqlx::Pool<Postgres>,
|
||||
pub redis_pool: RedisPool,
|
||||
pub clickhouse: Client,
|
||||
pub file_host: Arc<dyn file_hosting::FileHost + Send + Sync>,
|
||||
pub maxmind: Arc<queue::maxmind::MaxMindIndexer>,
|
||||
pub scheduler: Arc<Scheduler>,
|
||||
pub ip_salt: Pepper,
|
||||
pub search_config: search::SearchConfig,
|
||||
pub download_queue: web::Data<DownloadQueue>,
|
||||
pub session_queue: web::Data<AuthQueue>,
|
||||
pub payouts_queue: web::Data<Mutex<PayoutsQueue>>,
|
||||
pub analytics_queue: Arc<AnalyticsQueue>,
|
||||
pub active_sockets: web::Data<RwLock<ActiveSockets>>,
|
||||
}
|
||||
|
||||
pub fn app_setup(
|
||||
pool: sqlx::Pool<Postgres>,
|
||||
redis_pool: RedisPool,
|
||||
clickhouse: &mut Client,
|
||||
file_host: Arc<dyn file_hosting::FileHost + Send + Sync>,
|
||||
maxmind: Arc<queue::maxmind::MaxMindIndexer>,
|
||||
) -> LabrinthConfig {
|
||||
info!(
|
||||
"Starting Labrinth on {}",
|
||||
dotenvy::var("BIND_ADDR").unwrap()
|
||||
);
|
||||
|
||||
let search_config = search::SearchConfig {
|
||||
address: dotenvy::var("MEILISEARCH_ADDR").unwrap(),
|
||||
key: dotenvy::var("MEILISEARCH_KEY").unwrap(),
|
||||
};
|
||||
|
||||
let mut scheduler = scheduler::Scheduler::new();
|
||||
|
||||
// The interval in seconds at which the local database is indexed
|
||||
// for searching. Defaults to 1 hour if unset.
|
||||
let local_index_interval =
|
||||
std::time::Duration::from_secs(parse_var("LOCAL_INDEX_INTERVAL").unwrap_or(3600));
|
||||
|
||||
let pool_ref = pool.clone();
|
||||
let search_config_ref = search_config.clone();
|
||||
scheduler.run(local_index_interval, move || {
|
||||
let pool_ref = pool_ref.clone();
|
||||
let search_config_ref = search_config_ref.clone();
|
||||
async move {
|
||||
info!("Indexing local database");
|
||||
let result = index_projects(pool_ref, &search_config_ref).await;
|
||||
if let Err(e) = result {
|
||||
warn!("Local project indexing failed: {:?}", e);
|
||||
}
|
||||
info!("Done indexing local database");
|
||||
}
|
||||
});
|
||||
|
||||
// Changes statuses of scheduled projects/versions
|
||||
let pool_ref = pool.clone();
|
||||
// TODO: Clear cache when these are run
|
||||
scheduler.run(std::time::Duration::from_secs(60 * 5), move || {
|
||||
let pool_ref = pool_ref.clone();
|
||||
info!("Releasing scheduled versions/projects!");
|
||||
|
||||
async move {
|
||||
let projects_results = sqlx::query!(
|
||||
"
|
||||
UPDATE mods
|
||||
SET status = requested_status
|
||||
WHERE status = $1 AND approved < CURRENT_DATE AND requested_status IS NOT NULL
|
||||
",
|
||||
crate::models::projects::ProjectStatus::Scheduled.as_str(),
|
||||
)
|
||||
.execute(&pool_ref)
|
||||
.await;
|
||||
|
||||
if let Err(e) = projects_results {
|
||||
warn!("Syncing scheduled releases for projects failed: {:?}", e);
|
||||
}
|
||||
|
||||
let versions_results = sqlx::query!(
|
||||
"
|
||||
UPDATE versions
|
||||
SET status = requested_status
|
||||
WHERE status = $1 AND date_published < CURRENT_DATE AND requested_status IS NOT NULL
|
||||
",
|
||||
crate::models::projects::VersionStatus::Scheduled.as_str(),
|
||||
)
|
||||
.execute(&pool_ref)
|
||||
.await;
|
||||
|
||||
if let Err(e) = versions_results {
|
||||
warn!("Syncing scheduled releases for versions failed: {:?}", e);
|
||||
}
|
||||
|
||||
info!("Finished releasing scheduled versions/projects");
|
||||
}
|
||||
});
|
||||
|
||||
scheduler::schedule_versions(&mut scheduler, pool.clone());
|
||||
|
||||
let download_queue = web::Data::new(DownloadQueue::new());
|
||||
|
||||
let pool_ref = pool.clone();
|
||||
let download_queue_ref = download_queue.clone();
|
||||
scheduler.run(std::time::Duration::from_secs(60 * 5), move || {
|
||||
let pool_ref = pool_ref.clone();
|
||||
let download_queue_ref = download_queue_ref.clone();
|
||||
|
||||
async move {
|
||||
info!("Indexing download queue");
|
||||
let result = download_queue_ref.index(&pool_ref).await;
|
||||
if let Err(e) = result {
|
||||
warn!("Indexing download queue failed: {:?}", e);
|
||||
}
|
||||
info!("Done indexing download queue");
|
||||
}
|
||||
});
|
||||
|
||||
let session_queue = web::Data::new(AuthQueue::new());
|
||||
|
||||
let pool_ref = pool.clone();
|
||||
let redis_ref = redis_pool.clone();
|
||||
let session_queue_ref = session_queue.clone();
|
||||
scheduler.run(std::time::Duration::from_secs(60 * 30), move || {
|
||||
let pool_ref = pool_ref.clone();
|
||||
let redis_ref = redis_ref.clone();
|
||||
let session_queue_ref = session_queue_ref.clone();
|
||||
|
||||
async move {
|
||||
info!("Indexing sessions queue");
|
||||
let result = session_queue_ref.index(&pool_ref, &redis_ref).await;
|
||||
if let Err(e) = result {
|
||||
warn!("Indexing sessions queue failed: {:?}", e);
|
||||
}
|
||||
info!("Done indexing sessions queue");
|
||||
}
|
||||
});
|
||||
|
||||
let reader = maxmind.clone();
|
||||
{
|
||||
let reader_ref = reader.clone();
|
||||
scheduler.run(std::time::Duration::from_secs(60 * 60 * 24), move || {
|
||||
let reader_ref = reader_ref.clone();
|
||||
|
||||
async move {
|
||||
info!("Downloading MaxMind GeoLite2 country database");
|
||||
let result = reader_ref.index().await;
|
||||
if let Err(e) = result {
|
||||
warn!(
|
||||
"Downloading MaxMind GeoLite2 country database failed: {:?}",
|
||||
e
|
||||
);
|
||||
}
|
||||
info!("Done downloading MaxMind GeoLite2 country database");
|
||||
}
|
||||
});
|
||||
}
|
||||
info!("Downloading MaxMind GeoLite2 country database");
|
||||
|
||||
let analytics_queue = Arc::new(AnalyticsQueue::new());
|
||||
{
|
||||
let client_ref = clickhouse.clone();
|
||||
let analytics_queue_ref = analytics_queue.clone();
|
||||
scheduler.run(std::time::Duration::from_secs(60 * 5), move || {
|
||||
let client_ref = client_ref.clone();
|
||||
let analytics_queue_ref = analytics_queue_ref.clone();
|
||||
|
||||
async move {
|
||||
info!("Indexing analytics queue");
|
||||
let result = analytics_queue_ref.index(client_ref).await;
|
||||
if let Err(e) = result {
|
||||
warn!("Indexing analytics queue failed: {:?}", e);
|
||||
}
|
||||
info!("Done indexing analytics queue");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
let pool_ref = pool.clone();
|
||||
let redis_ref = redis_pool.clone();
|
||||
let client_ref = clickhouse.clone();
|
||||
scheduler.run(std::time::Duration::from_secs(60 * 60 * 6), move || {
|
||||
let pool_ref = pool_ref.clone();
|
||||
let redis_ref = redis_ref.clone();
|
||||
let client_ref = client_ref.clone();
|
||||
|
||||
async move {
|
||||
info!("Started running payouts");
|
||||
let result = process_payout(&pool_ref, &redis_ref, &client_ref).await;
|
||||
if let Err(e) = result {
|
||||
warn!("Payouts run failed: {:?}", e);
|
||||
}
|
||||
info!("Done running payouts");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let ip_salt = Pepper {
|
||||
pepper: models::ids::Base62Id(models::ids::random_base62(11)).to_string(),
|
||||
};
|
||||
|
||||
let payouts_queue = web::Data::new(Mutex::new(PayoutsQueue::new()));
|
||||
let active_sockets = web::Data::new(RwLock::new(ActiveSockets::default()));
|
||||
|
||||
LabrinthConfig {
|
||||
pool,
|
||||
redis_pool,
|
||||
clickhouse: clickhouse.clone(),
|
||||
file_host,
|
||||
maxmind,
|
||||
scheduler: Arc::new(scheduler),
|
||||
ip_salt,
|
||||
download_queue,
|
||||
search_config,
|
||||
session_queue,
|
||||
payouts_queue,
|
||||
analytics_queue,
|
||||
active_sockets,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn app_config(cfg: &mut web::ServiceConfig, labrinth_config: LabrinthConfig) {
|
||||
cfg.app_data(
|
||||
web::FormConfig::default()
|
||||
.error_handler(|err, _req| routes::ApiError::Validation(err.to_string()).into()),
|
||||
)
|
||||
.app_data(
|
||||
web::PathConfig::default()
|
||||
.error_handler(|err, _req| routes::ApiError::Validation(err.to_string()).into()),
|
||||
)
|
||||
.app_data(
|
||||
web::QueryConfig::default()
|
||||
.error_handler(|err, _req| routes::ApiError::Validation(err.to_string()).into()),
|
||||
)
|
||||
.app_data(
|
||||
web::JsonConfig::default()
|
||||
.error_handler(|err, _req| routes::ApiError::Validation(err.to_string()).into()),
|
||||
)
|
||||
.app_data(web::Data::new(labrinth_config.redis_pool.clone()))
|
||||
.app_data(web::Data::new(labrinth_config.pool.clone()))
|
||||
.app_data(web::Data::new(labrinth_config.file_host.clone()))
|
||||
.app_data(web::Data::new(labrinth_config.search_config.clone()))
|
||||
.app_data(labrinth_config.download_queue.clone())
|
||||
.app_data(labrinth_config.session_queue.clone())
|
||||
.app_data(labrinth_config.payouts_queue.clone())
|
||||
.app_data(web::Data::new(labrinth_config.ip_salt.clone()))
|
||||
.app_data(web::Data::new(labrinth_config.analytics_queue.clone()))
|
||||
.app_data(web::Data::new(labrinth_config.clickhouse.clone()))
|
||||
.app_data(web::Data::new(labrinth_config.maxmind.clone()))
|
||||
.app_data(labrinth_config.active_sockets.clone())
|
||||
.configure(routes::v2::config)
|
||||
.configure(routes::v3::config)
|
||||
.configure(routes::root_config)
|
||||
.default_service(web::get().wrap(default_cors()).to(routes::not_found));
|
||||
}
|
||||
|
||||
// This is so that env vars not used immediately don't panic at runtime
|
||||
pub fn check_env_vars() -> bool {
|
||||
let mut failed = false;
|
||||
|
||||
fn check_var<T: std::str::FromStr>(var: &'static str) -> bool {
|
||||
let check = parse_var::<T>(var).is_none();
|
||||
if check {
|
||||
warn!(
|
||||
"Variable `{}` missing in dotenv or not of type `{}`",
|
||||
var,
|
||||
std::any::type_name::<T>()
|
||||
);
|
||||
}
|
||||
check
|
||||
}
|
||||
|
||||
failed |= check_var::<String>("SITE_URL");
|
||||
failed |= check_var::<String>("CDN_URL");
|
||||
failed |= check_var::<String>("LABRINTH_ADMIN_KEY");
|
||||
failed |= check_var::<String>("RATE_LIMIT_IGNORE_KEY");
|
||||
failed |= check_var::<String>("DATABASE_URL");
|
||||
failed |= check_var::<String>("MEILISEARCH_ADDR");
|
||||
failed |= check_var::<String>("MEILISEARCH_KEY");
|
||||
failed |= check_var::<String>("REDIS_URL");
|
||||
failed |= check_var::<String>("BIND_ADDR");
|
||||
failed |= check_var::<String>("SELF_ADDR");
|
||||
|
||||
failed |= check_var::<String>("STORAGE_BACKEND");
|
||||
|
||||
let storage_backend = dotenvy::var("STORAGE_BACKEND").ok();
|
||||
match storage_backend.as_deref() {
|
||||
Some("backblaze") => {
|
||||
failed |= check_var::<String>("BACKBLAZE_KEY_ID");
|
||||
failed |= check_var::<String>("BACKBLAZE_KEY");
|
||||
failed |= check_var::<String>("BACKBLAZE_BUCKET_ID");
|
||||
}
|
||||
Some("s3") => {
|
||||
failed |= check_var::<String>("S3_ACCESS_TOKEN");
|
||||
failed |= check_var::<String>("S3_SECRET");
|
||||
failed |= check_var::<String>("S3_URL");
|
||||
failed |= check_var::<String>("S3_REGION");
|
||||
failed |= check_var::<String>("S3_BUCKET_NAME");
|
||||
}
|
||||
Some("local") => {
|
||||
failed |= check_var::<String>("MOCK_FILE_PATH");
|
||||
}
|
||||
Some(backend) => {
|
||||
warn!("Variable `STORAGE_BACKEND` contains an invalid value: {}. Expected \"backblaze\", \"s3\", or \"local\".", backend);
|
||||
failed |= true;
|
||||
}
|
||||
_ => {
|
||||
warn!("Variable `STORAGE_BACKEND` is not set!");
|
||||
failed |= true;
|
||||
}
|
||||
}
|
||||
|
||||
failed |= check_var::<usize>("LOCAL_INDEX_INTERVAL");
|
||||
failed |= check_var::<usize>("VERSION_INDEX_INTERVAL");
|
||||
|
||||
if parse_strings_from_var("WHITELISTED_MODPACK_DOMAINS").is_none() {
|
||||
warn!("Variable `WHITELISTED_MODPACK_DOMAINS` missing in dotenv or not a json array of strings");
|
||||
failed |= true;
|
||||
}
|
||||
|
||||
if parse_strings_from_var("ALLOWED_CALLBACK_URLS").is_none() {
|
||||
warn!("Variable `ALLOWED_CALLBACK_URLS` missing in dotenv or not a json array of strings");
|
||||
failed |= true;
|
||||
}
|
||||
|
||||
failed |= check_var::<String>("PAYPAL_API_URL");
|
||||
failed |= check_var::<String>("PAYPAL_CLIENT_ID");
|
||||
failed |= check_var::<String>("PAYPAL_CLIENT_SECRET");
|
||||
|
||||
failed |= check_var::<String>("GITHUB_CLIENT_ID");
|
||||
failed |= check_var::<String>("GITHUB_CLIENT_SECRET");
|
||||
failed |= check_var::<String>("GITLAB_CLIENT_ID");
|
||||
failed |= check_var::<String>("GITLAB_CLIENT_SECRET");
|
||||
failed |= check_var::<String>("DISCORD_CLIENT_ID");
|
||||
failed |= check_var::<String>("DISCORD_CLIENT_SECRET");
|
||||
failed |= check_var::<String>("MICROSOFT_CLIENT_ID");
|
||||
failed |= check_var::<String>("MICROSOFT_CLIENT_SECRET");
|
||||
failed |= check_var::<String>("GOOGLE_CLIENT_ID");
|
||||
failed |= check_var::<String>("GOOGLE_CLIENT_SECRET");
|
||||
failed |= check_var::<String>("STEAM_API_KEY");
|
||||
|
||||
failed |= check_var::<String>("TURNSTILE_SECRET");
|
||||
|
||||
failed |= check_var::<String>("SMTP_USERNAME");
|
||||
failed |= check_var::<String>("SMTP_PASSWORD");
|
||||
failed |= check_var::<String>("SMTP_HOST");
|
||||
|
||||
failed |= check_var::<String>("SITE_VERIFY_EMAIL_PATH");
|
||||
failed |= check_var::<String>("SITE_RESET_PASSWORD_PATH");
|
||||
|
||||
failed |= check_var::<String>("BEEHIIV_PUBLICATION_ID");
|
||||
failed |= check_var::<String>("BEEHIIV_API_KEY");
|
||||
|
||||
if parse_strings_from_var("ANALYTICS_ALLOWED_ORIGINS").is_none() {
|
||||
warn!(
|
||||
"Variable `ANALYTICS_ALLOWED_ORIGINS` missing in dotenv or not a json array of strings"
|
||||
);
|
||||
failed |= true;
|
||||
}
|
||||
|
||||
failed |= check_var::<String>("CLICKHOUSE_URL");
|
||||
failed |= check_var::<String>("CLICKHOUSE_USER");
|
||||
failed |= check_var::<String>("CLICKHOUSE_PASSWORD");
|
||||
failed |= check_var::<String>("CLICKHOUSE_DATABASE");
|
||||
|
||||
failed |= check_var::<String>("MAXMIND_LICENSE_KEY");
|
||||
|
||||
failed |= check_var::<u64>("PAYOUTS_BUDGET");
|
||||
|
||||
failed
|
||||
}
|
||||
391
src/main.rs
391
src/main.rs
@@ -1,34 +1,15 @@
|
||||
use crate::file_hosting::S3Host;
|
||||
use crate::queue::analytics::AnalyticsQueue;
|
||||
use crate::queue::download::DownloadQueue;
|
||||
use crate::queue::payouts::{process_payout, PayoutsQueue};
|
||||
use crate::queue::session::AuthQueue;
|
||||
use crate::queue::socket::ActiveSockets;
|
||||
use crate::ratelimit::errors::ARError;
|
||||
use crate::ratelimit::memory::{MemoryStore, MemoryStoreActor};
|
||||
use crate::ratelimit::middleware::RateLimiter;
|
||||
use crate::util::cors::default_cors;
|
||||
use crate::util::env::{parse_strings_from_var, parse_var};
|
||||
use actix_web::{web, App, HttpServer};
|
||||
use deadpool_redis::{Config, Runtime};
|
||||
use actix_web::{App, HttpServer};
|
||||
use env_logger::Env;
|
||||
use log::{error, info, warn};
|
||||
use search::indexing::index_projects;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{Mutex, RwLock};
|
||||
use labrinth::database::redis::RedisPool;
|
||||
use labrinth::file_hosting::S3Host;
|
||||
use labrinth::ratelimit::errors::ARError;
|
||||
use labrinth::ratelimit::memory::{MemoryStore, MemoryStoreActor};
|
||||
use labrinth::ratelimit::middleware::RateLimiter;
|
||||
use labrinth::util::env::parse_var;
|
||||
use labrinth::{check_env_vars, clickhouse, database, file_hosting, queue};
|
||||
use log::{error, info};
|
||||
|
||||
mod auth;
|
||||
mod clickhouse;
|
||||
mod database;
|
||||
mod file_hosting;
|
||||
mod models;
|
||||
mod queue;
|
||||
mod ratelimit;
|
||||
mod routes;
|
||||
mod scheduler;
|
||||
mod search;
|
||||
mod util;
|
||||
mod validate;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Pepper {
|
||||
@@ -63,11 +44,6 @@ async fn main() -> std::io::Result<()> {
|
||||
dotenvy::var("BIND_ADDR").unwrap()
|
||||
);
|
||||
|
||||
let search_config = search::SearchConfig {
|
||||
address: dotenvy::var("MEILISEARCH_ADDR").unwrap(),
|
||||
key: dotenvy::var("MEILISEARCH_KEY").unwrap(),
|
||||
};
|
||||
|
||||
database::check_for_migrations()
|
||||
.await
|
||||
.expect("An error occurred while running migrations.");
|
||||
@@ -78,18 +54,7 @@ async fn main() -> std::io::Result<()> {
|
||||
.expect("Database connection failed");
|
||||
|
||||
// Redis connector
|
||||
let redis_pool = Config::from_url(dotenvy::var("REDIS_URL").expect("Redis URL not set"))
|
||||
.builder()
|
||||
.expect("Error building Redis pool")
|
||||
.max_size(
|
||||
dotenvy::var("DATABASE_MAX_CONNECTIONS")
|
||||
.ok()
|
||||
.and_then(|x| x.parse().ok())
|
||||
.unwrap_or(10000),
|
||||
)
|
||||
.runtime(Runtime::Tokio1)
|
||||
.build()
|
||||
.expect("Redis connection failed");
|
||||
let redis_pool = RedisPool::new(None);
|
||||
|
||||
let storage_backend = dotenvy::var("STORAGE_BACKEND").unwrap_or_else(|_| "local".to_string());
|
||||
|
||||
@@ -116,184 +81,23 @@ async fn main() -> std::io::Result<()> {
|
||||
_ => panic!("Invalid storage backend specified. Aborting startup!"),
|
||||
};
|
||||
|
||||
let mut scheduler = scheduler::Scheduler::new();
|
||||
|
||||
// The interval in seconds at which the local database is indexed
|
||||
// for searching. Defaults to 1 hour if unset.
|
||||
let local_index_interval =
|
||||
std::time::Duration::from_secs(parse_var("LOCAL_INDEX_INTERVAL").unwrap_or(3600));
|
||||
|
||||
let pool_ref = pool.clone();
|
||||
let search_config_ref = search_config.clone();
|
||||
scheduler.run(local_index_interval, move || {
|
||||
let pool_ref = pool_ref.clone();
|
||||
let search_config_ref = search_config_ref.clone();
|
||||
async move {
|
||||
info!("Indexing local database");
|
||||
let result = index_projects(pool_ref, &search_config_ref).await;
|
||||
if let Err(e) = result {
|
||||
warn!("Local project indexing failed: {:?}", e);
|
||||
}
|
||||
info!("Done indexing local database");
|
||||
}
|
||||
});
|
||||
|
||||
// Changes statuses of scheduled projects/versions
|
||||
let pool_ref = pool.clone();
|
||||
// TODO: Clear cache when these are run
|
||||
scheduler.run(std::time::Duration::from_secs(60 * 5), move || {
|
||||
let pool_ref = pool_ref.clone();
|
||||
info!("Releasing scheduled versions/projects!");
|
||||
|
||||
async move {
|
||||
let projects_results = sqlx::query!(
|
||||
"
|
||||
UPDATE mods
|
||||
SET status = requested_status
|
||||
WHERE status = $1 AND approved < CURRENT_DATE AND requested_status IS NOT NULL
|
||||
",
|
||||
crate::models::projects::ProjectStatus::Scheduled.as_str(),
|
||||
)
|
||||
.execute(&pool_ref)
|
||||
.await;
|
||||
|
||||
if let Err(e) = projects_results {
|
||||
warn!("Syncing scheduled releases for projects failed: {:?}", e);
|
||||
}
|
||||
|
||||
let versions_results = sqlx::query!(
|
||||
"
|
||||
UPDATE versions
|
||||
SET status = requested_status
|
||||
WHERE status = $1 AND date_published < CURRENT_DATE AND requested_status IS NOT NULL
|
||||
",
|
||||
crate::models::projects::VersionStatus::Scheduled.as_str(),
|
||||
)
|
||||
.execute(&pool_ref)
|
||||
.await;
|
||||
|
||||
if let Err(e) = versions_results {
|
||||
warn!("Syncing scheduled releases for versions failed: {:?}", e);
|
||||
}
|
||||
|
||||
info!("Finished releasing scheduled versions/projects");
|
||||
}
|
||||
});
|
||||
|
||||
scheduler::schedule_versions(&mut scheduler, pool.clone());
|
||||
|
||||
let download_queue = web::Data::new(DownloadQueue::new());
|
||||
|
||||
let pool_ref = pool.clone();
|
||||
let download_queue_ref = download_queue.clone();
|
||||
scheduler.run(std::time::Duration::from_secs(60 * 5), move || {
|
||||
let pool_ref = pool_ref.clone();
|
||||
let download_queue_ref = download_queue_ref.clone();
|
||||
|
||||
async move {
|
||||
info!("Indexing download queue");
|
||||
let result = download_queue_ref.index(&pool_ref).await;
|
||||
if let Err(e) = result {
|
||||
warn!("Indexing download queue failed: {:?}", e);
|
||||
}
|
||||
info!("Done indexing download queue");
|
||||
}
|
||||
});
|
||||
|
||||
let session_queue = web::Data::new(AuthQueue::new());
|
||||
|
||||
let pool_ref = pool.clone();
|
||||
let redis_ref = redis_pool.clone();
|
||||
let session_queue_ref = session_queue.clone();
|
||||
scheduler.run(std::time::Duration::from_secs(60 * 30), move || {
|
||||
let pool_ref = pool_ref.clone();
|
||||
let redis_ref = redis_ref.clone();
|
||||
let session_queue_ref = session_queue_ref.clone();
|
||||
|
||||
async move {
|
||||
info!("Indexing sessions queue");
|
||||
let result = session_queue_ref.index(&pool_ref, &redis_ref).await;
|
||||
if let Err(e) = result {
|
||||
warn!("Indexing sessions queue failed: {:?}", e);
|
||||
}
|
||||
info!("Done indexing sessions queue");
|
||||
}
|
||||
});
|
||||
|
||||
info!("Initializing clickhouse connection");
|
||||
let clickhouse = clickhouse::init_client().await.unwrap();
|
||||
let mut clickhouse = clickhouse::init_client().await.unwrap();
|
||||
|
||||
let reader = Arc::new(queue::maxmind::MaxMindIndexer::new().await.unwrap());
|
||||
{
|
||||
let reader_ref = reader.clone();
|
||||
scheduler.run(std::time::Duration::from_secs(60 * 60 * 24), move || {
|
||||
let reader_ref = reader_ref.clone();
|
||||
|
||||
async move {
|
||||
info!("Downloading MaxMind GeoLite2 country database");
|
||||
let result = reader_ref.index().await;
|
||||
if let Err(e) = result {
|
||||
warn!(
|
||||
"Downloading MaxMind GeoLite2 country database failed: {:?}",
|
||||
e
|
||||
);
|
||||
}
|
||||
info!("Done downloading MaxMind GeoLite2 country database");
|
||||
}
|
||||
});
|
||||
}
|
||||
info!("Downloading MaxMind GeoLite2 country database");
|
||||
|
||||
let analytics_queue = Arc::new(AnalyticsQueue::new());
|
||||
{
|
||||
let client_ref = clickhouse.clone();
|
||||
let analytics_queue_ref = analytics_queue.clone();
|
||||
scheduler.run(std::time::Duration::from_secs(60 * 5), move || {
|
||||
let client_ref = client_ref.clone();
|
||||
let analytics_queue_ref = analytics_queue_ref.clone();
|
||||
|
||||
async move {
|
||||
info!("Indexing analytics queue");
|
||||
let result = analytics_queue_ref.index(client_ref).await;
|
||||
if let Err(e) = result {
|
||||
warn!("Indexing analytics queue failed: {:?}", e);
|
||||
}
|
||||
info!("Done indexing analytics queue");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
let pool_ref = pool.clone();
|
||||
let redis_ref = redis_pool.clone();
|
||||
let client_ref = clickhouse.clone();
|
||||
scheduler.run(std::time::Duration::from_secs(60 * 60 * 6), move || {
|
||||
let pool_ref = pool_ref.clone();
|
||||
let redis_ref = redis_ref.clone();
|
||||
let client_ref = client_ref.clone();
|
||||
|
||||
async move {
|
||||
info!("Started running payouts");
|
||||
let result = process_payout(&pool_ref, &redis_ref, &client_ref).await;
|
||||
if let Err(e) = result {
|
||||
warn!("Payouts run failed: {:?}", e);
|
||||
}
|
||||
info!("Done running payouts");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let ip_salt = Pepper {
|
||||
pepper: models::ids::Base62Id(models::ids::random_base62(11)).to_string(),
|
||||
};
|
||||
|
||||
let payouts_queue = web::Data::new(Mutex::new(PayoutsQueue::new()));
|
||||
let active_sockets = web::Data::new(RwLock::new(ActiveSockets::default()));
|
||||
let maxmind_reader = Arc::new(queue::maxmind::MaxMindIndexer::new().await.unwrap());
|
||||
|
||||
let store = MemoryStore::new();
|
||||
|
||||
info!("Starting Actix HTTP server!");
|
||||
|
||||
let labrinth_config = labrinth::app_setup(
|
||||
pool.clone(),
|
||||
redis_pool.clone(),
|
||||
&mut clickhouse,
|
||||
file_host.clone(),
|
||||
maxmind_reader.clone(),
|
||||
);
|
||||
|
||||
// Init App
|
||||
HttpServer::new(move || {
|
||||
App::new()
|
||||
@@ -320,160 +124,9 @@ async fn main() -> std::io::Result<()> {
|
||||
.with_ignore_key(dotenvy::var("RATE_LIMIT_IGNORE_KEY").ok()),
|
||||
)
|
||||
.wrap(sentry_actix::Sentry::new())
|
||||
.app_data(
|
||||
web::FormConfig::default().error_handler(|err, _req| {
|
||||
routes::ApiError::Validation(err.to_string()).into()
|
||||
}),
|
||||
)
|
||||
.app_data(
|
||||
web::PathConfig::default().error_handler(|err, _req| {
|
||||
routes::ApiError::Validation(err.to_string()).into()
|
||||
}),
|
||||
)
|
||||
.app_data(
|
||||
web::QueryConfig::default().error_handler(|err, _req| {
|
||||
routes::ApiError::Validation(err.to_string()).into()
|
||||
}),
|
||||
)
|
||||
.app_data(
|
||||
web::JsonConfig::default().error_handler(|err, _req| {
|
||||
routes::ApiError::Validation(err.to_string()).into()
|
||||
}),
|
||||
)
|
||||
.app_data(web::Data::new(redis_pool.clone()))
|
||||
.app_data(web::Data::new(pool.clone()))
|
||||
.app_data(web::Data::new(file_host.clone()))
|
||||
.app_data(web::Data::new(search_config.clone()))
|
||||
.app_data(download_queue.clone())
|
||||
.app_data(session_queue.clone())
|
||||
.app_data(payouts_queue.clone())
|
||||
.app_data(web::Data::new(ip_salt.clone()))
|
||||
.app_data(web::Data::new(analytics_queue.clone()))
|
||||
.app_data(web::Data::new(clickhouse.clone()))
|
||||
.app_data(web::Data::new(reader.clone()))
|
||||
.app_data(active_sockets.clone())
|
||||
.configure(routes::v2::config)
|
||||
.configure(routes::v3::config)
|
||||
.configure(routes::root_config)
|
||||
.default_service(web::get().wrap(default_cors()).to(routes::not_found))
|
||||
.configure(|cfg| labrinth::app_config(cfg, labrinth_config.clone()))
|
||||
})
|
||||
.bind(dotenvy::var("BIND_ADDR").unwrap())?
|
||||
.run()
|
||||
.await
|
||||
}
|
||||
|
||||
// This is so that env vars not used immediately don't panic at runtime
|
||||
fn check_env_vars() -> bool {
|
||||
let mut failed = false;
|
||||
|
||||
fn check_var<T: std::str::FromStr>(var: &'static str) -> bool {
|
||||
let check = parse_var::<T>(var).is_none();
|
||||
if check {
|
||||
warn!(
|
||||
"Variable `{}` missing in dotenv or not of type `{}`",
|
||||
var,
|
||||
std::any::type_name::<T>()
|
||||
);
|
||||
}
|
||||
check
|
||||
}
|
||||
|
||||
failed |= check_var::<String>("SITE_URL");
|
||||
failed |= check_var::<String>("CDN_URL");
|
||||
failed |= check_var::<String>("LABRINTH_ADMIN_KEY");
|
||||
failed |= check_var::<String>("RATE_LIMIT_IGNORE_KEY");
|
||||
failed |= check_var::<String>("DATABASE_URL");
|
||||
failed |= check_var::<String>("MEILISEARCH_ADDR");
|
||||
failed |= check_var::<String>("MEILISEARCH_KEY");
|
||||
failed |= check_var::<String>("REDIS_URL");
|
||||
failed |= check_var::<String>("BIND_ADDR");
|
||||
failed |= check_var::<String>("SELF_ADDR");
|
||||
|
||||
failed |= check_var::<String>("STORAGE_BACKEND");
|
||||
|
||||
let storage_backend = dotenvy::var("STORAGE_BACKEND").ok();
|
||||
match storage_backend.as_deref() {
|
||||
Some("backblaze") => {
|
||||
failed |= check_var::<String>("BACKBLAZE_KEY_ID");
|
||||
failed |= check_var::<String>("BACKBLAZE_KEY");
|
||||
failed |= check_var::<String>("BACKBLAZE_BUCKET_ID");
|
||||
}
|
||||
Some("s3") => {
|
||||
failed |= check_var::<String>("S3_ACCESS_TOKEN");
|
||||
failed |= check_var::<String>("S3_SECRET");
|
||||
failed |= check_var::<String>("S3_URL");
|
||||
failed |= check_var::<String>("S3_REGION");
|
||||
failed |= check_var::<String>("S3_BUCKET_NAME");
|
||||
}
|
||||
Some("local") => {
|
||||
failed |= check_var::<String>("MOCK_FILE_PATH");
|
||||
}
|
||||
Some(backend) => {
|
||||
warn!("Variable `STORAGE_BACKEND` contains an invalid value: {}. Expected \"backblaze\", \"s3\", or \"local\".", backend);
|
||||
failed |= true;
|
||||
}
|
||||
_ => {
|
||||
warn!("Variable `STORAGE_BACKEND` is not set!");
|
||||
failed |= true;
|
||||
}
|
||||
}
|
||||
|
||||
failed |= check_var::<usize>("LOCAL_INDEX_INTERVAL");
|
||||
failed |= check_var::<usize>("VERSION_INDEX_INTERVAL");
|
||||
|
||||
if parse_strings_from_var("WHITELISTED_MODPACK_DOMAINS").is_none() {
|
||||
warn!("Variable `WHITELISTED_MODPACK_DOMAINS` missing in dotenv or not a json array of strings");
|
||||
failed |= true;
|
||||
}
|
||||
|
||||
if parse_strings_from_var("ALLOWED_CALLBACK_URLS").is_none() {
|
||||
warn!("Variable `ALLOWED_CALLBACK_URLS` missing in dotenv or not a json array of strings");
|
||||
failed |= true;
|
||||
}
|
||||
|
||||
failed |= check_var::<String>("PAYPAL_API_URL");
|
||||
failed |= check_var::<String>("PAYPAL_CLIENT_ID");
|
||||
failed |= check_var::<String>("PAYPAL_CLIENT_SECRET");
|
||||
|
||||
failed |= check_var::<String>("GITHUB_CLIENT_ID");
|
||||
failed |= check_var::<String>("GITHUB_CLIENT_SECRET");
|
||||
failed |= check_var::<String>("GITLAB_CLIENT_ID");
|
||||
failed |= check_var::<String>("GITLAB_CLIENT_SECRET");
|
||||
failed |= check_var::<String>("DISCORD_CLIENT_ID");
|
||||
failed |= check_var::<String>("DISCORD_CLIENT_SECRET");
|
||||
failed |= check_var::<String>("MICROSOFT_CLIENT_ID");
|
||||
failed |= check_var::<String>("MICROSOFT_CLIENT_SECRET");
|
||||
failed |= check_var::<String>("GOOGLE_CLIENT_ID");
|
||||
failed |= check_var::<String>("GOOGLE_CLIENT_SECRET");
|
||||
failed |= check_var::<String>("STEAM_API_KEY");
|
||||
|
||||
failed |= check_var::<String>("TURNSTILE_SECRET");
|
||||
|
||||
failed |= check_var::<String>("SMTP_USERNAME");
|
||||
failed |= check_var::<String>("SMTP_PASSWORD");
|
||||
failed |= check_var::<String>("SMTP_HOST");
|
||||
|
||||
failed |= check_var::<String>("SITE_VERIFY_EMAIL_PATH");
|
||||
failed |= check_var::<String>("SITE_RESET_PASSWORD_PATH");
|
||||
|
||||
failed |= check_var::<String>("BEEHIIV_PUBLICATION_ID");
|
||||
failed |= check_var::<String>("BEEHIIV_API_KEY");
|
||||
|
||||
if parse_strings_from_var("ANALYTICS_ALLOWED_ORIGINS").is_none() {
|
||||
warn!(
|
||||
"Variable `ANALYTICS_ALLOWED_ORIGINS` missing in dotenv or not a json array of strings"
|
||||
);
|
||||
failed |= true;
|
||||
}
|
||||
|
||||
failed |= check_var::<String>("CLICKHOUSE_URL");
|
||||
failed |= check_var::<String>("CLICKHOUSE_USER");
|
||||
failed |= check_var::<String>("CLICKHOUSE_PASSWORD");
|
||||
failed |= check_var::<String>("CLICKHOUSE_DATABASE");
|
||||
|
||||
failed |= check_var::<String>("MAXMIND_LICENSE_KEY");
|
||||
|
||||
failed |= check_var::<u64>("PAYOUTS_BUDGET");
|
||||
|
||||
failed
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::models::projects::SideType;
|
||||
use crate::parse_strings_from_var;
|
||||
use crate::{models::projects::SideType, util::env::parse_strings_from_var};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator::Validate;
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ bitflags::bitflags! {
|
||||
const VERSION_READ = 1 << 15;
|
||||
// write to a version's data (metadata, files, etc)
|
||||
const VERSION_WRITE = 1 << 16;
|
||||
// delete a project
|
||||
// delete a version
|
||||
const VERSION_DELETE = 1 << 17;
|
||||
|
||||
// create a report
|
||||
@@ -103,26 +103,26 @@ bitflags::bitflags! {
|
||||
// delete an organization
|
||||
const ORGANIZATION_DELETE = 1 << 38;
|
||||
|
||||
const ALL = 0b111111111111111111111111111111111111111;
|
||||
const NOT_RESTRICTED = 0b1111111100000011111111111111100111;
|
||||
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
|
||||
| Scopes::USER_DELETE
|
||||
| Scopes::PERFORM_ANALYTICS,
|
||||
)
|
||||
pub fn restricted() -> Scopes {
|
||||
Scopes::PAT_CREATE
|
||||
| Scopes::PAT_READ
|
||||
| Scopes::PAT_WRITE
|
||||
| Scopes::PAT_DELETE
|
||||
| Scopes::SESSION_READ
|
||||
| Scopes::SESSION_DELETE
|
||||
| Scopes::USER_AUTH_WRITE
|
||||
| Scopes::USER_DELETE
|
||||
| Scopes::PERFORM_ANALYTICS
|
||||
}
|
||||
|
||||
pub fn is_restricted(&self) -> bool {
|
||||
self.intersects(Self::restricted())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ use chrono::{DateTime, Utc};
|
||||
use rust_decimal::Decimal;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
|
||||
#[serde(from = "Base62Id")]
|
||||
#[serde(into = "Base62Id")]
|
||||
pub struct UserId(pub u64);
|
||||
@@ -35,7 +35,7 @@ impl Default for Badges {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct User {
|
||||
pub id: UserId,
|
||||
pub username: String,
|
||||
@@ -57,7 +57,7 @@ pub struct User {
|
||||
pub github_id: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct UserPayoutData {
|
||||
pub balance: Decimal,
|
||||
pub payout_wallet: Option<RecipientWallet>,
|
||||
@@ -156,7 +156,7 @@ impl From<DBUser> for User {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)]
|
||||
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum Role {
|
||||
Developer,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::models::projects::MonetizationStatus;
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::env::parse_var;
|
||||
use crate::{database::redis::RedisPool, models::projects::MonetizationStatus};
|
||||
use base64::Engine;
|
||||
use chrono::{DateTime, Datelike, Duration, Utc, Weekday};
|
||||
use rust_decimal::Decimal;
|
||||
@@ -203,7 +203,7 @@ impl PayoutsQueue {
|
||||
|
||||
pub async fn process_payout(
|
||||
pool: &PgPool,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
client: &clickhouse::Client,
|
||||
) -> Result<(), ApiError> {
|
||||
let start: DateTime<Utc> = DateTime::from_utc(
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::auth::session::SessionMetadata;
|
||||
use crate::database::models::pat_item::PersonalAccessToken;
|
||||
use crate::database::models::session_item::Session;
|
||||
use crate::database::models::{DatabaseError, PatId, SessionId, UserId};
|
||||
use crate::database::redis::RedisPool;
|
||||
use chrono::Utc;
|
||||
use sqlx::PgPool;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
@@ -42,11 +43,7 @@ impl AuthQueue {
|
||||
std::mem::replace(&mut queue, HashSet::with_capacity(len))
|
||||
}
|
||||
|
||||
pub async fn index(
|
||||
&self,
|
||||
pool: &PgPool,
|
||||
redis: &deadpool_redis::Pool,
|
||||
) -> Result<(), DatabaseError> {
|
||||
pub async fn index(&self, pool: &PgPool, redis: &RedisPool) -> Result<(), DatabaseError> {
|
||||
let session_queue = self.take_sessions().await;
|
||||
let pat_queue = self.take_pats().await;
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ impl MemoryStore {
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use actix_ratelimit::MemoryStore;
|
||||
/// use labrinth::ratelimit::memory::MemoryStore;
|
||||
///
|
||||
/// let store = MemoryStore::new();
|
||||
/// ```
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use crate::auth::get_user_from_headers;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::analytics::{PageView, Playtime};
|
||||
use crate::models::pats::Scopes;
|
||||
use crate::queue::analytics::AnalyticsQueue;
|
||||
use crate::queue::maxmind::MaxMindIndexer;
|
||||
use crate::queue::session::AuthQueue;
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::env::parse_strings_from_var;
|
||||
use crate::AnalyticsQueue;
|
||||
use actix_web::{post, web};
|
||||
use actix_web::{HttpRequest, HttpResponse};
|
||||
use chrono::Utc;
|
||||
@@ -63,7 +64,7 @@ pub async fn page_view_ingest(
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
url_input: web::Json<UrlInput>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue, None)
|
||||
.await
|
||||
@@ -169,7 +170,7 @@ pub async fn playtime_ingest(
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
playtime_input: web::Json<HashMap<crate::models::ids::VersionId, PlaytimeInput>>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let (_, user) = get_user_from_headers(
|
||||
&req,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::database::models::categories::Loader;
|
||||
use crate::database::models::project_item::QueryProject;
|
||||
use crate::database::models::version_item::{QueryFile, QueryVersion};
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::pats::Scopes;
|
||||
use crate::models::projects::{ProjectId, VersionId};
|
||||
use crate::queue::session::AuthQueue;
|
||||
@@ -71,7 +72,7 @@ pub async fn maven_metadata(
|
||||
req: HttpRequest,
|
||||
params: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let project_id = params.into_inner().0;
|
||||
@@ -156,7 +157,7 @@ async fn find_version(
|
||||
project: &QueryProject,
|
||||
vcoords: &String,
|
||||
pool: &PgPool,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Option<QueryVersion>, ApiError> {
|
||||
let id_option = crate::models::ids::base62_impl::parse_base62(vcoords)
|
||||
.ok()
|
||||
@@ -245,7 +246,7 @@ pub async fn version_file(
|
||||
req: HttpRequest,
|
||||
params: web::Path<(String, String, String)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let (project_id, vnum, file) = params.into_inner();
|
||||
@@ -306,7 +307,7 @@ pub async fn version_file_sha1(
|
||||
req: HttpRequest,
|
||||
params: web::Path<(String, String, String)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let (project_id, vnum, file) = params.into_inner();
|
||||
@@ -348,7 +349,7 @@ pub async fn version_file_sha512(
|
||||
req: HttpRequest,
|
||||
params: web::Path<(String, String, String)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let (project_id, vnum, file) = params.into_inner();
|
||||
|
||||
@@ -6,6 +6,7 @@ use sqlx::PgPool;
|
||||
|
||||
use crate::auth::{filter_authorized_versions, get_user_from_headers, is_authorized};
|
||||
use crate::database;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::pats::Scopes;
|
||||
use crate::models::projects::VersionType;
|
||||
use crate::queue::session::AuthQueue;
|
||||
@@ -32,7 +33,7 @@ pub async fn forge_updates(
|
||||
web::Query(neo): web::Query<NeoForge>,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
const ERROR: &str = "The specified project does not exist!";
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
use crate::auth::validate::get_user_record_from_bearer_token;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::analytics::Download;
|
||||
use crate::models::ids::ProjectId;
|
||||
use crate::models::pats::Scopes;
|
||||
use crate::queue::analytics::AnalyticsQueue;
|
||||
use crate::queue::download::DownloadQueue;
|
||||
use crate::queue::maxmind::MaxMindIndexer;
|
||||
use crate::queue::session::AuthQueue;
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::guards::admin_key_guard;
|
||||
use crate::DownloadQueue;
|
||||
use actix_web::{patch, web, HttpRequest, HttpResponse};
|
||||
use chrono::Utc;
|
||||
use serde::Deserialize;
|
||||
@@ -37,7 +38,7 @@ pub struct DownloadBody {
|
||||
pub async fn count_download(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
maxmind: web::Data<Arc<MaxMindIndexer>>,
|
||||
analytics_queue: web::Data<Arc<AnalyticsQueue>>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
use super::ApiError;
|
||||
use actix_web::{get, web, HttpRequest, HttpResponse};
|
||||
use chrono::{Duration, NaiveDate, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::PgPool;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::{
|
||||
auth::{filter_authorized_projects, filter_authorized_versions, get_user_from_headers},
|
||||
database::models::{project_item, user_item, version_item},
|
||||
@@ -17,6 +12,11 @@ use crate::{
|
||||
},
|
||||
queue::session::AuthQueue,
|
||||
};
|
||||
use actix_web::{get, web, HttpRequest, HttpResponse};
|
||||
use chrono::{Duration, NaiveDate, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::PgPool;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(
|
||||
@@ -70,7 +70,7 @@ pub async fn playtimes_get(
|
||||
data: web::Query<GetData>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user_option = get_user_from_headers(
|
||||
&req,
|
||||
@@ -153,7 +153,7 @@ pub async fn views_get(
|
||||
data: web::Query<GetData>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user_option = get_user_from_headers(
|
||||
&req,
|
||||
@@ -236,7 +236,7 @@ pub async fn downloads_get(
|
||||
data: web::Query<GetData>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user_option = get_user_from_headers(
|
||||
&req,
|
||||
@@ -322,7 +322,7 @@ pub async fn countries_downloads_get(
|
||||
data: web::Query<GetData>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user_option = get_user_from_headers(
|
||||
&req,
|
||||
@@ -406,7 +406,7 @@ pub async fn countries_views_get(
|
||||
data: web::Query<GetData>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user_option = get_user_from_headers(
|
||||
&req,
|
||||
@@ -476,7 +476,7 @@ async fn filter_allowed_ids(
|
||||
version_ids: Option<Vec<String>>,
|
||||
user_option: Option<crate::models::users::User>,
|
||||
pool: &web::Data<PgPool>,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<(Option<Vec<ProjectId>>, Option<Vec<VersionId>>), ApiError> {
|
||||
if project_ids.is_some() && version_ids.is_some() {
|
||||
return Err(ApiError::InvalidInput(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::auth::checks::{filter_authorized_collections, is_authorized_collection};
|
||||
use crate::auth::get_user_from_headers;
|
||||
use crate::database;
|
||||
use crate::database::models::{collection_item, generate_collection_id, project_item};
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::file_hosting::FileHost;
|
||||
use crate::models::collections::{Collection, CollectionStatus};
|
||||
use crate::models::ids::base62_impl::parse_base62;
|
||||
@@ -11,6 +11,7 @@ use crate::queue::session::AuthQueue;
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::routes::read_from_payload;
|
||||
use crate::util::validate::validation_errors_to_string;
|
||||
use crate::{database, models};
|
||||
use actix_web::web::Data;
|
||||
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
|
||||
use chrono::Utc;
|
||||
@@ -56,7 +57,7 @@ pub async fn collection_create(
|
||||
req: HttpRequest,
|
||||
collection_create_data: web::Json<CollectionCreateData>,
|
||||
client: Data<PgPool>,
|
||||
redis: Data<deadpool_redis::Pool>,
|
||||
redis: Data<RedisPool>,
|
||||
session_queue: Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, CreateError> {
|
||||
let collection_create_data = collection_create_data.into_inner();
|
||||
@@ -130,7 +131,7 @@ pub async fn collections_get(
|
||||
req: HttpRequest,
|
||||
web::Query(ids): web::Query<CollectionIds>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let ids = serde_json::from_str::<Vec<&str>>(&ids.ids)?;
|
||||
@@ -162,7 +163,7 @@ pub async fn collection_get(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let string = info.into_inner().0;
|
||||
@@ -208,19 +209,18 @@ pub async fn collection_edit(
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
new_collection: web::Json<EditCollection>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user_option = get_user_from_headers(
|
||||
let user = get_user_from_headers(
|
||||
&req,
|
||||
&**pool,
|
||||
&redis,
|
||||
&session_queue,
|
||||
Some(&[Scopes::COLLECTION_WRITE]),
|
||||
)
|
||||
.await
|
||||
.map(|x| x.1)
|
||||
.ok();
|
||||
.await?
|
||||
.1;
|
||||
|
||||
new_collection
|
||||
.validate()
|
||||
@@ -231,7 +231,7 @@ pub async fn collection_edit(
|
||||
let result = database::models::Collection::get(id, &**pool, &redis).await?;
|
||||
|
||||
if let Some(collection_item) = result {
|
||||
if !is_authorized_collection(&collection_item, &user_option).await? {
|
||||
if !can_modify_collection(&collection_item, &user) {
|
||||
return Ok(HttpResponse::Unauthorized().body(""));
|
||||
}
|
||||
|
||||
@@ -268,27 +268,25 @@ pub async fn collection_edit(
|
||||
}
|
||||
|
||||
if let Some(status) = &new_collection.status {
|
||||
if let Some(user) = user_option {
|
||||
if !(user.role.is_mod()
|
||||
|| collection_item.status.is_approved() && status.can_be_requested())
|
||||
{
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You don't have permission to set this status!".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE collections
|
||||
SET status = $1
|
||||
WHERE (id = $2)
|
||||
",
|
||||
status.to_string(),
|
||||
id as database::models::ids::CollectionId,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
if !(user.role.is_mod()
|
||||
|| collection_item.status.is_approved() && status.can_be_requested())
|
||||
{
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You don't have permission to set this status!".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE collections
|
||||
SET status = $1
|
||||
WHERE (id = $2)
|
||||
",
|
||||
status.to_string(),
|
||||
id as database::models::ids::CollectionId,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
}
|
||||
|
||||
if let Some(new_project_ids) = &new_collection.new_projects {
|
||||
@@ -348,23 +346,22 @@ pub async fn collection_icon_edit(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||
mut payload: web::Payload,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
if let Some(content_type) = crate::util::ext::get_image_content_type(&ext.ext) {
|
||||
let cdn_url = dotenvy::var("CDN_URL")?;
|
||||
let user_option = get_user_from_headers(
|
||||
let user = get_user_from_headers(
|
||||
&req,
|
||||
&**pool,
|
||||
&redis,
|
||||
&session_queue,
|
||||
Some(&[Scopes::COLLECTION_WRITE]),
|
||||
)
|
||||
.await
|
||||
.map(|x| x.1)
|
||||
.ok();
|
||||
.await?
|
||||
.1;
|
||||
|
||||
let string = info.into_inner().0;
|
||||
let id = database::models::CollectionId(parse_base62(&string)? as i64);
|
||||
@@ -374,7 +371,7 @@ pub async fn collection_icon_edit(
|
||||
ApiError::InvalidInput("The specified collection does not exist!".to_string())
|
||||
})?;
|
||||
|
||||
if !is_authorized_collection(&collection_item, &user_option).await? {
|
||||
if !can_modify_collection(&collection_item, &user) {
|
||||
return Ok(HttpResponse::Unauthorized().body(""));
|
||||
}
|
||||
|
||||
@@ -434,20 +431,20 @@ pub async fn delete_collection_icon(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user_option = get_user_from_headers(
|
||||
let user = get_user_from_headers(
|
||||
&req,
|
||||
&**pool,
|
||||
&redis,
|
||||
&session_queue,
|
||||
Some(&[Scopes::COLLECTION_WRITE]),
|
||||
)
|
||||
.await
|
||||
.map(|x| x.1)
|
||||
.ok();
|
||||
.await?
|
||||
.1;
|
||||
|
||||
let string = info.into_inner().0;
|
||||
let id = database::models::CollectionId(parse_base62(&string)? as i64);
|
||||
let collection_item = database::models::Collection::get(id, &**pool, &redis)
|
||||
@@ -455,7 +452,7 @@ pub async fn delete_collection_icon(
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The specified collection does not exist!".to_string())
|
||||
})?;
|
||||
if !is_authorized_collection(&collection_item, &user_option).await? {
|
||||
if !can_modify_collection(&collection_item, &user) {
|
||||
return Ok(HttpResponse::Unauthorized().body(""));
|
||||
}
|
||||
|
||||
@@ -493,19 +490,18 @@ pub async fn collection_delete(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user_option = get_user_from_headers(
|
||||
let user = get_user_from_headers(
|
||||
&req,
|
||||
&**pool,
|
||||
&redis,
|
||||
&session_queue,
|
||||
Some(&[Scopes::COLLECTION_DELETE]),
|
||||
)
|
||||
.await
|
||||
.map(|x| x.1)
|
||||
.ok();
|
||||
.await?
|
||||
.1;
|
||||
|
||||
let string = info.into_inner().0;
|
||||
let id = database::models::CollectionId(parse_base62(&string)? as i64);
|
||||
@@ -514,7 +510,7 @@ pub async fn collection_delete(
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The specified collection does not exist!".to_string())
|
||||
})?;
|
||||
if !is_authorized_collection(&collection, &user_option).await? {
|
||||
if !can_modify_collection(&collection, &user) {
|
||||
return Ok(HttpResponse::Unauthorized().body(""));
|
||||
}
|
||||
let mut transaction = pool.begin().await?;
|
||||
@@ -531,3 +527,10 @@ pub async fn collection_delete(
|
||||
Ok(HttpResponse::NotFound().body(""))
|
||||
}
|
||||
}
|
||||
|
||||
fn can_modify_collection(
|
||||
collection: &database::models::Collection,
|
||||
user: &models::users::User,
|
||||
) -> bool {
|
||||
collection.user_id == user.id.into() || user.role.is_mod()
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::sync::Arc;
|
||||
use crate::auth::{get_user_from_headers, is_authorized, is_authorized_version};
|
||||
use crate::database;
|
||||
use crate::database::models::{project_item, report_item, thread_item, version_item};
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::file_hosting::FileHost;
|
||||
use crate::models::ids::{ThreadMessageId, VersionId};
|
||||
use crate::models::images::{Image, ImageContext};
|
||||
@@ -41,7 +42,7 @@ pub async fn images_add(
|
||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||
mut payload: web::Payload,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
if let Some(content_type) = crate::util::ext::get_image_content_type(&data.ext) {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use super::ApiError;
|
||||
use crate::auth::check_is_moderator_from_headers;
|
||||
use crate::database;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::projects::ProjectStatus;
|
||||
use crate::queue::session::AuthQueue;
|
||||
use crate::{auth::check_is_moderator_from_headers, models::pats::Scopes};
|
||||
use actix_web::{get, web, HttpRequest, HttpResponse};
|
||||
use serde::Deserialize;
|
||||
use sqlx::PgPool;
|
||||
@@ -25,11 +26,18 @@ fn default_count() -> i16 {
|
||||
pub async fn get_projects(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
count: web::Query<ResultCount>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
check_is_moderator_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||
check_is_moderator_from_headers(
|
||||
&req,
|
||||
&**pool,
|
||||
&redis,
|
||||
&session_queue,
|
||||
Some(&[Scopes::PROJECT_READ]),
|
||||
)
|
||||
.await?;
|
||||
|
||||
use futures::stream::TryStreamExt;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::auth::get_user_from_headers;
|
||||
use crate::database;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::ids::NotificationId;
|
||||
use crate::models::notifications::Notification;
|
||||
use crate::models::pats::Scopes;
|
||||
@@ -17,7 +18,7 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(
|
||||
web::scope("notification")
|
||||
.service(notification_get)
|
||||
.service(notifications_read)
|
||||
.service(notification_read)
|
||||
.service(notification_delete),
|
||||
);
|
||||
}
|
||||
@@ -32,7 +33,7 @@ pub async fn notifications_get(
|
||||
req: HttpRequest,
|
||||
web::Query(ids): web::Query<NotificationIds>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(
|
||||
@@ -72,7 +73,7 @@ pub async fn notification_get(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(NotificationId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(
|
||||
@@ -106,7 +107,7 @@ pub async fn notification_read(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(NotificationId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(
|
||||
@@ -149,7 +150,7 @@ pub async fn notification_delete(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(NotificationId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(
|
||||
@@ -192,7 +193,7 @@ pub async fn notifications_read(
|
||||
req: HttpRequest,
|
||||
web::Query(ids): web::Query<NotificationIds>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(
|
||||
@@ -237,7 +238,7 @@ pub async fn notifications_delete(
|
||||
req: HttpRequest,
|
||||
web::Query(ids): web::Query<NotificationIds>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::sync::Arc;
|
||||
use crate::auth::{filter_authorized_projects, get_user_from_headers};
|
||||
use crate::database::models::team_item::TeamMember;
|
||||
use crate::database::models::{generate_organization_id, team_item, Organization};
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::file_hosting::FileHost;
|
||||
use crate::models::ids::base62_impl::parse_base62;
|
||||
use crate::models::organizations::OrganizationId;
|
||||
@@ -39,16 +40,14 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
|
||||
#[derive(Deserialize, Validate)]
|
||||
pub struct NewOrganization {
|
||||
#[validate(length(min = 3, max = 256))]
|
||||
pub description: String,
|
||||
#[validate(
|
||||
length(min = 3, max = 64),
|
||||
regex = "crate::util::validate::RE_URL_SAFE"
|
||||
)]
|
||||
// Title of the organization, also used as slug
|
||||
pub title: String,
|
||||
#[serde(default = "crate::models::teams::ProjectPermissions::default")]
|
||||
pub default_project_permissions: ProjectPermissions,
|
||||
#[validate(length(min = 3, max = 256))]
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
#[post("organization")]
|
||||
@@ -56,7 +55,7 @@ pub async fn organization_create(
|
||||
req: HttpRequest,
|
||||
new_organization: web::Json<NewOrganization>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, CreateError> {
|
||||
let current_user = get_user_from_headers(
|
||||
@@ -143,7 +142,7 @@ pub async fn organization_get(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let id = info.into_inner().0;
|
||||
@@ -208,7 +207,7 @@ pub async fn organizations_get(
|
||||
req: HttpRequest,
|
||||
web::Query(ids): web::Query<OrganizationIds>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let ids = serde_json::from_str::<Vec<&str>>(&ids.ids)?;
|
||||
@@ -289,7 +288,6 @@ pub struct OrganizationEdit {
|
||||
)]
|
||||
// Title of the organization, also used as slug
|
||||
pub title: Option<String>,
|
||||
pub default_project_permissions: Option<ProjectPermissions>,
|
||||
}
|
||||
|
||||
#[patch("{id}")]
|
||||
@@ -298,7 +296,7 @@ pub async fn organizations_edit(
|
||||
info: web::Path<(String,)>,
|
||||
new_organization: web::Json<OrganizationEdit>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(
|
||||
@@ -434,7 +432,7 @@ pub async fn organization_delete(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(
|
||||
@@ -498,7 +496,7 @@ pub async fn organization_projects_get(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let info = info.into_inner().0;
|
||||
@@ -507,7 +505,7 @@ pub async fn organization_projects_get(
|
||||
&**pool,
|
||||
&redis,
|
||||
&session_queue,
|
||||
Some(&[Scopes::ORGANIZATION_READ]),
|
||||
Some(&[Scopes::ORGANIZATION_READ, Scopes::PROJECT_READ]),
|
||||
)
|
||||
.await
|
||||
.map(|x| x.1)
|
||||
@@ -519,7 +517,7 @@ pub async fn organization_projects_get(
|
||||
let project_ids = sqlx::query!(
|
||||
"
|
||||
SELECT m.id FROM organizations o
|
||||
LEFT JOIN mods m ON m.id = o.id
|
||||
INNER JOIN mods m ON m.organization_id = o.id
|
||||
WHERE (o.id = $1 AND $1 IS NOT NULL) OR (o.title = $2 AND $2 IS NOT NULL)
|
||||
",
|
||||
possible_organization_id.map(|x| x as i64),
|
||||
@@ -547,7 +545,7 @@ pub async fn organization_projects_add(
|
||||
info: web::Path<(String,)>,
|
||||
project_info: web::Json<OrganizationProjectAdd>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let info = info.into_inner().0;
|
||||
@@ -649,7 +647,7 @@ pub async fn organization_projects_remove(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String, String)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let (organization_id, project_id) = info.into_inner();
|
||||
@@ -743,7 +741,7 @@ pub async fn organization_icon_edit(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||
mut payload: web::Payload,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
@@ -848,7 +846,7 @@ pub async fn delete_organization_icon(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
|
||||
@@ -2,6 +2,7 @@ use super::version_creation::InitialVersionData;
|
||||
use crate::auth::{get_user_from_headers, AuthenticationError};
|
||||
use crate::database::models::thread_item::ThreadBuilder;
|
||||
use crate::database::models::{self, image_item};
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::file_hosting::{FileHost, FileHostingError};
|
||||
use crate::models::error::ApiError;
|
||||
use crate::models::ids::ImageId;
|
||||
@@ -283,7 +284,7 @@ pub async fn project_create(
|
||||
req: HttpRequest,
|
||||
mut payload: Multipart,
|
||||
client: Data<PgPool>,
|
||||
redis: Data<deadpool_redis::Pool>,
|
||||
redis: Data<RedisPool>,
|
||||
file_host: Data<Arc<dyn FileHost + Send + Sync>>,
|
||||
session_queue: Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, CreateError> {
|
||||
@@ -354,7 +355,7 @@ async fn project_create_inner(
|
||||
file_host: &dyn FileHost,
|
||||
uploaded_files: &mut Vec<UploadedFile>,
|
||||
pool: &PgPool,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
session_queue: &AuthQueue,
|
||||
) -> Result<HttpResponse, CreateError> {
|
||||
// The base URL for files uploaded to backblaze
|
||||
@@ -405,7 +406,6 @@ async fn project_create_inner(
|
||||
"`data` field must come before file fields",
|
||||
)));
|
||||
}
|
||||
|
||||
let mut data = Vec::new();
|
||||
while let Some(chunk) = field.next().await {
|
||||
data.extend_from_slice(&chunk.map_err(CreateError::MultipartError)?);
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::database;
|
||||
use crate::database::models::image_item;
|
||||
use crate::database::models::notification_item::NotificationBuilder;
|
||||
use crate::database::models::thread_item::ThreadMessageBuilder;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::file_hosting::FileHost;
|
||||
use crate::models;
|
||||
use crate::models::ids::base62_impl::parse_base62;
|
||||
@@ -79,7 +80,7 @@ pub struct RandomProjects {
|
||||
pub async fn random_projects_get(
|
||||
web::Query(count): web::Query<RandomProjects>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
count
|
||||
.validate()
|
||||
@@ -119,7 +120,7 @@ pub async fn projects_get(
|
||||
req: HttpRequest,
|
||||
web::Query(ids): web::Query<ProjectIds>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let ids = serde_json::from_str::<Vec<&str>>(&ids.ids)?;
|
||||
@@ -146,13 +147,12 @@ pub async fn project_get(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let string = info.into_inner().0;
|
||||
|
||||
let project_data = database::models::Project::get(&string, &**pool, &redis).await?;
|
||||
|
||||
let user_option = get_user_from_headers(
|
||||
&req,
|
||||
&**pool,
|
||||
@@ -177,7 +177,7 @@ pub async fn project_get(
|
||||
pub async fn project_get_check(
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let slug = info.into_inner().0;
|
||||
|
||||
@@ -203,7 +203,7 @@ pub async fn dependency_list(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let string = info.into_inner().0;
|
||||
@@ -275,7 +275,7 @@ pub async fn dependency_list(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Validate)]
|
||||
#[derive(Serialize, Deserialize, Validate)]
|
||||
pub struct EditProject {
|
||||
#[validate(
|
||||
length(min = 3, max = 64),
|
||||
@@ -381,7 +381,7 @@ pub async fn project_edit(
|
||||
pool: web::Data<PgPool>,
|
||||
config: web::Data<SearchConfig>,
|
||||
new_project: web::Json<EditProject>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(
|
||||
@@ -997,7 +997,6 @@ pub async fn project_edit(
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
}
|
||||
|
||||
if let Some(donations) = &new_project.donation_urls {
|
||||
if !perms.contains(ProjectPermissions::EDIT_DETAILS) {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
@@ -1244,7 +1243,7 @@ pub async fn projects_edit(
|
||||
web::Query(ids): web::Query<ProjectIds>,
|
||||
pool: web::Data<PgPool>,
|
||||
bulk_edit_project: web::Json<BulkEditProject>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(
|
||||
@@ -1622,7 +1621,7 @@ pub async fn project_schedule(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
scheduling_data: web::Json<SchedulingData>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
@@ -1724,7 +1723,7 @@ pub async fn project_icon_edit(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||
mut payload: web::Payload,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
@@ -1840,7 +1839,7 @@ pub async fn delete_project_icon(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
@@ -1943,7 +1942,7 @@ pub async fn add_gallery_item(
|
||||
web::Query(item): web::Query<GalleryCreateQuery>,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||
mut payload: web::Payload,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
@@ -2106,7 +2105,7 @@ pub async fn edit_gallery_item(
|
||||
web::Query(item): web::Query<GalleryEditQuery>,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(
|
||||
@@ -2269,7 +2268,7 @@ pub async fn delete_gallery_item(
|
||||
web::Query(item): web::Query<GalleryDeleteQuery>,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
@@ -2375,7 +2374,7 @@ pub async fn project_delete(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
config: web::Data<SearchConfig>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
@@ -2465,7 +2464,7 @@ pub async fn project_follow(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(
|
||||
@@ -2544,7 +2543,7 @@ pub async fn project_unfollow(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::auth::{check_is_moderator_from_headers, get_user_from_headers};
|
||||
use crate::database;
|
||||
use crate::database::models::image_item;
|
||||
use crate::database::models::thread_item::{ThreadBuilder, ThreadMessageBuilder};
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::ids::ImageId;
|
||||
use crate::models::ids::{base62_impl::parse_base62, ProjectId, UserId, VersionId};
|
||||
use crate::models::images::{Image, ImageContext};
|
||||
@@ -44,7 +45,7 @@ pub async fn report_create(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
mut body: web::Payload,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let mut transaction = pool.begin().await?;
|
||||
@@ -235,7 +236,7 @@ fn default_all() -> bool {
|
||||
pub async fn reports(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
count: web::Query<ReportsRequestOptions>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
@@ -310,7 +311,7 @@ pub async fn reports_get(
|
||||
req: HttpRequest,
|
||||
web::Query(ids): web::Query<ReportIds>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let report_ids: Vec<crate::database::models::ids::ReportId> =
|
||||
@@ -345,7 +346,7 @@ pub async fn reports_get(
|
||||
pub async fn report_get(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
info: web::Path<(crate::models::reports::ReportId,)>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
@@ -385,7 +386,7 @@ pub struct EditReport {
|
||||
pub async fn report_edit(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
info: web::Path<(crate::models::reports::ReportId,)>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
edit_report: web::Json<EditReport>,
|
||||
@@ -404,7 +405,7 @@ pub async fn report_edit(
|
||||
let report = crate::database::models::report_item::Report::get(id, &**pool).await?;
|
||||
|
||||
if let Some(report) = report {
|
||||
if !user.role.is_mod() && report.user_id != Some(user.id.into()) {
|
||||
if !user.role.is_mod() && report.reporter != user.id.into() {
|
||||
return Ok(HttpResponse::NotFound().body(""));
|
||||
}
|
||||
|
||||
@@ -492,10 +493,17 @@ pub async fn report_delete(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
info: web::Path<(crate::models::reports::ReportId,)>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
check_is_moderator_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||
check_is_moderator_from_headers(
|
||||
&req,
|
||||
&**pool,
|
||||
&redis,
|
||||
&session_queue,
|
||||
Some(&[Scopes::REPORT_DELETE]),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::ApiError;
|
||||
use crate::database::models;
|
||||
use crate::database::models::categories::{DonationPlatform, ProjectType, ReportType, SideType};
|
||||
use crate::database::redis::RedisPool;
|
||||
use actix_web::{get, web, HttpResponse};
|
||||
use chrono::{DateTime, Utc};
|
||||
use models::categories::{Category, GameVersion, Loader};
|
||||
@@ -32,7 +33,7 @@ pub struct CategoryData {
|
||||
#[get("category")]
|
||||
pub async fn category_list(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let results = Category::list(&**pool, &redis)
|
||||
.await?
|
||||
@@ -58,7 +59,7 @@ pub struct LoaderData {
|
||||
#[get("loader")]
|
||||
pub async fn loader_list(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let mut results = Loader::list(&**pool, &redis)
|
||||
.await?
|
||||
@@ -94,7 +95,7 @@ pub struct GameVersionQuery {
|
||||
pub async fn game_version_list(
|
||||
pool: web::Data<PgPool>,
|
||||
query: web::Query<GameVersionQuery>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let results: Vec<GameVersionQueryData> = if query.type_.is_some() || query.major.is_some() {
|
||||
GameVersion::list_filter(query.type_.as_deref(), query.major, &**pool, &redis).await?
|
||||
@@ -172,7 +173,7 @@ pub struct DonationPlatformQueryData {
|
||||
#[get("donation_platform")]
|
||||
pub async fn donation_platform_list(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let results: Vec<DonationPlatformQueryData> = DonationPlatform::list(&**pool, &redis)
|
||||
.await?
|
||||
@@ -188,7 +189,7 @@ pub async fn donation_platform_list(
|
||||
#[get("report_type")]
|
||||
pub async fn report_type_list(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let results = ReportType::list(&**pool, &redis).await?;
|
||||
Ok(HttpResponse::Ok().json(results))
|
||||
@@ -197,7 +198,7 @@ pub async fn report_type_list(
|
||||
#[get("project_type")]
|
||||
pub async fn project_type_list(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let results = ProjectType::list(&**pool, &redis).await?;
|
||||
Ok(HttpResponse::Ok().json(results))
|
||||
@@ -206,7 +207,7 @@ pub async fn project_type_list(
|
||||
#[get("side_type")]
|
||||
pub async fn side_type_list(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let results = SideType::list(&**pool, &redis).await?;
|
||||
Ok(HttpResponse::Ok().json(results))
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::auth::{get_user_from_headers, is_authorized};
|
||||
use crate::database::models::notification_item::NotificationBuilder;
|
||||
use crate::database::models::team_item::TeamAssociationId;
|
||||
use crate::database::models::{Organization, Team, TeamMember};
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::database::Project;
|
||||
use crate::models::notifications::NotificationBody;
|
||||
use crate::models::pats::Scopes;
|
||||
@@ -37,7 +38,7 @@ pub async fn team_members_get_project(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let string = info.into_inner().0;
|
||||
@@ -116,7 +117,7 @@ pub async fn team_members_get_organization(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let string = info.into_inner().0;
|
||||
@@ -182,7 +183,7 @@ pub async fn team_members_get(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(TeamId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let id = info.into_inner().0;
|
||||
@@ -244,7 +245,7 @@ pub async fn teams_get(
|
||||
req: HttpRequest,
|
||||
web::Query(ids): web::Query<TeamIds>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
use itertools::Itertools;
|
||||
@@ -309,7 +310,7 @@ pub async fn join_team(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(TeamId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let team_id = info.into_inner().0.into();
|
||||
@@ -389,7 +390,7 @@ pub async fn add_team_member(
|
||||
info: web::Path<(TeamId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
new_member: web::Json<NewTeamMember>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let team_id = info.into_inner().0.into();
|
||||
@@ -452,7 +453,6 @@ pub async fn add_team_member(
|
||||
let organization_permissions =
|
||||
OrganizationPermissions::get_permissions_by_role(¤t_user.role, &member)
|
||||
.unwrap_or_default();
|
||||
println!("{:?}", organization_permissions);
|
||||
if !organization_permissions.contains(OrganizationPermissions::MANAGE_INVITES) {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You don't have permission to invite users to this organization".to_string(),
|
||||
@@ -571,7 +571,7 @@ pub async fn edit_team_member(
|
||||
info: web::Path<(TeamId, UserId)>,
|
||||
pool: web::Data<PgPool>,
|
||||
edit_member: web::Json<EditTeamMember>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let ids = info.into_inner();
|
||||
@@ -724,7 +724,7 @@ pub async fn transfer_ownership(
|
||||
info: web::Path<(TeamId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
new_owner: web::Json<TransferOwnership>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let id = info.into_inner().0;
|
||||
@@ -822,7 +822,7 @@ pub async fn remove_team_member(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(TeamId, UserId)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let ids = info.into_inner();
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::database;
|
||||
use crate::database::models::image_item;
|
||||
use crate::database::models::notification_item::NotificationBuilder;
|
||||
use crate::database::models::thread_item::ThreadMessageBuilder;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::file_hosting::FileHost;
|
||||
use crate::models::ids::ThreadMessageId;
|
||||
use crate::models::images::{Image, ImageContext};
|
||||
@@ -83,7 +84,7 @@ pub async fn filter_authorized_threads(
|
||||
threads: Vec<database::models::Thread>,
|
||||
user: &User,
|
||||
pool: &web::Data<PgPool>,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Vec<Thread>, ApiError> {
|
||||
let user_id: database::models::UserId = user.id.into();
|
||||
|
||||
@@ -225,7 +226,7 @@ pub async fn thread_get(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(ThreadId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let string = info.into_inner().0.into();
|
||||
@@ -276,7 +277,7 @@ pub async fn threads_get(
|
||||
req: HttpRequest,
|
||||
web::Query(ids): web::Query<ThreadIds>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(
|
||||
@@ -313,7 +314,7 @@ pub async fn thread_send_message(
|
||||
info: web::Path<(ThreadId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
new_message: web::Json<NewThreadMessage>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(
|
||||
@@ -508,10 +509,17 @@ pub async fn thread_send_message(
|
||||
pub async fn moderation_inbox(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> 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,
|
||||
Some(&[Scopes::THREAD_READ]),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let ids = sqlx::query!(
|
||||
"
|
||||
@@ -527,7 +535,6 @@ pub async fn moderation_inbox(
|
||||
|
||||
let threads_data = database::models::Thread::get_many(&ids, &**pool).await?;
|
||||
let threads = filter_authorized_threads(threads_data, &user, &pool, &redis).await?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(threads))
|
||||
}
|
||||
|
||||
@@ -536,10 +543,17 @@ pub async fn thread_read(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(ThreadId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
check_is_moderator_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||
check_is_moderator_from_headers(
|
||||
&req,
|
||||
&**pool,
|
||||
&redis,
|
||||
&session_queue,
|
||||
Some(&[Scopes::THREAD_READ]),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let id = info.into_inner().0;
|
||||
let mut transaction = pool.begin().await?;
|
||||
@@ -565,7 +579,7 @@ pub async fn message_delete(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(ThreadMessageId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::auth::{get_user_from_headers, AuthenticationError};
|
||||
use crate::database::models::User;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::file_hosting::FileHost;
|
||||
use crate::models::collections::{Collection, CollectionStatus};
|
||||
use crate::models::notifications::Notification;
|
||||
@@ -46,7 +47,7 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
pub async fn user_auth_get(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let (scopes, mut user) = get_user_from_headers(
|
||||
@@ -66,17 +67,7 @@ pub async fn user_auth_get(
|
||||
user.payout_data = None;
|
||||
}
|
||||
|
||||
Ok(HttpResponse::Ok().json(
|
||||
get_user_from_headers(
|
||||
&req,
|
||||
&**pool,
|
||||
&redis,
|
||||
&session_queue,
|
||||
Some(&[Scopes::USER_READ]),
|
||||
)
|
||||
.await?
|
||||
.1,
|
||||
))
|
||||
Ok(HttpResponse::Ok().json(user))
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -88,7 +79,7 @@ pub struct UserIds {
|
||||
pub async fn users_get(
|
||||
web::Query(ids): web::Query<UserIds>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user_ids = serde_json::from_str::<Vec<String>>(&ids.ids)?;
|
||||
|
||||
@@ -103,7 +94,7 @@ pub async fn users_get(
|
||||
pub async fn user_get(
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user_data = User::get(&info.into_inner().0, &**pool, &redis).await?;
|
||||
|
||||
@@ -120,7 +111,7 @@ pub async fn projects_list(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(
|
||||
@@ -164,7 +155,7 @@ pub async fn collections_list(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(
|
||||
@@ -250,7 +241,7 @@ pub async fn user_edit(
|
||||
info: web::Path<(String,)>,
|
||||
new_user: web::Json<EditUser>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let (scopes, user) = get_user_from_headers(
|
||||
@@ -471,7 +462,7 @@ pub async fn user_icon_edit(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||
mut payload: web::Payload,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
@@ -560,7 +551,7 @@ pub async fn user_delete(
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
removal_type: web::Query<RemovalType>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(
|
||||
@@ -608,7 +599,7 @@ pub async fn user_follows(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(
|
||||
@@ -664,7 +655,7 @@ pub async fn user_notifications(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(
|
||||
@@ -712,7 +703,7 @@ pub async fn user_payouts(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(
|
||||
@@ -797,7 +788,7 @@ pub async fn user_payouts_request(
|
||||
pool: web::Data<PgPool>,
|
||||
data: web::Json<PayoutData>,
|
||||
payouts_queue: web::Data<Mutex<PayoutsQueue>>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let mut payouts_queue = payouts_queue.lock().await;
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::database::models::version_item::{
|
||||
DependencyBuilder, VersionBuilder, VersionFileBuilder,
|
||||
};
|
||||
use crate::database::models::{self, image_item, Organization};
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::file_hosting::FileHost;
|
||||
use crate::models::images::{Image, ImageContext, ImageId};
|
||||
use crate::models::notifications::NotificationBody;
|
||||
@@ -89,7 +90,7 @@ pub async fn version_create(
|
||||
req: HttpRequest,
|
||||
mut payload: Multipart,
|
||||
client: Data<PgPool>,
|
||||
redis: Data<deadpool_redis::Pool>,
|
||||
redis: Data<RedisPool>,
|
||||
file_host: Data<Arc<dyn FileHost + Send + Sync>>,
|
||||
session_queue: Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, CreateError> {
|
||||
@@ -129,7 +130,7 @@ async fn version_create_inner(
|
||||
req: HttpRequest,
|
||||
payload: &mut Multipart,
|
||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
file_host: &dyn FileHost,
|
||||
uploaded_files: &mut Vec<UploadedFile>,
|
||||
pool: &PgPool,
|
||||
@@ -507,7 +508,7 @@ pub async fn upload_file_to_version(
|
||||
url_data: web::Path<(VersionId,)>,
|
||||
mut payload: Multipart,
|
||||
client: Data<PgPool>,
|
||||
redis: Data<deadpool_redis::Pool>,
|
||||
redis: Data<RedisPool>,
|
||||
file_host: Data<Arc<dyn FileHost + Send + Sync>>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, CreateError> {
|
||||
@@ -551,7 +552,7 @@ async fn upload_file_to_version_inner(
|
||||
payload: &mut Multipart,
|
||||
client: Data<PgPool>,
|
||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
redis: Data<deadpool_redis::Pool>,
|
||||
redis: Data<RedisPool>,
|
||||
file_host: &dyn FileHost,
|
||||
uploaded_files: &mut Vec<UploadedFile>,
|
||||
version_id: models::VersionId,
|
||||
@@ -729,6 +730,9 @@ async fn upload_file_to_version_inner(
|
||||
}
|
||||
}
|
||||
|
||||
// Clear version cache
|
||||
models::Version::clear_cache(&version, &redis).await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::auth::{
|
||||
filter_authorized_projects, filter_authorized_versions, get_user_from_headers,
|
||||
is_authorized_version,
|
||||
};
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::ids::VersionId;
|
||||
use crate::models::pats::Scopes;
|
||||
use crate::models::projects::VersionType;
|
||||
@@ -21,7 +22,8 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
.service(delete_file)
|
||||
.service(get_version_from_hash)
|
||||
.service(download_version)
|
||||
.service(get_update_from_hash),
|
||||
.service(get_update_from_hash)
|
||||
.service(get_projects_from_hashes),
|
||||
);
|
||||
|
||||
cfg.service(
|
||||
@@ -32,7 +34,7 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct HashQuery {
|
||||
#[serde(default = "default_algorithm")]
|
||||
pub algorithm: String,
|
||||
@@ -49,7 +51,7 @@ pub async fn get_version_from_hash(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
hash_query: web::Query<HashQuery>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
@@ -63,7 +65,6 @@ pub async fn get_version_from_hash(
|
||||
.await
|
||||
.map(|x| x.1)
|
||||
.ok();
|
||||
|
||||
let hash = info.into_inner().0.to_lowercase();
|
||||
let file = database::models::Version::get_file_from_hash(
|
||||
hash_query.algorithm.clone(),
|
||||
@@ -73,10 +74,8 @@ pub async fn get_version_from_hash(
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(file) = file {
|
||||
let version = database::models::Version::get(file.version_id, &**pool, &redis).await?;
|
||||
|
||||
if let Some(version) = version {
|
||||
if !is_authorized_version(&version.inner, &user_option, &pool).await? {
|
||||
return Ok(HttpResponse::NotFound().body(""));
|
||||
@@ -102,7 +101,7 @@ pub async fn download_version(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
hash_query: web::Query<HashQuery>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
@@ -152,7 +151,7 @@ pub async fn delete_file(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
hash_query: web::Query<HashQuery>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
@@ -274,7 +273,7 @@ pub async fn get_update_from_hash(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
hash_query: web::Query<HashQuery>,
|
||||
update_data: web::Json<UpdateData>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
@@ -343,6 +342,7 @@ pub async fn get_update_from_hash(
|
||||
// Requests above with multiple versions below
|
||||
#[derive(Deserialize)]
|
||||
pub struct FileHashes {
|
||||
#[serde(default = "default_algorithm")]
|
||||
pub algorithm: String,
|
||||
pub hashes: Vec<String>,
|
||||
}
|
||||
@@ -352,7 +352,7 @@ pub struct FileHashes {
|
||||
pub async fn get_versions_from_hashes(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
file_data: web::Json<FileHashes>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
@@ -400,7 +400,7 @@ pub async fn get_versions_from_hashes(
|
||||
pub async fn get_projects_from_hashes(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
file_data: web::Json<FileHashes>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
@@ -409,7 +409,7 @@ pub async fn get_projects_from_hashes(
|
||||
&**pool,
|
||||
&redis,
|
||||
&session_queue,
|
||||
Some(&[Scopes::VERSION_READ]),
|
||||
Some(&[Scopes::PROJECT_READ, Scopes::VERSION_READ]),
|
||||
)
|
||||
.await
|
||||
.map(|x| x.1)
|
||||
@@ -447,6 +447,7 @@ pub async fn get_projects_from_hashes(
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ManyUpdateData {
|
||||
#[serde(default = "default_algorithm")]
|
||||
pub algorithm: String,
|
||||
pub hashes: Vec<String>,
|
||||
pub loaders: Option<Vec<String>>,
|
||||
@@ -458,7 +459,7 @@ pub struct ManyUpdateData {
|
||||
pub async fn update_files(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
update_data: web::Json<ManyUpdateData>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
@@ -550,6 +551,7 @@ pub struct FileUpdateData {
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ManyFileUpdateData {
|
||||
#[serde(default = "default_algorithm")]
|
||||
pub algorithm: String,
|
||||
pub hashes: Vec<FileUpdateData>,
|
||||
}
|
||||
@@ -558,7 +560,7 @@ pub struct ManyFileUpdateData {
|
||||
pub async fn update_individual_files(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
update_data: web::Json<ManyFileUpdateData>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::auth::{
|
||||
};
|
||||
use crate::database;
|
||||
use crate::database::models::{image_item, Organization};
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models;
|
||||
use crate::models::ids::base62_impl::parse_base62;
|
||||
use crate::models::images::ImageContext;
|
||||
@@ -49,7 +50,7 @@ pub async fn version_list(
|
||||
info: web::Path<(String,)>,
|
||||
web::Query(filters): web::Query<VersionListFilters>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let string = info.into_inner().0;
|
||||
@@ -170,7 +171,7 @@ pub async fn version_project_get(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String, String)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let id = info.into_inner();
|
||||
@@ -221,7 +222,7 @@ pub async fn versions_get(
|
||||
req: HttpRequest,
|
||||
web::Query(ids): web::Query<VersionIds>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let version_ids = serde_json::from_str::<Vec<models::ids::VersionId>>(&ids.ids)?
|
||||
@@ -251,7 +252,7 @@ pub async fn version_get(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(models::ids::VersionId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let id = info.into_inner().0;
|
||||
@@ -318,7 +319,7 @@ pub async fn version_edit(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(models::ids::VersionId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
new_version: web::Json<EditVersion>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
@@ -738,7 +739,7 @@ pub async fn version_schedule(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(models::ids::VersionId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
scheduling_data: web::Json<SchedulingData>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
@@ -835,7 +836,7 @@ pub async fn version_delete(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(models::ids::VersionId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<deadpool_redis::Pool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use crate::database;
|
||||
use crate::database::models::image_item;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::images::ImageContext;
|
||||
use crate::routes::ApiError;
|
||||
use color_thief::ColorFormat;
|
||||
use image::imageops::FilterType;
|
||||
use image::{EncodableLayout, ImageError};
|
||||
|
||||
use crate::database;
|
||||
use crate::database::models::image_item;
|
||||
use crate::models::images::ImageContext;
|
||||
use crate::routes::ApiError;
|
||||
|
||||
pub fn get_color_from_img(data: &[u8]) -> Result<Option<u32>, ImageError> {
|
||||
let image = image::load_from_memory(data)?
|
||||
.resize(256, 256, FilterType::Nearest)
|
||||
@@ -26,7 +26,7 @@ pub async fn delete_unused_images(
|
||||
context: ImageContext,
|
||||
reference_strings: Vec<&str>,
|
||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<(), ApiError> {
|
||||
let uploaded_images = database::models::Image::get_many_contexted(context, transaction).await?;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::database::models::categories::GameVersion;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::projects::ProjectId;
|
||||
use crate::routes::ApiError;
|
||||
use chrono::{DateTime, Utc};
|
||||
@@ -72,7 +73,7 @@ const PLUGIN_LOADERS: &[&str] = &[
|
||||
pub async fn send_discord_webhook(
|
||||
project_id: ProjectId,
|
||||
pool: &PgPool,
|
||||
redis: &deadpool_redis::Pool,
|
||||
redis: &RedisPool,
|
||||
webhook_url: String,
|
||||
message: Option<String>,
|
||||
) -> Result<(), ApiError> {
|
||||
|
||||
Reference in New Issue
Block a user