| @@ -12,6 +12,7 @@ import { NoteReadService } from '@/core/NoteReadService.js'; | ||||
| import { DI } from '@/di-symbols.js'; | ||||
| import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; | ||||
| import { IdService } from '@/core/IdService.js'; | ||||
| import { RedisTimelineService } from '@/core/RedisTimelineService.js'; | ||||
| import { ApiError } from '../../error.js'; | ||||
|  | ||||
| export const meta = { | ||||
| @@ -69,8 +70,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 		private noteEntityService: NoteEntityService, | ||||
| 		private queryService: QueryService, | ||||
| 		private noteReadService: NoteReadService, | ||||
| 		private redisTimelineService: RedisTimelineService, | ||||
| 	) { | ||||
| 		super(meta, paramDef, async (ps, me) => { | ||||
| 			const untilId = ps.untilId ?? ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null; | ||||
| 			const sinceId = ps.sinceId ?? ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null; | ||||
|  | ||||
| 			const antenna = await this.antennasRepository.findOneBy({ | ||||
| 				id: ps.antennaId, | ||||
| 				userId: me.id, | ||||
| @@ -85,15 +90,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 				lastUsedAt: new Date(), | ||||
| 			}); | ||||
|  | ||||
| 			const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1 | ||||
|  | ||||
| 			const noteIds = await this.redisForTimelines.xrevrange( | ||||
| 				`antennaTimeline:${antenna.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, | ||||
| 			).then(res => res.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId)); | ||||
|  | ||||
| 			let noteIds = await this.redisTimelineService.get(`antennaTimeline:${antenna.id}`, untilId, sinceId); | ||||
| 			noteIds = noteIds.slice(0, ps.limit); | ||||
| 			if (noteIds.length === 0) { | ||||
| 				return []; | ||||
| 			} | ||||
|   | ||||
| @@ -12,6 +12,9 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; | ||||
| import ActiveUsersChart from '@/core/chart/charts/active-users.js'; | ||||
| import { DI } from '@/di-symbols.js'; | ||||
| import { IdService } from '@/core/IdService.js'; | ||||
| import { RedisTimelineService } from '@/core/RedisTimelineService.js'; | ||||
| import { isUserRelated } from '@/misc/is-user-related.js'; | ||||
| import { CacheService } from '@/core/CacheService.js'; | ||||
| import { ApiError } from '../../error.js'; | ||||
|  | ||||
| export const meta = { | ||||
| @@ -66,9 +69,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 		private idService: IdService, | ||||
| 		private noteEntityService: NoteEntityService, | ||||
| 		private queryService: QueryService, | ||||
| 		private redisTimelineService: RedisTimelineService, | ||||
| 		private cacheService: CacheService, | ||||
| 		private activeUsersChart: ActiveUsersChart, | ||||
| 	) { | ||||
| 		super(meta, paramDef, async (ps, me) => { | ||||
| 			const untilId = ps.untilId ?? ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null; | ||||
| 			const sinceId = ps.sinceId ?? ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null; | ||||
| 			const isRangeSpecified = untilId != null && sinceId != null; | ||||
|  | ||||
| 			const channel = await this.channelsRepository.findOneBy({ | ||||
| 				id: ps.channelId, | ||||
| 			}); | ||||
| @@ -77,68 +86,66 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 				throw new ApiError(meta.errors.noSuchChannel); | ||||
| 			} | ||||
|  | ||||
| 			let timeline: MiNote[] = []; | ||||
|  | ||||
| 			const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1 | ||||
| 			let noteIdsRes: [string, string[]][] = []; | ||||
|  | ||||
| 			if (!ps.sinceId && !ps.sinceDate) { | ||||
| 				noteIdsRes = await this.redisForTimelines.xrevrange( | ||||
| 					`channelTimeline:${channel.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); | ||||
| 			} | ||||
|  | ||||
| 			// redis から取得していないとき・取得数が足りないとき | ||||
| 			if (noteIdsRes.length < limit) { | ||||
| 				//#region Construct query | ||||
| 				const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) | ||||
| 					.andWhere('note.channelId = :channelId', { channelId: channel.id }) | ||||
| 					.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 (me) { | ||||
| 					this.queryService.generateMutedUserQuery(query, me); | ||||
| 					this.queryService.generateBlockedUserQuery(query, me); | ||||
| 				} | ||||
| 				//#endregion | ||||
|  | ||||
| 				timeline = await query.limit(ps.limit).getMany(); | ||||
| 			} else { | ||||
| 				const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId); | ||||
|  | ||||
| 				if (noteIds.length === 0) { | ||||
| 					return []; | ||||
| 				} | ||||
|  | ||||
| 				//#region Construct query | ||||
| 				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 (me) { | ||||
| 					this.queryService.generateMutedUserQuery(query, me); | ||||
| 					this.queryService.generateBlockedUserQuery(query, me); | ||||
| 				} | ||||
| 				//#endregion | ||||
|  | ||||
| 				timeline = await query.getMany(); | ||||
| 				timeline.sort((a, b) => a.id > b.id ? -1 : 1); | ||||
| 			} | ||||
|  | ||||
| 			if (me) this.activeUsersChart.read(me); | ||||
|  | ||||
| 			if (isRangeSpecified || sinceId == null) { | ||||
| 				const [ | ||||
| 					userIdsWhoMeMuting, | ||||
| 				] = me ? await Promise.all([ | ||||
| 					this.cacheService.userMutingsCache.fetch(me.id), | ||||
| 				]) : [new Set<string>()]; | ||||
|  | ||||
| 				let noteIds = await this.redisTimelineService.get(`channelTimeline:${channel.id}`, untilId, sinceId); | ||||
| 				noteIds = noteIds.slice(0, ps.limit); | ||||
|  | ||||
| 				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'); | ||||
|  | ||||
| 					let timeline = await query.getMany(); | ||||
|  | ||||
| 					timeline = timeline.filter(note => { | ||||
| 						if (me && isUserRelated(note, userIdsWhoMeMuting, true)) return false; | ||||
|  | ||||
| 						return true; | ||||
| 					}); | ||||
|  | ||||
| 					// TODO: フィルタで件数が減った場合の埋め合わせ処理 | ||||
|  | ||||
| 					timeline.sort((a, b) => a.id > b.id ? -1 : 1); | ||||
|  | ||||
| 					if (timeline.length > 0) { | ||||
| 						return await this.noteEntityService.packMany(timeline, me); | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			//#region fallback to database | ||||
| 			const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) | ||||
| 				.andWhere('note.channelId = :channelId', { channelId: channel.id }) | ||||
| 				.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 (me) { | ||||
| 				this.queryService.generateMutedUserQuery(query, me); | ||||
| 				this.queryService.generateBlockedUserQuery(query, me); | ||||
| 			} | ||||
| 			//#endregion | ||||
|  | ||||
| 			const timeline = await query.limit(ps.limit).getMany(); | ||||
|  | ||||
| 			return await this.noteEntityService.packMany(timeline, me); | ||||
| 			//#endregion | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -15,6 +15,7 @@ import { RoleService } from '@/core/RoleService.js'; | ||||
| import { IdService } from '@/core/IdService.js'; | ||||
| import { isUserRelated } from '@/misc/is-user-related.js'; | ||||
| import { CacheService } from '@/core/CacheService.js'; | ||||
| import { RedisTimelineService } from '@/core/RedisTimelineService.js'; | ||||
| import { ApiError } from '../../error.js'; | ||||
|  | ||||
| export const meta = { | ||||
| @@ -72,8 +73,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 		private activeUsersChart: ActiveUsersChart, | ||||
| 		private idService: IdService, | ||||
| 		private cacheService: CacheService, | ||||
| 		private redisTimelineService: RedisTimelineService, | ||||
| 	) { | ||||
| 		super(meta, paramDef, async (ps, me) => { | ||||
| 			const untilId = ps.untilId ?? ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null; | ||||
| 			const sinceId = ps.sinceId ?? ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null; | ||||
|  | ||||
| 			const policies = await this.roleService.getUserPolicies(me.id); | ||||
| 			if (!policies.ltlAvailable) { | ||||
| 				throw new ApiError(meta.errors.stlDisabled); | ||||
| @@ -89,27 +94,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 				this.cacheService.userBlockedCache.fetch(me.id), | ||||
| 			]); | ||||
|  | ||||
| 			let timeline: MiNote[] = []; | ||||
|  | ||||
| 			const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1 | ||||
|  | ||||
| 			const redisPipeline = this.redisForTimelines.pipeline(); | ||||
| 			redisPipeline.xrevrange( | ||||
| 			const [htlNoteIds, ltlNoteIds] = await this.redisTimelineService.getMulti([ | ||||
| 				ps.withFiles ? `homeTimelineWithFiles:${me.id}` : `homeTimeline:${me.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, | ||||
| 			); | ||||
| 			redisPipeline.xrevrange( | ||||
| 				ps.withFiles ? 'localTimelineWithFiles' : 'localTimeline', | ||||
| 				ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+', | ||||
| 				ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-', | ||||
| 				'COUNT', limit, | ||||
| 			); | ||||
| 			const [htlNoteIds, ltlNoteIds] = await redisPipeline.exec().then(res => res ? [ | ||||
| 				(res[0][1] as string[][]).map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId), | ||||
| 				(res[1][1] as string[][]).map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId), | ||||
| 			] : []); | ||||
| 			], untilId, sinceId); | ||||
|  | ||||
| 			let noteIds = Array.from(new Set([...htlNoteIds, ...ltlNoteIds])); | ||||
| 			noteIds.sort((a, b) => a > b ? -1 : 1); | ||||
| @@ -128,7 +116,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 				.leftJoinAndSelect('renote.user', 'renoteUser') | ||||
| 				.leftJoinAndSelect('note.channel', 'channel'); | ||||
|  | ||||
| 			timeline = await query.getMany(); | ||||
| 			let timeline = await query.getMany(); | ||||
|  | ||||
| 			timeline = timeline.filter(note => { | ||||
| 				if (note.userId === me.id) { | ||||
|   | ||||
| @@ -15,6 +15,7 @@ import { RoleService } from '@/core/RoleService.js'; | ||||
| import { IdService } from '@/core/IdService.js'; | ||||
| import { CacheService } from '@/core/CacheService.js'; | ||||
| import { isUserRelated } from '@/misc/is-user-related.js'; | ||||
| import { RedisTimelineService } from '@/core/RedisTimelineService.js'; | ||||
| import { ApiError } from '../../error.js'; | ||||
|  | ||||
| export const meta = { | ||||
| @@ -68,8 +69,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 		private activeUsersChart: ActiveUsersChart, | ||||
| 		private idService: IdService, | ||||
| 		private cacheService: CacheService, | ||||
| 		private redisTimelineService: RedisTimelineService, | ||||
| 	) { | ||||
| 		super(meta, paramDef, async (ps, me) => { | ||||
| 			const untilId = ps.untilId ?? ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null; | ||||
| 			const sinceId = ps.sinceId ?? ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null; | ||||
|  | ||||
| 			const policies = await this.roleService.getUserPolicies(me ? me.id : null); | ||||
| 			if (!policies.ltlAvailable) { | ||||
| 				throw new ApiError(meta.errors.ltlDisabled); | ||||
| @@ -85,16 +90,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 				this.cacheService.userBlockedCache.fetch(me.id), | ||||
| 			]) : [new Set<string>(), new Set<string>(), new Set<string>()]; | ||||
|  | ||||
| 			let timeline: MiNote[] = []; | ||||
|  | ||||
| 			const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1 | ||||
|  | ||||
| 			const noteIds = await this.redisForTimelines.xrevrange( | ||||
| 				ps.withFiles ? 'localTimelineWithFiles' : 'localTimeline', | ||||
| 				ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+', | ||||
| 				ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-', | ||||
| 				'COUNT', limit, | ||||
| 			).then(res => res.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId)); | ||||
| 			let noteIds = await this.redisTimelineService.get(ps.withFiles ? 'localTimelineWithFiles' : 'localTimeline', untilId, sinceId); | ||||
| 			noteIds = noteIds.slice(0, ps.limit); | ||||
|  | ||||
| 			if (noteIds.length === 0) { | ||||
| 				return []; | ||||
| @@ -109,7 +106,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 				.leftJoinAndSelect('renote.user', 'renoteUser') | ||||
| 				.leftJoinAndSelect('note.channel', 'channel'); | ||||
|  | ||||
| 			timeline = await query.getMany(); | ||||
| 			let timeline = await query.getMany(); | ||||
|  | ||||
| 			timeline = timeline.filter(note => { | ||||
| 				if (me && (note.userId === me.id)) { | ||||
| @@ -127,6 +124,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 				return true; | ||||
| 			}); | ||||
|  | ||||
| 			// TODO: フィルタした結果件数が足りなかった場合の対応 | ||||
|  | ||||
| 			timeline.sort((a, b) => a.id > b.id ? -1 : 1); | ||||
|  | ||||
| 			process.nextTick(() => { | ||||
|   | ||||
| @@ -15,6 +15,7 @@ import { DI } from '@/di-symbols.js'; | ||||
| import { IdService } from '@/core/IdService.js'; | ||||
| import { CacheService } from '@/core/CacheService.js'; | ||||
| import { isUserRelated } from '@/misc/is-user-related.js'; | ||||
| import { RedisTimelineService } from '@/core/RedisTimelineService.js'; | ||||
|  | ||||
| export const meta = { | ||||
| 	tags: ['notes'], | ||||
| @@ -62,8 +63,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 		private activeUsersChart: ActiveUsersChart, | ||||
| 		private idService: IdService, | ||||
| 		private cacheService: CacheService, | ||||
| 		private redisTimelineService: RedisTimelineService, | ||||
| 	) { | ||||
| 		super(meta, paramDef, async (ps, me) => { | ||||
| 			const untilId = ps.untilId ?? ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null; | ||||
| 			const sinceId = ps.sinceId ?? ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null; | ||||
|  | ||||
| 			const [ | ||||
| 				followings, | ||||
| 				userIdsWhoMeMuting, | ||||
| @@ -76,16 +81,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 				this.cacheService.userBlockedCache.fetch(me.id), | ||||
| 			]); | ||||
|  | ||||
| 			let timeline: MiNote[] = []; | ||||
|  | ||||
| 			const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1 | ||||
|  | ||||
| 			const noteIds = await this.redisForTimelines.xrevrange( | ||||
| 				ps.withFiles ? `homeTimelineWithFiles:${me.id}` : `homeTimeline:${me.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, | ||||
| 			).then(res => res.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId)); | ||||
| 			let noteIds = await this.redisTimelineService.get(ps.withFiles ? `homeTimelineWithFiles:${me.id}` : `homeTimeline:${me.id}`, untilId, sinceId); | ||||
| 			noteIds = noteIds.slice(0, ps.limit); | ||||
|  | ||||
| 			if (noteIds.length === 0) { | ||||
| 				return []; | ||||
| @@ -100,7 +97,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 				.leftJoinAndSelect('renote.user', 'renoteUser') | ||||
| 				.leftJoinAndSelect('note.channel', 'channel'); | ||||
|  | ||||
| 			timeline = await query.getMany(); | ||||
| 			let timeline = await query.getMany(); | ||||
|  | ||||
| 			timeline = timeline.filter(note => { | ||||
| 				if (note.userId === me.id) { | ||||
|   | ||||
| @@ -15,6 +15,7 @@ import { DI } from '@/di-symbols.js'; | ||||
| import { CacheService } from '@/core/CacheService.js'; | ||||
| import { IdService } from '@/core/IdService.js'; | ||||
| import { isUserRelated } from '@/misc/is-user-related.js'; | ||||
| import { RedisTimelineService } from '@/core/RedisTimelineService.js'; | ||||
| import { ApiError } from '../../error.js'; | ||||
|  | ||||
| export const meta = { | ||||
| @@ -79,8 +80,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 		private activeUsersChart: ActiveUsersChart, | ||||
| 		private cacheService: CacheService, | ||||
| 		private idService: IdService, | ||||
| 		private redisTimelineService: RedisTimelineService, | ||||
| 	) { | ||||
| 		super(meta, paramDef, async (ps, me) => { | ||||
| 			const untilId = ps.untilId ?? ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null; | ||||
| 			const sinceId = ps.sinceId ?? ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null; | ||||
|  | ||||
| 			const list = await this.userListsRepository.findOneBy({ | ||||
| 				id: ps.listId, | ||||
| 				userId: me.id, | ||||
| @@ -100,16 +105,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 				this.cacheService.userBlockedCache.fetch(me.id), | ||||
| 			]); | ||||
|  | ||||
| 			let timeline: MiNote[] = []; | ||||
|  | ||||
| 			const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1 | ||||
|  | ||||
| 			const noteIds = await this.redisForTimelines.xrevrange( | ||||
| 				ps.withFiles ? `userListTimelineWithFiles:${list.id}` : `userListTimeline:${list.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, | ||||
| 			).then(res => res.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId)); | ||||
| 			let noteIds = await this.redisTimelineService.get(ps.withFiles ? `userListTimelineWithFiles:${list.id}` : `userListTimeline:${list.id}`, untilId, sinceId); | ||||
| 			noteIds = noteIds.slice(0, ps.limit); | ||||
|  | ||||
| 			if (noteIds.length === 0) { | ||||
| 				return []; | ||||
| @@ -124,7 +121,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 				.leftJoinAndSelect('renote.user', 'renoteUser') | ||||
| 				.leftJoinAndSelect('note.channel', 'channel'); | ||||
|  | ||||
| 			timeline = await query.getMany(); | ||||
| 			let timeline = await query.getMany(); | ||||
|  | ||||
| 			timeline = timeline.filter(note => { | ||||
| 				if (note.userId === me.id) { | ||||
|   | ||||
| @@ -11,6 +11,7 @@ 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 { RedisTimelineService } from '@/core/RedisTimelineService.js'; | ||||
| import { ApiError } from '../../error.js'; | ||||
|  | ||||
| export const meta = { | ||||
| @@ -65,8 +66,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 		private idService: IdService, | ||||
| 		private noteEntityService: NoteEntityService, | ||||
| 		private queryService: QueryService, | ||||
| 		private redisTimelineService: RedisTimelineService, | ||||
| 	) { | ||||
| 		super(meta, paramDef, async (ps, me) => { | ||||
| 			const untilId = ps.untilId ?? ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null; | ||||
| 			const sinceId = ps.sinceId ?? ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null; | ||||
|  | ||||
| 			const role = await this.rolesRepository.findOneBy({ | ||||
| 				id: ps.roleId, | ||||
| 				isPublic: true, | ||||
| @@ -78,14 +83,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 			if (!role.isExplorable) { | ||||
| 				return []; | ||||
| 			} | ||||
| 			const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1 | ||||
|  | ||||
| 			const noteIds = await this.redisForTimelines.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, | ||||
| 			).then(res => res.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId)); | ||||
| 			let noteIds = await this.redisTimelineService.get(`roleTimeline:${role.id}`, untilId, sinceId); | ||||
| 			noteIds = noteIds.slice(0, ps.limit); | ||||
|  | ||||
| 			if (noteIds.length === 0) { | ||||
| 				return []; | ||||
|   | ||||
| @@ -14,6 +14,7 @@ import { CacheService } from '@/core/CacheService.js'; | ||||
| import { IdService } from '@/core/IdService.js'; | ||||
| import { isUserRelated } from '@/misc/is-user-related.js'; | ||||
| import { QueryService } from '@/core/QueryService.js'; | ||||
| import { RedisTimelineService } from '@/core/RedisTimelineService.js'; | ||||
| import { ApiError } from '../../error.js'; | ||||
|  | ||||
| export const meta = { | ||||
| @@ -70,42 +71,24 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 		private queryService: QueryService, | ||||
| 		private cacheService: CacheService, | ||||
| 		private idService: IdService, | ||||
| 		private redisTimelineService: RedisTimelineService, | ||||
| 	) { | ||||
| 		super(meta, paramDef, async (ps, me) => { | ||||
| 			const isRangeSpecified = (ps.sinceId != null || ps.sinceDate != null) && (ps.untilId != null || ps.untilDate != null); | ||||
| 			const untilId = ps.untilId ?? ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null; | ||||
| 			const sinceId = ps.sinceId ?? ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null; | ||||
| 			const isRangeSpecified = untilId != null && sinceId != null; | ||||
|  | ||||
| 			if (isRangeSpecified || !(ps.sinceId != null || ps.sinceDate != null)) { | ||||
| 			if (isRangeSpecified || sinceId == null) { | ||||
| 				const [ | ||||
| 					userIdsWhoMeMuting, | ||||
| 				] = me ? await Promise.all([ | ||||
| 					this.cacheService.userMutingsCache.fetch(me.id), | ||||
| 				]) : [new Set<string>()]; | ||||
|  | ||||
| 				const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1 | ||||
|  | ||||
| 				const [noteIdsRes, repliesNoteIdsRes, channelNoteIdsRes] = await Promise.all([ | ||||
| 					this.redisForTimelines.xrevrange( | ||||
| 						ps.withFiles ? `userTimelineWithFiles:${ps.userId}` : `userTimeline:${ps.userId}`, | ||||
| 						ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+', | ||||
| 						ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-', | ||||
| 						'COUNT', limit, | ||||
| 					).then(res => res.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId)), | ||||
| 					ps.withReplies | ||||
| 						? this.redisForTimelines.xrevrange( | ||||
| 							`userTimelineWithReplies:${ps.userId}`, | ||||
| 							ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+', | ||||
| 							ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-', | ||||
| 							'COUNT', limit, | ||||
| 						).then(res => res.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId)) | ||||
| 						: Promise.resolve([]), | ||||
| 					ps.withChannelNotes | ||||
| 						? this.redisForTimelines.xrevrange( | ||||
| 							`userTimelineWithChannel:${ps.userId}`, | ||||
| 							ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+', | ||||
| 							ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-', | ||||
| 							'COUNT', limit, | ||||
| 						).then(res => res.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId)) | ||||
| 						: Promise.resolve([]), | ||||
| 					this.redisTimelineService.get(ps.withFiles ? `userTimelineWithFiles:${ps.userId}` : `userTimeline:${ps.userId}`, untilId, sinceId), | ||||
| 					ps.withReplies ? this.redisTimelineService.get(`userTimelineWithReplies:${ps.userId}`, untilId, sinceId) : Promise.resolve([]), | ||||
| 					ps.withChannelNotes ? this.redisTimelineService.get(`userTimelineWithChannel:${ps.userId}`, untilId, sinceId) : Promise.resolve([]), | ||||
| 				]); | ||||
|  | ||||
| 				let noteIds = Array.from(new Set([ | ||||
| @@ -145,6 +128,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 						return true; | ||||
| 					}); | ||||
|  | ||||
| 					// TODO: フィルタで件数が減った場合の埋め合わせ処理 | ||||
|  | ||||
| 					timeline.sort((a, b) => a.id > b.id ? -1 : 1); | ||||
|  | ||||
| 					if (timeline.length > 0) { | ||||
| @@ -153,9 +138,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			// fallback to database | ||||
|  | ||||
| 			//#region Construct query | ||||
| 			//#region fallback to database | ||||
| 			const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) | ||||
| 				.andWhere('note.userId = :userId', { userId: ps.userId }) | ||||
| 				.innerJoinAndSelect('note.user', 'user') | ||||
| @@ -188,11 +171,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 					qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); | ||||
| 				})); | ||||
| 			} | ||||
| 			//#endregion | ||||
|  | ||||
| 			const timeline = await query.limit(ps.limit).getMany(); | ||||
|  | ||||
| 			return await this.noteEntityService.packMany(timeline, me); | ||||
| 			//#endregion | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 syuilo
					syuilo