diff --git a/server/commands/documentCollaborativeUpdater.ts b/server/commands/documentCollaborativeUpdater.ts index 205ab7ed6..357640b96 100644 --- a/server/commands/documentCollaborativeUpdater.ts +++ b/server/commands/documentCollaborativeUpdater.ts @@ -7,18 +7,23 @@ import { schema, serializer } from "@server/editor"; import Logger from "@server/logging/Logger"; import { Document, Event } from "@server/models"; +type Props = { + /** The document ID to update */ + documentId: string; + /** Current collaobrative state */ + ydoc: Y.Doc; + /** The user ID that is performing the update, if known */ + userId?: string; +}; + export default async function documentCollaborativeUpdater({ documentId, ydoc, userId, -}: { - documentId: string; - ydoc: Y.Doc; - userId?: string; -}) { +}: Props) { return sequelize.transaction(async (transaction) => { const document = await Document.unscoped() - .scope("withState") + .scope("withoutState") .findOne({ where: { id: documentId, @@ -36,48 +41,41 @@ export default async function documentCollaborativeUpdater({ const node = Node.fromJSON(schema, yDocToProsemirrorJSON(ydoc, "default")); const text = serializer.serialize(node, undefined); const isUnchanged = document.text === text; - const hasMultiplayerState = !!document.state; + const lastModifiedById = userId ?? document.lastModifiedById; - if (isUnchanged && hasMultiplayerState) { + if (isUnchanged) { return; } Logger.info( "multiplayer", - `Persisting ${documentId}, attributed to ${userId}` + `Persisting ${documentId}, attributed to ${lastModifiedById}` ); // extract collaborators from doc user data const pud = new Y.PermanentUserData(ydoc); const pudIds = Array.from(pud.clients.values()); - const existingIds = document.collaboratorIds; - const collaboratorIds = uniq([...pudIds, ...existingIds]); + const collaboratorIds = uniq([...document.collaboratorIds, ...pudIds]); await document.update( { text, state: Buffer.from(state), - lastModifiedById: - isUnchanged || !userId ? document.lastModifiedById : userId, + lastModifiedById, collaboratorIds, }, { transaction, - silent: isUnchanged, hooks: false, } ); - if (isUnchanged) { - return; - } - await Event.schedule({ name: "documents.update", documentId: document.id, collectionId: document.collectionId, teamId: document.teamId, - actorId: userId ?? document.lastModifiedById, + actorId: lastModifiedById, data: { multiplayer: true, title: document.title, diff --git a/server/models/Document.ts b/server/models/Document.ts index 6cd0c794e..01a87b7a7 100644 --- a/server/models/Document.ts +++ b/server/models/Document.ts @@ -3,6 +3,7 @@ import { compact, uniq } from "lodash"; import randomstring from "randomstring"; import type { SaveOptions } from "sequelize"; import { + Sequelize, Transaction, Op, FindOptions, @@ -116,6 +117,17 @@ export const DOCUMENT_VERSION = 2; }, ], }, + withStateIsEmpty: { + attributes: { + exclude: ["state"], + include: [ + [ + Sequelize.literal(`CASE WHEN state IS NULL THEN true ELSE false END`), + "stateIsEmpty", + ], + ], + }, + }, withState: { attributes: { // resets to include the state column