Add support for donation dialog (fixes #4856) (#5318)

* Add support for donation dialog (fixes #4856)

* more changes

* test

* remove files

* default value for new user last_donation_notification

* move disable_donation_dialog to local_site

* restore formatting
This commit is contained in:
Nutomic 2025-01-17 12:28:41 +00:00 committed by GitHub
parent 4120c2fc2f
commit 67b36c6537
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 78 additions and 44 deletions

View file

@ -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<LemmyContext>,
local_user_view: LocalUserView,
) -> LemmyResult<Json<SuccessResponse>> {
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()))
}

View file

@ -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;

View file

@ -238,6 +238,8 @@ pub struct CreateSite {
pub comment_upvotes: Option<FederationMode>,
#[cfg_attr(feature = "full", ts(optional))]
pub comment_downvotes: Option<FederationMode>,
#[cfg_attr(feature = "full", ts(optional))]
pub disable_donation_dialog: Option<bool>,
}
#[skip_serializing_none]
@ -365,6 +367,10 @@ pub struct EditSite {
/// What kind of comment downvotes your site allows.
#[cfg_attr(feature = "full", ts(optional))]
pub comment_downvotes: Option<FederationMode>,
/// 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<bool>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]

View file

@ -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()
};

View file

@ -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()
};

View file

@ -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<String> {
async fn create_local_user(
context: &Data<LemmyContext>,
language_tags: Vec<String>,
local_user_form: &LocalUserInsertForm,
local_site_id: SiteId,
mut local_user_form: LocalUserInsertForm,
local_site: &LocalSite,
) -> Result<LocalUser, LemmyError> {
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)
}

View file

@ -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,
}
}

View file

@ -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<FederationMode>,
#[new(default)]
pub comment_downvotes: Option<FederationMode>,
#[new(default)]
pub disable_donation_dialog: Option<bool>,
}
#[derive(Clone, Default)]
@ -175,4 +180,5 @@ pub struct LocalSiteUpdateForm {
pub post_downvotes: Option<FederationMode>,
pub comment_upvotes: Option<FederationMode>,
pub comment_downvotes: Option<FederationMode>,
pub disable_donation_dialog: Option<bool>,
}

View file

@ -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<Utc>,
/// 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<bool>,
#[new(default)]
pub last_donation_notification: Option<DateTime<Utc>>,
#[new(default)]
pub hide_media: Option<bool>,
}
@ -164,5 +170,6 @@ pub struct LocalUserUpdateForm {
pub collapse_bot_comments: Option<bool>,
pub default_comment_sort_type: Option<CommentSortType>,
pub auto_mark_fetched_posts_as_read: Option<bool>,
pub last_donation_notification: Option<DateTime<Utc>>,
pub hide_media: Option<bool>,
}

View file

@ -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,

View file

@ -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<PictrsConfig>,
/// Email sending configuration. All options except login/password are mandatory
#[default(None)]
#[doku(example = "Some(Default::default())")]
pub email: Option<EmailConfig>,
/// Parameters for automatic configuration of new instance (only used at first start)
#[default(None)]
#[doku(example = "Some(Default::default())")]
pub setup: Option<SetupConfig>,
/// 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<Url>,
#[default(Default::default())]
pub federation: FederationWorkerConfig,
// Prometheus configuration.
#[default(None)]
#[doku(example = "Some(Default::default())")]
pub prometheus: Option<PrometheusConfig>,
/// 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<String>,
}
@ -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<String>,
/// 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<String>,
}

View file

@ -0,0 +1,6 @@
ALTER TABLE local_user
DROP COLUMN last_donation_notification;
ALTER TABLE local_site
DROP COLUMN disable_donation_dialog;

View file

@ -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;

View file

@ -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))