summaryrefslogtreecommitdiff
path: root/packages/backend/src/core/LatestNoteService.ts
diff options
context:
space:
mode:
authorHazelnoot <acomputerdog@gmail.com>2024-10-15 18:01:57 -0400
committerHazelnoot <acomputerdog@gmail.com>2024-10-15 18:09:11 -0400
commit8a34d8e9d25546f7ef42f072a69f9923d5ba2e84 (patch)
tree4523856296102c786f724243375d198f3fada789 /packages/backend/src/core/LatestNoteService.ts
parentFix indentation on locales/generateDTS.js (diff)
parentmerge: Refresh locales after any change, not just a version update (resolves ... (diff)
downloadsharkey-8a34d8e9d25546f7ef42f072a69f9923d5ba2e84.tar.gz
sharkey-8a34d8e9d25546f7ef42f072a69f9923d5ba2e84.tar.bz2
sharkey-8a34d8e9d25546f7ef42f072a69f9923d5ba2e84.zip
Merge branch 'develop' into feature/2024.9.0
# Conflicts: # locales/en-US.yml # locales/ja-JP.yml # packages/backend/src/core/NoteCreateService.ts # packages/backend/src/core/NoteDeleteService.ts # packages/backend/src/core/NoteEditService.ts # packages/frontend-shared/js/config.ts # packages/frontend/src/boot/common.ts # packages/frontend/src/pages/following-feed.vue # packages/misskey-js/src/autogen/endpoint.ts
Diffstat (limited to 'packages/backend/src/core/LatestNoteService.ts')
-rw-r--r--packages/backend/src/core/LatestNoteService.ts139
1 files changed, 139 insertions, 0 deletions
diff --git a/packages/backend/src/core/LatestNoteService.ts b/packages/backend/src/core/LatestNoteService.ts
new file mode 100644
index 0000000000..c379805506
--- /dev/null
+++ b/packages/backend/src/core/LatestNoteService.ts
@@ -0,0 +1,139 @@
+import { Inject, Injectable } from '@nestjs/common';
+import { Not } from 'typeorm';
+import { MiNote } from '@/models/Note.js';
+import { isPureRenote } from '@/misc/is-renote.js';
+import { SkLatestNote } from '@/models/LatestNote.js';
+import { DI } from '@/di-symbols.js';
+import type { LatestNotesRepository, NotesRepository } from '@/models/_.js';
+import { LoggerService } from '@/core/LoggerService.js';
+import Logger from '@/logger.js';
+
+@Injectable()
+export class LatestNoteService {
+ private readonly logger: Logger;
+
+ constructor(
+ @Inject(DI.notesRepository)
+ private notesRepository: NotesRepository,
+
+ @Inject(DI.latestNotesRepository)
+ private latestNotesRepository: LatestNotesRepository,
+
+ loggerService: LoggerService,
+ ) {
+ this.logger = loggerService.getLogger('LatestNoteService');
+ }
+
+ handleUpdatedNoteBG(before: MiNote, after: MiNote): void {
+ this
+ .handleUpdatedNote(before, after)
+ .catch(err => this.logger.error('Unhandled exception while updating latest_note (after update):', err));
+ }
+
+ async handleUpdatedNote(before: MiNote, after: MiNote): Promise<void> {
+ // If the key didn't change, then there's nothing to update
+ if (SkLatestNote.areEquivalent(before, after)) return;
+
+ // Simulate update as delete + create
+ await this.handleDeletedNote(before);
+ await this.handleCreatedNote(after);
+ }
+
+ handleCreatedNoteBG(note: MiNote): void {
+ this
+ .handleCreatedNote(note)
+ .catch(err => this.logger.error('Unhandled exception while updating latest_note (after create):', err));
+ }
+
+ async handleCreatedNote(note: MiNote): Promise<void> {
+ // Ignore DMs.
+ // Followers-only posts are *included*, as this table is used to back the "following" feed.
+ if (note.visibility === 'specified') return;
+
+ // Ignore pure renotes
+ if (isPureRenote(note)) return;
+
+ // Compute the compound key of the entry to check
+ const key = SkLatestNote.keyFor(note);
+
+ // Make sure that this isn't an *older* post.
+ // We can get older posts through replies, lookups, updates, etc.
+ const currentLatest = await this.latestNotesRepository.findOneBy(key);
+ if (currentLatest != null && currentLatest.noteId >= note.id) return;
+
+ // Record this as the latest note for the given user
+ const latestNote = new SkLatestNote({
+ ...key,
+ noteId: note.id,
+ });
+ await this.latestNotesRepository.upsert(latestNote, ['userId', 'isPublic', 'isReply', 'isQuote']);
+ }
+
+ handleDeletedNoteBG(note: MiNote): void {
+ this
+ .handleDeletedNote(note)
+ .catch(err => this.logger.error('Unhandled exception while updating latest_note (after delete):', err));
+ }
+
+ async handleDeletedNote(note: MiNote): Promise<void> {
+ // If it's a DM, then it can't possibly be the latest note so we can safely skip this.
+ if (note.visibility === 'specified') return;
+
+ // If it's a pure renote, then it can't possibly be the latest note so we can safely skip this.
+ if (isPureRenote(note)) return;
+
+ // Compute the compound key of the entry to check
+ const key = SkLatestNote.keyFor(note);
+
+ // Check if the deleted note was possibly the latest for the user
+ const existingLatest = await this.latestNotesRepository.findOneBy(key);
+ if (existingLatest == null || existingLatest.noteId !== note.id) return;
+
+ // Find the newest remaining note for the user.
+ // We exclude DMs and pure renotes.
+ const nextLatest = await this.notesRepository
+ .createQueryBuilder('note')
+ .select()
+ .where({
+ userId: key.userId,
+ visibility: key.isPublic
+ ? 'public'
+ : Not('specified'),
+ replyId: key.isReply
+ ? Not(null)
+ : null,
+ renoteId: key.isQuote
+ ? Not(null)
+ : null,
+ })
+ .andWhere(`
+ (
+ note."renoteId" IS NULL
+ OR note.text IS NOT NULL
+ OR note.cw IS NOT NULL
+ OR note."replyId" IS NOT NULL
+ OR note."hasPoll"
+ OR note."fileIds" != '{}'
+ )
+ `)
+ .orderBy({ id: 'DESC' })
+ .getOne();
+ if (!nextLatest) return;
+
+ // Record it as the latest
+ const latestNote = new SkLatestNote({
+ ...key,
+ noteId: nextLatest.id,
+ });
+
+ // When inserting the latest note, it's possible that another worker has "raced" the insert and already added a newer note.
+ // We must use orIgnore() to ensure that the query ignores conflicts, otherwise an exception may be thrown.
+ await this.latestNotesRepository
+ .createQueryBuilder('latest')
+ .insert()
+ .into(SkLatestNote)
+ .values(latestNote)
+ .orIgnore()
+ .execute();
+ }
+}