521 lines
19 KiB
JavaScript
521 lines
19 KiB
JavaScript
module.exports = (app, client) => {
|
|
const constant = require('./constant'),
|
|
zlib = require('zlib'),
|
|
{ pick } = require('stream-json/filters/Pick'),
|
|
{ parser } = require('stream-json'),
|
|
{ streamArray } = require('stream-json/streamers/StreamArray'),
|
|
{ chain } = require('stream-chain'),
|
|
clean = str => {
|
|
return str.replace(/[/\\^$+?()`'¡¿¨!"·%&=;,\|\[\]{}]+/gmi, '')
|
|
}
|
|
/**
|
|
* @swagger
|
|
* /api/stats:
|
|
* get:
|
|
* summary: Retrieve stats of instances.
|
|
* description: Retrieve stats of total instances.
|
|
* responses:
|
|
* 200:
|
|
* description: A object with stats parameters.
|
|
* content:
|
|
* application/json:
|
|
* schema:
|
|
* type: object
|
|
* properties:
|
|
* data:
|
|
* type: object
|
|
* properties:
|
|
* instance_count:
|
|
* type: integer
|
|
* description: Count of instances.
|
|
* example: 0
|
|
* status_avg:
|
|
* type: float
|
|
* description: Average statuses of total instances.
|
|
* example: 0
|
|
* status_max:
|
|
* type: integer
|
|
* description: Max of total statuses instances.
|
|
* example: 0
|
|
* user_avg:
|
|
* type: float
|
|
* description: Average users of total instances.
|
|
* example: 0
|
|
* user_max:
|
|
* type: integer
|
|
* description: Max of total users instances.
|
|
* example: 0
|
|
* domain_avg:
|
|
* type: float
|
|
* description: Number of average domains instances.
|
|
* example: 0
|
|
* domain_max:
|
|
* type: integer
|
|
* description: Number of max domains instances.
|
|
* example: 0
|
|
* stats_filtered:
|
|
* type: integer
|
|
* description: Number of instances stats.
|
|
* example: 0
|
|
*/
|
|
app.use('/api/stats', async (req, res) => {
|
|
const result = await client.search({
|
|
index: constant.index,
|
|
body: {
|
|
size: 0,
|
|
aggs: {
|
|
instance_count: {
|
|
value_count: {
|
|
field: '_id'
|
|
}
|
|
},
|
|
stats_filtered: {
|
|
filter: {
|
|
exists: {
|
|
field: 'api.stats'
|
|
}
|
|
}
|
|
},
|
|
user_avg: {
|
|
avg: {
|
|
field: 'api.stats.user_count'
|
|
}
|
|
},
|
|
user_max: {
|
|
max: {
|
|
field: 'api.stats.user_count'
|
|
}
|
|
},
|
|
domain_avg: {
|
|
avg: {
|
|
field: 'api.stats.domain_count'
|
|
}
|
|
},
|
|
domain_max: {
|
|
max: {
|
|
field: 'api.stats.domain_count'
|
|
}
|
|
},
|
|
status_avg: {
|
|
avg: {
|
|
field: 'api.stats.status_count'
|
|
}
|
|
},
|
|
status_max: {
|
|
max: {
|
|
field: 'api.stats.status_count'
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
})
|
|
res.json(Object.keys(result.aggregations).reduce((prev, curr) => ({
|
|
...prev, [curr]: result.aggregations[curr][Object.keys(result.aggregations[curr])[0]]
|
|
}), {}))
|
|
})
|
|
/**
|
|
* @swagger
|
|
* /api/count:
|
|
* get:
|
|
* summary: Retrieve count of instances.
|
|
* description: Retrieve a number of total instances.
|
|
* responses:
|
|
* 200:
|
|
* description: A object with count parameter.
|
|
* content:
|
|
* application/json:
|
|
* schema:
|
|
* type: object
|
|
* properties:
|
|
* data:
|
|
* type: object
|
|
* properties:
|
|
* count:
|
|
* type: integer
|
|
* description: Number of instances.
|
|
* example: 0
|
|
*/
|
|
app.use('/api/count', async (req, res) => {
|
|
res.json({ count: (await client.count({ index: constant.index })).count })
|
|
})
|
|
/**
|
|
* @swagger
|
|
* /api/ranking:
|
|
* get:
|
|
* summary: Retrieve one ranking of instances.
|
|
* description: Retrieve a top ten ranking of total fediblock instances.
|
|
* responses:
|
|
* 200:
|
|
* description: A object with count parameter.
|
|
* content:
|
|
* application/json:
|
|
* schema:
|
|
* type: object
|
|
* properties:
|
|
* data:
|
|
* type: object
|
|
* properties:
|
|
* domain:
|
|
* type: string
|
|
* description: Domain of the instance.
|
|
* example: "mastodon.social"
|
|
* count:
|
|
* type: integer
|
|
* description: Number of fediblocks.
|
|
* example: 0
|
|
*/
|
|
app.use('/api/ranking', async (req, res) => {
|
|
const result = await client.search({
|
|
index: constant.index,
|
|
body: {
|
|
size: 0,
|
|
aggs: {
|
|
blocks: {
|
|
nested: {
|
|
path: 'blocks'
|
|
},
|
|
aggs: {
|
|
ranking: {
|
|
terms: {
|
|
field: 'blocks.domain',
|
|
size: 100
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
const ranking = result.aggregations?.blocks?.ranking?.buckets
|
|
res.json(ranking.map(r => ({ domain: r.key, count: r.doc_count })))
|
|
})
|
|
/**
|
|
* @swagger
|
|
* /api/list/{instance}:
|
|
* get:
|
|
* summary: Search result array of intances.
|
|
* description: Retrieve a result array of instances matching search input.
|
|
* parameters:
|
|
* - in: path
|
|
* name: instance
|
|
* required: true
|
|
* description: String for search instance
|
|
* schema:
|
|
* type: string
|
|
* responses:
|
|
* 200:
|
|
* description: A list of instances.
|
|
* content:
|
|
* application/json:
|
|
* schema:
|
|
* type: object
|
|
* properties:
|
|
* data:
|
|
* type: array
|
|
* items:
|
|
* type: object
|
|
* properties:
|
|
* instances:
|
|
* type: array
|
|
* items:
|
|
* type: data
|
|
* description: List of instances.
|
|
* example: "mastodon.social"
|
|
* suggests:
|
|
* type: array
|
|
* items:
|
|
* type: data
|
|
* description: Suggest of the instance.
|
|
* example: "mastodon.social"
|
|
*/
|
|
app.use('/api/list/:instance', async (req, res) => {
|
|
if (req.params.instance && req.params.instance.length > 0) {
|
|
const result = await client.search({
|
|
index: constant.index,
|
|
size: 10,
|
|
query: {
|
|
wildcard: {
|
|
instance: {
|
|
value: `*${clean(req.params.instance)}*`
|
|
}
|
|
},
|
|
},
|
|
suggest: {
|
|
suggests: {
|
|
text: req.params.instance,
|
|
term: {
|
|
field: 'instance',
|
|
size: 3,
|
|
sort: 'score',
|
|
suggest_mode: 'always',
|
|
max_edits: 2,
|
|
min_word_length: 1
|
|
}
|
|
}
|
|
}
|
|
})
|
|
const instances = result.hits?.hits?.length > 0 ? result.hits.hits : [],
|
|
suggests = result.suggest.suggests && result.suggest.suggests.length > 0 && result.suggest.suggests[0].options.length > 0 ? result.suggest.suggests[0].options : []
|
|
res.json({
|
|
instances: instances.map(instance => ({
|
|
domain: instance._source.instance,
|
|
api: instance._source.api ? instance._source.api : null,
|
|
blocks: instance._source.blocks && instance._source.blocks.length > 0 ? instance._source.blocks.length : null,
|
|
last: instance._source.last ? instance._source.last : null,
|
|
nodeinfo: instance._source.nodeinfo ? true : false
|
|
})), suggests: suggests.map(instance => instance.text)
|
|
})
|
|
} else {
|
|
res.status(404).end()
|
|
}
|
|
})
|
|
/**
|
|
* @swagger
|
|
* /api/detail/{instance}:
|
|
* get:
|
|
* summary: Search result array of fediblocks intances.
|
|
* description: Retrieve a result array of fediblock instances matching search input.
|
|
* parameters:
|
|
* - in: path
|
|
* name: instance
|
|
* required: true
|
|
* description: String to detail of the instance
|
|
* schema:
|
|
* type: string
|
|
* responses:
|
|
* 200:
|
|
* description: A list of instances.
|
|
* content:
|
|
* application/json:
|
|
* schema:
|
|
* type: object
|
|
* properties:
|
|
* data:
|
|
* type: array
|
|
* items:
|
|
* type: object
|
|
* properties:
|
|
* domain:
|
|
* type: string
|
|
* description: Domain of the block instance.
|
|
* example: "mastodon.social"
|
|
* comment:
|
|
* type: string
|
|
* description: Comment of the block instance.
|
|
* example: "mastodon.social"
|
|
* severity:
|
|
* type: string
|
|
* description: Severity of the block instance.
|
|
* example: "mastodon.social"
|
|
*
|
|
*/
|
|
app.use('/api/detail/:instance', async (req, res) => {
|
|
if (req.params.instance && req.params.instance.length > 0) {
|
|
const result = await client.search({
|
|
index: constant.index,
|
|
size: 1,
|
|
query: {
|
|
term: {
|
|
instance: clean(req.params.instance)
|
|
}
|
|
}
|
|
})
|
|
const instances = result.hits?.hits?.length > 0 ? result.hits.hits : []
|
|
res.json(instances.length > 0 && instances[0]._source.blocks && instances[0]._source.blocks.length > 0 ?
|
|
{
|
|
blocks: instances[0]._source.blocks.map(instance => ({ domain: instance.domain, comment: instance.comment, severity: instance.severity })),
|
|
last: instances[0]._source.last,
|
|
instance: instances[0]._source.instance,
|
|
nodeinfo: instances[0]._source.nodeinfo,
|
|
api: instances[0]._source.api,
|
|
took: result.took
|
|
} : [])
|
|
} else {
|
|
res.status(404).end()
|
|
}
|
|
})
|
|
/**
|
|
* @swagger
|
|
* /api/detail_api/{instance}:
|
|
* get:
|
|
* summary: Search result of detail's api intance.
|
|
* description: Retrieve a result of detail's api instance.
|
|
* parameters:
|
|
* - in: path
|
|
* name: instance
|
|
* required: true
|
|
* description: String to detail of the instance
|
|
* schema:
|
|
* type: string
|
|
* responses:
|
|
* 200:
|
|
* description: Detail of the api instance.
|
|
* content:
|
|
* application/json:
|
|
* schema:
|
|
* type: object
|
|
* properties:
|
|
* data:
|
|
* type: object
|
|
*/
|
|
app.use('/api/detail_api/:instance', async (req, res) => {
|
|
if (req.params.instance && req.params.instance.length > 0) {
|
|
const result = await client.search({
|
|
index: constant.index,
|
|
size: 1,
|
|
query: {
|
|
term: {
|
|
instance: clean(req.params.instance)
|
|
}
|
|
}
|
|
})
|
|
const instances = result.hits?.hits?.length > 0 ? result.hits.hits : []
|
|
res.json(instances.length > 0 ? instances[0]._source.api : {})
|
|
} else {
|
|
res.status(404).end()
|
|
}
|
|
})
|
|
/**
|
|
* @swagger
|
|
* /api/detail_nodeinfo/{instance}:
|
|
* get:
|
|
* summary: Search result of detail's nodeinfo intance.
|
|
* description: Retrieve a result of detail's nodeinfo instance.
|
|
* parameters:
|
|
* - in: path
|
|
* name: instance
|
|
* required: true
|
|
* description: String to detail of the instance
|
|
* schema:
|
|
* type: string
|
|
* responses:
|
|
* 200:
|
|
* description: Detail of the nodeinfo instance.
|
|
* content:
|
|
* application/json:
|
|
* schema:
|
|
* type: object
|
|
* properties:
|
|
* data:
|
|
* type: object
|
|
*/
|
|
app.use('/api/detail_nodeinfo/:instance', async (req, res) => {
|
|
if (req.params.instance && req.params.instance.length > 0) {
|
|
const result = await client.search({
|
|
index: constant.index,
|
|
size: 1,
|
|
query: {
|
|
term: {
|
|
instance: clean(req.params.instance)
|
|
}
|
|
}
|
|
})
|
|
const instances = result.hits?.hits?.length > 0 ? result.hits.hits : []
|
|
res.json(instances.length > 0 ? instances[0]._source.nodeinfo : {})
|
|
} else {
|
|
res.status(404).end()
|
|
}
|
|
})
|
|
/**
|
|
* @swagger
|
|
* /api/block_count/{instance}:
|
|
* get:
|
|
* summary: Retrieve count of fediblocked instances.
|
|
* description: Retrieve a number of total fediblocked instances.
|
|
* parameters:
|
|
* - in: path
|
|
* name: instance
|
|
* required: true
|
|
* description: String to search fediblocks of the instance
|
|
* schema:
|
|
* type: string
|
|
* responses:
|
|
* 200:
|
|
* description: A object with block_count parameter.
|
|
* content:
|
|
* application/json:
|
|
* schema:
|
|
* type: object
|
|
* properties:
|
|
* data:
|
|
* type: array
|
|
* items:
|
|
* type: object
|
|
* properties:
|
|
* instance:
|
|
* type: string
|
|
* description: Instance of the block.
|
|
* example: mastodon.social
|
|
* comment:
|
|
* type: string
|
|
* description: Comment about the block.
|
|
* example: spam
|
|
*/
|
|
app.use('/api/block_count/:instance', async (req, res) => {
|
|
if (req.params.instance && req.params.instance.length > 0) {
|
|
let block_count = 0
|
|
const result = await client.search({
|
|
index: constant.index,
|
|
size: 9999,
|
|
query: {
|
|
nested: {
|
|
path: 'blocks',
|
|
query: {
|
|
term: {
|
|
'blocks.domain': clean(req.params.instance)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}, { asStream: true, meta: false }),
|
|
instancescomment = [],
|
|
pipeline = chain([
|
|
parser(),
|
|
pick({ filter: 'hits.hits' }),
|
|
streamArray(),
|
|
data => ({ instance: data.value._source.instance, comment: data.value._source.blocks.find(block => block.domain === clean(req.params.instance)).comment })
|
|
])
|
|
result.pipe(pipeline).pipe(res, { end: true })
|
|
} else {
|
|
res.status(404).end()
|
|
}
|
|
})
|
|
/**
|
|
* @swagger
|
|
* /api/download_index:
|
|
* get:
|
|
* summary: Retrieve all content of ElasticSearch index.
|
|
* description: Retrieve all content of ElasticSearch index.
|
|
* responses:
|
|
* 200:
|
|
* description: A file compressed with gzip.
|
|
* content:
|
|
* application/gzip:
|
|
*/
|
|
app.use('/api/download_index', async (req, res) => {
|
|
try {
|
|
res.setHeader('Content-Type', 'application/gzip')
|
|
res.setHeader('Content-disposition', 'attachment; filename=fediblock-index.jsonl.gz')
|
|
const result = await client.search({
|
|
index: constant.index,
|
|
size: 9999,
|
|
query: {
|
|
match_all: {}
|
|
}
|
|
}, { asStream: true, meta: false }),
|
|
pipeline = chain([
|
|
parser(),
|
|
pick({ filter: 'hits.hits' }),
|
|
streamArray(),
|
|
data => JSON.stringify(data.value._source) + '\n',
|
|
zlib.createGzip()
|
|
])
|
|
result.pipe(pipeline).pipe(res)
|
|
} catch (e) {
|
|
console.error(e)
|
|
res.status(404).end()
|
|
}
|
|
})
|
|
}
|