fix(labrinth): retire Sendy for new email newsletter subscriptions (#4073)
* tweak(frontend): do not sign up for the newsletter by default * fix(labrinth): retire Sendy for new email newsletter subscriptions
This commit is contained in:
parent
175b90be5a
commit
c7d0839bfb
@ -218,7 +218,7 @@ const username = ref("");
|
|||||||
const password = ref("");
|
const password = ref("");
|
||||||
const confirmPassword = ref("");
|
const confirmPassword = ref("");
|
||||||
const token = ref("");
|
const token = ref("");
|
||||||
const subscribe = ref(true);
|
const subscribe = ref(false);
|
||||||
|
|
||||||
async function createAccount() {
|
async function createAccount() {
|
||||||
startLoading();
|
startLoading();
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "PostgreSQL",
|
"db_name": "PostgreSQL",
|
||||||
"query": "\n INSERT INTO users (\n id, username, email,\n avatar_url, raw_avatar_url, bio, created,\n github_id, discord_id, gitlab_id, google_id, steam_id, microsoft_id,\n email_verified, password, paypal_id, paypal_country, paypal_email,\n venmo_handle, stripe_customer_id, allow_friend_requests\n )\n VALUES (\n $1, $2, $3, $4, $5,\n $6, $7,\n $8, $9, $10, $11, $12, $13,\n $14, $15, $16, $17, $18, $19, $20, $21\n )\n ",
|
"query": "\n INSERT INTO users (\n id, username, email,\n avatar_url, raw_avatar_url, bio, created,\n github_id, discord_id, gitlab_id, google_id, steam_id, microsoft_id,\n email_verified, password, paypal_id, paypal_country, paypal_email,\n venmo_handle, stripe_customer_id, allow_friend_requests, is_subscribed_to_newsletter\n )\n VALUES (\n $1, $2, $3, $4, $5,\n $6, $7,\n $8, $9, $10, $11, $12, $13,\n $14, $15, $16, $17, $18, $19, $20, $21, $22\n )\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@ -25,10 +25,11 @@
|
|||||||
"Text",
|
"Text",
|
||||||
"Text",
|
"Text",
|
||||||
"Text",
|
"Text",
|
||||||
|
"Bool",
|
||||||
"Bool"
|
"Bool"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"nullable": []
|
"nullable": []
|
||||||
},
|
},
|
||||||
"hash": "32fa1030a3a69f6bc36c6ec916ba8d0724dfd683576629ab05f5df321d5f9a55"
|
"hash": "010c69fa61e1329156020b251e75d46bc09344c1846b3098accce5801e571e5e"
|
||||||
}
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "PostgreSQL",
|
"db_name": "PostgreSQL",
|
||||||
"query": "\n SELECT id, email,\n avatar_url, raw_avatar_url, username, bio,\n created, role, badges,\n github_id, discord_id, gitlab_id, google_id, steam_id, microsoft_id,\n email_verified, password, totp_secret, paypal_id, paypal_country, paypal_email,\n venmo_handle, stripe_customer_id, allow_friend_requests\n FROM users\n WHERE id = ANY($1) OR LOWER(username) = ANY($2)\n ",
|
"query": "\n SELECT id, email,\n avatar_url, raw_avatar_url, username, bio,\n created, role, badges,\n github_id, discord_id, gitlab_id, google_id, steam_id, microsoft_id,\n email_verified, password, totp_secret, paypal_id, paypal_country, paypal_email,\n venmo_handle, stripe_customer_id, allow_friend_requests, is_subscribed_to_newsletter\n FROM users\n WHERE id = ANY($1) OR LOWER(username) = ANY($2)\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@ -122,6 +122,11 @@
|
|||||||
"ordinal": 23,
|
"ordinal": 23,
|
||||||
"name": "allow_friend_requests",
|
"name": "allow_friend_requests",
|
||||||
"type_info": "Bool"
|
"type_info": "Bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 24,
|
||||||
|
"name": "is_subscribed_to_newsletter",
|
||||||
|
"type_info": "Bool"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@ -154,8 +159,9 @@
|
|||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
|
false,
|
||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "b5857aafa522ca62294bc296fb6cddd862eca2889203e43b4962df07ba1221a0"
|
"hash": "5fcdeeeb820ada62e10feb0beefa29b0535241bbb6d74143925e16cf8cd720c4"
|
||||||
}
|
}
|
||||||
14
apps/labrinth/.sqlx/query-c960b09ddc19530383f143c349c7e34bf813ddbfb88bf31b9863078bc48c8623.json
generated
Normal file
14
apps/labrinth/.sqlx/query-c960b09ddc19530383f143c349c7e34bf813ddbfb88bf31b9863078bc48c8623.json
generated
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n UPDATE users\n SET is_subscribed_to_newsletter = TRUE\n WHERE id = $1\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "c960b09ddc19530383f143c349c7e34bf813ddbfb88bf31b9863078bc48c8623"
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE users ADD COLUMN is_subscribed_to_newsletter BOOLEAN NOT NULL DEFAULT FALSE;
|
||||||
@ -1,6 +1,6 @@
|
|||||||
use super::AuthProvider;
|
use super::AuthProvider;
|
||||||
use crate::auth::AuthenticationError;
|
use crate::auth::AuthenticationError;
|
||||||
use crate::database::models::user_item;
|
use crate::database::models::{DBUser, user_item};
|
||||||
use crate::database::redis::RedisPool;
|
use crate::database::redis::RedisPool;
|
||||||
use crate::models::pats::Scopes;
|
use crate::models::pats::Scopes;
|
||||||
use crate::models::users::User;
|
use crate::models::users::User;
|
||||||
@ -44,17 +44,16 @@ where
|
|||||||
Ok(Some((scopes, User::from_full(db_user))))
|
Ok(Some((scopes, User::from_full(db_user))))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_user_from_headers<'a, E>(
|
pub async fn get_full_user_from_headers<'a, E>(
|
||||||
req: &HttpRequest,
|
req: &HttpRequest,
|
||||||
executor: E,
|
executor: E,
|
||||||
redis: &RedisPool,
|
redis: &RedisPool,
|
||||||
session_queue: &AuthQueue,
|
session_queue: &AuthQueue,
|
||||||
required_scopes: Scopes,
|
required_scopes: Scopes,
|
||||||
) -> Result<(Scopes, User), AuthenticationError>
|
) -> Result<(Scopes, DBUser), AuthenticationError>
|
||||||
where
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||||
{
|
{
|
||||||
// Fetch DB user record and minos user from headers
|
|
||||||
let (scopes, db_user) = get_user_record_from_bearer_token(
|
let (scopes, db_user) = get_user_record_from_bearer_token(
|
||||||
req,
|
req,
|
||||||
None,
|
None,
|
||||||
@ -65,13 +64,33 @@ where
|
|||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
||||||
|
|
||||||
let user = User::from_full(db_user);
|
|
||||||
|
|
||||||
if !scopes.contains(required_scopes) {
|
if !scopes.contains(required_scopes) {
|
||||||
return Err(AuthenticationError::InvalidCredentials);
|
return Err(AuthenticationError::InvalidCredentials);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((scopes, user))
|
Ok((scopes, db_user))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_user_from_headers<'a, E>(
|
||||||
|
req: &HttpRequest,
|
||||||
|
executor: E,
|
||||||
|
redis: &RedisPool,
|
||||||
|
session_queue: &AuthQueue,
|
||||||
|
required_scopes: Scopes,
|
||||||
|
) -> Result<(Scopes, User), AuthenticationError>
|
||||||
|
where
|
||||||
|
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||||
|
{
|
||||||
|
let (scopes, db_user) = get_full_user_from_headers(
|
||||||
|
req,
|
||||||
|
executor,
|
||||||
|
redis,
|
||||||
|
session_queue,
|
||||||
|
required_scopes,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok((scopes, User::from_full(db_user)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_user_record_from_bearer_token<'a, 'b, E>(
|
pub async fn get_user_record_from_bearer_token<'a, 'b, E>(
|
||||||
|
|||||||
@ -49,6 +49,8 @@ pub struct DBUser {
|
|||||||
pub badges: Badges,
|
pub badges: Badges,
|
||||||
|
|
||||||
pub allow_friend_requests: bool,
|
pub allow_friend_requests: bool,
|
||||||
|
|
||||||
|
pub is_subscribed_to_newsletter: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DBUser {
|
impl DBUser {
|
||||||
@ -63,13 +65,13 @@ impl DBUser {
|
|||||||
avatar_url, raw_avatar_url, bio, created,
|
avatar_url, raw_avatar_url, bio, created,
|
||||||
github_id, discord_id, gitlab_id, google_id, steam_id, microsoft_id,
|
github_id, discord_id, gitlab_id, google_id, steam_id, microsoft_id,
|
||||||
email_verified, password, paypal_id, paypal_country, paypal_email,
|
email_verified, password, paypal_id, paypal_country, paypal_email,
|
||||||
venmo_handle, stripe_customer_id, allow_friend_requests
|
venmo_handle, stripe_customer_id, allow_friend_requests, is_subscribed_to_newsletter
|
||||||
)
|
)
|
||||||
VALUES (
|
VALUES (
|
||||||
$1, $2, $3, $4, $5,
|
$1, $2, $3, $4, $5,
|
||||||
$6, $7,
|
$6, $7,
|
||||||
$8, $9, $10, $11, $12, $13,
|
$8, $9, $10, $11, $12, $13,
|
||||||
$14, $15, $16, $17, $18, $19, $20, $21
|
$14, $15, $16, $17, $18, $19, $20, $21, $22
|
||||||
)
|
)
|
||||||
",
|
",
|
||||||
self.id as DBUserId,
|
self.id as DBUserId,
|
||||||
@ -93,6 +95,7 @@ impl DBUser {
|
|||||||
self.venmo_handle,
|
self.venmo_handle,
|
||||||
self.stripe_customer_id,
|
self.stripe_customer_id,
|
||||||
self.allow_friend_requests,
|
self.allow_friend_requests,
|
||||||
|
self.is_subscribed_to_newsletter,
|
||||||
)
|
)
|
||||||
.execute(&mut **transaction)
|
.execute(&mut **transaction)
|
||||||
.await?;
|
.await?;
|
||||||
@ -178,7 +181,7 @@ impl DBUser {
|
|||||||
created, role, badges,
|
created, role, badges,
|
||||||
github_id, discord_id, gitlab_id, google_id, steam_id, microsoft_id,
|
github_id, discord_id, gitlab_id, google_id, steam_id, microsoft_id,
|
||||||
email_verified, password, totp_secret, paypal_id, paypal_country, paypal_email,
|
email_verified, password, totp_secret, paypal_id, paypal_country, paypal_email,
|
||||||
venmo_handle, stripe_customer_id, allow_friend_requests
|
venmo_handle, stripe_customer_id, allow_friend_requests, is_subscribed_to_newsletter
|
||||||
FROM users
|
FROM users
|
||||||
WHERE id = ANY($1) OR LOWER(username) = ANY($2)
|
WHERE id = ANY($1) OR LOWER(username) = ANY($2)
|
||||||
",
|
",
|
||||||
@ -212,6 +215,7 @@ impl DBUser {
|
|||||||
stripe_customer_id: u.stripe_customer_id,
|
stripe_customer_id: u.stripe_customer_id,
|
||||||
totp_secret: u.totp_secret,
|
totp_secret: u.totp_secret,
|
||||||
allow_friend_requests: u.allow_friend_requests,
|
allow_friend_requests: u.allow_friend_requests,
|
||||||
|
is_subscribed_to_newsletter: u.is_subscribed_to_newsletter,
|
||||||
};
|
};
|
||||||
|
|
||||||
acc.insert(u.id, (Some(u.username), user));
|
acc.insert(u.id, (Some(u.username), user));
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
use crate::auth::email::send_email;
|
use crate::auth::email::send_email;
|
||||||
use crate::auth::validate::get_user_record_from_bearer_token;
|
use crate::auth::validate::{
|
||||||
|
get_full_user_from_headers, get_user_record_from_bearer_token,
|
||||||
|
};
|
||||||
use crate::auth::{AuthProvider, AuthenticationError, get_user_from_headers};
|
use crate::auth::{AuthProvider, AuthenticationError, get_user_from_headers};
|
||||||
use crate::database::models::DBUser;
|
use crate::database::models::DBUser;
|
||||||
use crate::database::models::flow_item::DBFlow;
|
use crate::database::models::flow_item::DBFlow;
|
||||||
@ -232,6 +234,7 @@ impl TempUser {
|
|||||||
role: Role::Developer.to_string(),
|
role: Role::Developer.to_string(),
|
||||||
badges: Badges::default(),
|
badges: Badges::default(),
|
||||||
allow_friend_requests: true,
|
allow_friend_requests: true,
|
||||||
|
is_subscribed_to_newsletter: false,
|
||||||
}
|
}
|
||||||
.insert(transaction)
|
.insert(transaction)
|
||||||
.await?;
|
.await?;
|
||||||
@ -1291,37 +1294,6 @@ pub async fn delete_auth_provider(
|
|||||||
Ok(HttpResponse::NoContent().finish())
|
Ok(HttpResponse::NoContent().finish())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn sign_up_sendy(email: &str) -> Result<(), AuthenticationError> {
|
|
||||||
let url = dotenvy::var("SENDY_URL")?;
|
|
||||||
let id = dotenvy::var("SENDY_LIST_ID")?;
|
|
||||||
let api_key = dotenvy::var("SENDY_API_KEY")?;
|
|
||||||
let site_url = dotenvy::var("SITE_URL")?;
|
|
||||||
|
|
||||||
if url.is_empty() || url == "none" {
|
|
||||||
tracing::info!("Sendy URL not set, skipping signup");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut form = HashMap::new();
|
|
||||||
|
|
||||||
form.insert("api_key", &*api_key);
|
|
||||||
form.insert("email", email);
|
|
||||||
form.insert("list", &*id);
|
|
||||||
form.insert("referrer", &*site_url);
|
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
|
||||||
client
|
|
||||||
.post(format!("{url}/subscribe"))
|
|
||||||
.form(&form)
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.error_for_status()?
|
|
||||||
.text()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn check_sendy_subscription(
|
pub async fn check_sendy_subscription(
|
||||||
email: &str,
|
email: &str,
|
||||||
) -> Result<bool, AuthenticationError> {
|
) -> Result<bool, AuthenticationError> {
|
||||||
@ -1456,6 +1428,9 @@ pub async fn create_account_with_password(
|
|||||||
role: Role::Developer.to_string(),
|
role: Role::Developer.to_string(),
|
||||||
badges: Badges::default(),
|
badges: Badges::default(),
|
||||||
allow_friend_requests: true,
|
allow_friend_requests: true,
|
||||||
|
is_subscribed_to_newsletter: new_account
|
||||||
|
.sign_up_newsletter
|
||||||
|
.unwrap_or(false),
|
||||||
}
|
}
|
||||||
.insert(&mut transaction)
|
.insert(&mut transaction)
|
||||||
.await?;
|
.await?;
|
||||||
@ -1476,10 +1451,6 @@ pub async fn create_account_with_password(
|
|||||||
&format!("Welcome to Modrinth, {}!", new_account.username),
|
&format!("Welcome to Modrinth, {}!", new_account.username),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if new_account.sign_up_newsletter.unwrap_or(false) {
|
|
||||||
sign_up_sendy(&new_account.email).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
transaction.commit().await?;
|
transaction.commit().await?;
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(res))
|
Ok(HttpResponse::Ok().json(res))
|
||||||
@ -2420,15 +2391,24 @@ pub async fn subscribe_newsletter(
|
|||||||
.await?
|
.await?
|
||||||
.1;
|
.1;
|
||||||
|
|
||||||
if let Some(email) = user.email {
|
sqlx::query!(
|
||||||
sign_up_sendy(&email).await?;
|
"
|
||||||
|
UPDATE users
|
||||||
|
SET is_subscribed_to_newsletter = TRUE
|
||||||
|
WHERE id = $1
|
||||||
|
",
|
||||||
|
user.id.0 as i64,
|
||||||
|
)
|
||||||
|
.execute(&**pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(HttpResponse::NoContent().finish())
|
crate::database::models::DBUser::clear_caches(
|
||||||
} else {
|
&[(user.id.into(), None)],
|
||||||
Err(ApiError::InvalidInput(
|
&redis,
|
||||||
"User does not have an email.".to_string(),
|
)
|
||||||
))
|
.await?;
|
||||||
}
|
|
||||||
|
Ok(HttpResponse::NoContent().finish())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("email/subscribe")]
|
#[get("email/subscribe")]
|
||||||
@ -2438,7 +2418,7 @@ pub async fn get_newsletter_subscription_status(
|
|||||||
redis: Data<RedisPool>,
|
redis: Data<RedisPool>,
|
||||||
session_queue: Data<AuthQueue>,
|
session_queue: Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(
|
let user = get_full_user_from_headers(
|
||||||
&req,
|
&req,
|
||||||
&**pool,
|
&**pool,
|
||||||
&redis,
|
&redis,
|
||||||
@ -2448,16 +2428,16 @@ pub async fn get_newsletter_subscription_status(
|
|||||||
.await?
|
.await?
|
||||||
.1;
|
.1;
|
||||||
|
|
||||||
if let Some(email) = user.email {
|
let is_subscribed = user.is_subscribed_to_newsletter
|
||||||
let is_subscribed = check_sendy_subscription(&email).await?;
|
|| if let Some(email) = user.email {
|
||||||
Ok(HttpResponse::Ok().json(serde_json::json!({
|
check_sendy_subscription(&email).await?
|
||||||
"subscribed": is_subscribed
|
} else {
|
||||||
})))
|
false
|
||||||
} else {
|
};
|
||||||
Ok(HttpResponse::Ok().json(serde_json::json!({
|
|
||||||
"subscribed": false
|
Ok(HttpResponse::Ok().json(serde_json::json!({
|
||||||
})))
|
"subscribed": is_subscribed
|
||||||
}
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_email_verify(
|
fn send_email_verify(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user