Skip to content

Commit

Permalink
refactor: Use public-share aware functions from @nextcloud/files in…
Browse files Browse the repository at this point in the history
…stead of custom

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
  • Loading branch information
susnux committed Jun 21, 2024
1 parent 2e23828 commit 8bd79ea
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 51 deletions.
58 changes: 37 additions & 21 deletions __tests__/utils/uploader.spec.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,65 @@
import { beforeEach, describe, expect, test, vi } from 'vitest'
import { Uploader } from '../../lib/uploader.js'

import type { Uploader } from '../../lib/uploader'
import type { NextcloudUser } from '@nextcloud/auth'
import { beforeEach, describe, expect, test, vi } from 'vitest'

const initialState = vi.hoisted(() => ({ loadState: vi.fn() }))
const auth = vi.hoisted(() => ({ getCurrentUser: vi.fn<never, NextcloudUser | null>(() => null) }))
vi.mock('@nextcloud/initial-state', () => initialState)
vi.mock('@nextcloud/auth', () => auth)

describe('uploader', () => {
beforeEach(() => {
vi.resetModules()
vi.resetAllMocks()
const node = document.getElementById('sharingToken')
if (node) {
document.body.removeChild(node)
}
// Reset mocks of DOM
document.body.innerHTML = ''
})

// wrapper to enforce reimport for mocking different dependency state
const newUploader = async (...args: ConstructorParameters<typeof Uploader>) => {
const { Uploader } = await import('../../lib/uploader.js')
return new Uploader(...args)
}

test('constructor sets default target folder for user', async () => {
auth.getCurrentUser.mockImplementationOnce(() => ({ uid: 'my-user', displayName: 'User', isAdmin: false }))
const uploader = new Uploader()
auth.getCurrentUser.mockImplementation(() => ({ uid: 'my-user', displayName: 'User', isAdmin: false }))
const uploader = await newUploader()
expect(uploader.destination.source).match(/\/remote\.php\/dav\/files\/my-user\/?$/)
})

test('constructor sets default target folder for public share', async () => {
initialState.loadState.mockImplementationOnce((app, key) => app === 'files_sharing' && key === 'sharingToken' ? 'token-123' : null)
const uploader = new Uploader(true)
expect(uploader.destination.source).match(/\/public\.php\/dav\/files\/token-123\/?$/)
const isPublicInput = document.createElement('input')
isPublicInput.id = 'initial-state-files_sharing-isPublic'
isPublicInput.value = btoa(JSON.stringify(true))
document.body.appendChild(isPublicInput)

const input = document.createElement('input')
input.id = 'initial-state-files_sharing-sharingToken'
input.value = btoa(JSON.stringify('modern-token'))
document.body.appendChild(input)

const uploader = await newUploader(true)
expect(uploader.destination.source).match(/\/public\.php\/dav\/files\/modern-token\/?$/)
})

test('constructor sets default target folder for legacy public share', async () => {
const isPublicInput = document.createElement('input')
isPublicInput.id = 'isPublic'
isPublicInput.name = 'isPublic'
isPublicInput.type = 'hidden'
isPublicInput.value = '1'
document.body.appendChild(isPublicInput)

const input = document.createElement('input')
input.id = 'sharingToken'
input.name = 'sharingToken'
input.type = 'hidden'
input.value = 'legacy-token'
document.body.appendChild(input)
const uploader = new Uploader(true)
expect(uploader.destination.source).match(/\/public\.php\/dav\/files\/legacy-token\/?$/)
})

test('fails if no sharingToken on public share', async () => {
expect(() => new Uploader(true)).toThrow(/No sharing token found/)
const uploader = await newUploader(true)
expect(uploader.destination.source).match(/\/public\.php\/dav\/files\/legacy-token\/?$/)
})

test('fails if not logged in and not on public share', async () => {
expect(() => new Uploader()).toThrow(/User is not logged in/)
expect(initialState.loadState).not.toBeCalled()
expect(async () => await newUploader()).rejects.toThrow(/User is not logged in/)
})
})
8 changes: 6 additions & 2 deletions cypress/components/UploadPicker.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,13 @@ describe('UploadPicker valid uploads', () => {
})
})

