Skip to content
Snippets Groups Projects
Commit 65b27e27 authored by Steffen Schulze's avatar Steffen Schulze
Browse files

Merge branch 'chore/e2e' into 'main'

End to end run preparation

See merge request !40
parents 6b1697e3 e98a5ee8
No related branches found
No related tags found
1 merge request!40End to end run preparation
Pipeline #40246 failed
Showing
with 490 additions and 177 deletions
......@@ -11,6 +11,7 @@
# ... in these directories
!apps/**/src/*
!devtools/**/src/*
# Explicitly ignore these locations
node_modules
......
......@@ -20,7 +20,7 @@ module.exports = {
},
'import/resolver': {
typescript: {
project: 'packages/*/tsconfig.json',
project: ['apps/*/tsconfig.json', 'devtools/tsconfig.json'],
alwaysTryTypes: true,
},
},
......@@ -75,7 +75,7 @@ module.exports = {
},
overrides: [
{
files: ['*.spec.ts', '*.e2e-spec.ts', '**/tests/**', 'scripts/*.ts', 'scripts/*.mts'],
files: ['*.spec.ts', '*.e2e-spec.ts', '**/tests/**', '**/test/**'],
env: {
jest: true,
node: true,
......@@ -89,5 +89,11 @@ module.exports = {
],
},
},
{
files: ['devtools/**/*.ts'],
rules: {
'no-console': 'off',
}
}
],
};
......@@ -4,6 +4,7 @@
# Except for these files
!*.ts
!*.d.ts
!*.mts
!jest.config.js
# .. also in subdirectories
......@@ -11,6 +12,7 @@
# ... in these ones
!apps/**/src/*
!devtools/**/src/*
# Explicitly ignore these locations
node_modules
......
# Base
# Base
FROM node:20 AS base
ARG APP_HOME=/home/node/app
......@@ -8,27 +8,64 @@ WORKDIR ${APP_HOME}
RUN corepack enable
# Build
FROM base AS build
ARG APP_HOME=/home/node/app
WORKDIR ${APP_HOME}
# Dependencies
FROM base AS dependencies
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig*.json .swcrc ./
COPY apps/${SERVICE}/package.json ./apps/${SERVICE}/
COPY patches ./patches
COPY apps/shared/package.json ./apps/shared/
COPY devtools/package.json ./devtools/
RUN pnpm install --frozen-lockfile
COPY apps/${SERVICE} ./apps/${SERVICE}
# Build shared
FROM base AS build-shared
COPY apps/shared ./apps/shared
COPY --from=dependencies ${APP_HOME}/package.json ${APP_HOME}/pnpm-lock.yaml ${APP_HOME}/pnpm-workspace.yaml ${APP_HOME}/tsconfig*.json ${APP_HOME}/.swcrc ./
COPY --from=dependencies ${APP_HOME}/node_modules ./node_modules
COPY --from=dependencies ${APP_HOME}/apps/shared/node_modules ./apps/shared/node_modules
COPY --from=dependencies ${APP_HOME}/patches ./patches
RUN pnpm --filter shared build
RUN pnpm --filter ${SERVICE} build
RUN pnpm --filter ${SERVICE} --prod deploy build
RUN pnpm --filter shared --prod deploy shared
# Build DevTools
FROM base AS build-devtools
COPY --from=dependencies ${APP_HOME}/package.json ${APP_HOME}/pnpm-lock.yaml ${APP_HOME}/pnpm-workspace.yaml ${APP_HOME}/tsconfig*.json ${APP_HOME}/.swcrc ./
COPY --from=dependencies ${APP_HOME}/node_modules ./node_modules
COPY --from=dependencies ${APP_HOME}/devtools/node_modules ./devtools/node_modules
COPY --from=dependencies ${APP_HOME}/patches ./patches
COPY --from=build-shared ${APP_HOME}/apps/shared ./apps/shared
COPY devtools ./devtools
RUN pnpm --filter devtools build && pnpm --filter devtools --prod deploy build
# Final devtools
FROM node:20-slim AS devtools
ARG APP_HOME=/home/node/app
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}
WORKDIR ${APP_HOME}
CMD ["node", "dist/server.js"]
COPY --from=build-devtools --chown=node:node ${APP_HOME}/build/dist ./dist
COPY --from=build-devtools --chown=node:node ${APP_HOME}/build/node_modules ./node_modules
COPY --from=build-devtools --chown=node:node ${APP_HOME}/build/package.json .
USER node
# Build service
FROM base AS build-service
COPY --from=dependencies ${APP_HOME}/package.json ${APP_HOME}/pnpm-lock.yaml ${APP_HOME}/pnpm-workspace.yaml ${APP_HOME}/tsconfig*.json ${APP_HOME}/.swcrc ./
COPY --from=dependencies ${APP_HOME}/node_modules ./node_modules
COPY --from=dependencies ${APP_HOME}/patches ./patches
COPY --from=build-shared ${APP_HOME}/apps/shared ./apps/shared
COPY apps/${SERVICE} ./apps/${SERVICE}
RUN pnpm install --frozen-lockfile && pnpm --filter ${SERVICE} build && pnpm --filter ${SERVICE} --prod deploy build
# Final
FROM node:20 AS final
FROM node:20-slim AS final
ARG APP_HOME=/home/node/app
ARG NODE_ENV=production
......@@ -37,15 +74,14 @@ ENV NODE_ENV=${NODE_ENV}
WORKDIR ${APP_HOME}
CMD ["node", "dist/main.js"]
COPY --from=build --chown=node:node ${APP_HOME}/build/dist ./dist
COPY --from=build --chown=node:node ${APP_HOME}/shared/dist ./shared
COPY --from=build --chown=node:node ${APP_HOME}/build/node_modules ./node_modules
COPY --from=build --chown=node:node ${APP_HOME}/build/package.json .
COPY --from=build-service --chown=node:node ${APP_HOME}/build/dist ./dist
COPY --from=build-service --chown=node:node ${APP_HOME}/build/node_modules ./node_modules
COPY --from=build-service --chown=node:node ${APP_HOME}/build/package.json .
# Cut unnecessary stuff from package.json. Only leave name, version and module type
# Cut unnecessary stuff from package.json. Only leave name, version, description and module type
RUN node -e "\
const { name, version, type } = JSON.parse(fs.readFileSync('./package.json', 'utf-8'));\
fs.writeFileSync('./package.json', JSON.stringify({ name, version, type }, null, 2));\
const { name, description, version, type } = JSON.parse(fs.readFileSync('./package.json', 'utf-8'));\
fs.writeFileSync('./package.json', JSON.stringify({ name, version, description, type }, null, 2));\
"
USER node
HTTP_HOST=0.0.0.0
HTTP_PORT=3002
HTTP_HOSTNAME=0.0.0.0
HTTP_PORT=4002
NATS_URL=nats://localhost:4222
NATS_USER=nats_user
NATS_PASSWORD=
NATS_PASSWORD=nats_password
NATS_MONITORING_URL=http://localhost:8222
{
"name": "@ocm/connection-manager",
"version": "1.0.0",
"description": "Connection Manager for OCM",
"description": "Gaia-X OCM Connection Manager",
"contributors": [
"Konstantin Tsabolov <konstantin.tsabolov@spherity.com>"
],
......@@ -16,35 +16,36 @@
"test": "jest"
},
"dependencies": {
"@nestjs/common": "^10.3.0",
"@nestjs/config": "^3.1.1",
"@nestjs/core": "^10.3.0",
"@nestjs/microservices": "^10.3.0",
"@nestjs/platform-express": "^10.3.0",
"@nestjs/schedule": "^4.0.0",
"@nestjs/swagger": "^7.1.17",
"@nestjs/common": "10.3.3",
"@nestjs/config": "3.2.0",
"@nestjs/core": "10.3.3",
"@nestjs/microservices": "10.3.3",
"@nestjs/platform-express": "10.3.3",
"@nestjs/schedule": "4.0.1",
"@nestjs/swagger": "7.3.0",
"@ocm/shared": "workspace:*",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"express": "^4.17.3",
"joi": "^17.11.0",
"nats": "^2.18.0",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.1"
"class-transformer": "0.5.1",
"class-validator": "0.14.1",
"express": "4.18.2",
"helmet": "7.1.0",
"joi": "17.12.1",
"nats": "2.19.0",
"reflect-metadata": "0.2.1",
"rxjs": "7.8.1"
},
"devDependencies": {
"@jest/globals": "^29.7.0",
"@nestjs/cli": "^10.2.1",
"@nestjs/schematics": "^10.0.3",
"@nestjs/testing": "^10.3.0",
"@swc/cli": "^0.1.63",
"@swc/core": "^1.3.101",
"@swc/jest": "^0.2.29",
"@types/express": "^4.17.21",
"@types/jest": "29.5.11",
"@types/node": "^20.10.5",
"jest": "^29.7.0",
"rimraf": "^5.0.5",
"typescript": "^5.3.3"
"@jest/globals": "29.7.0",
"@nestjs/cli": "10.3.2",
"@nestjs/schematics": "10.1.1",
"@nestjs/testing": "10.3.3",
"@swc/cli": "0.3.9",
"@swc/core": "1.4.2",
"@swc/jest": "0.2.36",
"@types/express": "4.17.21",
"@types/jest": "29.5.12",
"@types/node": "20.11.19",
"jest": "29.7.0",
"rimraf": "5.0.5",
"typescript": "5.3.3"
}
}
import type { OnApplicationBootstrap } from '@nestjs/common';
import type { ConfigType } from '@nestjs/config';
import type { ClientProvider } from '@nestjs/microservices';
import { Module } from '@nestjs/common';
import { Inject, Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { RouterModule } from '@nestjs/core';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { ClientProxy, ClientsModule, Transport } from '@nestjs/microservices';
import { HealthModule } from '@ocm/shared';
import { NATS_CLIENT } from './common/constants.js';
......@@ -34,21 +34,14 @@ import { InvitationsModule } from './invitations/invitations.module.js';
{
name: NATS_CLIENT,
inject: [natsConfig.KEY],
useFactory: (config: ConfigType<typeof natsConfig>) => {
const provider: Required<ClientProvider> = {
transport: Transport.NATS,
options: {
servers: config.url as string,
},
};
if ('user' in config && 'password' in config) {
provider.options.user = config.user as string;
provider.options.pass = config.password as string;
}
return provider;
},
useFactory: (config: ConfigType<typeof natsConfig>) => ({
transport: Transport.NATS,
options: {
servers: [config.url],
user: config.user,
pass: config.password,
},
}),
},
],
}),
......@@ -78,4 +71,12 @@ import { InvitationsModule } from './invitations/invitations.module.js';
]),
],
})
export class Application {}
export class Application implements OnApplicationBootstrap {
public constructor(
@Inject(NATS_CLIENT) private readonly client: ClientProxy,
) {}
public async onApplicationBootstrap(): Promise<void> {
await this.client.connect();
}
}
import { registerAs } from '@nestjs/config';
export const httpConfig = registerAs('http', () => ({
host: process.env.HOST || '0.0.0.0',
port: Number(process.env.PORT) || 3000,
hostname: process.env.HTTP_HOSTNAME || '0.0.0.0',
port: Number(process.env.HTTP_PORT) || 3000,
}));
import Joi from 'joi';
export const validationSchema = Joi.object({
HTTP_HOST: Joi.string(),
HTTP_HOSTNAME: Joi.string(),
HTTP_PORT: Joi.number(),
NATS_URL: Joi.string().uri(),
......
/* c8 ignore start */
import type { ConfigType } from '@nestjs/config';
import type { MicroserviceOptions, NatsOptions } from '@nestjs/microservices';
import { VersioningType } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Logger, VersioningType } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { Transport } from '@nestjs/microservices';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import helmet from 'helmet';
import { createRequire } from 'module';
import { resolve } from 'node:path';
import { Application } from './application.js';
import { natsConfig } from './config/nats.config.js';
import { httpConfig } from './config/http.config.js';
const app = await NestFactory.create(Application);
const configService = app.get(ConfigService);
app.enableCors();
const { url, user, password } = app.get(natsConfig.KEY) as ConfigType<
typeof natsConfig
>;
const pkgPath = resolve('package.json');
const pkg = createRequire(import.meta.url)(pkgPath);
const microserviceOptions: Required<NatsOptions> = {
transport: Transport.NATS,
options: {
servers: [url],
},
};
if (user && password) {
microserviceOptions.options.user = user;
microserviceOptions.options.pass = password;
}
const app = await NestFactory.create(Application);
app.connectMicroservice<MicroserviceOptions>(microserviceOptions);
app.use(helmet());
app.enableVersioning({
defaultVersion: ['1'],
......@@ -39,15 +24,24 @@ app.enableVersioning({
});
const swaggerConfig = new DocumentBuilder()
.setTitle('Gaia-X Connection Manager API')
.setDescription('API documentation for GAIA-X Connection Manager')
.setVersion('1.0')
.setTitle(pkg.description)
.setVersion(pkg.version)
.build();
const document = SwaggerModule.createDocument(app, swaggerConfig);
SwaggerModule.setup('/swagger', app, document);
await app.startAllMicroservices();
SwaggerModule.setup('/', app, document, {
swaggerOptions: {
docExpansion: 'none',
tryItOutEnabled: true,
},
});
const { hostname, port } = app.get(httpConfig.KEY) as ConfigType<
typeof httpConfig
>;
await app.listen(port, hostname);
Logger.log(`Application is running on: ${await app.getUrl()}`);
await app.listen(configService.get('http.port') as number);
/* c8 ignore stop */
HTTP_HOST=0.0.0.0
HTTP_PORT=3003
HTTP_HOSTNAME=0.0.0.0
HTTP_PORT=4003
NATS_URL=nats://localhost:4222
NATS_USER=nats_user
NATS_PASSWORD=
NATS_PASSWORD=nats_password
NATS_MONITORING_URL=http://localhost:8222
{
"name": "@ocm/credential-manager",
"version": "1.0.0",
"description": "",
"description": "Gaia-X OCM Credential Manager",
"author": "Konstantin Tsabolov <konstantin.tsabolov@spherity.com>",
"contributors": [
"Konstantin Tsabolov <konstantin.tsabolov@spherity.com>"
......@@ -17,37 +17,37 @@
"test": "jest"
},
"dependencies": {
"@nestjs/common": "^10.3.0",
"@nestjs/config": "^3.1.1",
"@nestjs/core": "^10.3.0",
"@nestjs/microservices": "^10.3.0",
"@nestjs/platform-express": "^10.3.0",
"@nestjs/swagger": "^7.1.16",
"@nestjs/common": "10.3.3",
"@nestjs/config": "3.2.0",
"@nestjs/core": "10.3.3",
"@nestjs/microservices": "10.3.3",
"@nestjs/platform-express": "10.3.3",
"@nestjs/schedule": "4.0.1",
"@nestjs/swagger": "7.3.0",
"@ocm/shared": "workspace:*",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"express": "^4.17.3",
"joi": "^17.11.0",
"nats": "^2.18.0",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.1"
"class-transformer": "0.5.1",
"class-validator": "0.14.1",
"express": "4.18.2",
"helmet": "7.1.0",
"joi": "17.12.1",
"nats": "2.19.0",
"reflect-metadata": "0.2.1",
"rxjs": "7.8.1"
},
"devDependencies": {
"@nestjs/cli": "^10.3.0",
"@nestjs/schematics": "^10.1.0",
"@nestjs/testing": "^10.3.0",
"@swc/cli": "^0.1.62",
"@swc/core": "^1.3.96",
"@swc/jest": "^0.2.29",
"@types/express": "^4.17.21",
"@types/jest": "^29.5.8",
"@types/jsonwebtoken": "^9.0.5",
"@types/node": "^20.9.0",
"@types/supertest": "^2.0.16",
"dotenv-cli": "^7.3.0",
"eslint": "^8.53.0",
"jest": "^29.7.0",
"rimraf": "^5.0.5",
"typescript": "^5.3.2"
"@nestjs/cli": "10.3.2",
"@nestjs/schematics": "10.1.1",
"@nestjs/testing": "10.3.3",
"@swc/cli": "0.3.9",
"@swc/core": "1.4.2",
"@swc/jest": "0.2.36",
"@types/express": "4.17.21",
"@types/jest": "29.5.12",
"@types/node": "20.11.19",
"dotenv-cli": "7.3.0",
"eslint": "8.56.0",
"jest": "29.7.0",
"rimraf": "5.0.5",
"typescript": "5.3.3"
}
}
import type { OnApplicationBootstrap } from '@nestjs/common';
import type { ConfigType } from '@nestjs/config';
import type { ClientProvider } from '@nestjs/microservices';
import { Module } from '@nestjs/common';
import { Inject, Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { RouterModule } from '@nestjs/core';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { ClientProxy, ClientsModule, Transport } from '@nestjs/microservices';
import { HealthModule } from '@ocm/shared';
import { NATS_CLIENT } from './common/constants.js';
import { httpConfig } from './config/http.config.js';
import { natsConfig } from './config/nats.config.js';
import { policiesConfig } from './config/policies.config.js';
import { validationSchema } from './config/validation.js';
import { CredentialOffersModule } from './credential-offers/credential-offers.module.js';
import { CredentialRequestsModule } from './credential-requests/credential-requests.module.js';
import { CredentialsModule } from './credentials/credentials.module.js';
import { PoliciesModule } from './policies/policies.module.js';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
load: [httpConfig, natsConfig],
load: [httpConfig, natsConfig, policiesConfig],
cache: true,
expandVariables: true,
validationSchema,
......@@ -35,21 +37,14 @@ import { CredentialsModule } from './credentials/credentials.module.js';
{
name: NATS_CLIENT,
inject: [natsConfig.KEY],
useFactory: (config: ConfigType<typeof natsConfig>) => {
const provider: Required<ClientProvider> = {
transport: Transport.NATS,
options: {
servers: config.url as string,
},
};
if ('user' in config && 'password' in config) {
provider.options.user = config.user as string;
provider.options.pass = config.password as string;
}
return provider;
},
useFactory: (config: ConfigType<typeof natsConfig>) => ({
transport: Transport.NATS,
options: {
servers: [config.url],
user: config.user as string,
pass: config.password as string,
},
}),
},
],
}),
......@@ -72,13 +67,23 @@ import { CredentialsModule } from './credentials/credentials.module.js';
CredentialsModule,
CredentialOffersModule,
CredentialRequestsModule,
PoliciesModule,
RouterModule.register([
{ module: HealthModule, path: '/health' },
{ module: CredentialsModule, path: '/credentials' },
{ module: CredentialOffersModule, path: '/credential-offers' },
{ module: CredentialRequestsModule, path: '/credential-requests' },
{ module: PoliciesModule, path: '/policies' },
]),
],
})
export class Application {}
export class Application implements OnApplicationBootstrap {
public constructor(
@Inject(NATS_CLIENT) private readonly client: ClientProxy,
) {}
public async onApplicationBootstrap(): Promise<void> {
await this.client.connect();
}
}
import { registerAs } from '@nestjs/config';
export const httpConfig = registerAs('http', () => ({
host: process.env.HOST || '0.0.0.0',
port: Number(process.env.PORT) || 3000,
hostname: process.env.HTTP_HOSTNAME || '0.0.0.0',
port: Number(process.env.HTTP_PORT) || 3000,
}));
import { registerAs } from '@nestjs/config';
export const policiesConfig = registerAs(
'policies',
(): {
url?: string;
autoRevocation: {
policy?: `${string}/${string}/${string}/${string}`;
};
autoReissue: {
policy?: `${string}/${string}/${string}/${string}`;
};
refresh: {
policy?: `${string}/${string}/${string}/${string}`;
};
} => ({
url: process.env.POLICIES_URL,
autoRevocation: {
policy:
(process.env.POLICIES_AUTO_REVOCATION_POLICY as
| `${string}/${string}/${string}/${string}`
| undefined) || undefined,
},
autoReissue: {
policy:
(process.env.POLICIES_AUTO_REISSUE_POLICY as
| `${string}/${string}/${string}/${string}`
| undefined) || undefined,
},
refresh: {
policy:
(process.env.POLICIES_REFRESH_POLICY as
| `${string}/${string}/${string}/${string}`
| undefined) || undefined,
},
}),
);
import Joi from 'joi';
export const validationSchema = Joi.object({
HTTP_HOST: Joi.string(),
HTTP_HOSTNAME: Joi.string(),
HTTP_PORT: Joi.number(),
NATS_URL: Joi.string().uri(),
NATS_USER: Joi.string().optional(),
NATS_PASSWORD: Joi.string().optional(),
NATS_MONITORING_URL: Joi.string().uri(),
POLICIES_URL: Joi.string().uri(),
POLICIES_AUTO_REVOCATION_POLICY: Joi.string(),
POLICIES_AUTO_REISSUE_POLICY: Joi.string(),
POLICIES_REFRESH_POLICY: Joi.string(),
});
......@@ -15,7 +15,7 @@ import { MultitenancyParams, ResponseFormatInterceptor } from '@ocm/shared';
import { CredentialOffersService } from './credential-offers.service.js';
import { GetByIdParams } from './dto/get-by-id.dto.js';
import { OfferPayload } from './dto/offer.dto.js';
import { OfferPayload, OfferPayloadSelf } from './dto/offer.dto.js';
@Controller()
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
......@@ -58,13 +58,6 @@ export class CredentialOffersController {
'application/json': {
schema: {},
examples: {
'Credential offer not found': {
value: {
statusCode: 404,
message: 'Credential offer not found',
data: null,
},
},
'Tenant not found': {
value: {
statusCode: 404,
......@@ -249,13 +242,20 @@ export class CredentialOffersController {
})
public offer(
@Query() { tenantId }: MultitenancyParams,
@Body() { connectionId, credentialDefinitionId, attributes }: OfferPayload,
@Body()
{
connectionId,
credentialDefinitionId,
attributes,
revocationRegistryDefinitionId,
}: OfferPayload,
) {
return this.service.offer(
tenantId,
connectionId,
credentialDefinitionId,
attributes,
revocationRegistryDefinitionId,
);
}
......@@ -337,7 +337,7 @@ export class CredentialOffersController {
public offerToSelf(
@Query() { tenantId }: MultitenancyParams,
@Body()
{ credentialDefinitionId, attributes }: Omit<OfferPayload, 'connectionId'>,
{ credentialDefinitionId, attributes }: OfferPayloadSelf,
) {
return this.service.offerToSelf(
tenantId,
......@@ -345,4 +345,78 @@ export class CredentialOffersController {
attributes,
);
}
@Post(':credentialOfferId/accept')
@ApiOperation({
summary: 'Accept a credential offer',
description: 'This call accepts a credential offer for a given ID',
})
@ApiResponse({
status: HttpStatus.OK,
description: 'Credential offer accepted successfully',
content: {
'application/json': {
schema: {},
examples: {
'Credential offer accepted successfully': {
value: {
statusCode: 200,
message: 'Credential offer accepted successfully',
data: {
id: '71b784a3',
},
},
},
},
},
},
})
@ApiResponse({
status: HttpStatus.NOT_FOUND,
content: {
'application/json': {
schema: {},
examples: {
'Credential offer not found': {
value: {
statusCode: 404,
message: 'Credential offer not found',
data: null,
},
},
'Tenant not found': {
value: {
statusCode: 404,
message: 'Tenant not found',
data: null,
},
},
},
},
},
})
@ApiResponse({
status: HttpStatus.INTERNAL_SERVER_ERROR,
description: 'Something went wrong',
content: {
'application/json': {
schema: {},
examples: {
'Something went wrong': {
value: {
statusCode: 500,
message: 'Something went wrong',
error: 'Internal Server Error',
},
},
},
},
},
})
public acceptOffer(
@Param() { credentialOfferId }: GetByIdParams,
@Query() { tenantId }: MultitenancyParams,
) {
return this.service.acceptOffer(tenantId, credentialOfferId);
}
}
import type {
EventAnonCredsCredentialOfferGetAllInput,
EventAnonCredsCredentialOfferGetByIdInput,
EventDidcommAnonCredsCredentialsAcceptOfferInput,
EventDidcommAnonCredsCredentialsOfferInput,
EventDidcommAnonCredsCredentialsOfferToSelfInput,
} from '@ocm/shared';
......@@ -9,6 +10,7 @@ import type { Observable } from 'rxjs';
import { Inject, Injectable } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import {
EventDidcommAnonCredsCredentialsAcceptOffer,
EventDidcommAnonCredsCredentialsOffer,
EventDidcommAnonCredsCredentialsOfferToSelf,
EventAnonCredsCredentialOfferGetAll,
......@@ -50,6 +52,7 @@ export class CredentialOffersService {
connectionId: string,
credentialDefinitionId: string,
attributes: EventDidcommAnonCredsCredentialsOfferInput['attributes'],
revocationRegistryDefinitionId?: string,
): Observable<EventDidcommAnonCredsCredentialsOffer['data']> {
return this.natsClient
.send<
......@@ -60,6 +63,7 @@ export class CredentialOffersService {
connectionId,
credentialDefinitionId,
attributes,
revocationRegistryDefinitionId,
})
.pipe(map(({ data }) => data));
}
......@@ -80,4 +84,16 @@ export class CredentialOffersService {
})
.pipe(map(({ data }) => data));
}
public acceptOffer(tenantId: string, credentialId: string) {
return this.natsClient
.send<
EventDidcommAnonCredsCredentialsAcceptOffer,
EventDidcommAnonCredsCredentialsAcceptOfferInput
>(EventDidcommAnonCredsCredentialsAcceptOffer.token, {
tenantId,
credentialId,
})
.pipe(map(({ data }) => data));
}
}
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import {
IsArray,
IsNotEmpty,
IsOptional,
IsString,
ValidateNested,
} from 'class-validator';
class Attribute {
@IsString()
@IsNotEmpty()
@ApiProperty()
public name: string;
@IsString()
@IsNotEmpty()
@ApiProperty()
public value: string;
@IsString()
@IsOptional()
@ApiPropertyOptional()
public mimeType?: string;
}
export class OfferPayload {
@IsString()
@IsNotEmpty()
@ApiProperty()
public connectionId: string;
@IsString()
@IsNotEmpty()
@ApiProperty()
public credentialDefinitionId: string;
@IsArray()
@ValidateNested()
@Type(() => Attribute)
@ApiProperty({ type: [Attribute] })
public attributes: Attribute[];
@IsString()
@IsNotEmpty()
@IsOptional()
@ApiProperty()
public revocationRegistryDefinitionId?: string;
}
export class OfferPayloadSelf {
@IsString()
@IsNotEmpty()
@ApiProperty()
public credentialDefinitionId: string;
public attributes: Array<{
name: string;
value: string;
mimeType?: string;
}>;
@IsArray()
@ValidateNested()
@Type(() => Attribute)
@ApiProperty({ type: [Attribute] })
public attributes: Attribute[];
@IsString()
@IsNotEmpty()
@IsOptional()
@ApiProperty()
public revocationRegistryDefinitionId?: string;
}
......@@ -2,8 +2,10 @@ import {
Controller,
Delete,
Get,
HttpCode,
HttpStatus,
Param,
Post,
Query,
UseInterceptors,
UsePipes,
......@@ -15,6 +17,7 @@ import { MultitenancyParams, ResponseFormatInterceptor } from '@ocm/shared';
import { CredentialsService } from './credentials.service.js';
import { DeleteParams } from './dto/delete.dto.js';
import { GetParams } from './dto/get.dto.js';
import { RevokeParams } from './dto/revoke.dto.js';
@Controller()
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
......@@ -88,7 +91,7 @@ export class CredentialsController {
return this.service.find(tenantId);
}
@Get(':credentialId')
@Get(':credentialRecordId')
@ApiOperation({
summary: 'Fetch a credential',
description: 'This call provides a credential for a given tenant',
......@@ -161,6 +164,78 @@ export class CredentialsController {
return this.service.get(tenantId, credentialRecordId);
}
@Post(':credentialId/revoke')
@HttpCode(HttpStatus.OK)
@ApiOperation({
summary: 'Revoke a credential',
description: 'This call revokes a credential for a given tenant',
})
@ApiResponse({
status: HttpStatus.OK,
description: 'Credential revoked successfully',
content: {
'application/json': {
schema: {},
examples: {
'Credential revoked successfully': {
value: {
statusCode: 200,
message: 'Credential revoked successfully',
data: null,
},
},
},
},
},
})
@ApiResponse({
status: HttpStatus.NOT_FOUND,
content: {
'application/json': {
schema: {},
examples: {
'Credential not found': {
value: {
statusCode: 404,
message: 'Credential not found',
data: null,
},
},
'Tenant not found': {
value: {
statusCode: 404,
message: 'Tenant not found',
data: null,
},
},
},
},
},
})
@ApiResponse({
status: HttpStatus.INTERNAL_SERVER_ERROR,
content: {
'application/json': {
schema: {},
examples: {
'Internal server error': {
value: {
statusCode: 500,
message: 'Internal server error',
data: null,
},
},
},
},
},
})
public revoke(
@Query() { tenantId }: MultitenancyParams,
@Param() { credentialId }: RevokeParams,
) {
return this.service.revoke(tenantId, credentialId);
}
@Delete(':credentialId')
@ApiOperation({
summary: 'Delete a credential',
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment