From 4ed9ecb9d51d87e9ed17b575685e49fb76c93ac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Erik=20St=C3=B8wer?= Date: Tue, 17 Dec 2024 15:27:59 +0100 Subject: [PATCH] Refactor to remove reducer layer for roles --- src/actions/RolesActions.js | 18 -- src/actions/TiamatActions.js | 80 +++--- src/actions/Types.js | 6 +- src/actions/UserActions.js | 14 + .../EditParentStopPage/EditParentGeneral.js | 6 +- .../EditStopPage/EditStopGeneral.js | 87 +++---- .../EditStopPage/StopPlaceDetails.js | 26 +- .../EditGroupOfStopPlaces.js | 7 +- src/components/Header.js | 2 +- src/components/MainPage/SearchBox.js | 9 +- src/components/Map/MarkerList.js | 18 +- src/components/Map/NewStopMarker.js | 9 +- src/components/Map/StopPlacesMap.js | 29 +-- src/containers/App.js | 2 +- src/containers/StopPlace.tsx | 26 +- src/models/ParentStopPlace.js | 1 + src/reducers/index.ts | 2 - src/reducers/rolesReducer.js | 74 ------ src/reducers/rolesReducerUtils.js | 241 ------------------ src/reducers/snackbarReducer.js | 4 +- src/reducers/userReducer.d.ts | 1 + src/reducers/userReducer.js | 24 ++ src/reducers/zonesSlice.ts | 8 +- src/test/userAndRoles.spec.js | 115 --------- .../permissionUtils.test.js} | 48 ++-- src/utils/permissionsUtils.js | 149 +++++++++++ 26 files changed, 362 insertions(+), 644 deletions(-) delete mode 100644 src/actions/RolesActions.js delete mode 100644 src/reducers/rolesReducer.js delete mode 100644 src/reducers/rolesReducerUtils.js delete mode 100644 src/test/userAndRoles.spec.js rename src/{test/reducers/rolesReducerUtils.spec.js => utils/permissionUtils.test.js} (85%) create mode 100644 src/utils/permissionsUtils.js diff --git a/src/actions/RolesActions.js b/src/actions/RolesActions.js deleted file mode 100644 index ab4cab44f..000000000 --- a/src/actions/RolesActions.js +++ /dev/null @@ -1,18 +0,0 @@ -import { createThunk } from "."; -import { - getUserPermissions, - getLocationPermissionsForCoordinates, -} from "./TiamatActions"; -import * as types from "./Types"; - -export const updateAuth = (auth) => (dispatch) => { - dispatch(createThunk(types.UPDATED_AUTH, auth)); -}; - -export const fetchUserPermissions = () => (dispatch, getState) => { - dispatch(getUserPermissions()); -}; - -export const fetchLocationPermissions = (position) => (dispatch) => { - dispatch(getLocationPermissionsForCoordinates(position[1], position[0])); -}; diff --git a/src/actions/TiamatActions.js b/src/actions/TiamatActions.js index 20b695283..4bf33baf0 100644 --- a/src/actions/TiamatActions.js +++ b/src/actions/TiamatActions.js @@ -141,7 +141,7 @@ export const findTagByName = (name) => async (dispatch, getState) => variables: { name, }, - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); export const addTag = @@ -154,7 +154,7 @@ export const addTag = name, comment, }, - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); export const getStopPlaceById = (id) => async (dispatch, getState) => @@ -164,7 +164,7 @@ export const getStopPlaceById = (id) => async (dispatch, getState) => variables: { id, }, - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); export const getAddStopPlaceInfo = @@ -172,7 +172,7 @@ export const getAddStopPlaceInfo = handleQuery(getTiamatClient(), { query: getStopPlacesById(stopPlaceIds), fetchPolicy: "network-only", - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); export const saveStopPlaceBasedOnType = @@ -190,7 +190,7 @@ export const saveStopPlaceBasedOnType = mutation: mutateStopPlace, variables, fetchPolicy: "no-cache", - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch) .then((result) => { if (result.data.mutateStopPlace[0].id) { @@ -214,7 +214,7 @@ export const saveStopPlaceBasedOnType = mutation: updateChildOfParentStop, variables, fetchPolicy: "no-cache", - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch) .then((result) => { resolve(stopPlace.id); @@ -231,7 +231,7 @@ export const saveParentStopPlace = (variables) => async (dispatch, getState) => mutation: mutateParentStopPlace, variables, fetchPolicy: "no-cache", - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); export const removeStopPlaceFromMultiModalStop = @@ -243,7 +243,7 @@ export const removeStopPlaceFromMultiModalStop = parentSiteRef, }, fetchPolicy: "no-cache", - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); export const deleteQuay = (variables) => async (dispatch, getState) => { @@ -251,7 +251,7 @@ export const deleteQuay = (variables) => async (dispatch, getState) => { mutation: mutateDeleteQuay, variables, fetchPolicy: "no-cache", - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); }; @@ -262,7 +262,7 @@ export const deleteStopPlace = (stopPlaceId) => async (dispatch, getState) => stopPlaceId, }, fetchPolicy: "no-cache", - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); export const terminateStop = @@ -277,7 +277,7 @@ export const terminateStop = modificationEnumeration: shouldTerminatePermanently ? "delete" : null, }, fetchPolicy: "no-cache", - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); export const addToMultiModalStopPlace = @@ -289,7 +289,7 @@ export const addToMultiModalStopPlace = parentSiteRef, }, fetchPolicy: "no-cache", - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); export const createParentStopPlace = @@ -313,7 +313,7 @@ export const createParentStopPlace = stopPlaceIds, }, fetchPolicy: "no-cache", - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); export const mutateGroupOfStopPlace = @@ -323,7 +323,7 @@ export const mutateGroupOfStopPlace = mutation: mutateGroupOfStopPlaces, variables, fetchPolicy: "no-cache", - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch) .then(({ data }) => { const id = data["mutateGroupOfStopPlaces"] @@ -344,7 +344,7 @@ export const getStopPlaceVersions = id: stopPlaceId, }, fetchPolicy: "network-only", - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); export const mergeQuays = @@ -359,7 +359,7 @@ export const mergeQuays = versionComment, }, fetchPolicy: "no-cache", - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); export const getStopPlaceWithAll = (id) => async (dispatch, getState) => @@ -369,7 +369,7 @@ export const getStopPlaceWithAll = (id) => async (dispatch, getState) => id, }, fetchPolicy: "network-only", - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); export const mergeAllQuaysFromStop = @@ -384,7 +384,7 @@ export const mergeAllQuaysFromStop = toVersionComment, }, fetchPolicy: "no-cache", - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); export const moveQuaysToStop = @@ -399,7 +399,7 @@ export const moveQuaysToStop = toVersionComment, }, fetchPolicy: "no-cache", - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); export const moveQuaysToNewStop = @@ -413,7 +413,7 @@ export const moveQuaysToNewStop = toVersionComment, }, fetchPolicy: "no-cache", - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); export const getNeighbourStops = @@ -429,14 +429,14 @@ export const getNeighbourStops = lonMin: bounds.getSouthWest().lng, lonMax: bounds.getNorthEast().lng, }, - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); export const getTopographicPlaces = (ids) => async (dispatch, getState) => handleQuery(getTiamatClient(), { fetchPolicy: "network-only", query: getQueryTopographicPlaces(ids), - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); export const getMergeInfoForStops = @@ -447,7 +447,7 @@ export const getMergeInfoForStops = variables: { stopPlaceId, }, - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); export const findEntitiesWithFilters = @@ -475,7 +475,7 @@ export const findEntitiesWithFilters = pointInTime: showFutureAndExpired ? null : new Date().toISOString(), versionValidity: showFutureAndExpired ? "MAX_VERSION" : null, }, - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); }; @@ -486,7 +486,7 @@ export const findTopographicalPlace = (query) => async (dispatch, getState) => variables: { query, }, - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); export const getTags = (idReference) => async (dispatch, getState) => @@ -496,7 +496,7 @@ export const getTags = (idReference) => async (dispatch, getState) => variables: { idReference, }, - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); export const getTagsByName = (name) => async (dispatch, getState) => @@ -506,7 +506,7 @@ export const getTagsByName = (name) => async (dispatch, getState) => variables: { name, }, - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); export const removeTag = (name, idReference) => async (dispatch, getState) => @@ -517,7 +517,7 @@ export const removeTag = (name, idReference) => async (dispatch, getState) => idReference, }, fetchPolicy: "no-cache", - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); export const getGroupOfStopPlacesById = (id) => async (dispatch, getState) => @@ -527,7 +527,7 @@ export const getGroupOfStopPlacesById = (id) => async (dispatch, getState) => id, }, fetchPolicy: "network-only", - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); export const deleteGroupOfStopPlaces = (id) => async (dispatch, getState) => @@ -537,7 +537,7 @@ export const deleteGroupOfStopPlaces = (id) => async (dispatch, getState) => id, }, fetchPolicy: "no-cache", - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); export const deleteParking = (id) => async (dispatch, getState) => @@ -547,7 +547,7 @@ export const deleteParking = (id) => async (dispatch, getState) => id, }, fetchPolicy: "no-cache", - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); export const getStopPlaceAndPathLinkByVersion = @@ -559,7 +559,7 @@ export const getStopPlaceAndPathLinkByVersion = id, version: parseInt(version), }, - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); export const findStopForReport = @@ -568,7 +568,7 @@ export const findStopForReport = query: findStopForReportQuery, fetchPolicy: "network-only", variables: queryVariables, - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); export const getParkingForMultipleStopPlaces = @@ -576,7 +576,7 @@ export const getParkingForMultipleStopPlaces = handleQuery(getTiamatClient(), { query: getParkingForMultipleStopPlacesQuery(stopPlaceIds), fetchPolicy: "network-only", - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); export const topographicalPlaceSearch = @@ -587,7 +587,7 @@ export const topographicalPlaceSearch = variables: { query: searchText, }, - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); export const getNeighbourStopPlaceQuays = (id) => async (dispatch, getState) => @@ -597,7 +597,7 @@ export const getNeighbourStopPlaceQuays = (id) => async (dispatch, getState) => variables: { id: id, }, - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); export const savePathLink = (PathLink) => async (dispatch, getState) => @@ -605,7 +605,7 @@ export const savePathLink = (PathLink) => async (dispatch, getState) => fetchPolicy: "no-cache", mutation: mutatePathLink, variables: { PathLink }, - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); export const saveParking = (Parking) => async (dispatch, getState) => @@ -613,7 +613,7 @@ export const saveParking = (Parking) => async (dispatch, getState) => fetchPolicy: "no-cache", mutation: mutateParking, variables: { Parking }, - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); export const getLocationPermissionsForCoordinates = (longitude, latitude) => { @@ -622,7 +622,7 @@ export const getLocationPermissionsForCoordinates = (longitude, latitude) => { fetchPolicy: "no-cache", query: getLocationPermissions, variables: { longitude, latitude }, - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); }; }; @@ -632,7 +632,7 @@ export const getUserPermissions = () => { return handleQuery(getTiamatClient(), { fetchPolicy: "no-cache", query: getUserPermissionsQuery, - context: await getContext(getState().roles.auth), + context: await getContext(getState().user.auth), })(dispatch); }; }; diff --git a/src/actions/Types.js b/src/actions/Types.js index f868245fd..03e793baf 100644 --- a/src/actions/Types.js +++ b/src/actions/Types.js @@ -79,6 +79,7 @@ export const REMOVE_ADJACENT_SITE = "REMOVE_ADJACENT_SITE"; export const SET_STOP_PLACE_LOADING = "SET_STOP_PLACE_LOADING"; // UserActions +export const UPDATED_AUTH = "UPDATED_AUTH"; export const NAVIGATE_TO = "NAVIGATE_TO"; export const TOGGLED_IS_CREATING_NEW_STOP = "TOGGLED_IS_CREATING_NEW_STOP"; export const APPLIED_STOPTYPE_SEARCH_FILTER = "APPLIED_STOPTYPE_SEARCH_FILTER"; @@ -198,8 +199,3 @@ export const SETUP_NEW_GROUP = "SETUP_NEW_GROUP"; export const CREATED_NEW_GROUP_OF_STOP_PLACES = "CREATED_NEW_GROUP_OF_STOP_PLACES"; export const ERROR_NEW_GROUP = "ERROR_NEW_GROUP"; - -// RolesActions -export const UPDATED_AUTH = "UPDATED_AUTH"; -export const UPDATED_ALLOW_NEW_STOPS_EVERYWHERE = - "UPDATED_ALLOW_NEW_STOPS_EVERYWHERE"; diff --git a/src/actions/UserActions.js b/src/actions/UserActions.js index 89e9d3634..ec59c0e4e 100644 --- a/src/actions/UserActions.js +++ b/src/actions/UserActions.js @@ -19,6 +19,8 @@ import SettingsManager from "../singletons/SettingsManager"; import { getMergeInfoForStops, getAddStopPlaceInfo, + getLocationPermissionsForCoordinates, + getUserPermissions, } from "../actions/TiamatActions"; import { getIn } from "../utils/"; import ParentStopPlace from "../models/ParentStopPlace"; @@ -662,3 +664,15 @@ const getQuaysForMergeInfo = (stopPlace) => { }; export default UserActions; + +export const updateAuth = (auth) => (dispatch) => { + dispatch(createThunk(types.UPDATED_AUTH, auth)); +}; + +export const fetchUserPermissions = () => (dispatch, getState) => { + dispatch(getUserPermissions()); +}; + +export const fetchLocationPermissions = (position) => (dispatch) => { + dispatch(getLocationPermissionsForCoordinates(position[1], position[0])); +}; diff --git a/src/components/EditParentStopPage/EditParentGeneral.js b/src/components/EditParentStopPage/EditParentGeneral.js index 8bb332b9b..3135dde06 100644 --- a/src/components/EditParentStopPage/EditParentGeneral.js +++ b/src/components/EditParentStopPage/EditParentGeneral.js @@ -42,8 +42,8 @@ import { saveParentStopPlace, terminateStop, } from "../../actions/TiamatActions"; -import Routes from "../../routes"; import SettingsManager from "../../singletons/SettingsManager"; +import { getStopPermissions } from "../../utils/permissionsUtils"; class EditParentGeneral extends React.Component { constructor(props) { @@ -513,7 +513,7 @@ class EditParentGeneral extends React.Component { } } -const mapStateToProps = ({ stopPlace, mapUtils, roles, user }) => ({ +const mapStateToProps = ({ stopPlace, mapUtils, user }) => ({ stopPlace: stopPlace.current, versions: stopPlace.versions, activeMap: mapUtils.activeMap, @@ -521,7 +521,7 @@ const mapStateToProps = ({ stopPlace, mapUtils, roles, user }) => ({ removeStopPlaceFromParentOpen: mapUtils.removeStopPlaceFromParentOpen, removingStopPlaceFromParentId: mapUtils.removingStopPlaceFromParentId, adjacentStopDialogOpen: user.adjacentStopDialogOpen, - canDeleteStop: getIn(roles, ["allowanceInfo", "canDeleteStop"], false), + canDeleteStop: getStopPermissions(stopPlace).canDelete, deleteStopDialogOpen: mapUtils.deleteStopDialogOpen, originalStopPlace: stopPlace.originalCurrent, serverTimeDiff: user.serverTimeDiff, diff --git a/src/components/EditStopPage/EditStopGeneral.js b/src/components/EditStopPage/EditStopGeneral.js index 3ee2ae8f3..b32c5beac 100644 --- a/src/components/EditStopPage/EditStopGeneral.js +++ b/src/components/EditStopPage/EditStopGeneral.js @@ -21,12 +21,8 @@ import ConfirmDialog from "../Dialogs/ConfirmDialog"; import EditStopBoxTabs from "./EditStopBoxTabs"; import Tabs from "@mui/material/Tabs"; import Tab from "@mui/material/Tab"; -import TabContext from "@mui/lab/TabContext"; -import TabList from "@mui/lab/TabList"; -import TabPanel from "@mui/lab/TabPanel"; import StopPlaceDetails from "./StopPlaceDetails"; import mapToMutationVariables from "../../modelUtils/mapToQueryVariables"; -import { mutatePathLink, mutateParking } from "../../graphql/Tiamat/mutations"; import * as types from "../../actions/Types"; import EditStopAdditional from "./EditStopAdditional"; import MdUndo from "@mui/icons-material/Undo"; @@ -68,6 +64,7 @@ import { shouldMutatePathLinks, } from "../../modelUtils/shouldMutate"; import { replace } from "redux-first-history"; +import { getStopPermissions } from "../../utils/permissionsUtils"; class EditStopGeneral extends React.Component { constructor(props) { @@ -829,46 +826,46 @@ class EditStopGeneral extends React.Component { } } -const mapStateToProps = (state) => ({ - stopPlace: state.stopPlace.current, - mergeStopDialogOpen: state.stopPlace.mergeStopDialog - ? state.stopPlace.mergeStopDialog.isOpen - : false, - mergeSource: state.stopPlace.mergeStopDialog, - pathLink: state.stopPlace.pathLink, - stopHasBeenModified: state.stopPlace.stopHasBeenModified, - isMultiPolylinesEnabled: state.stopPlace.enablePolylines, - activeElementTab: state.user.activeElementTab, - showEditQuayAdditional: state.user.showEditQuayAdditional, - showEditStopAdditional: state.user.showEditStopAdditional, - mergingQuay: state.mapUtils.mergingQuay, - mergingQuayDialogOpen: state.mapUtils.mergingQuayDialogOpen, - deleteQuayDialogOpen: state.mapUtils.deleteQuayDialogOpen, - deleteQuayImportedId: state.mapUtils.deleteQuayImportedId, - deleteStopDialogOpen: state.mapUtils.deleteStopDialogOpen, - deletingQuay: state.mapUtils.deletingQuay, - versions: state.stopPlace.versions, - originalPathLink: state.stopPlace.originalPathLink, - moveQuayDialogOpen: state.mapUtils.moveQuayDialogOpen, - moveQuayToNewStopDialogOpen: state.mapUtils.moveQuayToNewStopDialogOpen, - movingQuay: state.mapUtils.movingQuay, - movingQuayToNewStop: state.mapUtils.movingQuayToNewStop, - activeMap: state.mapUtils.activeMap, - canDeleteStop: getIn(state.roles, ["allowanceInfo", "canDeleteStop"], false), - canEditParentStop: getIn( - state.roles, - ["allowanceInfo", "canEditParentStop"], - false, - ), - originalStopPlace: state.stopPlace.originalCurrent, - serverTimeDiff: state.user.serverTimeDiff, - isFetchingMergeInfo: state.stopPlace.isFetchingMergeInfo, - neighbourStopQuays: state.stopPlace.neighbourStopQuays, - deleteStopDialogWarning: state.user.deleteStopDialogWarning, - fetchOTPInfoMergeLoading: state.mapUtils.fetchOTPInfoMergeLoading, - mergeQuayWarning: state.mapUtils.mergeQuayWarning, - fetchOTPInfoDeleteLoading: state.mapUtils.fetchOTPInfoDeleteLoading, - deleteQuayWarning: state.mapUtils.deleteQuayWarning, -}); +const mapStateToProps = (state) => { + const stopPlace = state.stopPlace.current; + const permissions = getStopPermissions(stopPlace); + return { + canDeleteStop: permissions.canDelete, + stopPlace, + mergeStopDialogOpen: state.stopPlace.mergeStopDialog + ? state.stopPlace.mergeStopDialog.isOpen + : false, + mergeSource: state.stopPlace.mergeStopDialog, + pathLink: state.stopPlace.pathLink, + stopHasBeenModified: state.stopPlace.stopHasBeenModified, + isMultiPolylinesEnabled: state.stopPlace.enablePolylines, + activeElementTab: state.user.activeElementTab, + showEditQuayAdditional: state.user.showEditQuayAdditional, + showEditStopAdditional: state.user.showEditStopAdditional, + mergingQuay: state.mapUtils.mergingQuay, + mergingQuayDialogOpen: state.mapUtils.mergingQuayDialogOpen, + deleteQuayDialogOpen: state.mapUtils.deleteQuayDialogOpen, + deleteQuayImportedId: state.mapUtils.deleteQuayImportedId, + deleteStopDialogOpen: state.mapUtils.deleteStopDialogOpen, + deletingQuay: state.mapUtils.deletingQuay, + versions: state.stopPlace.versions, + originalPathLink: state.stopPlace.originalPathLink, + moveQuayDialogOpen: state.mapUtils.moveQuayDialogOpen, + moveQuayToNewStopDialogOpen: state.mapUtils.moveQuayToNewStopDialogOpen, + movingQuay: state.mapUtils.movingQuay, + movingQuayToNewStop: state.mapUtils.movingQuayToNewStop, + activeMap: state.mapUtils.activeMap, + canEditParentStop: permissions.canEditParentStop || false, + originalStopPlace: state.stopPlace.originalCurrent, + serverTimeDiff: state.user.serverTimeDiff, + isFetchingMergeInfo: state.stopPlace.isFetchingMergeInfo, + neighbourStopQuays: state.stopPlace.neighbourStopQuays, + deleteStopDialogWarning: state.user.deleteStopDialogWarning, + fetchOTPInfoMergeLoading: state.mapUtils.fetchOTPInfoMergeLoading, + mergeQuayWarning: state.mapUtils.mergeQuayWarning, + fetchOTPInfoDeleteLoading: state.mapUtils.fetchOTPInfoDeleteLoading, + deleteQuayWarning: state.mapUtils.deleteQuayWarning, + }; +}; export default injectIntl(connect(mapStateToProps)(EditStopGeneral)); diff --git a/src/components/EditStopPage/StopPlaceDetails.js b/src/components/EditStopPage/StopPlaceDetails.js index bfbc282e6..d537d1da8 100644 --- a/src/components/EditStopPage/StopPlaceDetails.js +++ b/src/components/EditStopPage/StopPlaceDetails.js @@ -57,12 +57,14 @@ import { getTags, removeTag, } from "../../actions/TiamatActions"; -import { Popover } from "@mui/material"; import { Link } from "react-router-dom"; import Routes from "../../routes"; -import { Signpost } from "@mui/icons-material"; import TransportSign from "../../static/icons/TransportSign"; import Menu from "@mui/material/Menu"; +import { + getStopPermissions, + getAllowanceInfoFromLocationPermissions, +} from "../../utils/permissionsUtils"; class StopPlaceDetails extends React.Component { constructor(props) { @@ -829,12 +831,18 @@ class StopPlaceDetails extends React.Component { } } -const mapStateToProps = (state) => ({ - stopPlace: state.stopPlace.current, - isPublicCodePrivateCodeEnabled: - state.stopPlace.enablePublicCodePrivateCodeOnStopPlaces, - keyValuesDialogOpen: state.user.keyValuesDialogOpen, - allowsInfo: state.roles.allowanceInfo, -}); +const mapStateToProps = (state) => { + const stopPlace = state.stopPlace.current; + const allowsInfo = stopPlace.permissions + ? getStopPermissions(state.stopPlace.current) + : getAllowanceInfoFromLocationPermissions(state.user.locationPermissions); + return { + stopPlace, + isPublicCodePrivateCodeEnabled: + state.stopPlace.enablePublicCodePrivateCodeOnStopPlaces, + keyValuesDialogOpen: state.user.keyValuesDialogOpen, + allowsInfo, + }; +}; export default connect(mapStateToProps)(StopPlaceDetails); diff --git a/src/components/GroupOfStopPlaces/EditGroupOfStopPlaces.js b/src/components/GroupOfStopPlaces/EditGroupOfStopPlaces.js index fcac703da..d8b3cc7c4 100644 --- a/src/components/GroupOfStopPlaces/EditGroupOfStopPlaces.js +++ b/src/components/GroupOfStopPlaces/EditGroupOfStopPlaces.js @@ -31,6 +31,7 @@ import Routes from "../../routes/"; import { UserActions, StopPlacesGroupActions } from "../../actions/"; import ConfirmDialog from "../Dialogs/ConfirmDialog"; import { getIn } from "../../utils/"; +import { getStopPermissions } from "../../utils/permissionsUtils"; class EditGroupOfStopPlaces extends Component { constructor(props) { @@ -284,12 +285,12 @@ class EditGroupOfStopPlaces extends Component { } } -const mapStateToProps = ({ stopPlacesGroup, roles }) => ({ +const mapStateToProps = ({ stopPlacesGroup }) => ({ isModified: stopPlacesGroup.isModified, groupOfStopPlaces: stopPlacesGroup.current, originalGOS: stopPlacesGroup.original, - canEdit: getIn(roles, ["allowanceInfo", "canEdit"], false), - canDelete: getIn(roles, ["allowanceInfo", "canDeleteStop"], false), + canEdit: getStopPermissions(stopPlacesGroup.current).canEdit, + canDelete: getStopPermissions(stopPlacesGroup.current).canDelete, }); export default connect(mapStateToProps)(injectIntl(EditGroupOfStopPlaces)); diff --git a/src/components/Header.js b/src/components/Header.js index 1d8c80c38..5f3517243 100644 --- a/src/components/Header.js +++ b/src/components/Header.js @@ -665,6 +665,7 @@ class Header extends React.Component { } const mapStateToProps = (state) => ({ + auth: state.user.auth, isCompassBearingEnabled: state.stopPlace.isCompassBearingEnabled, isDisplayingEditStopPlace: state.router.location.pathname.indexOf("/stop_place/") > -1, @@ -672,7 +673,6 @@ const mapStateToProps = (state) => ({ isPublicCodePrivateCodeOnStopPlacesEnabled: state.stopPlace.enablePublicCodePrivateCodeOnStopPlaces, isMultiPolylinesEnabled: state.stopPlace.enablePolylines, - auth: state.roles.auth, showExpiredStops: state.stopPlace.showExpiredStops, showMultimodalEdges: state.stopPlace.showMultimodalEdges, showPublicCode: state.user.showPublicCode, diff --git a/src/components/MainPage/SearchBox.js b/src/components/MainPage/SearchBox.js index 02f84a50f..96982b1e4 100644 --- a/src/components/MainPage/SearchBox.js +++ b/src/components/MainPage/SearchBox.js @@ -52,6 +52,7 @@ import { Popover, } from "@mui/material"; import TextField from "@mui/material/TextField"; +import { getStopPermissions } from "../../utils/permissionsUtils"; class SearchBox extends React.Component { constructor(props) { @@ -781,15 +782,11 @@ const mapStateToProps = (state) => { missingCoordinatesMap: state.user.missingCoordsMap, searchText: state.user.searchFilters.text, topographicalPlaces: state.stopPlace.topographicalPlaces || [], - canEdit: getIn( - state.roles, - ["allowanceInfoSearchResult", "canEdit"], - false, - ), + canEdit: getStopPermissions(state.stopPlace.current).canEdit, lookupCoordinatesOpen: state.user.lookupCoordinatesOpen, newStopIsMultiModal: state.user.newStopIsMultiModal, showFutureAndExpired: state.user.searchFilters.showFutureAndExpired, - isGuest: state.roles.isGuest, + isGuest: state.user.isGuest, }; }; diff --git a/src/components/Map/MarkerList.js b/src/components/Map/MarkerList.js index 078df6237..670b888bb 100644 --- a/src/components/Map/MarkerList.js +++ b/src/components/Map/MarkerList.js @@ -32,11 +32,9 @@ import CoordinateMarker from "./CoordinateMarker"; import Routes from "../../routes/"; import * as MarkerStrings from "./markerText"; import { Entities } from "../../models/Entities"; -import { - getNeighbourStopPlaceQuays, - getStopPlaceWithAll, -} from "../../actions/TiamatActions"; +import { getNeighbourStopPlaceQuays } from "../../actions/TiamatActions"; import BoardingPositionMarker from "./BoardingPositionMarker"; +import { getStopPermissions } from "../../utils/permissionsUtils"; class MarkerList extends React.Component { static propTypes = { @@ -628,15 +626,9 @@ const mapStateToProps = (state) => ({ activeMap: state.mapUtils.activeMap, pathLink: state.stopPlace.pathLink, showExpiredStops: state.stopPlace.showExpiredStops, - disabled: - (state.stopPlace.current && - state.stopPlace.current.permanentlyTerminated) || - !getIn(state.roles, ["allowanceInfo", "canEdit"], false), - disabledForSearch: !getIn( - state.roles, - ["allowanceInfoSearchResult", "canEdit"], - false, - ), + disabled: !getStopPermissions(state.stopPlace.current).canEdit, + stopPlace: state.stopPlace.current, + disabledForSearch: !getStopPermissions(state.stopPlace.current).canEdit, newStopIsMultiModal: state.user.newStopIsMultiModal, currentStopIsMultiModal: getIn( state.stopPlace, diff --git a/src/components/Map/NewStopMarker.js b/src/components/Map/NewStopMarker.js index 33274602d..0b9cef80d 100644 --- a/src/components/Map/NewStopMarker.js +++ b/src/components/Map/NewStopMarker.js @@ -17,10 +17,11 @@ import PropTypes from "prop-types"; import { Marker, Popup } from "react-leaflet"; import L from "leaflet"; import { connect } from "react-redux"; -import { fetchLocationPermissions } from "../../actions/RolesActions"; +import { fetchLocationPermissions } from "../../actions/UserActions"; import newStopIcon from "../../static/icons/new-stop-icon-2x.png"; import markerShadow from "../../static/icons/marker-shadow.png"; +import { getAllowanceInfoFromLocationPermissions } from "../../utils/permissionsUtils"; class NewStopMarker extends React.Component { static propTypes = { @@ -125,8 +126,10 @@ class NewStopMarker extends React.Component { } export default connect( - ({ roles }) => ({ - allowanceInfo: roles.allowanceInfo, + ({ user }) => ({ + allowanceInfo: getAllowanceInfoFromLocationPermissions( + user.locationPermissions, + ), }), { fetchLocationPermissions }, )(NewStopMarker); diff --git a/src/components/Map/StopPlacesMap.js b/src/components/Map/StopPlacesMap.js index 76ce52fbc..1e713fe41 100644 --- a/src/components/Map/StopPlacesMap.js +++ b/src/components/Map/StopPlacesMap.js @@ -103,23 +103,16 @@ class StopPlacesMap extends React.Component { } } -const mapStateToProps = (state) => { - return { - position: state.stopPlace.centerPosition, - neighbourMarkersCount: state.stopPlace.neighbourStops - ? state.stopPlace.neighbourStops.length - : 0, - markers: getMarkersForMap(state), - kc: state.roles.kc, - zoom: state.stopPlace.zoom, - isCreatingNewStop: state.user.isCreatingNewStop, - activeBaselayer: state.user.activeBaselayer, - ignoreStopId: getIn( - state.stopPlace, - ["activeSearchResult", "id"], - undefined, - ), - }; -}; +const mapStateToProps = (state) => ({ + position: state.stopPlace.centerPosition, + neighbourMarkersCount: state.stopPlace.neighbourStops + ? state.stopPlace.neighbourStops.length + : 0, + markers: getMarkersForMap(state), + zoom: state.stopPlace.zoom, + isCreatingNewStop: state.user.isCreatingNewStop, + activeBaselayer: state.user.activeBaselayer, + ignoreStopId: getIn(state.stopPlace, ["activeSearchResult", "id"], undefined), +}); export default injectIntl(connect(mapStateToProps)(StopPlacesMap)); diff --git a/src/containers/App.js b/src/containers/App.js index 0c33f75b7..5e08a5a58 100644 --- a/src/containers/App.js +++ b/src/containers/App.js @@ -24,7 +24,7 @@ import { useDispatch } from "react-redux"; import Header from "../components/Header"; import { getTheme, getV0Theme } from "../config/themeConfig"; import SnackbarWrapper from "../components/SnackbarWrapper"; -import { fetchUserPermissions, updateAuth } from "../actions/RolesActions"; +import { fetchUserPermissions, updateAuth } from "../actions/UserActions"; import { useAppSelector } from "../store/hooks"; import configureLocalization from "../localization/localization"; import { UserActions } from "../actions"; diff --git a/src/containers/StopPlace.tsx b/src/containers/StopPlace.tsx index 2b45f9b07..349ddee57 100644 --- a/src/containers/StopPlace.tsx +++ b/src/containers/StopPlace.tsx @@ -21,7 +21,6 @@ import { useIntl } from "react-intl"; import InformationManager from "../singletons/InformationManager"; import "../styles/main.css"; import { UserActions } from "../actions"; -import { getIn } from "../utils"; import NewElementsBox from "../components/EditStopPage/NewElementsBox"; import NewStopPlaceInfo from "../components/EditStopPage/NewStopPlaceInfo"; import LoadingPage from "./LoadingPage"; @@ -31,20 +30,23 @@ import { useAppDispatch, useAppSelector } from "../store/hooks"; import { createSelector } from "@reduxjs/toolkit"; import { RootState } from "../store/store"; import { Helmet } from "react-helmet"; +import { getStopPermissions } from "../utils/permissionsUtils"; const selectProps = createSelector( (state: RootState) => state, - (state) => ({ - isCreatingPolylines: state.stopPlace.isCreatingPolylines, - disabled: - (state.stopPlace.current && - state.stopPlace.current.permanentlyTerminated) || - !getIn(state.roles, ["allowanceInfo", "canEdit"], false), - stopPlace: state.stopPlace.current || state.stopPlace.newStop, - newStopCreated: state.user.newStopCreated, - originalStopPlace: state.stopPlace.originalCurrent, - stopPlaceLoading: state.stopPlace.loading, - }), + (state) => { + return { + isCreatingPolylines: state.stopPlace.isCreatingPolylines, + disabled: + (state.stopPlace.current && + state.stopPlace.current.permanentlyTerminated) || + !getStopPermissions(state.stopPlace.current).canEdit, + stopPlace: state.stopPlace.current || state.stopPlace.newStop, + newStopCreated: state.user.newStopCreated, + originalStopPlace: state.stopPlace.originalCurrent, + stopPlaceLoading: state.stopPlace.loading, + }; + }, ); export const StopPlace = () => { diff --git a/src/models/ParentStopPlace.js b/src/models/ParentStopPlace.js index 0ca7c232d..d026a8835 100644 --- a/src/models/ParentStopPlace.js +++ b/src/models/ParentStopPlace.js @@ -74,6 +74,7 @@ class ParentStopPlace { tags: stop.tags, version: stop.version, weighting: stop.weighting, + permissions: stop.permissions, }; if (stop.topographicPlace) { diff --git a/src/reducers/index.ts b/src/reducers/index.ts index 3ca51e018..e5d394707 100644 --- a/src/reducers/index.ts +++ b/src/reducers/index.ts @@ -18,7 +18,6 @@ import userReducer from "./userReducer"; import mapReducer from "./mapReducer"; import stopPlaceReducer from "./stopPlaceReducer"; import reportReducer from "./reportReducer"; -import rolesReducer from "./rolesReducer"; import snackbarReducer from "./snackbarReducer"; import groupOfStopPlaceReducer from "./groupOfStopPlacesReducer"; import zonesSlice from "./zonesSlice"; @@ -31,7 +30,6 @@ export const createRootReducer = (routerReducer: Reducer) => mapUtils: mapReducer, stopPlace: stopPlaceReducer, report: reportReducer, - roles: rolesReducer, snackbar: snackbarReducer, stopPlacesGroup: groupOfStopPlaceReducer, zones: zonesSlice, diff --git a/src/reducers/rolesReducer.js b/src/reducers/rolesReducer.js deleted file mode 100644 index d3028ddbb..000000000 --- a/src/reducers/rolesReducer.js +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by -the European Commission - subsequent versions of the EUPL (the "Licence"); -You may not use this work except in compliance with the Licence. -You may obtain a copy of the Licence at: - - https://joinup.ec.europa.eu/software/page/eupl - -Unless required by applicable law or agreed to in writing, software -distributed under the Licence is distributed on an "AS IS" basis, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the Licence for the specific language governing permissions and -limitations under the Licence. */ - -import * as types from "../actions/Types"; -import { - getAllowanceInfoForGroup, - getAllowanceSearchInfo, - getAllowanceInfoForStop, - getAllowanceInfoFromLocationPermissions, -} from "./rolesReducerUtils"; - -export const initialState = { - auth: {}, - isGuest: true, - allowNewStopEverywhere: false, -}; - -const rolesReducer = (state = initialState, action) => { - switch (action.type) { - case types.APOLLO_QUERY_RESULT: - if (action.operationName === "stopPlaceAndPathLink") { - return Object.assign({}, state, { - allowanceInfo: getAllowanceInfoForStop(action), - }); - } else if (action.operationName === "getGroupOfStopPlaces") { - return Object.assign({}, state, { - allowanceInfo: getAllowanceInfoForGroup(action), - }); - } else if (action.operationName === "getLocationPermissions") { - return Object.assign({}, state, { - allowanceInfo: getAllowanceInfoFromLocationPermissions( - action.result.data.locationPermissions, - ), - }); - } else if (action.operationName === "getUserPermissions") { - return Object.assign({}, state, { - isGuest: action.result.data.userPermissions.isGuest, - allowNewStopEverywhere: - action.result.data.userPermissions.allowNewStopEverywhere, - }); - } else { - return state; - } - - case types.SET_ACTIVE_MARKER: - return Object.assign({}, state, { - allowanceInfoSearchResult: getAllowanceSearchInfo( - action.payload, - state.auth.roleAssignments, - ), - }); - - case types.UPDATED_AUTH: - return Object.assign({}, state, { - auth: action.payload, - }); - - default: - return state; - } -}; - -export default rolesReducer; diff --git a/src/reducers/rolesReducerUtils.js b/src/reducers/rolesReducerUtils.js deleted file mode 100644 index b6a3d46dd..000000000 --- a/src/reducers/rolesReducerUtils.js +++ /dev/null @@ -1,241 +0,0 @@ -/* - * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by -the European Commission - subsequent versions of the EUPL (the "Licence"); -You may not use this work except in compliance with the Licence. -You may obtain a copy of the Licence at: - - https://joinup.ec.europa.eu/software/page/eupl - -Unless required by applicable law or agreed to in writing, software -distributed under the Licence is distributed on an "AS IS" basis, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the Licence for the specific language governing permissions and -limitations under the Licence. */ - -import stopTypes from "../models/stopTypes"; - -export const getAllowanceInfoForStop = ({ result, variables }) => { - const requestedStopPlaceId = variables.id; - const stopPlace = getStopPlace(result); - - if (!stopPlace) { - return { - legalStopPlaceTypes: [], - legalSubmodes: [], - canEdit: false, - }; - } - - let allowanceInfoForStopPlace; - - if (stopPlace.id !== requestedStopPlaceId) { - // Requested stop place ID seems to be a child - const childStopPlace = getStopPlace(result, requestedStopPlaceId); - allowanceInfoForStopPlace = buildAllowanceInfoForStopPlace(childStopPlace); - - // Check if the user is authorized to edit the parent stop place. - // It should be possible to edit a child of a multimodal stop place, of only authorized to edit that child. - // But it shall not be possible to set the termination date for the parent stop. - const allowanceInfoForParentStop = - buildAllowanceInfoForStopPlace(stopPlace); - allowanceInfoForStopPlace.canEditParentStop = - allowanceInfoForParentStop.canEdit; - } else { - allowanceInfoForStopPlace = buildAllowanceInfoForStopPlace(stopPlace); - } - return allowanceInfoForStopPlace; -}; - -const buildAllowanceInfoForStopPlace = (stopPlace) => { - const canEdit = stopPlace.permissions.canEdit; - const canDeleteStop = stopPlace.permissions.canDelete; - - let legalStopPlaceTypes = getLegalStopPlaceTypesForStopPlace(stopPlace); - let legalSubmodes = getLegalSubmodesForStopPlace(stopPlace); - - return { - legalStopPlaceTypes, - legalSubmodes, - blacklistedStopPlaceTypes: stopPlace.permissions.bannedStopPlaceTypes, - canEdit, - canDeleteStop, - }; -}; - -export const getAllowanceInfoForGroup = ({ result }) => { - const groupOfStopPlaces = getGroupOfStopPlaces(result); - - if (!groupOfStopPlaces) { - return { - legalStopPlaceTypes: [], - legalSubmodes: [], - canEdit: false, - canDeleteStop: false, - }; - } - - const canEdit = groupOfStopPlaces.permissions.canEdit; - const canDeleteStop = groupOfStopPlaces.permissions.canDelete; - - let legalStopPlaceTypes = - getLegalStopPlaceTypesForStopPlace(groupOfStopPlaces); - let legalSubmodes = getLegalSubmodesForStopPlace(groupOfStopPlaces); - - return { - legalStopPlaceTypes, - legalSubmodes, - blacklistedStopPlaceTypes: - groupOfStopPlaces.permissions.bannedStopPlaceTypes, - canEdit, - canDeleteStop, - }; -}; - -export const getAllowanceInfoFromLocationPermissions = ( - locationPermissions, -) => { - return { - canEdit: locationPermissions.canEdit, - canDelete: locationPermissions.canDelete, - legalStopPlaceTypes: getLegalStopPlacesTypes( - locationPermissions.allowedStopPlaceTypes, - locationPermissions.bannedStopPlaceTypes, - ), - legalSubmodes: getLegalSubmodes(locationPermissions), - }; -}; - -export const getLegalStopPlaceTypesForStopPlace = (stopPlace) => { - const { allowedStopPlaceTypes, bannedStopPlaceTypes } = stopPlace.permissions; - return getLegalStopPlacesTypes(allowedStopPlaceTypes, bannedStopPlaceTypes); -}; - -export const getLegalStopPlacesTypes = ( - allowedStopPlaceTypes, - bannedStopPlaceTypes, -) => { - const allStopTypes = Object.keys(stopTypes); - if (bannedStopPlaceTypes.includes("*")) { - return []; - } - - if (isWildcardOrEmpty(allowedStopPlaceTypes)) { - return allStopTypes.filter((type) => !bannedStopPlaceTypes.includes(type)); - } - - return allowedStopPlaceTypes.filter( - (type) => !bannedStopPlaceTypes.includes(type), - ); -}; - -const isWildcardOrEmpty = (list) => { - return !list || list.length === 0 || (list.length > 0 && list[0] === "*"); -}; - -const isStopTypeAllowed = (stopType, permissions) => { - const { allowedStopPlaceTypes } = permissions; - return ( - isWildcardOrEmpty(allowedStopPlaceTypes) || - allowedStopPlaceTypes.includes(stopType) - ); -}; - -const getSubmodesForStopType = (stopType, permissions) => { - const { submodes } = stopTypes[stopType]; - if (!submodes) return []; - - if (permissions.bannedStopPlaceTypes.includes(stopType)) return []; - if (!isStopTypeAllowed(stopType, permissions)) return []; - - return submodes; -}; - -const filterBySubmodePermissions = (submodes, permissions) => { - const { allowedSubmodes, bannedSubmodes } = permissions; - - if (allowedSubmodes.includes("*")) - return submodes.filter((type) => !bannedSubmodes.includes(type)); - if (bannedSubmodes.includes("*")) return []; - - if (allowedSubmodes.length > 0) { - return submodes.filter( - (type) => - allowedSubmodes.includes(type) && !bannedSubmodes.includes(type), - ); - } - - return submodes.filter((type) => !bannedSubmodes.includes(type)); -}; - -export const getLegalSubmodesForStopPlace = (stopPlace) => { - return getLegalSubmodes(stopPlace.permissions); -}; - -export const getLegalSubmodes = (permissions) => { - const applicableSubmodes = Object.keys(stopTypes).reduce( - (acc, stopType) => [ - ...acc, - ...getSubmodesForStopType(stopType, permissions), - ], - [], - ); - - return filterBySubmodePermissions(applicableSubmodes, permissions); -}; - -export const getAllowanceSearchInfo = (payload) => { - return { - canEdit: payload.permissions.canEdit, - }; -}; - -export const getStopPlace = (result, childId) => { - if ( - !result || - !result.data || - !result.data.stopPlace || - !result.data.stopPlace.length - ) { - return null; - } - - let stopPlace = result.data.stopPlace[0]; - - if ( - childId && - Array.isArray(stopPlace.children) && - stopPlace.children.length - ) { - let matchingChildStop = stopPlace.children.find( - (child) => child.id === childId, - ); - if (matchingChildStop) { - stopPlace = matchingChildStop; - } - } - - if (stopPlace) { - return JSON.parse(JSON.stringify(stopPlace)); - } - - return null; -}; - -export const getGroupOfStopPlaces = (result) => { - if ( - !result || - !result.data || - !result.data.groupOfStopPlaces || - !result.data.groupOfStopPlaces.length - ) { - return null; - } - - let groupOfStopPlaces = result.data.groupOfStopPlaces[0]; - - if (groupOfStopPlaces) { - return JSON.parse(JSON.stringify(groupOfStopPlaces)); - } - - return null; -}; diff --git a/src/reducers/snackbarReducer.js b/src/reducers/snackbarReducer.js index 27f699b4d..3f963d265 100644 --- a/src/reducers/snackbarReducer.js +++ b/src/reducers/snackbarReducer.js @@ -27,7 +27,7 @@ export const initialState = { }, }; -const rolesReducer = (state = initialState, action) => { +const snackbarReducer = (state = initialState, action) => { switch (action.type) { case APOLLO_MUTATION_ERROR: return Object.assign({}, state, { @@ -62,4 +62,4 @@ const getErrorMsg = (error) => { return null; }; -export default rolesReducer; +export default snackbarReducer; diff --git a/src/reducers/userReducer.d.ts b/src/reducers/userReducer.d.ts index 27f98fbf9..bb4de76ce 100644 --- a/src/reducers/userReducer.d.ts +++ b/src/reducers/userReducer.d.ts @@ -4,6 +4,7 @@ interface UserState { open: boolean; stopPlaceId: string; }; + auth: any; } declare const initialState: UserState; diff --git a/src/reducers/userReducer.js b/src/reducers/userReducer.js index 499e93509..9c379ce48 100644 --- a/src/reducers/userReducer.js +++ b/src/reducers/userReducer.js @@ -61,6 +61,9 @@ export const initialState = { }, showPublicCode: Settings.getShowPublicCode(), adjacentStopDialogOpen: false, + auth: {}, + isGuest: true, + allowNewStopEverywhere: false, }; const userReducer = (state = initialState, action) => { @@ -303,6 +306,27 @@ const userReducer = (state = initialState, action) => { deleteStopDialogWarning: action.payload, }); + case types.APOLLO_QUERY_RESULT: + if (action.operationName === "getUserPermissions") { + return { + ...state, + isGuest: action.result.data.userPermissions.isGuest, + allowNewStopEverywhere: + action.result.data.userPermissions.allowNewStopEverywhere, + }; + } else if (action.operationName === "getLocationPermissions") { + return { + ...state, + locationPermissions: action.result.data.locationPermissions, + }; + } + return state; + + case types.UPDATED_AUTH: + return Object.assign({}, state, { + auth: action.payload, + }); + default: return state; } diff --git a/src/reducers/zonesSlice.ts b/src/reducers/zonesSlice.ts index e8d8184ab..9699cc52d 100644 --- a/src/reducers/zonesSlice.ts +++ b/src/reducers/zonesSlice.ts @@ -71,7 +71,7 @@ export const getTariffZonesForFilterAction = createAsyncThunk< const response = await getTiamatClient().query({ query: findTariffZonesForFilter, fetchPolicy: "network-only", - context: getContext(thunkAPI.getState().roles.auth), + context: getContext(thunkAPI.getState().user.auth), }); return response.data.tariffZones; @@ -85,7 +85,7 @@ export const getFareZonesForFilterAction = createAsyncThunk< const response = await getTiamatClient().query({ query: findFareZonesForFilter, fetchPolicy: "network-only", - context: getContext(thunkAPI.getState().roles.auth), + context: getContext(thunkAPI.getState().user.auth), }); return response.data.fareZones; @@ -100,7 +100,7 @@ export const getTariffZonesByIdsAction = createAsyncThunk< query: findTariffZonesByIds, variables: { ids }, fetchPolicy: "network-only", - context: getContext(thunkAPI.getState().roles.auth), + context: getContext(thunkAPI.getState().user.auth), }); return response.data.tariffZones; }); @@ -114,7 +114,7 @@ export const getFareZonesByIdsAction = createAsyncThunk< query: findFareZones, variables: { ids }, fetchPolicy: "network-only", - context: getContext(thunkAPI.getState().roles.auth), + context: getContext(thunkAPI.getState().user.auth), }); return response.data.fareZones; diff --git a/src/test/userAndRoles.spec.js b/src/test/userAndRoles.spec.js deleted file mode 100644 index 2ead32473..000000000 --- a/src/test/userAndRoles.spec.js +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by -the European Commission - subsequent versions of the EUPL (the "Licence"); -You may not use this work except in compliance with the Licence. -You may obtain a copy of the Licence at: - - https://joinup.ec.europa.eu/software/page/eupl - -Unless required by applicable law or agreed to in writing, software -distributed under the Licence is distributed on an "AS IS" basis, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the Licence for the specific language governing permissions and -limitations under the Licence. */ - -import { getAllowanceInfoForStop } from "../reducers/rolesReducerUtils"; -import mockRailReplacementStop from "./mock/mockRailReplacementStop"; -import mockRailStop from "./mock/mockRailStop"; -import mockBusStop from "./mock/mockBusStop"; -import { mockedAllowanceInfoAction } from "./mock/mockedAllowanceInfoAction"; - -describe("User and roles - scenarios", () => { - test("nsbEditStops - train and railReplacementBus - verify railReplacementBus ", () => { - const mockStopWithPermissions = { - data: { - pathLink: mockRailReplacementStop.data.pathLink, - stopPlace: mockRailReplacementStop.data.stopPlace.map((stop) => ({ - ...stop, - permissions: { - canEdit: true, - canDelete: false, - allowedStopPlaceTypes: ["railStation"], - allowedSubmodes: ["railReplacementBus"], - bannedStopPlaceTypes: [], - bannedSubmodes: [], - }, - })), - }, - }; - - const allowanceInfo = getAllowanceInfoForStop( - mockedAllowanceInfoAction(mockStopWithPermissions), - ); - expect(allowanceInfo.canEdit).toEqual(true); - }); - - test("nsbEditStops - train and railReplacementBus - verify railStation", () => { - const mockStopWithPermissions = { - data: { - pathLink: mockRailStop.data.pathLink, - stopPlace: mockRailStop.data.stopPlace.map((stop) => ({ - ...stop, - permissions: { - canEdit: true, - canDelete: false, - allowedStopPlaceTypes: ["railStation"], - allowedSubmodes: ["railReplacementBus"], - bannedStopPlaceTypes: [], - bannedSubmodes: [], - }, - })), - }, - }; - - const allowanceInfo = getAllowanceInfoForStop( - mockedAllowanceInfoAction(mockStopWithPermissions), - ); - expect(allowanceInfo.canEdit).toEqual(true); - }); - - test("nsbEditStops - train and railReplacementBus - verify railStation", () => { - const mockRailStopWithPermissions = { - data: { - pathLink: mockRailStop.data.pathLink, - stopPlace: mockRailStop.data.stopPlace.map((stop) => ({ - ...stop, - permissions: { - canEdit: true, - canDelete: false, - allowedStopPlaceTypes: ["railStation"], - allowedSubmodes: ["railReplacementBus"], - bannedStopPlaceTypes: [], - bannedSubmodes: [], - }, - })), - }, - }; - - const allowanceInfo = getAllowanceInfoForStop( - mockedAllowanceInfoAction(mockRailStopWithPermissions), - ); - expect(allowanceInfo.canEdit).toEqual(true); - - const mockBusStopWithPermissions = { - data: { - pathLink: mockBusStop.data.pathLink, - stopPlace: mockBusStop.data.stopPlace.map((stop) => ({ - ...stop, - permissions: { - canEdit: false, - canDelete: false, - allowedStopPlaceTypes: [], - allowedSubmodes: [], - bannedStopPlaceTypes: [], - bannedSubmodes: [], - }, - })), - }, - }; - - const allowanceBusStop = getAllowanceInfoForStop( - mockedAllowanceInfoAction(mockBusStopWithPermissions), - ); - expect(allowanceBusStop.canEdit).toEqual(false); - }); -}); diff --git a/src/test/reducers/rolesReducerUtils.spec.js b/src/utils/permissionUtils.test.js similarity index 85% rename from src/test/reducers/rolesReducerUtils.spec.js rename to src/utils/permissionUtils.test.js index e64786e49..17ece6e72 100644 --- a/src/test/reducers/rolesReducerUtils.spec.js +++ b/src/utils/permissionUtils.test.js @@ -1,61 +1,51 @@ import { - getLegalStopPlaceTypesForStopPlace, + getLegalStopPlaceTypes, getLegalSubmodesForStopPlace, -} from "../../reducers/rolesReducerUtils"; -import stopTypes from "../../models/stopTypes"; +} from "./permissionsUtils"; +import stopTypes from "../models/stopTypes"; -describe("getLegalStopPlaceTypesForStopPlace", () => { +describe("getLegalStopPlaceTypes", () => { it("returns all stop types for empty allowed and banned stop place types", () => { expect( - getLegalStopPlaceTypesForStopPlace({ - permissions: { - allowedStopPlaceTypes: [], - bannedStopPlaceTypes: [], - }, + getLegalStopPlaceTypes({ + allowedStopPlaceTypes: [], + bannedStopPlaceTypes: [], }), ).toEqual(Object.keys(stopTypes)); }); it("returns all stop types for wildcarded allowedStopPlaceTypes", () => { expect( - getLegalStopPlaceTypesForStopPlace({ - permissions: { - allowedStopPlaceTypes: ["*"], - bannedStopPlaceTypes: [], - }, + getLegalStopPlaceTypes({ + allowedStopPlaceTypes: ["*"], + bannedStopPlaceTypes: [], }), ).toEqual(Object.keys(stopTypes)); }); it("returns the empty list for wildcarded bannedStopPlaceTypes", () => { expect( - getLegalStopPlaceTypesForStopPlace({ - permissions: { - allowedStopPlaceTypes: [], - bannedStopPlaceTypes: ["*"], - }, + getLegalStopPlaceTypes({ + allowedStopPlaceTypes: [], + bannedStopPlaceTypes: ["*"], }), ).toEqual([]); }); it("returns difference between allowed and banned when allowed list is non-empty", () => { expect( - getLegalStopPlaceTypesForStopPlace({ - permissions: { - allowedStopPlaceTypes: ["railStation", "onstreetBus"], - bannedStopPlaceTypes: ["railStation"], - }, + getLegalStopPlaceTypes({ + allowedStopPlaceTypes: ["railStation", "onstreetBus"], + bannedStopPlaceTypes: ["railStation"], }), ).toEqual(["onstreetBus"]); }); it("returns all stop place types except banned, when allowed list is empty", () => { expect( - getLegalStopPlaceTypesForStopPlace({ - permissions: { - allowedStopPlaceTypes: [], - bannedStopPlaceTypes: ["railStation"], - }, + getLegalStopPlaceTypes({ + allowedStopPlaceTypes: [], + bannedStopPlaceTypes: ["railStation"], }), ).toEqual(Object.keys(stopTypes).filter((type) => type !== "railStation")); }); diff --git a/src/utils/permissionsUtils.js b/src/utils/permissionsUtils.js new file mode 100644 index 000000000..9918b7cc5 --- /dev/null +++ b/src/utils/permissionsUtils.js @@ -0,0 +1,149 @@ +/* + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by + the European Commission - subsequent versions of the EUPL (the "Licence"); + You may not use this work except in compliance with the Licence. + You may obtain a copy of the Licence at: + + https://joinup.ec.europa.eu/software/page/eupl + + Unless required by applicable law or agreed to in writing, software + distributed under the Licence is distributed on an "AS IS" basis, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the Licence for the specific language governing permissions and + limitations under the Licence. */ + +import stopTypes from "../models/stopTypes"; + +const buildAllowanceInfo = (permissions) => { + const canEdit = permissions.canEdit; + const canDeleteStop = permissions.canDelete; + + let legalStopPlaceTypes = getLegalStopPlaceTypes(permissions); + let legalSubmodes = getLegalSubmodes(permissions); + + return { + legalStopPlaceTypes, + legalSubmodes, + blacklistedStopPlaceTypes: permissions.bannedStopPlaceTypes, + canEdit, + canDeleteStop, + }; +}; + +const buildAllowanceInfoForStopPlace = (stopPlace) => { + return buildAllowanceInfo(stopPlace.permissions); +}; + +export const getAllowanceInfoFromLocationPermissions = ( + locationPermissions, +) => { + if (!locationPermissions) { + return { + canEdit: false, + canDelete: false, + legalStopPlaceTypes: [], + legalSubmodes: [], + }; + } + return { + canEdit: locationPermissions.canEdit, + canDelete: locationPermissions.canDelete, + legalStopPlaceTypes: getLegalStopPlacesTypes( + locationPermissions.allowedStopPlaceTypes, + locationPermissions.bannedStopPlaceTypes, + ), + legalSubmodes: getLegalSubmodes(locationPermissions), + }; +}; + +export const getLegalStopPlaceTypes = ({ + allowedStopPlaceTypes, + bannedStopPlaceTypes, +}) => { + return getLegalStopPlacesTypes(allowedStopPlaceTypes, bannedStopPlaceTypes); +}; + +export const getLegalStopPlacesTypes = ( + allowedStopPlaceTypes, + bannedStopPlaceTypes, +) => { + const allStopTypes = Object.keys(stopTypes); + if (bannedStopPlaceTypes.includes("*")) { + return []; + } + + if (isWildcardOrEmpty(allowedStopPlaceTypes)) { + return allStopTypes.filter((type) => !bannedStopPlaceTypes.includes(type)); + } + + return allowedStopPlaceTypes.filter( + (type) => !bannedStopPlaceTypes.includes(type), + ); +}; + +const isWildcardOrEmpty = (list) => { + return !list || list.length === 0 || (list.length > 0 && list[0] === "*"); +}; + +const isStopTypeAllowed = (stopType, permissions) => { + const { allowedStopPlaceTypes } = permissions; + return ( + isWildcardOrEmpty(allowedStopPlaceTypes) || + allowedStopPlaceTypes.includes(stopType) + ); +}; + +const getSubmodesForStopType = (stopType, permissions) => { + const { submodes } = stopTypes[stopType]; + if (!submodes) return []; + + if (permissions.bannedStopPlaceTypes.includes(stopType)) return []; + if (!isStopTypeAllowed(stopType, permissions)) return []; + + return submodes; +}; + +const filterBySubmodePermissions = (submodes, permissions) => { + const { allowedSubmodes, bannedSubmodes } = permissions; + + if (allowedSubmodes.includes("*")) + return submodes.filter((type) => !bannedSubmodes.includes(type)); + if (bannedSubmodes.includes("*")) return []; + + if (allowedSubmodes.length > 0) { + return submodes.filter( + (type) => + allowedSubmodes.includes(type) && !bannedSubmodes.includes(type), + ); + } + + return submodes.filter((type) => !bannedSubmodes.includes(type)); +}; + +export const getLegalSubmodesForStopPlace = (stopPlace) => { + return getLegalSubmodes(stopPlace.permissions); +}; + +export const getLegalSubmodes = (permissions) => { + const applicableSubmodes = Object.keys(stopTypes).reduce( + (acc, stopType) => [ + ...acc, + ...getSubmodesForStopType(stopType, permissions), + ], + [], + ); + + return filterBySubmodePermissions(applicableSubmodes, permissions); +}; + +export const getStopPermissions = (stopPlace) => { + if (!stopPlace?.permissions) { + return { + canEdit: false, + canDelete: false, + legalStopPlaceTypes: [], + legalSubmodes: [], + }; + } + return buildAllowanceInfoForStopPlace(stopPlace); +};