Files
outline/server/queues/processors/BacklinksProcessor.ts

124 lines
3.3 KiB
TypeScript

import { Op } from "sequelize";
import { Document, Backlink } from "@server/models";
import { Event, DocumentEvent, RevisionEvent } from "@server/types";
import parseDocumentIds from "@server/utils/parseDocumentIds";
import BaseProcessor from "./BaseProcessor";
export default class BacklinksProcessor extends BaseProcessor {
static applicableEvents: Event["name"][] = [
"documents.publish",
"documents.update",
//"documents.title_change",
"documents.delete",
];
async perform(event: DocumentEvent | RevisionEvent) {
switch (event.name) {
case "documents.publish": {
const document = await Document.findByPk(event.documentId);
if (!document) {
return;
}
const linkIds = parseDocumentIds(document.text);
await Promise.all(
linkIds.map(async (linkId) => {
const linkedDocument = await Document.findByPk(linkId);
if (!linkedDocument || linkedDocument.id === event.documentId) {
return;
}
await Backlink.findOrCreate({
where: {
documentId: linkedDocument.id,
reverseDocumentId: event.documentId,
},
defaults: {
userId: document.lastModifiedById,
},
});
})
);
break;
}
case "documents.update": {
const document = await Document.findByPk(event.documentId);
if (!document) {
return;
}
// backlinks are only created for published documents
if (!document.publishedAt) {
return;
}
const linkIds = parseDocumentIds(document.text);
const linkedDocumentIds: string[] = [];
// create or find existing backlink records for referenced docs
await Promise.all(
linkIds.map(async (linkId) => {
const linkedDocument = await Document.findByPk(linkId);
if (!linkedDocument || linkedDocument.id === event.documentId) {
return;
}
await Backlink.findOrCreate({
where: {
documentId: linkedDocument.id,
reverseDocumentId: event.documentId,
},
defaults: {
userId: document.lastModifiedById,
},
});
linkedDocumentIds.push(linkedDocument.id);
})
);
// delete any backlinks that no longer exist
await Backlink.destroy({
where: {
documentId: {
[Op.notIn]: linkedDocumentIds,
},
reverseDocumentId: event.documentId,
},
});
break;
}
case "documents.title_change": {
// might as well check
const { title, previousTitle } = event.data;
if (!previousTitle || title === previousTitle) {
break;
}
// TODO: Handle re-writing of titles into CRDT
break;
}
case "documents.delete": {
await Backlink.destroy({
where: {
[Op.or]: [
{
reverseDocumentId: event.documentId,
},
{
documentId: event.documentId,
},
],
},
});
break;
}
default:
}
}
}