tangled
alpha
login
or
join now
leaflet.pub
/
leaflet
289
fork
atom
a tool for shared writing and social publishing
289
fork
atom
overview
issues
27
pulls
pipelines
handle migrating users
awarm.space
1 month ago
6bae920a
265dc693
+46
-4
4 changed files
expand all
collapse all
unified
split
app
api
inngest
functions
migrate_user_to_standard.ts
oauth
[route]
route.ts
drizzle
schema.ts
supabase
database.types.ts
+31
-4
app/api/inngest/functions/migrate_user_to_standard.ts
···
44
44
};
45
45
46
46
// Step 1: Verify OAuth session is valid
47
47
-
await step.run("verify-oauth-session", async () => {
47
47
+
const oauthValid = await step.run("verify-oauth-session", async () => {
48
48
const result = await restoreOAuthSession(did);
49
49
if (!result.ok) {
50
50
-
throw new Error(
51
51
-
`Failed to restore OAuth session: ${result.error.message}`,
52
52
-
);
50
50
+
// Mark identity as needing migration so we can retry later
51
51
+
await supabaseServerClient
52
52
+
.from("identities")
53
53
+
.update({
54
54
+
metadata: { needsStandardSiteMigration: true },
55
55
+
})
56
56
+
.eq("atp_did", did);
57
57
+
58
58
+
return { success: false, error: result.error.message };
53
59
}
54
60
return { success: true };
55
61
});
62
62
+
63
63
+
if (!oauthValid.success) {
64
64
+
return {
65
65
+
success: false,
66
66
+
error: `Failed to restore OAuth session`,
67
67
+
stats,
68
68
+
publicationUriMap: {},
69
69
+
documentUriMap: {},
70
70
+
userSubscriptionUriMap: {},
71
71
+
};
72
72
+
}
56
73
57
74
// Step 2: Get user's pub.leaflet.publication records
58
75
const oldPublications = await step.run(
···
471
488
// 2. External references (e.g., from other AT Proto apps) to old URIs continue to work
472
489
// 3. The normalization layer handles both schemas transparently for reads
473
490
// Old records are also kept on the user's PDS so existing AT-URI references remain valid.
491
491
+
492
492
+
// Clear the migration flag on success
493
493
+
if (stats.errors.length === 0) {
494
494
+
await step.run("clear-migration-flag", async () => {
495
495
+
await supabaseServerClient
496
496
+
.from("identities")
497
497
+
.update({ metadata: null })
498
498
+
.eq("atp_did", did);
499
499
+
});
500
500
+
}
474
501
475
502
return {
476
503
success: stats.errors.length === 0,
+11
app/api/oauth/[route]/route.ts
···
11
11
ActionAfterSignIn,
12
12
parseActionFromSearchParam,
13
13
} from "./afterSignInActions";
14
14
+
import { inngest } from "app/api/inngest/client";
14
15
15
16
type OauthRequestClientState = {
16
17
redirect: string | null;
···
84
85
.single();
85
86
identity = data;
86
87
}
88
88
+
89
89
+
// Trigger migration if identity needs it
90
90
+
const metadata = identity?.metadata as Record<string, unknown> | null;
91
91
+
if (metadata?.needsStandardSiteMigration) {
92
92
+
await inngest.send({
93
93
+
name: "user/migrate-to-standard",
94
94
+
data: { did: session.did },
95
95
+
});
96
96
+
}
97
97
+
87
98
let { data: token } = await supabaseServerClient
88
99
.from("email_auth_tokens")
89
100
.insert({
+1
drizzle/schema.ts
···
140
140
email: text("email"),
141
141
atp_did: text("atp_did"),
142
142
interface_state: jsonb("interface_state"),
143
143
+
metadata: jsonb("metadata"),
143
144
},
144
145
(table) => {
145
146
return {
+3
supabase/database.types.ts
···
551
551
home_page: string
552
552
id: string
553
553
interface_state: Json | null
554
554
+
metadata: Json | null
554
555
}
555
556
Insert: {
556
557
atp_did?: string | null
···
559
560
home_page?: string
560
561
id?: string
561
562
interface_state?: Json | null
563
563
+
metadata?: Json | null
562
564
}
563
565
Update: {
564
566
atp_did?: string | null
···
567
569
home_page?: string
568
570
id?: string
569
571
interface_state?: Json | null
572
572
+
metadata?: Json | null
570
573
}
571
574
Relationships: [
572
575
{