Skip to content

Commit

Permalink
feat(api-service): plain cards fetch user organizations (#7268)
Browse files Browse the repository at this point in the history
Co-authored-by: Dima Grossman <[email protected]>
  • Loading branch information
jainpawan21 and scopsy authored Dec 23, 2024
1 parent 4e502b8 commit 4914945
Show file tree
Hide file tree
Showing 11 changed files with 322 additions and 116 deletions.
2 changes: 2 additions & 0 deletions apps/api/src/.example.env
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ CLERK_LONG_LIVED_TOKEN=
TUNNEL_BASE_ADDRESS=
PLAIN_SUPPORT_KEY='PLAIN_SUPPORT_KEY'
PLAIN_IDENTITY_VERIFICATION_SECRET_KEY='PLAIN_IDENTITY_VERIFICATION_SECRET_KEY'
PLAIN_CARDS_HMAC_SECRET_KEY='PLAIN_CARDS_HMAC_SECRET_KEY'

NOVU_INTERNAL_SECRET_KEY=
# expressed in seconds or a string describing a time span [zeit/ms](https://github.com/zeit/ms.js). Eg: 60, "2 days", "10h", "7d"
SUBSCRIBER_WIDGET_JWT_EXPIRATION_TIME='15 days'
45 changes: 45 additions & 0 deletions apps/api/src/app/support/dto/plain-card.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { ApiProperty } from '@nestjs/swagger';

export class PlainCustomer {
@ApiProperty()
id: string;

@ApiProperty()
externalId?: string;

@ApiProperty()
email?: string;
}

export class PlainTenant {
@ApiProperty()
id?: string;

@ApiProperty()
externalId?: string;
}

export class PlainThread {
@ApiProperty()
id?: string;

@ApiProperty()
externalId?: string;
}

export class PlainCardRequestDto {
@ApiProperty()
cardKeys?: string[];

@ApiProperty()
customer?: PlainCustomer | null;

@ApiProperty()
tenant?: PlainTenant | null;

@ApiProperty()
thread?: PlainThread | null;

@ApiProperty()
timestamp: string;
}
18 changes: 18 additions & 0 deletions apps/api/src/app/support/guards/plain-cards.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import { Observable } from 'rxjs';
import crypto from 'node:crypto';

@Injectable()
export class PlainCardsGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();

const requestBody = JSON.stringify(request.body);
const plainCardsHMACSecretKey = process.env.PLAIN_CARDS_HMAC_SECRET_KEY as string;
const incomingSignature = request.headers['plain-request-signature'];
if (!incomingSignature) throw new UnauthorizedException('Plain request signature is missing');
const expectedSignature = crypto.createHmac('sha-256', plainCardsHMACSecretKey).update(requestBody).digest('hex');

return incomingSignature === expectedSignature;
}
}
125 changes: 11 additions & 114 deletions apps/api/src/app/support/support.controller.ts
Original file line number Diff line number Diff line change
@@ -1,127 +1,24 @@
import { Body, Controller, Post, UseGuards } from '@nestjs/common';
import { Body, Controller, Post, UseGuards, Request, Response, RawBodyRequest } from '@nestjs/common';
import { UserAuthGuard, UserSession } from '@novu/application-generic';
import { UserRepository } from '@novu/dal';
import { UserSessionData } from '@novu/shared';
import { CreateSupportThreadDto } from './dto/create-thread.dto';
import { CreateSupportThreadUsecase } from './usecases/create-thread.usecase';
import { CreateSupportThreadCommand } from './usecases/create-thread.command';
import { PlainCardRequestDto } from './dto/plain-card.dto';
import { PlainCardsCommand } from './usecases/plain-cards.command';
import { CreateSupportThreadUsecase, PlainCardsUsecase } from './usecases';
import { PlainCardsGuard } from './guards/plain-cards.guard';

@Controller('/support')
export class SupportController {
constructor(
private readonly userRepository: UserRepository,
private createSupportThreadUsecase: CreateSupportThreadUsecase
private createSupportThreadUsecase: CreateSupportThreadUsecase,
private plainCardsUsecase: PlainCardsUsecase
) {}

@Post('plain/cards')
async getPlainCards() {
return {
data: {},

cards: [
{
key: 'plain-customer-details',
components: [
{
componentSpacer: {
spacerSize: 'S',
},
},
{
componentRow: {
rowMainContent: [
{
componentText: {
text: 'Registered at',
textColor: 'MUTED',
},
},
],
rowAsideContent: [
{
componentText: {
text: '7/18/2024, 1:00 PM',
},
},
],
},
},
{
componentSpacer: {
spacerSize: 'M',
},
},
{
componentRow: {
rowMainContent: [
{
componentText: {
text: 'Last signed in',
textColor: 'MUTED',
},
},
],
rowAsideContent: [
{
componentText: {
text: '10/20/2024, 12:57 PM',
},
},
],
},
},
{
componentSpacer: {
spacerSize: 'M',
},
},
{
componentRow: {
rowMainContent: [
{
componentText: {
text: 'Last device used',
textColor: 'MUTED',
},
},
],
rowAsideContent: [
{
componentText: {
text: 'iPhone 13 🍎',
},
},
],
},
},
{
componentSpacer: {
spacerSize: 'M',
},
},
{
componentRow: {
rowMainContent: [
{
componentText: {
text: 'Marketing preferences',
textColor: 'MUTED',
},
},
],
rowAsideContent: [
{
componentText: {
text: 'Opted out 🙅',
},
},
],
},
},
],
},
],
};
@UseGuards(PlainCardsGuard)
@Post('user-organizations')
async fetchUserOrganizations(@Body() body: PlainCardRequestDto) {
return this.plainCardsUsecase.fetchUserOrganizations(PlainCardsCommand.create({ ...body }));
}

@UseGuards(UserAuthGuard)
Expand Down
6 changes: 4 additions & 2 deletions apps/api/src/app/support/support.module.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { Module } from '@nestjs/common';
import { SupportService } from '@novu/application-generic';
import { OrganizationRepository } from '@novu/dal';
import { SupportController } from './support.controller';
import { SharedModule } from '../shared/shared.module';
import { CreateSupportThreadUsecase } from './usecases/create-thread.usecase';
import { CreateSupportThreadUsecase, PlainCardsUsecase } from './usecases';
import { PlainCardsGuard } from './guards/plain-cards.guard';

@Module({
imports: [SharedModule],
controllers: [SupportController],
providers: [CreateSupportThreadUsecase, SupportService],
providers: [CreateSupportThreadUsecase, PlainCardsUsecase, SupportService, OrganizationRepository, PlainCardsGuard],
})
export class SupportModule {}
2 changes: 2 additions & 0 deletions apps/api/src/app/support/usecases/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './create-thread.usecase';
export * from './plain-cards.usecase';
22 changes: 22 additions & 0 deletions apps/api/src/app/support/usecases/plain-cards.command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { BaseCommand } from '@novu/application-generic';
import { IsArray, IsDefined, IsOptional, IsString } from 'class-validator';
import { PlainCustomer, PlainTenant, PlainThread } from '../dto/plain-card.dto';

export class PlainCardsCommand extends BaseCommand {
@IsOptional()
@IsArray()
cardKeys?: string[];

@IsOptional()
customer?: PlainCustomer | null;

@IsOptional()
tenant?: PlainTenant | null;

@IsOptional()
thread?: PlainThread | null;

@IsDefined()
@IsString()
timestamp: string;
}
Loading

0 comments on commit 4914945

Please sign in to comment.