From b2ee5de050ed838ee435af26a83f8a522e78fe1d Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Fri, 21 Jun 2024 17:39:19 +0200 Subject: [PATCH] storage: Add an email filter on the user email list --- crates/storage-pg/src/user/email.rs | 12 +++++++++- crates/storage-pg/src/user/tests.rs | 34 ++++++++++++++++++++++++++++- crates/storage/src/user/email.rs | 16 ++++++++++++++ 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/crates/storage-pg/src/user/email.rs b/crates/storage-pg/src/user/email.rs index 28cd3b66..3cbdf5f6 100644 --- a/crates/storage-pg/src/user/email.rs +++ b/crates/storage-pg/src/user/email.rs @@ -1,4 +1,4 @@ -// Copyright 2022, 2023 The Matrix.org Foundation C.I.C. +// Copyright 2022-2024 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -270,6 +270,11 @@ impl<'c> UserEmailRepository for PgUserEmailRepository<'c> { .and_where_option(filter.user().map(|user| { Expr::col((UserEmails::Table, UserEmails::UserId)).eq(Uuid::from(user.id)) })) + .and_where_option( + filter + .email() + .map(|email| Expr::col((UserEmails::Table, UserEmails::Email)).eq(email)), + ) .and_where_option(filter.state().map(|state| { if state.is_verified() { Expr::col((UserEmails::Table, UserEmails::ConfirmedAt)).is_not_null() @@ -305,6 +310,11 @@ impl<'c> UserEmailRepository for PgUserEmailRepository<'c> { .and_where_option(filter.user().map(|user| { Expr::col((UserEmails::Table, UserEmails::UserId)).eq(Uuid::from(user.id)) })) + .and_where_option( + filter + .email() + .map(|email| Expr::col((UserEmails::Table, UserEmails::Email)).eq(email)), + ) .and_where_option(filter.state().map(|state| { if state.is_verified() { Expr::col((UserEmails::Table, UserEmails::ConfirmedAt)).is_not_null() diff --git a/crates/storage-pg/src/user/tests.rs b/crates/storage-pg/src/user/tests.rs index 631b647a..5d7b0cca 100644 --- a/crates/storage-pg/src/user/tests.rs +++ b/crates/storage-pg/src/user/tests.rs @@ -1,4 +1,4 @@ -// Copyright 2023 The Matrix.org Foundation C.I.C. +// Copyright 2023, 2024 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -321,6 +321,38 @@ async fn test_user_email_repo(pool: PgPool) { assert!(!emails.has_next_page); assert!(emails.edges.is_empty()); + // Listing emails from the email address should work + let emails = repo + .user_email() + .list(all.for_email(EMAIL), Pagination::first(10)) + .await + .unwrap(); + assert!(!emails.has_next_page); + assert_eq!(emails.edges.len(), 1); + assert_eq!(emails.edges[0], user_email); + + // Filtering on another email should not return anything + let emails = repo + .user_email() + .list(all.for_email("hello@example.com"), Pagination::first(10)) + .await + .unwrap(); + assert!(!emails.has_next_page); + assert!(emails.edges.is_empty()); + + // Counting also works with the email filter + assert_eq!( + repo.user_email().count(all.for_email(EMAIL)).await.unwrap(), + 1 + ); + assert_eq!( + repo.user_email() + .count(all.for_email("hello@example.com")) + .await + .unwrap(), + 0 + ); + // Deleting the user email should work repo.user_email().remove(user_email).await.unwrap(); assert_eq!(repo.user_email().count(all).await.unwrap(), 0); diff --git a/crates/storage/src/user/email.rs b/crates/storage/src/user/email.rs index 12628657..731da54b 100644 --- a/crates/storage/src/user/email.rs +++ b/crates/storage/src/user/email.rs @@ -41,6 +41,7 @@ impl UserEmailState { #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] pub struct UserEmailFilter<'a> { user: Option<&'a User>, + email: Option<&'a str>, state: Option, } @@ -58,6 +59,13 @@ impl<'a> UserEmailFilter<'a> { self } + /// Filter for emails matching a specific email address + #[must_use] + pub fn for_email(mut self, email: &'a str) -> Self { + self.email = Some(email); + self + } + /// Get the user filter /// /// Returns [`None`] if no user filter is set @@ -66,6 +74,14 @@ impl<'a> UserEmailFilter<'a> { self.user } + /// Get the email filter + /// + /// Returns [`None`] if no email filter is set + #[must_use] + pub fn email(&self) -> Option<&str> { + self.email + } + /// Filter for emails that are verified #[must_use] pub fn verified_only(mut self) -> Self {