Skip to content

Commit 20c5f01

Browse files
authored
Merge pull request #33 from vishnuvinay89/all-saas-0.2-dev
TaskId #236323 task: Bulk Upload User API
2 parents e4608c8 + 631d127 commit 20c5f01

File tree

3 files changed

+157
-0
lines changed

3 files changed

+157
-0
lines changed

src/adapters/postgres/user-adapter.ts

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { JwtUtil } from '@utils/jwt-token';
3535
import { ConfigService } from '@nestjs/config';
3636
import { formatTime } from '@utils/formatTimeConversion';
3737
import { API_RESPONSES } from '@utils/response.messages';
38+
import { parse } from 'csv-parse/sync';
3839

3940

4041
@Injectable()
@@ -952,6 +953,137 @@ export class PostgresUserService implements IServicelocator {
952953
return APIResponse.error(response, apiId, "Internal Server Error", errorMessage, HttpStatus.INTERNAL_SERVER_ERROR);
953954
}
954955
}
956+
async bulkCreateUser(request: any, userCreateDto: UserCreateDto, response: Response) {
957+
// check and validate all fields
958+
let validatedRoles = await this.validateRequestBody(userCreateDto)
959+
960+
if (
961+
validatedRoles &&
962+
Array.isArray(validatedRoles) &&
963+
validatedRoles.length > 0
964+
) {
965+
const errorMessage = validatedRoles.join("; ");
966+
throw new Error(errorMessage);
967+
} else if (validatedRoles) {
968+
throw new Error("Validation Error");
969+
}
970+
971+
userCreateDto.username = userCreateDto.username.toLocaleLowerCase();
972+
const userSchema = new UserCreateDto(userCreateDto);
973+
974+
let errKeycloak = "";
975+
let resKeycloak = "";
976+
977+
const keycloakResponse = await getKeycloakAdminToken();
978+
const token = keycloakResponse.data.access_token;
979+
let checkUserinKeyCloakandDb = await this.checkUserinKeyCloakandDb(userCreateDto)
980+
// let checkUserinDb = await this.checkUserinKeyCloakandDb(userCreateDto.username);
981+
if (checkUserinKeyCloakandDb) {
982+
throw new Error('User Already Exist')
983+
}
984+
resKeycloak = await createUserInKeyCloak(userSchema, token).catch(
985+
(error) => {
986+
errKeycloak = error.response?.data.errorMessage;
987+
throw new Error(errKeycloak);
988+
}
989+
);
990+
991+
userCreateDto.userId = resKeycloak;
992+
993+
let result = await this.createUserInDatabase(request, userCreateDto, response);
994+
}
995+
996+
async bulkUploadUsers(request: any,csvFile :any, response: Response) {
997+
const apiId = APIID.USER_CREATE_BULK;
998+
const decoded: any = jwt_decode(request.headers.authorization);
999+
1000+
try {
1001+
if (csvFile == undefined) {
1002+
return APIResponse.error(
1003+
response,
1004+
apiId,
1005+
"BAD_REQUEST",
1006+
"CSV file is required",
1007+
HttpStatus.BAD_REQUEST
1008+
);
1009+
}
1010+
const csvData = csvFile.buffer.toString('utf8');
1011+
1012+
// Parse CSV data
1013+
const records = parse(csvData, {
1014+
columns: true,
1015+
skip_empty_lines: true,
1016+
trim: true
1017+
});
1018+
1019+
const results = {
1020+
success: 0,
1021+
failed: 0,
1022+
failedDetails: []
1023+
};
1024+
1025+
// Process each user record
1026+
for (const record of records) {
1027+
// Validate required fields
1028+
if (!record.username || !record.name || !record.password) {
1029+
results.failed++;
1030+
results.failedDetails.push({
1031+
record: record.username || 'Unknown',
1032+
error: 'Missing required fields (username, name, or password)'
1033+
});
1034+
continue;
1035+
}
1036+
1037+
// Create UserCreateDto from CSV record
1038+
let userCreateDto =
1039+
{
1040+
username :record.username.toLocaleLowerCase(),
1041+
name : record.name,
1042+
password : record.password,
1043+
createdBy : decoded.sub,
1044+
updatedBy : decoded.sub,
1045+
tenantCohortRoleMapping : [
1046+
{
1047+
roleId: request.body.roleId,
1048+
tenantId: request.body.tenantId,
1049+
cohortId: [request.body.cohortId]
1050+
}
1051+
]
1052+
}
1053+
const userSchema = new UserCreateDto(userCreateDto);
1054+
try {
1055+
await this.bulkCreateUser(request, userSchema, response);
1056+
results.success++;
1057+
}
1058+
catch (error) {
1059+
results.failed++;
1060+
results.failedDetails.push({
1061+
record: record.username || 'Unknown',
1062+
error : error.message
1063+
});
1064+
}
1065+
}
1066+
1067+
// Return overall results
1068+
return APIResponse.success(
1069+
response,
1070+
apiId,
1071+
results,
1072+
HttpStatus.OK,
1073+
`Bulk upload completed. Success: ${results.success}, Failed: ${results.failed}`
1074+
);
1075+
1076+
} catch (e) {
1077+
const errorMessage = e.message || 'Internal server error';
1078+
return APIResponse.error(
1079+
response,
1080+
apiId,
1081+
"Internal Server Error",
1082+
errorMessage,
1083+
HttpStatus.INTERNAL_SERVER_ERROR
1084+
);
1085+
}
1086+
}
9551087

