Skip to content

Commit

Permalink
Create wallet group
Browse files Browse the repository at this point in the history
  • Loading branch information
wcalderipe committed Feb 13, 2024
1 parent fd1fbd6 commit bc1d38a
Show file tree
Hide file tree
Showing 14 changed files with 336 additions and 10 deletions.
2 changes: 2 additions & 0 deletions apps/authz/src/app/http/rest/controller/admin.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ export class AdminController {
return response
}

// DONE
@Post('/wallets')
async registerWallet(@Body() body: RegisterWalletRequestDto) {
const payload: RegisterWalletRequest = body
Expand All @@ -113,6 +114,7 @@ export class AdminController {
return response
}

// DONE
@Post('/wallet-groups')
async assignWalletGroup(@Body() body: AssignWalletGroupRequestDto) {
const payload: AssignWalletGroupRequest = body
Expand Down
4 changes: 3 additions & 1 deletion apps/orchestration/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ const withSwagger = (app: INestApplication): INestApplication => {
.setVersion('1.0')
.build()
)
SwaggerModule.setup('docs', app, document)
SwaggerModule.setup('docs', app, document, {
customSiteTitle: 'Orchestration API'
})

return app
}
Expand Down
110 changes: 110 additions & 0 deletions apps/orchestration/src/store/entity/__test__/e2e/wallet-group.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { Action, OrganizationEntity, Signature } from '@narval/authz-shared'
import { HttpStatus, INestApplication } from '@nestjs/common'
import { ConfigModule } from '@nestjs/config'
import { Test, TestingModule } from '@nestjs/testing'
import request from 'supertest'
import { generateSignature } from '../../../../__test__/fixture/authorization-request.fixture'
import { load } from '../../../../orchestration.config'
import { REQUEST_HEADER_ORG_ID } from '../../../../orchestration.constant'
import { PolicyEngineModule } from '../../../../policy-engine/policy-engine.module'
import { PersistenceModule } from '../../../../shared/module/persistence/persistence.module'
import { TestPrismaService } from '../../../../shared/module/persistence/service/test-prisma.service'
import { QueueModule } from '../../../../shared/module/queue/queue.module'
import { EntityStoreModule } from '../../entity-store.module'
import { OrganizationRepository } from '../../persistence/repository/organization.repository'
import { WalletGroupRepository } from '../../persistence/repository/wallet-group.repository'

const API_RESOURCE_USER_ENTITY = '/store/wallet-groups'

describe('Wallet Group Store', () => {
let app: INestApplication
let module: TestingModule
let testPrismaService: TestPrismaService
let orgRepository: OrganizationRepository
let walletGroupRepository: WalletGroupRepository

const organization: OrganizationEntity = {
uid: 'ac1374c2-fd62-4b6e-bd49-a4afcdcb91cc'
}

const authentication: Signature = generateSignature()

const approvals: Signature[] = [generateSignature(), generateSignature()]

const nonce = 'b6d826b4-72cb-4c14-a6ca-235a2d8e9060'

const walletId = 'c7eac7d1-7572-4756-b52e-0caebe208364'

const groupId = '2a1509ad-ea87-422e-bebd-974547cd4fee'

beforeAll(async () => {
module = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({
load: [load],
isGlobal: true
}),
PersistenceModule,
QueueModule.forRoot(),
PolicyEngineModule,
EntityStoreModule
]
}).compile()

testPrismaService = module.get<TestPrismaService>(TestPrismaService)
orgRepository = module.get<OrganizationRepository>(OrganizationRepository)
walletGroupRepository = module.get<WalletGroupRepository>(WalletGroupRepository)

app = module.createNestApplication()

await app.init()
})

afterAll(async () => {
await testPrismaService.truncateAll()
await module.close()
await app.close()
})

beforeEach(async () => {
await orgRepository.create(organization.uid)
})

afterEach(async () => {
await testPrismaService.truncateAll()
})

