@truedat/auth 4.38.8 → 4.40.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -1
- package/package.json +4 -4
- package/src/messages/en.js +1 -1
- package/src/users/api.js +1 -3
- package/src/users/components/index.js +1 -3
- package/src/users/reducers/index.js +0 -1
- package/src/users/routines.js +0 -1
- package/src/users/sagas/index.js +2 -5
- package/src/users/selectors/index.js +0 -1
- package/src/users/components/UserDomainsLoader.js +0 -30
- package/src/users/components/__tests__/UserDomainsLoader.spec.js +0 -32
- package/src/users/reducers/__tests__/userDomains.spec.js +0 -66
- package/src/users/reducers/userDomains.js +0 -31
- package/src/users/sagas/__tests__/fetchUserDomains.spec.js +0 -84
- package/src/users/sagas/fetchUserDomains.js +0 -33
- package/src/users/selectors/getUserDomains.js +0 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [4.40.1] 2022-03-08
|
|
4
|
+
|
|
5
|
+
### Removed
|
|
6
|
+
|
|
7
|
+
- [TD-4604] Avoid fetching permissions from `/api/users/me/permissions`
|
|
8
|
+
|
|
9
|
+
## [4.40.0] 2022-03-07
|
|
10
|
+
|
|
11
|
+
### Removed
|
|
12
|
+
|
|
13
|
+
- Removed unused selector `getUserDomains`
|
|
14
|
+
|
|
3
15
|
## [4.38.7] 2022-02-22
|
|
4
16
|
|
|
5
17
|
### Added
|
|
6
18
|
|
|
7
|
-
- [TD-4437] Add manage_rule_results permission messages
|
|
19
|
+
- [TD-4437] Add `manage_rule_results` permission messages
|
|
8
20
|
|
|
9
21
|
## [4.32.1] 2021-11-15
|
|
10
22
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truedat/auth",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.40.1",
|
|
4
4
|
"description": "Truedat Web Auth",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"jsnext:main": "src/index.js",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"@testing-library/jest-dom": "^5.14.1",
|
|
35
35
|
"@testing-library/react": "^12.0.0",
|
|
36
36
|
"@testing-library/user-event": "^13.2.1",
|
|
37
|
-
"@truedat/test": "4.
|
|
37
|
+
"@truedat/test": "4.39.0",
|
|
38
38
|
"babel-jest": "^27.0.6",
|
|
39
39
|
"babel-plugin-dynamic-import-node": "^2.3.3",
|
|
40
40
|
"babel-plugin-lodash": "^3.3.4",
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
]
|
|
85
85
|
},
|
|
86
86
|
"dependencies": {
|
|
87
|
-
"@truedat/core": "4.
|
|
87
|
+
"@truedat/core": "4.40.1",
|
|
88
88
|
"auth0-js": "^9.12.2",
|
|
89
89
|
"immutable": "^4.0.0-rc.12",
|
|
90
90
|
"jwt-decode": "^2.2.0",
|
|
@@ -106,5 +106,5 @@
|
|
|
106
106
|
"react-dom": ">= 16.8.6 < 17",
|
|
107
107
|
"semantic-ui-react": ">= 0.88.2 < 2.1"
|
|
108
108
|
},
|
|
109
|
-
"gitHead": "
|
|
109
|
+
"gitHead": "58b35b48833fbed1602d1070853e6c805bcc48f1"
|
|
110
110
|
}
|
package/src/messages/en.js
CHANGED
|
@@ -55,7 +55,7 @@ export default {
|
|
|
55
55
|
"permission.create_business_concept_link": "Create Business Concept Links",
|
|
56
56
|
"permission.create_data_structure": "Create Structures",
|
|
57
57
|
"permission.create_domain": "Create Domains",
|
|
58
|
-
"permission.create_ingest": "
|
|
58
|
+
"permission.create_ingest": "Create Ingests",
|
|
59
59
|
"permission.create_structure_note": "Create note",
|
|
60
60
|
"permission.delete_acl_entry": "Delete Domain Roles",
|
|
61
61
|
"permission.delete_business_concept": "Delete Business Concepts",
|
package/src/users/api.js
CHANGED
|
@@ -2,7 +2,6 @@ const API_USER = "/api/users/:id";
|
|
|
2
2
|
const API_USERS = "/api/users";
|
|
3
3
|
const API_USER_CHANGE_PASSWORD = "/api/password";
|
|
4
4
|
const API_USERS_SEARCH = "/api/users/search";
|
|
5
|
-
const API_USER_DOMAINS = "/api/users/me/permissions?permissions=:permissions";
|
|
6
5
|
const API_INIT = "/api/init";
|
|
7
6
|
const API_CAN_INIT = "/api/init/can";
|
|
8
7
|
|
|
@@ -11,7 +10,6 @@ export {
|
|
|
11
10
|
API_USERS,
|
|
12
11
|
API_USERS_SEARCH,
|
|
13
12
|
API_USER_CHANGE_PASSWORD,
|
|
14
|
-
API_USER_DOMAINS,
|
|
15
13
|
API_INIT,
|
|
16
|
-
API_CAN_INIT
|
|
14
|
+
API_CAN_INIT,
|
|
17
15
|
};
|
|
@@ -12,7 +12,6 @@ import UsersLoader from "./UsersLoader";
|
|
|
12
12
|
import UsersSearchLoader from "./UsersSearchLoader";
|
|
13
13
|
import UserSelector from "./UserSelector";
|
|
14
14
|
import UserDomainsFilter from "./UserDomainsFilter";
|
|
15
|
-
import UserDomainsLoader from "./UserDomainsLoader";
|
|
16
15
|
import InitialUser from "./InitialUser";
|
|
17
16
|
import CanInitLoader from "./CanInitLoader";
|
|
18
17
|
|
|
@@ -31,7 +30,6 @@ export {
|
|
|
31
30
|
UsersLoader,
|
|
32
31
|
UsersSearchLoader,
|
|
33
32
|
UserSelector,
|
|
34
|
-
UserDomainsLoader,
|
|
35
33
|
InitialUser,
|
|
36
|
-
CanInitLoader
|
|
34
|
+
CanInitLoader,
|
|
37
35
|
};
|
package/src/users/routines.js
CHANGED
|
@@ -7,7 +7,6 @@ export const clearUsersSearch = createRoutine("CLEAR_USERS_SEARCH");
|
|
|
7
7
|
export const createUser = createRoutine("CREATE_USER");
|
|
8
8
|
export const deleteUser = createRoutine("DELETE_USER");
|
|
9
9
|
export const fetchUser = createRoutine("FETCH_USER");
|
|
10
|
-
export const fetchUserDomains = createRoutine("FETCH_USER_DOMAINS");
|
|
11
10
|
export const fetchUsers = createRoutine("FETCH_USERS");
|
|
12
11
|
export const searchUsers = createRoutine("SEARCH_USERS");
|
|
13
12
|
export const updatePassword = createRoutine("UPDATE_PASSWORD");
|
package/src/users/sagas/index.js
CHANGED
|
@@ -3,7 +3,6 @@ import { deleteUserRequestSaga } from "./deleteUser";
|
|
|
3
3
|
import { fetchUserRequestSaga } from "./fetchUser";
|
|
4
4
|
import { fetchUsersRequestSaga } from "./fetchUsers";
|
|
5
5
|
import { updateUserRequestSaga } from "./updateUser";
|
|
6
|
-
import { fetchUserDomainsRequestSaga } from "./fetchUserDomains";
|
|
7
6
|
import { createInitialUserRequestSaga } from "./createInitialUser";
|
|
8
7
|
import { fetchCanInitRequestSaga } from "./fetchCanInit";
|
|
9
8
|
import { searchUsersRequestSaga } from "./searchUsers";
|
|
@@ -14,10 +13,9 @@ export {
|
|
|
14
13
|
updateUserRequestSaga,
|
|
15
14
|
deleteUserRequestSaga,
|
|
16
15
|
fetchUsersRequestSaga,
|
|
17
|
-
fetchUserDomainsRequestSaga,
|
|
18
16
|
createInitialUserRequestSaga,
|
|
19
17
|
fetchCanInitRequestSaga,
|
|
20
|
-
searchUsersRequestSaga
|
|
18
|
+
searchUsersRequestSaga,
|
|
21
19
|
};
|
|
22
20
|
|
|
23
21
|
export default [
|
|
@@ -26,8 +24,7 @@ export default [
|
|
|
26
24
|
updateUserRequestSaga(),
|
|
27
25
|
deleteUserRequestSaga(),
|
|
28
26
|
fetchUsersRequestSaga(),
|
|
29
|
-
fetchUserDomainsRequestSaga(),
|
|
30
27
|
createInitialUserRequestSaga(),
|
|
31
28
|
fetchCanInitRequestSaga(),
|
|
32
|
-
searchUsersRequestSaga()
|
|
29
|
+
searchUsersRequestSaga(),
|
|
33
30
|
];
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { useEffect } from "react";
|
|
2
|
-
import PropTypes from "prop-types";
|
|
3
|
-
import { connect } from "react-redux";
|
|
4
|
-
import { clearUserDomains, fetchUserDomains } from "../routines";
|
|
5
|
-
|
|
6
|
-
export const UserDomainsLoader = ({
|
|
7
|
-
permissions,
|
|
8
|
-
fetchUserDomains,
|
|
9
|
-
clearUserDomains,
|
|
10
|
-
}) => {
|
|
11
|
-
useEffect(() => {
|
|
12
|
-
return () => {
|
|
13
|
-
clearUserDomains();
|
|
14
|
-
};
|
|
15
|
-
}, [clearUserDomains]);
|
|
16
|
-
useEffect(() => {
|
|
17
|
-
fetchUserDomains({ permissions });
|
|
18
|
-
}, [permissions, fetchUserDomains]);
|
|
19
|
-
return null;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
UserDomainsLoader.propTypes = {
|
|
23
|
-
clearUserDomains: PropTypes.func,
|
|
24
|
-
fetchUserDomains: PropTypes.func,
|
|
25
|
-
permissions: PropTypes.array,
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export default connect(null, { clearUserDomains, fetchUserDomains })(
|
|
29
|
-
UserDomainsLoader
|
|
30
|
-
);
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { mount } from "enzyme";
|
|
3
|
-
import { UserDomainsLoader } from "../UserDomainsLoader";
|
|
4
|
-
|
|
5
|
-
describe("<UserDomainsLoader />", () => {
|
|
6
|
-
it("calls fetchUserDomains with permissions when component mounts but not when it unmounts", () => {
|
|
7
|
-
const props = {
|
|
8
|
-
clearUserDomains: jest.fn(),
|
|
9
|
-
fetchUserDomains: jest.fn(),
|
|
10
|
-
permissions: ["view_dashboard"]
|
|
11
|
-
};
|
|
12
|
-
const wrapper = mount(<UserDomainsLoader {...props} />);
|
|
13
|
-
expect(props.fetchUserDomains.mock.calls[0]).toEqual([
|
|
14
|
-
{ permissions: ["view_dashboard"] }
|
|
15
|
-
]);
|
|
16
|
-
expect(props.fetchUserDomains.mock.calls.length).toBe(1);
|
|
17
|
-
wrapper.unmount();
|
|
18
|
-
expect(props.fetchUserDomains.mock.calls.length).toBe(1);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it("calls clearUserDomains when component unmounts", () => {
|
|
22
|
-
const props = {
|
|
23
|
-
clearUserDomains: jest.fn(),
|
|
24
|
-
fetchUserDomains: jest.fn(),
|
|
25
|
-
permissions: ["view_dashboard"]
|
|
26
|
-
};
|
|
27
|
-
const wrapper = mount(<UserDomainsLoader {...props} />);
|
|
28
|
-
expect(props.clearUserDomains.mock.calls.length).toBe(0);
|
|
29
|
-
wrapper.unmount();
|
|
30
|
-
expect(props.clearUserDomains.mock.calls.length).toBe(1);
|
|
31
|
-
});
|
|
32
|
-
});
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { fetchUserDomains, clearUserDomains } from "../../routines";
|
|
2
|
-
import { userDomains, userDomainsLoading } from "..";
|
|
3
|
-
|
|
4
|
-
const fooState = { foo: "bar" };
|
|
5
|
-
|
|
6
|
-
describe("reducers: userDomainsLoading", () => {
|
|
7
|
-
it("should provide the initial state", () => {
|
|
8
|
-
expect(userDomainsLoading(undefined, {})).toBe(false);
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it("should be true after receiving the TRIGGER action", () => {
|
|
12
|
-
expect(userDomainsLoading(false, { type: fetchUserDomains.TRIGGER })).toBe(
|
|
13
|
-
true
|
|
14
|
-
);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it("should be false after receiving the FULFILL action", () => {
|
|
18
|
-
expect(userDomainsLoading(true, { type: fetchUserDomains.FULFILL })).toBe(
|
|
19
|
-
false
|
|
20
|
-
);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it("should ignore unhandled actions", () => {
|
|
24
|
-
expect(userDomainsLoading(fooState, { type: "FOO" })).toBe(fooState);
|
|
25
|
-
});
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
describe("reducers: userDomains", () => {
|
|
29
|
-
const initialState = [];
|
|
30
|
-
|
|
31
|
-
it("should provide the initial state", () => {
|
|
32
|
-
expect(userDomains(undefined, {})).toEqual(initialState);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it("should return initial state on failure", () => {
|
|
36
|
-
expect(userDomains(undefined, { type: fetchUserDomains.FAILURE })).toEqual(
|
|
37
|
-
initialState
|
|
38
|
-
);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("should return initial state on clearUserDomains", () => {
|
|
42
|
-
expect(userDomains(undefined, { type: clearUserDomains.TRIGGER })).toEqual(
|
|
43
|
-
initialState
|
|
44
|
-
);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it("should handle the SUCCESS action", () => {
|
|
48
|
-
const userDomainsData = [
|
|
49
|
-
{ permission: "view_quality_rule", domains: { id: 1, name: "d1" } },
|
|
50
|
-
{ permission: "view_dashboard", domains: { id: 2, name: "d2" } }
|
|
51
|
-
];
|
|
52
|
-
|
|
53
|
-
const permission_domains = userDomainsData;
|
|
54
|
-
|
|
55
|
-
expect(
|
|
56
|
-
userDomains(fooState, {
|
|
57
|
-
type: fetchUserDomains.SUCCESS,
|
|
58
|
-
payload: { permission_domains }
|
|
59
|
-
})
|
|
60
|
-
).toMatchObject(permission_domains);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("should ignore unknown actions", () => {
|
|
64
|
-
expect(userDomains(fooState, { type: "FOO" })).toBe(fooState);
|
|
65
|
-
});
|
|
66
|
-
});
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import _ from "lodash/fp";
|
|
2
|
-
import { fetchUserDomains, clearUserDomains } from "../routines";
|
|
3
|
-
|
|
4
|
-
const initialState = [];
|
|
5
|
-
|
|
6
|
-
const userDomains = (state = initialState, { type, payload }) => {
|
|
7
|
-
switch (type) {
|
|
8
|
-
case fetchUserDomains.SUCCESS:
|
|
9
|
-
const permissionDomains = _.prop("permission_domains")(payload);
|
|
10
|
-
return _.isEqual(permissionDomains, state) ? state : permissionDomains;
|
|
11
|
-
case fetchUserDomains.FAILURE:
|
|
12
|
-
return initialState;
|
|
13
|
-
case clearUserDomains.TRIGGER:
|
|
14
|
-
return initialState;
|
|
15
|
-
default:
|
|
16
|
-
return state;
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const userDomainsLoading = (state = false, { type }) => {
|
|
21
|
-
switch (type) {
|
|
22
|
-
case fetchUserDomains.TRIGGER:
|
|
23
|
-
return true;
|
|
24
|
-
case fetchUserDomains.FULFILL:
|
|
25
|
-
return false;
|
|
26
|
-
default:
|
|
27
|
-
return state;
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export { userDomains, userDomainsLoading };
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { testSaga } from "redux-saga-test-plan";
|
|
2
|
-
import { apiJson, JSON_OPTS } from "@truedat/core/services/api";
|
|
3
|
-
import {
|
|
4
|
-
fetchUserDomainsRequestSaga,
|
|
5
|
-
fetchUserDomainsSaga
|
|
6
|
-
} from "../fetchUserDomains";
|
|
7
|
-
import { fetchUserDomains } from "../../routines";
|
|
8
|
-
|
|
9
|
-
describe("sagas: fetchUserDomainsRequestSaga", () => {
|
|
10
|
-
it("should invoke fetchUserDomainsSaga on fetchUserDomains.TRIGGER", () => {
|
|
11
|
-
expect(() => {
|
|
12
|
-
testSaga(fetchUserDomainsRequestSaga)
|
|
13
|
-
.next()
|
|
14
|
-
.finish()
|
|
15
|
-
.isDone();
|
|
16
|
-
}).not.toThrow();
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it("should throw exception if an unhandled action is received", () => {
|
|
20
|
-
expect(() => {
|
|
21
|
-
testSaga(fetchUserDomainsRequestSaga)
|
|
22
|
-
.next()
|
|
23
|
-
.takeLatest("FOO", fetchUserDomainsRequestSaga);
|
|
24
|
-
}).toThrow();
|
|
25
|
-
});
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
describe("sagas: fetchUserDomainsSaga", () => {
|
|
29
|
-
const permissions = ["foo", "bar"];
|
|
30
|
-
const url = "/api/users/me/permissions?permissions=foo%2Cbar";
|
|
31
|
-
const data = { foo: "bar" };
|
|
32
|
-
|
|
33
|
-
it("should fetch user domains", () => {
|
|
34
|
-
expect(() => {
|
|
35
|
-
testSaga(fetchUserDomainsSaga, { payload: { permissions } })
|
|
36
|
-
.next()
|
|
37
|
-
.put(fetchUserDomains.request())
|
|
38
|
-
.next()
|
|
39
|
-
.call(apiJson, url, JSON_OPTS)
|
|
40
|
-
.next({ data })
|
|
41
|
-
.put(fetchUserDomains.success(data))
|
|
42
|
-
.next()
|
|
43
|
-
.put(fetchUserDomains.fulfill())
|
|
44
|
-
.next()
|
|
45
|
-
.isDone();
|
|
46
|
-
}).not.toThrow();
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it("should catch response error", () => {
|
|
50
|
-
const response = { status: 400, data: "error" };
|
|
51
|
-
const error = { response };
|
|
52
|
-
expect(() => {
|
|
53
|
-
testSaga(fetchUserDomainsSaga, { payload: { permissions } })
|
|
54
|
-
.next()
|
|
55
|
-
.put(fetchUserDomains.request())
|
|
56
|
-
.next()
|
|
57
|
-
.call(apiJson, url, JSON_OPTS)
|
|
58
|
-
.throw(error)
|
|
59
|
-
.put(fetchUserDomains.failure(response))
|
|
60
|
-
.next()
|
|
61
|
-
.put(fetchUserDomains.fulfill())
|
|
62
|
-
.next()
|
|
63
|
-
.isDone();
|
|
64
|
-
}).not.toThrow();
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it("should catch message error", () => {
|
|
68
|
-
const message = "error";
|
|
69
|
-
const error = { message };
|
|
70
|
-
expect(() => {
|
|
71
|
-
testSaga(fetchUserDomainsSaga, { payload: { permissions } })
|
|
72
|
-
.next()
|
|
73
|
-
.put(fetchUserDomains.request())
|
|
74
|
-
.next()
|
|
75
|
-
.call(apiJson, url, JSON_OPTS)
|
|
76
|
-
.throw(error)
|
|
77
|
-
.put(fetchUserDomains.failure(message))
|
|
78
|
-
.next()
|
|
79
|
-
.put(fetchUserDomains.fulfill())
|
|
80
|
-
.next()
|
|
81
|
-
.isDone();
|
|
82
|
-
}).not.toThrow();
|
|
83
|
-
});
|
|
84
|
-
});
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import _ from "lodash/fp";
|
|
2
|
-
import { compile } from "path-to-regexp";
|
|
3
|
-
import { call, put, takeLatest } from "redux-saga/effects";
|
|
4
|
-
import { apiJson, JSON_OPTS } from "@truedat/core/services/api";
|
|
5
|
-
import { fetchUserDomains } from "../routines";
|
|
6
|
-
import { API_USER_DOMAINS } from "../api";
|
|
7
|
-
|
|
8
|
-
export function* fetchUserDomainsSaga({ payload }) {
|
|
9
|
-
try {
|
|
10
|
-
const permissions = _.flow(
|
|
11
|
-
_.propOr([], "permissions"),
|
|
12
|
-
_.castArray,
|
|
13
|
-
_.join(",")
|
|
14
|
-
)(payload);
|
|
15
|
-
const url = compile(API_USER_DOMAINS)({ permissions });
|
|
16
|
-
yield put(fetchUserDomains.request());
|
|
17
|
-
const { data } = yield call(apiJson, url, JSON_OPTS);
|
|
18
|
-
yield put(fetchUserDomains.success(data));
|
|
19
|
-
} catch (error) {
|
|
20
|
-
if (error.response) {
|
|
21
|
-
const { status, data } = error.response;
|
|
22
|
-
yield put(fetchUserDomains.failure({ status, data }));
|
|
23
|
-
} else {
|
|
24
|
-
yield put(fetchUserDomains.failure(error.message));
|
|
25
|
-
}
|
|
26
|
-
} finally {
|
|
27
|
-
yield put(fetchUserDomains.fulfill());
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function* fetchUserDomainsRequestSaga() {
|
|
32
|
-
yield takeLatest(fetchUserDomains.TRIGGER, fetchUserDomainsSaga);
|
|
33
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import _ from "lodash/fp";
|
|
2
|
-
import { createSelector } from "reselect";
|
|
3
|
-
|
|
4
|
-
const getUserPermissionsDomains = ({ userDomains }) => userDomains;
|
|
5
|
-
|
|
6
|
-
export const getUserDomains = createSelector(
|
|
7
|
-
getUserPermissionsDomains,
|
|
8
|
-
_.flow(_.map("domains"), _.flatten, _.uniqBy("id"))
|
|
9
|
-
);
|