tangled
alpha
login
or
join now
danabra.mov
/
statusphere-react
forked from
samuel.fm/statusphere-react
0
fork
atom
the statusphere demo reworked into a vite/react app in a monorepo
0
fork
atom
overview
issues
pulls
pipelines
Switch to a multiple-status model
Paul Frazee
2 years ago
7215d1e0
881f2adb
+32
-47
8 changed files
expand all
collapse all
unified
split
lexicons
status.json
package-lock.json
package.json
src
db.ts
firehose
ingester.ts
lexicon
lexicons.ts
types
com
example
status.ts
routes.ts
+2
-2
lexicons/status.json
···
7
7
"key": "literal:self",
8
8
"record": {
9
9
"type": "object",
10
10
-
"required": ["status", "updatedAt"],
10
10
+
"required": ["status", "createdAt"],
11
11
"properties": {
12
12
"status": {
13
13
"type": "string",
···
15
15
"maxGraphemes": 1,
16
16
"maxLength": 32
17
17
},
18
18
-
"updatedAt": { "type": "string", "format": "datetime" }
18
18
+
"createdAt": { "type": "string", "format": "datetime" }
19
19
}
20
20
}
21
21
}
+2
-25
package-lock.json
···
9
9
"version": "0.0.1",
10
10
"license": "MIT",
11
11
"dependencies": {
12
12
+
"@atproto/common": "^0.4.1",
12
13
"@atproto/identity": "^0.4.0",
13
14
"@atproto/lexicon": "0.4.1-rc.0",
14
15
"@atproto/oauth-client-node": "0.0.2-rc.2",
···
23
24
"kysely": "^0.27.4",
24
25
"multiformats": "^9.9.0",
25
26
"pino": "^9.3.2",
26
26
-
"pino-http": "^10.0.0",
27
27
"uhtml": "^4.5.9"
28
28
},
29
29
"devDependencies": {
···
142
142
"version": "0.4.1",
143
143
"resolved": "https://registry.npmjs.org/@atproto/common/-/common-0.4.1.tgz",
144
144
"integrity": "sha512-uL7kQIcBTbvkBDNfxMXL6lBH4fO2DQpHd2BryJxMtbw/4iEPKe9xBYApwECHhEIk9+zhhpTRZ15FJ3gxTXN82Q==",
145
145
+
"license": "MIT",
145
146
"dependencies": {
146
147
"@atproto/common-web": "^0.3.0",
147
148
"@ipld/dag-cbor": "^7.0.3",
···
1944
1945
"resolved": "https://registry.npmjs.org/gc-hook/-/gc-hook-0.3.1.tgz",
1945
1946
"integrity": "sha512-E5M+O/h2o7eZzGhzRZGex6hbB3k4NWqO0eA+OzLRLXxhdbYPajZnynPwAtphnh+cRHPwsj5Z80dqZlfI4eK55A=="
1946
1947
},
1947
1947
-
"node_modules/get-caller-file": {
1948
1948
-
"version": "2.0.5",
1949
1949
-
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
1950
1950
-
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
1951
1951
-
"engines": {
1952
1952
-
"node": "6.* || 8.* || >= 10.*"
1953
1953
-
}
1954
1954
-
},
1955
1948
"node_modules/get-intrinsic": {
1956
1949
"version": "1.2.4",
1957
1950
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
···
2748
2741
"readable-stream": "^4.0.0",
2749
2742
"split2": "^4.0.0"
2750
2743
}
2751
2751
-
},
2752
2752
-
"node_modules/pino-http": {
2753
2753
-
"version": "10.2.0",
2754
2754
-
"resolved": "https://registry.npmjs.org/pino-http/-/pino-http-10.2.0.tgz",
2755
2755
-
"integrity": "sha512-am03BxnV3Ckx68OkbH0iZs3indsrH78wncQ6w1w51KroIbvJZNImBKX2X1wjdY8lSyaJ0UrX/dnO2DY3cTeCRw==",
2756
2756
-
"dependencies": {
2757
2757
-
"get-caller-file": "^2.0.5",
2758
2758
-
"pino": "^9.0.0",
2759
2759
-
"pino-std-serializers": "^7.0.0",
2760
2760
-
"process-warning": "^3.0.0"
2761
2761
-
}
2762
2762
-
},
2763
2763
-
"node_modules/pino-http/node_modules/process-warning": {
2764
2764
-
"version": "3.0.0",
2765
2765
-
"resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz",
2766
2766
-
"integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ=="
2767
2744
},
2768
2745
"node_modules/pino-pretty": {
2769
2746
"version": "11.2.2",
+1
package.json
···
14
14
"clean": "rimraf dist coverage"
15
15
},
16
16
"dependencies": {
17
17
+
"@atproto/common": "^0.4.1",
17
18
"@atproto/identity": "^0.4.0",
18
19
"@atproto/lexicon": "0.4.1-rc.0",
19
20
"@atproto/oauth-client-node": "0.0.2-rc.2",
+5
-3
src/db.ts
···
16
16
}
17
17
18
18
export type Status = {
19
19
+
uri: string
19
20
authorDid: string
20
21
status: string
21
21
-
updatedAt: string
22
22
+
createdAt: string
22
23
indexedAt: string
23
24
}
24
25
···
50
51
async up(db: Kysely<unknown>) {
51
52
await db.schema
52
53
.createTable('status')
53
53
-
.addColumn('authorDid', 'varchar', (col) => col.primaryKey())
54
54
+
.addColumn('uri', 'varchar', (col) => col.primaryKey())
55
55
+
.addColumn('authorDid', 'varchar', (col) => col.notNull())
54
56
.addColumn('status', 'varchar', (col) => col.notNull())
55
55
-
.addColumn('updatedAt', 'varchar', (col) => col.notNull())
57
57
+
.addColumn('createdAt', 'varchar', (col) => col.notNull())
56
58
.addColumn('indexedAt', 'varchar', (col) => col.notNull())
57
59
.execute()
58
60
await db.schema
+9
-3
src/firehose/ingester.ts
···
24
24
await this.db
25
25
.insertInto('status')
26
26
.values({
27
27
+
uri: evt.uri.toString(),
27
28
authorDid: evt.author,
28
29
status: record.status,
29
29
-
updatedAt: record.updatedAt,
30
30
+
createdAt: record.createdAt,
30
31
indexedAt: new Date().toISOString(),
31
32
})
32
33
.onConflict((oc) =>
33
33
-
oc.column('authorDid').doUpdateSet({
34
34
+
oc.column('uri').doUpdateSet({
34
35
status: record.status,
35
35
-
updatedAt: record.updatedAt,
36
36
indexedAt: new Date().toISOString(),
37
37
})
38
38
)
39
39
.execute()
40
40
}
41
41
+
} else if (
42
42
+
evt.event === 'delete' &&
43
43
+
evt.collection === 'com.example.status'
44
44
+
) {
45
45
+
// Remove the status from our SQLite
46
46
+
await this.db.deleteFrom('status').where({ uri: evt.uri.toString() })
41
47
}
42
48
}
43
49
}
+2
-2
src/lexicon/lexicons.ts
···
68
68
key: 'literal:self',
69
69
record: {
70
70
type: 'object',
71
71
-
required: ['status', 'updatedAt'],
71
71
+
required: ['status', 'createdAt'],
72
72
properties: {
73
73
status: {
74
74
type: 'string',
···
76
76
maxGraphemes: 1,
77
77
maxLength: 32,
78
78
},
79
79
-
updatedAt: {
79
79
+
createdAt: {
80
80
type: 'string',
81
81
format: 'datetime',
82
82
},
+1
-1
src/lexicon/types/com/example/status.ts
···
8
8
9
9
export interface Record {
10
10
status: string
11
11
-
updatedAt: string
11
11
+
createdAt: string
12
12
[k: string]: unknown
13
13
}
14
14
+10
-11
src/routes.ts
···
3
3
import type { IncomingMessage, ServerResponse } from 'node:http'
4
4
import { OAuthResolverError } from '@atproto/oauth-client-node'
5
5
import { isValidHandle } from '@atproto/syntax'
6
6
+
import { TID } from '@atproto/common'
6
7
import express from 'express'
7
8
import { getIronSession } from 'iron-session'
8
9
import type { AppContext } from '#/index'
···
154
155
.selectFrom('status')
155
156
.selectAll()
156
157
.where('authorDid', '=', agent.accountDid)
158
158
+
.orderBy('indexedAt', 'desc')
157
159
.executeTakeFirst()
158
160
: undefined
159
161
···
204
206
}
205
207
206
208
// Construct & validate their status record
209
209
+
const rkey = TID.nextStr()
207
210
const record = {
208
211
$type: 'com.example.status',
209
212
status: req.body?.status,
210
210
-
updatedAt: new Date().toISOString(),
213
213
+
createdAt: new Date().toISOString(),
211
214
}
212
215
if (!Status.validateRecord(record).success) {
213
216
return res.status(400).json({ error: 'Invalid status' })
214
217
}
215
218
219
219
+
let uri
216
220
try {
217
221
// Write the status record to the user's repository
218
218
-
await agent.com.atproto.repo.putRecord({
222
222
+
const res = await agent.com.atproto.repo.putRecord({
219
223
repo: agent.accountDid,
220
224
collection: 'com.example.status',
221
221
-
rkey: 'self',
225
225
+
rkey,
222
226
record,
223
227
validate: false,
224
228
})
229
229
+
uri = res.data.uri
225
230
} catch (err) {
226
231
ctx.logger.warn({ err }, 'failed to write record')
227
232
return res.status(500).json({ error: 'Failed to write record' })
···
235
240
await ctx.db
236
241
.insertInto('status')
237
242
.values({
243
243
+
uri,
238
244
authorDid: agent.accountDid,
239
245
status: record.status,
240
240
-
updatedAt: record.updatedAt,
246
246
+
createdAt: record.createdAt,
241
247
indexedAt: new Date().toISOString(),
242
248
})
243
243
-
.onConflict((oc) =>
244
244
-
oc.column('authorDid').doUpdateSet({
245
245
-
status: record.status,
246
246
-
updatedAt: record.updatedAt,
247
247
-
indexedAt: new Date().toISOString(),
248
248
-
})
249
249
-
)
250
249
.execute()
251
250
} catch (err) {
252
251
ctx.logger.warn(