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 +
+ 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 +
+ 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"