Graphical PDS migrator for AT Protocol

more descriptive

Changed files
+42 -9
islands
routes
api
migrate
+14
islands/MigrationProgress.tsx
··· 225 225 throw new Error(jsonData.message || "Data migration failed"); 226 226 } 227 227 console.log("Data migration: Success response:", jsonData); 228 + 229 + // Display migration logs 230 + if (jsonData.logs && Array.isArray(jsonData.logs)) { 231 + console.log("Data migration: Logs:", jsonData.logs); 232 + jsonData.logs.forEach((log: string) => { 233 + if (log.includes("Successfully migrated blob:")) { 234 + console.log("Data migration: Blob success:", log); 235 + } else if (log.includes("Failed to migrate blob")) { 236 + console.error("Data migration: Blob failure:", log); 237 + } else { 238 + console.log("Data migration:", log); 239 + } 240 + }); 241 + } 228 242 } catch (e) { 229 243 console.error("Data migration: Failed to parse response:", e); 230 244 throw new Error("Invalid response from server during data migration");
+28 -9
routes/api/migrate/data.ts
··· 178 178 let blobCursor: string | undefined = undefined; 179 179 const migratedBlobs: string[] = []; 180 180 const failedBlobs: Array<{ cid: string; error: string }> = []; 181 + const migrationLogs: string[] = []; 181 182 182 183 do { 183 184 try { ··· 189 190 { 190 191 maxRetries: 5, 191 192 onRetry: (attempt, error) => { 192 - console.log(`Retrying blob list fetch (attempt ${attempt}):`, error.message); 193 + const log = `Retrying blob list fetch (attempt ${attempt}): ${error.message}`; 194 + console.log(log); 195 + migrationLogs.push(log); 193 196 }, 194 197 } 195 198 ); ··· 204 207 { 205 208 maxRetries: 5, 206 209 onRetry: (attempt, error) => { 207 - console.log(`Retrying blob download for ${cid} (attempt ${attempt}):`, error.message); 210 + const log = `Retrying blob download for ${cid} (attempt ${attempt}): ${error.message}`; 211 + console.log(log); 212 + migrationLogs.push(log); 208 213 }, 209 214 } 210 215 ); 211 216 212 217 await handleBlobUpload(newAgent, blobRes, cid); 213 218 migratedBlobs.push(cid); 214 - console.log(`Successfully migrated blob: ${cid}`); 219 + const log = `Successfully migrated blob: ${cid}`; 220 + console.log(log); 221 + migrationLogs.push(log); 215 222 } catch (error) { 223 + const errorMsg = error instanceof Error ? error.message : String(error); 216 224 console.error(`Failed to migrate blob ${cid}:`, error); 217 225 failedBlobs.push({ 218 226 cid, 219 - error: error instanceof Error ? error.message : String(error), 227 + error: errorMsg, 220 228 }); 229 + migrationLogs.push(`Failed to migrate blob ${cid}: ${errorMsg}`); 221 230 } 222 231 } 223 232 blobCursor = listedBlobs.data.cursor; 224 233 } catch (error) { 234 + const errorMsg = error instanceof Error ? error.message : String(error); 225 235 console.error("Error during blob migration batch:", error); 226 - // If we hit a critical error during blob listing, break the loop 236 + migrationLogs.push(`Error during blob migration batch: ${errorMsg}`); 227 237 if (error instanceof Error && 228 238 (error.message.includes("Unauthorized") || 229 239 error.message.includes("Invalid token"))) { ··· 239 249 { 240 250 maxRetries: 3, 241 251 onRetry: (attempt, error) => { 242 - console.log(`Retrying preferences fetch (attempt ${attempt}):`, error.message); 252 + const log = `Retrying preferences fetch (attempt ${attempt}): ${error.message}`; 253 + console.log(log); 254 + migrationLogs.push(log); 243 255 }, 244 256 } 245 257 ); ··· 249 261 { 250 262 maxRetries: 3, 251 263 onRetry: (attempt, error) => { 252 - console.log(`Retrying preferences update (attempt ${attempt}):`, error.message); 264 + const log = `Retrying preferences update (attempt ${attempt}): ${error.message}`; 265 + console.log(log); 266 + migrationLogs.push(log); 253 267 }, 254 268 } 255 269 ); 256 270 271 + const completionMessage = `Data migration completed: ${migratedBlobs.length} blobs migrated${failedBlobs.length > 0 ? `, ${failedBlobs.length} failed` : ''}`; 272 + console.log(completionMessage); 273 + migrationLogs.push(completionMessage); 274 + 257 275 return new Response( 258 276 JSON.stringify({ 259 277 success: true, ··· 264 282 failedBlobs, 265 283 totalMigrated: migratedBlobs.length, 266 284 totalFailed: failedBlobs.length, 285 + logs: migrationLogs, 267 286 }), 268 287 { 269 - status: failedBlobs.length > 0 ? 207 : 200, // Use 207 Multi-Status if some blobs failed 288 + status: failedBlobs.length > 0 ? 207 : 200, 270 289 headers: { 271 290 "Content-Type": "application/json", 272 - ...Object.fromEntries(res.headers), // Include session cookie headers 291 + ...Object.fromEntries(res.headers), 273 292 }, 274 293 }, 275 294 );