diff options
Diffstat (limited to 'server')
-rw-r--r-- | server/middlewares/validators/video-channels.ts | 2 | ||||
-rw-r--r-- | server/models/account/user.ts | 50 | ||||
-rw-r--r-- | server/models/application/application.ts | 18 | ||||
-rw-r--r-- | server/models/oauth/oauth-token.ts | 59 | ||||
-rw-r--r-- | server/models/video/video-channel-share.ts | 43 | ||||
-rw-r--r-- | server/models/video/video-channel.ts | 113 | ||||
-rw-r--r-- | server/models/video/video-share.ts | 36 | ||||
-rw-r--r-- | server/models/video/video.ts | 243 | ||||
-rw-r--r-- | server/tests/api/single-server.ts | 3 | ||||
-rw-r--r-- | server/tests/api/video-abuse.ts | 2 |
10 files changed, 261 insertions, 308 deletions
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' |
16 | import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared' | 16 | import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared' |
@@ -27,6 +27,25 @@ import { getSort, throwIfNotValid } from '../utils' | |||
27 | import { VideoChannelModel } from '../video/video-channel' | 27 | import { VideoChannelModel } from '../video/video-channel' |
28 | import { AccountModel } from './account' | 28 | import { 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 @@ | |||
1 | import { Transaction } from 'sequelize' | ||
2 | import { AllowNull, Column, Default, IsInt, Model, Table } from 'sequelize-typescript' | 1 | import { 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 @@ | |||
1 | import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' | 1 | import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' |
2 | import { logger } from '../../helpers' | 2 | import { logger } from '../../helpers' |
3 | import { AccountModel } from '../account/account' | 3 | import { AccountModel } from '../account/account' |
4 | import { UserModel } from '../account/user' | 4 | import { UserModel } from '../account/user' |
@@ -15,6 +15,25 @@ export type OAuthTokenInfo = { | |||
15 | } | 15 | } |
16 | } | 16 | } |
17 | 17 | ||
18 | enum 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' | 2 | import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' |
3 | import { AccountModel } from '../account/account' | 3 | import { AccountModel } from '../account/account' |
4 | import { VideoChannelModel } from './video-channel' | 4 | import { VideoChannelModel } from './video-channel' |
5 | 5 | ||
6 | enum 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' | |||
28 | import { VideoModel } from './video' | 28 | import { VideoModel } from './video' |
29 | import { VideoChannelShareModel } from './video-channel-share' | 29 | import { VideoChannelShareModel } from './video-channel-share' |
30 | 30 | ||
31 | enum 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' | 2 | import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' |
3 | import { AccountModel } from '../account/account' | 3 | import { AccountModel } from '../account/account' |
4 | import { VideoModel } from './video' | 4 | import { VideoModel } from './video' |
5 | 5 | ||
6 | enum 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' |
27 | import { IIncludeOptions } from 'sequelize-typescript/lib/interfaces/IIncludeOptions' | 28 | import { IIncludeOptions } from 'sequelize-typescript/lib/interfaces/IIncludeOptions' |
28 | import { VideoPrivacy, VideoResolution } from '../../../shared' | 29 | import { VideoPrivacy, VideoResolution } from '../../../shared' |
29 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' | 30 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' |
31 | import { Video, VideoDetails } from '../../../shared/models/videos' | ||
30 | import { | 32 | import { |
31 | activityPubCollection, | 33 | activityPubCollection, |
32 | createTorrentPromise, | 34 | createTorrentPromise, |
@@ -76,6 +78,79 @@ import { VideoFileModel } from './video-file' | |||
76 | import { VideoShareModel } from './video-share' | 78 | import { VideoShareModel } from './video-share' |
77 | import { VideoTagModel } from './video-tag' | 79 | import { VideoTagModel } from './video-tag' |
78 | 80 | ||
81 | enum 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 |