* Create or get a thread by channel + external_id * Uses UPSERT to handle concurrent requests safely
(input: CreateThreadInput)
| 296 | * Uses UPSERT to handle concurrent requests safely |
| 297 | */ |
| 298 | async getOrCreateThread(input: CreateThreadInput): Promise<Thread> { |
| 299 | const result = await query<Thread>( |
| 300 | `INSERT INTO addie_threads ( |
| 301 | channel, external_id, user_type, user_id, user_display_name, |
| 302 | context, title, impersonator_user_id, impersonation_reason |
| 303 | ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) |
| 304 | ON CONFLICT (channel, external_id) DO UPDATE SET |
| 305 | user_display_name = COALESCE(EXCLUDED.user_display_name, addie_threads.user_display_name), |
| 306 | context = COALESCE(EXCLUDED.context, addie_threads.context), |
| 307 | updated_at = NOW() |
| 308 | RETURNING *`, |
| 309 | [ |
| 310 | input.channel, |
| 311 | input.external_id, |
| 312 | input.user_type || 'anonymous', |
| 313 | input.user_id || null, |
| 314 | input.user_display_name || null, |
| 315 | JSON.stringify(input.context || {}), |
| 316 | input.title || null, |
| 317 | input.impersonator_user_id || null, |
| 318 | input.impersonation_reason || null, |
| 319 | ] |
| 320 | ); |
| 321 | return result.rows[0]; |
| 322 | } |
| 323 | |
| 324 | /** |
| 325 | * Get a thread by ID |
no outgoing calls
no test coverage detected