systemprompt_users/repository/user/
merge.rs1use sqlx::{Acquire, Postgres, Transaction};
7use systemprompt_identifiers::UserId;
8
9use crate::error::Result;
10use crate::repository::UserRepository;
11
12#[derive(Debug, Clone, Copy)]
13pub struct MergeResult {
14 pub sessions: u64,
15 pub tasks: u64,
16 pub total_rows: u64,
17}
18
19pub const MERGE_EXCLUDED_SECURITY_TABLES: &[&str] = &[
24 "oauth_auth_codes",
25 "oauth_refresh_tokens",
26 "oauth_clients",
27 "webauthn_credentials",
28 "webauthn_challenges",
29 "webauthn_setup_tokens",
30 "user_api_keys",
31 "user_device_certs",
32 "bridge_sessions",
33 "bridge_exchange_codes",
34 "federated_identities",
35];
36
37impl UserRepository {
38 pub async fn merge_users(&self, source_id: &UserId, target_id: &UserId) -> Result<MergeResult> {
39 let mut conn = self.write_pool.acquire().await?;
40 let mut tx = conn.begin().await?;
41
42 let source = source_id.as_str();
43 let target = target_id.as_str();
44
45 let sessions = transfer_sessions(&mut tx, source, target).await?;
46 let tasks = transfer_tasks(&mut tx, source, target).await?;
47 let mut total_rows = sessions + tasks;
48 total_rows += transfer_audit_rows(&mut tx, source, target).await?;
49 total_rows += transfer_content_rows(&mut tx, source, target).await?;
50
51 sqlx::query!(
52 "UPDATE fingerprint_reputation SET associated_user_ids = \
53 array_replace(associated_user_ids, $2, $1) WHERE $2 = ANY(associated_user_ids)",
54 target,
55 source
56 )
57 .execute(&mut *tx)
58 .await?;
59
60 sqlx::query!(
61 "DELETE FROM ai_quota_buckets WHERE subject_kind = 'user' AND subject_id = $1",
62 source
63 )
64 .execute(&mut *tx)
65 .await?;
66
67 sqlx::query!("DELETE FROM users WHERE id = $1", source)
68 .execute(&mut *tx)
69 .await?;
70
71 tx.commit().await?;
72 Ok(MergeResult {
73 sessions,
74 tasks,
75 total_rows,
76 })
77 }
78}
79
80async fn transfer_sessions(
81 tx: &mut Transaction<'_, Postgres>,
82 source: &str,
83 target: &str,
84) -> Result<u64> {
85 let result = sqlx::query!(
86 "UPDATE user_sessions SET user_id = $1 WHERE user_id = $2",
87 target,
88 source
89 )
90 .execute(&mut **tx)
91 .await?;
92 Ok(result.rows_affected())
93}
94
95async fn transfer_tasks(
96 tx: &mut Transaction<'_, Postgres>,
97 source: &str,
98 target: &str,
99) -> Result<u64> {
100 let result = sqlx::query!(
101 "UPDATE agent_tasks SET user_id = $1 WHERE user_id = $2",
102 target,
103 source
104 )
105 .execute(&mut **tx)
106 .await?;
107 Ok(result.rows_affected())
108}
109
110async fn transfer_audit_rows(
111 tx: &mut Transaction<'_, Postgres>,
112 source: &str,
113 target: &str,
114) -> Result<u64> {
115 let mut moved = 0;
116 moved += sqlx::query!(
117 "UPDATE task_messages SET user_id = $1 WHERE user_id = $2",
118 target,
119 source
120 )
121 .execute(&mut **tx)
122 .await?
123 .rows_affected();
124 moved += sqlx::query!(
125 "UPDATE user_contexts SET user_id = $1 WHERE user_id = $2",
126 target,
127 source
128 )
129 .execute(&mut **tx)
130 .await?
131 .rows_affected();
132 moved += sqlx::query!(
133 "UPDATE mcp_tool_executions SET user_id = $1 WHERE user_id = $2",
134 target,
135 source
136 )
137 .execute(&mut **tx)
138 .await?
139 .rows_affected();
140 moved += sqlx::query!(
141 "UPDATE mcp_artifacts SET user_id = $1 WHERE user_id = $2",
142 target,
143 source
144 )
145 .execute(&mut **tx)
146 .await?
147 .rows_affected();
148 moved += sqlx::query!(
149 "UPDATE mcp_sessions SET user_id = $1 WHERE user_id = $2",
150 target,
151 source
152 )
153 .execute(&mut **tx)
154 .await?
155 .rows_affected();
156 moved += sqlx::query!(
157 "UPDATE governance_decisions SET user_id = $1 WHERE user_id = $2",
158 target,
159 source
160 )
161 .execute(&mut **tx)
162 .await?
163 .rows_affected();
164 moved += sqlx::query!(
165 "UPDATE logs SET user_id = $1 WHERE user_id = $2",
166 target,
167 source
168 )
169 .execute(&mut **tx)
170 .await?
171 .rows_affected();
172 Ok(moved)
173}
174
175async fn transfer_content_rows(
176 tx: &mut Transaction<'_, Postgres>,
177 source: &str,
178 target: &str,
179) -> Result<u64> {
180 let mut moved = 0;
181 moved += sqlx::query!(
182 "UPDATE ai_requests SET user_id = $1 WHERE user_id = $2",
183 target,
184 source
185 )
186 .execute(&mut **tx)
187 .await?
188 .rows_affected();
189 moved += sqlx::query!(
190 "UPDATE engagement_events SET user_id = $1 WHERE user_id = $2",
191 target,
192 source
193 )
194 .execute(&mut **tx)
195 .await?
196 .rows_affected();
197 moved += sqlx::query!(
198 "UPDATE analytics_events SET user_id = $1 WHERE user_id = $2",
199 target,
200 source
201 )
202 .execute(&mut **tx)
203 .await?
204 .rows_affected();
205 moved += sqlx::query!(
206 "UPDATE event_outbox SET user_id = $1 WHERE user_id = $2",
207 target,
208 source
209 )
210 .execute(&mut **tx)
211 .await?
212 .rows_affected();
213 moved += sqlx::query!(
214 "UPDATE files SET user_id = $1 WHERE user_id = $2",
215 target,
216 source
217 )
218 .execute(&mut **tx)
219 .await?
220 .rows_affected();
221 moved += sqlx::query!(
222 "UPDATE link_clicks SET user_id = $1 WHERE user_id = $2",
223 target,
224 source
225 )
226 .execute(&mut **tx)
227 .await?
228 .rows_affected();
229 Ok(moved)
230}