refactor import users
MODIFY
src/scheduler/tasks/SynchronizeTagsImportTask.ts
src/scheduler/tasks/SynchronizeTagsImportTask.ts
@@ -32,7 +32,7 @@ export default class SynchronizeTagsImportTask extends SchedulerTask {
const foundTag = await TagStorage.getTag(tenant.id, importedTag.id);
if (foundTag) {
// Update it
- await TagStorage.saveImportedTag(tenant.id, { ...foundTag, ...importedTag });
+ await TagStorage.saveTag(tenant.id, { ...foundTag, ...importedTag });
// Remove the imported Tag
await TagStorage.deleteImportedTag(tenant.id, importedTag.id);
// Log

MODIFY
src/scheduler/tasks/SynchronizeUsersImportTask.ts
src/scheduler/tasks/SynchronizeUsersImportTask.ts
-import User, { UserRole, UserStatus } from '../../types/User';
+import User, { ImportedUser, UserRole, UserStatus } from '../../types/User';
import Constants from '../../utils/Constants';
+import { DataResult } from '../../types/DataResult';
import DbParams from '../../types/database/DbParams';
import { ImportStatus } from '../../types/GlobalType';
import { LockEntity } from '../../types/Locking';
@@ -20,16 +21,32 @@ export default class SynchronizeUsersImportTask extends SchedulerTask {
const synchronizeUsersImport = LockingManager.createExclusiveLock(tenant.id, LockEntity.USER, 'synchronize-users-import');
if (await LockingManager.acquire(synchronizeUsersImport)) {
try {
- const dbParams: DbParams = { limit: Constants.EXPORT_PAGE_SIZE, skip: 0 };
- let importedUsers = await UserStorage.getImportedUsers(tenant.id, { status: ImportStatus.READY }, dbParams);
- while (importedUsers && !Utils.isEmptyArray(importedUsers.result)) {
+ const dbParams: DbParams = { limit: Constants.IMPORT_PAGE_SIZE, skip: 0 };
+ let importedUsers: DataResult<ImportedUser>;
+ do {
+ // Get the imported users
+ importedUsers = await UserStorage.getImportedUsers(tenant.id, { status: ImportStatus.READY }, dbParams);
for (const importedUser of importedUsers.result) {
+ try {
+ // Existing Users
const foundUser = await UserStorage.getUserByEmail(tenant.id, importedUser.email);
if (foundUser) {
- importedUser.status = ImportStatus.ERROR;
- importedUser.errorDescription = 'Email already exists';
- await UserStorage.saveImportedUser(tenant.id, importedUser);
- } else {
+ // Update it
+ foundUser.name = importedUser.name;
+ foundUser.firstName = importedUser.firstName;
+ await UserStorage.saveUser(tenant.id, foundUser);
+ // Remove the imported User
+ await UserStorage.deleteImportedUser(tenant.id, importedUser.id);
+ // Log
+ await Logging.logDebug({
+ tenantID: tenant.id,
+ action: ServerAction.SYNCHRONIZE_USERS,
+ module: MODULE_NAME, method: 'processTenant',
+ message: `User with email: ${importedUser.email} have been updated successfully in Tenant ${Utils.buildTenantName(tenant)}`
+ });
+ continue;
+ }
+ // New User
const user: Partial<User> = {
firstName: importedUser.firstName,
name: importedUser.name,
@@ -40,39 +57,40 @@ export default class SynchronizeUsersImportTask extends SchedulerTask {
role: UserRole.BASIC,
status: UserStatus.ACTIVE,
createdBy: { id: importedUser.importedBy },
- createdOn: new Date(),
+ createdOn: importedUser.importedOn,
notificationsActive: true
};
- try {
+ // Save the new User
user.id = await UserStorage.saveUser(tenant.id, user);
// Role need to be set separately
await UserStorage.saveUserRole(tenant.id, user.id, UserRole.BASIC);
// Status need to be set separately
await UserStorage.saveUserStatus(tenant.id, user.id, UserStatus.ACTIVE);
+ // Remove the imported User
await UserStorage.deleteImportedUser(tenant.id, importedUser.id);
+ // Log
await Logging.logDebug({
tenantID: tenant.id,
action: ServerAction.SYNCHRONIZE_USERS,
- module: MODULE_NAME, method: 'SYNCHRONIZE_USERS',
+ module: MODULE_NAME, method: 'processTenant',
message: `User with email: ${importedUser.email} have been created in Tenant ${Utils.buildTenantName(tenant)}`
});
} catch (error) {
importedUser.status = ImportStatus.ERROR;
importedUser.errorDescription = error.message;
+ // Update it
await UserStorage.saveImportedUser(tenant.id, importedUser);
// Error
await Logging.logError({
tenantID: tenant.id,
action: ServerAction.SYNCHRONIZE_USERS,
- module: MODULE_NAME, method: 'createUser',
+ module: MODULE_NAME, method: 'processTenant',
message: `An error occurred when importing user with email: ${importedUser.email}`,
- detailedMessages: { error }
+ detailedMessages: { error: error.message, stack: error.stack }
});
}
}
- }
- importedUsers = await UserStorage.getImportedUsers(tenant.id, { status: ImportStatus.READY }, dbParams);
- }
+ } while (!Utils.isEmptyArray(importedUsers?.result));
} catch (error) {
// Log error
await Logging.logActionExceptionMessage(tenant.id, ServerAction.SYNCHRONIZE_USERS, error);

MODIFY
src/server/rest/v1/service/TagService.ts
src/server/rest/v1/service/TagService.ts
import { Action, Entity } from '../../../../types/Authorization';
+import { ActionsResponse, ImportStatus } from '../../../../types/GlobalType';
import { HTTPAuthError, HTTPError } from '../../../../types/HTTPError';
import { NextFunction, Request, Response } from 'express';
import { OCPITokenType, OCPITokenWhitelist } from '../../../../types/ocpi/OCPIToken';
import Tag, { ImportedTag } from '../../../../types/Tag';
-import { ActionsResponse } from '../../../../types/GlobalType';
import AppAuthError from '../../../../exception/AppAuthError';
import AppError from '../../../../exception/AppError';
import AuthorizationService from './AuthorizationService';
@@ -555,7 +555,7 @@ export default class TagService {
// Log
void Logging.logActionsResponse(
req.user.tenantID, action,
- MODULE_NAME, 'deleteTransactions', result,
+ MODULE_NAME, 'handleImportTags', result,
'{{inSuccess}} Tag(s) were successfully uploaded and ready for asynchronous import',
'{{inError}} Tag(s) failed to be uploaded',
'{{inSuccess}} Tag(s) were successfully uploaded and ready for asynchronous import and {{inError}} failed to be uploaded',
@@ -677,8 +677,11 @@ export default class TagService {
id: importedTag.id.toUpperCase(),
description: importedTag.description ? importedTag.description : `Badge ID '${importedTag.id}'`,
};
- // Validate Tad data
+ // Validate Tag data
TagValidator.getInstance().validateImportedTagCreation(newImportedTag);
+ newImportedTag.importedBy = importedTag.importedBy;
+ newImportedTag.importedOn = importedTag.importedOn;
+ newImportedTag.status = ImportStatus.READY;
// Save it for import
await TagStorage.saveImportedTag(req.user.tenantID, newImportedTag);
return true;

MODIFY
src/server/rest/v1/service/UserService.ts
src/server/rest/v1/service/UserService.ts
import { Action, Entity } from '../../../../types/Authorization';
+import { ActionsResponse, ImportStatus } from '../../../../types/GlobalType';
import { HTTPAuthError, HTTPError } from '../../../../types/HTTPError';
import { NextFunction, Request, Response } from 'express';
import { OCPITokenType, OCPITokenWhitelist } from '../../../../types/ocpi/OCPIToken';
-import User, { ImportedUser, UserStatus } from '../../../../types/User';
+import User, { ImportedUser, UserRequiredImportProperties, UserStatus } from '../../../../types/User';
import Address from '../../../../types/Address';
import AppAuthError from '../../../../exception/AppAuthError';
@@ -19,7 +20,7 @@ import Cypher from '../../../../utils/Cypher';
import { DataResult } from '../../../../types/DataResult';
import EmspOCPIClient from '../../../../client/ocpi/EmspOCPIClient';
import I18nManager from '../../../../utils/I18nManager';
-import JSONStream from 'JSONStream';
+import JSONStream from 'jsonstream';
import Logging from '../../../../utils/Logging';
import NotificationHandler from '../../../../notification/NotificationHandler';
import OCPIClientFactory from '../../../../client/ocpi/OCPIClientFactory';
@@ -860,6 +861,13 @@ export default class UserService {
module: MODULE_NAME, method: 'handleImportUser'
});
}
+ // Default values for User import
+ const importedBy = req.user.id;
+ const importedOn = new Date();
+ const result: ActionsResponse = {
+ inSuccess: 0,
+ inError: 0
+ };
const busboy = new Busboy({ headers: req.headers });
req.pipe(busboy);
busboy.on('file', function(fieldname, file, filename, encoding, mimetype) {
@@ -869,15 +877,36 @@ export default class UserService {
delimiter: ['\t'],
quote: 'off'
});
- void converter.subscribe(async (user) => {
- await UserService.importUser(action, req, user);
+ void converter.subscribe(async (user: ImportedUser) => {
+ if (!result.inSuccess && !result.inError) {
+ if (!UserRequiredImportProperties.every((property) => Object.keys(user).includes(property))) {
+ void Logging.logError({
+ tenantID: req.user.tenantID,
+ module: MODULE_NAME, method: 'handleImportUsers',
+ action: action,
+ user: req.user.id,
+ message: `Invalid Csv file '${filename}', all properties: '${UserRequiredImportProperties.join(', ')}' are required`,
+ });
+ res.writeHead(HTTPError.INVALID_FILE_FORMAT);
+ res.end();
+ }
+ }
+ // Set default value
+ user.importedBy = importedBy;
+ user.importedOn = importedOn;
+ // Import
+ if (await UserService.importUser(action, req, user)) {
+ result.inSuccess++;
+ } else {
+ result.inError++;
+ }
}, (error) => {
void Logging.logError({
tenantID: req.user.tenantID,
- module: MODULE_NAME, method: 'handleUploadUsersFile',
+ module: MODULE_NAME, method: 'handleImportUsers',
action: action,
user: req.user.id,
- message: 'Invalid csv file',
+ message: `Invalid Csv file '${filename}'`,
detailedMessages: { error: error.message, stack: error.stack }
});
res.writeHead(HTTPError.INVALID_FILE_FORMAT);
@@ -886,16 +915,25 @@ export default class UserService {
void file.pipe(converter);
} else if (mimetype === 'application/json') {
const parser = JSONStream.parse('users.*');
- parser.on('data', (user) => {
- void UserService.importUser(action, req, user);
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
+ parser.on('data', async (user: ImportedUser) => {
+ // Set default value
+ user.importedBy = importedBy;
+ user.importedOn = importedOn;
+ // Import
+ if (await UserService.importUser(action, req, user)) {
+ result.inSuccess++;
+ } else {
+ result.inError++;
+ }
});
parser.on('error', function(error) {
void Logging.logError({
tenantID: req.user.tenantID,
- module: MODULE_NAME, method: 'handleUploadUsersFile',
+ module: MODULE_NAME, method: 'handleImportUsers',
action: action,
user: req.user.id,
- message: 'Invalid json file',
+ message: `Invalid Json file '${filename}'`,
detailedMessages: { error: error.message, stack: error.stack }
});
res.writeHead(HTTPError.INVALID_FILE_FORMAT);
@@ -905,22 +943,31 @@ export default class UserService {
} else {
void Logging.logError({
tenantID: req.user.tenantID,
- module: MODULE_NAME, method: 'handleUploadUsersFile',
+ module: MODULE_NAME, method: 'handleImportUsers',
action: action,
user: req.user.id,
- message: 'Invalid file format'
+ message: `Invalid file format '${mimetype}'`
});
res.writeHead(HTTPError.INVALID_FILE_FORMAT);
res.end();
}
});
busboy.on('finish', function() {
+ // Log
+ void Logging.logActionsResponse(
+ req.user.tenantID, action,
+ MODULE_NAME, 'handleImportUsers', result,
+ '{{inSuccess}} User(s) were successfully uploaded and ready for asynchronous import',
+ '{{inError}} User(s) failed to be uploaded',
+ '{{inSuccess}} User(s) were successfully uploaded and ready for asynchronous import and {{inError}} failed to be uploaded',
+ 'No User have been uploaded', req.user
+ );
void Logging.logInfo({
tenantID: req.user.tenantID,
action: action,
- module: MODULE_NAME, method: 'handleUploadUsersFile',
+ module: MODULE_NAME, method: 'handleImportUsers',
user: req.user,
- message: 'File successfully uploaded',
+ message: 'File has been successfully uploaded in database and ready for asynchronous import',
});
res.end();
next();
@@ -1274,24 +1321,30 @@ export default class UserService {
return users;
}
- private static async importUser(action: ServerAction, req: Request, user: any): Promise<void> {
+ private static async importUser(action: ServerAction, req: Request, importedUser: ImportedUser): Promise<boolean> {
try {
- const newUploadedUser: ImportedUser = {
- name: user.Name,
- firstName: user.FirstName,
- email: user.Email,
+ const newImportedUser: ImportedUser = {
+ name: importedUser.name.toUpperCase(),
+ firstName: importedUser.firstName,
+ email: importedUser.email,
};
- UserValidator.getInstance().validateImportedUserCreation(newUploadedUser);
- newUploadedUser.importedBy = req.user.id;
- await UserStorage.saveImportedUser(req.user.tenantID, newUploadedUser);
+ // Validate User data
+ UserValidator.getInstance().validateImportedUserCreation(newImportedUser);
+ newImportedUser.importedBy = importedUser.importedBy;
+ newImportedUser.importedOn = importedUser.importedOn;
+ newImportedUser.status = ImportStatus.READY;
+ // Save it for import
+ await UserStorage.saveImportedUser(req.user.tenantID, newImportedUser);
+ return true;
} catch (error) {
await Logging.logError({
tenantID: req.user.tenantID,
- module: MODULE_NAME, method: 'handleUploadUsersFile',
+ module: MODULE_NAME, method: 'importUser',
action: action,
message: 'User cannot be imported',
- detailedMessages: { error: error.message, stack: error.stack }
+ detailedMessages: { user: importedUser, error: error.message, stack: error.stack }
});
+ return false;
}
}
}

MODIFY
src/storage/mongodb/UserStorage.ts
src/storage/mongodb/UserStorage.ts
@@ -282,7 +282,7 @@ export default class UserStorage {
name: importedUserToSave.name,
status: importedUserToSave.status,
errorDescription: importedUserToSave.errorDescription,
- importedOn:importedUserToSave.importedOn ? importedUserToSave.importedOn : new Date(),
+ importedOn: Utils.convertToDate(importedUserToSave.importedOn),
importedBy: Utils.convertToObjectID(importedUserToSave.importedBy)
};
await global.database.getCollection<any>(tenantID, 'usersImport').findOneAndUpdate(
@@ -303,7 +303,7 @@ export default class UserStorage {
// Delete
await global.database.getCollection<any>(tenantID, 'usersImport').deleteOne(
{
- '_id': importedUserID,
+ '_id': Utils.convertToObjectID(importedUserID),
});
// Debug
await Logging.traceEnd(tenantID, MODULE_NAME, 'deleteImportedUser', uniqueTimerID, { id: importedUserID });

MODIFY
src/types/Tag.ts
src/types/Tag.ts
@@ -24,3 +24,6 @@ export interface ImportedTag {
status?: ImportStatus;
errorDescription?: string;
}
+
+export const TagRequiredImportProperties = ['id'];
+

MODIFY
src/types/User.ts
src/types/User.ts
@@ -83,3 +83,5 @@ export enum UserRole {
DEMO = 'D',
}
+export const UserRequiredImportProperties = ['email', 'firstName', 'name'];
+

refactor import users