Graphical PDS migrator for AT Protocol
1import { checkDidsMatch } from "../../../lib/check-dids.ts";
2import { getSessionAgent } from "../../../lib/sessions.ts";
3import { define } from "../../../utils.ts";
4
5export const handler = define.handlers({
6 async GET(ctx) {
7 console.log("Status check: Starting");
8 const url = new URL(ctx.req.url);
9 const params = new URLSearchParams(url.search);
10 const step = params.get("step");
11 console.log("Status check: Step", step);
12
13 console.log("Status check: Getting agents");
14 const oldAgent = await getSessionAgent(ctx.req);
15 const newAgent = await getSessionAgent(ctx.req, new Response(), true);
16
17 if (!oldAgent || !newAgent) {
18 console.log("Status check: Unauthorized - missing agents", {
19 hasOldAgent: !!oldAgent,
20 hasNewAgent: !!newAgent,
21 });
22 return new Response("Unauthorized", { status: 401 });
23 }
24
25 const didsMatch = await checkDidsMatch(ctx.req);
26
27 console.log("Status check: Fetching account statuses");
28 const oldStatus = await oldAgent.com.atproto.server.checkAccountStatus();
29 const newStatus = await newAgent.com.atproto.server.checkAccountStatus();
30
31 if (!oldStatus.data || !newStatus.data) {
32 console.error("Status check: Failed to verify status", {
33 hasOldStatus: !!oldStatus.data,
34 hasNewStatus: !!newStatus.data,
35 });
36 return new Response("Could not verify status", { status: 500 });
37 }
38
39 console.log("Status check: Account statuses", {
40 old: oldStatus.data,
41 new: newStatus.data,
42 });
43
44 const readyToContinue = () => {
45 if (!didsMatch) {
46 return {
47 ready: false,
48 reason: "Invalid state, original and target DIDs do not match",
49 };
50 }
51 if (step) {
52 console.log("Status check: Evaluating step", step);
53 switch (step) {
54 case "1": {
55 if (newStatus.data) {
56 console.log("Status check: Step 1 ready");
57 return { ready: true };
58 }
59 console.log(
60 "Status check: Step 1 not ready - new account status not available",
61 );
62 return { ready: false, reason: "New account status not available" };
63 }
64 case "2": {
65 const isReady = newStatus.data.repoCommit &&
66 newStatus.data.indexedRecords === oldStatus.data.indexedRecords &&
67 newStatus.data.privateStateValues ===
68 oldStatus.data.privateStateValues &&
69 newStatus.data.expectedBlobs === newStatus.data.importedBlobs &&
70 newStatus.data.importedBlobs === oldStatus.data.importedBlobs;
71
72 if (isReady) {
73 console.log("Status check: Step 2 ready");
74 return { ready: true };
75 }
76
77 const reasons = [];
78 if (!newStatus.data.repoCommit) {
79 reasons.push("Repository not imported.");
80 }
81 if (newStatus.data.indexedRecords < oldStatus.data.indexedRecords) {
82 reasons.push("Not all records imported.");
83 }
84 if (
85 newStatus.data.privateStateValues <
86 oldStatus.data.privateStateValues
87 ) {
88 reasons.push("Not all private state values imported.");
89 }
90 if (newStatus.data.expectedBlobs !== newStatus.data.importedBlobs) {
91 reasons.push("Expected blobs not fully imported.");
92 }
93 if (newStatus.data.importedBlobs < oldStatus.data.importedBlobs) {
94 reasons.push("Not all blobs imported.");
95 }
96
97 console.log("Status check: Step 2 not ready", { reasons });
98 return { ready: false, reason: reasons.join(", ") };
99 }
100 case "3": {
101 if (newStatus.data.validDid) {
102 console.log("Status check: Step 3 ready");
103 return { ready: true };
104 }
105 console.log("Status check: Step 3 not ready - DID not valid");
106 return { ready: false, reason: "DID not valid" };
107 }
108 case "4": {
109 if (
110 newStatus.data.activated === true &&
111 oldStatus.data.activated === false
112 ) {
113 console.log("Status check: Step 4 ready");
114 return { ready: true };
115 }
116 console.log(
117 "Status check: Step 4 not ready - Account not activated",
118 );
119 return { ready: false, reason: "Account not activated" };
120 }
121 }
122 } else {
123 console.log("Status check: No step specified, returning ready");
124 return { ready: true };
125 }
126 };
127
128 const status = {
129 activated: newStatus.data.activated,
130 validDid: newStatus.data.validDid,
131 repoCommit: newStatus.data.repoCommit,
132 repoRev: newStatus.data.repoRev,
133 repoBlocks: newStatus.data.repoBlocks,
134 expectedRecords: oldStatus.data.indexedRecords,
135 indexedRecords: newStatus.data.indexedRecords,
136 privateStateValues: newStatus.data.privateStateValues,
137 expectedBlobs: newStatus.data.expectedBlobs,
138 importedBlobs: newStatus.data.importedBlobs,
139 ...readyToContinue(),
140 };
141
142 console.log("Status check: Complete", status);
143 return Response.json(status);
144 },
145});