chore: Refactor backlinks and revisions (#1611)
* Update backlinks service to not rely on revisions * fix: Add missing index for finding backlinks * Debounce revision creation (#1616) * refactor debounce logic to service * Debounce slack notification * Revisions created by service * fix: Revision sidebar latest * test: Add tests for notifications
This commit is contained in:
@@ -1,15 +1,17 @@
|
||||
// @flow
|
||||
import { difference } from "lodash";
|
||||
import type { DocumentEvent } from "../events";
|
||||
import { Document, Revision, Backlink } from "../models";
|
||||
import type { DocumentEvent, RevisionEvent } from "../events";
|
||||
import { Document, Backlink } from "../models";
|
||||
import { Op } from "../sequelize";
|
||||
import parseDocumentIds from "../utils/parseDocumentIds";
|
||||
import slugify from "../utils/slugify";
|
||||
|
||||
export default class Backlinks {
|
||||
async on(event: DocumentEvent) {
|
||||
async on(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(
|
||||
@@ -32,36 +34,18 @@ export default class Backlinks {
|
||||
break;
|
||||
}
|
||||
case "documents.update": {
|
||||
// no-op for now
|
||||
if (event.data.autosave) return;
|
||||
|
||||
// no-op for drafts
|
||||
const document = await Document.findByPk(event.documentId);
|
||||
if (!document) return;
|
||||
|
||||
// backlinks are only created for published documents
|
||||
if (!document.publishedAt) return;
|
||||
|
||||
const [currentRevision, previousRevision] = await Revision.findAll({
|
||||
where: { documentId: event.documentId },
|
||||
order: [["createdAt", "desc"]],
|
||||
limit: 2,
|
||||
});
|
||||
const linkIds = parseDocumentIds(document.text);
|
||||
const linkedDocumentIds = [];
|
||||
|
||||
// before parsing document text we must make sure it's been migrated to
|
||||
// the latest version or the parser may fail on version differences
|
||||
await currentRevision.migrateVersion();
|
||||
if (previousRevision) {
|
||||
await previousRevision.migrateVersion();
|
||||
}
|
||||
|
||||
const previousLinkIds = previousRevision
|
||||
? parseDocumentIds(previousRevision.text)
|
||||
: [];
|
||||
const currentLinkIds = parseDocumentIds(currentRevision.text);
|
||||
const addedLinkIds = difference(currentLinkIds, previousLinkIds);
|
||||
const removedLinkIds = difference(previousLinkIds, currentLinkIds);
|
||||
|
||||
// add any new backlinks that were created
|
||||
// create or find existing backlink records for referenced docs
|
||||
await Promise.all(
|
||||
addedLinkIds.map(async (linkId) => {
|
||||
linkIds.map(async (linkId) => {
|
||||
const linkedDocument = await Document.findByPk(linkId);
|
||||
if (!linkedDocument || linkedDocument.id === event.documentId) {
|
||||
return;
|
||||
@@ -73,35 +57,31 @@ export default class Backlinks {
|
||||
reverseDocumentId: event.documentId,
|
||||
},
|
||||
defaults: {
|
||||
userId: currentRevision.userId,
|
||||
userId: document.lastModifiedById,
|
||||
},
|
||||
});
|
||||
linkedDocumentIds.push(linkedDocument.id);
|
||||
})
|
||||
);
|
||||
|
||||
// delete any backlinks that were removed
|
||||
await Promise.all(
|
||||
removedLinkIds.map(async (linkId) => {
|
||||
const document = await Document.findByPk(linkId, {
|
||||
paranoid: false,
|
||||
});
|
||||
if (document) {
|
||||
await Backlink.destroy({
|
||||
where: {
|
||||
documentId: document.id,
|
||||
reverseDocumentId: event.documentId,
|
||||
},
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
// delete any backlinks that no longer exist
|
||||
await Backlink.destroy({
|
||||
where: {
|
||||
documentId: {
|
||||
[Op.notIn]: linkedDocumentIds,
|
||||
},
|
||||
reverseDocumentId: event.documentId,
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "documents.title_change": {
|
||||
const document = await Document.findByPk(event.documentId);
|
||||
if (!document) return;
|
||||
|
||||
if (
|
||||
!previousRevision ||
|
||||
currentRevision.title === previousRevision.title
|
||||
) {
|
||||
break;
|
||||
}
|
||||
// might as well check
|
||||
const { title, previousTitle } = event.data;
|
||||
if (!previousTitle || title === previousTitle) break;
|
||||
|
||||
// update any link titles in documents that lead to this one
|
||||
const backlinks = await Backlink.findAll({
|
||||
@@ -113,7 +93,7 @@ export default class Backlinks {
|
||||
|
||||
await Promise.all(
|
||||
backlinks.map(async (backlink) => {
|
||||
const previousUrl = `/doc/${slugify(previousRevision.title)}-${
|
||||
const previousUrl = `/doc/${slugify(previousTitle)}-${
|
||||
document.urlId
|
||||
}`;
|
||||
|
||||
@@ -121,8 +101,8 @@ export default class Backlinks {
|
||||
// the old title as anchor text. Go ahead and update those to the
|
||||
// new title automatically
|
||||
backlink.reverseDocument.text = backlink.reverseDocument.text.replace(
|
||||
`[${previousRevision.title}](${previousUrl})`,
|
||||
`[${document.title}](${document.url})`
|
||||
`[${previousTitle}](${previousUrl})`,
|
||||
`[${title}](${document.url})`
|
||||
);
|
||||
await backlink.reverseDocument.save({
|
||||
silent: true,
|
||||
@@ -136,12 +116,10 @@ export default class Backlinks {
|
||||
case "documents.delete": {
|
||||
await Backlink.destroy({
|
||||
where: {
|
||||
reverseDocumentId: event.documentId,
|
||||
},
|
||||
});
|
||||
await Backlink.destroy({
|
||||
where: {
|
||||
documentId: event.documentId,
|
||||
[Op.or]: [
|
||||
{ reverseDocumentId: event.documentId },
|
||||
{ documentId: event.documentId },
|
||||
],
|
||||
},
|
||||
});
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user