Merge branch 'main' into auto_resolve_reports_1

auto_resolve_reports_1
Dessalines 2024-02-14 11:12:46 -05:00 committed by GitHub
commit fac80ec87d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 3556 additions and 3093 deletions

View File

@ -3,6 +3,7 @@
variables: variables:
- &rust_image "rust:1.75" - &rust_image "rust:1.75"
- &install_pnpm "corepack enable pnpm"
- &slow_check_paths - &slow_check_paths
- path: - path:
# rust source code # rust source code
@ -41,7 +42,7 @@ steps:
prettier_check: prettier_check:
image: tmknom/prettier:3.0.0 image: tmknom/prettier:3.0.0
commands: commands:
- prettier -c . '!**/volumes' '!**/dist' '!target' '!**/translations' - prettier -c . '!**/volumes' '!**/dist' '!target' '!**/translations' '!api_tests/pnpm-lock.yaml'
toml_fmt: toml_fmt:
image: tamasfe/taplo:0.8.1 image: tamasfe/taplo:0.8.1
@ -182,11 +183,12 @@ steps:
LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432 LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432
DO_WRITE_HOSTS_FILE: "1" DO_WRITE_HOSTS_FILE: "1"
commands: commands:
- *install_pnpm
- apt update && apt install -y bash curl postgresql-client - apt update && apt install -y bash curl postgresql-client
- bash api_tests/prepare-drone-federation-test.sh - bash api_tests/prepare-drone-federation-test.sh
- cd api_tests/ - cd api_tests/
- yarn - pnpm i
- yarn api-test - pnpm api-test
when: *slow_check_paths when: *slow_check_paths
federation_tests_server_output: federation_tests_server_output:
@ -255,7 +257,7 @@ steps:
services: services:
database: database:
image: postgres:15.2-alpine image: postgres:16-alpine
environment: environment:
POSTGRES_USER: lemmy POSTGRES_USER: lemmy
POSTGRES_PASSWORD: password POSTGRES_PASSWORD: password

7
Cargo.lock generated
View File

