Skip to main content

max / makenotwork

1.7 KB · 62 lines History Blame Raw
1 //! Email signup capture (launch waitlist / newsletter): insert a signup with
2 //! its `source`, and read the full list + count for the admin view.
3
4 use sqlx::PgPool;
5 use uuid::Uuid;
6
7 use crate::error::Result;
8
9 /// Insert a new email signup, ignoring duplicates.
10 /// Returns the signup ID (new or existing).
11 #[tracing::instrument(skip_all)]
12 pub(crate) async fn insert_email_signup(pool: &PgPool, email: &str, source: &str) -> Result<Uuid> {
13 let id = sqlx::query_scalar!(
14 r#"
15 INSERT INTO email_signups (email, source)
16 VALUES ($1, $2)
17 ON CONFLICT (email) DO UPDATE SET email = EXCLUDED.email
18 RETURNING id
19 "#,
20 email,
21 source,
22 )
23 .fetch_one(pool)
24 .await?;
25 Ok(id)
26 }
27
28 /// Row returned by the admin email signups query.
29 pub(crate) struct DbEmailSignup {
30 pub email: String,
31 pub source: String,
32 pub created_at: chrono::DateTime<chrono::Utc>,
33 }
34
35 /// Get email signups ordered by newest first (capped at 500).
36 #[tracing::instrument(skip_all)]
37 pub(crate) async fn get_all_email_signups(pool: &PgPool) -> Result<Vec<DbEmailSignup>> {
38 let rows = sqlx::query_as!(
39 DbEmailSignup,
40 r#"
41 SELECT email, source,
42 created_at as "created_at: chrono::DateTime<chrono::Utc>"
43 FROM email_signups
44 ORDER BY created_at DESC
45 LIMIT 500
46 "#,
47 )
48 .fetch_all(pool)
49 .await?;
50 Ok(rows)
51 }
52
53 /// Count total email signups.
54 #[tracing::instrument(skip_all)]
55 pub(crate) async fn count_email_signups(pool: &PgPool) -> Result<i64> {
56 let count = sqlx::query_scalar!("SELECT COUNT(*) FROM email_signups")
57 .fetch_one(pool)
58 .await?
59 .unwrap_or(0);
60 Ok(count)
61 }
62