Systém hlášení a vizuální změny

Tento commit je obsažen v:
sneedmaster 2024-02-17 22:24:39 +01:00
rodič 72fd48d11e
revize a453130797
39 změnil soubory, kde provedl 511 přidání a 216 odebrání

217
Cargo.lock vygenerováno
Zobrazit soubor

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

Zobrazit soubor

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

Zobrazit soubor

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

Zobrazit soubor

@ -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<Vec<Self>, NekrochanError> {
pub async fn read_reports_page(ctx: &Ctx, page: i64) -> Result<Vec<Self>, NekrochanError> {
let posts = query_as(
r#"SELECT * FROM overboard
WHERE reports != '[]'::jsonb
ORDER BY jsonb_array_length(reports), reported DESC"#,
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<Vec<Self>, 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)

Zobrazit soubor

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

Zobrazit soubor

@ -26,7 +26,7 @@ pub async fn edit_posts(
let ids = edits.keys().map(|s| s.to_owned()).collect::<Vec<String>>();
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))

Zobrazit soubor

@ -17,7 +17,7 @@ pub struct ActionTemplate {
pub response: String,
}
pub async fn get_posts_from_ids(ctx: &Ctx, ids: Vec<String>) -> Vec<Post> {
pub async fn get_posts_from_ids(ctx: &Ctx, ids: &Vec<String>) -> Vec<Post> {
let mut posts = Vec::new();
for id in ids {

Zobrazit soubor

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

Zobrazit soubor

@ -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<String>,
pub toggle_sticky: Option<String>,
pub toggle_lock: Option<String>,
pub remove_reports: Option<String>,
pub ban_user: Option<String>,
pub ban_reporters: Option<String>,
pub global_ban: Option<String>,
pub unappealable_ban: Option<String>,
pub ban_reason: Option<String>,
pub ban_duration: Option<i64>,
pub ban_duration: Option<u64>,
pub ban_range: Option<String>,
}
@ -44,7 +46,7 @@ pub async fn staff_post_actions(
) -> Result<HttpResponse, NekrochanError> {
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,49 +125,41 @@ 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();
let expires = if ban_duration == 0 {
None
} else {
Some(Utc::now() + Duration::days(ban_duration))
};
Ban::create(&ctx, account, board, ip_range, reason, appealable, expires).await?;
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
@ -166,9 +171,10 @@ pub async fn staff_post_actions(
);
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(())
}

Zobrazit soubor

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

Zobrazit soubor

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

Zobrazit soubor

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

Zobrazit soubor

@ -19,6 +19,11 @@ struct AccountsTemplate {
#[get("/staff/accounts")]
pub async fn accounts(ctx: Data<Ctx>, req: HttpRequest) -> Result<HttpResponse, NekrochanError> {
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 };

Zobrazit soubor

@ -20,6 +20,7 @@ pub struct UpdateBoardConfigForm {
flags: Option<String>,
thread_captcha: String,
reply_captcha: String,
board_theme: String,
require_thread_content: Option<String>,
require_thread_file: Option<String>,
require_reply_content: Option<String>,
@ -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,

Zobrazit soubor

@ -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<Ctx>, req: HttpRequest) -> Result<HttpResponse, NekrochanError> {
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);
}

Zobrazit soubor

@ -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<Ctx>, req: HttpRequest) -> Result<HttpResponse, NekrochanError> {
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);
}

Zobrazit soubor

@ -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<String>,
) -> Result<HttpResponse, NekrochanError> {
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);
}

Zobrazit soubor

@ -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<Ctx>, req: HttpRequest) -> Result<HttpResponse, NekrochanError> {
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);
}

Zobrazit soubor

@ -18,6 +18,11 @@ struct NewsTemplate {
#[get("/staff/news")]
pub async fn news(ctx: Data<Ctx>, req: HttpRequest) -> Result<HttpResponse, NekrochanError> {
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 };

Zobrazit soubor

@ -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<String>,
) -> Result<HttpResponse, NekrochanError> {
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())

Zobrazit soubor

@ -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<String, Board>,
posts: Vec<Post>,
page: i64,
}
#[get("/staff/reports")]
async fn reports(ctx: Data<Ctx>, req: HttpRequest) -> Result<HttpResponse, NekrochanError> {
async fn reports(
ctx: Data<Ctx>,
req: HttpRequest,
query: Option<Query<BoardQuery>>,
) -> Result<HttpResponse, NekrochanError> {
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)
}

Zobrazit soubor

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

Zobrazit soubor

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

Zobrazit soubor

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