@ -8,6 +8,12 @@ version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
[[package]]
name = "accept-language"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f27d075294830fcab6f66e320dab524bc6d048f4a151698e153205559113772"
[[package]] [[package]]
name = "activitypub_federation" name = "activitypub_federation"
version = "0.5.1-beta.1" version = "0.5.1-beta.1"
@ -2608,6 +2614,7 @@ dependencies = [
name = "lemmy_api_crud" name = "lemmy_api_crud"
version = "0.19.3" version = "0.19.3"
dependencies = [ dependencies = [
"accept-language",
"activitypub_federation", "activitypub_federation",
"actix-web", "actix-web",
"anyhow", "anyhow",

View File

@ -149,7 +149,11 @@ http = "0.2.11"
rosetta-i18n = "0.1.3" rosetta-i18n = "0.1.3"
opentelemetry = { version = "0.19.0", features = ["rt-tokio"] } opentelemetry = { version = "0.19.0", features = ["rt-tokio"] }
tracing-opentelemetry = { version = "0.19.0" } tracing-opentelemetry = { version = "0.19.0" }
ts-rs = { version = "7.1.1", features = ["serde-compat", "chrono-impl"] } ts-rs = { version = "7.1.1", features = [
"serde-compat",
"chrono-impl",
"no-serde-warnings",
] }
rustls = { version = "0.21.10", features = ["dangerous_configuration"] } rustls = { version = "0.21.10", features = ["dangerous_configuration"] }
futures-util = "0.3.30" futures-util = "0.3.30"
tokio-postgres = "0.7.10" tokio-postgres = "0.7.10"

View File

@ -107,7 +107,6 @@ Each Lemmy server can set its own moderation policy; appointing site-wide admins
- NSFW post / community support. - NSFW post / community support.
- High performance. - High performance.
- Server is written in rust. - Server is written in rust.
- Front end is `~80kB` gzipped.
- Supports arm64 / Raspberry Pi. - Supports arm64 / Raspberry Pi.
## Installation ## Installation

File diff suppressed because it is too large Load Diff

View File

@ -10,8 +10,8 @@ killall -s1 lemmy_server || true
./api_tests/prepare-drone-federation-test.sh ./api_tests/prepare-drone-federation-test.sh
popd popd
yarn pnpm i
yarn api-test || true pnpm api-test || true
killall -s1 lemmy_server || true killall -s1 lemmy_server || true
killall -s1 pict-rs || true killall -s1 pict-rs || true

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@ use lemmy_db_schema::{
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
pub struct Claims { pub struct Claims {
/// local_user_id, standard claim by RFC 7519. /// local_user_id, standard claim by RFC 7519.
pub sub: String, pub sub: String,

View File

@ -10,7 +10,7 @@ use serde_with::skip_serializing_none;
use ts_rs::TS; use ts_rs::TS;
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Create a comment. /// Create a comment.
@ -22,7 +22,7 @@ pub struct CreateComment {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Fetch an individual comment. /// Fetch an individual comment.
@ -31,7 +31,7 @@ pub struct GetComment {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Edit a comment. /// Edit a comment.
@ -42,7 +42,7 @@ pub struct EditComment {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Distinguish a comment (IE speak as moderator). /// Distinguish a comment (IE speak as moderator).
@ -52,7 +52,7 @@ pub struct DistinguishComment {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Delete your own comment. /// Delete your own comment.
@ -62,7 +62,7 @@ pub struct DeleteComment {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Remove a comment (only doable by mods). /// Remove a comment (only doable by mods).
@ -72,7 +72,7 @@ pub struct RemoveComment {
pub reason: Option<String>, pub reason: Option<String>,
} }
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Save / bookmark a comment. /// Save / bookmark a comment.
@ -91,7 +91,7 @@ pub struct CommentResponse {
pub recipient_ids: Vec<LocalUserId>, pub recipient_ids: Vec<LocalUserId>,
} }
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Like a comment. /// Like a comment.
@ -102,7 +102,7 @@ pub struct CreateCommentLike {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Get a list of comments. /// Get a list of comments.
@ -146,7 +146,7 @@ pub struct CommentReportResponse {
pub comment_report_view: CommentReportView, pub comment_report_view: CommentReportView,
} }
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Resolve a comment report (only doable by mods). /// Resolve a comment report (only doable by mods).
@ -156,7 +156,7 @@ pub struct ResolveCommentReport {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// List comment reports. /// List comment reports.
@ -178,7 +178,7 @@ pub struct ListCommentReportsResponse {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// List comment likes. Admins-only. /// List comment likes. Admins-only.

View File

@ -12,7 +12,7 @@ use serde_with::skip_serializing_none;
use ts_rs::TS; use ts_rs::TS;
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Get a community. Must provide either an id, or a name. /// Get a community. Must provide either an id, or a name.
@ -37,7 +37,7 @@ pub struct GetCommunityResponse {
#[skip_serializing_none] #[skip_serializing_none]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
/// Create a community. /// Create a community.
pub struct CreateCommunity { pub struct CreateCommunity {
/// The unique name. /// The unique name.
@ -68,7 +68,7 @@ pub struct CommunityResponse {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Fetches a list of communities. /// Fetches a list of communities.
@ -89,7 +89,7 @@ pub struct ListCommunitiesResponse {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Ban a user from a community. /// Ban a user from a community.
@ -114,7 +114,7 @@ pub struct BanFromCommunityResponse {
pub banned: bool, pub banned: bool,
} }
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Add a moderator to a community. /// Add a moderator to a community.
@ -133,7 +133,7 @@ pub struct AddModToCommunityResponse {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Edit a community. /// Edit a community.
@ -156,7 +156,7 @@ pub struct EditCommunity {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Hide a community from the main view. /// Hide a community from the main view.
@ -167,7 +167,7 @@ pub struct HideCommunity {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Delete your own community. /// Delete your own community.
@ -177,7 +177,7 @@ pub struct DeleteCommunity {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Remove a community (only doable by moderators). /// Remove a community (only doable by moderators).
@ -187,7 +187,7 @@ pub struct RemoveCommunity {
pub reason: Option<String>, pub reason: Option<String>,
} }
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Follow / subscribe to a community. /// Follow / subscribe to a community.
@ -196,7 +196,7 @@ pub struct FollowCommunity {
pub follow: bool, pub follow: bool,
} }
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Block a community. /// Block a community.
@ -215,7 +215,7 @@ pub struct BlockCommunityResponse {
pub blocked: bool, pub blocked: bool,
} }
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Transfer a community to a new owner. /// Transfer a community to a new owner.

View File

@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
use ts_rs::TS; use ts_rs::TS;
use url::Url; use url::Url;
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Create a custom emoji. /// Create a custom emoji.
@ -18,7 +18,7 @@ pub struct CreateCustomEmoji {
pub keywords: Vec<String>, pub keywords: Vec<String>,
} }
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Edit a custom emoji. /// Edit a custom emoji.
@ -31,7 +31,7 @@ pub struct EditCustomEmoji {
pub keywords: Vec<String>, pub keywords: Vec<String>,
} }
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Delete a custom emoji. /// Delete a custom emoji.

View File

@ -20,7 +20,7 @@ use serde_with::skip_serializing_none;
use ts_rs::TS; use ts_rs::TS;
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Logging into lemmy. /// Logging into lemmy.
@ -32,7 +32,7 @@ pub struct Login {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Register / Sign up to lemmy. /// Register / Sign up to lemmy.
@ -77,7 +77,7 @@ pub struct CaptchaResponse {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Saves settings for your user. /// Saves settings for your user.
@ -131,7 +131,7 @@ pub struct SaveUserSettings {
pub collapse_bot_comments: Option<bool>, pub collapse_bot_comments: Option<bool>,
} }
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Changes your account password. /// Changes your account password.
@ -156,7 +156,7 @@ pub struct LoginResponse {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Gets a person's details. /// Gets a person's details.
@ -186,7 +186,7 @@ pub struct GetPersonDetailsResponse {
pub moderates: Vec<CommunityModeratorView>, pub moderates: Vec<CommunityModeratorView>,
} }
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Adds an admin to a site. /// Adds an admin to a site.
@ -204,7 +204,7 @@ pub struct AddAdminResponse {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Ban a person from the site. /// Ban a person from the site.
@ -238,7 +238,7 @@ pub struct BanPersonResponse {
pub banned: bool, pub banned: bool,
} }
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Block a person. /// Block a person.
@ -257,7 +257,7 @@ pub struct BlockPersonResponse {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Get comment replies. /// Get comment replies.
@ -278,7 +278,7 @@ pub struct GetRepliesResponse {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Get mentions for your user. /// Get mentions for your user.
@ -297,7 +297,7 @@ pub struct GetPersonMentionsResponse {
pub mentions: Vec<PersonMentionView>, pub mentions: Vec<PersonMentionView>,
} }
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Mark a person mention as read. /// Mark a person mention as read.
@ -314,7 +314,7 @@ pub struct PersonMentionResponse {
pub person_mention_view: PersonMentionView, pub person_mention_view: PersonMentionView,
} }
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Mark a comment reply as read. /// Mark a comment reply as read.
@ -331,7 +331,7 @@ pub struct CommentReplyResponse {
pub comment_reply_view: CommentReplyView, pub comment_reply_view: CommentReplyView,
} }
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Delete your account. /// Delete your account.
@ -340,7 +340,7 @@ pub struct DeleteAccount {
pub delete_content: bool, pub delete_content: bool,
} }
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Reset your password via email. /// Reset your password via email.
@ -348,7 +348,7 @@ pub struct PasswordReset {
pub email: Sensitive<String>, pub email: Sensitive<String>,
} }
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Change your password after receiving a reset request. /// Change your password after receiving a reset request.
@ -359,7 +359,7 @@ pub struct PasswordChangeAfterReset {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Get a count of the number of reports. /// Get a count of the number of reports.
@ -389,7 +389,7 @@ pub struct GetUnreadCountResponse {
pub private_messages: i64, pub private_messages: i64,
} }
#[derive(Serialize, Deserialize, Clone, Default, Debug)] #[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Verify your email. /// Verify your email.
@ -404,7 +404,7 @@ pub struct GenerateTotpSecretResponse {
pub totp_secret_url: Sensitive<String>, pub totp_secret_url: Sensitive<String>,
} }
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
pub struct UpdateTotp { pub struct UpdateTotp {

View File

@ -13,7 +13,7 @@ use ts_rs::TS;
use url::Url; use url::Url;
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Create a post. /// Create a post.
@ -38,7 +38,7 @@ pub struct PostResponse {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Get a post. Needs either the post id, or comment_id. /// Get a post. Needs either the post id, or comment_id.
@ -61,7 +61,7 @@ pub struct GetPostResponse {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, Clone, Default)] #[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Get a list of posts. /// Get a list of posts.
@ -90,7 +90,7 @@ pub struct GetPostsResponse {
pub next_page: Option<PaginationCursor>, pub next_page: Option<PaginationCursor>,
} }
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Like a post. /// Like a post.
@ -101,7 +101,7 @@ pub struct CreatePostLike {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Edit a post. /// Edit a post.
@ -116,7 +116,7 @@ pub struct EditPost {
pub language_id: Option<LanguageId>, pub language_id: Option<LanguageId>,
} }
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Delete a post. /// Delete a post.
@ -126,7 +126,7 @@ pub struct DeletePost {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Remove a post (only doable by mods). /// Remove a post (only doable by mods).
@ -137,7 +137,7 @@ pub struct RemovePost {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Mark a post as read. /// Mark a post as read.
@ -148,7 +148,7 @@ pub struct MarkPostAsRead {
pub read: bool, pub read: bool,
} }
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Lock a post (prevent new comments). /// Lock a post (prevent new comments).
@ -157,7 +157,7 @@ pub struct LockPost {
pub locked: bool, pub locked: bool,
} }
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Feature a post (stickies / pins to the top). /// Feature a post (stickies / pins to the top).
@ -167,7 +167,7 @@ pub struct FeaturePost {
pub feature_type: PostFeatureType, pub feature_type: PostFeatureType,
} }
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Save / bookmark a post. /// Save / bookmark a post.
@ -193,7 +193,7 @@ pub struct PostReportResponse {
pub post_report_view: PostReportView, pub post_report_view: PostReportView,
} }
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Resolve a post report (mods only). /// Resolve a post report (mods only).
@ -203,7 +203,7 @@ pub struct ResolvePostReport {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// List post reports. /// List post reports.
@ -224,7 +224,7 @@ pub struct ListPostReportsResponse {
pub post_reports: Vec<PostReportView>, pub post_reports: Vec<PostReportView>,
} }
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Get metadata for a given site. /// Get metadata for a given site.
@ -242,7 +242,7 @@ pub struct GetSiteMetadataResponse {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone, Default)] #[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone, Default, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Site metadata, from its opengraph tags. /// Site metadata, from its opengraph tags.
@ -255,7 +255,7 @@ pub struct LinkMetadata {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone, Default)] #[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone, Default, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Site metadata, from its opengraph tags. /// Site metadata, from its opengraph tags.
@ -267,7 +267,7 @@ pub struct OpenGraphData {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// List post likes. Admins-only. /// List post likes. Admins-only.

View File

@ -5,7 +5,7 @@ use serde_with::skip_serializing_none;
#[cfg(feature = "full")] #[cfg(feature = "full")]
use ts_rs::TS; use ts_rs::TS;
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Create a private message. /// Create a private message.
@ -14,7 +14,7 @@ pub struct CreatePrivateMessage {
pub recipient_id: PersonId, pub recipient_id: PersonId,
} }
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Edit a private message. /// Edit a private message.
@ -23,7 +23,7 @@ pub struct EditPrivateMessage {
pub content: String, pub content: String,
} }
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Delete a private message. /// Delete a private message.
@ -32,7 +32,7 @@ pub struct DeletePrivateMessage {
pub deleted: bool, pub deleted: bool,
} }
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Mark a private message as read. /// Mark a private message as read.
@ -42,7 +42,7 @@ pub struct MarkPrivateMessageAsRead {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Get your private messages. /// Get your private messages.
@ -69,7 +69,7 @@ pub struct PrivateMessageResponse {
pub private_message_view: PrivateMessageView, pub private_message_view: PrivateMessageView,
} }
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Create a report for a private message. /// Create a report for a private message.
@ -86,7 +86,7 @@ pub struct PrivateMessageReportResponse {
pub private_message_report_view: PrivateMessageReportView, pub private_message_report_view: PrivateMessageReportView,
} }
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Resolve a private message report. /// Resolve a private message report.
@ -96,7 +96,7 @@ pub struct ResolvePrivateMessageReport {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// List private message reports. /// List private message reports.

View File

@ -54,7 +54,7 @@ use serde_with::skip_serializing_none;
use ts_rs::TS; use ts_rs::TS;
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Searches the site, given a query string, and some optional filters. /// Searches the site, given a query string, and some optional filters.
@ -83,7 +83,7 @@ pub struct SearchResponse {
pub users: Vec<PersonView>, pub users: Vec<PersonView>,
} }
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Does an apub fetch for an object. /// Does an apub fetch for an object.
@ -106,7 +106,7 @@ pub struct ResolveObjectResponse {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Fetches the modlog. /// Fetches the modlog.
@ -143,7 +143,7 @@ pub struct GetModlogResponse {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Creates a site. Should be done after first running lemmy. /// Creates a site. Should be done after first running lemmy.
@ -190,7 +190,7 @@ pub struct CreateSite {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Edits a site. /// Edits a site.
@ -363,7 +363,7 @@ pub struct InstanceWithFederationState {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Purges a person from the database. This will delete all content attached to that person. /// Purges a person from the database. This will delete all content attached to that person.
@ -373,7 +373,7 @@ pub struct PurgePerson {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Purges a community from the database. This will delete all content attached to that community. /// Purges a community from the database. This will delete all content attached to that community.
@ -383,7 +383,7 @@ pub struct PurgeCommunity {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Purges a post from the database. This will delete all content attached to that post. /// Purges a post from the database. This will delete all content attached to that post.
@ -393,7 +393,7 @@ pub struct PurgePost {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Purges a comment from the database. This will delete all content attached to that comment. /// Purges a comment from the database. This will delete all content attached to that comment.
@ -403,7 +403,7 @@ pub struct PurgeComment {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Fetches a list of registration applications. /// Fetches a list of registration applications.
@ -423,7 +423,7 @@ pub struct ListRegistrationApplicationsResponse {
} }
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Approves a registration application. /// Approves a registration application.
@ -449,7 +449,7 @@ pub struct GetUnreadRegistrationApplicationCountResponse {
pub registration_applications: i64, pub registration_applications: i64,
} }
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Block an instance as user /// Block an instance as user

View File

@ -29,6 +29,7 @@ moka.workspace = true
once_cell.workspace = true once_cell.workspace = true
anyhow.workspace = true anyhow.workspace = true
webmention = "0.5.0" webmention = "0.5.0"
accept-language = "3.1.0"
[package.metadata.cargo-machete] [package.metadata.cargo-machete]
ignored = ["futures"] ignored = ["futures"]

View File

@ -52,7 +52,7 @@ pub async fn delete_post(
.await?; .await?;
ActivityChannel::submit_activity( ActivityChannel::submit_activity(
SendActivityData::DeletePost(post, local_user_view.person.clone(), data.0.clone()), SendActivityData::DeletePost(post, local_user_view.person.clone(), data.0),
&context, &context,
) )
.await?; .await?;

View File

@ -126,6 +126,14 @@ pub async fn register(
// Also fixes a bug which allows users to log in when registrations are changed to closed. // Also fixes a bug which allows users to log in when registrations are changed to closed.
let accepted_application = Some(!require_registration_application); let accepted_application = Some(!require_registration_application);
// Get the user's preferred language using the Accept-Language header
let language_tag = req.headers().get("Accept-Language").and_then(|hdr| {
accept_language::parse(hdr.to_str().unwrap_or_default())
.first()
// Remove the optional region code
.map(|lang_str| lang_str.split('-').next().unwrap_or_default().to_string())
});
// Create the local user // Create the local user
let local_user_form = LocalUserInsertForm::builder() let local_user_form = LocalUserInsertForm::builder()
.person_id(inserted_person.id) .person_id(inserted_person.id)
@ -134,6 +142,7 @@ pub async fn register(
.show_nsfw(Some(data.show_nsfw)) .show_nsfw(Some(data.show_nsfw))
.accepted_application(accepted_application) .accepted_application(accepted_application)
.default_listing_type(Some(local_site.default_post_listing_type)) .default_listing_type(Some(local_site.default_post_listing_type))
.interface_language(language_tag)
// If its the initial site setup, they are an admin // If its the initial site setup, they are an admin
.admin(Some(!local_site.site_setup)) .admin(Some(!local_site.site_setup))
.build(); .build();

View File

@ -130,6 +130,10 @@ async fn try_main() -> LemmyResult<()> {
// Make sure the println above shows the correct amount // Make sure the println above shows the correct amount
assert_eq!(num_inserted_posts, num_posts as usize); assert_eq!(num_inserted_posts, num_posts as usize);
// Manually trigger and wait for a statistics update to ensure consistent and high amount of accuracy in the statistics used for query planning
println!("🧮 updating database statistics");
conn.batch_execute("ANALYZE;").await?;
// Enable auto_explain // Enable auto_explain
conn conn
.batch_execute( .batch_execute(

View File

@ -140,6 +140,15 @@ impl LocalUser {
} }
} }
impl LocalUserInsertForm {
pub fn test_form(person_id: PersonId) -> Self {
Self::builder()
.person_id(person_id)
.password_encrypted(String::new())
.build()
}
}
pub struct UserBackupLists { pub struct UserBackupLists {
pub followed_communities: Vec<DbUrl>, pub followed_communities: Vec<DbUrl>,
pub saved_posts: Vec<DbUrl>, pub saved_posts: Vec<DbUrl>,

View File

@ -1,5 +1,5 @@
use crate::{ use crate::{
newtypes::{CommunityId, DbUrl, PersonId}, newtypes::{CommunityId, DbUrl, InstanceId, PersonId},
schema::{instance, local_user, person, person_follower}, schema::{instance, local_user, person, person_follower},
source::person::{ source::person::{
Person, Person,
@ -86,6 +86,16 @@ impl Person {
} }
} }
impl PersonInsertForm {
pub fn test_form(instance_id: InstanceId, name: &str) -> Self {
Self::builder()
.name(name.to_owned())
.public_key("pubkey".to_string())
.instance_id(instance_id)
.build()
}
}
#[async_trait] #[async_trait]
impl ApubActor for Person { impl ApubActor for Person {
async fn read_from_apub_id( async fn read_from_apub_id(

View File

@ -49,7 +49,7 @@ use strum_macros::{Display, EnumString};
use ts_rs::TS; use ts_rs::TS;
#[derive( #[derive(
EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default, EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default, Hash,
)] )]
#[cfg_attr(feature = "full", derive(DbEnum, TS))] #[cfg_attr(feature = "full", derive(DbEnum, TS))]
#[cfg_attr( #[cfg_attr(
@ -83,7 +83,7 @@ pub enum SortType {
Scaled, Scaled,
} }
#[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy)] #[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// The comment sort types. See here for descriptions: https://join-lemmy.org/docs/en/users/03-votes-and-ranking.html /// The comment sort types. See here for descriptions: https://join-lemmy.org/docs/en/users/03-votes-and-ranking.html
@ -96,7 +96,7 @@ pub enum CommentSortType {
} }
#[derive( #[derive(
EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default, EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default, Hash,
)] )]
#[cfg_attr(feature = "full", derive(DbEnum, TS))] #[cfg_attr(feature = "full", derive(DbEnum, TS))]
#[cfg_attr( #[cfg_attr(
@ -119,7 +119,7 @@ pub enum ListingType {
} }
#[derive( #[derive(
EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default, EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default, Hash,
)] )]
#[cfg_attr(feature = "full", derive(DbEnum, TS))] #[cfg_attr(feature = "full", derive(DbEnum, TS))]
#[cfg_attr( #[cfg_attr(
@ -139,7 +139,7 @@ pub enum RegistrationMode {
Open, Open,
} }
#[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] #[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(DbEnum, TS))] #[cfg_attr(feature = "full", derive(DbEnum, TS))]
#[cfg_attr( #[cfg_attr(
feature = "full", feature = "full",
@ -157,7 +157,7 @@ pub enum PostListingMode {
SmallCard, SmallCard,
} }
#[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy)] #[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// The type of content returned from a search. /// The type of content returned from a search.
@ -170,7 +170,7 @@ pub enum SearchType {
Url, Url,
} }
#[derive(EnumString, Display, Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)] #[derive(EnumString, Display, Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// A type / status for a community subscribe. /// A type / status for a community subscribe.
@ -180,7 +180,7 @@ pub enum SubscribedType {
Pending, Pending,
} }
#[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] #[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// A list of possible types for the various modlog actions. /// A list of possible types for the various modlog actions.
@ -204,7 +204,7 @@ pub enum ModlogActionType {
} }
#[derive( #[derive(
EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash,
)] )]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
@ -218,7 +218,7 @@ pub enum PostFeatureType {
} }
#[derive( #[derive(
EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default, EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default, Hash,
)] )]
#[cfg_attr(feature = "full", derive(DbEnum, TS))] #[cfg_attr(feature = "full", derive(DbEnum, TS))]
#[cfg_attr( #[cfg_attr(

