| 849 |
849 |
|
}
|
| 850 |
850 |
|
|
| 851 |
851 |
|
/// Update a user's email notification preferences.
|
| 852 |
|
- |
/// Email notification toggles for a user, one field per `users` column.
|
|
852 |
+ |
///
|
|
853 |
+ |
/// Writes subscriptions only. The `users.notify_*` columns these used to set
|
|
854 |
+ |
/// are gone (migration 189); `db::lists` is the single record, and the
|
|
855 |
+ |
/// preferences page and the unsubscribe links write the same rows.
|
| 853 |
856 |
|
#[derive(Debug, Clone, Copy)]
|
| 854 |
857 |
|
pub struct NotificationPreferences {
|
| 855 |
858 |
|
pub notify_sale: bool,
|
| 874 |
877 |
|
notify_issues,
|
| 875 |
878 |
|
notify_status,
|
| 876 |
879 |
|
} = prefs;
|
| 877 |
|
- |
sqlx::query(
|
| 878 |
|
- |
r"
|
| 879 |
|
- |
UPDATE users
|
| 880 |
|
- |
SET notify_sale = $2,
|
| 881 |
|
- |
notify_follower = $3,
|
| 882 |
|
- |
notify_release = $4,
|
| 883 |
|
- |
login_notification_enabled = $5,
|
| 884 |
|
- |
notify_issues = $6,
|
| 885 |
|
- |
notify_status = $7,
|
| 886 |
|
- |
updated_at = NOW()
|
| 887 |
|
- |
WHERE id = $1
|
| 888 |
|
- |
",
|
| 889 |
|
- |
)
|
| 890 |
|
- |
.bind(id)
|
| 891 |
|
- |
.bind(notify_sale)
|
| 892 |
|
- |
.bind(notify_follower)
|
| 893 |
|
- |
.bind(notify_release)
|
| 894 |
|
- |
.bind(login_notification_enabled)
|
| 895 |
|
- |
.bind(notify_issues)
|
| 896 |
|
- |
.bind(notify_status)
|
| 897 |
|
- |
.execute(pool)
|
| 898 |
|
- |
.await?;
|
| 899 |
880 |
|
|
| 900 |
|
- |
// Keep the subscription side in step. Reads still use the columns above,
|
| 901 |
|
- |
// so this is the mirror rather than the source, but the preferences page
|
| 902 |
|
- |
// and any consent question are answered from it.
|
| 903 |
881 |
|
for (kind, enabled) in [
|
| 904 |
882 |
|
("sale", notify_sale),
|
| 905 |
883 |
|
("follower", notify_follower),
|
| 914 |
892 |
|
Ok(())
|
| 915 |
893 |
|
}
|
| 916 |
894 |
|
|
| 917 |
|
- |
/// Update a user's tip preferences (tips_enabled toggle and notification).
|
|
895 |
+ |
/// Update tip settings.
|
|
896 |
+ |
///
|
|
897 |
+ |
/// `tips_enabled` is a capability (whether the creator accepts tips at all) and
|
|
898 |
+ |
/// stays a column. `notify_tip` is a notification preference and now lives in
|
|
899 |
+ |
/// subscriptions with the other six, so the two are written to different
|
|
900 |
+ |
/// places despite arriving from the same form.
|
| 918 |
901 |
|
#[tracing::instrument(skip_all)]
|
| 919 |
902 |
|
pub async fn update_tip_preferences(
|
| 920 |
903 |
|
pool: &PgPool,
|
| 922 |
905 |
|
tips_enabled: bool,
|
| 923 |
906 |
|
notify_tip: bool,
|
| 924 |
907 |
|
) -> Result<()> {
|
| 925 |
|
- |
sqlx::query(
|
| 926 |
|
- |
r"
|
| 927 |
|
- |
UPDATE users
|
| 928 |
|
- |
SET tips_enabled = $2,
|
| 929 |
|
- |
notify_tip = $3,
|
| 930 |
|
- |
updated_at = NOW()
|
| 931 |
|
- |
WHERE id = $1
|
| 932 |
|
- |
",
|
| 933 |
|
- |
)
|
| 934 |
|
- |
.bind(id)
|
| 935 |
|
- |
.bind(tips_enabled)
|
| 936 |
|
- |
.bind(notify_tip)
|
| 937 |
|
- |
.execute(pool)
|
| 938 |
|
- |
.await?;
|
|
908 |
+ |
sqlx::query("UPDATE users SET tips_enabled = $2, updated_at = NOW() WHERE id = $1")
|
|
909 |
+ |
.bind(id)
|
|
910 |
+ |
.bind(tips_enabled)
|
|
911 |
+ |
.execute(pool)
|
|
912 |
+ |
.await?;
|
| 939 |
913 |
|
|
|
914 |
+ |
crate::db::lists::sync_notification_subscription(pool, id, "tip", notify_tip).await?;
|
| 940 |
915 |
|
Ok(())
|
| 941 |
916 |
|
}
|
| 942 |
917 |
|
|
| 943 |
|
- |
/// Disable a single notification preference by column name.
|
|
918 |
+ |
/// Turn one notification off, by the legacy preference name the unsubscribe
|
|
919 |
+ |
/// links carry.
|
| 944 |
920 |
|
///
|
| 945 |
|
- |
/// Used by the email unsubscribe handler. Only accepts known column names
|
| 946 |
|
- |
/// to prevent SQL injection.
|
|
921 |
+ |
/// The names are the old column names because they are baked into signed URLs
|
|
922 |
+ |
/// already sitting in inboxes. They map to list kinds here rather than being
|
|
923 |
+ |
/// renamed, which would invalidate every link ever sent.
|
| 947 |
924 |
|
#[tracing::instrument(skip_all)]
|
| 948 |
925 |
|
pub async fn disable_notification(
|
| 949 |
926 |
|
pool: &PgPool,
|
| 950 |
927 |
|
user_id: UserId,
|
| 951 |
928 |
|
preference: &str,
|
| 952 |
929 |
|
) -> Result<bool> {
|
| 953 |
|
- |
let sql = match preference {
|
| 954 |
|
- |
"notify_sale" => "UPDATE users SET notify_sale = false, updated_at = NOW() WHERE id = $1",
|
| 955 |
|
- |
"notify_follower" => {
|
| 956 |
|
- |
"UPDATE users SET notify_follower = false, updated_at = NOW() WHERE id = $1"
|
| 957 |
|
- |
}
|
| 958 |
|
- |
"notify_release" => {
|
| 959 |
|
- |
"UPDATE users SET notify_release = false, updated_at = NOW() WHERE id = $1"
|
| 960 |
|
- |
}
|
| 961 |
|
- |
"login_notification_enabled" => {
|
| 962 |
|
- |
"UPDATE users SET login_notification_enabled = false, updated_at = NOW() WHERE id = $1"
|
| 963 |
|
- |
}
|
| 964 |
|
- |
"notify_issues" => {
|
| 965 |
|
- |
"UPDATE users SET notify_issues = false, updated_at = NOW() WHERE id = $1"
|
| 966 |
|
- |
}
|
| 967 |
|
- |
"notify_tip" => "UPDATE users SET notify_tip = false, updated_at = NOW() WHERE id = $1",
|
| 968 |
|
- |
"notify_status" => {
|
| 969 |
|
- |
"UPDATE users SET notify_status = false, updated_at = NOW() WHERE id = $1"
|
| 970 |
|
- |
}
|
| 971 |
|
- |
_ => return Ok(false),
|
| 972 |
|
- |
};
|
| 973 |
|
- |
let result = sqlx::query(sql).bind(user_id).execute(pool).await?;
|
| 974 |
|
- |
|
| 975 |
|
- |
// Same mirror, for the single-preference path the email links use.
|
| 976 |
|
- |
if let Some(kind) = crate::db::lists::NOTIFICATION_LISTS
|
|
930 |
+ |
let Some(kind) = crate::db::lists::NOTIFICATION_LISTS
|
| 977 |
931 |
|
.iter()
|
| 978 |
|
- |
.find(|(_, col)| *col == preference)
|
|
932 |
+ |
.find(|(_, legacy)| *legacy == preference)
|
| 979 |
933 |
|
.map(|(kind, _)| *kind)
|
| 980 |
|
- |
{
|
| 981 |
|
- |
crate::db::lists::sync_notification_subscription(pool, user_id, kind, false).await?;
|
| 982 |
|
- |
}
|
| 983 |
|
- |
|
| 984 |
|
- |
Ok(result.rows_affected() > 0)
|
| 985 |
|
- |
}
|
| 986 |
|
- |
|
| 987 |
|
- |
/// Set one notification column, either way.
|
| 988 |
|
- |
///
|
| 989 |
|
- |
/// The write-back half of the preferences page: reads still use these columns,
|
| 990 |
|
- |
/// so a subscription change has to land here to have any effect.
|
| 991 |
|
- |
///
|
| 992 |
|
- |
/// Deliberately does NOT mirror into subscriptions, unlike
|
| 993 |
|
- |
/// [`disable_notification`]. This function is called *by* the subscription
|
| 994 |
|
- |
/// side, and mirroring back would loop and append a duplicate consent event
|
| 995 |
|
- |
/// for a change already recorded.
|
| 996 |
|
- |
///
|
| 997 |
|
- |
/// `column` comes from `lists::NOTIFICATION_LISTS`, never from a request, and
|
| 998 |
|
- |
/// is matched against a fixed set anyway so it cannot reach the query as text.
|
| 999 |
|
- |
#[tracing::instrument(skip_all)]
|
| 1000 |
|
- |
pub async fn set_notification(
|
| 1001 |
|
- |
pool: &PgPool,
|
| 1002 |
|
- |
user_id: UserId,
|
| 1003 |
|
- |
column: &str,
|
| 1004 |
|
- |
enabled: bool,
|
| 1005 |
|
- |
) -> Result<bool> {
|
| 1006 |
|
- |
let sql = match column {
|
| 1007 |
|
- |
"notify_sale" => "UPDATE users SET notify_sale = $2, updated_at = NOW() WHERE id = $1",
|
| 1008 |
|
- |
"notify_follower" => {
|
| 1009 |
|
- |
"UPDATE users SET notify_follower = $2, updated_at = NOW() WHERE id = $1"
|
| 1010 |
|
- |
}
|
| 1011 |
|
- |
"notify_release" => {
|
| 1012 |
|
- |
"UPDATE users SET notify_release = $2, updated_at = NOW() WHERE id = $1"
|
| 1013 |
|
- |
}
|
| 1014 |
|
- |
"login_notification_enabled" => {
|
| 1015 |
|
- |
"UPDATE users SET login_notification_enabled = $2, updated_at = NOW() WHERE id = $1"
|
| 1016 |
|
- |
}
|
| 1017 |
|
- |
"notify_issues" => "UPDATE users SET notify_issues = $2, updated_at = NOW() WHERE id = $1",
|
| 1018 |
|
- |
"notify_tip" => "UPDATE users SET notify_tip = $2, updated_at = NOW() WHERE id = $1",
|
| 1019 |
|
- |
"notify_status" => "UPDATE users SET notify_status = $2, updated_at = NOW() WHERE id = $1",
|
| 1020 |
|
- |
_ => return Ok(false),
|
|
934 |
+ |
else {
|
|
935 |
+ |
return Ok(false);
|
| 1021 |
936 |
|
};
|
| 1022 |
|
- |
let result = sqlx::query(sql)
|
| 1023 |
|
- |
.bind(user_id)
|
| 1024 |
|
- |
.bind(enabled)
|
| 1025 |
|
- |
.execute(pool)
|
| 1026 |
|
- |
.await?;
|
| 1027 |
|
- |
Ok(result.rows_affected() > 0)
|
|
937 |
+ |
crate::db::lists::sync_notification_subscription(pool, user_id, kind, false).await?;
|
|
938 |
+ |
Ok(true)
|
| 1028 |
939 |
|
}
|
| 1029 |
940 |
|
|
| 1030 |
941 |
|
/// A user who opted into platform status notifications.
|