aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models/video
diff options
context:
space:
mode:
authorChocobozzz <florian.bigard@gmail.com>2017-10-24 19:41:09 +0200
committerChocobozzz <florian.bigard@gmail.com>2017-10-26 09:11:38 +0200
commit72c7248b6fdcdb2175e726ff51b42e7555f2bd84 (patch)
tree1bfdee99dbe2392cc997edba8e314e2a8a401c72 /server/models/video
parent8113a93a0d9f31aa9e23702bbc31b8a76275ae22 (diff)
downloadPeerTube-72c7248b6fdcdb2175e726ff51b42e7555f2bd84.tar.gz
PeerTube-72c7248b6fdcdb2175e726ff51b42e7555f2bd84.tar.zst
PeerTube-72c7248b6fdcdb2175e726ff51b42e7555f2bd84.zip
Add video channels
Diffstat (limited to 'server/models/video')
-rw-r--r--server/models/video/author-interface.ts29
-rw-r--r--server/models/video/author.ts103
-rw-r--r--server/models/video/index.ts1
-rw-r--r--server/models/video/video-channel-interface.ts64
-rw-r--r--server/models/video/video-channel.ts349
-rw-r--r--server/models/video/video-interface.ts16
-rw-r--r--server/models/video/video.ts180
7 files changed, 675 insertions, 67 deletions
diff --git a/server/models/video/author-interface.ts b/server/models/video/author-interface.ts
index 52a00a1d3..fc69ff3c2 100644
--- a/server/models/video/author-interface.ts
+++ b/server/models/video/author-interface.ts
@@ -2,31 +2,44 @@ import * as Sequelize from 'sequelize'
2import * as Promise from 'bluebird' 2import * as Promise from 'bluebird'
3 3
4import { PodInstance } from '../pod/pod-interface' 4import { PodInstance } from '../pod/pod-interface'
5import { RemoteVideoAuthorCreateData } from '../../../shared/models/pods/remote-video/remote-video-author-create-request.model'
6import { VideoChannelInstance } from './video-channel-interface'
5 7
6export namespace AuthorMethods { 8export namespace AuthorMethods {
7 export type FindOrCreateAuthor = ( 9 export type Load = (id: number) => Promise<AuthorInstance>
8 name: string, 10 export type LoadByUUID = (uuid: string) => Promise<AuthorInstance>
9 podId: number, 11 export type LoadAuthorByPodAndUUID = (uuid: string, podId: number, transaction: Sequelize.Transaction) => Promise<AuthorInstance>
10 userId: number, 12 export type ListOwned = () => Promise<AuthorInstance[]>
11 transaction: Sequelize.Transaction 13
12 ) => Promise<AuthorInstance> 14 export type ToAddRemoteJSON = (this: AuthorInstance) => RemoteVideoAuthorCreateData
15 export type IsOwned = (this: AuthorInstance) => boolean
13} 16}
14 17
15export interface AuthorClass { 18export interface AuthorClass {
16 findOrCreateAuthor: AuthorMethods.FindOrCreateAuthor 19 loadAuthorByPodAndUUID: AuthorMethods.LoadAuthorByPodAndUUID
20 load: AuthorMethods.Load
21 loadByUUID: AuthorMethods.LoadByUUID
22 listOwned: AuthorMethods.ListOwned
17} 23}
18 24
19export interface AuthorAttributes { 25export interface AuthorAttributes {
20 name: string 26 name: string
27 uuid?: string
28
29 podId?: number
30 userId?: number
21} 31}
22 32
23export interface AuthorInstance extends AuthorClass, AuthorAttributes, Sequelize.Instance<AuthorAttributes> { 33export interface AuthorInstance extends AuthorClass, AuthorAttributes, Sequelize.Instance<AuthorAttributes> {
34 isOwned: AuthorMethods.IsOwned
35 toAddRemoteJSON: AuthorMethods.ToAddRemoteJSON
36
24 id: number 37 id: number
25 createdAt: Date 38 createdAt: Date
26 updatedAt: Date 39 updatedAt: Date
27 40
28 podId: number
29 Pod: PodInstance 41 Pod: PodInstance
42 VideoChannels: VideoChannelInstance[]
30} 43}
31 44
32export interface AuthorModel extends AuthorClass, Sequelize.Model<AuthorInstance, AuthorAttributes> {} 45export interface AuthorModel extends AuthorClass, Sequelize.Model<AuthorInstance, AuthorAttributes> {}
diff --git a/server/models/video/author.ts b/server/models/video/author.ts
index fd0f44f6b..6f27ea7bd 100644
--- a/server/models/video/author.ts
+++ b/server/models/video/author.ts
@@ -1,6 +1,7 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3import { isUserUsernameValid } from '../../helpers' 3import { isUserUsernameValid } from '../../helpers'
4import { removeVideoAuthorToFriends } from '../../lib'
4 5
5import { addMethodsToModel } from '../utils' 6import { addMethodsToModel } from '../utils'
6import { 7import {
@@ -11,11 +12,24 @@ import {
11} from './author-interface' 12} from './author-interface'
12 13
13let Author: Sequelize.Model<AuthorInstance, AuthorAttributes> 14let Author: Sequelize.Model<AuthorInstance, AuthorAttributes>
14let findOrCreateAuthor: AuthorMethods.FindOrCreateAuthor 15let loadAuthorByPodAndUUID: AuthorMethods.LoadAuthorByPodAndUUID
16let load: AuthorMethods.Load
17let loadByUUID: AuthorMethods.LoadByUUID
18let listOwned: AuthorMethods.ListOwned
19let isOwned: AuthorMethods.IsOwned
20let toAddRemoteJSON: AuthorMethods.ToAddRemoteJSON
15 21
16export default function defineAuthor (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { 22export default function defineAuthor (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
17 Author = sequelize.define<AuthorInstance, AuthorAttributes>('Author', 23 Author = sequelize.define<AuthorInstance, AuthorAttributes>('Author',
18 { 24 {
25 uuid: {
26 type: DataTypes.UUID,
27 defaultValue: DataTypes.UUIDV4,
28 allowNull: false,
29 validate: {
30 isUUID: 4
31 }
32 },
19 name: { 33 name: {
20 type: DataTypes.STRING, 34 type: DataTypes.STRING,
21 allowNull: false, 35 allowNull: false,
@@ -43,12 +57,23 @@ export default function defineAuthor (sequelize: Sequelize.Sequelize, DataTypes:
43 fields: [ 'name', 'podId' ], 57 fields: [ 'name', 'podId' ],
44 unique: true 58 unique: true
45 } 59 }
46 ] 60 ],
61 hooks: { afterDestroy }
47 } 62 }
48 ) 63 )
49 64
50 const classMethods = [ associate, findOrCreateAuthor ] 65 const classMethods = [
51 addMethodsToModel(Author, classMethods) 66 associate,
67 loadAuthorByPodAndUUID,
68 load,
69 loadByUUID,
70 listOwned
71 ]
72 const instanceMethods = [
73 isOwned,
74 toAddRemoteJSON
75 ]
76 addMethodsToModel(Author, classMethods, instanceMethods)
52 77
53 return Author 78 return Author
54} 79}
@@ -72,27 +97,75 @@ function associate (models) {
72 onDelete: 'cascade' 97 onDelete: 'cascade'
73 }) 98 })
74 99
75 Author.hasMany(models.Video, { 100 Author.hasMany(models.VideoChannel, {
76 foreignKey: { 101 foreignKey: {
77 name: 'authorId', 102 name: 'authorId',
78 allowNull: false 103 allowNull: false
79 }, 104 },
80 onDelete: 'cascade' 105 onDelete: 'cascade',
106 hooks: true
81 }) 107 })
82} 108}
83 109
84findOrCreateAuthor = function (name: string, podId: number, userId: number, transaction: Sequelize.Transaction) { 110function afterDestroy (author: AuthorInstance, options: { transaction: Sequelize.Transaction }) {
85 const author = { 111 if (author.isOwned()) {
86 name, 112 const removeVideoAuthorToFriendsParams = {
87 podId, 113 uuid: author.uuid
88 userId 114 }
115
116 return removeVideoAuthorToFriends(removeVideoAuthorToFriendsParams, options.transaction)
117 }
118
119 return undefined
120}
121
122toAddRemoteJSON = function (this: AuthorInstance) {
123 const json = {
124 uuid: this.uuid,
125 name: this.name
126 }
127
128 return json
129}
130
131isOwned = function (this: AuthorInstance) {
132 return this.podId === null
133}
134
135// ------------------------------ STATICS ------------------------------
136
137listOwned = function () {
138 const query: Sequelize.FindOptions<AuthorAttributes> = {
139 where: {
140 podId: null
141 }
142 }
143
144 return Author.findAll(query)
145}
146
147load = function (id: number) {
148 return Author.findById(id)
149}
150
151loadByUUID = function (uuid: string) {
152 const query: Sequelize.FindOptions<AuthorAttributes> = {
153 where: {
154 uuid
155 }
89 } 156 }
90 157
91 const query: Sequelize.FindOrInitializeOptions<AuthorAttributes> = { 158 return Author.findOne(query)
92 where: author, 159}
93 defaults: author, 160
161loadAuthorByPodAndUUID = function (uuid: string, podId: number, transaction: Sequelize.Transaction) {
162 const query: Sequelize.FindOptions<AuthorAttributes> = {
163 where: {
164 podId,
165 uuid
166 },
94 transaction 167 transaction
95 } 168 }
96 169
97 return Author.findOrCreate(query).then(([ authorInstance ]) => authorInstance) 170 return Author.find(query)
98} 171}
diff --git a/server/models/video/index.ts b/server/models/video/index.ts
index 08b360376..dba6a5590 100644
--- a/server/models/video/index.ts
+++ b/server/models/video/index.ts
@@ -2,6 +2,7 @@ export * from './author-interface'
2export * from './tag-interface' 2export * from './tag-interface'
3export * from './video-abuse-interface' 3export * from './video-abuse-interface'
4export * from './video-blacklist-interface' 4export * from './video-blacklist-interface'
5export * from './video-channel-interface'
5export * from './video-tag-interface' 6export * from './video-tag-interface'
6export * from './video-file-interface' 7export * from './video-file-interface'
7export * from './video-interface' 8export * from './video-interface'
diff --git a/server/models/video/video-channel-interface.ts b/server/models/video/video-channel-interface.ts
new file mode 100644
index 000000000..b8d3e0f42
--- /dev/null
+++ b/server/models/video/video-channel-interface.ts
@@ -0,0 +1,64 @@
1import * as Sequelize from 'sequelize'
2import * as Promise from 'bluebird'
3
4import { ResultList, RemoteVideoChannelCreateData, RemoteVideoChannelUpdateData } from '../../../shared'
5
6// Don't use barrel, import just what we need
7import { VideoChannel as FormattedVideoChannel } from '../../../shared/models/videos/video-channel.model'
8import { AuthorInstance } from './author-interface'
9import { VideoInstance } from './video-interface'
10
11export namespace VideoChannelMethods {
12 export type ToFormattedJSON = (this: VideoChannelInstance) => FormattedVideoChannel
13 export type ToAddRemoteJSON = (this: VideoChannelInstance) => RemoteVideoChannelCreateData
14 export type ToUpdateRemoteJSON = (this: VideoChannelInstance) => RemoteVideoChannelUpdateData
15 export type IsOwned = (this: VideoChannelInstance) => boolean
16
17 export type CountByAuthor = (authorId: number) => Promise<number>
18 export type ListOwned = () => Promise<VideoChannelInstance[]>
19 export type ListForApi = (start: number, count: number, sort: string) => Promise< ResultList<VideoChannelInstance> >
20 export type LoadByIdAndAuthor = (id: number, authorId: number) => Promise<VideoChannelInstance>
21 export type ListByAuthor = (authorId: number) => Promise< ResultList<VideoChannelInstance> >
22 export type LoadAndPopulateAuthor = (id: number) => Promise<VideoChannelInstance>
23 export type LoadByUUIDAndPopulateAuthor = (uuid: string) => Promise<VideoChannelInstance>
24 export type LoadByUUID = (uuid: string, t?: Sequelize.Transaction) => Promise<VideoChannelInstance>
25 export type LoadByHostAndUUID = (uuid: string, podHost: string, t?: Sequelize.Transaction) => Promise<VideoChannelInstance>
26 export type LoadAndPopulateAuthorAndVideos = (id: number) => Promise<VideoChannelInstance>
27}
28
29export interface VideoChannelClass {
30 countByAuthor: VideoChannelMethods.CountByAuthor
31 listForApi: VideoChannelMethods.ListForApi
32 listByAuthor: VideoChannelMethods.ListByAuthor
33 listOwned: VideoChannelMethods.ListOwned
34 loadByIdAndAuthor: VideoChannelMethods.LoadByIdAndAuthor
35 loadByUUID: VideoChannelMethods.LoadByUUID
36 loadByHostAndUUID: VideoChannelMethods.LoadByHostAndUUID
37 loadAndPopulateAuthor: VideoChannelMethods.LoadAndPopulateAuthor
38 loadByUUIDAndPopulateAuthor: VideoChannelMethods.LoadByUUIDAndPopulateAuthor
39 loadAndPopulateAuthorAndVideos: VideoChannelMethods.LoadAndPopulateAuthorAndVideos
40}
41
42export interface VideoChannelAttributes {
43 id?: number
44 uuid?: string
45 name: string
46 description: string
47 remote: boolean
48
49 Author?: AuthorInstance
50 Videos?: VideoInstance[]
51}
52
53export interface VideoChannelInstance extends VideoChannelClass, VideoChannelAttributes, Sequelize.Instance<VideoChannelAttributes> {
54 id: number
55 createdAt: Date
56 updatedAt: Date
57
58 isOwned: VideoChannelMethods.IsOwned
59 toFormattedJSON: VideoChannelMethods.ToFormattedJSON
60 toAddRemoteJSON: VideoChannelMethods.ToAddRemoteJSON
61 toUpdateRemoteJSON: VideoChannelMethods.ToUpdateRemoteJSON
62}
63
64export interface VideoChannelModel extends VideoChannelClass, Sequelize.Model<VideoChannelInstance, VideoChannelAttributes> {}
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts
new file mode 100644
index 000000000..e469383e9
--- /dev/null
+++ b/server/models/video/video-channel.ts
@@ -0,0 +1,349 @@
1import * as Sequelize from 'sequelize'
2
3import { isVideoChannelNameValid, isVideoChannelDescriptionValid } from '../../helpers'
4import { removeVideoChannelToFriends } from '../../lib'
5
6import { addMethodsToModel, getSort } from '../utils'
7import {
8 VideoChannelInstance,
9 VideoChannelAttributes,
10
11 VideoChannelMethods
12} from './video-channel-interface'
13
14let VideoChannel: Sequelize.Model<VideoChannelInstance, VideoChannelAttributes>
15let toFormattedJSON: VideoChannelMethods.ToFormattedJSON
16let toAddRemoteJSON: VideoChannelMethods.ToAddRemoteJSON
17let toUpdateRemoteJSON: VideoChannelMethods.ToUpdateRemoteJSON
18let isOwned: VideoChannelMethods.IsOwned
19let countByAuthor: VideoChannelMethods.CountByAuthor
20let listOwned: VideoChannelMethods.ListOwned
21let listForApi: VideoChannelMethods.ListForApi
22let listByAuthor: VideoChannelMethods.ListByAuthor
23let loadByIdAndAuthor: VideoChannelMethods.LoadByIdAndAuthor
24let loadByUUID: VideoChannelMethods.LoadByUUID
25let loadAndPopulateAuthor: VideoChannelMethods.LoadAndPopulateAuthor
26let loadByUUIDAndPopulateAuthor: VideoChannelMethods.LoadByUUIDAndPopulateAuthor
27let loadByHostAndUUID: VideoChannelMethods.LoadByHostAndUUID
28let loadAndPopulateAuthorAndVideos: VideoChannelMethods.LoadAndPopulateAuthorAndVideos
29
30export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
31 VideoChannel = sequelize.define<VideoChannelInstance, VideoChannelAttributes>('VideoChannel',
32 {
33 uuid: {
34 type: DataTypes.UUID,
35 defaultValue: DataTypes.UUIDV4,
36 allowNull: false,
37 validate: {
38 isUUID: 4
39 }
40 },
41 name: {
42 type: DataTypes.STRING,
43 allowNull: false,
44 validate: {
45 nameValid: value => {
46 const res = isVideoChannelNameValid(value)
47 if (res === false) throw new Error('Video channel name is not valid.')
48 }
49 }
50 },
51 description: {
52 type: DataTypes.STRING,
53 allowNull: true,
54 validate: {
55 descriptionValid: value => {
56 const res = isVideoChannelDescriptionValid(value)
57 if (res === false) throw new Error('Video channel description is not valid.')
58 }
59 }
60 },
61 remote: {
62 type: DataTypes.BOOLEAN,
63 allowNull: false,
64 defaultValue: false
65 }
66 },
67 {
68 indexes: [
69 {
70 fields: [ 'authorId' ]
71 }
72 ],
73 hooks: {
74 afterDestroy
75 }
76 }
77 )
78
79 const classMethods = [
80 associate,
81
82 listForApi,
83 listByAuthor,
84 listOwned,
85 loadByIdAndAuthor,
86 loadAndPopulateAuthor,
87 loadByUUIDAndPopulateAuthor,
88 loadByUUID,
89 loadByHostAndUUID,
90 loadAndPopulateAuthorAndVideos,
91 countByAuthor
92 ]
93 const instanceMethods = [
94 isOwned,
95 toFormattedJSON,
96 toAddRemoteJSON,
97 toUpdateRemoteJSON
98 ]
99 addMethodsToModel(VideoChannel, classMethods, instanceMethods)
100
101 return VideoChannel
102}
103
104// ------------------------------ METHODS ------------------------------
105
106isOwned = function (this: VideoChannelInstance) {
107 return this.remote === false
108}
109
110toFormattedJSON = function (this: VideoChannelInstance) {
111 const json = {
112 id: this.id,
113 uuid: this.uuid,
114 name: this.name,
115 description: this.description,
116 isLocal: this.isOwned(),
117 createdAt: this.createdAt,
118 updatedAt: this.updatedAt
119 }
120
121 if (this.Author !== undefined) {
122 json['owner'] = {
123 name: this.Author.name,
124 uuid: this.Author.uuid
125 }
126 }
127
128 if (Array.isArray(this.Videos)) {
129 json['videos'] = this.Videos.map(v => v.toFormattedJSON())
130 }
131
132 return json
133}
134
135toAddRemoteJSON = function (this: VideoChannelInstance) {
136 const json = {
137 uuid: this.uuid,
138 name: this.name,
139 description: this.description,
140 createdAt: this.createdAt,
141 updatedAt: this.updatedAt,
142 ownerUUID: this.Author.uuid
143 }
144
145 return json
146}
147
148toUpdateRemoteJSON = function (this: VideoChannelInstance) {
149 const json = {
150 uuid: this.uuid,
151 name: this.name,
152 description: this.description,
153 createdAt: this.createdAt,
154 updatedAt: this.updatedAt,
155 ownerUUID: this.Author.uuid
156 }
157
158 return json
159}
160
161// ------------------------------ STATICS ------------------------------
162
163function associate (models) {
164 VideoChannel.belongsTo(models.Author, {
165 foreignKey: {
166 name: 'authorId',
167 allowNull: false
168 },
169 onDelete: 'CASCADE'
170 })
171
172 VideoChannel.hasMany(models.Video, {
173 foreignKey: {
174 name: 'channelId',
175 allowNull: false
176 },
177 onDelete: 'CASCADE'
178 })
179}
180
181function afterDestroy (videoChannel: VideoChannelInstance, options: { transaction: Sequelize.Transaction }) {
182 if (videoChannel.isOwned()) {
183 const removeVideoChannelToFriendsParams = {
184 uuid: videoChannel.uuid
185 }
186
187 return removeVideoChannelToFriends(removeVideoChannelToFriendsParams, options.transaction)
188 }
189
190 return undefined
191}
192
193countByAuthor = function (authorId: number) {
194 const query = {
195 where: {
196 authorId
197 }
198 }
199
200 return VideoChannel.count(query)
201}
202
203listOwned = function () {
204 const query = {
205 where: {
206 remote: false
207 },
208 include: [ VideoChannel['sequelize'].models.Author ]
209 }
210
211 return VideoChannel.findAll(query)
212}
213
214listForApi = function (start: number, count: number, sort: string) {
215 const query = {
216 offset: start,
217 limit: count,
218 order: [ getSort(sort) ],
219 include: [
220 {
221 model: VideoChannel['sequelize'].models.Author,
222 required: true,
223 include: [ { model: VideoChannel['sequelize'].models.Pod, required: false } ]
224 }
225 ]
226 }
227
228 return VideoChannel.findAndCountAll(query).then(({ rows, count }) => {
229 return { total: count, data: rows }
230 })
231}
232
233listByAuthor = function (authorId: number) {
234 const query = {
235 order: [ getSort('createdAt') ],
236 include: [
237 {
238 model: VideoChannel['sequelize'].models.Author,
239 where: {
240 id: authorId
241 },
242 required: true,
243 include: [ { model: VideoChannel['sequelize'].models.Pod, required: false } ]
244 }
245 ]
246 }
247
248 return VideoChannel.findAndCountAll(query).then(({ rows, count }) => {
249 return { total: count, data: rows }
250 })
251}
252
253loadByUUID = function (uuid: string, t?: Sequelize.Transaction) {
254 const query: Sequelize.FindOptions<VideoChannelAttributes> = {
255 where: {
256 uuid
257 }
258 }
259
260 if (t !== undefined) query.transaction = t
261
262 return VideoChannel.findOne(query)
263}
264
265loadByHostAndUUID = function (fromHost: string, uuid: string, t?: Sequelize.Transaction) {
266 const query: Sequelize.FindOptions<VideoChannelAttributes> = {
267 where: {
268 uuid
269 },
270 include: [
271 {
272 model: VideoChannel['sequelize'].models.Author,
273 include: [
274 {
275 model: VideoChannel['sequelize'].models.Pod,
276 required: true,
277 where: {
278 host: fromHost
279 }
280 }
281 ]
282 }
283 ]
284 }
285
286 if (t !== undefined) query.transaction = t
287
288 return VideoChannel.findOne(query)
289}
290
291loadByIdAndAuthor = function (id: number, authorId: number) {
292 const options = {
293 where: {
294 id,
295 authorId
296 },
297 include: [
298 {
299 model: VideoChannel['sequelize'].models.Author,
300 include: [ { model: VideoChannel['sequelize'].models.Pod, required: false } ]
301 }
302 ]
303 }
304
305 return VideoChannel.findOne(options)
306}
307
308loadAndPopulateAuthor = function (id: number) {
309 const options = {
310 include: [
311 {
312 model: VideoChannel['sequelize'].models.Author,
313 include: [ { model: VideoChannel['sequelize'].models.Pod, required: false } ]
314 }
315 ]
316 }
317
318 return VideoChannel.findById(id, options)
319}
320
321loadByUUIDAndPopulateAuthor = function (uuid: string) {
322 const options = {
323 where: {
324 uuid
325 },
326 include: [
327 {
328 model: VideoChannel['sequelize'].models.Author,
329 include: [ { model: VideoChannel['sequelize'].models.Pod, required: false } ]
330 }
331 ]
332 }
333
334 return VideoChannel.findOne(options)
335}
336
337loadAndPopulateAuthorAndVideos = function (id: number) {
338 const options = {
339 include: [
340 {
341 model: VideoChannel['sequelize'].models.Author,
342 include: [ { model: VideoChannel['sequelize'].models.Pod, required: false } ]
343 },
344 VideoChannel['sequelize'].models.Video
345 ]
346 }
347
348 return VideoChannel.findById(id, options)
349}
diff --git a/server/models/video/video-interface.ts b/server/models/video/video-interface.ts
index 86ce84dd9..4b5ae08c2 100644
--- a/server/models/video/video-interface.ts
+++ b/server/models/video/video-interface.ts
@@ -6,16 +6,21 @@ import { TagAttributes, TagInstance } from './tag-interface'
6import { VideoFileAttributes, VideoFileInstance } from './video-file-interface' 6import { VideoFileAttributes, VideoFileInstance } from './video-file-interface'
7 7
8// Don't use barrel, import just what we need 8// Don't use barrel, import just what we need
9import { Video as FormattedVideo } from '../../../shared/models/videos/video.model' 9import {
10 Video as FormattedVideo,
11 VideoDetails as FormattedDetailsVideo
12} from '../../../shared/models/videos/video.model'
10import { RemoteVideoUpdateData } from '../../../shared/models/pods/remote-video/remote-video-update-request.model' 13import { RemoteVideoUpdateData } from '../../../shared/models/pods/remote-video/remote-video-update-request.model'
11import { RemoteVideoCreateData } from '../../../shared/models/pods/remote-video/remote-video-create-request.model' 14import { RemoteVideoCreateData } from '../../../shared/models/pods/remote-video/remote-video-create-request.model'
12import { ResultList } from '../../../shared/models/result-list.model' 15import { ResultList } from '../../../shared/models/result-list.model'
16import { VideoChannelInstance } from './video-channel-interface'
13 17
14export namespace VideoMethods { 18export namespace VideoMethods {
15 export type GetThumbnailName = (this: VideoInstance) => string 19 export type GetThumbnailName = (this: VideoInstance) => string
16 export type GetPreviewName = (this: VideoInstance) => string 20 export type GetPreviewName = (this: VideoInstance) => string
17 export type IsOwned = (this: VideoInstance) => boolean 21 export type IsOwned = (this: VideoInstance) => boolean
18 export type ToFormattedJSON = (this: VideoInstance) => FormattedVideo 22 export type ToFormattedJSON = (this: VideoInstance) => FormattedVideo
23 export type ToFormattedDetailsJSON = (this: VideoInstance) => FormattedDetailsVideo
19 24
20 export type GetOriginalFile = (this: VideoInstance) => VideoFileInstance 25 export type GetOriginalFile = (this: VideoInstance) => VideoFileInstance
21 export type GetTorrentFileName = (this: VideoInstance, videoFile: VideoFileInstance) => string 26 export type GetTorrentFileName = (this: VideoInstance, videoFile: VideoFileInstance) => string
@@ -52,8 +57,8 @@ export namespace VideoMethods {
52 ) => Promise< ResultList<VideoInstance> > 57 ) => Promise< ResultList<VideoInstance> >
53 58
54 export type Load = (id: number) => Promise<VideoInstance> 59 export type Load = (id: number) => Promise<VideoInstance>
55 export type LoadByUUID = (uuid: string) => Promise<VideoInstance> 60 export type LoadByUUID = (uuid: string, t?: Sequelize.Transaction) => Promise<VideoInstance>
56 export type LoadByHostAndUUID = (fromHost: string, uuid: string) => Promise<VideoInstance> 61 export type LoadByHostAndUUID = (fromHost: string, uuid: string, t?: Sequelize.Transaction) => Promise<VideoInstance>
57 export type LoadAndPopulateAuthor = (id: number) => Promise<VideoInstance> 62 export type LoadAndPopulateAuthor = (id: number) => Promise<VideoInstance>
58 export type LoadAndPopulateAuthorAndPodAndTags = (id: number) => Promise<VideoInstance> 63 export type LoadAndPopulateAuthorAndPodAndTags = (id: number) => Promise<VideoInstance>
59 export type LoadByUUIDAndPopulateAuthorAndPodAndTags = (uuid: string) => Promise<VideoInstance> 64 export type LoadByUUIDAndPopulateAuthorAndPodAndTags = (uuid: string) => Promise<VideoInstance>
@@ -94,7 +99,9 @@ export interface VideoAttributes {
94 dislikes?: number 99 dislikes?: number
95 remote: boolean 100 remote: boolean
96 101
97 Author?: AuthorInstance 102 channelId?: number
103
104 VideoChannel?: VideoChannelInstance
98 Tags?: TagInstance[] 105 Tags?: TagInstance[]
99 VideoFiles?: VideoFileInstance[] 106 VideoFiles?: VideoFileInstance[]
100} 107}
@@ -121,6 +128,7 @@ export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.In
121 removeTorrent: VideoMethods.RemoveTorrent 128 removeTorrent: VideoMethods.RemoveTorrent
122 toAddRemoteJSON: VideoMethods.ToAddRemoteJSON 129 toAddRemoteJSON: VideoMethods.ToAddRemoteJSON
123 toFormattedJSON: VideoMethods.ToFormattedJSON 130 toFormattedJSON: VideoMethods.ToFormattedJSON
131 toFormattedDetailsJSON: VideoMethods.ToFormattedDetailsJSON
124 toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON 132 toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON
125 optimizeOriginalVideofile: VideoMethods.OptimizeOriginalVideofile 133 optimizeOriginalVideofile: VideoMethods.OptimizeOriginalVideofile
126 transcodeOriginalVideofile: VideoMethods.TranscodeOriginalVideofile 134 transcodeOriginalVideofile: VideoMethods.TranscodeOriginalVideofile
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 0b1af4d21..d9b976404 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -60,6 +60,7 @@ let getPreviewPath: VideoMethods.GetPreviewPath
60let getTorrentFileName: VideoMethods.GetTorrentFileName 60let getTorrentFileName: VideoMethods.GetTorrentFileName
61let isOwned: VideoMethods.IsOwned 61let isOwned: VideoMethods.IsOwned
62let toFormattedJSON: VideoMethods.ToFormattedJSON 62let toFormattedJSON: VideoMethods.ToFormattedJSON
63let toFormattedDetailsJSON: VideoMethods.ToFormattedDetailsJSON
63let toAddRemoteJSON: VideoMethods.ToAddRemoteJSON 64let toAddRemoteJSON: VideoMethods.ToAddRemoteJSON
64let toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON 65let toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON
65let optimizeOriginalVideofile: VideoMethods.OptimizeOriginalVideofile 66let optimizeOriginalVideofile: VideoMethods.OptimizeOriginalVideofile
@@ -206,9 +207,6 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
206 { 207 {
207 indexes: [ 208 indexes: [
208 { 209 {
209 fields: [ 'authorId' ]
210 },
211 {
212 fields: [ 'name' ] 210 fields: [ 'name' ]
213 }, 211 },
214 { 212 {
@@ -225,6 +223,9 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
225 }, 223 },
226 { 224 {
227 fields: [ 'uuid' ] 225 fields: [ 'uuid' ]
226 },
227 {
228 fields: [ 'channelId' ]
228 } 229 }
229 ], 230 ],
230 hooks: { 231 hooks: {
@@ -268,6 +269,7 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
268 removeTorrent, 269 removeTorrent,
269 toAddRemoteJSON, 270 toAddRemoteJSON,
270 toFormattedJSON, 271 toFormattedJSON,
272 toFormattedDetailsJSON,
271 toUpdateRemoteJSON, 273 toUpdateRemoteJSON,
272 optimizeOriginalVideofile, 274 optimizeOriginalVideofile,
273 transcodeOriginalVideofile, 275 transcodeOriginalVideofile,
@@ -282,9 +284,9 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
282// ------------------------------ METHODS ------------------------------ 284// ------------------------------ METHODS ------------------------------
283 285
284function associate (models) { 286function associate (models) {
285 Video.belongsTo(models.Author, { 287 Video.belongsTo(models.VideoChannel, {
286 foreignKey: { 288 foreignKey: {
287 name: 'authorId', 289 name: 'channelId',
288 allowNull: false 290 allowNull: false
289 }, 291 },
290 onDelete: 'cascade' 292 onDelete: 'cascade'
@@ -439,8 +441,8 @@ getPreviewPath = function (this: VideoInstance) {
439toFormattedJSON = function (this: VideoInstance) { 441toFormattedJSON = function (this: VideoInstance) {
440 let podHost 442 let podHost
441 443
442 if (this.Author.Pod) { 444 if (this.VideoChannel.Author.Pod) {
443 podHost = this.Author.Pod.host 445 podHost = this.VideoChannel.Author.Pod.host
444 } else { 446 } else {
445 // It means it's our video 447 // It means it's our video
446 podHost = CONFIG.WEBSERVER.HOST 448 podHost = CONFIG.WEBSERVER.HOST
@@ -472,7 +474,59 @@ toFormattedJSON = function (this: VideoInstance) {
472 description: this.description, 474 description: this.description,
473 podHost, 475 podHost,
474 isLocal: this.isOwned(), 476 isLocal: this.isOwned(),
475 author: this.Author.name, 477 author: this.VideoChannel.Author.name,
478 duration: this.duration,
479 views: this.views,
480 likes: this.likes,
481 dislikes: this.dislikes,
482 tags: map<TagInstance, string>(this.Tags, 'name'),
483 thumbnailPath: this.getThumbnailPath(),
484 previewPath: this.getPreviewPath(),
485 embedPath: this.getEmbedPath(),
486 createdAt: this.createdAt,
487 updatedAt: this.updatedAt
488 }
489
490 return json
491}
492
493toFormattedDetailsJSON = function (this: VideoInstance) {
494 let podHost
495
496 if (this.VideoChannel.Author.Pod) {
497 podHost = this.VideoChannel.Author.Pod.host
498 } else {
499 // It means it's our video
500 podHost = CONFIG.WEBSERVER.HOST
501 }
502
503 // Maybe our pod is not up to date and there are new categories since our version
504 let categoryLabel = VIDEO_CATEGORIES[this.category]
505 if (!categoryLabel) categoryLabel = 'Misc'
506
507 // Maybe our pod is not up to date and there are new licences since our version
508 let licenceLabel = VIDEO_LICENCES[this.licence]
509 if (!licenceLabel) licenceLabel = 'Unknown'
510
511 // Language is an optional attribute
512 let languageLabel = VIDEO_LANGUAGES[this.language]
513 if (!languageLabel) languageLabel = 'Unknown'
514
515 const json = {
516 id: this.id,
517 uuid: this.uuid,
518 name: this.name,
519 category: this.category,
520 categoryLabel,
521 licence: this.licence,
522 licenceLabel,
523 language: this.language,
524 languageLabel,
525 nsfw: this.nsfw,
526 description: this.description,
527 podHost,
528 isLocal: this.isOwned(),
529 author: this.VideoChannel.Author.name,
476 duration: this.duration, 530 duration: this.duration,
477 views: this.views, 531 views: this.views,
478 likes: this.likes, 532 likes: this.likes,
@@ -483,6 +537,7 @@ toFormattedJSON = function (this: VideoInstance) {
483 embedPath: this.getEmbedPath(), 537 embedPath: this.getEmbedPath(),
484 createdAt: this.createdAt, 538 createdAt: this.createdAt,
485 updatedAt: this.updatedAt, 539 updatedAt: this.updatedAt,
540 channel: this.VideoChannel.toFormattedJSON(),
486 files: [] 541 files: []
487 } 542 }
488 543
@@ -525,7 +580,7 @@ toAddRemoteJSON = function (this: VideoInstance) {
525 language: this.language, 580 language: this.language,
526 nsfw: this.nsfw, 581 nsfw: this.nsfw,
527 description: this.description, 582 description: this.description,
528 author: this.Author.name, 583 channelUUID: this.VideoChannel.uuid,
529 duration: this.duration, 584 duration: this.duration,
530 thumbnailData: thumbnailData.toString('binary'), 585 thumbnailData: thumbnailData.toString('binary'),
531 tags: map<TagInstance, string>(this.Tags, 'name'), 586 tags: map<TagInstance, string>(this.Tags, 'name'),
@@ -559,7 +614,6 @@ toUpdateRemoteJSON = function (this: VideoInstance) {
559 language: this.language, 614 language: this.language,
560 nsfw: this.nsfw, 615 nsfw: this.nsfw,
561 description: this.description, 616 description: this.description,
562 author: this.Author.name,
563 duration: this.duration, 617 duration: this.duration,
564 tags: map<TagInstance, string>(this.Tags, 'name'), 618 tags: map<TagInstance, string>(this.Tags, 'name'),
565 createdAt: this.createdAt, 619 createdAt: this.createdAt,
@@ -723,8 +777,18 @@ listForApi = function (start: number, count: number, sort: string) {
723 order: [ getSort(sort), [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ], 777 order: [ getSort(sort), [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ],
724 include: [ 778 include: [
725 { 779 {
726 model: Video['sequelize'].models.Author, 780 model: Video['sequelize'].models.VideoChannel,
727 include: [ { model: Video['sequelize'].models.Pod, required: false } ] 781 include: [
782 {
783 model: Video['sequelize'].models.Author,
784 include: [
785 {
786 model: Video['sequelize'].models.Pod,
787 required: false
788 }
789 ]
790 }
791 ]
728 }, 792 },
729 Video['sequelize'].models.Tag, 793 Video['sequelize'].models.Tag,
730 Video['sequelize'].models.VideoFile 794 Video['sequelize'].models.VideoFile
@@ -740,8 +804,8 @@ listForApi = function (start: number, count: number, sort: string) {
740 }) 804 })
741} 805}
742 806
743loadByHostAndUUID = function (fromHost: string, uuid: string) { 807loadByHostAndUUID = function (fromHost: string, uuid: string, t?: Sequelize.Transaction) {
744 const query = { 808 const query: Sequelize.FindOptions<VideoAttributes> = {
745 where: { 809 where: {
746 uuid 810 uuid
747 }, 811 },
@@ -750,20 +814,27 @@ loadByHostAndUUID = function (fromHost: string, uuid: string) {
750 model: Video['sequelize'].models.VideoFile 814 model: Video['sequelize'].models.VideoFile
751 }, 815 },
752 { 816 {
753 model: Video['sequelize'].models.Author, 817 model: Video['sequelize'].models.VideoChannel,
754 include: [ 818 include: [
755 { 819 {
756 model: Video['sequelize'].models.Pod, 820 model: Video['sequelize'].models.Author,
757 required: true, 821 include: [
758 where: { 822 {
759 host: fromHost 823 model: Video['sequelize'].models.Pod,
760 } 824 required: true,
825 where: {
826 host: fromHost
827 }
828 }
829 ]
761 } 830 }
762 ] 831 ]
763 } 832 }
764 ] 833 ]
765 } 834 }
766 835
836 if (t !== undefined) query.transaction = t
837
767 return Video.findOne(query) 838 return Video.findOne(query)
768} 839}
769 840
@@ -774,7 +845,10 @@ listOwnedAndPopulateAuthorAndTags = function () {
774 }, 845 },
775 include: [ 846 include: [
776 Video['sequelize'].models.VideoFile, 847 Video['sequelize'].models.VideoFile,
777 Video['sequelize'].models.Author, 848 {
849 model: Video['sequelize'].models.VideoChannel,
850 include: [ Video['sequelize'].models.Author ]
851 },
778 Video['sequelize'].models.Tag 852 Video['sequelize'].models.Tag
779 ] 853 ]
780 } 854 }
@@ -792,10 +866,15 @@ listOwnedByAuthor = function (author: string) {
792 model: Video['sequelize'].models.VideoFile 866 model: Video['sequelize'].models.VideoFile
793 }, 867 },
794 { 868 {
795 model: Video['sequelize'].models.Author, 869 model: Video['sequelize'].models.VideoChannel,
796 where: { 870 include: [
797 name: author 871 {
798 } 872 model: Video['sequelize'].models.Author,
873 where: {
874 name: author
875 }
876 }
877 ]
799 } 878 }
800 ] 879 ]
801 } 880 }
@@ -807,19 +886,28 @@ load = function (id: number) {
807 return Video.findById(id) 886 return Video.findById(id)
808} 887}
809 888
810loadByUUID = function (uuid: string) { 889loadByUUID = function (uuid: string, t?: Sequelize.Transaction) {
811 const query = { 890 const query: Sequelize.FindOptions<VideoAttributes> = {
812 where: { 891 where: {
813 uuid 892 uuid
814 }, 893 },
815 include: [ Video['sequelize'].models.VideoFile ] 894 include: [ Video['sequelize'].models.VideoFile ]
816 } 895 }
896
897 if (t !== undefined) query.transaction = t
898
817 return Video.findOne(query) 899 return Video.findOne(query)
818} 900}
819 901
820loadAndPopulateAuthor = function (id: number) { 902loadAndPopulateAuthor = function (id: number) {
821 const options = { 903 const options = {
822 include: [ Video['sequelize'].models.VideoFile, Video['sequelize'].models.Author ] 904 include: [
905 Video['sequelize'].models.VideoFile,
906 {
907 model: Video['sequelize'].models.VideoChannel,
908 include: [ Video['sequelize'].models.Author ]
909 }
910 ]
823 } 911 }
824 912
825 return Video.findById(id, options) 913 return Video.findById(id, options)
@@ -829,8 +917,13 @@ loadAndPopulateAuthorAndPodAndTags = function (id: number) {
829 const options = { 917 const options = {
830 include: [ 918 include: [
831 { 919 {
832 model: Video['sequelize'].models.Author, 920 model: Video['sequelize'].models.VideoChannel,
833 include: [ { model: Video['sequelize'].models.Pod, required: false } ] 921 include: [
922 {
923 model: Video['sequelize'].models.Author,
924 include: [ { model: Video['sequelize'].models.Pod, required: false } ]
925 }
926 ]
834 }, 927 },
835 Video['sequelize'].models.Tag, 928 Video['sequelize'].models.Tag,
836 Video['sequelize'].models.VideoFile 929 Video['sequelize'].models.VideoFile
@@ -847,8 +940,13 @@ loadByUUIDAndPopulateAuthorAndPodAndTags = function (uuid: string) {
847 }, 940 },
848 include: [ 941 include: [
849 { 942 {
850 model: Video['sequelize'].models.Author, 943 model: Video['sequelize'].models.VideoChannel,
851 include: [ { model: Video['sequelize'].models.Pod, required: false } ] 944 include: [
945 {
946 model: Video['sequelize'].models.Author,
947 include: [ { model: Video['sequelize'].models.Pod, required: false } ]
948 }
949 ]
852 }, 950 },
853 Video['sequelize'].models.Tag, 951 Video['sequelize'].models.Tag,
854 Video['sequelize'].models.VideoFile 952 Video['sequelize'].models.VideoFile
@@ -866,9 +964,13 @@ searchAndPopulateAuthorAndPodAndTags = function (value: string, field: string, s
866 964
867 const authorInclude: Sequelize.IncludeOptions = { 965 const authorInclude: Sequelize.IncludeOptions = {
868 model: Video['sequelize'].models.Author, 966 model: Video['sequelize'].models.Author,
869 include: [ 967 include: [ podInclude ]
870 podInclude 968 }
871 ] 969
970 const videoChannelInclude: Sequelize.IncludeOptions = {
971 model: Video['sequelize'].models.VideoChannel,
972 include: [ authorInclude ],
973 required: true
872 } 974 }
873 975
874 const tagInclude: Sequelize.IncludeOptions = { 976 const tagInclude: Sequelize.IncludeOptions = {
@@ -915,8 +1017,6 @@ searchAndPopulateAuthorAndPodAndTags = function (value: string, field: string, s
915 $iLike: '%' + value + '%' 1017 $iLike: '%' + value + '%'
916 } 1018 }
917 } 1019 }
918
919 // authorInclude.or = true
920 } else { 1020 } else {
921 query.where[field] = { 1021 query.where[field] = {
922 $iLike: '%' + value + '%' 1022 $iLike: '%' + value + '%'
@@ -924,7 +1024,7 @@ searchAndPopulateAuthorAndPodAndTags = function (value: string, field: string, s
924 } 1024 }
925 1025
926 query.include = [ 1026 query.include = [
927 authorInclude, tagInclude, videoFileInclude 1027 videoChannelInclude, tagInclude, videoFileInclude
928 ] 1028 ]
929 1029
930 return Video.findAndCountAll(query).then(({ rows, count }) => { 1030 return Video.findAndCountAll(query).then(({ rows, count }) => {
@@ -955,8 +1055,8 @@ function getBaseUrls (video: VideoInstance) {
955 baseUrlHttp = CONFIG.WEBSERVER.URL 1055 baseUrlHttp = CONFIG.WEBSERVER.URL
956 baseUrlWs = CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT 1056 baseUrlWs = CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT
957 } else { 1057 } else {
958 baseUrlHttp = REMOTE_SCHEME.HTTP + '://' + video.Author.Pod.host 1058 baseUrlHttp = REMOTE_SCHEME.HTTP + '://' + video.VideoChannel.Author.Pod.host
959 baseUrlWs = REMOTE_SCHEME.WS + '://' + video.Author.Pod.host 1059 baseUrlWs = REMOTE_SCHEME.WS + '://' + video.VideoChannel.Author.Pod.host
960 } 1060 }
961 1061
962 return { baseUrlHttp, baseUrlWs } 1062 return { baseUrlHttp, baseUrlWs }