+7
-21
server/src/backfill/index.ts
+7
-21
server/src/backfill/index.ts
···
3
3
import * as schema from "../db/schema.ts";
4
4
import { db as db_type } from "../utils.ts";
5
5
import { Client, simpleFetchHandler } from "@atcute/client";
6
+
import oldRecords from "./old-records.ts";
6
7
7
8
const db: db_type = drizzle<typeof schema>(Deno.env.get("DB_FILE_NAME")!);
8
9
···
13
14
res = p_res;
14
15
});
15
16
17
+
// clear old records
18
+
// accounts could have deleted their sites or anything
19
+
// so we should just nuke them
20
+
await db.delete(routes);
21
+
16
22
const backfillClient = new Client({
17
23
handler: simpleFetchHandler({
18
24
service:
···
23
29
}),
24
30
});
25
31
26
-
let repos: `did:${string}:${string}`[] = [];
27
-
let cursor: string | undefined;
28
-
while (true) {
29
-
const { data, ok } = await backfillClient.get(
30
-
"com.atproto.sync.listReposByCollection",
31
-
{
32
-
params: {
33
-
collection: "dev.atcities.route",
34
-
cursor,
35
-
},
36
-
}
37
-
);
38
-
39
-
if (!ok) {
40
-
throw data.error + "\n" + data.message;
41
-
}
42
-
43
-
repos = [...repos, ...data.repos.map((x) => x.did)];
44
-
cursor = data.cursor;
45
-
if (!cursor) break;
46
-
}
32
+
oldRecords(backfillClient, db);
47
33
48
34
res(db);
+79
server/src/backfill/old-records.ts
+79
server/src/backfill/old-records.ts
···
1
+
import { Client, simpleFetchHandler } from "@atcute/client";
2
+
import { db, getPds, isDid, rkeyToUrl } from "../utils.ts";
3
+
import { is } from "@atcute/lexicons";
4
+
import { DevAtcitiesRoute } from "../lexicons/index.ts";
5
+
import { routes } from "../db/schema.ts";
6
+
7
+
async function index(did: `did:${"web" | "plc"}:${string}`, db: db) {
8
+
const pds = await getPds(did);
9
+
if (!pds) return console.error(did, "could not be resolved to a pds.");
10
+
const pdsClient = new Client({
11
+
handler: simpleFetchHandler({ service: pds }),
12
+
});
13
+
let cursor: string | undefined;
14
+
while (true) {
15
+
const { data, ok } = await pdsClient.get("com.atproto.repo.listRecords", {
16
+
params: {
17
+
repo: did,
18
+
collection: "dev.atcities.route",
19
+
cursor,
20
+
},
21
+
});
22
+
23
+
if (!ok) {
24
+
console.error("Got error:", data.error, data.message);
25
+
break;
26
+
}
27
+
28
+
for (const record of data.records)
29
+
if (is(DevAtcitiesRoute.mainSchema, record.value)) {
30
+
if (record.value.page.$type === "dev.atcities.route#blob") {
31
+
const url = rkeyToUrl(record.uri.split("/")[4]);
32
+
if (!url) continue;
33
+
const data: typeof routes.$inferInsert = {
34
+
did: did,
35
+
url_route: url,
36
+
blob_cid:
37
+
"ref" in record.value.page.blob
38
+
? record.value.page.blob.ref.$link
39
+
: record.value.page.blob.cid,
40
+
mime: record.value.page.blob.mimeType,
41
+
};
42
+
db.insert(routes).values(data);
43
+
}
44
+
} else console.warn("Invalid record:", record.uri);
45
+
46
+
cursor = data.cursor;
47
+
if (!cursor) break;
48
+
}
49
+
}
50
+
51
+
export default async function (backfillClient: Client, db: db) {
52
+
let repos: `did:${string}:${string}`[] = [];
53
+
let cursor: string | undefined;
54
+
while (true) {
55
+
const { data, ok } = await backfillClient.get(
56
+
"com.atproto.sync.listReposByCollection",
57
+
{
58
+
params: {
59
+
collection: "dev.atcities.route",
60
+
cursor,
61
+
},
62
+
}
63
+
);
64
+
65
+
if (!ok) {
66
+
throw data.error + "\n" + data.message;
67
+
}
68
+
69
+
repos = [...repos, ...data.repos.map((x) => x.did)];
70
+
cursor = data.cursor;
71
+
if (!cursor) break;
72
+
}
73
+
74
+
for (const i in repos) {
75
+
const did = repos[i];
76
+
console.log(`indexing ${Number(i) + 1}/${repos.length} ${did}`);
77
+
if (isDid(did)) index(did, db);
78
+
}
79
+
}
+16
-15
server/src/routes/user.ts
+16
-15
server/src/routes/user.ts
···
24
24
req: Request,
25
25
user:
26
26
| { handle: `${string}.${string}` }
27
-
| { did: `did:plc:${string}` | `did:web:${string}` },
27
+
| { did: `did:plc:${string}` | `did:web:${string}` }
28
28
): Promise<Response> {
29
29
// if handle: resolve did
30
30
let did: `did:${"plc" | "web"}:${string}`;
···
36
36
return new Response(
37
37
`${ascii}
38
38
39
-
This handle
39
+
This handle could not be resolved.
40
40
`,
41
41
{
42
42
status: 500,
43
43
statusText: "Internal Server Error",
44
-
},
44
+
}
45
45
);
46
46
}
47
47
} else did = user.did;
48
48
49
49
// look up in db
50
-
const db_res = (
51
-
await db
52
-
.select()
53
-
.from(routes)
54
-
.where(
55
-
and(
56
-
eq(routes.did, did),
57
-
eq(routes.url_route, new URL(req.url).pathname),
58
-
),
59
-
)
60
-
).at(0) ??
50
+
const db_res =
51
+
(
52
+
await db
53
+
.select()
54
+
.from(routes)
55
+
.where(
56
+
and(
57
+
eq(routes.did, did),
58
+
eq(routes.url_route, new URL(req.url).pathname)
59
+
)
60
+
)
61
+
).at(0) ??
61
62
(
62
63
await db
63
64
.select()
···
80
81
}
81
82
try {
82
83
const file = await Deno.readFile(
83
-
`./blobs/${db_res.did}/${db_res.blob_cid}`,
84
+
`./blobs/${db_res.did}/${db_res.blob_cid}`
84
85
);
85
86
return new Response(file, {
86
87
headers: {
+34
server/src/utils.ts
+34
server/src/utils.ts
···
1
1
import { LibSQLDatabase } from "drizzle-orm/libsql";
2
2
import { Client } from "@libsql/client";
3
3
import * as schema from "./db/schema.ts";
4
+
import {
5
+
CompositeDidDocumentResolver,
6
+
PlcDidDocumentResolver,
7
+
WebDidDocumentResolver,
8
+
} from "@atcute/identity-resolver";
4
9
5
10
export type db = LibSQLDatabase<typeof schema> & {
6
11
$client: Client;
···
23
28
head.append("Set-Cookie", `${key}=; Max-Age=-1`);
24
29
}
25
30
return head;
31
+
}
32
+
33
+
const docResolver = new CompositeDidDocumentResolver({
34
+
methods: {
35
+
plc: new PlcDidDocumentResolver(),
36
+
web: new WebDidDocumentResolver(),
37
+
},
38
+
});
39
+
40
+
export async function getPds(
41
+
did: `did:${"plc" | "web"}:${string}`
42
+
): Promise<string | undefined> {
43
+
try {
44
+
const doc = await docResolver.resolve(did);
45
+
const pds = doc.service?.filter(
46
+
(x) =>
47
+
x.id.endsWith("#atproto_pds") && x.type === "AtprotoPersonalDataServer"
48
+
)[0].serviceEndpoint;
49
+
return typeof pds === "string" ? pds : undefined;
50
+
} catch {
51
+
return undefined;
52
+
}
53
+
}
54
+
55
+
export function isDid(did: unknown): did is `did:${"plc" | "web"}:${string}` {
56
+
return (
57
+
typeof did === "string" &&
58
+
(did.startsWith("did:web:") || did.startsWith("did:plc:"))
59
+
);
26
60
}
27
61
28
62
/**