From 244e76a552ef05a5067134b1065d26dd89246d8c Mon Sep 17 00:00:00 2001 From: Rigel Kent Date: Tue, 17 Apr 2018 00:49:04 +0200 Subject: feature: initial syndication feeds support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Provides rss 2.0, atom 1.0 and json 1.0 feeds for videos (instance and account-wide) on listings and video-watch views. * still lacks redis caching * still lacks lastBuildDate support * still lacks channel-wide support * still lacks semantic annotation (for licenses, NSFW warnings, etc.) * still lacks love ( ˘ ³˘) * RSS: has MRSS support for torrent lists! * RSS: includes the first torrent in an enclosure * JSON: lists all torrents in the 'attachments' object * ATOM: lacking torrent listing support Advances #23 Partial implementation for the accountId generation in the client, which will need a hotfix to add a way to get the proper account id. --- server/models/video/video.ts | 166 +++++++++++++++++++++++++------------------ 1 file changed, 95 insertions(+), 71 deletions(-) (limited to 'server/models/video') diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 54fe54535..240a2b5a2 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts @@ -95,14 +95,15 @@ enum ScopeNames { } @Scopes({ - [ScopeNames.AVAILABLE_FOR_LIST]: (actorId: number, filter?: VideoFilter) => ({ - where: { - id: { - [Sequelize.Op.notIn]: Sequelize.literal( - '(SELECT "videoBlacklist"."videoId" FROM "videoBlacklist")' - ), - [ Sequelize.Op.in ]: Sequelize.literal( - '(' + + [ScopeNames.AVAILABLE_FOR_LIST]: (actorId: number, filter?: VideoFilter, withFiles?: boolean) => { + const query: IFindOptions = { + where: { + id: { + [Sequelize.Op.notIn]: Sequelize.literal( + '(SELECT "videoBlacklist"."videoId" FROM "videoBlacklist")' + ), + [ Sequelize.Op.in ]: Sequelize.literal( + '(' + 'SELECT "videoShare"."videoId" AS "id" FROM "videoShare" ' + 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' + 'WHERE "actorFollow"."actorId" = ' + parseInt(actorId.toString(), 10) + @@ -113,45 +114,55 @@ enum ScopeNames { 'INNER JOIN "actor" ON "account"."actorId" = "actor"."id" ' + 'LEFT JOIN "actorFollow" ON "actorFollow"."targetActorId" = "actor"."id" ' + 'WHERE "actor"."serverId" IS NULL OR "actorFollow"."actorId" = ' + parseInt(actorId.toString(), 10) + - ')' - ) + ')' + ) + }, + privacy: VideoPrivacy.PUBLIC }, - privacy: VideoPrivacy.PUBLIC - }, - include: [ - { - attributes: [ 'name', 'description' ], - model: VideoChannelModel.unscoped(), - required: true, - include: [ - { - attributes: [ 'name' ], - model: AccountModel.unscoped(), - required: true, - include: [ - { - attributes: [ 'preferredUsername', 'url', 'serverId' ], - model: ActorModel.unscoped(), - required: true, - where: VideoModel.buildActorWhereWithFilter(filter), - include: [ - { - attributes: [ 'host' ], - model: ServerModel.unscoped(), - required: false - }, - { - model: AvatarModel.unscoped(), - required: false - } - ] - } - ] - } - ] - } - ] - }), + include: [ + { + attributes: [ 'name', 'description' ], + model: VideoChannelModel.unscoped(), + required: true, + include: [ + { + attributes: [ 'name' ], + model: AccountModel.unscoped(), + required: true, + include: [ + { + attributes: [ 'preferredUsername', 'url', 'serverId', 'avatarId' ], + model: ActorModel.unscoped(), + required: true, + where: VideoModel.buildActorWhereWithFilter(filter), + include: [ + { + attributes: [ 'host' ], + model: ServerModel.unscoped(), + required: false + }, + { + model: AvatarModel.unscoped(), + required: false + } + ] + } + ] + } + ] + } + ] + } + + if (withFiles === true) { + query.include.push({ + model: VideoFileModel.unscoped(), + required: true + }) + } + + return query + }, [ScopeNames.WITH_ACCOUNT_DETAILS]: { include: [ { @@ -629,8 +640,8 @@ export class VideoModel extends Model { }) } - static listUserVideosForApi (userId: number, start: number, count: number, sort: string) { - const query = { + static listUserVideosForApi (userId: number, start: number, count: number, sort: string, withFiles = false) { + const query: IFindOptions = { offset: start, limit: count, order: getSort(sort), @@ -651,6 +662,13 @@ export class VideoModel extends Model { ] } + if (withFiles === true) { + query.include.push({ + model: VideoFileModel.unscoped(), + required: true + }) + } + return VideoModel.findAndCountAll(query).then(({ rows, count }) => { return { data: rows, @@ -659,7 +677,7 @@ export class VideoModel extends Model { }) } - static async listForApi (start: number, count: number, sort: string, filter?: VideoFilter) { + static async listForApi (start: number, count: number, sort: string, filter?: VideoFilter, withFiles = false) { const query = { offset: start, limit: count, @@ -668,7 +686,7 @@ export class VideoModel extends Model { const serverActor = await getServerActor() - return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST, serverActor.id, filter ] }) + return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST, serverActor.id, filter, withFiles ] }) .findAndCountAll(query) .then(({ rows, count }) => { return { @@ -707,7 +725,8 @@ export class VideoModel extends Model { const serverActor = await getServerActor() return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST, serverActor.id ] }) - .findAndCountAll(query).then(({ rows, count }) => { + .findAndCountAll(query) + .then(({ rows, count }) => { return { data: rows, total: count @@ -1006,29 +1025,34 @@ export class VideoModel extends Model { } // Format and sort video files + detailsJson.files = this.getFormattedVideoFilesJSON() + + return Object.assign(formattedJson, detailsJson) + } + + getFormattedVideoFilesJSON (): VideoFile[] { const { baseUrlHttp, baseUrlWs } = this.getBaseUrls() - detailsJson.files = this.VideoFiles - .map(videoFile => { - let resolutionLabel = videoFile.resolution + 'p' - return { - resolution: { - id: videoFile.resolution, - label: resolutionLabel - }, - magnetUri: this.generateMagnetUri(videoFile, baseUrlHttp, baseUrlWs), - size: videoFile.size, - torrentUrl: this.getTorrentUrl(videoFile, baseUrlHttp), - fileUrl: this.getVideoFileUrl(videoFile, baseUrlHttp) - } as VideoFile - }) - .sort((a, b) => { - if (a.resolution.id < b.resolution.id) return 1 - if (a.resolution.id === b.resolution.id) return 0 - return -1 - }) + return this.VideoFiles + .map(videoFile => { + let resolutionLabel = videoFile.resolution + 'p' - return Object.assign(formattedJson, detailsJson) + return { + resolution: { + id: videoFile.resolution, + label: resolutionLabel + }, + magnetUri: this.generateMagnetUri(videoFile, baseUrlHttp, baseUrlWs), + size: videoFile.size, + torrentUrl: this.getTorrentUrl(videoFile, baseUrlHttp), + fileUrl: this.getVideoFileUrl(videoFile, baseUrlHttp) + } as VideoFile + }) + .sort((a, b) => { + if (a.resolution.id < b.resolution.id) return 1 + if (a.resolution.id === b.resolution.id) return 0 + return -1 + }) } toActivityPubObject (): VideoTorrentObject { -- cgit v1.2.3