Skip to main content

max / makenotwork

2.1 KB · 67 lines History Blame Raw
1 //! Walking a new creator through the first-run steps.
2
3 use sqlx::PgPool;
4
5 use crate::db::UserId;
6 use crate::db::models::DbUser;
7 use crate::error::Result;
8
9 /// Users who need the next onboarding email. Returns users at a given step
10 /// whose last email was sent more than `min_age` ago (or never).
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 // Per-tick LIMIT bounds the scheduler's input list. The caller advances
19 // each returned user's step (so the WHERE re-excludes them), meaning the
20 // remainder is drained on the next tick, same re-tick pattern as the
21 // sandbox/terminated/content-removal cleanup queries. Run #14 MEDIUM: a
22 // signup surge must not load an unbounded user vec into the lock-held tick.
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 /// Advance a user's onboarding email step and record the send time.
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 /// Advance onboarding step for multiple users in a single query.
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