import Router from "koa-router"; import { Op } from "sequelize"; import { RevisionHelper } from "@shared/utils/RevisionHelper"; import slugify from "@shared/utils/slugify"; import { ValidationError } from "@server/errors"; import auth from "@server/middlewares/authentication"; import validate from "@server/middlewares/validate"; import { Document, Revision } from "@server/models"; import { DocumentHelper } from "@server/models/helpers/DocumentHelper"; import { authorize } from "@server/policies"; import { presentRevision } from "@server/presenters"; import { APIContext } from "@server/types"; import pagination from "../middlewares/pagination"; import * as T from "./schema"; const router = new Router(); router.post( "revisions.info", auth(), validate(T.RevisionsInfoSchema), async (ctx: APIContext) => { const { id, documentId } = ctx.input.body; const { user } = ctx.state.auth; let before: Revision | null, after: Revision; if (id) { const revision = await Revision.findByPk(id, { rejectOnEmpty: true, }); const document = await Document.findByPk(revision.documentId, { userId: user.id, }); authorize(user, "listRevisions", document); after = revision; before = await revision.before(); } else if (documentId) { const document = await Document.findByPk(documentId, { userId: user.id, }); authorize(user, "listRevisions", document); after = Revision.buildFromDocument(document); after.id = RevisionHelper.latestId(document.id); after.user = document.updatedBy; before = await Revision.findLatest(documentId); } else { throw ValidationError("Either id or documentId must be provided"); } ctx.body = { data: await presentRevision( after, await DocumentHelper.diff(before, after, { includeTitle: false, includeStyles: false, }) ), }; } ); router.post( "revisions.diff", auth(), validate(T.RevisionsDiffSchema), async (ctx: APIContext) => { const { id, compareToId } = ctx.input.body; const { user } = ctx.state.auth; const revision = await Revision.findByPk(id, { rejectOnEmpty: true, }); const document = await Document.findByPk(revision.documentId, { userId: user.id, }); authorize(user, "listRevisions", document); let before; if (compareToId) { before = await Revision.findOne({ where: { id: compareToId, documentId: revision.documentId, createdAt: { [Op.lt]: revision.createdAt, }, }, }); if (!before) { throw ValidationError( "Revision could not be found, compareToId must be a revision of the same document before the provided revision" ); } } else { before = await revision.before(); } const accept = ctx.request.headers["accept"]; const content = await DocumentHelper.diff(before, revision); if (accept?.includes("text/html")) { const name = `${slugify(document.titleWithDefault)}-${revision.id}.html`; ctx.set("Content-Type", "text/html"); ctx.attachment(name); ctx.body = content; return; } ctx.body = { data: content, }; } ); router.post( "revisions.list", auth(), pagination(), validate(T.RevisionsListSchema), async (ctx: APIContext) => { const { direction, documentId, sort } = ctx.input.body; const { user } = ctx.state.auth; const document = await Document.findByPk(documentId, { userId: user.id, }); authorize(user, "listRevisions", document); const revisions = await Revision.findAll({ where: { documentId: document.id, }, order: [[sort, direction]], offset: ctx.state.pagination.offset, limit: ctx.state.pagination.limit, }); const data = await Promise.all( revisions.map((revision) => presentRevision(revision)) ); ctx.body = { pagination: ctx.state.pagination, data, }; } ); export default router;