aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models
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
parent8113a93a0d9f31aa9e23702bbc31b8a76275ae22 (diff)
downloadPeerTube-72c7248b6fdcdb2175e726ff51b42e7555f2bd84.tar.gz
PeerTube-72c7248b6fdcdb2175e726ff51b42e7555f2bd84.tar.zst
PeerTube-72c7248b6fdcdb2175e726ff51b42e7555f2bd84.zip
Add video channels
Diffstat (limited to 'server/models')
-rw-r--r--server/models/oauth/oauth-token.ts24
-rw-r--r--server/models/request/request-video-event.ts5
-rw-r--r--server/models/user/user-interface.ts8
-rw-r--r--server/models/user/user.ts69
-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
11 files changed, 760 insertions, 88 deletions
diff --git a/server/models/oauth/oauth-token.ts b/server/models/oauth/oauth-token.ts
index e3de9468e..dc8bcd872 100644
--- a/server/models/oauth/oauth-token.ts
+++ b/server/models/oauth/oauth-token.ts
@@ -126,7 +126,17 @@ getByTokenAndPopulateUser = function (bearerToken: string) {
126 where: { 126 where: {
127 accessToken: bearerToken 127 accessToken: bearerToken
128 }, 128 },
129 include: [ OAuthToken['sequelize'].models.User ] 129 include: [
130 {
131 model: OAuthToken['sequelize'].models.User,
132 include: [
133 {
134 model: OAuthToken['sequelize'].models.Author,
135 required: true
136 }
137 ]
138 }
139 ]
130 } 140 }
131 141
132 return OAuthToken.findOne(query).then(token => { 142 return OAuthToken.findOne(query).then(token => {
@@ -141,7 +151,17 @@ getByRefreshTokenAndPopulateUser = function (refreshToken: string) {
141 where: { 151 where: {
142 refreshToken: refreshToken 152 refreshToken: refreshToken
143 }, 153 },
144 include: [ OAuthToken['sequelize'].models.User ] 154 include: [
155 {
156 model: OAuthToken['sequelize'].models.User,
157 include: [
158 {
159 model: OAuthToken['sequelize'].models.Author,
160 required: true
161 }
162 ]
163 }
164 ]
145 } 165 }
146 166
147 return OAuthToken.findOne(query).then(token => { 167 return OAuthToken.findOne(query).then(token => {
diff --git a/server/models/request/request-video-event.ts b/server/models/request/request-video-event.ts
index 4862a5745..34d5c7162 100644
--- a/server/models/request/request-video-event.ts
+++ b/server/models/request/request-video-event.ts
@@ -85,7 +85,8 @@ listWithLimitAndRandom = function (limitPods: number, limitRequestsPerPod: numbe
85 const Pod = db.Pod 85 const Pod = db.Pod
86 86
87 // We make a join between videos and authors to find the podId of our video event requests 87 // We make a join between videos and authors to find the podId of our video event requests
88 const podJoins = 'INNER JOIN "Videos" ON "Videos"."authorId" = "Authors"."id" ' + 88 const podJoins = 'INNER JOIN "VideoChannels" ON "VideoChannels"."authorId" = "Authors"."id" ' +
89 'INNER JOIN "Videos" ON "Videos"."channelId" = "VideoChannels"."id" ' +
89 'INNER JOIN "RequestVideoEvents" ON "RequestVideoEvents"."videoId" = "Videos"."id"' 90 'INNER JOIN "RequestVideoEvents" ON "RequestVideoEvents"."videoId" = "Videos"."id"'
90 91
91 return Pod.listRandomPodIdsWithRequest(limitPods, 'Authors', podJoins).then(podIds => { 92 return Pod.listRandomPodIdsWithRequest(limitPods, 'Authors', podJoins).then(podIds => {
@@ -161,7 +162,7 @@ function groupAndTruncateRequests (events: RequestVideoEventInstance[], limitReq
161 const eventsGrouped: RequestsVideoEventGrouped = {} 162 const eventsGrouped: RequestsVideoEventGrouped = {}
162 163
163 events.forEach(event => { 164 events.forEach(event => {
164 const pod = event.Video.Author.Pod 165 const pod = event.Video.VideoChannel.Author.Pod
165 166
166 if (!eventsGrouped[pod.id]) eventsGrouped[pod.id] = [] 167 if (!eventsGrouped[pod.id]) eventsGrouped[pod.id] = []
167 168
diff --git a/server/models/user/user-interface.ts b/server/models/user/user-interface.ts
index 8974a9a97..1b5233eaf 100644
--- a/server/models/user/user-interface.ts
+++ b/server/models/user/user-interface.ts
@@ -5,6 +5,7 @@ import * as Promise from 'bluebird'
5import { User as FormattedUser } from '../../../shared/models/users/user.model' 5import { User as FormattedUser } from '../../../shared/models/users/user.model'
6import { UserRole } from '../../../shared/models/users/user-role.type' 6import { UserRole } from '../../../shared/models/users/user-role.type'
7import { ResultList } from '../../../shared/models/result-list.model' 7import { ResultList } from '../../../shared/models/result-list.model'
8import { AuthorInstance } from '../video/author-interface'
8 9
9export namespace UserMethods { 10export namespace UserMethods {
10 export type IsPasswordMatch = (this: UserInstance, password: string) => Promise<boolean> 11 export type IsPasswordMatch = (this: UserInstance, password: string) => Promise<boolean>
@@ -17,13 +18,12 @@ export namespace UserMethods {
17 18
18 export type GetByUsername = (username: string) => Promise<UserInstance> 19 export type GetByUsername = (username: string) => Promise<UserInstance>
19 20
20 export type List = () => Promise<UserInstance[]>
21
22 export type ListForApi = (start: number, count: number, sort: string) => Promise< ResultList<UserInstance> > 21 export type ListForApi = (start: number, count: number, sort: string) => Promise< ResultList<UserInstance> >
23 22
24 export type LoadById = (id: number) => Promise<UserInstance> 23 export type LoadById = (id: number) => Promise<UserInstance>
25 24
26 export type LoadByUsername = (username: string) => Promise<UserInstance> 25 export type LoadByUsername = (username: string) => Promise<UserInstance>
26 export type LoadByUsernameAndPopulateChannels = (username: string) => Promise<UserInstance>
27 27
28 export type LoadByUsernameOrEmail = (username: string, email: string) => Promise<UserInstance> 28 export type LoadByUsernameOrEmail = (username: string, email: string) => Promise<UserInstance>
29} 29}
@@ -36,10 +36,10 @@ export interface UserClass {
36 36
37 countTotal: UserMethods.CountTotal, 37 countTotal: UserMethods.CountTotal,
38 getByUsername: UserMethods.GetByUsername, 38 getByUsername: UserMethods.GetByUsername,
39 list: UserMethods.List,
40 listForApi: UserMethods.ListForApi, 39 listForApi: UserMethods.ListForApi,
41 loadById: UserMethods.LoadById, 40 loadById: UserMethods.LoadById,
42 loadByUsername: UserMethods.LoadByUsername, 41 loadByUsername: UserMethods.LoadByUsername,
42 loadByUsernameAndPopulateChannels: UserMethods.LoadByUsernameAndPopulateChannels,
43 loadByUsernameOrEmail: UserMethods.LoadByUsernameOrEmail 43 loadByUsernameOrEmail: UserMethods.LoadByUsernameOrEmail
44} 44}
45 45
@@ -51,6 +51,8 @@ export interface UserAttributes {
51 displayNSFW?: boolean 51 displayNSFW?: boolean
52 role: UserRole 52 role: UserRole
53 videoQuota: number 53 videoQuota: number
54
55 Author?: AuthorInstance
54} 56}
55 57
56export interface UserInstance extends UserClass, UserAttributes, Sequelize.Instance<UserAttributes> { 58export interface UserInstance extends UserClass, UserAttributes, Sequelize.Instance<UserAttributes> {
diff --git a/server/models/user/user.ts b/server/models/user/user.ts
index 0dc52d3cf..f8598c40f 100644
--- a/server/models/user/user.ts
+++ b/server/models/user/user.ts
@@ -27,10 +27,10 @@ let toFormattedJSON: UserMethods.ToFormattedJSON
27let isAdmin: UserMethods.IsAdmin 27let isAdmin: UserMethods.IsAdmin
28let countTotal: UserMethods.CountTotal 28let countTotal: UserMethods.CountTotal
29let getByUsername: UserMethods.GetByUsername 29let getByUsername: UserMethods.GetByUsername
30let list: UserMethods.List
31let listForApi: UserMethods.ListForApi 30let listForApi: UserMethods.ListForApi
32let loadById: UserMethods.LoadById 31let loadById: UserMethods.LoadById
33let loadByUsername: UserMethods.LoadByUsername 32let loadByUsername: UserMethods.LoadByUsername
33let loadByUsernameAndPopulateChannels: UserMethods.LoadByUsernameAndPopulateChannels
34let loadByUsernameOrEmail: UserMethods.LoadByUsernameOrEmail 34let loadByUsernameOrEmail: UserMethods.LoadByUsernameOrEmail
35let isAbleToUploadVideo: UserMethods.IsAbleToUploadVideo 35let isAbleToUploadVideo: UserMethods.IsAbleToUploadVideo
36 36
@@ -113,10 +113,10 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
113 113
114 countTotal, 114 countTotal,
115 getByUsername, 115 getByUsername,
116 list,
117 listForApi, 116 listForApi,
118 loadById, 117 loadById,
119 loadByUsername, 118 loadByUsername,
119 loadByUsernameAndPopulateChannels,
120 loadByUsernameOrEmail 120 loadByUsernameOrEmail
121 ] 121 ]
122 const instanceMethods = [ 122 const instanceMethods = [
@@ -144,15 +144,34 @@ isPasswordMatch = function (this: UserInstance, password: string) {
144} 144}
145 145
146toFormattedJSON = function (this: UserInstance) { 146toFormattedJSON = function (this: UserInstance) {
147 return { 147 const json = {
148 id: this.id, 148 id: this.id,
149 username: this.username, 149 username: this.username,
150 email: this.email, 150 email: this.email,
151 displayNSFW: this.displayNSFW, 151 displayNSFW: this.displayNSFW,
152 role: this.role, 152 role: this.role,
153 videoQuota: this.videoQuota, 153 videoQuota: this.videoQuota,
154 createdAt: this.createdAt 154 createdAt: this.createdAt,
155 author: {
156 id: this.Author.id,
157 uuid: this.Author.uuid
158 }
155 } 159 }
160
161 if (Array.isArray(this.Author.VideoChannels) === true) {
162 const videoChannels = this.Author.VideoChannels
163 .map(c => c.toFormattedJSON())
164 .sort((v1, v2) => {
165 if (v1.createdAt < v2.createdAt) return -1
166 if (v1.createdAt === v2.createdAt) return 0
167
168 return 1
169 })
170
171 json['videoChannels'] = videoChannels
172 }
173
174 return json
156} 175}
157 176
158isAdmin = function (this: UserInstance) { 177isAdmin = function (this: UserInstance) {
@@ -189,21 +208,19 @@ getByUsername = function (username: string) {
189 const query = { 208 const query = {
190 where: { 209 where: {
191 username: username 210 username: username
192 } 211 },
212 include: [ { model: User['sequelize'].models.Author, required: true } ]
193 } 213 }
194 214
195 return User.findOne(query) 215 return User.findOne(query)
196} 216}
197 217
198list = function () {
199 return User.findAll()
200}
201
202listForApi = function (start: number, count: number, sort: string) { 218listForApi = function (start: number, count: number, sort: string) {
203 const query = { 219 const query = {
204 offset: start, 220 offset: start,
205 limit: count, 221 limit: count,
206 order: [ getSort(sort) ] 222 order: [ getSort(sort) ],
223 include: [ { model: User['sequelize'].models.Author, required: true } ]
207 } 224 }
208 225
209 return User.findAndCountAll(query).then(({ rows, count }) => { 226 return User.findAndCountAll(query).then(({ rows, count }) => {
@@ -215,14 +232,36 @@ listForApi = function (start: number, count: number, sort: string) {
215} 232}
216 233
217loadById = function (id: number) { 234loadById = function (id: number) {
218 return User.findById(id) 235 const options = {
236 include: [ { model: User['sequelize'].models.Author, required: true } ]
237 }
238
239 return User.findById(id, options)
219} 240}
220 241
221loadByUsername = function (username: string) { 242loadByUsername = function (username: string) {
222 const query = { 243 const query = {
223 where: { 244 where: {
224 username 245 username
225 } 246 },
247 include: [ { model: User['sequelize'].models.Author, required: true } ]
248 }
249
250 return User.findOne(query)
251}
252
253loadByUsernameAndPopulateChannels = function (username: string) {
254 const query = {
255 where: {
256 username
257 },
258 include: [
259 {
260 model: User['sequelize'].models.Author,
261 required: true,
262 include: [ User['sequelize'].models.VideoChannel ]
263 }
264 ]
226 } 265 }
227 266
228 return User.findOne(query) 267 return User.findOne(query)
@@ -230,6 +269,7 @@ loadByUsername = function (username: string) {
230 269
231loadByUsernameOrEmail = function (username: string, email: string) { 270loadByUsernameOrEmail = function (username: string, email: string) {
232 const query = { 271 const query = {
272 include: [ { model: User['sequelize'].models.Author, required: true } ],
233 where: { 273 where: {
234 $or: [ { username }, { email } ] 274 $or: [ { username }, { email } ]
235 } 275 }
@@ -242,11 +282,12 @@ loadByUsernameOrEmail = function (username: string, email: string) {
242// --------------------------------------------------------------------------- 282// ---------------------------------------------------------------------------
243 283
244function getOriginalVideoFileTotalFromUser (user: UserInstance) { 284function getOriginalVideoFileTotalFromUser (user: UserInstance) {
245 // Don't use sequelize because we need to use a subquery 285 // Don't use sequelize because we need to use a sub query
246 const query = 'SELECT SUM("size") AS "total" FROM ' + 286 const query = 'SELECT SUM("size") AS "total" FROM ' +
247 '(SELECT MAX("VideoFiles"."size") AS "size" FROM "VideoFiles" ' + 287 '(SELECT MAX("VideoFiles"."size") AS "size" FROM "VideoFiles" ' +
248 'INNER JOIN "Videos" ON "VideoFiles"."videoId" = "Videos"."id" ' + 288 'INNER JOIN "Videos" ON "VideoFiles"."videoId" = "Videos"."id" ' +
249 'INNER JOIN "Authors" ON "Videos"."authorId" = "Authors"."id" ' + 289 'INNER JOIN "VideoChannels" ON "VideoChannels"."id" = "Videos"."channelId" ' +
290 'INNER JOIN "Authors" ON "VideoChannels"."authorId" = "Authors"."id" ' +
250 'INNER JOIN "Users" ON "Authors"."userId" = "Users"."id" ' + 291 'INNER JOIN "Users" ON "Authors"."userId" = "Users"."id" ' +
251 'WHERE "Users"."id" = $userId GROUP BY "Videos"."id") t' 292 'WHERE "Users"."id" = $userId GROUP BY "Videos"."id") t'
252 293
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 }