2023-07-27 14:31:52 +09:00
/ *
2024-02-13 15:59:27 +00:00
* SPDX - FileCopyrightText : syuilo and misskey - project
2023-07-27 14:31:52 +09:00
* SPDX - License - Identifier : AGPL - 3.0 - only
* /
2022-09-18 03:27:08 +09:00
import { Inject , Injectable } from '@nestjs/common' ;
import { DI } from '@/di-symbols.js' ;
2023-09-15 14:28:29 +09:00
import type { FollowingsRepository } from '@/models/_.js' ;
2022-09-18 03:27:08 +09:00
import { awaitAll } from '@/misc/prelude/await-all.js' ;
2023-03-10 14:22:37 +09:00
import type { Packed } from '@/misc/json-schema.js' ;
2024-10-07 18:56:48 -04:00
import { MiBlocking } from '@/models/Blocking.js' ;
import { MiUserProfile } from '@/models/UserProfile.js' ;
import type { MiLocalUser , MiUser } from '@/models/User.js' ;
import { MiFollowing } from '@/models/Following.js' ;
2023-03-10 14:22:37 +09:00
import { bindThis } from '@/decorators.js' ;
2023-10-16 10:45:22 +09:00
import { IdService } from '@/core/IdService.js' ;
2024-10-07 18:56:48 -04:00
import { QueryService } from '@/core/QueryService.js' ;
import { RoleService } from '@/core/RoleService.js' ;
2024-11-02 17:43:11 -04:00
import { UserEntityService } from './UserEntityService.js' ;
2019-04-13 01:43:22 +09:00
2023-08-16 17:51:28 +09:00
type LocalFollowerFollowing = MiFollowing & {
2019-04-13 01:43:22 +09:00
followerHost : null ;
followerInbox : null ;
followerSharedInbox : null ;
} ;
2023-08-16 17:51:28 +09:00
type RemoteFollowerFollowing = MiFollowing & {
2019-04-13 01:43:22 +09:00
followerHost : string ;
followerInbox : string ;
followerSharedInbox : string ;
} ;
2023-08-16 17:51:28 +09:00
type LocalFolloweeFollowing = MiFollowing & {
2019-04-13 01:43:22 +09:00
followeeHost : null ;
followeeInbox : null ;
followeeSharedInbox : null ;
} ;
2023-08-16 17:51:28 +09:00
type RemoteFolloweeFollowing = MiFollowing & {
2019-04-13 01:43:22 +09:00
followeeHost : string ;
followeeInbox : string ;
followeeSharedInbox : string ;
} ;
2019-04-07 21:50:36 +09:00
2022-09-18 03:27:08 +09:00
@Injectable ( )
export class FollowingEntityService {
constructor (
@Inject ( DI . followingsRepository )
private followingsRepository : FollowingsRepository ,
private userEntityService : UserEntityService ,
2023-10-16 10:45:22 +09:00
private idService : IdService ,
2024-10-07 18:56:48 -04:00
private queryService : QueryService ,
private roleService : RoleService ,
2022-09-18 03:27:08 +09:00
) {
}
2022-12-04 15:03:09 +09:00
@bindThis
2023-08-16 17:51:28 +09:00
public isLocalFollower ( following : MiFollowing ) : following is LocalFollowerFollowing {
2019-04-13 01:43:22 +09:00
return following . followerHost == null ;
2022-09-18 03:27:08 +09:00
}
2019-04-13 01:43:22 +09:00
2022-12-04 15:03:09 +09:00
@bindThis
2023-08-16 17:51:28 +09:00
public isRemoteFollower ( following : MiFollowing ) : following is RemoteFollowerFollowing {
2019-04-13 01:43:22 +09:00
return following . followerHost != null ;
2022-09-18 03:27:08 +09:00
}
2019-04-13 01:43:22 +09:00
2022-12-04 15:03:09 +09:00
@bindThis
2023-08-16 17:51:28 +09:00
public isLocalFollowee ( following : MiFollowing ) : following is LocalFolloweeFollowing {
2019-04-13 01:43:22 +09:00
return following . followeeHost == null ;
2022-09-18 03:27:08 +09:00
}
2019-04-13 01:43:22 +09:00
2022-12-04 15:03:09 +09:00
@bindThis
2023-08-16 17:51:28 +09:00
public isRemoteFollowee ( following : MiFollowing ) : following is RemoteFolloweeFollowing {
2019-04-13 01:43:22 +09:00
return following . followeeHost != null ;
2022-09-18 03:27:08 +09:00
}
2019-04-13 01:43:22 +09:00
2024-10-07 18:56:48 -04:00
@bindThis
public async getFollowing ( me : MiLocalUser , params : FollowsQueryParams ) {
return await this . getFollows ( me , params , 'following.followerHost = :host' ) ;
}
@bindThis
public async getFollowers ( me : MiLocalUser , params : FollowsQueryParams ) {
return await this . getFollows ( me , params , 'following.followeeHost = :host' ) ;
}
private async getFollows ( me : MiLocalUser , params : FollowsQueryParams , condition : string ) {
const builder = this . followingsRepository . createQueryBuilder ( 'following' ) ;
const query = this . queryService
. makePaginationQuery ( builder , params . sinceId , params . untilId )
. andWhere ( condition , { host : params.host } )
. limit ( params . limit ) ;
if ( ! await this . roleService . isModerator ( me ) ) {
query . setParameter ( 'me' , me . id ) ;
// Make sure that the followee doesn't block us, if their profile will be included.
if ( params . includeFollowee ) {
query . leftJoin ( MiBlocking , 'followee_blocking' , 'followee_blocking."blockerId" = following."followeeId" AND followee_blocking."blockeeId" = :me' ) ;
query . andWhere ( 'followee_blocking.id IS NULL' ) ;
}
// Make sure that the follower doesn't block us, if their profile will be included.
if ( params . includeFollower ) {
query . leftJoin ( MiBlocking , 'follower_blocking' , 'follower_blocking."blockerId" = following."followerId" AND follower_blocking."blockeeId" = :me' ) ;
query . andWhere ( 'follower_blocking.id IS NULL' ) ;
}
// Make sure that the followee hasn't hidden this connection.
query . leftJoin ( MiUserProfile , 'followee' , 'followee."userId" = following."followeeId"' ) ;
query . leftJoin ( MiFollowing , 'me_following_followee' , 'me_following_followee."followerId" = :me AND me_following_followee."followeeId" = following."followerId"' ) ;
query . andWhere ( '(followee."userId" = :me OR followee."followersVisibility" = \'public\' OR (followee."followersVisibility" = \'followers\' AND me_following_followee.id IS NOT NULL))' ) ;
// Make sure that the follower hasn't hidden this connection.
query . leftJoin ( MiUserProfile , 'follower' , 'follower."userId" = following."followerId"' ) ;
query . leftJoin ( MiFollowing , 'me_following_follower' , 'me_following_follower."followerId" = :me AND me_following_follower."followeeId" = following."followerId"' ) ;
query . andWhere ( '(follower."userId" = :me OR follower."followingVisibility" = \'public\' OR (follower."followingVisibility" = \'followers\' AND me_following_follower.id IS NOT NULL))' ) ;
}
const followings = await query . getMany ( ) ;
return await this . packMany ( followings , me , { populateFollowee : params.includeFollowee , populateFollower : params.includeFollower } ) ;
}
2022-12-04 15:03:09 +09:00
@bindThis
2022-09-18 03:27:08 +09:00
public async pack (
2023-08-16 17:51:28 +09:00
src : MiFollowing [ 'id' ] | MiFollowing ,
me ? : { id : MiUser [ 'id' ] } | null | undefined ,
2019-04-07 21:50:36 +09:00
opts ? : {
populateFollowee? : boolean ;
populateFollower? : boolean ;
2022-09-18 03:27:08 +09:00
} ,
2024-05-31 15:32:42 +09:00
hint ? : {
packedFollowee? : Packed < 'UserDetailedNotMe' > ,
packedFollower? : Packed < 'UserDetailedNotMe' > ,
} ,
2021-09-22 22:35:55 +09:00
) : Promise < Packed < 'Following' > > {
2022-09-18 03:27:08 +09:00
const following = typeof src === 'object' ? src : await this . followingsRepository . findOneByOrFail ( { id : src } ) ;
2019-04-07 21:50:36 +09:00
if ( opts == null ) opts = { } ;
2019-04-23 22:35:26 +09:00
return await awaitAll ( {
2019-04-07 21:50:36 +09:00
id : following.id ,
2023-10-16 10:45:22 +09:00
createdAt : this.idService.parse ( following . id ) . date . toISOString ( ) ,
2019-04-07 21:50:36 +09:00
followeeId : following.followeeId ,
followerId : following.followerId ,
2024-05-31 15:32:42 +09:00
followee : opts.populateFollowee ? hint ? . packedFollowee ? ? this . userEntityService . pack ( following . followee ? ? following . followeeId , me , {
2024-01-31 15:45:35 +09:00
schema : 'UserDetailedNotMe' ,
2019-04-23 22:35:26 +09:00
} ) : undefined ,
2024-05-31 15:32:42 +09:00
follower : opts.populateFollower ? hint ? . packedFollower ? ? this . userEntityService . pack ( following . follower ? ? following . followerId , me , {
2024-01-31 15:45:35 +09:00
schema : 'UserDetailedNotMe' ,
2019-04-23 22:35:26 +09:00
} ) : undefined ,
2019-04-07 21:50:36 +09:00
} ) ;
2022-09-18 03:27:08 +09:00
}
2019-04-25 13:27:07 +09:00
2022-12-04 15:03:09 +09:00
@bindThis
2024-05-31 15:32:42 +09:00
public async packMany (
followings : MiFollowing [ ] ,
2023-08-16 17:51:28 +09:00
me ? : { id : MiUser [ 'id' ] } | null | undefined ,
2019-04-25 13:27:07 +09:00
opts ? : {
populateFollowee? : boolean ;
populateFollower? : boolean ;
2022-09-18 03:27:08 +09:00
} ,
2019-04-25 13:27:07 +09:00
) {
2024-05-31 15:32:42 +09:00
const _followees = opts ? . populateFollowee ? followings . map ( ( { followee , followeeId } ) = > followee ? ? followeeId ) : [ ] ;
const _followers = opts ? . populateFollower ? followings . map ( ( { follower , followerId } ) = > follower ? ? followerId ) : [ ] ;
const _userMap = await this . userEntityService . packMany ( [ . . . _followees , . . . _followers ] , me , { schema : 'UserDetailedNotMe' } )
. then ( users = > new Map ( users . map ( u = > [ u . id , u ] ) ) ) ;
return Promise . all (
followings . map ( following = > {
const packedFollowee = opts ? . populateFollowee ? _userMap . get ( following . followeeId ) : undefined ;
const packedFollower = opts ? . populateFollower ? _userMap . get ( following . followerId ) : undefined ;
return this . pack ( following , me , opts , { packedFollowee , packedFollower } ) ;
} ) ,
) ;
2022-09-18 03:27:08 +09:00
}
}
2024-10-07 18:56:48 -04:00
interface FollowsQueryParams {
readonly host : string ;
readonly limit : number ;
readonly includeFollower : boolean ;
readonly includeFollowee : boolean ;
readonly sinceId? : string ;
readonly untilId? : string ;
}