forked from Plume/Plume
commit
6fe70cd723
15 changed files with 332 additions and 107 deletions
60
Cargo.lock
generated
60
Cargo.lock
generated
|
@ -592,6 +592,11 @@ dependencies = [
|
|||
"unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "if_chain"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.0.1"
|
||||
|
@ -994,7 +999,11 @@ dependencies = [
|
|||
"rocket_csrf 0.1.0 (git+https://github.com/fdb-hiroshima/rocket_csrf?rev=13ca47ef73be86cef9caca30c516e4e95f3051ce)",
|
||||
"rocket_i18n 0.1.1 (git+https://github.com/BaptisteGelez/rocket_i18n?rev=5b4225d5bed5769482dc926a7e6d6b79f1217be6)",
|
||||
"rpassword 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.43 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"validator 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"validator_derive 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"webfinger 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
|
@ -1183,6 +1192,18 @@ dependencies = [
|
|||
"utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex-syntax 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.5.5"
|
||||
|
@ -1191,6 +1212,14 @@ dependencies = [
|
|||
"ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "relay"
|
||||
version = "0.1.1"
|
||||
|
@ -1926,6 +1955,32 @@ dependencies = [
|
|||
"rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "validator"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.43 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "validator_derive"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"if_chain 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.14.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"validator 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.3"
|
||||
|
@ -2064,6 +2119,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
"checksum hyper 0.11.25 (registry+https://github.com/rust-lang/crates.io-index)" = "549dbb86397490ce69d908425b9beebc85bbaad25157d67479d4995bb56fdf9a"
|
||||
"checksum hyper-tls 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a5aa51f6ae9842239b0fac14af5f22123b8432b4cc774a44ff059fcba0f675ca"
|
||||
"checksum idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "014b298351066f1512874135335d62a789ffe78a9974f94b43ed5621951eaf7d"
|
||||
"checksum if_chain 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "61bb90bdd39e3af69b0172dfc6130f6cd6332bf040fbb9bdd4401d37adbd48b8"
|
||||
"checksum indexmap 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "08173ba1e906efb6538785a8844dd496f5d34f0a2d88038e95195172fc667220"
|
||||
"checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08"
|
||||
"checksum isatty 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a118a53ba42790ef25c82bb481ecf36e2da892646cccd361e69a6bb881e19398"
|
||||
|
@ -2128,7 +2184,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
"checksum rand_core 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "edecf0f94da5551fc9b492093e30b041a891657db7940ee221f9d2f66e82eef2"
|
||||
"checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd"
|
||||
"checksum regex 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "aec3f58d903a7d2a9dc2bf0e41a746f4530e0cab6b615494e058f67a3ef947fb"
|
||||
"checksum regex 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "13c93d55961981ba9226a213b385216f83ab43bd6ac53ab16b2eeb47e337cf4e"
|
||||
"checksum regex-syntax 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "bd90079345f4a4c3409214734ae220fd773c6f2e8a543d07370c6c1c369cfbfb"
|
||||
"checksum regex-syntax 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05b06a75f5217880fc5e905952a42750bf44787e56a6c6d6852ed0992f5e1d54"
|
||||
"checksum relay 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1576e382688d7e9deecea24417e350d3062d97e32e45d70b1cde65994ff1489a"
|
||||
"checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5"
|
||||
"checksum reqwest 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)" = "241faa9a8ca28a03cbbb9815a5d085f271d4c0168a19181f106aa93240c22ddb"
|
||||
|
@ -2210,6 +2268,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
"checksum utf-8 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1262dfab4c30d5cb7c07026be00ee343a6cf5027fdc0104a9160f354e5db75c"
|
||||
"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
|
||||
"checksum uuid 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcc7e3b898aa6f6c08e5295b6c89258d1331e9ac578cc992fb818759951bdc22"
|
||||
"checksum validator 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4a8c44fecf027a477e70a86cd7f4863410adf120ca2cb13408cb099057b8e2d0"
|
||||
"checksum validator_derive 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "708ee89305635499f793d0e2dd9d0b1b5d00daba90fdfb1392b87c7279521fab"
|
||||
"checksum vcpkg 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7ed0f6789c8a85ca41bbc1c9d175422116a9869bd1cf31bb08e1493ecce60380"
|
||||
"checksum version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6b772017e347561807c1aa192438c5fd74242a670a6cffacc40f2defd1dc069d"
|
||||
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||
|
|
12
Cargo.toml
12
Cargo.toml
|
@ -10,19 +10,23 @@ failure = "0.1"
|
|||
gettext-rs = "0.4"
|
||||
heck = "0.3.0"
|
||||
rpassword = "2.0"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0"
|
||||
validator = "0.7"
|
||||
validator_derive = "0.7"
|
||||
webfinger = "0.2"
|
||||
|
||||
[dependencies.diesel]
|
||||
features = ["postgres", "r2d2", "chrono"]
|
||||
version = "*"
|
||||
|
||||
[dependencies.plume-models]
|
||||
path = "plume-models"
|
||||
|
||||
[dependencies.plume-common]
|
||||
path = "plume-common"
|
||||
|
||||
[dependencies.plume-models]
|
||||
path = "plume-models"
|
||||
|
||||
[dependencies.rocket]
|
||||
git = "https://github.com/SergioBenitez/Rocket"
|
||||
rev = "df7111143e466c18d1f56377a8d9530a5a306aba"
|
||||
|
@ -45,4 +49,4 @@ git = "https://github.com/BaptisteGelez/rocket_i18n"
|
|||
rev = "5b4225d5bed5769482dc926a7e6d6b79f1217be6"
|
||||
|
||||
[workspace]
|
||||
members = ['plume-models', 'plume-common']
|
||||
members = ["plume-models", "plume-common"]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
-- Your SQL goes here
|
||||
l-- Your SQL goes here
|
||||
CREATE TABLE instances (
|
||||
id SERIAL PRIMARY KEY,
|
||||
local_domain VARCHAR NOT NULL,
|
||||
|
|
34
po/plume.pot
34
po/plume.pot
|
@ -281,3 +281,37 @@ msgstr ""
|
|||
|
||||
msgid "Your comment"
|
||||
msgstr ""
|
||||
|
||||
msgid "Unknown error"
|
||||
msgstr ""
|
||||
|
||||
msgid "Invalid name"
|
||||
msgstr ""
|
||||
|
||||
msgid "A blog with the same name already exists."
|
||||
msgstr ""
|
||||
|
||||
msgid "Your comment can't be empty"
|
||||
msgstr ""
|
||||
|
||||
msgid "A post with the same title already exists."
|
||||
msgstr ""
|
||||
|
||||
msgid "We need an email or a username to identify you"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your password should be at least 8 characters long"
|
||||
msgstr ""
|
||||
|
||||
msgid "Passwords are not matching"
|
||||
msgstr ""
|
||||
|
||||
msgid "Username can't be empty"
|
||||
msgstr ""
|
||||
|
||||
msgid "Invalid email"
|
||||
msgstr ""
|
||||
|
||||
msgid "Password should be at least 8 characters long"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -15,8 +15,14 @@ extern crate rocket_contrib;
|
|||
extern crate rocket_csrf;
|
||||
extern crate rocket_i18n;
|
||||
extern crate rpassword;
|
||||
extern crate serde;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
#[macro_use]
|
||||
extern crate serde_json;
|
||||
extern crate validator;
|
||||
#[macro_use]
|
||||
extern crate validator_derive;
|
||||
extern crate webfinger;
|
||||
|
||||
use rocket_contrib::Template;
|
||||
|
|
|
@ -5,6 +5,8 @@ use rocket::{
|
|||
};
|
||||
use rocket_contrib::Template;
|
||||
use serde_json;
|
||||
use std::{collections::HashMap, borrow::Cow};
|
||||
use validator::{Validate, ValidationError, ValidationErrors};
|
||||
|
||||
use plume_common::activity_pub::ActivityStream;
|
||||
use plume_common::utils;
|
||||
|
@ -40,7 +42,9 @@ fn activity_details(name: String, conn: DbConn) -> ActivityStream<CustomGroup> {
|
|||
#[get("/blogs/new")]
|
||||
fn new(user: User) -> Template {
|
||||
Template::render("blogs/new", json!({
|
||||
"account": user
|
||||
"account": user,
|
||||
"errors": null,
|
||||
"form": null
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -49,21 +53,41 @@ fn new_auth() -> Flash<Redirect>{
|
|||
utils::requires_login("You need to be logged in order to create a new blog", uri!(new))
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
#[derive(FromForm, Validate, Serialize)]
|
||||
struct NewBlogForm {
|
||||
#[validate(custom(function = "valid_slug", message = "Invalid name"))]
|
||||
pub title: String
|
||||
}
|
||||
|
||||
fn valid_slug(title: &str) -> Result<(), ValidationError> {
|
||||
let slug = utils::make_actor_id(title.to_string());
|
||||
if slug.len() == 0 {
|
||||
Err(ValidationError::new("empty_slug"))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/blogs/new", data = "<data>")]
|
||||
fn create(conn: DbConn, data: LenientForm<NewBlogForm>, user: User) -> Redirect {
|
||||
fn create(conn: DbConn, data: LenientForm<NewBlogForm>, user: User) -> Result<Redirect, Template> {
|
||||
let form = data.get();
|
||||
let slug = utils::make_actor_id(form.title.to_string());
|
||||
|
||||
if Blog::find_local(&*conn, slug.clone()).is_some() || slug.len() == 0 {
|
||||
Redirect::to(uri!(new))
|
||||
} else {
|
||||
let mut errors = match form.validate() {
|
||||
Ok(_) => ValidationErrors::new(),
|
||||
Err(e) => e
|
||||
};
|
||||
if let Some(_) = Blog::find_local(&*conn, slug.clone()) {
|
||||
errors.add("title", ValidationError {
|
||||
code: Cow::from("existing_slug"),
|
||||
message: Some(Cow::from("A blog with the same name already exists.")),
|
||||
params: HashMap::new()
|
||||
});
|
||||
}
|
||||
|
||||
if errors.is_empty() {
|
||||
let blog = Blog::insert(&*conn, NewBlog::new_local(
|
||||
slug.to_string(),
|
||||
slug.clone(),
|
||||
form.title.to_string(),
|
||||
String::from(""),
|
||||
Instance::local_id(&*conn)
|
||||
|
@ -76,7 +100,14 @@ fn create(conn: DbConn, data: LenientForm<NewBlogForm>, user: User) -> Redirect
|
|||
is_owner: true
|
||||
});
|
||||
|
||||
Redirect::to(uri!(details: name = slug))
|
||||
Ok(Redirect::to(uri!(details: name = slug.clone())))
|
||||
} else {
|
||||
println!("{:?}", errors);
|
||||
Err(Template::render("blogs/new", json!({
|
||||
"account": user,
|
||||
"errors": errors.inner(),
|
||||
"form": form
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,9 @@ use rocket::{
|
|||
request::LenientForm,
|
||||
response::Redirect
|
||||
};
|
||||
use rocket_contrib::Template;
|
||||
use serde_json;
|
||||
use validator::Validate;
|
||||
|
||||
use plume_common::activity_pub::broadcast;
|
||||
use plume_models::{
|
||||
|
@ -15,30 +17,52 @@ use plume_models::{
|
|||
};
|
||||
use inbox::Inbox;
|
||||
|
||||
#[derive(FromForm, Debug)]
|
||||
#[derive(FromForm, Debug, Validate)]
|
||||
struct NewCommentForm {
|
||||
pub responding_to: Option<i32>,
|
||||
#[validate(length(min = "1", message = "Your comment can't be empty"))]
|
||||
pub content: String
|
||||
}
|
||||
|
||||
#[post("/~/<blog_name>/<slug>/comment", data = "<data>")]
|
||||
fn create(blog_name: String, slug: String, data: LenientForm<NewCommentForm>, user: User, conn: DbConn) -> Redirect {
|
||||
fn create(blog_name: String, slug: String, data: LenientForm<NewCommentForm>, user: User, conn: DbConn) -> Result<Redirect, Template> {
|
||||
let blog = Blog::find_by_fqn(&*conn, blog_name.clone()).unwrap();
|
||||
let post = Post::find_by_slug(&*conn, slug.clone(), blog.id).unwrap();
|
||||
let form = data.get();
|
||||
println!("form: {:?}", form);
|
||||
form.validate()
|
||||
.map(|_| {
|
||||
let (new_comment, id) = NewComment::build()
|
||||
.content(form.content.clone())
|
||||
.in_response_to_id(form.responding_to.clone())
|
||||
.post(post.clone())
|
||||
.author(user.clone())
|
||||
.create(&*conn);
|
||||
|
||||
let (new_comment, id) = NewComment::build()
|
||||
.content(form.content.clone())
|
||||
.in_response_to_id(form.responding_to.clone())
|
||||
.post(post)
|
||||
.author(user.clone())
|
||||
.create(&*conn);
|
||||
let instance = Instance::get_local(&*conn).unwrap();
|
||||
instance.received(&*conn, serde_json::to_value(new_comment.clone()).expect("JSON serialization error"))
|
||||
.expect("We are not compatible with ourselve: local broadcast failed (new comment)");
|
||||
broadcast(&user, new_comment, user.get_followers(&*conn));
|
||||
|
||||
let instance = Instance::get_local(&*conn).unwrap();
|
||||
instance.received(&*conn, serde_json::to_value(new_comment.clone()).expect("JSON serialization error"))
|
||||
.expect("We are not compatible with ourselve: local broadcast failed (new comment)");
|
||||
broadcast(&user, new_comment, user.get_followers(&*conn));
|
||||
Redirect::to(format!("/~/{}/{}/#comment-{}", blog_name, slug, id))
|
||||
})
|
||||
.map_err(|errors| {
|
||||
// TODO: de-duplicate this code
|
||||
let comments = Comment::list_by_post(&*conn, post.id);
|
||||
|
||||
Redirect::to(format!("/~/{}/{}/#comment-{}", blog_name, slug, id))
|
||||
Template::render("posts/details", json!({
|
||||
"author": post.get_authors(&*conn)[0].to_json(&*conn),
|
||||
"post": post,
|
||||
"blog": blog,
|
||||
"comments": comments.into_iter().map(|c| c.to_json(&*conn)).collect::<Vec<serde_json::Value>>(),
|
||||
"n_likes": post.get_likes(&*conn).len(),
|
||||
"has_liked": user.has_liked(&*conn, &post),
|
||||
"n_reshares": post.get_reshares(&*conn).len(),
|
||||
"has_reshared": user.has_reshared(&*conn, &post),
|
||||
"account": user,
|
||||
"date": &post.creation_date.timestamp(),
|
||||
"previous": form.responding_to.map(|r| Comment::get(&*conn, r).expect("Error retrieving previous comment").to_json(&*conn)),
|
||||
"user_fqn": user.get_fqn(&*conn),
|
||||
"errors": errors
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ use rocket::request::LenientForm;
|
|||
use rocket::response::{Redirect, Flash};
|
||||
use rocket_contrib::Template;
|
||||
use serde_json;
|
||||
use std::{collections::HashMap, borrow::Cow};
|
||||
use validator::{Validate, ValidationError, ValidationErrors};
|
||||
|
||||
use plume_common::activity_pub::{broadcast, ActivityStream};
|
||||
use plume_common::utils;
|
||||
|
@ -76,29 +78,54 @@ fn new(blog: String, user: User, conn: DbConn) -> Template {
|
|||
}))
|
||||
} else {
|
||||
Template::render("posts/new", json!({
|
||||
"account": user
|
||||
"account": user,
|
||||
"errors": null,
|
||||
"form": null
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
#[derive(FromForm, Validate, Serialize)]
|
||||
struct NewPostForm {
|
||||
#[validate(custom(function = "valid_slug", message = "Invalid title"))]
|
||||
pub title: String,
|
||||
pub content: String,
|
||||
pub license: String
|
||||
}
|
||||
|
||||
fn valid_slug(title: &str) -> Result<(), ValidationError> {
|
||||
let slug = title.to_string().to_kebab_case();
|
||||
if slug.len() == 0 {
|
||||
Err(ValidationError::new("empty_slug"))
|
||||
} else if slug == "new" {
|
||||
Err(ValidationError::new("invalid_slug"))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/~/<blog_name>/new", data = "<data>")]
|
||||
fn create(blog_name: String, data: LenientForm<NewPostForm>, user: User, conn: DbConn) -> Redirect {
|
||||
fn create(blog_name: String, data: LenientForm<NewPostForm>, user: User, conn: DbConn) -> Result<Redirect, Template> {
|
||||
let blog = Blog::find_by_fqn(&*conn, blog_name.to_string()).unwrap();
|
||||
let form = data.get();
|
||||
let slug = form.title.to_string().to_kebab_case();
|
||||
|
||||
let mut errors = match form.validate() {
|
||||
Ok(_) => ValidationErrors::new(),
|
||||
Err(e) => e
|
||||
};
|
||||
if let Some(_) = Post::find_by_slug(&*conn, slug.clone(), blog.id) {
|
||||
errors.add("title", ValidationError {
|
||||
code: Cow::from("existing_slug"),
|
||||
message: Some(Cow::from("A post with the same title already exists.")),
|
||||
params: HashMap::new()
|
||||
});
|
||||
}
|
||||
|
||||
if !user.is_author_in(&*conn, blog.clone()) {
|
||||
Redirect::to(uri!(super::blogs::details: name = blog_name))
|
||||
} else {
|
||||
if slug == "new" || Post::find_by_slug(&*conn, slug.clone(), blog.id).is_some() {
|
||||
Redirect::to(uri!(new: blog = blog_name))
|
||||
if errors.is_empty() {
|
||||
if !user.is_author_in(&*conn, blog.clone()) {
|
||||
// actually it's not "Ok"…
|
||||
Ok(Redirect::to(uri!(super::blogs::details: name = blog_name)))
|
||||
} else {
|
||||
let (content, mentions) = utils::md_to_html(form.content.to_string().as_ref());
|
||||
|
||||
|
@ -124,7 +151,13 @@ fn create(blog_name: String, data: LenientForm<NewPostForm>, user: User, conn: D
|
|||
let act = post.create_activity(&*conn);
|
||||
broadcast(&user, act, user.get_followers(&*conn));
|
||||
|
||||
Redirect::to(uri!(details: blog = blog_name, slug = slug))
|
||||
Ok(Redirect::to(uri!(details: blog = blog_name, slug = slug)))
|
||||
}
|
||||
} else {
|
||||
Err(Template::render("posts/new", json!({
|
||||
"account": user,
|
||||
"errors": errors.inner(),
|
||||
"form": form
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use gettextrs::gettext;
|
||||
use rocket::{
|
||||
http::{Cookie, Cookies, uri::Uri},
|
||||
response::{Redirect, status::NotFound},
|
||||
response::Redirect,
|
||||
request::{LenientForm,FlashMessage}
|
||||
};
|
||||
use rocket_contrib::Template;
|
||||
use validator::{Validate, ValidationError, ValidationErrors};
|
||||
|
||||
use plume_models::{
|
||||
db_conn::DbConn,
|
||||
|
@ -14,7 +14,9 @@ use plume_models::{
|
|||
#[get("/login")]
|
||||
fn new(user: Option<User>) -> Template {
|
||||
Template::render("session/login", json!({
|
||||
"account": user
|
||||
"account": user,
|
||||
"errors": null,
|
||||
"form": null
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -27,40 +29,50 @@ struct Message {
|
|||
fn new_message(user: Option<User>, message: Message) -> Template {
|
||||
Template::render("session/login", json!({
|
||||
"account": user,
|
||||
"message": message.m
|
||||
"message": message.m,
|
||||
"errors": null,
|
||||
"form": null
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
#[derive(FromForm)]
|
||||
#[derive(FromForm, Validate, Serialize)]
|
||||
struct LoginForm {
|
||||
#[validate(length(min = "1", message = "We need an email or a username to identify you"))]
|
||||
email_or_name: String,
|
||||
#[validate(length(min = "8", message = "Your password should be at least 8 characters long"))]
|
||||
password: String
|
||||
}
|
||||
|
||||
#[post("/login", data = "<data>")]
|
||||
fn create(conn: DbConn, data: LenientForm<LoginForm>, flash: Option<FlashMessage>, mut cookies: Cookies) -> Result<Redirect, NotFound<String>> {
|
||||
fn create(conn: DbConn, data: LenientForm<LoginForm>, flash: Option<FlashMessage>, mut cookies: Cookies) -> Result<Redirect, Template> {
|
||||
let form = data.get();
|
||||
let user = match User::find_by_email(&*conn, form.email_or_name.to_string()) {
|
||||
Some(usr) => Ok(usr),
|
||||
None => match User::find_local(&*conn, form.email_or_name.to_string()) {
|
||||
Some(usr) => Ok(usr),
|
||||
None => Err(gettext("Invalid username or password"))
|
||||
}
|
||||
let user = User::find_by_email(&*conn, form.email_or_name.to_string())
|
||||
.map(|u| Ok(u))
|
||||
.unwrap_or_else(|| User::find_local(&*conn, form.email_or_name.to_string()).map(|u| Ok(u)).unwrap_or(Err(())));
|
||||
|
||||
let mut errors = match form.validate() {
|
||||
Ok(_) => ValidationErrors::new(),
|
||||
Err(e) => e
|
||||
};
|
||||
match user {
|
||||
Ok(usr) => {
|
||||
if usr.auth(form.password.to_string()) {
|
||||
cookies.add_private(Cookie::new(AUTH_COOKIE, usr.id.to_string()));
|
||||
Ok(Redirect::to(Uri::new(flash
|
||||
.and_then(|f| if f.name() == "callback" { Some(f.msg().to_owned()) } else { None })
|
||||
.unwrap_or("/".to_owned()))
|
||||
))
|
||||
} else {
|
||||
Err(NotFound(gettext("Invalid username or password")))
|
||||
}
|
||||
},
|
||||
Err(e) => Err(NotFound(String::from(e)))
|
||||
if let Err(_) = user.clone() {
|
||||
errors.add("email_or_name", ValidationError::new("invalid_login"))
|
||||
} else if !user.clone().expect("User not found").auth(form.password.clone()) {
|
||||
errors.add("email_or_name", ValidationError::new("invalid_login"))
|
||||
}
|
||||
|
||||
if errors.is_empty() {
|
||||
cookies.add_private(Cookie::new(AUTH_COOKIE, user.unwrap().id.to_string()));
|
||||
Ok(Redirect::to(Uri::new(flash
|
||||
.and_then(|f| if f.name() == "callback" { Some(f.msg().to_owned()) } else { None })
|
||||
.unwrap_or("/".to_owned()))
|
||||
))
|
||||
} else {
|
||||
Err(Template::render("session/login", json!({
|
||||
"account": user,
|
||||
"errors": errors.inner(),
|
||||
"form": form
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ use rocket::{request::LenientForm,
|
|||
};
|
||||
use rocket_contrib::Template;
|
||||
use serde_json;
|
||||
use validator::{Validate, ValidationError};
|
||||
|
||||
use plume_common::activity_pub::{
|
||||
ActivityStream, broadcast, Id, IntoId,
|
||||
|
@ -120,7 +121,9 @@ fn activity_details(name: String, conn: DbConn) -> ActivityStream<CustomPerson>
|
|||
#[get("/users/new")]
|
||||
fn new(user: Option<User>) -> Template {
|
||||
Template::render("users/new", json!({
|
||||
"account": user
|
||||
"account": user,
|
||||
"errors": null,
|
||||
"form": null
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -157,40 +160,49 @@ fn update(_name: String, conn: DbConn, user: User, data: LenientForm<UpdateUserF
|
|||
Redirect::to(uri!(me))
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
#[derive(FromForm, Serialize, Validate)]
|
||||
#[validate(schema(function = "passwords_match", skip_on_field_errors = "false", message = "Passwords are not matching"))]
|
||||
struct NewUserForm {
|
||||
#[validate(length(min = "1", message = "Username can't be empty"))]
|
||||
username: String,
|
||||
#[validate(email(message = "Invalid email"))]
|
||||
email: String,
|
||||
#[validate(length(min = "8", message = "Password should be at least 8 characters long"))]
|
||||
password: String,
|
||||
#[validate(length(min = "8", message = "Password should be at least 8 characters long"))]
|
||||
password_confirmation: String
|
||||
}
|
||||
|
||||
#[post("/users/new", data = "<data>")]
|
||||
fn create(conn: DbConn, data: LenientForm<NewUserForm>) -> Result<Redirect, String> {
|
||||
let form = data.get();
|
||||
|
||||
if form.username.clone().len() < 1 {
|
||||
Err(String::from("Username is required"))
|
||||
} else if form.email.clone().len() < 1 {
|
||||
Err(String::from("Email is required"))
|
||||
} else if form.password.clone().len() < 8 {
|
||||
Err(String::from("Password should be at least 8 characters long"))
|
||||
} else if form.password == form.password_confirmation {
|
||||
NewUser::new_local(
|
||||
&*conn,
|
||||
form.username.to_string(),
|
||||
form.username.to_string(),
|
||||
false,
|
||||
String::from(""),
|
||||
form.email.to_string(),
|
||||
User::hash_pass(form.password.to_string())
|
||||
).update_boxes(&*conn);
|
||||
Ok(Redirect::to(uri!(super::session::new)))
|
||||
fn passwords_match(form: &NewUserForm) -> Result<(), ValidationError> {
|
||||
if form.password != form.password_confirmation {
|
||||
Err(ValidationError::new("password_match"))
|
||||
} else {
|
||||
Err(String::from("Passwords don't match"))
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/users/new", data = "<data>")]
|
||||
fn create(conn: DbConn, data: LenientForm<NewUserForm>) -> Result<Redirect, Template> {
|
||||
let form = data.get();
|
||||
form.validate()
|
||||
.map(|_| {
|
||||
NewUser::new_local(
|
||||
&*conn,
|
||||
form.username.to_string(),
|
||||
form.username.to_string(),
|
||||
false,
|
||||
String::from(""),
|
||||
form.email.to_string(),
|
||||
User::hash_pass(form.password.to_string())
|
||||
).update_boxes(&*conn);
|
||||
Redirect::to(uri!(super::session::new))
|
||||
})
|
||||
.map_err(|e| Template::render("users/new", json!({
|
||||
"errors": e.inner(),
|
||||
"form": form
|
||||
})))
|
||||
}
|
||||
|
||||
#[get("/@/<name>/outbox")]
|
||||
fn outbox(name: String, conn: DbConn) -> ActivityStream<OrderedCollection> {
|
||||
let user = User::find_local(&*conn, name).unwrap();
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{% extends "base" %}
|
||||
{% import "macros" as macros %}
|
||||
|
||||
{% block title %}
|
||||
{{ "New blog" | _ }}
|
||||
|
@ -7,8 +8,8 @@
|
|||
{% block content %}
|
||||
<h1>{{ "Create a blog" | _ }}</h1>
|
||||
<form method="post">
|
||||
<label for="title">{{ "Title" | _ }}</label>
|
||||
<input type="text" id="title" name="title" />
|
||||
{{ macros::input(name="title", label="Title", errors=errors, form=form, props='required minlength="1"') }}
|
||||
|
||||
<input type="submit" value="{{ "Create blog" | _ }}"/>
|
||||
</form>
|
||||
{% endblock content %}
|
||||
|
|
|
@ -21,3 +21,12 @@
|
|||
</p>
|
||||
</div>
|
||||
{% endmacro post_card %}
|
||||
{% macro input(name, label, errors, form, type="text", props="") %}
|
||||
<label for="{{ name }}">{{ label | _ }}</label>
|
||||
{% if errors is defined and errors[name] %}
|
||||
{% for err in errors[name] %}
|
||||
<p class="error">{{ err.message | default(value="Unknown error") | _ }}</p>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<input type="{{ type }}" id="{{ name }}" name="{{ name }}" value="{{ form[name] | default(value="") }}" {{ props | safe }}/>
|
||||
{% endmacro input %}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{% extends "base" %}
|
||||
{% import "macros" as macros %}
|
||||
|
||||
{% block title %}
|
||||
{{ "New post" | _ }}
|
||||
|
@ -7,11 +8,17 @@
|
|||
{% block content %}
|
||||
<h1>{{ "Create a post" | _ }}</h1>
|
||||
<form class="new-post" method="post">
|
||||
<input type="text" class="title" name="title" placeholder="{{ "Title" | _ }}">
|
||||
<textarea name="content" placeholder="{{ "Content" | _ }}"></textarea>
|
||||
{{ macros::input(name="title", label="Title", errors=errors, form=form) }}
|
||||
|
||||
<label for="license">{{ "License" | _ }}</label>
|
||||
<input type="text" id="licence" name="license" />
|
||||
{% if errors is defined and errors.content %}
|
||||
{% for err in errors.content %}
|
||||
<p class="error">{{ err.message | default(value="Unknown error") | _ }}</p>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<textarea id="content" name="content" placeholder="{{ "Content" | _ }}" value="{{ form.content | default(value="") }}"></textarea>
|
||||
|
||||
{{ macros::input(name="license", label="License", errors=errors, form=form) }}
|
||||
|
||||
<input type="submit" value="{{ "Publish" | _ }}" />
|
||||
</form>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{% extends "base" %}
|
||||
{% import "macros" as macros %}
|
||||
|
||||
{% block title %}
|
||||
{{ "Login" | _ }}
|
||||
|
@ -10,11 +11,8 @@
|
|||
<p>{{ message }}</p>
|
||||
{% endif %}
|
||||
<form method="post">
|
||||
<label for="email_or_name">{{ "Username or email" | _ }}</label>
|
||||
<input type="text" id="email_or_name" name="email_or_name" />
|
||||
|
||||
<label for="password">{{ "Password" | _ }}</label>
|
||||
<input type="password" id="password" name="password" />
|
||||
{{ macros::input(name="email_or_name", label="Username or email", errors=errors, form=form, props='minlenght="1"') }}
|
||||
{{ macros::input(name="password", label="Password", errors=errors, form=form, type="password", props='minlenght="8"') }}
|
||||
|
||||
<input type="submit" value="{{ "Login" | _ }}" />
|
||||
</form>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{% extends "base" %}
|
||||
{% import "macros" as macros %}
|
||||
|
||||
{% block title %}
|
||||
{{ "New Account" | _ }}
|
||||
|
@ -7,17 +8,10 @@
|
|||
{% block content %}
|
||||
<h1>{{ "Create an account" | _ }}</h1>
|
||||
<form method="post">
|
||||
<label for="username">{{ "Username" | _ }}</label>
|
||||
<input type="text" id="username" name="username" />
|
||||
|
||||
<label for="email">{{ "Email" | _ }}</label>
|
||||
<input type="email" id="email" name="email" />
|
||||
|
||||
<label for="password">{{ "Password" | _ }}</label>
|
||||
<input type="password" id="password" name="password" />
|
||||
|
||||
<label for="password_confirmation">{{ "Password confirmation" | _ }}</label>
|
||||
<input type="password" id="password_confirmation" name="password_confirmation" />
|
||||
{{ macros::input(name="username", label="Username", errors=errors, form=form, props='minlenght="1"') }}
|
||||
{{ macros::input(name="email", label="Email", errors=errors, form=form, type="email") }}
|
||||
{{ macros::input(name="password", label="Password", errors=errors, form=form, type="password", props='minlenght="8"') }}
|
||||
{{ macros::input(name="password_confirmation", label="Password confirmation", errors=errors, form=form, type="password", props='minlenght="8"') }}
|
||||
|
||||
<input type="submit" value="{{ "Create account" | _ }}" />
|
||||
</form>
|
||||
|
|
Loading…
Reference in a new issue