Add a fqn field to blogs and users (#457)

Fixes #319
This commit is contained in:
Baptiste Gelez 2019-03-06 18:28:10 +01:00 committed by GitHub
parent eff2698664
commit fe6e69d7c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 258 additions and 141 deletions

View File

@ -0,0 +1,3 @@
-- This file should undo anything in `up.sql`
ALTER TABLE blogs DROP COLUMN fqn;
ALTER TABLE users DROP COLUMN fqn;

View File

@ -0,0 +1,18 @@
-- Your SQL goes here
ALTER TABLE blogs ADD COLUMN fqn TEXT NOT NULL DEFAULT '';
UPDATE blogs SET fqn =
(CASE WHEN (SELECT local FROM instances WHERE id = instance_id) THEN
actor_id
ELSE
(actor_id || '@' || (SELECT public_domain FROM instances WHERE id = instance_id LIMIT 1))
END)
WHERE fqn = '';
ALTER TABLE users ADD COLUMN fqn TEXT NOT NULL DEFAULT '';
UPDATE users SET fqn =
(CASE WHEN (SELECT local FROM instances WHERE id = instance_id) THEN
username
ELSE
(username || '@' || (SELECT public_domain FROM instances WHERE id = instance_id LIMIT 1))
END)
WHERE fqn = '';

View File

@ -0,0 +1,77 @@
-- This file should undo anything in `up.sql`
CREATE TABLE blogs_no_fqn (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
actor_id VARCHAR NOT NULL,
title VARCHAR NOT NULL,
summary TEXT NOT NULL DEFAULT '',
outbox_url VARCHAR NOT NULL UNIQUE,
inbox_url VARCHAR NOT NULL UNIQUE,
instance_id INTEGER REFERENCES instances(id) ON DELETE CASCADE NOT NULL,
creation_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
ap_url text not null default '' UNIQUE,
private_key TEXT,
public_key TEXT NOT NULL DEFAULT '',
CONSTRAINT blog_unique UNIQUE (actor_id, instance_id)
);
INSERT INTO blogs_no_fqn SELECT
id,
actor_id,
title,
summary,
outbox_url,
inbox_url,
instance_id,
creation_date,
ap_url,
private_key,
public_key
FROM blogs;
DROP TABLE blogs;
ALTER TABLE blogs_no_fqn RENAME TO blogs;
CREATE TABLE users_no_fqn (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
username VARCHAR NOT NULL,
display_name VARCHAR NOT NULL DEFAULT '',
outbox_url VARCHAR NOT NULL UNIQUE,
inbox_url VARCHAR NOT NULL UNIQUE,
is_admin BOOLEAN NOT NULL DEFAULT 'f',
summary TEXT NOT NULL DEFAULT '',
email TEXT,
hashed_password TEXT,
instance_id INTEGER REFERENCES instances(id) ON DELETE CASCADE NOT NULL,
creation_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
ap_url TEXT NOT NULL default '' UNIQUE,
private_key TEXT,
public_key TEXT NOT NULL DEFAULT '',
shared_inbox_url VARCHAR,
followers_endpoint VARCHAR NOT NULL DEFAULT '' UNIQUE,
avatar_id INTEGER REFERENCES medias(id) ON DELETE SET NULL,
last_fetched_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT blog_authors_unique UNIQUE (username, instance_id)
);
INSERT INTO users_no_fqn SELECT
id,
username,
display_name,
outbox_url,
inbox_url,
is_admin,
summary,
email,
hashed_password,
instance_id,
creation_date,
ap_url,
private_key,
public_key,
shared_inbox_url,
followers_endpoint,
avatar_id,
last_fetched_date
FROM users;
DROP TABLE users;
ALTER TABLE users_no_fqn RENAME TO users;

View File

@ -0,0 +1,18 @@
-- Your SQL goes here
ALTER TABLE blogs ADD COLUMN fqn TEXT NOT NULL DEFAULT '';
UPDATE blogs SET fqn =
(CASE WHEN (SELECT local FROM instances WHERE id = instance_id) THEN
actor_id
ELSE
(actor_id || '@' || (SELECT public_domain FROM instances WHERE id = instance_id LIMIT 1))
END)
WHERE fqn = '';
ALTER TABLE users ADD COLUMN fqn TEXT NOT NULL DEFAULT '';
UPDATE users SET fqn =
(CASE WHEN (SELECT local FROM instances WHERE id = instance_id) THEN
username
ELSE
(username || '@' || (SELECT public_domain FROM instances WHERE id = instance_id LIMIT 1))
END)
WHERE fqn = '';

View File

@ -43,6 +43,7 @@ pub struct Blog {
pub ap_url: String, pub ap_url: String,
pub private_key: Option<String>, pub private_key: Option<String>,
pub public_key: String, pub public_key: String,
pub fqn: String,
} }
#[derive(Default, Insertable)] #[derive(Default, Insertable)]
@ -87,6 +88,15 @@ impl Blog {
"", "",
); );
} }
if inserted.fqn.is_empty() {
if instance.local {
inserted.fqn = inserted.actor_id.clone();
} else {
inserted.fqn = format!("{}@{}", inserted.actor_id, instance.public_domain);
}
}
inserted.save_changes(conn).map_err(Error::from) inserted.save_changes(conn).map_err(Error::from)
}); });
get!(blogs); get!(blogs);
@ -129,19 +139,17 @@ impl Blog {
.map_err(Error::from) .map_err(Error::from)
} }
pub fn find_local(conn: &Connection, name: &str) -> Result<Blog> {
Blog::find_by_name(conn, name, Instance::get_local(conn)?.id)
}
pub fn find_by_fqn(conn: &Connection, fqn: &str) -> Result<Blog> { pub fn find_by_fqn(conn: &Connection, fqn: &str) -> Result<Blog> {
let mut split_fqn = fqn.split('@'); let from_db = blogs::table
let actor = split_fqn.next().ok_or(Error::InvalidValue)?; .filter(blogs::fqn.eq(fqn))
if let Some(domain) = split_fqn.next() { // remote blog .limit(1)
Instance::find_by_domain(conn, domain) .load::<Blog>(conn)?
.and_then(|instance| Blog::find_by_name(conn, actor, instance.id)) .into_iter()
.or_else(|_| Blog::fetch_from_webfinger(conn, fqn)) .next();
} else { // local blog if let Some(from_db) = from_db {
Blog::find_local(conn, actor) Ok(from_db)
} else {
Blog::fetch_from_webfinger(conn, fqn)
} }
} }
@ -338,18 +346,6 @@ impl Blog {
}) })
} }
pub fn get_fqn(&self, conn: &Connection) -> String {
if self.instance_id == Instance::get_local(conn).ok().expect("Blog::get_fqn: local instance error").id {
self.actor_id.clone()
} else {
format!(
"{}@{}",
self.actor_id,
self.get_instance(conn).ok().expect("Blog::get_fqn: instance error").public_domain
)
}
}
pub fn delete(&self, conn: &Connection, searcher: &Searcher) -> Result<()> { pub fn delete(&self, conn: &Connection, searcher: &Searcher) -> Result<()> {
for post in Post::get_for_blog(conn, &self)? { for post in Post::get_for_blog(conn, &self)? {
post.delete(&(conn, searcher))?; post.delete(&(conn, searcher))?;
@ -649,7 +645,7 @@ pub(crate) mod tests {
).unwrap(); ).unwrap();
assert_eq!( assert_eq!(
Blog::find_local(conn, "SomeName").unwrap().id, Blog::find_by_fqn(conn, "SomeName").unwrap().id,
blog.id blog.id
); );
@ -673,7 +669,7 @@ pub(crate) mod tests {
).unwrap(), ).unwrap(),
).unwrap(); ).unwrap();
assert_eq!(blog.get_fqn(conn), "SomeName"); assert_eq!(blog.fqn, "SomeName");
Ok(()) Ok(())
}); });

