X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Fcontrollers%2Fapi%2Fvideos%2Findex.ts;h=fc1bcc73d4e1ae1e6017d18fa990f2625fe27ea0;hb=ad5db1044c8599eaaaa2a578b350777ae996b068;hp=47ab098ef02bdba670a8c040737d5cacd78da3f6;hpb=76148b27f7501bac061992136852be4303370c8d;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index 47ab098ef..fc1bcc73d 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts @@ -1,20 +1,21 @@ -import * as express from 'express' -import toInt from 'validator/lib/toInt' -import { LiveManager } from '@server/lib/live-manager' +import express from 'express' +import { pickCommonVideoQuery } from '@server/helpers/query' +import { doJSONRequest } from '@server/helpers/requests' +import { VideoViews } from '@server/lib/video-views' +import { openapiOperationDoc } from '@server/middlewares/doc' import { getServerActor } from '@server/models/application/application' -import { VideosCommonQuery } from '../../../../shared' -import { HttpStatusCode } from '../../../../shared/core-utils/miscs' +import { guessAdditionalAttributesFromQuery } from '@server/models/video/formatter/video-format-utils' +import { MVideoAccountLight } from '@server/types/models' +import { HttpStatusCode } from '../../../../shared/models' import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils' import { logger } from '../../../helpers/logger' import { getFormattedObjects } from '../../../helpers/utils' -import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../initializers/constants' +import { REMOTE_SCHEME, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../initializers/constants' import { sequelizeTypescript } from '../../../initializers/database' import { sendView } from '../../../lib/activitypub/send/send-view' -import { fetchRemoteVideoDescription } from '../../../lib/activitypub/videos' import { JobQueue } from '../../../lib/job-queue' import { Hooks } from '../../../lib/plugins/hooks' -import { Redis } from '../../../lib/redis' import { asyncMiddleware, asyncRetryTransactionMiddleware, @@ -25,21 +26,21 @@ import { paginationValidator, setDefaultPagination, setDefaultVideosSort, - videoFileMetadataGetValidator, videosCustomGetValidator, videosGetValidator, videosRemoveValidator, videosSortValidator } from '../../../middlewares' import { VideoModel } from '../../../models/video/video' -import { VideoFileModel } from '../../../models/video/video-file' import { blacklistRouter } from './blacklist' import { videoCaptionsRouter } from './captions' import { videoCommentRouter } from './comment' +import { filesRouter } from './files' import { videoImportsRouter } from './import' import { liveRouter } from './live' import { ownershipVideoRouter } from './ownership' import { rateVideoRouter } from './rate' +import { transcodingRouter } from './transcoding' import { updateRouter } from './update' import { uploadRouter } from './upload' import { watchingRouter } from './watching' @@ -57,13 +58,28 @@ videosRouter.use('/', watchingRouter) videosRouter.use('/', liveRouter) videosRouter.use('/', uploadRouter) videosRouter.use('/', updateRouter) +videosRouter.use('/', filesRouter) +videosRouter.use('/', transcodingRouter) -videosRouter.get('/categories', listVideoCategories) -videosRouter.get('/licences', listVideoLicences) -videosRouter.get('/languages', listVideoLanguages) -videosRouter.get('/privacies', listVideoPrivacies) +videosRouter.get('/categories', + openapiOperationDoc({ operationId: 'getCategories' }), + listVideoCategories +) +videosRouter.get('/licences', + openapiOperationDoc({ operationId: 'getLicences' }), + listVideoLicences +) +videosRouter.get('/languages', + openapiOperationDoc({ operationId: 'getLanguages' }), + listVideoLanguages +) +videosRouter.get('/privacies', + openapiOperationDoc({ operationId: 'getPrivacies' }), + listVideoPrivacies +) videosRouter.get('/', + openapiOperationDoc({ operationId: 'getVideos' }), paginationValidator, videosSortValidator, setDefaultVideosSort, @@ -74,25 +90,25 @@ videosRouter.get('/', ) videosRouter.get('/:id/description', + openapiOperationDoc({ operationId: 'getVideoDesc' }), asyncMiddleware(videosGetValidator), asyncMiddleware(getVideoDescription) ) -videosRouter.get('/:id/metadata/:videoFileId', - asyncMiddleware(videoFileMetadataGetValidator), - asyncMiddleware(getVideoFileMetadata) -) videosRouter.get('/:id', + openapiOperationDoc({ operationId: 'getVideo' }), optionalAuthenticate, - asyncMiddleware(videosCustomGetValidator('only-video-with-rights')), + asyncMiddleware(videosCustomGetValidator('for-api')), asyncMiddleware(checkVideoFollowConstraints), - asyncMiddleware(getVideo) + getVideo ) videosRouter.post('/:id/views', - asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')), + openapiOperationDoc({ operationId: 'addView' }), + asyncMiddleware(videosCustomGetValidator('only-video')), asyncMiddleware(viewVideo) ) videosRouter.delete('/:id', + openapiOperationDoc({ operationId: 'delVideo' }), authenticate, asyncMiddleware(videosRemoveValidator), asyncRetryTransactionMiddleware(removeVideo) @@ -122,15 +138,8 @@ function listVideoPrivacies (_req: express.Request, res: express.Response) { res.json(VIDEO_PRIVACIES) } -async function getVideo (_req: express.Request, res: express.Response) { - // We need more attributes - const userId: number = res.locals.oauth ? res.locals.oauth.token.User.id : null - - const video = await Hooks.wrapPromiseFun( - VideoModel.loadForGetAPI, - { id: res.locals.onlyVideoWithRights.id, userId }, - 'filter:api.video.get.result' - ) +function getVideo (_req: express.Request, res: express.Response) { + const video = res.locals.videoAPI if (video.isOutdated()) { JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video', url: video.url } }) @@ -140,44 +149,17 @@ async function getVideo (_req: express.Request, res: express.Response) { } async function viewVideo (req: express.Request, res: express.Response) { - const immutableVideoAttrs = res.locals.onlyImmutableVideo + const video = res.locals.onlyVideo const ip = req.ip - const exists = await Redis.Instance.doesVideoIPViewExist(ip, immutableVideoAttrs.uuid) - if (exists) { - logger.debug('View for ip %s and video %s already exists.', ip, immutableVideoAttrs.uuid) - return res.status(HttpStatusCode.NO_CONTENT_204).end() - } + const success = await VideoViews.Instance.processView({ video, ip }) - const video = await VideoModel.load(immutableVideoAttrs.id) - - const promises: Promise[] = [ - Redis.Instance.setIPVideoView(ip, video.uuid, video.isLive) - ] - - let federateView = true - - // Increment our live manager - if (video.isLive && video.isOwned()) { - LiveManager.Instance.addViewTo(video.id) - - // Views of our local live will be sent by our live manager - federateView = false - } - - // Increment our video views cache counter - if (!video.isLive) { - promises.push(Redis.Instance.addVideoView(video.id)) - } - - if (federateView) { + if (success) { const serverActor = await getServerActor() - promises.push(sendView(serverActor, video, undefined)) - } + await sendView(serverActor, video, undefined) - await Promise.all(promises) - - Hooks.runAction('action:api.video.viewed', { video, ip }) + Hooks.runAction('action:api.video.viewed', { video: video, ip }) + } return res.status(HttpStatusCode.NO_CONTENT_204).end() } @@ -192,30 +174,20 @@ async function getVideoDescription (req: express.Request, res: express.Response) return res.json({ description }) } -async function getVideoFileMetadata (req: express.Request, res: express.Response) { - const videoFile = await VideoFileModel.loadWithMetadata(toInt(req.params.videoFileId)) - - return res.json(videoFile.metadata) -} - async function listVideos (req: express.Request, res: express.Response) { - const query = req.query as VideosCommonQuery + const serverActor = await getServerActor() + + const query = pickCommonVideoQuery(req.query) const countVideos = getCountVideos(req) const apiOptions = await Hooks.wrapObject({ - start: query.start, - count: query.count, - sort: query.sort, - includeLocalVideos: true, - categoryOneOf: query.categoryOneOf, - licenceOneOf: query.licenceOneOf, - languageOneOf: query.languageOneOf, - tagsOneOf: query.tagsOneOf, - tagsAllOf: query.tagsAllOf, + ...query, + + displayOnlyForFollower: { + actorId: serverActor.id, + orLocalVideos: true + }, nsfw: buildNSFWFilter(res, query.nsfw), - isLive: query.isLive, - filter: query.filter, - withFiles: false, user: res.locals.oauth ? res.locals.oauth.token.User : undefined, countVideos }, 'filter:api.videos.list.params') @@ -226,7 +198,7 @@ async function listVideos (req: express.Request, res: express.Response) { 'filter:api.videos.list.result' ) - return res.json(getFormattedObjects(resultList.data, resultList.total)) + return res.json(getFormattedObjects(resultList.data, resultList.total, guessAdditionalAttributesFromQuery(query))) } async function removeVideo (_req: express.Request, res: express.Response) { @@ -245,3 +217,15 @@ async function removeVideo (_req: express.Request, res: express.Response) { .status(HttpStatusCode.NO_CONTENT_204) .end() } + +// --------------------------------------------------------------------------- + +// FIXME: Should not exist, we rely on specific API +async function fetchRemoteVideoDescription (video: MVideoAccountLight) { + const host = video.VideoChannel.Account.Actor.Server.host + const path = video.getDescriptionAPIPath() + const url = REMOTE_SCHEME.HTTP + '://' + host + path + + const { body } = await doJSONRequest(url) + return body.description || '' +}