@name UsageEventEntry ExportUserData gathers everything a user owns into a single struct for the right-of-access flow. Reads run inside a REPEATABLE READ transaction so the snapshot is internally consistent even if writes arrive while the export is being assembled.
(ctx context.Context, userID string)
| 112 | // transaction so the snapshot is internally consistent even if writes |
| 113 | // arrive while the export is being assembled. |
| 114 | func (s *Store) ExportUserData(ctx context.Context, userID string) (*UserExport, error) { |
| 115 | tx, err := s.pool.BeginTx(ctx, pgx.TxOptions{IsoLevel: pgx.RepeatableRead, AccessMode: pgx.ReadOnly}) |
| 116 | if err != nil { |
| 117 | return nil, fmt.Errorf("export: begin tx: %w", err) |
| 118 | } |
| 119 | defer tx.Rollback(ctx) |
| 120 | |
| 121 | // Profile |
| 122 | var u UserExportUser |
| 123 | err = tx.QueryRow(ctx, |
| 124 | `SELECT id, email, name, created_at FROM users WHERE id = $1`, userID, |
| 125 | ).Scan(&u.ID, &u.Email, &u.Name, &u.CreatedAt) |
| 126 | if err != nil { |
| 127 | return nil, fmt.Errorf("export: load user: %w", err) |
| 128 | } |
| 129 | |
| 130 | // Domains |
| 131 | domains, err := scanDomainsForUser(ctx, tx, userID) |
| 132 | if err != nil { |
| 133 | return nil, fmt.Errorf("export: load domains: %w", err) |
| 134 | } |
| 135 | |
| 136 | // Agents |
| 137 | agents, err := scanAgentsForUser(ctx, tx, userID) |
| 138 | if err != nil { |
| 139 | return nil, fmt.Errorf("export: load agents: %w", err) |
| 140 | } |
| 141 | |
| 142 | // API keys (metadata only) |
| 143 | keys, err := scanAPIKeysForUser(ctx, tx, userID) |
| 144 | if err != nil { |
| 145 | return nil, fmt.Errorf("export: load api keys: %w", err) |
| 146 | } |
| 147 | |
| 148 | // Messages — across all the user's agents in one query so the order |
| 149 | // is consistent and pagination doesn't matter for the export. |
| 150 | messages, err := scanMessagesForUser(ctx, tx, userID) |
| 151 | if err != nil { |
| 152 | return nil, fmt.Errorf("export: load messages: %w", err) |
| 153 | } |
| 154 | |
| 155 | // Suppressions (recipient addresses the account suppressed) |
| 156 | suppressions, err := scanSuppressionsForUser(ctx, tx, userID) |
| 157 | if err != nil { |
| 158 | return nil, fmt.Errorf("export: load suppressions: %w", err) |
| 159 | } |
| 160 | |
| 161 | // Protection events (the screening audit log across the user's agents) |
| 162 | protectionEvents, err := scanProtectionEventsForUser(ctx, tx, userID) |
| 163 | if err != nil { |
| 164 | return nil, fmt.Errorf("export: load protection events: %w", err) |
| 165 | } |
| 166 | |
| 167 | // Usage events (only present if E2A_USAGE_TRACKING is on) |
| 168 | events, err := scanUsageEventsForUser(ctx, tx, userID) |
| 169 | if err != nil { |
| 170 | return nil, fmt.Errorf("export: load usage events: %w", err) |
| 171 | } |