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

feat: 0.20.1 patch release #4653

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .commitlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"subject-case": [
2,
"always",
["sentence-case", "start-case", "pascal-case", "upper-case", "lower-case"]
["sentence-case", "start-case", "pascal-case", "upper-case", "lower-case", "camel-case"]
],
"type-enum": [
2,
Expand Down
23 changes: 14 additions & 9 deletions apps/api/src/app/events/e2e/delay-events.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ describe('Trigger event - Delay triggered events - /v1/events/trigger (POST)', f
let standardQueueService: StandardQueueService;
const messageRepository = new MessageRepository();

const triggerEvent = async (payload, transactionId?: string, overrides = {}) => {
const triggerEvent = async (payload, transactionId?: string, overrides = {}, to = [subscriber.subscriberId]) => {
await axiosInstance.post(
`${session.serverUrl}/v1/events/trigger`,
{
transactionId,
name: template.triggers[0].identifier,
to: [subscriber.subscriberId],
to,
payload,
overrides,
},
Expand Down Expand Up @@ -330,6 +330,8 @@ describe('Trigger event - Delay triggered events - /v1/events/trigger (POST)', f
});

it('should be able to cancel delay', async function () {
const secondSubscriber = await subscriberService.createSubscriber();

const id = MessageRepository.createObjectId();
template = await session.createTemplate({
steps: [
Expand All @@ -342,7 +344,7 @@ describe('Trigger event - Delay triggered events - /v1/events/trigger (POST)', f
content: '',
metadata: {
unit: DigestUnitEnum.SECONDS,
amount: 0.1,
amount: 5,
type: DelayTypeEnum.REGULAR,
},
},
Expand All @@ -357,17 +359,19 @@ describe('Trigger event - Delay triggered events - /v1/events/trigger (POST)', f
{
customVar: 'Testing of User Name',
},
id
id,
{},
[subscriber.subscriberId, secondSubscriber.subscriberId]
);

await session.awaitRunningJobs(template?._id, true, 1);
await session.awaitRunningJobs(template?._id, true, 2);
await axiosInstance.delete(`${session.serverUrl}/v1/events/trigger/${id}`, {
headers: {
authorization: `ApiKey ${session.apiKey}`,
},
});

let delayedJob = await jobRepository.findOne({
let delayedJobs = await jobRepository.find({
_environmentId: session.environment._id,
_templateId: template._id,
type: StepTypeEnum.DELAY,
Expand All @@ -380,14 +384,15 @@ describe('Trigger event - Delay triggered events - /v1/events/trigger (POST)', f
transactionId: id,
});

expect(pendingJobs).to.equal(1);
expect(pendingJobs).to.equal(2);

delayedJob = await jobRepository.findOne({
delayedJobs = await jobRepository.find({
_environmentId: session.environment._id,
_templateId: template._id,
type: StepTypeEnum.DELAY,
transactionId: id,
});
expect(delayedJob!.status).to.equal(JobStatusEnum.CANCELED);
expect(delayedJobs[0]!.status).to.equal(JobStatusEnum.CANCELED);
expect(delayedJobs[1]!.status).to.equal(JobStatusEnum.CANCELED);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,25 @@ export class CancelDelayed {
constructor(private jobRepository: JobRepository) {}

public async execute(command: CancelDelayedCommand): Promise<boolean> {
const job = await this.jobRepository.findOne({
_environmentId: command.environmentId,
transactionId: command.transactionId,
status: JobStatusEnum.DELAYED,
});
const jobs = await this.jobRepository.find(
{
_environmentId: command.environmentId,
transactionId: command.transactionId,
status: JobStatusEnum.DELAYED,
},
'_id'
);

if (!job) {
if (!jobs?.length) {
return false;
}

await this.jobRepository.update(
{
_environmentId: command.environmentId,
_id: job._id,
_id: {
$in: jobs.map((job) => job._id),
},
},
{
$set: {
Expand Down
78 changes: 78 additions & 0 deletions apps/api/src/app/organization/dtos/member-response.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { MemberRoleEnum, MemberStatusEnum } from '@novu/shared';
import { IsArray, IsDate, IsObject, IsString, IsEnum } 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;

@ApiPropertyOptional()
@IsDate()
answerDate?: Date;

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

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

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

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

@ApiPropertyOptional({
enum: MemberRoleEnum,
isArray: true,
})
@IsEnum(MemberRoleEnum)
roles?: MemberRoleEnum;

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

@ApiPropertyOptional({
enum: { ...MemberStatusEnum },
})
@IsEnum(MemberStatusEnum)
memberStatus?: MemberStatusEnum;

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

export class IPartnerConfigurationResponseDto {
@ApiPropertyOptional()
@IsArray()
@IsString({ each: true })
projectIds?: string[];

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

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

@ApiPropertyOptional()
@IsString()
teamId: string;

@ApiProperty({
enum: { ...PartnerTypeEnum },
description: 'Partner Type Enum',
})
@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;

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

@ApiPropertyOptional()
@IsObject()
partnerConfigurations: IPartnerConfigurationResponseDto[];
}
62 changes: 47 additions & 15 deletions apps/api/src/app/organization/organization.controller.ts
Original file line number Diff line number Diff line change
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)
@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(
@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) {
return await this.getMembers.execute(
GetMembersCommand.create({
Expand All @@ -130,19 +164,12 @@ export class OrganizationController {
);
}

@Post('/members/invite')
@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)
@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)
@ApiOperation({
summary: 'Rename organization name',
})
async renameOrganization(@UserSession() user: IJwtPayload, @Body() body: RenameOrganizationDto) {
return await this.renameOrganizationUsecase.execute(
RenameOrganizationCommand.create({
Expand Down
Loading
Loading