diff --git a/packages/apps/origin-backend-irec-app/package.json b/packages/apps/origin-backend-irec-app/package.json
index f638ff3b48..de6d004874 100644
--- a/packages/apps/origin-backend-irec-app/package.json
+++ b/packages/apps/origin-backend-irec-app/package.json
@@ -39,8 +39,8 @@
"@energyweb/exchange-io-erc1888": "1.1.1",
"@energyweb/exchange-irec": "1.0.2",
"@energyweb/issuer": "3.2.0",
- "@energyweb/issuer-api": "0.2.0",
- "@energyweb/issuer-irec-api-wrapper": "0.3.0",
+ "@energyweb/issuer-irec-api": "0.1.0",
+ "@energyweb/issuer-irec-api-wrapper": "0.4.0",
"@energyweb/origin-backend": "10.0.1",
"@energyweb/origin-backend-core": "8.0.1",
"@energyweb/origin-backend-utils": "1.5.1",
diff --git a/packages/apps/origin-backend-irec-app/src/cron/check-certificate-state.task.ts b/packages/apps/origin-backend-irec-app/src/cron/check-certificate-state.task.ts
new file mode 100644
index 0000000000..4c9760de23
--- /dev/null
+++ b/packages/apps/origin-backend-irec-app/src/cron/check-certificate-state.task.ts
@@ -0,0 +1,50 @@
+import { Injectable } from '@nestjs/common';
+import { Cron, CronExpression } from '@nestjs/schedule';
+import { CommandBus, EventBus, QueryBus } from '@nestjs/cqrs';
+import {
+ IrecCertificateService,
+ CertificationRequestStatusChangedEvent,
+ GetAllCertificationRequestsQuery,
+ ApproveCertificationRequestCommand
+} from '@energyweb/issuer-irec-api';
+import { IssuanceStatus } from '@energyweb/issuer-irec-api-wrapper';
+
+@Injectable()
+export class CheckCertificateStateTask {
+ constructor(
+ private readonly commandBus: CommandBus,
+ private readonly irecCertificateService: IrecCertificateService,
+ private readonly eventBus: EventBus,
+ private readonly queryBus: QueryBus
+ ) {}
+
+ @Cron(CronExpression.EVERY_MINUTE)
+ async handleCron() {
+ if (!this.irecCertificateService.isIrecIntegrationEnabled()) {
+ return;
+ }
+
+ const certificateRequests = await this.queryBus.execute(
+ new GetAllCertificationRequestsQuery({ approved: false })
+ );
+
+ for (const certificateRequest of certificateRequests) {
+ const irecIssue = await this.irecCertificateService.getIssue(
+ certificateRequest.userId,
+ certificateRequest.irecIssueId
+ );
+
+ if (irecIssue.status === IssuanceStatus.Approved) {
+ await this.commandBus.execute(
+ new ApproveCertificationRequestCommand(certificateRequest.id)
+ );
+ this.eventBus.publish(
+ new CertificationRequestStatusChangedEvent(
+ certificateRequest,
+ IssuanceStatus.Approved
+ )
+ );
+ }
+ }
+ }
+}
diff --git a/packages/apps/origin-backend-irec-app/src/cron/check-device-state.task.ts b/packages/apps/origin-backend-irec-app/src/cron/check-device-state.task.ts
index d52e1525d9..edb851e50e 100644
--- a/packages/apps/origin-backend-irec-app/src/cron/check-device-state.task.ts
+++ b/packages/apps/origin-backend-irec-app/src/cron/check-device-state.task.ts
@@ -16,7 +16,7 @@ export class CheckDeviceStateTask {
private readonly eventBus: EventBus
) {}
- @Cron(CronExpression.EVERY_5_MINUTES)
+ @Cron(CronExpression.EVERY_MINUTE)
async handleCron() {
if (!this.irecDeviceService.isIrecIntegrationEnabled()) {
return;
diff --git a/packages/apps/origin-backend-irec-app/src/integration/exchange-configuration.service.ts b/packages/apps/origin-backend-irec-app/src/integration/exchange-configuration.service.ts
index 5f4ce9155e..a87ae8d52f 100644
--- a/packages/apps/origin-backend-irec-app/src/integration/exchange-configuration.service.ts
+++ b/packages/apps/origin-backend-irec-app/src/integration/exchange-configuration.service.ts
@@ -1,6 +1,6 @@
import { IExchangeConfigurationService } from '@energyweb/exchange';
import { ConfigurationService } from '@energyweb/origin-backend';
-import { BlockchainPropertiesService } from '@energyweb/issuer-api';
+import { BlockchainPropertiesService } from '@energyweb/issuer-irec-api';
import { IDeviceType } from '@energyweb/origin-backend-core';
import { Injectable } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
diff --git a/packages/apps/origin-backend-irec-app/src/main.ts b/packages/apps/origin-backend-irec-app/src/main.ts
index f36494d48c..f0ca0722d2 100644
--- a/packages/apps/origin-backend-irec-app/src/main.ts
+++ b/packages/apps/origin-backend-irec-app/src/main.ts
@@ -23,7 +23,7 @@ export async function startAPI(logger?: LoggerService) {
'@energyweb/origin-backend-irec-app': parsed.version,
'@energyweb/exchange': parsed.dependencies['@energyweb/exchange'],
'@energyweb/origin-backend': parsed.dependencies['@energyweb/origin-backend'],
- '@energyweb/issuer-api': parsed.dependencies['@energyweb/issuer-api'],
+ '@energyweb/issuer-irec-api': parsed.dependencies['@energyweb/issuer-irec-api'],
'@energyweb/origin-organization-irec-api':
parsed.dependencies['@energyweb/origin-organization-irec-api']
};
diff --git a/packages/apps/origin-backend-irec-app/src/notification/handlers/certificate-request-approved.handler.ts b/packages/apps/origin-backend-irec-app/src/notification/handlers/certificate-request-approved.handler.ts
index 64a06df352..9b8860d013 100644
--- a/packages/apps/origin-backend-irec-app/src/notification/handlers/certificate-request-approved.handler.ts
+++ b/packages/apps/origin-backend-irec-app/src/notification/handlers/certificate-request-approved.handler.ts
@@ -1,7 +1,7 @@
import {
CertificateRequestApprovedEvent,
GetCertificationRequestQuery
-} from '@energyweb/issuer-api';
+} from '@energyweb/issuer-irec-api';
import { UserService } from '@energyweb/origin-backend';
import { Role } from '@energyweb/origin-backend-core';
import { DeviceService } from '@energyweb/origin-device-registry-irec-local-api';
diff --git a/packages/apps/origin-backend-irec-app/src/origin-app.module.ts b/packages/apps/origin-backend-irec-app/src/origin-app.module.ts
index eec1122b4f..31e99fedd0 100644
--- a/packages/apps/origin-backend-irec-app/src/origin-app.module.ts
+++ b/packages/apps/origin-backend-irec-app/src/origin-app.module.ts
@@ -1,7 +1,7 @@
import { AppModule as ExchangeModule, entities as ExchangeEntities } from '@energyweb/exchange';
import { ExchangeErc1888Module } from '@energyweb/exchange-io-erc1888';
import { AppModule as ExchangeIRECModule } from '@energyweb/exchange-irec';
-import { AppModule as IssuerModule, entities as IssuerEntities } from '@energyweb/issuer-api';
+import { AppModule as IssuerModule, entities as IssuerEntities } from '@energyweb/issuer-irec-api';
import {
AppModule as OriginBackendModule,
entities as OriginBackendEntities,
diff --git a/packages/apps/origin-backend-irec-app/test/smoke.e2e-spec.ts b/packages/apps/origin-backend-irec-app/test/smoke.e2e-spec.ts
index b82b0bc3dd..a1557afdd3 100644
--- a/packages/apps/origin-backend-irec-app/test/smoke.e2e-spec.ts
+++ b/packages/apps/origin-backend-irec-app/test/smoke.e2e-spec.ts
@@ -6,7 +6,7 @@ import { Test } from '@nestjs/testing';
import request from 'supertest';
import ganache from 'ganache-core';
-import { BlockchainPropertiesService } from '@energyweb/issuer-api';
+import { BlockchainPropertiesService } from '@energyweb/issuer-irec-api';
import { Contracts } from '@energyweb/issuer';
import { getProviderWithFallback } from '@energyweb/utils-general';
import { OriginAppModule } from '../src/origin-app.module';
diff --git a/packages/devices/origin-device-registry-irec-local-api/package.json b/packages/devices/origin-device-registry-irec-local-api/package.json
index 5799bb8eab..6a34ca7b05 100644
--- a/packages/devices/origin-device-registry-irec-local-api/package.json
+++ b/packages/devices/origin-device-registry-irec-local-api/package.json
@@ -30,7 +30,7 @@
"precommit": "lint-staged"
},
"dependencies": {
- "@energyweb/issuer-irec-api-wrapper": "0.3.0",
+ "@energyweb/issuer-irec-api-wrapper": "0.4.0",
"@energyweb/origin-backend-core": "8.0.1",
"@energyweb/origin-backend-utils": "1.5.1",
"@energyweb/origin-device-registry-api": "0.1.0",
diff --git a/packages/organizations/origin-organization-irec-api/package.json b/packages/organizations/origin-organization-irec-api/package.json
index a806acaaca..f8c1f0910c 100644
--- a/packages/organizations/origin-organization-irec-api/package.json
+++ b/packages/organizations/origin-organization-irec-api/package.json
@@ -30,7 +30,7 @@
"precommit": "lint-staged"
},
"dependencies": {
- "@energyweb/issuer-irec-api-wrapper": "0.3.0",
+ "@energyweb/issuer-irec-api-wrapper": "0.4.0",
"@energyweb/origin-backend-core": "8.0.1",
"@energyweb/origin-backend-utils": "1.5.1",
"@nestjs/common": "7.6.17",
diff --git a/packages/origin-backend/src/index.ts b/packages/origin-backend/src/index.ts
index 930dfb686c..7e9f9e3ca8 100644
--- a/packages/origin-backend/src/index.ts
+++ b/packages/origin-backend/src/index.ts
@@ -11,11 +11,13 @@ import { Organization, OrganizationModule } from './pods/organization';
import { User, UserModule } from './pods/user';
export { AppModule } from './app.module';
+
+export * from './pods/configuration';
export * from './pods/email-confirmation/events';
export * from './pods/invitation/events';
-export * from './pods/user';
+export * from './pods/file';
export * from './pods/organization';
-export * from './pods/configuration';
+export * from './pods/user';
export const entities = [Configuration, Organization, User, Invitation, EmailConfirmation, File];
diff --git a/packages/traceability/issuer-api-client/README.md b/packages/traceability/issuer-api-client/README.md
index 6a319565c1..da6b3f9ca2 100644
--- a/packages/traceability/issuer-api-client/README.md
+++ b/packages/traceability/issuer-api-client/README.md
@@ -4,6 +4,6 @@
EnergyWeb Origin
-
Issuer API CLient
+ Issuer API Client
diff --git a/packages/traceability/issuer-api/package.json b/packages/traceability/issuer-api/package.json
index a6be6e6a81..20440356f2 100644
--- a/packages/traceability/issuer-api/package.json
+++ b/packages/traceability/issuer-api/package.json
@@ -26,7 +26,7 @@
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:mocha": "mocha -r ts-node/register test/*.e2e-spec.ts --timeout 60000 --exit",
- "test:e2e": "yarn typeorm:run && concurrently --success first --kill-others -n eth,test \"yarn start-ganache\" \"wait-on tcp:8581 && yarn test:mocha\"",
+ "test:e2e": "yarn typeorm:run && npx concurrently --success first --kill-others -n eth,test \"yarn start-ganache\" \"npx wait-on tcp:8581 && yarn test:mocha\"",
"start-ganache": "ganache-cli -m 'chalk park staff buzz chair purchase wise oak receive avoid avoid home' -l 8000000 -e 1000000 -a 20 -p 8581 -q",
"clean": "shx rm -rf dist dist-shakeable",
"typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js --config ormconfig-dev.ts",
diff --git a/packages/traceability/issuer-api/src/index.ts b/packages/traceability/issuer-api/src/index.ts
index 79cccc8f33..9d09b09983 100644
--- a/packages/traceability/issuer-api/src/index.ts
+++ b/packages/traceability/issuer-api/src/index.ts
@@ -5,6 +5,8 @@ import { CertificationRequest, CertificationRequestModule } from './pods/certifi
export * from './pods/certificate';
export * from './pods/certification-request';
export * from './pods/blockchain';
+export * from './utils';
+export * from './types';
export { BlockchainPropertiesService } from './pods/blockchain/blockchain-properties.service';
export { AppModule, providers } from './app.module';
diff --git a/packages/traceability/issuer-api/src/pods/certification-request/certification-request.controller.ts b/packages/traceability/issuer-api/src/pods/certification-request/certification-request.controller.ts
index ae9dfa5ef2..bd8fc3019b 100644
--- a/packages/traceability/issuer-api/src/pods/certification-request/certification-request.controller.ts
+++ b/packages/traceability/issuer-api/src/pods/certification-request/certification-request.controller.ts
@@ -26,19 +26,23 @@ import {
Role,
ValidateDeviceOwnershipQuery
} from '@energyweb/origin-backend-core';
-
import { ApiBearerAuth, ApiBody, ApiResponse, ApiTags } from '@nestjs/swagger';
-import { CreateCertificationRequestCommand } from './commands/create-certification-request.command';
-import { CreateCertificationRequestDTO } from './commands/create-certification-request.dto';
-import { GetAllCertificationRequestsQuery } from './queries/get-all-certification-requests.query';
-import { GetCertificationRequestQuery } from './queries/get-certification-request.query';
-import { ApproveCertificationRequestCommand } from './commands/approve-certification-request.command';
-import { RevokeCertificationRequestCommand } from './commands/revoke-certification-request.command';
-import { GetCertificationRequestByCertificateQuery } from './queries/get-certification-request-by-certificate.query';
+
+import {
+ CreateCertificationRequestCommand,
+ ApproveCertificationRequestCommand,
+ RevokeCertificationRequestCommand,
+ ValidateCertificationRequestCommand,
+ CertificateBoundToCertificationRequestCommand,
+ CreateCertificationRequestDTO
+} from './commands';
+import {
+ GetAllCertificationRequestsQuery,
+ GetCertificationRequestQuery,
+ GetCertificationRequestByCertificateQuery
+} from './queries';
import { CertificationRequestDTO } from './certification-request.dto';
-import { SuccessResponseDTO } from '../../utils/success-response.dto';
-import { ValidateCertificationRequestCommand } from './commands/validate-certification-request.command';
-import { CertificateBoundToCertificationRequestCommand } from './commands/certificate-bound-to-certification-request.command';
+import { SuccessResponseDTO } from '../../utils';
@ApiTags('certification-requests')
@ApiBearerAuth('access-token')
diff --git a/packages/traceability/issuer-api/src/pods/certification-request/events/certificate-request-approved-event.ts b/packages/traceability/issuer-api/src/pods/certification-request/events/certificate-request-approved.event.ts
similarity index 100%
rename from packages/traceability/issuer-api/src/pods/certification-request/events/certificate-request-approved-event.ts
rename to packages/traceability/issuer-api/src/pods/certification-request/events/certificate-request-approved.event.ts
diff --git a/packages/traceability/issuer-api/src/pods/certification-request/events/index.ts b/packages/traceability/issuer-api/src/pods/certification-request/events/index.ts
index ffe12358e7..66786ae1d0 100644
--- a/packages/traceability/issuer-api/src/pods/certification-request/events/index.ts
+++ b/packages/traceability/issuer-api/src/pods/certification-request/events/index.ts
@@ -1 +1 @@
-export * from './certificate-request-approved-event';
+export * from './certificate-request-approved.event';
diff --git a/packages/traceability/issuer-api/src/pods/certification-request/handlers/create-certification-request.handler.ts b/packages/traceability/issuer-api/src/pods/certification-request/handlers/create-certification-request.handler.ts
index f588741f2e..b1ebb0ec3f 100644
--- a/packages/traceability/issuer-api/src/pods/certification-request/handlers/create-certification-request.handler.ts
+++ b/packages/traceability/issuer-api/src/pods/certification-request/handlers/create-certification-request.handler.ts
@@ -7,28 +7,37 @@ import { concatMap } from 'rxjs/operators';
import { Repository } from 'typeorm';
import { isAddress, getAddress } from 'ethers/lib/utils';
-import { BlockchainPropertiesService } from '../../blockchain/blockchain-properties.service';
+import { BlockchainPropertiesService } from '../../blockchain';
import { CertificationRequestStatus } from '../certification-request-status.enum';
import { CertificationRequestDTO } from '../certification-request.dto';
import { CertificationRequest } from '../certification-request.entity';
-import { CreateCertificationRequestCommand } from '../commands/create-certification-request.command';
+import { CreateCertificationRequestCommand } from '../commands';
@CommandHandler(CreateCertificationRequestCommand)
export class CreateCertificationRequestHandler
- implements ICommandHandler {
- private readonly logger = new Logger(CreateCertificationRequestHandler.name);
+ implements ICommandHandler
+{
+ readonly logger = new Logger(CreateCertificationRequestHandler.name);
private readonly requestQueue = new Subject();
constructor(
@InjectRepository(CertificationRequest)
- private readonly repository: Repository,
- private readonly blockchainPropertiesService: BlockchainPropertiesService
+ readonly repository: Repository,
+ readonly blockchainPropertiesService: BlockchainPropertiesService
) {
this.requestQueue.pipe(concatMap((id) => this.process(id))).subscribe();
}
- async execute({
+ async execute(params: CreateCertificationRequestCommand): Promise {
+ const stored = await this.createCertificationRequest(params);
+
+ this.addToQueue(stored.id);
+
+ return stored;
+ }
+
+ async createCertificationRequest({
to,
energy,
fromTime,
@@ -56,14 +65,22 @@ export class CreateCertificationRequestHandler
owner: getAddress(to) // it returns checksum address
});
- const stored = await this.repository.save(certificationRequest);
+ return this.repository.save(certificationRequest);
+ }
- this.requestQueue.next(stored.id);
+ addToQueue(id: number) {
+ this.requestQueue.next(id);
+ }
- return stored;
+ async process(requestId: number) {
+ const request: CertificationRequest = await this.getCertificationRequest(requestId);
+
+ if (request) {
+ await this.mintCertificationRequest(request);
+ }
}
- private async process(requestId: number) {
+ async getCertificationRequest(requestId: number): Promise {
this.logger.debug(`Processing certification request ${requestId}`);
const request = await this.repository.findOne(requestId);
@@ -82,10 +99,12 @@ export class CreateCertificationRequestHandler
return;
}
- const blockchainProperties = await this.blockchainPropertiesService.get();
-
- const { fromTime, toTime, deviceId, owner } = request;
+ return request;
+ }
+ async mintCertificationRequest(request: CertificationRequest): Promise {
+ const { id, fromTime, toTime, deviceId, owner } = request;
+ const blockchainProperties = await this.blockchainPropertiesService.get();
try {
const { created, id } = await CertificationRequestFacade.create(
fromTime,
@@ -94,9 +113,7 @@ export class CreateCertificationRequestHandler
blockchainProperties.wrap(),
owner
);
- this.logger.debug(
- `Certification request ${requestId} has been deployed with id ${id} `
- );
+ this.logger.debug(`Certification request ${id} has been deployed with id ${id} `);
await this.repository.update(request.id, {
created,
@@ -105,7 +122,7 @@ export class CreateCertificationRequestHandler
});
} catch (e) {
this.logger.error(
- `Certification request ${requestId} deployment has failed with the error: ${e.message}`
+ `Certification request ${id} deployment has failed with the error: ${e.message}`
);
await this.repository.update(request.id, {
diff --git a/packages/traceability/issuer-api/src/pods/certification-request/handlers/get-all-certification-requests.handler.ts b/packages/traceability/issuer-api/src/pods/certification-request/handlers/get-all-certification-requests.handler.ts
index df7dc4ac81..4b3ee3b99d 100644
--- a/packages/traceability/issuer-api/src/pods/certification-request/handlers/get-all-certification-requests.handler.ts
+++ b/packages/traceability/issuer-api/src/pods/certification-request/handlers/get-all-certification-requests.handler.ts
@@ -1,18 +1,21 @@
import { IQueryHandler, QueryHandler } from '@nestjs/cqrs';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
-import { GetAllCertificationRequestsQuery } from '../queries/get-all-certification-requests.query';
+
+import { GetAllCertificationRequestsQuery } from '../queries';
import { CertificationRequest } from '../certification-request.entity';
+import { CertificationRequestDTO } from '../certification-request.dto';
@QueryHandler(GetAllCertificationRequestsQuery)
export class GetAllCertificationRequestsHandler
- implements IQueryHandler {
+ implements IQueryHandler
+{
constructor(
@InjectRepository(CertificationRequest)
- private readonly repository: Repository
+ readonly repository: Repository
) {}
- async execute(): Promise {
- return this.repository.find();
+ async execute({ query }: GetAllCertificationRequestsQuery): Promise {
+ return this.repository.find(query);
}
}
diff --git a/packages/traceability/issuer-api/src/pods/certification-request/handlers/get-certification-request-by-certificate.handler.ts b/packages/traceability/issuer-api/src/pods/certification-request/handlers/get-certification-request-by-certificate.handler.ts
index 47bd639610..6aee8f9add 100644
--- a/packages/traceability/issuer-api/src/pods/certification-request/handlers/get-certification-request-by-certificate.handler.ts
+++ b/packages/traceability/issuer-api/src/pods/certification-request/handlers/get-certification-request-by-certificate.handler.ts
@@ -1,30 +1,30 @@
import { IQueryHandler, QueryHandler } from '@nestjs/cqrs';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
-import { Certificate } from '../../certificate/certificate.entity';
+import { Certificate } from '../../certificate';
import { CertificationRequest } from '../certification-request.entity';
-import { GetCertificationRequestByCertificateQuery } from '../queries/get-certification-request-by-certificate.query';
+import { GetCertificationRequestByCertificateQuery } from '../queries';
+import { CertificationRequestDTO } from '../certification-request.dto';
@QueryHandler(GetCertificationRequestByCertificateQuery)
export class GetCertificationRequestByCertificateHandler
- implements IQueryHandler {
+ implements IQueryHandler
+{
constructor(
@InjectRepository(CertificationRequest)
- private readonly repository: Repository,
+ readonly repository: Repository,
@InjectRepository(Certificate)
- private readonly certificateRepository: Repository
+ readonly certificateRepository: Repository
) {}
async execute({
certificateId
- }: GetCertificationRequestByCertificateQuery): Promise {
+ }: GetCertificationRequestByCertificateQuery): Promise {
const certificate = await this.certificateRepository.findOne(certificateId);
- const certificationRequest = await this.repository.findOne({
+ return await this.repository.findOne({
where: {
issuedCertificateTokenId: certificate.tokenId
}
});
-
- return certificationRequest;
}
}
diff --git a/packages/traceability/issuer-api/src/pods/certification-request/handlers/get-certification-request.handler.ts b/packages/traceability/issuer-api/src/pods/certification-request/handlers/get-certification-request.handler.ts
index 0e024ca03f..5068d1a3ab 100644
--- a/packages/traceability/issuer-api/src/pods/certification-request/handlers/get-certification-request.handler.ts
+++ b/packages/traceability/issuer-api/src/pods/certification-request/handlers/get-certification-request.handler.ts
@@ -2,16 +2,17 @@ import { IQueryHandler, QueryHandler } from '@nestjs/cqrs';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CertificationRequest } from '../certification-request.entity';
-import { GetCertificationRequestQuery } from '../queries/get-certification-request.query';
+import { GetCertificationRequestQuery } from '../queries';
+import { CertificationRequestDTO } from '../certification-request.dto';
@QueryHandler(GetCertificationRequestQuery)
export class GetCertificationRequestHandler implements IQueryHandler {
constructor(
@InjectRepository(CertificationRequest)
- private readonly repository: Repository
+ readonly repository: Repository
) {}
- async execute({ id }: GetCertificationRequestQuery): Promise {
+ async execute({ id }: GetCertificationRequestQuery): Promise {
return this.repository.findOne(id);
}
}
diff --git a/packages/traceability/issuer-api/src/pods/certification-request/queries/get-all-certification-requests.query.ts b/packages/traceability/issuer-api/src/pods/certification-request/queries/get-all-certification-requests.query.ts
index 10ec7bb70d..2614aa0f6e 100644
--- a/packages/traceability/issuer-api/src/pods/certification-request/queries/get-all-certification-requests.query.ts
+++ b/packages/traceability/issuer-api/src/pods/certification-request/queries/get-all-certification-requests.query.ts
@@ -1 +1,10 @@
-export class GetAllCertificationRequestsQuery {}
+import { CertificationRequestStatus } from '../certification-request-status.enum';
+
+interface GetCertificationRequestsParams {
+ approved?: boolean;
+ status?: CertificationRequestStatus;
+}
+
+export class GetAllCertificationRequestsQuery {
+ constructor(public readonly query?: GetCertificationRequestsParams) {}
+}
diff --git a/packages/traceability/issuer-api/src/utils/index.ts b/packages/traceability/issuer-api/src/utils/index.ts
new file mode 100644
index 0000000000..029705fd4a
--- /dev/null
+++ b/packages/traceability/issuer-api/src/utils/index.ts
@@ -0,0 +1 @@
+export * from './success-response.dto';
diff --git a/packages/traceability/issuer-irec-api-client/.gitignore b/packages/traceability/issuer-irec-api-client/.gitignore
new file mode 100644
index 0000000000..d33237d6d9
--- /dev/null
+++ b/packages/traceability/issuer-irec-api-client/.gitignore
@@ -0,0 +1,38 @@
+# compiled output
+/dist
+/node_modules
+
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+
+# OS
+.DS_Store
+
+# Tests
+/coverage
+/.nyc_output
+
+# IDEs and editors
+/.idea
+.project
+.classpath
+.c9/
+*.launch
+.settings/
+*.sublime-workspace
+
+# IDE - VSCode
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+
+# Ignore autogenerated files
+src/*
+!src/generateSchema.ts
\ No newline at end of file
diff --git a/packages/traceability/issuer-irec-api-client/README.md b/packages/traceability/issuer-irec-api-client/README.md
new file mode 100644
index 0000000000..12e85c4b3c
--- /dev/null
+++ b/packages/traceability/issuer-irec-api-client/README.md
@@ -0,0 +1,9 @@
+
+
+
+
+ EnergyWeb Origin
+
+ Issuer IREC API Client
+
+
diff --git a/packages/traceability/issuer-irec-api-client/openapitools.json b/packages/traceability/issuer-irec-api-client/openapitools.json
new file mode 100644
index 0000000000..a550dc0a66
--- /dev/null
+++ b/packages/traceability/issuer-irec-api-client/openapitools.json
@@ -0,0 +1,7 @@
+{
+ "$schema": "node_modules/@openapitools/openapi-generator-cli/config.schema.json",
+ "spaces": 2,
+ "generator-cli": {
+ "version": "5.0.1"
+ }
+}
diff --git a/packages/traceability/issuer-irec-api-client/package.json b/packages/traceability/issuer-irec-api-client/package.json
new file mode 100644
index 0000000000..290180df27
--- /dev/null
+++ b/packages/traceability/issuer-irec-api-client/package.json
@@ -0,0 +1,52 @@
+{
+ "name": "@energyweb/issuer-irec-api-client",
+ "version": "0.1.4",
+ "description": "Client library interacting with the Issuer IREC API",
+ "homepage": "https://github.com/energywebfoundation/origin/tree/master/packages/issuer-irec-api-client#readme",
+ "author": "EnergyWeb DevHub GmbH; Joseph Bagaric, joseph.bagaric@energyweb.org",
+ "license": "GPL-3.0-or-later",
+ "main": "dist/js/index.js",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/energywebfoundation/origin.git"
+ },
+ "bugs": {
+ "url": "https://github.com/energywebfoundation/origin/issues"
+ },
+ "scripts": {
+ "build": "yarn build:ts",
+ "build:ts": "yarn build:client 1>/dev/null 2>/dev/null && tsc -b tsconfig.json",
+ "build:client": "yarn client:generate:schema && yarn client:generate && yarn client:clean",
+ "clean": "shx rm -rf dist dist-shakeable",
+ "client:generate": "openapi-generator-cli generate -g typescript-axios -i src/schema.yaml -o src --api-name-suffix Client --remove-operation-id-prefix",
+ "client:generate:schema": "ts-node src/generateSchema.ts",
+ "client:clean": "find src/ -type f ! -name \"*.ts\" -delete"
+ },
+ "dependencies": {
+ "axios": "0.21.1"
+ },
+ "devDependencies": {
+ "typescript": "4.2.4",
+ "@energyweb/issuer-irec-api": "0.1.0",
+ "@nestjs/swagger": "4.8.0",
+ "@nestjs/testing": "7.6.15",
+ "@nestjs/typeorm": "7.1.5",
+ "@openapitools/openapi-generator-cli": "2.2.6",
+ "json-to-pretty-yaml": "1.2.2",
+ "prettier": "2.3.0",
+ "ts-node": "9.1.1",
+ "@types/mocha": "8.2.2",
+ "@types/node": "14.14.45",
+ "mocha": "8.4.0"
+ },
+ "publishConfig": {
+ "access": "public",
+ "registry": "https://registry.npmjs.org"
+ },
+ "files": [
+ "dist"
+ ],
+ "resolutions": {
+ "tslib": "1.14.1"
+ }
+}
diff --git a/packages/traceability/issuer-irec-api-client/src/generateSchema.ts b/packages/traceability/issuer-irec-api-client/src/generateSchema.ts
new file mode 100644
index 0000000000..51be38b534
--- /dev/null
+++ b/packages/traceability/issuer-irec-api-client/src/generateSchema.ts
@@ -0,0 +1,49 @@
+import fs from 'fs';
+import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
+import { Test } from '@nestjs/testing';
+import { TypeOrmModule } from '@nestjs/typeorm';
+import { entities, usedEntities, AppModule } from '@energyweb/issuer-irec-api';
+
+// eslint-disable-next-line @typescript-eslint/no-var-requires
+const Yaml = require('json-to-pretty-yaml');
+
+export const generateSchema = async () => {
+ const moduleFixture = await Test.createTestingModule({
+ imports: [
+ TypeOrmModule.forRoot({
+ type: 'postgres',
+ host: process.env.DB_HOST ?? 'localhost',
+ port: Number(process.env.DB_PORT) || 5432,
+ username: process.env.DB_USERNAME ?? 'postgres',
+ password: process.env.DB_PASSWORD ?? 'postgres',
+ database: process.env.DB_DATABASE ?? 'origin',
+ entities: [...usedEntities, ...entities],
+ logging: ['info']
+ }),
+ AppModule
+ ]
+ }).compile();
+
+ const app = moduleFixture.createNestApplication();
+
+ app.setGlobalPrefix('api');
+
+ const options = new DocumentBuilder()
+ .setTitle('Issuer API')
+ .setDescription('Swagger documentation for the Issuer IREC API')
+ .setVersion('0.1')
+ .addBearerAuth({ type: 'http', scheme: 'bearer', bearerFormat: 'JWT' }, 'access-token')
+ .build();
+
+ const document = SwaggerModule.createDocument(app, options);
+
+ if (!document.components.schemas) {
+ document.components.schemas = {};
+ }
+
+ fs.writeFileSync('./src/schema.yaml', Yaml.stringify(document));
+};
+
+(async () => {
+ await generateSchema();
+})();
diff --git a/packages/traceability/issuer-irec-api-client/tsconfig.json b/packages/traceability/issuer-irec-api-client/tsconfig.json
new file mode 100644
index 0000000000..045531ce13
--- /dev/null
+++ b/packages/traceability/issuer-irec-api-client/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "extends": "../../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./dist/js",
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "target": "ES2020",
+ "types": ["node", "mocha"]
+ },
+ "include": ["src/**/*"]
+}
diff --git a/packages/traceability/issuer-irec-api-wrapper/package.json b/packages/traceability/issuer-irec-api-wrapper/package.json
index 7e4094fc37..9f96e5d932 100644
--- a/packages/traceability/issuer-irec-api-wrapper/package.json
+++ b/packages/traceability/issuer-irec-api-wrapper/package.json
@@ -1,6 +1,6 @@
{
"name": "@energyweb/issuer-irec-api-wrapper",
- "version": "0.3.0",
+ "version": "0.4.0",
"description": "A Typescript wrapper for I-REC Evident API",
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
diff --git a/packages/traceability/issuer-irec-api-wrapper/src/IRECAPIClient.ts b/packages/traceability/issuer-irec-api-wrapper/src/IRECAPIClient.ts
index 17cdc285f9..93f1aa72c3 100644
--- a/packages/traceability/issuer-irec-api-wrapper/src/IRECAPIClient.ts
+++ b/packages/traceability/issuer-irec-api-wrapper/src/IRECAPIClient.ts
@@ -181,18 +181,18 @@ export class IRECAPIClient extends EventEmitter {
};
return {
- create: async (issue: Issue): Promise => {
+ create: async (issue: Issue): Promise => {
const issueParams = issue instanceof Issue ? issue : plainToClass(Issue, issue);
await validateOrReject(issue);
const url = `${issueManagementUrl}/create`;
- const response = await this.axiosInstance.post<{ code: string }>(
+ const response = await this.axiosInstance.post(
url,
classToPlain(issueParams),
this.config
);
- return response.data.code;
+ return response.data;
},
update: async (code: string, issue: Issue): Promise => {
await validateOrReject(issue, { skipMissingProperties: true });
diff --git a/packages/traceability/issuer-irec-api-wrapper/src/Issue.ts b/packages/traceability/issuer-irec-api-wrapper/src/Issue.ts
index 7323345c40..250e5f4557 100644
--- a/packages/traceability/issuer-irec-api-wrapper/src/Issue.ts
+++ b/packages/traceability/issuer-irec-api-wrapper/src/Issue.ts
@@ -1,8 +1,8 @@
import { Expose, Transform } from 'class-transformer';
-import { IsDate, IsPositive, IsOptional, IsString, IsNotEmpty } from 'class-validator';
+import { IsDate, IsEnum, IsNotEmpty, IsOptional, IsPositive, IsString } from 'class-validator';
import { FileIds } from './File';
-export enum IssueStatus {
+export enum IssuanceStatus {
Draft = 'Draft',
InProgress = 'In-progress',
Rejected = 'Rejected',
@@ -65,7 +65,11 @@ export class ApproveIssue {
}
export class IssueWithStatus extends Issue {
+ @IsString()
+ @IsNotEmpty()
code: string;
- status: IssueStatus;
+ @IsNotEmpty()
+ @IsEnum(IssuanceStatus)
+ status: IssuanceStatus;
}
diff --git a/packages/traceability/issuer-irec-api-wrapper/test/IRECAPIClient.test.ts b/packages/traceability/issuer-irec-api-wrapper/test/IRECAPIClient.test.ts
index ae7686a33e..1c71cb3002 100644
--- a/packages/traceability/issuer-irec-api-wrapper/test/IRECAPIClient.test.ts
+++ b/packages/traceability/issuer-irec-api-wrapper/test/IRECAPIClient.test.ts
@@ -89,7 +89,7 @@ describe('IREC API', () => {
const beforeTransactions = await participantClient.account.getTransactions(tradeAccount);
- const code = await registrantClient.issue.create({
+ const createdIssue = await registrantClient.issue.create({
device: 'DEVICE001',
recipient: tradeAccount,
start: moment(lastItem.asset.end).add(1, 'day').toDate(),
@@ -98,13 +98,13 @@ describe('IREC API', () => {
fuel: 'ES200'
});
- await participantClient.issue.submit(code, 'Note');
- await participantClient.issue.verify(code, 'Note');
+ await participantClient.issue.submit(createdIssue.code, 'Note');
+ await participantClient.issue.verify(createdIssue.code, 'Note');
const approval = new ApproveIssue();
approval.issuer = issueAccount;
- await participantClient.issue.approve(code, approval);
+ await participantClient.issue.approve(createdIssue.code, approval);
const afterTransactions = await registrantClient.account.getTransactions(tradeAccount);
diff --git a/packages/traceability/issuer-irec-api-wrapper/test/flow.test.ts b/packages/traceability/issuer-irec-api-wrapper/test/flow.test.ts
index 95427c4a55..7fc29479c9 100644
--- a/packages/traceability/issuer-irec-api-wrapper/test/flow.test.ts
+++ b/packages/traceability/issuer-irec-api-wrapper/test/flow.test.ts
@@ -13,7 +13,7 @@ import {
DeviceState,
IRECAPIClient,
Issue,
- IssueStatus,
+ IssuanceStatus,
IssueWithStatus,
Organisation
} from '../src';
@@ -128,34 +128,35 @@ describe('API flows', () => {
notes: 'Some note',
files: [fileId]
};
- const issueCode: string = await registrantClient.issue.create(issueParams);
+ const createdIssue = await registrantClient.issue.create(issueParams);
+ const issueCode = createdIssue.code;
let issue: IssueWithStatus = await registrantClient.issue.get(issueCode);
expect(issue.code).to.equal(issueCode);
- expect(issue.status).to.equal(IssueStatus.Draft);
+ expect(issue.status).to.equal(IssuanceStatus.Draft);
await registrantClient.issue.submit(issueCode);
issue = await registrantClient.issue.get(issueCode);
- expect(issue.status).to.equal(IssueStatus.InProgress);
+ expect(issue.status).to.equal(IssuanceStatus.InProgress);
let issuerIssue = await issuerClient.issue.get(issueCode);
- expect(issuerIssue.status).to.equal(IssueStatus.Submitted);
+ expect(issuerIssue.status).to.equal(IssuanceStatus.Submitted);
await issuerClient.issue.verify(issueCode);
issue = await registrantClient.issue.get(issueCode);
- expect(issue.status).to.equal(IssueStatus.InProgress);
+ expect(issue.status).to.equal(IssuanceStatus.InProgress);
issuerIssue = await issuerClient.issue.get(issueCode);
- expect(issuerIssue.status).to.equal(IssueStatus.Verified);
+ expect(issuerIssue.status).to.equal(IssuanceStatus.Verified);
await issuerClient.issue.refer(issueCode);
issue = await registrantClient.issue.get(issueCode);
- expect(issue.status).to.equal(IssueStatus.InProgress);
+ expect(issue.status).to.equal(IssuanceStatus.InProgress);
issuerIssue = await issuerClient.issue.get(issueCode);
- expect(issuerIssue.status).to.equal(IssueStatus.Referred);
+ expect(issuerIssue.status).to.equal(IssuanceStatus.Referred);
await issuerClient.issue.reject(issueCode);
issue = await registrantClient.issue.get(issueCode);
- expect(issue.status).to.equal(IssueStatus.Rejected);
+ expect(issue.status).to.equal(IssuanceStatus.Rejected);
issuerIssue = await issuerClient.issue.get(issueCode);
- expect(issuerIssue.status).to.equal(IssueStatus.Rejected);
+ expect(issuerIssue.status).to.equal(IssuanceStatus.Rejected);
await registrantClient.issue.submit(issueCode);
await issuerClient.issue.verify(issueCode);
@@ -164,9 +165,9 @@ describe('API flows', () => {
notes: 'it is ok'
});
issue = await registrantClient.issue.get(issueCode);
- expect(issue.status).to.equal(IssueStatus.Issued);
+ expect(issue.status).to.equal(IssuanceStatus.Issued);
issuerIssue = await issuerClient.issue.get(issueCode);
- expect(issuerIssue.status).to.equal(IssueStatus.Issued);
+ expect(issuerIssue.status).to.equal(IssuanceStatus.Issued);
}).timeout(10000);
it('should create and update beneficiary', async () => {
diff --git a/packages/traceability/issuer-irec-api/.eslintrc.js b/packages/traceability/issuer-irec-api/.eslintrc.js
new file mode 100644
index 0000000000..05dc6ece58
--- /dev/null
+++ b/packages/traceability/issuer-irec-api/.eslintrc.js
@@ -0,0 +1,18 @@
+const path = require('path');
+
+module.exports = {
+ extends: ['../../../.eslintrc.js'],
+ rules: {
+ 'import/no-extraneous-dependencies': [
+ 'error',
+ {
+ packageDir: [__dirname, path.join(__dirname, '../../../')]
+ }
+ ],
+ 'no-useless-constructor': 'off'
+ },
+ plugins: ['jest'],
+ env: {
+ 'jest/globals': true
+ }
+};
diff --git a/packages/traceability/issuer-irec-api/.gitignore b/packages/traceability/issuer-irec-api/.gitignore
new file mode 100644
index 0000000000..09ea0947f9
--- /dev/null
+++ b/packages/traceability/issuer-irec-api/.gitignore
@@ -0,0 +1,34 @@
+# compiled output
+/dist
+/node_modules
+
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+
+# OS
+.DS_Store
+
+# Tests
+/coverage
+/.nyc_output
+
+# IDEs and editors
+/.idea
+.project
+.classpath
+.c9/
+*.launch
+.settings/
+*.sublime-workspace
+
+# IDE - VSCode
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
diff --git a/packages/traceability/issuer-irec-api/.lintstagedrc b/packages/traceability/issuer-irec-api/.lintstagedrc
new file mode 100644
index 0000000000..99dbc7b8a8
--- /dev/null
+++ b/packages/traceability/issuer-irec-api/.lintstagedrc
@@ -0,0 +1,6 @@
+{
+ "*.{ts,tsx}": [
+ "prettier --write --config-precedence file-override './src/**/*'",
+ "eslint --fix"
+ ]
+}
\ No newline at end of file
diff --git a/packages/traceability/issuer-irec-api/README.md b/packages/traceability/issuer-irec-api/README.md
new file mode 100644
index 0000000000..9107f42ea4
--- /dev/null
+++ b/packages/traceability/issuer-irec-api/README.md
@@ -0,0 +1,9 @@
+
+
+
+
+ EnergyWeb Origin
+
+ Issuer API
+
+
diff --git a/packages/traceability/issuer-irec-api/bin/issuer-irec-api-migrate b/packages/traceability/issuer-irec-api/bin/issuer-irec-api-migrate
new file mode 100755
index 0000000000..609ecb94bb
--- /dev/null
+++ b/packages/traceability/issuer-irec-api/bin/issuer-irec-api-migrate
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+
+typeorm migration:run --config ormconfig.js
\ No newline at end of file
diff --git a/packages/traceability/issuer-irec-api/migrations/1621926541087-Init.ts b/packages/traceability/issuer-irec-api/migrations/1621926541087-Init.ts
new file mode 100644
index 0000000000..eb3883a2d1
--- /dev/null
+++ b/packages/traceability/issuer-irec-api/migrations/1621926541087-Init.ts
@@ -0,0 +1,22 @@
+import { MigrationInterface, QueryRunner } from 'typeorm';
+
+export class Init1621926541087 implements MigrationInterface {
+ name = 'Init1621926541087';
+
+ public async up(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(
+ `CREATE TABLE "irec_issuer_certification_request" (
+ "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
+ "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
+ "certificationRequestId" integer NOT NULL,
+ "userId" character varying NOT NULL,
+ "irecIssueId" character varying NOT NULL,
+ CONSTRAINT "PK_d723daa2222d7cb23a68d181551" PRIMARY KEY ("certificationRequestId")
+ )`
+ );
+ }
+
+ public async down(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(`DROP TABLE "irec_issuer_certification_request"`);
+ }
+}
diff --git a/packages/traceability/issuer-irec-api/nest-cli.json b/packages/traceability/issuer-irec-api/nest-cli.json
new file mode 100644
index 0000000000..c828dfa92e
--- /dev/null
+++ b/packages/traceability/issuer-irec-api/nest-cli.json
@@ -0,0 +1,7 @@
+{
+ "collection": "@nestjs/schematics",
+ "sourceRoot": "src",
+ "compilerOptions": {
+ "plugins": ["@nestjs/swagger/plugin"]
+ }
+}
diff --git a/packages/traceability/issuer-irec-api/ormconfig-dev.ts b/packages/traceability/issuer-irec-api/ormconfig-dev.ts
new file mode 100644
index 0000000000..e903a4edd5
--- /dev/null
+++ b/packages/traceability/issuer-irec-api/ormconfig-dev.ts
@@ -0,0 +1,35 @@
+import { ConnectionOptions } from 'typeorm';
+import { entities } from './src';
+
+const getDBConnectionOptions = (): ConnectionOptions => {
+ return process.env.DATABASE_URL
+ ? {
+ type: 'postgres',
+ url: process.env.DATABASE_URL,
+ ssl: {
+ rejectUnauthorized: false
+ }
+ }
+ : {
+ type: 'postgres',
+ host: process.env.DB_HOST ?? 'localhost',
+ port: Number(process.env.DB_PORT) || 5432,
+ username: process.env.DB_USERNAME ?? 'postgres',
+ password: process.env.DB_PASSWORD ?? 'postgres',
+ database: process.env.DB_DATABASE ?? 'origin'
+ };
+};
+
+const config: ConnectionOptions = {
+ ...getDBConnectionOptions(),
+ entities,
+ synchronize: false,
+ migrationsRun: true,
+ migrations: ['migrations/*.ts'],
+ migrationsTableName: 'migrations_irec_issuer',
+ cli: {
+ migrationsDir: 'migrations'
+ }
+};
+
+export = config;
diff --git a/packages/traceability/issuer-irec-api/ormconfig.ts b/packages/traceability/issuer-irec-api/ormconfig.ts
new file mode 100644
index 0000000000..814d283bb6
--- /dev/null
+++ b/packages/traceability/issuer-irec-api/ormconfig.ts
@@ -0,0 +1,32 @@
+import { ConnectionOptions } from 'typeorm';
+import { entities } from './src';
+
+const getDBConnectionOptions = (): ConnectionOptions => {
+ return process.env.DATABASE_URL
+ ? {
+ type: 'postgres',
+ url: process.env.DATABASE_URL,
+ ssl: {
+ rejectUnauthorized: false
+ }
+ }
+ : {
+ type: 'postgres',
+ host: process.env.DB_HOST ?? 'localhost',
+ port: Number(process.env.DB_PORT) || 5432,
+ username: process.env.DB_USERNAME ?? 'postgres',
+ password: process.env.DB_PASSWORD ?? 'postgres',
+ database: process.env.DB_DATABASE ?? 'origin'
+ };
+};
+
+const config: ConnectionOptions = {
+ ...getDBConnectionOptions(),
+ entities,
+ synchronize: false,
+ migrationsRun: true,
+ migrations: ['migrations/*.ts'],
+ migrationsTableName: 'migrations_irec_issuer'
+};
+
+export = config;
diff --git a/packages/traceability/issuer-irec-api/package.json b/packages/traceability/issuer-irec-api/package.json
new file mode 100644
index 0000000000..780524a8c4
--- /dev/null
+++ b/packages/traceability/issuer-irec-api/package.json
@@ -0,0 +1,106 @@
+{
+ "name": "@energyweb/issuer-irec-api",
+ "version": "0.1.0",
+ "description": "NestJS module for interacting with renewable energy certificates with IREC connectivity",
+ "homepage": "https://github.com/energywebfoundation/origin/tree/master/packages/issuer-irec-api#readme",
+ "author": "EnergyWeb DevHub GmbH; Aleksandr Marenin, aleksandr.marenin@energyweb.org",
+ "license": "GPL-3.0-or-later",
+ "main": "dist/js/src/index.js",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/energywebfoundation/origin.git"
+ },
+ "bugs": {
+ "url": "https://github.com/energywebfoundation/origin/issues"
+ },
+ "bin": {
+ "issuer-irec-api-migrate": "./bin/issuer-irec-api-migrate"
+ },
+ "scripts": {
+ "build": "yarn build:ts",
+ "build:ts": "tsc -b tsconfig.json",
+ "prettier": "prettier --write --config-precedence file-override './src/**/*'",
+ "lint": "eslint \"src/**/*{.ts,.tsx}\" \"test/**/*{.ts,.tsx}\" --quiet",
+ "lint-fix": "eslint \"src/**/*{.ts,.tsx}\" \"test/**/*{.ts,.tsx}\" --quiet --fix",
+ "start-ganache": "ganache-cli -m 'chalk park staff buzz chair purchase wise oak receive avoid avoid home' -l 8000000 -e 1000000 -a 20 -p 8581 -q",
+ "clean": "shx rm -rf dist dist-shakeable",
+ "typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js --config ormconfig-dev.ts",
+ "typeorm:migrate": "yarn typeorm migration:generate -- -n",
+ "typeorm:run": "yarn typeorm migration:run",
+ "typeorm:drop": "yarn typeorm schema:drop",
+ "typeorm:dropAndMigrate": "yarn typeorm:drop && yarn typeorm:run",
+ "precommit": "lint-staged"
+ },
+ "dependencies": {
+ "@energyweb/issuer": "3.2.0",
+ "@energyweb/issuer-api": "0.2.0",
+ "@energyweb/issuer-irec-api-wrapper": "0.4.0",
+ "@energyweb/origin-backend": "10.0.1",
+ "@energyweb/origin-backend-core": "8.0.1",
+ "@energyweb/origin-backend-utils": "1.5.1",
+ "@energyweb/origin-organization-irec-api": "1.4.0",
+ "@energyweb/utils-general": "11.0.2",
+ "@nestjs/common": "7.6.17",
+ "@nestjs/config": "0.6.3",
+ "@nestjs/core": "7.6.15",
+ "@nestjs/cqrs": "7.0.1",
+ "@nestjs/passport": "7.1.5",
+ "@nestjs/schedule": "0.4.3",
+ "@nestjs/swagger": "4.8.0",
+ "@nestjs/typeorm": "7.1.5",
+ "class-validator": "0.13.1",
+ "ethers": "5.1.4",
+ "moment": "2.29.1",
+ "moment-range": "4.0.2",
+ "pg": "8.6.0",
+ "precise-proofs-js": "1.2.0",
+ "rxjs": "6.6.7",
+ "swagger-ui-express": "4.1.6",
+ "typeorm": "0.2.32"
+ },
+ "devDependencies": {
+ "typescript": "4.2.4",
+ "@nestjs/cli": "7.6.0",
+ "@nestjs/schematics": "7.3.1",
+ "@nestjs/testing": "7.6.15",
+ "@types/express": "4.17.11",
+ "@types/mocha": "8.2.2",
+ "@types/node": "14.14.37",
+ "@types/supertest": "2.0.11",
+ "@types/superagent": "4.1.10",
+ "eslint-plugin-jest": "24.3.3",
+ "polly-js": "1.8.2",
+ "prettier": "2.2.1",
+ "supertest": "6.1.3",
+ "mocha": "8.4.0",
+ "chai": "4.3.0",
+ "@types/chai": "4.2.15",
+ "ts-node": "9.1.1",
+ "ganache-cli": "6.12.2"
+ },
+ "jest": {
+ "moduleFileExtensions": [
+ "js",
+ "json",
+ "ts"
+ ],
+ "rootDir": "src",
+ "testRegex": ".spec.ts$",
+ "transform": {
+ "^.+\\.(t|j)s$": "ts-jest"
+ },
+ "coverageDirectory": "../coverage",
+ "testEnvironment": "node"
+ },
+ "publishConfig": {
+ "access": "public",
+ "registry": "https://registry.npmjs.org"
+ },
+ "files": [
+ "dist",
+ "bin"
+ ],
+ "resolutions": {
+ "tslib": "1.14.1"
+ }
+}
diff --git a/packages/traceability/issuer-irec-api/src/app.module.ts b/packages/traceability/issuer-irec-api/src/app.module.ts
new file mode 100644
index 0000000000..20531c1c3b
--- /dev/null
+++ b/packages/traceability/issuer-irec-api/src/app.module.ts
@@ -0,0 +1,52 @@
+import path from 'path';
+import fs from 'fs';
+import { Module, ValidationPipe } from '@nestjs/common';
+
+import { APP_PIPE } from '@nestjs/core';
+import { ConfigModule } from '@nestjs/config';
+import { ScheduleModule } from '@nestjs/schedule';
+import { IntUnitsOfEnergy } from '@energyweb/origin-backend-utils';
+import {
+ AppModule as OriginBackendModule,
+ FileModule,
+ UserModule
+} from '@energyweb/origin-backend';
+import { CertificateModule, BlockchainPropertiesModule } from '@energyweb/issuer-api';
+import { CertificationRequestModule } from './pods/certification-request';
+
+export const providers = [{ provide: APP_PIPE, useClass: ValidationPipe }, IntUnitsOfEnergy];
+
+const getEnvFilePath = () => {
+ const pathsToTest = ['../../../../../.env', '../../../../../../.env'];
+
+ let finalPath = null;
+
+ for (const pathToTest of pathsToTest) {
+ const resolvedPath = path.resolve(__dirname, pathToTest);
+
+ if (__dirname.includes('dist/js') && fs.existsSync(resolvedPath)) {
+ finalPath = resolvedPath;
+ break;
+ }
+ }
+
+ return finalPath;
+};
+
+@Module({
+ imports: [
+ ConfigModule.forRoot({
+ envFilePath: getEnvFilePath(),
+ isGlobal: true
+ }),
+ ScheduleModule.forRoot(),
+ OriginBackendModule,
+ UserModule,
+ FileModule,
+ CertificateModule,
+ BlockchainPropertiesModule,
+ CertificationRequestModule
+ ],
+ providers
+})
+export class AppModule {}
diff --git a/packages/traceability/issuer-irec-api/src/index.ts b/packages/traceability/issuer-irec-api/src/index.ts
new file mode 100644
index 0000000000..56d2ef38c4
--- /dev/null
+++ b/packages/traceability/issuer-irec-api/src/index.ts
@@ -0,0 +1,29 @@
+import {
+ BlockchainProperties,
+ BlockchainPropertiesModule,
+ Certificate,
+ CertificateModule,
+ CertificationRequest
+} from '@energyweb/issuer-api';
+import { entities as OriginBackendEntities } from '@energyweb/origin-backend';
+import { CertificationRequestModule, IrecCertificationRequest } from './';
+
+export * from '@energyweb/issuer-api/dist/js/src/pods/certificate';
+export * from '@energyweb/issuer-api/dist/js/src/pods/blockchain';
+
+export * from '@energyweb/issuer-api/dist/js/src/utils';
+export * from '@energyweb/issuer-api/dist/js/src/types';
+
+export { BlockchainPropertiesService } from '@energyweb/issuer-api/dist/js/src/pods/blockchain/blockchain-properties.service';
+export { AppModule, providers } from './app.module';
+export * from './pods/certification-request';
+
+export const entities = [
+ Certificate,
+ CertificationRequest,
+ IrecCertificationRequest,
+ BlockchainProperties
+];
+export const usedEntities = OriginBackendEntities;
+
+export const modules = [CertificateModule, CertificationRequestModule, BlockchainPropertiesModule];
diff --git a/packages/traceability/issuer-irec-api/src/pods/certification-request/certification-request.controller.ts b/packages/traceability/issuer-irec-api/src/pods/certification-request/certification-request.controller.ts
new file mode 100644
index 0000000000..3ce53b2404
--- /dev/null
+++ b/packages/traceability/issuer-irec-api/src/pods/certification-request/certification-request.controller.ts
@@ -0,0 +1,164 @@
+import {
+ ActiveUserGuard,
+ ExceptionInterceptor,
+ Roles,
+ RolesGuard,
+ UserDecorator
+} from '@energyweb/origin-backend-utils';
+import {
+ Body,
+ Controller,
+ Get,
+ HttpStatus,
+ Param,
+ ParseIntPipe,
+ Post,
+ Put,
+ UseGuards,
+ UseInterceptors
+} from '@nestjs/common';
+import { AuthGuard } from '@nestjs/passport';
+import { CommandBus, QueryBus } from '@nestjs/cqrs';
+import {
+ ILoggedInUser,
+ ISuccessResponse,
+ ResponseFailure,
+ Role,
+ ValidateDeviceOwnershipQuery
+} from '@energyweb/origin-backend-core';
+import { ApiBearerAuth, ApiBody, ApiResponse, ApiTags } from '@nestjs/swagger';
+
+import {
+ ApproveCertificationRequestCommand,
+ CertificateBoundToCertificationRequestCommand,
+ CreateCertificationRequestDTO,
+ GetAllCertificationRequestsQuery,
+ GetCertificationRequestByCertificateQuery,
+ GetCertificationRequestQuery,
+ RevokeCertificationRequestCommand,
+ SuccessResponseDTO,
+ ValidateCertificationRequestCommand
+} from '@energyweb/issuer-api';
+import { CreateIrecCertificationRequestCommand } from './commands';
+import { FullCertificationRequestDTO } from './full-certification-request.dto';
+
+@ApiTags('certification-requests')
+@ApiBearerAuth('access-token')
+@Controller('certification-request')
+@UseInterceptors(ExceptionInterceptor)
+export class CertificationRequestController {
+ constructor(private readonly commandBus: CommandBus, private readonly queryBus: QueryBus) {}
+
+ @Get('/:id')
+ @UseGuards(AuthGuard(), ActiveUserGuard)
+ @ApiResponse({
+ status: HttpStatus.OK,
+ type: FullCertificationRequestDTO,
+ description: 'Returns a Certification Request'
+ })
+ public async get(
+ @Param('id', new ParseIntPipe()) id: number
+ ): Promise {
+ return this.queryBus.execute(new GetCertificationRequestQuery(id));
+ }
+
+ @Get()
+ @UseGuards(AuthGuard(), ActiveUserGuard)
+ @ApiResponse({
+ status: HttpStatus.OK,
+ type: [FullCertificationRequestDTO],
+ description: 'Returns all Certification Requests'
+ })
+ public async getAll(): Promise {
+ return this.queryBus.execute(new GetAllCertificationRequestsQuery());
+ }
+
+ @Get('/:certificateId')
+ @UseGuards(AuthGuard(), ActiveUserGuard)
+ @ApiResponse({
+ status: HttpStatus.OK,
+ type: FullCertificationRequestDTO,
+ description: 'Returns a Certification Request by a certificate ID'
+ })
+ public async getByCertificate(
+ @Param('certificateId', new ParseIntPipe()) certificateId: number
+ ): Promise {
+ const validationCheck = await this.queryBus.execute<
+ CertificateBoundToCertificationRequestCommand,
+ ISuccessResponse
+ >(new CertificateBoundToCertificationRequestCommand(certificateId));
+
+ if (!validationCheck.success) {
+ return validationCheck;
+ }
+
+ return this.queryBus.execute(new GetCertificationRequestByCertificateQuery(certificateId));
+ }
+
+ @Post()
+ @UseGuards(AuthGuard(), ActiveUserGuard, RolesGuard)
+ @Roles(Role.Issuer, Role.Admin, Role.OrganizationAdmin, Role.OrganizationDeviceManager)
+ @ApiResponse({
+ status: HttpStatus.OK,
+ type: FullCertificationRequestDTO,
+ description: 'Creates a Certification Request'
+ })
+ @ApiBody({ type: CreateCertificationRequestDTO })
+ public async create(
+ @UserDecorator() user: ILoggedInUser,
+ @Body() dto: CreateCertificationRequestDTO
+ ): Promise {
+ const isOwnerOfTheDevice = await this.queryBus.execute(
+ new ValidateDeviceOwnershipQuery(user.ownerId, dto.deviceId)
+ );
+
+ if (!isOwnerOfTheDevice) {
+ return ResponseFailure('Not a device owner', HttpStatus.FORBIDDEN);
+ }
+
+ const validationCheck = await this.commandBus.execute(
+ new ValidateCertificationRequestCommand(dto)
+ );
+
+ if (!validationCheck.success) {
+ return validationCheck;
+ }
+
+ return this.commandBus.execute(
+ new CreateIrecCertificationRequestCommand(
+ user,
+ dto.to,
+ dto.energy,
+ dto.fromTime,
+ dto.toTime,
+ dto.deviceId,
+ dto.files,
+ dto.isPrivate
+ )
+ );
+ }
+
+ @Put('/:id/approve')
+ @UseGuards(AuthGuard(), ActiveUserGuard, RolesGuard)
+ @Roles(Role.Issuer, Role.Admin)
+ @ApiResponse({
+ status: HttpStatus.OK,
+ type: SuccessResponseDTO,
+ description: 'Approves a Certification Request'
+ })
+ public async approve(@Param('id', new ParseIntPipe()) id: number): Promise {
+ return this.commandBus.execute(new ApproveCertificationRequestCommand(id));
+ }
+
+ @Put('/:id/revoke')
+ @UseGuards(AuthGuard(), ActiveUserGuard, RolesGuard)
+ @Roles(Role.Issuer, Role.Admin)
+ @ApiResponse({
+ status: HttpStatus.OK,
+ type: SuccessResponseDTO,
+ description: 'Revokes a Certification Request'
+ })
+ public async revoke(@Param('id', new ParseIntPipe()) id: number): Promise {
+ return this.commandBus.execute(new RevokeCertificationRequestCommand(id));
+ }
+}
diff --git a/packages/traceability/issuer-irec-api/src/pods/certification-request/certification-request.module.ts b/packages/traceability/issuer-irec-api/src/pods/certification-request/certification-request.module.ts
new file mode 100644
index 0000000000..a2ad7c2faf
--- /dev/null
+++ b/packages/traceability/issuer-irec-api/src/pods/certification-request/certification-request.module.ts
@@ -0,0 +1,34 @@
+import { Module } from '@nestjs/common';
+import { TypeOrmModule } from '@nestjs/typeorm';
+import { CqrsModule } from '@nestjs/cqrs';
+import { ConfigModule } from '@nestjs/config';
+
+import { FileModule, UserModule } from '@energyweb/origin-backend';
+import {
+ BlockchainPropertiesModule,
+ CertificateModule,
+ Certificate,
+ SyncCertificationRequestsTask,
+ CertificationRequest
+} from '@energyweb/issuer-api';
+
+import { Handlers } from './handlers';
+import { IrecCertificationRequest } from './irec-certification-request.entity';
+import { CertificationRequestController } from './certification-request.controller';
+import { IrecCertificateService } from './irec-certificate.service';
+
+@Module({
+ imports: [
+ CqrsModule,
+ TypeOrmModule.forFeature([CertificationRequest, Certificate, IrecCertificationRequest]),
+ BlockchainPropertiesModule,
+ CertificateModule,
+ ConfigModule,
+ UserModule,
+ FileModule
+ ],
+ controllers: [CertificationRequestController],
+ providers: [...Handlers, SyncCertificationRequestsTask, IrecCertificateService],
+ exports: [...Handlers]
+})
+export class CertificationRequestModule {}
diff --git a/packages/traceability/issuer-irec-api/src/pods/certification-request/commands/create-irec-certification-request.command.ts b/packages/traceability/issuer-irec-api/src/pods/certification-request/commands/create-irec-certification-request.command.ts
new file mode 100644
index 0000000000..1e1aade297
--- /dev/null
+++ b/packages/traceability/issuer-irec-api/src/pods/certification-request/commands/create-irec-certification-request.command.ts
@@ -0,0 +1,14 @@
+import { ILoggedInUser } from '@energyweb/origin-backend-core';
+
+export class CreateIrecCertificationRequestCommand {
+ constructor(
+ public readonly user: ILoggedInUser,
+ public readonly to: string,
+ public readonly energy: string,
+ public readonly fromTime: number,
+ public readonly toTime: number,
+ public readonly deviceId: string,
+ public readonly files?: string[],
+ public readonly isPrivate?: boolean
+ ) {}
+}
diff --git a/packages/traceability/issuer-irec-api/src/pods/certification-request/commands/index.ts b/packages/traceability/issuer-irec-api/src/pods/certification-request/commands/index.ts
new file mode 100644
index 0000000000..623c1c51d1
--- /dev/null
+++ b/packages/traceability/issuer-irec-api/src/pods/certification-request/commands/index.ts
@@ -0,0 +1,16 @@
+import {
+ ApproveCertificationRequestCommand,
+ CertificateBoundToCertificationRequestCommand,
+ CreateCertificationRequestCommand,
+ RevokeCertificationRequestCommand,
+ ValidateCertificationRequestCommand
+} from '@energyweb/issuer-api';
+
+export * from './create-irec-certification-request.command';
+export {
+ ApproveCertificationRequestCommand,
+ CertificateBoundToCertificationRequestCommand,
+ CreateCertificationRequestCommand,
+ RevokeCertificationRequestCommand,
+ ValidateCertificationRequestCommand
+};
diff --git a/packages/traceability/issuer-irec-api/src/pods/certification-request/events/certification-request-status-changed.event.ts b/packages/traceability/issuer-irec-api/src/pods/certification-request/events/certification-request-status-changed.event.ts
new file mode 100644
index 0000000000..99f6c4f233
--- /dev/null
+++ b/packages/traceability/issuer-irec-api/src/pods/certification-request/events/certification-request-status-changed.event.ts
@@ -0,0 +1,9 @@
+import { IssuanceStatus } from '@energyweb/issuer-irec-api-wrapper';
+import { CertificationRequest } from '@energyweb/issuer-api';
+
+export class CertificationRequestStatusChangedEvent {
+ constructor(
+ public readonly certificationRequest: CertificationRequest,
+ public readonly status: IssuanceStatus
+ ) {}
+}
diff --git a/packages/traceability/issuer-irec-api/src/pods/certification-request/events/index.ts b/packages/traceability/issuer-irec-api/src/pods/certification-request/events/index.ts
new file mode 100644
index 0000000000..33d3e683e0
--- /dev/null
+++ b/packages/traceability/issuer-irec-api/src/pods/certification-request/events/index.ts
@@ -0,0 +1 @@
+export * from './certification-request-status-changed.event';
diff --git a/packages/traceability/issuer-irec-api/src/pods/certification-request/full-certification-request.dto.ts b/packages/traceability/issuer-irec-api/src/pods/certification-request/full-certification-request.dto.ts
new file mode 100644
index 0000000000..1de1fcf5e7
--- /dev/null
+++ b/packages/traceability/issuer-irec-api/src/pods/certification-request/full-certification-request.dto.ts
@@ -0,0 +1,20 @@
+import { ApiProperty, IntersectionType } from '@nestjs/swagger';
+import { CertificationRequestDTO } from '@energyweb/issuer-api';
+
+export class CertificationRequestFieldsIrec {
+ @ApiProperty({ type: String, required: true })
+ userId: string;
+
+ @ApiProperty({ type: String, required: false })
+ irecIssueId?: string;
+}
+
+export class IrecCertificationRequestDTO extends CertificationRequestFieldsIrec {
+ @ApiProperty({ type: Number })
+ certificationRequestId: number;
+}
+
+export class FullCertificationRequestDTO extends IntersectionType(
+ CertificationRequestDTO,
+ CertificationRequestFieldsIrec
+) {}
diff --git a/packages/traceability/issuer-irec-api/src/pods/certification-request/handlers/create-certification-request.handler.ts b/packages/traceability/issuer-irec-api/src/pods/certification-request/handlers/create-certification-request.handler.ts
new file mode 100644
index 0000000000..81b741809d
--- /dev/null
+++ b/packages/traceability/issuer-irec-api/src/pods/certification-request/handlers/create-certification-request.handler.ts
@@ -0,0 +1,104 @@
+import { CommandBus, CommandHandler, EventBus, ICommandHandler } from '@nestjs/cqrs';
+import { InjectRepository } from '@nestjs/typeorm';
+import { Repository } from 'typeorm';
+import { createReadStream } from 'fs';
+import {
+ BlockchainPropertiesService,
+ CertificationRequest,
+ CreateCertificationRequestHandler as OriginalHandler
+} from '@energyweb/issuer-api';
+import { FileService, UserService } from '@energyweb/origin-backend';
+import { CreateIrecCertificationRequestCommand } from '../commands';
+import { IrecCertificateService } from '../irec-certificate.service';
+import { FullCertificationRequestDTO } from '../full-certification-request.dto';
+import { IrecCertificationRequest } from '../irec-certification-request.entity';
+
+@CommandHandler(CreateIrecCertificationRequestCommand)
+export class CreateCertificationRequestHandler
+ extends OriginalHandler
+ implements ICommandHandler
+{
+ constructor(
+ @InjectRepository(CertificationRequest)
+ readonly repository: Repository,
+ readonly blockchainPropertiesService: BlockchainPropertiesService,
+
+ @InjectRepository(IrecCertificationRequest)
+ readonly irecRepository: Repository,
+ readonly eventBus: EventBus,
+ readonly commandBus: CommandBus,
+ readonly irecCertificateService: IrecCertificateService,
+ readonly userService: UserService,
+ readonly fileService: FileService
+ ) {
+ super(repository, blockchainPropertiesService);
+ }
+
+ async execute(
+ params: CreateIrecCertificationRequestCommand
+ ): Promise {
+ const stored = await this.createCertificationRequest(params);
+
+ this.addToQueue(stored.id);
+
+ return stored;
+ }
+
+ async createCertificationRequest(
+ params: CreateIrecCertificationRequestCommand
+ ): Promise {
+ const certificationRequest = await super.createCertificationRequest(params);
+ const irecCertificationRequest = this.irecRepository.create({
+ certificationRequestId: certificationRequest.id,
+ userId: String(params.user.id)
+ });
+ await this.irecRepository.save(irecCertificationRequest);
+
+ return { ...certificationRequest, userId: irecCertificationRequest.userId };
+ }
+
+ async process(requestId: number) {
+ const request: CertificationRequest = await this.getCertificationRequest(requestId);
+
+ if (request) {
+ await this.createIrecIssuanceRequest(request);
+ await this.mintCertificationRequest(request);
+ }
+ }
+
+ async createIrecIssuanceRequest(request: CertificationRequest): Promise {
+ const irecCertificationRequest = await this.irecRepository.findOne({
+ certificationRequestId: request.id
+ });
+ const { userId } = irecCertificationRequest;
+
+ const platformAdmin = await this.userService.getPlatformAdmin();
+
+ let fileIds: string[];
+ if (request.files?.length) {
+ const files = await Promise.all(
+ request.files.map((fileId) => this.fileService.get(fileId))
+ );
+ fileIds = await this.irecCertificateService.uploadFiles(
+ userId,
+ files.map((file) => createReadStream(file.data))
+ );
+ }
+ const irecDevice = await this.irecCertificateService.getDevice(userId, request.deviceId);
+ const platformTradeAccount = await this.irecCertificateService.getTradeAccountCode(
+ platformAdmin.id
+ );
+ const irecIssue = await this.irecCertificateService.createIrecIssue(platformAdmin.id, {
+ device: request.deviceId,
+ fuel: irecDevice.fuel,
+ recipient: platformTradeAccount,
+ start: new Date(request.fromTime),
+ end: new Date(request.toTime),
+ production: Number(request.energy)
+ });
+ await this.repository.update(request.id, { files: fileIds });
+ await this.irecRepository.update(irecCertificationRequest.certificationRequestId, {
+ irecIssueId: irecIssue.code
+ });
+ }
+}
diff --git a/packages/traceability/issuer-irec-api/src/pods/certification-request/handlers/get-all-certification-requests.handler.ts b/packages/traceability/issuer-irec-api/src/pods/certification-request/handlers/get-all-certification-requests.handler.ts
new file mode 100644
index 0000000000..2d39b3ddd2
--- /dev/null
+++ b/packages/traceability/issuer-irec-api/src/pods/certification-request/handlers/get-all-certification-requests.handler.ts
@@ -0,0 +1,46 @@
+import { IQueryHandler, QueryHandler } from '@nestjs/cqrs';
+import { InjectRepository } from '@nestjs/typeorm';
+import { In, Repository } from 'typeorm';
+
+import {
+ CertificationRequest,
+ GetAllCertificationRequestsQuery,
+ GetAllCertificationRequestsHandler as OriginalHandler
+} from '@energyweb/issuer-api';
+import { FullCertificationRequestDTO } from '../full-certification-request.dto';
+import { IrecCertificationRequest } from '../irec-certification-request.entity';
+
+@QueryHandler(GetAllCertificationRequestsQuery)
+export class GetAllCertificationRequestsHandler
+ extends OriginalHandler
+ implements IQueryHandler
+{
+ constructor(
+ @InjectRepository(CertificationRequest)
+ readonly repository: Repository,
+ @InjectRepository(IrecCertificationRequest)
+ readonly irecRepository: Repository
+ ) {
+ super(repository);
+ }
+
+ async execute({
+ query
+ }: GetAllCertificationRequestsQuery): Promise {
+ const certificationRequests = await super.execute({ query });
+ const irecCertificationRequests = await this.irecRepository.find({
+ certificationRequestId: In(certificationRequests.map((c) => c.id))
+ });
+
+ return certificationRequests.map((certificationRequest) => {
+ const irecCertificationRequest = irecCertificationRequests.find(
+ (cr) => cr.certificationRequestId === certificationRequest.id
+ );
+ return {
+ ...certificationRequest,
+ irecIssueId: irecCertificationRequest?.irecIssueId,
+ userId: irecCertificationRequest?.userId
+ };
+ });
+ }
+}
diff --git a/packages/traceability/issuer-irec-api/src/pods/certification-request/handlers/get-certification-request-by-certificate.handler.ts b/packages/traceability/issuer-irec-api/src/pods/certification-request/handlers/get-certification-request-by-certificate.handler.ts
new file mode 100644
index 0000000000..bfd1c32efb
--- /dev/null
+++ b/packages/traceability/issuer-irec-api/src/pods/certification-request/handlers/get-certification-request-by-certificate.handler.ts
@@ -0,0 +1,45 @@
+import { IQueryHandler, QueryHandler } from '@nestjs/cqrs';
+import { InjectRepository } from '@nestjs/typeorm';
+import { Repository } from 'typeorm';
+import {
+ Certificate,
+ CertificationRequest,
+ GetCertificationRequestByCertificateQuery,
+ GetCertificationRequestByCertificateHandler as OriginalHandler
+} from '@energyweb/issuer-api';
+import { IrecCertificationRequest } from '../irec-certification-request.entity';
+import { FullCertificationRequestDTO } from '../full-certification-request.dto';
+
+@QueryHandler(GetCertificationRequestByCertificateQuery)
+export class GetCertificationRequestByCertificateHandler
+ extends OriginalHandler
+ implements IQueryHandler
+{
+ constructor(
+ @InjectRepository(CertificationRequest)
+ readonly repository: Repository,
+ @InjectRepository(Certificate)
+ readonly certificateRepository: Repository,
+ @InjectRepository(IrecCertificationRequest)
+ readonly irecRepository: Repository
+ ) {
+ super(repository, certificateRepository);
+ }
+
+ async execute({
+ certificateId
+ }: GetCertificationRequestByCertificateQuery): Promise {
+ const certificationRequest = await super.execute({
+ certificateId
+ });
+ const irecCertificationRequest = await this.irecRepository.findOne({
+ certificationRequestId: certificationRequest.id
+ });
+
+ return {
+ ...certificationRequest,
+ irecIssueId: irecCertificationRequest.irecIssueId,
+ userId: irecCertificationRequest.userId
+ };
+ }
+}
diff --git a/packages/traceability/issuer-irec-api/src/pods/certification-request/handlers/get-certification-request.handler.ts b/packages/traceability/issuer-irec-api/src/pods/certification-request/handlers/get-certification-request.handler.ts
new file mode 100644
index 0000000000..3ba318e164
--- /dev/null
+++ b/packages/traceability/issuer-irec-api/src/pods/certification-request/handlers/get-certification-request.handler.ts
@@ -0,0 +1,39 @@
+import { IQueryHandler, QueryHandler } from '@nestjs/cqrs';
+import { InjectRepository } from '@nestjs/typeorm';
+import { Repository } from 'typeorm';
+
+import {
+ GetCertificationRequestQuery,
+ CertificationRequest,
+ GetCertificationRequestHandler as OriginalHandler
+} from '@energyweb/issuer-api';
+import { FullCertificationRequestDTO } from '../full-certification-request.dto';
+import { IrecCertificationRequest } from '../irec-certification-request.entity';
+
+@QueryHandler(GetCertificationRequestQuery)
+export class GetCertificationRequestHandler
+ extends OriginalHandler
+ implements IQueryHandler
+{
+ constructor(
+ @InjectRepository(CertificationRequest)
+ readonly repository: Repository,
+ @InjectRepository(IrecCertificationRequest)
+ readonly irecRepository: Repository
+ ) {
+ super(repository);
+ }
+
+ async execute({ id }: GetCertificationRequestQuery): Promise {
+ const certificationRequest = await super.execute({ id });
+ const irecCertificationRequest = await this.irecRepository.findOne({
+ certificationRequestId: id
+ });
+
+ return {
+ ...certificationRequest,
+ irecIssueId: irecCertificationRequest?.irecIssueId,
+ userId: irecCertificationRequest?.userId
+ };
+ }
+}
diff --git a/packages/traceability/issuer-irec-api/src/pods/certification-request/handlers/index.ts b/packages/traceability/issuer-irec-api/src/pods/certification-request/handlers/index.ts
new file mode 100644
index 0000000000..ae2670edba
--- /dev/null
+++ b/packages/traceability/issuer-irec-api/src/pods/certification-request/handlers/index.ts
@@ -0,0 +1,33 @@
+import {
+ ApproveCertificationRequestHandler,
+ CertificateBoundToCertificationRequestCommand,
+ RevokeCertificationRequestHandler,
+ ValidateCertificationRequestHandler
+} from '@energyweb/issuer-api';
+
+import { CreateCertificationRequestHandler } from './create-certification-request.handler';
+import { GetCertificationRequestHandler } from './get-certification-request.handler';
+import { GetAllCertificationRequestsHandler } from './get-all-certification-requests.handler';
+import { GetCertificationRequestByCertificateHandler } from './get-certification-request-by-certificate.handler';
+
+export {
+ ApproveCertificationRequestHandler,
+ CertificateBoundToCertificationRequestCommand,
+ CreateCertificationRequestHandler,
+ GetAllCertificationRequestsHandler,
+ GetCertificationRequestHandler,
+ GetCertificationRequestByCertificateHandler,
+ RevokeCertificationRequestHandler,
+ ValidateCertificationRequestHandler
+};
+
+export const Handlers = [
+ ApproveCertificationRequestHandler,
+ CertificateBoundToCertificationRequestCommand,
+ CreateCertificationRequestHandler,
+ GetAllCertificationRequestsHandler,
+ GetCertificationRequestHandler,
+ GetCertificationRequestByCertificateHandler,
+ RevokeCertificationRequestHandler,
+ ValidateCertificationRequestHandler
+];
diff --git a/packages/traceability/issuer-irec-api/src/pods/certification-request/index.ts b/packages/traceability/issuer-irec-api/src/pods/certification-request/index.ts
new file mode 100644
index 0000000000..7199ab6a56
--- /dev/null
+++ b/packages/traceability/issuer-irec-api/src/pods/certification-request/index.ts
@@ -0,0 +1,13 @@
+export * from '@energyweb/issuer-api/dist/js/src/pods/certification-request/events';
+export * from '@energyweb/issuer-api/dist/js/src/pods/certification-request/queries';
+export * from '@energyweb/issuer-api/dist/js/src/pods/certification-request/certification-request-status.enum';
+export * from '@energyweb/issuer-api/dist/js/src/pods/certification-request/sync-certification-request.task';
+
+export * from './handlers';
+export * from './commands';
+export * from './events';
+export * from './certification-request.controller';
+export * from './full-certification-request.dto';
+export * from './irec-certification-request.entity';
+export * from './certification-request.module';
+export * from './irec-certificate.service';
diff --git a/packages/traceability/issuer-irec-api/src/pods/certification-request/irec-certificate.service.ts b/packages/traceability/issuer-irec-api/src/pods/certification-request/irec-certificate.service.ts
new file mode 100644
index 0000000000..4827c8b2ee
--- /dev/null
+++ b/packages/traceability/issuer-irec-api/src/pods/certification-request/irec-certificate.service.ts
@@ -0,0 +1,115 @@
+import { ILoggedInUser } from '@energyweb/origin-backend-core';
+import { BadRequestException, ForbiddenException, Injectable } from '@nestjs/common';
+import { ConfigService } from '@nestjs/config';
+import { CommandBus } from '@nestjs/cqrs';
+import {
+ AccessTokens,
+ Account,
+ AccountType,
+ Device,
+ IRECAPIClient,
+ Issue,
+ IssuanceStatus,
+ IssueWithStatus
+} from '@energyweb/issuer-irec-api-wrapper';
+import {
+ GetConnectionCommand,
+ RefreshTokensCommand
+} from '@energyweb/origin-organization-irec-api';
+import { ReadStream } from 'fs';
+
+export type UserIdentifier = ILoggedInUser | string | number;
+
+@Injectable()
+export class IrecCertificateService {
+ constructor(
+ private readonly commandBus: CommandBus,
+ private readonly configService: ConfigService
+ ) {}
+
+ isIrecIntegrationEnabled(): boolean {
+ return !!this.configService.get('IREC_API_URL');
+ }
+
+ private async getIrecClient(user: UserIdentifier | string | number) {
+ const irecConnection = await this.commandBus.execute(new GetConnectionCommand(user));
+
+ if (!irecConnection) {
+ throw new ForbiddenException('User does not have an IREC connection');
+ }
+
+ const client = new IRECAPIClient(this.configService.get('IREC_API_URL'), {
+ accessToken: irecConnection.accessToken,
+ refreshToken: irecConnection.refreshToken,
+ expiryDate: irecConnection.expiryDate
+ });
+
+ client.on('tokensRefreshed', (accessToken: AccessTokens) => {
+ this.commandBus.execute(new RefreshTokensCommand(user, accessToken));
+ });
+
+ return client;
+ }
+
+ async createIrecIssue(user: UserIdentifier, issue: Issue): Promise {
+ if (!this.isIrecIntegrationEnabled()) {
+ return {
+ ...issue,
+ status: IssuanceStatus.InProgress,
+ code: ''
+ };
+ }
+ const irecClient = await this.getIrecClient(user);
+ const irecIssue: IssueWithStatus = await irecClient.issue.create(issue);
+ await irecClient.issue.submit(irecIssue.code);
+ irecIssue.status = IssuanceStatus.InProgress;
+ return irecIssue;
+ }
+
+ async update(user: UserIdentifier, code: string, issue: Issue): Promise {
+ if (!this.isIrecIntegrationEnabled()) {
+ return {
+ ...issue,
+ status: IssuanceStatus.InProgress,
+ code
+ } as IssueWithStatus;
+ }
+
+ const irecClient = await this.getIrecClient(user);
+ const irecIssue = await irecClient.issue.get(code);
+ if (irecIssue.status === IssuanceStatus.InProgress) {
+ throw new BadRequestException('Issue in "In Progress" state is not available to edit');
+ }
+
+ await irecClient.issue.update(code, issue);
+ const updatedIredIssue = await irecClient.issue.get(code);
+ await irecClient.device.submit(code);
+ updatedIredIssue.status = IssuanceStatus.InProgress;
+ return updatedIredIssue;
+ }
+
+ async getIssue(user: UserIdentifier, code: string): Promise {
+ const irecClient = await this.getIrecClient(user);
+ return irecClient.issue.get(code);
+ }
+
+ async getDevice(user: UserIdentifier, code: string): Promise {
+ const irecClient = await this.getIrecClient(user);
+ return irecClient.device.get(code);
+ }
+
+ async uploadFiles(user: UserIdentifier, files: Blob[] | ReadStream[]) {
+ const irecClient = await this.getIrecClient(user);
+ return irecClient.file.upload(files);
+ }
+
+ async getAccountInfo(user: UserIdentifier): Promise {
+ const irecClient = await this.getIrecClient(user);
+ return irecClient.account.getAll();
+ }
+
+ async getTradeAccountCode(user: UserIdentifier): Promise {
+ const accounts = await this.getAccountInfo(user);
+ return accounts.find((account: Account) => account.type === AccountType.Trade)?.code || '';
+ }
+}
diff --git a/packages/traceability/issuer-irec-api/src/pods/certification-request/irec-certification-request.entity.ts b/packages/traceability/issuer-irec-api/src/pods/certification-request/irec-certification-request.entity.ts
new file mode 100644
index 0000000000..59ad812a1e
--- /dev/null
+++ b/packages/traceability/issuer-irec-api/src/pods/certification-request/irec-certification-request.entity.ts
@@ -0,0 +1,20 @@
+import { ExtendedBaseEntity } from '@energyweb/origin-backend-utils';
+import { Column, Entity, PrimaryColumn } from 'typeorm';
+import { IrecCertificationRequestDTO } from './full-certification-request.dto';
+
+export const CERTIFICATION_REQUESTS_TABLE_NAME = 'irec_issuer_certification_request';
+
+@Entity({ name: CERTIFICATION_REQUESTS_TABLE_NAME })
+export class IrecCertificationRequest
+ extends ExtendedBaseEntity
+ implements IrecCertificationRequestDTO
+{
+ @PrimaryColumn()
+ certificationRequestId: number;
+
+ @Column()
+ userId: string;
+
+ @Column()
+ irecIssueId: string;
+}
diff --git a/packages/traceability/issuer-irec-api/tsconfig.json b/packages/traceability/issuer-irec-api/tsconfig.json
new file mode 100644
index 0000000000..8cc2ae200e
--- /dev/null
+++ b/packages/traceability/issuer-irec-api/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "extends": "../../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./dist/js",
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "target": "ES2020",
+ "types": ["node", "mocha"],
+ "baseUrl": "./"
+ },
+ "include": ["src/**/*", "test/**/*", "migrations/*", "ormconfig*.ts"]
+}
diff --git a/rush.json b/rush.json
index ea6ddb212a..e6f47700dc 100644
--- a/rush.json
+++ b/rush.json
@@ -472,6 +472,14 @@
"packageName": "@energyweb/issuer-api-client",
"projectFolder": "packages/traceability/issuer-api-client"
},
+ {
+ "packageName": "@energyweb/issuer-irec-api",
+ "projectFolder": "packages/traceability/issuer-irec-api"
+ },
+ {
+ "packageName": "@energyweb/issuer-irec-api-client",
+ "projectFolder": "packages/traceability/issuer-irec-api-client"
+ },
{
"packageName": "@energyweb/issuer-irec-api-wrapper",
"projectFolder": "packages/traceability/issuer-irec-api-wrapper"