mirror of https://github.com/LemmyNet/lemmy.git
Merge branch 'main' into markdown-link-rule
commit
f057abff71
File diff suppressed because it is too large
Load Diff
56
Cargo.toml
56
Cargo.toml
|
@ -70,15 +70,15 @@ lemmy_routes = { version = "=0.19.0-rc.3", path = "./crates/routes" }
|
||||||
lemmy_db_views = { version = "=0.19.0-rc.3", path = "./crates/db_views" }
|
lemmy_db_views = { version = "=0.19.0-rc.3", path = "./crates/db_views" }
|
||||||
lemmy_db_views_actor = { version = "=0.19.0-rc.3", path = "./crates/db_views_actor" }
|
lemmy_db_views_actor = { version = "=0.19.0-rc.3", path = "./crates/db_views_actor" }
|
||||||
lemmy_db_views_moderator = { version = "=0.19.0-rc.3", path = "./crates/db_views_moderator" }
|
lemmy_db_views_moderator = { version = "=0.19.0-rc.3", path = "./crates/db_views_moderator" }
|
||||||
activitypub_federation = { version = "0.5.0-beta.3", default-features = false, features = [
|
activitypub_federation = { git = "https://github.com/LemmyNet/activitypub-federation-rust.git", branch = "webfinger-alphabets", default-features = false, features = [
|
||||||
"actix-web",
|
"actix-web",
|
||||||
] }
|
] }
|
||||||
diesel = "2.1.0"
|
diesel = "2.1.3"
|
||||||
diesel_migrations = "2.1.0"
|
diesel_migrations = "2.1.0"
|
||||||
diesel-async = "0.3.1"
|
diesel-async = "0.3.2"
|
||||||
serde = { version = "1.0.167", features = ["derive"] }
|
serde = { version = "1.0.189", features = ["derive"] }
|
||||||
serde_with = "3.0.0"
|
serde_with = "3.4.0"
|
||||||
actix-web = { version = "4.3.1", default-features = false, features = [
|
actix-web = { version = "4.4.0", default-features = false, features = [
|
||||||
"macros",
|
"macros",
|
||||||
"rustls",
|
"rustls",
|
||||||
"compress-brotli",
|
"compress-brotli",
|
||||||
|
@ -86,37 +86,37 @@ actix-web = { version = "4.3.1", default-features = false, features = [
|
||||||
"compress-zstd",
|
"compress-zstd",
|
||||||
"cookies",
|
"cookies",
|
||||||
] }
|
] }
|
||||||
tracing = "0.1.37"
|
tracing = "0.1.40"
|
||||||
tracing-actix-web = { version = "0.7.5", default-features = false }
|
tracing-actix-web = { version = "0.7.8", default-features = false }
|
||||||
tracing-error = "0.2.0"
|
tracing-error = "0.2.0"
|
||||||
tracing-log = "0.1.3"
|
tracing-log = "0.1.4"
|
||||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||||
url = { version = "2.4.0", features = ["serde"] }
|
url = { version = "2.4.1", features = ["serde"] }
|
||||||
reqwest = { version = "0.11.18", features = ["json", "blocking", "gzip"] }
|
reqwest = { version = "0.11.22", features = ["json", "blocking", "gzip"] }
|
||||||
reqwest-middleware = "0.2.2"
|
reqwest-middleware = "0.2.4"
|
||||||
reqwest-tracing = "0.4.5"
|
reqwest-tracing = "0.4.6"
|
||||||
clokwerk = "0.4.0"
|
clokwerk = "0.4.0"
|
||||||
doku = { version = "0.21.1", features = ["url-2"] }
|
doku = { version = "0.21.1", features = ["url-2"] }
|
||||||
bcrypt = "0.15.0"
|
bcrypt = "0.15.0"
|
||||||
chrono = { version = "0.4.26", features = ["serde"], default-features = false }
|
chrono = { version = "0.4.31", features = ["serde"], default-features = false }
|
||||||
serde_json = { version = "1.0.100", features = ["preserve_order"] }
|
serde_json = { version = "1.0.107", features = ["preserve_order"] }
|
||||||
base64 = "0.21.2"
|
base64 = "0.21.5"
|
||||||
uuid = { version = "1.4.0", features = ["serde", "v4"] }
|
uuid = { version = "1.5.0", features = ["serde", "v4"] }
|
||||||
async-trait = "0.1.71"
|
async-trait = "0.1.74"
|
||||||
captcha = "0.0.9"
|
captcha = "0.0.9"
|
||||||
anyhow = { version = "1.0.71", features = [
|
anyhow = { version = "1.0.75", features = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
] } # backtrace is on by default on nightly, but not stable rust
|
] } # backtrace is on by default on nightly, but not stable rust
|
||||||
diesel_ltree = "0.3.0"
|
diesel_ltree = "0.3.0"
|
||||||
typed-builder = "0.15.0"
|
typed-builder = "0.15.2"
|
||||||
serial_test = "2.0.0"
|
serial_test = "2.0.0"
|
||||||
tokio = { version = "1.29.1", features = ["full"] }
|
tokio = { version = "1.33.0", features = ["full"] }
|
||||||
regex = "1.9.0"
|
regex = "1.10.2"
|
||||||
once_cell = "1.18.0"
|
once_cell = "1.18.0"
|
||||||
diesel-derive-newtype = "2.1.0"
|
diesel-derive-newtype = "2.1.0"
|
||||||
diesel-derive-enum = { version = "2.1.0", features = ["postgres"] }
|
diesel-derive-enum = { version = "2.1.0", features = ["postgres"] }
|
||||||
strum = "0.25.0"
|
strum = "0.25.0"
|
||||||
strum_macros = "0.25.1"
|
strum_macros = "0.25.3"
|
||||||
itertools = "0.11.0"
|
itertools = "0.11.0"
|
||||||
futures = "0.3.28"
|
futures = "0.3.28"
|
||||||
http = "0.2.9"
|
http = "0.2.9"
|
||||||
|
@ -125,12 +125,12 @@ rosetta-i18n = "0.1.3"
|
||||||
opentelemetry = { version = "0.19.0", features = ["rt-tokio"] }
|
opentelemetry = { version = "0.19.0", features = ["rt-tokio"] }
|
||||||
tracing-opentelemetry = { version = "0.19.0" }
|
tracing-opentelemetry = { version = "0.19.0" }
|
||||||
ts-rs = { version = "7.0.0", features = ["serde-compat", "chrono-impl"] }
|
ts-rs = { version = "7.0.0", features = ["serde-compat", "chrono-impl"] }
|
||||||
rustls = { version = "0.21.3", features = ["dangerous_configuration"] }
|
rustls = { version = "0.21.8", features = ["dangerous_configuration"] }
|
||||||
futures-util = "0.3.28"
|
futures-util = "0.3.28"
|
||||||
tokio-postgres = "0.7.8"
|
tokio-postgres = "0.7.10"
|
||||||
tokio-postgres-rustls = "0.10.0"
|
tokio-postgres-rustls = "0.10.0"
|
||||||
urlencoding = "2.1.3"
|
urlencoding = "2.1.3"
|
||||||
enum-map = "2.6"
|
enum-map = "2.7"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lemmy_api = { workspace = true }
|
lemmy_api = { workspace = true }
|
||||||
|
@ -162,7 +162,7 @@ tracing-opentelemetry = { workspace = true, optional = true }
|
||||||
opentelemetry = { workspace = true, optional = true }
|
opentelemetry = { workspace = true, optional = true }
|
||||||
console-subscriber = { version = "0.1.10", optional = true }
|
console-subscriber = { version = "0.1.10", optional = true }
|
||||||
opentelemetry-otlp = { version = "0.12.0", optional = true }
|
opentelemetry-otlp = { version = "0.12.0", optional = true }
|
||||||
pict-rs = { version = "0.4.0-rc.12", optional = true }
|
pict-rs = { version = "0.4.5", optional = true }
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
actix-cors = "0.6.4"
|
actix-cors = "0.6.4"
|
||||||
rustls = { workspace = true }
|
rustls = { workspace = true }
|
||||||
|
@ -173,5 +173,5 @@ chrono = { workspace = true }
|
||||||
prometheus = { version = "0.13.3", features = ["process"], optional = true }
|
prometheus = { version = "0.13.3", features = ["process"], optional = true }
|
||||||
actix-web-prom = { version = "0.6.0", optional = true }
|
actix-web-prom = { version = "0.6.0", optional = true }
|
||||||
serial_test = { workspace = true }
|
serial_test = { workspace = true }
|
||||||
clap = { version = "4.3.19", features = ["derive"] }
|
clap = { version = "4.4.7", features = ["derive"] }
|
||||||
actix-web-httpauth = "0.8.1"
|
actix-web-httpauth = "0.8.1"
|
||||||
|
|
|
@ -12,11 +12,11 @@
|
||||||
"api-test": "jest -i follow.spec.ts && jest -i post.spec.ts && jest -i comment.spec.ts && jest -i private_message.spec.ts && jest -i user.spec.ts && jest -i community.spec.ts"
|
"api-test": "jest -i follow.spec.ts && jest -i post.spec.ts && jest -i comment.spec.ts && jest -i private_message.spec.ts && jest -i user.spec.ts && jest -i community.spec.ts"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^29.5.1",
|
"@types/jest": "^29.5.6",
|
||||||
"@types/node": "^20.8.6",
|
"@types/node": "^20.8.7",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.7.5",
|
"@typescript-eslint/eslint-plugin": "^6.8.0",
|
||||||
"@typescript-eslint/parser": "^6.7.5",
|
"@typescript-eslint/parser": "^6.8.0",
|
||||||
"eslint": "^8.51.0",
|
"eslint": "^8.52.0",
|
||||||
"eslint-plugin-prettier": "^5.0.1",
|
"eslint-plugin-prettier": "^5.0.1",
|
||||||
"jest": "^29.5.0",
|
"jest": "^29.5.0",
|
||||||
"lemmy-js-client": "0.19.0-rc.12",
|
"lemmy-js-client": "0.19.0-rc.12",
|
||||||
|
|
|
@ -25,7 +25,6 @@ import {
|
||||||
getCommunityByName,
|
getCommunityByName,
|
||||||
blockInstance,
|
blockInstance,
|
||||||
waitUntil,
|
waitUntil,
|
||||||
delay,
|
|
||||||
alphaUrl,
|
alphaUrl,
|
||||||
delta,
|
delta,
|
||||||
betaAllowedInstances,
|
betaAllowedInstances,
|
||||||
|
|
|
@ -17,8 +17,9 @@ import {
|
||||||
saveUserSettingsFederated,
|
saveUserSettingsFederated,
|
||||||
setupLogins,
|
setupLogins,
|
||||||
alphaUrl,
|
alphaUrl,
|
||||||
|
saveUserSettings,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
import { LemmyHttp } from "lemmy-js-client";
|
import { LemmyHttp, SaveUserSettings } from "lemmy-js-client";
|
||||||
import { GetPosts } from "lemmy-js-client/dist/types/GetPosts";
|
import { GetPosts } from "lemmy-js-client/dist/types/GetPosts";
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
|
@ -57,6 +58,15 @@ test("Set some user settings, check that they are federated", async () => {
|
||||||
let alphaPerson = (await resolvePerson(alpha, apShortname)).person;
|
let alphaPerson = (await resolvePerson(alpha, apShortname)).person;
|
||||||
let betaPerson = (await resolvePerson(beta, apShortname)).person;
|
let betaPerson = (await resolvePerson(beta, apShortname)).person;
|
||||||
assertUserFederation(alphaPerson, betaPerson);
|
assertUserFederation(alphaPerson, betaPerson);
|
||||||
|
|
||||||
|
// Catches a bug where when only the person or local_user changed
|
||||||
|
let form: SaveUserSettings = {
|
||||||
|
theme: "test",
|
||||||
|
};
|
||||||
|
await saveUserSettings(beta, form);
|
||||||
|
|
||||||
|
let site = await getSite(beta);
|
||||||
|
expect(site.my_user?.local_user_view.local_user.theme).toBe("test");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Delete user", async () => {
|
test("Delete user", async () => {
|
||||||
|
@ -119,3 +129,21 @@ test("Requests with invalid auth should be treated as unauthenticated", async ()
|
||||||
let posts = invalid_auth.getPosts(form);
|
let posts = invalid_auth.getPosts(form);
|
||||||
expect((await posts).posts).toBeDefined();
|
expect((await posts).posts).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Create user with Arabic name", async () => {
|
||||||
|
let userRes = await registerUser(alpha, "تجريب");
|
||||||
|
expect(userRes.jwt).toBeDefined();
|
||||||
|
let user = new LemmyHttp(alphaUrl, {
|
||||||
|
headers: { Authorization: `Bearer ${userRes.jwt ?? ""}` },
|
||||||
|
});
|
||||||
|
|
||||||
|
let site = await getSite(user);
|
||||||
|
expect(site.my_user).toBeDefined();
|
||||||
|
if (!site.my_user) {
|
||||||
|
throw "Missing site user";
|
||||||
|
}
|
||||||
|
apShortname = `@${site.my_user.local_user_view.person.name}@lemmy-alpha:8541`;
|
||||||
|
|
||||||
|
let alphaPerson = (await resolvePerson(alpha, apShortname)).person;
|
||||||
|
expect(alphaPerson).toBeDefined();
|
||||||
|
});
|
||||||
|
|
|
@ -329,17 +329,17 @@
|
||||||
minimatch "^3.1.2"
|
minimatch "^3.1.2"
|
||||||
strip-json-comments "^3.1.1"
|
strip-json-comments "^3.1.1"
|
||||||
|
|
||||||
"@eslint/js@8.51.0":
|
"@eslint/js@8.52.0":
|
||||||
version "8.51.0"
|
version "8.52.0"
|
||||||
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.51.0.tgz#6d419c240cfb2b66da37df230f7e7eef801c32fa"
|
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.52.0.tgz#78fe5f117840f69dc4a353adf9b9cd926353378c"
|
||||||
integrity sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==
|
integrity sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==
|
||||||
|
|
||||||
"@humanwhocodes/config-array@^0.11.11":
|
"@humanwhocodes/config-array@^0.11.13":
|
||||||
version "0.11.11"
|
version "0.11.13"
|
||||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.11.tgz#88a04c570dbbc7dd943e4712429c3df09bc32844"
|
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.13.tgz#075dc9684f40a531d9b26b0822153c1e832ee297"
|
||||||
integrity sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==
|
integrity sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@humanwhocodes/object-schema" "^1.2.1"
|
"@humanwhocodes/object-schema" "^2.0.1"
|
||||||
debug "^4.1.1"
|
debug "^4.1.1"
|
||||||
minimatch "^3.0.5"
|
minimatch "^3.0.5"
|
||||||
|
|
||||||
|
@ -348,10 +348,10 @@
|
||||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c"
|
resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c"
|
||||||
integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==
|
integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==
|
||||||
|
|
||||||
"@humanwhocodes/object-schema@^1.2.1":
|
"@humanwhocodes/object-schema@^2.0.1":
|
||||||
version "1.2.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
|
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz#e5211452df060fa8522b55c7b3c0c4d1981cb044"
|
||||||
integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
|
integrity sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==
|
||||||
|
|
||||||
"@istanbuljs/load-nyc-config@^1.0.0":
|
"@istanbuljs/load-nyc-config@^1.0.0":
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
|
@ -704,10 +704,10 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/istanbul-lib-report" "*"
|
"@types/istanbul-lib-report" "*"
|
||||||
|
|
||||||
"@types/jest@^29.5.1":
|
"@types/jest@^29.5.6":
|
||||||
version "29.5.5"
|
version "29.5.6"
|
||||||
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.5.tgz#727204e06228fe24373df9bae76b90f3e8236a2a"
|
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.6.tgz#f4cf7ef1b5b0bfc1aa744e41b24d9cc52533130b"
|
||||||
integrity sha512-ebylz2hnsWR9mYvmBFbXJXr+33UPc4+ZdxyDXh5w0FlPBTfCVN3wPL+kuOiQt3xvrK419v7XWeAs+AeOksafXg==
|
integrity sha512-/t9NnzkOpXb4Nfvg17ieHE6EeSjDS2SGSpNYfoLbUAeL/EOueU/RSdOWFpfQTXBEM7BguYW1XQ0EbM+6RlIh6w==
|
||||||
dependencies:
|
dependencies:
|
||||||
expect "^29.0.0"
|
expect "^29.0.0"
|
||||||
pretty-format "^29.0.0"
|
pretty-format "^29.0.0"
|
||||||
|
@ -722,10 +722,10 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.8.0.tgz#10ddf0119cf20028781c06d7115562934e53f745"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.8.0.tgz#10ddf0119cf20028781c06d7115562934e53f745"
|
||||||
integrity sha512-LzcWltT83s1bthcvjBmiBvGJiiUe84NWRHkw+ZV6Fr41z2FbIzvc815dk2nQ3RAKMuN2fkenM/z3Xv2QzEpYxQ==
|
integrity sha512-LzcWltT83s1bthcvjBmiBvGJiiUe84NWRHkw+ZV6Fr41z2FbIzvc815dk2nQ3RAKMuN2fkenM/z3Xv2QzEpYxQ==
|
||||||
|
|
||||||
"@types/node@^20.8.6":
|
"@types/node@^20.8.7":
|
||||||
version "20.8.6"
|
version "20.8.7"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.8.6.tgz#0dbd4ebcc82ad0128df05d0e6f57e05359ee47fa"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.8.7.tgz#ad23827850843de973096edfc5abc9e922492a25"
|
||||||
integrity sha512-eWO4K2Ji70QzKUqRy6oyJWUeB7+g2cRagT3T/nxYibYcT4y2BDL8lqolRXjTHmkZCdJfIPaY73KbJAZmcryxTQ==
|
integrity sha512-21TKHHh3eUHIi2MloeptJWALuCu5H7HQTdTrWIFReA8ad+aggoX+lRes3ex7/FtpC+sVUpFMQ+QTfYr74mruiQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types "~5.25.1"
|
undici-types "~5.25.1"
|
||||||
|
|
||||||
|
@ -751,16 +751,16 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/yargs-parser" "*"
|
"@types/yargs-parser" "*"
|
||||||
|
|
||||||
"@typescript-eslint/eslint-plugin@^6.7.5":
|
"@typescript-eslint/eslint-plugin@^6.8.0":
|
||||||
version "6.7.5"
|
version "6.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.5.tgz#f4024b9f63593d0c2b5bd6e4ca027e6f30934d4f"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.8.0.tgz#06abe4265e7c82f20ade2dcc0e3403c32d4f148b"
|
||||||
integrity sha512-JhtAwTRhOUcP96D0Y6KYnwig/MRQbOoLGXTON2+LlyB/N35SP9j1boai2zzwXb7ypKELXMx3DVk9UTaEq1vHEw==
|
integrity sha512-GosF4238Tkes2SHPQ1i8f6rMtG6zlKwMEB0abqSJ3Npvos+doIlc/ATG+vX1G9coDF3Ex78zM3heXHLyWEwLUw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@eslint-community/regexpp" "^4.5.1"
|
"@eslint-community/regexpp" "^4.5.1"
|
||||||
"@typescript-eslint/scope-manager" "6.7.5"
|
"@typescript-eslint/scope-manager" "6.8.0"
|
||||||
"@typescript-eslint/type-utils" "6.7.5"
|
"@typescript-eslint/type-utils" "6.8.0"
|
||||||
"@typescript-eslint/utils" "6.7.5"
|
"@typescript-eslint/utils" "6.8.0"
|
||||||
"@typescript-eslint/visitor-keys" "6.7.5"
|
"@typescript-eslint/visitor-keys" "6.8.0"
|
||||||
debug "^4.3.4"
|
debug "^4.3.4"
|
||||||
graphemer "^1.4.0"
|
graphemer "^1.4.0"
|
||||||
ignore "^5.2.4"
|
ignore "^5.2.4"
|
||||||
|
@ -768,74 +768,79 @@
|
||||||
semver "^7.5.4"
|
semver "^7.5.4"
|
||||||
ts-api-utils "^1.0.1"
|
ts-api-utils "^1.0.1"
|
||||||
|
|
||||||
"@typescript-eslint/parser@^6.7.5":
|
"@typescript-eslint/parser@^6.8.0":
|
||||||
version "6.7.5"
|
version "6.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.7.5.tgz#8d7ca3d1fbd9d5a58cc4d30b2aa797a760137886"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.8.0.tgz#bb2a969d583db242f1ee64467542f8b05c2e28cb"
|
||||||
integrity sha512-bIZVSGx2UME/lmhLcjdVc7ePBwn7CLqKarUBL4me1C5feOd663liTGjMBGVcGr+BhnSLeP4SgwdvNnnkbIdkCw==
|
integrity sha512-5tNs6Bw0j6BdWuP8Fx+VH4G9fEPDxnVI7yH1IAPkQH5RUtvKwRoqdecAPdQXv4rSOADAaz1LFBZvZG7VbXivSg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/scope-manager" "6.7.5"
|
"@typescript-eslint/scope-manager" "6.8.0"
|
||||||
"@typescript-eslint/types" "6.7.5"
|
"@typescript-eslint/types" "6.8.0"
|
||||||
"@typescript-eslint/typescript-estree" "6.7.5"
|
"@typescript-eslint/typescript-estree" "6.8.0"
|
||||||
"@typescript-eslint/visitor-keys" "6.7.5"
|
"@typescript-eslint/visitor-keys" "6.8.0"
|
||||||
debug "^4.3.4"
|
debug "^4.3.4"
|
||||||
|
|
||||||
"@typescript-eslint/scope-manager@6.7.5":
|
"@typescript-eslint/scope-manager@6.8.0":
|
||||||
version "6.7.5"
|
version "6.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.7.5.tgz#1cf33b991043886cd67f4f3600b8e122fc14e711"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.8.0.tgz#5cac7977385cde068ab30686889dd59879811efd"
|
||||||
integrity sha512-GAlk3eQIwWOJeb9F7MKQ6Jbah/vx1zETSDw8likab/eFcqkjSD7BI75SDAeC5N2L0MmConMoPvTsmkrg71+B1A==
|
integrity sha512-xe0HNBVwCph7rak+ZHcFD6A+q50SMsFwcmfdjs9Kz4qDh5hWhaPhFjRs/SODEhroBI5Ruyvyz9LfwUJ624O40g==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/types" "6.7.5"
|
"@typescript-eslint/types" "6.8.0"
|
||||||
"@typescript-eslint/visitor-keys" "6.7.5"
|
"@typescript-eslint/visitor-keys" "6.8.0"
|
||||||
|
|
||||||
"@typescript-eslint/type-utils@6.7.5":
|
"@typescript-eslint/type-utils@6.8.0":
|
||||||
version "6.7.5"
|
version "6.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.7.5.tgz#0a65949ec16588d8956f6d967f7d9c84ddb2d72a"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.8.0.tgz#50365e44918ca0fd159844b5d6ea96789731e11f"
|
||||||
integrity sha512-Gs0qos5wqxnQrvpYv+pf3XfcRXW6jiAn9zE/K+DlmYf6FcpxeNYN0AIETaPR7rHO4K2UY+D0CIbDP9Ut0U4m1g==
|
integrity sha512-RYOJdlkTJIXW7GSldUIHqc/Hkto8E+fZN96dMIFhuTJcQwdRoGN2rEWA8U6oXbLo0qufH7NPElUb+MceHtz54g==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/typescript-estree" "6.7.5"
|
"@typescript-eslint/typescript-estree" "6.8.0"
|
||||||
"@typescript-eslint/utils" "6.7.5"
|
"@typescript-eslint/utils" "6.8.0"
|
||||||
debug "^4.3.4"
|
debug "^4.3.4"
|
||||||
ts-api-utils "^1.0.1"
|
ts-api-utils "^1.0.1"
|
||||||
|
|
||||||
"@typescript-eslint/types@6.7.5":
|
"@typescript-eslint/types@6.8.0":
|
||||||
version "6.7.5"
|
version "6.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.7.5.tgz#4571320fb9cf669de9a95d9849f922c3af809790"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.8.0.tgz#1ab5d4fe1d613e3f65f6684026ade6b94f7e3ded"
|
||||||
integrity sha512-WboQBlOXtdj1tDFPyIthpKrUb+kZf2VroLZhxKa/VlwLlLyqv/PwUNgL30BlTVZV1Wu4Asu2mMYPqarSO4L5ZQ==
|
integrity sha512-p5qOxSum7W3k+llc7owEStXlGmSl8FcGvhYt8Vjy7FqEnmkCVlM3P57XQEGj58oqaBWDQXbJDZxwUWMS/EAPNQ==
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree@6.7.5":
|
"@typescript-eslint/typescript-estree@6.8.0":
|
||||||
version "6.7.5"
|
version "6.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.5.tgz#4578de1a26e9f24950f029a4f00d1bfe41f15a39"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.8.0.tgz#9565f15e0cd12f55cf5aa0dfb130a6cb0d436ba1"
|
||||||
integrity sha512-NhJiJ4KdtwBIxrKl0BqG1Ur+uw7FiOnOThcYx9DpOGJ/Abc9z2xNzLeirCG02Ig3vkvrc2qFLmYSSsaITbKjlg==
|
integrity sha512-ISgV0lQ8XgW+mvv5My/+iTUdRmGspducmQcDw5JxznasXNnZn3SKNrTRuMsEXv+V/O+Lw9AGcQCfVaOPCAk/Zg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/types" "6.7.5"
|
"@typescript-eslint/types" "6.8.0"
|
||||||
"@typescript-eslint/visitor-keys" "6.7.5"
|
"@typescript-eslint/visitor-keys" "6.8.0"
|
||||||
debug "^4.3.4"
|
debug "^4.3.4"
|
||||||
globby "^11.1.0"
|
globby "^11.1.0"
|
||||||
is-glob "^4.0.3"
|
is-glob "^4.0.3"
|
||||||
semver "^7.5.4"
|
semver "^7.5.4"
|
||||||
ts-api-utils "^1.0.1"
|
ts-api-utils "^1.0.1"
|
||||||
|
|
||||||
"@typescript-eslint/utils@6.7.5":
|
"@typescript-eslint/utils@6.8.0":
|
||||||
version "6.7.5"
|
version "6.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.7.5.tgz#ab847b53d6b65e029314b8247c2336843dba81ab"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.8.0.tgz#d42939c2074c6b59844d0982ce26a51d136c4029"
|
||||||
integrity sha512-pfRRrH20thJbzPPlPc4j0UNGvH1PjPlhlCMq4Yx7EGjV7lvEeGX0U6MJYe8+SyFutWgSHsdbJ3BXzZccYggezA==
|
integrity sha512-dKs1itdE2qFG4jr0dlYLQVppqTE+Itt7GmIf/vX6CSvsW+3ov8PbWauVKyyfNngokhIO9sKZeRGCUo1+N7U98Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@eslint-community/eslint-utils" "^4.4.0"
|
"@eslint-community/eslint-utils" "^4.4.0"
|
||||||
"@types/json-schema" "^7.0.12"
|
"@types/json-schema" "^7.0.12"
|
||||||
"@types/semver" "^7.5.0"
|
"@types/semver" "^7.5.0"
|
||||||
"@typescript-eslint/scope-manager" "6.7.5"
|
"@typescript-eslint/scope-manager" "6.8.0"
|
||||||
"@typescript-eslint/types" "6.7.5"
|
"@typescript-eslint/types" "6.8.0"
|
||||||
"@typescript-eslint/typescript-estree" "6.7.5"
|
"@typescript-eslint/typescript-estree" "6.8.0"
|
||||||
semver "^7.5.4"
|
semver "^7.5.4"
|
||||||
|
|
||||||
"@typescript-eslint/visitor-keys@6.7.5":
|
"@typescript-eslint/visitor-keys@6.8.0":
|
||||||
version "6.7.5"
|
version "6.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.5.tgz#84c68d6ceb5b12d5246b918b84f2b79affd6c2f1"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.8.0.tgz#cffebed56ae99c45eba901c378a6447b06be58b8"
|
||||||
integrity sha512-3MaWdDZtLlsexZzDSdQWsFQ9l9nL8B80Z4fImSpyllFC/KLqWQRdEcB+gGGO+N3Q2uL40EsG66wZLsohPxNXvg==
|
integrity sha512-oqAnbA7c+pgOhW2OhGvxm0t1BULX5peQI/rLsNDpGM78EebV3C9IGbX5HNZabuZ6UQrYveCLjKo8Iy/lLlBkkg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/types" "6.7.5"
|
"@typescript-eslint/types" "6.8.0"
|
||||||
eslint-visitor-keys "^3.4.1"
|
eslint-visitor-keys "^3.4.1"
|
||||||
|
|
||||||
|
"@ungap/structured-clone@^1.2.0":
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406"
|
||||||
|
integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==
|
||||||
|
|
||||||
acorn-jsx@^5.3.2:
|
acorn-jsx@^5.3.2:
|
||||||
version "5.3.2"
|
version "5.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
|
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
|
||||||
|
@ -1328,18 +1333,19 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4
|
||||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800"
|
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800"
|
||||||
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
|
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
|
||||||
|
|
||||||
eslint@^8.51.0:
|
eslint@^8.52.0:
|
||||||
version "8.51.0"
|
version "8.52.0"
|
||||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.51.0.tgz#4a82dae60d209ac89a5cff1604fea978ba4950f3"
|
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.52.0.tgz#d0cd4a1fac06427a61ef9242b9353f36ea7062fc"
|
||||||
integrity sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==
|
integrity sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@eslint-community/eslint-utils" "^4.2.0"
|
"@eslint-community/eslint-utils" "^4.2.0"
|
||||||
"@eslint-community/regexpp" "^4.6.1"
|
"@eslint-community/regexpp" "^4.6.1"
|
||||||
"@eslint/eslintrc" "^2.1.2"
|
"@eslint/eslintrc" "^2.1.2"
|
||||||
"@eslint/js" "8.51.0"
|
"@eslint/js" "8.52.0"
|
||||||
"@humanwhocodes/config-array" "^0.11.11"
|
"@humanwhocodes/config-array" "^0.11.13"
|
||||||
"@humanwhocodes/module-importer" "^1.0.1"
|
"@humanwhocodes/module-importer" "^1.0.1"
|
||||||
"@nodelib/fs.walk" "^1.2.8"
|
"@nodelib/fs.walk" "^1.2.8"
|
||||||
|
"@ungap/structured-clone" "^1.2.0"
|
||||||
ajv "^6.12.4"
|
ajv "^6.12.4"
|
||||||
chalk "^4.0.0"
|
chalk "^4.0.0"
|
||||||
cross-spawn "^7.0.2"
|
cross-spawn "^7.0.2"
|
||||||
|
|
|
@ -34,7 +34,7 @@ chrono = { workspace = true }
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
wav = "1.0.0"
|
wav = "1.0.0"
|
||||||
sitemap-rs = "0.2.0"
|
sitemap-rs = "0.2.0"
|
||||||
totp-rs = { version = "5.0.2", features = ["gen_secret", "otpauth"] }
|
totp-rs = { version = "5.4.0", features = ["gen_secret", "otpauth"] }
|
||||||
actix-web-httpauth = "0.8.1"
|
actix-web-httpauth = "0.8.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
|
@ -21,7 +21,7 @@ use lemmy_db_schema::{
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
error::{LemmyError, LemmyErrorExt, LemmyErrorType},
|
error::{LemmyError, LemmyErrorType},
|
||||||
utils::validation::{is_valid_bio_field, is_valid_display_name, is_valid_matrix_id},
|
utils::validation::{is_valid_bio_field, is_valid_display_name, is_valid_matrix_id},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -98,9 +98,11 @@ pub async fn save_user_settings(
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Ignore errors, because 'no fields updated' will return an error.
|
||||||
|
// https://github.com/LemmyNet/lemmy/issues/4076
|
||||||
Person::update(&mut context.pool(), person_id, &person_form)
|
Person::update(&mut context.pool(), person_id, &person_form)
|
||||||
.await
|
.await
|
||||||
.with_lemmy_type(LemmyErrorType::UserAlreadyExists)?;
|
.ok();
|
||||||
|
|
||||||
if let Some(discussion_languages) = data.discussion_languages.clone() {
|
if let Some(discussion_languages) = data.discussion_languages.clone() {
|
||||||
LocalUserLanguage::update(&mut context.pool(), discussion_languages, local_user_id).await?;
|
LocalUserLanguage::update(&mut context.pool(), discussion_languages, local_user_id).await?;
|
||||||
|
@ -128,7 +130,11 @@ pub async fn save_user_settings(
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
LocalUser::update(&mut context.pool(), local_user_id, &local_user_form).await?;
|
// Ignore errors, because 'no fields updated' will return an error.
|
||||||
|
// https://github.com/LemmyNet/lemmy/issues/4076
|
||||||
|
LocalUser::update(&mut context.pool(), local_user_id, &local_user_form)
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
|
||||||
Ok(Json(SuccessResponse::default()))
|
Ok(Json(SuccessResponse::default()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,9 @@ pub async fn leave_admin(
|
||||||
local_user_view.local_user.id,
|
local_user_view.local_user.id,
|
||||||
&LocalUserUpdateForm {
|
&LocalUserUpdateForm {
|
||||||
admin: Some(false),
|
admin: Some(false),
|
||||||
|
// Necessary because admins can bypass the registration applications (if they're turned on)
|
||||||
|
// but then won't be able to log in because they haven't been approved.
|
||||||
|
accepted_application: Some(true),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -47,6 +47,7 @@ pub struct GetPost {
|
||||||
pub comment_id: Option<CommentId>,
|
pub comment_id: Option<CommentId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
#[cfg_attr(feature = "full", derive(TS))]
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
|
|
@ -8,6 +8,7 @@ use lemmy_utils::{
|
||||||
REQWEST_TIMEOUT,
|
REQWEST_TIMEOUT,
|
||||||
};
|
};
|
||||||
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
|
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
|
||||||
|
use reqwest::{Client, ClientBuilder};
|
||||||
use reqwest_middleware::ClientWithMiddleware;
|
use reqwest_middleware::ClientWithMiddleware;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
@ -288,12 +289,17 @@ async fn is_image_content_type(client: &ClientWithMiddleware, url: &Url) -> Resu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_user_agent(settings: &Settings) -> String {
|
pub fn client_builder(settings: &Settings) -> ClientBuilder {
|
||||||
format!(
|
let user_agent = format!(
|
||||||
"Lemmy/{}; +{}",
|
"Lemmy/{}; +{}",
|
||||||
VERSION,
|
VERSION,
|
||||||
settings.get_protocol_and_hostname()
|
settings.get_protocol_and_hostname()
|
||||||
)
|
);
|
||||||
|
|
||||||
|
Client::builder()
|
||||||
|
.user_agent(user_agent.clone())
|
||||||
|
.timeout(REQWEST_TIMEOUT)
|
||||||
|
.connect_timeout(REQWEST_TIMEOUT)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -301,12 +307,7 @@ mod tests {
|
||||||
#![allow(clippy::unwrap_used)]
|
#![allow(clippy::unwrap_used)]
|
||||||
#![allow(clippy::indexing_slicing)]
|
#![allow(clippy::indexing_slicing)]
|
||||||
|
|
||||||
use crate::request::{
|
use crate::request::{client_builder, fetch_site_metadata, html_to_site_metadata, SiteMetadata};
|
||||||
build_user_agent,
|
|
||||||
fetch_site_metadata,
|
|
||||||
html_to_site_metadata,
|
|
||||||
SiteMetadata,
|
|
||||||
};
|
|
||||||
use lemmy_utils::settings::SETTINGS;
|
use lemmy_utils::settings::SETTINGS;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
@ -314,11 +315,7 @@ mod tests {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_site_metadata() {
|
async fn test_site_metadata() {
|
||||||
let settings = &SETTINGS.clone();
|
let settings = &SETTINGS.clone();
|
||||||
let client = reqwest::Client::builder()
|
let client = client_builder(settings).build().unwrap().into();
|
||||||
.user_agent(build_user_agent(settings))
|
|
||||||
.build()
|
|
||||||
.unwrap()
|
|
||||||
.into();
|
|
||||||
let sample_url = Url::parse("https://gitlab.com/IzzyOnDroid/repo/-/wikis/FAQ").unwrap();
|
let sample_url = Url::parse("https://gitlab.com/IzzyOnDroid/repo/-/wikis/FAQ").unwrap();
|
||||||
let sample_res = fetch_site_metadata(&client, &sample_url).await.unwrap();
|
let sample_res = fetch_site_metadata(&client, &sample_url).await.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
|
@ -18,7 +18,7 @@ pub async fn get_private_message(
|
||||||
let limit = data.limit;
|
let limit = data.limit;
|
||||||
let unread_only = data.unread_only.unwrap_or_default();
|
let unread_only = data.unread_only.unwrap_or_default();
|
||||||
let creator_id = data.creator_id;
|
let creator_id = data.creator_id;
|
||||||
let mut messages = PrivateMessageQuery {
|
let messages = PrivateMessageQuery {
|
||||||
page,
|
page,
|
||||||
limit,
|
limit,
|
||||||
unread_only,
|
unread_only,
|
||||||
|
@ -27,14 +27,6 @@ pub async fn get_private_message(
|
||||||
.list(&mut context.pool(), person_id)
|
.list(&mut context.pool(), person_id)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Messages sent by ourselves should be marked as read. The `read` column in database is only
|
|
||||||
// for the recipient, and shouldnt be exposed to sender.
|
|
||||||
messages.iter_mut().for_each(|pmv| {
|
|
||||||
if pmv.creator.id == person_id {
|
|
||||||
pmv.private_message.read = true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(Json(PrivateMessagesResponse {
|
Ok(Json(PrivateMessagesResponse {
|
||||||
private_messages: messages,
|
private_messages: messages,
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -59,10 +59,10 @@ pub(crate) mod tests {
|
||||||
|
|
||||||
use activitypub_federation::config::{Data, FederationConfig};
|
use activitypub_federation::config::{Data, FederationConfig};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use lemmy_api_common::{context::LemmyContext, request::build_user_agent};
|
use lemmy_api_common::{context::LemmyContext, request::client_builder};
|
||||||
use lemmy_db_schema::{source::secret::Secret, utils::build_db_pool_for_tests};
|
use lemmy_db_schema::{source::secret::Secret, utils::build_db_pool_for_tests};
|
||||||
use lemmy_utils::{rate_limit::RateLimitCell, settings::SETTINGS};
|
use lemmy_utils::{rate_limit::RateLimitCell, settings::SETTINGS};
|
||||||
use reqwest::{Client, Request, Response};
|
use reqwest::{Request, Response};
|
||||||
use reqwest_middleware::{ClientBuilder, Middleware, Next};
|
use reqwest_middleware::{ClientBuilder, Middleware, Next};
|
||||||
use task_local_extensions::Extensions;
|
use task_local_extensions::Extensions;
|
||||||
|
|
||||||
|
@ -86,11 +86,7 @@ pub(crate) mod tests {
|
||||||
// call this to run migrations
|
// call this to run migrations
|
||||||
let pool = build_db_pool_for_tests().await;
|
let pool = build_db_pool_for_tests().await;
|
||||||
|
|
||||||
let settings = SETTINGS.clone();
|
let client = client_builder(&SETTINGS).build().unwrap();
|
||||||
let client = Client::builder()
|
|
||||||
.user_agent(build_user_agent(&settings))
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let client = ClientBuilder::new(client).with(BlockedMiddleware).build();
|
let client = ClientBuilder::new(client).with(BlockedMiddleware).build();
|
||||||
let secret = Secret {
|
let secret = Secret {
|
||||||
|
|
|
@ -71,8 +71,10 @@ pub struct PersonAggregates {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub person_id: PersonId,
|
pub person_id: PersonId,
|
||||||
pub post_count: i64,
|
pub post_count: i64,
|
||||||
|
#[serde(skip)]
|
||||||
pub post_score: i64,
|
pub post_score: i64,
|
||||||
pub comment_count: i64,
|
pub comment_count: i64,
|
||||||
|
#[serde(skip)]
|
||||||
pub comment_score: i64,
|
pub comment_score: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,8 +30,8 @@ pub mod newtypes;
|
||||||
pub mod schema;
|
pub mod schema;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
pub mod aliases {
|
pub mod aliases {
|
||||||
use crate::schema::person;
|
use crate::schema::{community_moderator, person};
|
||||||
diesel::alias!(person as person1: Person1, person as person2: Person2);
|
diesel::alias!(person as person1: Person1, person as person2: Person2, community_moderator as community_moderator1: CommunityModerator1);
|
||||||
}
|
}
|
||||||
pub mod source;
|
pub mod source;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
|
|
|
@ -12,6 +12,7 @@ use diesel::{
|
||||||
use diesel_async::RunQueryDsl;
|
use diesel_async::RunQueryDsl;
|
||||||
use diesel_ltree::{nlevel, subpath, Ltree, LtreeExtensions};
|
use diesel_ltree::{nlevel, subpath, Ltree, LtreeExtensions};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
|
aliases,
|
||||||
newtypes::{CommentId, CommunityId, LocalUserId, PersonId, PostId},
|
newtypes::{CommentId, CommunityId, LocalUserId, PersonId, PostId},
|
||||||
schema::{
|
schema::{
|
||||||
comment,
|
comment,
|
||||||
|
@ -90,6 +91,17 @@ fn queries<'a>() -> Queries<
|
||||||
.and(community_moderator::person_id.eq(person_id_join)),
|
.and(community_moderator::person_id.eq(person_id_join)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
.left_join(
|
||||||
|
aliases::community_moderator1.on(
|
||||||
|
community::id
|
||||||
|
.eq(aliases::community_moderator1.field(community_moderator::community_id))
|
||||||
|
.and(
|
||||||
|
aliases::community_moderator1
|
||||||
|
.field(community_moderator::person_id)
|
||||||
|
.eq(comment::creator_id),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let selection = (
|
let selection = (
|
||||||
|
@ -99,6 +111,10 @@ fn queries<'a>() -> Queries<
|
||||||
community::all_columns,
|
community::all_columns,
|
||||||
comment_aggregates::all_columns,
|
comment_aggregates::all_columns,
|
||||||
community_person_ban::id.nullable().is_not_null(),
|
community_person_ban::id.nullable().is_not_null(),
|
||||||
|
aliases::community_moderator1
|
||||||
|
.field(community_moderator::id)
|
||||||
|
.nullable()
|
||||||
|
.is_not_null(),
|
||||||
CommunityFollower::select_subscribed_type(),
|
CommunityFollower::select_subscribed_type(),
|
||||||
comment_saved::id.nullable().is_not_null(),
|
comment_saved::id.nullable().is_not_null(),
|
||||||
person_block::id.nullable().is_not_null(),
|
person_block::id.nullable().is_not_null(),
|
||||||
|
@ -338,7 +354,7 @@ mod tests {
|
||||||
source::{
|
source::{
|
||||||
actor_language::LocalUserLanguage,
|
actor_language::LocalUserLanguage,
|
||||||
comment::{Comment, CommentInsertForm, CommentLike, CommentLikeForm, CommentUpdateForm},
|
comment::{Comment, CommentInsertForm, CommentLike, CommentLikeForm, CommentUpdateForm},
|
||||||
community::{Community, CommunityInsertForm},
|
community::{Community, CommunityInsertForm, CommunityModerator, CommunityModeratorForm},
|
||||||
instance::Instance,
|
instance::Instance,
|
||||||
language::Language,
|
language::Language,
|
||||||
local_user::{LocalUser, LocalUserInsertForm},
|
local_user::{LocalUser, LocalUserInsertForm},
|
||||||
|
@ -346,7 +362,7 @@ mod tests {
|
||||||
person_block::{PersonBlock, PersonBlockForm},
|
person_block::{PersonBlock, PersonBlockForm},
|
||||||
post::{Post, PostInsertForm},
|
post::{Post, PostInsertForm},
|
||||||
},
|
},
|
||||||
traits::{Blockable, Crud, Likeable},
|
traits::{Blockable, Crud, Joinable, Likeable},
|
||||||
utils::build_db_pool_for_tests,
|
utils::build_db_pool_for_tests,
|
||||||
SubscribedType,
|
SubscribedType,
|
||||||
};
|
};
|
||||||
|
@ -779,6 +795,30 @@ mod tests {
|
||||||
cleanup(data, pool).await;
|
cleanup(data, pool).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn test_creator_is_moderator() {
|
||||||
|
let pool = &build_db_pool_for_tests().await;
|
||||||
|
let pool = &mut pool.into();
|
||||||
|
let data = init_data(pool).await;
|
||||||
|
|
||||||
|
// Make one of the inserted persons a moderator
|
||||||
|
let person_id = data.inserted_person_2.id;
|
||||||
|
let community_id = data.inserted_community.id;
|
||||||
|
let form = CommunityModeratorForm {
|
||||||
|
community_id,
|
||||||
|
person_id,
|
||||||
|
};
|
||||||
|
CommunityModerator::join(pool, &form).await.unwrap();
|
||||||
|
|
||||||
|
// Make sure that they come back as a mod in the list
|
||||||
|
let comments = CommentQuery::default().list(pool).await.unwrap();
|
||||||
|
|
||||||
|
assert!(comments[1].creator_is_moderator);
|
||||||
|
|
||||||
|
cleanup(data, pool).await;
|
||||||
|
}
|
||||||
|
|
||||||
async fn cleanup(data: Data, pool: &mut DbPool<'_>) {
|
async fn cleanup(data: Data, pool: &mut DbPool<'_>) {
|
||||||
CommentLike::remove(
|
CommentLike::remove(
|
||||||
pool,
|
pool,
|
||||||
|
@ -814,6 +854,7 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
CommentView {
|
CommentView {
|
||||||
creator_banned_from_community: false,
|
creator_banned_from_community: false,
|
||||||
|
creator_is_moderator: false,
|
||||||
my_vote: None,
|
my_vote: None,
|
||||||
subscribed: SubscribedType::NotSubscribed,
|
subscribed: SubscribedType::NotSubscribed,
|
||||||
saved: false,
|
saved: false,
|
||||||
|
|
|
@ -107,6 +107,13 @@ fn queries<'a>() -> Queries<
|
||||||
.and(community_person_ban::person_id.eq(post_aggregates::creator_id)),
|
.and(community_person_ban::person_id.eq(post_aggregates::creator_id)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
let creator_is_moderator = exists(
|
||||||
|
community_moderator::table.filter(
|
||||||
|
post_aggregates::community_id
|
||||||
|
.eq(community_moderator::community_id)
|
||||||
|
.and(community_moderator::person_id.eq(post_aggregates::creator_id)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
let is_saved = |person_id| {
|
let is_saved = |person_id| {
|
||||||
exists(
|
exists(
|
||||||
|
@ -226,6 +233,7 @@ fn queries<'a>() -> Queries<
|
||||||
person::all_columns,
|
person::all_columns,
|
||||||
community::all_columns,
|
community::all_columns,
|
||||||
is_creator_banned_from_community,
|
is_creator_banned_from_community,
|
||||||
|
creator_is_moderator,
|
||||||
post_aggregates::all_columns,
|
post_aggregates::all_columns,
|
||||||
subscribed_type_selection,
|
subscribed_type_selection,
|
||||||
is_saved_selection,
|
is_saved_selection,
|
||||||
|
@ -542,16 +550,10 @@ impl PostView {
|
||||||
my_person_id: Option<PersonId>,
|
my_person_id: Option<PersonId>,
|
||||||
is_mod_or_admin: bool,
|
is_mod_or_admin: bool,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let mut res = queries()
|
let res = queries()
|
||||||
.read(pool, (post_id, my_person_id, is_mod_or_admin))
|
.read(pool, (post_id, my_person_id, is_mod_or_admin))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// If a person is given, then my_vote, if None, should be 0, not null
|
|
||||||
// Necessary to differentiate between other person's votes
|
|
||||||
if my_person_id.is_some() && res.my_vote.is_none() {
|
|
||||||
res.my_vote = Some(0)
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -711,7 +713,7 @@ mod tests {
|
||||||
newtypes::LanguageId,
|
newtypes::LanguageId,
|
||||||
source::{
|
source::{
|
||||||
actor_language::LocalUserLanguage,
|
actor_language::LocalUserLanguage,
|
||||||
community::{Community, CommunityInsertForm},
|
community::{Community, CommunityInsertForm, CommunityModerator, CommunityModeratorForm},
|
||||||
community_block::{CommunityBlock, CommunityBlockForm},
|
community_block::{CommunityBlock, CommunityBlockForm},
|
||||||
instance::Instance,
|
instance::Instance,
|
||||||
instance_block::{InstanceBlock, InstanceBlockForm},
|
instance_block::{InstanceBlock, InstanceBlockForm},
|
||||||
|
@ -721,7 +723,7 @@ mod tests {
|
||||||
person_block::{PersonBlock, PersonBlockForm},
|
person_block::{PersonBlock, PersonBlockForm},
|
||||||
post::{Post, PostInsertForm, PostLike, PostLikeForm, PostUpdateForm},
|
post::{Post, PostInsertForm, PostLike, PostLikeForm, PostUpdateForm},
|
||||||
},
|
},
|
||||||
traits::{Blockable, Crud, Likeable},
|
traits::{Blockable, Crud, Joinable, Likeable},
|
||||||
utils::{build_db_pool_for_tests, DbPool},
|
utils::{build_db_pool_for_tests, DbPool},
|
||||||
SortType,
|
SortType,
|
||||||
SubscribedType,
|
SubscribedType,
|
||||||
|
@ -877,7 +879,7 @@ mod tests {
|
||||||
assert_eq!(1, read_post_listing.len());
|
assert_eq!(1, read_post_listing.len());
|
||||||
|
|
||||||
assert_eq!(expected_post_listing_with_user, read_post_listing[0]);
|
assert_eq!(expected_post_listing_with_user, read_post_listing[0]);
|
||||||
expected_post_listing_with_user.my_vote = Some(0);
|
expected_post_listing_with_user.my_vote = None;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
expected_post_listing_with_user,
|
expected_post_listing_with_user,
|
||||||
post_listing_single_with_person
|
post_listing_single_with_person
|
||||||
|
@ -1069,6 +1071,36 @@ mod tests {
|
||||||
cleanup(data, pool).await;
|
cleanup(data, pool).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn creator_is_moderator() {
|
||||||
|
let pool = &build_db_pool_for_tests().await;
|
||||||
|
let pool = &mut pool.into();
|
||||||
|
let data = init_data(pool).await;
|
||||||
|
|
||||||
|
// Make one of the inserted persons a moderator
|
||||||
|
let person_id = data.local_user_view.person.id;
|
||||||
|
let community_id = data.inserted_community.id;
|
||||||
|
let form = CommunityModeratorForm {
|
||||||
|
community_id,
|
||||||
|
person_id,
|
||||||
|
};
|
||||||
|
CommunityModerator::join(pool, &form).await.unwrap();
|
||||||
|
|
||||||
|
let post_listing = PostQuery {
|
||||||
|
sort: (Some(SortType::New)),
|
||||||
|
community_id: (Some(data.inserted_community.id)),
|
||||||
|
local_user: (Some(&data.local_user_view)),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.list(pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(post_listing[1].creator_is_moderator);
|
||||||
|
cleanup(data, pool).await;
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn post_listing_person_language() {
|
async fn post_listing_person_language() {
|
||||||
|
@ -1402,6 +1434,7 @@ mod tests {
|
||||||
last_refreshed_at: inserted_person.last_refreshed_at,
|
last_refreshed_at: inserted_person.last_refreshed_at,
|
||||||
},
|
},
|
||||||
creator_banned_from_community: false,
|
creator_banned_from_community: false,
|
||||||
|
creator_is_moderator: false,
|
||||||
community: Community {
|
community: Community {
|
||||||
id: inserted_community.id,
|
id: inserted_community.id,
|
||||||
name: inserted_community.name.clone(),
|
name: inserted_community.name.clone(),
|
||||||
|
|
|
@ -12,7 +12,7 @@ use diesel_async::RunQueryDsl;
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
aliases,
|
aliases,
|
||||||
newtypes::{PersonId, PrivateMessageId},
|
newtypes::{PersonId, PrivateMessageId},
|
||||||
schema::{person, private_message},
|
schema::{person, person_block, private_message},
|
||||||
utils::{get_conn, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn},
|
utils::{get_conn, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn},
|
||||||
};
|
};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
@ -27,6 +27,13 @@ fn queries<'a>() -> Queries<
|
||||||
.inner_join(
|
.inner_join(
|
||||||
aliases::person1.on(private_message::recipient_id.eq(aliases::person1.field(person::id))),
|
aliases::person1.on(private_message::recipient_id.eq(aliases::person1.field(person::id))),
|
||||||
)
|
)
|
||||||
|
.left_join(
|
||||||
|
person_block::table.on(
|
||||||
|
private_message::creator_id
|
||||||
|
.eq(person_block::target_id)
|
||||||
|
.and(person_block::person_id.eq(aliases::person1.field(person::id))),
|
||||||
|
),
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let selection = (
|
let selection = (
|
||||||
|
@ -45,7 +52,10 @@ fn queries<'a>() -> Queries<
|
||||||
|
|
||||||
let list = move |mut conn: DbConn<'a>,
|
let list = move |mut conn: DbConn<'a>,
|
||||||
(options, recipient_id): (PrivateMessageQuery, PersonId)| async move {
|
(options, recipient_id): (PrivateMessageQuery, PersonId)| async move {
|
||||||
let mut query = all_joins(private_message::table.into_boxed()).select(selection);
|
let mut query = all_joins(private_message::table.into_boxed())
|
||||||
|
.select(selection)
|
||||||
|
// Dont show replies from blocked users
|
||||||
|
.filter(person_block::person_id.is_null());
|
||||||
|
|
||||||
// If its unread, I only want the ones to me
|
// If its unread, I only want the ones to me
|
||||||
if options.unread_only {
|
if options.unread_only {
|
||||||
|
@ -106,6 +116,15 @@ impl PrivateMessageView {
|
||||||
use diesel::dsl::count;
|
use diesel::dsl::count;
|
||||||
let conn = &mut get_conn(pool).await?;
|
let conn = &mut get_conn(pool).await?;
|
||||||
private_message::table
|
private_message::table
|
||||||
|
.left_join(
|
||||||
|
person_block::table.on(
|
||||||
|
private_message::creator_id
|
||||||
|
.eq(person_block::target_id)
|
||||||
|
.and(person_block::person_id.eq(my_person_id)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
// Dont count replies from blocked users
|
||||||
|
.filter(person_block::person_id.is_null())
|
||||||
.filter(private_message::read.eq(false))
|
.filter(private_message::read.eq(false))
|
||||||
.filter(private_message::recipient_id.eq(my_person_id))
|
.filter(private_message::recipient_id.eq(my_person_id))
|
||||||
.filter(private_message::deleted.eq(false))
|
.filter(private_message::deleted.eq(false))
|
||||||
|
@ -138,14 +157,15 @@ mod tests {
|
||||||
#![allow(clippy::unwrap_used)]
|
#![allow(clippy::unwrap_used)]
|
||||||
#![allow(clippy::indexing_slicing)]
|
#![allow(clippy::indexing_slicing)]
|
||||||
|
|
||||||
use crate::private_message_view::PrivateMessageQuery;
|
use crate::{private_message_view::PrivateMessageQuery, structs::PrivateMessageView};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
instance::Instance,
|
instance::Instance,
|
||||||
person::{Person, PersonInsertForm},
|
person::{Person, PersonInsertForm},
|
||||||
|
person_block::{PersonBlock, PersonBlockForm},
|
||||||
private_message::{PrivateMessage, PrivateMessageInsertForm},
|
private_message::{PrivateMessage, PrivateMessageInsertForm},
|
||||||
},
|
},
|
||||||
traits::Crud,
|
traits::{Blockable, Crud},
|
||||||
utils::build_db_pool_for_tests,
|
utils::build_db_pool_for_tests,
|
||||||
};
|
};
|
||||||
use serial_test::serial;
|
use serial_test::serial;
|
||||||
|
@ -280,5 +300,39 @@ mod tests {
|
||||||
assert_eq!(timmy_sara_unread_messages.len(), 1);
|
assert_eq!(timmy_sara_unread_messages.len(), 1);
|
||||||
assert_eq!(timmy_sara_unread_messages[0].creator.id, sara.id);
|
assert_eq!(timmy_sara_unread_messages[0].creator.id, sara.id);
|
||||||
assert_eq!(timmy_sara_unread_messages[0].recipient.id, timmy.id);
|
assert_eq!(timmy_sara_unread_messages[0].recipient.id, timmy.id);
|
||||||
|
|
||||||
|
// Make sure blocks are working
|
||||||
|
let timmy_blocks_sara_form = PersonBlockForm {
|
||||||
|
person_id: timmy.id,
|
||||||
|
target_id: sara.id,
|
||||||
|
};
|
||||||
|
|
||||||
|
let inserted_block = PersonBlock::block(pool, &timmy_blocks_sara_form)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let expected_block = PersonBlock {
|
||||||
|
id: inserted_block.id,
|
||||||
|
person_id: timmy.id,
|
||||||
|
target_id: sara.id,
|
||||||
|
published: inserted_block.published,
|
||||||
|
};
|
||||||
|
assert_eq!(expected_block, inserted_block);
|
||||||
|
|
||||||
|
let timmy_messages = PrivateMessageQuery {
|
||||||
|
unread_only: true,
|
||||||
|
creator_id: Option::None,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.list(pool, timmy.id)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(timmy_messages.len(), 1);
|
||||||
|
|
||||||
|
let timmy_unread_messages = PrivateMessageView::get_unread_messages(pool, timmy.id)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(timmy_unread_messages, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,7 @@ pub struct CommentView {
|
||||||
pub community: Community,
|
pub community: Community,
|
||||||
pub counts: CommentAggregates,
|
pub counts: CommentAggregates,
|
||||||
pub creator_banned_from_community: bool,
|
pub creator_banned_from_community: bool,
|
||||||
|
pub creator_is_moderator: bool,
|
||||||
pub subscribed: SubscribedType,
|
pub subscribed: SubscribedType,
|
||||||
pub saved: bool,
|
pub saved: bool,
|
||||||
pub creator_blocked: bool,
|
pub creator_blocked: bool,
|
||||||
|
@ -107,6 +108,7 @@ pub struct PostView {
|
||||||
pub creator: Person,
|
pub creator: Person,
|
||||||
pub community: Community,
|
pub community: Community,
|
||||||
pub creator_banned_from_community: bool,
|
pub creator_banned_from_community: bool,
|
||||||
|
pub creator_is_moderator: bool,
|
||||||
pub counts: PostAggregates,
|
pub counts: PostAggregates,
|
||||||
pub subscribed: SubscribedType,
|
pub subscribed: SubscribedType,
|
||||||
pub saved: bool,
|
pub saved: bool,
|
||||||
|
|
|
@ -31,3 +31,7 @@ ts-rs = { workspace = true, optional = true }
|
||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
strum = { workspace = true }
|
strum = { workspace = true }
|
||||||
strum_macros = { workspace = true }
|
strum_macros = { workspace = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
serial_test = { workspace = true }
|
||||||
|
tokio = { workspace = true }
|
||||||
|
|
|
@ -164,6 +164,15 @@ impl CommentReplyView {
|
||||||
|
|
||||||
comment_reply::table
|
comment_reply::table
|
||||||
.inner_join(comment::table)
|
.inner_join(comment::table)
|
||||||
|
.left_join(
|
||||||
|
person_block::table.on(
|
||||||
|
comment::creator_id
|
||||||
|
.eq(person_block::target_id)
|
||||||
|
.and(person_block::person_id.eq(my_person_id)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
// Dont count replies from blocked users
|
||||||
|
.filter(person_block::person_id.is_null())
|
||||||
.filter(comment_reply::recipient_id.eq(my_person_id))
|
.filter(comment_reply::recipient_id.eq(my_person_id))
|
||||||
.filter(comment_reply::read.eq(false))
|
.filter(comment_reply::read.eq(false))
|
||||||
.filter(comment::deleted.eq(false))
|
.filter(comment::deleted.eq(false))
|
||||||
|
|
|
@ -52,6 +52,7 @@ fn queries<'a>(
|
||||||
query
|
query
|
||||||
.inner_join(person_aggregates::table)
|
.inner_join(person_aggregates::table)
|
||||||
.left_join(local_user::table)
|
.left_join(local_user::table)
|
||||||
|
.filter(person::deleted.eq(false))
|
||||||
.select((person::all_columns, person_aggregates::all_columns))
|
.select((person::all_columns, person_aggregates::all_columns))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -151,3 +152,165 @@ impl PersonQuery {
|
||||||
queries().list(pool, ListMode::Query(self)).await
|
queries().list(pool, ListMode::Query(self)).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#![allow(clippy::unwrap_used)]
|
||||||
|
#![allow(clippy::indexing_slicing)]
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use diesel::NotFound;
|
||||||
|
use lemmy_db_schema::{
|
||||||
|
source::{
|
||||||
|
instance::Instance,
|
||||||
|
local_user::{LocalUser, LocalUserInsertForm, LocalUserUpdateForm},
|
||||||
|
person::{Person, PersonInsertForm, PersonUpdateForm},
|
||||||
|
},
|
||||||
|
traits::Crud,
|
||||||
|
utils::build_db_pool_for_tests,
|
||||||
|
};
|
||||||
|
use serial_test::serial;
|
||||||
|
|
||||||
|
struct Data {
|
||||||
|
alice: Person,
|
||||||
|
alice_local_user: LocalUser,
|
||||||
|
bob: Person,
|
||||||
|
bob_local_user: LocalUser,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn init_data(pool: &mut DbPool<'_>) -> Data {
|
||||||
|
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let alice_form = PersonInsertForm::builder()
|
||||||
|
.name("alice".to_string())
|
||||||
|
.public_key("pubkey".to_string())
|
||||||
|
.instance_id(inserted_instance.id)
|
||||||
|
.build();
|
||||||
|
let alice = Person::create(pool, &alice_form).await.unwrap();
|
||||||
|
let alice_local_user_form = LocalUserInsertForm::builder()
|
||||||
|
.person_id(alice.id)
|
||||||
|
.password_encrypted(String::new())
|
||||||
|
.build();
|
||||||
|
let alice_local_user = LocalUser::create(pool, &alice_local_user_form)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let bob_form = PersonInsertForm::builder()
|
||||||
|
.name("bob".to_string())
|
||||||
|
.bot_account(Some(true))
|
||||||
|
.public_key("pubkey".to_string())
|
||||||
|
.instance_id(inserted_instance.id)
|
||||||
|
.build();
|
||||||
|
let bob = Person::create(pool, &bob_form).await.unwrap();
|
||||||
|
let bob_local_user_form = LocalUserInsertForm::builder()
|
||||||
|
.person_id(bob.id)
|
||||||
|
.password_encrypted(String::new())
|
||||||
|
.build();
|
||||||
|
let bob_local_user = LocalUser::create(pool, &bob_local_user_form).await.unwrap();
|
||||||
|
|
||||||
|
Data {
|
||||||
|
alice,
|
||||||
|
alice_local_user,
|
||||||
|
bob,
|
||||||
|
bob_local_user,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn cleanup(data: Data, pool: &mut DbPool<'_>) {
|
||||||
|
LocalUser::delete(pool, data.alice_local_user.id)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
LocalUser::delete(pool, data.bob_local_user.id)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
Person::delete(pool, data.alice.id).await.unwrap();
|
||||||
|
Person::delete(pool, data.bob.id).await.unwrap();
|
||||||
|
Instance::delete(pool, data.bob.instance_id).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn exclude_deleted() {
|
||||||
|
let pool = &build_db_pool_for_tests().await;
|
||||||
|
let pool = &mut pool.into();
|
||||||
|
let data = init_data(pool).await;
|
||||||
|
|
||||||
|
Person::update(
|
||||||
|
pool,
|
||||||
|
data.alice.id,
|
||||||
|
&PersonUpdateForm {
|
||||||
|
deleted: Some(true),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let read = PersonView::read(pool, data.alice.id).await;
|
||||||
|
assert_eq!(read.err(), Some(NotFound));
|
||||||
|
|
||||||
|
let list = PersonQuery::default().list(pool).await.unwrap();
|
||||||
|
assert_eq!(list.len(), 1);
|
||||||
|
assert_eq!(list[0].person.id, data.bob.id);
|
||||||
|
|
||||||
|
cleanup(data, pool).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn list_banned() {
|
||||||
|
let pool = &build_db_pool_for_tests().await;
|
||||||
|
let pool = &mut pool.into();
|
||||||
|
let data = init_data(pool).await;
|
||||||
|
|
||||||
|
Person::update(
|
||||||
|
pool,
|
||||||
|
data.alice.id,
|
||||||
|
&PersonUpdateForm {
|
||||||
|
banned: Some(true),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let list = PersonView::banned(pool).await.unwrap();
|
||||||
|
assert_eq!(list.len(), 1);
|
||||||
|
assert_eq!(list[0].person.id, data.alice.id);
|
||||||
|
|
||||||
|
cleanup(data, pool).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn list_admins() {
|
||||||
|
let pool = &build_db_pool_for_tests().await;
|
||||||
|
let pool = &mut pool.into();
|
||||||
|
let data = init_data(pool).await;
|
||||||
|
|
||||||
|
LocalUser::update(
|
||||||
|
pool,
|
||||||
|
data.alice_local_user.id,
|
||||||
|
&LocalUserUpdateForm {
|
||||||
|
admin: Some(true),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let list = PersonView::admins(pool).await.unwrap();
|
||||||
|
assert_eq!(list.len(), 1);
|
||||||
|
assert_eq!(list[0].person.id, data.alice.id);
|
||||||
|
|
||||||
|
let is_admin = PersonView::is_admin(pool, data.alice.id).await.unwrap();
|
||||||
|
assert!(is_admin);
|
||||||
|
|
||||||
|
let is_admin = PersonView::is_admin(pool, data.bob.id).await.unwrap();
|
||||||
|
assert!(!is_admin);
|
||||||
|
|
||||||
|
cleanup(data, pool).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -30,12 +30,12 @@ serde.workspace = true
|
||||||
tokio = { workspace = true, features = ["full"] }
|
tokio = { workspace = true, features = ["full"] }
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
|
|
||||||
async-trait = "0.1.71"
|
async-trait = "0.1.74"
|
||||||
bytes = "1.4.0"
|
bytes = "1.5.0"
|
||||||
enum_delegate = "0.2.0"
|
enum_delegate = "0.2.0"
|
||||||
moka = { version = "0.11.2", features = ["future"] }
|
moka = { version = "0.11.3", features = ["future"] }
|
||||||
openssl = "0.10.55"
|
openssl = "0.10.57"
|
||||||
reqwest-middleware = "0.2.2"
|
reqwest-middleware = "0.2.4"
|
||||||
reqwest-tracing = "0.4.5"
|
reqwest-tracing = "0.4.6"
|
||||||
tokio-util = "0.7.8"
|
tokio-util = "0.7.9"
|
||||||
tracing-subscriber = "0.3.17"
|
tracing-subscriber = "0.3.17"
|
||||||
|
|
|
@ -31,4 +31,4 @@ once_cell = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
urlencoding = { workspace = true }
|
urlencoding = { workspace = true }
|
||||||
rss = "2.0.4"
|
rss = "2.0.6"
|
||||||
|
|
|
@ -2,7 +2,7 @@ use crate::local_user_view_from_jwt;
|
||||||
use actix_web::{error::ErrorBadRequest, web, Error, HttpRequest, HttpResponse, Result};
|
use actix_web::{error::ErrorBadRequest, web, Error, HttpRequest, HttpResponse, Result};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use lemmy_api_common::context::LemmyContext;
|
use lemmy_api_common::{context::LemmyContext, utils::check_private_instance};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{community::Community, person::Person},
|
source::{community::Community, person::Person},
|
||||||
traits::ApubActor,
|
traits::ApubActor,
|
||||||
|
@ -132,6 +132,8 @@ async fn get_feed_data(
|
||||||
) -> Result<HttpResponse, LemmyError> {
|
) -> Result<HttpResponse, LemmyError> {
|
||||||
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||||
|
|
||||||
|
check_private_instance(&None, &site_view.local_site)?;
|
||||||
|
|
||||||
let posts = PostQuery {
|
let posts = PostQuery {
|
||||||
listing_type: (Some(listing_type)),
|
listing_type: (Some(listing_type)),
|
||||||
sort: (Some(sort_type)),
|
sort: (Some(sort_type)),
|
||||||
|
@ -235,6 +237,8 @@ async fn get_feed_user(
|
||||||
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||||
let person = Person::read_from_name(&mut context.pool(), user_name, false).await?;
|
let person = Person::read_from_name(&mut context.pool(), user_name, false).await?;
|
||||||
|
|
||||||
|
check_private_instance(&None, &site_view.local_site)?;
|
||||||
|
|
||||||
let posts = PostQuery {
|
let posts = PostQuery {
|
||||||
listing_type: (Some(ListingType::All)),
|
listing_type: (Some(ListingType::All)),
|
||||||
sort: (Some(*sort_type)),
|
sort: (Some(*sort_type)),
|
||||||
|
@ -269,6 +273,8 @@ async fn get_feed_community(
|
||||||
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||||
let community = Community::read_from_name(&mut context.pool(), community_name, false).await?;
|
let community = Community::read_from_name(&mut context.pool(), community_name, false).await?;
|
||||||
|
|
||||||
|
check_private_instance(&None, &site_view.local_site)?;
|
||||||
|
|
||||||
let posts = PostQuery {
|
let posts = PostQuery {
|
||||||
sort: (Some(*sort_type)),
|
sort: (Some(*sort_type)),
|
||||||
community_id: (Some(community.id)),
|
community_id: (Some(community.id)),
|
||||||
|
@ -306,6 +312,8 @@ async fn get_feed_front(
|
||||||
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||||
let local_user = local_user_view_from_jwt(jwt, context).await?;
|
let local_user = local_user_view_from_jwt(jwt, context).await?;
|
||||||
|
|
||||||
|
check_private_instance(&Some(local_user.clone()), &site_view.local_site)?;
|
||||||
|
|
||||||
let posts = PostQuery {
|
let posts = PostQuery {
|
||||||
listing_type: (Some(ListingType::Subscribed)),
|
listing_type: (Some(ListingType::Subscribed)),
|
||||||
local_user: (Some(&local_user)),
|
local_user: (Some(&local_user)),
|
||||||
|
@ -343,6 +351,8 @@ async fn get_feed_inbox(context: &LemmyContext, jwt: &str) -> Result<ChannelBuil
|
||||||
|
|
||||||
let sort = CommentSortType::New;
|
let sort = CommentSortType::New;
|
||||||
|
|
||||||
|
check_private_instance(&Some(local_user.clone()), &site_view.local_site)?;
|
||||||
|
|
||||||
let replies = CommentReplyQuery {
|
let replies = CommentReplyQuery {
|
||||||
recipient_id: (Some(person_id)),
|
recipient_id: (Some(person_id)),
|
||||||
my_person_id: (Some(person_id)),
|
my_person_id: (Some(person_id)),
|
||||||
|
|
|
@ -23,8 +23,13 @@ use reqwest_middleware::{ClientWithMiddleware, RequestBuilder};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) {
|
pub fn config(
|
||||||
|
cfg: &mut web::ServiceConfig,
|
||||||
|
client: ClientWithMiddleware,
|
||||||
|
rate_limit: &RateLimitCell,
|
||||||
|
) {
|
||||||
cfg
|
cfg
|
||||||
|
.app_data(web::Data::new(client))
|
||||||
.service(
|
.service(
|
||||||
web::resource("/pictrs/image")
|
web::resource("/pictrs/image")
|
||||||
.wrap(rate_limit.image())
|
.wrap(rate_limit.image())
|
||||||
|
@ -90,13 +95,14 @@ async fn upload(
|
||||||
body: web::Payload,
|
body: web::Payload,
|
||||||
// require login
|
// require login
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
|
client: web::Data<ClientWithMiddleware>,
|
||||||
context: web::Data<LemmyContext>,
|
context: web::Data<LemmyContext>,
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
// TODO: check rate limit here
|
// TODO: check rate limit here
|
||||||
let pictrs_config = context.settings().pictrs_config()?;
|
let pictrs_config = context.settings().pictrs_config()?;
|
||||||
let image_url = format!("{}image", pictrs_config.url);
|
let image_url = format!("{}image", pictrs_config.url);
|
||||||
|
|
||||||
let mut client_req = adapt_request(&req, context.client(), image_url);
|
let mut client_req = adapt_request(&req, &client, image_url);
|
||||||
|
|
||||||
if let Some(addr) = req.head().peer_addr {
|
if let Some(addr) = req.head().peer_addr {
|
||||||
client_req = client_req.header("X-Forwarded-For", addr.to_string())
|
client_req = client_req.header("X-Forwarded-For", addr.to_string())
|
||||||
|
@ -130,6 +136,7 @@ async fn full_res(
|
||||||
filename: web::Path<String>,
|
filename: web::Path<String>,
|
||||||
web::Query(params): web::Query<PictrsParams>,
|
web::Query(params): web::Query<PictrsParams>,
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
|
client: web::Data<ClientWithMiddleware>,
|
||||||
context: web::Data<LemmyContext>,
|
context: web::Data<LemmyContext>,
|
||||||
local_user_view: Option<LocalUserView>,
|
local_user_view: Option<LocalUserView>,
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
|
@ -160,7 +167,7 @@ async fn full_res(
|
||||||
url
|
url
|
||||||
};
|
};
|
||||||
|
|
||||||
image(url, req, context.client()).await
|
image(url, req, &client).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn image(
|
async fn image(
|
||||||
|
@ -196,6 +203,7 @@ async fn image(
|
||||||
async fn delete(
|
async fn delete(
|
||||||
components: web::Path<(String, String)>,
|
components: web::Path<(String, String)>,
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
|
client: web::Data<ClientWithMiddleware>,
|
||||||
context: web::Data<LemmyContext>,
|
context: web::Data<LemmyContext>,
|
||||||
// require login
|
// require login
|
||||||
_local_user_view: LocalUserView,
|
_local_user_view: LocalUserView,
|
||||||
|
@ -205,7 +213,7 @@ async fn delete(
|
||||||
let pictrs_config = context.settings().pictrs_config()?;
|
let pictrs_config = context.settings().pictrs_config()?;
|
||||||
let url = format!("{}image/delete/{}/{}", pictrs_config.url, &token, &file);
|
let url = format!("{}image/delete/{}/{}", pictrs_config.url, &token, &file);
|
||||||
|
|
||||||
let mut client_req = adapt_request(&req, context.client(), url);
|
let mut client_req = adapt_request(&req, &client, url);
|
||||||
|
|
||||||
if let Some(addr) = req.head().peer_addr {
|
if let Some(addr) = req.head().peer_addr {
|
||||||
client_req = client_req.header("X-Forwarded-For", addr.to_string());
|
client_req = client_req.header("X-Forwarded-For", addr.to_string());
|
||||||
|
|
|
@ -17,6 +17,7 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||||
"/nodeinfo/2.0.json",
|
"/nodeinfo/2.0.json",
|
||||||
web::get().to(node_info).wrap(cache_1hour()),
|
web::get().to(node_info).wrap(cache_1hour()),
|
||||||
)
|
)
|
||||||
|
.service(web::redirect("/version", "/nodeinfo/2.0.json"))
|
||||||
.route(
|
.route(
|
||||||
"/.well-known/nodeinfo",
|
"/.well-known/nodeinfo",
|
||||||
web::get().to(node_info_well_known).wrap(cache_3days()),
|
web::get().to(node_info_well_known).wrap(cache_3days()),
|
||||||
|
|
|
@ -41,7 +41,7 @@ typed-builder = { workspace = true }
|
||||||
percent-encoding = { workspace = true }
|
percent-encoding = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
urlencoding = { workspace = true }
|
urlencoding = { workspace = true }
|
||||||
openssl = "0.10.55"
|
openssl = "0.10.57"
|
||||||
html2text = "0.6.0"
|
html2text = "0.6.0"
|
||||||
deser-hjson = "1.2.0"
|
deser-hjson = "1.2.0"
|
||||||
smart-default = "0.7.1"
|
smart-default = "0.7.1"
|
||||||
|
|
|
@ -56,6 +56,9 @@ impl Display for LemmyError {
|
||||||
|
|
||||||
impl actix_web::error::ResponseError for LemmyError {
|
impl actix_web::error::ResponseError for LemmyError {
|
||||||
fn status_code(&self) -> http::StatusCode {
|
fn status_code(&self) -> http::StatusCode {
|
||||||
|
if self.error_type == LemmyErrorType::IncorrectLogin {
|
||||||
|
return http::StatusCode::UNAUTHORIZED;
|
||||||
|
}
|
||||||
match self.inner.downcast_ref::<diesel::result::Error>() {
|
match self.inner.downcast_ref::<diesel::result::Error>() {
|
||||||
Some(diesel::result::Error::NotFound) => http::StatusCode::NOT_FOUND,
|
Some(diesel::result::Error::NotFound) => http::StatusCode::NOT_FOUND,
|
||||||
_ => http::StatusCode::BAD_REQUEST,
|
_ => http::StatusCode::BAD_REQUEST,
|
||||||
|
|
|
@ -4,18 +4,20 @@ use once_cell::sync::Lazy;
|
||||||
use regex::{Regex, RegexBuilder};
|
use regex::{Regex, RegexBuilder};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
static VALID_ACTOR_NAME_REGEX: Lazy<Regex> =
|
|
||||||
Lazy::new(|| Regex::new(r"^[a-zA-Z0-9_]{3,}$").expect("compile regex"));
|
|
||||||
static VALID_POST_TITLE_REGEX: Lazy<Regex> =
|
static VALID_POST_TITLE_REGEX: Lazy<Regex> =
|
||||||
Lazy::new(|| Regex::new(r".*\S{3,200}.*").expect("compile regex"));
|
Lazy::new(|| Regex::new(r".*\S{3,200}.*").expect("compile regex"));
|
||||||
|
|
||||||
|
// From here: https://github.com/vector-im/element-android/blob/develop/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt#L35
|
||||||
static VALID_MATRIX_ID_REGEX: Lazy<Regex> = Lazy::new(|| {
|
static VALID_MATRIX_ID_REGEX: Lazy<Regex> = Lazy::new(|| {
|
||||||
Regex::new(r"^@[A-Za-z0-9._=-]+:[A-Za-z0-9.-]+\.[A-Za-z]{2,}$").expect("compile regex")
|
Regex::new(r"^@[A-Za-z0-9\\x21-\\x39\\x3B-\\x7F]+:[A-Za-z0-9.-]+(:[0-9]{2,5})?$")
|
||||||
|
.expect("compile regex")
|
||||||
});
|
});
|
||||||
// taken from https://en.wikipedia.org/wiki/UTM_parameters
|
// taken from https://en.wikipedia.org/wiki/UTM_parameters
|
||||||
static CLEAN_URL_PARAMS_REGEX: Lazy<Regex> = Lazy::new(|| {
|
static CLEAN_URL_PARAMS_REGEX: Lazy<Regex> = Lazy::new(|| {
|
||||||
Regex::new(r"^utm_source|utm_medium|utm_campaign|utm_term|utm_content|gclid|gclsrc|dclid|fbclid$")
|
Regex::new(r"^utm_source|utm_medium|utm_campaign|utm_term|utm_content|gclid|gclsrc|dclid|fbclid$")
|
||||||
.expect("compile regex")
|
.expect("compile regex")
|
||||||
});
|
});
|
||||||
|
const ALLOWED_POST_URL_SCHEMES: [&str; 3] = ["http", "https", "magnet"];
|
||||||
|
|
||||||
const BODY_MAX_LENGTH: usize = 10000;
|
const BODY_MAX_LENGTH: usize = 10000;
|
||||||
const POST_BODY_MAX_LENGTH: usize = 50000;
|
const POST_BODY_MAX_LENGTH: usize = 50000;
|
||||||
|
@ -85,23 +87,52 @@ fn has_newline(name: &str) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_valid_actor_name(name: &str, actor_name_max_length: usize) -> LemmyResult<()> {
|
pub fn is_valid_actor_name(name: &str, actor_name_max_length: usize) -> LemmyResult<()> {
|
||||||
let check = name.chars().count() <= actor_name_max_length
|
static VALID_ACTOR_NAME_REGEX_EN: Lazy<Regex> =
|
||||||
&& VALID_ACTOR_NAME_REGEX.is_match(name)
|
Lazy::new(|| Regex::new(r"^[a-zA-Z0-9_]{3,}$").expect("compile regex"));
|
||||||
&& !has_newline(name);
|
static VALID_ACTOR_NAME_REGEX_AR: Lazy<Regex> =
|
||||||
if !check {
|
Lazy::new(|| Regex::new(r"^[\p{Arabic}0-9_]{3,}$").expect("compile regex"));
|
||||||
|
static VALID_ACTOR_NAME_REGEX_RU: Lazy<Regex> =
|
||||||
|
Lazy::new(|| Regex::new(r"^[\p{Cyrillic}0-9_]{3,}$").expect("compile regex"));
|
||||||
|
|
||||||
|
let check = name.chars().count() <= actor_name_max_length && !has_newline(name);
|
||||||
|
|
||||||
|
// Only allow characters from a single alphabet per username. This avoids problems with lookalike
|
||||||
|
// characters like `o` which looks identical in Latin and Cyrillic, and can be used to imitate
|
||||||
|
// other users. Checks for additional alphabets can be added in the same way.
|
||||||
|
let lang_check = VALID_ACTOR_NAME_REGEX_EN.is_match(name)
|
||||||
|
|| VALID_ACTOR_NAME_REGEX_AR.is_match(name)
|
||||||
|
|| VALID_ACTOR_NAME_REGEX_RU.is_match(name);
|
||||||
|
|
||||||
|
if !check || !lang_check {
|
||||||
Err(LemmyErrorType::InvalidName.into())
|
Err(LemmyErrorType::InvalidName.into())
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn has_3_permitted_display_chars(name: &str) -> bool {
|
||||||
|
let mut num_non_fdc: i8 = 0;
|
||||||
|
for c in name.chars() {
|
||||||
|
if !FORBIDDEN_DISPLAY_CHARS.contains(&c) {
|
||||||
|
num_non_fdc += 1;
|
||||||
|
if num_non_fdc >= 3 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if num_non_fdc >= 3 {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
// Can't do a regex here, reverse lookarounds not supported
|
// Can't do a regex here, reverse lookarounds not supported
|
||||||
pub fn is_valid_display_name(name: &str, actor_name_max_length: usize) -> LemmyResult<()> {
|
pub fn is_valid_display_name(name: &str, actor_name_max_length: usize) -> LemmyResult<()> {
|
||||||
let check = !name.contains(FORBIDDEN_DISPLAY_CHARS)
|
let check = !name.starts_with('@')
|
||||||
&& !name.starts_with('@')
|
&& !name.starts_with(FORBIDDEN_DISPLAY_CHARS)
|
||||||
&& name.chars().count() >= 3
|
|
||||||
&& name.chars().count() <= actor_name_max_length
|
&& name.chars().count() <= actor_name_max_length
|
||||||
&& !has_newline(name);
|
&& !has_newline(name)
|
||||||
|
&& has_3_permitted_display_chars(name);
|
||||||
if !check {
|
if !check {
|
||||||
Err(LemmyErrorType::InvalidDisplayName.into())
|
Err(LemmyErrorType::InvalidDisplayName.into())
|
||||||
} else {
|
} else {
|
||||||
|
@ -247,7 +278,7 @@ pub fn check_site_visibility_valid(
|
||||||
|
|
||||||
pub fn check_url_scheme(url: &Option<Url>) -> LemmyResult<()> {
|
pub fn check_url_scheme(url: &Option<Url>) -> LemmyResult<()> {
|
||||||
if let Some(url) = url {
|
if let Some(url) = url {
|
||||||
if url.scheme() != "http" && url.scheme() != "https" {
|
if !ALLOWED_POST_URL_SCHEMES.contains(&url.scheme()) {
|
||||||
Err(LemmyErrorType::InvalidUrlScheme.into())
|
Err(LemmyErrorType::InvalidUrlScheme.into())
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -309,8 +340,18 @@ mod tests {
|
||||||
let actor_name_max_length = 20;
|
let actor_name_max_length = 20;
|
||||||
assert!(is_valid_actor_name("Hello_98", actor_name_max_length).is_ok());
|
assert!(is_valid_actor_name("Hello_98", actor_name_max_length).is_ok());
|
||||||
assert!(is_valid_actor_name("ten", actor_name_max_length).is_ok());
|
assert!(is_valid_actor_name("ten", actor_name_max_length).is_ok());
|
||||||
|
assert!(is_valid_actor_name("تجريب", actor_name_max_length).is_ok());
|
||||||
|
assert!(is_valid_actor_name("تجريب_123", actor_name_max_length).is_ok());
|
||||||
|
assert!(is_valid_actor_name("Владимир", actor_name_max_length).is_ok());
|
||||||
|
|
||||||
|
// mixed scripts
|
||||||
|
assert!(is_valid_actor_name("تجريب_abc", actor_name_max_length).is_err());
|
||||||
|
assert!(is_valid_actor_name("Влад_abc", actor_name_max_length).is_err());
|
||||||
|
// dash
|
||||||
assert!(is_valid_actor_name("Hello-98", actor_name_max_length).is_err());
|
assert!(is_valid_actor_name("Hello-98", actor_name_max_length).is_err());
|
||||||
|
// too short
|
||||||
assert!(is_valid_actor_name("a", actor_name_max_length).is_err());
|
assert!(is_valid_actor_name("a", actor_name_max_length).is_err());
|
||||||
|
// empty
|
||||||
assert!(is_valid_actor_name("", actor_name_max_length).is_err());
|
assert!(is_valid_actor_name("", actor_name_max_length).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -319,6 +360,13 @@ mod tests {
|
||||||
let actor_name_max_length = 20;
|
let actor_name_max_length = 20;
|
||||||
assert!(is_valid_display_name("hello @there", actor_name_max_length).is_ok());
|
assert!(is_valid_display_name("hello @there", actor_name_max_length).is_ok());
|
||||||
assert!(is_valid_display_name("@hello there", actor_name_max_length).is_err());
|
assert!(is_valid_display_name("@hello there", actor_name_max_length).is_err());
|
||||||
|
assert!(is_valid_display_name("\u{200d}hello", actor_name_max_length).is_err());
|
||||||
|
assert!(is_valid_display_name(
|
||||||
|
"\u{1f3f3}\u{fe0f}\u{200d}\u{26a7}\u{fe0f}Name",
|
||||||
|
actor_name_max_length
|
||||||
|
)
|
||||||
|
.is_ok());
|
||||||
|
assert!(is_valid_display_name("\u{2003}1\u{ffa0}2\u{200d}", actor_name_max_length).is_err());
|
||||||
|
|
||||||
// Make sure zero-space with an @ doesn't work
|
// Make sure zero-space with an @ doesn't work
|
||||||
assert!(
|
assert!(
|
||||||
|
@ -336,9 +384,11 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_valid_matrix_id() {
|
fn test_valid_matrix_id() {
|
||||||
assert!(is_valid_matrix_id("@dess:matrix.org").is_ok());
|
assert!(is_valid_matrix_id("@dess:matrix.org").is_ok());
|
||||||
|
assert!(is_valid_matrix_id("@dess:matrix.org:443").is_ok());
|
||||||
assert!(is_valid_matrix_id("dess:matrix.org").is_err());
|
assert!(is_valid_matrix_id("dess:matrix.org").is_err());
|
||||||
assert!(is_valid_matrix_id(" @dess:matrix.org").is_err());
|
assert!(is_valid_matrix_id(" @dess:matrix.org").is_err());
|
||||||
assert!(is_valid_matrix_id("@dess:matrix.org t").is_err());
|
assert!(is_valid_matrix_id("@dess:matrix.org t").is_err());
|
||||||
|
assert!(is_valid_matrix_id("@dess:matrix.org t").is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -472,7 +522,11 @@ mod tests {
|
||||||
assert!(check_url_scheme(&None).is_ok());
|
assert!(check_url_scheme(&None).is_ok());
|
||||||
assert!(check_url_scheme(&Some(Url::parse("http://example.com").unwrap())).is_ok());
|
assert!(check_url_scheme(&Some(Url::parse("http://example.com").unwrap())).is_ok());
|
||||||
assert!(check_url_scheme(&Some(Url::parse("https://example.com").unwrap())).is_ok());
|
assert!(check_url_scheme(&Some(Url::parse("https://example.com").unwrap())).is_ok());
|
||||||
|
assert!(check_url_scheme(&Some(Url::parse("https://example.com").unwrap())).is_ok());
|
||||||
assert!(check_url_scheme(&Some(Url::parse("ftp://example.com").unwrap())).is_err());
|
assert!(check_url_scheme(&Some(Url::parse("ftp://example.com").unwrap())).is_err());
|
||||||
assert!(check_url_scheme(&Some(Url::parse("javascript:void").unwrap())).is_err());
|
assert!(check_url_scheme(&Some(Url::parse("javascript:void").unwrap())).is_err());
|
||||||
|
|
||||||
|
let magnet_link="magnet:?xt=urn:btih:4b390af3891e323778959d5abfff4b726510f14c&dn=Ravel%20Complete%20Piano%20Sheet%20Music%20-%20Public%20Domain&tr=udp%3A%2F%2Fopen.tracker.cl%3A1337%2Fannounce";
|
||||||
|
assert!(check_url_scheme(&Some(Url::parse(magnet_link).unwrap())).is_ok());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ services:
|
||||||
|
|
||||||
lemmy-ui:
|
lemmy-ui:
|
||||||
# use "image" to pull down an already compiled lemmy-ui. make sure to comment out "build".
|
# use "image" to pull down an already compiled lemmy-ui. make sure to comment out "build".
|
||||||
image: dessalines/lemmy-ui:0.18.4
|
image: dessalines/lemmy-ui:0.19.0-rc.3
|
||||||
# platform: linux/x86_64 # no arm64 support. uncomment platform if using m1.
|
# platform: linux/x86_64 # no arm64 support. uncomment platform if using m1.
|
||||||
# use "build" to build your local lemmy ui image for development. make sure to comment out "image".
|
# use "build" to build your local lemmy ui image for development. make sure to comment out "image".
|
||||||
# run: docker compose up --build
|
# run: docker compose up --build
|
||||||
|
|
|
@ -45,9 +45,9 @@ if [ "$ARCH" = 'arm64' ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "$LOG_PREFIX Initializing images in the background. Please be patient if compiling from source..."
|
echo "$LOG_PREFIX Initializing images in the background. Please be patient if compiling from source..."
|
||||||
docker compose up -d --build
|
docker compose up --build
|
||||||
else
|
else
|
||||||
sudo docker compose up -d --build
|
sudo docker compose up --build
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "$LOG_PREFIX Complete! You can now access the UI at http://localhost:1236."
|
echo "$LOG_PREFIX Complete! You can now access the UI at http://localhost:1236."
|
||||||
|
|
|
@ -56,7 +56,7 @@ http {
|
||||||
}
|
}
|
||||||
|
|
||||||
# backend
|
# backend
|
||||||
location ~ ^/(api|pictrs|feeds|nodeinfo|.well-known) {
|
location ~ ^/(api|pictrs|feeds|nodeinfo|version|.well-known) {
|
||||||
proxy_pass "http://lemmy";
|
proxy_pass "http://lemmy";
|
||||||
# proxy common stuff
|
# proxy common stuff
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
|
|
48
src/lib.rs
48
src/lib.rs
|
@ -28,7 +28,7 @@ use clap::{ArgAction, Parser};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
lemmy_db_views::structs::SiteView,
|
lemmy_db_views::structs::SiteView,
|
||||||
request::build_user_agent,
|
request::client_builder,
|
||||||
send_activity::{ActivityChannel, MATCH_OUTGOING_ACTIVITIES},
|
send_activity::{ActivityChannel, MATCH_OUTGOING_ACTIVITIES},
|
||||||
utils::{
|
utils::{
|
||||||
check_private_instance_and_federation_enabled,
|
check_private_instance_and_federation_enabled,
|
||||||
|
@ -52,11 +52,10 @@ use lemmy_utils::{
|
||||||
response::jsonify_plain_text_errors,
|
response::jsonify_plain_text_errors,
|
||||||
settings::{structs::Settings, SETTINGS},
|
settings::{structs::Settings, SETTINGS},
|
||||||
};
|
};
|
||||||
use reqwest::Client;
|
|
||||||
use reqwest_middleware::ClientBuilder;
|
use reqwest_middleware::ClientBuilder;
|
||||||
use reqwest_tracing::TracingMiddleware;
|
use reqwest_tracing::TracingMiddleware;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::{env, ops::Deref, time::Duration};
|
use std::{env, ops::Deref};
|
||||||
use tokio::signal::unix::SignalKind;
|
use tokio::signal::unix::SignalKind;
|
||||||
use tracing::subscriber::set_global_default;
|
use tracing::subscriber::set_global_default;
|
||||||
use tracing_actix_web::TracingLogger;
|
use tracing_actix_web::TracingLogger;
|
||||||
|
@ -112,13 +111,9 @@ pub struct CmdArgs {
|
||||||
#[arg(long, default_value_t = 1)]
|
#[arg(long, default_value_t = 1)]
|
||||||
federate_process_count: i32,
|
federate_process_count: i32,
|
||||||
}
|
}
|
||||||
/// Max timeout for http requests
|
|
||||||
pub(crate) const REQWEST_TIMEOUT: Duration = Duration::from_secs(10);
|
|
||||||
|
|
||||||
/// Placing the main function in lib.rs allows other crates to import it and embed Lemmy
|
/// Placing the main function in lib.rs allows other crates to import it and embed Lemmy
|
||||||
pub async fn start_lemmy_server(args: CmdArgs) -> Result<(), LemmyError> {
|
pub async fn start_lemmy_server(args: CmdArgs) -> Result<(), LemmyError> {
|
||||||
let settings = SETTINGS.to_owned();
|
|
||||||
|
|
||||||
// return error 503 while running db migrations and startup tasks
|
// return error 503 while running db migrations and startup tasks
|
||||||
let mut startup_server_handle = None;
|
let mut startup_server_handle = None;
|
||||||
if args.http_server {
|
if args.http_server {
|
||||||
|
@ -126,14 +121,14 @@ pub async fn start_lemmy_server(args: CmdArgs) -> Result<(), LemmyError> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the DB migrations
|
// Run the DB migrations
|
||||||
let db_url = get_database_url(Some(&settings));
|
let db_url = get_database_url(Some(&SETTINGS));
|
||||||
run_migrations(&db_url);
|
run_migrations(&db_url);
|
||||||
|
|
||||||
// Set up the connection pool
|
// Set up the connection pool
|
||||||
let pool = build_db_pool(&settings).await?;
|
let pool = build_db_pool(&SETTINGS).await?;
|
||||||
|
|
||||||
// Run the Code-required migrations
|
// Run the Code-required migrations
|
||||||
run_advanced_migrations(&mut (&pool).into(), &settings).await?;
|
run_advanced_migrations(&mut (&pool).into(), &SETTINGS).await?;
|
||||||
|
|
||||||
// Initialize the secrets
|
// Initialize the secrets
|
||||||
let secret = Secret::init(&mut (&pool).into())
|
let secret = Secret::init(&mut (&pool).into())
|
||||||
|
@ -148,7 +143,7 @@ pub async fn start_lemmy_server(args: CmdArgs) -> Result<(), LemmyError> {
|
||||||
let federation_enabled = local_site.federation_enabled;
|
let federation_enabled = local_site.federation_enabled;
|
||||||
|
|
||||||
if federation_enabled {
|
if federation_enabled {
|
||||||
println!("federation enabled, host is {}", &settings.hostname);
|
println!("federation enabled, host is {}", &SETTINGS.hostname);
|
||||||
}
|
}
|
||||||
|
|
||||||
check_private_instance_and_federation_enabled(&local_site)?;
|
check_private_instance_and_federation_enabled(&local_site)?;
|
||||||
|
@ -160,20 +155,12 @@ pub async fn start_lemmy_server(args: CmdArgs) -> Result<(), LemmyError> {
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"Starting http server at {}:{}",
|
"Starting http server at {}:{}",
|
||||||
settings.bind, settings.port
|
SETTINGS.bind, SETTINGS.port
|
||||||
);
|
);
|
||||||
|
|
||||||
let user_agent = build_user_agent(&settings);
|
let client = ClientBuilder::new(client_builder(&SETTINGS).build()?)
|
||||||
let reqwest_client = Client::builder()
|
|
||||||
.user_agent(user_agent.clone())
|
|
||||||
.timeout(REQWEST_TIMEOUT)
|
|
||||||
.connect_timeout(REQWEST_TIMEOUT)
|
|
||||||
.build()?;
|
|
||||||
|
|
||||||
let client = ClientBuilder::new(reqwest_client.clone())
|
|
||||||
.with(TracingMiddleware::default())
|
.with(TracingMiddleware::default())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let context = LemmyContext::create(
|
let context = LemmyContext::create(
|
||||||
pool.clone(),
|
pool.clone(),
|
||||||
client.clone(),
|
client.clone(),
|
||||||
|
@ -187,10 +174,10 @@ pub async fn start_lemmy_server(args: CmdArgs) -> Result<(), LemmyError> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "prometheus-metrics")]
|
#[cfg(feature = "prometheus-metrics")]
|
||||||
serve_prometheus(settings.prometheus.as_ref(), context.clone());
|
serve_prometheus(SETTINGS.prometheus.as_ref(), context.clone());
|
||||||
|
|
||||||
let federation_config = FederationConfig::builder()
|
let federation_config = FederationConfig::builder()
|
||||||
.domain(settings.hostname.clone())
|
.domain(SETTINGS.hostname.clone())
|
||||||
.app_data(context.clone())
|
.app_data(context.clone())
|
||||||
.client(client.clone())
|
.client(client.clone())
|
||||||
.http_fetch_limit(FEDERATION_HTTP_FETCH_LIMIT)
|
.http_fetch_limit(FEDERATION_HTTP_FETCH_LIMIT)
|
||||||
|
@ -212,9 +199,10 @@ pub async fn start_lemmy_server(args: CmdArgs) -> Result<(), LemmyError> {
|
||||||
if let Some(startup_server_handle) = startup_server_handle {
|
if let Some(startup_server_handle) = startup_server_handle {
|
||||||
startup_server_handle.stop(true).await;
|
startup_server_handle.stop(true).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(create_http_server(
|
Some(create_http_server(
|
||||||
federation_config.clone(),
|
federation_config.clone(),
|
||||||
settings.clone(),
|
SETTINGS.clone(),
|
||||||
federation_enabled,
|
federation_enabled,
|
||||||
)?)
|
)?)
|
||||||
} else {
|
} else {
|
||||||
|
@ -293,6 +281,12 @@ fn create_http_server(
|
||||||
let context: LemmyContext = federation_config.deref().clone();
|
let context: LemmyContext = federation_config.deref().clone();
|
||||||
let rate_limit_cell = federation_config.rate_limit_cell().clone();
|
let rate_limit_cell = federation_config.rate_limit_cell().clone();
|
||||||
let self_origin = settings.get_protocol_and_hostname();
|
let self_origin = settings.get_protocol_and_hostname();
|
||||||
|
|
||||||
|
// Pictrs cannot use proxy
|
||||||
|
let pictrs_client = ClientBuilder::new(client_builder(&SETTINGS).no_proxy().build()?)
|
||||||
|
.with(TracingMiddleware::default())
|
||||||
|
.build();
|
||||||
|
|
||||||
// Create Http server with websocket support
|
// Create Http server with websocket support
|
||||||
let server = HttpServer::new(move || {
|
let server = HttpServer::new(move || {
|
||||||
let cors_origin = env::var("LEMMY_CORS_ORIGIN");
|
let cors_origin = env::var("LEMMY_CORS_ORIGIN");
|
||||||
|
@ -335,7 +329,7 @@ fn create_http_server(
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.configure(feeds::config)
|
.configure(feeds::config)
|
||||||
.configure(|cfg| images::config(cfg, &rate_limit_cell))
|
.configure(|cfg| images::config(cfg, pictrs_client.clone(), &rate_limit_cell))
|
||||||
.configure(nodeinfo::config)
|
.configure(nodeinfo::config)
|
||||||
})
|
})
|
||||||
.disable_signals()
|
.disable_signals()
|
||||||
|
@ -358,9 +352,9 @@ pub fn init_logging(opentelemetry_url: &Option<Url>) -> Result<(), LemmyError> {
|
||||||
|
|
||||||
let format_layer = {
|
let format_layer = {
|
||||||
#[cfg(feature = "json-log")]
|
#[cfg(feature = "json-log")]
|
||||||
let layer = tracing_subscriber::fmt::layer().json();
|
let layer = tracing_subscriber::fmt::layer().with_ansi(false).json();
|
||||||
#[cfg(not(feature = "json-log"))]
|
#[cfg(not(feature = "json-log"))]
|
||||||
let layer = tracing_subscriber::fmt::layer();
|
let layer = tracing_subscriber::fmt::layer().with_ansi(false);
|
||||||
|
|
||||||
layer.with_filter(targets.clone())
|
layer.with_filter(targets.clone())
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue