adding initial support for nodeinfo

This commit is contained in:
Rigel Kent 2018-07-21 23:00:25 +02:00 committed by Chocobozzz
parent 4278710d5b
commit 3f6d68d967
7 changed files with 246 additions and 16 deletions

View File

@ -16,7 +16,7 @@ import { VideoModel } from '../../models/video/video'
import { VideoChannelModel } from '../../models/video/video-channel'
import { VideoCommentModel } from '../../models/video/video-comment'
import { VideoShareModel } from '../../models/video/video-share'
import { cacheRoute } from '../../middlewares/cache'
import { cache } from '../../middlewares/cache'
import { activityPubResponse } from './utils'
import { AccountVideoRateModel } from '../../models/account/account-video-rate'
import {
@ -25,7 +25,6 @@ import {
getVideoLikesActivityPubUrl,
getVideoSharesActivityPubUrl
} from '../../lib/activitypub'
import { VideoCaption } from '../../../shared/models/videos/video-caption.model'
import { VideoCaptionModel } from '../../models/video/video-caption'
const activityPubClientRouter = express.Router()
@ -44,7 +43,7 @@ activityPubClientRouter.get('/accounts?/:name/following',
)
activityPubClientRouter.get('/videos/watch/:id',
executeIfActivityPub(asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS))),
executeIfActivityPub(asyncMiddleware(cache(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS))),
executeIfActivityPub(asyncMiddleware(videosGetValidator)),
executeIfActivityPub(asyncMiddleware(videoController))
)

View File

@ -5,7 +5,7 @@ import { asyncMiddleware, setDefaultSort, videoCommentsFeedsValidator, videoFeed
import { VideoModel } from '../models/video/video'
import * as Feed from 'pfeed'
import { AccountModel } from '../models/account/account'
import { cacheRoute } from '../middlewares/cache'
import { cache } from '../middlewares/cache'
import { VideoChannelModel } from '../models/video/video-channel'
import { VideoCommentModel } from '../models/video/video-comment'
import { buildNSFWFilter } from '../helpers/express-utils'
@ -13,7 +13,7 @@ import { buildNSFWFilter } from '../helpers/express-utils'
const feedsRouter = express.Router()
feedsRouter.get('/feeds/video-comments.:format',
asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.FEEDS)),
asyncMiddleware(cache(ROUTE_CACHE_LIFETIME.FEEDS)),
asyncMiddleware(videoCommentsFeedsValidator),
asyncMiddleware(generateVideoCommentsFeed)
)
@ -21,7 +21,7 @@ feedsRouter.get('/feeds/video-comments.:format',
feedsRouter.get('/feeds/videos.:format',
videosSortValidator,
setDefaultSort,
asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.FEEDS)),
asyncMiddleware(cache(ROUTE_CACHE_LIFETIME.FEEDS)),
asyncMiddleware(videoFeedsValidator),
asyncMiddleware(generateVideoFeed)
)

View File

