Use Ructe (#327)

All the template are now compiled at compile-time with the `ructe` crate.

I preferred to use it instead of askama because it allows more complex Rust expressions, where askama only supports a small subset of expressions and doesn't allow them everywhere (for instance, `{{ macro!() | filter }}` would result in a parsing error).

The diff is quite huge, but there is normally no changes in functionality.

Fixes #161 and unblocks #110 and #273
This commit is contained in:
Baptiste Gelez
2018-12-06 18:54:16 +01:00
committed by GitHub
parent 5f059c3e98
commit 70af57c6e1
121 changed files with 3132 additions and 3260 deletions
+4 -20
View File
@@ -68,26 +68,10 @@ impl Comment {
.len() // TODO count in database?
}
pub fn to_json(&self, conn: &Connection, others: &[Comment]) -> serde_json::Value {
let mut json = serde_json::to_value(self).expect("Comment::to_json: serialization error");
json["author"] = self.get_author(conn).to_json(conn);
let mentions = Mention::list_for_comment(conn, self.id)
.into_iter()
.map(|m| {
m.get_mentioned(conn)
.map(|u| u.get_fqn(conn))
.unwrap_or_default()
})
.collect::<Vec<String>>();
json["mentions"] = serde_json::to_value(mentions).expect("Comment::to_json: mention error");
json["responses"] = json!(
others
.into_iter()
.filter(|c| c.in_response_to_id.map(|id| id == self.id).unwrap_or(false))
.map(|c| c.to_json(conn, others))
.collect::<Vec<_>>()
);
json
pub fn get_responses(&self, conn: &Connection) -> Vec<Comment> {
comments::table.filter(comments::in_response_to_id.eq(self.id))
.load::<Comment>(conn)
.expect("Comment::get_responses: loading error")
}
pub fn update_ap_url(&self, conn: &Connection) -> Comment {
+11 -10
View File
@@ -20,9 +20,9 @@ pub struct Instance {
pub open_registrations: bool,
pub short_description: SafeString,
pub long_description: SafeString,
pub default_license: String,
pub long_description_html: String,
pub short_description_html: String,
pub default_license : String,
pub long_description_html: SafeString,
pub short_description_html: SafeString,
}
#[derive(Clone, Insertable)]
@@ -244,14 +244,15 @@ pub(crate) mod tests {
default_license,
local,
long_description,
long_description_html,
short_description,
short_description_html,
name,
open_registrations,
public_domain
]
);
assert_eq!(res.long_description_html.get(), &inserted.long_description_html);
assert_eq!(res.short_description_html.get(), &inserted.short_description_html);
assert_eq!(Instance::local_id(conn), res.id);
Ok(())
});
@@ -282,14 +283,14 @@ pub(crate) mod tests {
default_license,
local,
long_description,
long_description_html,
short_description,
short_description_html,
name,
open_registrations,
public_domain
]
)
);
assert_eq!(&newinst.long_description_html, inst.long_description_html.get());
assert_eq!(&newinst.short_description_html, inst.short_description_html.get());
});
let page = Instance::page(conn, (0, 2));
@@ -391,12 +392,12 @@ pub(crate) mod tests {
);
assert_eq!(
inst.long_description_html,
"<p><a href=\"/with_link\">long_description</a></p>\n"
SafeString::new("<p><a href=\"/with_link\">long_description</a></p>\n")
);
assert_eq!(inst.short_description.get(), "[short](#link)");
assert_eq!(
inst.short_description_html,
"<p><a href=\"#link\">short</a></p>\n"
SafeString::new("<p><a href=\"#link\">short</a></p>\n")
);
Ok(())
+1 -1
View File
@@ -1,8 +1,8 @@
#![allow(proc_macro_derive_resolution_fallback)] // This can be removed after diesel-1.4
#![feature(crate_in_paths)]
extern crate activitypub;
extern crate ammonia;
extern crate askama_escape;
extern crate bcrypt;
extern crate canapi;
extern crate chrono;
+65 -56
View File
@@ -1,13 +1,14 @@
use activitypub::object::Image;
use askama_escape::escape;
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
use guid_create::GUID;
use reqwest;
use serde_json;
use std::{fs, path::Path};
use plume_common::activity_pub::Id;
use instance::Instance;
use safe_string::SafeString;
use schema::medias;
use users::User;
use {ap_url, Connection};
@@ -36,6 +37,14 @@ pub struct NewMedia {
pub owner_id: i32,
}
#[derive(PartialEq)]
pub enum MediaCategory {
Image,
Audio,
Video,
Unknown,
}
impl Media {
insert!(medias, NewMedia);
get!(medias);
@@ -47,65 +56,65 @@ impl Media {
.expect("Media::list_all_medias: loading error")
}
pub fn to_json(&self, conn: &Connection) -> serde_json::Value {
let mut json = serde_json::to_value(self).expect("Media::to_json: serialization error");
let url = self.url(conn);
let (cat, preview, html, md) = match self
pub fn category(&self) -> MediaCategory {
match self
.file_path
.rsplitn(2, '.')
.next()
.expect("Media::to_json: extension error")
.expect("Media::category: extension error")
{
"png" | "jpg" | "jpeg" | "gif" | "svg" => (
"image",
format!(
"<img src=\"{}\" alt=\"{}\" title=\"{}\" class=\"preview\">",
url, self.alt_text, self.alt_text
),
format!(
"<img src=\"{}\" alt=\"{}\" title=\"{}\">",
url, self.alt_text, self.alt_text
),
format!("![{}]({})", self.alt_text, url),
),
"mp3" | "wav" | "flac" => (
"audio",
format!(
"<audio src=\"{}\" title=\"{}\" class=\"preview\"></audio>",
url, self.alt_text
),
format!(
"<audio src=\"{}\" title=\"{}\"></audio>",
url, self.alt_text
),
format!(
"<audio src=\"{}\" title=\"{}\"></audio>",
url, self.alt_text
),
),
"mp4" | "avi" | "webm" | "mov" => (
"video",
format!(
"<video src=\"{}\" title=\"{}\" class=\"preview\"></video>",
url, self.alt_text
),
format!(
"<video src=\"{}\" title=\"{}\"></video>",
url, self.alt_text
),
format!(
"<video src=\"{}\" title=\"{}\"></video>",
url, self.alt_text
),
),
_ => ("unknown", String::new(), String::new(), String::new()),
};
json["html_preview"] = json!(preview);
json["html"] = json!(html);
json["url"] = json!(url);
json["md"] = json!(md);
json["category"] = json!(cat);
json
"png" | "jpg" | "jpeg" | "gif" | "svg" => MediaCategory::Image,
"mp3" | "wav" | "flac" => MediaCategory::Audio,
"mp4" | "avi" | "webm" | "mov" => MediaCategory::Video,
_ => MediaCategory::Unknown,
}
}
pub fn preview_html(&self, conn: &Connection) -> SafeString {
let url = self.url(conn);
match self.category() {
MediaCategory::Image => SafeString::new(&format!(
r#"<img src="{}" alt="{}" title="{}" class=\"preview\">"#,
url, escape(&self.alt_text), escape(&self.alt_text)
)),
MediaCategory::Audio => SafeString::new(&format!(
r#"<audio src="{}" title="{}" class="preview"></audio>"#,
url, escape(&self.alt_text)
)),
MediaCategory::Video => SafeString::new(&format!(
r#"<video src="{}" title="{}" class="preview"></video>"#,
url, escape(&self.alt_text)
)),
MediaCategory::Unknown => SafeString::new(""),
}
}
pub fn html(&self, conn: &Connection) -> SafeString {
let url = self.url(conn);
match self.category() {
MediaCategory::Image => SafeString::new(&format!(
r#"<img src="{}" alt="{}" title="{}">"#,
url, escape(&self.alt_text), escape(&self.alt_text)
)),
MediaCategory::Audio => SafeString::new(&format!(
r#"<audio src="{}" title="{}"></audio>"#,
url, escape(&self.alt_text)
)),
MediaCategory::Video => SafeString::new(&format!(
r#"<video src="{}" title="{}"></video>"#,
url, escape(&self.alt_text)
)),
MediaCategory::Unknown => SafeString::new(""),
}
}
pub fn markdown(&self, conn: &Connection) -> SafeString {
let url = self.url(conn);
match self.category() {
MediaCategory::Image => SafeString::new(&format!("![{}]({})", escape(&self.alt_text), url)),
MediaCategory::Audio | MediaCategory::Video => self.html(conn),
MediaCategory::Unknown => SafeString::new(""),
}
}
pub fn url(&self, conn: &Connection) -> String {
+59 -40
View File
@@ -1,6 +1,5 @@
use chrono::NaiveDateTime;
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
use serde_json;
use comments::Comment;
use follows::Follow;
@@ -71,45 +70,65 @@ impl Notification {
.ok()
}
pub fn to_json(&self, conn: &Connection) -> serde_json::Value {
let mut json = json!(self);
json["object"] = json!(match self.kind.as_ref() {
notification_kind::COMMENT => Comment::get(conn, self.object_id).map(|comment| json!({
"post": comment.get_post(conn).to_json(conn),
"user": comment.get_author(conn).to_json(conn),
"id": comment.id
})),
notification_kind::FOLLOW => Follow::get(conn, self.object_id).map(|follow| {
json!({
"follower": User::get(conn, follow.follower_id).map(|u| u.to_json(conn))
})
}),
notification_kind::LIKE => Like::get(conn, self.object_id).map(|like| {
json!({
"post": Post::get(conn, like.post_id).map(|p| p.to_json(conn)),
"user": User::get(conn, like.user_id).map(|u| u.to_json(conn))
})
}),
notification_kind::MENTION => Mention::get(conn, self.object_id).map(|mention| {
json!({
"user": mention.get_user(conn).map(|u| u.to_json(conn)),
"url": mention.get_post(conn).map(|p| p.to_json(conn)["url"].clone())
.unwrap_or_else(|| {
let comment = mention.get_comment(conn).expect("Notification::to_json: comment not found error");
let post = comment.get_post(conn).to_json(conn);
json!(format!("{}#comment-{}", post["url"].as_str().expect("Notification::to_json: post url error"), comment.id))
})
})
}),
notification_kind::RESHARE => Reshare::get(conn, self.object_id).map(|reshare| {
json!({
"post": reshare.get_post(conn).map(|p| p.to_json(conn)),
"user": reshare.get_user(conn).map(|u| u.to_json(conn))
})
}),
_ => Some(json!({})),
});
json
pub fn get_message(&self) -> &'static str {
match self.kind.as_ref() {
notification_kind::COMMENT => "{0} commented your article.",
notification_kind::FOLLOW => "{0} is now following you.",
notification_kind::LIKE => "{0} liked your article.",
notification_kind::MENTION => "{0} mentioned you.",
notification_kind::RESHARE => "{0} boosted your article.",
_ => unreachable!("Notification::get_message: Unknow type"),
}
}
pub fn get_url(&self, conn: &Connection) -> Option<String> {
match self.kind.as_ref() {
notification_kind::COMMENT => self.get_post(conn).map(|p| format!("{}#comment-{}", p.url(conn), self.object_id)),
notification_kind::FOLLOW => Some(format!("/@/{}/", self.get_actor(conn).get_fqn(conn))),
notification_kind::MENTION => Mention::get(conn, self.object_id).map(|mention|
mention.get_post(conn).map(|p| p.url(conn))
.unwrap_or_else(|| {
let comment = mention.get_comment(conn).expect("Notification::get_url: comment not found error");
format!("{}#comment-{}", comment.get_post(conn).url(conn), comment.id)
})
),
_ => None,
}
}
pub fn get_post(&self, conn: &Connection) -> Option<Post> {
match self.kind.as_ref() {
notification_kind::COMMENT => Comment::get(conn, self.object_id).map(|comment| comment.get_post(conn)),
notification_kind::LIKE => Like::get(conn, self.object_id).and_then(|like| Post::get(conn, like.post_id)),
notification_kind::RESHARE => Reshare::get(conn, self.object_id).and_then(|reshare| reshare.get_post(conn)),
_ => None,
}
}
pub fn get_actor(&self, conn: &Connection) -> User {
match self.kind.as_ref() {
notification_kind::COMMENT => Comment::get(conn, self.object_id).expect("Notification::get_actor: comment error").get_author(conn),
notification_kind::FOLLOW => User::get(conn, Follow::get(conn, self.object_id).expect("Notification::get_actor: follow error").follower_id)
.expect("Notification::get_actor: follower error"),
notification_kind::LIKE => User::get(conn, Like::get(conn, self.object_id).expect("Notification::get_actor: like error").user_id)
.expect("Notification::get_actor: liker error"),
notification_kind::MENTION => Mention::get(conn, self.object_id).expect("Notification::get_actor: mention error").get_user(conn)
.expect("Notification::get_actor: mentioner error"),
notification_kind::RESHARE => Reshare::get(conn, self.object_id).expect("Notification::get_actor: reshare error").get_user(conn)
.expect("Notification::get_actor: resharer error"),
_ => unreachable!("Notification::get_actor: Unknow type"),
}
}
pub fn icon_class(&self) -> &'static str {
match self.kind.as_ref() {
notification_kind::COMMENT => "icon-message-circle",
notification_kind::FOLLOW => "icon-user-plus",
notification_kind::LIKE => "icon-heart",
notification_kind::MENTION => "icon-at-sign",
notification_kind::RESHARE => "icon-repeat",
_ => unreachable!("Notification::get_actor: Unknow type"),
}
}
pub fn delete(&self, conn: &Connection) {
+6 -10
View File
@@ -763,17 +763,9 @@ impl Post {
}
}
pub fn to_json(&self, conn: &Connection) -> serde_json::Value {
pub fn url(&self, conn: &Connection) -> String {
let blog = self.get_blog(conn);
json!({
"post": self,
"author": self.get_authors(conn)[0].to_json(conn),
"url": format!("/~/{}/{}/", blog.get_fqn(conn), self.slug),
"date": self.creation_date.timestamp(),
"blog": blog.to_json(conn),
"tags": Tag::for_post(&*conn, self.id),
"cover": self.cover_id.and_then(|i| Media::get(conn, i).map(|m| m.to_json(conn))),
})
format!("/~/{}/{}", blog.get_fqn(conn), self.slug)
}
pub fn compute_id(&self, conn: &Connection) -> String {
@@ -784,6 +776,10 @@ impl Post {
self.slug
))
}
pub fn cover_url(&self, conn: &Connection) -> Option<String> {
self.cover_id.and_then(|i| Media::get(conn, i)).map(|c| c.url(conn))
}
}
impl<'a> FromActivity<Article, (&'a Connection, &'a Searcher)> for Post {
+17 -14
View File
@@ -26,6 +26,7 @@ use rocket::{
request::{self, FromRequest, Request},
};
use serde_json;
use std::cmp::PartialEq;
use url::Url;
use webfinger::*;
@@ -797,20 +798,8 @@ impl User {
CustomPerson::new(actor, ap_signature)
}
pub fn to_json(&self, conn: &Connection) -> serde_json::Value {
let mut json = serde_json::to_value(self).expect("User::to_json: serializing error");
json["fqn"] = serde_json::Value::String(self.get_fqn(conn));
json["name"] = if !self.display_name.is_empty() {
json!(self.display_name)
} else {
json!(self.get_fqn(conn))
};
json["avatar"] = json!(
self.avatar_id
.and_then(|id| Media::get(conn, id).map(|m| m.url(conn)))
.unwrap_or_else(|| String::from("/static/default-avatar.png"))
);
json
pub fn avatar_url(&self, conn: &Connection) -> String {
self.avatar_id.and_then(|id| Media::get(conn, id).map(|m| m.url(conn))).unwrap_or("/static/default-avatar.png".to_string())
}
pub fn webfinger(&self, conn: &Connection) -> Webfinger {
@@ -874,6 +863,14 @@ impl User {
pub fn needs_update(&self) -> bool {
(Utc::now().naive_utc() - self.last_fetched_date).num_days() > 1
}
pub fn name(&self, conn: &Connection) -> String {
if !self.display_name.is_empty() {
self.display_name.clone()
} else {
self.get_fqn(conn)
}
}
}
impl<'a, 'r> FromRequest<'a, 'r> for User {
@@ -946,6 +943,12 @@ impl Signer for User {
}
}
impl PartialEq for User {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl NewUser {
/// Creates a new local user
pub fn new_local(