From 1eeffc71c6ea4eaa409e72036d92b22448b48faf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sn=C3=ADda=C5=88ov=C3=BD=20Mistr?= Date: Fri, 15 Dec 2023 20:41:51 +0100 Subject: [PATCH] =?UTF-8?q?Upravov=C3=A1n=C3=AD=20p=C5=99=C3=ADsp=C4=9Bvk?= =?UTF-8?q?=C5=AF=20+=20=C3=BAklid?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/error.rs | 206 +++++++++--------- src/main.rs | 42 ++-- src/web/actions/edit_posts.rs | 55 +++++ src/web/actions/mod.rs | 3 +- src/web/board_catalog.rs | 2 +- src/web/edit_posts.rs | 41 ++++ src/web/mod.rs | 1 + src/web/overboard_catalog.rs | 2 +- src/web/staff/actions/update_board_config.rs | 2 +- static/style.css | 32 +-- ...{board_catalog.html => board-catalog.html} | 0 templates/edit-posts.html | 20 ++ ...rd_catalog.html => overboard-catalog.html} | 0 templates/staff/bans.html | 4 +- 14 files changed, 266 insertions(+), 144 deletions(-) create mode 100644 src/web/actions/edit_posts.rs create mode 100644 src/web/edit_posts.rs rename templates/{board_catalog.html => board-catalog.html} (100%) create mode 100644 templates/edit-posts.html rename templates/{overboard_catalog.html => overboard-catalog.html} (100%) diff --git a/src/error.rs b/src/error.rs index 878c4c5..da3b21f 100755 --- a/src/error.rs +++ b/src/error.rs @@ -4,85 +4,84 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum NekrochanError { - #[error("Chyba při zpracovávání souboru '{}': {}", .0, .1)] - FileError(String, &'static str), - #[error("Uživatelské jméno musí mít 1-32 znaků.")] - UsernameFormatError, - #[error("Heslo musí mít alespoň 8 znaků.")] - PasswordFormatError, - #[error("ID musí mít 1-16 znaků.")] - IdFormatError, - #[error("Jméno nástěnky musí mít 1-32 znaků.")] - BoardNameFormatError, - #[error("Popis musí mít 1-128 znaků.")] - DescriptionFormatError, - #[error("Jméno nesmí mít více než 32 znaků.")] - PostNameFormatError, - #[error("Capcode nesmí mít více než 32 znaků.")] - CapcodeFormatError, - #[error("E-mail nesmí mít více než 256 znaků.")] - EmailFormatError, - #[error("Obsah nesmí mít více než 10000 znaků.")] - ContentFormatError, - #[error("Nástěnka /{}/ neexistuje.", .0)] - BoardNotFound(String), #[error("Účet '{}' neexistuje.", .0)] AccountNotFound(String), - #[error("Příspěvek /{}/{} neexistuje.", .0, .1)] - PostNotFound(String, i32), + #[error("Tento ban už byl odvolán.")] + AlreadyAppealedError, #[error("Žádný takový ban pro tuto IP adresu neexistuje.")] BanNotFound, - #[error("Nedostatečná oprávnění.")] - InsufficientPermissionError, - #[error("Nesprávné přihlašovací údaje.")] - IncorrectCredentialError, - #[error("Neplatná strana.")] - InvalidPageError, - #[error("Neplatný autentizační token. Vymaž soubory cookie.")] - InvalidAuthError, - #[error("Pro přístup se musíš přihlásit.")] - NotLoggedInError, - #[error("Účet vlastníka nemůže být vymazán.")] - OwnerDeletionError, - #[error("Reverzní proxy nevrátilo vyžadovanou hlavičku '{}'.", .0)] - HeaderError(&'static str), #[error("Nástěnka /{}/ je uzamčená.", .0)] BoardLockError(String), - #[error("Toto vlákno je uzamčené.")] - ThreadLockError, - #[error("Nelze vytvořit odpověď na odpověď.")] - ReplyReplyError, - #[error("Vlákno dosáhlo limitu odpovědí.")] - ReplyLimitError, + #[error("Jméno nástěnky musí mít 1-32 znaků.")] + BoardNameFormatError, + #[error("Nástěnka /{}/ neexistuje.", .0)] + BoardNotFound(String), + #[error("Capcode nesmí mít více než 32 znaků.")] + CapcodeFormatError, + #[error("Obsah nesmí mít více než 10000 znaků.")] + ContentFormatError, + #[error("Popis musí mít 1-128 znaků.")] + DescriptionFormatError, + #[error("E-mail nesmí mít více než 256 znaků.")] + EmailFormatError, + #[error("Příspěvek musí mít obsah nebo soubor.")] + EmptyPostError, + #[error("Chyba při zpracovávání souboru '{}': {}", .0, .1)] + FileError(String, &'static str), + #[error("Maximální počet souborů na této nástěnce je {}.", .0)] + FileLimitError(usize), + #[error("Tvůj příspěvek vypadá jako spam.")] + FloodError, + #[error("Reverzní proxy nevrátilo vyžadovanou hlavičku '{}'.", .0)] + HeaderError(&'static str), + #[error("ID musí mít 1-16 znaků.")] + IdFormatError, + #[error("Nesprávné řešení CAPTCHA.")] + IncorrectCaptchaError, + #[error("Nesprávné přihlašovací údaje.")] + IncorrectCredentialError, + #[error("Nesprávné heslo pro příspěvek #{}.", .0)] + IncorrectPasswordError(i32), + #[error("Nedostatečná oprávnění.")] + InsufficientPermissionError, + #[error("Server se připojil k 41 procentům.")] + InternalError, + #[error("Neplatný autentizační token. Vymaž soubory cookie.")] + InvalidAuthError, + #[error("Neplatná strana.")] + InvalidPageError, #[error("Příspěvek musí mít obsah.")] NoContentError, #[error("Příspěvek musí mít soubor.")] NoFileError, - #[error("Příspěvek musí mít obsah nebo soubor.")] - EmptyPostError, - #[error("Na této nástěnce se musí vyplnit CAPTCHA.")] - RequiredCaptchaError, - #[error("Nesprávné řešení CAPTCHA.")] - IncorrectCaptchaError, - #[error("Tato CAPTCHA neexistuje nebo už byla vyřešena.")] - SolvedCaptchaError, #[error("Nebyly vybrány žádné příspěvky.")] NoPostsError, - #[error("Maximální počet souborů na této nástěnce je {}.", .0)] - FileLimitError(usize), - #[error("Nesprávné heslo pro příspěvek #{}.", .0)] - IncorrectPasswordError(i32), - #[error("Tento ban už byl odvolán.")] - AlreadyAppealedError, - #[error("Tento ban nelze odvolat.")] - UnappealableError, - #[error("Tvůj příspěvek vypadá jako spam.")] - FloodError, - // 500 + #[error("Pro přístup se musíš přihlásit.")] + NotLoggedInError, #[error("Nadnástěnka nebyla inicializována.")] OverboardError, - #[error("Server se připojil k 41 procentům.")] - InternalError, + #[error("Účet vlastníka nemůže být vymazán.")] + OwnerDeletionError, + #[error("Heslo musí mít alespoň 8 znaků.")] + PasswordFormatError, + #[error("Jméno nesmí mít více než 32 znaků.")] + PostNameFormatError, + #[error("Příspěvek /{}/{} neexistuje.", .0, .1)] + PostNotFound(String, i32), + #[error("Vlákno dosáhlo limitu odpovědí.")] + ReplyLimitError, + #[error("Nelze vytvořit odpověď na odpověď.")] + ReplyReplyError, + #[error("Na této nástěnce se musí vyplnit CAPTCHA.")] + RequiredCaptchaError, + #[error("Tato CAPTCHA neexistuje nebo už byla vyřešena.")] + SolvedCaptchaError, + #[error("Toto vlákno je uzamčené.")] + ThreadLockError, + #[error("Tento ban nelze odvolat.")] + UnappealableError, + #[error("Uživatelské jméno musí mít 1-32 znaků.")] + UsernameFormatError, } impl From for NekrochanError { @@ -201,46 +200,45 @@ impl From for NekrochanError { impl ResponseError for NekrochanError { fn status_code(&self) -> StatusCode { match self { - NekrochanError::FileError(_, _) - | NekrochanError::UsernameFormatError - | NekrochanError::PasswordFormatError - | NekrochanError::IdFormatError - | NekrochanError::BoardNameFormatError - | NekrochanError::DescriptionFormatError - | NekrochanError::PostNameFormatError - | NekrochanError::CapcodeFormatError - | NekrochanError::EmailFormatError - | NekrochanError::ContentFormatError - | NekrochanError::ReplyReplyError - | NekrochanError::NoContentError - | NekrochanError::NoFileError - | NekrochanError::EmptyPostError - | NekrochanError::RequiredCaptchaError - | NekrochanError::SolvedCaptchaError - | NekrochanError::NoPostsError - | NekrochanError::FileLimitError(_) - | NekrochanError::AlreadyAppealedError - | NekrochanError::UnappealableError => StatusCode::BAD_REQUEST, - NekrochanError::BoardNotFound(_) - | NekrochanError::AccountNotFound(_) - | NekrochanError::PostNotFound(_, _) - | NekrochanError::BanNotFound - | NekrochanError::InvalidPageError - | NekrochanError::InvalidAuthError => StatusCode::NOT_FOUND, - NekrochanError::InsufficientPermissionError - | NekrochanError::ReplyLimitError - | NekrochanError::ThreadLockError - | NekrochanError::BoardLockError(_) - | NekrochanError::OwnerDeletionError => StatusCode::FORBIDDEN, - NekrochanError::IncorrectCredentialError - | NekrochanError::NotLoggedInError - | NekrochanError::IncorrectCaptchaError - | NekrochanError::IncorrectPasswordError(_) => StatusCode::UNAUTHORIZED, - NekrochanError::HeaderError(_) => StatusCode::BAD_GATEWAY, + NekrochanError::AccountNotFound(_) => StatusCode::NOT_FOUND, + NekrochanError::AlreadyAppealedError => StatusCode::BAD_REQUEST, + NekrochanError::BanNotFound => StatusCode::NOT_FOUND, + NekrochanError::BoardLockError(_) => StatusCode::FORBIDDEN, + NekrochanError::BoardNameFormatError => StatusCode::BAD_REQUEST, + NekrochanError::BoardNotFound(_) => StatusCode::NOT_FOUND, + NekrochanError::CapcodeFormatError => StatusCode::BAD_REQUEST, + NekrochanError::ContentFormatError => StatusCode::BAD_REQUEST, + NekrochanError::DescriptionFormatError => StatusCode::BAD_REQUEST, + NekrochanError::EmailFormatError => StatusCode::BAD_REQUEST, + NekrochanError::EmptyPostError => StatusCode::BAD_REQUEST, + NekrochanError::FileError(_, _) => StatusCode::UNPROCESSABLE_ENTITY, + NekrochanError::FileLimitError(_) => StatusCode::BAD_REQUEST, NekrochanError::FloodError => StatusCode::TOO_MANY_REQUESTS, - NekrochanError::OverboardError | NekrochanError::InternalError => { - StatusCode::INTERNAL_SERVER_ERROR - } + NekrochanError::HeaderError(_) => StatusCode::BAD_GATEWAY, + NekrochanError::IdFormatError => StatusCode::BAD_REQUEST, + NekrochanError::IncorrectCaptchaError => StatusCode::UNAUTHORIZED, + NekrochanError::IncorrectCredentialError => StatusCode::UNAUTHORIZED, + NekrochanError::IncorrectPasswordError(_) => StatusCode::UNAUTHORIZED, + NekrochanError::InsufficientPermissionError => StatusCode::FORBIDDEN, + NekrochanError::InternalError => StatusCode::INTERNAL_SERVER_ERROR, + NekrochanError::InvalidAuthError => StatusCode::UNAUTHORIZED, + NekrochanError::InvalidPageError => StatusCode::BAD_REQUEST, + NekrochanError::NoContentError => StatusCode::BAD_REQUEST, + NekrochanError::NoFileError => StatusCode::BAD_REQUEST, + NekrochanError::NoPostsError => StatusCode::BAD_REQUEST, + NekrochanError::NotLoggedInError => StatusCode::UNAUTHORIZED, + NekrochanError::OverboardError => StatusCode::INTERNAL_SERVER_ERROR, + NekrochanError::OwnerDeletionError => StatusCode::FORBIDDEN, + NekrochanError::PasswordFormatError => StatusCode::BAD_REQUEST, + NekrochanError::PostNameFormatError => StatusCode::BAD_REQUEST, + NekrochanError::PostNotFound(_, _) => StatusCode::NOT_FOUND, + NekrochanError::ReplyLimitError => StatusCode::FORBIDDEN, + NekrochanError::ReplyReplyError => StatusCode::BAD_REQUEST, + NekrochanError::RequiredCaptchaError => StatusCode::UNAUTHORIZED, + NekrochanError::SolvedCaptchaError => StatusCode::BAD_REQUEST, + NekrochanError::ThreadLockError => StatusCode::FORBIDDEN, + NekrochanError::UnappealableError => StatusCode::BAD_REQUEST, + NekrochanError::UsernameFormatError => StatusCode::BAD_REQUEST, } } } diff --git a/src/main.rs b/src/main.rs index 2bdc35a..7b7693e 100755 --- a/src/main.rs +++ b/src/main.rs @@ -61,41 +61,43 @@ async fn run() -> Result<(), Error> { HttpServer::new(move || { App::new() .app_data(Data::new(ctx.clone())) - .service(web::index::index) .service(web::board::board) .service(web::board_catalog::board_catalog) - .service(web::overboard::overboard) - .service(web::overboard_catalog::overboard_catalog) - .service(web::thread::thread) - .service(web::actions::create_post::create_post) - .service(web::actions::user_post_actions::user_post_actions) - .service(web::actions::staff_post_actions::staff_post_actions) - .service(web::actions::report_posts::report_posts) - .service(web::actions::appeal_ban::appeal_ban) + .service(web::index::index) + .service(web::edit_posts::edit_posts) .service(web::login::login_get) .service(web::login::login_post) .service(web::logout::logout) + .service(web::overboard::overboard) + .service(web::overboard_catalog::overboard_catalog) + .service(web::thread::thread) + .service(web::actions::appeal_ban::appeal_ban) + .service(web::actions::create_post::create_post) + .service(web::actions::edit_posts::edit_posts) + .service(web::actions::report_posts::report_posts) + .service(web::actions::staff_post_actions::staff_post_actions) + .service(web::actions::user_post_actions::user_post_actions) .service(web::staff::account::account) .service(web::staff::accounts::accounts) - .service(web::staff::boards::boards) .service(web::staff::bans::bans) - .service(web::staff::reports::reports) - .service(web::staff::permissions::permissions) .service(web::staff::banners::banners) .service(web::staff::board_config::board_config) + .service(web::staff::boards::boards) + .service(web::staff::permissions::permissions) + .service(web::staff::reports::reports) + .service(web::staff::actions::add_banners::add_banners) .service(web::staff::actions::change_password::change_password) - .service(web::staff::actions::transfer_ownership::transfer_ownership) + .service(web::staff::actions::create_account::create_account) + .service(web::staff::actions::create_board::create_board) .service(web::staff::actions::delete_account::delete_account) .service(web::staff::actions::remove_accounts::remove_accounts) - .service(web::staff::actions::create_account::create_account) - .service(web::staff::actions::remove_boards::remove_boards) - .service(web::staff::actions::update_boards::update_boards) - .service(web::staff::actions::create_board::create_board) - .service(web::staff::actions::remove_bans::remove_bans) - .service(web::staff::actions::update_permissions::update_permissions) .service(web::staff::actions::remove_banners::remove_banners) - .service(web::staff::actions::add_banners::add_banners) + .service(web::staff::actions::remove_bans::remove_bans) + .service(web::staff::actions::remove_boards::remove_boards) + .service(web::staff::actions::transfer_ownership::transfer_ownership) .service(web::staff::actions::update_board_config::update_board_config) + .service(web::staff::actions::update_boards::update_boards) + .service(web::staff::actions::update_permissions::update_permissions) .service(debug) .service(favicon) .service(Files::new("/static", "./static")) diff --git a/src/web/actions/edit_posts.rs b/src/web/actions/edit_posts.rs new file mode 100644 index 0000000..d342ef5 --- /dev/null +++ b/src/web/actions/edit_posts.rs @@ -0,0 +1,55 @@ +use actix_web::{post, web::Data, HttpRequest, HttpResponse}; +use std::{collections::HashMap, fmt::Write}; + +use crate::{ + ctx::Ctx, + db::models::Post, + error::NekrochanError, + markup::markup, + qsform::QsForm, + web::{tcx::TemplateCtx, template_response}, +}; + +use super::{get_posts_from_ids, ActionTemplate}; + +#[post("/actions/edit-posts")] +pub async fn edit_posts( + ctx: Data, + req: HttpRequest, + QsForm(edits): QsForm>, +) -> Result { + let tcx = TemplateCtx::new(&ctx, &req).await?; + + if !(tcx.perms.owner() || tcx.perms.edit_posts()) { + return Err(NekrochanError::InsufficientPermissionError); + } + + let ids = edits.keys().map(|s| s.to_owned()).collect::>(); + + let posts = get_posts_from_ids(&ctx, ids) + .await + .into_iter() + .map(|post| (format!("{}/{}", post.board, post.id), post)) + .collect::>(); + + let mut response = String::new(); + let mut posts_edited = 0; + + for (key, content_nomarkup) in edits { + let post = &posts[&key]; + let content_nomarkup = content_nomarkup.trim(); + let content = markup(&ctx, &post.board, post.thread, content_nomarkup).await?; + + post.update_content(&ctx, content, content_nomarkup.into()) + .await?; + posts_edited += 1; + } + + if posts_edited != 0 { + writeln!(&mut response, "[Úspěch] Upraveny příspěvky: {posts_edited}").ok(); + } + + let template = ActionTemplate { tcx, response }; + + template_response(&template) +} diff --git a/src/web/actions/mod.rs b/src/web/actions/mod.rs index 66aa05c..8a98e18 100644 --- a/src/web/actions/mod.rs +++ b/src/web/actions/mod.rs @@ -3,8 +3,9 @@ use askama::Template; use super::tcx::TemplateCtx; use crate::{ctx::Ctx, db::models::Post}; -pub mod create_post; pub mod appeal_ban; +pub mod create_post; +pub mod edit_posts; pub mod report_posts; pub mod staff_post_actions; pub mod user_post_actions; diff --git a/src/web/board_catalog.rs b/src/web/board_catalog.rs index c901697..31b7584 100644 --- a/src/web/board_catalog.rs +++ b/src/web/board_catalog.rs @@ -14,7 +14,7 @@ use crate::{ }; #[derive(Template)] -#[template(path = "board_catalog.html")] +#[template(path = "board-catalog.html")] struct BoardCatalogTemplate { tcx: TemplateCtx, board: Board, diff --git a/src/web/edit_posts.rs b/src/web/edit_posts.rs new file mode 100644 index 0000000..9a5c320 --- /dev/null +++ b/src/web/edit_posts.rs @@ -0,0 +1,41 @@ +use actix_web::{post, web::Data, HttpRequest, HttpResponse}; +use askama::Template; +use serde::Deserialize; + +use crate::{ + ctx::Ctx, + db::models::Post, + error::NekrochanError, + qsform::QsForm, + web::{actions::get_posts_from_ids, template_response, TemplateCtx}, +}; + +#[derive(Deserialize)] +pub struct EditPostsForm { + pub posts: Vec, +} + +#[derive(Template)] +#[template(path = "edit-posts.html")] +struct EditPostsTemplate { + tcx: TemplateCtx, + posts: Vec, +} + +#[post("/edit-posts")] +pub async fn edit_posts( + ctx: Data, + req: HttpRequest, + QsForm(form): QsForm, +) -> Result { + let tcx = TemplateCtx::new(&ctx, &req).await?; + + if !(tcx.perms.owner() || tcx.perms.edit_posts()) { + return Err(NekrochanError::InsufficientPermissionError); + } + + let posts = get_posts_from_ids(&ctx, form.posts).await; + let template = EditPostsTemplate { tcx, posts }; + + template_response(&template) +} diff --git a/src/web/mod.rs b/src/web/mod.rs index 0d9341a..fe60e9a 100755 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -4,6 +4,7 @@ use askama::Template; pub mod actions; pub mod board; pub mod board_catalog; +pub mod edit_posts; pub mod index; pub mod login; pub mod logout; diff --git a/src/web/overboard_catalog.rs b/src/web/overboard_catalog.rs index 3cfde6e..afef01e 100644 --- a/src/web/overboard_catalog.rs +++ b/src/web/overboard_catalog.rs @@ -10,7 +10,7 @@ use crate::{ }; #[derive(Template)] -#[template(path = "overboard_catalog.html")] +#[template(path = "overboard-catalog.html")] struct OverboardCatalogTemplate { tcx: TemplateCtx, threads: Vec, diff --git a/src/web/staff/actions/update_board_config.rs b/src/web/staff/actions/update_board_config.rs index e8ad34f..7175bcb 100755 --- a/src/web/staff/actions/update_board_config.rs +++ b/src/web/staff/actions/update_board_config.rs @@ -86,7 +86,7 @@ pub async fn update_board_config( antispam, antispam_ip, antispam_content, - antispam_both + antispam_both, }; board.update_config(&ctx, config).await?; diff --git a/static/style.css b/static/style.css index e79a7ec..5b276ab 100755 --- a/static/style.css +++ b/static/style.css @@ -51,13 +51,18 @@ summary { padding: 0; } +.edit-post { + display: block; + width: 100%; +} + .form-table input[type="text"], .form-table input[type="password"], .form-table input[type="number"], .form-table textarea, .form-table select, -.input-wrapper { - /* FUCK IE7 */ +.input-wrapper, +.edit-post { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; @@ -76,7 +81,8 @@ summary { margin: 0 auto; } -.form-table textarea { +.form-table textarea, +.edit-post { height: 8rem; resize: none; } @@ -274,24 +280,24 @@ summary { } .catalog { - text-align: center; + text-align: center; } .catalog-entry { - display: inline-block; - width: 200px; - height: 250px; - overflow: scroll; - margin: 4px; - padding: 8px; + display: inline-block; + width: 200px; + height: 250px; + overflow: scroll; + margin: 4px; + padding: 8px; } .catalog-entry .thumb { display: block; - max-width: 100%; - max-height: 50%; + max-width: 100%; + max-height: 50%; box-shadow: 0 0 3px #000; - margin: 4px auto; + margin: 4px auto; padding: 2px; } diff --git a/templates/board_catalog.html b/templates/board-catalog.html similarity index 100% rename from templates/board_catalog.html rename to templates/board-catalog.html diff --git a/templates/edit-posts.html b/templates/edit-posts.html new file mode 100644 index 0000000..ef95c7e --- /dev/null +++ b/templates/edit-posts.html @@ -0,0 +1,20 @@ +{% extends "base.html" %} + +{% block title %}Upravit příspěvky{% endblock %} + +{% block content %} +
+

Upravit příspěvky

+
+
+ {% for post in posts %} +
+ Příspěvek #{{ post.id }} na /{{ post.board }}/ + +
+
+ {% endfor %} + +
+
+{% endblock content %} diff --git a/templates/overboard_catalog.html b/templates/overboard-catalog.html similarity index 100% rename from templates/overboard_catalog.html rename to templates/overboard-catalog.html diff --git a/templates/staff/bans.html b/templates/staff/bans.html index 1f45038..2878e13 100755 --- a/templates/staff/bans.html +++ b/templates/staff/bans.html @@ -39,9 +39,7 @@ - {% if tcx.perms.owner() || tcx.perms.bans() %} - - {% endif %} +
{% endblock %}