From 945170d5ff6805bd227b77bab11c2e1526c6bc8d Mon Sep 17 00:00:00 2001 From: Jannik Stehle Date: Fri, 20 Oct 2023 09:49:09 +0200 Subject: [PATCH] fix: resolving external URLs via file ID --- .../unreleased/bugfix-external-url-resolving | 6 + .../OcNotificationMessage.vue | 2 +- packages/web-app-external/src/App.vue | 109 +++++++++++++++++- .../Shares/Collaborators/EditDropdown.vue | 2 +- .../web-pkg/src/composables/fileInfo/index.ts | 1 + .../fileInfo/useLoadFileInfoById.ts | 38 ++++++ 6 files changed, 154 insertions(+), 4 deletions(-) create mode 100644 changelog/unreleased/bugfix-external-url-resolving create mode 100644 packages/web-pkg/src/composables/fileInfo/index.ts create mode 100644 packages/web-pkg/src/composables/fileInfo/useLoadFileInfoById.ts diff --git a/changelog/unreleased/bugfix-external-url-resolving b/changelog/unreleased/bugfix-external-url-resolving new file mode 100644 index 00000000000..30eb803ccde --- /dev/null +++ b/changelog/unreleased/bugfix-external-url-resolving @@ -0,0 +1,6 @@ +Bugfix: Resolving external URLs + +Resolving external URLs when only the file ID is given has been fixed. + +https://github.com/owncloud/web/issues/9804 +https://github.com/owncloud/web/pull/9833 diff --git a/packages/design-system/src/components/OcNotificationMessage/OcNotificationMessage.vue b/packages/design-system/src/components/OcNotificationMessage/OcNotificationMessage.vue index 15ec854d055..319a2c87598 100644 --- a/packages/design-system/src/components/OcNotificationMessage/OcNotificationMessage.vue +++ b/packages/design-system/src/components/OcNotificationMessage/OcNotificationMessage.vue @@ -11,7 +11,7 @@ {{ title }} - diff --git a/packages/web-app-external/src/App.vue b/packages/web-app-external/src/App.vue index d8c5a1bcd60..c8a21597c54 100644 --- a/packages/web-app-external/src/App.vue +++ b/packages/web-app-external/src/App.vue @@ -39,10 +39,26 @@ import { mapGetters } from 'vuex' import { computed, defineComponent, unref } from 'vue' import { urlJoin } from 'web-client/src/utils' import AppTopBar from 'web-pkg/src/components/AppTopBar.vue' -import { queryItemAsString, useAppDefaults, useRouteQuery } from 'web-pkg/src/composables' +import { + queryItemAsString, + useAppDefaults, + useClientService, + useRoute, + useRouteParam, + useRouteQuery, + useRouter, + useStore +} from 'web-pkg/src/composables' import { configurationManager } from 'web-pkg/src/configuration' import ErrorScreen from './components/ErrorScreen.vue' import LoadingScreen from './components/LoadingScreen.vue' +import { + Resource, + SpaceResource, + buildShareSpaceResource, + isMountPointSpaceResource +} from 'web-client/src/helpers' +import { useLoadFileInfoById } from 'web-pkg/src/composables/fileInfo' export default defineComponent({ name: 'ExternalApp', @@ -52,14 +68,99 @@ export default defineComponent({ LoadingScreen }, setup() { + const store = useStore() + const router = useRouter() + const currentRoute = useRoute() + const clientService = useClientService() + const { loadFileInfoByIdTask } = useLoadFileInfoById({ clientService }) const appName = useRouteQuery('app') const applicationName = computed(() => queryItemAsString(unref(appName))) + + const fileIdQueryItem = useRouteQuery('fileId') + const fileId = computed(() => { + return queryItemAsString(unref(fileIdQueryItem)) + }) + + const driveAliasAndItem = useRouteParam('driveAliasAndItem') + + const getMatchingSpace = (id): SpaceResource => { + return store.getters['runtime/spaces/spaces'].find((space) => id.startsWith(space.id)) + } + const findMatchingMountPoint = (id: string | number): SpaceResource => { + return store.getters['runtime/spaces/spaces'].find( + (space) => isMountPointSpaceResource(space) && space.root?.remoteItem?.id === id + ) + } + + const addMissingDriveAliasAndItem = async () => { + const id = unref(fileId) + let path: string + let matchingSpace = getMatchingSpace(id) + if (matchingSpace) { + path = await clientService.owncloudSdk.files.getPathForFileId(id) + const driveAliasAndItem = matchingSpace.getDriveAliasAndItem({ path } as Resource) + console.log(unref(currentRoute).query) + return router.push({ + params: { + ...unref(currentRoute).params, + driveAliasAndItem + }, + query: { + ...(unref(currentRoute).query?.app && { app: unref(currentRoute).query?.app }), + contextRouteName: 'files-spaces-generic' + } + }) + } + + // no matching space found => the file doesn't lie in own spaces => it's a share. + // do PROPFINDs on parents until root of accepted share is found in `mountpoint` spaces + await store.dispatch('runtime/spaces/loadMountPoints', { + graphClient: clientService.graphAuthenticated + }) + let mountPoint = findMatchingMountPoint(id) + const resource = await loadFileInfoByIdTask.perform(id) + const sharePathSegments = mountPoint ? [] : [unref(resource).name] + let tmpResource = unref(resource) + while (!mountPoint) { + try { + tmpResource = await loadFileInfoByIdTask.perform(tmpResource.parentFolderId) + } catch (e) { + throw Error(e) + } + mountPoint = findMatchingMountPoint(tmpResource.id) + if (!mountPoint) { + sharePathSegments.unshift(tmpResource.name) + } + } + matchingSpace = buildShareSpaceResource({ + shareId: mountPoint.nodeId, + shareName: mountPoint.name, + serverUrl: configurationManager.serverUrl + }) + path = urlJoin(...sharePathSegments) + + const driveAliasAndItem = matchingSpace.getDriveAliasAndItem({ path } as Resource) + return router.push({ + params: { + ...unref(currentRoute).params, + driveAliasAndItem + }, + query: { + shareId: matchingSpace.shareId, + ...(unref(currentRoute).query?.app && { app: unref(currentRoute).query?.app }), + contextRouteName: 'files-shares-with-me' + } + }) + } + return { ...useAppDefaults({ applicationId: 'external', applicationName }), - applicationName + applicationName, + driveAliasAndItem, + addMissingDriveAliasAndItem } }, @@ -94,6 +195,10 @@ export default defineComponent({ async created() { this.loading = true try { + if (!this.driveAliasAndItem) { + await this.addMissingDriveAliasAndItem() + } + this.resource = await this.getFileInfo(this.currentFileContext, { davProperties: [] }) diff --git a/packages/web-app-files/src/components/SideBar/Shares/Collaborators/EditDropdown.vue b/packages/web-app-files/src/components/SideBar/Shares/Collaborators/EditDropdown.vue index 08cad53e625..aa1421abc3f 100644 --- a/packages/web-app-files/src/components/SideBar/Shares/Collaborators/EditDropdown.vue +++ b/packages/web-app-files/src/components/SideBar/Shares/Collaborators/EditDropdown.vue @@ -29,8 +29,8 @@ > diff --git a/packages/web-pkg/src/composables/fileInfo/index.ts b/packages/web-pkg/src/composables/fileInfo/index.ts new file mode 100644 index 00000000000..2ffc69d647f --- /dev/null +++ b/packages/web-pkg/src/composables/fileInfo/index.ts @@ -0,0 +1 @@ +export * from './useLoadFileInfoById' diff --git a/packages/web-pkg/src/composables/fileInfo/useLoadFileInfoById.ts b/packages/web-pkg/src/composables/fileInfo/useLoadFileInfoById.ts new file mode 100644 index 00000000000..42cd94126c5 --- /dev/null +++ b/packages/web-pkg/src/composables/fileInfo/useLoadFileInfoById.ts @@ -0,0 +1,38 @@ +import { ClientService } from 'web-pkg/src/services' +import { useClientService } from 'web-pkg/src/composables' +import { useTask } from 'vue-concurrency' +import { buildSpace, buildWebDavSpacesPath } from 'web-client/src/helpers' +import { DavProperty } from 'web-client/src/webdav/constants' + +export interface LoadFileInfoByIdOptions { + clientService?: ClientService + davProperties?: DavProperty[] +} + +export const useLoadFileInfoById = (options: LoadFileInfoByIdOptions) => { + const { webdav } = options.clientService || useClientService() + const davProperties = options.davProperties || [ + DavProperty.FileId, + DavProperty.FileParent, + DavProperty.Name, + DavProperty.ResourceType + ] + + const loadFileInfoByIdTask = useTask(function* (signal, fileId: string | number) { + const space = buildSpace({ + id: fileId, + webDavPath: buildWebDavSpacesPath(fileId) + }) + return yield webdav.getFileInfo( + space, + {}, + { + davProperties + } + ) + }) + + return { + loadFileInfoByIdTask + } +}