@ -1,11 +1,16 @@
import * as cors from 'cors'
import * as express from 'express'
import { CONFIG, STATIC_DOWNLOAD_PATHS, STATIC_MAX_AGE, STATIC_PATHS } from '../initializers'
import { CONFIG, STATIC_DOWNLOAD_PATHS, STATIC_MAX_AGE, STATIC_PATHS, ROUTE_CACHE_LIFETIME } from '../initializers'
import { VideosPreviewCache } from '../lib/cache'
import { cache } from '../middlewares/cache'
import { asyncMiddleware, videosGetValidator } from '../middlewares'
import { VideoModel } from '../models/video/video'
import { VideosCaptionCache } from '../lib/cache/videos-caption-cache'
import { UserModel } from '../models/account/user'
import { VideoCommentModel } from '../models/video/video-comment'
import { HttpNodeinfoDiasporaSoftwareNsSchema20 } from '../models/nodeinfo'
const packageJSON = require('../../../package.json')
const staticRouter = express.Router()
staticRouter.use(cors())
@ -65,10 +70,32 @@ staticRouter.use(
)
// robots.txt service
staticRouter.get('/robots.txt', (req: express.Request, res: express.Response) => {
res.type('text/plain')
return res.send(CONFIG.INSTANCE.ROBOTS)
})
staticRouter.get('/robots.txt',
asyncMiddleware(cache(ROUTE_CACHE_LIFETIME.ROBOTS)),
(_, res: express.Response) => {
res.type('text/plain')
return res.send(CONFIG.INSTANCE.ROBOTS)
}
)
// nodeinfo service
staticRouter.use('/.well-known/nodeinfo',
asyncMiddleware(cache(ROUTE_CACHE_LIFETIME.NODEINFO)),
(_, res: express.Response) => {
return res.json({
links: [
{
rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0',
href: CONFIG.WEBSERVER.URL + '/nodeinfo/2.0.json'
}
]
})
}
)
staticRouter.use('/nodeinfo/:version.json',
asyncMiddleware(cache(ROUTE_CACHE_LIFETIME.NODEINFO)),
asyncMiddleware(generateNodeinfo)
)
// ---------------------------------------------------------------------------
@ -95,6 +122,54 @@ async function getVideoCaption (req: express.Request, res: express.Response) {
return res.sendFile(path, { maxAge: STATIC_MAX_AGE })
}
async function generateNodeinfo (req: express.Request, res: express.Response, next: express.NextFunction) {
const { totalVideos } = await VideoModel.getStats()
const { totalLocalVideoComments } = await VideoCommentModel.getStats()
const { totalUsers } = await UserModel.getStats()
let json = {}
if (req.params.version && (req.params.version === '2.0')) {
json = {
version: '2.0',
software: {
name: 'peertube',
version: packageJSON.version
},
protocols: [
'activitypub'
],
services: {
inbound: [],
outbound: [
'atom1.0',
'rss2.0'
]
},
openRegistrations: CONFIG.SIGNUP.ENABLED,
usage: {
users: {
total: totalUsers
},
localPosts: totalVideos,
localComments: totalLocalVideoComments
},
metadata: {
taxonomy: {
postsName: 'Videos'
},
nodeName: CONFIG.INSTANCE.NAME,
nodeDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION
}
} as HttpNodeinfoDiasporaSoftwareNsSchema20
res.set('Content-Type', 'application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.0#; charset=utf-8')
} else {
json = { error: 'Nodeinfo schema version not handled' }
res.status(404)
}
return res.end(JSON.stringify(json))
}
async function downloadTorrent (req: express.Request, res: express.Response, next: express.NextFunction) {
const { video, videoFile } = getVideoAndFile(req, res)
if (!videoFile) return res.status(404).end()

View File

@ -104,6 +104,36 @@ function computeResolutionsToTranscode (videoFileHeight: number) {
return resolutionsEnabled
}
const timeTable = {
ms: 1,
second: 1000,
minute: 60000,
hour: 3600000,
day: 3600000 * 24,
week: 3600000 * 24 * 7,
month: 3600000 * 24 * 30
}
export function parseDuration (duration: number | string, defaultDuration: number): number {
if (typeof duration === 'number') return duration
if (typeof duration === 'string') {
const split = duration.match(/^([\d\.,]+)\s?(\w+)$/)
if (split.length === 3) {
const len = parseFloat(split[1])
let unit = split[2].replace(/s$/i,'').toLowerCase()
if (unit === 'm') {
unit = 'ms'
}
return (len || 1) * (timeTable[unit] || 0)
}
}
logger.error('Duration could not be properly parsed, defaulting to ' + defaultDuration)
return defaultDuration
}
function resetSequelizeInstance (instance: Model<any>, savedFields: object) {
Object.keys(savedFields).forEach(key => {
const value = savedFields[key]

View File

@ -46,9 +46,11 @@ const OAUTH_LIFETIME = {
}
const ROUTE_CACHE_LIFETIME = {
FEEDS: 1000 * 60 * 15, // 15 minutes
FEEDS: '15 minutes',
ROBOTS: '2 hours',
NODEINFO: '10 minutes',
ACTIVITY_PUB: {
VIDEOS: 1000 // 1 second, cache concurrent requests after a broadcast for example
VIDEOS: '1 second' // 1 second, cache concurrent requests after a broadcast for example
}
}

View File

@ -1,5 +1,6 @@
import * as express from 'express'
import * as AsyncLock from 'async-lock'
import { parseDuration } from '../helpers/utils'
import { Redis } from '../lib/redis'
import { logger } from '../helpers/logger'
@ -20,7 +21,7 @@ function cacheRoute (lifetime: number) {
res.send = (body) => {
if (res.statusCode >= 200 && res.statusCode < 400) {
const contentType = res.getHeader('content-type').toString()
const contentType = res.get('content-type')
Redis.Instance.setCachedRoute(req, body, lifetime, contentType, res.statusCode)
.then(() => done())
.catch(err => {
@ -35,7 +36,7 @@ function cacheRoute (lifetime: number) {
return next()
}
if (cached.contentType) res.contentType(cached.contentType)
if (cached.contentType) res.set('content-type', cached.contentType)
if (cached.statusCode) {
const statusCode = parseInt(cached.statusCode, 10)
@ -50,8 +51,14 @@ function cacheRoute (lifetime: number) {
}
}
const cache = (duration: number | string) => {
const _lifetime = parseDuration(duration, 3600000)
return cacheRoute(_lifetime)
}
// ---------------------------------------------------------------------------
export {
cacheRoute
cacheRoute,
cache
}

117
server/models/nodeinfo/index.d.ts vendored Normal file
View File

@ -0,0 +1,117 @@
/**
* NodeInfo schema version 2.0.
*/
export interface HttpNodeinfoDiasporaSoftwareNsSchema20 {
/**
* The schema version, must be 2.0.
*/
version: '2.0'
/**
* Metadata about server software in use.
*/
software: {
/**
* The canonical name of this server software.
*/
name: string
/**
* The version of this server software.
*/
version: string
}
/**
* The protocols supported on this server.
*/
protocols: (
| 'activitypub'
| 'buddycloud'
| 'dfrn'
| 'diaspora'
| 'libertree'
| 'ostatus'
| 'pumpio'
| 'tent'
| 'xmpp'
| 'zot')[]
/**
* The third party sites this server can connect to via their application API.
*/
services: {
/**
* The third party sites this server can retrieve messages from for combined display with regular traffic.
*/
inbound: ('atom1.0' | 'gnusocial' | 'imap' | 'pnut' | 'pop3' | 'pumpio' | 'rss2.0' | 'twitter')[]
/**
* The third party sites this server can publish messages to on the behalf of a user.
*/
outbound: (
| 'atom1.0'
| 'blogger'
| 'buddycloud'
| 'diaspora'
| 'dreamwidth'
| 'drupal'
| 'facebook'
| 'friendica'
| 'gnusocial'
| 'google'
| 'insanejournal'
| 'libertree'
| 'linkedin'
| 'livejournal'
| 'mediagoblin'
| 'myspace'
| 'pinterest'
| 'pnut'
| 'posterous'
| 'pumpio'
| 'redmatrix'
| 'rss2.0'
| 'smtp'
| 'tent'
| 'tumblr'
| 'twitter'
| 'wordpress'
| 'xmpp')[]
}
/**
* Whether this server allows open self-registration.
*/
openRegistrations: boolean
/**
* Usage statistics for this server.
*/
usage: {
/**
* statistics about the users of this server.
*/
users: {
/**
* The total amount of on this server registered users.
*/
total?: number
/**
* The amount of users that signed in at least once in the last 180 days.
*/
activeHalfyear?: number
/**
* The amount of users that signed in at least once in the last 30 days.
*/
activeMonth?: number
};
/**
* The amount of posts that were made by users that are registered on this server.
*/
localPosts?: number
/**
* The amount of comments that were made by users that are registered on this server.
*/
localComments?: number
}
/**
* Free form key value pairs for software specific values. Clients should not rely on any specific key present.
*/
metadata: {
[k: string]: any
}
}