| 1 |
|
| 2 |
|
| 3 |
use sqlx::PgPool; |
| 4 |
|
| 5 |
use crate::db::UserId; |
| 6 |
use crate::db::models::DbUser; |
| 7 |
use crate::error::Result; |
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
#[tracing::instrument(skip_all)] |
| 12 |
pub async fn get_onboarding_candidates( |
| 13 |
pool: &PgPool, |
| 14 |
step: i16, |
| 15 |
min_age: chrono::Duration, |
| 16 |
) -> Result<Vec<DbUser>> { |
| 17 |
let cutoff = chrono::Utc::now() - min_age; |
| 18 |
|
| 19 |
|
| 20 |
|
| 21 |
|
| 22 |
|
| 23 |
let users = sqlx::query_as::<_, DbUser>( |
| 24 |
"SELECT * FROM users |
| 25 |
WHERE onboarding_email_step = $1 |
| 26 |
AND (onboarding_email_sent_at IS NULL OR onboarding_email_sent_at < $2) |
| 27 |
AND suspended_at IS NULL |
| 28 |
ORDER BY onboarding_email_sent_at ASC NULLS FIRST |
| 29 |
LIMIT 1000", |
| 30 |
) |
| 31 |
.bind(step) |
| 32 |
.bind(cutoff) |
| 33 |
.fetch_all(pool) |
| 34 |
.await?; |
| 35 |
Ok(users) |
| 36 |
} |
| 37 |
|
| 38 |
|
| 39 |
#[tracing::instrument(skip_all)] |
| 40 |
pub async fn advance_onboarding_step(pool: &PgPool, user_id: UserId, new_step: i16) -> Result<()> { |
| 41 |
sqlx::query( |
| 42 |
"UPDATE users SET onboarding_email_step = $2, onboarding_email_sent_at = NOW() WHERE id = $1", |
| 43 |
) |
| 44 |
.bind(user_id) |
| 45 |
.bind(new_step) |
| 46 |
.execute(pool) |
| 47 |
.await?; |
| 48 |
Ok(()) |
| 49 |
} |
| 50 |
|
| 51 |
|
| 52 |
#[tracing::instrument(skip_all)] |
| 53 |
pub async fn batch_advance_onboarding_step( |
| 54 |
pool: &PgPool, |
| 55 |
user_ids: &[UserId], |
| 56 |
new_step: i16, |
| 57 |
) -> Result<()> { |
| 58 |
sqlx::query( |
| 59 |
"UPDATE users SET onboarding_email_step = $2, onboarding_email_sent_at = NOW() WHERE id = ANY($1)", |
| 60 |
) |
| 61 |
.bind(user_ids) |
| 62 |
.bind(new_step) |
| 63 |
.execute(pool) |
| 64 |
.await?; |
| 65 |
Ok(()) |
| 66 |
} |
| 67 |
|