fix notification limit with exclude/include types (#13836)
* fix: /i/notificationsがsinceIdのみのときに正しく動かない問題 Fix #10902 again * chore: use exclusive range to fetch data * fix: フィルタによって通知が0件だった場合でもリトライするように * docs(changelog): `/i/notifications`に includeTypes`か`excludeTypes`を指定しているとき、通知が存在するのに空配列を返すことがある問題を修正
This commit is contained in:
parent
acf84a2516
commit
4d0db37d2e
|
@ -81,6 +81,7 @@
|
||||||
- Fix: リノートをミュートしたユーザの投稿のリノートがミュートされる問題を修正
|
- Fix: リノートをミュートしたユーザの投稿のリノートがミュートされる問題を修正
|
||||||
- Fix: AP Link等は添付ファイル扱いしないようになど (#13754)
|
- Fix: AP Link等は添付ファイル扱いしないようになど (#13754)
|
||||||
- Fix: FTTが有効かつsinceIdのみを指定した場合に帰って来るレスポンスが逆順である問題を修正
|
- Fix: FTTが有効かつsinceIdのみを指定した場合に帰って来るレスポンスが逆順である問題を修正
|
||||||
|
- Fix: `/i/notifications`に `includeTypes`か`excludeTypes`を指定しているとき、通知が存在するのに空配列を返すことがある問題を修正
|
||||||
|
|
||||||
## 2024.3.1
|
## 2024.3.1
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { In } from 'typeorm';
|
||||||
import * as Redis from 'ioredis';
|
import * as Redis from 'ioredis';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import type { NotesRepository } from '@/models/_.js';
|
import type { NotesRepository } from '@/models/_.js';
|
||||||
import { obsoleteNotificationTypes, notificationTypes, FilterUnionByProperty } from '@/types.js';
|
import { FilterUnionByProperty, notificationTypes, obsoleteNotificationTypes } from '@/types.js';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { NoteReadService } from '@/core/NoteReadService.js';
|
import { NoteReadService } from '@/core/NoteReadService.js';
|
||||||
import { NotificationEntityService } from '@/core/entities/NotificationEntityService.js';
|
import { NotificationEntityService } from '@/core/entities/NotificationEntityService.js';
|
||||||
|
@ -84,18 +84,33 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
const includeTypes = ps.includeTypes && ps.includeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof notificationTypes[number][];
|
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 excludeTypes = ps.excludeTypes && ps.excludeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof notificationTypes[number][];
|
||||||
|
|
||||||
const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1
|
let sinceTime = ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime().toString() : null;
|
||||||
const notificationsRes = await this.redisClient.xrevrange(
|
let untilTime = ps.untilId ? this.idService.parse(ps.untilId).date.getTime().toString() : null;
|
||||||
|
|
||||||
|
let notifications: MiNotification[];
|
||||||
|
for (;;) {
|
||||||
|
let notificationsRes: [id: string, fields: string[]][];
|
||||||
|
|
||||||
|
// sinceidのみの場合は古い順、そうでない場合は新しい順。 QueryService.makePaginationQueryも参照
|
||||||
|
if (sinceTime && !untilTime) {
|
||||||
|
notificationsRes = await this.redisClient.xrange(
|
||||||
`notificationTimeline:${me.id}`,
|
`notificationTimeline:${me.id}`,
|
||||||
ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : '+',
|
'(' + sinceTime,
|
||||||
ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : '-',
|
'+',
|
||||||
'COUNT', limit);
|
'COUNT', ps.limit);
|
||||||
|
} else {
|
||||||
|
notificationsRes = await this.redisClient.xrevrange(
|
||||||
|
`notificationTimeline:${me.id}`,
|
||||||
|
untilTime ? '(' + untilTime : '+',
|
||||||
|
sinceTime ? '(' + sinceTime : '-',
|
||||||
|
'COUNT', ps.limit);
|
||||||
|
}
|
||||||
|
|
||||||
if (notificationsRes.length === 0) {
|
if (notificationsRes.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
let notifications = notificationsRes.map(x => JSON.parse(x[1][1])).filter(x => x.id !== ps.untilId && x !== ps.sinceId) as MiNotification[];
|
notifications = notificationsRes.map(x => JSON.parse(x[1][1])) as MiNotification[];
|
||||||
|
|
||||||
if (includeTypes && includeTypes.length > 0) {
|
if (includeTypes && includeTypes.length > 0) {
|
||||||
notifications = notifications.filter(notification => includeTypes.includes(notification.type));
|
notifications = notifications.filter(notification => includeTypes.includes(notification.type));
|
||||||
|
@ -103,8 +118,17 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
notifications = notifications.filter(notification => !excludeTypes.includes(notification.type));
|
notifications = notifications.filter(notification => !excludeTypes.includes(notification.type));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notifications.length === 0) {
|
if (notifications.length !== 0) {
|
||||||
return [];
|
// 通知が1件以上ある場合は返す
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// フィルタしたことで通知が0件になった場合、次のページを取得する
|
||||||
|
if (ps.sinceId && !ps.untilId) {
|
||||||
|
sinceTime = notificationsRes[notificationsRes.length - 1][0];
|
||||||
|
} else {
|
||||||
|
untilTime = notificationsRes[notificationsRes.length - 1][0];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark all as read
|
// Mark all as read
|
||||||
|
|
Loading…
Reference in New Issue