Make a distinction between moderators and admins (#619)
* Make a distinction between moderators and admins And rework the user list in the moderation interface, to be able to run the same action on many users, and to have a huge list of actions whithout loosing space. * Make user's role an enum + make it impossible for a moderator to escalate privileges With the help of diesel-derive-enum (maybe it could be used in other places too?) Also, moderators are still able to grant or revoke moderation rights to other people, but maybe only admins should be able to do it? * Cargo fmt * copy/pasting is bad * Remove diesel-derive-enum and use an integer instead It was not compatible with both Postgres and SQlite, because for one it generated a schema with the "User_role" type, but for the other it was "Text"… * Reset translations * Use an enum to avoid magic numbers + fix the tests * Reset translations * Fix down.sql
This commit is contained in:
parent
12c80f9981
commit
309e1200d0
@ -162,3 +162,11 @@ header.center {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
form > header {
|
||||
display: flex;
|
||||
|
||||
input[type="submit"] {
|
||||
margin-left: 1em;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,4 @@
|
||||
-- This file should undo anything in `up.sql`
|
||||
ALTER TABLE users ADD COLUMN is_admin BOOLEAN NOT NULL DEFAULT 'f';
|
||||
UPDATE users SET is_admin = 't' WHERE role = 0;
|
||||
ALTER TABLE users DROP COLUMN role;
|
@ -0,0 +1,4 @@
|
||||
-- Your SQL goes here
|
||||
ALTER TABLE users ADD COLUMN role INTEGER NOT NULL DEFAULT 2;
|
||||
UPDATE users SET role = 0 WHERE is_admin = 't';
|
||||
ALTER TABLE users DROP COLUMN is_admin;
|
72
migrations/sqlite/2019-06-18-175952_moderator_role/down.sql
Normal file
72
migrations/sqlite/2019-06-18-175952_moderator_role/down.sql
Normal file
@ -0,0 +1,72 @@
|
||||
-- This file should undo anything in `up.sql`
|
||||
CREATE TABLE IF NOT EXISTS "users_without_role" (
|
||||
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 CASCADE,
|
||||
last_fetched_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
fqn TEXT NOT NULL DEFAULT '',
|
||||
summary_html TEXT NOT NULL DEFAULT '',
|
||||
FOREIGN KEY (avatar_id) REFERENCES medias(id) ON DELETE SET NULL,
|
||||
CONSTRAINT blog_authors_unique UNIQUE (username, instance_id)
|
||||
);
|
||||
|
||||
INSERT INTO users_without_role SELECT
|
||||
id,
|
||||
username,
|
||||
display_name,
|
||||
outbox_url,
|
||||
inbox_url,
|
||||
't',
|
||||
summary,
|
||||
email,
|
||||
hashed_password,
|
||||
instance_id,
|
||||
creation_date,
|
||||
ap_url,
|
||||
private_key,
|
||||
public_key,
|
||||
shared_inbox_url,
|
||||
followers_endpoint,
|
||||
avatar_id,
|
||||
last_fetched_date,
|
||||
fqn,
|
||||
summary
|
||||
FROM users WHERE role = 0;
|
||||
INSERT INTO users_without_role SELECT
|
||||
id,
|
||||
username,
|
||||
display_name,
|
||||
outbox_url,
|
||||
inbox_url,
|
||||
'f',
|
||||
summary,
|
||||
email,
|
||||
hashed_password,
|
||||
instance_id,
|
||||
creation_date,
|
||||
ap_url,
|
||||
private_key,
|
||||
public_key,
|
||||
shared_inbox_url,
|
||||
followers_endpoint,
|
||||
avatar_id,
|
||||
last_fetched_date,
|
||||
fqn,
|
||||
summary
|
||||
FROM users WHERE role != 0;
|
||||
DROP TABLE users;
|
||||
ALTER TABLE users_without_role RENAME TO users;
|
74
migrations/sqlite/2019-06-18-175952_moderator_role/up.sql
Normal file
74
migrations/sqlite/2019-06-18-175952_moderator_role/up.sql
Normal file
@ -0,0 +1,74 @@
|
||||
-- Your SQL goes here
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "users_with_role" (
|
||||
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,
|
||||
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 CASCADE,
|
||||
last_fetched_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
fqn TEXT NOT NULL DEFAULT '',
|
||||
summary_html TEXT NOT NULL DEFAULT '',
|
||||
role INTEGER NOT NULL DEFAULT 2,
|
||||
FOREIGN KEY (avatar_id) REFERENCES medias(id) ON DELETE SET NULL,
|
||||
CONSTRAINT blog_authors_unique UNIQUE (username, instance_id)
|
||||
);
|
||||
|
||||
|
||||
INSERT INTO users_with_role SELECT
|
||||
id,
|
||||
username,
|
||||
display_name,
|
||||
outbox_url,
|
||||
inbox_url,
|
||||
summary,
|
||||
email,
|
||||
hashed_password,
|
||||
instance_id,
|
||||
creation_date,
|
||||
ap_url,
|
||||
private_key,
|
||||
public_key,
|
||||
shared_inbox_url,
|
||||
followers_endpoint,
|
||||
avatar_id,
|
||||
last_fetched_date,
|
||||
fqn,
|
||||
summary,
|
||||
0
|
||||
FROM users WHERE is_admin = 't';
|
||||
INSERT INTO users_with_role SELECT
|
||||
id,
|
||||
username,
|
||||
display_name,
|
||||
outbox_url,
|
||||
inbox_url,
|
||||
summary,
|
||||
email,
|
||||
hashed_password,
|
||||
instance_id,
|
||||
creation_date,
|
||||
ap_url,
|
||||
private_key,
|
||||
public_key,
|
||||
shared_inbox_url,
|
||||
followers_endpoint,
|
||||
avatar_id,
|
||||
last_fetched_date,
|
||||
fqn,
|
||||
summary,
|
||||
2
|
||||
FROM users WHERE is_admin = 'f';
|
||||
DROP TABLE users;
|
||||
ALTER TABLE users_with_role RENAME TO users;
|
@ -44,7 +44,6 @@ CREATE TABLE users_before_themes (
|
||||
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,
|
||||
@ -59,6 +58,7 @@ CREATE TABLE users_before_themes (
|
||||
last_fetched_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
fqn TEXT NOT NULL DEFAULT '',
|
||||
summary_html TEXT NOT NULL DEFAULT '',
|
||||
role INTEGER NOT NULL DEFAULT 2,
|
||||
FOREIGN KEY (avatar_id) REFERENCES medias(id) ON DELETE SET NULL,
|
||||
CONSTRAINT blog_authors_unique UNIQUE (username, instance_id)
|
||||
);
|
||||
@ -68,7 +68,6 @@ INSERT INTO users_before_themes SELECT
|
||||
display_name,
|
||||
outbox_url,
|
||||
inbox_url,
|
||||
is_admin,
|
||||
summary,
|
||||
email,
|
||||
hashed_password,
|
||||
@ -82,7 +81,8 @@ INSERT INTO users_before_themes SELECT
|
||||
avatar_id,
|
||||
last_fetched_date,
|
||||
fqn,
|
||||
summary_html
|
||||
summary_html,
|
||||
role
|
||||
FROM users;
|
||||
DROP TABLE users;
|
||||
ALTER TABLE users_before_themes RENAME TO users;
|
||||
|
@ -52,6 +52,12 @@ pub fn command<'a, 'b>() -> App<'a, 'b> {
|
||||
.long("admin")
|
||||
.help("Makes the user an administrator of the instance"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("moderator")
|
||||
.short("m")
|
||||
.long("moderator")
|
||||
.help("Makes the user a moderator of the instance"),
|
||||
)
|
||||
.about("Create a new user on this instance"),
|
||||
)
|
||||
.subcommand(
|
||||
@ -94,7 +100,17 @@ fn new<'a>(args: &ArgMatches<'a>, conn: &Connection) {
|
||||
.value_of("display-name")
|
||||
.map(String::from)
|
||||
.unwrap_or_else(|| super::ask_for("Display name"));
|
||||
|
||||
let admin = args.is_present("admin");
|
||||
let moderator = args.is_present("moderator");
|
||||
let role = if admin {
|
||||
Role::Admin
|
||||
} else if moderator {
|
||||
Role::Moderator
|
||||
} else {
|
||||
Role::Normal
|
||||
};
|
||||
|
||||
let bio = args.value_of("biography").unwrap_or("").to_string();
|
||||
let email = args
|
||||
.value_of("email")
|
||||
@ -113,7 +129,7 @@ fn new<'a>(args: &ArgMatches<'a>, conn: &Connection) {
|
||||
conn,
|
||||
username,
|
||||
display_name,
|
||||
admin,
|
||||
role,
|
||||
&bio,
|
||||
email,
|
||||
User::hash_pass(&password).expect("Couldn't hash password"),
|
||||
|
@ -50,5 +50,5 @@ path = "../plume-macro"
|
||||
diesel_migrations = "1.3.0"
|
||||
|
||||
[features]
|
||||
postgres = ["diesel/postgres", "plume-macro/postgres"]
|
||||
sqlite = ["diesel/sqlite", "plume-macro/sqlite"]
|
||||
postgres = ["diesel/postgres", "plume-macro/postgres" ]
|
||||
sqlite = ["diesel/sqlite", "plume-macro/sqlite" ]
|
||||
|
@ -14,10 +14,26 @@ impl<'a, 'r> FromRequest<'a, 'r> for Admin {
|
||||
|
||||
fn from_request(request: &'a Request<'r>) -> request::Outcome<Admin, ()> {
|
||||
let user = request.guard::<User>()?;
|
||||
if user.is_admin {
|
||||
if user.is_admin() {
|
||||
Outcome::Success(Admin(user))
|
||||
} else {
|
||||
Outcome::Failure((Status::Unauthorized, ()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as `Admin` but for moderators.
|
||||
pub struct Moderator(pub User);
|
||||
|
||||
impl<'a, 'r> FromRequest<'a, 'r> for Moderator {
|
||||
type Error = ();
|
||||
|
||||
fn from_request(request: &'a Request<'r>) -> request::Outcome<Moderator, ()> {
|
||||
let user = request.guard::<User>()?;
|
||||
if user.is_moderator() {
|
||||
Outcome::Success(Moderator(user))
|
||||
} else {
|
||||
Outcome::Failure((Status::Unauthorized, ()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ use medias::Media;
|
||||
use plume_common::utils::md_to_html;
|
||||
use safe_string::SafeString;
|
||||
use schema::{instances, users};
|
||||
use users::User;
|
||||
use users::{Role, User};
|
||||
use {Connection, Error, Result};
|
||||
|
||||
#[derive(Clone, Identifiable, Queryable)]
|
||||
@ -117,7 +117,7 @@ impl Instance {
|
||||
pub fn has_admin(&self, conn: &Connection) -> Result<bool> {
|
||||
users::table
|
||||
.filter(users::instance_id.eq(self.id))
|
||||
.filter(users::is_admin.eq(true))
|
||||
.filter(users::role.eq(Role::Admin as i32))
|
||||
.load::<User>(conn)
|
||||
.map_err(Error::from)
|
||||
.map(|r| !r.is_empty())
|
||||
@ -126,7 +126,7 @@ impl Instance {
|
||||
pub fn main_admin(&self, conn: &Connection) -> Result<User> {
|
||||
users::table
|
||||
.filter(users::instance_id.eq(self.id))
|
||||
.filter(users::is_admin.eq(true))
|
||||
.filter(users::role.eq(Role::Admin as i32))
|
||||
.limit(1)
|
||||
.get_result::<User>(conn)
|
||||
.map_err(Error::from)
|
||||
|
@ -375,6 +375,7 @@ pub mod post_authors;
|
||||
pub mod posts;
|
||||
pub mod reshares;
|
||||
pub mod safe_string;
|
||||
#[allow(unused_imports)]
|
||||
pub mod schema;
|
||||
pub mod search;
|
||||
pub mod tags;
|
||||
|
@ -202,7 +202,6 @@ table! {
|
||||
display_name -> Varchar,
|
||||
outbox_url -> Varchar,
|
||||
inbox_url -> Varchar,
|
||||
is_admin -> Bool,
|
||||
summary -> Text,
|
||||
email -> Nullable<Text>,
|
||||
hashed_password -> Nullable<Text>,
|
||||
@ -217,6 +216,7 @@ table! {
|
||||
last_fetched_date -> Timestamp,
|
||||
fqn -> Text,
|
||||
summary_html -> Text,
|
||||
role -> Int4,
|
||||
preferred_theme -> Nullable<Varchar>,
|
||||
hide_custom_css -> Bool,
|
||||
}
|
||||
|
@ -52,6 +52,12 @@ use {ap_url, Connection, Error, PlumeRocket, Result};
|
||||
|
||||
pub type CustomPerson = CustomObject<ApSignature, Person>;
|
||||
|
||||
pub enum Role {
|
||||
Admin = 0,
|
||||
Moderator = 1,
|
||||
Normal = 2,
|
||||
}
|
||||
|
||||
#[derive(Queryable, Identifiable, Clone, Debug, AsChangeset)]
|
||||
pub struct User {
|
||||
pub id: i32,
|
||||
@ -59,7 +65,6 @@ pub struct User {
|
||||
pub display_name: String,
|
||||
pub outbox_url: String,
|
||||
pub inbox_url: String,
|
||||
pub is_admin: bool,
|
||||
pub summary: String,
|
||||
pub email: Option<String>,
|
||||
pub hashed_password: Option<String>,
|
||||
@ -74,6 +79,10 @@ pub struct User {
|
||||
pub last_fetched_date: NaiveDateTime,
|
||||
pub fqn: String,
|
||||
pub summary_html: SafeString,
|
||||
/// 0 = admin
|
||||
/// 1 = moderator
|
||||
/// anything else = normal user
|
||||
pub role: i32,
|
||||
pub preferred_theme: Option<String>,
|
||||
pub hide_custom_css: bool,
|
||||
}
|
||||
@ -85,7 +94,6 @@ pub struct NewUser {
|
||||
pub display_name: String,
|
||||
pub outbox_url: String,
|
||||
pub inbox_url: String,
|
||||
pub is_admin: bool,
|
||||
pub summary: String,
|
||||
pub email: Option<String>,
|
||||
pub hashed_password: Option<String>,
|
||||
@ -97,6 +105,7 @@ pub struct NewUser {
|
||||
pub followers_endpoint: String,
|
||||
pub avatar_id: Option<i32>,
|
||||
pub summary_html: SafeString,
|
||||
pub role: i32,
|
||||
pub fqn: String,
|
||||
}
|
||||
|
||||
@ -110,6 +119,14 @@ impl User {
|
||||
find_by!(users, find_by_name, username as &str, instance_id as i32);
|
||||
find_by!(users, find_by_ap_url, ap_url as &str);
|
||||
|
||||
pub fn is_moderator(&self) -> bool {
|
||||
self.role == Role::Admin as i32 || self.role == Role::Moderator as i32
|
||||
}
|
||||
|
||||
pub fn is_admin(&self) -> bool {
|
||||
self.role == Role::Admin as i32
|
||||
}
|
||||
|
||||
pub fn one_by_instance(conn: &Connection) -> Result<Vec<User>> {
|
||||
users::table
|
||||
.filter(users::instance_id.eq_any(users::table.select(users::instance_id).distinct()))
|
||||
@ -162,17 +179,9 @@ impl User {
|
||||
Instance::get(conn, self.instance_id)
|
||||
}
|
||||
|
||||
pub fn grant_admin_rights(&self, conn: &Connection) -> Result<()> {
|
||||
pub fn set_role(&self, conn: &Connection, new_role: Role) -> Result<()> {
|
||||
diesel::update(self)
|
||||
.set(users::is_admin.eq(true))
|
||||
.execute(conn)
|
||||
.map(|_| ())
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
pub fn revoke_admin_rights(&self, conn: &Connection) -> Result<()> {
|
||||
diesel::update(self)
|
||||
.set(users::is_admin.eq(false))
|
||||
.set(users::role.eq(new_role as i32))
|
||||
.execute(conn)
|
||||
.map(|_| ())
|
||||
.map_err(Error::from)
|
||||
@ -762,7 +771,7 @@ impl FromId<PlumeRocket> for User {
|
||||
username,
|
||||
outbox_url: acct.object.ap_actor_props.outbox_string()?,
|
||||
inbox_url: acct.object.ap_actor_props.inbox_string()?,
|
||||
is_admin: false,
|
||||
role: 2,
|
||||
summary: acct
|
||||
.object
|
||||
.object_props
|
||||
@ -879,7 +888,7 @@ impl NewUser {
|
||||
conn: &Connection,
|
||||
username: String,
|
||||
display_name: String,
|
||||
is_admin: bool,
|
||||
role: Role,
|
||||
summary: &str,
|
||||
email: String,
|
||||
password: String,
|
||||
@ -892,7 +901,7 @@ impl NewUser {
|
||||
NewUser {
|
||||
username: username.clone(),
|
||||
display_name,
|
||||
is_admin,
|
||||
role: role as i32,
|
||||
summary: summary.to_owned(),
|
||||
summary_html: SafeString::new(&utils::md_to_html(&summary, None, false, None).0),
|
||||
email: Some(email),
|
||||
@ -927,7 +936,7 @@ pub(crate) mod tests {
|
||||
conn,
|
||||
"admin".to_owned(),
|
||||
"The admin".to_owned(),
|
||||
true,
|
||||
Role::Admin,
|
||||
"Hello there, I'm the admin",
|
||||
"admin@example.com".to_owned(),
|
||||
"invalid_admin_password".to_owned(),
|
||||
@ -937,7 +946,7 @@ pub(crate) mod tests {
|
||||
conn,
|
||||
"user".to_owned(),
|
||||
"Some user".to_owned(),
|
||||
false,
|
||||
Role::Normal,
|
||||
"Hello there, I'm no one",
|
||||
"user@example.com".to_owned(),
|
||||
"invalid_user_password".to_owned(),
|
||||
@ -947,7 +956,7 @@ pub(crate) mod tests {
|
||||
conn,
|
||||
"other".to_owned(),
|
||||
"Another user".to_owned(),
|
||||
false,
|
||||
Role::Normal,
|
||||
"Hello there, I'm someone else",
|
||||
"other@example.com".to_owned(),
|
||||
"invalid_other_password".to_owned(),
|
||||
@ -966,7 +975,7 @@ pub(crate) mod tests {
|
||||
conn,
|
||||
"test".to_owned(),
|
||||
"test user".to_owned(),
|
||||
false,
|
||||
Role::Normal,
|
||||
"Hello I'm a test",
|
||||
"test@example.com".to_owned(),
|
||||
User::hash_pass("test_password").unwrap(),
|
||||
@ -1031,11 +1040,11 @@ pub(crate) mod tests {
|
||||
local_inst
|
||||
.main_admin(conn)
|
||||
.unwrap()
|
||||
.revoke_admin_rights(conn)
|
||||
.set_role(conn, Role::Normal)
|
||||
.unwrap();
|
||||
i += 1;
|
||||
}
|
||||
inserted[0].grant_admin_rights(conn).unwrap();
|
||||
inserted[0].set_role(conn, Role::Admin).unwrap();
|
||||
assert_eq!(inserted[0].id, local_inst.main_admin(conn).unwrap().id);
|
||||
|
||||
Ok(())
|
||||
@ -1051,7 +1060,7 @@ pub(crate) mod tests {
|
||||
conn,
|
||||
"test".to_owned(),
|
||||
"test user".to_owned(),
|
||||
false,
|
||||
Role::Normal,
|
||||
"Hello I'm a test",
|
||||
"test@example.com".to_owned(),
|
||||
User::hash_pass("test_password").unwrap(),
|
||||
|
950
po/plume/ar.po
950
po/plume/ar.po
File diff suppressed because it is too large
Load Diff
895
po/plume/bg.po
895
po/plume/bg.po
File diff suppressed because it is too large
Load Diff
922
po/plume/ca.po
922
po/plume/ca.po
File diff suppressed because it is too large
Load Diff
966
po/plume/cs.po
966
po/plume/cs.po
File diff suppressed because it is too large
Load Diff
972
po/plume/de.po
972
po/plume/de.po
File diff suppressed because it is too large
Load Diff
849
po/plume/en.po
849
po/plume/en.po
File diff suppressed because it is too large
Load Diff
859
po/plume/eo.po
859
po/plume/eo.po
File diff suppressed because it is too large
Load Diff
962
po/plume/es.po
962
po/plume/es.po
File diff suppressed because it is too large
Load Diff
1000
po/plume/fr.po
1000
po/plume/fr.po
File diff suppressed because it is too large
Load Diff
970
po/plume/gl.po
970
po/plume/gl.po
File diff suppressed because it is too large
Load Diff
921
po/plume/hi.po
921
po/plume/hi.po
File diff suppressed because it is too large
Load Diff
899
po/plume/hr.po
899
po/plume/hr.po
File diff suppressed because it is too large
Load Diff
968
po/plume/it.po
968
po/plume/it.po
File diff suppressed because it is too large
Load Diff
980
po/plume/ja.po
980
po/plume/ja.po
File diff suppressed because it is too large
Load Diff
1006
po/plume/nb.po
1006
po/plume/nb.po
File diff suppressed because it is too large
Load Diff
962
po/plume/pl.po
962
po/plume/pl.po
File diff suppressed because it is too large
Load Diff
@ -73,11 +73,11 @@ msgid "Your blog information have been updated."
|
||||
msgstr ""
|
||||
|
||||
# src/routes/comments.rs:97
|
||||
msgid "Your comment have been posted."
|
||||
msgid "Your comment has been posted."
|
||||
msgstr ""
|
||||
|
||||
# src/routes/comments.rs:172
|
||||
msgid "Your comment have been deleted."
|
||||
msgid "Your comment has been deleted."
|
||||
msgstr ""
|
||||
|
||||
# src/routes/instance.rs:134
|
||||
@ -109,7 +109,7 @@ msgid "You are not allowed to delete this media."
|
||||
msgstr ""
|
||||
|
||||
# src/routes/medias.rs:163
|
||||
msgid "Your avatar have been updated."
|
||||
msgid "Your avatar has been updated."
|
||||
msgstr ""
|
||||
|
||||
# src/routes/medias.rs:168
|
||||
@ -145,11 +145,15 @@ msgid "You are not allowed to publish on this blog."
|
||||
msgstr ""
|
||||
|
||||
# src/routes/posts.rs:350
|
||||
msgid "Your article have been updated."
|
||||
msgid "Your article has been updated."
|
||||
msgstr ""
|
||||
|
||||
# src/routes/posts.rs:532
|
||||
msgid "Your post have been saved."
|
||||
msgid "Your article has been saved."
|
||||
msgstr ""
|
||||
|
||||
# src/routes/posts.rs:538
|
||||
msgid "New article"
|
||||
msgstr ""
|
||||
|
||||
# src/routes/posts.rs:572
|
||||
@ -157,7 +161,7 @@ msgid "You are not allowed to delete this article."
|
||||
msgstr ""
|
||||
|
||||
# src/routes/posts.rs:597
|
||||
msgid "Your article have been deleted."
|
||||
msgid "Your article has been deleted."
|
||||
msgstr ""
|
||||
|
||||
# src/routes/posts.rs:602
|
||||
@ -208,328 +212,152 @@ msgstr ""
|
||||
msgid "You are now following {}."
|
||||
msgstr ""
|
||||
|
||||
# src/routes/user.rs:255
|
||||
# src/routes/user.rs:254
|
||||
msgid "To subscribe to someone, you need to be logged in"
|
||||
msgstr ""
|
||||
|
||||
# src/routes/user.rs:357
|
||||
# src/routes/user.rs:356
|
||||
msgid "To edit your profile, you need to be logged in"
|
||||
msgstr ""
|
||||
|
||||
# src/routes/user.rs:399
|
||||
msgid "Your profile have been updated."
|
||||
# src/routes/user.rs:398
|
||||
msgid "Your profile has been updated."
|
||||
msgstr ""
|
||||
|
||||
# src/routes/user.rs:426
|
||||
msgid "Your account have been deleted."
|
||||
# src/routes/user.rs:425
|
||||
msgid "Your account has been deleted."
|
||||
msgstr ""
|
||||
|
||||
# src/routes/user.rs:432
|
||||
# src/routes/user.rs:431
|
||||
msgid "You can't delete someone else's account."
|
||||
msgstr ""
|
||||
|
||||
# src/routes/user.rs:504
|
||||
# src/routes/user.rs:503
|
||||
msgid "Registrations are closed on this instance."
|
||||
msgstr ""
|
||||
|
||||
# src/routes/user.rs:528
|
||||
msgid "Your account have been created. You just need to login before you can use it."
|
||||
# src/routes/user.rs:527
|
||||
msgid "Your account has been created. Now you just need to log in, before you can use it."
|
||||
msgstr ""
|
||||
|
||||
msgid "Plume"
|
||||
msgid "Internal server error"
|
||||
msgstr ""
|
||||
|
||||
msgid "Menu"
|
||||
msgid "Something broke on our side."
|
||||
msgstr ""
|
||||
|
||||
msgid "Search"
|
||||
msgid "Sorry about that. If you think this is a bug, please report it."
|
||||
msgstr ""
|
||||
|
||||
msgid "Dashboard"
|
||||
msgid "You are not authorized."
|
||||
msgstr ""
|
||||
|
||||
msgid "Notifications"
|
||||
msgid "Page not found"
|
||||
msgstr ""
|
||||
|
||||
msgid "Log Out"
|
||||
msgid "We couldn't find this page."
|
||||
msgstr ""
|
||||
|
||||
msgid "My account"
|
||||
msgid "The link that led you here may be broken."
|
||||
msgstr ""
|
||||
|
||||
msgid "Log In"
|
||||
msgid "The content you sent can't be processed."
|
||||
msgstr ""
|
||||
|
||||
msgid "Register"
|
||||
msgid "Maybe it was too long."
|
||||
msgstr ""
|
||||
|
||||
msgid "About this instance"
|
||||
msgid "Invalid CSRF token"
|
||||
msgstr ""
|
||||
|
||||
msgid "Source code"
|
||||
msgid "Something is wrong with your CSRF token. Make sure cookies are enabled in you browser, and try reloading this page. If you continue to see this error message, please report it."
|
||||
msgstr ""
|
||||
|
||||
msgid "Matrix room"
|
||||
msgid "Articles tagged \"{0}\""
|
||||
msgstr ""
|
||||
|
||||
msgid "Administration"
|
||||
msgid "There are currently no articles with such a tag"
|
||||
msgstr ""
|
||||
|
||||
msgid "Welcome to {}"
|
||||
msgid "New Blog"
|
||||
msgstr ""
|
||||
|
||||
msgid "Latest articles"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your feed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Federated feed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Local feed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Administration of {0}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Instances"
|
||||
msgstr ""
|
||||
|
||||
msgid "Configuration"
|
||||
msgstr ""
|
||||
|
||||
msgid "Users"
|
||||
msgstr ""
|
||||
|
||||
msgid "Unblock"
|
||||
msgstr ""
|
||||
|
||||
msgid "Block"
|
||||
msgstr ""
|
||||
|
||||
msgid "Ban"
|
||||
msgstr ""
|
||||
|
||||
msgid "All the articles of the Fediverse"
|
||||
msgstr ""
|
||||
|
||||
msgid "Articles from {}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nothing to see here yet. Try subscribing to more people."
|
||||
msgid "Create a blog"
|
||||
msgstr ""
|
||||
|
||||
# src/template_utils.rs:251
|
||||
msgid "Name"
|
||||
msgid "Title"
|
||||
msgstr ""
|
||||
|
||||
# src/template_utils.rs:254
|
||||
msgid "Optional"
|
||||
msgstr ""
|
||||
|
||||
msgid "Allow anyone to register here"
|
||||
msgid "Create blog"
|
||||
msgstr ""
|
||||
|
||||
msgid "Short description"
|
||||
msgid "Edit \"{}\""
|
||||
msgstr ""
|
||||
|
||||
msgid "Description"
|
||||
msgstr ""
|
||||
|
||||
msgid "Markdown syntax is supported"
|
||||
msgstr ""
|
||||
|
||||
msgid "Long description"
|
||||
msgid "You can upload images to your gallery, to use them as blog icons, or banners."
|
||||
msgstr ""
|
||||
|
||||
# src/template_utils.rs:251
|
||||
msgid "Default article license"
|
||||
msgid "Upload images"
|
||||
msgstr ""
|
||||
|
||||
msgid "Save these settings"
|
||||
msgid "Blog icon"
|
||||
msgstr ""
|
||||
|
||||
msgid "About {0}"
|
||||
msgid "Blog banner"
|
||||
msgstr ""
|
||||
|
||||
msgid "Home to <em>{0}</em> people"
|
||||
msgstr ""
|
||||
|
||||
msgid "Who wrote <em>{0}</em> articles"
|
||||
msgstr ""
|
||||
|
||||
msgid "And are connected to <em>{0}</em> other instances"
|
||||
msgstr ""
|
||||
|
||||
msgid "Administred by"
|
||||
msgstr ""
|
||||
|
||||
msgid "Runs Plume {0}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Follow {}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Log in to follow"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enter your full username handle to follow"
|
||||
msgstr ""
|
||||
|
||||
msgid "Edit your account"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your Profile"
|
||||
msgstr ""
|
||||
|
||||
msgid "To change your avatar, upload it to your gallery and then select from there."
|
||||
msgstr ""
|
||||
|
||||
msgid "Upload an avatar"
|
||||
msgstr ""
|
||||
|
||||
# src/template_utils.rs:251
|
||||
msgid "Display name"
|
||||
msgstr ""
|
||||
|
||||
# src/template_utils.rs:251
|
||||
msgid "Email"
|
||||
msgstr ""
|
||||
|
||||
msgid "Summary"
|
||||
msgstr ""
|
||||
|
||||
msgid "Update account"
|
||||
msgid "Update blog"
|
||||
msgstr ""
|
||||
|
||||
msgid "Danger zone"
|
||||
msgstr ""
|
||||
|
||||
msgid "Be very careful, any action taken here can't be cancelled."
|
||||
msgid "Be very careful, any action taken here can't be reversed."
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete your account"
|
||||
msgid "Permanently delete this blog"
|
||||
msgstr ""
|
||||
|
||||
msgid "Sorry, but as an admin, you can't leave your own instance."
|
||||
msgid "{}'s icon"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your Dashboard"
|
||||
msgid "Edit"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your Blogs"
|
||||
msgid "There's one author on this blog: "
|
||||
msgid_plural "There are {0} authors on this blog: "
|
||||
msgstr[0] ""
|
||||
|
||||
msgid "Latest articles"
|
||||
msgstr ""
|
||||
|
||||
msgid "You don't have any blog yet. Create your own, or ask to join one."
|
||||
msgid "No posts to see here yet."
|
||||
msgstr ""
|
||||
|
||||
msgid "Start a new blog"
|
||||
msgid "Search result(s) for \"{0}\""
|
||||
msgstr ""
|
||||
|
||||
msgid "Your Drafts"
|
||||
msgid "Search result(s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your media"
|
||||
msgid "No results for your query"
|
||||
msgstr ""
|
||||
|
||||
msgid "Go to your gallery"
|
||||
msgid "No more results for your query"
|
||||
msgstr ""
|
||||
|
||||
msgid "Create your account"
|
||||
msgstr ""
|
||||
|
||||
msgid "Create an account"
|
||||
msgstr ""
|
||||
|
||||
# src/template_utils.rs:251
|
||||
msgid "Username"
|
||||
msgstr ""
|
||||
|
||||
# src/template_utils.rs:251
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
|
||||
# src/template_utils.rs:251
|
||||
msgid "Password confirmation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Apologies, but registrations are closed on this particular instance. You can, however, find a different one."
|
||||
msgstr ""
|
||||
|
||||
msgid "Articles"
|
||||
msgstr ""
|
||||
|
||||
msgid "Subscribers"
|
||||
msgstr ""
|
||||
|
||||
msgid "Subscriptions"
|
||||
msgstr ""
|
||||
|
||||
msgid "Atom feed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Recently boosted"
|
||||
msgstr ""
|
||||
|
||||
msgid "Admin"
|
||||
msgstr ""
|
||||
|
||||
msgid "It is you"
|
||||
msgstr ""
|
||||
|
||||
msgid "Edit your profile"
|
||||
msgstr ""
|
||||
|
||||
msgid "Open on {0}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Unsubscribe"
|
||||
msgstr ""
|
||||
|
||||
msgid "Subscribe"
|
||||
msgstr ""
|
||||
|
||||
msgid "{0}'s subscriptions"
|
||||
msgstr ""
|
||||
|
||||
msgid "{0}'s subscribers"
|
||||
msgstr ""
|
||||
|
||||
msgid "Respond"
|
||||
msgstr ""
|
||||
|
||||
msgid "Are you sure?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete this comment"
|
||||
msgstr ""
|
||||
|
||||
msgid "What is Plume?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Plume is a decentralized blogging engine."
|
||||
msgstr ""
|
||||
|
||||
msgid "Authors can manage multiple blogs, each as its own website."
|
||||
msgstr ""
|
||||
|
||||
msgid "Articles are also visible on other Plume instances, and you can interact with them directly from other platforms like Mastodon."
|
||||
msgstr ""
|
||||
|
||||
msgid "Read the detailed rules"
|
||||
msgstr ""
|
||||
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
msgid "No description"
|
||||
msgstr ""
|
||||
|
||||
msgid "View all"
|
||||
msgstr ""
|
||||
|
||||
msgid "By {0}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Draft"
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your query"
|
||||
@ -542,9 +370,6 @@ msgstr ""
|
||||
msgid "Article title matching these words"
|
||||
msgstr ""
|
||||
|
||||
msgid "Title"
|
||||
msgstr ""
|
||||
|
||||
# src/template_utils.rs:339
|
||||
msgid "Subtitle matching these words"
|
||||
msgstr ""
|
||||
@ -609,52 +434,6 @@ msgstr ""
|
||||
msgid "Article license"
|
||||
msgstr ""
|
||||
|
||||
msgid "Search result(s) for \"{0}\""
|
||||
msgstr ""
|
||||
|
||||
msgid "Search result(s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "No results for your query"
|
||||
msgstr ""
|
||||
|
||||
msgid "No more results for your query"
|
||||
msgstr ""
|
||||
|
||||
msgid "Reset your password"
|
||||
msgstr ""
|
||||
|
||||
# src/template_utils.rs:251
|
||||
msgid "New password"
|
||||
msgstr ""
|
||||
|
||||
# src/template_utils.rs:251
|
||||
msgid "Confirmation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Update password"
|
||||
msgstr ""
|
||||
|
||||
msgid "Check your inbox!"
|
||||
msgstr ""
|
||||
|
||||
msgid "We sent a mail to the address you gave us, with a link to reset your password."
|
||||
msgstr ""
|
||||
|
||||
# src/template_utils.rs:251
|
||||
msgid "E-mail"
|
||||
msgstr ""
|
||||
|
||||
msgid "Send password reset link"
|
||||
msgstr ""
|
||||
|
||||
msgid "Log in"
|
||||
msgstr ""
|
||||
|
||||
# src/template_utils.rs:251
|
||||
msgid "Username, or email"
|
||||
msgstr ""
|
||||
|
||||
msgid "Interact with {}"
|
||||
msgstr ""
|
||||
|
||||
@ -713,12 +492,6 @@ msgstr ""
|
||||
msgid "Written by {0}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Edit"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete this article"
|
||||
msgstr ""
|
||||
|
||||
msgid "All rights reserved."
|
||||
msgstr ""
|
||||
|
||||
@ -748,6 +521,12 @@ msgstr ""
|
||||
msgid "{0}Log in{1}, or {2}use your Fediverse account{3} to interact with this article"
|
||||
msgstr ""
|
||||
|
||||
msgid "Unsubscribe"
|
||||
msgstr ""
|
||||
|
||||
msgid "Subscribe"
|
||||
msgstr ""
|
||||
|
||||
msgid "Comments"
|
||||
msgstr ""
|
||||
|
||||
@ -764,120 +543,16 @@ msgstr ""
|
||||
msgid "No comments yet. Be the first to react!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Invalid CSRF token"
|
||||
msgstr ""
|
||||
|
||||
msgid "Something is wrong with your CSRF token. Make sure cookies are enabled in you browser, and try reloading this page. If you continue to see this error message, please report it."
|
||||
msgstr ""
|
||||
|
||||
msgid "Page not found"
|
||||
msgstr ""
|
||||
|
||||
msgid "We couldn't find this page."
|
||||
msgstr ""
|
||||
|
||||
msgid "The link that led you here may be broken."
|
||||
msgstr ""
|
||||
|
||||
msgid "The content you sent can't be processed."
|
||||
msgstr ""
|
||||
|
||||
msgid "Maybe it was too long."
|
||||
msgstr ""
|
||||
|
||||
msgid "You are not authorized."
|
||||
msgstr ""
|
||||
|
||||
msgid "Internal server error"
|
||||
msgstr ""
|
||||
|
||||
msgid "Something broke on our side."
|
||||
msgstr ""
|
||||
|
||||
msgid "Sorry about that. If you think this is a bug, please report it."
|
||||
msgstr ""
|
||||
|
||||
msgid "Edit \"{}\""
|
||||
msgstr ""
|
||||
|
||||
msgid "Description"
|
||||
msgstr ""
|
||||
|
||||
msgid "You can upload images to your gallery, to use them as blog icons, or banners."
|
||||
msgstr ""
|
||||
|
||||
msgid "Upload images"
|
||||
msgstr ""
|
||||
|
||||
msgid "Blog icon"
|
||||
msgstr ""
|
||||
|
||||
msgid "Blog banner"
|
||||
msgstr ""
|
||||
|
||||
msgid "Update blog"
|
||||
msgstr ""
|
||||
|
||||
msgid "Be very careful, any action taken here can't be reversed."
|
||||
msgstr ""
|
||||
|
||||
msgid "Permanently delete this blog"
|
||||
msgstr ""
|
||||
|
||||
msgid "New Blog"
|
||||
msgstr ""
|
||||
|
||||
msgid "Create a blog"
|
||||
msgstr ""
|
||||
|
||||
msgid "Create blog"
|
||||
msgstr ""
|
||||
|
||||
msgid "{}'s icon"
|
||||
msgstr ""
|
||||
|
||||
msgid "New article"
|
||||
msgstr ""
|
||||
|
||||
msgid "There's one author on this blog: "
|
||||
msgid_plural "There are {0} authors on this blog: "
|
||||
msgstr[0] ""
|
||||
|
||||
msgid "No posts to see here yet."
|
||||
msgstr ""
|
||||
|
||||
msgid "Articles tagged \"{0}\""
|
||||
msgstr ""
|
||||
|
||||
msgid "There are currently no articles with such a tag"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm from this instance"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm from another instance"
|
||||
msgstr ""
|
||||
|
||||
# src/template_utils.rs:259
|
||||
msgid "Example: user@plu.me"
|
||||
msgstr ""
|
||||
|
||||
msgid "Continue to your instance"
|
||||
msgstr ""
|
||||
|
||||
msgid "Upload"
|
||||
msgstr ""
|
||||
|
||||
msgid "You don't have any media yet."
|
||||
msgstr ""
|
||||
|
||||
msgid "Content warning: {0}"
|
||||
msgid "Are you sure?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
|
||||
msgid "Details"
|
||||
msgid "This article is still a draft. Only you and other authors can see it."
|
||||
msgstr ""
|
||||
|
||||
msgid "Only you and other authors can edit this article."
|
||||
msgstr ""
|
||||
|
||||
msgid "Media upload"
|
||||
@ -895,6 +570,21 @@ msgstr ""
|
||||
msgid "Send"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your media"
|
||||
msgstr ""
|
||||
|
||||
msgid "Upload"
|
||||
msgstr ""
|
||||
|
||||
msgid "You don't have any media yet."
|
||||
msgstr ""
|
||||
|
||||
msgid "Content warning: {0}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Details"
|
||||
msgstr ""
|
||||
|
||||
msgid "Media details"
|
||||
msgstr ""
|
||||
|
||||
@ -909,3 +599,333 @@ msgstr ""
|
||||
|
||||
msgid "Use as an avatar"
|
||||
msgstr ""
|
||||
|
||||
msgid "Notifications"
|
||||
msgstr ""
|
||||
|
||||
msgid "Plume"
|
||||
msgstr ""
|
||||
|
||||
msgid "Menu"
|
||||
msgstr ""
|
||||
|
||||
msgid "Dashboard"
|
||||
msgstr ""
|
||||
|
||||
msgid "Log Out"
|
||||
msgstr ""
|
||||
|
||||
msgid "My account"
|
||||
msgstr ""
|
||||
|
||||
msgid "Log In"
|
||||
msgstr ""
|
||||
|
||||
msgid "Register"
|
||||
msgstr ""
|
||||
|
||||
msgid "About this instance"
|
||||
msgstr ""
|
||||
|
||||
msgid "Privacy policy"
|
||||
msgstr ""
|
||||
|
||||
msgid "Administration"
|
||||
msgstr ""
|
||||
|
||||
msgid "Documentation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Source code"
|
||||
msgstr ""
|
||||
|
||||
msgid "Matrix room"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your feed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Federated feed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Local feed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nothing to see here yet. Try subscribing to more people."
|
||||
msgstr ""
|
||||
|
||||
msgid "Articles from {}"
|
||||
msgstr ""
|
||||
|
||||
msgid "All the articles of the Fediverse"
|
||||
msgstr ""
|
||||
|
||||
msgid "Users"
|
||||
msgstr ""
|
||||
|
||||
msgid "Configuration"
|
||||
msgstr ""
|
||||
|
||||
msgid "Instances"
|
||||
msgstr ""
|
||||
|
||||
msgid "Ban"
|
||||
msgstr ""
|
||||
|
||||
msgid "Administration of {0}"
|
||||
msgstr ""
|
||||
|
||||
# src/template_utils.rs:251
|
||||
msgid "Name"
|
||||
msgstr ""
|
||||
|
||||
msgid "Allow anyone to register here"
|
||||
msgstr ""
|
||||
|
||||
msgid "Short description"
|
||||
msgstr ""
|
||||
|
||||
msgid "Long description"
|
||||
msgstr ""
|
||||
|
||||
# src/template_utils.rs:251
|
||||
msgid "Default article license"
|
||||
msgstr ""
|
||||
|
||||
msgid "Save these settings"
|
||||
msgstr ""
|
||||
|
||||
msgid "About {0}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Runs Plume {0}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Home to <em>{0}</em> people"
|
||||
msgstr ""
|
||||
|
||||
msgid "Who wrote <em>{0}</em> articles"
|
||||
msgstr ""
|
||||
|
||||
msgid "And are connected to <em>{0}</em> other instances"
|
||||
msgstr ""
|
||||
|
||||
msgid "Administred by"
|
||||
msgstr ""
|
||||
|
||||
msgid "If you are browsing this site as a visitor, no data about you is collected."
|
||||
msgstr ""
|
||||
|
||||
msgid "As a registered user, you have to provide your username (which does not have to be your real name), your functional email address and a password, in order to be able to log in, write articles and comment. The content you submit is stored until you delete it."
|
||||
msgstr ""
|
||||
|
||||
msgid "When you log in, we store two cookies, one to keep your session open, the second to prevent other people to act on your behalf. We don't store any other cookies."
|
||||
msgstr ""
|
||||
|
||||
msgid "Welcome to {}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Unblock"
|
||||
msgstr ""
|
||||
|
||||
msgid "Block"
|
||||
msgstr ""
|
||||
|
||||
msgid "Reset your password"
|
||||
msgstr ""
|
||||
|
||||
# src/template_utils.rs:251
|
||||
msgid "New password"
|
||||
msgstr ""
|
||||
|
||||
# src/template_utils.rs:251
|
||||
msgid "Confirmation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Update password"
|
||||
msgstr ""
|
||||
|
||||
msgid "Log in"
|
||||
msgstr ""
|
||||
|
||||
# src/template_utils.rs:251
|
||||
msgid "Username, or email"
|
||||
msgstr ""
|
||||
|
||||
# src/template_utils.rs:251
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
|
||||
# src/template_utils.rs:251
|
||||
msgid "E-mail"
|
||||
msgstr ""
|
||||
|
||||
msgid "Send password reset link"
|
||||
msgstr ""
|
||||
|
||||
msgid "Check your inbox!"
|
||||
msgstr ""
|
||||
|
||||
msgid "We sent a mail to the address you gave us, with a link to reset your password."
|
||||
msgstr ""
|
||||
|
||||
msgid "Admin"
|
||||
msgstr ""
|
||||
|
||||
msgid "It is you"
|
||||
msgstr ""
|
||||
|
||||
msgid "Edit your profile"
|
||||
msgstr ""
|
||||
|
||||
msgid "Open on {0}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Follow {}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Log in to follow"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enter your full username handle to follow"
|
||||
msgstr ""
|
||||
|
||||
msgid "{0}'s subscriptions"
|
||||
msgstr ""
|
||||
|
||||
msgid "Articles"
|
||||
msgstr ""
|
||||
|
||||
msgid "Subscribers"
|
||||
msgstr ""
|
||||
|
||||
msgid "Subscriptions"
|
||||
msgstr ""
|
||||
|
||||
msgid "Create your account"
|
||||
msgstr ""
|
||||
|
||||
msgid "Create an account"
|
||||
msgstr ""
|
||||
|
||||
# src/template_utils.rs:251
|
||||
msgid "Username"
|
||||
msgstr ""
|
||||
|
||||
# src/template_utils.rs:251
|
||||
msgid "Email"
|
||||
msgstr ""
|
||||
|
||||
# src/template_utils.rs:251
|
||||
msgid "Password confirmation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Apologies, but registrations are closed on this particular instance. You can, however, find a different one."
|
||||
msgstr ""
|
||||
|
||||
msgid "{0}'s subscribers"
|
||||
msgstr ""
|
||||
|
||||
msgid "Edit your account"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your Profile"
|
||||
msgstr ""
|
||||
|
||||
msgid "To change your avatar, upload it to your gallery and then select from there."
|
||||
msgstr ""
|
||||
|
||||
msgid "Upload an avatar"
|
||||
msgstr ""
|
||||
|
||||
# src/template_utils.rs:251
|
||||
msgid "Display name"
|
||||
msgstr ""
|
||||
|
||||
msgid "Summary"
|
||||
msgstr ""
|
||||
|
||||
msgid "Update account"
|
||||
msgstr ""
|
||||
|
||||
msgid "Be very careful, any action taken here can't be cancelled."
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete your account"
|
||||
msgstr ""
|
||||
|
||||
msgid "Sorry, but as an admin, you can't leave your own instance."
|
||||
msgstr ""
|
||||
|
||||
msgid "Your Dashboard"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your Blogs"
|
||||
msgstr ""
|
||||
|
||||
msgid "You don't have any blog yet. Create your own, or ask to join one."
|
||||
msgstr ""
|
||||
|
||||
msgid "Start a new blog"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your Drafts"
|
||||
msgstr ""
|
||||
|
||||
msgid "Go to your gallery"
|
||||
msgstr ""
|
||||
|
||||
msgid "Atom feed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Recently boosted"
|
||||
msgstr ""
|
||||
|
||||
msgid "What is Plume?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Plume is a decentralized blogging engine."
|
||||
msgstr ""
|
||||
|
||||
msgid "Authors can manage multiple blogs, each as its own website."
|
||||
msgstr ""
|
||||
|
||||
msgid "Articles are also visible on other Plume instances, and you can interact with them directly from other platforms like Mastodon."
|
||||
msgstr ""
|
||||
|
||||
msgid "Read the detailed rules"
|
||||
msgstr ""
|
||||
|
||||
msgid "View all"
|
||||
msgstr ""
|
||||
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
msgid "No description"
|
||||
msgstr ""
|
||||
|
||||
msgid "By {0}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Draft"
|
||||
msgstr ""
|
||||
|
||||
msgid "Respond"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete this comment"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm from this instance"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm from another instance"
|
||||
msgstr ""
|
||||
|
||||
# src/template_utils.rs:259
|
||||
msgid "Example: user@plu.me"
|
||||
msgstr ""
|
||||
|
||||
msgid "Continue to your instance"
|
||||
msgstr ""
|
||||
|
954
po/plume/pt.po
954
po/plume/pt.po
File diff suppressed because it is too large
Load Diff
899
po/plume/ro.po
899
po/plume/ro.po
File diff suppressed because it is too large
Load Diff
906
po/plume/ru.po
906
po/plume/ru.po
File diff suppressed because it is too large
Load Diff
968
po/plume/sk.po
968
po/plume/sk.po
File diff suppressed because it is too large
Load Diff
855
po/plume/sr.po
855
po/plume/sr.po
File diff suppressed because it is too large
Load Diff
849
po/plume/sv.po
849
po/plume/sv.po
File diff suppressed because it is too large
Load Diff
@ -197,9 +197,10 @@ Then try to restart Plume
|
||||
routes::instance::feed,
|
||||
routes::instance::federated,
|
||||
routes::instance::admin,
|
||||
routes::instance::admin_mod,
|
||||
routes::instance::admin_instances,
|
||||
routes::instance::admin_users,
|
||||
routes::instance::ban,
|
||||
routes::instance::edit_users,
|
||||
routes::instance::toggle_block,
|
||||
routes::instance::update_settings,
|
||||
routes::instance::shared_inbox,
|
||||
|
@ -1,17 +1,27 @@
|
||||
use rocket::{
|
||||
request::LenientForm,
|
||||
request::{FormItems, FromForm, LenientForm},
|
||||
response::{status, Flash, Redirect},
|
||||
};
|
||||
use rocket_contrib::json::Json;
|
||||
use rocket_i18n::I18n;
|
||||
use scheduled_thread_pool::ScheduledThreadPool;
|
||||
use serde_json;
|
||||
use std::str::FromStr;
|
||||
use validator::{Validate, ValidationErrors};
|
||||
|
||||
use inbox;
|
||||
use plume_common::activity_pub::{broadcast, inbox::FromId};
|
||||
use plume_models::{
|
||||
admin::Admin, comments::Comment, db_conn::DbConn, headers::Headers, instance::*, posts::Post,
|
||||
safe_string::SafeString, users::User, Error, PlumeRocket, CONFIG,
|
||||
admin::*,
|
||||
comments::Comment,
|
||||
db_conn::DbConn,
|
||||
headers::Headers,
|
||||
instance::*,
|
||||
posts::Post,
|
||||
safe_string::SafeString,
|
||||
search::Searcher,
|
||||
users::{Role, User},
|
||||
Connection, Error, PlumeRocket, CONFIG,
|
||||
};
|
||||
use routes::{errors::ErrorPage, rocket_uri_macro_static_files, Page, RespondOrRedirect};
|
||||
use template_utils::{IntoContext, Ructe};
|
||||
@ -98,6 +108,11 @@ pub fn admin(_admin: Admin, rockets: PlumeRocket) -> Result<Ructe, ErrorPage> {
|
||||
)))
|
||||
}
|
||||
|
||||
#[get("/admin", rank = 2)]
|
||||
pub fn admin_mod(_mod: Moderator, rockets: PlumeRocket) -> Ructe {
|
||||
render!(instance::admin_mod(&rockets.to_context()))
|
||||
}
|
||||
|
||||
#[derive(Clone, FromForm, Validate)]
|
||||
pub struct InstanceSettingsForm {
|
||||
#[validate(length(min = "1"))]
|
||||
@ -149,7 +164,7 @@ pub fn update_settings(
|
||||
|
||||
#[get("/admin/instances?<page>")]
|
||||
pub fn admin_instances(
|
||||
_admin: Admin,
|
||||
_mod: Moderator,
|
||||
page: Option<Page>,
|
||||
rockets: PlumeRocket,
|
||||
) -> Result<Ructe, ErrorPage> {
|
||||
@ -166,7 +181,7 @@ pub fn admin_instances(
|
||||
|
||||
#[post("/admin/instances/<id>/block")]
|
||||
pub fn toggle_block(
|
||||
_admin: Admin,
|
||||
_mod: Moderator,
|
||||
conn: DbConn,
|
||||
id: i32,
|
||||
intl: I18n,
|
||||
@ -187,7 +202,7 @@ pub fn toggle_block(
|
||||
|
||||
#[get("/admin/users?<page>")]
|
||||
pub fn admin_users(
|
||||
_admin: Admin,
|
||||
_mod: Moderator,
|
||||
page: Option<Page>,
|
||||
rockets: PlumeRocket,
|
||||
) -> Result<Ructe, ErrorPage> {
|
||||
@ -200,27 +215,150 @@ pub fn admin_users(
|
||||
)))
|
||||
}
|
||||
|
||||
#[post("/admin/users/<id>/ban")]
|
||||
pub fn ban(_admin: Admin, id: i32, rockets: PlumeRocket) -> Result<Flash<Redirect>, ErrorPage> {
|
||||
let u = User::get(&*rockets.conn, id)?;
|
||||
u.delete(&*rockets.conn, &rockets.searcher)?;
|
||||
/// A structure to handle forms that are a list of items on which actions are applied.
|
||||
///
|
||||
/// This is for instance the case of the user list in the administration.
|
||||
pub struct MultiAction<T>
|
||||
where
|
||||
T: FromStr,
|
||||
{
|
||||
ids: Vec<i32>,
|
||||
action: T,
|
||||
}
|
||||
|
||||
impl<'f, T> FromForm<'f> for MultiAction<T>
|
||||
where
|
||||
T: FromStr,
|
||||
{
|
||||
type Error = ();
|
||||
|
||||
fn from_form(items: &mut FormItems, _strict: bool) -> Result<Self, Self::Error> {
|
||||
let (ids, act) = items.fold((vec![], None), |(mut ids, act), item| {
|
||||
let (name, val) = item.key_value_decoded();
|
||||
|
||||
if name == "action" {
|
||||
(ids, T::from_str(&val).ok())
|
||||
} else if let Ok(id) = name.parse::<i32>() {
|
||||
ids.push(id);
|
||||
(ids, act)
|
||||
} else {
|
||||
(ids, act)
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(act) = act {
|
||||
Ok(MultiAction { ids, action: act })
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum UserActions {
|
||||
Admin,
|
||||
RevokeAdmin,
|
||||
Moderator,
|
||||
RevokeModerator,
|
||||
Ban,
|
||||
}
|
||||
|
||||
impl FromStr for UserActions {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"admin" => Ok(UserActions::Admin),
|
||||
"un-admin" => Ok(UserActions::RevokeAdmin),
|
||||
"moderator" => Ok(UserActions::Moderator),
|
||||
"un-moderator" => Ok(UserActions::RevokeModerator),
|
||||
"ban" => Ok(UserActions::Ban),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/admin/users/edit", data = "<form>")]
|
||||
pub fn edit_users(
|
||||
moderator: Moderator,
|
||||
form: LenientForm<MultiAction<UserActions>>,
|
||||
rockets: PlumeRocket,
|
||||
) -> Result<Flash<Redirect>, ErrorPage> {
|
||||
// you can't change your own rights
|
||||
if form.ids.contains(&moderator.0.id) {
|
||||
return Ok(Flash::error(
|
||||
Redirect::to(uri!(admin_users: page = _)),
|
||||
i18n!(rockets.intl.catalog, "You can't change your own rights."),
|
||||
));
|
||||
}
|
||||
|
||||
// moderators can't grant or revoke admin rights
|
||||
if !moderator.0.is_admin() {
|
||||
match form.action {
|
||||
UserActions::Admin | UserActions::RevokeAdmin => {
|
||||
return Ok(Flash::error(
|
||||
Redirect::to(uri!(admin_users: page = _)),
|
||||
i18n!(
|
||||
rockets.intl.catalog,
|
||||
"You are not allowed to take this action."
|
||||
),
|
||||
))
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let conn = &rockets.conn;
|
||||
let searcher = &*rockets.searcher;
|
||||
let worker = &*rockets.worker;
|
||||
match form.action {
|
||||
UserActions::Admin => {
|
||||
for u in form.ids.clone() {
|
||||
User::get(conn, u)?.set_role(conn, Role::Admin)?;
|
||||
}
|
||||
}
|
||||
UserActions::Moderator => {
|
||||
for u in form.ids.clone() {
|
||||
User::get(conn, u)?.set_role(conn, Role::Moderator)?;
|
||||
}
|
||||
}
|
||||
UserActions::RevokeAdmin | UserActions::RevokeModerator => {
|
||||
for u in form.ids.clone() {
|
||||
User::get(conn, u)?.set_role(conn, Role::Normal)?;
|
||||
}
|
||||
}
|
||||
UserActions::Ban => {
|
||||
for u in form.ids.clone() {
|
||||
ban(u, conn, searcher, worker)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Flash::success(
|
||||
Redirect::to(uri!(admin_users: page = _)),
|
||||
i18n!(rockets.intl.catalog, "Done."),
|
||||
))
|
||||
}
|
||||
|
||||
fn ban(
|
||||
id: i32,
|
||||
conn: &Connection,
|
||||
searcher: &Searcher,
|
||||
worker: &ScheduledThreadPool,
|
||||
) -> Result<(), ErrorPage> {
|
||||
let u = User::get(&*conn, id)?;
|
||||
u.delete(&*conn, searcher)?;
|
||||
|
||||
if Instance::get_local()
|
||||
.map(|i| u.instance_id == i.id)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
let target = User::one_by_instance(&*rockets.conn)?;
|
||||
let delete_act = u.delete_activity(&*rockets.conn)?;
|
||||
let target = User::one_by_instance(&*conn)?;
|
||||
let delete_act = u.delete_activity(&*conn)?;
|
||||
let u_clone = u.clone();
|
||||
rockets
|
||||
.worker
|
||||
.execute(move || broadcast(&u_clone, delete_act, target));
|
||||
worker.execute(move || broadcast(&u_clone, delete_act, target));
|
||||
}
|
||||
|
||||
Ok(Flash::success(
|
||||
Redirect::to(uri!(admin_users: page = _)),
|
||||
i18n!(rockets.intl.catalog, "{} has been banned."; u.name()),
|
||||
))
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[post("/inbox", data = "<data>")]
|
||||
|
@ -522,7 +522,7 @@ pub fn create(
|
||||
conn,
|
||||
form.username.to_string(),
|
||||
form.username.to_string(),
|
||||
false,
|
||||
Role::Normal,
|
||||
"",
|
||||
form.email.to_string(),
|
||||
User::hash_pass(&form.password).map_err(to_validation)?,
|
||||
|
@ -84,7 +84,7 @@
|
||||
<h3>@Instance::get_local().map(|i| i.name).unwrap_or_default()</h3>
|
||||
<a href="@uri!(instance::about)">@i18n!(ctx.1, "About this instance")</a>
|
||||
<a href="@uri!(instance::privacy)">@i18n!(ctx.1, "Privacy policy")</a>
|
||||
@if ctx.2.clone().map(|a| a.is_admin).unwrap_or(false) {
|
||||
@if ctx.2.clone().map(|u| u.is_admin()).unwrap_or(false) {
|
||||
<a href="@uri!(instance::admin)">@i18n!(ctx.1, "Administration")</a>
|
||||
}
|
||||
</div>
|
||||
|
15
templates/instance/admin_mod.rs.html
Normal file
15
templates/instance/admin_mod.rs.html
Normal file
@ -0,0 +1,15 @@
|
||||
@use templates::base;
|
||||
@use template_utils::*;
|
||||
@use routes::*;
|
||||
|
||||
@(ctx: BaseContext)
|
||||
|
||||
@:base(ctx, i18n!(ctx.1, "Moderation"), {}, {}, {
|
||||
<h1>@i18n!(ctx.1, "Moderation")</h1>
|
||||
|
||||
@tabs(&[
|
||||
(&uri!(instance::admin).to_string(), i18n!(ctx.1, "Home"), true),
|
||||
(&uri!(instance::admin_instances: page = _).to_string(), i18n!(ctx.1, "Instances"), false),
|
||||
(&uri!(instance::admin_users: page = _).to_string(), i18n!(ctx.1, "Users"), false),
|
||||
])
|
||||
})
|
@ -14,21 +14,36 @@
|
||||
(&uri!(instance::admin_users: page = _).to_string(), i18n!(ctx.1, "Users"), true),
|
||||
])
|
||||
|
||||
<div class="list">
|
||||
@for user in users {
|
||||
<div class="card flex compact">
|
||||
@avatar(ctx.0, &user, Size::Small, false, ctx.1)
|
||||
<p class="grow">
|
||||
<a href="@uri!(user::details: name = &user.fqn)">@user.name()</a>
|
||||
<small>@format!("@{}", user.username)</small>
|
||||
</p>
|
||||
@if !user.is_admin {
|
||||
<form class="inline" method="post" action="@uri!(instance::ban: id = user.id)">
|
||||
<input type="submit" value="@i18n!(ctx.1, "Ban")">
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<form method="post" action="@uri!(instance::edit_users)">
|
||||
<header>
|
||||
<select name="action">
|
||||
<option value="admin">@i18n!(ctx.1, "Grant admin rights")</option>
|
||||
<option value="un-admin">@i18n!(ctx.1, "Revoke admin rights")</option>
|
||||
<option value="moderator">@i18n!(ctx.1, "Grant moderator rights")</option>
|
||||
<option value="un-moderator">@i18n!(ctx.1, "Revoke moderator rights")</option>
|
||||
<option value="ban">@i18n!(ctx.1, "Ban")</option>
|
||||
</select>
|
||||
<input type="submit" value="@i18n!(ctx.1, "Run on selected users")">
|
||||
</header>
|
||||
<div class="list">
|
||||
@for user in users {
|
||||
<div class="card flex compact">
|
||||
<input type="checkbox" name="@user.id">
|
||||
@avatar(ctx.0, &user, Size::Small, false, ctx.1)
|
||||
<p class="grow">
|
||||
<a href="@uri!(user::details: name = &user.fqn)">@user.name()</a>
|
||||
<small>@format!("@{}", user.username)</small>
|
||||
</p>
|
||||
@if user.is_admin() {
|
||||
<p class="badge">@i18n!(ctx.1, "Admin")</p>
|
||||
} else {
|
||||
@if user.is_moderator() {
|
||||
<p class="badge">@i18n!(ctx.1, "Moderator")</p>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</form>
|
||||
@paginate(ctx.1, page, n_pages)
|
||||
})
|
||||
|
@ -52,7 +52,7 @@
|
||||
|
||||
<h2>@i18n!(ctx.1, "Danger zone")</h2>
|
||||
<p>@i18n!(ctx.1, "Be very careful, any action taken here can't be cancelled.")
|
||||
@if !u.is_admin {
|
||||
@if !u.is_admin() {
|
||||
<form method="post" action="@uri!(user::delete: name = u.username)">
|
||||
<input type="submit" class="inline-block button destructive" value="@i18n!(ctx.1, "Delete your account")">
|
||||
</form>
|
||||
|
@ -15,7 +15,7 @@
|
||||
</h1>
|
||||
|
||||
<p>
|
||||
@if user.is_admin {
|
||||
@if user.is_admin() {
|
||||
<span class="badge">@i18n!(ctx.1, "Admin")</span>
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user