From a453130797ba85d068ee0dd13d1aa6277cd544af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sn=C3=ADda=C5=88ov=C3=BD=20Mistr?= Date: Sat, 17 Feb 2024 22:24:39 +0100 Subject: [PATCH] =?UTF-8?q?Syst=C3=A9m=20hl=C3=A1=C5=A1en=C3=AD=20a=20vizu?= =?UTF-8?q?=C3=A1ln=C3=AD=20zm=C4=9Bny?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 217 ++++++++++++++----- Cargo.toml | 2 +- src/cfg.rs | 1 + src/db/post.rs | 18 +- src/lib.rs | 10 +- src/web/actions/edit_posts.rs | 2 +- src/web/actions/mod.rs | 2 +- src/web/actions/report_posts.rs | 2 +- src/web/actions/staff_post_actions.rs | 182 +++++++++++----- src/web/actions/user_post_actions.rs | 2 +- src/web/edit_posts.rs | 2 +- src/web/overboard.rs | 9 +- src/web/staff/accounts.rs | 5 + src/web/staff/actions/update_board_config.rs | 3 + src/web/staff/banners.rs | 8 +- src/web/staff/bans.rs | 8 +- src/web/staff/board_config.rs | 8 +- src/web/staff/boards.rs | 8 +- src/web/staff/news.rs | 5 + src/web/staff/permissions.rs | 10 +- src/web/staff/reports.rs | 46 +++- static/style.css | 29 +-- static/themes/yotsuba-b.css | 5 +- static/themes/yotsuba.css | 5 +- templates/banned.html | 8 +- templates/base.html | 2 +- templates/board-catalog.html | 1 + templates/board.html | 1 + templates/error.html | 2 +- templates/macros/pagination.html | 28 ++- templates/macros/post-actions.html | 27 ++- templates/macros/post.html | 2 +- templates/macros/staff-nav.html | 14 +- templates/macros/static-pagination.html | 13 ++ templates/overboard-catalog.html | 2 +- templates/staff/board-config.html | 5 + templates/staff/reports.html | 31 ++- templates/thread.html | 1 + theme.txt | 1 + 39 files changed, 511 insertions(+), 216 deletions(-) create mode 100644 templates/macros/static-pagination.html create mode 100644 theme.txt diff --git a/Cargo.lock b/Cargo.lock index 4c1c355..b622257 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -327,6 +327,54 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "anyhow" version = "1.0.74" @@ -583,7 +631,7 @@ dependencies = [ "pure-rust-locales", "serde", "wasm-bindgen", - "windows-targets", + "windows-targets 0.48.1", ] [[package]] @@ -623,6 +671,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "combine" version = "4.6.6" @@ -1004,16 +1058,26 @@ dependencies = [ ] [[package]] -name = "env_logger" -version = "0.10.0" +name = "env_filter" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" dependencies = [ - "humantime", - "is-terminal", "log", "regex", - "termcolor", +] + +[[package]] +name = "env_logger" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c012a26a7f605efc424dd53697843a72be7dc86ad2d01f7814337794a12231d" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", ] [[package]] @@ -1030,7 +1094,7 @@ checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1051,7 +1115,7 @@ checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" dependencies = [ "cfg-if", "home", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1378,7 +1442,7 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1539,17 +1603,6 @@ dependencies = [ "serde", ] -[[package]] -name = "is-terminal" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" -dependencies = [ - "hermit-abi", - "rustix", - "windows-sys", -] - [[package]] name = "itertools" version = "0.10.5" @@ -1785,7 +1838,7 @@ dependencies = [ "libc", "log", "wasi", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1967,7 +2020,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.48.1", ] [[package]] @@ -2313,7 +2366,7 @@ dependencies = [ "libc", "spin 0.9.8", "untrusted", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2363,7 +2416,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2595,7 +2648,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2889,16 +2942,7 @@ dependencies = [ "fastrand", "redox_syscall", "rustix", - "windows-sys", -] - -[[package]] -name = "termcolor" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" -dependencies = [ - "winapi-util", + "windows-sys 0.48.0", ] [[package]] @@ -2990,7 +3034,7 @@ dependencies = [ "signal-hook-registry", "socket2 0.5.3", "tokio-macros", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -3167,6 +3211,12 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5190c9442dcdaf0ddd50f37420417d219ae5261bbf5db120d0f9bab996c9cba1" +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "vcpkg" version = "0.2.15" @@ -3267,15 +3317,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -3288,7 +3329,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-targets", + "windows-targets 0.48.1", ] [[package]] @@ -3297,7 +3338,16 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.1", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", ] [[package]] @@ -3306,13 +3356,28 @@ version = "0.48.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] [[package]] @@ -3321,42 +3386,84 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "winnow" version = "0.5.18" diff --git a/Cargo.toml b/Cargo.toml index acb9581..ee6edbf 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ chrono-tz = "0.8.5" dotenv = "0.15.0" enumflags2 = "0.7.7" encoding = "0.2.33" -env_logger = "0.10.0" +env_logger = "0.11.2" glob = "0.3.1" image = "0.24.7" ipnetwork = "0.20.0" diff --git a/src/cfg.rs b/src/cfg.rs index 781d70f..0cf68f0 100755 --- a/src/cfg.rs +++ b/src/cfg.rs @@ -64,6 +64,7 @@ pub struct BoardCfg { pub flags: bool, pub thread_captcha: String, pub reply_captcha: String, + pub board_theme: String, pub require_thread_content: bool, pub require_thread_file: bool, pub require_reply_content: bool, diff --git a/src/db/post.rs b/src/db/post.rs index 6af2746..b570a16 100755 --- a/src/db/post.rs +++ b/src/db/post.rs @@ -5,7 +5,7 @@ use sqlx::{query, query_as, types::Json}; use std::net::IpAddr; use super::models::{Board, File, Post, Report}; -use crate::{ctx::Ctx, error::NekrochanError}; +use crate::{ctx::Ctx, error::NekrochanError, GENERIC_PAGE_SIZE}; impl Post { #[allow(clippy::too_many_arguments)] @@ -161,8 +161,8 @@ impl Post { LIMIT $1 OFFSET $2"#, ) - .bind(15) - .bind((page - 1) * 15) + .bind(GENERIC_PAGE_SIZE) + .bind((page - 1) * GENERIC_PAGE_SIZE) .fetch_all(ctx.db()) .await?; @@ -181,12 +181,16 @@ impl Post { Ok(posts) } - pub async fn read_reports(ctx: &Ctx) -> Result, NekrochanError> { + pub async fn read_reports_page(ctx: &Ctx, page: i64) -> Result, NekrochanError> { let posts = query_as( r#"SELECT * FROM overboard - WHERE reports != '[]'::jsonb - ORDER BY jsonb_array_length(reports), reported DESC"#, + WHERE reports != '[]'::jsonb + ORDER BY jsonb_array_length(reports), reported DESC + LIMIT $1 + OFFSET $2"#, ) + .bind(GENERIC_PAGE_SIZE) + .bind((page - 1) * GENERIC_PAGE_SIZE) .fetch_all(ctx.db()) .await?; @@ -195,7 +199,7 @@ impl Post { pub async fn read_replies(&self, ctx: &Ctx) -> Result, NekrochanError> { let replies = query_as(&format!( - "SELECT * FROM posts_{} WHERE thread = $1 ORDER BY created ASC", + "SELECT * FROM posts_{} WHERE thread = $1 ORDER BY sticky DESC, created ASC", self.board )) .bind(self.id) diff --git a/src/lib.rs b/src/lib.rs index 64755f0..a0bbe6e 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,8 @@ lazy_static! { RwLock::new(HashMap::new()); } +const GENERIC_PAGE_SIZE: i64 = 15; + pub mod auth; pub mod cfg; pub mod ctx; @@ -22,7 +24,13 @@ pub mod trip; pub mod web; pub fn paginate(page_size: i64, count: i64) -> i64 { - count / page_size + (count % page_size).signum() + let pages = count / page_size + (count % page_size).signum(); + + if pages == 0 { + return 1; + } else { + return pages; + } } pub fn check_page( diff --git a/src/web/actions/edit_posts.rs b/src/web/actions/edit_posts.rs index 0c6c76f..982cfef 100644 --- a/src/web/actions/edit_posts.rs +++ b/src/web/actions/edit_posts.rs @@ -26,7 +26,7 @@ pub async fn edit_posts( let ids = edits.keys().map(|s| s.to_owned()).collect::>(); - let posts = get_posts_from_ids(&ctx, ids) + let posts = get_posts_from_ids(&ctx, &ids) .await .into_iter() .map(|post| (format!("{}/{}", post.board, post.id), post)) diff --git a/src/web/actions/mod.rs b/src/web/actions/mod.rs index 5d1d6cd..bb58849 100644 --- a/src/web/actions/mod.rs +++ b/src/web/actions/mod.rs @@ -17,7 +17,7 @@ pub struct ActionTemplate { pub response: String, } -pub async fn get_posts_from_ids(ctx: &Ctx, ids: Vec) -> Vec { +pub async fn get_posts_from_ids(ctx: &Ctx, ids: &Vec) -> Vec { let mut posts = Vec::new(); for id in ids { diff --git a/src/web/actions/report_posts.rs b/src/web/actions/report_posts.rs index 220eeeb..b98347b 100644 --- a/src/web/actions/report_posts.rs +++ b/src/web/actions/report_posts.rs @@ -39,7 +39,7 @@ pub async fn report_posts( } let boards = Board::read_all_map(&ctx).await?; - let posts = get_posts_from_ids(&ctx, form.posts).await; + let posts = get_posts_from_ids(&ctx, &form.posts).await; let mut response = String::new(); let mut posts_reported = 0; diff --git a/src/web/actions/staff_post_actions.rs b/src/web/actions/staff_post_actions.rs index d0011b0..b78f5b9 100644 --- a/src/web/actions/staff_post_actions.rs +++ b/src/web/actions/staff_post_actions.rs @@ -2,11 +2,11 @@ use actix_web::{post, web::Data, HttpRequest, HttpResponse}; use chrono::{Duration, Utc}; use ipnetwork::IpNetwork; use serde::Deserialize; -use std::{collections::HashSet, fmt::Write}; +use std::{collections::HashSet, fmt::Write, net::IpAddr}; use crate::{ ctx::Ctx, - db::models::Ban, + db::models::{Account, Ban}, error::NekrochanError, qsform::QsForm, web::{ @@ -28,11 +28,13 @@ pub struct StaffPostActionsForm { pub toggle_spoiler: Option, pub toggle_sticky: Option, pub toggle_lock: Option, + pub remove_reports: Option, pub ban_user: Option, + pub ban_reporters: Option, pub global_ban: Option, pub unappealable_ban: Option, pub ban_reason: Option, - pub ban_duration: Option, + pub ban_duration: Option, pub ban_range: Option, } @@ -44,7 +46,7 @@ pub async fn staff_post_actions( ) -> Result { let tcx = TemplateCtx::new(&ctx, &req).await?; let account = account_from_auth(&ctx, &req).await?; - let posts = get_posts_from_ids(&ctx, form.posts).await; + let posts = get_posts_from_ids(&ctx, &form.posts).await; let mut response = String::new(); @@ -53,6 +55,7 @@ pub async fn staff_post_actions( let mut spoilers_toggled = 0; let mut stickies_toggled = 0; let mut locks_toggled = 0; + let mut reports_removed = 0; let mut bans_issued = 0; for post in &posts { @@ -89,20 +92,30 @@ pub async fn staff_post_actions( if form.toggle_sticky.is_some() { post.update_sticky(&ctx).await?; - stickies_toggled += post.files.0.len(); + stickies_toggled += 1; } if form.toggle_lock.is_some() { post.update_lock(&ctx).await?; - locks_toggled += post.files.0.len(); + locks_toggled += 1; + } + + if form.remove_reports.is_some() { + post.delete_reports(&ctx).await?; + reports_removed += post.reports.0.len(); } } let mut already_banned = HashSet::new(); for post in posts { - if let (Some(_), Some(ban_reason), Some(ban_duration), Some(ban_range)) = ( - form.ban_user.clone(), + if let ( + (Some(_), None) | (None, Some(_)) | (Some(_), Some(_)), + Some(reason), + Some(duration), + Some(range), + ) = ( + (form.ban_user.clone(), form.ban_reporters.clone()), form.ban_reason.clone(), form.ban_duration, form.ban_range.clone(), @@ -112,63 +125,56 @@ pub async fn staff_post_actions( continue; } - if already_banned.contains(&post.ip) { + let mut ips_to_ban = HashSet::new(); + + if form.ban_user.is_some() && !already_banned.contains(&post.ip) { + ips_to_ban.insert(post.ip); + } + + if form.ban_reporters.is_some() { + ips_to_ban.extend( + post.reports + .0 + .iter() + .map(|r| r.reporter_ip) + .filter(|ip| !already_banned.contains(ip)), + ) + } + + if ips_to_ban.is_empty() { continue; } - let account = account.username.clone(); - - let board = if form.global_ban.is_none() { - Some(post.board.clone()) - } else { - None - }; - - let prefix = if post.ip.is_ipv4() { - match ban_range.as_str() { - "lan" => 24, - "isp" => 16, - _ => 32, - } - } else { - match ban_range.as_str() { - "lan" => 48, - "isp" => 24, - _ => 128, - } - }; - - let ip_range = IpNetwork::new(post.ip, prefix)?; - let reason: String = ban_reason.trim().into(); - - if reason.is_empty() || reason.len() > 200 { - return Err(NekrochanError::BanReasonFormatError); + for ip in &ips_to_ban { + ban_ip( + &ctx, + &account, + &form, + *ip, + post.board.clone(), + reason.clone(), + duration, + &range, + ) + .await?; } - let appealable = form.unappealable_ban.is_none(); + if form.ban_user.is_some() { + let content_nomarkup = format!( + "{}\n\n##(UŽIVATEL BYL ZA TENTO PŘÍSPĚVEK ZABANOVÁN)##", + post.content_nomarkup + ); - let expires = if ban_duration == 0 { - None - } else { - Some(Utc::now() + Duration::days(ban_duration)) - }; + let content = format!( + "{}\n\n(UŽIVATEL BYL ZA TENTO PŘÍSPĚVEK ZABANOVÁN)", + post.content + ); - Ban::create(&ctx, account, board, ip_range, reason, appealable, expires).await?; + post.update_content(&ctx, content, content_nomarkup).await?; + } - let content_nomarkup = format!( - "{}\n\n##(UŽIVATEL BYL ZA TENTO PŘÍSPĚVEK ZABANOVÁN)##", - post.content_nomarkup - ); - - let content = format!( - "{}\n\n(UŽIVATEL BYL ZA TENTO PŘÍSPĚVEK ZABANOVÁN)", - post.content - ); - - post.update_content(&ctx, content, content_nomarkup).await?; - - already_banned.insert(post.ip); - bans_issued += 1; + bans_issued += ips_to_ban.len(); + already_banned.extend(ips_to_ban); } } @@ -199,7 +205,7 @@ pub async fn staff_post_actions( if stickies_toggled != 0 { writeln!( &mut response, - "[Úspěch] Připnuta/odepnuta vlákna: {stickies_toggled}" + "[Úspěch] Připnuty/odepnuty příspěvky: {stickies_toggled}" ) .ok(); } @@ -212,6 +218,14 @@ pub async fn staff_post_actions( .ok(); } + if reports_removed != 0 { + writeln!( + &mut response, + "[Úspěch] Odstraněna hlášení: {reports_removed}" + ) + .ok(); + } + if bans_issued != 0 { writeln!(&mut response, "[Úspěch] Uděleny bany: {bans_issued}").ok(); } @@ -220,3 +234,55 @@ pub async fn staff_post_actions( template_response(&template) } + +async fn ban_ip( + ctx: &Ctx, + account: &Account, + form: &StaffPostActionsForm, + ip: IpAddr, + board: String, + reason: String, + duration: u64, + range: &str, +) -> Result<(), NekrochanError> { + let account = account.username.clone(); + + let board = if form.global_ban.is_none() { + Some(board) + } else { + None + }; + + let prefix = if ip.is_ipv4() { + match range { + "lan" => 24, + "isp" => 16, + _ => 32, + } + } else { + match range { + "lan" => 48, + "isp" => 24, + _ => 128, + } + }; + + let ip_range = IpNetwork::new(ip, prefix)?; + let reason: String = reason.trim().into(); + + if reason.is_empty() || reason.len() > 200 { + return Err(NekrochanError::BanReasonFormatError); + } + + let appealable = form.unappealable_ban.is_none(); + + let expires = if duration == 0 { + None + } else { + Some(Utc::now() + Duration::days(duration as i64)) + }; + + Ban::create(&ctx, account, board, ip_range, reason, appealable, expires).await?; + + Ok(()) +} diff --git a/src/web/actions/user_post_actions.rs b/src/web/actions/user_post_actions.rs index 4e82e3e..57360be 100644 --- a/src/web/actions/user_post_actions.rs +++ b/src/web/actions/user_post_actions.rs @@ -42,7 +42,7 @@ pub async fn user_post_actions( } } - let posts = get_posts_from_ids(&ctx, form.posts).await; + let posts = get_posts_from_ids(&ctx, &form.posts).await; let boards = Board::read_all_map(&ctx).await?; let mut response = String::new(); diff --git a/src/web/edit_posts.rs b/src/web/edit_posts.rs index 3a53331..e9fabbc 100644 --- a/src/web/edit_posts.rs +++ b/src/web/edit_posts.rs @@ -35,7 +35,7 @@ pub async fn edit_posts( return Err(NekrochanError::InsufficientPermissionError); } - let posts = get_posts_from_ids(&ctx, form.posts).await; + let posts = get_posts_from_ids(&ctx, &form.posts).await; let template = EditPostsTemplate { tcx, posts }; template_response(&template) diff --git a/src/web/overboard.rs b/src/web/overboard.rs index edf9e5d..13421d1 100644 --- a/src/web/overboard.rs +++ b/src/web/overboard.rs @@ -9,12 +9,7 @@ use serde::Deserialize; use std::collections::HashMap; use crate::{ - check_page, - ctx::Ctx, - db::models::{Board, Post}, - error::NekrochanError, - filters, paginate, - web::{tcx::TemplateCtx, template_response}, + check_page, ctx::Ctx, db::models::{Board, Post}, error::NekrochanError, filters, paginate, web::{tcx::TemplateCtx, template_response}, GENERIC_PAGE_SIZE }; #[derive(Deserialize)] @@ -43,7 +38,7 @@ pub async fn overboard( let count = ctx.cache().get("total_threads").await?; let page = query.map_or(1, |q| q.page); - let pages = paginate(15, count); + let pages = paginate(GENERIC_PAGE_SIZE, count); check_page(page, pages, None)?; diff --git a/src/web/staff/accounts.rs b/src/web/staff/accounts.rs index 10165eb..b8cca52 100755 --- a/src/web/staff/accounts.rs +++ b/src/web/staff/accounts.rs @@ -19,6 +19,11 @@ struct AccountsTemplate { #[get("/staff/accounts")] pub async fn accounts(ctx: Data, req: HttpRequest) -> Result { let tcx = TemplateCtx::new(&ctx, &req).await?; + + if tcx.account.is_none() { + return Err(NekrochanError::NotLoggedInError); + } + let accounts = Account::read_all(&ctx).await?; let template = AccountsTemplate { tcx, accounts }; diff --git a/src/web/staff/actions/update_board_config.rs b/src/web/staff/actions/update_board_config.rs index 7175bcb..f1cd630 100755 --- a/src/web/staff/actions/update_board_config.rs +++ b/src/web/staff/actions/update_board_config.rs @@ -20,6 +20,7 @@ pub struct UpdateBoardConfigForm { flags: Option, thread_captcha: String, reply_captcha: String, + board_theme: String, require_thread_content: Option, require_thread_file: Option, require_reply_content: Option, @@ -58,6 +59,7 @@ pub async fn update_board_config( let flags = form.flags.is_some(); let thread_captcha = form.thread_captcha; let reply_captcha = form.reply_captcha; + let board_theme = form.board_theme; let require_thread_content = form.require_thread_content.is_some(); let require_thread_file = form.require_thread_file.is_some(); let require_reply_content = form.require_reply_content.is_some(); @@ -79,6 +81,7 @@ pub async fn update_board_config( flags, thread_captcha, reply_captcha, + board_theme, require_thread_content, require_thread_file, require_reply_content, diff --git a/src/web/staff/banners.rs b/src/web/staff/banners.rs index 621c42f..1b651d7 100755 --- a/src/web/staff/banners.rs +++ b/src/web/staff/banners.rs @@ -5,10 +5,7 @@ use crate::{ ctx::Ctx, db::models::Banner, error::NekrochanError, - web::{ - tcx::{account_from_auth, TemplateCtx}, - template_response, - }, + web::{tcx::TemplateCtx, template_response}, }; #[derive(Template)] @@ -21,9 +18,8 @@ struct BannersTemplate { #[get("/staff/banners")] pub async fn banners(ctx: Data, req: HttpRequest) -> Result { let tcx = TemplateCtx::new(&ctx, &req).await?; - let account = account_from_auth(&ctx, &req).await?; - if !(account.perms().owner() || account.perms().banners()) { + if !(tcx.perms.owner() || tcx.perms.banners()) { return Err(NekrochanError::InsufficientPermissionError); } diff --git a/src/web/staff/bans.rs b/src/web/staff/bans.rs index 70a770e..70d1efd 100755 --- a/src/web/staff/bans.rs +++ b/src/web/staff/bans.rs @@ -6,10 +6,7 @@ use crate::{ db::models::Ban, error::NekrochanError, filters, - web::{ - tcx::{account_from_auth, TemplateCtx}, - template_response, - }, + web::{tcx::TemplateCtx, template_response}, }; #[derive(Template)] @@ -22,9 +19,8 @@ struct BansTemplate { #[get("/staff/bans")] pub async fn bans(ctx: Data, req: HttpRequest) -> Result { let tcx = TemplateCtx::new(&ctx, &req).await?; - let account = account_from_auth(&ctx, &req).await?; - if !(account.perms().owner() || account.perms().bans()) { + if !(tcx.perms.owner() || tcx.perms.bans()) { return Err(NekrochanError::InsufficientPermissionError); } diff --git a/src/web/staff/board_config.rs b/src/web/staff/board_config.rs index 53cb17d..6da2dd6 100755 --- a/src/web/staff/board_config.rs +++ b/src/web/staff/board_config.rs @@ -9,10 +9,7 @@ use crate::{ ctx::Ctx, db::models::Board, error::NekrochanError, - web::{ - tcx::{account_from_auth, TemplateCtx}, - template_response, - }, + web::{tcx::TemplateCtx, template_response}, }; #[derive(Template)] @@ -29,9 +26,8 @@ pub async fn board_config( board: Path, ) -> Result { let tcx = TemplateCtx::new(&ctx, &req).await?; - let account = account_from_auth(&ctx, &req).await?; - if !(account.perms().owner() || account.perms().board_config()) { + if !(tcx.perms.owner() || tcx.perms.board_config()) { return Err(NekrochanError::InsufficientPermissionError); } diff --git a/src/web/staff/boards.rs b/src/web/staff/boards.rs index 74017ab..bd3b1ff 100755 --- a/src/web/staff/boards.rs +++ b/src/web/staff/boards.rs @@ -6,10 +6,7 @@ use crate::{ db::models::Board, error::NekrochanError, filters, - web::{ - tcx::{account_from_auth, TemplateCtx}, - template_response, - }, + web::{tcx::TemplateCtx, template_response}, }; #[derive(Template)] @@ -22,9 +19,8 @@ struct BoardsTemplate { #[get("/staff/boards")] pub async fn boards(ctx: Data, req: HttpRequest) -> Result { let tcx = TemplateCtx::new(&ctx, &req).await?; - let account = account_from_auth(&ctx, &req).await?; - if !(account.perms().owner() || account.perms().board_config() || account.perms().banners()) { + if !(tcx.perms.owner() || tcx.perms.board_config() || tcx.perms.banners()) { return Err(NekrochanError::InsufficientPermissionError); } diff --git a/src/web/staff/news.rs b/src/web/staff/news.rs index d01e5cb..7b0773a 100644 --- a/src/web/staff/news.rs +++ b/src/web/staff/news.rs @@ -18,6 +18,11 @@ struct NewsTemplate { #[get("/staff/news")] pub async fn news(ctx: Data, req: HttpRequest) -> Result { let tcx = TemplateCtx::new(&ctx, &req).await?; + + if !(tcx.perms.owner() || tcx.perms.news()) { + return Err(NekrochanError::InsufficientPermissionError); + } + let news = NewsPost::read_all(&ctx).await?; let template = NewsTemplate { tcx, news }; diff --git a/src/web/staff/permissions.rs b/src/web/staff/permissions.rs index 2735a74..251ed33 100755 --- a/src/web/staff/permissions.rs +++ b/src/web/staff/permissions.rs @@ -9,10 +9,7 @@ use crate::{ ctx::Ctx, db::models::Account, error::NekrochanError, - web::{ - tcx::{account_from_auth, TemplateCtx}, - template_response, - }, + web::{tcx::TemplateCtx, template_response}, }; #[derive(Template)] @@ -29,7 +26,10 @@ pub async fn permissions( path: Path, ) -> Result { let tcx = TemplateCtx::new(&ctx, &req).await?; - let _ = account_from_auth(&ctx, &req).await?; + + if !tcx.perms.owner() { + return Err(NekrochanError::InsufficientPermissionError); + } let account = path.into_inner(); let account = Account::read(&ctx, account.clone()) diff --git a/src/web/staff/reports.rs b/src/web/staff/reports.rs index 032c32b..3e33249 100755 --- a/src/web/staff/reports.rs +++ b/src/web/staff/reports.rs @@ -1,23 +1,63 @@ -use actix_web::{get, web::Data, HttpRequest, HttpResponse}; +use std::collections::HashMap; + +use actix_web::{ + get, + web::{Data, Query}, + HttpRequest, HttpResponse, +}; use askama::Template; +use serde::Deserialize; use crate::{ ctx::Ctx, + db::models::{Board, Post}, error::NekrochanError, + filters, web::{tcx::TemplateCtx, template_response}, }; +#[derive(Deserialize)] +pub struct BoardQuery { + page: i64, +} + #[allow(dead_code)] #[derive(Template)] #[template(path = "staff/reports.html")] struct ReportsTemplate { tcx: TemplateCtx, + boards: HashMap, + posts: Vec, + page: i64, } #[get("/staff/reports")] -async fn reports(ctx: Data, req: HttpRequest) -> Result { +async fn reports( + ctx: Data, + req: HttpRequest, + query: Option>, +) -> Result { let tcx = TemplateCtx::new(&ctx, &req).await?; - let template = ReportsTemplate { tcx }; + + if !(tcx.perms.owner() || tcx.perms.reports()) { + return Err(NekrochanError::InsufficientPermissionError); + } + + let boards = Board::read_all_map(&ctx).await?; + let page = query.map_or(1, |q| q.page); + + if page <= 0 { + return Err(NekrochanError::InvalidPageError); + } + + let posts = Post::read_reports_page(&ctx, page).await?; + + let template = ReportsTemplate { + tcx, + boards, + posts, + page, + }; template_response(&template) } diff --git a/static/style.css b/static/style.css index 70b1529..91ff268 100755 --- a/static/style.css +++ b/static/style.css @@ -56,7 +56,7 @@ summary { .edit-box { display: block; - width: 100%; + width: 100%; } .form-table input[type="text"], @@ -90,7 +90,6 @@ summary { resize: none; } - .reply-mode { font-weight: bold; font-size: 18px; @@ -105,26 +104,25 @@ summary { } .table-wrap { - overflow-x: auto; + overflow: scroll; } .data-table { width: 100%; - background-color: var(--table-primary); border-spacing: 0; border-collapse: collapse; margin: 8px 0; } -.data-table tr:nth-child(2n + 1) { - background-color: var(--table-secondary); -} - .data-table th { background-color: var(--table-head); } -.data-table td, +.data-table td { + background-color: var(--table-background); +} + +.data-table td:not(.form-table td), .data-table th { border: 1px solid var(--table-border); padding: 4px; @@ -213,6 +211,10 @@ summary { table-layout: fixed; } +.m-0 { + margin: 0; +} + .form-table .button, .full-width { display: block; @@ -238,8 +240,9 @@ summary { clear: both; } -.board-links { - color: var(--board-links-color); +.board-links, +.pagination { + color: var(--link-list-color); } .link-separator::after { @@ -432,8 +435,8 @@ summary { } .icon { - height: 1em; - vertical-align: text-top; + height: 0.8em; + vertical-align: middle; } .posts-omitted { diff --git a/static/themes/yotsuba-b.css b/static/themes/yotsuba-b.css index 6d85a7a..68efb52 100644 --- a/static/themes/yotsuba-b.css +++ b/static/themes/yotsuba-b.css @@ -8,7 +8,7 @@ --link-hover: #dd0000; --post-link-color: #34345c; --post-link-hover: #dd0000; - --board-links-color: #8899aa; + --link-list-color: #8899aa; --title-color: #af0a0f; --title-font: tahoma; --hr-color: #d3d3d3; @@ -18,8 +18,7 @@ /* Tables */ --table-head: #9988ee; --table-border: #000000; - --table-primary: #ffffff; - --table-secondary: #d6daf0; + --table-background: #ffffff; /* Forms */ --input-color: #ffffff; --input-border: #808080; diff --git a/static/themes/yotsuba.css b/static/themes/yotsuba.css index 180099e..9402a17 100644 --- a/static/themes/yotsuba.css +++ b/static/themes/yotsuba.css @@ -8,7 +8,7 @@ --link-hover: #dd0000; --post-link-color: #000080; --post-link-hover: #dd0000; - --board-links-color: #bb8866; + --link-list-color: #bb8866; --title-color: #af0a0f; --title-font: tahoma; --hr-color: #d9bfb7; @@ -18,8 +18,7 @@ /* Tables */ --table-head: #ffccaa; --table-border: #800000; - --table-primary: #ffffff; - --table-secondary: #ffffff; + --table-background: #ffffff; /* Forms */ --input-color: #ffffff; --input-border: #808080; diff --git a/templates/banned.html b/templates/banned.html index 85877e3..7218aa8 100644 --- a/templates/banned.html +++ b/templates/banned.html @@ -4,7 +4,7 @@ {% block content %}
-

Jsi trans, btw.

+

Zabanován!!!

@@ -20,7 +20,7 @@ z následujícího důvodu: -
+
{{ ban.reason }}
Udělil {{ ban.issued_by }} @@ -45,7 +45,7 @@ {% else %} Můžeš se pokusit svůj ban odvolat:
- +
Máš ban!
@@ -62,6 +62,6 @@ {% endif %} - +
Odvolání
{% endblock %} diff --git a/templates/base.html b/templates/base.html index 4c53d24..dda4fe6 100755 --- a/templates/base.html +++ b/templates/base.html @@ -7,7 +7,7 @@ {% block title %}{% endblock %} - + diff --git a/templates/board-catalog.html b/templates/board-catalog.html index 3c961ee..fad2d10 100755 --- a/templates/board-catalog.html +++ b/templates/board-catalog.html @@ -3,6 +3,7 @@ {% extends "base.html" %} +{% block theme %}{{ board.config.0.board_theme }}{% endblock %} {% block title %}Katalog (/{{ board.id }}/){% endblock %} {% block content %} diff --git a/templates/board.html b/templates/board.html index 3093752..9ba1611 100755 --- a/templates/board.html +++ b/templates/board.html @@ -5,6 +5,7 @@ {% extends "base.html" %} +{% block theme %}{{ board.config.0.board_theme }}{% endblock %} {% block title %}/{{ board.id }}/ - {{ board.name }}{% endblock %} {% block content %} diff --git a/templates/error.html b/templates/error.html index d2ae333..10caf3d 100755 --- a/templates/error.html +++ b/templates/error.html @@ -4,7 +4,7 @@ Chyba - + diff --git a/templates/macros/pagination.html b/templates/macros/pagination.html index 444b24b..54b6eda 100644 --- a/templates/macros/pagination.html +++ b/templates/macros/pagination.html @@ -1,15 +1,25 @@ {% macro pagination(base, pages, current) %} {% endmacro %} diff --git a/templates/macros/post-actions.html b/templates/macros/post-actions.html index 7ec4ea6..060aeca 100644 --- a/templates/macros/post-actions.html +++ b/templates/macros/post-actions.html @@ -35,7 +35,6 @@ type="text" autocomplete="new-password" value="{{ tcx.password }}" - required="" > @@ -53,7 +52,7 @@ - + {% endif %} + {% if tcx.perms.owner() || tcx.perms.reports() %} + + + + + {% endif %} {% if tcx.perms.owner() || tcx.perms.bans() %} @@ -123,6 +132,16 @@ + {% if tcx.perms.owner() || tcx.perms.reports() %} + + + + + {% endif %} - - + + diff --git a/templates/macros/post.html b/templates/macros/post.html index 0ae6d07..18ed97c 100644 --- a/templates/macros/post.html +++ b/templates/macros/post.html @@ -44,7 +44,7 @@ {% if file.spoiler %} [Spoiler] {% else %} - {{ file.original_name }} + {{ file.original_name|truncate(20) }} {% endif %}
diff --git a/templates/macros/staff-nav.html b/templates/macros/staff-nav.html index fede065..76e3cd5 100644 --- a/templates/macros/staff-nav.html +++ b/templates/macros/staff-nav.html @@ -1,26 +1,26 @@ {% macro staff_nav() %} {% endmacro %} diff --git a/templates/macros/static-pagination.html b/templates/macros/static-pagination.html new file mode 100644 index 0000000..2310c26 --- /dev/null +++ b/templates/macros/static-pagination.html @@ -0,0 +1,13 @@ +{% macro static_pagination(base, current) %} + +{% endmacro %} diff --git a/templates/overboard-catalog.html b/templates/overboard-catalog.html index 0dc8a36..6139abe 100755 --- a/templates/overboard-catalog.html +++ b/templates/overboard-catalog.html @@ -18,7 +18,7 @@
{% for thread in threads %} - {% call catalog_entry::catalog_entry(thread, loop.index, 15) %} + {% call catalog_entry::catalog_entry(thread, loop.index, crate::GENERIC_PAGE_SIZE) %} {% endfor %}

diff --git a/templates/staff/board-config.html b/templates/staff/board-config.html index 56ddda4..97a6bf7 100755 --- a/templates/staff/board-config.html +++ b/templates/staff/board-config.html @@ -109,6 +109,11 @@ + + + + +
Důvod hlášení
@@ -114,6 +113,16 @@
Odstranit hlášení +
+ +
+
Zabanovat uživatele
Zabanovat nahlašovatele +
+ +
+
Globální ban @@ -144,8 +163,8 @@
Délka banu (dny)Délka banu (dny, 0 = trvalý)
Rozsah banu
Motiv nástěnky
Vyžadovat obsah ve vlákně diff --git a/templates/staff/reports.html b/templates/staff/reports.html index 5f263c3..02fbc05 100755 --- a/templates/staff/reports.html +++ b/templates/staff/reports.html @@ -1,13 +1,38 @@ -{% import "../macros/pagination.html" as pagination %} +{% import "../macros/post-actions.html" as post_actions %} +{% import "../macros/post.html" as post %} {% import "../macros/staff-nav.html" as staff_nav %} +{% import "../macros/static-pagination.html" as static_pagination %} {% extends "base.html" %} {% block title %}Hlášení{% endblock %} {% block content %} -

Hlášení

+
+

Hlášení

+

>Ukliď to!!!

+
{% call staff_nav::staff_nav() %}
-

Dočasně nedostupné

+ + {% for post in posts %} + {% call post::post(boards[post.board.as_str()], post, true) %} + + + + + + {% for report in post.reports.0 %} + + + + + {% endfor %} +
IP adresaDůvod hlášení
{{ report.reporter_ip }} (){{ report.reason }}
+
+ {% endfor %} + {% call static_pagination::static_pagination("/staff/reports", page) %} +
+ {% call post_actions::post_actions() %} + {% endblock %} diff --git a/templates/thread.html b/templates/thread.html index 2275454..a9fdfed 100644 --- a/templates/thread.html +++ b/templates/thread.html @@ -4,6 +4,7 @@ {% extends "base.html" %} +{% block theme %}{{ board.config.0.board_theme }}{% endblock %} {% block title %}/{{ board.id }}/ - {{ thread.content_nomarkup|inline_post }}{% endblock %} {% block content %} diff --git a/theme.txt b/theme.txt new file mode 100644 index 0000000..78512c0 --- /dev/null +++ b/theme.txt @@ -0,0 +1 @@ +yotsuba.css \ No newline at end of file