Zobrazit soubor

@ -4,7 +4,7 @@
{% block content %}
<div class="container">
<h1 class="title">Jsi trans, btw.</h1>
<h1 class="title">Zabanován!!!</h1>
<table class="data-table">
<tr><th>Máš ban!</th></tr>
<tr>
@ -20,7 +20,7 @@
&#32;z následujícího důvodu:
</b>
<div class="post-content">
<div class="post-content box">
{{ ban.reason }}
</div>
<span>Udělil <i>{{ ban.issued_by }}</i></span>
@ -45,7 +45,7 @@
{% else %}
<b>Můžeš se pokusit svůj ban odvolat:</b>
<form method="post" action="/actions/appeal-ban">
<input name="id" type="hidden" value="{{ ban.id }}" />
<input name="id" type="hidden" value="{{ ban.id }}">
<table class="form-table">
<tr>
<td class="label">Odvolání</td>
@ -62,6 +62,6 @@
{% endif %}
</td>
</tr>
</div>
</table>
</div>
{% endblock %}

Zobrazit soubor

@ -7,7 +7,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{% endblock %}</title>
<meta name="description" content="{{ tcx.cfg.site.description }}">
<link rel="stylesheet" href="/static/themes/yotsuba.css">
<link rel="stylesheet" href='/static/themes/{% block theme %}{% include "../theme.txt" %}{% endblock %}'>
<link rel="stylesheet" href="/static/style.css">
<!-- Muh flash of unstyled content -->
<script>0</script>

Zobrazit soubor

@ -3,6 +3,7 @@
{% extends "base.html" %}
{% block theme %}{{ board.config.0.board_theme }}{% endblock %}
{% block title %}Katalog (/{{ board.id }}/){% endblock %}
{% block content %}

Zobrazit soubor

@ -5,6 +5,7 @@
{% extends "base.html" %}
{% block theme %}{{ board.config.0.board_theme }}{% endblock %}
{% block title %}/{{ board.id }}/ - {{ board.name }}{% endblock %}
{% block content %}

Zobrazit soubor

@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chyba</title>
<link rel="stylesheet" href="/static/themes/yotsuba.css">
<link rel="stylesheet" href='/static/themes/{% include "../theme.txt" %}'>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>

Zobrazit soubor

@ -1,15 +1,25 @@
{% macro pagination(base, pages, current) %}
<div class="box inline-block pagination">
{% if pages == 0 %}
<b><a href="{{ base }}?page=1">[1]</a></b>&#32;
{% if current == 1 %}
[Předchozí]
{% else %}
[<a href="{{ base }}?page={{ current - 1 }}">Předchozí</a>]
{% endif %}
&#32;
{% for page in 1..(pages + 1) %}
{% if page == current %}
<b><a href="{{ base }}?page={{ page }}">[{{ page }}]</a></b>&#32;
[<b><a href="{{ base }}?page={{ page }}">{{ page }}</a></b>]
{% else %}
<a href="{{ base }}?page={{ page }}">[{{ page }}]</a>&#32;
[<a href="{{ base }}?page={{ page }}">{{ page }}</a>]
{% endif %}
{% endfor %}
&#32;
{% if current == pages %}
[Další]
{% else %}
[<a href="{{ base }}?page={{ current + 1 }}">Další</a>]
{% endif %}
</div>
{% endmacro %}

Zobrazit soubor

@ -35,7 +35,6 @@
type="text"
autocomplete="new-password"
value="{{ tcx.password }}"
required=""
>
</td>
</tr>
@ -53,7 +52,7 @@
<table class="form-table">
<tr>
<td class="label">Důvod hlášení</td>
<td><textarea name="report_reason"></textarea></td>
<td><input name="report_reason" type="text"></td>
</tr>
<tr>
<td colspan="2">
@ -114,6 +113,16 @@
</td>
</tr>
{% endif %}
{% if tcx.perms.owner() || tcx.perms.reports() %}
<tr>
<td class="label">Odstranit hlášení</td>
<td>
<div class="input-wrapper">
<input name="remove_reports" type="checkbox">
</div>
</td>
</tr>
{% endif %}
{% if tcx.perms.owner() || tcx.perms.bans() %}
<tr>
<td class="label">Zabanovat uživatele</td>
@ -123,6 +132,16 @@
</div>
</td>
</tr>
{% if tcx.perms.owner() || tcx.perms.reports() %}
<tr>
<td class="label">Zabanovat nahlašovatele</td>
<td>
<div class="input-wrapper">
<input name="ban_reporters" type="checkbox">
</div>
</td>
</tr>
{% endif %}
<tr>
<td class="label">Globální ban</td>
<td>
@ -144,8 +163,8 @@
<td><textarea name="ban_reason"></textarea></td>
</tr>
<tr>
<td class="label">Délka banu (dny)</td>
<td><input name="ban_duration" type="number" min="0" placeholder="0 = trvalý ban"></td>
<td class="label">Délka banu <span class="small">(dny, 0 = trvalý)</span></td>
<td><input name="ban_duration" type="number" min="0" value="0"></td>
</tr>
<tr>
<td class="label">Rozsah banu</td>

