|
1 | 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ |
2 | 2 | import type { FastifyInstance, FastifyRequest } from 'fastify'; |
3 | | -import { and, eq, ne } from 'drizzle-orm'; |
| 3 | +import { and, eq, ne, or } from 'drizzle-orm'; |
4 | 4 | import { getDb, getSchema } from '../../db/index'; |
5 | 5 | import { McpInstallationService } from '../../services/mcpInstallationService'; |
6 | 6 | import { SatelliteCommandService } from '../../services/satelliteCommandService'; |
@@ -360,15 +360,120 @@ export default async function deleteMyAccountRoute(server: FastifyInstance) { |
360 | 360 | // Don't fail account deletion if event emission fails |
361 | 361 | } |
362 | 362 |
|
363 | | - // STEP 7: Delete satellite commands targeting this team |
| 363 | + // STEP 6.5: Invalidate satellite token caches |
364 | 364 | server.log.debug({ |
365 | | - operation: 'account_deletion_delete_satellite_commands', |
| 365 | + operation: 'account_deletion_invalidate_satellite_cache', |
| 366 | + userId, |
| 367 | + userEmail: user.email |
| 368 | + }, 'Sending cache invalidation commands to satellites'); |
| 369 | + |
| 370 | + try { |
| 371 | + const satelliteCommandService = new SatelliteCommandService(db, server.log); |
| 372 | + const commands = await satelliteCommandService.notifyUserDeletion(userId, user.email); |
| 373 | + |
| 374 | + server.log.info({ |
| 375 | + operation: 'account_deletion_cache_invalidation_sent', |
| 376 | + userId, |
| 377 | + userEmail: user.email, |
| 378 | + satelliteCommandsCreated: commands.length |
| 379 | + }, `Cache invalidation sent to ${commands.length} satellites`); |
| 380 | + } catch (cacheError) { |
| 381 | + server.log.error(cacheError, 'Failed to send cache invalidation - continuing deletion'); |
| 382 | + // Non-fatal: caches expire naturally within 5 minutes |
| 383 | + } |
| 384 | + |
| 385 | + // STEP 7: Handle satellite commands - preserve pending commands |
| 386 | + server.log.debug({ |
| 387 | + operation: 'account_deletion_handle_satellite_commands', |
| 388 | + userId, |
| 389 | + teamId |
| 390 | + }, 'Handling satellite commands'); |
| 391 | + |
| 392 | + // Set target_team_id = NULL for PENDING commands for this team (allows team deletion without blocking satellite execution) |
| 393 | + await db |
| 394 | + .update(schema.satelliteCommands) |
| 395 | + .set({ target_team_id: null }) |
| 396 | + .where( |
| 397 | + and( |
| 398 | + eq(schema.satelliteCommands.target_team_id, teamId), |
| 399 | + eq(schema.satelliteCommands.status, 'pending') |
| 400 | + ) |
| 401 | + ); |
| 402 | + |
| 403 | + // Delete non-pending satellite commands for this team (completed/failed/executing) since they're no longer needed |
| 404 | + await db |
| 405 | + .delete(schema.satelliteCommands) |
| 406 | + .where( |
| 407 | + and( |
| 408 | + eq(schema.satelliteCommands.target_team_id, teamId), |
| 409 | + or( |
| 410 | + eq(schema.satelliteCommands.status, 'completed'), |
| 411 | + eq(schema.satelliteCommands.status, 'failed'), |
| 412 | + eq(schema.satelliteCommands.status, 'acknowledged'), |
| 413 | + eq(schema.satelliteCommands.status, 'executing') |
| 414 | + ) |
| 415 | + ) |
| 416 | + ); |
| 417 | + |
| 418 | + // Handle ALL commands created by this user (for any team) - set created_by = NULL for pending, delete completed |
| 419 | + await db |
| 420 | + .update(schema.satelliteCommands) |
| 421 | + .set({ created_by: null }) |
| 422 | + .where( |
| 423 | + and( |
| 424 | + eq(schema.satelliteCommands.created_by, userId), |
| 425 | + eq(schema.satelliteCommands.status, 'pending') |
| 426 | + ) |
| 427 | + ); |
| 428 | + |
| 429 | + await db |
| 430 | + .delete(schema.satelliteCommands) |
| 431 | + .where( |
| 432 | + and( |
| 433 | + eq(schema.satelliteCommands.created_by, userId), |
| 434 | + or( |
| 435 | + eq(schema.satelliteCommands.status, 'completed'), |
| 436 | + eq(schema.satelliteCommands.status, 'failed'), |
| 437 | + eq(schema.satelliteCommands.status, 'acknowledged'), |
| 438 | + eq(schema.satelliteCommands.status, 'executing') |
| 439 | + ) |
| 440 | + ) |
| 441 | + ); |
| 442 | + |
| 443 | + server.log.info({ |
| 444 | + operation: 'account_deletion_satellite_commands_handled', |
366 | 445 | userId, |
367 | 446 | teamId |
368 | | - }, 'Deleting satellite commands for team'); |
| 447 | + }, 'Satellite commands handled - pending commands preserved'); |
369 | 448 |
|
370 | | - await db.delete(schema.satelliteCommands) |
371 | | - .where(eq(schema.satelliteCommands.target_team_id, teamId)); |
| 449 | + // STEP 7.5: Delete satellite-related records |
| 450 | + server.log.debug({ |
| 451 | + operation: 'account_deletion_delete_satellite_data', |
| 452 | + userId, |
| 453 | + teamId |
| 454 | + }, 'Deleting satellite-related data'); |
| 455 | + |
| 456 | + // Delete satellite processes for user's default team |
| 457 | + await db.delete(schema.satelliteProcesses) |
| 458 | + .where(eq(schema.satelliteProcesses.team_id, teamId)); |
| 459 | + |
| 460 | + // Delete satellite usage logs for user's default team |
| 461 | + await db.delete(schema.satelliteUsageLogs) |
| 462 | + .where(eq(schema.satelliteUsageLogs.team_id, teamId)); |
| 463 | + |
| 464 | + // Delete MCP client activity records for the user |
| 465 | + await db.delete(schema.mcpClientActivity) |
| 466 | + .where(eq(schema.mcpClientActivity.user_id, userId)); |
| 467 | + |
| 468 | + // Delete MCP client activity metrics for the user |
| 469 | + await db.delete(schema.mcpClientActivityMetrics) |
| 470 | + .where(eq(schema.mcpClientActivityMetrics.user_id, userId)); |
| 471 | + |
| 472 | + server.log.info({ |
| 473 | + operation: 'account_deletion_satellite_data_deleted', |
| 474 | + userId, |
| 475 | + teamId |
| 476 | + }, 'Satellite-related data deleted'); |
372 | 477 |
|
373 | 478 | // STEP 8: Delete the default team |
374 | 479 | server.log.debug({ |
|
0 commit comments