import { ReadStream } from 'fs' ; import { BadRequestException , ForbiddenException , Injectable , NotFoundException , UnauthorizedException } from '@nestjs/common' ; import { CommandBus } from '@nestjs/cqrs' ; import { ConfigService } from '@nestjs/config' ; import { AccessTokens , Account , AccountItem , AccountType , ApproveTransaction , Beneficiary , BeneficiaryUpdateParams , CreateAccountParams , Device , Device as IrecDevice , DeviceCreateParams , DeviceState , DeviceUpdateParams , IRECAPIClient , IssuanceStatus , Issue , IssueWithStatus , Organisation , RedeemTransactionResult , ReservationItem , TransactionResult } from '@energyweb/issuer-irec-api-wrapper' ; import { ILoggedInUser } from '@energyweb/origin-backend-core' ; import { TUserBaseEntity , UserService } from '@energyweb/origin-backend' ; import { CreateConnectionDTO } from './dto' ; import { ConnectionDTO , GetConnectionCommand , RefreshTokensCommand } from '../connection' ; export type UserIdentifier = ILoggedInUser | string | number ; export interface ICreateBeneficiary { name : string ; countryCode : string ; location : string ; } export interface IClaimData { beneficiary : string ; location : string ; countryCode : string ; periodStartDate : string ; periodEndDate : string ; purpose : string ; } export interface IIrecService { getConnectionInfo ( user ) : Promise < ConnectionDTO > ; login ( { userName , password , clientId , clientSecret } ) : Promise < AccessTokens > ; createBeneficiary ( user , beneficiary ) : Promise < Beneficiary > ; updateBeneficiary ( user , beneficiaryId , params ) : Promise < Beneficiary > ; createDevice ( user , deviceData ) : Promise < IrecDevice > ; updateDevice ( user , code , device ) : Promise < IrecDevice > ; getDevice ( user , code ) : Promise < Device > ; getDevices ( user ) : Promise < IrecDevice [ ] > ; getAccountInfo ( user ) : Promise < Account [ ] > ; getAccountInfoByTokens ( clientId , clientSecret , tokens ) : Promise < Account [ ] > ; getTradeAccountCode ( user ) : Promise < string > ; getIssueAccountCode ( user ) : Promise < string > ; getRedemptionAccountCode ( user ) : Promise < string > ; createAccount ( user , params ) : Promise < void > ; createAccountByTokens ( clientId , clientSecret , tokens , params ) : Promise < void > ; createIssueRequest ( user , issue ) : Promise < IssueWithStatus > ; updateIssueRequest ( user , code , issue ) : Promise < IssueWithStatus > ; getIssueRequest ( user , code ) : Promise < IssueWithStatus > ; uploadFiles ( user , files ) : Promise < string [ ] > ; verifyIssueRequest ( user , issueRequestCode ) : Promise < void > ; approveIssueRequest ( user , issueRequestCode , issuerAccountCode ) : Promise < ApproveTransaction > ; rejectIssueRequest ( user , issueRequestCode ) : Promise < void > ; getCertificates ( user , accountCode ? ) : Promise < AccountItem [ ] > ; transferCertificate ( fromUser , toTradeAccount , assetId , fromTradeAccount ? ) : Promise < TransactionResult > ; redeem ( user , assetId , claimData ) : Promise < RedeemTransactionResult > ; approveDevice ( user , deviceId ) : Promise < IrecDevice > ; rejectDevice ( user , deviceId ) : Promise < IrecDevice > ; getUserOrganization ( user ) : Promise < Organisation > ; getUserOrganizationByTokens ( clientId , clientSecret , tokens ) : Promise < Organisation > ; } @ Injectable ( ) export class IrecService implements IIrecService { private platformAdmin : TUserBaseEntity ; constructor ( private readonly commandBus , private readonly configService , private readonly userService ) { } public isSingleUserMode ( ) { return this . configService . get < string > ( 'IREC_API_MODE' ) === 'single' ; } public async getConnectionInfo ( user ) { return await this . commandBus . execute ( new GetConnectionCommand ( user ) ) ; } private async getPlatformAdmin ( ) { if ( ! this . platformAdmin ) { this . platformAdmin = await this . userService . getPlatformAdmin ( ) ; } return this . platformAdmin ; } private async getIrecClient ( u ) { const user = this . isSingleUserMode ( ) ? ( await this . getPlatformAdmin ( ) ) . organization . id : u ; const irecConnection = await this . getConnectionInfo ( user ) ; if ( ! irecConnection ) { throw new ForbiddenException ( 'User does not have an IREC connection' ) ; } if ( ! irecConnection . active ) { throw new UnauthorizedException ( 'IREC connection is no longer active, please consider update IREC connection credentials' ) ; } const accessTokens = { accessToken : irecConnection . accessToken , refreshToken : irecConnection . refreshToken , expiryDate : irecConnection . expiryDate } ; return new IRECAPIClient ( this . configService . get < string > ( 'IREC_API_URL' ) , irecConnection . clientId , irecConnection . clientSecret , async ( newTokens ) => { await this . commandBus . execute ( new RefreshTokensCommand ( user , newTokens ) ) ; } , accessTokens ) ; } private async getIrecClientByTokens ( clientId , clientSecret , tokens ) { return new IRECAPIClient ( this . configService . get < string > ( 'IREC_API_URL' ) , clientId , clientSecret , undefined , tokens ) ; } async login ( { userName , password , clientId , clientSecret } ) { const client = new IRECAPIClient ( this . configService . get < string > ( 'IREC_API_URL' ) , clientId , clientSecret ) ; return client . login ( userName , password ) ; } async createBeneficiary ( user , beneficiary ) { const irecClient = await this . getIrecClient ( user ) ; return await irecClient . beneficiary . create ( { name : beneficiary . name , countryCode : beneficiary . countryCode , location : beneficiary . location , active : true } ) ; } async updateBeneficiary ( user , beneficiaryId , params ) { const irecClient = await this . getIrecClient ( user ) ; return await irecClient . beneficiary . update ( beneficiaryId , params ) ; } async createDevice ( user , deviceData ) { const irecClient = await this . getIrecClient ( user ) ; const irecDevice = await irecClient . device . create ( deviceData ) ; await irecClient . device . submit ( irecDevice . code ) ; irecDevice . status = DeviceState . InProgress ; return irecDevice ; } async updateDevice ( user , code , device ) { const irecClient = await this . getIrecClient ( user ) ; const irecDevice = await irecClient . device . get ( code ) ; if ( irecDevice . status === DeviceState . InProgress ) { throw new BadRequestException ( 'Device in "In Progress" state is not available to edit' ) ; } const updatedIredDevice = await irecClient . device . edit ( code , device ) ; await irecClient . device . submit ( code ) ; updatedIredDevice . status = DeviceState . InProgress ; return updatedIredDevice ; } async getDevice ( user , code ) { const irecClient = await this . getIrecClient ( user ) ; return irecClient . device . get ( code ) ; } async getDevices ( user ) { const irecClient = await this . getIrecClient ( user ) ; return irecClient . device . getAll ( ) ; } async getAccountInfo ( user ) { const irecClient = await this . getIrecClient ( user ) ; return irecClient . account . getAll ( ) ; } async getTradeAccountCode ( user ) { const accounts = await this . getAccountInfo ( user ) ; return accounts . find ( ( account ) => account . type === AccountType . Trade ) ?. code || '' ; } async getIssueAccountCode ( user ) { const accounts = await this . getAccountInfo ( user ) ; return accounts . find ( ( account ) => account . type === AccountType . Issue ) ?. code || '' ; } async getRedemptionAccountCode ( user ) { const accounts = await this . getAccountInfo ( user ) ; return accounts . find ( ( account ) => account . type === AccountType . Issue ) ?. code || '' ; } async createAccount ( user , params ) { const irecClient = await this . getIrecClient ( user ) ; await irecClient . account . create ( params ) ; } async createAccountByTokens ( clientId , clientSecret , tokens , params ) { const irecClient = await this . getIrecClientByTokens ( clientId , clientSecret , tokens ) ; await irecClient . account . create ( params ) ; } async createIssueRequest ( user , issue ) { const irecClient = await this . getIrecClient ( user ) ; const irecIssue = await irecClient . issue . create ( issue ) ; await irecClient . issue . submit ( irecIssue . code ) ; irecIssue . status = IssuanceStatus . InProgress ; return irecIssue ; } async updateIssueRequest ( user , code , issue ) { 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 getIssueRequest ( user , code ) { const irecClient = await this . getIrecClient ( user ) ; return irecClient . issue . get ( code ) ; } async uploadFiles ( user , files ) { const irecClient = await this . getIrecClient ( user ) ; return irecClient . file . upload ( files ) ; } async verifyIssueRequest ( user , issueRequestCode ) { const irecClient = await this . getIrecClient ( user ) ; await irecClient . issue . verify ( issueRequestCode ) ; } async approveIssueRequest ( user , issueRequestCode , issuerAccountCode ) { const irecClient = await this . getIrecClient ( user ) ; return irecClient . issue . approve ( issueRequestCode , { issuer : issuerAccountCode } ) ; } async rejectIssueRequest ( user , issueRequestCode ) { const irecClient = await this . getIrecClient ( user ) ; return irecClient . issue . reject ( issueRequestCode ) ; } async getCertificates ( user , accountCode ? ) { const irecClient = await this . getIrecClient ( user ) ; return irecClient . account . getItems ( accountCode || ( await this . getTradeAccountCode ( user ) ) ) ; } async transferCertificate ( fromUser , toTradeAccount , assetId , fromTradeAccount ? ) { const fromUserClient = await this . getIrecClient ( fromUser ) ; const fromUserConnectionInfo = await this . getConnectionInfo ( fromUser ) ; const fromUserTradeAccount = fromTradeAccount || ( await this . getTradeAccountCode ( fromUser ) ) ; const items = await fromUserClient . account . getItems ( fromUserTradeAccount ) ; const item = items . find ( ( i ) => i . asset === assetId ) ; if ( ! item ) { throw new NotFoundException ( 'IREC item not found' ) ; } const transferItem = new ReservationItem ( ) ; transferItem . code = item . code ; transferItem . amount = item . volume ; return fromUserClient . transfer ( { sender : fromUserTradeAccount , recipient : toTradeAccount , approver : fromUserConnectionInfo . userName , volume : transferItem . amount , items : [ transferItem ] , notes : 'Transfer certificate from origin to third-party organization' } ) ; } async redeem ( user , assetId , claimData , fromTradeAccount ? , toRedemptionAccount ? ) { const userClient = await this . getIrecClient ( user ) ; const userConnectionInfo = await this . getConnectionInfo ( user ) ; const userTradeAccount = fromTradeAccount || ( await this . getTradeAccountCode ( user ) ) ; const accountTo = toRedemptionAccount || ( await this . getRedemptionAccountCode ( user ) ) ; const items = await userClient . account . getItems ( userTradeAccount ) ; const item = items . find ( ( i ) => i . asset === assetId ) ; if ( ! item ) { throw new NotFoundException ( 'IREC item not found' ) ; } const claimItem = new ReservationItem ( ) ; claimItem . code = item . code ; claimItem . amount = item . volume ; return userClient . redeem ( { sender : userTradeAccount , recipient : accountTo , approver : userConnectionInfo . userName , volume : claimItem . amount , items : [ claimItem ] , notes : '' , beneficiary : Number ( claimData . beneficiary ) , start : new Date ( claimData . periodStartDate ) , end : new Date ( claimData . periodEndDate ) , purpose : claimData . purpose } ) ; } async approveDevice ( user , code ) { const irecClient = await this . getIrecClient ( user ) ; const device = await irecClient . device . get ( code ) ; const allowedStatuses = [ DeviceState . InProgress , DeviceState . Submitted ] ; if ( ! allowedStatuses . includes ( device . status ) ) { throw new Error ( 'To approve IREC device its state has to be In-Progress' ) ; } await irecClient . device . verify ( code ) ; await irecClient . device . approve ( code ) ; device . status = DeviceState . Approved ; return device ; } async rejectDevice ( user , code ) { const irecClient = await this . getIrecClient ( user ) ; const device = await irecClient . device . get ( code ) ; if ( device . status !== DeviceState . InProgress ) { throw new Error ( 'To reject IREC device its state have to be In-Progress' ) ; } await irecClient . device . reject ( code ) ; device . status = DeviceState . Rejected ; return device ; } async getUserOrganization ( user ) { const irecClient = await this . getIrecClient ( user ) ; return irecClient . organisation . get ( ) ; } async getUserOrganizationByTokens ( clientId , clientSecret , tokens ) { const irecClient = await this . getIrecClientByTokens ( clientId , clientSecret , tokens ) ; return irecClient . organisation . get ( ) ; } async getAccountInfoByTokens ( clientId , clientSecret , tokens ) { const irecClient = await this . getIrecClientByTokens ( clientId , clientSecret , tokens ) ; return irecClient . account . getAll ( ) ; } }