Zobrazit soubor

@ -44,7 +44,7 @@
{% if file.spoiler %}
[Spoiler]
{% else %}
{{ file.original_name }}
{{ file.original_name|truncate(20) }}
{% endif %}
</a>
<br>

Zobrazit soubor

@ -1,26 +1,26 @@
{% macro staff_nav() %}
<div class="box inline-block pagination">
<a href="/staff/account">[Účet]</a>&#32;
<a href="/staff/accounts">[Účty]</a>&#32;
[<a href="/staff/account">Účet</a>]&#32;
[<a href="/staff/accounts">Účty</a>]&#32;
{% if tcx.perms.owner() || tcx.perms.board_config() || tcx.perms.banners() %}
<a href="/staff/boards">[Nástěnky]</a>&#32;
[<a href="/staff/boards">Nástěnky</a>]&#32;
{% endif %}
{% if tcx.perms.owner() || tcx.perms.bans() %}
<a href="/staff/bans">[Bany]</a>&#32;
[<a href="/staff/bans">Bany</a>]&#32;
{% endif %}
{% if tcx.perms.owner() || tcx.perms.banners() %}
<a href="/staff/banners">[Bannery]</a>&#32;
[<a href="/staff/banners">Bannery</a>]&#32;
{% endif %}
{% if tcx.perms.owner() || tcx.perms.reports() %}
<a href="/staff/reports">[Hlášení]</a>&#32;
[<a href="/staff/reports">Hlášení</a>]&#32;
{% endif %}
{% if tcx.perms.owner() || tcx.perms.news() %}
<a href="/staff/news">[Novinky]</a>&#32;
[<a href="/staff/news">Novinky</a>]&#32;
{% endif %}
</div>
{% endmacro %}

Zobrazit soubor

@ -0,0 +1,13 @@
{% macro static_pagination(base, current) %}
<div class="box inline-block pagination">
{% if current == 1 %}
[Předchozí]
{% else %}
[<a href="{{ base }}?page={{ current - 1 }}">Předchozí</a>]
{% endif %}
&#32;
[<b><a href="{{ base }}?page={{ current }}">{{ current }}</a></b>]&#32;
[<a href="{{ base }}?page={{ current + 1 }}">Další</a>]
</div>
{% endmacro %}

Zobrazit soubor

@ -18,7 +18,7 @@
<form method="post">
<div class="center">
{% 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 %}
</div>
<hr>

Zobrazit soubor

@ -109,6 +109,11 @@
</td>
</tr>
<tr>
<td class="label">Motiv nástěnky</td>
<td><input name="board_theme" type="text" value="{{ board.config.0.board_theme }}" required=""></td>
</tr>
<tr>
<td class="label">Vyžadovat obsah ve vlákně</td>
<td>

Zobrazit soubor

@ -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 %}
<div class="center">
<h1 class="title">Hlášení</h1>
<p class="description greentext">&gt;Ukliď to!!!</p>
</div>
{% call staff_nav::staff_nav() %}
<hr>
<h1>Dočasně nedostupné</h1>
<form method="post">
{% for post in posts %}
{% call post::post(boards[post.board.as_str()], post, true) %}
<table class="data-table inline-block m-0">
<tr>
<th>IP adresa</th>
<th>Důvod hlášení</th>
</tr>
{% for report in post.reports.0 %}
<tr>
<td><b>{{ report.reporter_ip }}</b> (<img class="icon" src="/static/flags/{{ report.reporter_country }}.png">)</td>
<td>{{ report.reason }}</td>
</tr>
{% endfor %}
</table>
<hr>
{% endfor %}
{% call static_pagination::static_pagination("/staff/reports", page) %}
<hr>
{% call post_actions::post_actions() %}
</form>
{% endblock %}

Zobrazit soubor

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

1
theme.txt Normální soubor
Zobrazit soubor

@ -0,0 +1 @@
yotsuba.css