Merge remote-tracking branch 'upstream/main' into smoosh-tables-together

This commit is contained in:
Dull Bananas 2024-03-29 16:35:34 +00:00
commit cfe4bf90db
98 changed files with 1820 additions and 1102 deletions

2
.github/CODEOWNERS vendored
View file

@ -1,3 +1,3 @@
* @Nutomic @dessalines @phiresky @dullbananas
* @Nutomic @dessalines @phiresky @dullbananas @SleeplessOne1917
crates/apub/ @Nutomic
migrations/ @dessalines @phiresky @dullbananas

View file

@ -5,22 +5,25 @@ variables:
- &rust_image "rust:1.76"
- &install_pnpm "corepack enable pnpm"
- &slow_check_paths
- path:
# rust source code
- "crates/**"
- "src/**"
- "**/Cargo.toml"
- "Cargo.lock"
# database migrations
- "migrations/**"
# typescript tests
- "api_tests/**"
# config files and scripts used by ci
- ".woodpecker.yml"
- ".rustfmt.toml"
- "scripts/update_config_defaults.sh"
- "diesel.toml"
- ".gitmodules"
- event: pull_request
path:
include: [
# rust source code
"crates/**",
"src/**",
"**/Cargo.toml",
"Cargo.lock",
# database migrations
"migrations/**",
# typescript tests
"api_tests/**",
# config files and scripts used by ci
".woodpecker.yml",
".rustfmt.toml",
"scripts/update_config_defaults.sh",
"diesel.toml",
".gitmodules",
]
# Broken for cron jobs currently, see
# https://github.com/woodpecker-ci/woodpecker/issues/1716
@ -38,21 +41,29 @@ steps:
- apk add git
- git submodule init
- git submodule update
when:
- event: pull_request
prettier_check:
image: tmknom/prettier:3.0.0
commands:
- prettier -c . '!**/volumes' '!**/dist' '!target' '!**/translations' '!api_tests/pnpm-lock.yaml'
when:
- event: pull_request
toml_fmt:
image: tamasfe/taplo:0.8.1
commands:
- taplo format --check
when:
- event: pull_request
sql_fmt:
image: backplane/pgformatter:latest
commands:
- ./scripts/sql_format_check.sh
when:
- event: pull_request
cargo_fmt:
image: rustlang/rust:nightly
@ -62,6 +73,8 @@ steps:
commands:
- rustup component add rustfmt
- cargo +nightly fmt -- --check
when:
- event: pull_request
cargo_machete:
image: rustlang/rust:nightly
@ -71,6 +84,8 @@ steps:
- cp cargo-binstall /usr/local/cargo/bin
- cargo binstall -y cargo-machete
- cargo machete
when:
- event: pull_request
ignored_files:
image: alpine:3
@ -78,6 +93,8 @@ steps:
- apk add git
- IGNORED=$(git ls-files --cached -i --exclude-standard)
- if [[ "$IGNORED" ]]; then echo "Ignored files present:\n$IGNORED\n"; exit 1; fi
when:
- event: pull_request
# make sure api builds with default features (used by other crates relying on lemmy api)
check_api_common_default_features:
@ -198,7 +215,8 @@ steps:
- cat target/log/lemmy_*.out || true
- "# If you can't see all output, then use the download button"
when:
status: [failure]
- event: pull_request
status: failure
publish_release_docker:
image: woodpeckerci/plugin-docker-buildx
@ -211,7 +229,7 @@ steps:
- RUST_RELEASE_MODE=release
tag: ${CI_COMMIT_TAG}
when:
event: tag
- event: tag
nightly_build:
image: woodpeckerci/plugin-docker-buildx
@ -224,7 +242,7 @@ steps:
- RUST_RELEASE_MODE=release
tag: dev
when:
event: cron
- event: cron
# using https://github.com/pksunkara/cargo-workspaces
publish_to_crates_io:
@ -237,7 +255,7 @@ steps:
- cargo workspaces publish --from-git --allow-dirty --no-verify --allow-branch "${CI_COMMIT_TAG}" --yes custom "${CI_COMMIT_TAG}"
secrets: [cargo_api_token]
when:
event: tag
- event: tag
notify_on_failure:
image: alpine:3
@ -245,7 +263,8 @@ steps:
- apk add curl
- "curl -d'Lemmy CI build failed: ${CI_PIPELINE_URL}' ntfy.sh/lemmy_drone_ci"
when:
status: [failure]
- event: pull_request
status: failure
notify_on_tag_deploy:
image: alpine:3
@ -253,7 +272,7 @@ steps:
- apk add curl
- "curl -d'lemmy:${CI_COMMIT_TAG} deployed' ntfy.sh/lemmy_drone_ci"
when:
event: tag
- event: tag
services:
database:

970
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -37,6 +37,8 @@ debug = 0
[features]
embed-pictrs = ["pict-rs"]
# This feature requires building with `tokio_unstable` flag, see documentation:
# https://docs.rs/tokio/latest/tokio/#unstable-features
console = [
"console-subscriber",
"opentelemetry",
@ -102,9 +104,9 @@ activitypub_federation = { version = "0.5.2", default-features = false, features
diesel = "2.1.4"
diesel_migrations = "2.1.0"
diesel-async = "0.4.1"
serde = { version = "1.0.195", features = ["derive"] }
serde_with = "3.5.1"
actix-web = { version = "4.4.1", default-features = false, features = [
serde = { version = "1.0.197", features = ["derive"] }
serde_with = "3.7.0"
actix-web = { version = "4.5.1", default-features = false, features = [
"macros",
"rustls",
"compress-brotli",
@ -113,39 +115,39 @@ actix-web = { version = "4.4.1", default-features = false, features = [
"cookies",
] }
tracing = "0.1.40"
tracing-actix-web = { version = "0.7.9", default-features = false }
tracing-actix-web = { version = "0.7.10", default-features = false }
tracing-error = "0.2.0"
tracing-log = "0.2.0"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
url = { version = "2.5.0", features = ["serde"] }
reqwest = { version = "0.11.23", features = ["json", "blocking", "gzip"] }
reqwest = { version = "0.11.26", features = ["json", "blocking", "gzip"] }
reqwest-middleware = "0.2.4"
reqwest-tracing = "0.4.7"
clokwerk = "0.4.0"
doku = { version = "0.21.1", features = ["url-2"] }
bcrypt = "0.15.0"
chrono = { version = "0.4.32", features = ["serde"], default-features = false }
serde_json = { version = "1.0.111", features = ["preserve_order"] }
chrono = { version = "0.4.35", features = ["serde"], default-features = false }
serde_json = { version = "1.0.114", features = ["preserve_order"] }
base64 = "0.21.7"
uuid = { version = "1.7.0", features = ["serde", "v4"] }
async-trait = "0.1.77"
captcha = "0.0.9"
anyhow = { version = "1.0.79", features = [
anyhow = { version = "1.0.81", features = [
"backtrace",
] } # backtrace is on by default on nightly, but not stable rust
diesel_ltree = "0.3.1"
typed-builder = "0.18.1"
serial_test = "2.0.0"
tokio = { version = "1.35.1", features = ["full"] }
tokio = { version = "1.36.0", features = ["full"] }
regex = "1.10.3"
once_cell = "1.19.0"
diesel-derive-newtype = "2.1.0"
diesel-derive-enum = { version = "2.1.0", features = ["postgres"] }
strum = "0.25.0"
strum_macros = "0.25.3"
itertools = "0.12.0"
itertools = "0.12.1"
futures = "0.3.30"
http = "0.2.11"
http = "0.2.12"
rosetta-i18n = "0.1.3"
opentelemetry = { version = "0.19.0", features = ["rt-tokio"] }
tracing-opentelemetry = { version = "0.19.0" }
@ -160,9 +162,9 @@ tokio-postgres = "0.7.10"
tokio-postgres-rustls = "0.10.0"
urlencoding = "2.1.3"
enum-map = "2.7"
moka = { version = "0.12.4", features = ["future"] }
moka = { version = "0.12.5", features = ["future"] }
i-love-jesus = { version = "0.1.0" }
clap = { version = "4.4.18", features = ["derive"] }
clap = { version = "4.5.2", features = ["derive"] }
pretty_assertions = "1.4.0"
diesel-bind-if-some = { path = "../diesel-utils/crates/diesel-bind-if-some" }
@ -194,7 +196,7 @@ tracing-opentelemetry = { workspace = true, optional = true }
opentelemetry = { workspace = true, optional = true }
console-subscriber = { version = "0.1.10", optional = true }
opentelemetry-otlp = { version = "0.12.0", optional = true }
pict-rs = { version = "0.5.1", optional = true }
pict-rs = { version = "0.5.9", optional = true }
tokio.workspace = true
actix-cors = "0.6.5"
futures-util = { workspace = true }

View file

@ -20,16 +20,16 @@
},
"devDependencies": {
"@types/jest": "^29.5.12",
"@types/node": "^20.11.22",
"@typescript-eslint/eslint-plugin": "^7.1.0",
"@typescript-eslint/parser": "^7.1.0",
"@types/node": "^20.11.27",
"@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0",
"download-file-sync": "^1.0.4",
"eslint": "^8.57.0",
"eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-prettier": "^5.1.3",
"jest": "^29.5.0",
"lemmy-js-client": "0.19.4-alpha.8",
"lemmy-js-client": "0.19.4-alpha.13",
"prettier": "^3.2.5",
"ts-jest": "^29.1.0",
"typescript": "^5.3.3"
"typescript": "^5.4.2"
}
}

View file

@ -9,14 +9,14 @@ devDependencies:
specifier: ^29.5.12
version: 29.5.12
'@types/node':
specifier: ^20.11.22
version: 20.11.22
specifier: ^20.11.27
version: 20.11.27
'@typescript-eslint/eslint-plugin':
specifier: ^7.1.0
version: 7.1.0(@typescript-eslint/parser@7.1.0)(eslint@8.57.0)(typescript@5.3.3)
specifier: ^7.2.0
version: 7.2.0(@typescript-eslint/parser@7.2.0)(eslint@8.57.0)(typescript@5.4.2)
'@typescript-eslint/parser':
specifier: ^7.1.0
version: 7.1.0(eslint@8.57.0)(typescript@5.3.3)
specifier: ^7.2.0
version: 7.2.0(eslint@8.57.0)(typescript@5.4.2)
download-file-sync:
specifier: ^1.0.4
version: 1.0.4
@ -24,23 +24,23 @@ devDependencies:
specifier: ^8.57.0
version: 8.57.0
eslint-plugin-prettier:
specifier: ^5.0.1
specifier: ^5.1.3
version: 5.1.3(eslint@8.57.0)(prettier@3.2.5)
jest:
specifier: ^29.5.0
version: 29.7.0(@types/node@20.11.22)
version: 29.7.0(@types/node@20.11.27)
lemmy-js-client:
specifier: 0.19.4-alpha.8
version: 0.19.4-alpha.8
specifier: 0.19.4-alpha.13
version: 0.19.4-alpha.13
prettier:
specifier: ^3.2.5
version: 3.2.5
ts-jest:
specifier: ^29.1.0
version: 29.1.2(@babel/core@7.23.9)(jest@29.7.0)(typescript@5.3.3)
version: 29.1.2(@babel/core@7.23.9)(jest@29.7.0)(typescript@5.4.2)
typescript:
specifier: ^5.3.3
version: 5.3.3
specifier: ^5.4.2
version: 5.4.2
packages:
@ -464,7 +464,7 @@ packages:
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
'@jest/types': 29.6.3
'@types/node': 20.11.22
'@types/node': 20.11.27
chalk: 4.1.2
jest-message-util: 29.7.0
jest-util: 29.7.0
@ -485,14 +485,14 @@ packages:
'@jest/test-result': 29.7.0
'@jest/transform': 29.7.0
'@jest/types': 29.6.3
'@types/node': 20.11.22
'@types/node': 20.11.27
ansi-escapes: 4.3.2
chalk: 4.1.2
ci-info: 3.9.0
exit: 0.1.2
graceful-fs: 4.2.11
jest-changed-files: 29.7.0
jest-config: 29.7.0(@types/node@20.11.22)
jest-config: 29.7.0(@types/node@20.11.27)
jest-haste-map: 29.7.0
jest-message-util: 29.7.0
jest-regex-util: 29.6.3
@ -520,7 +520,7 @@ packages:
dependencies:
'@jest/fake-timers': 29.7.0
'@jest/types': 29.6.3
'@types/node': 20.11.22
'@types/node': 20.11.27
jest-mock: 29.7.0
dev: true
@ -547,7 +547,7 @@ packages:
dependencies:
'@jest/types': 29.6.3
'@sinonjs/fake-timers': 10.3.0
'@types/node': 20.11.22
'@types/node': 20.11.27
jest-message-util: 29.7.0
jest-mock: 29.7.0
jest-util: 29.7.0
@ -580,7 +580,7 @@ packages:
'@jest/transform': 29.7.0
'@jest/types': 29.6.3
'@jridgewell/trace-mapping': 0.3.22
'@types/node': 20.11.22
'@types/node': 20.11.27
chalk: 4.1.2
collect-v8-coverage: 1.0.2
exit: 0.1.2
@ -668,7 +668,7 @@ packages:
'@jest/schemas': 29.6.3
'@types/istanbul-lib-coverage': 2.0.6
'@types/istanbul-reports': 3.0.4
'@types/node': 20.11.22
'@types/node': 20.11.27
'@types/yargs': 17.0.32
chalk: 4.1.2
dev: true
@ -777,7 +777,7 @@ packages:
/@types/graceful-fs@4.1.9:
resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==}
dependencies:
'@types/node': 20.11.22
'@types/node': 20.11.27
dev: true
/@types/istanbul-lib-coverage@2.0.6:
@ -807,8 +807,8 @@ packages:
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
dev: true
/@types/node@20.11.22:
resolution: {integrity: sha512-/G+IxWxma6V3E+pqK1tSl2Fo1kl41pK1yeCyDsgkF9WlVAme4j5ISYM2zR11bgLFJGLN5sVK40T4RJNuiZbEjA==}
/@types/node@20.11.27:
resolution: {integrity: sha512-qyUZfMnCg1KEz57r7pzFtSGt49f6RPkPBis3Vo4PbS7roQEDn22hiHzl/Lo1q4i4hDEgBJmBF/NTNg2XR0HbFg==}
dependencies:
undici-types: 5.26.5
dev: true
@ -831,8 +831,8 @@ packages:
'@types/yargs-parser': 21.0.3
dev: true
/@typescript-eslint/eslint-plugin@7.1.0(@typescript-eslint/parser@7.1.0)(eslint@8.57.0)(typescript@5.3.3):
resolution: {integrity: sha512-j6vT/kCulhG5wBmGtstKeiVr1rdXE4nk+DT1k6trYkwlrvW9eOF5ZbgKnd/YR6PcM4uTEXa0h6Fcvf6X7Dxl0w==}
/@typescript-eslint/eslint-plugin@7.2.0(@typescript-eslint/parser@7.2.0)(eslint@8.57.0)(typescript@5.4.2):
resolution: {integrity: sha512-mdekAHOqS9UjlmyF/LSs6AIEvfceV749GFxoBAjwAv0nkevfKHWQFDMcBZWUiIC5ft6ePWivXoS36aKQ0Cy3sw==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
'@typescript-eslint/parser': ^7.0.0
@ -843,25 +843,25 @@ packages:
optional: true
dependencies:
'@eslint-community/regexpp': 4.10.0
'@typescript-eslint/parser': 7.1.0(eslint@8.57.0)(typescript@5.3.3)
'@typescript-eslint/scope-manager': 7.1.0
'@typescript-eslint/type-utils': 7.1.0(eslint@8.57.0)(typescript@5.3.3)
'@typescript-eslint/utils': 7.1.0(eslint@8.57.0)(typescript@5.3.3)
'@typescript-eslint/visitor-keys': 7.1.0
'@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.4.2)
'@typescript-eslint/scope-manager': 7.2.0
'@typescript-eslint/type-utils': 7.2.0(eslint@8.57.0)(typescript@5.4.2)
'@typescript-eslint/utils': 7.2.0(eslint@8.57.0)(typescript@5.4.2)
'@typescript-eslint/visitor-keys': 7.2.0
debug: 4.3.4
eslint: 8.57.0
graphemer: 1.4.0
ignore: 5.3.1
natural-compare: 1.4.0
semver: 7.6.0
ts-api-utils: 1.2.1(typescript@5.3.3)
typescript: 5.3.3
ts-api-utils: 1.3.0(typescript@5.4.2)
typescript: 5.4.2
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/parser@7.1.0(eslint@8.57.0)(typescript@5.3.3):
resolution: {integrity: sha512-V1EknKUubZ1gWFjiOZhDSNToOjs63/9O0puCgGS8aDOgpZY326fzFu15QAUjwaXzRZjf/qdsdBrckYdv9YxB8w==}
/@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.2):
resolution: {integrity: sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
eslint: ^8.56.0
@ -870,27 +870,27 @@ packages:
typescript:
optional: true
dependencies:
'@typescript-eslint/scope-manager': 7.1.0
'@typescript-eslint/types': 7.1.0
'@typescript-eslint/typescript-estree': 7.1.0(typescript@5.3.3)
'@typescript-eslint/visitor-keys': 7.1.0
'@typescript-eslint/scope-manager': 7.2.0
'@typescript-eslint/types': 7.2.0
'@typescript-eslint/typescript-estree': 7.2.0(typescript@5.4.2)
'@typescript-eslint/visitor-keys': 7.2.0
debug: 4.3.4
eslint: 8.57.0
typescript: 5.3.3
typescript: 5.4.2
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/scope-manager@7.1.0:
resolution: {integrity: sha512-6TmN4OJiohHfoOdGZ3huuLhpiUgOGTpgXNUPJgeZOZR3DnIpdSgtt83RS35OYNNXxM4TScVlpVKC9jyQSETR1A==}
/@typescript-eslint/scope-manager@7.2.0:
resolution: {integrity: sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==}
engines: {node: ^16.0.0 || >=18.0.0}
dependencies:
'@typescript-eslint/types': 7.1.0
'@typescript-eslint/visitor-keys': 7.1.0
'@typescript-eslint/types': 7.2.0
'@typescript-eslint/visitor-keys': 7.2.0
dev: true
/@typescript-eslint/type-utils@7.1.0(eslint@8.57.0)(typescript@5.3.3):
resolution: {integrity: sha512-UZIhv8G+5b5skkcuhgvxYWHjk7FW7/JP5lPASMEUoliAPwIH/rxoUSQPia2cuOj9AmDZmwUl1usKm85t5VUMew==}
/@typescript-eslint/type-utils@7.2.0(eslint@8.57.0)(typescript@5.4.2):
resolution: {integrity: sha512-xHi51adBHo9O9330J8GQYQwrKBqbIPJGZZVQTHHmy200hvkLZFWJIFtAG/7IYTWUyun6DE6w5InDReePJYJlJA==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
eslint: ^8.56.0
@ -899,23 +899,23 @@ packages:
typescript:
optional: true
dependencies:
'@typescript-eslint/typescript-estree': 7.1.0(typescript@5.3.3)
'@typescript-eslint/utils': 7.1.0(eslint@8.57.0)(typescript@5.3.3)
'@typescript-eslint/typescript-estree': 7.2.0(typescript@5.4.2)
'@typescript-eslint/utils': 7.2.0(eslint@8.57.0)(typescript@5.4.2)
debug: 4.3.4
eslint: 8.57.0
ts-api-utils: 1.2.1(typescript@5.3.3)
typescript: 5.3.3
ts-api-utils: 1.3.0(typescript@5.4.2)
typescript: 5.4.2
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/types@7.1.0:
resolution: {integrity: sha512-qTWjWieJ1tRJkxgZYXx6WUYtWlBc48YRxgY2JN1aGeVpkhmnopq+SUC8UEVGNXIvWH7XyuTjwALfG6bFEgCkQA==}
/@typescript-eslint/types@7.2.0:
resolution: {integrity: sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==}
engines: {node: ^16.0.0 || >=18.0.0}
dev: true
/@typescript-eslint/typescript-estree@7.1.0(typescript@5.3.3):
resolution: {integrity: sha512-k7MyrbD6E463CBbSpcOnwa8oXRdHzH1WiVzOipK3L5KSML92ZKgUBrTlehdi7PEIMT8k0bQixHUGXggPAlKnOQ==}
/@typescript-eslint/typescript-estree@7.2.0(typescript@5.4.2):
resolution: {integrity: sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
typescript: '*'
@ -923,21 +923,21 @@ packages:
typescript:
optional: true
dependencies:
'@typescript-eslint/types': 7.1.0
'@typescript-eslint/visitor-keys': 7.1.0
'@typescript-eslint/types': 7.2.0
'@typescript-eslint/visitor-keys': 7.2.0
debug: 4.3.4
globby: 11.1.0
is-glob: 4.0.3
minimatch: 9.0.3
semver: 7.6.0
ts-api-utils: 1.2.1(typescript@5.3.3)
typescript: 5.3.3
ts-api-utils: 1.3.0(typescript@5.4.2)
typescript: 5.4.2
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/utils@7.1.0(eslint@8.57.0)(typescript@5.3.3):
resolution: {integrity: sha512-WUFba6PZC5OCGEmbweGpnNJytJiLG7ZvDBJJoUcX4qZYf1mGZ97mO2Mps6O2efxJcJdRNpqweCistDbZMwIVHw==}
/@typescript-eslint/utils@7.2.0(eslint@8.57.0)(typescript@5.4.2):
resolution: {integrity: sha512-YfHpnMAGb1Eekpm3XRK8hcMwGLGsnT6L+7b2XyRv6ouDuJU1tZir1GS2i0+VXRatMwSI1/UfcyPe53ADkU+IuA==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
eslint: ^8.56.0
@ -945,9 +945,9 @@ packages:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
'@types/json-schema': 7.0.15
'@types/semver': 7.5.8
'@typescript-eslint/scope-manager': 7.1.0
'@typescript-eslint/types': 7.1.0
'@typescript-eslint/typescript-estree': 7.1.0(typescript@5.3.3)
'@typescript-eslint/scope-manager': 7.2.0
'@typescript-eslint/types': 7.2.0
'@typescript-eslint/typescript-estree': 7.2.0(typescript@5.4.2)
eslint: 8.57.0
semver: 7.6.0
transitivePeerDependencies:
@ -955,11 +955,11 @@ packages:
- typescript
dev: true
/@typescript-eslint/visitor-keys@7.1.0:
resolution: {integrity: sha512-FhUqNWluiGNzlvnDZiXad4mZRhtghdoKW6e98GoEOYSu5cND+E39rG5KwJMUzeENwm1ztYBRqof8wMLP+wNPIA==}
/@typescript-eslint/visitor-keys@7.2.0:
resolution: {integrity: sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==}
engines: {node: ^16.0.0 || >=18.0.0}
dependencies:
'@typescript-eslint/types': 7.1.0
'@typescript-eslint/types': 7.2.0
eslint-visitor-keys: 3.4.3
dev: true
@ -1044,10 +1044,6 @@ packages:
engines: {node: '>=8'}
dev: true
/asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
dev: true
/babel-jest@29.7.0(@babel/core@7.23.9):
resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@ -1261,13 +1257,6 @@ packages:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
dev: true
/combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
dependencies:
delayed-stream: 1.0.0
dev: true
/concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
dev: true
@ -1276,7 +1265,7 @@ packages:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
dev: true
/create-jest@29.7.0(@types/node@20.11.22):
/create-jest@29.7.0(@types/node@20.11.27):
resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
hasBin: true
@ -1285,7 +1274,7 @@ packages:
chalk: 4.1.2
exit: 0.1.2
graceful-fs: 4.2.11
jest-config: 29.7.0(@types/node@20.11.22)
jest-config: 29.7.0(@types/node@20.11.27)
jest-util: 29.7.0
prompts: 2.4.2
transitivePeerDependencies:
@ -1295,14 +1284,6 @@ packages:
- ts-node
dev: true
/cross-fetch@4.0.0:
resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==}
dependencies:
node-fetch: 2.7.0
transitivePeerDependencies:
- encoding
dev: true
/cross-spawn@7.0.3:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
engines: {node: '>= 8'}
@ -1342,11 +1323,6 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
dev: true
/detect-newline@3.1.0:
resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==}
engines: {node: '>=8'}
@ -1646,15 +1622,6 @@ packages:
resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==}
dev: true
/form-data@4.0.0:
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
engines: {node: '>= 6'}
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
mime-types: 2.1.35
dev: true
/fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
dev: true
@ -1939,7 +1906,7 @@ packages:
'@jest/expect': 29.7.0
'@jest/test-result': 29.7.0
'@jest/types': 29.6.3
'@types/node': 20.11.22
'@types/node': 20.11.27
chalk: 4.1.2
co: 4.6.0
dedent: 1.5.1
@ -1960,7 +1927,7 @@ packages:
- supports-color
dev: true
/jest-cli@29.7.0(@types/node@20.11.22):
/jest-cli@29.7.0(@types/node@20.11.27):
resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
hasBin: true
@ -1974,10 +1941,10 @@ packages:
'@jest/test-result': 29.7.0
'@jest/types': 29.6.3
chalk: 4.1.2
create-jest: 29.7.0(@types/node@20.11.22)
create-jest: 29.7.0(@types/node@20.11.27)
exit: 0.1.2
import-local: 3.1.0
jest-config: 29.7.0(@types/node@20.11.22)
jest-config: 29.7.0(@types/node@20.11.27)
jest-util: 29.7.0
jest-validate: 29.7.0
yargs: 17.7.2
@ -1988,7 +1955,7 @@ packages:
- ts-node
dev: true
/jest-config@29.7.0(@types/node@20.11.22):
/jest-config@29.7.0(@types/node@20.11.27):
resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
peerDependencies:
@ -2003,7 +1970,7 @@ packages:
'@babel/core': 7.23.9
'@jest/test-sequencer': 29.7.0
'@jest/types': 29.6.3
'@types/node': 20.11.22
'@types/node': 20.11.27
babel-jest: 29.7.0(@babel/core@7.23.9)
chalk: 4.1.2
ci-info: 3.9.0
@ -2063,7 +2030,7 @@ packages:
'@jest/environment': 29.7.0
'@jest/fake-timers': 29.7.0
'@jest/types': 29.6.3
'@types/node': 20.11.22
'@types/node': 20.11.27
jest-mock: 29.7.0
jest-util: 29.7.0
dev: true
@ -2079,7 +2046,7 @@ packages:
dependencies:
'@jest/types': 29.6.3
'@types/graceful-fs': 4.1.9
'@types/node': 20.11.22
'@types/node': 20.11.27
anymatch: 3.1.3
fb-watchman: 2.0.2
graceful-fs: 4.2.11
@ -2130,7 +2097,7 @@ packages:
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
'@jest/types': 29.6.3
'@types/node': 20.11.22
'@types/node': 20.11.27
jest-util: 29.7.0
dev: true
@ -2185,7 +2152,7 @@ packages:
'@jest/test-result': 29.7.0
'@jest/transform': 29.7.0
'@jest/types': 29.6.3
'@types/node': 20.11.22
'@types/node': 20.11.27
chalk: 4.1.2
emittery: 0.13.1
graceful-fs: 4.2.11
@ -2216,7 +2183,7 @@ packages:
'@jest/test-result': 29.7.0
'@jest/transform': 29.7.0
'@jest/types': 29.6.3
'@types/node': 20.11.22
'@types/node': 20.11.27
chalk: 4.1.2
cjs-module-lexer: 1.2.3
collect-v8-coverage: 1.0.2
@ -2268,7 +2235,7 @@ packages:
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
'@jest/types': 29.6.3
'@types/node': 20.11.22
'@types/node': 20.11.27
chalk: 4.1.2
ci-info: 3.9.0
graceful-fs: 4.2.11
@ -2293,7 +2260,7 @@ packages:
dependencies:
'@jest/test-result': 29.7.0
'@jest/types': 29.6.3
'@types/node': 20.11.22
'@types/node': 20.11.27
ansi-escapes: 4.3.2
chalk: 4.1.2
emittery: 0.13.1
@ -2305,13 +2272,13 @@ packages:
resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
'@types/node': 20.11.22
'@types/node': 20.11.27
jest-util: 29.7.0
merge-stream: 2.0.0
supports-color: 8.1.1
dev: true
/jest@29.7.0(@types/node@20.11.22):
/jest@29.7.0(@types/node@20.11.27):
resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
hasBin: true
@ -2324,7 +2291,7 @@ packages:
'@jest/core': 29.7.0
'@jest/types': 29.6.3
import-local: 3.1.0
jest-cli: 29.7.0(@types/node@20.11.22)
jest-cli: 29.7.0(@types/node@20.11.27)
transitivePeerDependencies:
- '@types/node'
- babel-plugin-macros
@ -2390,13 +2357,8 @@ packages:
engines: {node: '>=6'}
dev: true
/lemmy-js-client@0.19.4-alpha.8:
resolution: {integrity: sha512-8vjqUYVOhyUTcmG9FvPLjrWziVwNa2/Zi+kSflTrajJsK0V+5DclJ5dhdVMUQ4DEA70gb0OuNMDlipPG2FoS5A==}
dependencies:
cross-fetch: 4.0.0
form-data: 4.0.0
transitivePeerDependencies:
- encoding
/lemmy-js-client@0.19.4-alpha.13:
resolution: {integrity: sha512-ru1dCqPSfOJdsGq7am5J7P7f+/hpyHGhNbCEV/JAZP2U1lGHul32gLpBkilDnStDNdeq52scjKx+3WskRJFGFA==}
dev: true
/leven@3.1.0:
@ -2485,18 +2447,6 @@ packages:
picomatch: 2.3.1
dev: true
/mime-db@1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
dev: true
/mime-types@2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
dependencies:
mime-db: 1.52.0
dev: true
/mimic-fn@2.1.0:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'}
@ -2523,18 +2473,6 @@ packages:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
dev: true
/node-fetch@2.7.0:
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
engines: {node: 4.x || >=6.0.0}
peerDependencies:
encoding: ^0.1.0
peerDependenciesMeta:
encoding:
optional: true
dependencies:
whatwg-url: 5.0.0
dev: true
/node-int64@0.4.0:
resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==}
dev: true
@ -2952,20 +2890,16 @@ packages:
is-number: 7.0.0
dev: true
/tr46@0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
dev: true
/ts-api-utils@1.2.1(typescript@5.3.3):
resolution: {integrity: sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA==}
/ts-api-utils@1.3.0(typescript@5.4.2):
resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==}
engines: {node: '>=16'}
peerDependencies:
typescript: '>=4.2.0'
dependencies:
typescript: 5.3.3
typescript: 5.4.2
dev: true
/ts-jest@29.1.2(@babel/core@7.23.9)(jest@29.7.0)(typescript@5.3.3):
/ts-jest@29.1.2(@babel/core@7.23.9)(jest@29.7.0)(typescript@5.4.2):
resolution: {integrity: sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==}
engines: {node: ^16.10.0 || ^18.0.0 || >=20.0.0}
hasBin: true
@ -2989,13 +2923,13 @@ packages:
'@babel/core': 7.23.9
bs-logger: 0.2.6
fast-json-stable-stringify: 2.1.0
jest: 29.7.0(@types/node@20.11.22)
jest: 29.7.0(@types/node@20.11.27)
jest-util: 29.7.0
json5: 2.2.3
lodash.memoize: 4.1.2
make-error: 1.3.6
semver: 7.5.4
typescript: 5.3.3
typescript: 5.4.2
yargs-parser: 21.1.1
dev: true
@ -3025,8 +2959,8 @@ packages:
engines: {node: '>=10'}
dev: true
/typescript@5.3.3:
resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==}
/typescript@5.4.2:
resolution: {integrity: sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==}
engines: {node: '>=14.17'}
hasBin: true
dev: true
@ -3067,17 +3001,6 @@ packages:
makeerror: 1.0.12
dev: true
/webidl-conversions@3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
dev: true
/whatwg-url@5.0.0:
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
dependencies:
tr46: 0.0.3
webidl-conversions: 3.0.1
dev: true
/which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'}

View file

@ -5,18 +5,18 @@ import {
setupLogins,
resolveBetaCommunity,
followCommunity,
unfollowRemotes,
getSite,
waitUntil,
beta,
betaUrl,
registerUser,
unfollows,
} from "./shared";
beforeAll(setupLogins);
afterAll(() => {
unfollowRemotes(alpha);
unfollows();
});
test("Follow local community", async () => {

View file

@ -14,25 +14,33 @@ import {
betaUrl,
createCommunity,
createPost,
deleteAllImages,
delta,
epsilon,
followCommunity,
gamma,
getSite,
imageFetchLimit,
registerUser,
resolveBetaCommunity,
resolveCommunity,
resolvePost,
setupLogins,
unfollowRemotes,
waitForPost,
unfollows,
} from "./shared";
const downloadFileSync = require("download-file-sync");
beforeAll(setupLogins);
afterAll(() => {
unfollowRemotes(alphaImage);
unfollows();
});
test("Upload image and delete it", async () => {
// Before running this test, you need to delete all previous images in the DB
await deleteAllImages(alpha);
// Upload test image. We use a simple string buffer as pictrs doesnt require an actual image
// in testing mode.
const upload_form: UploadImage = {
@ -48,6 +56,24 @@ test("Upload image and delete it", async () => {
const content = downloadFileSync(upload.url);
expect(content.length).toBeGreaterThan(0);
// Ensure that it comes back with the list_media endpoint
const listMediaRes = await alphaImage.listMedia();
expect(listMediaRes.images.length).toBe(1);
// Ensure that it also comes back with the admin all images
const listAllMediaRes = await alphaImage.listAllMedia({
limit: imageFetchLimit,
});
// This number comes from all the previous thumbnails fetched in other tests.
const previousThumbnails = 1;
expect(listAllMediaRes.images.length).toBe(previousThumbnails);
// The deleteUrl is a combination of the endpoint, delete token, and alias
let firstImage = listMediaRes.images[0];
let deleteUrl = `${alphaUrl}/pictrs/image/delete/${firstImage.pictrs_delete_token}/${firstImage.pictrs_alias}`;
expect(deleteUrl).toBe(upload.delete_url);
// delete image
const delete_form: DeleteImage = {
token: upload.files![0].delete_token,
@ -59,6 +85,16 @@ test("Upload image and delete it", async () => {
// ensure that image is deleted
const content2 = downloadFileSync(upload.url);
expect(content2).toBe("");
// Ensure that it shows the image is deleted
const deletedListMediaRes = await alphaImage.listMedia();
expect(deletedListMediaRes.images.length).toBe(0);
// Ensure that the admin shows its deleted
const deletedListAllMediaRes = await alphaImage.listAllMedia({
limit: imageFetchLimit,
});
expect(deletedListAllMediaRes.images.length).toBe(previousThumbnails - 1);
});
test("Purge user, uploaded image removed", async () => {
@ -80,10 +116,10 @@ test("Purge user, uploaded image removed", async () => {
// purge user
let site = await getSite(user);
const purge_form: PurgePerson = {
const purgeForm: PurgePerson = {
person_id: site.my_user!.local_user_view.person.id,
};
const delete_ = await alphaImage.purgePerson(purge_form);
const delete_ = await alphaImage.purgePerson(purgeForm);
expect(delete_.success).toBe(true);
// ensure that image is deleted
@ -117,10 +153,11 @@ test("Purge post, linked image removed", async () => {
expect(post.post_view.post.url).toBe(upload.url);
// purge post
const purge_form: PurgePost = {
const purgeForm: PurgePost = {
post_id: post.post_view.post.id,
};
const delete_ = await beta.purgePost(purge_form);
const delete_ = await beta.purgePost(purgeForm);
expect(delete_.success).toBe(true);
// ensure that image is deleted
@ -175,6 +212,11 @@ test("Images in remote post are proxied if setting enabled", async () => {
test("No image proxying if setting is disabled", async () => {
let user = await registerUser(beta, betaUrl);
let community = await createCommunity(alpha);
let betaCommunity = await resolveCommunity(
beta,
community.community_view.community.actor_id,
);
await followCommunity(beta, true, betaCommunity.community!.community.id);
const upload_form: UploadImage = {
image: Buffer.from("test"),
@ -194,15 +236,19 @@ test("No image proxying if setting is disabled", async () => {
).toBeTruthy();
expect(post.post_view.post.body).toBe("![](http://example.com/image2.png)");
let gammaPost = await resolvePost(delta, post.post_view.post);
expect(gammaPost.post).toBeDefined();
let betaPost = await waitForPost(
beta,
post.post_view.post,
res => res?.post.alt_text != null,
);
expect(betaPost.post).toBeDefined();
// remote image doesnt get proxied after federation
expect(
gammaPost.post!.post.url?.startsWith("http://127.0.0.1:8551/pictrs/image/"),
betaPost.post.url?.startsWith("http://127.0.0.1:8551/pictrs/image/"),
).toBeTruthy();
expect(gammaPost.post!.post.body).toBe("![](http://example.com/image2.png)");
expect(betaPost.post.body).toBe("![](http://example.com/image2.png)");
// Make sure the alt text got federated
expect(post.post_view.post.alt_text).toBe(gammaPost.post!.post.alt_text);
expect(post.post_view.post.alt_text).toBe(betaPost.post.alt_text);
});

View file

@ -55,7 +55,18 @@ afterAll(() => {
unfollows();
});
function assertPostFederation(postOne?: PostView, postTwo?: PostView) {
async function assertPostFederation(postOne: PostView, postTwo: PostView) {
// Link metadata is generated in background task and may not be ready yet at this time,
// so wait for it explicitly. For removed posts we cant refetch anything.
postOne = await waitForPost(beta, postOne.post, res => {
return res === null || res?.post.embed_title !== null;
});
postTwo = await waitForPost(
beta,
postTwo.post,
res => res === null || res?.post.embed_title !== null,
);
expect(postOne?.post.ap_id).toBe(postTwo?.post.ap_id);
expect(postOne?.post.name).toBe(postTwo?.post.name);
expect(postOne?.post.body).toBe(postTwo?.post.body);
@ -109,7 +120,7 @@ test("Create a post", async () => {
expect(betaPost?.community.local).toBe(true);
expect(betaPost?.creator.local).toBe(false);
expect(betaPost?.counts.score).toBe(1);
assertPostFederation(betaPost, postRes.post_view);
await assertPostFederation(betaPost, postRes.post_view);
// Delta only follows beta, so it should not see an alpha ap_id
await expect(
@ -157,7 +168,7 @@ test("Unlike a post", async () => {
expect(betaPost?.community.local).toBe(true);
expect(betaPost?.creator.local).toBe(false);
expect(betaPost?.counts.score).toBe(0);
assertPostFederation(betaPost, postRes.post_view);
await assertPostFederation(betaPost, postRes.post_view);
});
test("Update a post", async () => {
@ -178,7 +189,7 @@ test("Update a post", async () => {
expect(betaPost.community.local).toBe(true);
expect(betaPost.creator.local).toBe(false);
expect(betaPost.post.name).toBe(updatedName);
assertPostFederation(betaPost, updatedPost.post_view);
await assertPostFederation(betaPost, updatedPost.post_view);
// Make sure lemmy beta cannot update the post
await expect(editPost(beta, betaPost.post)).rejects.toStrictEqual(
@ -220,9 +231,10 @@ test("Sticky a post", async () => {
if (!gammaPost) {
throw "Missing gamma post";
}
let gammaTrySticky = await featurePost(gamma, true, gammaPost.post);
// This has been failing occasionally
await featurePost(gamma, true, gammaPost.post);
let betaPost3 = (await resolvePost(beta, postRes.post_view.post)).post;
expect(gammaTrySticky.post_view.post.featured_community).toBe(true);
// expect(gammaTrySticky.post_view.post.featured_community).toBe(true);
expect(betaPost3?.post.featured_community).toBe(false);
});
@ -328,7 +340,7 @@ test("Delete a post", async () => {
throw "Missing beta post 2";
}
expect(betaPost2.post.deleted).toBe(false);
assertPostFederation(betaPost2, undeletedPost.post_view);
await assertPostFederation(betaPost2, undeletedPost.post_view);
// Make sure lemmy beta cannot delete the post
await expect(deletePost(beta, true, betaPost2.post)).rejects.toStrictEqual(
@ -371,7 +383,7 @@ test("Remove a post from admin and community on different instance", async () =>
// Make sure lemmy beta sees post is undeleted
let betaPost2 = (await resolvePost(beta, postRes.post_view.post)).post;
expect(betaPost2?.post.removed).toBe(false);
assertPostFederation(betaPost2, undeletedPost.post_view);
await assertPostFederation(betaPost2!, undeletedPost.post_view);
});
test("Remove a post from admin and community on same instance", async () => {
@ -402,7 +414,7 @@ test("Remove a post from admin and community on same instance", async () => {
p => p?.post_view.post.removed ?? false,
);
expect(alphaPost?.post_view.post.removed).toBe(true);
assertPostFederation(alphaPost.post_view, removePostRes.post_view);
await assertPostFederation(alphaPost.post_view, removePostRes.post_view);
// Undelete
let undeletedPost = await removePost(beta, false, betaPost.post);
@ -415,7 +427,7 @@ test("Remove a post from admin and community on same instance", async () => {
p => !!p && !p.post.removed,
);
expect(alphaPost2.post.removed).toBe(false);
assertPostFederation(alphaPost2, undeletedPost.post_view);
await assertPostFederation(alphaPost2, undeletedPost.post_view);
await unfollowRemotes(alpha);
});

View file

@ -8,9 +8,9 @@ import {
editPrivateMessage,
listPrivateMessages,
deletePrivateMessage,
unfollowRemotes,
waitUntil,
reportPrivateMessage,
unfollows,
} from "./shared";
let recipient_id: number;
@ -22,7 +22,7 @@ beforeAll(async () => {
});
afterAll(() => {
unfollowRemotes(alpha);
unfollows();
});
test("Create a private message", async () => {

View file

@ -5,6 +5,7 @@ import {
BlockInstanceResponse,
CommunityId,
CreatePrivateMessageReport,
DeleteImage,
EditCommunity,
GetReplies,
GetRepliesResponse,
@ -79,6 +80,7 @@ import { GetPersonDetails } from "lemmy-js-client/dist/types/GetPersonDetails";
import { ListingType } from "lemmy-js-client/dist/types/ListingType";
export const fetchFunction = fetch;
export const imageFetchLimit = 50;
export let alphaUrl = "http://127.0.0.1:8541";
export let betaUrl = "http://127.0.0.1:8551";
@ -865,9 +867,25 @@ export function randomString(length: number): string {
return result;
}
export async function deleteAllImages(api: LemmyHttp) {
const imagesRes = await api.listAllMedia({
limit: imageFetchLimit,
});
imagesRes.images;
for (const image of imagesRes.images) {
const form: DeleteImage = {
token: image.pictrs_delete_token,
filename: image.pictrs_alias,
};
await api.deleteImage(form);
}
}
export async function unfollows() {
await Promise.all([
unfollowRemotes(alpha),
unfollowRemotes(beta),
unfollowRemotes(gamma),
unfollowRemotes(delta),
unfollowRemotes(epsilon),

View file

@ -19,8 +19,9 @@ import {
getPost,
getComments,
fetchFunction,
alphaImage,
} from "./shared";
import { LemmyHttp, SaveUserSettings } from "lemmy-js-client";
import { LemmyHttp, SaveUserSettings, UploadImage } from "lemmy-js-client";
import { GetPosts } from "lemmy-js-client/dist/types/GetPosts";
beforeAll(setupLogins);
@ -139,3 +140,54 @@ test("Create user with Arabic name", async () => {
let alphaPerson = (await resolvePerson(alpha, apShortname)).person;
expect(alphaPerson).toBeDefined();
});
test("Create user with accept-language", async () => {
let lemmy_http = new LemmyHttp(alphaUrl, {
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language#syntax
headers: { "Accept-Language": "fr-CH, en;q=0.8, de;q=0.7, *;q=0.5" },
});
let user = await registerUser(lemmy_http, alphaUrl);
let site = await getSite(user);
expect(site.my_user).toBeDefined();
expect(site.my_user?.local_user_view.local_user.interface_language).toBe(
"fr",
);
let langs = site.all_languages
.filter(a => site.my_user?.discussion_languages.includes(a.id))
.map(l => l.code);
// should have languages from accept header, as well as "undetermined"
// which is automatically enabled by backend
expect(langs).toStrictEqual(["und", "de", "en", "fr"]);
});
test("Set a new avatar, old avatar is deleted", async () => {
const listMediaRes = await alphaImage.listMedia();
expect(listMediaRes.images.length).toBe(0);
const upload_form1: UploadImage = {
image: Buffer.from("test1"),
};
const upload1 = await alphaImage.uploadImage(upload_form1);
expect(upload1.url).toBeDefined();
let form1 = {
avatar: upload1.url,
};
await saveUserSettings(alpha, form1);
const listMediaRes1 = await alphaImage.listMedia();
expect(listMediaRes1.images.length).toBe(1);
const upload_form2: UploadImage = {
image: Buffer.from("test2"),
};
const upload2 = await alphaImage.uploadImage(upload_form2);
expect(upload2.url).toBeDefined();
let form2 = {
avatar: upload1.url,
};
await saveUserSettings(alpha, form2);
// make sure only the new avatar is kept
const listMediaRes2 = await alphaImage.listMedia();
expect(listMediaRes2.images.length).toBe(1);
});

View file

@ -34,7 +34,7 @@ tracing = { workspace = true }
chrono = { workspace = true }
url = { workspace = true }
wav = "1.0.0"
sitemap-rs = "0.2.0"
sitemap-rs = "0.2.1"
totp-rs = { version = "5.5.1", features = ["gen_secret", "otpauth"] }
actix-web-httpauth = "0.8.1"

View file

@ -259,9 +259,9 @@ pub async fn local_user_view_from_jwt(
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use super::*;

View file

@ -6,10 +6,7 @@ use lemmy_api_common::{
person::GenerateTotpSecretResponse,
sensitive::Sensitive,
};
use lemmy_db_schema::{
source::local_user::{LocalUser, LocalUserUpdateForm},
traits::Crud,
};
use lemmy_db_schema::source::local_user::{LocalUser, LocalUserUpdateForm};
use lemmy_db_views::structs::{LocalUserView, SiteView};
use lemmy_utils::error::{LemmyError, LemmyErrorType};

View file

@ -0,0 +1,26 @@
use actix_web::web::{Data, Json, Query};
use lemmy_api_common::{
context::LemmyContext,
person::{ListMedia, ListMediaResponse},
};
use lemmy_db_schema::source::images::LocalImage;
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyError;
#[tracing::instrument(skip(context))]
pub async fn list_media(
data: Query<ListMedia>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<ListMediaResponse>, LemmyError> {
let page = data.page;
let limit = data.limit;
let images = LocalImage::get_all_paged_by_local_user_id(
&mut context.pool(),
local_user_view.local_user.id,
page,
limit,
)
.await?;
Ok(Json(ListMediaResponse { images }))
}

View file

@ -10,6 +10,7 @@ pub mod generate_totp_secret;
pub mod get_captcha;
pub mod list_banned;
pub mod list_logins;
pub mod list_media;
pub mod login;
pub mod logout;
pub mod notifications;

View file

@ -1,7 +1,9 @@
use actix_web::web::{Data, Json};
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
context::LemmyContext,
person::SaveUserSettings,
request::replace_image,
utils::{
get_url_blocklist,
local_site_to_slur_regex,
@ -40,6 +42,8 @@ pub async fn save_user_settings(
let bio = diesel_option_overwrite(
process_markdown_opt(&data.bio, &slur_regex, &url_blocklist, &context).await?,
);
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 banner = proxy_image_link_opt_api(&data.banner, &context).await?;

View file

@ -4,10 +4,7 @@ use lemmy_api_common::{
context::LemmyContext,
person::{UpdateTotp, UpdateTotpResponse},
};
use lemmy_db_schema::{
source::local_user::{LocalUser, LocalUserUpdateForm},
traits::Crud,
};
use lemmy_db_schema::source::local_user::{LocalUser, LocalUserUpdateForm};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyError;

View file

@ -0,0 +1,24 @@
use actix_web::web::{Data, Json, Query};
use lemmy_api_common::{
context::LemmyContext,
person::{ListMedia, ListMediaResponse},
utils::is_admin,
};
use lemmy_db_schema::source::images::LocalImage;
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyError;
#[tracing::instrument(skip(context))]
pub async fn list_all_media(
data: Query<ListMedia>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<ListMediaResponse>, LemmyError> {
// Only let admins view all media
is_admin(&local_user_view)?;
let page = data.page;
let limit = data.limit;
let images = LocalImage::get_all(&mut context.pool(), page, limit).await?;
Ok(Json(ListMediaResponse { images }))
}

View file

@ -1,6 +1,7 @@
pub mod block;
pub mod federated_instances;
pub mod leave_admin;
pub mod list_all_media;
pub mod mod_log;
pub mod purge;
pub mod registration_applications;

View file

@ -3,15 +3,13 @@ use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
context::LemmyContext,
request::delete_image_from_pictrs,
send_activity::{ActivityChannel, SendActivityData},
site::PurgePerson,
utils::is_admin,
utils::{is_admin, purge_user_account},
SuccessResponse,
};
use lemmy_db_schema::{
source::{
images::LocalImage,
moderator::{AdminPurgePerson, AdminPurgePersonForm},
person::{Person, PersonUpdateForm},
},
@ -29,18 +27,6 @@ pub async fn purge_person(
// Only let admin purge an item
is_admin(&local_user_view)?;
// Read the local user to get their images, and delete them
if let Ok(local_user) = LocalUserView::read_person(&mut context.pool(), data.person_id).await {
let pictrs_uploads =
LocalImage::get_all_by_local_user_id(&mut context.pool(), &local_user.local_user.id).await?;
for upload in pictrs_uploads {
delete_image_from_pictrs(&upload.pictrs_alias, &upload.pictrs_delete_token, &context)
.await
.ok();
}
}
let person = Person::read(&mut context.pool(), data.person_id).await?;
ban_nonlocal_user_from_local_communities(
&local_user_view,
@ -54,7 +40,8 @@ pub async fn purge_person(
.await?;
// Clear profile data.
Person::delete_account(&mut context.pool(), data.person_id).await?;
purge_user_account(data.person_id, &context).await?;
// Keep person record, but mark as banned to prevent login or refetching from home instance.
let person = Person::update(
&mut context.pool(),

View file

@ -42,8 +42,8 @@ pub async fn get_sitemap(context: Data<LemmyContext>) -> LemmyResult<HttpRespons
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
pub(crate) mod tests {
#![allow(clippy::unwrap_used)]
use crate::sitemap::generate_urlset;
use chrono::{DateTime, NaiveDate, Utc};

View file

@ -72,9 +72,9 @@ impl Claims {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::{claims::Claims, context::LemmyContext};
use actix_web::test::TestRequest;
@ -124,7 +124,9 @@ mod tests {
.password_encrypted("123456".to_string())
.build();
let inserted_local_user = LocalUser::create(pool, &local_user_form).await.unwrap();
let inserted_local_user = LocalUser::create(pool, &local_user_form, vec![])
.await
.unwrap();
let req = TestRequest::default().to_http_request();
let jwt = Claims::generate(inserted_local_user.id, req, &context)

View file

@ -1,7 +1,7 @@
use crate::sensitive::Sensitive;
use lemmy_db_schema::{
newtypes::{CommentReplyId, CommunityId, LanguageId, PersonId, PersonMentionId},
source::site::Site,
source::{images::LocalImage, site::Site},
CommentSortType,
ListingType,
PostListingMode,
@ -422,3 +422,20 @@ pub struct UpdateTotp {
pub struct UpdateTotpResponse {
pub enabled: bool,
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Get your user's image / media uploads.
pub struct ListMedia {
pub page: Option<i64>,
pub limit: Option<i64>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
pub struct ListMediaResponse {
pub images: Vec<LocalImage>,
}

View file

@ -1,16 +1,24 @@
use crate::{
context::LemmyContext,
lemmy_db_schema::traits::Crud,
post::{LinkMetadata, OpenGraphData},
utils::proxy_image_link,
send_activity::{ActivityChannel, SendActivityData},
utils::{local_site_opt_to_sensitive, proxy_image_link, proxy_image_link_opt_apub},
};
use activitypub_federation::config::Data;
use encoding::{all::encodings, DecoderTrap};
use lemmy_db_schema::{
newtypes::DbUrl,
source::images::{LocalImage, LocalImageForm},
source::{
images::{LocalImage, LocalImageForm},
local_site::LocalSite,
post::{Post, PostUpdateForm},
},
};
use lemmy_utils::{
error::{LemmyError, LemmyErrorType},
settings::structs::{PictrsImageMode, Settings},
spawn_try_task,
version::VERSION,
REQWEST_TIMEOUT,
};
@ -82,6 +90,50 @@ pub async fn fetch_link_metadata_opt(
_ => Default::default(),
}
}
/// Generate post thumbnail in background task, because some sites can be very slow to respond.
///
/// Takes a callback to generate a send activity task, so that post can be federated with metadata.
pub fn generate_post_link_metadata(
post: Post,
custom_thumbnail: Option<Url>,
send_activity: impl FnOnce(Post) -> Option<SendActivityData> + Send + 'static,
local_site: Option<LocalSite>,
context: Data<LemmyContext>,
) {
spawn_try_task(async move {
let allow_sensitive = local_site_opt_to_sensitive(&local_site);
let page_is_sensitive = post.nsfw;
let allow_generate_thumbnail = allow_sensitive || !page_is_sensitive;
let mut thumbnail_url = custom_thumbnail.or_else(|| post.thumbnail_url.map(Into::into));
let do_generate_thumbnail = thumbnail_url.is_none() && allow_generate_thumbnail;
// Generate local thumbnail only if no thumbnail was federated and 'sensitive' attributes allow it.
let metadata = fetch_link_metadata_opt(
post.url.map(Into::into).as_ref(),
do_generate_thumbnail,
&context,
)
.await;
if let Some(thumbnail_url_) = metadata.thumbnail {
thumbnail_url = Some(thumbnail_url_.into());
}
let thumbnail_url = proxy_image_link_opt_apub(thumbnail_url, &context).await?;
let form = PostUpdateForm {
embed_title: Some(metadata.opengraph_data.title),
embed_description: Some(metadata.opengraph_data.description),
embed_video_url: Some(metadata.opengraph_data.embed_video_url),
thumbnail_url: Some(thumbnail_url),
url_content_type: Some(metadata.content_type),
..Default::default()
};
let updated_post = Post::update(&mut context.pool(), post.id, &form).await?;
if let Some(send_activity) = send_activity(updated_post) {
ActivityChannel::submit_activity(send_activity, &context).await?;
}
Ok(())
});
}
/// Extract site metadata from HTML Opengraph attributes.
fn extract_opengraph_data(html_bytes: &[u8], url: &Url) -> Result<OpenGraphData, LemmyError> {
@ -312,10 +364,30 @@ async fn is_image_content_type(client: &ClientWithMiddleware, url: &Url) -> Resu
}
}
/// When adding a new avatar or similar image, delete the old one.
pub async fn replace_image(
new_image: &Option<String>,
old_image: &Option<DbUrl>,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
if new_image.is_some() {
// Ignore errors because image may be stored externally.
if let Some(avatar) = &old_image {
let image = LocalImage::delete_by_url(&mut context.pool(), avatar)
.await
.ok();
if let Some(image) = image {
delete_image_from_pictrs(&image.pictrs_alias, &image.pictrs_delete_token, context).await?;
}
}
}
Ok(())
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::{
context::LemmyContext,

View file

@ -1,6 +1,6 @@
use crate::{
context::LemmyContext,
request::purge_image_from_pictrs,
request::{delete_image_from_pictrs, purge_image_from_pictrs},
site::{FederatedInstances, InstanceWithFederationState},
};
use chrono::{DateTime, Days, Local, TimeZone, Utc};
@ -12,7 +12,7 @@ use lemmy_db_schema::{
community::{Community, CommunityModerator, CommunityUpdateForm},
community_block::CommunityBlock,
email_verification::{EmailVerification, EmailVerificationForm},
images::RemoteImage,
images::{LocalImage, RemoteImage},
instance::Instance,
instance_block::InstanceBlock,
local_site::LocalSite,
@ -663,6 +663,25 @@ pub async fn purge_image_posts_for_person(
Ok(())
}
/// Delete a local_user's images
async fn delete_local_user_images(
person_id: PersonId,
context: &LemmyContext,
) -> Result<(), LemmyError> {
if let Ok(local_user) = LocalUserView::read_person(&mut context.pool(), person_id).await {
let pictrs_uploads =
LocalImage::get_all_by_local_user_id(&mut context.pool(), local_user.local_user.id).await?;
// Delete their images
for upload in pictrs_uploads {
delete_image_from_pictrs(&upload.pictrs_alias, &upload.pictrs_delete_token, context)
.await
.ok();
}
}
Ok(())
}
pub async fn purge_image_posts_for_community(
banned_community_id: CommunityId,
context: &LemmyContext,
@ -804,15 +823,22 @@ pub async fn purge_user_account(
context: &LemmyContext,
) -> Result<(), LemmyError> {
let pool = &mut context.pool();
// Delete their images
let person = Person::read(pool, person_id).await?;
// Delete their local images, if they're a local user
delete_local_user_images(person_id, context).await.ok();
// No need to update avatar and banner, those are handled in Person::delete_account
if let Some(avatar) = person.avatar {
purge_image_from_pictrs(&avatar, context).await.ok();
}
if let Some(banner) = person.banner {
purge_image_from_pictrs(&banner, context).await.ok();
}
// No need to update avatar and banner, those are handled in Person::delete_account
// Purge image posts
purge_image_posts_for_person(person_id, context).await.ok();
// Comments
Comment::permadelete_for_creator(pool, person_id)
@ -824,9 +850,6 @@ pub async fn purge_user_account(
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdatePost)?;
// Purge image posts
purge_image_posts_for_person(person_id, context).await?;
// Leave communities they mod
CommunityModerator::leave_all_communities(pool, person_id).await?;
@ -1019,9 +1042,9 @@ pub async fn proxy_image_link_opt_apub(
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use super::*;
use pretty_assertions::assert_eq;

View file

@ -164,10 +164,15 @@ pub async fn create_comment(
)
.await?;
// If its a reply, mark the parent as read
// If we're responding to a comment where we're the recipient,
// (ie we're the grandparent, or the recipient of the parent comment_reply),
// then mark the parent as read.
// Then we don't have to do it manually after we respond to a comment.
if let Some(parent) = parent_opt {
let person_id = local_user_view.person.id;
let parent_id = parent.id;
let comment_reply = CommentReply::read_by_comment(&mut context.pool(), parent_id).await;
let comment_reply =
CommentReply::read_by_comment_and_person(&mut context.pool(), parent_id, person_id).await;
if let Ok(reply) = comment_reply {
CommentReply::update(
&mut context.pool(),
@ -179,7 +184,6 @@ pub async fn create_comment(
}
// If the parent has PersonMentions mark them as read too
let person_id = local_user_view.person.id;
let person_mention =
PersonMention::read_by_comment_and_person(&mut context.pool(), parent_id, person_id).await;
if let Ok(mention) = person_mention {

View file

@ -4,6 +4,7 @@ use lemmy_api_common::{
build_response::build_community_response,
community::{CommunityResponse, EditCommunity},
context::LemmyContext,
request::replace_image,
send_activity::{ActivityChannel, SendActivityData},
utils::{
check_community_mod_action,
@ -42,6 +43,9 @@ pub async fn update_community(
let description =
process_markdown_opt(&data.description, &slur_regex, &url_blocklist, &context).await?;
is_valid_body_field(&data.description, false)?;
let old_community = Community::read(&mut context.pool(), data.community_id).await?;
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 = proxy_image_link_opt_api(&data.icon, &context).await?;

View file

@ -4,8 +4,8 @@ use lemmy_api_common::{
build_response::build_post_response,
context::LemmyContext,
post::{CreatePost, PostResponse},
request::fetch_link_metadata_opt,
send_activity::{ActivityChannel, SendActivityData},
request::generate_post_link_metadata,
send_activity::SendActivityData,
utils::{
check_community_user_action,
generate_local_apub_endpoint,
@ -75,6 +75,7 @@ pub async fn create_post(
is_url_blocked(&url, &url_blocklist)?;
check_url_scheme(&url)?;
check_url_scheme(&custom_thumbnail)?;
let url = proxy_image_link_opt_apub(url, &context).await?;
check_community_user_action(
&local_user_view.person,
@ -98,18 +99,6 @@ pub async fn create_post(
}
}
// Only generate the thumbnail if there's no custom thumbnail provided,
// otherwise it will save it in pictrs
let generate_thumbnail = custom_thumbnail.is_none();
// Fetch post links and pictrs cached image
let metadata = fetch_link_metadata_opt(url.as_ref(), generate_thumbnail, &context).await;
let url = proxy_image_link_opt_apub(url, &context).await?;
let thumbnail_url = proxy_image_link_opt_apub(custom_thumbnail, &context)
.await?
.map(Into::into)
.or(metadata.thumbnail);
// Only need to check if language is allowed in case user set it explicitly. When using default
// language, it already only returns allowed languages.
CommunityLanguage::is_allowed_community_language(
@ -134,18 +123,13 @@ pub async fn create_post(
let post_form = PostInsertForm::builder()
.name(data.name.trim().to_string())
.url_content_type(metadata.content_type)
.url(url)
.body(body)
.alt_text(data.alt_text.clone())
.community_id(data.community_id)
.creator_id(local_user_view.person.id)
.nsfw(data.nsfw)
.embed_title(metadata.opengraph_data.title)
.embed_description(metadata.opengraph_data.description)
.embed_video_url(metadata.opengraph_data.embed_video_url)
.language_id(language_id)
.thumbnail_url(thumbnail_url)
.build();
let inserted_post = Post::create(&mut context.pool(), &post_form)
@ -170,6 +154,14 @@ pub async fn create_post(
.await
.with_lemmy_type(LemmyErrorType::CouldntCreatePost)?;
generate_post_link_metadata(
updated_post.clone(),
custom_thumbnail,
|post| Some(SendActivityData::CreatePost(post)),
Some(local_site),
context.reset_request_count(),
);
// They like their own post by default
let person_id = local_user_view.person.id;
let post_id = inserted_post.id;
@ -183,9 +175,6 @@ pub async fn create_post(
.await
.with_lemmy_type(LemmyErrorType::CouldntLikePost)?;
ActivityChannel::submit_activity(SendActivityData::CreatePost(updated_post.clone()), &context)
.await?;
// Mark the post as read
mark_post_as_read(person_id, post_id, &mut context.pool()).await?;

View file

@ -4,8 +4,8 @@ use lemmy_api_common::{
build_response::build_post_response,
context::LemmyContext,
post::{EditPost, PostResponse},
request::fetch_link_metadata,
send_activity::{ActivityChannel, SendActivityData},
request::generate_post_link_metadata,
send_activity::SendActivityData,
utils::{
check_community_user_action,
get_url_blocklist,
@ -84,40 +84,11 @@ pub async fn update_post(
Err(LemmyErrorType::NoPostEditAllowed)?
}
// Fetch post links and thumbnail if url was updated
let (embed_title, embed_description, embed_video_url, metadata_thumbnail, metadata_content_type) =
match &url {
Some(url) => {
// Only generate the thumbnail if there's no custom thumbnail provided,
// otherwise it will save it in pictrs
let generate_thumbnail = custom_thumbnail.is_none() || orig_post.thumbnail_url.is_none();
let metadata = fetch_link_metadata(url, generate_thumbnail, &context).await?;
(
Some(metadata.opengraph_data.title),
Some(metadata.opengraph_data.description),
Some(metadata.opengraph_data.embed_video_url),
Some(metadata.thumbnail),
Some(metadata.content_type),
)
}
_ => Default::default(),
};
let url = match url {
Some(url) => Some(proxy_image_link_opt_apub(Some(url), &context).await?),
_ => Default::default(),
};
let custom_thumbnail = match custom_thumbnail {
Some(custom_thumbnail) => {
Some(proxy_image_link_opt_apub(Some(custom_thumbnail), &context).await?)
}
_ => Default::default(),
};
let thumbnail_url = custom_thumbnail.or(metadata_thumbnail);
let language_id = data.language_id;
CommunityLanguage::is_allowed_community_language(
&mut context.pool(),
@ -129,15 +100,10 @@ pub async fn update_post(
let post_form = PostUpdateForm {
name: data.name.clone(),
url,
url_content_type: metadata_content_type,
body: diesel_option_overwrite(body),
alt_text: diesel_option_overwrite(data.alt_text.clone()),
nsfw: data.nsfw,
embed_title,
embed_description,
embed_video_url,
language_id: data.language_id,
thumbnail_url,
updated: Some(Some(naive_now())),
..Default::default()
};
@ -147,7 +113,13 @@ pub async fn update_post(
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdatePost)?;
ActivityChannel::submit_activity(SendActivityData::UpdatePost(updated_post), &context).await?;
generate_post_link_metadata(
updated_post.clone(),
custom_thumbnail,
|post| Some(SendActivityData::UpdatePost(post)),
Some(local_site),
context.reset_request_count(),
);
build_post_response(
context.deref(),

View file

@ -189,9 +189,9 @@ fn validate_create_payload(local_site: &LocalSite, create_site: &CreateSite) ->
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::site::create::validate_create_payload;
use lemmy_api_common::site::CreateSite;

View file

@ -41,9 +41,9 @@ pub fn application_question_check(
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::site::{application_question_check, site_default_post_listing_type_check};
use lemmy_db_schema::{ListingType, RegistrationMode};

View file

@ -1,7 +1,9 @@
use crate::site::{application_question_check, site_default_post_listing_type_check};
use actix_web::web::{Data, Json};
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
context::LemmyContext,
request::replace_image,
site::{EditSite, SiteResponse},
utils::{
get_url_blocklist,
@ -63,6 +65,9 @@ pub async fn update_site(
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 url_blocklist = get_url_blocklist(&context).await?;
let sidebar = process_markdown_opt(&data.sidebar, &slur_regex, &url_blocklist, &context).await?;
@ -231,9 +236,9 @@ fn validate_update_payload(local_site: &LocalSite, edit_site: &EditSite) -> Lemm
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::site::update::validate_update_payload;
use lemmy_api_common::site::EditSite;

View file

@ -20,6 +20,7 @@ use lemmy_db_schema::{
aggregates::structs::PersonAggregates,
source::{
captcha_answer::{CaptchaAnswer, CheckCaptchaAnswer},
language::Language,
local_user::{LocalUser, LocalUserInsertForm},
local_user_vote_display_mode::LocalUserVoteDisplayMode,
person::{Person, PersonInsertForm},
@ -36,6 +37,7 @@ use lemmy_utils::{
validation::is_valid_actor_name,
},
};
use std::collections::HashSet;
#[tracing::instrument(skip(context))]
pub async fn register(
@ -128,12 +130,15 @@ pub async fn register(
let accepted_application = Some(!require_registration_application);
// Get the user's preferred language using the Accept-Language header
let language_tag = req.headers().get("Accept-Language").and_then(|hdr| {
accept_language::parse(hdr.to_str().unwrap_or_default())
.first()
// Remove the optional region code
.map(|lang_str| lang_str.split('-').next().unwrap_or_default().to_string())
});
let language_tags: Vec<String> = req
.headers()
.get("Accept-Language")
.map(|hdr| accept_language::parse(hdr.to_str().unwrap_or_default()))
.iter()
.flatten()
// Remove the optional region code
.map(|lang_str| lang_str.split('-').next().unwrap_or_default().to_string())
.collect();
// Create the local user
let local_user_form = LocalUserInsertForm::builder()
@ -144,12 +149,23 @@ pub async fn register(
.accepted_application(accepted_application)
.default_listing_type(Some(local_site.default_post_listing_type))
.post_listing_mode(Some(local_site.default_post_listing_mode))
.interface_language(language_tag)
.interface_language(language_tags.first().cloned())
// If its the initial site setup, they are an admin
.admin(Some(!local_site.site_setup))
.build();
let inserted_local_user = LocalUser::create(&mut context.pool(), &local_user_form).await?;
let all_languages = Language::read_all(&mut context.pool()).await?;
// use hashset to avoid duplicates
let mut language_ids = HashSet::new();
for l in language_tags {
if let Some(found) = all_languages.iter().find(|all| all.code == l) {
language_ids.insert(found.id);
}
}
let language_ids = language_ids.into_iter().collect();
let inserted_local_user =
LocalUser::create(&mut context.pool(), &local_user_form, language_ids).await?;
if local_site.site_setup && require_registration_application {
// Create the registration application

View file

@ -3,5 +3,6 @@
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"object": "http://ds9.lemmy.ml/u/lemmy_alpha",
"type": "Delete",
"id": "http://ds9.lemmy.ml/activities/delete/f2abee48-c7bb-41d5-9e27-8775ff32db12"
"id": "http://ds9.lemmy.ml/activities/delete/f2abee48-c7bb-41d5-9e27-8775ff32db12",
"removeData": true
}

View file

@ -72,6 +72,7 @@ impl BlockUser {
)?,
audience,
expires,
end_time: expires,
})
}
@ -149,7 +150,7 @@ impl ActivityHandler for BlockUser {
#[tracing::instrument(skip_all)]
async fn receive(self, context: &Data<LemmyContext>) -> Result<(), LemmyError> {
insert_received_activity(&self.id, context).await?;
let expires = self.expires.map(Into::into);
let expires = self.expires.or(self.end_time).map(Into::into);
let mod_person = self.actor.dereference(context).await?;
let blocked_person = self.object.dereference(context).await?;
let target = self.target.dereference(context).await?;

View file

@ -98,7 +98,7 @@ impl ActivityHandler for UndoBlockUser {
#[tracing::instrument(skip_all)]
async fn receive(self, context: &Data<LemmyContext>) -> Result<(), LemmyError> {
insert_received_activity(&self.id, context).await?;
let expires = self.object.expires.map(Into::into);
let expires = self.object.expires.or(self.object.end_time).map(Into::into);
let mod_person = self.actor.dereference(context).await?;
let blocked_person = self.object.object.dereference(context).await?;
match self.object.target.dereference(context).await? {

View file

@ -2,7 +2,7 @@ use crate::{
activities::{
generate_activity_id,
verify_person_in_community,
voting::{vote_comment, vote_post},
voting::{undo_vote_comment, undo_vote_post, vote_comment, vote_post},
},
insert_received_activity,
objects::{community::ApubCommunity, person::ApubPerson},
@ -17,7 +17,6 @@ use activitypub_federation::{
fetch::object_id::ObjectId,
traits::{ActivityHandler, Actor},
};
use anyhow::anyhow;
use lemmy_api_common::{context::LemmyContext, utils::check_bot_account};
use lemmy_db_schema::source::local_site::LocalSite;
use lemmy_utils::error::LemmyError;
@ -58,15 +57,7 @@ impl ActivityHandler for Vote {
async fn verify(&self, context: &Data<LemmyContext>) -> Result<(), LemmyError> {
let community = self.community(context).await?;
verify_person_in_community(&self.actor, &community, context).await?;
let enable_downvotes = LocalSite::read(&mut context.pool())
.await
.map(|l| l.enable_downvotes)
.unwrap_or(true);
if self.kind == VoteType::Dislike && !enable_downvotes {
Err(anyhow!("Downvotes disabled").into())
} else {
Ok(())
}
Ok(())
}
#[tracing::instrument(skip_all)]
@ -77,9 +68,22 @@ impl ActivityHandler for Vote {
check_bot_account(&actor.0)?;
match object {
PostOrComment::Post(p) => vote_post(&self.kind, actor, &p, context).await,
PostOrComment::Comment(c) => vote_comment(&self.kind, actor, &c, context).await,
let enable_downvotes = LocalSite::read(&mut context.pool())
.await
.map(|l| l.enable_downvotes)
.unwrap_or(true);
if self.kind == VoteType::Dislike && !enable_downvotes {
// If this is a downvote but downvotes are ignored, only undo any existing vote
match object {
PostOrComment::Post(p) => undo_vote_post(actor, &p, context).await,
PostOrComment::Comment(c) => undo_vote_comment(actor, &c, context).await,
}
} else {
// Otherwise apply the vote normally
match object {
PostOrComment::Post(p) => vote_post(&self.kind, actor, &p, context).await,
PostOrComment::Comment(c) => vote_comment(&self.kind, actor, &c, context).await,
}
}
}
}

View file

@ -123,8 +123,8 @@ impl InCommunity for AnnouncableActivities {
}
#[cfg(test)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::indexing_slicing)]
use crate::{
activity_lists::{GroupInboxActivities, PersonInboxActivities, SharedInboxActivities},

View file

@ -319,8 +319,8 @@ pub async fn import_settings(
}
#[cfg(test)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::indexing_slicing)]
use crate::api::user_settings_backup::{export_settings, import_settings};
use activitypub_federation::config::Data;
@ -361,7 +361,7 @@ mod tests {
.person_id(person.id)
.password_encrypted("pass".to_string())
.build();
let local_user = LocalUser::create(&mut context.pool(), &user_form).await?;
let local_user = LocalUser::create(&mut context.pool(), &user_form, vec![]).await?;
Ok(LocalUserView::read(&mut context.pool(), local_user.id).await?)
}

View file

@ -101,8 +101,8 @@ impl Collection for ApubCommunityModerators {
}
#[cfg(test)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::indexing_slicing)]
use super::*;
use crate::{

View file

@ -115,9 +115,9 @@ pub(crate) async fn get_apub_community_featured(
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
pub(crate) mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use super::*;
use crate::protocol::objects::{group::Group, tombstone::Tombstone};

View file

@ -24,10 +24,9 @@ use chrono::{DateTime, Utc};
use html2text::{from_read_with_decorator, render::text_renderer::TrivialDecorator};
use lemmy_api_common::{
context::LemmyContext,
request::fetch_link_metadata_opt,
request::generate_post_link_metadata,
utils::{
get_url_blocklist,
local_site_opt_to_sensitive,
local_site_opt_to_slur_regex,
process_markdown_opt,
proxy_image_link_opt_apub,
@ -218,6 +217,7 @@ impl Object for ApubPost {
let old_post = page.id.dereference_local(context).await;
let first_attachment = page.attachment.first();
let local_site = LocalSite::read(&mut context.pool()).await.ok();
let form = if !page.is_mod_action(context).await? {
let url = if let Some(attachment) = first_attachment.cloned() {
@ -231,20 +231,8 @@ impl Object for ApubPost {
check_url_scheme(&url)?;
let alt_text = first_attachment.cloned().and_then(Attachment::alt_text);
let local_site = LocalSite::read(&mut context.pool()).await.ok();
let allow_sensitive = local_site_opt_to_sensitive(&local_site);
let page_is_sensitive = page.sensitive.unwrap_or(false);
let allow_generate_thumbnail = allow_sensitive || !page_is_sensitive;
let mut thumbnail_url = page.image.map(|i| i.url);
let do_generate_thumbnail = thumbnail_url.is_none() && allow_generate_thumbnail;
// Generate local thumbnail only if no thumbnail was federated and 'sensitive' attributes allow it.
let metadata = fetch_link_metadata_opt(url.as_ref(), do_generate_thumbnail, context).await;
if let Some(thumbnail_url_) = metadata.thumbnail {
thumbnail_url = Some(thumbnail_url_.into());
}
let url = proxy_image_link_opt_apub(url, context).await?;
let thumbnail_url = proxy_image_link_opt_apub(thumbnail_url, context).await?;
let slur_regex = &local_site_opt_to_slur_regex(&local_site);
let url_blocklist = get_url_blocklist(context).await?;
@ -254,30 +242,22 @@ impl Object for ApubPost {
let language_id =
LanguageTag::to_language_id_single(page.language, &mut context.pool()).await?;
PostInsertForm {
name,
url: url.map(Into::into),
body,
alt_text,
creator_id: creator.id,
community_id: community.id,
removed: None,
locked: page.comments_enabled.map(|e| !e),
published: page.published.map(Into::into),
updated: page.updated.map(Into::into),
deleted: Some(false),
nsfw: page.sensitive,
embed_title: metadata.opengraph_data.title,
embed_description: metadata.opengraph_data.description,
embed_video_url: metadata.opengraph_data.embed_video_url,
thumbnail_url,
ap_id: Some(page.id.clone().into()),
local: Some(false),
language_id,
featured_community: None,
featured_local: None,
url_content_type: metadata.content_type,
}
PostInsertForm::builder()
.name(name)
.url(url.map(Into::into))
.body(body)
.alt_text(alt_text)
.creator_id(creator.id)
.community_id(community.id)
.locked(page.comments_enabled.map(|e| !e))
.published(page.published.map(Into::into))
.updated(page.updated.map(Into::into))
.deleted(Some(false))
.nsfw(page.sensitive)
.ap_id(Some(page.id.clone().into()))
.local(Some(false))
.language_id(language_id)
.build()
} else {
// if is mod action, only update locked/stickied fields, nothing else
PostInsertForm::builder()
@ -292,6 +272,14 @@ impl Object for ApubPost {
let post = Post::create(&mut context.pool(), &form).await?;
generate_post_link_metadata(
post.clone(),
page.image.map(|i| i.url),
|_| None,
local_site,
context.reset_request_count(),
);
// write mod log entry for lock
if Page::is_locked_changed(&old_post, &page.comments_enabled) {
let form = ModLockPostForm {

View file

@ -38,7 +38,9 @@ pub struct BlockUser {
pub(crate) remove_data: Option<bool>,
/// block reason, written to mod log
pub(crate) summary: Option<String>,
/// TODO: deprecated
pub(crate) expires: Option<DateTime<Utc>>,
pub(crate) end_time: Option<DateTime<Utc>>,
}
#[async_trait::async_trait]

View file

@ -33,9 +33,9 @@ impl CommentAggregates {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::{
aggregates::comment_aggregates::CommentAggregates,

View file

@ -31,9 +31,9 @@ impl CommunityAggregates {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::{
aggregates::community_aggregates::CommunityAggregates,

View file

@ -18,9 +18,9 @@ impl PersonAggregates {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::{
aggregates::person_aggregates::PersonAggregates,

View file

@ -52,9 +52,9 @@ impl PostAggregates {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::{
aggregates::post_aggregates::PostAggregates,

View file

@ -14,9 +14,9 @@ impl SiteAggregates {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::{
aggregates::site_aggregates::SiteAggregates,

View file

@ -61,9 +61,9 @@ impl ReceivedActivity {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use super::*;
use crate::{source::activity::ActorType, utils::build_db_pool_for_tests};

View file

@ -385,9 +385,9 @@ async fn convert_read_languages(
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use super::*;
use crate::{
@ -523,10 +523,6 @@ mod tests {
let pool = &mut pool.into();
let (site, instance) = create_test_site(pool).await;
let mut test_langs = test_langs1(pool).await;
SiteLanguage::update(pool, test_langs.clone(), &site)
.await
.unwrap();
let person_form = PersonInsertForm::builder()
.name("my test person".to_string())
@ -539,14 +535,13 @@ mod tests {
.password_encrypted("my_pw".to_string())
.build();
let local_user = LocalUser::create(pool, &local_user_form).await.unwrap();
let local_user = LocalUser::create(pool, &local_user_form, vec![])
.await
.unwrap();
let local_user_langs1 = LocalUserLanguage::read(pool, local_user.id).await.unwrap();
// new user should be initialized with site languages and undetermined
//test_langs.push(UNDETERMINED_ID);
//test_langs.sort();
test_langs.insert(0, UNDETERMINED_ID);
assert_eq!(test_langs, local_user_langs1);
// new user should be initialized with all languages
assert_eq!(0, local_user_langs1.len());
// update user languages
let test_langs2 = test_langs2(pool).await;
@ -655,7 +650,9 @@ mod tests {
.person_id(person.id)
.password_encrypted("my_pw".to_string())
.build();
let local_user = LocalUser::create(pool, &local_user_form).await.unwrap();
let local_user = LocalUser::create(pool, &local_user_form, vec![])
.await
.unwrap();
LocalUserLanguage::update(pool, test_langs2, local_user.id)
.await
.unwrap();

View file

@ -48,9 +48,9 @@ impl CaptchaAnswer {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::{
source::captcha_answer::{CaptchaAnswer, CaptchaAnswerForm, CheckCaptchaAnswer},

View file

@ -286,9 +286,9 @@ impl Saveable for CommentSaved {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::{
newtypes::LanguageId,

View file

@ -1,6 +1,6 @@
use crate::{
newtypes::{CommentId, CommentReplyId, PersonId},
schema::comment_reply::dsl::{comment_id, comment_reply, read, recipient_id},
schema::comment_reply,
source::comment_reply::{CommentReply, CommentReplyInsertForm, CommentReplyUpdateForm},
traits::Crud,
utils::{get_conn, DbPool},
@ -22,9 +22,9 @@ impl Crud for CommentReply {
// since the return here isnt utilized, we dont need to do an update
// but get_result doesnt return the existing row here
insert_into(comment_reply)
insert_into(comment_reply::table)
.values(comment_reply_form)
.on_conflict((recipient_id, comment_id))
.on_conflict((comment_reply::recipient_id, comment_reply::comment_id))
.do_update()
.set(comment_reply_form)
.get_result::<Self>(conn)
@ -37,7 +37,7 @@ impl Crud for CommentReply {
comment_reply_form: &Self::UpdateForm,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
diesel::update(comment_reply.find(comment_reply_id))
diesel::update(comment_reply::table.find(comment_reply_id))
.set(comment_reply_form)
.get_result::<Self>(conn)
.await
@ -51,11 +51,11 @@ impl CommentReply {
) -> Result<Vec<CommentReply>, Error> {
let conn = &mut get_conn(pool).await?;
diesel::update(
comment_reply
.filter(recipient_id.eq(for_recipient_id))
.filter(read.eq(false)),
comment_reply::table
.filter(comment_reply::recipient_id.eq(for_recipient_id))
.filter(comment_reply::read.eq(false)),
)
.set(read.eq(true))
.set(comment_reply::read.eq(true))
.get_results::<Self>(conn)
.await
}
@ -65,17 +65,30 @@ impl CommentReply {
for_comment_id: CommentId,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
comment_reply
.filter(comment_id.eq(for_comment_id))
comment_reply::table
.filter(comment_reply::comment_id.eq(for_comment_id))
.first::<Self>(conn)
.await
}
pub async fn read_by_comment_and_person(
pool: &mut DbPool<'_>,
for_comment_id: CommentId,
for_recipient_id: PersonId,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
comment_reply::table
.filter(comment_reply::comment_id.eq(for_comment_id))
.filter(comment_reply::recipient_id.eq(for_recipient_id))
.first::<Self>(conn)
.await
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::{
source::{

View file

@ -451,9 +451,9 @@ impl ApubActor for Community {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::{
source::{

View file

@ -48,9 +48,9 @@ impl FederationAllowList {
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::{
source::{federation_allowlist::FederationAllowList, instance::Instance},

View file

@ -1,11 +1,8 @@
use crate::{
newtypes::{DbUrl, LocalUserId},
schema::{
local_image::dsl::{local_image, local_user_id, pictrs_alias},
remote_image::dsl::{link, remote_image},
},
schema::{local_image, remote_image},
source::images::{LocalImage, LocalImageForm, RemoteImage, RemoteImageForm},
utils::{get_conn, DbPool},
utils::{get_conn, limit_and_offset, DbPool},
};
use diesel::{
dsl::exists,
@ -15,7 +12,6 @@ use diesel::{
ExpressionMethods,
NotFound,
QueryDsl,
Table,
};
use diesel_async::RunQueryDsl;
use url::Url;
@ -23,29 +19,71 @@ use url::Url;
impl LocalImage {
pub async fn create(pool: &mut DbPool<'_>, form: &LocalImageForm) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
insert_into(local_image)
insert_into(local_image::table)
.values(form)
.get_result::<Self>(conn)
.await
}
pub async fn get_all_paged_by_local_user_id(
pool: &mut DbPool<'_>,
user_id: LocalUserId,
page: Option<i64>,
limit: Option<i64>,
) -> Result<Vec<Self>, Error> {
Self::get_all_helper(pool, Some(user_id), page, limit, false).await
}
pub async fn get_all_by_local_user_id(
pool: &mut DbPool<'_>,
user_id: &LocalUserId,
user_id: LocalUserId,
) -> Result<Vec<Self>, Error> {
Self::get_all_helper(pool, Some(user_id), None, None, true).await
}
pub async fn get_all(
pool: &mut DbPool<'_>,
page: Option<i64>,
limit: Option<i64>,
) -> Result<Vec<Self>, Error> {
Self::get_all_helper(pool, None, page, limit, false).await
}
async fn get_all_helper(
pool: &mut DbPool<'_>,
user_id: Option<LocalUserId>,
page: Option<i64>,
limit: Option<i64>,
ignore_page_limits: bool,
) -> Result<Vec<Self>, Error> {
let conn = &mut get_conn(pool).await?;
local_image
.filter(local_user_id.eq(user_id))
.select(local_image::all_columns())
.load::<LocalImage>(conn)
let mut query = local_image::table
.select(local_image::all_columns)
.order_by(local_image::published.desc())
.into_boxed();
if let Some(user_id) = user_id {
query = query.filter(local_image::local_user_id.eq(user_id))
}
if !ignore_page_limits {
let (limit, offset) = limit_and_offset(page, limit)?;
query = query.limit(limit).offset(offset);
}
query.load::<LocalImage>(conn).await
}
pub async fn delete_by_alias(pool: &mut DbPool<'_>, alias: &str) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
diesel::delete(local_image::table.filter(local_image::pictrs_alias.eq(alias)))
.get_result(conn)
.await
}
pub async fn delete_by_alias(pool: &mut DbPool<'_>, alias: &str) -> Result<usize, Error> {
let conn = &mut get_conn(pool).await?;
diesel::delete(local_image.filter(pictrs_alias.eq(alias)))
.execute(conn)
.await
pub async fn delete_by_url(pool: &mut DbPool<'_>, url: &DbUrl) -> Result<Self, Error> {
let alias = url.as_str().split('/').last().ok_or(NotFound)?;
Self::delete_by_alias(pool, alias).await
}
}
@ -56,7 +94,7 @@ impl RemoteImage {
.into_iter()
.map(|url| RemoteImageForm { link: url.into() })
.collect::<Vec<_>>();
insert_into(remote_image)
insert_into(remote_image::table)
.values(forms)
.on_conflict_do_nothing()
.execute(conn)
@ -66,9 +104,11 @@ impl RemoteImage {
pub async fn validate(pool: &mut DbPool<'_>, link_: DbUrl) -> Result<(), Error> {
let conn = &mut get_conn(pool).await?;
let exists = select(exists(remote_image.filter((link).eq(link_))))
.get_result::<bool>(conn)
.await?;
let exists = select(exists(
remote_image::table.filter(remote_image::link.eq(link_)),
))
.get_result::<bool>(conn)
.await?;
if exists {
Ok(())
} else {

View file

@ -41,9 +41,9 @@ impl Language {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::{source::language::Language, utils::build_db_pool_for_tests};
use pretty_assertions::assert_eq;

View file

@ -1,12 +1,11 @@
use crate::{
newtypes::{DbUrl, LocalUserId, PersonId},
newtypes::{DbUrl, LanguageId, LocalUserId, PersonId},
schema::{local_user, person, registration_application},
source::{
actor_language::{LocalUserLanguage, SiteLanguage},
actor_language::LocalUserLanguage,
local_user::{LocalUser, LocalUserInsertForm, LocalUserUpdateForm},
local_user_vote_display_mode::{LocalUserVoteDisplayMode, LocalUserVoteDisplayModeInsertForm},
},
traits::Crud,
utils::{
action_query,
functions::{coalesce, lower},
@ -26,6 +25,52 @@ use diesel::{
use diesel_async::RunQueryDsl;
impl LocalUser {
pub async fn create(
pool: &mut DbPool<'_>,
form: &LocalUserInsertForm,
languages: Vec<LanguageId>,
) -> Result<LocalUser, Error> {
let conn = &mut get_conn(pool).await?;
let mut form_with_encrypted_password = form.clone();
let password_hash =
hash(&form.password_encrypted, DEFAULT_COST).expect("Couldn't hash password");
form_with_encrypted_password.password_encrypted = password_hash;
let local_user_ = insert_into(local_user::table)
.values(form_with_encrypted_password)
.get_result::<Self>(conn)
.await?;
LocalUserLanguage::update(pool, languages, local_user_.id).await?;
// Create their vote_display_modes
let vote_display_mode_form = LocalUserVoteDisplayModeInsertForm::builder()
.local_user_id(local_user_.id)
.build();
LocalUserVoteDisplayMode::create(pool, &vote_display_mode_form).await?;
Ok(local_user_)
}
pub async fn update(
pool: &mut DbPool<'_>,
local_user_id: LocalUserId,
form: &LocalUserUpdateForm,
) -> Result<LocalUser, Error> {
let conn = &mut get_conn(pool).await?;
diesel::update(local_user::table.find(local_user_id))
.set(form)
.get_result::<Self>(conn)
.await
}
pub async fn delete(pool: &mut DbPool<'_>, id: LocalUserId) -> Result<usize, Error> {
let conn = &mut *get_conn(pool).await?;
diesel::delete(local_user::table.find(id))
.execute(conn)
.await
}
pub async fn update_password(
pool: &mut DbPool<'_>,
local_user_id: LocalUserId,
@ -183,52 +228,3 @@ pub struct UserBackupLists {
pub blocked_users: Vec<DbUrl>,
pub blocked_instances: Vec<String>,
}
#[async_trait]
impl Crud for LocalUser {
type InsertForm = LocalUserInsertForm;
type UpdateForm = LocalUserUpdateForm;
type IdType = LocalUserId;
async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
let mut form_with_encrypted_password = form.clone();
let password_hash =
hash(&form.password_encrypted, DEFAULT_COST).expect("Couldn't hash password");
form_with_encrypted_password.password_encrypted = password_hash;
let local_user_ = insert_into(local_user::table)
.values(form_with_encrypted_password)
.get_result::<Self>(conn)
.await?;
let site_languages = SiteLanguage::read_local_raw(pool).await;
if let Ok(langs) = site_languages {
// if site exists, init user with site languages
LocalUserLanguage::update(pool, langs, local_user_.id).await?;
} else {
// otherwise, init with all languages (this only happens during tests and
// for first admin user, which is created before site)
LocalUserLanguage::update(pool, vec![], local_user_.id).await?;
}
// Create their vote_display_modes
let vote_display_mode_form = LocalUserVoteDisplayModeInsertForm::builder()
.local_user_id(local_user_.id)
.build();
LocalUserVoteDisplayMode::create(pool, &vote_display_mode_form).await?;
Ok(local_user_)
}
async fn update(
pool: &mut DbPool<'_>,
local_user_id: LocalUserId,
form: &Self::UpdateForm,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
diesel::update(local_user::table.find(local_user_id))
.set(form)
.get_result::<Self>(conn)
.await
}
}

View file

@ -465,9 +465,9 @@ impl Crud for AdminPurgeComment {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::{
source::{

View file

@ -81,9 +81,9 @@ impl PasswordResetRequest {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::{
source::{
@ -121,7 +121,9 @@ mod tests {
.password_encrypted("pass".to_string())
.build();
let inserted_local_user = LocalUser::create(pool, &new_local_user).await.unwrap();
let inserted_local_user = LocalUser::create(pool, &new_local_user, vec![])
.await
.unwrap();
let token = "nope";

View file

@ -241,9 +241,9 @@ impl PersonFollower {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::{
source::{

View file

@ -1,6 +1,6 @@
use crate::{
newtypes::{CommentId, PersonId, PersonMentionId},
schema::person_mention::dsl::{comment_id, person_mention, read, recipient_id},
schema::person_mention,
source::person_mention::{PersonMention, PersonMentionInsertForm, PersonMentionUpdateForm},
traits::Crud,
utils::{get_conn, DbPool},
@ -21,9 +21,9 @@ impl Crud for PersonMention {
let conn = &mut get_conn(pool).await?;
// since the return here isnt utilized, we dont need to do an update
// but get_result doesnt return the existing row here
insert_into(person_mention)
insert_into(person_mention::table)
.values(person_mention_form)
.on_conflict((recipient_id, comment_id))
.on_conflict((person_mention::recipient_id, person_mention::comment_id))
.do_update()
.set(person_mention_form)
.get_result::<Self>(conn)
@ -36,7 +36,7 @@ impl Crud for PersonMention {
person_mention_form: &Self::UpdateForm,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
diesel::update(person_mention.find(person_mention_id))
diesel::update(person_mention::table.find(person_mention_id))
.set(person_mention_form)
.get_result::<Self>(conn)
.await
@ -50,11 +50,11 @@ impl PersonMention {
) -> Result<Vec<PersonMention>, Error> {
let conn = &mut get_conn(pool).await?;
diesel::update(
person_mention
.filter(recipient_id.eq(for_recipient_id))
.filter(read.eq(false)),
person_mention::table
.filter(person_mention::recipient_id.eq(for_recipient_id))
.filter(person_mention::read.eq(false)),
)
.set(read.eq(true))
.set(person_mention::read.eq(true))
.get_results::<Self>(conn)
.await
}
@ -65,18 +65,18 @@ impl PersonMention {
for_recipient_id: PersonId,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
person_mention
.filter(comment_id.eq(for_comment_id))
.filter(recipient_id.eq(for_recipient_id))
person_mention::table
.filter(person_mention::comment_id.eq(for_comment_id))
.filter(person_mention::recipient_id.eq(for_recipient_id))
.first::<Self>(conn)
.await
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::{
source::{

View file

@ -28,7 +28,7 @@ use crate::{
},
};
use ::url::Url;
use chrono::{DateTime, Duration, Utc};
use chrono::{DateTime, Utc};
use diesel::{
dsl::{self, insert_into},
result::Error,
@ -112,7 +112,9 @@ impl Post {
.filter(post::local.eq(true))
.filter(post::deleted.eq(false))
.filter(post::removed.eq(false))
.filter(post::published.ge(Utc::now().naive_utc() - Duration::days(SITEMAP_DAYS)))
.filter(
post::published.ge(Utc::now().naive_utc() - SITEMAP_DAYS.expect("TimeDelta out of bounds")),
)
.order(post::published.desc())
.limit(SITEMAP_LIMIT)
.load::<(DbUrl, chrono::DateTime<Utc>)>(conn)
@ -425,9 +427,9 @@ impl PostHide {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::{
source::{

View file

@ -80,9 +80,9 @@ impl Reportable for PostReport {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use super::*;
use crate::{

View file

@ -74,9 +74,9 @@ impl PrivateMessage {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::{
source::{

View file

@ -2,16 +2,27 @@ use crate::newtypes::{DbUrl, LocalUserId};
#[cfg(feature = "full")]
use crate::schema::{local_image, remote_image};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use std::fmt::Debug;
#[cfg(feature = "full")]
use ts_rs::TS;
use typed_builder::TypedBuilder;
#[derive(PartialEq, Eq, Debug, Clone)]
#[cfg_attr(feature = "full", derive(Queryable, Associations))]
#[skip_serializing_none]
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
#[cfg_attr(
feature = "full",
derive(Queryable, Selectable, Identifiable, Associations, TS)
)]
#[cfg_attr(feature = "full", ts(export))]
#[cfg_attr(feature = "full", diesel(table_name = local_image))]
#[cfg_attr(
feature = "full",
diesel(belongs_to(crate::source::local_user::LocalUser))
)]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
#[cfg_attr(feature = "full", diesel(primary_key(local_user_id)))]
pub struct LocalImage {
pub local_user_id: Option<LocalUserId>,
pub pictrs_alias: String,

View file

@ -6,7 +6,7 @@ use crate::{
SortType,
};
use anyhow::Context;
use chrono::{DateTime, Utc};
use chrono::{DateTime, TimeDelta, Utc};
use deadpool::Runtime;
use diesel::{
dsl,
@ -66,7 +66,7 @@ use url::Url;
const FETCH_LIMIT_DEFAULT: i64 = 10;
pub const FETCH_LIMIT_MAX: i64 = 50;
pub const SITEMAP_LIMIT: i64 = 50000;
pub const SITEMAP_DAYS: i64 = 31;
pub const SITEMAP_DAYS: Option<TimeDelta> = TimeDelta::try_days(31);
pub const RANK_DEFAULT: f64 = 0.0001;
pub type ActualDbPool = Pool<AsyncPgConnection>;
@ -649,9 +649,9 @@ impl<RF, LF> Queries<RF, LF> {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use super::*;
use pretty_assertions::assert_eq;

View file

@ -234,9 +234,9 @@ impl CommentReportQuery {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::{
comment_report_view::{CommentReportQuery, CommentReportView},
@ -284,7 +284,9 @@ mod tests {
.person_id(inserted_timmy.id)
.password_encrypted("123".to_string())
.build();
let timmy_local_user = LocalUser::create(pool, &new_local_user).await.unwrap();
let timmy_local_user = LocalUser::create(pool, &new_local_user, vec![])
.await
.unwrap();
let timmy_view = LocalUserView {
local_user: timmy_local_user,
local_user_vote_display_mode: LocalUserVoteDisplayMode::default(),

View file

@ -91,6 +91,9 @@ fn queries<'a>() -> Queries<
.field(community_actions::received_ban)
.nullable()
.is_not_null(),
community_actions::received_ban
.nullable()
.is_not_null(),
creator_community_actions
.field(community_actions::became_moderator)
.nullable()
@ -321,9 +324,9 @@ impl<'a> CommentQuery<'a> {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::{
comment_view::{CommentQuery, CommentSortType, CommentView, DbPool},
@ -350,6 +353,8 @@ mod tests {
CommunityInsertForm,
CommunityModerator,
CommunityModeratorForm,
CommunityPersonBan,
CommunityPersonBanForm,
CommunityUpdateForm,
},
instance::Instance,
@ -360,11 +365,12 @@ mod tests {
person_block::{PersonBlock, PersonBlockForm},
post::{Post, PostInsertForm},
},
traits::{Blockable, Crud, Joinable, Likeable, Saveable},
traits::{Bannable, Blockable, Crud, Joinable, Likeable, Saveable},
utils::{build_db_pool_for_tests, RANK_DEFAULT},
CommunityVisibility,
SubscribedType,
};
use lemmy_utils::error::LemmyResult;
use pretty_assertions::assert_eq;
use serial_test::serial;
@ -395,7 +401,7 @@ mod tests {
.admin(Some(true))
.password_encrypted(String::new())
.build();
let inserted_timmy_local_user = LocalUser::create(pool, &timmy_local_user_form)
let inserted_timmy_local_user = LocalUser::create(pool, &timmy_local_user_form, vec![])
.await
.unwrap();
@ -547,12 +553,12 @@ mod tests {
#[tokio::test]
#[serial]
async fn test_crud() {
async fn test_crud() -> LemmyResult<()> {
let pool = &build_db_pool_for_tests().await;
let pool = &mut pool.into();
let data = init_data(pool).await;
let expected_comment_view_no_person = expected_comment_view(&data, pool).await;
let expected_comment_view_no_person = expected_comment_view(&data, pool).await?;
let mut expected_comment_view_with_person = expected_comment_view_no_person.clone();
expected_comment_view_with_person.my_vote = Some(1);
@ -563,8 +569,7 @@ mod tests {
..Default::default()
}
.list(pool)
.await
.unwrap();
.await?;
assert_eq!(
expected_comment_view_no_person,
@ -578,8 +583,7 @@ mod tests {
..Default::default()
}
.list(pool)
.await
.unwrap();
.await?;
assert_eq!(
expected_comment_view_with_person,
@ -594,8 +598,7 @@ mod tests {
data.inserted_comment_1.id,
Some(data.timmy_local_user_view.person.id),
)
.await
.unwrap();
.await?;
// Make sure block set the creator blocked
assert!(read_comment_from_blocked_person.creator_blocked);
@ -606,8 +609,7 @@ mod tests {
..Default::default()
}
.list(pool)
.await
.unwrap();
.await?;
assert_eq!(
expected_comment_view_with_person,
@ -622,17 +624,16 @@ mod tests {
..Default::default()
}
.list(pool)
.await
.unwrap();
.await?;
assert!(read_disliked_comment_views.is_empty());
cleanup(data, pool).await;
cleanup(data, pool).await
}
#[tokio::test]
#[serial]
async fn test_comment_tree() {
async fn test_comment_tree() -> LemmyResult<()> {
let pool = &build_db_pool_for_tests().await;
let pool = &mut pool.into();
let data = init_data(pool).await;
@ -644,8 +645,7 @@ mod tests {
..Default::default()
}
.list(pool)
.await
.unwrap();
.await?;
let child_path = data.inserted_comment_1.path.clone();
let read_comment_views_child_path = CommentQuery {
@ -654,8 +654,7 @@ mod tests {
..Default::default()
}
.list(pool)
.await
.unwrap();
.await?;
// Make sure the comment parent-limited fetch is correct
assert_length!(6, read_comment_views_top_path);
@ -675,12 +674,11 @@ mod tests {
..Default::default()
}
.list(pool)
.await
.unwrap();
.await?;
// Make sure a depth limited one only has the top comment
assert_eq!(
expected_comment_view(&data, pool).await,
expected_comment_view(&data, pool).await?,
read_comment_views_top_max_depth[0]
);
assert_length!(1, read_comment_views_top_max_depth);
@ -694,8 +692,7 @@ mod tests {
..Default::default()
}
.list(pool)
.await
.unwrap();
.await?;
// Make sure a depth limited one, and given child comment 1, has 3
assert!(read_comment_views_parent_max_depth[2]
@ -704,12 +701,12 @@ mod tests {
.eq("Comment 3"));
assert_length!(3, read_comment_views_parent_max_depth);
cleanup(data, pool).await;
cleanup(data, pool).await
}
#[tokio::test]
#[serial]
async fn test_languages() {
async fn test_languages() -> LemmyResult<()> {
let pool = &build_db_pool_for_tests().await;
let pool = &mut pool.into();
let data = init_data(pool).await;
@ -721,29 +718,25 @@ mod tests {
..Default::default()
}
.list(pool)
.await
.unwrap();
.await?;
assert_length!(5, all_languages);
// change user lang to finnish, should only show one post in finnish and one undetermined
let finnish_id = Language::read_id_from_code(pool, Some("fi"))
.await
.unwrap()
.await?
.unwrap();
LocalUserLanguage::update(
pool,
vec![finnish_id],
data.timmy_local_user_view.local_user.id,
)
.await
.unwrap();
.await?;
let finnish_comments = CommentQuery {
local_user: (Some(&data.timmy_local_user_view)),
..Default::default()
}
.list(pool)
.await
.unwrap();
.await?;
assert_length!(2, finnish_comments);
let finnish_comment = finnish_comments
.iter()
@ -760,23 +753,21 @@ mod tests {
vec![UNDETERMINED_ID],
data.timmy_local_user_view.local_user.id,
)
.await
.unwrap();
.await?;
let undetermined_comment = CommentQuery {
local_user: (Some(&data.timmy_local_user_view)),
..Default::default()
}
.list(pool)
.await
.unwrap();
.await?;
assert_length!(1, undetermined_comment);
cleanup(data, pool).await;
cleanup(data, pool).await
}
#[tokio::test]
#[serial]
async fn test_distinguished_first() {
async fn test_distinguished_first() -> LemmyResult<()> {
let pool = &build_db_pool_for_tests().await;
let pool = &mut pool.into();
let data = init_data(pool).await;
@ -785,26 +776,23 @@ mod tests {
distinguished: Some(true),
..Default::default()
};
Comment::update(pool, data.inserted_comment_2.id, &form)
.await
.unwrap();
Comment::update(pool, data.inserted_comment_2.id, &form).await?;
let comments = CommentQuery {
post_id: Some(data.inserted_comment_2.post_id),
..Default::default()
}
.list(pool)
.await
.unwrap();
.await?;
assert_eq!(comments[0].comment.id, data.inserted_comment_2.id);
assert!(comments[0].comment.distinguished);
cleanup(data, pool).await;
cleanup(data, pool).await
}
#[tokio::test]
#[serial]
async fn test_creator_is_moderator() {
async fn test_creator_is_moderator() -> LemmyResult<()> {
let pool = &build_db_pool_for_tests().await;
let pool = &mut pool.into();
let data = init_data(pool).await;
@ -816,7 +804,7 @@ mod tests {
community_id,
person_id,
};
CommunityModerator::join(pool, &form).await.unwrap();
CommunityModerator::join(pool, &form).await?;
// Make sure that they come back as a mod in the list
let comments = CommentQuery {
@ -824,19 +812,18 @@ mod tests {
..Default::default()
}
.list(pool)
.await
.unwrap();
.await?;
assert_eq!(comments[1].creator.name, "sara");
assert!(comments[1].creator_is_moderator);
assert!(!comments[0].creator_is_moderator);
cleanup(data, pool).await;
cleanup(data, pool).await
}
#[tokio::test]
#[serial]
async fn test_creator_is_admin() {
async fn test_creator_is_admin() -> LemmyResult<()> {
let pool = &build_db_pool_for_tests().await;
let pool = &mut pool.into();
let data = init_data(pool).await;
@ -846,8 +833,7 @@ mod tests {
..Default::default()
}
.list(pool)
.await
.unwrap();
.await?;
// Timmy is an admin, and make sure that field is true
assert_eq!(comments[0].creator.name, "timmy");
@ -857,12 +843,12 @@ mod tests {
assert_eq!(comments[1].creator.name, "sara");
assert!(!comments[1].creator_is_admin);
cleanup(data, pool).await;
cleanup(data, pool).await
}
#[tokio::test]
#[serial]
async fn test_saved_order() {
async fn test_saved_order() -> LemmyResult<()> {
let pool = &build_db_pool_for_tests().await;
let pool = &mut pool.into();
let data = init_data(pool).await;
@ -872,17 +858,13 @@ mod tests {
person_id: data.timmy_local_user_view.person.id,
comment_id: data.inserted_comment_0.id,
};
CommentSaved::save(pool, &save_comment_0_form)
.await
.unwrap();
CommentSaved::save(pool, &save_comment_0_form).await?;
let save_comment_2_form = CommentSavedForm {
person_id: data.timmy_local_user_view.person.id,
comment_id: data.inserted_comment_2.id,
};
CommentSaved::save(pool, &save_comment_2_form)
.await
.unwrap();
CommentSaved::save(pool, &save_comment_2_form).await?;
// Fetch the saved comments
let comments = CommentQuery {
@ -891,8 +873,7 @@ mod tests {
..Default::default()
}
.list(pool)
.await
.unwrap();
.await?;
// There should only be two comments
assert_eq!(2, comments.len());
@ -903,47 +884,33 @@ mod tests {
// The second comment, should be the first one saved
assert_eq!(comments[1].comment.id, data.inserted_comment_0.id);
cleanup(data, pool).await;
cleanup(data, pool).await
}
async fn cleanup(data: Data, pool: &mut DbPool<'_>) {
async fn cleanup(data: Data, pool: &mut DbPool<'_>) -> LemmyResult<()> {
CommentLike::remove(
pool,
data.timmy_local_user_view.person.id,
data.inserted_comment_0.id,
)
.await
.unwrap();
Comment::delete(pool, data.inserted_comment_0.id)
.await
.unwrap();
Comment::delete(pool, data.inserted_comment_1.id)
.await
.unwrap();
Post::delete(pool, data.inserted_post.id).await.unwrap();
Community::delete(pool, data.inserted_community.id)
.await
.unwrap();
Person::delete(pool, data.timmy_local_user_view.person.id)
.await
.unwrap();
LocalUser::delete(pool, data.timmy_local_user_view.local_user.id)
.await
.unwrap();
Person::delete(pool, data.inserted_sara_person.id)
.await
.unwrap();
Instance::delete(pool, data.inserted_instance.id)
.await
.unwrap();
.await?;
Comment::delete(pool, data.inserted_comment_0.id).await?;
Comment::delete(pool, data.inserted_comment_1.id).await?;
Post::delete(pool, data.inserted_post.id).await?;
Community::delete(pool, data.inserted_community.id).await?;
Person::delete(pool, data.timmy_local_user_view.person.id).await?;
LocalUser::delete(pool, data.timmy_local_user_view.local_user.id).await?;
Person::delete(pool, data.inserted_sara_person.id).await?;
Instance::delete(pool, data.inserted_instance.id).await?;
Ok(())
}
async fn expected_comment_view(data: &Data, pool: &mut DbPool<'_>) -> CommentView {
let agg = CommentAggregates::read(pool, data.inserted_comment_0.id)
.await
.unwrap();
CommentView {
async fn expected_comment_view(data: &Data, pool: &mut DbPool<'_>) -> LemmyResult<CommentView> {
let agg = CommentAggregates::read(pool, data.inserted_comment_0.id).await?;
Ok(CommentView {
creator_banned_from_community: false,
banned_from_community: false,
creator_is_moderator: false,
creator_is_admin: true,
my_vote: None,
@ -1050,12 +1017,12 @@ mod tests {
hot_rank: RANK_DEFAULT,
controversy_rank: 0.0,
},
}
})
}
#[tokio::test]
#[serial]
async fn local_only_instance() {
async fn local_only_instance() -> LemmyResult<()> {
let pool = &build_db_pool_for_tests().await;
let pool = &mut pool.into();
let data = init_data(pool).await;
@ -1068,15 +1035,13 @@ mod tests {
..Default::default()
},
)
.await
.unwrap();
.await?;
let unauthenticated_query = CommentQuery {
..Default::default()
}
.list(pool)
.await
.unwrap();
.await?;
assert_eq!(0, unauthenticated_query.len());
let authenticated_query = CommentQuery {
@ -1084,8 +1049,7 @@ mod tests {
..Default::default()
}
.list(pool)
.await
.unwrap();
.await?;
assert_eq!(5, authenticated_query.len());
let unauthenticated_comment = CommentView::read(pool, data.inserted_comment_0.id, None).await;
@ -1099,6 +1063,67 @@ mod tests {
.await;
assert!(authenticated_comment.is_ok());
cleanup(data, pool).await;
cleanup(data, pool).await
}
#[tokio::test]
#[serial]
async fn comment_listing_local_user_banned_from_community() -> LemmyResult<()> {
let pool = &build_db_pool_for_tests().await;
let pool = &mut pool.into();
let data = init_data(pool).await;
// Test that comment view shows if local user is blocked from community
let banned_from_comm_person = PersonInsertForm::test_form(data.inserted_instance.id, "jill");
let inserted_banned_from_comm_person = Person::create(pool, &banned_from_comm_person).await?;
let inserted_banned_from_comm_local_user = LocalUser::create(
pool,
&LocalUserInsertForm::test_form(inserted_banned_from_comm_person.id),
vec![],
)
.await?;
CommunityPersonBan::ban(
pool,
&CommunityPersonBanForm {
community_id: data.inserted_community.id,
person_id: inserted_banned_from_comm_person.id,
expires: None,
},
)
.await?;
let comment_view = CommentView::read(
pool,
data.inserted_comment_0.id,
Some(inserted_banned_from_comm_local_user.person_id),
)
.await?;
assert!(comment_view.banned_from_community);
Person::delete(pool, inserted_banned_from_comm_person.id).await?;
cleanup(data, pool).await
}
#[tokio::test]
#[serial]
async fn comment_listing_local_user_not_banned_from_community() -> LemmyResult<()> {
let pool = &build_db_pool_for_tests().await;
let pool = &mut pool.into();
let data = init_data(pool).await;
let comment_view = CommentView::read(
pool,
data.inserted_comment_0.id,
Some(data.timmy_local_user_view.person.id),
)
.await?;
assert!(!comment_view.banned_from_community);
cleanup(data, pool).await
}
}

View file

@ -77,7 +77,7 @@ impl CustomEmojiView {
}
for emoji in &mut result {
if let Some(keywords) = hash.get_mut(&emoji.custom_emoji.id) {
emoji.keywords = keywords.clone();
emoji.keywords.clone_from(keywords);
}
}
result

View file

@ -215,9 +215,9 @@ impl PostReportQuery {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::{
post_report_view::{PostReportQuery, PostReportView},
@ -262,7 +262,9 @@ mod tests {
.person_id(inserted_timmy.id)
.password_encrypted("123".to_string())
.build();
let timmy_local_user = LocalUser::create(pool, &new_local_user).await.unwrap();
let timmy_local_user = LocalUser::create(pool, &new_local_user, vec![])
.await
.unwrap();
let timmy_view = LocalUserView {
local_user: timmy_local_user,
local_user_vote_display_mode: LocalUserVoteDisplayMode::default(),

View file

@ -98,6 +98,9 @@ fn queries<'a>() -> Queries<
.field(community_actions::received_ban)
.nullable()
.is_not_null(),
community_actions::received_ban
.nullable()
.is_not_null(),
creator_community_actions
.field(community_actions::became_moderator)
.nullable()
@ -571,6 +574,8 @@ mod tests {
CommunityInsertForm,
CommunityModerator,
CommunityModeratorForm,
CommunityPersonBan,
CommunityPersonBanForm,
CommunityUpdateForm,
},
community_block::{CommunityBlock, CommunityBlockForm},
@ -584,7 +589,7 @@ mod tests {
post::{Post, PostHide, PostInsertForm, PostLike, PostLikeForm, PostRead, PostUpdateForm},
site::Site,
},
traits::{Blockable, Crud, Joinable, Likeable},
traits::{Bannable, Blockable, Crud, Joinable, Likeable},
utils::{build_db_pool, build_db_pool_for_tests, DbPool, RANK_DEFAULT},
CommunityVisibility,
SortType,
@ -636,7 +641,7 @@ mod tests {
admin: Some(true),
..LocalUserInsertForm::test_form(inserted_person.id)
};
let inserted_local_user = LocalUser::create(pool, &local_user_form).await?;
let inserted_local_user = LocalUser::create(pool, &local_user_form, vec![]).await?;
let new_bot = PersonInsertForm {
bot_account: Some(true),
@ -662,6 +667,7 @@ mod tests {
let inserted_blocked_local_user = LocalUser::create(
pool,
&LocalUserInsertForm::test_form(inserted_blocked_person.id),
vec![],
)
.await?;
@ -1433,6 +1439,7 @@ mod tests {
last_refreshed_at: inserted_person.last_refreshed_at,
},
creator_banned_from_community: false,
banned_from_community: false,
creator_is_moderator: false,
creator_is_admin: true,
community: Community {
@ -1536,4 +1543,67 @@ mod tests {
cleanup(data, pool).await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn post_listing_local_user_banned_from_community() -> LemmyResult<()> {
let pool = &build_db_pool().await?;
let pool = &mut pool.into();
let data = init_data(pool).await?;
// Test that post view shows if local user is blocked from community
let banned_from_comm_person = PersonInsertForm::test_form(data.inserted_instance.id, "jill");
let inserted_banned_from_comm_person = Person::create(pool, &banned_from_comm_person).await?;
let inserted_banned_from_comm_local_user = LocalUser::create(
pool,
&LocalUserInsertForm::test_form(inserted_banned_from_comm_person.id),
vec![],
)
.await?;
CommunityPersonBan::ban(
pool,
&CommunityPersonBanForm {
community_id: data.inserted_community.id,
person_id: inserted_banned_from_comm_person.id,
expires: None,
},
)
.await?;
let post_view = PostView::read(
pool,
data.inserted_post.id,
Some(inserted_banned_from_comm_local_user.person_id),
false,
)
.await?;
assert!(post_view.banned_from_community);
Person::delete(pool, inserted_banned_from_comm_person.id).await?;
cleanup(data, pool).await
}
#[tokio::test]
#[serial]
async fn post_listing_local_user_not_banned_from_community() -> LemmyResult<()> {
let pool = &build_db_pool().await?;
let pool = &mut pool.into();
let data = init_data(pool).await?;
let post_view = PostView::read(
pool,
data.inserted_post.id,
Some(data.local_user_view.person.id),
false,
)
.await?;
assert!(!post_view.banned_from_community);
cleanup(data, pool).await
}
}

View file

@ -106,9 +106,9 @@ impl PrivateMessageReportQuery {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::private_message_report_view::PrivateMessageReportQuery;
use lemmy_db_schema::{

View file

@ -165,9 +165,9 @@ impl PrivateMessageQuery {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::{private_message_view::PrivateMessageQuery, structs::PrivateMessageView};
use lemmy_db_schema::{

View file

@ -127,9 +127,9 @@ impl RegistrationApplicationQuery {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::registration_application_view::{
RegistrationApplicationQuery,
@ -176,7 +176,7 @@ mod tests {
.admin(Some(true))
.build();
let _inserted_timmy_local_user = LocalUser::create(pool, &timmy_local_user_form)
let _inserted_timmy_local_user = LocalUser::create(pool, &timmy_local_user_form, vec![])
.await
.unwrap();
@ -193,7 +193,7 @@ mod tests {
.password_encrypted("nada".to_string())
.build();
let inserted_sara_local_user = LocalUser::create(pool, &sara_local_user_form)
let inserted_sara_local_user = LocalUser::create(pool, &sara_local_user_form, vec![])
.await
.unwrap();
@ -224,7 +224,7 @@ mod tests {
.password_encrypted("nada".to_string())
.build();
let inserted_jess_local_user = LocalUser::create(pool, &jess_local_user_form)
let inserted_jess_local_user = LocalUser::create(pool, &jess_local_user_form, vec![])
.await
.unwrap();

View file

@ -64,6 +64,7 @@ pub struct CommentView {
pub community: Community,
pub counts: CommentAggregates,
pub creator_banned_from_community: bool,
pub banned_from_community: bool,
pub creator_is_moderator: bool,
pub creator_is_admin: bool,
pub subscribed: SubscribedType,
@ -129,6 +130,7 @@ pub struct PostView {
pub creator: Person,
pub community: Community,
pub creator_banned_from_community: bool,
pub banned_from_community: bool,
pub creator_is_moderator: bool,
pub creator_is_admin: bool,
pub counts: PostAggregates,
@ -209,5 +211,6 @@ pub struct CustomEmojiView {
/// A vote view for checking a post or comments votes.
pub struct VoteView {
pub creator: Person,
pub creator_banned_from_community: bool,
pub score: i16,
}

View file

@ -1,10 +1,17 @@
use crate::structs::VoteView;
use diesel::{result::Error, ExpressionMethods, NullableExpressionMethods, QueryDsl};
use diesel::{
result::Error,
BoolExpressionMethods,
ExpressionMethods,
JoinOnDsl,
NullableExpressionMethods,
QueryDsl,
};
use diesel_async::RunQueryDsl;
use lemmy_db_schema::{
newtypes::{CommentId, PostId},
schema::{comment_actions, person, post_actions},
utils::{action_query, get_conn, limit_and_offset, DbPool},
schema::{comment_actions, community_actions, person, post, post_actions},
utils::{action_query, actions_alias, get_conn, limit_and_offset, DbPool},aliases::creator_community_actions
};
impl VoteView {
@ -19,9 +26,12 @@ impl VoteView {
action_query(post_actions::like_score)
.inner_join(person::table)
.inner_join(post::table)
.left_join(actions_alias(creator_community_actions, post_actions::person_id, post::community_id))
.filter(post_actions::post_id.eq(post_id))
.select((
person::all_columns,
creator_community_actions.field(community_actions::received_ban).nullable().is_not_null(),
post_actions::like_score.assume_not_null(),
))
.order_by(post_actions::like_score)
@ -42,9 +52,12 @@ impl VoteView {
action_query(comment_actions::like_score)
.inner_join(person::table)
.inner_join(post::table)
.left_join(actions_alias(creator_community_actions, comment_actions::person_id, post::community_id))
.filter(comment_actions::comment_id.eq(comment_id))
.select((
person::all_columns,
creator_community_actions.field(community_actions::received_ban).nullable().is_not_null(),
comment_actions::like_score.assume_not_null(),
))
.order_by(comment_actions::like_score)
@ -56,20 +69,20 @@ impl VoteView {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::structs::VoteView;
use lemmy_db_schema::{
source::{
comment::{Comment, CommentInsertForm, CommentLike, CommentLikeForm},
community::{Community, CommunityInsertForm},
community::{Community, CommunityInsertForm, CommunityPersonBan, CommunityPersonBanForm},
instance::Instance,
person::{Person, PersonInsertForm},
post::{Post, PostInsertForm, PostLike, PostLikeForm},
},
traits::{Crud, Likeable},
traits::{Bannable, Crud, Likeable},
utils::build_db_pool_for_tests,
};
use pretty_assertions::assert_eq;
@ -145,10 +158,12 @@ mod tests {
let expected_post_vote_views = [
VoteView {
creator: inserted_sara.clone(),
creator_banned_from_community: false,
score: -1,
},
VoteView {
creator: inserted_timmy.clone(),
creator_banned_from_community: false,
score: 1,
},
];
@ -183,10 +198,12 @@ mod tests {
let expected_comment_vote_views = [
VoteView {
creator: inserted_timmy.clone(),
creator_banned_from_community: false,
score: -1,
},
VoteView {
creator: inserted_sara.clone(),
creator_banned_from_community: false,
score: 1,
},
];
@ -196,6 +213,41 @@ mod tests {
.unwrap();
assert_eq!(read_comment_vote_views, expected_comment_vote_views);
// Ban timmy from that community
let ban_timmy_form = CommunityPersonBanForm {
community_id: inserted_community.id,
person_id: inserted_timmy.id,
expires: None,
};
CommunityPersonBan::ban(pool, &ban_timmy_form)
.await
.unwrap();
// Make sure creator_banned_from_community is true
let read_comment_vote_views_after_ban =
VoteView::list_for_comment(pool, inserted_comment.id, None, None)
.await
.unwrap();
assert!(
read_comment_vote_views_after_ban
.first()
.unwrap()
.creator_banned_from_community
);
let read_post_vote_views_after_ban =
VoteView::list_for_post(pool, inserted_post.id, None, None)
.await
.unwrap();
assert!(
read_post_vote_views_after_ban
.get(1)
.unwrap()
.creator_banned_from_community
);
// Cleanup
Instance::delete(pool, inserted_instance.id).await.unwrap();
}

View file

@ -74,6 +74,7 @@ fn queries<'a>() -> Queries<
.field(community_actions::received_ban)
.nullable()
.is_not_null(),
community_actions::received_ban.nullable().is_not_null(),
creator_community_actions
.field(community_actions::became_moderator)
.nullable()

View file

@ -221,9 +221,9 @@ impl<'a> CommunityQuery<'a> {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::{community_view::CommunityQuery, structs::CommunityView};
use lemmy_db_schema::{
@ -267,7 +267,9 @@ mod tests {
.person_id(inserted_person.id)
.password_encrypted(String::new())
.build();
let local_user = LocalUser::create(pool, &local_user_form).await.unwrap();
let local_user = LocalUser::create(pool, &local_user_form, vec![])
.await
.unwrap();
let new_community = CommunityInsertForm::builder()
.name("test_community_3".to_string())

View file

@ -74,6 +74,7 @@ fn queries<'a>() -> Queries<
.field(community_actions::received_ban)
.nullable()
.is_not_null(),
community_actions::received_ban.nullable().is_not_null(),
creator_community_actions
.field(community_actions::became_moderator)
.nullable()

View file

@ -163,9 +163,9 @@ impl PersonQuery {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use super::*;
use diesel::NotFound;
@ -204,7 +204,7 @@ mod tests {
.person_id(alice.id)
.password_encrypted(String::new())
.build();
let alice_local_user = LocalUser::create(pool, &alice_local_user_form).await?;
let alice_local_user = LocalUser::create(pool, &alice_local_user_form, vec![]).await?;
let bob_form = PersonInsertForm::builder()
.name("bob".to_string())
@ -218,7 +218,7 @@ mod tests {
.person_id(bob.id)
.password_encrypted(String::new())
.build();
let bob_local_user = LocalUser::create(pool, &bob_local_user_form).await?;
let bob_local_user = LocalUser::create(pool, &bob_local_user_form, vec![]).await?;
Ok(Data {
alice,

View file

@ -108,6 +108,7 @@ pub struct PersonMentionView {
pub recipient: Person,
pub counts: CommentAggregates,
pub creator_banned_from_community: bool,
pub banned_from_community: bool,
pub creator_is_moderator: bool,
pub creator_is_admin: bool,
pub subscribed: SubscribedType,
@ -131,6 +132,7 @@ pub struct CommentReplyView {
pub recipient: Person,
pub counts: CommentAggregates,
pub creator_banned_from_community: bool,
pub banned_from_community: bool,
pub creator_is_moderator: bool,
pub creator_is_admin: bool,
pub subscribed: SubscribedType,

View file

@ -46,17 +46,17 @@ static SAVE_STATE_EVERY_TIME: Duration = Duration::from_secs(60);
/// this delay limits the maximum time until the follow actually results in activities from that community id being sent to that inbox url.
/// This delay currently needs to not be too small because the DB load is currently fairly high because of the current structure of storing inboxes for every person, not having a separate list of shared_inboxes, and the architecture of having every instance queue be fully separate.
/// (see https://github.com/LemmyNet/lemmy/issues/3958)
static FOLLOW_ADDITIONS_RECHECK_DELAY: Lazy<chrono::Duration> = Lazy::new(|| {
static FOLLOW_ADDITIONS_RECHECK_DELAY: Lazy<chrono::TimeDelta> = Lazy::new(|| {
if *LEMMY_TEST_FAST_FEDERATION {
chrono::Duration::seconds(1)
chrono::TimeDelta::try_seconds(1).expect("TimeDelta out of bounds")
} else {
chrono::Duration::minutes(2)
chrono::TimeDelta::try_minutes(2).expect("TimeDelta out of bounds")
}
});
/// The same as FOLLOW_ADDITIONS_RECHECK_DELAY, but triggering when the last person on an instance unfollows a specific remote community.
/// This is expected to happen pretty rarely and updating it in a timely manner is not too important.
static FOLLOW_REMOVALS_RECHECK_DELAY: Lazy<chrono::Duration> =
Lazy::new(|| chrono::Duration::hours(1));
static FOLLOW_REMOVALS_RECHECK_DELAY: Lazy<chrono::TimeDelta> =
Lazy::new(|| chrono::TimeDelta::try_hours(1).expect("TimeDelta out of bounds"));
pub(crate) struct InstanceWorker {
instance: Instance,
// load site lazily because if an instance is first seen due to being on allowlist,
@ -332,7 +332,8 @@ impl InstanceWorker {
instance_id: InstanceId,
last_fetch: DateTime<Utc>,
) -> Result<(HashMap<CommunityId, HashSet<Url>>, DateTime<Utc>)> {
let new_last_fetch = Utc::now() - chrono::Duration::seconds(10); // update to time before fetch to ensure overlap. subtract 10s to ensure overlap even if published date is not exact
let new_last_fetch =
Utc::now() - chrono::TimeDelta::try_seconds(10).expect("TimeDelta out of bounds"); // update to time before fetch to ensure overlap. subtract 10s to ensure overlap even if published date is not exact
Ok((
CommunityFollowerView::get_instance_followed_community_inboxes(pool, instance_id, last_fetch)
.await?

View file

@ -74,11 +74,11 @@ uuid = { workspace = true, features = ["serde", "v4"], optional = true }
rosetta-i18n = { workspace = true, optional = true }
tokio = { workspace = true, optional = true }
urlencoding = { workspace = true, optional = true }
openssl = { version = "0.10.63", optional = true }
openssl = { version = "0.10.64", optional = true }
html2text = { version = "0.6.0", optional = true }
deser-hjson = { version = "2.2.4", optional = true }
smart-default = { version = "0.7.1", optional = true }
lettre = { version = "0.11.3", features = [
lettre = { version = "0.11.4", features = [
"tokio1",
"tokio1-native-tls",
], optional = true }

View file

@ -221,9 +221,9 @@ fn parse_ip(addr: &str) -> Option<IpAddr> {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
#[test]
fn test_parse_ip() {

View file

@ -158,7 +158,7 @@ impl<K: Eq + Hash, C: MapLevel> MapLevel for Map<K, C> {
// Evaluated if `some_children_remaining` is false
let total_has_refill_in_future = || {
group.total.into_iter().all(|(action_type, bucket)| {
group.total.into_iter().any(|(action_type, bucket)| {
#[allow(clippy::indexing_slicing)]
let config = configs[action_type];
bucket.update(now, config).tokens != config.capacity
@ -306,9 +306,9 @@ fn split_ipv6(ip: Ipv6Addr) -> ([u8; 6], u8, u8) {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use super::{ActionType, BucketConfig, InstantSecs, RateLimitState, RateLimitedGroup};
use pretty_assertions::assert_eq;
@ -416,5 +416,23 @@ mod tests {
rate_limiter.remove_full_buckets(now);
assert!(rate_limiter.ipv4_buckets.is_empty());
assert!(rate_limiter.ipv6_buckets.is_empty());
// `remove full buckets` should not remove empty buckets
let ip = "1.1.1.1".parse().unwrap();
// empty the bucket with 2 requests
assert!(rate_limiter.check(ActionType::Post, ip, now));
assert!(rate_limiter.check(ActionType::Post, ip, now));
rate_limiter.remove_full_buckets(now);
assert!(!rate_limiter.ipv4_buckets.is_empty());
// `remove full buckets` should not remove partial buckets
now.secs += 2;
let ip = "1.1.1.1".parse().unwrap();
// Only make one request, so bucket still has 1 token
assert!(rate_limiter.check(ActionType::Post, ip, now));
rate_limiter.remove_full_buckets(now);
assert!(!rate_limiter.ipv4_buckets.is_empty());
}
}

View file

@ -107,9 +107,9 @@ pub fn markdown_check_for_blocked_urls(text: &str, blocklist: &RegexSet) -> Lemm
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use super::*;
use pretty_assertions::assert_eq;

View file

@ -134,9 +134,9 @@ pub fn add(markdown_parser: &mut MarkdownIt) {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::utils::markdown::spoiler_rule::add;
use markdown_it::MarkdownIt;

View file

@ -34,9 +34,9 @@ pub fn scrape_text_for_mentions(text: &str) -> Vec<MentionData> {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod test {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::utils::mention::scrape_text_for_mentions;
use pretty_assertions::assert_eq;

View file

@ -64,9 +64,9 @@ pub(crate) fn slurs_vec_to_str(slurs: &[&str]) -> String {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod test {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::utils::slurs::{remove_slurs, slur_check, slurs_vec_to_str};
use pretty_assertions::assert_eq;

View file

@ -327,9 +327,9 @@ pub fn check_urls_are_valid(urls: &Vec<String>) -> LemmyResult<Vec<String>> {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::{
error::LemmyErrorType,

View file

@ -19,6 +19,7 @@ FROM --platform=${BUILDPLATFORM} ${AMD_BUILDER_IMAGE} AS build-amd64
ARG CARGO_BUILD_FEATURES
ARG RUST_RELEASE_MODE
ARG RUSTFLAGS
WORKDIR /lemmy
@ -48,6 +49,7 @@ FROM --platform=linux/amd64 ${ARM_BUILDER_IMAGE} AS build-arm64
ARG RUST_RELEASE_MODE
ARG CARGO_BUILD_FEATURES
ARG RUSTFLAGS
WORKDIR /home/lemmy/src
USER 10001:10001

View file

@ -29,6 +29,7 @@ use lemmy_api::{
get_captcha::get_captcha,
list_banned::list_banned_users,
list_logins::list_logins,
list_media::list_media,
login::login,
logout::logout,
notifications::{
@ -71,6 +72,7 @@ use lemmy_api::{
block::block_instance,
federated_instances::get_federated_instances,
leave_admin::leave_admin,
list_all_media::list_all_media,
mod_log::get_mod_log,
purge::{
comment::purge_comment,
@ -282,6 +284,12 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) {
.wrap(rate_limit.import_user_settings())
.route(web::post().to(import_settings)),
)
// TODO, all the current account related actions under /user need to get moved here eventually
.service(
web::scope("/account")
.wrap(rate_limit.message())
.route("/list_media", web::get().to(list_media)),
)
// User actions
.service(
web::scope("/user")
@ -339,6 +347,7 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) {
"/registration_application/approve",
web::put().to(approve_registration_application),
)
.route("/list_all_media", web::get().to(list_all_media))
.service(
web::scope("/purge")
.route("/person", web::post().to(purge_person))

View file

@ -475,7 +475,7 @@ async fn initialize_local_site_2022_10_10(
.email(setup.admin_email.clone())
.admin(Some(true))
.build();
LocalUser::create(pool, &local_user_form).await?;
LocalUser::create(pool, &local_user_form, vec![]).await?;
};
// Add an entry for the site table

View file

@ -517,9 +517,9 @@ async fn update_instance_software(
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use lemmy_routes::nodeinfo::NodeInfo;
use pretty_assertions::assert_eq;

View file

@ -98,9 +98,9 @@ where
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use super::*;
use actix_web::test::TestRequest;
@ -155,7 +155,9 @@ mod tests {
.password_encrypted("123456".to_string())
.build();
let inserted_local_user = LocalUser::create(pool, &local_user_form).await.unwrap();
let inserted_local_user = LocalUser::create(pool, &local_user_form, vec![])
.await
.unwrap();
let req = TestRequest::default().to_http_request();
let jwt = Claims::generate(inserted_local_user.id, req, &context)