diff --git a/crates/api/src/local_user/donation_dialog_shown.rs b/crates/api/src/local_user/donation_dialog_shown.rs new file mode 100644 index 000000000..103d6382d --- /dev/null +++ b/crates/api/src/local_user/donation_dialog_shown.rs @@ -0,0 +1,19 @@ +use actix_web::web::{Data, Json}; +use chrono::Utc; +use lemmy_api_common::{context::LemmyContext, SuccessResponse}; +use lemmy_db_schema::source::local_user::{LocalUser, LocalUserUpdateForm}; +use lemmy_db_views::structs::LocalUserView; +use lemmy_utils::error::LemmyResult; + +pub async fn donation_dialog_shown( + context: Data, + local_user_view: LocalUserView, +) -> LemmyResult> { + let form = LocalUserUpdateForm { + last_donation_notification: Some(Utc::now()), + ..Default::default() + }; + LocalUser::update(&mut context.pool(), local_user_view.local_user.id, &form).await?; + + Ok(Json(SuccessResponse::default())) +} diff --git a/crates/api/src/local_user/mod.rs b/crates/api/src/local_user/mod.rs index 0dcd24aff..15243056d 100644 --- a/crates/api/src/local_user/mod.rs +++ b/crates/api/src/local_user/mod.rs @@ -3,6 +3,7 @@ pub mod ban_person; pub mod block; pub mod change_password; pub mod change_password_after_reset; +pub mod donation_dialog_shown; pub mod generate_totp_secret; pub mod get_captcha; pub mod list_banned; diff --git a/crates/api_common/src/site.rs b/crates/api_common/src/site.rs index 59927df8c..3ed7fb9fa 100644 --- a/crates/api_common/src/site.rs +++ b/crates/api_common/src/site.rs @@ -236,6 +236,8 @@ pub struct CreateSite { pub comment_upvotes: Option, #[cfg_attr(feature = "full", ts(optional))] pub comment_downvotes: Option, + #[cfg_attr(feature = "full", ts(optional))] + pub disable_donation_dialog: Option, } #[skip_serializing_none] @@ -363,6 +365,10 @@ pub struct EditSite { /// What kind of comment downvotes your site allows. #[cfg_attr(feature = "full", ts(optional))] pub comment_downvotes: Option, + /// If this is true, users will never see the dialog asking to support Lemmy development with + /// donations. + #[cfg_attr(feature = "full", ts(optional))] + pub disable_donation_dialog: Option, } #[derive(Debug, Serialize, Deserialize, Clone)] diff --git a/crates/api_crud/src/site/create.rs b/crates/api_crud/src/site/create.rs index 34965742d..454b04dbd 100644 --- a/crates/api_crud/src/site/create.rs +++ b/crates/api_crud/src/site/create.rs @@ -105,6 +105,7 @@ pub async fn create_site( post_downvotes: data.post_downvotes, comment_upvotes: data.comment_upvotes, comment_downvotes: data.comment_downvotes, + disable_donation_dialog: data.disable_donation_dialog, ..Default::default() }; diff --git a/crates/api_crud/src/site/update.rs b/crates/api_crud/src/site/update.rs index 959c07ff0..439e9f9c4 100644 --- a/crates/api_crud/src/site/update.rs +++ b/crates/api_crud/src/site/update.rs @@ -111,6 +111,7 @@ pub async fn update_site( post_downvotes: data.post_downvotes, comment_upvotes: data.comment_upvotes, comment_downvotes: data.comment_downvotes, + disable_donation_dialog: data.disable_donation_dialog, ..Default::default() }; diff --git a/crates/api_crud/src/user/create.rs b/crates/api_crud/src/user/create.rs index 16156abe4..e82ae8cc4 100644 --- a/crates/api_crud/src/user/create.rs +++ b/crates/api_crud/src/user/create.rs @@ -21,7 +21,7 @@ use lemmy_api_common::{ }; use lemmy_db_schema::{ aggregates::structs::PersonAggregates, - newtypes::{InstanceId, OAuthProviderId, SiteId}, + newtypes::{InstanceId, OAuthProviderId}, source::{ actor_language::SiteLanguage, captcha_answer::{CaptchaAnswer, CheckCaptchaAnswer}, @@ -139,21 +139,11 @@ pub async fn register( email: data.email.as_deref().map(str::to_lowercase), show_nsfw: Some(show_nsfw), accepted_application, - default_listing_type: Some(local_site.default_post_listing_type), - post_listing_mode: Some(local_site.default_post_listing_mode), - interface_language: language_tags.first().cloned(), - // If its the initial site setup, they are an admin - admin: Some(!local_site.site_setup), ..LocalUserInsertForm::new(inserted_person.id, Some(data.password.to_string())) }; - let inserted_local_user = create_local_user( - &context, - language_tags, - &local_user_form, - local_site.site_id, - ) - .await?; + let inserted_local_user = + create_local_user(&context, language_tags, local_user_form, &local_site).await?; if local_site.site_setup && require_registration_application { if let Some(answer) = data.answer.clone() { @@ -369,20 +359,10 @@ pub async fn authenticate_with_oauth( show_nsfw: Some(show_nsfw), accepted_application: Some(!require_registration_application), email_verified: Some(oauth_provider.auto_verify_email), - post_listing_mode: Some(local_site.default_post_listing_mode), - interface_language: language_tags.first().cloned(), - // If its the initial site setup, they are an admin - admin: Some(!local_site.site_setup), ..LocalUserInsertForm::new(person.id, None) }; - local_user = create_local_user( - &context, - language_tags, - &local_user_form, - local_site.site_id, - ) - .await?; + local_user = create_local_user(&context, language_tags, local_user_form, &local_site).await?; // Create the oauth account let oauth_account_form = @@ -472,28 +452,33 @@ fn get_language_tags(req: &HttpRequest) -> Vec { async fn create_local_user( context: &Data, language_tags: Vec, - local_user_form: &LocalUserInsertForm, - local_site_id: SiteId, + mut local_user_form: LocalUserInsertForm, + local_site: &LocalSite, ) -> Result { let all_languages = Language::read_all(&mut context.pool()).await?; // use hashset to avoid duplicates let mut language_ids = HashSet::new(); // Enable languages from `Accept-Language` header - for l in language_tags { - if let Some(found) = all_languages.iter().find(|all| all.code == l) { + for l in &language_tags { + if let Some(found) = all_languages.iter().find(|all| &all.code == l) { language_ids.insert(found.id); } } // Enable site languages. Ignored if all languages are enabled. - let discussion_languages = SiteLanguage::read(&mut context.pool(), local_site_id).await?; + let discussion_languages = SiteLanguage::read(&mut context.pool(), local_site.site_id).await?; language_ids.extend(discussion_languages); let language_ids = language_ids.into_iter().collect(); + local_user_form.default_listing_type = Some(local_site.default_post_listing_type); + local_user_form.post_listing_mode = Some(local_site.default_post_listing_mode); + // If its the initial site setup, they are an admin + local_user_form.admin = Some(!local_site.site_setup); + local_user_form.interface_language = language_tags.first().cloned(); let inserted_local_user = - LocalUser::create(&mut context.pool(), local_user_form, language_ids).await?; + LocalUser::create(&mut context.pool(), &local_user_form, language_ids).await?; Ok(inserted_local_user) } diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index aa85bff3a..2e229ce85 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -443,6 +443,7 @@ diesel::table! { post_downvotes -> FederationModeEnum, comment_upvotes -> FederationModeEnum, comment_downvotes -> FederationModeEnum, + disable_donation_dialog -> Bool, } } @@ -514,6 +515,7 @@ diesel::table! { collapse_bot_comments -> Bool, default_comment_sort_type -> CommentSortTypeEnum, auto_mark_fetched_posts_as_read -> Bool, + last_donation_notification -> Timestamptz, hide_media -> Bool, } } diff --git a/crates/db_schema/src/source/local_site.rs b/crates/db_schema/src/source/local_site.rs index b5bcebc58..25ec40ca9 100644 --- a/crates/db_schema/src/source/local_site.rs +++ b/crates/db_schema/src/source/local_site.rs @@ -83,6 +83,9 @@ pub struct LocalSite { pub comment_upvotes: FederationMode, /// What kind of comment downvotes your site allows. pub comment_downvotes: FederationMode, + /// If this is true, users will never see the dialog asking to support Lemmy development with + /// donations. + pub disable_donation_dialog: bool, } #[derive(Clone, derive_new::new)] @@ -142,6 +145,8 @@ pub struct LocalSiteInsertForm { pub comment_upvotes: Option, #[new(default)] pub comment_downvotes: Option, + #[new(default)] + pub disable_donation_dialog: Option, } #[derive(Clone, Default)] @@ -175,4 +180,5 @@ pub struct LocalSiteUpdateForm { pub post_downvotes: Option, pub comment_upvotes: Option, pub comment_downvotes: Option, + pub disable_donation_dialog: Option, } diff --git a/crates/db_schema/src/source/local_user.rs b/crates/db_schema/src/source/local_user.rs index 357d7b6b7..26e1c5475 100644 --- a/crates/db_schema/src/source/local_user.rs +++ b/crates/db_schema/src/source/local_user.rs @@ -8,6 +8,7 @@ use crate::{ PostListingMode, PostSortType, }; +use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; #[cfg(feature = "full")] @@ -70,6 +71,9 @@ pub struct LocalUser { pub default_comment_sort_type: CommentSortType, /// Whether to automatically mark fetched posts as read. pub auto_mark_fetched_posts_as_read: bool, + /// The last time a donation request was shown to this user. If this is more than a year ago, + /// a new notification request should be shown. + pub last_donation_notification: DateTime, /// Whether to hide posts containing images/videos pub hide_media: bool, } @@ -131,6 +135,8 @@ pub struct LocalUserInsertForm { #[new(default)] pub auto_mark_fetched_posts_as_read: Option, #[new(default)] + pub last_donation_notification: Option>, + #[new(default)] pub hide_media: Option, } @@ -164,5 +170,6 @@ pub struct LocalUserUpdateForm { pub collapse_bot_comments: Option, pub default_comment_sort_type: Option, pub auto_mark_fetched_posts_as_read: Option, + pub last_donation_notification: Option>, pub hide_media: Option, } diff --git a/crates/db_views/src/registration_applications/registration_application_view.rs b/crates/db_views/src/registration_applications/registration_application_view.rs index d901e9e4a..cdb6af61d 100644 --- a/crates/db_views/src/registration_applications/registration_application_view.rs +++ b/crates/db_views/src/registration_applications/registration_application_view.rs @@ -235,15 +235,14 @@ mod tests { password_encrypted: inserted_sara_local_user.password_encrypted, open_links_in_new_tab: inserted_sara_local_user.open_links_in_new_tab, infinite_scroll_enabled: inserted_sara_local_user.infinite_scroll_enabled, - admin: false, post_listing_mode: inserted_sara_local_user.post_listing_mode, totp_2fa_enabled: inserted_sara_local_user.totp_2fa_enabled, enable_keyboard_navigation: inserted_sara_local_user.enable_keyboard_navigation, enable_animated_images: inserted_sara_local_user.enable_animated_images, enable_private_messages: inserted_sara_local_user.enable_private_messages, collapse_bot_comments: inserted_sara_local_user.collapse_bot_comments, - auto_mark_fetched_posts_as_read: false, - hide_media: false, + last_donation_notification: inserted_sara_local_user.last_donation_notification, + ..Default::default() }, creator: Person { id: inserted_sara_person.id, diff --git a/crates/utils/src/settings/structs.rs b/crates/utils/src/settings/structs.rs index 4e5152acc..effd68a64 100644 --- a/crates/utils/src/settings/structs.rs +++ b/crates/utils/src/settings/structs.rs @@ -12,17 +12,14 @@ use url::Url; #[serde(default)] pub struct Settings { /// settings related to the postgresql database - #[default(Default::default())] pub database: DatabaseConfig, /// Pictrs image server configuration. #[default(Some(Default::default()))] pub(crate) pictrs: Option, /// Email sending configuration. All options except login/password are mandatory - #[default(None)] #[doku(example = "Some(Default::default())")] pub email: Option, /// Parameters for automatic configuration of new instance (only used at first start) - #[default(None)] #[doku(example = "Some(Default::default())")] pub setup: Option, /// the domain name of your instance (mandatory) @@ -41,18 +38,14 @@ pub struct Settings { pub tls_enabled: bool, /// Set the URL for opentelemetry exports. If you do not have an opentelemetry collector, do not /// set this option - #[default(None)] #[doku(skip)] pub opentelemetry_url: Option, - #[default(Default::default())] pub federation: FederationWorkerConfig, // Prometheus configuration. - #[default(None)] #[doku(example = "Some(Default::default())")] pub prometheus: Option, /// Sets a response Access-Control-Allow-Origin CORS header /// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin - #[default(None)] #[doku(example = "lemmy.tld")] cors_origin: Option, } @@ -74,7 +67,6 @@ pub struct PictrsConfig { pub url: Url, /// Set a custom pictrs API key. ( Required for deleting images ) - #[default(None)] pub api_key: Option, /// Specifies how to handle remote images, so that users don't have to connect directly to remote @@ -114,7 +106,7 @@ pub struct PictrsConfig { pub image_upload_disabled: bool, } -#[derive(Debug, Deserialize, Serialize, Clone, SmartDefault, Document, PartialEq)] +#[derive(Debug, Deserialize, Serialize, Clone, Default, Document, PartialEq)] #[serde(deny_unknown_fields)] pub enum PictrsImageMode { /// Leave images unchanged, don't generate any local thumbnails for post urls. Instead the @@ -185,7 +177,7 @@ impl EmailConfig { } } -#[derive(Debug, Deserialize, Serialize, Clone, SmartDefault, Document)] +#[derive(Debug, Deserialize, Serialize, Clone, Default, Document)] #[serde(deny_unknown_fields)] pub struct SetupConfig { /// Username for the admin user @@ -199,7 +191,6 @@ pub struct SetupConfig { pub site_name: String, /// Email for the admin user (optional, can be omitted and set later through the website) #[doku(example = "user@example.com")] - #[default(None)] pub admin_email: Option, } diff --git a/migrations/2025-01-10-135505_donation-dialog/down.sql b/migrations/2025-01-10-135505_donation-dialog/down.sql new file mode 100644 index 000000000..8956c15eb --- /dev/null +++ b/migrations/2025-01-10-135505_donation-dialog/down.sql @@ -0,0 +1,6 @@ +ALTER TABLE local_user + DROP COLUMN last_donation_notification; + +ALTER TABLE local_site + DROP COLUMN disable_donation_dialog; + diff --git a/migrations/2025-01-10-135505_donation-dialog/up.sql b/migrations/2025-01-10-135505_donation-dialog/up.sql new file mode 100644 index 000000000..0f263e004 --- /dev/null +++ b/migrations/2025-01-10-135505_donation-dialog/up.sql @@ -0,0 +1,8 @@ +-- Generate new column last_donation_notification with default value at random time in the +-- past year (so that users dont see it all at the same time after instance upgrade). +ALTER TABLE local_user + ADD COLUMN last_donation_notification timestamptz NOT NULL DEFAULT (now() - (random() * (interval '12 months'))); + +ALTER TABLE local_site + ADD COLUMN disable_donation_dialog boolean NOT NULL DEFAULT FALSE; + diff --git a/src/api_routes_v4.rs b/src/api_routes_v4.rs index c0eb33fab..852e868fd 100644 --- a/src/api_routes_v4.rs +++ b/src/api_routes_v4.rs @@ -26,6 +26,7 @@ use lemmy_api::{ block::user_block_person, change_password::change_password, change_password_after_reset::change_password_after_reset, + donation_dialog_shown::donation_dialog_shown, generate_totp_secret::generate_totp_secret, get_captcha::get_captcha, list_banned::list_banned_users, @@ -331,6 +332,7 @@ pub fn config(cfg: &mut ServiceConfig, rate_limit: &RateLimitCell) { .route("/unread_count", get().to(unread_count)) .route("/list_logins", get().to(list_logins)) .route("/validate_auth", get().to(validate_auth)) + .route("/donation_dialog_shown", post().to(donation_dialog_shown)) .route("/avatar", post().to(upload_user_avatar)) .route("/avatar", delete().to(delete_user_avatar)) .route("/banner", post().to(upload_user_banner))