Skip to main content

max / makenotwork

2.3 KB · 91 lines History Blame Raw
1 use sqlx::PgPool;
2
3 use crate::db::enums::SyncPlatform;
4 use crate::db::models::*;
5 use crate::db::{SyncAppId, SyncDeviceId, UserId};
6 use crate::error::Result;
7
8 // ── Sync Devices ──
9
10 /// Register or update a sync device.
11 ///
12 /// Upserts on the `(app_id, user_id, device_name)` unique constraint.
13 /// On conflict (same device re-registering), updates the platform string
14 /// and bumps `last_seen_at` so stale-device detection stays accurate.
15 #[tracing::instrument(skip_all)]
16 pub async fn upsert_sync_device(
17 pool: &PgPool,
18 app_id: SyncAppId,
19 user_id: UserId,
20 device_name: &str,
21 platform: SyncPlatform,
22 ) -> Result<DbSyncDevice> {
23 let device = sqlx::query_as::<_, DbSyncDevice>(
24 r#"
25 INSERT INTO sync_devices (app_id, user_id, device_name, platform)
26 VALUES ($1, $2, $3, $4)
27 ON CONFLICT (app_id, user_id, device_name)
28 DO UPDATE SET platform = EXCLUDED.platform, last_seen_at = NOW()
29 RETURNING *
30 "#,
31 )
32 .bind(app_id)
33 .bind(user_id)
34 .bind(device_name)
35 .bind(platform)
36 .fetch_one(pool)
37 .await?;
38
39 Ok(device)
40 }
41
42 /// List all devices for a user within an app.
43 #[tracing::instrument(skip_all)]
44 pub async fn get_sync_devices(
45 pool: &PgPool,
46 app_id: SyncAppId,
47 user_id: UserId,
48 ) -> Result<Vec<DbSyncDevice>> {
49 let devices = sqlx::query_as::<_, DbSyncDevice>(
50 "SELECT * FROM sync_devices WHERE app_id = $1 AND user_id = $2 ORDER BY last_seen_at DESC LIMIT 100",
51 )
52 .bind(app_id)
53 .bind(user_id)
54 .fetch_all(pool)
55 .await?;
56
57 Ok(devices)
58 }
59
60 /// Delete a device by ID (only if owned by user within app).
61 #[tracing::instrument(skip_all)]
62 pub async fn delete_sync_device(
63 pool: &PgPool,
64 device_id: SyncDeviceId,
65 app_id: SyncAppId,
66 user_id: UserId,
67 ) -> Result<bool> {
68 let result = sqlx::query(
69 "DELETE FROM sync_devices WHERE id = $1 AND app_id = $2 AND user_id = $3",
70 )
71 .bind(device_id)
72 .bind(app_id)
73 .bind(user_id)
74 .execute(pool)
75 .await?;
76
77 Ok(result.rows_affected() > 0)
78 }
79
80 /// Touch last_seen_at for a device.
81 #[tracing::instrument(skip_all)]
82 pub async fn touch_sync_device(pool: &PgPool, device_id: SyncDeviceId) -> Result<()> {
83 sqlx::query("UPDATE sync_devices SET last_seen_at = NOW() WHERE id = $1")
84 .bind(device_id)
85 .execute(pool)
86 .await?;
87
88 Ok(())
89 }
90
91