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:
@@ -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 {
|
||||
|
||||
@@ -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,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
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user