mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-11-23 18:01:00 +00:00
Merge remote-tracking branch 'LemmyNet/master'
This commit is contained in:
commit
d71897620c
103 changed files with 3388 additions and 1742 deletions
3
.travis.yml
vendored
3
.travis.yml
vendored
|
@ -24,10 +24,11 @@ script:
|
||||||
- cargo clippy -- -D clippy::style -D clippy::correctness -D clippy::complexity -D clippy::perf
|
- cargo clippy -- -D clippy::style -D clippy::correctness -D clippy::complexity -D clippy::perf
|
||||||
- cargo install diesel_cli --no-default-features --features postgres --force
|
- cargo install diesel_cli --no-default-features --features postgres --force
|
||||||
- diesel migration run
|
- diesel migration run
|
||||||
- cargo test
|
- cargo test --workspace
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
- DATABASE_URL=postgres://lemmy:password@localhost:5432/lemmy
|
- DATABASE_URL=postgres://lemmy:password@localhost:5432/lemmy
|
||||||
|
- LEMMY_DATABASE_URL=postgres://lemmy:password@localhost:5432/lemmy
|
||||||
- RUST_TEST_THREADS=1
|
- RUST_TEST_THREADS=1
|
||||||
|
|
||||||
addons:
|
addons:
|
||||||
|
|
2
ansible/VERSION
vendored
2
ansible/VERSION
vendored
|
@ -1 +1 @@
|
||||||
v0.7.13
|
v0.7.19
|
||||||
|
|
5
docker/dev/Dockerfile
vendored
5
docker/dev/Dockerfile
vendored
|
@ -18,11 +18,12 @@ RUN sudo chown -R rust:rust .
|
||||||
RUN USER=root cargo new server
|
RUN USER=root cargo new server
|
||||||
WORKDIR /app/server
|
WORKDIR /app/server
|
||||||
COPY server/Cargo.toml server/Cargo.lock ./
|
COPY server/Cargo.toml server/Cargo.lock ./
|
||||||
RUN sudo chown -R rust:rust .
|
COPY server/lemmy_db ./lemmy_db
|
||||||
|
COPY server/lemmy_utils ./lemmy_utils
|
||||||
RUN mkdir -p ./src/bin \
|
RUN mkdir -p ./src/bin \
|
||||||
&& echo 'fn main() { println!("Dummy") }' > ./src/bin/main.rs
|
&& echo 'fn main() { println!("Dummy") }' > ./src/bin/main.rs
|
||||||
RUN cargo build
|
RUN cargo build
|
||||||
RUN rm -f ./target/x86_64-unknown-linux-musl/release/deps/lemmy_server*
|
RUN find target/debug -type f -name "$(echo "lemmy_server" | tr '-' '_')*" -exec touch -t 200001010000 {} +
|
||||||
COPY server/src ./src/
|
COPY server/src ./src/
|
||||||
COPY server/migrations ./migrations/
|
COPY server/migrations ./migrations/
|
||||||
|
|
||||||
|
|
11
docker/prod/Dockerfile
vendored
11
docker/prod/Dockerfile
vendored
|
@ -10,14 +10,15 @@ WORKDIR /app
|
||||||
RUN sudo chown -R rust:rust .
|
RUN sudo chown -R rust:rust .
|
||||||
RUN USER=root cargo new server
|
RUN USER=root cargo new server
|
||||||
WORKDIR /app/server
|
WORKDIR /app/server
|
||||||
COPY --chown=rust:rust server/Cargo.toml server/Cargo.lock ./
|
COPY server/Cargo.toml server/Cargo.lock ./
|
||||||
#RUN sudo chown -R rust:rust .
|
COPY server/lemmy_db ./lemmy_db
|
||||||
|
COPY server/lemmy_utils ./lemmy_utils
|
||||||
RUN mkdir -p ./src/bin \
|
RUN mkdir -p ./src/bin \
|
||||||
&& echo 'fn main() { println!("Dummy") }' > ./src/bin/main.rs
|
&& echo 'fn main() { println!("Dummy") }' > ./src/bin/main.rs
|
||||||
RUN cargo build --release
|
RUN cargo build --release
|
||||||
RUN rm -f ./target/$CARGO_BUILD_TARGET/$RUSTRELEASEDIR/deps/lemmy_server*
|
RUN find target/$CARGO_BUILD_TARGET/$RUSTRELEASEDIR -type f -name "$(echo "lemmy_server" | tr '-' '_')*" -exec touch -t 200001010000 {} +
|
||||||
COPY --chown=rust:rust server/src ./src/
|
COPY server/src ./src/
|
||||||
COPY --chown=rust:rust server/migrations ./migrations/
|
COPY server/migrations ./migrations/
|
||||||
|
|
||||||
# build for release
|
# build for release
|
||||||
# workaround for https://github.com/rust-lang/rust/issues/62896
|
# workaround for https://github.com/rust-lang/rust/issues/62896
|
||||||
|
|
2
docker/prod/docker-compose.yml
vendored
2
docker/prod/docker-compose.yml
vendored
|
@ -12,7 +12,7 @@ services:
|
||||||
restart: always
|
restart: always
|
||||||
|
|
||||||
lemmy:
|
lemmy:
|
||||||
image: dessalines/lemmy:v0.7.13
|
image: dessalines/lemmy:v0.7.19
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:8536:8536"
|
- "127.0.0.1:8536:8536"
|
||||||
restart: always
|
restart: always
|
||||||
|
|
2
docs/src/administration_configuration.md
vendored
2
docs/src/administration_configuration.md
vendored
|
@ -5,6 +5,8 @@ The configuration is based on the file
|
||||||
This file also contains documentation for all the available options. To override the defaults, you
|
This file also contains documentation for all the available options. To override the defaults, you
|
||||||
can copy the options you want to change into your local `config.hjson` file.
|
can copy the options you want to change into your local `config.hjson` file.
|
||||||
|
|
||||||
|
To use a different `config.hjson` location than the current directory, set the environment variable `LEMMY_CONFIG_LOCATION`.
|
||||||
|
|
||||||
Additionally, you can override any config files with environment variables. These have the same
|
Additionally, you can override any config files with environment variables. These have the same
|
||||||
name as the config options, and are prefixed with `LEMMY_`. For example, you can override the
|
name as the config options, and are prefixed with `LEMMY_`. For example, you can override the
|
||||||
`database.password` with `LEMMY_DATABASE__POOL_SIZE=10`.
|
`database.password` with `LEMMY_DATABASE__POOL_SIZE=10`.
|
||||||
|
|
4
docs/src/contributing_tests.md
vendored
4
docs/src/contributing_tests.md
vendored
|
@ -7,9 +7,7 @@ following commands in the `server` subfolder:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
psql -U lemmy -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;"
|
psql -U lemmy -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;"
|
||||||
export DATABASE_URL=postgres://lemmy:password@localhost:5432/lemmy
|
./test.sh
|
||||||
diesel migration run
|
|
||||||
RUST_TEST_THREADS=1 cargo test
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Federation
|
### Federation
|
||||||
|
|
1
docs/src/contributing_websocket_http_api.md
vendored
1
docs/src/contributing_websocket_http_api.md
vendored
|
@ -1149,6 +1149,7 @@ Post listing types are `All, Subscribed, Community`
|
||||||
page: Option<i64>,
|
page: Option<i64>,
|
||||||
limit: Option<i64>,
|
limit: Option<i64>,
|
||||||
community_id: Option<i32>,
|
community_id: Option<i32>,
|
||||||
|
community_name: Option<String>,
|
||||||
auth: Option<String>
|
auth: Option<String>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
2
install.sh
vendored
2
install.sh
vendored
|
@ -1,4 +1,4 @@
|
||||||
#!/bin/sh
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# Set the database variable to the default first.
|
# Set the database variable to the default first.
|
||||||
|
|
53
server/Cargo.lock
generated
vendored
53
server/Cargo.lock
generated
vendored
|
@ -1399,12 +1399,6 @@ dependencies = [
|
||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "htmlescape"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
@ -1428,9 +1422,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http-signature-normalization-actix"
|
name = "http-signature-normalization-actix"
|
||||||
version = "0.4.0-alpha.0"
|
version = "0.4.0-alpha.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09afff6987c7edbed101d1cddd2185786fb0af0dd9c06b654aca73a0a763680f"
|
checksum = "131fc982391a6b37847888b568cbe0e9cd302f1b0015f4f6f4a50234bebd049c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-http",
|
"actix-http",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
|
@ -1572,6 +1566,21 @@ version = "1.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lemmy_db"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"bcrypt",
|
||||||
|
"chrono",
|
||||||
|
"diesel",
|
||||||
|
"log",
|
||||||
|
"serde 1.0.114",
|
||||||
|
"serde_json",
|
||||||
|
"sha2",
|
||||||
|
"strum",
|
||||||
|
"strum_macros",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lemmy_server"
|
name = "lemmy_server"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
|
@ -1589,27 +1598,23 @@ dependencies = [
|
||||||
"base64 0.12.3",
|
"base64 0.12.3",
|
||||||
"bcrypt",
|
"bcrypt",
|
||||||
"chrono",
|
"chrono",
|
||||||
"comrak",
|
|
||||||
"config",
|
|
||||||
"diesel",
|
"diesel",
|
||||||
"diesel_migrations",
|
"diesel_migrations",
|
||||||
"dotenv",
|
"dotenv",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"failure",
|
"failure",
|
||||||
"futures",
|
"futures",
|
||||||
"htmlescape",
|
|
||||||
"http",
|
"http",
|
||||||
"http-signature-normalization-actix",
|
"http-signature-normalization-actix",
|
||||||
"itertools",
|
"itertools",
|
||||||
"jsonwebtoken",
|
"jsonwebtoken",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"lettre",
|
"lemmy_db",
|
||||||
"lettre_email",
|
"lemmy_utils",
|
||||||
"log",
|
"log",
|
||||||
"openssl",
|
"openssl",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"rand 0.7.3",
|
"rand 0.7.3",
|
||||||
"regex",
|
|
||||||
"rss",
|
"rss",
|
||||||
"serde 1.0.114",
|
"serde 1.0.114",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -1621,6 +1626,26 @@ dependencies = [
|
||||||
"uuid 0.8.1",
|
"uuid 0.8.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lemmy_utils"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"comrak",
|
||||||
|
"config",
|
||||||
|
"itertools",
|
||||||
|
"lazy_static",
|
||||||
|
"lettre",
|
||||||
|
"lettre_email",
|
||||||
|
"log",
|
||||||
|
"openssl",
|
||||||
|
"rand 0.7.3",
|
||||||
|
"regex",
|
||||||
|
"serde 1.0.114",
|
||||||
|
"serde_json",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lettre"
|
name = "lettre"
|
||||||
version = "0.9.3"
|
version = "0.9.3"
|
||||||
|
|
19
server/Cargo.toml
vendored
19
server/Cargo.toml
vendored
|
@ -1,14 +1,21 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lemmy_server"
|
name = "lemmy_server"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
authors = ["Dessalines <tyhou13@gmx.com>"]
|
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = [
|
||||||
|
"lemmy_utils",
|
||||||
|
"lemmy_db"
|
||||||
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
diesel = { version = "1.4.4", features = ["postgres","chrono","r2d2","64-column-tables","serde_json"] }
|
lemmy_utils = { path = "./lemmy_utils" }
|
||||||
|
lemmy_db = { path = "./lemmy_db" }
|
||||||
|
diesel = "1.4.4"
|
||||||
diesel_migrations = "1.4.0"
|
diesel_migrations = "1.4.0"
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
activitystreams = "0.6.2"
|
activitystreams = "0.6.2"
|
||||||
|
@ -31,19 +38,13 @@ rand = "0.7.3"
|
||||||
strum = "0.18.0"
|
strum = "0.18.0"
|
||||||
strum_macros = "0.18.0"
|
strum_macros = "0.18.0"
|
||||||
jsonwebtoken = "7.0.1"
|
jsonwebtoken = "7.0.1"
|
||||||
regex = "1.3.5"
|
|
||||||
lazy_static = "1.3.0"
|
lazy_static = "1.3.0"
|
||||||
lettre = "0.9.3"
|
|
||||||
lettre_email = "0.9.4"
|
|
||||||
rss = "1.9.0"
|
rss = "1.9.0"
|
||||||
htmlescape = "0.3.1"
|
|
||||||
url = { version = "2.1.1", features = ["serde"] }
|
url = { version = "2.1.1", features = ["serde"] }
|
||||||
config = {version = "0.10.1", default-features = false, features = ["hjson"] }
|
|
||||||
percent-encoding = "2.1.0"
|
percent-encoding = "2.1.0"
|
||||||
comrak = "0.7"
|
|
||||||
openssl = "0.10"
|
openssl = "0.10"
|
||||||
http = "0.2.1"
|
http = "0.2.1"
|
||||||
http-signature-normalization-actix = { version = "0.4.0-alpha.0", default-features = false, features = ["sha-2"] }
|
http-signature-normalization-actix = { version = "0.4.0-alpha.2", default-features = false, features = ["sha-2"] }
|
||||||
base64 = "0.12.1"
|
base64 = "0.12.1"
|
||||||
tokio = "0.2.21"
|
tokio = "0.2.21"
|
||||||
futures = "0.3.5"
|
futures = "0.3.5"
|
||||||
|
|
3
server/db-init.sh
vendored
3
server/db-init.sh
vendored
|
@ -1,4 +1,5 @@
|
||||||
#!/bin/sh
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
# Default configurations
|
# Default configurations
|
||||||
username=lemmy
|
username=lemmy
|
||||||
|
|
2
server/diesel.toml
vendored
2
server/diesel.toml
vendored
|
@ -2,4 +2,4 @@
|
||||||
# see diesel.rs/guides/configuring-diesel-cli
|
# see diesel.rs/guides/configuring-diesel-cli
|
||||||
|
|
||||||
[print_schema]
|
[print_schema]
|
||||||
file = "src/schema.rs"
|
file = "lemmy_db/src/schema.rs"
|
||||||
|
|
15
server/lemmy_db/Cargo.toml
vendored
Normal file
15
server/lemmy_db/Cargo.toml
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
[package]
|
||||||
|
name = "lemmy_db"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
diesel = { version = "1.4.4", features = ["postgres","chrono","r2d2","64-column-tables","serde_json"] }
|
||||||
|
chrono = { version = "0.4.7", features = ["serde"] }
|
||||||
|
serde = { version = "1.0.105", features = ["derive"] }
|
||||||
|
serde_json = { version = "1.0.52", features = ["preserve_order"]}
|
||||||
|
strum = "0.18.0"
|
||||||
|
strum_macros = "0.18.0"
|
||||||
|
log = "0.4.0"
|
||||||
|
sha2 = "0.9"
|
||||||
|
bcrypt = "0.8.0"
|
|
@ -1,9 +1,12 @@
|
||||||
use crate::{blocking, db::Crud, schema::activity, DbPool, LemmyError};
|
use crate::{schema::activity, Crud};
|
||||||
use diesel::{dsl::*, result::Error, *};
|
use diesel::{dsl::*, result::Error, *};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::fmt::Debug;
|
use std::{
|
||||||
|
fmt::Debug,
|
||||||
|
io::{Error as IoError, ErrorKind},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
|
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
|
||||||
#[table_name = "activity"]
|
#[table_name = "activity"]
|
||||||
|
@ -55,46 +58,43 @@ impl Crud<ActivityForm> for Activity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn insert_activity<T>(
|
pub fn do_insert_activity<T>(
|
||||||
user_id: i32,
|
|
||||||
data: T,
|
|
||||||
local: bool,
|
|
||||||
pool: &DbPool,
|
|
||||||
) -> Result<(), LemmyError>
|
|
||||||
where
|
|
||||||
T: Serialize + Debug + Send + 'static,
|
|
||||||
{
|
|
||||||
blocking(pool, move |conn| {
|
|
||||||
do_insert_activity(conn, user_id, &data, local)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn do_insert_activity<T>(
|
|
||||||
conn: &PgConnection,
|
conn: &PgConnection,
|
||||||
user_id: i32,
|
user_id: i32,
|
||||||
data: &T,
|
data: &T,
|
||||||
local: bool,
|
local: bool,
|
||||||
) -> Result<(), LemmyError>
|
) -> Result<Activity, IoError>
|
||||||
where
|
where
|
||||||
T: Serialize + Debug,
|
T: Serialize + Debug,
|
||||||
{
|
{
|
||||||
|
debug!("inserting activity for user {}, data {:?}", user_id, &data);
|
||||||
let activity_form = ActivityForm {
|
let activity_form = ActivityForm {
|
||||||
user_id,
|
user_id,
|
||||||
data: serde_json::to_value(&data)?,
|
data: serde_json::to_value(&data)?,
|
||||||
local,
|
local,
|
||||||
updated: None,
|
updated: None,
|
||||||
};
|
};
|
||||||
debug!("inserting activity for user {}, data {:?}", user_id, data);
|
let result = Activity::create(&conn, &activity_form);
|
||||||
Activity::create(&conn, &activity_form)?;
|
match result {
|
||||||
Ok(())
|
Ok(s) => Ok(s),
|
||||||
|
Err(e) => Err(IoError::new(
|
||||||
|
ErrorKind::Other,
|
||||||
|
format!("Failed to insert activity into database: {}", e),
|
||||||
|
)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{super::user::*, *};
|
use crate::{
|
||||||
use crate::db::{establish_unpooled_connection, Crud, ListingType, SortType};
|
activity::{Activity, ActivityForm},
|
||||||
|
tests::establish_unpooled_connection,
|
||||||
|
user::{UserForm, User_},
|
||||||
|
Crud,
|
||||||
|
ListingType,
|
||||||
|
SortType,
|
||||||
|
};
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_crud() {
|
fn test_crud() {
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
db::Crud,
|
|
||||||
schema::{category, category::dsl::*},
|
schema::{category, category::dsl::*},
|
||||||
|
Crud,
|
||||||
};
|
};
|
||||||
use diesel::{dsl::*, result::Error, *};
|
use diesel::{dsl::*, result::Error, *};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -52,8 +52,7 @@ impl Category {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use crate::{category::Category, tests::establish_unpooled_connection};
|
||||||
use crate::db::establish_unpooled_connection;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_crud() {
|
fn test_crud() {
|
|
@ -1,9 +1,5 @@
|
||||||
use super::{post::Post, *};
|
use super::{post::Post, *};
|
||||||
use crate::{
|
use crate::schema::{comment, comment_like, comment_saved};
|
||||||
apub::{make_apub_endpoint, EndpointType},
|
|
||||||
naive_now,
|
|
||||||
schema::{comment, comment_like, comment_saved},
|
|
||||||
};
|
|
||||||
|
|
||||||
// WITH RECURSIVE MyTree AS (
|
// WITH RECURSIVE MyTree AS (
|
||||||
// SELECT * FROM comment WHERE parent_id IS NULL
|
// SELECT * FROM comment WHERE parent_id IS NULL
|
||||||
|
@ -77,12 +73,15 @@ impl Crud<CommentForm> for Comment {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Comment {
|
impl Comment {
|
||||||
pub fn update_ap_id(conn: &PgConnection, comment_id: i32) -> Result<Self, Error> {
|
pub fn update_ap_id(
|
||||||
|
conn: &PgConnection,
|
||||||
|
comment_id: i32,
|
||||||
|
apub_id: String,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
use crate::schema::comment::dsl::*;
|
use crate::schema::comment::dsl::*;
|
||||||
|
|
||||||
let apid = make_apub_endpoint(EndpointType::Comment, &comment_id.to_string()).to_string();
|
|
||||||
diesel::update(comment.find(comment_id))
|
diesel::update(comment.find(comment_id))
|
||||||
.set(ap_id.eq(apid))
|
.set(ap_id.eq(apub_id))
|
||||||
.get_result::<Self>(conn)
|
.get_result::<Self>(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,10 +203,8 @@ impl Saveable<CommentSavedForm> for CommentSaved {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{
|
use crate::{comment::*, community::*, post::*, tests::establish_unpooled_connection, user::*};
|
||||||
super::{community::*, post::*, user::*},
|
|
||||||
*,
|
|
||||||
};
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_crud() {
|
fn test_crud() {
|
||||||
let conn = establish_unpooled_connection();
|
let conn = establish_unpooled_connection();
|
|
@ -1,5 +1,5 @@
|
||||||
// TODO, remove the cross join here, just join to user directly
|
// TODO, remove the cross join here, just join to user directly
|
||||||
use crate::db::{fuzzy_search, limit_and_offset, ListingType, MaybeOptional, SortType};
|
use crate::{fuzzy_search, limit_and_offset, ListingType, MaybeOptional, SortType};
|
||||||
use diesel::{dsl::*, pg::Pg, result::Error, *};
|
use diesel::{dsl::*, pg::Pg, result::Error, *};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ table! {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
creator_id -> Int4,
|
creator_id -> Int4,
|
||||||
post_id -> Int4,
|
post_id -> Int4,
|
||||||
|
post_name -> Varchar,
|
||||||
parent_id -> Nullable<Int4>,
|
parent_id -> Nullable<Int4>,
|
||||||
content -> Text,
|
content -> Text,
|
||||||
removed -> Bool,
|
removed -> Bool,
|
||||||
|
@ -27,6 +28,7 @@ table! {
|
||||||
creator_actor_id -> Text,
|
creator_actor_id -> Text,
|
||||||
creator_local -> Bool,
|
creator_local -> Bool,
|
||||||
creator_name -> Varchar,
|
creator_name -> Varchar,
|
||||||
|
creator_published -> Timestamp,
|
||||||
creator_avatar -> Nullable<Text>,
|
creator_avatar -> Nullable<Text>,
|
||||||
score -> BigInt,
|
score -> BigInt,
|
||||||
upvotes -> BigInt,
|
upvotes -> BigInt,
|
||||||
|
@ -44,6 +46,7 @@ table! {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
creator_id -> Int4,
|
creator_id -> Int4,
|
||||||
post_id -> Int4,
|
post_id -> Int4,
|
||||||
|
post_name -> Varchar,
|
||||||
parent_id -> Nullable<Int4>,
|
parent_id -> Nullable<Int4>,
|
||||||
content -> Text,
|
content -> Text,
|
||||||
removed -> Bool,
|
removed -> Bool,
|
||||||
|
@ -62,6 +65,7 @@ table! {
|
||||||
creator_actor_id -> Text,
|
creator_actor_id -> Text,
|
||||||
creator_local -> Bool,
|
creator_local -> Bool,
|
||||||
creator_name -> Varchar,
|
creator_name -> Varchar,
|
||||||
|
creator_published -> Timestamp,
|
||||||
creator_avatar -> Nullable<Text>,
|
creator_avatar -> Nullable<Text>,
|
||||||
score -> BigInt,
|
score -> BigInt,
|
||||||
upvotes -> BigInt,
|
upvotes -> BigInt,
|
||||||
|
@ -82,6 +86,7 @@ pub struct CommentView {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub creator_id: i32,
|
pub creator_id: i32,
|
||||||
pub post_id: i32,
|
pub post_id: i32,
|
||||||
|
pub post_name: String,
|
||||||
pub parent_id: Option<i32>,
|
pub parent_id: Option<i32>,
|
||||||
pub content: String,
|
pub content: String,
|
||||||
pub removed: bool,
|
pub removed: bool,
|
||||||
|
@ -100,6 +105,7 @@ pub struct CommentView {
|
||||||
pub creator_actor_id: String,
|
pub creator_actor_id: String,
|
||||||
pub creator_local: bool,
|
pub creator_local: bool,
|
||||||
pub creator_name: String,
|
pub creator_name: String,
|
||||||
|
pub creator_published: chrono::NaiveDateTime,
|
||||||
pub creator_avatar: Option<String>,
|
pub creator_avatar: Option<String>,
|
||||||
pub score: i64,
|
pub score: i64,
|
||||||
pub upvotes: i64,
|
pub upvotes: i64,
|
||||||
|
@ -295,6 +301,7 @@ table! {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
creator_id -> Int4,
|
creator_id -> Int4,
|
||||||
post_id -> Int4,
|
post_id -> Int4,
|
||||||
|
post_name -> Varchar,
|
||||||
parent_id -> Nullable<Int4>,
|
parent_id -> Nullable<Int4>,
|
||||||
content -> Text,
|
content -> Text,
|
||||||
removed -> Bool,
|
removed -> Bool,
|
||||||
|
@ -314,6 +321,7 @@ table! {
|
||||||
creator_local -> Bool,
|
creator_local -> Bool,
|
||||||
creator_name -> Varchar,
|
creator_name -> Varchar,
|
||||||
creator_avatar -> Nullable<Text>,
|
creator_avatar -> Nullable<Text>,
|
||||||
|
creator_published -> Timestamp,
|
||||||
score -> BigInt,
|
score -> BigInt,
|
||||||
upvotes -> BigInt,
|
upvotes -> BigInt,
|
||||||
downvotes -> BigInt,
|
downvotes -> BigInt,
|
||||||
|
@ -334,6 +342,7 @@ pub struct ReplyView {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub creator_id: i32,
|
pub creator_id: i32,
|
||||||
pub post_id: i32,
|
pub post_id: i32,
|
||||||
|
pub post_name: String,
|
||||||
pub parent_id: Option<i32>,
|
pub parent_id: Option<i32>,
|
||||||
pub content: String,
|
pub content: String,
|
||||||
pub removed: bool,
|
pub removed: bool,
|
||||||
|
@ -353,6 +362,7 @@ pub struct ReplyView {
|
||||||
pub creator_local: bool,
|
pub creator_local: bool,
|
||||||
pub creator_name: String,
|
pub creator_name: String,
|
||||||
pub creator_avatar: Option<String>,
|
pub creator_avatar: Option<String>,
|
||||||
|
pub creator_published: chrono::NaiveDateTime,
|
||||||
pub score: i64,
|
pub score: i64,
|
||||||
pub upvotes: i64,
|
pub upvotes: i64,
|
||||||
pub downvotes: i64,
|
pub downvotes: i64,
|
||||||
|
@ -455,11 +465,17 @@ impl<'a> ReplyQueryBuilder<'a> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{
|
use crate::{
|
||||||
super::{comment::*, community::*, post::*, user::*},
|
comment::*,
|
||||||
|
comment_view::*,
|
||||||
|
community::*,
|
||||||
|
post::*,
|
||||||
|
tests::establish_unpooled_connection,
|
||||||
|
user::*,
|
||||||
|
Crud,
|
||||||
|
Likeable,
|
||||||
*,
|
*,
|
||||||
};
|
};
|
||||||
use crate::db::{establish_unpooled_connection, Crud, Likeable};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_crud() {
|
fn test_crud() {
|
||||||
|
@ -565,6 +581,7 @@ mod tests {
|
||||||
content: "A test comment 32".into(),
|
content: "A test comment 32".into(),
|
||||||
creator_id: inserted_user.id,
|
creator_id: inserted_user.id,
|
||||||
post_id: inserted_post.id,
|
post_id: inserted_post.id,
|
||||||
|
post_name: inserted_post.name.to_owned(),
|
||||||
community_id: inserted_community.id,
|
community_id: inserted_community.id,
|
||||||
community_name: inserted_community.name.to_owned(),
|
community_name: inserted_community.name.to_owned(),
|
||||||
parent_id: None,
|
parent_id: None,
|
||||||
|
@ -576,6 +593,7 @@ mod tests {
|
||||||
published: inserted_comment.published,
|
published: inserted_comment.published,
|
||||||
updated: None,
|
updated: None,
|
||||||
creator_name: inserted_user.name.to_owned(),
|
creator_name: inserted_user.name.to_owned(),
|
||||||
|
creator_published: inserted_user.published,
|
||||||
creator_avatar: None,
|
creator_avatar: None,
|
||||||
score: 1,
|
score: 1,
|
||||||
downvotes: 0,
|
downvotes: 0,
|
||||||
|
@ -598,6 +616,7 @@ mod tests {
|
||||||
content: "A test comment 32".into(),
|
content: "A test comment 32".into(),
|
||||||
creator_id: inserted_user.id,
|
creator_id: inserted_user.id,
|
||||||
post_id: inserted_post.id,
|
post_id: inserted_post.id,
|
||||||
|
post_name: inserted_post.name.to_owned(),
|
||||||
community_id: inserted_community.id,
|
community_id: inserted_community.id,
|
||||||
community_name: inserted_community.name.to_owned(),
|
community_name: inserted_community.name.to_owned(),
|
||||||
parent_id: None,
|
parent_id: None,
|
||||||
|
@ -609,6 +628,7 @@ mod tests {
|
||||||
published: inserted_comment.published,
|
published: inserted_comment.published,
|
||||||
updated: None,
|
updated: None,
|
||||||
creator_name: inserted_user.name.to_owned(),
|
creator_name: inserted_user.name.to_owned(),
|
||||||
|
creator_published: inserted_user.published,
|
||||||
creator_avatar: None,
|
creator_avatar: None,
|
||||||
score: 1,
|
score: 1,
|
||||||
downvotes: 0,
|
downvotes: 0,
|
|
@ -1,6 +1,9 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
db::{Bannable, Crud, Followable, Joinable},
|
|
||||||
schema::{community, community_follower, community_moderator, community_user_ban},
|
schema::{community, community_follower, community_moderator, community_user_ban},
|
||||||
|
Bannable,
|
||||||
|
Crud,
|
||||||
|
Followable,
|
||||||
|
Joinable,
|
||||||
};
|
};
|
||||||
use diesel::{dsl::*, result::Error, *};
|
use diesel::{dsl::*, result::Error, *};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -232,8 +235,7 @@ impl Followable<CommunityFollowerForm> for CommunityFollower {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{super::user::*, *};
|
use crate::{community::*, tests::establish_unpooled_connection, user::*, ListingType, SortType};
|
||||||
use crate::db::{establish_unpooled_connection, ListingType, SortType};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_crud() {
|
fn test_crud() {
|
|
@ -1,5 +1,5 @@
|
||||||
use super::community_view::community_fast_view::BoxedQuery;
|
use super::community_view::community_fast_view::BoxedQuery;
|
||||||
use crate::db::{fuzzy_search, limit_and_offset, MaybeOptional, SortType};
|
use crate::{fuzzy_search, limit_and_offset, MaybeOptional, SortType};
|
||||||
use diesel::{pg::Pg, result::Error, *};
|
use diesel::{pg::Pg, result::Error, *};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -299,6 +299,7 @@ impl CommunityModeratorView {
|
||||||
use super::community_view::community_moderator_view::dsl::*;
|
use super::community_view::community_moderator_view::dsl::*;
|
||||||
community_moderator_view
|
community_moderator_view
|
||||||
.filter(community_id.eq(from_community_id))
|
.filter(community_id.eq(from_community_id))
|
||||||
|
.order_by(published)
|
||||||
.load::<Self>(conn)
|
.load::<Self>(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -306,6 +307,7 @@ impl CommunityModeratorView {
|
||||||
use super::community_view::community_moderator_view::dsl::*;
|
use super::community_view::community_moderator_view::dsl::*;
|
||||||
community_moderator_view
|
community_moderator_view
|
||||||
.filter(user_id.eq(from_user_id))
|
.filter(user_id.eq(from_user_id))
|
||||||
|
.order_by(published)
|
||||||
.load::<Self>(conn)
|
.load::<Self>(conn)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,10 +1,22 @@
|
||||||
use crate::settings::Settings;
|
#[macro_use]
|
||||||
|
pub extern crate diesel;
|
||||||
|
#[macro_use]
|
||||||
|
pub extern crate strum_macros;
|
||||||
|
pub extern crate bcrypt;
|
||||||
|
pub extern crate chrono;
|
||||||
|
pub extern crate log;
|
||||||
|
pub extern crate serde;
|
||||||
|
pub extern crate serde_json;
|
||||||
|
pub extern crate sha2;
|
||||||
|
pub extern crate strum;
|
||||||
|
|
||||||
|
use chrono::NaiveDateTime;
|
||||||
use diesel::{dsl::*, result::Error, *};
|
use diesel::{dsl::*, result::Error, *};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{env, env::VarError};
|
||||||
|
|
||||||
pub mod activity;
|
pub mod activity;
|
||||||
pub mod category;
|
pub mod category;
|
||||||
pub mod code_migrations;
|
|
||||||
pub mod comment;
|
pub mod comment;
|
||||||
pub mod comment_view;
|
pub mod comment_view;
|
||||||
pub mod community;
|
pub mod community;
|
||||||
|
@ -16,6 +28,7 @@ pub mod post;
|
||||||
pub mod post_view;
|
pub mod post_view;
|
||||||
pub mod private_message;
|
pub mod private_message;
|
||||||
pub mod private_message_view;
|
pub mod private_message_view;
|
||||||
|
pub mod schema;
|
||||||
pub mod site;
|
pub mod site;
|
||||||
pub mod site_view;
|
pub mod site_view;
|
||||||
pub mod user;
|
pub mod user;
|
||||||
|
@ -111,9 +124,8 @@ impl<T> MaybeOptional<T> for Option<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn establish_unpooled_connection() -> PgConnection {
|
pub fn get_database_url_from_env() -> Result<String, VarError> {
|
||||||
let db_url = Settings::get().get_database_url();
|
env::var("LEMMY_DATABASE_URL")
|
||||||
PgConnection::establish(&db_url).unwrap_or_else(|_| panic!("Error connecting to {}", db_url))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(EnumString, ToString, Debug, Serialize, Deserialize)]
|
#[derive(EnumString, ToString, Debug, Serialize, Deserialize)]
|
||||||
|
@ -155,9 +167,25 @@ pub fn limit_and_offset(page: Option<i64>, limit: Option<i64>) -> (i64, i64) {
|
||||||
let offset = limit * (page - 1);
|
let offset = limit * (page - 1);
|
||||||
(limit, offset)
|
(limit, offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn naive_now() -> NaiveDateTime {
|
||||||
|
chrono::prelude::Utc::now().naive_utc()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::fuzzy_search;
|
use super::fuzzy_search;
|
||||||
|
use crate::get_database_url_from_env;
|
||||||
|
use diesel::{Connection, PgConnection};
|
||||||
|
|
||||||
|
pub fn establish_unpooled_connection() -> PgConnection {
|
||||||
|
let db_url = match get_database_url_from_env() {
|
||||||
|
Ok(url) => url,
|
||||||
|
Err(e) => panic!("Failed to read database URL from env var LEMMY_DATABASE_URL: {}", e),
|
||||||
|
};
|
||||||
|
PgConnection::establish(&db_url).unwrap_or_else(|_| panic!("Error connecting to {}", db_url))
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_fuzzy_search() {
|
fn test_fuzzy_search() {
|
||||||
let test = "This is a fuzzy search";
|
let test = "This is a fuzzy search";
|
|
@ -1,5 +1,4 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
db::Crud,
|
|
||||||
schema::{
|
schema::{
|
||||||
mod_add,
|
mod_add,
|
||||||
mod_add_community,
|
mod_add_community,
|
||||||
|
@ -11,6 +10,7 @@ use crate::{
|
||||||
mod_remove_post,
|
mod_remove_post,
|
||||||
mod_sticky_post,
|
mod_sticky_post,
|
||||||
},
|
},
|
||||||
|
Crud,
|
||||||
};
|
};
|
||||||
use diesel::{dsl::*, result::Error, *};
|
use diesel::{dsl::*, result::Error, *};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -437,11 +437,16 @@ impl Crud<ModAddForm> for ModAdd {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{
|
use crate::{
|
||||||
super::{comment::*, community::*, post::*, user::*},
|
comment::*,
|
||||||
*,
|
community::*,
|
||||||
|
moderator::*,
|
||||||
|
post::*,
|
||||||
|
tests::establish_unpooled_connection,
|
||||||
|
user::*,
|
||||||
|
ListingType,
|
||||||
|
SortType,
|
||||||
};
|
};
|
||||||
use crate::db::{establish_unpooled_connection, ListingType, SortType};
|
|
||||||
|
|
||||||
// use Crud;
|
// use Crud;
|
||||||
#[test]
|
#[test]
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::db::limit_and_offset;
|
use crate::limit_and_offset;
|
||||||
use diesel::{result::Error, *};
|
use diesel::{result::Error, *};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
db::Crud,
|
|
||||||
schema::{password_reset_request, password_reset_request::dsl::*},
|
schema::{password_reset_request, password_reset_request::dsl::*},
|
||||||
|
Crud,
|
||||||
};
|
};
|
||||||
use diesel::{dsl::*, result::Error, *};
|
use diesel::{dsl::*, result::Error, *};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
|
@ -82,7 +82,7 @@ impl PasswordResetRequest {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{super::user::*, *};
|
use super::{super::user::*, *};
|
||||||
use crate::db::{establish_unpooled_connection, ListingType, SortType};
|
use crate::{tests::establish_unpooled_connection, ListingType, SortType};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_crud() {
|
fn test_crud() {
|
|
@ -1,8 +1,10 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
apub::{make_apub_endpoint, EndpointType},
|
|
||||||
db::{Crud, Likeable, Readable, Saveable},
|
|
||||||
naive_now,
|
naive_now,
|
||||||
schema::{post, post_like, post_read, post_saved},
|
schema::{post, post_like, post_read, post_saved},
|
||||||
|
Crud,
|
||||||
|
Likeable,
|
||||||
|
Readable,
|
||||||
|
Saveable,
|
||||||
};
|
};
|
||||||
use diesel::{dsl::*, result::Error, *};
|
use diesel::{dsl::*, result::Error, *};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -75,12 +77,11 @@ impl Post {
|
||||||
post.filter(ap_id.eq(object_id)).first::<Self>(conn)
|
post.filter(ap_id.eq(object_id)).first::<Self>(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_ap_id(conn: &PgConnection, post_id: i32) -> Result<Self, Error> {
|
pub fn update_ap_id(conn: &PgConnection, post_id: i32, apub_id: String) -> Result<Self, Error> {
|
||||||
use crate::schema::post::dsl::*;
|
use crate::schema::post::dsl::*;
|
||||||
|
|
||||||
let apid = make_apub_endpoint(EndpointType::Post, &post_id.to_string()).to_string();
|
|
||||||
diesel::update(post.find(post_id))
|
diesel::update(post.find(post_id))
|
||||||
.set(ap_id.eq(apid))
|
.set(ap_id.eq(apub_id))
|
||||||
.get_result::<Self>(conn)
|
.get_result::<Self>(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,11 +242,14 @@ impl Readable<PostReadForm> for PostRead {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{
|
use crate::{
|
||||||
super::{community::*, user::*},
|
community::*,
|
||||||
*,
|
post::*,
|
||||||
|
tests::establish_unpooled_connection,
|
||||||
|
user::*,
|
||||||
|
ListingType,
|
||||||
|
SortType,
|
||||||
};
|
};
|
||||||
use crate::db::{establish_unpooled_connection, ListingType, SortType};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_crud() {
|
fn test_crud() {
|
|
@ -1,5 +1,5 @@
|
||||||
use super::post_view::post_fast_view::BoxedQuery;
|
use super::post_view::post_fast_view::BoxedQuery;
|
||||||
use crate::db::{fuzzy_search, limit_and_offset, ListingType, MaybeOptional, SortType};
|
use crate::{fuzzy_search, limit_and_offset, ListingType, MaybeOptional, SortType};
|
||||||
use diesel::{dsl::*, pg::Pg, result::Error, *};
|
use diesel::{dsl::*, pg::Pg, result::Error, *};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@ table! {
|
||||||
creator_actor_id -> Text,
|
creator_actor_id -> Text,
|
||||||
creator_local -> Bool,
|
creator_local -> Bool,
|
||||||
creator_name -> Varchar,
|
creator_name -> Varchar,
|
||||||
|
creator_published -> Timestamp,
|
||||||
creator_avatar -> Nullable<Text>,
|
creator_avatar -> Nullable<Text>,
|
||||||
banned -> Bool,
|
banned -> Bool,
|
||||||
banned_from_community -> Bool,
|
banned_from_community -> Bool,
|
||||||
|
@ -75,6 +76,7 @@ table! {
|
||||||
creator_actor_id -> Text,
|
creator_actor_id -> Text,
|
||||||
creator_local -> Bool,
|
creator_local -> Bool,
|
||||||
creator_name -> Varchar,
|
creator_name -> Varchar,
|
||||||
|
creator_published -> Timestamp,
|
||||||
creator_avatar -> Nullable<Text>,
|
creator_avatar -> Nullable<Text>,
|
||||||
banned -> Bool,
|
banned -> Bool,
|
||||||
banned_from_community -> Bool,
|
banned_from_community -> Bool,
|
||||||
|
@ -125,6 +127,7 @@ pub struct PostView {
|
||||||
pub creator_actor_id: String,
|
pub creator_actor_id: String,
|
||||||
pub creator_local: bool,
|
pub creator_local: bool,
|
||||||
pub creator_name: String,
|
pub creator_name: String,
|
||||||
|
pub creator_published: chrono::NaiveDateTime,
|
||||||
pub creator_avatar: Option<String>,
|
pub creator_avatar: Option<String>,
|
||||||
pub banned: bool,
|
pub banned: bool,
|
||||||
pub banned_from_community: bool,
|
pub banned_from_community: bool,
|
||||||
|
@ -155,6 +158,7 @@ pub struct PostQueryBuilder<'a> {
|
||||||
my_user_id: Option<i32>,
|
my_user_id: Option<i32>,
|
||||||
for_creator_id: Option<i32>,
|
for_creator_id: Option<i32>,
|
||||||
for_community_id: Option<i32>,
|
for_community_id: Option<i32>,
|
||||||
|
for_community_name: Option<String>,
|
||||||
search_term: Option<String>,
|
search_term: Option<String>,
|
||||||
url_search: Option<String>,
|
url_search: Option<String>,
|
||||||
show_nsfw: bool,
|
show_nsfw: bool,
|
||||||
|
@ -178,6 +182,7 @@ impl<'a> PostQueryBuilder<'a> {
|
||||||
my_user_id: None,
|
my_user_id: None,
|
||||||
for_creator_id: None,
|
for_creator_id: None,
|
||||||
for_community_id: None,
|
for_community_id: None,
|
||||||
|
for_community_name: None,
|
||||||
search_term: None,
|
search_term: None,
|
||||||
url_search: None,
|
url_search: None,
|
||||||
show_nsfw: true,
|
show_nsfw: true,
|
||||||
|
@ -203,6 +208,11 @@ impl<'a> PostQueryBuilder<'a> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn for_community_name<T: MaybeOptional<String>>(mut self, for_community_name: T) -> Self {
|
||||||
|
self.for_community_name = for_community_name.get_optional();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn for_creator_id<T: MaybeOptional<i32>>(mut self, for_creator_id: T) -> Self {
|
pub fn for_creator_id<T: MaybeOptional<i32>>(mut self, for_creator_id: T) -> Self {
|
||||||
self.for_creator_id = for_creator_id.get_optional();
|
self.for_creator_id = for_creator_id.get_optional();
|
||||||
self
|
self
|
||||||
|
@ -262,6 +272,11 @@ impl<'a> PostQueryBuilder<'a> {
|
||||||
query = query.then_order_by(stickied.desc());
|
query = query.then_order_by(stickied.desc());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(for_community_name) = self.for_community_name {
|
||||||
|
query = query.filter(community_name.eq(for_community_name));
|
||||||
|
query = query.then_order_by(stickied.desc());
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(url_search) = self.url_search {
|
if let Some(url_search) = self.url_search {
|
||||||
query = query.filter(url.eq(url_search));
|
query = query.filter(url.eq(url_search));
|
||||||
}
|
}
|
||||||
|
@ -364,11 +379,16 @@ impl PostView {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{
|
use crate::{
|
||||||
super::{community::*, post::*, user::*},
|
community::*,
|
||||||
|
post::*,
|
||||||
|
post_view::*,
|
||||||
|
tests::establish_unpooled_connection,
|
||||||
|
user::*,
|
||||||
|
Crud,
|
||||||
|
Likeable,
|
||||||
*,
|
*,
|
||||||
};
|
};
|
||||||
use crate::db::{establish_unpooled_connection, Crud, Likeable};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_crud() {
|
fn test_crud() {
|
||||||
|
@ -499,6 +519,7 @@ mod tests {
|
||||||
body: None,
|
body: None,
|
||||||
creator_id: inserted_user.id,
|
creator_id: inserted_user.id,
|
||||||
creator_name: user_name.to_owned(),
|
creator_name: user_name.to_owned(),
|
||||||
|
creator_published: inserted_user.published,
|
||||||
creator_avatar: None,
|
creator_avatar: None,
|
||||||
banned: false,
|
banned: false,
|
||||||
banned_from_community: false,
|
banned_from_community: false,
|
||||||
|
@ -548,6 +569,7 @@ mod tests {
|
||||||
stickied: false,
|
stickied: false,
|
||||||
creator_id: inserted_user.id,
|
creator_id: inserted_user.id,
|
||||||
creator_name: user_name,
|
creator_name: user_name,
|
||||||
|
creator_published: inserted_user.published,
|
||||||
creator_avatar: None,
|
creator_avatar: None,
|
||||||
banned: false,
|
banned: false,
|
||||||
banned_from_community: false,
|
banned_from_community: false,
|
|
@ -1,8 +1,4 @@
|
||||||
use crate::{
|
use crate::{schema::private_message, Crud};
|
||||||
apub::{make_apub_endpoint, EndpointType},
|
|
||||||
db::Crud,
|
|
||||||
schema::private_message,
|
|
||||||
};
|
|
||||||
use diesel::{dsl::*, result::Error, *};
|
use diesel::{dsl::*, result::Error, *};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -66,16 +62,15 @@ impl Crud<PrivateMessageForm> for PrivateMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PrivateMessage {
|
impl PrivateMessage {
|
||||||
pub fn update_ap_id(conn: &PgConnection, private_message_id: i32) -> Result<Self, Error> {
|
pub fn update_ap_id(
|
||||||
|
conn: &PgConnection,
|
||||||
|
private_message_id: i32,
|
||||||
|
apub_id: String,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
use crate::schema::private_message::dsl::*;
|
use crate::schema::private_message::dsl::*;
|
||||||
|
|
||||||
let apid = make_apub_endpoint(
|
|
||||||
EndpointType::PrivateMessage,
|
|
||||||
&private_message_id.to_string(),
|
|
||||||
)
|
|
||||||
.to_string();
|
|
||||||
diesel::update(private_message.find(private_message_id))
|
diesel::update(private_message.find(private_message_id))
|
||||||
.set(ap_id.eq(apid))
|
.set(ap_id.eq(apub_id))
|
||||||
.get_result::<Self>(conn)
|
.get_result::<Self>(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,8 +84,13 @@ impl PrivateMessage {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{super::user::*, *};
|
use crate::{
|
||||||
use crate::db::{establish_unpooled_connection, ListingType, SortType};
|
private_message::*,
|
||||||
|
tests::establish_unpooled_connection,
|
||||||
|
user::*,
|
||||||
|
ListingType,
|
||||||
|
SortType,
|
||||||
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_crud() {
|
fn test_crud() {
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::db::{limit_and_offset, MaybeOptional};
|
use crate::{limit_and_offset, MaybeOptional};
|
||||||
use diesel::{pg::Pg, result::Error, *};
|
use diesel::{pg::Pg, result::Error, *};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
|
@ -47,6 +47,7 @@ table! {
|
||||||
deleted -> Nullable<Bool>,
|
deleted -> Nullable<Bool>,
|
||||||
ap_id -> Nullable<Varchar>,
|
ap_id -> Nullable<Varchar>,
|
||||||
local -> Nullable<Bool>,
|
local -> Nullable<Bool>,
|
||||||
|
post_name -> Nullable<Varchar>,
|
||||||
community_id -> Nullable<Int4>,
|
community_id -> Nullable<Int4>,
|
||||||
community_actor_id -> Nullable<Varchar>,
|
community_actor_id -> Nullable<Varchar>,
|
||||||
community_local -> Nullable<Bool>,
|
community_local -> Nullable<Bool>,
|
||||||
|
@ -56,6 +57,7 @@ table! {
|
||||||
creator_actor_id -> Nullable<Varchar>,
|
creator_actor_id -> Nullable<Varchar>,
|
||||||
creator_local -> Nullable<Bool>,
|
creator_local -> Nullable<Bool>,
|
||||||
creator_name -> Nullable<Varchar>,
|
creator_name -> Nullable<Varchar>,
|
||||||
|
creator_published -> Nullable<Timestamp>,
|
||||||
creator_avatar -> Nullable<Text>,
|
creator_avatar -> Nullable<Text>,
|
||||||
score -> Nullable<Int8>,
|
score -> Nullable<Int8>,
|
||||||
upvotes -> Nullable<Int8>,
|
upvotes -> Nullable<Int8>,
|
||||||
|
@ -317,6 +319,7 @@ table! {
|
||||||
creator_actor_id -> Nullable<Varchar>,
|
creator_actor_id -> Nullable<Varchar>,
|
||||||
creator_local -> Nullable<Bool>,
|
creator_local -> Nullable<Bool>,
|
||||||
creator_name -> Nullable<Varchar>,
|
creator_name -> Nullable<Varchar>,
|
||||||
|
creator_published -> Nullable<Timestamp>,
|
||||||
creator_avatar -> Nullable<Text>,
|
creator_avatar -> Nullable<Text>,
|
||||||
banned -> Nullable<Bool>,
|
banned -> Nullable<Bool>,
|
||||||
banned_from_community -> Nullable<Bool>,
|
banned_from_community -> Nullable<Bool>,
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{db::Crud, schema::site};
|
use crate::{schema::site, Crud};
|
||||||
use diesel::{dsl::*, result::Error, *};
|
use diesel::{dsl::*, result::Error, *};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
|
@ -1,14 +1,10 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
db::Crud,
|
|
||||||
is_email_regex,
|
|
||||||
naive_now,
|
naive_now,
|
||||||
schema::{user_, user_::dsl::*},
|
schema::{user_, user_::dsl::*},
|
||||||
settings::Settings,
|
Crud,
|
||||||
};
|
};
|
||||||
use bcrypt::{hash, DEFAULT_COST};
|
use bcrypt::{hash, DEFAULT_COST};
|
||||||
use diesel::{dsl::*, result::Error, *};
|
use diesel::{dsl::*, result::Error, *};
|
||||||
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, TokenData, Validation};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Clone, Queryable, Identifiable, PartialEq, Debug)]
|
#[derive(Clone, Queryable, Identifiable, PartialEq, Debug)]
|
||||||
#[table_name = "user_"]
|
#[table_name = "user_"]
|
||||||
|
@ -131,90 +127,23 @@ impl User_ {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct Claims {
|
|
||||||
pub id: i32,
|
|
||||||
pub username: String,
|
|
||||||
pub iss: String,
|
|
||||||
pub show_nsfw: bool,
|
|
||||||
pub theme: String,
|
|
||||||
pub default_sort_type: i16,
|
|
||||||
pub default_listing_type: i16,
|
|
||||||
pub lang: String,
|
|
||||||
pub avatar: Option<String>,
|
|
||||||
pub show_avatars: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Claims {
|
|
||||||
pub fn decode(jwt: &str) -> Result<TokenData<Claims>, jsonwebtoken::errors::Error> {
|
|
||||||
let v = Validation {
|
|
||||||
validate_exp: false,
|
|
||||||
..Validation::default()
|
|
||||||
};
|
|
||||||
decode::<Claims>(
|
|
||||||
&jwt,
|
|
||||||
&DecodingKey::from_secret(Settings::get().jwt_secret.as_ref()),
|
|
||||||
&v,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Jwt = String;
|
|
||||||
impl User_ {
|
impl User_ {
|
||||||
pub fn jwt(&self) -> Jwt {
|
pub fn find_by_username(conn: &PgConnection, username: &str) -> Result<User_, Error> {
|
||||||
let my_claims = Claims {
|
|
||||||
id: self.id,
|
|
||||||
username: self.name.to_owned(),
|
|
||||||
iss: Settings::get().hostname,
|
|
||||||
show_nsfw: self.show_nsfw,
|
|
||||||
theme: self.theme.to_owned(),
|
|
||||||
default_sort_type: self.default_sort_type,
|
|
||||||
default_listing_type: self.default_listing_type,
|
|
||||||
lang: self.lang.to_owned(),
|
|
||||||
avatar: self.avatar.to_owned(),
|
|
||||||
show_avatars: self.show_avatars.to_owned(),
|
|
||||||
};
|
|
||||||
encode(
|
|
||||||
&Header::default(),
|
|
||||||
&my_claims,
|
|
||||||
&EncodingKey::from_secret(Settings::get().jwt_secret.as_ref()),
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn find_by_username(conn: &PgConnection, username: &str) -> Result<Self, Error> {
|
|
||||||
user_.filter(name.eq(username)).first::<User_>(conn)
|
user_.filter(name.eq(username)).first::<User_>(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_email(conn: &PgConnection, from_email: &str) -> Result<Self, Error> {
|
pub fn find_by_email(conn: &PgConnection, from_email: &str) -> Result<User_, Error> {
|
||||||
user_.filter(email.eq(from_email)).first::<User_>(conn)
|
user_.filter(email.eq(from_email)).first::<User_>(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_email_or_username(
|
pub fn get_profile_url(&self, hostname: &str) -> String {
|
||||||
conn: &PgConnection,
|
format!("https://{}/u/{}", hostname, self.name)
|
||||||
username_or_email: &str,
|
|
||||||
) -> Result<Self, Error> {
|
|
||||||
if is_email_regex(username_or_email) {
|
|
||||||
User_::find_by_email(conn, username_or_email)
|
|
||||||
} else {
|
|
||||||
User_::find_by_username(conn, username_or_email)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_profile_url(&self) -> String {
|
|
||||||
format!("https://{}/u/{}", Settings::get().hostname, self.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn find_by_jwt(conn: &PgConnection, jwt: &str) -> Result<Self, Error> {
|
|
||||||
let claims: Claims = Claims::decode(&jwt).expect("Invalid token").claims;
|
|
||||||
Self::read(&conn, claims.id)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{User_, *};
|
use crate::{tests::establish_unpooled_connection, user::*, ListingType, SortType};
|
||||||
use crate::db::{establish_unpooled_connection, ListingType, SortType};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_crud() {
|
fn test_crud() {
|
|
@ -1,5 +1,5 @@
|
||||||
use super::comment::Comment;
|
use super::comment::Comment;
|
||||||
use crate::{db::Crud, schema::user_mention};
|
use crate::{schema::user_mention, Crud};
|
||||||
use diesel::{dsl::*, result::Error, *};
|
use diesel::{dsl::*, result::Error, *};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -54,11 +54,16 @@ impl Crud<UserMentionForm> for UserMention {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{
|
use crate::{
|
||||||
super::{comment::*, community::*, post::*, user::*},
|
comment::*,
|
||||||
*,
|
community::*,
|
||||||
|
post::*,
|
||||||
|
tests::establish_unpooled_connection,
|
||||||
|
user::*,
|
||||||
|
user_mention::*,
|
||||||
|
ListingType,
|
||||||
|
SortType,
|
||||||
};
|
};
|
||||||
use crate::db::{establish_unpooled_connection, ListingType, SortType};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_crud() {
|
fn test_crud() {
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::db::{limit_and_offset, MaybeOptional, SortType};
|
use crate::{limit_and_offset, MaybeOptional, SortType};
|
||||||
use diesel::{dsl::*, pg::Pg, result::Error, *};
|
use diesel::{dsl::*, pg::Pg, result::Error, *};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ table! {
|
||||||
creator_actor_id -> Text,
|
creator_actor_id -> Text,
|
||||||
creator_local -> Bool,
|
creator_local -> Bool,
|
||||||
post_id -> Int4,
|
post_id -> Int4,
|
||||||
|
post_name -> Varchar,
|
||||||
parent_id -> Nullable<Int4>,
|
parent_id -> Nullable<Int4>,
|
||||||
content -> Text,
|
content -> Text,
|
||||||
removed -> Bool,
|
removed -> Bool,
|
||||||
|
@ -47,6 +48,7 @@ table! {
|
||||||
creator_actor_id -> Text,
|
creator_actor_id -> Text,
|
||||||
creator_local -> Bool,
|
creator_local -> Bool,
|
||||||
post_id -> Int4,
|
post_id -> Int4,
|
||||||
|
post_name -> Varchar,
|
||||||
parent_id -> Nullable<Int4>,
|
parent_id -> Nullable<Int4>,
|
||||||
content -> Text,
|
content -> Text,
|
||||||
removed -> Bool,
|
removed -> Bool,
|
||||||
|
@ -86,6 +88,7 @@ pub struct UserMentionView {
|
||||||
pub creator_actor_id: String,
|
pub creator_actor_id: String,
|
||||||
pub creator_local: bool,
|
pub creator_local: bool,
|
||||||
pub post_id: i32,
|
pub post_id: i32,
|
||||||
|
pub post_name: String,
|
||||||
pub parent_id: Option<i32>,
|
pub parent_id: Option<i32>,
|
||||||
pub content: String,
|
pub content: String,
|
||||||
pub removed: bool,
|
pub removed: bool,
|
|
@ -1,5 +1,5 @@
|
||||||
use super::user_view::user_fast::BoxedQuery;
|
use super::user_view::user_fast::BoxedQuery;
|
||||||
use crate::db::{fuzzy_search, limit_and_offset, MaybeOptional, SortType};
|
use crate::{fuzzy_search, limit_and_offset, MaybeOptional, SortType};
|
||||||
use diesel::{dsl::*, pg::Pg, result::Error, *};
|
use diesel::{dsl::*, pg::Pg, result::Error, *};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -157,7 +157,10 @@ impl UserView {
|
||||||
|
|
||||||
pub fn admins(conn: &PgConnection) -> Result<Vec<Self>, Error> {
|
pub fn admins(conn: &PgConnection) -> Result<Vec<Self>, Error> {
|
||||||
use super::user_view::user_fast::dsl::*;
|
use super::user_view::user_fast::dsl::*;
|
||||||
user_fast.filter(admin.eq(true)).load::<Self>(conn)
|
user_fast
|
||||||
|
.filter(admin.eq(true))
|
||||||
|
.order_by(published)
|
||||||
|
.load::<Self>(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn banned(conn: &PgConnection) -> Result<Vec<Self>, Error> {
|
pub fn banned(conn: &PgConnection) -> Result<Vec<Self>, Error> {
|
22
server/lemmy_utils/Cargo.toml
vendored
Normal file
22
server/lemmy_utils/Cargo.toml
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
[package]
|
||||||
|
name = "lemmy_utils"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
regex = "1.3.5"
|
||||||
|
config = { version = "0.10.1", default-features = false, features = ["hjson"] }
|
||||||
|
chrono = { version = "0.4.7", features = ["serde"] }
|
||||||
|
lettre = "0.9.3"
|
||||||
|
lettre_email = "0.9.4"
|
||||||
|
log = "0.4.0"
|
||||||
|
itertools = "0.9.0"
|
||||||
|
rand = "0.7.3"
|
||||||
|
serde = { version = "1.0.105", features = ["derive"] }
|
||||||
|
serde_json = { version = "1.0.52", features = ["preserve_order"]}
|
||||||
|
comrak = "0.7"
|
||||||
|
lazy_static = "1.3.0"
|
||||||
|
openssl = "0.10"
|
||||||
|
url = { version = "2.1.1", features = ["serde"] }
|
337
server/lemmy_utils/src/lib.rs
Normal file
337
server/lemmy_utils/src/lib.rs
Normal file
|
@ -0,0 +1,337 @@
|
||||||
|
#[macro_use]
|
||||||
|
pub extern crate lazy_static;
|
||||||
|
pub extern crate comrak;
|
||||||
|
pub extern crate lettre;
|
||||||
|
pub extern crate lettre_email;
|
||||||
|
pub extern crate openssl;
|
||||||
|
pub extern crate rand;
|
||||||
|
pub extern crate regex;
|
||||||
|
pub extern crate serde_json;
|
||||||
|
pub extern crate url;
|
||||||
|
|
||||||
|
pub mod settings;
|
||||||
|
|
||||||
|
use crate::settings::Settings;
|
||||||
|
use chrono::{DateTime, FixedOffset, Local, NaiveDateTime, Utc};
|
||||||
|
use itertools::Itertools;
|
||||||
|
use lettre::{
|
||||||
|
smtp::{
|
||||||
|
authentication::{Credentials, Mechanism},
|
||||||
|
extension::ClientId,
|
||||||
|
ConnectionReuseParameters,
|
||||||
|
},
|
||||||
|
ClientSecurity,
|
||||||
|
SmtpClient,
|
||||||
|
Transport,
|
||||||
|
};
|
||||||
|
use lettre_email::Email;
|
||||||
|
use openssl::{pkey::PKey, rsa::Rsa};
|
||||||
|
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||||
|
use regex::{Regex, RegexBuilder};
|
||||||
|
use std::io::{Error, ErrorKind};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
pub fn to_datetime_utc(ndt: NaiveDateTime) -> DateTime<Utc> {
|
||||||
|
DateTime::<Utc>::from_utc(ndt, Utc)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn naive_from_unix(time: i64) -> NaiveDateTime {
|
||||||
|
NaiveDateTime::from_timestamp(time, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn convert_datetime(datetime: NaiveDateTime) -> DateTime<FixedOffset> {
|
||||||
|
let now = Local::now();
|
||||||
|
DateTime::<FixedOffset>::from_utc(datetime, *now.offset())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_email_regex(test: &str) -> bool {
|
||||||
|
EMAIL_REGEX.is_match(test)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_slurs(test: &str) -> String {
|
||||||
|
SLUR_REGEX.replace_all(test, "*removed*").to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn slur_check(test: &str) -> Result<(), Vec<&str>> {
|
||||||
|
let mut matches: Vec<&str> = SLUR_REGEX.find_iter(test).map(|mat| mat.as_str()).collect();
|
||||||
|
|
||||||
|
// Unique
|
||||||
|
matches.sort_unstable();
|
||||||
|
matches.dedup();
|
||||||
|
|
||||||
|
if matches.is_empty() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(matches)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn slurs_vec_to_str(slurs: Vec<&str>) -> String {
|
||||||
|
let start = "No slurs - ";
|
||||||
|
let combined = &slurs.join(", ");
|
||||||
|
[start, combined].concat()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_random_string() -> String {
|
||||||
|
thread_rng().sample_iter(&Alphanumeric).take(30).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_email(
|
||||||
|
subject: &str,
|
||||||
|
to_email: &str,
|
||||||
|
to_username: &str,
|
||||||
|
html: &str,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let email_config = Settings::get().email.ok_or("no_email_setup")?;
|
||||||
|
|
||||||
|
let email = Email::builder()
|
||||||
|
.to((to_email, to_username))
|
||||||
|
.from(email_config.smtp_from_address.to_owned())
|
||||||
|
.subject(subject)
|
||||||
|
.html(html)
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mailer = if email_config.use_tls {
|
||||||
|
SmtpClient::new_simple(&email_config.smtp_server).unwrap()
|
||||||
|
} else {
|
||||||
|
SmtpClient::new(&email_config.smtp_server, ClientSecurity::None).unwrap()
|
||||||
|
}
|
||||||
|
.hello_name(ClientId::Domain(Settings::get().hostname))
|
||||||
|
.smtp_utf8(true)
|
||||||
|
.authentication_mechanism(Mechanism::Plain)
|
||||||
|
.connection_reuse(ConnectionReuseParameters::ReuseUnlimited);
|
||||||
|
let mailer = if let (Some(login), Some(password)) =
|
||||||
|
(&email_config.smtp_login, &email_config.smtp_password)
|
||||||
|
{
|
||||||
|
mailer.credentials(Credentials::new(login.to_owned(), password.to_owned()))
|
||||||
|
} else {
|
||||||
|
mailer
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut transport = mailer.transport();
|
||||||
|
let result = transport.send(email.into());
|
||||||
|
transport.close();
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(e) => Err(e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn markdown_to_html(text: &str) -> String {
|
||||||
|
comrak::markdown_to_html(text, &comrak::ComrakOptions::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO nothing is done with community / group webfingers yet, so just ignore those for now
|
||||||
|
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct MentionData {
|
||||||
|
pub name: String,
|
||||||
|
pub domain: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MentionData {
|
||||||
|
pub fn is_local(&self) -> bool {
|
||||||
|
Settings::get().hostname.eq(&self.domain)
|
||||||
|
}
|
||||||
|
pub fn full_name(&self) -> String {
|
||||||
|
format!("@{}@{}", &self.name, &self.domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scrape_text_for_mentions(text: &str) -> Vec<MentionData> {
|
||||||
|
let mut out: Vec<MentionData> = Vec::new();
|
||||||
|
for caps in MENTIONS_REGEX.captures_iter(text) {
|
||||||
|
out.push(MentionData {
|
||||||
|
name: caps["name"].to_string(),
|
||||||
|
domain: caps["domain"].to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
out.into_iter().unique().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_valid_username(name: &str) -> bool {
|
||||||
|
VALID_USERNAME_REGEX.is_match(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_valid_community_name(name: &str) -> bool {
|
||||||
|
VALID_COMMUNITY_NAME_REGEX.is_match(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_valid_post_title(title: &str) -> bool {
|
||||||
|
VALID_POST_TITLE_REGEX.is_match(title)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::{
|
||||||
|
is_email_regex,
|
||||||
|
is_valid_community_name,
|
||||||
|
is_valid_post_title,
|
||||||
|
is_valid_username,
|
||||||
|
remove_slurs,
|
||||||
|
scrape_text_for_mentions,
|
||||||
|
slur_check,
|
||||||
|
slurs_vec_to_str,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mentions_regex() {
|
||||||
|
let text = "Just read a great blog post by [@tedu@honk.teduangst.com](/u/test). And another by !test_community@fish.teduangst.com . Another [@lemmy@lemmy-alpha:8540](/u/fish)";
|
||||||
|
let mentions = scrape_text_for_mentions(text);
|
||||||
|
|
||||||
|
assert_eq!(mentions[0].name, "tedu".to_string());
|
||||||
|
assert_eq!(mentions[0].domain, "honk.teduangst.com".to_string());
|
||||||
|
assert_eq!(mentions[1].domain, "lemmy-alpha:8540".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_email() {
|
||||||
|
assert!(is_email_regex("gush@gmail.com"));
|
||||||
|
assert!(!is_email_regex("nada_neutho"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_valid_register_username() {
|
||||||
|
assert!(is_valid_username("Hello_98"));
|
||||||
|
assert!(is_valid_username("ten"));
|
||||||
|
assert!(!is_valid_username("Hello-98"));
|
||||||
|
assert!(!is_valid_username("a"));
|
||||||
|
assert!(!is_valid_username(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_valid_community_name() {
|
||||||
|
assert!(is_valid_community_name("example"));
|
||||||
|
assert!(is_valid_community_name("example_community"));
|
||||||
|
assert!(!is_valid_community_name("Example"));
|
||||||
|
assert!(!is_valid_community_name("Ex"));
|
||||||
|
assert!(!is_valid_community_name(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_valid_post_title() {
|
||||||
|
assert!(is_valid_post_title("Post Title"));
|
||||||
|
assert!(is_valid_post_title(" POST TITLE 😃😃😃😃😃"));
|
||||||
|
assert!(!is_valid_post_title("\n \n \n \n ")); // tabs/spaces/newlines
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_slur_filter() {
|
||||||
|
let test =
|
||||||
|
"coons test dindu ladyboy tranny retardeds. Capitalized Niggerz. This is a bunch of other safe text.";
|
||||||
|
let slur_free = "No slurs here";
|
||||||
|
assert_eq!(
|
||||||
|
remove_slurs(&test),
|
||||||
|
"*removed* test *removed* *removed* *removed* *removed*. Capitalized *removed*. This is a bunch of other safe text."
|
||||||
|
.to_string()
|
||||||
|
);
|
||||||
|
|
||||||
|
let has_slurs_vec = vec![
|
||||||
|
"Niggerz",
|
||||||
|
"coons",
|
||||||
|
"dindu",
|
||||||
|
"ladyboy",
|
||||||
|
"retardeds",
|
||||||
|
"tranny",
|
||||||
|
];
|
||||||
|
let has_slurs_err_str = "No slurs - Niggerz, coons, dindu, ladyboy, retardeds, tranny";
|
||||||
|
|
||||||
|
assert_eq!(slur_check(test), Err(has_slurs_vec));
|
||||||
|
assert_eq!(slur_check(slur_free), Ok(()));
|
||||||
|
if let Err(slur_vec) = slur_check(test) {
|
||||||
|
assert_eq!(&slurs_vec_to_str(slur_vec), has_slurs_err_str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// These helped with testing
|
||||||
|
// #[test]
|
||||||
|
// fn test_send_email() {
|
||||||
|
// let result = send_email("not a subject", "test_email@gmail.com", "ur user", "<h1>HI there</h1>");
|
||||||
|
// assert!(result.is_ok());
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref EMAIL_REGEX: Regex = Regex::new(r"^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$").unwrap();
|
||||||
|
static ref SLUR_REGEX: Regex = RegexBuilder::new(r"(fag(g|got|tard)?|maricos?|cock\s?sucker(s|ing)?|\bn(i|1)g(\b|g?(a|er)?(s|z)?)\b|dindu(s?)|mudslime?s?|kikes?|mongoloids?|towel\s*heads?|\bspi(c|k)s?\b|\bchinks?|niglets?|beaners?|\bnips?\b|\bcoons?\b|jungle\s*bunn(y|ies?)|jigg?aboo?s?|\bpakis?\b|rag\s*heads?|gooks?|cunts?|bitch(es|ing|y)?|puss(y|ies?)|twats?|feminazis?|whor(es?|ing)|\bslut(s|t?y)?|\btr(a|@)nn?(y|ies?)|ladyboy(s?)|\b(b|re|r)tard(ed)?s?)").case_insensitive(true).build().unwrap();
|
||||||
|
static ref USERNAME_MATCHES_REGEX: Regex = Regex::new(r"/u/[a-zA-Z][0-9a-zA-Z_]*").unwrap();
|
||||||
|
// TODO keep this old one, it didn't work with port well tho
|
||||||
|
// static ref MENTIONS_REGEX: Regex = Regex::new(r"@(?P<name>[\w.]+)@(?P<domain>[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+)").unwrap();
|
||||||
|
static ref MENTIONS_REGEX: Regex = Regex::new(r"@(?P<name>[\w.]+)@(?P<domain>[a-zA-Z0-9._:-]+)").unwrap();
|
||||||
|
static ref VALID_USERNAME_REGEX: Regex = Regex::new(r"^[a-zA-Z0-9_]{3,20}$").unwrap();
|
||||||
|
static ref VALID_COMMUNITY_NAME_REGEX: Regex = Regex::new(r"^[a-z0-9_]{3,20}$").unwrap();
|
||||||
|
static ref VALID_POST_TITLE_REGEX: Regex = Regex::new(r".*\S.*").unwrap();
|
||||||
|
pub static ref WEBFINGER_COMMUNITY_REGEX: Regex = Regex::new(&format!(
|
||||||
|
"^group:([a-z0-9_]{{3, 20}})@{}$",
|
||||||
|
Settings::get().hostname
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
pub static ref WEBFINGER_USER_REGEX: Regex = Regex::new(&format!(
|
||||||
|
"^acct:([a-z0-9_]{{3, 20}})@{}$",
|
||||||
|
Settings::get().hostname
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
pub static ref CACHE_CONTROL_REGEX: Regex =
|
||||||
|
Regex::new("^((text|image)/.+|application/javascript)$").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Keypair {
|
||||||
|
pub private_key: String,
|
||||||
|
pub public_key: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate the asymmetric keypair for ActivityPub HTTP signatures.
|
||||||
|
pub fn generate_actor_keypair() -> Result<Keypair, Error> {
|
||||||
|
let rsa = Rsa::generate(2048)?;
|
||||||
|
let pkey = PKey::from_rsa(rsa)?;
|
||||||
|
let public_key = pkey.public_key_to_pem()?;
|
||||||
|
let private_key = pkey.private_key_to_pem_pkcs8()?;
|
||||||
|
let key_to_string = |key| match String::from_utf8(key) {
|
||||||
|
Ok(s) => Ok(s),
|
||||||
|
Err(e) => Err(Error::new(
|
||||||
|
ErrorKind::Other,
|
||||||
|
format!("Failed converting key to string: {}", e),
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
Ok(Keypair {
|
||||||
|
private_key: key_to_string(private_key)?,
|
||||||
|
public_key: key_to_string(public_key)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum EndpointType {
|
||||||
|
Community,
|
||||||
|
User,
|
||||||
|
Post,
|
||||||
|
Comment,
|
||||||
|
PrivateMessage,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_apub_protocol_string() -> &'static str {
|
||||||
|
if Settings::get().federation.tls_enabled {
|
||||||
|
"https"
|
||||||
|
} else {
|
||||||
|
"http"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates the ActivityPub ID for a given object type and ID.
|
||||||
|
pub fn make_apub_endpoint(endpoint_type: EndpointType, name: &str) -> Url {
|
||||||
|
let point = match endpoint_type {
|
||||||
|
EndpointType::Community => "c",
|
||||||
|
EndpointType::User => "u",
|
||||||
|
EndpointType::Post => "post",
|
||||||
|
EndpointType::Comment => "comment",
|
||||||
|
EndpointType::PrivateMessage => "private_message",
|
||||||
|
};
|
||||||
|
|
||||||
|
Url::parse(&format!(
|
||||||
|
"{}://{}/{}/{}",
|
||||||
|
get_apub_protocol_string(),
|
||||||
|
Settings::get().hostname,
|
||||||
|
point,
|
||||||
|
name
|
||||||
|
))
|
||||||
|
.unwrap()
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::LemmyError;
|
|
||||||
use config::{Config, ConfigError, Environment, File};
|
use config::{Config, ConfigError, Environment, File};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{env, fs, net::IpAddr, sync::RwLock};
|
use std::{fs, io::Error, net::IpAddr, sync::RwLock};
|
||||||
|
use std::env;
|
||||||
|
|
||||||
static CONFIG_FILE_DEFAULTS: &str = "config/defaults.hjson";
|
static CONFIG_FILE_DEFAULTS: &str = "config/defaults.hjson";
|
||||||
static CONFIG_FILE: &str = "config/config.hjson";
|
static CONFIG_FILE: &str = "config/config.hjson";
|
||||||
|
@ -76,12 +76,15 @@ impl Settings {
|
||||||
/// First, defaults are loaded from CONFIG_FILE_DEFAULTS, then these values can be overwritten
|
/// First, defaults are loaded from CONFIG_FILE_DEFAULTS, then these values can be overwritten
|
||||||
/// from CONFIG_FILE (optional). Finally, values from the environment (with prefix LEMMY) are
|
/// from CONFIG_FILE (optional). Finally, values from the environment (with prefix LEMMY) are
|
||||||
/// added to the config.
|
/// added to the config.
|
||||||
|
///
|
||||||
|
/// Note: The env var `LEMMY_DATABASE_URL` is parsed in
|
||||||
|
/// `server/lemmy_db/src/lib.rs::get_database_url_from_env()`
|
||||||
fn init() -> Result<Self, ConfigError> {
|
fn init() -> Result<Self, ConfigError> {
|
||||||
let mut s = Config::new();
|
let mut s = Config::new();
|
||||||
|
|
||||||
s.merge(File::with_name(CONFIG_FILE_DEFAULTS))?;
|
s.merge(File::with_name(CONFIG_FILE_DEFAULTS))?;
|
||||||
|
|
||||||
s.merge(File::with_name(CONFIG_FILE).required(false))?;
|
s.merge(File::with_name(&Self::get_config_location()).required(false))?;
|
||||||
|
|
||||||
// Add in settings from the environment (with a prefix of LEMMY)
|
// Add in settings from the environment (with a prefix of LEMMY)
|
||||||
// Eg.. `LEMMY_DEBUG=1 ./target/app` would set the `debug` key
|
// Eg.. `LEMMY_DEBUG=1 ./target/app` would set the `debug` key
|
||||||
|
@ -98,32 +101,31 @@ impl Settings {
|
||||||
SETTINGS.read().unwrap().to_owned()
|
SETTINGS.read().unwrap().to_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the postgres connection url. If LEMMY_DATABASE_URL is set, that is used,
|
|
||||||
/// otherwise the connection url is generated from the config.
|
|
||||||
pub fn get_database_url(&self) -> String {
|
pub fn get_database_url(&self) -> String {
|
||||||
match env::var("LEMMY_DATABASE_URL") {
|
format!(
|
||||||
Ok(url) => url,
|
|
||||||
Err(_) => format!(
|
|
||||||
"postgres://{}:{}@{}:{}/{}",
|
"postgres://{}:{}@{}:{}/{}",
|
||||||
self.database.user,
|
self.database.user,
|
||||||
self.database.password,
|
self.database.password,
|
||||||
self.database.host,
|
self.database.host,
|
||||||
self.database.port,
|
self.database.port,
|
||||||
self.database.database
|
self.database.database
|
||||||
),
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn api_endpoint(&self) -> String {
|
pub fn api_endpoint(&self) -> String {
|
||||||
format!("{}/api/v1", self.hostname)
|
format!("{}/api/v1", self.hostname)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_config_file() -> Result<String, LemmyError> {
|
pub fn get_config_location() -> String {
|
||||||
Ok(fs::read_to_string(CONFIG_FILE)?)
|
env::var("LEMMY_CONFIG_LOCATION").unwrap_or_else(|_| CONFIG_FILE.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save_config_file(data: &str) -> Result<String, LemmyError> {
|
pub fn read_config_file() -> Result<String, Error> {
|
||||||
fs::write(CONFIG_FILE, data)?;
|
fs::read_to_string(Self::get_config_location())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_config_file(data: &str) -> Result<String, Error> {
|
||||||
|
fs::write(Self::get_config_location(), data)?;
|
||||||
|
|
||||||
// Reload the new settings
|
// Reload the new settings
|
||||||
// From https://stackoverflow.com/questions/29654927/how-do-i-assign-a-string-to-a-mutable-static-variable/47181804#47181804
|
// From https://stackoverflow.com/questions/29654927/how-do-i-assign-a-string-to-a-mutable-static-variable/47181804#47181804
|
388
server/migrations/2020-07-08-202609_add_creator_published/down.sql
vendored
Normal file
388
server/migrations/2020-07-08-202609_add_creator_published/down.sql
vendored
Normal file
|
@ -0,0 +1,388 @@
|
||||||
|
drop view user_mention_view;
|
||||||
|
drop view reply_fast_view;
|
||||||
|
drop view comment_fast_view;
|
||||||
|
drop view comment_view;
|
||||||
|
|
||||||
|
drop view user_mention_fast_view;
|
||||||
|
drop table comment_aggregates_fast;
|
||||||
|
drop view comment_aggregates_view;
|
||||||
|
|
||||||
|
create view comment_aggregates_view as
|
||||||
|
select
|
||||||
|
ct.*,
|
||||||
|
-- community details
|
||||||
|
p.community_id,
|
||||||
|
c.actor_id as community_actor_id,
|
||||||
|
c."local" as community_local,
|
||||||
|
c."name" as community_name,
|
||||||
|
-- creator details
|
||||||
|
u.banned as banned,
|
||||||
|
coalesce(cb.id, 0)::bool as banned_from_community,
|
||||||
|
u.actor_id as creator_actor_id,
|
||||||
|
u.local as creator_local,
|
||||||
|
u.name as creator_name,
|
||||||
|
u.avatar as creator_avatar,
|
||||||
|
-- score details
|
||||||
|
coalesce(cl.total, 0) as score,
|
||||||
|
coalesce(cl.up, 0) as upvotes,
|
||||||
|
coalesce(cl.down, 0) as downvotes,
|
||||||
|
hot_rank(coalesce(cl.total, 0), ct.published) as hot_rank
|
||||||
|
from comment ct
|
||||||
|
left join post p on ct.post_id = p.id
|
||||||
|
left join community c on p.community_id = c.id
|
||||||
|
left join user_ u on ct.creator_id = u.id
|
||||||
|
left join community_user_ban cb on ct.creator_id = cb.user_id and p.id = ct.post_id and p.community_id = cb.community_id
|
||||||
|
left join (
|
||||||
|
select
|
||||||
|
l.comment_id as id,
|
||||||
|
sum(l.score) as total,
|
||||||
|
count(case when l.score = 1 then 1 else null end) as up,
|
||||||
|
count(case when l.score = -1 then 1 else null end) as down
|
||||||
|
from comment_like l
|
||||||
|
group by comment_id
|
||||||
|
) as cl on cl.id = ct.id;
|
||||||
|
|
||||||
|
create or replace view comment_view as (
|
||||||
|
select
|
||||||
|
cav.*,
|
||||||
|
us.user_id as user_id,
|
||||||
|
us.my_vote as my_vote,
|
||||||
|
us.is_subbed::bool as subscribed,
|
||||||
|
us.is_saved::bool as saved
|
||||||
|
from comment_aggregates_view cav
|
||||||
|
cross join lateral (
|
||||||
|
select
|
||||||
|
u.id as user_id,
|
||||||
|
coalesce(cl.score, 0) as my_vote,
|
||||||
|
coalesce(cf.id, 0) as is_subbed,
|
||||||
|
coalesce(cs.id, 0) as is_saved
|
||||||
|
from user_ u
|
||||||
|
left join comment_like cl on u.id = cl.user_id and cav.id = cl.comment_id
|
||||||
|
left join comment_saved cs on u.id = cs.user_id and cs.comment_id = cav.id
|
||||||
|
left join community_follower cf on u.id = cf.user_id and cav.community_id = cf.community_id
|
||||||
|
) as us
|
||||||
|
|
||||||
|
union all
|
||||||
|
|
||||||
|
select
|
||||||
|
cav.*,
|
||||||
|
null as user_id,
|
||||||
|
null as my_vote,
|
||||||
|
null as subscribed,
|
||||||
|
null as saved
|
||||||
|
from comment_aggregates_view cav
|
||||||
|
);
|
||||||
|
|
||||||
|
create table comment_aggregates_fast as select * from comment_aggregates_view;
|
||||||
|
alter table comment_aggregates_fast add primary key (id);
|
||||||
|
|
||||||
|
create view comment_fast_view as
|
||||||
|
select
|
||||||
|
cav.*,
|
||||||
|
us.user_id as user_id,
|
||||||
|
us.my_vote as my_vote,
|
||||||
|
us.is_subbed::bool as subscribed,
|
||||||
|
us.is_saved::bool as saved
|
||||||
|
from comment_aggregates_fast cav
|
||||||
|
cross join lateral (
|
||||||
|
select
|
||||||
|
u.id as user_id,
|
||||||
|
coalesce(cl.score, 0) as my_vote,
|
||||||
|
coalesce(cf.id, 0) as is_subbed,
|
||||||
|
coalesce(cs.id, 0) as is_saved
|
||||||
|
from user_ u
|
||||||
|
left join comment_like cl on u.id = cl.user_id and cav.id = cl.comment_id
|
||||||
|
left join comment_saved cs on u.id = cs.user_id and cs.comment_id = cav.id
|
||||||
|
left join community_follower cf on u.id = cf.user_id and cav.community_id = cf.community_id
|
||||||
|
) as us
|
||||||
|
|
||||||
|
union all
|
||||||
|
|
||||||
|
select
|
||||||
|
cav.*,
|
||||||
|
null as user_id,
|
||||||
|
null as my_vote,
|
||||||
|
null as subscribed,
|
||||||
|
null as saved
|
||||||
|
from comment_aggregates_fast cav;
|
||||||
|
|
||||||
|
create view user_mention_view as
|
||||||
|
select
|
||||||
|
c.id,
|
||||||
|
um.id as user_mention_id,
|
||||||
|
c.creator_id,
|
||||||
|
c.creator_actor_id,
|
||||||
|
c.creator_local,
|
||||||
|
c.post_id,
|
||||||
|
c.parent_id,
|
||||||
|
c.content,
|
||||||
|
c.removed,
|
||||||
|
um.read,
|
||||||
|
c.published,
|
||||||
|
c.updated,
|
||||||
|
c.deleted,
|
||||||
|
c.community_id,
|
||||||
|
c.community_actor_id,
|
||||||
|
c.community_local,
|
||||||
|
c.community_name,
|
||||||
|
c.banned,
|
||||||
|
c.banned_from_community,
|
||||||
|
c.creator_name,
|
||||||
|
c.creator_avatar,
|
||||||
|
c.score,
|
||||||
|
c.upvotes,
|
||||||
|
c.downvotes,
|
||||||
|
c.hot_rank,
|
||||||
|
c.user_id,
|
||||||
|
c.my_vote,
|
||||||
|
c.saved,
|
||||||
|
um.recipient_id,
|
||||||
|
(select actor_id from user_ u where u.id = um.recipient_id) as recipient_actor_id,
|
||||||
|
(select local from user_ u where u.id = um.recipient_id) as recipient_local
|
||||||
|
from user_mention um, comment_view c
|
||||||
|
where um.comment_id = c.id;
|
||||||
|
|
||||||
|
create view user_mention_fast_view as
|
||||||
|
select
|
||||||
|
ac.id,
|
||||||
|
um.id as user_mention_id,
|
||||||
|
ac.creator_id,
|
||||||
|
ac.creator_actor_id,
|
||||||
|
ac.creator_local,
|
||||||
|
ac.post_id,
|
||||||
|
ac.parent_id,
|
||||||
|
ac.content,
|
||||||
|
ac.removed,
|
||||||
|
um.read,
|
||||||
|
ac.published,
|
||||||
|
ac.updated,
|
||||||
|
ac.deleted,
|
||||||
|
ac.community_id,
|
||||||
|
ac.community_actor_id,
|
||||||
|
ac.community_local,
|
||||||
|
ac.community_name,
|
||||||
|
ac.banned,
|
||||||
|
ac.banned_from_community,
|
||||||
|
ac.creator_name,
|
||||||
|
ac.creator_avatar,
|
||||||
|
ac.score,
|
||||||
|
ac.upvotes,
|
||||||
|
ac.downvotes,
|
||||||
|
ac.hot_rank,
|
||||||
|
u.id as user_id,
|
||||||
|
coalesce(cl.score, 0) as my_vote,
|
||||||
|
(select cs.id::bool from comment_saved cs where u.id = cs.user_id and cs.comment_id = ac.id) as saved,
|
||||||
|
um.recipient_id,
|
||||||
|
(select actor_id from user_ u where u.id = um.recipient_id) as recipient_actor_id,
|
||||||
|
(select local from user_ u where u.id = um.recipient_id) as recipient_local
|
||||||
|
from user_ u
|
||||||
|
cross join (
|
||||||
|
select
|
||||||
|
ca.*
|
||||||
|
from comment_aggregates_fast ca
|
||||||
|
) ac
|
||||||
|
left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id
|
||||||
|
left join user_mention um on um.comment_id = ac.id
|
||||||
|
|
||||||
|
union all
|
||||||
|
|
||||||
|
select
|
||||||
|
ac.id,
|
||||||
|
um.id as user_mention_id,
|
||||||
|
ac.creator_id,
|
||||||
|
ac.creator_actor_id,
|
||||||
|
ac.creator_local,
|
||||||
|
ac.post_id,
|
||||||
|
ac.parent_id,
|
||||||
|
ac.content,
|
||||||
|
ac.removed,
|
||||||
|
um.read,
|
||||||
|
ac.published,
|
||||||
|
ac.updated,
|
||||||
|
ac.deleted,
|
||||||
|
ac.community_id,
|
||||||
|
ac.community_actor_id,
|
||||||
|
ac.community_local,
|
||||||
|
ac.community_name,
|
||||||
|
ac.banned,
|
||||||
|
ac.banned_from_community,
|
||||||
|
ac.creator_name,
|
||||||
|
ac.creator_avatar,
|
||||||
|
ac.score,
|
||||||
|
ac.upvotes,
|
||||||
|
ac.downvotes,
|
||||||
|
ac.hot_rank,
|
||||||
|
null as user_id,
|
||||||
|
null as my_vote,
|
||||||
|
null as saved,
|
||||||
|
um.recipient_id,
|
||||||
|
(select actor_id from user_ u where u.id = um.recipient_id) as recipient_actor_id,
|
||||||
|
(select local from user_ u where u.id = um.recipient_id) as recipient_local
|
||||||
|
from comment_aggregates_fast ac
|
||||||
|
left join user_mention um on um.comment_id = ac.id
|
||||||
|
;
|
||||||
|
|
||||||
|
-- Do the reply_view referencing the comment_fast_view
|
||||||
|
create view reply_fast_view as
|
||||||
|
with closereply as (
|
||||||
|
select
|
||||||
|
c2.id,
|
||||||
|
c2.creator_id as sender_id,
|
||||||
|
c.creator_id as recipient_id
|
||||||
|
from comment c
|
||||||
|
inner join comment c2 on c.id = c2.parent_id
|
||||||
|
where c2.creator_id != c.creator_id
|
||||||
|
-- Do union where post is null
|
||||||
|
union
|
||||||
|
select
|
||||||
|
c.id,
|
||||||
|
c.creator_id as sender_id,
|
||||||
|
p.creator_id as recipient_id
|
||||||
|
from comment c, post p
|
||||||
|
where c.post_id = p.id and c.parent_id is null and c.creator_id != p.creator_id
|
||||||
|
)
|
||||||
|
select cv.*,
|
||||||
|
closereply.recipient_id
|
||||||
|
from comment_fast_view cv, closereply
|
||||||
|
where closereply.id = cv.id
|
||||||
|
;
|
||||||
|
|
||||||
|
-- add creator_published to the post view
|
||||||
|
drop view post_fast_view;
|
||||||
|
drop table post_aggregates_fast;
|
||||||
|
drop view post_view;
|
||||||
|
drop view post_aggregates_view;
|
||||||
|
|
||||||
|
create view post_aggregates_view as
|
||||||
|
select
|
||||||
|
p.*,
|
||||||
|
-- creator details
|
||||||
|
u.actor_id as creator_actor_id,
|
||||||
|
u."local" as creator_local,
|
||||||
|
u."name" as creator_name,
|
||||||
|
u.avatar as creator_avatar,
|
||||||
|
u.banned as banned,
|
||||||
|
cb.id::bool as banned_from_community,
|
||||||
|
-- community details
|
||||||
|
c.actor_id as community_actor_id,
|
||||||
|
c."local" as community_local,
|
||||||
|
c."name" as community_name,
|
||||||
|
c.removed as community_removed,
|
||||||
|
c.deleted as community_deleted,
|
||||||
|
c.nsfw as community_nsfw,
|
||||||
|
-- post score data/comment count
|
||||||
|
coalesce(ct.comments, 0) as number_of_comments,
|
||||||
|
coalesce(pl.score, 0) as score,
|
||||||
|
coalesce(pl.upvotes, 0) as upvotes,
|
||||||
|
coalesce(pl.downvotes, 0) as downvotes,
|
||||||
|
hot_rank(
|
||||||
|
coalesce(pl.score , 0), (
|
||||||
|
case
|
||||||
|
when (p.published < ('now'::timestamp - '1 month'::interval))
|
||||||
|
then p.published
|
||||||
|
else greatest(ct.recent_comment_time, p.published)
|
||||||
|
end
|
||||||
|
)
|
||||||
|
) as hot_rank,
|
||||||
|
(
|
||||||
|
case
|
||||||
|
when (p.published < ('now'::timestamp - '1 month'::interval))
|
||||||
|
then p.published
|
||||||
|
else greatest(ct.recent_comment_time, p.published)
|
||||||
|
end
|
||||||
|
) as newest_activity_time
|
||||||
|
from post p
|
||||||
|
left join user_ u on p.creator_id = u.id
|
||||||
|
left join community_user_ban cb on p.creator_id = cb.user_id and p.community_id = cb.community_id
|
||||||
|
left join community c on p.community_id = c.id
|
||||||
|
left join (
|
||||||
|
select
|
||||||
|
post_id,
|
||||||
|
count(*) as comments,
|
||||||
|
max(published) as recent_comment_time
|
||||||
|
from comment
|
||||||
|
group by post_id
|
||||||
|
) ct on ct.post_id = p.id
|
||||||
|
left join (
|
||||||
|
select
|
||||||
|
post_id,
|
||||||
|
sum(score) as score,
|
||||||
|
sum(score) filter (where score = 1) as upvotes,
|
||||||
|
-sum(score) filter (where score = -1) as downvotes
|
||||||
|
from post_like
|
||||||
|
group by post_id
|
||||||
|
) pl on pl.post_id = p.id
|
||||||
|
order by p.id;
|
||||||
|
|
||||||
|
create view post_view as
|
||||||
|
select
|
||||||
|
pav.*,
|
||||||
|
us.id as user_id,
|
||||||
|
us.user_vote as my_vote,
|
||||||
|
us.is_subbed::bool as subscribed,
|
||||||
|
us.is_read::bool as read,
|
||||||
|
us.is_saved::bool as saved
|
||||||
|
from post_aggregates_view pav
|
||||||
|
cross join lateral (
|
||||||
|
select
|
||||||
|
u.id,
|
||||||
|
coalesce(cf.community_id, 0) as is_subbed,
|
||||||
|
coalesce(pr.post_id, 0) as is_read,
|
||||||
|
coalesce(ps.post_id, 0) as is_saved,
|
||||||
|
coalesce(pl.score, 0) as user_vote
|
||||||
|
from user_ u
|
||||||
|
left join community_user_ban cb on u.id = cb.user_id and cb.community_id = pav.community_id
|
||||||
|
left join community_follower cf on u.id = cf.user_id and cf.community_id = pav.community_id
|
||||||
|
left join post_read pr on u.id = pr.user_id and pr.post_id = pav.id
|
||||||
|
left join post_saved ps on u.id = ps.user_id and ps.post_id = pav.id
|
||||||
|
left join post_like pl on u.id = pl.user_id and pav.id = pl.post_id
|
||||||
|
) as us
|
||||||
|
|
||||||
|
union all
|
||||||
|
|
||||||
|
select
|
||||||
|
pav.*,
|
||||||
|
null as user_id,
|
||||||
|
null as my_vote,
|
||||||
|
null as subscribed,
|
||||||
|
null as read,
|
||||||
|
null as saved
|
||||||
|
from post_aggregates_view pav;
|
||||||
|
|
||||||
|
create table post_aggregates_fast as select * from post_aggregates_view;
|
||||||
|
alter table post_aggregates_fast add primary key (id);
|
||||||
|
|
||||||
|
create view post_fast_view as
|
||||||
|
select
|
||||||
|
pav.*,
|
||||||
|
us.id as user_id,
|
||||||
|
us.user_vote as my_vote,
|
||||||
|
us.is_subbed::bool as subscribed,
|
||||||
|
us.is_read::bool as read,
|
||||||
|
us.is_saved::bool as saved
|
||||||
|
from post_aggregates_fast pav
|
||||||
|
cross join lateral (
|
||||||
|
select
|
||||||
|
u.id,
|
||||||
|
coalesce(cf.community_id, 0) as is_subbed,
|
||||||
|
coalesce(pr.post_id, 0) as is_read,
|
||||||
|
coalesce(ps.post_id, 0) as is_saved,
|
||||||
|
coalesce(pl.score, 0) as user_vote
|
||||||
|
from user_ u
|
||||||
|
left join community_user_ban cb on u.id = cb.user_id and cb.community_id = pav.community_id
|
||||||
|
left join community_follower cf on u.id = cf.user_id and cf.community_id = pav.community_id
|
||||||
|
left join post_read pr on u.id = pr.user_id and pr.post_id = pav.id
|
||||||
|
left join post_saved ps on u.id = ps.user_id and ps.post_id = pav.id
|
||||||
|
left join post_like pl on u.id = pl.user_id and pav.id = pl.post_id
|
||||||
|
) as us
|
||||||
|
|
||||||
|
union all
|
||||||
|
|
||||||
|
select
|
||||||
|
pav.*,
|
||||||
|
null as user_id,
|
||||||
|
null as my_vote,
|
||||||
|
null as subscribed,
|
||||||
|
null as read,
|
||||||
|
null as saved
|
||||||
|
from post_aggregates_fast pav;
|
390
server/migrations/2020-07-08-202609_add_creator_published/up.sql
vendored
Normal file
390
server/migrations/2020-07-08-202609_add_creator_published/up.sql
vendored
Normal file
|
@ -0,0 +1,390 @@
|
||||||
|
drop view user_mention_view;
|
||||||
|
drop view reply_fast_view;
|
||||||
|
drop view comment_fast_view;
|
||||||
|
drop view comment_view;
|
||||||
|
|
||||||
|
drop view user_mention_fast_view;
|
||||||
|
drop table comment_aggregates_fast;
|
||||||
|
drop view comment_aggregates_view;
|
||||||
|
|
||||||
|
create view comment_aggregates_view as
|
||||||
|
select
|
||||||
|
ct.*,
|
||||||
|
-- community details
|
||||||
|
p.community_id,
|
||||||
|
c.actor_id as community_actor_id,
|
||||||
|
c."local" as community_local,
|
||||||
|
c."name" as community_name,
|
||||||
|
-- creator details
|
||||||
|
u.banned as banned,
|
||||||
|
coalesce(cb.id, 0)::bool as banned_from_community,
|
||||||
|
u.actor_id as creator_actor_id,
|
||||||
|
u.local as creator_local,
|
||||||
|
u.name as creator_name,
|
||||||
|
u.published as creator_published,
|
||||||
|
u.avatar as creator_avatar,
|
||||||
|
-- score details
|
||||||
|
coalesce(cl.total, 0) as score,
|
||||||
|
coalesce(cl.up, 0) as upvotes,
|
||||||
|
coalesce(cl.down, 0) as downvotes,
|
||||||
|
hot_rank(coalesce(cl.total, 0), ct.published) as hot_rank
|
||||||
|
from comment ct
|
||||||
|
left join post p on ct.post_id = p.id
|
||||||
|
left join community c on p.community_id = c.id
|
||||||
|
left join user_ u on ct.creator_id = u.id
|
||||||
|
left join community_user_ban cb on ct.creator_id = cb.user_id and p.id = ct.post_id and p.community_id = cb.community_id
|
||||||
|
left join (
|
||||||
|
select
|
||||||
|
l.comment_id as id,
|
||||||
|
sum(l.score) as total,
|
||||||
|
count(case when l.score = 1 then 1 else null end) as up,
|
||||||
|
count(case when l.score = -1 then 1 else null end) as down
|
||||||
|
from comment_like l
|
||||||
|
group by comment_id
|
||||||
|
) as cl on cl.id = ct.id;
|
||||||
|
|
||||||
|
create or replace view comment_view as (
|
||||||
|
select
|
||||||
|
cav.*,
|
||||||
|
us.user_id as user_id,
|
||||||
|
us.my_vote as my_vote,
|
||||||
|
us.is_subbed::bool as subscribed,
|
||||||
|
us.is_saved::bool as saved
|
||||||
|
from comment_aggregates_view cav
|
||||||
|
cross join lateral (
|
||||||
|
select
|
||||||
|
u.id as user_id,
|
||||||
|
coalesce(cl.score, 0) as my_vote,
|
||||||
|
coalesce(cf.id, 0) as is_subbed,
|
||||||
|
coalesce(cs.id, 0) as is_saved
|
||||||
|
from user_ u
|
||||||
|
left join comment_like cl on u.id = cl.user_id and cav.id = cl.comment_id
|
||||||
|
left join comment_saved cs on u.id = cs.user_id and cs.comment_id = cav.id
|
||||||
|
left join community_follower cf on u.id = cf.user_id and cav.community_id = cf.community_id
|
||||||
|
) as us
|
||||||
|
|
||||||
|
union all
|
||||||
|
|
||||||
|
select
|
||||||
|
cav.*,
|
||||||
|
null as user_id,
|
||||||
|
null as my_vote,
|
||||||
|
null as subscribed,
|
||||||
|
null as saved
|
||||||
|
from comment_aggregates_view cav
|
||||||
|
);
|
||||||
|
|
||||||
|
create table comment_aggregates_fast as select * from comment_aggregates_view;
|
||||||
|
alter table comment_aggregates_fast add primary key (id);
|
||||||
|
|
||||||
|
create view comment_fast_view as
|
||||||
|
select
|
||||||
|
cav.*,
|
||||||
|
us.user_id as user_id,
|
||||||
|
us.my_vote as my_vote,
|
||||||
|
us.is_subbed::bool as subscribed,
|
||||||
|
us.is_saved::bool as saved
|
||||||
|
from comment_aggregates_fast cav
|
||||||
|
cross join lateral (
|
||||||
|
select
|
||||||
|
u.id as user_id,
|
||||||
|
coalesce(cl.score, 0) as my_vote,
|
||||||
|
coalesce(cf.id, 0) as is_subbed,
|
||||||
|
coalesce(cs.id, 0) as is_saved
|
||||||
|
from user_ u
|
||||||
|
left join comment_like cl on u.id = cl.user_id and cav.id = cl.comment_id
|
||||||
|
left join comment_saved cs on u.id = cs.user_id and cs.comment_id = cav.id
|
||||||
|
left join community_follower cf on u.id = cf.user_id and cav.community_id = cf.community_id
|
||||||
|
) as us
|
||||||
|
|
||||||
|
union all
|
||||||
|
|
||||||
|
select
|
||||||
|
cav.*,
|
||||||
|
null as user_id,
|
||||||
|
null as my_vote,
|
||||||
|
null as subscribed,
|
||||||
|
null as saved
|
||||||
|
from comment_aggregates_fast cav;
|
||||||
|
|
||||||
|
create view user_mention_view as
|
||||||
|
select
|
||||||
|
c.id,
|
||||||
|
um.id as user_mention_id,
|
||||||
|
c.creator_id,
|
||||||
|
c.creator_actor_id,
|
||||||
|
c.creator_local,
|
||||||
|
c.post_id,
|
||||||
|
c.parent_id,
|
||||||
|
c.content,
|
||||||
|
c.removed,
|
||||||
|
um.read,
|
||||||
|
c.published,
|
||||||
|
c.updated,
|
||||||
|
c.deleted,
|
||||||
|
c.community_id,
|
||||||
|
c.community_actor_id,
|
||||||
|
c.community_local,
|
||||||
|
c.community_name,
|
||||||
|
c.banned,
|
||||||
|
c.banned_from_community,
|
||||||
|
c.creator_name,
|
||||||
|
c.creator_avatar,
|
||||||
|
c.score,
|
||||||
|
c.upvotes,
|
||||||
|
c.downvotes,
|
||||||
|
c.hot_rank,
|
||||||
|
c.user_id,
|
||||||
|
c.my_vote,
|
||||||
|
c.saved,
|
||||||
|
um.recipient_id,
|
||||||
|
(select actor_id from user_ u where u.id = um.recipient_id) as recipient_actor_id,
|
||||||
|
(select local from user_ u where u.id = um.recipient_id) as recipient_local
|
||||||
|
from user_mention um, comment_view c
|
||||||
|
where um.comment_id = c.id;
|
||||||
|
|
||||||
|
create view user_mention_fast_view as
|
||||||
|
select
|
||||||
|
ac.id,
|
||||||
|
um.id as user_mention_id,
|
||||||
|
ac.creator_id,
|
||||||
|
ac.creator_actor_id,
|
||||||
|
ac.creator_local,
|
||||||
|
ac.post_id,
|
||||||
|
ac.parent_id,
|
||||||
|
ac.content,
|
||||||
|
ac.removed,
|
||||||
|
um.read,
|
||||||
|
ac.published,
|
||||||
|
ac.updated,
|
||||||
|
ac.deleted,
|
||||||
|
ac.community_id,
|
||||||
|
ac.community_actor_id,
|
||||||
|
ac.community_local,
|
||||||
|
ac.community_name,
|
||||||
|
ac.banned,
|
||||||
|
ac.banned_from_community,
|
||||||
|
ac.creator_name,
|
||||||
|
ac.creator_avatar,
|
||||||
|
ac.score,
|
||||||
|
ac.upvotes,
|
||||||
|
ac.downvotes,
|
||||||
|
ac.hot_rank,
|
||||||
|
u.id as user_id,
|
||||||
|
coalesce(cl.score, 0) as my_vote,
|
||||||
|
(select cs.id::bool from comment_saved cs where u.id = cs.user_id and cs.comment_id = ac.id) as saved,
|
||||||
|
um.recipient_id,
|
||||||
|
(select actor_id from user_ u where u.id = um.recipient_id) as recipient_actor_id,
|
||||||
|
(select local from user_ u where u.id = um.recipient_id) as recipient_local
|
||||||
|
from user_ u
|
||||||
|
cross join (
|
||||||
|
select
|
||||||
|
ca.*
|
||||||
|
from comment_aggregates_fast ca
|
||||||
|
) ac
|
||||||
|
left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id
|
||||||
|
left join user_mention um on um.comment_id = ac.id
|
||||||
|
|
||||||
|
union all
|
||||||
|
|
||||||
|
select
|
||||||
|
ac.id,
|
||||||
|
um.id as user_mention_id,
|
||||||
|
ac.creator_id,
|
||||||
|
ac.creator_actor_id,
|
||||||
|
ac.creator_local,
|
||||||
|
ac.post_id,
|
||||||
|
ac.parent_id,
|
||||||
|
ac.content,
|
||||||
|
ac.removed,
|
||||||
|
um.read,
|
||||||
|
ac.published,
|
||||||
|
ac.updated,
|
||||||
|
ac.deleted,
|
||||||
|
ac.community_id,
|
||||||
|
ac.community_actor_id,
|
||||||
|
ac.community_local,
|
||||||
|
ac.community_name,
|
||||||
|
ac.banned,
|
||||||
|
ac.banned_from_community,
|
||||||
|
ac.creator_name,
|
||||||
|
ac.creator_avatar,
|
||||||
|
ac.score,
|
||||||
|
ac.upvotes,
|
||||||
|
ac.downvotes,
|
||||||
|
ac.hot_rank,
|
||||||
|
null as user_id,
|
||||||
|
null as my_vote,
|
||||||
|
null as saved,
|
||||||
|
um.recipient_id,
|
||||||
|
(select actor_id from user_ u where u.id = um.recipient_id) as recipient_actor_id,
|
||||||
|
(select local from user_ u where u.id = um.recipient_id) as recipient_local
|
||||||
|
from comment_aggregates_fast ac
|
||||||
|
left join user_mention um on um.comment_id = ac.id
|
||||||
|
;
|
||||||
|
|
||||||
|
-- Do the reply_view referencing the comment_fast_view
|
||||||
|
create view reply_fast_view as
|
||||||
|
with closereply as (
|
||||||
|
select
|
||||||
|
c2.id,
|
||||||
|
c2.creator_id as sender_id,
|
||||||
|
c.creator_id as recipient_id
|
||||||
|
from comment c
|
||||||
|
inner join comment c2 on c.id = c2.parent_id
|
||||||
|
where c2.creator_id != c.creator_id
|
||||||
|
-- Do union where post is null
|
||||||
|
union
|
||||||
|
select
|
||||||
|
c.id,
|
||||||
|
c.creator_id as sender_id,
|
||||||
|
p.creator_id as recipient_id
|
||||||
|
from comment c, post p
|
||||||
|
where c.post_id = p.id and c.parent_id is null and c.creator_id != p.creator_id
|
||||||
|
)
|
||||||
|
select cv.*,
|
||||||
|
closereply.recipient_id
|
||||||
|
from comment_fast_view cv, closereply
|
||||||
|
where closereply.id = cv.id
|
||||||
|
;
|
||||||
|
|
||||||
|
-- add creator_published to the post view
|
||||||
|
drop view post_fast_view;
|
||||||
|
drop table post_aggregates_fast;
|
||||||
|
drop view post_view;
|
||||||
|
drop view post_aggregates_view;
|
||||||
|
|
||||||
|
create view post_aggregates_view as
|
||||||
|
select
|
||||||
|
p.*,
|
||||||
|
-- creator details
|
||||||
|
u.actor_id as creator_actor_id,
|
||||||
|
u."local" as creator_local,
|
||||||
|
u."name" as creator_name,
|
||||||
|
u.published as creator_published,
|
||||||
|
u.avatar as creator_avatar,
|
||||||
|
u.banned as banned,
|
||||||
|
cb.id::bool as banned_from_community,
|
||||||
|
-- community details
|
||||||
|
c.actor_id as community_actor_id,
|
||||||
|
c."local" as community_local,
|
||||||
|
c."name" as community_name,
|
||||||
|
c.removed as community_removed,
|
||||||
|
c.deleted as community_deleted,
|
||||||
|
c.nsfw as community_nsfw,
|
||||||
|
-- post score data/comment count
|
||||||
|
coalesce(ct.comments, 0) as number_of_comments,
|
||||||
|
coalesce(pl.score, 0) as score,
|
||||||
|
coalesce(pl.upvotes, 0) as upvotes,
|
||||||
|
coalesce(pl.downvotes, 0) as downvotes,
|
||||||
|
hot_rank(
|
||||||
|
coalesce(pl.score , 0), (
|
||||||
|
case
|
||||||
|
when (p.published < ('now'::timestamp - '1 month'::interval))
|
||||||
|
then p.published
|
||||||
|
else greatest(ct.recent_comment_time, p.published)
|
||||||
|
end
|
||||||
|
)
|
||||||
|
) as hot_rank,
|
||||||
|
(
|
||||||
|
case
|
||||||
|
when (p.published < ('now'::timestamp - '1 month'::interval))
|
||||||
|
then p.published
|
||||||
|
else greatest(ct.recent_comment_time, p.published)
|
||||||
|
end
|
||||||
|
) as newest_activity_time
|
||||||
|
from post p
|
||||||
|
left join user_ u on p.creator_id = u.id
|
||||||
|
left join community_user_ban cb on p.creator_id = cb.user_id and p.community_id = cb.community_id
|
||||||
|
left join community c on p.community_id = c.id
|
||||||
|
left join (
|
||||||
|
select
|
||||||
|
post_id,
|
||||||
|
count(*) as comments,
|
||||||
|
max(published) as recent_comment_time
|
||||||
|
from comment
|
||||||
|
group by post_id
|
||||||
|
) ct on ct.post_id = p.id
|
||||||
|
left join (
|
||||||
|
select
|
||||||
|
post_id,
|
||||||
|
sum(score) as score,
|
||||||
|
sum(score) filter (where score = 1) as upvotes,
|
||||||
|
-sum(score) filter (where score = -1) as downvotes
|
||||||
|
from post_like
|
||||||
|
group by post_id
|
||||||
|
) pl on pl.post_id = p.id
|
||||||
|
order by p.id;
|
||||||
|
|
||||||
|
create view post_view as
|
||||||
|
select
|
||||||
|
pav.*,
|
||||||
|
us.id as user_id,
|
||||||
|
us.user_vote as my_vote,
|
||||||
|
us.is_subbed::bool as subscribed,
|
||||||
|
us.is_read::bool as read,
|
||||||
|
us.is_saved::bool as saved
|
||||||
|
from post_aggregates_view pav
|
||||||
|
cross join lateral (
|
||||||
|
select
|
||||||
|
u.id,
|
||||||
|
coalesce(cf.community_id, 0) as is_subbed,
|
||||||
|
coalesce(pr.post_id, 0) as is_read,
|
||||||
|
coalesce(ps.post_id, 0) as is_saved,
|
||||||
|
coalesce(pl.score, 0) as user_vote
|
||||||
|
from user_ u
|
||||||
|
left join community_user_ban cb on u.id = cb.user_id and cb.community_id = pav.community_id
|
||||||
|
left join community_follower cf on u.id = cf.user_id and cf.community_id = pav.community_id
|
||||||
|
left join post_read pr on u.id = pr.user_id and pr.post_id = pav.id
|
||||||
|
left join post_saved ps on u.id = ps.user_id and ps.post_id = pav.id
|
||||||
|
left join post_like pl on u.id = pl.user_id and pav.id = pl.post_id
|
||||||
|
) as us
|
||||||
|
|
||||||
|
union all
|
||||||
|
|
||||||
|
select
|
||||||
|
pav.*,
|
||||||
|
null as user_id,
|
||||||
|
null as my_vote,
|
||||||
|
null as subscribed,
|
||||||
|
null as read,
|
||||||
|
null as saved
|
||||||
|
from post_aggregates_view pav;
|
||||||
|
|
||||||
|
create table post_aggregates_fast as select * from post_aggregates_view;
|
||||||
|
alter table post_aggregates_fast add primary key (id);
|
||||||
|
|
||||||
|
create view post_fast_view as
|
||||||
|
select
|
||||||
|
pav.*,
|
||||||
|
us.id as user_id,
|
||||||
|
us.user_vote as my_vote,
|
||||||
|
us.is_subbed::bool as subscribed,
|
||||||
|
us.is_read::bool as read,
|
||||||
|
us.is_saved::bool as saved
|
||||||
|
from post_aggregates_fast pav
|
||||||
|
cross join lateral (
|
||||||
|
select
|
||||||
|
u.id,
|
||||||
|
coalesce(cf.community_id, 0) as is_subbed,
|
||||||
|
coalesce(pr.post_id, 0) as is_read,
|
||||||
|
coalesce(ps.post_id, 0) as is_saved,
|
||||||
|
coalesce(pl.score, 0) as user_vote
|
||||||
|
from user_ u
|
||||||
|
left join community_user_ban cb on u.id = cb.user_id and cb.community_id = pav.community_id
|
||||||
|
left join community_follower cf on u.id = cf.user_id and cf.community_id = pav.community_id
|
||||||
|
left join post_read pr on u.id = pr.user_id and pr.post_id = pav.id
|
||||||
|
left join post_saved ps on u.id = ps.user_id and ps.post_id = pav.id
|
||||||
|
left join post_like pl on u.id = pl.user_id and pav.id = pl.post_id
|
||||||
|
) as us
|
||||||
|
|
||||||
|
union all
|
||||||
|
|
||||||
|
select
|
||||||
|
pav.*,
|
||||||
|
null as user_id,
|
||||||
|
null as my_vote,
|
||||||
|
null as subscribed,
|
||||||
|
null as read,
|
||||||
|
null as saved
|
||||||
|
from post_aggregates_fast pav;
|
249
server/migrations/2020-07-12-100442_add_post_title_to_comments_view/down.sql
vendored
Normal file
249
server/migrations/2020-07-12-100442_add_post_title_to_comments_view/down.sql
vendored
Normal file
|
@ -0,0 +1,249 @@
|
||||||
|
drop view user_mention_view;
|
||||||
|
drop view reply_fast_view;
|
||||||
|
drop view comment_fast_view;
|
||||||
|
drop view comment_view;
|
||||||
|
|
||||||
|
drop view user_mention_fast_view;
|
||||||
|
drop table comment_aggregates_fast;
|
||||||
|
drop view comment_aggregates_view;
|
||||||
|
|
||||||
|
create view comment_aggregates_view as
|
||||||
|
select
|
||||||
|
ct.*,
|
||||||
|
-- community details
|
||||||
|
p.community_id,
|
||||||
|
c.actor_id as community_actor_id,
|
||||||
|
c."local" as community_local,
|
||||||
|
c."name" as community_name,
|
||||||
|
-- creator details
|
||||||
|
u.banned as banned,
|
||||||
|
coalesce(cb.id, 0)::bool as banned_from_community,
|
||||||
|
u.actor_id as creator_actor_id,
|
||||||
|
u.local as creator_local,
|
||||||
|
u.name as creator_name,
|
||||||
|
u.published as creator_published,
|
||||||
|
u.avatar as creator_avatar,
|
||||||
|
-- score details
|
||||||
|
coalesce(cl.total, 0) as score,
|
||||||
|
coalesce(cl.up, 0) as upvotes,
|
||||||
|
coalesce(cl.down, 0) as downvotes,
|
||||||
|
hot_rank(coalesce(cl.total, 0), ct.published) as hot_rank
|
||||||
|
from comment ct
|
||||||
|
left join post p on ct.post_id = p.id
|
||||||
|
left join community c on p.community_id = c.id
|
||||||
|
left join user_ u on ct.creator_id = u.id
|
||||||
|
left join community_user_ban cb on ct.creator_id = cb.user_id and p.id = ct.post_id and p.community_id = cb.community_id
|
||||||
|
left join (
|
||||||
|
select
|
||||||
|
l.comment_id as id,
|
||||||
|
sum(l.score) as total,
|
||||||
|
count(case when l.score = 1 then 1 else null end) as up,
|
||||||
|
count(case when l.score = -1 then 1 else null end) as down
|
||||||
|
from comment_like l
|
||||||
|
group by comment_id
|
||||||
|
) as cl on cl.id = ct.id;
|
||||||
|
|
||||||
|
create or replace view comment_view as (
|
||||||
|
select
|
||||||
|
cav.*,
|
||||||
|
us.user_id as user_id,
|
||||||
|
us.my_vote as my_vote,
|
||||||
|
us.is_subbed::bool as subscribed,
|
||||||
|
us.is_saved::bool as saved
|
||||||
|
from comment_aggregates_view cav
|
||||||
|
cross join lateral (
|
||||||
|
select
|
||||||
|
u.id as user_id,
|
||||||
|
coalesce(cl.score, 0) as my_vote,
|
||||||
|
coalesce(cf.id, 0) as is_subbed,
|
||||||
|
coalesce(cs.id, 0) as is_saved
|
||||||
|
from user_ u
|
||||||
|
left join comment_like cl on u.id = cl.user_id and cav.id = cl.comment_id
|
||||||
|
left join comment_saved cs on u.id = cs.user_id and cs.comment_id = cav.id
|
||||||
|
left join community_follower cf on u.id = cf.user_id and cav.community_id = cf.community_id
|
||||||
|
) as us
|
||||||
|
|
||||||
|
union all
|
||||||
|
|
||||||
|
select
|
||||||
|
cav.*,
|
||||||
|
null as user_id,
|
||||||
|
null as my_vote,
|
||||||
|
null as subscribed,
|
||||||
|
null as saved
|
||||||
|
from comment_aggregates_view cav
|
||||||
|
);
|
||||||
|
|
||||||
|
create table comment_aggregates_fast as select * from comment_aggregates_view;
|
||||||
|
alter table comment_aggregates_fast add primary key (id);
|
||||||
|
|
||||||
|
create view comment_fast_view as
|
||||||
|
select
|
||||||
|
cav.*,
|
||||||
|
us.user_id as user_id,
|
||||||
|
us.my_vote as my_vote,
|
||||||
|
us.is_subbed::bool as subscribed,
|
||||||
|
us.is_saved::bool as saved
|
||||||
|
from comment_aggregates_fast cav
|
||||||
|
cross join lateral (
|
||||||
|
select
|
||||||
|
u.id as user_id,
|
||||||
|
coalesce(cl.score, 0) as my_vote,
|
||||||
|
coalesce(cf.id, 0) as is_subbed,
|
||||||
|
coalesce(cs.id, 0) as is_saved
|
||||||
|
from user_ u
|
||||||
|
left join comment_like cl on u.id = cl.user_id and cav.id = cl.comment_id
|
||||||
|
left join comment_saved cs on u.id = cs.user_id and cs.comment_id = cav.id
|
||||||
|
left join community_follower cf on u.id = cf.user_id and cav.community_id = cf.community_id
|
||||||
|
) as us
|
||||||
|
|
||||||
|
union all
|
||||||
|
|
||||||
|
select
|
||||||
|
cav.*,
|
||||||
|
null as user_id,
|
||||||
|
null as my_vote,
|
||||||
|
null as subscribed,
|
||||||
|
null as saved
|
||||||
|
from comment_aggregates_fast cav;
|
||||||
|
|
||||||
|
create view user_mention_view as
|
||||||
|
select
|
||||||
|
c.id,
|
||||||
|
um.id as user_mention_id,
|
||||||
|
c.creator_id,
|
||||||
|
c.creator_actor_id,
|
||||||
|
c.creator_local,
|
||||||
|
c.post_id,
|
||||||
|
c.parent_id,
|
||||||
|
c.content,
|
||||||
|
c.removed,
|
||||||
|
um.read,
|
||||||
|
c.published,
|
||||||
|
c.updated,
|
||||||
|
c.deleted,
|
||||||
|
c.community_id,
|
||||||
|
c.community_actor_id,
|
||||||
|
c.community_local,
|
||||||
|
c.community_name,
|
||||||
|
c.banned,
|
||||||
|
c.banned_from_community,
|
||||||
|
c.creator_name,
|
||||||
|
c.creator_avatar,
|
||||||
|
c.score,
|
||||||
|
c.upvotes,
|
||||||
|
c.downvotes,
|
||||||
|
c.hot_rank,
|
||||||
|
c.user_id,
|
||||||
|
c.my_vote,
|
||||||
|
c.saved,
|
||||||
|
um.recipient_id,
|
||||||
|
(select actor_id from user_ u where u.id = um.recipient_id) as recipient_actor_id,
|
||||||
|
(select local from user_ u where u.id = um.recipient_id) as recipient_local
|
||||||
|
from user_mention um, comment_view c
|
||||||
|
where um.comment_id = c.id;
|
||||||
|
|
||||||
|
create view user_mention_fast_view as
|
||||||
|
select
|
||||||
|
ac.id,
|
||||||
|
um.id as user_mention_id,
|
||||||
|
ac.creator_id,
|
||||||
|
ac.creator_actor_id,
|
||||||
|
ac.creator_local,
|
||||||
|
ac.post_id,
|
||||||
|
ac.parent_id,
|
||||||
|
ac.content,
|
||||||
|
ac.removed,
|
||||||
|
um.read,
|
||||||
|
ac.published,
|
||||||
|
ac.updated,
|
||||||
|
ac.deleted,
|
||||||
|
ac.community_id,
|
||||||
|
ac.community_actor_id,
|
||||||
|
ac.community_local,
|
||||||
|
ac.community_name,
|
||||||
|
ac.banned,
|
||||||
|
ac.banned_from_community,
|
||||||
|
ac.creator_name,
|
||||||
|
ac.creator_avatar,
|
||||||
|
ac.score,
|
||||||
|
ac.upvotes,
|
||||||
|
ac.downvotes,
|
||||||
|
ac.hot_rank,
|
||||||
|
u.id as user_id,
|
||||||
|
coalesce(cl.score, 0) as my_vote,
|
||||||
|
(select cs.id::bool from comment_saved cs where u.id = cs.user_id and cs.comment_id = ac.id) as saved,
|
||||||
|
um.recipient_id,
|
||||||
|
(select actor_id from user_ u where u.id = um.recipient_id) as recipient_actor_id,
|
||||||
|
(select local from user_ u where u.id = um.recipient_id) as recipient_local
|
||||||
|
from user_ u
|
||||||
|
cross join (
|
||||||
|
select
|
||||||
|
ca.*
|
||||||
|
from comment_aggregates_fast ca
|
||||||
|
) ac
|
||||||
|
left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id
|
||||||
|
left join user_mention um on um.comment_id = ac.id
|
||||||
|
|
||||||
|
union all
|
||||||
|
|
||||||
|
select
|
||||||
|
ac.id,
|
||||||
|
um.id as user_mention_id,
|
||||||
|
ac.creator_id,
|
||||||
|
ac.creator_actor_id,
|
||||||
|
ac.creator_local,
|
||||||
|
ac.post_id,
|
||||||
|
ac.parent_id,
|
||||||
|
ac.content,
|
||||||
|
ac.removed,
|
||||||
|
um.read,
|
||||||
|
ac.published,
|
||||||
|
ac.updated,
|
||||||
|
ac.deleted,
|
||||||
|
ac.community_id,
|
||||||
|
ac.community_actor_id,
|
||||||
|
ac.community_local,
|
||||||
|
ac.community_name,
|
||||||
|
ac.banned,
|
||||||
|
ac.banned_from_community,
|
||||||
|
ac.creator_name,
|
||||||
|
ac.creator_avatar,
|
||||||
|
ac.score,
|
||||||
|
ac.upvotes,
|
||||||
|
ac.downvotes,
|
||||||
|
ac.hot_rank,
|
||||||
|
null as user_id,
|
||||||
|
null as my_vote,
|
||||||
|
null as saved,
|
||||||
|
um.recipient_id,
|
||||||
|
(select actor_id from user_ u where u.id = um.recipient_id) as recipient_actor_id,
|
||||||
|
(select local from user_ u where u.id = um.recipient_id) as recipient_local
|
||||||
|
from comment_aggregates_fast ac
|
||||||
|
left join user_mention um on um.comment_id = ac.id
|
||||||
|
;
|
||||||
|
|
||||||
|
-- Do the reply_view referencing the comment_fast_view
|
||||||
|
create view reply_fast_view as
|
||||||
|
with closereply as (
|
||||||
|
select
|
||||||
|
c2.id,
|
||||||
|
c2.creator_id as sender_id,
|
||||||
|
c.creator_id as recipient_id
|
||||||
|
from comment c
|
||||||
|
inner join comment c2 on c.id = c2.parent_id
|
||||||
|
where c2.creator_id != c.creator_id
|
||||||
|
-- Do union where post is null
|
||||||
|
union
|
||||||
|
select
|
||||||
|
c.id,
|
||||||
|
c.creator_id as sender_id,
|
||||||
|
p.creator_id as recipient_id
|
||||||
|
from comment c, post p
|
||||||
|
where c.post_id = p.id and c.parent_id is null and c.creator_id != p.creator_id
|
||||||
|
)
|
||||||
|
select cv.*,
|
||||||
|
closereply.recipient_id
|
||||||
|
from comment_fast_view cv, closereply
|
||||||
|
where closereply.id = cv.id
|
||||||
|
;
|
254
server/migrations/2020-07-12-100442_add_post_title_to_comments_view/up.sql
vendored
Normal file
254
server/migrations/2020-07-12-100442_add_post_title_to_comments_view/up.sql
vendored
Normal file
|
@ -0,0 +1,254 @@
|
||||||
|
drop view user_mention_view;
|
||||||
|
drop view reply_fast_view;
|
||||||
|
drop view comment_fast_view;
|
||||||
|
drop view comment_view;
|
||||||
|
|
||||||
|
drop view user_mention_fast_view;
|
||||||
|
drop table comment_aggregates_fast;
|
||||||
|
drop view comment_aggregates_view;
|
||||||
|
|
||||||
|
create view comment_aggregates_view as
|
||||||
|
select
|
||||||
|
ct.*,
|
||||||
|
-- post details
|
||||||
|
p."name" as post_name,
|
||||||
|
p.community_id,
|
||||||
|
-- community details
|
||||||
|
c.actor_id as community_actor_id,
|
||||||
|
c."local" as community_local,
|
||||||
|
c."name" as community_name,
|
||||||
|
-- creator details
|
||||||
|
u.banned as banned,
|
||||||
|
coalesce(cb.id, 0)::bool as banned_from_community,
|
||||||
|
u.actor_id as creator_actor_id,
|
||||||
|
u.local as creator_local,
|
||||||
|
u.name as creator_name,
|
||||||
|
u.published as creator_published,
|
||||||
|
u.avatar as creator_avatar,
|
||||||
|
-- score details
|
||||||
|
coalesce(cl.total, 0) as score,
|
||||||
|
coalesce(cl.up, 0) as upvotes,
|
||||||
|
coalesce(cl.down, 0) as downvotes,
|
||||||
|
hot_rank(coalesce(cl.total, 0), ct.published) as hot_rank
|
||||||
|
from comment ct
|
||||||
|
left join post p on ct.post_id = p.id
|
||||||
|
left join community c on p.community_id = c.id
|
||||||
|
left join user_ u on ct.creator_id = u.id
|
||||||
|
left join community_user_ban cb on ct.creator_id = cb.user_id and p.id = ct.post_id and p.community_id = cb.community_id
|
||||||
|
left join (
|
||||||
|
select
|
||||||
|
l.comment_id as id,
|
||||||
|
sum(l.score) as total,
|
||||||
|
count(case when l.score = 1 then 1 else null end) as up,
|
||||||
|
count(case when l.score = -1 then 1 else null end) as down
|
||||||
|
from comment_like l
|
||||||
|
group by comment_id
|
||||||
|
) as cl on cl.id = ct.id;
|
||||||
|
|
||||||
|
create or replace view comment_view as (
|
||||||
|
select
|
||||||
|
cav.*,
|
||||||
|
us.user_id as user_id,
|
||||||
|
us.my_vote as my_vote,
|
||||||
|
us.is_subbed::bool as subscribed,
|
||||||
|
us.is_saved::bool as saved
|
||||||
|
from comment_aggregates_view cav
|
||||||
|
cross join lateral (
|
||||||
|
select
|
||||||
|
u.id as user_id,
|
||||||
|
coalesce(cl.score, 0) as my_vote,
|
||||||
|
coalesce(cf.id, 0) as is_subbed,
|
||||||
|
coalesce(cs.id, 0) as is_saved
|
||||||
|
from user_ u
|
||||||
|
left join comment_like cl on u.id = cl.user_id and cav.id = cl.comment_id
|
||||||
|
left join comment_saved cs on u.id = cs.user_id and cs.comment_id = cav.id
|
||||||
|
left join community_follower cf on u.id = cf.user_id and cav.community_id = cf.community_id
|
||||||
|
) as us
|
||||||
|
|
||||||
|
union all
|
||||||
|
|
||||||
|
select
|
||||||
|
cav.*,
|
||||||
|
null as user_id,
|
||||||
|
null as my_vote,
|
||||||
|
null as subscribed,
|
||||||
|
null as saved
|
||||||
|
from comment_aggregates_view cav
|
||||||
|
);
|
||||||
|
|
||||||
|
create table comment_aggregates_fast as select * from comment_aggregates_view;
|
||||||
|
alter table comment_aggregates_fast add primary key (id);
|
||||||
|
|
||||||
|
create view comment_fast_view as
|
||||||
|
select
|
||||||
|
cav.*,
|
||||||
|
us.user_id as user_id,
|
||||||
|
us.my_vote as my_vote,
|
||||||
|
us.is_subbed::bool as subscribed,
|
||||||
|
us.is_saved::bool as saved
|
||||||
|
from comment_aggregates_fast cav
|
||||||
|
cross join lateral (
|
||||||
|
select
|
||||||
|
u.id as user_id,
|
||||||
|
coalesce(cl.score, 0) as my_vote,
|
||||||
|
coalesce(cf.id, 0) as is_subbed,
|
||||||
|
coalesce(cs.id, 0) as is_saved
|
||||||
|
from user_ u
|
||||||
|
left join comment_like cl on u.id = cl.user_id and cav.id = cl.comment_id
|
||||||
|
left join comment_saved cs on u.id = cs.user_id and cs.comment_id = cav.id
|
||||||
|
left join community_follower cf on u.id = cf.user_id and cav.community_id = cf.community_id
|
||||||
|
) as us
|
||||||
|
|
||||||
|
union all
|
||||||
|
|
||||||
|
select
|
||||||
|
cav.*,
|
||||||
|
null as user_id,
|
||||||
|
null as my_vote,
|
||||||
|
null as subscribed,
|
||||||
|
null as saved
|
||||||
|
from comment_aggregates_fast cav;
|
||||||
|
|
||||||
|
create view user_mention_view as
|
||||||
|
select
|
||||||
|
c.id,
|
||||||
|
um.id as user_mention_id,
|
||||||
|
c.creator_id,
|
||||||
|
c.creator_actor_id,
|
||||||
|
c.creator_local,
|
||||||
|
c.post_id,
|
||||||
|
c.post_name,
|
||||||
|
c.parent_id,
|
||||||
|
c.content,
|
||||||
|
c.removed,
|
||||||
|
um.read,
|
||||||
|
c.published,
|
||||||
|
c.updated,
|
||||||
|
c.deleted,
|
||||||
|
c.community_id,
|
||||||
|
c.community_actor_id,
|
||||||
|
c.community_local,
|
||||||
|
c.community_name,
|
||||||
|
c.banned,
|
||||||
|
c.banned_from_community,
|
||||||
|
c.creator_name,
|
||||||
|
c.creator_avatar,
|
||||||
|
c.score,
|
||||||
|
c.upvotes,
|
||||||
|
c.downvotes,
|
||||||
|
c.hot_rank,
|
||||||
|
c.user_id,
|
||||||
|
c.my_vote,
|
||||||
|
c.saved,
|
||||||
|
um.recipient_id,
|
||||||
|
(select actor_id from user_ u where u.id = um.recipient_id) as recipient_actor_id,
|
||||||
|
(select local from user_ u where u.id = um.recipient_id) as recipient_local
|
||||||
|
from user_mention um, comment_view c
|
||||||
|
where um.comment_id = c.id;
|
||||||
|
|
||||||
|
create view user_mention_fast_view as
|
||||||
|
select
|
||||||
|
ac.id,
|
||||||
|
um.id as user_mention_id,
|
||||||
|
ac.creator_id,
|
||||||
|
ac.creator_actor_id,
|
||||||
|
ac.creator_local,
|
||||||
|
ac.post_id,
|
||||||
|
ac.post_name,
|
||||||
|
ac.parent_id,
|
||||||
|
ac.content,
|
||||||
|
ac.removed,
|
||||||
|
um.read,
|
||||||
|
ac.published,
|
||||||
|
ac.updated,
|
||||||
|
ac.deleted,
|
||||||
|
ac.community_id,
|
||||||
|
ac.community_actor_id,
|
||||||
|
ac.community_local,
|
||||||
|
ac.community_name,
|
||||||
|
ac.banned,
|
||||||
|
ac.banned_from_community,
|
||||||
|
ac.creator_name,
|
||||||
|
ac.creator_avatar,
|
||||||
|
ac.score,
|
||||||
|
ac.upvotes,
|
||||||
|
ac.downvotes,
|
||||||
|
ac.hot_rank,
|
||||||
|
u.id as user_id,
|
||||||
|
coalesce(cl.score, 0) as my_vote,
|
||||||
|
(select cs.id::bool from comment_saved cs where u.id = cs.user_id and cs.comment_id = ac.id) as saved,
|
||||||
|
um.recipient_id,
|
||||||
|
(select actor_id from user_ u where u.id = um.recipient_id) as recipient_actor_id,
|
||||||
|
(select local from user_ u where u.id = um.recipient_id) as recipient_local
|
||||||
|
from user_ u
|
||||||
|
cross join (
|
||||||
|
select
|
||||||
|
ca.*
|
||||||
|
from comment_aggregates_fast ca
|
||||||
|
) ac
|
||||||
|
left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id
|
||||||
|
left join user_mention um on um.comment_id = ac.id
|
||||||
|
|
||||||
|
union all
|
||||||
|
|
||||||
|
select
|
||||||
|
ac.id,
|
||||||
|
um.id as user_mention_id,
|
||||||
|
ac.creator_id,
|
||||||
|
ac.creator_actor_id,
|
||||||
|
ac.creator_local,
|
||||||
|
ac.post_id,
|
||||||
|
ac.post_name,
|
||||||
|
ac.parent_id,
|
||||||
|
ac.content,
|
||||||
|
ac.removed,
|
||||||
|
um.read,
|
||||||
|
ac.published,
|
||||||
|
ac.updated,
|
||||||
|
ac.deleted,
|
||||||
|
ac.community_id,
|
||||||
|
ac.community_actor_id,
|
||||||
|
ac.community_local,
|
||||||
|
ac.community_name,
|
||||||
|
ac.banned,
|
||||||
|
ac.banned_from_community,
|
||||||
|
ac.creator_name,
|
||||||
|
ac.creator_avatar,
|
||||||
|
ac.score,
|
||||||
|
ac.upvotes,
|
||||||
|
ac.downvotes,
|
||||||
|
ac.hot_rank,
|
||||||
|
null as user_id,
|
||||||
|
null as my_vote,
|
||||||
|
null as saved,
|
||||||
|
um.recipient_id,
|
||||||
|
(select actor_id from user_ u where u.id = um.recipient_id) as recipient_actor_id,
|
||||||
|
(select local from user_ u where u.id = um.recipient_id) as recipient_local
|
||||||
|
from comment_aggregates_fast ac
|
||||||
|
left join user_mention um on um.comment_id = ac.id
|
||||||
|
;
|
||||||
|
|
||||||
|
-- Do the reply_view referencing the comment_fast_view
|
||||||
|
create view reply_fast_view as
|
||||||
|
with closereply as (
|
||||||
|
select
|
||||||
|
c2.id,
|
||||||
|
c2.creator_id as sender_id,
|
||||||
|
c.creator_id as recipient_id
|
||||||
|
from comment c
|
||||||
|
inner join comment c2 on c.id = c2.parent_id
|
||||||
|
where c2.creator_id != c.creator_id
|
||||||
|
-- Do union where post is null
|
||||||
|
union
|
||||||
|
select
|
||||||
|
c.id,
|
||||||
|
c.creator_id as sender_id,
|
||||||
|
p.creator_id as recipient_id
|
||||||
|
from comment c, post p
|
||||||
|
where c.post_id = p.id and c.parent_id is null and c.creator_id != p.creator_id
|
||||||
|
)
|
||||||
|
select cv.*,
|
||||||
|
closereply.recipient_id
|
||||||
|
from comment_fast_view cv, closereply
|
||||||
|
where closereply.id = cv.id
|
||||||
|
;
|
73
server/src/api/claims.rs
Normal file
73
server/src/api/claims.rs
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
use diesel::{result::Error, PgConnection};
|
||||||
|
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, TokenData, Validation};
|
||||||
|
use lemmy_db::{user::User_, Crud};
|
||||||
|
use lemmy_utils::{is_email_regex, settings::Settings};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
type Jwt = String;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct Claims {
|
||||||
|
pub id: i32,
|
||||||
|
pub username: String,
|
||||||
|
pub iss: String,
|
||||||
|
pub show_nsfw: bool,
|
||||||
|
pub theme: String,
|
||||||
|
pub default_sort_type: i16,
|
||||||
|
pub default_listing_type: i16,
|
||||||
|
pub lang: String,
|
||||||
|
pub avatar: Option<String>,
|
||||||
|
pub show_avatars: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Claims {
|
||||||
|
pub fn decode(jwt: &str) -> Result<TokenData<Claims>, jsonwebtoken::errors::Error> {
|
||||||
|
let v = Validation {
|
||||||
|
validate_exp: false,
|
||||||
|
..Validation::default()
|
||||||
|
};
|
||||||
|
decode::<Claims>(
|
||||||
|
&jwt,
|
||||||
|
&DecodingKey::from_secret(Settings::get().jwt_secret.as_ref()),
|
||||||
|
&v,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn jwt(user: User_, hostname: String) -> Jwt {
|
||||||
|
let my_claims = Claims {
|
||||||
|
id: user.id,
|
||||||
|
username: user.name.to_owned(),
|
||||||
|
iss: hostname,
|
||||||
|
show_nsfw: user.show_nsfw,
|
||||||
|
theme: user.theme.to_owned(),
|
||||||
|
default_sort_type: user.default_sort_type,
|
||||||
|
default_listing_type: user.default_listing_type,
|
||||||
|
lang: user.lang.to_owned(),
|
||||||
|
avatar: user.avatar.to_owned(),
|
||||||
|
show_avatars: user.show_avatars.to_owned(),
|
||||||
|
};
|
||||||
|
encode(
|
||||||
|
&Header::default(),
|
||||||
|
&my_claims,
|
||||||
|
&EncodingKey::from_secret(Settings::get().jwt_secret.as_ref()),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: move these into user?
|
||||||
|
pub fn find_by_email_or_username(
|
||||||
|
conn: &PgConnection,
|
||||||
|
username_or_email: &str,
|
||||||
|
) -> Result<User_, Error> {
|
||||||
|
if is_email_regex(username_or_email) {
|
||||||
|
User_::find_by_email(conn, username_or_email)
|
||||||
|
} else {
|
||||||
|
User_::find_by_username(conn, username_or_email)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_by_jwt(conn: &PgConnection, jwt: &str) -> Result<User_, Error> {
|
||||||
|
let claims: Claims = Claims::decode(&jwt).expect("Invalid token").claims;
|
||||||
|
User_::read(&conn, claims.id)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,21 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
api::{APIError, Oper, Perform},
|
api::{claims::Claims, APIError, Oper, Perform},
|
||||||
apub::{ApubLikeableType, ApubObjectType},
|
apub::{ApubLikeableType, ApubObjectType},
|
||||||
blocking,
|
blocking,
|
||||||
db::{
|
websocket::{
|
||||||
|
server::{JoinCommunityRoom, SendComment},
|
||||||
|
UserOperation,
|
||||||
|
WebsocketInfo,
|
||||||
|
},
|
||||||
|
DbPool,
|
||||||
|
LemmyError,
|
||||||
|
};
|
||||||
|
use lemmy_db::{
|
||||||
comment::*,
|
comment::*,
|
||||||
comment_view::*,
|
comment_view::*,
|
||||||
community_view::*,
|
community_view::*,
|
||||||
moderator::*,
|
moderator::*,
|
||||||
|
naive_now,
|
||||||
post::*,
|
post::*,
|
||||||
site_view::*,
|
site_view::*,
|
||||||
user::*,
|
user::*,
|
||||||
|
@ -17,19 +26,14 @@ use crate::{
|
||||||
ListingType,
|
ListingType,
|
||||||
Saveable,
|
Saveable,
|
||||||
SortType,
|
SortType,
|
||||||
},
|
};
|
||||||
naive_now,
|
use lemmy_utils::{
|
||||||
|
make_apub_endpoint,
|
||||||
remove_slurs,
|
remove_slurs,
|
||||||
scrape_text_for_mentions,
|
scrape_text_for_mentions,
|
||||||
send_email,
|
send_email,
|
||||||
settings::Settings,
|
settings::Settings,
|
||||||
websocket::{
|
EndpointType,
|
||||||
server::{JoinCommunityRoom, SendComment},
|
|
||||||
UserOperation,
|
|
||||||
WebsocketInfo,
|
|
||||||
},
|
|
||||||
DbPool,
|
|
||||||
LemmyError,
|
|
||||||
MentionData,
|
MentionData,
|
||||||
};
|
};
|
||||||
use log::error;
|
use log::error;
|
||||||
|
@ -155,7 +159,9 @@ impl Perform for Oper<CreateComment> {
|
||||||
|
|
||||||
let inserted_comment_id = inserted_comment.id;
|
let inserted_comment_id = inserted_comment.id;
|
||||||
let updated_comment: Comment = match blocking(pool, move |conn| {
|
let updated_comment: Comment = match blocking(pool, move |conn| {
|
||||||
Comment::update_ap_id(&conn, inserted_comment_id)
|
let apub_id =
|
||||||
|
make_apub_endpoint(EndpointType::Comment, &inserted_comment_id.to_string()).to_string();
|
||||||
|
Comment::update_ap_id(&conn, inserted_comment_id, apub_id)
|
||||||
})
|
})
|
||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,26 +1,24 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
api::{APIError, Oper, Perform},
|
api::{claims::Claims, APIError, Oper, Perform},
|
||||||
apub::{
|
apub::ActorType,
|
||||||
extensions::signatures::generate_actor_keypair,
|
|
||||||
make_apub_endpoint,
|
|
||||||
ActorType,
|
|
||||||
EndpointType,
|
|
||||||
},
|
|
||||||
blocking,
|
blocking,
|
||||||
db::{Bannable, Crud, Followable, Joinable, SortType},
|
|
||||||
is_valid_community_name,
|
|
||||||
naive_from_unix,
|
|
||||||
naive_now,
|
|
||||||
slur_check,
|
|
||||||
slurs_vec_to_str,
|
|
||||||
websocket::{
|
websocket::{
|
||||||
server::{JoinCommunityRoom, SendCommunityRoomMessage},
|
server::{JoinCommunityRoom, SendCommunityRoomMessage},
|
||||||
UserOperation,
|
UserOperation,
|
||||||
WebsocketInfo,
|
WebsocketInfo,
|
||||||
},
|
},
|
||||||
DbPool,
|
DbPool,
|
||||||
LemmyError,
|
};
|
||||||
|
use lemmy_db::{naive_now, Bannable, Crud, Followable, Joinable, SortType};
|
||||||
|
use lemmy_utils::{
|
||||||
|
generate_actor_keypair,
|
||||||
|
is_valid_community_name,
|
||||||
|
make_apub_endpoint,
|
||||||
|
naive_from_unix,
|
||||||
|
slur_check,
|
||||||
|
slurs_vec_to_str,
|
||||||
|
EndpointType,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
use crate::{
|
use crate::{websocket::WebsocketInfo, DbPool, LemmyError};
|
||||||
db::{community::*, community_view::*, moderator::*, site::*, user::*, user_view::*},
|
|
||||||
websocket::WebsocketInfo,
|
|
||||||
DbPool,
|
|
||||||
LemmyError,
|
|
||||||
};
|
|
||||||
use actix_web::client::Client;
|
use actix_web::client::Client;
|
||||||
|
use lemmy_db::{community::*, community_view::*, moderator::*, site::*, user::*, user_view::*};
|
||||||
|
|
||||||
|
pub mod claims;
|
||||||
pub mod comment;
|
pub mod comment;
|
||||||
pub mod community;
|
pub mod community;
|
||||||
pub mod post;
|
pub mod post;
|
||||||
|
|
|
@ -1,11 +1,21 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
api::{APIError, Oper, Perform},
|
api::{claims::Claims, APIError, Oper, Perform},
|
||||||
apub::{ApubLikeableType, ApubObjectType},
|
apub::{ApubLikeableType, ApubObjectType},
|
||||||
blocking,
|
blocking,
|
||||||
db::{
|
fetch_iframely_and_pictrs_data,
|
||||||
|
websocket::{
|
||||||
|
server::{JoinCommunityRoom, JoinPostRoom, SendPost},
|
||||||
|
UserOperation,
|
||||||
|
WebsocketInfo,
|
||||||
|
},
|
||||||
|
DbPool,
|
||||||
|
LemmyError,
|
||||||
|
};
|
||||||
|
use lemmy_db::{
|
||||||
comment_view::*,
|
comment_view::*,
|
||||||
community_view::*,
|
community_view::*,
|
||||||
moderator::*,
|
moderator::*,
|
||||||
|
naive_now,
|
||||||
post::*,
|
post::*,
|
||||||
post_view::*,
|
post_view::*,
|
||||||
site::*,
|
site::*,
|
||||||
|
@ -17,18 +27,13 @@ use crate::{
|
||||||
ListingType,
|
ListingType,
|
||||||
Saveable,
|
Saveable,
|
||||||
SortType,
|
SortType,
|
||||||
},
|
};
|
||||||
fetch_iframely_and_pictrs_data,
|
use lemmy_utils::{
|
||||||
naive_now,
|
is_valid_post_title,
|
||||||
|
make_apub_endpoint,
|
||||||
slur_check,
|
slur_check,
|
||||||
slurs_vec_to_str,
|
slurs_vec_to_str,
|
||||||
websocket::{
|
EndpointType,
|
||||||
server::{JoinCommunityRoom, JoinPostRoom, SendPost},
|
|
||||||
UserOperation,
|
|
||||||
WebsocketInfo,
|
|
||||||
},
|
|
||||||
DbPool,
|
|
||||||
LemmyError,
|
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
@ -71,6 +76,7 @@ pub struct GetPosts {
|
||||||
page: Option<i64>,
|
page: Option<i64>,
|
||||||
limit: Option<i64>,
|
limit: Option<i64>,
|
||||||
pub community_id: Option<i32>,
|
pub community_id: Option<i32>,
|
||||||
|
pub community_name: Option<String>,
|
||||||
auth: Option<String>,
|
auth: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,6 +142,10 @@ impl Perform for Oper<CreatePost> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !is_valid_post_title(&data.name) {
|
||||||
|
return Err(APIError::err("invalid_post_title").into());
|
||||||
|
}
|
||||||
|
|
||||||
let user_id = claims.id;
|
let user_id = claims.id;
|
||||||
|
|
||||||
// Check for a community ban
|
// Check for a community ban
|
||||||
|
@ -157,7 +167,7 @@ impl Perform for Oper<CreatePost> {
|
||||||
fetch_iframely_and_pictrs_data(&self.client, data.url.to_owned()).await;
|
fetch_iframely_and_pictrs_data(&self.client, data.url.to_owned()).await;
|
||||||
|
|
||||||
let post_form = PostForm {
|
let post_form = PostForm {
|
||||||
name: data.name.to_owned(),
|
name: data.name.trim().to_owned(),
|
||||||
url: data.url.to_owned(),
|
url: data.url.to_owned(),
|
||||||
body: data.body.to_owned(),
|
body: data.body.to_owned(),
|
||||||
community_id: data.community_id,
|
community_id: data.community_id,
|
||||||
|
@ -191,8 +201,13 @@ impl Perform for Oper<CreatePost> {
|
||||||
};
|
};
|
||||||
|
|
||||||
let inserted_post_id = inserted_post.id;
|
let inserted_post_id = inserted_post.id;
|
||||||
let updated_post =
|
let updated_post = match blocking(pool, move |conn| {
|
||||||
match blocking(pool, move |conn| Post::update_ap_id(conn, inserted_post_id)).await? {
|
let apub_id =
|
||||||
|
make_apub_endpoint(EndpointType::Post, &inserted_post_id.to_string()).to_string();
|
||||||
|
Post::update_ap_id(conn, inserted_post_id, apub_id)
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
{
|
||||||
Ok(post) => post,
|
Ok(post) => post,
|
||||||
Err(_e) => return Err(APIError::err("couldnt_create_post").into()),
|
Err(_e) => return Err(APIError::err("couldnt_create_post").into()),
|
||||||
};
|
};
|
||||||
|
@ -361,12 +376,14 @@ impl Perform for Oper<GetPosts> {
|
||||||
let page = data.page;
|
let page = data.page;
|
||||||
let limit = data.limit;
|
let limit = data.limit;
|
||||||
let community_id = data.community_id;
|
let community_id = data.community_id;
|
||||||
|
let community_name = data.community_name.to_owned();
|
||||||
let posts = match blocking(pool, move |conn| {
|
let posts = match blocking(pool, move |conn| {
|
||||||
PostQueryBuilder::create(conn)
|
PostQueryBuilder::create(conn)
|
||||||
.listing_type(type_)
|
.listing_type(type_)
|
||||||
.sort(&sort)
|
.sort(&sort)
|
||||||
.show_nsfw(show_nsfw)
|
.show_nsfw(show_nsfw)
|
||||||
.for_community_id(community_id)
|
.for_community_id(community_id)
|
||||||
|
.for_community_name(community_name)
|
||||||
.my_user_id(user_id)
|
.my_user_id(user_id)
|
||||||
.page(page)
|
.page(page)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
|
@ -512,6 +529,10 @@ impl Perform for Oper<EditPost> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !is_valid_post_title(&data.name) {
|
||||||
|
return Err(APIError::err("invalid_post_title").into());
|
||||||
|
}
|
||||||
|
|
||||||
let claims = match Claims::decode(&data.auth) {
|
let claims = match Claims::decode(&data.auth) {
|
||||||
Ok(claims) => claims.claims,
|
Ok(claims) => claims.claims,
|
||||||
Err(_e) => return Err(APIError::err("not_logged_in").into()),
|
Err(_e) => return Err(APIError::err("not_logged_in").into()),
|
||||||
|
@ -561,7 +582,7 @@ impl Perform for Oper<EditPost> {
|
||||||
let read_post = blocking(pool, move |conn| Post::read(conn, edit_id)).await??;
|
let read_post = blocking(pool, move |conn| Post::read(conn, edit_id)).await??;
|
||||||
|
|
||||||
let post_form = PostForm {
|
let post_form = PostForm {
|
||||||
name: data.name.to_owned(),
|
name: data.name.trim().to_owned(),
|
||||||
url: data.url.to_owned(),
|
url: data.url.to_owned(),
|
||||||
body: data.body.to_owned(),
|
body: data.body.to_owned(),
|
||||||
creator_id: data.creator_id.to_owned(),
|
creator_id: data.creator_id.to_owned(),
|
||||||
|
|
|
@ -1,31 +1,28 @@
|
||||||
use super::user::Register;
|
use super::user::Register;
|
||||||
use crate::{
|
use crate::{
|
||||||
api::{APIError, Oper, Perform},
|
api::{claims::Claims, APIError, Oper, Perform},
|
||||||
apub::fetcher::search_by_apub_id,
|
apub::fetcher::search_by_apub_id,
|
||||||
blocking,
|
blocking,
|
||||||
db::{
|
websocket::{server::SendAllMessage, UserOperation, WebsocketInfo},
|
||||||
|
DbPool,
|
||||||
|
LemmyError,
|
||||||
|
};
|
||||||
|
use lemmy_db::{
|
||||||
category::*,
|
category::*,
|
||||||
comment_view::*,
|
comment_view::*,
|
||||||
community_view::*,
|
community_view::*,
|
||||||
moderator::*,
|
moderator::*,
|
||||||
moderator_views::*,
|
moderator_views::*,
|
||||||
|
naive_now,
|
||||||
post_view::*,
|
post_view::*,
|
||||||
site::*,
|
site::*,
|
||||||
site_view::*,
|
site_view::*,
|
||||||
user::*,
|
|
||||||
user_view::*,
|
user_view::*,
|
||||||
Crud,
|
Crud,
|
||||||
SearchType,
|
SearchType,
|
||||||
SortType,
|
SortType,
|
||||||
},
|
|
||||||
naive_now,
|
|
||||||
settings::Settings,
|
|
||||||
slur_check,
|
|
||||||
slurs_vec_to_str,
|
|
||||||
websocket::{server::SendAllMessage, UserOperation, WebsocketInfo},
|
|
||||||
DbPool,
|
|
||||||
LemmyError,
|
|
||||||
};
|
};
|
||||||
|
use lemmy_utils::{settings::Settings, slur_check, slurs_vec_to_str};
|
||||||
use log::{debug, info};
|
use log::{debug, info};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
|
@ -1,18 +1,23 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
api::{APIError, Oper, Perform},
|
api::{claims::Claims, APIError, Oper, Perform},
|
||||||
apub::{
|
apub::ApubObjectType,
|
||||||
extensions::signatures::generate_actor_keypair,
|
|
||||||
make_apub_endpoint,
|
|
||||||
ApubObjectType,
|
|
||||||
EndpointType,
|
|
||||||
},
|
|
||||||
blocking,
|
blocking,
|
||||||
db::{
|
websocket::{
|
||||||
|
server::{JoinUserRoom, SendAllMessage, SendUserRoomMessage},
|
||||||
|
UserOperation,
|
||||||
|
WebsocketInfo,
|
||||||
|
},
|
||||||
|
DbPool,
|
||||||
|
LemmyError,
|
||||||
|
};
|
||||||
|
use bcrypt::verify;
|
||||||
|
use lemmy_db::{
|
||||||
comment::*,
|
comment::*,
|
||||||
comment_view::*,
|
comment_view::*,
|
||||||
community::*,
|
community::*,
|
||||||
community_view::*,
|
community_view::*,
|
||||||
moderator::*,
|
moderator::*,
|
||||||
|
naive_now,
|
||||||
password_reset_request::*,
|
password_reset_request::*,
|
||||||
post::*,
|
post::*,
|
||||||
post_view::*,
|
post_view::*,
|
||||||
|
@ -29,25 +34,20 @@ use crate::{
|
||||||
Joinable,
|
Joinable,
|
||||||
ListingType,
|
ListingType,
|
||||||
SortType,
|
SortType,
|
||||||
},
|
};
|
||||||
|
use lemmy_utils::{
|
||||||
|
generate_actor_keypair,
|
||||||
generate_random_string,
|
generate_random_string,
|
||||||
is_valid_username,
|
is_valid_username,
|
||||||
|
make_apub_endpoint,
|
||||||
naive_from_unix,
|
naive_from_unix,
|
||||||
naive_now,
|
|
||||||
remove_slurs,
|
remove_slurs,
|
||||||
send_email,
|
send_email,
|
||||||
settings::Settings,
|
settings::Settings,
|
||||||
slur_check,
|
slur_check,
|
||||||
slurs_vec_to_str,
|
slurs_vec_to_str,
|
||||||
websocket::{
|
EndpointType,
|
||||||
server::{JoinUserRoom, SendAllMessage, SendUserRoomMessage},
|
|
||||||
UserOperation,
|
|
||||||
WebsocketInfo,
|
|
||||||
},
|
|
||||||
DbPool,
|
|
||||||
LemmyError,
|
|
||||||
};
|
};
|
||||||
use bcrypt::verify;
|
|
||||||
use log::error;
|
use log::error;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
@ -264,7 +264,7 @@ impl Perform for Oper<Login> {
|
||||||
// Fetch that username / email
|
// Fetch that username / email
|
||||||
let username_or_email = data.username_or_email.clone();
|
let username_or_email = data.username_or_email.clone();
|
||||||
let user = match blocking(pool, move |conn| {
|
let user = match blocking(pool, move |conn| {
|
||||||
User_::find_by_email_or_username(conn, &username_or_email)
|
Claims::find_by_email_or_username(conn, &username_or_email)
|
||||||
})
|
})
|
||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
|
@ -279,7 +279,9 @@ impl Perform for Oper<Login> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the jwt
|
// Return the jwt
|
||||||
Ok(LoginResponse { jwt: user.jwt() })
|
Ok(LoginResponse {
|
||||||
|
jwt: Claims::jwt(user, Settings::get().hostname),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -421,7 +423,7 @@ impl Perform for Oper<Register> {
|
||||||
|
|
||||||
// Return the jwt
|
// Return the jwt
|
||||||
Ok(LoginResponse {
|
Ok(LoginResponse {
|
||||||
jwt: inserted_user.jwt(),
|
jwt: Claims::jwt(inserted_user, Settings::get().hostname),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -451,6 +453,11 @@ impl Perform for Oper<SaveUserSettings> {
|
||||||
None => read_user.email,
|
None => read_user.email,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let avatar = match &data.avatar {
|
||||||
|
Some(avatar) => Some(avatar.to_owned()),
|
||||||
|
None => read_user.avatar,
|
||||||
|
};
|
||||||
|
|
||||||
let password_encrypted = match &data.new_password {
|
let password_encrypted = match &data.new_password {
|
||||||
Some(new_password) => {
|
Some(new_password) => {
|
||||||
match &data.new_password_verify {
|
match &data.new_password_verify {
|
||||||
|
@ -488,7 +495,7 @@ impl Perform for Oper<SaveUserSettings> {
|
||||||
name: read_user.name,
|
name: read_user.name,
|
||||||
email,
|
email,
|
||||||
matrix_user_id: data.matrix_user_id.to_owned(),
|
matrix_user_id: data.matrix_user_id.to_owned(),
|
||||||
avatar: data.avatar.to_owned(),
|
avatar,
|
||||||
password_encrypted,
|
password_encrypted,
|
||||||
preferred_username: read_user.preferred_username,
|
preferred_username: read_user.preferred_username,
|
||||||
updated: Some(naive_now()),
|
updated: Some(naive_now()),
|
||||||
|
@ -527,7 +534,7 @@ impl Perform for Oper<SaveUserSettings> {
|
||||||
|
|
||||||
// Return the jwt
|
// Return the jwt
|
||||||
Ok(LoginResponse {
|
Ok(LoginResponse {
|
||||||
jwt: updated_user.jwt(),
|
jwt: Claims::jwt(updated_user, Settings::get().hostname),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1150,7 +1157,7 @@ impl Perform for Oper<PasswordChange> {
|
||||||
|
|
||||||
// Return the jwt
|
// Return the jwt
|
||||||
Ok(LoginResponse {
|
Ok(LoginResponse {
|
||||||
jwt: updated_user.jwt(),
|
jwt: Claims::jwt(updated_user, Settings::get().hostname),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1208,7 +1215,12 @@ impl Perform for Oper<CreatePrivateMessage> {
|
||||||
|
|
||||||
let inserted_private_message_id = inserted_private_message.id;
|
let inserted_private_message_id = inserted_private_message.id;
|
||||||
let updated_private_message = match blocking(pool, move |conn| {
|
let updated_private_message = match blocking(pool, move |conn| {
|
||||||
PrivateMessage::update_ap_id(&conn, inserted_private_message_id)
|
let apub_id = make_apub_endpoint(
|
||||||
|
EndpointType::PrivateMessage,
|
||||||
|
&inserted_private_message_id.to_string(),
|
||||||
|
)
|
||||||
|
.to_string();
|
||||||
|
PrivateMessage::update_ap_id(&conn, inserted_private_message_id, apub_id)
|
||||||
})
|
})
|
||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
apub::{extensions::signatures::sign, is_apub_id_valid, ActorType},
|
apub::{
|
||||||
db::{activity::insert_activity, community::Community, user::User_},
|
community::do_announce,
|
||||||
|
extensions::signatures::sign,
|
||||||
|
insert_activity,
|
||||||
|
is_apub_id_valid,
|
||||||
|
ActorType,
|
||||||
|
},
|
||||||
request::retry_custom,
|
request::retry_custom,
|
||||||
DbPool,
|
DbPool,
|
||||||
LemmyError,
|
LemmyError,
|
||||||
};
|
};
|
||||||
use activitystreams::{context, object::properties::ObjectProperties, public, Activity, Base};
|
use activitystreams::{context, object::properties::ObjectProperties, public, Activity, Base};
|
||||||
use actix_web::client::Client;
|
use actix_web::client::Client;
|
||||||
|
use lemmy_db::{community::Community, user::User_};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
@ -43,7 +49,7 @@ where
|
||||||
|
|
||||||
// if this is a local community, we need to do an announce from the community instead
|
// if this is a local community, we need to do an announce from the community instead
|
||||||
if community.local {
|
if community.local {
|
||||||
Community::do_announce(activity, &community, creator, client, pool).await?;
|
do_announce(activity, &community, creator, client, pool).await?;
|
||||||
} else {
|
} else {
|
||||||
send_activity(client, &activity, creator, to).await?;
|
send_activity(client, &activity, creator, to).await?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +1,16 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
apub::{
|
apub::{
|
||||||
activities::{populate_object_props, send_activity_to_community},
|
activities::{populate_object_props, send_activity_to_community},
|
||||||
create_apub_response,
|
create_apub_response, create_apub_tombstone_response, create_tombstone, fetch_webfinger_url,
|
||||||
create_apub_tombstone_response,
|
|
||||||
create_tombstone,
|
|
||||||
fetch_webfinger_url,
|
|
||||||
fetcher::{
|
fetcher::{
|
||||||
get_or_fetch_and_insert_remote_comment,
|
get_or_fetch_and_insert_remote_comment, get_or_fetch_and_insert_remote_post,
|
||||||
get_or_fetch_and_insert_remote_post,
|
|
||||||
get_or_fetch_and_upsert_remote_user,
|
get_or_fetch_and_upsert_remote_user,
|
||||||
},
|
},
|
||||||
ActorType,
|
ActorType, ApubLikeableType, ApubObjectType, FromApub, ToApub,
|
||||||
ApubLikeableType,
|
|
||||||
ApubObjectType,
|
|
||||||
FromApub,
|
|
||||||
ToApub,
|
|
||||||
},
|
},
|
||||||
blocking,
|
blocking,
|
||||||
convert_datetime,
|
|
||||||
db::{
|
|
||||||
comment::{Comment, CommentForm},
|
|
||||||
community::Community,
|
|
||||||
post::Post,
|
|
||||||
user::User_,
|
|
||||||
Crud,
|
|
||||||
},
|
|
||||||
routes::DbPoolParam,
|
routes::DbPoolParam,
|
||||||
scrape_text_for_mentions,
|
DbPool, LemmyError,
|
||||||
DbPool,
|
|
||||||
LemmyError,
|
|
||||||
MentionData,
|
|
||||||
};
|
};
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
activity::{Create, Delete, Dislike, Like, Remove, Undo, Update},
|
activity::{Create, Delete, Dislike, Like, Remove, Undo, Update},
|
||||||
|
@ -40,6 +21,14 @@ use activitystreams::{
|
||||||
use activitystreams_new::object::Tombstone;
|
use activitystreams_new::object::Tombstone;
|
||||||
use actix_web::{body::Body, client::Client, web::Path, HttpResponse};
|
use actix_web::{body::Body, client::Client, web::Path, HttpResponse};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use lemmy_db::{
|
||||||
|
comment::{Comment, CommentForm},
|
||||||
|
community::Community,
|
||||||
|
post::Post,
|
||||||
|
user::User_,
|
||||||
|
Crud,
|
||||||
|
};
|
||||||
|
use lemmy_utils::{convert_datetime, scrape_text_for_mentions, MentionData};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
@ -123,7 +112,7 @@ impl FromApub for CommentForm {
|
||||||
|
|
||||||
/// Parse an ActivityPub note received from another instance into a Lemmy comment
|
/// Parse an ActivityPub note received from another instance into a Lemmy comment
|
||||||
async fn from_apub(
|
async fn from_apub(
|
||||||
note: &mut Note,
|
note: &Note,
|
||||||
client: &Client,
|
client: &Client,
|
||||||
pool: &DbPool,
|
pool: &DbPool,
|
||||||
) -> Result<CommentForm, LemmyError> {
|
) -> Result<CommentForm, LemmyError> {
|
||||||
|
|
|
@ -1,35 +1,18 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
apub::{
|
apub::{
|
||||||
activities::{populate_object_props, send_activity},
|
activities::{populate_object_props, send_activity},
|
||||||
create_apub_response,
|
create_apub_response, create_apub_tombstone_response, create_tombstone,
|
||||||
create_apub_tombstone_response,
|
|
||||||
create_tombstone,
|
|
||||||
extensions::group_extensions::GroupExtension,
|
extensions::group_extensions::GroupExtension,
|
||||||
fetcher::get_or_fetch_and_upsert_remote_user,
|
fetcher::get_or_fetch_and_upsert_remote_user,
|
||||||
get_shared_inbox,
|
get_shared_inbox, insert_activity, ActorType, FromApub, GroupExt, ToApub,
|
||||||
ActorType,
|
|
||||||
FromApub,
|
|
||||||
GroupExt,
|
|
||||||
ToApub,
|
|
||||||
},
|
},
|
||||||
blocking,
|
blocking,
|
||||||
convert_datetime,
|
|
||||||
db::{
|
|
||||||
activity::insert_activity,
|
|
||||||
community::{Community, CommunityForm},
|
|
||||||
community_view::{CommunityFollowerView, CommunityModeratorView},
|
|
||||||
user::User_,
|
|
||||||
},
|
|
||||||
naive_now,
|
|
||||||
routes::DbPoolParam,
|
routes::DbPoolParam,
|
||||||
DbPool,
|
DbPool, LemmyError,
|
||||||
LemmyError,
|
|
||||||
};
|
};
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
activity::{Accept, Announce, Delete, Remove, Undo},
|
activity::{Accept, Announce, Delete, Remove, Undo},
|
||||||
Activity,
|
Activity, Base, BaseBox,
|
||||||
Base,
|
|
||||||
BaseBox,
|
|
||||||
};
|
};
|
||||||
use activitystreams_ext::Ext2;
|
use activitystreams_ext::Ext2;
|
||||||
use activitystreams_new::{
|
use activitystreams_new::{
|
||||||
|
@ -44,6 +27,13 @@ use activitystreams_new::{
|
||||||
};
|
};
|
||||||
use actix_web::{body::Body, client::Client, web, HttpResponse};
|
use actix_web::{body::Body, client::Client, web, HttpResponse};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use lemmy_db::{
|
||||||
|
community::{Community, CommunityForm},
|
||||||
|
community_view::{CommunityFollowerView, CommunityModeratorView},
|
||||||
|
naive_now,
|
||||||
|
user::User_,
|
||||||
|
};
|
||||||
|
use lemmy_utils::convert_datetime;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{fmt::Debug, str::FromStr};
|
use std::{fmt::Debug, str::FromStr};
|
||||||
|
|
||||||
|
@ -367,13 +357,8 @@ impl FromApub for CommunityForm {
|
||||||
type ApubType = GroupExt;
|
type ApubType = GroupExt;
|
||||||
|
|
||||||
/// Parse an ActivityPub group received from another instance into a Lemmy community.
|
/// Parse an ActivityPub group received from another instance into a Lemmy community.
|
||||||
async fn from_apub(
|
async fn from_apub(group: &GroupExt, client: &Client, pool: &DbPool) -> Result<Self, LemmyError> {
|
||||||
group: &mut GroupExt,
|
let creator_and_moderator_uris = group.attributed_to().unwrap();
|
||||||
client: &Client,
|
|
||||||
pool: &DbPool,
|
|
||||||
) -> Result<Self, LemmyError> {
|
|
||||||
// TODO: this is probably gonna cause problems cause fetcher:292 also calls take_attributed_to()
|
|
||||||
let creator_and_moderator_uris = group.clone().take_attributed_to().unwrap();
|
|
||||||
let creator_uri = creator_and_moderator_uris
|
let creator_uri = creator_and_moderator_uris
|
||||||
.as_many()
|
.as_many()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -386,27 +371,20 @@ impl FromApub for CommunityForm {
|
||||||
let creator = get_or_fetch_and_upsert_remote_user(creator_uri.as_str(), client, pool).await?;
|
let creator = get_or_fetch_and_upsert_remote_user(creator_uri.as_str(), client, pool).await?;
|
||||||
|
|
||||||
Ok(CommunityForm {
|
Ok(CommunityForm {
|
||||||
name: group
|
name: group.name().unwrap().as_single_xsd_string().unwrap().into(),
|
||||||
.take_name()
|
title: group.inner.preferred_username().unwrap().to_string(),
|
||||||
.unwrap()
|
|
||||||
.as_single_xsd_string()
|
|
||||||
.unwrap()
|
|
||||||
.into(),
|
|
||||||
title: group.inner.take_preferred_username().unwrap(),
|
|
||||||
// TODO: should be parsed as html and tags like <script> removed (or use markdown source)
|
// TODO: should be parsed as html and tags like <script> removed (or use markdown source)
|
||||||
// -> same for post.content etc
|
// -> same for post.content etc
|
||||||
description: group
|
description: group
|
||||||
.take_content()
|
.content()
|
||||||
.map(|s| s.as_single_xsd_string().unwrap().into()),
|
.map(|s| s.as_single_xsd_string().unwrap().into()),
|
||||||
category_id: group.ext_one.category.identifier.parse::<i32>()?,
|
category_id: group.ext_one.category.identifier.parse::<i32>()?,
|
||||||
creator_id: creator.id,
|
creator_id: creator.id,
|
||||||
removed: None,
|
removed: None,
|
||||||
published: group
|
published: group
|
||||||
.take_published()
|
.published()
|
||||||
.map(|u| u.as_ref().to_owned().naive_local()),
|
|
||||||
updated: group
|
|
||||||
.take_updated()
|
|
||||||
.map(|u| u.as_ref().to_owned().naive_local()),
|
.map(|u| u.as_ref().to_owned().naive_local()),
|
||||||
|
updated: group.updated().map(|u| u.as_ref().to_owned().naive_local()),
|
||||||
deleted: None,
|
deleted: None,
|
||||||
nsfw: group.ext_one.sensitive,
|
nsfw: group.ext_one.sensitive,
|
||||||
actor_id: group.id().unwrap().to_string(),
|
actor_id: group.id().unwrap().to_string(),
|
||||||
|
@ -462,17 +440,16 @@ pub async fn get_apub_community_followers(
|
||||||
Ok(create_apub_response(&collection))
|
Ok(create_apub_response(&collection))
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Community {
|
pub async fn do_announce<A>(
|
||||||
pub async fn do_announce<A>(
|
|
||||||
activity: A,
|
activity: A,
|
||||||
community: &Community,
|
community: &Community,
|
||||||
sender: &dyn ActorType,
|
sender: &dyn ActorType,
|
||||||
client: &Client,
|
client: &Client,
|
||||||
pool: &DbPool,
|
pool: &DbPool,
|
||||||
) -> Result<HttpResponse, LemmyError>
|
) -> Result<HttpResponse, LemmyError>
|
||||||
where
|
where
|
||||||
A: Activity + Base + Serialize + Debug,
|
A: Activity + Base + Serialize + Debug,
|
||||||
{
|
{
|
||||||
let mut announce = Announce::default();
|
let mut announce = Announce::default();
|
||||||
populate_object_props(
|
populate_object_props(
|
||||||
&mut announce.object_props,
|
&mut announce.object_props,
|
||||||
|
@ -496,5 +473,4 @@ impl Community {
|
||||||
send_activity(client, &announce, community, to).await?;
|
send_activity(client, &announce, community, to).await?;
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().finish())
|
Ok(HttpResponse::Ok().finish())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,21 +2,21 @@ use crate::{
|
||||||
apub::{
|
apub::{
|
||||||
extensions::signatures::verify,
|
extensions::signatures::verify,
|
||||||
fetcher::{get_or_fetch_and_upsert_remote_community, get_or_fetch_and_upsert_remote_user},
|
fetcher::{get_or_fetch_and_upsert_remote_community, get_or_fetch_and_upsert_remote_user},
|
||||||
|
insert_activity,
|
||||||
ActorType,
|
ActorType,
|
||||||
},
|
},
|
||||||
blocking,
|
blocking,
|
||||||
db::{
|
|
||||||
activity::insert_activity,
|
|
||||||
community::{Community, CommunityFollower, CommunityFollowerForm},
|
|
||||||
user::User_,
|
|
||||||
Followable,
|
|
||||||
},
|
|
||||||
routes::{ChatServerParam, DbPoolParam},
|
routes::{ChatServerParam, DbPoolParam},
|
||||||
LemmyError,
|
LemmyError,
|
||||||
};
|
};
|
||||||
use activitystreams::activity::Undo;
|
use activitystreams::activity::Undo;
|
||||||
use activitystreams_new::activity::Follow;
|
use activitystreams_new::activity::Follow;
|
||||||
use actix_web::{client::Client, web, HttpRequest, HttpResponse};
|
use actix_web::{client::Client, web, HttpRequest, HttpResponse};
|
||||||
|
use lemmy_db::{
|
||||||
|
community::{Community, CommunityFollower, CommunityFollowerForm},
|
||||||
|
user::User_,
|
||||||
|
Followable,
|
||||||
|
};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
use crate::{
|
use crate::LemmyError;
|
||||||
db::{category::Category, Crud},
|
|
||||||
LemmyError,
|
|
||||||
};
|
|
||||||
use activitystreams::{ext::Extension, Actor};
|
use activitystreams::{ext::Extension, Actor};
|
||||||
use diesel::PgConnection;
|
use diesel::PgConnection;
|
||||||
|
use lemmy_db::{category::Category, Crud};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||||
|
|
|
@ -9,7 +9,6 @@ use log::debug;
|
||||||
use openssl::{
|
use openssl::{
|
||||||
hash::MessageDigest,
|
hash::MessageDigest,
|
||||||
pkey::PKey,
|
pkey::PKey,
|
||||||
rsa::Rsa,
|
|
||||||
sign::{Signer, Verifier},
|
sign::{Signer, Verifier},
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -19,23 +18,6 @@ lazy_static! {
|
||||||
static ref HTTP_SIG_CONFIG: Config = Config::new();
|
static ref HTTP_SIG_CONFIG: Config = Config::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Keypair {
|
|
||||||
pub private_key: String,
|
|
||||||
pub public_key: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate the asymmetric keypair for ActivityPub HTTP signatures.
|
|
||||||
pub fn generate_actor_keypair() -> Result<Keypair, LemmyError> {
|
|
||||||
let rsa = Rsa::generate(2048)?;
|
|
||||||
let pkey = PKey::from_rsa(rsa)?;
|
|
||||||
let public_key = pkey.public_key_to_pem()?;
|
|
||||||
let private_key = pkey.private_key_to_pem_pkcs8()?;
|
|
||||||
Ok(Keypair {
|
|
||||||
private_key: String::from_utf8(private_key)?,
|
|
||||||
public_key: String::from_utf8(public_key)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Signs request headers with the given keypair.
|
/// Signs request headers with the given keypair.
|
||||||
pub async fn sign(
|
pub async fn sign(
|
||||||
request: ClientRequest,
|
request: ClientRequest,
|
||||||
|
|
|
@ -1,39 +1,29 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
api::site::SearchResponse,
|
api::site::SearchResponse,
|
||||||
apub::{
|
apub::{is_apub_id_valid, FromApub, GroupExt, PageExt, PersonExt, APUB_JSON_CONTENT_TYPE},
|
||||||
get_apub_protocol_string,
|
|
||||||
is_apub_id_valid,
|
|
||||||
FromApub,
|
|
||||||
GroupExt,
|
|
||||||
PageExt,
|
|
||||||
PersonExt,
|
|
||||||
APUB_JSON_CONTENT_TYPE,
|
|
||||||
},
|
|
||||||
blocking,
|
blocking,
|
||||||
db::{
|
|
||||||
comment::{Comment, CommentForm},
|
|
||||||
comment_view::CommentView,
|
|
||||||
community::{Community, CommunityForm, CommunityModerator, CommunityModeratorForm},
|
|
||||||
community_view::CommunityView,
|
|
||||||
post::{Post, PostForm},
|
|
||||||
post_view::PostView,
|
|
||||||
user::{UserForm, User_},
|
|
||||||
user_view::UserView,
|
|
||||||
Crud,
|
|
||||||
Joinable,
|
|
||||||
SearchType,
|
|
||||||
},
|
|
||||||
naive_now,
|
|
||||||
request::{retry, RecvError},
|
request::{retry, RecvError},
|
||||||
routes::nodeinfo::{NodeInfo, NodeInfoWellKnown},
|
routes::nodeinfo::{NodeInfo, NodeInfoWellKnown},
|
||||||
DbPool,
|
DbPool, LemmyError,
|
||||||
LemmyError,
|
|
||||||
};
|
};
|
||||||
use activitystreams::object::Note;
|
use activitystreams::object::Note;
|
||||||
use activitystreams_new::{base::BaseExt, prelude::*, primitives::XsdAnyUri};
|
use activitystreams_new::{base::BaseExt, prelude::*, primitives::XsdAnyUri};
|
||||||
use actix_web::client::Client;
|
use actix_web::client::Client;
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use diesel::{result::Error::NotFound, PgConnection};
|
use diesel::{result::Error::NotFound, PgConnection};
|
||||||
|
use lemmy_db::{
|
||||||
|
comment::{Comment, CommentForm},
|
||||||
|
comment_view::CommentView,
|
||||||
|
community::{Community, CommunityForm, CommunityModerator, CommunityModeratorForm},
|
||||||
|
community_view::CommunityView,
|
||||||
|
naive_now,
|
||||||
|
post::{Post, PostForm},
|
||||||
|
post_view::PostView,
|
||||||
|
user::{UserForm, User_},
|
||||||
|
user_view::UserView,
|
||||||
|
Crud, Joinable, SearchType,
|
||||||
|
};
|
||||||
|
use lemmy_utils::get_apub_protocol_string;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{fmt::Debug, time::Duration};
|
use std::{fmt::Debug, time::Duration};
|
||||||
|
@ -171,15 +161,15 @@ pub async fn search_by_apub_id(
|
||||||
|
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
SearchAcceptedObjects::Page(mut p) => {
|
SearchAcceptedObjects::Page(p) => {
|
||||||
let post_form = PostForm::from_apub(&mut p, client, pool).await?;
|
let post_form = PostForm::from_apub(&p, client, pool).await?;
|
||||||
|
|
||||||
let p = blocking(pool, move |conn| upsert_post(&post_form, conn)).await??;
|
let p = blocking(pool, move |conn| upsert_post(&post_form, conn)).await??;
|
||||||
response.posts = vec![blocking(pool, move |conn| PostView::read(conn, p.id, None)).await??];
|
response.posts = vec![blocking(pool, move |conn| PostView::read(conn, p.id, None)).await??];
|
||||||
|
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
SearchAcceptedObjects::Comment(mut c) => {
|
SearchAcceptedObjects::Comment(c) => {
|
||||||
let post_url = c
|
let post_url = c
|
||||||
.object_props
|
.object_props
|
||||||
.get_many_in_reply_to_xsd_any_uris()
|
.get_many_in_reply_to_xsd_any_uris()
|
||||||
|
@ -189,9 +179,9 @@ pub async fn search_by_apub_id(
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
// TODO: also fetch parent comments if any
|
// TODO: also fetch parent comments if any
|
||||||
let mut post = fetch_remote_object(client, &Url::parse(&post_url)?).await?;
|
let post = fetch_remote_object(client, &Url::parse(&post_url)?).await?;
|
||||||
let post_form = PostForm::from_apub(&mut post, client, pool).await?;
|
let post_form = PostForm::from_apub(&post, client, pool).await?;
|
||||||
let comment_form = CommentForm::from_apub(&mut c, client, pool).await?;
|
let comment_form = CommentForm::from_apub(&c, client, pool).await?;
|
||||||
|
|
||||||
blocking(pool, move |conn| upsert_post(&post_form, conn)).await??;
|
blocking(pool, move |conn| upsert_post(&post_form, conn)).await??;
|
||||||
let c = blocking(pool, move |conn| upsert_comment(&comment_form, conn)).await??;
|
let c = blocking(pool, move |conn| upsert_comment(&comment_form, conn)).await??;
|
||||||
|
@ -221,9 +211,9 @@ pub async fn get_or_fetch_and_upsert_remote_user(
|
||||||
// If its older than a day, re-fetch it
|
// If its older than a day, re-fetch it
|
||||||
Ok(u) if !u.local && should_refetch_actor(u.last_refreshed_at) => {
|
Ok(u) if !u.local && should_refetch_actor(u.last_refreshed_at) => {
|
||||||
debug!("Fetching and updating from remote user: {}", apub_id);
|
debug!("Fetching and updating from remote user: {}", apub_id);
|
||||||
let mut person = fetch_remote_object::<PersonExt>(client, &Url::parse(apub_id)?).await?;
|
let person = fetch_remote_object::<PersonExt>(client, &Url::parse(apub_id)?).await?;
|
||||||
|
|
||||||
let mut uf = UserForm::from_apub(&mut person, client, pool).await?;
|
let mut uf = UserForm::from_apub(&person, client, pool).await?;
|
||||||
uf.last_refreshed_at = Some(naive_now());
|
uf.last_refreshed_at = Some(naive_now());
|
||||||
let user = blocking(pool, move |conn| User_::update(conn, u.id, &uf)).await??;
|
let user = blocking(pool, move |conn| User_::update(conn, u.id, &uf)).await??;
|
||||||
|
|
||||||
|
@ -232,9 +222,9 @@ pub async fn get_or_fetch_and_upsert_remote_user(
|
||||||
Ok(u) => Ok(u),
|
Ok(u) => Ok(u),
|
||||||
Err(NotFound {}) => {
|
Err(NotFound {}) => {
|
||||||
debug!("Fetching and creating remote user: {}", apub_id);
|
debug!("Fetching and creating remote user: {}", apub_id);
|
||||||
let mut person = fetch_remote_object::<PersonExt>(client, &Url::parse(apub_id)?).await?;
|
let person = fetch_remote_object::<PersonExt>(client, &Url::parse(apub_id)?).await?;
|
||||||
|
|
||||||
let uf = UserForm::from_apub(&mut person, client, pool).await?;
|
let uf = UserForm::from_apub(&person, client, pool).await?;
|
||||||
let user = blocking(pool, move |conn| User_::create(conn, &uf)).await??;
|
let user = blocking(pool, move |conn| User_::create(conn, &uf)).await??;
|
||||||
|
|
||||||
Ok(user)
|
Ok(user)
|
||||||
|
@ -272,9 +262,9 @@ pub async fn get_or_fetch_and_upsert_remote_community(
|
||||||
match community {
|
match community {
|
||||||
Ok(c) if !c.local && should_refetch_actor(c.last_refreshed_at) => {
|
Ok(c) if !c.local && should_refetch_actor(c.last_refreshed_at) => {
|
||||||
debug!("Fetching and updating from remote community: {}", apub_id);
|
debug!("Fetching and updating from remote community: {}", apub_id);
|
||||||
let mut group = fetch_remote_object::<GroupExt>(client, &Url::parse(apub_id)?).await?;
|
let group = fetch_remote_object::<GroupExt>(client, &Url::parse(apub_id)?).await?;
|
||||||
|
|
||||||
let mut cf = CommunityForm::from_apub(&mut group, client, pool).await?;
|
let mut cf = CommunityForm::from_apub(&group, client, pool).await?;
|
||||||
cf.last_refreshed_at = Some(naive_now());
|
cf.last_refreshed_at = Some(naive_now());
|
||||||
let community = blocking(pool, move |conn| Community::update(conn, c.id, &cf)).await??;
|
let community = blocking(pool, move |conn| Community::update(conn, c.id, &cf)).await??;
|
||||||
|
|
||||||
|
@ -283,13 +273,13 @@ pub async fn get_or_fetch_and_upsert_remote_community(
|
||||||
Ok(c) => Ok(c),
|
Ok(c) => Ok(c),
|
||||||
Err(NotFound {}) => {
|
Err(NotFound {}) => {
|
||||||
debug!("Fetching and creating remote community: {}", apub_id);
|
debug!("Fetching and creating remote community: {}", apub_id);
|
||||||
let mut group = fetch_remote_object::<GroupExt>(client, &Url::parse(apub_id)?).await?;
|
let group = fetch_remote_object::<GroupExt>(client, &Url::parse(apub_id)?).await?;
|
||||||
|
|
||||||
let cf = CommunityForm::from_apub(&mut group, client, pool).await?;
|
let cf = CommunityForm::from_apub(&group, client, pool).await?;
|
||||||
let community = blocking(pool, move |conn| Community::create(conn, &cf)).await??;
|
let community = blocking(pool, move |conn| Community::create(conn, &cf)).await??;
|
||||||
|
|
||||||
// Also add the community moderators too
|
// Also add the community moderators too
|
||||||
let attributed_to = group.inner.take_attributed_to().unwrap();
|
let attributed_to = group.inner.attributed_to().unwrap();
|
||||||
let creator_and_moderator_uris: Vec<&XsdAnyUri> = attributed_to
|
let creator_and_moderator_uris: Vec<&XsdAnyUri> = attributed_to
|
||||||
.as_many()
|
.as_many()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -349,8 +339,8 @@ pub async fn get_or_fetch_and_insert_remote_post(
|
||||||
Ok(p) => Ok(p),
|
Ok(p) => Ok(p),
|
||||||
Err(NotFound {}) => {
|
Err(NotFound {}) => {
|
||||||
debug!("Fetching and creating remote post: {}", post_ap_id);
|
debug!("Fetching and creating remote post: {}", post_ap_id);
|
||||||
let mut post = fetch_remote_object::<PageExt>(client, &Url::parse(post_ap_id)?).await?;
|
let post = fetch_remote_object::<PageExt>(client, &Url::parse(post_ap_id)?).await?;
|
||||||
let post_form = PostForm::from_apub(&mut post, client, pool).await?;
|
let post_form = PostForm::from_apub(&post, client, pool).await?;
|
||||||
|
|
||||||
let post = blocking(pool, move |conn| Post::create(conn, &post_form)).await??;
|
let post = blocking(pool, move |conn| Post::create(conn, &post_form)).await??;
|
||||||
|
|
||||||
|
@ -387,8 +377,8 @@ pub async fn get_or_fetch_and_insert_remote_comment(
|
||||||
"Fetching and creating remote comment and its parents: {}",
|
"Fetching and creating remote comment and its parents: {}",
|
||||||
comment_ap_id
|
comment_ap_id
|
||||||
);
|
);
|
||||||
let mut comment = fetch_remote_object::<Note>(client, &Url::parse(comment_ap_id)?).await?;
|
let comment = fetch_remote_object::<Note>(client, &Url::parse(comment_ap_id)?).await?;
|
||||||
let comment_form = CommentForm::from_apub(&mut comment, client, pool).await?;
|
let comment_form = CommentForm::from_apub(&comment, client, pool).await?;
|
||||||
|
|
||||||
let comment = blocking(pool, move |conn| Comment::create(conn, &comment_form)).await??;
|
let comment = blocking(pool, move |conn| Comment::create(conn, &comment_form)).await??;
|
||||||
|
|
||||||
|
|
|
@ -16,14 +16,10 @@ use crate::{
|
||||||
page_extension::PageExtension,
|
page_extension::PageExtension,
|
||||||
signatures::{PublicKey, PublicKeyExtension},
|
signatures::{PublicKey, PublicKeyExtension},
|
||||||
},
|
},
|
||||||
convert_datetime,
|
blocking,
|
||||||
db::user::User_,
|
|
||||||
request::{retry, RecvError},
|
request::{retry, RecvError},
|
||||||
routes::webfinger::WebFingerResponse,
|
routes::webfinger::WebFingerResponse,
|
||||||
DbPool,
|
DbPool, LemmyError,
|
||||||
LemmyError,
|
|
||||||
MentionData,
|
|
||||||
Settings,
|
|
||||||
};
|
};
|
||||||
use activitystreams::object::Page;
|
use activitystreams::object::Page;
|
||||||
use activitystreams_ext::{Ext1, Ext2};
|
use activitystreams_ext::{Ext1, Ext2};
|
||||||
|
@ -35,6 +31,9 @@ use activitystreams_new::{
|
||||||
};
|
};
|
||||||
use actix_web::{body::Body, client::Client, HttpResponse};
|
use actix_web::{body::Body, client::Client, HttpResponse};
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
|
use failure::_core::fmt::Debug;
|
||||||
|
use lemmy_db::{activity::do_insert_activity, user::User_};
|
||||||
|
use lemmy_utils::{convert_datetime, get_apub_protocol_string, settings::Settings, MentionData};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -45,14 +44,6 @@ type PageExt = Ext1<Page, PageExtension>;
|
||||||
|
|
||||||
pub static APUB_JSON_CONTENT_TYPE: &str = "application/activity+json";
|
pub static APUB_JSON_CONTENT_TYPE: &str = "application/activity+json";
|
||||||
|
|
||||||
pub enum EndpointType {
|
|
||||||
Community,
|
|
||||||
User,
|
|
||||||
Post,
|
|
||||||
Comment,
|
|
||||||
PrivateMessage,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert the data to json and turn it into an HTTP Response with the correct ActivityPub
|
/// Convert the data to json and turn it into an HTTP Response with the correct ActivityPub
|
||||||
/// headers.
|
/// headers.
|
||||||
fn create_apub_response<T>(data: &T) -> HttpResponse<Body>
|
fn create_apub_response<T>(data: &T) -> HttpResponse<Body>
|
||||||
|
@ -73,34 +64,6 @@ where
|
||||||
.json(data)
|
.json(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates the ActivityPub ID for a given object type and ID.
|
|
||||||
pub fn make_apub_endpoint(endpoint_type: EndpointType, name: &str) -> Url {
|
|
||||||
let point = match endpoint_type {
|
|
||||||
EndpointType::Community => "c",
|
|
||||||
EndpointType::User => "u",
|
|
||||||
EndpointType::Post => "post",
|
|
||||||
EndpointType::Comment => "comment",
|
|
||||||
EndpointType::PrivateMessage => "private_message",
|
|
||||||
};
|
|
||||||
|
|
||||||
Url::parse(&format!(
|
|
||||||
"{}://{}/{}/{}",
|
|
||||||
get_apub_protocol_string(),
|
|
||||||
Settings::get().hostname,
|
|
||||||
point,
|
|
||||||
name
|
|
||||||
))
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_apub_protocol_string() -> &'static str {
|
|
||||||
if Settings::get().federation.tls_enabled {
|
|
||||||
"https"
|
|
||||||
} else {
|
|
||||||
"http"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks if the ID has a valid format, correct scheme, and is in the allowed instance list.
|
// Checks if the ID has a valid format, correct scheme, and is in the allowed instance list.
|
||||||
fn is_apub_id_valid(apub_id: &Url) -> bool {
|
fn is_apub_id_valid(apub_id: &Url) -> bool {
|
||||||
debug!("Checking {}", apub_id);
|
debug!("Checking {}", apub_id);
|
||||||
|
@ -165,7 +128,7 @@ fn create_tombstone(
|
||||||
pub trait FromApub {
|
pub trait FromApub {
|
||||||
type ApubType;
|
type ApubType;
|
||||||
async fn from_apub(
|
async fn from_apub(
|
||||||
apub: &mut Self::ApubType,
|
apub: &Self::ApubType,
|
||||||
client: &Client,
|
client: &Client,
|
||||||
pool: &DbPool,
|
pool: &DbPool,
|
||||||
) -> Result<Self, LemmyError>
|
) -> Result<Self, LemmyError>
|
||||||
|
@ -374,3 +337,19 @@ pub async fn fetch_webfinger_url(
|
||||||
.to_owned()
|
.to_owned()
|
||||||
.ok_or_else(|| format_err!("No href found.").into())
|
.ok_or_else(|| format_err!("No href found.").into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn insert_activity<T>(
|
||||||
|
user_id: i32,
|
||||||
|
data: T,
|
||||||
|
local: bool,
|
||||||
|
pool: &DbPool,
|
||||||
|
) -> Result<(), LemmyError>
|
||||||
|
where
|
||||||
|
T: Serialize + Debug + Send + 'static,
|
||||||
|
{
|
||||||
|
blocking(pool, move |conn| {
|
||||||
|
do_insert_activity(conn, user_id, &data, local)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -1,31 +1,14 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
apub::{
|
apub::{
|
||||||
activities::{populate_object_props, send_activity_to_community},
|
activities::{populate_object_props, send_activity_to_community},
|
||||||
create_apub_response,
|
create_apub_response, create_apub_tombstone_response, create_tombstone,
|
||||||
create_apub_tombstone_response,
|
|
||||||
create_tombstone,
|
|
||||||
extensions::page_extension::PageExtension,
|
extensions::page_extension::PageExtension,
|
||||||
fetcher::{get_or_fetch_and_upsert_remote_community, get_or_fetch_and_upsert_remote_user},
|
fetcher::{get_or_fetch_and_upsert_remote_community, get_or_fetch_and_upsert_remote_user},
|
||||||
get_apub_protocol_string,
|
ActorType, ApubLikeableType, ApubObjectType, FromApub, PageExt, ToApub,
|
||||||
ActorType,
|
|
||||||
ApubLikeableType,
|
|
||||||
ApubObjectType,
|
|
||||||
FromApub,
|
|
||||||
PageExt,
|
|
||||||
ToApub,
|
|
||||||
},
|
},
|
||||||
blocking,
|
blocking,
|
||||||
convert_datetime,
|
|
||||||
db::{
|
|
||||||
community::Community,
|
|
||||||
post::{Post, PostForm},
|
|
||||||
user::User_,
|
|
||||||
Crud,
|
|
||||||
},
|
|
||||||
routes::DbPoolParam,
|
routes::DbPoolParam,
|
||||||
DbPool,
|
DbPool, LemmyError,
|
||||||
LemmyError,
|
|
||||||
Settings,
|
|
||||||
};
|
};
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
activity::{Create, Delete, Dislike, Like, Remove, Undo, Update},
|
activity::{Create, Delete, Dislike, Like, Remove, Undo, Update},
|
||||||
|
@ -36,6 +19,13 @@ use activitystreams::{
|
||||||
use activitystreams_ext::Ext1;
|
use activitystreams_ext::Ext1;
|
||||||
use activitystreams_new::object::Tombstone;
|
use activitystreams_new::object::Tombstone;
|
||||||
use actix_web::{body::Body, client::Client, web, HttpResponse};
|
use actix_web::{body::Body, client::Client, web, HttpResponse};
|
||||||
|
use lemmy_db::{
|
||||||
|
community::Community,
|
||||||
|
post::{Post, PostForm},
|
||||||
|
user::User_,
|
||||||
|
Crud,
|
||||||
|
};
|
||||||
|
use lemmy_utils::{convert_datetime, get_apub_protocol_string, settings::Settings};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
@ -164,7 +154,7 @@ impl FromApub for PostForm {
|
||||||
|
|
||||||
/// Parse an ActivityPub page received from another instance into a Lemmy post.
|
/// Parse an ActivityPub page received from another instance into a Lemmy post.
|
||||||
async fn from_apub(
|
async fn from_apub(
|
||||||
page: &mut PageExt,
|
page: &PageExt,
|
||||||
client: &Client,
|
client: &Client,
|
||||||
pool: &DbPool,
|
pool: &DbPool,
|
||||||
) -> Result<PostForm, LemmyError> {
|
) -> Result<PostForm, LemmyError> {
|
||||||
|
|
|
@ -1,22 +1,9 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
apub::{
|
apub::{
|
||||||
activities::send_activity,
|
activities::send_activity, create_tombstone, fetcher::get_or_fetch_and_upsert_remote_user,
|
||||||
create_tombstone,
|
insert_activity, ApubObjectType, FromApub, ToApub,
|
||||||
fetcher::get_or_fetch_and_upsert_remote_user,
|
|
||||||
ApubObjectType,
|
|
||||||
FromApub,
|
|
||||||
ToApub,
|
|
||||||
},
|
},
|
||||||
blocking,
|
blocking, DbPool, LemmyError,
|
||||||
convert_datetime,
|
|
||||||
db::{
|
|
||||||
activity::insert_activity,
|
|
||||||
private_message::{PrivateMessage, PrivateMessageForm},
|
|
||||||
user::User_,
|
|
||||||
Crud,
|
|
||||||
},
|
|
||||||
DbPool,
|
|
||||||
LemmyError,
|
|
||||||
};
|
};
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
activity::{Create, Delete, Undo, Update},
|
activity::{Create, Delete, Undo, Update},
|
||||||
|
@ -25,6 +12,12 @@ use activitystreams::{
|
||||||
};
|
};
|
||||||
use activitystreams_new::object::Tombstone;
|
use activitystreams_new::object::Tombstone;
|
||||||
use actix_web::client::Client;
|
use actix_web::client::Client;
|
||||||
|
use lemmy_db::{
|
||||||
|
private_message::{PrivateMessage, PrivateMessageForm},
|
||||||
|
user::User_,
|
||||||
|
Crud,
|
||||||
|
};
|
||||||
|
use lemmy_utils::convert_datetime;
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl ToApub for PrivateMessage {
|
impl ToApub for PrivateMessage {
|
||||||
|
@ -71,7 +64,7 @@ impl FromApub for PrivateMessageForm {
|
||||||
|
|
||||||
/// Parse an ActivityPub note received from another instance into a Lemmy Private message
|
/// Parse an ActivityPub note received from another instance into a Lemmy Private message
|
||||||
async fn from_apub(
|
async fn from_apub(
|
||||||
note: &mut Note,
|
note: &Note,
|
||||||
client: &Client,
|
client: &Client,
|
||||||
pool: &DbPool,
|
pool: &DbPool,
|
||||||
) -> Result<PrivateMessageForm, LemmyError> {
|
) -> Result<PrivateMessageForm, LemmyError> {
|
||||||
|
|
|
@ -5,47 +5,39 @@ use crate::{
|
||||||
post::PostResponse,
|
post::PostResponse,
|
||||||
},
|
},
|
||||||
apub::{
|
apub::{
|
||||||
|
community::do_announce,
|
||||||
extensions::signatures::verify,
|
extensions::signatures::verify,
|
||||||
fetcher::{
|
fetcher::{
|
||||||
get_or_fetch_and_insert_remote_comment,
|
get_or_fetch_and_insert_remote_comment, get_or_fetch_and_insert_remote_post,
|
||||||
get_or_fetch_and_insert_remote_post,
|
get_or_fetch_and_upsert_remote_community, get_or_fetch_and_upsert_remote_user,
|
||||||
get_or_fetch_and_upsert_remote_community,
|
|
||||||
get_or_fetch_and_upsert_remote_user,
|
|
||||||
},
|
},
|
||||||
FromApub,
|
insert_activity, FromApub, GroupExt, PageExt,
|
||||||
GroupExt,
|
|
||||||
PageExt,
|
|
||||||
},
|
},
|
||||||
blocking,
|
blocking,
|
||||||
db::{
|
|
||||||
activity::insert_activity,
|
|
||||||
comment::{Comment, CommentForm, CommentLike, CommentLikeForm},
|
|
||||||
comment_view::CommentView,
|
|
||||||
community::{Community, CommunityForm},
|
|
||||||
community_view::CommunityView,
|
|
||||||
post::{Post, PostForm, PostLike, PostLikeForm},
|
|
||||||
post_view::PostView,
|
|
||||||
Crud,
|
|
||||||
Likeable,
|
|
||||||
},
|
|
||||||
naive_now,
|
|
||||||
routes::{ChatServerParam, DbPoolParam},
|
routes::{ChatServerParam, DbPoolParam},
|
||||||
scrape_text_for_mentions,
|
|
||||||
websocket::{
|
websocket::{
|
||||||
server::{SendComment, SendCommunityRoomMessage, SendPost},
|
server::{SendComment, SendCommunityRoomMessage, SendPost},
|
||||||
UserOperation,
|
UserOperation,
|
||||||
},
|
},
|
||||||
DbPool,
|
DbPool, LemmyError,
|
||||||
LemmyError,
|
|
||||||
};
|
};
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
activity::{Announce, Create, Delete, Dislike, Like, Remove, Undo, Update},
|
activity::{Announce, Create, Delete, Dislike, Like, Remove, Undo, Update},
|
||||||
object::Note,
|
object::Note,
|
||||||
Activity,
|
Activity, Base, BaseBox,
|
||||||
Base,
|
|
||||||
BaseBox,
|
|
||||||
};
|
};
|
||||||
use actix_web::{client::Client, web, HttpRequest, HttpResponse};
|
use actix_web::{client::Client, web, HttpRequest, HttpResponse};
|
||||||
|
use lemmy_db::{
|
||||||
|
comment::{Comment, CommentForm, CommentLike, CommentLikeForm},
|
||||||
|
comment_view::CommentView,
|
||||||
|
community::{Community, CommunityForm},
|
||||||
|
community_view::CommunityView,
|
||||||
|
naive_now,
|
||||||
|
post::{Post, PostForm, PostLike, PostLikeForm},
|
||||||
|
post_view::PostView,
|
||||||
|
Crud, Likeable,
|
||||||
|
};
|
||||||
|
use lemmy_utils::scrape_text_for_mentions;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
@ -234,7 +226,7 @@ where
|
||||||
if community.local {
|
if community.local {
|
||||||
let sending_user = get_or_fetch_and_upsert_remote_user(sender, client, pool).await?;
|
let sending_user = get_or_fetch_and_upsert_remote_user(sender, client, pool).await?;
|
||||||
|
|
||||||
Community::do_announce(activity, &community, &sending_user, client, pool).await
|
do_announce(activity, &community, &sending_user, client, pool).await
|
||||||
} else {
|
} else {
|
||||||
Ok(HttpResponse::NotFound().finish())
|
Ok(HttpResponse::NotFound().finish())
|
||||||
}
|
}
|
||||||
|
@ -335,7 +327,7 @@ async fn receive_create_post(
|
||||||
pool: &DbPool,
|
pool: &DbPool,
|
||||||
chat_server: ChatServerParam,
|
chat_server: ChatServerParam,
|
||||||
) -> Result<HttpResponse, LemmyError> {
|
) -> Result<HttpResponse, LemmyError> {
|
||||||
let mut page = create
|
let page = create
|
||||||
.create_props
|
.create_props
|
||||||
.get_object_base_box()
|
.get_object_base_box()
|
||||||
.to_owned()
|
.to_owned()
|
||||||
|
@ -353,7 +345,7 @@ async fn receive_create_post(
|
||||||
|
|
||||||
insert_activity(user.id, create, false, pool).await?;
|
insert_activity(user.id, create, false, pool).await?;
|
||||||
|
|
||||||
let post = PostForm::from_apub(&mut page, client, pool).await?;
|
let post = PostForm::from_apub(&page, client, pool).await?;
|
||||||
|
|
||||||
let inserted_post = blocking(pool, move |conn| Post::create(conn, &post)).await??;
|
let inserted_post = blocking(pool, move |conn| Post::create(conn, &post)).await??;
|
||||||
|
|
||||||
|
@ -381,7 +373,7 @@ async fn receive_create_comment(
|
||||||
pool: &DbPool,
|
pool: &DbPool,
|
||||||
chat_server: ChatServerParam,
|
chat_server: ChatServerParam,
|
||||||
) -> Result<HttpResponse, LemmyError> {
|
) -> Result<HttpResponse, LemmyError> {
|
||||||
let mut note = create
|
let note = create
|
||||||
.create_props
|
.create_props
|
||||||
.get_object_base_box()
|
.get_object_base_box()
|
||||||
.to_owned()
|
.to_owned()
|
||||||
|
@ -399,7 +391,7 @@ async fn receive_create_comment(
|
||||||
|
|
||||||
insert_activity(user.id, create, false, pool).await?;
|
insert_activity(user.id, create, false, pool).await?;
|
||||||
|
|
||||||
let comment = CommentForm::from_apub(&mut note, client, pool).await?;
|
let comment = CommentForm::from_apub(¬e, client, pool).await?;
|
||||||
|
|
||||||
let inserted_comment = blocking(pool, move |conn| Comment::create(conn, &comment)).await??;
|
let inserted_comment = blocking(pool, move |conn| Comment::create(conn, &comment)).await??;
|
||||||
|
|
||||||
|
@ -440,7 +432,7 @@ async fn receive_update_post(
|
||||||
pool: &DbPool,
|
pool: &DbPool,
|
||||||
chat_server: ChatServerParam,
|
chat_server: ChatServerParam,
|
||||||
) -> Result<HttpResponse, LemmyError> {
|
) -> Result<HttpResponse, LemmyError> {
|
||||||
let mut page = update
|
let page = update
|
||||||
.update_props
|
.update_props
|
||||||
.get_object_base_box()
|
.get_object_base_box()
|
||||||
.to_owned()
|
.to_owned()
|
||||||
|
@ -458,7 +450,7 @@ async fn receive_update_post(
|
||||||
|
|
||||||
insert_activity(user.id, update, false, pool).await?;
|
insert_activity(user.id, update, false, pool).await?;
|
||||||
|
|
||||||
let post = PostForm::from_apub(&mut page, client, pool).await?;
|
let post = PostForm::from_apub(&page, client, pool).await?;
|
||||||
|
|
||||||
let post_id = get_or_fetch_and_insert_remote_post(&post.ap_id, client, pool)
|
let post_id = get_or_fetch_and_insert_remote_post(&post.ap_id, client, pool)
|
||||||
.await?
|
.await?
|
||||||
|
@ -486,7 +478,7 @@ async fn receive_like_post(
|
||||||
pool: &DbPool,
|
pool: &DbPool,
|
||||||
chat_server: ChatServerParam,
|
chat_server: ChatServerParam,
|
||||||
) -> Result<HttpResponse, LemmyError> {
|
) -> Result<HttpResponse, LemmyError> {
|
||||||
let mut page = like
|
let page = like
|
||||||
.like_props
|
.like_props
|
||||||
.get_object_base_box()
|
.get_object_base_box()
|
||||||
.to_owned()
|
.to_owned()
|
||||||
|
@ -500,7 +492,7 @@ async fn receive_like_post(
|
||||||
|
|
||||||
insert_activity(user.id, like, false, pool).await?;
|
insert_activity(user.id, like, false, pool).await?;
|
||||||
|
|
||||||
let post = PostForm::from_apub(&mut page, client, pool).await?;
|
let post = PostForm::from_apub(&page, client, pool).await?;
|
||||||
|
|
||||||
let post_id = get_or_fetch_and_insert_remote_post(&post.ap_id, client, pool)
|
let post_id = get_or_fetch_and_insert_remote_post(&post.ap_id, client, pool)
|
||||||
.await?
|
.await?
|
||||||
|
@ -537,7 +529,7 @@ async fn receive_dislike_post(
|
||||||
pool: &DbPool,
|
pool: &DbPool,
|
||||||
chat_server: ChatServerParam,
|
chat_server: ChatServerParam,
|
||||||
) -> Result<HttpResponse, LemmyError> {
|
) -> Result<HttpResponse, LemmyError> {
|
||||||
let mut page = dislike
|
let page = dislike
|
||||||
.dislike_props
|
.dislike_props
|
||||||
.get_object_base_box()
|
.get_object_base_box()
|
||||||
.to_owned()
|
.to_owned()
|
||||||
|
@ -555,7 +547,7 @@ async fn receive_dislike_post(
|
||||||
|
|
||||||
insert_activity(user.id, dislike, false, pool).await?;
|
insert_activity(user.id, dislike, false, pool).await?;
|
||||||
|
|
||||||
let post = PostForm::from_apub(&mut page, client, pool).await?;
|
let post = PostForm::from_apub(&page, client, pool).await?;
|
||||||
|
|
||||||
let post_id = get_or_fetch_and_insert_remote_post(&post.ap_id, client, pool)
|
let post_id = get_or_fetch_and_insert_remote_post(&post.ap_id, client, pool)
|
||||||
.await?
|
.await?
|
||||||
|
@ -592,7 +584,7 @@ async fn receive_update_comment(
|
||||||
pool: &DbPool,
|
pool: &DbPool,
|
||||||
chat_server: ChatServerParam,
|
chat_server: ChatServerParam,
|
||||||
) -> Result<HttpResponse, LemmyError> {
|
) -> Result<HttpResponse, LemmyError> {
|
||||||
let mut note = update
|
let note = update
|
||||||
.update_props
|
.update_props
|
||||||
.get_object_base_box()
|
.get_object_base_box()
|
||||||
.to_owned()
|
.to_owned()
|
||||||
|
@ -610,7 +602,7 @@ async fn receive_update_comment(
|
||||||
|
|
||||||
insert_activity(user.id, update, false, pool).await?;
|
insert_activity(user.id, update, false, pool).await?;
|
||||||
|
|
||||||
let comment = CommentForm::from_apub(&mut note, client, pool).await?;
|
let comment = CommentForm::from_apub(¬e, client, pool).await?;
|
||||||
|
|
||||||
let comment_id = get_or_fetch_and_insert_remote_comment(&comment.ap_id, client, pool)
|
let comment_id = get_or_fetch_and_insert_remote_comment(&comment.ap_id, client, pool)
|
||||||
.await?
|
.await?
|
||||||
|
@ -651,7 +643,7 @@ async fn receive_like_comment(
|
||||||
pool: &DbPool,
|
pool: &DbPool,
|
||||||
chat_server: ChatServerParam,
|
chat_server: ChatServerParam,
|
||||||
) -> Result<HttpResponse, LemmyError> {
|
) -> Result<HttpResponse, LemmyError> {
|
||||||
let mut note = like
|
let note = like
|
||||||
.like_props
|
.like_props
|
||||||
.get_object_base_box()
|
.get_object_base_box()
|
||||||
.to_owned()
|
.to_owned()
|
||||||
|
@ -665,7 +657,7 @@ async fn receive_like_comment(
|
||||||
|
|
||||||
insert_activity(user.id, like, false, pool).await?;
|
insert_activity(user.id, like, false, pool).await?;
|
||||||
|
|
||||||
let comment = CommentForm::from_apub(&mut note, client, pool).await?;
|
let comment = CommentForm::from_apub(¬e, client, pool).await?;
|
||||||
|
|
||||||
let comment_id = get_or_fetch_and_insert_remote_comment(&comment.ap_id, client, pool)
|
let comment_id = get_or_fetch_and_insert_remote_comment(&comment.ap_id, client, pool)
|
||||||
.await?
|
.await?
|
||||||
|
@ -709,7 +701,7 @@ async fn receive_dislike_comment(
|
||||||
pool: &DbPool,
|
pool: &DbPool,
|
||||||
chat_server: ChatServerParam,
|
chat_server: ChatServerParam,
|
||||||
) -> Result<HttpResponse, LemmyError> {
|
) -> Result<HttpResponse, LemmyError> {
|
||||||
let mut note = dislike
|
let note = dislike
|
||||||
.dislike_props
|
.dislike_props
|
||||||
.get_object_base_box()
|
.get_object_base_box()
|
||||||
.to_owned()
|
.to_owned()
|
||||||
|
@ -727,7 +719,7 @@ async fn receive_dislike_comment(
|
||||||
|
|
||||||
insert_activity(user.id, dislike, false, pool).await?;
|
insert_activity(user.id, dislike, false, pool).await?;
|
||||||
|
|
||||||
let comment = CommentForm::from_apub(&mut note, client, pool).await?;
|
let comment = CommentForm::from_apub(¬e, client, pool).await?;
|
||||||
|
|
||||||
let comment_id = get_or_fetch_and_insert_remote_comment(&comment.ap_id, client, pool)
|
let comment_id = get_or_fetch_and_insert_remote_comment(&comment.ap_id, client, pool)
|
||||||
.await?
|
.await?
|
||||||
|
@ -777,7 +769,7 @@ async fn receive_delete_community(
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let mut group = delete
|
let group = delete
|
||||||
.delete_props
|
.delete_props
|
||||||
.get_object_base_box()
|
.get_object_base_box()
|
||||||
.to_owned()
|
.to_owned()
|
||||||
|
@ -789,7 +781,7 @@ async fn receive_delete_community(
|
||||||
|
|
||||||
insert_activity(user.id, delete, false, pool).await?;
|
insert_activity(user.id, delete, false, pool).await?;
|
||||||
|
|
||||||
let community_actor_id = CommunityForm::from_apub(&mut group, client, pool)
|
let community_actor_id = CommunityForm::from_apub(&group, client, pool)
|
||||||
.await?
|
.await?
|
||||||
.actor_id;
|
.actor_id;
|
||||||
|
|
||||||
|
@ -854,7 +846,7 @@ async fn receive_remove_community(
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let mut group = remove
|
let group = remove
|
||||||
.remove_props
|
.remove_props
|
||||||
.get_object_base_box()
|
.get_object_base_box()
|
||||||
.to_owned()
|
.to_owned()
|
||||||
|
@ -866,7 +858,7 @@ async fn receive_remove_community(
|
||||||
|
|
||||||
insert_activity(mod_.id, remove, false, pool).await?;
|
insert_activity(mod_.id, remove, false, pool).await?;
|
||||||
|
|
||||||
let community_actor_id = CommunityForm::from_apub(&mut group, client, pool)
|
let community_actor_id = CommunityForm::from_apub(&group, client, pool)
|
||||||
.await?
|
.await?
|
||||||
.actor_id;
|
.actor_id;
|
||||||
|
|
||||||
|
@ -931,7 +923,7 @@ async fn receive_delete_post(
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let mut page = delete
|
let page = delete
|
||||||
.delete_props
|
.delete_props
|
||||||
.get_object_base_box()
|
.get_object_base_box()
|
||||||
.to_owned()
|
.to_owned()
|
||||||
|
@ -943,7 +935,7 @@ async fn receive_delete_post(
|
||||||
|
|
||||||
insert_activity(user.id, delete, false, pool).await?;
|
insert_activity(user.id, delete, false, pool).await?;
|
||||||
|
|
||||||
let post_ap_id = PostForm::from_apub(&mut page, client, pool).await?.ap_id;
|
let post_ap_id = PostForm::from_apub(&page, client, pool).await?.ap_id;
|
||||||
|
|
||||||
let post = get_or_fetch_and_insert_remote_post(&post_ap_id, client, pool).await?;
|
let post = get_or_fetch_and_insert_remote_post(&post_ap_id, client, pool).await?;
|
||||||
|
|
||||||
|
@ -997,7 +989,7 @@ async fn receive_remove_post(
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let mut page = remove
|
let page = remove
|
||||||
.remove_props
|
.remove_props
|
||||||
.get_object_base_box()
|
.get_object_base_box()
|
||||||
.to_owned()
|
.to_owned()
|
||||||
|
@ -1009,7 +1001,7 @@ async fn receive_remove_post(
|
||||||
|
|
||||||
insert_activity(mod_.id, remove, false, pool).await?;
|
insert_activity(mod_.id, remove, false, pool).await?;
|
||||||
|
|
||||||
let post_ap_id = PostForm::from_apub(&mut page, client, pool).await?.ap_id;
|
let post_ap_id = PostForm::from_apub(&page, client, pool).await?.ap_id;
|
||||||
|
|
||||||
let post = get_or_fetch_and_insert_remote_post(&post_ap_id, client, pool).await?;
|
let post = get_or_fetch_and_insert_remote_post(&post_ap_id, client, pool).await?;
|
||||||
|
|
||||||
|
@ -1063,7 +1055,7 @@ async fn receive_delete_comment(
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let mut note = delete
|
let note = delete
|
||||||
.delete_props
|
.delete_props
|
||||||
.get_object_base_box()
|
.get_object_base_box()
|
||||||
.to_owned()
|
.to_owned()
|
||||||
|
@ -1075,7 +1067,7 @@ async fn receive_delete_comment(
|
||||||
|
|
||||||
insert_activity(user.id, delete, false, pool).await?;
|
insert_activity(user.id, delete, false, pool).await?;
|
||||||
|
|
||||||
let comment_ap_id = CommentForm::from_apub(&mut note, client, pool).await?.ap_id;
|
let comment_ap_id = CommentForm::from_apub(¬e, client, pool).await?.ap_id;
|
||||||
|
|
||||||
let comment = get_or_fetch_and_insert_remote_comment(&comment_ap_id, client, pool).await?;
|
let comment = get_or_fetch_and_insert_remote_comment(&comment_ap_id, client, pool).await?;
|
||||||
|
|
||||||
|
@ -1131,7 +1123,7 @@ async fn receive_remove_comment(
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let mut note = remove
|
let note = remove
|
||||||
.remove_props
|
.remove_props
|
||||||
.get_object_base_box()
|
.get_object_base_box()
|
||||||
.to_owned()
|
.to_owned()
|
||||||
|
@ -1143,7 +1135,7 @@ async fn receive_remove_comment(
|
||||||
|
|
||||||
insert_activity(mod_.id, remove, false, pool).await?;
|
insert_activity(mod_.id, remove, false, pool).await?;
|
||||||
|
|
||||||
let comment_ap_id = CommentForm::from_apub(&mut note, client, pool).await?.ap_id;
|
let comment_ap_id = CommentForm::from_apub(¬e, client, pool).await?.ap_id;
|
||||||
|
|
||||||
let comment = get_or_fetch_and_insert_remote_comment(&comment_ap_id, client, pool).await?;
|
let comment = get_or_fetch_and_insert_remote_comment(&comment_ap_id, client, pool).await?;
|
||||||
|
|
||||||
|
@ -1259,7 +1251,7 @@ async fn receive_undo_delete_comment(
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let mut note = delete
|
let note = delete
|
||||||
.delete_props
|
.delete_props
|
||||||
.get_object_base_box()
|
.get_object_base_box()
|
||||||
.to_owned()
|
.to_owned()
|
||||||
|
@ -1271,7 +1263,7 @@ async fn receive_undo_delete_comment(
|
||||||
|
|
||||||
insert_activity(user.id, delete, false, pool).await?;
|
insert_activity(user.id, delete, false, pool).await?;
|
||||||
|
|
||||||
let comment_ap_id = CommentForm::from_apub(&mut note, client, pool).await?.ap_id;
|
let comment_ap_id = CommentForm::from_apub(¬e, client, pool).await?.ap_id;
|
||||||
|
|
||||||
let comment = get_or_fetch_and_insert_remote_comment(&comment_ap_id, client, pool).await?;
|
let comment = get_or_fetch_and_insert_remote_comment(&comment_ap_id, client, pool).await?;
|
||||||
|
|
||||||
|
@ -1327,7 +1319,7 @@ async fn receive_undo_remove_comment(
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let mut note = remove
|
let note = remove
|
||||||
.remove_props
|
.remove_props
|
||||||
.get_object_base_box()
|
.get_object_base_box()
|
||||||
.to_owned()
|
.to_owned()
|
||||||
|
@ -1339,7 +1331,7 @@ async fn receive_undo_remove_comment(
|
||||||
|
|
||||||
insert_activity(mod_.id, remove, false, pool).await?;
|
insert_activity(mod_.id, remove, false, pool).await?;
|
||||||
|
|
||||||
let comment_ap_id = CommentForm::from_apub(&mut note, client, pool).await?.ap_id;
|
let comment_ap_id = CommentForm::from_apub(¬e, client, pool).await?.ap_id;
|
||||||
|
|
||||||
let comment = get_or_fetch_and_insert_remote_comment(&comment_ap_id, client, pool).await?;
|
let comment = get_or_fetch_and_insert_remote_comment(&comment_ap_id, client, pool).await?;
|
||||||
|
|
||||||
|
@ -1395,7 +1387,7 @@ async fn receive_undo_delete_post(
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let mut page = delete
|
let page = delete
|
||||||
.delete_props
|
.delete_props
|
||||||
.get_object_base_box()
|
.get_object_base_box()
|
||||||
.to_owned()
|
.to_owned()
|
||||||
|
@ -1407,7 +1399,7 @@ async fn receive_undo_delete_post(
|
||||||
|
|
||||||
insert_activity(user.id, delete, false, pool).await?;
|
insert_activity(user.id, delete, false, pool).await?;
|
||||||
|
|
||||||
let post_ap_id = PostForm::from_apub(&mut page, client, pool).await?.ap_id;
|
let post_ap_id = PostForm::from_apub(&page, client, pool).await?.ap_id;
|
||||||
|
|
||||||
let post = get_or_fetch_and_insert_remote_post(&post_ap_id, client, pool).await?;
|
let post = get_or_fetch_and_insert_remote_post(&post_ap_id, client, pool).await?;
|
||||||
|
|
||||||
|
@ -1461,7 +1453,7 @@ async fn receive_undo_remove_post(
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let mut page = remove
|
let page = remove
|
||||||
.remove_props
|
.remove_props
|
||||||
.get_object_base_box()
|
.get_object_base_box()
|
||||||
.to_owned()
|
.to_owned()
|
||||||
|
@ -1473,7 +1465,7 @@ async fn receive_undo_remove_post(
|
||||||
|
|
||||||
insert_activity(mod_.id, remove, false, pool).await?;
|
insert_activity(mod_.id, remove, false, pool).await?;
|
||||||
|
|
||||||
let post_ap_id = PostForm::from_apub(&mut page, client, pool).await?.ap_id;
|
let post_ap_id = PostForm::from_apub(&page, client, pool).await?.ap_id;
|
||||||
|
|
||||||
let post = get_or_fetch_and_insert_remote_post(&post_ap_id, client, pool).await?;
|
let post = get_or_fetch_and_insert_remote_post(&post_ap_id, client, pool).await?;
|
||||||
|
|
||||||
|
@ -1527,7 +1519,7 @@ async fn receive_undo_delete_community(
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let mut group = delete
|
let group = delete
|
||||||
.delete_props
|
.delete_props
|
||||||
.get_object_base_box()
|
.get_object_base_box()
|
||||||
.to_owned()
|
.to_owned()
|
||||||
|
@ -1539,7 +1531,7 @@ async fn receive_undo_delete_community(
|
||||||
|
|
||||||
insert_activity(user.id, delete, false, pool).await?;
|
insert_activity(user.id, delete, false, pool).await?;
|
||||||
|
|
||||||
let community_actor_id = CommunityForm::from_apub(&mut group, client, pool)
|
let community_actor_id = CommunityForm::from_apub(&group, client, pool)
|
||||||
.await?
|
.await?
|
||||||
.actor_id;
|
.actor_id;
|
||||||
|
|
||||||
|
@ -1604,7 +1596,7 @@ async fn receive_undo_remove_community(
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let mut group = remove
|
let group = remove
|
||||||
.remove_props
|
.remove_props
|
||||||
.get_object_base_box()
|
.get_object_base_box()
|
||||||
.to_owned()
|
.to_owned()
|
||||||
|
@ -1616,7 +1608,7 @@ async fn receive_undo_remove_community(
|
||||||
|
|
||||||
insert_activity(mod_.id, remove, false, pool).await?;
|
insert_activity(mod_.id, remove, false, pool).await?;
|
||||||
|
|
||||||
let community_actor_id = CommunityForm::from_apub(&mut group, client, pool)
|
let community_actor_id = CommunityForm::from_apub(&group, client, pool)
|
||||||
.await?
|
.await?
|
||||||
.actor_id;
|
.actor_id;
|
||||||
|
|
||||||
|
@ -1704,7 +1696,7 @@ async fn receive_undo_like_comment(
|
||||||
pool: &DbPool,
|
pool: &DbPool,
|
||||||
chat_server: ChatServerParam,
|
chat_server: ChatServerParam,
|
||||||
) -> Result<HttpResponse, LemmyError> {
|
) -> Result<HttpResponse, LemmyError> {
|
||||||
let mut note = like
|
let note = like
|
||||||
.like_props
|
.like_props
|
||||||
.get_object_base_box()
|
.get_object_base_box()
|
||||||
.to_owned()
|
.to_owned()
|
||||||
|
@ -1718,7 +1710,7 @@ async fn receive_undo_like_comment(
|
||||||
|
|
||||||
insert_activity(user.id, like, false, pool).await?;
|
insert_activity(user.id, like, false, pool).await?;
|
||||||
|
|
||||||
let comment = CommentForm::from_apub(&mut note, client, pool).await?;
|
let comment = CommentForm::from_apub(¬e, client, pool).await?;
|
||||||
|
|
||||||
let comment_id = get_or_fetch_and_insert_remote_comment(&comment.ap_id, client, pool)
|
let comment_id = get_or_fetch_and_insert_remote_comment(&comment.ap_id, client, pool)
|
||||||
.await?
|
.await?
|
||||||
|
@ -1758,7 +1750,7 @@ async fn receive_undo_like_post(
|
||||||
pool: &DbPool,
|
pool: &DbPool,
|
||||||
chat_server: ChatServerParam,
|
chat_server: ChatServerParam,
|
||||||
) -> Result<HttpResponse, LemmyError> {
|
) -> Result<HttpResponse, LemmyError> {
|
||||||
let mut page = like
|
let page = like
|
||||||
.like_props
|
.like_props
|
||||||
.get_object_base_box()
|
.get_object_base_box()
|
||||||
.to_owned()
|
.to_owned()
|
||||||
|
@ -1772,7 +1764,7 @@ async fn receive_undo_like_post(
|
||||||
|
|
||||||
insert_activity(user.id, like, false, pool).await?;
|
insert_activity(user.id, like, false, pool).await?;
|
||||||
|
|
||||||
let post = PostForm::from_apub(&mut page, client, pool).await?;
|
let post = PostForm::from_apub(&page, client, pool).await?;
|
||||||
|
|
||||||
let post_id = get_or_fetch_and_insert_remote_post(&post.ap_id, client, pool)
|
let post_id = get_or_fetch_and_insert_remote_post(&post.ap_id, client, pool)
|
||||||
.await?
|
.await?
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
apub::{activities::send_activity, create_apub_response, ActorType, FromApub, PersonExt, ToApub},
|
api::claims::Claims,
|
||||||
blocking,
|
apub::{
|
||||||
convert_datetime,
|
activities::send_activity, create_apub_response, insert_activity, ActorType, FromApub,
|
||||||
db::{
|
PersonExt, ToApub,
|
||||||
activity::insert_activity,
|
|
||||||
user::{UserForm, User_},
|
|
||||||
},
|
},
|
||||||
naive_now,
|
blocking,
|
||||||
routes::DbPoolParam,
|
routes::DbPoolParam,
|
||||||
DbPool,
|
DbPool, LemmyError,
|
||||||
LemmyError,
|
|
||||||
};
|
};
|
||||||
use activitystreams_ext::Ext1;
|
use activitystreams_ext::Ext1;
|
||||||
use activitystreams_new::{
|
use activitystreams_new::{
|
||||||
|
@ -22,6 +19,11 @@ use activitystreams_new::{
|
||||||
};
|
};
|
||||||
use actix_web::{body::Body, client::Client, web, HttpResponse};
|
use actix_web::{body::Body, client::Client, web, HttpResponse};
|
||||||
use failure::_core::str::FromStr;
|
use failure::_core::str::FromStr;
|
||||||
|
use lemmy_db::{
|
||||||
|
naive_now,
|
||||||
|
user::{UserForm, User_},
|
||||||
|
};
|
||||||
|
use lemmy_utils::convert_datetime;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
@ -185,8 +187,8 @@ impl ActorType for User_ {
|
||||||
impl FromApub for UserForm {
|
impl FromApub for UserForm {
|
||||||
type ApubType = PersonExt;
|
type ApubType = PersonExt;
|
||||||
/// Parse an ActivityPub person received from another instance into a Lemmy user.
|
/// Parse an ActivityPub person received from another instance into a Lemmy user.
|
||||||
async fn from_apub(person: &mut PersonExt, _: &Client, _: &DbPool) -> Result<Self, LemmyError> {
|
async fn from_apub(person: &PersonExt, _: &Client, _: &DbPool) -> Result<Self, LemmyError> {
|
||||||
let avatar = match person.take_icon() {
|
let avatar = match person.icon() {
|
||||||
Some(any_image) => Image::from_any_base(any_image.as_one().unwrap().clone())
|
Some(any_image) => Image::from_any_base(any_image.as_one().unwrap().clone())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -199,19 +201,19 @@ impl FromApub for UserForm {
|
||||||
|
|
||||||
Ok(UserForm {
|
Ok(UserForm {
|
||||||
name: person
|
name: person
|
||||||
.take_name()
|
.name()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_single_xsd_string()
|
.as_single_xsd_string()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into(),
|
.into(),
|
||||||
preferred_username: person.inner.take_preferred_username(),
|
preferred_username: person.inner.preferred_username().map(|u| u.to_string()),
|
||||||
password_encrypted: "".to_string(),
|
password_encrypted: "".to_string(),
|
||||||
admin: false,
|
admin: false,
|
||||||
banned: false,
|
banned: false,
|
||||||
email: None,
|
email: None,
|
||||||
avatar,
|
avatar,
|
||||||
updated: person
|
updated: person
|
||||||
.take_updated()
|
.updated()
|
||||||
.map(|u| u.as_ref().to_owned().naive_local()),
|
.map(|u| u.as_ref().to_owned().naive_local()),
|
||||||
show_nsfw: false,
|
show_nsfw: false,
|
||||||
theme: "".to_string(),
|
theme: "".to_string(),
|
||||||
|
@ -223,7 +225,7 @@ impl FromApub for UserForm {
|
||||||
matrix_user_id: None,
|
matrix_user_id: None,
|
||||||
actor_id: person.id().unwrap().to_string(),
|
actor_id: person.id().unwrap().to_string(),
|
||||||
bio: person
|
bio: person
|
||||||
.take_summary()
|
.summary()
|
||||||
.map(|s| s.as_single_xsd_string().unwrap().into()),
|
.map(|s| s.as_single_xsd_string().unwrap().into()),
|
||||||
local: false,
|
local: false,
|
||||||
private_key: None,
|
private_key: None,
|
||||||
|
@ -240,7 +242,7 @@ pub async fn get_apub_user_http(
|
||||||
) -> Result<HttpResponse<Body>, LemmyError> {
|
) -> Result<HttpResponse<Body>, LemmyError> {
|
||||||
let user_name = info.into_inner().user_name;
|
let user_name = info.into_inner().user_name;
|
||||||
let user = blocking(&db, move |conn| {
|
let user = blocking(&db, move |conn| {
|
||||||
User_::find_by_email_or_username(conn, &user_name)
|
Claims::find_by_email_or_username(conn, &user_name)
|
||||||
})
|
})
|
||||||
.await??;
|
.await??;
|
||||||
let u = user.to_apub(&db).await?;
|
let u = user.to_apub(&db).await?;
|
||||||
|
|
|
@ -3,29 +3,26 @@ use crate::{
|
||||||
apub::{
|
apub::{
|
||||||
extensions::signatures::verify,
|
extensions::signatures::verify,
|
||||||
fetcher::{get_or_fetch_and_upsert_remote_community, get_or_fetch_and_upsert_remote_user},
|
fetcher::{get_or_fetch_and_upsert_remote_community, get_or_fetch_and_upsert_remote_user},
|
||||||
FromApub,
|
insert_activity, FromApub,
|
||||||
},
|
},
|
||||||
blocking,
|
blocking,
|
||||||
db::{
|
|
||||||
activity::insert_activity,
|
|
||||||
community::{CommunityFollower, CommunityFollowerForm},
|
|
||||||
private_message::{PrivateMessage, PrivateMessageForm},
|
|
||||||
private_message_view::PrivateMessageView,
|
|
||||||
user::User_,
|
|
||||||
Crud,
|
|
||||||
Followable,
|
|
||||||
},
|
|
||||||
naive_now,
|
|
||||||
routes::{ChatServerParam, DbPoolParam},
|
routes::{ChatServerParam, DbPoolParam},
|
||||||
websocket::{server::SendUserRoomMessage, UserOperation},
|
websocket::{server::SendUserRoomMessage, UserOperation},
|
||||||
DbPool,
|
DbPool, LemmyError,
|
||||||
LemmyError,
|
|
||||||
};
|
};
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
activity::{Accept, Create, Delete, Undo, Update},
|
activity::{Accept, Create, Delete, Undo, Update},
|
||||||
object::Note,
|
object::Note,
|
||||||
};
|
};
|
||||||
use actix_web::{client::Client, web, HttpRequest, HttpResponse};
|
use actix_web::{client::Client, web, HttpRequest, HttpResponse};
|
||||||
|
use lemmy_db::{
|
||||||
|
community::{CommunityFollower, CommunityFollowerForm},
|
||||||
|
naive_now,
|
||||||
|
private_message::{PrivateMessage, PrivateMessageForm},
|
||||||
|
private_message_view::PrivateMessageView,
|
||||||
|
user::User_,
|
||||||
|
Crud, Followable,
|
||||||
|
};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
@ -116,7 +113,7 @@ async fn receive_create_private_message(
|
||||||
pool: &DbPool,
|
pool: &DbPool,
|
||||||
chat_server: ChatServerParam,
|
chat_server: ChatServerParam,
|
||||||
) -> Result<HttpResponse, LemmyError> {
|
) -> Result<HttpResponse, LemmyError> {
|
||||||
let mut note = create
|
let note = create
|
||||||
.create_props
|
.create_props
|
||||||
.get_object_base_box()
|
.get_object_base_box()
|
||||||
.to_owned()
|
.to_owned()
|
||||||
|
@ -135,7 +132,7 @@ async fn receive_create_private_message(
|
||||||
|
|
||||||
insert_activity(user.id, create, false, pool).await?;
|
insert_activity(user.id, create, false, pool).await?;
|
||||||
|
|
||||||
let private_message = PrivateMessageForm::from_apub(&mut note, client, pool).await?;
|
let private_message = PrivateMessageForm::from_apub(¬e, client, pool).await?;
|
||||||
|
|
||||||
let inserted_private_message = blocking(pool, move |conn| {
|
let inserted_private_message = blocking(pool, move |conn| {
|
||||||
PrivateMessage::create(conn, &private_message)
|
PrivateMessage::create(conn, &private_message)
|
||||||
|
@ -168,7 +165,7 @@ async fn receive_update_private_message(
|
||||||
pool: &DbPool,
|
pool: &DbPool,
|
||||||
chat_server: ChatServerParam,
|
chat_server: ChatServerParam,
|
||||||
) -> Result<HttpResponse, LemmyError> {
|
) -> Result<HttpResponse, LemmyError> {
|
||||||
let mut note = update
|
let note = update
|
||||||
.update_props
|
.update_props
|
||||||
.get_object_base_box()
|
.get_object_base_box()
|
||||||
.to_owned()
|
.to_owned()
|
||||||
|
@ -187,7 +184,7 @@ async fn receive_update_private_message(
|
||||||
|
|
||||||
insert_activity(user.id, update, false, pool).await?;
|
insert_activity(user.id, update, false, pool).await?;
|
||||||
|
|
||||||
let private_message_form = PrivateMessageForm::from_apub(&mut note, client, pool).await?;
|
let private_message_form = PrivateMessageForm::from_apub(¬e, client, pool).await?;
|
||||||
|
|
||||||
let private_message_ap_id = private_message_form.ap_id.clone();
|
let private_message_ap_id = private_message_form.ap_id.clone();
|
||||||
let private_message = blocking(pool, move |conn| {
|
let private_message = blocking(pool, move |conn| {
|
||||||
|
@ -228,7 +225,7 @@ async fn receive_delete_private_message(
|
||||||
pool: &DbPool,
|
pool: &DbPool,
|
||||||
chat_server: ChatServerParam,
|
chat_server: ChatServerParam,
|
||||||
) -> Result<HttpResponse, LemmyError> {
|
) -> Result<HttpResponse, LemmyError> {
|
||||||
let mut note = delete
|
let note = delete
|
||||||
.delete_props
|
.delete_props
|
||||||
.get_object_base_box()
|
.get_object_base_box()
|
||||||
.to_owned()
|
.to_owned()
|
||||||
|
@ -247,7 +244,7 @@ async fn receive_delete_private_message(
|
||||||
|
|
||||||
insert_activity(user.id, delete, false, pool).await?;
|
insert_activity(user.id, delete, false, pool).await?;
|
||||||
|
|
||||||
let private_message_form = PrivateMessageForm::from_apub(&mut note, client, pool).await?;
|
let private_message_form = PrivateMessageForm::from_apub(¬e, client, pool).await?;
|
||||||
|
|
||||||
let private_message_ap_id = private_message_form.ap_id;
|
let private_message_ap_id = private_message_form.ap_id;
|
||||||
let private_message = blocking(pool, move |conn| {
|
let private_message = blocking(pool, move |conn| {
|
||||||
|
@ -308,7 +305,7 @@ async fn receive_undo_delete_private_message(
|
||||||
.to_owned()
|
.to_owned()
|
||||||
.into_concrete::<Delete>()?;
|
.into_concrete::<Delete>()?;
|
||||||
|
|
||||||
let mut note = delete
|
let note = delete
|
||||||
.delete_props
|
.delete_props
|
||||||
.get_object_base_box()
|
.get_object_base_box()
|
||||||
.to_owned()
|
.to_owned()
|
||||||
|
@ -327,7 +324,7 @@ async fn receive_undo_delete_private_message(
|
||||||
|
|
||||||
insert_activity(user.id, delete, false, pool).await?;
|
insert_activity(user.id, delete, false, pool).await?;
|
||||||
|
|
||||||
let private_message = PrivateMessageForm::from_apub(&mut note, client, pool).await?;
|
let private_message = PrivateMessageForm::from_apub(¬e, client, pool).await?;
|
||||||
|
|
||||||
let private_message_ap_id = private_message.ap_id.clone();
|
let private_message_ap_id = private_message.ap_id.clone();
|
||||||
let private_message_id = blocking(pool, move |conn| {
|
let private_message_id = blocking(pool, move |conn| {
|
||||||
|
|
|
@ -1,18 +1,16 @@
|
||||||
// This is for db migrations that require code
|
// This is for db migrations that require code
|
||||||
use super::{
|
use crate::LemmyError;
|
||||||
|
use diesel::*;
|
||||||
|
use lemmy_db::{
|
||||||
comment::Comment,
|
comment::Comment,
|
||||||
community::{Community, CommunityForm},
|
community::{Community, CommunityForm},
|
||||||
|
naive_now,
|
||||||
post::Post,
|
post::Post,
|
||||||
private_message::PrivateMessage,
|
private_message::PrivateMessage,
|
||||||
user::{UserForm, User_},
|
user::{UserForm, User_},
|
||||||
|
Crud,
|
||||||
};
|
};
|
||||||
use crate::{
|
use lemmy_utils::{generate_actor_keypair, make_apub_endpoint, EndpointType};
|
||||||
apub::{extensions::signatures::generate_actor_keypair, make_apub_endpoint, EndpointType},
|
|
||||||
db::Crud,
|
|
||||||
naive_now,
|
|
||||||
LemmyError,
|
|
||||||
};
|
|
||||||
use diesel::*;
|
|
||||||
use log::info;
|
use log::info;
|
||||||
|
|
||||||
pub fn run_advanced_migrations(conn: &PgConnection) -> Result<(), LemmyError> {
|
pub fn run_advanced_migrations(conn: &PgConnection) -> Result<(), LemmyError> {
|
||||||
|
@ -26,7 +24,7 @@ pub fn run_advanced_migrations(conn: &PgConnection) -> Result<(), LemmyError> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn user_updates_2020_04_02(conn: &PgConnection) -> Result<(), LemmyError> {
|
fn user_updates_2020_04_02(conn: &PgConnection) -> Result<(), LemmyError> {
|
||||||
use crate::schema::user_::dsl::*;
|
use lemmy_db::schema::user_::dsl::*;
|
||||||
|
|
||||||
info!("Running user_updates_2020_04_02");
|
info!("Running user_updates_2020_04_02");
|
||||||
|
|
||||||
|
@ -77,7 +75,7 @@ fn user_updates_2020_04_02(conn: &PgConnection) -> Result<(), LemmyError> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn community_updates_2020_04_02(conn: &PgConnection) -> Result<(), LemmyError> {
|
fn community_updates_2020_04_02(conn: &PgConnection) -> Result<(), LemmyError> {
|
||||||
use crate::schema::community::dsl::*;
|
use lemmy_db::schema::community::dsl::*;
|
||||||
|
|
||||||
info!("Running community_updates_2020_04_02");
|
info!("Running community_updates_2020_04_02");
|
||||||
|
|
||||||
|
@ -121,7 +119,7 @@ fn community_updates_2020_04_02(conn: &PgConnection) -> Result<(), LemmyError> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn post_updates_2020_04_03(conn: &PgConnection) -> Result<(), LemmyError> {
|
fn post_updates_2020_04_03(conn: &PgConnection) -> Result<(), LemmyError> {
|
||||||
use crate::schema::post::dsl::*;
|
use lemmy_db::schema::post::dsl::*;
|
||||||
|
|
||||||
info!("Running post_updates_2020_04_03");
|
info!("Running post_updates_2020_04_03");
|
||||||
|
|
||||||
|
@ -134,7 +132,8 @@ fn post_updates_2020_04_03(conn: &PgConnection) -> Result<(), LemmyError> {
|
||||||
sql_query("alter table post disable trigger refresh_post").execute(conn)?;
|
sql_query("alter table post disable trigger refresh_post").execute(conn)?;
|
||||||
|
|
||||||
for cpost in &incorrect_posts {
|
for cpost in &incorrect_posts {
|
||||||
Post::update_ap_id(&conn, cpost.id)?;
|
let apub_id = make_apub_endpoint(EndpointType::Post, &cpost.id.to_string()).to_string();
|
||||||
|
Post::update_ap_id(&conn, cpost.id, apub_id)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("{} post rows updated.", incorrect_posts.len());
|
info!("{} post rows updated.", incorrect_posts.len());
|
||||||
|
@ -145,7 +144,7 @@ fn post_updates_2020_04_03(conn: &PgConnection) -> Result<(), LemmyError> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn comment_updates_2020_04_03(conn: &PgConnection) -> Result<(), LemmyError> {
|
fn comment_updates_2020_04_03(conn: &PgConnection) -> Result<(), LemmyError> {
|
||||||
use crate::schema::comment::dsl::*;
|
use lemmy_db::schema::comment::dsl::*;
|
||||||
|
|
||||||
info!("Running comment_updates_2020_04_03");
|
info!("Running comment_updates_2020_04_03");
|
||||||
|
|
||||||
|
@ -158,7 +157,8 @@ fn comment_updates_2020_04_03(conn: &PgConnection) -> Result<(), LemmyError> {
|
||||||
sql_query("alter table comment disable trigger refresh_comment").execute(conn)?;
|
sql_query("alter table comment disable trigger refresh_comment").execute(conn)?;
|
||||||
|
|
||||||
for ccomment in &incorrect_comments {
|
for ccomment in &incorrect_comments {
|
||||||
Comment::update_ap_id(&conn, ccomment.id)?;
|
let apub_id = make_apub_endpoint(EndpointType::Comment, &ccomment.id.to_string()).to_string();
|
||||||
|
Comment::update_ap_id(&conn, ccomment.id, apub_id)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
sql_query("alter table comment enable trigger refresh_comment").execute(conn)?;
|
sql_query("alter table comment enable trigger refresh_comment").execute(conn)?;
|
||||||
|
@ -169,7 +169,7 @@ fn comment_updates_2020_04_03(conn: &PgConnection) -> Result<(), LemmyError> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn private_message_updates_2020_05_05(conn: &PgConnection) -> Result<(), LemmyError> {
|
fn private_message_updates_2020_05_05(conn: &PgConnection) -> Result<(), LemmyError> {
|
||||||
use crate::schema::private_message::dsl::*;
|
use lemmy_db::schema::private_message::dsl::*;
|
||||||
|
|
||||||
info!("Running private_message_updates_2020_05_05");
|
info!("Running private_message_updates_2020_05_05");
|
||||||
|
|
||||||
|
@ -180,7 +180,8 @@ fn private_message_updates_2020_05_05(conn: &PgConnection) -> Result<(), LemmyEr
|
||||||
.load::<PrivateMessage>(conn)?;
|
.load::<PrivateMessage>(conn)?;
|
||||||
|
|
||||||
for cpm in &incorrect_pms {
|
for cpm in &incorrect_pms {
|
||||||
PrivateMessage::update_ap_id(&conn, cpm.id)?;
|
let apub_id = make_apub_endpoint(EndpointType::PrivateMessage, &cpm.id.to_string()).to_string();
|
||||||
|
PrivateMessage::update_ap_id(&conn, cpm.id, apub_id)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("{} private message rows updated.", incorrect_pms.len());
|
info!("{} private message rows updated.", incorrect_pms.len());
|
|
@ -5,76 +5,34 @@ pub extern crate strum_macros;
|
||||||
pub extern crate lazy_static;
|
pub extern crate lazy_static;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub extern crate failure;
|
pub extern crate failure;
|
||||||
#[macro_use]
|
|
||||||
pub extern crate diesel;
|
|
||||||
pub extern crate actix;
|
pub extern crate actix;
|
||||||
pub extern crate actix_web;
|
pub extern crate actix_web;
|
||||||
pub extern crate bcrypt;
|
pub extern crate bcrypt;
|
||||||
pub extern crate chrono;
|
pub extern crate chrono;
|
||||||
pub extern crate comrak;
|
pub extern crate diesel;
|
||||||
pub extern crate dotenv;
|
pub extern crate dotenv;
|
||||||
pub extern crate jsonwebtoken;
|
pub extern crate jsonwebtoken;
|
||||||
pub extern crate lettre;
|
|
||||||
pub extern crate lettre_email;
|
|
||||||
extern crate log;
|
extern crate log;
|
||||||
pub extern crate openssl;
|
pub extern crate openssl;
|
||||||
pub extern crate rand;
|
|
||||||
pub extern crate regex;
|
|
||||||
pub extern crate rss;
|
pub extern crate rss;
|
||||||
pub extern crate serde;
|
pub extern crate serde;
|
||||||
pub extern crate serde_json;
|
pub extern crate serde_json;
|
||||||
pub extern crate sha2;
|
pub extern crate sha2;
|
||||||
pub extern crate strum;
|
pub extern crate strum;
|
||||||
|
|
||||||
pub async fn blocking<F, T>(pool: &DbPool, f: F) -> Result<T, LemmyError>
|
|
||||||
where
|
|
||||||
F: FnOnce(&diesel::PgConnection) -> T + Send + 'static,
|
|
||||||
T: Send + 'static,
|
|
||||||
{
|
|
||||||
let pool = pool.clone();
|
|
||||||
let res = actix_web::web::block(move || {
|
|
||||||
let conn = pool.get()?;
|
|
||||||
let res = (f)(&conn);
|
|
||||||
Ok(res) as Result<_, LemmyError>
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod api;
|
pub mod api;
|
||||||
pub mod apub;
|
pub mod apub;
|
||||||
pub mod db;
|
pub mod code_migrations;
|
||||||
pub mod rate_limit;
|
pub mod rate_limit;
|
||||||
pub mod request;
|
pub mod request;
|
||||||
pub mod routes;
|
pub mod routes;
|
||||||
pub mod schema;
|
|
||||||
pub mod settings;
|
|
||||||
pub mod version;
|
pub mod version;
|
||||||
pub mod websocket;
|
pub mod websocket;
|
||||||
|
|
||||||
use crate::{
|
use crate::request::{retry, RecvError};
|
||||||
request::{retry, RecvError},
|
|
||||||
settings::Settings,
|
|
||||||
};
|
|
||||||
use actix_web::{client::Client, dev::ConnectionInfo};
|
use actix_web::{client::Client, dev::ConnectionInfo};
|
||||||
use chrono::{DateTime, FixedOffset, Local, NaiveDateTime, Utc};
|
|
||||||
use itertools::Itertools;
|
|
||||||
use lettre::{
|
|
||||||
smtp::{
|
|
||||||
authentication::{Credentials, Mechanism},
|
|
||||||
extension::ClientId,
|
|
||||||
ConnectionReuseParameters,
|
|
||||||
},
|
|
||||||
ClientSecurity,
|
|
||||||
SmtpClient,
|
|
||||||
Transport,
|
|
||||||
};
|
|
||||||
use lettre_email::Email;
|
|
||||||
use log::error;
|
use log::error;
|
||||||
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
|
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
|
||||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
|
||||||
use regex::{Regex, RegexBuilder};
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
pub type DbPool = diesel::r2d2::Pool<diesel::r2d2::ConnectionManager<diesel::PgConnection>>;
|
pub type DbPool = diesel::r2d2::Pool<diesel::r2d2::ConnectionManager<diesel::PgConnection>>;
|
||||||
|
@ -89,14 +47,6 @@ pub struct LemmyError {
|
||||||
inner: failure::Error,
|
inner: failure::Error,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for LemmyError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
self.inner.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl actix_web::error::ResponseError for LemmyError {}
|
|
||||||
|
|
||||||
impl<T> From<T> for LemmyError
|
impl<T> From<T> for LemmyError
|
||||||
where
|
where
|
||||||
T: Into<failure::Error>,
|
T: Into<failure::Error>,
|
||||||
|
@ -106,113 +56,13 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_datetime_utc(ndt: NaiveDateTime) -> DateTime<Utc> {
|
impl std::fmt::Display for LemmyError {
|
||||||
DateTime::<Utc>::from_utc(ndt, Utc)
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
}
|
self.inner.fmt(f)
|
||||||
|
|
||||||
pub fn naive_now() -> NaiveDateTime {
|
|
||||||
chrono::prelude::Utc::now().naive_utc()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn naive_from_unix(time: i64) -> NaiveDateTime {
|
|
||||||
NaiveDateTime::from_timestamp(time, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn convert_datetime(datetime: NaiveDateTime) -> DateTime<FixedOffset> {
|
|
||||||
let now = Local::now();
|
|
||||||
DateTime::<FixedOffset>::from_utc(datetime, *now.offset())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_email_regex(test: &str) -> bool {
|
|
||||||
EMAIL_REGEX.is_match(test)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn is_image_content_type(client: &Client, test: &str) -> Result<(), LemmyError> {
|
|
||||||
let response = retry(|| client.get(test).send()).await?;
|
|
||||||
|
|
||||||
if response
|
|
||||||
.headers()
|
|
||||||
.get("Content-Type")
|
|
||||||
.ok_or_else(|| format_err!("No Content-Type header"))?
|
|
||||||
.to_str()?
|
|
||||||
.starts_with("image/")
|
|
||||||
{
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(format_err!("Not an image type.").into())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_slurs(test: &str) -> String {
|
impl actix_web::error::ResponseError for LemmyError {}
|
||||||
SLUR_REGEX.replace_all(test, "*removed*").to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn slur_check(test: &str) -> Result<(), Vec<&str>> {
|
|
||||||
let mut matches: Vec<&str> = SLUR_REGEX.find_iter(test).map(|mat| mat.as_str()).collect();
|
|
||||||
|
|
||||||
// Unique
|
|
||||||
matches.sort_unstable();
|
|
||||||
matches.dedup();
|
|
||||||
|
|
||||||
if matches.is_empty() {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(matches)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn slurs_vec_to_str(slurs: Vec<&str>) -> String {
|
|
||||||
let start = "No slurs - ";
|
|
||||||
let combined = &slurs.join(", ");
|
|
||||||
[start, combined].concat()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn generate_random_string() -> String {
|
|
||||||
thread_rng().sample_iter(&Alphanumeric).take(30).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn send_email(
|
|
||||||
subject: &str,
|
|
||||||
to_email: &str,
|
|
||||||
to_username: &str,
|
|
||||||
html: &str,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
let email_config = Settings::get().email.ok_or("no_email_setup")?;
|
|
||||||
|
|
||||||
let email = Email::builder()
|
|
||||||
.to((to_email, to_username))
|
|
||||||
.from(email_config.smtp_from_address.to_owned())
|
|
||||||
.subject(subject)
|
|
||||||
.html(html)
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mailer = if email_config.use_tls {
|
|
||||||
SmtpClient::new_simple(&email_config.smtp_server).unwrap()
|
|
||||||
} else {
|
|
||||||
SmtpClient::new(&email_config.smtp_server, ClientSecurity::None).unwrap()
|
|
||||||
}
|
|
||||||
.hello_name(ClientId::Domain(Settings::get().hostname))
|
|
||||||
.smtp_utf8(true)
|
|
||||||
.authentication_mechanism(Mechanism::Plain)
|
|
||||||
.connection_reuse(ConnectionReuseParameters::ReuseUnlimited);
|
|
||||||
let mailer = if let (Some(login), Some(password)) =
|
|
||||||
(&email_config.smtp_login, &email_config.smtp_password)
|
|
||||||
{
|
|
||||||
mailer.credentials(Credentials::new(login.to_owned(), password.to_owned()))
|
|
||||||
} else {
|
|
||||||
mailer
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut transport = mailer.transport();
|
|
||||||
let result = transport.send(email.into());
|
|
||||||
transport.close();
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(_) => Ok(()),
|
|
||||||
Err(e) => Err(e.to_string()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct IframelyResponse {
|
pub struct IframelyResponse {
|
||||||
|
@ -319,8 +169,20 @@ async fn fetch_iframely_and_pictrs_data(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn markdown_to_html(text: &str) -> String {
|
pub async fn is_image_content_type(client: &Client, test: &str) -> Result<(), LemmyError> {
|
||||||
comrak::markdown_to_html(text, &comrak::ComrakOptions::default())
|
let response = retry(|| client.get(test).send()).await?;
|
||||||
|
|
||||||
|
if response
|
||||||
|
.headers()
|
||||||
|
.get("Content-Type")
|
||||||
|
.ok_or_else(|| format_err!("No Content-Type header"))?
|
||||||
|
.to_str()?
|
||||||
|
.starts_with("image/")
|
||||||
|
{
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(format_err!("Not an image type.").into())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_ip(conn_info: &ConnectionInfo) -> String {
|
pub fn get_ip(conn_info: &ConnectionInfo) -> String {
|
||||||
|
@ -333,63 +195,25 @@ pub fn get_ip(conn_info: &ConnectionInfo) -> String {
|
||||||
.to_string()
|
.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO nothing is done with community / group webfingers yet, so just ignore those for now
|
pub async fn blocking<F, T>(pool: &DbPool, f: F) -> Result<T, LemmyError>
|
||||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
where
|
||||||
pub struct MentionData {
|
F: FnOnce(&diesel::PgConnection) -> T + Send + 'static,
|
||||||
pub name: String,
|
T: Send + 'static,
|
||||||
pub domain: String,
|
{
|
||||||
}
|
let pool = pool.clone();
|
||||||
|
let res = actix_web::web::block(move || {
|
||||||
|
let conn = pool.get()?;
|
||||||
|
let res = (f)(&conn);
|
||||||
|
Ok(res) as Result<_, LemmyError>
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
impl MentionData {
|
Ok(res)
|
||||||
pub fn is_local(&self) -> bool {
|
|
||||||
Settings::get().hostname.eq(&self.domain)
|
|
||||||
}
|
|
||||||
pub fn full_name(&self) -> String {
|
|
||||||
format!("@{}@{}", &self.name, &self.domain)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scrape_text_for_mentions(text: &str) -> Vec<MentionData> {
|
|
||||||
let mut out: Vec<MentionData> = Vec::new();
|
|
||||||
for caps in WEBFINGER_USER_REGEX.captures_iter(text) {
|
|
||||||
out.push(MentionData {
|
|
||||||
name: caps["name"].to_string(),
|
|
||||||
domain: caps["domain"].to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
out.into_iter().unique().collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_valid_username(name: &str) -> bool {
|
|
||||||
VALID_USERNAME_REGEX.is_match(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_valid_community_name(name: &str) -> bool {
|
|
||||||
VALID_COMMUNITY_NAME_REGEX.is_match(name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{
|
use crate::is_image_content_type;
|
||||||
is_email_regex,
|
|
||||||
is_image_content_type,
|
|
||||||
is_valid_community_name,
|
|
||||||
is_valid_username,
|
|
||||||
remove_slurs,
|
|
||||||
scrape_text_for_mentions,
|
|
||||||
slur_check,
|
|
||||||
slurs_vec_to_str,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_mentions_regex() {
|
|
||||||
let text = "Just read a great blog post by [@tedu@honk.teduangst.com](/u/test). And another by !test_community@fish.teduangst.com . Another [@lemmy@lemmy-alpha:8540](/u/fish)";
|
|
||||||
let mentions = scrape_text_for_mentions(text);
|
|
||||||
|
|
||||||
assert_eq!(mentions[0].name, "tedu".to_string());
|
|
||||||
assert_eq!(mentions[0].domain, "honk.teduangst.com".to_string());
|
|
||||||
assert_eq!(mentions[1].domain, "lemmy-alpha:8540".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_image() {
|
fn test_image() {
|
||||||
|
@ -404,58 +228,6 @@ mod tests {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_email() {
|
|
||||||
assert!(is_email_regex("gush@gmail.com"));
|
|
||||||
assert!(!is_email_regex("nada_neutho"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_valid_register_username() {
|
|
||||||
assert!(is_valid_username("Hello_98"));
|
|
||||||
assert!(is_valid_username("ten"));
|
|
||||||
assert!(!is_valid_username("Hello-98"));
|
|
||||||
assert!(!is_valid_username("a"));
|
|
||||||
assert!(!is_valid_username(""));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_valid_community_name() {
|
|
||||||
assert!(is_valid_community_name("example"));
|
|
||||||
assert!(is_valid_community_name("example_community"));
|
|
||||||
assert!(!is_valid_community_name("Example"));
|
|
||||||
assert!(!is_valid_community_name("Ex"));
|
|
||||||
assert!(!is_valid_community_name(""));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_slur_filter() {
|
|
||||||
let test =
|
|
||||||
"coons test dindu ladyboy tranny retardeds. Capitalized Niggerz. This is a bunch of other safe text.";
|
|
||||||
let slur_free = "No slurs here";
|
|
||||||
assert_eq!(
|
|
||||||
remove_slurs(&test),
|
|
||||||
"*removed* test *removed* *removed* *removed* *removed*. Capitalized *removed*. This is a bunch of other safe text."
|
|
||||||
.to_string()
|
|
||||||
);
|
|
||||||
|
|
||||||
let has_slurs_vec = vec![
|
|
||||||
"Niggerz",
|
|
||||||
"coons",
|
|
||||||
"dindu",
|
|
||||||
"ladyboy",
|
|
||||||
"retardeds",
|
|
||||||
"tranny",
|
|
||||||
];
|
|
||||||
let has_slurs_err_str = "No slurs - Niggerz, coons, dindu, ladyboy, retardeds, tranny";
|
|
||||||
|
|
||||||
assert_eq!(slur_check(test), Err(has_slurs_vec));
|
|
||||||
assert_eq!(slur_check(slur_free), Ok(()));
|
|
||||||
if let Err(slur_vec) = slur_check(test) {
|
|
||||||
assert_eq!(&slurs_vec_to_str(slur_vec), has_slurs_err_str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// These helped with testing
|
// These helped with testing
|
||||||
// #[test]
|
// #[test]
|
||||||
// fn test_iframely() {
|
// fn test_iframely() {
|
||||||
|
@ -470,21 +242,4 @@ mod tests {
|
||||||
// let res_other = fetch_pictshare("https://upload.wikimedia.org/wikipedia/en/2/27/The_Mandalorian_logo.jpgaoeu");
|
// let res_other = fetch_pictshare("https://upload.wikimedia.org/wikipedia/en/2/27/The_Mandalorian_logo.jpgaoeu");
|
||||||
// assert!(res_other.is_err());
|
// assert!(res_other.is_err());
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn test_send_email() {
|
|
||||||
// let result = send_email("not a subject", "test_email@gmail.com", "ur user", "<h1>HI there</h1>");
|
|
||||||
// assert!(result.is_ok());
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref EMAIL_REGEX: Regex = Regex::new(r"^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$").unwrap();
|
|
||||||
static ref SLUR_REGEX: Regex = RegexBuilder::new(r"(fag(g|got|tard)?|maricos?|cock\s?sucker(s|ing)?|\bn(i|1)g(\b|g?(a|er)?(s|z)?)\b|dindu(s?)|mudslime?s?|kikes?|mongoloids?|towel\s*heads?|\bspi(c|k)s?\b|\bchinks?|niglets?|beaners?|\bnips?\b|\bcoons?\b|jungle\s*bunn(y|ies?)|jigg?aboo?s?|\bpakis?\b|rag\s*heads?|gooks?|cunts?|bitch(es|ing|y)?|puss(y|ies?)|twats?|feminazis?|whor(es?|ing)|\bslut(s|t?y)?|\btr(a|@)nn?(y|ies?)|ladyboy(s?)|\b(b|re|r)tard(ed)?s?)").case_insensitive(true).build().unwrap();
|
|
||||||
static ref USERNAME_MATCHES_REGEX: Regex = Regex::new(r"/u/[a-zA-Z][0-9a-zA-Z_]*").unwrap();
|
|
||||||
// TODO keep this old one, it didn't work with port well tho
|
|
||||||
// static ref WEBFINGER_USER_REGEX: Regex = Regex::new(r"@(?P<name>[\w.]+)@(?P<domain>[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+)").unwrap();
|
|
||||||
static ref WEBFINGER_USER_REGEX: Regex = Regex::new(r"@(?P<name>[\w.]+)@(?P<domain>[a-zA-Z0-9._:-]+)").unwrap();
|
|
||||||
static ref VALID_USERNAME_REGEX: Regex = Regex::new(r"^[a-zA-Z0-9_]{3,20}$").unwrap();
|
|
||||||
static ref VALID_COMMUNITY_NAME_REGEX: Regex = Regex::new(r"^[a-z0-9_]{3,20}$").unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,22 +22,20 @@ use diesel::{
|
||||||
r2d2::{ConnectionManager, Pool},
|
r2d2::{ConnectionManager, Pool},
|
||||||
PgConnection,
|
PgConnection,
|
||||||
};
|
};
|
||||||
|
use lemmy_db::get_database_url_from_env;
|
||||||
use lemmy_server::{
|
use lemmy_server::{
|
||||||
blocking,
|
blocking,
|
||||||
db::code_migrations::run_advanced_migrations,
|
code_migrations::run_advanced_migrations,
|
||||||
rate_limit::{rate_limiter::RateLimiter, RateLimit},
|
rate_limit::{rate_limiter::RateLimiter, RateLimit},
|
||||||
routes::{api, federation, feeds, index, nodeinfo, webfinger},
|
routes::{api, federation, feeds, index, nodeinfo, webfinger},
|
||||||
settings::Settings,
|
|
||||||
websocket::server::*,
|
websocket::server::*,
|
||||||
LemmyError,
|
LemmyError,
|
||||||
};
|
};
|
||||||
use regex::Regex;
|
use lemmy_utils::{settings::Settings, CACHE_CONTROL_REGEX};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref CACHE_CONTROL_REGEX: Regex =
|
|
||||||
Regex::new("^((text|image)/.+|application/javascript)$").unwrap();
|
|
||||||
// static ref CACHE_CONTROL_VALUE: String = format!("public, max-age={}", 365 * 24 * 60 * 60);
|
// static ref CACHE_CONTROL_VALUE: String = format!("public, max-age={}", 365 * 24 * 60 * 60);
|
||||||
// Test out 1 hour here, this is breaking some things
|
// Test out 1 hour here, this is breaking some things
|
||||||
static ref CACHE_CONTROL_VALUE: String = format!("public, max-age={}", 60 * 60);
|
static ref CACHE_CONTROL_VALUE: String = format!("public, max-age={}", 60 * 60);
|
||||||
|
@ -51,11 +49,15 @@ async fn main() -> Result<(), LemmyError> {
|
||||||
let settings = Settings::get();
|
let settings = Settings::get();
|
||||||
|
|
||||||
// Set up the r2d2 connection pool
|
// Set up the r2d2 connection pool
|
||||||
let manager = ConnectionManager::<PgConnection>::new(&settings.get_database_url());
|
let db_url = match get_database_url_from_env() {
|
||||||
|
Ok(url) => url,
|
||||||
|
Err(_) => settings.get_database_url(),
|
||||||
|
};
|
||||||
|
let manager = ConnectionManager::<PgConnection>::new(&db_url);
|
||||||
let pool = Pool::builder()
|
let pool = Pool::builder()
|
||||||
.max_size(settings.database.pool_size)
|
.max_size(settings.database.pool_size)
|
||||||
.build(manager)
|
.build(manager)
|
||||||
.unwrap_or_else(|_| panic!("Error connecting to {}", settings.get_database_url()));
|
.unwrap_or_else(|_| panic!("Error connecting to {}", db_url));
|
||||||
|
|
||||||
// Run the migrations from code
|
// Run the migrations from code
|
||||||
blocking(&pool, move |conn| {
|
blocking(&pool, move |conn| {
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use super::{IPAddr, Settings};
|
use super::IPAddr;
|
||||||
use crate::{get_ip, settings::RateLimitConfig, LemmyError};
|
use crate::{get_ip, LemmyError};
|
||||||
use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform};
|
use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform};
|
||||||
use futures::future::{ok, Ready};
|
use futures::future::{ok, Ready};
|
||||||
|
use lemmy_utils::settings::{RateLimitConfig, Settings};
|
||||||
use rate_limiter::{RateLimitType, RateLimiter};
|
use rate_limiter::{RateLimitType, RateLimiter};
|
||||||
use std::{
|
use std::{
|
||||||
future::Future,
|
future::Future,
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use crate::{
|
use crate::apub::{
|
||||||
apub::{
|
|
||||||
comment::get_apub_comment,
|
comment::get_apub_comment,
|
||||||
community::*,
|
community::*,
|
||||||
community_inbox::community_inbox,
|
community_inbox::community_inbox,
|
||||||
|
@ -8,14 +7,17 @@ use crate::{
|
||||||
user::*,
|
user::*,
|
||||||
user_inbox::user_inbox,
|
user_inbox::user_inbox,
|
||||||
APUB_JSON_CONTENT_TYPE,
|
APUB_JSON_CONTENT_TYPE,
|
||||||
},
|
|
||||||
settings::Settings,
|
|
||||||
};
|
};
|
||||||
use actix_web::*;
|
use actix_web::*;
|
||||||
|
use http_signature_normalization_actix::digest::middleware::VerifyDigest;
|
||||||
|
use lemmy_utils::settings::Settings;
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||||
if Settings::get().federation.enabled {
|
if Settings::get().federation.enabled {
|
||||||
println!("federation enabled, host is {}", Settings::get().hostname);
|
println!("federation enabled, host is {}", Settings::get().hostname);
|
||||||
|
let digest_verifier = VerifyDigest::new(Sha256::new());
|
||||||
|
|
||||||
cfg
|
cfg
|
||||||
.service(
|
.service(
|
||||||
web::scope("/")
|
web::scope("/")
|
||||||
|
@ -38,8 +40,20 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||||
.route("/comment/{comment_id}", web::get().to(get_apub_comment)),
|
.route("/comment/{comment_id}", web::get().to(get_apub_comment)),
|
||||||
)
|
)
|
||||||
// Inboxes dont work with the header guard for some reason.
|
// Inboxes dont work with the header guard for some reason.
|
||||||
.route("/c/{community_name}/inbox", web::post().to(community_inbox))
|
.service(
|
||||||
.route("/u/{user_name}/inbox", web::post().to(user_inbox))
|
web::resource("/c/{community_name}/inbox")
|
||||||
.route("/inbox", web::post().to(shared_inbox));
|
.wrap(digest_verifier.clone())
|
||||||
|
.route(web::post().to(community_inbox)),
|
||||||
|
)
|
||||||
|
.service(
|
||||||
|
web::resource("/u/{user_name}/inbox")
|
||||||
|
.wrap(digest_verifier.clone())
|
||||||
|
.route(web::post().to(user_inbox)),
|
||||||
|
)
|
||||||
|
.service(
|
||||||
|
web::resource("/inbox")
|
||||||
|
.wrap(digest_verifier)
|
||||||
|
.route(web::post().to(shared_inbox)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +1,21 @@
|
||||||
use crate::{
|
use crate::{api::claims::Claims, blocking, routes::DbPoolParam, LemmyError};
|
||||||
blocking,
|
|
||||||
db::{
|
|
||||||
comment_view::{ReplyQueryBuilder, ReplyView},
|
|
||||||
community::Community,
|
|
||||||
post_view::{PostQueryBuilder, PostView},
|
|
||||||
site_view::SiteView,
|
|
||||||
user::{Claims, User_},
|
|
||||||
user_mention_view::{UserMentionQueryBuilder, UserMentionView},
|
|
||||||
ListingType,
|
|
||||||
SortType,
|
|
||||||
},
|
|
||||||
markdown_to_html,
|
|
||||||
routes::DbPoolParam,
|
|
||||||
settings::Settings,
|
|
||||||
LemmyError,
|
|
||||||
};
|
|
||||||
use actix_web::{error::ErrorBadRequest, *};
|
use actix_web::{error::ErrorBadRequest, *};
|
||||||
use chrono::{DateTime, NaiveDateTime, Utc};
|
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||||
use diesel::{
|
use diesel::{
|
||||||
r2d2::{ConnectionManager, Pool},
|
r2d2::{ConnectionManager, Pool},
|
||||||
PgConnection,
|
PgConnection,
|
||||||
};
|
};
|
||||||
|
use lemmy_db::{
|
||||||
|
comment_view::{ReplyQueryBuilder, ReplyView},
|
||||||
|
community::Community,
|
||||||
|
post_view::{PostQueryBuilder, PostView},
|
||||||
|
site_view::SiteView,
|
||||||
|
user::User_,
|
||||||
|
user_mention_view::{UserMentionQueryBuilder, UserMentionView},
|
||||||
|
ListingType,
|
||||||
|
SortType,
|
||||||
|
};
|
||||||
|
use lemmy_utils::{markdown_to_html, settings::Settings};
|
||||||
use rss::{CategoryBuilder, ChannelBuilder, GuidBuilder, Item, ItemBuilder};
|
use rss::{CategoryBuilder, ChannelBuilder, GuidBuilder, Item, ItemBuilder};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
@ -131,7 +126,7 @@ fn get_feed_user(
|
||||||
) -> Result<ChannelBuilder, LemmyError> {
|
) -> Result<ChannelBuilder, LemmyError> {
|
||||||
let site_view = SiteView::read(&conn)?;
|
let site_view = SiteView::read(&conn)?;
|
||||||
let user = User_::find_by_username(&conn, &user_name)?;
|
let user = User_::find_by_username(&conn, &user_name)?;
|
||||||
let user_url = user.get_profile_url();
|
let user_url = user.get_profile_url(&Settings::get().hostname);
|
||||||
|
|
||||||
let posts = PostQueryBuilder::create(&conn)
|
let posts = PostQueryBuilder::create(&conn)
|
||||||
.listing_type(ListingType::All)
|
.listing_type(ListingType::All)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::settings::Settings;
|
|
||||||
use actix_files::NamedFile;
|
use actix_files::NamedFile;
|
||||||
use actix_web::*;
|
use actix_web::*;
|
||||||
|
use lemmy_utils::settings::Settings;
|
||||||
|
|
||||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||||
cfg
|
cfg
|
||||||
|
|
|
@ -1,13 +1,7 @@
|
||||||
use crate::{
|
use crate::{blocking, routes::DbPoolParam, version, LemmyError};
|
||||||
apub::get_apub_protocol_string,
|
|
||||||
blocking,
|
|
||||||
db::site_view::SiteView,
|
|
||||||
routes::DbPoolParam,
|
|
||||||
version,
|
|
||||||
LemmyError,
|
|
||||||
Settings,
|
|
||||||
};
|
|
||||||
use actix_web::{body::Body, error::ErrorBadRequest, *};
|
use actix_web::{body::Body, error::ErrorBadRequest, *};
|
||||||
|
use lemmy_db::site_view::SiteView;
|
||||||
|
use lemmy_utils::{get_apub_protocol_string, settings::Settings};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,7 @@
|
||||||
use crate::{
|
use crate::{blocking, routes::DbPoolParam, LemmyError};
|
||||||
blocking,
|
|
||||||
db::{community::Community, user::User_},
|
|
||||||
routes::DbPoolParam,
|
|
||||||
LemmyError,
|
|
||||||
Settings,
|
|
||||||
};
|
|
||||||
use actix_web::{error::ErrorBadRequest, web::Query, *};
|
use actix_web::{error::ErrorBadRequest, web::Query, *};
|
||||||
use regex::Regex;
|
use lemmy_db::{community::Community, user::User_};
|
||||||
|
use lemmy_utils::{settings::Settings, WEBFINGER_COMMUNITY_REGEX, WEBFINGER_USER_REGEX};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
@ -40,19 +35,6 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref WEBFINGER_COMMUNITY_REGEX: Regex = Regex::new(&format!(
|
|
||||||
"^group:([a-z0-9_]{{3, 20}})@{}$",
|
|
||||||
Settings::get().hostname
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
static ref WEBFINGER_USER_REGEX: Regex = Regex::new(&format!(
|
|
||||||
"^acct:([a-z0-9_]{{3, 20}})@{}$",
|
|
||||||
Settings::get().hostname
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Responds to webfinger requests of the following format. There isn't any real documentation for
|
/// Responds to webfinger requests of the following format. There isn't any real documentation for
|
||||||
/// this, but it described in this blog post:
|
/// this, but it described in this blog post:
|
||||||
/// https://mastodon.social/.well-known/webfinger?resource=acct:gargron@mastodon.social
|
/// https://mastodon.social/.well-known/webfinger?resource=acct:gargron@mastodon.social
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
pub const VERSION: &str = "v0.7.13";
|
pub const VERSION: &str = "v0.7.19";
|
||||||
|
|
5
server/test.sh
vendored
Executable file
5
server/test.sh
vendored
Executable file
|
@ -0,0 +1,5 @@
|
||||||
|
#!/bin/sh
|
||||||
|
export DATABASE_URL=postgres://lemmy:password@localhost:5432/lemmy
|
||||||
|
diesel migration run
|
||||||
|
export LEMMY_DATABASE_URL=postgres://lemmy:password@localhost:5432/lemmy
|
||||||
|
RUST_TEST_THREADS=1 cargo test --workspace
|
1
ui/assets/css/choices.min.css
vendored
Normal file
1
ui/assets/css/choices.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
7
ui/assets/css/main.css
vendored
7
ui/assets/css/main.css
vendored
|
@ -264,3 +264,10 @@ pre {
|
||||||
width: 0px !important;
|
width: 0px !important;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
br.big {
|
||||||
|
display: block;
|
||||||
|
content: "";
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
7
ui/assets/css/selectr.min.css
vendored
7
ui/assets/css/selectr.min.css
vendored
File diff suppressed because one or more lines are too long
8
ui/fuse.js
vendored
8
ui/fuse.js
vendored
|
@ -6,12 +6,10 @@ const {
|
||||||
WebIndexPlugin,
|
WebIndexPlugin,
|
||||||
QuantumPlugin,
|
QuantumPlugin,
|
||||||
} = require('fuse-box');
|
} = require('fuse-box');
|
||||||
// const transformInferno = require('../../dist').default
|
|
||||||
const transformInferno = require('ts-transform-inferno').default;
|
const transformInferno = require('ts-transform-inferno').default;
|
||||||
const transformClasscat = require('ts-transform-classcat').default;
|
const transformClasscat = require('ts-transform-classcat').default;
|
||||||
let fuse, app;
|
let fuse, app;
|
||||||
let isProduction = false;
|
let isProduction = false;
|
||||||
// var setVersion = require('./set_version.js').setVersion;
|
|
||||||
|
|
||||||
Sparky.task('config', _ => {
|
Sparky.task('config', _ => {
|
||||||
fuse = new FuseBox({
|
fuse = new FuseBox({
|
||||||
|
@ -45,18 +43,18 @@ Sparky.task('config', _ => {
|
||||||
});
|
});
|
||||||
app = fuse.bundle('app').instructions('>index.tsx');
|
app = fuse.bundle('app').instructions('>index.tsx');
|
||||||
});
|
});
|
||||||
// Sparky.task('version', _ => setVersion());
|
|
||||||
Sparky.task('clean', _ => Sparky.src('dist/').clean('dist/'));
|
Sparky.task('clean', _ => Sparky.src('dist/').clean('dist/'));
|
||||||
Sparky.task('env', _ => (isProduction = true));
|
Sparky.task('env', _ => (isProduction = true));
|
||||||
Sparky.task('copy-assets', () =>
|
Sparky.task('copy-assets', () =>
|
||||||
Sparky.src('assets/**/**.*').dest(isProduction ? 'dist/' : 'dist/static')
|
Sparky.src('assets/**/**.*').dest(isProduction ? 'dist/' : 'dist/static')
|
||||||
);
|
);
|
||||||
Sparky.task('dev', ['clean', 'config', 'copy-assets'], _ => {
|
Sparky.task('dev', ['clean', 'config', 'copy-assets'], _ => {
|
||||||
fuse.dev();
|
fuse.dev({
|
||||||
|
fallback: 'index.html',
|
||||||
|
});
|
||||||
app.hmr().watch();
|
app.hmr().watch();
|
||||||
return fuse.run();
|
return fuse.run();
|
||||||
});
|
});
|
||||||
Sparky.task('prod', ['clean', 'env', 'config', 'copy-assets'], _ => {
|
Sparky.task('prod', ['clean', 'env', 'config', 'copy-assets'], _ => {
|
||||||
// fuse.dev({ reload: true }); // remove after demo
|
|
||||||
return fuse.run();
|
return fuse.run();
|
||||||
});
|
});
|
||||||
|
|
6
ui/package.json
vendored
6
ui/package.json
vendored
|
@ -15,7 +15,6 @@
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@joeattardi/emoji-button": "^2.12.1",
|
|
||||||
"@types/autosize": "^3.0.6",
|
"@types/autosize": "^3.0.6",
|
||||||
"@types/js-cookie": "^2.2.6",
|
"@types/js-cookie": "^2.2.6",
|
||||||
"@types/jwt-decode": "^2.2.1",
|
"@types/jwt-decode": "^2.2.1",
|
||||||
|
@ -24,6 +23,7 @@
|
||||||
"@types/node": "^13.11.1",
|
"@types/node": "^13.11.1",
|
||||||
"autosize": "^4.0.2",
|
"autosize": "^4.0.2",
|
||||||
"bootswatch": "^4.3.1",
|
"bootswatch": "^4.3.1",
|
||||||
|
"choices.js": "^9.0.1",
|
||||||
"classcat": "^4.0.2",
|
"classcat": "^4.0.2",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
"emoji-short-name": "^1.0.0",
|
"emoji-short-name": "^1.0.0",
|
||||||
|
@ -37,7 +37,6 @@
|
||||||
"markdown-it": "^10.0.0",
|
"markdown-it": "^10.0.0",
|
||||||
"markdown-it-container": "^2.0.0",
|
"markdown-it-container": "^2.0.0",
|
||||||
"markdown-it-emoji": "^1.4.0",
|
"markdown-it-emoji": "^1.4.0",
|
||||||
"mobius1-selectr": "^2.4.13",
|
|
||||||
"moment": "^2.24.0",
|
"moment": "^2.24.0",
|
||||||
"node-fetch": "^2.6.0",
|
"node-fetch": "^2.6.0",
|
||||||
"prettier": "^2.0.4",
|
"prettier": "^2.0.4",
|
||||||
|
@ -47,7 +46,6 @@
|
||||||
"tippy.js": "^6.1.1",
|
"tippy.js": "^6.1.1",
|
||||||
"toastify-js": "^1.7.0",
|
"toastify-js": "^1.7.0",
|
||||||
"tributejs": "^5.1.3",
|
"tributejs": "^5.1.3",
|
||||||
"twemoji": "^12.1.2",
|
|
||||||
"ws": "^7.2.3"
|
"ws": "^7.2.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -72,7 +70,7 @@
|
||||||
"engineStrict": true,
|
"engineStrict": true,
|
||||||
"husky": {
|
"husky": {
|
||||||
"hooks": {
|
"hooks": {
|
||||||
"pre-commit": "cargo clippy --manifest-path ../server/Cargo.toml --all-targets --all-features -- -D warnings && lint-staged"
|
"pre-commit": "cargo clippy --manifest-path ../server/Cargo.toml --all-targets --workspace -- -D warnings && lint-staged"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
|
|
25
ui/src/components/cake-day.tsx
vendored
Normal file
25
ui/src/components/cake-day.tsx
vendored
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { Component } from 'inferno';
|
||||||
|
import { i18n } from '../i18next';
|
||||||
|
|
||||||
|
interface CakeDayProps {
|
||||||
|
creatorName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CakeDay extends Component<CakeDayProps, any> {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`mx-2 d-inline-block unselectable pointer`}
|
||||||
|
data-tippy-content={this.cakeDayTippy()}
|
||||||
|
>
|
||||||
|
<svg class="icon icon-inline">
|
||||||
|
<use xlinkHref="#icon-cake"></use>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
cakeDayTippy(): string {
|
||||||
|
return i18n.t('cake_day_info', { creator_name: this.props.creatorName });
|
||||||
|
}
|
||||||
|
}
|
69
ui/src/components/comment-form.tsx
vendored
69
ui/src/components/comment-form.tsx
vendored
|
@ -1,4 +1,5 @@
|
||||||
import { Component, linkEvent } from 'inferno';
|
import { Component, linkEvent } from 'inferno';
|
||||||
|
import { Link } from 'inferno-router';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { retryWhen, delay, take } from 'rxjs/operators';
|
import { retryWhen, delay, take } from 'rxjs/operators';
|
||||||
import { Prompt } from 'inferno-router';
|
import { Prompt } from 'inferno-router';
|
||||||
|
@ -17,7 +18,6 @@ import {
|
||||||
toast,
|
toast,
|
||||||
setupTribute,
|
setupTribute,
|
||||||
wsJsonToRes,
|
wsJsonToRes,
|
||||||
emojiPicker,
|
|
||||||
pictrsDeleteToast,
|
pictrsDeleteToast,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { WebSocketService, UserService } from '../services';
|
import { WebSocketService, UserService } from '../services';
|
||||||
|
@ -25,6 +25,7 @@ import autosize from 'autosize';
|
||||||
import Tribute from 'tributejs/src/Tribute.js';
|
import Tribute from 'tributejs/src/Tribute.js';
|
||||||
import emojiShortName from 'emoji-short-name';
|
import emojiShortName from 'emoji-short-name';
|
||||||
import { i18n } from '../i18next';
|
import { i18n } from '../i18next';
|
||||||
|
import { T } from 'inferno-i18next';
|
||||||
|
|
||||||
interface CommentFormProps {
|
interface CommentFormProps {
|
||||||
postId?: number;
|
postId?: number;
|
||||||
|
@ -32,6 +33,7 @@ interface CommentFormProps {
|
||||||
onReplyCancel?(): any;
|
onReplyCancel?(): any;
|
||||||
edit?: boolean;
|
edit?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
focus?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CommentFormState {
|
interface CommentFormState {
|
||||||
|
@ -72,7 +74,6 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
this.tribute = setupTribute();
|
this.tribute = setupTribute();
|
||||||
this.setupEmojiPicker();
|
|
||||||
|
|
||||||
this.state = this.emptyState;
|
this.state = this.emptyState;
|
||||||
|
|
||||||
|
@ -98,7 +99,8 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
var textarea: any = document.getElementById(this.id);
|
let textarea: any = document.getElementById(this.id);
|
||||||
|
if (textarea) {
|
||||||
autosize(textarea);
|
autosize(textarea);
|
||||||
this.tribute.attach(textarea);
|
this.tribute.attach(textarea);
|
||||||
textarea.addEventListener('tribute-replaced', () => {
|
textarea.addEventListener('tribute-replaced', () => {
|
||||||
|
@ -106,6 +108,25 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
autosize.update(textarea);
|
autosize.update(textarea);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Quoting of selected text
|
||||||
|
let selectedText = window.getSelection().toString();
|
||||||
|
if (selectedText) {
|
||||||
|
let quotedText =
|
||||||
|
selectedText
|
||||||
|
.split('\n')
|
||||||
|
.map(t => `> ${t}`)
|
||||||
|
.join('\n') + '\n\n';
|
||||||
|
this.state.commentForm.content = quotedText;
|
||||||
|
this.setState(this.state);
|
||||||
|
// Not sure why this needs a delay
|
||||||
|
setTimeout(() => autosize.update(textarea), 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props.focus) {
|
||||||
|
textarea.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
|
@ -128,6 +149,7 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
||||||
when={this.state.commentForm.content}
|
when={this.state.commentForm.content}
|
||||||
message={i18n.t('block_leaving')}
|
message={i18n.t('block_leaving')}
|
||||||
/>
|
/>
|
||||||
|
{UserService.Instance.user ? (
|
||||||
<form
|
<form
|
||||||
id={this.formId}
|
id={this.formId}
|
||||||
onSubmit={linkEvent(this, this.handleCommentSubmit)}
|
onSubmit={linkEvent(this, this.handleCommentSubmit)}
|
||||||
|
@ -136,7 +158,9 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
||||||
<div className={`col-sm-12`}>
|
<div className={`col-sm-12`}>
|
||||||
<textarea
|
<textarea
|
||||||
id={this.id}
|
id={this.id}
|
||||||
className={`form-control ${this.state.previewMode && 'd-none'}`}
|
className={`form-control ${
|
||||||
|
this.state.previewMode && 'd-none'
|
||||||
|
}`}
|
||||||
value={this.state.commentForm.content}
|
value={this.state.commentForm.content}
|
||||||
onInput={linkEvent(this, this.handleCommentContentChange)}
|
onInput={linkEvent(this, this.handleCommentContentChange)}
|
||||||
onPaste={linkEvent(this, this.handleImageUploadPaste)}
|
onPaste={linkEvent(this, this.handleImageUploadPaste)}
|
||||||
|
@ -225,36 +249,23 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
||||||
<use xlinkHref="#icon-spinner"></use>
|
<use xlinkHref="#icon-spinner"></use>
|
||||||
</svg>
|
</svg>
|
||||||
)}
|
)}
|
||||||
<span
|
|
||||||
onClick={linkEvent(this, this.handleEmojiPickerClick)}
|
|
||||||
class="pointer unselectable d-inline-block mr-3 float-right text-muted font-weight-bold"
|
|
||||||
data-tippy-content={i18n.t('emoji_picker')}
|
|
||||||
>
|
|
||||||
<svg class="icon icon-inline">
|
|
||||||
<use xlinkHref="#icon-smile"></use>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
) : (
|
||||||
|
<div class="alert alert-warning" role="alert">
|
||||||
|
<svg class="icon icon-inline mr-2">
|
||||||
|
<use xlinkHref="#icon-alert-triangle"></use>
|
||||||
|
</svg>
|
||||||
|
<T i18nKey="must_login" class="d-inline">
|
||||||
|
#<Link to="/login">#</Link>
|
||||||
|
</T>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
setupEmojiPicker() {
|
|
||||||
emojiPicker.on('emoji', twemojiHtmlStr => {
|
|
||||||
if (this.state.commentForm.content == null) {
|
|
||||||
this.state.commentForm.content = '';
|
|
||||||
}
|
|
||||||
var el = document.createElement('div');
|
|
||||||
el.innerHTML = twemojiHtmlStr;
|
|
||||||
let nativeUnicode = (el.childNodes[0] as HTMLElement).getAttribute('alt');
|
|
||||||
let shortName = `:${emojiShortName[nativeUnicode]}:`;
|
|
||||||
this.state.commentForm.content += shortName;
|
|
||||||
this.setState(this.state);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleFinished(op: UserOperation, data: CommentResponse) {
|
handleFinished(op: UserOperation, data: CommentResponse) {
|
||||||
let isReply =
|
let isReply =
|
||||||
this.props.node !== undefined && data.comment.parent_id !== null;
|
this.props.node !== undefined && data.comment.parent_id !== null;
|
||||||
|
@ -302,10 +313,6 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
||||||
i.setState(i.state);
|
i.setState(i.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleEmojiPickerClick(_i: CommentForm, event: any) {
|
|
||||||
emojiPicker.togglePicker(event.target);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleCommentContentChange(i: CommentForm, event: any) {
|
handleCommentContentChange(i: CommentForm, event: any) {
|
||||||
i.state.commentForm.content = event.target.value;
|
i.state.commentForm.content = event.target.value;
|
||||||
i.setState(i.state);
|
i.setState(i.state);
|
||||||
|
|
32
ui/src/components/comment-node.tsx
vendored
32
ui/src/components/comment-node.tsx
vendored
|
@ -32,6 +32,7 @@ import { MomentTime } from './moment-time';
|
||||||
import { CommentForm } from './comment-form';
|
import { CommentForm } from './comment-form';
|
||||||
import { CommentNodes } from './comment-nodes';
|
import { CommentNodes } from './comment-nodes';
|
||||||
import { UserListing } from './user-listing';
|
import { UserListing } from './user-listing';
|
||||||
|
import { CommunityLink } from './community-link';
|
||||||
import { i18n } from '../i18next';
|
import { i18n } from '../i18next';
|
||||||
|
|
||||||
interface CommentNodeState {
|
interface CommentNodeState {
|
||||||
|
@ -158,9 +159,11 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
id: node.comment.creator_id,
|
id: node.comment.creator_id,
|
||||||
local: node.comment.creator_local,
|
local: node.comment.creator_local,
|
||||||
actor_id: node.comment.creator_actor_id,
|
actor_id: node.comment.creator_actor_id,
|
||||||
|
published: node.comment.creator_published,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{this.isMod && (
|
{this.isMod && (
|
||||||
<div className="badge badge-light d-none d-sm-inline mr-2">
|
<div className="badge badge-light d-none d-sm-inline mr-2">
|
||||||
{i18n.t('mod')}
|
{i18n.t('mod')}
|
||||||
|
@ -184,13 +187,22 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
{this.props.showCommunity && (
|
{this.props.showCommunity && (
|
||||||
<>
|
<>
|
||||||
<span class="mx-1">{i18n.t('to')}</span>
|
<span class="mx-1">{i18n.t('to')}</span>
|
||||||
<Link class="mr-2" to={`/c/${node.comment.community_name}`}>
|
<CommunityLink
|
||||||
{node.comment.community_name}
|
community={{
|
||||||
|
name: node.comment.community_name,
|
||||||
|
id: node.comment.community_id,
|
||||||
|
local: node.comment.community_local,
|
||||||
|
actor_id: node.comment.community_actor_id,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span class="mx-2">•</span>
|
||||||
|
<Link class="mr-2" to={`/post/${node.comment.post_id}`}>
|
||||||
|
{node.comment.post_name}
|
||||||
</Link>
|
</Link>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<div
|
<button
|
||||||
className="mr-lg-4 flex-grow-1 flex-lg-grow-0 unselectable pointer mx-2"
|
class="btn btn-sm text-muted"
|
||||||
onClick={linkEvent(this, this.handleCommentCollapse)}
|
onClick={linkEvent(this, this.handleCommentCollapse)}
|
||||||
>
|
>
|
||||||
{this.state.collapsed ? (
|
{this.state.collapsed ? (
|
||||||
|
@ -202,9 +214,11 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
<use xlinkHref="#icon-minus-square"></use>
|
<use xlinkHref="#icon-minus-square"></use>
|
||||||
</svg>
|
</svg>
|
||||||
)}
|
)}
|
||||||
</div>
|
</button>
|
||||||
<span
|
{/* This is an expanding spacer for mobile */}
|
||||||
className={`unselectable pointer ${this.scoreColor}`}
|
<div className="mr-lg-4 flex-grow-1 flex-lg-grow-0 unselectable pointer mx-2"></div>
|
||||||
|
<button
|
||||||
|
className={`btn btn-sm p-0 unselectable pointer ${this.scoreColor}`}
|
||||||
onClick={linkEvent(node, this.handleCommentUpvote)}
|
onClick={linkEvent(node, this.handleCommentUpvote)}
|
||||||
data-tippy-content={this.pointsTippy}
|
data-tippy-content={this.pointsTippy}
|
||||||
>
|
>
|
||||||
|
@ -212,7 +226,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
<use xlinkHref="#icon-zap"></use>
|
<use xlinkHref="#icon-zap"></use>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="mr-1">{this.state.score}</span>
|
<span class="mr-1">{this.state.score}</span>
|
||||||
</span>
|
</button>
|
||||||
<span className="mr-1">•</span>
|
<span className="mr-1">•</span>
|
||||||
<span>
|
<span>
|
||||||
<MomentTime data={node.comment} />
|
<MomentTime data={node.comment} />
|
||||||
|
@ -225,6 +239,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
edit
|
edit
|
||||||
onReplyCancel={this.handleReplyCancel}
|
onReplyCancel={this.handleReplyCancel}
|
||||||
disabled={this.props.locked}
|
disabled={this.props.locked}
|
||||||
|
focus
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!this.state.showEdit && !this.state.collapsed && (
|
{!this.state.showEdit && !this.state.collapsed && (
|
||||||
|
@ -693,6 +708,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
node={node}
|
node={node}
|
||||||
onReplyCancel={this.handleReplyCancel}
|
onReplyCancel={this.handleReplyCancel}
|
||||||
disabled={this.props.locked}
|
disabled={this.props.locked}
|
||||||
|
focus
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{node.children && !this.state.collapsed && (
|
{node.children && !this.state.collapsed && (
|
||||||
|
|
2
ui/src/components/communities.tsx
vendored
2
ui/src/components/communities.tsx
vendored
|
@ -160,7 +160,7 @@ export class Communities extends Component<any, CommunitiesState> {
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{this.state.communities.length == communityLimit && (
|
{this.state.communities.length > 0 && (
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm btn-secondary"
|
class="btn btn-sm btn-secondary"
|
||||||
onClick={linkEvent(this, this.nextPage)}
|
onClick={linkEvent(this, this.nextPage)}
|
||||||
|
|
2
ui/src/components/community.tsx
vendored
2
ui/src/components/community.tsx
vendored
|
@ -260,7 +260,7 @@ export class Community extends Component<any, State> {
|
||||||
{i18n.t('prev')}
|
{i18n.t('prev')}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{this.state.posts.length == fetchLimit && (
|
{this.state.posts.length > 0 && (
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm btn-secondary"
|
class="btn btn-sm btn-secondary"
|
||||||
onClick={linkEvent(this, this.nextPage)}
|
onClick={linkEvent(this, this.nextPage)}
|
||||||
|
|
7
ui/src/components/create-community.tsx
vendored
7
ui/src/components/create-community.tsx
vendored
|
@ -9,7 +9,7 @@ import {
|
||||||
GetSiteResponse,
|
GetSiteResponse,
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
import { toast, wsJsonToRes } from '../utils';
|
import { toast, wsJsonToRes } from '../utils';
|
||||||
import { WebSocketService } from '../services';
|
import { WebSocketService, UserService } from '../services';
|
||||||
import { i18n } from '../i18next';
|
import { i18n } from '../i18next';
|
||||||
|
|
||||||
interface CreateCommunityState {
|
interface CreateCommunityState {
|
||||||
|
@ -26,6 +26,11 @@ export class CreateCommunity extends Component<any, CreateCommunityState> {
|
||||||
this.handleCommunityCreate = this.handleCommunityCreate.bind(this);
|
this.handleCommunityCreate = this.handleCommunityCreate.bind(this);
|
||||||
this.state = this.emptyState;
|
this.state = this.emptyState;
|
||||||
|
|
||||||
|
if (!UserService.Instance.user) {
|
||||||
|
toast(i18n.t('not_logged_in'), 'danger');
|
||||||
|
this.context.router.history.push(`/login`);
|
||||||
|
}
|
||||||
|
|
||||||
this.subscription = WebSocketService.Instance.subject
|
this.subscription = WebSocketService.Instance.subject
|
||||||
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
|
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
|
||||||
.subscribe(
|
.subscribe(
|
||||||
|
|
7
ui/src/components/create-post.tsx
vendored
7
ui/src/components/create-post.tsx
vendored
|
@ -3,7 +3,7 @@ import { Subscription } from 'rxjs';
|
||||||
import { retryWhen, delay, take } from 'rxjs/operators';
|
import { retryWhen, delay, take } from 'rxjs/operators';
|
||||||
import { PostForm } from './post-form';
|
import { PostForm } from './post-form';
|
||||||
import { toast, wsJsonToRes } from '../utils';
|
import { toast, wsJsonToRes } from '../utils';
|
||||||
import { WebSocketService } from '../services';
|
import { WebSocketService, UserService } from '../services';
|
||||||
import {
|
import {
|
||||||
UserOperation,
|
UserOperation,
|
||||||
PostFormParams,
|
PostFormParams,
|
||||||
|
@ -41,6 +41,11 @@ export class CreatePost extends Component<any, CreatePostState> {
|
||||||
this.handlePostCreate = this.handlePostCreate.bind(this);
|
this.handlePostCreate = this.handlePostCreate.bind(this);
|
||||||
this.state = this.emptyState;
|
this.state = this.emptyState;
|
||||||
|
|
||||||
|
if (!UserService.Instance.user) {
|
||||||
|
toast(i18n.t('not_logged_in'), 'danger');
|
||||||
|
this.context.router.history.push(`/login`);
|
||||||
|
}
|
||||||
|
|
||||||
this.subscription = WebSocketService.Instance.subject
|
this.subscription = WebSocketService.Instance.subject
|
||||||
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
|
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
|
||||||
.subscribe(
|
.subscribe(
|
||||||
|
|
7
ui/src/components/create-private-message.tsx
vendored
7
ui/src/components/create-private-message.tsx
vendored
|
@ -2,7 +2,7 @@ import { Component } from 'inferno';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { retryWhen, delay, take } from 'rxjs/operators';
|
import { retryWhen, delay, take } from 'rxjs/operators';
|
||||||
import { PrivateMessageForm } from './private-message-form';
|
import { PrivateMessageForm } from './private-message-form';
|
||||||
import { WebSocketService } from '../services';
|
import { WebSocketService, UserService } from '../services';
|
||||||
import {
|
import {
|
||||||
UserOperation,
|
UserOperation,
|
||||||
WebSocketJsonResponse,
|
WebSocketJsonResponse,
|
||||||
|
@ -20,6 +20,11 @@ export class CreatePrivateMessage extends Component<any, any> {
|
||||||
this
|
this
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!UserService.Instance.user) {
|
||||||
|
toast(i18n.t('not_logged_in'), 'danger');
|
||||||
|
this.context.router.history.push(`/login`);
|
||||||
|
}
|
||||||
|
|
||||||
this.subscription = WebSocketService.Instance.subject
|
this.subscription = WebSocketService.Instance.subject
|
||||||
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
|
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
|
||||||
.subscribe(
|
.subscribe(
|
||||||
|
|
23
ui/src/components/inbox.tsx
vendored
23
ui/src/components/inbox.tsx
vendored
|
@ -267,6 +267,7 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
nodes={[{ comment: i }]}
|
nodes={[{ comment: i }]}
|
||||||
noIndent
|
noIndent
|
||||||
markable
|
markable
|
||||||
|
showCommunity
|
||||||
showContext
|
showContext
|
||||||
enableDownvotes={this.state.enableDownvotes}
|
enableDownvotes={this.state.enableDownvotes}
|
||||||
/>
|
/>
|
||||||
|
@ -285,6 +286,7 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
nodes={commentsToFlatNodes(this.state.replies)}
|
nodes={commentsToFlatNodes(this.state.replies)}
|
||||||
noIndent
|
noIndent
|
||||||
markable
|
markable
|
||||||
|
showCommunity
|
||||||
showContext
|
showContext
|
||||||
enableDownvotes={this.state.enableDownvotes}
|
enableDownvotes={this.state.enableDownvotes}
|
||||||
/>
|
/>
|
||||||
|
@ -300,6 +302,7 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
nodes={[{ comment: mention }]}
|
nodes={[{ comment: mention }]}
|
||||||
noIndent
|
noIndent
|
||||||
markable
|
markable
|
||||||
|
showCommunity
|
||||||
showContext
|
showContext
|
||||||
enableDownvotes={this.state.enableDownvotes}
|
enableDownvotes={this.state.enableDownvotes}
|
||||||
/>
|
/>
|
||||||
|
@ -329,12 +332,14 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
{i18n.t('prev')}
|
{i18n.t('prev')}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
{this.unreadCount() > 0 && (
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm btn-secondary"
|
class="btn btn-sm btn-secondary"
|
||||||
onClick={linkEvent(this, this.nextPage)}
|
onClick={linkEvent(this, this.nextPage)}
|
||||||
>
|
>
|
||||||
{i18n.t('next')}
|
{i18n.t('next')}
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -534,15 +539,19 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
sendUnreadCount() {
|
sendUnreadCount() {
|
||||||
let count =
|
UserService.Instance.user.unreadCount = this.unreadCount();
|
||||||
this.state.replies.filter(r => !r.read).length +
|
|
||||||
this.state.mentions.filter(r => !r.read).length +
|
|
||||||
this.state.messages.filter(
|
|
||||||
r => !r.read && r.creator_id !== UserService.Instance.user.id
|
|
||||||
).length;
|
|
||||||
UserService.Instance.user.unreadCount = count;
|
|
||||||
UserService.Instance.sub.next({
|
UserService.Instance.sub.next({
|
||||||
user: UserService.Instance.user,
|
user: UserService.Instance.user,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unreadCount(): number {
|
||||||
|
return (
|
||||||
|
this.state.replies.filter(r => !r.read).length +
|
||||||
|
this.state.mentions.filter(r => !r.read).length +
|
||||||
|
this.state.messages.filter(
|
||||||
|
r => !r.read && r.creator_id !== UserService.Instance.user.id
|
||||||
|
).length
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
12
ui/src/components/main.tsx
vendored
12
ui/src/components/main.tsx
vendored
|
@ -373,17 +373,21 @@ export class Main extends Component<any, MainState> {
|
||||||
#
|
#
|
||||||
</a>
|
</a>
|
||||||
<a href="https://en.wikipedia.org/wiki/Fediverse">#</a>
|
<a href="https://en.wikipedia.org/wiki/Fediverse">#</a>
|
||||||
<br></br>
|
<br class="big"></br>
|
||||||
<code>#</code>
|
<code>#</code>
|
||||||
<br></br>
|
<br></br>
|
||||||
<b>#</b>
|
<b>#</b>
|
||||||
<br></br>
|
<br class="big"></br>
|
||||||
<a href={repoUrl}>#</a>
|
<a href={repoUrl}>#</a>
|
||||||
<br></br>
|
<br class="big"></br>
|
||||||
<a href="https://www.rust-lang.org">#</a>
|
<a href="https://www.rust-lang.org">#</a>
|
||||||
<a href="https://actix.rs/">#</a>
|
<a href="https://actix.rs/">#</a>
|
||||||
<a href="https://infernojs.org">#</a>
|
<a href="https://infernojs.org">#</a>
|
||||||
<a href="https://www.typescriptlang.org/">#</a>
|
<a href="https://www.typescriptlang.org/">#</a>
|
||||||
|
<br class="big"></br>
|
||||||
|
<a href="https://github.com/LemmyNet/lemmy/graphs/contributors?type=a">
|
||||||
|
#
|
||||||
|
</a>
|
||||||
</T>
|
</T>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -493,7 +497,7 @@ export class Main extends Component<any, MainState> {
|
||||||
{i18n.t('prev')}
|
{i18n.t('prev')}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{this.state.posts.length == fetchLimit && (
|
{this.state.posts.length > 0 && (
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm btn-secondary"
|
class="btn btn-sm btn-secondary"
|
||||||
onClick={linkEvent(this, this.nextPage)}
|
onClick={linkEvent(this, this.nextPage)}
|
||||||
|
|
85
ui/src/components/post-form.tsx
vendored
85
ui/src/components/post-form.tsx
vendored
|
@ -33,14 +33,14 @@ import {
|
||||||
randomStr,
|
randomStr,
|
||||||
setupTribute,
|
setupTribute,
|
||||||
setupTippy,
|
setupTippy,
|
||||||
emojiPicker,
|
|
||||||
hostname,
|
hostname,
|
||||||
pictrsDeleteToast,
|
pictrsDeleteToast,
|
||||||
|
validTitle,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import autosize from 'autosize';
|
import autosize from 'autosize';
|
||||||
import Tribute from 'tributejs/src/Tribute.js';
|
import Tribute from 'tributejs/src/Tribute.js';
|
||||||
import emojiShortName from 'emoji-short-name';
|
import emojiShortName from 'emoji-short-name';
|
||||||
import Selectr from 'mobius1-selectr';
|
import Choices from 'choices.js';
|
||||||
import { i18n } from '../i18next';
|
import { i18n } from '../i18next';
|
||||||
|
|
||||||
const MAX_POST_TITLE_LENGTH = 200;
|
const MAX_POST_TITLE_LENGTH = 200;
|
||||||
|
@ -70,6 +70,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
private id = `post-form-${randomStr()}`;
|
private id = `post-form-${randomStr()}`;
|
||||||
private tribute: Tribute;
|
private tribute: Tribute;
|
||||||
private subscription: Subscription;
|
private subscription: Subscription;
|
||||||
|
private choices: Choices;
|
||||||
private emptyState: PostFormState = {
|
private emptyState: PostFormState = {
|
||||||
postForm: {
|
postForm: {
|
||||||
name: null,
|
name: null,
|
||||||
|
@ -95,7 +96,6 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
this.fetchPageTitle = debounce(this.fetchPageTitle).bind(this);
|
this.fetchPageTitle = debounce(this.fetchPageTitle).bind(this);
|
||||||
|
|
||||||
this.tribute = setupTribute();
|
this.tribute = setupTribute();
|
||||||
this.setupEmojiPicker();
|
|
||||||
|
|
||||||
this.state = this.emptyState;
|
this.state = this.emptyState;
|
||||||
|
|
||||||
|
@ -166,6 +166,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.subscription.unsubscribe();
|
this.subscription.unsubscribe();
|
||||||
|
this.choices && this.choices.destroy();
|
||||||
window.onbeforeunload = null;
|
window.onbeforeunload = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,12 +272,19 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
value={this.state.postForm.name}
|
value={this.state.postForm.name}
|
||||||
id="post-title"
|
id="post-title"
|
||||||
onInput={linkEvent(this, this.handlePostNameChange)}
|
onInput={linkEvent(this, this.handlePostNameChange)}
|
||||||
class="form-control"
|
class={`form-control ${
|
||||||
|
!validTitle(this.state.postForm.name) && 'is-invalid'
|
||||||
|
}`}
|
||||||
required
|
required
|
||||||
rows={2}
|
rows={2}
|
||||||
minLength={3}
|
minLength={3}
|
||||||
maxLength={MAX_POST_TITLE_LENGTH}
|
maxLength={MAX_POST_TITLE_LENGTH}
|
||||||
/>
|
/>
|
||||||
|
{!validTitle(this.state.postForm.name) && (
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
{i18n.t('invalid_post_title')}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{this.state.suggestedPosts.length > 0 && (
|
{this.state.suggestedPosts.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<div class="my-1 text-muted small font-weight-bold">
|
<div class="my-1 text-muted small font-weight-bold">
|
||||||
|
@ -332,15 +340,6 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
<use xlinkHref="#icon-help-circle"></use>
|
<use xlinkHref="#icon-help-circle"></use>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
<span
|
|
||||||
onClick={linkEvent(this, this.handleEmojiPickerClick)}
|
|
||||||
class="pointer unselectable d-inline-block mr-3 float-right text-muted font-weight-bold"
|
|
||||||
data-tippy-content={i18n.t('emoji_picker')}
|
|
||||||
>
|
|
||||||
<svg class="icon icon-inline">
|
|
||||||
<use xlinkHref="#icon-smile"></use>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{!this.props.post && (
|
{!this.props.post && (
|
||||||
|
@ -420,20 +419,6 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
setupEmojiPicker() {
|
|
||||||
emojiPicker.on('emoji', twemojiHtmlStr => {
|
|
||||||
if (this.state.postForm.body == null) {
|
|
||||||
this.state.postForm.body = '';
|
|
||||||
}
|
|
||||||
var el = document.createElement('div');
|
|
||||||
el.innerHTML = twemojiHtmlStr;
|
|
||||||
let nativeUnicode = (el.childNodes[0] as HTMLElement).getAttribute('alt');
|
|
||||||
let shortName = `:${emojiShortName[nativeUnicode]}:`;
|
|
||||||
this.state.postForm.body += shortName;
|
|
||||||
this.setState(this.state);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handlePostSubmit(i: PostForm, event: any) {
|
handlePostSubmit(i: PostForm, event: any) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
|
@ -596,10 +581,6 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleEmojiPickerClick(_i: PostForm, event: any) {
|
|
||||||
emojiPicker.togglePicker(event.target);
|
|
||||||
}
|
|
||||||
|
|
||||||
parseMessage(msg: WebSocketJsonResponse) {
|
parseMessage(msg: WebSocketJsonResponse) {
|
||||||
let res = wsJsonToRes(msg);
|
let res = wsJsonToRes(msg);
|
||||||
if (msg.error) {
|
if (msg.error) {
|
||||||
|
@ -625,11 +606,45 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
// Set up select searching
|
// Set up select searching
|
||||||
let selectId: any = document.getElementById('post-community');
|
let selectId: any = document.getElementById('post-community');
|
||||||
if (selectId) {
|
if (selectId) {
|
||||||
let selector = new Selectr(selectId, { nativeDropdown: false });
|
this.choices = new Choices(selectId, {
|
||||||
selector.on('selectr.select', option => {
|
shouldSort: false,
|
||||||
this.state.postForm.community_id = Number(option.value);
|
classNames: {
|
||||||
this.setState(this.state);
|
containerOuter: 'choices',
|
||||||
|
containerInner: 'choices__inner bg-secondary border-0',
|
||||||
|
input: 'form-control',
|
||||||
|
inputCloned: 'choices__input--cloned',
|
||||||
|
list: 'choices__list',
|
||||||
|
listItems: 'choices__list--multiple',
|
||||||
|
listSingle: 'choices__list--single',
|
||||||
|
listDropdown: 'choices__list--dropdown',
|
||||||
|
item: 'choices__item bg-secondary',
|
||||||
|
itemSelectable: 'choices__item--selectable',
|
||||||
|
itemDisabled: 'choices__item--disabled',
|
||||||
|
itemChoice: 'choices__item--choice',
|
||||||
|
placeholder: 'choices__placeholder',
|
||||||
|
group: 'choices__group',
|
||||||
|
groupHeading: 'choices__heading',
|
||||||
|
button: 'choices__button',
|
||||||
|
activeState: 'is-active',
|
||||||
|
focusState: 'is-focused',
|
||||||
|
openState: 'is-open',
|
||||||
|
disabledState: 'is-disabled',
|
||||||
|
highlightedState: 'text-info',
|
||||||
|
selectedState: 'text-info',
|
||||||
|
flippedState: 'is-flipped',
|
||||||
|
loadingState: 'is-loading',
|
||||||
|
noResults: 'has-no-results',
|
||||||
|
noChoices: 'has-no-choices',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
this.choices.passedElement.element.addEventListener(
|
||||||
|
'choice',
|
||||||
|
(e: any) => {
|
||||||
|
this.state.postForm.community_id = Number(e.detail.choice.value);
|
||||||
|
this.setState(this.state);
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else if (res.op == UserOperation.CreatePost) {
|
} else if (res.op == UserOperation.CreatePost) {
|
||||||
let data = res.data as PostResponse;
|
let data = res.data as PostResponse;
|
||||||
|
|
11
ui/src/components/post-listing.tsx
vendored
11
ui/src/components/post-listing.tsx
vendored
|
@ -33,6 +33,7 @@ import {
|
||||||
setupTippy,
|
setupTippy,
|
||||||
hostname,
|
hostname,
|
||||||
previewLines,
|
previewLines,
|
||||||
|
toast,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { i18n } from '../i18next';
|
import { i18n } from '../i18next';
|
||||||
|
|
||||||
|
@ -434,8 +435,10 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
||||||
id: post.creator_id,
|
id: post.creator_id,
|
||||||
local: post.creator_local,
|
local: post.creator_local,
|
||||||
actor_id: post.creator_actor_id,
|
actor_id: post.creator_actor_id,
|
||||||
|
published: post.creator_published,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{this.isMod && (
|
{this.isMod && (
|
||||||
<span className="mx-1 badge badge-light">
|
<span className="mx-1 badge badge-light">
|
||||||
{i18n.t('mod')}
|
{i18n.t('mod')}
|
||||||
|
@ -1030,6 +1033,10 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePostLike(i: PostListing) {
|
handlePostLike(i: PostListing) {
|
||||||
|
if (!UserService.Instance.user) {
|
||||||
|
this.context.router.history.push(`/login`);
|
||||||
|
}
|
||||||
|
|
||||||
let new_vote = i.state.my_vote == 1 ? 0 : 1;
|
let new_vote = i.state.my_vote == 1 ? 0 : 1;
|
||||||
|
|
||||||
if (i.state.my_vote == 1) {
|
if (i.state.my_vote == 1) {
|
||||||
|
@ -1057,6 +1064,10 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePostDisLike(i: PostListing) {
|
handlePostDisLike(i: PostListing) {
|
||||||
|
if (!UserService.Instance.user) {
|
||||||
|
this.context.router.history.push(`/login`);
|
||||||
|
}
|
||||||
|
|
||||||
let new_vote = i.state.my_vote == -1 ? 0 : -1;
|
let new_vote = i.state.my_vote == -1 ? 0 : -1;
|
||||||
|
|
||||||
if (i.state.my_vote == 1) {
|
if (i.state.my_vote == 1) {
|
||||||
|
|
47
ui/src/components/post.tsx
vendored
47
ui/src/components/post.tsx
vendored
|
@ -11,6 +11,7 @@ import {
|
||||||
CommentForm as CommentFormI,
|
CommentForm as CommentFormI,
|
||||||
CommentResponse,
|
CommentResponse,
|
||||||
CommentSortType,
|
CommentSortType,
|
||||||
|
CommentViewType,
|
||||||
CommunityUser,
|
CommunityUser,
|
||||||
CommunityResponse,
|
CommunityResponse,
|
||||||
CommentNode as CommentNodeI,
|
CommentNode as CommentNodeI,
|
||||||
|
@ -49,6 +50,7 @@ interface PostState {
|
||||||
post: PostI;
|
post: PostI;
|
||||||
comments: Array<Comment>;
|
comments: Array<Comment>;
|
||||||
commentSort: CommentSortType;
|
commentSort: CommentSortType;
|
||||||
|
commentViewType: CommentViewType;
|
||||||
community: Community;
|
community: Community;
|
||||||
moderators: Array<CommunityUser>;
|
moderators: Array<CommunityUser>;
|
||||||
online: number;
|
online: number;
|
||||||
|
@ -65,6 +67,7 @@ export class Post extends Component<any, PostState> {
|
||||||
post: null,
|
post: null,
|
||||||
comments: [],
|
comments: [],
|
||||||
commentSort: CommentSortType.Hot,
|
commentSort: CommentSortType.Hot,
|
||||||
|
commentViewType: CommentViewType.Tree,
|
||||||
community: null,
|
community: null,
|
||||||
moderators: [],
|
moderators: [],
|
||||||
online: null,
|
online: null,
|
||||||
|
@ -208,12 +211,12 @@ export class Post extends Component<any, PostState> {
|
||||||
disabled={this.state.post.locked}
|
disabled={this.state.post.locked}
|
||||||
/>
|
/>
|
||||||
{this.state.comments.length > 0 && this.sortRadios()}
|
{this.state.comments.length > 0 && this.sortRadios()}
|
||||||
{this.commentsTree()}
|
{this.state.commentViewType == CommentViewType.Tree &&
|
||||||
</div>
|
this.commentsTree()}
|
||||||
<div class="col-12 col-sm-12 col-md-4">
|
{this.state.commentViewType == CommentViewType.Chat &&
|
||||||
{this.state.comments.length > 0 && this.newComments()}
|
this.commentsFlat()}
|
||||||
{this.sidebar()}
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-12 col-sm-12 col-md-4">{this.sidebar()}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -222,7 +225,8 @@ export class Post extends Component<any, PostState> {
|
||||||
|
|
||||||
sortRadios() {
|
sortRadios() {
|
||||||
return (
|
return (
|
||||||
<div class="btn-group btn-group-toggle mb-2">
|
<>
|
||||||
|
<div class="btn-group btn-group-toggle mr-3 mb-2">
|
||||||
<label
|
<label
|
||||||
className={`btn btn-sm btn-secondary pointer ${
|
className={`btn btn-sm btn-secondary pointer ${
|
||||||
this.state.commentSort === CommentSortType.Hot && 'active'
|
this.state.commentSort === CommentSortType.Hot && 'active'
|
||||||
|
@ -276,14 +280,28 @@ export class Post extends Component<any, PostState> {
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="btn-group btn-group-toggle mb-2">
|
||||||
|
<label
|
||||||
|
className={`btn btn-sm btn-secondary pointer ${
|
||||||
|
this.state.commentViewType === CommentViewType.Chat && 'active'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{i18n.t('chat')}
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
value={CommentViewType.Chat}
|
||||||
|
checked={this.state.commentViewType === CommentViewType.Chat}
|
||||||
|
onChange={linkEvent(this, this.handleCommentViewTypeChange)}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
newComments() {
|
commentsFlat() {
|
||||||
return (
|
return (
|
||||||
<div class="d-none d-md-block new-comments mb-3 card border-secondary">
|
<div>
|
||||||
<div class="card-body small">
|
|
||||||
<h6>{i18n.t('recent_comments')}</h6>
|
|
||||||
<CommentNodes
|
<CommentNodes
|
||||||
nodes={commentsToFlatNodes(this.state.comments)}
|
nodes={commentsToFlatNodes(this.state.comments)}
|
||||||
noIndent
|
noIndent
|
||||||
|
@ -293,9 +311,9 @@ export class Post extends Component<any, PostState> {
|
||||||
postCreatorId={this.state.post.creator_id}
|
postCreatorId={this.state.post.creator_id}
|
||||||
showContext
|
showContext
|
||||||
enableDownvotes={this.state.siteRes.site.enable_downvotes}
|
enableDownvotes={this.state.siteRes.site.enable_downvotes}
|
||||||
|
sort={this.state.commentSort}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -315,6 +333,13 @@ export class Post extends Component<any, PostState> {
|
||||||
|
|
||||||
handleCommentSortChange(i: Post, event: any) {
|
handleCommentSortChange(i: Post, event: any) {
|
||||||
i.state.commentSort = Number(event.target.value);
|
i.state.commentSort = Number(event.target.value);
|
||||||
|
i.state.commentViewType = CommentViewType.Tree;
|
||||||
|
i.setState(i.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCommentViewTypeChange(i: Post, event: any) {
|
||||||
|
i.state.commentViewType = Number(event.target.value);
|
||||||
|
i.state.commentSort = CommentSortType.New;
|
||||||
i.setState(i.state);
|
i.setState(i.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
40
ui/src/components/search.tsx
vendored
40
ui/src/components/search.tsx
vendored
|
@ -148,7 +148,7 @@ export class Search extends Component<any, SearchState> {
|
||||||
{this.state.type_ == SearchType.Posts && this.posts()}
|
{this.state.type_ == SearchType.Posts && this.posts()}
|
||||||
{this.state.type_ == SearchType.Communities && this.communities()}
|
{this.state.type_ == SearchType.Communities && this.communities()}
|
||||||
{this.state.type_ == SearchType.Users && this.users()}
|
{this.state.type_ == SearchType.Users && this.users()}
|
||||||
{this.noResults()}
|
{this.resultsCount() == 0 && <span>{i18n.t('no_results')}</span>}
|
||||||
{this.paginator()}
|
{this.paginator()}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -275,6 +275,7 @@ export class Search extends Component<any, SearchState> {
|
||||||
{i.type_ == 'users' && (
|
{i.type_ == 'users' && (
|
||||||
<div>
|
<div>
|
||||||
<span>
|
<span>
|
||||||
|
@
|
||||||
<UserListing
|
<UserListing
|
||||||
user={{
|
user={{
|
||||||
name: (i.data as UserView).name,
|
name: (i.data as UserView).name,
|
||||||
|
@ -282,9 +283,9 @@ export class Search extends Component<any, SearchState> {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span>{` - ${
|
<span>{` - ${i18n.t('number_of_comments', {
|
||||||
(i.data as UserView).comment_score
|
count: (i.data as UserView).number_of_comments,
|
||||||
} comment karma`}</span>
|
})}`}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -359,12 +360,17 @@ export class Search extends Component<any, SearchState> {
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<span>
|
<span>
|
||||||
<Link
|
@
|
||||||
className="text-info"
|
<UserListing
|
||||||
to={`/u/${user.name}`}
|
user={{
|
||||||
>{`/u/${user.name}`}</Link>
|
name: user.name,
|
||||||
|
avatar: user.avatar,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span>{` - ${user.comment_score} comment karma`}</span>
|
<span>{` - ${i18n.t('number_of_comments', {
|
||||||
|
count: user.number_of_comments,
|
||||||
|
})}`}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
@ -383,26 +389,26 @@ export class Search extends Component<any, SearchState> {
|
||||||
{i18n.t('prev')}
|
{i18n.t('prev')}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{this.resultsCount() > 0 && (
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm btn-secondary"
|
class="btn btn-sm btn-secondary"
|
||||||
onClick={linkEvent(this, this.nextPage)}
|
onClick={linkEvent(this, this.nextPage)}
|
||||||
>
|
>
|
||||||
{i18n.t('next')}
|
{i18n.t('next')}
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
noResults() {
|
resultsCount(): number {
|
||||||
let res = this.state.searchResponse;
|
let res = this.state.searchResponse;
|
||||||
return (
|
return (
|
||||||
<div>
|
res.posts.length +
|
||||||
{res &&
|
res.comments.length +
|
||||||
res.posts.length == 0 &&
|
res.communities.length +
|
||||||
res.comments.length == 0 &&
|
res.users.length
|
||||||
res.communities.length == 0 &&
|
|
||||||
res.users.length == 0 && <span>{i18n.t('no_results')}</span>}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
3
ui/src/components/symbols.tsx
vendored
3
ui/src/components/symbols.tsx
vendored
File diff suppressed because one or more lines are too long
13
ui/src/components/user-listing.tsx
vendored
13
ui/src/components/user-listing.tsx
vendored
|
@ -1,7 +1,13 @@
|
||||||
import { Component } from 'inferno';
|
import { Component } from 'inferno';
|
||||||
import { Link } from 'inferno-router';
|
import { Link } from 'inferno-router';
|
||||||
import { UserView } from '../interfaces';
|
import { UserView } from '../interfaces';
|
||||||
import { pictrsAvatarThumbnail, showAvatars, hostname } from '../utils';
|
import {
|
||||||
|
pictrsAvatarThumbnail,
|
||||||
|
showAvatars,
|
||||||
|
hostname,
|
||||||
|
isCakeDay,
|
||||||
|
} from '../utils';
|
||||||
|
import { CakeDay } from './cake-day';
|
||||||
|
|
||||||
interface UserOther {
|
interface UserOther {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -9,6 +15,7 @@ interface UserOther {
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
local?: boolean;
|
local?: boolean;
|
||||||
actor_id?: string;
|
actor_id?: string;
|
||||||
|
published?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UserListingProps {
|
interface UserListingProps {
|
||||||
|
@ -35,6 +42,7 @@ export class UserListing extends Component<UserListingProps, any> {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<Link className="text-body font-weight-bold" to={link}>
|
<Link className="text-body font-weight-bold" to={link}>
|
||||||
{user.avatar && showAvatars() && (
|
{user.avatar && showAvatars() && (
|
||||||
<img
|
<img
|
||||||
|
@ -46,6 +54,9 @@ export class UserListing extends Component<UserListingProps, any> {
|
||||||
)}
|
)}
|
||||||
<span>{name_}</span>
|
<span>{name_}</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
|
{isCakeDay(user.published) && <CakeDay creatorName={name_} />}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
45
ui/src/components/user.tsx
vendored
45
ui/src/components/user.tsx
vendored
|
@ -48,6 +48,7 @@ import { ListingTypeSelect } from './listing-type-select';
|
||||||
import { CommentNodes } from './comment-nodes';
|
import { CommentNodes } from './comment-nodes';
|
||||||
import { MomentTime } from './moment-time';
|
import { MomentTime } from './moment-time';
|
||||||
import { i18n } from '../i18next';
|
import { i18n } from '../i18next';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
enum View {
|
enum View {
|
||||||
Overview,
|
Overview,
|
||||||
|
@ -382,6 +383,7 @@ export class User extends Component<any, UserState> {
|
||||||
nodes={[{ comment: i.data as Comment }]}
|
nodes={[{ comment: i.data as Comment }]}
|
||||||
admins={this.state.admins}
|
admins={this.state.admins}
|
||||||
noIndent
|
noIndent
|
||||||
|
showCommunity
|
||||||
showContext
|
showContext
|
||||||
enableDownvotes={this.state.site.enable_downvotes}
|
enableDownvotes={this.state.site.enable_downvotes}
|
||||||
/>
|
/>
|
||||||
|
@ -399,6 +401,7 @@ export class User extends Component<any, UserState> {
|
||||||
nodes={commentsToFlatNodes(this.state.comments)}
|
nodes={commentsToFlatNodes(this.state.comments)}
|
||||||
admins={this.state.admins}
|
admins={this.state.admins}
|
||||||
noIndent
|
noIndent
|
||||||
|
showCommunity
|
||||||
showContext
|
showContext
|
||||||
enableDownvotes={this.state.site.enable_downvotes}
|
enableDownvotes={this.state.site.enable_downvotes}
|
||||||
/>
|
/>
|
||||||
|
@ -440,6 +443,15 @@ export class User extends Component<any, UserState> {
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
</h5>
|
</h5>
|
||||||
|
<div className="d-flex align-items-center mb-2">
|
||||||
|
<svg class="icon">
|
||||||
|
<use xlinkHref="#icon-cake"></use>
|
||||||
|
</svg>
|
||||||
|
<span className="ml-2">
|
||||||
|
{i18n.t('cake_day_title')}{' '}
|
||||||
|
{moment.utc(user.published).local().format('MMM DD, YYYY')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{i18n.t('joined')} <MomentTime data={user} showAgo />
|
{i18n.t('joined')} <MomentTime data={user} showAgo />
|
||||||
</div>
|
</div>
|
||||||
|
@ -525,7 +537,7 @@ export class User extends Component<any, UserState> {
|
||||||
htmlFor="file-upload"
|
htmlFor="file-upload"
|
||||||
class="pointer ml-4 text-muted small font-weight-bold"
|
class="pointer ml-4 text-muted small font-weight-bold"
|
||||||
>
|
>
|
||||||
{!this.state.userSettingsForm.avatar ? (
|
{!this.checkSettingsAvatar ? (
|
||||||
<span class="btn btn-sm btn-secondary">
|
<span class="btn btn-sm btn-secondary">
|
||||||
{i18n.t('upload_avatar')}
|
{i18n.t('upload_avatar')}
|
||||||
</span>
|
</span>
|
||||||
|
@ -549,6 +561,18 @@ export class User extends Component<any, UserState> {
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
{this.checkSettingsAvatar && (
|
||||||
|
<div class="form-group">
|
||||||
|
<button
|
||||||
|
class="btn btn-secondary btn-block"
|
||||||
|
onClick={linkEvent(this, this.removeAvatar)}
|
||||||
|
>
|
||||||
|
{`${capitalizeFirstLetter(i18n.t('remove'))} ${i18n.t(
|
||||||
|
'avatar'
|
||||||
|
)}`}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>{i18n.t('language')}</label>
|
<label>{i18n.t('language')}</label>
|
||||||
<select
|
<select
|
||||||
|
@ -883,12 +907,14 @@ export class User extends Component<any, UserState> {
|
||||||
{i18n.t('prev')}
|
{i18n.t('prev')}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
{this.state.comments.length + this.state.posts.length > 0 && (
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm btn-secondary"
|
class="btn btn-sm btn-secondary"
|
||||||
onClick={linkEvent(this, this.nextPage)}
|
onClick={linkEvent(this, this.nextPage)}
|
||||||
>
|
>
|
||||||
{i18n.t('next')}
|
{i18n.t('next')}
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1061,6 +1087,22 @@ export class User extends Component<any, UserState> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removeAvatar(i: User, event: any) {
|
||||||
|
event.preventDefault();
|
||||||
|
i.state.userSettingsLoading = true;
|
||||||
|
i.state.userSettingsForm.avatar = '';
|
||||||
|
i.setState(i.state);
|
||||||
|
|
||||||
|
WebSocketService.Instance.saveUserSettings(i.state.userSettingsForm);
|
||||||
|
}
|
||||||
|
|
||||||
|
get checkSettingsAvatar(): boolean {
|
||||||
|
return (
|
||||||
|
this.state.userSettingsForm.avatar &&
|
||||||
|
this.state.userSettingsForm.avatar != ''
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
handleUserSettingsSubmit(i: User, event: any) {
|
handleUserSettingsSubmit(i: User, event: any) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
i.state.userSettingsLoading = true;
|
i.state.userSettingsLoading = true;
|
||||||
|
@ -1178,7 +1220,6 @@ export class User extends Component<any, UserState> {
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (res.op == UserOperation.SaveUserSettings) {
|
} else if (res.op == UserOperation.SaveUserSettings) {
|
||||||
let data = res.data as LoginResponse;
|
let data = res.data as LoginResponse;
|
||||||
this.state = this.emptyState;
|
|
||||||
this.state.userSettingsLoading = false;
|
this.state.userSettingsLoading = false;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
UserService.Instance.login(data);
|
UserService.Instance.login(data);
|
||||||
|
|
2
ui/src/index.html
vendored
2
ui/src/index.html
vendored
|
@ -13,7 +13,7 @@
|
||||||
<!-- Styles -->
|
<!-- Styles -->
|
||||||
<link rel="stylesheet" type="text/css" href="/static/assets/css/tribute.css" />
|
<link rel="stylesheet" type="text/css" href="/static/assets/css/tribute.css" />
|
||||||
<link rel="stylesheet" type="text/css" href="/static/assets/css/toastify.css" />
|
<link rel="stylesheet" type="text/css" href="/static/assets/css/toastify.css" />
|
||||||
<link rel="stylesheet" type="text/css" href="/static/assets/css/selectr.min.css" />
|
<link rel="stylesheet" type="text/css" href="/static/assets/css/choices.min.css" />
|
||||||
<link rel="stylesheet" type="text/css" href="/static/assets/css/tippy.css" />
|
<link rel="stylesheet" type="text/css" href="/static/assets/css/tippy.css" />
|
||||||
<link rel="stylesheet" type="text/css" href="/static/assets/css/themes/litely.min.css" id="default-light" media="(prefers-color-scheme: light)" />
|
<link rel="stylesheet" type="text/css" href="/static/assets/css/themes/litely.min.css" id="default-light" media="(prefers-color-scheme: light)" />
|
||||||
<link rel="stylesheet" type="text/css" href="/static/assets/css/themes/darkly.min.css" id="default-dark" media="(prefers-color-scheme: no-preference), (prefers-color-scheme: dark)" />
|
<link rel="stylesheet" type="text/css" href="/static/assets/css/themes/darkly.min.css" id="default-dark" media="(prefers-color-scheme: no-preference), (prefers-color-scheme: dark)" />
|
||||||
|
|
8
ui/src/interfaces.ts
vendored
8
ui/src/interfaces.ts
vendored
|
@ -54,6 +54,11 @@ export enum CommentSortType {
|
||||||
Old,
|
Old,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum CommentViewType {
|
||||||
|
Tree,
|
||||||
|
Chat,
|
||||||
|
}
|
||||||
|
|
||||||
export enum ListingType {
|
export enum ListingType {
|
||||||
All,
|
All,
|
||||||
Subscribed,
|
Subscribed,
|
||||||
|
@ -183,6 +188,7 @@ export interface Post {
|
||||||
creator_actor_id: string;
|
creator_actor_id: string;
|
||||||
creator_local: boolean;
|
creator_local: boolean;
|
||||||
creator_name: string;
|
creator_name: string;
|
||||||
|
creator_published: string;
|
||||||
creator_avatar?: string;
|
creator_avatar?: string;
|
||||||
community_actor_id: string;
|
community_actor_id: string;
|
||||||
community_local: boolean;
|
community_local: boolean;
|
||||||
|
@ -210,6 +216,7 @@ export interface Comment {
|
||||||
local: boolean;
|
local: boolean;
|
||||||
creator_id: number;
|
creator_id: number;
|
||||||
post_id: number;
|
post_id: number;
|
||||||
|
post_name: string;
|
||||||
parent_id?: number;
|
parent_id?: number;
|
||||||
content: string;
|
content: string;
|
||||||
removed: boolean;
|
removed: boolean;
|
||||||
|
@ -227,6 +234,7 @@ export interface Comment {
|
||||||
creator_local: boolean;
|
creator_local: boolean;
|
||||||
creator_name: string;
|
creator_name: string;
|
||||||
creator_avatar?: string;
|
creator_avatar?: string;
|
||||||
|
creator_published: string;
|
||||||
score: number;
|
score: number;
|
||||||
upvotes: number;
|
upvotes: number;
|
||||||
downvotes: number;
|
downvotes: number;
|
||||||
|
|
40
ui/src/utils.ts
vendored
40
ui/src/utils.ts
vendored
|
@ -51,11 +51,10 @@ import Tribute from 'tributejs/src/Tribute.js';
|
||||||
import markdown_it from 'markdown-it';
|
import markdown_it from 'markdown-it';
|
||||||
import markdownitEmoji from 'markdown-it-emoji/light';
|
import markdownitEmoji from 'markdown-it-emoji/light';
|
||||||
import markdown_it_container from 'markdown-it-container';
|
import markdown_it_container from 'markdown-it-container';
|
||||||
import twemoji from 'twemoji';
|
|
||||||
import emojiShortName from 'emoji-short-name';
|
import emojiShortName from 'emoji-short-name';
|
||||||
import Toastify from 'toastify-js';
|
import Toastify from 'toastify-js';
|
||||||
import tippy from 'tippy.js';
|
import tippy from 'tippy.js';
|
||||||
import EmojiButton from '@joeattardi/emoji-button';
|
import moment from 'moment';
|
||||||
|
|
||||||
export const repoUrl = 'https://github.com/LemmyNet/lemmy';
|
export const repoUrl = 'https://github.com/LemmyNet/lemmy';
|
||||||
export const helpGuideUrl = '/docs/about_guide.html';
|
export const helpGuideUrl = '/docs/about_guide.html';
|
||||||
|
@ -114,14 +113,6 @@ export const themes = [
|
||||||
'litely',
|
'litely',
|
||||||
];
|
];
|
||||||
|
|
||||||
export const emojiPicker = new EmojiButton({
|
|
||||||
// Use the emojiShortName from native
|
|
||||||
style: 'twemoji',
|
|
||||||
theme: 'dark',
|
|
||||||
position: 'auto-start',
|
|
||||||
// TODO i18n
|
|
||||||
});
|
|
||||||
|
|
||||||
const DEFAULT_ALPHABET =
|
const DEFAULT_ALPHABET =
|
||||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
|
|
||||||
|
@ -178,10 +169,6 @@ export const md = new markdown_it({
|
||||||
defs: objectFlip(emojiShortName),
|
defs: objectFlip(emojiShortName),
|
||||||
});
|
});
|
||||||
|
|
||||||
md.renderer.rules.emoji = function (token, idx) {
|
|
||||||
return twemoji.parse(token[idx].content);
|
|
||||||
};
|
|
||||||
|
|
||||||
export function hotRankComment(comment: Comment): number {
|
export function hotRankComment(comment: Comment): number {
|
||||||
return hotRank(comment.score, comment.published);
|
return hotRank(comment.score, comment.published);
|
||||||
}
|
}
|
||||||
|
@ -501,6 +488,19 @@ export function showAvatars(): boolean {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isCakeDay(published: string): boolean {
|
||||||
|
// moment(undefined) or moment.utc(undefined) returns the current date/time
|
||||||
|
// moment(null) or moment.utc(null) returns null
|
||||||
|
const userCreationDate = moment.utc(published || null).local();
|
||||||
|
const currentDate = moment(new Date());
|
||||||
|
|
||||||
|
return (
|
||||||
|
userCreationDate.date() === currentDate.date() &&
|
||||||
|
userCreationDate.month() === currentDate.month() &&
|
||||||
|
userCreationDate.year() !== currentDate.year()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Converts to image thumbnail
|
// Converts to image thumbnail
|
||||||
export function pictrsImage(hash: string, thumbnail: boolean = false): string {
|
export function pictrsImage(hash: string, thumbnail: boolean = false): string {
|
||||||
let root = `/pictrs/image`;
|
let root = `/pictrs/image`;
|
||||||
|
@ -590,8 +590,7 @@ export function setupTribute(): Tribute {
|
||||||
trigger: ':',
|
trigger: ':',
|
||||||
menuItemTemplate: (item: any) => {
|
menuItemTemplate: (item: any) => {
|
||||||
let shortName = `:${item.original.key}:`;
|
let shortName = `:${item.original.key}:`;
|
||||||
let twemojiIcon = twemoji.parse(item.original.val);
|
return `${item.original.val} ${shortName}`;
|
||||||
return `${twemojiIcon} ${shortName}`;
|
|
||||||
},
|
},
|
||||||
selectTemplate: (item: any) => {
|
selectTemplate: (item: any) => {
|
||||||
return `:${item.original.key}:`;
|
return `:${item.original.key}:`;
|
||||||
|
@ -988,3 +987,12 @@ function canUseWebP() {
|
||||||
// // very old browser like IE 8, canvas not supported
|
// // very old browser like IE 8, canvas not supported
|
||||||
// return false;
|
// return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function validTitle(title?: string): boolean {
|
||||||
|
// Initial title is null, minimum length is taken care of by textarea's minLength={3}
|
||||||
|
if (title === null || title.length < 3) return true;
|
||||||
|
|
||||||
|
const regex = new RegExp(/.*\S.*/, 'g');
|
||||||
|
|
||||||
|
return regex.test(title);
|
||||||
|
}
|
||||||
|
|
2
ui/src/version.ts
vendored
2
ui/src/version.ts
vendored
|
@ -1 +1 @@
|
||||||
export const version: string = 'v0.7.13';
|
export const version: string = 'v0.7.19';
|
||||||
|
|
8
ui/translations/en.json
vendored
8
ui/translations/en.json
vendored
|
@ -217,9 +217,10 @@
|
||||||
"no": "no",
|
"no": "no",
|
||||||
"powered_by": "Powered by",
|
"powered_by": "Powered by",
|
||||||
"landing_0":
|
"landing_0":
|
||||||
"Lemmy is a <1>link aggregator</1> / reddit alternative, intended to work in the <2>fediverse</2>.<3></3>It's self-hostable, has live-updating comment threads, and is tiny (<4>~80kB</4>). Federation into the ActivityPub network is on the roadmap. <5></5>This is a <6>very early beta version</6>, and a lot of features are currently broken or missing. <7></7>Suggest new features or report bugs <8>here.</8><9></9>Made with <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>.",
|
"Lemmy is a <1>link aggregator</1> / reddit alternative, intended to work in the <2>fediverse</2>.<3></3>It's self-hostable, has live-updating comment threads, and is tiny (<4>~80kB</4>). Federation into the ActivityPub network is on the roadmap. <5></5>This is a <6>very early beta version</6>, and a lot of features are currently broken or missing. <7></7>Suggest new features or report bugs <8>here.</8><9></9>Made with <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>. <14></14> <15>Thank you to our contributors: </15> dessalines, Nutomic, asonix, zacanger, and iav.",
|
||||||
"not_logged_in": "Not logged in.",
|
"not_logged_in": "Not logged in.",
|
||||||
"logged_in": "Logged in.",
|
"logged_in": "Logged in.",
|
||||||
|
"must_login": "You must <1>log in or register</1> to comment.",
|
||||||
"site_saved": "Site Saved.",
|
"site_saved": "Site Saved.",
|
||||||
"community_ban": "You have been banned from this community.",
|
"community_ban": "You have been banned from this community.",
|
||||||
"site_ban": "You have been banned from the site",
|
"site_ban": "You have been banned from the site",
|
||||||
|
@ -265,5 +266,8 @@
|
||||||
"action": "Action",
|
"action": "Action",
|
||||||
"emoji_picker": "Emoji Picker",
|
"emoji_picker": "Emoji Picker",
|
||||||
"block_leaving": "Are you sure you want to leave?",
|
"block_leaving": "Are you sure you want to leave?",
|
||||||
"what_is": "What is"
|
"what_is": "What is",
|
||||||
|
"cake_day_title": "Cake day:",
|
||||||
|
"cake_day_info": "It's {{ creator_name }}'s cake day today!",
|
||||||
|
"invalid_post_title": "Invalid post title"
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue