Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 15cb3cf156 |
@@ -20,5 +20,3 @@ search_index
|
|||||||
__pycache__
|
__pycache__
|
||||||
.vscode/
|
.vscode/
|
||||||
*-journal
|
*-journal
|
||||||
.direnv/
|
|
||||||
build.log*
|
|
||||||
|
|||||||
@@ -8,18 +8,11 @@
|
|||||||
|
|
||||||
- Add 'My feed' to i18n timeline name (#1084)
|
- Add 'My feed' to i18n timeline name (#1084)
|
||||||
- Bidirectional support for user page header (#1092)
|
- Bidirectional support for user page header (#1092)
|
||||||
- Add non anonymous bind to LDAP server, taken from https://git.joinplu.me/Plume/Plume/src/branch/ldap-non-anon PR
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Use blog title as slug (#1094, #1126, #1127)
|
- Use blog title as slug (#1094, #1126, #1127)
|
||||||
- Bump Rust to nightly 2022-07-19 (#1119)
|
- Bump Rust to nightly 2022-07-19 (#1119)
|
||||||
- Force LDAP simple bind with *cn* rdn instead of *uid*
|
|
||||||
- Update rust-toolchain to nightly-2023-04-14
|
|
||||||
- Update chrono from 0.4.0 to 0.4.31
|
|
||||||
- Update scheduled-thread-pool from 0.2.6 to 0.2.7
|
|
||||||
- Change blockquote color to `lightpurple`
|
|
||||||
- Some colour has been added to the tables to make them more visible
|
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
@@ -29,13 +22,6 @@
|
|||||||
- Allow empty avatar for remote users (#1129)
|
- Allow empty avatar for remote users (#1129)
|
||||||
- Percent encode blog FQN for federation interoperability (#1129)
|
- Percent encode blog FQN for federation interoperability (#1129)
|
||||||
- The same to `preferredUsername` (#1129)
|
- The same to `preferredUsername` (#1129)
|
||||||
- Deprecation warnings during build process(see rust crate updates)
|
|
||||||
- Server error 500 creating new blog with white spaces inside title. Bug reported on https://git.joinplu.me/Plume/Plume/issues/1152
|
|
||||||
- Show _Subscribe_ button in column format instead of row format in screen smaller than 600px. https://git.lainoa.eus/aitzol/Plume/commit/db8cc6e7e8351a5d74f7ce0399126e13493c62d9
|
|
||||||
|
|
||||||
### To do
|
|
||||||
|
|
||||||
- Choose rdn via environment variables for LDAP simple bind
|
|
||||||
|
|
||||||
## [[0.7.2]] - 2022-05-11
|
## [[0.7.2]] - 2022-05-11
|
||||||
|
|
||||||
|
|||||||
Generated
+1223
-1528
File diff suppressed because it is too large
Load Diff
+10
-10
@@ -1,9 +1,9 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["Plume contributors"]
|
authors = ["Plume contributors"]
|
||||||
name = "plume"
|
name = "plume"
|
||||||
version = "0.7.3-dev-fork"
|
version = "0.7.3-dev"
|
||||||
repository = "https://git.lainoa.eus/aitzol/Plume"
|
repository = "https://github.com/Plume-org/Plume"
|
||||||
edition = "2021"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
atom_syndication = "0.12.0"
|
atom_syndication = "0.12.0"
|
||||||
@@ -12,14 +12,14 @@ dotenv = "0.15.0"
|
|||||||
gettext = "0.4.0"
|
gettext = "0.4.0"
|
||||||
gettext-macros = "0.6.1"
|
gettext-macros = "0.6.1"
|
||||||
gettext-utils = "0.1.0"
|
gettext-utils = "0.1.0"
|
||||||
guid-create = "0.4.1"
|
guid-create = "0.2"
|
||||||
conv = "0.3.3"
|
|
||||||
lettre_email = "0.9.2"
|
lettre_email = "0.9.2"
|
||||||
num_cpus = "1.16.0"
|
num_cpus = "1.10"
|
||||||
rocket = "0.4.11"
|
rocket = "0.4.11"
|
||||||
rocket_contrib = { version = "0.4.11", features = ["json"] }
|
rocket_contrib = { version = "0.4.11", features = ["json"] }
|
||||||
rocket_i18n = "0.4.1"
|
rocket_i18n = "0.4.1"
|
||||||
scheduled-thread-pool = "0.2.7"
|
scheduled-thread-pool = "0.2.6"
|
||||||
|
rust-s3 = { version = "0.29.0", no-default-features = true, features = ["blocking"], optional = true}
|
||||||
serde = "1.0.137"
|
serde = "1.0.137"
|
||||||
serde_json = "1.0.81"
|
serde_json = "1.0.81"
|
||||||
shrinkwraprs = "0.3.0"
|
shrinkwraprs = "0.3.0"
|
||||||
@@ -36,7 +36,7 @@ path = "src/main.rs"
|
|||||||
|
|
||||||
[dependencies.chrono]
|
[dependencies.chrono]
|
||||||
features = ["serde"]
|
features = ["serde"]
|
||||||
version = "0.4.31"
|
version = "0.4"
|
||||||
|
|
||||||
[dependencies.ctrlc]
|
[dependencies.ctrlc]
|
||||||
features = ["termination"]
|
features = ["termination"]
|
||||||
@@ -69,13 +69,13 @@ ructe = "0.15.0"
|
|||||||
rsass = "0.26"
|
rsass = "0.26"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["postgres", "s3"]
|
default = ["postgres"]
|
||||||
postgres = ["plume-models/postgres", "diesel/postgres"]
|
postgres = ["plume-models/postgres", "diesel/postgres"]
|
||||||
sqlite = ["plume-models/sqlite", "diesel/sqlite"]
|
sqlite = ["plume-models/sqlite", "diesel/sqlite"]
|
||||||
debug-mailer = []
|
debug-mailer = []
|
||||||
test = []
|
test = []
|
||||||
search-lindera = ["plume-models/search-lindera"]
|
search-lindera = ["plume-models/search-lindera"]
|
||||||
s3 = ["plume-models/s3"]
|
s3 = ["rust-s3"]
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["plume-api", "plume-cli", "plume-models", "plume-common", "plume-front", "plume-macro"]
|
members = ["plume-api", "plume-cli", "plume-models", "plume-common", "plume-front", "plume-macro"]
|
||||||
|
|||||||
+3
-4
@@ -1,4 +1,4 @@
|
|||||||
FROM rust:latest AS builder
|
FROM rust:1 as builder
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
@@ -16,8 +16,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
WORKDIR /scratch
|
WORKDIR /scratch
|
||||||
COPY script/wasm-deps.sh .
|
COPY script/wasm-deps.sh .
|
||||||
RUN chmod a+x ./wasm-deps.sh && sleep 1 && ./wasm-deps.sh
|
RUN chmod a+x ./wasm-deps.sh && sleep 1 && ./wasm-deps.sh
|
||||||
RUN ln -s /usr/bin/python3 /usr/bin/python & \
|
|
||||||
ln -s /usr/bin/pip3 /usr/bin/pip
|
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
@@ -32,7 +30,8 @@ FROM debian:stable-slim
|
|||||||
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
libpq5
|
libpq5 \
|
||||||
|
libssl1.1
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ main article {
|
|||||||
}
|
}
|
||||||
|
|
||||||
blockquote {
|
blockquote {
|
||||||
border-inline-start: 5px solid $lightpurple;
|
border-inline-start: 5px solid $gray;
|
||||||
margin: 1em auto;
|
margin: 1em auto;
|
||||||
padding: 0em 2em;
|
padding: 0em 2em;
|
||||||
}
|
}
|
||||||
@@ -228,7 +228,7 @@ main .article-meta {
|
|||||||
fill: currentColor;
|
fill: currentColor;
|
||||||
}
|
}
|
||||||
.action.liked:hover svg.feather {
|
.action.liked:hover svg.feather {
|
||||||
background: transparentize($red, 0.75);
|
background: transparentize($red, 0.75)
|
||||||
color: $red;
|
color: $red;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -252,7 +252,7 @@ main .article-meta {
|
|||||||
background: $primary;
|
background: $primary;
|
||||||
}
|
}
|
||||||
.action.reshared:hover svg.feather {
|
.action.reshared:hover svg.feather {
|
||||||
background: transparentize($primary, 0.75);
|
background: transparentize($primary, 0.75)
|
||||||
color: $primary;
|
color: $primary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,10 +37,6 @@ body > header {
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
&.right-nav {
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 0.2em;
|
width: 0.2em;
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
/* tables */
|
|
||||||
table, td, th, tr {
|
|
||||||
border: 1px dotted $lightpurple;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
tr:nth-child(even){background-color: $gray;}
|
|
||||||
@@ -12,4 +12,3 @@
|
|||||||
@import "header";
|
@import "header";
|
||||||
@import "article";
|
@import "article";
|
||||||
@import "forms";
|
@import "forms";
|
||||||
@import "tables";
|
|
||||||
|
|||||||
@@ -12,4 +12,3 @@
|
|||||||
@import "header";
|
@import "header";
|
||||||
@import "article";
|
@import "article";
|
||||||
@import "forms";
|
@import "forms";
|
||||||
@import "tables";
|
|
||||||
|
|||||||
Generated
-116
@@ -1,116 +0,0 @@
|
|||||||
{
|
|
||||||
"nodes": {
|
|
||||||
"flake-utils": {
|
|
||||||
"inputs": {
|
|
||||||
"systems": "systems"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681202837,
|
|
||||||
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-utils_2": {
|
|
||||||
"inputs": {
|
|
||||||
"systems": "systems_2"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681202837,
|
|
||||||
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1683408522,
|
|
||||||
"narHash": "sha256-9kcPh6Uxo17a3kK3XCHhcWiV1Yu1kYj22RHiymUhMkU=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "897876e4c484f1e8f92009fd11b7d988a121a4e7",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixos-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-utils": "flake-utils",
|
|
||||||
"nixpkgs": "nixpkgs",
|
|
||||||
"rust-overlay": "rust-overlay"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"rust-overlay": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-utils": "flake-utils_2",
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1683857898,
|
|
||||||
"narHash": "sha256-pyVY4UxM6zUX97g6bk6UyCbZGCWZb2Zykrne8YxacRA=",
|
|
||||||
"owner": "oxalica",
|
|
||||||
"repo": "rust-overlay",
|
|
||||||
"rev": "4e7fba3f37f5e184ada0ef3cf1e4d8ef450f240b",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "oxalica",
|
|
||||||
"repo": "rust-overlay",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"systems": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"systems_2": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": "root",
|
|
||||||
"version": 7
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
{
|
|
||||||
description = "Developpment shell for Plume including nightly Rust compiler";
|
|
||||||
|
|
||||||
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
|
||||||
inputs.rust-overlay = {
|
|
||||||
url = "github:oxalica/rust-overlay";
|
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
};
|
|
||||||
inputs.flake-utils.url = "github:numtide/flake-utils";
|
|
||||||
|
|
||||||
outputs = { self, nixpkgs, flake-utils, rust-overlay, ... }:
|
|
||||||
flake-utils.lib.eachDefaultSystem (system:
|
|
||||||
let
|
|
||||||
overlays = [ (import rust-overlay) ];
|
|
||||||
pkgs = import nixpkgs { inherit system overlays; };
|
|
||||||
inputs = with pkgs; [
|
|
||||||
(rust-bin.nightly.latest.default.override {
|
|
||||||
targets = [ "wasm32-unknown-unknown" ];
|
|
||||||
})
|
|
||||||
wasm-pack
|
|
||||||
openssl
|
|
||||||
pkg-config
|
|
||||||
gettext
|
|
||||||
postgresql
|
|
||||||
sqlite
|
|
||||||
];
|
|
||||||
in {
|
|
||||||
packages.default = pkgs.rustPlatform.buildRustPackage {
|
|
||||||
pname = "plume";
|
|
||||||
version = "0.7.3-dev";
|
|
||||||
|
|
||||||
src = ./.;
|
|
||||||
|
|
||||||
cargoLock = {
|
|
||||||
lockFile = ./Cargo.lock;
|
|
||||||
outputHashes = {
|
|
||||||
"pulldown-cmark-0.8.0" = "sha256-lpfoRDuY3zJ3QmUqJ5k9OL0MEdGDpwmpJ+u5BCj2kIA=";
|
|
||||||
"rocket_csrf-0.1.2" = "sha256-WywZfMiwZqTPfSDcAE7ivTSYSaFX+N9fjnRsLSLb9wE=";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
buildNoDefaultFeatures = true;
|
|
||||||
buildFeatures = ["postgresql" "s3"];
|
|
||||||
|
|
||||||
nativeBuildInputs = inputs;
|
|
||||||
|
|
||||||
buildPhase = ''
|
|
||||||
wasm-pack build --target web --release plume-front
|
|
||||||
cargo build --no-default-features --features postgresql,s3 --path .
|
|
||||||
cargo build --no-default-features --features postgresql,s3 --path plume-cli
|
|
||||||
'';
|
|
||||||
installPhase = ''
|
|
||||||
cargo install --no-default-features --features postgresql,s3 --path . --target-dir $out
|
|
||||||
cargo install --no-default-features --features postgresql,s3 --path plume-cli --target-dir $out
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
devShells.default = pkgs.mkShell {
|
|
||||||
packages = inputs;
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -24,4 +24,3 @@ path = "../plume-models"
|
|||||||
postgres = ["plume-models/postgres", "diesel/postgres"]
|
postgres = ["plume-models/postgres", "diesel/postgres"]
|
||||||
sqlite = ["plume-models/sqlite", "diesel/sqlite"]
|
sqlite = ["plume-models/sqlite", "diesel/sqlite"]
|
||||||
search-lindera = ["plume-models/search-lindera"]
|
search-lindera = ["plume-models/search-lindera"]
|
||||||
s3 = ["plume-models/s3"]
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ futures = "0.3.25"
|
|||||||
|
|
||||||
[dependencies.chrono]
|
[dependencies.chrono]
|
||||||
features = ["serde"]
|
features = ["serde"]
|
||||||
version = "0.4.31"
|
version = "0.4"
|
||||||
|
|
||||||
[dependencies.pulldown-cmark]
|
[dependencies.pulldown-cmark]
|
||||||
default-features = false
|
default-features = false
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
name = "plume-front"
|
name = "plume-front"
|
||||||
version = "0.7.2"
|
version = "0.7.2"
|
||||||
authors = ["Plume contributors"]
|
authors = ["Plume contributors"]
|
||||||
edition = "2021"
|
edition = "2018"
|
||||||
|
|
||||||
[package.metadata.wasm-pack.profile.release]
|
[package.metadata.wasm-pack.profile.release]
|
||||||
wasm-opt = false
|
wasm-opt = false
|
||||||
@@ -17,13 +17,13 @@ gettext-utils = "0.1.0"
|
|||||||
lazy_static = "1.3"
|
lazy_static = "1.3"
|
||||||
serde = "1.0.137"
|
serde = "1.0.137"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
wasm-bindgen = "0.2.99"
|
wasm-bindgen = "0.2.81"
|
||||||
js-sys = "0.3.76"
|
js-sys = "0.3.58"
|
||||||
serde_derive = "1.0.123"
|
serde_derive = "1.0.123"
|
||||||
console_error_panic_hook = "0.1.6"
|
console_error_panic_hook = "0.1.6"
|
||||||
|
|
||||||
[dependencies.web-sys]
|
[dependencies.web-sys]
|
||||||
version = "0.3.76"
|
version = "0.3.58"
|
||||||
features = [
|
features = [
|
||||||
'console',
|
'console',
|
||||||
'ClipboardEvent',
|
'ClipboardEvent',
|
||||||
|
|||||||
@@ -168,14 +168,10 @@ fn load_autosave() {
|
|||||||
.get(&get_autosave_id())
|
.get(&get_autosave_id())
|
||||||
{
|
{
|
||||||
let autosave_info: AutosaveInformation = serde_json::from_str(&autosave_str).ok().unwrap();
|
let autosave_info: AutosaveInformation = serde_json::from_str(&autosave_str).ok().unwrap();
|
||||||
let d = &JsValue::from_f64(autosave_info.last_saved);
|
|
||||||
|
|
||||||
let message = i18n!(
|
let message = i18n!(
|
||||||
CATALOG,
|
CATALOG,
|
||||||
"Do you want to load the local autosave last edited at {}?";
|
"Do you want to load the local autosave last edited at {}?";
|
||||||
// next line shows 'unexpected token' error on docker image building
|
Date::new(&JsValue::from_f64(autosave_info.last_saved)).to_date_string().as_string().unwrap()
|
||||||
//Date::new(&JsValue::from_f64(autosave_info.last_saved)).to_date_string().as_string().unwrap()
|
|
||||||
Date::new(d).to_date_string().as_string().unwrap()
|
|
||||||
);
|
);
|
||||||
if let Ok(true) = window().unwrap().confirm_with_message(&message) {
|
if let Ok(true) = window().unwrap().confirm_with_message(&message) {
|
||||||
set_value("editor-content", &autosave_info.contents);
|
set_value("editor-content", &autosave_info.contents);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ edition = "2018"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
ammonia = "3.2.0"
|
ammonia = "3.2.0"
|
||||||
bcrypt = "0.12.1"
|
bcrypt = "0.12.1"
|
||||||
guid-create = "0.3.0"
|
guid-create = "0.2"
|
||||||
itertools = "0.10.3"
|
itertools = "0.10.3"
|
||||||
lazy_static = "1.0"
|
lazy_static = "1.0"
|
||||||
ldap3 = "0.11.1"
|
ldap3 = "0.11.1"
|
||||||
@@ -16,9 +16,9 @@ openssl = "0.10.40"
|
|||||||
rocket = "0.4.11"
|
rocket = "0.4.11"
|
||||||
rocket_i18n = "0.4.1"
|
rocket_i18n = "0.4.1"
|
||||||
reqwest = "0.11.11"
|
reqwest = "0.11.11"
|
||||||
scheduled-thread-pool = "0.2.7"
|
scheduled-thread-pool = "0.2.6"
|
||||||
serde = "1.0.137"
|
serde = "1.0.137"
|
||||||
rust-s3 = { version = "0.33.0", optional = true, features = ["blocking"] }
|
rust-s3 = { version = "0.29.0", no-default-features = true, features = ["blocking"] }
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde_json = "1.0.81"
|
serde_json = "1.0.81"
|
||||||
tantivy = "0.13.3"
|
tantivy = "0.13.3"
|
||||||
@@ -36,12 +36,10 @@ once_cell = "1.12.0"
|
|||||||
lettre = "0.9.6"
|
lettre = "0.9.6"
|
||||||
native-tls = "0.2.10"
|
native-tls = "0.2.10"
|
||||||
activitystreams = "=0.7.0-alpha.20"
|
activitystreams = "=0.7.0-alpha.20"
|
||||||
ahash = "=0.8.11"
|
|
||||||
heck = "0.4.1"
|
|
||||||
|
|
||||||
[dependencies.chrono]
|
[dependencies.chrono]
|
||||||
features = ["serde"]
|
features = ["serde"]
|
||||||
version = "0.4.31"
|
version = "0.4"
|
||||||
|
|
||||||
[dependencies.diesel]
|
[dependencies.diesel]
|
||||||
features = ["r2d2", "chrono"]
|
features = ["r2d2", "chrono"]
|
||||||
@@ -64,4 +62,3 @@ diesel_migrations = "1.3.0"
|
|||||||
postgres = ["diesel/postgres", "plume-macro/postgres" ]
|
postgres = ["diesel/postgres", "plume-macro/postgres" ]
|
||||||
sqlite = ["diesel/sqlite", "plume-macro/sqlite" ]
|
sqlite = ["diesel/sqlite", "plume-macro/sqlite" ]
|
||||||
search-lindera = ["lindera-tantivy"]
|
search-lindera = ["lindera-tantivy"]
|
||||||
s3 = ["rust-s3"]
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use rocket::{
|
|||||||
Outcome,
|
Outcome,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Wrapper around User to use as a request guard on pages exclusively reserved to admins.
|
/// Wrapper around User to use as a request guard on pages reserved to admins.
|
||||||
pub struct Admin(pub User);
|
pub struct Admin(pub User);
|
||||||
|
|
||||||
impl<'a, 'r> FromRequest<'a, 'r> for Admin {
|
impl<'a, 'r> FromRequest<'a, 'r> for Admin {
|
||||||
@@ -21,23 +21,6 @@ impl<'a, 'r> FromRequest<'a, 'r> for Admin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Same as `Admin` but it forwards to next guard if the user is not an admin.
|
|
||||||
/// It's useful when there are multiple implementations of routes for admin and moderator.
|
|
||||||
pub struct InclusiveAdmin(pub User);
|
|
||||||
|
|
||||||
impl<'a, 'r> FromRequest<'a, 'r> for InclusiveAdmin {
|
|
||||||
type Error = ();
|
|
||||||
|
|
||||||
fn from_request(request: &'a Request<'r>) -> request::Outcome<InclusiveAdmin, ()> {
|
|
||||||
let user = request.guard::<User>()?;
|
|
||||||
if user.is_admin() {
|
|
||||||
Outcome::Success(InclusiveAdmin(user))
|
|
||||||
} else {
|
|
||||||
Outcome::Forward(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Same as `Admin` but for moderators.
|
/// Same as `Admin` but for moderators.
|
||||||
pub struct Moderator(pub User);
|
pub struct Moderator(pub User);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use heck::ToUpperCamelCase;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
instance::*, medias::Media, posts::Post, safe_string::SafeString, schema::blogs, users::User,
|
instance::*, medias::Media, posts::Post, safe_string::SafeString, schema::blogs, users::User,
|
||||||
Connection, Error, PlumeRocket, Result, CONFIG, ITEMS_PER_PAGE,
|
Connection, Error, PlumeRocket, Result, CONFIG, ITEMS_PER_PAGE,
|
||||||
@@ -103,18 +102,9 @@ impl Blog {
|
|||||||
find_by!(blogs, find_by_ap_url, ap_url as &str);
|
find_by!(blogs, find_by_ap_url, ap_url as &str);
|
||||||
find_by!(blogs, find_by_name, actor_id as &str, instance_id as i32);
|
find_by!(blogs, find_by_name, actor_id as &str, instance_id as i32);
|
||||||
|
|
||||||
/// Remove non alphanumeric characters and CamelCase a string
|
|
||||||
pub fn slug(title: &str) -> String {
|
|
||||||
title.to_upper_camel_case()
|
|
||||||
.chars()
|
|
||||||
.filter(|c| c.is_alphanumeric())
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
pub fn slug(title: &str) -> &str {
|
pub fn slug(title: &str) -> &str {
|
||||||
title
|
title
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
pub fn get_instance(&self, conn: &Connection) -> Result<Instance> {
|
pub fn get_instance(&self, conn: &Connection) -> Result<Instance> {
|
||||||
Instance::get(conn, self.instance_id)
|
Instance::get(conn, self.instance_id)
|
||||||
|
|||||||
@@ -73,7 +73,6 @@ impl Comment {
|
|||||||
});
|
});
|
||||||
get!(comments);
|
get!(comments);
|
||||||
list_by!(comments, list_by_post, post_id as i32);
|
list_by!(comments, list_by_post, post_id as i32);
|
||||||
list_by!(comments, list_by_author, author_id as i32);
|
|
||||||
find_by!(comments, find_by_ap_url, ap_url as &str);
|
find_by!(comments, find_by_ap_url, ap_url as &str);
|
||||||
|
|
||||||
pub fn get_author(&self, conn: &Connection) -> Result<User> {
|
pub fn get_author(&self, conn: &Connection) -> Result<User> {
|
||||||
@@ -136,7 +135,7 @@ impl Comment {
|
|||||||
|id| Comment::get(conn, id).map(|comment| comment.ap_url.unwrap_or_default()),
|
|id| Comment::get(conn, id).map(|comment| comment.ap_url.unwrap_or_default()),
|
||||||
)?);
|
)?);
|
||||||
note.set_published(
|
note.set_published(
|
||||||
OffsetDateTime::from_unix_timestamp_nanos(self.creation_date.timestamp_nanos_opt().unwrap().into())
|
OffsetDateTime::from_unix_timestamp_nanos(self.creation_date.timestamp_nanos().into())
|
||||||
.expect("OffsetDateTime"),
|
.expect("OffsetDateTime"),
|
||||||
);
|
);
|
||||||
note.set_attributed_to(author.into_id().parse::<IriString>()?);
|
note.set_attributed_to(author.into_id().parse::<IriString>()?);
|
||||||
|
|||||||
+45
-66
@@ -6,8 +6,9 @@ use rocket::Config as RocketConfig;
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::env::{self, var};
|
use std::env::{self, var};
|
||||||
|
|
||||||
#[cfg(feature = "s3")]
|
use s3::{Bucket, Region};
|
||||||
use s3::{Bucket, Region, creds::Credentials};
|
use s3::creds::Credentials;
|
||||||
|
|
||||||
|
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
const DB_NAME: &str = "plume";
|
const DB_NAME: &str = "plume";
|
||||||
@@ -293,7 +294,6 @@ pub struct LdapConfig {
|
|||||||
pub tls: bool,
|
pub tls: bool,
|
||||||
pub user_name_attr: String,
|
pub user_name_attr: String,
|
||||||
pub mail_attr: String,
|
pub mail_attr: String,
|
||||||
pub user: Option<(String, String)>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_ldap_config() -> Option<LdapConfig> {
|
fn get_ldap_config() -> Option<LdapConfig> {
|
||||||
@@ -305,26 +305,16 @@ fn get_ldap_config() -> Option<LdapConfig> {
|
|||||||
let tls = string_to_bool(&tls, "LDAP_TLS");
|
let tls = string_to_bool(&tls, "LDAP_TLS");
|
||||||
let user_name_attr = var("LDAP_USER_NAME_ATTR").unwrap_or_else(|_| "cn".to_owned());
|
let user_name_attr = var("LDAP_USER_NAME_ATTR").unwrap_or_else(|_| "cn".to_owned());
|
||||||
let mail_attr = var("LDAP_USER_MAIL_ATTR").unwrap_or_else(|_| "mail".to_owned());
|
let mail_attr = var("LDAP_USER_MAIL_ATTR").unwrap_or_else(|_| "mail".to_owned());
|
||||||
//2023-12-30
|
|
||||||
let user = var("LDAP_USER").ok();
|
|
||||||
let password = var("LDAP_PASSWORD").ok();
|
|
||||||
let user = match (user, password) {
|
|
||||||
(Some(user), Some(password)) => Some((user, password)),
|
|
||||||
(None, None) => None,
|
|
||||||
_ => panic!("Invalid LDAP configuration both or neither of LDAP_USER and LDAP_PASSWORD must be set")
|
|
||||||
};
|
|
||||||
//
|
|
||||||
Some(LdapConfig {
|
Some(LdapConfig {
|
||||||
addr,
|
addr,
|
||||||
base_dn,
|
base_dn,
|
||||||
tls,
|
tls,
|
||||||
user_name_attr,
|
user_name_attr,
|
||||||
mail_attr,
|
mail_attr,
|
||||||
user,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
(None, None) => None,
|
(None, None) => None,
|
||||||
_ => {
|
(_, _) => {
|
||||||
panic!("Invalid LDAP configuration : both LDAP_ADDR and LDAP_BASE_DN must be set")
|
panic!("Invalid LDAP configuration : both LDAP_ADDR and LDAP_BASE_DN must be set")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -380,18 +370,18 @@ pub struct S3Config {
|
|||||||
pub hostname: String,
|
pub hostname: String,
|
||||||
// may be useful when using self hosted s3. Won't work with recent AWS buckets
|
// may be useful when using self hosted s3. Won't work with recent AWS buckets
|
||||||
pub path_style: bool,
|
pub path_style: bool,
|
||||||
// http or https
|
|
||||||
pub protocol: String,
|
pub protocol: String,
|
||||||
|
|
||||||
|
// options below this comment are not used yet
|
||||||
|
// upload directly from user to S3, without going through Plume. Uses PostObject endpoint
|
||||||
|
pub direct_upload: bool,
|
||||||
// download directly from s3 to user, wihout going through Plume. Require public read on bucket
|
// download directly from s3 to user, wihout going through Plume. Require public read on bucket
|
||||||
pub direct_download: bool,
|
pub direct_download: bool,
|
||||||
// use this hostname for downloads, can be used with caching proxy in front of s3 (expected to
|
// use this hostname for downloads, can be used with caching proxy in front of s3
|
||||||
// be reachable through https)
|
|
||||||
pub alias: Option<String>,
|
pub alias: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl S3Config {
|
impl S3Config {
|
||||||
#[cfg(feature = "s3")]
|
|
||||||
pub fn get_bucket(&self) -> Bucket {
|
pub fn get_bucket(&self) -> Bucket {
|
||||||
let region = Region::Custom {
|
let region = Region::Custom {
|
||||||
region: self.region.clone(),
|
region: self.region.clone(),
|
||||||
@@ -402,15 +392,13 @@ impl S3Config {
|
|||||||
secret_key: Some(self.access_key_secret.clone()),
|
secret_key: Some(self.access_key_secret.clone()),
|
||||||
security_token: None,
|
security_token: None,
|
||||||
session_token: None,
|
session_token: None,
|
||||||
expiration: None,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let bucket = Bucket::new(&self.bucket, region, credentials).unwrap();
|
|
||||||
if self.path_style {
|
if self.path_style {
|
||||||
bucket.with_path_style()
|
Bucket::new_with_path_style(&self.bucket, region, credentials)
|
||||||
} else {
|
} else {
|
||||||
bucket
|
Bucket::new(&self.bucket, region, credentials)
|
||||||
}
|
}.unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,50 +409,41 @@ fn get_s3_config() -> Option<S3Config> {
|
|||||||
if bucket.is_none() && access_key_id.is_none() && access_key_secret.is_none() {
|
if bucket.is_none() && access_key_id.is_none() && access_key_secret.is_none() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
if bucket.is_none() || access_key_id.is_none() || access_key_secret.is_none() {
|
||||||
#[cfg(not(feature = "s3"))]
|
panic!("Invalid S3 configuration: some required values are set, but not others");
|
||||||
panic!("S3 support is not enabled in this build");
|
|
||||||
|
|
||||||
#[cfg(feature = "s3")]
|
|
||||||
{
|
|
||||||
if bucket.is_none() || access_key_id.is_none() || access_key_secret.is_none() {
|
|
||||||
panic!("Invalid S3 configuration: some required values are set, but not others");
|
|
||||||
}
|
|
||||||
let bucket = bucket.unwrap();
|
|
||||||
let access_key_id = access_key_id.unwrap();
|
|
||||||
let access_key_secret = access_key_secret.unwrap();
|
|
||||||
|
|
||||||
let region = var("S3_REGION").unwrap_or_else(|_| "us-east-1".to_owned());
|
|
||||||
let hostname = var("S3_HOSTNAME").unwrap_or_else(|_| format!("{}.amazonaws.com", region));
|
|
||||||
|
|
||||||
let protocol = var("S3_PROTOCOL").unwrap_or_else(|_| "https".to_owned());
|
|
||||||
if protocol != "http" && protocol != "https" {
|
|
||||||
panic!("Invalid S3 configuration: invalid protocol {}", protocol);
|
|
||||||
}
|
|
||||||
|
|
||||||
let path_style = var("S3_PATH_STYLE").unwrap_or_else(|_| "false".to_owned());
|
|
||||||
let path_style = string_to_bool(&path_style, "S3_PATH_STYLE");
|
|
||||||
let direct_download = var("S3_DIRECT_DOWNLOAD").unwrap_or_else(|_| "false".to_owned());
|
|
||||||
let direct_download = string_to_bool(&direct_download, "S3_DIRECT_DOWNLOAD");
|
|
||||||
|
|
||||||
let alias = var("S3_ALIAS_HOST").ok();
|
|
||||||
|
|
||||||
if direct_download && protocol == "http" && alias.is_none() {
|
|
||||||
panic!("S3 direct download is disabled because bucket is accessed through plain HTTP. Use HTTPS or set an alias hostname (S3_ALIAS_HOST).");
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(S3Config {
|
|
||||||
bucket,
|
|
||||||
access_key_id,
|
|
||||||
access_key_secret,
|
|
||||||
region,
|
|
||||||
hostname,
|
|
||||||
protocol,
|
|
||||||
path_style,
|
|
||||||
direct_download,
|
|
||||||
alias,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
let bucket = bucket.unwrap();
|
||||||
|
let access_key_id = access_key_id.unwrap();
|
||||||
|
let access_key_secret = access_key_secret.unwrap();
|
||||||
|
|
||||||
|
let region = var("S3_REGION").unwrap_or_else(|_| "us-east-1".to_owned());
|
||||||
|
let hostname = var("S3_HOSTNAME").unwrap_or_else(|_| format!("{}.amazonaws.com", region));
|
||||||
|
|
||||||
|
let protocol = var("S3_PROTOCOL").unwrap_or_else(|_| "https".to_owned());
|
||||||
|
if protocol != "http" && protocol != "https" {
|
||||||
|
panic!("Invalid S3 configuration: invalid protocol {}", protocol);
|
||||||
|
}
|
||||||
|
|
||||||
|
let path_style = var("S3_PATH_STYLE").unwrap_or_else(|_| "false".to_owned());
|
||||||
|
let path_style = string_to_bool(&path_style, "S3_PATH_STYLE");
|
||||||
|
let direct_upload = var("S3_DIRECT_UPLOAD").unwrap_or_else(|_| "false".to_owned());
|
||||||
|
let direct_upload = string_to_bool(&direct_upload, "S3_DIRECT_UPLOAD");
|
||||||
|
let direct_download = var("S3_DIRECT_DOWNLOAD").unwrap_or_else(|_| "false".to_owned());
|
||||||
|
let direct_download = string_to_bool(&direct_download, "S3_DIRECT_DOWNLOAD");
|
||||||
|
|
||||||
|
let alias = var("S3_ALIAS_HOST").ok();
|
||||||
|
Some(S3Config {
|
||||||
|
bucket,
|
||||||
|
access_key_id,
|
||||||
|
access_key_secret,
|
||||||
|
region,
|
||||||
|
hostname,
|
||||||
|
protocol,
|
||||||
|
path_style,
|
||||||
|
direct_upload,
|
||||||
|
direct_download,
|
||||||
|
alias,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ use activitystreams::activity::{Announce, Create, Delete, Follow, Like, Undo, Up
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
comments::Comment,
|
comments::Comment,
|
||||||
|
db_conn::DbConn,
|
||||||
follows, likes,
|
follows, likes,
|
||||||
posts::{Post, PostUpdate},
|
posts::{Post, PostUpdate},
|
||||||
reshares::Reshare,
|
reshares::Reshare,
|
||||||
users::User,
|
users::User,
|
||||||
Connection, Error, CONFIG,
|
Error, CONFIG,
|
||||||
};
|
};
|
||||||
use plume_common::activity_pub::inbox::Inbox;
|
use plume_common::activity_pub::inbox::Inbox;
|
||||||
|
|
||||||
@@ -45,8 +46,8 @@ impl_into_inbox_result! {
|
|||||||
Reshare => Reshared
|
Reshare => Reshared
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn inbox(conn: &Connection, act: serde_json::Value) -> Result<InboxResult, Error> {
|
pub fn inbox(conn: &DbConn, act: serde_json::Value) -> Result<InboxResult, Error> {
|
||||||
Inbox::handle(conn, act)
|
Inbox::handle(&**conn, act)
|
||||||
.with::<User, Announce, Post>(CONFIG.proxy())
|
.with::<User, Announce, Post>(CONFIG.proxy())
|
||||||
.with::<User, Create, Comment>(CONFIG.proxy())
|
.with::<User, Create, Comment>(CONFIG.proxy())
|
||||||
.with::<User, Create, Post>(CONFIG.proxy())
|
.with::<User, Create, Post>(CONFIG.proxy())
|
||||||
|
|||||||
@@ -69,8 +69,7 @@ pub enum Error {
|
|||||||
Webfinger,
|
Webfinger,
|
||||||
Expired,
|
Expired,
|
||||||
UserAlreadyExists,
|
UserAlreadyExists,
|
||||||
#[cfg(feature = "s3")]
|
Anyhow(anyhow::Error),
|
||||||
S3(s3::error::S3Error),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<bcrypt::BcryptError> for Error {
|
impl From<bcrypt::BcryptError> for Error {
|
||||||
@@ -172,10 +171,9 @@ impl From<request::Error> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "s3")]
|
impl From<anyhow::Error> for Error {
|
||||||
impl From<s3::error::S3Error> for Error {
|
fn from(err: anyhow::Error) -> Error {
|
||||||
fn from(err: s3::error::S3Error) -> Error {
|
Error::Anyhow(err)
|
||||||
Error::S3(err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+36
-178
@@ -16,9 +16,6 @@ use std::{
|
|||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
#[cfg(feature = "s3")]
|
|
||||||
use crate::config::S3Config;
|
|
||||||
|
|
||||||
const REMOTE_MEDIA_DIRECTORY: &str = "remote";
|
const REMOTE_MEDIA_DIRECTORY: &str = "remote";
|
||||||
|
|
||||||
#[derive(Clone, Identifiable, Queryable, AsChangeset)]
|
#[derive(Clone, Identifiable, Queryable, AsChangeset)]
|
||||||
@@ -108,7 +105,7 @@ impl Media {
|
|||||||
.file_path
|
.file_path
|
||||||
.rsplit_once('.')
|
.rsplit_once('.')
|
||||||
.map(|x| x.1)
|
.map(|x| x.1)
|
||||||
.unwrap_or("")
|
.expect("Media::category: extension error")
|
||||||
.to_lowercase()
|
.to_lowercase()
|
||||||
{
|
{
|
||||||
"png" | "jpg" | "jpeg" | "gif" | "svg" => MediaCategory::Image,
|
"png" | "jpg" | "jpeg" | "gif" | "svg" => MediaCategory::Image,
|
||||||
@@ -154,98 +151,30 @@ impl Media {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns full file path for medias stored in the local media directory.
|
|
||||||
pub fn local_path(&self) -> Option<PathBuf> {
|
|
||||||
if self.file_path.is_empty() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
if CONFIG.s3.is_some() {
|
|
||||||
#[cfg(feature="s3")]
|
|
||||||
unreachable!("Called Media::local_path() but media are stored on S3");
|
|
||||||
#[cfg(not(feature="s3"))]
|
|
||||||
unreachable!();
|
|
||||||
}
|
|
||||||
|
|
||||||
let relative_path = self
|
|
||||||
.file_path
|
|
||||||
.trim_start_matches(&CONFIG.media_directory)
|
|
||||||
.trim_start_matches(path::MAIN_SEPARATOR)
|
|
||||||
.trim_start_matches("static/media/");
|
|
||||||
|
|
||||||
Some(Path::new(&CONFIG.media_directory).join(relative_path))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the relative URL to access this file, which is also the key at which
|
|
||||||
/// it is stored in the S3 bucket if we are using S3 storage.
|
|
||||||
/// Does not start with a '/', it is of the form "static/media/<...>"
|
|
||||||
pub fn relative_url(&self) -> Option<String> {
|
|
||||||
if self.file_path.is_empty() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let relative_path = self
|
|
||||||
.file_path
|
|
||||||
.trim_start_matches(&CONFIG.media_directory)
|
|
||||||
.replace(path::MAIN_SEPARATOR, "/");
|
|
||||||
|
|
||||||
let relative_path = relative_path
|
|
||||||
.trim_start_matches('/')
|
|
||||||
.trim_start_matches("static/media/");
|
|
||||||
|
|
||||||
Some(format!("static/media/{}", relative_path))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a public URL through which this media file can be accessed
|
|
||||||
pub fn url(&self) -> Result<String> {
|
pub fn url(&self) -> Result<String> {
|
||||||
if self.is_remote {
|
if self.is_remote {
|
||||||
Ok(self.remote_url.clone().unwrap_or_default())
|
Ok(self.remote_url.clone().unwrap_or_default())
|
||||||
} else {
|
} else {
|
||||||
let relative_url = self.relative_url().unwrap_or_default();
|
let file_path = self.file_path.replace(path::MAIN_SEPARATOR, "/").replacen(
|
||||||
|
&CONFIG.media_directory,
|
||||||
#[cfg(feature="s3")]
|
"static/media",
|
||||||
if CONFIG.s3.as_ref().map(|x| x.direct_download).unwrap_or(false) {
|
1,
|
||||||
let s3_url = match CONFIG.s3.as_ref().unwrap() {
|
); // "static/media" from plume::routs::plume_media_files()
|
||||||
S3Config { alias: Some(alias), .. } => {
|
|
||||||
format!("https://{}/{}", alias, relative_url)
|
|
||||||
}
|
|
||||||
S3Config { path_style: true, hostname, bucket, .. } => {
|
|
||||||
format!("https://{}/{}/{}",
|
|
||||||
hostname,
|
|
||||||
bucket,
|
|
||||||
relative_url
|
|
||||||
)
|
|
||||||
}
|
|
||||||
S3Config { path_style: false, hostname, bucket, .. } => {
|
|
||||||
format!("https://{}.{}/{}",
|
|
||||||
bucket,
|
|
||||||
hostname,
|
|
||||||
relative_url
|
|
||||||
)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return Ok(s3_url);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(ap_url(&format!(
|
Ok(ap_url(&format!(
|
||||||
"{}/{}",
|
"{}/{}",
|
||||||
Instance::get_local()?.public_domain,
|
Instance::get_local()?.public_domain,
|
||||||
relative_url
|
&file_path
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(&self, conn: &Connection) -> Result<()> {
|
pub fn delete(&self, conn: &Connection) -> Result<()> {
|
||||||
if !self.is_remote {
|
if !self.is_remote {
|
||||||
if CONFIG.s3.is_some() {
|
if let Some(config) = &CONFIG.s3 {
|
||||||
#[cfg(not(feature="s3"))]
|
config.get_bucket()
|
||||||
unreachable!();
|
.delete_object_blocking(&self.file_path)?;
|
||||||
|
|
||||||
#[cfg(feature = "s3")]
|
|
||||||
CONFIG.s3.as_ref().unwrap().get_bucket()
|
|
||||||
.delete_object_blocking(&self.relative_url().ok_or(Error::NotFound)?)?;
|
|
||||||
} else {
|
} else {
|
||||||
fs::remove_file(self.local_path().ok_or(Error::NotFound)?)?;
|
fs::remove_file(self.file_path.as_str())?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
diesel::delete(self)
|
diesel::delete(self)
|
||||||
@@ -287,60 +216,22 @@ impl Media {
|
|||||||
.url()
|
.url()
|
||||||
.and_then(|url| url.to_as_uri())
|
.and_then(|url| url.to_as_uri())
|
||||||
.ok_or(Error::MissingApProperty)?;
|
.ok_or(Error::MissingApProperty)?;
|
||||||
|
let path = determine_mirror_file_path(&remote_url);
|
||||||
|
let parent = path.parent().ok_or(Error::InvalidValue)?;
|
||||||
|
if !parent.is_dir() {
|
||||||
|
DirBuilder::new().recursive(true).create(parent)?;
|
||||||
|
}
|
||||||
|
|
||||||
let file_path = if CONFIG.s3.is_some() {
|
let mut dest = fs::File::create(path.clone())?;
|
||||||
#[cfg(not(feature="s3"))]
|
// TODO: conditional GET
|
||||||
unreachable!();
|
request::get(
|
||||||
|
remote_url.as_str(),
|
||||||
|
User::get_sender(),
|
||||||
|
CONFIG.proxy().cloned(),
|
||||||
|
)?
|
||||||
|
.copy_to(&mut dest)?;
|
||||||
|
|
||||||
#[cfg(feature = "s3")]
|
Media::find_by_file_path(conn, path.to_str().ok_or(Error::InvalidValue)?)
|
||||||
{
|
|
||||||
use rocket::http::ContentType;
|
|
||||||
|
|
||||||
let dest = determine_mirror_s3_path(&remote_url);
|
|
||||||
|
|
||||||
let media = request::get(
|
|
||||||
remote_url.as_str(),
|
|
||||||
User::get_sender(),
|
|
||||||
CONFIG.proxy().cloned(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let content_type = media
|
|
||||||
.headers()
|
|
||||||
.get(reqwest::header::CONTENT_TYPE)
|
|
||||||
.and_then(|x| x.to_str().ok())
|
|
||||||
.and_then(ContentType::parse_flexible)
|
|
||||||
.unwrap_or(ContentType::Binary);
|
|
||||||
|
|
||||||
let bytes = media.bytes()?;
|
|
||||||
|
|
||||||
let bucket = CONFIG.s3.as_ref().unwrap().get_bucket();
|
|
||||||
bucket.put_object_with_content_type_blocking(
|
|
||||||
&dest,
|
|
||||||
&bytes,
|
|
||||||
&content_type.to_string()
|
|
||||||
)?;
|
|
||||||
|
|
||||||
dest
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let path = determine_mirror_file_path(&remote_url);
|
|
||||||
let parent = path.parent().ok_or(Error::InvalidValue)?;
|
|
||||||
if !parent.is_dir() {
|
|
||||||
DirBuilder::new().recursive(true).create(parent)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut dest = fs::File::create(path.clone())?;
|
|
||||||
// TODO: conditional GET
|
|
||||||
request::get(
|
|
||||||
remote_url.as_str(),
|
|
||||||
User::get_sender(),
|
|
||||||
CONFIG.proxy().cloned(),
|
|
||||||
)?
|
|
||||||
.copy_to(&mut dest)?;
|
|
||||||
path.to_str().ok_or(Error::InvalidValue)?.to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
Media::find_by_file_path(conn, &file_path)
|
|
||||||
.and_then(|mut media| {
|
.and_then(|mut media| {
|
||||||
let mut updated = false;
|
let mut updated = false;
|
||||||
|
|
||||||
@@ -381,7 +272,7 @@ impl Media {
|
|||||||
Media::insert(
|
Media::insert(
|
||||||
conn,
|
conn,
|
||||||
NewMedia {
|
NewMedia {
|
||||||
file_path,
|
file_path: path.to_str().ok_or(Error::InvalidValue)?.to_string(),
|
||||||
alt_text: image
|
alt_text: image
|
||||||
.content()
|
.content()
|
||||||
.and_then(|content| content.to_as_string())
|
.and_then(|content| content.to_as_string())
|
||||||
@@ -421,10 +312,12 @@ impl Media {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn determine_mirror_file_path(url: &str) -> PathBuf {
|
fn determine_mirror_file_path(url: &str) -> PathBuf {
|
||||||
let mut file_path = Path::new(&CONFIG.media_directory).join(REMOTE_MEDIA_DIRECTORY);
|
let mut file_path = Path::new(&super::CONFIG.media_directory).join(REMOTE_MEDIA_DIRECTORY);
|
||||||
|
Url::parse(url)
|
||||||
match Url::parse(url) {
|
.map(|url| {
|
||||||
Ok(url) if url.has_host() => {
|
if !url.has_host() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
file_path.push(url.host_str().unwrap());
|
file_path.push(url.host_str().unwrap());
|
||||||
for segment in url.path_segments().expect("FIXME") {
|
for segment in url.path_segments().expect("FIXME") {
|
||||||
file_path.push(segment);
|
file_path.push(segment);
|
||||||
@@ -432,54 +325,19 @@ fn determine_mirror_file_path(url: &str) -> PathBuf {
|
|||||||
// TODO: handle query
|
// TODO: handle query
|
||||||
// HINT: Use characters which must be percent-encoded in path as separator between path and query
|
// HINT: Use characters which must be percent-encoded in path as separator between path and query
|
||||||
// HINT: handle extension
|
// HINT: handle extension
|
||||||
}
|
})
|
||||||
other => {
|
.unwrap_or_else(|err| {
|
||||||
if let Err(err) = other {
|
warn!("Failed to parse url: {} {}", &url, err);
|
||||||
warn!("Failed to parse url: {} {}", &url, err);
|
|
||||||
} else {
|
|
||||||
warn!("Error without a host: {}", &url);
|
|
||||||
}
|
|
||||||
let ext = url
|
let ext = url
|
||||||
.rsplit('.')
|
.rsplit('.')
|
||||||
.next()
|
.next()
|
||||||
.map(ToOwned::to_owned)
|
.map(ToOwned::to_owned)
|
||||||
.unwrap_or_else(|| String::from("png"));
|
.unwrap_or_else(|| String::from("png"));
|
||||||
file_path.push(format!("{}.{}", GUID::rand(), ext));
|
file_path.push(format!("{}.{}", GUID::rand(), ext));
|
||||||
}
|
});
|
||||||
}
|
|
||||||
file_path
|
file_path
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature="s3")]
|
|
||||||
fn determine_mirror_s3_path(url: &str) -> String {
|
|
||||||
match Url::parse(url) {
|
|
||||||
Ok(url) if url.has_host() => {
|
|
||||||
format!("static/media/{}/{}/{}",
|
|
||||||
REMOTE_MEDIA_DIRECTORY,
|
|
||||||
url.host_str().unwrap(),
|
|
||||||
url.path().trim_start_matches('/'),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
other => {
|
|
||||||
if let Err(err) = other {
|
|
||||||
warn!("Failed to parse url: {} {}", &url, err);
|
|
||||||
} else {
|
|
||||||
warn!("Error without a host: {}", &url);
|
|
||||||
}
|
|
||||||
let ext = url
|
|
||||||
.rsplit('.')
|
|
||||||
.next()
|
|
||||||
.map(ToOwned::to_owned)
|
|
||||||
.unwrap_or_else(|| String::from("png"));
|
|
||||||
format!("static/media/{}/{}.{}",
|
|
||||||
REMOTE_MEDIA_DIRECTORY,
|
|
||||||
GUID::rand(),
|
|
||||||
ext,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod tests {
|
pub(crate) mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -373,7 +373,7 @@ impl Post {
|
|||||||
}))?;
|
}))?;
|
||||||
article.set_source(source);
|
article.set_source(source);
|
||||||
article.set_published(
|
article.set_published(
|
||||||
OffsetDateTime::from_unix_timestamp_nanos(self.creation_date.timestamp_nanos_opt().unwrap().into())
|
OffsetDateTime::from_unix_timestamp_nanos(self.creation_date.timestamp_nanos().into())
|
||||||
.expect("OffsetDateTime"),
|
.expect("OffsetDateTime"),
|
||||||
);
|
);
|
||||||
article.set_summary(&*self.subtitle);
|
article.set_summary(&*self.subtitle);
|
||||||
|
|||||||
@@ -221,8 +221,7 @@ impl Timeline {
|
|||||||
|
|
||||||
pub fn add_to_all_timelines(conn: &Connection, post: &Post, kind: Kind<'_>) -> Result<()> {
|
pub fn add_to_all_timelines(conn: &Connection, post: &Post, kind: Kind<'_>) -> Result<()> {
|
||||||
let timelines = timeline_definition::table
|
let timelines = timeline_definition::table
|
||||||
//.load::<Self>(conn.deref())
|
.load::<Self>(conn.deref())
|
||||||
.load::<Self>(conn)
|
|
||||||
.map_err(Error::from)?;
|
.map_err(Error::from)?;
|
||||||
|
|
||||||
for t in timelines {
|
for t in timelines {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
ap_url, blocklisted_emails::BlocklistedEmail, blogs::Blog, comments::Comment, db_conn::DbConn,
|
ap_url, blocklisted_emails::BlocklistedEmail, blogs::Blog, db_conn::DbConn, follows::Follow,
|
||||||
follows::Follow, instance::*, medias::Media, notifications::Notification,
|
instance::*, medias::Media, notifications::Notification, post_authors::PostAuthor, posts::Post,
|
||||||
post_authors::PostAuthor, posts::Post, safe_string::SafeString, schema::users,
|
safe_string::SafeString, schema::users, timeline::Timeline, Connection, Error, Result,
|
||||||
timeline::Timeline, Connection, Error, Result, UserEvent::*, CONFIG, ITEMS_PER_PAGE, USER_CHAN,
|
UserEvent::*, CONFIG, ITEMS_PER_PAGE, USER_CHAN,
|
||||||
};
|
};
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
activity::Delete,
|
activity::Delete,
|
||||||
@@ -168,14 +168,6 @@ impl User {
|
|||||||
notif.delete(conn)?
|
notif.delete(conn)?
|
||||||
}
|
}
|
||||||
|
|
||||||
for comment in Comment::list_by_author(conn, self.id)? {
|
|
||||||
let delete_activity = comment.build_delete(conn)?;
|
|
||||||
crate::inbox::inbox(
|
|
||||||
conn,
|
|
||||||
serde_json::to_value(&delete_activity).map_err(Error::from)?,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
diesel::delete(self)
|
diesel::delete(self)
|
||||||
.execute(conn)
|
.execute(conn)
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
@@ -342,42 +334,6 @@ impl User {
|
|||||||
bcrypt::hash(pass, 10).map_err(Error::from)
|
bcrypt::hash(pass, 10).map_err(Error::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
// [[ LDAP non anonymous bind copied from ....
|
|
||||||
fn ldap_preconn(ldap_conn: &mut LdapConn) -> Result<()> {
|
|
||||||
let ldap = CONFIG.ldap.as_ref().unwrap();
|
|
||||||
|
|
||||||
if let Some((user, password)) = ldap.user.as_ref() {
|
|
||||||
let bind = ldap_conn
|
|
||||||
.simple_bind(user, password)
|
|
||||||
.map_err(|_| Error::NotFound)?;
|
|
||||||
|
|
||||||
if bind.success().is_err() {
|
|
||||||
return Err(Error::NotFound);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_dn(ldap_conn: &mut LdapConn, fil: &str) -> Result<String> {
|
|
||||||
let mut cn: String = String::new();
|
|
||||||
let ldap = CONFIG.ldap.as_ref().unwrap();
|
|
||||||
let (rs, _res) = ldap_conn.search(
|
|
||||||
&ldap.base_dn,
|
|
||||||
Scope::Subtree,
|
|
||||||
fil,
|
|
||||||
vec!["cn"]
|
|
||||||
)
|
|
||||||
.map_err(|_| Error::NotFound)?
|
|
||||||
.success()
|
|
||||||
.map_err(|_| Error::NotFound)?;
|
|
||||||
for entry in rs {
|
|
||||||
println!("{:?}", SearchEntry::construct(entry.clone()).dn);
|
|
||||||
cn = SearchEntry::construct(entry).dn;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(cn.to_owned())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ldap_register(conn: &Connection, name: &str, password: &str) -> Result<User> {
|
fn ldap_register(conn: &Connection, name: &str, password: &str) -> Result<User> {
|
||||||
if CONFIG.ldap.is_none() {
|
if CONFIG.ldap.is_none() {
|
||||||
return Err(Error::NotFound);
|
return Err(Error::NotFound);
|
||||||
@@ -385,12 +341,7 @@ impl User {
|
|||||||
let ldap = CONFIG.ldap.as_ref().unwrap();
|
let ldap = CONFIG.ldap.as_ref().unwrap();
|
||||||
|
|
||||||
let mut ldap_conn = LdapConn::new(&ldap.addr).map_err(|_| Error::NotFound)?;
|
let mut ldap_conn = LdapConn::new(&ldap.addr).map_err(|_| Error::NotFound)?;
|
||||||
|
let ldap_name = format!("{}={},{}", ldap.user_name_attr, name, ldap.base_dn);
|
||||||
User::ldap_preconn(&mut ldap_conn)?;
|
|
||||||
|
|
||||||
let _filter = format!("(&(objectClass=*)(uid={}))", name).to_owned();
|
|
||||||
let ldap_name = User::search_dn(&mut ldap_conn, &_filter).unwrap();
|
|
||||||
//let ldap_name = format!("{}={},{}", ldap.user_name_attr, name, ldap.base_dn);
|
|
||||||
let bind = ldap_conn
|
let bind = ldap_conn
|
||||||
.simple_bind(&ldap_name, password)
|
.simple_bind(&ldap_name, password)
|
||||||
.map_err(|_| Error::NotFound)?;
|
.map_err(|_| Error::NotFound)?;
|
||||||
@@ -436,18 +387,10 @@ impl User {
|
|||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
if User::ldap_preconn(&mut conn).is_err() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
//
|
|
||||||
let _filter = format!("(&(objectClass=*)(uid={}))", &self.username).to_owned();
|
|
||||||
let name = User::search_dn(&mut conn, &_filter).unwrap();
|
|
||||||
/*
|
|
||||||
let name = format!(
|
let name = format!(
|
||||||
"{}={},{}",
|
"{}={},{}",
|
||||||
ldap.user_name_attr, &self.username, ldap.base_dn
|
ldap.user_name_attr, &self.username, ldap.base_dn
|
||||||
);
|
);
|
||||||
*/
|
|
||||||
if let Ok(bind) = conn.simple_bind(&name, password) {
|
if let Ok(bind) = conn.simple_bind(&name, password) {
|
||||||
bind.success().is_ok()
|
bind.success().is_ok()
|
||||||
} else {
|
} else {
|
||||||
@@ -494,7 +437,7 @@ impl User {
|
|||||||
}
|
}
|
||||||
// if no user was found, and we were unable to auto-register from ldap
|
// if no user was found, and we were unable to auto-register from ldap
|
||||||
// fake-verify a password, and return an error.
|
// fake-verify a password, and return an error.
|
||||||
let other = User::get(&*conn, 1)
|
let other = User::get(conn, 1)
|
||||||
.expect("No user is registered")
|
.expect("No user is registered")
|
||||||
.hashed_password;
|
.hashed_password;
|
||||||
other.map(|pass| bcrypt::verify(password, &pass));
|
other.map(|pass| bcrypt::verify(password, &pass));
|
||||||
@@ -502,7 +445,7 @@ impl User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ... ldap-non-anon PR https://git.joinplu.me/Plume/Plume/src/branch/ldap-non-anon ]].
|
|
||||||
pub fn reset_password(&self, conn: &Connection, pass: &str) -> Result<()> {
|
pub fn reset_password(&self, conn: &Connection, pass: &str) -> Result<()> {
|
||||||
diesel::update(self)
|
diesel::update(self)
|
||||||
.set(users::hashed_password.eq(User::hash_pass(pass)?))
|
.set(users::hashed_password.eq(User::hash_pass(pass)?))
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
msgid ""
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
\n"
|
"Project-Id-Version: plume-front\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
@@ -12,6 +12,9 @@ msgstr ""
|
|||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
|
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
|
||||||
|
|
||||||
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
+1016
File diff suppressed because it is too large
Load Diff
+692
-613
File diff suppressed because it is too large
Load Diff
+641
-729
File diff suppressed because it is too large
Load Diff
+643
-716
File diff suppressed because it is too large
Load Diff
+662
-672
File diff suppressed because it is too large
Load Diff
+1088
File diff suppressed because it is too large
Load Diff
+1016
File diff suppressed because it is too large
Load Diff
+632
-740
File diff suppressed because it is too large
Load Diff
+1016
File diff suppressed because it is too large
Load Diff
+1016
File diff suppressed because it is too large
Load Diff
+860
-259
File diff suppressed because it is too large
Load Diff
+632
-739
File diff suppressed because it is too large
Load Diff
+632
-730
File diff suppressed because it is too large
Load Diff
+632
-732
File diff suppressed because it is too large
Load Diff
+918
-129
File diff suppressed because it is too large
Load Diff
+632
-741
File diff suppressed because it is too large
Load Diff
+632
-730
File diff suppressed because it is too large
Load Diff
+1023
-2
File diff suppressed because it is too large
Load Diff
+829
-333
File diff suppressed because it is too large
Load Diff
+970
-85
File diff suppressed because it is too large
Load Diff
+1016
File diff suppressed because it is too large
Load Diff
+643
-720
File diff suppressed because it is too large
Load Diff
+631
-721
File diff suppressed because it is too large
Load Diff
+1013
File diff suppressed because it is too large
Load Diff
+1009
-421
File diff suppressed because it is too large
Load Diff
+641
-717
File diff suppressed because it is too large
Load Diff
+744
-500
File diff suppressed because it is too large
Load Diff
+651
-727
File diff suppressed because it is too large
Load Diff
@@ -11,3 +11,945 @@ msgstr ""
|
|||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
|
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
|
||||||
|
|
||||||
|
msgid "Someone"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "{0} commented on your article."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "{0} is subscribed to you."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "{0} liked your article."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "{0} mentioned you."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "{0} boosted your article."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Your feed"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Local feed"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Federated feed"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "{0}'s avatar"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Previous page"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Next page"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Optional"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "To create a new blog, you need to be logged in"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "A blog with the same name already exists."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Your blog was successfully created!"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Your blog was deleted."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "You are not allowed to delete this blog."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "You are not allowed to edit this blog."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "You can't use this media as a blog icon."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "You can't use this media as a blog banner."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Your blog information have been updated."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Your comment has been posted."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Your comment has been deleted."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Registrations are closed on this instance."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "User registration"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Here is the link for registration: {0}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Your account has been created. Now you just need to log in, before you can use it."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Instance settings have been saved."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "{} has been unblocked."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "{} has been blocked."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Blocks deleted"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Email already blocked"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Email Blocked"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "You can't change your own rights."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "You are not allowed to take this action."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Done."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "To like a post, you need to be logged in"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Your media have been deleted."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "You are not allowed to delete this media."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Your avatar has been updated."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "You are not allowed to use this media."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "To see your notifications, you need to be logged in"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "This post isn't published yet."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "To write a new post, you need to be logged in"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "You are not an author of this blog."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "New post"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Edit {0}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "You are not allowed to publish on this blog."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Your article has been updated."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Your article has been saved."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "New article"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "You are not allowed to delete this article."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Your article has been deleted."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "It looks like the article you tried to delete doesn't exist. Maybe it is already gone?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Couldn't obtain enough information about your account. Please make sure your username is correct."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "To reshare a post, you need to be logged in"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "You are now connected."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "You are now logged off."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Password reset"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Here is the link to reset your password: {0}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Your password was successfully reset."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "To access your dashboard, you need to be logged in"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "You are no longer following {}."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "You are now following {}."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "To subscribe to someone, you need to be logged in"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "To edit your profile, you need to be logged in"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Your profile has been updated."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Your account has been deleted."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "You can't delete someone else's account."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Create your account"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Create an account"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Email"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Email confirmation"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "An email will be sent to provided email. You can continue signing-up via the email."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Apologies, but registrations are closed on this particular instance. You can, however, find a different one."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Registration"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Check your inbox!"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "We sent a mail to the address you gave us, with a link for registration."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Username"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Password"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Password confirmation"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Media upload"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Description"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Useful for visually impaired people, as well as licensing information"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Content warning"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Leave it empty, if none is needed"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "File"
|
||||||
|
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 "Delete"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Details"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Media details"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Go back to the gallery"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Markdown syntax"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Copy it into your articles, to insert this media:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Use as an avatar"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Plume"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Menu"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Search"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Dashboard"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Notifications"
|
||||||
|
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 "Admin"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "It is you"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Edit your profile"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Open on {0}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Unsubscribe"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Subscribe"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Follow {}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Log in to follow"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Enter your full username handle to follow"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "{0}'s subscribers"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Articles"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Subscribers"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Subscriptions"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "{0}'s subscriptions"
|
||||||
|
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 "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 ""
|
||||||
|
|
||||||
|
msgid "Display name"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Summary"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Theme"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Default theme"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Error while loading theme selector."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Never load blogs custom themes"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Update account"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Danger zone"
|
||||||
|
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 "Latest articles"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Atom feed"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Recently boosted"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Articles tagged \"{0}\""
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "There are currently no articles with such a tag"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "The content you sent can't be processed."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Maybe it was too long."
|
||||||
|
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 "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 "You are not authorized."
|
||||||
|
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 "Users"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Configuration"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Instances"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Email blocklist"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Grant admin rights"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Revoke admin rights"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Grant moderator rights"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Revoke moderator rights"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Ban"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Run on selected users"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Moderator"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Moderation"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Home"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Administration of {0}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Unblock"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Block"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Name"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Allow anyone to register here"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Short description"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Markdown syntax is supported"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Long description"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Default article license"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Save these settings"
|
||||||
|
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 "Blocklisted Emails"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Email address"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "The email address you wish to block. In order to block domains, you can use globbing syntax, for example '*@example.com' blocks all addresses from example.com"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Note"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Notify the user?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Optional, shows a message to the user when they attempt to create an account with that address"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Blocklisting notification"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "The message to be shown when the user attempts to create an account with this email address"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Add blocklisted address"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "There are no blocked emails on your instance"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Delete selected emails"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Email address:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Blocklisted for:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Will notify them on account creation with this message:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "The user will be silently prevented from making an account"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Welcome to {}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Nothing to see here yet."
|
||||||
|
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 "Interact with {}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Log in to interact"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Enter your full username to interact"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Publish"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Classic editor (any changes will be lost)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Title"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Subtitle"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Content"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "You can upload media to your gallery, and then copy their Markdown code into your articles to insert them."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Upload media"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Tags, separated by commas"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "License"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Illustration"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "This is a draft, don't publish it yet."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Update"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Update, or publish"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Publish your post"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Written by {0}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "All rights reserved."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "This article is under the {0} license."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "One like"
|
||||||
|
msgid_plural "{0} likes"
|
||||||
|
msgstr[0] ""
|
||||||
|
|
||||||
|
msgid "I don't like this anymore"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Add yours"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "One boost"
|
||||||
|
msgid_plural "{0} boosts"
|
||||||
|
msgstr[0] ""
|
||||||
|
|
||||||
|
msgid "I don't want to boost this anymore"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Boost"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "{0}Log in{1}, or {2}use your Fediverse account{3} to interact with this article"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Comments"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Your comment"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Submit comment"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "No comments yet. Be the first to react!"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Are you sure?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
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 "Edit"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "I'm from this instance"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Username, or email"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Log in"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "I'm from another instance"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Continue to your instance"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Reset your password"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "New password"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Confirmation"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Update password"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "We sent a mail to the address you gave us, with a link to reset your password."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Send password reset link"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "This token has expired"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Please start the process again by clicking <a href=\"/password-reset\">here</a>."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "New Blog"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Create a blog"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Create blog"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Edit \"{}\""
|
||||||
|
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 "Custom theme"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Update blog"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Be very careful, any action taken here can't be reversed."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Are you sure that you want to permanently delete this blog?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Permanently delete this blog"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "{}'s icon"
|
||||||
|
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 "None"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "No description"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Respond"
|
||||||
|
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 "By {0}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Draft"
|
||||||
|
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 "Advanced search"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Article title matching these words"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Subtitle matching these words"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Content macthing these words"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Body content"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "From this date"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "To this date"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Containing these tags"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Tags"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Posted on one of these instances"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Instance domain"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Posted by one of these authors"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Author(s)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Posted on one of these blogs"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Blog title"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Written in this language"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Language"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Published under this license"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Article license"
|
||||||
|
msgstr ""
|
||||||
|
|||||||
+641
-715
File diff suppressed because it is too large
Load Diff
+941
-129
File diff suppressed because it is too large
Load Diff
+648
-739
File diff suppressed because it is too large
Load Diff
+980
-53
File diff suppressed because it is too large
Load Diff
+995
-40
File diff suppressed because it is too large
Load Diff
+639
-738
File diff suppressed because it is too large
Load Diff
+985
-65
File diff suppressed because it is too large
Load Diff
+1000
-34
File diff suppressed because it is too large
Load Diff
+970
-75
File diff suppressed because it is too large
Load Diff
+695
-612
File diff suppressed because it is too large
Load Diff
+953
-78
File diff suppressed because it is too large
Load Diff
+1013
File diff suppressed because it is too large
Load Diff
+629
-693
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -1 +1 @@
|
|||||||
nightly-2024-07-19
|
nightly-2022-07-19
|
||||||
|
|||||||
+1
-2
@@ -101,8 +101,7 @@ Then try to restart Plume.
|
|||||||
"#
|
"#
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
//let workpool = ScheduledThreadPool::with_name("worker {}", num_cpus::get());
|
let workpool = ScheduledThreadPool::with_name("worker {}", num_cpus::get());
|
||||||
let workpool = ScheduledThreadPool::new(num_cpus::get());
|
|
||||||
// we want a fast exit here, so
|
// we want a fast exit here, so
|
||||||
let searcher = Arc::new(UnmanagedSearcher::open_or_recreate(
|
let searcher = Arc::new(UnmanagedSearcher::open_or_recreate(
|
||||||
&CONFIG.search_index,
|
&CONFIG.search_index,
|
||||||
|
|||||||
+2
-2
@@ -101,7 +101,7 @@ pub fn create(
|
|||||||
Ok(_) => ValidationErrors::new(),
|
Ok(_) => ValidationErrors::new(),
|
||||||
Err(e) => e,
|
Err(e) => e,
|
||||||
};
|
};
|
||||||
if Blog::find_by_fqn(&conn, &slug).is_ok() {
|
if Blog::find_by_fqn(&conn, slug).is_ok() {
|
||||||
errors.add(
|
errors.add(
|
||||||
"title",
|
"title",
|
||||||
ValidationError {
|
ValidationError {
|
||||||
@@ -122,7 +122,7 @@ pub fn create(
|
|||||||
let blog = Blog::insert(
|
let blog = Blog::insert(
|
||||||
&conn,
|
&conn,
|
||||||
NewBlog::new_local(
|
NewBlog::new_local(
|
||||||
slug.clone().into(),
|
slug.into(),
|
||||||
form.title.to_string(),
|
form.title.to_string(),
|
||||||
String::from(""),
|
String::from(""),
|
||||||
Instance::get_local()
|
Instance::get_local()
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ pub fn index(conn: DbConn, rockets: PlumeRocket) -> Result<Ructe, ErrorPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/admin")]
|
#[get("/admin")]
|
||||||
pub fn admin(_admin: InclusiveAdmin, conn: DbConn, rockets: PlumeRocket) -> Result<Ructe, ErrorPage> {
|
pub fn admin(_admin: Admin, conn: DbConn, rockets: PlumeRocket) -> Result<Ructe, ErrorPage> {
|
||||||
let local_inst = Instance::get_local()?;
|
let local_inst = Instance::get_local()?;
|
||||||
Ok(render!(instance::admin(
|
Ok(render!(instance::admin(
|
||||||
&(&conn, &rockets).to_context(),
|
&(&conn, &rockets).to_context(),
|
||||||
|
|||||||
+34
-77
@@ -2,7 +2,7 @@ use crate::routes::{errors::ErrorPage, Page};
|
|||||||
use crate::template_utils::{IntoContext, Ructe};
|
use crate::template_utils::{IntoContext, Ructe};
|
||||||
use guid_create::GUID;
|
use guid_create::GUID;
|
||||||
use multipart::server::{
|
use multipart::server::{
|
||||||
save::{SaveResult, SavedField, SavedData},
|
save::{SaveResult, SavedData},
|
||||||
Multipart,
|
Multipart,
|
||||||
};
|
};
|
||||||
use plume_models::{db_conn::DbConn, medias::*, users::User, Error, PlumeRocket, CONFIG};
|
use plume_models::{db_conn::DbConn, medias::*, users::User, Error, PlumeRocket, CONFIG};
|
||||||
@@ -55,16 +55,41 @@ pub fn upload(
|
|||||||
if let SaveResult::Full(entries) = Multipart::with_body(data.open(), boundary).save().temp() {
|
if let SaveResult::Full(entries) = Multipart::with_body(data.open(), boundary).save().temp() {
|
||||||
let fields = entries.fields;
|
let fields = entries.fields;
|
||||||
|
|
||||||
let file = fields
|
let filename = fields
|
||||||
.get("file")
|
.get("file")
|
||||||
.and_then(|v| v.iter().next())
|
.and_then(|v| v.iter().next())
|
||||||
.ok_or(status::BadRequest(Some("No file uploaded")))?;
|
.ok_or(status::BadRequest(Some("No file uploaded")))?
|
||||||
|
.headers
|
||||||
|
.filename
|
||||||
|
.clone();
|
||||||
|
// Remove extension if it contains something else than just letters and numbers
|
||||||
|
let ext = filename
|
||||||
|
.and_then(|f| {
|
||||||
|
f.rsplit('.')
|
||||||
|
.next()
|
||||||
|
.and_then(|ext| {
|
||||||
|
if ext.chars().any(|c| !c.is_alphanumeric()) {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(ext.to_lowercase())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(|ext| format!(".{}", ext))
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
let dest = format!("{}/{}{}", CONFIG.media_directory, GUID::rand(), ext);
|
||||||
|
|
||||||
let file_path = match save_uploaded_file(file) {
|
match fields["file"][0].data {
|
||||||
Ok(Some(file_path)) => file_path,
|
SavedData::Bytes(ref bytes) => fs::write(&dest, bytes)
|
||||||
Ok(None) => return Ok(Redirect::to(uri!(new))),
|
.map_err(|_| status::BadRequest(Some("Couldn't save upload")))?,
|
||||||
Err(_) => return Err(status::BadRequest(Some("Couldn't save uploaded media: {}"))),
|
SavedData::File(ref path, _) => {
|
||||||
};
|
fs::copy(path, &dest)
|
||||||
|
.map_err(|_| status::BadRequest(Some("Couldn't copy upload")))?;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Ok(Redirect::to(uri!(new)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let has_cw = !read(&fields["cw"][0].data)
|
let has_cw = !read(&fields["cw"][0].data)
|
||||||
.map(|cw| cw.is_empty())
|
.map(|cw| cw.is_empty())
|
||||||
@@ -72,7 +97,7 @@ pub fn upload(
|
|||||||
let media = Media::insert(
|
let media = Media::insert(
|
||||||
&conn,
|
&conn,
|
||||||
NewMedia {
|
NewMedia {
|
||||||
file_path,
|
file_path: dest,
|
||||||
alt_text: read(&fields["alt"][0].data)?,
|
alt_text: read(&fields["alt"][0].data)?,
|
||||||
is_remote: false,
|
is_remote: false,
|
||||||
remote_url: None,
|
remote_url: None,
|
||||||
@@ -92,74 +117,6 @@ pub fn upload(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_uploaded_file(file: &SavedField) -> Result<Option<String>, plume_models::Error> {
|
|
||||||
// Remove extension if it contains something else than just letters and numbers
|
|
||||||
let ext = file
|
|
||||||
.headers
|
|
||||||
.filename
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|f| {
|
|
||||||
f.rsplit('.')
|
|
||||||
.next()
|
|
||||||
.and_then(|ext| {
|
|
||||||
if ext.chars().any(|c| !c.is_alphanumeric()) {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(ext.to_lowercase())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
if CONFIG.s3.is_some() {
|
|
||||||
#[cfg(not(feature="s3"))]
|
|
||||||
unreachable!();
|
|
||||||
|
|
||||||
#[cfg(feature="s3")]
|
|
||||||
{
|
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
let dest = format!("static/media/{}.{}", GUID::rand(), ext);
|
|
||||||
|
|
||||||
let bytes = match file.data {
|
|
||||||
SavedData::Bytes(ref bytes) => Cow::from(bytes),
|
|
||||||
SavedData::File(ref path, _) => Cow::from(fs::read(path)?),
|
|
||||||
_ => {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let bucket = CONFIG.s3.as_ref().unwrap().get_bucket();
|
|
||||||
let content_type = match &file.headers.content_type {
|
|
||||||
Some(ct) => ct.to_string(),
|
|
||||||
None => ContentType::from_extension(&ext)
|
|
||||||
.unwrap_or(ContentType::Binary)
|
|
||||||
.to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
bucket.put_object_with_content_type_blocking(&dest, &bytes, &content_type)?;
|
|
||||||
|
|
||||||
Ok(Some(dest))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let dest = format!("{}/{}.{}", CONFIG.media_directory, GUID::rand(), ext);
|
|
||||||
|
|
||||||
match file.data {
|
|
||||||
SavedData::Bytes(ref bytes) => {
|
|
||||||
fs::write(&dest, bytes)?;
|
|
||||||
}
|
|
||||||
SavedData::File(ref path, _) => {
|
|
||||||
fs::copy(path, &dest)?;
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Some(dest))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read(data: &SavedData) -> Result<String, status::BadRequest<&'static str>> {
|
fn read(data: &SavedData) -> Result<String, status::BadRequest<&'static str>> {
|
||||||
if let SavedData::Text(s) = data {
|
if let SavedData::Text(s) = data {
|
||||||
Ok(s.clone())
|
Ok(s.clone())
|
||||||
|
|||||||
+17
-26
@@ -9,7 +9,7 @@ use rocket::{
|
|||||||
http::{
|
http::{
|
||||||
hyper::header::{CacheControl, CacheDirective, ETag, EntityTag},
|
hyper::header::{CacheControl, CacheDirective, ETag, EntityTag},
|
||||||
uri::{FromUriParam, Query},
|
uri::{FromUriParam, Query},
|
||||||
RawStr, Status,
|
ContentType, RawStr, Status,
|
||||||
},
|
},
|
||||||
request::{self, FromFormValue, FromRequest, Request},
|
request::{self, FromFormValue, FromRequest, Request},
|
||||||
response::{self, Flash, NamedFile, Redirect, Responder, Response},
|
response::{self, Flash, NamedFile, Redirect, Responder, Response},
|
||||||
@@ -21,9 +21,6 @@ use std::{
|
|||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "s3")]
|
|
||||||
use rocket::http::ContentType;
|
|
||||||
|
|
||||||
/// Special return type used for routes that "cannot fail", and instead
|
/// Special return type used for routes that "cannot fail", and instead
|
||||||
/// `Redirect`, or `Flash<Redirect>`, when we cannot deliver a `Ructe` Response
|
/// `Redirect`, or `Flash<Redirect>`, when we cannot deliver a `Ructe` Response
|
||||||
#[allow(clippy::large_enum_variant)]
|
#[allow(clippy::large_enum_variant)]
|
||||||
@@ -143,7 +140,7 @@ pub fn build_atom_feed(
|
|||||||
FeedBuilder::default()
|
FeedBuilder::default()
|
||||||
.title(title)
|
.title(title)
|
||||||
.id(uri)
|
.id(uri)
|
||||||
.updated(DateTime::<Utc>::from_naive_utc_and_offset(*updated, Utc))
|
.updated(DateTime::<Utc>::from_utc(*updated, Utc))
|
||||||
.entries(
|
.entries(
|
||||||
entries
|
entries
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -182,9 +179,9 @@ fn post_to_atom(post: Post, conn: &Connection) -> Entry {
|
|||||||
// Using RFC 4287 format, see https://tools.ietf.org/html/rfc4287#section-3.3 for dates
|
// Using RFC 4287 format, see https://tools.ietf.org/html/rfc4287#section-3.3 for dates
|
||||||
// eg: 2003-12-13T18:30:02Z (Z is here because there is no timezone support with the NaiveDateTime crate)
|
// eg: 2003-12-13T18:30:02Z (Z is here because there is no timezone support with the NaiveDateTime crate)
|
||||||
.published(Some(
|
.published(Some(
|
||||||
DateTime::<Utc>::from_naive_utc_and_offset(post.creation_date, Utc).into(),
|
DateTime::<Utc>::from_utc(post.creation_date, Utc).into(),
|
||||||
))
|
))
|
||||||
.updated(DateTime::<Utc>::from_naive_utc_and_offset(post.creation_date, Utc))
|
.updated(DateTime::<Utc>::from_utc(post.creation_date, Utc))
|
||||||
.id(post.ap_url.clone())
|
.id(post.ap_url.clone())
|
||||||
.links(vec![LinkBuilder::default().href(post.ap_url).build()])
|
.links(vec![LinkBuilder::default().href(post.ap_url).build()])
|
||||||
.build()
|
.build()
|
||||||
@@ -210,7 +207,6 @@ pub mod well_known;
|
|||||||
#[derive(Responder)]
|
#[derive(Responder)]
|
||||||
enum FileKind {
|
enum FileKind {
|
||||||
Local(NamedFile),
|
Local(NamedFile),
|
||||||
#[cfg(feature = "s3")]
|
|
||||||
S3(Vec<u8>, ContentType),
|
S3(Vec<u8>, ContentType),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,26 +259,21 @@ pub fn plume_static_files(file: PathBuf, build_id: &RawStr) -> Option<CachedFile
|
|||||||
}
|
}
|
||||||
#[get("/static/media/<file..>")]
|
#[get("/static/media/<file..>")]
|
||||||
pub fn plume_media_files(file: PathBuf) -> Option<CachedFile> {
|
pub fn plume_media_files(file: PathBuf) -> Option<CachedFile> {
|
||||||
if CONFIG.s3.is_some() {
|
if let Some(config) = &CONFIG.s3 {
|
||||||
#[cfg(not(feature="s3"))]
|
let ct = file.extension()
|
||||||
unreachable!();
|
.and_then(|ext| ContentType::from_extension(&ext.to_string_lossy()))
|
||||||
|
.unwrap_or(ContentType::Binary);
|
||||||
|
|
||||||
#[cfg(feature="s3")]
|
let (data, code) = config.get_bucket()
|
||||||
{
|
.get_object_blocking(format!("plume-media/{}", file.to_string_lossy())).ok()?;
|
||||||
let data = CONFIG.s3.as_ref().unwrap().get_bucket()
|
if code != 200 {
|
||||||
.get_object_blocking(format!("static/media/{}", file.to_string_lossy())).ok()?;
|
return None;
|
||||||
|
|
||||||
let ct = data.headers().get("content-type")
|
|
||||||
.and_then(|x| ContentType::parse_flexible(&x))
|
|
||||||
.or_else(|| file.extension()
|
|
||||||
.and_then(|ext| ContentType::from_extension(&ext.to_string_lossy())))
|
|
||||||
.unwrap_or(ContentType::Binary);
|
|
||||||
|
|
||||||
Some(CachedFile {
|
|
||||||
inner: FileKind::S3(data.to_vec(), ct),
|
|
||||||
cache_control: CacheControl(vec![CacheDirective::MaxAge(60 * 60 * 24 * 30)]),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some(CachedFile {
|
||||||
|
inner: FileKind::S3 ( data, ct),
|
||||||
|
cache_control: CacheControl(vec![CacheDirective::MaxAge(60 * 60 * 24 * 30)]),
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
NamedFile::open(Path::new(&CONFIG.media_directory).join(file))
|
NamedFile::open(Path::new(&CONFIG.media_directory).join(file))
|
||||||
.ok()
|
.ok()
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
<hr/>
|
<hr/>
|
||||||
@:header()
|
@:header()
|
||||||
</nav>
|
</nav>
|
||||||
<nav class="right-nav">
|
<nav>
|
||||||
@if ctx.2.is_some() {
|
@if ctx.2.is_some() {
|
||||||
<a href="@uri!(search::search: _)">
|
<a href="@uri!(search::search: _)">
|
||||||
<i class="icon icon-search"></i>
|
<i class="icon icon-search"></i>
|
||||||
@@ -87,8 +87,6 @@
|
|||||||
<a href="@uri!(instance::privacy)">@i18n!(ctx.1, "Privacy policy")</a>
|
<a href="@uri!(instance::privacy)">@i18n!(ctx.1, "Privacy policy")</a>
|
||||||
@if ctx.2.clone().map(|u| u.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>
|
<a href="@uri!(instance::admin)">@i18n!(ctx.1, "Administration")</a>
|
||||||
} else if ctx.2.clone().map(|u| u.is_moderator()).unwrap_or(false) {
|
|
||||||
<a href="@uri!(instance::admin_mod)">@i18n!(ctx.1, "Moderation")</a>
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
@use plume_models::instance::Instance;
|
@use plume_models::instance::Instance;
|
||||||
@use validator::ValidationErrors;
|
@use validator::ValidationErrors;
|
||||||
@use crate::templates::{base, instance::admin_header};
|
@use crate::templates::base;
|
||||||
@use crate::template_utils::*;
|
@use crate::template_utils::*;
|
||||||
@use crate::routes::instance::InstanceSettingsForm;
|
@use crate::routes::instance::InstanceSettingsForm;
|
||||||
@use crate::routes::*;
|
@use crate::routes::*;
|
||||||
@@ -8,7 +8,15 @@
|
|||||||
@(ctx: BaseContext, instance: Instance, form: InstanceSettingsForm, errors: ValidationErrors)
|
@(ctx: BaseContext, instance: Instance, form: InstanceSettingsForm, errors: ValidationErrors)
|
||||||
|
|
||||||
@:base(ctx, i18n!(ctx.1, "Administration of {0}"; instance.name.clone()), {}, {}, {
|
@:base(ctx, i18n!(ctx.1, "Administration of {0}"; instance.name.clone()), {}, {}, {
|
||||||
@:admin_header(ctx, "Administration", 1)
|
<h1>@i18n!(ctx.1, "Administration")</h1>
|
||||||
|
|
||||||
|
@tabs(&[
|
||||||
|
(&uri!(instance::admin).to_string(), i18n!(ctx.1, "Configuration"), 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),
|
||||||
|
(&uri!(instance::admin_email_blocklist: page=_).to_string(), i18n!(ctx.1, "Email blocklist"), false)
|
||||||
|
])
|
||||||
|
|
||||||
<form method="post" action="@uri!(instance::update_settings)">
|
<form method="post" action="@uri!(instance::update_settings)">
|
||||||
@(Input::new("name", i18n!(ctx.1, "Name"))
|
@(Input::new("name", i18n!(ctx.1, "Name"))
|
||||||
.default(&form.name)
|
.default(&form.name)
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
@use crate::template_utils::*;
|
|
||||||
@use crate::routes::*;
|
|
||||||
|
|
||||||
@(ctx: BaseContext, title: &str, selected_tab: u8)
|
|
||||||
|
|
||||||
<h1>@i18n!(ctx.1, title)</h1>
|
|
||||||
|
|
||||||
@if ctx.2.clone().map(|u| u.is_admin()).unwrap_or(false) {
|
|
||||||
@tabs(&[
|
|
||||||
(&uri!(instance::admin).to_string(), i18n!(ctx.1, "Configuration"), selected_tab == 1),
|
|
||||||
(&uri!(instance::admin_instances: page = _).to_string(), i18n!(ctx.1, "Instances"), selected_tab == 2),
|
|
||||||
(&uri!(instance::admin_users: page = _).to_string(), i18n!(ctx.1, "Users"), selected_tab == 3),
|
|
||||||
(&uri!(instance::admin_email_blocklist: page=_).to_string(), i18n!(ctx.1, "Email blocklist"), selected_tab == 4)
|
|
||||||
])
|
|
||||||
} else {
|
|
||||||
@tabs(&[
|
|
||||||
(&uri!(instance::admin_instances: page = _).to_string(), i18n!(ctx.1, "Instances"), selected_tab == 2),
|
|
||||||
(&uri!(instance::admin_users: page = _).to_string(), i18n!(ctx.1, "Users"), selected_tab == 3),
|
|
||||||
(&uri!(instance::admin_email_blocklist: page=_).to_string(), i18n!(ctx.1, "Email blocklist"), selected_tab == 4)
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,8 +1,15 @@
|
|||||||
@use crate::templates::{base, instance::admin_header};
|
@use crate::templates::base;
|
||||||
@use crate::template_utils::*;
|
@use crate::template_utils::*;
|
||||||
|
@use crate::routes::*;
|
||||||
|
|
||||||
@(ctx: BaseContext)
|
@(ctx: BaseContext)
|
||||||
|
|
||||||
@:base(ctx, i18n!(ctx.1, "Moderation"), {}, {}, {
|
@:base(ctx, i18n!(ctx.1, "Moderation"), {}, {}, {
|
||||||
@:admin_header(ctx, "Moderation", 0)
|
<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),
|
||||||
|
])
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
@use plume_models::blocklisted_emails::BlocklistedEmail;
|
@use plume_models::blocklisted_emails::BlocklistedEmail;
|
||||||
@use crate::templates::{base, instance::admin_header};
|
@use crate::templates::base;
|
||||||
@use crate::template_utils::*;
|
@use crate::template_utils::*;
|
||||||
@use crate::routes::*;
|
@use crate::routes::*;
|
||||||
|
|
||||||
@(ctx:BaseContext, emails: Vec<BlocklistedEmail>, page:i32, n_pages:i32)
|
@(ctx:BaseContext, emails: Vec<BlocklistedEmail>, page:i32, n_pages:i32)
|
||||||
@:base(ctx, i18n!(ctx.1, "Blocklisted Emails"), {}, {}, {
|
@:base(ctx, i18n!(ctx.1, "Blocklisted Emails"), {}, {}, {
|
||||||
@:admin_header(ctx, "Blocklisted Emails", 4)
|
<h1>@i18n!(ctx.1,"Blocklisted Emails")</h1>
|
||||||
|
@tabs(&[
|
||||||
|
(&uri!(instance::admin).to_string(), i18n!(ctx.1, "Configuration"), false),
|
||||||
|
(&uri!(instance::admin_instances: page = _).to_string(), i18n!(ctx.1, "Instances"), false),
|
||||||
|
(&uri!(instance::admin_users: page = _).to_string(), i18n!(ctx.1, "Users"), false),
|
||||||
|
(&uri!(instance::admin_email_blocklist:page=_).to_string(), i18n!(ctx.1, "Email blocklist"), true),
|
||||||
|
])
|
||||||
<form method="post" action="@uri!(instance::add_email_blocklist)">
|
<form method="post" action="@uri!(instance::add_email_blocklist)">
|
||||||
@(Input::new("email_address", i18n!(ctx.1, "Email address"))
|
@(Input::new("email_address", i18n!(ctx.1, "Email address"))
|
||||||
.details(i18n!(ctx.1, "The email address you wish to block. In order to block domains, you can use globbing syntax, for example '*@example.com' blocks all addresses from example.com"))
|
.details(i18n!(ctx.1, "The email address you wish to block. In order to block domains, you can use globbing syntax, for example '*@example.com' blocks all addresses from example.com"))
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
@use plume_models::instance::Instance;
|
@use plume_models::instance::Instance;
|
||||||
@use crate::templates::{base, instance::admin_header};
|
@use crate::templates::base;
|
||||||
@use crate::template_utils::*;
|
@use crate::template_utils::*;
|
||||||
@use crate::routes::*;
|
@use crate::routes::*;
|
||||||
|
|
||||||
@(ctx: BaseContext, instance: Instance, instances: Vec<Instance>, page: i32, n_pages: i32)
|
@(ctx: BaseContext, instance: Instance, instances: Vec<Instance>, page: i32, n_pages: i32)
|
||||||
|
|
||||||
@:base(ctx, i18n!(ctx.1, "Administration of {0}"; instance.name), {}, {}, {
|
@:base(ctx, i18n!(ctx.1, "Administration of {0}"; instance.name), {}, {}, {
|
||||||
@:admin_header(ctx, "Instances", 2))
|
<h1>@i18n!(ctx.1, "Instances")</h1>
|
||||||
|
|
||||||
|
@tabs(&[
|
||||||
|
(&uri!(instance::admin).to_string(), i18n!(ctx.1, "Configuration"), false),
|
||||||
|
(&uri!(instance::admin_instances: page = _).to_string(), i18n!(ctx.1, "Instances"), true),
|
||||||
|
(&uri!(instance::admin_users: page = _).to_string(), i18n!(ctx.1, "Users"), false),
|
||||||
|
(&uri!(instance::admin_email_blocklist:page=_).to_string(), i18n!(ctx.1, "Email blocklist"), false),
|
||||||
|
])
|
||||||
|
|
||||||
<div class="list">
|
<div class="list">
|
||||||
@for instance in instances {
|
@for instance in instances {
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
@use plume_models::users::User;
|
@use plume_models::users::User;
|
||||||
@use crate::templates::{base, instance::admin_header};
|
@use crate::templates::base;
|
||||||
@use crate::template_utils::*;
|
@use crate::template_utils::*;
|
||||||
@use crate::routes::*;
|
@use crate::routes::*;
|
||||||
|
|
||||||
@(ctx: BaseContext, users: Vec<User>, user: Option<&str>, page: i32, n_pages: i32)
|
@(ctx: BaseContext, users: Vec<User>, user: Option<&str>, page: i32, n_pages: i32)
|
||||||
|
|
||||||
@:base(ctx, i18n!(ctx.1, "Users"), {}, {}, {
|
@:base(ctx, i18n!(ctx.1, "Users"), {}, {}, {
|
||||||
@:admin_header(ctx, "Users", 3))
|
<h1>@i18n!(ctx.1, "Users")</h1>
|
||||||
|
|
||||||
|
@tabs(&[
|
||||||
|
(&uri!(instance::admin).to_string(), i18n!(ctx.1, "Configuration"), false),
|
||||||
|
(&uri!(instance::admin_instances: page = _).to_string(), i18n!(ctx.1, "Instances"), false),
|
||||||
|
(&uri!(instance::admin_users: page = _).to_string(), i18n!(ctx.1, "Users"), true),
|
||||||
|
(&uri!(instance::admin_email_blocklist: page=_).to_string(), i18n!(ctx.1, "Email blocklist"), false)
|
||||||
|
])
|
||||||
|
|
||||||
<form method="get" action="@uri!(instance::admin_search_users: page = _, user = user.unwrap_or_default())">
|
<form method="get" action="@uri!(instance::admin_search_users: page = _, user = user.unwrap_or_default())">
|
||||||
<header>
|
<header>
|
||||||
|
|||||||
@@ -124,7 +124,7 @@
|
|||||||
</section>
|
</section>
|
||||||
}
|
}
|
||||||
<section class="banner">
|
<section class="banner">
|
||||||
<div class="flex wrap p-author h-card user" dir="auto">
|
<div class="flex p-author h-card user" dir="auto">
|
||||||
@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">
|
||||||
|
|||||||
Reference in New Issue
Block a user