Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: organization apis are accessible through api key #4619

Merged
merged 7 commits into from Oct 25, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
78 changes: 78 additions & 0 deletions apps/api/src/app/organization/dtos/member-response.dto.ts
@@ -0,0 +1,78 @@
import { MemberRoleEnum, MemberStatusEnum } from '@novu/shared';
import { IsArray, IsDate, IsObject, IsString } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';

export class MemberUserDto {
@ApiProperty()
@IsString()
_id: string;

@ApiProperty()
@IsString()
firstName: string;

@ApiProperty()
@IsString()
lastName: string;

@ApiProperty()
@IsString()
email: string;
}

export class MemberInviteDTO {
@ApiProperty()
@IsString()
email: string;

@ApiProperty()
@IsString()
token: string;

@ApiProperty()
@IsDate()
invitationDate: Date;

@ApiProperty()
@IsDate()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@IsDate()
@IsDate()
@ApiPropertyOptional()

answerDate?: Date;

@ApiProperty()
@IsDate()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@IsDate()
@IsString()

_inviterId: string;
}

export class MemberResponseDto {
@ApiProperty()
@IsString()
_id: string;

@ApiProperty()
@IsString()
_userId: string;

@ApiPropertyOptional()
@IsObject()
user?: MemberUserDto;

@ApiPropertyOptional({
enum: MemberRoleEnum,
isArray: true,
})
@IsArray()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@IsArray()
@IsEnum(MemberRoleEnum)

roles?: MemberRoleEnum;

@ApiPropertyOptional()
@IsObject()
invite?: MemberInviteDTO;

@ApiPropertyOptional({
enum: { ...MemberStatusEnum },
})
@IsString()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@IsString()
@IsEnum(MemberStatusEnum)

memberStatus?: MemberStatusEnum;

@ApiProperty()
@IsString()
_organizationId: string;
}
50 changes: 50 additions & 0 deletions apps/api/src/app/organization/dtos/organization-response.dto.ts
@@ -0,0 +1,50 @@
import { PartnerTypeEnum, DirectionEnum } from '@novu/dal';
import { IsObject, IsOptional, IsString } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { UpdateBrandingDetailsDto } from './update-branding-details.dto';

export class IPartnerConfigurationResponseDto {
@ApiPropertyOptional()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@ApiPropertyOptional()
@ApiPropertyOptional()
@IsArray()
@IsString({ each: true })

projectIds?: string[];

@ApiProperty()
accessToken: string;

@ApiProperty()
configurationId: string;

@ApiPropertyOptional()
teamId: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@IsString()


@ApiProperty({
enum: { ...PartnerTypeEnum },
description: 'Partner Type Enum',
})
@IsString()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@IsString()
@IsEnum(PartnerTypeEnum)

partnerType: PartnerTypeEnum;
}

export class OrganizationBrandingResponseDto extends UpdateBrandingDetailsDto {
@ApiPropertyOptional({
enum: { ...DirectionEnum },
})
@IsString()
direction?: DirectionEnum;
}

export class OrganizationResponseDto {
@ApiProperty()
@IsString()
name: string;

@ApiPropertyOptional()
@IsString()
logo?: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to add here @ApiPropertyOptional()?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added ✅


@ApiProperty()
@IsObject()
branding: OrganizationBrandingResponseDto;

@ApiPropertyOptional()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@ApiPropertyOptional()
@ApiPropertyOptional()
@IsObject()

partnerConfigurations: IPartnerConfigurationResponseDto[];
}
62 changes: 47 additions & 15 deletions apps/api/src/app/organization/organization.controller.ts
Expand Up @@ -13,7 +13,7 @@ import {
} from '@nestjs/common';
import { OrganizationEntity } from '@novu/dal';
import { IJwtPayload, MemberRoleEnum } from '@novu/shared';
import { ApiExcludeController, ApiTags } from '@nestjs/swagger';
import { ApiTags, ApiOperation, ApiParam } from '@nestjs/swagger';
import { Roles } from '../auth/framework/roles.decorator';
import { UserSession } from '../shared/framework/user.decorator';
import { CreateOrganizationDto } from './dtos/create-organization.dto';
Expand All @@ -39,12 +39,14 @@ import { RenameOrganization } from './usecases/rename-organization/rename-organi
import { RenameOrganizationDto } from './dtos/rename-organization.dto';
import { UpdateBrandingDetailsDto } from './dtos/update-branding-details.dto';
import { UpdateMemberRolesDto } from './dtos/update-member-roles.dto';