describe(`POST ${API_RESOURCE_USER_ENTITY}`, () => {
it('assigns a wallet to a group', async () => {
const payload = {
authentication,
approvals,
request: {
action: Action.ASSIGN_WALLET_GROUP,
nonce,
data: { walletId, groupId }
}
}

const { status, body } = await request(app.getHttpServer())
.post(API_RESOURCE_USER_ENTITY)
.set(REQUEST_HEADER_ORG_ID, organization.uid)
.send(payload)

const actualGroup = await walletGroupRepository.findById(groupId)

expect(body).toEqual({
data: {
walletId,
groupId
}
})
expect(status).toEqual(HttpStatus.CREATED)

expect(actualGroup).toEqual({
uid: groupId,
wallets: [walletId]
})
})
})
})
14 changes: 12 additions & 2 deletions apps/orchestration/src/store/entity/core/service/wallet.service.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
import { WalletEntity } from '@narval/authz-shared'
import { WalletEntity, WalletGroupMembership } from '@narval/authz-shared'
import { Injectable } from '@nestjs/common'
import { WalletGroupRepository } from '../../persistence/repository/wallet-group.repository'
import { WalletRepository } from '../../persistence/repository/wallet.repository'

@Injectable()
export class WalletService {
constructor(private walletRepository: WalletRepository) {}
constructor(private walletRepository: WalletRepository, private walletGroupRepository: WalletGroupRepository) {}

async create(orgId: string, wallet: WalletEntity): Promise<WalletEntity> {
return this.walletRepository.create(orgId, wallet)
}

async assignGroup(orgId: string, walletId: string, groupId: string): Promise<WalletGroupMembership> {
await this.walletGroupRepository.maybeCreate(orgId, {
uid: groupId,
wallets: [walletId]
})

return { groupId, walletId }
}
}
5 changes: 4 additions & 1 deletion apps/orchestration/src/store/entity/entity-store.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,18 @@ import { WalletService } from './core/service/wallet.service'
import { OrganizationController } from './http/rest/controller/organization.controller'
import { UserGroupController } from './http/rest/controller/user-group.controller'
import { UserController } from './http/rest/controller/user.controller'
import { WalletGroupController } from './http/rest/controller/wallet-group.controller'
import { WalletController } from './http/rest/controller/wallet.controller'
import { AuthCredentialRepository } from './persistence/repository/auth-credential.repository'
import { OrganizationRepository } from './persistence/repository/organization.repository'
import { UserGroupRepository } from './persistence/repository/user-group.repository'
import { UserRepository } from './persistence/repository/user.repository'
import { WalletGroupRepository } from './persistence/repository/wallet-group.repository'
import { WalletRepository } from './persistence/repository/wallet.repository'

