なんかもうめっちゃ変えた

This commit is contained in:
syuilo
2022-09-18 03:27:08 +09:00
committed by GitHub
parent d9ab03f086
commit b75184ec8e
946 changed files with 41219 additions and 28839 deletions

View File

@@ -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);
});
}
}

View File

@@ -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,
const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
@Inject(DI.followingsRepository)
private followingsRepository: FollowingsRepository,
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);
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 (user == null) {
throw new ApiError(meta.errors.noSuchUser);
}
}
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
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);
}
}
}
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 });
});
}
const query = makePaginationQuery(Followings.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 Followings.packMany(followings, me, { populateFollower: true });
});
}

View File

@@ -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,
const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
@Inject(DI.followingsRepository)
private followingsRepository: FollowingsRepository,
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);
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 (user == null) {
throw new ApiError(meta.errors.noSuchUser);
}
}
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
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);
}
}
}
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 });
});
}
const query = makePaginationQuery(Followings.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 Followings.packMany(followings, me, { populateFollowee: true });
});
}

View File

@@ -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);
});
}
}

View File

@@ -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;
});
// Fetch recent notes
const recentNotes = await this.notesRepository.find({
where: {
userId: user.id,
replyId: Not(IsNull()),
},
order: {
id: -1,
},
take: 1000,
select: ['replyId'],
});
// 投稿が少なかったら中断
if (recentNotes.length === 0) {
return [];
}
// TODO ミュートを考慮
const replyTargetNotes = await this.notesRepository.find({
where: {
id: In(recentNotes.map(p => p.replyId)),
},
select: ['userId'],
});
const repliedUsers: any = {};
// Extract replies from recent notes
for (const userId of replyTargetNotes.map(x => x.userId.toString())) {
if (repliedUsers[userId]) {
repliedUsers[userId]++;
} else {
repliedUsers[userId] = 1;
}
}
// Calc peak
const peak = maximum(Object.values(repliedUsers));
// Sort replies by frequency
const repliedUsersSorted = Object.keys(repliedUsers).sort((a, b) => repliedUsers[b] - repliedUsers[a]);
// Extract top replied users
const topRepliedUsers = repliedUsersSorted.slice(0, ps.limit);
// 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;
});
}
// TODO ミュートを考慮
const replyTargetNotes = await Notes.find({
where: {
id: In(recentNotes.map(p => p.replyId)),
},
select: ['userId'],
});
const repliedUsers: any = {};
// Extract replies from recent notes
for (const userId of replyTargetNotes.map(x => x.userId.toString())) {
if (repliedUsers[userId]) {
repliedUsers[userId]++;
} else {
repliedUsers[userId] = 1;
}
}
// Calc peak
const peak = maximum(Object.values(repliedUsers));
// Sort replies by frequency
const repliedUsersSorted = Object.keys(repliedUsers).sort((a, b) => repliedUsers[b] - repliedUsers[a]);
// Extract top replied users
const topRepliedUsers = repliedUsersSorted.slice(0, ps.limit);
// 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,
})));
return repliesObj;
});
}

View File

@@ -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);
});
}
}

View File

@@ -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 this.userGroupsRepository.delete(userGroup.id);
});
}
await UserGroups.delete(userGroup.id);
});
}

View File

@@ -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,
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);
}
if (invitation.userId !== me.id) {
throw new ApiError(meta.errors.noSuchInvitation);
}
// 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);
});
}
if (invitation.userId !== user.id) {
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);
UserGroupInvitations.delete(invitation.id);
});
}

View File

@@ -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 !== me.id) {
throw new ApiError(meta.errors.noSuchInvitation);
}
await this.userGroupInvitationsRepository.delete(invitation.id);
});
}
if (invitation.userId !== user.id) {
throw new ApiError(meta.errors.noSuchInvitation);
}
await UserGroupInvitations.delete(invitation.id);
});
}

View File

@@ -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,
@Inject(DI.userGroupJoiningsRepository)
private userGroupJoiningsRepository: UserGroupJoiningsRepository,
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 (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;
});
const joining = await this.userGroupJoiningsRepository.findOneBy({
userGroupId: userGroup.id,
userId: user.id,
});
if (joining) {
throw new ApiError(meta.errors.alreadyAdded);
}
const existInvitation = await this.userGroupInvitationsRepository.findOneBy({
userGroupId: userGroup.id,
userId: user.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,
});
});
}
// 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;
});
const joining = await UserGroupJoinings.findOneBy({
userGroupId: userGroup.id,
userId: user.id,
});
if (joining) {
throw new ApiError(meta.errors.alreadyAdded);
}
const existInvitation = await UserGroupInvitations.findOneBy({
userGroupId: userGroup.id,
userId: user.id,
});
if (existInvitation) {
throw new ApiError(meta.errors.alreadyInvited);
}
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]));
// 通知を作成
createNotification(user.id, 'groupInvited', {
notifierId: me.id,
userGroupInvitationId: invitation.id,
});
});
}

