mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-24 11:22:19 +03:00
Wrapped collection's post adding logic in transaction
refs https://github.com/TryGhost/Arch/issues/16 - Having transactional collection post updates makes sure there are no race conditions when updating collection_posts relations. Without the transactions collection was prone to update relations based on a stale state causing problems like described in the linked issue
This commit is contained in:
parent
04c92d2ca5
commit
0880770d50
@ -1,8 +1,9 @@
|
||||
import {Collection} from './Collection';
|
||||
|
||||
export interface CollectionRepository {
|
||||
save(collection: Collection): Promise<void>
|
||||
getById(id: string): Promise<Collection | null>
|
||||
createTransaction(fn: (transaction: any) => Promise<any>): Promise<any>
|
||||
save(collection: Collection, options?: {transaction: any}): Promise<void>
|
||||
getById(id: string, options?: {transaction: any}): Promise<Collection | null>
|
||||
getBySlug(slug: string): Promise<Collection | null>
|
||||
getAll(options?: any): Promise<Collection[]>
|
||||
}
|
||||
|
@ -6,6 +6,10 @@ export class CollectionsRepositoryInMemory extends InMemoryRepository<string, Co
|
||||
return entity.toJSON();
|
||||
}
|
||||
|
||||
createTransaction(cb: (transaction: any) => Promise<any>): Promise<any> {
|
||||
return cb(null);
|
||||
}
|
||||
|
||||
async getBySlug(slug: string): Promise<Collection | null> {
|
||||
return this.store.find(item => item.slug === slug) || null;
|
||||
}
|
||||
|
@ -92,6 +92,7 @@ type QueryOptions = {
|
||||
include?: string;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
transaction?: any;
|
||||
}
|
||||
|
||||
interface PostsRepository {
|
||||
@ -253,32 +254,22 @@ export class CollectionsService {
|
||||
}
|
||||
|
||||
private async addPostToMatchingCollections(post: CollectionPost) {
|
||||
const collections = await this.collectionsRepository.getAll({
|
||||
filter: 'type:automatic'
|
||||
});
|
||||
return await this.collectionsRepository.createTransaction(async (transaction) => {
|
||||
const collections = await this.collectionsRepository.getAll({
|
||||
filter: 'type:automatic',
|
||||
transaction: transaction
|
||||
});
|
||||
|
||||
for (const collection of collections) {
|
||||
const added = await collection.addPost(post);
|
||||
for (const collection of collections) {
|
||||
const added = await collection.addPost(post);
|
||||
|
||||
if (added) {
|
||||
await this.collectionsRepository.save(collection);
|
||||
if (added) {
|
||||
await this.collectionsRepository.save(collection, {
|
||||
transaction: transaction
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Updates all automatic collections. Can be time intensive and is a temporary solution
|
||||
* while all of the events are mapped out and handled optimally
|
||||
*/
|
||||
async updateCollections() {
|
||||
const collections = await this.collectionsRepository.getAll({
|
||||
filter: 'type:automatic'
|
||||
});
|
||||
|
||||
for (const collection of collections) {
|
||||
await this.updateAutomaticCollectionItems(collection);
|
||||
await this.collectionsRepository.save(collection);
|
||||
}
|
||||
}
|
||||
|
||||
async updatePostInMatchingCollections(postEdit: PostEditedEvent['data']) {
|
||||
|
@ -12,6 +12,10 @@ module.exports = class BookshelfCollectionsRepository {
|
||||
this.#model = model;
|
||||
}
|
||||
|
||||
async createTransaction(cb) {
|
||||
return this.#model.transaction(cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} id
|
||||
* @returns {Promise<Collection>}
|
||||
@ -40,9 +44,15 @@ module.exports = class BookshelfCollectionsRepository {
|
||||
* @param {object} [options]
|
||||
* @param {string} [options.filter]
|
||||
* @param {string} [options.order]
|
||||
* @param {import('knex').Transaction} [options.transaction]
|
||||
*/
|
||||
async getAll(options = {}) {
|
||||
const models = await this.#model.findAll({...options, withRelated: ['posts']});
|
||||
const models = await this.#model.findAll({
|
||||
...options,
|
||||
transacting: options.transaction,
|
||||
withRelated: ['posts']
|
||||
});
|
||||
|
||||
return await Promise.all(models.map(model => this.#modelToCollection(model)));
|
||||
}
|
||||
|
||||
@ -65,9 +75,11 @@ module.exports = class BookshelfCollectionsRepository {
|
||||
|
||||
/**
|
||||
* @param {Collection} collection
|
||||
* @param {object} [options]
|
||||
* @param {import('knex').Transaction} [options.transaction]
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async save(collection) {
|
||||
async save(collection, options = {}) {
|
||||
if (collection.deleted) {
|
||||
await this.#model.destroy({id: collection.id});
|
||||
return;
|
||||
@ -85,13 +97,22 @@ module.exports = class BookshelfCollectionsRepository {
|
||||
updated_at: collection.updatedAt
|
||||
};
|
||||
|
||||
const existing = await this.#model.findOne({id: data.id}, {require: false});
|
||||
const existing = await this.#model.findOne(
|
||||
{id: data.id},
|
||||
{
|
||||
require: false,
|
||||
transacting: options.transaction
|
||||
}
|
||||
);
|
||||
|
||||
if (!existing) {
|
||||
await this.#model.add(data);
|
||||
await this.#model.add(data, {
|
||||
transacting: options.transaction
|
||||
});
|
||||
} else {
|
||||
await this.#model.edit(data, {
|
||||
id: data.id
|
||||
return this.#model.edit(data, {
|
||||
id: data.id,
|
||||
transacting: options.transaction
|
||||
});
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user