mirror of
https://github.com/Arcadia-Solutions/arcadia.git
synced 2025-12-19 08:19:33 -06:00
feat(backend): configurable user classes and permissions
This commit is contained in:
@@ -22,6 +22,8 @@ ARCADIA_FRONTEND_URL=https://site.com
|
||||
ARCADIA_TRACKER_URL=https://site.com
|
||||
ARCADIA_GLOBAL_UPLOAD_FACTOR=100
|
||||
ARCADIA_GLOBAL_DOWNLOAD_FACTOR=100
|
||||
ARCADIA_USER_CLASS_NAME_ON_SIGNUP=newbie
|
||||
|
||||
|
||||
# Redis
|
||||
REDIS_HOST=127.0.0.1
|
||||
|
||||
@@ -45,6 +45,8 @@ ARCADIA_TRACKER_URL=https://site.com
|
||||
ARCADIA_GLOBAL_UPLOAD_FACTOR=100
|
||||
# Global download factor.
|
||||
ARCADIA_GLOBAL_DOWNLOAD_FACTOR=100
|
||||
# Default user class name when a user registers an account
|
||||
ARCADIA_USER_CLASS_NAME_ON_SIGNUP=newbie
|
||||
|
||||
# Redis
|
||||
REDIS_HOST=127.0.0.1
|
||||
|
||||
@@ -43,7 +43,6 @@ use crate::handlers::user_applications::get_user_applications::GetUserApplicatio
|
||||
crate::handlers::torrents::download_dottorrent_file::exec,
|
||||
crate::handlers::torrents::create_torrent::exec,
|
||||
crate::handlers::torrents::edit_torrent::exec,
|
||||
crate::handlers::torrents::get_registered_torrents::exec,
|
||||
crate::handlers::torrents::get_upload_information::exec,
|
||||
crate::handlers::torrents::get_top_torrents::exec,
|
||||
crate::handlers::torrents::delete_torrent::exec,
|
||||
|
||||
@@ -12,6 +12,8 @@ pub struct Env {
|
||||
pub jwt_secret: String,
|
||||
#[envconfig(from = "ARCADIA_OPEN_SIGNUPS")]
|
||||
pub open_signups: OpenSignups,
|
||||
#[envconfig(from = "ARCADIA_USER_CLASS_NAME_ON_SIGNUP")]
|
||||
pub user_class_name_on_signup: String,
|
||||
#[envconfig(from = "ARCADIA_FRONTEND_URL")]
|
||||
pub frontend_url: Url,
|
||||
#[envconfig(from = "ARCADIA_GLOBAL_UPLOAD_FACTOR")]
|
||||
|
||||
@@ -2,7 +2,8 @@ use crate::{middlewares::auth_middleware::Authdata, Arcadia};
|
||||
use actix_web::{web::Data, web::Json, HttpResponse};
|
||||
use arcadia_common::error::{Error, Result};
|
||||
use arcadia_storage::models::artist::Artist;
|
||||
use arcadia_storage::models::user::UserClass;
|
||||
|
||||
use arcadia_storage::models::user::UserPermission;
|
||||
use arcadia_storage::{models::artist::EditedArtist, redis::RedisPoolInterface};
|
||||
|
||||
const GRACE_PERIOD_IN_DAYS: i64 = 7;
|
||||
@@ -26,9 +27,13 @@ pub async fn exec<R: RedisPoolInterface + 'static>(
|
||||
) -> Result<HttpResponse> {
|
||||
let mut artist = arc.pool.find_artist_by_id(form.id).await?;
|
||||
|
||||
// Admins. can edit any artist, but other users can edit their own artist for a grace period of
|
||||
// users can edit their own artist for a grace period of
|
||||
// 7 days after creation, to prevent e.g. hostile account takeovers.
|
||||
if user.class != UserClass::Staff {
|
||||
if !arc
|
||||
.pool
|
||||
.user_has_permission(user.sub, &UserPermission::EditArtist)
|
||||
.await?
|
||||
{
|
||||
let grace_period = chrono::Utc::now() - chrono::Duration::days(GRACE_PERIOD_IN_DAYS);
|
||||
if artist.created_by_id != user.sub || artist.created_at < grace_period {
|
||||
return Err(Error::InsufficientPrivileges);
|
||||
|
||||
@@ -44,7 +44,6 @@ pub async fn exec<R: RedisPoolInterface + 'static>(
|
||||
sub: user.id,
|
||||
exp: refresh_token_expiration_date.timestamp(),
|
||||
iat: now.timestamp(),
|
||||
class: user.class.clone(),
|
||||
};
|
||||
refresh_token = encode(
|
||||
&Header::default(),
|
||||
@@ -58,7 +57,6 @@ pub async fn exec<R: RedisPoolInterface + 'static>(
|
||||
sub: user.id,
|
||||
exp: token_expiration_date.timestamp(),
|
||||
iat: now.timestamp(),
|
||||
class: user.class,
|
||||
};
|
||||
|
||||
let token = encode(
|
||||
|
||||
@@ -52,7 +52,6 @@ pub async fn exec<R: RedisPoolInterface + 'static>(
|
||||
sub: old_refresh_token.claims.sub,
|
||||
iat: now.timestamp(),
|
||||
exp: (Utc::now() + *AUTH_TOKEN_LONG_DURATION).timestamp(),
|
||||
class: old_refresh_token.claims.class.clone(),
|
||||
};
|
||||
|
||||
let token = encode(
|
||||
@@ -66,7 +65,6 @@ pub async fn exec<R: RedisPoolInterface + 'static>(
|
||||
sub: old_refresh_token.claims.sub,
|
||||
exp: (now + *REFRESH_TOKEN_DURATION).timestamp(),
|
||||
iat: now.timestamp(),
|
||||
class: old_refresh_token.claims.class.clone(),
|
||||
};
|
||||
|
||||
let refresh_token = encode(
|
||||
|
||||
@@ -96,6 +96,7 @@ pub async fn exec<R: RedisPoolInterface + 'static>(
|
||||
&password_hash,
|
||||
&invitation,
|
||||
&arc.is_open_signups(),
|
||||
&arc.env.user_class_name_on_signup,
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -5,8 +5,10 @@ use actix_web::{
|
||||
};
|
||||
use arcadia_common::error::{Error, Result};
|
||||
use arcadia_storage::{
|
||||
models::css_sheet::{CssSheet, UserCreatedCssSheet},
|
||||
models::user::UserClass,
|
||||
models::{
|
||||
css_sheet::{CssSheet, UserCreatedCssSheet},
|
||||
user::UserPermission,
|
||||
},
|
||||
redis::RedisPoolInterface,
|
||||
};
|
||||
|
||||
@@ -27,7 +29,11 @@ pub async fn exec<R: RedisPoolInterface + 'static>(
|
||||
arc: Data<Arcadia<R>>,
|
||||
user: Authdata,
|
||||
) -> Result<HttpResponse> {
|
||||
if user.class != UserClass::Staff {
|
||||
if !arc
|
||||
.pool
|
||||
.user_has_permission(user.sub, &UserPermission::CreateCssSheet)
|
||||
.await?
|
||||
{
|
||||
return Err(Error::InsufficientPrivileges);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,10 @@ use actix_web::{
|
||||
};
|
||||
use arcadia_common::error::{Error, Result};
|
||||
use arcadia_storage::{
|
||||
models::css_sheet::{CssSheet, EditedCssSheet},
|
||||
models::user::UserClass,
|
||||
models::{
|
||||
css_sheet::{CssSheet, EditedCssSheet},
|
||||
user::UserPermission,
|
||||
},
|
||||
redis::RedisPoolInterface,
|
||||
};
|
||||
|
||||
@@ -27,7 +29,11 @@ pub async fn exec<R: RedisPoolInterface + 'static>(
|
||||
arc: Data<Arcadia<R>>,
|
||||
user: Authdata,
|
||||
) -> Result<HttpResponse> {
|
||||
if user.class != UserClass::Staff {
|
||||
if !arc
|
||||
.pool
|
||||
.user_has_permission(user.sub, &UserPermission::EditCssSheet)
|
||||
.await?
|
||||
{
|
||||
return Err(Error::InsufficientPrivileges);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ use actix_web::{
|
||||
HttpResponse,
|
||||
};
|
||||
use arcadia_common::error::{Error, Result};
|
||||
use arcadia_storage::{models::user::UserClass, redis::RedisPoolInterface};
|
||||
use arcadia_storage::{models::user::UserPermission, redis::RedisPoolInterface};
|
||||
|
||||
#[utoipa::path(
|
||||
put,
|
||||
@@ -23,7 +23,11 @@ pub async fn exec<R: RedisPoolInterface + 'static>(
|
||||
arc: Data<Arcadia<R>>,
|
||||
user: Authdata,
|
||||
) -> Result<HttpResponse> {
|
||||
if user.class != UserClass::Staff {
|
||||
if !arc
|
||||
.pool
|
||||
.user_has_permission(user.sub, &UserPermission::SetDefaultCssSheet)
|
||||
.await?
|
||||
{
|
||||
return Err(Error::InsufficientPrivileges);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use arcadia_common::error::{Error, Result};
|
||||
use arcadia_storage::{
|
||||
models::{
|
||||
forum::{ForumCategory, UserCreatedForumCategory},
|
||||
user::UserClass,
|
||||
user::UserPermission,
|
||||
},
|
||||
redis::RedisPoolInterface,
|
||||
};
|
||||
@@ -29,7 +29,11 @@ pub async fn exec<R: RedisPoolInterface + 'static>(
|
||||
arc: Data<Arcadia<R>>,
|
||||
user: Authdata,
|
||||
) -> Result<HttpResponse> {
|
||||
if user.class != UserClass::Staff {
|
||||
if !arc
|
||||
.pool
|
||||
.user_has_permission(user.sub, &UserPermission::CreateForumCategory)
|
||||
.await?
|
||||
{
|
||||
return Err(Error::InsufficientPrivileges);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use arcadia_common::error::{Error, Result};
|
||||
use arcadia_storage::{
|
||||
models::{
|
||||
forum::{ForumSubCategory, UserCreatedForumSubCategory},
|
||||
user::UserClass,
|
||||
user::UserPermission,
|
||||
},
|
||||
redis::RedisPoolInterface,
|
||||
};
|
||||
@@ -29,7 +29,11 @@ pub async fn exec<R: RedisPoolInterface + 'static>(
|
||||
arc: Data<Arcadia<R>>,
|
||||
user: Authdata,
|
||||
) -> Result<HttpResponse> {
|
||||
if user.class != UserClass::Staff {
|
||||
if !arc
|
||||
.pool
|
||||
.user_has_permission(user.sub, &UserPermission::CreateForumSubCategory)
|
||||
.await?
|
||||
{
|
||||
return Err(Error::InsufficientPrivileges);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use arcadia_common::error::{Error, Result};
|
||||
use arcadia_storage::{
|
||||
models::{
|
||||
forum::{EditedForumCategory, ForumCategory},
|
||||
user::UserClass,
|
||||
user::UserPermission,
|
||||
},
|
||||
redis::RedisPoolInterface,
|
||||
};
|
||||
@@ -29,7 +29,11 @@ pub async fn exec<R: RedisPoolInterface + 'static>(
|
||||
arc: Data<Arcadia<R>>,
|
||||
user: Authdata,
|
||||
) -> Result<HttpResponse> {
|
||||
if user.class != UserClass::Staff {
|
||||
if !arc
|
||||
.pool
|
||||
.user_has_permission(user.sub, &UserPermission::EditForumCategory)
|
||||
.await?
|
||||
{
|
||||
return Err(Error::InsufficientPrivileges);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use arcadia_common::error::{Error, Result};
|
||||
use arcadia_storage::{
|
||||
models::{
|
||||
forum::{EditedForumPost, ForumPost},
|
||||
user::UserClass,
|
||||
user::UserPermission,
|
||||
},
|
||||
redis::RedisPoolInterface,
|
||||
};
|
||||
@@ -31,7 +31,12 @@ pub async fn exec<R: RedisPoolInterface + 'static>(
|
||||
) -> Result<HttpResponse> {
|
||||
let original_forum_post = arc.pool.find_forum_post(edited_forum_post.id).await?;
|
||||
|
||||
if original_forum_post.created_by_id == user.sub || user.class == UserClass::Staff {
|
||||
if original_forum_post.created_by_id == user.sub
|
||||
|| arc
|
||||
.pool
|
||||
.user_has_permission(user.sub, &UserPermission::EditForumPost)
|
||||
.await?
|
||||
{
|
||||
let forum_post = arc.pool.update_forum_post(&edited_forum_post).await?;
|
||||
Ok(HttpResponse::Created().json(forum_post))
|
||||
} else {
|
||||
|
||||
@@ -7,7 +7,7 @@ use arcadia_common::error::{Error, Result};
|
||||
use arcadia_storage::{
|
||||
models::{
|
||||
forum::{EditedForumSubCategory, ForumSubCategory},
|
||||
user::UserClass,
|
||||
user::UserPermission,
|
||||
},
|
||||
redis::RedisPoolInterface,
|
||||
};
|
||||
@@ -29,7 +29,11 @@ pub async fn exec<R: RedisPoolInterface + 'static>(
|
||||
arc: Data<Arcadia<R>>,
|
||||
user: Authdata,
|
||||
) -> Result<HttpResponse> {
|
||||
if user.class != UserClass::Staff {
|
||||
if !arc
|
||||
.pool
|
||||
.user_has_permission(user.sub, &UserPermission::EditForumSubCategory)
|
||||
.await?
|
||||
{
|
||||
return Err(Error::InsufficientPrivileges);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use arcadia_common::error::{Error, Result};
|
||||
use arcadia_storage::{
|
||||
models::{
|
||||
forum::{EditedForumThread, ForumThreadEnriched},
|
||||
user::UserClass,
|
||||
user::UserPermission,
|
||||
},
|
||||
redis::RedisPoolInterface,
|
||||
};
|
||||
@@ -31,7 +31,12 @@ pub async fn exec<R: RedisPoolInterface + 'static>(
|
||||
.find_forum_thread(edited_forum_thread.id, user.sub)
|
||||
.await?;
|
||||
|
||||
if user.class != UserClass::Staff && original_thread.created_by_id != user.sub {
|
||||
if !arc
|
||||
.pool
|
||||
.user_has_permission(user.sub, &UserPermission::EditForumThread)
|
||||
.await?
|
||||
&& original_thread.created_by_id != user.sub
|
||||
{
|
||||
return Err(Error::InsufficientPrivileges);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use arcadia_common::error::{Error, Result};
|
||||
use arcadia_storage::{
|
||||
models::{
|
||||
series::{EditedSeries, Series},
|
||||
user::UserClass,
|
||||
user::UserPermission,
|
||||
},
|
||||
redis::RedisPoolInterface,
|
||||
};
|
||||
@@ -31,7 +31,12 @@ pub async fn exec<R: RedisPoolInterface + 'static>(
|
||||
) -> Result<HttpResponse> {
|
||||
let series = arc.pool.find_series(&form.id).await?;
|
||||
|
||||
if user.class != UserClass::Staff && series.created_by_id != user.sub {
|
||||
if !arc
|
||||
.pool
|
||||
.user_has_permission(user.sub, &UserPermission::EditSeries)
|
||||
.await?
|
||||
&& series.created_by_id != user.sub
|
||||
{
|
||||
return Err(Error::InsufficientPrivileges);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use arcadia_common::error::Result;
|
||||
use arcadia_storage::{
|
||||
models::{
|
||||
staff_pm::{StaffPmMessage, UserCreatedStaffPmMessage},
|
||||
user::UserClass,
|
||||
user::UserPermission,
|
||||
},
|
||||
redis::RedisPoolInterface,
|
||||
};
|
||||
@@ -25,13 +25,23 @@ pub async fn exec<R: RedisPoolInterface + 'static>(
|
||||
arc: Data<Arcadia<R>>,
|
||||
user: Authdata,
|
||||
) -> Result<HttpResponse> {
|
||||
let is_staff = user.class == UserClass::Staff;
|
||||
let can_reply_staff_pm = arc
|
||||
.pool
|
||||
.user_has_permission(user.sub, &UserPermission::ReplyStaffPm)
|
||||
.await?;
|
||||
let can_read_staff_pm = arc
|
||||
.pool
|
||||
.user_has_permission(user.sub, &UserPermission::ReadStaffPm)
|
||||
.await?;
|
||||
// Allow creator (non-staff) to reply only to their own thread, and staff to any. We'll rely on DB check in get step.
|
||||
// Quick check: ensure user has access to thread
|
||||
let _ = arc
|
||||
.pool
|
||||
.get_staff_pm(message.staff_pm_id, user.sub, is_staff)
|
||||
.get_staff_pm(message.staff_pm_id, user.sub, can_read_staff_pm)
|
||||
.await?;
|
||||
let created = arc
|
||||
.pool
|
||||
.create_staff_pm_message(&message, user.sub, can_reply_staff_pm)
|
||||
.await?;
|
||||
let created = arc.pool.create_staff_pm_message(&message, user.sub).await?;
|
||||
Ok(HttpResponse::Created().json(created))
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ use actix_web::{
|
||||
HttpResponse,
|
||||
};
|
||||
use arcadia_common::error::Result;
|
||||
use arcadia_storage::models::staff_pm::StaffPmHierarchy;
|
||||
use arcadia_storage::{models::user::UserClass, redis::RedisPoolInterface};
|
||||
use arcadia_storage::models::{staff_pm::StaffPmHierarchy, user::UserPermission};
|
||||
use arcadia_storage::redis::RedisPoolInterface;
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
@@ -21,10 +21,13 @@ pub async fn exec<R: RedisPoolInterface + 'static>(
|
||||
user: Authdata,
|
||||
id: Path<i64>,
|
||||
) -> Result<HttpResponse> {
|
||||
let is_staff = user.class == UserClass::Staff;
|
||||
let can_read_staff_pm = arc
|
||||
.pool
|
||||
.user_has_permission(user.sub, &UserPermission::ReadStaffPm)
|
||||
.await?;
|
||||
let conv = arc
|
||||
.pool
|
||||
.get_staff_pm(id.into_inner(), user.sub, is_staff)
|
||||
.get_staff_pm(id.into_inner(), user.sub, can_read_staff_pm)
|
||||
.await?;
|
||||
Ok(HttpResponse::Ok().json(conv))
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@ use crate::{middlewares::auth_middleware::Authdata, Arcadia};
|
||||
use actix_web::{web::Data, HttpResponse};
|
||||
use arcadia_common::error::Result;
|
||||
use arcadia_storage::models::staff_pm::StaffPmOverview;
|
||||
use arcadia_storage::{models::user::UserClass, redis::RedisPoolInterface};
|
||||
use arcadia_storage::models::user::UserPermission;
|
||||
use arcadia_storage::redis::RedisPoolInterface;
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
@@ -16,7 +17,10 @@ pub async fn exec<R: RedisPoolInterface + 'static>(
|
||||
arc: Data<Arcadia<R>>,
|
||||
user: Authdata,
|
||||
) -> Result<HttpResponse> {
|
||||
let is_staff = user.class == UserClass::Staff;
|
||||
let conversations = arc.pool.list_staff_pms(user.sub, is_staff).await?;
|
||||
let can_read_staff_pm = arc
|
||||
.pool
|
||||
.user_has_permission(user.sub, &UserPermission::ReadStaffPm)
|
||||
.await?;
|
||||
let conversations = arc.pool.list_staff_pms(user.sub, can_read_staff_pm).await?;
|
||||
Ok(HttpResponse::Ok().json(conversations))
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use actix_web::{
|
||||
HttpResponse,
|
||||
};
|
||||
use arcadia_common::error::{Error, Result};
|
||||
use arcadia_storage::{models::user::UserClass, redis::RedisPoolInterface};
|
||||
use arcadia_storage::{models::user::UserPermission, redis::RedisPoolInterface};
|
||||
|
||||
#[utoipa::path(
|
||||
put,
|
||||
@@ -20,7 +20,11 @@ pub async fn exec<R: RedisPoolInterface + 'static>(
|
||||
user: Authdata,
|
||||
id: Path<i64>,
|
||||
) -> Result<HttpResponse> {
|
||||
if user.class != UserClass::Staff {
|
||||
if !arc
|
||||
.pool
|
||||
.user_has_permission(user.sub, &UserPermission::ResolveStaffPm)
|
||||
.await?
|
||||
{
|
||||
return Err(Error::InsufficientPrivileges);
|
||||
}
|
||||
let updated = arc.pool.resolve_staff_pm(id.into_inner(), user.sub).await?;
|
||||
|
||||
@@ -4,7 +4,7 @@ use actix_web::{
|
||||
HttpResponse,
|
||||
};
|
||||
use arcadia_common::error::{Error, Result};
|
||||
use arcadia_storage::{models::user::UserClass, redis::RedisPoolInterface};
|
||||
use arcadia_storage::{models::user::UserPermission, redis::RedisPoolInterface};
|
||||
|
||||
#[utoipa::path(
|
||||
put,
|
||||
@@ -20,7 +20,11 @@ pub async fn exec<R: RedisPoolInterface + 'static>(
|
||||
user: Authdata,
|
||||
id: Path<i64>,
|
||||
) -> Result<HttpResponse> {
|
||||
if user.class != UserClass::Staff {
|
||||
if !arc
|
||||
.pool
|
||||
.user_has_permission(user.sub, &UserPermission::UnresolveStaffPm)
|
||||
.await?
|
||||
{
|
||||
return Err(Error::InsufficientPrivileges);
|
||||
}
|
||||
let updated = arc
|
||||
|
||||
@@ -4,7 +4,7 @@ use actix_web::{
|
||||
HttpResponse,
|
||||
};
|
||||
use arcadia_common::error::{Error, Result};
|
||||
use arcadia_storage::{models::user::UserClass, redis::RedisPoolInterface};
|
||||
use arcadia_storage::{models::user::UserPermission, redis::RedisPoolInterface};
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
use utoipa::ToSchema;
|
||||
@@ -31,8 +31,11 @@ pub async fn exec<R: RedisPoolInterface + 'static>(
|
||||
arc: Data<Arcadia<R>>,
|
||||
user: Authdata,
|
||||
) -> Result<HttpResponse> {
|
||||
// Only staff can delete tags
|
||||
if user.class != UserClass::Staff {
|
||||
if !arc
|
||||
.pool
|
||||
.user_has_permission(user.sub, &UserPermission::DeleteTitleGroupTag)
|
||||
.await?
|
||||
{
|
||||
return Err(Error::InsufficientPrivileges);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use arcadia_common::error::{Error, Result};
|
||||
use arcadia_storage::{
|
||||
models::{
|
||||
title_group_tag::{EditedTitleGroupTag, TitleGroupTag},
|
||||
user::UserClass,
|
||||
user::UserPermission,
|
||||
},
|
||||
redis::RedisPoolInterface,
|
||||
};
|
||||
@@ -29,8 +29,11 @@ pub async fn exec<R: RedisPoolInterface + 'static>(
|
||||
arc: Data<Arcadia<R>>,
|
||||
user: Authdata,
|
||||
) -> Result<HttpResponse> {
|
||||
// Only staff can edit tags
|
||||
if user.class != UserClass::Staff {
|
||||
if !arc
|
||||
.pool
|
||||
.user_has_permission(user.sub, &UserPermission::EditTitleGroupTag)
|
||||
.await?
|
||||
{
|
||||
return Err(Error::InsufficientPrivileges);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ use actix_web::{
|
||||
use arcadia_storage::{
|
||||
models::{
|
||||
title_group::{EditedTitleGroup, TitleGroup},
|
||||
user::UserClass,
|
||||
user::UserPermission,
|
||||
},
|
||||
redis::RedisPoolInterface,
|
||||
};
|
||||
@@ -32,7 +32,12 @@ pub async fn exec<R: RedisPoolInterface + 'static>(
|
||||
) -> Result<HttpResponse> {
|
||||
let title_group = arc.pool.find_title_group(form.id).await?;
|
||||
|
||||
if user.class != UserClass::Staff && title_group.created_by_id != user.sub {
|
||||
if !arc
|
||||
.pool
|
||||
.user_has_permission(user.sub, &UserPermission::EditTitleGroup)
|
||||
.await?
|
||||
&& title_group.created_by_id != user.sub
|
||||
{
|
||||
return Err(Error::InsufficientPrivileges);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use arcadia_common::error::{Error, Result};
|
||||
use arcadia_storage::{
|
||||
models::{
|
||||
title_group_comment::{EditedTitleGroupComment, TitleGroupComment},
|
||||
user::UserClass,
|
||||
user::UserPermission,
|
||||
},
|
||||
redis::RedisPoolInterface,
|
||||
};
|
||||
@@ -38,7 +38,10 @@ pub async fn exec<R: RedisPoolInterface + 'static>(
|
||||
|
||||
let comment = arc.pool.find_title_group_comment(comment_id).await?;
|
||||
|
||||
let is_staff = user.class == UserClass::Staff;
|
||||
let is_staff = arc
|
||||
.pool
|
||||
.user_has_permission(user.sub, &UserPermission::EditTitleGroupComment)
|
||||
.await?;
|
||||
let is_owner = comment.created_by_id == user.sub;
|
||||
|
||||
if !is_staff && (!is_owner || comment.locked) {
|
||||
|
||||
@@ -5,7 +5,7 @@ use actix_web::{
|
||||
use arcadia_storage::{
|
||||
models::{
|
||||
torrent_request::{EditedTorrentRequest, TorrentRequest},
|
||||
user::UserClass,
|
||||
user::UserPermission,
|
||||
},
|
||||
redis::RedisPoolInterface,
|
||||
};
|
||||
@@ -33,7 +33,12 @@ pub async fn exec<R: RedisPoolInterface + 'static>(
|
||||
) -> Result<HttpResponse> {
|
||||
let torrent_request = arc.pool.find_torrent_request(form.id).await?;
|
||||
|
||||
if user.class != UserClass::Staff && torrent_request.created_by_id != user.sub {
|
||||
if !arc
|
||||
.pool
|
||||
.user_has_permission(user.sub, &UserPermission::EditTorrentRequest)
|
||||
.await?
|
||||
&& torrent_request.created_by_id != user.sub
|
||||
{
|
||||
return Err(Error::InsufficientPrivileges);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use serde_json::json;
|
||||
use crate::{middlewares::auth_middleware::Authdata, Arcadia};
|
||||
use arcadia_common::error::{Error, Result};
|
||||
use arcadia_storage::{
|
||||
models::{torrent::TorrentToDelete, user::UserClass},
|
||||
models::{torrent::TorrentToDelete, user::UserPermission},
|
||||
redis::RedisPoolInterface,
|
||||
};
|
||||
|
||||
@@ -28,7 +28,11 @@ pub async fn exec<R: RedisPoolInterface + 'static>(
|
||||
arc: Data<Arcadia<R>>,
|
||||
user: Authdata,
|
||||
) -> Result<HttpResponse> {
|
||||
if user.class != UserClass::Staff {
|
||||
if !arc
|
||||
.pool
|
||||
.user_has_permission(user.sub, &UserPermission::DeleteTorrent)
|
||||
.await?
|
||||
{
|
||||
return Err(Error::InsufficientPrivileges);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ use arcadia_common::error::{Error, Result};
|
||||
use arcadia_storage::{
|
||||
models::{
|
||||
torrent::{EditedTorrent, Torrent},
|
||||
user::UserClass,
|
||||
user::UserPermission,
|
||||
},
|
||||
redis::RedisPoolInterface,
|
||||
};
|
||||
@@ -32,7 +32,12 @@ pub async fn exec<R: RedisPoolInterface + 'static>(
|
||||
) -> Result<HttpResponse> {
|
||||
let torrent = arc.pool.find_torrent(form.id).await?;
|
||||
|
||||
if user.class != UserClass::Staff && torrent.created_by_id != user.sub {
|
||||
if !arc
|
||||
.pool
|
||||
.user_has_permission(user.sub, &UserPermission::EditTorrent)
|
||||
.await?
|
||||
&& torrent.created_by_id != user.sub
|
||||
{
|
||||
return Err(Error::InsufficientPrivileges);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
use actix_web::{web::Data, HttpResponse};
|
||||
|
||||
use crate::{middlewares::auth_middleware::Authdata, Arcadia};
|
||||
use arcadia_common::error::{Error, Result};
|
||||
use arcadia_storage::{
|
||||
models::{torrent::TorrentMinimal, user::UserClass},
|
||||
redis::RedisPoolInterface,
|
||||
};
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "Get registered torrents",
|
||||
tag = "Torrent",
|
||||
path = "/api/torrents/registered",
|
||||
security(
|
||||
("http" = ["Bearer"])
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "All registered torrents", body=Vec<TorrentMinimal>),
|
||||
)
|
||||
)]
|
||||
pub async fn exec<R: RedisPoolInterface + 'static>(
|
||||
arc: Data<Arcadia<R>>,
|
||||
user: Authdata,
|
||||
) -> Result<HttpResponse> {
|
||||
if user.class != UserClass::Tracker {
|
||||
return Err(Error::InsufficientPrivileges);
|
||||
}
|
||||
|
||||
let torrents = arc.pool.find_registered_torrents().await?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(torrents))
|
||||
}
|
||||
@@ -3,7 +3,6 @@ pub mod create_torrent_report;
|
||||
pub mod delete_torrent;
|
||||
pub mod download_dottorrent_file;
|
||||
pub mod edit_torrent;
|
||||
pub mod get_registered_torrents;
|
||||
pub mod get_top_torrents;
|
||||
pub mod get_upload_information;
|
||||
|
||||
@@ -18,7 +17,6 @@ pub fn config<R: RedisPoolInterface + 'static>(cfg: &mut ServiceConfig) {
|
||||
.route(put().to(self::edit_torrent::exec::<R>))
|
||||
.route(delete().to(self::delete_torrent::exec::<R>)),
|
||||
);
|
||||
cfg.service(resource("/registered").route(get().to(self::get_registered_torrents::exec::<R>)));
|
||||
cfg.service(resource("/upload-info").route(get().to(self::get_upload_information::exec::<R>)));
|
||||
cfg.service(resource("/top").route(get().to(self::get_top_torrents::exec::<R>)));
|
||||
cfg.service(resource("/reports").route(post().to(self::create_torrent_report::exec::<R>)));
|
||||
|
||||
@@ -6,7 +6,7 @@ use actix_web::{
|
||||
use arcadia_common::error::{Error, Result};
|
||||
use arcadia_storage::{
|
||||
models::{
|
||||
user::UserClass,
|
||||
user::UserPermission,
|
||||
user_application::{UserApplication, UserApplicationStatus},
|
||||
},
|
||||
redis::RedisPoolInterface,
|
||||
@@ -42,7 +42,11 @@ pub async fn exec<R: RedisPoolInterface + 'static>(
|
||||
user: Authdata,
|
||||
query: Query<GetUserApplicationsQuery>,
|
||||
) -> Result<HttpResponse> {
|
||||
if user.class != UserClass::Staff {
|
||||
if !arc
|
||||
.pool
|
||||
.user_has_permission(user.sub, &UserPermission::GetUserApplication)
|
||||
.await?
|
||||
{
|
||||
return Err(Error::InsufficientPrivileges);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ use actix_web::{
|
||||
use arcadia_common::error::{Error, Result};
|
||||
use arcadia_storage::{
|
||||
models::{
|
||||
user::UserClass,
|
||||
user::UserPermission,
|
||||
user_application::{UserApplication, UserApplicationStatus},
|
||||
},
|
||||
redis::RedisPoolInterface,
|
||||
@@ -39,7 +39,11 @@ pub async fn exec<R: RedisPoolInterface + 'static>(
|
||||
user: Authdata,
|
||||
form: Json<UpdateUserApplication>,
|
||||
) -> Result<HttpResponse> {
|
||||
if user.class != UserClass::Staff {
|
||||
if !arc
|
||||
.pool
|
||||
.user_has_permission(user.sub, &UserPermission::UpdateUserApplication)
|
||||
.await?
|
||||
{
|
||||
return Err(Error::InsufficientPrivileges);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ use actix_web::{
|
||||
};
|
||||
use arcadia_common::error::{Error, Result};
|
||||
use arcadia_storage::{
|
||||
models::user::{UserClass, UserCreatedUserWarning, UserWarning},
|
||||
models::user::{UserCreatedUserWarning, UserPermission, UserWarning},
|
||||
redis::RedisPoolInterface,
|
||||
};
|
||||
|
||||
@@ -26,7 +26,11 @@ pub async fn exec<R: RedisPoolInterface + 'static>(
|
||||
user: Authdata,
|
||||
arc: Data<Arcadia<R>>,
|
||||
) -> Result<HttpResponse> {
|
||||
if user.class != UserClass::Staff {
|
||||
if !arc
|
||||
.pool
|
||||
.user_has_permission(user.sub, &UserPermission::WarnUser)
|
||||
.await?
|
||||
{
|
||||
return Err(Error::InsufficientPrivileges);
|
||||
}
|
||||
let user_warning = arc.pool.create_user_warning(user.sub, &form).await?;
|
||||
|
||||
@@ -6,7 +6,7 @@ use actix_web::{
|
||||
use arcadia_common::error::{Error, Result};
|
||||
use arcadia_storage::{
|
||||
models::{
|
||||
user::UserClass,
|
||||
user::UserPermission,
|
||||
wiki::{UserCreatedWikiArticle, WikiArticle},
|
||||
},
|
||||
redis::RedisPoolInterface,
|
||||
@@ -29,7 +29,11 @@ pub async fn exec<R: RedisPoolInterface + 'static>(
|
||||
arc: Data<Arcadia<R>>,
|
||||
user: Authdata,
|
||||
) -> Result<HttpResponse> {
|
||||
if user.class != UserClass::Staff {
|
||||
if !arc
|
||||
.pool
|
||||
.user_has_permission(user.sub, &UserPermission::CreateWikiArticle)
|
||||
.await?
|
||||
{
|
||||
return Err(Error::InsufficientPrivileges);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ use actix_web::{
|
||||
use arcadia_common::error::{Error, Result};
|
||||
use arcadia_storage::{
|
||||
models::{
|
||||
user::UserClass,
|
||||
user::UserPermission,
|
||||
wiki::{EditedWikiArticle, WikiArticle},
|
||||
},
|
||||
redis::RedisPoolInterface,
|
||||
@@ -29,7 +29,11 @@ pub async fn exec<R: RedisPoolInterface + 'static>(
|
||||
arc: Data<Arcadia<R>>,
|
||||
user: Authdata,
|
||||
) -> Result<HttpResponse> {
|
||||
if user.class != UserClass::Staff {
|
||||
if !arc
|
||||
.pool
|
||||
.user_has_permission(user.sub, &UserPermission::EditWikiArticle)
|
||||
.await?
|
||||
{
|
||||
return Err(Error::InsufficientPrivileges);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,17 +6,13 @@ use actix_web::{
|
||||
Error, FromRequest, HttpMessage as _, HttpRequest,
|
||||
};
|
||||
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
||||
use arcadia_storage::{
|
||||
models::user::{Claims, UserClass},
|
||||
redis::RedisPoolInterface,
|
||||
};
|
||||
use arcadia_storage::{models::user::Claims, redis::RedisPoolInterface};
|
||||
use futures_util::future::{err, ok, Ready};
|
||||
use jsonwebtoken::{decode, errors::ErrorKind, DecodingKey, Validation};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Authdata {
|
||||
pub sub: i32,
|
||||
pub class: UserClass,
|
||||
}
|
||||
|
||||
impl FromRequest for Authdata {
|
||||
@@ -96,10 +92,7 @@ async fn validate_bearer_auth<R: RedisPoolInterface + 'static>(
|
||||
}
|
||||
Ok(_) => {
|
||||
let _ = arc.pool.update_last_seen(user_id).await;
|
||||
req.extensions_mut().insert(Authdata {
|
||||
sub: user_id,
|
||||
class: token_data.claims.class,
|
||||
});
|
||||
req.extensions_mut().insert(Authdata { sub: user_id });
|
||||
}
|
||||
Err(e) => return Err((ErrorUnauthorized(e.to_string()), req)),
|
||||
};
|
||||
@@ -118,10 +111,7 @@ async fn validate_user_api_key<R: RedisPoolInterface + 'static>(
|
||||
Err(e) => return Err((actix_web::error::ErrorUnauthorized(e.to_string()), req)),
|
||||
};
|
||||
|
||||
req.extensions_mut().insert(Authdata {
|
||||
sub: user.id,
|
||||
class: user.class,
|
||||
});
|
||||
req.extensions_mut().insert(Authdata { sub: user.id });
|
||||
|
||||
Ok(req)
|
||||
}
|
||||
|
||||
@@ -48,31 +48,53 @@ pub async fn create_test_app<R: RedisPoolInterface + 'static>(
|
||||
}
|
||||
|
||||
pub enum TestUser {
|
||||
// Requires the "with_test_user" fixture.
|
||||
// Requires the "with_test_users" fixture.
|
||||
Standard,
|
||||
|
||||
// Requires the "with_test_user2" fixture.
|
||||
Staff,
|
||||
EditArtist,
|
||||
EditSeries,
|
||||
EditTitleGroupComment,
|
||||
CreateCssSheet,
|
||||
EditCssSheet,
|
||||
SetDefaultCssSheet,
|
||||
CreateForumCategory,
|
||||
EditForumCategory,
|
||||
CreateForumSubCategory,
|
||||
EditForumSubCategory,
|
||||
EditForumThread,
|
||||
EditForumPost,
|
||||
ForumCategoryFlow,
|
||||
ForumSubCategoryFlow,
|
||||
}
|
||||
|
||||
impl TestUser {
|
||||
fn get_login_payload(&self) -> Login {
|
||||
match self {
|
||||
TestUser::Standard => Login {
|
||||
username: "test_user".into(),
|
||||
password: "test_password".into(),
|
||||
remember_me: true,
|
||||
},
|
||||
TestUser::Staff => Login {
|
||||
username: "test_user2".into(),
|
||||
password: "test_password".into(),
|
||||
remember_me: true,
|
||||
},
|
||||
let username = match self {
|
||||
TestUser::Standard => "user_basic",
|
||||
TestUser::EditArtist => "user_edit_art",
|
||||
TestUser::EditSeries => "user_edit_ser",
|
||||
TestUser::EditTitleGroupComment => "user_edit_tgc",
|
||||
TestUser::CreateCssSheet => "user_css_crt",
|
||||
TestUser::EditCssSheet => "user_css_edit",
|
||||
TestUser::SetDefaultCssSheet => "user_css_def",
|
||||
TestUser::CreateForumCategory => "user_cat_crt",
|
||||
TestUser::EditForumCategory => "user_cat_edit",
|
||||
TestUser::CreateForumSubCategory => "user_sub_crt",
|
||||
TestUser::EditForumSubCategory => "user_sub_edit",
|
||||
TestUser::EditForumThread => "user_thr_edit",
|
||||
TestUser::EditForumPost => "user_post_edit",
|
||||
TestUser::ForumCategoryFlow => "user_cat_flow",
|
||||
TestUser::ForumSubCategoryFlow => "user_sub_flow",
|
||||
};
|
||||
|
||||
Login {
|
||||
username: username.into(),
|
||||
password: "test_password".into(),
|
||||
remember_me: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Requires "with_test_user" fixture.
|
||||
// Requires "with_test_users" fixture.
|
||||
pub async fn create_test_app_and_login<R: RedisPoolInterface + 'static>(
|
||||
pool: Arc<ConnectionPool>,
|
||||
redis_pool: R,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
INSERT INTO
|
||||
users (banned, username, email, password_hash, registered_from_ip, passkey)
|
||||
users (banned, username, email, password_hash, registered_from_ip, passkey, class_name)
|
||||
VALUES
|
||||
-- passkey d2037c66dd3e13044e0d2f9b891c3837
|
||||
(true, 'test_user', 'test_email@testdomain.com', '$argon2id$v=19$m=19456,t=2,p=1$WM6V9pJ2ya7+N+NNIUtolg$n128u9idizCHLwZ9xhKaxOttLaAVZZgvfRZlRAnfyKk', '10.10.4.88', 'mqdfkjqmsdkf')
|
||||
(true, 'test_user', 'test_email@testdomain.com', '$argon2id$v=19$m=19456,t=2,p=1$WM6V9pJ2ya7+N+NNIUtolg$n128u9idizCHLwZ9xhKaxOttLaAVZZgvfRZlRAnfyKk', '10.10.4.88', 'mqdfkjqmsdkf', 'newbie')
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
INSERT INTO
|
||||
users (id, username, email, password_hash, registered_from_ip, passkey, class)
|
||||
VALUES
|
||||
(100, 'test_user', 'test_email@testdomain.com', '$argon2id$v=19$m=19456,t=2,p=1$WM6V9pJ2ya7+N+NNIUtolg$n128u9idizCHLwZ9xhKaxOttLaAVZZgvfRZlRAnfyKk', '10.10.4.88', 'd2037c66dd3e13044e0d2f9b891c3837', 'newbie')
|
||||
@@ -1,4 +0,0 @@
|
||||
INSERT INTO
|
||||
users (id, username, email, password_hash, registered_from_ip, passkey, class)
|
||||
VALUES
|
||||
(101, 'test_user2', 'test_email2@testdomain.com', '$argon2id$v=19$m=19456,t=2,p=1$WM6V9pJ2ya7+N+NNIUtolg$n128u9idizCHLwZ9xhKaxOttLaAVZZgvfRZlRAnfyKk', '10.10.4.88', 'd2037c66dd3e13044e0d2f9b891c3838', 'staff')
|
||||
59
backend/api/tests/fixtures/with_test_users.sql
vendored
Normal file
59
backend/api/tests/fixtures/with_test_users.sql
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
-- Basic user for read operations and permission denial tests
|
||||
INSERT INTO users (id, username, email, password_hash, registered_from_ip, passkey, class_name, permissions)
|
||||
VALUES (100, 'user_basic', 'test_user@testdomain.com', '$argon2id$v=19$m=19456,t=2,p=1$WM6V9pJ2ya7+N+NNIUtolg$n128u9idizCHLwZ9xhKaxOttLaAVZZgvfRZlRAnfyKk', '10.10.4.88', 'd2037c66dd3e13044e0d2f9b891c3837', 'newbie', '{download_torrent}');
|
||||
|
||||
-- User with edit_artist permission
|
||||
INSERT INTO users (id, username, email, password_hash, registered_from_ip, passkey, class_name, permissions)
|
||||
VALUES (101, 'user_edit_art', 'test_user_edit_artist@testdomain.com', '$argon2id$v=19$m=19456,t=2,p=1$WM6V9pJ2ya7+N+NNIUtolg$n128u9idizCHLwZ9xhKaxOttLaAVZZgvfRZlRAnfyKk', '10.10.4.88', 'd2037c66dd3e13044e0d2f9b891c3838', 'newbie', '{edit_artist}');
|
||||
|
||||
-- User with edit_series permission
|
||||
INSERT INTO users (id, username, email, password_hash, registered_from_ip, passkey, class_name, permissions)
|
||||
VALUES (102, 'user_edit_ser', 'test_user_edit_series@testdomain.com', '$argon2id$v=19$m=19456,t=2,p=1$WM6V9pJ2ya7+N+NNIUtolg$n128u9idizCHLwZ9xhKaxOttLaAVZZgvfRZlRAnfyKk', '10.10.4.88', 'd2037c66dd3e13044e0d2f9b891c3839', 'newbie', '{edit_series}');
|
||||
|
||||
-- User with edit_title_group_comment permission
|
||||
INSERT INTO users (id, username, email, password_hash, registered_from_ip, passkey, class_name, permissions)
|
||||
VALUES (103, 'user_edit_tgc', 'test_user_edit_title_group_comment@testdomain.com', '$argon2id$v=19$m=19456,t=2,p=1$WM6V9pJ2ya7+N+NNIUtolg$n128u9idizCHLwZ9xhKaxOttLaAVZZgvfRZlRAnfyKk', '10.10.4.88', 'd2037c66dd3e13044e0d2f9b891c383a', 'newbie', '{edit_title_group_comment}');
|
||||
|
||||
-- User with create_css_sheet permission
|
||||
INSERT INTO users (id, username, email, password_hash, registered_from_ip, passkey, class_name, permissions)
|
||||
VALUES (104, 'user_css_crt', 'test_user_create_css_sheet@testdomain.com', '$argon2id$v=19$m=19456,t=2,p=1$WM6V9pJ2ya7+N+NNIUtolg$n128u9idizCHLwZ9xhKaxOttLaAVZZgvfRZlRAnfyKk', '10.10.4.88', 'd2037c66dd3e13044e0d2f9b891c383b', 'newbie', '{create_css_sheet}');
|
||||
|
||||
-- User with edit_css_sheet permission
|
||||
INSERT INTO users (id, username, email, password_hash, registered_from_ip, passkey, class_name, permissions)
|
||||
VALUES (105, 'user_css_edit', 'test_user_edit_css_sheet@testdomain.com', '$argon2id$v=19$m=19456,t=2,p=1$WM6V9pJ2ya7+N+NNIUtolg$n128u9idizCHLwZ9xhKaxOttLaAVZZgvfRZlRAnfyKk', '10.10.4.88', 'd2037c66dd3e13044e0d2f9b891c383c', 'newbie', '{edit_css_sheet}');
|
||||
|
||||
-- User with set_default_css_sheet permission
|
||||
INSERT INTO users (id, username, email, password_hash, registered_from_ip, passkey, class_name, permissions)
|
||||
VALUES (106, 'user_css_def', 'test_user_set_default_css_sheet@testdomain.com', '$argon2id$v=19$m=19456,t=2,p=1$WM6V9pJ2ya7+N+NNIUtolg$n128u9idizCHLwZ9xhKaxOttLaAVZZgvfRZlRAnfyKk', '10.10.4.88', 'd2037c66dd3e13044e0d2f9b891c383d', 'newbie', '{set_default_css_sheet}');
|
||||
|
||||
-- User with create_forum_category permission
|
||||
INSERT INTO users (id, username, email, password_hash, registered_from_ip, passkey, class_name, permissions)
|
||||
VALUES (107, 'user_cat_crt', 'test_user_create_forum_category@testdomain.com', '$argon2id$v=19$m=19456,t=2,p=1$WM6V9pJ2ya7+N+NNIUtolg$n128u9idizCHLwZ9xhKaxOttLaAVZZgvfRZlRAnfyKk', '10.10.4.88', 'd2037c66dd3e13044e0d2f9b891c383e', 'newbie', '{create_forum_category}');
|
||||
|
||||
-- User with edit_forum_category permission
|
||||
INSERT INTO users (id, username, email, password_hash, registered_from_ip, passkey, class_name, permissions)
|
||||
VALUES (108, 'user_cat_edit', 'test_user_edit_forum_category@testdomain.com', '$argon2id$v=19$m=19456,t=2,p=1$WM6V9pJ2ya7+N+NNIUtolg$n128u9idizCHLwZ9xhKaxOttLaAVZZgvfRZlRAnfyKk', '10.10.4.88', 'd2037c66dd3e13044e0d2f9b891c383f', 'newbie', '{edit_forum_category}');
|
||||
|
||||
-- User with create_forum_sub_category permission
|
||||
INSERT INTO users (id, username, email, password_hash, registered_from_ip, passkey, class_name, permissions)
|
||||
VALUES (109, 'user_sub_crt', 'test_user_create_forum_sub_category@testdomain.com', '$argon2id$v=19$m=19456,t=2,p=1$WM6V9pJ2ya7+N+NNIUtolg$n128u9idizCHLwZ9xhKaxOttLaAVZZgvfRZlRAnfyKk', '10.10.4.88', 'd2037c66dd3e13044e0d2f9b891c3840', 'newbie', '{create_forum_sub_category}');
|
||||
|
||||
-- User with edit_forum_sub_category permission
|
||||
INSERT INTO users (id, username, email, password_hash, registered_from_ip, passkey, class_name, permissions)
|
||||
VALUES (110, 'user_sub_edit', 'test_user_edit_forum_sub_category@testdomain.com', '$argon2id$v=19$m=19456,t=2,p=1$WM6V9pJ2ya7+N+NNIUtolg$n128u9idizCHLwZ9xhKaxOttLaAVZZgvfRZlRAnfyKk', '10.10.4.88', 'd2037c66dd3e13044e0d2f9b891c3841', 'newbie', '{edit_forum_sub_category}');
|
||||
|
||||
-- User with edit_forum_thread permission
|
||||
INSERT INTO users (id, username, email, password_hash, registered_from_ip, passkey, class_name, permissions)
|
||||
VALUES (111, 'user_thr_edit', 'test_user_edit_forum_thread@testdomain.com', '$argon2id$v=19$m=19456,t=2,p=1$WM6V9pJ2ya7+N+NNIUtolg$n128u9idizCHLwZ9xhKaxOttLaAVZZgvfRZlRAnfyKk', '10.10.4.88', 'd2037c66dd3e13044e0d2f9b891c3842', 'newbie', '{edit_forum_thread}');
|
||||
|
||||
-- User with edit_forum_post permission
|
||||
INSERT INTO users (id, username, email, password_hash, registered_from_ip, passkey, class_name, permissions)
|
||||
VALUES (112, 'user_post_edit', 'test_user_edit_forum_post@testdomain.com', '$argon2id$v=19$m=19456,t=2,p=1$WM6V9pJ2ya7+N+NNIUtolg$n128u9idizCHLwZ9xhKaxOttLaAVZZgvfRZlRAnfyKk', '10.10.4.88', 'd2037c66dd3e13044e0d2f9b891c3843', 'newbie', '{edit_forum_post}');
|
||||
|
||||
-- User with both create and edit forum category permissions (for flow tests)
|
||||
INSERT INTO users (id, username, email, password_hash, registered_from_ip, passkey, class_name, permissions)
|
||||
VALUES (113, 'user_cat_flow', 'test_user_cat_flow@testdomain.com', '$argon2id$v=19$m=19456,t=2,p=1$WM6V9pJ2ya7+N+NNIUtolg$n128u9idizCHLwZ9xhKaxOttLaAVZZgvfRZlRAnfyKk', '10.10.4.88', 'd2037c66dd3e13044e0d2f9b891c3844', 'newbie', '{create_forum_category,edit_forum_category}');
|
||||
|
||||
-- User with both create and edit forum sub category permissions (for flow tests)
|
||||
INSERT INTO users (id, username, email, password_hash, registered_from_ip, passkey, class_name, permissions)
|
||||
VALUES (114, 'user_sub_flow', 'test_user_sub_flow@testdomain.com', '$argon2id$v=19$m=19456,t=2,p=1$WM6V9pJ2ya7+N+NNIUtolg$n128u9idizCHLwZ9xhKaxOttLaAVZZgvfRZlRAnfyKk', '10.10.4.88', 'd2037c66dd3e13044e0d2f9b891c3845', 'newbie', '{create_forum_sub_category,edit_forum_sub_category}');
|
||||
@@ -14,13 +14,19 @@ use sqlx::PgPool;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures("with_test_user2", "with_test_artist"),
|
||||
fixtures("with_test_users", "with_test_artist"),
|
||||
migrations = "../storage/migrations"
|
||||
)]
|
||||
async fn test_staff_can_edit_artist(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let (service, user) =
|
||||
create_test_app_and_login(pool, MockRedisPool::default(), 100, 100, TestUser::Staff).await;
|
||||
let (service, user) = create_test_app_and_login(
|
||||
pool,
|
||||
MockRedisPool::default(),
|
||||
100,
|
||||
100,
|
||||
TestUser::EditArtist,
|
||||
)
|
||||
.await;
|
||||
|
||||
let req_body = EditedArtist{
|
||||
id: 1,
|
||||
@@ -49,7 +55,7 @@ async fn test_staff_can_edit_artist(pool: PgPool) {
|
||||
}
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures("with_test_user", "with_test_artists_for_search"),
|
||||
fixtures("with_test_users", "with_test_artists_for_search"),
|
||||
migrations = "../storage/migrations"
|
||||
)]
|
||||
async fn test_search_artists_returns_paginated_results(pool: PgPool) {
|
||||
@@ -73,7 +79,7 @@ async fn test_search_artists_returns_paginated_results(pool: PgPool) {
|
||||
}
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures("with_test_user", "with_test_artists_for_search"),
|
||||
fixtures("with_test_users", "with_test_artists_for_search"),
|
||||
migrations = "../storage/migrations"
|
||||
)]
|
||||
async fn test_search_artists_filters_by_name(pool: PgPool) {
|
||||
@@ -96,7 +102,7 @@ async fn test_search_artists_filters_by_name(pool: PgPool) {
|
||||
}
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures("with_test_user", "with_test_artists_for_search"),
|
||||
fixtures("with_test_users", "with_test_artists_for_search"),
|
||||
migrations = "../storage/migrations"
|
||||
)]
|
||||
async fn test_search_artists_pagination(pool: PgPool) {
|
||||
|
||||
@@ -133,7 +133,7 @@ async fn test_duplicate_username_registration(pool: PgPool) {
|
||||
}
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures("with_test_user", "with_test_user_invite"),
|
||||
fixtures("with_test_users", "with_test_user_invite"),
|
||||
migrations = "../storage/migrations"
|
||||
)]
|
||||
async fn test_closed_registration_failures(pool: PgPool) {
|
||||
@@ -191,7 +191,7 @@ async fn test_closed_registration_failures(pool: PgPool) {
|
||||
}
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures("with_test_user", "with_test_user_invite"),
|
||||
fixtures("with_test_users", "with_test_user_invite"),
|
||||
migrations = "../storage/migrations"
|
||||
)]
|
||||
async fn test_closed_registration_success(pool: PgPool) {
|
||||
@@ -255,7 +255,7 @@ async fn test_closed_registration_success(pool: PgPool) {
|
||||
}
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures("with_test_user", "with_expired_test_user_invite"),
|
||||
fixtures("with_test_users", "with_expired_test_user_invite"),
|
||||
migrations = "../storage/migrations"
|
||||
)]
|
||||
async fn test_closed_registration_expired_failure(pool: PgPool) {
|
||||
@@ -290,7 +290,7 @@ async fn test_closed_registration_expired_failure(pool: PgPool) {
|
||||
);
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("with_test_user"), migrations = "../storage/migrations")]
|
||||
#[sqlx::test(fixtures("with_test_users"), migrations = "../storage/migrations")]
|
||||
async fn test_authorized_endpoint_after_login(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let (service, user) =
|
||||
@@ -314,7 +314,7 @@ async fn test_authorized_endpoint_after_login(pool: PgPool) {
|
||||
|
||||
let user = call_and_read_body_json::<MeResponse, _>(&service, req).await;
|
||||
|
||||
assert_eq!(user.user.username, "test_user");
|
||||
assert_eq!(user.user.username, "user_basic");
|
||||
}
|
||||
|
||||
#[sqlx::test(
|
||||
@@ -346,7 +346,7 @@ async fn test_login_with_banned_user(pool: PgPool) {
|
||||
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("with_test_user"), migrations = "../storage/migrations")]
|
||||
#[sqlx::test(fixtures("with_test_users"), migrations = "../storage/migrations")]
|
||||
async fn test_refresh_with_invalidated_token(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let (service, user) = create_test_app_and_login(
|
||||
|
||||
@@ -15,11 +15,17 @@ use mocks::mock_redis::MockRedisPool;
|
||||
use sqlx::PgPool;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[sqlx::test(fixtures("with_test_user2"), migrations = "../storage/migrations")]
|
||||
#[sqlx::test(fixtures("with_test_users"), migrations = "../storage/migrations")]
|
||||
async fn test_staff_can_create_css_sheet(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let (service, user) =
|
||||
create_test_app_and_login(pool, MockRedisPool::default(), 100, 100, TestUser::Staff).await;
|
||||
let (service, user) = create_test_app_and_login(
|
||||
pool,
|
||||
MockRedisPool::default(),
|
||||
100,
|
||||
100,
|
||||
TestUser::CreateCssSheet,
|
||||
)
|
||||
.await;
|
||||
|
||||
let css_sheet = UserCreatedCssSheet {
|
||||
name: "test_sheet".into(),
|
||||
@@ -43,7 +49,7 @@ async fn test_staff_can_create_css_sheet(pool: PgPool) {
|
||||
assert_eq!(created.preview_image_url, "https://example.com/preview.png");
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("with_test_user"), migrations = "../storage/migrations")]
|
||||
#[sqlx::test(fixtures("with_test_users"), migrations = "../storage/migrations")]
|
||||
async fn test_regular_user_cannot_create_css_sheet(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let (service, user) =
|
||||
@@ -68,13 +74,14 @@ async fn test_regular_user_cannot_create_css_sheet(pool: PgPool) {
|
||||
}
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures("with_test_user2", "with_test_css_sheets"),
|
||||
fixtures("with_test_users", "with_test_css_sheets"),
|
||||
migrations = "../storage/migrations"
|
||||
)]
|
||||
async fn test_get_css_sheets(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let (service, user) =
|
||||
create_test_app_and_login(pool, MockRedisPool::default(), 100, 100, TestUser::Staff).await;
|
||||
create_test_app_and_login(pool, MockRedisPool::default(), 100, 100, TestUser::Standard)
|
||||
.await;
|
||||
|
||||
let req = test::TestRequest::get()
|
||||
.insert_header(("X-Forwarded-For", "10.10.4.88"))
|
||||
@@ -91,13 +98,14 @@ async fn test_get_css_sheets(pool: PgPool) {
|
||||
}
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures("with_test_user2", "with_test_css_sheets"),
|
||||
fixtures("with_test_users", "with_test_css_sheets"),
|
||||
migrations = "../storage/migrations"
|
||||
)]
|
||||
async fn test_get_css_sheet_by_name(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let (service, user) =
|
||||
create_test_app_and_login(pool, MockRedisPool::default(), 100, 100, TestUser::Staff).await;
|
||||
create_test_app_and_login(pool, MockRedisPool::default(), 100, 100, TestUser::Standard)
|
||||
.await;
|
||||
|
||||
let req = test::TestRequest::get()
|
||||
.insert_header(("X-Forwarded-For", "10.10.4.88"))
|
||||
@@ -111,11 +119,12 @@ async fn test_get_css_sheet_by_name(pool: PgPool) {
|
||||
assert_eq!(sheet.css, "body { color: red; }");
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("with_test_user2"), migrations = "../storage/migrations")]
|
||||
#[sqlx::test(fixtures("with_test_users"), migrations = "../storage/migrations")]
|
||||
async fn test_get_nonexistent_css_sheet(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let (service, user) =
|
||||
create_test_app_and_login(pool, MockRedisPool::default(), 100, 100, TestUser::Staff).await;
|
||||
create_test_app_and_login(pool, MockRedisPool::default(), 100, 100, TestUser::Standard)
|
||||
.await;
|
||||
|
||||
let req = test::TestRequest::get()
|
||||
.insert_header(("X-Forwarded-For", "10.10.4.88"))
|
||||
@@ -128,13 +137,19 @@ async fn test_get_nonexistent_css_sheet(pool: PgPool) {
|
||||
}
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures("with_test_user2", "with_test_css_sheets"),
|
||||
fixtures("with_test_users", "with_test_css_sheets"),
|
||||
migrations = "../storage/migrations"
|
||||
)]
|
||||
async fn test_staff_can_edit_css_sheet(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let (service, user) =
|
||||
create_test_app_and_login(pool, MockRedisPool::default(), 100, 100, TestUser::Staff).await;
|
||||
let (service, user) = create_test_app_and_login(
|
||||
pool,
|
||||
MockRedisPool::default(),
|
||||
100,
|
||||
100,
|
||||
TestUser::EditCssSheet,
|
||||
)
|
||||
.await;
|
||||
|
||||
let edited = EditedCssSheet {
|
||||
old_name: "test_sheet_1".into(),
|
||||
@@ -161,7 +176,7 @@ async fn test_staff_can_edit_css_sheet(pool: PgPool) {
|
||||
}
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures("with_test_user", "with_test_css_sheets"),
|
||||
fixtures("with_test_users", "with_test_css_sheets"),
|
||||
migrations = "../storage/migrations"
|
||||
)]
|
||||
async fn test_regular_user_cannot_edit_css_sheet(pool: PgPool) {
|
||||
@@ -189,13 +204,19 @@ async fn test_regular_user_cannot_edit_css_sheet(pool: PgPool) {
|
||||
}
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures("with_test_user2", "with_test_css_sheets"),
|
||||
fixtures("with_test_users", "with_test_css_sheets"),
|
||||
migrations = "../storage/migrations"
|
||||
)]
|
||||
async fn test_staff_can_set_default_css_sheet(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let (service, user) =
|
||||
create_test_app_and_login(pool, MockRedisPool::default(), 100, 100, TestUser::Staff).await;
|
||||
let (service, user) = create_test_app_and_login(
|
||||
pool,
|
||||
MockRedisPool::default(),
|
||||
100,
|
||||
100,
|
||||
TestUser::SetDefaultCssSheet,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Set a fixture sheet as default
|
||||
let req = test::TestRequest::put()
|
||||
@@ -220,7 +241,7 @@ async fn test_staff_can_set_default_css_sheet(pool: PgPool) {
|
||||
}
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures("with_test_user", "with_test_css_sheets"),
|
||||
fixtures("with_test_users", "with_test_css_sheets"),
|
||||
migrations = "../storage/migrations"
|
||||
)]
|
||||
async fn test_regular_user_cannot_set_default_css_sheet(pool: PgPool) {
|
||||
@@ -240,7 +261,7 @@ async fn test_regular_user_cannot_set_default_css_sheet(pool: PgPool) {
|
||||
}
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures("with_test_user2", "with_test_css_sheets"),
|
||||
fixtures("with_test_users", "with_test_css_sheets"),
|
||||
migrations = "../storage/migrations"
|
||||
)]
|
||||
async fn test_get_css_sheet_content_public(pool: PgPool) {
|
||||
@@ -268,7 +289,7 @@ async fn test_get_css_sheet_content_public(pool: PgPool) {
|
||||
assert_eq!(css_content, "body { color: red; }");
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("with_test_user2"), migrations = "../storage/migrations")]
|
||||
#[sqlx::test(fixtures("with_test_users"), migrations = "../storage/migrations")]
|
||||
async fn test_get_nonexistent_css_sheet_content(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let service = create_test_app(
|
||||
@@ -289,7 +310,7 @@ async fn test_get_nonexistent_css_sheet_content(pool: PgPool) {
|
||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("with_test_user2"), migrations = "../storage/migrations")]
|
||||
#[sqlx::test(fixtures("with_test_users"), migrations = "../storage/migrations")]
|
||||
async fn test_css_sheet_endpoints_require_auth(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let service = create_test_app(
|
||||
@@ -321,13 +342,19 @@ async fn test_css_sheet_endpoints_require_auth(pool: PgPool) {
|
||||
}
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures("with_test_user2", "with_test_css_sheets"),
|
||||
fixtures("with_test_users", "with_test_css_sheets"),
|
||||
migrations = "../storage/migrations"
|
||||
)]
|
||||
async fn test_edit_default_css_sheet_name_updates_default(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let (service, user) =
|
||||
create_test_app_and_login(pool, MockRedisPool::default(), 100, 100, TestUser::Staff).await;
|
||||
let (service, user) = create_test_app_and_login(
|
||||
pool,
|
||||
MockRedisPool::default(),
|
||||
100,
|
||||
100,
|
||||
TestUser::EditCssSheet,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Edit the default CSS sheet name (arcadia is the default from migration)
|
||||
let edited = EditedCssSheet {
|
||||
|
||||
@@ -16,11 +16,17 @@ use std::sync::Arc;
|
||||
// CREATE CATEGORY TESTS
|
||||
// ============================================================================
|
||||
|
||||
#[sqlx::test(fixtures("with_test_user2"), migrations = "../storage/migrations")]
|
||||
#[sqlx::test(fixtures("with_test_users"), migrations = "../storage/migrations")]
|
||||
async fn test_staff_can_create_category(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let (service, staff) =
|
||||
create_test_app_and_login(pool, MockRedisPool::default(), 101, 101, TestUser::Staff).await;
|
||||
let (service, staff) = create_test_app_and_login(
|
||||
pool,
|
||||
MockRedisPool::default(),
|
||||
101,
|
||||
101,
|
||||
TestUser::CreateForumCategory,
|
||||
)
|
||||
.await;
|
||||
|
||||
let create_body = UserCreatedForumCategory {
|
||||
name: "New Category".into(),
|
||||
@@ -37,10 +43,10 @@ async fn test_staff_can_create_category(pool: PgPool) {
|
||||
common::call_and_read_body_json_with_status(&service, req, StatusCode::CREATED).await;
|
||||
|
||||
assert_eq!(category.name, "New Category");
|
||||
assert_eq!(category.created_by_id, 101);
|
||||
assert_eq!(category.created_by_id, 107);
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("with_test_user"), migrations = "../storage/migrations")]
|
||||
#[sqlx::test(fixtures("with_test_users"), migrations = "../storage/migrations")]
|
||||
async fn test_non_staff_cannot_create_category(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let (service, user) =
|
||||
@@ -62,7 +68,7 @@ async fn test_non_staff_cannot_create_category(pool: PgPool) {
|
||||
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("with_test_user"), migrations = "../storage/migrations")]
|
||||
#[sqlx::test(fixtures("with_test_users"), migrations = "../storage/migrations")]
|
||||
async fn test_create_category_without_auth(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let service = common::create_test_app(
|
||||
@@ -88,11 +94,17 @@ async fn test_create_category_without_auth(pool: PgPool) {
|
||||
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("with_test_user2"), migrations = "../storage/migrations")]
|
||||
#[sqlx::test(fixtures("with_test_users"), migrations = "../storage/migrations")]
|
||||
async fn test_create_category_with_empty_name(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let (service, staff) =
|
||||
create_test_app_and_login(pool, MockRedisPool::default(), 101, 101, TestUser::Staff).await;
|
||||
let (service, staff) = create_test_app_and_login(
|
||||
pool,
|
||||
MockRedisPool::default(),
|
||||
101,
|
||||
101,
|
||||
TestUser::CreateForumCategory,
|
||||
)
|
||||
.await;
|
||||
|
||||
let create_body = UserCreatedForumCategory { name: "".into() };
|
||||
|
||||
@@ -107,11 +119,17 @@ async fn test_create_category_with_empty_name(pool: PgPool) {
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("with_test_user2"), migrations = "../storage/migrations")]
|
||||
#[sqlx::test(fixtures("with_test_users"), migrations = "../storage/migrations")]
|
||||
async fn test_create_category_with_whitespace_only_name(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let (service, staff) =
|
||||
create_test_app_and_login(pool, MockRedisPool::default(), 101, 101, TestUser::Staff).await;
|
||||
let (service, staff) = create_test_app_and_login(
|
||||
pool,
|
||||
MockRedisPool::default(),
|
||||
101,
|
||||
101,
|
||||
TestUser::CreateForumCategory,
|
||||
)
|
||||
.await;
|
||||
|
||||
let create_body = UserCreatedForumCategory { name: " ".into() };
|
||||
|
||||
@@ -131,13 +149,19 @@ async fn test_create_category_with_whitespace_only_name(pool: PgPool) {
|
||||
// ============================================================================
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures("with_test_user", "with_test_user2", "with_test_forum_category"),
|
||||
fixtures("with_test_users", "with_test_forum_category"),
|
||||
migrations = "../storage/migrations"
|
||||
)]
|
||||
async fn test_staff_can_edit_category(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let (service, staff) =
|
||||
create_test_app_and_login(pool, MockRedisPool::default(), 101, 101, TestUser::Staff).await;
|
||||
let (service, staff) = create_test_app_and_login(
|
||||
pool,
|
||||
MockRedisPool::default(),
|
||||
101,
|
||||
101,
|
||||
TestUser::EditForumCategory,
|
||||
)
|
||||
.await;
|
||||
|
||||
let edit_body = EditedForumCategory {
|
||||
id: 100,
|
||||
@@ -159,7 +183,7 @@ async fn test_staff_can_edit_category(pool: PgPool) {
|
||||
}
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures("with_test_user", "with_test_forum_category"),
|
||||
fixtures("with_test_users", "with_test_forum_category"),
|
||||
migrations = "../storage/migrations"
|
||||
)]
|
||||
async fn test_non_staff_cannot_edit_category(pool: PgPool) {
|
||||
@@ -185,7 +209,7 @@ async fn test_non_staff_cannot_edit_category(pool: PgPool) {
|
||||
}
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures("with_test_user", "with_test_forum_category"),
|
||||
fixtures("with_test_users", "with_test_forum_category"),
|
||||
migrations = "../storage/migrations"
|
||||
)]
|
||||
async fn test_edit_category_without_auth(pool: PgPool) {
|
||||
@@ -214,11 +238,17 @@ async fn test_edit_category_without_auth(pool: PgPool) {
|
||||
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("with_test_user2"), migrations = "../storage/migrations")]
|
||||
#[sqlx::test(fixtures("with_test_users"), migrations = "../storage/migrations")]
|
||||
async fn test_edit_nonexistent_category(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let (service, staff) =
|
||||
create_test_app_and_login(pool, MockRedisPool::default(), 101, 101, TestUser::Staff).await;
|
||||
let (service, staff) = create_test_app_and_login(
|
||||
pool,
|
||||
MockRedisPool::default(),
|
||||
101,
|
||||
101,
|
||||
TestUser::EditForumCategory,
|
||||
)
|
||||
.await;
|
||||
|
||||
let edit_body = EditedForumCategory {
|
||||
id: 999,
|
||||
@@ -237,13 +267,19 @@ async fn test_edit_nonexistent_category(pool: PgPool) {
|
||||
}
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures("with_test_user", "with_test_user2", "with_test_forum_category"),
|
||||
fixtures("with_test_users", "with_test_forum_category"),
|
||||
migrations = "../storage/migrations"
|
||||
)]
|
||||
async fn test_edit_category_with_empty_name(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let (service, staff) =
|
||||
create_test_app_and_login(pool, MockRedisPool::default(), 101, 101, TestUser::Staff).await;
|
||||
let (service, staff) = create_test_app_and_login(
|
||||
pool,
|
||||
MockRedisPool::default(),
|
||||
101,
|
||||
101,
|
||||
TestUser::EditForumCategory,
|
||||
)
|
||||
.await;
|
||||
|
||||
let edit_body = EditedForumCategory {
|
||||
id: 100,
|
||||
@@ -262,13 +298,19 @@ async fn test_edit_category_with_empty_name(pool: PgPool) {
|
||||
}
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures("with_test_user", "with_test_user2", "with_test_forum_category"),
|
||||
fixtures("with_test_users", "with_test_forum_category"),
|
||||
migrations = "../storage/migrations"
|
||||
)]
|
||||
async fn test_edit_category_with_whitespace_only_name(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let (service, staff) =
|
||||
create_test_app_and_login(pool, MockRedisPool::default(), 101, 101, TestUser::Staff).await;
|
||||
let (service, staff) = create_test_app_and_login(
|
||||
pool,
|
||||
MockRedisPool::default(),
|
||||
101,
|
||||
101,
|
||||
TestUser::EditForumCategory,
|
||||
)
|
||||
.await;
|
||||
|
||||
let edit_body = EditedForumCategory {
|
||||
id: 100,
|
||||
@@ -290,11 +332,17 @@ async fn test_edit_category_with_whitespace_only_name(pool: PgPool) {
|
||||
// INTEGRATION TESTS
|
||||
// ============================================================================
|
||||
|
||||
#[sqlx::test(fixtures("with_test_user2"), migrations = "../storage/migrations")]
|
||||
#[sqlx::test(fixtures("with_test_users"), migrations = "../storage/migrations")]
|
||||
async fn test_create_and_edit_category_flow(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let (service, staff) =
|
||||
create_test_app_and_login(pool, MockRedisPool::default(), 101, 101, TestUser::Staff).await;
|
||||
let (service, staff) = create_test_app_and_login(
|
||||
pool,
|
||||
MockRedisPool::default(),
|
||||
101,
|
||||
101,
|
||||
TestUser::ForumCategoryFlow,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Create a category
|
||||
let create_body = UserCreatedForumCategory {
|
||||
|
||||
@@ -17,13 +17,19 @@ use std::sync::Arc;
|
||||
// ============================================================================
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures("with_test_user", "with_test_user2", "with_test_forum_category"),
|
||||
fixtures("with_test_users", "with_test_forum_category"),
|
||||
migrations = "../storage/migrations"
|
||||
)]
|
||||
async fn test_staff_can_create_sub_category(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let (service, staff) =
|
||||
create_test_app_and_login(pool, MockRedisPool::default(), 101, 101, TestUser::Staff).await;
|
||||
let (service, staff) = create_test_app_and_login(
|
||||
pool,
|
||||
MockRedisPool::default(),
|
||||
101,
|
||||
101,
|
||||
TestUser::CreateForumSubCategory,
|
||||
)
|
||||
.await;
|
||||
|
||||
let create_body = UserCreatedForumSubCategory {
|
||||
forum_category_id: 100,
|
||||
@@ -42,11 +48,11 @@ async fn test_staff_can_create_sub_category(pool: PgPool) {
|
||||
|
||||
assert_eq!(sub_category.name, "New Sub Category");
|
||||
assert_eq!(sub_category.forum_category_id, 100);
|
||||
assert_eq!(sub_category.created_by_id, 101);
|
||||
assert_eq!(sub_category.created_by_id, 109);
|
||||
}
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures("with_test_user", "with_test_forum_category"),
|
||||
fixtures("with_test_users", "with_test_forum_category"),
|
||||
migrations = "../storage/migrations"
|
||||
)]
|
||||
async fn test_non_staff_cannot_create_sub_category(pool: PgPool) {
|
||||
@@ -72,7 +78,7 @@ async fn test_non_staff_cannot_create_sub_category(pool: PgPool) {
|
||||
}
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures("with_test_user", "with_test_forum_category"),
|
||||
fixtures("with_test_users", "with_test_forum_category"),
|
||||
migrations = "../storage/migrations"
|
||||
)]
|
||||
async fn test_create_sub_category_without_auth(pool: PgPool) {
|
||||
@@ -102,13 +108,19 @@ async fn test_create_sub_category_without_auth(pool: PgPool) {
|
||||
}
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures("with_test_user", "with_test_user2", "with_test_forum_category"),
|
||||
fixtures("with_test_users", "with_test_forum_category"),
|
||||
migrations = "../storage/migrations"
|
||||
)]
|
||||
async fn test_create_sub_category_with_empty_name(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let (service, staff) =
|
||||
create_test_app_and_login(pool, MockRedisPool::default(), 101, 101, TestUser::Staff).await;
|
||||
let (service, staff) = create_test_app_and_login(
|
||||
pool,
|
||||
MockRedisPool::default(),
|
||||
101,
|
||||
101,
|
||||
TestUser::CreateForumSubCategory,
|
||||
)
|
||||
.await;
|
||||
|
||||
let create_body = UserCreatedForumSubCategory {
|
||||
forum_category_id: 100,
|
||||
@@ -127,13 +139,19 @@ async fn test_create_sub_category_with_empty_name(pool: PgPool) {
|
||||
}
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures("with_test_user", "with_test_user2", "with_test_forum_category"),
|
||||
fixtures("with_test_users", "with_test_forum_category"),
|
||||
migrations = "../storage/migrations"
|
||||
)]
|
||||
async fn test_create_sub_category_with_whitespace_only_name(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let (service, staff) =
|
||||
create_test_app_and_login(pool, MockRedisPool::default(), 101, 101, TestUser::Staff).await;
|
||||
let (service, staff) = create_test_app_and_login(
|
||||
pool,
|
||||
MockRedisPool::default(),
|
||||
101,
|
||||
101,
|
||||
TestUser::CreateForumSubCategory,
|
||||
)
|
||||
.await;
|
||||
|
||||
let create_body = UserCreatedForumSubCategory {
|
||||
forum_category_id: 100,
|
||||
@@ -157,8 +175,7 @@ async fn test_create_sub_category_with_whitespace_only_name(pool: PgPool) {
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures(
|
||||
"with_test_user",
|
||||
"with_test_user2",
|
||||
"with_test_users",
|
||||
"with_test_forum_category",
|
||||
"with_test_forum_sub_category"
|
||||
),
|
||||
@@ -166,8 +183,14 @@ async fn test_create_sub_category_with_whitespace_only_name(pool: PgPool) {
|
||||
)]
|
||||
async fn test_staff_can_edit_sub_category(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let (service, staff) =
|
||||
create_test_app_and_login(pool, MockRedisPool::default(), 101, 101, TestUser::Staff).await;
|
||||
let (service, staff) = create_test_app_and_login(
|
||||
pool,
|
||||
MockRedisPool::default(),
|
||||
101,
|
||||
101,
|
||||
TestUser::EditForumSubCategory,
|
||||
)
|
||||
.await;
|
||||
|
||||
let edit_body = EditedForumSubCategory {
|
||||
id: 100,
|
||||
@@ -190,7 +213,7 @@ async fn test_staff_can_edit_sub_category(pool: PgPool) {
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures(
|
||||
"with_test_user",
|
||||
"with_test_users",
|
||||
"with_test_forum_category",
|
||||
"with_test_forum_sub_category"
|
||||
),
|
||||
@@ -220,7 +243,7 @@ async fn test_non_staff_cannot_edit_sub_category(pool: PgPool) {
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures(
|
||||
"with_test_user",
|
||||
"with_test_users",
|
||||
"with_test_forum_category",
|
||||
"with_test_forum_sub_category"
|
||||
),
|
||||
@@ -253,13 +276,19 @@ async fn test_edit_sub_category_without_auth(pool: PgPool) {
|
||||
}
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures("with_test_user", "with_test_user2", "with_test_forum_category"),
|
||||
fixtures("with_test_users", "with_test_forum_category"),
|
||||
migrations = "../storage/migrations"
|
||||
)]
|
||||
async fn test_edit_nonexistent_sub_category(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let (service, staff) =
|
||||
create_test_app_and_login(pool, MockRedisPool::default(), 101, 101, TestUser::Staff).await;
|
||||
let (service, staff) = create_test_app_and_login(
|
||||
pool,
|
||||
MockRedisPool::default(),
|
||||
101,
|
||||
101,
|
||||
TestUser::EditForumSubCategory,
|
||||
)
|
||||
.await;
|
||||
|
||||
let edit_body = EditedForumSubCategory {
|
||||
id: 999,
|
||||
@@ -279,8 +308,7 @@ async fn test_edit_nonexistent_sub_category(pool: PgPool) {
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures(
|
||||
"with_test_user",
|
||||
"with_test_user2",
|
||||
"with_test_users",
|
||||
"with_test_forum_category",
|
||||
"with_test_forum_sub_category"
|
||||
),
|
||||
@@ -288,8 +316,14 @@ async fn test_edit_nonexistent_sub_category(pool: PgPool) {
|
||||
)]
|
||||
async fn test_edit_sub_category_with_empty_name(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let (service, staff) =
|
||||
create_test_app_and_login(pool, MockRedisPool::default(), 101, 101, TestUser::Staff).await;
|
||||
let (service, staff) = create_test_app_and_login(
|
||||
pool,
|
||||
MockRedisPool::default(),
|
||||
101,
|
||||
101,
|
||||
TestUser::EditForumSubCategory,
|
||||
)
|
||||
.await;
|
||||
|
||||
let edit_body = EditedForumSubCategory {
|
||||
id: 100,
|
||||
@@ -309,8 +343,7 @@ async fn test_edit_sub_category_with_empty_name(pool: PgPool) {
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures(
|
||||
"with_test_user",
|
||||
"with_test_user2",
|
||||
"with_test_users",
|
||||
"with_test_forum_category",
|
||||
"with_test_forum_sub_category"
|
||||
),
|
||||
@@ -318,8 +351,14 @@ async fn test_edit_sub_category_with_empty_name(pool: PgPool) {
|
||||
)]
|
||||
async fn test_edit_sub_category_with_whitespace_only_name(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let (service, staff) =
|
||||
create_test_app_and_login(pool, MockRedisPool::default(), 101, 101, TestUser::Staff).await;
|
||||
let (service, staff) = create_test_app_and_login(
|
||||
pool,
|
||||
MockRedisPool::default(),
|
||||
101,
|
||||
101,
|
||||
TestUser::EditForumSubCategory,
|
||||
)
|
||||
.await;
|
||||
|
||||
let edit_body = EditedForumSubCategory {
|
||||
id: 100,
|
||||
@@ -342,13 +381,19 @@ async fn test_edit_sub_category_with_whitespace_only_name(pool: PgPool) {
|
||||
// ============================================================================
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures("with_test_user", "with_test_user2", "with_test_forum_category"),
|
||||
fixtures("with_test_users", "with_test_forum_category"),
|
||||
migrations = "../storage/migrations"
|
||||
)]
|
||||
async fn test_create_and_edit_sub_category_flow(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let (service, staff) =
|
||||
create_test_app_and_login(pool, MockRedisPool::default(), 101, 101, TestUser::Staff).await;
|
||||
let (service, staff) = create_test_app_and_login(
|
||||
pool,
|
||||
MockRedisPool::default(),
|
||||
101,
|
||||
101,
|
||||
TestUser::ForumSubCategoryFlow,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Create a sub category
|
||||
let create_body = UserCreatedForumSubCategory {
|
||||
|
||||
@@ -20,7 +20,7 @@ use std::sync::Arc;
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures(
|
||||
"with_test_user",
|
||||
"with_test_users",
|
||||
"with_test_forum_category",
|
||||
"with_test_forum_sub_category"
|
||||
),
|
||||
@@ -61,7 +61,7 @@ async fn test_create_thread_success(pool: PgPool) {
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures(
|
||||
"with_test_user",
|
||||
"with_test_users",
|
||||
"with_test_forum_category",
|
||||
"with_test_forum_sub_category"
|
||||
),
|
||||
@@ -99,7 +99,7 @@ async fn test_create_thread_without_auth(pool: PgPool) {
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures(
|
||||
"with_test_user",
|
||||
"with_test_users",
|
||||
"with_test_forum_category",
|
||||
"with_test_forum_sub_category"
|
||||
),
|
||||
@@ -133,7 +133,7 @@ async fn test_create_thread_with_empty_name(pool: PgPool) {
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures(
|
||||
"with_test_user",
|
||||
"with_test_users",
|
||||
"with_test_forum_category",
|
||||
"with_test_forum_sub_category"
|
||||
),
|
||||
@@ -165,7 +165,7 @@ async fn test_create_thread_with_empty_post(pool: PgPool) {
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("with_test_user"), migrations = "../storage/migrations")]
|
||||
#[sqlx::test(fixtures("with_test_users"), migrations = "../storage/migrations")]
|
||||
async fn test_create_thread_with_invalid_sub_category(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let (service, user) =
|
||||
@@ -202,7 +202,7 @@ async fn test_create_thread_with_invalid_sub_category(pool: PgPool) {
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures(
|
||||
"with_test_user",
|
||||
"with_test_users",
|
||||
"with_test_forum_category",
|
||||
"with_test_forum_sub_category",
|
||||
"with_test_forum_thread",
|
||||
@@ -237,7 +237,7 @@ async fn test_get_thread_success(pool: PgPool) {
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures(
|
||||
"with_test_user",
|
||||
"with_test_users",
|
||||
"with_test_forum_category",
|
||||
"with_test_forum_sub_category",
|
||||
"with_test_forum_thread",
|
||||
@@ -265,7 +265,7 @@ async fn test_get_thread_without_auth(pool: PgPool) {
|
||||
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("with_test_user"), migrations = "../storage/migrations")]
|
||||
#[sqlx::test(fixtures("with_test_users"), migrations = "../storage/migrations")]
|
||||
async fn test_get_nonexistent_thread(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let (service, user) =
|
||||
@@ -288,7 +288,7 @@ async fn test_get_nonexistent_thread(pool: PgPool) {
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures(
|
||||
"with_test_user",
|
||||
"with_test_users",
|
||||
"with_test_forum_category",
|
||||
"with_test_forum_sub_category",
|
||||
"with_test_forum_thread",
|
||||
@@ -326,7 +326,7 @@ async fn test_owner_can_edit_thread_name(pool: PgPool) {
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures(
|
||||
"with_test_user",
|
||||
"with_test_users",
|
||||
"with_test_forum_category",
|
||||
"with_test_forum_sub_category",
|
||||
"with_test_forum_thread",
|
||||
@@ -363,7 +363,7 @@ async fn test_owner_can_toggle_sticky(pool: PgPool) {
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures(
|
||||
"with_test_user",
|
||||
"with_test_users",
|
||||
"with_test_forum_category",
|
||||
"with_test_forum_sub_category",
|
||||
"with_test_forum_thread",
|
||||
@@ -400,7 +400,7 @@ async fn test_owner_can_toggle_locked(pool: PgPool) {
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures(
|
||||
"with_test_user",
|
||||
"with_test_users",
|
||||
"with_test_forum_category",
|
||||
"with_test_forum_sub_category",
|
||||
"with_test_forum_thread",
|
||||
@@ -438,8 +438,7 @@ async fn test_owner_can_move_thread_to_different_sub_category(pool: PgPool) {
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures(
|
||||
"with_test_user",
|
||||
"with_test_user2",
|
||||
"with_test_users",
|
||||
"with_test_forum_category",
|
||||
"with_test_forum_sub_category",
|
||||
"with_test_forum_thread",
|
||||
@@ -456,7 +455,7 @@ async fn test_non_owner_cannot_edit_thread(pool: PgPool) {
|
||||
MockRedisPool::default(),
|
||||
100,
|
||||
100,
|
||||
TestUser::Staff,
|
||||
TestUser::EditArtist,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -506,8 +505,7 @@ async fn test_non_owner_cannot_edit_thread(pool: PgPool) {
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures(
|
||||
"with_test_user",
|
||||
"with_test_user2",
|
||||
"with_test_users",
|
||||
"with_test_forum_category",
|
||||
"with_test_forum_sub_category",
|
||||
"with_test_forum_thread",
|
||||
@@ -520,8 +518,14 @@ async fn test_staff_can_edit_any_thread(pool: PgPool) {
|
||||
|
||||
// Thread 100 is owned by user 100 (standard user)
|
||||
// Login as staff user
|
||||
let (service, staff) =
|
||||
create_test_app_and_login(pool, MockRedisPool::default(), 100, 100, TestUser::Staff).await;
|
||||
let (service, staff) = create_test_app_and_login(
|
||||
pool,
|
||||
MockRedisPool::default(),
|
||||
100,
|
||||
100,
|
||||
TestUser::EditForumThread,
|
||||
)
|
||||
.await;
|
||||
|
||||
let edit_body = EditedForumThread {
|
||||
id: 100, // Thread owned by user 100
|
||||
@@ -548,7 +552,7 @@ async fn test_staff_can_edit_any_thread(pool: PgPool) {
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures(
|
||||
"with_test_user",
|
||||
"with_test_users",
|
||||
"with_test_forum_category",
|
||||
"with_test_forum_sub_category",
|
||||
"with_test_forum_thread",
|
||||
@@ -585,7 +589,7 @@ async fn test_edit_thread_without_auth(pool: PgPool) {
|
||||
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("with_test_user"), migrations = "../storage/migrations")]
|
||||
#[sqlx::test(fixtures("with_test_users"), migrations = "../storage/migrations")]
|
||||
async fn test_edit_nonexistent_thread(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let (service, user) =
|
||||
@@ -613,7 +617,7 @@ async fn test_edit_nonexistent_thread(pool: PgPool) {
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures(
|
||||
"with_test_user",
|
||||
"with_test_users",
|
||||
"with_test_forum_category",
|
||||
"with_test_forum_sub_category",
|
||||
"with_test_forum_thread",
|
||||
@@ -652,7 +656,7 @@ async fn test_edit_thread_with_empty_name(pool: PgPool) {
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures(
|
||||
"with_test_user",
|
||||
"with_test_users",
|
||||
"with_test_forum_category",
|
||||
"with_test_forum_sub_category",
|
||||
"with_test_forum_thread",
|
||||
@@ -685,7 +689,7 @@ async fn test_cannot_post_in_locked_thread(pool: PgPool) {
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures(
|
||||
"with_test_user",
|
||||
"with_test_users",
|
||||
"with_test_forum_category",
|
||||
"with_test_forum_sub_category",
|
||||
"with_test_forum_thread",
|
||||
@@ -743,7 +747,7 @@ async fn test_can_unlock_and_post(pool: PgPool) {
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures(
|
||||
"with_test_user",
|
||||
"with_test_users",
|
||||
"with_test_forum_category",
|
||||
"with_test_forum_sub_category"
|
||||
),
|
||||
@@ -832,7 +836,7 @@ async fn test_create_thread_add_posts_edit_thread_flow(pool: PgPool) {
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures(
|
||||
"with_test_user",
|
||||
"with_test_users",
|
||||
"with_test_forum_category",
|
||||
"with_test_forum_sub_category"
|
||||
),
|
||||
@@ -911,7 +915,7 @@ async fn test_move_thread_with_posts_between_sub_categories(pool: PgPool) {
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures(
|
||||
"with_test_user",
|
||||
"with_test_users",
|
||||
"with_test_forum_category",
|
||||
"with_test_forum_sub_category",
|
||||
"with_test_forum_thread",
|
||||
@@ -940,7 +944,7 @@ async fn test_get_thread_posts_success(pool: PgPool) {
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures(
|
||||
"with_test_user",
|
||||
"with_test_users",
|
||||
"with_test_forum_category",
|
||||
"with_test_forum_sub_category"
|
||||
),
|
||||
@@ -1029,7 +1033,7 @@ async fn test_get_thread_posts_with_multiple_posts_pagination(pool: PgPool) {
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures(
|
||||
"with_test_user",
|
||||
"with_test_users",
|
||||
"with_test_forum_category",
|
||||
"with_test_forum_sub_category",
|
||||
"with_test_forum_thread",
|
||||
@@ -1057,7 +1061,7 @@ async fn test_get_thread_posts_without_auth(pool: PgPool) {
|
||||
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("with_test_user"), migrations = "../storage/migrations")]
|
||||
#[sqlx::test(fixtures("with_test_users"), migrations = "../storage/migrations")]
|
||||
async fn test_get_posts_for_nonexistent_thread(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let (service, user) =
|
||||
@@ -1081,7 +1085,7 @@ async fn test_get_posts_for_nonexistent_thread(pool: PgPool) {
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures(
|
||||
"with_test_user",
|
||||
"with_test_users",
|
||||
"with_test_forum_category",
|
||||
"with_test_forum_sub_category",
|
||||
"with_test_forum_thread",
|
||||
@@ -1115,7 +1119,7 @@ async fn test_get_sub_category_threads_success(pool: PgPool) {
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures(
|
||||
"with_test_user",
|
||||
"with_test_users",
|
||||
"with_test_forum_category",
|
||||
"with_test_forum_sub_category",
|
||||
"with_test_forum_thread",
|
||||
@@ -1148,7 +1152,7 @@ async fn test_sub_category_threads_show_sticky_threads(pool: PgPool) {
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures(
|
||||
"with_test_user",
|
||||
"with_test_users",
|
||||
"with_test_forum_category",
|
||||
"with_test_forum_sub_category",
|
||||
"with_test_forum_thread",
|
||||
@@ -1181,7 +1185,7 @@ async fn test_sub_category_threads_show_locked_threads(pool: PgPool) {
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures(
|
||||
"with_test_user",
|
||||
"with_test_users",
|
||||
"with_test_forum_category",
|
||||
"with_test_forum_sub_category",
|
||||
"with_test_forum_thread",
|
||||
@@ -1209,7 +1213,7 @@ async fn test_get_sub_category_threads_without_auth(pool: PgPool) {
|
||||
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("with_test_user"), migrations = "../storage/migrations")]
|
||||
#[sqlx::test(fixtures("with_test_users"), migrations = "../storage/migrations")]
|
||||
async fn test_get_nonexistent_sub_category_threads(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let (service, user) =
|
||||
|
||||
@@ -22,13 +22,19 @@ use crate::common::{
|
||||
};
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures("with_test_user2", "with_test_series"),
|
||||
fixtures("with_test_users", "with_test_series"),
|
||||
migrations = "../storage/migrations"
|
||||
)]
|
||||
async fn test_edit_series(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let (service, user) =
|
||||
create_test_app_and_login(pool, MockRedisPool::default(), 100, 100, TestUser::Staff).await;
|
||||
let (service, user) = create_test_app_and_login(
|
||||
pool,
|
||||
MockRedisPool::default(),
|
||||
100,
|
||||
100,
|
||||
TestUser::EditSeries,
|
||||
)
|
||||
.await;
|
||||
|
||||
let payload = EditedSeries {
|
||||
id: 1,
|
||||
@@ -55,7 +61,7 @@ async fn test_edit_series(pool: PgPool) {
|
||||
}
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures("with_test_user", "with_test_series", "with_test_title_group"),
|
||||
fixtures("with_test_users", "with_test_series", "with_test_title_group"),
|
||||
migrations = "../storage/migrations"
|
||||
)]
|
||||
async fn test_add_title_group_to_series(pool: PgPool) {
|
||||
|
||||
@@ -13,7 +13,7 @@ use sqlx::PgPool;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures("with_test_user", "with_test_title_group"),
|
||||
fixtures("with_test_users", "with_test_title_group"),
|
||||
migrations = "../storage/migrations"
|
||||
)]
|
||||
async fn test_owner_can_edit_their_comment(pool: PgPool) {
|
||||
@@ -60,7 +60,7 @@ async fn test_owner_can_edit_their_comment(pool: PgPool) {
|
||||
}
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures("with_test_user", "with_test_user2", "with_test_title_group"),
|
||||
fixtures("with_test_users", "with_test_title_group"),
|
||||
migrations = "../storage/migrations"
|
||||
)]
|
||||
async fn test_staff_can_edit_any_comment(pool: PgPool) {
|
||||
@@ -94,8 +94,14 @@ async fn test_staff_can_edit_any_comment(pool: PgPool) {
|
||||
common::call_and_read_body_json_with_status(&service, req, StatusCode::CREATED).await;
|
||||
|
||||
// Staff edits it
|
||||
let (service, staff) =
|
||||
create_test_app_and_login(pool, MockRedisPool::default(), 100, 100, TestUser::Staff).await;
|
||||
let (service, staff) = create_test_app_and_login(
|
||||
pool,
|
||||
MockRedisPool::default(),
|
||||
100,
|
||||
100,
|
||||
TestUser::EditTitleGroupComment,
|
||||
)
|
||||
.await;
|
||||
|
||||
let edit_body = EditedTitleGroupComment {
|
||||
content: "Staff edited".into(),
|
||||
|
||||
@@ -18,7 +18,7 @@ use crate::{
|
||||
mocks::mock_redis::{MockRedis, MockRedisPool},
|
||||
};
|
||||
|
||||
#[sqlx::test(fixtures("with_test_user"), migrations = "../storage/migrations")]
|
||||
#[sqlx::test(fixtures("with_test_users"), migrations = "../storage/migrations")]
|
||||
async fn test_reject_invalidated_tokens(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let redis_pool = MockRedisPool::default();
|
||||
|
||||
@@ -20,7 +20,7 @@ use crate::common::{auth_header, TestUser};
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures(
|
||||
"with_test_user",
|
||||
"with_test_users",
|
||||
"with_test_title_group",
|
||||
"with_test_edition_group",
|
||||
"with_test_torrent"
|
||||
@@ -77,7 +77,7 @@ async fn test_valid_torrent(pool: PgPool) {
|
||||
}
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures("with_test_user", "with_test_title_group", "with_test_edition_group"),
|
||||
fixtures("with_test_users", "with_test_title_group", "with_test_edition_group"),
|
||||
migrations = "../storage/migrations"
|
||||
)]
|
||||
async fn test_upload_torrent(pool: PgPool) {
|
||||
@@ -157,7 +157,7 @@ async fn test_upload_torrent(pool: PgPool) {
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures(
|
||||
"with_test_user",
|
||||
"with_test_users",
|
||||
"with_test_title_group",
|
||||
"with_test_edition_group",
|
||||
"with_test_torrent"
|
||||
@@ -214,7 +214,7 @@ async fn test_find_torrents_by_external_link(pool: PgPool) {
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures(
|
||||
"with_test_user",
|
||||
"with_test_users",
|
||||
"with_test_title_group",
|
||||
"with_test_edition_group",
|
||||
"with_test_torrent"
|
||||
@@ -271,7 +271,7 @@ async fn test_find_torrents_by_name(pool: PgPool) {
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures(
|
||||
"with_test_user",
|
||||
"with_test_users",
|
||||
"with_test_title_group",
|
||||
"with_test_edition_group",
|
||||
"with_test_torrent"
|
||||
|
||||
@@ -14,7 +14,7 @@ use mocks::mock_redis::MockRedisPool;
|
||||
use sqlx::PgPool;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[sqlx::test(fixtures("with_test_user"), migrations = "../storage/migrations")]
|
||||
#[sqlx::test(fixtures("with_test_users"), migrations = "../storage/migrations")]
|
||||
async fn test_get_user_settings(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let (service, user) =
|
||||
@@ -30,10 +30,7 @@ async fn test_get_user_settings(pool: PgPool) {
|
||||
let _ = call_and_read_body_json::<UserSettings, _>(&service, req).await;
|
||||
}
|
||||
|
||||
#[sqlx::test(
|
||||
fixtures("with_test_user", "with_test_user2"),
|
||||
migrations = "../storage/migrations"
|
||||
)]
|
||||
#[sqlx::test(fixtures("with_test_users"), migrations = "../storage/migrations")]
|
||||
async fn test_update_user_settings(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
|
||||
@@ -43,7 +40,7 @@ async fn test_update_user_settings(pool: PgPool) {
|
||||
MockRedisPool::default(),
|
||||
100,
|
||||
100,
|
||||
TestUser::Staff,
|
||||
TestUser::CreateCssSheet,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -92,7 +89,7 @@ async fn test_update_user_settings(pool: PgPool) {
|
||||
assert_eq!(updated_settings.css_sheet_name, "custom_sheet");
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("with_test_user"), migrations = "../storage/migrations")]
|
||||
#[sqlx::test(fixtures("with_test_users"), migrations = "../storage/migrations")]
|
||||
async fn test_get_user_settings_requires_auth(pool: PgPool) {
|
||||
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
|
||||
let service = create_test_app(
|
||||
|
||||
@@ -80,127 +80,178 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"name": "class",
|
||||
"name": "class_name",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"name": "class_locked",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"name": "permissions",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
"name": "user_class_enum",
|
||||
"name": "user_permissions_enum[]",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"newbie",
|
||||
"staff",
|
||||
"tracker"
|
||||
]
|
||||
"Array": {
|
||||
"Custom": {
|
||||
"name": "user_permissions_enum",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"upload_torrent",
|
||||
"download_torrent",
|
||||
"create_torrent_request",
|
||||
"immune_activity_pruning",
|
||||
"edit_title_group",
|
||||
"edit_title_group_comment",
|
||||
"edit_edition_group",
|
||||
"edit_torrent",
|
||||
"edit_artist",
|
||||
"edit_collage",
|
||||
"edit_series",
|
||||
"edit_torrent_request",
|
||||
"edit_forum_post",
|
||||
"edit_forum_thread",
|
||||
"edit_forum_sub_category",
|
||||
"edit_forum_category",
|
||||
"create_forum_category",
|
||||
"create_forum_sub_category",
|
||||
"create_forum_thread",
|
||||
"create_forum_post",
|
||||
"send_pm",
|
||||
"create_css_sheet",
|
||||
"edit_css_sheet",
|
||||
"set_default_css_sheet",
|
||||
"read_staff_pm",
|
||||
"reply_staff_pm",
|
||||
"resolve_staff_pm",
|
||||
"unresolve_staff_pm",
|
||||
"delete_title_group_tag",
|
||||
"edit_title_group_tag",
|
||||
"delete_torrent",
|
||||
"get_user_application",
|
||||
"update_user_application",
|
||||
"warn_user",
|
||||
"edit_user",
|
||||
"create_wiki_article",
|
||||
"edit_wiki_article"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"ordinal": 18,
|
||||
"name": "forum_posts",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"ordinal": 19,
|
||||
"name": "forum_threads",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"ordinal": 20,
|
||||
"name": "torrent_comments",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"ordinal": 21,
|
||||
"name": "request_comments",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"ordinal": 22,
|
||||
"name": "artist_comments",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 21,
|
||||
"ordinal": 23,
|
||||
"name": "seeding",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 22,
|
||||
"ordinal": 24,
|
||||
"name": "leeching",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 23,
|
||||
"ordinal": 25,
|
||||
"name": "snatched",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 24,
|
||||
"ordinal": 26,
|
||||
"name": "seeding_size",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 25,
|
||||
"ordinal": 27,
|
||||
"name": "requests_filled",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 26,
|
||||
"ordinal": 28,
|
||||
"name": "collages_started",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 27,
|
||||
"ordinal": 29,
|
||||
"name": "requests_voted",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 28,
|
||||
"ordinal": 30,
|
||||
"name": "average_seeding_time",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 29,
|
||||
"ordinal": 31,
|
||||
"name": "invited",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 30,
|
||||
"ordinal": 32,
|
||||
"name": "invitations",
|
||||
"type_info": "Int2"
|
||||
},
|
||||
{
|
||||
"ordinal": 31,
|
||||
"ordinal": 33,
|
||||
"name": "bonus_points",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 32,
|
||||
"ordinal": 34,
|
||||
"name": "freeleech_tokens",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 33,
|
||||
"ordinal": 35,
|
||||
"name": "passkey",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 34,
|
||||
"ordinal": 36,
|
||||
"name": "warned",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 35,
|
||||
"ordinal": 37,
|
||||
"name": "banned",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 36,
|
||||
"ordinal": 38,
|
||||
"name": "staff_note",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 37,
|
||||
"ordinal": 39,
|
||||
"name": "css_sheet_name",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
@@ -248,6 +299,8 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO users (username, email, password_hash, registered_from_ip, passkey)\n VALUES ($1, $2, $3, $4, $5)\n RETURNING *\n ",
|
||||
"query": "\n INSERT INTO users (username, email, password_hash, registered_from_ip, passkey, class_name)\n VALUES ($1, $2, $3, $4, $5, $6)\n RETURNING *\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -80,127 +80,178 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"name": "class",
|
||||
"name": "class_name",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"name": "class_locked",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"name": "permissions",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
"name": "user_class_enum",
|
||||
"name": "user_permissions_enum[]",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"newbie",
|
||||
"staff",
|
||||
"tracker"
|
||||
]
|
||||
"Array": {
|
||||
"Custom": {
|
||||
"name": "user_permissions_enum",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"upload_torrent",
|
||||
"download_torrent",
|
||||
"create_torrent_request",
|
||||
"immune_activity_pruning",
|
||||
"edit_title_group",
|
||||
"edit_title_group_comment",
|
||||
"edit_edition_group",
|
||||
"edit_torrent",
|
||||
"edit_artist",
|
||||
"edit_collage",
|
||||
"edit_series",
|
||||
"edit_torrent_request",
|
||||
"edit_forum_post",
|
||||
"edit_forum_thread",
|
||||
"edit_forum_sub_category",
|
||||
"edit_forum_category",
|
||||
"create_forum_category",
|
||||
"create_forum_sub_category",
|
||||
"create_forum_thread",
|
||||
"create_forum_post",
|
||||
"send_pm",
|
||||
"create_css_sheet",
|
||||
"edit_css_sheet",
|
||||
"set_default_css_sheet",
|
||||
"read_staff_pm",
|
||||
"reply_staff_pm",
|
||||
"resolve_staff_pm",
|
||||
"unresolve_staff_pm",
|
||||
"delete_title_group_tag",
|
||||
"edit_title_group_tag",
|
||||
"delete_torrent",
|
||||
"get_user_application",
|
||||
"update_user_application",
|
||||
"warn_user",
|
||||
"edit_user",
|
||||
"create_wiki_article",
|
||||
"edit_wiki_article"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"ordinal": 18,
|
||||
"name": "forum_posts",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"ordinal": 19,
|
||||
"name": "forum_threads",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"ordinal": 20,
|
||||
"name": "torrent_comments",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"ordinal": 21,
|
||||
"name": "request_comments",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"ordinal": 22,
|
||||
"name": "artist_comments",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 21,
|
||||
"ordinal": 23,
|
||||
"name": "seeding",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 22,
|
||||
"ordinal": 24,
|
||||
"name": "leeching",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 23,
|
||||
"ordinal": 25,
|
||||
"name": "snatched",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 24,
|
||||
"ordinal": 26,
|
||||
"name": "seeding_size",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 25,
|
||||
"ordinal": 27,
|
||||
"name": "requests_filled",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 26,
|
||||
"ordinal": 28,
|
||||
"name": "collages_started",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 27,
|
||||
"ordinal": 29,
|
||||
"name": "requests_voted",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 28,
|
||||
"ordinal": 30,
|
||||
"name": "average_seeding_time",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 29,
|
||||
"ordinal": 31,
|
||||
"name": "invited",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 30,
|
||||
"ordinal": 32,
|
||||
"name": "invitations",
|
||||
"type_info": "Int2"
|
||||
},
|
||||
{
|
||||
"ordinal": 31,
|
||||
"ordinal": 33,
|
||||
"name": "bonus_points",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 32,
|
||||
"ordinal": 34,
|
||||
"name": "freeleech_tokens",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 33,
|
||||
"ordinal": 35,
|
||||
"name": "passkey",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 34,
|
||||
"ordinal": 36,
|
||||
"name": "warned",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 35,
|
||||
"ordinal": 37,
|
||||
"name": "banned",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 36,
|
||||
"ordinal": 38,
|
||||
"name": "staff_note",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 37,
|
||||
"ordinal": 39,
|
||||
"name": "css_sheet_name",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
@@ -211,6 +262,7 @@
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Inet",
|
||||
"Varchar",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
@@ -252,8 +304,10 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "aaf68bfe32bb1b08287cfbba92bd39f56e810e4a82cc21a8f66fa4867bac75c0"
|
||||
"hash": "79ed9d5eee7f6d19a6ad85df3666c83828e0e162b8496533552b92efd807cd29"
|
||||
}
|
||||
68
backend/storage/.sqlx/query-a3a1ac6a31364650446c2dd008a3df96617ad9aaa623d3ca128e43159eaf8779.json
generated
Normal file
68
backend/storage/.sqlx/query-a3a1ac6a31364650446c2dd008a3df96617ad9aaa623d3ca128e43159eaf8779.json
generated
Normal file
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT EXISTS(\n SELECT 1 FROM users WHERE id = $1 AND $2 = ANY(permissions)\n ) as \"exists!\"\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "exists!",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4",
|
||||
{
|
||||
"Custom": {
|
||||
"name": "user_permissions_enum",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"upload_torrent",
|
||||
"download_torrent",
|
||||
"create_torrent_request",
|
||||
"immune_activity_pruning",
|
||||
"edit_title_group",
|
||||
"edit_title_group_comment",
|
||||
"edit_edition_group",
|
||||
"edit_torrent",
|
||||
"edit_artist",
|
||||
"edit_collage",
|
||||
"edit_series",
|
||||
"edit_torrent_request",
|
||||
"edit_forum_post",
|
||||
"edit_forum_thread",
|
||||
"edit_forum_sub_category",
|
||||
"edit_forum_category",
|
||||
"create_forum_category",
|
||||
"create_forum_sub_category",
|
||||
"create_forum_thread",
|
||||
"create_forum_post",
|
||||
"send_pm",
|
||||
"create_css_sheet",
|
||||
"edit_css_sheet",
|
||||
"set_default_css_sheet",
|
||||
"read_staff_pm",
|
||||
"reply_staff_pm",
|
||||
"resolve_staff_pm",
|
||||
"unresolve_staff_pm",
|
||||
"delete_title_group_tag",
|
||||
"edit_title_group_tag",
|
||||
"delete_torrent",
|
||||
"get_user_application",
|
||||
"update_user_application",
|
||||
"warn_user",
|
||||
"edit_user",
|
||||
"create_wiki_article",
|
||||
"edit_wiki_article"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "a3a1ac6a31364650446c2dd008a3df96617ad9aaa623d3ca128e43159eaf8779"
|
||||
}
|
||||
@@ -80,127 +80,178 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"name": "class",
|
||||
"name": "class_name",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"name": "class_locked",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"name": "permissions",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
"name": "user_class_enum",
|
||||
"name": "user_permissions_enum[]",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"newbie",
|
||||
"staff",
|
||||
"tracker"
|
||||
]
|
||||
"Array": {
|
||||
"Custom": {
|
||||
"name": "user_permissions_enum",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"upload_torrent",
|
||||
"download_torrent",
|
||||
"create_torrent_request",
|
||||
"immune_activity_pruning",
|
||||
"edit_title_group",
|
||||
"edit_title_group_comment",
|
||||
"edit_edition_group",
|
||||
"edit_torrent",
|
||||
"edit_artist",
|
||||
"edit_collage",
|
||||
"edit_series",
|
||||
"edit_torrent_request",
|
||||
"edit_forum_post",
|
||||
"edit_forum_thread",
|
||||
"edit_forum_sub_category",
|
||||
"edit_forum_category",
|
||||
"create_forum_category",
|
||||
"create_forum_sub_category",
|
||||
"create_forum_thread",
|
||||
"create_forum_post",
|
||||
"send_pm",
|
||||
"create_css_sheet",
|
||||
"edit_css_sheet",
|
||||
"set_default_css_sheet",
|
||||
"read_staff_pm",
|
||||
"reply_staff_pm",
|
||||
"resolve_staff_pm",
|
||||
"unresolve_staff_pm",
|
||||
"delete_title_group_tag",
|
||||
"edit_title_group_tag",
|
||||
"delete_torrent",
|
||||
"get_user_application",
|
||||
"update_user_application",
|
||||
"warn_user",
|
||||
"edit_user",
|
||||
"create_wiki_article",
|
||||
"edit_wiki_article"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"ordinal": 18,
|
||||
"name": "forum_posts",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"ordinal": 19,
|
||||
"name": "forum_threads",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"ordinal": 20,
|
||||
"name": "torrent_comments",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"ordinal": 21,
|
||||
"name": "request_comments",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"ordinal": 22,
|
||||
"name": "artist_comments",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 21,
|
||||
"ordinal": 23,
|
||||
"name": "seeding",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 22,
|
||||
"ordinal": 24,
|
||||
"name": "leeching",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 23,
|
||||
"ordinal": 25,
|
||||
"name": "snatched",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 24,
|
||||
"ordinal": 26,
|
||||
"name": "seeding_size",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 25,
|
||||
"ordinal": 27,
|
||||
"name": "requests_filled",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 26,
|
||||
"ordinal": 28,
|
||||
"name": "collages_started",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 27,
|
||||
"ordinal": 29,
|
||||
"name": "requests_voted",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 28,
|
||||
"ordinal": 30,
|
||||
"name": "average_seeding_time",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 29,
|
||||
"ordinal": 31,
|
||||
"name": "invited",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 30,
|
||||
"ordinal": 32,
|
||||
"name": "invitations",
|
||||
"type_info": "Int2"
|
||||
},
|
||||
{
|
||||
"ordinal": 31,
|
||||
"ordinal": 33,
|
||||
"name": "bonus_points",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 32,
|
||||
"ordinal": 34,
|
||||
"name": "freeleech_tokens",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 33,
|
||||
"ordinal": 35,
|
||||
"name": "passkey",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 34,
|
||||
"ordinal": 36,
|
||||
"name": "warned",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 35,
|
||||
"ordinal": 37,
|
||||
"name": "banned",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 36,
|
||||
"ordinal": 38,
|
||||
"name": "staff_note",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 37,
|
||||
"ordinal": 39,
|
||||
"name": "css_sheet_name",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
@@ -248,6 +299,8 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
|
||||
@@ -80,127 +80,178 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"name": "class",
|
||||
"name": "class_name",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"name": "class_locked",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"name": "permissions",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
"name": "user_class_enum",
|
||||
"name": "user_permissions_enum[]",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"newbie",
|
||||
"staff",
|
||||
"tracker"
|
||||
]
|
||||
"Array": {
|
||||
"Custom": {
|
||||
"name": "user_permissions_enum",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"upload_torrent",
|
||||
"download_torrent",
|
||||
"create_torrent_request",
|
||||
"immune_activity_pruning",
|
||||
"edit_title_group",
|
||||
"edit_title_group_comment",
|
||||
"edit_edition_group",
|
||||
"edit_torrent",
|
||||
"edit_artist",
|
||||
"edit_collage",
|
||||
"edit_series",
|
||||
"edit_torrent_request",
|
||||
"edit_forum_post",
|
||||
"edit_forum_thread",
|
||||
"edit_forum_sub_category",
|
||||
"edit_forum_category",
|
||||
"create_forum_category",
|
||||
"create_forum_sub_category",
|
||||
"create_forum_thread",
|
||||
"create_forum_post",
|
||||
"send_pm",
|
||||
"create_css_sheet",
|
||||
"edit_css_sheet",
|
||||
"set_default_css_sheet",
|
||||
"read_staff_pm",
|
||||
"reply_staff_pm",
|
||||
"resolve_staff_pm",
|
||||
"unresolve_staff_pm",
|
||||
"delete_title_group_tag",
|
||||
"edit_title_group_tag",
|
||||
"delete_torrent",
|
||||
"get_user_application",
|
||||
"update_user_application",
|
||||
"warn_user",
|
||||
"edit_user",
|
||||
"create_wiki_article",
|
||||
"edit_wiki_article"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"ordinal": 18,
|
||||
"name": "forum_posts",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"ordinal": 19,
|
||||
"name": "forum_threads",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"ordinal": 20,
|
||||
"name": "torrent_comments",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"ordinal": 21,
|
||||
"name": "request_comments",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"ordinal": 22,
|
||||
"name": "artist_comments",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 21,
|
||||
"ordinal": 23,
|
||||
"name": "seeding",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 22,
|
||||
"ordinal": 24,
|
||||
"name": "leeching",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 23,
|
||||
"ordinal": 25,
|
||||
"name": "snatched",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 24,
|
||||
"ordinal": 26,
|
||||
"name": "seeding_size",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 25,
|
||||
"ordinal": 27,
|
||||
"name": "requests_filled",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 26,
|
||||
"ordinal": 28,
|
||||
"name": "collages_started",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 27,
|
||||
"ordinal": 29,
|
||||
"name": "requests_voted",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 28,
|
||||
"ordinal": 30,
|
||||
"name": "average_seeding_time",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 29,
|
||||
"ordinal": 31,
|
||||
"name": "invited",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 30,
|
||||
"ordinal": 32,
|
||||
"name": "invitations",
|
||||
"type_info": "Int2"
|
||||
},
|
||||
{
|
||||
"ordinal": 31,
|
||||
"ordinal": 33,
|
||||
"name": "bonus_points",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 32,
|
||||
"ordinal": 34,
|
||||
"name": "freeleech_tokens",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 33,
|
||||
"ordinal": 35,
|
||||
"name": "passkey",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 34,
|
||||
"ordinal": 36,
|
||||
"name": "warned",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 35,
|
||||
"ordinal": 37,
|
||||
"name": "banned",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 36,
|
||||
"ordinal": 38,
|
||||
"name": "staff_note",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 37,
|
||||
"ordinal": 39,
|
||||
"name": "css_sheet_name",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
@@ -248,6 +299,8 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT id, created_at, ENCODE(info_hash, 'hex') as info_hash FROM torrents WHERE deleted_at IS NULL;\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "info_hash",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "c888cb000555bfb6f90440d3545d6b87dbe652b364e1713bb85bcc484c8679af"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n id,\n username,\n avatar,\n created_at,\n description,\n uploaded,\n downloaded,\n real_uploaded,\n real_downloaded,\n ratio,\n required_ratio,\n last_seen,\n class as \"class!: UserClass\",\n forum_posts,\n forum_threads,\n torrent_comments,\n request_comments,\n artist_comments,\n seeding,\n leeching,\n snatched,\n seeding_size,\n requests_filled,\n collages_started,\n requests_voted,\n average_seeding_time,\n invited,\n invitations,\n bonus_points,\n warned,\n banned\n FROM users\n WHERE id = $1\n ",
|
||||
"query": "\n SELECT\n id,\n username,\n avatar,\n created_at,\n description,\n uploaded,\n downloaded,\n real_uploaded,\n real_downloaded,\n ratio,\n required_ratio,\n last_seen,\n class_name,\n forum_posts,\n forum_threads,\n torrent_comments,\n request_comments,\n artist_comments,\n seeding,\n leeching,\n snatched,\n seeding_size,\n requests_filled,\n collages_started,\n requests_voted,\n average_seeding_time,\n invited,\n invitations,\n bonus_points,\n warned,\n banned\n FROM users\n WHERE id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -65,19 +65,8 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "class!: UserClass",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
"name": "user_class_enum",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"newbie",
|
||||
"staff",
|
||||
"tracker"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
"name": "class_name",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
@@ -209,5 +198,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "65d365c80d200c5dc02d84406798abf34ffa3156bf976257279377181a6a575d"
|
||||
"hash": "cc527f43d3cc692dbdec9053c276e9d50c3e1157ad658f2ed07afeff774307c3"
|
||||
}
|
||||
22
backend/storage/.sqlx/query-f364f7fe65cb51f1fb0eae6bcdc28b8e5edb4bfe4466afed8e2e1071b937134e.json
generated
Normal file
22
backend/storage/.sqlx/query-f364f7fe65cb51f1fb0eae6bcdc28b8e5edb4bfe4466afed8e2e1071b937134e.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT created_by_id\n FROM staff_pms\n WHERE id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "created_by_id",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "f364f7fe65cb51f1fb0eae6bcdc28b8e5edb4bfe4466afed8e2e1071b937134e"
|
||||
}
|
||||
@@ -1,8 +1,48 @@
|
||||
CREATE TYPE user_class_enum AS ENUM (
|
||||
'newbie',
|
||||
'staff',
|
||||
'tracker'
|
||||
CREATE TYPE user_permissions_enum AS ENUM (
|
||||
'upload_torrent',
|
||||
'download_torrent',
|
||||
'create_torrent_request',
|
||||
'immune_activity_pruning',
|
||||
'edit_title_group',
|
||||
'edit_title_group_comment',
|
||||
'edit_edition_group',
|
||||
'edit_torrent',
|
||||
'edit_artist',
|
||||
'edit_collage',
|
||||
'edit_series',
|
||||
'edit_torrent_request',
|
||||
'edit_forum_post',
|
||||
'edit_forum_thread',
|
||||
'edit_forum_sub_category',
|
||||
'edit_forum_category',
|
||||
'create_forum_category',
|
||||
'create_forum_sub_category',
|
||||
'create_forum_thread',
|
||||
'create_forum_post',
|
||||
'send_pm',
|
||||
'create_css_sheet',
|
||||
'edit_css_sheet',
|
||||
'set_default_css_sheet',
|
||||
'read_staff_pm',
|
||||
'reply_staff_pm',
|
||||
'resolve_staff_pm',
|
||||
'unresolve_staff_pm',
|
||||
'delete_title_group_tag',
|
||||
'edit_title_group_tag',
|
||||
'delete_torrent',
|
||||
'get_user_application',
|
||||
'update_user_application',
|
||||
'warn_user',
|
||||
'edit_user',
|
||||
'create_wiki_article',
|
||||
'edit_wiki_article'
|
||||
);
|
||||
CREATE TABLE user_classes (
|
||||
name VARCHAR(30) UNIQUE NOT NULL,
|
||||
default_permissions user_permissions_enum[] NOT NULL DEFAULT '{}'
|
||||
);
|
||||
INSERT INTO user_classes (name, default_permissions)
|
||||
VALUES ('newbie', '{}');
|
||||
CREATE TABLE users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
username VARCHAR(15) UNIQUE NOT NULL,
|
||||
@@ -20,7 +60,9 @@ CREATE TABLE users (
|
||||
ratio FLOAT NOT NULL DEFAULT 0.0,
|
||||
required_ratio FLOAT NOT NULL DEFAULT 0.0,
|
||||
last_seen TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
class user_class_enum NOT NULL DEFAULT 'newbie',
|
||||
class_name VARCHAR(30) NOT NULL REFERENCES user_classes(name) ON UPDATE CASCADE,
|
||||
class_locked BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
permissions user_permissions_enum[] NOT NULL DEFAULT '{}',
|
||||
forum_posts INTEGER NOT NULL DEFAULT 0,
|
||||
forum_threads INTEGER NOT NULL DEFAULT 0,
|
||||
torrent_comments INTEGER NOT NULL DEFAULT 0,
|
||||
@@ -46,8 +88,8 @@ CREATE TABLE users (
|
||||
|
||||
UNIQUE(passkey)
|
||||
);
|
||||
INSERT INTO users (username, email, password_hash, registered_from_ip, passkey)
|
||||
VALUES ('creator', 'none@domain.com', 'none', '127.0.0.1', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
|
||||
INSERT INTO users (username, email, password_hash, registered_from_ip, passkey, class_name)
|
||||
VALUES ('creator', 'none@domain.com', 'none', '127.0.0.1', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'newbie');
|
||||
CREATE TABLE css_sheets (
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
created_by_id INT NOT NULL REFERENCES users(id),
|
||||
|
||||
@@ -22,16 +22,18 @@ SET row_security = off;
|
||||
|
||||
INSERT INTO public._sqlx_migrations VALUES (20250312215600, 'initdb', '2025-09-17 12:42:13.702455+00', true, '\xcbb89aaf6e977779db8db1699079eb30481c2745b8b69cfdd75caa158cd61110a59dcaf436456572a0e6671a330fbfb8', 34635271);
|
||||
|
||||
INSERT INTO user_classes (name, default_permissions)
|
||||
VALUES ('staff', '{upload_torrent,download_torrent,create_torrent_request,immune_activity_pruning,edit_title_group,edit_title_group_comment,edit_edition_group,edit_torrent,edit_artist,edit_collage,edit_series,edit_torrent_request,edit_forum_post,edit_forum_thread,edit_forum_sub_category,edit_forum_category,create_forum_category,create_forum_sub_category,create_forum_thread,create_forum_post,send_pm,create_css_sheet,edit_css_sheet,set_default_css_sheet,read_staff_pm,reply_staff_pm,resolve_staff_pm,unresolve_staff_pm,delete_title_group_tag,edit_title_group_tag,delete_torrent,get_user_application,update_user_application,warn_user,edit_user,create_wiki_article,edit_wiki_article}');
|
||||
|
||||
--
|
||||
-- Data for Name: users; Type: TABLE DATA; Schema: public; Owner: arcadia
|
||||
--
|
||||
|
||||
INSERT INTO public.users VALUES (1, 'creator', NULL, 'none@domain.com', 'none', '127.0.0.1', '2025-09-17 12:42:13.702455+00', '', 0, 0, 1, 1, 0, 0, '2025-09-17 12:42:13.702455+00', 'newbie', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '111111111111111111111111111111111', false, false, '');
|
||||
INSERT INTO public.users VALUES (5, 'waterbottle', 'https://i.pinimg.com/736x/a6/27/12/a6271204df8d387c3e614986c106f549.jpg', 'user2@example.com', 'hashedpassword2', '192.168.1.2', '2025-03-30 16:24:57.388152+00', '', 0, 0, 1, 1, 0, 0, '2025-03-30 16:24:57.388152+00', 'newbie', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '22222222222222222222222222222222', false, false, '''''');
|
||||
INSERT INTO public.users VALUES (3, 'coolguy', 'https://i.pinimg.com/474x/c1/5a/6c/c15a6c91515e22f6ea8b766f89c12f0c.jpg', 'user3@example.com', 'hashedpassword3', '192.168.1.3', '2025-03-30 16:24:57.388152+00', '', 0, 0, 1, 1, 0, 0, '2025-03-30 16:24:57.388152+00', 'newbie', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '33333333333333333333333333333333', false, false, '''''');
|
||||
INSERT INTO public.users VALUES (4, 'test', NULL, 'test@test.tsttt', '$argon2id$v=19$m=19456,t=2,p=1$yaA+WqA4OfSyAqR3iXhDng$/Ngv7VeJvVNHli9rBgQG0d/O2W+qoI2yHhQxZSxxW2M', '127.0.0.1', '2025-04-10 19:15:51.036818+00', '', 979900000000, 0, 1, 1, 0, 0, '2025-09-17 09:15:44.322914+00', 'newbie', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 99999000, 0, '55555555555555555555555555555555', false, false, '''''');
|
||||
INSERT INTO public.users VALUES (2, 'picolo', 'https://img.freepik.com/premium-vector/random-people-line-art-vector_567805-63.jpg', 'user1@example.com', '$argon2id$v=19$m=19456,t=2,p=1$s4XJtCUk9IrGgNsTfP6Ofw$ktoGbBEoFaVgdiTn19Gh9h45LjFiv7AUEL5KHhzm4d0', '192.168.1.1', '2025-03-30 16:24:57.388152+00', '', 10000, 0, 1, 1, 0, 0, '2025-11-28 17:29:24.968105+00', 'staff', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 999999410, 0, '44444444444444444444444444444444', false, false, '''''');
|
||||
INSERT INTO public.users VALUES (1, 'creator', NULL, 'none@domain.com', 'none', '127.0.0.1', '2025-09-17 12:42:13.702455+00', '', 0, 0, 1, 1, 0, 0, '2025-09-17 12:42:13.702455+00', 'newbie', false, '{}', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '111111111111111111111111111111111', false, false, '');
|
||||
INSERT INTO public.users VALUES (5, 'waterbottle', 'https://i.pinimg.com/736x/a6/27/12/a6271204df8d387c3e614986c106f549.jpg', 'user2@example.com', 'hashedpassword2', '192.168.1.2', '2025-03-30 16:24:57.388152+00', '', 0, 0, 1, 1, 0, 0, '2025-03-30 16:24:57.388152+00', 'newbie', false, '{}', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '22222222222222222222222222222222', false, false, '''''');
|
||||
INSERT INTO public.users VALUES (3, 'coolguy', 'https://i.pinimg.com/474x/c1/5a/6c/c15a6c91515e22f6ea8b766f89c12f0c.jpg', 'user3@example.com', 'hashedpassword3', '192.168.1.3', '2025-03-30 16:24:57.388152+00', '', 0, 0, 1, 1, 0, 0, '2025-03-30 16:24:57.388152+00', 'newbie', false, '{}', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '33333333333333333333333333333333', false, false, '''''');
|
||||
INSERT INTO public.users VALUES (4, 'test', NULL, 'test@test.tsttt', '$argon2id$v=19$m=19456,t=2,p=1$yaA+WqA4OfSyAqR3iXhDng$/Ngv7VeJvVNHli9rBgQG0d/O2W+qoI2yHhQxZSxxW2M', '127.0.0.1', '2025-04-10 19:15:51.036818+00', '', 979900000000, 0, 1, 1, 0, 0, '2025-09-17 09:15:44.322914+00', 'newbie', false, '{}', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 99999000, 0, '55555555555555555555555555555555', false, false, '''''');
|
||||
INSERT INTO public.users VALUES (2, 'picolo', 'https://img.freepik.com/premium-vector/random-people-line-art-vector_567805-63.jpg', 'user1@example.com', '$argon2id$v=19$m=19456,t=2,p=1$s4XJtCUk9IrGgNsTfP6Ofw$ktoGbBEoFaVgdiTn19Gh9h45LjFiv7AUEL5KHhzm4d0', '192.168.1.1', '2025-03-30 16:24:57.388152+00', '', 10000, 0, 1, 1, 0, 0, '2025-11-28 17:29:24.968105+00', 'newbie', false, '{upload_torrent,download_torrent,create_torrent_request,immune_activity_pruning,edit_title_group,edit_title_group_comment,edit_edition_group,edit_torrent,edit_artist,edit_collage,edit_series,edit_torrent_request,edit_forum_post,edit_forum_thread,edit_forum_sub_category,edit_forum_category,create_forum_category,create_forum_sub_category,create_forum_thread,create_forum_post,send_pm,create_css_sheet,edit_css_sheet,set_default_css_sheet,read_staff_pm,reply_staff_pm,resolve_staff_pm,unresolve_staff_pm,delete_title_group_tag,edit_title_group_tag,delete_torrent,get_user_application,update_user_application,warn_user,edit_user,create_wiki_article,edit_wiki_article}', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 999999410, 0, '44444444444444444444444444444444', false, false, '''''');
|
||||
|
||||
|
||||
--
|
||||
|
||||
@@ -564,12 +564,3 @@ pub struct TorrentToDelete {
|
||||
pub reason: String,
|
||||
pub displayed_reason: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, FromRow, ToSchema)]
|
||||
pub struct TorrentMinimal {
|
||||
pub id: i32,
|
||||
#[schema(value_type = String, format = DateTime)]
|
||||
pub created_at: DateTime<Local>,
|
||||
// TODO: remove Option<>, this should never be null, but without it, the deserialization with sqlx fails somehow
|
||||
pub info_hash: Option<String>,
|
||||
}
|
||||
|
||||
@@ -28,7 +28,9 @@ pub struct User {
|
||||
pub required_ratio: f64,
|
||||
#[schema(value_type = String, format = DateTime)]
|
||||
pub last_seen: DateTime<Utc>,
|
||||
pub class: UserClass,
|
||||
pub class_name: String,
|
||||
pub class_locked: bool,
|
||||
pub permissions: Vec<UserPermission>,
|
||||
pub forum_posts: i32,
|
||||
pub forum_threads: i32,
|
||||
pub torrent_comments: i32,
|
||||
@@ -54,12 +56,46 @@ pub struct User {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::Type, ToSchema, PartialEq, Eq)]
|
||||
#[sqlx(type_name = "user_class_enum", rename_all = "lowercase")]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum UserClass {
|
||||
Newbie,
|
||||
Staff,
|
||||
Tracker,
|
||||
#[sqlx(type_name = "user_permissions_enum", rename_all = "snake_case")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum UserPermission {
|
||||
UploadTorrent,
|
||||
DownloadTorrent,
|
||||
CreateTorrentRequest,
|
||||
ImmuneActivityPruning,
|
||||
EditTitleGroup,
|
||||
EditTitleGroupComment,
|
||||
EditEditionGroup,
|
||||
EditTorrent,
|
||||
EditArtist,
|
||||
EditCollage,
|
||||
EditSeries,
|
||||
EditTorrentRequest,
|
||||
EditForumPost,
|
||||
EditForumThread,
|
||||
EditForumSubCategory,
|
||||
EditForumCategory,
|
||||
CreateForumCategory,
|
||||
CreateForumSubCategory,
|
||||
CreateForumThread,
|
||||
CreateForumPost,
|
||||
SendPm,
|
||||
CreateCssSheet,
|
||||
EditCssSheet,
|
||||
SetDefaultCssSheet,
|
||||
ReadStaffPm,
|
||||
ReplyStaffPm,
|
||||
ResolveStaffPm,
|
||||
UnresolveStaffPm,
|
||||
DeleteTitleGroupTag,
|
||||
EditTitleGroupTag,
|
||||
DeleteTorrent,
|
||||
GetUserApplication,
|
||||
UpdateUserApplication,
|
||||
WarnUser,
|
||||
EditUser,
|
||||
CreateWikiArticle,
|
||||
EditWikiArticle,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||
@@ -88,7 +124,6 @@ pub struct Claims {
|
||||
pub sub: i32,
|
||||
pub exp: i64,
|
||||
pub iat: i64,
|
||||
pub class: UserClass,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, ToSchema)]
|
||||
@@ -119,7 +154,7 @@ pub struct PublicUser {
|
||||
pub required_ratio: f64,
|
||||
#[schema(value_type = String, format = DateTime)]
|
||||
pub last_seen: DateTime<Utc>,
|
||||
pub class: UserClass,
|
||||
pub class_name: String,
|
||||
pub forum_posts: i32,
|
||||
pub forum_threads: i32,
|
||||
pub torrent_comments: i32,
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::{
|
||||
connection_pool::ConnectionPool,
|
||||
models::{
|
||||
invitation::Invitation,
|
||||
user::{APIKey, Login, Register, User, UserCreatedAPIKey},
|
||||
user::{APIKey, Login, Register, User, UserCreatedAPIKey, UserPermission},
|
||||
},
|
||||
};
|
||||
use arcadia_common::error::{Error, Result};
|
||||
@@ -36,6 +36,7 @@ impl ConnectionPool {
|
||||
password_hash: &str,
|
||||
invitation: &Invitation,
|
||||
open_signups: &bool,
|
||||
user_class_name: &str,
|
||||
) -> Result<User> {
|
||||
let rng = rand::rng();
|
||||
|
||||
@@ -54,15 +55,16 @@ impl ConnectionPool {
|
||||
let registered_user = sqlx::query_as_unchecked!(
|
||||
User,
|
||||
r#"
|
||||
INSERT INTO users (username, email, password_hash, registered_from_ip, passkey)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
INSERT INTO users (username, email, password_hash, registered_from_ip, passkey, class_name)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
RETURNING *
|
||||
"#,
|
||||
&user.username,
|
||||
&user.email,
|
||||
password_hash,
|
||||
from_ip,
|
||||
passkey
|
||||
passkey,
|
||||
user_class_name
|
||||
)
|
||||
.fetch_one(self.borrow())
|
||||
.await
|
||||
@@ -190,4 +192,24 @@ impl ConnectionPool {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn user_has_permission(
|
||||
&self,
|
||||
user_id: i32,
|
||||
permission: &UserPermission,
|
||||
) -> Result<bool> {
|
||||
let result = sqlx::query_scalar!(
|
||||
r#"
|
||||
SELECT EXISTS(
|
||||
SELECT 1 FROM users WHERE id = $1 AND $2 = ANY(permissions)
|
||||
) as "exists!"
|
||||
"#,
|
||||
user_id,
|
||||
permission as &UserPermission
|
||||
)
|
||||
.fetch_one(self.borrow())
|
||||
.await?;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ impl ConnectionPool {
|
||||
.map_err(Error::CouldNotCreateConversation)?;
|
||||
|
||||
conversation.first_message.staff_pm_id = created_conversation.id;
|
||||
self.create_staff_pm_message(&conversation.first_message, current_user_id)
|
||||
self.create_staff_pm_message(&conversation.first_message, current_user_id, true)
|
||||
.await?;
|
||||
|
||||
Ok(created_conversation)
|
||||
@@ -37,7 +37,26 @@ impl ConnectionPool {
|
||||
&self,
|
||||
message: &UserCreatedStaffPmMessage,
|
||||
current_user_id: i32,
|
||||
can_reply_staff_pm: bool,
|
||||
) -> Result<StaffPmMessage> {
|
||||
// Check if the user created the staff PM
|
||||
let staff_pm_creator_id = sqlx::query_scalar!(
|
||||
r#"
|
||||
SELECT created_by_id
|
||||
FROM staff_pms
|
||||
WHERE id = $1
|
||||
"#,
|
||||
message.staff_pm_id
|
||||
)
|
||||
.fetch_one(self.borrow())
|
||||
.await
|
||||
.map_err(Error::CouldNotFindConversation)?;
|
||||
|
||||
// If the user didn't create the staff PM and doesn't have permission to reply, deny access
|
||||
if staff_pm_creator_id != current_user_id && !can_reply_staff_pm {
|
||||
return Err(Error::InsufficientPrivileges);
|
||||
}
|
||||
|
||||
let message = sqlx::query_as!(
|
||||
StaffPmMessage,
|
||||
r#"
|
||||
@@ -100,7 +119,11 @@ impl ConnectionPool {
|
||||
Ok(updated)
|
||||
}
|
||||
|
||||
pub async fn list_staff_pms(&self, current_user_id: i32, is_staff: bool) -> Result<Value> {
|
||||
pub async fn list_staff_pms(
|
||||
&self,
|
||||
current_user_id: i32,
|
||||
can_read_staff_pm: bool,
|
||||
) -> Result<Value> {
|
||||
let row = sqlx::query_unchecked!(
|
||||
r#"
|
||||
SELECT
|
||||
@@ -144,7 +167,7 @@ impl ConnectionPool {
|
||||
WHERE ($2)::bool OR sp.created_by_id = $1;
|
||||
"#,
|
||||
current_user_id,
|
||||
is_staff,
|
||||
can_read_staff_pm,
|
||||
)
|
||||
.fetch_one(self.borrow())
|
||||
.await
|
||||
@@ -157,7 +180,7 @@ impl ConnectionPool {
|
||||
&self,
|
||||
staff_pm_id: i64,
|
||||
current_user_id: i32,
|
||||
is_staff: bool,
|
||||
can_read_staff_pm: bool,
|
||||
) -> Result<Value> {
|
||||
let row = sqlx::query_unchecked!(
|
||||
r#"
|
||||
@@ -198,7 +221,7 @@ impl ConnectionPool {
|
||||
"#,
|
||||
staff_pm_id,
|
||||
current_user_id,
|
||||
is_staff,
|
||||
can_read_staff_pm,
|
||||
)
|
||||
.fetch_one(self.borrow())
|
||||
.await
|
||||
|
||||
@@ -5,8 +5,8 @@ use crate::{
|
||||
edition_group::EditionGroupHierarchyLite,
|
||||
title_group::TitleGroupHierarchyLite,
|
||||
torrent::{
|
||||
EditedTorrent, Features, Torrent, TorrentHierarchyLite, TorrentMinimal, TorrentSearch,
|
||||
TorrentToDelete, UploadedTorrent,
|
||||
EditedTorrent, Features, Torrent, TorrentHierarchyLite, TorrentSearch, TorrentToDelete,
|
||||
UploadedTorrent,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -758,17 +758,4 @@ impl ConnectionPool {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn find_registered_torrents(&self) -> Result<Vec<TorrentMinimal>> {
|
||||
let torrents = sqlx::query_as!(
|
||||
TorrentMinimal,
|
||||
r#"
|
||||
SELECT id, created_at, ENCODE(info_hash, 'hex') as info_hash FROM torrents WHERE deleted_at IS NULL;
|
||||
"#
|
||||
)
|
||||
.fetch_all(self.borrow())
|
||||
.await?;
|
||||
|
||||
Ok(torrents)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use crate::{
|
||||
connection_pool::ConnectionPool,
|
||||
models::user::{
|
||||
EditedUser, PublicUser, UserClass, UserCreatedUserWarning, UserMinimal, UserSettings,
|
||||
UserWarning,
|
||||
EditedUser, PublicUser, UserCreatedUserWarning, UserMinimal, UserSettings, UserWarning,
|
||||
},
|
||||
};
|
||||
use arcadia_common::error::{Error, Result};
|
||||
@@ -27,7 +26,7 @@ impl ConnectionPool {
|
||||
ratio,
|
||||
required_ratio,
|
||||
last_seen,
|
||||
class as "class!: UserClass",
|
||||
class_name,
|
||||
forum_posts,
|
||||
forum_threads,
|
||||
torrent_comments,
|
||||
|
||||
Reference in New Issue
Block a user