@Module({
imports: [ConfigModule.forRoot({ load: [load] }), PersistenceModule, PolicyEngineModule],
controllers: [OrganizationController, UserController, UserGroupController, WalletController],
controllers: [OrganizationController, UserController, UserGroupController, WalletController, WalletGroupController],
providers: [
OrganizationService,
OrganizationRepository,
Expand All @@ -31,6 +33,7 @@ import { WalletRepository } from './persistence/repository/wallet.repository'
AuthCredentialRepository,
WalletService,
WalletRepository,
WalletGroupRepository,
{
provide: APP_INTERCEPTOR,
useClass: ClassSerializerInterceptor
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Body, Controller, HttpStatus, Post } from '@nestjs/common'
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'
import { ApiHeader, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'
import { REQUEST_HEADER_ORG_ID } from '../../../../../orchestration.constant'
import { OrgId } from '../../../../../shared/decorator/org-id.decorator'
import { UserGroupService } from '../../../core/service/user-group.service'
import { AssignUserGroupRequestDto } from '../dto/assign-user-group-request.dto'
Expand All @@ -14,6 +15,9 @@ export class UserGroupController {
@ApiOperation({
summary: "Assigns a user to a group. If the group doesn't exist, creates it first."
})
@ApiHeader({
name: REQUEST_HEADER_ORG_ID
})
@ApiResponse({
status: HttpStatus.CREATED,
type: AssignUserGroupResponseDto
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Body, Controller, HttpStatus, Patch, Post } from '@nestjs/common'
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'
import { ApiHeader, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'
import { REQUEST_HEADER_ORG_ID } from '../../../../../orchestration.constant'
import { OrgId } from '../../../../../shared/decorator/org-id.decorator'
import { UserService } from '../../../core/service/user.service'
import { CreateUserRequestDto } from '../dto/create-user-request.dto'
Expand All @@ -14,7 +15,10 @@ export class UserController {

@Post()
@ApiOperation({
summary: 'Creates a new user entity'
summary: 'Creates a new user entity.'
})
@ApiHeader({
name: REQUEST_HEADER_ORG_ID
})
@ApiResponse({
status: HttpStatus.CREATED,
Expand All @@ -32,7 +36,10 @@ export class UserController {

@Patch('/:uid')
@ApiOperation({
summary: 'Updates an existing user'
summary: 'Updates an existing user.'
})
@ApiHeader({
name: REQUEST_HEADER_ORG_ID
})
@ApiResponse({
status: HttpStatus.OK,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Body, Controller, HttpStatus, Post } from '@nestjs/common'
import { ApiHeader, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'
import { REQUEST_HEADER_ORG_ID } from '../../../../../orchestration.constant'
import { OrgId } from '../../../../../shared/decorator/org-id.decorator'
import { WalletService } from '../../../core/service/wallet.service'
import { AssignWalletGroupRequestDto } from '../dto/assign-wallet-group-request.dto'
import { AssignWalletGroupResponseDto } from '../dto/assign-wallet-group-response.dto'

@Controller('/store/wallet-groups')
@ApiTags('Entity Store')
export class WalletGroupController {
constructor(private walletService: WalletService) {}

@Post()
@ApiOperation({
summary: "Assigns a wallet to a group. If the group doesn't exist, creates it first."
})
@ApiHeader({
name: REQUEST_HEADER_ORG_ID
})
@ApiResponse({
status: HttpStatus.CREATED,
type: AssignWalletGroupResponseDto
})
async assign(
@OrgId() orgId: string,
@Body() body: AssignWalletGroupRequestDto
): Promise<AssignWalletGroupResponseDto> {
const membership = await this.walletService.assignGroup(
orgId,
body.request.data.walletId,
body.request.data.groupId
)

return new AssignWalletGroupResponseDto({ data: membership })
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Body, Controller, HttpStatus, Post } from '@nestjs/common'
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'
import { ApiHeader, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'
import { REQUEST_HEADER_ORG_ID } from '../../../../../orchestration.constant'
import { OrgId } from '../../../../../shared/decorator/org-id.decorator'
import { WalletService } from '../../../core/service/wallet.service'
import { RegisterWalletRequestDto } from '../dto/register-wallet-request.dto'
Expand All @@ -12,7 +13,10 @@ export class WalletController {

@Post()
@ApiOperation({
summary: 'Registers wallet as an entity'
summary: 'Registers wallet as an entity.'
})
@ApiHeader({
name: REQUEST_HEADER_ORG_ID
})
@ApiResponse({
status: HttpStatus.CREATED,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Action, BaseAdminRequestPayloadDto } from '@narval/authz-shared'
import { ApiProperty } from '@nestjs/swagger'
import { Type } from 'class-transformer'
import { IsDefined, IsEnum, ValidateNested } from 'class-validator'
import { BaseActionDto } from './base-action.dto'
import { WalletGroupMembershipDto } from './wallet-group-membership.dto'

class AssignWalletGroupActionDto extends BaseActionDto {
@IsEnum(Action)
@IsDefined()
@ApiProperty({
enum: Object.values(Action),
default: Action.ASSIGN_WALLET_GROUP
})
action: typeof Action.ASSIGN_WALLET_GROUP

@IsDefined()
@Type(() => WalletGroupMembershipDto)
@ValidateNested()
@ApiProperty({
type: WalletGroupMembershipDto
})
data: WalletGroupMembershipDto
}

export class AssignWalletGroupRequestDto extends BaseAdminRequestPayloadDto {
@IsDefined()
@Type(() => AssignWalletGroupActionDto)
@ValidateNested()
@ApiProperty({
type: AssignWalletGroupActionDto
})
request: AssignWalletGroupActionDto
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ApiProperty } from '@nestjs/swagger'
import { Type } from 'class-transformer'
import { IsDefined, ValidateNested } from 'class-validator'
import { WalletGroupMembershipDto } from './wallet-group-membership.dto'

export class AssignWalletGroupResponseDto {
@IsDefined()
@Type(() => WalletGroupMembershipDto)
@ValidateNested()
@ApiProperty({
type: WalletGroupMembershipDto
})
data: WalletGroupMembershipDto

constructor(partial: Partial<AssignWalletGroupResponseDto>) {
Object.assign(this, partial)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export class WalletDto {
@IsEthereumAddress()
@IsNotEmpty()
@ApiProperty({
type: String,
format: 'address'
})
address: Address
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ApiProperty } from '@nestjs/swagger'
import { IsNotEmpty, IsString } from 'class-validator'

export class WalletGroupMembershipDto {
@IsString()
@IsNotEmpty()
@ApiProperty()
walletId: string

@IsString()
@IsNotEmpty()
@ApiProperty()
groupId: string

constructor(partial: Partial<WalletGroupMembershipDto>) {
Object.assign(this, partial)
}
}
Loading

0 comments on commit bc1d38a

Please sign in to comment.