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
add feed service
awarm.space
9 months ago
42a37014
9b64c7f4
+113
6 changed files
expand all
collapse all
unified
split
.gitignore
disco.json
feeds
Dockerfile
index.ts
package-lock.json
package.json
+1
.gitignore
···
5
5
6
6
# Appview
7
7
appview/dist
8
8
+
feeds/dist
8
9
9
10
.next
10
11
node_modules
+7
disco.json
···
1
1
{
2
2
"version": "1.0",
3
3
"services": {
4
4
+
"web": {
5
5
+
"image": "feed-service",
6
6
+
"port": 3000
7
7
+
},
4
8
"worker": {
5
9
"image": "appview",
6
10
"volumes": [
···
12
16
}
13
17
},
14
18
"images": {
19
19
+
"feed-service": {
20
20
+
"dockerfile": "feeds/Dockerfile"
21
21
+
},
15
22
"appview": {
16
23
"dockerfile": "appview/Dockerfile"
17
24
}
+12
feeds/Dockerfile
···
1
1
+
FROM node:22
2
2
+
WORKDIR /code
3
3
+
4
4
+
# start with dependencies to enjoy caching
5
5
+
COPY ./package.json /code/package.json
6
6
+
COPY ./package-lock.json /code/package-lock.json
7
7
+
RUN npm ci
8
8
+
9
9
+
# copy rest and build
10
10
+
COPY . /code/.
11
11
+
RUN npm run build-feed-service
12
12
+
CMD ["node", "/code/feeds/dist/index.js"]
+67
feeds/index.ts
···
1
1
+
import { Hono, HonoRequest } from "hono";
2
2
+
import { serve } from "@hono/node-server";
3
3
+
import { DidResolver } from "@atproto/identity";
4
4
+
import { parseReqNsid, verifyJwt } from "@atproto/xrpc-server";
5
5
+
import { supabaseServerClient } from "supabase/serverClient";
6
6
+
import { PubLeafletDocument } from "lexicons/api";
7
7
+
8
8
+
const app = new Hono();
9
9
+
10
10
+
const domain = "feeds.leaflet.pub";
11
11
+
const serviceDid = `did:web:${domain}`;
12
12
+
13
13
+
app.get("/.well-known/did.json", (c) => {
14
14
+
return c.json({
15
15
+
"@context": ["https://www.w3.org/ns/did/v1"],
16
16
+
id: serviceDid,
17
17
+
service: [
18
18
+
{
19
19
+
id: "#bsky_fg",
20
20
+
type: "BskyFeedGenerator",
21
21
+
serviceEndpoint: `https://${domain}`,
22
22
+
},
23
23
+
],
24
24
+
});
25
25
+
});
26
26
+
27
27
+
app.get("/xrpc/app.bsky.feed.getFeedSkeleton", async (c) => {
28
28
+
let auth = await validateAuth(c.req, serviceDid);
29
29
+
if (!auth) return c.json({ feed: [] });
30
30
+
31
31
+
let { data: publications } = await supabaseServerClient
32
32
+
.from("publication_subscriptions")
33
33
+
.select(`publications(documents_in_publications(documents(*)))`)
34
34
+
.eq("identity", auth);
35
35
+
const feed = (publications || []).flatMap((pub) => {
36
36
+
let posts = pub.publications?.documents_in_publications || [];
37
37
+
return posts.flatMap((p) => {
38
38
+
if (!p.documents?.data) return [];
39
39
+
let record = p.documents.data as PubLeafletDocument.Record;
40
40
+
if (!record.postRef) return [];
41
41
+
return { post: record.postRef.uri };
42
42
+
});
43
43
+
});
44
44
+
45
45
+
return c.json({
46
46
+
feed,
47
47
+
});
48
48
+
});
49
49
+
50
50
+
const didResolver = new DidResolver({});
51
51
+
const validateAuth = async (
52
52
+
req: HonoRequest,
53
53
+
serviceDid: string,
54
54
+
): Promise<string | null> => {
55
55
+
const authorization = req.header("authorization");
56
56
+
if (!authorization?.startsWith("Bearer ")) {
57
57
+
return null;
58
58
+
}
59
59
+
const jwt = authorization.replace("Bearer ", "").trim();
60
60
+
const nsid = parseReqNsid(req);
61
61
+
const parsed = await verifyJwt(jwt, serviceDid, nsid, async (did: string) => {
62
62
+
return didResolver.resolveAtprotoKey(did);
63
63
+
});
64
64
+
return parsed.iss;
65
65
+
};
66
66
+
67
67
+
serve(app);
+23
package-lock.json
···
16
16
"@atproto/sync": "^0.1.23",
17
17
"@atproto/syntax": "^0.3.3",
18
18
"@atproto/xrpc": "^0.6.9",
19
19
+
"@hono/node-server": "^1.14.3",
19
20
"@mdx-js/loader": "^3.1.0",
20
21
"@mdx-js/react": "^3.1.0",
21
22
"@next/bundle-analyzer": "^15.3.2",
···
38
39
"drizzle-orm": "^0.30.10",
39
40
"feed": "^5.0.1",
40
41
"fractional-indexing": "^3.2.0",
42
42
+
"hono": "^4.7.11",
41
43
"linkifyjs": "^4.2.0",
42
44
"multiformats": "^13.3.2",
43
45
"next": "15.3.2",
···
1839
1841
"license": "MIT",
1840
1842
"dependencies": {
1841
1843
"tslib": "^2.8.0"
1844
1844
+
}
1845
1845
+
},
1846
1846
+
"node_modules/@hono/node-server": {
1847
1847
+
"version": "1.14.3",
1848
1848
+
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.14.3.tgz",
1849
1849
+
"integrity": "sha512-KuDMwwghtFYSmIpr4WrKs1VpelTrptvJ+6x6mbUcZnFcc213cumTF5BdqfHyW93B19TNI4Vaev14vOI2a0Ie3w==",
1850
1850
+
"license": "MIT",
1851
1851
+
"engines": {
1852
1852
+
"node": ">=18.14.1"
1853
1853
+
},
1854
1854
+
"peerDependencies": {
1855
1855
+
"hono": "^4"
1842
1856
}
1843
1857
},
1844
1858
"node_modules/@humanwhocodes/config-array": {
···
10159
10173
"resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz",
10160
10174
"integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==",
10161
10175
"dev": true
10176
10176
+
},
10177
10177
+
"node_modules/hono": {
10178
10178
+
"version": "4.7.11",
10179
10179
+
"resolved": "https://registry.npmjs.org/hono/-/hono-4.7.11.tgz",
10180
10180
+
"integrity": "sha512-rv0JMwC0KALbbmwJDEnxvQCeJh+xbS3KEWW5PC9cMJ08Ur9xgatI0HmtgYZfOdOSOeYsp5LO2cOhdI8cLEbDEQ==",
10181
10181
+
"license": "MIT",
10182
10182
+
"engines": {
10183
10183
+
"node": ">=16.9.0"
10184
10184
+
}
10162
10185
},
10163
10186
"node_modules/html-escaper": {
10164
10187
"version": "2.0.2",
+3
package.json
···
10
10
"lexgen": "tsx ./lexicons/build.ts && lex gen-api ./lexicons/api ./lexicons/pub/leaflet/* ./lexicons/pub/leaflet/*/* ./lexicons/com/atproto/*/* --yes && find './lexicons/api' -type f -exec sed -i 's/\\.js'/'/g' {} \\;",
11
11
"wrangler-dev": "wrangler dev",
12
12
"build-appview": "esbuild appview/index.ts --outfile=appview/dist/index.js --bundle --platform=node",
13
13
+
"build-feed-service": "esbuild feeds/index.ts --outfile=feeds/dist/index.js --bundle --platform=node",
13
14
"start-appview-dev": "tsx --env-file='./.env.local' --watch appview/index.ts",
14
15
"start-appview-prod": "npm run build-appview && node appview/dist/index.js"
15
16
},
···
24
25
"@atproto/sync": "^0.1.23",
25
26
"@atproto/syntax": "^0.3.3",
26
27
"@atproto/xrpc": "^0.6.9",
28
28
+
"@hono/node-server": "^1.14.3",
27
29
"@mdx-js/loader": "^3.1.0",
28
30
"@mdx-js/react": "^3.1.0",
29
31
"@next/bundle-analyzer": "^15.3.2",
···
46
48
"drizzle-orm": "^0.30.10",
47
49
"feed": "^5.0.1",
48
50
"fractional-indexing": "^3.2.0",
51
51
+
"hono": "^4.7.11",
49
52
"linkifyjs": "^4.2.0",
50
53
"multiformats": "^13.3.2",
51
54
"next": "15.3.2",