View File

@ -25,10 +25,11 @@ use diesel::{
use diesel_async::{ use diesel_async::{
pg::AsyncPgConnection, pg::AsyncPgConnection,
pooled_connection::{ pooled_connection::{
deadpool::{Object as PooledConnection, Pool}, deadpool::{Hook, HookError, Object as PooledConnection, Pool},
AsyncDieselConnectionManager, AsyncDieselConnectionManager,
ManagerConfig, ManagerConfig,
}, },
SimpleAsyncConnection,
}; };
use diesel_migrations::EmbeddedMigrations; use diesel_migrations::EmbeddedMigrations;
use futures_util::{future::BoxFuture, Future, FutureExt}; use futures_util::{future::BoxFuture, Future, FutureExt};
@ -46,7 +47,7 @@ use rustls::{
use std::{ use std::{
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
sync::Arc, sync::Arc,
time::SystemTime, time::{Duration, SystemTime},
}; };
use tracing::{error, info}; use tracing::{error, info};
use url::Url; use url::Url;
@ -335,7 +336,14 @@ fn establish_connection(config: &str) -> BoxFuture<ConnectionResult<AsyncPgConne
error!("Database connection failed: {e}"); error!("Database connection failed: {e}");
} }
}); });
AsyncPgConnection::try_from(client).await let mut conn = AsyncPgConnection::try_from(client).await?;
// * Change geqo_threshold back to default value if it was changed, so it's higher than the collapse limits
// * Change collapse limits from 8 to 11 so the query planner can find a better table join order for more complicated queries
conn
.batch_execute("SET geqo_threshold=12;SET from_collapse_limit=11;SET join_collapse_limit=11;")
.await
.map_err(ConnectionError::CouldntSetupConfiguration)?;
Ok(conn)
}; };
fut.boxed() fut.boxed()
} }
@ -389,6 +397,16 @@ pub async fn build_db_pool() -> Result<ActualDbPool, LemmyError> {
let pool = Pool::builder(manager) let pool = Pool::builder(manager)
.max_size(SETTINGS.database.pool_size) .max_size(SETTINGS.database.pool_size)
.runtime(Runtime::Tokio1) .runtime(Runtime::Tokio1)
// Limit connection age to prevent use of prepared statements that have query plans based on very old statistics
.pre_recycle(Hook::sync_fn(|_conn, metrics| {
// Preventing the first recycle can cause an infinite loop when trying to get a new connection from the pool
let conn_was_used = metrics.recycled.is_some();
if metrics.age() > Duration::from_secs(3 * 24 * 60 * 60) && conn_was_used {
Err(HookError::Continue(None))
} else {
Ok(())
}
}))
.build()?; .build()?;
run_migrations(&db_url)?; run_migrations(&db_url)?;

View File

@ -698,7 +698,7 @@ mod tests {
use lemmy_db_schema::{ use lemmy_db_schema::{
aggregates::structs::PostAggregates, aggregates::structs::PostAggregates,
impls::actor_language::UNDETERMINED_ID, impls::actor_language::UNDETERMINED_ID,
newtypes::{InstanceId, LanguageId, PersonId}, newtypes::LanguageId,
source::{ source::{
actor_language::LocalUserLanguage, actor_language::LocalUserLanguage,
comment::{Comment, CommentInsertForm}, comment::{Comment, CommentInsertForm},
@ -757,37 +757,22 @@ mod tests {
} }
} }
fn default_person_insert_form(instance_id: InstanceId, name: &str) -> PersonInsertForm {
PersonInsertForm::builder()
.name(name.to_owned())
.public_key("pubkey".to_string())
.instance_id(instance_id)
.build()
}
fn default_local_user_form(person_id: PersonId) -> LocalUserInsertForm {
LocalUserInsertForm::builder()
.person_id(person_id)
.password_encrypted(String::new())
.build()
}
async fn init_data(pool: &mut DbPool<'_>) -> LemmyResult<Data> { async fn init_data(pool: &mut DbPool<'_>) -> LemmyResult<Data> {
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
let new_person = default_person_insert_form(inserted_instance.id, "tegan"); let new_person = PersonInsertForm::test_form(inserted_instance.id, "tegan");
let inserted_person = Person::create(pool, &new_person).await?; let inserted_person = Person::create(pool, &new_person).await?;
let local_user_form = LocalUserInsertForm { let local_user_form = LocalUserInsertForm {
admin: Some(true), admin: Some(true),
..default_local_user_form(inserted_person.id) ..LocalUserInsertForm::test_form(inserted_person.id)
}; };
let inserted_local_user = LocalUser::create(pool, &local_user_form).await?; let inserted_local_user = LocalUser::create(pool, &local_user_form).await?;
let new_bot = PersonInsertForm { let new_bot = PersonInsertForm {
bot_account: Some(true), bot_account: Some(true),
..default_person_insert_form(inserted_instance.id, "mybot") ..PersonInsertForm::test_form(inserted_instance.id, "mybot")
}; };
let inserted_bot = Person::create(pool, &new_bot).await?; let inserted_bot = Person::create(pool, &new_bot).await?;
@ -802,12 +787,15 @@ mod tests {
let inserted_community = Community::create(pool, &new_community).await?; let inserted_community = Community::create(pool, &new_community).await?;
// Test a person block, make sure the post query doesn't include their post // Test a person block, make sure the post query doesn't include their post
let blocked_person = default_person_insert_form(inserted_instance.id, "john"); let blocked_person = PersonInsertForm::test_form(inserted_instance.id, "john");
let inserted_blocked_person = Person::create(pool, &blocked_person).await?; let inserted_blocked_person = Person::create(pool, &blocked_person).await?;
let inserted_blocked_local_user = let inserted_blocked_local_user = LocalUser::create(
LocalUser::create(pool, &default_local_user_form(inserted_blocked_person.id)).await?; pool,
&LocalUserInsertForm::test_form(inserted_blocked_person.id),
)
.await?;
let post_from_blocked_person = PostInsertForm::builder() let post_from_blocked_person = PostInsertForm::builder()
.name(POST_BY_BLOCKED_PERSON.to_string()) .name(POST_BY_BLOCKED_PERSON.to_string())

View File

@ -12,7 +12,7 @@ use diesel_async::RunQueryDsl;
use lemmy_db_schema::{ use lemmy_db_schema::{
aliases, aliases,
newtypes::{PersonId, PrivateMessageId}, newtypes::{PersonId, PrivateMessageId},
schema::{person, person_block, private_message}, schema::{instance_block, person, person_block, private_message},
utils::{get_conn, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn}, utils::{get_conn, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn},
}; };
use tracing::debug; use tracing::debug;
@ -34,6 +34,13 @@ fn queries<'a>() -> Queries<
.and(person_block::person_id.eq(aliases::person1.field(person::id))), .and(person_block::person_id.eq(aliases::person1.field(person::id))),
), ),
) )
.left_join(
instance_block::table.on(
person::instance_id
.eq(instance_block::instance_id)
.and(instance_block::person_id.eq(aliases::person1.field(person::id))),
),
)
}; };
let selection = ( let selection = (
@ -55,7 +62,9 @@ fn queries<'a>() -> Queries<
let mut query = all_joins(private_message::table.into_boxed()) let mut query = all_joins(private_message::table.into_boxed())
.select(selection) .select(selection)
// Dont show replies from blocked users // Dont show replies from blocked users
.filter(person_block::person_id.is_null()); .filter(person_block::person_id.is_null())
// Dont show replies from blocked instances
.filter(instance_block::person_id.is_null());
// If its unread, I only want the ones to me // If its unread, I only want the ones to me
if options.unread_only { if options.unread_only {
@ -116,6 +125,8 @@ impl PrivateMessageView {
use diesel::dsl::count; use diesel::dsl::count;
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
private_message::table private_message::table
// Necessary to get the senders instance_id
.inner_join(person::table.on(private_message::creator_id.eq(person::id)))
.left_join( .left_join(
person_block::table.on( person_block::table.on(
private_message::creator_id private_message::creator_id
@ -123,8 +134,17 @@ impl PrivateMessageView {
.and(person_block::person_id.eq(my_person_id)), .and(person_block::person_id.eq(my_person_id)),
), ),
) )
.left_join(
instance_block::table.on(
person::instance_id
.eq(instance_block::instance_id)
.and(instance_block::person_id.eq(my_person_id)),
),
)
// Dont count replies from blocked users // Dont count replies from blocked users
.filter(person_block::person_id.is_null()) .filter(person_block::person_id.is_null())
// Dont count replies from blocked instances
.filter(instance_block::person_id.is_null())
.filter(private_message::read.eq(false)) .filter(private_message::read.eq(false))
.filter(private_message::recipient_id.eq(my_person_id)) .filter(private_message::recipient_id.eq(my_person_id))
.filter(private_message::deleted.eq(false)) .filter(private_message::deleted.eq(false))
@ -160,24 +180,30 @@ mod tests {
use crate::{private_message_view::PrivateMessageQuery, structs::PrivateMessageView}; use crate::{private_message_view::PrivateMessageQuery, structs::PrivateMessageView};
use lemmy_db_schema::{ use lemmy_db_schema::{
assert_length, assert_length,
newtypes::InstanceId,
source::{ source::{
instance::Instance, instance::Instance,
instance_block::{InstanceBlock, InstanceBlockForm},
person::{Person, PersonInsertForm}, person::{Person, PersonInsertForm},
person_block::{PersonBlock, PersonBlockForm}, person_block::{PersonBlock, PersonBlockForm},
private_message::{PrivateMessage, PrivateMessageInsertForm}, private_message::{PrivateMessage, PrivateMessageInsertForm},
}, },
traits::{Blockable, Crud}, traits::{Blockable, Crud},
utils::build_db_pool_for_tests, utils::{build_db_pool_for_tests, DbPool},
}; };
use lemmy_utils::error::LemmyResult;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use serial_test::serial; use serial_test::serial;
#[tokio::test] struct Data {
#[serial] instance: Instance,
async fn test_crud() { timmy: Person,
jess: Person,
sara: Person,
}
async fn init_data(pool: &mut DbPool<'_>) -> LemmyResult<Data> {
let message_content = String::new(); let message_content = String::new();
let pool = &build_db_pool_for_tests().await;
let pool = &mut pool.into();
let instance = Instance::read_or_create(pool, "my_domain.tld".to_string()) let instance = Instance::read_or_create(pool, "my_domain.tld".to_string())
.await .await
@ -243,6 +269,32 @@ mod tests {
.await .await
.unwrap(); .unwrap();
Ok(Data {
instance,
timmy,
jess,
sara,
})
}
async fn cleanup(instance_id: InstanceId, pool: &mut DbPool<'_>) -> LemmyResult<()> {
// This also deletes all persons and private messages thanks to sql `on delete cascade`
Instance::delete(pool, instance_id).await.unwrap();
Ok(())
}
#[tokio::test]
#[serial]
async fn read_private_messages() -> LemmyResult<()> {
let pool = &build_db_pool_for_tests().await;
let pool = &mut pool.into();
let Data {
timmy,
jess,
sara,
instance,
} = init_data(pool).await?;
let timmy_messages = PrivateMessageQuery { let timmy_messages = PrivateMessageQuery {
unread_only: false, unread_only: false,
creator_id: None, creator_id: None,
@ -303,6 +355,21 @@ mod tests {
assert_eq!(timmy_sara_unread_messages[0].creator.id, sara.id); assert_eq!(timmy_sara_unread_messages[0].creator.id, sara.id);
assert_eq!(timmy_sara_unread_messages[0].recipient.id, timmy.id); assert_eq!(timmy_sara_unread_messages[0].recipient.id, timmy.id);
cleanup(instance.id, pool).await
}
#[tokio::test]
#[serial]
async fn ensure_person_block() -> LemmyResult<()> {
let pool = &build_db_pool_for_tests().await;
let pool = &mut pool.into();
let Data {
timmy,
sara,
instance,
jess: _,
} = init_data(pool).await?;
// Make sure blocks are working // Make sure blocks are working
let timmy_blocks_sara_form = PersonBlockForm { let timmy_blocks_sara_form = PersonBlockForm {
person_id: timmy.id, person_id: timmy.id,
@ -336,7 +403,52 @@ mod tests {
.unwrap(); .unwrap();
assert_eq!(timmy_unread_messages, 1); assert_eq!(timmy_unread_messages, 1);
// This also deletes all persons and private messages thanks to sql `on delete cascade` cleanup(instance.id, pool).await
Instance::delete(pool, instance.id).await.unwrap(); }
#[tokio::test]
#[serial]
async fn ensure_instance_block() -> LemmyResult<()> {
let pool = &build_db_pool_for_tests().await;
let pool = &mut pool.into();
let Data {
timmy,
jess: _,
sara,
instance,
} = init_data(pool).await?;
// Make sure instance_blocks are working
let timmy_blocks_instance_form = InstanceBlockForm {
person_id: timmy.id,
instance_id: sara.instance_id,
};
let inserted_instance_block = InstanceBlock::block(pool, &timmy_blocks_instance_form)
.await
.unwrap();
let expected_instance_block = InstanceBlock {
person_id: timmy.id,
instance_id: sara.instance_id,
published: inserted_instance_block.published,
};
assert_eq!(expected_instance_block, inserted_instance_block);
let timmy_messages = PrivateMessageQuery {
unread_only: true,
creator_id: None,
..Default::default()
}
.list(pool, timmy.id)
.await
.unwrap();
assert_length!(0, &timmy_messages);
let timmy_unread_messages = PrivateMessageView::get_unread_messages(pool, timmy.id)
.await
.unwrap();
assert_eq!(timmy_unread_messages, 0);
cleanup(instance.id, pool).await
} }
} }

View File

@ -98,7 +98,7 @@ pub struct PostReportView {
/// currently this is just a wrapper around post id, but should be seen as opaque from the client's perspective /// currently this is just a wrapper around post id, but should be seen as opaque from the client's perspective
/// stringified since we might want to use arbitrary info later, with a P prepended to prevent ossification /// stringified since we might want to use arbitrary info later, with a P prepended to prevent ossification
/// (api users love to make assumptions (e.g. parse stuff that looks like numbers as numbers) about apis that aren't part of the spec /// (api users love to make assumptions (e.g. parse stuff that looks like numbers as numbers) about apis that aren't part of the spec
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(ts_rs::TS))] #[cfg_attr(feature = "full", derive(ts_rs::TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
pub struct PaginationCursor(pub String); pub struct PaginationCursor(pub String);

View File

@ -26,7 +26,13 @@ use lemmy_utils::{
utils::markdown::{markdown_to_html, sanitize_html}, utils::markdown::{markdown_to_html, sanitize_html},
}; };
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use rss::{extension::dublincore::DublinCoreExtension, Channel, Guid, Item}; use rss::{
extension::{dublincore::DublinCoreExtension, ExtensionBuilder, ExtensionMap},
Channel,
EnclosureBuilder,
Guid,
Item,
};
use serde::Deserialize; use serde::Deserialize;
use std::{collections::BTreeMap, str::FromStr}; use std::{collections::BTreeMap, str::FromStr};
@ -80,9 +86,30 @@ static RSS_NAMESPACE: Lazy<BTreeMap<String, String>> = Lazy::new(|| {
"dc".to_string(), "dc".to_string(),
rss::extension::dublincore::NAMESPACE.to_string(), rss::extension::dublincore::NAMESPACE.to_string(),
); );
h.insert(
"media".to_string(),
"http://search.yahoo.com/mrss/".to_string(),
);
h h
}); });
/// Removes any characters disallowed by the XML grammar.
/// See https://www.w3.org/TR/xml/#NT-Char for details.
fn sanitize_xml(input: String) -> String {
input
.chars()
.filter(|&c| {
matches!(c,
'\u{09}'
| '\u{0A}'
| '\u{0D}'
| '\u{20}'..='\u{D7FF}'
| '\u{E000}'..='\u{FFFD}'
| '\u{10000}'..='\u{10FFFF}')
})
.collect()
}
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn get_all_feed( async fn get_all_feed(
info: web::Query<Params>, info: web::Query<Params>,
@ -247,10 +274,9 @@ async fn get_feed_user(
.await?; .await?;
let items = create_post_items(posts, &context.settings().get_protocol_and_hostname())?; let items = create_post_items(posts, &context.settings().get_protocol_and_hostname())?;
let channel = Channel { let channel = Channel {
namespaces: RSS_NAMESPACE.clone(), namespaces: RSS_NAMESPACE.clone(),
title: format!("{} - {}", site_view.site.name, person.name), title: format!("{} - {}", sanitize_xml(site_view.site.name), person.name),
link: person.actor_id.to_string(), link: person.actor_id.to_string(),
items, items,
..Default::default() ..Default::default()
@ -289,7 +315,7 @@ async fn get_feed_community(
let mut channel = Channel { let mut channel = Channel {
namespaces: RSS_NAMESPACE.clone(), namespaces: RSS_NAMESPACE.clone(),
title: format!("{} - {}", site_view.site.name, community.name), title: format!("{} - {}", sanitize_xml(site_view.site.name), community.name),
link: community.actor_id.to_string(), link: community.actor_id.to_string(),
items, items,
..Default::default() ..Default::default()
@ -328,10 +354,9 @@ async fn get_feed_front(
let protocol_and_hostname = context.settings().get_protocol_and_hostname(); let protocol_and_hostname = context.settings().get_protocol_and_hostname();
let items = create_post_items(posts, &protocol_and_hostname)?; let items = create_post_items(posts, &protocol_and_hostname)?;
let mut channel = Channel { let mut channel = Channel {
namespaces: RSS_NAMESPACE.clone(), namespaces: RSS_NAMESPACE.clone(),
title: format!("{} - Subscribed", site_view.site.name), title: format!("{} - Subscribed", sanitize_xml(site_view.site.name)),
link: protocol_and_hostname, link: protocol_and_hostname,
items, items,
..Default::default() ..Default::default()
@ -382,7 +407,7 @@ async fn get_feed_inbox(context: &LemmyContext, jwt: &str) -> Result<Channel, Le
let mut channel = Channel { let mut channel = Channel {
namespaces: RSS_NAMESPACE.clone(), namespaces: RSS_NAMESPACE.clone(),
title: format!("{} - Inbox", site_view.site.name), title: format!("{} - Inbox", sanitize_xml(site_view.site.name)),
link: format!("{protocol_and_hostname}/inbox"), link: format!("{protocol_and_hostname}/inbox"),
items, items,
..Default::default() ..Default::default()
@ -471,7 +496,6 @@ fn create_post_items(
let mut items: Vec<Item> = Vec::new(); let mut items: Vec<Item> = Vec::new();
for p in posts { for p in posts {
// TODO add images
let post_url = format!("{}/post/{}", protocol_and_hostname, p.post.id); let post_url = format!("{}/post/{}", protocol_and_hostname, p.post.id);
let community_url = format!( let community_url = format!(
"{}/c/{}", "{}/c/{}",
@ -496,12 +520,21 @@ fn create_post_items(
p.counts.comments); p.counts.comments);
// If its a url post, add it to the description // If its a url post, add it to the description
let link = Some(if let Some(url) = p.post.url { // and see if we can parse it as a media enclosure.
let enclosure_opt = p.post.url.map(|url| {
let link_html = format!("<br><a href=\"{url}\">{url}</a>"); let link_html = format!("<br><a href=\"{url}\">{url}</a>");
description.push_str(&link_html); description.push_str(&link_html);
url.to_string()
} else { let mime_type = p
post_url.clone() .post
.url_content_type
.unwrap_or_else(|| "application/octet-stream".to_string());
let mut enclosure_bld = EnclosureBuilder::default();
enclosure_bld.url(url.as_str().to_string());
enclosure_bld.mime_type(mime_type);
enclosure_bld.length("0".to_string());
enclosure_bld.build()
}); });
if let Some(body) = p.post.body { if let Some(body) = p.post.body {
@ -509,14 +542,34 @@ fn create_post_items(
description.push_str(&html); description.push_str(&html);
} }
let mut extensions = ExtensionMap::new();
// If there's a thumbnail URL, add a media:content tag to display it.
// See https://www.rssboard.org/media-rss#media-content for details.
if let Some(url) = p.post.thumbnail_url {
let mut thumbnail_ext = ExtensionBuilder::default();
thumbnail_ext.name("media:content".to_string());
thumbnail_ext.attrs(BTreeMap::from([
("url".to_string(), url.to_string()),
("medium".to_string(), "image".to_string()),
]));
extensions.insert(
"media".to_string(),
BTreeMap::from([("content".to_string(), vec![thumbnail_ext.build()])]),
);
}
let i = Item { let i = Item {
title: Some(sanitize_html(&p.post.name)), title: Some(sanitize_html(sanitize_xml(p.post.name).as_str())),
pub_date: Some(p.post.published.to_rfc2822()), pub_date: Some(p.post.published.to_rfc2822()),
comments: Some(post_url.clone()), comments: Some(post_url.clone()),
guid, guid,
description: Some(description), description: Some(sanitize_xml(description)),
dublin_core_ext, dublin_core_ext,
link, link: Some(post_url.clone()),
extensions,
enclosure: enclosure_opt,
..Default::default() ..Default::default()
}; };

View File

@ -99,7 +99,7 @@ services:
logging: *default-logging logging: *default-logging
postgres: postgres:
image: postgres:15-alpine image: postgres:16-alpine
# this needs to match the database host in lemmy.hson # this needs to match the database host in lemmy.hson
# Tune your settings via # Tune your settings via
# https://pgtune.leopard.in.ua/#/ # https://pgtune.leopard.in.ua/#/

View File

@ -20,7 +20,7 @@ x-lemmy-default: &lemmy-default
restart: always restart: always
x-postgres-default: &postgres-default x-postgres-default: &postgres-default
image: postgres:15-alpine image: postgres:16-alpine
environment: environment:
- POSTGRES_USER=lemmy - POSTGRES_USER=lemmy
- POSTGRES_PASSWORD=password - POSTGRES_PASSWORD=password

View File

@ -52,7 +52,7 @@ ask_to_auto_reload() {
done done
if [ "$auto_reload_final" = 1 ] if [ "$auto_reload_final" = 1 ]
then then
cd ui && yarn start cd ui && pnpm dev
cd server && cargo watch -x run cd server && cargo watch -x run
fi fi
} }
@ -62,8 +62,9 @@ ask_to_init_db
# Build the web client # Build the web client
cd ui cd ui
yarn pnpm i
yarn build pnpm prebuild:prod
pnpm build:prod
# Build and run the backend # Build and run the backend
cd ../server cd ../server

View File

@ -5,8 +5,6 @@ CWD="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
cd $CWD/../ cd $CWD/../
cargo clippy --workspace --fix --allow-staged --allow-dirty --tests --all-targets --all-features -- -D warnings
# Format rust files # Format rust files
cargo +nightly fmt cargo +nightly fmt
@ -15,3 +13,5 @@ taplo format
# Format sql files # Format sql files
find migrations -type f -name '*.sql' -exec pg_format -i {} + find migrations -type f -name '*.sql' -exec pg_format -i {} +
cargo clippy --workspace --fix --allow-staged --allow-dirty --tests --all-targets --all-features -- -D warnings

View File

@ -0,0 +1,42 @@
#!/bin/sh
set -e
echo "Do not stop in the middle of this upgrade, wait until you see the message: Upgrade complete."
echo "Stopping lemmy and all services..."
sudo docker-compose stop
echo "Make sure postgres is started..."
sudo docker-compose up -d postgres
echo "Waiting..."
sleep 20s
echo "Exporting the Database to 15_16.dump.sql ..."
sudo docker-compose exec -T postgres pg_dumpall -c -U lemmy > 15_16_dump.sql
echo "Done."
echo "Stopping postgres..."
sudo docker-compose stop postgres
echo "Waiting..."
sleep 20s
echo "Removing the old postgres folder"
sudo rm -rf volumes/postgres
echo "Updating docker-compose to use postgres version 16."
sed -i "s/image: postgres:.*/image: postgres:16-alpine/" ./docker-compose.yml
echo "Starting up new postgres..."
sudo docker-compose up -d postgres
echo "Waiting..."
sleep 20s
echo "Importing the database...."
cat 15_16_dump.sql | sudo docker-compose exec -T postgres psql -U lemmy
echo "Done."
echo "Starting up lemmy..."
sudo docker-compose up -d
echo "A copy of your old database is at 15_16.dump.sql . You can delete this file if the upgrade went smoothly."
echo "Upgrade complete."

View File

@ -26,8 +26,6 @@ if [ ! -z "${third_semver##*[!0-9]*}" ]; then
echo $new_tag > "VERSION" echo $new_tag > "VERSION"
git add "VERSION" git add "VERSION"
git commit -m"Updating VERSION" git commit -m"Updating VERSION"
git tag $new_tag
git push origin $new_tag
git push git push
popd popd
fi fi