Fix for CASCADE DELETE not being federated (#5812)
* Fix for CASCADE DELETE not being federated * Use JOIN to get user * fix typo
This commit is contained in:
parent
8755b5f353
commit
f40dcbfe13
|
@ -1,11 +1,17 @@
|
|||
import { DriveFile } from '../../models/entities/drive-file';
|
||||
import { InternalStorage } from './internal-storage';
|
||||
import { DriveFiles, Instances, Notes } from '../../models';
|
||||
import { DriveFiles, Instances, Notes, Users } from '../../models';
|
||||
import { driveChart, perUserDriveChart, instanceChart } from '../chart';
|
||||
import { createDeleteObjectStorageFileJob } from '../../queue';
|
||||
import { fetchMeta } from '../../misc/fetch-meta';
|
||||
import { getS3 } from './s3';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { Note } from '../../models/entities/note';
|
||||
import { renderActivity } from '../../remote/activitypub/renderer';
|
||||
import renderDelete from '../../remote/activitypub/renderer/delete';
|
||||
import renderTombstone from '../../remote/activitypub/renderer/tombstone';
|
||||
import config from '../../config';
|
||||
import { deliverToFollowers } from '../../remote/activitypub/deliver-manager';
|
||||
|
||||
export async function deleteFile(file: DriveFile, isExpired = false) {
|
||||
if (file.storedInternal) {
|
||||
|
@ -63,7 +69,7 @@ export async function deleteFileSync(file: DriveFile, isExpired = false) {
|
|||
postProcess(file, isExpired);
|
||||
}
|
||||
|
||||
function postProcess(file: DriveFile, isExpired = false) {
|
||||
async function postProcess(file: DriveFile, isExpired = false) {
|
||||
// リモートファイル期限切れ削除後は直リンクにする
|
||||
if (isExpired && file.userHost !== null && file.uri != null) {
|
||||
DriveFiles.update(file.id, {
|
||||
|
@ -81,8 +87,23 @@ function postProcess(file: DriveFile, isExpired = false) {
|
|||
DriveFiles.delete(file.id);
|
||||
|
||||
// TODO: トランザクション
|
||||
const relatedNotes = await findRelatedNotes(file.id);
|
||||
for (const relatedNote of relatedNotes) { // for each note with deleted driveFile
|
||||
const cascadingNotes = (await findCascadingNotes(relatedNote)).filter(note => !note.localOnly);
|
||||
for (const cascadingNote of cascadingNotes) { // for each notes subject to cascade deletion
|
||||
if (!cascadingNote.user) continue;
|
||||
if (!Users.isLocalUser(cascadingNote.user)) continue;
|
||||
const content = renderActivity(renderDelete(renderTombstone(`${config.url}/notes/${cascadingNote.id}`), cascadingNote.user));
|
||||
deliverToFollowers(cascadingNote.user, content); // federate delete msg
|
||||
}
|
||||
if (!relatedNote.user) continue;
|
||||
if (Users.isLocalUser(relatedNote.user)) {
|
||||
const content = renderActivity(renderDelete(renderTombstone(`${config.url}/notes/${relatedNote.id}`), relatedNote.user));
|
||||
deliverToFollowers(relatedNote.user, content);
|
||||
}
|
||||
}
|
||||
Notes.createQueryBuilder().delete()
|
||||
.where(':id = ANY(fileIds)', { id: file.id })
|
||||
.where(':id = ANY("fileIds")', { id: file.id })
|
||||
.execute();
|
||||
}
|
||||
|
||||
|
@ -106,3 +127,32 @@ export async function deleteObjectStorageFile(key: string) {
|
|||
Key: key
|
||||
}).promise();
|
||||
}
|
||||
|
||||
async function findRelatedNotes(fileId: string) {
|
||||
// NOTE: When running raw query, TypeORM converts field name to lowercase. Wrap in quotes to prevent conversion.
|
||||
const relatedNotes = await Notes.createQueryBuilder('note').where(':id = ANY("fileIds")', { id: fileId }).getMany();
|
||||
for (const relatedNote of relatedNotes) {
|
||||
const user = await Users.findOne({ id: relatedNote.userId });
|
||||
if (user)
|
||||
relatedNote.user = user;
|
||||
}
|
||||
return relatedNotes;
|
||||
}
|
||||
|
||||
async function findCascadingNotes(note: Note) {
|
||||
const cascadingNotes: Note[] = [];
|
||||
|
||||
const recursive = async (noteId: string) => {
|
||||
const query = Notes.createQueryBuilder('note')
|
||||
.where('note.replyId = :noteId', { noteId })
|
||||
.leftJoinAndSelect('note.user', 'user');
|
||||
const replies = await query.getMany();
|
||||
for (const reply of replies) {
|
||||
cascadingNotes.push(reply);
|
||||
await recursive(reply.id);
|
||||
}
|
||||
};
|
||||
await recursive(note.id);
|
||||
|
||||
return cascadingNotes.filter(note => note.userHost === null); // filter out non-local users
|
||||
}
|
||||
|
|
|
@ -20,11 +20,6 @@ import { deliverToFollowers } from '../../remote/activitypub/deliver-manager';
|
|||
export default async function(user: User, note: Note, quiet = false) {
|
||||
const deletedAt = new Date();
|
||||
|
||||
await Notes.delete({
|
||||
id: note.id,
|
||||
userId: user.id
|
||||
});
|
||||
|
||||
if (note.renoteId) {
|
||||
Notes.decrement({ id: note.renoteId }, 'renoteCount', 1);
|
||||
Notes.decrement({ id: note.renoteId }, 'score', 1);
|
||||
|
@ -39,6 +34,7 @@ export default async function(user: User, note: Note, quiet = false) {
|
|||
if (Users.isLocalUser(user)) {
|
||||
let renote: Note | undefined;
|
||||
|
||||
// if deletd note is renote
|
||||
if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length == 0)) {
|
||||
renote = await Notes.findOne({
|
||||
id: note.renoteId
|
||||
|
@ -51,6 +47,15 @@ export default async function(user: User, note: Note, quiet = false) {
|
|||
|
||||
deliverToFollowers(user, content);
|
||||
}
|
||||
|
||||
// also deliever delete activity to cascaded notes
|
||||
const cascadingNotes = (await findCascadingNotes(note)).filter(note => !note.localOnly); // filter out local-only notes
|
||||
for (const cascadingNote of cascadingNotes) {
|
||||
if (!cascadingNote.user) continue;
|
||||
if (!Users.isLocalUser(cascadingNote.user)) continue;
|
||||
const content = renderActivity(renderDelete(renderTombstone(`${config.url}/notes/${cascadingNote.id}`), cascadingNote.user));
|
||||
deliverToFollowers(cascadingNote.user, content);
|
||||
}
|
||||
//#endregion
|
||||
|
||||
// 統計を更新
|
||||
|
@ -64,4 +69,27 @@ export default async function(user: User, note: Note, quiet = false) {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
await Notes.delete({
|
||||
id: note.id,
|
||||
userId: user.id
|
||||
});
|
||||
}
|
||||
|
||||
async function findCascadingNotes(note: Note) {
|
||||
const cascadingNotes: Note[] = [];
|
||||
|
||||
const recursive = async (noteId: string) => {
|
||||
const query = Notes.createQueryBuilder('note')
|
||||
.where('note.replyId = :noteId', { noteId })
|
||||
.leftJoinAndSelect('note.user', 'user');
|
||||
const replies = await query.getMany();
|
||||
for (const reply of replies) {
|
||||
cascadingNotes.push(reply);
|
||||
await recursive(reply.id);
|
||||
}
|
||||
};
|
||||
await recursive(note.id);
|
||||
|
||||
return cascadingNotes.filter(note => note.userHost === null); // filter out non-local users
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue