Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PS - 666 A custom Text type field if storing PII data, the value should be stored as masked, and the actual value should be encrypted & PS - 939 FieldValue create and update API changes to handle isMultiselect and maxSelection attribute #207

Open
wants to merge 7 commits into
base: fieldMaskingNencryption
Choose a base branch
from
104 changes: 88 additions & 16 deletions src/adapters/postgres/fields-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,14 @@ import { ErrorResponse } from "src/error-response";
import { Fields } from "../../fields/entities/fields.entity";
import { FieldValues } from "../../fields/entities/fields-values.entity";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { SuccessResponse } from "src/success-response";
import { ErrorResponseTypeOrm } from "src/error-response-typeorm";
import { In, Repository } from "typeorm";
import APIResponse from "src/common/responses/response";
import { APIID } from "src/common/utils/api-id.config";
import { IServicelocatorfields } from "../fieldsservicelocator";
import { Response } from "express";
import { readFileSync } from "fs";
import path, { join } from 'path';
import { maskPiiData, encrypt } from "@utils/mask-data";

@Injectable()
export class PostgresFieldsService implements IServicelocatorfields {
Expand Down Expand Up @@ -113,17 +112,17 @@ export class PostgresFieldsService implements IServicelocatorfields {
APIResponse.error(
res,
apiId,
`Fields not found`,
`Fields not found`,
`Fields not found or already exist`,
`Fields not found or already exist`,
(HttpStatus.NOT_FOUND)
)

}
return APIResponse.success(res, apiId, result, (HttpStatus.CREATED), "Ok");
return APIResponse.success(res, apiId, result, (HttpStatus.CREATED), "Field Values created successfully");


} catch (error) {
const errorMessage = error.message || 'Internal server error';
const errorMessage = error.message || 'Something went wrong';
return APIResponse.error(res, apiId, "Internal Server Error", errorMessage, (HttpStatus.INTERNAL_SERVER_ERROR));

}
Expand Down Expand Up @@ -271,12 +270,23 @@ export class PostgresFieldsService implements IServicelocatorfields {

public async findAndSaveFieldValues(fieldValuesDto: FieldValuesDto) {

const field = await this.fieldsRepository.findOne({ where : {fieldId: fieldValuesDto.fieldId} })
const checkFieldValueExist = await this.fieldsValuesRepository.find({
where: { itemId: fieldValuesDto.itemId, fieldId: fieldValuesDto.fieldId },
});


const { isPII } = field?.fieldAttributes;

if(isPII && checkFieldValueExist.length === 0) {
const maskedFieldValue = maskPiiData("text",fieldValuesDto.value);
const encryptedFieldValue = encrypt(fieldValuesDto.value);
fieldValuesDto.value = maskedFieldValue;
fieldValuesDto["encryptedValue"] = encryptedFieldValue;
}

if (checkFieldValueExist.length == 0) {
let result = await this.fieldsValuesRepository.save(fieldValuesDto);
const { value, encryptedValue, ...result } = await this.fieldsValuesRepository.save(fieldValuesDto);
return result
}
return false;
Expand Down Expand Up @@ -381,13 +391,13 @@ export class PostgresFieldsService implements IServicelocatorfields {
label: result.name
}));
}
async findCustomFields(context: string, contextType?: string) {
async findCustomFields(context: string, contextType?: string[]) {
const condition: any = {
context: context,
};

if (contextType) {
condition.contextType = contextType;
if (contextType.length) {
condition.contextType = In(contextType);
}

let customFields = await this.fieldsRepository.find({
Expand All @@ -396,20 +406,28 @@ export class PostgresFieldsService implements IServicelocatorfields {
return customFields;
}

async findFieldValues(cohortId: string) {
let query = `SELECT C."cohortId",F."fieldId",F."value" FROM public."Cohort" C
async findFieldValues(contextId: string, context: string) {
let query = "";
if(context === "COHORT") {
query = `SELECT C."cohortId",F."fieldId",F."value" FROM public."Cohort" C
LEFT JOIN public."FieldValues" F
ON C."cohortId" = F."itemId" where C."cohortId" =$1`;
let result = await this.fieldsRepository.query(query, [cohortId]);
} else if (context === "USERS") {
query = `SELECT U."userId",F."fieldId",F."value" FROM public."Users" U
LEFT JOIN public."FieldValues" F
ON U."userId" = F."itemId" where U."userId" =$1`;
}

let result = await this.fieldsRepository.query(query, [contextId]);
return result;
}

async getFieldValuesData(id: string, context: string, contextType?: string) {
let customField;
let fieldsArr = [];
const [filledValues, customFields] = await Promise.all([
this.findFieldValues(id),
this.findCustomFields(context, contextType)
this.findFieldValues(id,context),
this.findCustomFields(context, [contextType])
]);

const filledValuesMap = new Map(filledValues.map(item => [item.fieldId, item.value]));
Expand All @@ -422,6 +440,8 @@ export class PostgresFieldsService implements IServicelocatorfields {
order: data?.ordering,
isRequired: data?.fieldAttributes?.isRequired,
isEditable: data?.fieldAttributes?.isEditable,
isMultiSelect: data.fieldAttributes ? data.fieldAttributes['isMultiSelect'] : '',
maxSelections: data.fieldAttributes ? data.fieldAttributes['maxSelections'] : '',
value: fieldValue || '',
options: data?.fieldParams?.['options'] || {},
type: data?.type || ''
Expand Down Expand Up @@ -451,4 +471,56 @@ export class PostgresFieldsService implements IServicelocatorfields {

return fieldsArr;
}

async getEditableFieldsAttributes() {
const getFieldsAttributesQuery = `
SELECT *
FROM "public"."Fields"
WHERE "fieldAttributes"->>'isEditable' = $1
`;
const getFieldsAttributesParams = ['true'];
return await this.fieldsRepository.query(getFieldsAttributesQuery, getFieldsAttributesParams);
}

async updateCustomFields(itemId, data, fieldAttributesAndParams) {
const fieldOptions = fieldAttributesAndParams?.fieldParams?.options.map(({value}) => value);
if (Array.isArray(data.value) === true) {
let dataArray = [];
for (let value of data.value) {
// check against options
if(!fieldOptions.includes(value)) {
return {
correctValue : false,
fieldName: fieldAttributesAndParams.fieldName,
valueIssue: "Value not a part of given options"
};
}
else {
dataArray.push(value);
}
}
if(fieldAttributesAndParams?.fieldAttributes?.isMultiSelect && parseInt(fieldAttributesAndParams?.fieldAttributes?.maxSelections) >= dataArray.length ) {
data.value = dataArray.join(',');
} else {
return {
correctValue : false,
fieldName : fieldAttributesAndParams.fieldName,
valueIssue: "Multiselect max selections exceeded"
};
}
}

let result : any = await this.fieldsValuesRepository.update({ itemId, fieldId: data.fieldId }, { value: data.value });
let newResult;
if (result.affected === 0) {
newResult = await this.fieldsValuesRepository.save({
itemId,
fieldId: data.fieldId,
value: data.value
});
}
Object.assign(result, newResult);
result["correctValue"] = true;
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ import { FieldsService } from "src/fields/fields.service";
import { TypeOrmModule } from "@nestjs/typeorm";
import { User } from "src/user/entities/user-entity";
import { CohortMembers } from "src/cohortMembers/entities/cohort-member.entity";
import { Field } from "src/user/entities/field-entity";
import { Fields } from "src/fields/entities/fields.entity";
import { FieldValues } from "src/user/entities/field-value-entities";
import { FieldValues } from "src/fields/entities/fields-values.entity";
import { AttendanceEntity } from "src/attendance/entities/attendance.entity";
import { PostgresAttendanceService } from "./attendance-adapter";
import { PostgresFieldsService } from "./fields-adapter";
Expand All @@ -17,12 +16,11 @@ import { Tenants } from "src/userTenantMapping/entities/tenant.entity";
import { UserRoleMapping } from "src/rbac/assign-role/entities/assign-role.entity";
import { Role } from "src/rbac/role/entities/role.entity";


@Module({
imports: [HttpModule,
TypeOrmModule.forFeature([
User,
Field,
Fields,
FieldValues,
CohortMembers,
AttendanceEntity,
Expand Down
Loading