summaryrefslogtreecommitdiff
path: root/packages/backend/src
diff options
context:
space:
mode:
authorsyuilo <4439005+syuilo@users.noreply.github.com>2025-07-31 14:40:51 +0900
committerGitHub <noreply@github.com>2025-07-31 14:40:51 +0900
commitf2a23fb55ef2100bd26e3f2bcd7f939052c2ea09 (patch)
treedff9f61bbb387e2e7d871cdf1d83d1580ebef10a /packages/backend/src
parentfix(test): Fix name of a test in e2e/timelines.ts (#16334) (diff)
downloadmisskey-f2a23fb55ef2100bd26e3f2bcd7f939052c2ea09.tar.gz
misskey-f2a23fb55ef2100bd26e3f2bcd7f939052c2ea09.tar.bz2
misskey-f2a23fb55ef2100bd26e3f2bcd7f939052c2ea09.zip
ノートの脱CASCADE削除 (#16332)
* wip * Update CHANGELOG.md * Update QueryService.ts * Update QueryService.ts * wip * Update MkNoteDetailed.vue * Update NoteEntityService.ts * wip * Update antennas.ts * Update create.ts * Update NoteEntityService.ts * wip * Update CHANGELOG.md * Update NoteEntityService.ts * Update NoteCreateService.ts * Update note.test.ts * Update note.test.ts * Update ClientServerService.ts * Update ClientServerService.ts * add error handling * Update NoteDeleteService.ts * Update CHANGELOG.md * Update entities.ts * Update entities.ts * Update misskey-js.api.md
Diffstat (limited to 'packages/backend/src')
-rw-r--r--packages/backend/src/core/NoteCreateService.ts8
-rw-r--r--packages/backend/src/core/NoteDeleteService.ts36
-rw-r--r--packages/backend/src/core/QueryService.ts4
-rw-r--r--packages/backend/src/core/entities/NoteEntityService.ts25
-rw-r--r--packages/backend/src/models/Note.ts4
-rw-r--r--packages/backend/src/server/api/GetterService.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/notes/create.ts10
-rw-r--r--packages/backend/src/server/api/endpoints/notes/show.ts2
-rw-r--r--packages/backend/src/server/web/ClientServerService.ts9
9 files changed, 46 insertions, 56 deletions
diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts
index 469426f87e..1eefcfa054 100644
--- a/packages/backend/src/core/NoteCreateService.ts
+++ b/packages/backend/src/core/NoteCreateService.ts
@@ -421,7 +421,7 @@ export class NoteCreateService implements OnApplicationShutdown {
emojis,
userId: user.id,
localOnly: data.localOnly!,
- reactionAcceptance: data.reactionAcceptance,
+ reactionAcceptance: data.reactionAcceptance ?? null,
visibility: data.visibility as any,
visibleUserIds: data.visibility === 'specified'
? data.visibleUsers
@@ -483,7 +483,11 @@ export class NoteCreateService implements OnApplicationShutdown {
await this.notesRepository.insert(insert);
}
- return insert;
+ return {
+ ...insert,
+ reply: data.reply ?? null,
+ renote: data.renote ?? null,
+ };
} catch (e) {
// duplicate key error
if (isDuplicateKeyValueError(e)) {
diff --git a/packages/backend/src/core/NoteDeleteService.ts b/packages/backend/src/core/NoteDeleteService.ts
index e394506a44..af1f0eda9a 100644
--- a/packages/backend/src/core/NoteDeleteService.ts
+++ b/packages/backend/src/core/NoteDeleteService.ts
@@ -62,7 +62,6 @@ export class NoteDeleteService {
*/
async delete(user: { id: MiUser['id']; uri: MiUser['uri']; host: MiUser['host']; isBot: MiUser['isBot']; }, note: MiNote, quiet = false, deleter?: MiUser) {
const deletedAt = new Date();
- const cascadingNotes = await this.findCascadingNotes(note);
if (note.replyId) {
await this.notesRepository.decrement({ id: note.replyId }, 'repliesCount', 1);
@@ -90,15 +89,6 @@ export class NoteDeleteService {
this.deliverToConcerned(user, note, content);
}
-
- // also deliver delete activity to cascaded notes
- const federatedLocalCascadingNotes = (cascadingNotes).filter(note => !note.localOnly && note.userHost == null); // filter out local-only notes
- for (const cascadingNote of federatedLocalCascadingNotes) {
- if (!cascadingNote.user) continue;
- if (!this.userEntityService.isLocalUser(cascadingNote.user)) continue;
- const content = this.apRendererService.addContext(this.apRendererService.renderDelete(this.apRendererService.renderTombstone(`${this.config.url}/notes/${cascadingNote.id}`), cascadingNote.user));
- this.deliverToConcerned(cascadingNote.user, cascadingNote, content);
- }
//#endregion
this.notesChart.update(note, false);
@@ -118,9 +108,6 @@ export class NoteDeleteService {
}
}
- for (const cascadingNote of cascadingNotes) {
- this.searchService.unindexNote(cascadingNote);
- }
this.searchService.unindexNote(note);
await this.notesRepository.delete({
@@ -141,29 +128,6 @@ export class NoteDeleteService {
}
@bindThis
- private async findCascadingNotes(note: MiNote): Promise<MiNote[]> {
- const recursive = async (noteId: string): Promise<MiNote[]> => {
- const query = this.notesRepository.createQueryBuilder('note')
- .where('note.replyId = :noteId', { noteId })
- .orWhere(new Brackets(q => {
- q.where('note.renoteId = :noteId', { noteId })
- .andWhere('note.text IS NOT NULL');
- }))
- .leftJoinAndSelect('note.user', 'user');
- const replies = await query.getMany();
-
- return [
- replies,
- ...await Promise.all(replies.map(reply => recursive(reply.id))),
- ].flat();
- };
-
- const cascadingNotes: MiNote[] = await recursive(note.id);
-
- return cascadingNotes;
- }
-
- @bindThis
private async getMentionedRemoteUsers(note: MiNote) {
const where = [] as any[];
diff --git a/packages/backend/src/core/QueryService.ts b/packages/backend/src/core/QueryService.ts
index d398e83230..49f93ad108 100644
--- a/packages/backend/src/core/QueryService.ts
+++ b/packages/backend/src/core/QueryService.ts
@@ -360,7 +360,7 @@ export class QueryService {
public generateSuspendedUserQueryForNote(q: SelectQueryBuilder<any>, excludeAuthor?: boolean): void {
if (excludeAuthor) {
const brakets = (user: string) => new Brackets(qb => qb
- .where(`note.${user}Id IS NULL`)
+ .where(`${user}.id IS NULL`) // そもそもreplyやrenoteではない、もしくはleftjoinなどでuserが存在しなかった場合を考慮
.orWhere(`user.id = ${user}.id`)
.orWhere(`${user}.isSuspended = FALSE`));
q
@@ -368,7 +368,7 @@ export class QueryService {
.andWhere(brakets('renoteUser'));
} else {
const brakets = (user: string) => new Brackets(qb => qb
- .where(`note.${user}Id IS NULL`)
+ .where(`${user}.id IS NULL`) // そもそもreplyやrenoteではない、もしくはleftjoinなどでuserが存在しなかった場合を考慮
.orWhere(`${user}.isSuspended = FALSE`));
q
.andWhere('user.isSuspended = FALSE')
diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts
index 92caad908c..6871ba2c72 100644
--- a/packages/backend/src/core/entities/NoteEntityService.ts
+++ b/packages/backend/src/core/entities/NoteEntityService.ts
@@ -4,7 +4,7 @@
*/
import { Inject, Injectable } from '@nestjs/common';
-import { In } from 'typeorm';
+import { EntityNotFoundError, In } from 'typeorm';
import { ModuleRef } from '@nestjs/core';
import { DI } from '@/di-symbols.js';
import type { Packed } from '@/misc/json-schema.js';
@@ -46,6 +46,17 @@ function getAppearNoteIds(notes: MiNote[]): Set<string> {
return appearNoteIds;
}
+async function nullIfEntityNotFound<T>(promise: Promise<T>): Promise<T | null> {
+ try {
+ return await promise;
+ } catch (err) {
+ if (err instanceof EntityNotFoundError) {
+ return null;
+ }
+ throw err;
+ }
+}
+
@Injectable()
export class NoteEntityService implements OnModuleInit {
private userEntityService: UserEntityService;
@@ -436,19 +447,21 @@ export class NoteEntityService implements OnModuleInit {
...(opts.detail ? {
clippedCount: note.clippedCount,
- reply: note.replyId ? this.pack(note.reply ?? note.replyId, me, {
+ // そもそもJOINしていない場合はundefined、JOINしたけど存在していなかった場合はnullで区別される
+ reply: (note.replyId && note.reply === null) ? null : note.replyId ? nullIfEntityNotFound(this.pack(note.reply ?? note.replyId, me, {
detail: false,
skipHide: opts.skipHide,
withReactionAndUserPairCache: opts.withReactionAndUserPairCache,
_hint_: options?._hint_,
- }) : undefined,
+ })) : undefined,
- renote: note.renoteId ? this.pack(note.renote ?? note.renoteId, me, {
+ // そもそもJOINしていない場合はundefined、JOINしたけど存在していなかった場合はnullで区別される
+ renote: (note.renoteId && note.renote === null) ? null : note.renoteId ? nullIfEntityNotFound(this.pack(note.renote ?? note.renoteId, me, {
detail: true,
skipHide: opts.skipHide,
withReactionAndUserPairCache: opts.withReactionAndUserPairCache,
_hint_: options?._hint_,
- }) : undefined,
+ })) : undefined,
poll: note.hasPoll ? this.populatePoll(note, meId) : undefined,
@@ -591,7 +604,7 @@ export class NoteEntityService implements OnModuleInit {
private findNoteOrFail(id: string): Promise<MiNote> {
return this.notesRepository.findOneOrFail({
where: { id },
- relations: ['user'],
+ relations: ['user', 'renote', 'reply'],
});
}
diff --git a/packages/backend/src/models/Note.ts b/packages/backend/src/models/Note.ts
index 9822ec94e4..ff46615729 100644
--- a/packages/backend/src/models/Note.ts
+++ b/packages/backend/src/models/Note.ts
@@ -36,7 +36,7 @@ export class MiNote {
public replyId: MiNote['id'] | null;
@ManyToOne(type => MiNote, {
- onDelete: 'CASCADE',
+ createForeignKeyConstraints: false,
})
@JoinColumn()
public reply: MiNote | null;
@@ -50,7 +50,7 @@ export class MiNote {
public renoteId: MiNote['id'] | null;
@ManyToOne(type => MiNote, {
- onDelete: 'CASCADE',
+ createForeignKeyConstraints: false,
})
@JoinColumn()
public renote: MiNote | null;
diff --git a/packages/backend/src/server/api/GetterService.ts b/packages/backend/src/server/api/GetterService.ts
index 444e6db744..8f4213dfb6 100644
--- a/packages/backend/src/server/api/GetterService.ts
+++ b/packages/backend/src/server/api/GetterService.ts
@@ -40,8 +40,8 @@ export class GetterService {
}
@bindThis
- public async getNoteWithUser(noteId: MiNote['id']) {
- const note = await this.notesRepository.findOne({ where: { id: noteId }, relations: ['user'] });
+ public async getNoteWithRelations(noteId: MiNote['id']) {
+ const note = await this.notesRepository.findOne({ where: { id: noteId }, relations: ['user', 'reply', 'renote', 'reply.user', 'renote.user'] });
if (note == null) {
throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', 'No such note.');
diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts
index 253a360815..7caea8eedc 100644
--- a/packages/backend/src/server/api/endpoints/notes/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/create.ts
@@ -269,7 +269,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
let renote: MiNote | null = null;
if (ps.renoteId != null) {
// Fetch renote to note
- renote = await this.notesRepository.findOneBy({ id: ps.renoteId });
+ renote = await this.notesRepository.findOne({
+ where: { id: ps.renoteId },
+ relations: ['user', 'renote', 'reply'],
+ });
if (renote == null) {
throw new ApiError(meta.errors.noSuchRenoteTarget);
@@ -315,7 +318,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
let reply: MiNote | null = null;
if (ps.replyId != null) {
// Fetch reply
- reply = await this.notesRepository.findOneBy({ id: ps.replyId });
+ reply = await this.notesRepository.findOne({
+ where: { id: ps.replyId },
+ relations: ['user'],
+ });
if (reply == null) {
throw new ApiError(meta.errors.noSuchReplyTarget);
diff --git a/packages/backend/src/server/api/endpoints/notes/show.ts b/packages/backend/src/server/api/endpoints/notes/show.ts
index b93c73b0c5..cae0e752da 100644
--- a/packages/backend/src/server/api/endpoints/notes/show.ts
+++ b/packages/backend/src/server/api/endpoints/notes/show.ts
@@ -55,7 +55,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private getterService: GetterService,
) {
super(meta, paramDef, async (ps, me) => {
- const note = await this.getterService.getNoteWithUser(ps.noteId).catch(err => {
+ const note = await this.getterService.getNoteWithRelations(ps.noteId).catch(err => {
if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
throw err;
});
diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts
index 8ca61a497d..4d122b0fcf 100644
--- a/packages/backend/src/server/web/ClientServerService.ts
+++ b/packages/backend/src/server/web/ClientServerService.ts
@@ -580,7 +580,7 @@ export class ClientServerService {
id: request.params.note,
visibility: In(['public', 'home']),
},
- relations: ['user'],
+ relations: ['user', 'reply', 'renote'],
});
if (
@@ -821,8 +821,11 @@ export class ClientServerService {
fastify.get<{ Params: { note: string; } }>('/embed/notes/:note', async (request, reply) => {
reply.removeHeader('X-Frame-Options');
- const note = await this.notesRepository.findOneBy({
- id: request.params.note,
+ const note = await this.notesRepository.findOne({
+ where: {
+ id: request.params.note,
+ },
+ relations: ['user', 'reply', 'renote'],
});
if (note == null) return;