Ensure issuer has a valid bool value + Remove transactionCount for perfs issue + Handle delete of tag with transactions
MODIFY
src/server/rest/service/UserService.ts
src/server/rest/service/UserService.ts
@@ -1028,11 +1028,12 @@ export default class UserService {
{
search: filteredRequest.Search,
userIDs: filteredRequest.UserID ? filteredRequest.UserID.split('|') : null,
+ issuer: filteredRequest.Issuer,
withUser: true,
},
{ limit: filteredRequest.Limit, skip: filteredRequest.Skip, sort: filteredRequest.Sort, onlyRecordCount: filteredRequest.OnlyRecordCount },
[ 'id', 'userID', 'active', 'ocpiToken', 'description', 'issuer', 'user.id', 'user.name', 'user.firstName', 'user.email',
- 'transactionsCount', 'createdOn', 'createdBy', 'lastChangedOn', 'lastChangedBy' ],
+ 'createdOn', 'createdBy', 'lastChangedOn', 'lastChangedBy' ],
);
// Filter
UserSecurity.filterTagsResponse(tags, req.user);
@@ -1056,7 +1057,7 @@ export default class UserService {
});
}
// Get Tag
- const tag = await UserStorage.getTag(req.user.tenantID, tagId);
+ const tag = await UserStorage.getTag(req.user.tenantID, tagId, { withNbrTransactions: true });
UtilsService.assertObjectExists(action, tag, `Tag ID '${tagId}' does not exist`,
MODULE_NAME, 'handleDeleteTag', req.user);
// Only current organizations tags can be deleted
@@ -1074,8 +1075,8 @@ export default class UserService {
if (tag.transactionsCount > 0) {
throw new AppError({
source: Constants.CENTRAL_SERVER,
- errorCode: HTTPError.GENERAL_ERROR,
- message: 'Tags with transactions cannot be deleted but only deactivated',
+ errorCode: HTTPError.TAG_HAS_TRANSACTIONS,
+ message: `Cannot delete Tag ID '${tag.id}' which has '${tag.transactionsCount}' transaction(s)`,
module: MODULE_NAME, method: 'handleDeleteTag',
user: req.user,
action: action
@@ -1136,6 +1137,33 @@ export default class UserService {
const filteredRequest = UserSecurity.filterTagRequest(req.body);
// Check
await Utils.checkIfUserTagIsValid(filteredRequest, req);
+ // Check Tag
+ const tag = await UserStorage.getTag(req.user.tenantID, filteredRequest.id);
+ if (tag) {
+ throw new AppError({
+ source: Constants.CENTRAL_SERVER,
+ errorCode: HTTPError.TAG_ALREADY_EXIST_ERROR,
+ message: `Tag with ID '${filteredRequest.id}' already exists`,
+ module: MODULE_NAME, method: 'handleCreateTag',
+ user: req.user,
+ action: action
+ });
+ }
+ // Check User
+ const user = await UserStorage.getUser(req.user.tenantID, filteredRequest.userID);
+ UtilsService.assertObjectExists(action, user, `User ID '${filteredRequest.userID}' does not exist`,
+ MODULE_NAME, 'handleCreateTag', req.user);
+ // Only current organization User can be assigned to Tag
+ if (!user.issuer) {
+ throw new AppError({
+ source: Constants.CENTRAL_SERVER,
+ errorCode: HTTPError.GENERAL_ERROR,
+ message: `User ID '${user.id}' not issued by the organization cannot be assigned to Tag ID '${tag.id}'`,
+ module: MODULE_NAME, method: 'handleCreateTag',
+ user: req.user,
+ action: action
+ });
+ }
// Create
const newTag: Tag = {
id: filteredRequest.id,
@@ -1169,7 +1197,7 @@ export default class UserService {
Logging.logError({
tenantID: req.user.tenantID,
action: action,
- module: MODULE_NAME, method: 'handleUpdateTag',
+ module: MODULE_NAME, method: 'handleCreateTag',
message: `Unable to synchronize tokens of user ${filteredRequest.userID} with IOP`,
detailedMessages: { error: error.message, stack: error.stack }
});
@@ -1177,9 +1205,10 @@ export default class UserService {
}
Logging.logSecurityInfo({
tenantID: req.user.tenantID,
- user: req.user, module: MODULE_NAME, method: 'handleCreateTag',
- message: `Tag with ID '${newTag.id}'has been created successfully`,
action: action,
+ user: req.user, actionOnUser: user,
+ module: MODULE_NAME, method: 'handleCreateTag',
+ message: `Tag with ID '${newTag.id}'has been created successfully`,
detailedMessages: { tag: newTag }
});
res.json(Object.assign({ id: newTag.id }, Constants.REST_RESPONSE_SUCCESS));
@@ -1201,26 +1230,37 @@ export default class UserService {
// Check
await Utils.checkIfUserTagIsValid(filteredRequest, req);
// Get Tag
- const tag = await UserStorage.getTag(req.user.tenantID, filteredRequest.id);
+ const tag = await UserStorage.getTag(req.user.tenantID, filteredRequest.id, { withNbrTransactions: true });
UtilsService.assertObjectExists(action, tag, `Tag ID '${filteredRequest.id}' does not exist`,
MODULE_NAME, 'handleUpdateTag', req.user);
// Get User
const user = await UserStorage.getUser(req.user.tenantID, filteredRequest.userID);
- UtilsService.assertObjectExists(action, tag, `User ID '${filteredRequest.userID}' does not exist`,
+ UtilsService.assertObjectExists(action, user, `User ID '${filteredRequest.userID}' does not exist`,
MODULE_NAME, 'handleUpdateTag', req.user);
- // Only current organizations tags can be updated
- if (!tag.issuer) {
+ // Only current organization User can be assigned to Tag
+ if (!user.issuer) {
throw new AppError({
source: Constants.CENTRAL_SERVER,
errorCode: HTTPError.GENERAL_ERROR,
- message: `Tag ID '${tag.id}' not issued by the organization cannot be deleted`,
- module: MODULE_NAME, method: 'handleDeleteTag',
- user: req.user, actionOnUser: user,
+ message: `User ID '${user.id}' not issued by the organization cannot be assigned to Tag ID '${tag.id}'`,
+ module: MODULE_NAME, method: 'handleUpdateTag',
+ user: req.user,
action: action
});
}
// Check User reassignment
if (tag.userID !== filteredRequest.userID) {
+ // Has transactions
+ if (tag.transactionsCount > 0) {
+ throw new AppError({
+ source: Constants.CENTRAL_SERVER,
+ errorCode: HTTPError.TAG_HAS_TRANSACTIONS,
+ message: `Cannot change the User of the Tag ID '${tag.id}' which has '${tag.transactionsCount}' transaction(s)`,
+ module: MODULE_NAME, method: 'handleUpdateTag',
+ user: req.user,
+ action: action
+ });
+ }
// Recompute the former User's Hash (trigger unlog)
await SessionHashService.rebuildUserHashID(req.user.tenantID, tag.userID);
}

MODIFY
src/server/rest/service/security/UserSecurity.ts
src/server/rest/service/security/UserSecurity.ts
@@ -92,6 +92,7 @@ export default class UserSecurity {
const filteredRequest: HttpTagsRequest = {
Search: sanitize(request.Search),
UserID: sanitize(request.UserID),
+ Issuer: UtilsSecurity.filterBoolean(request.Issuer),
} as HttpTagsRequest;
UtilsSecurity.filterSkipAndLimit(request, filteredRequest);
UtilsSecurity.filterSort(request, filteredRequest);

MODIFY
src/storage/mongodb/ChargingStationStorage.ts
src/storage/mongodb/ChargingStationStorage.ts
@@ -165,7 +165,7 @@ export default class ChargingStationStorage {
filters.lastHeartBeat = { $lte: params.offlineSince };
}
// Issuer
- if (Utils.objectHasProperty(params, 'issuer')) {
+ if (Utils.objectHasProperty(params, 'issuer') && Utils.isBooleanValue(params.issuer)) {
filters.issuer = params.issuer;
}
// Add Charging Station inactive flag

MODIFY
src/storage/mongodb/CompanyStorage.ts
src/storage/mongodb/CompanyStorage.ts
@@ -133,7 +133,7 @@ export default class CompanyStorage {
$in: params.companyIDs.map((companyID) => Utils.convertToObjectID(companyID))
};
}
- if (Utils.objectHasProperty(params, 'issuer')) {
+ if (Utils.objectHasProperty(params, 'issuer') && Utils.isBooleanValue(params.issuer)) {
aggregation.push({
$match: {
'issuer': params.issuer

MODIFY
src/storage/mongodb/SiteAreaStorage.ts
src/storage/mongodb/SiteAreaStorage.ts
@@ -200,7 +200,7 @@ export default class SiteAreaStorage {
$in: params.siteIDs.map((siteID) => Utils.convertToObjectID(siteID))
};
}
- if (Utils.objectHasProperty(params, 'issuer')) {
+ if (Utils.objectHasProperty(params, 'issuer') && Utils.isBooleanValue(params.issuer)) {
filters.issuer = params.issuer;
}
if (params.smartCharging === true || params.smartCharging === false) {

MODIFY
src/storage/mongodb/SiteStorage.ts
src/storage/mongodb/SiteStorage.ts
@@ -362,7 +362,7 @@ export default class SiteStorage {
};
}
// Issuer
- if (Utils.objectHasProperty(params, 'issuer')) {
+ if (Utils.objectHasProperty(params, 'issuer') && Utils.isBooleanValue(params.issuer)) {
filters.issuer = params.issuer;
}
// Public Site

MODIFY
src/storage/mongodb/TransactionStorage.ts
src/storage/mongodb/TransactionStorage.ts
@@ -310,7 +310,7 @@ export default class TransactionStorage {
};
}
// Issuer
- if (Utils.objectHasProperty(params, 'issuer')) {
+ if (Utils.objectHasProperty(params, 'issuer') && Utils.isBooleanValue(params.issuer)) {
filters.issuer = params.issuer;
}
// Charge Box
@@ -747,7 +747,7 @@ export default class TransactionStorage {
];
}
// Issuer
- if (Utils.objectHasProperty(params, 'issuer')) {
+ if (Utils.objectHasProperty(params, 'issuer') && Utils.isBooleanValue(params.issuer)) {
match.issuer = params.issuer;
}
// User / Site Admin

MODIFY
src/storage/mongodb/UserStorage.ts
src/storage/mongodb/UserStorage.ts
@@ -95,7 +95,10 @@ export default class UserStorage {
// Debug
const uniqueTimerID = Logging.traceStart(MODULE_NAME, 'getUserByTagId');
// Get user
- const user = await UserStorage.getUsers(tenantID, { tagIDs: [tagID] }, Constants.DB_PARAMS_SINGLE_RECORD);
+ const user = await UserStorage.getUsers(tenantID, {
+ tagIDs: [tagID],
+ withTag: true
+ }, Constants.DB_PARAMS_SINGLE_RECORD);
// Debug
Logging.traceEnd(MODULE_NAME, 'getUserByTagId', uniqueTimerID, { tagID });
return user.count === 1 ? user.result[0] : null;
@@ -109,7 +112,10 @@ export default class UserStorage {
// Debug
const uniqueTimerID = Logging.traceStart(MODULE_NAME, 'getUserByEmail');
// Get user
- const user = await UserStorage.getUsers(tenantID, { email: email }, Constants.DB_PARAMS_SINGLE_RECORD);
+ const user = await UserStorage.getUsers(tenantID, {
+ email: email,
+ withTag: true
+ }, Constants.DB_PARAMS_SINGLE_RECORD);
// Debug
Logging.traceEnd(MODULE_NAME, 'getUserByEmail', uniqueTimerID, { email });
return user.count === 1 ? user.result[0] : null;
@@ -119,7 +125,9 @@ export default class UserStorage {
// Debug
const uniqueTimerID = Logging.traceStart(MODULE_NAME, 'getUserByPasswordResetHash');
// Get user
- const user = await UserStorage.getUsers(tenantID, { passwordResetHash: passwordResetHash }, Constants.DB_PARAMS_SINGLE_RECORD);
+ const user = await UserStorage.getUsers(tenantID, {
+ passwordResetHash: passwordResetHash
+ }, Constants.DB_PARAMS_SINGLE_RECORD);
// Debug
Logging.traceEnd(MODULE_NAME, 'getUserByPasswordResetHash', uniqueTimerID, { passwordResetHash });
return user.count === 1 ? user.result[0] : null;
@@ -144,7 +152,9 @@ export default class UserStorage {
// Debug
const uniqueTimerID = Logging.traceStart(MODULE_NAME, 'getUserByBillingID');
// Get user
- const user = await UserStorage.getUsers(tenantID, { billingUserID: billingID }, Constants.DB_PARAMS_SINGLE_RECORD);
+ const user = await UserStorage.getUsers(tenantID, {
+ billingUserID: billingID
+ }, Constants.DB_PARAMS_SINGLE_RECORD);
// Debug
Logging.traceEnd(MODULE_NAME, 'getUserByBillingID', uniqueTimerID, { customerID: billingID });
return user.count === 1 ? user.result[0] : null;
@@ -552,7 +562,7 @@ export default class UserStorage {
filters._id = { $in: params.userIDs.map((userID) => Utils.convertToObjectID(userID)) };
}
// Issuer
- if (Utils.objectHasProperty(params, 'issuer')) {
+ if (Utils.objectHasProperty(params, 'issuer') && Utils.isBooleanValue(params.issuer)) {
filters.issuer = params.issuer;
}
// Exclude Users
@@ -683,8 +693,8 @@ export default class UserStorage {
aggregation.push({
$limit: dbParams.limit
});
- // Add Number of Session per Badge if one user only is requested
- if (params.withTag && dbParams.limit === 1) {
+ // Add Number of Session per Badge
+ if (params.withTag) {
// Transactions per Tag
DatabaseUtils.pushArrayLookupInAggregation('tags',
DatabaseUtils.pushTransactionsLookupInAggregation.bind(this), {
@@ -718,13 +728,14 @@ export default class UserStorage {
}
public static async getTag(tenantID: string, id: string,
- params: { withUser?: boolean } = {}): Promise<Tag> {
+ params: { withUser?: boolean; withNbrTransactions?: boolean } = {}): Promise<Tag> {
// Debug
const uniqueTimerID = Logging.traceStart(MODULE_NAME, 'getTag');
// Get tag
const tag = await UserStorage.getTags(tenantID, {
tagIDs: [id],
withUser: params.withUser,
+ withNbrTransactions: params.withNbrTransactions,
}, Constants.DB_PARAMS_SINGLE_RECORD);
// Debug
Logging.traceEnd(MODULE_NAME, 'getTag', uniqueTimerID, { id });
@@ -732,8 +743,8 @@ export default class UserStorage {
}
public static async getTags(tenantID: string,
- params: { issuer?: boolean; tagIDs?: string[]; userIDs?: string[]; dateFrom?: Date; dateTo?: Date,
- withUser?: boolean, search?: string },
+ params: { issuer?: boolean; tagIDs?: string[]; userIDs?: string[]; dateFrom?: Date; dateTo?: Date;
+ withUser?: boolean; withNbrTransactions?: boolean; search?: string },
dbParams: DbParams, projectFields?: string[]): Promise<DataResult<Tag>> {
const uniqueTimerID = Logging.traceStart(MODULE_NAME, 'getTags');
// Check Tenant
@@ -761,7 +772,7 @@ export default class UserStorage {
if (!Utils.isEmptyArray(params.userIDs)) {
filters.userID = { $in: params.userIDs.map((userID) => Utils.convertToObjectID(userID)) };
}
- if (Utils.objectHasProperty(params, 'issuer')) {
+ if (Utils.objectHasProperty(params, 'issuer') && Utils.isBooleanValue(params.issuer)) {
filters.issuer = params.issuer;
}
if (params.dateFrom && moment(params.dateFrom).isValid()) {
@@ -807,11 +818,13 @@ export default class UserStorage {
$limit: dbParams.limit
});
// Transactions
+ if (params.withNbrTransactions) {
DatabaseUtils.pushTransactionsLookupInAggregation({
tenantID, aggregation: aggregation, localField: '_id', foreignField: 'tagID',
count: true, asField: 'transactionsCount', oneToOneCardinality: false,
objectIDFields: ['createdBy', 'lastChangedBy']
});
+ }
// Users
if (params.withUser) {
DatabaseUtils.pushUserLookupInAggregation({

MODIFY
src/types/HTTPError.ts
src/types/HTTPError.ts
@@ -22,6 +22,9 @@ export enum HTTPError {
USER_TAG_ID_ALREADY_USED_ERROR = 540,
USER_NO_BADGE_ERROR = 570,
+ TAG_HAS_TRANSACTIONS = 575,
+ TAG_ALREADY_EXIST_ERROR = 576,
+
USER_ACCOUNT_LOCKED_ERROR = 590,
USER_ACCOUNT_ALREADY_ACTIVE_ERROR = 591,
USER_ACCOUNT_PENDING_ERROR = 592,
@@ -55,4 +58,3 @@ export enum HTTPError {
export enum HTTPAuthError {
ERROR = 560,
}
-

MODIFY
src/types/requests/HttpUserRequest.ts
src/types/requests/HttpUserRequest.ts
@@ -50,6 +50,7 @@ export interface HttpUserSitesRequest extends HttpDatabaseRequest {
export interface HttpTagsRequest extends HttpDatabaseRequest {
Search: string;
UserID?: string;
+ Issuer?: boolean;
}
export interface HttpLoginRequest {

MODIFY
src/utils/Utils.ts
src/utils/Utils.ts
@@ -213,6 +213,10 @@ export default class Utils {
return _.has(object, key);
}
+ public static isBooleanValue(value: boolean): boolean {
+ return _.isBoolean(value);
+ }
+
public static generateGUID(): string {
return uuid();
}
@@ -1626,6 +1630,16 @@ export default class Utils {
user: req.user.id
});
}
+ // Only current organization Tag can be updated
+ if (!tag.issuer) {
+ throw new AppError({
+ source: Constants.CENTRAL_SERVER,
+ errorCode: HTTPError.GENERAL_ERROR,
+ message: `Tag ID '${tag.id}' not issued by the organization`,
+ module: MODULE_NAME, method: 'checkIfUserTagIsValid',
+ user: req.user
+ });
+ }
if (!Utils.objectHasProperty(tag, 'active')) {
throw new AppError({
source: Constants.CENTRAL_SERVER,

Ensure issuer has a valid bool value + Remove transactionCount for perfs issue + Handle delete of tag with transactions