summaryrefslogtreecommitdiff
path: root/packages/backend/src/server/api/endpoints/users
diff options
context:
space:
mode:
Diffstat (limited to 'packages/backend/src/server/api/endpoints/users')
-rw-r--r--packages/backend/src/server/api/endpoints/users/clips.ts38
-rw-r--r--packages/backend/src/server/api/endpoints/users/followers.ts93
-rw-r--r--packages/backend/src/server/api/endpoints/users/following.ts93
-rw-r--r--packages/backend/src/server/api/endpoints/users/gallery/posts.ts36
-rw-r--r--packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts127
-rw-r--r--packages/backend/src/server/api/endpoints/users/groups/create.ts59
-rw-r--r--packages/backend/src/server/api/endpoints/users/groups/delete.ts34
-rw-r--r--packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts63
-rw-r--r--packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts40
-rw-r--r--packages/backend/src/server/api/endpoints/users/groups/invite.ts112
-rw-r--r--packages/backend/src/server/api/endpoints/users/groups/joined.ts44
-rw-r--r--packages/backend/src/server/api/endpoints/users/groups/leave.ts43
-rw-r--r--packages/backend/src/server/api/endpoints/users/groups/owned.ts29
-rw-r--r--packages/backend/src/server/api/endpoints/users/groups/pull.ts61
-rw-r--r--packages/backend/src/server/api/endpoints/users/groups/show.ts54
-rw-r--r--packages/backend/src/server/api/endpoints/users/groups/transfer.ts75
-rw-r--r--packages/backend/src/server/api/endpoints/users/groups/update.ts45
-rw-r--r--packages/backend/src/server/api/endpoints/users/lists/create.ts40
-rw-r--r--packages/backend/src/server/api/endpoints/users/lists/delete.ts34
-rw-r--r--packages/backend/src/server/api/endpoints/users/lists/list.ts29
-rw-r--r--packages/backend/src/server/api/endpoints/users/lists/pull.ts62
-rw-r--r--packages/backend/src/server/api/endpoints/users/lists/push.ts95
-rw-r--r--packages/backend/src/server/api/endpoints/users/lists/show.ts39
-rw-r--r--packages/backend/src/server/api/endpoints/users/lists/update.ts45
-rw-r--r--packages/backend/src/server/api/endpoints/users/notes.ts138
-rw-r--r--packages/backend/src/server/api/endpoints/users/pages.ts38
-rw-r--r--packages/backend/src/server/api/endpoints/users/reactions.ts54
-rw-r--r--packages/backend/src/server/api/endpoints/users/recommendation.ts64
-rw-r--r--packages/backend/src/server/api/endpoints/users/relation.ts27
-rw-r--r--packages/backend/src/server/api/endpoints/users/report-abuse.ts125
-rw-r--r--packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts140
-rw-r--r--packages/backend/src/server/api/endpoints/users/search.ts166
-rw-r--r--packages/backend/src/server/api/endpoints/users/show.ts108
-rw-r--r--packages/backend/src/server/api/endpoints/users/stats.ts183
34 files changed, 1483 insertions, 950 deletions
diff --git a/packages/backend/src/server/api/endpoints/users/clips.ts b/packages/backend/src/server/api/endpoints/users/clips.ts
index 09fdf27c23..2d5545cbab 100644
--- a/packages/backend/src/server/api/endpoints/users/clips.ts
+++ b/packages/backend/src/server/api/endpoints/users/clips.ts
@@ -1,6 +1,9 @@
-import { Clips } from '@/models/index.js';
-import define from '../../define.js';
-import { makePaginationQuery } from '../../common/make-pagination-query.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { ClipsRepository } from '@/models/index.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { QueryService } from '@/core/QueryService.js';
+import { ClipEntityService } from '@/core/entities/ClipEntityService.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
tags: ['users', 'clips'],
@@ -30,14 +33,25 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const query = makePaginationQuery(Clips.createQueryBuilder('clip'), ps.sinceId, ps.untilId)
- .andWhere('clip.userId = :userId', { userId: ps.userId })
- .andWhere('clip.isPublic = true');
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.clipsRepository)
+ private clipsRepository: ClipsRepository,
- const clips = await query
- .take(ps.limit)
- .getMany();
+ private clipEntityService: ClipEntityService,
+ private queryService: QueryService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const query = this.queryService.makePaginationQuery(this.clipsRepository.createQueryBuilder('clip'), ps.sinceId, ps.untilId)
+ .andWhere('clip.userId = :userId', { userId: ps.userId })
+ .andWhere('clip.isPublic = true');
- return await Clips.packMany(clips);
-});
+ const clips = await query
+ .take(ps.limit)
+ .getMany();
+
+ return await this.clipEntityService.packMany(clips);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/users/followers.ts b/packages/backend/src/server/api/endpoints/users/followers.ts
index 7f9f980764..08bcdd9f88 100644
--- a/packages/backend/src/server/api/endpoints/users/followers.ts
+++ b/packages/backend/src/server/api/endpoints/users/followers.ts
@@ -1,9 +1,12 @@
import { IsNull } from 'typeorm';
-import { Users, Followings, UserProfiles } from '@/models/index.js';
-import { toPunyNullable } from '@/misc/convert-host.js';
-import define from '../../define.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { UsersRepository, FollowingsRepository, UserProfilesRepository } from '@/models/index.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { QueryService } from '@/core/QueryService.js';
+import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js';
+import { UtilityService } from '@/core/UtilityService.js';
+import { DI } from '@/di-symbols.js';
import { ApiError } from '../../error.js';
-import { makePaginationQuery } from '../../common/make-pagination-query.js';
export const meta = {
tags: ['users'],
@@ -66,42 +69,60 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, me) => {
- const user = await Users.findOneBy(ps.userId != null
- ? { id: ps.userId }
- : { usernameLower: ps.username!.toLowerCase(), host: toPunyNullable(ps.host) ?? IsNull() });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- if (user == null) {
- throw new ApiError(meta.errors.noSuchUser);
- }
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
+
+ @Inject(DI.followingsRepository)
+ private followingsRepository: FollowingsRepository,
- const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
+ private utilityService: UtilityService,
+ private followingEntityService: FollowingEntityService,
+ private queryService: QueryService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const user = await this.usersRepository.findOneBy(ps.userId != null
+ ? { id: ps.userId }
+ : { usernameLower: ps.username!.toLowerCase(), host: this.utilityService.toPunyNullable(ps.host) ?? IsNull() });
- if (profile.ffVisibility === 'private') {
- if (me == null || (me.id !== user.id)) {
- throw new ApiError(meta.errors.forbidden);
- }
- } else if (profile.ffVisibility === 'followers') {
- if (me == null) {
- throw new ApiError(meta.errors.forbidden);
- } else if (me.id !== user.id) {
- const following = await Followings.findOneBy({
- followeeId: user.id,
- followerId: me.id,
- });
- if (following == null) {
- throw new ApiError(meta.errors.forbidden);
+ if (user == null) {
+ throw new ApiError(meta.errors.noSuchUser);
}
- }
- }
- const query = makePaginationQuery(Followings.createQueryBuilder('following'), ps.sinceId, ps.untilId)
- .andWhere('following.followeeId = :userId', { userId: user.id })
- .innerJoinAndSelect('following.follower', 'follower');
+ const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
- const followings = await query
- .take(ps.limit)
- .getMany();
+ if (profile.ffVisibility === 'private') {
+ if (me == null || (me.id !== user.id)) {
+ throw new ApiError(meta.errors.forbidden);
+ }
+ } else if (profile.ffVisibility === 'followers') {
+ if (me == null) {
+ throw new ApiError(meta.errors.forbidden);
+ } else if (me.id !== user.id) {
+ const following = await this.followingsRepository.findOneBy({
+ followeeId: user.id,
+ followerId: me.id,
+ });
+ if (following == null) {
+ throw new ApiError(meta.errors.forbidden);
+ }
+ }
+ }
- return await Followings.packMany(followings, me, { populateFollower: true });
-});
+ const query = this.queryService.makePaginationQuery(this.followingsRepository.createQueryBuilder('following'), ps.sinceId, ps.untilId)
+ .andWhere('following.followeeId = :userId', { userId: user.id })
+ .innerJoinAndSelect('following.follower', 'follower');
+
+ const followings = await query
+ .take(ps.limit)
+ .getMany();
+
+ return await this.followingEntityService.packMany(followings, me, { populateFollower: true });
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/users/following.ts b/packages/backend/src/server/api/endpoints/users/following.ts
index 0aaa810f76..225ab5210a 100644
--- a/packages/backend/src/server/api/endpoints/users/following.ts
+++ b/packages/backend/src/server/api/endpoints/users/following.ts
@@ -1,9 +1,12 @@
import { IsNull } from 'typeorm';
-import { Users, Followings, UserProfiles } from '@/models/index.js';
-import { toPunyNullable } from '@/misc/convert-host.js';
-import define from '../../define.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { UsersRepository, FollowingsRepository, UserProfilesRepository } from '@/models/index.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { QueryService } from '@/core/QueryService.js';
+import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js';
+import { UtilityService } from '@/core/UtilityService.js';
+import { DI } from '@/di-symbols.js';
import { ApiError } from '../../error.js';
-import { makePaginationQuery } from '../../common/make-pagination-query.js';
export const meta = {
tags: ['users'],
@@ -66,42 +69,60 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, me) => {
- const user = await Users.findOneBy(ps.userId != null
- ? { id: ps.userId }
- : { usernameLower: ps.username!.toLowerCase(), host: toPunyNullable(ps.host) ?? IsNull() });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- if (user == null) {
- throw new ApiError(meta.errors.noSuchUser);
- }
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
+
+ @Inject(DI.followingsRepository)
+ private followingsRepository: FollowingsRepository,
- const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
+ private utilityService: UtilityService,
+ private followingEntityService: FollowingEntityService,
+ private queryService: QueryService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const user = await this.usersRepository.findOneBy(ps.userId != null
+ ? { id: ps.userId }
+ : { usernameLower: ps.username!.toLowerCase(), host: this.utilityService.toPunyNullable(ps.host) ?? IsNull() });
- if (profile.ffVisibility === 'private') {
- if (me == null || (me.id !== user.id)) {
- throw new ApiError(meta.errors.forbidden);
- }
- } else if (profile.ffVisibility === 'followers') {
- if (me == null) {
- throw new ApiError(meta.errors.forbidden);
- } else if (me.id !== user.id) {
- const following = await Followings.findOneBy({
- followeeId: user.id,
- followerId: me.id,
- });
- if (following == null) {
- throw new ApiError(meta.errors.forbidden);
+ if (user == null) {
+ throw new ApiError(meta.errors.noSuchUser);
}
- }
- }
- const query = makePaginationQuery(Followings.createQueryBuilder('following'), ps.sinceId, ps.untilId)
- .andWhere('following.followerId = :userId', { userId: user.id })
- .innerJoinAndSelect('following.followee', 'followee');
+ const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
- const followings = await query
- .take(ps.limit)
- .getMany();
+ if (profile.ffVisibility === 'private') {
+ if (me == null || (me.id !== user.id)) {
+ throw new ApiError(meta.errors.forbidden);
+ }
+ } else if (profile.ffVisibility === 'followers') {
+ if (me == null) {
+ throw new ApiError(meta.errors.forbidden);
+ } else if (me.id !== user.id) {
+ const following = await this.followingsRepository.findOneBy({
+ followeeId: user.id,
+ followerId: me.id,
+ });
+ if (following == null) {
+ throw new ApiError(meta.errors.forbidden);
+ }
+ }
+ }
- return await Followings.packMany(followings, me, { populateFollowee: true });
-});
+ const query = this.queryService.makePaginationQuery(this.followingsRepository.createQueryBuilder('following'), ps.sinceId, ps.untilId)
+ .andWhere('following.followerId = :userId', { userId: user.id })
+ .innerJoinAndSelect('following.followee', 'followee');
+
+ const followings = await query
+ .take(ps.limit)
+ .getMany();
+
+ return await this.followingEntityService.packMany(followings, me, { populateFollowee: true });
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/users/gallery/posts.ts b/packages/backend/src/server/api/endpoints/users/gallery/posts.ts
index 35bf2df598..2d28d6ca07 100644
--- a/packages/backend/src/server/api/endpoints/users/gallery/posts.ts
+++ b/packages/backend/src/server/api/endpoints/users/gallery/posts.ts
@@ -1,6 +1,9 @@
-import define from '../../../define.js';
-import { GalleryPosts } from '@/models/index.js';
-import { makePaginationQuery } from '../../../common/make-pagination-query.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { GalleryPostsRepository } from '@/models/index.js';
+import { QueryService } from '@/core/QueryService.js';
+import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
tags: ['users', 'gallery'],
@@ -30,13 +33,24 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const query = makePaginationQuery(GalleryPosts.createQueryBuilder('post'), ps.sinceId, ps.untilId)
- .andWhere(`post.userId = :userId`, { userId: ps.userId });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.galleryPostsRepository)
+ private galleryPostsRepository: GalleryPostsRepository,
- const posts = await query
- .take(ps.limit)
- .getMany();
+ private galleryPostEntityService: GalleryPostEntityService,
+ private queryService: QueryService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const query = this.queryService.makePaginationQuery(this.galleryPostsRepository.createQueryBuilder('post'), ps.sinceId, ps.untilId)
+ .andWhere('post.userId = :userId', { userId: ps.userId });
- return await GalleryPosts.packMany(posts, user);
-});
+ const posts = await query
+ .take(ps.limit)
+ .getMany();
+
+ return await this.galleryPostEntityService.packMany(posts, me);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts
index 56965d3066..3eeca7562f 100644
--- a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts
+++ b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts
@@ -1,9 +1,12 @@
import { Not, In, IsNull } from 'typeorm';
-import { maximum } from '@/prelude/array.js';
-import { Notes, Users } from '@/models/index.js';
-import define from '../../define.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { maximum } from '@/misc/prelude/array.js';
+import { NotesRepository, UsersRepository } from '@/models/index.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { DI } from '@/di-symbols.js';
import { ApiError } from '../../error.js';
-import { getUser } from '../../common/getters.js';
+import { GetterService } from '../../common/GetterService.js';
export const meta = {
tags: ['users'],
@@ -51,64 +54,78 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, me) => {
- // Lookup user
- const user = await getUser(ps.userId).catch(e => {
- if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
- throw e;
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- // Fetch recent notes
- const recentNotes = await Notes.find({
- where: {
- userId: user.id,
- replyId: Not(IsNull()),
- },
- order: {
- id: -1,
- },
- take: 1000,
- select: ['replyId'],
- });
+ @Inject(DI.notesRepository)
+ private notesRepository: NotesRepository,
- // 投稿が少なかったら中断
- if (recentNotes.length === 0) {
- return [];
- }
+ private userEntityService: UserEntityService,
+ private getterService: GetterService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ // Lookup user
+ const user = await this.getterService.getUser(ps.userId).catch(err => {
+ if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+ throw err;
+ });
- // TODO ミュートを考慮
- const replyTargetNotes = await Notes.find({
- where: {
- id: In(recentNotes.map(p => p.replyId)),
- },
- select: ['userId'],
- });
+ // Fetch recent notes
+ const recentNotes = await this.notesRepository.find({
+ where: {
+ userId: user.id,
+ replyId: Not(IsNull()),
+ },
+ order: {
+ id: -1,
+ },
+ take: 1000,
+ select: ['replyId'],
+ });
- const repliedUsers: any = {};
+ // 投稿が少なかったら中断
+ if (recentNotes.length === 0) {
+ return [];
+ }
- // Extract replies from recent notes
- for (const userId of replyTargetNotes.map(x => x.userId.toString())) {
- if (repliedUsers[userId]) {
- repliedUsers[userId]++;
- } else {
- repliedUsers[userId] = 1;
- }
- }
+ // TODO ミュートを考慮
+ const replyTargetNotes = await this.notesRepository.find({
+ where: {
+ id: In(recentNotes.map(p => p.replyId)),
+ },
+ select: ['userId'],
+ });
+
+ const repliedUsers: any = {};
- // Calc peak
- const peak = maximum(Object.values(repliedUsers));
+ // Extract replies from recent notes
+ for (const userId of replyTargetNotes.map(x => x.userId.toString())) {
+ if (repliedUsers[userId]) {
+ repliedUsers[userId]++;
+ } else {
+ repliedUsers[userId] = 1;
+ }
+ }
- // Sort replies by frequency
- const repliedUsersSorted = Object.keys(repliedUsers).sort((a, b) => repliedUsers[b] - repliedUsers[a]);
+ // Calc peak
+ const peak = maximum(Object.values(repliedUsers));
- // Extract top replied users
- const topRepliedUsers = repliedUsersSorted.slice(0, ps.limit);
+ // Sort replies by frequency
+ const repliedUsersSorted = Object.keys(repliedUsers).sort((a, b) => repliedUsers[b] - repliedUsers[a]);
- // Make replies object (includes weights)
- const repliesObj = await Promise.all(topRepliedUsers.map(async (user) => ({
- user: await Users.pack(user, me, { detail: true }),
- weight: repliedUsers[user] / peak,
- })));
+ // Extract top replied users
+ const topRepliedUsers = repliedUsersSorted.slice(0, ps.limit);
- return repliesObj;
-});
+ // Make replies object (includes weights)
+ const repliesObj = await Promise.all(topRepliedUsers.map(async (user) => ({
+ user: await this.userEntityService.pack(user, me, { detail: true }),
+ weight: repliedUsers[user] / peak,
+ })));
+
+ return repliesObj;
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/users/groups/create.ts b/packages/backend/src/server/api/endpoints/users/groups/create.ts
index 4a6362a3c6..5d7ad84ae0 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/create.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/create.ts
@@ -1,8 +1,11 @@
-import { UserGroups, UserGroupJoinings } from '@/models/index.js';
-import { genId } from '@/misc/gen-id.js';
-import { UserGroup } from '@/models/entities/user-group.js';
-import { UserGroupJoining } from '@/models/entities/user-group-joining.js';
-import define from '../../../define.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js';
+import { IdService } from '@/core/IdService.js';
+import type { UserGroup } from '@/models/entities/UserGroup.js';
+import type { UserGroupJoining } from '@/models/entities/UserGroupJoining.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { UserGroupEntityService } from '@/core/entities/UserGroupEntityService.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
tags: ['groups'],
@@ -29,21 +32,35 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const userGroup = await UserGroups.insert({
- id: genId(),
- createdAt: new Date(),
- userId: user.id,
- name: ps.name,
- } as UserGroup).then(x => UserGroups.findOneByOrFail(x.identifiers[0]));
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userGroupsRepository)
+ private userGroupsRepository: UserGroupsRepository,
- // Push the owner
- await UserGroupJoinings.insert({
- id: genId(),
- createdAt: new Date(),
- userId: user.id,
- userGroupId: userGroup.id,
- } as UserGroupJoining);
+ @Inject(DI.userGroupJoiningsRepository)
+ private userGroupJoiningsRepository: UserGroupJoiningsRepository,
- return await UserGroups.pack(userGroup);
-});
+ private userGroupEntityService: UserGroupEntityService,
+ private idService: IdService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const userGroup = await this.userGroupsRepository.insert({
+ id: this.idService.genId(),
+ createdAt: new Date(),
+ userId: me.id,
+ name: ps.name,
+ } as UserGroup).then(x => this.userGroupsRepository.findOneByOrFail(x.identifiers[0]));
+
+ // Push the owner
+ await this.userGroupJoiningsRepository.insert({
+ id: this.idService.genId(),
+ createdAt: new Date(),
+ userId: me.id,
+ userGroupId: userGroup.id,
+ } as UserGroupJoining);
+
+ return await this.userGroupEntityService.pack(userGroup);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/users/groups/delete.ts b/packages/backend/src/server/api/endpoints/users/groups/delete.ts
index 2ff1f9aec1..50156b049e 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/delete.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/delete.ts
@@ -1,5 +1,7 @@
-import { UserGroups } from '@/models/index.js';
-import define from '../../../define.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { UserGroupsRepository } from '@/models/index.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { DI } from '@/di-symbols.js';
import { ApiError } from '../../../error.js';
export const meta = {
@@ -29,15 +31,23 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const userGroup = await UserGroups.findOneBy({
- id: ps.groupId,
- userId: user.id,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userGroupsRepository)
+ private userGroupsRepository: UserGroupsRepository,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const userGroup = await this.userGroupsRepository.findOneBy({
+ id: ps.groupId,
+ userId: me.id,
+ });
- if (userGroup == null) {
- throw new ApiError(meta.errors.noSuchGroup);
- }
+ if (userGroup == null) {
+ throw new ApiError(meta.errors.noSuchGroup);
+ }
- await UserGroups.delete(userGroup.id);
-});
+ await this.userGroupsRepository.delete(userGroup.id);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts b/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts
index 220fff5f3e..0490fd41a0 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts
@@ -1,8 +1,10 @@
-import { UserGroupJoinings, UserGroupInvitations } from '@/models/index.js';
-import { genId } from '@/misc/gen-id.js';
-import { UserGroupJoining } from '@/models/entities/user-group-joining.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { UserGroupInvitationsRepository, UserGroupJoiningsRepository } from '@/models/index.js';
+import { IdService } from '@/core/IdService.js';
+import type { UserGroupJoining } from '@/models/entities/UserGroupJoining.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { DI } from '@/di-symbols.js';
import { ApiError } from '../../../../error.js';
-import define from '../../../../define.js';
export const meta = {
tags: ['groups', 'users'],
@@ -31,27 +33,40 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- // Fetch the invitation
- const invitation = await UserGroupInvitations.findOneBy({
- id: ps.invitationId,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userGroupInvitationsRepository)
+ private userGroupInvitationsRepository: UserGroupInvitationsRepository,
- if (invitation == null) {
- throw new ApiError(meta.errors.noSuchInvitation);
- }
+ @Inject(DI.userGroupJoiningsRepository)
+ private userGroupJoiningsRepository: UserGroupJoiningsRepository,
- if (invitation.userId !== user.id) {
- throw new ApiError(meta.errors.noSuchInvitation);
- }
+ private idService: IdService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ // Fetch the invitation
+ const invitation = await this.userGroupInvitationsRepository.findOneBy({
+ id: ps.invitationId,
+ });
+
+ if (invitation == null) {
+ throw new ApiError(meta.errors.noSuchInvitation);
+ }
- // Push the user
- await UserGroupJoinings.insert({
- id: genId(),
- createdAt: new Date(),
- userId: user.id,
- userGroupId: invitation.userGroupId,
- } as UserGroupJoining);
+ if (invitation.userId !== me.id) {
+ throw new ApiError(meta.errors.noSuchInvitation);
+ }
- UserGroupInvitations.delete(invitation.id);
-});
+ // Push the user
+ await this.userGroupJoiningsRepository.insert({
+ id: this.idService.genId(),
+ createdAt: new Date(),
+ userId: me.id,
+ userGroupId: invitation.userGroupId,
+ } as UserGroupJoining);
+
+ this.userGroupInvitationsRepository.delete(invitation.id);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts b/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts
index 8d1d3db734..26efc1ecf3 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts
@@ -1,5 +1,7 @@
-import { UserGroupInvitations } from '@/models/index.js';
-import define from '../../../../define.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { UserGroupInvitationsRepository } from '@/models/index.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { DI } from '@/di-symbols.js';
import { ApiError } from '../../../../error.js';
export const meta = {
@@ -29,19 +31,27 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- // Fetch the invitation
- const invitation = await UserGroupInvitations.findOneBy({
- id: ps.invitationId,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userGroupInvitationsRepository)
+ private userGroupInvitationsRepository: UserGroupInvitationsRepository,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ // Fetch the invitation
+ const invitation = await this.userGroupInvitationsRepository.findOneBy({
+ id: ps.invitationId,
+ });
- if (invitation == null) {
- throw new ApiError(meta.errors.noSuchInvitation);
- }
+ if (invitation == null) {
+ throw new ApiError(meta.errors.noSuchInvitation);
+ }
- if (invitation.userId !== user.id) {
- throw new ApiError(meta.errors.noSuchInvitation);
- }
+ if (invitation.userId !== me.id) {
+ throw new ApiError(meta.errors.noSuchInvitation);
+ }
- await UserGroupInvitations.delete(invitation.id);
-});
+ await this.userGroupInvitationsRepository.delete(invitation.id);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/users/groups/invite.ts b/packages/backend/src/server/api/endpoints/users/groups/invite.ts
index 1a8d320f3a..4ae32a6bda 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/invite.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/invite.ts
@@ -1,10 +1,12 @@
-import { UserGroups, UserGroupJoinings, UserGroupInvitations } from '@/models/index.js';
-import { genId } from '@/misc/gen-id.js';
-import { UserGroupInvitation } from '@/models/entities/user-group-invitation.js';
-import { createNotification } from '@/services/create-notification.js';
-import { getUser } from '../../../common/getters.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { UserGroupsRepository, UserGroupJoiningsRepository, UserGroupInvitationsRepository } from '@/models/index.js';
+import { IdService } from '@/core/IdService.js';
+import type { UserGroupInvitation } from '@/models/entities/UserGroupInvitation.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { GetterService } from '@/server/api/common/GetterService.js';
+import { CreateNotificationService } from '@/core/CreateNotificationService.js';
+import { DI } from '@/di-symbols.js';
import { ApiError } from '../../../error.js';
-import define from '../../../define.js';
export const meta = {
tags: ['groups', 'users'],
@@ -52,51 +54,69 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, me) => {
- // Fetch the group
- const userGroup = await UserGroups.findOneBy({
- id: ps.groupId,
- userId: me.id,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userGroupsRepository)
+ private userGroupsRepository: UserGroupsRepository,
- if (userGroup == null) {
- throw new ApiError(meta.errors.noSuchGroup);
- }
+ @Inject(DI.userGroupInvitationsRepository)
+ private userGroupInvitationsRepository: UserGroupInvitationsRepository,
- // Fetch the user
- const user = await getUser(ps.userId).catch(e => {
- if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
- throw e;
- });
+ @Inject(DI.userGroupJoiningsRepository)
+ private userGroupJoiningsRepository: UserGroupJoiningsRepository,
- const joining = await UserGroupJoinings.findOneBy({
- userGroupId: userGroup.id,
- userId: user.id,
- });
+ private idService: IdService,
+ private getterService: GetterService,
+ private createNotificationService: CreateNotificationService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ // Fetch the group
+ const userGroup = await this.userGroupsRepository.findOneBy({
+ id: ps.groupId,
+ userId: me.id,
+ });
- if (joining) {
- throw new ApiError(meta.errors.alreadyAdded);
- }
+ if (userGroup == null) {
+ throw new ApiError(meta.errors.noSuchGroup);
+ }
- const existInvitation = await UserGroupInvitations.findOneBy({
- userGroupId: userGroup.id,
- userId: user.id,
- });
+ // Fetch the user
+ const user = await this.getterService.getUser(ps.userId).catch(err => {
+ if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+ throw err;
+ });
- if (existInvitation) {
- throw new ApiError(meta.errors.alreadyInvited);
- }
+ const joining = await this.userGroupJoiningsRepository.findOneBy({
+ userGroupId: userGroup.id,
+ userId: user.id,
+ });
+
+ if (joining) {
+ throw new ApiError(meta.errors.alreadyAdded);
+ }
- const invitation = await UserGroupInvitations.insert({
- id: genId(),
- createdAt: new Date(),
- userId: user.id,
- userGroupId: userGroup.id,
- } as UserGroupInvitation).then(x => UserGroupInvitations.findOneByOrFail(x.identifiers[0]));
+ const existInvitation = await this.userGroupInvitationsRepository.findOneBy({
+ userGroupId: userGroup.id,
+ userId: user.id,
+ });
- // 通知を作成
- createNotification(user.id, 'groupInvited', {
- notifierId: me.id,
- userGroupInvitationId: invitation.id,
- });
-});
+ if (existInvitation) {
+ throw new ApiError(meta.errors.alreadyInvited);
+ }
+
+ const invitation = await this.userGroupInvitationsRepository.insert({
+ id: this.idService.genId(),
+ createdAt: new Date(),
+ userId: user.id,
+ userGroupId: userGroup.id,
+ } as UserGroupInvitation).then(x => this.userGroupInvitationsRepository.findOneByOrFail(x.identifiers[0]));
+
+ // 通知を作成
+ this.createNotificationService.createNotification(user.id, 'groupInvited', {
+ notifierId: me.id,
+ userGroupInvitationId: invitation.id,
+ });
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/users/groups/joined.ts b/packages/backend/src/server/api/endpoints/users/groups/joined.ts
index 16c6e544e5..e7e69f257d 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/joined.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/joined.ts
@@ -1,6 +1,9 @@
import { Not, In } from 'typeorm';
-import { UserGroups, UserGroupJoinings } from '@/models/index.js';
-import define from '../../../define.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { UserGroupEntityService } from '@/core/entities/UserGroupEntityService.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
tags: ['groups', 'account'],
@@ -29,17 +32,30 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, me) => {
- const ownedGroups = await UserGroups.findBy({
- userId: me.id,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userGroupsRepository)
+ private userGroupsRepository: UserGroupsRepository,
- const joinings = await UserGroupJoinings.findBy({
- userId: me.id,
- ...(ownedGroups.length > 0 ? {
- userGroupId: Not(In(ownedGroups.map(x => x.id))),
- } : {}),
- });
+ @Inject(DI.userGroupJoiningsRepository)
+ private userGroupJoiningsRepository: UserGroupJoiningsRepository,
- return await Promise.all(joinings.map(x => UserGroups.pack(x.userGroupId)));
-});
+ private userGroupEntityService: UserGroupEntityService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const ownedGroups = await this.userGroupsRepository.findBy({
+ userId: me.id,
+ });
+
+ const joinings = await this.userGroupJoiningsRepository.findBy({
+ userId: me.id,
+ ...(ownedGroups.length > 0 ? {
+ userGroupId: Not(In(ownedGroups.map(x => x.id))),
+ } : {}),
+ });
+
+ return await Promise.all(joinings.map(x => this.userGroupEntityService.pack(x.userGroupId)));
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/users/groups/leave.ts b/packages/backend/src/server/api/endpoints/users/groups/leave.ts
index 83dc757db1..0a63dbb7f1 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/leave.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/leave.ts
@@ -1,5 +1,7 @@
-import { UserGroups, UserGroupJoinings } from '@/models/index.js';
-import define from '../../../define.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { DI } from '@/di-symbols.js';
import { ApiError } from '../../../error.js';
export const meta = {
@@ -35,19 +37,30 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, me) => {
- // Fetch the group
- const userGroup = await UserGroups.findOneBy({
- id: ps.groupId,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userGroupsRepository)
+ private userGroupsRepository: UserGroupsRepository,
- if (userGroup == null) {
- throw new ApiError(meta.errors.noSuchGroup);
- }
+ @Inject(DI.userGroupJoiningsRepository)
+ private userGroupJoiningsRepository: UserGroupJoiningsRepository,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ // Fetch the group
+ const userGroup = await this.userGroupsRepository.findOneBy({
+ id: ps.groupId,
+ });
- if (me.id === userGroup.userId) {
- throw new ApiError(meta.errors.youAreOwner);
- }
+ if (userGroup == null) {
+ throw new ApiError(meta.errors.noSuchGroup);
+ }
- await UserGroupJoinings.delete({ userGroupId: userGroup.id, userId: me.id });
-});
+ if (me.id === userGroup.userId) {
+ throw new ApiError(meta.errors.youAreOwner);
+ }
+
+ await this.userGroupJoiningsRepository.delete({ userGroupId: userGroup.id, userId: me.id });
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/users/groups/owned.ts b/packages/backend/src/server/api/endpoints/users/groups/owned.ts
index d77cf1a52e..c9ae39561f 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/owned.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/owned.ts
@@ -1,5 +1,8 @@
-import { UserGroups } from '@/models/index.js';
-import define from '../../../define.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { UserGroupsRepository } from '@/models/index.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { UserGroupEntityService } from '@/core/entities/UserGroupEntityService.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
tags: ['groups', 'account'],
@@ -28,10 +31,20 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, me) => {
- const userGroups = await UserGroups.findBy({
- userId: me.id,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userGroupsRepository)
+ private userGroupsRepository: UserGroupsRepository,
- return await Promise.all(userGroups.map(x => UserGroups.pack(x)));
-});
+ private userGroupEntityService: UserGroupEntityService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const userGroups = await this.userGroupsRepository.findBy({
+ userId: me.id,
+ });
+
+ return await Promise.all(userGroups.map(x => this.userGroupEntityService.pack(x)));
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/users/groups/pull.ts b/packages/backend/src/server/api/endpoints/users/groups/pull.ts
index ba67a1e5c9..e6f60eef0a 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/pull.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/pull.ts
@@ -1,7 +1,9 @@
-import { UserGroups, UserGroupJoinings } from '@/models/index.js';
-import define from '../../../define.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { GetterService } from '@/server/api/common/GetterService.js';
+import { DI } from '@/di-symbols.js';
import { ApiError } from '../../../error.js';
-import { getUser } from '../../../common/getters.js';
export const meta = {
tags: ['groups', 'users'],
@@ -43,27 +45,40 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, me) => {
- // Fetch the group
- const userGroup = await UserGroups.findOneBy({
- id: ps.groupId,
- userId: me.id,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userGroupsRepository)
+ private userGroupsRepository: UserGroupsRepository,
- if (userGroup == null) {
- throw new ApiError(meta.errors.noSuchGroup);
- }
+ @Inject(DI.userGroupJoiningsRepository)
+ private userGroupJoiningsRepository: UserGroupJoiningsRepository,
- // Fetch the user
- const user = await getUser(ps.userId).catch(e => {
- if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
- throw e;
- });
+ private getterService: GetterService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ // Fetch the group
+ const userGroup = await this.userGroupsRepository.findOneBy({
+ id: ps.groupId,
+ userId: me.id,
+ });
- if (user.id === userGroup.userId) {
- throw new ApiError(meta.errors.isOwner);
- }
+ if (userGroup == null) {
+ throw new ApiError(meta.errors.noSuchGroup);
+ }
+
+ // Fetch the user
+ const user = await this.getterService.getUser(ps.userId).catch(err => {
+ if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+ throw err;
+ });
- // Pull the user
- await UserGroupJoinings.delete({ userGroupId: userGroup.id, userId: user.id });
-});
+ if (user.id === userGroup.userId) {
+ throw new ApiError(meta.errors.isOwner);
+ }
+
+ // Pull the user
+ await this.userGroupJoiningsRepository.delete({ userGroupId: userGroup.id, userId: user.id });
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/users/groups/show.ts b/packages/backend/src/server/api/endpoints/users/groups/show.ts
index 21e3d9da26..1cebfcd204 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/show.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/show.ts
@@ -1,5 +1,8 @@
-import { UserGroups, UserGroupJoinings } from '@/models/index.js';
-import define from '../../../define.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { UserGroupEntityService } from '@/core/entities/UserGroupEntityService.js';
+import { DI } from '@/di-symbols.js';
import { ApiError } from '../../../error.js';
export const meta = {
@@ -35,24 +38,37 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, me) => {
- // Fetch the group
- const userGroup = await UserGroups.findOneBy({
- id: ps.groupId,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userGroupsRepository)
+ private userGroupsRepository: UserGroupsRepository,
- if (userGroup == null) {
- throw new ApiError(meta.errors.noSuchGroup);
- }
+ @Inject(DI.userGroupJoiningsRepository)
+ private userGroupJoiningsRepository: UserGroupJoiningsRepository,
- const joining = await UserGroupJoinings.findOneBy({
- userId: me.id,
- userGroupId: userGroup.id,
- });
+ private userGroupEntityService: UserGroupEntityService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ // Fetch the group
+ const userGroup = await this.userGroupsRepository.findOneBy({
+ id: ps.groupId,
+ });
- if (joining == null && userGroup.userId !== me.id) {
- throw new ApiError(meta.errors.noSuchGroup);
- }
+ if (userGroup == null) {
+ throw new ApiError(meta.errors.noSuchGroup);
+ }
+
+ const joining = await this.userGroupJoiningsRepository.findOneBy({
+ userId: me.id,
+ userGroupId: userGroup.id,
+ });
- return await UserGroups.pack(userGroup);
-});
+ if (joining == null && userGroup.userId !== me.id) {
+ throw new ApiError(meta.errors.noSuchGroup);
+ }
+
+ return await this.userGroupEntityService.pack(userGroup);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts
index 6456e70dd5..a8b2533b73 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts
@@ -1,7 +1,10 @@
-import { UserGroups, UserGroupJoinings } from '@/models/index.js';
-import define from '../../../define.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { UserGroupEntityService } from '@/core/entities/UserGroupEntityService.js';
+import { GetterService } from '@/server/api/common/GetterService.js';
+import { DI } from '@/di-symbols.js';
import { ApiError } from '../../../error.js';
-import { getUser } from '../../../common/getters.js';
export const meta = {
tags: ['groups', 'users'],
@@ -49,35 +52,49 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, me) => {
- // Fetch the group
- const userGroup = await UserGroups.findOneBy({
- id: ps.groupId,
- userId: me.id,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userGroupsRepository)
+ private userGroupsRepository: UserGroupsRepository,
- if (userGroup == null) {
- throw new ApiError(meta.errors.noSuchGroup);
- }
+ @Inject(DI.userGroupJoiningsRepository)
+ private userGroupJoiningsRepository: UserGroupJoiningsRepository,
- // Fetch the user
- const user = await getUser(ps.userId).catch(e => {
- if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
- throw e;
- });
+ private userGroupEntityService: UserGroupEntityService,
+ private getterService: GetterService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ // Fetch the group
+ const userGroup = await this.userGroupsRepository.findOneBy({
+ id: ps.groupId,
+ userId: me.id,
+ });
- const joining = await UserGroupJoinings.findOneBy({
- userGroupId: userGroup.id,
- userId: user.id,
- });
+ if (userGroup == null) {
+ throw new ApiError(meta.errors.noSuchGroup);
+ }
- if (joining == null) {
- throw new ApiError(meta.errors.noSuchGroupMember);
- }
+ // Fetch the user
+ const user = await this.getterService.getUser(ps.userId).catch(err => {
+ if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+ throw err;
+ });
+
+ const joining = await this.userGroupJoiningsRepository.findOneBy({
+ userGroupId: userGroup.id,
+ userId: user.id,
+ });
- await UserGroups.update(userGroup.id, {
- userId: ps.userId,
- });
+ if (joining == null) {
+ throw new ApiError(meta.errors.noSuchGroupMember);
+ }
- return await UserGroups.pack(userGroup.id);
-});
+ await this.userGroupsRepository.update(userGroup.id, {
+ userId: ps.userId,
+ });
+
+ return await this.userGroupEntityService.pack(userGroup.id);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/users/groups/update.ts b/packages/backend/src/server/api/endpoints/users/groups/update.ts
index 0a96165fc4..b679625c85 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/update.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/update.ts
@@ -1,5 +1,8 @@
-import { UserGroups } from '@/models/index.js';
-import define from '../../../define.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { UserGroupsRepository } from '@/models/index.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { UserGroupEntityService } from '@/core/entities/UserGroupEntityService.js';
+import { DI } from '@/di-symbols.js';
import { ApiError } from '../../../error.js';
export const meta = {
@@ -36,20 +39,30 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, me) => {
- // Fetch the group
- const userGroup = await UserGroups.findOneBy({
- id: ps.groupId,
- userId: me.id,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userGroupsRepository)
+ private userGroupsRepository: UserGroupsRepository,
- if (userGroup == null) {
- throw new ApiError(meta.errors.noSuchGroup);
- }
+ private userGroupEntityService: UserGroupEntityService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ // Fetch the group
+ const userGroup = await this.userGroupsRepository.findOneBy({
+ id: ps.groupId,
+ userId: me.id,
+ });
+
+ if (userGroup == null) {
+ throw new ApiError(meta.errors.noSuchGroup);
+ }
- await UserGroups.update(userGroup.id, {
- name: ps.name,
- });
+ await this.userGroupsRepository.update(userGroup.id, {
+ name: ps.name,
+ });
- return await UserGroups.pack(userGroup.id);
-});
+ return await this.userGroupEntityService.pack(userGroup.id);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/users/lists/create.ts b/packages/backend/src/server/api/endpoints/users/lists/create.ts
index 783e63f5de..aa64ca1229 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/create.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/create.ts
@@ -1,7 +1,10 @@
-import { UserLists } from '@/models/index.js';
-import { genId } from '@/misc/gen-id.js';
-import { UserList } from '@/models/entities/user-list.js';
-import define from '../../../define.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { UserListsRepository } from '@/models/index.js';
+import { IdService } from '@/core/IdService.js';
+import type { UserList } from '@/models/entities/UserList.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { UserListEntityService } from '@/core/entities/UserListEntityService.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
tags: ['lists'],
@@ -28,13 +31,24 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const userList = await UserLists.insert({
- id: genId(),
- createdAt: new Date(),
- userId: user.id,
- name: ps.name,
- } as UserList).then(x => UserLists.findOneByOrFail(x.identifiers[0]));
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userListsRepository)
+ private userListsRepository: UserListsRepository,
- return await UserLists.pack(userList);
-});
+ private userListEntityService: UserListEntityService,
+ private idService: IdService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const userList = await this.userListsRepository.insert({
+ id: this.idService.genId(),
+ createdAt: new Date(),
+ userId: me.id,
+ name: ps.name,
+ } as UserList).then(x => this.userListsRepository.findOneByOrFail(x.identifiers[0]));
+
+ return await this.userListEntityService.pack(userList);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/users/lists/delete.ts b/packages/backend/src/server/api/endpoints/users/lists/delete.ts
index 5a7613c98a..0f4125a39f 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/delete.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/delete.ts
@@ -1,5 +1,7 @@
-import { UserLists } from '@/models/index.js';
-import define from '../../../define.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { UserListsRepository } from '@/models/index.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { DI } from '@/di-symbols.js';
import { ApiError } from '../../../error.js';
export const meta = {
@@ -29,15 +31,23 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const userList = await UserLists.findOneBy({
- id: ps.listId,
- userId: user.id,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userListsRepository)
+ private userListsRepository: UserListsRepository,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const userList = await this.userListsRepository.findOneBy({
+ id: ps.listId,
+ userId: me.id,
+ });
- if (userList == null) {
- throw new ApiError(meta.errors.noSuchList);
- }
+ if (userList == null) {
+ throw new ApiError(meta.errors.noSuchList);
+ }
- await UserLists.delete(userList.id);
-});
+ await this.userListsRepository.delete(userList.id);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/users/lists/list.ts b/packages/backend/src/server/api/endpoints/users/lists/list.ts
index 889052fa30..919de22377 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/list.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/list.ts
@@ -1,5 +1,8 @@
-import { UserLists } from '@/models/index.js';
-import define from '../../../define.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { UserListsRepository } from '@/models/index.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { UserListEntityService } from '@/core/entities/UserListEntityService.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
tags: ['lists', 'account'],
@@ -28,10 +31,20 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, me) => {
- const userLists = await UserLists.findBy({
- userId: me.id,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userListsRepository)
+ private userListsRepository: UserListsRepository,
- return await Promise.all(userLists.map(x => UserLists.pack(x)));
-});
+ private userListEntityService: UserListEntityService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const userLists = await this.userListsRepository.findBy({
+ userId: me.id,
+ });
+
+ return await Promise.all(userLists.map(x => this.userListEntityService.pack(x)));
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/users/lists/pull.ts b/packages/backend/src/server/api/endpoints/users/lists/pull.ts
index d3d1d6555c..89d97be93e 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/pull.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/pull.ts
@@ -1,8 +1,11 @@
-import { publishUserListStream } from '@/services/stream.js';
-import { UserLists, UserListJoinings, Users } from '@/models/index.js';
-import define from '../../../define.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { UserListsRepository, UserListJoiningsRepository } from '@/models/index.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { GetterService } from '@/server/api/common/GetterService.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { DI } from '@/di-symbols.js';
import { ApiError } from '../../../error.js';
-import { getUser } from '../../../common/getters.js';
export const meta = {
tags: ['lists', 'users'],
@@ -38,25 +41,40 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, me) => {
- // Fetch the list
- const userList = await UserLists.findOneBy({
- id: ps.listId,
- userId: me.id,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userListsRepository)
+ private userListsRepository: UserListsRepository,
- if (userList == null) {
- throw new ApiError(meta.errors.noSuchList);
- }
+ @Inject(DI.userListJoiningsRepository)
+ private userListJoiningsRepository: UserListJoiningsRepository,
+
+ private userEntityService: UserEntityService,
+ private getterService: GetterService,
+ private globalEventService: GlobalEventService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ // Fetch the list
+ const userList = await this.userListsRepository.findOneBy({
+ id: ps.listId,
+ userId: me.id,
+ });
- // Fetch the user
- const user = await getUser(ps.userId).catch(e => {
- if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
- throw e;
- });
+ if (userList == null) {
+ throw new ApiError(meta.errors.noSuchList);
+ }
- // Pull the user
- await UserListJoinings.delete({ userListId: userList.id, userId: user.id });
+ // Fetch the user
+ const user = await this.getterService.getUser(ps.userId).catch(err => {
+ if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+ throw err;
+ });
- publishUserListStream(userList.id, 'userRemoved', await Users.pack(user));
-});
+ // Pull the user
+ await this.userListJoiningsRepository.delete({ userListId: userList.id, userId: user.id });
+
+ this.globalEventService.publishUserListStream(userList.id, 'userRemoved', await this.userEntityService.pack(user));
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/users/lists/push.ts b/packages/backend/src/server/api/endpoints/users/lists/push.ts
index 12b7b86342..77ad772b13 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/push.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts
@@ -1,8 +1,10 @@
-import { pushUserToUserList } from '@/services/user-list/push.js';
-import { UserLists, UserListJoinings, Blockings } from '@/models/index.js';
-import define from '../../../define.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { UserListsRepository, UserListJoiningsRepository, BlockingsRepository } from '@/models/index.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { GetterService } from '@/server/api/common/GetterService.js';
+import { UserListService } from '@/core/UserListService.js';
+import { DI } from '@/di-symbols.js';
import { ApiError } from '../../../error.js';
-import { getUser } from '../../../common/getters.js';
export const meta = {
tags: ['lists', 'users'],
@@ -50,43 +52,60 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, me) => {
- // Fetch the list
- const userList = await UserLists.findOneBy({
- id: ps.listId,
- userId: me.id,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userListsRepository)
+ private userListsRepository: UserListsRepository,
- if (userList == null) {
- throw new ApiError(meta.errors.noSuchList);
- }
+ @Inject(DI.userListJoiningsRepository)
+ private userListJoiningsRepository: UserListJoiningsRepository,
- // Fetch the user
- const user = await getUser(ps.userId).catch(e => {
- if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
- throw e;
- });
+ @Inject(DI.blockingsRepository)
+ private blockingsRepository: BlockingsRepository,
- // Check blocking
- if (user.id !== me.id) {
- const block = await Blockings.findOneBy({
- blockerId: user.id,
- blockeeId: me.id,
- });
- if (block) {
- throw new ApiError(meta.errors.youHaveBeenBlocked);
- }
- }
+ private getterService: GetterService,
+ private userListService: UserListService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ // Fetch the list
+ const userList = await this.userListsRepository.findOneBy({
+ id: ps.listId,
+ userId: me.id,
+ });
- const exist = await UserListJoinings.findOneBy({
- userListId: userList.id,
- userId: user.id,
- });
+ if (userList == null) {
+ throw new ApiError(meta.errors.noSuchList);
+ }
- if (exist) {
- throw new ApiError(meta.errors.alreadyAdded);
- }
+ // Fetch the user
+ const user = await this.getterService.getUser(ps.userId).catch(err => {
+ if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+ throw err;
+ });
+
+ // Check blocking
+ if (user.id !== me.id) {
+ const block = await this.blockingsRepository.findOneBy({
+ blockerId: user.id,
+ blockeeId: me.id,
+ });
+ if (block) {
+ throw new ApiError(meta.errors.youHaveBeenBlocked);
+ }
+ }
- // Push the user
- await pushUserToUserList(user, userList);
-});
+ const exist = await this.userListJoiningsRepository.findOneBy({
+ userListId: userList.id,
+ userId: user.id,
+ });
+
+ if (exist) {
+ throw new ApiError(meta.errors.alreadyAdded);
+ }
+
+ // Push the user
+ await this.userListService.push(user, userList);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/users/lists/show.ts b/packages/backend/src/server/api/endpoints/users/lists/show.ts
index fd0612f735..62e730b2f7 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/show.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/show.ts
@@ -1,5 +1,8 @@
-import { UserLists } from '@/models/index.js';
-import define from '../../../define.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { UserListsRepository } from '@/models/index.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { UserListEntityService } from '@/core/entities/UserListEntityService.js';
+import { DI } from '@/di-symbols.js';
import { ApiError } from '../../../error.js';
export const meta = {
@@ -35,16 +38,26 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, me) => {
- // Fetch the list
- const userList = await UserLists.findOneBy({
- id: ps.listId,
- userId: me.id,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userListsRepository)
+ private userListsRepository: UserListsRepository,
- if (userList == null) {
- throw new ApiError(meta.errors.noSuchList);
- }
+ private userListEntityService: UserListEntityService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ // Fetch the list
+ const userList = await this.userListsRepository.findOneBy({
+ id: ps.listId,
+ userId: me.id,
+ });
+
+ if (userList == null) {
+ throw new ApiError(meta.errors.noSuchList);
+ }
- return await UserLists.pack(userList);
-});
+ return await this.userListEntityService.pack(userList);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/users/lists/update.ts b/packages/backend/src/server/api/endpoints/users/lists/update.ts
index 65e708b959..c6669d24d1 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/update.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/update.ts
@@ -1,5 +1,8 @@
-import { UserLists } from '@/models/index.js';
-import define from '../../../define.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { UserListsRepository } from '@/models/index.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { UserListEntityService } from '@/core/entities/UserListEntityService.js';
+import { DI } from '@/di-symbols.js';
import { ApiError } from '../../../error.js';
export const meta = {
@@ -36,20 +39,30 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- // Fetch the list
- const userList = await UserLists.findOneBy({
- id: ps.listId,
- userId: user.id,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userListsRepository)
+ private userListsRepository: UserListsRepository,
- if (userList == null) {
- throw new ApiError(meta.errors.noSuchList);
- }
+ private userListEntityService: UserListEntityService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ // Fetch the list
+ const userList = await this.userListsRepository.findOneBy({
+ id: ps.listId,
+ userId: me.id,
+ });
+
+ if (userList == null) {
+ throw new ApiError(meta.errors.noSuchList);
+ }
- await UserLists.update(userList.id, {
- name: ps.name,
- });
+ await this.userListsRepository.update(userList.id, {
+ name: ps.name,
+ });
- return await UserLists.pack(userList.id);
-});
+ return await this.userListEntityService.pack(userList.id);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts
index 9fa56fe83a..bb8104584c 100644
--- a/packages/backend/src/server/api/endpoints/users/notes.ts
+++ b/packages/backend/src/server/api/endpoints/users/notes.ts
@@ -1,12 +1,12 @@
import { Brackets } from 'typeorm';
-import { Notes } from '@/models/index.js';
-import define from '../../define.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { NotesRepository } from '@/models/index.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { QueryService } from '@/core/QueryService.js';
+import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
+import { DI } from '@/di-symbols.js';
import { ApiError } from '../../error.js';
-import { getUser } from '../../common/getters.js';
-import { makePaginationQuery } from '../../common/make-pagination-query.js';
-import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
-import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
-import { generateBlockedUserQuery } from '../../common/generate-block-query.js';
+import { GetterService } from '../../common/GetterService.js';
export const meta = {
tags: ['users', 'notes'],
@@ -53,70 +53,82 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, me) => {
- // Lookup user
- const user = await getUser(ps.userId).catch(e => {
- if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
- throw e;
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.notesRepository)
+ private notesRepository: NotesRepository,
- //#region Construct query
- const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
- .andWhere('note.userId = :userId', { userId: user.id })
- .innerJoinAndSelect('note.user', 'user')
- .leftJoinAndSelect('user.avatar', 'avatar')
- .leftJoinAndSelect('user.banner', 'banner')
- .leftJoinAndSelect('note.reply', 'reply')
- .leftJoinAndSelect('note.renote', 'renote')
- .leftJoinAndSelect('reply.user', 'replyUser')
- .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar')
- .leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
- .leftJoinAndSelect('renote.user', 'renoteUser')
- .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
- .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
+ private noteEntityService: NoteEntityService,
+ private queryService: QueryService,
+ private getterService: GetterService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ // Lookup user
+ const user = await this.getterService.getUser(ps.userId).catch(err => {
+ if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+ throw err;
+ });
- generateVisibilityQuery(query, me);
- if (me) {
- generateMutedUserQuery(query, me, user);
- generateBlockedUserQuery(query, me);
- }
+ //#region Construct query
+ const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
+ .andWhere('note.userId = :userId', { userId: user.id })
+ .innerJoinAndSelect('note.user', 'user')
+ .leftJoinAndSelect('user.avatar', 'avatar')
+ .leftJoinAndSelect('user.banner', 'banner')
+ .leftJoinAndSelect('note.reply', 'reply')
+ .leftJoinAndSelect('note.renote', 'renote')
+ .leftJoinAndSelect('reply.user', 'replyUser')
+ .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar')
+ .leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
+ .leftJoinAndSelect('renote.user', 'renoteUser')
+ .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
+ .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
- if (ps.withFiles) {
- query.andWhere('note.fileIds != \'{}\'');
- }
+ this.queryService.generateVisibilityQuery(query, me);
+ if (me) {
+ this.queryService.generateMutedUserQuery(query, me, user);
+ this.queryService.generateBlockedUserQuery(query, me);
+ }
- if (ps.fileType != null) {
- query.andWhere('note.fileIds != \'{}\'');
- query.andWhere(new Brackets(qb => {
- for (const type of ps.fileType!) {
- const i = ps.fileType!.indexOf(type);
- qb.orWhere(`:type${i} = ANY(note.attachedFileTypes)`, { [`type${i}`]: type });
+ if (ps.withFiles) {
+ query.andWhere('note.fileIds != \'{}\'');
}
- }));
- if (ps.excludeNsfw) {
- query.andWhere('note.cw IS NULL');
- query.andWhere('0 = (SELECT COUNT(*) FROM drive_file df WHERE df.id = ANY(note."fileIds") AND df."isSensitive" = TRUE)');
- }
- }
+ if (ps.fileType != null) {
+ query.andWhere('note.fileIds != \'{}\'');
+ query.andWhere(new Brackets(qb => {
+ for (const type of ps.fileType!) {
+ const i = ps.fileType!.indexOf(type);
+ qb.orWhere(`:type${i} = ANY(note.attachedFileTypes)`, { [`type${i}`]: type });
+ }
+ }));
- if (!ps.includeReplies) {
- query.andWhere('note.replyId IS NULL');
- }
+ if (ps.excludeNsfw) {
+ query.andWhere('note.cw IS NULL');
+ query.andWhere('0 = (SELECT COUNT(*) FROM drive_file df WHERE df.id = ANY(note."fileIds") AND df."isSensitive" = TRUE)');
+ }
+ }
- if (ps.includeMyRenotes === false) {
- query.andWhere(new Brackets(qb => {
- qb.orWhere('note.userId != :userId', { userId: user.id });
- qb.orWhere('note.renoteId IS NULL');
- qb.orWhere('note.text IS NOT NULL');
- qb.orWhere('note.fileIds != \'{}\'');
- qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
- }));
- }
+ if (!ps.includeReplies) {
+ query.andWhere('note.replyId IS NULL');
+ }
- //#endregion
+ if (ps.includeMyRenotes === false) {
+ query.andWhere(new Brackets(qb => {
+ qb.orWhere('note.userId != :userId', { userId: user.id });
+ qb.orWhere('note.renoteId IS NULL');
+ qb.orWhere('note.text IS NOT NULL');
+ qb.orWhere('note.fileIds != \'{}\'');
+ qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
+ }));
+ }
- const timeline = await query.take(ps.limit).getMany();
+ //#endregion
- return await Notes.packMany(timeline, me);
-});
+ const timeline = await query.take(ps.limit).getMany();
+
+ return await this.noteEntityService.packMany(timeline, me);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/users/pages.ts b/packages/backend/src/server/api/endpoints/users/pages.ts
index b1d28af845..96c7ef1e70 100644
--- a/packages/backend/src/server/api/endpoints/users/pages.ts
+++ b/packages/backend/src/server/api/endpoints/users/pages.ts
@@ -1,6 +1,9 @@
-import { Pages } from '@/models/index.js';
-import define from '../../define.js';
-import { makePaginationQuery } from '../../common/make-pagination-query.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { QueryService } from '@/core/QueryService.js';
+import { PageEntityService } from '@/core/entities/PageEntityService.js';
+import { PagesRepository } from '@/models';
+import { DI } from '@/di-symbols.js';
export const meta = {
tags: ['users', 'pages'],
@@ -30,14 +33,25 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const query = makePaginationQuery(Pages.createQueryBuilder('page'), ps.sinceId, ps.untilId)
- .andWhere('page.userId = :userId', { userId: ps.userId })
- .andWhere('page.visibility = \'public\'');
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.pagesRepository)
+ private pagesRepository: PagesRepository,
- const pages = await query
- .take(ps.limit)
- .getMany();
+ private pageEntityService: PageEntityService,
+ private queryService: QueryService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const query = this.queryService.makePaginationQuery(this.pagesRepository.createQueryBuilder('page'), ps.sinceId, ps.untilId)
+ .andWhere('page.userId = :userId', { userId: ps.userId })
+ .andWhere('page.visibility = \'public\'');
- return await Pages.packMany(pages);
-});
+ const pages = await query
+ .take(ps.limit)
+ .getMany();
+
+ return await this.pageEntityService.packMany(pages);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/users/reactions.ts b/packages/backend/src/server/api/endpoints/users/reactions.ts
index 9668bd21b8..6b4d882b7c 100644
--- a/packages/backend/src/server/api/endpoints/users/reactions.ts
+++ b/packages/backend/src/server/api/endpoints/users/reactions.ts
@@ -1,7 +1,9 @@
-import { NoteReactions, UserProfiles } from '@/models/index.js';
-import define from '../../define.js';
-import { makePaginationQuery } from '../../common/make-pagination-query.js';
-import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { UserProfilesRepository, NoteReactionsRepository } from '@/models/index.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { QueryService } from '@/core/QueryService.js';
+import { NoteReactionEntityService } from '@/core/entities/NoteReactionEntityService.js';
+import { DI } from '@/di-symbols.js';
import { ApiError } from '../../error.js';
export const meta = {
@@ -44,23 +46,37 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, me) => {
- const profile = await UserProfiles.findOneByOrFail({ userId: ps.userId });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
- if (me == null || (me.id !== ps.userId && !profile.publicReactions)) {
- throw new ApiError(meta.errors.reactionsNotPublic);
- }
+ @Inject(DI.noteReactionsRepository)
+ private noteReactionsRepository: NoteReactionsRepository,
+
+ private noteReactionEntityService: NoteReactionEntityService,
+ private queryService: QueryService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const profile = await this.userProfilesRepository.findOneByOrFail({ userId: ps.userId });
- const query = makePaginationQuery(NoteReactions.createQueryBuilder('reaction'),
- ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
- .andWhere('reaction.userId = :userId', { userId: ps.userId })
- .leftJoinAndSelect('reaction.note', 'note');
+ if (me == null || (me.id !== ps.userId && !profile.publicReactions)) {
+ throw new ApiError(meta.errors.reactionsNotPublic);
+ }
- generateVisibilityQuery(query, me);
+ const query = this.queryService.makePaginationQuery(this.noteReactionsRepository.createQueryBuilder('reaction'),
+ ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
+ .andWhere('reaction.userId = :userId', { userId: ps.userId })
+ .leftJoinAndSelect('reaction.note', 'note');
- const reactions = await query
- .take(ps.limit)
- .getMany();
+ this.queryService.generateVisibilityQuery(query, me);
- return await Promise.all(reactions.map(reaction => NoteReactions.pack(reaction, me, { withNote: true })));
-});
+ const reactions = await query
+ .take(ps.limit)
+ .getMany();
+
+ return await Promise.all(reactions.map(reaction => this.noteReactionEntityService.pack(reaction, me, { withNote: true })));
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/users/recommendation.ts b/packages/backend/src/server/api/endpoints/users/recommendation.ts
index e7654e1714..e50a5706d9 100644
--- a/packages/backend/src/server/api/endpoints/users/recommendation.ts
+++ b/packages/backend/src/server/api/endpoints/users/recommendation.ts
@@ -1,8 +1,10 @@
import ms from 'ms';
-import { Users, Followings } from '@/models/index.js';
-import define from '../../define.js';
-import { generateMutedUserQueryForUsers } from '../../common/generate-muted-user-query.js';
-import { generateBlockedUserQuery, generateBlockQueryForUsers } from '../../common/generate-block-query.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { UsersRepository, FollowingsRepository } from '@/models/index.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { QueryService } from '@/core/QueryService.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
tags: ['users'],
@@ -34,29 +36,43 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, me) => {
- const query = Users.createQueryBuilder('user')
- .where('user.isLocked = FALSE')
- .andWhere('user.isExplorable = TRUE')
- .andWhere('user.host IS NULL')
- .andWhere('user.updatedAt >= :date', { date: new Date(Date.now() - ms('7days')) })
- .andWhere('user.id != :meId', { meId: me.id })
- .orderBy('user.followersCount', 'DESC');
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- generateMutedUserQueryForUsers(query, me);
- generateBlockQueryForUsers(query, me);
- generateBlockedUserQuery(query, me);
+ @Inject(DI.followingsRepository)
+ private followingsRepository: FollowingsRepository,
+
+ private userEntityService: UserEntityService,
+ private queryService: QueryService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const query = this.usersRepository.createQueryBuilder('user')
+ .where('user.isLocked = FALSE')
+ .andWhere('user.isExplorable = TRUE')
+ .andWhere('user.host IS NULL')
+ .andWhere('user.updatedAt >= :date', { date: new Date(Date.now() - ms('7days')) })
+ .andWhere('user.id != :meId', { meId: me.id })
+ .orderBy('user.followersCount', 'DESC');
- const followingQuery = Followings.createQueryBuilder('following')
- .select('following.followeeId')
- .where('following.followerId = :followerId', { followerId: me.id });
+ this.queryService.generateMutedUserQueryForUsers(query, me);
+ this.queryService.generateBlockQueryForUsers(query, me);
+ this.queryService.generateBlockedUserQuery(query, me);
- query
- .andWhere(`user.id NOT IN (${ followingQuery.getQuery() })`);
+ const followingQuery = this.followingsRepository.createQueryBuilder('following')
+ .select('following.followeeId')
+ .where('following.followerId = :followerId', { followerId: me.id });
- query.setParameters(followingQuery.getParameters());
+ query
+ .andWhere(`user.id NOT IN (${ followingQuery.getQuery() })`);
- const users = await query.take(ps.limit).skip(ps.offset).getMany();
+ query.setParameters(followingQuery.getParameters());
- return await Users.packMany(users, me, { detail: true });
-});
+ const users = await query.take(ps.limit).skip(ps.offset).getMany();
+
+ return await this.userEntityService.packMany(users, me, { detail: true });
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/users/relation.ts b/packages/backend/src/server/api/endpoints/users/relation.ts
index 233a6a90b4..aea75ae799 100644
--- a/packages/backend/src/server/api/endpoints/users/relation.ts
+++ b/packages/backend/src/server/api/endpoints/users/relation.ts
@@ -1,5 +1,8 @@
-import { Users } from '@/models/index.js';
-import define from '../../define.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { UsersRepository } from '@/models/index.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
tags: ['users'],
@@ -112,10 +115,20 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, me) => {
- const ids = Array.isArray(ps.userId) ? ps.userId : [ps.userId];
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- const relations = await Promise.all(ids.map(id => Users.getRelation(me.id, id)));
+ private userEntityService: UserEntityService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const ids = Array.isArray(ps.userId) ? ps.userId : [ps.userId];
- return Array.isArray(ps.userId) ? relations : relations[0];
-});
+ const relations = await Promise.all(ids.map(id => this.userEntityService.getRelation(me.id, id)));
+
+ return Array.isArray(ps.userId) ? relations : relations[0];
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/users/report-abuse.ts b/packages/backend/src/server/api/endpoints/users/report-abuse.ts
index a9987eafa9..5c211a9017 100644
--- a/packages/backend/src/server/api/endpoints/users/report-abuse.ts
+++ b/packages/backend/src/server/api/endpoints/users/report-abuse.ts
@@ -1,12 +1,14 @@
import * as sanitizeHtml from 'sanitize-html';
-import { publishAdminStream } from '@/services/stream.js';
-import { AbuseUserReports, Users } from '@/models/index.js';
-import { genId } from '@/misc/gen-id.js';
-import { sendEmail } from '@/services/send-email.js';
-import { fetchMeta } from '@/misc/fetch-meta.js';
-import { getUser } from '../../common/getters.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { UsersRepository, AbuseUserReportsRepository } from '@/models/index.js';
+import { IdService } from '@/core/IdService.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { MetaService } from '@/core/MetaService.js';
+import { EmailService } from '@/core/EmailService.js';
+import { DI } from '@/di-symbols.js';
import { ApiError } from '../../error.js';
-import define from '../../define.js';
+import { GetterService } from '../../common/GetterService.js';
export const meta = {
tags: ['users'],
@@ -46,55 +48,72 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, me) => {
- // Lookup user
- const user = await getUser(ps.userId).catch(e => {
- if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
- throw e;
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- if (user.id === me.id) {
- throw new ApiError(meta.errors.cannotReportYourself);
- }
+ @Inject(DI.abuseUserReportsRepository)
+ private abuseUserReportsRepository: AbuseUserReportsRepository,
- if (user.isAdmin) {
- throw new ApiError(meta.errors.cannotReportAdmin);
- }
+ private idService: IdService,
+ private metaService: MetaService,
+ private emailService: EmailService,
+ private getterService: GetterService,
+ private globalEventService: GlobalEventService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ // Lookup user
+ const user = await this.getterService.getUser(ps.userId).catch(err => {
+ if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+ throw err;
+ });
- const report = await AbuseUserReports.insert({
- id: genId(),
- createdAt: new Date(),
- targetUserId: user.id,
- targetUserHost: user.host,
- reporterId: me.id,
- reporterHost: null,
- comment: ps.comment,
- }).then(x => AbuseUserReports.findOneByOrFail(x.identifiers[0]));
+ if (user.id === me.id) {
+ throw new ApiError(meta.errors.cannotReportYourself);
+ }
- // Publish event to moderators
- setImmediate(async () => {
- const moderators = await Users.find({
- where: [{
- isAdmin: true,
- }, {
- isModerator: true,
- }],
- });
+ if (user.isAdmin) {
+ throw new ApiError(meta.errors.cannotReportAdmin);
+ }
- for (const moderator of moderators) {
- publishAdminStream(moderator.id, 'newAbuseUserReport', {
- id: report.id,
- targetUserId: report.targetUserId,
- reporterId: report.reporterId,
- comment: report.comment,
- });
- }
+ const report = await this.abuseUserReportsRepository.insert({
+ id: this.idService.genId(),
+ createdAt: new Date(),
+ targetUserId: user.id,
+ targetUserHost: user.host,
+ reporterId: me.id,
+ reporterHost: null,
+ comment: ps.comment,
+ }).then(x => this.abuseUserReportsRepository.findOneByOrFail(x.identifiers[0]));
- const meta = await fetchMeta();
- if (meta.email) {
- sendEmail(meta.email, 'New abuse report',
- sanitizeHtml(ps.comment),
- sanitizeHtml(ps.comment));
- }
- });
-});
+ // Publish event to moderators
+ setImmediate(async () => {
+ const moderators = await this.usersRepository.find({
+ where: [{
+ isAdmin: true,
+ }, {
+ isModerator: true,
+ }],
+ });
+
+ for (const moderator of moderators) {
+ this.globalEventService.publishAdminStream(moderator.id, 'newAbuseUserReport', {
+ id: report.id,
+ targetUserId: report.targetUserId,
+ reporterId: report.reporterId,
+ comment: report.comment,
+ });
+ }
+
+ const meta = await this.metaService.fetch();
+ if (meta.email) {
+ this.emailService.sendEmail(meta.email, 'New abuse report',
+ sanitizeHtml(ps.comment),
+ sanitizeHtml(ps.comment));
+ }
+ });
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts
index 6e5bc46bb5..1747dc93f6 100644
--- a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts
+++ b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts
@@ -1,8 +1,11 @@
import { Brackets } from 'typeorm';
-import { Followings, Users } from '@/models/index.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { UsersRepository, FollowingsRepository } from '@/models/index.js';
import { USER_ACTIVE_THRESHOLD } from '@/const.js';
-import { User } from '@/models/entities/user.js';
-import define from '../../define.js';
+import type { User } from '@/models/entities/User.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
tags: ['users'],
@@ -39,78 +42,91 @@ export const paramDef = {
// TODO: avatar,bannerをJOINしたいけどエラーになる
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, me) => {
- const activeThreshold = new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)); // 30日
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- if (ps.host) {
- const q = Users.createQueryBuilder('user')
- .where('user.isSuspended = FALSE')
- .andWhere('user.host LIKE :host', { host: ps.host.toLowerCase() + '%' });
+ @Inject(DI.followingsRepository)
+ private followingsRepository: FollowingsRepository,
- if (ps.username) {
- q.andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' });
- }
+ private userEntityService: UserEntityService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const activeThreshold = new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)); // 30日
- q.andWhere('user.updatedAt IS NOT NULL');
- q.orderBy('user.updatedAt', 'DESC');
+ if (ps.host) {
+ const q = this.usersRepository.createQueryBuilder('user')
+ .where('user.isSuspended = FALSE')
+ .andWhere('user.host LIKE :host', { host: ps.host.toLowerCase() + '%' });
- const users = await q.take(ps.limit).getMany();
+ if (ps.username) {
+ q.andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' });
+ }
- return await Users.packMany(users, me, { detail: ps.detail });
- } else if (ps.username) {
- let users: User[] = [];
+ q.andWhere('user.updatedAt IS NOT NULL');
+ q.orderBy('user.updatedAt', 'DESC');
- if (me) {
- const followingQuery = Followings.createQueryBuilder('following')
- .select('following.followeeId')
- .where('following.followerId = :followerId', { followerId: me.id });
+ const users = await q.take(ps.limit).getMany();
- const query = Users.createQueryBuilder('user')
- .where(`user.id IN (${ followingQuery.getQuery() })`)
- .andWhere('user.id != :meId', { meId: me.id })
- .andWhere('user.isSuspended = FALSE')
- .andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' })
- .andWhere(new Brackets(qb => { qb
- .where('user.updatedAt IS NULL')
- .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold });
- }));
+ return await this.userEntityService.packMany(users, me, { detail: ps.detail });
+ } else if (ps.username) {
+ let users: User[] = [];
- query.setParameters(followingQuery.getParameters());
+ if (me) {
+ const followingQuery = this.followingsRepository.createQueryBuilder('following')
+ .select('following.followeeId')
+ .where('following.followerId = :followerId', { followerId: me.id });
- users = await query
- .orderBy('user.usernameLower', 'ASC')
- .take(ps.limit)
- .getMany();
+ const query = this.usersRepository.createQueryBuilder('user')
+ .where(`user.id IN (${ followingQuery.getQuery() })`)
+ .andWhere('user.id != :meId', { meId: me.id })
+ .andWhere('user.isSuspended = FALSE')
+ .andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' })
+ .andWhere(new Brackets(qb => { qb
+ .where('user.updatedAt IS NULL')
+ .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold });
+ }));
- if (users.length < ps.limit) {
- const otherQuery = await Users.createQueryBuilder('user')
- .where(`user.id NOT IN (${ followingQuery.getQuery() })`)
- .andWhere('user.id != :meId', { meId: me.id })
- .andWhere('user.isSuspended = FALSE')
- .andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' })
- .andWhere('user.updatedAt IS NOT NULL');
+ query.setParameters(followingQuery.getParameters());
- otherQuery.setParameters(followingQuery.getParameters());
+ users = await query
+ .orderBy('user.usernameLower', 'ASC')
+ .take(ps.limit)
+ .getMany();
- const otherUsers = await otherQuery
- .orderBy('user.updatedAt', 'DESC')
- .take(ps.limit - users.length)
- .getMany();
+ if (users.length < ps.limit) {
+ const otherQuery = await this.usersRepository.createQueryBuilder('user')
+ .where(`user.id NOT IN (${ followingQuery.getQuery() })`)
+ .andWhere('user.id != :meId', { meId: me.id })
+ .andWhere('user.isSuspended = FALSE')
+ .andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' })
+ .andWhere('user.updatedAt IS NOT NULL');
- users = users.concat(otherUsers);
+ otherQuery.setParameters(followingQuery.getParameters());
+
+ const otherUsers = await otherQuery
+ .orderBy('user.updatedAt', 'DESC')
+ .take(ps.limit - users.length)
+ .getMany();
+
+ users = users.concat(otherUsers);
+ }
+ } else {
+ users = await this.usersRepository.createQueryBuilder('user')
+ .where('user.isSuspended = FALSE')
+ .andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' })
+ .andWhere('user.updatedAt IS NOT NULL')
+ .orderBy('user.updatedAt', 'DESC')
+ .take(ps.limit - users.length)
+ .getMany();
+ }
+
+ return await this.userEntityService.packMany(users, me, { detail: !!ps.detail });
}
- } else {
- users = await Users.createQueryBuilder('user')
- .where('user.isSuspended = FALSE')
- .andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' })
- .andWhere('user.updatedAt IS NOT NULL')
- .orderBy('user.updatedAt', 'DESC')
- .take(ps.limit - users.length)
- .getMany();
- }
- return await Users.packMany(users, me, { detail: !!ps.detail });
+ return [];
+ });
}
-
- return [];
-});
+}
diff --git a/packages/backend/src/server/api/endpoints/users/search.ts b/packages/backend/src/server/api/endpoints/users/search.ts
index 01729de667..9879b1b68b 100644
--- a/packages/backend/src/server/api/endpoints/users/search.ts
+++ b/packages/backend/src/server/api/endpoints/users/search.ts
@@ -1,7 +1,10 @@
import { Brackets } from 'typeorm';
-import { UserProfiles, Users } from '@/models/index.js';
-import { User } from '@/models/entities/user.js';
-import define from '../../define.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { UsersRepository, UserProfilesRepository } from '@/models/index.js';
+import type { User } from '@/models/entities/User.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
tags: ['users'],
@@ -34,89 +37,102 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, me) => {
- const activeThreshold = new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)); // 30日
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- const isUsername = ps.query.startsWith('@');
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
- let users: User[] = [];
+ private userEntityService: UserEntityService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const activeThreshold = new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)); // 30日
- if (isUsername) {
- const usernameQuery = Users.createQueryBuilder('user')
- .where('user.usernameLower LIKE :username', { username: ps.query.replace('@', '').toLowerCase() + '%' })
- .andWhere(new Brackets(qb => { qb
- .where('user.updatedAt IS NULL')
- .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold });
- }))
- .andWhere('user.isSuspended = FALSE');
+ const isUsername = ps.query.startsWith('@');
- if (ps.origin === 'local') {
- usernameQuery.andWhere('user.host IS NULL');
- } else if (ps.origin === 'remote') {
- usernameQuery.andWhere('user.host IS NOT NULL');
- }
+ let users: User[] = [];
- users = await usernameQuery
- .orderBy('user.updatedAt', 'DESC', 'NULLS LAST')
- .take(ps.limit)
- .skip(ps.offset)
- .getMany();
- } else {
- const nameQuery = Users.createQueryBuilder('user')
- .where(new Brackets(qb => {
- qb.where('user.name ILIKE :query', { query: '%' + ps.query + '%' });
+ if (isUsername) {
+ const usernameQuery = this.usersRepository.createQueryBuilder('user')
+ .where('user.usernameLower LIKE :username', { username: ps.query.replace('@', '').toLowerCase() + '%' })
+ .andWhere(new Brackets(qb => { qb
+ .where('user.updatedAt IS NULL')
+ .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold });
+ }))
+ .andWhere('user.isSuspended = FALSE');
- // Also search username if it qualifies as username
- if (Users.validateLocalUsername(ps.query)) {
- qb.orWhere('user.usernameLower LIKE :username', { username: '%' + ps.query.toLowerCase() + '%' });
+ if (ps.origin === 'local') {
+ usernameQuery.andWhere('user.host IS NULL');
+ } else if (ps.origin === 'remote') {
+ usernameQuery.andWhere('user.host IS NOT NULL');
}
- }))
- .andWhere(new Brackets(qb => { qb
- .where('user.updatedAt IS NULL')
- .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold });
- }))
- .andWhere('user.isSuspended = FALSE');
- if (ps.origin === 'local') {
- nameQuery.andWhere('user.host IS NULL');
- } else if (ps.origin === 'remote') {
- nameQuery.andWhere('user.host IS NOT NULL');
- }
+ users = await usernameQuery
+ .orderBy('user.updatedAt', 'DESC', 'NULLS LAST')
+ .take(ps.limit)
+ .skip(ps.offset)
+ .getMany();
+ } else {
+ const nameQuery = this.usersRepository.createQueryBuilder('user')
+ .where(new Brackets(qb => {
+ qb.where('user.name ILIKE :query', { query: '%' + ps.query + '%' });
- users = await nameQuery
- .orderBy('user.updatedAt', 'DESC', 'NULLS LAST')
- .take(ps.limit)
- .skip(ps.offset)
- .getMany();
+ // Also search username if it qualifies as username
+ if (this.userEntityService.validateLocalUsername(ps.query)) {
+ qb.orWhere('user.usernameLower LIKE :username', { username: '%' + ps.query.toLowerCase() + '%' });
+ }
+ }))
+ .andWhere(new Brackets(qb => { qb
+ .where('user.updatedAt IS NULL')
+ .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold });
+ }))
+ .andWhere('user.isSuspended = FALSE');
- if (users.length < ps.limit) {
- const profQuery = UserProfiles.createQueryBuilder('prof')
- .select('prof.userId')
- .where('prof.description ILIKE :query', { query: '%' + ps.query + '%' });
+ if (ps.origin === 'local') {
+ nameQuery.andWhere('user.host IS NULL');
+ } else if (ps.origin === 'remote') {
+ nameQuery.andWhere('user.host IS NOT NULL');
+ }
- if (ps.origin === 'local') {
- profQuery.andWhere('prof.userHost IS NULL');
- } else if (ps.origin === 'remote') {
- profQuery.andWhere('prof.userHost IS NOT NULL');
- }
+ users = await nameQuery
+ .orderBy('user.updatedAt', 'DESC', 'NULLS LAST')
+ .take(ps.limit)
+ .skip(ps.offset)
+ .getMany();
- const query = Users.createQueryBuilder('user')
- .where(`user.id IN (${ profQuery.getQuery() })`)
- .andWhere(new Brackets(qb => { qb
- .where('user.updatedAt IS NULL')
- .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold });
- }))
- .andWhere('user.isSuspended = FALSE')
- .setParameters(profQuery.getParameters());
+ if (users.length < ps.limit) {
+ const profQuery = this.userProfilesRepository.createQueryBuilder('prof')
+ .select('prof.userId')
+ .where('prof.description ILIKE :query', { query: '%' + ps.query + '%' });
- users = users.concat(await query
- .orderBy('user.updatedAt', 'DESC', 'NULLS LAST')
- .take(ps.limit)
- .skip(ps.offset)
- .getMany(),
- );
- }
- }
+ if (ps.origin === 'local') {
+ profQuery.andWhere('prof.userHost IS NULL');
+ } else if (ps.origin === 'remote') {
+ profQuery.andWhere('prof.userHost IS NOT NULL');
+ }
+
+ const query = this.usersRepository.createQueryBuilder('user')
+ .where(`user.id IN (${ profQuery.getQuery() })`)
+ .andWhere(new Brackets(qb => { qb
+ .where('user.updatedAt IS NULL')
+ .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold });
+ }))
+ .andWhere('user.isSuspended = FALSE')
+ .setParameters(profQuery.getParameters());
- return await Users.packMany(users, me, { detail: ps.detail });
-});
+ users = users.concat(await query
+ .orderBy('user.updatedAt', 'DESC', 'NULLS LAST')
+ .take(ps.limit)
+ .skip(ps.offset)
+ .getMany(),
+ );
+ }
+ }
+
+ return await this.userEntityService.packMany(users, me, { detail: ps.detail });
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts
index 846d83b49f..98f5f03063 100644
--- a/packages/backend/src/server/api/endpoints/users/show.ts
+++ b/packages/backend/src/server/api/endpoints/users/show.ts
@@ -1,10 +1,14 @@
-import { FindOptionsWhere, In, IsNull } from 'typeorm';
-import { resolveUser } from '@/remote/resolve-user.js';
-import { Users } from '@/models/index.js';
-import { User } from '@/models/entities/user.js';
-import define from '../../define.js';
-import { apiLogger } from '../../logger.js';
+import { In, IsNull } from 'typeorm';
+import { Inject, Injectable } from '@nestjs/common';
+import { UsersRepository } from '@/models/index.js';
+import type { User } from '@/models/entities/User.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { ResolveUserService } from '@/core/remote/ResolveUserService.js';
+import { DI } from '@/di-symbols.js';
import { ApiError } from '../../error.js';
+import { ApiLoggerService } from '../../ApiLoggerService.js';
+import type { FindOptionsWhere } from 'typeorm';
export const meta = {
tags: ['users'],
@@ -78,53 +82,65 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, me) => {
- let user;
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- const isAdminOrModerator = me && (me.isAdmin || me.isModerator);
+ private userEntityService: UserEntityService,
+ private resolveUserService: ResolveUserService,
+ private apiLoggerService: ApiLoggerService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ let user;
- if (ps.userIds) {
- if (ps.userIds.length === 0) {
- return [];
- }
+ const isAdminOrModerator = me && (me.isAdmin || me.isModerator);
- const users = await Users.findBy(isAdminOrModerator ? {
- id: In(ps.userIds),
- } : {
- id: In(ps.userIds),
- isSuspended: false,
- });
+ if (ps.userIds) {
+ if (ps.userIds.length === 0) {
+ return [];
+ }
+
+ const users = await this.usersRepository.findBy(isAdminOrModerator ? {
+ id: In(ps.userIds),
+ } : {
+ id: In(ps.userIds),
+ isSuspended: false,
+ });
- // リクエストされた通りに並べ替え
- const _users: User[] = [];
- for (const id of ps.userIds) {
- _users.push(users.find(x => x.id === id)!);
- }
+ // リクエストされた通りに並べ替え
+ const _users: User[] = [];
+ for (const id of ps.userIds) {
+ _users.push(users.find(x => x.id === id)!);
+ }
- return await Promise.all(_users.map(u => Users.pack(u, me, {
- detail: true,
- })));
- } else {
- // Lookup user
- if (typeof ps.host === 'string' && typeof ps.username === 'string') {
- user = await resolveUser(ps.username, ps.host).catch(e => {
- apiLogger.warn(`failed to resolve remote user: ${e}`);
- throw new ApiError(meta.errors.failedToResolveRemoteUser);
- });
- } else {
- const q: FindOptionsWhere<User> = ps.userId != null
- ? { id: ps.userId }
- : { usernameLower: ps.username!.toLowerCase(), host: IsNull() };
+ return await Promise.all(_users.map(u => this.userEntityService.pack(u, me, {
+ detail: true,
+ })));
+ } else {
+ // Lookup user
+ if (typeof ps.host === 'string' && typeof ps.username === 'string') {
+ user = await this.resolveUserService.resolveUser(ps.username, ps.host).catch(err => {
+ this.apiLoggerService.logger.warn(`failed to resolve remote user: ${err}`);
+ throw new ApiError(meta.errors.failedToResolveRemoteUser);
+ });
+ } else {
+ const q: FindOptionsWhere<User> = ps.userId != null
+ ? { id: ps.userId }
+ : { usernameLower: ps.username!.toLowerCase(), host: IsNull() };
- user = await Users.findOneBy(q);
- }
+ user = await this.usersRepository.findOneBy(q);
+ }
- if (user == null || (!isAdminOrModerator && user.isSuspended)) {
- throw new ApiError(meta.errors.noSuchUser);
- }
+ if (user == null || (!isAdminOrModerator && user.isSuspended)) {
+ throw new ApiError(meta.errors.noSuchUser);
+ }
- return await Users.pack(user, me, {
- detail: true,
+ return await this.userEntityService.pack(user, me, {
+ detail: true,
+ });
+ }
});
}
-});
+}
diff --git a/packages/backend/src/server/api/endpoints/users/stats.ts b/packages/backend/src/server/api/endpoints/users/stats.ts
index 47f322ee9b..71f4ca0cfa 100644
--- a/packages/backend/src/server/api/endpoints/users/stats.ts
+++ b/packages/backend/src/server/api/endpoints/users/stats.ts
@@ -1,6 +1,8 @@
-import { DriveFiles, Followings, NoteFavorites, NoteReactions, Notes, PageLikes, PollVotes, Users } from '@/models/index.js';
-import { awaitAll } from '@/prelude/await-all.js';
-import define from '../../define.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { awaitAll } from '@/misc/prelude/await-all.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
+import { DI } from '@/di-symbols.js';
import { ApiError } from '../../error.js';
export const meta = {
@@ -116,78 +118,109 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, me) => {
- const user = await Users.findOneBy({ id: ps.userId });
- if (user == null) {
- throw new ApiError(meta.errors.noSuchUser);
- }
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
+
+ @Inject(DI.notesRepository)
+ private notesRepository: NotesRepository,
+
+ @Inject(DI.followingsRepository)
+ private followingsRepository: FollowingsRepository,
+
+ @Inject(DI.driveFilesRepository)
+ private driveFilesRepository: DriveFilesRepository,
+
+ @Inject(DI.noteReactionsRepository)
+ private noteReactionsRepository: NoteReactionsRepository,
- const result = await awaitAll({
- notesCount: Notes.createQueryBuilder('note')
- .where('note.userId = :userId', { userId: user.id })
- .getCount(),
- repliesCount: Notes.createQueryBuilder('note')
- .where('note.userId = :userId', { userId: user.id })
- .andWhere('note.replyId IS NOT NULL')
- .getCount(),
- renotesCount: Notes.createQueryBuilder('note')
- .where('note.userId = :userId', { userId: user.id })
- .andWhere('note.renoteId IS NOT NULL')
- .getCount(),
- repliedCount: Notes.createQueryBuilder('note')
- .where('note.replyUserId = :userId', { userId: user.id })
- .getCount(),
- renotedCount: Notes.createQueryBuilder('note')
- .where('note.renoteUserId = :userId', { userId: user.id })
- .getCount(),
- pollVotesCount: PollVotes.createQueryBuilder('vote')
- .where('vote.userId = :userId', { userId: user.id })
- .getCount(),
- pollVotedCount: PollVotes.createQueryBuilder('vote')
- .innerJoin('vote.note', 'note')
- .where('note.userId = :userId', { userId: user.id })
- .getCount(),
- localFollowingCount: Followings.createQueryBuilder('following')
- .where('following.followerId = :userId', { userId: user.id })
- .andWhere('following.followeeHost IS NULL')
- .getCount(),
- remoteFollowingCount: Followings.createQueryBuilder('following')
- .where('following.followerId = :userId', { userId: user.id })
- .andWhere('following.followeeHost IS NOT NULL')
- .getCount(),
- localFollowersCount: Followings.createQueryBuilder('following')
- .where('following.followeeId = :userId', { userId: user.id })
- .andWhere('following.followerHost IS NULL')
- .getCount(),
- remoteFollowersCount: Followings.createQueryBuilder('following')
- .where('following.followeeId = :userId', { userId: user.id })
- .andWhere('following.followerHost IS NOT NULL')
- .getCount(),
- sentReactionsCount: NoteReactions.createQueryBuilder('reaction')
- .where('reaction.userId = :userId', { userId: user.id })
- .getCount(),
- receivedReactionsCount: NoteReactions.createQueryBuilder('reaction')
- .innerJoin('reaction.note', 'note')
- .where('note.userId = :userId', { userId: user.id })
- .getCount(),
- noteFavoritesCount: NoteFavorites.createQueryBuilder('favorite')
- .where('favorite.userId = :userId', { userId: user.id })
- .getCount(),
- pageLikesCount: PageLikes.createQueryBuilder('like')
- .where('like.userId = :userId', { userId: user.id })
- .getCount(),
- pageLikedCount: PageLikes.createQueryBuilder('like')
- .innerJoin('like.page', 'page')
- .where('page.userId = :userId', { userId: user.id })
- .getCount(),
- driveFilesCount: DriveFiles.createQueryBuilder('file')
- .where('file.userId = :userId', { userId: user.id })
- .getCount(),
- driveUsage: DriveFiles.calcDriveUsageOf(user),
- });
+ @Inject(DI.pageLikesRepository)
+ private pageLikesRepository: PageLikesRepository,
- result.followingCount = result.localFollowingCount + result.remoteFollowingCount;
- result.followersCount = result.localFollowersCount + result.remoteFollowersCount;
+ @Inject(DI.noteFavoritesRepository)
+ private noteFavoritesRepository: NoteFavoritesRepository,
- return result;
-});
+ @Inject(DI.pollVotesRepository)
+ private pollVotesRepository: PollVotesRepository,
+
+ private driveFileEntityService: DriveFileEntityService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const user = await this.usersRepository.findOneBy({ id: ps.userId });
+ if (user == null) {
+ throw new ApiError(meta.errors.noSuchUser);
+ }
+
+ const result = await awaitAll({
+ notesCount: this.notesRepository.createQueryBuilder('note')
+ .where('note.userId = :userId', { userId: user.id })
+ .getCount(),
+ repliesCount: this.notesRepository.createQueryBuilder('note')
+ .where('note.userId = :userId', { userId: user.id })
+ .andWhere('note.replyId IS NOT NULL')
+ .getCount(),
+ renotesCount: this.notesRepository.createQueryBuilder('note')
+ .where('note.userId = :userId', { userId: user.id })
+ .andWhere('note.renoteId IS NOT NULL')
+ .getCount(),
+ repliedCount: this.notesRepository.createQueryBuilder('note')
+ .where('note.replyUserId = :userId', { userId: user.id })
+ .getCount(),
+ renotedCount: this.notesRepository.createQueryBuilder('note')
+ .where('note.renoteUserId = :userId', { userId: user.id })
+ .getCount(),
+ pollVotesCount: this.pollVotesRepository.createQueryBuilder('vote')
+ .where('vote.userId = :userId', { userId: user.id })
+ .getCount(),
+ pollVotedCount: this.pollVotesRepository.createQueryBuilder('vote')
+ .innerJoin('vote.note', 'note')
+ .where('note.userId = :userId', { userId: user.id })
+ .getCount(),
+ localFollowingCount: this.followingsRepository.createQueryBuilder('following')
+ .where('following.followerId = :userId', { userId: user.id })
+ .andWhere('following.followeeHost IS NULL')
+ .getCount(),
+ remoteFollowingCount: this.followingsRepository.createQueryBuilder('following')
+ .where('following.followerId = :userId', { userId: user.id })
+ .andWhere('following.followeeHost IS NOT NULL')
+ .getCount(),
+ localFollowersCount: this.followingsRepository.createQueryBuilder('following')
+ .where('following.followeeId = :userId', { userId: user.id })
+ .andWhere('following.followerHost IS NULL')
+ .getCount(),
+ remoteFollowersCount: this.followingsRepository.createQueryBuilder('following')
+ .where('following.followeeId = :userId', { userId: user.id })
+ .andWhere('following.followerHost IS NOT NULL')
+ .getCount(),
+ sentReactionsCount: this.noteReactionsRepository.createQueryBuilder('reaction')
+ .where('reaction.userId = :userId', { userId: user.id })
+ .getCount(),
+ receivedReactionsCount: this.noteReactionsRepository.createQueryBuilder('reaction')
+ .innerJoin('reaction.note', 'note')
+ .where('note.userId = :userId', { userId: user.id })
+ .getCount(),
+ noteFavoritesCount: this.noteFavoritesRepository.createQueryBuilder('favorite')
+ .where('favorite.userId = :userId', { userId: user.id })
+ .getCount(),
+ pageLikesCount: this.pageLikesRepository.createQueryBuilder('like')
+ .where('like.userId = :userId', { userId: user.id })
+ .getCount(),
+ pageLikedCount: this.pageLikesRepository.createQueryBuilder('like')
+ .innerJoin('like.page', 'page')
+ .where('page.userId = :userId', { userId: user.id })
+ .getCount(),
+ driveFilesCount: this.driveFilesRepository.createQueryBuilder('file')
+ .where('file.userId = :userId', { userId: user.id })
+ .getCount(),
+ driveUsage: this.driveFileEntityService.calcDriveUsageOf(user),
+ });
+
+ result.followingCount = result.localFollowingCount + result.remoteFollowingCount;
+ result.followersCount = result.localFollowersCount + result.remoteFollowersCount;
+
+ return result;
+ });
+ }
+}