fediblock-instance/lib/apiswagger.js
ale 1fcadc150d
Some checks reported errors
continuous-integration/drone/push Build was killed
refactor blockcount
2024-11-15 20:50:56 +01:00

523 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'),
{ stringer } = require('stream-json/jsonl/Stringer'),
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) {
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 }),
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 }),
stringer()
])
res.once('data', data => '[' + data.toString())
res.on('finish', () => res.end(']'))
result.pipe(pipeline).pipe(res)
} 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()
}
})
}