diff options
author | Chocobozzz <me@florianbigard.com> | 2021-10-19 09:44:43 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2021-10-20 09:25:44 +0200 |
commit | 4beda9e12adc7b1f3b178cecd6863ebf3cf431f1 (patch) | |
tree | 6244a10b286d66c6dcd7799aee630670d0493781 /server | |
parent | 9593a78ae1368a9ad8bb11044fce6fde2892701a (diff) | |
download | PeerTube-4beda9e12adc7b1f3b178cecd6863ebf3cf431f1.tar.gz PeerTube-4beda9e12adc7b1f3b178cecd6863ebf3cf431f1.tar.zst PeerTube-4beda9e12adc7b1f3b178cecd6863ebf3cf431f1.zip |
Add ability to view my followers
Diffstat (limited to 'server')
-rw-r--r-- | server/controllers/api/accounts.ts | 33 | ||||
-rw-r--r-- | server/controllers/api/server/follows.ts | 4 | ||||
-rw-r--r-- | server/controllers/api/users/my-subscriptions.ts | 2 | ||||
-rw-r--r-- | server/controllers/api/video-channel.ts | 55 | ||||
-rw-r--r-- | server/initializers/constants.ts | 3 | ||||
-rw-r--r-- | server/middlewares/validators/sort.ts | 5 | ||||
-rw-r--r-- | server/middlewares/validators/users.ts | 22 | ||||
-rw-r--r-- | server/middlewares/validators/videos/video-channels.ts | 16 | ||||
-rw-r--r-- | server/models/actor/actor-follow.ts | 22 | ||||
-rw-r--r-- | server/models/video/video-channel.ts | 24 | ||||
-rw-r--r-- | server/tests/api/check-params/users.ts | 28 | ||||
-rw-r--r-- | server/tests/api/check-params/video-channels.ts | 28 | ||||
-rw-r--r-- | server/tests/api/users/user-subscriptions.ts | 156 |
13 files changed, 349 insertions, 49 deletions
diff --git a/server/controllers/api/accounts.ts b/server/controllers/api/accounts.ts index 75679b0f4..77edfa7c2 100644 --- a/server/controllers/api/accounts.ts +++ b/server/controllers/api/accounts.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | import express from 'express' | 1 | import express from 'express' |
2 | import { pickCommonVideoQuery } from '@server/helpers/query' | 2 | import { pickCommonVideoQuery } from '@server/helpers/query' |
3 | import { ActorFollowModel } from '@server/models/actor/actor-follow' | ||
3 | import { getServerActor } from '@server/models/application/application' | 4 | import { getServerActor } from '@server/models/application/application' |
4 | import { buildNSFWFilter, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' | 5 | import { buildNSFWFilter, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' |
5 | import { getFormattedObjects } from '../../helpers/utils' | 6 | import { getFormattedObjects } from '../../helpers/utils' |
@@ -20,6 +21,7 @@ import { | |||
20 | } from '../../middlewares' | 21 | } from '../../middlewares' |
21 | import { | 22 | import { |
22 | accountNameWithHostGetValidator, | 23 | accountNameWithHostGetValidator, |
24 | accountsFollowersSortValidator, | ||
23 | accountsSortValidator, | 25 | accountsSortValidator, |
24 | ensureAuthUserOwnsAccountValidator, | 26 | ensureAuthUserOwnsAccountValidator, |
25 | videoChannelsSortValidator, | 27 | videoChannelsSortValidator, |
@@ -93,6 +95,17 @@ accountsRouter.get('/:accountName/ratings', | |||
93 | asyncMiddleware(listAccountRatings) | 95 | asyncMiddleware(listAccountRatings) |
94 | ) | 96 | ) |
95 | 97 | ||
98 | accountsRouter.get('/:accountName/followers', | ||
99 | authenticate, | ||
100 | asyncMiddleware(accountNameWithHostGetValidator), | ||
101 | ensureAuthUserOwnsAccountValidator, | ||
102 | paginationValidator, | ||
103 | accountsFollowersSortValidator, | ||
104 | setDefaultSort, | ||
105 | setDefaultPagination, | ||
106 | asyncMiddleware(listAccountFollowers) | ||
107 | ) | ||
108 | |||
96 | // --------------------------------------------------------------------------- | 109 | // --------------------------------------------------------------------------- |
97 | 110 | ||
98 | export { | 111 | export { |
@@ -127,7 +140,7 @@ async function listAccountChannels (req: express.Request, res: express.Response) | |||
127 | search: req.query.search | 140 | search: req.query.search |
128 | } | 141 | } |
129 | 142 | ||
130 | const resultList = await VideoChannelModel.listByAccount(options) | 143 | const resultList = await VideoChannelModel.listByAccountForAPI(options) |
131 | 144 | ||
132 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | 145 | return res.json(getFormattedObjects(resultList.data, resultList.total)) |
133 | } | 146 | } |
@@ -195,3 +208,21 @@ async function listAccountRatings (req: express.Request, res: express.Response) | |||
195 | }) | 208 | }) |
196 | return res.json(getFormattedObjects(resultList.rows, resultList.count)) | 209 | return res.json(getFormattedObjects(resultList.rows, resultList.count)) |
197 | } | 210 | } |
211 | |||
212 | async function listAccountFollowers (req: express.Request, res: express.Response) { | ||
213 | const account = res.locals.account | ||
214 | |||
215 | const channels = await VideoChannelModel.listAllByAccount(account.id) | ||
216 | const actorIds = [ account.actorId ].concat(channels.map(c => c.actorId)) | ||
217 | |||
218 | const resultList = await ActorFollowModel.listFollowersForApi({ | ||
219 | actorIds, | ||
220 | start: req.query.start, | ||
221 | count: req.query.count, | ||
222 | sort: req.query.sort, | ||
223 | search: req.query.search, | ||
224 | state: 'accepted', | ||
225 | }) | ||
226 | |||
227 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | ||
228 | } | ||
diff --git a/server/controllers/api/server/follows.ts b/server/controllers/api/server/follows.ts index 76ed75186..c613386b2 100644 --- a/server/controllers/api/server/follows.ts +++ b/server/controllers/api/server/follows.ts | |||
@@ -98,7 +98,7 @@ export { | |||
98 | 98 | ||
99 | async function listFollowing (req: express.Request, res: express.Response) { | 99 | async function listFollowing (req: express.Request, res: express.Response) { |
100 | const serverActor = await getServerActor() | 100 | const serverActor = await getServerActor() |
101 | const resultList = await ActorFollowModel.listFollowingForApi({ | 101 | const resultList = await ActorFollowModel.listInstanceFollowingForApi({ |
102 | id: serverActor.id, | 102 | id: serverActor.id, |
103 | start: req.query.start, | 103 | start: req.query.start, |
104 | count: req.query.count, | 104 | count: req.query.count, |
@@ -114,7 +114,7 @@ async function listFollowing (req: express.Request, res: express.Response) { | |||
114 | async function listFollowers (req: express.Request, res: express.Response) { | 114 | async function listFollowers (req: express.Request, res: express.Response) { |
115 | const serverActor = await getServerActor() | 115 | const serverActor = await getServerActor() |
116 | const resultList = await ActorFollowModel.listFollowersForApi({ | 116 | const resultList = await ActorFollowModel.listFollowersForApi({ |
117 | actorId: serverActor.id, | 117 | actorIds: [ serverActor.id ], |
118 | start: req.query.start, | 118 | start: req.query.start, |
119 | count: req.query.count, | 119 | count: req.query.count, |
120 | sort: req.query.sort, | 120 | sort: req.query.sort, |
diff --git a/server/controllers/api/users/my-subscriptions.ts b/server/controllers/api/users/my-subscriptions.ts index e3c0cf089..b2b441673 100644 --- a/server/controllers/api/users/my-subscriptions.ts +++ b/server/controllers/api/users/my-subscriptions.ts | |||
@@ -95,7 +95,7 @@ async function areSubscriptionsExist (req: express.Request, res: express.Respons | |||
95 | return { name, host, uri: u } | 95 | return { name, host, uri: u } |
96 | }) | 96 | }) |
97 | 97 | ||
98 | const results = await ActorFollowModel.listSubscribedIn(user.Account.Actor.id, handles) | 98 | const results = await ActorFollowModel.listSubscriptionsOf(user.Account.Actor.id, handles) |
99 | 99 | ||
100 | const existObject: { [id: string ]: boolean } = {} | 100 | const existObject: { [id: string ]: boolean } = {} |
101 | for (const handle of handles) { | 101 | for (const handle of handles) { |
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts index b79dc5933..f370c7004 100644 --- a/server/controllers/api/video-channel.ts +++ b/server/controllers/api/video-channel.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import express from 'express' | 1 | import express from 'express' |
2 | import { pickCommonVideoQuery } from '@server/helpers/query' | 2 | import { pickCommonVideoQuery } from '@server/helpers/query' |
3 | import { Hooks } from '@server/lib/plugins/hooks' | 3 | import { Hooks } from '@server/lib/plugins/hooks' |
4 | import { ActorFollowModel } from '@server/models/actor/actor-follow' | ||
4 | import { getServerActor } from '@server/models/application/application' | 5 | import { getServerActor } from '@server/models/application/application' |
5 | import { MChannelBannerAccountDefault } from '@server/types/models' | 6 | import { MChannelBannerAccountDefault } from '@server/types/models' |
6 | import { ActorImageType, VideoChannelCreate, VideoChannelUpdate } from '../../../shared' | 7 | import { ActorImageType, VideoChannelCreate, VideoChannelUpdate } from '../../../shared' |
@@ -33,7 +34,13 @@ import { | |||
33 | videoChannelsUpdateValidator, | 34 | videoChannelsUpdateValidator, |
34 | videoPlaylistsSortValidator | 35 | videoPlaylistsSortValidator |
35 | } from '../../middlewares' | 36 | } from '../../middlewares' |
36 | import { videoChannelsListValidator, videoChannelsNameWithHostValidator, videosSortValidator } from '../../middlewares/validators' | 37 | import { |
38 | ensureAuthUserOwnsChannelValidator, | ||
39 | videoChannelsFollowersSortValidator, | ||
40 | videoChannelsListValidator, | ||
41 | videoChannelsNameWithHostValidator, | ||
42 | videosSortValidator | ||
43 | } from '../../middlewares/validators' | ||
37 | import { updateAvatarValidator, updateBannerValidator } from '../../middlewares/validators/actor-image' | 44 | import { updateAvatarValidator, updateBannerValidator } from '../../middlewares/validators/actor-image' |
38 | import { commonVideoPlaylistFiltersValidator } from '../../middlewares/validators/videos/video-playlists' | 45 | import { commonVideoPlaylistFiltersValidator } from '../../middlewares/validators/videos/video-playlists' |
39 | import { AccountModel } from '../../models/account/account' | 46 | import { AccountModel } from '../../models/account/account' |
@@ -65,8 +72,8 @@ videoChannelRouter.post('/', | |||
65 | videoChannelRouter.post('/:nameWithHost/avatar/pick', | 72 | videoChannelRouter.post('/:nameWithHost/avatar/pick', |
66 | authenticate, | 73 | authenticate, |
67 | reqAvatarFile, | 74 | reqAvatarFile, |
68 | // Check the rights | 75 | asyncMiddleware(videoChannelsNameWithHostValidator), |
69 | asyncMiddleware(videoChannelsUpdateValidator), | 76 | ensureAuthUserOwnsChannelValidator, |
70 | updateAvatarValidator, | 77 | updateAvatarValidator, |
71 | asyncMiddleware(updateVideoChannelAvatar) | 78 | asyncMiddleware(updateVideoChannelAvatar) |
72 | ) | 79 | ) |
@@ -74,29 +81,31 @@ videoChannelRouter.post('/:nameWithHost/avatar/pick', | |||
74 | videoChannelRouter.post('/:nameWithHost/banner/pick', | 81 | videoChannelRouter.post('/:nameWithHost/banner/pick', |
75 | authenticate, | 82 | authenticate, |
76 | reqBannerFile, | 83 | reqBannerFile, |
77 | // Check the rights | 84 | asyncMiddleware(videoChannelsNameWithHostValidator), |
78 | asyncMiddleware(videoChannelsUpdateValidator), | 85 | ensureAuthUserOwnsChannelValidator, |
79 | updateBannerValidator, | 86 | updateBannerValidator, |
80 | asyncMiddleware(updateVideoChannelBanner) | 87 | asyncMiddleware(updateVideoChannelBanner) |
81 | ) | 88 | ) |
82 | 89 | ||
83 | videoChannelRouter.delete('/:nameWithHost/avatar', | 90 | videoChannelRouter.delete('/:nameWithHost/avatar', |
84 | authenticate, | 91 | authenticate, |
85 | // Check the rights | 92 | asyncMiddleware(videoChannelsNameWithHostValidator), |
86 | asyncMiddleware(videoChannelsUpdateValidator), | 93 | ensureAuthUserOwnsChannelValidator, |
87 | asyncMiddleware(deleteVideoChannelAvatar) | 94 | asyncMiddleware(deleteVideoChannelAvatar) |
88 | ) | 95 | ) |
89 | 96 | ||
90 | videoChannelRouter.delete('/:nameWithHost/banner', | 97 | videoChannelRouter.delete('/:nameWithHost/banner', |
91 | authenticate, | 98 | authenticate, |
92 | // Check the rights | 99 | asyncMiddleware(videoChannelsNameWithHostValidator), |
93 | asyncMiddleware(videoChannelsUpdateValidator), | 100 | ensureAuthUserOwnsChannelValidator, |
94 | asyncMiddleware(deleteVideoChannelBanner) | 101 | asyncMiddleware(deleteVideoChannelBanner) |
95 | ) | 102 | ) |
96 | 103 | ||
97 | videoChannelRouter.put('/:nameWithHost', | 104 | videoChannelRouter.put('/:nameWithHost', |
98 | authenticate, | 105 | authenticate, |
99 | asyncMiddleware(videoChannelsUpdateValidator), | 106 | asyncMiddleware(videoChannelsNameWithHostValidator), |
107 | ensureAuthUserOwnsChannelValidator, | ||
108 | videoChannelsUpdateValidator, | ||
100 | asyncRetryTransactionMiddleware(updateVideoChannel) | 109 | asyncRetryTransactionMiddleware(updateVideoChannel) |
101 | ) | 110 | ) |
102 | 111 | ||
@@ -132,6 +141,17 @@ videoChannelRouter.get('/:nameWithHost/videos', | |||
132 | asyncMiddleware(listVideoChannelVideos) | 141 | asyncMiddleware(listVideoChannelVideos) |
133 | ) | 142 | ) |
134 | 143 | ||
144 | videoChannelRouter.get('/:nameWithHost/followers', | ||
145 | authenticate, | ||
146 | asyncMiddleware(videoChannelsNameWithHostValidator), | ||
147 | ensureAuthUserOwnsChannelValidator, | ||
148 | paginationValidator, | ||
149 | videoChannelsFollowersSortValidator, | ||
150 | setDefaultSort, | ||
151 | setDefaultPagination, | ||
152 | asyncMiddleware(listVideoChannelFollowers) | ||
153 | ) | ||
154 | |||
135 | // --------------------------------------------------------------------------- | 155 | // --------------------------------------------------------------------------- |
136 | 156 | ||
137 | export { | 157 | export { |
@@ -332,3 +352,18 @@ async function listVideoChannelVideos (req: express.Request, res: express.Respon | |||
332 | 352 | ||
333 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | 353 | return res.json(getFormattedObjects(resultList.data, resultList.total)) |
334 | } | 354 | } |
355 | |||
356 | async function listVideoChannelFollowers (req: express.Request, res: express.Response) { | ||
357 | const channel = res.locals.videoChannel | ||
358 | |||
359 | const resultList = await ActorFollowModel.listFollowersForApi({ | ||
360 | actorIds: [ channel.actorId ], | ||
361 | start: req.query.start, | ||
362 | count: req.query.count, | ||
363 | sort: req.query.sort, | ||
364 | search: req.query.search, | ||
365 | state: 'accepted', | ||
366 | }) | ||
367 | |||
368 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | ||
369 | } | ||
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 029984559..dcbad9264 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -69,8 +69,11 @@ const SORTABLE_COLUMNS = { | |||
69 | 69 | ||
70 | VIDEO_RATES: [ 'createdAt' ], | 70 | VIDEO_RATES: [ 'createdAt' ], |
71 | BLACKLISTS: [ 'id', 'name', 'duration', 'views', 'likes', 'dislikes', 'uuid', 'createdAt' ], | 71 | BLACKLISTS: [ 'id', 'name', 'duration', 'views', 'likes', 'dislikes', 'uuid', 'createdAt' ], |
72 | |||
72 | INSTANCE_FOLLOWERS: [ 'createdAt', 'state', 'score' ], | 73 | INSTANCE_FOLLOWERS: [ 'createdAt', 'state', 'score' ], |
73 | INSTANCE_FOLLOWING: [ 'createdAt', 'redundancyAllowed', 'state' ], | 74 | INSTANCE_FOLLOWING: [ 'createdAt', 'redundancyAllowed', 'state' ], |
75 | ACCOUNT_FOLLOWERS: [ 'createdAt' ], | ||
76 | CHANNEL_FOLLOWERS: [ 'createdAt' ], | ||
74 | 77 | ||
75 | VIDEOS: [ 'name', 'duration', 'createdAt', 'publishedAt', 'originallyPublishedAt', 'views', 'likes', 'trending', 'hot', 'best' ], | 78 | VIDEOS: [ 'name', 'duration', 'createdAt', 'publishedAt', 'originallyPublishedAt', 'views', 'likes', 'trending', 'hot', 'best' ], |
76 | 79 | ||
diff --git a/server/middlewares/validators/sort.ts b/server/middlewares/validators/sort.ts index ce8df8fee..3ba668460 100644 --- a/server/middlewares/validators/sort.ts +++ b/server/middlewares/validators/sort.ts | |||
@@ -53,6 +53,9 @@ const pluginsSortValidator = checkSortFactory(SORTABLE_COLUMNS.PLUGINS) | |||
53 | const availablePluginsSortValidator = checkSortFactory(SORTABLE_COLUMNS.AVAILABLE_PLUGINS) | 53 | const availablePluginsSortValidator = checkSortFactory(SORTABLE_COLUMNS.AVAILABLE_PLUGINS) |
54 | const videoRedundanciesSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_REDUNDANCIES) | 54 | const videoRedundanciesSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_REDUNDANCIES) |
55 | 55 | ||
56 | const accountsFollowersSortValidator = checkSortFactory(SORTABLE_COLUMNS.ACCOUNT_FOLLOWERS) | ||
57 | const videoChannelsFollowersSortValidator = checkSortFactory(SORTABLE_COLUMNS.CHANNEL_FOLLOWERS) | ||
58 | |||
56 | // --------------------------------------------------------------------------- | 59 | // --------------------------------------------------------------------------- |
57 | 60 | ||
58 | export { | 61 | export { |
@@ -79,5 +82,7 @@ export { | |||
79 | videoPlaylistsSortValidator, | 82 | videoPlaylistsSortValidator, |
80 | videoRedundanciesSortValidator, | 83 | videoRedundanciesSortValidator, |
81 | videoPlaylistsSearchSortValidator, | 84 | videoPlaylistsSearchSortValidator, |
85 | accountsFollowersSortValidator, | ||
86 | videoChannelsFollowersSortValidator, | ||
82 | pluginsSortValidator | 87 | pluginsSortValidator |
83 | } | 88 | } |
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts index c06b85862..c6eeeaf18 100644 --- a/server/middlewares/validators/users.ts +++ b/server/middlewares/validators/users.ts | |||
@@ -3,9 +3,7 @@ import { body, param, query } from 'express-validator' | |||
3 | import { omit } from 'lodash' | 3 | import { omit } from 'lodash' |
4 | import { Hooks } from '@server/lib/plugins/hooks' | 4 | import { Hooks } from '@server/lib/plugins/hooks' |
5 | import { MUserDefault } from '@server/types/models' | 5 | import { MUserDefault } from '@server/types/models' |
6 | import { HttpStatusCode } from '../../../shared/models/http/http-error-codes' | 6 | import { HttpStatusCode, UserRegister, UserRole } from '@shared/models' |
7 | import { UserRole } from '../../../shared/models/users' | ||
8 | import { UserRegister } from '../../../shared/models/users/user-register.model' | ||
9 | import { toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' | 7 | import { toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' |
10 | import { isThemeNameValid } from '../../helpers/custom-validators/plugins' | 8 | import { isThemeNameValid } from '../../helpers/custom-validators/plugins' |
11 | import { | 9 | import { |
@@ -462,7 +460,22 @@ const ensureAuthUserOwnsAccountValidator = [ | |||
462 | if (res.locals.account.id !== user.Account.id) { | 460 | if (res.locals.account.id !== user.Account.id) { |
463 | return res.fail({ | 461 | return res.fail({ |
464 | status: HttpStatusCode.FORBIDDEN_403, | 462 | status: HttpStatusCode.FORBIDDEN_403, |
465 | message: 'Only owner can access ratings list.' | 463 | message: 'Only owner of this account can access this ressource.' |
464 | }) | ||
465 | } | ||
466 | |||
467 | return next() | ||
468 | } | ||
469 | ] | ||
470 | |||
471 | const ensureAuthUserOwnsChannelValidator = [ | ||
472 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
473 | const user = res.locals.oauth.token.User | ||
474 | |||
475 | if (res.locals.videoChannel.Account.userId !== user.id) { | ||
476 | return res.fail({ | ||
477 | status: HttpStatusCode.FORBIDDEN_403, | ||
478 | message: 'Only owner of this video channel can access this ressource' | ||
466 | }) | 479 | }) |
467 | } | 480 | } |
468 | 481 | ||
@@ -506,6 +519,7 @@ export { | |||
506 | usersVerifyEmailValidator, | 519 | usersVerifyEmailValidator, |
507 | userAutocompleteValidator, | 520 | userAutocompleteValidator, |
508 | ensureAuthUserOwnsAccountValidator, | 521 | ensureAuthUserOwnsAccountValidator, |
522 | ensureAuthUserOwnsChannelValidator, | ||
509 | ensureCanManageUser | 523 | ensureCanManageUser |
510 | } | 524 | } |
511 | 525 | ||
diff --git a/server/middlewares/validators/videos/video-channels.ts b/server/middlewares/validators/videos/video-channels.ts index fc717abf6..ec107fa51 100644 --- a/server/middlewares/validators/videos/video-channels.ts +++ b/server/middlewares/validators/videos/video-channels.ts | |||
@@ -65,22 +65,6 @@ const videoChannelsUpdateValidator = [ | |||
65 | logger.debug('Checking videoChannelsUpdate parameters', { parameters: req.body }) | 65 | logger.debug('Checking videoChannelsUpdate parameters', { parameters: req.body }) |
66 | 66 | ||
67 | if (areValidationErrors(req, res)) return | 67 | if (areValidationErrors(req, res)) return |
68 | if (!await doesVideoChannelNameWithHostExist(req.params.nameWithHost, res)) return | ||
69 | |||
70 | // We need to make additional checks | ||
71 | if (res.locals.videoChannel.Actor.isOwned() === false) { | ||
72 | return res.fail({ | ||
73 | status: HttpStatusCode.FORBIDDEN_403, | ||
74 | message: 'Cannot update video channel of another server' | ||
75 | }) | ||
76 | } | ||
77 | |||
78 | if (res.locals.videoChannel.Account.userId !== res.locals.oauth.token.User.id) { | ||
79 | return res.fail({ | ||
80 | status: HttpStatusCode.FORBIDDEN_403, | ||
81 | message: 'Cannot update video channel of another user' | ||
82 | }) | ||
83 | } | ||
84 | 68 | ||
85 | return next() | 69 | return next() |
86 | } | 70 | } |
diff --git a/server/models/actor/actor-follow.ts b/server/models/actor/actor-follow.ts index fc1cc7499..d6a2387a5 100644 --- a/server/models/actor/actor-follow.ts +++ b/server/models/actor/actor-follow.ts | |||
@@ -143,7 +143,7 @@ export class ActorFollowModel extends Model<Partial<AttributesOnly<ActorFollowMo | |||
143 | * @deprecated Use `findOrCreateCustom` instead | 143 | * @deprecated Use `findOrCreateCustom` instead |
144 | */ | 144 | */ |
145 | static findOrCreate (): any { | 145 | static findOrCreate (): any { |
146 | throw new Error('Should not be called') | 146 | throw new Error('Must not be called') |
147 | } | 147 | } |
148 | 148 | ||
149 | // findOrCreate has issues with actor follow hooks | 149 | // findOrCreate has issues with actor follow hooks |
@@ -288,7 +288,7 @@ export class ActorFollowModel extends Model<Partial<AttributesOnly<ActorFollowMo | |||
288 | return ActorFollowModel.findOne(query) | 288 | return ActorFollowModel.findOne(query) |
289 | } | 289 | } |
290 | 290 | ||
291 | static listSubscribedIn (actorId: number, targets: { name: string, host?: string }[]): Promise<MActorFollowFollowingHost[]> { | 291 | static listSubscriptionsOf (actorId: number, targets: { name: string, host?: string }[]): Promise<MActorFollowFollowingHost[]> { |
292 | const whereTab = targets | 292 | const whereTab = targets |
293 | .map(t => { | 293 | .map(t => { |
294 | if (t.host) { | 294 | if (t.host) { |
@@ -348,7 +348,7 @@ export class ActorFollowModel extends Model<Partial<AttributesOnly<ActorFollowMo | |||
348 | return ActorFollowModel.findAll(query) | 348 | return ActorFollowModel.findAll(query) |
349 | } | 349 | } |
350 | 350 | ||
351 | static listFollowingForApi (options: { | 351 | static listInstanceFollowingForApi (options: { |
352 | id: number | 352 | id: number |
353 | start: number | 353 | start: number |
354 | count: number | 354 | count: number |
@@ -415,7 +415,7 @@ export class ActorFollowModel extends Model<Partial<AttributesOnly<ActorFollowMo | |||
415 | } | 415 | } |
416 | 416 | ||
417 | static listFollowersForApi (options: { | 417 | static listFollowersForApi (options: { |
418 | actorId: number | 418 | actorIds: number[] |
419 | start: number | 419 | start: number |
420 | count: number | 420 | count: number |
421 | sort: string | 421 | sort: string |
@@ -423,7 +423,7 @@ export class ActorFollowModel extends Model<Partial<AttributesOnly<ActorFollowMo | |||
423 | actorType?: ActivityPubActorType | 423 | actorType?: ActivityPubActorType |
424 | search?: string | 424 | search?: string |
425 | }) { | 425 | }) { |
426 | const { actorId, start, count, sort, search, state, actorType } = options | 426 | const { actorIds, start, count, sort, search, state, actorType } = options |
427 | 427 | ||
428 | const followWhere = state ? { state } : {} | 428 | const followWhere = state ? { state } : {} |
429 | const followerWhere: WhereOptions = {} | 429 | const followerWhere: WhereOptions = {} |
@@ -452,20 +452,16 @@ export class ActorFollowModel extends Model<Partial<AttributesOnly<ActorFollowMo | |||
452 | model: ActorModel, | 452 | model: ActorModel, |
453 | required: true, | 453 | required: true, |
454 | as: 'ActorFollower', | 454 | as: 'ActorFollower', |
455 | where: followerWhere, | 455 | where: followerWhere |
456 | include: [ | ||
457 | { | ||
458 | model: ServerModel, | ||
459 | required: true | ||
460 | } | ||
461 | ] | ||
462 | }, | 456 | }, |
463 | { | 457 | { |
464 | model: ActorModel, | 458 | model: ActorModel, |
465 | as: 'ActorFollowing', | 459 | as: 'ActorFollowing', |
466 | required: true, | 460 | required: true, |
467 | where: { | 461 | where: { |
468 | id: actorId | 462 | id: { |
463 | [Op.in]: actorIds | ||
464 | } | ||
469 | } | 465 | } |
470 | } | 466 | } |
471 | ] | 467 | ] |
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts index 8ccb818b3..a151ad61c 100644 --- a/server/models/video/video-channel.ts +++ b/server/models/video/video-channel.ts | |||
@@ -26,7 +26,7 @@ import { | |||
26 | isVideoChannelDisplayNameValid, | 26 | isVideoChannelDisplayNameValid, |
27 | isVideoChannelSupportValid | 27 | isVideoChannelSupportValid |
28 | } from '../../helpers/custom-validators/video-channels' | 28 | } from '../../helpers/custom-validators/video-channels' |
29 | import { CONSTRAINTS_FIELDS, WEBSERVER } from '../../initializers/constants' | 29 | import { CONSTRAINTS_FIELDS, VIDEO_CHANNELS, WEBSERVER } from '../../initializers/constants' |
30 | import { sendDeleteActor } from '../../lib/activitypub/send' | 30 | import { sendDeleteActor } from '../../lib/activitypub/send' |
31 | import { | 31 | import { |
32 | MChannelActor, | 32 | MChannelActor, |
@@ -527,7 +527,7 @@ ON "Account->Actor"."serverId" = "Account->Actor->Server"."id"` | |||
527 | }) | 527 | }) |
528 | } | 528 | } |
529 | 529 | ||
530 | static listByAccount (options: { | 530 | static listByAccountForAPI (options: { |
531 | accountId: number | 531 | accountId: number |
532 | start: number | 532 | start: number |
533 | count: number | 533 | count: number |
@@ -582,6 +582,26 @@ ON "Account->Actor"."serverId" = "Account->Actor->Server"."id"` | |||
582 | }) | 582 | }) |
583 | } | 583 | } |
584 | 584 | ||
585 | |||
586 | static listAllByAccount (accountId: number) { | ||
587 | const query = { | ||
588 | limit: VIDEO_CHANNELS.MAX_PER_USER, | ||
589 | include: [ | ||
590 | { | ||
591 | attributes: [], | ||
592 | model: AccountModel, | ||
593 | where: { | ||
594 | id: accountId | ||
595 | }, | ||
596 | required: true | ||
597 | } | ||
598 | ] | ||
599 | } | ||
600 | |||
601 | return VideoChannelModel.findAll(query) | ||
602 | } | ||
603 | |||
604 | |||
585 | static loadAndPopulateAccount (id: number, transaction?: Transaction): Promise<MChannelBannerAccountDefault> { | 605 | static loadAndPopulateAccount (id: number, transaction?: Transaction): Promise<MChannelBannerAccountDefault> { |
586 | return VideoChannelModel.unscoped() | 606 | return VideoChannelModel.unscoped() |
587 | .scope([ ScopeNames.WITH_ACTOR_BANNER, ScopeNames.WITH_ACCOUNT ]) | 607 | .scope([ ScopeNames.WITH_ACTOR_BANNER, ScopeNames.WITH_ACCOUNT ]) |
diff --git a/server/tests/api/check-params/users.ts b/server/tests/api/check-params/users.ts index 58b360f92..517e2f423 100644 --- a/server/tests/api/check-params/users.ts +++ b/server/tests/api/check-params/users.ts | |||
@@ -840,6 +840,34 @@ describe('Test users API validators', function () { | |||
840 | }) | 840 | }) |
841 | }) | 841 | }) |
842 | 842 | ||
843 | describe('When getting my global followers', function () { | ||
844 | const path = '/api/v1/accounts/user1/followers' | ||
845 | |||
846 | it('Should fail with a bad start pagination', async function () { | ||
847 | await checkBadStartPagination(server.url, path, userToken) | ||
848 | }) | ||
849 | |||
850 | it('Should fail with a bad count pagination', async function () { | ||
851 | await checkBadCountPagination(server.url, path, userToken) | ||
852 | }) | ||
853 | |||
854 | it('Should fail with an incorrect sort', async function () { | ||
855 | await checkBadSortPagination(server.url, path, userToken) | ||
856 | }) | ||
857 | |||
858 | it('Should fail with a unauthenticated user', async function () { | ||
859 | await makeGetRequest({ url: server.url, path, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) | ||
860 | }) | ||
861 | |||
862 | it('Should fail with a another user', async function () { | ||
863 | await makeGetRequest({ url: server.url, path, token: server.accessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | ||
864 | }) | ||
865 | |||
866 | it('Should succeed with the correct params', async function () { | ||
867 | await makeGetRequest({ url: server.url, path, token: userToken, expectedStatus: HttpStatusCode.OK_200 }) | ||
868 | }) | ||
869 | }) | ||
870 | |||
843 | describe('When blocking/unblocking/removing user', function () { | 871 | describe('When blocking/unblocking/removing user', function () { |
844 | 872 | ||
845 | it('Should fail with an incorrect id', async function () { | 873 | it('Should fail with an incorrect id', async function () { |
diff --git a/server/tests/api/check-params/video-channels.ts b/server/tests/api/check-params/video-channels.ts index 2e63916d4..e86c315fa 100644 --- a/server/tests/api/check-params/video-channels.ts +++ b/server/tests/api/check-params/video-channels.ts | |||
@@ -321,6 +321,34 @@ describe('Test video channels API validator', function () { | |||
321 | }) | 321 | }) |
322 | }) | 322 | }) |
323 | 323 | ||
324 | describe('When getting channel followers', function () { | ||
325 | const path = '/api/v1/video-channels/super_channel/followers' | ||
326 | |||
327 | it('Should fail with a bad start pagination', async function () { | ||
328 | await checkBadStartPagination(server.url, path, server.accessToken) | ||
329 | }) | ||
330 | |||
331 | it('Should fail with a bad count pagination', async function () { | ||
332 | await checkBadCountPagination(server.url, path, server.accessToken) | ||
333 | }) | ||
334 | |||
335 | it('Should fail with an incorrect sort', async function () { | ||
336 | await checkBadSortPagination(server.url, path, server.accessToken) | ||
337 | }) | ||
338 | |||
339 | it('Should fail with a unauthenticated user', async function () { | ||
340 | await makeGetRequest({ url: server.url, path, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) | ||
341 | }) | ||
342 | |||
343 | it('Should fail with a another user', async function () { | ||
344 | await makeGetRequest({ url: server.url, path, token: accessTokenUser, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | ||
345 | }) | ||
346 | |||
347 | it('Should succeed with the correct params', async function () { | ||
348 | await makeGetRequest({ url: server.url, path, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 }) | ||
349 | }) | ||
350 | }) | ||
351 | |||
324 | describe('When deleting a video channel', function () { | 352 | describe('When deleting a video channel', function () { |
325 | it('Should fail with a non authenticated user', async function () { | 353 | it('Should fail with a non authenticated user', async function () { |
326 | await command.delete({ token: 'coucou', channelName: 'super_channel', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) | 354 | await command.delete({ token: 'coucou', channelName: 'super_channel', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) |
diff --git a/server/tests/api/users/user-subscriptions.ts b/server/tests/api/users/user-subscriptions.ts index 441f70d07..b49367be6 100644 --- a/server/tests/api/users/user-subscriptions.ts +++ b/server/tests/api/users/user-subscriptions.ts | |||
@@ -368,6 +368,162 @@ describe('Test users subscriptions', function () { | |||
368 | } | 368 | } |
369 | }) | 369 | }) |
370 | 370 | ||
371 | it('Should follow user channels of server 3 by root of server 3', async function () { | ||
372 | this.timeout(60000) | ||
373 | |||
374 | await servers[2].channels.create({ token: users[2].accessToken, attributes: { name: 'user3_channel2' } }) | ||
375 | |||
376 | await servers[2].subscriptions.add({ token: servers[2].accessToken, targetUri: 'user3_channel@localhost:' + servers[2].port }) | ||
377 | await servers[2].subscriptions.add({ token: servers[2].accessToken, targetUri: 'user3_channel2@localhost:' + servers[2].port }) | ||
378 | |||
379 | await waitJobs(servers) | ||
380 | }) | ||
381 | |||
382 | it('Should list user 3 followers', async function () { | ||
383 | { | ||
384 | const { total, data } = await servers[2].accounts.listFollowers({ | ||
385 | token: users[2].accessToken, | ||
386 | accountName: 'user3', | ||
387 | start: 0, | ||
388 | count: 5, | ||
389 | sort: 'createdAt' | ||
390 | }) | ||
391 | |||
392 | expect(total).to.equal(3) | ||
393 | expect(data[0].following.host).to.equal(servers[2].host) | ||
394 | expect(data[0].following.name).to.equal('user3_channel') | ||
395 | expect(data[0].follower.host).to.equal(servers[0].host) | ||
396 | expect(data[0].follower.name).to.equal('user1') | ||
397 | |||
398 | expect(data[1].following.host).to.equal(servers[2].host) | ||
399 | expect(data[1].following.name).to.equal('user3_channel') | ||
400 | expect(data[1].follower.host).to.equal(servers[2].host) | ||
401 | expect(data[1].follower.name).to.equal('root') | ||
402 | |||
403 | expect(data[2].following.host).to.equal(servers[2].host) | ||
404 | expect(data[2].following.name).to.equal('user3_channel2') | ||
405 | expect(data[2].follower.host).to.equal(servers[2].host) | ||
406 | expect(data[2].follower.name).to.equal('root') | ||
407 | } | ||
408 | |||
409 | { | ||
410 | const { total, data } = await servers[2].accounts.listFollowers({ | ||
411 | token: users[2].accessToken, | ||
412 | accountName: 'user3', | ||
413 | start: 0, | ||
414 | count: 1, | ||
415 | sort: '-createdAt' | ||
416 | }) | ||
417 | |||
418 | expect(total).to.equal(3) | ||
419 | expect(data[0].following.host).to.equal(servers[2].host) | ||
420 | expect(data[0].following.name).to.equal('user3_channel2') | ||
421 | expect(data[0].follower.host).to.equal(servers[2].host) | ||
422 | expect(data[0].follower.name).to.equal('root') | ||
423 | } | ||
424 | |||
425 | { | ||
426 | const { total, data } = await servers[2].accounts.listFollowers({ | ||
427 | token: users[2].accessToken, | ||
428 | accountName: 'user3', | ||
429 | start: 1, | ||
430 | count: 1, | ||
431 | sort: '-createdAt' | ||
432 | }) | ||
433 | |||
434 | expect(total).to.equal(3) | ||
435 | expect(data[0].following.host).to.equal(servers[2].host) | ||
436 | expect(data[0].following.name).to.equal('user3_channel') | ||
437 | expect(data[0].follower.host).to.equal(servers[2].host) | ||
438 | expect(data[0].follower.name).to.equal('root') | ||
439 | } | ||
440 | |||
441 | { | ||
442 | const { total, data } = await servers[2].accounts.listFollowers({ | ||
443 | token: users[2].accessToken, | ||
444 | accountName: 'user3', | ||
445 | search: 'user1', | ||
446 | sort: '-createdAt' | ||
447 | }) | ||
448 | |||
449 | expect(total).to.equal(1) | ||
450 | expect(data[0].following.host).to.equal(servers[2].host) | ||
451 | expect(data[0].following.name).to.equal('user3_channel') | ||
452 | expect(data[0].follower.host).to.equal(servers[0].host) | ||
453 | expect(data[0].follower.name).to.equal('user1') | ||
454 | } | ||
455 | }) | ||
456 | |||
457 | it('Should list user3_channel followers', async function () { | ||
458 | { | ||
459 | const { total, data } = await servers[2].channels.listFollowers({ | ||
460 | token: users[2].accessToken, | ||
461 | channelName: 'user3_channel', | ||
462 | start: 0, | ||
463 | count: 5, | ||
464 | sort: 'createdAt' | ||
465 | }) | ||
466 | |||
467 | expect(total).to.equal(2) | ||
468 | expect(data[0].following.host).to.equal(servers[2].host) | ||
469 | expect(data[0].following.name).to.equal('user3_channel') | ||
470 | expect(data[0].follower.host).to.equal(servers[0].host) | ||
471 | expect(data[0].follower.name).to.equal('user1') | ||
472 | |||
473 | expect(data[1].following.host).to.equal(servers[2].host) | ||
474 | expect(data[1].following.name).to.equal('user3_channel') | ||
475 | expect(data[1].follower.host).to.equal(servers[2].host) | ||
476 | expect(data[1].follower.name).to.equal('root') | ||
477 | } | ||
478 | |||
479 | { | ||
480 | const { total, data } = await servers[2].channels.listFollowers({ | ||
481 | token: users[2].accessToken, | ||
482 | channelName: 'user3_channel', | ||
483 | start: 0, | ||
484 | count: 1, | ||
485 | sort: '-createdAt' | ||
486 | }) | ||
487 | |||
488 | expect(total).to.equal(2) | ||
489 | expect(data[0].following.host).to.equal(servers[2].host) | ||
490 | expect(data[0].following.name).to.equal('user3_channel') | ||
491 | expect(data[0].follower.host).to.equal(servers[2].host) | ||
492 | expect(data[0].follower.name).to.equal('root') | ||
493 | } | ||
494 | |||
495 | { | ||
496 | const { total, data } = await servers[2].channels.listFollowers({ | ||
497 | token: users[2].accessToken, | ||
498 | channelName: 'user3_channel', | ||
499 | start: 1, | ||
500 | count: 1, | ||
501 | sort: '-createdAt' | ||
502 | }) | ||
503 | |||
504 | expect(total).to.equal(2) | ||
505 | expect(data[0].following.host).to.equal(servers[2].host) | ||
506 | expect(data[0].following.name).to.equal('user3_channel') | ||
507 | expect(data[0].follower.host).to.equal(servers[0].host) | ||
508 | expect(data[0].follower.name).to.equal('root') | ||
509 | } | ||
510 | |||
511 | { | ||
512 | const { total, data } = await servers[2].channels.listFollowers({ | ||
513 | token: users[2].accessToken, | ||
514 | channelName: 'user3_channel', | ||
515 | search: 'user1', | ||
516 | sort: '-createdAt' | ||
517 | }) | ||
518 | |||
519 | expect(total).to.equal(1) | ||
520 | expect(data[0].following.host).to.equal(servers[2].host) | ||
521 | expect(data[0].following.name).to.equal('user3_channel') | ||
522 | expect(data[0].follower.host).to.equal(servers[0].host) | ||
523 | expect(data[0].follower.name).to.equal('user1') | ||
524 | } | ||
525 | }) | ||
526 | |||
371 | after(async function () { | 527 | after(async function () { |
372 | await cleanupTests(servers) | 528 | await cleanupTests(servers) |
373 | }) | 529 | }) |