View File

@@ -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)));
});
}
}

View File

@@ -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 (userGroup == null) {
throw new ApiError(meta.errors.noSuchGroup);
}
if (me.id === userGroup.userId) {
throw new ApiError(meta.errors.youAreOwner);
}
await this.userGroupJoiningsRepository.delete({ userGroupId: userGroup.id, userId: me.id });
});
}
if (me.id === userGroup.userId) {
throw new ApiError(meta.errors.youAreOwner);
}
await UserGroupJoinings.delete({ userGroupId: userGroup.id, userId: me.id });
});
}

View File

@@ -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)));
});
}
}

View File

@@ -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,
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 (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;
});
if (user.id === userGroup.userId) {
throw new ApiError(meta.errors.isOwner);
}
// Pull the user
await this.userGroupJoiningsRepository.delete({ userGroupId: userGroup.id, userId: user.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 (user.id === userGroup.userId) {
throw new ApiError(meta.errors.isOwner);
}
// Pull the user
await UserGroupJoinings.delete({ userGroupId: userGroup.id, userId: user.id });
});
}

View File

@@ -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,
private userGroupEntityService: UserGroupEntityService,
) {
super(meta, paramDef, async (ps, me) => {
// Fetch the group
const userGroup = await this.userGroupsRepository.findOneBy({
id: ps.groupId,
});
if (userGroup == null) {
throw new ApiError(meta.errors.noSuchGroup);
}
const joining = await this.userGroupJoiningsRepository.findOneBy({
userId: me.id,
userGroupId: userGroup.id,
});
if (joining == null && userGroup.userId !== me.id) {
throw new ApiError(meta.errors.noSuchGroup);
}
return await this.userGroupEntityService.pack(userGroup);
});
}
const joining = await UserGroupJoinings.findOneBy({
userId: me.id,
userGroupId: userGroup.id,
});
if (joining == null && userGroup.userId !== me.id) {
throw new ApiError(meta.errors.noSuchGroup);
}
return await UserGroups.pack(userGroup);
});
}

View File

@@ -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,
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,
});
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;
});
const joining = await this.userGroupJoiningsRepository.findOneBy({
userGroupId: userGroup.id,
userId: user.id,
});
if (joining == null) {
throw new ApiError(meta.errors.noSuchGroupMember);
}
await this.userGroupsRepository.update(userGroup.id, {
userId: ps.userId,
});
return await this.userGroupEntityService.pack(userGroup.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;
});
const joining = await UserGroupJoinings.findOneBy({
userGroupId: userGroup.id,
userId: user.id,
});
if (joining == null) {
throw new ApiError(meta.errors.noSuchGroupMember);
}
await UserGroups.update(userGroup.id, {
userId: ps.userId,
});
return await UserGroups.pack(userGroup.id);
});
}

View File

@@ -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 this.userGroupsRepository.update(userGroup.id, {
name: ps.name,
});
return await this.userGroupEntityService.pack(userGroup.id);
});
}
await UserGroups.update(userGroup.id, {
name: ps.name,
});
return await UserGroups.pack(userGroup.id);
});
}

View File

@@ -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);
});
}
}

View File

@@ -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 this.userListsRepository.delete(userList.id);
});
}
await UserLists.delete(userList.id);
});
}

View File

@@ -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)));
});
}
}

View File

@@ -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,
});
if (userList == null) {
throw new ApiError(meta.errors.noSuchList);
}
// 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 this.userListJoiningsRepository.delete({ userListId: userList.id, userId: user.id });
this.globalEventService.publishUserListStream(userList.id, 'userRemoved', await this.userEntityService.pack(user));
});
}
// 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;
});
// Pull the user
await UserListJoinings.delete({ userListId: userList.id, userId: user.id });
publishUserListStream(userList.id, 'userRemoved', await Users.pack(user));
});
}

View File

@@ -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,
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,
});
if (userList == null) {
throw new ApiError(meta.errors.noSuchList);
}
// 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);
}
}
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);
});
if (block) {
throw new ApiError(meta.errors.youHaveBeenBlocked);
}
}
const exist = await UserListJoinings.findOneBy({
userListId: userList.id,
userId: user.id,
});
if (exist) {
throw new ApiError(meta.errors.alreadyAdded);
}
// Push the user
await pushUserToUserList(user, userList);
});
}

View File

