@evanp/activitypub-bot 0.18.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/lib/actorstorage.js +12 -3
- package/lib/botdatastorage.js +2 -1
- package/lib/index.js +2 -0
- package/lib/migrations/001-initial.js +56 -29
- package/lib/migrations/002-last-activity.js +5 -5
- package/lib/migrations/index.js +21 -7
- package/lib/objectstorage.js +6 -6
- package/lib/remotekeystorage.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -332,7 +332,11 @@ PRs accepted.
|
|
|
332
332
|
|
|
333
333
|
JavaScript code should use [JavaScript Standard Style](https://standardjs.com).
|
|
334
334
|
|
|
335
|
-
There is a test suite using the Node [test runner](https://nodejs.org/api/test.html#test-runner).
|
|
335
|
+
There is a test suite using the Node [test runner](https://nodejs.org/api/test.html#test-runner). Run `npm run test` to start the tests. You can run just one test
|
|
336
|
+
by adding it to the command-line, like `npm run test -- tests/botcontext.test.js`. The tests use `TEST_DATABASE_URL` for database configuration and default to `sqlite::memory:` (SQLite in-memory) when it is unset. This lets you test against Postgres or MySQL when needed. NOTE that the tests are destructive af and you should
|
|
337
|
+
not use your production database as a unit test DB!
|
|
338
|
+
|
|
339
|
+
If you add a new feature, add tests for it. If you find a bug and fix it, add a test to make sure it stays fixed.
|
|
336
340
|
|
|
337
341
|
If editing the Readme, please conform to the [standard-readme](https://github.com/RichardLitt/standard-readme) specification.
|
|
338
342
|
|
package/lib/actorstorage.js
CHANGED
|
@@ -315,7 +315,7 @@ export class ActorStorage {
|
|
|
315
315
|
`
|
|
316
316
|
INSERT INTO lastactivity (username, type, object_id, activity_id)
|
|
317
317
|
VALUES (?, ?, ?, ?)
|
|
318
|
-
ON CONFLICT DO UPDATE
|
|
318
|
+
ON CONFLICT (username, type, object_id) DO UPDATE
|
|
319
319
|
SET activity_id = EXCLUDED.activity_id,
|
|
320
320
|
updatedAt = CURRENT_TIMESTAMP
|
|
321
321
|
`,
|
|
@@ -332,14 +332,23 @@ export class ActorStorage {
|
|
|
332
332
|
|
|
333
333
|
async #getCollectionInfo (username, property) {
|
|
334
334
|
const [result] = await this.#connection.query(
|
|
335
|
-
`SELECT
|
|
335
|
+
`SELECT
|
|
336
|
+
first,
|
|
337
|
+
totalItems AS totalitems,
|
|
338
|
+
createdAt AS createdat,
|
|
339
|
+
updatedAt AS updatedat
|
|
336
340
|
FROM actorcollection
|
|
337
341
|
WHERE username = ? AND property = ?;`,
|
|
338
342
|
{ replacements: [username, property] }
|
|
339
343
|
)
|
|
340
344
|
if (result.length > 0) {
|
|
341
345
|
const row = result[0]
|
|
342
|
-
return [
|
|
346
|
+
return [
|
|
347
|
+
row.totalitems,
|
|
348
|
+
row.first,
|
|
349
|
+
row.createdat,
|
|
350
|
+
row.updatedat
|
|
351
|
+
]
|
|
343
352
|
} else {
|
|
344
353
|
return [0, 1, null, null]
|
|
345
354
|
}
|
package/lib/botdatastorage.js
CHANGED
|
@@ -27,7 +27,8 @@ export class BotDataStorage {
|
|
|
27
27
|
assert.equal(typeof key, 'string', 'key must be a string')
|
|
28
28
|
await this.#connection.query(`
|
|
29
29
|
INSERT INTO botdata (value, username, key) VALUES (?, ?, ?)
|
|
30
|
-
ON CONFLICT
|
|
30
|
+
ON CONFLICT (username, key) DO UPDATE
|
|
31
|
+
SET value = EXCLUDED.value, updatedAt = CURRENT_TIMESTAMP`,
|
|
31
32
|
{ replacements: [JSON.stringify(value), username, key] }
|
|
32
33
|
)
|
|
33
34
|
}
|
package/lib/index.js
CHANGED
|
@@ -3,3 +3,5 @@ export { default as Bot } from './bot.js'
|
|
|
3
3
|
export { default as BotFactory } from './botfactory.js'
|
|
4
4
|
export { default as OKBot } from './bots/ok.js'
|
|
5
5
|
export { default as DoNothingBot } from './bots/donothing.js'
|
|
6
|
+
export { default as RelayClientBot } from './bots/relayclient.js'
|
|
7
|
+
export { default as RelayServerBot } from './bots/relayserver.js'
|
|
@@ -1,63 +1,65 @@
|
|
|
1
1
|
export const id = '001-initial'
|
|
2
2
|
|
|
3
|
-
export async function up (connection) {
|
|
3
|
+
export async function up (connection, queryOptions = {}) {
|
|
4
4
|
await connection.query(`
|
|
5
5
|
CREATE TABLE IF NOT EXISTS actorcollection (
|
|
6
6
|
username varchar(512) NOT NULL,
|
|
7
7
|
property varchar(512) NOT NULL,
|
|
8
8
|
first INTEGER NOT NULL,
|
|
9
9
|
totalItems INTEGER NOT NULL DEFAULT 0,
|
|
10
|
-
createdAt
|
|
11
|
-
updatedAt
|
|
10
|
+
createdAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
11
|
+
updatedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
12
12
|
PRIMARY KEY (username, property)
|
|
13
13
|
);
|
|
14
|
-
|
|
14
|
+
`, queryOptions)
|
|
15
15
|
await connection.query(`
|
|
16
16
|
CREATE TABLE IF NOT EXISTS actorcollectionpage (
|
|
17
17
|
username varchar(512) NOT NULL,
|
|
18
18
|
property varchar(512) NOT NULL,
|
|
19
19
|
item varchar(512) NOT NULL,
|
|
20
20
|
page INTEGER NOT NULL,
|
|
21
|
-
createdAt
|
|
21
|
+
createdAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
22
22
|
PRIMARY KEY (username, property, item)
|
|
23
23
|
);
|
|
24
|
-
|
|
24
|
+
`, queryOptions)
|
|
25
25
|
await connection.query(
|
|
26
26
|
`CREATE INDEX IF NOT EXISTS actorcollectionpage_username_property_page
|
|
27
|
-
ON actorcollectionpage (username, property, page)
|
|
27
|
+
ON actorcollectionpage (username, property, page);`,
|
|
28
|
+
queryOptions
|
|
28
29
|
)
|
|
29
30
|
|
|
30
31
|
await connection.query(`
|
|
31
32
|
CREATE TABLE IF NOT EXISTS objects (
|
|
32
33
|
id VARCHAR(512) PRIMARY KEY,
|
|
33
34
|
data TEXT NOT NULL,
|
|
34
|
-
createdAt
|
|
35
|
-
updatedAt
|
|
35
|
+
createdAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
36
|
+
updatedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
36
37
|
)
|
|
37
|
-
|
|
38
|
+
`, queryOptions)
|
|
38
39
|
await connection.query(`
|
|
39
40
|
CREATE TABLE IF NOT EXISTS collections (
|
|
40
41
|
id VARCHAR(512) NOT NULL,
|
|
41
42
|
property VARCHAR(512) NOT NULL,
|
|
42
43
|
first INTEGER NOT NULL,
|
|
43
|
-
createdAt
|
|
44
|
-
updatedAt
|
|
44
|
+
createdAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
45
|
+
updatedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
45
46
|
PRIMARY KEY (id, property)
|
|
46
47
|
)
|
|
47
|
-
|
|
48
|
+
`, queryOptions)
|
|
48
49
|
await connection.query(`
|
|
49
50
|
CREATE TABLE IF NOT EXISTS pages (
|
|
50
51
|
id VARCHAR(512) NOT NULL,
|
|
51
52
|
property VARCHAR(64) NOT NULL,
|
|
52
53
|
item VARCHAR(512) NOT NULL,
|
|
53
54
|
page INTEGER NOT NULL,
|
|
54
|
-
createdAt
|
|
55
|
+
createdAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
55
56
|
PRIMARY KEY (id, property, item)
|
|
56
57
|
)
|
|
57
|
-
|
|
58
|
+
`, queryOptions)
|
|
58
59
|
await connection.query(
|
|
59
60
|
`CREATE INDEX IF NOT EXISTS pages_username_property_page
|
|
60
|
-
ON pages (id, property, page)
|
|
61
|
+
ON pages (id, property, page);`,
|
|
62
|
+
queryOptions
|
|
61
63
|
)
|
|
62
64
|
|
|
63
65
|
await connection.query(`
|
|
@@ -65,11 +67,11 @@ export async function up (connection) {
|
|
|
65
67
|
username VARCHAR(512) not null,
|
|
66
68
|
key VARCHAR(512) not null,
|
|
67
69
|
value TEXT not null,
|
|
68
|
-
createdAt
|
|
69
|
-
updatedAt
|
|
70
|
+
createdAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
71
|
+
updatedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
70
72
|
PRIMARY KEY (username, key)
|
|
71
73
|
)
|
|
72
|
-
|
|
74
|
+
`, queryOptions)
|
|
73
75
|
|
|
74
76
|
await connection.query(`
|
|
75
77
|
CREATE TABLE IF NOT EXISTS new_keys (
|
|
@@ -77,14 +79,14 @@ export async function up (connection) {
|
|
|
77
79
|
public_key TEXT,
|
|
78
80
|
private_key TEXT
|
|
79
81
|
)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
`, queryOptions)
|
|
83
|
+
if (await hasTable(connection, 'keys', queryOptions)) {
|
|
82
84
|
await connection.query(`
|
|
83
|
-
INSERT
|
|
85
|
+
INSERT INTO new_keys (username, public_key, private_key)
|
|
84
86
|
SELECT bot_id, public_key, private_key
|
|
85
87
|
FROM keys
|
|
86
|
-
|
|
87
|
-
|
|
88
|
+
ON CONFLICT DO NOTHING
|
|
89
|
+
`, queryOptions)
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
await connection.query(`
|
|
@@ -95,13 +97,38 @@ export async function up (connection) {
|
|
|
95
97
|
createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
96
98
|
updatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
97
99
|
)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
+
`, queryOptions)
|
|
101
|
+
if (await hasTable(connection, 'remotekeys', queryOptions)) {
|
|
100
102
|
await connection.query(`
|
|
101
|
-
INSERT
|
|
103
|
+
INSERT INTO new_remotekeys (id, owner, publicKeyPem)
|
|
102
104
|
SELECT id, owner, publicKeyPem
|
|
103
105
|
FROM remotekeys
|
|
104
|
-
|
|
105
|
-
|
|
106
|
+
ON CONFLICT DO NOTHING
|
|
107
|
+
`, queryOptions)
|
|
106
108
|
}
|
|
107
109
|
}
|
|
110
|
+
|
|
111
|
+
function normalizeTableName (table) {
|
|
112
|
+
if (typeof table === 'string') {
|
|
113
|
+
return table
|
|
114
|
+
.split('.')
|
|
115
|
+
.pop()
|
|
116
|
+
.replaceAll('"', '')
|
|
117
|
+
.toLowerCase()
|
|
118
|
+
}
|
|
119
|
+
if (table && typeof table === 'object') {
|
|
120
|
+
if (typeof table.tableName === 'string') {
|
|
121
|
+
return table.tableName.toLowerCase()
|
|
122
|
+
}
|
|
123
|
+
if (typeof table.name === 'string') {
|
|
124
|
+
return table.name.toLowerCase()
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return ''
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function hasTable (connection, tableName, queryOptions = {}) {
|
|
131
|
+
const tables = await connection.getQueryInterface().showAllTables(queryOptions)
|
|
132
|
+
const normalized = tableName.toLowerCase()
|
|
133
|
+
return tables.some((table) => normalizeTableName(table) === normalized)
|
|
134
|
+
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
export const id = '002-last-activity'
|
|
2
2
|
|
|
3
|
-
export async function up (connection) {
|
|
3
|
+
export async function up (connection, queryOptions = {}) {
|
|
4
4
|
await connection.query(`
|
|
5
|
-
CREATE TABLE lastactivity (
|
|
5
|
+
CREATE TABLE IF NOT EXISTS lastactivity (
|
|
6
6
|
username varchar(512) NOT NULL,
|
|
7
7
|
type varchar(512) NOT NULL,
|
|
8
8
|
object_id varchar(512) NOT NULL,
|
|
9
9
|
activity_id varchar(512),
|
|
10
|
-
createdAt
|
|
11
|
-
updatedAt
|
|
10
|
+
createdAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
11
|
+
updatedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
12
12
|
PRIMARY KEY (username, type, object_id)
|
|
13
13
|
);
|
|
14
|
-
|
|
14
|
+
`, queryOptions)
|
|
15
15
|
}
|
package/lib/migrations/index.js
CHANGED
|
@@ -6,25 +6,39 @@ const migrations = [
|
|
|
6
6
|
{ id: lastId, up: lastUp }
|
|
7
7
|
]
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
async function runMigrationsInternal (connection, queryOptions = {}) {
|
|
10
10
|
await connection.query(`
|
|
11
11
|
CREATE TABLE IF NOT EXISTS migrations (
|
|
12
12
|
id VARCHAR(255) PRIMARY KEY,
|
|
13
|
-
ranAt
|
|
13
|
+
ranAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
14
14
|
)
|
|
15
|
-
|
|
15
|
+
`, queryOptions)
|
|
16
16
|
|
|
17
|
-
const [rows] = await connection.query('SELECT id FROM migrations')
|
|
17
|
+
const [rows] = await connection.query('SELECT id FROM migrations', queryOptions)
|
|
18
18
|
const applied = new Set(rows.map((row) => row.id))
|
|
19
19
|
|
|
20
20
|
for (const migration of migrations) {
|
|
21
21
|
if (applied.has(migration.id)) {
|
|
22
22
|
continue
|
|
23
23
|
}
|
|
24
|
-
await migration.up(connection)
|
|
24
|
+
await migration.up(connection, queryOptions)
|
|
25
25
|
await connection.query(
|
|
26
|
-
'INSERT INTO migrations (id) VALUES (?)',
|
|
27
|
-
{ replacements: [migration.id] }
|
|
26
|
+
'INSERT INTO migrations (id) VALUES (?) ON CONFLICT (id) DO NOTHING',
|
|
27
|
+
{ ...queryOptions, replacements: [migration.id] }
|
|
28
28
|
)
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
+
|
|
32
|
+
export async function runMigrations (connection) {
|
|
33
|
+
if (connection.getDialect() === 'postgres') {
|
|
34
|
+
await connection.transaction(async (transaction) => {
|
|
35
|
+
await connection.query(
|
|
36
|
+
'SELECT pg_advisory_xact_lock(1600846134, 804024271)',
|
|
37
|
+
{ transaction }
|
|
38
|
+
)
|
|
39
|
+
await runMigrationsInternal(connection, { transaction })
|
|
40
|
+
})
|
|
41
|
+
} else {
|
|
42
|
+
await runMigrationsInternal(connection)
|
|
43
|
+
}
|
|
44
|
+
}
|
package/lib/objectstorage.js
CHANGED
|
@@ -77,19 +77,19 @@ export class ObjectStorage {
|
|
|
77
77
|
let first = 1
|
|
78
78
|
let createdAt = null
|
|
79
79
|
const row = await this.#connection.query(
|
|
80
|
-
'SELECT first, createdAt FROM collections WHERE id = ? AND property = ?',
|
|
80
|
+
'SELECT first, createdAt AS createdat FROM collections WHERE id = ? AND property = ?',
|
|
81
81
|
{ replacements: [id, property] }
|
|
82
82
|
)
|
|
83
83
|
if (row[0].length > 0) {
|
|
84
84
|
first = row[0][0].first
|
|
85
|
-
createdAt = row[0][0].
|
|
85
|
+
createdAt = row[0][0].createdat
|
|
86
86
|
}
|
|
87
87
|
const count = await this.#connection.query(
|
|
88
|
-
'SELECT COUNT(*) FROM pages WHERE id = ? AND property = ?',
|
|
88
|
+
'SELECT COUNT(*) AS count FROM pages WHERE id = ? AND property = ?',
|
|
89
89
|
{ replacements: [id, property] }
|
|
90
90
|
)
|
|
91
91
|
if (count[0].length > 0) {
|
|
92
|
-
totalItems = count[0][0]
|
|
92
|
+
totalItems = Number(count[0][0].count ?? 0)
|
|
93
93
|
}
|
|
94
94
|
assert.equal(typeof totalItems, 'number', 'totalItems must be a number')
|
|
95
95
|
assert.ok(totalItems >= 0, 'totalItems must be greater than or equal to 0')
|
|
@@ -223,10 +223,10 @@ export class ObjectStorage {
|
|
|
223
223
|
assert.equal(typeof page, 'number', 'page must be a number')
|
|
224
224
|
assert.ok(page >= 1, 'page must be greater than or equal to 1')
|
|
225
225
|
const rows = await this.#connection.query(
|
|
226
|
-
'SELECT COUNT(*) FROM pages WHERE id = ? AND property = ? AND page = ?',
|
|
226
|
+
'SELECT COUNT(*) AS count FROM pages WHERE id = ? AND property = ? AND page = ?',
|
|
227
227
|
{ replacements: [id, property, page] }
|
|
228
228
|
)
|
|
229
|
-
return rows[0][0]
|
|
229
|
+
return Number(rows[0][0].count ?? 0)
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
async removeFromCollection (id, property, object) {
|
package/lib/remotekeystorage.js
CHANGED
|
@@ -34,13 +34,13 @@ export class RemoteKeyStorage {
|
|
|
34
34
|
async #getCachedPublicKey (id) {
|
|
35
35
|
this.debug(`#getCachedPublicKey(${id})`)
|
|
36
36
|
const [result] = await this.#connection.query(
|
|
37
|
-
'SELECT publicKeyPem, owner FROM new_remotekeys WHERE id = ?',
|
|
37
|
+
'SELECT publicKeyPem AS publickeypem, owner FROM new_remotekeys WHERE id = ?',
|
|
38
38
|
{ replacements: [id] }
|
|
39
39
|
)
|
|
40
40
|
if (result.length > 0) {
|
|
41
41
|
this.debug(`cache hit for ${id}`)
|
|
42
42
|
return {
|
|
43
|
-
publicKeyPem: result[0].
|
|
43
|
+
publicKeyPem: result[0].publickeypem,
|
|
44
44
|
owner: result[0].owner
|
|
45
45
|
}
|
|
46
46
|
} else {
|