chore: documentStructure database locking (#3254)

This commit is contained in:
Tom Moor
2022-03-18 08:59:11 -07:00
committed by GitHub
parent c98c397fa8
commit 5e655e42f6
7 changed files with 566 additions and 433 deletions

View File

@@ -232,35 +232,50 @@ class Document extends ParanoidModel {
// hooks
@BeforeSave
static async updateInCollectionStructure(model: Document) {
if (!model.publishedAt || model.template) {
static async updateTitleInCollectionStructure(model: Document) {
// templates, drafts, and archived documents don't appear in the structure
// and so never need to be updated when the title changes
if (
model.archivedAt ||
model.template ||
!model.publishedAt ||
!model.changed("title")
) {
return;
}
const collection = await Collection.findByPk(model.collectionId);
return this.sequelize!.transaction(async (transaction: Transaction) => {
const collection = await Collection.findByPk(model.collectionId, {
transaction,
lock: transaction.LOCK.UPDATE,
});
if (!collection) {
return;
}
if (!collection) {
return;
}
await collection.updateDocument(model);
model.collection = collection;
await collection.updateDocument(model, { transaction });
model.collection = collection;
});
}
@AfterCreate
static async addDocumentToCollectionStructure(model: Document) {
if (!model.publishedAt || model.template) {
if (model.archivedAt || model.template || !model.publishedAt) {
return;
}
const collection = await Collection.findByPk(model.collectionId);
return this.sequelize!.transaction(async (transaction: Transaction) => {
const collection = await Collection.findByPk(model.collectionId, {
transaction,
lock: transaction.LOCK.UPDATE,
});
if (!collection) {
return;
}
if (!collection) {
return;
}
await collection.addDocumentToStructure(model, 0);
model.collection = collection;
await collection.addDocumentToStructure(model, 0, { transaction });
model.collection = collection;
});
}
@BeforeValidate
@@ -674,6 +689,43 @@ class Document extends ParanoidModel {
return undefined;
};
/**
* Calculate all of the document ids that are children of this document by
* iterating through parentDocumentId references in the most efficient way.
*
* @param options FindOptions
* @returns A promise that resolves to a list of document ids
*/
getChildDocumentIds = async (
options?: FindOptions<Document>
): Promise<string[]> => {
const getChildDocumentIds = async (
...parentDocumentId: string[]
): Promise<string[]> => {
const childDocuments = await (this
.constructor as typeof Document).findAll({
attributes: ["id"],
where: {
parentDocumentId,
},
...options,
});
const childDocumentIds = childDocuments.map((doc) => doc.id);
if (childDocumentIds.length > 0) {
return [
...childDocumentIds,
...(await getChildDocumentIds(...childDocumentIds)),
];
}
return childDocumentIds;
};
return getChildDocumentIds(this.id);
};
archiveWithChildren = async (
userId: string,
options?: FindOptions<Document>
@@ -702,47 +754,73 @@ class Document extends ParanoidModel {
return this.save(options);
};
publish = async (userId: string, options?: FindOptions<Document>) => {
publish = async (userId: string) => {
// If the document is already published then calling publish should act like
// a regular save
if (this.publishedAt) {
return this.save(options);
return this.save();
}
if (!this.template) {
const collection = await Collection.findByPk(this.collectionId);
await collection?.addDocumentToStructure(this, 0);
}
await this.sequelize.transaction(async (transaction: Transaction) => {
if (!this.template) {
const collection = await Collection.findByPk(this.collectionId, {
transaction,
lock: transaction.LOCK.UPDATE,
});
if (collection) {
await collection.addDocumentToStructure(this, 0, { transaction });
this.collection = collection;
}
}
});
this.lastModifiedById = userId;
this.publishedAt = new Date();
await this.save(options);
return this;
return this.save();
};
unpublish = async (userId: string, options?: FindOptions<Document>) => {
unpublish = async (userId: string) => {
// If the document is already a draft then calling unpublish should act like
// a regular save
if (!this.publishedAt) {
return this;
return this.save();
}
const collection = await this.$get("collection");
await collection?.removeDocumentInStructure(this);
// unpublishing a document converts the "ownership" to yourself, so that it
// can appear in your drafts rather than the original creators
await this.sequelize.transaction(async (transaction: Transaction) => {
const collection = await Collection.findByPk(this.collectionId, {
transaction,
lock: transaction.LOCK.UPDATE,
});
if (collection) {
await collection.removeDocumentInStructure(this, { transaction });
this.collection = collection;
}
});
// unpublishing a document converts the ownership to yourself, so that it
// will appear in your drafts rather than the original creators
this.createdById = userId;
this.lastModifiedById = userId;
this.publishedAt = null;
await this.save(options);
return this;
return this.save();
};
// Moves a document from being visible to the team within a collection
// to the archived area, where it can be subsequently restored.
archive = async (userId: string) => {
// archive any children and remove from the document structure
const collection = await this.$get("collection");
if (collection) {
await collection.removeDocumentInStructure(this);
this.collection = collection;
}
await this.sequelize.transaction(async (transaction: Transaction) => {
const collection = await Collection.findByPk(this.collectionId, {
transaction,
lock: transaction.LOCK.UPDATE,
});
if (collection) {
await collection.removeDocumentInStructure(this, { transaction });
this.collection = collection;
}
});
await this.archiveWithChildren(userId);
return this;
@@ -750,28 +828,35 @@ class Document extends ParanoidModel {
// Restore an archived document back to being visible to the team
unarchive = async (userId: string) => {
const collection = await this.$get("collection");
// check to see if the documents parent hasn't been archived also
// If it has then restore the document to the collection root.
if (this.parentDocumentId) {
const parent = await (this.constructor as typeof Document).findOne({
where: {
id: this.parentDocumentId,
archivedAt: {
[Op.is]: null,
},
},
await this.sequelize.transaction(async (transaction: Transaction) => {
const collection = await Collection.findByPk(this.collectionId, {
transaction,
lock: transaction.LOCK.UPDATE,
});
if (!parent) {
this.parentDocumentId = null;
}
}
if (!this.template && collection) {
await collection.addDocumentToStructure(this);
this.collection = collection;
}
// check to see if the documents parent hasn't been archived also
// If it has then restore the document to the collection root.
if (this.parentDocumentId) {
const parent = await (this.constructor as typeof Document).findOne({
where: {
id: this.parentDocumentId,
archivedAt: {
[Op.is]: null,
},
},
});
if (!parent) {
this.parentDocumentId = null;
}
}
if (!this.template && collection) {
await collection.addDocumentToStructure(this, undefined, {
transaction,
});
this.collection = collection;
}
});
if (this.deletedAt) {
await this.restore();
@@ -785,39 +870,36 @@ class Document extends ParanoidModel {
// Delete a document, archived or otherwise.
delete = (userId: string) => {
return this.sequelize.transaction(
async (transaction: Transaction): Promise<Document> => {
if (!this.archivedAt && !this.template) {
// delete any children and remove from the document structure
const collection = await this.$get("collection", {
transaction,
});
if (collection) {
await collection.deleteDocument(this);
}
} else {
await this.destroy({
transaction,
});
}
await Revision.destroy({
where: {
documentId: this.id,
},
return this.sequelize.transaction(async (transaction: Transaction) => {
if (!this.archivedAt && !this.template) {
// delete any children and remove from the document structure
const collection = await Collection.findByPk(this.collectionId, {
transaction,
lock: transaction.LOCK.UPDATE,
});
await collection?.deleteDocument(this, { transaction });
} else {
await this.destroy({
transaction,
});
await this.update(
{
lastModifiedById: userId,
},
{
transaction,
}
);
return this;
}
);
await Revision.destroy({
where: {
documentId: this.id,
},
transaction,
});
await this.update(
{
lastModifiedById: userId,
},
{
transaction,
}
);
return this;
});
};
getTimestamp = () => {