refactor: ⚡ antenna notes in cache
Co-authored-by: Kainoa Kanter <kainoa@t1c.dev>
This commit is contained in:
parent
5e85d0761e
commit
fd1bc109d9
7 changed files with 113 additions and 123 deletions
9
packages/backend/migration/1680491187535-cleanup.js
Normal file
9
packages/backend/migration/1680491187535-cleanup.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
export class cleanup1680491187535 {
|
||||||
|
name = "cleanup1680491187535";
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`DROP TABLE "antenna_note" `);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {}
|
||||||
|
}
|
|
@ -123,7 +123,6 @@ export const ModerationLogs = ModerationLogRepository;
|
||||||
export const Clips = ClipRepository;
|
export const Clips = ClipRepository;
|
||||||
export const ClipNotes = db.getRepository(ClipNote);
|
export const ClipNotes = db.getRepository(ClipNote);
|
||||||
export const Antennas = AntennaRepository;
|
export const Antennas = AntennaRepository;
|
||||||
export const AntennaNotes = db.getRepository(AntennaNote);
|
|
||||||
export const PromoNotes = db.getRepository(PromoNote);
|
export const PromoNotes = db.getRepository(PromoNote);
|
||||||
export const PromoReads = db.getRepository(PromoRead);
|
export const PromoReads = db.getRepository(PromoRead);
|
||||||
export const Relays = RelayRepository;
|
export const Relays = RelayRepository;
|
||||||
|
|
|
@ -18,7 +18,6 @@ import { createPerson } from "@/remote/activitypub/models/person.js";
|
||||||
import {
|
import {
|
||||||
AnnouncementReads,
|
AnnouncementReads,
|
||||||
Announcements,
|
Announcements,
|
||||||
AntennaNotes,
|
|
||||||
Blockings,
|
Blockings,
|
||||||
ChannelFollowings,
|
ChannelFollowings,
|
||||||
DriveFiles,
|
DriveFiles,
|
||||||
|
@ -258,23 +257,24 @@ export const UserRepository = db.getRepository(User).extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
async getHasUnreadAntenna(userId: User["id"]): Promise<boolean> {
|
async getHasUnreadAntenna(userId: User["id"]): Promise<boolean> {
|
||||||
try {
|
// try {
|
||||||
const myAntennas = (await getAntennas()).filter(
|
// const myAntennas = (await getAntennas()).filter(
|
||||||
(a) => a.userId === userId,
|
// (a) => a.userId === userId,
|
||||||
);
|
// );
|
||||||
|
|
||||||
const unread =
|
// const unread =
|
||||||
myAntennas.length > 0
|
// myAntennas.length > 0
|
||||||
? await AntennaNotes.findOneBy({
|
// ? await AntennaNotes.findOneBy({
|
||||||
antennaId: In(myAntennas.map((x) => x.id)),
|
// antennaId: In(myAntennas.map((x) => x.id)),
|
||||||
read: false,
|
// read: false,
|
||||||
})
|
// })
|
||||||
: null;
|
// : null;
|
||||||
|
|
||||||
return unread != null;
|
// return unread != null;
|
||||||
} catch (e) {
|
// } catch (e) {
|
||||||
return false;
|
// return false;
|
||||||
}
|
// }
|
||||||
|
return false; // TODO
|
||||||
},
|
},
|
||||||
|
|
||||||
async getHasUnreadChannel(userId: User["id"]): Promise<boolean> {
|
async getHasUnreadChannel(userId: User["id"]): Promise<boolean> {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import define from "../../define.js";
|
import define from "../../define.js";
|
||||||
import { Antennas, AntennaNotes } from "@/models/index.js";
|
import { Antennas } from "@/models/index.js";
|
||||||
import { FindOptionsWhere } from "typeorm";
|
import { FindOptionsWhere } from "typeorm";
|
||||||
import { AntennaNote } from "@/models/entities/antenna-note.js";
|
import { AntennaNote } from "@/models/entities/antenna-note.js";
|
||||||
|
|
||||||
|
@ -29,15 +29,15 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
await AntennaNotes.update(
|
// await AntennaNotes.update(
|
||||||
{
|
// {
|
||||||
antennaId: antenna.id,
|
// antennaId: antenna.id,
|
||||||
read: false,
|
// read: false,
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
read: true,
|
// read: true,
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import define from "../../define.js";
|
import define from "../../define.js";
|
||||||
import readNote from "@/services/note/read.js";
|
import readNote from "@/services/note/read.js";
|
||||||
import { Antennas, Notes, AntennaNotes } from "@/models/index.js";
|
import { Antennas, Notes } from "@/models/index.js";
|
||||||
|
import { redisClient } from "@/db/redis.js";
|
||||||
|
import { genId } from "@/misc/gen-id.js";
|
||||||
import { makePaginationQuery } from "../../common/make-pagination-query.js";
|
import { makePaginationQuery } from "../../common/make-pagination-query.js";
|
||||||
import { generateVisibilityQuery } from "../../common/generate-visibility-query.js";
|
import { generateVisibilityQuery } from "../../common/generate-visibility-query.js";
|
||||||
import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js";
|
import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js";
|
||||||
|
@ -58,6 +60,26 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
throw new ApiError(meta.errors.noSuchAntenna);
|
throw new ApiError(meta.errors.noSuchAntenna);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const noteIdsRes = await redisClient.xrevrange(
|
||||||
|
`antennaTimeline:${antenna.id}`,
|
||||||
|
ps.untilDate || "+",
|
||||||
|
"-",
|
||||||
|
"COUNT",
|
||||||
|
ps.limit + 1,
|
||||||
|
); // untilIdに指定したものも含まれるため+1
|
||||||
|
|
||||||
|
if (noteIdsRes.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const noteIds = noteIdsRes
|
||||||
|
.map((x) => x[1][1])
|
||||||
|
.filter((x) => x !== ps.untilId);
|
||||||
|
|
||||||
|
if (noteIds.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
const query = makePaginationQuery(
|
const query = makePaginationQuery(
|
||||||
Notes.createQueryBuilder("note"),
|
Notes.createQueryBuilder("note"),
|
||||||
ps.sinceId,
|
ps.sinceId,
|
||||||
|
@ -65,11 +87,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
ps.sinceDate,
|
ps.sinceDate,
|
||||||
ps.untilDate,
|
ps.untilDate,
|
||||||
)
|
)
|
||||||
.innerJoin(
|
.where("note.id IN (:...noteIds)", { noteIds: noteIds })
|
||||||
AntennaNotes.metadata.targetName,
|
|
||||||
"antennaNote",
|
|
||||||
"antennaNote.noteId = note.id",
|
|
||||||
)
|
|
||||||
.innerJoinAndSelect("note.user", "user")
|
.innerJoinAndSelect("note.user", "user")
|
||||||
.leftJoinAndSelect("user.avatar", "avatar")
|
.leftJoinAndSelect("user.avatar", "avatar")
|
||||||
.leftJoinAndSelect("user.banner", "banner")
|
.leftJoinAndSelect("user.banner", "banner")
|
||||||
|
@ -81,7 +99,6 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
.leftJoinAndSelect("renote.user", "renoteUser")
|
.leftJoinAndSelect("renote.user", "renoteUser")
|
||||||
.leftJoinAndSelect("renoteUser.avatar", "renoteUserAvatar")
|
.leftJoinAndSelect("renoteUser.avatar", "renoteUserAvatar")
|
||||||
.leftJoinAndSelect("renoteUser.banner", "renoteUserBanner")
|
.leftJoinAndSelect("renoteUser.banner", "renoteUserBanner")
|
||||||
.andWhere("antennaNote.antennaId = :antennaId", { antennaId: antenna.id })
|
|
||||||
.andWhere("note.visibility != 'home'");
|
.andWhere("note.visibility != 'home'");
|
||||||
|
|
||||||
generateVisibilityQuery(query, user);
|
generateVisibilityQuery(query, user);
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import type { Antenna } from "@/models/entities/antenna.js";
|
import type { Antenna } from "@/models/entities/antenna.js";
|
||||||
import type { Note } from "@/models/entities/note.js";
|
import type { Note } from "@/models/entities/note.js";
|
||||||
import { AntennaNotes, Mutings, Notes } from "@/models/index.js";
|
|
||||||
import { genId } from "@/misc/gen-id.js";
|
import { genId } from "@/misc/gen-id.js";
|
||||||
import { isUserRelated } from "@/misc/is-user-related.js";
|
import { redisClient } from "@/db/redis.js";
|
||||||
import { publishAntennaStream, publishMainStream } from "@/services/stream.js";
|
import { publishAntennaStream } from "@/services/stream.js";
|
||||||
import type { User } from "@/models/entities/user.js";
|
import type { User } from "@/models/entities/user.js";
|
||||||
|
|
||||||
export async function addNoteToAntenna(
|
export async function addNoteToAntenna(
|
||||||
|
@ -14,48 +13,15 @@ export async function addNoteToAntenna(
|
||||||
// 通知しない設定になっているか、自分自身の投稿なら既読にする
|
// 通知しない設定になっているか、自分自身の投稿なら既読にする
|
||||||
const read = !antenna.notify || antenna.userId === noteUser.id;
|
const read = !antenna.notify || antenna.userId === noteUser.id;
|
||||||
|
|
||||||
AntennaNotes.insert({
|
redisClient.xadd(
|
||||||
id: genId(),
|
`antennaTimeline:${antenna.id}`,
|
||||||
antennaId: antenna.id,
|
"MAXLEN",
|
||||||
noteId: note.id,
|
"~",
|
||||||
read: read,
|
"200",
|
||||||
});
|
`${genId(note.createdAt)}-*`,
|
||||||
|
"note",
|
||||||
|
note.id,
|
||||||
|
);
|
||||||
|
|
||||||
publishAntennaStream(antenna.id, "note", note);
|
publishAntennaStream(antenna.id, "note", note);
|
||||||
|
|
||||||
if (!read) {
|
|
||||||
const mutings = await Mutings.find({
|
|
||||||
where: {
|
|
||||||
muterId: antenna.userId,
|
|
||||||
},
|
|
||||||
select: ["muteeId"],
|
|
||||||
});
|
|
||||||
|
|
||||||
// Copy
|
|
||||||
const _note: Note = {
|
|
||||||
...note,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (note.replyId != null) {
|
|
||||||
_note.reply = await Notes.findOneByOrFail({ id: note.replyId });
|
|
||||||
}
|
|
||||||
if (note.renoteId != null) {
|
|
||||||
_note.renote = await Notes.findOneByOrFail({ id: note.renoteId });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isUserRelated(_note, new Set<string>(mutings.map((x) => x.muteeId)))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2秒経っても既読にならなかったら通知
|
|
||||||
setTimeout(async () => {
|
|
||||||
const unread = await AntennaNotes.findOneBy({
|
|
||||||
antennaId: antenna.id,
|
|
||||||
read: false,
|
|
||||||
});
|
|
||||||
if (unread) {
|
|
||||||
publishMainStream(antenna.userId, "unreadAntenna", antenna);
|
|
||||||
}
|
|
||||||
}, 2000);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ import type { Note } from "@/models/entities/note.js";
|
||||||
import type { User } from "@/models/entities/user.js";
|
import type { User } from "@/models/entities/user.js";
|
||||||
import {
|
import {
|
||||||
NoteUnreads,
|
NoteUnreads,
|
||||||
AntennaNotes,
|
|
||||||
Users,
|
Users,
|
||||||
Followings,
|
Followings,
|
||||||
ChannelFollowings,
|
ChannelFollowings,
|
||||||
|
@ -55,7 +54,7 @@ export default async function (
|
||||||
const readMentions: (Note | Packed<"Note">)[] = [];
|
const readMentions: (Note | Packed<"Note">)[] = [];
|
||||||
const readSpecifiedNotes: (Note | Packed<"Note">)[] = [];
|
const readSpecifiedNotes: (Note | Packed<"Note">)[] = [];
|
||||||
const readChannelNotes: (Note | Packed<"Note">)[] = [];
|
const readChannelNotes: (Note | Packed<"Note">)[] = [];
|
||||||
const readAntennaNotes: (Note | Packed<"Note">)[] = [];
|
// const readAntennaNotes: (Note | Packed<"Note">)[] = [];
|
||||||
|
|
||||||
for (const note of notes) {
|
for (const note of notes) {
|
||||||
if (note.mentions?.includes(userId)) {
|
if (note.mentions?.includes(userId)) {
|
||||||
|
@ -68,22 +67,22 @@ export default async function (
|
||||||
readChannelNotes.push(note);
|
readChannelNotes.push(note);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (note.user != null) {
|
// if (note.user != null) {
|
||||||
// たぶんnullになることは無いはずだけど一応
|
// // たぶんnullになることは無いはずだけど一応
|
||||||
for (const antenna of myAntennas) {
|
// for (const antenna of myAntennas) {
|
||||||
if (
|
// if (
|
||||||
await checkHitAntenna(
|
// await checkHitAntenna(
|
||||||
antenna,
|
// antenna,
|
||||||
note,
|
// note,
|
||||||
note.user,
|
// note.user,
|
||||||
undefined,
|
// undefined,
|
||||||
Array.from(following),
|
// Array.from(following),
|
||||||
)
|
// )
|
||||||
) {
|
// ) {
|
||||||
readAntennaNotes.push(note);
|
// readAntennaNotes.push(note);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -141,33 +140,33 @@ export default async function (
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (readAntennaNotes.length > 0) {
|
// if (readAntennaNotes.length > 0) {
|
||||||
await AntennaNotes.update(
|
// await AntennaNotes.update(
|
||||||
{
|
// {
|
||||||
antennaId: In(myAntennas.map((a) => a.id)),
|
// antennaId: In(myAntennas.map((a) => a.id)),
|
||||||
noteId: In(readAntennaNotes.map((n) => n.id)),
|
// noteId: In(readAntennaNotes.map((n) => n.id)),
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
read: true,
|
// read: true,
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
|
|
||||||
// TODO: まとめてクエリしたい
|
// // TODO: まとめてクエリしたい
|
||||||
for (const antenna of myAntennas) {
|
// for (const antenna of myAntennas) {
|
||||||
const count = await AntennaNotes.countBy({
|
// const count = await AntennaNotes.countBy({
|
||||||
antennaId: antenna.id,
|
// antennaId: antenna.id,
|
||||||
read: false,
|
// read: false,
|
||||||
});
|
// });
|
||||||
|
|
||||||
if (count === 0) {
|
// if (count === 0) {
|
||||||
publishMainStream(userId, "readAntenna", antenna);
|
// publishMainStream(userId, "readAntenna", antenna);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
Users.getHasUnreadAntenna(userId).then((unread) => {
|
// Users.getHasUnreadAntenna(userId).then((unread) => {
|
||||||
if (!unread) {
|
// if (!unread) {
|
||||||
publishMainStream(userId, "readAllAntennas");
|
// publishMainStream(userId, "readAllAntennas");
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue