forked from
joebasser.com/atmosphere-account
this repo has no description
1import { resolveIdentity } from "./identity.ts";
2import { PROFILE_NSID, validateProfile } from "./lexicons.ts";
3import { getRecordPublic, listRecordsPublic } from "./pds.ts";
4import { getProfileByDid, upsertProfile } from "./registry.ts";
5
6interface ProfileRecordEnvelope {
7 uri: string;
8 cid: string;
9 value: unknown;
10 rkey: string;
11}
12
13function rkeyFromAtUri(uri: string): string | null {
14 const parts = uri.split("/");
15 return parts.length > 0 ? parts[parts.length - 1] || null : null;
16}
17
18export async function upsertProfileFromRecord(input: {
19 did: string;
20 handle: string;
21 pdsUrl: string;
22 record: ProfileRecordEnvelope;
23 recordRev: string;
24}): Promise<boolean> {
25 const validation = validateProfile(input.record.value);
26 if (!validation.ok || !validation.value) {
27 console.warn(
28 `[profile-sync] invalid profile from ${input.did}/${input.record.rkey}: ${validation.error}`,
29 );
30 return false;
31 }
32
33 const r = validation.value;
34 await upsertProfile({
35 did: input.did,
36 handle: input.handle,
37 profileType: r.profileType,
38 name: r.name,
39 description: r.description,
40 mainLink: r.mainLink ?? null,
41 iosLink: r.iosLink ?? null,
42 androidLink: r.androidLink ?? null,
43 categories: r.categories ?? [],
44 subcategories: r.subcategories ?? [],
45 links: r.links ?? [],
46 screenshots: r.screenshots ?? [],
47 avatarCid: r.avatar?.ref.$link ?? null,
48 avatarMime: r.avatar?.mimeType ?? null,
49 iconCid: r.icon?.ref.$link ?? null,
50 iconMime: r.icon?.mimeType ?? null,
51 iconBwCid: r.iconBw?.ref.$link ?? null,
52 iconBwMime: r.iconBw?.mimeType ?? null,
53 pdsUrl: input.pdsUrl,
54 recordCid: input.record.cid,
55 recordRev: input.recordRev,
56 createdAt: Date.parse(r.createdAt) || Date.now(),
57 });
58 return true;
59}
60
61export async function syncProfileByIdentifier(
62 identifier: string,
63): Promise<boolean> {
64 const identity = await resolveIdentity(identifier);
65 const canonical = await getRecordPublic(
66 identity.pdsUrl,
67 identity.did,
68 PROFILE_NSID,
69 "self",
70 );
71
72 if (canonical) {
73 return await upsertProfileFromRecord({
74 did: identity.did,
75 handle: identity.handle,
76 pdsUrl: identity.pdsUrl,
77 record: { ...canonical, rkey: "self" },
78 recordRev: canonical.cid,
79 });
80 }
81
82 const listed = await listRecordsPublic(
83 identity.pdsUrl,
84 identity.did,
85 PROFILE_NSID,
86 {
87 limit: 25,
88 reverse: true,
89 },
90 );
91 for (const record of listed.records) {
92 const rkey = rkeyFromAtUri(record.uri);
93 if (!rkey) continue;
94 const synced = await upsertProfileFromRecord({
95 did: identity.did,
96 handle: identity.handle,
97 pdsUrl: identity.pdsUrl,
98 record: { ...record, rkey },
99 recordRev: record.cid,
100 });
101 if (synced) return true;
102 }
103
104 return (await getProfileByDid(identity.did).catch(() => null)) !== null;
105}