import { ExternalApiAccessible } from '../auth/framework/external-api.decorator';
import { ApiResponse } from '../shared/framework/response.decorator';
import { OrganizationBrandingResponseDto, OrganizationResponseDto } from './dtos/organization-response.dto';
import { MemberResponseDto } from './dtos/member-response.dto';
@Controller('/organizations')
@UseInterceptors(ClassSerializerInterceptor)
@UseGuards(JwtAuthGuard)
@ApiTags('Organizations')
@ApiExcludeController()
export class OrganizationController {
constructor(
private createOrganizationUsecase: CreateOrganization,
Expand All @@ -58,6 +60,11 @@ export class OrganizationController {
) {}

@Post('/')
@ExternalApiAccessible()
@ApiResponse(OrganizationResponseDto, 201)
@ApiOperation({
summary: 'Create an organization',
})
async createOrganization(
@UserSession() user: IJwtPayload,
@Body() body: CreateOrganizationDto
Expand All @@ -72,6 +79,11 @@ export class OrganizationController {
}

@Get('/')
@ExternalApiAccessible()
@ApiResponse(OrganizationResponseDto, 200, true)
@ApiOperation({
summary: 'Fetch all organizations',
})
async getOrganizations(@UserSession() user: IJwtPayload): Promise<IGetOrganizationsDto> {
const command = GetOrganizationsCommand.create({
userId: user._id,
Expand All @@ -81,6 +93,11 @@ export class OrganizationController {
}

@Get('/me')
@ExternalApiAccessible()
@ApiResponse(OrganizationResponseDto)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add?

Suggested change
@ApiResponse(OrganizationResponseDto)
@ApiResponse(OrganizationResponseDto, 200)

@ApiOperation({
summary: 'Fetch current organization details',
})
async getMyOrganization(@UserSession() user: IJwtPayload): Promise<IGetMyOrganizationDto> {
const command = GetMyOrganizationCommand.create({
userId: user._id,
Expand All @@ -91,7 +108,13 @@ export class OrganizationController {
}

@Delete('/members/:memberId')
@ExternalApiAccessible()
@Roles(MemberRoleEnum.ADMIN)
@ApiResponse(MemberResponseDto)
@ApiOperation({
summary: 'Remove a member from organization using memberId',
})
@ApiParam({ name: 'memberId', type: String, required: true })
async removeMember(@UserSession() user: IJwtPayload, @Param('memberId') memberId: string) {
return await this.removeMemberUsecase.execute(
RemoveMemberCommand.create({
Expand All @@ -103,7 +126,13 @@ export class OrganizationController {
}

@Put('/members/:memberId/roles')
@ExternalApiAccessible()
@Roles(MemberRoleEnum.ADMIN)
@ApiResponse(MemberResponseDto)
@ApiOperation({
summary: 'Update a member role to admin',
})
@ApiParam({ name: 'memberId', type: String, required: true })
async updateMemberRoles(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to add here?
@ApiResponse(MemberDto, 200)

@UserSession() user: IJwtPayload,
@Param('memberId') memberId: string,
Expand All @@ -120,6 +149,11 @@ export class OrganizationController {
}

@Get('/members')
@ExternalApiAccessible()
@ApiResponse(MemberResponseDto, 200, true)
@ApiOperation({
summary: 'Fetch all members of current organizations',
})
async getMember(@UserSession() user: IJwtPayload) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to add here?
@ApiResponse(MemberDto, 200, true)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added MemberDto ✅

return await this.getMembers.execute(
GetMembersCommand.create({
Expand All @@ -130,19 +164,12 @@ export class OrganizationController {
);
}

@Post('/members/invite')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure what this endpoint was responsible of but i guess we don't need @post('/members/invite') that returns members. 👏

@Roles(MemberRoleEnum.ADMIN)
async inviteMember(@UserSession() user: IJwtPayload) {
return await this.getMembers.execute(
GetMembersCommand.create({
user,
userId: user._id,
organizationId: user.organizationId,
})
);
}

@Put('/branding')
@ExternalApiAccessible()
@ApiResponse(OrganizationBrandingResponseDto)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be with 200?

Suggested change
@ApiResponse(OrganizationBrandingResponseDto)
@ApiResponse(OrganizationBrandingResponseDto, 200)

@ApiOperation({
summary: 'Update organization branding details',
})
async updateBrandingDetails(@UserSession() user: IJwtPayload, @Body() body: UpdateBrandingDetailsDto) {
return await this.updateBrandingDetailsUsecase.execute(
UpdateBrandingDetailsCommand.create({
Expand All @@ -158,7 +185,12 @@ export class OrganizationController {
}

@Patch('/')
@ExternalApiAccessible()
@Roles(MemberRoleEnum.ADMIN)
@ApiResponse(RenameOrganizationDto)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be with 200?

Suggested change
@ApiResponse(RenameOrganizationDto)
@ApiResponse(RenameOrganizationDto, 200)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ApiResponse() has 200 as default status code

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, good to know :)

@ApiOperation({
summary: 'Rename organization name',
})
async renameOrganization(@UserSession() user: IJwtPayload, @Body() body: RenameOrganizationDto) {
return await this.renameOrganizationUsecase.execute(
RenameOrganizationCommand.create({
Expand Down
5 changes: 3 additions & 2 deletions apps/api/src/app/organization/organization.module.ts
@@ -1,14 +1,15 @@
import { MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common';
import { MiddlewareConsumer, Module, NestModule, RequestMethod, forwardRef } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { EnvironmentsModule } from '../environments/environments.module';
import { IntegrationModule } from '../integrations/integrations.module';
import { SharedModule } from '../shared/shared.module';
import { UserModule } from '../user/user.module';
import { OrganizationController } from './organization.controller';
import { USE_CASES } from './usecases';
import { AuthModule } from '../auth/auth.module';

@Module({
imports: [SharedModule, UserModule, EnvironmentsModule, IntegrationModule],
imports: [SharedModule, UserModule, EnvironmentsModule, IntegrationModule, forwardRef(() => AuthModule)],
controllers: [OrganizationController],
providers: [...USE_CASES],
exports: [...USE_CASES],
Expand Down
@@ -1,13 +1,13 @@
import { Injectable, Scope } from '@nestjs/common';
import { OrganizationRepository, MemberRepository } from '@novu/dal';
import { MemberRepository } from '@novu/dal';
import { MemberRoleEnum, MemberStatusEnum } from '@novu/shared';
import { GetMembersCommand } from './get-members.command';

@Injectable({
scope: Scope.REQUEST,
})
export class GetMembers {
constructor(private organizationRepository: OrganizationRepository, private membersRepository: MemberRepository) {}
constructor(private membersRepository: MemberRepository) {}

async execute(command: GetMembersCommand) {
return (await this.membersRepository.getOrganizationMembers(command.organizationId))
Expand Down
3 changes: 2 additions & 1 deletion apps/api/src/bootstrap.ts
Expand Up @@ -94,7 +94,7 @@ export async function bootstrap(expressApp?): Promise<INestApplication> {

const options = new DocumentBuilder()
.setTitle('Novu API')
.setDescription('The Novu API description')
.setDescription('Open API Specification for Novu API')
.setVersion('1.0')
.addTag('Events')
.addTag('Subscribers')
Expand All @@ -111,6 +111,7 @@ export async function bootstrap(expressApp?): Promise<INestApplication> {
.addTag('Feeds')
.addTag('Tenants')
.addTag('Messages')
.addTag('Organizations')
.addTag('Execution Details')
.build();
const document = SwaggerModule.createDocument(app, options);
Expand Down
5 changes: 5 additions & 0 deletions libs/dal/src/repositories/organization/organization.entity.ts
Expand Up @@ -30,3 +30,8 @@ export interface IPartnerConfiguration {
export enum PartnerTypeEnum {
VERCEL = 'vercel',
}

export enum DirectionEnum {
LTR = 'ltr',
RTL = 'trl',
}
4 changes: 2 additions & 2 deletions packages/node/README.md
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏

Expand Up @@ -712,7 +712,7 @@ const novu = new Novu('<NOVU_API_KEY>');

const payload = {
content: "<h1>Layout Start</h1>{{{body}}}<h1>Layout End</h1>",
description: "Organisation's first layout",
description: "Organization's first layout",
name: "First Layout",
identifier: "firstlayout",
variables: [
Expand All @@ -737,7 +737,7 @@ const novu = new Novu('<NOVU_API_KEY>');

const payloadToUpdate = {
content: "<h1>Layout Start</h1>{{{body}}}<h1>Layout End</h1>",
description: "Organisation's first layout",
description: "Organization's first layout",
name: "First Layout",
identifier: "firstlayout",
variables: [
Expand Down