diff options
author | Chocobozzz <florian.bigard@gmail.com> | 2017-10-24 19:41:09 +0200 |
---|---|---|
committer | Chocobozzz <florian.bigard@gmail.com> | 2017-10-26 09:11:38 +0200 |
commit | 72c7248b6fdcdb2175e726ff51b42e7555f2bd84 (patch) | |
tree | 1bfdee99dbe2392cc997edba8e314e2a8a401c72 /server/models | |
parent | 8113a93a0d9f31aa9e23702bbc31b8a76275ae22 (diff) | |
download | PeerTube-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.ts | 24 | ||||
-rw-r--r-- | server/models/request/request-video-event.ts | 5 | ||||
-rw-r--r-- | server/models/user/user-interface.ts | 8 | ||||
-rw-r--r-- | server/models/user/user.ts | 69 | ||||
-rw-r--r-- | server/models/video/author-interface.ts | 29 | ||||
-rw-r--r-- | server/models/video/author.ts | 103 | ||||
-rw-r--r-- | server/models/video/index.ts | 1 | ||||
-rw-r--r-- | server/models/video/video-channel-interface.ts | 64 | ||||
-rw-r--r-- | server/models/video/video-channel.ts | 349 | ||||
-rw-r--r-- | server/models/video/video-interface.ts | 16 | ||||
-rw-r--r-- | server/models/video/video.ts | 180 |
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' | |||
5 | import { User as FormattedUser } from '../../../shared/models/users/user.model' | 5 | import { User as FormattedUser } from '../../../shared/models/users/user.model' |
6 | import { UserRole } from '../../../shared/models/users/user-role.type' | 6 | import { UserRole } from '../../../shared/models/users/user-role.type' |
7 | import { ResultList } from '../../../shared/models/result-list.model' | 7 | import { ResultList } from '../../../shared/models/result-list.model' |
8 | import { AuthorInstance } from '../video/author-interface' | ||
8 | 9 | ||
9 | export namespace UserMethods { | 10 | export 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 | ||
56 | export interface UserInstance extends UserClass, UserAttributes, Sequelize.Instance<UserAttributes> { | 58 | export 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 | |||
27 | let isAdmin: UserMethods.IsAdmin | 27 | let isAdmin: UserMethods.IsAdmin |
28 | let countTotal: UserMethods.CountTotal | 28 | let countTotal: UserMethods.CountTotal |
29 | let getByUsername: UserMethods.GetByUsername | 29 | let getByUsername: UserMethods.GetByUsername |
30 | let list: UserMethods.List | ||
31 | let listForApi: UserMethods.ListForApi | 30 | let listForApi: UserMethods.ListForApi |
32 | let loadById: UserMethods.LoadById | 31 | let loadById: UserMethods.LoadById |
33 | let loadByUsername: UserMethods.LoadByUsername | 32 | let loadByUsername: UserMethods.LoadByUsername |
33 | let loadByUsernameAndPopulateChannels: UserMethods.LoadByUsernameAndPopulateChannels | ||
34 | let loadByUsernameOrEmail: UserMethods.LoadByUsernameOrEmail | 34 | let loadByUsernameOrEmail: UserMethods.LoadByUsernameOrEmail |
35 | let isAbleToUploadVideo: UserMethods.IsAbleToUploadVideo | 35 | let 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 | ||
146 | toFormattedJSON = function (this: UserInstance) { | 146 | toFormattedJSON = 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 | ||
158 | isAdmin = function (this: UserInstance) { | 177 | isAdmin = 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 | ||
198 | list = function () { | ||
199 | return User.findAll() | ||
200 | } | ||
201 | |||
202 | listForApi = function (start: number, count: number, sort: string) { | 218 | listForApi = 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 | ||
217 | loadById = function (id: number) { | 234 | loadById = 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 | ||
221 | loadByUsername = function (username: string) { | 242 | loadByUsername = 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 | |||
253 | loadByUsernameAndPopulateChannels = 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 | ||
231 | loadByUsernameOrEmail = function (username: string, email: string) { | 270 | loadByUsernameOrEmail = 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 | ||
244 | function getOriginalVideoFileTotalFromUser (user: UserInstance) { | 284 | function 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' | |||
2 | import * as Promise from 'bluebird' | 2 | import * as Promise from 'bluebird' |
3 | 3 | ||
4 | import { PodInstance } from '../pod/pod-interface' | 4 | import { PodInstance } from '../pod/pod-interface' |
5 | import { RemoteVideoAuthorCreateData } from '../../../shared/models/pods/remote-video/remote-video-author-create-request.model' | ||
6 | import { VideoChannelInstance } from './video-channel-interface' | ||
5 | 7 | ||
6 | export namespace AuthorMethods { | 8 | export 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 | ||
15 | export interface AuthorClass { | 18 | export 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 | ||
19 | export interface AuthorAttributes { | 25 | export interface AuthorAttributes { |
20 | name: string | 26 | name: string |
27 | uuid?: string | ||
28 | |||
29 | podId?: number | ||
30 | userId?: number | ||
21 | } | 31 | } |
22 | 32 | ||
23 | export interface AuthorInstance extends AuthorClass, AuthorAttributes, Sequelize.Instance<AuthorAttributes> { | 33 | export 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 | ||
32 | export interface AuthorModel extends AuthorClass, Sequelize.Model<AuthorInstance, AuthorAttributes> {} | 45 | export 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | import { isUserUsernameValid } from '../../helpers' | 3 | import { isUserUsernameValid } from '../../helpers' |
4 | import { removeVideoAuthorToFriends } from '../../lib' | ||
4 | 5 | ||
5 | import { addMethodsToModel } from '../utils' | 6 | import { addMethodsToModel } from '../utils' |
6 | import { | 7 | import { |
@@ -11,11 +12,24 @@ import { | |||
11 | } from './author-interface' | 12 | } from './author-interface' |
12 | 13 | ||
13 | let Author: Sequelize.Model<AuthorInstance, AuthorAttributes> | 14 | let Author: Sequelize.Model<AuthorInstance, AuthorAttributes> |
14 | let findOrCreateAuthor: AuthorMethods.FindOrCreateAuthor | 15 | let loadAuthorByPodAndUUID: AuthorMethods.LoadAuthorByPodAndUUID |
16 | let load: AuthorMethods.Load | ||
17 | let loadByUUID: AuthorMethods.LoadByUUID | ||
18 | let listOwned: AuthorMethods.ListOwned | ||
19 | let isOwned: AuthorMethods.IsOwned | ||
20 | let toAddRemoteJSON: AuthorMethods.ToAddRemoteJSON | ||
15 | 21 | ||
16 | export default function defineAuthor (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { | 22 | export 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 | ||
84 | findOrCreateAuthor = function (name: string, podId: number, userId: number, transaction: Sequelize.Transaction) { | 110 | function 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 | |||
122 | toAddRemoteJSON = function (this: AuthorInstance) { | ||
123 | const json = { | ||
124 | uuid: this.uuid, | ||
125 | name: this.name | ||
126 | } | ||
127 | |||
128 | return json | ||
129 | } | ||
130 | |||
131 | isOwned = function (this: AuthorInstance) { | ||
132 | return this.podId === null | ||
133 | } | ||
134 | |||
135 | // ------------------------------ STATICS ------------------------------ | ||
136 | |||
137 | listOwned = function () { | ||
138 | const query: Sequelize.FindOptions<AuthorAttributes> = { | ||
139 | where: { | ||
140 | podId: null | ||
141 | } | ||
142 | } | ||
143 | |||
144 | return Author.findAll(query) | ||
145 | } | ||
146 | |||
147 | load = function (id: number) { | ||
148 | return Author.findById(id) | ||
149 | } | ||
150 | |||
151 | loadByUUID = 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 | |
161 | loadAuthorByPodAndUUID = 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' | |||
2 | export * from './tag-interface' | 2 | export * from './tag-interface' |
3 | export * from './video-abuse-interface' | 3 | export * from './video-abuse-interface' |
4 | export * from './video-blacklist-interface' | 4 | export * from './video-blacklist-interface' |
5 | export * from './video-channel-interface' | ||
5 | export * from './video-tag-interface' | 6 | export * from './video-tag-interface' |
6 | export * from './video-file-interface' | 7 | export * from './video-file-interface' |
7 | export * from './video-interface' | 8 | export * 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | import * as Promise from 'bluebird' | ||
3 | |||
4 | import { ResultList, RemoteVideoChannelCreateData, RemoteVideoChannelUpdateData } from '../../../shared' | ||
5 | |||
6 | // Don't use barrel, import just what we need | ||
7 | import { VideoChannel as FormattedVideoChannel } from '../../../shared/models/videos/video-channel.model' | ||
8 | import { AuthorInstance } from './author-interface' | ||
9 | import { VideoInstance } from './video-interface' | ||
10 | |||
11 | export 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 | |||
29 | export 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 | |||
42 | export 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 | |||
53 | export 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 | |||
64 | export 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | import { isVideoChannelNameValid, isVideoChannelDescriptionValid } from '../../helpers' | ||
4 | import { removeVideoChannelToFriends } from '../../lib' | ||
5 | |||
6 | import { addMethodsToModel, getSort } from '../utils' | ||
7 | import { | ||
8 | VideoChannelInstance, | ||
9 | VideoChannelAttributes, | ||
10 | |||
11 | VideoChannelMethods | ||
12 | } from './video-channel-interface' | ||
13 | |||
14 | let VideoChannel: Sequelize.Model<VideoChannelInstance, VideoChannelAttributes> | ||
15 | let toFormattedJSON: VideoChannelMethods.ToFormattedJSON | ||
16 | let toAddRemoteJSON: VideoChannelMethods.ToAddRemoteJSON | ||
17 | let toUpdateRemoteJSON: VideoChannelMethods.ToUpdateRemoteJSON | ||
18 | let isOwned: VideoChannelMethods.IsOwned | ||
19 | let countByAuthor: VideoChannelMethods.CountByAuthor | ||
20 | let listOwned: VideoChannelMethods.ListOwned | ||
21 | let listForApi: VideoChannelMethods.ListForApi | ||
22 | let listByAuthor: VideoChannelMethods.ListByAuthor | ||
23 | let loadByIdAndAuthor: VideoChannelMethods.LoadByIdAndAuthor | ||
24 | let loadByUUID: VideoChannelMethods.LoadByUUID | ||
25 | let loadAndPopulateAuthor: VideoChannelMethods.LoadAndPopulateAuthor | ||
26 | let loadByUUIDAndPopulateAuthor: VideoChannelMethods.LoadByUUIDAndPopulateAuthor | ||
27 | let loadByHostAndUUID: VideoChannelMethods.LoadByHostAndUUID | ||
28 | let loadAndPopulateAuthorAndVideos: VideoChannelMethods.LoadAndPopulateAuthorAndVideos | ||
29 | |||
30 | export 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 | |||
106 | isOwned = function (this: VideoChannelInstance) { | ||
107 | return this.remote === false | ||
108 | } | ||
109 | |||
110 | toFormattedJSON = 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 | |||
135 | toAddRemoteJSON = 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 | |||
148 | toUpdateRemoteJSON = 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 | |||
163 | function 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 | |||
181 | function 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 | |||
193 | countByAuthor = function (authorId: number) { | ||
194 | const query = { | ||
195 | where: { | ||
196 | authorId | ||
197 | } | ||
198 | } | ||
199 | |||
200 | return VideoChannel.count(query) | ||
201 | } | ||
202 | |||
203 | listOwned = 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 | |||
214 | listForApi = 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 | |||
233 | listByAuthor = 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 | |||
253 | loadByUUID = 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 | |||
265 | loadByHostAndUUID = 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 | |||
291 | loadByIdAndAuthor = 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 | |||
308 | loadAndPopulateAuthor = 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 | |||
321 | loadByUUIDAndPopulateAuthor = 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 | |||
337 | loadAndPopulateAuthorAndVideos = 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' | |||
6 | import { VideoFileAttributes, VideoFileInstance } from './video-file-interface' | 6 | import { 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 |
9 | import { Video as FormattedVideo } from '../../../shared/models/videos/video.model' | 9 | import { |
10 | Video as FormattedVideo, | ||
11 | VideoDetails as FormattedDetailsVideo | ||
12 | } from '../../../shared/models/videos/video.model' | ||
10 | import { RemoteVideoUpdateData } from '../../../shared/models/pods/remote-video/remote-video-update-request.model' | 13 | import { RemoteVideoUpdateData } from '../../../shared/models/pods/remote-video/remote-video-update-request.model' |
11 | import { RemoteVideoCreateData } from '../../../shared/models/pods/remote-video/remote-video-create-request.model' | 14 | import { RemoteVideoCreateData } from '../../../shared/models/pods/remote-video/remote-video-create-request.model' |
12 | import { ResultList } from '../../../shared/models/result-list.model' | 15 | import { ResultList } from '../../../shared/models/result-list.model' |
16 | import { VideoChannelInstance } from './video-channel-interface' | ||
13 | 17 | ||
14 | export namespace VideoMethods { | 18 | export 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 | |||
60 | let getTorrentFileName: VideoMethods.GetTorrentFileName | 60 | let getTorrentFileName: VideoMethods.GetTorrentFileName |
61 | let isOwned: VideoMethods.IsOwned | 61 | let isOwned: VideoMethods.IsOwned |
62 | let toFormattedJSON: VideoMethods.ToFormattedJSON | 62 | let toFormattedJSON: VideoMethods.ToFormattedJSON |
63 | let toFormattedDetailsJSON: VideoMethods.ToFormattedDetailsJSON | ||
63 | let toAddRemoteJSON: VideoMethods.ToAddRemoteJSON | 64 | let toAddRemoteJSON: VideoMethods.ToAddRemoteJSON |
64 | let toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON | 65 | let toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON |
65 | let optimizeOriginalVideofile: VideoMethods.OptimizeOriginalVideofile | 66 | let 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 | ||
284 | function associate (models) { | 286 | function 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) { | |||
439 | toFormattedJSON = function (this: VideoInstance) { | 441 | toFormattedJSON = 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 | |||
493 | toFormattedDetailsJSON = 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 | ||
743 | loadByHostAndUUID = function (fromHost: string, uuid: string) { | 807 | loadByHostAndUUID = 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 | ||
810 | loadByUUID = function (uuid: string) { | 889 | loadByUUID = 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 | ||
820 | loadAndPopulateAuthor = function (id: number) { | 902 | loadAndPopulateAuthor = 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 } |