@evanp/activitypub-bot 0.46.3 → 0.46.4
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/CHANGELOG.md +11 -0
- package/lib/app.js +4 -2
- package/lib/migrations/014-server-stats.js +17 -0
- package/lib/routes/nodeinfo.js +5 -4
- package/lib/serverstats.js +80 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,17 @@ and this project adheres to
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## [0.46.4] - 2026-05-25
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- server_stats table to cache the calculations for nodeinfo.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- nodeinfo uses cached server stats
|
|
21
|
+
when available.
|
|
22
|
+
|
|
12
23
|
## [0.46.3] - 2026-05-25
|
|
13
24
|
|
|
14
25
|
### Fixed
|
package/lib/app.js
CHANGED
|
@@ -53,6 +53,7 @@ import { HTTPMessageSignature } from './httpmessagesignature.js'
|
|
|
53
53
|
import { SignaturePolicyStorage } from './signaturepolicystorage.js'
|
|
54
54
|
import { SafeFetcher } from './safefetcher.js'
|
|
55
55
|
import { EndpointCache } from './endpointcache.js'
|
|
56
|
+
import { ServerStats } from './serverstats.js'
|
|
56
57
|
|
|
57
58
|
const currentDir = dirname(fileURLToPath(import.meta.url))
|
|
58
59
|
const DEFAULT_INDEX_FILENAME = resolve(currentDir, '..', 'web', 'index.html')
|
|
@@ -130,7 +131,7 @@ export async function makeApp ({ databaseUrl, origin, bots, logLevel = 'silent',
|
|
|
130
131
|
client,
|
|
131
132
|
jobQueue
|
|
132
133
|
)
|
|
133
|
-
|
|
134
|
+
const stats = new ServerStats(keyStorage, actorStorage, formatter.hostname, connection)
|
|
134
135
|
// TODO: Make an endpoint for tagged objects
|
|
135
136
|
const transformer = new Transformer(
|
|
136
137
|
origin + '/tag/', client, fetcher, formatter
|
|
@@ -222,7 +223,8 @@ export async function makeApp ({ databaseUrl, origin, bots, logLevel = 'silent',
|
|
|
222
223
|
deliverer,
|
|
223
224
|
deliveryWorkers,
|
|
224
225
|
indexFileName,
|
|
225
|
-
profileFileName
|
|
226
|
+
profileFileName,
|
|
227
|
+
stats
|
|
226
228
|
}
|
|
227
229
|
|
|
228
230
|
app.disable('x-powered-by')
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const id = '014-server-stats'
|
|
2
|
+
|
|
3
|
+
export async function up (connection, queryOptions = {}) {
|
|
4
|
+
const tsType = (connection.getDialect() === 'postgres')
|
|
5
|
+
? 'TIMESTAMPTZ'
|
|
6
|
+
: 'TIMESTAMP'
|
|
7
|
+
await connection.query(`
|
|
8
|
+
CREATE TABLE server_stats (
|
|
9
|
+
domain VARCHAR(256) PRIMARY KEY,
|
|
10
|
+
total_users INT NOT NULL DEFAULT 0,
|
|
11
|
+
active_monthly INT NOT NULL DEFAULT 0,
|
|
12
|
+
active_half_yearly INT NOT NULL DEFAULT 0,
|
|
13
|
+
created_at ${tsType} NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
14
|
+
updated_at ${tsType} NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
15
|
+
)
|
|
16
|
+
`, queryOptions)
|
|
17
|
+
}
|
package/lib/routes/nodeinfo.js
CHANGED
|
@@ -33,10 +33,11 @@ router.get('/.well-known/nodeinfo', async (req, res, next) => {
|
|
|
33
33
|
|
|
34
34
|
router.get('/nodeinfo/:nodeinfoVersion', async (req, res, next) => {
|
|
35
35
|
const { nodeinfoVersion } = req.params
|
|
36
|
-
const {
|
|
36
|
+
const { stats } = req.app.locals
|
|
37
37
|
if (!VERSIONS.includes(nodeinfoVersion)) {
|
|
38
38
|
throw new ProblemDetailsError(404, 'unsupported nodeinfo version')
|
|
39
39
|
}
|
|
40
|
+
const data = await stats.get()
|
|
40
41
|
res.status(200).json({
|
|
41
42
|
version: nodeinfoVersion,
|
|
42
43
|
software: {
|
|
@@ -51,9 +52,9 @@ router.get('/nodeinfo/:nodeinfoVersion', async (req, res, next) => {
|
|
|
51
52
|
openRegistrations: false,
|
|
52
53
|
usage: {
|
|
53
54
|
users: {
|
|
54
|
-
total:
|
|
55
|
-
activeMonth:
|
|
56
|
-
activeHalfyear:
|
|
55
|
+
total: data.totalUsers,
|
|
56
|
+
activeMonth: data.activeMonthly,
|
|
57
|
+
activeHalfyear: data.activeHalfYearly
|
|
57
58
|
}
|
|
58
59
|
},
|
|
59
60
|
metadata: {}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import assert from 'node:assert'
|
|
2
|
+
|
|
3
|
+
const STATS_EXPIRY = 24 * 60 * 60 * 1000
|
|
4
|
+
|
|
5
|
+
export class ServerStats {
|
|
6
|
+
#keyStorage
|
|
7
|
+
#actorStorage
|
|
8
|
+
#domain
|
|
9
|
+
#connection
|
|
10
|
+
|
|
11
|
+
constructor (keyStorage, actorStorage, domain, connection) {
|
|
12
|
+
assert.strictEqual(typeof keyStorage, 'object')
|
|
13
|
+
assert.strictEqual(typeof actorStorage, 'object')
|
|
14
|
+
assert.strictEqual(typeof domain, 'string')
|
|
15
|
+
assert.strictEqual(typeof connection, 'object')
|
|
16
|
+
|
|
17
|
+
this.#keyStorage = keyStorage
|
|
18
|
+
this.#actorStorage = actorStorage
|
|
19
|
+
this.#domain = domain
|
|
20
|
+
this.#connection = connection
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async get () {
|
|
24
|
+
let data = await this.#get()
|
|
25
|
+
if (!data || new Date() - data.updatedAt > STATS_EXPIRY) {
|
|
26
|
+
data = {
|
|
27
|
+
totalUsers: await this.#keyStorage.count(),
|
|
28
|
+
activeMonthly: await this.#actorStorage.activeUsers(30),
|
|
29
|
+
activeHalfYearly: await this.#actorStorage.activeUsers(180),
|
|
30
|
+
updatedAt: new Date()
|
|
31
|
+
}
|
|
32
|
+
await this.#update(data)
|
|
33
|
+
}
|
|
34
|
+
return data
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async #update (data) {
|
|
38
|
+
await this.#connection.query(
|
|
39
|
+
`
|
|
40
|
+
INSERT INTO server_stats (domain, total_users, active_monthly, active_half_yearly)
|
|
41
|
+
VALUES (?, ?, ?, ?)
|
|
42
|
+
ON CONFLICT (domain) DO UPDATE
|
|
43
|
+
SET total_users = EXCLUDED.total_users,
|
|
44
|
+
active_monthly = EXCLUDED.active_monthly,
|
|
45
|
+
active_half_yearly = EXCLUDED.active_half_yearly,
|
|
46
|
+
updated_at = CURRENT_TIMESTAMP
|
|
47
|
+
`,
|
|
48
|
+
{
|
|
49
|
+
replacements: [
|
|
50
|
+
this.#domain,
|
|
51
|
+
data.totalUsers,
|
|
52
|
+
data.activeMonthly,
|
|
53
|
+
data.activeHalfYearly
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
)
|
|
57
|
+
return data
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async #get () {
|
|
61
|
+
const [rows] = await this.#connection.query(
|
|
62
|
+
`SELECT total_users, active_monthly, active_half_yearly, updated_at
|
|
63
|
+
FROM server_stats
|
|
64
|
+
WHERE domain = ?
|
|
65
|
+
;`,
|
|
66
|
+
{ replacements: [this.#domain] }
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
if (rows.length === 0) {
|
|
70
|
+
return null
|
|
71
|
+
} else {
|
|
72
|
+
return {
|
|
73
|
+
totalUsers: rows[0].total_users,
|
|
74
|
+
activeMonthly: rows[0].active_monthly,
|
|
75
|
+
activeHalfYearly: rows[0].active_half_yearly,
|
|
76
|
+
updatedAt: rows[0].updated_at
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|