Edit blogs, and add blog icons and banners (#460)
Also adds a parameter to `md_to_html` to only render inline elements (so that we don't have titles or images in blog descriptions). And moves the delete button for the blog on the edition page. I still have to update the SQLite migration once others PRs with migrations will be merged. Also, there will be a problem when you edit a blog while not owning its banner or icon: when validating they will be reset to their default values… I don't see a good solution to this until we have a better way to handle uploads with Rocket (the same is probably happening for articles btw). And the icon/banner are not federated yet, I don't know if I should add it to this PR or if it can come after?   Fixes #453 Fixes #454
This commit is contained in:
+2
-1
@@ -127,7 +127,6 @@ Then try to restart Plume
|
||||
})
|
||||
.expect("Error setting Ctrl-c handler");
|
||||
|
||||
|
||||
let mail = mail::init();
|
||||
if mail.is_none() && CONFIG.rocket.as_ref().unwrap().environment.is_prod() {
|
||||
println!("Warning: the email server is not configured (or not completely).");
|
||||
@@ -145,6 +144,8 @@ Then try to restart Plume
|
||||
routes::blogs::new_auth,
|
||||
routes::blogs::create,
|
||||
routes::blogs::delete,
|
||||
routes::blogs::edit,
|
||||
routes::blogs::update,
|
||||
routes::blogs::atom_feed,
|
||||
routes::comments::create,
|
||||
routes::comments::delete,
|
||||
|
||||
+141
-5
@@ -1,5 +1,6 @@
|
||||
use activitypub::collection::OrderedCollection;
|
||||
use atom_syndication::{Entry, FeedBuilder};
|
||||
use diesel::SaveChangesDsl;
|
||||
use rocket::{
|
||||
http::ContentType,
|
||||
request::LenientForm,
|
||||
@@ -11,7 +12,10 @@ use validator::{Validate, ValidationError, ValidationErrors};
|
||||
|
||||
use plume_common::activity_pub::{ActivityStream, ApRequest};
|
||||
use plume_common::utils;
|
||||
use plume_models::{blog_authors::*, blogs::*, db_conn::DbConn, instance::Instance, posts::Post};
|
||||
use plume_models::{
|
||||
blog_authors::*, blogs::*, db_conn::DbConn, instance::Instance, medias::*, posts::Post,
|
||||
safe_string::SafeString, users::User, Connection,
|
||||
};
|
||||
use routes::{errors::ErrorPage, Page, PlumeRocket};
|
||||
use template_utils::Ructe;
|
||||
|
||||
@@ -28,13 +32,10 @@ pub fn details(name: String, page: Option<Page>, rockets: PlumeRocket) -> Result
|
||||
|
||||
Ok(render!(blogs::details(
|
||||
&(&*conn, &intl.catalog, user.clone()),
|
||||
blog.clone(),
|
||||
blog,
|
||||
authors,
|
||||
articles_count,
|
||||
page.0,
|
||||
Page::total(articles_count as i32),
|
||||
user.and_then(|x| x.is_author_in(&*conn, &blog).ok())
|
||||
.unwrap_or(false),
|
||||
posts
|
||||
)))
|
||||
}
|
||||
@@ -170,6 +171,141 @@ pub fn delete(name: String, rockets: PlumeRocket) -> Result<Redirect, Ructe> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromForm, Validate)]
|
||||
pub struct EditForm {
|
||||
#[validate(custom(function = "valid_slug", message = "Invalid name"))]
|
||||
pub title: String,
|
||||
pub summary: String,
|
||||
pub icon: Option<i32>,
|
||||
pub banner: Option<i32>,
|
||||
}
|
||||
|
||||
#[get("/~/<name>/edit")]
|
||||
pub fn edit(
|
||||
conn: DbConn,
|
||||
name: String,
|
||||
user: Option<User>,
|
||||
intl: I18n,
|
||||
) -> Result<Ructe, ErrorPage> {
|
||||
let blog = Blog::find_by_fqn(&*conn, &name)?;
|
||||
if user
|
||||
.clone()
|
||||
.and_then(|u| u.is_author_in(&*conn, &blog).ok())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
let user = user.expect("blogs::edit: User was None while it shouldn't");
|
||||
let medias = Media::for_user(&*conn, user.id).expect("Couldn't list media");
|
||||
Ok(render!(blogs::edit(
|
||||
&(&*conn, &intl.catalog, Some(user)),
|
||||
&blog,
|
||||
medias,
|
||||
&EditForm {
|
||||
title: blog.title.clone(),
|
||||
summary: blog.summary.clone(),
|
||||
icon: blog.icon_id,
|
||||
banner: blog.banner_id,
|
||||
},
|
||||
ValidationErrors::default()
|
||||
)))
|
||||
} else {
|
||||
// TODO actually return 403 error code
|
||||
Ok(render!(errors::not_authorized(
|
||||
&(&*conn, &intl.catalog, user),
|
||||
i18n!(intl.catalog, "You are not allowed to edit this blog.")
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the media is owned by `user` and is a picture
|
||||
fn check_media(conn: &Connection, id: i32, user: &User) -> bool {
|
||||
if let Ok(media) = Media::get(conn, id) {
|
||||
media.owner_id == user.id && media.category() == MediaCategory::Image
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[put("/~/<name>/edit", data = "<form>")]
|
||||
pub fn update(
|
||||
conn: DbConn,
|
||||
name: String,
|
||||
user: Option<User>,
|
||||
intl: I18n,
|
||||
form: LenientForm<EditForm>,
|
||||
) -> Result<Redirect, Ructe> {
|
||||
let mut blog = Blog::find_by_fqn(&*conn, &name).expect("blog::update: blog not found");
|
||||
if user
|
||||
.clone()
|
||||
.and_then(|u| u.is_author_in(&*conn, &blog).ok())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
let user = user.expect("blogs::edit: User was None while it shouldn't");
|
||||
form.validate()
|
||||
.and_then(|_| {
|
||||
if let Some(icon) = form.icon {
|
||||
if !check_media(&*conn, icon, &user) {
|
||||
let mut errors = ValidationErrors::new();
|
||||
errors.add(
|
||||
"",
|
||||
ValidationError {
|
||||
code: Cow::from("icon"),
|
||||
message: Some(Cow::from(i18n!(
|
||||
intl.catalog,
|
||||
"You can't use this media as blog icon."
|
||||
))),
|
||||
params: HashMap::new(),
|
||||
},
|
||||
);
|
||||
return Err(errors);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(banner) = form.banner {
|
||||
if !check_media(&*conn, banner, &user) {
|
||||
let mut errors = ValidationErrors::new();
|
||||
errors.add(
|
||||
"",
|
||||
ValidationError {
|
||||
code: Cow::from("banner"),
|
||||
message: Some(Cow::from(i18n!(
|
||||
intl.catalog,
|
||||
"You can't use this media as blog banner."
|
||||
))),
|
||||
params: HashMap::new(),
|
||||
},
|
||||
);
|
||||
return Err(errors);
|
||||
}
|
||||
}
|
||||
|
||||
blog.title = form.title.clone();
|
||||
blog.summary = form.summary.clone();
|
||||
blog.summary_html = SafeString::new(&utils::md_to_html(&form.summary, "", true).0);
|
||||
blog.icon_id = form.icon;
|
||||
blog.banner_id = form.banner;
|
||||
blog.save_changes::<Blog>(&*conn)
|
||||
.expect("Couldn't save blog changes");
|
||||
Ok(Redirect::to(uri!(details: name = name, page = _)))
|
||||
})
|
||||
.map_err(|err| {
|
||||
let medias = Media::for_user(&*conn, user.id).expect("Couldn't list media");
|
||||
render!(blogs::edit(
|
||||
&(&*conn, &intl.catalog, Some(user)),
|
||||
&blog,
|
||||
medias,
|
||||
&*form,
|
||||
err
|
||||
))
|
||||
})
|
||||
} else {
|
||||
// TODO actually return 403 error code
|
||||
Err(render!(errors::not_authorized(
|
||||
&(&*conn, &intl.catalog, user),
|
||||
i18n!(intl.catalog, "You are not allowed to edit this blog.")
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/~/<name>/outbox")]
|
||||
pub fn outbox(name: String, conn: DbConn) -> Option<ActivityStream<OrderedCollection>> {
|
||||
let blog = Blog::find_by_fqn(&*conn, &name).ok()?;
|
||||
|
||||
@@ -44,6 +44,7 @@ pub fn create(
|
||||
&Instance::get_local(&conn)
|
||||
.expect("comments::create: local instance error")
|
||||
.public_domain,
|
||||
true,
|
||||
);
|
||||
let comm = Comment::insert(
|
||||
&*conn,
|
||||
|
||||
@@ -263,6 +263,7 @@ pub fn update(
|
||||
&Instance::get_local(&conn)
|
||||
.expect("posts::update: Error getting local instance")
|
||||
.public_domain,
|
||||
false,
|
||||
);
|
||||
|
||||
// update publication date if when this article is no longer a draft
|
||||
@@ -422,6 +423,7 @@ pub fn create(
|
||||
&Instance::get_local(&conn)
|
||||
.expect("post::create: local instance error")
|
||||
.public_domain,
|
||||
false,
|
||||
);
|
||||
|
||||
let searcher = rockets.searcher;
|
||||
|
||||
Reference in New Issue
Block a user