Files
outline/server/queues/processors/DocumentMovedProcessor.ts
Salihu 38a3e651a7 fix: Rebuild of sourced permissions on document move (#11229)
* only recalculate permssions for moved document

* stop duplicating permissions

* add tests

* fix comment

* filter duplicates

* filter duplicates

* minor fixes

* fix child document permissions not being recalculated

* expand tests

* Update DocumentMovedProcessor.test.ts

* Update server/queues/processors/DocumentMovedProcessor.test.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* requested changes

* remove all sourced permissions before calculating new ones

* remove all sourced permissions before recalculating

* Add additional tests

---------

Co-authored-by: Tom Moor <tom@getoutline.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-05 08:22:03 -05:00

111 lines
3.2 KiB
TypeScript

import type { Transaction } from "sequelize";
import { Document, GroupMembership, UserMembership } from "@server/models";
import { sequelize } from "@server/storage/database";
import type { DocumentMovedEvent, Event } from "@server/types";
import BaseProcessor from "./BaseProcessor";
import { Op } from "sequelize";
export default class DocumentMovedProcessor extends BaseProcessor {
static applicableEvents: Event["name"][] = ["documents.move"];
async perform(event: DocumentMovedEvent) {
await sequelize.transaction(async (transaction) => {
const document = await Document.findByPk(event.documentId, {
transaction,
});
if (!document) {
return;
}
// If there are any sourced memberships for this document, we need to go to the source
// memberships and recalculate the membership for the user or group.
const [parentDocumentUserMemberships, parentDocumentGroupMemberships] =
await Promise.all([
document.parentDocumentId
? UserMembership.findRootMembershipsForDocument(
document.parentDocumentId,
undefined,
{ transaction }
)
: [],
document.parentDocumentId
? GroupMembership.findRootMembershipsForDocument(
document.parentDocumentId,
undefined,
{ transaction }
)
: [],
]);
await this.destroyUserMemberships(document.id);
await this.destroyGroupMemberships(document.id);
await this.recalculateUserMemberships(
parentDocumentUserMemberships,
transaction,
document.id
);
await this.recalculateGroupMemberships(
parentDocumentGroupMemberships,
transaction,
document.id
);
});
}
private async destroyUserMemberships(documentId: string) {
const document = await Document.findByPk(documentId);
const childDocumentIds = await document.findAllChildDocumentIds();
await UserMembership.destroy({
where: {
sourceId: { [Op.ne]: null },
documentId: [...childDocumentIds, documentId],
},
});
}
private async destroyGroupMemberships(documentId: string) {
const document = await Document.findByPk(documentId);
const childDocumentIds = await document.findAllChildDocumentIds();
await GroupMembership.destroy({
where: {
sourceId: { [Op.ne]: null },
documentId: [...childDocumentIds, documentId],
},
});
}
private async recalculateUserMemberships(
memberships: UserMembership[],
transaction?: Transaction,
documentId?: string
) {
await Promise.all(
memberships.map((membership) =>
UserMembership.createSourcedMemberships(membership, {
transaction,
documentId,
})
)
);
}
private async recalculateGroupMemberships(
memberships: GroupMembership[],
transaction?: Transaction,
documentId?: string
) {
await Promise.all(
memberships.map((membership) =>
GroupMembership.createSourcedMemberships(membership, {
transaction,
documentId,
})
)
);
}
}