@@ -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 this.userListEntityService.pack(userList);
});
}
return await UserLists.pack(userList);
});
}

View File

@@ -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 this.userListsRepository.update(userList.id, {
name: ps.name,
});
return await this.userListEntityService.pack(userList.id);
});
}
await UserLists.update(userList.id, {
name: ps.name,
});
return await UserLists.pack(userList.id);
});
}

View File

@@ -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 != \'{}\'');
}
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 });
this.queryService.generateVisibilityQuery(query, me);
if (me) {
this.queryService.generateMutedUserQuery(query, me, user);
this.queryService.generateBlockedUserQuery(query, me);
}
}));
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.withFiles) {
query.andWhere('note.fileIds != \'{}\'');
}
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.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.includeReplies) {
query.andWhere('note.replyId IS NULL');
}
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)');
}));
}
//#endregion
const timeline = await query.take(ps.limit).getMany();
return await this.noteEntityService.packMany(timeline, me);
});
}
if (!ps.includeReplies) {
query.andWhere('note.replyId IS NULL');
}
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)');
}));
}
//#endregion
const timeline = await query.take(ps.limit).getMany();
return await Notes.packMany(timeline, me);
});
}

View File

@@ -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);
});
}
}

View File

@@ -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 });
if (me == null || (me.id !== ps.userId && !profile.publicReactions)) {
throw new ApiError(meta.errors.reactionsNotPublic);
}
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');
this.queryService.generateVisibilityQuery(query, me);
const reactions = await query
.take(ps.limit)
.getMany();
return await Promise.all(reactions.map(reaction => this.noteReactionEntityService.pack(reaction, me, { withNote: true })));
});
}
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');
generateVisibilityQuery(query, me);
const reactions = await query
.take(ps.limit)
.getMany();
return await Promise.all(reactions.map(reaction => NoteReactions.pack(reaction, me, { withNote: true })));
});
}

View File

@@ -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 });
});
}
}

View File

@@ -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];
});
}
}

View File

@@ -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);
}
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]));
// Publish event to moderators
setImmediate(async () => {
const moderators = await Users.find({
where: [{
isAdmin: true,
}, {
isModerator: true,
}],
});
for (const moderator of moderators) {
publishAdminStream(moderator.id, 'newAbuseUserReport', {
id: report.id,
targetUserId: report.targetUserId,
reporterId: report.reporterId,
comment: report.comment,
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 meta = await fetchMeta();
if (meta.email) {
sendEmail(meta.email, 'New abuse report',
sanitizeHtml(ps.comment),
sanitizeHtml(ps.comment));
}
});
});
if (user.id === me.id) {
throw new ApiError(meta.errors.cannotReportYourself);
}
if (user.isAdmin) {
throw new ApiError(meta.errors.cannotReportAdmin);
}
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]));
// 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));
}
});
});
}
}

View File

@@ -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 [];
});
}

View File

@@ -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();
if (users.length < ps.limit) {
const profQuery = this.userProfilesRepository.createQueryBuilder('prof')
.select('prof.userId')
.where('prof.description ILIKE :query', { query: '%' + ps.query + '%' });
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());
users = users.concat(await query
.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());
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 });
});
}
return await Users.packMany(users, me, { detail: ps.detail });
});
}

View File

@@ -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: User[] = [];
for (const id of ps.userIds) {
_users.push(users.find(x => x.id === id)!);
}
const users = await this.usersRepository.findBy(isAdminOrModerator ? {
id: In(ps.userIds),
} : {
id: In(ps.userIds),
isSuspended: false,
});
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() };
// リクエストされた通りに並べ替え
const _users: User[] = [];
for (const id of ps.userIds) {
_users.push(users.find(x => x.id === id)!);
}
user = await Users.findOneBy(q);
}
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() };
if (user == null || (!isAdminOrModerator && user.isSuspended)) {
throw new ApiError(meta.errors.noSuchUser);
}
user = await this.usersRepository.findOneBy(q);
}
return await Users.pack(user, me, {
detail: true,
if (user == null || (!isAdminOrModerator && user.isSuspended)) {
throw new ApiError(meta.errors.noSuchUser);
}
return await this.userEntityService.pack(user, me, {
detail: true,
});
}
});
}
});
}

View File

@@ -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,
@Inject(DI.pageLikesRepository)
private pageLikesRepository: PageLikesRepository,
@Inject(DI.noteFavoritesRepository)
private noteFavoritesRepository: NoteFavoritesRepository,
@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;
});
}
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),
});
result.followingCount = result.localFollowingCount + result.remoteFollowingCount;
result.followersCount = result.localFollowersCount + result.remoteFollowersCount;
return result;
});
}