mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
Server: Refactor ShareType
This commit is contained in:
parent
489995daef
commit
b9955f58d3
@ -237,9 +237,8 @@ export function changeTypeToString(t: ChangeType): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum ShareType {
|
export enum ShareType {
|
||||||
Link = 1, // When a note is shared via a public link
|
Note = 1, // When a note is shared via a public link
|
||||||
App = 2, // When a note is shared with another user on the same server instance
|
Folder = 3, // When a complete folder is shared with another Joplin Server user
|
||||||
JoplinRootFolder = 3,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ShareUserStatus {
|
export enum ShareUserStatus {
|
||||||
|
@ -416,7 +416,7 @@ export default class ItemModel extends BaseModel<Item> {
|
|||||||
const path = await this.joplinItemPath(jopId);
|
const path = await this.joplinItemPath(jopId);
|
||||||
if (!path.length) throw new ApiError(`Cannot retrieve path for item: ${jopId}`, null, 'noPathForItem');
|
if (!path.length) throw new ApiError(`Cannot retrieve path for item: ${jopId}`, null, 'noPathForItem');
|
||||||
const rootFolderItem = path[path.length - 1];
|
const rootFolderItem = path[path.length - 1];
|
||||||
const share = await this.models().share().itemShare(ShareType.JoplinRootFolder, rootFolderItem.id);
|
const share = await this.models().share().itemShare(ShareType.Folder, rootFolderItem.id);
|
||||||
if (!share) return null;
|
if (!share) return null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -27,7 +27,7 @@ describe('ShareModel', function() {
|
|||||||
error = await checkThrowAsync(async () => await models().share().createShare(user.id, 20 as ShareType, item.id));
|
error = await checkThrowAsync(async () => await models().share().createShare(user.id, 20 as ShareType, item.id));
|
||||||
expect(error instanceof ErrorBadRequest).toBe(true);
|
expect(error instanceof ErrorBadRequest).toBe(true);
|
||||||
|
|
||||||
error = await checkThrowAsync(async () => await models().share().createShare(user.id, ShareType.Link, 'doesntexist'));
|
error = await checkThrowAsync(async () => await models().share().createShare(user.id, ShareType.Note, 'doesntexist'));
|
||||||
expect(error instanceof ErrorNotFound).toBe(true);
|
expect(error instanceof ErrorNotFound).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -49,14 +49,14 @@ describe('ShareModel', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const folderItem1 = await models().item().loadByJopId(user1.id, '000000000000000000000000000000F1');
|
const folderItem1 = await models().item().loadByJopId(user1.id, '000000000000000000000000000000F1');
|
||||||
await shareWithUserAndAccept(session1.id, session3.id, user3, ShareType.JoplinRootFolder, folderItem1);
|
await shareWithUserAndAccept(session1.id, session3.id, user3, ShareType.Folder, folderItem1);
|
||||||
|
|
||||||
const folderItem2 = await models().item().loadByJopId(user2.id, '000000000000000000000000000000F2');
|
const folderItem2 = await models().item().loadByJopId(user2.id, '000000000000000000000000000000F2');
|
||||||
await shareWithUserAndAccept(session2.id, session1.id, user1, ShareType.JoplinRootFolder, folderItem2);
|
await shareWithUserAndAccept(session2.id, session1.id, user1, ShareType.Folder, folderItem2);
|
||||||
|
|
||||||
const shares1 = await models().share().byUserId(user1.id, ShareType.JoplinRootFolder);
|
const shares1 = await models().share().byUserId(user1.id, ShareType.Folder);
|
||||||
const shares2 = await models().share().byUserId(user2.id, ShareType.JoplinRootFolder);
|
const shares2 = await models().share().byUserId(user2.id, ShareType.Folder);
|
||||||
const shares3 = await models().share().byUserId(user3.id, ShareType.JoplinRootFolder);
|
const shares3 = await models().share().byUserId(user3.id, ShareType.Folder);
|
||||||
|
|
||||||
expect(shares1.length).toBe(2);
|
expect(shares1.length).toBe(2);
|
||||||
expect(shares1.find(s => s.folder_id === '000000000000000000000000000000F1')).toBeTruthy();
|
expect(shares1.find(s => s.folder_id === '000000000000000000000000000000F1')).toBeTruthy();
|
||||||
|
@ -16,7 +16,7 @@ export default class ShareModel extends BaseModel<Share> {
|
|||||||
if (action === AclAction.Create) {
|
if (action === AclAction.Create) {
|
||||||
if (!await this.models().item().userHasItem(user.id, resource.item_id)) throw new ErrorForbidden('cannot share an item not owned by the user');
|
if (!await this.models().item().userHasItem(user.id, resource.item_id)) throw new ErrorForbidden('cannot share an item not owned by the user');
|
||||||
|
|
||||||
if (resource.type === ShareType.JoplinRootFolder) {
|
if (resource.type === ShareType.Folder) {
|
||||||
const item = await this.models().item().loadByJopId(user.id, resource.folder_id);
|
const item = await this.models().item().loadByJopId(user.id, resource.folder_id);
|
||||||
if (item.jop_parent_id) throw new ErrorForbidden('A shared notebook must be at the root');
|
if (item.jop_parent_id) throw new ErrorForbidden('A shared notebook must be at the root');
|
||||||
}
|
}
|
||||||
@ -44,8 +44,8 @@ export default class ShareModel extends BaseModel<Share> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async validate(share: Share, options: ValidateOptions = {}): Promise<Share> {
|
protected async validate(share: Share, options: ValidateOptions = {}): Promise<Share> {
|
||||||
if ('type' in share && ![ShareType.Link, ShareType.App, ShareType.JoplinRootFolder].includes(share.type)) throw new ErrorBadRequest(`Invalid share type: ${share.type}`);
|
if ('type' in share && ![ShareType.Note, ShareType.Folder].includes(share.type)) throw new ErrorBadRequest(`Invalid share type: ${share.type}`);
|
||||||
if (share.type !== ShareType.Link && await this.itemIsShared(share.type, share.item_id)) throw new ErrorBadRequest('A shared item cannot be shared again');
|
if (share.type !== ShareType.Note && await this.itemIsShared(share.type, share.item_id)) throw new ErrorBadRequest('A shared item cannot be shared again');
|
||||||
|
|
||||||
const item = await this.models().item().load(share.item_id);
|
const item = await this.models().item().load(share.item_id);
|
||||||
if (!item) throw new ErrorNotFound(`Could not find item: ${share.item_id}`);
|
if (!item) throw new ErrorNotFound(`Could not find item: ${share.item_id}`);
|
||||||
@ -299,7 +299,7 @@ export default class ShareModel extends BaseModel<Share> {
|
|||||||
if (share) return share;
|
if (share) return share;
|
||||||
|
|
||||||
const shareToSave = {
|
const shareToSave = {
|
||||||
type: ShareType.JoplinRootFolder,
|
type: ShareType.Folder,
|
||||||
item_id: folderItem.id,
|
item_id: folderItem.id,
|
||||||
owner_id: owner.id,
|
owner_id: owner.id,
|
||||||
folder_id: folderId,
|
folder_id: folderId,
|
||||||
@ -317,7 +317,7 @@ export default class ShareModel extends BaseModel<Share> {
|
|||||||
if (existingShare) return existingShare;
|
if (existingShare) return existingShare;
|
||||||
|
|
||||||
const shareToSave = {
|
const shareToSave = {
|
||||||
type: ShareType.Link,
|
type: ShareType.Note,
|
||||||
item_id: noteItem.id,
|
item_id: noteItem.id,
|
||||||
owner_id: owner.id,
|
owner_id: owner.id,
|
||||||
note_id: noteId,
|
note_id: noteId,
|
||||||
|
@ -128,7 +128,7 @@ export default class ShareUserModel extends BaseModel<ShareUser> {
|
|||||||
public async deleteByShare(share: Share): Promise<void> {
|
public async deleteByShare(share: Share): Promise<void> {
|
||||||
// Notes that are shared by link do not have associated ShareUser items,
|
// Notes that are shared by link do not have associated ShareUser items,
|
||||||
// so there's nothing to do.
|
// so there's nothing to do.
|
||||||
if (share.type !== ShareType.JoplinRootFolder) return;
|
if (share.type !== ShareType.Folder) return;
|
||||||
|
|
||||||
const shareUsers = await this.byShareId(share.id, null);
|
const shareUsers = await this.byShareId(share.id, null);
|
||||||
|
|
||||||
|
@ -29,8 +29,8 @@ describe('share_users', function() {
|
|||||||
});
|
});
|
||||||
const folderItem1 = await models().item().loadByJopId(user1.id, '000000000000000000000000000000F1');
|
const folderItem1 = await models().item().loadByJopId(user1.id, '000000000000000000000000000000F1');
|
||||||
const folderItem2 = await models().item().loadByJopId(user1.id, '000000000000000000000000000000F2');
|
const folderItem2 = await models().item().loadByJopId(user1.id, '000000000000000000000000000000F2');
|
||||||
const { share: share1 } = await shareWithUserAndAccept(session1.id, session2.id, user2, ShareType.JoplinRootFolder, folderItem1);
|
const { share: share1 } = await shareWithUserAndAccept(session1.id, session2.id, user2, ShareType.Folder, folderItem1);
|
||||||
const { share: share2 } = await shareWithUserAndAccept(session1.id, session2.id, user2, ShareType.JoplinRootFolder, folderItem2);
|
const { share: share2 } = await shareWithUserAndAccept(session1.id, session2.id, user2, ShareType.Folder, folderItem2);
|
||||||
|
|
||||||
const shareUsers = await getApi<PaginatedResults>(session2.id, 'share_users');
|
const shareUsers = await getApi<PaginatedResults>(session2.id, 'share_users');
|
||||||
expect(shareUsers.items.length).toBe(2);
|
expect(shareUsers.items.length).toBe(2);
|
||||||
@ -44,7 +44,7 @@ describe('share_users', function() {
|
|||||||
|
|
||||||
await createItemTree(user1.id, '', { '000000000000000000000000000000F1': {} });
|
await createItemTree(user1.id, '', { '000000000000000000000000000000F1': {} });
|
||||||
const folderItem = await models().item().loadByJopId(user1.id, '000000000000000000000000000000F1');
|
const folderItem = await models().item().loadByJopId(user1.id, '000000000000000000000000000000F1');
|
||||||
const { shareUser } = await shareWithUserAndAccept(session1.id, session2.id, user2, ShareType.JoplinRootFolder, folderItem);
|
const { shareUser } = await shareWithUserAndAccept(session1.id, session2.id, user2, ShareType.Folder, folderItem);
|
||||||
|
|
||||||
// User can modify own UserShare object
|
// User can modify own UserShare object
|
||||||
await patchApi(session2.id, `share_users/${shareUser.id}`, { status: ShareUserStatus.Rejected });
|
await patchApi(session2.id, `share_users/${shareUser.id}`, { status: ShareUserStatus.Rejected });
|
||||||
|
@ -32,7 +32,7 @@ describe('shares.folder', function() {
|
|||||||
// Create the file share object
|
// Create the file share object
|
||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
const share = await postApi<Share>(session1.id, 'shares', {
|
const share = await postApi<Share>(session1.id, 'shares', {
|
||||||
type: ShareType.JoplinRootFolder,
|
type: ShareType.Folder,
|
||||||
folder_id: folderItem.jop_id,
|
folder_id: folderItem.jop_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ describe('shares', function() {
|
|||||||
await createItemTree(user1.id, '', tree);
|
await createItemTree(user1.id, '', tree);
|
||||||
const folderItem = await itemModel1.loadByJopId(user1.id, '000000000000000000000000000000F1');
|
const folderItem = await itemModel1.loadByJopId(user1.id, '000000000000000000000000000000F1');
|
||||||
const noteItem2 = await itemModel1.loadByJopId(user1.id, '00000000000000000000000000000002');
|
const noteItem2 = await itemModel1.loadByJopId(user1.id, '00000000000000000000000000000002');
|
||||||
const { share } = await shareWithUserAndAccept(session1.id, session2.id, user2, ShareType.JoplinRootFolder, folderItem);
|
const { share } = await shareWithUserAndAccept(session1.id, session2.id, user2, ShareType.Folder, folderItem);
|
||||||
|
|
||||||
// Only share with user 3, without accepting it
|
// Only share with user 3, without accepting it
|
||||||
await postApi(session1.id, `shares/${share.id}/users`, {
|
await postApi(session1.id, `shares/${share.id}/users`, {
|
||||||
@ -54,11 +54,11 @@ describe('shares', function() {
|
|||||||
|
|
||||||
const share1: Share = shares.items.find(it => it.folder_id === '000000000000000000000000000000F1');
|
const share1: Share = shares.items.find(it => it.folder_id === '000000000000000000000000000000F1');
|
||||||
expect(share1).toBeTruthy();
|
expect(share1).toBeTruthy();
|
||||||
expect(share1.type).toBe(ShareType.JoplinRootFolder);
|
expect(share1.type).toBe(ShareType.Folder);
|
||||||
|
|
||||||
const share2: Share = shares.items.find(it => it.note_id === '00000000000000000000000000000002');
|
const share2: Share = shares.items.find(it => it.note_id === '00000000000000000000000000000002');
|
||||||
expect(share2).toBeTruthy();
|
expect(share2).toBeTruthy();
|
||||||
expect(share2.type).toBe(ShareType.Link);
|
expect(share2.type).toBe(ShareType.Note);
|
||||||
|
|
||||||
const shareUsers = await getApi<PaginatedResults>(session1.id, `shares/${share1.id}/users`);
|
const shareUsers = await getApi<PaginatedResults>(session1.id, `shares/${share1.id}/users`);
|
||||||
expect(shareUsers.items.length).toBe(2);
|
expect(shareUsers.items.length).toBe(2);
|
||||||
|
@ -92,7 +92,7 @@ router.get('api/shares/:id', async (path: SubPath, ctx: AppContext) => {
|
|||||||
const shareModel = ctx.models.share();
|
const shareModel = ctx.models.share();
|
||||||
const share = await shareModel.load(path.id);
|
const share = await shareModel.load(path.id);
|
||||||
|
|
||||||
if (share && share.type === ShareType.Link) {
|
if (share && share.type === ShareType.Note) {
|
||||||
// No authentication is necessary - anyone who knows the share ID is allowed
|
// No authentication is necessary - anyone who knows the share ID is allowed
|
||||||
// to access the file. It is essentially public.
|
// to access the file. It is essentially public.
|
||||||
return shareModel.toApiOutput(share);
|
return shareModel.toApiOutput(share);
|
||||||
|
@ -62,7 +62,7 @@ describe('shares.link', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const share = await postApi<Share>(session.id, 'shares', {
|
const share = await postApi<Share>(session.id, 'shares', {
|
||||||
type: ShareType.Link,
|
type: ShareType.Note,
|
||||||
note_id: noteItem.jop_id,
|
note_id: noteItem.jop_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -82,7 +82,7 @@ describe('shares.link', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const share = await postApi<Share>(session.id, 'shares', {
|
const share = await postApi<Share>(session.id, 'shares', {
|
||||||
type: ShareType.Link,
|
type: ShareType.Note,
|
||||||
note_id: noteItem.jop_id,
|
note_id: noteItem.jop_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -103,7 +103,7 @@ describe('shares.link', function() {
|
|||||||
await createItem(session.id, 'root:/.resource/96765a68655f4446b3dbad7d41b6566e:', await testImageBuffer());
|
await createItem(session.id, 'root:/.resource/96765a68655f4446b3dbad7d41b6566e:', await testImageBuffer());
|
||||||
|
|
||||||
const share = await postApi<Share>(session.id, 'shares', {
|
const share = await postApi<Share>(session.id, 'shares', {
|
||||||
type: ShareType.Link,
|
type: ShareType.Note,
|
||||||
note_id: noteItem.jop_id,
|
note_id: noteItem.jop_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -137,7 +137,7 @@ describe('shares.link', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const share = await postApi<Share>(session.id, 'shares', {
|
const share = await postApi<Share>(session.id, 'shares', {
|
||||||
type: ShareType.Link,
|
type: ShareType.Note,
|
||||||
note_id: noteItem.jop_id,
|
note_id: noteItem.jop_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -151,7 +151,7 @@ describe('shares.link', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const share = await postApi<Share>(session.id, 'shares', {
|
const share = await postApi<Share>(session.id, 'shares', {
|
||||||
type: ShareType.Link,
|
type: ShareType.Note,
|
||||||
note_id: noteItem.jop_id,
|
note_id: noteItem.jop_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ export async function createFolderShare(sessionId: string, folderId: string): Pr
|
|||||||
// const item = await createFolder(sessionId, { id: '00000000 });
|
// const item = await createFolder(sessionId, { id: '00000000 });
|
||||||
|
|
||||||
return postApi<Share>(sessionId, 'shares', {
|
return postApi<Share>(sessionId, 'shares', {
|
||||||
type: ShareType.JoplinRootFolder,
|
type: ShareType.Folder,
|
||||||
folder_id: folderId,
|
folder_id: folderId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -75,7 +75,7 @@ export async function shareFolderWithUser(sharerSessionId: string, shareeSession
|
|||||||
});
|
});
|
||||||
|
|
||||||
const share: Share = await postApi<Share>(sharerSessionId, 'shares', {
|
const share: Share = await postApi<Share>(sharerSessionId, 'shares', {
|
||||||
type: ShareType.JoplinRootFolder,
|
type: ShareType.Folder,
|
||||||
folder_id: rootFolderItem.jop_id,
|
folder_id: rootFolderItem.jop_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -113,16 +113,16 @@ export async function shareFolderWithUser(sharerSessionId: string, shareeSession
|
|||||||
// - User 2 accepts the share
|
// - User 2 accepts the share
|
||||||
//
|
//
|
||||||
// The result is that user 2 will have a file linked to user 1's file.
|
// The result is that user 2 will have a file linked to user 1's file.
|
||||||
export async function shareWithUserAndAccept(sharerSessionId: string, shareeSessionId: string, sharee: User, shareType: ShareType = ShareType.App, item: Item = null): Promise<ShareResult> {
|
export async function shareWithUserAndAccept(sharerSessionId: string, shareeSessionId: string, sharee: User, shareType: ShareType = ShareType.Folder, item: Item = null): Promise<ShareResult> {
|
||||||
item = item || await createItem(sharerSessionId, 'root:/test.txt:', 'testing share');
|
item = item || await createItem(sharerSessionId, 'root:/test.txt:', 'testing share');
|
||||||
|
|
||||||
let share: Share = null;
|
let share: Share = null;
|
||||||
|
|
||||||
if ([ShareType.JoplinRootFolder, ShareType.Link].includes(shareType)) {
|
if ([ShareType.Folder, ShareType.Note].includes(shareType)) {
|
||||||
share = await postApi<Share>(sharerSessionId, 'shares', {
|
share = await postApi<Share>(sharerSessionId, 'shares', {
|
||||||
type: shareType,
|
type: shareType,
|
||||||
note_id: shareType === ShareType.Link ? item.jop_id : undefined,
|
note_id: shareType === ShareType.Note ? item.jop_id : undefined,
|
||||||
folder_id: shareType === ShareType.JoplinRootFolder ? item.jop_id : undefined,
|
folder_id: shareType === ShareType.Folder ? item.jop_id : undefined,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const sharer = await models().session().sessionUser(sharerSessionId);
|
const sharer = await models().session().sessionUser(sharerSessionId);
|
||||||
|
Loading…
Reference in New Issue
Block a user