diff options
Diffstat (limited to 'server')
-rw-r--r-- | server/controllers/api/accounts.ts | 38 | ||||
-rw-r--r-- | server/helpers/custom-validators/video-rates.ts | 5 | ||||
-rw-r--r-- | server/initializers/constants.ts | 1 | ||||
-rw-r--r-- | server/middlewares/validators/sort.ts | 3 | ||||
-rw-r--r-- | server/middlewares/validators/users.ts | 18 | ||||
-rw-r--r-- | server/middlewares/validators/videos/video-rates.ts | 18 | ||||
-rw-r--r-- | server/models/account/account-video-rate.ts | 43 | ||||
-rw-r--r-- | server/tests/api/users/users.ts | 32 |
8 files changed, 151 insertions, 7 deletions
diff --git a/server/controllers/api/accounts.ts b/server/controllers/api/accounts.ts index adbf69781..aa01ea1eb 100644 --- a/server/controllers/api/accounts.ts +++ b/server/controllers/api/accounts.ts | |||
@@ -1,16 +1,25 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { getFormattedObjects, getServerActor } from '../../helpers/utils' | 2 | import { getFormattedObjects, getServerActor } from '../../helpers/utils' |
3 | import { | 3 | import { |
4 | authenticate, | ||
4 | asyncMiddleware, | 5 | asyncMiddleware, |
5 | commonVideosFiltersValidator, | 6 | commonVideosFiltersValidator, |
7 | videoRatingValidator, | ||
6 | optionalAuthenticate, | 8 | optionalAuthenticate, |
7 | paginationValidator, | 9 | paginationValidator, |
8 | setDefaultPagination, | 10 | setDefaultPagination, |
9 | setDefaultSort, | 11 | setDefaultSort, |
10 | videoPlaylistsSortValidator | 12 | videoPlaylistsSortValidator, |
13 | videoRatesSortValidator | ||
11 | } from '../../middlewares' | 14 | } from '../../middlewares' |
12 | import { accountNameWithHostGetValidator, accountsSortValidator, videosSortValidator } from '../../middlewares/validators' | 15 | import { |
16 | accountNameWithHostGetValidator, | ||
17 | accountsSortValidator, | ||
18 | videosSortValidator, | ||
19 | ensureAuthUserOwnsAccountValidator | ||
20 | } from '../../middlewares/validators' | ||
13 | import { AccountModel } from '../../models/account/account' | 21 | import { AccountModel } from '../../models/account/account' |
22 | import { AccountVideoRateModel } from '../../models/account/account-video-rate' | ||
14 | import { VideoModel } from '../../models/video/video' | 23 | import { VideoModel } from '../../models/video/video' |
15 | import { buildNSFWFilter, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' | 24 | import { buildNSFWFilter, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' |
16 | import { VideoChannelModel } from '../../models/video/video-channel' | 25 | import { VideoChannelModel } from '../../models/video/video-channel' |
@@ -61,6 +70,18 @@ accountsRouter.get('/:accountName/video-playlists', | |||
61 | asyncMiddleware(listAccountPlaylists) | 70 | asyncMiddleware(listAccountPlaylists) |
62 | ) | 71 | ) |
63 | 72 | ||
73 | accountsRouter.get('/:accountName/ratings', | ||
74 | authenticate, | ||
75 | asyncMiddleware(accountNameWithHostGetValidator), | ||
76 | ensureAuthUserOwnsAccountValidator, | ||
77 | paginationValidator, | ||
78 | videoRatesSortValidator, | ||
79 | setDefaultSort, | ||
80 | setDefaultPagination, | ||
81 | videoRatingValidator, | ||
82 | asyncMiddleware(listAccountRatings) | ||
83 | ) | ||
84 | |||
64 | // --------------------------------------------------------------------------- | 85 | // --------------------------------------------------------------------------- |
65 | 86 | ||
66 | export { | 87 | export { |
@@ -138,3 +159,16 @@ async function listAccountVideos (req: express.Request, res: express.Response) { | |||
138 | 159 | ||
139 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | 160 | return res.json(getFormattedObjects(resultList.data, resultList.total)) |
140 | } | 161 | } |
162 | |||
163 | async function listAccountRatings (req: express.Request, res: express.Response) { | ||
164 | const account = res.locals.account | ||
165 | |||
166 | const resultList = await AccountVideoRateModel.listByAccountForApi({ | ||
167 | accountId: account.id, | ||
168 | start: req.query.start, | ||
169 | count: req.query.count, | ||
170 | sort: req.query.sort, | ||
171 | type: req.query.rating | ||
172 | }) | ||
173 | return res.json(getFormattedObjects(resultList.rows, resultList.count)) | ||
174 | } | ||
diff --git a/server/helpers/custom-validators/video-rates.ts b/server/helpers/custom-validators/video-rates.ts new file mode 100644 index 000000000..f2b6f7cae --- /dev/null +++ b/server/helpers/custom-validators/video-rates.ts | |||
@@ -0,0 +1,5 @@ | |||
1 | function isRatingValid (value: any) { | ||
2 | return value === 'like' || value === 'dislike' | ||
3 | } | ||
4 | |||
5 | export { isRatingValid } | ||
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 78dd7cb9d..097199f84 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -42,6 +42,7 @@ const SORTABLE_COLUMNS = { | |||
42 | VIDEO_CHANNELS: [ 'id', 'name', 'updatedAt', 'createdAt' ], | 42 | VIDEO_CHANNELS: [ 'id', 'name', 'updatedAt', 'createdAt' ], |
43 | VIDEO_IMPORTS: [ 'createdAt' ], | 43 | VIDEO_IMPORTS: [ 'createdAt' ], |
44 | VIDEO_COMMENT_THREADS: [ 'createdAt' ], | 44 | VIDEO_COMMENT_THREADS: [ 'createdAt' ], |
45 | VIDEO_RATES: [ 'createdAt' ], | ||
45 | BLACKLISTS: [ 'id', 'name', 'duration', 'views', 'likes', 'dislikes', 'uuid', 'createdAt' ], | 46 | BLACKLISTS: [ 'id', 'name', 'duration', 'views', 'likes', 'dislikes', 'uuid', 'createdAt' ], |
46 | FOLLOWERS: [ 'createdAt' ], | 47 | FOLLOWERS: [ 'createdAt' ], |
47 | FOLLOWING: [ 'createdAt' ], | 48 | FOLLOWING: [ 'createdAt' ], |
diff --git a/server/middlewares/validators/sort.ts b/server/middlewares/validators/sort.ts index ea59fbf73..44295c325 100644 --- a/server/middlewares/validators/sort.ts +++ b/server/middlewares/validators/sort.ts | |||
@@ -11,6 +11,7 @@ const SORTABLE_VIDEOS_SEARCH_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VI | |||
11 | const SORTABLE_VIDEO_CHANNELS_SEARCH_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_CHANNELS_SEARCH) | 11 | const SORTABLE_VIDEO_CHANNELS_SEARCH_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_CHANNELS_SEARCH) |
12 | const SORTABLE_VIDEO_IMPORTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_IMPORTS) | 12 | const SORTABLE_VIDEO_IMPORTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_IMPORTS) |
13 | const SORTABLE_VIDEO_COMMENT_THREADS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_COMMENT_THREADS) | 13 | const SORTABLE_VIDEO_COMMENT_THREADS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_COMMENT_THREADS) |
14 | const SORTABLE_VIDEO_RATES_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_RATES) | ||
14 | const SORTABLE_BLACKLISTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.BLACKLISTS) | 15 | const SORTABLE_BLACKLISTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.BLACKLISTS) |
15 | const SORTABLE_VIDEO_CHANNELS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_CHANNELS) | 16 | const SORTABLE_VIDEO_CHANNELS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_CHANNELS) |
16 | const SORTABLE_FOLLOWERS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.FOLLOWERS) | 17 | const SORTABLE_FOLLOWERS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.FOLLOWERS) |
@@ -30,6 +31,7 @@ const videoImportsSortValidator = checkSort(SORTABLE_VIDEO_IMPORTS_COLUMNS) | |||
30 | const videosSearchSortValidator = checkSort(SORTABLE_VIDEOS_SEARCH_COLUMNS) | 31 | const videosSearchSortValidator = checkSort(SORTABLE_VIDEOS_SEARCH_COLUMNS) |
31 | const videoChannelsSearchSortValidator = checkSort(SORTABLE_VIDEO_CHANNELS_SEARCH_COLUMNS) | 32 | const videoChannelsSearchSortValidator = checkSort(SORTABLE_VIDEO_CHANNELS_SEARCH_COLUMNS) |
32 | const videoCommentThreadsSortValidator = checkSort(SORTABLE_VIDEO_COMMENT_THREADS_COLUMNS) | 33 | const videoCommentThreadsSortValidator = checkSort(SORTABLE_VIDEO_COMMENT_THREADS_COLUMNS) |
34 | const videoRatesSortValidator = checkSort(SORTABLE_VIDEO_RATES_COLUMNS) | ||
33 | const blacklistSortValidator = checkSort(SORTABLE_BLACKLISTS_COLUMNS) | 35 | const blacklistSortValidator = checkSort(SORTABLE_BLACKLISTS_COLUMNS) |
34 | const videoChannelsSortValidator = checkSort(SORTABLE_VIDEO_CHANNELS_COLUMNS) | 36 | const videoChannelsSortValidator = checkSort(SORTABLE_VIDEO_CHANNELS_COLUMNS) |
35 | const followersSortValidator = checkSort(SORTABLE_FOLLOWERS_COLUMNS) | 37 | const followersSortValidator = checkSort(SORTABLE_FOLLOWERS_COLUMNS) |
@@ -55,6 +57,7 @@ export { | |||
55 | followingSortValidator, | 57 | followingSortValidator, |
56 | jobsSortValidator, | 58 | jobsSortValidator, |
57 | videoCommentThreadsSortValidator, | 59 | videoCommentThreadsSortValidator, |
60 | videoRatesSortValidator, | ||
58 | userSubscriptionsSortValidator, | 61 | userSubscriptionsSortValidator, |
59 | videoChannelsSearchSortValidator, | 62 | videoChannelsSearchSortValidator, |
60 | accountsBlocklistSortValidator, | 63 | accountsBlocklistSortValidator, |
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts index 4be446732..35f41c450 100644 --- a/server/middlewares/validators/users.ts +++ b/server/middlewares/validators/users.ts | |||
@@ -22,6 +22,7 @@ import { logger } from '../../helpers/logger' | |||
22 | import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup' | 22 | import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup' |
23 | import { Redis } from '../../lib/redis' | 23 | import { Redis } from '../../lib/redis' |
24 | import { UserModel } from '../../models/account/user' | 24 | import { UserModel } from '../../models/account/user' |
25 | import { AccountModel } from '../../models/account/account' | ||
25 | import { areValidationErrors } from './utils' | 26 | import { areValidationErrors } from './utils' |
26 | import { ActorModel } from '../../models/activitypub/actor' | 27 | import { ActorModel } from '../../models/activitypub/actor' |
27 | 28 | ||
@@ -317,6 +318,20 @@ const userAutocompleteValidator = [ | |||
317 | param('search').isString().not().isEmpty().withMessage('Should have a search parameter') | 318 | param('search').isString().not().isEmpty().withMessage('Should have a search parameter') |
318 | ] | 319 | ] |
319 | 320 | ||
321 | const ensureAuthUserOwnsAccountValidator = [ | ||
322 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
323 | const user = res.locals.oauth.token.User | ||
324 | |||
325 | if (res.locals.account.id !== user.Account.id) { | ||
326 | return res.status(403) | ||
327 | .send({ error: 'Only owner can access ratings list.' }) | ||
328 | .end() | ||
329 | } | ||
330 | |||
331 | return next() | ||
332 | } | ||
333 | ] | ||
334 | |||
320 | // --------------------------------------------------------------------------- | 335 | // --------------------------------------------------------------------------- |
321 | 336 | ||
322 | export { | 337 | export { |
@@ -335,7 +350,8 @@ export { | |||
335 | usersResetPasswordValidator, | 350 | usersResetPasswordValidator, |
336 | usersAskSendVerifyEmailValidator, | 351 | usersAskSendVerifyEmailValidator, |
337 | usersVerifyEmailValidator, | 352 | usersVerifyEmailValidator, |
338 | userAutocompleteValidator | 353 | userAutocompleteValidator, |
354 | ensureAuthUserOwnsAccountValidator | ||
339 | } | 355 | } |
340 | 356 | ||
341 | // --------------------------------------------------------------------------- | 357 | // --------------------------------------------------------------------------- |
diff --git a/server/middlewares/validators/videos/video-rates.ts b/server/middlewares/validators/videos/video-rates.ts index 280385912..e79d80e97 100644 --- a/server/middlewares/validators/videos/video-rates.ts +++ b/server/middlewares/validators/videos/video-rates.ts | |||
@@ -1,7 +1,8 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import 'express-validator' | 2 | import 'express-validator' |
3 | import { body, param } from 'express-validator/check' | 3 | import { body, param, query } from 'express-validator/check' |
4 | import { isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc' | 4 | import { isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc' |
5 | import { isRatingValid } from '../../../helpers/custom-validators/video-rates' | ||
5 | import { doesVideoExist, isVideoRatingTypeValid } from '../../../helpers/custom-validators/videos' | 6 | import { doesVideoExist, isVideoRatingTypeValid } from '../../../helpers/custom-validators/videos' |
6 | import { logger } from '../../../helpers/logger' | 7 | import { logger } from '../../../helpers/logger' |
7 | import { areValidationErrors } from '../utils' | 8 | import { areValidationErrors } from '../utils' |
@@ -47,9 +48,22 @@ const getAccountVideoRateValidator = function (rateType: VideoRateType) { | |||
47 | ] | 48 | ] |
48 | } | 49 | } |
49 | 50 | ||
51 | const videoRatingValidator = [ | ||
52 | query('rating').optional().custom(isRatingValid).withMessage('Value must be one of "like" or "dislike"'), | ||
53 | |||
54 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
55 | logger.debug('Checking rating parameter', { parameters: req.params }) | ||
56 | |||
57 | if (areValidationErrors(req, res)) return | ||
58 | |||
59 | return next() | ||
60 | } | ||
61 | ] | ||
62 | |||
50 | // --------------------------------------------------------------------------- | 63 | // --------------------------------------------------------------------------- |
51 | 64 | ||
52 | export { | 65 | export { |
53 | videoUpdateRateValidator, | 66 | videoUpdateRateValidator, |
54 | getAccountVideoRateValidator | 67 | getAccountVideoRateValidator, |
68 | videoRatingValidator | ||
55 | } | 69 | } |
diff --git a/server/models/account/account-video-rate.ts b/server/models/account/account-video-rate.ts index e5d39582b..f462df4b3 100644 --- a/server/models/account/account-video-rate.ts +++ b/server/models/account/account-video-rate.ts | |||
@@ -7,8 +7,10 @@ import { CONSTRAINTS_FIELDS, VIDEO_RATE_TYPES } from '../../initializers' | |||
7 | import { VideoModel } from '../video/video' | 7 | import { VideoModel } from '../video/video' |
8 | import { AccountModel } from './account' | 8 | import { AccountModel } from './account' |
9 | import { ActorModel } from '../activitypub/actor' | 9 | import { ActorModel } from '../activitypub/actor' |
10 | import { throwIfNotValid } from '../utils' | 10 | import { throwIfNotValid, getSort } from '../utils' |
11 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' | 11 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' |
12 | import { AccountVideoRate } from '../../../shared' | ||
13 | import { VideoChannelModel, ScopeNames as VideoChannelScopeNames } from '../video/video-channel' | ||
12 | 14 | ||
13 | /* | 15 | /* |
14 | Account rates per video. | 16 | Account rates per video. |
@@ -88,6 +90,38 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> { | |||
88 | return AccountVideoRateModel.findOne(options) | 90 | return AccountVideoRateModel.findOne(options) |
89 | } | 91 | } |
90 | 92 | ||
93 | static listByAccountForApi (options: { | ||
94 | start: number, | ||
95 | count: number, | ||
96 | sort: string, | ||
97 | type?: string, | ||
98 | accountId: number | ||
99 | }) { | ||
100 | const query: IFindOptions<AccountVideoRateModel> = { | ||
101 | offset: options.start, | ||
102 | limit: options.count, | ||
103 | order: getSort(options.sort), | ||
104 | where: { | ||
105 | accountId: options.accountId | ||
106 | }, | ||
107 | include: [ | ||
108 | { | ||
109 | model: VideoModel, | ||
110 | required: true, | ||
111 | include: [ | ||
112 | { | ||
113 | model: VideoChannelModel.scope({ method: [VideoChannelScopeNames.SUMMARY, true] }), | ||
114 | required: true | ||
115 | } | ||
116 | ] | ||
117 | } | ||
118 | ] | ||
119 | } | ||
120 | if (options.type) query.where['type'] = options.type | ||
121 | |||
122 | return AccountVideoRateModel.findAndCountAll(query) | ||
123 | } | ||
124 | |||
91 | static loadLocalAndPopulateVideo (rateType: VideoRateType, accountName: string, videoId: number, transaction?: Transaction) { | 125 | static loadLocalAndPopulateVideo (rateType: VideoRateType, accountName: string, videoId: number, transaction?: Transaction) { |
92 | const options: IFindOptions<AccountVideoRateModel> = { | 126 | const options: IFindOptions<AccountVideoRateModel> = { |
93 | where: { | 127 | where: { |
@@ -185,4 +219,11 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> { | |||
185 | else if (type === 'dislike') await VideoModel.increment({ dislikes: -deleted }, options) | 219 | else if (type === 'dislike') await VideoModel.increment({ dislikes: -deleted }, options) |
186 | }) | 220 | }) |
187 | } | 221 | } |
222 | |||
223 | toFormattedJSON (): AccountVideoRate { | ||
224 | return { | ||
225 | video: this.Video.toFormattedJSON(), | ||
226 | rating: this.type | ||
227 | } | ||
228 | } | ||
188 | } | 229 | } |
diff --git a/server/tests/api/users/users.ts b/server/tests/api/users/users.ts index c4465d541..bc069a7be 100644 --- a/server/tests/api/users/users.ts +++ b/server/tests/api/users/users.ts | |||
@@ -8,6 +8,7 @@ import { | |||
8 | createUser, | 8 | createUser, |
9 | deleteMe, | 9 | deleteMe, |
10 | flushTests, | 10 | flushTests, |
11 | getAccountRatings, | ||
11 | getBlacklistedVideosList, | 12 | getBlacklistedVideosList, |
12 | getMyUserInformation, | 13 | getMyUserInformation, |
13 | getMyUserVideoQuotaUsed, | 14 | getMyUserVideoQuotaUsed, |
@@ -32,7 +33,7 @@ import { | |||
32 | updateUser, | 33 | updateUser, |
33 | uploadVideo, | 34 | uploadVideo, |
34 | userLogin | 35 | userLogin |
35 | } from '../../../../shared/utils/index' | 36 | } from '../../../../shared/utils' |
36 | import { follow } from '../../../../shared/utils/server/follows' | 37 | import { follow } from '../../../../shared/utils/server/follows' |
37 | import { setAccessTokensToServers } from '../../../../shared/utils/users/login' | 38 | import { setAccessTokensToServers } from '../../../../shared/utils/users/login' |
38 | import { getMyVideos } from '../../../../shared/utils/videos/videos' | 39 | import { getMyVideos } from '../../../../shared/utils/videos/videos' |
@@ -137,6 +138,35 @@ describe('Test users', function () { | |||
137 | expect(rating.rating).to.equal('like') | 138 | expect(rating.rating).to.equal('like') |
138 | }) | 139 | }) |
139 | 140 | ||
141 | it('Should retrieve ratings list', async function () { | ||
142 | await rateVideo(server.url, accessToken, videoId, 'like') | ||
143 | const res = await getAccountRatings(server.url, server.user.username, server.accessToken, 200) | ||
144 | const ratings = res.body | ||
145 | |||
146 | expect(ratings.data[0].video.id).to.equal(videoId) | ||
147 | expect(ratings.data[0].rating).to.equal('like') | ||
148 | }) | ||
149 | |||
150 | it('Should retrieve ratings list by rating type', async function () { | ||
151 | await rateVideo(server.url, accessToken, videoId, 'like') | ||
152 | let res = await getAccountRatings(server.url, server.user.username, server.accessToken, 200, { rating: 'like' }) | ||
153 | let ratings = res.body | ||
154 | expect(ratings.data.length).to.equal(1) | ||
155 | res = await getAccountRatings(server.url, server.user.username, server.accessToken, 200, { rating: 'dislike' }) | ||
156 | ratings = res.body | ||
157 | expect(ratings.data.length).to.equal(0) | ||
158 | await getAccountRatings(server.url, server.user.username, server.accessToken, 400, { rating: 'invalid' }) | ||
159 | }) | ||
160 | |||
161 | it('Should not access ratings list if not logged with correct user', async function () { | ||
162 | const user = { username: 'anuragh', password: 'passbyme' } | ||
163 | const resUser = await createUser(server.url, server.accessToken, user.username, user.password) | ||
164 | const userId = resUser.body.user.id | ||
165 | const userAccessToken = await userLogin(server, user) | ||
166 | await getAccountRatings(server.url, server.user.username, userAccessToken, 403) | ||
167 | await removeUser(server.url, userId, server.accessToken) | ||
168 | }) | ||
169 | |||
140 | it('Should not be able to remove the video with an incorrect token', async function () { | 170 | it('Should not be able to remove the video with an incorrect token', async function () { |
141 | await removeVideo(server.url, 'bad_token', videoId, 401) | 171 | await removeVideo(server.url, 'bad_token', videoId, 401) |
142 | }) | 172 | }) |