Skip to content

Commit

Permalink
Merge branch 'master' into exui-2106
Browse files Browse the repository at this point in the history
  • Loading branch information
RiteshHMCTS authored Jul 25, 2024
2 parents ad7a2ea + 80ad0aa commit 7f4a8a8
Show file tree
Hide file tree
Showing 29 changed files with 313 additions and 165 deletions.
3 changes: 3 additions & 0 deletions api/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { initProxy } from './proxy.config';
import routes from './routes';
import workAllocationRouter from './workAllocation/routes';
import { idamCheck } from './idamCheck';
import { getNewUsersByServiceName } from './workAllocation';

export const app = express();

Expand Down Expand Up @@ -129,3 +130,5 @@ const logger: JUILogger = log4jui.getLogger('Application');
logger.info(`Started up using ${getConfigValue(PROTOCOL)}`);

new Promise(idamCheck).then(() => 'IDAM is up and running');
// EUI-2028 - Get the caseworkers, ideally prior to a user logging into application
new Promise(getNewUsersByServiceName).then(() => 'Caseworkers have been loaded');
2 changes: 1 addition & 1 deletion api/roleAccess/dtos/to-role-assignment-dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ export function toSARequestRoleAssignmentBody(allocateRoleData: AllocateRoleData
}

export function getActorId(currentUserId: string, allocateRoleData: AllocateRoleData): string {
if (allocateRoleData.allocateTo === AllocateTo.RESERVE_TO_ME) {
if (allocateRoleData.allocateTo === AllocateTo.ALLOCATE_TO_ME) {
return currentUserId;
}

Expand Down
2 changes: 1 addition & 1 deletion api/roleAccess/models/allocate-role.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export enum RoleCategory {
}

export enum AllocateTo {
RESERVE_TO_ME = 'Reserve to me',
ALLOCATE_TO_ME = 'Allocate to me',
ALLOCATE_TO_ANOTHER_PERSON = 'Allocate to another person',
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const REQUEST_BODY = {
id: 'lead-judge',
name: 'Lead judge'
},
allocateTo: 'Reserve to me',
allocateTo: 'Allocate to me',
personToBeRemoved: {
id: 'p111111',
name: 'test1',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const REQUEST_BODY = {
typeOfRole: { id: 'specific-access-granted', name: 'specific-access-granted' },
requestedRole: 'specific-access-legal-ops',
requestId: '59bedc19-9cc6-4bff-9f58-041c3ba664a0',
allocateTo: 'Reserve to me',
allocateTo: 'Allocate to me',
personToBeRemoved: null,
person: { id: 'db17f6f7-1abf-4223-8b5e-1eece04ee5d8', name: null, domain: null },
actorId: '123',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const REQUEST_BODY = {
typeOfRole: { id: 'specific-access-granted', name: 'specific-access-granted' },
requestedRole: 'specific-access-legal-ops',
requestId: '59bedc19-9cc6-4bff-9f58-041c3ba664a0',
allocateTo: 'Reserve to me',
allocateTo: 'Allocate to me',
personToBeRemoved: null,
person: { id: 'db17f6f7-1abf-4223-8b5e-1eece04ee5d8', name: null, domain: null },
actorId: '123',
Expand Down
16 changes: 16 additions & 0 deletions api/workAllocation/caseWorkerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ export async function handleUsersGet(path: string, req: EnhancedRequest): Promis
return response.data;
}

export async function handleNewUsersGet(path: string, headers: any): Promise<any> {
logger.info('getting users for', path);
const response: AxiosResponse = await http.get(path, { headers });
return response.data;
}

export async function handleCaseWorkerGetAll(path: string, req: EnhancedRequest): Promise<any> {
logger.info('getting all caseworkers for', path);
const headers = setHeaders(req);
Expand Down Expand Up @@ -69,6 +75,16 @@ export async function handlePostRoleAssignments(path: string, payload: any, req:
return response;
}

export async function handlePostRoleAssignmentsWithNewUsers(path: string, payload: any, headers: any): Promise<any> {
// sort
// direction
const response: AxiosResponse = await http.post(path, payload, { headers });
if (response.data.roleAssignmentResponse.length >= MAX_RECORDS) {
logger.warn('Case workers now returning MAX_RECORDS', response.data.roleAssignmentResponse.length);
}
return response;
}

export async function handleCaseWorkersForServicesPost(path: string, payloads: CaseworkerPayload [], req: EnhancedRequest):
Promise<ServiceCaseworkerData[]> {
const headers = setHeaders(req);
Expand Down
86 changes: 86 additions & 0 deletions api/workAllocation/caseWorkerUserDataCacheService.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import * as chai from 'chai';
import { expect } from 'chai';
import 'mocha';
import * as sinon from 'sinon';
import * as sinonChai from 'sinon-chai';
import { mockRes } from 'sinon-express-mock';
import { http } from '../lib/http';
import { fetchNewUserData, fetchRoleAssignmentsForNewUsers } from './caseWorkerUserDataCacheService';
import { StaffUserDetails } from './interfaces/staffUserDetails';

chai.use(sinonChai);

describe('Caseworker Cache Service', () => {
let sandbox: sinon.SinonSandbox;

beforeEach(() => {
sandbox = sinon.createSandbox();
});

afterEach(() => {
sandbox.restore();
});

describe('fetchNewUserData', () => {
it('should make a get request', async () => {
const mockStaffDetails = [{}, {}, {}];
const res = mockRes({ status: 200, data: mockStaffDetails });
sandbox.stub(http, 'get').resolves(res);
const data = await fetchNewUserData();
expect(data).to.equal(mockStaffDetails);
});
});

describe('fetchRoleAssignmentsForNewUsers', () => {
it('should make a post request', async () => {
const mockStaffUserDetails: StaffUserDetails[] = [
{
ccd_service_name: 'EMPLOYMENT',
staff_profile: {
id: '0bdd43aa-527b-40ac-9d68-d72bd45054f4',
first_name: 'Latest',
last_name: 'New',
region_id: '1',
user_type: 'CTSC',
suspended: 'false',
case_allocator: 'Y',
task_supervisor: 'Y',
staff_admin: 'N',
email_id: '[email protected]',
region: 'London',
base_location: [{
location_id: '637145',
location: 'Wrexham',
is_primary: true
}],
user_type_id: '1',
role: [],
skills: [],
work_area: [],
idam_roles: '',
created_time: new Date(),
last_updated_time: new Date()
}
}];
const mockRoleAssignments = [{
id: '123',
attributes: {},
roleCategory: 'ADMIN',
actorId: '0bdd43aa-527b-40ac-9d68-d72bd45054f4'
}];
const finalCaseworkers = [{
email: '[email protected]',
firstName: 'Latest',
idamId: '0bdd43aa-527b-40ac-9d68-d72bd45054f4',
lastName: 'New',
location: { id: '637145', locationName: 'Wrexham', services: undefined },
roleCategory: 'ADMIN',
service: 'EMPLOYMENT'
}];
const res = mockRes({ status: 200, data: { roleAssignmentResponse: mockRoleAssignments } });
sandbox.stub(http, 'post').resolves(res);
const data = await fetchRoleAssignmentsForNewUsers(mockStaffUserDetails);
expect(data).to.deep.equal(finalCaseworkers);
});
});
});
137 changes: 116 additions & 21 deletions api/workAllocation/caseWorkerUserDataCacheService.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { NextFunction } from 'express';
import { authenticator } from 'otplib';

import { baseCaseWorkerRefUrl, baseRoleAssignmentUrl } from './index';
import { ServiceUserDetailList, StaffUserDetails } from './interfaces/staffUserDetails';
import { StaffUserDetails } from './interfaces/staffUserDetails';
import { getConfigValue } from '../configuration';
import { IDAM_SECRET, MICROSERVICE, S2S_SECRET, SERVICES_IDAM_API_URL, SERVICES_IDAM_CLIENT_ID, SERVICE_S2S_PATH, STAFF_SUPPORTED_JURISDICTIONS, SYSTEM_USER_NAME, SYSTEM_USER_PASSWORD } from '../configuration/references';
import { http } from '../lib/http';
import { EnhancedRequest } from '../lib/models';
import { getStaffSupportedJurisdictionsList } from '../staffSupportedJurisdictions';
import { mapUsersToCaseworkers, prepareGetUsersUrl, prepareRoleApiRequest, prepareRoleApiUrl } from './util';
import { handlePostRoleAssignments, handleUsersGet } from './caseWorkerService';
import { NextFunction } from 'express';
import { handleNewUsersGet, handlePostRoleAssignments, handlePostRoleAssignmentsWithNewUsers, handleUsersGet } from './caseWorkerService';
import { Caseworker } from './interfaces/common';
import { FullUserDetailCache } from './fullUserDetailCache';

Expand All @@ -13,37 +18,62 @@ const TTL = 600;

let timestamp: Date;
let refreshRoles: boolean;
let initialServiceAuthToken: string;
let initialAuthToken: string;

let cachedUsers: ServiceUserDetailList[];
let cachedUsers: StaffUserDetails[];
let cachedUsersWithRoles: Caseworker[];

export async function fetchUserData(req: EnhancedRequest, next: NextFunction): Promise<StaffUserDetails[]> {
try {
if (hasTTLExpired() || !cachedUsers) {
if (hasTTLExpired() || (!cachedUsers || cachedUsers.length === 0)) {
// hasTTLExpired to determine whether roles require refreshing
// cachedUsers to ensure rerun if user restarts request early
refreshRoles = true;
const jurisdictions = getStaffSupportedJurisdictionsList();
cachedUsers = [];
for (const jurisdiction of jurisdictions) {
const getUsersPath: string = prepareGetUsersUrl(baseCaseWorkerRefUrl, jurisdiction);
const userResponse = await handleUsersGet(getUsersPath, req);
// TODO: Response will be cached eventually via API so caching below should be removed eventually
cachedUsers.push({ service: jurisdiction, staffUserList: userResponse });
}
const jurisdictions = getConfigValue(STAFF_SUPPORTED_JURISDICTIONS);
const getUsersPath: string = prepareGetUsersUrl(baseCaseWorkerRefUrl, jurisdictions);
const userResponse = await handleUsersGet(getUsersPath, req);
// TODO: Response will be cached eventually via API so caching below should be removed eventually
cachedUsers = userResponse;
} else {
refreshRoles = false;
}
return getCachedDataForServices();
return cachedUsers;
} catch (error) {
if (cachedUsers) {
return getCachedDataForServices();
return cachedUsers;
}
next(error);
}
}

export async function fetchRoleAssignments(cachedUserData: StaffUserDetails[], req: EnhancedRequest, next: NextFunction): Promise<any> {
// Note: May not be needed once API caching is in effect
export async function fetchNewUserData(): Promise<StaffUserDetails[]> {
try {
// first ensure that there is no immediate re-caching
timestamp = new Date();
refreshRoles = true;
await getAuthTokens();
// add the tokens to the request headers
const caseworkerHeaders = getRequestHeaders();
const jurisdictions = getConfigValue(STAFF_SUPPORTED_JURISDICTIONS);
cachedUsers = [];
const getUsersPath: string = prepareGetUsersUrl(baseCaseWorkerRefUrl, jurisdictions);
const userResponse = await handleNewUsersGet(getUsersPath, caseworkerHeaders);
cachedUsers = userResponse;
return cachedUsers;
} catch (error) {
if (cachedUsers) {
return cachedUsers;
}
// in case of error, reset the cache on application start up
timestamp = null;
refreshRoles = false;
}
}

export async function fetchRoleAssignments(cachedUserData: StaffUserDetails[], req: EnhancedRequest, next: NextFunction): Promise<Caseworker[]> {
// note: this has been done to cache role categories
// it is separate from the above as above caching will be done by backend
const fullUserDetailCache = FullUserDetailCache.getInstance();
Expand All @@ -68,6 +98,61 @@ export async function fetchRoleAssignments(cachedUserData: StaffUserDetails[], r
}
}

export async function fetchRoleAssignmentsForNewUsers(cachedUserData: StaffUserDetails[]): Promise<Caseworker[]> {
// note: this has been done to cache role categories
// it is separate from the above as above caching will be done by backend
const fullUserDetailCache = FullUserDetailCache.getInstance();
try {
if (refreshRoles && !cachedUsersWithRoles) {
// refreshRoles to determine whether roles require refreshing
// cachedUsersWithRoles to ensure rerun if user restarts request early
const roleApiPath: string = prepareRoleApiUrl(baseRoleAssignmentUrl);
const jurisdictions = getStaffSupportedJurisdictionsList();
const payload = prepareRoleApiRequest(jurisdictions);
const roleAssignmentHeaders = {
...getRequestHeaders(),
pageNumber: 0,
size: 10000
};
const { data } = await handlePostRoleAssignmentsWithNewUsers(roleApiPath, payload, roleAssignmentHeaders);
const roleAssignments = data.roleAssignmentResponse;
cachedUsersWithRoles = mapUsersToCaseworkers(cachedUserData, roleAssignments);
fullUserDetailCache.setUserDetails(cachedUsersWithRoles);
}
return fullUserDetailCache.getAllUserDetails();
} catch (error) {
if (fullUserDetailCache.getAllUserDetails()) {
return fullUserDetailCache.getAllUserDetails();
}
}
}

export async function getAuthTokens(): Promise<void> {
try {
// get config values for getting auth tokens
const microservice = getConfigValue(MICROSERVICE);
const s2sEndpointUrl = `${getConfigValue(SERVICE_S2S_PATH)}/lease`;
const s2sSecret = getConfigValue(S2S_SECRET).trim();
const oneTimePassword = authenticator.generate(s2sSecret);
const idamPath = getConfigValue(SERVICES_IDAM_API_URL);
const authURL = `${idamPath}/o/token`;
const axiosConfig = {
headers: { 'content-type': 'application/x-www-form-urlencoded' }
};
const authBody = getRequestBody();
// get auth token to use for pre-sign-in API calls
const serviceAuthResponse = await http.post(`${s2sEndpointUrl}`, {
microservice,
oneTimePassword
});
initialServiceAuthToken = serviceAuthResponse.data;
const authResponse = await http.post(authURL, authBody, axiosConfig);
initialAuthToken = authResponse.data.access_token;
} catch (error) {
console.log('Cannot get auth tokens');
}
}

export function hasTTLExpired(): boolean {
if (!timestamp) {
// started caching
Expand All @@ -86,10 +171,20 @@ export function timestampExists(): boolean {
return !!timestamp;
}

export function getCachedDataForServices(): StaffUserDetails[] {
let staffDetailsList = [];
for (const cachedServiceData of cachedUsers) {
staffDetailsList = [...staffDetailsList, ...cachedServiceData.staffUserList];
}
return staffDetailsList;
export function getRequestHeaders(): any {
return {
'content-type': 'application/json',
'serviceAuthorization': `Bearer ${initialServiceAuthToken}`,
'authorization': `Bearer ${initialAuthToken}`
};
}

// get the request information from config - similarly in node-lib
export const getRequestBody = (): string => {
const userName = getConfigValue(SYSTEM_USER_NAME);
const userPassword = encodeURIComponent(getConfigValue(SYSTEM_USER_PASSWORD));
const scope = 'openid profile roles manage-user create-user search-user';
const clientSecret = getConfigValue(IDAM_SECRET);
const idamClient = getConfigValue(SERVICES_IDAM_CLIENT_ID);
return `grant_type=password&password=${userPassword}&username=${userName}&scope=${scope}&client_id=${idamClient}&client_secret=${clientSecret}`;
};
Loading

0 comments on commit 7f4a8a8

Please sign in to comment.