diff --git a/server/scripts/20210716000000-backfill-revisions.js b/server/scripts/20210716000000-backfill-revisions.js new file mode 100644 index 00000000..95b95bf5 --- /dev/null +++ b/server/scripts/20210716000000-backfill-revisions.js @@ -0,0 +1,62 @@ +// @flow +import "./bootstrap"; +import debug from "debug"; +import { Revision, Document, Event } from "../models"; + +const log = debug("server"); +let page = 0; +let limit = 100; + +export default async function main(exit = false) { + const work = async (page: number) => { + log(`Backfill revision events… page ${page}`); + + const revisions = await Revision.findAll({ + limit, + offset: page * limit, + order: [["createdAt", "DESC"]], + include: [ + { + model: Document, + as: "document", + required: true, + paranoid: false, + }, + ], + }); + + for (const revision of revisions) { + try { + await Event.findOrCreate({ + where: { + name: "revisions.create", + modelId: revision.id, + documentId: revision.documentId, + actorId: revision.userId, + teamId: revision.document.teamId, + }, + defaults: { + createdAt: revision.createdAt, + }, + }); + } catch (err) { + console.error(`Failed at ${revision.id}:`, err); + continue; + } + } + + return revisions.length === limit ? work(page + 1) : undefined; + }; + + await work(page); + + if (exit) { + log("Backfill complete"); + process.exit(0); + } +} + +// In the test suite we import the script rather than run via node CLI +if (process.env.NODE_ENV !== "test") { + main(true); +} diff --git a/server/scripts/20210716000000-backfill-revisions.test.js b/server/scripts/20210716000000-backfill-revisions.test.js new file mode 100644 index 00000000..88f51323 --- /dev/null +++ b/server/scripts/20210716000000-backfill-revisions.test.js @@ -0,0 +1,52 @@ +// @flow +import { Revision, Event } from "../models"; +import { buildDocument } from "../test/factories"; +import { flushdb } from "../test/support"; +import script from "./20210716000000-backfill-revisions"; + +beforeEach(() => flushdb()); + +describe("#work", () => { + it("should create events for revisions", async () => { + const document = await buildDocument(); + const revision = await Revision.createFromDocument(document); + + await script(); + + const event = await Event.findOne(); + + expect(event.name).toEqual("revisions.create"); + expect(event.modelId).toEqual(revision.id); + expect(event.documentId).toEqual(document.id); + expect(event.teamId).toEqual(document.teamId); + expect(event.createdAt).toEqual(revision.createdAt); + }); + + it("should create events for revisions of deleted documents", async () => { + const document = await buildDocument(); + const revision = await Revision.createFromDocument(document); + + await document.destroy(); + + await script(); + + const event = await Event.findOne(); + + expect(event.name).toEqual("revisions.create"); + expect(event.modelId).toEqual(revision.id); + expect(event.documentId).toEqual(document.id); + expect(event.teamId).toEqual(document.teamId); + expect(event.createdAt).toEqual(revision.createdAt); + }); + + it("should be idempotent", async () => { + const document = await buildDocument(); + await Revision.createFromDocument(document); + + await script(); + await script(); + + const count = await Event.count(); + expect(count).toEqual(1); + }); +});