9561088

9571089
async validateRequestBody(userCreateDto) {

src/common/utils/api-id.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export const APIID = {
22
USER_GET: "api.user.get",
33
USER_CREATE: "api.user.create",
4+
USER_CREATE_BULK : "api.user.bulkCreate",
45
USER_UPDATE: "api.user.update",
56
USER_LIST: "api.user.list",
67
USER_RESET_PASSWORD: "api.user.resetPassword",

src/user/user.controller.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import {
1616
Delete,
1717
ParseUUIDPipe,
1818
UseFilters,
19+
UploadedFile,
20+
UseInterceptors
1921
} from "@nestjs/common";
2022

2123
import {
@@ -31,6 +33,7 @@ import {
3133
ApiInternalServerErrorResponse,
3234
ApiBadRequestResponse,
3335
ApiConflictResponse,
36+
ApiConsumes,
3437
} from "@nestjs/swagger";
3538

3639
import { UserSearchDto } from "./dto/user-search.dto";
@@ -43,6 +46,7 @@ import { AllExceptionsFilter } from "src/common/filters/exception.filter";
4346
import { APIID } from "src/common/utils/api-id.config";
4447
import { ForgotPasswordDto, ResetUserPasswordDto, SendPasswordResetLinkDto,learnerForgotPasswordDto } from "./dto/passwordReset.dto";
4548
import { PostgresUserService } from "src/adapters/postgres/user-adapter";
49+
import { FileInterceptor } from '@nestjs/platform-express';
4650

4751
export interface UserData {
4852
context: string;
@@ -115,6 +119,26 @@ export class UserController {
115119

116120
}
117121

122+
// bulk create of users
123+
@UseInterceptors(FileInterceptor('csvFile'))
124+
@UseFilters(new AllExceptionsFilter(APIID.USER_CREATE_BULK))
125+
@Post("/bulk-create")
126+
@UseGuards(JwtAuthGuard)
127+
@ApiBasicAuth("access-token")
128+
@ApiConsumes('multipart/form-data')
129+
@ApiCreatedResponse({ description: "Users have been created successfully." })
130+
@ApiForbiddenResponse({ description: "Forbidden" })
131+
@ApiHeader({
132+
name: "tenantid",
133+
})
134+
public async bulkCreateUsers(
135+
@Req() request: Request,
136+
@UploadedFile() csvFile: Express.Multer.File,
137+
@Res() response: Response
138+
) {
139+
return await this.postgresUserService.bulkUploadUsers(request, csvFile, response)
140+
}
141+
118142
@UseFilters(new AllExceptionsFilter(APIID.USER_UPDATE))
119143
@Patch("update/:userid")
120144
@UsePipes(new ValidationPipe())

0 commit comments

Comments
 (0)