Upravování příspěvků + úklid

Tento commit je obsažen v:
sneedmaster 2023-12-15 20:41:51 +01:00
rodič eb33ad9a19
revize 1eeffc71c6
14 změnil soubory, kde provedl 266 přidání a 144 odebrání

Zobrazit soubor

@ -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<askama::Error> for NekrochanError {
@ -201,46 +200,45 @@ impl From<tokio::task::JoinError> 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,
}
}
}

Zobrazit soubor

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

55
src/web/actions/edit_posts.rs Normální soubor
Zobrazit soubor

@ -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<Ctx>,
req: HttpRequest,
QsForm(edits): QsForm<HashMap<String, String>>,
) -> Result<HttpResponse, NekrochanError> {
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::<Vec<String>>();
let posts = get_posts_from_ids(&ctx, ids)
.await
.into_iter()
.map(|post| (format!("{}/{}", post.board, post.id), post))
.collect::<HashMap<String, Post>>();
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)
}

Zobrazit soubor

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

Zobrazit soubor

@ -14,7 +14,7 @@ use crate::{
};
#[derive(Template)]
#[template(path = "board_catalog.html")]
#[template(path = "board-catalog.html")]
struct BoardCatalogTemplate {
tcx: TemplateCtx,
board: Board,

41
src/web/edit_posts.rs Normální soubor
Zobrazit soubor

@ -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<String>,
}
#[derive(Template)]
#[template(path = "edit-posts.html")]
struct EditPostsTemplate {
tcx: TemplateCtx,
posts: Vec<Post>,
}
#[post("/edit-posts")]
pub async fn edit_posts(
ctx: Data<Ctx>,
req: HttpRequest,
QsForm(form): QsForm<EditPostsForm>,
) -> Result<HttpResponse, NekrochanError> {
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)
}

Zobrazit soubor

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

Zobrazit soubor

@ -10,7 +10,7 @@ use crate::{
};
#[derive(Template)]
#[template(path = "overboard_catalog.html")]
#[template(path = "overboard-catalog.html")]
struct OverboardCatalogTemplate {
tcx: TemplateCtx,
threads: Vec<Post>,

Zobrazit soubor

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

Zobrazit soubor

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

20
templates/edit-posts.html Normální soubor
Zobrazit soubor

@ -0,0 +1,20 @@
{% extends "base.html" %}
{% block title %}Upravit příspěvky{% endblock %}
{% block content %}
<div class="container">
<h1 class="title">Upravit příspěvky</h1>
<hr>
<form method="post" action="/actions/edit-posts">
{% for post in posts %}
<div class="box">
<b>Příspěvek #{{ post.id }} na /{{ post.board }}/</b>
<textarea class="edit-post" name="{{ post.board }}/{{ post.id }}">{{ post.content_nomarkup }}</textarea>
</div>
<hr>
{% endfor %}
<input class="button" type="submit" value="Upravit">
</form>
</div>
{% endblock content %}

Zobrazit soubor

@ -39,9 +39,7 @@
</table>
</div>
{% if tcx.perms.owner() || tcx.perms.bans() %}
<input class="button" type="submit" value="Odstranit vybrané">
{% endif %}
</form>
<hr>
{% endblock %}