aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models/video
diff options
context:
space:
mode:
authorRigel Kent <sendmemail@rigelk.eu>2018-04-17 00:49:04 +0200
committerRigel <sendmemail@rigelk.eu>2018-04-17 01:09:06 +0200
commit244e76a552ef05a5067134b1065d26dd89246d8c (patch)
treea15fcd52bce99797fc9366572fea62a7a44aaabe /server/models/video
parentc36d5a6b98056ef7fec3db43fbee880ee7332dcf (diff)
downloadPeerTube-244e76a552ef05a5067134b1065d26dd89246d8c.tar.gz
PeerTube-244e76a552ef05a5067134b1065d26dd89246d8c.tar.zst
PeerTube-244e76a552ef05a5067134b1065d26dd89246d8c.zip
feature: initial syndication feeds support
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.
Diffstat (limited to 'server/models/video')
-rw-r--r--server/models/video/video.ts166
1 files changed, 95 insertions, 71 deletions
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 {
95} 95}
96 96
97@Scopes({ 97@Scopes({
98 [ScopeNames.AVAILABLE_FOR_LIST]: (actorId: number, filter?: VideoFilter) => ({ 98 [ScopeNames.AVAILABLE_FOR_LIST]: (actorId: number, filter?: VideoFilter, withFiles?: boolean) => {
99 where: { 99 const query: IFindOptions<VideoModel> = {
100 id: { 100 where: {
101 [Sequelize.Op.notIn]: Sequelize.literal( 101 id: {
102 '(SELECT "videoBlacklist"."videoId" FROM "videoBlacklist")' 102 [Sequelize.Op.notIn]: Sequelize.literal(
103 ), 103 '(SELECT "videoBlacklist"."videoId" FROM "videoBlacklist")'
104 [ Sequelize.Op.in ]: Sequelize.literal( 104 ),
105 '(' + 105 [ Sequelize.Op.in ]: Sequelize.literal(
106 '(' +
106 'SELECT "videoShare"."videoId" AS "id" FROM "videoShare" ' + 107 'SELECT "videoShare"."videoId" AS "id" FROM "videoShare" ' +
107 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' + 108 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' +
108 'WHERE "actorFollow"."actorId" = ' + parseInt(actorId.toString(), 10) + 109 'WHERE "actorFollow"."actorId" = ' + parseInt(actorId.toString(), 10) +
@@ -113,45 +114,55 @@ enum ScopeNames {
113 'INNER JOIN "actor" ON "account"."actorId" = "actor"."id" ' + 114 'INNER JOIN "actor" ON "account"."actorId" = "actor"."id" ' +
114 'LEFT JOIN "actorFollow" ON "actorFollow"."targetActorId" = "actor"."id" ' + 115 'LEFT JOIN "actorFollow" ON "actorFollow"."targetActorId" = "actor"."id" ' +
115 'WHERE "actor"."serverId" IS NULL OR "actorFollow"."actorId" = ' + parseInt(actorId.toString(), 10) + 116 'WHERE "actor"."serverId" IS NULL OR "actorFollow"."actorId" = ' + parseInt(actorId.toString(), 10) +
116 ')' 117 ')'
117 ) 118 )
119 },
120 privacy: VideoPrivacy.PUBLIC
118 }, 121 },
119 privacy: VideoPrivacy.PUBLIC 122 include: [
120 }, 123 {
121 include: [ 124 attributes: [ 'name', 'description' ],
122 { 125 model: VideoChannelModel.unscoped(),
123 attributes: [ 'name', 'description' ], 126 required: true,
124 model: VideoChannelModel.unscoped(), 127 include: [
125 required: true, 128 {
126 include: [ 129 attributes: [ 'name' ],
127 { 130 model: AccountModel.unscoped(),
128 attributes: [ 'name' ], 131 required: true,
129 model: AccountModel.unscoped(), 132 include: [
130 required: true, 133 {
131 include: [ 134 attributes: [ 'preferredUsername', 'url', 'serverId', 'avatarId' ],
132 { 135 model: ActorModel.unscoped(),
133 attributes: [ 'preferredUsername', 'url', 'serverId' ], 136 required: true,
134 model: ActorModel.unscoped(), 137 where: VideoModel.buildActorWhereWithFilter(filter),
135 required: true, 138 include: [
136 where: VideoModel.buildActorWhereWithFilter(filter), 139 {
137 include: [ 140 attributes: [ 'host' ],
138 { 141 model: ServerModel.unscoped(),
139 attributes: [ 'host' ], 142 required: false
140 model: ServerModel.unscoped(), 143 },
141 required: false 144 {
142 }, 145 model: AvatarModel.unscoped(),
143 { 146 required: false
144 model: AvatarModel.unscoped(), 147 }
145 required: false 148 ]
146 } 149 }
147 ] 150 ]
148 } 151 }
149 ] 152 ]
150 } 153 }
151 ] 154 ]
152 } 155 }
153 ] 156
154 }), 157 if (withFiles === true) {
158 query.include.push({
159 model: VideoFileModel.unscoped(),
160 required: true
161 })
162 }
163
164 return query
165 },
155 [ScopeNames.WITH_ACCOUNT_DETAILS]: { 166 [ScopeNames.WITH_ACCOUNT_DETAILS]: {
156 include: [ 167 include: [
157 { 168 {
@@ -629,8 +640,8 @@ export class VideoModel extends Model<VideoModel> {
629 }) 640 })
630 } 641 }
631 642
632 static listUserVideosForApi (userId: number, start: number, count: number, sort: string) { 643 static listUserVideosForApi (userId: number, start: number, count: number, sort: string, withFiles = false) {
633 const query = { 644 const query: IFindOptions<VideoModel> = {
634 offset: start, 645 offset: start,
635 limit: count, 646 limit: count,
636 order: getSort(sort), 647 order: getSort(sort),
@@ -651,6 +662,13 @@ export class VideoModel extends Model<VideoModel> {
651 ] 662 ]
652 } 663 }
653 664
665 if (withFiles === true) {
666 query.include.push({
667 model: VideoFileModel.unscoped(),
668 required: true
669 })
670 }
671
654 return VideoModel.findAndCountAll(query).then(({ rows, count }) => { 672 return VideoModel.findAndCountAll(query).then(({ rows, count }) => {
655 return { 673 return {
656 data: rows, 674 data: rows,
@@ -659,7 +677,7 @@ export class VideoModel extends Model<VideoModel> {
659 }) 677 })
660 } 678 }
661 679
662 static async listForApi (start: number, count: number, sort: string, filter?: VideoFilter) { 680 static async listForApi (start: number, count: number, sort: string, filter?: VideoFilter, withFiles = false) {
663 const query = { 681 const query = {
664 offset: start, 682 offset: start,
665 limit: count, 683 limit: count,
@@ -668,7 +686,7 @@ export class VideoModel extends Model<VideoModel> {
668 686
669 const serverActor = await getServerActor() 687 const serverActor = await getServerActor()
670 688
671 return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST, serverActor.id, filter ] }) 689 return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST, serverActor.id, filter, withFiles ] })
672 .findAndCountAll(query) 690 .findAndCountAll(query)
673 .then(({ rows, count }) => { 691 .then(({ rows, count }) => {
674 return { 692 return {
@@ -707,7 +725,8 @@ export class VideoModel extends Model<VideoModel> {
707 const serverActor = await getServerActor() 725 const serverActor = await getServerActor()
708 726
709 return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST, serverActor.id ] }) 727 return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST, serverActor.id ] })
710 .findAndCountAll(query).then(({ rows, count }) => { 728 .findAndCountAll(query)
729 .then(({ rows, count }) => {
711 return { 730 return {
712 data: rows, 731 data: rows,
713 total: count 732 total: count
@@ -1006,29 +1025,34 @@ export class VideoModel extends Model<VideoModel> {
1006 } 1025 }
1007 1026
1008 // Format and sort video files 1027 // Format and sort video files
1028 detailsJson.files = this.getFormattedVideoFilesJSON()
1029
1030 return Object.assign(formattedJson, detailsJson)
1031 }
1032
1033 getFormattedVideoFilesJSON (): VideoFile[] {
1009 const { baseUrlHttp, baseUrlWs } = this.getBaseUrls() 1034 const { baseUrlHttp, baseUrlWs } = this.getBaseUrls()
1010 detailsJson.files = this.VideoFiles
1011 .map(videoFile => {
1012 let resolutionLabel = videoFile.resolution + 'p'
1013 1035
1014 return { 1036 return this.VideoFiles
1015 resolution: { 1037 .map(videoFile => {
1016 id: videoFile.resolution, 1038 let resolutionLabel = videoFile.resolution + 'p'
1017 label: resolutionLabel
1018 },
1019 magnetUri: this.generateMagnetUri(videoFile, baseUrlHttp, baseUrlWs),
1020 size: videoFile.size,
1021 torrentUrl: this.getTorrentUrl(videoFile, baseUrlHttp),
1022 fileUrl: this.getVideoFileUrl(videoFile, baseUrlHttp)
1023 } as VideoFile
1024 })
1025 .sort((a, b) => {
1026 if (a.resolution.id < b.resolution.id) return 1
1027 if (a.resolution.id === b.resolution.id) return 0
1028 return -1
1029 })
1030 1039
1031 return Object.assign(formattedJson, detailsJson) 1040 return {
1041 resolution: {
1042 id: videoFile.resolution,
1043 label: resolutionLabel
1044 },
1045 magnetUri: this.generateMagnetUri(videoFile, baseUrlHttp, baseUrlWs),
1046 size: videoFile.size,
1047 torrentUrl: this.getTorrentUrl(videoFile, baseUrlHttp),
1048 fileUrl: this.getVideoFileUrl(videoFile, baseUrlHttp)
1049 } as VideoFile
1050 })
1051 .sort((a, b) => {
1052 if (a.resolution.id < b.resolution.id) return 1
1053 if (a.resolution.id === b.resolution.id) return 0
1054 return -1
1055 })
1032 } 1056 }
1033 1057
1034 toActivityPubObject (): VideoTorrentObject { 1058 toActivityPubObject (): VideoTorrentObject {