mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-11-20 00:11:01 +00:00
Merge remote-tracking branch 'origin/main' into federation-send-parallel
This commit is contained in:
commit
2e2345e6f7
96 changed files with 2003 additions and 1407 deletions
763
Cargo.lock
generated
763
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
41
Cargo.toml
41
Cargo.toml
|
@ -1,5 +1,5 @@
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.19.4-rc.5"
|
version = "0.19.5"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "A link aggregator for the fediverse"
|
description = "A link aggregator for the fediverse"
|
||||||
license = "AGPL-3.0"
|
license = "AGPL-3.0"
|
||||||
|
@ -88,24 +88,24 @@ unused_self = "deny"
|
||||||
unwrap_used = "deny"
|
unwrap_used = "deny"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
lemmy_api = { version = "=0.19.4-rc.5", path = "./crates/api" }
|
lemmy_api = { version = "=0.19.5", path = "./crates/api" }
|
||||||
lemmy_api_crud = { version = "=0.19.4-rc.5", path = "./crates/api_crud" }
|
lemmy_api_crud = { version = "=0.19.5", path = "./crates/api_crud" }
|
||||||
lemmy_apub = { version = "=0.19.4-rc.5", path = "./crates/apub" }
|
lemmy_apub = { version = "=0.19.5", path = "./crates/apub" }
|
||||||
lemmy_utils = { version = "=0.19.4-rc.5", path = "./crates/utils", default-features = false }
|
lemmy_utils = { version = "=0.19.5", path = "./crates/utils", default-features = false }
|
||||||
lemmy_db_schema = { version = "=0.19.4-rc.5", path = "./crates/db_schema" }
|
lemmy_db_schema = { version = "=0.19.5", path = "./crates/db_schema" }
|
||||||
lemmy_api_common = { version = "=0.19.4-rc.5", path = "./crates/api_common" }
|
lemmy_api_common = { version = "=0.19.5", path = "./crates/api_common" }
|
||||||
lemmy_routes = { version = "=0.19.4-rc.5", path = "./crates/routes" }
|
lemmy_routes = { version = "=0.19.5", path = "./crates/routes" }
|
||||||
lemmy_db_views = { version = "=0.19.4-rc.5", path = "./crates/db_views" }
|
lemmy_db_views = { version = "=0.19.5", path = "./crates/db_views" }
|
||||||
lemmy_db_views_actor = { version = "=0.19.4-rc.5", path = "./crates/db_views_actor" }
|
lemmy_db_views_actor = { version = "=0.19.5", path = "./crates/db_views_actor" }
|
||||||
lemmy_db_views_moderator = { version = "=0.19.4-rc.5", path = "./crates/db_views_moderator" }
|
lemmy_db_views_moderator = { version = "=0.19.5", path = "./crates/db_views_moderator" }
|
||||||
lemmy_federate = { version = "=0.19.4-rc.5", path = "./crates/federate" }
|
lemmy_federate = { version = "=0.19.5", path = "./crates/federate" }
|
||||||
activitypub_federation = { version = "0.5.6", default-features = false, features = [
|
activitypub_federation = { version = "0.5.6", default-features = false, features = [
|
||||||
"actix-web",
|
"actix-web",
|
||||||
] }
|
] }
|
||||||
diesel = "2.1.6"
|
diesel = "2.1.6"
|
||||||
diesel_migrations = "2.1.0"
|
diesel_migrations = "2.1.0"
|
||||||
diesel-async = "0.4.1"
|
diesel-async = "0.4.1"
|
||||||
serde = { version = "1.0.202", features = ["derive"] }
|
serde = { version = "1.0.203", features = ["derive"] }
|
||||||
serde_with = "3.8.1"
|
serde_with = "3.8.1"
|
||||||
actix-web = { version = "4.6.0", default-features = false, features = [
|
actix-web = { version = "4.6.0", default-features = false, features = [
|
||||||
"macros",
|
"macros",
|
||||||
|
@ -116,7 +116,7 @@ actix-web = { version = "4.6.0", default-features = false, features = [
|
||||||
"cookies",
|
"cookies",
|
||||||
] }
|
] }
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.40"
|
||||||
tracing-actix-web = { version = "0.7.10", default-features = false }
|
tracing-actix-web = { version = "0.7.11", default-features = false }
|
||||||
tracing-error = "0.2.0"
|
tracing-error = "0.2.0"
|
||||||
tracing-log = "0.2.0"
|
tracing-log = "0.2.0"
|
||||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||||
|
@ -139,13 +139,13 @@ anyhow = { version = "1.0.86", features = [
|
||||||
diesel_ltree = "0.3.1"
|
diesel_ltree = "0.3.1"
|
||||||
typed-builder = "0.18.2"
|
typed-builder = "0.18.2"
|
||||||
serial_test = "3.1.1"
|
serial_test = "3.1.1"
|
||||||
tokio = { version = "1.37.0", features = ["full"] }
|
tokio = { version = "1.38.0", features = ["full"] }
|
||||||
regex = "1.10.4"
|
regex = "1.10.4"
|
||||||
once_cell = "1.19.0"
|
once_cell = "1.19.0"
|
||||||
diesel-derive-newtype = "2.1.2"
|
diesel-derive-newtype = "2.1.2"
|
||||||
diesel-derive-enum = { version = "2.1.0", features = ["postgres"] }
|
diesel-derive-enum = { version = "2.1.0", features = ["postgres"] }
|
||||||
strum = "0.26.2"
|
strum = "0.26.2"
|
||||||
strum_macros = "0.26.2"
|
strum_macros = "0.26.4"
|
||||||
itertools = "0.13.0"
|
itertools = "0.13.0"
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
http = "0.2.12"
|
http = "0.2.12"
|
||||||
|
@ -157,7 +157,7 @@ ts-rs = { version = "7.1.1", features = [
|
||||||
"chrono-impl",
|
"chrono-impl",
|
||||||
"no-serde-warnings",
|
"no-serde-warnings",
|
||||||
] }
|
] }
|
||||||
rustls = { version = "0.23.8", features = ["ring"] }
|
rustls = { version = "0.23.9", features = ["ring"] }
|
||||||
futures-util = "0.3.30"
|
futures-util = "0.3.30"
|
||||||
tokio-postgres = "0.7.10"
|
tokio-postgres = "0.7.10"
|
||||||
tokio-postgres-rustls = "0.12.0"
|
tokio-postgres-rustls = "0.12.0"
|
||||||
|
@ -165,8 +165,9 @@ urlencoding = "2.1.3"
|
||||||
enum-map = "2.7"
|
enum-map = "2.7"
|
||||||
moka = { version = "0.12.7", features = ["future"] }
|
moka = { version = "0.12.7", features = ["future"] }
|
||||||
i-love-jesus = { version = "0.1.0" }
|
i-love-jesus = { version = "0.1.0" }
|
||||||
clap = { version = "4.5.4", features = ["derive", "env"] }
|
clap = { version = "4.5.6", features = ["derive", "env"] }
|
||||||
pretty_assertions = "1.4.0"
|
pretty_assertions = "1.4.0"
|
||||||
|
derive-new = "0.6.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lemmy_api = { workspace = true }
|
lemmy_api = { workspace = true }
|
||||||
|
@ -194,9 +195,9 @@ clokwerk = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
tracing-opentelemetry = { workspace = true, optional = true }
|
tracing-opentelemetry = { workspace = true, optional = true }
|
||||||
opentelemetry = { workspace = true, optional = true }
|
opentelemetry = { workspace = true, optional = true }
|
||||||
console-subscriber = { version = "0.2.0", optional = true }
|
console-subscriber = { version = "0.3.0", optional = true }
|
||||||
opentelemetry-otlp = { version = "0.12.0", optional = true }
|
opentelemetry-otlp = { version = "0.12.0", optional = true }
|
||||||
pict-rs = { version = "0.5.14", optional = true }
|
pict-rs = { version = "0.5.15", optional = true }
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
actix-cors = "0.7.0"
|
actix-cors = "0.7.0"
|
||||||
futures-util = { workspace = true }
|
futures-util = { workspace = true }
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
{
|
|
||||||
"root": true,
|
|
||||||
"env": {
|
|
||||||
"browser": true
|
|
||||||
},
|
|
||||||
"plugins": ["@typescript-eslint"],
|
|
||||||
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
|
|
||||||
"parser": "@typescript-eslint/parser",
|
|
||||||
"parserOptions": {
|
|
||||||
"project": "./tsconfig.json",
|
|
||||||
"warnOnUnsupportedTypeScriptVersion": false
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"@typescript-eslint/ban-ts-comment": 0,
|
|
||||||
"@typescript-eslint/no-explicit-any": 0,
|
|
||||||
"@typescript-eslint/explicit-module-boundary-types": 0,
|
|
||||||
"@typescript-eslint/no-var-requires": 0,
|
|
||||||
"arrow-body-style": 0,
|
|
||||||
"curly": 0,
|
|
||||||
"eol-last": 0,
|
|
||||||
"eqeqeq": 0,
|
|
||||||
"func-style": 0,
|
|
||||||
"import/no-duplicates": 0,
|
|
||||||
"max-statements": 0,
|
|
||||||
"max-params": 0,
|
|
||||||
"new-cap": 0,
|
|
||||||
"no-console": 0,
|
|
||||||
"no-duplicate-imports": 0,
|
|
||||||
"no-extra-parens": 0,
|
|
||||||
"no-return-assign": 0,
|
|
||||||
"no-throw-literal": 0,
|
|
||||||
"no-trailing-spaces": 0,
|
|
||||||
"no-unused-expressions": 0,
|
|
||||||
"no-useless-constructor": 0,
|
|
||||||
"no-useless-escape": 0,
|
|
||||||
"no-var": 0,
|
|
||||||
"prefer-const": 0,
|
|
||||||
"prefer-rest-params": 0,
|
|
||||||
"quote-props": 0,
|
|
||||||
"unicorn/filename-case": 0
|
|
||||||
}
|
|
||||||
}
|
|
56
api_tests/eslint.config.mjs
Normal file
56
api_tests/eslint.config.mjs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import pluginJs from "@eslint/js";
|
||||||
|
import tseslint from "typescript-eslint";
|
||||||
|
|
||||||
|
export default [
|
||||||
|
pluginJs.configs.recommended,
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
parser: tseslint.parser,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// For some reason this has to be in its own block
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
"putTypesInIndex.js",
|
||||||
|
"dist/*",
|
||||||
|
"docs/*",
|
||||||
|
".yalc",
|
||||||
|
"jest.config.js",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ["src/**/*"],
|
||||||
|
rules: {
|
||||||
|
"@typescript-eslint/no-empty-interface": 0,
|
||||||
|
"@typescript-eslint/no-empty-function": 0,
|
||||||
|
"@typescript-eslint/ban-ts-comment": 0,
|
||||||
|
"@typescript-eslint/no-explicit-any": 0,
|
||||||
|
"@typescript-eslint/explicit-module-boundary-types": 0,
|
||||||
|
"@typescript-eslint/no-var-requires": 0,
|
||||||
|
"arrow-body-style": 0,
|
||||||
|
curly: 0,
|
||||||
|
"eol-last": 0,
|
||||||
|
eqeqeq: 0,
|
||||||
|
"func-style": 0,
|
||||||
|
"import/no-duplicates": 0,
|
||||||
|
"max-statements": 0,
|
||||||
|
"max-params": 0,
|
||||||
|
"new-cap": 0,
|
||||||
|
"no-console": 0,
|
||||||
|
"no-duplicate-imports": 0,
|
||||||
|
"no-extra-parens": 0,
|
||||||
|
"no-return-assign": 0,
|
||||||
|
"no-throw-literal": 0,
|
||||||
|
"no-trailing-spaces": 0,
|
||||||
|
"no-unused-expressions": 0,
|
||||||
|
"no-useless-constructor": 0,
|
||||||
|
"no-useless-escape": 0,
|
||||||
|
"no-var": 0,
|
||||||
|
"prefer-const": 0,
|
||||||
|
"prefer-rest-params": 0,
|
||||||
|
"quote-props": 0,
|
||||||
|
"unicorn/filename-case": 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
|
@ -6,9 +6,9 @@
|
||||||
"repository": "https://github.com/LemmyNet/lemmy",
|
"repository": "https://github.com/LemmyNet/lemmy",
|
||||||
"author": "Dessalines",
|
"author": "Dessalines",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"packageManager": "pnpm@9.1.4",
|
"packageManager": "pnpm@9.4.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "tsc --noEmit && eslint --report-unused-disable-directives --ext .js,.ts,.tsx src && prettier --check 'src/**/*.ts'",
|
"lint": "tsc --noEmit && eslint --report-unused-disable-directives && prettier --check 'src/**/*.ts'",
|
||||||
"fix": "prettier --write src && eslint --fix src",
|
"fix": "prettier --write src && eslint --fix src",
|
||||||
"api-test": "jest -i follow.spec.ts && jest -i image.spec.ts && jest -i user.spec.ts && jest -i private_message.spec.ts && jest -i community.spec.ts && jest -i post.spec.ts && jest -i comment.spec.ts ",
|
"api-test": "jest -i follow.spec.ts && jest -i image.spec.ts && jest -i user.spec.ts && jest -i private_message.spec.ts && jest -i community.spec.ts && jest -i post.spec.ts && jest -i comment.spec.ts ",
|
||||||
"api-test-follow": "jest -i follow.spec.ts",
|
"api-test-follow": "jest -i follow.spec.ts",
|
||||||
|
@ -25,12 +25,13 @@
|
||||||
"@typescript-eslint/eslint-plugin": "^7.5.0",
|
"@typescript-eslint/eslint-plugin": "^7.5.0",
|
||||||
"@typescript-eslint/parser": "^7.5.0",
|
"@typescript-eslint/parser": "^7.5.0",
|
||||||
"download-file-sync": "^1.0.4",
|
"download-file-sync": "^1.0.4",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^9.0.0",
|
||||||
"eslint-plugin-prettier": "^5.1.3",
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
"jest": "^29.5.0",
|
"jest": "^29.5.0",
|
||||||
"lemmy-js-client": "0.19.4-alpha.18",
|
"lemmy-js-client": "0.19.5-alpha.1",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"ts-jest": "^29.1.0",
|
"ts-jest": "^29.1.0",
|
||||||
"typescript": "^5.4.4"
|
"typescript": "^5.4.4",
|
||||||
|
"typescript-eslint": "^7.13.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -15,7 +15,7 @@ export LEMMY_TEST_FAST_FEDERATION=1 # by default, the persistent federation queu
|
||||||
|
|
||||||
# pictrs setup
|
# pictrs setup
|
||||||
if [ ! -f "api_tests/pict-rs" ]; then
|
if [ ! -f "api_tests/pict-rs" ]; then
|
||||||
curl "https://git.asonix.dog/asonix/pict-rs/releases/download/v0.5.13/pict-rs-linux-amd64" -o api_tests/pict-rs
|
curl "https://git.asonix.dog/asonix/pict-rs/releases/download/v0.5.16/pict-rs-linux-amd64" -o api_tests/pict-rs
|
||||||
chmod +x api_tests/pict-rs
|
chmod +x api_tests/pict-rs
|
||||||
fi
|
fi
|
||||||
./api_tests/pict-rs \
|
./api_tests/pict-rs \
|
||||||
|
|
|
@ -160,6 +160,7 @@ test("Purge post, linked image removed", async () => {
|
||||||
upload.url,
|
upload.url,
|
||||||
);
|
);
|
||||||
expect(post.post_view.post.url).toBe(upload.url);
|
expect(post.post_view.post.url).toBe(upload.url);
|
||||||
|
expect(post.post_view.image_details).toBeDefined();
|
||||||
|
|
||||||
// purge post
|
// purge post
|
||||||
const purgeForm: PurgePost = {
|
const purgeForm: PurgePost = {
|
||||||
|
@ -184,6 +185,9 @@ test("Images in remote image post are proxied if setting enabled", async () => {
|
||||||
const post = postRes.post_view.post;
|
const post = postRes.post_view.post;
|
||||||
expect(post).toBeDefined();
|
expect(post).toBeDefined();
|
||||||
|
|
||||||
|
// Make sure it fetched the image details
|
||||||
|
expect(postRes.post_view.image_details).toBeDefined();
|
||||||
|
|
||||||
// remote image gets proxied after upload
|
// remote image gets proxied after upload
|
||||||
expect(
|
expect(
|
||||||
post.thumbnail_url?.startsWith(
|
post.thumbnail_url?.startsWith(
|
||||||
|
|
|
@ -512,7 +512,7 @@ test("Enforce site ban federation for local user", async () => {
|
||||||
}
|
}
|
||||||
let newAlphaUserJwt = await loginUser(alpha, alphaUserPerson.name);
|
let newAlphaUserJwt = await loginUser(alpha, alphaUserPerson.name);
|
||||||
alphaUserHttp.setHeaders({
|
alphaUserHttp.setHeaders({
|
||||||
Authorization: "Bearer " + newAlphaUserJwt.jwt ?? "",
|
Authorization: "Bearer " + newAlphaUserJwt.jwt,
|
||||||
});
|
});
|
||||||
// alpha makes new post in beta community, it federates
|
// alpha makes new post in beta community, it federates
|
||||||
let postRes2 = await createPost(alphaUserHttp, betaCommunity!.community.id);
|
let postRes2 = await createPost(alphaUserHttp, betaCommunity!.community.id);
|
||||||
|
|
|
@ -21,6 +21,7 @@ import {
|
||||||
fetchFunction,
|
fetchFunction,
|
||||||
alphaImage,
|
alphaImage,
|
||||||
unfollows,
|
unfollows,
|
||||||
|
saveUserSettingsBio,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
import { LemmyHttp, SaveUserSettings, UploadImage } from "lemmy-js-client";
|
import { LemmyHttp, SaveUserSettings, UploadImage } from "lemmy-js-client";
|
||||||
import { GetPosts } from "lemmy-js-client/dist/types/GetPosts";
|
import { GetPosts } from "lemmy-js-client/dist/types/GetPosts";
|
||||||
|
@ -190,10 +191,26 @@ test("Set a new avatar, old avatar is deleted", async () => {
|
||||||
expect(upload2.url).toBeDefined();
|
expect(upload2.url).toBeDefined();
|
||||||
|
|
||||||
let form2 = {
|
let form2 = {
|
||||||
avatar: upload1.url,
|
avatar: upload2.url,
|
||||||
};
|
};
|
||||||
await saveUserSettings(alpha, form2);
|
await saveUserSettings(alpha, form2);
|
||||||
// make sure only the new avatar is kept
|
// make sure only the new avatar is kept
|
||||||
const listMediaRes2 = await alphaImage.listMedia();
|
const listMediaRes2 = await alphaImage.listMedia();
|
||||||
expect(listMediaRes2.images.length).toBe(1);
|
expect(listMediaRes2.images.length).toBe(1);
|
||||||
|
|
||||||
|
// Upload that same form2 avatar, make sure it isn't replaced / deleted
|
||||||
|
await saveUserSettings(alpha, form2);
|
||||||
|
// make sure only the new avatar is kept
|
||||||
|
const listMediaRes3 = await alphaImage.listMedia();
|
||||||
|
expect(listMediaRes3.images.length).toBe(1);
|
||||||
|
|
||||||
|
// Now try to save a user settings, with the icon missing,
|
||||||
|
// and make sure it doesn't clear the data, or delete the image
|
||||||
|
await saveUserSettingsBio(alpha);
|
||||||
|
let site = await getSite(alpha);
|
||||||
|
expect(site.my_user?.local_user_view.person.avatar).toBe(upload2.url);
|
||||||
|
|
||||||
|
// make sure only the new avatar is kept
|
||||||
|
const listMediaRes4 = await alphaImage.listMedia();
|
||||||
|
expect(listMediaRes4.images.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
|
@ -26,6 +26,7 @@ body = """
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
{%- endfor -%}
|
{%- endfor -%}
|
||||||
|
|
||||||
|
{%- if github -%}
|
||||||
{% if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %}
|
{% if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %}
|
||||||
{% raw %}\n{% endraw -%}
|
{% raw %}\n{% endraw -%}
|
||||||
## New Contributors
|
## New Contributors
|
||||||
|
@ -36,6 +37,7 @@ body = """
|
||||||
[#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) \
|
[#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) \
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
{%- endfor -%}
|
{%- endfor -%}
|
||||||
|
{%- endif -%}
|
||||||
|
|
||||||
{% if version %}
|
{% if version %}
|
||||||
{% if previous.version %}
|
{% if previous.version %}
|
||||||
|
@ -70,6 +72,7 @@ commit_preprocessors = [
|
||||||
# remove issue numbers from commits
|
# remove issue numbers from commits
|
||||||
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "" },
|
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "" },
|
||||||
]
|
]
|
||||||
|
commit_parsers = [{ field = "author.name", pattern = "renovate", skip = true }]
|
||||||
# protect breaking changes from being skipped due to matching a skipping commit_parser
|
# protect breaking changes from being skipped due to matching a skipping commit_parser
|
||||||
protect_breaking_commits = false
|
protect_breaking_commits = false
|
||||||
# filter out the commits that are not matched by commit parsers
|
# filter out the commits that are not matched by commit parsers
|
||||||
|
|
|
@ -33,7 +33,7 @@ anyhow = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
chrono = { workspace = true }
|
chrono = { workspace = true }
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
wav = "1.0.1"
|
hound = "3.5.1"
|
||||||
sitemap-rs = "0.2.1"
|
sitemap-rs = "0.2.1"
|
||||||
totp-rs = { version = "5.5.1", features = ["gen_secret", "otpauth"] }
|
totp-rs = { version = "5.5.1", features = ["gen_secret", "otpauth"] }
|
||||||
actix-web-httpauth = "0.8.1"
|
actix-web-httpauth = "0.8.1"
|
||||||
|
|
|
@ -43,7 +43,10 @@ pub async fn ban_from_community(
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
is_valid_body_field(&data.reason, false)?;
|
|
||||||
|
if let Some(reason) = &data.reason {
|
||||||
|
is_valid_body_field(reason, false)?;
|
||||||
|
}
|
||||||
|
|
||||||
let community_user_ban_form = CommunityPersonBanForm {
|
let community_user_ban_form = CommunityPersonBanForm {
|
||||||
community_id: data.community_id,
|
community_id: data.community_id,
|
||||||
|
|
|
@ -44,32 +44,37 @@ pub mod site;
|
||||||
pub mod sitemap;
|
pub mod sitemap;
|
||||||
|
|
||||||
/// Converts the captcha to a base64 encoded wav audio file
|
/// Converts the captcha to a base64 encoded wav audio file
|
||||||
#[allow(deprecated)]
|
|
||||||
pub(crate) fn captcha_as_wav_base64(captcha: &Captcha) -> LemmyResult<String> {
|
pub(crate) fn captcha_as_wav_base64(captcha: &Captcha) -> LemmyResult<String> {
|
||||||
let letters = captcha.as_wav();
|
let letters = captcha.as_wav();
|
||||||
|
|
||||||
// Decode each wav file, concatenate the samples
|
// Decode each wav file, concatenate the samples
|
||||||
let mut concat_samples: Vec<i16> = Vec::new();
|
let mut concat_samples: Vec<i16> = Vec::new();
|
||||||
let mut any_header: Option<wav::Header> = None;
|
let mut any_header: Option<hound::WavSpec> = None;
|
||||||
for letter in letters {
|
for letter in letters {
|
||||||
let mut cursor = Cursor::new(letter.unwrap_or_default());
|
let mut cursor = Cursor::new(letter.unwrap_or_default());
|
||||||
let (header, samples) = wav::read(&mut cursor)?;
|
let reader = hound::WavReader::new(&mut cursor)?;
|
||||||
any_header = Some(header);
|
any_header = Some(reader.spec());
|
||||||
if let Some(samples16) = samples.as_sixteen() {
|
let samples16 = reader
|
||||||
|
.into_samples::<i16>()
|
||||||
|
.collect::<Result<Vec<_>, _>>()
|
||||||
|
.with_lemmy_type(LemmyErrorType::CouldntCreateAudioCaptcha)?;
|
||||||
concat_samples.extend(samples16);
|
concat_samples.extend(samples16);
|
||||||
} else {
|
|
||||||
Err(LemmyErrorType::CouldntCreateAudioCaptcha)?
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode the concatenated result as a wav file
|
// Encode the concatenated result as a wav file
|
||||||
let mut output_buffer = Cursor::new(vec![]);
|
let mut output_buffer = Cursor::new(vec![]);
|
||||||
if let Some(header) = any_header {
|
if let Some(header) = any_header {
|
||||||
wav::write(
|
let mut writer = hound::WavWriter::new(&mut output_buffer, header)
|
||||||
header,
|
.with_lemmy_type(LemmyErrorType::CouldntCreateAudioCaptcha)?;
|
||||||
&wav::BitDepth::Sixteen(concat_samples),
|
let mut writer16 = writer.get_i16_writer(concat_samples.len() as u32);
|
||||||
&mut output_buffer,
|
for sample in concat_samples {
|
||||||
)
|
writer16.write_sample(sample);
|
||||||
|
}
|
||||||
|
writer16
|
||||||
|
.flush()
|
||||||
|
.with_lemmy_type(LemmyErrorType::CouldntCreateAudioCaptcha)?;
|
||||||
|
writer
|
||||||
|
.finalize()
|
||||||
.with_lemmy_type(LemmyErrorType::CouldntCreateAudioCaptcha)?;
|
.with_lemmy_type(LemmyErrorType::CouldntCreateAudioCaptcha)?;
|
||||||
|
|
||||||
Ok(base64.encode(output_buffer.into_inner()))
|
Ok(base64.encode(output_buffer.into_inner()))
|
||||||
|
|
|
@ -31,7 +31,9 @@ pub async fn ban_from_site(
|
||||||
// Make sure user is an admin
|
// Make sure user is an admin
|
||||||
is_admin(&local_user_view)?;
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
is_valid_body_field(&data.reason, false)?;
|
if let Some(reason) = &data.reason {
|
||||||
|
is_valid_body_field(reason, false)?;
|
||||||
|
}
|
||||||
|
|
||||||
let expires = check_expire_time(data.expires)?;
|
let expires = check_expire_time(data.expires)?;
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ use lemmy_db_schema::{
|
||||||
person::{Person, PersonUpdateForm},
|
person::{Person, PersonUpdateForm},
|
||||||
},
|
},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
utils::diesel_option_overwrite,
|
utils::{diesel_string_update, diesel_url_update},
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
|
@ -42,18 +42,24 @@ pub async fn save_user_settings(
|
||||||
|
|
||||||
let slur_regex = local_site_to_slur_regex(&site_view.local_site);
|
let slur_regex = local_site_to_slur_regex(&site_view.local_site);
|
||||||
let url_blocklist = get_url_blocklist(&context).await?;
|
let url_blocklist = get_url_blocklist(&context).await?;
|
||||||
let bio = diesel_option_overwrite(
|
let bio = diesel_string_update(
|
||||||
process_markdown_opt(&data.bio, &slur_regex, &url_blocklist, &context).await?,
|
process_markdown_opt(&data.bio, &slur_regex, &url_blocklist, &context)
|
||||||
|
.await?
|
||||||
|
.as_deref(),
|
||||||
);
|
);
|
||||||
replace_image(&data.avatar, &local_user_view.person.avatar, &context).await?;
|
|
||||||
replace_image(&data.banner, &local_user_view.person.banner, &context).await?;
|
|
||||||
|
|
||||||
let avatar = proxy_image_link_opt_api(&data.avatar, &context).await?;
|
let avatar = diesel_url_update(data.avatar.as_deref())?;
|
||||||
let banner = proxy_image_link_opt_api(&data.banner, &context).await?;
|
replace_image(&avatar, &local_user_view.person.avatar, &context).await?;
|
||||||
let display_name = diesel_option_overwrite(data.display_name.clone());
|
let avatar = proxy_image_link_opt_api(avatar, &context).await?;
|
||||||
let matrix_user_id = diesel_option_overwrite(data.matrix_user_id.clone());
|
|
||||||
|
let banner = diesel_url_update(data.banner.as_deref())?;
|
||||||
|
replace_image(&banner, &local_user_view.person.banner, &context).await?;
|
||||||
|
let banner = proxy_image_link_opt_api(banner, &context).await?;
|
||||||
|
|
||||||
|
let display_name = diesel_string_update(data.display_name.as_deref());
|
||||||
|
let matrix_user_id = diesel_string_update(data.matrix_user_id.as_deref());
|
||||||
let email_deref = data.email.as_deref().map(str::to_lowercase);
|
let email_deref = data.email.as_deref().map(str::to_lowercase);
|
||||||
let email = diesel_option_overwrite(email_deref.clone());
|
let email = diesel_string_update(email_deref.as_deref());
|
||||||
|
|
||||||
if let Some(Some(email)) = &email {
|
if let Some(Some(email)) = &email {
|
||||||
let previous_email = local_user_view.local_user.email.clone().unwrap_or_default();
|
let previous_email = local_user_view.local_user.email.clone().unwrap_or_default();
|
||||||
|
|
|
@ -4,14 +4,19 @@ use lemmy_api_common::{
|
||||||
post::{GetSiteMetadata, GetSiteMetadataResponse},
|
post::{GetSiteMetadata, GetSiteMetadataResponse},
|
||||||
request::fetch_link_metadata,
|
request::fetch_link_metadata,
|
||||||
};
|
};
|
||||||
use lemmy_utils::error::LemmyResult;
|
use lemmy_utils::{
|
||||||
|
error::{LemmyErrorExt, LemmyResult},
|
||||||
|
LemmyErrorType,
|
||||||
|
};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn get_link_metadata(
|
pub async fn get_link_metadata(
|
||||||
data: Query<GetSiteMetadata>,
|
data: Query<GetSiteMetadata>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
) -> LemmyResult<Json<GetSiteMetadataResponse>> {
|
) -> LemmyResult<Json<GetSiteMetadataResponse>> {
|
||||||
let metadata = fetch_link_metadata(&data.url, &context).await?;
|
let url = Url::parse(&data.url).with_lemmy_type(LemmyErrorType::InvalidUrl)?;
|
||||||
|
let metadata = fetch_link_metadata(&url, &context).await?;
|
||||||
|
|
||||||
Ok(Json(GetSiteMetadataResponse { metadata }))
|
Ok(Json(GetSiteMetadataResponse { metadata }))
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ use lemmy_db_schema::{
|
||||||
registration_application::{RegistrationApplication, RegistrationApplicationUpdateForm},
|
registration_application::{RegistrationApplication, RegistrationApplicationUpdateForm},
|
||||||
},
|
},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
utils::diesel_option_overwrite,
|
utils::diesel_string_update,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{LocalUserView, RegistrationApplicationView};
|
use lemmy_db_views::structs::{LocalUserView, RegistrationApplicationView};
|
||||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
||||||
|
@ -26,7 +26,7 @@ pub async fn approve_registration_application(
|
||||||
is_admin(&local_user_view)?;
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
// Update the registration with reason, admin_id
|
// Update the registration with reason, admin_id
|
||||||
let deny_reason = diesel_option_overwrite(data.deny_reason.clone());
|
let deny_reason = diesel_string_update(data.deny_reason.as_deref());
|
||||||
let app_form = RegistrationApplicationUpdateForm {
|
let app_form = RegistrationApplicationUpdateForm {
|
||||||
admin_id: Some(Some(local_user_view.person.id)),
|
admin_id: Some(Some(local_user_view.person.id)),
|
||||||
deny_reason,
|
deny_reason,
|
||||||
|
|
|
@ -112,11 +112,7 @@ mod tests {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let new_person = PersonInsertForm::builder()
|
let new_person = PersonInsertForm::test_form(inserted_instance.id, "Gerry9812");
|
||||||
.name("Gerry9812".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let inserted_person = Person::create(pool, &new_person).await.unwrap();
|
let inserted_person = Person::create(pool, &new_person).await.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,6 @@ use serde::{Deserialize, Serialize};
|
||||||
use serde_with::skip_serializing_none;
|
use serde_with::skip_serializing_none;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
|
||||||
|
@ -20,8 +19,7 @@ use url::Url;
|
||||||
pub struct CreatePost {
|
pub struct CreatePost {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub community_id: CommunityId,
|
pub community_id: CommunityId,
|
||||||
#[cfg_attr(feature = "full", ts(type = "string"))]
|
pub url: Option<String>,
|
||||||
pub url: Option<Url>,
|
|
||||||
/// An optional body for the post in markdown.
|
/// An optional body for the post in markdown.
|
||||||
pub body: Option<String>,
|
pub body: Option<String>,
|
||||||
/// An optional alt_text, usable for image posts.
|
/// An optional alt_text, usable for image posts.
|
||||||
|
@ -30,9 +28,8 @@ pub struct CreatePost {
|
||||||
pub honeypot: Option<String>,
|
pub honeypot: Option<String>,
|
||||||
pub nsfw: Option<bool>,
|
pub nsfw: Option<bool>,
|
||||||
pub language_id: Option<LanguageId>,
|
pub language_id: Option<LanguageId>,
|
||||||
#[cfg_attr(feature = "full", ts(type = "string"))]
|
|
||||||
/// Instead of fetching a thumbnail, use a custom one.
|
/// Instead of fetching a thumbnail, use a custom one.
|
||||||
pub custom_thumbnail: Option<Url>,
|
pub custom_thumbnail: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
@ -82,6 +79,8 @@ pub struct GetPosts {
|
||||||
pub liked_only: Option<bool>,
|
pub liked_only: Option<bool>,
|
||||||
pub disliked_only: Option<bool>,
|
pub disliked_only: Option<bool>,
|
||||||
pub show_hidden: Option<bool>,
|
pub show_hidden: Option<bool>,
|
||||||
|
/// If true, then show the read posts (even if your user setting is to hide them)
|
||||||
|
pub show_read: Option<bool>,
|
||||||
pub page_cursor: Option<PaginationCursor>,
|
pub page_cursor: Option<PaginationCursor>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,17 +113,15 @@ pub struct CreatePostLike {
|
||||||
pub struct EditPost {
|
pub struct EditPost {
|
||||||
pub post_id: PostId,
|
pub post_id: PostId,
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
#[cfg_attr(feature = "full", ts(type = "string"))]
|
pub url: Option<String>,
|
||||||
pub url: Option<Url>,
|
|
||||||
/// An optional body for the post in markdown.
|
/// An optional body for the post in markdown.
|
||||||
pub body: Option<String>,
|
pub body: Option<String>,
|
||||||
/// An optional alt_text, usable for image posts.
|
/// An optional alt_text, usable for image posts.
|
||||||
pub alt_text: Option<String>,
|
pub alt_text: Option<String>,
|
||||||
pub nsfw: Option<bool>,
|
pub nsfw: Option<bool>,
|
||||||
pub language_id: Option<LanguageId>,
|
pub language_id: Option<LanguageId>,
|
||||||
#[cfg_attr(feature = "full", ts(type = "string"))]
|
|
||||||
/// Instead of fetching a thumbnail, use a custom one.
|
/// Instead of fetching a thumbnail, use a custom one.
|
||||||
pub custom_thumbnail: Option<Url>,
|
pub custom_thumbnail: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
|
||||||
|
@ -249,8 +246,7 @@ pub struct ListPostReportsResponse {
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
/// Get metadata for a given site.
|
/// Get metadata for a given site.
|
||||||
pub struct GetSiteMetadata {
|
pub struct GetSiteMetadata {
|
||||||
#[cfg_attr(feature = "full", ts(type = "string"))]
|
pub url: String,
|
||||||
pub url: Url,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
|
|
@ -11,7 +11,7 @@ use encoding_rs::{Encoding, UTF_8};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::DbUrl,
|
newtypes::DbUrl,
|
||||||
source::{
|
source::{
|
||||||
images::{LocalImage, LocalImageForm},
|
images::{ImageDetailsForm, LocalImage, LocalImageForm},
|
||||||
local_site::LocalSite,
|
local_site::LocalSite,
|
||||||
post::{Post, PostUpdateForm},
|
post::{Post, PostUpdateForm},
|
||||||
},
|
},
|
||||||
|
@ -209,6 +209,19 @@ pub struct PictrsFileDetails {
|
||||||
pub created_at: DateTime<Utc>,
|
pub created_at: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PictrsFileDetails {
|
||||||
|
/// Builds the image form. This should always use the thumbnail_url,
|
||||||
|
/// Because the post_view joins to it
|
||||||
|
pub fn build_image_details_form(&self, thumbnail_url: &Url) -> ImageDetailsForm {
|
||||||
|
ImageDetailsForm {
|
||||||
|
link: thumbnail_url.clone().into(),
|
||||||
|
width: self.width.into(),
|
||||||
|
height: self.height.into(),
|
||||||
|
content_type: self.content_type.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
struct PictrsPurgeResponse {
|
struct PictrsPurgeResponse {
|
||||||
msg: String,
|
msg: String,
|
||||||
|
@ -316,11 +329,52 @@ async fn generate_pictrs_thumbnail(image_url: &Url, context: &LemmyContext) -> L
|
||||||
let protocol_and_hostname = context.settings().get_protocol_and_hostname();
|
let protocol_and_hostname = context.settings().get_protocol_and_hostname();
|
||||||
let thumbnail_url = image.thumbnail_url(&protocol_and_hostname)?;
|
let thumbnail_url = image.thumbnail_url(&protocol_and_hostname)?;
|
||||||
|
|
||||||
LocalImage::create(&mut context.pool(), &form).await?;
|
// Also store the details for the image
|
||||||
|
let details_form = image.details.build_image_details_form(&thumbnail_url);
|
||||||
|
LocalImage::create(&mut context.pool(), &form, &details_form).await?;
|
||||||
|
|
||||||
Ok(thumbnail_url)
|
Ok(thumbnail_url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fetches the image details for pictrs proxied images
|
||||||
|
///
|
||||||
|
/// We don't need to check for image mode, as that's already been done
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
pub async fn fetch_pictrs_proxied_image_details(
|
||||||
|
image_url: &Url,
|
||||||
|
context: &LemmyContext,
|
||||||
|
) -> LemmyResult<PictrsFileDetails> {
|
||||||
|
let pictrs_url = context.settings().pictrs_config()?.url;
|
||||||
|
let encoded_image_url = encode(image_url.as_str());
|
||||||
|
|
||||||
|
// Pictrs needs you to fetch the proxied image before you can fetch the details
|
||||||
|
let proxy_url = format!("{pictrs_url}image/original?proxy={encoded_image_url}");
|
||||||
|
|
||||||
|
let res = context
|
||||||
|
.client()
|
||||||
|
.get(&proxy_url)
|
||||||
|
.timeout(REQWEST_TIMEOUT)
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.status();
|
||||||
|
if !res.is_success() {
|
||||||
|
Err(LemmyErrorType::NotAnImageType)?
|
||||||
|
}
|
||||||
|
|
||||||
|
let details_url = format!("{pictrs_url}image/details/original?proxy={encoded_image_url}");
|
||||||
|
|
||||||
|
let res = context
|
||||||
|
.client()
|
||||||
|
.get(&details_url)
|
||||||
|
.timeout(REQWEST_TIMEOUT)
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.json()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: get rid of this by reading content type from db
|
// TODO: get rid of this by reading content type from db
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn is_image_content_type(client: &ClientWithMiddleware, url: &Url) -> LemmyResult<()> {
|
async fn is_image_content_type(client: &ClientWithMiddleware, url: &Url) -> LemmyResult<()> {
|
||||||
|
@ -338,16 +392,19 @@ async fn is_image_content_type(client: &ClientWithMiddleware, url: &Url) -> Lemm
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// When adding a new avatar or similar image, delete the old one.
|
/// When adding a new avatar, banner or similar image, delete the old one.
|
||||||
pub async fn replace_image(
|
pub async fn replace_image(
|
||||||
new_image: &Option<String>,
|
new_image: &Option<Option<DbUrl>>,
|
||||||
old_image: &Option<DbUrl>,
|
old_image: &Option<DbUrl>,
|
||||||
context: &Data<LemmyContext>,
|
context: &Data<LemmyContext>,
|
||||||
) -> LemmyResult<()> {
|
) -> LemmyResult<()> {
|
||||||
if new_image.is_some() {
|
if let (Some(Some(new_image)), Some(old_image)) = (new_image, old_image) {
|
||||||
|
// Note: Oftentimes front ends will include the current image in the form.
|
||||||
|
// In this case, deleting `old_image` would also be deletion of `new_image`,
|
||||||
|
// so the deletion must be skipped for the image to be kept.
|
||||||
|
if new_image != old_image {
|
||||||
// Ignore errors because image may be stored externally.
|
// Ignore errors because image may be stored externally.
|
||||||
if let Some(avatar) = &old_image {
|
let image = LocalImage::delete_by_url(&mut context.pool(), old_image)
|
||||||
let image = LocalImage::delete_by_url(&mut context.pool(), avatar)
|
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
if let Some(image) = image {
|
if let Some(image) = image {
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
request::{delete_image_from_pictrs, purge_image_from_pictrs},
|
request::{
|
||||||
|
delete_image_from_pictrs,
|
||||||
|
fetch_pictrs_proxied_image_details,
|
||||||
|
purge_image_from_pictrs,
|
||||||
|
},
|
||||||
site::{FederatedInstances, InstanceWithFederationState},
|
site::{FederatedInstances, InstanceWithFederationState},
|
||||||
};
|
};
|
||||||
use chrono::{DateTime, Days, Local, TimeZone, Utc};
|
use chrono::{DateTime, Days, Local, TimeZone, Utc};
|
||||||
|
@ -949,7 +953,18 @@ pub async fn process_markdown(
|
||||||
|
|
||||||
if context.settings().pictrs_config()?.image_mode() == PictrsImageMode::ProxyAllImages {
|
if context.settings().pictrs_config()?.image_mode() == PictrsImageMode::ProxyAllImages {
|
||||||
let (text, links) = markdown_rewrite_image_links(text);
|
let (text, links) = markdown_rewrite_image_links(text);
|
||||||
RemoteImage::create(&mut context.pool(), links).await?;
|
|
||||||
|
// Create images and image detail rows
|
||||||
|
for link in links {
|
||||||
|
// Insert image details for the remote image
|
||||||
|
let details_res = fetch_pictrs_proxied_image_details(&link, context).await;
|
||||||
|
if let Ok(details) = details_res {
|
||||||
|
let proxied =
|
||||||
|
build_proxied_image_url(&link, &context.settings().get_protocol_and_hostname())?;
|
||||||
|
let details_form = details.build_image_details_form(&proxied);
|
||||||
|
RemoteImage::create(&mut context.pool(), &details_form).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(text)
|
Ok(text)
|
||||||
} else {
|
} else {
|
||||||
Ok(text)
|
Ok(text)
|
||||||
|
@ -984,8 +999,14 @@ async fn proxy_image_link_internal(
|
||||||
Ok(link.into())
|
Ok(link.into())
|
||||||
} else if image_mode == PictrsImageMode::ProxyAllImages {
|
} else if image_mode == PictrsImageMode::ProxyAllImages {
|
||||||
let proxied = build_proxied_image_url(&link, &context.settings().get_protocol_and_hostname())?;
|
let proxied = build_proxied_image_url(&link, &context.settings().get_protocol_and_hostname())?;
|
||||||
|
// This should fail softly, since pictrs might not even be running
|
||||||
|
let details_res = fetch_pictrs_proxied_image_details(&link, context).await;
|
||||||
|
|
||||||
|
if let Ok(details) = details_res {
|
||||||
|
let details_form = details.build_image_details_form(&proxied);
|
||||||
|
RemoteImage::create(&mut context.pool(), &details_form).await?;
|
||||||
|
};
|
||||||
|
|
||||||
RemoteImage::create(&mut context.pool(), vec![link]).await?;
|
|
||||||
Ok(proxied.into())
|
Ok(proxied.into())
|
||||||
} else {
|
} else {
|
||||||
Ok(link.into())
|
Ok(link.into())
|
||||||
|
@ -1004,26 +1025,25 @@ pub(crate) async fn proxy_image_link(link: Url, context: &LemmyContext) -> Lemmy
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn proxy_image_link_opt_api(
|
pub async fn proxy_image_link_opt_api(
|
||||||
link: &Option<String>,
|
link: Option<Option<DbUrl>>,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
) -> LemmyResult<Option<Option<DbUrl>>> {
|
) -> LemmyResult<Option<Option<DbUrl>>> {
|
||||||
proxy_image_link_api(link, context).await.map(Some)
|
if let Some(Some(link)) = link {
|
||||||
|
proxy_image_link(link.into(), context)
|
||||||
|
.await
|
||||||
|
.map(Some)
|
||||||
|
.map(Some)
|
||||||
|
} else {
|
||||||
|
Ok(link)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn proxy_image_link_api(
|
pub async fn proxy_image_link_api(
|
||||||
link: &Option<String>,
|
link: Option<DbUrl>,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
) -> LemmyResult<Option<DbUrl>> {
|
) -> LemmyResult<Option<DbUrl>> {
|
||||||
let link: Option<DbUrl> = match link.as_ref().map(String::as_str) {
|
if let Some(link) = link {
|
||||||
// An empty string is an erase
|
proxy_image_link(link.into(), context).await.map(Some)
|
||||||
Some("") => None,
|
|
||||||
Some(str_url) => Url::parse(str_url)
|
|
||||||
.map(|u| Some(u.into()))
|
|
||||||
.with_lemmy_type(LemmyErrorType::InvalidUrl)?,
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
if let Some(l) = link {
|
|
||||||
proxy_image_link(l.into(), context).await.map(Some)
|
|
||||||
} else {
|
} else {
|
||||||
Ok(link)
|
Ok(link)
|
||||||
}
|
}
|
||||||
|
@ -1124,35 +1144,13 @@ mod tests {
|
||||||
"https://lemmy-alpha/api/v3/image_proxy?url=http%3A%2F%2Flemmy-beta%2Fimage.png",
|
"https://lemmy-alpha/api/v3/image_proxy?url=http%3A%2F%2Flemmy-beta%2Fimage.png",
|
||||||
proxied.as_str()
|
proxied.as_str()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// This fails, because the details can't be fetched without pictrs running,
|
||||||
|
// And a remote image won't be inserted.
|
||||||
assert!(
|
assert!(
|
||||||
RemoteImage::validate(&mut context.pool(), remote_image.into())
|
RemoteImage::validate(&mut context.pool(), remote_image.into())
|
||||||
.await
|
|
||||||
.is_ok()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[serial]
|
|
||||||
async fn test_diesel_option_overwrite_to_url() {
|
|
||||||
let context = LemmyContext::init_test_context().await;
|
|
||||||
|
|
||||||
assert!(matches!(
|
|
||||||
proxy_image_link_api(&None, &context).await,
|
|
||||||
Ok(None)
|
|
||||||
));
|
|
||||||
assert!(matches!(
|
|
||||||
proxy_image_link_opt_api(&Some(String::new()), &context).await,
|
|
||||||
Ok(Some(None))
|
|
||||||
));
|
|
||||||
assert!(
|
|
||||||
proxy_image_link_opt_api(&Some("invalid_url".to_string()), &context)
|
|
||||||
.await
|
.await
|
||||||
.is_err()
|
.is_err()
|
||||||
);
|
);
|
||||||
let example_url = "https://lemmy-alpha/image.png";
|
|
||||||
assert!(matches!(
|
|
||||||
proxy_image_link_opt_api(&Some(example_url.to_string()), &context).await,
|
|
||||||
Ok(Some(Some(url))) if url == Url::parse(example_url).unwrap().into()
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ pub async fn create_comment(
|
||||||
let slur_regex = local_site_to_slur_regex(&local_site);
|
let slur_regex = local_site_to_slur_regex(&local_site);
|
||||||
let url_blocklist = get_url_blocklist(&context).await?;
|
let url_blocklist = get_url_blocklist(&context).await?;
|
||||||
let content = process_markdown(&data.content, &slur_regex, &url_blocklist, &context).await?;
|
let content = process_markdown(&data.content, &slur_regex, &url_blocklist, &context).await?;
|
||||||
is_valid_body_field(&Some(content.clone()), false)?;
|
is_valid_body_field(&content, false)?;
|
||||||
|
|
||||||
// Check for a community ban
|
// Check for a community ban
|
||||||
let post_id = data.post_id;
|
let post_id = data.post_id;
|
||||||
|
|
|
@ -63,7 +63,9 @@ pub async fn update_comment(
|
||||||
let slur_regex = local_site_to_slur_regex(&local_site);
|
let slur_regex = local_site_to_slur_regex(&local_site);
|
||||||
let url_blocklist = get_url_blocklist(&context).await?;
|
let url_blocklist = get_url_blocklist(&context).await?;
|
||||||
let content = process_markdown_opt(&data.content, &slur_regex, &url_blocklist, &context).await?;
|
let content = process_markdown_opt(&data.content, &slur_regex, &url_blocklist, &context).await?;
|
||||||
is_valid_body_field(&content, false)?;
|
if let Some(content) = &content {
|
||||||
|
is_valid_body_field(content, false)?;
|
||||||
|
}
|
||||||
|
|
||||||
let comment_id = data.comment_id;
|
let comment_id = data.comment_id;
|
||||||
let form = CommentUpdateForm {
|
let form = CommentUpdateForm {
|
||||||
|
|
|
@ -30,6 +30,7 @@ use lemmy_db_schema::{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
traits::{ApubActor, Crud, Followable, Joinable},
|
traits::{ApubActor, Crud, Followable, Joinable},
|
||||||
|
utils::diesel_url_create,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
|
@ -61,11 +62,18 @@ pub async fn create_community(
|
||||||
check_slurs(&data.title, &slur_regex)?;
|
check_slurs(&data.title, &slur_regex)?;
|
||||||
let description =
|
let description =
|
||||||
process_markdown_opt(&data.description, &slur_regex, &url_blocklist, &context).await?;
|
process_markdown_opt(&data.description, &slur_regex, &url_blocklist, &context).await?;
|
||||||
let icon = proxy_image_link_api(&data.icon, &context).await?;
|
|
||||||
let banner = proxy_image_link_api(&data.banner, &context).await?;
|
let icon = diesel_url_create(data.icon.as_deref())?;
|
||||||
|
let icon = proxy_image_link_api(icon, &context).await?;
|
||||||
|
|
||||||
|
let banner = diesel_url_create(data.banner.as_deref())?;
|
||||||
|
let banner = proxy_image_link_api(banner, &context).await?;
|
||||||
|
|
||||||
is_valid_actor_name(&data.name, local_site.actor_name_max_length as usize)?;
|
is_valid_actor_name(&data.name, local_site.actor_name_max_length as usize)?;
|
||||||
is_valid_body_field(&data.description, false)?;
|
|
||||||
|
if let Some(desc) = &data.description {
|
||||||
|
is_valid_body_field(desc, false)?;
|
||||||
|
}
|
||||||
|
|
||||||
// Double check for duplicate community actor_ids
|
// Double check for duplicate community actor_ids
|
||||||
let community_actor_id = generate_local_apub_endpoint(
|
let community_actor_id = generate_local_apub_endpoint(
|
||||||
|
|
|
@ -21,7 +21,7 @@ use lemmy_db_schema::{
|
||||||
local_site::LocalSite,
|
local_site::LocalSite,
|
||||||
},
|
},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
utils::{diesel_option_overwrite, naive_now},
|
utils::{diesel_string_update, diesel_url_update, naive_now},
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
|
@ -40,18 +40,28 @@ pub async fn update_community(
|
||||||
let slur_regex = local_site_to_slur_regex(&local_site);
|
let slur_regex = local_site_to_slur_regex(&local_site);
|
||||||
let url_blocklist = get_url_blocklist(&context).await?;
|
let url_blocklist = get_url_blocklist(&context).await?;
|
||||||
check_slurs_opt(&data.title, &slur_regex)?;
|
check_slurs_opt(&data.title, &slur_regex)?;
|
||||||
let description =
|
|
||||||
process_markdown_opt(&data.description, &slur_regex, &url_blocklist, &context).await?;
|
let description = diesel_string_update(
|
||||||
is_valid_body_field(&data.description, false)?;
|
process_markdown_opt(&data.description, &slur_regex, &url_blocklist, &context)
|
||||||
|
.await?
|
||||||
|
.as_deref(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(Some(desc)) = &description {
|
||||||
|
is_valid_body_field(desc, false)?;
|
||||||
|
}
|
||||||
|
|
||||||
let old_community = Community::read(&mut context.pool(), data.community_id)
|
let old_community = Community::read(&mut context.pool(), data.community_id)
|
||||||
.await?
|
.await?
|
||||||
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
|
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
|
||||||
replace_image(&data.icon, &old_community.icon, &context).await?;
|
|
||||||
replace_image(&data.banner, &old_community.banner, &context).await?;
|
|
||||||
|
|
||||||
let description = diesel_option_overwrite(description);
|
let icon = diesel_url_update(data.icon.as_deref())?;
|
||||||
let icon = proxy_image_link_opt_api(&data.icon, &context).await?;
|
replace_image(&icon, &old_community.icon, &context).await?;
|
||||||
let banner = proxy_image_link_opt_api(&data.banner, &context).await?;
|
let icon = proxy_image_link_opt_api(icon, &context).await?;
|
||||||
|
|
||||||
|
let banner = diesel_url_update(data.banner.as_deref())?;
|
||||||
|
replace_image(&banner, &old_community.banner, &context).await?;
|
||||||
|
let banner = proxy_image_link_opt_api(banner, &context).await?;
|
||||||
|
|
||||||
// Verify its a mod (only mods can edit it)
|
// Verify its a mod (only mods can edit it)
|
||||||
check_community_mod_action(
|
check_community_mod_action(
|
||||||
|
|
|
@ -26,6 +26,7 @@ use lemmy_db_schema::{
|
||||||
post::{Post, PostInsertForm, PostLike, PostLikeForm, PostUpdateForm},
|
post::{Post, PostInsertForm, PostLike, PostLikeForm, PostUpdateForm},
|
||||||
},
|
},
|
||||||
traits::{Crud, Likeable},
|
traits::{Crud, Likeable},
|
||||||
|
utils::diesel_url_create,
|
||||||
CommunityVisibility,
|
CommunityVisibility,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
|
@ -37,7 +38,6 @@ use lemmy_utils::{
|
||||||
slurs::check_slurs,
|
slurs::check_slurs,
|
||||||
validation::{
|
validation::{
|
||||||
check_url_scheme,
|
check_url_scheme,
|
||||||
clean_url_params,
|
|
||||||
is_url_blocked,
|
is_url_blocked,
|
||||||
is_valid_alt_text_field,
|
is_valid_alt_text_field,
|
||||||
is_valid_body_field,
|
is_valid_body_field,
|
||||||
|
@ -64,16 +64,27 @@ pub async fn create_post(
|
||||||
let url_blocklist = get_url_blocklist(&context).await?;
|
let url_blocklist = get_url_blocklist(&context).await?;
|
||||||
|
|
||||||
let body = process_markdown_opt(&data.body, &slur_regex, &url_blocklist, &context).await?;
|
let body = process_markdown_opt(&data.body, &slur_regex, &url_blocklist, &context).await?;
|
||||||
let data_url = data.url.as_ref();
|
let url = diesel_url_create(data.url.as_deref())?;
|
||||||
let url = data_url.map(clean_url_params); // TODO no good way to handle a "clear"
|
let custom_thumbnail = diesel_url_create(data.custom_thumbnail.as_deref())?;
|
||||||
let custom_thumbnail = data.custom_thumbnail.as_ref().map(clean_url_params);
|
|
||||||
|
|
||||||
is_valid_post_title(&data.name)?;
|
is_valid_post_title(&data.name)?;
|
||||||
is_valid_body_field(&body, true)?;
|
|
||||||
is_valid_alt_text_field(&data.alt_text)?;
|
if let Some(url) = &url {
|
||||||
is_url_blocked(&url, &url_blocklist)?;
|
is_url_blocked(url, &url_blocklist)?;
|
||||||
check_url_scheme(&url)?;
|
check_url_scheme(url)?;
|
||||||
check_url_scheme(&custom_thumbnail)?;
|
}
|
||||||
|
|
||||||
|
if let Some(custom_thumbnail) = &custom_thumbnail {
|
||||||
|
check_url_scheme(custom_thumbnail)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(alt_text) = &data.alt_text {
|
||||||
|
is_valid_alt_text_field(alt_text)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(body) = &body {
|
||||||
|
is_valid_body_field(body, true)?;
|
||||||
|
}
|
||||||
|
|
||||||
check_community_user_action(
|
check_community_user_action(
|
||||||
&local_user_view.person,
|
&local_user_view.person,
|
||||||
|
@ -156,7 +167,7 @@ pub async fn create_post(
|
||||||
|
|
||||||
generate_post_link_metadata(
|
generate_post_link_metadata(
|
||||||
updated_post.clone(),
|
updated_post.clone(),
|
||||||
custom_thumbnail,
|
custom_thumbnail.map(Into::into),
|
||||||
|post| Some(SendActivityData::CreatePost(post)),
|
|post| Some(SendActivityData::CreatePost(post)),
|
||||||
Some(local_site),
|
Some(local_site),
|
||||||
context.reset_request_count(),
|
context.reset_request_count(),
|
||||||
|
|
|
@ -83,11 +83,13 @@ pub async fn get_post(
|
||||||
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
|
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
|
||||||
|
|
||||||
let moderators = CommunityModeratorView::for_community(&mut context.pool(), community_id).await?;
|
let moderators = CommunityModeratorView::for_community(&mut context.pool(), community_id).await?;
|
||||||
|
let local_user = local_user_view.as_ref().map(|u| &u.local_user);
|
||||||
|
|
||||||
// Fetch the cross_posts
|
// Fetch the cross_posts
|
||||||
let cross_posts = if let Some(url) = &post_view.post.url {
|
let cross_posts = if let Some(url) = &post_view.post.url {
|
||||||
let mut x_posts = PostQuery {
|
let mut x_posts = PostQuery {
|
||||||
url_search: Some(url.inner().as_str().into()),
|
url_search: Some(url.inner().as_str().into()),
|
||||||
|
local_user,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
.list(&local_site.site, &mut context.pool())
|
.list(&local_site.site, &mut context.pool())
|
||||||
|
|
|
@ -20,16 +20,15 @@ use lemmy_db_schema::{
|
||||||
post::{Post, PostUpdateForm},
|
post::{Post, PostUpdateForm},
|
||||||
},
|
},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
utils::{diesel_option_overwrite, naive_now},
|
utils::{diesel_string_update, diesel_url_update, naive_now},
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
|
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
|
||||||
utils::{
|
utils::{
|
||||||
slurs::check_slurs_opt,
|
slurs::check_slurs,
|
||||||
validation::{
|
validation::{
|
||||||
check_url_scheme,
|
check_url_scheme,
|
||||||
clean_url_params,
|
|
||||||
is_url_blocked,
|
is_url_blocked,
|
||||||
is_valid_alt_text_field,
|
is_valid_alt_text_field,
|
||||||
is_valid_body_field,
|
is_valid_body_field,
|
||||||
|
@ -47,26 +46,43 @@ pub async fn update_post(
|
||||||
) -> LemmyResult<Json<PostResponse>> {
|
) -> LemmyResult<Json<PostResponse>> {
|
||||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||||
|
|
||||||
// TODO No good way to handle a clear.
|
let url = diesel_url_update(data.url.as_deref())?;
|
||||||
// Issue link: https://github.com/LemmyNet/lemmy/issues/2287
|
|
||||||
let url = data.url.as_ref().map(clean_url_params);
|
let custom_thumbnail = diesel_url_update(data.custom_thumbnail.as_deref())?;
|
||||||
let custom_thumbnail = data.custom_thumbnail.as_ref().map(clean_url_params);
|
|
||||||
|
|
||||||
let url_blocklist = get_url_blocklist(&context).await?;
|
let url_blocklist = get_url_blocklist(&context).await?;
|
||||||
|
|
||||||
let slur_regex = local_site_to_slur_regex(&local_site);
|
let slur_regex = local_site_to_slur_regex(&local_site);
|
||||||
check_slurs_opt(&data.name, &slur_regex)?;
|
|
||||||
let body = process_markdown_opt(&data.body, &slur_regex, &url_blocklist, &context).await?;
|
let body = diesel_string_update(
|
||||||
|
process_markdown_opt(&data.body, &slur_regex, &url_blocklist, &context)
|
||||||
|
.await?
|
||||||
|
.as_deref(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let alt_text = diesel_string_update(data.alt_text.as_deref());
|
||||||
|
|
||||||
if let Some(name) = &data.name {
|
if let Some(name) = &data.name {
|
||||||
is_valid_post_title(name)?;
|
is_valid_post_title(name)?;
|
||||||
|
check_slurs(name, &slur_regex)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
is_valid_body_field(&body, true)?;
|
if let Some(Some(body)) = &body {
|
||||||
is_valid_alt_text_field(&data.alt_text)?;
|
is_valid_body_field(body, true)?;
|
||||||
is_url_blocked(&url, &url_blocklist)?;
|
}
|
||||||
check_url_scheme(&url)?;
|
|
||||||
check_url_scheme(&custom_thumbnail)?;
|
if let Some(Some(alt_text)) = &alt_text {
|
||||||
|
is_valid_alt_text_field(alt_text)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(Some(url)) = &url {
|
||||||
|
is_url_blocked(url, &url_blocklist)?;
|
||||||
|
check_url_scheme(url)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(Some(custom_thumbnail)) = &custom_thumbnail {
|
||||||
|
check_url_scheme(custom_thumbnail)?;
|
||||||
|
}
|
||||||
|
|
||||||
let post_id = data.post_id;
|
let post_id = data.post_id;
|
||||||
let orig_post = Post::read(&mut context.pool(), post_id)
|
let orig_post = Post::read(&mut context.pool(), post_id)
|
||||||
|
@ -95,9 +111,9 @@ pub async fn update_post(
|
||||||
|
|
||||||
let post_form = PostUpdateForm {
|
let post_form = PostUpdateForm {
|
||||||
name: data.name.clone(),
|
name: data.name.clone(),
|
||||||
url: Some(url.map(Into::into)),
|
url,
|
||||||
body: diesel_option_overwrite(body),
|
body,
|
||||||
alt_text: diesel_option_overwrite(data.alt_text.clone()),
|
alt_text,
|
||||||
nsfw: data.nsfw,
|
nsfw: data.nsfw,
|
||||||
language_id: data.language_id,
|
language_id: data.language_id,
|
||||||
updated: Some(Some(naive_now())),
|
updated: Some(Some(naive_now())),
|
||||||
|
@ -111,7 +127,7 @@ pub async fn update_post(
|
||||||
|
|
||||||
generate_post_link_metadata(
|
generate_post_link_metadata(
|
||||||
updated_post.clone(),
|
updated_post.clone(),
|
||||||
custom_thumbnail,
|
custom_thumbnail.flatten().map(Into::into),
|
||||||
|post| Some(SendActivityData::UpdatePost(post)),
|
|post| Some(SendActivityData::UpdatePost(post)),
|
||||||
Some(local_site),
|
Some(local_site),
|
||||||
context.reset_request_count(),
|
context.reset_request_count(),
|
||||||
|
|
|
@ -39,7 +39,7 @@ pub async fn create_private_message(
|
||||||
let slur_regex = local_site_to_slur_regex(&local_site);
|
let slur_regex = local_site_to_slur_regex(&local_site);
|
||||||
let url_blocklist = get_url_blocklist(&context).await?;
|
let url_blocklist = get_url_blocklist(&context).await?;
|
||||||
let content = process_markdown(&data.content, &slur_regex, &url_blocklist, &context).await?;
|
let content = process_markdown(&data.content, &slur_regex, &url_blocklist, &context).await?;
|
||||||
is_valid_body_field(&Some(content.clone()), false)?;
|
is_valid_body_field(&content, false)?;
|
||||||
|
|
||||||
check_person_block(
|
check_person_block(
|
||||||
local_user_view.person.id,
|
local_user_view.person.id,
|
||||||
|
|
|
@ -41,7 +41,7 @@ pub async fn update_private_message(
|
||||||
let slur_regex = local_site_to_slur_regex(&local_site);
|
let slur_regex = local_site_to_slur_regex(&local_site);
|
||||||
let url_blocklist = get_url_blocklist(&context).await?;
|
let url_blocklist = get_url_blocklist(&context).await?;
|
||||||
let content = process_markdown(&data.content, &slur_regex, &url_blocklist, &context).await?;
|
let content = process_markdown(&data.content, &slur_regex, &url_blocklist, &context).await?;
|
||||||
is_valid_body_field(&Some(content.clone()), false)?;
|
is_valid_body_field(&content, false)?;
|
||||||
|
|
||||||
let private_message_id = data.private_message_id;
|
let private_message_id = data.private_message_id;
|
||||||
PrivateMessage::update(
|
PrivateMessage::update(
|
||||||
|
|
|
@ -11,7 +11,7 @@ use lemmy_api_common::{
|
||||||
local_site_rate_limit_to_rate_limit_config,
|
local_site_rate_limit_to_rate_limit_config,
|
||||||
local_site_to_slur_regex,
|
local_site_to_slur_regex,
|
||||||
process_markdown_opt,
|
process_markdown_opt,
|
||||||
proxy_image_link_opt_api,
|
proxy_image_link_api,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
|
@ -23,7 +23,7 @@ use lemmy_db_schema::{
|
||||||
tagline::Tagline,
|
tagline::Tagline,
|
||||||
},
|
},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
utils::{diesel_option_overwrite, naive_now},
|
utils::{diesel_string_update, diesel_url_create, naive_now},
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
|
@ -61,21 +61,25 @@ pub async fn create_site(
|
||||||
let slur_regex = local_site_to_slur_regex(&local_site);
|
let slur_regex = local_site_to_slur_regex(&local_site);
|
||||||
let url_blocklist = get_url_blocklist(&context).await?;
|
let url_blocklist = get_url_blocklist(&context).await?;
|
||||||
let sidebar = process_markdown_opt(&data.sidebar, &slur_regex, &url_blocklist, &context).await?;
|
let sidebar = process_markdown_opt(&data.sidebar, &slur_regex, &url_blocklist, &context).await?;
|
||||||
let icon = proxy_image_link_opt_api(&data.icon, &context).await?;
|
|
||||||
let banner = proxy_image_link_opt_api(&data.banner, &context).await?;
|
let icon = diesel_url_create(data.icon.as_deref())?;
|
||||||
|
let icon = proxy_image_link_api(icon, &context).await?;
|
||||||
|
|
||||||
|
let banner = diesel_url_create(data.banner.as_deref())?;
|
||||||
|
let banner = proxy_image_link_api(banner, &context).await?;
|
||||||
|
|
||||||
let site_form = SiteUpdateForm {
|
let site_form = SiteUpdateForm {
|
||||||
name: Some(data.name.clone()),
|
name: Some(data.name.clone()),
|
||||||
sidebar: diesel_option_overwrite(sidebar),
|
sidebar: diesel_string_update(sidebar.as_deref()),
|
||||||
description: diesel_option_overwrite(data.description.clone()),
|
description: diesel_string_update(data.description.as_deref()),
|
||||||
icon,
|
icon: Some(icon),
|
||||||
banner,
|
banner: Some(banner),
|
||||||
actor_id: Some(actor_id),
|
actor_id: Some(actor_id),
|
||||||
last_refreshed_at: Some(naive_now()),
|
last_refreshed_at: Some(naive_now()),
|
||||||
inbox_url,
|
inbox_url,
|
||||||
private_key: Some(Some(keypair.private_key)),
|
private_key: Some(Some(keypair.private_key)),
|
||||||
public_key: Some(keypair.public_key),
|
public_key: Some(keypair.public_key),
|
||||||
content_warning: diesel_option_overwrite(data.content_warning.clone()),
|
content_warning: diesel_string_update(data.content_warning.as_deref()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -91,16 +95,16 @@ pub async fn create_site(
|
||||||
enable_nsfw: data.enable_nsfw,
|
enable_nsfw: data.enable_nsfw,
|
||||||
community_creation_admin_only: data.community_creation_admin_only,
|
community_creation_admin_only: data.community_creation_admin_only,
|
||||||
require_email_verification: data.require_email_verification,
|
require_email_verification: data.require_email_verification,
|
||||||
application_question: diesel_option_overwrite(data.application_question.clone()),
|
application_question: diesel_string_update(data.application_question.as_deref()),
|
||||||
private_instance: data.private_instance,
|
private_instance: data.private_instance,
|
||||||
default_theme: data.default_theme.clone(),
|
default_theme: data.default_theme.clone(),
|
||||||
default_post_listing_type: data.default_post_listing_type,
|
default_post_listing_type: data.default_post_listing_type,
|
||||||
default_sort_type: data.default_sort_type,
|
default_sort_type: data.default_sort_type,
|
||||||
legal_information: diesel_option_overwrite(data.legal_information.clone()),
|
legal_information: diesel_string_update(data.legal_information.as_deref()),
|
||||||
application_email_admins: data.application_email_admins,
|
application_email_admins: data.application_email_admins,
|
||||||
hide_modlog_mod_names: data.hide_modlog_mod_names,
|
hide_modlog_mod_names: data.hide_modlog_mod_names,
|
||||||
updated: Some(Some(naive_now())),
|
updated: Some(Some(naive_now())),
|
||||||
slur_filter_regex: diesel_option_overwrite(data.slur_filter_regex.clone()),
|
slur_filter_regex: diesel_string_update(data.slur_filter_regex.as_deref()),
|
||||||
actor_name_max_length: data.actor_name_max_length,
|
actor_name_max_length: data.actor_name_max_length,
|
||||||
federation_enabled: data.federation_enabled,
|
federation_enabled: data.federation_enabled,
|
||||||
captcha_enabled: data.captcha_enabled,
|
captcha_enabled: data.captcha_enabled,
|
||||||
|
@ -179,7 +183,9 @@ fn validate_create_payload(local_site: &LocalSite, create_site: &CreateSite) ->
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Ensure that the sidebar has fewer than the max num characters...
|
// Ensure that the sidebar has fewer than the max num characters...
|
||||||
is_valid_body_field(&create_site.sidebar, false)?;
|
if let Some(body) = &create_site.sidebar {
|
||||||
|
is_valid_body_field(body, false)?;
|
||||||
|
}
|
||||||
|
|
||||||
application_question_check(
|
application_question_check(
|
||||||
&local_site.application_question,
|
&local_site.application_question,
|
||||||
|
|
|
@ -27,7 +27,7 @@ use lemmy_db_schema::{
|
||||||
tagline::Tagline,
|
tagline::Tagline,
|
||||||
},
|
},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
utils::{diesel_option_overwrite, naive_now},
|
utils::{diesel_string_update, diesel_url_update, naive_now},
|
||||||
RegistrationMode,
|
RegistrationMode,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
||||||
|
@ -67,22 +67,29 @@ pub async fn update_site(
|
||||||
SiteLanguage::update(&mut context.pool(), discussion_languages.clone(), &site).await?;
|
SiteLanguage::update(&mut context.pool(), discussion_languages.clone(), &site).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
replace_image(&data.icon, &site.icon, &context).await?;
|
|
||||||
replace_image(&data.banner, &site.banner, &context).await?;
|
|
||||||
|
|
||||||
let slur_regex = local_site_to_slur_regex(&local_site);
|
let slur_regex = local_site_to_slur_regex(&local_site);
|
||||||
let url_blocklist = get_url_blocklist(&context).await?;
|
let url_blocklist = get_url_blocklist(&context).await?;
|
||||||
let sidebar = process_markdown_opt(&data.sidebar, &slur_regex, &url_blocklist, &context).await?;
|
let sidebar = diesel_string_update(
|
||||||
let icon = proxy_image_link_opt_api(&data.icon, &context).await?;
|
process_markdown_opt(&data.sidebar, &slur_regex, &url_blocklist, &context)
|
||||||
let banner = proxy_image_link_opt_api(&data.banner, &context).await?;
|
.await?
|
||||||
|
.as_deref(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let icon = diesel_url_update(data.icon.as_deref())?;
|
||||||
|
replace_image(&icon, &site.icon, &context).await?;
|
||||||
|
let icon = proxy_image_link_opt_api(icon, &context).await?;
|
||||||
|
|
||||||
|
let banner = diesel_url_update(data.banner.as_deref())?;
|
||||||
|
replace_image(&banner, &site.banner, &context).await?;
|
||||||
|
let banner = proxy_image_link_opt_api(banner, &context).await?;
|
||||||
|
|
||||||
let site_form = SiteUpdateForm {
|
let site_form = SiteUpdateForm {
|
||||||
name: data.name.clone(),
|
name: data.name.clone(),
|
||||||
sidebar: diesel_option_overwrite(sidebar),
|
sidebar,
|
||||||
description: diesel_option_overwrite(data.description.clone()),
|
description: diesel_string_update(data.description.as_deref()),
|
||||||
icon,
|
icon,
|
||||||
banner,
|
banner,
|
||||||
content_warning: diesel_option_overwrite(data.content_warning.clone()),
|
content_warning: diesel_string_update(data.content_warning.as_deref()),
|
||||||
updated: Some(Some(naive_now())),
|
updated: Some(Some(naive_now())),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
@ -99,16 +106,16 @@ pub async fn update_site(
|
||||||
enable_nsfw: data.enable_nsfw,
|
enable_nsfw: data.enable_nsfw,
|
||||||
community_creation_admin_only: data.community_creation_admin_only,
|
community_creation_admin_only: data.community_creation_admin_only,
|
||||||
require_email_verification: data.require_email_verification,
|
require_email_verification: data.require_email_verification,
|
||||||
application_question: diesel_option_overwrite(data.application_question.clone()),
|
application_question: diesel_string_update(data.application_question.as_deref()),
|
||||||
private_instance: data.private_instance,
|
private_instance: data.private_instance,
|
||||||
default_theme: data.default_theme.clone(),
|
default_theme: data.default_theme.clone(),
|
||||||
default_post_listing_type: data.default_post_listing_type,
|
default_post_listing_type: data.default_post_listing_type,
|
||||||
default_sort_type: data.default_sort_type,
|
default_sort_type: data.default_sort_type,
|
||||||
legal_information: diesel_option_overwrite(data.legal_information.clone()),
|
legal_information: diesel_string_update(data.legal_information.as_deref()),
|
||||||
application_email_admins: data.application_email_admins,
|
application_email_admins: data.application_email_admins,
|
||||||
hide_modlog_mod_names: data.hide_modlog_mod_names,
|
hide_modlog_mod_names: data.hide_modlog_mod_names,
|
||||||
updated: Some(Some(naive_now())),
|
updated: Some(Some(naive_now())),
|
||||||
slur_filter_regex: diesel_option_overwrite(data.slur_filter_regex.clone()),
|
slur_filter_regex: diesel_string_update(data.slur_filter_regex.as_deref()),
|
||||||
actor_name_max_length: data.actor_name_max_length,
|
actor_name_max_length: data.actor_name_max_length,
|
||||||
federation_enabled: data.federation_enabled,
|
federation_enabled: data.federation_enabled,
|
||||||
captcha_enabled: data.captcha_enabled,
|
captcha_enabled: data.captcha_enabled,
|
||||||
|
@ -229,7 +236,9 @@ fn validate_update_payload(local_site: &LocalSite, edit_site: &EditSite) -> Lemm
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Ensure that the sidebar has fewer than the max num characters...
|
// Ensure that the sidebar has fewer than the max num characters...
|
||||||
is_valid_body_field(&edit_site.sidebar, false)?;
|
if let Some(body) = &edit_site.sidebar {
|
||||||
|
is_valid_body_field(body, false)?;
|
||||||
|
}
|
||||||
|
|
||||||
application_question_check(
|
application_question_check(
|
||||||
&local_site.application_question,
|
&local_site.application_question,
|
||||||
|
|
|
@ -112,15 +112,17 @@ pub async fn register(
|
||||||
// We have to create both a person, and local_user
|
// We have to create both a person, and local_user
|
||||||
|
|
||||||
// Register the new person
|
// Register the new person
|
||||||
let person_form = PersonInsertForm::builder()
|
let person_form = PersonInsertForm {
|
||||||
.name(data.username.clone())
|
actor_id: Some(actor_id.clone()),
|
||||||
.actor_id(Some(actor_id.clone()))
|
inbox_url: Some(generate_inbox_url(&actor_id)?),
|
||||||
.private_key(Some(actor_keypair.private_key))
|
shared_inbox_url: Some(generate_shared_inbox_url(context.settings())?),
|
||||||
.public_key(actor_keypair.public_key)
|
private_key: Some(actor_keypair.private_key),
|
||||||
.inbox_url(Some(generate_inbox_url(&actor_id)?))
|
..PersonInsertForm::new(
|
||||||
.shared_inbox_url(Some(generate_shared_inbox_url(context.settings())?))
|
data.username.clone(),
|
||||||
.instance_id(site_view.site.instance_id)
|
actor_keypair.public_key,
|
||||||
.build();
|
site_view.site.instance_id,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
// insert the person
|
// insert the person
|
||||||
let inserted_person = Person::create(&mut context.pool(), &person_form)
|
let inserted_person = Person::create(&mut context.pool(), &person_form)
|
||||||
|
|
|
@ -37,11 +37,11 @@ pub async fn list_comments(
|
||||||
};
|
};
|
||||||
let sort = data.sort;
|
let sort = data.sort;
|
||||||
let max_depth = data.max_depth;
|
let max_depth = data.max_depth;
|
||||||
let saved_only = data.saved_only.unwrap_or_default();
|
let saved_only = data.saved_only;
|
||||||
|
|
||||||
let liked_only = data.liked_only.unwrap_or_default();
|
let liked_only = data.liked_only;
|
||||||
let disliked_only = data.disliked_only.unwrap_or_default();
|
let disliked_only = data.disliked_only;
|
||||||
if liked_only && disliked_only {
|
if liked_only.unwrap_or_default() && disliked_only.unwrap_or_default() {
|
||||||
return Err(LemmyError::from(LemmyErrorType::ContradictingFilters));
|
return Err(LemmyError::from(LemmyErrorType::ContradictingFilters));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,6 +70,8 @@ pub async fn list_comments(
|
||||||
|
|
||||||
let parent_path_cloned = parent_path.clone();
|
let parent_path_cloned = parent_path.clone();
|
||||||
let post_id = data.post_id;
|
let post_id = data.post_id;
|
||||||
|
let local_user = local_user_view.as_ref().map(|l| &l.local_user);
|
||||||
|
|
||||||
let comments = CommentQuery {
|
let comments = CommentQuery {
|
||||||
listing_type,
|
listing_type,
|
||||||
sort,
|
sort,
|
||||||
|
@ -80,7 +82,7 @@ pub async fn list_comments(
|
||||||
community_id,
|
community_id,
|
||||||
parent_path: parent_path_cloned,
|
parent_path: parent_path_cloned,
|
||||||
post_id,
|
post_id,
|
||||||
local_user: local_user_view.as_ref(),
|
local_user,
|
||||||
page,
|
page,
|
||||||
limit,
|
limit,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
|
@ -40,26 +40,27 @@ pub async fn list_posts(
|
||||||
} else {
|
} else {
|
||||||
data.community_id
|
data.community_id
|
||||||
};
|
};
|
||||||
let saved_only = data.saved_only.unwrap_or_default();
|
let saved_only = data.saved_only;
|
||||||
let show_hidden = data.show_hidden.unwrap_or_default();
|
let show_hidden = data.show_hidden;
|
||||||
|
let show_read = data.show_read;
|
||||||
|
|
||||||
let liked_only = data.liked_only.unwrap_or_default();
|
let liked_only = data.liked_only;
|
||||||
let disliked_only = data.disliked_only.unwrap_or_default();
|
let disliked_only = data.disliked_only;
|
||||||
if liked_only && disliked_only {
|
if liked_only.unwrap_or_default() && disliked_only.unwrap_or_default() {
|
||||||
return Err(LemmyError::from(LemmyErrorType::ContradictingFilters));
|
return Err(LemmyError::from(LemmyErrorType::ContradictingFilters));
|
||||||
}
|
}
|
||||||
|
|
||||||
let local_user_ref = local_user_view.as_ref().map(|u| &u.local_user);
|
let local_user = local_user_view.as_ref().map(|u| &u.local_user);
|
||||||
let listing_type = Some(listing_type_with_default(
|
let listing_type = Some(listing_type_with_default(
|
||||||
data.type_,
|
data.type_,
|
||||||
local_user_ref,
|
local_user,
|
||||||
&local_site.local_site,
|
&local_site.local_site,
|
||||||
community_id,
|
community_id,
|
||||||
));
|
));
|
||||||
|
|
||||||
let sort = Some(sort_type_with_default(
|
let sort = Some(sort_type_with_default(
|
||||||
data.sort,
|
data.sort,
|
||||||
local_user_ref,
|
local_user,
|
||||||
&local_site.local_site,
|
&local_site.local_site,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -71,7 +72,7 @@ pub async fn list_posts(
|
||||||
};
|
};
|
||||||
|
|
||||||
let posts = PostQuery {
|
let posts = PostQuery {
|
||||||
local_user: local_user_view.as_ref(),
|
local_user,
|
||||||
listing_type,
|
listing_type,
|
||||||
sort,
|
sort,
|
||||||
community_id,
|
community_id,
|
||||||
|
@ -82,6 +83,7 @@ pub async fn list_posts(
|
||||||
page_after,
|
page_after,
|
||||||
limit,
|
limit,
|
||||||
show_hidden,
|
show_hidden,
|
||||||
|
show_read,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
.list(&local_site.site, &mut context.pool())
|
.list(&local_site.site, &mut context.pool())
|
||||||
|
|
|
@ -55,20 +55,22 @@ pub async fn read_person(
|
||||||
let sort = data.sort;
|
let sort = data.sort;
|
||||||
let page = data.page;
|
let page = data.page;
|
||||||
let limit = data.limit;
|
let limit = data.limit;
|
||||||
let saved_only = data.saved_only.unwrap_or_default();
|
let saved_only = data.saved_only;
|
||||||
let community_id = data.community_id;
|
let community_id = data.community_id;
|
||||||
// If its saved only, you don't care what creator it was
|
// If its saved only, you don't care what creator it was
|
||||||
// Or, if its not saved, then you only want it for that specific creator
|
// Or, if its not saved, then you only want it for that specific creator
|
||||||
let creator_id = if !saved_only {
|
let creator_id = if !saved_only.unwrap_or_default() {
|
||||||
Some(person_details_id)
|
Some(person_details_id)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let local_user = local_user_view.as_ref().map(|l| &l.local_user);
|
||||||
|
|
||||||
let posts = PostQuery {
|
let posts = PostQuery {
|
||||||
sort,
|
sort,
|
||||||
saved_only,
|
saved_only,
|
||||||
local_user: local_user_view.as_ref(),
|
local_user,
|
||||||
community_id,
|
community_id,
|
||||||
page,
|
page,
|
||||||
limit,
|
limit,
|
||||||
|
@ -79,7 +81,7 @@ pub async fn read_person(
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let comments = CommentQuery {
|
let comments = CommentQuery {
|
||||||
local_user: local_user_view.as_ref(),
|
local_user,
|
||||||
sort: sort.map(post_to_comment_sort_type),
|
sort: sort.map(post_to_comment_sort_type),
|
||||||
saved_only,
|
saved_only,
|
||||||
community_id,
|
community_id,
|
||||||
|
|
|
@ -55,7 +55,7 @@ pub async fn search(
|
||||||
data.community_id
|
data.community_id
|
||||||
};
|
};
|
||||||
let creator_id = data.creator_id;
|
let creator_id = data.creator_id;
|
||||||
let local_user = local_user_view.as_ref().map(|luv| &luv.local_user);
|
let local_user = local_user_view.as_ref().map(|l| &l.local_user);
|
||||||
|
|
||||||
match search_type {
|
match search_type {
|
||||||
SearchType::Posts => {
|
SearchType::Posts => {
|
||||||
|
@ -64,7 +64,7 @@ pub async fn search(
|
||||||
listing_type: (listing_type),
|
listing_type: (listing_type),
|
||||||
community_id: (community_id),
|
community_id: (community_id),
|
||||||
creator_id: (creator_id),
|
creator_id: (creator_id),
|
||||||
local_user: (local_user_view.as_ref()),
|
local_user,
|
||||||
search_term: (Some(q)),
|
search_term: (Some(q)),
|
||||||
page: (page),
|
page: (page),
|
||||||
limit: (limit),
|
limit: (limit),
|
||||||
|
@ -80,7 +80,7 @@ pub async fn search(
|
||||||
search_term: (Some(q)),
|
search_term: (Some(q)),
|
||||||
community_id: (community_id),
|
community_id: (community_id),
|
||||||
creator_id: (creator_id),
|
creator_id: (creator_id),
|
||||||
local_user: (local_user_view.as_ref()),
|
local_user,
|
||||||
page: (page),
|
page: (page),
|
||||||
limit: (limit),
|
limit: (limit),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -125,7 +125,7 @@ pub async fn search(
|
||||||
listing_type: (listing_type),
|
listing_type: (listing_type),
|
||||||
community_id: (community_id),
|
community_id: (community_id),
|
||||||
creator_id: (creator_id),
|
creator_id: (creator_id),
|
||||||
local_user: (local_user_view.as_ref()),
|
local_user,
|
||||||
search_term: (Some(q)),
|
search_term: (Some(q)),
|
||||||
page: (page),
|
page: (page),
|
||||||
limit: (limit),
|
limit: (limit),
|
||||||
|
@ -142,7 +142,7 @@ pub async fn search(
|
||||||
search_term: (Some(q)),
|
search_term: (Some(q)),
|
||||||
community_id: (community_id),
|
community_id: (community_id),
|
||||||
creator_id: (creator_id),
|
creator_id: (creator_id),
|
||||||
local_user: (local_user_view.as_ref()),
|
local_user,
|
||||||
page: (page),
|
page: (page),
|
||||||
limit: (limit),
|
limit: (limit),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -192,6 +192,7 @@ pub async fn search(
|
||||||
community_id: (community_id),
|
community_id: (community_id),
|
||||||
creator_id: (creator_id),
|
creator_id: (creator_id),
|
||||||
url_search: (Some(q)),
|
url_search: (Some(q)),
|
||||||
|
local_user,
|
||||||
page: (page),
|
page: (page),
|
||||||
limit: (limit),
|
limit: (limit),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
|
@ -338,13 +338,11 @@ mod tests {
|
||||||
context: &Data<LemmyContext>,
|
context: &Data<LemmyContext>,
|
||||||
) -> LemmyResult<LocalUserView> {
|
) -> LemmyResult<LocalUserView> {
|
||||||
let instance = Instance::read_or_create(&mut context.pool(), "example.com".to_string()).await?;
|
let instance = Instance::read_or_create(&mut context.pool(), "example.com".to_string()).await?;
|
||||||
let person_form = PersonInsertForm::builder()
|
let person_form = PersonInsertForm {
|
||||||
.name(name.clone())
|
display_name: Some(name.clone()),
|
||||||
.display_name(Some(name.clone()))
|
bio,
|
||||||
.bio(bio)
|
..PersonInsertForm::test_form(instance.id, &name)
|
||||||
.public_key("asd".to_string())
|
};
|
||||||
.instance_id(instance.id)
|
|
||||||
.build();
|
|
||||||
let person = Person::create(&mut context.pool(), &person_form).await?;
|
let person = Person::create(&mut context.pool(), &person_form).await?;
|
||||||
|
|
||||||
let user_form = LocalUserInsertForm::builder()
|
let user_form = LocalUserInsertForm::builder()
|
||||||
|
|
|
@ -129,11 +129,7 @@ mod tests {
|
||||||
let inserted_instance =
|
let inserted_instance =
|
||||||
Instance::read_or_create(&mut context.pool(), "my_domain.tld".to_string()).await?;
|
Instance::read_or_create(&mut context.pool(), "my_domain.tld".to_string()).await?;
|
||||||
|
|
||||||
let old_mod = PersonInsertForm::builder()
|
let old_mod = PersonInsertForm::test_form(inserted_instance.id, "holly");
|
||||||
.name("holly".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let old_mod = Person::create(&mut context.pool(), &old_mod).await?;
|
let old_mod = Person::create(&mut context.pool(), &old_mod).await?;
|
||||||
let community_moderator_form = CommunityModeratorForm {
|
let community_moderator_form = CommunityModeratorForm {
|
||||||
|
|
|
@ -219,7 +219,10 @@ impl Object for ApubPost {
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
check_url_scheme(&url)?;
|
|
||||||
|
if let Some(url) = &url {
|
||||||
|
check_url_scheme(url)?;
|
||||||
|
}
|
||||||
|
|
||||||
let alt_text = first_attachment.cloned().and_then(Attachment::alt_text);
|
let alt_text = first_attachment.cloned().and_then(Attachment::alt_text);
|
||||||
|
|
||||||
|
|
|
@ -72,11 +72,7 @@ async fn try_main() -> LemmyResult<()> {
|
||||||
println!("🫃 creating {} people", args.people);
|
println!("🫃 creating {} people", args.people);
|
||||||
let mut person_ids = vec![];
|
let mut person_ids = vec![];
|
||||||
for i in 0..args.people.get() {
|
for i in 0..args.people.get() {
|
||||||
let form = PersonInsertForm::builder()
|
let form = PersonInsertForm::test_form(instance.id, &format!("p{i}"));
|
||||||
.name(format!("p{i}"))
|
|
||||||
.public_key("pubkey".to_owned())
|
|
||||||
.instance_id(instance.id)
|
|
||||||
.build();
|
|
||||||
person_ids.push(Person::create(&mut conn.into(), &form).await?.id);
|
person_ids.push(Person::create(&mut conn.into(), &form).await?.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -81,6 +81,7 @@ uuid = { workspace = true, features = ["v4"] }
|
||||||
i-love-jesus = { workspace = true, optional = true }
|
i-love-jesus = { workspace = true, optional = true }
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
moka.workspace = true
|
moka.workspace = true
|
||||||
|
derive-new.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serial_test = { workspace = true }
|
serial_test = { workspace = true }
|
||||||
|
|
|
@ -5,12 +5,17 @@
|
||||||
-- (even if only other columns are updated) because triggers can run after the deletion of referenced rows and
|
-- (even if only other columns are updated) because triggers can run after the deletion of referenced rows and
|
||||||
-- before the automatic deletion of the row that references it. This is not a problem for insert or delete.
|
-- before the automatic deletion of the row that references it. This is not a problem for insert or delete.
|
||||||
--
|
--
|
||||||
-- After a row update begins, a concurrent update on the same row can't begin until the whole
|
-- Triggers that update multiple tables should use this order: person_aggregates, comment_aggregates,
|
||||||
-- transaction that contains the first update is finished. To reduce this locking, statements in
|
-- post_aggregates, community_aggregates, site_aggregates
|
||||||
-- triggers should be ordered based on the likelihood of concurrent writers. For example, updating
|
-- * The order matters because the updated rows are locked until the end of the transaction, and statements
|
||||||
-- site_aggregates should be done last because the same row is updated for all local stuff. If
|
-- in a trigger don't use separate transactions. This means that updates closer to the beginning cause
|
||||||
-- it were not last, then the locking period for concurrent writers would extend to include the
|
-- longer locks because the duration of each update extends the durations of the locks caused by previous
|
||||||
-- time consumed by statements that come after.
|
-- updates. Long locks are worse on rows that have more concurrent transactions trying to update them. The
|
||||||
|
-- listed order starts with tables that are less likely to have such rows.
|
||||||
|
-- https://www.postgresql.org/docs/16/transaction-iso.html#XACT-READ-COMMITTED
|
||||||
|
-- * Using the same order in every trigger matters because a deadlock is possible if multiple transactions
|
||||||
|
-- update the same rows in a different order.
|
||||||
|
-- https://www.postgresql.org/docs/current/explicit-locking.html#LOCKING-DEADLOCKS
|
||||||
--
|
--
|
||||||
--
|
--
|
||||||
-- Create triggers for both post and comments
|
-- Create triggers for both post and comments
|
||||||
|
@ -481,7 +486,7 @@ BEGIN
|
||||||
INNER JOIN old_post ON old_post.id = new_post.id
|
INNER JOIN old_post ON old_post.id = new_post.id
|
||||||
AND (old_post.featured_community,
|
AND (old_post.featured_community,
|
||||||
old_post.featured_local) != (new_post.featured_community,
|
old_post.featured_local) != (new_post.featured_community,
|
||||||
old_post.featured_local)
|
new_post.featured_local)
|
||||||
WHERE
|
WHERE
|
||||||
post_aggregates.post_id = new_post.id;
|
post_aggregates.post_id = new_post.id;
|
||||||
RETURN NULL;
|
RETURN NULL;
|
||||||
|
|
|
@ -64,19 +64,11 @@ mod tests {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let new_person = PersonInsertForm::builder()
|
let new_person = PersonInsertForm::test_form(inserted_instance.id, "thommy_comment_agg");
|
||||||
.name("thommy_comment_agg".into())
|
|
||||||
.public_key("pubkey".into())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let inserted_person = Person::create(pool, &new_person).await.unwrap();
|
let inserted_person = Person::create(pool, &new_person).await.unwrap();
|
||||||
|
|
||||||
let another_person = PersonInsertForm::builder()
|
let another_person = PersonInsertForm::test_form(inserted_instance.id, "jerry_comment_agg");
|
||||||
.name("jerry_comment_agg".into())
|
|
||||||
.public_key("pubkey".into())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let another_inserted_person = Person::create(pool, &another_person).await.unwrap();
|
let another_inserted_person = Person::create(pool, &another_person).await.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -65,19 +65,11 @@ mod tests {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let new_person = PersonInsertForm::builder()
|
let new_person = PersonInsertForm::test_form(inserted_instance.id, "thommy_community_agg");
|
||||||
.name("thommy_community_agg".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let inserted_person = Person::create(pool, &new_person).await.unwrap();
|
let inserted_person = Person::create(pool, &new_person).await.unwrap();
|
||||||
|
|
||||||
let another_person = PersonInsertForm::builder()
|
let another_person = PersonInsertForm::test_form(inserted_instance.id, "jerry_community_agg");
|
||||||
.name("jerry_community_agg".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let another_inserted_person = Person::create(pool, &another_person).await.unwrap();
|
let another_inserted_person = Person::create(pool, &another_person).await.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -49,19 +49,11 @@ mod tests {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let new_person = PersonInsertForm::builder()
|
let new_person = PersonInsertForm::test_form(inserted_instance.id, "thommy_user_agg");
|
||||||
.name("thommy_user_agg".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let inserted_person = Person::create(pool, &new_person).await.unwrap();
|
let inserted_person = Person::create(pool, &new_person).await.unwrap();
|
||||||
|
|
||||||
let another_person = PersonInsertForm::builder()
|
let another_person = PersonInsertForm::test_form(inserted_instance.id, "jerry_user_agg");
|
||||||
.name("jerry_user_agg".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let another_inserted_person = Person::create(pool, &another_person).await.unwrap();
|
let another_inserted_person = Person::create(pool, &another_person).await.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -83,19 +83,11 @@ mod tests {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let new_person = PersonInsertForm::builder()
|
let new_person = PersonInsertForm::test_form(inserted_instance.id, "thommy_community_agg");
|
||||||
.name("thommy_community_agg".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let inserted_person = Person::create(pool, &new_person).await.unwrap();
|
let inserted_person = Person::create(pool, &new_person).await.unwrap();
|
||||||
|
|
||||||
let another_person = PersonInsertForm::builder()
|
let another_person = PersonInsertForm::test_form(inserted_instance.id, "jerry_community_agg");
|
||||||
.name("jerry_community_agg".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let another_inserted_person = Person::create(pool, &another_person).await.unwrap();
|
let another_inserted_person = Person::create(pool, &another_person).await.unwrap();
|
||||||
|
|
||||||
|
@ -229,11 +221,7 @@ mod tests {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let new_person = PersonInsertForm::builder()
|
let new_person = PersonInsertForm::test_form(inserted_instance.id, "thommy_community_agg");
|
||||||
.name("thommy_community_agg".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let inserted_person = Person::create(pool, &new_person).await.unwrap();
|
let inserted_person = Person::create(pool, &new_person).await.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -42,11 +42,7 @@ mod tests {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let new_person = PersonInsertForm::builder()
|
let new_person = PersonInsertForm::test_form(inserted_instance.id, "thommy_site_agg");
|
||||||
.name("thommy_site_agg".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let inserted_person = Person::create(pool, &new_person).await.unwrap();
|
let inserted_person = Person::create(pool, &new_person).await.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -531,11 +531,7 @@ mod tests {
|
||||||
|
|
||||||
let (site, instance) = create_test_site(pool).await;
|
let (site, instance) = create_test_site(pool).await;
|
||||||
|
|
||||||
let person_form = PersonInsertForm::builder()
|
let person_form = PersonInsertForm::test_form(instance.id, "my test person");
|
||||||
.name("my test person".to_string())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(instance.id)
|
|
||||||
.build();
|
|
||||||
let person = Person::create(pool, &person_form).await.unwrap();
|
let person = Person::create(pool, &person_form).await.unwrap();
|
||||||
let local_user_form = LocalUserInsertForm::builder()
|
let local_user_form = LocalUserInsertForm::builder()
|
||||||
.person_id(person.id)
|
.person_id(person.id)
|
||||||
|
@ -647,11 +643,7 @@ mod tests {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let person_form = PersonInsertForm::builder()
|
let person_form = PersonInsertForm::test_form(instance.id, "my test person");
|
||||||
.name("my test person".to_string())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(instance.id)
|
|
||||||
.build();
|
|
||||||
let person = Person::create(pool, &person_form).await.unwrap();
|
let person = Person::create(pool, &person_form).await.unwrap();
|
||||||
let local_user_form = LocalUserInsertForm::builder()
|
let local_user_form = LocalUserInsertForm::builder()
|
||||||
.person_id(person.id)
|
.person_id(person.id)
|
||||||
|
|
|
@ -118,8 +118,9 @@ impl Crud for Comment {
|
||||||
type IdType = CommentId;
|
type IdType = CommentId;
|
||||||
|
|
||||||
/// This is unimplemented, use [[Comment::create]]
|
/// This is unimplemented, use [[Comment::create]]
|
||||||
async fn create(_pool: &mut DbPool<'_>, _comment_form: &Self::InsertForm) -> Result<Self, Error> {
|
async fn create(pool: &mut DbPool<'_>, comment_form: &Self::InsertForm) -> Result<Self, Error> {
|
||||||
unimplemented!();
|
debug_assert!(false);
|
||||||
|
Comment::create(pool, comment_form, None).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update(
|
async fn update(
|
||||||
|
@ -233,11 +234,7 @@ mod tests {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let new_person = PersonInsertForm::builder()
|
let new_person = PersonInsertForm::test_form(inserted_instance.id, "terry");
|
||||||
.name("terry".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let inserted_person = Person::create(pool, &new_person).await.unwrap();
|
let inserted_person = Person::create(pool, &new_person).await.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -434,11 +434,7 @@ mod tests {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let new_person = PersonInsertForm::builder()
|
let new_person = PersonInsertForm::test_form(inserted_instance.id, "bobbee");
|
||||||
.name("bobbee".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let inserted_person = Person::create(pool, &new_person).await.unwrap();
|
let inserted_person = Person::create(pool, &new_person).await.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
newtypes::DbUrl,
|
newtypes::DbUrl,
|
||||||
schema::{local_image, remote_image},
|
schema::{image_details, local_image, remote_image},
|
||||||
source::images::{LocalImage, LocalImageForm, RemoteImage, RemoteImageForm},
|
source::images::{
|
||||||
|
ImageDetails,
|
||||||
|
ImageDetailsForm,
|
||||||
|
LocalImage,
|
||||||
|
LocalImageForm,
|
||||||
|
RemoteImage,
|
||||||
|
RemoteImageForm,
|
||||||
|
},
|
||||||
utils::{get_conn, DbPool},
|
utils::{get_conn, DbPool},
|
||||||
};
|
};
|
||||||
use diesel::{
|
use diesel::{
|
||||||
|
@ -13,15 +20,29 @@ use diesel::{
|
||||||
NotFound,
|
NotFound,
|
||||||
QueryDsl,
|
QueryDsl,
|
||||||
};
|
};
|
||||||
use diesel_async::RunQueryDsl;
|
use diesel_async::{AsyncPgConnection, RunQueryDsl};
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
impl LocalImage {
|
impl LocalImage {
|
||||||
pub async fn create(pool: &mut DbPool<'_>, form: &LocalImageForm) -> Result<Self, Error> {
|
pub async fn create(
|
||||||
|
pool: &mut DbPool<'_>,
|
||||||
|
form: &LocalImageForm,
|
||||||
|
image_details_form: &ImageDetailsForm,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
let conn = &mut get_conn(pool).await?;
|
let conn = &mut get_conn(pool).await?;
|
||||||
insert_into(local_image::table)
|
conn
|
||||||
|
.build_transaction()
|
||||||
|
.run(|conn| {
|
||||||
|
Box::pin(async move {
|
||||||
|
let local_insert = insert_into(local_image::table)
|
||||||
.values(form)
|
.values(form)
|
||||||
.get_result::<Self>(conn)
|
.get_result::<Self>(conn)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
ImageDetails::create(conn, image_details_form).await?;
|
||||||
|
|
||||||
|
local_insert
|
||||||
|
}) as _
|
||||||
|
})
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,16 +60,26 @@ impl LocalImage {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RemoteImage {
|
impl RemoteImage {
|
||||||
pub async fn create(pool: &mut DbPool<'_>, links: Vec<Url>) -> Result<usize, Error> {
|
pub async fn create(pool: &mut DbPool<'_>, form: &ImageDetailsForm) -> Result<usize, Error> {
|
||||||
let conn = &mut get_conn(pool).await?;
|
let conn = &mut get_conn(pool).await?;
|
||||||
let forms = links
|
conn
|
||||||
.into_iter()
|
.build_transaction()
|
||||||
.map(|url| RemoteImageForm { link: url.into() })
|
.run(|conn| {
|
||||||
.collect::<Vec<_>>();
|
Box::pin(async move {
|
||||||
insert_into(remote_image::table)
|
let remote_image_form = RemoteImageForm {
|
||||||
.values(forms)
|
link: form.link.clone(),
|
||||||
|
};
|
||||||
|
let remote_insert = insert_into(remote_image::table)
|
||||||
|
.values(remote_image_form)
|
||||||
.on_conflict_do_nothing()
|
.on_conflict_do_nothing()
|
||||||
.execute(conn)
|
.execute(conn)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
ImageDetails::create(conn, form).await?;
|
||||||
|
|
||||||
|
remote_insert
|
||||||
|
}) as _
|
||||||
|
})
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,3 +98,16 @@ impl RemoteImage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ImageDetails {
|
||||||
|
pub(crate) async fn create(
|
||||||
|
conn: &mut AsyncPgConnection,
|
||||||
|
form: &ImageDetailsForm,
|
||||||
|
) -> Result<usize, Error> {
|
||||||
|
insert_into(image_details::table)
|
||||||
|
.values(form)
|
||||||
|
.on_conflict_do_nothing()
|
||||||
|
.execute(conn)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ use crate::{
|
||||||
actor_language::LocalUserLanguage,
|
actor_language::LocalUserLanguage,
|
||||||
local_user::{LocalUser, LocalUserInsertForm, LocalUserUpdateForm},
|
local_user::{LocalUser, LocalUserInsertForm, LocalUserUpdateForm},
|
||||||
local_user_vote_display_mode::{LocalUserVoteDisplayMode, LocalUserVoteDisplayModeInsertForm},
|
local_user_vote_display_mode::{LocalUserVoteDisplayMode, LocalUserVoteDisplayModeInsertForm},
|
||||||
|
site::Site,
|
||||||
},
|
},
|
||||||
utils::{
|
utils::{
|
||||||
functions::{coalesce, lower},
|
functions::{coalesce, lower},
|
||||||
|
@ -216,6 +217,44 @@ impl LocalUser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds some helper functions for an optional LocalUser
|
||||||
|
pub trait LocalUserOptionHelper {
|
||||||
|
fn person_id(&self) -> Option<PersonId>;
|
||||||
|
fn local_user_id(&self) -> Option<LocalUserId>;
|
||||||
|
fn show_bot_accounts(&self) -> bool;
|
||||||
|
fn show_read_posts(&self) -> bool;
|
||||||
|
fn is_admin(&self) -> bool;
|
||||||
|
fn show_nsfw(&self, site: &Site) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LocalUserOptionHelper for Option<&LocalUser> {
|
||||||
|
fn person_id(&self) -> Option<PersonId> {
|
||||||
|
self.map(|l| l.person_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn local_user_id(&self) -> Option<LocalUserId> {
|
||||||
|
self.map(|l| l.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_bot_accounts(&self) -> bool {
|
||||||
|
self.map(|l| l.show_bot_accounts).unwrap_or(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_read_posts(&self) -> bool {
|
||||||
|
self.map(|l| l.show_read_posts).unwrap_or(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_admin(&self) -> bool {
|
||||||
|
self.map(|l| l.admin).unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_nsfw(&self, site: &Site) -> bool {
|
||||||
|
self
|
||||||
|
.map(|l| l.show_nsfw)
|
||||||
|
.unwrap_or(site.content_warning.is_some())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl LocalUserInsertForm {
|
impl LocalUserInsertForm {
|
||||||
pub fn test_form(person_id: PersonId) -> Self {
|
pub fn test_form(person_id: PersonId) -> Self {
|
||||||
Self::builder()
|
Self::builder()
|
||||||
|
|
|
@ -513,19 +513,11 @@ mod tests {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let new_mod = PersonInsertForm::builder()
|
let new_mod = PersonInsertForm::test_form(inserted_instance.id, "the mod");
|
||||||
.name("the mod".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let inserted_mod = Person::create(pool, &new_mod).await.unwrap();
|
let inserted_mod = Person::create(pool, &new_mod).await.unwrap();
|
||||||
|
|
||||||
let new_person = PersonInsertForm::builder()
|
let new_person = PersonInsertForm::test_form(inserted_instance.id, "jim2");
|
||||||
.name("jim2".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let inserted_person = Person::create(pool, &new_person).await.unwrap();
|
let inserted_person = Person::create(pool, &new_person).await.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -70,11 +70,7 @@ mod tests {
|
||||||
|
|
||||||
// Setup
|
// Setup
|
||||||
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
|
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
|
||||||
let new_person = PersonInsertForm::builder()
|
let new_person = PersonInsertForm::test_form(inserted_instance.id, "thommy prw");
|
||||||
.name("thommy prw".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
let inserted_person = Person::create(pool, &new_person).await?;
|
let inserted_person = Person::create(pool, &new_person).await?;
|
||||||
let new_local_user = LocalUserInsertForm::builder()
|
let new_local_user = LocalUserInsertForm::builder()
|
||||||
.person_id(inserted_person.id)
|
.person_id(inserted_person.id)
|
||||||
|
|
|
@ -12,7 +12,14 @@ use crate::{
|
||||||
traits::{ApubActor, Crud, Followable},
|
traits::{ApubActor, Crud, Followable},
|
||||||
utils::{functions::lower, get_conn, naive_now, DbPool},
|
utils::{functions::lower, get_conn, naive_now, DbPool},
|
||||||
};
|
};
|
||||||
use diesel::{dsl::insert_into, result::Error, CombineDsl, ExpressionMethods, JoinOnDsl, QueryDsl};
|
use diesel::{
|
||||||
|
dsl::{insert_into, not},
|
||||||
|
result::Error,
|
||||||
|
CombineDsl,
|
||||||
|
ExpressionMethods,
|
||||||
|
JoinOnDsl,
|
||||||
|
QueryDsl,
|
||||||
|
};
|
||||||
use diesel_async::RunQueryDsl;
|
use diesel_async::RunQueryDsl;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
@ -100,6 +107,8 @@ impl Person {
|
||||||
.inner_join(post::table)
|
.inner_join(post::table)
|
||||||
.inner_join(community::table.on(post::community_id.eq(community::id)))
|
.inner_join(community::table.on(post::community_id.eq(community::id)))
|
||||||
.filter(community::local.eq(true))
|
.filter(community::local.eq(true))
|
||||||
|
.filter(not(community::deleted))
|
||||||
|
.filter(not(community::removed))
|
||||||
.filter(comment::creator_id.eq(for_creator_id))
|
.filter(comment::creator_id.eq(for_creator_id))
|
||||||
.select(community::id)
|
.select(community::id)
|
||||||
.union(
|
.union(
|
||||||
|
@ -116,11 +125,7 @@ impl Person {
|
||||||
|
|
||||||
impl PersonInsertForm {
|
impl PersonInsertForm {
|
||||||
pub fn test_form(instance_id: InstanceId, name: &str) -> Self {
|
pub fn test_form(instance_id: InstanceId, name: &str) -> Self {
|
||||||
Self::builder()
|
Self::new(name.to_owned(), "pubkey".to_string(), instance_id)
|
||||||
.name(name.to_owned())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(instance_id)
|
|
||||||
.build()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,11 +245,7 @@ mod tests {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let new_person = PersonInsertForm::builder()
|
let new_person = PersonInsertForm::test_form(inserted_instance.id, "holly");
|
||||||
.name("holly".into())
|
|
||||||
.public_key("nada".to_owned())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let inserted_person = Person::create(pool, &new_person).await.unwrap();
|
let inserted_person = Person::create(pool, &new_person).await.unwrap();
|
||||||
|
|
||||||
|
@ -263,7 +264,7 @@ mod tests {
|
||||||
local: true,
|
local: true,
|
||||||
bot_account: false,
|
bot_account: false,
|
||||||
private_key: None,
|
private_key: None,
|
||||||
public_key: "nada".to_owned(),
|
public_key: "pubkey".to_owned(),
|
||||||
last_refreshed_at: inserted_person.published,
|
last_refreshed_at: inserted_person.published,
|
||||||
inbox_url: inserted_person.inbox_url.clone(),
|
inbox_url: inserted_person.inbox_url.clone(),
|
||||||
shared_inbox_url: None,
|
shared_inbox_url: None,
|
||||||
|
@ -303,17 +304,9 @@ mod tests {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let person_form_1 = PersonInsertForm::builder()
|
let person_form_1 = PersonInsertForm::test_form(inserted_instance.id, "erich");
|
||||||
.name("erich".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
let person_1 = Person::create(pool, &person_form_1).await.unwrap();
|
let person_1 = Person::create(pool, &person_form_1).await.unwrap();
|
||||||
let person_form_2 = PersonInsertForm::builder()
|
let person_form_2 = PersonInsertForm::test_form(inserted_instance.id, "michele");
|
||||||
.name("michele".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
let person_2 = Person::create(pool, &person_form_2).await.unwrap();
|
let person_2 = Person::create(pool, &person_form_2).await.unwrap();
|
||||||
|
|
||||||
let follow_form = PersonFollowerForm {
|
let follow_form = PersonFollowerForm {
|
||||||
|
|
|
@ -401,11 +401,7 @@ mod tests {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let new_person = PersonInsertForm::builder()
|
let new_person = PersonInsertForm::test_form(inserted_instance.id, "jim");
|
||||||
.name("jim".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let inserted_person = Person::create(pool, &new_person).await.unwrap();
|
let inserted_person = Person::create(pool, &new_person).await.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -101,11 +101,7 @@ mod tests {
|
||||||
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string())
|
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let person_form = PersonInsertForm::builder()
|
let person_form = PersonInsertForm::test_form(inserted_instance.id, "jim");
|
||||||
.name("jim".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
let person = Person::create(pool, &person_form).await.unwrap();
|
let person = Person::create(pool, &person_form).await.unwrap();
|
||||||
|
|
||||||
let community_form = CommunityInsertForm::builder()
|
let community_form = CommunityInsertForm::builder()
|
||||||
|
|
|
@ -111,19 +111,11 @@ mod tests {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let creator_form = PersonInsertForm::builder()
|
let creator_form = PersonInsertForm::test_form(inserted_instance.id, "creator_pm");
|
||||||
.name("creator_pm".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let inserted_creator = Person::create(pool, &creator_form).await.unwrap();
|
let inserted_creator = Person::create(pool, &creator_form).await.unwrap();
|
||||||
|
|
||||||
let recipient_form = PersonInsertForm::builder()
|
let recipient_form = PersonInsertForm::test_form(inserted_instance.id, "recipient_pm");
|
||||||
.name("recipient_pm".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let inserted_recipient = Person::create(pool, &recipient_form).await.unwrap();
|
let inserted_recipient = Person::create(pool, &recipient_form).await.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -309,6 +309,15 @@ diesel::table! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
image_details (link) {
|
||||||
|
link -> Text,
|
||||||
|
width -> Int4,
|
||||||
|
height -> Int4,
|
||||||
|
content_type -> Text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
diesel::table! {
|
diesel::table! {
|
||||||
instance (id) {
|
instance (id) {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
|
@ -849,8 +858,7 @@ diesel::table! {
|
||||||
}
|
}
|
||||||
|
|
||||||
diesel::table! {
|
diesel::table! {
|
||||||
remote_image (id) {
|
remote_image (link) {
|
||||||
id -> Int4,
|
|
||||||
link -> Text,
|
link -> Text,
|
||||||
published -> Timestamptz,
|
published -> Timestamptz,
|
||||||
}
|
}
|
||||||
|
@ -1055,6 +1063,7 @@ diesel::allow_tables_to_appear_in_same_query!(
|
||||||
federation_allowlist,
|
federation_allowlist,
|
||||||
federation_blocklist,
|
federation_blocklist,
|
||||||
federation_queue_state,
|
federation_queue_state,
|
||||||
|
image_details,
|
||||||
instance,
|
instance,
|
||||||
instance_block,
|
instance_block,
|
||||||
language,
|
language,
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
use crate::newtypes::{DbUrl, LocalUserId};
|
use crate::newtypes::{DbUrl, LocalUserId};
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
use crate::schema::{local_image, remote_image};
|
use crate::schema::{image_details, local_image, remote_image};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_with::skip_serializing_none;
|
use serde_with::skip_serializing_none;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
use typed_builder::TypedBuilder;
|
|
||||||
|
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||||
|
@ -30,7 +29,7 @@ pub struct LocalImage {
|
||||||
pub published: DateTime<Utc>,
|
pub published: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, TypedBuilder)]
|
#[derive(Debug, Clone)]
|
||||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||||
#[cfg_attr(feature = "full", diesel(table_name = local_image))]
|
#[cfg_attr(feature = "full", diesel(table_name = local_image))]
|
||||||
pub struct LocalImageForm {
|
pub struct LocalImageForm {
|
||||||
|
@ -46,15 +45,39 @@ pub struct LocalImageForm {
|
||||||
#[cfg_attr(feature = "full", derive(Queryable, Selectable, Identifiable))]
|
#[cfg_attr(feature = "full", derive(Queryable, Selectable, Identifiable))]
|
||||||
#[cfg_attr(feature = "full", diesel(table_name = remote_image))]
|
#[cfg_attr(feature = "full", diesel(table_name = remote_image))]
|
||||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(primary_key(link)))]
|
||||||
pub struct RemoteImage {
|
pub struct RemoteImage {
|
||||||
pub id: i32,
|
|
||||||
pub link: DbUrl,
|
pub link: DbUrl,
|
||||||
pub published: DateTime<Utc>,
|
pub published: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, TypedBuilder)]
|
#[derive(Debug, Clone)]
|
||||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||||
#[cfg_attr(feature = "full", diesel(table_name = remote_image))]
|
#[cfg_attr(feature = "full", diesel(table_name = remote_image))]
|
||||||
pub struct RemoteImageForm {
|
pub struct RemoteImageForm {
|
||||||
pub link: DbUrl,
|
pub link: DbUrl,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||||
|
#[cfg_attr(feature = "full", derive(Queryable, Selectable, Identifiable, TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(table_name = image_details))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(primary_key(link)))]
|
||||||
|
pub struct ImageDetails {
|
||||||
|
pub link: DbUrl,
|
||||||
|
pub width: i32,
|
||||||
|
pub height: i32,
|
||||||
|
pub content_type: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(table_name = image_details))]
|
||||||
|
pub struct ImageDetailsForm {
|
||||||
|
pub link: DbUrl,
|
||||||
|
pub width: i32,
|
||||||
|
pub height: i32,
|
||||||
|
pub content_type: String,
|
||||||
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ use serde::{Deserialize, Serialize};
|
||||||
use serde_with::skip_serializing_none;
|
use serde_with::skip_serializing_none;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
use typed_builder::TypedBuilder;
|
|
||||||
|
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||||
|
@ -60,33 +59,46 @@ pub struct Person {
|
||||||
pub instance_id: InstanceId,
|
pub instance_id: InstanceId,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, TypedBuilder)]
|
#[derive(Clone, derive_new::new)]
|
||||||
#[builder(field_defaults(default))]
|
|
||||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||||
#[cfg_attr(feature = "full", diesel(table_name = person))]
|
#[cfg_attr(feature = "full", diesel(table_name = person))]
|
||||||
pub struct PersonInsertForm {
|
pub struct PersonInsertForm {
|
||||||
#[builder(!default)]
|
|
||||||
pub name: String,
|
pub name: String,
|
||||||
#[builder(!default)]
|
|
||||||
pub public_key: String,
|
pub public_key: String,
|
||||||
#[builder(!default)]
|
|
||||||
pub instance_id: InstanceId,
|
pub instance_id: InstanceId,
|
||||||
|
#[new(default)]
|
||||||
pub display_name: Option<String>,
|
pub display_name: Option<String>,
|
||||||
|
#[new(default)]
|
||||||
pub avatar: Option<DbUrl>,
|
pub avatar: Option<DbUrl>,
|
||||||
|
#[new(default)]
|
||||||
pub banned: Option<bool>,
|
pub banned: Option<bool>,
|
||||||
|
#[new(default)]
|
||||||
pub published: Option<DateTime<Utc>>,
|
pub published: Option<DateTime<Utc>>,
|
||||||
|
#[new(default)]
|
||||||
pub updated: Option<DateTime<Utc>>,
|
pub updated: Option<DateTime<Utc>>,
|
||||||
|
#[new(default)]
|
||||||
pub actor_id: Option<DbUrl>,
|
pub actor_id: Option<DbUrl>,
|
||||||
|
#[new(default)]
|
||||||
pub bio: Option<String>,
|
pub bio: Option<String>,
|
||||||
|
#[new(default)]
|
||||||
pub local: Option<bool>,
|
pub local: Option<bool>,
|
||||||
|
#[new(default)]
|
||||||
pub private_key: Option<String>,
|
pub private_key: Option<String>,
|
||||||
|
#[new(default)]
|
||||||
pub last_refreshed_at: Option<DateTime<Utc>>,
|
pub last_refreshed_at: Option<DateTime<Utc>>,
|
||||||
|
#[new(default)]
|
||||||
pub banner: Option<DbUrl>,
|
pub banner: Option<DbUrl>,
|
||||||
|
#[new(default)]
|
||||||
pub deleted: Option<bool>,
|
pub deleted: Option<bool>,
|
||||||
|
#[new(default)]
|
||||||
pub inbox_url: Option<DbUrl>,
|
pub inbox_url: Option<DbUrl>,
|
||||||
|
#[new(default)]
|
||||||
pub shared_inbox_url: Option<DbUrl>,
|
pub shared_inbox_url: Option<DbUrl>,
|
||||||
|
#[new(default)]
|
||||||
pub matrix_user_id: Option<String>,
|
pub matrix_user_id: Option<String>,
|
||||||
|
#[new(default)]
|
||||||
pub bot_account: Option<bool>,
|
pub bot_account: Option<bool>,
|
||||||
|
#[new(default)]
|
||||||
pub ban_expires: Option<DateTime<Utc>>,
|
pub ban_expires: Option<DateTime<Utc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,15 @@
|
||||||
use crate::{newtypes::DbUrl, CommentSortType, SortType};
|
use crate::{
|
||||||
|
diesel::ExpressionMethods,
|
||||||
|
newtypes::{DbUrl, PersonId},
|
||||||
|
schema::community,
|
||||||
|
CommentSortType,
|
||||||
|
CommunityVisibility,
|
||||||
|
SortType,
|
||||||
|
};
|
||||||
use chrono::{DateTime, TimeDelta, Utc};
|
use chrono::{DateTime, TimeDelta, Utc};
|
||||||
use deadpool::Runtime;
|
use deadpool::Runtime;
|
||||||
use diesel::{
|
use diesel::{
|
||||||
|
dsl,
|
||||||
helper_types::AsExprOf,
|
helper_types::AsExprOf,
|
||||||
pg::Pg,
|
pg::Pg,
|
||||||
query_builder::{Query, QueryFragment},
|
query_builder::{Query, QueryFragment},
|
||||||
|
@ -29,6 +37,7 @@ use i_love_jesus::CursorKey;
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
|
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
|
||||||
settings::SETTINGS,
|
settings::SETTINGS,
|
||||||
|
utils::validation::clean_url_params,
|
||||||
};
|
};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
@ -287,37 +296,35 @@ pub fn is_email_regex(test: &str) -> bool {
|
||||||
EMAIL_REGEX.is_match(test)
|
EMAIL_REGEX.is_match(test)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn diesel_option_overwrite(opt: Option<String>) -> Option<Option<String>> {
|
/// Takes an API text input, and converts it to an optional diesel DB update.
|
||||||
|
pub fn diesel_string_update(opt: Option<&str>) -> Option<Option<String>> {
|
||||||
match opt {
|
match opt {
|
||||||
// An empty string is an erase
|
// An empty string is an erase
|
||||||
Some(unwrapped) => {
|
Some("") => Some(None),
|
||||||
if !unwrapped.eq("") {
|
Some(str) => Some(Some(str.into())),
|
||||||
Some(Some(unwrapped))
|
|
||||||
} else {
|
|
||||||
Some(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => None,
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn diesel_option_overwrite_to_url(opt: &Option<String>) -> LemmyResult<Option<Option<DbUrl>>> {
|
/// Takes an optional API URL-type input, and converts it to an optional diesel DB update.
|
||||||
match opt.as_ref().map(String::as_str) {
|
/// Also cleans the url params.
|
||||||
|
pub fn diesel_url_update(opt: Option<&str>) -> LemmyResult<Option<Option<DbUrl>>> {
|
||||||
|
match opt {
|
||||||
// An empty string is an erase
|
// An empty string is an erase
|
||||||
Some("") => Ok(Some(None)),
|
Some("") => Ok(Some(None)),
|
||||||
Some(str_url) => Url::parse(str_url)
|
Some(str_url) => Url::parse(str_url)
|
||||||
.map(|u| Some(Some(u.into())))
|
.map(|u| Some(Some(clean_url_params(&u).into())))
|
||||||
.with_lemmy_type(LemmyErrorType::InvalidUrl),
|
.with_lemmy_type(LemmyErrorType::InvalidUrl),
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn diesel_option_overwrite_to_url_create(opt: &Option<String>) -> LemmyResult<Option<DbUrl>> {
|
/// Takes an optional API URL-type input, and converts it to an optional diesel DB create.
|
||||||
match opt.as_ref().map(String::as_str) {
|
/// Also cleans the url params.
|
||||||
// An empty string is nothing
|
pub fn diesel_url_create(opt: Option<&str>) -> LemmyResult<Option<DbUrl>> {
|
||||||
Some("") => Ok(None),
|
match opt {
|
||||||
Some(str_url) => Url::parse(str_url)
|
Some(str_url) => Url::parse(str_url)
|
||||||
.map(|u| Some(u.into()))
|
.map(|u| Some(clean_url_params(&u).into()))
|
||||||
.with_lemmy_type(LemmyErrorType::InvalidUrl),
|
.with_lemmy_type(LemmyErrorType::InvalidUrl),
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
}
|
}
|
||||||
|
@ -325,6 +332,10 @@ pub fn diesel_option_overwrite_to_url_create(opt: &Option<String>) -> LemmyResul
|
||||||
|
|
||||||
fn establish_connection(config: &str) -> BoxFuture<ConnectionResult<AsyncPgConnection>> {
|
fn establish_connection(config: &str) -> BoxFuture<ConnectionResult<AsyncPgConnection>> {
|
||||||
let fut = async {
|
let fut = async {
|
||||||
|
rustls::crypto::ring::default_provider()
|
||||||
|
.install_default()
|
||||||
|
.expect("Failed to install rustls crypto provider");
|
||||||
|
|
||||||
let rustls_config = DangerousClientConfigBuilder {
|
let rustls_config = DangerousClientConfigBuilder {
|
||||||
cfg: ClientConfig::builder(),
|
cfg: ClientConfig::builder(),
|
||||||
}
|
}
|
||||||
|
@ -568,8 +579,21 @@ impl<RF, LF> Queries<RF, LF> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn visible_communities_only<Q>(my_person_id: Option<PersonId>, query: Q) -> Q
|
||||||
|
where
|
||||||
|
Q: diesel::query_dsl::methods::FilterDsl<
|
||||||
|
dsl::Eq<community::visibility, CommunityVisibility>,
|
||||||
|
Output = Q,
|
||||||
|
>,
|
||||||
|
{
|
||||||
|
if my_person_id.is_none() {
|
||||||
|
query.filter(community::visibility.eq(CommunityVisibility::Public))
|
||||||
|
} else {
|
||||||
|
query
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
#[allow(clippy::indexing_slicing)]
|
#[allow(clippy::indexing_slicing)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
|
@ -593,26 +617,24 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_diesel_option_overwrite() {
|
fn test_diesel_option_overwrite() {
|
||||||
assert_eq!(diesel_option_overwrite(None), None);
|
assert_eq!(diesel_string_update(None), None);
|
||||||
assert_eq!(diesel_option_overwrite(Some(String::new())), Some(None));
|
assert_eq!(diesel_string_update(Some("")), Some(None));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
diesel_option_overwrite(Some("test".to_string())),
|
diesel_string_update(Some("test")),
|
||||||
Some(Some("test".to_string()))
|
Some(Some("test".to_string()))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_diesel_option_overwrite_to_url() {
|
fn test_diesel_option_overwrite_to_url() -> LemmyResult<()> {
|
||||||
assert!(matches!(diesel_option_overwrite_to_url(&None), Ok(None)));
|
assert!(matches!(diesel_url_update(None), Ok(None)));
|
||||||
assert!(matches!(
|
assert!(matches!(diesel_url_update(Some("")), Ok(Some(None))));
|
||||||
diesel_option_overwrite_to_url(&Some(String::new())),
|
assert!(diesel_url_update(Some("invalid_url")).is_err());
|
||||||
Ok(Some(None))
|
|
||||||
));
|
|
||||||
assert!(diesel_option_overwrite_to_url(&Some("invalid_url".to_string())).is_err());
|
|
||||||
let example_url = "https://example.com";
|
let example_url = "https://example.com";
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
diesel_option_overwrite_to_url(&Some(example_url.to_string())),
|
diesel_url_update(Some(example_url)),
|
||||||
Ok(Some(Some(url))) if url == Url::parse(example_url).unwrap().into()
|
Ok(Some(Some(url))) if url == Url::parse(example_url)?.into()
|
||||||
));
|
));
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -297,11 +297,7 @@ mod tests {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let new_person = PersonInsertForm::builder()
|
let new_person = PersonInsertForm::test_form(inserted_instance.id, "timmy_crv");
|
||||||
.name("timmy_crv".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let inserted_timmy = Person::create(pool, &new_person).await.unwrap();
|
let inserted_timmy = Person::create(pool, &new_person).await.unwrap();
|
||||||
|
|
||||||
|
@ -319,20 +315,12 @@ mod tests {
|
||||||
counts: Default::default(),
|
counts: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let new_person_2 = PersonInsertForm::builder()
|
let new_person_2 = PersonInsertForm::test_form(inserted_instance.id, "sara_crv");
|
||||||
.name("sara_crv".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let inserted_sara = Person::create(pool, &new_person_2).await.unwrap();
|
let inserted_sara = Person::create(pool, &new_person_2).await.unwrap();
|
||||||
|
|
||||||
// Add a third person, since new ppl can only report something once.
|
// Add a third person, since new ppl can only report something once.
|
||||||
let new_person_3 = PersonInsertForm::builder()
|
let new_person_3 = PersonInsertForm::test_form(inserted_instance.id, "jessica_crv");
|
||||||
.name("jessica_crv".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let inserted_jessica = Person::create(pool, &new_person_3).await.unwrap();
|
let inserted_jessica = Person::create(pool, &new_person_3).await.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::structs::{CommentView, LocalUserView};
|
use crate::structs::CommentView;
|
||||||
use diesel::{
|
use diesel::{
|
||||||
dsl::{exists, not},
|
dsl::{exists, not},
|
||||||
pg::Pg,
|
pg::Pg,
|
||||||
|
@ -16,6 +16,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::{
|
||||||
|
impls::local_user::LocalUserOptionHelper,
|
||||||
newtypes::{CommentId, CommunityId, LocalUserId, PersonId, PostId},
|
newtypes::{CommentId, CommunityId, LocalUserId, PersonId, PostId},
|
||||||
schema::{
|
schema::{
|
||||||
comment,
|
comment,
|
||||||
|
@ -34,9 +35,18 @@ use lemmy_db_schema::{
|
||||||
person_block,
|
person_block,
|
||||||
post,
|
post,
|
||||||
},
|
},
|
||||||
utils::{fuzzy_search, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn},
|
source::local_user::LocalUser,
|
||||||
|
utils::{
|
||||||
|
fuzzy_search,
|
||||||
|
limit_and_offset,
|
||||||
|
visible_communities_only,
|
||||||
|
DbConn,
|
||||||
|
DbPool,
|
||||||
|
ListFn,
|
||||||
|
Queries,
|
||||||
|
ReadFn,
|
||||||
|
},
|
||||||
CommentSortType,
|
CommentSortType,
|
||||||
CommunityVisibility,
|
|
||||||
ListingType,
|
ListingType,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -174,22 +184,19 @@ fn queries<'a>() -> Queries<
|
||||||
let read = move |mut conn: DbConn<'a>,
|
let read = move |mut conn: DbConn<'a>,
|
||||||
(comment_id, my_person_id): (CommentId, Option<PersonId>)| async move {
|
(comment_id, my_person_id): (CommentId, Option<PersonId>)| async move {
|
||||||
let mut query = all_joins(comment::table.find(comment_id).into_boxed(), my_person_id);
|
let mut query = all_joins(comment::table.find(comment_id).into_boxed(), my_person_id);
|
||||||
// Hide local only communities from unauthenticated users
|
query = visible_communities_only(my_person_id, query);
|
||||||
if my_person_id.is_none() {
|
|
||||||
query = query.filter(community::visibility.eq(CommunityVisibility::Public));
|
|
||||||
}
|
|
||||||
query.first(&mut conn).await
|
query.first(&mut conn).await
|
||||||
};
|
};
|
||||||
|
|
||||||
let list = move |mut conn: DbConn<'a>, options: CommentQuery<'a>| async move {
|
let list = move |mut conn: DbConn<'a>, options: CommentQuery<'a>| async move {
|
||||||
let my_person_id = options.local_user.map(|l| l.person.id);
|
|
||||||
let my_local_user_id = options.local_user.map(|l| l.local_user.id);
|
|
||||||
|
|
||||||
// The left join below will return None in this case
|
// The left join below will return None in this case
|
||||||
let person_id_join = my_person_id.unwrap_or(PersonId(-1));
|
let person_id_join = options.local_user.person_id().unwrap_or(PersonId(-1));
|
||||||
let local_user_id_join = my_local_user_id.unwrap_or(LocalUserId(-1));
|
let local_user_id_join = options
|
||||||
|
.local_user
|
||||||
|
.local_user_id()
|
||||||
|
.unwrap_or(LocalUserId(-1));
|
||||||
|
|
||||||
let mut query = all_joins(comment::table.into_boxed(), my_person_id);
|
let mut query = all_joins(comment::table.into_boxed(), options.local_user.person_id());
|
||||||
|
|
||||||
if let Some(creator_id) = options.creator_id {
|
if let Some(creator_id) = options.creator_id {
|
||||||
query = query.filter(comment::creator_id.eq(creator_id));
|
query = query.filter(comment::creator_id.eq(creator_id));
|
||||||
|
@ -245,26 +252,22 @@ fn queries<'a>() -> Queries<
|
||||||
}
|
}
|
||||||
|
|
||||||
// If its saved only, then filter, and order by the saved time, not the comment creation time.
|
// If its saved only, then filter, and order by the saved time, not the comment creation time.
|
||||||
if options.saved_only {
|
if options.saved_only.unwrap_or_default() {
|
||||||
query = query
|
query = query
|
||||||
.filter(comment_saved::person_id.is_not_null())
|
.filter(comment_saved::person_id.is_not_null())
|
||||||
.then_order_by(comment_saved::published.desc());
|
.then_order_by(comment_saved::published.desc());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(my_id) = my_person_id {
|
if let Some(my_id) = options.local_user.person_id() {
|
||||||
let not_creator_filter = comment::creator_id.ne(my_id);
|
let not_creator_filter = comment::creator_id.ne(my_id);
|
||||||
if options.liked_only {
|
if options.liked_only.unwrap_or_default() {
|
||||||
query = query.filter(not_creator_filter).filter(score(my_id).eq(1));
|
query = query.filter(not_creator_filter).filter(score(my_id).eq(1));
|
||||||
} else if options.disliked_only {
|
} else if options.disliked_only.unwrap_or_default() {
|
||||||
query = query.filter(not_creator_filter).filter(score(my_id).eq(-1));
|
query = query.filter(not_creator_filter).filter(score(my_id).eq(-1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !options
|
if !options.local_user.show_bot_accounts() {
|
||||||
.local_user
|
|
||||||
.map(|l| l.local_user.show_bot_accounts)
|
|
||||||
.unwrap_or(true)
|
|
||||||
{
|
|
||||||
query = query.filter(person::bot_account.eq(false));
|
query = query.filter(person::bot_account.eq(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -298,10 +301,7 @@ fn queries<'a>() -> Queries<
|
||||||
query = query.filter(not(is_creator_blocked(person_id_join)));
|
query = query.filter(not(is_creator_blocked(person_id_join)));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Hide comments in local only communities from unauthenticated users
|
query = visible_communities_only(options.local_user.person_id(), query);
|
||||||
if options.local_user.is_none() {
|
|
||||||
query = query.filter(community::visibility.eq(CommunityVisibility::Public));
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Max depth given means its a tree fetch
|
// A Max depth given means its a tree fetch
|
||||||
let (limit, offset) = if let Some(max_depth) = options.max_depth {
|
let (limit, offset) = if let Some(max_depth) = options.max_depth {
|
||||||
|
@ -396,11 +396,11 @@ pub struct CommentQuery<'a> {
|
||||||
pub post_id: Option<PostId>,
|
pub post_id: Option<PostId>,
|
||||||
pub parent_path: Option<Ltree>,
|
pub parent_path: Option<Ltree>,
|
||||||
pub creator_id: Option<PersonId>,
|
pub creator_id: Option<PersonId>,
|
||||||
pub local_user: Option<&'a LocalUserView>,
|
pub local_user: Option<&'a LocalUser>,
|
||||||
pub search_term: Option<String>,
|
pub search_term: Option<String>,
|
||||||
pub saved_only: bool,
|
pub saved_only: Option<bool>,
|
||||||
pub liked_only: bool,
|
pub liked_only: Option<bool>,
|
||||||
pub disliked_only: bool,
|
pub disliked_only: Option<bool>,
|
||||||
pub page: Option<i64>,
|
pub page: Option<i64>,
|
||||||
pub limit: Option<i64>,
|
pub limit: Option<i64>,
|
||||||
pub max_depth: Option<i32>,
|
pub max_depth: Option<i32>,
|
||||||
|
@ -488,11 +488,7 @@ mod tests {
|
||||||
async fn init_data(pool: &mut DbPool<'_>) -> LemmyResult<Data> {
|
async fn init_data(pool: &mut DbPool<'_>) -> LemmyResult<Data> {
|
||||||
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
|
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
|
||||||
|
|
||||||
let timmy_person_form = PersonInsertForm::builder()
|
let timmy_person_form = PersonInsertForm::test_form(inserted_instance.id, "timmy");
|
||||||
.name("timmy".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
let inserted_timmy_person = Person::create(pool, &timmy_person_form).await?;
|
let inserted_timmy_person = Person::create(pool, &timmy_person_form).await?;
|
||||||
let timmy_local_user_form = LocalUserInsertForm::builder()
|
let timmy_local_user_form = LocalUserInsertForm::builder()
|
||||||
.person_id(inserted_timmy_person.id)
|
.person_id(inserted_timmy_person.id)
|
||||||
|
@ -501,11 +497,7 @@ mod tests {
|
||||||
.build();
|
.build();
|
||||||
let inserted_timmy_local_user = LocalUser::create(pool, &timmy_local_user_form, vec![]).await?;
|
let inserted_timmy_local_user = LocalUser::create(pool, &timmy_local_user_form, vec![]).await?;
|
||||||
|
|
||||||
let sara_person_form = PersonInsertForm::builder()
|
let sara_person_form = PersonInsertForm::test_form(inserted_instance.id, "sara");
|
||||||
.name("sara".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
let inserted_sara_person = Person::create(pool, &sara_person_form).await?;
|
let inserted_sara_person = Person::create(pool, &sara_person_form).await?;
|
||||||
|
|
||||||
let new_community = CommunityInsertForm::builder()
|
let new_community = CommunityInsertForm::builder()
|
||||||
|
@ -667,7 +659,7 @@ mod tests {
|
||||||
let read_comment_views_with_person = CommentQuery {
|
let read_comment_views_with_person = CommentQuery {
|
||||||
sort: (Some(CommentSortType::Old)),
|
sort: (Some(CommentSortType::Old)),
|
||||||
post_id: (Some(data.inserted_post.id)),
|
post_id: (Some(data.inserted_post.id)),
|
||||||
local_user: (Some(&data.timmy_local_user_view)),
|
local_user: (Some(&data.timmy_local_user_view.local_user)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
.list(pool)
|
.list(pool)
|
||||||
|
@ -719,8 +711,8 @@ mod tests {
|
||||||
CommentLike::like(pool, &comment_like_form).await?;
|
CommentLike::like(pool, &comment_like_form).await?;
|
||||||
|
|
||||||
let read_liked_comment_views = CommentQuery {
|
let read_liked_comment_views = CommentQuery {
|
||||||
local_user: (Some(&data.timmy_local_user_view)),
|
local_user: Some(&data.timmy_local_user_view.local_user),
|
||||||
liked_only: (true),
|
liked_only: Some(true),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
.list(pool)
|
.list(pool)
|
||||||
|
@ -735,8 +727,8 @@ mod tests {
|
||||||
assert_length!(1, read_liked_comment_views);
|
assert_length!(1, read_liked_comment_views);
|
||||||
|
|
||||||
let read_disliked_comment_views: Vec<CommentView> = CommentQuery {
|
let read_disliked_comment_views: Vec<CommentView> = CommentQuery {
|
||||||
local_user: (Some(&data.timmy_local_user_view)),
|
local_user: Some(&data.timmy_local_user_view.local_user),
|
||||||
disliked_only: (true),
|
disliked_only: Some(true),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
.list(pool)
|
.list(pool)
|
||||||
|
@ -830,7 +822,7 @@ mod tests {
|
||||||
// by default, user has all languages enabled and should see all comments
|
// by default, user has all languages enabled and should see all comments
|
||||||
// (except from blocked user)
|
// (except from blocked user)
|
||||||
let all_languages = CommentQuery {
|
let all_languages = CommentQuery {
|
||||||
local_user: (Some(&data.timmy_local_user_view)),
|
local_user: (Some(&data.timmy_local_user_view.local_user)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
.list(pool)
|
.list(pool)
|
||||||
|
@ -848,7 +840,7 @@ mod tests {
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let finnish_comments = CommentQuery {
|
let finnish_comments = CommentQuery {
|
||||||
local_user: (Some(&data.timmy_local_user_view)),
|
local_user: (Some(&data.timmy_local_user_view.local_user)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
.list(pool)
|
.list(pool)
|
||||||
|
@ -874,7 +866,7 @@ mod tests {
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let undetermined_comment = CommentQuery {
|
let undetermined_comment = CommentQuery {
|
||||||
local_user: (Some(&data.timmy_local_user_view)),
|
local_user: (Some(&data.timmy_local_user_view.local_user)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
.list(pool)
|
.list(pool)
|
||||||
|
@ -987,8 +979,8 @@ mod tests {
|
||||||
|
|
||||||
// Fetch the saved comments
|
// Fetch the saved comments
|
||||||
let comments = CommentQuery {
|
let comments = CommentQuery {
|
||||||
local_user: Some(&data.timmy_local_user_view),
|
local_user: Some(&data.timmy_local_user_view.local_user),
|
||||||
saved_only: true,
|
saved_only: Some(true),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
.list(pool)
|
.list(pool)
|
||||||
|
@ -1166,7 +1158,7 @@ mod tests {
|
||||||
assert_eq!(0, unauthenticated_query.len());
|
assert_eq!(0, unauthenticated_query.len());
|
||||||
|
|
||||||
let authenticated_query = CommentQuery {
|
let authenticated_query = CommentQuery {
|
||||||
local_user: Some(&data.timmy_local_user_view),
|
local_user: Some(&data.timmy_local_user_view.local_user),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
.list(pool)
|
.list(pool)
|
||||||
|
|
|
@ -319,11 +319,7 @@ mod tests {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let new_person = PersonInsertForm::builder()
|
let new_person = PersonInsertForm::test_form(inserted_instance.id, "timmy_prv");
|
||||||
.name("timmy_prv".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let inserted_timmy = Person::create(pool, &new_person).await.unwrap();
|
let inserted_timmy = Person::create(pool, &new_person).await.unwrap();
|
||||||
|
|
||||||
|
@ -341,20 +337,12 @@ mod tests {
|
||||||
counts: Default::default(),
|
counts: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let new_person_2 = PersonInsertForm::builder()
|
let new_person_2 = PersonInsertForm::test_form(inserted_instance.id, "sara_prv");
|
||||||
.name("sara_prv".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let inserted_sara = Person::create(pool, &new_person_2).await.unwrap();
|
let inserted_sara = Person::create(pool, &new_person_2).await.unwrap();
|
||||||
|
|
||||||
// Add a third person, since new ppl can only report something once.
|
// Add a third person, since new ppl can only report something once.
|
||||||
let new_person_3 = PersonInsertForm::builder()
|
let new_person_3 = PersonInsertForm::test_form(inserted_instance.id, "jessica_prv");
|
||||||
.name("jessica_prv".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let inserted_jessica = Person::create(pool, &new_person_3).await.unwrap();
|
let inserted_jessica = Person::create(pool, &new_person_3).await.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::structs::{LocalUserView, PaginationCursor, PostView};
|
use crate::structs::{PaginationCursor, PostView};
|
||||||
use diesel::{
|
use diesel::{
|
||||||
debug_query,
|
debug_query,
|
||||||
dsl::{exists, not, IntervalDsl},
|
dsl::{exists, not, IntervalDsl},
|
||||||
|
@ -20,6 +20,7 @@ use diesel_async::RunQueryDsl;
|
||||||
use i_love_jesus::PaginatedQueryBuilder;
|
use i_love_jesus::PaginatedQueryBuilder;
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
aggregates::structs::{post_aggregates_keys as key, PostAggregates},
|
aggregates::structs::{post_aggregates_keys as key, PostAggregates},
|
||||||
|
impls::local_user::LocalUserOptionHelper,
|
||||||
newtypes::{CommunityId, LocalUserId, PersonId, PostId},
|
newtypes::{CommunityId, LocalUserId, PersonId, PostId},
|
||||||
schema::{
|
schema::{
|
||||||
community,
|
community,
|
||||||
|
@ -27,6 +28,7 @@ use lemmy_db_schema::{
|
||||||
community_follower,
|
community_follower,
|
||||||
community_moderator,
|
community_moderator,
|
||||||
community_person_ban,
|
community_person_ban,
|
||||||
|
image_details,
|
||||||
instance_block,
|
instance_block,
|
||||||
local_user,
|
local_user,
|
||||||
local_user_language,
|
local_user_language,
|
||||||
|
@ -40,13 +42,14 @@ use lemmy_db_schema::{
|
||||||
post_read,
|
post_read,
|
||||||
post_saved,
|
post_saved,
|
||||||
},
|
},
|
||||||
source::site::Site,
|
source::{local_user::LocalUser, site::Site},
|
||||||
utils::{
|
utils::{
|
||||||
functions::coalesce,
|
functions::coalesce,
|
||||||
fuzzy_search,
|
fuzzy_search,
|
||||||
get_conn,
|
get_conn,
|
||||||
limit_and_offset,
|
limit_and_offset,
|
||||||
now,
|
now,
|
||||||
|
visible_communities_only,
|
||||||
Commented,
|
Commented,
|
||||||
DbConn,
|
DbConn,
|
||||||
DbPool,
|
DbPool,
|
||||||
|
@ -55,7 +58,6 @@ use lemmy_db_schema::{
|
||||||
ReadFn,
|
ReadFn,
|
||||||
ReverseTimestampKey,
|
ReverseTimestampKey,
|
||||||
},
|
},
|
||||||
CommunityVisibility,
|
|
||||||
ListingType,
|
ListingType,
|
||||||
SortType,
|
SortType,
|
||||||
};
|
};
|
||||||
|
@ -217,6 +219,7 @@ fn queries<'a>() -> Queries<
|
||||||
.inner_join(person::table)
|
.inner_join(person::table)
|
||||||
.inner_join(community::table)
|
.inner_join(community::table)
|
||||||
.inner_join(post::table)
|
.inner_join(post::table)
|
||||||
|
.left_join(image_details::table.on(post::thumbnail_url.eq(image_details::link.nullable())))
|
||||||
.left_join(
|
.left_join(
|
||||||
post_saved::table.on(
|
post_saved::table.on(
|
||||||
post_aggregates::post_id
|
post_aggregates::post_id
|
||||||
|
@ -228,6 +231,7 @@ fn queries<'a>() -> Queries<
|
||||||
post::all_columns,
|
post::all_columns,
|
||||||
person::all_columns,
|
person::all_columns,
|
||||||
community::all_columns,
|
community::all_columns,
|
||||||
|
image_details::all_columns.nullable(),
|
||||||
is_creator_banned_from_community,
|
is_creator_banned_from_community,
|
||||||
is_local_user_banned_from_community_selection,
|
is_local_user_banned_from_community_selection,
|
||||||
creator_is_moderator,
|
creator_is_moderator,
|
||||||
|
@ -285,10 +289,7 @@ fn queries<'a>() -> Queries<
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide posts in local only communities from unauthenticated users
|
query = visible_communities_only(my_person_id, query);
|
||||||
if my_person_id.is_none() {
|
|
||||||
query = query.filter(community::visibility.eq(CommunityVisibility::Public));
|
|
||||||
}
|
|
||||||
|
|
||||||
Commented::new(query)
|
Commented::new(query)
|
||||||
.text("PostView::read")
|
.text("PostView::read")
|
||||||
|
@ -297,31 +298,30 @@ fn queries<'a>() -> Queries<
|
||||||
};
|
};
|
||||||
|
|
||||||
let list = move |mut conn: DbConn<'a>, (options, site): (PostQuery<'a>, &'a Site)| async move {
|
let list = move |mut conn: DbConn<'a>, (options, site): (PostQuery<'a>, &'a Site)| async move {
|
||||||
let my_person_id = options.local_user.map(|l| l.person.id);
|
|
||||||
let my_local_user_id = options.local_user.map(|l| l.local_user.id);
|
|
||||||
|
|
||||||
// The left join below will return None in this case
|
// The left join below will return None in this case
|
||||||
let person_id_join = my_person_id.unwrap_or(PersonId(-1));
|
let person_id_join = options.local_user.person_id().unwrap_or(PersonId(-1));
|
||||||
let local_user_id_join = my_local_user_id.unwrap_or(LocalUserId(-1));
|
let local_user_id_join = options
|
||||||
|
.local_user
|
||||||
|
.local_user_id()
|
||||||
|
.unwrap_or(LocalUserId(-1));
|
||||||
|
|
||||||
let mut query = all_joins(post_aggregates::table.into_boxed(), my_person_id);
|
let mut query = all_joins(
|
||||||
|
post_aggregates::table.into_boxed(),
|
||||||
|
options.local_user.person_id(),
|
||||||
|
);
|
||||||
|
|
||||||
// hide posts from deleted communities
|
// hide posts from deleted communities
|
||||||
query = query.filter(community::deleted.eq(false));
|
query = query.filter(community::deleted.eq(false));
|
||||||
|
|
||||||
// only show deleted posts to creator
|
// only show deleted posts to creator
|
||||||
if let Some(person_id) = my_person_id {
|
if let Some(person_id) = options.local_user.person_id() {
|
||||||
query = query.filter(post::deleted.eq(false).or(post::creator_id.eq(person_id)));
|
query = query.filter(post::deleted.eq(false).or(post::creator_id.eq(person_id)));
|
||||||
} else {
|
} else {
|
||||||
query = query.filter(post::deleted.eq(false));
|
query = query.filter(post::deleted.eq(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
let is_admin = options
|
|
||||||
.local_user
|
|
||||||
.map(|l| l.local_user.admin)
|
|
||||||
.unwrap_or(false);
|
|
||||||
// only show removed posts to admin when viewing user profile
|
// only show removed posts to admin when viewing user profile
|
||||||
if !(options.creator_id.is_some() && is_admin) {
|
if !(options.creator_id.is_some() && options.local_user.is_admin()) {
|
||||||
query = query
|
query = query
|
||||||
.filter(community::removed.eq(false))
|
.filter(community::removed.eq(false))
|
||||||
.filter(post::removed.eq(false));
|
.filter(post::removed.eq(false));
|
||||||
|
@ -335,7 +335,7 @@ fn queries<'a>() -> Queries<
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(listing_type) = options.listing_type {
|
if let Some(listing_type) = options.listing_type {
|
||||||
if let Some(person_id) = my_person_id {
|
if let Some(person_id) = options.local_user.person_id() {
|
||||||
let is_subscribed = exists(
|
let is_subscribed = exists(
|
||||||
community_follower::table.filter(
|
community_follower::table.filter(
|
||||||
post_aggregates::community_id
|
post_aggregates::community_id
|
||||||
|
@ -392,28 +392,18 @@ fn queries<'a>() -> Queries<
|
||||||
.filter(not(post::removed.or(post::deleted)));
|
.filter(not(post::removed.or(post::deleted)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there is a content warning, show nsfw content by default.
|
if !options.local_user.show_nsfw(site) {
|
||||||
let has_content_warning = site.content_warning.is_some();
|
|
||||||
if !options
|
|
||||||
.local_user
|
|
||||||
.map(|l| l.local_user.show_nsfw)
|
|
||||||
.unwrap_or(has_content_warning)
|
|
||||||
{
|
|
||||||
query = query
|
query = query
|
||||||
.filter(post::nsfw.eq(false))
|
.filter(post::nsfw.eq(false))
|
||||||
.filter(community::nsfw.eq(false));
|
.filter(community::nsfw.eq(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
if !options
|
if !options.local_user.show_bot_accounts() {
|
||||||
.local_user
|
|
||||||
.map(|l| l.local_user.show_bot_accounts)
|
|
||||||
.unwrap_or(true)
|
|
||||||
{
|
|
||||||
query = query.filter(person::bot_account.eq(false));
|
query = query.filter(person::bot_account.eq(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
// If its saved only, then filter, and order by the saved time, not the comment creation time.
|
// If its saved only, then filter, and order by the saved time, not the comment creation time.
|
||||||
if let (true, Some(_person_id)) = (options.saved_only, my_person_id) {
|
if options.saved_only.unwrap_or_default() {
|
||||||
query = query
|
query = query
|
||||||
.filter(post_saved::person_id.is_not_null())
|
.filter(post_saved::person_id.is_not_null())
|
||||||
.then_order_by(post_saved::published.desc());
|
.then_order_by(post_saved::published.desc());
|
||||||
|
@ -421,41 +411,37 @@ fn queries<'a>() -> Queries<
|
||||||
// Only hide the read posts, if the saved_only is false. Otherwise ppl with the hide_read
|
// Only hide the read posts, if the saved_only is false. Otherwise ppl with the hide_read
|
||||||
// setting wont be able to see saved posts.
|
// setting wont be able to see saved posts.
|
||||||
else if !options
|
else if !options
|
||||||
.local_user
|
.show_read
|
||||||
.map(|l| l.local_user.show_read_posts)
|
.unwrap_or(options.local_user.show_read_posts())
|
||||||
.unwrap_or(true)
|
|
||||||
{
|
{
|
||||||
// Do not hide read posts when it is a user profile view
|
// Do not hide read posts when it is a user profile view
|
||||||
// Or, only hide read posts on non-profile views
|
// Or, only hide read posts on non-profile views
|
||||||
if let (None, Some(person_id)) = (options.creator_id, my_person_id) {
|
if let (None, Some(person_id)) = (options.creator_id, options.local_user.person_id()) {
|
||||||
query = query.filter(not(is_read(person_id)));
|
query = query.filter(not(is_read(person_id)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !options.show_hidden {
|
if !options.show_hidden.unwrap_or_default() {
|
||||||
// If a creator id isn't given (IE its on home or community pages), hide the hidden posts
|
// If a creator id isn't given (IE its on home or community pages), hide the hidden posts
|
||||||
if let (None, Some(person_id)) = (options.creator_id, my_person_id) {
|
if let (None, Some(person_id)) = (options.creator_id, options.local_user.person_id()) {
|
||||||
query = query.filter(not(is_hidden(person_id)));
|
query = query.filter(not(is_hidden(person_id)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(my_id) = my_person_id {
|
if let Some(my_id) = options.local_user.person_id() {
|
||||||
let not_creator_filter = post_aggregates::creator_id.ne(my_id);
|
let not_creator_filter = post_aggregates::creator_id.ne(my_id);
|
||||||
if options.liked_only {
|
if options.liked_only.unwrap_or_default() {
|
||||||
query = query.filter(not_creator_filter).filter(score(my_id).eq(1));
|
query = query.filter(not_creator_filter).filter(score(my_id).eq(1));
|
||||||
} else if options.disliked_only {
|
} else if options.disliked_only.unwrap_or_default() {
|
||||||
query = query.filter(not_creator_filter).filter(score(my_id).eq(-1));
|
query = query.filter(not_creator_filter).filter(score(my_id).eq(-1));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Hide posts in local only communities from unauthenticated users
|
query = visible_communities_only(options.local_user.person_id(), query);
|
||||||
if options.local_user.is_none() {
|
|
||||||
query = query.filter(community::visibility.eq(CommunityVisibility::Public));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dont filter blocks or missing languages for moderator view type
|
// Dont filter blocks or missing languages for moderator view type
|
||||||
if let (Some(person_id), false) = (
|
if let (Some(person_id), false) = (
|
||||||
my_person_id,
|
options.local_user.person_id(),
|
||||||
options.listing_type.unwrap_or_default() == ListingType::ModeratorView,
|
options.listing_type.unwrap_or_default() == ListingType::ModeratorView,
|
||||||
) {
|
) {
|
||||||
// Filter out the rows with missing languages
|
// Filter out the rows with missing languages
|
||||||
|
@ -493,7 +479,7 @@ fn queries<'a>() -> Queries<
|
||||||
let page_after = options.page_after.map(|c| c.0);
|
let page_after = options.page_after.map(|c| c.0);
|
||||||
let page_before_or_equal = options.page_before_or_equal.map(|c| c.0);
|
let page_before_or_equal = options.page_before_or_equal.map(|c| c.0);
|
||||||
|
|
||||||
if options.page_back {
|
if options.page_back.unwrap_or_default() {
|
||||||
query = query
|
query = query
|
||||||
.before(page_after)
|
.before(page_after)
|
||||||
.after_or_equal(page_before_or_equal)
|
.after_or_equal(page_before_or_equal)
|
||||||
|
@ -618,18 +604,19 @@ pub struct PostQuery<'a> {
|
||||||
// if true, the query should be handled as if community_id was not given except adding the
|
// if true, the query should be handled as if community_id was not given except adding the
|
||||||
// literal filter
|
// literal filter
|
||||||
pub community_id_just_for_prefetch: bool,
|
pub community_id_just_for_prefetch: bool,
|
||||||
pub local_user: Option<&'a LocalUserView>,
|
pub local_user: Option<&'a LocalUser>,
|
||||||
pub search_term: Option<String>,
|
pub search_term: Option<String>,
|
||||||
pub url_search: Option<String>,
|
pub url_search: Option<String>,
|
||||||
pub saved_only: bool,
|
pub saved_only: Option<bool>,
|
||||||
pub liked_only: bool,
|
pub liked_only: Option<bool>,
|
||||||
pub disliked_only: bool,
|
pub disliked_only: Option<bool>,
|
||||||
pub page: Option<i64>,
|
pub page: Option<i64>,
|
||||||
pub limit: Option<i64>,
|
pub limit: Option<i64>,
|
||||||
pub page_after: Option<PaginationCursorData>,
|
pub page_after: Option<PaginationCursorData>,
|
||||||
pub page_before_or_equal: Option<PaginationCursorData>,
|
pub page_before_or_equal: Option<PaginationCursorData>,
|
||||||
pub page_back: bool,
|
pub page_back: Option<bool>,
|
||||||
pub show_hidden: bool,
|
pub show_hidden: Option<bool>,
|
||||||
|
pub show_read: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> PostQuery<'a> {
|
impl<'a> PostQuery<'a> {
|
||||||
|
@ -663,11 +650,7 @@ impl<'a> PostQuery<'a> {
|
||||||
"legacy pagination cannot be combined with v2 pagination".into(),
|
"legacy pagination cannot be combined with v2 pagination".into(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let self_person_id = self
|
let self_person_id = self.local_user.expect("part of the above if").person_id;
|
||||||
.local_user
|
|
||||||
.expect("part of the above if")
|
|
||||||
.local_user
|
|
||||||
.person_id;
|
|
||||||
let largest_subscribed = {
|
let largest_subscribed = {
|
||||||
let conn = &mut get_conn(pool).await?;
|
let conn = &mut get_conn(pool).await?;
|
||||||
community_follower
|
community_follower
|
||||||
|
@ -704,7 +687,7 @@ impl<'a> PostQuery<'a> {
|
||||||
if (v.len() as i64) < limit {
|
if (v.len() as i64) < limit {
|
||||||
Ok(Some(self.clone()))
|
Ok(Some(self.clone()))
|
||||||
} else {
|
} else {
|
||||||
let item = if self.page_back {
|
let item = if self.page_back.unwrap_or_default() {
|
||||||
// for backward pagination, get first element instead
|
// for backward pagination, get first element instead
|
||||||
v.into_iter().next()
|
v.into_iter().next()
|
||||||
} else {
|
} else {
|
||||||
|
@ -807,7 +790,7 @@ mod tests {
|
||||||
fn default_post_query(&self) -> PostQuery<'_> {
|
fn default_post_query(&self) -> PostQuery<'_> {
|
||||||
PostQuery {
|
PostQuery {
|
||||||
sort: Some(SortType::New),
|
sort: Some(SortType::New),
|
||||||
local_user: Some(&self.local_user_view),
|
local_user: Some(&self.local_user_view.local_user),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1143,7 +1126,7 @@ mod tests {
|
||||||
// Read the liked only
|
// Read the liked only
|
||||||
let read_liked_post_listing = PostQuery {
|
let read_liked_post_listing = PostQuery {
|
||||||
community_id: Some(data.inserted_community.id),
|
community_id: Some(data.inserted_community.id),
|
||||||
liked_only: true,
|
liked_only: Some(true),
|
||||||
..data.default_post_query()
|
..data.default_post_query()
|
||||||
}
|
}
|
||||||
.list(&data.site, pool)
|
.list(&data.site, pool)
|
||||||
|
@ -1154,7 +1137,7 @@ mod tests {
|
||||||
|
|
||||||
let read_disliked_post_listing = PostQuery {
|
let read_disliked_post_listing = PostQuery {
|
||||||
community_id: Some(data.inserted_community.id),
|
community_id: Some(data.inserted_community.id),
|
||||||
disliked_only: true,
|
disliked_only: Some(true),
|
||||||
..data.default_post_query()
|
..data.default_post_query()
|
||||||
}
|
}
|
||||||
.list(&data.site, pool)
|
.list(&data.site, pool)
|
||||||
|
@ -1324,8 +1307,8 @@ mod tests {
|
||||||
// Deleted post is only shown to creator
|
// Deleted post is only shown to creator
|
||||||
for (local_user, expect_contains_deleted) in [
|
for (local_user, expect_contains_deleted) in [
|
||||||
(None, false),
|
(None, false),
|
||||||
(Some(&data.blocked_local_user_view), false),
|
(Some(&data.blocked_local_user_view.local_user), false),
|
||||||
(Some(&data.local_user_view), true),
|
(Some(&data.local_user_view.local_user), true),
|
||||||
] {
|
] {
|
||||||
let contains_deleted = PostQuery {
|
let contains_deleted = PostQuery {
|
||||||
local_user,
|
local_user,
|
||||||
|
@ -1480,7 +1463,7 @@ mod tests {
|
||||||
loop {
|
loop {
|
||||||
let post_listings = PostQuery {
|
let post_listings = PostQuery {
|
||||||
page_after: page_before,
|
page_after: page_before,
|
||||||
page_back: true,
|
page_back: Some(true),
|
||||||
..options.clone()
|
..options.clone()
|
||||||
}
|
}
|
||||||
.list(&data.site, pool)
|
.list(&data.site, pool)
|
||||||
|
@ -1538,6 +1521,26 @@ mod tests {
|
||||||
let post_listings_hide_read = data.default_post_query().list(&data.site, pool).await?;
|
let post_listings_hide_read = data.default_post_query().list(&data.site, pool).await?;
|
||||||
assert_eq!(vec![POST], names(&post_listings_hide_read));
|
assert_eq!(vec![POST], names(&post_listings_hide_read));
|
||||||
|
|
||||||
|
// Test with the show_read override as true
|
||||||
|
let post_listings_show_read_true = PostQuery {
|
||||||
|
show_read: Some(true),
|
||||||
|
..data.default_post_query()
|
||||||
|
}
|
||||||
|
.list(&data.site, pool)
|
||||||
|
.await?;
|
||||||
|
assert_eq!(
|
||||||
|
vec![POST_BY_BOT, POST],
|
||||||
|
names(&post_listings_show_read_true)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test with the show_read override as false
|
||||||
|
let post_listings_show_read_false = PostQuery {
|
||||||
|
show_read: Some(false),
|
||||||
|
..data.default_post_query()
|
||||||
|
}
|
||||||
|
.list(&data.site, pool)
|
||||||
|
.await?;
|
||||||
|
assert_eq!(vec![POST], names(&post_listings_show_read_false));
|
||||||
cleanup(data, pool).await
|
cleanup(data, pool).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1563,8 +1566,8 @@ mod tests {
|
||||||
// Make sure it does come back with the show_hidden option
|
// Make sure it does come back with the show_hidden option
|
||||||
let post_listings_show_hidden = PostQuery {
|
let post_listings_show_hidden = PostQuery {
|
||||||
sort: Some(SortType::New),
|
sort: Some(SortType::New),
|
||||||
local_user: Some(&data.local_user_view),
|
local_user: Some(&data.local_user_view.local_user),
|
||||||
show_hidden: true,
|
show_hidden: Some(true),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
.list(&data.site, pool)
|
.list(&data.site, pool)
|
||||||
|
@ -1655,6 +1658,7 @@ mod tests {
|
||||||
public_key: inserted_person.public_key.clone(),
|
public_key: inserted_person.public_key.clone(),
|
||||||
last_refreshed_at: inserted_person.last_refreshed_at,
|
last_refreshed_at: inserted_person.last_refreshed_at,
|
||||||
},
|
},
|
||||||
|
image_details: None,
|
||||||
creator_banned_from_community: false,
|
creator_banned_from_community: false,
|
||||||
banned_from_community: false,
|
banned_from_community: false,
|
||||||
creator_is_moderator: false,
|
creator_is_moderator: false,
|
||||||
|
@ -1738,7 +1742,7 @@ mod tests {
|
||||||
assert_eq!(0, unauthenticated_query.len());
|
assert_eq!(0, unauthenticated_query.len());
|
||||||
|
|
||||||
let authenticated_query = PostQuery {
|
let authenticated_query = PostQuery {
|
||||||
local_user: Some(&data.local_user_view),
|
local_user: Some(&data.local_user_view.local_user),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
.list(&data.site, pool)
|
.list(&data.site, pool)
|
||||||
|
|
|
@ -140,18 +140,10 @@ mod tests {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let new_person_1 = PersonInsertForm::builder()
|
let new_person_1 = PersonInsertForm::test_form(inserted_instance.id, "timmy_mrv");
|
||||||
.name("timmy_mrv".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
let inserted_timmy = Person::create(pool, &new_person_1).await.unwrap();
|
let inserted_timmy = Person::create(pool, &new_person_1).await.unwrap();
|
||||||
|
|
||||||
let new_person_2 = PersonInsertForm::builder()
|
let new_person_2 = PersonInsertForm::test_form(inserted_instance.id, "jessica_mrv");
|
||||||
.name("jessica_mrv".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
let inserted_jessica = Person::create(pool, &new_person_2).await.unwrap();
|
let inserted_jessica = Person::create(pool, &new_person_2).await.unwrap();
|
||||||
|
|
||||||
// timmy sends private message to jessica
|
// timmy sends private message to jessica
|
||||||
|
@ -184,11 +176,7 @@ mod tests {
|
||||||
assert_eq!(pm_report.reason, reports[0].private_message_report.reason);
|
assert_eq!(pm_report.reason, reports[0].private_message_report.reason);
|
||||||
assert_eq!(pm.content, reports[0].private_message.content);
|
assert_eq!(pm.content, reports[0].private_message.content);
|
||||||
|
|
||||||
let new_person_3 = PersonInsertForm::builder()
|
let new_person_3 = PersonInsertForm::test_form(inserted_instance.id, "admin_mrv");
|
||||||
.name("admin_mrv".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
let inserted_admin = Person::create(pool, &new_person_3).await.unwrap();
|
let inserted_admin = Person::create(pool, &new_person_3).await.unwrap();
|
||||||
|
|
||||||
// admin resolves the report (after taking appropriate action)
|
// admin resolves the report (after taking appropriate action)
|
||||||
|
|
|
@ -209,27 +209,15 @@ mod tests {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let timmy_form = PersonInsertForm::builder()
|
let timmy_form = PersonInsertForm::test_form(instance.id, "timmy_rav");
|
||||||
.name("timmy_rav".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let timmy = Person::create(pool, &timmy_form).await.unwrap();
|
let timmy = Person::create(pool, &timmy_form).await.unwrap();
|
||||||
|
|
||||||
let sara_form = PersonInsertForm::builder()
|
let sara_form = PersonInsertForm::test_form(instance.id, "sara_rav");
|
||||||
.name("sara_rav".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let sara = Person::create(pool, &sara_form).await.unwrap();
|
let sara = Person::create(pool, &sara_form).await.unwrap();
|
||||||
|
|
||||||
let jess_form = PersonInsertForm::builder()
|
let jess_form = PersonInsertForm::test_form(instance.id, "jess_rav");
|
||||||
.name("jess_rav".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let jess = Person::create(pool, &jess_form).await.unwrap();
|
let jess = Person::create(pool, &jess_form).await.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -163,11 +163,7 @@ mod tests {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let timmy_person_form = PersonInsertForm::builder()
|
let timmy_person_form = PersonInsertForm::test_form(inserted_instance.id, "timmy_rav");
|
||||||
.name("timmy_rav".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let inserted_timmy_person = Person::create(pool, &timmy_person_form).await.unwrap();
|
let inserted_timmy_person = Person::create(pool, &timmy_person_form).await.unwrap();
|
||||||
|
|
||||||
|
@ -181,11 +177,7 @@ mod tests {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let sara_person_form = PersonInsertForm::builder()
|
let sara_person_form = PersonInsertForm::test_form(inserted_instance.id, "sara_rav");
|
||||||
.name("sara_rav".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let inserted_sara_person = Person::create(pool, &sara_person_form).await.unwrap();
|
let inserted_sara_person = Person::create(pool, &sara_person_form).await.unwrap();
|
||||||
|
|
||||||
|
@ -213,11 +205,7 @@ mod tests {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let jess_person_form = PersonInsertForm::builder()
|
let jess_person_form = PersonInsertForm::test_form(inserted_instance.id, "jess_rav");
|
||||||
.name("jess_rav".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let inserted_jess_person = Person::create(pool, &jess_person_form).await.unwrap();
|
let inserted_jess_person = Person::create(pool, &jess_person_form).await.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ use lemmy_db_schema::{
|
||||||
community::Community,
|
community::Community,
|
||||||
custom_emoji::CustomEmoji,
|
custom_emoji::CustomEmoji,
|
||||||
custom_emoji_keyword::CustomEmojiKeyword,
|
custom_emoji_keyword::CustomEmojiKeyword,
|
||||||
images::LocalImage,
|
images::{ImageDetails, LocalImage},
|
||||||
local_site::LocalSite,
|
local_site::LocalSite,
|
||||||
local_site_rate_limit::LocalSiteRateLimit,
|
local_site_rate_limit::LocalSiteRateLimit,
|
||||||
local_user::LocalUser,
|
local_user::LocalUser,
|
||||||
|
@ -131,6 +131,7 @@ pub struct PostView {
|
||||||
pub post: Post,
|
pub post: Post,
|
||||||
pub creator: Person,
|
pub creator: Person,
|
||||||
pub community: Community,
|
pub community: Community,
|
||||||
|
pub image_details: Option<ImageDetails>,
|
||||||
pub creator_banned_from_community: bool,
|
pub creator_banned_from_community: bool,
|
||||||
pub banned_from_community: bool,
|
pub banned_from_community: bool,
|
||||||
pub creator_is_moderator: bool,
|
pub creator_is_moderator: bool,
|
||||||
|
|
|
@ -112,19 +112,11 @@ mod tests {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let new_person = PersonInsertForm::builder()
|
let new_person = PersonInsertForm::test_form(inserted_instance.id, "timmy_vv");
|
||||||
.name("timmy_vv".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let inserted_timmy = Person::create(pool, &new_person).await.unwrap();
|
let inserted_timmy = Person::create(pool, &new_person).await.unwrap();
|
||||||
|
|
||||||
let new_person_2 = PersonInsertForm::builder()
|
let new_person_2 = PersonInsertForm::test_form(inserted_instance.id, "sara_vv");
|
||||||
.name("sara_vv".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let inserted_sara = Person::create(pool, &new_person_2).await.unwrap();
|
let inserted_sara = Person::create(pool, &new_person_2).await.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -334,19 +334,13 @@ mod tests {
|
||||||
|
|
||||||
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
|
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
|
||||||
|
|
||||||
let terry_form = PersonInsertForm::builder()
|
let terry_form = PersonInsertForm::test_form(inserted_instance.id, "terrylake");
|
||||||
.name("terrylake".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
let inserted_terry = Person::create(pool, &terry_form).await?;
|
let inserted_terry = Person::create(pool, &terry_form).await?;
|
||||||
|
|
||||||
let recipient_form = PersonInsertForm::builder()
|
let recipient_form = PersonInsertForm {
|
||||||
.name("terrylakes recipient".into())
|
local: Some(true),
|
||||||
.public_key("pubkey".to_string())
|
..PersonInsertForm::test_form(inserted_instance.id, "terrylakes recipient")
|
||||||
.instance_id(inserted_instance.id)
|
};
|
||||||
.local(Some(true))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let inserted_recipient = Person::create(pool, &recipient_form).await?;
|
let inserted_recipient = Person::create(pool, &recipient_form).await?;
|
||||||
let recipient_id = inserted_recipient.id;
|
let recipient_id = inserted_recipient.id;
|
||||||
|
|
|
@ -90,7 +90,7 @@ impl CommunityModeratorView {
|
||||||
.distinct_on(community_moderator::community_id)
|
.distinct_on(community_moderator::community_id)
|
||||||
.order_by((
|
.order_by((
|
||||||
community_moderator::community_id,
|
community_moderator::community_id,
|
||||||
community_moderator::person_id,
|
community_moderator::published,
|
||||||
))
|
))
|
||||||
.load::<CommunityModeratorView>(conn)
|
.load::<CommunityModeratorView>(conn)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -11,6 +11,7 @@ use diesel::{
|
||||||
};
|
};
|
||||||
use diesel_async::RunQueryDsl;
|
use diesel_async::RunQueryDsl;
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
|
impls::local_user::LocalUserOptionHelper,
|
||||||
newtypes::{CommunityId, PersonId},
|
newtypes::{CommunityId, PersonId},
|
||||||
schema::{
|
schema::{
|
||||||
community,
|
community,
|
||||||
|
@ -19,11 +20,18 @@ use lemmy_db_schema::{
|
||||||
community_follower,
|
community_follower,
|
||||||
community_person_ban,
|
community_person_ban,
|
||||||
instance_block,
|
instance_block,
|
||||||
local_user,
|
|
||||||
},
|
},
|
||||||
source::{community::CommunityFollower, local_user::LocalUser, site::Site},
|
source::{community::CommunityFollower, local_user::LocalUser, site::Site},
|
||||||
utils::{fuzzy_search, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn},
|
utils::{
|
||||||
CommunityVisibility,
|
fuzzy_search,
|
||||||
|
limit_and_offset,
|
||||||
|
visible_communities_only,
|
||||||
|
DbConn,
|
||||||
|
DbPool,
|
||||||
|
ListFn,
|
||||||
|
Queries,
|
||||||
|
ReadFn,
|
||||||
|
},
|
||||||
ListingType,
|
ListingType,
|
||||||
SortType,
|
SortType,
|
||||||
};
|
};
|
||||||
|
@ -97,10 +105,7 @@ fn queries<'a>() -> Queries<
|
||||||
query = query.filter(not_removed_or_deleted);
|
query = query.filter(not_removed_or_deleted);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide local only communities from unauthenticated users
|
query = visible_communities_only(my_person_id, query);
|
||||||
if my_person_id.is_none() {
|
|
||||||
query = query.filter(community::visibility.eq(CommunityVisibility::Public));
|
|
||||||
}
|
|
||||||
|
|
||||||
query.first(&mut conn).await
|
query.first(&mut conn).await
|
||||||
};
|
};
|
||||||
|
@ -108,13 +113,13 @@ fn queries<'a>() -> Queries<
|
||||||
let list = move |mut conn: DbConn<'a>, (options, site): (CommunityQuery<'a>, &'a Site)| async move {
|
let list = move |mut conn: DbConn<'a>, (options, site): (CommunityQuery<'a>, &'a Site)| async move {
|
||||||
use SortType::*;
|
use SortType::*;
|
||||||
|
|
||||||
let my_person_id = options.local_user.map(|l| l.person_id);
|
|
||||||
|
|
||||||
// The left join below will return None in this case
|
// The left join below will return None in this case
|
||||||
let person_id_join = my_person_id.unwrap_or(PersonId(-1));
|
let person_id_join = options.local_user.person_id().unwrap_or(PersonId(-1));
|
||||||
|
|
||||||
let mut query = all_joins(community::table.into_boxed(), my_person_id)
|
let mut query = all_joins(
|
||||||
.left_join(local_user::table.on(local_user::person_id.eq(person_id_join)))
|
community::table.into_boxed(),
|
||||||
|
options.local_user.person_id(),
|
||||||
|
)
|
||||||
.select(selection);
|
.select(selection);
|
||||||
|
|
||||||
if let Some(search_term) = options.search_term {
|
if let Some(search_term) = options.search_term {
|
||||||
|
@ -162,20 +167,13 @@ fn queries<'a>() -> Queries<
|
||||||
|
|
||||||
// Don't show blocked communities and communities on blocked instances. nsfw communities are
|
// Don't show blocked communities and communities on blocked instances. nsfw communities are
|
||||||
// also hidden (based on profile setting)
|
// also hidden (based on profile setting)
|
||||||
if options.local_user.is_some() {
|
|
||||||
query = query.filter(instance_block::person_id.is_null());
|
query = query.filter(instance_block::person_id.is_null());
|
||||||
query = query.filter(community_block::person_id.is_null());
|
query = query.filter(community_block::person_id.is_null());
|
||||||
query = query.filter(community::nsfw.eq(false).or(local_user::show_nsfw.eq(true)));
|
if !(options.local_user.show_nsfw(site) || options.show_nsfw) {
|
||||||
} else {
|
|
||||||
// No person in request, only show nsfw communities if show_nsfw is passed into request or if
|
|
||||||
// site has content warning.
|
|
||||||
let has_content_warning = site.content_warning.is_some();
|
|
||||||
if !options.show_nsfw && !has_content_warning {
|
|
||||||
query = query.filter(community::nsfw.eq(false));
|
query = query.filter(community::nsfw.eq(false));
|
||||||
}
|
}
|
||||||
// Hide local only communities from unauthenticated users
|
|
||||||
query = query.filter(community::visibility.eq(CommunityVisibility::Public));
|
query = visible_communities_only(options.local_user.person_id(), query);
|
||||||
}
|
|
||||||
|
|
||||||
let (limit, offset) = limit_and_offset(options.page, options.limit)?;
|
let (limit, offset) = limit_and_offset(options.page, options.limit)?;
|
||||||
query
|
query
|
||||||
|
@ -286,11 +284,7 @@ mod tests {
|
||||||
|
|
||||||
let person_name = "tegan".to_string();
|
let person_name = "tegan".to_string();
|
||||||
|
|
||||||
let new_person = PersonInsertForm::builder()
|
let new_person = PersonInsertForm::test_form(inserted_instance.id, &person_name);
|
||||||
.name(person_name.clone())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let inserted_person = Person::create(pool, &new_person).await.unwrap();
|
let inserted_person = Person::create(pool, &new_person).await.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -334,19 +334,11 @@ mod tests {
|
||||||
|
|
||||||
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
|
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
|
||||||
|
|
||||||
let new_person = PersonInsertForm::builder()
|
let new_person = PersonInsertForm::test_form(inserted_instance.id, "terrylake");
|
||||||
.name("terrylake".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let inserted_person = Person::create(pool, &new_person).await?;
|
let inserted_person = Person::create(pool, &new_person).await?;
|
||||||
|
|
||||||
let recipient_form = PersonInsertForm::builder()
|
let recipient_form = PersonInsertForm::test_form(inserted_instance.id, "terrylakes recipient");
|
||||||
.name("terrylakes recipient".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let inserted_recipient = Person::create(pool, &recipient_form).await?;
|
let inserted_recipient = Person::create(pool, &recipient_form).await?;
|
||||||
let recipient_id = inserted_recipient.id;
|
let recipient_id = inserted_recipient.id;
|
||||||
|
|
|
@ -191,12 +191,10 @@ mod tests {
|
||||||
async fn init_data(pool: &mut DbPool<'_>) -> LemmyResult<Data> {
|
async fn init_data(pool: &mut DbPool<'_>) -> LemmyResult<Data> {
|
||||||
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
|
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
|
||||||
|
|
||||||
let alice_form = PersonInsertForm::builder()
|
let alice_form = PersonInsertForm {
|
||||||
.name("alice".to_string())
|
local: Some(true),
|
||||||
.public_key("pubkey".to_string())
|
..PersonInsertForm::test_form(inserted_instance.id, "alice")
|
||||||
.instance_id(inserted_instance.id)
|
};
|
||||||
.local(Some(true))
|
|
||||||
.build();
|
|
||||||
let alice = Person::create(pool, &alice_form).await?;
|
let alice = Person::create(pool, &alice_form).await?;
|
||||||
let alice_local_user_form = LocalUserInsertForm::builder()
|
let alice_local_user_form = LocalUserInsertForm::builder()
|
||||||
.person_id(alice.id)
|
.person_id(alice.id)
|
||||||
|
@ -204,13 +202,11 @@ mod tests {
|
||||||
.build();
|
.build();
|
||||||
let alice_local_user = LocalUser::create(pool, &alice_local_user_form, vec![]).await?;
|
let alice_local_user = LocalUser::create(pool, &alice_local_user_form, vec![]).await?;
|
||||||
|
|
||||||
let bob_form = PersonInsertForm::builder()
|
let bob_form = PersonInsertForm {
|
||||||
.name("bob".to_string())
|
bot_account: Some(true),
|
||||||
.bot_account(Some(true))
|
local: Some(false),
|
||||||
.public_key("pubkey".to_string())
|
..PersonInsertForm::test_form(inserted_instance.id, "bob")
|
||||||
.instance_id(inserted_instance.id)
|
};
|
||||||
.local(Some(false))
|
|
||||||
.build();
|
|
||||||
let bob = Person::create(pool, &bob_form).await?;
|
let bob = Person::create(pool, &bob_form).await?;
|
||||||
let bob_local_user_form = LocalUserInsertForm::builder()
|
let bob_local_user_form = LocalUserInsertForm::builder()
|
||||||
.person_id(bob.id)
|
.person_id(bob.id)
|
||||||
|
|
|
@ -136,6 +136,14 @@ impl InstanceWorker {
|
||||||
// lazily fetch latest id only if we have cought up
|
// lazily fetch latest id only if we have cought up
|
||||||
newest_id = self.get_latest_ids().await?.1;
|
newest_id = self.get_latest_ids().await?.1;
|
||||||
if next_id_to_send > newest_id {
|
if next_id_to_send > newest_id {
|
||||||
|
if next_id_to_send > ActivityId(newest_id.0 + 1) {
|
||||||
|
tracing::error!(
|
||||||
|
"{}: next send id {} is higher than latest id {}+1 in database (did the db get cleared?)",
|
||||||
|
self.instance.domain,
|
||||||
|
next_id_to_send.0,
|
||||||
|
newest_id.0
|
||||||
|
);
|
||||||
|
}
|
||||||
// no more work to be done, wait before rechecking
|
// no more work to be done, wait before rechecking
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
() = sleep(*WORK_FINISHED_RECHECK_DELAY) => {},
|
() = sleep(*WORK_FINISHED_RECHECK_DELAY) => {},
|
||||||
|
|
|
@ -355,7 +355,7 @@ async fn get_feed_front(
|
||||||
|
|
||||||
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.local_user)),
|
||||||
sort: (Some(*sort_type)),
|
sort: (Some(*sort_type)),
|
||||||
limit: (Some(*limit)),
|
limit: (Some(*limit)),
|
||||||
page: (Some(*page)),
|
page: (Some(*page)),
|
||||||
|
|
|
@ -103,7 +103,13 @@ async fn upload(
|
||||||
pictrs_alias: image.file.to_string(),
|
pictrs_alias: image.file.to_string(),
|
||||||
pictrs_delete_token: image.delete_token.to_string(),
|
pictrs_delete_token: image.delete_token.to_string(),
|
||||||
};
|
};
|
||||||
LocalImage::create(&mut context.pool(), &form).await?;
|
|
||||||
|
let protocol_and_hostname = context.settings().get_protocol_and_hostname();
|
||||||
|
let thumbnail_url = image.thumbnail_url(&protocol_and_hostname)?;
|
||||||
|
|
||||||
|
// Also store the details for the image
|
||||||
|
let details_form = image.details.build_image_details_form(&thumbnail_url);
|
||||||
|
LocalImage::create(&mut context.pool(), &form, &details_form).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,9 @@ static VALID_MATRIX_ID_REGEX: Lazy<Regex> = Lazy::new(|| {
|
||||||
});
|
});
|
||||||
// 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 ALLOWED_POST_URL_SCHEMES: [&str; 3] = ["http", "https", "magnet"];
|
||||||
|
@ -158,14 +160,12 @@ pub fn is_valid_post_title(title: &str) -> LemmyResult<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This could be post bodies, comments, or any description field
|
/// This could be post bodies, comments, or any description field
|
||||||
pub fn is_valid_body_field(body: &Option<String>, post: bool) -> LemmyResult<()> {
|
pub fn is_valid_body_field(body: &str, post: bool) -> LemmyResult<()> {
|
||||||
if let Some(body) = body {
|
|
||||||
if post {
|
if post {
|
||||||
max_length_check(body, POST_BODY_MAX_LENGTH, LemmyErrorType::InvalidBodyField)?;
|
max_length_check(body, POST_BODY_MAX_LENGTH, LemmyErrorType::InvalidBodyField)?;
|
||||||
} else {
|
} else {
|
||||||
max_length_check(body, BODY_MAX_LENGTH, LemmyErrorType::InvalidBodyField)?;
|
max_length_check(body, BODY_MAX_LENGTH, LemmyErrorType::InvalidBodyField)?;
|
||||||
};
|
};
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,17 +173,15 @@ pub fn is_valid_bio_field(bio: &str) -> LemmyResult<()> {
|
||||||
max_length_check(bio, BIO_MAX_LENGTH, LemmyErrorType::BioLengthOverflow)
|
max_length_check(bio, BIO_MAX_LENGTH, LemmyErrorType::BioLengthOverflow)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_valid_alt_text_field(alt_text: &Option<String>) -> LemmyResult<()> {
|
pub fn is_valid_alt_text_field(alt_text: &str) -> LemmyResult<()> {
|
||||||
if let Some(alt_text) = alt_text {
|
|
||||||
max_length_check(
|
max_length_check(
|
||||||
alt_text,
|
alt_text,
|
||||||
ALT_TEXT_MAX_LENGTH,
|
ALT_TEXT_MAX_LENGTH,
|
||||||
LemmyErrorType::AltTextLengthOverflow,
|
LemmyErrorType::AltTextLengthOverflow,
|
||||||
)
|
)?;
|
||||||
} else {
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks the site name length, the limit as defined in the DB.
|
/// Checks the site name length, the limit as defined in the DB.
|
||||||
pub fn site_name_length_check(name: &str) -> LemmyResult<()> {
|
pub fn site_name_length_check(name: &str) -> LemmyResult<()> {
|
||||||
|
@ -260,12 +258,11 @@ pub fn build_and_check_regex(regex_str_opt: &Option<&str>) -> LemmyResult<Option
|
||||||
|
|
||||||
pub fn clean_url_params(url: &Url) -> Url {
|
pub fn clean_url_params(url: &Url) -> Url {
|
||||||
let mut url_out = url.clone();
|
let mut url_out = url.clone();
|
||||||
if url.query().is_some() {
|
if let Some(query) = url.query() {
|
||||||
let new_query = url
|
let new_query = query
|
||||||
.query_pairs()
|
.split_inclusive('&')
|
||||||
.filter(|q| !CLEAN_URL_PARAMS_REGEX.is_match(&q.0))
|
.filter(|q| !CLEAN_URL_PARAMS_REGEX.is_match(q))
|
||||||
.map(|q| format!("{}={}", q.0, q.1))
|
.collect::<String>();
|
||||||
.join("&");
|
|
||||||
url_out.set_query(Some(&new_query));
|
url_out.set_query(Some(&new_query));
|
||||||
}
|
}
|
||||||
url_out
|
url_out
|
||||||
|
@ -287,24 +284,18 @@ pub fn check_site_visibility_valid(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_url_scheme(url: &Option<Url>) -> LemmyResult<()> {
|
pub fn check_url_scheme(url: &Url) -> LemmyResult<()> {
|
||||||
if let Some(url) = url {
|
|
||||||
if !ALLOWED_POST_URL_SCHEMES.contains(&url.scheme()) {
|
if !ALLOWED_POST_URL_SCHEMES.contains(&url.scheme()) {
|
||||||
Err(LemmyErrorType::InvalidUrlScheme.into())
|
Err(LemmyErrorType::InvalidUrlScheme)?
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_url_blocked(url: &Option<Url>, blocklist: &RegexSet) -> LemmyResult<()> {
|
Ok(())
|
||||||
if let Some(url) = url {
|
}
|
||||||
|
|
||||||
|
pub fn is_url_blocked(url: &Url, blocklist: &RegexSet) -> LemmyResult<()> {
|
||||||
if blocklist.is_match(url.as_str()) {
|
if blocklist.is_match(url.as_str()) {
|
||||||
Err(LemmyErrorType::BlockedUrl)?
|
Err(LemmyErrorType::BlockedUrl)?
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -350,12 +341,11 @@ pub fn build_url_str_without_scheme(url_str: &str) -> LemmyResult<String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
#[allow(clippy::indexing_slicing)]
|
#[allow(clippy::indexing_slicing)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::LemmyErrorType,
|
error::{LemmyErrorType, LemmyResult},
|
||||||
utils::validation::{
|
utils::validation::{
|
||||||
build_and_check_regex,
|
build_and_check_regex,
|
||||||
check_site_visibility_valid,
|
check_site_visibility_valid,
|
||||||
|
@ -379,15 +369,17 @@ mod tests {
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_clean_url_params() {
|
fn test_clean_url_params() -> LemmyResult<()> {
|
||||||
let url = Url::parse("https://example.com/path/123?utm_content=buffercf3b2&utm_medium=social&username=randomuser&id=123").unwrap();
|
let url = Url::parse("https://example.com/path/123?utm_content=buffercf3b2&utm_medium=social&user+name=random+user%20&id=123")?;
|
||||||
let cleaned = clean_url_params(&url);
|
let cleaned = clean_url_params(&url);
|
||||||
let expected = Url::parse("https://example.com/path/123?username=randomuser&id=123").unwrap();
|
let expected = Url::parse("https://example.com/path/123?user+name=random+user%20&id=123")?;
|
||||||
assert_eq!(expected.to_string(), cleaned.to_string());
|
assert_eq!(expected.to_string(), cleaned.to_string());
|
||||||
|
|
||||||
let url = Url::parse("https://example.com/path/123").unwrap();
|
let url = Url::parse("https://example.com/path/123")?;
|
||||||
let cleaned = clean_url_params(&url);
|
let cleaned = clean_url_params(&url);
|
||||||
assert_eq!(url.to_string(), cleaned.to_string());
|
assert_eq!(url.to_string(), cleaned.to_string());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -465,7 +457,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_valid_site_name() {
|
fn test_valid_site_name() -> LemmyResult<()> {
|
||||||
let valid_names = [
|
let valid_names = [
|
||||||
(0..SITE_NAME_MAX_LENGTH).map(|_| 'A').collect::<String>(),
|
(0..SITE_NAME_MAX_LENGTH).map(|_| 'A').collect::<String>(),
|
||||||
String::from("A"),
|
String::from("A"),
|
||||||
|
@ -496,12 +488,13 @@ mod tests {
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
assert!(
|
assert!(
|
||||||
result.unwrap_err().error_type.eq(&expected_err.clone()),
|
result.is_err_and(|e| e.error_type.eq(&expected_err.clone())),
|
||||||
"Testing {}, expected error {}",
|
"Testing {}, expected error {}",
|
||||||
invalid_name,
|
invalid_name,
|
||||||
expected_err
|
expected_err
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -513,10 +506,7 @@ mod tests {
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
invalid_result.is_err()
|
invalid_result.is_err()
|
||||||
&& invalid_result
|
&& invalid_result.is_err_and(|e| e.error_type.eq(&LemmyErrorType::BioLengthOverflow))
|
||||||
.unwrap_err()
|
|
||||||
.error_type
|
|
||||||
.eq(&LemmyErrorType::BioLengthOverflow)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -537,10 +527,9 @@ mod tests {
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
invalid_result.is_err()
|
invalid_result.is_err()
|
||||||
&& invalid_result
|
&& invalid_result.is_err_and(|e| e
|
||||||
.unwrap_err()
|
|
||||||
.error_type
|
.error_type
|
||||||
.eq(&LemmyErrorType::SiteDescriptionLengthOverflow)
|
.eq(&LemmyErrorType::SiteDescriptionLengthOverflow))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -570,7 +559,7 @@ mod tests {
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
assert!(
|
assert!(
|
||||||
result.unwrap_err().error_type.eq(&expected_err.clone()),
|
result.is_err_and(|e| e.error_type.eq(&expected_err.clone())),
|
||||||
"Testing regex {:?}, expected error {}",
|
"Testing regex {:?}, expected error {}",
|
||||||
regex_str,
|
regex_str,
|
||||||
expected_err
|
expected_err
|
||||||
|
@ -591,38 +580,38 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_check_url_scheme() {
|
fn test_check_url_scheme() -> LemmyResult<()> {
|
||||||
assert!(check_url_scheme(&None).is_ok());
|
assert!(check_url_scheme(&Url::parse("http://example.com")?).is_ok());
|
||||||
assert!(check_url_scheme(&Some(Url::parse("http://example.com").unwrap())).is_ok());
|
assert!(check_url_scheme(&Url::parse("https://example.com")?).is_ok());
|
||||||
assert!(check_url_scheme(&Some(Url::parse("https://example.com").unwrap())).is_ok());
|
assert!(check_url_scheme(&Url::parse("https://example.com")?).is_ok());
|
||||||
assert!(check_url_scheme(&Some(Url::parse("https://example.com").unwrap())).is_ok());
|
assert!(check_url_scheme(&Url::parse("ftp://example.com")?).is_err());
|
||||||
assert!(check_url_scheme(&Some(Url::parse("ftp://example.com").unwrap())).is_err());
|
assert!(check_url_scheme(&Url::parse("javascript:void")?).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";
|
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());
|
assert!(check_url_scheme(&Url::parse(magnet_link)?).is_ok());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_url_block() {
|
fn test_url_block() -> LemmyResult<()> {
|
||||||
let set = regex::RegexSet::new(vec![
|
let set = regex::RegexSet::new(vec![
|
||||||
r"(https://)?example\.org/page/to/article",
|
r"(https://)?example\.org/page/to/article",
|
||||||
r"(https://)?example\.net/?",
|
r"(https://)?example\.net/?",
|
||||||
r"(https://)?example\.com/?",
|
r"(https://)?example\.com/?",
|
||||||
])
|
])?;
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert!(is_url_blocked(&Some(Url::parse("https://example.blog").unwrap()), &set).is_ok());
|
assert!(is_url_blocked(&Url::parse("https://example.blog")?, &set).is_ok());
|
||||||
|
|
||||||
assert!(is_url_blocked(&Some(Url::parse("https://example.org").unwrap()), &set).is_ok());
|
assert!(is_url_blocked(&Url::parse("https://example.org")?, &set).is_ok());
|
||||||
|
|
||||||
assert!(is_url_blocked(&None, &set).is_ok());
|
assert!(is_url_blocked(&Url::parse("https://example.com")?, &set).is_err());
|
||||||
|
|
||||||
assert!(is_url_blocked(&Some(Url::parse("https://example.com").unwrap()), &set).is_err());
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_url_parsed() {
|
fn test_url_parsed() -> LemmyResult<()> {
|
||||||
// Make sure the scheme is removed, and uniques also
|
// Make sure the scheme is removed, and uniques also
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&check_urls_are_valid(&vec![
|
&check_urls_are_valid(&vec![
|
||||||
|
@ -630,8 +619,7 @@ mod tests {
|
||||||
"http://example.com".to_string(),
|
"http://example.com".to_string(),
|
||||||
"https://example.com".to_string(),
|
"https://example.com".to_string(),
|
||||||
"https://example.com/test?q=test2&q2=test3#test4".to_string(),
|
"https://example.com/test?q=test2&q2=test3#test4".to_string(),
|
||||||
])
|
])?,
|
||||||
.unwrap(),
|
|
||||||
&vec![
|
&vec![
|
||||||
"example.com".to_string(),
|
"example.com".to_string(),
|
||||||
"example.com/test?q=test2&q2=test3#test4".to_string()
|
"example.com/test?q=test2&q2=test3#test4".to_string()
|
||||||
|
@ -639,5 +627,6 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(check_urls_are_valid(&vec!["https://example .com".to_string()]).is_err());
|
assert!(check_urls_are_valid(&vec!["https://example .com".to_string()]).is_err());
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit dca5d8ca0309d7cb07eaca821140e4525e75ee57
|
Subproject commit ee2cffac809ad466644f061ad79ac577b6c2e4fd
|
|
@ -1,4 +1,4 @@
|
||||||
# syntax=docker/dockerfile:1.7
|
# syntax=docker/dockerfile:1.8
|
||||||
ARG RUST_VERSION=1.78
|
ARG RUST_VERSION=1.78
|
||||||
ARG CARGO_BUILD_FEATURES=default
|
ARG CARGO_BUILD_FEATURES=default
|
||||||
ARG RUST_RELEASE_MODE=debug
|
ARG RUST_RELEASE_MODE=debug
|
||||||
|
|
|
@ -23,7 +23,7 @@ services:
|
||||||
|
|
||||||
lemmy:
|
lemmy:
|
||||||
# use "image" to pull down an already compiled lemmy. make sure to comment out "build".
|
# use "image" to pull down an already compiled lemmy. make sure to comment out "build".
|
||||||
# image: dessalines/lemmy:0.19.3
|
# image: dessalines/lemmy:0.19.5
|
||||||
# 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 server image for development. make sure to comment out "image".
|
# use "build" to build your local lemmy server image for development. make sure to comment out "image".
|
||||||
# run: docker compose up --build
|
# run: docker compose up --build
|
||||||
|
@ -53,7 +53,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.19.4-rc.3
|
image: dessalines/lemmy-ui:0.19.5
|
||||||
# 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
|
||||||
|
@ -75,7 +75,7 @@ services:
|
||||||
init: true
|
init: true
|
||||||
|
|
||||||
pictrs:
|
pictrs:
|
||||||
image: asonix/pictrs:0.5.14
|
image: asonix/pictrs:0.5.16
|
||||||
# this needs to match the pictrs url in lemmy.hjson
|
# this needs to match the pictrs url in lemmy.hjson
|
||||||
hostname: pictrs
|
hostname: pictrs
|
||||||
# we can set options to pictrs like this, here we set max. image size and forced format for conversion
|
# we can set options to pictrs like this, here we set max. image size and forced format for conversion
|
||||||
|
|
|
@ -2,7 +2,7 @@ version: "3.7"
|
||||||
|
|
||||||
x-ui-default: &ui-default
|
x-ui-default: &ui-default
|
||||||
init: true
|
init: true
|
||||||
image: dessalines/lemmy-ui:0.19.3
|
image: dessalines/lemmy-ui:0.19.5
|
||||||
# assuming lemmy-ui is cloned besides lemmy directory
|
# assuming lemmy-ui is cloned besides lemmy directory
|
||||||
# build:
|
# build:
|
||||||
# context: ../../../lemmy-ui
|
# context: ../../../lemmy-ui
|
||||||
|
@ -49,7 +49,7 @@ services:
|
||||||
|
|
||||||
pictrs:
|
pictrs:
|
||||||
restart: always
|
restart: always
|
||||||
image: asonix/pictrs:0.5.14
|
image: asonix/pictrs:0.5.16
|
||||||
user: 991:991
|
user: 991:991
|
||||||
volumes:
|
volumes:
|
||||||
- ./volumes/pictrs_alpha:/mnt:Z
|
- ./volumes/pictrs_alpha:/mnt:Z
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
ALTER TABLE remote_image
|
||||||
|
ADD UNIQUE (link),
|
||||||
|
DROP CONSTRAINT remote_image_pkey,
|
||||||
|
ADD COLUMN id serial PRIMARY KEY;
|
||||||
|
|
||||||
|
DROP TABLE image_details;
|
||||||
|
|
15
migrations/2024-05-05-162540_add_image_detail_table/up.sql
Normal file
15
migrations/2024-05-05-162540_add_image_detail_table/up.sql
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
-- Drop the id column from the remote_image table, just use link
|
||||||
|
ALTER TABLE remote_image
|
||||||
|
DROP COLUMN id,
|
||||||
|
ADD PRIMARY KEY (link),
|
||||||
|
DROP CONSTRAINT remote_image_link_key;
|
||||||
|
|
||||||
|
-- No good way to do references here unfortunately, unless we combine the images tables
|
||||||
|
-- The link should be the URL, not the pictrs_alias, to allow joining from post.thumbnail_url
|
||||||
|
CREATE TABLE image_details (
|
||||||
|
link text PRIMARY KEY,
|
||||||
|
width integer NOT NULL,
|
||||||
|
height integer NOT NULL,
|
||||||
|
content_type text NOT NULL
|
||||||
|
);
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
SELECT
|
||||||
|
;
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
-- Fix rows that were not updated because of the old incorrect trigger
|
||||||
|
UPDATE
|
||||||
|
post_aggregates
|
||||||
|
SET
|
||||||
|
featured_local = post.featured_local
|
||||||
|
FROM
|
||||||
|
post
|
||||||
|
WHERE
|
||||||
|
post.id = post_aggregates.post_id
|
||||||
|
AND post.featured_local != post_aggregates.featured_local;
|
||||||
|
|
|
@ -24,7 +24,7 @@ echo "Removing the old postgres folder"
|
||||||
sudo rm -rf volumes/postgres
|
sudo rm -rf volumes/postgres
|
||||||
|
|
||||||
echo "Updating docker compose to use postgres version 16."
|
echo "Updating docker compose to use postgres version 16."
|
||||||
sudo sed -i "s/image: .*postgres:.*/image: docker.io/postgres:16-alpine/" ./docker-compose.yml
|
sudo sed -i "s/image: .*postgres:.*/image: docker.io\/postgres:16-alpine/" ./docker-compose.yml
|
||||||
|
|
||||||
echo "Starting up new postgres..."
|
echo "Starting up new postgres..."
|
||||||
sudo docker compose up -d postgres
|
sudo docker compose up -d postgres
|
||||||
|
|
|
@ -10,7 +10,7 @@ third_semver=$(echo $new_tag | cut -d "." -f 3)
|
||||||
CWD="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
CWD="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
||||||
cd $CWD/../
|
cd $CWD/../
|
||||||
|
|
||||||
# The ansible and docker installs should only update for non release-candidates
|
# The docker installs should only update for non release-candidates
|
||||||
# IE, when the third semver is a number, not '2-rc'
|
# IE, when the third semver is a number, not '2-rc'
|
||||||
if [ ! -z "${third_semver##*[!0-9]*}" ]; then
|
if [ ! -z "${third_semver##*[!0-9]*}" ]; then
|
||||||
pushd docker
|
pushd docker
|
||||||
|
@ -20,14 +20,6 @@ if [ ! -z "${third_semver##*[!0-9]*}" ]; then
|
||||||
git add docker-compose.yml
|
git add docker-compose.yml
|
||||||
git add federation/docker-compose.yml
|
git add federation/docker-compose.yml
|
||||||
popd
|
popd
|
||||||
|
|
||||||
# Setting the version for Ansible
|
|
||||||
pushd ../lemmy-ansible
|
|
||||||
echo $new_tag > "VERSION"
|
|
||||||
git add "VERSION"
|
|
||||||
git commit -m"Updating VERSION"
|
|
||||||
git push
|
|
||||||
popd
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Update crate versions
|
# Update crate versions
|
||||||
|
|
|
@ -5,6 +5,9 @@ pushd ../
|
||||||
# Check unused deps
|
# Check unused deps
|
||||||
cargo udeps --all-targets
|
cargo udeps --all-targets
|
||||||
|
|
||||||
|
# Update deps first
|
||||||
|
cargo update
|
||||||
|
|
||||||
# Upgrade deps
|
# Upgrade deps
|
||||||
cargo upgrade
|
cargo upgrade
|
||||||
|
|
||||||
|
|
|
@ -455,15 +455,17 @@ async fn initialize_local_site_2022_10_10(
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Register the user if there's a site setup
|
// Register the user if there's a site setup
|
||||||
let person_form = PersonInsertForm::builder()
|
let person_form = PersonInsertForm {
|
||||||
.name(setup.admin_username.clone())
|
actor_id: Some(person_actor_id.clone()),
|
||||||
.instance_id(instance.id)
|
inbox_url: Some(generate_inbox_url(&person_actor_id)?),
|
||||||
.actor_id(Some(person_actor_id.clone()))
|
shared_inbox_url: Some(generate_shared_inbox_url(settings)?),
|
||||||
.private_key(Some(person_keypair.private_key))
|
private_key: Some(person_keypair.private_key),
|
||||||
.public_key(person_keypair.public_key)
|
..PersonInsertForm::new(
|
||||||
.inbox_url(Some(generate_inbox_url(&person_actor_id)?))
|
setup.admin_username.clone(),
|
||||||
.shared_inbox_url(Some(generate_shared_inbox_url(settings)?))
|
person_keypair.public_key,
|
||||||
.build();
|
instance.id,
|
||||||
|
)
|
||||||
|
};
|
||||||
let person_inserted = Person::create(pool, &person_form).await?;
|
let person_inserted = Person::create(pool, &person_form).await?;
|
||||||
|
|
||||||
let local_user_form = LocalUserInsertForm::builder()
|
let local_user_form = LocalUserInsertForm::builder()
|
||||||
|
|
|
@ -484,9 +484,6 @@ async fn update_instance_software(
|
||||||
/// This builds an instance update form, for a given domain.
|
/// This builds an instance update form, for a given domain.
|
||||||
/// If the instance sends a response, but doesn't have a well-known or nodeinfo,
|
/// If the instance sends a response, but doesn't have a well-known or nodeinfo,
|
||||||
/// Then return a default form with only the updated field.
|
/// Then return a default form with only the updated field.
|
||||||
///
|
|
||||||
/// TODO This function is a bit of a nightmare with its embedded matches, but the only other way
|
|
||||||
/// would be to extract the fetches into functions which return the default_form on errors.
|
|
||||||
async fn build_update_instance_form(
|
async fn build_update_instance_form(
|
||||||
domain: &str,
|
domain: &str,
|
||||||
client: &ClientWithMiddleware,
|
client: &ClientWithMiddleware,
|
||||||
|
@ -504,55 +501,51 @@ async fn build_update_instance_form(
|
||||||
// First, fetch their /.well-known/nodeinfo, then extract the correct nodeinfo link from it
|
// First, fetch their /.well-known/nodeinfo, then extract the correct nodeinfo link from it
|
||||||
let well_known_url = format!("https://{}/.well-known/nodeinfo", domain);
|
let well_known_url = format!("https://{}/.well-known/nodeinfo", domain);
|
||||||
|
|
||||||
match client.get(&well_known_url).send().await {
|
let Ok(res) = client.get(&well_known_url).send().await else {
|
||||||
Ok(res) if res.status().is_client_error() => {
|
// This is the only kind of error that means the instance is dead
|
||||||
// Instance doesn't have well-known but sent a response, consider it alive
|
return None;
|
||||||
Some(instance_form)
|
};
|
||||||
|
|
||||||
|
// In this block, returning `None` is ignored, and only means not writing nodeinfo to db
|
||||||
|
async {
|
||||||
|
if res.status().is_client_error() {
|
||||||
|
return None;
|
||||||
}
|
}
|
||||||
Ok(res) => match res.json::<NodeInfoWellKnown>().await {
|
|
||||||
Ok(well_known) => {
|
let node_info_url = res
|
||||||
// Find the first link where the rel contains the allowed rels above
|
.json::<NodeInfoWellKnown>()
|
||||||
match well_known.links.into_iter().find(|links| {
|
.await
|
||||||
|
.ok()?
|
||||||
|
.links
|
||||||
|
.into_iter()
|
||||||
|
.find(|links| {
|
||||||
links
|
links
|
||||||
.rel
|
.rel
|
||||||
.as_str()
|
.as_str()
|
||||||
.starts_with("http://nodeinfo.diaspora.software/ns/schema/2.")
|
.starts_with("http://nodeinfo.diaspora.software/ns/schema/2.")
|
||||||
}) {
|
})?
|
||||||
Some(well_known_link) => {
|
.href;
|
||||||
let node_info_url = well_known_link.href;
|
|
||||||
|
|
||||||
// Fetch the node_info from the well known href
|
let software = client
|
||||||
match client.get(node_info_url).send().await {
|
.get(node_info_url)
|
||||||
Ok(node_info_res) => match node_info_res.json::<NodeInfo>().await {
|
.send()
|
||||||
Ok(node_info) => {
|
.await
|
||||||
// Instance sent valid nodeinfo, write it to db
|
.ok()?
|
||||||
// Set the instance form fields.
|
.json::<NodeInfo>()
|
||||||
if let Some(software) = node_info.software.as_ref() {
|
.await
|
||||||
instance_form.software.clone_from(&software.name);
|
.ok()?
|
||||||
instance_form.version.clone_from(&software.version);
|
.software?;
|
||||||
|
|
||||||
|
instance_form.software = software.name;
|
||||||
|
instance_form.version = software.version;
|
||||||
|
|
||||||
|
Some(())
|
||||||
}
|
}
|
||||||
|
.await;
|
||||||
|
|
||||||
Some(instance_form)
|
Some(instance_form)
|
||||||
}
|
}
|
||||||
Err(_) => Some(instance_form),
|
|
||||||
},
|
|
||||||
Err(_) => Some(instance_form),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If none is found, use the default form above
|
|
||||||
None => Some(instance_form),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
// No valid nodeinfo but valid HTTP response, consider instance alive
|
|
||||||
Some(instance_form)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(_) => {
|
|
||||||
// dead instance, do nothing
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[allow(clippy::indexing_slicing)]
|
#[allow(clippy::indexing_slicing)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
|
@ -142,11 +142,7 @@ mod tests {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let new_person = PersonInsertForm::builder()
|
let new_person = PersonInsertForm::test_form(inserted_instance.id, "Gerry9812");
|
||||||
.name("Gerry9812".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let inserted_person = Person::create(pool, &new_person).await.unwrap();
|
let inserted_person = Person::create(pool, &new_person).await.unwrap();
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue