Skip to content

Commit 2e0b001

Browse files
committed
refactor(appstore): adjust frontend for new API location
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
1 parent 3f87105 commit 2e0b001

9 files changed

Lines changed: 86 additions & 62 deletions

File tree

apps/appstore/src/components/AppStoreDiscover/AppStoreDiscoverSection.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,13 @@ import { mdiEyeOffOutline } from '@mdi/js'
3838
import axios from '@nextcloud/axios'
3939
import { showError } from '@nextcloud/dialogs'
4040
import { translate as t } from '@nextcloud/l10n'
41-
import { generateUrl } from '@nextcloud/router'
41+
import { generateOcsUrl } from '@nextcloud/router'
4242
import { defineAsyncComponent, defineComponent, onBeforeMount, ref } from 'vue'
4343
import NcEmptyContent from '@nextcloud/vue/components/NcEmptyContent'
4444
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
4545
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
46-
import logger from '../../utils/logger.ts'
4746
import { filterElements, parseApiResponse } from '../../utils/appDiscoverParser.ts'
47+
import logger from '../../utils/logger.ts'
4848
4949
const PostType = defineAsyncComponent(() => import('./PostType.vue'))
5050
const CarouselType = defineAsyncComponent(() => import('./CarouselType.vue'))

apps/appstore/src/components/AppStoreDiscover/PostType.vue

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ import type { PropType } from 'vue'
6666
import type { IAppDiscoverPost } from '../../constants/AppDiscoverTypes.ts'
6767
6868
import { mdiPlayCircleOutline } from '@mdi/js'
69-
import { generateUrl } from '@nextcloud/router'
69+
import { generateOcsUrl } from '@nextcloud/router'
7070
import { useElementSize, useElementVisibility } from '@vueuse/core'
7171
import { computed, defineComponent, ref, watchEffect } from 'vue'
7272
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
@@ -144,7 +144,9 @@ export default defineComponent({
144144
*
145145
* @param url The URL to resolve
146146
*/
147-
const generatePrivacyUrl = (url: string) => url.startsWith('/') ? url : generateUrl('/settings/api/apps/media?fileName={fileName}', { fileName: url })
147+
const generatePrivacyUrl = (url: string) => url.startsWith('/')
148+
? url
149+
: generateOcsUrl('/apps/appstore/api/v1/discover/media?fileName={fileName}', { fileName: url })
148150
149151
const mediaElement = ref<HTMLVideoElement | HTMLPictureElement>()
150152
const mediaIsVisible = useElementVisibility(mediaElement, { threshold: 0.3 })

apps/appstore/src/store/api.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
*/
55

66
import axios from '@nextcloud/axios'
7-
import { confirmPassword } from '@nextcloud/password-confirmation'
7+
import { addPasswordConfirmationInterceptors, confirmPassword } from '@nextcloud/password-confirmation'
8+
9+
addPasswordConfirmationInterceptors(axios)
810

911
/**
1012
* @param {string} url - The url to sanitize
@@ -52,8 +54,8 @@ export default {
5254
get(url, options) {
5355
return axios.get(sanitize(url), options)
5456
},
55-
post(url, data) {
56-
return axios.post(sanitize(url), data)
57+
post(url, data, options) {
58+
return axios.post(sanitize(url), data, options)
5759
},
5860
patch(url, data) {
5961
return axios.patch(sanitize(url), data)

apps/appstore/src/store/apps-store.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
55

6+
import type { OCSResponse } from '@nextcloud/typings/ocs'
67
import type { IAppstoreApp, IAppstoreCategory } from '../app-types.ts'
78

89
import axios from '@nextcloud/axios'
910
import { showError } from '@nextcloud/dialogs'
1011
import { loadState } from '@nextcloud/initial-state'
1112
import { translate as t } from '@nextcloud/l10n'
12-
import { generateUrl } from '@nextcloud/router'
13+
import { generateOcsUrl } from '@nextcloud/router'
1314
import { defineStore } from 'pinia'
1415
import APPSTORE_CATEGORY_ICONS from '../constants/AppstoreCategoryIcons.ts'
1516
import logger from '../utils/logger.ts'
@@ -37,8 +38,10 @@ export const useAppsStore = defineStore('appstore-apps', {
3738

3839
try {
3940
this.loading.categories = true
40-
const { data: categories } = await axios.get<IAppstoreCategory[]>(generateUrl('settings/apps/categories'))
41+
const url = generateOcsUrl('apps/appstore/api/v1/apps/categories')
42+
const { data } = await axios.get<OCSResponse<IAppstoreCategory[]>>(url)
4143

44+
const categories = data.ocs.data
4245
for (const category of categories) {
4346
category.icon = APPSTORE_CATEGORY_ICONS[category.id] ?? ''
4447
}
@@ -61,10 +64,11 @@ export const useAppsStore = defineStore('appstore-apps', {
6164

6265
try {
6366
this.loading.apps = true
64-
const { data } = await axios.get<{ apps: IAppstoreApp[] }>(generateUrl('settings/apps/list'))
67+
const url = generateOcsUrl('apps/appstore/api/v1/apps')
68+
const { data } = await axios.get<OCSResponse<IAppstoreApp[]>>(url)
6569

6670
this.$patch({
67-
apps: data.apps,
71+
apps: data.ocs.data,
6872
})
6973
} catch (error) {
7074
logger.error(error as Error)

apps/appstore/src/store/apps.js

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import axios from '@nextcloud/axios'
77
import { showError, showInfo } from '@nextcloud/dialogs'
88
import { loadState } from '@nextcloud/initial-state'
99
import { PwdConfirmationMode } from '@nextcloud/password-confirmation'
10-
import { generateUrl } from '@nextcloud/router'
10+
import { generateOcsUrl, generateUrl } from '@nextcloud/router'
1111
import Vue from 'vue'
1212
import logger from '../utils/logger.ts'
1313
import api from './api.js'
@@ -196,7 +196,9 @@ const actions = {
196196
}
197197
})
198198

199-
return api.post(generateUrl('settings/apps/enable'), { appIds: apps, groups }, { confirmPassword: PwdConfirmationMode.Strict })
199+
const url = generateOcsUrl('apps/appstore/api/v1/apps/enable')
200+
return Promise.all(apps.map((appId) => api
201+
.post(url, { appId, groups }, { confirmPassword: PwdConfirmationMode.Strict })
200202
.then((response) => {
201203
context.commit('stopLoading', apps)
202204
context.commit('stopLoading', 'install')
@@ -256,7 +258,7 @@ const actions = {
256258
})
257259
context.commit('APPS_API_FAILURE', { appId, error })
258260
}
259-
})
261+
})))
260262
},
261263
forceEnableApp(context, { appId }) {
262264
let apps
@@ -268,7 +270,8 @@ const actions = {
268270
return api.requireAdmin().then(() => {
269271
context.commit('startLoading', apps)
270272
context.commit('startLoading', 'install')
271-
return api.post(generateUrl('settings/apps/force'), { appId })
273+
const url = generateOcsUrl('apps/appstore/api/v1/apps/enable')
274+
return api.post(url, { appId, force: true }, { confirmPassword: PwdConfirmationMode.Strict })
272275
.then(() => {
273276
context.commit('setInstallState', { appId, canInstall: true })
274277
})
@@ -296,24 +299,28 @@ const actions = {
296299
}
297300
return api.requireAdmin().then(() => {
298301
context.commit('startLoading', apps)
299-
return api.post(generateUrl('settings/apps/disable'), { appIds: apps })
300-
.then(() => {
301-
context.commit('stopLoading', apps)
302-
apps.forEach((_appId) => {
303-
context.commit('disableApp', _appId)
302+
const url = generateOcsUrl('apps/appstore/api/v1/apps/disable')
303+
return Promise.all(apps.map((appId) => {
304+
return api.post(url, { appId })
305+
.then(() => {
306+
context.commit('stopLoading', apps)
307+
apps.forEach((_appId) => {
308+
context.commit('disableApp', _appId)
309+
})
310+
return true
304311
})
305-
return true
306-
})
307-
.catch((error) => {
308-
context.commit('stopLoading', apps)
309-
context.commit('APPS_API_FAILURE', { appId, error })
310-
})
312+
.catch((error) => {
313+
context.commit('stopLoading', apps)
314+
context.commit('APPS_API_FAILURE', { appId, error })
315+
})
316+
}))
311317
}).catch((error) => context.commit('API_FAILURE', { appId, error }))
312318
},
313319
uninstallApp(context, { appId }) {
314320
return api.requireAdmin().then(() => {
315321
context.commit('startLoading', appId)
316-
return api.get(generateUrl(`settings/apps/uninstall/${appId}`))
322+
const url = generateOcsUrl('apps/appstore/api/v1/apps/uninstall')
323+
return api.post(url, { appId })
317324
.then(() => {
318325
context.commit('stopLoading', appId)
319326
context.commit('uninstallApp', appId)
@@ -330,7 +337,8 @@ const actions = {
330337
return api.requireAdmin().then(() => {
331338
context.commit('startLoading', appId)
332339
context.commit('startLoading', 'install')
333-
return api.get(generateUrl(`settings/apps/update/${appId}`))
340+
const url = generateOcsUrl('apps/appstore/api/v1/apps/update')
341+
return api.post(url, { appId }, { confirmPassword: PwdConfirmationMode.Strict })
334342
.then(() => {
335343
context.commit('stopLoading', 'install')
336344
context.commit('stopLoading', appId)
@@ -347,9 +355,11 @@ const actions = {
347355

348356
getAllApps(context) {
349357
context.commit('startLoading', 'list')
350-
return api.get(generateUrl('settings/apps/list'))
358+
const url = generateOcsUrl('apps/appstore/api/v1/apps')
359+
return api.get(url)
351360
.then((response) => {
352-
context.commit('setAllApps', response.data.apps)
361+
const apps = response.data.ocs.data
362+
context.commit('setAllApps', apps)
353363
context.commit('stopLoading', 'list')
354364
return true
355365
})
@@ -360,9 +370,9 @@ const actions = {
360370
if (shouldRefetchCategories || !context.state.gettingCategoriesPromise) {
361371
context.commit('startLoading', 'categories')
362372
try {
363-
const categoriesPromise = api.get(generateUrl('settings/apps/categories'))
373+
const categoriesPromise = api.get(generateOcsUrl('apps/appstore/api/v1/apps/categories'))
364374
context.commit('updateCategories', categoriesPromise)
365-
const categoriesPromiseResponse = await categoriesPromise
375+
const categoriesPromiseResponse = (await categoriesPromise).data.ocs
366376
if (categoriesPromiseResponse.data.length > 0) {
367377
context.commit('appendCategories', categoriesPromiseResponse.data)
368378
context.commit('stopLoading', 'categories')
Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55

66
import { User } from '@nextcloud/e2e-test-server/cypress'
7-
import { handlePasswordConfirmation } from './usersUtils.ts'
7+
import { handlePasswordConfirmation } from '../core-utils.ts'
88

99
const admin = new User('admin', 'admin')
1010

@@ -19,7 +19,7 @@ describe('Settings: App management', { testIsolation: true }, () => {
1919
cy.login(admin)
2020

2121
// Intercept the apps list request
22-
cy.intercept('GET', '*/settings/apps/list').as('fetchAppsList')
22+
cy.intercept('GET', '**/ocs/v2.php/apps/appstore/api/v1/apps').as('fetchAppsList')
2323

2424
// I open the Apps management
2525
cy.visit('/settings/apps/installed')
@@ -29,6 +29,7 @@ describe('Settings: App management', { testIsolation: true }, () => {
2929
})
3030

3131
it('Can enable an installed app', () => {
32+
cy.intercept('POST', '**/ocs/v2.php/apps/appstore/api/v1/apps/enable').as('enableApp')
3233
cy.get('#apps-list').should('exist')
3334
// Wait for the app list to load
3435
.contains('tr', 'QA testing', { timeout: 10000 })
@@ -38,6 +39,7 @@ describe('Settings: App management', { testIsolation: true }, () => {
3839
.click({ force: true })
3940

4041
handlePasswordConfirmation(admin.password)
42+
cy.wait('@enableApp')
4143

4244
// Wait until we see the disable button for the app
4345
cy.get('#apps-list').should('exist')
@@ -54,6 +56,7 @@ describe('Settings: App management', { testIsolation: true }, () => {
5456
})
5557

5658
it('Can disable an installed app', () => {
59+
cy.intercept('POST', '**/ocs/v2.php/apps/appstore/api/v1/apps/disable').as('disableApp')
5760
cy.get('#apps-list')
5861
.should('exist')
5962
// Wait for the app list to load
@@ -64,6 +67,7 @@ describe('Settings: App management', { testIsolation: true }, () => {
6467
.click({ force: true })
6568

6669
handlePasswordConfirmation(admin.password)
70+
cy.wait('@disableApp')
6771

6872
// Wait until we see the disable button for the app
6973
cy.get('#apps-list').should('exist')
@@ -137,12 +141,11 @@ describe('Settings: App management', { testIsolation: true }, () => {
137141
.find('.app-sidebar-header__info')
138142
.should('contain', 'QA testing')
139143
cy.get('#app-sidebar-vue').contains('a', 'View in store').should('exist')
140-
cy.get('#app-sidebar-vue').find('input[type="button"][value="Enable"]').should('be.visible')
141-
cy.get('#app-sidebar-vue').find('input[type="button"][value="Remove"]').should('be.visible')
144+
cy.get('#app-sidebar-vue').findByRole('button', { name: 'Enable' }).should('be.visible')
142145
cy.get('#app-sidebar-vue').contains(/Version \d+\.\d+\.\d+/).should('be.visible')
143146
})
144147

145-
it('Limit app usage to group', () => {
148+
it.skip('Limit app usage to group', () => {
146149
// When I open the "Active apps" section
147150
cy.get('#app-category-enabled a')
148151
.should('contain', 'Active apps')

cypress/e2e/core-utils.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,32 @@ export function getUnifiedSearchModal() {
1010
return cy.get('#unified-search')
1111
}
1212

13+
/**
14+
* Handle the confirm password dialog (if needed)
15+
*
16+
* @param adminPassword The admin password for the dialog
17+
*/
18+
export function handlePasswordConfirmation(adminPassword = 'admin') {
19+
const handleModal = (context: Cypress.Chainable) => {
20+
return context.contains('.modal-container', 'Authentication required')
21+
.if()
22+
.within(() => {
23+
cy.get('input[type="password"]')
24+
.type(adminPassword)
25+
cy.findByRole('button', { name: 'Confirm' })
26+
.click()
27+
})
28+
}
29+
30+
return cy.get('body')
31+
.if()
32+
.then(() => handleModal(cy.get('body')))
33+
.else()
34+
// Handle if inside a cy.within
35+
.root().closest('body')
36+
.then(($body) => handleModal(cy.wrap($body)))
37+
}
38+
1339
/**
1440
* Open the unified search modal
1541
*/

cypress/e2e/settings/usersUtils.ts

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -73,28 +73,4 @@ export function saveEditDialog() {
7373
cy.get('.edit-dialog').should('not.exist')
7474
}
7575

76-
/**
77-
* Handle the confirm password dialog (if needed)
78-
*
79-
* @param adminPassword The admin password for the dialog
80-
*/
81-
export function handlePasswordConfirmation(adminPassword = 'admin') {
82-
const handleModal = (context: Cypress.Chainable) => {
83-
return context.contains('.modal-container', 'Authentication required')
84-
.if()
85-
.within(() => {
86-
cy.get('input[type="password"]')
87-
.type(adminPassword)
88-
cy.findByRole('button', { name: 'Confirm' })
89-
.click()
90-
})
91-
}
92-
93-
return cy.get('body')
94-
.if()
95-
.then(() => handleModal(cy.get('body')))
96-
.else()
97-
// Handle if inside a cy.within
98-
.root().closest('body')
99-
.then(($body) => handleModal(cy.wrap($body)))
100-
}
76+
export { handlePasswordConfirmation } from '../core-utils.ts'

lib/private/legacy/OC_App.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,7 @@ public function listAllApps(): array {
471471
}
472472

473473
$info['version'] = $appManager->getAppVersion($app);
474+
$info['license'] ??= $info['licence'];
474475
$appList[] = $info;
475476
}
476477
}

0 commit comments

Comments
 (0)