Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/hotwax/preorder into preorder/
Browse files Browse the repository at this point in the history
  • Loading branch information
amansinghbais committed Oct 12, 2023
2 parents ddb5e14 + cc61673 commit 26385e5
Show file tree
Hide file tree
Showing 28 changed files with 6,778 additions and 3,448 deletions.
9,397 changes: 6,158 additions & 3,239 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "preorder-app",
"version": "1.11.0",
"version": "1.16.0",
"private": true,
"description": "HotWax Commece Pre-order App",
"scripts": {
Expand All @@ -16,13 +16,15 @@
"@capacitor/android": "^2.5.0",
"@capacitor/core": "^2.4.7",
"@capacitor/ios": "^2.5.0",
"@casl/ability": "^6.0.0",
"@hotwax/app-version-info": "^1.0.0",
"@hotwax/apps-theme": "^1.2.3",
"@hotwax/dxp-components": "^1.3.4",
"@hotwax/oms-api": "^1.6.0",
"@hotwax/dxp-components": "1.7.5",
"@hotwax/oms-api": "^1.10.0",
"@ionic/core": "6.7.5",
"@ionic/vue": "6.7.5",
"@ionic/vue-router": "6.7.5",
"boon-js": "^2.0.3",
"core-js": "^3.6.5",
"luxon": "^3.2.0",
"mitt": "^2.1.0",
Expand Down
3 changes: 2 additions & 1 deletion src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ export default defineComponent({
}
},
async unauthorised() {
this.store.dispatch("user/logout");
// Mark the user as unauthorised, this will help in not making the logout api call in actions
this.store.dispatch("user/logout", { isUserUnauthorised: true });
const redirectUrl = window.location.origin + '/login';
window.location.href = `${process.env.VUE_APP_LOGIN_URL}?redirectUrl=${redirectUrl}`;
}
Expand Down
4 changes: 3 additions & 1 deletion src/adapter/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { api, client, initialise, resetConfig, updateInstanceUrl, updateToken } from '@hotwax/oms-api'
import { api, client, getConfig, initialise, logout, resetConfig, updateInstanceUrl, updateToken } from '@hotwax/oms-api'

export {
api,
client,
getConfig,
initialise,
logout,
resetConfig,
updateInstanceUrl,
updateToken
Expand Down
3 changes: 3 additions & 0 deletions src/authorization/Actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
"APP_INV_CNFG_UPDT": "APP_INV_CNFG_UPDT",
}
8 changes: 8 additions & 0 deletions src/authorization/Rules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default {
"APP_ORDERS_VIEW": "",
"APP_PRODUCTS_VIEW": "",
"APP_CATALOG_VIEW": "",
"APP_PRDT_DTLS_VIEW": "",
"APP_CTLG_PRDT_DTLS_VIEW": "",
"APP_INV_CNFG_UPDT": "COMMON_ADMIN"
} as any
124 changes: 124 additions & 0 deletions src/authorization/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { AbilityBuilder, PureAbility } from '@casl/ability';
import { getEvaluator, parse } from 'boon-js';
import { Tokens } from 'boon-js/lib/types'

// TODO Improve this
// We will move this code to an external plugin and use below Actions and Rules accordlingly
let Actions = {} as any;
let Rules = {} as any;

// We are using CASL library to define permissions.
// Instead of using Action-Subject based authorisation we are going with Claim based Authorization.
// We would be defining the permissions for each action and case, map with server permissiosn based upon certain rules.
// https://casl.js.org/v5/en/cookbook/claim-authorization
// Following the comment of Sergii Stotskyi, author of CASL
// https://github.com/stalniy/casl/issues/525
// We are defining a PureAbility and creating an instance with AbilityBuilder.
type ClaimBasedAbility = PureAbility<string>;
const { build } = new AbilityBuilder<ClaimBasedAbility>(PureAbility);
const ability = build();

/**
* The method returns list of permissions required for the rules. We are having set of rules,
* through which app permissions are defined based upon the server permissions.
* When getting server permissions, as all the permissions are not be required.
* Specific permissions used defining the rules are extracted and sent to server.
* @returns permissions
*/
const getServerPermissionsFromRules = () => {
// Iterate for each rule
const permissions = Object.keys(Rules).reduce((permissions: any, rule: any) => {
const permissionRule = Rules[rule];
// some rules may be empty, no permission is required from server
if (permissionRule) {
// Each rule may have multiple permissions along with operators
// Boon js parse rules into tokens, each token may be operator or server permission
// permissionId will have token name as identifier.
const permissionTokens = parse(permissionRule);
permissions = permissionTokens.reduce((permissions: any, permissionToken: any) => {
// Token object with name as identifier has permissionId
if (Tokens.IDENTIFIER === permissionToken.name) {
permissions.push(permissionToken.value);
}
return permissions;
}, permissions)
}
return permissions;
}, [])
return permissions;
}

/**
* The method is used to prepare app permissions from the server permissions.
* Rules could be defined such that each app permission could be defined based upon certain one or more server permissions.
* @param serverPermissions
* @returns appPermissions
*/
const prepareAppPermissions = (serverPermissions: any) => {
const serverPermissionsInput = serverPermissions.reduce((serverPermissionsInput: any, permission: any) => {
serverPermissionsInput[permission] = true;
return serverPermissionsInput;
}, {})
// Boonjs evaluator needs server permissions as object with permissionId and boolean value
// Each rule is passed to evaluator along with the server permissions
// if the server permissions and rule matches, app permission is added to list
const permissions = Object.keys(Rules).reduce((permissions: any, rule: any) => {
const permissionRule = Rules[rule];
// If for any app permission, we have empty rule we user is assigned the permission
// If rule is not defined, the app permisions is still evaluated or provided to all the users.
if (!permissionRule || (permissionRule && getEvaluator(permissionRule)(serverPermissionsInput))) {
permissions.push(rule);
}
return permissions;
}, [])
const { can, rules } = new AbilityBuilder<ClaimBasedAbility>(PureAbility);
permissions.map((permission: any) => {
can(permission);
})
return rules;
}

/**
*
* Sets the current app permissions. This should be used after perparing the app permissions from the server permissions
* @param permissions
* @returns
*/
const setPermissions = (permissions: any) => {
// If the user has passed undefined or null, it should not break the code
if (!permissions) permissions = [];
ability.update(permissions)
return true;
};

/**
* Resets the permissions list. Used for cases like logout
*/
const resetPermissions = () => setPermissions([]);

/**
*
* @param permission
* @returns
*/
const hasPermission = (permission: string) => ability.can(permission);

export { Actions, getServerPermissionsFromRules, hasPermission, prepareAppPermissions, resetPermissions, setPermissions };

// TODO Move this code to an external plugin, to be used across the apps
export default {
install(app: any, options: any) {

// Rules and Actions could be app and OMS package specific
Rules = options.rules;
Actions = options.actions;

// TODO Check why global properties is not working and apply across.
app.config.globalProperties.$permission = this;
},
getServerPermissionsFromRules,
hasPermission,
prepareAppPermissions,
resetPermissions,
setPermissions
}
25 changes: 20 additions & 5 deletions src/components/Menu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

<ion-content>
<ion-list id="preorder-list">
<ion-menu-toggle auto-hide="false" v-for="(p, i) in appPages" :key="i">
<ion-menu-toggle auto-hide="false" v-for="(p, i) in getValidMenuItems(appPages)" :key="i">
<ion-item
button
router-direction="root"
Expand Down Expand Up @@ -44,6 +44,7 @@ import { mapGetters } from "vuex";
import { albums ,shirt, pricetags, settings } from "ionicons/icons";
import { useStore } from "@/store";
import { useRouter } from "vue-router";
import { hasPermission } from "@/authorization";
export default defineComponent({
name: "Menu",
Expand All @@ -67,26 +68,40 @@ export default defineComponent({
setup() {
const store = useStore();
const router = useRouter();
const getValidMenuItems = (appPages: any) => {
return appPages.filter((appPage: any) => (!appPage.meta || !appPage.meta.permissionId) || hasPermission(appPage.meta.permissionId));
}
const appPages = [
{
title: "Orders",
url: "/orders",
iosIcon: pricetags,
mdIcon: pricetags,
meta: {
permissionId: "APP_ORDERS_VIEW"
}
},
{
title: "Products",
url: "/products",
childRoutes: ["/product-details/"],
iosIcon: shirt,
mdIcon: shirt,
meta: {
permissionId: "APP_PRODUCTS_VIEW"
}
},
{
title: "Catalog",
url: "/catalog",
childRoutes: ["/catalog-product-details/"],
iosIcon: albums,
mdIcon: albums,
meta: {
permissionId: "APP_CATALOG_VIEW"
}
},
{
title: "Settings",
Expand All @@ -98,17 +113,17 @@ export default defineComponent({
const selectedIndex = computed(() => {
const path = router.currentRoute.value.path
return appPages.findIndex((screen) => screen.url === path || screen.childRoutes?.includes(path) || screen.childRoutes?.some((route)=> path.includes(route)))
return getValidMenuItems(appPages).findIndex((screen: any) => screen.url === path || screen.childRoutes?.includes(path) || screen.childRoutes?.some((route: any)=> path.includes(route)))
})
return {
selectedIndex,
appPages,
albums,
shirt,
getValidMenuItems,
pricetags,
settings,
selectedIndex,
shirt,
store
};
}
Expand Down
21 changes: 15 additions & 6 deletions src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
"Cancel orders": "Cancel orders",
"Catalog": "Catalog",
"Category": "Category",
"Category and brokering jobs": "Category and brokering jobs",
"Change": "Change",
"Change time zone": "Change time zone",
"Click the backdrop to dismiss.": "Click the backdrop to dismiss.",
Expand All @@ -46,18 +45,27 @@
"Deselect": "Deselect",
"Deselect items": "Deselect items",
"Deselect the selected order items": "Deselect the {count} selected order items",
"disable": "disable",
"disabled": "disabled",
"Dismiss": "Dismiss",
"Disable Reserve Inventory?": "Disable Reserve Inventory?",
"Disable Hold Pre-order Physical Inventory?": "Disable Hold Pre-order Physical Inventory?",
"Disabling inventory reservations prevents committed inventory from being reduced until it has been shipped. Orders that are pending allocation or haven’t been shipped will not be reduced from sellable inventory.": "Disabling inventory reservations prevents committed inventory from being reduced until it has been shipped. Orders that are pending allocation or haven’t been shipped will not be reduced from sellable inventory.",
"Disabling this setting will push excess physical inventory for pre-sell products online and start selling them as in-stock items.": "Disabling this setting will push excess physical inventory for pre-sell products online and start selling them as in-stock items.",
"Don't cancel": "Don't cancel",
"Edit promise date": "Edit promise date",
"Edit shipping date": "Edit shipping date",
"Eligible": "Eligible",
"enable": "enable",
"Enable Reserve Inventory?": "Enable Reserve Inventory?",
"Enable Hold Pre-order Physical Inventory?": "Enable Hold Pre-order Physical Inventory?",
"Enabling inventory reservations reduces inventory counts for committed inventory before it has been shipped. Committed inventory includes orders waiting to be brokered or waiting to be shipped.": "Enabling inventory reservations reduces inventory counts for committed inventory before it has been shipped. Committed inventory includes orders waiting to be brokered or waiting to be shipped.",
"Enabling this setting will prevent pre-selling products from publishing physical inventory online until their pre-selling queue is cleared.": "Enabling this setting will prevent pre-selling products from publishing physical inventory online until their pre-selling queue is cleared.",
"Enter a product name, style name, SKU, UPCA or external ID.": "Enter a product name, style name, SKU, UPCA or external ID.",
"Enter an order ID, product name, style name, SKU, customer name, UPCA or external ID": "Enter an order ID, product name, style name, SKU, customer name, UPCA or external ID",
"Excluded ATP": "Excluded ATP",
"from date": "from date",
"Failed to update configuration": "Failed to update configuration",
"Failed to get pre-order/backorder categories": "Failed to get pre-order/backorder categories",
"Go to Launchpad": "Go to Launchpad",
"Go to OMS": "Go to OMS",
"History": "History",
Expand Down Expand Up @@ -91,13 +99,11 @@
"Loyalty status": "Loyalty status",
"Never in any category": "Never in any category",
"No": "No",
"No backorder category found": "No backorder category found",
"No job found": "No job found",
"No jobs found": "No jobs found",
"No jobs have run yet": "No jobs have run yet",
"No listing data": "No listing data",
"No products found": "No products found",
"No pre-order category found": "No pre-order category found",
"No results found": "No results found",
"No selected store found.": "No selected store found.",
"No time zone found": "No time zone found",
Expand Down Expand Up @@ -145,7 +151,7 @@
"preorders will be cancelled. This action cannot be undone.": "{count} preorders will be cancelled. This action cannot be undone.",
"Products": "Products",
"Product details": "Product details",
"Product summary": "Product summary",
"Product audit": "Product audit",
"Product cannot be pre-sold because it does not have active purchase orders": "Product cannot be pre-sold because it does not have active purchase orders",
"Product has been accepting from against PO #": "Product has been accepting {category}s from {fromDate} against PO #${POID}",
"Product is eligible for but not added to the category": "Product is eligible for {category}s but not added to the {category} category",
Expand All @@ -159,6 +165,7 @@
"Product not found": "Product not found",
"Purchase orders": "Purchase orders",
"Quantity on hand": "Quantity on hand",
"Related jobs": "Related jobs",
"Release": "Release",
"Release item": "Release item",
"Release orders": "Release orders",
Expand Down Expand Up @@ -196,6 +203,7 @@
"sku selected": "sku selected",
"Some listing data not available": "Some listing data not available",
"Something went wrong": "Something went wrong",
"Something went wrong while login. Please contact administrator.": "Something went wrong while login. Please contact administrator.",
"Something went wrong, could not fetch": "Something went wrong, could not fetch {data}",
"Sorry, your username or password is incorrect. Please try again.": "Sorry, your username or password is incorrect. Please try again.",
"Sort by": "Sort by",
Expand Down Expand Up @@ -231,5 +239,6 @@
"When this product entered there was no sellable inventory and was available in": "When this product entered {categoryName} there was no sellable inventory and {poItemATP} was available in {poId}",
"With Hold Pre-order Queue Physical Inventory disabled, the excess inventory is now available for sale online after deducting the queues": "With Hold Pre-order Queue Physical Inventory disabled, the excess inventory is now available for sale online after deducting the queues",
"Would you like to update your time zone to . Your profile is currently set to . This setting can always be changed from the settings menu.": "Would you like to update your time zone to {localTimeZone}. Your profile is currently set to {profileTimeZone}. This setting can always be changed from the settings menu.",
"Yes": "Yes"
"Yes": "Yes",
"You do not have permission to access this page": "You do not have permission to access this page"
}
13 changes: 12 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ import '@ionic/vue/css/display.css';
import './theme/variables.css';
import { dxpComponents } from '@hotwax/dxp-components'
import { login, logout, loader } from './user-utils';
import permissionPlugin from '@/authorization';
import permissionRules from '@/authorization/Rules';
import permissionActions from '@/authorization/Actions';
import { getConfig, initialise } from '@/adapter'

const app = createApp(App)
.use(IonicVue, {
Expand All @@ -38,11 +42,18 @@ const app = createApp(App)
.use(router)
.use(i18n)
.use(store)
.use(permissionPlugin, {
rules: permissionRules,
actions: permissionActions
})
.use(dxpComponents, {
defaultImgUrl: require("@/assets/images/defaultImage.png"),
login,
logout,
loader,
appLoginUrl: process.env.VUE_APP_LOGIN_URL as string
appLoginUrl: process.env.VUE_APP_LOGIN_URL as string,
getConfig: getConfig,
initialise: initialise
});

// Filters are removed in Vue 3 and global filter introduced https://v3.vuejs.org/guide/migration/filters.html#global-filters
Expand Down
Loading

0 comments on commit 26385e5

Please sign in to comment.