mirror of
https://github.com/laurent22/joplin.git
synced 2025-01-23 18:53:36 +02:00
Server: Fixed transaction locking issue when a sub-transaction fails
This commit is contained in:
parent
5bb68ba65d
commit
12aae48ce6
@ -227,54 +227,6 @@ export default class ItemModel extends BaseModel<Item> {
|
||||
return output;
|
||||
}
|
||||
|
||||
// private async folderChildrenItems(userId: Uuid, folderId: string): Promise<Item[]> {
|
||||
// let output: Item[] = [];
|
||||
|
||||
// const rows: Item[] = await this
|
||||
// .db('user_items')
|
||||
// .leftJoin('items', 'items.id', 'user_items.item_id')
|
||||
// .select('items.id', 'items.jop_id', 'items.jop_type')
|
||||
// .where('items.jop_parent_id', '=', folderId)
|
||||
// .where('user_items.user_id', '=', userId);
|
||||
|
||||
// for (const row of rows) {
|
||||
// output.push(row);
|
||||
|
||||
// if (row.jop_type === ModelType.Folder) {
|
||||
// const children = await this.folderChildrenItems(userId, row.jop_id);
|
||||
// output = output.concat(children);
|
||||
// }
|
||||
// }
|
||||
|
||||
// return output;
|
||||
// }
|
||||
|
||||
// public async shareJoplinFolderAndContent(shareId: Uuid, fromUserId: Uuid, toUserId: Uuid, folderId: string) {
|
||||
// const folderItem = await this.loadByJopId(fromUserId, folderId, { fields: ['id'] });
|
||||
// if (!folderItem) throw new ErrorNotFound(`Could not find folder "${folderId}" for share "${shareId}"`);
|
||||
|
||||
// const items = [folderItem].concat(await this.folderChildrenItems(fromUserId, folderId));
|
||||
|
||||
// const alreadySharedItemIds: string[] = await this
|
||||
// .db('user_items')
|
||||
// .pluck('item_id')
|
||||
// .whereIn('item_id', items.map(i => i.id))
|
||||
// .where('user_id', '=', toUserId)
|
||||
// // .where('share_id', '!=', '');
|
||||
|
||||
// await this.withTransaction(async () => {
|
||||
// for (const item of items) {
|
||||
// if (alreadySharedItemIds.includes(item.id)) continue;
|
||||
// await this.models().userItem().add(toUserId, item.id, shareId);
|
||||
|
||||
// if (item.jop_type === ModelType.Note) {
|
||||
// const resourceIds = await this.models().itemResource().byItemId(item.id);
|
||||
// await this.models().share().updateResourceShareStatus(true, shareId, fromUserId, toUserId, resourceIds);
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
public itemToJoplinItem(itemRow: Item): any {
|
||||
if (itemRow.jop_type <= 0) throw new Error(`Not a Joplin item: ${itemRow.id}`);
|
||||
if (!itemRow.content) throw new Error('Item content is missing');
|
||||
@ -410,7 +362,7 @@ export default class ItemModel extends BaseModel<Item> {
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
}, 'ItemModel::saveFromRawContent');
|
||||
|
||||
return output;
|
||||
}
|
||||
@ -636,7 +588,7 @@ export default class ItemModel extends BaseModel<Item> {
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
}, 'ItemModel::saveForUser');
|
||||
}
|
||||
|
||||
public async save(_item: Item, _options: SaveOptions = {}): Promise<Item> {
|
||||
|
@ -105,14 +105,14 @@ router.get('api/shares/:id', async (path: SubPath, ctx: AppContext) => {
|
||||
router.get('api/shares', async (_path: SubPath, ctx: AppContext) => {
|
||||
ownerRequired(ctx);
|
||||
|
||||
const items = ctx.models.share().toApiOutput(await ctx.models.share().sharesByUser(ctx.owner.id)) as Share[];
|
||||
const shares = ctx.models.share().toApiOutput(await ctx.models.share().sharesByUser(ctx.owner.id)) as Share[];
|
||||
// Fake paginated results so that it can be added later on, if needed.
|
||||
return {
|
||||
items: items.map(i => {
|
||||
items: shares.map(share => {
|
||||
return {
|
||||
...i,
|
||||
...share,
|
||||
user: {
|
||||
id: i.owner_id,
|
||||
id: share.owner_id,
|
||||
},
|
||||
};
|
||||
}),
|
||||
|
@ -68,10 +68,21 @@ export default class TransactionHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// Only the function that started the transaction can rollback it. In
|
||||
// practice it works as expected even for nested transactions: If any of the
|
||||
// sub-function throws an error, it will propagate to the parent function,
|
||||
// which will rollback the connection.
|
||||
//
|
||||
// If a sub-function throws an error, but it's catched by the parent, we
|
||||
// also don't want the transaction to be rollbacked, because the errors are
|
||||
// essentially managed by the parent function. This is for example how
|
||||
// ItemModel::saveFromRawContent works because it catches any error and
|
||||
// saves them to an array, to be returned to the caller. So we don't want
|
||||
// any error to rollback everything.
|
||||
public async rollback(txIndex: number): Promise<void> {
|
||||
this.log(`Rollback transaction: ${txIndex}`);
|
||||
this.finishTransaction(txIndex);
|
||||
if (this.activeTransaction_) {
|
||||
const isLastTransaction = this.finishTransaction(txIndex);
|
||||
if (isLastTransaction) {
|
||||
this.log(`Transaction is active - doing rollback: ${txIndex}`);
|
||||
await this.activeTransaction_.rollback();
|
||||
this.activeTransaction_ = null;
|
||||
|
Loading…
x
Reference in New Issue
Block a user