diff --git a/backend/postman/CC Portal develop.postman_collection.json b/backend/postman/CC Portal develop.postman_collection.json index a7986dfe..10d6daba 100644 --- a/backend/postman/CC Portal develop.postman_collection.json +++ b/backend/postman/CC Portal develop.postman_collection.json @@ -1,7 +1,7 @@ { "info": { - "_postman_id": "374f247a-1ea9-4c6a-8a4f-5b5eaa052036", - "name": "CC Portal develop", + "_postman_id": "e842d76b-56e2-476d-b31d-bbeffd0853bc", + "name": "CC Portal develop Copy", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", "_exporter_id": "20133713" }, @@ -623,6 +623,32 @@ } }, "response": [] + }, + { + "name": "Get IPNS URL", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "{{accessToken}}", + "type": "text" + } + ], + "url": { + "raw": "{{base-url}}/api/constitution/ipns/url", + "host": [ + "{{base-url}}" + ], + "path": [ + "api", + "constitution", + "ipns", + "url" + ] + } + }, + "response": [] } ] }, diff --git a/backend/src/constitution/api/constitution.controller.ts b/backend/src/constitution/api/constitution.controller.ts index cfaba99a..9bef1f28 100644 --- a/backend/src/constitution/api/constitution.controller.ts +++ b/backend/src/constitution/api/constitution.controller.ts @@ -10,6 +10,7 @@ import { HttpStatus, } from '@nestjs/common'; import { + ApiBearerAuth, ApiBody, ApiConsumes, ApiOperation, @@ -25,6 +26,7 @@ import { PermissionGuard } from 'src/auth/guard/permission.guard'; import { FileInterceptor } from '@nestjs/platform-express'; import { ConstitutionMetadataResponse } from './response/constitution-metadata.response'; import { getFileValidator } from '../pipe/fileValidatorPipe'; +import { ApiConditionalExcludeEndpoint } from 'src/common/decorators/api-conditional-exclude-endpoint.decorator'; @ApiTags('Constitution') @Controller('constitution') @@ -60,6 +62,7 @@ export class ConstitutionController { return await this.constitutionFacade.getConstitutionFileByCid(cid); } + @ApiConditionalExcludeEndpoint() @ApiOperation({ summary: 'Store constitution file' }) @ApiBody({ schema: { @@ -125,4 +128,21 @@ export class ConstitutionController { async getAllConstitutionMetadata(): Promise { return this.constitutionFacade.getAllConstitutionMetadata(); } + + @ApiConditionalExcludeEndpoint() + @ApiOperation({ + summary: 'Get IPNS URL for constitution', + }) + @ApiBearerAuth('JWT-auth') + @ApiResponse({ + status: 200, + description: 'Returns a IPNS URL', + type: ConstitutionMetadataResponse, + }) + @Permissions(PermissionEnum.ADD_CONSTITUTION) + @UseGuards(JwtAuthGuard, PermissionGuard) + @Get('ipns/url') + async getIpnsUrl() { + return await this.constitutionFacade.getIpnsUrl(); + } } diff --git a/backend/src/constitution/api/response/constitutio-ipns-url.response.ts b/backend/src/constitution/api/response/constitutio-ipns-url.response.ts new file mode 100644 index 00000000..74242502 --- /dev/null +++ b/backend/src/constitution/api/response/constitutio-ipns-url.response.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Expose } from 'class-transformer'; + +export class ConstitutionIpnsUrlResponse { + @ApiProperty({ + description: 'IPNS URL related to a deployed Constitution', + example: + 'https://ipfs.io/ipns/QmVcVq1zssBLdcuw8nM19VYiLWjt9cMQSUrHSwVd5oY4s2', + }) + @Expose({ name: 'ipns_url' }) + ipnsUrl: string; +} diff --git a/backend/src/constitution/facade/constitution.facade.ts b/backend/src/constitution/facade/constitution.facade.ts index 281390c6..91892be8 100644 --- a/backend/src/constitution/facade/constitution.facade.ts +++ b/backend/src/constitution/facade/constitution.facade.ts @@ -6,6 +6,7 @@ import { ConstitutionDto } from 'src/redis/dto/constitution.dto'; import { ConstitutionService } from '../services/constitution.service'; import { IpfsService } from 'src/ipfs/services/ipfs.service'; import { ConstitutionMetadataResponse } from '../api/response/constitution-metadata.response'; +import { ConstitutionIpnsUrlResponse } from '../api/response/constitutio-ipns-url.response'; @Injectable() export class ConstitutionFacade { @@ -63,4 +64,9 @@ export class ConstitutionFacade { ConstitutionMapper.ipfsMetadataDtoToConstitutionResponse(metadataDto), ); } + + async getIpnsUrl(): Promise { + const ipnsUrl = await this.ipfsService.getIpnsUrl(); + return ConstitutionMapper.ipnsUrlToResponse(ipnsUrl); + } } diff --git a/backend/src/constitution/mapper/constitution.mapper.ts b/backend/src/constitution/mapper/constitution.mapper.ts index 5ec6b756..4ddf0582 100644 --- a/backend/src/constitution/mapper/constitution.mapper.ts +++ b/backend/src/constitution/mapper/constitution.mapper.ts @@ -5,6 +5,7 @@ import { CreateConstitutionDto } from '../dto/create-constitution.dto'; import { IpfsContentDto } from 'src/ipfs/dto/ipfs-content.dto'; import { IpfsMetadataDto } from 'src/ipfs/dto/ipfs-metadata.dto'; import { ConstitutionMetadataResponse } from '../api/response/constitution-metadata.response'; +import { ConstitutionIpnsUrlResponse } from '../api/response/constitutio-ipns-url.response'; import { IPFS_PUBLIC_URL } from 'src/common/constants/ipfs.constants'; export class ConstitutionMapper { @@ -48,4 +49,10 @@ export class ConstitutionMapper { return constitutionResponse; } + + static ipnsUrlToResponse(ipnsUrl: string): ConstitutionIpnsUrlResponse { + const constitutionIpnsUrlResponse = new ConstitutionIpnsUrlResponse(); + constitutionIpnsUrlResponse.ipnsUrl = ipnsUrl; + return constitutionIpnsUrlResponse; + } } diff --git a/backend/src/ipfs/services/ipfs.service.ts b/backend/src/ipfs/services/ipfs.service.ts index 18367efe..30054ab2 100644 --- a/backend/src/ipfs/services/ipfs.service.ts +++ b/backend/src/ipfs/services/ipfs.service.ts @@ -176,4 +176,19 @@ export class IpfsService { contents: content, }; } + + async getIpnsUrl(): Promise { + const apiLink = + this.configService.getOrThrow('IPFS_SERVICE_URL') + '/ipfs/ipns/url'; + try { + const response = await axios.get(apiLink); + const ipnsUrl = response.data; + return ipnsUrl; + } catch (error) { + this.logger.error(`Error when getting IPNS URL from IPFS: ${error}`); + throw new InternalServerErrorException( + `Error when getting IPNS URL from IPFS service`, + ); + } + } } diff --git a/ipfs-service/example.env b/ipfs-service/example.env index 1fffecd0..acbbf98f 100644 --- a/ipfs-service/example.env +++ b/ipfs-service/example.env @@ -6,4 +6,6 @@ ANNOUNCE_TCP_ADDRESS='/ip4//tcp/4001' ANNOUNCE_WS_ADDRESS='/ip4//tcp/4003/ws' IPFS_PUBLIC_URL='https://ipfs.io/ipfs/' -IPNS_PUBLIC_URL='https://ipfs.io/ipns/' \ No newline at end of file +IPNS_PUBLIC_URL='https://ipfs.io/ipns/' + +IPNS_CONSTITUTION_KEY_NAME='some-key-name' \ No newline at end of file diff --git a/ipfs-service/src/app.controller.ts b/ipfs-service/src/app.controller.ts index 7b018d00..666c95c9 100644 --- a/ipfs-service/src/app.controller.ts +++ b/ipfs-service/src/app.controller.ts @@ -40,4 +40,9 @@ export class AppController { } return doc } + + @Get('ipns/url') + async getIpnsUrl(): Promise { + return await this.appService.getIpnsUrl(); + } } diff --git a/ipfs-service/src/app.service.ts b/ipfs-service/src/app.service.ts index a4852797..94a04430 100644 --- a/ipfs-service/src/app.service.ts +++ b/ipfs-service/src/app.service.ts @@ -148,7 +148,7 @@ export class AppService implements OnModuleInit { async getIpns() { this.ipns = ipns(this.helia); - const keyName = 'my-key11'; + const keyName = process.env.IPNS_CONSTITUTION_KEY_NAME; const existingKeys = await this.helia.libp2p.services.keychain.listKeys(); // if keyName already exists if (existingKeys.some((x) => x.name === keyName)) { @@ -264,4 +264,12 @@ export class AppService implements OnModuleInit { attemptToProvide(); } + + async getIpnsUrl(): Promise { + if (!this.ipnsPeerId) { + throw new InternalServerErrorException(`IPNS Peer Id not exists`); + } + const ipnsUrl = process.env.IPNS_PUBLIC_URL + this.ipnsPeerId.toString() + return ipnsUrl; + } }