describe('UploadPicker invalid uploads', () => {
describe('UploadPicker invalid uploads', { testIsolation: true }, () => {

// Cypress shares the module state between tests, we need to reset it
// ref: https://github.com/cypress-io/cypress/issues/25441
beforeEach(() => getUploader(true))
beforeEach(() => {
getUploader(false, true)
})

afterEach(() => {
// Make sure we clear the body
Expand Down Expand Up @@ -197,6 +199,8 @@ describe('UploadPicker invalid uploads', () => {
// Mount picker
cy.mount(UploadPicker, { propsData }).as('uploadPicker')

cy.wait(4000)

// Label is displayed before upload
cy.get('[data-cy-upload-picker]').contains('New').should('be.visible')

Expand Down
8 changes: 3 additions & 5 deletions lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { Node } from '@nextcloud/files'
import type { AsyncComponent } from 'vue'

import { loadState } from '@nextcloud/initial-state'
import { Uploader } from './uploader'
import UploadPicker from './components/UploadPicker.vue'
import Vue, { defineAsyncComponent } from 'vue'
import { isPublicShare } from '@nextcloud/sharing/public'

export type { Uploader } from './uploader'
export { Status as UploaderStatus } from './uploader'
Expand All @@ -18,12 +18,10 @@ export type ConflictResolutionResult<T extends File|FileSystemEntry|Node> = {
}
/**
* Get an Uploader instance
* @param isPublic Set to true to use public upload endpoint (by default it is auto detected)
* @param forceRecreate Force a new uploader instance - main purpose is for testing
*/
export function getUploader(forceRecreate = false): Uploader {
const isPublic = loadState<boolean | null>('files_sharing', 'isPublic', null)
?? document.querySelector('input[name="isPublic"][value="1"]') !== null

export function getUploader(isPublic: boolean = isPublicShare(), forceRecreate = false): Uploader {
if (_uploader instanceof Uploader && !forceRecreate) {
return _uploader
}
Expand Down
19 changes: 5 additions & 14 deletions lib/uploader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ import type { AxiosError, AxiosResponse } from 'axios'
import type { WebDAVClient } from 'webdav'

import { getCurrentUser } from '@nextcloud/auth'
import { Folder, Permission, davGetClient } from '@nextcloud/files'
import { loadState } from '@nextcloud/initial-state'
import { Folder, Permission, davGetClient, davRemoteURL, davRootPath } from '@nextcloud/files'
import { encodePath } from '@nextcloud/paths'
import { generateRemoteUrl } from '@nextcloud/router'
import { normalize } from 'path'
import { join, normalize } from 'path'

import axios, { isCancel } from '@nextcloud/axios'
import PCancelable from 'p-cancelable'
Expand Down Expand Up @@ -54,31 +52,24 @@ export class Uploader {
this._isPublic = isPublic

if (!destinationFolder) {
const source = join(davRemoteURL, davRootPath)
let owner: string
let source: string

if (isPublic) {
const sharingToken = loadState<string | null>('files_sharing', 'sharingToken', null) ?? document.querySelector<HTMLInputElement>('input#sharingToken')?.value
if (!sharingToken) {
logger.error('No sharing token found for public shares, please specify the destination folder manually.')
throw new Error('No sharing token found.')
}
owner = sharingToken
source = generateRemoteUrl(`dav/files/${sharingToken}`).replace('remote.php', 'public.php')
owner = 'anonymous'
} else {
const user = getCurrentUser()?.uid
if (!user) {
throw new Error('User is not logged in')
}
owner = user
source = generateRemoteUrl(`dav/files/${owner}`)
}

destinationFolder = new Folder({
id: 0,
owner,
permissions: Permission.ALL,
root: `/files/${owner}`,
root: davRootPath,
source,
})
}
Expand Down
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,11 @@
"@nextcloud/axios": "^2.5.0",
"@nextcloud/dialogs": "^5.2.0",
"@nextcloud/files": "^3.5.1",
"@nextcloud/initial-state": "^2.2.0",
"@nextcloud/l10n": "^3.0.1",
"@nextcloud/logger": "^3.0.1",
"@nextcloud/l10n": "^3.1.0",
"@nextcloud/logger": "^3.0.2",
"@nextcloud/paths": "^2.1.0",
"@nextcloud/router": "^3.0.0",
"@nextcloud/sharing": "^0.2.2",
"axios": "^1.7.2",
"axios-retry": "^4.4.0",
"crypto-browserify": "^3.12.0",
Expand Down

0 comments on commit 8bd79ea

Please sign in to comment.