Skip to content
Merged
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: 2 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
PORT=3006
PAYMENTS_URL=http://payments-api:8003

GATEWAY_HEADER_TOKEN=some-gateway-token
JWT_SECRET=jwt-secret
JITSI_SECRET=jitsi-secret-base64
JITSI_APP_ID=jitsi-app-id
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"@nestjs/swagger": "^11.1.0",
"agentkeepalive": "^4.6.0",
"axios": "^1.8.4",
"body-parser": "^2.2.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"cross-env": "^7.0.3",
Expand Down
2 changes: 2 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import configuration from './config/configuration';
import { SequelizeModule, SequelizeModuleOptions } from '@nestjs/sequelize';
import { format } from 'sql-formatter';
import { UserModule } from './modules/user/user.module';
import { WebhookModule } from './modules/webhook/webhook.module';

const defaultDbConfig = (
configService: ConfigService,
Expand Down Expand Up @@ -75,6 +76,7 @@ const defaultDbConfig = (
}),
CallModule,
UserModule,
WebhookModule,
],
controllers: [],
})
Expand Down
7 changes: 7 additions & 0 deletions src/config/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ export default () => ({
appId: process.env.JITSI_APP_ID,
apiKey: process.env.JITSI_API_KEY,
},
jitsiWebhook: {
secret: process.env.JITSI_WEBHOOK_SECRET,
events: {
participantLeft:
process.env.JITSI_WEBHOOK_PARTICIPANT_LEFT_ENABLED === 'true',
},
},
database: {
host: process.env.DB_HOSTNAME,
host2: process.env.DB_HOSTNAME2,
Expand Down
7 changes: 5 additions & 2 deletions src/modules/call/call.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,7 @@ describe('Testing Call Endpoints', () => {
mockUserToken.payload.email,
);
expect(callUseCase.createCallAndRoom).toHaveBeenCalledWith(
mockUserToken.payload.uuid,
mockUserToken.payload.email,
mockUserToken.payload,
);
expect(result).toEqual(mockResponse);
});
Expand Down Expand Up @@ -143,6 +142,7 @@ describe('Testing Call Endpoints', () => {
expect(result).toEqual(mockJoinCallResponse);
expect(callUseCase.joinCall).toHaveBeenCalledWith(mockRoomId, {
userId: user.uuid,
email: user.email,
name: mockJoinCallDto.name,
lastName: mockJoinCallDto.lastName,
anonymous: mockJoinCallDto.anonymous,
Expand All @@ -161,6 +161,7 @@ describe('Testing Call Endpoints', () => {
expect(result).toEqual(mockJoinCallResponse);
expect(callUseCase.joinCall).toHaveBeenCalledWith(mockRoomId, {
userId: undefined,
email: mockJoinCallDto.email,
name: mockJoinCallDto.name,
lastName: mockJoinCallDto.lastName,
anonymous: true,
Expand All @@ -185,6 +186,7 @@ describe('Testing Call Endpoints', () => {
name: anonymousDto.name,
lastName: anonymousDto.lastName,
anonymous: true,
email: mockUserPayload.email,
});
});

Expand Down Expand Up @@ -252,6 +254,7 @@ describe('Testing Call Endpoints', () => {

expect(callUseCase.joinCall).toHaveBeenCalledWith(mockRoomId, {
userId: mockUserToken.payload.uuid,
email: mockUserToken.payload.email,
name: undefined,
lastName: undefined,
anonymous: false,
Expand Down
5 changes: 3 additions & 2 deletions src/modules/call/call.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export class CallController {

try {
await this.callUseCase.validateUserHasNoActiveRoom(uuid, email);
const call = await this.callUseCase.createCallAndRoom(uuid, email);
const call = await this.callUseCase.createCallAndRoom(user);
return call;
} catch (error) {
const err = error as Error;
Expand Down Expand Up @@ -124,13 +124,14 @@ export class CallController {
@User() user: UserTokenData['payload'],
@Body() joinCallDto?: JoinCallDto,
): Promise<JoinCallResponseDto> {
const { uuid } = user || {};
const { uuid, email } = user || {};

return await this.callUseCase.joinCall(roomId, {
userId: uuid,
name: joinCallDto?.name,
lastName: joinCallDto?.lastName,
anonymous: joinCallDto?.anonymous || !user,
email: email,
});
}

Expand Down
16 changes: 11 additions & 5 deletions src/modules/call/call.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as uuid from 'uuid';
import configuration from '../../config/configuration';
import { PaymentService, Tier } from '../../externals/payments.service';
import { CallService } from './call.service';
import { mockUserPayload } from './fixtures';

jest.mock('uuid');
jest.mock('jsonwebtoken');
Expand Down Expand Up @@ -39,6 +40,7 @@ describe('Call service', () => {
});

it('When the user has meet enabled, then a call token should be created', async () => {
const userPayload = mockUserPayload;
const getUserTierSpy = jest
.spyOn(paymentService, 'getUserTier')
.mockResolvedValue({
Expand All @@ -53,7 +55,7 @@ describe('Call service', () => {
(uuid.v4 as jest.Mock).mockReturnValue('test-room-id');
(jwt.sign as jest.Mock).mockReturnValue('test-jitsi-token');

const result = await callService.createCallToken('user-123');
const result = await callService.createCallToken(userPayload);

expect(result).toEqual({
appId: 'jitsi-app-id',
Expand All @@ -62,10 +64,11 @@ describe('Call service', () => {
paxPerCall: 10,
});

expect(getUserTierSpy).toHaveBeenCalledWith('user-123');
expect(getUserTierSpy).toHaveBeenCalledWith(userPayload.uuid);
});

it('When the user does not have meet enabled, then an error indicating so is thrown', async () => {
const userPayload = mockUserPayload;
const getUserTierSpy = jest
.spyOn(paymentService, 'getUserTier')
.mockResolvedValue({
Expand All @@ -77,16 +80,17 @@ describe('Call service', () => {
},
} as Tier);

await expect(callService.createCallToken('user-123')).rejects.toThrow(
await expect(callService.createCallToken(userPayload)).rejects.toThrow(
UnauthorizedException,
);

expect(getUserTierSpy).toHaveBeenCalledWith('user-123');
expect(getUserTierSpy).toHaveBeenCalledWith(userPayload.uuid);
});

describe('createCallTokenForParticipant', () => {
it('should create a token for a registered user (non-moderator)', () => {
const userId = 'test-user-id';
const userPayload = mockUserPayload;
const userId = userPayload.uuid;
const roomId = 'test-room-id';
const isAnonymous = false;
const isModerator = false;
Expand All @@ -99,6 +103,7 @@ describe('Call service', () => {
roomId,
isAnonymous,
isModerator,
userPayload,
);

expect(result).toStrictEqual({
Expand Down Expand Up @@ -145,6 +150,7 @@ describe('Call service', () => {
roomId,
isAnonymous,
isModerator,
mockUserPayload,
);

expect(result).toStrictEqual({
Expand Down
22 changes: 14 additions & 8 deletions src/modules/call/call.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ import {
getJitsiJWTPayload,
getJitsiJWTSecret,
} from '../../lib/jitsi';
import { UserTokenData } from '../auth/dto/user.dto';
import { UserDataForToken } from '../user/user.attributes';
import { User } from '../user/user.domain';

export function SignWithRS256AndHeader(
payload: object,
secret: string,
Expand Down Expand Up @@ -51,7 +55,8 @@ export class CallService {
.getUserTier(userUuid)
.catch((err) => {
Logger.error(
`Failed to retrieve user tier from payment service: ${err.message}`,
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
`Failed to retrieve user tier from payment service: ${err?.message}`,
);
throw err;
});
Expand All @@ -61,8 +66,8 @@ export class CallService {
return meetFeature;
}

async createCallToken(userUuid: string) {
const meetFeatures = await this.getMeetFeatureConfigForUser(userUuid);
async createCallToken(user: User | UserTokenData['payload']) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Just curious, what is the difference between User and UserTokenData['payload]? Is it the same? Or User is for authenticated users and UserTokenData['payload'] is for anonymous users?

const meetFeatures = await this.getMeetFeatureConfigForUser(user.uuid);

if (!meetFeatures.enabled)
throw new UnauthorizedException(
Expand All @@ -72,9 +77,9 @@ export class CallService {
const newRoom = v4();
const token = generateJitsiJWT(
{
id: userUuid,
email: '[email protected]',
name: 'Example',
id: user.uuid,
email: user.email,
name: `${user.name} ${user.lastname}`,
},
newRoom,
true,
Expand All @@ -93,12 +98,13 @@ export class CallService {
roomId: string,
isAnonymous: boolean,
isModerator: boolean,
user?: UserDataForToken,
) {
const token = generateJitsiJWT(
{
id: userId,
email: isAnonymous ? '[email protected]' : 'user@inxt.com',
name: isAnonymous ? 'Anonymous' : 'User',
email: isAnonymous ? '[email protected]' : user.email,
name: isAnonymous ? 'Anonymous' : `${user.name} ${user?.lastName}`,
},
roomId,
isModerator,
Expand Down
Loading
Loading