From c3c4d9843d8d9ad117bee68b270260a58134796f Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Mon, 26 Aug 2024 13:53:14 +0800 Subject: [PATCH 1/2] implementing permission based locks into ui --- .../add-product-as-entity-button.js | 6 +- .../components/modals/create-first-store.hbs | 4 +- .../widget/storefront-key-metrics.hbs | 2 +- addon/controllers/application.js | 2 +- .../products/index/category/edit.js | 1 + .../products/index/category/new.js | 3 +- addon/routes/application.js | 17 +- addon/routes/customers/index.js | 11 + addon/routes/customers/index/edit.js | 15 +- addon/routes/customers/index/view.js | 11 + addon/routes/networks/index.js | 11 + addon/routes/networks/index/network.js | 11 + addon/routes/orders/index.js | 11 + addon/routes/orders/index/edit.js | 15 +- addon/routes/orders/index/new.js | 15 +- addon/routes/orders/index/view.js | 10 + addon/routes/products/index.js | 11 + addon/routes/products/index/category.js | 13 +- addon/routes/products/index/category/edit.js | 11 + addon/routes/products/index/category/new.js | 16 +- addon/routes/products/index/index.js | 11 + addon/routes/settings/index.js | 11 + addon/services/storefront.js | 109 +- addon/templates/application.hbs | 36 +- addon/templates/customers/index.hbs | 2 +- addon/templates/networks/index.hbs | 7 +- addon/templates/orders/index.hbs | 4 +- addon/templates/products/index.hbs | 16 +- addon/templates/products/index/category.hbs | 15 +- .../templates/products/index/category/new.hbs | 571 +- addon/templates/products/index/index.hbs | 4 +- addon/templates/settings/index.hbs | 1 + composer.json | 2 +- extension.json | 2 +- package.json | 9 +- pnpm-lock.yaml | 22944 ++++++++-------- server/src/Auth/Schemas/Storefront.php | 21 +- .../Http/Controllers/v1/CartController.php | 2 +- .../Controllers/v1/CustomerController.php | 2 +- server/src/Http/Resources/Product.php | 10 +- server/src/Models/Cart.php | 2 +- server/src/Models/Network.php | 6 +- server/src/Models/Store.php | 8 +- server/src/Support/Metrics.php | 16 +- server/src/Support/QPay.php | 8 +- 45 files changed, 11685 insertions(+), 12330 deletions(-) diff --git a/addon/components/add-product-as-entity-button.js b/addon/components/add-product-as-entity-button.js index 4db8f67..ee46f7f 100644 --- a/addon/components/add-product-as-entity-button.js +++ b/addon/components/add-product-as-entity-button.js @@ -40,12 +40,12 @@ export default class AddProductAsEntityButtonComponent extends Component { this.order.meta.pushObjects([ { key: 'storefront', - value: selectedStorefront.name + value: selectedStorefront.name, }, { key: 'storefront_id', - value: selectedStorefront.public_id - } + value: selectedStorefront.public_id, + }, ]); } }) diff --git a/addon/components/modals/create-first-store.hbs b/addon/components/modals/create-first-store.hbs index d396f00..3ea1cd2 100644 --- a/addon/components/modals/create-first-store.hbs +++ b/addon/components/modals/create-first-store.hbs @@ -15,18 +15,20 @@ @name={{t "storefront.component.modals.create-first-store.storefront-name"}} @value={{@options.store.name}} @helpText={{t "storefront.component.modals.create-first-store.enter-name"}} + @disabled={{cannot "storefront create store"}} /> - + diff --git a/addon/components/widget/storefront-key-metrics.hbs b/addon/components/widget/storefront-key-metrics.hbs index b297016..bc6556a 100644 --- a/addon/components/widget/storefront-key-metrics.hbs +++ b/addon/components/widget/storefront-key-metrics.hbs @@ -10,7 +10,7 @@ {{else}}
{{#each-in this.metrics as |title options|}} - + {{/each-in}}
{{/if}} diff --git a/addon/controllers/application.js b/addon/controllers/application.js index cc67f40..c41332c 100644 --- a/addon/controllers/application.js +++ b/addon/controllers/application.js @@ -12,7 +12,7 @@ export default class ApplicationController extends Controller { @action createNewStorefront() { return this.storefront.createNewStorefront({ onSuccess: () => { - const loader = this.loader.show({ loadingMessage: `Switching to newly created store...` }); + const loader = this.loader.show({ loadingMessage: 'Switching to newly created store...' }); this.hostRouter.refresh().then(() => { this.notifyPropertyChange('activeStore'); diff --git a/addon/controllers/products/index/category/edit.js b/addon/controllers/products/index/category/edit.js index cfe4bf1..a7aa61a 100644 --- a/addon/controllers/products/index/category/edit.js +++ b/addon/controllers/products/index/category/edit.js @@ -11,6 +11,7 @@ export default class ProductsIndexCategoryEditController extends ProductsIndexCa @tracked overlayActionButtonTitle = 'Save Changes'; @tracked overlayActionButtonIcon = 'save'; @tracked overlayExitButtonTitle = 'Done'; + abilityPermission = 'storefront update product'; get overlayTitle() { return `Edit ${this.product.name}`; diff --git a/addon/controllers/products/index/category/new.js b/addon/controllers/products/index/category/new.js index befe937..216a5cc 100644 --- a/addon/controllers/products/index/category/new.js +++ b/addon/controllers/products/index/category/new.js @@ -26,6 +26,7 @@ export default class ProductsIndexCategoryNewController extends BaseController { @tracked uploadedFiles = []; @tracked primaryFile = null; @tracked isSaving = false; + abilityPermission = 'storefront create product'; /** overlay options */ @tracked overlayTitle = 'New Product'; @@ -66,7 +67,7 @@ export default class ProductsIndexCategoryNewController extends BaseController { this.loader.removeLoader(loader); this.notifications.success(this.intl.t('storefront.products.index.new.new-product-created-success')); - + try { yield this.transitionToRoute('products.index.category', category.slug); } catch (error) {} diff --git a/addon/routes/application.js b/addon/routes/application.js index a1e604a..279c6b7 100644 --- a/addon/routes/application.js +++ b/addon/routes/application.js @@ -8,20 +8,31 @@ export default class ApplicationRoute extends Route { @service loader; @service currentUser; @service modalsManager; - // @service theme; + @service notifications; + @service hostRouter; + @service abilities; + @service intl; @service storefront; @action loading(transition) { this.loader.showOnInitialTransition(transition, 'section.next-view-section', { loadingMessage: 'Loading storefront...' }); } + @action error(error) { + this.notifications.serverError(error); + } + @action willTransition() { this.modalsManager.done(); } beforeModel() { - this.disableSandbox(); + if (this.abilities.cannot('storefront view extension')) { + this.notifications.warning(this.intl.t('common.unauthorized-access')); + return this.hostRouter.transitionTo('console'); + } + this.disableSandbox(); return this.fetch.get('actions/store-count', {}, { namespace: 'storefront/int/v1' }).then(({ storeCount }) => { // if no store count prompt to create a store if (!storeCount) { @@ -40,7 +51,7 @@ export default class ApplicationRoute extends Route { } } - @action disableSandbox() { + disableSandbox() { this.currentUser.setOption('sandbox', false); // this.theme.setEnvironment(); } diff --git a/addon/routes/customers/index.js b/addon/routes/customers/index.js index 1a41a90..fcd62dd 100644 --- a/addon/routes/customers/index.js +++ b/addon/routes/customers/index.js @@ -6,6 +6,10 @@ import isNestedRouteTransition from '@fleetbase/ember-core/utils/is-nested-route export default class CustomersIndexRoute extends Route { @service store; @service storefront; + @service intl; + @service abilities; + @service hostRouter; + @service notifications; queryParams = { page: { refreshModel: true }, @@ -29,6 +33,13 @@ export default class CustomersIndexRoute extends Route { } } + beforeModel() { + if (this.abilities.cannot('storefront list customer')) { + this.notifications.warning(this.intl.t('common.unauthorized-access')); + return this.hostRouter.transitionTo('console.storefront'); + } + } + model(params) { return this.store.query('customer', { ...params, storefront: this.storefront.getActiveStore('public_id') }); } diff --git a/addon/routes/customers/index/edit.js b/addon/routes/customers/index/edit.js index 1cd64b4..4290631 100644 --- a/addon/routes/customers/index/edit.js +++ b/addon/routes/customers/index/edit.js @@ -1,3 +1,16 @@ import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; -export default class CustomersIndexEditRoute extends Route {} +export default class CustomersIndexEditRoute extends Route { + @service intl; + @service abilities; + @service hostRouter; + @service notifications; + + beforeModel() { + if (this.abilities.cannot('storefront update customer')) { + this.notifications.warning(this.intl.t('common.unauthorized-access')); + return this.hostRouter.transitionTo('console.storefront'); + } + } +} diff --git a/addon/routes/customers/index/view.js b/addon/routes/customers/index/view.js index 94612de..75a94a9 100644 --- a/addon/routes/customers/index/view.js +++ b/addon/routes/customers/index/view.js @@ -3,11 +3,22 @@ import { inject as service } from '@ember/service'; export default class CustomersIndexViewRoute extends Route { @service store; + @service intl; + @service abilities; + @service hostRouter; + @service notifications; queryParams = { view: { refreshModel: false }, }; + beforeModel() { + if (this.abilities.cannot('storefront view customer')) { + this.notifications.warning(this.intl.t('common.unauthorized-access')); + return this.hostRouter.transitionTo('console.storefront'); + } + } + model({ public_id }) { return this.store.findRecord('contact', public_id); } diff --git a/addon/routes/networks/index.js b/addon/routes/networks/index.js index cdc8735..0dbe597 100644 --- a/addon/routes/networks/index.js +++ b/addon/routes/networks/index.js @@ -5,6 +5,10 @@ import isNestedRouteTransition from '@fleetbase/ember-core/utils/is-nested-route export default class NetworksIndexRoute extends Route { @service store; + @service intl; + @service abilities; + @service hostRouter; + @service notifications; queryParams = { page: { refreshModel: true }, @@ -22,6 +26,13 @@ export default class NetworksIndexRoute extends Route { } } + beforeModel() { + if (this.abilities.cannot('storefront list network')) { + this.notifications.warning(this.intl.t('common.unauthorized-access')); + return this.hostRouter.transitionTo('console.storefront'); + } + } + model(params) { return this.store.query('network', { with_gateways: 1, with_notification_channels: 1, ...params }); } diff --git a/addon/routes/networks/index/network.js b/addon/routes/networks/index/network.js index 2ff4ea2..f0b671f 100644 --- a/addon/routes/networks/index/network.js +++ b/addon/routes/networks/index/network.js @@ -3,6 +3,17 @@ import { inject as service } from '@ember/service'; export default class NetworksIndexNetworkRoute extends Route { @service store; + @service intl; + @service abilities; + @service hostRouter; + @service notifications; + + beforeModel() { + if (this.abilities.cannot('storefront view network')) { + this.notifications.warning(this.intl.t('common.unauthorized-access')); + return this.hostRouter.transitionTo('console.storefront'); + } + } model({ public_id }) { return this.store.findRecord('network', public_id); diff --git a/addon/routes/orders/index.js b/addon/routes/orders/index.js index 00a5de7..08260b5 100644 --- a/addon/routes/orders/index.js +++ b/addon/routes/orders/index.js @@ -6,6 +6,10 @@ import isNestedRouteTransition from '@fleetbase/ember-core/utils/is-nested-route export default class OrdersIndexRoute extends Route { @service store; @service storefront; + @service intl; + @service abilities; + @service hostRouter; + @service notifications; queryParams = { page: { refreshModel: true }, @@ -33,6 +37,13 @@ export default class OrdersIndexRoute extends Route { } } + beforeModel() { + if (this.abilities.cannot('storefront list order')) { + this.notifications.warning(this.intl.t('common.unauthorized-access')); + return this.hostRouter.transitionTo('console.storefront'); + } + } + model(params) { return this.store.query('order', { ...params, storefront: this.storefront.getActiveStore('public_id') }); } diff --git a/addon/routes/orders/index/edit.js b/addon/routes/orders/index/edit.js index e5ce7aa..0f97835 100644 --- a/addon/routes/orders/index/edit.js +++ b/addon/routes/orders/index/edit.js @@ -1,3 +1,16 @@ import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; -export default class OrdersIndexEditRoute extends Route {} +export default class OrdersIndexEditRoute extends Route { + @service intl; + @service abilities; + @service hostRouter; + @service notifications; + + beforeModel() { + if (this.abilities.cannot('storefront update order')) { + this.notifications.warning(this.intl.t('common.unauthorized-access')); + return this.hostRouter.transitionTo('console.storefront'); + } + } +} diff --git a/addon/routes/orders/index/new.js b/addon/routes/orders/index/new.js index 697b8be..82780cc 100644 --- a/addon/routes/orders/index/new.js +++ b/addon/routes/orders/index/new.js @@ -1,3 +1,16 @@ import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; -export default class OrdersIndexNewRoute extends Route {} +export default class OrdersIndexNewRoute extends Route { + @service intl; + @service abilities; + @service hostRouter; + @service notifications; + + beforeModel() { + if (this.abilities.cannot('storefront create order')) { + this.notifications.warning(this.intl.t('common.unauthorized-access')); + return this.hostRouter.transitionTo('console.storefront'); + } + } +} diff --git a/addon/routes/orders/index/view.js b/addon/routes/orders/index/view.js index 66e7482..a8fc2a0 100644 --- a/addon/routes/orders/index/view.js +++ b/addon/routes/orders/index/view.js @@ -7,11 +7,21 @@ export default class OrdersIndexViewRoute extends Route { @service notifications; @service store; @service socket; + @service intl; + @service abilities; + @service hostRouter; @action error(error) { this.notifications.serverError(error); } + beforeModel() { + if (this.abilities.cannot('storefront view order')) { + this.notifications.warning(this.intl.t('common.unauthorized-access')); + return this.hostRouter.transitionTo('console.storefront'); + } + } + model({ public_id }) { const order = this.store.queryRecord('order', { public_id, diff --git a/addon/routes/products/index.js b/addon/routes/products/index.js index 99754f1..77352c3 100644 --- a/addon/routes/products/index.js +++ b/addon/routes/products/index.js @@ -6,6 +6,10 @@ import isNestedRouteTransition from '@fleetbase/ember-core/utils/is-nested-route export default class ProductsIndexRoute extends Route { @service store; @service currentUser; + @service intl; + @service abilities; + @service hostRouter; + @service notifications; @action willTransition(transition) { this.controller.category = null; @@ -16,6 +20,13 @@ export default class ProductsIndexRoute extends Route { } } + beforeModel() { + if (this.abilities.cannot('storefront list product')) { + this.notifications.warning(this.intl.t('common.unauthorized-access')); + return this.hostRouter.transitionTo('console.storefront'); + } + } + model(params = {}) { return this.store.query('category', { for: 'storefront_product', diff --git a/addon/routes/products/index/category.js b/addon/routes/products/index/category.js index 2c89bf1..34cf0aa 100644 --- a/addon/routes/products/index/category.js +++ b/addon/routes/products/index/category.js @@ -7,6 +7,10 @@ export default class ProductsIndexCategoryRoute extends Route { @service store; @service currentUser; @service loader; + @service intl; + @service abilities; + @service hostRouter; + @service notifications; @tracked categorySlug; queryParams = { @@ -26,10 +30,17 @@ export default class ProductsIndexCategoryRoute extends Route { } } - loading(transition) { + @action loading(transition) { this.loader.showOnInitialTransition(transition, 'section.next-view-section', { loadingMessage: 'Loading products...' }); } + beforeModel() { + if (this.abilities.cannot('storefront list product')) { + this.notifications.warning(this.intl.t('common.unauthorized-access')); + return this.hostRouter.transitionTo('console.storefront'); + } + } + model({ slug, ...params }) { this.categorySlug = slug; diff --git a/addon/routes/products/index/category/edit.js b/addon/routes/products/index/category/edit.js index 13c868d..c058d6d 100644 --- a/addon/routes/products/index/category/edit.js +++ b/addon/routes/products/index/category/edit.js @@ -4,8 +4,19 @@ import { filterHasManyForNewRecords } from '../../../../serializers/product'; export default class ProductsIndexCategoryEditRoute extends Route { @service store; + @service intl; + @service abilities; + @service hostRouter; + @service notifications; templateName = 'products.index.category.new'; + beforeModel() { + if (this.abilities.cannot('storefront update product')) { + this.notifications.warning(this.intl.t('common.unauthorized-access')); + return this.hostRouter.transitionTo('console.storefront'); + } + } + model({ public_id }) { return this.store.queryRecord('product', { public_id, diff --git a/addon/routes/products/index/category/new.js b/addon/routes/products/index/category/new.js index b0c6810..5b2498d 100644 --- a/addon/routes/products/index/category/new.js +++ b/addon/routes/products/index/category/new.js @@ -1,9 +1,23 @@ import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; +import { action } from '@ember/object'; export default class ProductsIndexCategoryNewRoute extends Route { - didTransition() { + @service intl; + @service abilities; + @service hostRouter; + @service notifications; + + @action didTransition() { if (this.controller) { this.controller.reset(); } } + + beforeModel() { + if (this.abilities.cannot('storefront create product')) { + this.notifications.warning(this.intl.t('common.unauthorized-access')); + return this.hostRouter.transitionTo('console.storefront'); + } + } } diff --git a/addon/routes/products/index/index.js b/addon/routes/products/index/index.js index e48661c..fe42d24 100644 --- a/addon/routes/products/index/index.js +++ b/addon/routes/products/index/index.js @@ -4,6 +4,10 @@ import { inject as service } from '@ember/service'; export default class ProductsIndexIndexRoute extends Route { @service store; @service currentUser; + @service intl; + @service abilities; + @service hostRouter; + @service notifications; queryParams = { page: { refreshModel: true }, @@ -16,6 +20,13 @@ export default class ProductsIndexIndexRoute extends Route { updated_at: { refreshModel: true }, }; + beforeModel() { + if (this.abilities.cannot('storefront list product')) { + this.notifications.warning(this.intl.t('common.unauthorized-access')); + return this.hostRouter.transitionTo('console.storefront'); + } + } + model(params) { return this.store.query('product', { store_uuid: this.currentUser.getOption('activeStorefront'), ...params }); } diff --git a/addon/routes/settings/index.js b/addon/routes/settings/index.js index 5575fae..f64ea26 100644 --- a/addon/routes/settings/index.js +++ b/addon/routes/settings/index.js @@ -5,6 +5,17 @@ export default class SettingsIndexRoute extends Route { @service store; @service currentUser; @service storefront; + @service intl; + @service abilities; + @service hostRouter; + @service notifications; + + beforeModel() { + if (this.abilities.cannot('storefront view settings')) { + this.notifications.warning(this.intl.t('common.unauthorized-access')); + return this.hostRouter.transitionTo('console'); + } + } model() { return this.store.peekRecord('store', this.currentUser.getOption('activeStorefront')); diff --git a/addon/services/storefront.js b/addon/services/storefront.js index cce8df1..687bf73 100644 --- a/addon/services/storefront.js +++ b/addon/services/storefront.js @@ -7,47 +7,14 @@ import { get } from '@ember/object'; * Service to manage storefront operations. */ export default class StorefrontService extends Service.extend(Evented) { - /** - * Ember service for data store. - * @type {Service} - */ @service store; - - /** - * Ember service for intl. - * @type {Service} - */ @service intl; - - /** - * Ember service for fetch operations. - * @type {Service} - */ @service fetch; - - /** - * Ember service for notifications. - * @type {Service} - */ @service notifications; - - /** - * Ember service for current user information. - * @type {Service} - */ @service currentUser; - - /** - * Ember service for managing modals. - * @type {Service} - */ @service modalsManager; - - /** - * Ember service for router operations. - * @type {Service} - */ @service hostRouter; + @service abilities; /** * Gets the active store. @@ -137,18 +104,17 @@ export default class StorefrontService extends Service.extend(Evented) { backdropClose: false, order, store, - confirm: (modal) => { + confirm: async (modal) => { modal.startLoading(); - return this.fetch - .post('orders/accept', { order: order.id }, { namespace: 'storefront/int/v1' }) - .then(() => { - this.trigger('order.accepted', order); - modal.stopLoading(); - }) - .catch((error) => { - this.notifications.serverError(error); - }); + try { + await this.fetch.post('orders/accept', { order: order.id }, { namespace: 'storefront/int/v1' }); + this.trigger('order.accepted', order); + modal.done(); + } catch (error) { + modal.stopLoading(); + this.notifications.serverError(error); + } }, }); } @@ -201,6 +167,7 @@ export default class StorefrontService extends Service.extend(Evented) { title: this.intl.t('storefront.service.storefront.create-first-storefront'), acceptButtonIcon: 'check', acceptButtonIconPrefix: 'fas', + acceptButtonDisabled: this.abilities.cannot('storefront create store'), closeButton: false, backdropClose: false, keyboard: true, @@ -209,20 +176,18 @@ export default class StorefrontService extends Service.extend(Evented) { declineButtonIcon: 'times', declineButtonIconPrefix: 'fas', store, - confirm: (modal, done) => { + confirm: async (modal) => { modal.startLoading(); - store - .save() - .then((store) => { - this.notifications.success(this.intl.t('storefront.service.storefront.storefront-has-been-create-success')); - this.setActiveStorefront(store); - return done(); - }) - .catch((error) => { - modal.stopLoading(); - this.notifications.serverError(error); - }); + try { + await store.save(); + this.notifications.success(this.intl.t('storefront.service.storefront.storefront-has-been-create-success')); + this.setActiveStorefront(store); + modal.done(); + } catch (error) { + modal.stopLoading(); + this.notifications.serverError(error); + } }, decline: () => { this.hostRouter.transitionTo('console'); @@ -250,26 +215,22 @@ export default class StorefrontService extends Service.extend(Evented) { declineButtonIcon: 'times', declineButtonIconPrefix: 'fas', store, - confirm: (modal, done) => { + confirm: async (modal, done) => { modal.startLoading(); - store - .save() - .then((store) => { - this.notifications.success(this.intl.t('storefront.service.storefront.storefront-create-success')); - // this.currentUser.setOption('activeStorefront', store.id); - this.setActiveStorefront(store); - - if (typeof options?.onSuccess === 'function') { - options.onSuccess(store); - } - - return done(); - }) - .catch((error) => { - modal.stopLoading(); - this.notifications.serverError(error); - }); + try { + const store = await store.save(); + this.notifications.success(this.intl.t('storefront.service.storefront.storefront-create-success')); + // this.currentUser.setOption('activeStorefront', store.id); + this.setActiveStorefront(store); + if (typeof options?.onSuccess === 'function') { + options.onSuccess(store); + } + modal.done(); + } catch (error) { + modal.stopLoading(); + this.notifications.serverError(error); + } }, ...options, }); diff --git a/addon/templates/application.hbs b/addon/templates/application.hbs index 303e672..efb1c8f 100644 --- a/addon/templates/application.hbs +++ b/addon/templates/application.hbs @@ -2,11 +2,37 @@ {{t "storefront.sidebar.dashboard"}} - {{t "storefront.sidebar.products"}} - {{t "storefront.sidebar.customers"}} - {{t "storefront.sidebar.orders"}} - {{t "storefront.sidebar.networks"}} - {{t "storefront.sidebar.settings"}} + {{t "storefront.sidebar.products"}} + {{t + "storefront.sidebar.customers" + }} + {{t "storefront.sidebar.orders"}} + {{t "storefront.sidebar.networks"}} + {{t "storefront.sidebar.settings"}} {{t "storefront.sidebar.launch-app"}} diff --git a/addon/templates/customers/index.hbs b/addon/templates/customers/index.hbs index fb1dcb2..8f5a07c 100644 --- a/addon/templates/customers/index.hbs +++ b/addon/templates/customers/index.hbs @@ -8,7 +8,7 @@ @buttonWrapperClass="mr-2" /> -