841 lines
22 KiB
Rust
841 lines
22 KiB
Rust
use crate::{
|
|
error::{Error, OptionExt},
|
|
extensions::SubmissionExt,
|
|
middleware::UserProfile,
|
|
nav::NavState,
|
|
views::OwnedProfileView,
|
|
ActixLoader, State,
|
|
};
|
|
use actix_web::{web, HttpResponse, Scope};
|
|
use hyaenidae_profiles::store::{Comment, Profile};
|
|
use hyaenidae_toolkit::TextInput;
|
|
use i18n_embed_fl::fl;
|
|
use uuid::Uuid;
|
|
|
|
mod node;
|
|
|
|
pub(crate) use node::{Cache, CommentNode, Item};
|
|
|
|
pub(crate) fn scope() -> Scope {
|
|
web::scope("/comments").service(
|
|
web::scope("/{comment_id}")
|
|
.route("", web::get().to(view_comment))
|
|
.service(
|
|
web::resource("/edit")
|
|
.route(web::get().to(edit_page))
|
|
.route(web::post().to(update_comment)),
|
|
)
|
|
.service(
|
|
web::resource("/reply")
|
|
.route(web::get().to(route_to_comment_page))
|
|
.route(web::post().to(reply)),
|
|
)
|
|
.service(
|
|
web::resource("/report")
|
|
.route(web::get().to(report_page))
|
|
.route(web::post().to(report)),
|
|
)
|
|
.route("/report-success", web::get().to(report_success_page)),
|
|
)
|
|
}
|
|
|
|
fn to_comment_page(comment_id: Uuid) -> HttpResponse {
|
|
crate::redirect(&format!("/comments/{}", comment_id))
|
|
}
|
|
|
|
fn to_report_success_page(comment_id: Uuid) -> HttpResponse {
|
|
crate::redirect(&format!("/comments/{}/report-success", comment_id))
|
|
}
|
|
|
|
pub struct CommentView {
|
|
pub(crate) cache: Cache,
|
|
pub(crate) submission: Uuid,
|
|
pub(crate) comments: CommentNode,
|
|
pub(crate) logged_in: bool,
|
|
input_value: Option<String>,
|
|
input_error: Option<String>,
|
|
}
|
|
|
|
impl CommentView {
|
|
fn new(cache: Cache, submission: Uuid, comments: CommentNode, logged_in: bool) -> Self {
|
|
CommentView {
|
|
cache,
|
|
submission,
|
|
comments,
|
|
logged_in,
|
|
input_value: None,
|
|
input_error: None,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn author(&self) -> OwnedProfileView {
|
|
let profile = self
|
|
.cache
|
|
.profiles
|
|
.get(&self.comments.author_id)
|
|
.unwrap()
|
|
.clone();
|
|
let icon = profile
|
|
.icon()
|
|
.and_then(|i| self.cache.files.get(&i))
|
|
.map(|f| f.clone());
|
|
let banner = profile
|
|
.banner()
|
|
.and_then(|b| self.cache.files.get(&b))
|
|
.map(|b| b.clone());
|
|
|
|
OwnedProfileView {
|
|
profile,
|
|
icon,
|
|
banner,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn parent(&self) -> CommentNode {
|
|
let comment_id = match self.comments.item {
|
|
Item::Comment(id) => id,
|
|
_ => unimplemented!(),
|
|
};
|
|
|
|
let comment = self.cache.comments.get(&comment_id).unwrap();
|
|
|
|
if let Some(reply_to_id) = comment.comment_id() {
|
|
let comment = self.cache.comments.get(&reply_to_id).unwrap();
|
|
|
|
CommentNode {
|
|
item: Item::Comment(comment.id()),
|
|
author_id: comment.profile_id(),
|
|
is_self: self.comments.author_id == comment.profile_id(),
|
|
children: vec![],
|
|
}
|
|
} else {
|
|
let submission = self
|
|
.cache
|
|
.submissions
|
|
.get(&comment.submission_id())
|
|
.unwrap();
|
|
|
|
CommentNode {
|
|
item: Item::Submission(submission.id()),
|
|
author_id: submission.profile_id(),
|
|
is_self: self.comments.author_id == submission.profile_id(),
|
|
children: vec![],
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn submission_path(&self) -> String {
|
|
let submission = self.cache.submissions.get(&self.submission).unwrap();
|
|
|
|
submission.view_path()
|
|
}
|
|
|
|
pub(crate) fn input(&self, loader: &ActixLoader) -> TextInput {
|
|
let input = TextInput::new("body")
|
|
.placeholder(&fl!(loader, "reply-placeholder"))
|
|
.textarea()
|
|
.error_opt(self.input_error.clone());
|
|
|
|
if let Some(value) = &self.input_value {
|
|
input.value(value)
|
|
} else {
|
|
input
|
|
}
|
|
}
|
|
|
|
fn value(mut self, value: &str) -> Self {
|
|
self.input_value = Some(value.to_owned());
|
|
self
|
|
}
|
|
|
|
fn error_opt(mut self, opt: Option<String>) -> Self {
|
|
self.input_error = opt;
|
|
self
|
|
}
|
|
}
|
|
|
|
fn can_view_logged_out(
|
|
comment: &Comment,
|
|
store: &hyaenidae_profiles::State,
|
|
) -> Result<bool, Error> {
|
|
let submission = match store.store.submissions.by_id(comment.submission_id())? {
|
|
Some(submission) => submission,
|
|
None => return Ok(false),
|
|
};
|
|
|
|
if submission.is_followers_only() {
|
|
return Ok(false);
|
|
}
|
|
|
|
let submissioner = match store.store.profiles.by_id(submission.profile_id())? {
|
|
Some(s) => s,
|
|
None => return Ok(false),
|
|
};
|
|
|
|
if submissioner.login_required() {
|
|
return Ok(false);
|
|
}
|
|
|
|
can_view_comment_logged_out(comment, store)
|
|
}
|
|
|
|
fn can_view_comment_logged_out(
|
|
comment: &Comment,
|
|
store: &hyaenidae_profiles::State,
|
|
) -> Result<bool, Error> {
|
|
if can_view_comment_logged_out_no_recurse(comment, store)? {
|
|
if let Some(reply_to_id) = comment.comment_id() {
|
|
let new_comment = match store.store.comments.by_id(reply_to_id)? {
|
|
Some(comment) => comment,
|
|
None => return Ok(false),
|
|
};
|
|
|
|
return can_view_logged_out(&new_comment, store);
|
|
}
|
|
|
|
return Ok(true);
|
|
}
|
|
|
|
Ok(false)
|
|
}
|
|
|
|
fn can_view_comment_logged_out_no_recurse(
|
|
comment: &Comment,
|
|
store: &hyaenidae_profiles::State,
|
|
) -> Result<bool, Error> {
|
|
let commenter = match store.store.profiles.by_id(comment.profile_id())? {
|
|
Some(c) => c,
|
|
None => return Ok(false),
|
|
};
|
|
|
|
if commenter.login_required() {
|
|
return Ok(false);
|
|
}
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
fn can_view(
|
|
profile: &Profile,
|
|
comment: &Comment,
|
|
store: &hyaenidae_profiles::State,
|
|
) -> Result<bool, Error> {
|
|
let submission = match store.store.submissions.by_id(comment.submission_id())? {
|
|
Some(s) => s,
|
|
None => return Ok(false),
|
|
};
|
|
|
|
let submissioner_id = submission.profile_id();
|
|
|
|
let blocking_submissioner = store
|
|
.store
|
|
.view
|
|
.blocks
|
|
.by_forward(submissioner_id, profile.id())?
|
|
.is_some();
|
|
|
|
if blocking_submissioner {
|
|
return Ok(false);
|
|
}
|
|
|
|
let blocked_by_submissioner = store
|
|
.store
|
|
.view
|
|
.blocks
|
|
.by_forward(profile.id(), submissioner_id)?
|
|
.is_some();
|
|
|
|
if blocked_by_submissioner {
|
|
return Ok(false);
|
|
}
|
|
|
|
if submission.is_followers_only() {
|
|
let is_submissioner = profile.id() == submissioner_id;
|
|
|
|
if !is_submissioner {
|
|
let follows_submissioner = store
|
|
.store
|
|
.view
|
|
.follows
|
|
.by_forward(submissioner_id, profile.id())?
|
|
.is_some();
|
|
|
|
if !follows_submissioner {
|
|
return Ok(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
can_view_comment(profile, comment, store)
|
|
}
|
|
|
|
fn can_view_comment(
|
|
profile: &Profile,
|
|
comment: &Comment,
|
|
store: &hyaenidae_profiles::State,
|
|
) -> Result<bool, Error> {
|
|
if can_view_comment_no_recurse(profile.id(), comment, store)? {
|
|
if let Some(reply_to_id) = comment.comment_id() {
|
|
let new_comment = match store.store.comments.by_id(reply_to_id)? {
|
|
Some(c) => c,
|
|
None => return Ok(false),
|
|
};
|
|
|
|
return can_view_comment(profile, &new_comment, store);
|
|
}
|
|
|
|
return Ok(true);
|
|
}
|
|
|
|
Ok(false)
|
|
}
|
|
|
|
fn can_view_comment_no_recurse(
|
|
profile_id: Uuid,
|
|
comment: &Comment,
|
|
store: &hyaenidae_profiles::State,
|
|
) -> Result<bool, Error> {
|
|
let blocking_commenter = store
|
|
.store
|
|
.view
|
|
.blocks
|
|
.by_forward(comment.profile_id(), profile_id)?
|
|
.is_some();
|
|
|
|
if blocking_commenter {
|
|
return Ok(false);
|
|
}
|
|
|
|
let blocked_by_commenter = store
|
|
.store
|
|
.view
|
|
.blocks
|
|
.by_forward(profile_id, comment.profile_id())?
|
|
.is_some();
|
|
|
|
if blocked_by_commenter {
|
|
return Ok(false);
|
|
}
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
async fn prepare_view(
|
|
comment: Comment,
|
|
profile: Option<&Profile>,
|
|
state: &State,
|
|
) -> Result<Option<CommentView>, Error> {
|
|
match profile {
|
|
Some(profile) if !can_view(&profile, &comment, &state.profiles)? => {
|
|
return Ok(None);
|
|
}
|
|
None if !can_view_logged_out(&comment, &state.profiles)? => {
|
|
return Ok(None);
|
|
}
|
|
_ => (),
|
|
}
|
|
|
|
let mut cache = Cache::new();
|
|
|
|
let store = state.profiles.clone();
|
|
let submission_id = comment.submission_id();
|
|
let reply_to_id = comment.comment_id();
|
|
let author_id = comment.profile_id();
|
|
let (cache, author) = web::block(move || {
|
|
let submission = store.store.submissions.by_id(submission_id)?.req()?;
|
|
|
|
let submission_author_id = submission.profile_id();
|
|
|
|
cache.submissions.insert(submission.id(), submission);
|
|
|
|
if let Some(comment_id) = reply_to_id {
|
|
let comment = store.store.comments.by_id(comment_id)?.req()?;
|
|
let author = store.store.profiles.by_id(comment.profile_id())?.req()?;
|
|
|
|
cache.comments.insert(comment.id(), comment);
|
|
cache.profiles.insert(author.id(), author);
|
|
} else {
|
|
let author = store.store.profiles.by_id(submission_author_id)?.req()?;
|
|
|
|
cache.profiles.insert(author.id(), author);
|
|
}
|
|
|
|
let author = store.store.profiles.by_id(author_id)?.req()?;
|
|
|
|
Ok((cache, author)) as Result<_, Error>
|
|
})
|
|
.await?;
|
|
|
|
let submission = comment.submission_id();
|
|
|
|
let (node, cache) = match CommentNode::from_root(
|
|
comment,
|
|
author,
|
|
profile.as_ref().map(|p| p.id()),
|
|
cache,
|
|
&state,
|
|
)
|
|
.await
|
|
{
|
|
Ok(node) => node,
|
|
_ => return Ok(None),
|
|
};
|
|
|
|
let view = CommentView::new(cache, submission, node, profile.is_some());
|
|
|
|
Ok(Some(view))
|
|
}
|
|
|
|
async fn edit_page(
|
|
loader: ActixLoader,
|
|
comment_id: web::Path<Uuid>,
|
|
profile: UserProfile,
|
|
nav_state: NavState,
|
|
state: web::Data<State>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let profile = profile.0;
|
|
|
|
let comment = match state
|
|
.profiles
|
|
.store
|
|
.comments
|
|
.by_id(comment_id.into_inner())?
|
|
{
|
|
Some(comment) => comment,
|
|
None => return Ok(crate::to_404()),
|
|
};
|
|
|
|
if comment.deleted() {
|
|
return Ok(crate::to_404());
|
|
}
|
|
|
|
if comment.profile_id() != profile.id() {
|
|
return Ok(crate::to_404());
|
|
}
|
|
|
|
let body = comment.body_source().unwrap_or(comment.body()).to_owned();
|
|
let view = match prepare_view(comment, Some(&profile), &state).await? {
|
|
Some(v) => v.value(&body),
|
|
None => return Ok(crate::to_404()),
|
|
};
|
|
|
|
crate::rendered(HttpResponse::Ok(), |cursor| {
|
|
crate::templates::comments::edit(cursor, &loader, &view, &nav_state)
|
|
})
|
|
}
|
|
|
|
async fn update_comment(
|
|
loader: ActixLoader,
|
|
comment_id: web::Path<Uuid>,
|
|
form: web::Form<CommentForm>,
|
|
profile: UserProfile,
|
|
nav_state: NavState,
|
|
state: web::Data<State>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let profile = profile.0;
|
|
|
|
use hyaenidae_profiles::apub::actions::UpdateComment;
|
|
let comment = match state
|
|
.profiles
|
|
.store
|
|
.comments
|
|
.by_id(comment_id.into_inner())?
|
|
{
|
|
Some(comment) => comment,
|
|
None => return Ok(crate::to_404()),
|
|
};
|
|
|
|
if comment.deleted() {
|
|
return Ok(crate::to_404());
|
|
}
|
|
|
|
if comment.profile_id() != profile.id() {
|
|
return Ok(crate::to_404());
|
|
}
|
|
|
|
let form = form.into_inner();
|
|
|
|
let error = if form.body.trim().is_empty() {
|
|
"Must be present".to_owned()
|
|
} else if form.body.len() > MAX_COMMENT_LEN {
|
|
format!("Must be shorter than {} characters", MAX_COMMENT_LEN)
|
|
} else {
|
|
let res = state
|
|
.profiles
|
|
.run(UpdateComment::from_text(
|
|
comment.id(),
|
|
form.body.trim().to_owned(),
|
|
))
|
|
.await;
|
|
|
|
match res {
|
|
Ok(_) => return Ok(to_comment_page(comment.id())),
|
|
Err(e) => e.to_string(),
|
|
}
|
|
};
|
|
|
|
let body = comment.body_source().unwrap_or(comment.body()).to_owned();
|
|
let view = match prepare_view(comment, Some(&profile), &state).await? {
|
|
Some(v) => v.value(&body).error_opt(Some(error)),
|
|
None => return Ok(crate::to_404()),
|
|
};
|
|
|
|
crate::rendered(HttpResponse::BadRequest(), |cursor| {
|
|
crate::templates::comments::edit(cursor, &loader, &view, &nav_state)
|
|
})
|
|
}
|
|
|
|
async fn view_comment(
|
|
loader: ActixLoader,
|
|
comment_id: web::Path<Uuid>,
|
|
profile: Option<UserProfile>,
|
|
nav_state: NavState,
|
|
state: web::Data<State>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let profile = profile.map(|p| p.0);
|
|
|
|
let comment = match state
|
|
.profiles
|
|
.store
|
|
.comments
|
|
.by_id(comment_id.into_inner())?
|
|
{
|
|
Some(comment) => comment,
|
|
None => return Ok(crate::to_404()),
|
|
};
|
|
|
|
if comment.deleted() {
|
|
return Ok(crate::to_404());
|
|
}
|
|
|
|
let view = match prepare_view(comment, profile.as_ref(), &state).await? {
|
|
Some(v) => v,
|
|
None => return Ok(crate::to_404()),
|
|
};
|
|
|
|
crate::rendered(HttpResponse::Ok(), |cursor| {
|
|
crate::templates::comments::public(cursor, &loader, &view, &nav_state)
|
|
})
|
|
}
|
|
|
|
async fn route_to_comment_page(comment_id: web::Path<Uuid>) -> HttpResponse {
|
|
to_comment_page(comment_id.into_inner())
|
|
}
|
|
|
|
#[derive(Clone, Debug, serde::Deserialize)]
|
|
struct CommentForm {
|
|
body: String,
|
|
}
|
|
|
|
const MAX_COMMENT_LEN: usize = 500;
|
|
|
|
async fn reply(
|
|
loader: ActixLoader,
|
|
form: web::Form<CommentForm>,
|
|
comment_id: web::Path<Uuid>,
|
|
profile: UserProfile,
|
|
nav_state: NavState,
|
|
state: web::Data<State>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let profile = profile.0;
|
|
|
|
use hyaenidae_profiles::apub::actions::CreateComment;
|
|
let comment = match state
|
|
.profiles
|
|
.store
|
|
.comments
|
|
.by_id(comment_id.into_inner())?
|
|
{
|
|
Some(comment) => comment,
|
|
None => return Ok(crate::to_404()),
|
|
};
|
|
|
|
if !can_view(&profile, &comment, &state.profiles)? {
|
|
return Ok(crate::to_404());
|
|
}
|
|
|
|
let form = form.into_inner();
|
|
|
|
let error = if form.body.trim().is_empty() {
|
|
"Must be present".to_owned()
|
|
} else if form.body.len() > MAX_COMMENT_LEN {
|
|
format!("Must be shorter than {} characters", MAX_COMMENT_LEN)
|
|
} else {
|
|
let res = state
|
|
.profiles
|
|
.run(CreateComment::from_text(
|
|
comment.submission_id(),
|
|
profile.id(),
|
|
Some(comment.id()),
|
|
form.body.trim().to_owned(),
|
|
))
|
|
.await;
|
|
|
|
match res {
|
|
Ok(_) => return Ok(to_comment_page(comment.id())),
|
|
Err(e) => e.to_string(),
|
|
}
|
|
};
|
|
|
|
let view = match prepare_view(comment, Some(&profile), &state).await? {
|
|
Some(v) => v.error_opt(Some(error)),
|
|
None => return Ok(crate::to_404()),
|
|
};
|
|
|
|
crate::rendered(HttpResponse::BadRequest(), |cursor| {
|
|
crate::templates::comments::public(cursor, &loader, &view, &nav_state)
|
|
})
|
|
}
|
|
|
|
pub struct ReportView {
|
|
pub(crate) cache: Cache,
|
|
pub(crate) comment: Comment,
|
|
pub(crate) author: Profile,
|
|
input_value: Option<String>,
|
|
input_error: Option<String>,
|
|
}
|
|
|
|
impl ReportView {
|
|
async fn prepare(comment: Comment, state: &State) -> Result<Self, Error> {
|
|
let store = state.profiles.clone();
|
|
|
|
let view = web::block(move || {
|
|
let mut cache = Cache::new();
|
|
|
|
let author = store.store.profiles.by_id(comment.profile_id())?.req()?;
|
|
|
|
if let Some(file_id) = author.icon() {
|
|
if !cache.files.contains_key(&file_id) {
|
|
let file = store.store.files.by_id(file_id)?.req()?;
|
|
cache.files.insert(file.id(), file);
|
|
}
|
|
}
|
|
|
|
if let Some(comment_id) = comment.comment_id() {
|
|
let parent_comment = store.store.comments.by_id(comment_id)?.req()?;
|
|
let parent_author = store
|
|
.store
|
|
.profiles
|
|
.by_id(parent_comment.profile_id())?
|
|
.req()?;
|
|
|
|
cache.comments.insert(parent_comment.id(), parent_comment);
|
|
cache.profiles.insert(parent_author.id(), parent_author);
|
|
} else {
|
|
let parent_submission = store
|
|
.store
|
|
.submissions
|
|
.by_id(comment.submission_id())?
|
|
.req()?;
|
|
let parent_author = store
|
|
.store
|
|
.profiles
|
|
.by_id(parent_submission.profile_id())?
|
|
.req()?;
|
|
|
|
cache
|
|
.submissions
|
|
.insert(parent_submission.id(), parent_submission);
|
|
cache.profiles.insert(parent_author.id(), parent_author);
|
|
}
|
|
|
|
Ok(ReportView {
|
|
cache,
|
|
comment,
|
|
author,
|
|
input_value: None,
|
|
input_error: None,
|
|
}) as Result<_, Error>
|
|
})
|
|
.await?;
|
|
|
|
Ok(view)
|
|
}
|
|
|
|
pub(crate) fn author(&self) -> OwnedProfileView {
|
|
OwnedProfileView {
|
|
profile: self.author.clone(),
|
|
icon: self
|
|
.author
|
|
.icon()
|
|
.and_then(|icon| self.cache.files.get(&icon))
|
|
.map(|icon| icon.clone()),
|
|
banner: self
|
|
.author
|
|
.banner()
|
|
.and_then(|banner| self.cache.files.get(&banner))
|
|
.map(|banner| banner.clone()),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn parent(&self) -> CommentNode {
|
|
if let Some(reply_to_id) = self.comment.comment_id() {
|
|
let comment = self.cache.comments.get(&reply_to_id).unwrap();
|
|
|
|
CommentNode {
|
|
item: Item::Comment(comment.id()),
|
|
author_id: comment.profile_id(),
|
|
is_self: self.comment.profile_id() == comment.profile_id(),
|
|
children: vec![],
|
|
}
|
|
} else {
|
|
let submission = self
|
|
.cache
|
|
.submissions
|
|
.get(&self.comment.submission_id())
|
|
.unwrap();
|
|
|
|
CommentNode {
|
|
item: Item::Submission(submission.id()),
|
|
author_id: submission.profile_id(),
|
|
is_self: self.comment.profile_id() == submission.profile_id(),
|
|
children: vec![],
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn input(&self, loader: &ActixLoader) -> TextInput {
|
|
let input = TextInput::new("body")
|
|
.title(&fl!(loader, "report-input"))
|
|
.placeholder(&fl!(loader, "report-placeholder"))
|
|
.textarea()
|
|
.error_opt(self.input_error.clone());
|
|
|
|
if let Some(value) = &self.input_value {
|
|
input.value(value)
|
|
} else {
|
|
input
|
|
}
|
|
}
|
|
|
|
fn error_opt(mut self, opt: Option<String>) -> Self {
|
|
self.input_error = opt;
|
|
self
|
|
}
|
|
|
|
fn value_opt(mut self, opt: Option<String>) -> Self {
|
|
self.input_value = opt;
|
|
self
|
|
}
|
|
}
|
|
|
|
async fn report_page(
|
|
loader: ActixLoader,
|
|
comment_id: web::Path<Uuid>,
|
|
profile: UserProfile,
|
|
nav_state: NavState,
|
|
state: web::Data<State>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let profile = profile.0;
|
|
|
|
let comment = match state
|
|
.profiles
|
|
.store
|
|
.comments
|
|
.by_id(comment_id.into_inner())?
|
|
{
|
|
Some(comment) => comment,
|
|
None => return Ok(crate::to_404()),
|
|
};
|
|
|
|
if !can_view(&profile, &comment, &state.profiles)? {
|
|
return Ok(crate::to_404());
|
|
}
|
|
|
|
let view = ReportView::prepare(comment, &state).await?;
|
|
|
|
crate::rendered(HttpResponse::Ok(), |cursor| {
|
|
crate::templates::comments::report(cursor, &loader, &view, &nav_state)
|
|
})
|
|
}
|
|
|
|
#[derive(Clone, Debug, serde::Deserialize)]
|
|
struct ReportForm {
|
|
body: String,
|
|
}
|
|
|
|
const MAX_REPORT_LEN: usize = 1000;
|
|
|
|
async fn report(
|
|
loader: ActixLoader,
|
|
comment_id: web::Path<Uuid>,
|
|
form: web::Form<ReportForm>,
|
|
profile: UserProfile,
|
|
nav_state: NavState,
|
|
state: web::Data<State>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let profile = profile.0;
|
|
|
|
use hyaenidae_profiles::apub::actions::CreateReport;
|
|
|
|
let comment_id = comment_id.into_inner();
|
|
|
|
let comment = match state.profiles.store.comments.by_id(comment_id)? {
|
|
Some(comment) => comment,
|
|
None => return Ok(crate::to_404()),
|
|
};
|
|
|
|
if !can_view(&profile, &comment, &state.profiles)? {
|
|
return Ok(crate::to_404());
|
|
}
|
|
|
|
let form = form.into_inner();
|
|
|
|
let error = if form.body.trim().len() > MAX_REPORT_LEN {
|
|
format!("Must be fewer than {} characters", MAX_REPORT_LEN)
|
|
} else {
|
|
let res = state
|
|
.profiles
|
|
.run(CreateReport::from_comment(
|
|
comment_id,
|
|
profile.id(),
|
|
Some(form.body.clone()),
|
|
))
|
|
.await;
|
|
|
|
match res {
|
|
Ok(_) => return Ok(to_report_success_page(comment_id)),
|
|
Err(e) => e.to_string(),
|
|
}
|
|
};
|
|
|
|
let view = ReportView::prepare(comment, &state)
|
|
.await?
|
|
.error_opt(Some(error))
|
|
.value_opt(Some(form.body));
|
|
|
|
crate::rendered(HttpResponse::Ok(), |cursor| {
|
|
crate::templates::comments::report(cursor, &loader, &view, &nav_state)
|
|
})
|
|
}
|
|
|
|
async fn report_success_page(
|
|
loader: ActixLoader,
|
|
comment_id: web::Path<Uuid>,
|
|
profile: UserProfile,
|
|
nav_state: NavState,
|
|
state: web::Data<State>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let profile = profile.0;
|
|
|
|
let comment = match state
|
|
.profiles
|
|
.store
|
|
.comments
|
|
.by_id(comment_id.into_inner())?
|
|
{
|
|
Some(comment) => comment,
|
|
None => return Ok(crate::to_404()),
|
|
};
|
|
|
|
if !can_view(&profile, &comment, &state.profiles)? {
|
|
return Ok(crate::to_404());
|
|
}
|
|
|
|
let view = ReportView::prepare(comment, &state).await?;
|
|
|
|
crate::rendered(HttpResponse::Ok(), |cursor| {
|
|
crate::templates::comments::report_success(cursor, &loader, &view, &nav_state)
|
|
})
|
|
}
|