feat(backend): configurable user classes and permissions

This commit is contained in:
FrenchGithubUser
2025-12-14 11:00:42 +01:00
parent 5393b9d02c
commit 88c6bfa172
70 changed files with 1175 additions and 520 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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,

View File

@@ -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")]

View File

@@ -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);

View File

@@ -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(

View File

@@ -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(

View File

@@ -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?;

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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 {

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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))
}

View File

@@ -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))
}

View File

@@ -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))
}

View File

@@ -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?;

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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))
}

View File

@@ -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>)));

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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?;

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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)
}

View File

@@ -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,

View File

@@ -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')

View File

@@ -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')

View File

@@ -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')

View 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}');

View File

@@ -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) {

View File

@@ -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(

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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) =

View File

@@ -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) {

View File

@@ -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(),

View File

@@ -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();

View File

@@ -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"

View File

@@ -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(

View File

@@ -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
]
},

View File

@@ -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"
}

View 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"
}

View File

@@ -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
]
},

View File

@@ -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
]
},

View File

@@ -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"
}

View File

@@ -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"
}

View 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"
}

View File

@@ -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),

View File

@@ -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, '''''');
--

View File

@@ -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>,
}

View File

@@ -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,

View File

@@ -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)
}
}

View File

@@ -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

View File

@@ -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)
}
}

View File

@@ -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,