Merge branch 'main' into nested-comments-stack-overflow

This commit is contained in:
Felix Ableitner 2024-09-30 16:05:24 +02:00
commit 84a18ec0a6
265 changed files with 4894 additions and 5071 deletions

View File

@ -2,7 +2,7 @@
# See https://github.com/woodpecker-ci/woodpecker/issues/1677 # See https://github.com/woodpecker-ci/woodpecker/issues/1677
variables: variables:
- &rust_image "rust:1.80" - &rust_image "rust:1.81"
- &rust_nightly_image "rustlang/rust:nightly" - &rust_nightly_image "rustlang/rust:nightly"
- &install_pnpm "corepack enable pnpm" - &install_pnpm "corepack enable pnpm"
- &install_binstall "wget -O- https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-x86_64-unknown-linux-musl.tgz | tar -xvz -C /usr/local/cargo/bin" - &install_binstall "wget -O- https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-x86_64-unknown-linux-musl.tgz | tar -xvz -C /usr/local/cargo/bin"

1918
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -36,7 +36,6 @@ opt-level = "z" # Optimize for size.
debug = 0 debug = 0
[features] [features]
embed-pictrs = ["pict-rs"]
json-log = ["tracing-subscriber/json"] json-log = ["tracing-subscriber/json"]
default = [] default = []
@ -132,7 +131,6 @@ anyhow = { version = "1.0.86", features = [
"backtrace", "backtrace",
] } # backtrace is on by default on nightly, but not stable rust ] } # backtrace is on by default on nightly, but not stable rust
diesel_ltree = "0.3.1" diesel_ltree = "0.3.1"
typed-builder = "0.19.1"
serial_test = "3.1.1" serial_test = "3.1.1"
tokio = { version = "1.39.2", features = ["full"] } tokio = { version = "1.39.2", features = ["full"] }
regex = "1.10.5" regex = "1.10.5"
@ -182,7 +180,6 @@ reqwest-middleware = { workspace = true }
reqwest-tracing = { workspace = true } reqwest-tracing = { workspace = true }
clokwerk = { workspace = true } clokwerk = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
pict-rs = { version = "0.5.16", optional = true }
rustls = { workspace = true } rustls = { workspace = true }
tokio.workspace = true tokio.workspace = true
actix-cors = "0.7.0" actix-cors = "0.7.0"

View File

@ -21,16 +21,16 @@
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^29.5.12", "@types/jest": "^29.5.12",
"@types/node": "^22.0.2", "@types/node": "^22.3.0",
"@typescript-eslint/eslint-plugin": "^8.0.0", "@typescript-eslint/eslint-plugin": "^8.1.0",
"@typescript-eslint/parser": "^8.0.0", "@typescript-eslint/parser": "^8.1.0",
"eslint": "^9.8.0", "eslint": "^9.9.0",
"eslint-plugin-prettier": "^5.1.3", "eslint-plugin-prettier": "^5.1.3",
"jest": "^29.5.0", "jest": "^29.5.0",
"lemmy-js-client": "0.19.5-alpha.1", "lemmy-js-client": "0.20.0-alpha.11",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"ts-jest": "^29.1.0", "ts-jest": "^29.1.0",
"typescript": "^5.5.4", "typescript": "^5.5.4",
"typescript-eslint": "^8.0.0" "typescript-eslint": "^8.1.0"
} }
} }

View File

