aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--client/src/app/shared/video/video-details.model.ts1
-rw-r--r--client/src/app/shared/video/video.model.ts2
-rw-r--r--server/middlewares/validators/video-channels.ts2
-rw-r--r--server/models/account/user.ts50
-rw-r--r--server/models/application/application.ts18
-rw-r--r--server/models/oauth/oauth-token.ts59
-rw-r--r--server/models/video/video-channel-share.ts43
-rw-r--r--server/models/video/video-channel.ts113
-rw-r--r--server/models/video/video-share.ts36
-rw-r--r--server/models/video/video.ts243
-rw-r--r--server/tests/api/single-server.ts3
-rw-r--r--server/tests/api/video-abuse.ts2
-rw-r--r--shared/models/videos/video.model.ts2
13 files changed, 263 insertions, 311 deletions
diff --git a/client/src/app/shared/video/video-details.model.ts b/client/src/app/shared/video/video-details.model.ts
index b96f8f6c8..d51bc01a7 100644
--- a/client/src/app/shared/video/video-details.model.ts
+++ b/client/src/app/shared/video/video-details.model.ts
@@ -58,6 +58,7 @@ export class VideoDetails extends Video implements VideoDetailsServerModel {
58 this.files = hash.files 58 this.files = hash.files
59 this.channel = hash.channel 59 this.channel = hash.channel
60 this.account = hash.account 60 this.account = hash.account
61 this.tags = hash.tags
61 62
62 this.likesPercent = (this.likes / (this.likes + this.dislikes)) * 100 63 this.likesPercent = (this.likes / (this.likes + this.dislikes)) * 100
63 this.dislikesPercent = (this.dislikes / (this.likes + this.dislikes)) * 100 64 this.dislikesPercent = (this.dislikes / (this.likes + this.dislikes)) * 100
diff --git a/client/src/app/shared/video/video.model.ts b/client/src/app/shared/video/video.model.ts
index 32c33829d..c3759cb65 100644
--- a/client/src/app/shared/video/video.model.ts
+++ b/client/src/app/shared/video/video.model.ts
@@ -22,7 +22,6 @@ export class Video implements VideoServerModel {
22 isLocal: boolean 22 isLocal: boolean
23 name: string 23 name: string
24 serverHost: string 24 serverHost: string
25 tags: string[]
26 thumbnailPath: string 25 thumbnailPath: string
27 thumbnailUrl: string 26 thumbnailUrl: string
28 previewPath: string 27 previewPath: string
@@ -71,7 +70,6 @@ export class Video implements VideoServerModel {
71 this.isLocal = hash.isLocal 70 this.isLocal = hash.isLocal
72 this.name = hash.name 71 this.name = hash.name
73 this.serverHost = hash.serverHost 72 this.serverHost = hash.serverHost
74 this.tags = hash.tags
75 this.thumbnailPath = hash.thumbnailPath 73 this.thumbnailPath = hash.thumbnailPath
76 this.thumbnailUrl = absoluteAPIUrl + hash.thumbnailPath 74 this.thumbnailUrl = absoluteAPIUrl + hash.thumbnailPath
77 this.previewPath = hash.previewPath 75 this.previewPath = hash.previewPath
diff --git a/server/middlewares/validators/video-channels.ts b/server/middlewares/validators/video-channels.ts
index 660390080..068fd210f 100644
--- a/server/middlewares/validators/video-channels.ts
+++ b/server/middlewares/validators/video-channels.ts
@@ -78,7 +78,7 @@ const videoChannelsRemoveValidator = [
78 if (!await isVideoChannelExist(req.params.id, res)) return 78 if (!await isVideoChannelExist(req.params.id, res)) return
79 79
80 // Check if the user who did the request is able to delete the video 80 // Check if the user who did the request is able to delete the video
81 if (!checkUserCanDeleteVideoChannel(res.locals.user, res.locals.videoChannel, res)) return 81 if (!checkUserCanDeleteVideoChannel(res.locals.oauth.token.User, res.locals.videoChannel, res)) return
82 if (!await checkVideoChannelIsNotTheLastOne(res)) return 82 if (!await checkVideoChannelIsNotTheLastOne(res)) return
83 83
84 return next() 84 return next()
diff --git a/server/models/account/user.ts b/server/models/account/user.ts
index 84adad96e..26f04dcb5 100644
--- a/server/models/account/user.ts
+++ b/server/models/account/user.ts
@@ -5,12 +5,12 @@ import {
5 BeforeUpdate, 5 BeforeUpdate,
6 Column, CreatedAt, 6 Column, CreatedAt,
7 DataType, 7 DataType,
8 Default, 8 Default, DefaultScope,
9 HasMany, 9 HasMany,
10 HasOne, 10 HasOne,
11 Is, 11 Is,
12 IsEmail, 12 IsEmail,
13 Model, 13 Model, Scopes,
14 Table, UpdatedAt 14 Table, UpdatedAt
15} from 'sequelize-typescript' 15} from 'sequelize-typescript'
16import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared' 16import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared'
@@ -27,6 +27,25 @@ import { getSort, throwIfNotValid } from '../utils'
27import { VideoChannelModel } from '../video/video-channel' 27import { VideoChannelModel } from '../video/video-channel'
28import { AccountModel } from './account' 28import { AccountModel } from './account'
29 29
30@DefaultScope({
31 include: [
32 {
33 model: () => AccountModel,
34 required: true
35 }
36 ]
37})
38@Scopes({
39 withVideoChannel: {
40 include: [
41 {
42 model: () => AccountModel,
43 required: true,
44 include: [ () => VideoChannelModel ]
45 }
46 ]
47 }
48})
30@Table({ 49@Table({
31 tableName: 'user', 50 tableName: 'user',
32 indexes: [ 51 indexes: [
@@ -122,8 +141,7 @@ export class UserModel extends Model<UserModel> {
122 const query = { 141 const query = {
123 offset: start, 142 offset: start,
124 limit: count, 143 limit: count,
125 order: [ getSort(sort) ], 144 order: [ getSort(sort) ]
126 include: [ { model: AccountModel, required: true } ]
127 } 145 }
128 146
129 return UserModel.findAndCountAll(query) 147 return UserModel.findAndCountAll(query)
@@ -136,19 +154,14 @@ export class UserModel extends Model<UserModel> {
136 } 154 }
137 155
138 static loadById (id: number) { 156 static loadById (id: number) {
139 const options = { 157 return UserModel.findById(id)
140 include: [ { model: AccountModel, required: true } ]
141 }
142
143 return UserModel.findById(id, options)
144 } 158 }
145 159
146 static loadByUsername (username: string) { 160 static loadByUsername (username: string) {
147 const query = { 161 const query = {
148 where: { 162 where: {
149 username 163 username
150 }, 164 }
151 include: [ { model: AccountModel, required: true } ]
152 } 165 }
153 166
154 return UserModel.findOne(query) 167 return UserModel.findOne(query)
@@ -158,29 +171,20 @@ export class UserModel extends Model<UserModel> {
158 const query = { 171 const query = {
159 where: { 172 where: {
160 username 173 username
161 }, 174 }
162 include: [
163 {
164 model: AccountModel,
165 required: true,
166 include: [ VideoChannelModel ]
167 }
168 ]
169 } 175 }
170 176
171 return UserModel.findOne(query) 177 return UserModel.scope('withVideoChannel').findOne(query)
172 } 178 }
173 179
174 static loadByUsernameOrEmail (username: string, email: string) { 180 static loadByUsernameOrEmail (username: string, email: string) {
175 const query = { 181 const query = {
176 include: [ { model: AccountModel, required: true } ],
177 where: { 182 where: {
178 [ Sequelize.Op.or ]: [ { username }, { email } ] 183 [ Sequelize.Op.or ]: [ { username }, { email } ]
179 } 184 }
180 } 185 }
181 186
182 // FIXME: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18387 187 return UserModel.findOne(query)
183 return (UserModel as any).findOne(query)
184 } 188 }
185 189
186 private static getOriginalVideoFileTotalFromUser (user: UserModel) { 190 private static getOriginalVideoFileTotalFromUser (user: UserModel) {
diff --git a/server/models/application/application.ts b/server/models/application/application.ts
index f3c0f1052..9fc07e850 100644
--- a/server/models/application/application.ts
+++ b/server/models/application/application.ts
@@ -1,4 +1,3 @@
1import { Transaction } from 'sequelize'
2import { AllowNull, Column, Default, IsInt, Model, Table } from 'sequelize-typescript' 1import { AllowNull, Column, Default, IsInt, Model, Table } from 'sequelize-typescript'
3 2
4@Table({ 3@Table({
@@ -15,21 +14,4 @@ export class ApplicationModel extends Model<ApplicationModel> {
15 static countTotal () { 14 static countTotal () {
16 return ApplicationModel.count() 15 return ApplicationModel.count()
17 } 16 }
18
19 static loadMigrationVersion () {
20 const query = {
21 attributes: [ 'migrationVersion' ]
22 }
23
24 return ApplicationModel.findOne(query).then(data => data ? data.migrationVersion : null)
25 }
26
27 static updateMigrationVersion (newVersion: number, transaction: Transaction) {
28 const options = {
29 where: {},
30 transaction: transaction
31 }
32
33 return ApplicationModel.update({ migrationVersion: newVersion }, options)
34 }
35} 17}
diff --git a/server/models/oauth/oauth-token.ts b/server/models/oauth/oauth-token.ts
index 0d21c42fd..995fa33d5 100644
--- a/server/models/oauth/oauth-token.ts
+++ b/server/models/oauth/oauth-token.ts
@@ -1,4 +1,4 @@
1import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' 1import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
2import { logger } from '../../helpers' 2import { logger } from '../../helpers'
3import { AccountModel } from '../account/account' 3import { AccountModel } from '../account/account'
4import { UserModel } from '../account/user' 4import { UserModel } from '../account/user'
@@ -15,6 +15,25 @@ export type OAuthTokenInfo = {
15 } 15 }
16} 16}
17 17
18enum ScopeNames {
19 WITH_ACCOUNT = 'WITH_ACCOUNT'
20}
21
22@Scopes({
23 [ScopeNames.WITH_ACCOUNT]: {
24 include: [
25 {
26 model: () => UserModel,
27 include: [
28 {
29 model: () => AccountModel,
30 required: true
31 }
32 ]
33 }
34 ]
35 }
36})
18@Table({ 37@Table({
19 tableName: 'oAuthToken', 38 tableName: 'oAuthToken',
20 indexes: [ 39 indexes: [
@@ -115,21 +134,10 @@ export class OAuthTokenModel extends Model<OAuthTokenModel> {
115 const query = { 134 const query = {
116 where: { 135 where: {
117 accessToken: bearerToken 136 accessToken: bearerToken
118 }, 137 }
119 include: [
120 {
121 model: UserModel,
122 include: [
123 {
124 model: AccountModel,
125 required: true
126 }
127 ]
128 }
129 ]
130 } 138 }
131 139
132 return OAuthTokenModel.findOne(query).then(token => { 140 return OAuthTokenModel.scope(ScopeNames.WITH_ACCOUNT).findOne(query).then(token => {
133 if (token) token['user'] = token.User 141 if (token) token['user'] = token.User
134 142
135 return token 143 return token
@@ -140,24 +148,15 @@ export class OAuthTokenModel extends Model<OAuthTokenModel> {
140 const query = { 148 const query = {
141 where: { 149 where: {
142 refreshToken: refreshToken 150 refreshToken: refreshToken
143 }, 151 }
144 include: [
145 {
146 model: UserModel,
147 include: [
148 {
149 model: AccountModel,
150 required: true
151 }
152 ]
153 }
154 ]
155 } 152 }
156 153
157 return OAuthTokenModel.findOne(query).then(token => { 154 return OAuthTokenModel.scope(ScopeNames.WITH_ACCOUNT)
158 token['user'] = token.User 155 .findOne(query)
156 .then(token => {
157 token['user'] = token.User
159 158
160 return token 159 return token
161 }) 160 })
162 } 161 }
163} 162}
diff --git a/server/models/video/video-channel-share.ts b/server/models/video/video-channel-share.ts
index cdba32fcd..f5b7a7cd5 100644
--- a/server/models/video/video-channel-share.ts
+++ b/server/models/video/video-channel-share.ts
@@ -1,8 +1,35 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' 2import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
3import { AccountModel } from '../account/account' 3import { AccountModel } from '../account/account'
4import { VideoChannelModel } from './video-channel' 4import { VideoChannelModel } from './video-channel'
5 5
6enum ScopeNames {
7 FULL = 'FULL',
8 WITH_ACCOUNT = 'WITH_ACCOUNT'
9}
10
11@Scopes({
12 [ScopeNames.FULL]: {
13 include: [
14 {
15 model: () => AccountModel,
16 required: true
17 },
18 {
19 model: () => VideoChannelModel,
20 required: true
21 }
22 ]
23 },
24 [ScopeNames.WITH_ACCOUNT]: {
25 include: [
26 {
27 model: () => AccountModel,
28 required: true
29 }
30 ]
31 }
32})
6@Table({ 33@Table({
7 tableName: 'videoChannelShare', 34 tableName: 'videoChannelShare',
8 indexes: [ 35 indexes: [
@@ -46,15 +73,11 @@ export class VideoChannelShareModel extends Model<VideoChannelShareModel> {
46 VideoChannel: VideoChannelModel 73 VideoChannel: VideoChannelModel
47 74
48 static load (accountId: number, videoChannelId: number, t: Sequelize.Transaction) { 75 static load (accountId: number, videoChannelId: number, t: Sequelize.Transaction) {
49 return VideoChannelShareModel.findOne({ 76 return VideoChannelShareModel.scope(ScopeNames.FULL).findOne({
50 where: { 77 where: {
51 accountId, 78 accountId,
52 videoChannelId 79 videoChannelId
53 }, 80 },
54 include: [
55 AccountModel,
56 VideoChannelModel
57 ],
58 transaction: t 81 transaction: t
59 }) 82 })
60 } 83 }
@@ -64,16 +87,10 @@ export class VideoChannelShareModel extends Model<VideoChannelShareModel> {
64 where: { 87 where: {
65 videoChannelId 88 videoChannelId
66 }, 89 },
67 include: [
68 {
69 model: AccountModel,
70 required: true
71 }
72 ],
73 transaction: t 90 transaction: t
74 } 91 }
75 92
76 return VideoChannelShareModel.findAll(query) 93 return VideoChannelShareModel.scope(ScopeNames.WITH_ACCOUNT).findAll(query)
77 .then(res => res.map(r => r.Account)) 94 .then(res => res.map(r => r.Account))
78 } 95 }
79} 96}
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts
index 9b545a4ef..068c8029d 100644
--- a/server/models/video/video-channel.ts
+++ b/server/models/video/video-channel.ts
@@ -11,7 +11,7 @@ import {
11 HasMany, 11 HasMany,
12 Is, 12 Is,
13 IsUUID, 13 IsUUID,
14 Model, 14 Model, Scopes,
15 Table, 15 Table,
16 UpdatedAt 16 UpdatedAt
17} from 'sequelize-typescript' 17} from 'sequelize-typescript'
@@ -28,6 +28,26 @@ import { getSort, throwIfNotValid } from '../utils'
28import { VideoModel } from './video' 28import { VideoModel } from './video'
29import { VideoChannelShareModel } from './video-channel-share' 29import { VideoChannelShareModel } from './video-channel-share'
30 30
31enum ScopeNames {
32 WITH_ACCOUNT = 'WITH_ACCOUNT',
33 WITH_VIDEOS = 'WITH_VIDEOS'
34}
35
36@Scopes({
37 [ScopeNames.WITH_ACCOUNT]: {
38 include: [
39 {
40 model: () => AccountModel,
41 include: [ { model: () => ServerModel, required: false } ]
42 }
43 ]
44 },
45 [ScopeNames.WITH_VIDEOS]: {
46 include: [
47 () => VideoModel
48 ]
49 }
50})
31@Table({ 51@Table({
32 tableName: 'videoChannel', 52 tableName: 'videoChannel',
33 indexes: [ 53 indexes: [
@@ -122,17 +142,10 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
122 const query = { 142 const query = {
123 offset: start, 143 offset: start,
124 limit: count, 144 limit: count,
125 order: [ getSort(sort) ], 145 order: [ getSort(sort) ]
126 include: [
127 {
128 model: AccountModel,
129 required: true,
130 include: [ { model: ServerModel, required: false } ]
131 }
132 ]
133 } 146 }
134 147
135 return VideoChannelModel.findAndCountAll(query) 148 return VideoChannelModel.scope(ScopeNames.WITH_ACCOUNT).findAndCountAll(query)
136 .then(({ rows, count }) => { 149 .then(({ rows, count }) => {
137 return { total: count, data: rows } 150 return { total: count, data: rows }
138 }) 151 })
@@ -159,29 +172,16 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
159 }) 172 })
160 } 173 }
161 174
162 static loadByUUID (uuid: string, t?: Sequelize.Transaction) {
163 const query: IFindOptions<VideoChannelModel> = {
164 where: {
165 uuid
166 }
167 }
168
169 if (t !== undefined) query.transaction = t
170
171 return VideoChannelModel.findOne(query)
172 }
173
174 static loadByUrl (url: string, t?: Sequelize.Transaction) { 175 static loadByUrl (url: string, t?: Sequelize.Transaction) {
175 const query: IFindOptions<VideoChannelModel> = { 176 const query: IFindOptions<VideoChannelModel> = {
176 where: { 177 where: {
177 url 178 url
178 }, 179 }
179 include: [ AccountModel ]
180 } 180 }
181 181
182 if (t !== undefined) query.transaction = t 182 if (t !== undefined) query.transaction = t
183 183
184 return VideoChannelModel.findOne(query) 184 return VideoChannelModel.scope(ScopeNames.WITH_ACCOUNT).findOne(query)
185 } 185 }
186 186
187 static loadByUUIDOrUrl (uuid: string, url: string, t?: Sequelize.Transaction) { 187 static loadByUUIDOrUrl (uuid: string, url: string, t?: Sequelize.Transaction) {
@@ -199,90 +199,39 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
199 return VideoChannelModel.findOne(query) 199 return VideoChannelModel.findOne(query)
200 } 200 }
201 201
202 static loadByHostAndUUID (fromHost: string, uuid: string, t?: Sequelize.Transaction) {
203 const query: IFindOptions<VideoChannelModel> = {
204 where: {
205 uuid
206 },
207 include: [
208 {
209 model: AccountModel,
210 include: [
211 {
212 model: ServerModel,
213 required: true,
214 where: {
215 host: fromHost
216 }
217 }
218 ]
219 }
220 ]
221 }
222
223 if (t !== undefined) query.transaction = t
224
225 return VideoChannelModel.findOne(query)
226 }
227
228 static loadByIdAndAccount (id: number, accountId: number) { 202 static loadByIdAndAccount (id: number, accountId: number) {
229 const options = { 203 const options = {
230 where: { 204 where: {
231 id, 205 id,
232 accountId 206 accountId
233 }, 207 }
234 include: [
235 {
236 model: AccountModel,
237 include: [ { model: ServerModel, required: false } ]
238 }
239 ]
240 } 208 }
241 209
242 return VideoChannelModel.findOne(options) 210 return VideoChannelModel.scope(ScopeNames.WITH_ACCOUNT).findOne(options)
243 } 211 }
244 212
245 static loadAndPopulateAccount (id: number) { 213 static loadAndPopulateAccount (id: number) {
246 const options = { 214 return VideoChannelModel.scope(ScopeNames.WITH_ACCOUNT).findById(id)
247 include: [
248 {
249 model: AccountModel,
250 include: [ { model: ServerModel, required: false } ]
251 }
252 ]
253 }
254
255 return VideoChannelModel.findById(id, options)
256 } 215 }
257 216
258 static loadByUUIDAndPopulateAccount (uuid: string) { 217 static loadByUUIDAndPopulateAccount (uuid: string) {
259 const options = { 218 const options = {
260 where: { 219 where: {
261 uuid 220 uuid
262 }, 221 }
263 include: [
264 {
265 model: AccountModel,
266 include: [ { model: ServerModel, required: false } ]
267 }
268 ]
269 } 222 }
270 223
271 return VideoChannelModel.findOne(options) 224 return VideoChannelModel.scope(ScopeNames.WITH_ACCOUNT).findOne(options)
272 } 225 }
273 226
274 static loadAndPopulateAccountAndVideos (id: number) { 227 static loadAndPopulateAccountAndVideos (id: number) {
275 const options = { 228 const options = {
276 include: [ 229 include: [
277 {
278 model: AccountModel,
279 include: [ { model: ServerModel, required: false } ]
280 },
281 VideoModel 230 VideoModel
282 ] 231 ]
283 } 232 }
284 233
285 return VideoChannelModel.findById(id, options) 234 return VideoChannelModel.scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_VIDEOS ]).findById(id, options)
286 } 235 }
287 236
288 isOwned () { 237 isOwned () {
diff --git a/server/models/video/video-share.ts b/server/models/video/video-share.ts
index 01b6d3d34..e1733b3a7 100644
--- a/server/models/video/video-share.ts
+++ b/server/models/video/video-share.ts
@@ -1,8 +1,35 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' 2import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
3import { AccountModel } from '../account/account' 3import { AccountModel } from '../account/account'
4import { VideoModel } from './video' 4import { VideoModel } from './video'
5 5
6enum ScopeNames {
7 FULL = 'FULL',
8 WITH_ACCOUNT = 'WITH_ACCOUNT'
9}
10
11@Scopes({
12 [ScopeNames.FULL]: {
13 include: [
14 {
15 model: () => AccountModel,
16 required: true
17 },
18 {
19 model: () => VideoModel,
20 required: true
21 }
22 ]
23 },
24 [ScopeNames.WITH_ACCOUNT]: {
25 include: [
26 {
27 model: () => AccountModel,
28 required: true
29 }
30 ]
31 }
32})
6@Table({ 33@Table({
7 tableName: 'videoShare', 34 tableName: 'videoShare',
8 indexes: [ 35 indexes: [
@@ -46,14 +73,11 @@ export class VideoShareModel extends Model<VideoShareModel> {
46 Video: VideoModel 73 Video: VideoModel
47 74
48 static load (accountId: number, videoId: number, t: Sequelize.Transaction) { 75 static load (accountId: number, videoId: number, t: Sequelize.Transaction) {
49 return VideoShareModel.findOne({ 76 return VideoShareModel.scope(ScopeNames.WITH_ACCOUNT).findOne({
50 where: { 77 where: {
51 accountId, 78 accountId,
52 videoId 79 videoId
53 }, 80 },
54 include: [
55 AccountModel
56 ],
57 transaction: t 81 transaction: t
58 }) 82 })
59 } 83 }
@@ -72,7 +96,7 @@ export class VideoShareModel extends Model<VideoShareModel> {
72 transaction: t 96 transaction: t
73 } 97 }
74 98
75 return VideoShareModel.findAll(query) 99 return VideoShareModel.scope(ScopeNames.FULL).findAll(query)
76 .then(res => res.map(r => r.Account)) 100 .then(res => res.map(r => r.Account))
77 } 101 }
78} 102}
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 9e26f9bbe..1f940a50d 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -21,12 +21,14 @@ import {
21 IsUUID, 21 IsUUID,
22 Min, 22 Min,
23 Model, 23 Model,
24 Scopes,
24 Table, 25 Table,
25 UpdatedAt 26 UpdatedAt
26} from 'sequelize-typescript' 27} from 'sequelize-typescript'
27import { IIncludeOptions } from 'sequelize-typescript/lib/interfaces/IIncludeOptions' 28import { IIncludeOptions } from 'sequelize-typescript/lib/interfaces/IIncludeOptions'
28import { VideoPrivacy, VideoResolution } from '../../../shared' 29import { VideoPrivacy, VideoResolution } from '../../../shared'
29import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' 30import { VideoTorrentObject } from '../../../shared/models/activitypub/objects'
31import { Video, VideoDetails } from '../../../shared/models/videos'
30import { 32import {
31 activityPubCollection, 33 activityPubCollection,
32 createTorrentPromise, 34 createTorrentPromise,
@@ -76,6 +78,79 @@ import { VideoFileModel } from './video-file'
76import { VideoShareModel } from './video-share' 78import { VideoShareModel } from './video-share'
77import { VideoTagModel } from './video-tag' 79import { VideoTagModel } from './video-tag'
78 80
81enum ScopeNames {
82 NOT_IN_BLACKLIST = 'NOT_IN_BLACKLIST',
83 PUBLIC = 'PUBLIC',
84 WITH_ACCOUNT = 'WITH_ACCOUNT',
85 WITH_TAGS = 'WITH_TAGS',
86 WITH_FILES = 'WITH_FILES',
87 WITH_SHARES = 'WITH_SHARES',
88 WITH_RATES = 'WITH_RATES'
89}
90
91@Scopes({
92 [ScopeNames.NOT_IN_BLACKLIST]: {
93 where: {
94 id: {
95 [Sequelize.Op.notIn]: Sequelize.literal(
96 '(SELECT "videoBlacklist"."videoId" FROM "videoBlacklist")'
97 )
98 }
99 }
100 },
101 [ScopeNames.PUBLIC]: {
102 where: {
103 privacy: VideoPrivacy.PUBLIC
104 }
105 },
106 [ScopeNames.WITH_ACCOUNT]: {
107 include: [
108 {
109 model: () => VideoChannelModel,
110 required: true,
111 include: [
112 {
113 model: () => AccountModel,
114 required: true,
115 include: [
116 {
117 model: () => ServerModel,
118 required: false
119 }
120 ]
121 }
122 ]
123 }
124 ]
125 },
126 [ScopeNames.WITH_TAGS]: {
127 include: [ () => TagModel ]
128 },
129 [ScopeNames.WITH_FILES]: {
130 include: [
131 {
132 model: () => VideoFileModel,
133 required: true
134 }
135 ]
136 },
137 [ScopeNames.WITH_SHARES]: {
138 include: [
139 {
140 model: () => VideoShareModel,
141 include: [ () => AccountModel ]
142 }
143 ]
144 },
145 [ScopeNames.WITH_RATES]: {
146 include: [
147 {
148 model: () => AccountVideoRateModel,
149 include: [ () => AccountModel ]
150 }
151 ]
152 }
153})
79@Table({ 154@Table({
80 tableName: 'video', 155 tableName: 'video',
81 indexes: [ 156 indexes: [
@@ -273,11 +348,7 @@ export class VideoModel extends Model<VideoModel> {
273 } 348 }
274 349
275 static list () { 350 static list () {
276 const query = { 351 return VideoModel.scope(ScopeNames.WITH_FILES).findAll()
277 include: [ VideoFileModel ]
278 }
279
280 return VideoModel.findAll(query)
281 } 352 }
282 353
283 static listAllAndSharedByAccountForOutbox (accountId: number, start: number, count: number) { 354 static listAllAndSharedByAccountForOutbox (accountId: number, start: number, count: number) {
@@ -363,10 +434,9 @@ export class VideoModel extends Model<VideoModel> {
363 434
364 static listUserVideosForApi (userId: number, start: number, count: number, sort: string) { 435 static listUserVideosForApi (userId: number, start: number, count: number, sort: string) {
365 const query = { 436 const query = {
366 distinct: true,
367 offset: start, 437 offset: start,
368 limit: count, 438 limit: count,
369 order: [ getSort(sort), [ 'Tags', 'name', 'ASC' ] ], 439 order: [ getSort(sort) ],
370 include: [ 440 include: [
371 { 441 {
372 model: VideoChannelModel, 442 model: VideoChannelModel,
@@ -380,8 +450,7 @@ export class VideoModel extends Model<VideoModel> {
380 required: true 450 required: true
381 } 451 }
382 ] 452 ]
383 }, 453 }
384 TagModel
385 ] 454 ]
386 } 455 }
387 456
@@ -395,74 +464,35 @@ export class VideoModel extends Model<VideoModel> {
395 464
396 static listForApi (start: number, count: number, sort: string) { 465 static listForApi (start: number, count: number, sort: string) {
397 const query = { 466 const query = {
398 distinct: true,
399 offset: start, 467 offset: start,
400 limit: count, 468 limit: count,
401 order: [ getSort(sort), [ 'Tags', 'name', 'ASC' ] ], 469 order: [ getSort(sort) ]
402 include: [
403 {
404 model: VideoChannelModel,
405 required: true,
406 include: [
407 {
408 model: AccountModel,
409 required: true,
410 include: [
411 {
412 model: ServerModel,
413 required: false
414 }
415 ]
416 }
417 ]
418 },
419 TagModel
420 ],
421 where: this.createBaseVideosWhere()
422 } 470 }
423 471
424 return VideoModel.findAndCountAll(query).then(({ rows, count }) => { 472 return VideoModel.scope([ ScopeNames.NOT_IN_BLACKLIST, ScopeNames.PUBLIC, ScopeNames.WITH_ACCOUNT ])
425 return { 473 .findAndCountAll(query)
426 data: rows, 474 .then(({ rows, count }) => {
427 total: count 475 return {
428 } 476 data: rows,
429 }) 477 total: count
478 }
479 })
430 } 480 }
431 481
432 static load (id: number) { 482 static load (id: number) {
433 return VideoModel.findById(id) 483 return VideoModel.findById(id)
434 } 484 }
435 485
436 static loadByUUID (uuid: string, t?: Sequelize.Transaction) {
437 const query: IFindOptions<VideoModel> = {
438 where: {
439 uuid
440 },
441 include: [ VideoFileModel ]
442 }
443
444 if (t !== undefined) query.transaction = t
445
446 return VideoModel.findOne(query)
447 }
448
449 static loadByUrlAndPopulateAccount (url: string, t?: Sequelize.Transaction) { 486 static loadByUrlAndPopulateAccount (url: string, t?: Sequelize.Transaction) {
450 const query: IFindOptions<VideoModel> = { 487 const query: IFindOptions<VideoModel> = {
451 where: { 488 where: {
452 url 489 url
453 }, 490 }
454 include: [
455 VideoFileModel,
456 {
457 model: VideoChannelModel,
458 include: [ AccountModel ]
459 }
460 ]
461 } 491 }
462 492
463 if (t !== undefined) query.transaction = t 493 if (t !== undefined) query.transaction = t
464 494
465 return VideoModel.findOne(query) 495 return VideoModel.scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_FILES ]).findOne(query)
466 } 496 }
467 497
468 static loadByUUIDOrURL (uuid: string, url: string, t?: Sequelize.Transaction) { 498 static loadByUUIDOrURL (uuid: string, url: string, t?: Sequelize.Transaction) {
@@ -472,42 +502,22 @@ export class VideoModel extends Model<VideoModel> {
472 { uuid }, 502 { uuid },
473 { url } 503 { url }
474 ] 504 ]
475 }, 505 }
476 include: [ VideoFileModel ]
477 } 506 }
478 507
479 if (t !== undefined) query.transaction = t 508 if (t !== undefined) query.transaction = t
480 509
481 return VideoModel.findOne(query) 510 return VideoModel.scope(ScopeNames.WITH_FILES).findOne(query)
482 } 511 }
483 512
484 static loadAndPopulateAccountAndServerAndTags (id: number) { 513 static loadAndPopulateAccountAndServerAndTags (id: number) {
485 const options = { 514 const options = {
486 order: [ [ 'Tags', 'name', 'ASC' ] ], 515 order: [ [ 'Tags', 'name', 'ASC' ] ]
487 include: [
488 {
489 model: VideoChannelModel,
490 include: [
491 {
492 model: AccountModel,
493 include: [ { model: ServerModel, required: false } ]
494 }
495 ]
496 },
497 {
498 model: AccountVideoRateModel,
499 include: [ AccountModel ]
500 },
501 {
502 model: VideoShareModel,
503 include: [ AccountModel ]
504 },
505 TagModel,
506 VideoFileModel
507 ]
508 } 516 }
509 517
510 return VideoModel.findById(id, options) 518 return VideoModel
519 .scope([ ScopeNames.WITH_RATES, ScopeNames.WITH_SHARES, ScopeNames.WITH_TAGS, ScopeNames.WITH_FILES, ScopeNames.WITH_ACCOUNT ])
520 .findById(id, options)
511 } 521 }
512 522
513 static loadByUUIDAndPopulateAccountAndServerAndTags (uuid: string) { 523 static loadByUUIDAndPopulateAccountAndServerAndTags (uuid: string) {
@@ -515,31 +525,12 @@ export class VideoModel extends Model<VideoModel> {
515 order: [ [ 'Tags', 'name', 'ASC' ] ], 525 order: [ [ 'Tags', 'name', 'ASC' ] ],
516 where: { 526 where: {
517 uuid 527 uuid
518 }, 528 }
519 include: [
520 {
521 model: VideoChannelModel,
522 include: [
523 {
524 model: AccountModel,
525 include: [ { model: ServerModel, required: false } ]
526 }
527 ]
528 },
529 {
530 model: AccountVideoRateModel,
531 include: [ AccountModel ]
532 },
533 {
534 model: VideoShareModel,
535 include: [ AccountModel ]
536 },
537 TagModel,
538 VideoFileModel
539 ]
540 } 529 }
541 530
542 return VideoModel.findOne(options) 531 return VideoModel
532 .scope([ ScopeNames.WITH_RATES, ScopeNames.WITH_SHARES, ScopeNames.WITH_TAGS, ScopeNames.WITH_FILES, ScopeNames.WITH_ACCOUNT ])
533 .findOne(options)
543 } 534 }
544 535
545 static searchAndPopulateAccountAndServerAndTags (value: string, start: number, count: number, sort: string) { 536 static searchAndPopulateAccountAndServerAndTags (value: string, start: number, count: number, sort: string) {
@@ -564,11 +555,11 @@ export class VideoModel extends Model<VideoModel> {
564 } 555 }
565 556
566 const query: IFindOptions<VideoModel> = { 557 const query: IFindOptions<VideoModel> = {
567 distinct: true, 558 distinct: true, // Because we have tags
568 where: this.createBaseVideosWhere(),
569 offset: start, 559 offset: start,
570 limit: count, 560 limit: count,
571 order: [ getSort(sort), [ 'Tags', 'name', 'ASC' ] ] 561 order: [ getSort(sort) ],
562 where: {}
572 } 563 }
573 564
574 // TODO: search on tags too 565 // TODO: search on tags too
@@ -595,23 +586,13 @@ export class VideoModel extends Model<VideoModel> {
595 videoChannelInclude, tagInclude 586 videoChannelInclude, tagInclude
596 ] 587 ]
597 588
598 return VideoModel.findAndCountAll(query).then(({ rows, count }) => { 589 return VideoModel.scope([ ScopeNames.NOT_IN_BLACKLIST, ScopeNames.PUBLIC ])
599 return { 590 .findAndCountAll(query).then(({ rows, count }) => {
600 data: rows, 591 return {
601 total: count 592 data: rows,
602 } 593 total: count
603 }) 594 }
604 } 595 })
605
606 private static createBaseVideosWhere () {
607 return {
608 id: {
609 [Sequelize.Op.notIn]: VideoModel.sequelize.literal(
610 '(SELECT "videoBlacklist"."videoId" FROM "videoBlacklist")'
611 )
612 },
613 privacy: VideoPrivacy.PUBLIC
614 }
615 } 596 }
616 597
617 getOriginalFile () { 598 getOriginalFile () {
@@ -733,13 +714,12 @@ export class VideoModel extends Model<VideoModel> {
733 views: this.views, 714 views: this.views,
734 likes: this.likes, 715 likes: this.likes,
735 dislikes: this.dislikes, 716 dislikes: this.dislikes,
736 tags: map<TagModel, string>(this.Tags, 'name'),
737 thumbnailPath: this.getThumbnailPath(), 717 thumbnailPath: this.getThumbnailPath(),
738 previewPath: this.getPreviewPath(), 718 previewPath: this.getPreviewPath(),
739 embedPath: this.getEmbedPath(), 719 embedPath: this.getEmbedPath(),
740 createdAt: this.createdAt, 720 createdAt: this.createdAt,
741 updatedAt: this.updatedAt 721 updatedAt: this.updatedAt
742 } 722 } as Video
743 } 723 }
744 724
745 toFormattedDetailsJSON () { 725 toFormattedDetailsJSON () {
@@ -755,6 +735,7 @@ export class VideoModel extends Model<VideoModel> {
755 descriptionPath: this.getDescriptionPath(), 735 descriptionPath: this.getDescriptionPath(),
756 channel: this.VideoChannel.toFormattedJSON(), 736 channel: this.VideoChannel.toFormattedJSON(),
757 account: this.VideoChannel.Account.toFormattedJSON(), 737 account: this.VideoChannel.Account.toFormattedJSON(),
738 tags: map<TagModel, string>(this.Tags, 'name'),
758 files: [] 739 files: []
759 } 740 }
760 741
@@ -779,7 +760,7 @@ export class VideoModel extends Model<VideoModel> {
779 return -1 760 return -1
780 }) 761 })
781 762
782 return Object.assign(formattedJson, detailsJson) 763 return Object.assign(formattedJson, detailsJson) as VideoDetails
783 } 764 }
784 765
785 toActivityPubObject (): VideoTorrentObject { 766 toActivityPubObject (): VideoTorrentObject {
diff --git a/server/tests/api/single-server.ts b/server/tests/api/single-server.ts
index 174fb480d..7f4351f5e 100644
--- a/server/tests/api/single-server.ts
+++ b/server/tests/api/single-server.ts
@@ -132,7 +132,6 @@ describe('Test a single server', function () {
132 expect(video.serverHost).to.equal('localhost:9001') 132 expect(video.serverHost).to.equal('localhost:9001')
133 expect(video.accountName).to.equal('root') 133 expect(video.accountName).to.equal('root')
134 expect(video.isLocal).to.be.true 134 expect(video.isLocal).to.be.true
135 expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ])
136 expect(dateIsValid(video.createdAt)).to.be.true 135 expect(dateIsValid(video.createdAt)).to.be.true
137 expect(dateIsValid(video.updatedAt)).to.be.true 136 expect(dateIsValid(video.updatedAt)).to.be.true
138 137
@@ -181,7 +180,6 @@ describe('Test a single server', function () {
181 expect(video.serverHost).to.equal('localhost:9001') 180 expect(video.serverHost).to.equal('localhost:9001')
182 expect(video.accountName).to.equal('root') 181 expect(video.accountName).to.equal('root')
183 expect(video.isLocal).to.be.true 182 expect(video.isLocal).to.be.true
184 expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ])
185 expect(dateIsValid(video.createdAt)).to.be.true 183 expect(dateIsValid(video.createdAt)).to.be.true
186 expect(dateIsValid(video.updatedAt)).to.be.true 184 expect(dateIsValid(video.updatedAt)).to.be.true
187 expect(video.channel.name).to.equal('Default root channel') 185 expect(video.channel.name).to.equal('Default root channel')
@@ -248,7 +246,6 @@ describe('Test a single server', function () {
248 expect(video.serverHost).to.equal('localhost:9001') 246 expect(video.serverHost).to.equal('localhost:9001')
249 expect(video.accountName).to.equal('root') 247 expect(video.accountName).to.equal('root')
250 expect(video.isLocal).to.be.true 248 expect(video.isLocal).to.be.true
251 expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ])
252 expect(dateIsValid(video.createdAt)).to.be.true 249 expect(dateIsValid(video.createdAt)).to.be.true
253 expect(dateIsValid(video.updatedAt)).to.be.true 250 expect(dateIsValid(video.updatedAt)).to.be.true
254 251
diff --git a/server/tests/api/video-abuse.ts b/server/tests/api/video-abuse.ts
index 60bee9c3d..4a0b6b504 100644
--- a/server/tests/api/video-abuse.ts
+++ b/server/tests/api/video-abuse.ts
@@ -47,7 +47,7 @@ describe('Test video abuses', function () {
47 await uploadVideo(servers[1].url, servers[1].accessToken, video2Attributes) 47 await uploadVideo(servers[1].url, servers[1].accessToken, video2Attributes)
48 48
49 // Wait videos propagation, server 2 has transcoding enabled 49 // Wait videos propagation, server 2 has transcoding enabled
50 await wait(10000) 50 await wait(15000)
51 51
52 const res = await getVideosList(servers[0].url) 52 const res = await getVideosList(servers[0].url)
53 const videos = res.body.data 53 const videos = res.body.data
diff --git a/shared/models/videos/video.model.ts b/shared/models/videos/video.model.ts
index dc12a05d9..3a378419f 100644
--- a/shared/models/videos/video.model.ts
+++ b/shared/models/videos/video.model.ts
@@ -28,7 +28,6 @@ export interface Video {
28 isLocal: boolean 28 isLocal: boolean
29 name: string 29 name: string
30 serverHost: string 30 serverHost: string
31 tags: string[]
32 thumbnailPath: string 31 thumbnailPath: string
33 previewPath: string 32 previewPath: string
34 embedPath: string 33 embedPath: string
@@ -43,6 +42,7 @@ export interface VideoDetails extends Video {
43 privacyLabel: string 42 privacyLabel: string
44 descriptionPath: string 43 descriptionPath: string
45 channel: VideoChannel 44 channel: VideoChannel
45 tags: string[]
46 files: VideoFile[] 46 files: VideoFile[]
47 account: Account 47 account: Account
48} 48}