Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop
This commit is contained in:
		| @@ -131,6 +131,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
|  | ||||
| 				shouldFallbackToDb = shouldFallbackToDb || (noteIds.length === 0); | ||||
|  | ||||
| 				let redisTimeline: MiNote[] = []; | ||||
|  | ||||
| 				if (!shouldFallbackToDb) { | ||||
| 					const query = this.notesRepository.createQueryBuilder('note') | ||||
| 						.where('note.id IN (:...noteIds)', { noteIds: noteIds }) | ||||
| @@ -141,9 +143,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 						.leftJoinAndSelect('renote.user', 'renoteUser') | ||||
| 						.leftJoinAndSelect('note.channel', 'channel'); | ||||
|  | ||||
| 					let timeline = await query.getMany(); | ||||
| 					redisTimeline = await query.getMany(); | ||||
|  | ||||
| 					timeline = timeline.filter(note => { | ||||
| 					redisTimeline = redisTimeline.filter(note => { | ||||
| 						if (note.userId === me.id) { | ||||
| 							return true; | ||||
| 						} | ||||
| @@ -159,15 +161,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 						return true; | ||||
| 					}); | ||||
|  | ||||
| 					// TODO: フィルタした結果件数が足りなかった場合の対応 | ||||
|  | ||||
| 					timeline.sort((a, b) => a.id > b.id ? -1 : 1); | ||||
| 					redisTimeline.sort((a, b) => a.id > b.id ? -1 : 1); | ||||
| 				} | ||||
|  | ||||
| 				if (redisTimeline.length > 0) { | ||||
| 					process.nextTick(() => { | ||||
| 						this.activeUsersChart.read(me); | ||||
| 					}); | ||||
|  | ||||
| 					return await this.noteEntityService.packMany(timeline, me); | ||||
| 					return await this.noteEntityService.packMany(redisTimeline, me); | ||||
| 				} else { // fallback to db | ||||
| 					return await this.getFromDb({ | ||||
| 						untilId, | ||||
|   | ||||
| @@ -110,6 +110,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
|  | ||||
| 				noteIds = noteIds.slice(0, ps.limit); | ||||
|  | ||||
| 				let redisTimeline: MiNote[] = []; | ||||
|  | ||||
| 				if (noteIds.length > 0) { | ||||
| 					const query = this.notesRepository.createQueryBuilder('note') | ||||
| 						.where('note.id IN (:...noteIds)', { noteIds: noteIds }) | ||||
| @@ -120,9 +122,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 						.leftJoinAndSelect('renote.user', 'renoteUser') | ||||
| 						.leftJoinAndSelect('note.channel', 'channel'); | ||||
|  | ||||
| 					let timeline = await query.getMany(); | ||||
| 					redisTimeline = await query.getMany(); | ||||
|  | ||||
| 					timeline = timeline.filter(note => { | ||||
| 					redisTimeline = redisTimeline.filter(note => { | ||||
| 						if (me && (note.userId === me.id)) { | ||||
| 							return true; | ||||
| 						} | ||||
| @@ -139,17 +141,17 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 						return true; | ||||
| 					}); | ||||
|  | ||||
| 					// TODO: フィルタした結果件数が足りなかった場合の対応 | ||||
|  | ||||
| 					timeline.sort((a, b) => a.id > b.id ? -1 : 1); | ||||
| 					redisTimeline.sort((a, b) => a.id > b.id ? -1 : 1); | ||||
| 				} | ||||
|  | ||||
| 				if (redisTimeline.length > 0) { | ||||
| 					process.nextTick(() => { | ||||
| 						if (me) { | ||||
| 							this.activeUsersChart.read(me); | ||||
| 						} | ||||
| 					}); | ||||
|  | ||||
| 					return await this.noteEntityService.packMany(timeline, me); | ||||
| 					return await this.noteEntityService.packMany(redisTimeline, me); | ||||
| 				} else { // fallback to db | ||||
| 					return await this.getFromDb({ | ||||
| 						untilId, | ||||
|   | ||||
| @@ -5,7 +5,7 @@ | ||||
|  | ||||
| import { Brackets } from 'typeorm'; | ||||
| import { Inject, Injectable } from '@nestjs/common'; | ||||
| import type { NotesRepository } from '@/models/_.js'; | ||||
| import type { MiNote, NotesRepository } from '@/models/_.js'; | ||||
| import { Endpoint } from '@/server/api/endpoint-base.js'; | ||||
| import { QueryService } from '@/core/QueryService.js'; | ||||
| import ActiveUsersChart from '@/core/chart/charts/active-users.js'; | ||||
| @@ -89,6 +89,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 				let noteIds = await this.funoutTimelineService.get(ps.withFiles ? `homeTimelineWithFiles:${me.id}` : `homeTimeline:${me.id}`, untilId, sinceId); | ||||
| 				noteIds = noteIds.slice(0, ps.limit); | ||||
|  | ||||
| 				let redisTimeline: MiNote[] = []; | ||||
|  | ||||
| 				if (noteIds.length > 0) { | ||||
| 					const query = this.notesRepository.createQueryBuilder('note') | ||||
| 						.where('note.id IN (:...noteIds)', { noteIds: noteIds }) | ||||
| @@ -99,9 +101,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 						.leftJoinAndSelect('renote.user', 'renoteUser') | ||||
| 						.leftJoinAndSelect('note.channel', 'channel'); | ||||
|  | ||||
| 					let timeline = await query.getMany(); | ||||
| 					redisTimeline = await query.getMany(); | ||||
|  | ||||
| 					timeline = timeline.filter(note => { | ||||
| 					redisTimeline = redisTimeline.filter(note => { | ||||
| 						if (note.userId === me.id) { | ||||
| 							return true; | ||||
| 						} | ||||
| @@ -120,15 +122,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 						return true; | ||||
| 					}); | ||||
|  | ||||
| 					// TODO: フィルタした結果件数が足りなかった場合の対応 | ||||
|  | ||||
| 					timeline.sort((a, b) => a.id > b.id ? -1 : 1); | ||||
| 					redisTimeline.sort((a, b) => a.id > b.id ? -1 : 1); | ||||
| 				} | ||||
|  | ||||
| 				if (redisTimeline.length > 0) { | ||||
| 					process.nextTick(() => { | ||||
| 						this.activeUsersChart.read(me); | ||||
| 					}); | ||||
|  | ||||
| 					return await this.noteEntityService.packMany(timeline, me); | ||||
| 					return await this.noteEntityService.packMany(redisTimeline, me); | ||||
| 				} else { // fallback to db | ||||
| 					return await this.getFromDb({ | ||||
| 						untilId, | ||||
| @@ -138,6 +140,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 						includeRenotedMyNotes: ps.includeRenotedMyNotes, | ||||
| 						includeLocalRenotes: ps.includeLocalRenotes, | ||||
| 						withFiles: ps.withFiles, | ||||
| 						withRenotes: ps.withRenotes, | ||||
| 					}, me); | ||||
| 				} | ||||
| 			} else { | ||||
| @@ -149,12 +152,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 					includeRenotedMyNotes: ps.includeRenotedMyNotes, | ||||
| 					includeLocalRenotes: ps.includeLocalRenotes, | ||||
| 					withFiles: ps.withFiles, | ||||
| 					withRenotes: ps.withRenotes, | ||||
| 				}, me); | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	private async getFromDb(ps: { untilId: string | null; sinceId: string | null; limit: number; includeMyRenotes: boolean; includeRenotedMyNotes: boolean; includeLocalRenotes: boolean; withFiles: boolean; }, me: MiLocalUser) { | ||||
| 	private async getFromDb(ps: { untilId: string | null; sinceId: string | null; limit: number; includeMyRenotes: boolean; includeRenotedMyNotes: boolean; includeLocalRenotes: boolean; withFiles: boolean; withRenotes: boolean; }, me: MiLocalUser) { | ||||
| 		const followees = await this.userFollowingService.getFollowees(me.id); | ||||
|  | ||||
| 		//#region Construct query | ||||
| @@ -222,6 +226,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 		if (ps.withFiles) { | ||||
| 			query.andWhere('note.fileIds != \'{}\''); | ||||
| 		} | ||||
|  | ||||
| 		if (ps.withRenotes === false) { | ||||
| 			query.andWhere('note.renoteId IS NULL'); | ||||
| 		} | ||||
| 		//#endregion | ||||
|  | ||||
| 		const timeline = await query.limit(ps.limit).getMany(); | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
|  */ | ||||
|  | ||||
| import { Inject, Injectable } from '@nestjs/common'; | ||||
| import type { NotesRepository, UserListsRepository } from '@/models/_.js'; | ||||
| import type { MiNote, NotesRepository, UserListMembershipsRepository, UserListsRepository } from '@/models/_.js'; | ||||
| import { Endpoint } from '@/server/api/endpoint-base.js'; | ||||
| import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; | ||||
| import ActiveUsersChart from '@/core/chart/charts/active-users.js'; | ||||
| @@ -13,7 +13,9 @@ import { CacheService } from '@/core/CacheService.js'; | ||||
| import { IdService } from '@/core/IdService.js'; | ||||
| import { isUserRelated } from '@/misc/is-user-related.js'; | ||||
| import { FunoutTimelineService } from '@/core/FunoutTimelineService.js'; | ||||
| import { QueryService } from '@/core/QueryService.js'; | ||||
| import { ApiError } from '../../error.js'; | ||||
| import { Brackets } from 'typeorm'; | ||||
|  | ||||
| export const meta = { | ||||
| 	tags: ['notes', 'lists'], | ||||
| @@ -70,11 +72,16 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 		@Inject(DI.userListsRepository) | ||||
| 		private userListsRepository: UserListsRepository, | ||||
|  | ||||
| 		@Inject(DI.userListMembershipsRepository) | ||||
| 		private userListMembershipsRepository: UserListMembershipsRepository, | ||||
|  | ||||
| 		private noteEntityService: NoteEntityService, | ||||
| 		private activeUsersChart: ActiveUsersChart, | ||||
| 		private cacheService: CacheService, | ||||
| 		private idService: IdService, | ||||
| 		private funoutTimelineService: FunoutTimelineService, | ||||
| 		private queryService: QueryService, | ||||
|  | ||||
| 	) { | ||||
| 		super(meta, paramDef, async (ps, me) => { | ||||
| 			const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); | ||||
| @@ -102,44 +109,129 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 			let noteIds = await this.funoutTimelineService.get(ps.withFiles ? `userListTimelineWithFiles:${list.id}` : `userListTimeline:${list.id}`, untilId, sinceId); | ||||
| 			noteIds = noteIds.slice(0, ps.limit); | ||||
|  | ||||
| 			if (noteIds.length === 0) { | ||||
| 				return []; | ||||
| 			let redisTimeline: MiNote[] = []; | ||||
|  | ||||
| 			if (noteIds.length > 0) { | ||||
| 				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') | ||||
| 					.leftJoinAndSelect('note.channel', 'channel'); | ||||
|  | ||||
| 				redisTimeline = await query.getMany(); | ||||
|  | ||||
| 				redisTimeline = redisTimeline.filter(note => { | ||||
| 					if (note.userId === me.id) { | ||||
| 						return true; | ||||
| 					} | ||||
| 					if (isUserRelated(note, userIdsWhoBlockingMe)) return false; | ||||
| 					if (isUserRelated(note, userIdsWhoMeMuting)) return false; | ||||
| 					if (note.renoteId) { | ||||
| 						if (note.text == null && note.fileIds.length === 0 && !note.hasPoll) { | ||||
| 							if (isUserRelated(note, userIdsWhoMeMutingRenotes)) return false; | ||||
| 							if (ps.withRenotes === false) return false; | ||||
| 						} | ||||
| 					} | ||||
|  | ||||
| 					return true; | ||||
| 				}); | ||||
|  | ||||
| 				redisTimeline.sort((a, b) => a.id > b.id ? -1 : 1); | ||||
| 			} | ||||
|  | ||||
| 			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') | ||||
| 				.leftJoinAndSelect('note.channel', 'channel'); | ||||
| 			if (redisTimeline.length > 0) { | ||||
| 				this.activeUsersChart.read(me); | ||||
| 				return await this.noteEntityService.packMany(redisTimeline, me); | ||||
| 			} else { // fallback to db | ||||
| 				//#region Construct query | ||||
| 				const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) | ||||
| 					.innerJoin(this.userListMembershipsRepository.metadata.targetName, 'userListMemberships', 'userListMemberships.userId = note.userId') | ||||
| 					.innerJoinAndSelect('note.user', 'user') | ||||
| 					.leftJoinAndSelect('note.reply', 'reply') | ||||
| 					.leftJoinAndSelect('note.renote', 'renote') | ||||
| 					.leftJoinAndSelect('reply.user', 'replyUser') | ||||
| 					.leftJoinAndSelect('renote.user', 'renoteUser') | ||||
| 					.andWhere('userListMemberships.userListId = :userListId', { userListId: list.id }) | ||||
| 					.andWhere('note.channelId IS NULL') // チャンネルノートではない | ||||
| 					.andWhere(new Brackets(qb => { | ||||
| 						qb | ||||
| 							.where('note.replyId IS NULL') // 返信ではない | ||||
| 							.orWhere(new Brackets(qb => { | ||||
| 								qb // 返信だけど投稿者自身への返信 | ||||
| 									.where('note.replyId IS NOT NULL') | ||||
| 									.andWhere('note.replyUserId = note.userId'); | ||||
| 							})) | ||||
| 							.orWhere(new Brackets(qb => { | ||||
| 								qb // 返信だけど自分宛ての返信 | ||||
| 									.where('note.replyId IS NOT NULL') | ||||
| 									.andWhere('note.replyUserId = :meId', { meId: me.id }); | ||||
| 							})) | ||||
| 							.orWhere(new Brackets(qb => { | ||||
| 								qb // 返信だけどwithRepliesがtrueの場合 | ||||
| 									.where('note.replyId IS NOT NULL') | ||||
| 									.andWhere('userListMemberships.withReplies = true'); | ||||
| 							})); | ||||
| 					})); | ||||
|  | ||||
| 			let timeline = await query.getMany(); | ||||
| 				this.queryService.generateVisibilityQuery(query, me); | ||||
| 				this.queryService.generateMutedUserQuery(query, me); | ||||
| 				this.queryService.generateBlockedUserQuery(query, me); | ||||
| 				this.queryService.generateMutedUserRenotesQueryForNotes(query, me); | ||||
|  | ||||
| 			timeline = timeline.filter(note => { | ||||
| 				if (note.userId === me.id) { | ||||
| 					return true; | ||||
| 				} | ||||
| 				if (isUserRelated(note, userIdsWhoBlockingMe)) return false; | ||||
| 				if (isUserRelated(note, userIdsWhoMeMuting)) return false; | ||||
| 				if (note.renoteId) { | ||||
| 					if (note.text == null && note.fileIds.length === 0 && !note.hasPoll) { | ||||
| 						if (isUserRelated(note, userIdsWhoMeMutingRenotes)) return false; | ||||
| 						if (ps.withRenotes === false) return false; | ||||
| 					} | ||||
| 				if (ps.includeMyRenotes === false) { | ||||
| 					query.andWhere(new Brackets(qb => { | ||||
| 						qb.orWhere('note.userId != :meId', { meId: me.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)'); | ||||
| 					})); | ||||
| 				} | ||||
|  | ||||
| 				return true; | ||||
| 			}); | ||||
| 				if (ps.includeRenotedMyNotes === false) { | ||||
| 					query.andWhere(new Brackets(qb => { | ||||
| 						qb.orWhere('note.renoteUserId != :meId', { meId: me.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)'); | ||||
| 					})); | ||||
| 				} | ||||
|  | ||||
| 			// TODO: フィルタした結果件数が足りなかった場合の対応 | ||||
| 				if (ps.includeLocalRenotes === false) { | ||||
| 					query.andWhere(new Brackets(qb => { | ||||
| 						qb.orWhere('note.renoteUserHost IS NOT NULL'); | ||||
| 						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)'); | ||||
| 					})); | ||||
| 				} | ||||
|  | ||||
| 			timeline.sort((a, b) => a.id > b.id ? -1 : 1); | ||||
| 				if (ps.withRenotes === false) { | ||||
| 					query.andWhere(new Brackets(qb => { | ||||
| 						qb.orWhere('note.renoteId IS NULL'); | ||||
| 						qb.orWhere(new Brackets(qb => { | ||||
| 							qb.orWhere('note.text IS NOT NULL'); | ||||
| 							qb.orWhere('note.fileIds != \'{}\''); | ||||
| 						})); | ||||
| 					})); | ||||
| 				} | ||||
|  | ||||
| 			this.activeUsersChart.read(me); | ||||
| 				if (ps.withFiles) { | ||||
| 					query.andWhere('note.fileIds != \'{}\''); | ||||
| 				} | ||||
| 				//#endregion | ||||
|  | ||||
| 			return await this.noteEntityService.packMany(timeline, me); | ||||
| 				const timeline = await query.limit(ps.limit).getMany(); | ||||
|  | ||||
| 				this.activeUsersChart.read(me); | ||||
|  | ||||
| 				return await this.noteEntityService.packMany(timeline, me); | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 syuilo
					syuilo