@ -12,38 +12,38 @@ importers:
specifier: ^29.5.12 specifier: ^29.5.12
version: 29.5.12 version: 29.5.12
'@types/node': '@types/node':
specifier: ^22.0.2 specifier: ^22.3.0
version: 22.5.1 version: 22.3.0
'@typescript-eslint/eslint-plugin': '@typescript-eslint/eslint-plugin':
specifier: ^8.0.0 specifier: ^8.1.0
version: 8.0.0(@typescript-eslint/parser@8.0.0(eslint@9.9.1)(typescript@5.5.4))(eslint@9.9.1)(typescript@5.5.4) version: 8.1.0(@typescript-eslint/parser@8.1.0(eslint@9.9.0)(typescript@5.5.4))(eslint@9.9.0)(typescript@5.5.4)
'@typescript-eslint/parser': '@typescript-eslint/parser':
specifier: ^8.0.0 specifier: ^8.1.0
version: 8.0.0(eslint@9.9.1)(typescript@5.5.4) version: 8.1.0(eslint@9.9.0)(typescript@5.5.4)
eslint: eslint:
specifier: ^9.8.0 specifier: ^9.9.0
version: 9.9.1 version: 9.9.0
eslint-plugin-prettier: eslint-plugin-prettier:
specifier: ^5.1.3 specifier: ^5.1.3
version: 5.2.1(eslint@9.9.1)(prettier@3.3.3) version: 5.2.1(eslint@9.9.0)(prettier@3.3.3)
jest: jest:
specifier: ^29.5.0 specifier: ^29.5.0
version: 29.7.0(@types/node@22.5.1) version: 29.7.0(@types/node@22.3.0)
lemmy-js-client: lemmy-js-client:
specifier: 0.19.5-alpha.1 specifier: 0.20.0-alpha.11
version: 0.19.5-alpha.1 version: 0.20.0-alpha.11
prettier: prettier:
specifier: ^3.2.5 specifier: ^3.2.5
version: 3.3.3 version: 3.3.3
ts-jest: ts-jest:
specifier: ^29.1.0 specifier: ^29.1.0
version: 29.2.5(@babel/core@7.23.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(jest@29.7.0(@types/node@22.5.1))(typescript@5.5.4) version: 29.2.4(@babel/core@7.23.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(jest@29.7.0(@types/node@22.3.0))(typescript@5.5.4)
typescript: typescript:
specifier: ^5.5.4 specifier: ^5.5.4
version: 5.5.4 version: 5.5.4
typescript-eslint: typescript-eslint:
specifier: ^8.0.0 specifier: ^8.1.0
version: 8.0.0(eslint@9.9.1)(typescript@5.5.4) version: 8.1.0(eslint@9.9.0)(typescript@5.5.4)
packages: packages:
@ -228,16 +228,16 @@ packages:
resolution: {integrity: sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==} resolution: {integrity: sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==}
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
'@eslint/config-array@0.18.0': '@eslint/config-array@0.17.1':
resolution: {integrity: sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==} resolution: {integrity: sha512-BlYOpej8AQ8Ev9xVqroV7a02JK3SkBAaN9GfMMH9W6Ch8FlQlkjGw4Ir7+FgYwfirivAf4t+GtzuAxqfukmISA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/eslintrc@3.1.0': '@eslint/eslintrc@3.1.0':
resolution: {integrity: sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==} resolution: {integrity: sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/js@9.9.1': '@eslint/js@9.9.0':
resolution: {integrity: sha512-xIDQRsfg5hNBqHz04H1R3scSVwmI+KUbqjsQKHKQ1DAUSaUjYPReZZmS/5PNiKu1fUvzDd6H7DEDKACSEhu+TQ==} resolution: {integrity: sha512-hhetes6ZHP3BlXLxmd8K2SNgkhNSi+UcecbnwWKwpP7kyi/uC75DJ1lOOBO3xrC4jyojtGE3YxKZPHfk4yrgug==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/object-schema@2.1.4': '@eslint/object-schema@2.1.4':
@ -396,8 +396,8 @@ packages:
'@types/jest@29.5.12': '@types/jest@29.5.12':
resolution: {integrity: sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==} resolution: {integrity: sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==}
'@types/node@22.5.1': '@types/node@22.3.0':
resolution: {integrity: sha512-KkHsxej0j9IW1KKOOAA/XBA0z08UFSrRQHErzEfA3Vgq57eXIMYboIlHJuYIfd+lwCQjtKqUu3UnmKbtUc9yRw==} resolution: {integrity: sha512-nrWpWVaDZuaVc5X84xJ0vNrLvomM205oQyLsRt7OHNZbSHslcWsvgFR7O7hire2ZonjLrWBbedmotmIlJDVd6g==}
'@types/stack-utils@2.0.3': '@types/stack-utils@2.0.3':
resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==}
@ -408,8 +408,8 @@ packages:
'@types/yargs@17.0.32': '@types/yargs@17.0.32':
resolution: {integrity: sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==} resolution: {integrity: sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==}
'@typescript-eslint/eslint-plugin@8.0.0': '@typescript-eslint/eslint-plugin@8.1.0':
resolution: {integrity: sha512-STIZdwEQRXAHvNUS6ILDf5z3u95Gc8jzywunxSNqX00OooIemaaNIA0vEgynJlycL5AjabYLLrIyHd4iazyvtg==} resolution: {integrity: sha512-LlNBaHFCEBPHyD4pZXb35mzjGkuGKXU5eeCA1SxvHfiRES0E82dOounfVpL4DCqYvJEKab0bZIA0gCRpdLKkCw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies: peerDependencies:
'@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0
@ -419,8 +419,8 @@ packages:
typescript: typescript:
optional: true optional: true
'@typescript-eslint/parser@8.0.0': '@typescript-eslint/parser@8.1.0':
resolution: {integrity: sha512-pS1hdZ+vnrpDIxuFXYQpLTILglTjSYJ9MbetZctrUawogUsPdz31DIIRZ9+rab0LhYNTsk88w4fIzVheiTbWOQ==} resolution: {integrity: sha512-U7iTAtGgJk6DPX9wIWPPOlt1gO57097G06gIcl0N0EEnNw8RGD62c+2/DiP/zL7KrkqnnqF7gtFGR7YgzPllTA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies: peerDependencies:
eslint: ^8.57.0 || ^9.0.0 eslint: ^8.57.0 || ^9.0.0
@ -429,12 +429,12 @@ packages:
typescript: typescript:
optional: true optional: true
'@typescript-eslint/scope-manager@8.0.0': '@typescript-eslint/scope-manager@8.1.0':
resolution: {integrity: sha512-V0aa9Csx/ZWWv2IPgTfY7T4agYwJyILESu/PVqFtTFz9RIS823mAze+NbnBI8xiwdX3iqeQbcTYlvB04G9wyQw==} resolution: {integrity: sha512-DsuOZQji687sQUjm4N6c9xABJa7fjvfIdjqpSIIVOgaENf2jFXiM9hIBZOL3hb6DHK9Nvd2d7zZnoMLf9e0OtQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/type-utils@8.0.0': '@typescript-eslint/type-utils@8.1.0':
resolution: {integrity: sha512-mJAFP2mZLTBwAn5WI4PMakpywfWFH5nQZezUQdSKV23Pqo6o9iShQg1hP2+0hJJXP2LnZkWPphdIq4juYYwCeg==} resolution: {integrity: sha512-oLYvTxljVvsMnldfl6jIKxTaU7ok7km0KDrwOt1RHYu6nxlhN3TIx8k5Q52L6wR33nOwDgM7VwW1fT1qMNfFIA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies: peerDependencies:
typescript: '*' typescript: '*'
@ -442,12 +442,12 @@ packages:
typescript: typescript:
optional: true optional: true
'@typescript-eslint/types@8.0.0': '@typescript-eslint/types@8.1.0':
resolution: {integrity: sha512-wgdSGs9BTMWQ7ooeHtu5quddKKs5Z5dS+fHLbrQI+ID0XWJLODGMHRfhwImiHoeO2S5Wir2yXuadJN6/l4JRxw==} resolution: {integrity: sha512-q2/Bxa0gMOu/2/AKALI0tCKbG2zppccnRIRCW6BaaTlRVaPKft4oVYPp7WOPpcnsgbr0qROAVCVKCvIQ0tbWog==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/typescript-estree@8.0.0': '@typescript-eslint/typescript-estree@8.1.0':
resolution: {integrity: sha512-5b97WpKMX+Y43YKi4zVcCVLtK5F98dFls3Oxui8LbnmRsseKenbbDinmvxrWegKDMmlkIq/XHuyy0UGLtpCDKg==} resolution: {integrity: sha512-NTHhmufocEkMiAord/g++gWKb0Fr34e9AExBRdqgWdVBaKoei2dIyYKD9Q0jBnvfbEA5zaf8plUFMUH6kQ0vGg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies: peerDependencies:
typescript: '*' typescript: '*'
@ -455,14 +455,14 @@ packages:
typescript: typescript:
optional: true optional: true
'@typescript-eslint/utils@8.0.0': '@typescript-eslint/utils@8.1.0':
resolution: {integrity: sha512-k/oS/A/3QeGLRvOWCg6/9rATJL5rec7/5s1YmdS0ZU6LHveJyGFwBvLhSRBv6i9xaj7etmosp+l+ViN1I9Aj/Q==} resolution: {integrity: sha512-ypRueFNKTIFwqPeJBfeIpxZ895PQhNyH4YID6js0UoBImWYoSjBsahUn9KMiJXh94uOjVBgHD9AmkyPsPnFwJA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies: peerDependencies:
eslint: ^8.57.0 || ^9.0.0 eslint: ^8.57.0 || ^9.0.0
'@typescript-eslint/visitor-keys@8.0.0': '@typescript-eslint/visitor-keys@8.1.0':
resolution: {integrity: sha512-oN0K4nkHuOyF3PVMyETbpP5zp6wfyOvm7tWhTMfoqxSSsPmJIh6JNASuZDlODE8eE+0EB9uar+6+vxr9DBTYOA==} resolution: {integrity: sha512-ba0lNI19awqZ5ZNKh6wCModMwoZs457StTebQ0q1NP58zSi2F6MOZRXwfKZy+jB78JNJ/WH8GSh2IQNzXX8Nag==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
acorn-jsx@5.3.2: acorn-jsx@5.3.2:
@ -512,8 +512,8 @@ packages:
resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
engines: {node: '>=8'} engines: {node: '>=8'}
async@3.2.6: async@3.2.5:
resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==}
babel-jest@29.7.0: babel-jest@29.7.0:
resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==}
@ -741,8 +741,8 @@ packages:
resolution: {integrity: sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==} resolution: {integrity: sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
eslint@9.9.1: eslint@9.9.0:
resolution: {integrity: sha512-dHvhrbfr4xFQ9/dq+jcVneZMyRYLjggWjk6RVsIiHsP8Rz6yZ8LvZ//iU4TrZF+SXWG+JkNF2OyiZRvzgRDqMg==} resolution: {integrity: sha512-JfiKJrbx0506OEerjK2Y1QlldtBxkAlLxT5OEcRF8uaQ86noDe2k31Vw9rnSWv+MXZHj7OOUV/dA0AhdLFcyvA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
@ -916,10 +916,6 @@ packages:
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
engines: {node: '>=10.17.0'} engines: {node: '>=10.17.0'}
ignore@5.3.1:
resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
engines: {node: '>= 4'}
ignore@5.3.2: ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'} engines: {node: '>= 4'}
@ -1179,8 +1175,8 @@ packages:
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
engines: {node: '>=6'} engines: {node: '>=6'}
lemmy-js-client@0.19.5-alpha.1: lemmy-js-client@0.20.0-alpha.11:
resolution: {integrity: sha512-GOhaiTQzrpwdmc3DFYemT2SmNmpuQJe2BWUms9QOzdYlkA1WZ0uu7axPE3s+T5OOxfy7K9Q2gsLe72dcVSlffw==} resolution: {integrity: sha512-iRSG4xHMjPDIreQqVIoJ5JrMY71uk07G0Zbgyf068xKbib22J3+i1x/XgCTs6tiHlqTnw1Ig/KRq7p7qJoA4uw==}
leven@3.1.0: leven@3.1.0:
resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
@ -1412,6 +1408,11 @@ packages:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true hasBin: true
semver@7.6.2:
resolution: {integrity: sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==}
engines: {node: '>=10'}
hasBin: true
semver@7.6.3: semver@7.6.3:
resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -1517,8 +1518,8 @@ packages:
peerDependencies: peerDependencies:
typescript: '>=4.2.0' typescript: '>=4.2.0'
ts-jest@29.2.5: ts-jest@29.2.4:
resolution: {integrity: sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==} resolution: {integrity: sha512-3d6tgDyhCI29HlpwIq87sNuI+3Q6GLTTCeYRHCs7vDz+/3GCMwEtV9jezLyl4ZtnBgx00I7hm8PCP8cTksMGrw==}
engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
@ -1556,8 +1557,8 @@ packages:
resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==}
engines: {node: '>=10'} engines: {node: '>=10'}
typescript-eslint@8.0.0: typescript-eslint@8.1.0:
resolution: {integrity: sha512-yQWBJutWL1PmpmDddIOl9/Mi6vZjqNCjqSGBMQ4vsc2Aiodk0SnbQQWPXbSy0HNuKCuGkw1+u4aQ2mO40TdhDQ==} resolution: {integrity: sha512-prB2U3jXPJLpo1iVLN338Lvolh6OrcCZO+9Yv6AR+tvegPPptYCDBIHiEEUdqRi8gAv2bXNKfMUrgAd2ejn/ow==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies: peerDependencies:
typescript: '*' typescript: '*'
@ -1570,8 +1571,8 @@ packages:
engines: {node: '>=14.17'} engines: {node: '>=14.17'}
hasBin: true hasBin: true
undici-types@6.19.8: undici-types@6.18.2:
resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} resolution: {integrity: sha512-5ruQbENj95yDYJNS3TvcaxPMshV7aizdv/hWYjGIKoANWKjhWNBsr2YEuYZKodQulB1b8l7ILOuDQep3afowQQ==}
update-browserslist-db@1.0.13: update-browserslist-db@1.0.13:
resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==}
@ -1834,14 +1835,14 @@ snapshots:
'@bcoe/v8-coverage@0.2.3': {} '@bcoe/v8-coverage@0.2.3': {}
'@eslint-community/eslint-utils@4.4.0(eslint@9.9.1)': '@eslint-community/eslint-utils@4.4.0(eslint@9.9.0)':
dependencies: dependencies:
eslint: 9.9.1 eslint: 9.9.0
eslint-visitor-keys: 3.4.3 eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.11.0': {} '@eslint-community/regexpp@4.11.0': {}
'@eslint/config-array@0.18.0': '@eslint/config-array@0.17.1':
dependencies: dependencies:
'@eslint/object-schema': 2.1.4 '@eslint/object-schema': 2.1.4
debug: 4.3.6 debug: 4.3.6
@ -1863,7 +1864,7 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@eslint/js@9.9.1': {} '@eslint/js@9.9.0': {}
'@eslint/object-schema@2.1.4': {} '@eslint/object-schema@2.1.4': {}
@ -1884,7 +1885,7 @@ snapshots:
'@jest/console@29.7.0': '@jest/console@29.7.0':
dependencies: dependencies:
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 22.5.1 '@types/node': 22.3.0
chalk: 4.1.2 chalk: 4.1.2
jest-message-util: 29.7.0 jest-message-util: 29.7.0
jest-util: 29.7.0 jest-util: 29.7.0
@ -1897,14 +1898,14 @@ snapshots:
'@jest/test-result': 29.7.0 '@jest/test-result': 29.7.0
'@jest/transform': 29.7.0 '@jest/transform': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 22.5.1 '@types/node': 22.3.0
ansi-escapes: 4.3.2 ansi-escapes: 4.3.2
chalk: 4.1.2 chalk: 4.1.2
ci-info: 3.9.0 ci-info: 3.9.0
exit: 0.1.2 exit: 0.1.2
graceful-fs: 4.2.11 graceful-fs: 4.2.11
jest-changed-files: 29.7.0 jest-changed-files: 29.7.0
jest-config: 29.7.0(@types/node@22.5.1) jest-config: 29.7.0(@types/node@22.3.0)
jest-haste-map: 29.7.0 jest-haste-map: 29.7.0
jest-message-util: 29.7.0 jest-message-util: 29.7.0
jest-regex-util: 29.6.3 jest-regex-util: 29.6.3
@ -1929,7 +1930,7 @@ snapshots:
dependencies: dependencies:
'@jest/fake-timers': 29.7.0 '@jest/fake-timers': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 22.5.1 '@types/node': 22.3.0
jest-mock: 29.7.0 jest-mock: 29.7.0
'@jest/expect-utils@29.7.0': '@jest/expect-utils@29.7.0':
@ -1947,7 +1948,7 @@ snapshots:
dependencies: dependencies:
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@sinonjs/fake-timers': 10.3.0 '@sinonjs/fake-timers': 10.3.0
'@types/node': 22.5.1 '@types/node': 22.3.0
jest-message-util: 29.7.0 jest-message-util: 29.7.0
jest-mock: 29.7.0 jest-mock: 29.7.0
jest-util: 29.7.0 jest-util: 29.7.0
@ -1969,7 +1970,7 @@ snapshots:
'@jest/transform': 29.7.0 '@jest/transform': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@jridgewell/trace-mapping': 0.3.22 '@jridgewell/trace-mapping': 0.3.22
'@types/node': 22.5.1 '@types/node': 22.3.0
chalk: 4.1.2 chalk: 4.1.2
collect-v8-coverage: 1.0.2 collect-v8-coverage: 1.0.2
exit: 0.1.2 exit: 0.1.2
@ -2039,7 +2040,7 @@ snapshots:
'@jest/schemas': 29.6.3 '@jest/schemas': 29.6.3
'@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-lib-coverage': 2.0.6
'@types/istanbul-reports': 3.0.4 '@types/istanbul-reports': 3.0.4
'@types/node': 22.5.1 '@types/node': 22.3.0
'@types/yargs': 17.0.32 '@types/yargs': 17.0.32
chalk: 4.1.2 chalk: 4.1.2
@ -2107,7 +2108,7 @@ snapshots:
'@types/graceful-fs@4.1.9': '@types/graceful-fs@4.1.9':
dependencies: dependencies:
'@types/node': 22.5.1 '@types/node': 22.3.0
'@types/istanbul-lib-coverage@2.0.6': {} '@types/istanbul-lib-coverage@2.0.6': {}
@ -2124,9 +2125,9 @@ snapshots:
expect: 29.7.0 expect: 29.7.0
pretty-format: 29.7.0 pretty-format: 29.7.0
'@types/node@22.5.1': '@types/node@22.3.0':
dependencies: dependencies:
undici-types: 6.19.8 undici-types: 6.18.2
'@types/stack-utils@2.0.3': {} '@types/stack-utils@2.0.3': {}
@ -2136,17 +2137,17 @@ snapshots:
dependencies: dependencies:
'@types/yargs-parser': 21.0.3 '@types/yargs-parser': 21.0.3
'@typescript-eslint/eslint-plugin@8.0.0(@typescript-eslint/parser@8.0.0(eslint@9.9.1)(typescript@5.5.4))(eslint@9.9.1)(typescript@5.5.4)': '@typescript-eslint/eslint-plugin@8.1.0(@typescript-eslint/parser@8.1.0(eslint@9.9.0)(typescript@5.5.4))(eslint@9.9.0)(typescript@5.5.4)':
dependencies: dependencies:
'@eslint-community/regexpp': 4.11.0 '@eslint-community/regexpp': 4.11.0
'@typescript-eslint/parser': 8.0.0(eslint@9.9.1)(typescript@5.5.4) '@typescript-eslint/parser': 8.1.0(eslint@9.9.0)(typescript@5.5.4)
'@typescript-eslint/scope-manager': 8.0.0 '@typescript-eslint/scope-manager': 8.1.0
'@typescript-eslint/type-utils': 8.0.0(eslint@9.9.1)(typescript@5.5.4) '@typescript-eslint/type-utils': 8.1.0(eslint@9.9.0)(typescript@5.5.4)
'@typescript-eslint/utils': 8.0.0(eslint@9.9.1)(typescript@5.5.4) '@typescript-eslint/utils': 8.1.0(eslint@9.9.0)(typescript@5.5.4)
'@typescript-eslint/visitor-keys': 8.0.0 '@typescript-eslint/visitor-keys': 8.1.0
eslint: 9.9.1 eslint: 9.9.0
graphemer: 1.4.0 graphemer: 1.4.0
ignore: 5.3.1 ignore: 5.3.2
natural-compare: 1.4.0 natural-compare: 1.4.0
ts-api-utils: 1.3.0(typescript@5.5.4) ts-api-utils: 1.3.0(typescript@5.5.4)
optionalDependencies: optionalDependencies:
@ -2154,28 +2155,28 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@typescript-eslint/parser@8.0.0(eslint@9.9.1)(typescript@5.5.4)': '@typescript-eslint/parser@8.1.0(eslint@9.9.0)(typescript@5.5.4)':
dependencies: dependencies:
'@typescript-eslint/scope-manager': 8.0.0 '@typescript-eslint/scope-manager': 8.1.0
'@typescript-eslint/types': 8.0.0 '@typescript-eslint/types': 8.1.0
'@typescript-eslint/typescript-estree': 8.0.0(typescript@5.5.4) '@typescript-eslint/typescript-estree': 8.1.0(typescript@5.5.4)
'@typescript-eslint/visitor-keys': 8.0.0 '@typescript-eslint/visitor-keys': 8.1.0
debug: 4.3.6 debug: 4.3.6
eslint: 9.9.1 eslint: 9.9.0
optionalDependencies: optionalDependencies:
typescript: 5.5.4 typescript: 5.5.4
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@typescript-eslint/scope-manager@8.0.0': '@typescript-eslint/scope-manager@8.1.0':
dependencies: dependencies:
'@typescript-eslint/types': 8.0.0 '@typescript-eslint/types': 8.1.0
'@typescript-eslint/visitor-keys': 8.0.0 '@typescript-eslint/visitor-keys': 8.1.0
'@typescript-eslint/type-utils@8.0.0(eslint@9.9.1)(typescript@5.5.4)': '@typescript-eslint/type-utils@8.1.0(eslint@9.9.0)(typescript@5.5.4)':
dependencies: dependencies:
'@typescript-eslint/typescript-estree': 8.0.0(typescript@5.5.4) '@typescript-eslint/typescript-estree': 8.1.0(typescript@5.5.4)
'@typescript-eslint/utils': 8.0.0(eslint@9.9.1)(typescript@5.5.4) '@typescript-eslint/utils': 8.1.0(eslint@9.9.0)(typescript@5.5.4)
debug: 4.3.6 debug: 4.3.6
ts-api-utils: 1.3.0(typescript@5.5.4) ts-api-utils: 1.3.0(typescript@5.5.4)
optionalDependencies: optionalDependencies:
@ -2184,12 +2185,12 @@ snapshots:
- eslint - eslint
- supports-color - supports-color
'@typescript-eslint/types@8.0.0': {} '@typescript-eslint/types@8.1.0': {}
'@typescript-eslint/typescript-estree@8.0.0(typescript@5.5.4)': '@typescript-eslint/typescript-estree@8.1.0(typescript@5.5.4)':
dependencies: dependencies:
'@typescript-eslint/types': 8.0.0 '@typescript-eslint/types': 8.1.0
'@typescript-eslint/visitor-keys': 8.0.0 '@typescript-eslint/visitor-keys': 8.1.0
debug: 4.3.6 debug: 4.3.6
globby: 11.1.0 globby: 11.1.0
is-glob: 4.0.3 is-glob: 4.0.3
@ -2201,20 +2202,20 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@typescript-eslint/utils@8.0.0(eslint@9.9.1)(typescript@5.5.4)': '@typescript-eslint/utils@8.1.0(eslint@9.9.0)(typescript@5.5.4)':
dependencies: dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@9.9.1) '@eslint-community/eslint-utils': 4.4.0(eslint@9.9.0)
'@typescript-eslint/scope-manager': 8.0.0 '@typescript-eslint/scope-manager': 8.1.0
'@typescript-eslint/types': 8.0.0 '@typescript-eslint/types': 8.1.0
'@typescript-eslint/typescript-estree': 8.0.0(typescript@5.5.4) '@typescript-eslint/typescript-estree': 8.1.0(typescript@5.5.4)
eslint: 9.9.1 eslint: 9.9.0
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
- typescript - typescript
'@typescript-eslint/visitor-keys@8.0.0': '@typescript-eslint/visitor-keys@8.1.0':
dependencies: dependencies:
'@typescript-eslint/types': 8.0.0 '@typescript-eslint/types': 8.1.0
eslint-visitor-keys: 3.4.3 eslint-visitor-keys: 3.4.3
acorn-jsx@5.3.2(acorn@8.12.1): acorn-jsx@5.3.2(acorn@8.12.1):
@ -2259,7 +2260,7 @@ snapshots:
array-union@2.1.0: {} array-union@2.1.0: {}
async@3.2.6: {} async@3.2.5: {}
babel-jest@29.7.0(@babel/core@7.23.9): babel-jest@29.7.0(@babel/core@7.23.9):
dependencies: dependencies:
@ -2400,13 +2401,13 @@ snapshots:
convert-source-map@2.0.0: {} convert-source-map@2.0.0: {}
create-jest@29.7.0(@types/node@22.5.1): create-jest@29.7.0(@types/node@22.3.0):
dependencies: dependencies:
'@jest/types': 29.6.3 '@jest/types': 29.6.3
chalk: 4.1.2 chalk: 4.1.2
exit: 0.1.2 exit: 0.1.2
graceful-fs: 4.2.11 graceful-fs: 4.2.11
jest-config: 29.7.0(@types/node@22.5.1) jest-config: 29.7.0(@types/node@22.3.0)
jest-util: 29.7.0 jest-util: 29.7.0
prompts: 2.4.2 prompts: 2.4.2
transitivePeerDependencies: transitivePeerDependencies:
@ -2461,9 +2462,9 @@ snapshots:
escape-string-regexp@4.0.0: {} escape-string-regexp@4.0.0: {}
eslint-plugin-prettier@5.2.1(eslint@9.9.1)(prettier@3.3.3): eslint-plugin-prettier@5.2.1(eslint@9.9.0)(prettier@3.3.3):
dependencies: dependencies:
eslint: 9.9.1 eslint: 9.9.0
prettier: 3.3.3 prettier: 3.3.3
prettier-linter-helpers: 1.0.0 prettier-linter-helpers: 1.0.0
synckit: 0.9.1 synckit: 0.9.1
@ -2477,13 +2478,13 @@ snapshots:
eslint-visitor-keys@4.0.0: {} eslint-visitor-keys@4.0.0: {}
eslint@9.9.1: eslint@9.9.0:
dependencies: dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@9.9.1) '@eslint-community/eslint-utils': 4.4.0(eslint@9.9.0)
'@eslint-community/regexpp': 4.11.0 '@eslint-community/regexpp': 4.11.0
'@eslint/config-array': 0.18.0 '@eslint/config-array': 0.17.1
'@eslint/eslintrc': 3.1.0 '@eslint/eslintrc': 3.1.0
'@eslint/js': 9.9.1 '@eslint/js': 9.9.0
'@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/module-importer': 1.0.1
'@humanwhocodes/retry': 0.3.0 '@humanwhocodes/retry': 0.3.0
'@nodelib/fs.walk': 1.2.8 '@nodelib/fs.walk': 1.2.8
@ -2676,8 +2677,6 @@ snapshots:
human-signals@2.1.0: {} human-signals@2.1.0: {}
ignore@5.3.1: {}
ignore@5.3.2: {} ignore@5.3.2: {}
import-fresh@3.3.0: import-fresh@3.3.0:
@ -2766,7 +2765,7 @@ snapshots:
jake@10.9.2: jake@10.9.2:
dependencies: dependencies:
async: 3.2.6 async: 3.2.5
chalk: 4.1.2 chalk: 4.1.2
filelist: 1.0.4 filelist: 1.0.4
minimatch: 3.1.2 minimatch: 3.1.2
@ -2783,7 +2782,7 @@ snapshots:
'@jest/expect': 29.7.0 '@jest/expect': 29.7.0
'@jest/test-result': 29.7.0 '@jest/test-result': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 22.5.1 '@types/node': 22.3.0
chalk: 4.1.2 chalk: 4.1.2
co: 4.6.0 co: 4.6.0
dedent: 1.5.1 dedent: 1.5.1
@ -2803,16 +2802,16 @@ snapshots:
- babel-plugin-macros - babel-plugin-macros
- supports-color - supports-color
jest-cli@29.7.0(@types/node@22.5.1): jest-cli@29.7.0(@types/node@22.3.0):
dependencies: dependencies:
'@jest/core': 29.7.0 '@jest/core': 29.7.0
'@jest/test-result': 29.7.0 '@jest/test-result': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
chalk: 4.1.2 chalk: 4.1.2
create-jest: 29.7.0(@types/node@22.5.1) create-jest: 29.7.0(@types/node@22.3.0)
exit: 0.1.2 exit: 0.1.2
import-local: 3.1.0 import-local: 3.1.0
jest-config: 29.7.0(@types/node@22.5.1) jest-config: 29.7.0(@types/node@22.3.0)
jest-util: 29.7.0 jest-util: 29.7.0
jest-validate: 29.7.0 jest-validate: 29.7.0
yargs: 17.7.2 yargs: 17.7.2
@ -2822,7 +2821,7 @@ snapshots:
- supports-color - supports-color
- ts-node - ts-node
jest-config@29.7.0(@types/node@22.5.1): jest-config@29.7.0(@types/node@22.3.0):
dependencies: dependencies:
'@babel/core': 7.23.9 '@babel/core': 7.23.9
'@jest/test-sequencer': 29.7.0 '@jest/test-sequencer': 29.7.0
@ -2847,7 +2846,7 @@ snapshots:
slash: 3.0.0 slash: 3.0.0
strip-json-comments: 3.1.1 strip-json-comments: 3.1.1
optionalDependencies: optionalDependencies:
'@types/node': 22.5.1 '@types/node': 22.3.0
transitivePeerDependencies: transitivePeerDependencies:
- babel-plugin-macros - babel-plugin-macros
- supports-color - supports-color
@ -2876,7 +2875,7 @@ snapshots:
'@jest/environment': 29.7.0 '@jest/environment': 29.7.0
'@jest/fake-timers': 29.7.0 '@jest/fake-timers': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 22.5.1 '@types/node': 22.3.0
jest-mock: 29.7.0 jest-mock: 29.7.0
jest-util: 29.7.0 jest-util: 29.7.0
@ -2886,7 +2885,7 @@ snapshots:
dependencies: dependencies:
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/graceful-fs': 4.1.9 '@types/graceful-fs': 4.1.9
'@types/node': 22.5.1 '@types/node': 22.3.0
anymatch: 3.1.3 anymatch: 3.1.3
fb-watchman: 2.0.2 fb-watchman: 2.0.2
graceful-fs: 4.2.11 graceful-fs: 4.2.11
@ -2925,7 +2924,7 @@ snapshots:
jest-mock@29.7.0: jest-mock@29.7.0:
dependencies: dependencies:
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 22.5.1 '@types/node': 22.3.0
jest-util: 29.7.0 jest-util: 29.7.0
jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): jest-pnp-resolver@1.2.3(jest-resolve@29.7.0):
@ -2960,7 +2959,7 @@ snapshots:
'@jest/test-result': 29.7.0 '@jest/test-result': 29.7.0
'@jest/transform': 29.7.0 '@jest/transform': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 22.5.1 '@types/node': 22.3.0
chalk: 4.1.2 chalk: 4.1.2
emittery: 0.13.1 emittery: 0.13.1
graceful-fs: 4.2.11 graceful-fs: 4.2.11
@ -2988,7 +2987,7 @@ snapshots:
'@jest/test-result': 29.7.0 '@jest/test-result': 29.7.0
'@jest/transform': 29.7.0 '@jest/transform': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 22.5.1 '@types/node': 22.3.0
chalk: 4.1.2 chalk: 4.1.2
cjs-module-lexer: 1.2.3 cjs-module-lexer: 1.2.3
collect-v8-coverage: 1.0.2 collect-v8-coverage: 1.0.2
@ -3027,14 +3026,14 @@ snapshots:
jest-util: 29.7.0 jest-util: 29.7.0
natural-compare: 1.4.0 natural-compare: 1.4.0
pretty-format: 29.7.0 pretty-format: 29.7.0
semver: 7.6.3 semver: 7.6.2
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
jest-util@29.7.0: jest-util@29.7.0:
dependencies: dependencies:
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 22.5.1 '@types/node': 22.3.0
chalk: 4.1.2 chalk: 4.1.2
ci-info: 3.9.0 ci-info: 3.9.0
graceful-fs: 4.2.11 graceful-fs: 4.2.11
@ -3053,7 +3052,7 @@ snapshots:
dependencies: dependencies:
'@jest/test-result': 29.7.0 '@jest/test-result': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 22.5.1 '@types/node': 22.3.0
ansi-escapes: 4.3.2 ansi-escapes: 4.3.2
chalk: 4.1.2 chalk: 4.1.2
emittery: 0.13.1 emittery: 0.13.1
@ -3062,17 +3061,17 @@ snapshots:
jest-worker@29.7.0: jest-worker@29.7.0:
dependencies: dependencies:
'@types/node': 22.5.1 '@types/node': 22.3.0
jest-util: 29.7.0 jest-util: 29.7.0
merge-stream: 2.0.0 merge-stream: 2.0.0
supports-color: 8.1.1 supports-color: 8.1.1
jest@29.7.0(@types/node@22.5.1): jest@29.7.0(@types/node@22.3.0):
dependencies: dependencies:
'@jest/core': 29.7.0 '@jest/core': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
import-local: 3.1.0 import-local: 3.1.0
jest-cli: 29.7.0(@types/node@22.5.1) jest-cli: 29.7.0(@types/node@22.3.0)
transitivePeerDependencies: transitivePeerDependencies:
- '@types/node' - '@types/node'
- babel-plugin-macros - babel-plugin-macros
@ -3108,7 +3107,7 @@ snapshots:
kleur@3.0.3: {} kleur@3.0.3: {}
lemmy-js-client@0.19.5-alpha.1: {} lemmy-js-client@0.20.0-alpha.11: {}
leven@3.1.0: {} leven@3.1.0: {}
@ -3306,6 +3305,8 @@ snapshots:
semver@6.3.1: {} semver@6.3.1: {}
semver@7.6.2: {}
semver@7.6.3: {} semver@7.6.3: {}
shebang-command@2.0.0: shebang-command@2.0.0:
@ -3393,12 +3394,12 @@ snapshots:
dependencies: dependencies:
typescript: 5.5.4 typescript: 5.5.4
ts-jest@29.2.5(@babel/core@7.23.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(jest@29.7.0(@types/node@22.5.1))(typescript@5.5.4): ts-jest@29.2.4(@babel/core@7.23.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(jest@29.7.0(@types/node@22.3.0))(typescript@5.5.4):
dependencies: dependencies:
bs-logger: 0.2.6 bs-logger: 0.2.6
ejs: 3.1.10 ejs: 3.1.10
fast-json-stable-stringify: 2.1.0 fast-json-stable-stringify: 2.1.0
jest: 29.7.0(@types/node@22.5.1) jest: 29.7.0(@types/node@22.3.0)
jest-util: 29.7.0 jest-util: 29.7.0
json5: 2.2.3 json5: 2.2.3
lodash.memoize: 4.1.2 lodash.memoize: 4.1.2
@ -3422,11 +3423,11 @@ snapshots:
type-fest@0.21.3: {} type-fest@0.21.3: {}
typescript-eslint@8.0.0(eslint@9.9.1)(typescript@5.5.4): typescript-eslint@8.1.0(eslint@9.9.0)(typescript@5.5.4):
dependencies: dependencies:
'@typescript-eslint/eslint-plugin': 8.0.0(@typescript-eslint/parser@8.0.0(eslint@9.9.1)(typescript@5.5.4))(eslint@9.9.1)(typescript@5.5.4) '@typescript-eslint/eslint-plugin': 8.1.0(@typescript-eslint/parser@8.1.0(eslint@9.9.0)(typescript@5.5.4))(eslint@9.9.0)(typescript@5.5.4)
'@typescript-eslint/parser': 8.0.0(eslint@9.9.1)(typescript@5.5.4) '@typescript-eslint/parser': 8.1.0(eslint@9.9.0)(typescript@5.5.4)
'@typescript-eslint/utils': 8.0.0(eslint@9.9.1)(typescript@5.5.4) '@typescript-eslint/utils': 8.1.0(eslint@9.9.0)(typescript@5.5.4)
optionalDependencies: optionalDependencies:
typescript: 5.5.4 typescript: 5.5.4
transitivePeerDependencies: transitivePeerDependencies:
@ -3435,7 +3436,7 @@ snapshots:
typescript@5.5.4: {} typescript@5.5.4: {}
undici-types@6.19.8: {} undici-types@6.18.2: {}
update-browserslist-db@1.0.13(browserslist@4.22.3): update-browserslist-db@1.0.13(browserslist@4.22.3):
dependencies: dependencies:

View File

@ -92,7 +92,7 @@ test("Create a comment", async () => {
test("Create a comment in a non-existent post", async () => { test("Create a comment in a non-existent post", async () => {
await expect(createComment(alpha, -1)).rejects.toStrictEqual( await expect(createComment(alpha, -1)).rejects.toStrictEqual(
Error("couldnt_find_post"), Error("not_found"),
); );
}); });
@ -143,7 +143,7 @@ test("Delete a comment", async () => {
await waitUntil( await waitUntil(
() => () =>
resolveComment(gamma, commentRes.comment_view.comment).catch(e => e), resolveComment(gamma, commentRes.comment_view.comment).catch(e => e),
r => r.message !== "couldnt_find_object", r => r.message !== "not_found",
) )
).comment; ).comment;
if (!gammaComment) { if (!gammaComment) {
@ -161,13 +161,13 @@ test("Delete a comment", async () => {
// Make sure that comment is undefined on beta // Make sure that comment is undefined on beta
await waitUntil( await waitUntil(
() => resolveComment(beta, commentRes.comment_view.comment).catch(e => e), () => resolveComment(beta, commentRes.comment_view.comment).catch(e => e),
e => e.message == "couldnt_find_object", e => e.message == "not_found",
); );
// Make sure that comment is undefined on gamma after delete // Make sure that comment is undefined on gamma after delete
await waitUntil( await waitUntil(
() => resolveComment(gamma, commentRes.comment_view.comment).catch(e => e), () => resolveComment(gamma, commentRes.comment_view.comment).catch(e => e),
e => e.message === "couldnt_find_object", e => e.message === "not_found",
); );
// Test undeleting the comment // Test undeleting the comment
@ -182,7 +182,7 @@ test("Delete a comment", async () => {
let betaComment2 = ( let betaComment2 = (
await waitUntil( await waitUntil(
() => resolveComment(beta, commentRes.comment_view.comment).catch(e => e), () => resolveComment(beta, commentRes.comment_view.comment).catch(e => e),
e => e.message !== "couldnt_find_object", e => e.message !== "not_found",
) )
).comment; ).comment;
expect(betaComment2?.comment.deleted).toBe(false); expect(betaComment2?.comment.deleted).toBe(false);

View File

@ -527,12 +527,12 @@ test("Content in local-only community doesn't federate", async () => {
// cant resolve the community from another instance // cant resolve the community from another instance
await expect( await expect(
resolveCommunity(beta, communityRes.actor_id), resolveCommunity(beta, communityRes.actor_id),
).rejects.toStrictEqual(Error("couldnt_find_object")); ).rejects.toStrictEqual(Error("not_found"));
// create a post, also cant resolve it // create a post, also cant resolve it
let postRes = await createPost(alpha, communityRes.id); let postRes = await createPost(alpha, communityRes.id);
await expect(resolvePost(beta, postRes.post_view.post)).rejects.toStrictEqual( await expect(resolvePost(beta, postRes.post_view.post)).rejects.toStrictEqual(
Error("couldnt_find_object"), Error("not_found"),
); );
}); });

View File

@ -125,12 +125,12 @@ test("Create a post", async () => {
// Delta only follows beta, so it should not see an alpha ap_id // Delta only follows beta, so it should not see an alpha ap_id
await expect( await expect(
resolvePost(delta, postRes.post_view.post), resolvePost(delta, postRes.post_view.post),
).rejects.toStrictEqual(Error("couldnt_find_object")); ).rejects.toStrictEqual(Error("not_found"));
// Epsilon has alpha blocked, it should not see the alpha post // Epsilon has alpha blocked, it should not see the alpha post
await expect( await expect(
resolvePost(epsilon, postRes.post_view.post), resolvePost(epsilon, postRes.post_view.post),
).rejects.toStrictEqual(Error("couldnt_find_object")); ).rejects.toStrictEqual(Error("not_found"));
// remove added allow/blocklists // remove added allow/blocklists
editSiteForm.allowed_instances = []; editSiteForm.allowed_instances = [];
@ -140,9 +140,7 @@ test("Create a post", async () => {
}); });
test("Create a post in a non-existent community", async () => { test("Create a post in a non-existent community", async () => {
await expect(createPost(alpha, -2)).rejects.toStrictEqual( await expect(createPost(alpha, -2)).rejects.toStrictEqual(Error("not_found"));
Error("couldnt_find_community"),
);
}); });
test("Unlike a post", async () => { test("Unlike a post", async () => {
@ -502,10 +500,17 @@ test("Enforce site ban federation for local user", async () => {
alpha, alpha,
alphaPerson.person.id, alphaPerson.person.id,
false, false,
false, true,
); );
expect(unBanAlpha.banned).toBe(false); expect(unBanAlpha.banned).toBe(false);
// existing alpha post should be restored on beta
betaBanRes = await waitUntil(
() => getPost(beta, searchBeta1.post.id),
s => !s.post_view.post.removed,
);
expect(betaBanRes.post_view.post.removed).toBe(false);
// Login gets invalidated by ban, need to login again // Login gets invalidated by ban, need to login again
if (!alphaUserPerson) { if (!alphaUserPerson) {
throw "Missing alpha person"; throw "Missing alpha person";
@ -623,7 +628,7 @@ test("Enforce community ban for federated user", async () => {
// Alpha tries to make post on beta, but it fails because of ban // Alpha tries to make post on beta, but it fails because of ban
await expect( await expect(
createPost(alpha, betaCommunity.community.id), createPost(alpha, betaCommunity.community.id),
).rejects.toStrictEqual(Error("banned_from_community")); ).rejects.toStrictEqual(Error("person_is_banned_from_community"));
// Unban alpha // Unban alpha
let unBanAlpha = await banPersonFromCommunity( let unBanAlpha = await banPersonFromCommunity(

View File

@ -419,13 +419,13 @@ export async function banPersonFromSite(
api: LemmyHttp, api: LemmyHttp,
person_id: number, person_id: number,
ban: boolean, ban: boolean,
remove_data: boolean, remove_or_restore_data: boolean,
): Promise<BanPersonResponse> { ): Promise<BanPersonResponse> {
// Make sure lemmy-beta/c/main is cached on lemmy_alpha // Make sure lemmy-beta/c/main is cached on lemmy_alpha
let form: BanPerson = { let form: BanPerson = {
person_id, person_id,
ban, ban,
remove_data, remove_or_restore_data,
}; };
return api.banPerson(form); return api.banPerson(form);
} }
@ -434,13 +434,13 @@ export async function banPersonFromCommunity(
api: LemmyHttp, api: LemmyHttp,
person_id: number, person_id: number,
community_id: number, community_id: number,
remove_data: boolean, remove_or_restore_data: boolean,
ban: boolean, ban: boolean,
): Promise<BanFromCommunityResponse> { ): Promise<BanFromCommunityResponse> {
let form: BanFromCommunity = { let form: BanFromCommunity = {
person_id, person_id,
community_id, community_id,
remove_data: remove_data, remove_or_restore_data,
ban, ban,
}; };
return api.banFromCommunity(form); return api.banFromCommunity(form);
@ -690,7 +690,7 @@ export async function saveUserSettingsBio(
blur_nsfw: false, blur_nsfw: false,
auto_expand: true, auto_expand: true,
theme: "darkly", theme: "darkly",
default_sort_type: "Active", default_post_sort_type: "Active",
default_listing_type: "All", default_listing_type: "All",
interface_language: "en", interface_language: "en",
show_avatars: true, show_avatars: true,
@ -710,7 +710,7 @@ export async function saveUserSettingsFederated(
show_nsfw: false, show_nsfw: false,
blur_nsfw: true, blur_nsfw: true,
auto_expand: false, auto_expand: false,
default_sort_type: "Hot", default_post_sort_type: "Hot",
default_listing_type: "All", default_listing_type: "All",
interface_language: "", interface_language: "",
avatar, avatar,

View File

@ -22,8 +22,7 @@ pub async fn distinguish_comment(
data.comment_id, data.comment_id,
Some(&local_user_view.local_user), Some(&local_user_view.local_user),
) )
.await? .await?;
.ok_or(LemmyErrorType::CouldntFindComment)?;
check_community_user_action( check_community_user_action(
&local_user_view.person, &local_user_view.person,
@ -60,8 +59,7 @@ pub async fn distinguish_comment(
data.comment_id, data.comment_id,
Some(&local_user_view.local_user), Some(&local_user_view.local_user),
) )
.await? .await?;
.ok_or(LemmyErrorType::CouldntFindComment)?;
Ok(Json(CommentResponse { Ok(Json(CommentResponse {
comment_view, comment_view,

View File

@ -40,8 +40,7 @@ pub async fn like_comment(
comment_id, comment_id,
Some(&local_user_view.local_user), Some(&local_user_view.local_user),
) )
.await? .await?;
.ok_or(LemmyErrorType::CouldntFindComment)?;
check_community_user_action( check_community_user_action(
&local_user_view.person, &local_user_view.person,
@ -54,8 +53,7 @@ pub async fn like_comment(
let comment_reply = CommentReply::read_by_comment(&mut context.pool(), comment_id).await; let comment_reply = CommentReply::read_by_comment(&mut context.pool(), comment_id).await;
if let Ok(Some(reply)) = comment_reply { if let Ok(Some(reply)) = comment_reply {
let recipient_id = reply.recipient_id; let recipient_id = reply.recipient_id;
if let Ok(Some(local_recipient)) = if let Ok(local_recipient) = LocalUserView::read_person(&mut context.pool(), recipient_id).await
LocalUserView::read_person(&mut context.pool(), recipient_id).await
{ {
recipient_ids.push(local_recipient.local_user.id); recipient_ids.push(local_recipient.local_user.id);
} }

View File

@ -5,7 +5,7 @@ use lemmy_api_common::{
utils::is_mod_or_admin, utils::is_mod_or_admin,
}; };
use lemmy_db_views::structs::{CommentView, LocalUserView, VoteView}; use lemmy_db_views::structs::{CommentView, LocalUserView, VoteView};
use lemmy_utils::{error::LemmyResult, LemmyErrorType}; use lemmy_utils::error::LemmyResult;
/// Lists likes for a comment /// Lists likes for a comment
#[tracing::instrument(skip(context))] #[tracing::instrument(skip(context))]
@ -19,8 +19,7 @@ pub async fn list_comment_likes(
data.comment_id, data.comment_id,
Some(&local_user_view.local_user), Some(&local_user_view.local_user),
) )
.await? .await?;
.ok_or(LemmyErrorType::CouldntFindComment)?;
is_mod_or_admin( is_mod_or_admin(
&mut context.pool(), &mut context.pool(),

View File

@ -37,8 +37,7 @@ pub async fn save_comment(
comment_id, comment_id,
Some(&local_user_view.local_user), Some(&local_user_view.local_user),
) )
.await? .await?;
.ok_or(LemmyErrorType::CouldntFindComment)?;
Ok(Json(CommentResponse { Ok(Json(CommentResponse {
comment_view, comment_view,

View File

@ -40,8 +40,7 @@ pub async fn create_comment_report(
comment_id, comment_id,
Some(&local_user_view.local_user), Some(&local_user_view.local_user),
) )
.await? .await?;
.ok_or(LemmyErrorType::CouldntFindComment)?;
check_community_user_action( check_community_user_action(
&local_user_view.person, &local_user_view.person,
@ -64,9 +63,8 @@ pub async fn create_comment_report(
.await .await
.with_lemmy_type(LemmyErrorType::CouldntCreateReport)?; .with_lemmy_type(LemmyErrorType::CouldntCreateReport)?;
let comment_report_view = CommentReportView::read(&mut context.pool(), report.id, person_id) let comment_report_view =
.await? CommentReportView::read(&mut context.pool(), report.id, person_id).await?;
.ok_or(LemmyErrorType::CouldntFindCommentReport)?;
// Email the admins // Email the admins
if local_site.reports_email_admins { if local_site.reports_email_admins {

View File

@ -17,9 +17,7 @@ pub async fn resolve_comment_report(
) -> LemmyResult<Json<CommentReportResponse>> { ) -> LemmyResult<Json<CommentReportResponse>> {
let report_id = data.report_id; let report_id = data.report_id;
let person_id = local_user_view.person.id; let person_id = local_user_view.person.id;
let report = CommentReportView::read(&mut context.pool(), report_id, person_id) let report = CommentReportView::read(&mut context.pool(), report_id, person_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindCommentReport)?;
let person_id = local_user_view.person.id; let person_id = local_user_view.person.id;
check_community_mod_action( check_community_mod_action(
@ -41,9 +39,8 @@ pub async fn resolve_comment_report(
} }
let report_id = data.report_id; let report_id = data.report_id;
let comment_report_view = CommentReportView::read(&mut context.pool(), report_id, person_id) let comment_report_view =
.await? CommentReportView::read(&mut context.pool(), report_id, person_id).await?;
.ok_or(LemmyErrorType::CouldntFindCommentReport)?;
Ok(Json(CommentReportResponse { Ok(Json(CommentReportResponse {
comment_report_view, comment_report_view,

View File

@ -46,23 +46,18 @@ pub async fn add_mod_to_community(
.await?; .await?;
} }
let community = Community::read(&mut context.pool(), community_id) let community = Community::read(&mut context.pool(), community_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
// If user is admin and community is remote, explicitly check that he is a // If user is admin and community is remote, explicitly check that he is a
// moderator. This is necessary because otherwise the action would be rejected // moderator. This is necessary because otherwise the action would be rejected
// by the community's home instance. // by the community's home instance.
if local_user_view.local_user.admin && !community.local { if local_user_view.local_user.admin && !community.local {
let is_mod = CommunityModeratorView::is_community_moderator( CommunityModeratorView::check_is_community_moderator(
&mut context.pool(), &mut context.pool(),
community.id, community.id,
local_user_view.person.id, local_user_view.person.id,
) )
.await?; .await?;
if !is_mod {
Err(LemmyErrorType::NotAModerator)?
}
} }
// Update in local database // Update in local database

View File

@ -4,7 +4,11 @@ use lemmy_api_common::{
community::{BanFromCommunity, BanFromCommunityResponse}, community::{BanFromCommunity, BanFromCommunityResponse},
context::LemmyContext, context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData}, send_activity::{ActivityChannel, SendActivityData},
utils::{check_community_mod_action, check_expire_time, remove_user_data_in_community}, utils::{
check_community_mod_action,
check_expire_time,
remove_or_restore_user_data_in_community,
},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
source::{ source::{
@ -33,7 +37,6 @@ pub async fn ban_from_community(
local_user_view: LocalUserView, local_user_view: LocalUserView,
) -> LemmyResult<Json<BanFromCommunityResponse>> { ) -> LemmyResult<Json<BanFromCommunityResponse>> {
let banned_person_id = data.person_id; let banned_person_id = data.person_id;
let remove_data = data.remove_data.unwrap_or(false);
let expires = check_expire_time(data.expires)?; let expires = check_expire_time(data.expires)?;
// Verify that only mods or admins can ban // Verify that only mods or admins can ban
@ -85,9 +88,16 @@ pub async fn ban_from_community(
} }
// Remove/Restore their data if that's desired // Remove/Restore their data if that's desired
if remove_data { if data.remove_or_restore_data.unwrap_or(false) {
remove_user_data_in_community(data.community_id, banned_person_id, &mut context.pool()).await?; let remove_data = data.ban;
} remove_or_restore_user_data_in_community(
data.community_id,
banned_person_id,
remove_data,
&mut context.pool(),
)
.await?;
};
// Mod tables // Mod tables
let form = ModBanFromCommunityForm { let form = ModBanFromCommunityForm {
@ -101,9 +111,7 @@ pub async fn ban_from_community(
ModBanFromCommunity::create(&mut context.pool(), &form).await?; ModBanFromCommunity::create(&mut context.pool(), &form).await?;
let person_view = PersonView::read(&mut context.pool(), data.person_id) let person_view = PersonView::read(&mut context.pool(), data.person_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindPerson)?;
ActivityChannel::submit_activity( ActivityChannel::submit_activity(
SendActivityData::BanFromCommunity { SendActivityData::BanFromCommunity {

View File

@ -56,8 +56,7 @@ pub async fn block_community(
Some(&local_user_view.local_user), Some(&local_user_view.local_user),
false, false,
) )
.await? .await?;
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
ActivityChannel::submit_activity( ActivityChannel::submit_activity(
SendActivityData::FollowCommunity( SendActivityData::FollowCommunity(

View File

@ -23,9 +23,7 @@ pub async fn follow_community(
context: Data<LemmyContext>, context: Data<LemmyContext>,
local_user_view: LocalUserView, local_user_view: LocalUserView,
) -> LemmyResult<Json<CommunityResponse>> { ) -> LemmyResult<Json<CommunityResponse>> {
let community = Community::read(&mut context.pool(), data.community_id) let community = Community::read(&mut context.pool(), data.community_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
let mut community_follower_form = CommunityFollowerForm { let mut community_follower_form = CommunityFollowerForm {
community_id: community.id, community_id: community.id,
person_id: local_user_view.person.id, person_id: local_user_view.person.id,
@ -68,8 +66,7 @@ pub async fn follow_community(
Some(&local_user_view.local_user), Some(&local_user_view.local_user),
false, false,
) )
.await? .await?;
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
let discussion_languages = CommunityLanguage::read(&mut context.pool(), community_id).await?; let discussion_languages = CommunityLanguage::read(&mut context.pool(), community_id).await?;

View File

@ -82,13 +82,10 @@ pub async fn transfer_community(
Some(&local_user_view.local_user), Some(&local_user_view.local_user),
false, false,
) )
.await? .await?;
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
let community_id = data.community_id; let community_id = data.community_id;
let moderators = CommunityModeratorView::for_community(&mut context.pool(), community_id) let moderators = CommunityModeratorView::for_community(&mut context.pool(), community_id).await?;
.await
.with_lemmy_type(LemmyErrorType::CouldntFindCommunity)?;
// Return the jwt // Return the jwt
Ok(Json(GetCommunityResponse { Ok(Json(GetCommunityResponse {

View File

@ -172,7 +172,7 @@ pub(crate) async fn ban_nonlocal_user_from_local_communities(
target: &Person, target: &Person,
ban: bool, ban: bool,
reason: &Option<String>, reason: &Option<String>,
remove_data: &Option<bool>, remove_or_restore_data: &Option<bool>,
expires: &Option<i64>, expires: &Option<i64>,
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
) -> LemmyResult<()> { ) -> LemmyResult<()> {
@ -230,7 +230,7 @@ pub(crate) async fn ban_nonlocal_user_from_local_communities(
person_id: target.id, person_id: target.id,
ban, ban,
reason: reason.clone(), reason: reason.clone(),
remove_data: *remove_data, remove_or_restore_data: *remove_or_restore_data,
expires: *expires, expires: *expires,
}; };
@ -258,17 +258,13 @@ pub async fn local_user_view_from_jwt(
let local_user_id = Claims::validate(jwt, context) let local_user_id = Claims::validate(jwt, context)
.await .await
.with_lemmy_type(LemmyErrorType::NotLoggedIn)?; .with_lemmy_type(LemmyErrorType::NotLoggedIn)?;
let local_user_view = LocalUserView::read(&mut context.pool(), local_user_id) let local_user_view = LocalUserView::read(&mut context.pool(), local_user_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindLocalUser)?;
check_user_valid(&local_user_view.person)?; check_user_valid(&local_user_view.person)?;
Ok(local_user_view) Ok(local_user_view)
} }
#[cfg(test)] #[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -36,8 +36,8 @@ pub async fn add_admin(
// Make sure that the person_id added is local // Make sure that the person_id added is local
let added_local_user = LocalUserView::read_person(&mut context.pool(), data.person_id) let added_local_user = LocalUserView::read_person(&mut context.pool(), data.person_id)
.await? .await
.ok_or(LemmyErrorType::ObjectNotLocal)?; .map_err(|_| LemmyErrorType::ObjectNotLocal)?;
LocalUser::update( LocalUser::update(
&mut context.pool(), &mut context.pool(),

View File

@ -5,7 +5,7 @@ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
person::{BanPerson, BanPersonResponse}, person::{BanPerson, BanPersonResponse},
send_activity::{ActivityChannel, SendActivityData}, send_activity::{ActivityChannel, SendActivityData},
utils::{check_expire_time, is_admin, remove_user_data}, utils::{check_expire_time, is_admin, remove_user_data, restore_user_data},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
source::{ source::{
@ -60,15 +60,18 @@ pub async fn ban_from_site(
// if its a local user, invalidate logins // if its a local user, invalidate logins
let local_user = LocalUserView::read_person(&mut context.pool(), person.id).await; let local_user = LocalUserView::read_person(&mut context.pool(), person.id).await;
if let Ok(Some(local_user)) = local_user { if let Ok(local_user) = local_user {
LoginToken::invalidate_all(&mut context.pool(), local_user.local_user.id).await?; LoginToken::invalidate_all(&mut context.pool(), local_user.local_user.id).await?;
} }
// Remove their data if that's desired // Remove their data if that's desired
let remove_data = data.remove_data.unwrap_or(false); if data.remove_or_restore_data.unwrap_or(false) {
if remove_data { if data.ban {
remove_user_data(person.id, &context).await?; remove_user_data(person.id, &context).await?;
} } else {
restore_user_data(person.id, &context).await?;
}
};
// Mod tables // Mod tables
let form = ModBanForm { let form = ModBanForm {
@ -81,16 +84,14 @@ pub async fn ban_from_site(
ModBan::create(&mut context.pool(), &form).await?; ModBan::create(&mut context.pool(), &form).await?;
let person_view = PersonView::read(&mut context.pool(), person.id) let person_view = PersonView::read(&mut context.pool(), person.id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindPerson)?;
ban_nonlocal_user_from_local_communities( ban_nonlocal_user_from_local_communities(
&local_user_view, &local_user_view,
&person, &person,
data.ban, data.ban,
&data.reason, &data.reason,
&data.remove_data, &data.remove_or_restore_data,
&data.expires, &data.expires,
&context, &context,
) )
@ -101,7 +102,7 @@ pub async fn ban_from_site(
moderator: local_user_view.person, moderator: local_user_view.person,
banned_user: person_view.person.clone(), banned_user: person_view.person.clone(),
reason: data.reason.clone(), reason: data.reason.clone(),
remove_data: data.remove_data, remove_or_restore_data: data.remove_or_restore_data,
ban: data.ban, ban: data.ban,
expires: data.expires, expires: data.expires,
}, },

View File

@ -32,8 +32,7 @@ pub async fn block_person(
let target_user = LocalUserView::read_person(&mut context.pool(), target_id) let target_user = LocalUserView::read_person(&mut context.pool(), target_id)
.await .await
.ok() .ok();
.flatten();
if target_user.is_some_and(|t| t.local_user.admin) { if target_user.is_some_and(|t| t.local_user.admin) {
Err(LemmyErrorType::CantBlockAdmin)? Err(LemmyErrorType::CantBlockAdmin)?
@ -49,9 +48,7 @@ pub async fn block_person(
.with_lemmy_type(LemmyErrorType::PersonBlockAlreadyExists)?; .with_lemmy_type(LemmyErrorType::PersonBlockAlreadyExists)?;
} }
let person_view = PersonView::read(&mut context.pool(), target_id) let person_view = PersonView::read(&mut context.pool(), target_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindPerson)?;
Ok(Json(BlockPersonResponse { Ok(Json(BlockPersonResponse {
person_view, person_view,
blocked: data.block, blocked: data.block,

View File

@ -28,11 +28,13 @@ pub async fn change_password(
} }
// Check the old password // Check the old password
let valid: bool = verify( let valid: bool = if let Some(password_encrypted) = &local_user_view.local_user.password_encrypted
&data.old_password, {
&local_user_view.local_user.password_encrypted, verify(&data.old_password, password_encrypted).unwrap_or(false)
) } else {
.unwrap_or(false); data.old_password.is_empty()
};
if !valid { if !valid {
Err(LemmyErrorType::IncorrectLogin)? Err(LemmyErrorType::IncorrectLogin)?
} }

View File

@ -21,7 +21,6 @@ pub async fn change_password_after_reset(
let token = data.token.clone(); let token = data.token.clone();
let local_user_id = PasswordResetRequest::read_and_delete(&mut context.pool(), &token) let local_user_id = PasswordResetRequest::read_and_delete(&mut context.pool(), &token)
.await? .await?
.ok_or(LemmyErrorType::TokenNotFound)?
.local_user_id; .local_user_id;
password_length_check(&data.password)?; password_length_check(&data.password)?;

View File

@ -2,8 +2,11 @@ use crate::{build_totp_2fa, generate_totp_2fa_secret};
use activitypub_federation::config::Data; use activitypub_federation::config::Data;
use actix_web::web::Json; use actix_web::web::Json;
use lemmy_api_common::{context::LemmyContext, person::GenerateTotpSecretResponse}; use lemmy_api_common::{context::LemmyContext, person::GenerateTotpSecretResponse};
use lemmy_db_schema::source::local_user::{LocalUser, LocalUserUpdateForm}; use lemmy_db_schema::source::{
use lemmy_db_views::structs::{LocalUserView, SiteView}; local_user::{LocalUser, LocalUserUpdateForm},
site::Site,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::{LemmyErrorType, LemmyResult}; use lemmy_utils::error::{LemmyErrorType, LemmyResult};
/// Generate a new secret for two-factor-authentication. Afterwards you need to call [toggle_totp] /// Generate a new secret for two-factor-authentication. Afterwards you need to call [toggle_totp]
@ -13,17 +16,14 @@ pub async fn generate_totp_secret(
local_user_view: LocalUserView, local_user_view: LocalUserView,
context: Data<LemmyContext>, context: Data<LemmyContext>,
) -> LemmyResult<Json<GenerateTotpSecretResponse>> { ) -> LemmyResult<Json<GenerateTotpSecretResponse>> {
let site_view = SiteView::read_local(&mut context.pool()) let site = Site::read_local(&mut context.pool()).await?;
.await?
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
if local_user_view.local_user.totp_2fa_enabled { if local_user_view.local_user.totp_2fa_enabled {
return Err(LemmyErrorType::TotpAlreadyEnabled)?; return Err(LemmyErrorType::TotpAlreadyEnabled)?;
} }
let secret = generate_totp_2fa_secret(); let secret = generate_totp_2fa_secret();
let secret_url = let secret_url = build_totp_2fa(&site.name, &local_user_view.person.name, &secret)?.get_url();
build_totp_2fa(&site_view.site.name, &local_user_view.person.name, &secret)?.get_url();
let local_user_form = LocalUserUpdateForm { let local_user_form = LocalUserUpdateForm {
totp_2fa_secret: Some(Some(secret)), totp_2fa_secret: Some(Some(secret)),

View File

@ -1,4 +1,4 @@
use crate::{check_totp_2fa_valid, local_user::check_email_verified}; use crate::check_totp_2fa_valid;
use actix_web::{ use actix_web::{
web::{Data, Json}, web::{Data, Json},
HttpRequest, HttpRequest,
@ -8,12 +8,7 @@ use lemmy_api_common::{
claims::Claims, claims::Claims,
context::LemmyContext, context::LemmyContext,
person::{Login, LoginResponse}, person::{Login, LoginResponse},
utils::check_user_valid, utils::{check_email_verified, check_registration_application, check_user_valid},
};
use lemmy_db_schema::{
source::{local_site::LocalSite, registration_application::RegistrationApplication},
utils::DbPool,
RegistrationMode,
}; };
use lemmy_db_views::structs::{LocalUserView, SiteView}; use lemmy_db_views::structs::{LocalUserView, SiteView};
use lemmy_utils::error::{LemmyErrorType, LemmyResult}; use lemmy_utils::error::{LemmyErrorType, LemmyResult};
@ -24,23 +19,20 @@ pub async fn login(
req: HttpRequest, req: HttpRequest,
context: Data<LemmyContext>, context: Data<LemmyContext>,
) -> LemmyResult<Json<LoginResponse>> { ) -> LemmyResult<Json<LoginResponse>> {
let site_view = SiteView::read_local(&mut context.pool()) let site_view = SiteView::read_local(&mut context.pool()).await?;
.await?
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
// Fetch that username / email // Fetch that username / email
let username_or_email = data.username_or_email.clone(); let username_or_email = data.username_or_email.clone();
let local_user_view = let local_user_view =
LocalUserView::find_by_email_or_name(&mut context.pool(), &username_or_email) LocalUserView::find_by_email_or_name(&mut context.pool(), &username_or_email).await?;
.await?
.ok_or(LemmyErrorType::IncorrectLogin)?;
// Verify the password // Verify the password
let valid: bool = verify( let valid: bool = local_user_view
&data.password, .local_user
&local_user_view.local_user.password_encrypted, .password_encrypted
) .as_ref()
.unwrap_or(false); .and_then(|password_encrypted| verify(&data.password, password_encrypted).ok())
.unwrap_or(false);
if !valid { if !valid {
Err(LemmyErrorType::IncorrectLogin)? Err(LemmyErrorType::IncorrectLogin)?
} }
@ -67,28 +59,3 @@ pub async fn login(
registration_created: false, registration_created: false,
})) }))
} }
async fn check_registration_application(
local_user_view: &LocalUserView,
local_site: &LocalSite,
pool: &mut DbPool<'_>,
) -> LemmyResult<()> {
if (local_site.registration_mode == RegistrationMode::RequireApplication
|| local_site.registration_mode == RegistrationMode::Closed)
&& !local_user_view.local_user.accepted_application
&& !local_user_view.local_user.admin
{
// Fetch the registration application. If no admin id is present its still pending. Otherwise it
// was processed (either accepted or denied).
let local_user_id = local_user_view.local_user.id;
let registration = RegistrationApplication::find_by_local_user_id(pool, local_user_id)
.await?
.ok_or(LemmyErrorType::CouldntFindRegistrationApplication)?;
if registration.admin_id.is_some() {
Err(LemmyErrorType::RegistrationDenied(registration.deny_reason))?
} else {
Err(LemmyErrorType::RegistrationApplicationIsPending)?
}
}
Ok(())
}

View File

@ -1,6 +1,3 @@
use lemmy_db_views::structs::{LocalUserView, SiteView};
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
pub mod add_admin; pub mod add_admin;
pub mod ban_person; pub mod ban_person;
pub mod block; pub mod block;
@ -20,15 +17,3 @@ pub mod save_settings;
pub mod update_totp; pub mod update_totp;
pub mod validate_auth; pub mod validate_auth;
pub mod verify_email; pub mod verify_email;
/// Check if the user's email is verified if email verification is turned on
/// However, skip checking verification if the user is an admin
fn check_email_verified(local_user_view: &LocalUserView, site_view: &SiteView) -> LemmyResult<()> {
if !local_user_view.local_user.admin
&& site_view.local_site.require_email_verification
&& !local_user_view.local_user.email_verified
{
Err(LemmyErrorType::EmailNotVerified)?
}
Ok(())
}

View File

@ -18,9 +18,7 @@ pub async fn mark_person_mention_as_read(
local_user_view: LocalUserView, local_user_view: LocalUserView,
) -> LemmyResult<Json<PersonMentionResponse>> { ) -> LemmyResult<Json<PersonMentionResponse>> {
let person_mention_id = data.person_mention_id; let person_mention_id = data.person_mention_id;
let read_person_mention = PersonMention::read(&mut context.pool(), person_mention_id) let read_person_mention = PersonMention::read(&mut context.pool(), person_mention_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindPersonMention)?;
if local_user_view.person.id != read_person_mention.recipient_id { if local_user_view.person.id != read_person_mention.recipient_id {
Err(LemmyErrorType::CouldntUpdateComment)? Err(LemmyErrorType::CouldntUpdateComment)?
@ -39,9 +37,7 @@ pub async fn mark_person_mention_as_read(
let person_mention_id = read_person_mention.id; let person_mention_id = read_person_mention.id;
let person_id = local_user_view.person.id; let person_id = local_user_view.person.id;
let person_mention_view = let person_mention_view =
PersonMentionView::read(&mut context.pool(), person_mention_id, Some(person_id)) PersonMentionView::read(&mut context.pool(), person_mention_id, Some(person_id)).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindPersonMention)?;
Ok(Json(PersonMentionResponse { Ok(Json(PersonMentionResponse {
person_mention_view, person_mention_view,

View File

@ -18,9 +18,7 @@ pub async fn mark_reply_as_read(
local_user_view: LocalUserView, local_user_view: LocalUserView,
) -> LemmyResult<Json<CommentReplyResponse>> { ) -> LemmyResult<Json<CommentReplyResponse>> {
let comment_reply_id = data.comment_reply_id; let comment_reply_id = data.comment_reply_id;
let read_comment_reply = CommentReply::read(&mut context.pool(), comment_reply_id) let read_comment_reply = CommentReply::read(&mut context.pool(), comment_reply_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindCommentReply)?;
if local_user_view.person.id != read_comment_reply.recipient_id { if local_user_view.person.id != read_comment_reply.recipient_id {
Err(LemmyErrorType::CouldntUpdateComment)? Err(LemmyErrorType::CouldntUpdateComment)?
@ -40,9 +38,7 @@ pub async fn mark_reply_as_read(
let comment_reply_id = read_comment_reply.id; let comment_reply_id = read_comment_reply.id;
let person_id = local_user_view.person.id; let person_id = local_user_view.person.id;
let comment_reply_view = let comment_reply_view =
CommentReplyView::read(&mut context.pool(), comment_reply_id, Some(person_id)) CommentReplyView::read(&mut context.pool(), comment_reply_id, Some(person_id)).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindCommentReply)?;
Ok(Json(CommentReplyResponse { comment_reply_view })) Ok(Json(CommentReplyResponse { comment_reply_view }))
} }

View File

@ -1,9 +1,8 @@
use crate::local_user::check_email_verified;
use actix_web::web::{Data, Json}; use actix_web::web::{Data, Json};
use lemmy_api_common::{ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
person::PasswordReset, person::PasswordReset,
utils::send_password_reset_email, utils::{check_email_verified, send_password_reset_email},
SuccessResponse, SuccessResponse,
}; };
use lemmy_db_views::structs::{LocalUserView, SiteView}; use lemmy_db_views::structs::{LocalUserView, SiteView};
@ -17,12 +16,10 @@ pub async fn reset_password(
// Fetch that email // Fetch that email
let email = data.email.to_lowercase(); let email = data.email.to_lowercase();
let local_user_view = LocalUserView::find_by_email(&mut context.pool(), &email) let local_user_view = LocalUserView::find_by_email(&mut context.pool(), &email)
.await? .await
.ok_or(LemmyErrorType::IncorrectLogin)?; .map_err(|_| LemmyErrorType::IncorrectLogin)?;
let site_view = SiteView::read_local(&mut context.pool()) let site_view = SiteView::read_local(&mut context.pool()).await?;
.await?
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
check_email_verified(&local_user_view, &site_view)?; check_email_verified(&local_user_view, &site_view)?;
// Email the pure token to the user. // Email the pure token to the user.

View File

@ -36,9 +36,7 @@ pub async fn save_user_settings(
context: Data<LemmyContext>, context: Data<LemmyContext>,
local_user_view: LocalUserView, local_user_view: LocalUserView,
) -> LemmyResult<Json<SuccessResponse>> { ) -> LemmyResult<Json<SuccessResponse>> {
let site_view = SiteView::read_local(&mut context.pool()) let site_view = SiteView::read_local(&mut context.pool()).await?;
.await?
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
let slur_regex = local_site_to_slur_regex(&site_view.local_site); let slur_regex = local_site_to_slur_regex(&site_view.local_site);
let url_blocklist = get_url_blocklist(&context).await?; let url_blocklist = get_url_blocklist(&context).await?;
@ -65,9 +63,7 @@ pub async fn save_user_settings(
let previous_email = local_user_view.local_user.email.clone().unwrap_or_default(); let previous_email = local_user_view.local_user.email.clone().unwrap_or_default();
// if email was changed, check that it is not taken and send verification mail // if email was changed, check that it is not taken and send verification mail
if previous_email.deref() != email { if previous_email.deref() != email {
if LocalUser::is_email_taken(&mut context.pool(), email).await? { LocalUser::check_is_email_taken(&mut context.pool(), email).await?;
return Err(LemmyErrorType::EmailAlreadyExists)?;
}
send_verification_email( send_verification_email(
&local_user_view, &local_user_view,
email, email,
@ -104,7 +100,8 @@ pub async fn save_user_settings(
let local_user_id = local_user_view.local_user.id; let local_user_id = local_user_view.local_user.id;
let person_id = local_user_view.person.id; let person_id = local_user_view.person.id;
let default_listing_type = data.default_listing_type; let default_listing_type = data.default_listing_type;
let default_sort_type = data.default_sort_type; let default_post_sort_type = data.default_post_sort_type;
let default_comment_sort_type = data.default_comment_sort_type;
let person_form = PersonUpdateForm { let person_form = PersonUpdateForm {
display_name, display_name,
@ -133,9 +130,9 @@ pub async fn save_user_settings(
send_notifications_to_email: data.send_notifications_to_email, send_notifications_to_email: data.send_notifications_to_email,
show_nsfw: data.show_nsfw, show_nsfw: data.show_nsfw,
blur_nsfw: data.blur_nsfw, blur_nsfw: data.blur_nsfw,
auto_expand: data.auto_expand,
show_bot_accounts: data.show_bot_accounts, show_bot_accounts: data.show_bot_accounts,
default_sort_type, default_post_sort_type,
default_comment_sort_type,
default_listing_type, default_listing_type,
theme: data.theme.clone(), theme: data.theme.clone(),
interface_language: data.interface_language.clone(), interface_language: data.interface_language.clone(),

View File

@ -10,19 +10,15 @@ use lemmy_db_schema::source::{
local_user::{LocalUser, LocalUserUpdateForm}, local_user::{LocalUser, LocalUserUpdateForm},
}; };
use lemmy_db_views::structs::{LocalUserView, SiteView}; use lemmy_db_views::structs::{LocalUserView, SiteView};
use lemmy_utils::error::{LemmyErrorType, LemmyResult}; use lemmy_utils::error::LemmyResult;
pub async fn verify_email( pub async fn verify_email(
data: Json<VerifyEmail>, data: Json<VerifyEmail>,
context: Data<LemmyContext>, context: Data<LemmyContext>,
) -> LemmyResult<Json<SuccessResponse>> { ) -> LemmyResult<Json<SuccessResponse>> {
let site_view = SiteView::read_local(&mut context.pool()) let site_view = SiteView::read_local(&mut context.pool()).await?;
.await?
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
let token = data.token.clone(); let token = data.token.clone();
let verification = EmailVerification::read_for_token(&mut context.pool(), &token) let verification = EmailVerification::read_for_token(&mut context.pool(), &token).await?;
.await?
.ok_or(LemmyErrorType::TokenNotFound)?;
let form = LocalUserUpdateForm { let form = LocalUserUpdateForm {
// necessary in case this is a new signup // necessary in case this is a new signup
@ -39,9 +35,7 @@ pub async fn verify_email(
// send out notification about registration application to admins if enabled // send out notification about registration application to admins if enabled
if site_view.local_site.application_email_admins { if site_view.local_site.application_email_admins {
let local_user = LocalUserView::read(&mut context.pool(), local_user_id) let local_user = LocalUserView::read(&mut context.pool(), local_user_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindPerson)?;
send_new_applicant_email_to_admins( send_new_applicant_email_to_admins(
&local_user.person.name, &local_user.person.name,

View File

@ -16,7 +16,7 @@ use lemmy_db_schema::{
PostFeatureType, PostFeatureType,
}; };
use lemmy_db_views::structs::LocalUserView; use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::{error::LemmyResult, LemmyErrorType}; use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))] #[tracing::instrument(skip(context))]
pub async fn feature_post( pub async fn feature_post(
@ -25,9 +25,7 @@ pub async fn feature_post(
local_user_view: LocalUserView, local_user_view: LocalUserView,
) -> LemmyResult<Json<PostResponse>> { ) -> LemmyResult<Json<PostResponse>> {
let post_id = data.post_id; let post_id = data.post_id;
let orig_post = Post::read(&mut context.pool(), post_id) let orig_post = Post::read(&mut context.pool(), post_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindPost)?;
check_community_mod_action( check_community_mod_action(
&local_user_view.person, &local_user_view.person,

View File

@ -38,9 +38,7 @@ pub async fn like_post(
// Check for a community ban // Check for a community ban
let post_id = data.post_id; let post_id = data.post_id;
let post = Post::read(&mut context.pool(), post_id) let post = Post::read(&mut context.pool(), post_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindPost)?;
check_community_user_action( check_community_user_action(
&local_user_view.person, &local_user_view.person,
@ -70,9 +68,7 @@ pub async fn like_post(
mark_post_as_read(person_id, post_id, &mut context.pool()).await?; mark_post_as_read(person_id, post_id, &mut context.pool()).await?;
let community = Community::read(&mut context.pool(), post.community_id) let community = Community::read(&mut context.pool(), post.community_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
ActivityChannel::submit_activity( ActivityChannel::submit_activity(
SendActivityData::LikePostOrComment { SendActivityData::LikePostOrComment {

View File

@ -6,7 +6,7 @@ use lemmy_api_common::{
}; };
use lemmy_db_schema::{source::post::Post, traits::Crud}; use lemmy_db_schema::{source::post::Post, traits::Crud};
use lemmy_db_views::structs::{LocalUserView, VoteView}; use lemmy_db_views::structs::{LocalUserView, VoteView};
use lemmy_utils::{error::LemmyResult, LemmyErrorType}; use lemmy_utils::error::LemmyResult;
/// Lists likes for a post /// Lists likes for a post
#[tracing::instrument(skip(context))] #[tracing::instrument(skip(context))]
@ -15,9 +15,7 @@ pub async fn list_post_likes(
context: Data<LemmyContext>, context: Data<LemmyContext>,
local_user_view: LocalUserView, local_user_view: LocalUserView,
) -> LemmyResult<Json<ListPostLikesResponse>> { ) -> LemmyResult<Json<ListPostLikesResponse>> {
let post = Post::read(&mut context.pool(), data.post_id) let post = Post::read(&mut context.pool(), data.post_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindPost)?;
is_mod_or_admin( is_mod_or_admin(
&mut context.pool(), &mut context.pool(),
&local_user_view.person, &local_user_view.person,

View File

@ -15,7 +15,7 @@ use lemmy_db_schema::{
traits::Crud, traits::Crud,
}; };
use lemmy_db_views::structs::LocalUserView; use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::{error::LemmyResult, LemmyErrorType}; use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))] #[tracing::instrument(skip(context))]
pub async fn lock_post( pub async fn lock_post(
@ -24,9 +24,7 @@ pub async fn lock_post(
local_user_view: LocalUserView, local_user_view: LocalUserView,
) -> LemmyResult<Json<PostResponse>> { ) -> LemmyResult<Json<PostResponse>> {
let post_id = data.post_id; let post_id = data.post_id;
let orig_post = Post::read(&mut context.pool(), post_id) let orig_post = Post::read(&mut context.pool(), post_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindPost)?;
check_community_mod_action( check_community_mod_action(
&local_user_view.person, &local_user_view.person,

View File

@ -40,8 +40,7 @@ pub async fn save_post(
Some(&local_user_view.local_user), Some(&local_user_view.local_user),
false, false,
) )
.await? .await?;
.ok_or(LemmyErrorType::CouldntFindPost)?;
mark_post_as_read(person_id, post_id, &mut context.pool()).await?; mark_post_as_read(person_id, post_id, &mut context.pool()).await?;

View File

@ -35,9 +35,7 @@ pub async fn create_post_report(
let person_id = local_user_view.person.id; let person_id = local_user_view.person.id;
let post_id = data.post_id; let post_id = data.post_id;
let post_view = PostView::read(&mut context.pool(), post_id, None, false) let post_view = PostView::read(&mut context.pool(), post_id, None, false).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindPost)?;
check_community_user_action( check_community_user_action(
&local_user_view.person, &local_user_view.person,
@ -61,9 +59,7 @@ pub async fn create_post_report(
.await .await
.with_lemmy_type(LemmyErrorType::CouldntCreateReport)?; .with_lemmy_type(LemmyErrorType::CouldntCreateReport)?;
let post_report_view = PostReportView::read(&mut context.pool(), report.id, person_id) let post_report_view = PostReportView::read(&mut context.pool(), report.id, person_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindPostReport)?;
// Email the admins // Email the admins
if local_site.reports_email_admins { if local_site.reports_email_admins {

View File

@ -17,9 +17,7 @@ pub async fn resolve_post_report(
) -> LemmyResult<Json<PostReportResponse>> { ) -> LemmyResult<Json<PostReportResponse>> {
let report_id = data.report_id; let report_id = data.report_id;
let person_id = local_user_view.person.id; let person_id = local_user_view.person.id;
let report = PostReportView::read(&mut context.pool(), report_id, person_id) let report = PostReportView::read(&mut context.pool(), report_id, person_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindPostReport)?;
let person_id = local_user_view.person.id; let person_id = local_user_view.person.id;
check_community_mod_action( check_community_mod_action(
@ -40,9 +38,7 @@ pub async fn resolve_post_report(
.with_lemmy_type(LemmyErrorType::CouldntResolveReport)?; .with_lemmy_type(LemmyErrorType::CouldntResolveReport)?;
} }
let post_report_view = PostReportView::read(&mut context.pool(), report_id, person_id) let post_report_view = PostReportView::read(&mut context.pool(), report_id, person_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindPostReport)?;
Ok(Json(PostReportResponse { post_report_view })) Ok(Json(PostReportResponse { post_report_view }))
} }

View File

@ -18,9 +18,7 @@ pub async fn mark_pm_as_read(
) -> LemmyResult<Json<PrivateMessageResponse>> { ) -> LemmyResult<Json<PrivateMessageResponse>> {
// Checking permissions // Checking permissions
let private_message_id = data.private_message_id; let private_message_id = data.private_message_id;
let orig_private_message = PrivateMessage::read(&mut context.pool(), private_message_id) let orig_private_message = PrivateMessage::read(&mut context.pool(), private_message_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindPrivateMessage)?;
if local_user_view.person.id != orig_private_message.recipient_id { if local_user_view.person.id != orig_private_message.recipient_id {
Err(LemmyErrorType::CouldntUpdatePrivateMessage)? Err(LemmyErrorType::CouldntUpdatePrivateMessage)?
} }
@ -39,9 +37,7 @@ pub async fn mark_pm_as_read(
.await .await
.with_lemmy_type(LemmyErrorType::CouldntUpdatePrivateMessage)?; .with_lemmy_type(LemmyErrorType::CouldntUpdatePrivateMessage)?;
let view = PrivateMessageView::read(&mut context.pool(), private_message_id) let view = PrivateMessageView::read(&mut context.pool(), private_message_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindPrivateMessage)?;
Ok(Json(PrivateMessageResponse { Ok(Json(PrivateMessageResponse {
private_message_view: view, private_message_view: view,
})) }))

View File

@ -29,9 +29,7 @@ pub async fn create_pm_report(
let person_id = local_user_view.person.id; let person_id = local_user_view.person.id;
let private_message_id = data.private_message_id; let private_message_id = data.private_message_id;
let private_message = PrivateMessage::read(&mut context.pool(), private_message_id) let private_message = PrivateMessage::read(&mut context.pool(), private_message_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindPrivateMessage)?;
// Make sure that only the recipient of the private message can create a report // Make sure that only the recipient of the private message can create a report
if person_id != private_message.recipient_id { if person_id != private_message.recipient_id {
@ -49,9 +47,8 @@ pub async fn create_pm_report(
.await .await
.with_lemmy_type(LemmyErrorType::CouldntCreateReport)?; .with_lemmy_type(LemmyErrorType::CouldntCreateReport)?;
let private_message_report_view = PrivateMessageReportView::read(&mut context.pool(), report.id) let private_message_report_view =
.await? PrivateMessageReportView::read(&mut context.pool(), report.id).await?;
.ok_or(LemmyErrorType::CouldntFindPrivateMessageReport)?;
// Email the admins // Email the admins
if local_site.reports_email_admins { if local_site.reports_email_admins {

View File

@ -28,9 +28,8 @@ pub async fn resolve_pm_report(
.with_lemmy_type(LemmyErrorType::CouldntResolveReport)?; .with_lemmy_type(LemmyErrorType::CouldntResolveReport)?;
} }
let private_message_report_view = PrivateMessageReportView::read(&mut context.pool(), report_id) let private_message_report_view =
.await? PrivateMessageReportView::read(&mut context.pool(), report_id).await?;
.ok_or(LemmyErrorType::CouldntFindPrivateMessageReport)?;
Ok(Json(PrivateMessageReportResponse { Ok(Json(PrivateMessageReportResponse {
private_message_report_view, private_message_report_view,

View File

@ -5,15 +5,13 @@ use lemmy_api_common::{
utils::build_federated_instances, utils::build_federated_instances,
}; };
use lemmy_db_views::structs::SiteView; use lemmy_db_views::structs::SiteView;
use lemmy_utils::{error::LemmyResult, LemmyErrorType}; use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))] #[tracing::instrument(skip(context))]
pub async fn get_federated_instances( pub async fn get_federated_instances(
context: Data<LemmyContext>, context: Data<LemmyContext>,
) -> LemmyResult<Json<GetFederatedInstancesResponse>> { ) -> LemmyResult<Json<GetFederatedInstancesResponse>> {
let site_view = SiteView::read_local(&mut context.pool()) let site_view = SiteView::read_local(&mut context.pool()).await?;
.await?
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
let federated_instances = let federated_instances =
build_federated_instances(&site_view.local_site, &mut context.pool()).await?; build_federated_instances(&site_view.local_site, &mut context.pool()).await?;

View File

@ -7,11 +7,12 @@ use lemmy_db_schema::{
local_site_url_blocklist::LocalSiteUrlBlocklist, local_site_url_blocklist::LocalSiteUrlBlocklist,
local_user::{LocalUser, LocalUserUpdateForm}, local_user::{LocalUser, LocalUserUpdateForm},
moderator::{ModAdd, ModAddForm}, moderator::{ModAdd, ModAddForm},
oauth_provider::OAuthProvider,
tagline::Tagline, tagline::Tagline,
}, },
traits::Crud, traits::Crud,
}; };
use lemmy_db_views::structs::{CustomEmojiView, LocalUserView, SiteView}; use lemmy_db_views::structs::{LocalUserView, SiteView};
use lemmy_db_views_actor::structs::PersonView; use lemmy_db_views_actor::structs::PersonView;
use lemmy_utils::{ use lemmy_utils::{
error::{LemmyErrorType, LemmyResult}, error::{LemmyErrorType, LemmyResult},
@ -55,17 +56,14 @@ pub async fn leave_admin(
ModAdd::create(&mut context.pool(), &form).await?; ModAdd::create(&mut context.pool(), &form).await?;
// Reread site and admins // Reread site and admins
let site_view = SiteView::read_local(&mut context.pool()) let site_view = SiteView::read_local(&mut context.pool()).await?;
.await?
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
let admins = PersonView::admins(&mut context.pool()).await?; let admins = PersonView::admins(&mut context.pool()).await?;
let all_languages = Language::read_all(&mut context.pool()).await?; let all_languages = Language::read_all(&mut context.pool()).await?;
let discussion_languages = SiteLanguage::read_local_raw(&mut context.pool()).await?; let discussion_languages = SiteLanguage::read_local_raw(&mut context.pool()).await?;
let taglines = Tagline::get_all(&mut context.pool(), site_view.local_site.id).await?; let oauth_providers = OAuthProvider::get_all_public(&mut context.pool()).await?;
let custom_emojis =
CustomEmojiView::get_all(&mut context.pool(), site_view.local_site.id).await?;
let blocked_urls = LocalSiteUrlBlocklist::get_all(&mut context.pool()).await?; let blocked_urls = LocalSiteUrlBlocklist::get_all(&mut context.pool()).await?;
let tagline = Tagline::get_random(&mut context.pool()).await?;
Ok(Json(GetSiteResponse { Ok(Json(GetSiteResponse {
site_view, site_view,
@ -74,8 +72,11 @@ pub async fn leave_admin(
my_user: None, my_user: None,
all_languages, all_languages,
discussion_languages, discussion_languages,
taglines, oauth_providers: Some(oauth_providers),
custom_emojis, admin_oauth_providers: None,
blocked_urls, blocked_urls,
tagline,
taglines: vec![],
custom_emojis: vec![],
})) }))
} }

View File

@ -16,7 +16,7 @@ use lemmy_db_schema::{
traits::Crud, traits::Crud,
}; };
use lemmy_db_views::structs::{CommentView, LocalUserView}; use lemmy_db_views::structs::{CommentView, LocalUserView};
use lemmy_utils::{error::LemmyResult, LemmyErrorType}; use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))] #[tracing::instrument(skip(context))]
pub async fn purge_comment( pub async fn purge_comment(
@ -35,8 +35,7 @@ pub async fn purge_comment(
comment_id, comment_id,
Some(&local_user_view.local_user), Some(&local_user_view.local_user),
) )
.await? .await?;
.ok_or(LemmyErrorType::CouldntFindComment)?;
// Also check that you're a higher admin // Also check that you're a higher admin
LocalUser::is_higher_admin_check( LocalUser::is_higher_admin_check(

View File

@ -19,7 +19,7 @@ use lemmy_db_schema::{
}; };
use lemmy_db_views::structs::LocalUserView; use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::structs::CommunityModeratorView; use lemmy_db_views_actor::structs::CommunityModeratorView;
use lemmy_utils::{error::LemmyResult, LemmyErrorType}; use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))] #[tracing::instrument(skip(context))]
pub async fn purge_community( pub async fn purge_community(
@ -31,9 +31,7 @@ pub async fn purge_community(
is_admin(&local_user_view)?; is_admin(&local_user_view)?;
// Read the community to get its images // Read the community to get its images
let community = Community::read(&mut context.pool(), data.community_id) let community = Community::read(&mut context.pool(), data.community_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
// Also check that you're a higher admin than all the mods // Also check that you're a higher admin than all the mods
let community_mod_person_ids = let community_mod_person_ids =

View File

@ -17,7 +17,7 @@ use lemmy_db_schema::{
traits::Crud, traits::Crud,
}; };
use lemmy_db_views::structs::LocalUserView; use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::{error::LemmyResult, LemmyErrorType}; use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))] #[tracing::instrument(skip(context))]
pub async fn purge_person( pub async fn purge_person(
@ -36,9 +36,7 @@ pub async fn purge_person(
) )
.await?; .await?;
let person = Person::read(&mut context.pool(), data.person_id) let person = Person::read(&mut context.pool(), data.person_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindPerson)?;
ban_nonlocal_user_from_local_communities( ban_nonlocal_user_from_local_communities(
&local_user_view, &local_user_view,
@ -77,7 +75,7 @@ pub async fn purge_person(
moderator: local_user_view.person, moderator: local_user_view.person,
banned_user: person, banned_user: person,
reason: data.reason.clone(), reason: data.reason.clone(),
remove_data: Some(true), remove_or_restore_data: Some(true),
ban: true, ban: true,
expires: None, expires: None,
}, },

View File

@ -17,7 +17,7 @@ use lemmy_db_schema::{
traits::Crud, traits::Crud,
}; };
use lemmy_db_views::structs::LocalUserView; use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::{error::LemmyResult, LemmyErrorType}; use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))] #[tracing::instrument(skip(context))]
pub async fn purge_post( pub async fn purge_post(
@ -29,9 +29,7 @@ pub async fn purge_post(
is_admin(&local_user_view)?; is_admin(&local_user_view)?;
// Read the post to get the community_id // Read the post to get the community_id
let post = Post::read(&mut context.pool(), data.post_id) let post = Post::read(&mut context.pool(), data.post_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindPost)?;
// Also check that you're a higher admin // Also check that you're a higher admin
LocalUser::is_higher_admin_check( LocalUser::is_higher_admin_check(

View File

@ -14,10 +14,7 @@ use lemmy_db_schema::{
utils::{diesel_string_update, get_conn}, utils::{diesel_string_update, get_conn},
}; };
use lemmy_db_views::structs::{LocalUserView, RegistrationApplicationView}; use lemmy_db_views::structs::{LocalUserView, RegistrationApplicationView};
use lemmy_utils::{ use lemmy_utils::error::{LemmyError, LemmyResult};
error::{LemmyError, LemmyResult},
LemmyErrorType,
};
pub async fn approve_registration_application( pub async fn approve_registration_application(
data: Json<ApproveRegistrationApplication>, data: Json<ApproveRegistrationApplication>,
@ -61,9 +58,8 @@ pub async fn approve_registration_application(
.await?; .await?;
if data.approve { if data.approve {
let approved_local_user_view = LocalUserView::read(&mut context.pool(), approved_user_id) let approved_local_user_view =
.await? LocalUserView::read(&mut context.pool(), approved_user_id).await?;
.ok_or(LemmyErrorType::CouldntFindLocalUser)?;
if approved_local_user_view.local_user.email.is_some() { if approved_local_user_view.local_user.email.is_some() {
// Email sending may fail, but this won't revert the application approval // Email sending may fail, but this won't revert the application approval
send_application_approved_email(&approved_local_user_view, context.settings()).await?; send_application_approved_email(&approved_local_user_view, context.settings()).await?;
@ -71,9 +67,8 @@ pub async fn approve_registration_application(
}; };
// Read the view // Read the view
let registration_application = RegistrationApplicationView::read(&mut context.pool(), app_id) let registration_application =
.await? RegistrationApplicationView::read(&mut context.pool(), app_id).await?;
.ok_or(LemmyErrorType::CouldntFindRegistrationApplication)?;
Ok(Json(RegistrationApplicationResponse { Ok(Json(RegistrationApplicationResponse {
registration_application, registration_application,

View File

@ -5,7 +5,7 @@ use lemmy_api_common::{
utils::is_admin, utils::is_admin,
}; };
use lemmy_db_views::structs::{LocalUserView, RegistrationApplicationView}; use lemmy_db_views::structs::{LocalUserView, RegistrationApplicationView};
use lemmy_utils::{error::LemmyResult, LemmyErrorType}; use lemmy_utils::error::LemmyResult;
/// Lists registration applications, filterable by undenied only. /// Lists registration applications, filterable by undenied only.
pub async fn get_registration_application( pub async fn get_registration_application(
@ -18,9 +18,7 @@ pub async fn get_registration_application(
// Read the view // Read the view
let registration_application = let registration_application =
RegistrationApplicationView::read_by_person(&mut context.pool(), data.person_id) RegistrationApplicationView::read_by_person(&mut context.pool(), data.person_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindRegistrationApplication)?;
Ok(Json(RegistrationApplicationResponse { Ok(Json(RegistrationApplicationResponse {
registration_application, registration_application,

View File

@ -34,7 +34,7 @@ use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::{error::LemmyResult, LemmyErrorType, CACHE_DURATION_API}; use lemmy_utils::{error::LemmyResult, LemmyErrorType, CACHE_DURATION_API};
use serial_test::serial; use serial_test::serial;
#[allow(clippy::unwrap_used)] #[expect(clippy::unwrap_used)]
async fn create_test_site(context: &Data<LemmyContext>) -> LemmyResult<(Instance, LocalUserView)> { async fn create_test_site(context: &Data<LemmyContext>) -> LemmyResult<(Instance, LocalUserView)> {
let pool = &mut context.pool(); let pool = &mut context.pool();
@ -54,32 +54,25 @@ async fn create_test_site(context: &Data<LemmyContext>) -> LemmyResult<(Instance
) )
.await?; .await?;
let admin_local_user_view = LocalUserView::read_person(pool, admin_person.id) let admin_local_user_view = LocalUserView::read_person(pool, admin_person.id).await?;
.await?
.unwrap();
let site_form = SiteInsertForm::builder() let site_form = SiteInsertForm::new("test site".to_string(), inserted_instance.id);
.name("test site".to_string())
.instance_id(inserted_instance.id)
.build();
let site = Site::create(pool, &site_form).await.unwrap(); let site = Site::create(pool, &site_form).await.unwrap();
// Create a local site, since this is necessary for determining if email verification is // Create a local site, since this is necessary for determining if email verification is
// required // required
let local_site_form = LocalSiteInsertForm::builder() let local_site_form = LocalSiteInsertForm {
.site_id(site.id) require_email_verification: Some(true),
.require_email_verification(Some(true)) application_question: Some(".".to_string()),
.application_question(Some(".".to_string())) registration_mode: Some(RegistrationMode::RequireApplication),
.registration_mode(Some(RegistrationMode::RequireApplication)) site_setup: Some(true),
.site_setup(Some(true)) ..LocalSiteInsertForm::new(site.id)
.build(); };
let local_site = LocalSite::create(pool, &local_site_form).await.unwrap(); let local_site = LocalSite::create(pool, &local_site_form).await.unwrap();
// Required to have a working local SiteView when updating the site to change email verification // Required to have a working local SiteView when updating the site to change email verification
// requirement or registration mode // requirement or registration mode
let rate_limit_form = LocalSiteRateLimitInsertForm::builder() let rate_limit_form = LocalSiteRateLimitInsertForm::new(local_site.id);
.local_site_id(local_site.id)
.build();
LocalSiteRateLimit::create(pool, &rate_limit_form) LocalSiteRateLimit::create(pool, &rate_limit_form)
.await .await
.unwrap(); .unwrap();
@ -116,7 +109,7 @@ async fn signup(
Ok((local_user, application)) Ok((local_user, application))
} }
#[allow(clippy::unwrap_used)] #[expect(clippy::unwrap_used)]
async fn get_application_statuses( async fn get_application_statuses(
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
admin: LocalUserView, admin: LocalUserView,
@ -145,10 +138,9 @@ async fn get_application_statuses(
Ok((application_count, unread_applications, all_applications)) Ok((application_count, unread_applications, all_applications))
} }
#[allow(clippy::indexing_slicing)]
#[allow(clippy::unwrap_used)]
#[tokio::test]
#[serial] #[serial]
#[tokio::test]
#[expect(clippy::indexing_slicing)]
async fn test_application_approval() -> LemmyResult<()> { async fn test_application_approval() -> LemmyResult<()> {
let context = LemmyContext::init_test_context().await; let context = LemmyContext::init_test_context().await;
let pool = &mut context.pool(); let pool = &mut context.pool();

View File

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

View File

@ -27,7 +27,6 @@ use lemmy_db_views_actor::structs::CommunityView;
use lemmy_utils::{ use lemmy_utils::{
error::LemmyResult, error::LemmyResult,
utils::{markdown::markdown_to_html, mention::MentionData}, utils::{markdown::markdown_to_html, mention::MentionData},
LemmyErrorType,
}; };
pub async fn build_comment_response( pub async fn build_comment_response(
@ -37,9 +36,8 @@ pub async fn build_comment_response(
recipient_ids: Vec<LocalUserId>, recipient_ids: Vec<LocalUserId>,
) -> LemmyResult<CommentResponse> { ) -> LemmyResult<CommentResponse> {
let local_user = local_user_view.map(|l| l.local_user); let local_user = local_user_view.map(|l| l.local_user);
let comment_view = CommentView::read(&mut context.pool(), comment_id, local_user.as_ref()) let comment_view =
.await? CommentView::read(&mut context.pool(), comment_id, local_user.as_ref()).await?;
.ok_or(LemmyErrorType::CouldntFindComment)?;
Ok(CommentResponse { Ok(CommentResponse {
comment_view, comment_view,
recipient_ids, recipient_ids,
@ -61,8 +59,7 @@ pub async fn build_community_response(
Some(&local_user), Some(&local_user),
is_mod_or_admin, is_mod_or_admin,
) )
.await? .await?;
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
let discussion_languages = CommunityLanguage::read(&mut context.pool(), community_id).await?; let discussion_languages = CommunityLanguage::read(&mut context.pool(), community_id).await?;
Ok(Json(CommunityResponse { Ok(Json(CommunityResponse {
@ -87,8 +84,7 @@ pub async fn build_post_response(
Some(&local_user), Some(&local_user),
is_mod_or_admin, is_mod_or_admin,
) )
.await? .await?;
.ok_or(LemmyErrorType::CouldntFindPost)?;
Ok(Json(PostResponse { post_view })) Ok(Json(PostResponse { post_view }))
} }
@ -112,8 +108,7 @@ pub async fn send_local_notifs(
comment_id, comment_id,
local_user_view.map(|view| &view.local_user), local_user_view.map(|view| &view.local_user),
) )
.await? .await?;
.ok_or(LemmyErrorType::CouldntFindComment)?;
let comment = comment_view.comment; let comment = comment_view.comment;
let post = comment_view.post; let post = comment_view.post;
let community = comment_view.community; let community = comment_view.community;
@ -125,7 +120,7 @@ pub async fn send_local_notifs(
{ {
let mention_name = mention.name.clone(); let mention_name = mention.name.clone();
let user_view = LocalUserView::read_from_name(&mut context.pool(), &mention_name).await; let user_view = LocalUserView::read_from_name(&mut context.pool(), &mention_name).await;
if let Ok(Some(mention_user_view)) = user_view { if let Ok(mention_user_view) = user_view {
// TODO // TODO
// At some point, make it so you can't tag the parent creator either // At some point, make it so you can't tag the parent creator either
// Potential duplication of notifications, one for reply and the other for mention, is handled // Potential duplication of notifications, one for reply and the other for mention, is handled
@ -161,9 +156,7 @@ pub async fn send_local_notifs(
// Send comment_reply to the parent commenter / poster // Send comment_reply to the parent commenter / poster
if let Some(parent_comment_id) = comment.parent_comment_id() { if let Some(parent_comment_id) = comment.parent_comment_id() {
let parent_comment = Comment::read(&mut context.pool(), parent_comment_id) let parent_comment = Comment::read(&mut context.pool(), parent_comment_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindComment)?;
// Get the parent commenter local_user // Get the parent commenter local_user
let parent_creator_id = parent_comment.creator_id; let parent_creator_id = parent_comment.creator_id;
@ -182,7 +175,7 @@ pub async fn send_local_notifs(
// Don't send a notif to yourself // Don't send a notif to yourself
if parent_comment.creator_id != person.id && !check_blocks { if parent_comment.creator_id != person.id && !check_blocks {
let user_view = LocalUserView::read_person(&mut context.pool(), parent_creator_id).await; let user_view = LocalUserView::read_person(&mut context.pool(), parent_creator_id).await;
if let Ok(Some(parent_user_view)) = user_view { if let Ok(parent_user_view) = user_view {
// Don't duplicate notif if already mentioned by checking recipient ids // Don't duplicate notif if already mentioned by checking recipient ids
if !recipient_ids.contains(&parent_user_view.local_user.id) { if !recipient_ids.contains(&parent_user_view.local_user.id) {
recipient_ids.push(parent_user_view.local_user.id); recipient_ids.push(parent_user_view.local_user.id);
@ -229,7 +222,7 @@ pub async fn send_local_notifs(
if post.creator_id != person.id && !check_blocks { if post.creator_id != person.id && !check_blocks {
let creator_id = post.creator_id; let creator_id = post.creator_id;
let parent_user = LocalUserView::read_person(&mut context.pool(), creator_id).await; let parent_user = LocalUserView::read_person(&mut context.pool(), creator_id).await;
if let Ok(Some(parent_user_view)) = parent_user { if let Ok(parent_user_view) = parent_user {
if !recipient_ids.contains(&parent_user_view.local_user.id) { if !recipient_ids.contains(&parent_user_view.local_user.id) {
recipient_ids.push(parent_user_view.local_user.id); recipient_ids.push(parent_user_view.local_user.id);

View File

@ -29,12 +29,8 @@ impl Claims {
let claims = let claims =
decode::<Claims>(jwt, &key, &validation).with_lemmy_type(LemmyErrorType::NotLoggedIn)?; decode::<Claims>(jwt, &key, &validation).with_lemmy_type(LemmyErrorType::NotLoggedIn)?;
let user_id = LocalUserId(claims.claims.sub.parse()?); let user_id = LocalUserId(claims.claims.sub.parse()?);
let is_valid = LoginToken::validate(&mut context.pool(), user_id, jwt).await?; LoginToken::validate(&mut context.pool(), user_id, jwt).await?;
if !is_valid { Ok(user_id)
Err(LemmyErrorType::NotLoggedIn)?
} else {
Ok(user_id)
}
} }
pub async fn generate( pub async fn generate(
@ -73,8 +69,7 @@ impl Claims {
} }
#[cfg(test)] #[cfg(test)]
#[allow(clippy::unwrap_used)] #[expect(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests { mod tests {
use crate::{claims::Claims, context::LemmyContext}; use crate::{claims::Claims, context::LemmyContext};

View File

@ -3,7 +3,7 @@ use lemmy_db_schema::{
source::site::Site, source::site::Site,
CommunityVisibility, CommunityVisibility,
ListingType, ListingType,
SortType, PostSortType,
}; };
use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView, PersonView}; use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView, PersonView};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -74,7 +74,7 @@ pub struct CommunityResponse {
/// Fetches a list of communities. /// Fetches a list of communities.
pub struct ListCommunities { pub struct ListCommunities {
pub type_: Option<ListingType>, pub type_: Option<ListingType>,
pub sort: Option<SortType>, pub sort: Option<PostSortType>,
pub show_nsfw: Option<bool>, pub show_nsfw: Option<bool>,
pub page: Option<i64>, pub page: Option<i64>,
pub limit: Option<i64>, pub limit: Option<i64>,
@ -97,7 +97,9 @@ pub struct BanFromCommunity {
pub community_id: CommunityId, pub community_id: CommunityId,
pub person_id: PersonId, pub person_id: PersonId,
pub ban: bool, pub ban: bool,
pub remove_data: Option<bool>, /// Optionally remove or restore all their data. Useful for new troll accounts.
/// If ban is true, then this means remove. If ban is false, it means restore.
pub remove_or_restore_data: Option<bool>,
pub reason: Option<String>, pub reason: Option<String>,
/// A time that the ban will expire, in unix epoch seconds. /// A time that the ban will expire, in unix epoch seconds.
/// ///

View File

@ -1,6 +1,7 @@
use lemmy_db_schema::newtypes::CustomEmojiId; use lemmy_db_schema::newtypes::CustomEmojiId;
use lemmy_db_views::structs::CustomEmojiView; use lemmy_db_views::structs::CustomEmojiView;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
#[cfg(feature = "full")] #[cfg(feature = "full")]
use ts_rs::TS; use ts_rs::TS;
use url::Url; use url::Url;
@ -46,3 +47,23 @@ pub struct DeleteCustomEmoji {
pub struct CustomEmojiResponse { pub struct CustomEmojiResponse {
pub custom_emoji: CustomEmojiView, pub custom_emoji: CustomEmojiView,
} }
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// A response for custom emojis.
pub struct ListCustomEmojisResponse {
pub custom_emojis: Vec<CustomEmojiView>,
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Fetches a list of custom emojis.
pub struct ListCustomEmojis {
pub page: Option<i64>,
pub limit: Option<i64>,
pub category: Option<String>,
pub ignore_page_limits: Option<bool>,
}

View File

@ -7,6 +7,7 @@ pub mod community;
#[cfg(feature = "full")] #[cfg(feature = "full")]
pub mod context; pub mod context;
pub mod custom_emoji; pub mod custom_emoji;
pub mod oauth_provider;
pub mod person; pub mod person;
pub mod post; pub mod post;
pub mod private_message; pub mod private_message;
@ -15,6 +16,7 @@ pub mod request;
#[cfg(feature = "full")] #[cfg(feature = "full")]
pub mod send_activity; pub mod send_activity;
pub mod site; pub mod site;
pub mod tagline;
#[cfg(feature = "full")] #[cfg(feature = "full")]
pub mod utils; pub mod utils;

View File

@ -0,0 +1,71 @@
use lemmy_db_schema::newtypes::OAuthProviderId;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
#[cfg(feature = "full")]
use ts_rs::TS;
use url::Url;
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Create an external auth method.
pub struct CreateOAuthProvider {
pub display_name: String,
pub issuer: String,
pub authorization_endpoint: String,
pub token_endpoint: String,
pub userinfo_endpoint: String,
pub id_claim: String,
pub client_id: String,
pub client_secret: String,
pub scopes: String,
pub auto_verify_email: Option<bool>,
pub account_linking_enabled: Option<bool>,
pub enabled: Option<bool>,
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Edit an external auth method.
pub struct EditOAuthProvider {
pub id: OAuthProviderId,
pub display_name: Option<String>,
pub authorization_endpoint: Option<String>,
pub token_endpoint: Option<String>,
pub userinfo_endpoint: Option<String>,
pub id_claim: Option<String>,
pub client_secret: Option<String>,
pub scopes: Option<String>,
pub auto_verify_email: Option<bool>,
pub account_linking_enabled: Option<bool>,
pub enabled: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Delete an external auth method.
pub struct DeleteOAuthProvider {
pub id: OAuthProviderId,
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Logging in with an OAuth 2.0 authorization
pub struct AuthenticateWithOauth {
pub code: String,
#[cfg_attr(feature = "full", ts(type = "string"))]
pub oauth_provider_id: OAuthProviderId,
#[cfg_attr(feature = "full", ts(type = "string"))]
pub redirect_uri: Url,
pub show_nsfw: Option<bool>,
/// Username is mandatory at registration time
pub username: Option<String>,
/// An answer is mandatory if require application is enabled on the server
pub answer: Option<String>,
}

View File

@ -5,7 +5,7 @@ use lemmy_db_schema::{
CommentSortType, CommentSortType,
ListingType, ListingType,
PostListingMode, PostListingMode,
SortType, PostSortType,
}; };
use lemmy_db_views::structs::{CommentView, LocalImageView, PostView}; use lemmy_db_views::structs::{CommentView, LocalImageView, PostView};
use lemmy_db_views_actor::structs::{ use lemmy_db_views_actor::structs::{
@ -84,12 +84,18 @@ pub struct CaptchaResponse {
pub struct SaveUserSettings { pub struct SaveUserSettings {
/// Show nsfw posts. /// Show nsfw posts.
pub show_nsfw: Option<bool>, pub show_nsfw: Option<bool>,
/// Blur nsfw posts.
pub blur_nsfw: Option<bool>, pub blur_nsfw: Option<bool>,
pub auto_expand: Option<bool>,
/// Your user's theme. /// Your user's theme.
pub theme: Option<String>, pub theme: Option<String>,
pub default_sort_type: Option<SortType>, /// The default post listing type, usually "local"
pub default_listing_type: Option<ListingType>, pub default_listing_type: Option<ListingType>,
/// A post-view mode that changes how multiple post listings look.
pub post_listing_mode: Option<PostListingMode>,
/// The default post sort, usually "active"
pub default_post_sort_type: Option<PostSortType>,
/// The default comment sort, usually "hot"
pub default_comment_sort_type: Option<CommentSortType>,
/// The language of the lemmy interface /// The language of the lemmy interface
pub interface_language: Option<String>, pub interface_language: Option<String>,
/// A URL for your avatar. /// A URL for your avatar.
@ -120,8 +126,6 @@ pub struct SaveUserSettings {
pub open_links_in_new_tab: Option<bool>, pub open_links_in_new_tab: Option<bool>,
/// Enable infinite scroll /// Enable infinite scroll
pub infinite_scroll_enabled: Option<bool>, pub infinite_scroll_enabled: Option<bool>,
/// A post-view mode that changes how multiple post listings look.
pub post_listing_mode: Option<PostListingMode>,
/// Whether to allow keyboard navigation (for browsing and interacting with posts and comments). /// Whether to allow keyboard navigation (for browsing and interacting with posts and comments).
pub enable_keyboard_navigation: Option<bool>, pub enable_keyboard_navigation: Option<bool>,
/// Whether user avatars or inline images in the UI that are gifs should be allowed to play or /// Whether user avatars or inline images in the UI that are gifs should be allowed to play or
@ -172,7 +176,7 @@ pub struct GetPersonDetails {
pub person_id: Option<PersonId>, pub person_id: Option<PersonId>,
/// Example: dessalines , or dessalines@xyz.tld /// Example: dessalines , or dessalines@xyz.tld
pub username: Option<String>, pub username: Option<String>,
pub sort: Option<SortType>, pub sort: Option<PostSortType>,
pub page: Option<i64>, pub page: Option<i64>,
pub limit: Option<i64>, pub limit: Option<i64>,
pub community_id: Option<CommunityId>, pub community_id: Option<CommunityId>,
@ -217,8 +221,9 @@ pub struct AddAdminResponse {
pub struct BanPerson { pub struct BanPerson {
pub person_id: PersonId, pub person_id: PersonId,
pub ban: bool, pub ban: bool,
/// Optionally remove all their data. Useful for new troll accounts. /// Optionally remove or restore all their data. Useful for new troll accounts.
pub remove_data: Option<bool>, /// If ban is true, then this means remove. If ban is false, it means restore.
pub remove_or_restore_data: Option<bool>,
pub reason: Option<String>, pub reason: Option<String>,
/// A time that the ban will expire, in unix epoch seconds. /// A time that the ban will expire, in unix epoch seconds.
/// ///

View File

@ -2,7 +2,7 @@ use lemmy_db_schema::{
newtypes::{CommentId, CommunityId, DbUrl, LanguageId, PostId, PostReportId}, newtypes::{CommentId, CommunityId, DbUrl, LanguageId, PostId, PostReportId},
ListingType, ListingType,
PostFeatureType, PostFeatureType,
SortType, PostSortType,
}; };
use lemmy_db_views::structs::{PaginationCursor, PostReportView, PostView, VoteView}; use lemmy_db_views::structs::{PaginationCursor, PostReportView, PostView, VoteView};
use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView}; use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView};
@ -30,6 +30,8 @@ pub struct CreatePost {
pub language_id: Option<LanguageId>, pub language_id: Option<LanguageId>,
/// Instead of fetching a thumbnail, use a custom one. /// Instead of fetching a thumbnail, use a custom one.
pub custom_thumbnail: Option<String>, pub custom_thumbnail: Option<String>,
/// Time when this post should be scheduled. Null means publish immediately.
pub scheduled_publish_time: Option<i64>,
} }
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
@ -69,7 +71,7 @@ pub struct GetPostResponse {
/// Get a list of posts. /// Get a list of posts.
pub struct GetPosts { pub struct GetPosts {
pub type_: Option<ListingType>, pub type_: Option<ListingType>,
pub sort: Option<SortType>, pub sort: Option<PostSortType>,
/// DEPRECATED, use page_cursor /// DEPRECATED, use page_cursor
pub page: Option<i64>, pub page: Option<i64>,
pub limit: Option<i64>, pub limit: Option<i64>,
@ -124,6 +126,8 @@ pub struct EditPost {
pub language_id: Option<LanguageId>, pub language_id: Option<LanguageId>,
/// Instead of fetching a thumbnail, use a custom one. /// Instead of fetching a thumbnail, use a custom one.
pub custom_thumbnail: Option<String>, pub custom_thumbnail: Option<String>,
/// Time when this post should be scheduled. Null means publish immediately.
pub scheduled_publish_time: Option<i64>,
} }
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]

View File

@ -3,7 +3,7 @@ use crate::{
lemmy_db_schema::traits::Crud, lemmy_db_schema::traits::Crud,
post::{LinkMetadata, OpenGraphData}, post::{LinkMetadata, OpenGraphData},
send_activity::{ActivityChannel, SendActivityData}, send_activity::{ActivityChannel, SendActivityData},
utils::{local_site_opt_to_sensitive, proxy_image_link}, utils::proxy_image_link,
}; };
use activitypub_federation::config::Data; use activitypub_federation::config::Data;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
@ -13,8 +13,8 @@ use lemmy_db_schema::{
newtypes::DbUrl, newtypes::DbUrl,
source::{ source::{
images::{ImageDetailsForm, LocalImage, LocalImageForm}, images::{ImageDetailsForm, LocalImage, LocalImageForm},
local_site::LocalSite,
post::{Post, PostUpdateForm}, post::{Post, PostUpdateForm},
site::Site,
}, },
}; };
use lemmy_utils::{ use lemmy_utils::{
@ -44,6 +44,7 @@ pub fn client_builder(settings: &Settings) -> ClientBuilder {
.user_agent(user_agent.clone()) .user_agent(user_agent.clone())
.timeout(REQWEST_TIMEOUT) .timeout(REQWEST_TIMEOUT)
.connect_timeout(REQWEST_TIMEOUT) .connect_timeout(REQWEST_TIMEOUT)
.use_rustls_tls()
} }
/// Fetches metadata for the given link and optionally generates thumbnail. /// Fetches metadata for the given link and optionally generates thumbnail.
@ -130,7 +131,6 @@ pub async fn generate_post_link_metadata(
post: Post, post: Post,
custom_thumbnail: Option<Url>, custom_thumbnail: Option<Url>,
send_activity: impl FnOnce(Post) -> Option<SendActivityData> + Send + 'static, send_activity: impl FnOnce(Post) -> Option<SendActivityData> + Send + 'static,
local_site: Option<LocalSite>,
context: Data<LemmyContext>, context: Data<LemmyContext>,
) -> LemmyResult<()> { ) -> LemmyResult<()> {
let metadata = match &post.url { let metadata = match &post.url {
@ -144,7 +144,8 @@ pub async fn generate_post_link_metadata(
.is_some_and(|content_type| content_type.starts_with("image")); .is_some_and(|content_type| content_type.starts_with("image"));
// Decide if we are allowed to generate local thumbnail // Decide if we are allowed to generate local thumbnail
let allow_sensitive = local_site_opt_to_sensitive(&local_site); let site = Site::read_local(&mut context.pool()).await?;
let allow_sensitive = site.content_warning.is_some();
let allow_generate_thumbnail = allow_sensitive || !post.nsfw; let allow_generate_thumbnail = allow_sensitive || !post.nsfw;
let image_url = if is_image_post { let image_url = if is_image_post {
@ -470,8 +471,7 @@ pub async fn replace_image(
} }
#[cfg(test)] #[cfg(test)]
#[allow(clippy::unwrap_used)] #[expect(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests { mod tests {
use crate::{ use crate::{

View File

@ -83,7 +83,7 @@ pub enum SendActivityData {
moderator: Person, moderator: Person,
banned_user: Person, banned_user: Person,
reason: Option<String>, reason: Option<String>,
remove_data: Option<bool>, remove_or_restore_data: Option<bool>,
ban: bool, ban: bool,
expires: Option<i64>, expires: Option<i64>,
}, },

View File

@ -16,19 +16,20 @@ use lemmy_db_schema::{
instance::Instance, instance::Instance,
language::Language, language::Language,
local_site_url_blocklist::LocalSiteUrlBlocklist, local_site_url_blocklist::LocalSiteUrlBlocklist,
oauth_provider::{OAuthProvider, PublicOAuthProvider},
person::Person, person::Person,
tagline::Tagline, tagline::Tagline,
}, },
CommentSortType,
ListingType, ListingType,
ModlogActionType, ModlogActionType,
PostListingMode, PostListingMode,
PostSortType,
RegistrationMode, RegistrationMode,
SearchType, SearchType,
SortType,
}; };
use lemmy_db_views::structs::{ use lemmy_db_views::structs::{
CommentView, CommentView,
CustomEmojiView,
LocalUserView, LocalUserView,
PostView, PostView,
RegistrationApplicationView, RegistrationApplicationView,
@ -73,10 +74,15 @@ pub struct Search {
pub community_name: Option<String>, pub community_name: Option<String>,
pub creator_id: Option<PersonId>, pub creator_id: Option<PersonId>,
pub type_: Option<SearchType>, pub type_: Option<SearchType>,
pub sort: Option<SortType>, pub sort: Option<PostSortType>,
pub listing_type: Option<ListingType>, pub listing_type: Option<ListingType>,
pub page: Option<i64>, pub page: Option<i64>,
pub limit: Option<i64>, pub limit: Option<i64>,
pub title_only: Option<bool>,
pub post_url_only: Option<bool>,
pub saved_only: Option<bool>,
pub liked_only: Option<bool>,
pub disliked_only: Option<bool>,
} }
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
@ -172,7 +178,9 @@ pub struct CreateSite {
pub private_instance: Option<bool>, pub private_instance: Option<bool>,
pub default_theme: Option<String>, pub default_theme: Option<String>,
pub default_post_listing_type: Option<ListingType>, pub default_post_listing_type: Option<ListingType>,
pub default_sort_type: Option<SortType>, pub default_post_listing_mode: Option<PostListingMode>,
pub default_post_sort_type: Option<PostSortType>,
pub default_comment_sort_type: Option<CommentSortType>,
pub legal_information: Option<String>, pub legal_information: Option<String>,
pub application_email_admins: Option<bool>, pub application_email_admins: Option<bool>,
pub hide_modlog_mod_names: Option<bool>, pub hide_modlog_mod_names: Option<bool>,
@ -197,10 +205,9 @@ pub struct CreateSite {
pub captcha_difficulty: Option<String>, pub captcha_difficulty: Option<String>,
pub allowed_instances: Option<Vec<String>>, pub allowed_instances: Option<Vec<String>>,
pub blocked_instances: Option<Vec<String>>, pub blocked_instances: Option<Vec<String>>,
pub taglines: Option<Vec<String>>,
pub registration_mode: Option<RegistrationMode>, pub registration_mode: Option<RegistrationMode>,
pub oauth_registration: Option<bool>,
pub content_warning: Option<String>, pub content_warning: Option<String>,
pub default_post_listing_mode: Option<PostListingMode>,
} }
#[skip_serializing_none] #[skip_serializing_none]
@ -231,9 +238,14 @@ pub struct EditSite {
pub private_instance: Option<bool>, pub private_instance: Option<bool>,
/// The default theme. Usually "browser" /// The default theme. Usually "browser"
pub default_theme: Option<String>, pub default_theme: Option<String>,
/// The default post listing type, usually "local"
pub default_post_listing_type: Option<ListingType>, pub default_post_listing_type: Option<ListingType>,
/// The default sort, usually "active" /// Default value for listing mode, usually "list"
pub default_sort_type: Option<SortType>, pub default_post_listing_mode: Option<PostListingMode>,
/// The default post sort, usually "active"
pub default_post_sort_type: Option<PostSortType>,
/// The default comment sort, usually "hot"
pub default_comment_sort_type: Option<CommentSortType>,
/// An optional page of legal information /// An optional page of legal information
pub legal_information: Option<String>, pub legal_information: Option<String>,
/// Whether to email admins when receiving a new application. /// Whether to email admins when receiving a new application.
@ -278,16 +290,14 @@ pub struct EditSite {
pub blocked_instances: Option<Vec<String>>, pub blocked_instances: Option<Vec<String>>,
/// A list of blocked URLs /// A list of blocked URLs
pub blocked_urls: Option<Vec<String>>, pub blocked_urls: Option<Vec<String>>,
/// A list of taglines shown at the top of the front page.
pub taglines: Option<Vec<String>>,
pub registration_mode: Option<RegistrationMode>, pub registration_mode: Option<RegistrationMode>,
/// Whether or not external auth methods can auto-register users.
pub oauth_registration: Option<bool>,
/// Whether to email admins for new reports. /// Whether to email admins for new reports.
pub reports_email_admins: Option<bool>, pub reports_email_admins: Option<bool>,
/// If present, nsfw content is visible by default. Should be displayed by frontends/clients /// If present, nsfw content is visible by default. Should be displayed by frontends/clients
/// when the site is first opened by a user. /// when the site is first opened by a user.
pub content_warning: Option<String>, pub content_warning: Option<String>,
/// Default value for [LocalUser.post_listing_mode]
pub default_post_listing_mode: Option<PostListingMode>,
} }
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
@ -296,7 +306,8 @@ pub struct EditSite {
/// The response for a site. /// The response for a site.
pub struct SiteResponse { pub struct SiteResponse {
pub site_view: SiteView, pub site_view: SiteView,
pub taglines: Vec<Tagline>, /// deprecated, use field `tagline` or /api/v3/tagline/list
pub taglines: Vec<()>,
} }
#[skip_serializing_none] #[skip_serializing_none]
@ -311,10 +322,15 @@ pub struct GetSiteResponse {
pub my_user: Option<MyUserInfo>, pub my_user: Option<MyUserInfo>,
pub all_languages: Vec<Language>, pub all_languages: Vec<Language>,
pub discussion_languages: Vec<LanguageId>, pub discussion_languages: Vec<LanguageId>,
/// A list of taglines shown at the top of the front page. /// deprecated, use field `tagline` or /api/v3/tagline/list
pub taglines: Vec<Tagline>, pub taglines: Vec<()>,
/// A list of custom emojis your site supports. /// deprecated, use /api/v3/custom_emoji/list
pub custom_emojis: Vec<CustomEmojiView>, pub custom_emojis: Vec<()>,
/// If the site has any taglines, a random one is included here for displaying
pub tagline: Option<Tagline>,
/// A list of external auth methods your site supports.
pub oauth_providers: Option<Vec<PublicOAuthProvider>>,
pub admin_oauth_providers: Option<Vec<OAuthProvider>>,
pub blocked_urls: Vec<LocalSiteUrlBlocklist>, pub blocked_urls: Vec<LocalSiteUrlBlocklist>,
} }

View File

@ -0,0 +1,55 @@
use lemmy_db_schema::{newtypes::TaglineId, source::tagline::Tagline};
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
#[cfg(feature = "full")]
use ts_rs::TS;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Create a tagline
pub struct CreateTagline {
pub content: String,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Update a tagline
pub struct UpdateTagline {
pub id: TaglineId,
pub content: String,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Delete a tagline
pub struct DeleteTagline {
pub id: TaglineId,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
pub struct TaglineResponse {
pub tagline: Tagline,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// A response for taglines.
pub struct ListTaglinesResponse {
pub taglines: Vec<Tagline>,
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Fetches a list of taglines.
pub struct ListTaglines {
pub page: Option<i64>,
pub limit: Option<i64>,
}

View File

@ -17,24 +17,27 @@ use lemmy_db_schema::{
community::{Community, CommunityModerator, CommunityUpdateForm}, community::{Community, CommunityModerator, CommunityUpdateForm},
community_block::CommunityBlock, community_block::CommunityBlock,
email_verification::{EmailVerification, EmailVerificationForm}, email_verification::{EmailVerification, EmailVerificationForm},
images::RemoteImage, images::{ImageDetails, RemoteImage},
instance::Instance, instance::Instance,
instance_block::InstanceBlock, instance_block::InstanceBlock,
local_site::LocalSite, local_site::LocalSite,
local_site_rate_limit::LocalSiteRateLimit, local_site_rate_limit::LocalSiteRateLimit,
local_site_url_blocklist::LocalSiteUrlBlocklist, local_site_url_blocklist::LocalSiteUrlBlocklist,
oauth_account::OAuthAccount,
password_reset_request::PasswordResetRequest, password_reset_request::PasswordResetRequest,
person::{Person, PersonUpdateForm}, person::{Person, PersonUpdateForm},
person_block::PersonBlock, person_block::PersonBlock,
post::{Post, PostRead}, post::{Post, PostRead},
registration_application::RegistrationApplication,
site::Site, site::Site,
}, },
traits::Crud, traits::Crud,
utils::DbPool, utils::DbPool,
RegistrationMode,
}; };
use lemmy_db_views::{ use lemmy_db_views::{
comment_view::CommentQuery, comment_view::CommentQuery,
structs::{LocalImageView, LocalUserView}, structs::{LocalImageView, LocalUserView, SiteView},
}; };
use lemmy_db_views_actor::structs::{ use lemmy_db_views_actor::structs::{
CommunityModeratorView, CommunityModeratorView,
@ -49,6 +52,7 @@ use lemmy_utils::{
utils::{ utils::{
markdown::{markdown_check_for_blocked_urls, markdown_rewrite_image_links}, markdown::{markdown_check_for_blocked_urls, markdown_rewrite_image_links},
slurs::{build_slur_regex, remove_slurs}, slurs::{build_slur_regex, remove_slurs},
validation::clean_urls_in_text,
}, },
CACHE_DURATION_FEDERATION, CACHE_DURATION_FEDERATION,
}; };
@ -69,13 +73,7 @@ pub async fn is_mod_or_admin(
community_id: CommunityId, community_id: CommunityId,
) -> LemmyResult<()> { ) -> LemmyResult<()> {
check_user_valid(person)?; check_user_valid(person)?;
CommunityView::check_is_mod_or_admin(pool, person.id, community_id).await
let is_mod_or_admin = CommunityView::is_mod_or_admin(pool, person.id, community_id).await?;
if !is_mod_or_admin {
Err(LemmyErrorType::NotAModOrAdmin)?
} else {
Ok(())
}
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
@ -106,13 +104,7 @@ pub async fn check_community_mod_of_any_or_admin_action(
let person = &local_user_view.person; let person = &local_user_view.person;
check_user_valid(person)?; check_user_valid(person)?;
CommunityView::check_is_mod_of_any_or_admin(pool, person.id).await
let is_mod_of_any_or_admin = CommunityView::is_mod_of_any_or_admin(pool, person.id).await?;
if !is_mod_of_any_or_admin {
Err(LemmyErrorType::NotAModOrAdmin)?
} else {
Ok(())
}
} }
pub fn is_admin(local_user_view: &LocalUserView) -> LemmyResult<()> { pub fn is_admin(local_user_view: &LocalUserView) -> LemmyResult<()> {
@ -171,9 +163,7 @@ pub async fn update_read_comments(
..PersonPostAggregatesForm::default() ..PersonPostAggregatesForm::default()
}; };
PersonPostAggregates::upsert(pool, &person_post_agg_form) PersonPostAggregates::upsert(pool, &person_post_agg_form).await?;
.await
.with_lemmy_type(LemmyErrorType::CouldntFindPost)?;
Ok(()) Ok(())
} }
@ -191,6 +181,44 @@ pub fn check_user_valid(person: &Person) -> LemmyResult<()> {
} }
} }
/// Check if the user's email is verified if email verification is turned on
/// However, skip checking verification if the user is an admin
pub fn check_email_verified(
local_user_view: &LocalUserView,
site_view: &SiteView,
) -> LemmyResult<()> {
if !local_user_view.local_user.admin
&& site_view.local_site.require_email_verification
&& !local_user_view.local_user.email_verified
{
Err(LemmyErrorType::EmailNotVerified)?
}
Ok(())
}
pub async fn check_registration_application(
local_user_view: &LocalUserView,
local_site: &LocalSite,
pool: &mut DbPool<'_>,
) -> LemmyResult<()> {
if (local_site.registration_mode == RegistrationMode::RequireApplication
|| local_site.registration_mode == RegistrationMode::Closed)
&& !local_user_view.local_user.accepted_application
&& !local_user_view.local_user.admin
{
// Fetch the registration application. If no admin id is present its still pending. Otherwise it
// was processed (either accepted or denied).
let local_user_id = local_user_view.local_user.id;
let registration = RegistrationApplication::find_by_local_user_id(pool, local_user_id).await?;
if registration.admin_id.is_some() {
Err(LemmyErrorType::RegistrationDenied(registration.deny_reason))?
} else {
Err(LemmyErrorType::RegistrationApplicationIsPending)?
}
}
Ok(())
}
/// Checks that a normal user action (eg posting or voting) is allowed in a given community. /// Checks that a normal user action (eg posting or voting) is allowed in a given community.
/// ///
/// In particular it checks that neither the user nor community are banned or deleted, and that /// In particular it checks that neither the user nor community are banned or deleted, and that
@ -202,7 +230,7 @@ pub async fn check_community_user_action(
) -> LemmyResult<()> { ) -> LemmyResult<()> {
check_user_valid(person)?; check_user_valid(person)?;
check_community_deleted_removed(community_id, pool).await?; check_community_deleted_removed(community_id, pool).await?;
check_community_ban(person, community_id, pool).await?; CommunityPersonBanView::check(pool, person.id, community_id).await?;
Ok(()) Ok(())
} }
@ -210,28 +238,13 @@ async fn check_community_deleted_removed(
community_id: CommunityId, community_id: CommunityId,
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
) -> LemmyResult<()> { ) -> LemmyResult<()> {
let community = Community::read(pool, community_id) let community = Community::read(pool, community_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
if community.deleted || community.removed { if community.deleted || community.removed {
Err(LemmyErrorType::Deleted)? Err(LemmyErrorType::Deleted)?
} }
Ok(()) Ok(())
} }
async fn check_community_ban(
person: &Person,
community_id: CommunityId,
pool: &mut DbPool<'_>,
) -> LemmyResult<()> {
// check if user was banned from site or community
let is_banned = CommunityPersonBanView::get(pool, person.id, community_id).await?;
if is_banned {
Err(LemmyErrorType::BannedFromCommunity)?
}
Ok(())
}
/// Check that the given user can perform a mod action in the community. /// Check that the given user can perform a mod action in the community.
/// ///
/// In particular it checks that he is an admin or mod, wasn't banned and the community isn't /// In particular it checks that he is an admin or mod, wasn't banned and the community isn't
@ -243,7 +256,7 @@ pub async fn check_community_mod_action(
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
) -> LemmyResult<()> { ) -> LemmyResult<()> {
is_mod_or_admin(pool, person, community_id).await?; is_mod_or_admin(pool, person, community_id).await?;
check_community_ban(person, community_id, pool).await?; CommunityPersonBanView::check(pool, person.id, community_id).await?;
// it must be possible to restore deleted community // it must be possible to restore deleted community
if !allow_deleted { if !allow_deleted {
@ -269,51 +282,6 @@ pub fn check_comment_deleted_or_removed(comment: &Comment) -> LemmyResult<()> {
} }
} }
/// Throws an error if a recipient has blocked a person.
#[tracing::instrument(skip_all)]
pub async fn check_person_block(
my_id: PersonId,
potential_blocker_id: PersonId,
pool: &mut DbPool<'_>,
) -> LemmyResult<()> {
let is_blocked = PersonBlock::read(pool, potential_blocker_id, my_id).await?;
if is_blocked {
Err(LemmyErrorType::PersonIsBlocked)?
} else {
Ok(())
}
}
/// Throws an error if a recipient has blocked a community.
#[tracing::instrument(skip_all)]
async fn check_community_block(
community_id: CommunityId,
person_id: PersonId,
pool: &mut DbPool<'_>,
) -> LemmyResult<()> {
let is_blocked = CommunityBlock::read(pool, person_id, community_id).await?;
if is_blocked {
Err(LemmyErrorType::CommunityIsBlocked)?
} else {
Ok(())
}
}
/// Throws an error if a recipient has blocked an instance.
#[tracing::instrument(skip_all)]
async fn check_instance_block(
instance_id: InstanceId,
person_id: PersonId,
pool: &mut DbPool<'_>,
) -> LemmyResult<()> {
let is_blocked = InstanceBlock::read(pool, person_id, instance_id).await?;
if is_blocked {
Err(LemmyErrorType::InstanceIsBlocked)?
} else {
Ok(())
}
}
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub async fn check_person_instance_community_block( pub async fn check_person_instance_community_block(
my_id: PersonId, my_id: PersonId,
@ -322,9 +290,9 @@ pub async fn check_person_instance_community_block(
community_id: CommunityId, community_id: CommunityId,
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
) -> LemmyResult<()> { ) -> LemmyResult<()> {
check_person_block(my_id, potential_blocker_id, pool).await?; PersonBlock::read(pool, potential_blocker_id, my_id).await?;
check_instance_block(community_instance_id, potential_blocker_id, pool).await?; InstanceBlock::read(pool, potential_blocker_id, community_instance_id).await?;
check_community_block(community_id, potential_blocker_id, pool).await?; CommunityBlock::read(pool, potential_blocker_id, community_id).await?;
Ok(()) Ok(())
} }
@ -537,13 +505,6 @@ pub fn local_site_opt_to_slur_regex(local_site: &Option<LocalSite>) -> Option<Re
.unwrap_or(None) .unwrap_or(None)
} }
pub fn local_site_opt_to_sensitive(local_site: &Option<LocalSite>) -> bool {
local_site
.as_ref()
.map(|site| site.enable_nsfw)
.unwrap_or(false)
}
pub async fn get_url_blocklist(context: &LemmyContext) -> LemmyResult<RegexSet> { pub async fn get_url_blocklist(context: &LemmyContext) -> LemmyResult<RegexSet> {
static URL_BLOCKLIST: LazyLock<Cache<(), RegexSet>> = LazyLock::new(|| { static URL_BLOCKLIST: LazyLock<Cache<(), RegexSet>> = LazyLock::new(|| {
Cache::builder() Cache::builder()
@ -667,7 +628,7 @@ pub async fn purge_image_posts_for_person(
/// Delete a local_user's images /// Delete a local_user's images
async fn delete_local_user_images(person_id: PersonId, context: &LemmyContext) -> LemmyResult<()> { async fn delete_local_user_images(person_id: PersonId, context: &LemmyContext) -> LemmyResult<()> {
if let Ok(Some(local_user)) = LocalUserView::read_person(&mut context.pool(), person_id).await { if let Ok(local_user) = LocalUserView::read_person(&mut context.pool(), person_id).await {
let pictrs_uploads = let pictrs_uploads =
LocalImageView::get_all_by_local_user_id(&mut context.pool(), local_user.local_user.id) LocalImageView::get_all_by_local_user_id(&mut context.pool(), local_user.local_user.id)
.await?; .await?;
@ -712,9 +673,7 @@ pub async fn remove_user_data(
) -> LemmyResult<()> { ) -> LemmyResult<()> {
let pool = &mut context.pool(); let pool = &mut context.pool();
// Purge user images // Purge user images
let person = Person::read(pool, banned_person_id) let person = Person::read(pool, banned_person_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindPerson)?;
if let Some(avatar) = person.avatar { if let Some(avatar) = person.avatar {
purge_image_from_pictrs(&avatar, context).await.ok(); purge_image_from_pictrs(&avatar, context).await.ok();
} }
@ -790,22 +749,40 @@ pub async fn remove_user_data(
Ok(()) Ok(())
} }
pub async fn remove_user_data_in_community( /// We can't restore their images, but we can unremove their posts and comments
pub async fn restore_user_data(
banned_person_id: PersonId,
context: &LemmyContext,
) -> LemmyResult<()> {
let pool = &mut context.pool();
// Posts
Post::update_removed_for_creator(pool, banned_person_id, None, false).await?;
// Comments
Comment::update_removed_for_creator(pool, banned_person_id, false).await?;
Ok(())
}
pub async fn remove_or_restore_user_data_in_community(
community_id: CommunityId, community_id: CommunityId,
banned_person_id: PersonId, banned_person_id: PersonId,
remove: bool,
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
) -> LemmyResult<()> { ) -> LemmyResult<()> {
// Posts // Posts
Post::update_removed_for_creator(pool, banned_person_id, Some(community_id), true).await?; Post::update_removed_for_creator(pool, banned_person_id, Some(community_id), remove).await?;
// Comments // Comments
// TODO Diesel doesn't allow updates with joins, so this has to be a loop // TODO Diesel doesn't allow updates with joins, so this has to be a loop
let site = Site::read_local(pool).await?;
let comments = CommentQuery { let comments = CommentQuery {
creator_id: Some(banned_person_id), creator_id: Some(banned_person_id),
community_id: Some(community_id), community_id: Some(community_id),
..Default::default() ..Default::default()
} }
.list(pool) .list(&site, pool)
.await?; .await?;
for comment_view in &comments { for comment_view in &comments {
@ -814,7 +791,7 @@ pub async fn remove_user_data_in_community(
pool, pool,
comment_id, comment_id,
&CommentUpdateForm { &CommentUpdateForm {
removed: Some(true), removed: Some(remove),
..Default::default() ..Default::default()
}, },
) )
@ -827,9 +804,7 @@ pub async fn remove_user_data_in_community(
pub async fn purge_user_account(person_id: PersonId, context: &LemmyContext) -> LemmyResult<()> { pub async fn purge_user_account(person_id: PersonId, context: &LemmyContext) -> LemmyResult<()> {
let pool = &mut context.pool(); let pool = &mut context.pool();
let person = Person::read(pool, person_id) let person = Person::read(pool, person_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindPerson)?;
// Delete their local images, if they're a local user // Delete their local images, if they're a local user
delete_local_user_images(person_id, context).await.ok(); delete_local_user_images(person_id, context).await.ok();
@ -858,6 +833,11 @@ pub async fn purge_user_account(person_id: PersonId, context: &LemmyContext) ->
// Leave communities they mod // Leave communities they mod
CommunityModerator::leave_all_communities(pool, person_id).await?; CommunityModerator::leave_all_communities(pool, person_id).await?;
// Delete the oauth accounts linked to the local user
if let Ok(local_user) = LocalUserView::read_person(pool, person_id).await {
OAuthAccount::delete_user_accounts(pool, local_user.local_user.id).await?;
}
Person::delete_account(pool, person_id).await?; Person::delete_account(pool, person_id).await?;
Ok(()) Ok(())
@ -940,6 +920,18 @@ fn limit_expire_time(expires: DateTime<Utc>) -> LemmyResult<Option<DateTime<Utc>
} }
} }
#[tracing::instrument(skip_all)]
pub fn check_conflicting_like_filters(
liked_only: Option<bool>,
disliked_only: Option<bool>,
) -> LemmyResult<()> {
if liked_only.unwrap_or_default() && disliked_only.unwrap_or_default() {
Err(LemmyErrorType::ContradictingFilters)?
} else {
Ok(())
}
}
pub async fn process_markdown( pub async fn process_markdown(
text: &str, text: &str,
slur_regex: &Option<Regex>, slur_regex: &Option<Regex>,
@ -947,11 +939,13 @@ pub async fn process_markdown(
context: &LemmyContext, context: &LemmyContext,
) -> LemmyResult<String> { ) -> LemmyResult<String> {
let text = remove_slurs(text, slur_regex); let text = remove_slurs(text, slur_regex);
let text = clean_urls_in_text(&text);
markdown_check_for_blocked_urls(&text, url_blocklist)?; markdown_check_for_blocked_urls(&text, url_blocklist)?;
if context.settings().pictrs_config()?.image_mode() == PictrsImageMode::ProxyAllImages { if context.settings().pictrs_config()?.image_mode() == PictrsImageMode::ProxyAllImages {
let (text, links) = markdown_rewrite_image_links(text); let (text, links) = markdown_rewrite_image_links(text);
RemoteImage::create(&mut context.pool(), links.clone()).await?;
// Create images and image detail rows // Create images and image detail rows
for link in links { for link in links {
@ -961,7 +955,7 @@ pub async fn process_markdown(
let proxied = let proxied =
build_proxied_image_url(&link, &context.settings().get_protocol_and_hostname())?; build_proxied_image_url(&link, &context.settings().get_protocol_and_hostname())?;
let details_form = details.build_image_details_form(&proxied); let details_form = details.build_image_details_form(&proxied);
RemoteImage::create(&mut context.pool(), &details_form).await?; ImageDetails::create(&mut context.pool(), &details_form).await?;
} }
} }
Ok(text) Ok(text)
@ -997,13 +991,15 @@ async fn proxy_image_link_internal(
if link.domain() == Some(&context.settings().hostname) { if link.domain() == Some(&context.settings().hostname) {
Ok(link.into()) Ok(link.into())
} else if image_mode == PictrsImageMode::ProxyAllImages { } else if image_mode == PictrsImageMode::ProxyAllImages {
RemoteImage::create(&mut context.pool(), vec![link.clone()]).await?;
let proxied = build_proxied_image_url(&link, &context.settings().get_protocol_and_hostname())?; let proxied = build_proxied_image_url(&link, &context.settings().get_protocol_and_hostname())?;
// This should fail softly, since pictrs might not even be running // This should fail softly, since pictrs might not even be running
let details_res = fetch_pictrs_proxied_image_details(&link, context).await; let details_res = fetch_pictrs_proxied_image_details(&link, context).await;
if let Ok(details) = details_res { if let Ok(details) = details_res {
let details_form = details.build_image_details_form(&proxied); let details_form = details.build_image_details_form(&proxied);
RemoteImage::create(&mut context.pool(), &details_form).await?; ImageDetails::create(&mut context.pool(), &details_form).await?;
}; };
Ok(proxied.into()) Ok(proxied.into())
@ -1071,8 +1067,7 @@ fn build_proxied_image_url(
} }
#[cfg(test)] #[cfg(test)]
#[allow(clippy::unwrap_used)] #[expect(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests { mod tests {
use super::*; use super::*;
@ -1149,7 +1144,7 @@ mod tests {
assert!( assert!(
RemoteImage::validate(&mut context.pool(), remote_image.into()) RemoteImage::validate(&mut context.pool(), remote_image.into())
.await .await
.is_err() .is_ok()
); );
} }
} }

View File

@ -27,8 +27,12 @@ futures.workspace = true
uuid = { workspace = true } uuid = { workspace = true }
moka.workspace = true moka.workspace = true
anyhow.workspace = true anyhow.workspace = true
webmention = "0.5.0" chrono.workspace = true
webmention = "0.6.0"
accept-language = "3.1.0" accept-language = "3.1.0"
serde_json = { workspace = true }
serde = { workspace = true }
serde_with = { workspace = true }
[package.metadata.cargo-machete] [package.metadata.cargo-machete]
ignored = ["futures"] ignored = ["futures"]

View File

@ -56,8 +56,7 @@ pub async fn create_comment(
Some(&local_user_view.local_user), Some(&local_user_view.local_user),
true, true,
) )
.await? .await?;
.ok_or(LemmyErrorType::CouldntFindPost)?;
let post = post_view.post; let post = post_view.post;
let community_id = post_view.community.id; let community_id = post_view.community.id;
@ -78,8 +77,7 @@ pub async fn create_comment(
Comment::read(&mut context.pool(), parent_id).await.ok() Comment::read(&mut context.pool(), parent_id).await.ok()
} else { } else {
None None
} };
.flatten();
// If there's a parent_id, check to make sure that comment is in that post // If there's a parent_id, check to make sure that comment is in that post
// Strange issue where sometimes the post ID of the parent comment is incorrect // Strange issue where sometimes the post ID of the parent comment is incorrect
@ -110,12 +108,10 @@ pub async fn create_comment(
} }
}; };
let comment_form = CommentInsertForm::builder() let comment_form = CommentInsertForm {
.content(content.clone()) language_id,
.post_id(data.post_id) ..CommentInsertForm::new(local_user_view.person.id, data.post_id, content.clone())
.creator_id(local_user_view.person.id) };
.language_id(language_id)
.build();
// Create the comment // Create the comment
let parent_path = parent_opt.clone().map(|t| t.path); let parent_path = parent_opt.clone().map(|t| t.path);

View File

@ -26,8 +26,7 @@ pub async fn delete_comment(
comment_id, comment_id,
Some(&local_user_view.local_user), Some(&local_user_view.local_user),
) )
.await? .await?;
.ok_or(LemmyErrorType::CouldntFindComment)?;
// Dont delete it if its already been deleted. // Dont delete it if its already been deleted.
if orig_comment.comment.deleted == data.deleted { if orig_comment.comment.deleted == data.deleted {

View File

@ -31,8 +31,7 @@ pub async fn remove_comment(
comment_id, comment_id,
Some(&local_user_view.local_user), Some(&local_user_view.local_user),
) )
.await? .await?;
.ok_or(LemmyErrorType::CouldntFindComment)?;
check_community_mod_action( check_community_mod_action(
&local_user_view.person, &local_user_view.person,

View File

@ -41,8 +41,7 @@ pub async fn update_comment(
comment_id, comment_id,
Some(&local_user_view.local_user), Some(&local_user_view.local_user),
) )
.await? .await?;
.ok_or(LemmyErrorType::CouldntFindComment)?;
check_community_user_action( check_community_user_action(
&local_user_view.person, &local_user_view.person,

View File

@ -47,9 +47,7 @@ pub async fn create_community(
context: Data<LemmyContext>, context: Data<LemmyContext>,
local_user_view: LocalUserView, local_user_view: LocalUserView,
) -> LemmyResult<Json<CommunityResponse>> { ) -> LemmyResult<Json<CommunityResponse>> {
let site_view = SiteView::read_local(&mut context.pool()) let site_view = SiteView::read_local(&mut context.pool()).await?;
.await?
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
let local_site = site_view.local_site; let local_site = site_view.local_site;
if local_site.community_creation_admin_only && is_admin(&local_user_view).is_err() { if local_site.community_creation_admin_only && is_admin(&local_user_view).is_err() {
@ -90,23 +88,25 @@ pub async fn create_community(
// When you create a community, make sure the user becomes a moderator and a follower // When you create a community, make sure the user becomes a moderator and a follower
let keypair = generate_actor_keypair()?; let keypair = generate_actor_keypair()?;
let community_form = CommunityInsertForm::builder() let community_form = CommunityInsertForm {
.name(data.name.clone()) description,
.title(data.title.clone()) icon,
.description(description) banner,
.icon(icon) nsfw: data.nsfw,
.banner(banner) actor_id: Some(community_actor_id.clone()),
.nsfw(data.nsfw) private_key: Some(keypair.private_key),
.actor_id(Some(community_actor_id.clone())) followers_url: Some(generate_followers_url(&community_actor_id)?),
.private_key(Some(keypair.private_key)) inbox_url: Some(generate_inbox_url(&community_actor_id)?),
.public_key(keypair.public_key) shared_inbox_url: Some(generate_shared_inbox_url(context.settings())?),
.followers_url(Some(generate_followers_url(&community_actor_id)?)) posting_restricted_to_mods: data.posting_restricted_to_mods,
.inbox_url(Some(generate_inbox_url(&community_actor_id)?)) visibility: data.visibility,
.shared_inbox_url(Some(generate_shared_inbox_url(context.settings())?)) ..CommunityInsertForm::new(
.posting_restricted_to_mods(data.posting_restricted_to_mods) site_view.site.instance_id,
.instance_id(site_view.site.instance_id) data.name.clone(),
.visibility(data.visibility) data.title.clone(),
.build(); keypair.public_key,
)
};
let inserted_community = Community::create(&mut context.pool(), &community_form) let inserted_community = Community::create(&mut context.pool(), &community_form)
.await .await

View File

@ -6,7 +6,7 @@ use lemmy_api_common::{
}; };
use lemmy_db_views::structs::{LocalUserView, SiteView}; use lemmy_db_views::structs::{LocalUserView, SiteView};
use lemmy_db_views_actor::community_view::CommunityQuery; use lemmy_db_views_actor::community_view::CommunityQuery;
use lemmy_utils::{error::LemmyResult, LemmyErrorType}; use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))] #[tracing::instrument(skip(context))]
pub async fn list_communities( pub async fn list_communities(
@ -14,9 +14,7 @@ pub async fn list_communities(
context: Data<LemmyContext>, context: Data<LemmyContext>,
local_user_view: Option<LocalUserView>, local_user_view: Option<LocalUserView>,
) -> LemmyResult<Json<ListCommunitiesResponse>> { ) -> LemmyResult<Json<ListCommunitiesResponse>> {
let local_site = SiteView::read_local(&mut context.pool()) let local_site = SiteView::read_local(&mut context.pool()).await?;
.await?
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
let is_admin = local_user_view let is_admin = local_user_view
.as_ref() .as_ref()
.map(|luv| is_admin(luv).is_ok()) .map(|luv| is_admin(luv).is_ok())

View File

@ -51,9 +51,7 @@ pub async fn update_community(
is_valid_body_field(desc, false)?; is_valid_body_field(desc, false)?;
} }
let old_community = Community::read(&mut context.pool(), data.community_id) let old_community = Community::read(&mut context.pool(), data.community_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
let icon = diesel_url_update(data.icon.as_deref())?; let icon = diesel_url_update(data.icon.as_deref())?;
replace_image(&icon, &old_community.icon, &context).await?; replace_image(&icon, &old_community.icon, &context).await?;

View File

@ -5,10 +5,12 @@ use lemmy_api_common::{
custom_emoji::{CreateCustomEmoji, CustomEmojiResponse}, custom_emoji::{CreateCustomEmoji, CustomEmojiResponse},
utils::is_admin, utils::is_admin,
}; };
use lemmy_db_schema::source::{ use lemmy_db_schema::{
custom_emoji::{CustomEmoji, CustomEmojiInsertForm}, source::{
custom_emoji_keyword::{CustomEmojiKeyword, CustomEmojiKeywordInsertForm}, custom_emoji::{CustomEmoji, CustomEmojiInsertForm},
local_site::LocalSite, custom_emoji_keyword::{CustomEmojiKeyword, CustomEmojiKeywordInsertForm},
},
traits::Crud,
}; };
use lemmy_db_views::structs::{CustomEmojiView, LocalUserView}; use lemmy_db_views::structs::{CustomEmojiView, LocalUserView};
use lemmy_utils::error::LemmyResult; use lemmy_utils::error::LemmyResult;
@ -19,24 +21,20 @@ pub async fn create_custom_emoji(
context: Data<LemmyContext>, context: Data<LemmyContext>,
local_user_view: LocalUserView, local_user_view: LocalUserView,
) -> LemmyResult<Json<CustomEmojiResponse>> { ) -> LemmyResult<Json<CustomEmojiResponse>> {
let local_site = LocalSite::read(&mut context.pool()).await?;
// Make sure user is an admin // Make sure user is an admin
is_admin(&local_user_view)?; is_admin(&local_user_view)?;
let emoji_form = CustomEmojiInsertForm::builder() let emoji_form = CustomEmojiInsertForm::new(
.local_site_id(local_site.id) data.shortcode.to_lowercase().trim().to_string(),
.shortcode(data.shortcode.to_lowercase().trim().to_string()) data.clone().image_url.into(),
.alt_text(data.alt_text.to_string()) data.alt_text.to_string(),
.category(data.category.to_string()) data.category.to_string(),
.image_url(data.clone().image_url.into()) );
.build();
let emoji = CustomEmoji::create(&mut context.pool(), &emoji_form).await?; let emoji = CustomEmoji::create(&mut context.pool(), &emoji_form).await?;
let mut keywords = vec![]; let mut keywords = vec![];
for keyword in &data.keywords { for keyword in &data.keywords {
let keyword_form = CustomEmojiKeywordInsertForm::builder() let keyword_form =
.custom_emoji_id(emoji.id) CustomEmojiKeywordInsertForm::new(emoji.id, keyword.to_lowercase().trim().to_string());
.keyword(keyword.to_lowercase().trim().to_string())
.build();
keywords.push(keyword_form); keywords.push(keyword_form);
} }
CustomEmojiKeyword::create(&mut context.pool(), keywords).await?; CustomEmojiKeyword::create(&mut context.pool(), keywords).await?;

View File

@ -6,7 +6,7 @@ use lemmy_api_common::{
utils::is_admin, utils::is_admin,
SuccessResponse, SuccessResponse,
}; };
use lemmy_db_schema::source::custom_emoji::CustomEmoji; use lemmy_db_schema::{source::custom_emoji::CustomEmoji, traits::Crud};
use lemmy_db_views::structs::LocalUserView; use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyResult; use lemmy_utils::error::LemmyResult;

View File

@ -0,0 +1,25 @@
use actix_web::web::{Data, Json, Query};
use lemmy_api_common::{
context::LemmyContext,
custom_emoji::{ListCustomEmojis, ListCustomEmojisResponse},
};
use lemmy_db_views::structs::{CustomEmojiView, LocalUserView};
use lemmy_utils::error::LemmyError;
#[tracing::instrument(skip(context))]
pub async fn list_custom_emojis(
data: Query<ListCustomEmojis>,
local_user_view: Option<LocalUserView>,
context: Data<LemmyContext>,
) -> Result<Json<ListCustomEmojisResponse>, LemmyError> {
let custom_emojis = CustomEmojiView::list(
&mut context.pool(),
&data.category,
data.page,
data.limit,
data.ignore_page_limits.unwrap_or(false),
)
.await?;
Ok(Json(ListCustomEmojisResponse { custom_emojis }))
}

View File

@ -1,3 +1,4 @@
pub mod create; pub mod create;
pub mod delete; pub mod delete;
pub mod list;
pub mod update; pub mod update;

View File

@ -5,10 +5,12 @@ use lemmy_api_common::{
custom_emoji::{CustomEmojiResponse, EditCustomEmoji}, custom_emoji::{CustomEmojiResponse, EditCustomEmoji},
utils::is_admin, utils::is_admin,
}; };
use lemmy_db_schema::source::{ use lemmy_db_schema::{
custom_emoji::{CustomEmoji, CustomEmojiUpdateForm}, source::{
custom_emoji_keyword::{CustomEmojiKeyword, CustomEmojiKeywordInsertForm}, custom_emoji::{CustomEmoji, CustomEmojiUpdateForm},
local_site::LocalSite, custom_emoji_keyword::{CustomEmojiKeyword, CustomEmojiKeywordInsertForm},
},
traits::Crud,
}; };
use lemmy_db_views::structs::{CustomEmojiView, LocalUserView}; use lemmy_db_views::structs::{CustomEmojiView, LocalUserView};
use lemmy_utils::error::LemmyResult; use lemmy_utils::error::LemmyResult;
@ -19,24 +21,20 @@ pub async fn update_custom_emoji(
context: Data<LemmyContext>, context: Data<LemmyContext>,
local_user_view: LocalUserView, local_user_view: LocalUserView,
) -> LemmyResult<Json<CustomEmojiResponse>> { ) -> LemmyResult<Json<CustomEmojiResponse>> {
let local_site = LocalSite::read(&mut context.pool()).await?;
// Make sure user is an admin // Make sure user is an admin
is_admin(&local_user_view)?; is_admin(&local_user_view)?;
let emoji_form = CustomEmojiUpdateForm::builder() let emoji_form = CustomEmojiUpdateForm::new(
.local_site_id(local_site.id) data.clone().image_url.into(),
.alt_text(data.alt_text.to_string()) data.alt_text.to_string(),
.category(data.category.to_string()) data.category.to_string(),
.image_url(data.clone().image_url.into()) );
.build();
let emoji = CustomEmoji::update(&mut context.pool(), data.id, &emoji_form).await?; let emoji = CustomEmoji::update(&mut context.pool(), data.id, &emoji_form).await?;
CustomEmojiKeyword::delete(&mut context.pool(), data.id).await?; CustomEmojiKeyword::delete(&mut context.pool(), data.id).await?;
let mut keywords = vec![]; let mut keywords = vec![];
for keyword in &data.keywords { for keyword in &data.keywords {
let keyword_form = CustomEmojiKeywordInsertForm::builder() let keyword_form =
.custom_emoji_id(emoji.id) CustomEmojiKeywordInsertForm::new(emoji.id, keyword.to_lowercase().trim().to_string());
.keyword(keyword.to_lowercase().trim().to_string())
.build();
keywords.push(keyword_form); keywords.push(keyword_form);
} }
CustomEmojiKeyword::create(&mut context.pool(), keywords).await?; CustomEmojiKeyword::create(&mut context.pool(), keywords).await?;

View File

@ -1,7 +1,9 @@
pub mod comment; pub mod comment;
pub mod community; pub mod community;
pub mod custom_emoji; pub mod custom_emoji;
pub mod oauth_provider;
pub mod post; pub mod post;
pub mod private_message; pub mod private_message;
pub mod site; pub mod site;
pub mod tagline;
pub mod user; pub mod user;

View File

@ -0,0 +1,42 @@
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
context::LemmyContext,
oauth_provider::CreateOAuthProvider,
utils::is_admin,
};
use lemmy_db_schema::{
source::oauth_provider::{OAuthProvider, OAuthProviderInsertForm},
traits::Crud,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyError;
use url::Url;
#[tracing::instrument(skip(context))]
pub async fn create_oauth_provider(
data: Json<CreateOAuthProvider>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<OAuthProvider>, LemmyError> {
// Make sure user is an admin
is_admin(&local_user_view)?;
let cloned_data = data.clone();
let oauth_provider_form = OAuthProviderInsertForm {
display_name: cloned_data.display_name,
issuer: Url::parse(&cloned_data.issuer)?.into(),
authorization_endpoint: Url::parse(&cloned_data.authorization_endpoint)?.into(),
token_endpoint: Url::parse(&cloned_data.token_endpoint)?.into(),
userinfo_endpoint: Url::parse(&cloned_data.userinfo_endpoint)?.into(),
id_claim: cloned_data.id_claim,
client_id: data.client_id.to_string(),
client_secret: data.client_secret.to_string(),
scopes: data.scopes.to_string(),
auto_verify_email: data.auto_verify_email,
account_linking_enabled: data.account_linking_enabled,
enabled: data.enabled,
};
let oauth_provider = OAuthProvider::create(&mut context.pool(), &oauth_provider_form).await?;
Ok(Json(oauth_provider))
}

View File

@ -0,0 +1,25 @@
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
context::LemmyContext,
oauth_provider::DeleteOAuthProvider,
utils::is_admin,
SuccessResponse,
};
use lemmy_db_schema::{source::oauth_provider::OAuthProvider, traits::Crud};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
#[tracing::instrument(skip(context))]
pub async fn delete_oauth_provider(
data: Json<DeleteOAuthProvider>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<SuccessResponse>, LemmyError> {
// Make sure user is an admin
is_admin(&local_user_view)?;
OAuthProvider::delete(&mut context.pool(), data.id)
.await
.with_lemmy_type(LemmyErrorType::CouldntDeleteOauthProvider)?;
Ok(Json(SuccessResponse::default()))
}

View File

@ -0,0 +1,3 @@
pub mod create;
pub mod delete;
pub mod update;

View File

@ -0,0 +1,42 @@
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{context::LemmyContext, oauth_provider::EditOAuthProvider, utils::is_admin};
use lemmy_db_schema::{
source::oauth_provider::{OAuthProvider, OAuthProviderUpdateForm},
traits::Crud,
utils::{diesel_required_string_update, diesel_required_url_update, naive_now},
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyError;
#[tracing::instrument(skip(context))]
pub async fn update_oauth_provider(
data: Json<EditOAuthProvider>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<OAuthProvider>, LemmyError> {
// Make sure user is an admin
is_admin(&local_user_view)?;
let cloned_data = data.clone();
let oauth_provider_form = OAuthProviderUpdateForm {
display_name: diesel_required_string_update(cloned_data.display_name.as_deref()),
authorization_endpoint: diesel_required_url_update(
cloned_data.authorization_endpoint.as_deref(),
)?,
token_endpoint: diesel_required_url_update(cloned_data.token_endpoint.as_deref())?,
userinfo_endpoint: diesel_required_url_update(cloned_data.userinfo_endpoint.as_deref())?,
id_claim: diesel_required_string_update(data.id_claim.as_deref()),
client_secret: diesel_required_string_update(data.client_secret.as_deref()),
scopes: diesel_required_string_update(data.scopes.as_deref()),
auto_verify_email: data.auto_verify_email,
account_linking_enabled: data.account_linking_enabled,
enabled: data.enabled,
updated: Some(Some(naive_now())),
};
let update_result =
OAuthProvider::update(&mut context.pool(), data.id, &oauth_provider_form).await?;
let oauth_provider = OAuthProvider::read(&mut context.pool(), update_result.id).await?;
Ok(Json(oauth_provider))
}

View File

@ -1,3 +1,4 @@
use super::convert_published_time;
use activitypub_federation::config::Data; use activitypub_federation::config::Data;
use actix_web::web::Json; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
@ -92,20 +93,15 @@ pub async fn create_post(
.await?; .await?;
let community_id = data.community_id; let community_id = data.community_id;
let community = Community::read(&mut context.pool(), community_id) let community = Community::read(&mut context.pool(), community_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
if community.posting_restricted_to_mods { if community.posting_restricted_to_mods {
let community_id = data.community_id; let community_id = data.community_id;
let is_mod = CommunityModeratorView::is_community_moderator( CommunityModeratorView::check_is_community_moderator(
&mut context.pool(), &mut context.pool(),
community_id, community_id,
local_user_view.local_user.person_id, local_user_view.local_user.person_id,
) )
.await?; .await?;
if !is_mod {
Err(LemmyErrorType::OnlyModsCanPostInCommunity)?
}
} }
// Only need to check if language is allowed in case user set it explicitly. When using default // Only need to check if language is allowed in case user set it explicitly. When using default
@ -130,26 +126,36 @@ pub async fn create_post(
} }
}; };
let post_form = PostInsertForm::builder() let scheduled_publish_time =
.name(data.name.trim().to_string()) convert_published_time(data.scheduled_publish_time, &local_user_view, &context).await?;
.url(url.map(Into::into)) let post_form = PostInsertForm {
.body(body) url: url.map(Into::into),
.alt_text(data.alt_text.clone()) body,
.community_id(data.community_id) alt_text: data.alt_text.clone(),
.creator_id(local_user_view.person.id) nsfw: data.nsfw,
.nsfw(data.nsfw) language_id,
.language_id(language_id) scheduled_publish_time,
.build(); ..PostInsertForm::new(
data.name.trim().to_string(),
local_user_view.person.id,
data.community_id,
)
};
let inserted_post = Post::create(&mut context.pool(), &post_form) let inserted_post = Post::create(&mut context.pool(), &post_form)
.await .await
.with_lemmy_type(LemmyErrorType::CouldntCreatePost)?; .with_lemmy_type(LemmyErrorType::CouldntCreatePost)?;
let federate_post = if scheduled_publish_time.is_none() {
send_webmention(inserted_post.clone(), community);
|post| Some(SendActivityData::CreatePost(post))
} else {
|_| None
};
generate_post_link_metadata( generate_post_link_metadata(
inserted_post.clone(), inserted_post.clone(),
custom_thumbnail.map(Into::into), custom_thumbnail.map(Into::into),
|post| Some(SendActivityData::CreatePost(post)), federate_post,
Some(local_site),
context.reset_request_count(), context.reset_request_count(),
) )
.await?; .await?;
@ -169,11 +175,14 @@ pub async fn create_post(
mark_post_as_read(person_id, post_id, &mut context.pool()).await?; mark_post_as_read(person_id, post_id, &mut context.pool()).await?;
if let Some(url) = inserted_post.url.clone() { build_post_response(&context, community_id, local_user_view, post_id).await
}
pub fn send_webmention(post: Post, community: Community) {
if let Some(url) = post.url.clone() {
if community.visibility == CommunityVisibility::Public { if community.visibility == CommunityVisibility::Public {
spawn_try_task(async move { spawn_try_task(async move {
let mut webmention = let mut webmention = Webmention::new::<Url>(post.ap_id.clone().into(), url.clone().into())?;
Webmention::new::<Url>(inserted_post.ap_id.clone().into(), url.clone().into())?;
webmention.set_checked(true); webmention.set_checked(true);
match webmention match webmention
.send() .send()
@ -187,6 +196,4 @@ pub async fn create_post(
}); });
} }
}; };
build_post_response(&context, community_id, local_user_view, post_id).await
} }

View File

@ -21,9 +21,7 @@ pub async fn delete_post(
local_user_view: LocalUserView, local_user_view: LocalUserView,
) -> LemmyResult<Json<PostResponse>> { ) -> LemmyResult<Json<PostResponse>> {
let post_id = data.post_id; let post_id = data.post_id;
let orig_post = Post::read(&mut context.pool(), post_id) let orig_post = Post::read(&mut context.pool(), post_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindPost)?;
// Dont delete it if its already been deleted. // Dont delete it if its already been deleted.
if orig_post.deleted == data.deleted { if orig_post.deleted == data.deleted {

View File

@ -1,5 +1,38 @@
use chrono::{DateTime, TimeZone, Utc};
use lemmy_api_common::context::LemmyContext;
use lemmy_db_schema::source::post::Post;
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
pub mod create; pub mod create;
pub mod delete; pub mod delete;
pub mod read; pub mod read;
pub mod remove; pub mod remove;
pub mod update; pub mod update;
async fn convert_published_time(
scheduled_publish_time: Option<i64>,
local_user_view: &LocalUserView,
context: &LemmyContext,
) -> LemmyResult<Option<DateTime<Utc>>> {
const MAX_SCHEDULED_POSTS: i64 = 10;
if let Some(scheduled_publish_time) = scheduled_publish_time {
let converted = Utc
.timestamp_opt(scheduled_publish_time, 0)
.single()
.ok_or(LemmyErrorType::InvalidUnixTime)?;
if converted < Utc::now() {
Err(LemmyErrorType::PostScheduleTimeMustBeInFuture)?;
}
if !local_user_view.local_user.admin {
let count =
Post::user_scheduled_post_count(local_user_view.person.id, &mut context.pool()).await?;
if count >= MAX_SCHEDULED_POSTS {
Err(LemmyErrorType::TooManyScheduledPosts)?;
}
}
Ok(Some(converted))
} else {
Ok(None)
}
}

View File

@ -21,9 +21,7 @@ pub async fn get_post(
context: Data<LemmyContext>, context: Data<LemmyContext>,
local_user_view: Option<LocalUserView>, local_user_view: Option<LocalUserView>,
) -> LemmyResult<Json<GetPostResponse>> { ) -> LemmyResult<Json<GetPostResponse>> {
let local_site = SiteView::read_local(&mut context.pool()) let local_site = SiteView::read_local(&mut context.pool()).await?;
.await?
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
check_private_instance(&local_user_view, &local_site.local_site)?; check_private_instance(&local_user_view, &local_site.local_site)?;
@ -35,16 +33,14 @@ pub async fn get_post(
} else if let Some(comment_id) = data.comment_id { } else if let Some(comment_id) = data.comment_id {
Comment::read(&mut context.pool(), comment_id) Comment::read(&mut context.pool(), comment_id)
.await? .await?
.ok_or(LemmyErrorType::CouldntFindComment)?
.post_id .post_id
} else { } else {
Err(LemmyErrorType::CouldntFindPost)? Err(LemmyErrorType::NotFound)?
}; };
// Check to see if the person is a mod or admin, to show deleted / removed // Check to see if the person is a mod or admin, to show deleted / removed
let community_id = Post::read(&mut context.pool(), post_id) let community_id = Post::read_xx(&mut context.pool(), post_id)
.await? .await?
.ok_or(LemmyErrorType::CouldntFindPost)?
.community_id; .community_id;
let is_mod_or_admin = is_mod_or_admin_opt( let is_mod_or_admin = is_mod_or_admin_opt(
@ -62,8 +58,7 @@ pub async fn get_post(
local_user.as_ref(), local_user.as_ref(),
is_mod_or_admin, is_mod_or_admin,
) )
.await? .await?;
.ok_or(LemmyErrorType::CouldntFindPost)?;
let post_id = post_view.post.id; let post_id = post_view.post.id;
if let Some(person_id) = person_id { if let Some(person_id) = person_id {
@ -85,15 +80,15 @@ pub async fn get_post(
local_user.as_ref(), local_user.as_ref(),
is_mod_or_admin, is_mod_or_admin,
) )
.await? .await?;
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
let moderators = CommunityModeratorView::for_community(&mut context.pool(), community_id).await?; let moderators = CommunityModeratorView::for_community(&mut context.pool(), community_id).await?;
// Fetch the cross_posts // Fetch the cross_posts
let cross_posts = if let Some(url) = &post_view.post.url { let cross_posts = if let Some(url) = &post_view.post.url {
let mut x_posts = PostQuery { let mut x_posts = PostQuery {
url_search: Some(url.inner().as_str().into()), url_only: Some(true),
search_term: Some(url.inner().as_str().into()),
local_user: local_user.as_ref(), local_user: local_user.as_ref(),
..Default::default() ..Default::default()
} }

View File

@ -17,7 +17,7 @@ use lemmy_db_schema::{
traits::{Crud, Reportable}, traits::{Crud, Reportable},
}; };
use lemmy_db_views::structs::LocalUserView; use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::{error::LemmyResult, LemmyErrorType}; use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))] #[tracing::instrument(skip(context))]
pub async fn remove_post( pub async fn remove_post(
@ -26,9 +26,7 @@ pub async fn remove_post(
local_user_view: LocalUserView, local_user_view: LocalUserView,
) -> LemmyResult<Json<PostResponse>> { ) -> LemmyResult<Json<PostResponse>> {
let post_id = data.post_id; let post_id = data.post_id;
let orig_post = Post::read(&mut context.pool(), post_id) let orig_post = Post::read(&mut context.pool(), post_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindPost)?;
check_community_mod_action( check_community_mod_action(
&local_user_view.person, &local_user_view.person,

View File

@ -1,3 +1,4 @@
use super::{convert_published_time, create::send_webmention};
use activitypub_federation::config::Data; use activitypub_federation::config::Data;
use actix_web::web::Json; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
@ -16,6 +17,7 @@ use lemmy_api_common::{
use lemmy_db_schema::{ use lemmy_db_schema::{
source::{ source::{
actor_language::CommunityLanguage, actor_language::CommunityLanguage,
community::Community,
local_site::LocalSite, local_site::LocalSite,
post::{Post, PostUpdateForm}, post::{Post, PostUpdateForm},
}, },
@ -85,9 +87,7 @@ pub async fn update_post(
} }
let post_id = data.post_id; let post_id = data.post_id;
let orig_post = Post::read(&mut context.pool(), post_id) let orig_post = Post::read(&mut context.pool(), post_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindPost)?;
check_community_user_action( check_community_user_action(
&local_user_view.person, &local_user_view.person,
@ -109,6 +109,21 @@ pub async fn update_post(
) )
.await?; .await?;
// handle changes to scheduled_publish_time
let scheduled_publish_time = match (
orig_post.scheduled_publish_time,
data.scheduled_publish_time,
) {
// schedule time can be changed if post is still scheduled (and not published yet)
(Some(_), Some(_)) => {
Some(convert_published_time(data.scheduled_publish_time, &local_user_view, &context).await?)
}
// post was scheduled, gets changed to publish immediately
(Some(_), None) => Some(None),
// unchanged
(_, _) => None,
};
let post_form = PostUpdateForm { let post_form = PostUpdateForm {
name: data.name.clone(), name: data.name.clone(),
url, url,
@ -117,6 +132,7 @@ pub async fn update_post(
nsfw: data.nsfw, nsfw: data.nsfw,
language_id: data.language_id, language_id: data.language_id,
updated: Some(Some(naive_now())), updated: Some(Some(naive_now())),
scheduled_publish_time,
..Default::default() ..Default::default()
}; };
@ -125,14 +141,36 @@ pub async fn update_post(
.await .await
.with_lemmy_type(LemmyErrorType::CouldntUpdatePost)?; .with_lemmy_type(LemmyErrorType::CouldntUpdatePost)?;
generate_post_link_metadata( // send out federation/webmention if necessary
updated_post.clone(), match (
custom_thumbnail.flatten().map(Into::into), orig_post.scheduled_publish_time,
|post| Some(SendActivityData::UpdatePost(post)), data.scheduled_publish_time,
Some(local_site), ) {
context.reset_request_count(), // schedule was removed, send create activity and webmention
) (Some(_), None) => {
.await?; let community = Community::read(&mut context.pool(), orig_post.community_id).await?;
send_webmention(updated_post.clone(), community);
generate_post_link_metadata(
updated_post.clone(),
custom_thumbnail.flatten().map(Into::into),
|post| Some(SendActivityData::CreatePost(post)),
context.reset_request_count(),
)
.await?;
}
// post was already public, send update
(None, _) => {
generate_post_link_metadata(
updated_post.clone(),
custom_thumbnail.flatten().map(Into::into),
|post| Some(SendActivityData::UpdatePost(post)),
context.reset_request_count(),
)
.await?
}
// schedule was changed, do nothing
(Some(_), Some(_)) => {}
};
build_post_response( build_post_response(
context.deref(), context.deref(),

View File

@ -5,7 +5,6 @@ use lemmy_api_common::{
private_message::{CreatePrivateMessage, PrivateMessageResponse}, private_message::{CreatePrivateMessage, PrivateMessageResponse},
send_activity::{ActivityChannel, SendActivityData}, send_activity::{ActivityChannel, SendActivityData},
utils::{ utils::{
check_person_block,
get_interface_language, get_interface_language,
get_url_blocklist, get_url_blocklist,
local_site_to_slur_regex, local_site_to_slur_regex,
@ -16,6 +15,7 @@ use lemmy_api_common::{
use lemmy_db_schema::{ use lemmy_db_schema::{
source::{ source::{
local_site::LocalSite, local_site::LocalSite,
person_block::PersonBlock,
private_message::{PrivateMessage, PrivateMessageInsertForm}, private_message::{PrivateMessage, PrivateMessageInsertForm},
}, },
traits::Crud, traits::Crud,
@ -39,33 +39,29 @@ pub async fn create_private_message(
let content = process_markdown(&data.content, &slur_regex, &url_blocklist, &context).await?; let content = process_markdown(&data.content, &slur_regex, &url_blocklist, &context).await?;
is_valid_body_field(&content, false)?; is_valid_body_field(&content, false)?;
check_person_block( PersonBlock::read(
local_user_view.person.id,
data.recipient_id,
&mut context.pool(), &mut context.pool(),
data.recipient_id,
local_user_view.person.id,
) )
.await?; .await?;
let private_message_form = PrivateMessageInsertForm::builder() let private_message_form = PrivateMessageInsertForm::new(
.content(content.clone()) local_user_view.person.id,
.creator_id(local_user_view.person.id) data.recipient_id,
.recipient_id(data.recipient_id) content.clone(),
.build(); );
let inserted_private_message = PrivateMessage::create(&mut context.pool(), &private_message_form) let inserted_private_message = PrivateMessage::create(&mut context.pool(), &private_message_form)
.await .await
.with_lemmy_type(LemmyErrorType::CouldntCreatePrivateMessage)?; .with_lemmy_type(LemmyErrorType::CouldntCreatePrivateMessage)?;
let view = PrivateMessageView::read(&mut context.pool(), inserted_private_message.id) let view = PrivateMessageView::read(&mut context.pool(), inserted_private_message.id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindPrivateMessage)?;
// Send email to the local recipient, if one exists // Send email to the local recipient, if one exists
if view.recipient.local { if view.recipient.local {
let recipient_id = data.recipient_id; let recipient_id = data.recipient_id;
let local_recipient = LocalUserView::read_person(&mut context.pool(), recipient_id) let local_recipient = LocalUserView::read_person(&mut context.pool(), recipient_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindPerson)?;
let lang = get_interface_language(&local_recipient); let lang = get_interface_language(&local_recipient);
let inbox_link = format!("{}/inbox", context.settings().get_protocol_and_hostname()); let inbox_link = format!("{}/inbox", context.settings().get_protocol_and_hostname());
let sender_name = &local_user_view.person.name; let sender_name = &local_user_view.person.name;

View File

@ -20,9 +20,7 @@ pub async fn delete_private_message(
) -> LemmyResult<Json<PrivateMessageResponse>> { ) -> LemmyResult<Json<PrivateMessageResponse>> {
// Checking permissions // Checking permissions
let private_message_id = data.private_message_id; let private_message_id = data.private_message_id;
let orig_private_message = PrivateMessage::read(&mut context.pool(), private_message_id) let orig_private_message = PrivateMessage::read(&mut context.pool(), private_message_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindPrivateMessage)?;
if local_user_view.person.id != orig_private_message.creator_id { if local_user_view.person.id != orig_private_message.creator_id {
Err(LemmyErrorType::EditPrivateMessageNotAllowed)? Err(LemmyErrorType::EditPrivateMessageNotAllowed)?
} }
@ -47,9 +45,7 @@ pub async fn delete_private_message(
) )
.await?; .await?;
let view = PrivateMessageView::read(&mut context.pool(), private_message_id) let view = PrivateMessageView::read(&mut context.pool(), private_message_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindPrivateMessage)?;
Ok(Json(PrivateMessageResponse { Ok(Json(PrivateMessageResponse {
private_message_view: view, private_message_view: view,
})) }))

View File

@ -30,9 +30,7 @@ pub async fn update_private_message(
// Checking permissions // Checking permissions
let private_message_id = data.private_message_id; let private_message_id = data.private_message_id;
let orig_private_message = PrivateMessage::read(&mut context.pool(), private_message_id) let orig_private_message = PrivateMessage::read(&mut context.pool(), private_message_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindPrivateMessage)?;
if local_user_view.person.id != orig_private_message.creator_id { if local_user_view.person.id != orig_private_message.creator_id {
Err(LemmyErrorType::EditPrivateMessageNotAllowed)? Err(LemmyErrorType::EditPrivateMessageNotAllowed)?
} }
@ -56,9 +54,7 @@ pub async fn update_private_message(
.await .await
.with_lemmy_type(LemmyErrorType::CouldntUpdatePrivateMessage)?; .with_lemmy_type(LemmyErrorType::CouldntUpdatePrivateMessage)?;
let view = PrivateMessageView::read(&mut context.pool(), private_message_id) let view = PrivateMessageView::read(&mut context.pool(), private_message_id).await?;
.await?
.ok_or(LemmyErrorType::CouldntFindPrivateMessage)?;
ActivityChannel::submit_activity( ActivityChannel::submit_activity(
SendActivityData::UpdatePrivateMessage(view.clone()), SendActivityData::UpdatePrivateMessage(view.clone()),

View File

@ -1,3 +1,4 @@
use super::not_zero;
use crate::site::{application_question_check, site_default_post_listing_type_check}; use crate::site::{application_question_check, site_default_post_listing_type_check};
use activitypub_federation::{config::Data, http_signatures::generate_actor_keypair}; use activitypub_federation::{config::Data, http_signatures::generate_actor_keypair};
use actix_web::web::Json; use actix_web::web::Json;
@ -20,7 +21,6 @@ use lemmy_db_schema::{
local_site::{LocalSite, LocalSiteUpdateForm}, local_site::{LocalSite, LocalSiteUpdateForm},
local_site_rate_limit::{LocalSiteRateLimit, LocalSiteRateLimitUpdateForm}, local_site_rate_limit::{LocalSiteRateLimit, LocalSiteRateLimitUpdateForm},
site::{Site, SiteUpdateForm}, site::{Site, SiteUpdateForm},
tagline::Tagline,
}, },
traits::Crud, traits::Crud,
utils::{diesel_string_update, diesel_url_create, naive_now}, utils::{diesel_string_update, diesel_url_create, naive_now},
@ -92,14 +92,14 @@ pub async fn create_site(
site_setup: Some(true), site_setup: Some(true),
enable_downvotes: data.enable_downvotes, enable_downvotes: data.enable_downvotes,
registration_mode: data.registration_mode, registration_mode: data.registration_mode,
enable_nsfw: data.enable_nsfw,
community_creation_admin_only: data.community_creation_admin_only, community_creation_admin_only: data.community_creation_admin_only,
require_email_verification: data.require_email_verification, require_email_verification: data.require_email_verification,
application_question: diesel_string_update(data.application_question.as_deref()), application_question: diesel_string_update(data.application_question.as_deref()),
private_instance: data.private_instance, private_instance: data.private_instance,
default_theme: data.default_theme.clone(), default_theme: data.default_theme.clone(),
default_post_listing_type: data.default_post_listing_type, default_post_listing_type: data.default_post_listing_type,
default_sort_type: data.default_sort_type, default_post_sort_type: data.default_post_sort_type,
default_comment_sort_type: data.default_comment_sort_type,
legal_information: diesel_string_update(data.legal_information.as_deref()), legal_information: diesel_string_update(data.legal_information.as_deref()),
application_email_admins: data.application_email_admins, application_email_admins: data.application_email_admins,
hide_modlog_mod_names: data.hide_modlog_mod_names, hide_modlog_mod_names: data.hide_modlog_mod_names,
@ -117,28 +117,23 @@ pub async fn create_site(
let local_site_rate_limit_form = LocalSiteRateLimitUpdateForm { let local_site_rate_limit_form = LocalSiteRateLimitUpdateForm {
message: data.rate_limit_message, message: data.rate_limit_message,
message_per_second: data.rate_limit_message_per_second, message_per_second: not_zero(data.rate_limit_message_per_second),
post: data.rate_limit_post, post: data.rate_limit_post,
post_per_second: data.rate_limit_post_per_second, post_per_second: not_zero(data.rate_limit_post_per_second),
register: data.rate_limit_register, register: data.rate_limit_register,
register_per_second: data.rate_limit_register_per_second, register_per_second: not_zero(data.rate_limit_register_per_second),
image: data.rate_limit_image, image: data.rate_limit_image,
image_per_second: data.rate_limit_image_per_second, image_per_second: not_zero(data.rate_limit_image_per_second),
comment: data.rate_limit_comment, comment: data.rate_limit_comment,
comment_per_second: data.rate_limit_comment_per_second, comment_per_second: not_zero(data.rate_limit_comment_per_second),
search: data.rate_limit_search, search: data.rate_limit_search,
search_per_second: data.rate_limit_search_per_second, search_per_second: not_zero(data.rate_limit_search_per_second),
..Default::default() ..Default::default()
}; };
LocalSiteRateLimit::update(&mut context.pool(), &local_site_rate_limit_form).await?; LocalSiteRateLimit::update(&mut context.pool(), &local_site_rate_limit_form).await?;
let site_view = SiteView::read_local(&mut context.pool()) let site_view = SiteView::read_local(&mut context.pool()).await?;
.await?
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
let new_taglines = data.taglines.clone();
let taglines = Tagline::replace(&mut context.pool(), local_site.id, new_taglines).await?;
let rate_limit_config = let rate_limit_config =
local_site_rate_limit_to_rate_limit_config(&site_view.local_site_rate_limit); local_site_rate_limit_to_rate_limit_config(&site_view.local_site_rate_limit);
@ -146,7 +141,7 @@ pub async fn create_site(
Ok(Json(SiteResponse { Ok(Json(SiteResponse {
site_view, site_view,
taglines, taglines: vec![],
})) }))
} }
@ -197,13 +192,16 @@ fn validate_create_payload(local_site: &LocalSite, create_site: &CreateSite) ->
} }
#[cfg(test)] #[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests { mod tests {
use crate::site::create::validate_create_payload; use crate::site::create::validate_create_payload;
use lemmy_api_common::site::CreateSite; use lemmy_api_common::site::CreateSite;
use lemmy_db_schema::{source::local_site::LocalSite, ListingType, RegistrationMode, SortType}; use lemmy_db_schema::{
source::local_site::LocalSite,
ListingType,
PostSortType,
RegistrationMode,
};
use lemmy_utils::error::LemmyErrorType; use lemmy_utils::error::LemmyErrorType;
#[test] #[test]
@ -212,170 +210,114 @@ mod tests {
( (
"CreateSite attempted on set up LocalSite", "CreateSite attempted on set up LocalSite",
LemmyErrorType::SiteAlreadyExists, LemmyErrorType::SiteAlreadyExists,
&generate_local_site( &LocalSite {
true, site_setup: true,
None::<String>, private_instance: true,
true, federation_enabled: false,
false, registration_mode: RegistrationMode::Open,
None::<String>, ..Default::default()
RegistrationMode::Open, },
), &CreateSite {
&generate_create_site( name: String::from("site_name"),
String::from("site_name"), ..Default::default()
None::<String>, },
None::<String>,
None::<ListingType>,
None::<SortType>,
None::<String>,
None::<bool>,
None::<bool>,
None::<String>,
None::<RegistrationMode>,
),
), ),
( (
"CreateSite name matches LocalSite slur filter", "CreateSite name matches LocalSite slur filter",
LemmyErrorType::Slurs, LemmyErrorType::Slurs,
&generate_local_site( &LocalSite {
false, site_setup: false,
Some(String::from("(foo|bar)")), private_instance: true,
true, slur_filter_regex: Some(String::from("(foo|bar)")),
false, federation_enabled: false,
None::<String>, registration_mode: RegistrationMode::Open,
RegistrationMode::Open, ..Default::default()
), },
&generate_create_site( &CreateSite {
String::from("foo site_name"), name: String::from("foo site_name"),
None::<String>, ..Default::default()
None::<String>, },
None::<ListingType>,
None::<SortType>,
None::<String>,
None::<bool>,
None::<bool>,
None::<String>,
None::<RegistrationMode>,
),
), ),
( (
"CreateSite name matches new slur filter", "CreateSite name matches new slur filter",
LemmyErrorType::Slurs, LemmyErrorType::Slurs,
&generate_local_site( &LocalSite {
false, site_setup: false,
Some(String::from("(foo|bar)")), private_instance: true,
true, slur_filter_regex: Some(String::from("(foo|bar)")),
false, federation_enabled: false,
None::<String>, registration_mode: RegistrationMode::Open,
RegistrationMode::Open, ..Default::default()
), },
&generate_create_site( &CreateSite {
String::from("zeta site_name"), name: String::from("zeta site_name"),
None::<String>, slur_filter_regex: Some(String::from("(zeta|alpha)")),
None::<String>, ..Default::default()
None::<ListingType>, },
None::<SortType>,
Some(String::from("(zeta|alpha)")),
None::<bool>,
None::<bool>,
None::<String>,
None::<RegistrationMode>,
),
), ),
( (
"CreateSite listing type is Subscribed, which is invalid", "CreateSite listing type is Subscribed, which is invalid",
LemmyErrorType::InvalidDefaultPostListingType, LemmyErrorType::InvalidDefaultPostListingType,
&generate_local_site( &LocalSite {
false, site_setup: false,
None::<String>, private_instance: true,
true, federation_enabled: false,
false, registration_mode: RegistrationMode::Open,
None::<String>, ..Default::default()
RegistrationMode::Open, },
), &CreateSite {
&generate_create_site( name: String::from("site_name"),
String::from("site_name"), default_post_listing_type: Some(ListingType::Subscribed),
None::<String>, ..Default::default()
None::<String>, },
Some(ListingType::Subscribed),
None::<SortType>,
None::<String>,
None::<bool>,
None::<bool>,
None::<String>,
None::<RegistrationMode>,
),
), ),
( (
"CreateSite is both private and federated", "CreateSite is both private and federated",
LemmyErrorType::CantEnablePrivateInstanceAndFederationTogether, LemmyErrorType::CantEnablePrivateInstanceAndFederationTogether,
&generate_local_site( &LocalSite {
false, site_setup: false,
None::<String>, private_instance: true,
true, federation_enabled: false,
false, ..Default::default()
None::<String>, },
RegistrationMode::Open, &CreateSite {
), name: String::from("site_name"),
&generate_create_site( private_instance: Some(true),
String::from("site_name"), federation_enabled: Some(true),
None::<String>, ..Default::default()
None::<String>, },
None::<ListingType>,
None::<SortType>,
None::<String>,
Some(true),
Some(true),
None::<String>,
None::<RegistrationMode>,
),
), ),
( (
"LocalSite is private, but CreateSite also makes it federated", "LocalSite is private, but CreateSite also makes it federated",
LemmyErrorType::CantEnablePrivateInstanceAndFederationTogether, LemmyErrorType::CantEnablePrivateInstanceAndFederationTogether,
&generate_local_site( &LocalSite {
false, site_setup: false,
None::<String>, private_instance: true,
true, federation_enabled: false,
false, registration_mode: RegistrationMode::Open,
None::<String>, ..Default::default()
RegistrationMode::Open, },
), &CreateSite {
&generate_create_site( name: String::from("site_name"),
String::from("site_name"), federation_enabled: Some(true),
None::<String>, ..Default::default()
None::<String>, },
None::<ListingType>,
None::<SortType>,
None::<String>,
None::<bool>,
Some(true),
None::<String>,
None::<RegistrationMode>,
),
), ),
( (
"CreateSite requires application, but neither it nor LocalSite has an application question", "CreateSite requires application, but neither it nor LocalSite has an application question",
LemmyErrorType::ApplicationQuestionRequired, LemmyErrorType::ApplicationQuestionRequired,
&generate_local_site( &LocalSite {
false, site_setup: false,
None::<String>, private_instance: true,
true, federation_enabled: false,
false, registration_mode: RegistrationMode::Open,
None::<String>, ..Default::default()
RegistrationMode::Open, },
), &CreateSite {
&generate_create_site( name: String::from("site_name"),
String::from("site_name"), registration_mode: Some(RegistrationMode::RequireApplication),
None::<String>, ..Default::default()
None::<String>, },
None::<ListingType>,
None::<SortType>,
None::<String>,
None::<bool>,
None::<bool>,
None::<String>,
Some(RegistrationMode::RequireApplication),
),
), ),
]; ];
@ -414,95 +356,72 @@ mod tests {
let valid_payloads = [ let valid_payloads = [
( (
"No changes between LocalSite and CreateSite", "No changes between LocalSite and CreateSite",
&generate_local_site( &LocalSite {
false, site_setup: false,
None::<String>, private_instance: true,
true, federation_enabled: false,
false, registration_mode: RegistrationMode::Open,
None::<String>, ..Default::default()
RegistrationMode::Open, },
), &CreateSite {
&generate_create_site( name: String::from("site_name"),
String::from("site_name"), ..Default::default()
None::<String>, },
None::<String>,
None::<ListingType>,
None::<SortType>,
None::<String>,
None::<bool>,
None::<bool>,
None::<String>,
None::<RegistrationMode>,
),
), ),
( (
"CreateSite allows clearing and changing values", "CreateSite allows clearing and changing values",
&generate_local_site( &LocalSite {
false, site_setup: false,
None::<String>, private_instance: true,
true, federation_enabled: false,
false, registration_mode: RegistrationMode::Open,
None::<String>, ..Default::default()
RegistrationMode::Open, },
), &CreateSite {
&generate_create_site( name: String::from("site_name"),
String::from("site_name"), sidebar: Some(String::new()),
Some(String::new()), description: Some(String::new()),
Some(String::new()), application_question: Some(String::new()),
Some(ListingType::All), private_instance: Some(false),
Some(SortType::Active), default_post_listing_type: Some(ListingType::All),
Some(String::new()), default_post_sort_type: Some(PostSortType::Active),
Some(false), slur_filter_regex: Some(String::new()),
Some(true), federation_enabled: Some(true),
Some(String::new()), registration_mode: Some(RegistrationMode::Open),
Some(RegistrationMode::Open), ..Default::default()
), },
), ),
( (
"CreateSite clears existing slur filter regex", "CreateSite clears existing slur filter regex",
&generate_local_site( &LocalSite {
false, site_setup: false,
Some(String::from("(foo|bar)")), private_instance: true,
true, slur_filter_regex: Some(String::from("(foo|bar)")),
false, federation_enabled: false,
None::<String>, registration_mode: RegistrationMode::Open,
RegistrationMode::Open, ..Default::default()
), },
&generate_create_site( &CreateSite {
String::from("foo site_name"), name: String::from("foo site_name"),
None::<String>, slur_filter_regex: Some(String::new()),
None::<String>, ..Default::default()
None::<ListingType>, },
None::<SortType>,
Some(String::new()),
None::<bool>,
None::<bool>,
None::<String>,
None::<RegistrationMode>,
),
), ),
( (
"LocalSite has application question and CreateSite now requires applications,", "LocalSite has application question and CreateSite now requires applications,",
&generate_local_site( &LocalSite {
false, site_setup: false,
None::<String>, application_question: Some(String::from("question")),
true, private_instance: true,
false, federation_enabled: false,
Some(String::from("question")), registration_mode: RegistrationMode::Open,
RegistrationMode::Open, ..Default::default()
), },
&generate_create_site( &CreateSite {
String::from("site_name"), name: String::from("site_name"),
None::<String>, registration_mode: Some(RegistrationMode::RequireApplication),
None::<String>, ..Default::default()
None::<ListingType>, },
None::<SortType>,
None::<String>,
None::<bool>,
None::<bool>,
None::<String>,
Some(RegistrationMode::RequireApplication),
),
), ),
]; ];
@ -518,84 +437,4 @@ mod tests {
); );
}) })
} }
fn generate_local_site(
site_setup: bool,
site_slur_filter_regex: Option<String>,
site_is_private: bool,
site_is_federated: bool,
site_application_question: Option<String>,
site_registration_mode: RegistrationMode,
) -> LocalSite {
LocalSite {
site_setup,
application_question: site_application_question,
private_instance: site_is_private,
slur_filter_regex: site_slur_filter_regex,
federation_enabled: site_is_federated,
registration_mode: site_registration_mode,
..Default::default()
}
}
// Allow the test helper function to have too many arguments.
// It's either this or generate the entire struct each time for testing.
#[allow(clippy::too_many_arguments)]
fn generate_create_site(
site_name: String,
site_description: Option<String>,
site_sidebar: Option<String>,
site_listing_type: Option<ListingType>,
site_sort_type: Option<SortType>,
site_slur_filter_regex: Option<String>,
site_is_private: Option<bool>,
site_is_federated: Option<bool>,
site_application_question: Option<String>,
site_registration_mode: Option<RegistrationMode>,
) -> CreateSite {
CreateSite {
name: site_name,
sidebar: site_sidebar,
description: site_description,
icon: None,
banner: None,
enable_downvotes: None,
enable_nsfw: None,
community_creation_admin_only: None,
require_email_verification: None,
application_question: site_application_question,
private_instance: site_is_private,
default_theme: None,
default_post_listing_type: site_listing_type,
default_sort_type: site_sort_type,
legal_information: None,
application_email_admins: None,
hide_modlog_mod_names: None,
discussion_languages: None,
slur_filter_regex: site_slur_filter_regex,
actor_name_max_length: None,
rate_limit_message: None,
rate_limit_message_per_second: None,
rate_limit_post: None,
rate_limit_post_per_second: None,
rate_limit_register: None,
rate_limit_register_per_second: None,
rate_limit_image: None,
rate_limit_image_per_second: None,
rate_limit_comment: None,
rate_limit_comment_per_second: None,
rate_limit_search: None,
rate_limit_search_per_second: None,
federation_enabled: site_is_federated,
federation_debug: None,
captcha_enabled: None,
captcha_difficulty: None,
allowed_instances: None,
blocked_instances: None,
taglines: None,
registration_mode: site_registration_mode,
content_warning: None,
default_post_listing_mode: None,
}
}
} }

View File

@ -40,12 +40,17 @@ pub fn application_question_check(
} }
} }
fn not_zero(val: Option<i32>) -> Option<i32> {
match val {
Some(0) => None,
v => v,
}
}
#[cfg(test)] #[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests { mod tests {
use crate::site::{application_question_check, site_default_post_listing_type_check}; use crate::site::{application_question_check, not_zero, site_default_post_listing_type_check};
use lemmy_db_schema::{ListingType, RegistrationMode}; use lemmy_db_schema::{ListingType, RegistrationMode};
#[test] #[test]
@ -93,4 +98,11 @@ mod tests {
RegistrationMode::RequireApplication RegistrationMode::RequireApplication
); );
} }
#[test]
fn test_not_zero() {
assert_eq!(None, not_zero(None));
assert_eq!(None, not_zero(Some(0)));
assert_eq!(Some(5), not_zero(Some(5)));
}
} }

View File

@ -9,10 +9,11 @@ use lemmy_db_schema::source::{
instance_block::InstanceBlock, instance_block::InstanceBlock,
language::Language, language::Language,
local_site_url_blocklist::LocalSiteUrlBlocklist, local_site_url_blocklist::LocalSiteUrlBlocklist,
oauth_provider::OAuthProvider,
person_block::PersonBlock, person_block::PersonBlock,
tagline::Tagline, tagline::Tagline,
}; };
use lemmy_db_views::structs::{CustomEmojiView, LocalUserView, SiteView}; use lemmy_db_views::structs::{LocalUserView, SiteView};
use lemmy_db_views_actor::structs::{CommunityFollowerView, CommunityModeratorView, PersonView}; use lemmy_db_views_actor::structs::{CommunityFollowerView, CommunityModeratorView, PersonView};
use lemmy_utils::{ use lemmy_utils::{
error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult}, error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult},
@ -37,16 +38,16 @@ pub async fn get_site(
// This data is independent from the user account so we can cache it across requests // This data is independent from the user account so we can cache it across requests
let mut site_response = CACHE let mut site_response = CACHE
.try_get_with::<_, LemmyError>((), async { .try_get_with::<_, LemmyError>((), async {
let site_view = SiteView::read_local(&mut context.pool()) let site_view = SiteView::read_local(&mut context.pool()).await?;
.await?
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
let admins = PersonView::admins(&mut context.pool()).await?; let admins = PersonView::admins(&mut context.pool()).await?;
let all_languages = Language::read_all(&mut context.pool()).await?; let all_languages = Language::read_all(&mut context.pool()).await?;
let discussion_languages = SiteLanguage::read_local_raw(&mut context.pool()).await?; let discussion_languages = SiteLanguage::read_local_raw(&mut context.pool()).await?;
let taglines = Tagline::get_all(&mut context.pool(), site_view.local_site.id).await?;
let custom_emojis =
CustomEmojiView::get_all(&mut context.pool(), site_view.local_site.id).await?;
let blocked_urls = LocalSiteUrlBlocklist::get_all(&mut context.pool()).await?; let blocked_urls = LocalSiteUrlBlocklist::get_all(&mut context.pool()).await?;
let tagline = Tagline::get_random(&mut context.pool()).await?;
let admin_oauth_providers = OAuthProvider::get_all(&mut context.pool()).await?;
let oauth_providers =
OAuthProvider::convert_providers_to_public(admin_oauth_providers.clone());
Ok(GetSiteResponse { Ok(GetSiteResponse {
site_view, site_view,
admins, admins,
@ -54,16 +55,19 @@ pub async fn get_site(
my_user: None, my_user: None,
all_languages, all_languages,
discussion_languages, discussion_languages,
taglines,
custom_emojis,
blocked_urls, blocked_urls,
tagline,
oauth_providers: Some(oauth_providers),
admin_oauth_providers: Some(admin_oauth_providers),
taglines: vec![],
custom_emojis: vec![],
}) })
}) })
.await .await
.map_err(|e| anyhow::anyhow!("Failed to construct site response: {e}"))?; .map_err(|e| anyhow::anyhow!("Failed to construct site response: {e}"))?;
// Build the local user with parallel queries and add it to site response // Build the local user with parallel queries and add it to site response
site_response.my_user = if let Some(local_user_view) = local_user_view { site_response.my_user = if let Some(ref local_user_view) = local_user_view {
let person_id = local_user_view.person.id; let person_id = local_user_view.person.id;
let local_user_id = local_user_view.local_user.id; let local_user_id = local_user_view.local_user.id;
let pool = &mut context.pool(); let pool = &mut context.pool();
@ -86,7 +90,7 @@ pub async fn get_site(
.with_lemmy_type(LemmyErrorType::SystemErrLogin)?; .with_lemmy_type(LemmyErrorType::SystemErrLogin)?;
Some(MyUserInfo { Some(MyUserInfo {
local_user_view, local_user_view: local_user_view.clone(),
follows, follows,
moderates, moderates,
community_blocks, community_blocks,
@ -98,5 +102,13 @@ pub async fn get_site(
None None
}; };
// filter oauth_providers for public access
if !local_user_view
.map(|l| l.local_user.admin)
.unwrap_or_default()
{
site_response.admin_oauth_providers = None;
}
Ok(Json(site_response)) Ok(Json(site_response))
} }

View File

@ -1,3 +1,4 @@
use super::not_zero;
use crate::site::{application_question_check, site_default_post_listing_type_check}; use crate::site::{application_question_check, site_default_post_listing_type_check};
use activitypub_federation::config::Data; use activitypub_federation::config::Data;
use actix_web::web::Json; use actix_web::web::Json;
@ -24,7 +25,6 @@ use lemmy_db_schema::{
local_site_url_blocklist::LocalSiteUrlBlocklist, local_site_url_blocklist::LocalSiteUrlBlocklist,
local_user::LocalUser, local_user::LocalUser,
site::{Site, SiteUpdateForm}, site::{Site, SiteUpdateForm},
tagline::Tagline,
}, },
traits::Crud, traits::Crud,
utils::{diesel_string_update, diesel_url_update, naive_now}, utils::{diesel_string_update, diesel_url_update, naive_now},
@ -52,9 +52,7 @@ pub async fn update_site(
context: Data<LemmyContext>, context: Data<LemmyContext>,
local_user_view: LocalUserView, local_user_view: LocalUserView,
) -> LemmyResult<Json<SiteResponse>> { ) -> LemmyResult<Json<SiteResponse>> {
let site_view = SiteView::read_local(&mut context.pool()) let site_view = SiteView::read_local(&mut context.pool()).await?;
.await?
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
let local_site = site_view.local_site; let local_site = site_view.local_site;
let site = site_view.site; let site = site_view.site;
@ -103,14 +101,14 @@ pub async fn update_site(
let local_site_form = LocalSiteUpdateForm { let local_site_form = LocalSiteUpdateForm {
enable_downvotes: data.enable_downvotes, enable_downvotes: data.enable_downvotes,
registration_mode: data.registration_mode, registration_mode: data.registration_mode,
enable_nsfw: data.enable_nsfw,
community_creation_admin_only: data.community_creation_admin_only, community_creation_admin_only: data.community_creation_admin_only,
require_email_verification: data.require_email_verification, require_email_verification: data.require_email_verification,
application_question: diesel_string_update(data.application_question.as_deref()), application_question: diesel_string_update(data.application_question.as_deref()),
private_instance: data.private_instance, private_instance: data.private_instance,
default_theme: data.default_theme.clone(), default_theme: data.default_theme.clone(),
default_post_listing_type: data.default_post_listing_type, default_post_listing_type: data.default_post_listing_type,
default_sort_type: data.default_sort_type, default_post_sort_type: data.default_post_sort_type,
default_comment_sort_type: data.default_comment_sort_type,
legal_information: diesel_string_update(data.legal_information.as_deref()), legal_information: diesel_string_update(data.legal_information.as_deref()),
application_email_admins: data.application_email_admins, application_email_admins: data.application_email_admins,
hide_modlog_mod_names: data.hide_modlog_mod_names, hide_modlog_mod_names: data.hide_modlog_mod_names,
@ -122,6 +120,7 @@ pub async fn update_site(
captcha_difficulty: data.captcha_difficulty.clone(), captcha_difficulty: data.captcha_difficulty.clone(),
reports_email_admins: data.reports_email_admins, reports_email_admins: data.reports_email_admins,
default_post_listing_mode: data.default_post_listing_mode, default_post_listing_mode: data.default_post_listing_mode,
oauth_registration: data.oauth_registration,
..Default::default() ..Default::default()
}; };
@ -131,17 +130,17 @@ pub async fn update_site(
let local_site_rate_limit_form = LocalSiteRateLimitUpdateForm { let local_site_rate_limit_form = LocalSiteRateLimitUpdateForm {
message: data.rate_limit_message, message: data.rate_limit_message,
message_per_second: data.rate_limit_message_per_second, message_per_second: not_zero(data.rate_limit_message_per_second),
post: data.rate_limit_post, post: data.rate_limit_post,
post_per_second: data.rate_limit_post_per_second, post_per_second: not_zero(data.rate_limit_post_per_second),
register: data.rate_limit_register, register: data.rate_limit_register,
register_per_second: data.rate_limit_register_per_second, register_per_second: not_zero(data.rate_limit_register_per_second),
image: data.rate_limit_image, image: data.rate_limit_image,
image_per_second: data.rate_limit_image_per_second, image_per_second: not_zero(data.rate_limit_image_per_second),
comment: data.rate_limit_comment, comment: data.rate_limit_comment,
comment_per_second: data.rate_limit_comment_per_second, comment_per_second: not_zero(data.rate_limit_comment_per_second),
search: data.rate_limit_search, search: data.rate_limit_search,
search_per_second: data.rate_limit_search_per_second, search_per_second: not_zero(data.rate_limit_search_per_second),
..Default::default() ..Default::default()
}; };
@ -188,12 +187,7 @@ pub async fn update_site(
.with_lemmy_type(LemmyErrorType::CouldntSetAllEmailVerified)?; .with_lemmy_type(LemmyErrorType::CouldntSetAllEmailVerified)?;
} }
let new_taglines = data.taglines.clone(); let site_view = SiteView::read_local(&mut context.pool()).await?;
let taglines = Tagline::replace(&mut context.pool(), local_site.id, new_taglines).await?;
let site_view = SiteView::read_local(&mut context.pool())
.await?
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
let rate_limit_config = let rate_limit_config =
local_site_rate_limit_to_rate_limit_config(&site_view.local_site_rate_limit); local_site_rate_limit_to_rate_limit_config(&site_view.local_site_rate_limit);
@ -201,7 +195,7 @@ pub async fn update_site(
Ok(Json(SiteResponse { Ok(Json(SiteResponse {
site_view, site_view,
taglines, taglines: vec![],
})) }))
} }
@ -250,13 +244,16 @@ fn validate_update_payload(local_site: &LocalSite, edit_site: &EditSite) -> Lemm
} }
#[cfg(test)] #[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests { mod tests {
use crate::site::update::validate_update_payload; use crate::site::update::validate_update_payload;
use lemmy_api_common::site::EditSite; use lemmy_api_common::site::EditSite;
use lemmy_db_schema::{source::local_site::LocalSite, ListingType, RegistrationMode, SortType}; use lemmy_db_schema::{
source::local_site::LocalSite,
ListingType,
PostSortType,
RegistrationMode,
};
use lemmy_utils::error::LemmyErrorType; use lemmy_utils::error::LemmyErrorType;
#[test] #[test]
@ -265,140 +262,94 @@ mod tests {
( (
"EditSite name matches LocalSite slur filter", "EditSite name matches LocalSite slur filter",
LemmyErrorType::Slurs, LemmyErrorType::Slurs,
&generate_local_site( &LocalSite {
Some(String::from("(foo|bar)")), private_instance: true,
true, slur_filter_regex: Some(String::from("(foo|bar)")),
false, federation_enabled: false,
None::<String>, registration_mode: RegistrationMode::Open,
RegistrationMode::Open, ..Default::default()
), },
&generate_edit_site( &EditSite {
Some(String::from("foo site_name")), name: Some(String::from("foo site_name")),
None::<String>, ..Default::default()
None::<String>, },
None::<ListingType>,
None::<SortType>,
None::<String>,
None::<bool>,
None::<bool>,
None::<String>,
None::<RegistrationMode>,
),
), ),
( (
"EditSite name matches new slur filter", "EditSite name matches new slur filter",
LemmyErrorType::Slurs, LemmyErrorType::Slurs,
&generate_local_site( &LocalSite {
Some(String::from("(foo|bar)")), private_instance: true,
true, slur_filter_regex: Some(String::from("(foo|bar)")),
false, federation_enabled: false,
None::<String>, registration_mode: RegistrationMode::Open,
RegistrationMode::Open, ..Default::default()
), },
&generate_edit_site( &EditSite {
Some(String::from("zeta site_name")), name: Some(String::from("zeta site_name")),
None::<String>, slur_filter_regex: Some(String::from("(zeta|alpha)")),
None::<String>, ..Default::default()
None::<ListingType>, },
None::<SortType>,
Some(String::from("(zeta|alpha)")),
None::<bool>,
None::<bool>,
None::<String>,
None::<RegistrationMode>,
),
), ),
( (
"EditSite listing type is Subscribed, which is invalid", "EditSite listing type is Subscribed, which is invalid",
LemmyErrorType::InvalidDefaultPostListingType, LemmyErrorType::InvalidDefaultPostListingType,
&generate_local_site( &LocalSite {
None::<String>, private_instance: true,
true, federation_enabled: false,
false, registration_mode: RegistrationMode::Open,
None::<String>, ..Default::default()
RegistrationMode::Open, },
), &EditSite {
&generate_edit_site( name: Some(String::from("site_name")),
Some(String::from("site_name")), default_post_listing_type: Some(ListingType::Subscribed),
None::<String>, ..Default::default()
None::<String>, },
Some(ListingType::Subscribed),
None::<SortType>,
None::<String>,
None::<bool>,
None::<bool>,
None::<String>,
None::<RegistrationMode>,
),
), ),
( (
"EditSite is both private and federated", "EditSite is both private and federated",
LemmyErrorType::CantEnablePrivateInstanceAndFederationTogether, LemmyErrorType::CantEnablePrivateInstanceAndFederationTogether,
&generate_local_site( &LocalSite {
None::<String>, private_instance: true,
true, federation_enabled: false,
false, registration_mode: RegistrationMode::Open,
None::<String>, ..Default::default()
RegistrationMode::Open, },
), &EditSite {
&generate_edit_site( name: Some(String::from("site_name")),
Some(String::from("site_name")), private_instance: Some(true),
None::<String>, federation_enabled: Some(true),
None::<String>, ..Default::default()
None::<ListingType>, },
None::<SortType>,
None::<String>,
Some(true),
Some(true),
None::<String>,
None::<RegistrationMode>,
),
), ),
( (
"LocalSite is private, but EditSite also makes it federated", "LocalSite is private, but EditSite also makes it federated",
LemmyErrorType::CantEnablePrivateInstanceAndFederationTogether, LemmyErrorType::CantEnablePrivateInstanceAndFederationTogether,
&generate_local_site( &LocalSite {
None::<String>, private_instance: true,
true, federation_enabled: false,
false, registration_mode: RegistrationMode::Open,
None::<String>, ..Default::default()
RegistrationMode::Open, },
), &EditSite {
&generate_edit_site( name: Some(String::from("site_name")),
Some(String::from("site_name")), federation_enabled: Some(true),
None::<String>, ..Default::default()
None::<String>, },
None::<ListingType>,
None::<SortType>,
None::<String>,
None::<bool>,
Some(true),
None::<String>,
None::<RegistrationMode>,
),
), ),
( (
"EditSite requires application, but neither it nor LocalSite has an application question", "EditSite requires application, but neither it nor LocalSite has an application question",
LemmyErrorType::ApplicationQuestionRequired, LemmyErrorType::ApplicationQuestionRequired,
&generate_local_site( &LocalSite {
None::<String>, private_instance: true,
true, federation_enabled: false,
false, registration_mode: RegistrationMode::Open,
None::<String>, ..Default::default()
RegistrationMode::Open, },
), &EditSite {
&generate_edit_site( name: Some(String::from("site_name")),
Some(String::from("site_name")), registration_mode: Some(RegistrationMode::RequireApplication),
None::<String>, ..Default::default()
None::<String>, },
None::<ListingType>,
None::<SortType>,
None::<String>,
None::<bool>,
None::<bool>,
None::<String>,
Some(RegistrationMode::RequireApplication),
),
), ),
]; ];
@ -434,91 +385,65 @@ mod tests {
let valid_payloads = [ let valid_payloads = [
( (
"No changes between LocalSite and EditSite", "No changes between LocalSite and EditSite",
&generate_local_site( &LocalSite {
None::<String>, private_instance: true,
true, federation_enabled: false,
false, registration_mode: RegistrationMode::Open,
None::<String>, ..Default::default()
RegistrationMode::Open, },
), &EditSite::default(),
&generate_edit_site(
None::<String>,
None::<String>,
None::<String>,
None::<ListingType>,
None::<SortType>,
None::<String>,
None::<bool>,
None::<bool>,
None::<String>,
None::<RegistrationMode>,
),
), ),
( (
"EditSite allows clearing and changing values", "EditSite allows clearing and changing values",
&generate_local_site( &LocalSite {
None::<String>, private_instance: true,
true, federation_enabled: false,
false, registration_mode: RegistrationMode::Open,
None::<String>, ..Default::default()
RegistrationMode::Open, },
), &EditSite {
&generate_edit_site( name: Some(String::from("site_name")),
Some(String::from("site_name")), sidebar: Some(String::new()),
Some(String::new()), description: Some(String::new()),
Some(String::new()), application_question: Some(String::new()),
Some(ListingType::All), private_instance: Some(false),
Some(SortType::Active), default_post_listing_type: Some(ListingType::All),
Some(String::new()), default_post_sort_type: Some(PostSortType::Active),
Some(false), slur_filter_regex: Some(String::new()),
Some(true), registration_mode: Some(RegistrationMode::Open),
Some(String::new()), federation_enabled: Some(true),
Some(RegistrationMode::Open), ..Default::default()
), },
), ),
( (
"EditSite name passes slur filter regex", "EditSite name passes slur filter regex",
&generate_local_site( &LocalSite {
Some(String::from("(foo|bar)")), private_instance: true,
true, slur_filter_regex: Some(String::from("(foo|bar)")),
false, registration_mode: RegistrationMode::Open,
None::<String>, federation_enabled: false,
RegistrationMode::Open, ..Default::default()
), },
&generate_edit_site( &EditSite {
Some(String::from("foo site_name")), name: Some(String::from("foo site_name")),
None::<String>, slur_filter_regex: Some(String::new()),
None::<String>, ..Default::default()
None::<ListingType>, },
None::<SortType>,
Some(String::new()),
None::<bool>,
None::<bool>,
None::<String>,
None::<RegistrationMode>,
),
), ),
( (
"LocalSite has application question and EditSite now requires applications,", "LocalSite has application question and EditSite now requires applications,",
&generate_local_site( &LocalSite {
None::<String>, application_question: Some(String::from("question")),
true, private_instance: true,
false, federation_enabled: false,
Some(String::from("question")), registration_mode: RegistrationMode::Open,
RegistrationMode::Open, ..Default::default()
), },
&generate_edit_site( &EditSite {
Some(String::from("site_name")), name: Some(String::from("site_name")),
None::<String>, registration_mode: Some(RegistrationMode::RequireApplication),
None::<String>, ..Default::default()
None::<ListingType>, },
None::<SortType>,
None::<String>,
None::<bool>,
None::<bool>,
None::<String>,
Some(RegistrationMode::RequireApplication),
),
), ),
]; ];
@ -534,84 +459,4 @@ mod tests {
); );
}) })
} }
fn generate_local_site(
site_slur_filter_regex: Option<String>,
site_is_private: bool,
site_is_federated: bool,
site_application_question: Option<String>,
site_registration_mode: RegistrationMode,
) -> LocalSite {
LocalSite {
application_question: site_application_question,
private_instance: site_is_private,
slur_filter_regex: site_slur_filter_regex,
federation_enabled: site_is_federated,
registration_mode: site_registration_mode,
..Default::default()
}
}
// Allow the test helper function to have too many arguments.
// It's either this or generate the entire struct each time for testing.
#[allow(clippy::too_many_arguments)]
fn generate_edit_site(
site_name: Option<String>,
site_description: Option<String>,
site_sidebar: Option<String>,
site_listing_type: Option<ListingType>,
site_sort_type: Option<SortType>,
site_slur_filter_regex: Option<String>,
site_is_private: Option<bool>,
site_is_federated: Option<bool>,
site_application_question: Option<String>,
site_registration_mode: Option<RegistrationMode>,
) -> EditSite {
EditSite {
name: site_name,
sidebar: site_sidebar,
description: site_description,
icon: None,
banner: None,
enable_downvotes: None,
enable_nsfw: None,
community_creation_admin_only: None,
require_email_verification: None,
application_question: site_application_question,
private_instance: site_is_private,
default_theme: None,
default_post_listing_type: site_listing_type,
default_sort_type: site_sort_type,
legal_information: None,
application_email_admins: None,
hide_modlog_mod_names: None,
discussion_languages: None,
slur_filter_regex: site_slur_filter_regex,
actor_name_max_length: None,
rate_limit_message: None,
rate_limit_message_per_second: None,
rate_limit_post: None,
rate_limit_post_per_second: None,
rate_limit_register: None,
rate_limit_register_per_second: None,
rate_limit_image: None,
rate_limit_image_per_second: None,
rate_limit_comment: None,
rate_limit_comment_per_second: None,
rate_limit_search: None,
rate_limit_search_per_second: None,
federation_enabled: site_is_federated,
federation_debug: None,
captcha_enabled: None,
captcha_difficulty: None,
allowed_instances: None,
blocked_instances: None,
blocked_urls: None,
taglines: None,
registration_mode: site_registration_mode,
reports_email_admins: None,
content_warning: None,
default_post_listing_mode: None,
}
}
} }

View File

@ -0,0 +1,38 @@
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
context::LemmyContext,
tagline::{CreateTagline, TaglineResponse},
utils::{get_url_blocklist, is_admin, local_site_to_slur_regex, process_markdown},
};
use lemmy_db_schema::{
source::{
local_site::LocalSite,
tagline::{Tagline, TaglineInsertForm},
},
traits::Crud,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyError;
#[tracing::instrument(skip(context))]
pub async fn create_tagline(
data: Json<CreateTagline>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<TaglineResponse>, LemmyError> {
// Make sure user is an admin
is_admin(&local_user_view)?;
let local_site = LocalSite::read(&mut context.pool()).await?;
let slur_regex = local_site_to_slur_regex(&local_site);
let url_blocklist = get_url_blocklist(&context).await?;
let content = process_markdown(&data.content, &slur_regex, &url_blocklist, &context).await?;
let tagline_form = TaglineInsertForm { content };
let tagline = Tagline::create(&mut context.pool(), &tagline_form).await?;
Ok(Json(TaglineResponse { tagline }))
}

View File

@ -0,0 +1,25 @@
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
context::LemmyContext,
tagline::DeleteTagline,
utils::is_admin,
SuccessResponse,
};
use lemmy_db_schema::{source::tagline::Tagline, traits::Crud};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyError;
#[tracing::instrument(skip(context))]
pub async fn delete_tagline(
data: Json<DeleteTagline>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<SuccessResponse>, LemmyError> {
// Make sure user is an admin
is_admin(&local_user_view)?;
Tagline::delete(&mut context.pool(), data.id).await?;
Ok(Json(SuccessResponse::default()))
}

Some files were not shown because too many files have changed in this diff Show More