View File

@ -71,7 +71,7 @@ impl Mention {
.set_href_string(user.ap_url.clone())?; .set_href_string(user.ap_url.clone())?;
mention mention
.link_props .link_props
.set_name_string(format!("@{}", user.get_fqn(conn)))?; .set_name_string(format!("@{}", user.fqn))?;
Ok(mention) Ok(mention)
} }

View File

@ -81,7 +81,7 @@ impl Notification {
pub fn get_url(&self, conn: &Connection) -> Option<String> { pub fn get_url(&self, conn: &Connection) -> Option<String> {
match self.kind.as_ref() { match self.kind.as_ref() {
notification_kind::COMMENT => self.get_post(conn).and_then(|p| Some(format!("{}#comment-{}", p.url(conn).ok()?, self.object_id))), notification_kind::COMMENT => self.get_post(conn).and_then(|p| Some(format!("{}#comment-{}", p.url(conn).ok()?, self.object_id))),
notification_kind::FOLLOW => Some(format!("/@/{}/", self.get_actor(conn).ok()?.get_fqn(conn))), notification_kind::FOLLOW => Some(format!("/@/{}/", self.get_actor(conn).ok()?.fqn)),
notification_kind::MENTION => Mention::get(conn, self.object_id).and_then(|mention| notification_kind::MENTION => Mention::get(conn, self.object_id).and_then(|mention|
mention.get_post(conn).and_then(|p| p.url(conn)) mention.get_post(conn).and_then(|p| p.url(conn))
.or_else(|_| { .or_else(|_| {

View File

@ -269,7 +269,7 @@ impl Post {
post.ap_url = ap_url(&format!( post.ap_url = ap_url(&format!(
"{}/~/{}/{}/", "{}/~/{}/{}/",
*BASE_URL, *BASE_URL,
post.get_blog(conn)?.get_fqn(conn), post.get_blog(conn)?.fqn,
post.slug post.slug
)); ));
let _: Post = post.save_changes(conn)?; let _: Post = post.save_changes(conn)?;
@ -850,7 +850,7 @@ impl Post {
pub fn url(&self, conn: &Connection) -> Result<String> { pub fn url(&self, conn: &Connection) -> Result<String> {
let blog = self.get_blog(conn)?; let blog = self.get_blog(conn)?;
Ok(format!("/~/{}/{}", blog.get_fqn(conn), self.slug)) Ok(format!("/~/{}/{}", blog.fqn, self.slug))
} }
pub fn cover_url(&self, conn: &Connection) -> Option<String> { pub fn cover_url(&self, conn: &Connection) -> Option<String> {

View File

@ -43,6 +43,7 @@ table! {
ap_url -> Text, ap_url -> Text,
private_key -> Nullable<Text>, private_key -> Nullable<Text>,
public_key -> Text, public_key -> Text,
fqn -> Text,
} }
} }
@ -201,6 +202,7 @@ table! {
followers_endpoint -> Varchar, followers_endpoint -> Varchar,
avatar_id -> Nullable<Int4>, avatar_id -> Nullable<Int4>,
last_fetched_date -> Timestamp, last_fetched_date -> Timestamp,
fqn -> Text,
} }
} }

View File

@ -144,7 +144,7 @@ impl Searcher {
let writer = writer.as_mut().unwrap(); let writer = writer.as_mut().unwrap();
writer.add_document(doc!( writer.add_document(doc!(
post_id => i64::from(post.id), post_id => i64::from(post.id),
author => post.get_authors(conn)?.into_iter().map(|u| u.get_fqn(conn)).join(" "), author => post.get_authors(conn)?.into_iter().map(|u| u.fqn).join(" "),
creation_date => i64::from(post.creation_date.num_days_from_ce()), creation_date => i64::from(post.creation_date.num_days_from_ce()),
instance => Instance::get(conn, post.get_blog(conn)?.instance_id)?.public_domain.clone(), instance => Instance::get(conn, post.get_blog(conn)?.instance_id)?.public_domain.clone(),
tag => Tag::for_post(conn, post.id)?.into_iter().map(|t| t.tag).join(" "), tag => Tag::for_post(conn, post.id)?.into_iter().map(|t| t.tag).join(" "),

View File

@ -64,6 +64,7 @@ pub struct User {
pub followers_endpoint: String, pub followers_endpoint: String,
pub avatar_id: Option<i32>, pub avatar_id: Option<i32>,
pub last_fetched_date: NaiveDateTime, pub last_fetched_date: NaiveDateTime,
pub fqn: String,
} }
#[derive(Default, Insertable)] #[derive(Default, Insertable)]
@ -119,8 +120,7 @@ impl User {
if inserted.shared_inbox_url.is_none() { if inserted.shared_inbox_url.is_none() {
inserted.shared_inbox_url = Some(ap_url(&format!( inserted.shared_inbox_url = Some(ap_url(&format!(
"{}/inbox", "{}/inbox",
Instance::get_local(conn)? instance.public_domain
.public_domain
))); )));
} }
@ -132,6 +132,14 @@ impl User {
); );
} }
if inserted.fqn.is_empty() {
if instance.local {
inserted.fqn = inserted.username.clone();
} else {
inserted.fqn = format!("{}@{}", inserted.username, instance.public_domain);
}
}
inserted.save_changes(conn).map_err(Error::from) inserted.save_changes(conn).map_err(Error::from)
}); });
get!(users); get!(users);
@ -218,19 +226,17 @@ impl User {
.map_err(Error::from) .map_err(Error::from)
} }
pub fn find_local(conn: &Connection, username: &str) -> Result<User> {
User::find_by_name(conn, username, Instance::get_local(conn)?.id)
}
pub fn find_by_fqn(conn: &Connection, fqn: &str) -> Result<User> { pub fn find_by_fqn(conn: &Connection, fqn: &str) -> Result<User> {
let mut split_fqn = fqn.split('@'); let from_db = users::table
let username = split_fqn.next().ok_or(Error::InvalidValue)?; .filter(users::fqn.eq(fqn))
if let Some(domain) = split_fqn.next() { // remote user .limit(1)
Instance::find_by_domain(conn, domain) .load::<User>(conn)?
.and_then(|instance| User::find_by_name(conn, username, instance.id)) .into_iter()
.or_else(|_| User::fetch_from_webfinger(conn, fqn)) .next();
} else { // local user if let Some(from_db) = from_db {
User::find_local(conn, username) Ok(from_db)
} else {
User::fetch_from_webfinger(conn, fqn)
} }
} }
@ -518,18 +524,6 @@ impl User {
.collect::<Vec<serde_json::Value>>()) .collect::<Vec<serde_json::Value>>())
} }
pub fn get_fqn(&self, conn: &Connection) -> String {
if self.instance_id == Instance::get_local(conn).ok().expect("User::get_fqn: instance error").id {
self.username.clone()
} else {
format!(
"{}@{}",
self.username,
self.get_instance(conn).ok().expect("User::get_fqn: instance error").public_domain
)
}
}
pub fn get_followers(&self, conn: &Connection) -> Result<Vec<User>> { pub fn get_followers(&self, conn: &Connection) -> Result<Vec<User>> {
use schema::follows; use schema::follows;
let follows = Follow::belonging_to(self).select(follows::follower_id); let follows = Follow::belonging_to(self).select(follows::follower_id);
@ -805,11 +799,11 @@ impl User {
(Utc::now().naive_utc() - self.last_fetched_date).num_days() > 1 (Utc::now().naive_utc() - self.last_fetched_date).num_days() > 1
} }
pub fn name(&self, conn: &Connection) -> String { pub fn name(&self) -> String {
if !self.display_name.is_empty() { if !self.display_name.is_empty() {
self.display_name.clone() self.display_name.clone()
} else { } else {
self.get_fqn(conn) self.fqn.clone()
} }
} }
} }
@ -987,7 +981,7 @@ pub(crate) mod tests {
); );
assert_eq!( assert_eq!(
test_user.id, test_user.id,
User::find_by_fqn(conn, &test_user.get_fqn(conn)).unwrap().id User::find_by_fqn(conn, &test_user.fqn).unwrap().id
); );
assert_eq!( assert_eq!(
test_user.id, test_user.id,

View File

@ -317,8 +317,9 @@ msgstr ""
msgid "Articles" msgid "Articles"
msgstr "المقالات" msgstr "المقالات"
#, fuzzy
msgid "Subscribers" msgid "Subscribers"
msgstr "" msgstr "الوصف"
#, fuzzy #, fuzzy
msgid "Subscriptions" msgid "Subscriptions"

View File

@ -302,8 +302,9 @@ msgid ""
"can, however, find a different one." "can, however, find a different one."
msgstr "" msgstr ""
#, fuzzy
msgid "Articles" msgid "Articles"
msgstr "" msgstr "Nueva publicación"
msgid "Subscribers" msgid "Subscribers"
msgstr "" msgstr ""

View File

@ -336,8 +336,9 @@ msgstr ""
msgid "Articles" msgid "Articles"
msgstr "Articles" msgstr "Articles"
#, fuzzy
msgid "Subscribers" msgid "Subscribers"
msgstr "" msgstr "Description"
#, fuzzy #, fuzzy
msgid "Subscriptions" msgid "Subscriptions"

View File

@ -332,8 +332,9 @@ msgstr ""
msgid "Articles" msgid "Articles"
msgstr "Artigos" msgstr "Artigos"
#, fuzzy
msgid "Subscribers" msgid "Subscribers"
msgstr "" msgstr "Descrición"
#, fuzzy #, fuzzy
msgid "Subscriptions" msgid "Subscriptions"

View File

@ -335,8 +335,9 @@ msgstr ""
msgid "Articles" msgid "Articles"
msgstr "Articoli" msgstr "Articoli"
#, fuzzy
msgid "Subscribers" msgid "Subscribers"
msgstr "" msgstr "Descrizione"
#, fuzzy #, fuzzy
msgid "Subscriptions" msgid "Subscriptions"

View File

@ -323,8 +323,9 @@ msgstr ""
msgid "Articles" msgid "Articles"
msgstr "記事" msgstr "記事"
#, fuzzy
msgid "Subscribers" msgid "Subscribers"
msgstr "" msgstr "説明"
#, fuzzy #, fuzzy
msgid "Subscriptions" msgid "Subscriptions"

View File

@ -336,8 +336,9 @@ msgstr ""
msgid "Articles" msgid "Articles"
msgstr "artikler" msgstr "artikler"
#, fuzzy
msgid "Subscribers" msgid "Subscribers"
msgstr "" msgstr "Lang beskrivelse"
#, fuzzy #, fuzzy
msgid "Subscriptions" msgid "Subscriptions"

View File

@ -301,8 +301,9 @@ msgstr ""
msgid "Articles" msgid "Articles"
msgstr "Artykuły" msgstr "Artykuły"
#, fuzzy
msgid "Subscribers" msgid "Subscribers"
msgstr "" msgstr "Opis"
#, fuzzy #, fuzzy
msgid "Subscriptions" msgid "Subscriptions"

View File

@ -36,11 +36,11 @@ msgstr ""
msgid "{0}'s avatar" msgid "{0}'s avatar"
msgstr "" msgstr ""
# src/routes/blogs.rs:65 # src/routes/blogs.rs:64
msgid "You need to be logged in order to create a new blog" msgid "You need to be logged in order to create a new blog"
msgstr "" msgstr ""
# src/routes/blogs.rs:135 # src/routes/blogs.rs:134
msgid "You are not allowed to delete this blog." msgid "You are not allowed to delete this blog."
msgstr "" msgstr ""

View File

@ -317,8 +317,9 @@ msgstr ""
msgid "Articles" msgid "Articles"
msgstr "Artigos" msgstr "Artigos"
#, fuzzy
msgid "Subscribers" msgid "Subscribers"
msgstr "" msgstr "Descrição"
#, fuzzy #, fuzzy
msgid "Subscriptions" msgid "Subscriptions"

View File

@ -339,8 +339,9 @@ msgstr ""
msgid "Articles" msgid "Articles"
msgstr "Статьи" msgstr "Статьи"
#, fuzzy
msgid "Subscribers" msgid "Subscribers"
msgstr "" msgstr "Описание"
#, fuzzy #, fuzzy
msgid "Subscriptions" msgid "Subscriptions"

View File

@ -49,7 +49,7 @@ pub struct OAuthRequest {
pub fn oauth(query: Form<OAuthRequest>, conn: DbConn) -> Result<Json<serde_json::Value>, ApiError> { pub fn oauth(query: Form<OAuthRequest>, conn: DbConn) -> Result<Json<serde_json::Value>, ApiError> {
let app = App::find_by_client_id(&*conn, &query.client_id)?; let app = App::find_by_client_id(&*conn, &query.client_id)?;
if app.client_secret == query.client_secret { if app.client_secret == query.client_secret {
if let Ok(user) = User::find_local(&*conn, &query.username) { if let Ok(user) = User::find_by_fqn(&*conn, &query.username) {
if user.auth(&query.password) { if user.auth(&query.password) {
let token = ApiToken::insert(&*conn, NewApiToken { let token = ApiToken::insert(&*conn, NewApiToken {
app_id: app.id, app_id: app.id,

View File

@ -34,7 +34,6 @@ pub fn details(intl: I18n, name: String, conn: DbConn, user: Option<User>, page:
Ok(render!(blogs::details( Ok(render!(blogs::details(
&(&*conn, &intl.catalog, user.clone()), &(&*conn, &intl.catalog, user.clone()),
blog.clone(), blog.clone(),
blog.get_fqn(&*conn),
authors, authors,
articles_count, articles_count,
page.0, page.0,
@ -46,7 +45,7 @@ pub fn details(intl: I18n, name: String, conn: DbConn, user: Option<User>, page:
#[get("/~/<name>", rank = 1)] #[get("/~/<name>", rank = 1)]
pub fn activity_details(name: String, conn: DbConn, _ap: ApRequest) -> Option<ActivityStream<CustomGroup>> { pub fn activity_details(name: String, conn: DbConn, _ap: ApRequest) -> Option<ActivityStream<CustomGroup>> {
let blog = Blog::find_local(&*conn, &name).ok()?; let blog = Blog::find_by_fqn(&*conn, &name).ok()?;
Some(ActivityStream::new(blog.to_activity(&*conn).ok()?)) Some(ActivityStream::new(blog.to_activity(&*conn).ok()?))
} }
@ -90,7 +89,7 @@ pub fn create(conn: DbConn, form: LenientForm<NewBlogForm>, user: User, intl: I1
Ok(_) => ValidationErrors::new(), Ok(_) => ValidationErrors::new(),
Err(e) => e Err(e) => e
}; };
if Blog::find_local(&*conn, &slug).is_ok() { if Blog::find_by_fqn(&*conn, &slug).is_ok() {
errors.add("title", ValidationError { errors.add("title", ValidationError {
code: Cow::from("existing_slug"), code: Cow::from("existing_slug"),
message: Some(Cow::from("A blog with the same name already exists.")), message: Some(Cow::from("A blog with the same name already exists.")),
@ -124,7 +123,7 @@ pub fn create(conn: DbConn, form: LenientForm<NewBlogForm>, user: User, intl: I1
#[post("/~/<name>/delete")] #[post("/~/<name>/delete")]
pub fn delete(conn: DbConn, name: String, user: Option<User>, intl: I18n, searcher: Searcher) -> Result<Redirect, Ructe>{ pub fn delete(conn: DbConn, name: String, user: Option<User>, intl: I18n, searcher: Searcher) -> Result<Redirect, Ructe>{
let blog = Blog::find_local(&*conn, &name).expect("blog::delete: blog not found"); let blog = Blog::find_by_fqn(&*conn, &name).expect("blog::delete: blog not found");
if user.clone().and_then(|u| u.is_author_in(&*conn, &blog).ok()).unwrap_or(false) { if user.clone().and_then(|u| u.is_author_in(&*conn, &blog).ok()).unwrap_or(false) {
blog.delete(&conn, &searcher).expect("blog::expect: deletion error"); blog.delete(&conn, &searcher).expect("blog::expect: deletion error");
Ok(Redirect::to(uri!(super::instance::index))) Ok(Redirect::to(uri!(super::instance::index)))
@ -139,7 +138,7 @@ pub fn delete(conn: DbConn, name: String, user: Option<User>, intl: I18n, search
#[get("/~/<name>/outbox")] #[get("/~/<name>/outbox")]
pub fn outbox(name: String, conn: DbConn) -> Option<ActivityStream<OrderedCollection>> { pub fn outbox(name: String, conn: DbConn) -> Option<ActivityStream<OrderedCollection>> {
let blog = Blog::find_local(&*conn, &name).ok()?; let blog = Blog::find_by_fqn(&*conn, &name).ok()?;
Some(blog.outbox(&*conn).ok()?) Some(blog.outbox(&*conn).ok()?)
} }

View File

@ -46,14 +46,14 @@ pub fn details(blog: String, slug: String, conn: DbConn, user: Option<User>, res
warning: previous.clone().map(|p| p.spoiler_text).unwrap_or_default(), warning: previous.clone().map(|p| p.spoiler_text).unwrap_or_default(),
content: previous.clone().and_then(|p| Some(format!( content: previous.clone().and_then(|p| Some(format!(
"@{} {}", "@{} {}",
p.get_author(&*conn).ok()?.get_fqn(&*conn), p.get_author(&*conn).ok()?.fqn,
Mention::list_for_comment(&*conn, p.id).ok()? Mention::list_for_comment(&*conn, p.id).ok()?
.into_iter() .into_iter()
.filter_map(|m| { .filter_map(|m| {
let user = user.clone(); let user = user.clone();
if let Ok(mentioned) = m.get_mentioned(&*conn) { if let Ok(mentioned) = m.get_mentioned(&*conn) {
if user.is_none() || mentioned.id != user.expect("posts::details_response: user error while listing mentions").id { if user.is_none() || mentioned.id != user.expect("posts::details_response: user error while listing mentions").id {
Some(format!("@{}", mentioned.get_fqn(&*conn))) Some(format!("@{}", mentioned.fqn))
} else { } else {
None None
} }

View File

@ -40,7 +40,7 @@ pub struct LoginForm {
#[post("/login", data = "<form>")] #[post("/login", data = "<form>")]
pub fn create(conn: DbConn, form: LenientForm<LoginForm>, flash: Option<FlashMessage>, mut cookies: Cookies, intl: I18n) -> Result<Redirect, Ructe> { pub fn create(conn: DbConn, form: LenientForm<LoginForm>, flash: Option<FlashMessage>, mut cookies: Cookies, intl: I18n) -> Result<Redirect, Ructe> {
let user = User::find_by_email(&*conn, &form.email_or_name) let user = User::find_by_email(&*conn, &form.email_or_name)
.or_else(|_| User::find_local(&*conn, &form.email_or_name)); .or_else(|_| User::find_by_fqn(&*conn, &form.email_or_name));
let mut errors = match form.validate() { let mut errors = match form.validate() {
Ok(_) => ValidationErrors::new(), Ok(_) => ValidationErrors::new(),
Err(e) => e Err(e) => e

View File

@ -203,7 +203,7 @@ pub fn activity_details(
conn: DbConn, conn: DbConn,
_ap: ApRequest, _ap: ApRequest,
) -> Option<ActivityStream<CustomPerson>> { ) -> Option<ActivityStream<CustomPerson>> {
let user = User::find_local(&*conn, &name).ok()?; let user = User::find_by_fqn(&*conn, &name).ok()?;
Some(ActivityStream::new(user.to_activity(&*conn).ok()?)) Some(ActivityStream::new(user.to_activity(&*conn).ok()?))
} }
@ -369,7 +369,7 @@ pub fn create(conn: DbConn, form: LenientForm<NewUserForm>, intl: I18n) -> Resul
#[get("/@/<name>/outbox")] #[get("/@/<name>/outbox")]
pub fn outbox(name: String, conn: DbConn) -> Option<ActivityStream<OrderedCollection>> { pub fn outbox(name: String, conn: DbConn) -> Option<ActivityStream<OrderedCollection>> {
let user = User::find_local(&*conn, &name).ok()?; let user = User::find_by_fqn(&*conn, &name).ok()?;
user.outbox(&*conn).ok() user.outbox(&*conn).ok()
} }
@ -381,7 +381,7 @@ pub fn inbox(
headers: Headers, headers: Headers,
searcher: Searcher, searcher: Searcher,
) -> Result<String, Option<status::BadRequest<&'static str>>> { ) -> Result<String, Option<status::BadRequest<&'static str>>> {
let user = User::find_local(&*conn, &name).map_err(|_| None)?; let user = User::find_by_fqn(&*conn, &name).map_err(|_| None)?;
let act = data.1.into_inner(); let act = data.1.into_inner();
let sig = data.0; let sig = data.0;
@ -429,7 +429,7 @@ pub fn ap_followers(
conn: DbConn, conn: DbConn,
_ap: ApRequest, _ap: ApRequest,
) -> Option<ActivityStream<OrderedCollection>> { ) -> Option<ActivityStream<OrderedCollection>> {
let user = User::find_local(&*conn, &name).ok()?; let user = User::find_by_fqn(&*conn, &name).ok()?;
let followers = user let followers = user
.get_followers(&*conn).ok()? .get_followers(&*conn).ok()?
.into_iter() .into_iter()

View File

@ -39,9 +39,9 @@ impl Resolver<DbConn> for WebfingerResolver {
} }
fn find(acct: String, conn: DbConn) -> Result<Webfinger, ResolverError> { fn find(acct: String, conn: DbConn) -> Result<Webfinger, ResolverError> {
User::find_local(&*conn, &acct) User::find_by_fqn(&*conn, &acct)
.and_then(|usr| usr.webfinger(&*conn)) .and_then(|usr| usr.webfinger(&*conn))
.or_else(|_| Blog::find_local(&*conn, &acct) .or_else(|_| Blog::find_by_fqn(&*conn, &acct)
.and_then(|blog| blog.webfinger(&*conn)) .and_then(|blog| blog.webfinger(&*conn))
.or(Err(ResolverError::NotFound))) .or(Err(ResolverError::NotFound)))
} }

View File

@ -29,7 +29,7 @@ macro_rules! render {
} }
pub fn translate_notification(ctx: BaseContext, notif: Notification) -> String { pub fn translate_notification(ctx: BaseContext, notif: Notification) -> String {
let name = notif.get_actor(ctx.0).unwrap().name(ctx.0); let name = notif.get_actor(ctx.0).unwrap().name();
match notif.kind.as_ref() { match notif.kind.as_ref() {
notification_kind::COMMENT => i18n!(ctx.1, "{0} commented your article."; &name), notification_kind::COMMENT => i18n!(ctx.1, "{0} commented your article."; &name),
notification_kind::FOLLOW => i18n!(ctx.1, "{0} is subscribed to you."; &name), notification_kind::FOLLOW => i18n!(ctx.1, "{0} is subscribed to you."; &name),
@ -55,7 +55,7 @@ impl Size {
} }
pub fn avatar(conn: &Connection, user: &User, size: Size, pad: bool, catalog: &Catalog) -> Html<String> { pub fn avatar(conn: &Connection, user: &User, size: Size, pad: bool, catalog: &Catalog) -> Html<String> {
let name = escape(&user.name(conn)).to_string(); let name = escape(&user.name()).to_string();
Html(format!( Html(format!(
r#"<div class="avatar {size} {padded}" r#"<div class="avatar {size} {padded}"
style="background-image: url('{url}');" style="background-image: url('{url}');"

View File

@ -5,26 +5,26 @@
@use template_utils::*; @use template_utils::*;
@use routes::*; @use routes::*;
@(ctx: BaseContext, blog: Blog, fqn: String, authors: &Vec<User>, total_articles: i64, page: i32, n_pages: i32, is_author: bool, posts: Vec<Post>) @(ctx: BaseContext, blog: Blog, authors: &Vec<User>, total_articles: i64, page: i32, n_pages: i32, is_author: bool, posts: Vec<Post>)
@:base(ctx, blog.title.clone(), {}, { @:base(ctx, blog.title.clone(), {}, {
<a href="@uri!(blogs::details: name = &fqn, page = _)">@blog.title</a> <a href="@uri!(blogs::details: name = &blog.fqn, page = _)">@blog.title</a>
}, { }, {
<div class="hidden"> <div class="hidden">
@for author in authors { @for author in authors {
<div class="h-card"> <div class="h-card">
<span class="p-name">@author.name(ctx.0)</span> <span class="p-name">@author.name()</span>
<a class="u-url" href="@author.ap_url"></a> <a class="u-url" href="@author.ap_url"></a>
</div> </div>
} }
</div> </div>
<div class="h-feed"> <div class="h-feed">
<h1><span class="p-name">@blog.title</span> <small>~@fqn</small></h1> <h1><span class="p-name">@blog.title</span> <small>~@blog.fqn</small></h1>
<p>@blog.summary</p> <p>@blog.summary</p>
<p> <p>
@i18n!(ctx.1, "There's one author on this blog: ", "There are {0} authors on this blog: "; authors.len()) @i18n!(ctx.1, "There's one author on this blog: ", "There are {0} authors on this blog: "; authors.len())
@for author in authors { @for author in authors {
<a class="author p-author" href="@uri!(user::details: name = author.get_fqn(ctx.0))">@author.name(ctx.0)</a> <a class="author p-author" href="@uri!(user::details: name = &author.fqn)">@author.name()</a>
} }
</p> </p>
<p> <p>
@ -34,13 +34,13 @@
<section> <section>
<h2> <h2>
@i18n!(ctx.1, "Latest articles") @i18n!(ctx.1, "Latest articles")
<small><a href="@uri!(blogs::atom_feed: name = &fqn)" title="Atom feed">@icon!("rss")</a></small> <small><a href="@uri!(blogs::atom_feed: name = &blog.fqn)" title="Atom feed">@icon!("rss")</a></small>
</h2> </h2>
@if posts.len() < 1 { @if posts.len() < 1 {
<p>@i18n!(ctx.1, "No posts to see here yet.")</p> <p>@i18n!(ctx.1, "No posts to see here yet.")</p>
} }
@if is_author { @if is_author {
<a href="@uri!(posts::new: blog = &fqn)" class="button inline-block">@i18n!(ctx.1, "New article")</a> <a href="@uri!(posts::new: blog = &blog.fqn)" class="button inline-block">@i18n!(ctx.1, "New article")</a>
} }
<div class="cards"> <div class="cards">
@for article in posts { @for article in posts {
@ -53,7 +53,7 @@
@if is_author { @if is_author {
<h2>@i18n!(ctx.1, "Danger zone")</h2> <h2>@i18n!(ctx.1, "Danger zone")</h2>
<p>@i18n!(ctx.1, "Be very careful, any action taken here can't be reversed.")</p> <p>@i18n!(ctx.1, "Be very careful, any action taken here can't be reversed.")</p>
<form method="post" action="@uri!(blogs::delete: name = &fqn)"> <form method="post" action="@uri!(blogs::delete: name = &blog.fqn)">
<input type="submit" class="inline-block button destructive" value="@i18n!(ctx.1, "Permanently delete this blog")"> <input type="submit" class="inline-block button destructive" value="@i18n!(ctx.1, "Permanently delete this blog")">
</form> </form>
} }

View File

@ -24,7 +24,7 @@
<div> <div>
<p>@i18n!(ctx.1, "Administred by")</p> <p>@i18n!(ctx.1, "Administred by")</p>
@avatar(ctx.0, &admin, Size::Small, false, ctx.1) @avatar(ctx.0, &admin, Size::Small, false, ctx.1)
<p><a href="@uri!(user::details: name = admin.get_fqn(ctx.0))">@admin.name(ctx.0)</a><small>@@@admin.get_fqn(ctx.0)</small></p> <p><a href="@uri!(user::details: name = &admin.fqn)">@admin.name()</a><small>@@@admin.fqn</small></p>
</div> </div>
</section> </section>
<p>@i18n!(ctx.1, "Runs Plume {0}"; env!("CARGO_PKG_VERSION"))</p> <p>@i18n!(ctx.1, "Runs Plume {0}"; env!("CARGO_PKG_VERSION"))</p>

View File

@ -19,7 +19,7 @@
<div class="flex"> <div class="flex">
@avatar(ctx.0, &user, Size::Small, false, ctx.1) @avatar(ctx.0, &user, Size::Small, false, ctx.1)
<p class="grow"> <p class="grow">
<a href="@uri!(user::details: name = user.get_fqn(ctx.0))">@user.name(ctx.0)</a> <a href="@uri!(user::details: name = &user.fqn)">@user.name()</a>
<small>@format!("@{}", user.username)</small> <small>@format!("@{}", user.username)</small>
</p> </p>
@if !user.is_admin { @if !user.is_admin {

View File

@ -7,10 +7,10 @@
@if let Some(ref comm) = Some(&comment_tree.comment) { @if let Some(ref comm) = Some(&comment_tree.comment) {
@if let Some(author) = comm.get_author(ctx.0).ok() { @if let Some(author) = comm.get_author(ctx.0).ok() {
<div class="comment u-comment h-cite" id="comment-@comm.id"> <div class="comment u-comment h-cite" id="comment-@comm.id">
<a class="author u-author h-card" href="@uri!(user::details: name = author.get_fqn(ctx.0))"> <a class="author u-author h-card" href="@uri!(user::details: name = &author.fqn)">
@avatar(ctx.0, &author, Size::Small, true, ctx.1) @avatar(ctx.0, &author, Size::Small, true, ctx.1)
<span class="display-name p-name">@author.name(ctx.0)</span> <span class="display-name p-name">@author.name()</span>
<small>@author.get_fqn(ctx.0)</small> <small>@&author.fqn</small>
</a> </a>
@if let Some(ref ap_url) = comm.ap_url { @if let Some(ref ap_url) = comm.ap_url {
<a class="u-url" href="@ap_url"></a> <a class="u-url" href="@ap_url"></a>

View File

@ -9,7 +9,7 @@
<div class="cover" style="background-image: url('@Html(article.cover_url(ctx.0).unwrap_or_default())')"></div> <div class="cover" style="background-image: url('@Html(article.cover_url(ctx.0).unwrap_or_default())')"></div>
} }
<h3 class="p-name"> <h3 class="p-name">
<a class="u-url" href="@uri!(posts::details: blog = article.get_blog(ctx.0).unwrap().get_fqn(ctx.0), slug = &article.slug, responding_to = _)"> <a class="u-url" href="@uri!(posts::details: blog = article.get_blog(ctx.0).unwrap().fqn, slug = &article.slug, responding_to = _)">
@article.title @article.title
</a> </a>
</h3> </h3>
@ -19,13 +19,13 @@
<footer class="authors"> <footer class="authors">
@Html(i18n!(ctx.1, "By {0}"; format!( @Html(i18n!(ctx.1, "By {0}"; format!(
"<a class=\"p-author h-card\" href=\"{}\">{}</a>", "<a class=\"p-author h-card\" href=\"{}\">{}</a>",
uri!(user::details: name = article.get_authors(ctx.0).unwrap_or_default()[0].get_fqn(ctx.0)), uri!(user::details: name = &article.get_authors(ctx.0).unwrap_or_default()[0].fqn),
escape(&article.get_authors(ctx.0).unwrap_or_default()[0].name(ctx.0)) escape(&article.get_authors(ctx.0).unwrap_or_default()[0].name())
))) )))
@if article.published { @if article.published {
<span class="dt-published" datetime="@article.creation_date.format("%F %T")">@article.creation_date.format("%B %e, %Y")</span> <span class="dt-published" datetime="@article.creation_date.format("%F %T")">@article.creation_date.format("%B %e, %Y")</span>
} }
<a href="@uri!(blogs::details: name = article.get_blog(ctx.0).unwrap().get_fqn(ctx.0), page = _)">@article.get_blog(ctx.0).unwrap().title</a> <a href="@uri!(blogs::details: name = &article.get_blog(ctx.0).unwrap().fqn, page = _)">@article.get_blog(ctx.0).unwrap().title</a>
@if !article.published { @if !article.published {
⋅ @i18n!(ctx.1, "Draft") ⋅ @i18n!(ctx.1, "Draft")
} }

View File

@ -17,10 +17,10 @@
@if article.cover_id.is_some() { @if article.cover_id.is_some() {
<meta property="og:image" content="@Html(article.cover_url(ctx.0).unwrap_or_default())"/> <meta property="og:image" content="@Html(article.cover_url(ctx.0).unwrap_or_default())"/>
} }
<meta property="og:url" content="@uri!(posts::details: blog = blog.get_fqn(ctx.0), slug = &article.slug, responding_to = _)"/> <meta property="og:url" content="@uri!(posts::details: blog = &blog.fqn, slug = &article.slug, responding_to = _)"/>
<meta property="og:description" content="@article.subtitle"/> <meta property="og:description" content="@article.subtitle"/>
}, { }, {
<a href="@uri!(blogs::details: name = blog.get_fqn(ctx.0), page = _)">@blog.title</a> <a href="@uri!(blogs::details: name = &blog.fqn, page = _)">@blog.title</a>
}, { }, {
<div class="h-entry"> <div class="h-entry">
<h1 class="article p-name">@&article.title</h1> <h1 class="article p-name">@&article.title</h1>
@ -28,16 +28,16 @@
<div class="article-info"> <div class="article-info">
<span class="author"> <span class="author">
@Html(i18n!(ctx.1, "Written by {0}"; format!("<a href=\"{}\">{}</a>", @Html(i18n!(ctx.1, "Written by {0}"; format!("<a href=\"{}\">{}</a>",
uri!(user::details: name = &author.get_fqn(ctx.0)), uri!(user::details: name = &author.fqn),
escape(&author.name(ctx.0))))) escape(&author.name()))))
</span> </span>
&mdash; &mdash;
<span class="date dt-published" datetime="@article.creation_date.format("%F %T")">@article.creation_date.format("%B %e, %Y")</span><a class="u-url" href="@article.ap_url"></a> <span class="date dt-published" datetime="@article.creation_date.format("%F %T")">@article.creation_date.format("%B %e, %Y")</span><a class="u-url" href="@article.ap_url"></a>
@if ctx.2.clone().map(|u| u.id == author.id).unwrap_or(false) { @if ctx.2.clone().map(|u| u.id == author.id).unwrap_or(false) {
&mdash; &mdash;
<a href="@uri!(posts::edit: blog = blog.get_fqn(ctx.0), slug = &article.slug)">@i18n!(ctx.1, "Edit")</a> <a href="@uri!(posts::edit: blog = &blog.fqn, slug = &article.slug)">@i18n!(ctx.1, "Edit")</a>
&mdash; &mdash;
<form class="inline" method="post" action="@uri!(posts::delete: blog_name = blog.get_fqn(ctx.0), slug = &article.slug)"> <form class="inline" method="post" action="@uri!(posts::delete: blog_name = &blog.fqn, slug = &article.slug)">
<input onclick="return confirm('Are you sure you?')" type="submit" value="@i18n!(ctx.1, "Delete this article")"> <input onclick="return confirm('Are you sure you?')" type="submit" value="@i18n!(ctx.1, "Delete this article")">
</form> </form>
} }
@ -73,20 +73,20 @@
@avatar(ctx.0, &author, Size::Medium, true, ctx.1) @avatar(ctx.0, &author, Size::Medium, true, ctx.1)
<div class="grow"> <div class="grow">
<h2 class="p-name"> <h2 class="p-name">
<a href="@uri!(user::details: name = author.get_fqn(ctx.0))">@author.name(ctx.0)</a> <a href="@uri!(user::details: name = &author.fqn)">@author.name()</a>
<a rel="author" class="u-url" href="@author.ap_url"></a> <a rel="author" class="u-url" href="@author.ap_url"></a>
</h2> </h2>
<p>@Html(&author.summary)</h2> <p>@Html(&author.summary)</h2>
</div> </div>
@if !ctx.2.as_ref().map(|u| u.id == author.id).unwrap_or(false) { @if !ctx.2.as_ref().map(|u| u.id == author.id).unwrap_or(false) {
<form action="@uri!(user::follow: name = author.get_fqn(ctx.0))" method="POST"> <form action="@uri!(user::follow: name = &author.fqn)" method="POST">
<input type="submit" class="button" value="@if is_following {@i18n!(ctx.1, "Unsubscribe")} else {@i18n!(ctx.1, "Subscribe")}"> <input type="submit" class="button" value="@if is_following {@i18n!(ctx.1, "Unsubscribe")} else {@i18n!(ctx.1, "Subscribe")}">
</form> </form>
} }
</div> </div>
@if ctx.2.is_some() { @if ctx.2.is_some() {
<div class="actions"> <div class="actions">
<form class="likes" action="@uri!(likes::create: blog = blog.get_fqn(ctx.0), slug = &article.slug)" method="POST"> <form class="likes" action="@uri!(likes::create: blog = &blog.fqn, slug = &article.slug)" method="POST">
<p aria-label="@i18n!(ctx.1, "One like", "{0} likes"; n_likes)" title="@i18n!(ctx.1, "One like", "{0} likes"; n_likes)"> <p aria-label="@i18n!(ctx.1, "One like", "{0} likes"; n_likes)" title="@i18n!(ctx.1, "One like", "{0} likes"; n_likes)">
@n_likes @n_likes
</p> </p>
@ -97,7 +97,7 @@
<button type="submit" class="action">@icon!("heart") @i18n!(ctx.1, "Add yours")</button> <button type="submit" class="action">@icon!("heart") @i18n!(ctx.1, "Add yours")</button>
} }
</form> </form>
<form class="reshares" action="@uri!(reshares::create: blog = blog.get_fqn(ctx.0), slug = &article.slug)" method="POST"> <form class="reshares" action="@uri!(reshares::create: blog = &blog.fqn, slug = &article.slug)" method="POST">
<p aria-label="@i18n!(ctx.1, "One boost", "{0} boost"; n_reshares)" title="@i18n!(ctx.1, "One boost", "{0} boosts"; n_reshares)"> <p aria-label="@i18n!(ctx.1, "One boost", "{0} boost"; n_reshares)" title="@i18n!(ctx.1, "One boost", "{0} boosts"; n_reshares)">
@n_reshares @n_reshares
</p> </p>
@ -131,7 +131,7 @@
<h2>@i18n!(ctx.1, "Comments")</h2> <h2>@i18n!(ctx.1, "Comments")</h2>
@if ctx.2.is_some() { @if ctx.2.is_some() {
<form method="post" action="@uri!(comments::create: blog_name = blog.get_fqn(ctx.0), slug = &article.slug)"> <form method="post" action="@uri!(comments::create: blog_name = &blog.fqn, slug = &article.slug)">
@input!(ctx.1, warning (optional text), "Content warning", comment_form, comment_errors, "") @input!(ctx.1, warning (optional text), "Content warning", comment_form, comment_errors, "")
<label for="plume-editor">@i18n!(ctx.1, "Your comment")</label> <label for="plume-editor">@i18n!(ctx.1, "Your comment")</label>
@ -146,7 +146,7 @@
@if !comments.is_empty() { @if !comments.is_empty() {
<div class="list"> <div class="list">
@for comm in comments { @for comm in comments {
@:comment(ctx, &comm, Some(&article.ap_url), &blog.get_fqn(ctx.0), &article.slug) @:comment(ctx, &comm, Some(&article.ap_url), &blog.fqn, &article.slug)
} }
</div> </div>
} else { } else {

View File

@ -6,20 +6,20 @@
@(ctx: BaseContext, user: User, follows: bool, is_remote: bool, remote_url: String, recents: Vec<Post>, reshares: Vec<Post>) @(ctx: BaseContext, user: User, follows: bool, is_remote: bool, remote_url: String, recents: Vec<Post>, reshares: Vec<Post>)
@:base(ctx, user.name(ctx.0), {}, {}, { @:base(ctx, user.name(), {}, {}, {
@:header(ctx, &user, follows, is_remote, remote_url) @:header(ctx, &user, follows, is_remote, remote_url)
@tabs(&[ @tabs(&[
(&uri!(user::details: name= user.get_fqn(ctx.0)).to_string(), i18n!(ctx.1, "Articles"), true), (&uri!(user::details: name = &user.fqn).to_string(), i18n!(ctx.1, "Articles"), true),
(&uri!(user::followers: name = user.get_fqn(ctx.0), page = _).to_string(), i18n!(ctx.1, "Subscribers"), false), (&uri!(user::followers: name = &user.fqn, page = _).to_string(), i18n!(ctx.1, "Subscribers"), false),
(&uri!(user::followed: name = user.get_fqn(ctx.0), page = _).to_string(), i18n!(ctx.1, "Subscriptions"), false) (&uri!(user::followed: name = &user.fqn, page = _).to_string(), i18n!(ctx.1, "Subscriptions"), false)
]) ])
@if !recents.is_empty() { @if !recents.is_empty() {
<div class="h-feed"> <div class="h-feed">
<h2> <h2>
<span class="p-name">@i18n!(ctx.1, "Latest articles")</span> <span class="p-name">@i18n!(ctx.1, "Latest articles")</span>
<small><a href="@uri!(user::atom_feed: name = user.get_fqn(ctx.0))" title="@i18n!(ctx.1, "Atom feed")">@icon!("rss")</a></small> <small><a href="@uri!(user::atom_feed: name = &user.fqn))" title="@i18n!(ctx.1, "Atom feed")">@icon!("rss")</a></small>
</h2> </h2>
<div class="cards"> <div class="cards">
@for article in recents { @for article in recents {

View File

@ -5,19 +5,19 @@
@(ctx: BaseContext, user: User, follows: bool, is_remote: bool, remote_url: String, followed: Vec<User>, page: i32, n_pages: i32) @(ctx: BaseContext, user: User, follows: bool, is_remote: bool, remote_url: String, followed: Vec<User>, page: i32, n_pages: i32)
@:base(ctx, i18n!(ctx.1, "{0}'s subscriptions'"; user.name(ctx.0)), {}, {}, { @:base(ctx, i18n!(ctx.1, "{0}'s subscriptions'"; user.name()), {}, {}, {
@:header(ctx, &user, follows, is_remote, remote_url) @:header(ctx, &user, follows, is_remote, remote_url)
@tabs(&[ @tabs(&[
(&uri!(user::details: name= user.get_fqn(ctx.0)).to_string(), i18n!(ctx.1, "Articles"), true), (&uri!(user::details: name = &user.fqn).to_string(), i18n!(ctx.1, "Articles"), true),
(&uri!(user::followers: name = user.get_fqn(ctx.0), page = _).to_string(), i18n!(ctx.1, "Subscribers"), false), (&uri!(user::followers: name = &user.fqn, page = _).to_string(), i18n!(ctx.1, "Subscribers"), false),
(&uri!(user::followed: name = user.get_fqn(ctx.0), page = _).to_string(), i18n!(ctx.1, "Subscriptions"), false) (&uri!(user::followed: name = &user.fqn, page = _).to_string(), i18n!(ctx.1, "Subscriptions"), false)
]) ])
<div class="cards"> <div class="cards">
@for follow in followed { @for follow in followed {
<div class="card"> <div class="card">
<h3><a href="@uri!(user::details: name = follow.get_fqn(ctx.0))">@follow.name(ctx.0)</a> <small>@format!("@{}", follow.get_fqn(ctx.0))</small></h3> <h3><a href="@uri!(user::details: name = &follow.fqn)">@follow.name()</a> <small>@format!("@{}", &follow.fqn)</small></h3>
<main><p>@Html(follow.summary)</p></main> <main><p>@Html(follow.summary)</p></main>
</div> </div>
} }

View File

@ -5,19 +5,19 @@
@(ctx: BaseContext, user: User, follows: bool, is_remote: bool, remote_url: String, followers: Vec<User>, page: i32, n_pages: i32) @(ctx: BaseContext, user: User, follows: bool, is_remote: bool, remote_url: String, followers: Vec<User>, page: i32, n_pages: i32)
@:base(ctx, i18n!(ctx.1, "{0}'s subscribers"; user.name(ctx.0)), {}, {}, { @:base(ctx, i18n!(ctx.1, "{0}'s subscribers"; user.name()), {}, {}, {
@:header(ctx, &user, follows, is_remote, remote_url) @:header(ctx, &user, follows, is_remote, remote_url)
@tabs(&[ @tabs(&[
(&uri!(user::details: name= user.get_fqn(ctx.0)).to_string(), i18n!(ctx.1, "Articles"), true), (&uri!(user::details: name = &user.fqn).to_string(), i18n!(ctx.1, "Articles"), true),
(&uri!(user::followers: name = user.get_fqn(ctx.0), page = _).to_string(), i18n!(ctx.1, "Subscribers"), false), (&uri!(user::followers: name = &user.fqn, page = _).to_string(), i18n!(ctx.1, "Subscribers"), false),
(&uri!(user::followed: name = user.get_fqn(ctx.0), page = _).to_string(), i18n!(ctx.1, "Subscriptions"), false) (&uri!(user::followed: name = &user.fqn, page = _).to_string(), i18n!(ctx.1, "Subscriptions"), false)
]) ])
<div class="cards"> <div class="cards">
@for follower in followers { @for follower in followers {
<div class="card"> <div class="card">
<h3><a href="@uri!(user::details: name = follower.get_fqn(ctx.0))">@follower.name(ctx.0)</a> <small>@format!("@{}", follower.get_fqn(ctx.0))</small></h3> <h3><a href="@uri!(user::details: name = &follower.fqn)">@follower.name()</a> <small>@format!("@{}", &follower.fqn)</small></h3>
<main><p>@Html(follower.summary)</p></main> <main><p>@Html(follower.summary)</p></main>
</div> </div>
} }

View File

@ -10,8 +10,8 @@
@avatar(ctx.0, &user, Size::Medium, false, ctx.1) @avatar(ctx.0, &user, Size::Medium, false, ctx.1)
<h1 class="grow flex vertical"> <h1 class="grow flex vertical">
<span class="p-name">@user.name(ctx.0)</span> <span class="p-name">@user.name()</span>
<small class="p-nickname">@user.get_fqn(ctx.0)</small> <small class="p-nickname">@&user.fqn</small>
</h1> </h1>
<p> <p>
@ -33,7 +33,7 @@
} }
@if ctx.2.clone().map(|u| u.id != user.id).unwrap_or(false) { @if ctx.2.clone().map(|u| u.id != user.id).unwrap_or(false) {
<form class="inline" method="post" action="@uri!(user::follow: name = user.get_fqn(ctx.0))"> <form class="inline" method="post" action="@uri!(user::follow: name = &user.fqn)">
@if follows { @if follows {
<input type="submit" value="@i18n!(ctx.1, "Unsubscribe")"> <input type="submit" value="@i18n!(ctx.1, "Unsubscribe")">
} else { } else {