2018-09-10 20:38:19 +02:00
use chrono ::Utc ;
2021-01-02 21:49:45 +01:00
use rocket ::http ::uri ::Uri ;
2018-12-06 18:54:16 +01:00
use rocket ::request ::LenientForm ;
2019-03-20 17:56:17 +01:00
use rocket ::response ::{ Flash , Redirect } ;
2018-12-06 18:54:16 +01:00
use rocket_i18n ::I18n ;
2019-01-05 22:30:28 +01:00
use std ::{
2019-03-20 17:56:17 +01:00
borrow ::Cow ,
2019-01-05 22:30:28 +01:00
collections ::{ HashMap , HashSet } ,
2019-03-20 17:56:17 +01:00
time ::Duration ,
2019-01-05 22:30:28 +01:00
} ;
2018-07-06 11:51:19 +02:00
use validator ::{ Validate , ValidationError , ValidationErrors } ;
2018-04-23 16:25:39 +02:00
2020-01-21 07:02:03 +01:00
use crate ::routes ::{
comments ::NewCommentForm , errors ::ErrorPage , ContentLen , RemoteForm , RespondOrRedirect ,
} ;
use crate ::template_utils ::{ IntoContext , Ructe } ;
2022-01-11 20:18:13 +01:00
use crate ::utils ::requires_login ;
2022-05-02 19:14:16 +02:00
use plume_common ::activity_pub ::{ broadcast , ActivityStream , ApRequest , LicensedArticle } ;
2022-01-11 20:18:13 +01:00
use plume_common ::utils ::md_to_html ;
2018-06-23 18:36:11 +02:00
use plume_models ::{
2018-05-19 09:39:59 +02:00
blogs ::* ,
2018-12-24 11:23:04 +01:00
comments ::{ Comment , CommentTree } ,
2021-01-30 13:44:29 +01:00
db_conn ::DbConn ,
2019-04-17 19:31:47 +02:00
inbox ::inbox ,
2018-07-27 20:31:47 +02:00
instance ::Instance ,
2018-10-30 21:04:59 +01:00
medias ::Media ,
2018-06-20 22:58:11 +02:00
mentions ::Mention ,
2018-05-19 09:39:59 +02:00
post_authors ::* ,
posts ::* ,
2018-06-23 18:36:11 +02:00
safe_string ::SafeString ,
2018-09-05 22:18:27 +02:00
tags ::* ,
2019-10-07 19:08:20 +02:00
timeline ::* ,
2019-03-20 17:56:17 +01:00
users ::User ,
2021-01-11 21:27:52 +01:00
Error , PlumeRocket , CONFIG ,
2018-05-19 09:39:59 +02:00
} ;
2018-06-27 00:19:18 +02:00
2018-12-06 18:54:16 +01:00
#[ get( " /~/<blog>/<slug>?<responding_to> " , rank = 4) ]
2019-03-20 17:56:17 +01:00
pub fn details (
blog : String ,
slug : String ,
responding_to : Option < i32 > ,
2021-01-30 13:44:29 +01:00
conn : DbConn ,
2019-04-17 19:31:47 +02:00
rockets : PlumeRocket ,
2019-03-20 17:56:17 +01:00
) -> Result < Ructe , ErrorPage > {
2019-04-17 19:31:47 +02:00
let user = rockets . user . clone ( ) ;
2021-01-30 13:44:29 +01:00
let blog = Blog ::find_by_fqn ( & conn , & blog ) ? ;
let post = Post ::find_by_slug ( & conn , & slug , blog . id ) ? ;
2019-06-14 09:33:30 +02:00
if ! ( post . published
2019-03-20 17:56:17 +01:00
| | post
2021-01-30 13:44:29 +01:00
. get_authors ( & conn ) ?
2019-03-20 17:56:17 +01:00
. into_iter ( )
2019-06-14 09:33:30 +02:00
. any ( | a | a . id = = user . clone ( ) . map ( | u | u . id ) . unwrap_or ( 0 ) ) )
2019-03-20 17:56:17 +01:00
{
2019-06-14 09:33:30 +02:00
return Ok ( render! ( errors ::not_authorized (
2021-01-30 13:44:29 +01:00
& ( & conn , & rockets ) . to_context ( ) ,
2019-06-14 09:33:30 +02:00
i18n! ( rockets . intl . catalog , " This post isn't published yet. " )
) ) ) ;
}
2018-12-06 18:54:16 +01:00
2021-01-30 13:44:29 +01:00
let comments = CommentTree ::from_post ( & conn , & post , user . as_ref ( ) ) ? ;
2018-12-06 18:54:16 +01:00
2021-01-30 13:44:29 +01:00
let previous = responding_to . and_then ( | r | Comment ::get ( & conn , r ) . ok ( ) ) ;
2019-06-14 09:33:30 +02:00
Ok ( render! ( posts ::details (
2021-01-30 13:44:29 +01:00
& ( & conn , & rockets ) . to_context ( ) ,
2018-12-06 18:54:16 +01:00
post . clone ( ) ,
blog ,
& NewCommentForm {
warning : previous . clone ( ) . map ( | p | p . spoiler_text ) . unwrap_or_default ( ) ,
2018-12-29 09:36:07 +01:00
content : previous . clone ( ) . and_then ( | p | Some ( format! (
2018-12-06 18:54:16 +01:00
" @{} {} " ,
2021-01-30 13:44:29 +01:00
p . get_author ( & conn ) . ok ( ) ? . fqn ,
Mention ::list_for_comment ( & conn , p . id ) . ok ( ) ?
2018-12-06 18:54:16 +01:00
. into_iter ( )
. filter_map ( | m | {
let user = user . clone ( ) ;
2021-01-30 13:44:29 +01:00
if let Ok ( mentioned ) = m . get_mentioned ( & conn ) {
2018-12-06 18:54:16 +01:00
if user . is_none ( ) | | mentioned . id ! = user . expect ( " posts::details_response: user error while listing mentions " ) . id {
2019-03-06 18:28:10 +01:00
Some ( format! ( " @ {} " , mentioned . fqn ) )
2018-12-06 18:54:16 +01:00
} else {
None
}
} else {
None
}
} ) . collect ::< Vec < String > > ( ) . join ( " " ) )
2018-12-29 09:36:07 +01:00
) ) . unwrap_or_default ( ) ,
2018-12-06 18:54:16 +01:00
.. NewCommentForm ::default ( )
} ,
ValidationErrors ::default ( ) ,
2021-01-30 13:44:29 +01:00
Tag ::for_post ( & conn , post . id ) ? ,
2018-12-24 11:23:04 +01:00
comments ,
2018-12-06 18:54:16 +01:00
previous ,
2021-01-30 13:44:29 +01:00
post . count_likes ( & conn ) ? ,
post . count_reshares ( & conn ) ? ,
user . clone ( ) . and_then ( | u | u . has_liked ( & conn , & post ) . ok ( ) ) . unwrap_or ( false ) ,
user . clone ( ) . and_then ( | u | u . has_reshared ( & conn , & post ) . ok ( ) ) . unwrap_or ( false ) ,
user . and_then ( | u | u . is_following ( & conn , post . get_authors ( & conn ) . ok ( ) ? [ 0 ] . id ) . ok ( ) ) . unwrap_or ( false ) ,
post . get_authors ( & conn ) ? [ 0 ] . clone ( )
2018-12-06 18:54:16 +01:00
) ) )
2018-04-23 16:25:39 +02:00
}
2018-07-11 17:30:01 +02:00
#[ get( " /~/<blog>/<slug> " , rank = 3) ]
2019-03-20 17:56:17 +01:00
pub fn activity_details (
blog : String ,
slug : String ,
_ap : ApRequest ,
2021-01-30 13:44:29 +01:00
conn : DbConn ,
2022-05-02 19:14:16 +02:00
) -> Result < ActivityStream < LicensedArticle > , Option < String > > {
2021-01-30 13:44:29 +01:00
let blog = Blog ::find_by_fqn ( & conn , & blog ) . map_err ( | _ | None ) ? ;
let post = Post ::find_by_slug ( & conn , & slug , blog . id ) . map_err ( | _ | None ) ? ;
2018-09-12 17:58:38 +02:00
if post . published {
2019-03-20 17:56:17 +01:00
Ok ( ActivityStream ::new (
2022-05-02 18:26:15 +02:00
post . to_activity ( & conn )
2019-03-20 17:56:17 +01:00
. map_err ( | _ | String ::from ( " Post serialization error " ) ) ? ,
) )
2018-09-12 17:58:38 +02:00
} else {
2018-10-20 11:04:20 +02:00
Err ( Some ( String ::from ( " Not published yet. " ) ) )
2018-09-12 17:58:38 +02:00
}
2018-04-23 16:25:39 +02:00
}
2018-06-04 21:57:03 +02:00
#[ get( " /~/<blog>/new " , rank = 2) ]
2018-12-06 18:54:16 +01:00
pub fn new_auth ( blog : String , i18n : I18n ) -> Flash < Redirect > {
2022-01-11 20:18:13 +01:00
requires_login (
2019-03-20 17:56:17 +01:00
& i18n! (
i18n . catalog ,
2019-04-01 20:09:29 +02:00
" To write a new post, you need to be logged in "
2019-03-20 17:56:17 +01:00
) ,
uri! ( new : blog = blog ) ,
2018-09-08 01:11:27 +02:00
)
2018-04-23 16:25:39 +02:00
}
2018-06-19 21:16:18 +02:00
#[ get( " /~/<blog>/new " , rank = 1) ]
2021-01-30 13:44:29 +01:00
pub fn new (
blog : String ,
cl : ContentLen ,
conn : DbConn ,
rockets : PlumeRocket ,
) -> Result < Ructe , ErrorPage > {
let b = Blog ::find_by_fqn ( & conn , & blog ) ? ;
2019-04-30 12:04:25 +02:00
let user = rockets . user . clone ( ) . unwrap ( ) ;
2018-06-20 10:44:56 +02:00
2021-01-30 13:44:29 +01:00
if ! user . is_author_in ( & conn , & b ) ? {
2018-12-06 18:54:16 +01:00
// TODO actually return 403 error code
2019-03-19 14:37:56 +01:00
return Ok ( render! ( errors ::not_authorized (
2021-01-30 13:44:29 +01:00
& ( & conn , & rockets ) . to_context ( ) ,
2019-04-30 12:04:25 +02:00
i18n! ( rockets . intl . catalog , " You are not an author of this blog. " )
2019-03-20 17:56:17 +01:00
) ) ) ;
2018-06-20 10:44:56 +02:00
}
2019-03-19 14:37:56 +01:00
2021-01-30 13:44:29 +01:00
let medias = Media ::for_user ( & conn , user . id ) ? ;
2019-03-19 14:37:56 +01:00
Ok ( render! ( posts ::new (
2021-01-30 13:44:29 +01:00
& ( & conn , & rockets ) . to_context ( ) ,
2019-04-30 12:04:25 +02:00
i18n! ( rockets . intl . catalog , " New post " ) ,
2019-03-19 14:37:56 +01:00
b ,
false ,
& NewPostForm {
2019-05-10 22:59:34 +02:00
license : Instance ::get_local ( ) ? . default_license ,
2019-03-19 14:37:56 +01:00
.. NewPostForm ::default ( )
} ,
true ,
None ,
ValidationErrors ::default ( ) ,
medias ,
cl . 0
) ) )
2018-05-04 13:09:08 +02:00
}
2018-09-06 23:39:22 +02:00
#[ get( " /~/<blog>/<slug>/edit " ) ]
2019-03-20 17:56:17 +01:00
pub fn edit (
blog : String ,
slug : String ,
cl : ContentLen ,
2021-01-30 13:44:29 +01:00
conn : DbConn ,
2019-03-20 17:56:17 +01:00
rockets : PlumeRocket ,
) -> Result < Ructe , ErrorPage > {
2019-04-17 19:31:47 +02:00
let intl = & rockets . intl . catalog ;
2021-01-30 13:44:29 +01:00
let b = Blog ::find_by_fqn ( & conn , & blog ) ? ;
let post = Post ::find_by_slug ( & conn , & slug , b . id ) ? ;
2019-04-30 12:04:25 +02:00
let user = rockets . user . clone ( ) . unwrap ( ) ;
2018-09-06 23:39:22 +02:00
2021-01-30 13:44:29 +01:00
if ! user . is_author_in ( & conn , & b ) ? {
2019-03-19 14:37:56 +01:00
return Ok ( render! ( errors ::not_authorized (
2021-01-30 13:44:29 +01:00
& ( & conn , & rockets ) . to_context ( ) ,
2019-04-17 19:31:47 +02:00
i18n! ( intl , " You are not an author of this blog. " )
2019-03-20 17:56:17 +01:00
) ) ) ;
2019-03-19 14:37:56 +01:00
}
let source = if ! post . source . is_empty ( ) {
post . source . clone ( )
2018-09-06 23:39:22 +02:00
} else {
2019-03-19 14:37:56 +01:00
post . content . get ( ) . clone ( ) // fallback to HTML if the markdown was not stored
} ;
2018-09-08 13:05:22 +02:00
2021-01-30 13:44:29 +01:00
let medias = Media ::for_user ( & conn , user . id ) ? ;
2019-03-19 14:37:56 +01:00
let title = post . title . clone ( ) ;
Ok ( render! ( posts ::new (
2021-01-30 13:44:29 +01:00
& ( & conn , & rockets ) . to_context ( ) ,
2019-04-17 19:31:47 +02:00
i18n! ( intl , " Edit {0} " ; & title ) ,
2019-03-19 14:37:56 +01:00
b ,
true ,
& NewPostForm {
title : post . title . clone ( ) ,
subtitle : post . subtitle . clone ( ) ,
content : source ,
2021-01-30 13:44:29 +01:00
tags : Tag ::for_post ( & conn , post . id ) ?
2019-03-19 14:37:56 +01:00
. into_iter ( )
2019-03-20 17:56:17 +01:00
. filter_map ( | t | if ! t . is_hashtag { Some ( t . tag ) } else { None } )
2019-03-19 14:37:56 +01:00
. collect ::< Vec < String > > ( )
. join ( " , " ) ,
license : post . license . clone ( ) ,
draft : true ,
cover : post . cover_id ,
} ,
! post . published ,
Some ( post ) ,
ValidationErrors ::default ( ) ,
medias ,
cl . 0
) ) )
2018-09-06 23:39:22 +02:00
}
2018-12-06 18:54:16 +01:00
#[ post( " /~/<blog>/<slug>/edit " , data = " <form> " ) ]
2019-03-20 17:56:17 +01:00
pub fn update (
blog : String ,
slug : String ,
cl : ContentLen ,
form : LenientForm < NewPostForm > ,
2021-01-30 13:44:29 +01:00
conn : DbConn ,
2019-03-20 17:56:17 +01:00
rockets : PlumeRocket ,
2019-06-14 09:33:30 +02:00
) -> RespondOrRedirect {
2021-01-30 13:44:29 +01:00
let b = Blog ::find_by_fqn ( & conn , & blog ) . expect ( " post::update: blog error " ) ;
2019-03-20 17:56:17 +01:00
let mut post =
2021-01-30 13:44:29 +01:00
Post ::find_by_slug ( & conn , & slug , b . id ) . expect ( " post::update: find by slug error " ) ;
2019-04-17 19:31:47 +02:00
let user = rockets . user . clone ( ) . unwrap ( ) ;
let intl = & rockets . intl . catalog ;
2018-09-06 23:39:22 +02:00
2018-10-11 14:23:23 +02:00
let new_slug = if ! post . published {
2021-04-10 09:32:58 +02:00
Post ::slug ( & form . title ) . to_string ( )
2018-10-11 14:23:23 +02:00
} else {
2018-12-07 12:05:01 +01:00
post . slug . clone ( )
2018-10-11 14:23:23 +02:00
} ;
2018-09-06 23:39:22 +02:00
let mut errors = match form . validate ( ) {
Ok ( _ ) = > ValidationErrors ::new ( ) ,
2019-03-20 17:56:17 +01:00
Err ( e ) = > e ,
2018-09-06 23:39:22 +02:00
} ;
2018-09-07 19:51:53 +02:00
2021-01-30 13:44:29 +01:00
if new_slug ! = slug & & Post ::find_by_slug ( & conn , & new_slug , b . id ) . is_ok ( ) {
2019-03-20 17:56:17 +01:00
errors . add (
" title " ,
ValidationError {
code : Cow ::from ( " existing_slug " ) ,
message : Some ( Cow ::from ( " A post with the same title already exists. " ) ) ,
params : HashMap ::new ( ) ,
} ,
) ;
2018-09-06 23:39:22 +02:00
}
if errors . is_empty ( ) {
2019-03-20 17:56:17 +01:00
if ! user
2021-01-30 13:44:29 +01:00
. is_author_in ( & conn , & b )
2019-03-20 17:56:17 +01:00
. expect ( " posts::update: is author in error " )
{
2018-09-06 23:39:22 +02:00
// actually it's not "Ok"…
2019-06-14 09:33:30 +02:00
Flash ::error (
2019-04-30 12:04:25 +02:00
Redirect ::to ( uri! ( super ::blogs ::details : name = blog , page = _ ) ) ,
i18n! ( & intl , " You are not allowed to publish on this blog. " ) ,
2019-06-14 09:33:30 +02:00
)
. into ( )
2018-09-06 23:39:22 +02:00
} else {
2022-01-11 20:18:13 +01:00
let ( content , mentions , hashtags ) = md_to_html (
2019-03-20 17:56:17 +01:00
form . content . to_string ( ) . as_ref ( ) ,
2019-05-04 17:33:50 +02:00
Some (
2019-05-10 22:59:34 +02:00
& Instance ::get_local ( )
2019-05-04 17:33:50 +02:00
. expect ( " posts::update: Error getting local instance " )
. public_domain ,
) ,
2019-03-22 19:51:36 +01:00
false ,
2019-04-06 19:20:33 +02:00
Some ( Media ::get_media_processor (
& conn ,
b . list_authors ( & conn )
. expect ( " Could not get author list " )
. iter ( )
. collect ( ) ,
) ) ,
2019-03-20 17:56:17 +01:00
) ;
2018-09-06 23:39:22 +02:00
2018-09-10 20:38:19 +02:00
// update publication date if when this article is no longer a draft
2018-10-28 11:42:01 +01:00
let newly_published = if ! post . published & & ! form . draft {
2018-09-10 20:38:19 +02:00
post . published = true ;
post . creation_date = Utc ::now ( ) . naive_utc ( ) ;
2021-04-09 03:55:09 +02:00
post . ap_url = Post ::ap_url ( post . get_blog ( & conn ) . unwrap ( ) , & new_slug ) ;
2018-10-28 11:42:01 +01:00
true
} else {
false
} ;
2018-09-10 20:38:19 +02:00
2018-09-06 23:39:22 +02:00
post . slug = new_slug . clone ( ) ;
post . title = form . title . clone ( ) ;
post . subtitle = form . subtitle . clone ( ) ;
post . content = SafeString ::new ( & content ) ;
post . source = form . content . clone ( ) ;
2018-12-09 18:43:34 +01:00
post . license = form . license . clone ( ) ;
2018-10-30 21:04:59 +01:00
post . cover_id = form . cover ;
2021-01-30 13:44:29 +01:00
post . update ( & conn ) . expect ( " post::update: update error " ) ;
2018-09-06 23:39:22 +02:00
2018-09-12 18:00:00 +02:00
if post . published {
2022-05-02 19:11:46 +02:00
post . update_mentions (
2019-03-20 17:56:17 +01:00
& conn ,
mentions
. into_iter ( )
2022-05-02 18:26:15 +02:00
. filter_map ( | m | Mention ::build_activity ( & conn , & m ) . ok ( ) )
2019-03-20 17:56:17 +01:00
. collect ( ) ,
)
2020-01-19 12:52:32 +01:00
. expect ( " post::update: mentions error " ) ;
2018-09-06 23:39:22 +02:00
}
2019-03-20 17:56:17 +01:00
let tags = form
. tags
. split ( ',' )
2020-11-22 14:24:43 +01:00
. map ( | t | t . trim ( ) )
2019-03-20 17:56:17 +01:00
. filter ( | t | ! t . is_empty ( ) )
. collect ::< HashSet < _ > > ( )
. into_iter ( )
2022-05-02 18:26:15 +02:00
. filter_map ( | t | Tag ::build_activity ( t . to_string ( ) ) . ok ( ) )
2019-03-20 17:56:17 +01:00
. collect ::< Vec < _ > > ( ) ;
2022-05-02 19:11:46 +02:00
post . update_tags ( & conn , tags )
2019-03-20 17:56:17 +01:00
. expect ( " post::update: tags error " ) ;
2018-10-27 20:44:42 +02:00
2019-03-20 17:56:17 +01:00
let hashtags = hashtags
. into_iter ( )
. collect ::< HashSet < _ > > ( )
. into_iter ( )
2022-05-02 18:26:15 +02:00
. filter_map ( | t | Tag ::build_activity ( t ) . ok ( ) )
2019-03-20 17:56:17 +01:00
. collect ::< Vec < _ > > ( ) ;
2022-05-02 19:11:46 +02:00
post . update_hashtags ( & conn , hashtags )
2019-03-20 17:56:17 +01:00
. expect ( " post::update: hashtags error " ) ;
2018-09-06 23:39:22 +02:00
2018-09-10 20:38:19 +02:00
if post . published {
2018-10-28 11:42:01 +01:00
if newly_published {
2019-03-20 17:56:17 +01:00
let act = post
2022-05-02 18:26:15 +02:00
. create_activity ( & conn )
2019-03-20 17:56:17 +01:00
. expect ( " post::update: act error " ) ;
2021-01-30 13:44:29 +01:00
let dest = User ::one_by_instance ( & conn ) . expect ( " post::update: dest error " ) ;
2021-01-11 21:27:52 +01:00
rockets
. worker
2022-05-02 18:12:39 +02:00
. execute ( move | | broadcast ( & user , act , dest , CONFIG . proxy ( ) . cloned ( ) ) ) ;
2019-10-07 19:08:20 +02:00
2021-01-30 13:44:29 +01:00
Timeline ::add_to_all_timelines ( & conn , & post , Kind ::Original ) . ok ( ) ;
2018-10-28 11:42:01 +01:00
} else {
2019-03-20 17:56:17 +01:00
let act = post
2022-05-02 18:26:15 +02:00
. update_activity ( & conn )
2019-03-20 17:56:17 +01:00
. expect ( " post::update: act error " ) ;
2021-01-30 13:44:29 +01:00
let dest = User ::one_by_instance ( & conn ) . expect ( " posts::update: dest error " ) ;
2021-01-11 21:27:52 +01:00
rockets
. worker
2022-05-02 18:12:39 +02:00
. execute ( move | | broadcast ( & user , act , dest , CONFIG . proxy ( ) . cloned ( ) ) ) ;
2018-10-28 11:42:01 +01:00
}
2018-09-10 20:38:19 +02:00
}
2018-09-06 23:39:22 +02:00
2019-06-14 09:33:30 +02:00
Flash ::success (
2021-01-15 17:13:45 +01:00
Redirect ::to ( uri! (
details : blog = blog ,
slug = new_slug ,
responding_to = _
) ) ,
2019-05-14 12:54:16 +02:00
i18n! ( intl , " Your article has been updated. " ) ,
2019-06-14 09:33:30 +02:00
)
. into ( )
2018-09-06 23:39:22 +02:00
}
} else {
2021-01-30 13:44:29 +01:00
let medias = Media ::for_user ( & conn , user . id ) . expect ( " posts:update: medias error " ) ;
2019-06-14 09:33:30 +02:00
render! ( posts ::new (
2021-01-30 13:44:29 +01:00
& ( & conn , & rockets ) . to_context ( ) ,
2019-04-17 19:31:47 +02:00
i18n! ( intl , " Edit {0} " ; & form . title ) ,
2018-12-07 12:05:01 +01:00
b ,
2018-12-06 18:54:16 +01:00
true ,
& * form ,
2019-03-19 14:37:56 +01:00
form . draft ,
2018-12-07 12:05:01 +01:00
Some ( post ) ,
2020-01-19 12:52:32 +01:00
errors ,
medias ,
2019-01-27 10:55:22 +01:00
cl . 0
2019-06-14 09:33:30 +02:00
) )
. into ( )
2018-09-06 23:39:22 +02:00
}
}
2019-03-12 19:40:54 +01:00
#[ derive(Default, FromForm, Validate) ]
2018-12-06 18:54:16 +01:00
pub struct NewPostForm {
2018-07-07 22:51:48 +02:00
#[ validate(custom(function = " valid_slug " , message = " Invalid title " )) ]
2018-04-23 16:25:39 +02:00
pub title : String ,
2018-09-04 13:26:13 +02:00
pub subtitle : String ,
2018-04-23 16:25:39 +02:00
pub content : String ,
2018-09-05 22:18:27 +02:00
pub tags : String ,
2018-09-10 20:38:19 +02:00
pub license : String ,
pub draft : bool ,
2018-10-30 21:04:59 +01:00
pub cover : Option < i32 > ,
2018-04-23 16:25:39 +02:00
}
2018-12-06 18:54:16 +01:00
pub fn valid_slug ( title : & str ) -> Result < ( ) , ValidationError > {
2021-04-10 09:32:58 +02:00
let slug = Post ::slug ( title ) ;
2018-11-26 10:21:52 +01:00
if slug . is_empty ( ) {
2018-06-29 14:56:00 +02:00
Err ( ValidationError ::new ( " empty_slug " ) )
2018-07-06 11:51:19 +02:00
} else if slug = = " new " {
Err ( ValidationError ::new ( " invalid_slug " ) )
2018-06-29 14:56:00 +02:00
} else {
Ok ( ( ) )
}
}
2018-12-06 18:54:16 +01:00
#[ post( " /~/<blog_name>/new " , data = " <form> " ) ]
2019-03-20 17:56:17 +01:00
pub fn create (
blog_name : String ,
form : LenientForm < NewPostForm > ,
cl : ContentLen ,
2021-01-30 13:44:29 +01:00
conn : DbConn ,
2019-03-20 17:56:17 +01:00
rockets : PlumeRocket ,
2019-06-14 09:33:30 +02:00
) -> Result < RespondOrRedirect , ErrorPage > {
2021-01-30 13:44:29 +01:00
let blog = Blog ::find_by_fqn ( & conn , & blog_name ) . expect ( " post::create: blog error " ) ;
2021-04-10 09:32:58 +02:00
let slug = Post ::slug ( & form . title ) ;
2019-04-17 19:31:47 +02:00
let user = rockets . user . clone ( ) . unwrap ( ) ;
2018-09-03 15:59:02 +02:00
2018-07-06 11:51:19 +02:00
let mut errors = match form . validate ( ) {
Ok ( _ ) = > ValidationErrors ::new ( ) ,
2019-03-20 17:56:17 +01:00
Err ( e ) = > e ,
2018-07-06 11:51:19 +02:00
} ;
2021-11-27 23:53:13 +01:00
if Post ::find_by_slug ( & conn , slug , blog . id ) . is_ok ( ) {
2019-03-20 17:56:17 +01:00
errors . add (
" title " ,
ValidationError {
code : Cow ::from ( " existing_slug " ) ,
message : Some ( Cow ::from ( " A post with the same title already exists. " ) ) ,
params : HashMap ::new ( ) ,
} ,
) ;
2018-07-06 11:51:19 +02:00
}
2018-05-24 12:42:45 +02:00
2018-07-06 11:51:19 +02:00
if errors . is_empty ( ) {
2019-03-20 17:56:17 +01:00
if ! user
2021-01-30 13:44:29 +01:00
. is_author_in ( & conn , & blog )
2019-03-20 17:56:17 +01:00
. expect ( " post::create: is author in error " )
{
2018-07-06 11:51:19 +02:00
// actually it's not "Ok"…
2019-04-30 12:04:25 +02:00
return Ok ( Flash ::error (
Redirect ::to ( uri! ( super ::blogs ::details : name = blog_name , page = _ ) ) ,
i18n! (
& rockets . intl . catalog ,
" You are not allowed to publish on this blog. "
) ,
2019-06-14 09:33:30 +02:00
)
. into ( ) ) ;
2019-03-19 14:37:56 +01:00
}
2018-09-05 22:18:27 +02:00
2022-01-11 20:18:13 +01:00
let ( content , mentions , hashtags ) = md_to_html (
2019-03-19 14:37:56 +01:00
form . content . to_string ( ) . as_ref ( ) ,
2019-05-04 17:33:50 +02:00
Some (
2019-05-10 22:59:34 +02:00
& Instance ::get_local ( )
2019-05-04 17:33:50 +02:00
. expect ( " post::create: local instance error " )
. public_domain ,
) ,
2019-03-22 19:51:36 +01:00
false ,
2019-04-06 19:20:33 +02:00
Some ( Media ::get_media_processor (
& conn ,
blog . list_authors ( & conn )
. expect ( " Could not get author list " )
. iter ( )
. collect ( ) ,
) ) ,
2019-03-19 14:37:56 +01:00
) ;
2019-03-20 17:56:17 +01:00
let post = Post ::insert (
2021-01-30 13:44:29 +01:00
& conn ,
2019-03-20 17:56:17 +01:00
NewPost {
blog_id : blog . id ,
slug : slug . to_string ( ) ,
title : form . title . to_string ( ) ,
content : SafeString ::new ( & content ) ,
published : ! form . draft ,
license : form . license . clone ( ) ,
ap_url : " " . to_string ( ) ,
creation_date : None ,
subtitle : form . subtitle . clone ( ) ,
source : form . content . clone ( ) ,
cover_id : form . cover ,
2019-03-19 14:37:56 +01:00
} ,
2019-03-20 17:56:17 +01:00
)
. expect ( " post::create: post save error " ) ;
PostAuthor ::insert (
2021-01-30 13:44:29 +01:00
& conn ,
2019-03-20 17:56:17 +01:00
NewPostAuthor {
post_id : post . id ,
author_id : user . id ,
} ,
)
. expect ( " post::create: author save error " ) ;
2019-03-19 14:37:56 +01:00
2019-03-20 17:56:17 +01:00
let tags = form
. tags
. split ( ',' )
2020-11-22 14:24:43 +01:00
. map ( | t | t . trim ( ) )
2019-03-19 14:37:56 +01:00
. filter ( | t | ! t . is_empty ( ) )
. collect ::< HashSet < _ > > ( ) ;
for tag in tags {
2019-03-20 17:56:17 +01:00
Tag ::insert (
2021-01-30 13:44:29 +01:00
& conn ,
2019-03-20 17:56:17 +01:00
NewTag {
2020-11-22 14:24:43 +01:00
tag : tag . to_string ( ) ,
2019-03-20 17:56:17 +01:00
is_hashtag : false ,
post_id : post . id ,
} ,
)
. expect ( " post::create: tags save error " ) ;
2019-03-19 14:37:56 +01:00
}
for hashtag in hashtags {
2019-03-20 17:56:17 +01:00
Tag ::insert (
2021-01-30 13:44:29 +01:00
& conn ,
2019-03-20 17:56:17 +01:00
NewTag {
2020-11-22 14:24:43 +01:00
tag : hashtag ,
2019-03-20 17:56:17 +01:00
is_hashtag : true ,
post_id : post . id ,
} ,
)
. expect ( " post::create: hashtags save error " ) ;
2019-03-19 14:37:56 +01:00
}
2018-09-10 20:38:19 +02:00
2019-03-19 14:37:56 +01:00
if post . published {
for m in mentions {
2022-05-02 18:22:55 +02:00
Mention ::from_activity (
2021-01-30 13:44:29 +01:00
& conn ,
2022-05-02 18:26:15 +02:00
& Mention ::build_activity ( & conn , & m ) . expect ( " post::create: mention build error " ) ,
2019-03-19 14:37:56 +01:00
post . id ,
true ,
2019-03-20 17:56:17 +01:00
true ,
)
. expect ( " post::create: mention save error " ) ;
2018-09-10 20:38:19 +02:00
}
2018-05-01 17:51:49 +02:00
2019-03-20 17:56:17 +01:00
let act = post
2022-05-02 18:26:15 +02:00
. create_activity ( & conn )
2019-03-20 17:56:17 +01:00
. expect ( " posts::create: activity error " ) ;
2021-01-30 13:44:29 +01:00
let dest = User ::one_by_instance ( & conn ) . expect ( " posts::create: dest error " ) ;
2019-10-07 19:08:20 +02:00
let worker = & rockets . worker ;
2022-05-02 18:12:39 +02:00
worker . execute ( move | | broadcast ( & user , act , dest , CONFIG . proxy ( ) . cloned ( ) ) ) ;
2019-10-07 19:08:20 +02:00
2021-01-30 13:44:29 +01:00
Timeline ::add_to_all_timelines ( & conn , & post , Kind ::Original ) ? ;
2018-06-20 10:44:56 +02:00
}
2019-03-19 14:37:56 +01:00
2019-04-30 12:04:25 +02:00
Ok ( Flash ::success (
2021-01-15 17:13:45 +01:00
Redirect ::to ( uri! (
details : blog = blog_name ,
slug = slug ,
responding_to = _
) ) ,
2019-05-14 12:54:16 +02:00
i18n! ( & rockets . intl . catalog , " Your article has been saved. " ) ,
2019-06-14 09:33:30 +02:00
)
. into ( ) )
2018-07-06 11:51:19 +02:00
} else {
2021-01-30 13:44:29 +01:00
let medias = Media ::for_user ( & conn , user . id ) . expect ( " posts::create: medias error " ) ;
2019-06-14 09:33:30 +02:00
Ok ( render! ( posts ::new (
2021-01-30 13:44:29 +01:00
& ( & conn , & rockets ) . to_context ( ) ,
2019-05-14 12:54:16 +02:00
i18n! ( rockets . intl . catalog , " New article " ) ,
2018-12-07 12:05:01 +01:00
blog ,
2018-12-06 18:54:16 +01:00
false ,
& * form ,
2018-12-07 12:05:01 +01:00
form . draft ,
None ,
2020-01-19 12:52:32 +01:00
errors ,
2019-01-27 10:55:22 +01:00
medias ,
cl . 0
2019-06-14 09:33:30 +02:00
) )
. into ( ) )
2018-06-19 21:16:18 +02:00
}
2018-04-23 16:25:39 +02:00
}
2018-09-01 17:28:47 +02:00
2018-09-19 19:13:07 +02:00
#[ post( " /~/<blog_name>/<slug>/delete " ) ]
2019-03-20 17:56:17 +01:00
pub fn delete (
blog_name : String ,
slug : String ,
2021-01-30 13:44:29 +01:00
conn : DbConn ,
2019-03-20 17:56:17 +01:00
rockets : PlumeRocket ,
2019-04-30 12:04:25 +02:00
intl : I18n ,
) -> Result < Flash < Redirect > , ErrorPage > {
2019-04-17 19:31:47 +02:00
let user = rockets . user . clone ( ) . unwrap ( ) ;
2021-01-30 13:44:29 +01:00
let post = Blog ::find_by_fqn ( & conn , & blog_name )
. and_then ( | blog | Post ::find_by_slug ( & conn , & slug , blog . id ) ) ;
2018-09-01 17:28:47 +02:00
2018-12-29 09:36:07 +01:00
if let Ok ( post ) = post {
2019-03-20 17:56:17 +01:00
if ! post
2021-01-30 13:44:29 +01:00
. get_authors ( & conn ) ?
2019-03-20 17:56:17 +01:00
. into_iter ( )
. any ( | a | a . id = = user . id )
{
2019-04-30 12:04:25 +02:00
return Ok ( Flash ::error (
2021-01-15 17:13:45 +01:00
Redirect ::to ( uri! (
details : blog = blog_name ,
slug = slug ,
responding_to = _
) ) ,
2019-04-30 12:04:25 +02:00
i18n! ( intl . catalog , " You are not allowed to delete this article. " ) ,
2019-03-20 17:56:17 +01:00
) ) ;
2018-09-01 17:28:47 +02:00
}
2019-03-19 14:37:56 +01:00
2021-01-30 13:44:29 +01:00
let dest = User ::one_by_instance ( & conn ) ? ;
2022-05-02 19:11:46 +02:00
let delete_activity = post . build_delete ( & conn ) ? ;
2019-04-17 19:31:47 +02:00
inbox (
2021-01-30 13:44:29 +01:00
& conn ,
2019-04-17 19:31:47 +02:00
serde_json ::to_value ( & delete_activity ) . map_err ( Error ::from ) ? ,
) ? ;
2019-03-19 14:37:56 +01:00
let user_c = user . clone ( ) ;
2019-04-17 19:31:47 +02:00
rockets
. worker
2022-05-02 18:12:39 +02:00
. execute ( move | | broadcast ( & user_c , delete_activity , dest , CONFIG . proxy ( ) . cloned ( ) ) ) ;
2019-04-17 19:31:47 +02:00
rockets
. worker
. execute_after ( Duration ::from_secs ( 10 * 60 ) , move | | {
2021-01-30 13:44:29 +01:00
user . rotate_keypair ( & conn )
2019-04-17 19:31:47 +02:00
. expect ( " Failed to rotate keypair " ) ;
} ) ;
2019-03-19 14:37:56 +01:00
2019-04-30 12:04:25 +02:00
Ok ( Flash ::success (
Redirect ::to ( uri! ( super ::blogs ::details : name = blog_name , page = _ ) ) ,
2019-05-14 12:54:16 +02:00
i18n! ( intl . catalog , " Your article has been deleted. " ) ,
2019-03-20 17:56:17 +01:00
) )
2018-09-01 17:28:47 +02:00
} else {
2019-04-30 12:04:25 +02:00
Ok ( Flash ::error ( Redirect ::to (
2019-03-20 17:56:17 +01:00
uri! ( super ::blogs ::details : name = blog_name , page = _ ) ,
2019-04-30 12:04:25 +02:00
) , i18n! ( intl . catalog , " It looks like the article you tried to delete doesn't exist. Maybe it is already gone? " ) ) )
2018-09-01 17:28:47 +02:00
}
}
2019-04-17 22:09:07 +02:00
#[ get( " /~/<blog_name>/<slug>/remote_interact " ) ]
pub fn remote_interact (
2021-01-30 13:44:29 +01:00
conn : DbConn ,
2019-04-17 22:09:07 +02:00
rockets : PlumeRocket ,
blog_name : String ,
slug : String ,
) -> Result < Ructe , ErrorPage > {
2021-01-30 13:44:29 +01:00
let target = Blog ::find_by_fqn ( & conn , & blog_name )
. and_then ( | blog | Post ::find_by_slug ( & conn , & slug , blog . id ) ) ? ;
2019-04-17 22:09:07 +02:00
Ok ( render! ( posts ::remote_interact (
2021-01-30 13:44:29 +01:00
& ( & conn , & rockets ) . to_context ( ) ,
2019-04-17 22:09:07 +02:00
target ,
super ::session ::LoginForm ::default ( ) ,
ValidationErrors ::default ( ) ,
RemoteForm ::default ( ) ,
ValidationErrors ::default ( )
) ) )
}
#[ post( " /~/<blog_name>/<slug>/remote_interact " , data = " <remote> " ) ]
pub fn remote_interact_post (
2021-01-30 13:44:29 +01:00
conn : DbConn ,
2019-04-17 22:09:07 +02:00
rockets : PlumeRocket ,
blog_name : String ,
slug : String ,
remote : LenientForm < RemoteForm > ,
2019-06-14 09:33:30 +02:00
) -> Result < RespondOrRedirect , ErrorPage > {
2021-01-30 13:44:29 +01:00
let target = Blog ::find_by_fqn ( & conn , & blog_name )
. and_then ( | blog | Post ::find_by_slug ( & conn , & slug , blog . id ) ) ? ;
2019-04-17 22:09:07 +02:00
if let Some ( uri ) = User ::fetch_remote_interact_uri ( & remote . remote )
. ok ( )
2021-01-02 21:49:45 +01:00
. map ( | uri | uri . replace ( " {uri} " , & Uri ::percent_encode ( & target . ap_url ) ) )
2019-04-17 22:09:07 +02:00
{
2019-06-14 09:33:30 +02:00
Ok ( Redirect ::to ( uri ) . into ( ) )
2019-04-17 22:09:07 +02:00
} else {
let mut errs = ValidationErrors ::new ( ) ;
errs . add ( " remote " , ValidationError {
code : Cow ::from ( " invalid_remote " ) ,
2019-04-30 12:04:25 +02:00
message : Some ( Cow ::from ( i18n! ( rockets . intl . catalog , " Couldn't obtain enough information about your account. Please make sure your username is correct. " ) ) ) ,
2019-04-17 22:09:07 +02:00
params : HashMap ::new ( ) ,
} ) ;
//could not get your remote url?
2019-06-14 09:33:30 +02:00
Ok ( render! ( posts ::remote_interact (
2021-01-30 13:44:29 +01:00
& ( & conn , & rockets ) . to_context ( ) ,
2019-04-17 22:09:07 +02:00
target ,
super ::session ::LoginForm ::default ( ) ,
ValidationErrors ::default ( ) ,
remote . clone ( ) ,
errs
2019-06-14 09:33:30 +02:00
) )
. into ( ) )
2019-04-17 22:09:07 +02:00
}
}