From a4916078fd082a24ab963811aa2747ab8a6957fc Mon Sep 17 00:00:00 2001 From: alexeh Date: Tue, 24 Sep 2024 09:13:22 +0200 Subject: [PATCH] add admin contract --- api/src/modules/admin/admin.controller.ts | 20 +++++++++++++------ .../authentication.controller.ts | 3 ++- api/test/integration/auth/create-user.spec.ts | 14 ++++++++----- shared/contracts/admin.contract.ts | 18 +++++++++++++++++ shared/contracts/{auth => }/auth.contract.ts | 0 shared/contracts/index.ts | 4 +++- shared/schemas/users/create-user.schema.ts | 2 +- 7 files changed, 47 insertions(+), 14 deletions(-) create mode 100644 shared/contracts/admin.contract.ts rename shared/contracts/{auth => }/auth.contract.ts (100%) diff --git a/api/src/modules/admin/admin.controller.ts b/api/src/modules/admin/admin.controller.ts index e62a37e3..5cccf6e8 100644 --- a/api/src/modules/admin/admin.controller.ts +++ b/api/src/modules/admin/admin.controller.ts @@ -1,19 +1,27 @@ -import { Body, Controller, Post, UseGuards } from '@nestjs/common'; +import { Controller, UseGuards } from '@nestjs/common'; import { RolesGuard } from '@api/modules/auth/guards/roles.guard'; import { AuthenticationService } from '@api/modules/auth/authentication/authentication.service'; -import { CreateUserDto } from '@shared/schemas/users/create-user.schema'; import { JwtAuthGuard } from '@api/modules/auth/guards/jwt-auth.guard'; import { ROLES } from '@api/modules/auth/authorisation/roles.enum'; import { RequiredRoles } from '@api/modules/auth/decorators/roles.decorator'; +import { tsRestHandler, TsRestHandler } from '@ts-rest/nest'; +import { ControllerResponse } from '@api/types/controller-response.type'; +import { adminContract } from '@shared/contracts/admin.contract'; -@Controller('admin') +@Controller() @UseGuards(JwtAuthGuard, RolesGuard) export class AdminController { constructor(private readonly auth: AuthenticationService) {} @RequiredRoles(ROLES.ADMIN) - @Post('/users') - async createUser(@Body() createUserDto: CreateUserDto): Promise { - return this.auth.createUser(createUserDto); + @TsRestHandler(adminContract.createUser) + async createUser(): Promise { + return tsRestHandler(adminContract.createUser, async ({ body }) => { + await this.auth.createUser(body); + return { + status: 201, + body: null, + }; + }); } } diff --git a/api/src/modules/auth/authentication/authentication.controller.ts b/api/src/modules/auth/authentication/authentication.controller.ts index f1ca8b83..1683bef6 100644 --- a/api/src/modules/auth/authentication/authentication.controller.ts +++ b/api/src/modules/auth/authentication/authentication.controller.ts @@ -12,11 +12,12 @@ import { LocalAuthGuard } from '@api/modules/auth/guards/local-auth.guard'; import { GetUser } from '@api/modules/auth/decorators/get-user.decorator'; import { Public } from '@api/modules/auth/decorators/is-public.decorator'; import { PasswordRecoveryService } from '@api/modules/auth/services/password-recovery.service'; -import { authContract } from '@shared/contracts/auth/auth.contract'; + import { tsRestHandler, TsRestHandler } from '@ts-rest/nest'; import { ControllerResponse } from '@api/types/controller-response.type'; import { AuthGuard } from '@nestjs/passport'; import { ResetPassword } from '@api/modules/auth/strategies/reset-password.strategy'; +import { authContract } from '@shared/contracts/auth.contract'; @Controller() @UseInterceptors(ClassSerializerInterceptor) diff --git a/api/test/integration/auth/create-user.spec.ts b/api/test/integration/auth/create-user.spec.ts index 0fb702fe..28d44e8d 100644 --- a/api/test/integration/auth/create-user.spec.ts +++ b/api/test/integration/auth/create-user.spec.ts @@ -15,11 +15,14 @@ describe('Create Users', () => { beforeAll(async () => { testManager = await TestManager.createTestManager(); + + mockEmailService = + testManager.getModule(IEmailServiceToken); + }); + beforeEach(async () => { const { user, jwtToken: token } = await testManager.setUpTestUser(); testUser = user; jwtToken = token; - mockEmailService = - testManager.getModule(IEmailServiceToken); }); afterEach(async () => { @@ -34,7 +37,9 @@ describe('Create Users', () => { // Given a user exists with valid credentials // But the user has the role partner - const user = await testManager.mocks().createUser({ role: ROLES.PARTNER }); + const user = await testManager + .mocks() + .createUser({ role: ROLES.PARTNER, email: 'random@test.com' }); const { jwtToken } = await testManager.logUserIn(user); // When the user creates a new user @@ -57,7 +62,7 @@ describe('Create Users', () => { .request() .post('/admin/users') .set('Authorization', `Bearer ${jwtToken}`) - .send({ email: testUser.email, password: '12345678' }); + .send({ email: testUser.email, partnerName: 'test' }); // Then the user should receive a 409 status code expect(response.status).toBe(HttpStatus.CONFLICT); @@ -72,7 +77,6 @@ describe('Create Users', () => { // beforeAll const newUser = { email: 'test@test.com', - password: '12345678', partnerName: 'test', }; diff --git a/shared/contracts/admin.contract.ts b/shared/contracts/admin.contract.ts new file mode 100644 index 00000000..2ab8f917 --- /dev/null +++ b/shared/contracts/admin.contract.ts @@ -0,0 +1,18 @@ +import { initContract } from "@ts-rest/core"; +import { JSONAPIError } from "@shared/dtos/json-api.error"; +import { CreateUserSchema } from "@shared/schemas/users/create-user.schema"; + +// TODO: This is a scaffold. We need to define types for responses, zod schemas for body and query param validation etc. + +const contract = initContract(); +export const adminContract = contract.router({ + createUser: { + method: "POST", + path: "/admin/users", + responses: { + 201: contract.type(), + 401: contract.type(), + }, + body: CreateUserSchema, + }, +}); diff --git a/shared/contracts/auth/auth.contract.ts b/shared/contracts/auth.contract.ts similarity index 100% rename from shared/contracts/auth/auth.contract.ts rename to shared/contracts/auth.contract.ts diff --git a/shared/contracts/index.ts b/shared/contracts/index.ts index 3f13f97d..cb5986fc 100644 --- a/shared/contracts/index.ts +++ b/shared/contracts/index.ts @@ -1,8 +1,10 @@ import { initContract } from "@ts-rest/core"; -import { authContract } from "./auth/auth.contract"; +import { adminContract } from "@shared/contracts/admin.contract"; +import { authContract } from "@shared/contracts/auth.contract"; const contract = initContract(); export const router = contract.router({ auth: authContract, + admin: adminContract, }); diff --git a/shared/schemas/users/create-user.schema.ts b/shared/schemas/users/create-user.schema.ts index b3223c65..d2274a7f 100644 --- a/shared/schemas/users/create-user.schema.ts +++ b/shared/schemas/users/create-user.schema.ts @@ -2,7 +2,7 @@ import { z } from "zod"; export const CreateUserSchema = z.object({ email: z.string().email(), - name: z.string(), + name: z.string().optional(), partnerName: z.string(), });