handle tags export
MODIFY
src/authorization/Authorizations.ts
src/authorization/Authorizations.ts
@@ -347,6 +347,10 @@ export default class Authorizations {
return Authorizations.canPerformAction(loggedUser, Entity.TAGS, Action.IMPORT);
}
+ public static async canExportTags(loggedUser: UserToken): Promise<boolean> {
+ return Authorizations.canPerformAction(loggedUser, Entity.TAGS, Action.EXPORT);
+ }
+
public static async canReadUser(loggedUser: UserToken, userID: string): Promise<boolean> {
return Authorizations.canPerformAction(loggedUser, Entity.USER, Action.READ,
{ user: userID, owner: loggedUser.id });

MODIFY
src/authorization/AuthorizationsDefinition.ts
src/authorization/AuthorizationsDefinition.ts
@@ -33,7 +33,7 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
{ resource: Entity.COMPANIES, action: Action.LIST, attributes: [
'id', 'name', 'address', 'logo', 'issuer', 'distanceMeters', 'createdOn', 'lastChangedOn',
'createdBy.name', 'createdBy.firstName', 'lastChangedBy.name', 'lastChangedBy.firstName' ] },
- { resource: Entity.TAGS, action: [Action.LIST, Action.IMPORT], attributes: ['*'] },
+ { resource: Entity.TAGS, action: [Action.LIST, Action.IMPORT, Action.EXPORT], attributes: ['*'] },
{ resource: Entity.TAG, action: [Action.CREATE, Action.UPDATE, Action.DELETE, Action.READ], attributes: ['*'] },
{ resource: Entity.CHARGING_PROFILES, action: Action.LIST, attributes: ['*'] },
{ resource: Entity.CHARGING_PROFILE, action: [Action.READ], attributes: ['*'] },
@@ -313,6 +313,7 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
attributes: ['*'],
args: { 'sites': '$.site' }
},
+ { resource: Entity.TAGS, action: Action.EXPORT, attributes: ['*'] },
]
},
siteOwner: {

MODIFY
src/server/rest/CentralRestServerService.ts
src/server/rest/CentralRestServerService.ts
@@ -160,6 +160,7 @@ class RequestMapper {
[ServerAction.NOTIFICATIONS]: NotificationService.handleGetNotifications.bind(this),
[ServerAction.TAGS]: TagService.handleGetTags.bind(this),
[ServerAction.TAG]: TagService.handleGetTag.bind(this),
+ [ServerAction.TAGS_EXPORT]: TagService.handleExportTags.bind(this),
[ServerAction.USER_DEFAULT_TAG_CAR]: UserService.handleGetUserDefaultTagCar.bind(this),
[ServerAction.TRANSACTIONS_COMPLETED]: TransactionService.handleGetTransactionsCompleted.bind(this),
[ServerAction.TRANSACTIONS_TO_REFUND]: TransactionService.handleGetTransactionsToRefund.bind(this),

MODIFY
src/server/rest/v1/service/TagService.ts
src/server/rest/v1/service/TagService.ts
@@ -13,6 +13,7 @@ import Authorizations from '../../../../authorization/Authorizations';
import Busboy from 'busboy';
import CSVError from 'csvtojson/v2/CSVError';
import Constants from '../../../../utils/Constants';
+import { DataResult } from '../../../../types/DataResult';
import EmspOCPIClient from '../../../../client/ocpi/EmspOCPIClient';
import JSONStream from 'JSONStream';
import LockingHelper from '../../../../locking/LockingHelper';
@@ -72,40 +73,8 @@ export default class TagService {
}
public static async handleGetTags(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
- // Check auth
- if (!await Authorizations.canListTags(req.user)) {
- throw new AppAuthError({
- errorCode: HTTPAuthError.FORBIDDEN,
- user: req.user,
- action: Action.LIST, entity: Entity.TAGS,
- module: MODULE_NAME, method: 'handleGetTags'
- });
- }
- // Filter
- const filteredRequest = TagSecurity.filterTagsRequest(req.query);
- let userID: string;
- if (Authorizations.isBasic(req.user)) {
- userID = req.user.id;
- } else {
- userID = filteredRequest.UserID;
- }
- // Get authorization filters
- const authorizationTagsFilters = await AuthorizationService.checkAndGetTagsAuthorizationFilters(
- req.tenant, req.user, filteredRequest);
- // Get the tags
- const tags = await TagStorage.getTags(req.user.tenantID,
- {
- search: filteredRequest.Search,
- userIDs: userID ? userID.split('|') : null,
- issuer: filteredRequest.Issuer,
- active: filteredRequest.Active,
- withUser: true,
- },
- { limit: filteredRequest.Limit, skip: filteredRequest.Skip, sort: filteredRequest.SortFields, onlyRecordCount: filteredRequest.OnlyRecordCount },
- authorizationTagsFilters.projectFields,
- );
// Return
- res.json(tags);
+ res.json(await TagService.getTags(req));
next();
}
@@ -681,6 +650,13 @@ export default class TagService {
}
}
+ public static async handleExportTags(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
+ // Export with users
+ await UtilsService.exportToCSV(req, res, 'exported-users.csv',
+ TagService.getTags.bind(this),
+ TagService.convertToCSV.bind(this));
+ }
+
private static async insertTags(tenantID: string, user: UserToken, action: ServerAction, tagsToBeImported: ImportedTag[], result: ActionsResponse): Promise<void> {
try {
const nbrInsertedTags = await TagStorage.saveImportedTags(tenantID, tagsToBeImported);
@@ -800,6 +776,69 @@ export default class TagService {
return result;
}
+ private static convertToCSV(req: Request, tags: Tag[], writeHeader = true): string {
+ let headers = null;
+ // Header
+ if (writeHeader) {
+ headers = [
+ 'id',
+ 'description',
+ 'firstName',
+ 'name',
+ 'email'
+ ].join(Constants.CSV_SEPARATOR);
+ }
+ // Content
+ const rows = tags.map((tag) => {
+ const row = [
+ tag.id,
+ tag.description,
+ tag.user?.firstName,
+ tag.user?.name,
+ tag.user?.email
+ ].map((value) => typeof value === 'string' ? '"' + value.replace(/^"|"$/g, '') + '"' : value);
+ return row;
+ }).join(Constants.CR_LF);
+ return Utils.isNullOrUndefined(headers) ? Constants.CR_LF + rows : [headers, rows].join(Constants.CR_LF);
+ }
+
+ private static async getTags(req: Request): Promise<DataResult<Tag>> {
+ // Check auth
+ if (!await Authorizations.canExportTags(req.user)) {
+ throw new AppAuthError({
+ errorCode: HTTPAuthError.FORBIDDEN,
+ user: req.user,
+ action: Action.LIST, entity: Entity.TAGS,
+ module: MODULE_NAME, method: 'handleGetTags'
+ });
+ }
+ // Filter
+ const filteredRequest = TagSecurity.filterTagsRequest(req.query);
+ let userID: string;
+ if (Authorizations.isBasic(req.user)) {
+ userID = req.user.id;
+ } else {
+ userID = filteredRequest.UserID;
+ }
+ // Get authorization filters
+ const authorizationTagsFilters = await AuthorizationService.checkAndGetTagsAuthorizationFilters(
+ req.tenant, req.user, filteredRequest);
+ // Get the tags
+ const tags = await TagStorage.getTags(req.user.tenantID,
+ {
+ search: filteredRequest.Search,
+ userIDs: userID ? userID.split('|') : null,
+ issuer: filteredRequest.Issuer,
+ active: filteredRequest.Active,
+ withUser: filteredRequest.WithUser,
+ },
+ { limit: filteredRequest.Limit, skip: filteredRequest.Skip, sort: filteredRequest.SortFields, onlyRecordCount: filteredRequest.OnlyRecordCount },
+ authorizationTagsFilters.projectFields,
+ );
+ // Return
+ return tags;
+ }
+
private static async processTag(action: ServerAction, req: Request, importedTag: ImportedTag, tagsToBeImported: ImportedTag[]): Promise<boolean> {
try {
const newImportedTag: ImportedTag = {

MODIFY
src/server/rest/v1/service/security/TagSecurity.ts
src/server/rest/v1/service/security/TagSecurity.ts
@@ -13,7 +13,8 @@ export default class TagSecurity {
Search: sanitize(request.Search),
UserID: sanitize(request.UserID),
Issuer: Utils.objectHasProperty(request, 'Issuer') ? UtilsSecurity.filterBoolean(request.Issuer) : null,
- Active: Utils.objectHasProperty(request, 'Active') ? UtilsSecurity.filterBoolean(request.Active) : null
+ Active: Utils.objectHasProperty(request, 'Active') ? UtilsSecurity.filterBoolean(request.Active) : null,
+ WithUser: Utils.objectHasProperty(request, 'WithUser') ? UtilsSecurity.filterBoolean(request.WithUser) : null,
} as HttpTagsRequest;
UtilsSecurity.filterSkipAndLimit(request, filteredRequest);
UtilsSecurity.filterSort(request, filteredRequest);

MODIFY
src/types/Server.ts
src/types/Server.ts
@@ -394,6 +394,7 @@ export enum ServerAction {
TAG_DELETE = 'TagDelete',
TAGS_DELETE = 'TagsDelete',
TAGS_IMPORT = 'TagsImport',
+ TAGS_EXPORT = 'TagsExport',
USER = 'User',
USERS_EXPORT = 'UsersExport',
USERS_IMPORT = 'UsersImport',

MODIFY
src/types/requests/HttpDatabaseRequest.ts
src/types/requests/HttpDatabaseRequest.ts
@@ -6,5 +6,6 @@ export default interface HttpDatabaseRequest extends HttpDatabaseProjectRequest
Limit?: number;
OnlyRecordCount?: boolean;
SortFields: any;
+ WithUser: boolean;
}

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

handle tags export