summaryrefslogtreecommitdiff
path: root/packages/backend/src/server/api
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2023-04-13 12:18:07 +0900
committerGitHub <noreply@github.com>2023-04-13 12:18:07 +0900
commitc5d2dba28d1e99f9c152840ca7f47e8f51c7423b (patch)
tree8d4b388039b3f936e7dfb8914efa827bf026b110 /packages/backend/src/server/api
parentMerge pull request #10606 from misskey-dev/EbiseLutica-patch-1 (diff)
parent[ci skip] improve readability (diff)
downloadmisskey-c5d2dba28d1e99f9c152840ca7f47e8f51c7423b.tar.gz
misskey-c5d2dba28d1e99f9c152840ca7f47e8f51c7423b.tar.bz2
misskey-c5d2dba28d1e99f9c152840ca7f47e8f51c7423b.zip
Merge pull request #10608 from misskey-dev/develop
Release: 13.11.3
Diffstat (limited to 'packages/backend/src/server/api')
-rw-r--r--packages/backend/src/server/api/EndpointsModule.ts4
-rw-r--r--packages/backend/src/server/api/endpoints.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts8
-rw-r--r--packages/backend/src/server/api/endpoints/admin/suspend-user.ts35
-rw-r--r--packages/backend/src/server/api/endpoints/antennas/notes.ts9
-rw-r--r--packages/backend/src/server/api/endpoints/i/notifications.ts3
-rw-r--r--packages/backend/src/server/api/endpoints/roles/notes.ts109
-rw-r--r--packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts2
-rw-r--r--packages/backend/src/server/api/stream/ChannelsService.ts3
-rw-r--r--packages/backend/src/server/api/stream/channels/role-timeline.ts75
-rw-r--r--packages/backend/src/server/api/stream/types.ts10
11 files changed, 230 insertions, 30 deletions
diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts
index ca89d82853..689f90287e 100644
--- a/packages/backend/src/server/api/EndpointsModule.ts
+++ b/packages/backend/src/server/api/EndpointsModule.ts
@@ -294,6 +294,7 @@ import * as ep___promo_read from './endpoints/promo/read.js';
import * as ep___roles_list from './endpoints/roles/list.js';
import * as ep___roles_show from './endpoints/roles/show.js';
import * as ep___roles_users from './endpoints/roles/users.js';
+import * as ep___roles_notes from './endpoints/roles/notes.js';
import * as ep___requestResetPassword from './endpoints/request-reset-password.js';
import * as ep___resetDb from './endpoints/reset-db.js';
import * as ep___resetPassword from './endpoints/reset-password.js';
@@ -628,6 +629,7 @@ const $promo_read: Provider = { provide: 'ep:promo/read', useClass: ep___promo_r
const $roles_list: Provider = { provide: 'ep:roles/list', useClass: ep___roles_list.default };
const $roles_show: Provider = { provide: 'ep:roles/show', useClass: ep___roles_show.default };
const $roles_users: Provider = { provide: 'ep:roles/users', useClass: ep___roles_users.default };
+const $roles_notes: Provider = { provide: 'ep:roles/notes', useClass: ep___roles_notes.default };
const $requestResetPassword: Provider = { provide: 'ep:request-reset-password', useClass: ep___requestResetPassword.default };
const $resetDb: Provider = { provide: 'ep:reset-db', useClass: ep___resetDb.default };
const $resetPassword: Provider = { provide: 'ep:reset-password', useClass: ep___resetPassword.default };
@@ -966,6 +968,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$roles_list,
$roles_show,
$roles_users,
+ $roles_notes,
$requestResetPassword,
$resetDb,
$resetPassword,
@@ -1298,6 +1301,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$roles_list,
$roles_show,
$roles_users,
+ $roles_notes,
$requestResetPassword,
$resetDb,
$resetPassword,
diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts
index dab897117d..d0fe6a57c1 100644
--- a/packages/backend/src/server/api/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints.ts
@@ -294,6 +294,7 @@ import * as ep___promo_read from './endpoints/promo/read.js';
import * as ep___roles_list from './endpoints/roles/list.js';
import * as ep___roles_show from './endpoints/roles/show.js';
import * as ep___roles_users from './endpoints/roles/users.js';
+import * as ep___roles_notes from './endpoints/roles/notes.js';
import * as ep___requestResetPassword from './endpoints/request-reset-password.js';
import * as ep___resetDb from './endpoints/reset-db.js';
import * as ep___resetPassword from './endpoints/reset-password.js';
@@ -626,6 +627,7 @@ const eps = [
['roles/list', ep___roles_list],
['roles/show', ep___roles_show],
['roles/users', ep___roles_users],
+ ['roles/notes', ep___roles_notes],
['request-reset-password', ep___requestResetPassword],
['reset-db', ep___resetDb],
['reset-password', ep___resetPassword],
diff --git a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts
index b073209a5b..54ce095488 100644
--- a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts
+++ b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts
@@ -1,8 +1,8 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { FollowingsRepository, UsersRepository } from '@/models/index.js';
-import { UserFollowingService } from '@/core/UserFollowingService.js';
import { DI } from '@/di-symbols.js';
+import { QueueService } from '@/core/QueueService.js';
export const meta = {
tags: ['admin'],
@@ -29,7 +29,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
@Inject(DI.notesRepository)
private followingsRepository: FollowingsRepository,
- private userFollowingService: UserFollowingService,
+ private queueService: QueueService,
) {
super(meta, paramDef, async (ps, me) => {
const followings = await this.followingsRepository.findBy({
@@ -41,9 +41,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
this.usersRepository.findOneByOrFail({ id: f.followeeId }),
])));
- for (const pair of pairs) {
- this.userFollowingService.unfollow(pair[0], pair[1]);
- }
+ this.queueService.createUnfollowJob(pairs.map(p => ({ to: p[0], from: p[1], silent: true })));
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts
index 3c99225272..eabbceac0e 100644
--- a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts
+++ b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts
@@ -1,15 +1,15 @@
+import { IsNull, Not } from 'typeorm';
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { UsersRepository, FollowingsRepository } from '@/models/index.js';
import type { User } from '@/models/entities/User.js';
-import { GlobalEventService } from '@/core/GlobalEventService.js';
+import type { RelationshipJobData } from '@/queue/types.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import { UserSuspendService } from '@/core/UserSuspendService.js';
-import { UserFollowingService } from '@/core/UserFollowingService.js';
import { DI } from '@/di-symbols.js';
-import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js';
import { RoleService } from '@/core/RoleService.js';
+import { QueueService } from '@/core/QueueService.js';
export const meta = {
tags: ['admin'],
@@ -36,12 +36,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
@Inject(DI.followingsRepository)
private followingsRepository: FollowingsRepository,
- private userEntityService: UserEntityService,
- private userFollowingService: UserFollowingService,
private userSuspendService: UserSuspendService,
private roleService: RoleService,
private moderationLogService: ModerationLogService,
- private globalEventService: GlobalEventService,
+ private queueService: QueueService,
) {
super(meta, paramDef, async (ps, me) => {
const user = await this.usersRepository.findOneBy({ id: ps.userId });
@@ -71,20 +69,23 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
@bindThis
private async unFollowAll(follower: User) {
- const followings = await this.followingsRepository.findBy({
- followerId: follower.id,
+ const followings = await this.followingsRepository.find({
+ where: {
+ followerId: follower.id,
+ followeeId: Not(IsNull()),
+ },
});
-
+
+ const jobs: RelationshipJobData[] = [];
for (const following of followings) {
- const followee = await this.usersRepository.findOneBy({
- id: following.followeeId,
- });
-
- if (followee == null) {
- throw `Cant find followee ${following.followeeId}`;
+ if (following.followeeId && following.followerId) {
+ jobs.push({
+ from: { id: following.followerId },
+ to: { id: following.followeeId },
+ silent: true,
+ });
}
-
- await this.userFollowingService.unfollow(follower, followee, true);
}
+ this.queueService.createUnfollowJob(jobs);
}
}
diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts
index f08c20ae48..88623ce26a 100644
--- a/packages/backend/src/server/api/endpoints/antennas/notes.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts
@@ -76,17 +76,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
throw new ApiError(meta.errors.noSuchAntenna);
}
+ const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1
const noteIdsRes = await this.redisClient.xrevrange(
`antennaTimeline:${antenna.id}`,
- ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : '+',
- '-',
- 'COUNT', ps.limit + 1); // untilIdに指定したものも含まれるため+1
+ ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+',
+ ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-',
+ 'COUNT', limit);
if (noteIdsRes.length === 0) {
return [];
}
- const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId);
+ const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId);
if (noteIds.length === 0) {
return [];
diff --git a/packages/backend/src/server/api/endpoints/i/notifications.ts b/packages/backend/src/server/api/endpoints/i/notifications.ts
index f27b4e86d4..ba0487f223 100644
--- a/packages/backend/src/server/api/endpoints/i/notifications.ts
+++ b/packages/backend/src/server/api/endpoints/i/notifications.ts
@@ -91,11 +91,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
const includeTypes = ps.includeTypes && ps.includeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof notificationTypes[number][];
const excludeTypes = ps.excludeTypes && ps.excludeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof notificationTypes[number][];
+ const limit = ps.limit + (ps.untilId ? 1 : 0); // untilIdに指定したものも含まれるため+1
const notificationsRes = await this.redisClient.xrevrange(
`notificationTimeline:${me.id}`,
ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : '+',
'-',
- 'COUNT', ps.limit + 1); // untilIdに指定したものも含まれるため+1
+ 'COUNT', limit);
if (notificationsRes.length === 0) {
return [];
diff --git a/packages/backend/src/server/api/endpoints/roles/notes.ts b/packages/backend/src/server/api/endpoints/roles/notes.ts
new file mode 100644
index 0000000000..b45d4af1fe
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/roles/notes.ts
@@ -0,0 +1,109 @@
+import { Inject, Injectable } from '@nestjs/common';
+import Redis from 'ioredis';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import type { NotesRepository, RolesRepository } from '@/models/index.js';
+import { QueryService } from '@/core/QueryService.js';
+import { DI } from '@/di-symbols.js';
+import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
+import { IdService } from '@/core/IdService.js';
+import { ApiError } from '../../error.js';
+
+export const meta = {
+ tags: ['role', 'notes'],
+
+ requireCredential: true,
+
+ errors: {
+ noSuchRole: {
+ message: 'No such role.',
+ code: 'NO_SUCH_ROLE',
+ id: 'eb70323a-df61-4dd4-ad90-89c83c7cf26e',
+ },
+ },
+
+ res: {
+ type: 'array',
+ optional: false, nullable: false,
+ items: {
+ type: 'object',
+ optional: false, nullable: false,
+ ref: 'Note',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ roleId: { type: 'string', format: 'misskey:id' },
+ limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+ sinceId: { type: 'string', format: 'misskey:id' },
+ untilId: { type: 'string', format: 'misskey:id' },
+ sinceDate: { type: 'integer' },
+ untilDate: { type: 'integer' },
+ },
+ required: ['roleId'],
+} as const;
+
+// eslint-disable-next-line import/no-default-export
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.redis)
+ private redisClient: Redis.Redis,
+
+ @Inject(DI.notesRepository)
+ private notesRepository: NotesRepository,
+
+ @Inject(DI.rolesRepository)
+ private rolesRepository: RolesRepository,
+
+ private idService: IdService,
+ private noteEntityService: NoteEntityService,
+ private queryService: QueryService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const role = await this.rolesRepository.findOneBy({
+ id: ps.roleId,
+ });
+
+ if (role == null) {
+ throw new ApiError(meta.errors.noSuchRole);
+ }
+
+ const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1
+ const noteIdsRes = await this.redisClient.xrevrange(
+ `roleTimeline:${role.id}`,
+ ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+',
+ ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-',
+ 'COUNT', limit);
+
+ if (noteIdsRes.length === 0) {
+ return [];
+ }
+
+ const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId);
+
+ if (noteIds.length === 0) {
+ return [];
+ }
+
+ const query = this.notesRepository.createQueryBuilder('note')
+ .where('note.id IN (:...noteIds)', { noteIds: noteIds })
+ .innerJoinAndSelect('note.user', 'user')
+ .leftJoinAndSelect('note.reply', 'reply')
+ .leftJoinAndSelect('note.renote', 'renote')
+ .leftJoinAndSelect('reply.user', 'replyUser')
+ .leftJoinAndSelect('renote.user', 'renoteUser');
+
+ this.queryService.generateVisibilityQuery(query, me);
+ this.queryService.generateMutedUserQuery(query, me);
+ this.queryService.generateBlockedUserQuery(query, me);
+
+ const notes = await query.getMany();
+ notes.sort((a, b) => a.id > b.id ? -1 : 1);
+
+ return await this.noteEntityService.packMany(notes, me);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts
index 6c340d8fb2..b001159ee8 100644
--- a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts
+++ b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts
@@ -41,8 +41,6 @@ export const paramDef = {
],
} as const;
-// TODO: avatar,bannerをJOINしたいけどエラーになる
-
// eslint-disable-next-line import/no-default-export
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> {
diff --git a/packages/backend/src/server/api/stream/ChannelsService.ts b/packages/backend/src/server/api/stream/ChannelsService.ts
index f9ef8218c1..c77ba66028 100644
--- a/packages/backend/src/server/api/stream/ChannelsService.ts
+++ b/packages/backend/src/server/api/stream/ChannelsService.ts
@@ -13,6 +13,7 @@ import { UserListChannelService } from './channels/user-list.js';
import { AntennaChannelService } from './channels/antenna.js';
import { DriveChannelService } from './channels/drive.js';
import { HashtagChannelService } from './channels/hashtag.js';
+import { RoleTimelineChannelService } from './channels/role-timeline.js';
@Injectable()
export class ChannelsService {
@@ -24,6 +25,7 @@ export class ChannelsService {
private globalTimelineChannelService: GlobalTimelineChannelService,
private userListChannelService: UserListChannelService,
private hashtagChannelService: HashtagChannelService,
+ private roleTimelineChannelService: RoleTimelineChannelService,
private antennaChannelService: AntennaChannelService,
private channelChannelService: ChannelChannelService,
private driveChannelService: DriveChannelService,
@@ -43,6 +45,7 @@ export class ChannelsService {
case 'globalTimeline': return this.globalTimelineChannelService;
case 'userList': return this.userListChannelService;
case 'hashtag': return this.hashtagChannelService;
+ case 'roleTimeline': return this.roleTimelineChannelService;
case 'antenna': return this.antennaChannelService;
case 'channel': return this.channelChannelService;
case 'drive': return this.driveChannelService;
diff --git a/packages/backend/src/server/api/stream/channels/role-timeline.ts b/packages/backend/src/server/api/stream/channels/role-timeline.ts
new file mode 100644
index 0000000000..9d106c8b2f
--- /dev/null
+++ b/packages/backend/src/server/api/stream/channels/role-timeline.ts
@@ -0,0 +1,75 @@
+import { Injectable } from '@nestjs/common';
+import { isUserRelated } from '@/misc/is-user-related.js';
+import type { Packed } from '@/misc/json-schema.js';
+import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
+import { bindThis } from '@/decorators.js';
+import Channel from '../channel.js';
+import { StreamMessages } from '../types.js';
+
+class RoleTimelineChannel extends Channel {
+ public readonly chName = 'roleTimeline';
+ public static shouldShare = false;
+ public static requireCredential = false;
+ private roleId: string;
+
+ constructor(
+ private noteEntityService: NoteEntityService,
+
+ id: string,
+ connection: Channel['connection'],
+ ) {
+ super(id, connection);
+ //this.onNote = this.onNote.bind(this);
+ }
+
+ @bindThis
+ public async init(params: any) {
+ this.roleId = params.roleId as string;
+
+ this.subscriber.on(`roleTimelineStream:${this.roleId}`, this.onEvent);
+ }
+
+ @bindThis
+ private async onEvent(data: StreamMessages['roleTimeline']['payload']) {
+ if (data.type === 'note') {
+ const note = data.body;
+
+ // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
+ if (isUserRelated(note, this.userIdsWhoMeMuting)) return;
+ // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
+ if (isUserRelated(note, this.userIdsWhoBlockingMe)) return;
+
+ if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
+
+ this.send('note', note);
+ } else {
+ this.send(data.type, data.body);
+ }
+ }
+
+ @bindThis
+ public dispose() {
+ // Unsubscribe events
+ this.subscriber.off(`roleTimelineStream:${this.roleId}`, this.onEvent);
+ }
+}
+
+@Injectable()
+export class RoleTimelineChannelService {
+ public readonly shouldShare = RoleTimelineChannel.shouldShare;
+ public readonly requireCredential = RoleTimelineChannel.requireCredential;
+
+ constructor(
+ private noteEntityService: NoteEntityService,
+ ) {
+ }
+
+ @bindThis
+ public create(id: string, connection: Channel['connection']): RoleTimelineChannel {
+ return new RoleTimelineChannel(
+ this.noteEntityService,
+ id,
+ connection,
+ );
+ }
+}
diff --git a/packages/backend/src/server/api/stream/types.ts b/packages/backend/src/server/api/stream/types.ts
index ed73897e73..d9dba682cd 100644
--- a/packages/backend/src/server/api/stream/types.ts
+++ b/packages/backend/src/server/api/stream/types.ts
@@ -148,6 +148,10 @@ export interface AntennaStreamTypes {
note: Note;
}
+export interface RoleTimelineStreamTypes {
+ note: Packed<'Note'>;
+}
+
export interface AdminStreamTypes {
newAbuseUserReport: {
id: AbuseUserReport['id'];
@@ -168,7 +172,7 @@ type EventUnionFromDictionary<
> = U[keyof U];
// redis通すとDateのインスタンスはstringに変換されるので
-type Serialized<T> = {
+export type Serialized<T> = {
[K in keyof T]:
T[K] extends Date
? string
@@ -209,6 +213,10 @@ export type StreamMessages = {
name: `userListStream:${UserList['id']}`;
payload: EventUnionFromDictionary<SerializedAll<UserListStreamTypes>>;
};
+ roleTimeline: {
+ name: `roleTimelineStream:${Role['id']}`;
+ payload: EventUnionFromDictionary<SerializedAll<RoleTimelineStreamTypes>>;
+ };
antenna: {
name: `antennaStream:${Antenna['id']}`;
payload: EventUnionFromDictionary<SerializedAll<AntennaStreamTypes>>;