1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-11-24 08:12:24 +02:00

Server: Generate only one share link per note

This commit is contained in:
Laurent Cozic 2021-05-16 12:33:36 +02:00
parent a24b0091ad
commit e156ee1b58
2 changed files with 18 additions and 306 deletions

View File

@ -69,4 +69,19 @@ describe('ShareModel', function() {
expect(shares3.find(s => s.folder_id === '000000000000000000000000000000F1')).toBeTruthy();
});
test('should generate only one link per shared note', async function() {
const { user: user1 } = await createUserAndSession(1);
await createItemTree(user1.id, '', {
'000000000000000000000000000000F1': {
'00000000000000000000000000000001': null,
},
});
const share1 = await models().share().shareNote(user1, '00000000000000000000000000000001');
const share2 = await models().share().shareNote(user1, '00000000000000000000000000000001');
expect(share1.id).toBe(share2.id);
});
});

View File

@ -241,295 +241,6 @@ export default class ShareModel extends BaseModel<Share> {
}
}
// public async updateSharedItems2(userId: Uuid) {
// const shares = await this.models().share().byUserId(userId, ShareType.JoplinRootFolder);
// if (!shares.length) return;
// const existingShareUserItems: UserItem[] = await this.models().userItem().itemsInShare(userId);
// const allShareUserItems: UserItem[] = [];
// for (const share of shares) {
// allShareUserItems.push({
// item_id: share.item_id,
// share_id: share.id,
// });
// const shareUserIds = await this.models().share().allShareUserIds(share);
// const shareItems = await this.models().item().sharedFolderChildrenItems(shareUserIds, share.folder_id);
// for (const item of shareItems) {
// allShareUserItems.push({
// item_id: item.id,
// share_id: share.id,
// });
// }
// }
// const userItemsToDelete: UserItem[] = [];
// for (const userItem of existingShareUserItems) {
// if (!allShareUserItems.find(ui => ui.item_id === userItem.item_id && ui.share_id === userItem.share_id)) {
// userItemsToDelete.push(userItem);
// }
// }
// const userItemsToCreate: UserItem[] = [];
// for (const userItem of allShareUserItems) {
// if (!existingShareUserItems.find(ui => ui.item_id === userItem.item_id && ui.share_id === userItem.share_id)) {
// userItemsToCreate.push(userItem);
// }
// }
// await this.withTransaction(async () => {
// await this.models().userItem().deleteByUserItemIds(userItemsToDelete.map(ui => ui.id));
// for (const userItem of userItemsToCreate) {
// await this.models().userItem().add(userId, userItem.item_id, userItem.share_id);
// }
// });
// }
// public async updateSharedItems() {
// enum ResourceChangeAction {
// Added = 1,
// Removed = 2,
// }
// interface ResourceChange {
// resourceIds: string[];
// share: Share;
// change: Change;
// action: ResourceChangeAction;
// }
// let resourceChanges: ResourceChange[] = [];
// const getSharedRootInfo = async (jopId: string) => {
// try {
// const output = await this.models().item().joplinItemSharedRootInfo(jopId);
// return output;
// } catch (error) {
// // "noPathForItem" means that the note or folder doesn't have a
// // parent yet. It can happen because items are synchronized in
// // random order, sometimes the children before the parents. In
// // that case we simply ignore the error, which means that for
// // now the share status of the item is unknown. The situation
// // will be resolved when the parent is received because in this
// // case, if the folder is within a shared folder, all its
// // children will be shared.
// if (error.code === 'noPathForItem') return null;
// throw error;
// }
// };
// const handleAddedToSharedFolder = async (item: Item, shareInfo: SharedRootInfo) => {
// const userIds = await this.allShareUserIds(shareInfo.share);
// for (const userId of userIds) {
// // If it's a folder we share its content. This is to ensure
// // that children that were synced before their parents get their
// // share status updated.
// // if (item.jop_type === ModelType.Folder) {
// // await this.models().item().shareJoplinFolderAndContent(shareInfo.share.id, shareInfo.share.owner_id, userId, item.jop_id);
// // }
// try {
// await this.models().userItem().add(userId, item.id);
// } catch (error) {
// if (isUniqueConstraintError(error)) {
// // Ignore - it means this user already has this item
// } else {
// throw error;
// }
// }
// }
// };
// const handleRemovedFromSharedFolder = async (change: Change, item: Item, shareInfo: SharedRootInfo) => {
// // This is called when a note parent ID changes and is moved out of
// // the shared folder. In that case, we need to unshare the item from
// // all users, except the one who did the action.
// //
// // - User 1 shares a folder with user 2
// // - User 2 moves a note out of the shared folder
// // - User 1 should no longer see the note. User 2 still sees it
// // since they have moved it to one of their own folders.
// const userIds = await this.allShareUserIds(shareInfo.share);
// for (const userId of userIds) {
// if (change.user_id !== userId) {
// await this.models().userItem().remove(userId, item.id);
// }
// }
// };
// const handleResourceSharing = async (change: Change, previousItem: ChangePreviousItem, item: Item, previousShareInfo: SharedRootInfo, currentShareInfo: SharedRootInfo) => {
// // Not a note - we can exit
// if (item.jop_type !== ModelType.Note) return;
// // Item was not in a shared folder and is still not in one - nothing to do
// if (!previousShareInfo && !currentShareInfo) return;
// // Item was moved out of a shared folder to a non-shared folder - unshare all resources
// if (previousShareInfo && !currentShareInfo) {
// resourceChanges.push({
// action: ResourceChangeAction.Removed,
// change,
// share: previousShareInfo.share,
// resourceIds: await this.models().itemResource().byItemId(item.id),
// });
// return;
// }
// // Item was moved from a non-shared folder to a shared one - share all resources
// if (!previousShareInfo && currentShareInfo) {
// resourceChanges.push({
// action: ResourceChangeAction.Added,
// change,
// share: currentShareInfo.share,
// resourceIds: await this.models().itemResource().byItemId(item.id),
// });
// return;
// }
// // Note either stayed in the same shared folder, or moved to another
// // shared folder. In that case, we check the note content before and
// // after and see if resources have been added or removed from it,
// // then we share/unshare resources based on this.
// const previousResourceIds = previousItem ? previousItem.jop_resource_ids : [];
// const currentResourceIds = await this.models().itemResource().byItemId(item.id);
// for (const resourceId of previousResourceIds) {
// if (!currentResourceIds.includes(resourceId)) {
// resourceChanges.push({
// action: ResourceChangeAction.Removed,
// change,
// share: currentShareInfo.share,
// resourceIds: [resourceId],
// });
// }
// }
// for (const resourceId of currentResourceIds) {
// if (!previousResourceIds.includes(resourceId)) {
// resourceChanges.push({
// action: ResourceChangeAction.Added,
// change,
// share: currentShareInfo.share,
// resourceIds: [resourceId],
// });
// }
// }
// };
// const handleCreatedItem = async (change: Change, item: Item) => {
// if (!item.jop_parent_id) return;
// const shareInfo = await getSharedRootInfo(item.jop_parent_id);
// if (!shareInfo) return;
// await handleResourceSharing(change, null, item, null, shareInfo);
// await handleAddedToSharedFolder(item, shareInfo);
// };
// const handleUpdatedItem = async (change: Change, item: Item) => {
// if (![ModelType.Note, ModelType.Folder].includes(item.jop_type)) return;
// const previousItem = this.models().change().unserializePreviousItem(change.previous_item);
// const previousShareInfo = previousItem?.jop_parent_id ? await getSharedRootInfo(previousItem.jop_parent_id) : null;
// const currentShareInfo = item.jop_parent_id ? await getSharedRootInfo(item.jop_parent_id) : null;
// await handleResourceSharing(change, previousItem, item, previousShareInfo, currentShareInfo);
// // Item was not in a shared folder and is still not in one
// if (!previousShareInfo && !currentShareInfo) return;
// // Item was in a shared folder and is still in the same shared folder
// if (previousShareInfo && currentShareInfo && previousShareInfo.item.jop_parent_id === currentShareInfo.item.jop_parent_id) return;
// // Item was not previously in a shared folder but has been moved to one
// if (!previousShareInfo && currentShareInfo) {
// await handleAddedToSharedFolder(item, currentShareInfo);
// return;
// }
// // Item was in a shared folder and is no longer in one
// if (previousShareInfo && !currentShareInfo) {
// await handleRemovedFromSharedFolder(change, item, previousShareInfo);
// return;
// }
// // Item was in a shared folder and has been moved to a different shared folder
// if (previousShareInfo && currentShareInfo && previousShareInfo.item.jop_parent_id !== currentShareInfo.item.jop_parent_id) {
// await handleRemovedFromSharedFolder(change, item, previousShareInfo);
// await handleAddedToSharedFolder(item, currentShareInfo);
// return;
// }
// // Sanity check - because normally all cases are covered above
// throw new Error('Unreachable');
// };
// // This loop essentially applies the change made by one user to all the
// // other users in the share.
// //
// // While it's processing changes, it's going to create new user_item
// // objects, which in turn generate more Change items, which are processed
// // again. However there are guards to ensure that it doesn't result in
// // an infinite loop - in particular once a user_item has been added,
// // adding it again will result in a UNIQUE constraint error and thus it
// // won't generate a Change object the second time.
// //
// // Rather than checking if the user_item exists before creating it, we
// // create it directly and let it fail, while catching the Unique error.
// // This is probably safer in terms of avoiding race conditions and
// // possibly faster.
// while (true) {
// const latestProcessedChange = await this.models().keyValue().value<string>('ShareService::latestProcessedChange');
// const changes = await this.models().change().allFromId(latestProcessedChange || '');
// if (!changes.length) break;
// const items = await this.models().item().loadByIds(changes.map(c => c.item_id));
// await this.withTransaction(async () => {
// for (const change of changes) {
// if (change.type === ChangeType.Create) {
// await handleCreatedItem(change, items.find(i => i.id === change.item_id));
// }
// if (change.type === ChangeType.Update) {
// await handleUpdatedItem(change, items.find(i => i.id === change.item_id));
// }
// // We don't need to handle ChangeType.Delete because when an
// // item is deleted, all its associated userItems are deleted
// // too.
// }
// for (const rc of resourceChanges) {
// const shareUserIds = await this.allShareUserIds(rc.share);
// const doShare = rc.action === ResourceChangeAction.Added;
// const changerUserId = rc.change.user_id;
// for (const shareUserId of shareUserIds) {
// // We apply the updates to all the users, except the one
// // who made the change, since they already have the
// // change.
// if (shareUserId === changerUserId) continue;
// await this.updateResourceShareStatus(doShare, rc.share.id, changerUserId, shareUserId, rc.resourceIds);
// }
// }
// resourceChanges = [];
// await this.models().keyValue().setValue('ShareService::latestProcessedChange', changes[changes.length - 1].id);
// });
// }
// }
public async updateResourceShareStatus(doShare: boolean, _shareId: Uuid, changerUserId: Uuid, toUserId: Uuid, resourceIds: string[]) {
const resourceItems = await this.models().item().loadByJopIds(changerUserId, resourceIds);
const resourceBlobNames = resourceIds.map(id => resourceBlobPath(id));
@ -595,30 +306,16 @@ export default class ShareModel extends BaseModel<Share> {
};
await this.checkIfAllowed(owner, AclAction.Create, shareToSave);
// const shareItems = await this.models().item().sharedFolderChildrenItems([owner.id], folderId);
// const userItems = await this.models().userItem().byItemIds(shareItems.map(s => s.id));
// const userItemIds = userItems.map(u => u.id);
return super.save(shareToSave);
// return this.withTransaction(async () => {
// const savedShare = await super.save(shareToSave);
// await this
// .db('user_items')
// .whereIn('id', userItemIds)
// .orWhere('item_id', '=', shareToSave.item_id)
// .update({ share_id: savedShare.id });
// return savedShare;
// });
}
public async shareNote(owner: User, noteId: string): Promise<Share> {
const noteItem = await this.models().item().loadByJopId(owner.id, noteId);
if (!noteItem) throw new ErrorNotFound(`No such note: ${noteId}`);
const existingShare = await this.byItemId(noteItem.id);
if (existingShare) return existingShare;
const shareToSave = {
type: ShareType.Link,
item_id: noteItem.id,