<div class="row" myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()">
<div class="col-xl-6 col-md-12">
- <div i18n class="subtitle">Followers</div>
+ <div i18n class="subtitle">Followers instances</div>
- <div i18n class="no-results" *ngIf="followersPagination.totalItems === 0">This instance does not have followers.</div>
+ <div i18n class="no-results" *ngIf="followersPagination.totalItems === 0">This instance does not have instances followers.</div>
<a *ngFor="let follower of followers" [href]="buildLink(follower)" target="_blank" rel="noopener noreferrer">
{{ follower }}
</div>
<div class="col-xl-6 col-md-12">
- <div i18n class="subtitle">Followings</div>
+ <div i18n class="subtitle">Followings instances</div>
- <div i18n class="no-results" *ngIf="followingsPagination.totalItems === 0">This instance does not have followings.</div>
+ <div i18n class="no-results" *ngIf="followingsPagination.totalItems === 0">This instance does not have instances followings.</div>
<a *ngFor="let following of followings" [href]="buildLink(following)" target="_blank" rel="noopener noreferrer">
{{ following }}
import { HttpClient, HttpParams } from '@angular/common/http'
import { Injectable } from '@angular/core'
import { Observable } from 'rxjs'
-import { ActorFollow, FollowState, ResultList } from '@shared/index'
+import { ActivityPubActorType, ActorFollow, FollowState, ResultList } from '@shared/index'
import { environment } from '../../../environments/environment'
import { RestExtractor, RestPagination, RestService } from '../rest'
import { SortMeta } from 'primeng/api'
pagination: RestPagination,
sort: SortMeta,
search?: string,
+ actorType?: ActivityPubActorType,
state?: FollowState
}): Observable<ResultList<ActorFollow>> {
- const { pagination, sort, search, state } = options
+ const { pagination, sort, search, state, actorType } = options
let params = new HttpParams()
params = this.restService.addRestGetParams(params, pagination, sort)
if (search) params = params.append('search', search)
if (state) params = params.append('state', state)
+ if (actorType) params = params.append('actorType', actorType)
return this.authHttp.get<ResultList<ActorFollow>>(FollowService.BASE_APPLICATION_URL + '/following', { params })
.pipe(
pagination: RestPagination,
sort: SortMeta,
search?: string,
+ actorType?: ActivityPubActorType,
state?: FollowState
}): Observable<ResultList<ActorFollow>> {
- const { pagination, sort, search, state } = options
+ const { pagination, sort, search, state, actorType } = options
let params = new HttpParams()
params = this.restService.addRestGetParams(params, pagination, sort)
if (search) params = params.append('search', search)
if (state) params = params.append('state', state)
+ if (actorType) params = params.append('actorType', actorType)
return this.authHttp.get<ResultList<ActorFollow>>(FollowService.BASE_APPLICATION_URL + '/followers', { params })
.pipe(
count: req.query.count,
sort: req.query.sort,
search: req.query.search,
+ actorType: req.query.actorType,
state: req.query.state
})
count: req.query.count,
sort: req.query.sort,
search: req.query.search,
+ actorType: req.query.actorType,
state: req.query.state
})
import { areValidationErrors } from './utils'
import { ActorModel } from '../../models/activitypub/actor'
import { loadActorUrlOrGetFromWebfinger } from '../../helpers/webfinger'
-import { isValidActorHandle } from '../../helpers/custom-validators/activitypub/actor'
+import { isActorTypeValid, isValidActorHandle } from '../../helpers/custom-validators/activitypub/actor'
import { MActorFollowActorsDefault } from '@server/typings/models'
import { isFollowStateValid } from '@server/helpers/custom-validators/follows'
query('state')
.optional()
.custom(isFollowStateValid).withMessage('Should have a valid follow state'),
+ query('actorType')
+ .optional()
+ .custom(isActorTypeValid).withMessage('Should have a valid actor type'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
if (areValidationErrors(req, res)) return
import { ActorModel, unusedActorAttributesForAPI } from './actor'
import { VideoChannelModel } from '../video/video-channel'
import { AccountModel } from '../account/account'
-import { IncludeOptions, Op, QueryTypes, Transaction } from 'sequelize'
+import { IncludeOptions, Op, QueryTypes, Transaction, WhereOptions } from 'sequelize'
import {
MActorFollowActorsDefault,
MActorFollowActorsDefaultSubscription,
MActorFollowFormattable,
MActorFollowSubscriptions
} from '@server/typings/models'
+import { ActivityPubActorType } from '@shared/models'
@Table({
tableName: 'actorFollow',
count: number,
sort: string,
state?: FollowState,
+ actorType?: ActivityPubActorType,
search?: string
}) {
- const { id, start, count, sort, search, state } = options
+ const { id, start, count, sort, search, state, actorType } = options
const followWhere = state ? { state } : {}
+ const followingWhere: WhereOptions = {}
+ const followingServerWhere: WhereOptions = {}
+
+ if (search) {
+ Object.assign(followingServerWhere, {
+ host: {
+ [ Op.iLike ]: '%' + search + '%'
+ }
+ })
+ }
+
+ if (actorType) {
+ Object.assign(followingWhere, { type: actorType })
+ }
const query = {
distinct: true,
model: ActorModel,
as: 'ActorFollowing',
required: true,
+ where: followingWhere,
include: [
{
model: ServerModel,
required: true,
- where: search ? {
- host: {
- [Op.iLike]: '%' + search + '%'
- }
- } : undefined
+ where: followingServerWhere
}
]
}
count: number,
sort: string,
state?: FollowState,
+ actorType?: ActivityPubActorType,
search?: string
}) {
- const { actorId, start, count, sort, search, state } = options
+ const { actorId, start, count, sort, search, state, actorType } = options
const followWhere = state ? { state } : {}
+ const followerWhere: WhereOptions = {}
+ const followerServerWhere: WhereOptions = {}
+
+ if (search) {
+ Object.assign(followerServerWhere, {
+ host: {
+ [ Op.iLike ]: '%' + search + '%'
+ }
+ })
+ }
+
+ if (actorType) {
+ Object.assign(followerWhere, { type: actorType })
+ }
const query = {
distinct: true,
model: ActorModel,
required: true,
as: 'ActorFollower',
+ where: followerWhere,
include: [
{
model: ServerModel,
required: true,
- where: search ? {
- host: {
- [ Op.iLike ]: '%' + search + '%'
- }
- } : undefined
+ where: followerServerWhere
}
]
},
})
})
+ it('Should fail with an incorrect actor type', async function () {
+ await makeGetRequest({
+ url: server.url,
+ path,
+ query: {
+ actorType: 'blabla'
+ }
+ })
+ })
+
it('Should fail succeed with the correct params', async function () {
await makeGetRequest({
url: server.url,
path,
statusCodeExpected: 200,
query: {
- state: 'accepted'
+ state: 'accepted',
+ actorType: 'Application'
}
})
})
await checkBadSortPagination(server.url, path)
})
+ it('Should fail with an incorrect actor type', async function () {
+ await makeGetRequest({
+ url: server.url,
+ path,
+ query: {
+ actorType: 'blabla'
+ }
+ })
+ })
+
it('Should fail with an incorrect state', async function () {
await makeGetRequest({
url: server.url,
path,
query: {
- state: 'blabla'
+ state: 'blabla',
+ actorType: 'Application'
}
})
})
async function enableRedundancyOnServer1 () {
await updateRedundancy(servers[ 0 ].url, servers[ 0 ].accessToken, servers[ 1 ].host, true)
- const res = await getFollowingListPaginationAndSort(servers[ 0 ].url, 0, 5, '-createdAt')
+ const res = await getFollowingListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 5, sort: '-createdAt' })
const follows: ActorFollow[] = res.body.data
const server2 = follows.find(f => f.following.host === `localhost:${servers[ 1 ].port}`)
const server3 = follows.find(f => f.following.host === `localhost:${servers[ 2 ].port}`)
async function disableRedundancyOnServer1 () {
await updateRedundancy(servers[ 0 ].url, servers[ 0 ].accessToken, servers[ 1 ].host, false)
- const res = await getFollowingListPaginationAndSort(servers[ 0 ].url, 0, 5, '-createdAt')
+ const res = await getFollowingListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 5, sort: '-createdAt' })
const follows: ActorFollow[] = res.body.data
const server2 = follows.find(f => f.following.host === `localhost:${servers[ 1 ].port}`)
const server3 = follows.find(f => f.following.host === `localhost:${servers[ 2 ].port}`)
async function checkFollow (follower: ServerInfo, following: ServerInfo, exists: boolean) {
{
- const res = await getFollowersListPaginationAndSort(following.url, 0, 5, '-createdAt')
+ const res = await getFollowersListPaginationAndSort({ url: following.url, start: 0, count: 5, sort: '-createdAt' })
const follows = res.body.data as ActorFollow[]
const follow = follows.find(f => {
}
{
- const res = await getFollowingListPaginationAndSort(follower.url, 0, 5, '-createdAt')
+ const res = await getFollowingListPaginationAndSort({ url: follower.url, start: 0, count: 5, sort: '-createdAt' })
const follows = res.body.data as ActorFollow[]
const follow = follows.find(f => {
async function checkServer1And2HasFollowers (servers: ServerInfo[], state = 'accepted') {
{
- const res = await getFollowingListPaginationAndSort(servers[0].url, 0, 5, 'createdAt')
+ const res = await getFollowingListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 5, sort: 'createdAt' })
expect(res.body.total).to.equal(1)
const follow = res.body.data[0] as ActorFollow
}
{
- const res = await getFollowersListPaginationAndSort(servers[1].url, 0, 5, 'createdAt')
+ const res = await getFollowersListPaginationAndSort({ url: servers[ 1 ].url, start: 0, count: 5, sort: 'createdAt' })
expect(res.body.total).to.equal(1)
const follow = res.body.data[0] as ActorFollow
async function checkNoFollowers (servers: ServerInfo[]) {
{
- const res = await getFollowingListPaginationAndSort(servers[ 0 ].url, 0, 5, 'createdAt')
+ const res = await getFollowingListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 5, sort: 'createdAt' })
expect(res.body.total).to.equal(0)
}
{
- const res = await getFollowersListPaginationAndSort(servers[ 1 ].url, 0, 5, 'createdAt')
+ const res = await getFollowersListPaginationAndSort({ url: servers[ 1 ].url, start: 0, count: 5, sort: 'createdAt' })
expect(res.body.total).to.equal(0)
}
}
await waitJobs(servers)
{
- const res = await getFollowingListPaginationAndSort(servers[0].url, 0, 5, 'createdAt')
+ const res = await getFollowingListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 5, sort: 'createdAt' })
expect(res.body.total).to.equal(2)
}
{
- const res = await getFollowersListPaginationAndSort(servers[1].url, 0, 5, 'createdAt')
+ const res = await getFollowersListPaginationAndSort({ url: servers[ 1 ].url, start: 0, count: 5, sort: 'createdAt' })
expect(res.body.total).to.equal(1)
}
{
- const res = await getFollowersListPaginationAndSort(servers[2].url, 0, 5, 'createdAt')
+ const res = await getFollowersListPaginationAndSort({ url: servers[ 2 ].url, start: 0, count: 5, sort: 'createdAt' })
expect(res.body.total).to.equal(1)
}
await checkServer1And2HasFollowers(servers)
{
- const res = await getFollowersListPaginationAndSort(servers[ 2 ].url, 0, 5, 'createdAt')
+ const res = await getFollowersListPaginationAndSort({ url: servers[ 2 ].url, start: 0, count: 5, sort: 'createdAt' })
expect(res.body.total).to.equal(0)
}
})
it('Should not have followers', async function () {
for (const server of servers) {
- const res = await getFollowersListPaginationAndSort(server.url, 0, 5, 'createdAt')
+ const res = await getFollowersListPaginationAndSort({ url: server.url, start: 0, count: 5, sort: 'createdAt' })
const follows = res.body.data
expect(res.body.total).to.equal(0)
it('Should not have following', async function () {
for (const server of servers) {
- const res = await getFollowingListPaginationAndSort(server.url, 0, 5, 'createdAt')
+ const res = await getFollowingListPaginationAndSort({ url: server.url, start: 0, count: 5, sort: 'createdAt' })
const follows = res.body.data
expect(res.body.total).to.equal(0)
})
it('Should have 2 followings on server 1', async function () {
- let res = await getFollowingListPaginationAndSort(servers[0].url, 0, 1, 'createdAt')
+ let res = await getFollowingListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 1, sort: 'createdAt' })
let follows = res.body.data
expect(res.body.total).to.equal(2)
expect(follows).to.be.an('array')
expect(follows.length).to.equal(1)
- res = await getFollowingListPaginationAndSort(servers[0].url, 1, 1, 'createdAt')
+ res = await getFollowingListPaginationAndSort({ url: servers[ 0 ].url, start: 1, count: 1, sort: 'createdAt' })
follows = follows.concat(res.body.data)
const server2Follow = follows.find(f => f.following.host === 'localhost:' + servers[1].port)
})
it('Should search/filter followings on server 1', async function () {
+ const sort = 'createdAt'
+ const start = 0
+ const count = 1
+ const url = servers[ 0 ].url
+
{
const search = ':' + servers[1].port
- const res = await getFollowingListPaginationAndSort(servers[ 0 ].url, 0, 1, 'createdAt', search)
- const follows = res.body.data
- expect(res.body.total).to.equal(1)
- expect(follows.length).to.equal(1)
- expect(follows[ 0 ].following.host).to.equal('localhost:' + servers[1].port)
+ {
+ const res = await getFollowingListPaginationAndSort({ url, start, count, sort, search })
+ const follows = res.body.data
- const res2 = await getFollowingListPaginationAndSort(servers[ 0 ].url, 0, 1, 'createdAt', search, 'accepted')
- expect(res2.body.total).to.equal(1)
- expect(res2.body.data).to.have.lengthOf(1)
+ expect(res.body.total).to.equal(1)
+ expect(follows.length).to.equal(1)
+ expect(follows[ 0 ].following.host).to.equal('localhost:' + servers[ 1 ].port)
+ }
+
+ {
+ const res = await getFollowingListPaginationAndSort({ url, start, count, sort, search, state: 'accepted' })
+ expect(res.body.total).to.equal(1)
+ expect(res.body.data).to.have.lengthOf(1)
+ }
- const res3 = await getFollowingListPaginationAndSort(servers[ 0 ].url, 0, 1, 'createdAt', search, 'pending')
- expect(res3.body.total).to.equal(0)
- expect(res3.body.data).to.have.lengthOf(0)
+ {
+ const res = await getFollowingListPaginationAndSort({ url, start, count, sort, search, state: 'accepted', actorType: 'Person' })
+ expect(res.body.total).to.equal(0)
+ expect(res.body.data).to.have.lengthOf(0)
+ }
+
+ {
+ const res = await getFollowingListPaginationAndSort({
+ url,
+ start,
+ count,
+ sort,
+ search,
+ state: 'accepted',
+ actorType: 'Application'
+ })
+ expect(res.body.total).to.equal(1)
+ expect(res.body.data).to.have.lengthOf(1)
+ }
+
+ {
+ const res = await getFollowingListPaginationAndSort({ url, start, count, sort, search, state: 'pending' })
+ expect(res.body.total).to.equal(0)
+ expect(res.body.data).to.have.lengthOf(0)
+ }
}
{
- const res = await getFollowingListPaginationAndSort(servers[ 0 ].url, 0, 1, 'createdAt', 'bla')
+ const res = await getFollowingListPaginationAndSort({ url, start, count, sort, search: 'bla' })
const follows = res.body.data
expect(res.body.total).to.equal(0)
it('Should have 0 followings on server 2 and 3', async function () {
for (const server of [ servers[1], servers[2] ]) {
- const res = await getFollowingListPaginationAndSort(server.url, 0, 5, 'createdAt')
+ const res = await getFollowingListPaginationAndSort({ url: server.url, start: 0, count: 5, sort: 'createdAt' })
const follows = res.body.data
expect(res.body.total).to.equal(0)
it('Should have 1 followers on server 2 and 3', async function () {
for (const server of [ servers[1], servers[2] ]) {
- let res = await getFollowersListPaginationAndSort(server.url, 0, 1, 'createdAt')
+ let res = await getFollowersListPaginationAndSort({ url: server.url, start: 0, count: 1, sort: 'createdAt' })
let follows = res.body.data
expect(res.body.total).to.equal(1)
})
it('Should search/filter followers on server 2', async function () {
+ const url = servers[ 2 ].url
+ const start = 0
+ const count = 5
+ const sort = 'createdAt'
+
{
const search = servers[0].port + ''
- const res = await getFollowersListPaginationAndSort(servers[ 2 ].url, 0, 5, 'createdAt', search)
- const follows = res.body.data
- expect(res.body.total).to.equal(1)
- expect(follows.length).to.equal(1)
- expect(follows[ 0 ].following.host).to.equal('localhost:' + servers[2].port)
+ {
+ const res = await getFollowersListPaginationAndSort({ url, start, count, sort, search })
+ const follows = res.body.data
- const res2 = await getFollowersListPaginationAndSort(servers[ 2 ].url, 0, 5, 'createdAt', search, 'accepted')
- expect(res2.body.total).to.equal(1)
- expect(res2.body.data).to.have.lengthOf(1)
+ expect(res.body.total).to.equal(1)
+ expect(follows.length).to.equal(1)
+ expect(follows[ 0 ].following.host).to.equal('localhost:' + servers[ 2 ].port)
+ }
+
+ {
+ const res = await getFollowersListPaginationAndSort({ url, start, count, sort, search, state: 'accepted' })
+ expect(res.body.total).to.equal(1)
+ expect(res.body.data).to.have.lengthOf(1)
+ }
- const res3 = await getFollowersListPaginationAndSort(servers[ 2 ].url, 0, 5, 'createdAt', search, 'pending')
- expect(res3.body.total).to.equal(0)
- expect(res3.body.data).to.have.lengthOf(0)
+ {
+ const res = await getFollowersListPaginationAndSort({ url, start, count, sort, search, state: 'accepted', actorType: 'Person' })
+ expect(res.body.total).to.equal(0)
+ expect(res.body.data).to.have.lengthOf(0)
+ }
+
+ {
+ const res = await getFollowersListPaginationAndSort({
+ url,
+ start,
+ count,
+ sort,
+ search,
+ state: 'accepted',
+ actorType: 'Application'
+ })
+ expect(res.body.total).to.equal(1)
+ expect(res.body.data).to.have.lengthOf(1)
+ }
+
+ {
+ const res = await getFollowersListPaginationAndSort({ url, start, count, sort, search, state: 'pending' })
+ expect(res.body.total).to.equal(0)
+ expect(res.body.data).to.have.lengthOf(0)
+ }
}
{
- const res = await getFollowersListPaginationAndSort(servers[ 2 ].url, 0, 5, 'createdAt', 'bla')
+ const res = await getFollowersListPaginationAndSort({ url, start, count, sort, search: 'bla' })
const follows = res.body.data
expect(res.body.total).to.equal(0)
})
it('Should have 0 followers on server 1', async function () {
- const res = await getFollowersListPaginationAndSort(servers[0].url, 0, 5, 'createdAt')
+ const res = await getFollowersListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 5, sort: 'createdAt' })
const follows = res.body.data
expect(res.body.total).to.equal(0)
})
it('Should not follow server 3 on server 1 anymore', async function () {
- const res = await getFollowingListPaginationAndSort(servers[0].url, 0, 2, 'createdAt')
+ const res = await getFollowingListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 2, sort: 'createdAt' })
let follows = res.body.data
expect(res.body.total).to.equal(1)
})
it('Should not have server 1 as follower on server 3 anymore', async function () {
- const res = await getFollowersListPaginationAndSort(servers[2].url, 0, 1, 'createdAt')
+ const res = await getFollowersListPaginationAndSort({ url: servers[ 2 ].url, start: 0, count: 1, sort: 'createdAt' })
let follows = res.body.data
expect(res.body.total).to.equal(0)
await wait(11000)
// Only server 3 is still a follower of server 1
- const res = await getFollowersListPaginationAndSort(servers[0].url, 0, 2, 'createdAt')
+ const res = await getFollowersListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 2, sort: 'createdAt' })
expect(res.body.data).to.be.an('array')
expect(res.body.data).to.have.lengthOf(1)
expect(res.body.data[0].follower.host).to.equal('localhost:' + servers[2].port)
await waitJobs(servers)
- const res = await getFollowersListPaginationAndSort(servers[0].url, 0, 2, 'createdAt')
+ const res = await getFollowersListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 2, sort: 'createdAt' })
expect(res.body.data).to.be.an('array')
expect(res.body.data).to.have.lengthOf(2)
})
import { ServerInfo } from './servers'
import { waitJobs } from './jobs'
import { makePostBodyRequest } from '../requests/requests'
-import { FollowState } from '@shared/models'
-
-function getFollowersListPaginationAndSort (url: string, start: number, count: number, sort: string, search?: string, state?: FollowState) {
+import { ActivityPubActorType, FollowState } from '@shared/models'
+
+function getFollowersListPaginationAndSort (options: {
+ url: string,
+ start: number,
+ count: number,
+ sort: string,
+ search?: string,
+ actorType?: ActivityPubActorType,
+ state?: FollowState
+}) {
+ const { url, start, count, sort, search, state, actorType } = options
const path = '/api/v1/server/followers'
const query = {
count,
sort,
search,
- state
+ state,
+ actorType
}
return request(url)
})
}
-function getFollowingListPaginationAndSort (url: string, start: number, count: number, sort: string, search?: string, state?: FollowState) {
+function getFollowingListPaginationAndSort (options: {
+ url: string,
+ start: number,
+ count: number,
+ sort: string,
+ search?: string,
+ actorType?: ActivityPubActorType,
+ state?: FollowState
+}) {
+ const { url, start, count, sort, search, state, actorType } = options
const path = '/api/v1/server/following'
const query = {
count,
sort,
search,
- state
+ state,
+ actorType
}
return request(url)