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/billing phone number #372

Merged
merged 4 commits into from
Jul 23, 2024
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
16 changes: 16 additions & 0 deletions migrations/20240722152617-add-phoneNumber-to-workspaces.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use strict';

const tableName = 'workspaces';
const newColumn = 'phone_number';

module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.addColumn(tableName, newColumn, {
type: Sequelize.STRING,
});
},

async down(queryInterface) {
await queryInterface.removeColumn(tableName, newColumn);
},
};
26 changes: 26 additions & 0 deletions src/externals/payments/payments.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,30 @@ export class PaymentsService {
);
return res.data;
}

async updateBillingInfo(
userUuid: UserAttributes['uuid'],
payload: {
phoneNumber?: string;
address?: string;
},
): Promise<void> {
const jwt = Sign(
{ payload: { uuid: userUuid } },
this.configService.get('secrets.jwt'),
);

const params = {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${jwt}`,
},
};

await this.httpClient.patch(
`${this.configService.get('apis.payments.url')}/billing`,
payload,
params,
);
}
}
7 changes: 7 additions & 0 deletions src/modules/gateway/dto/initialize-workspace.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ export class InitializeWorkspaceDto {
@IsOptional()
address?: string;

@ApiProperty({
example: '+34 622 111 333',
description: 'Phone number',
})
@IsOptional()
phoneNumber?: string;

@ApiProperty({
example: 312321312,
description: 'Workspace max space in bytes',
Expand Down
4 changes: 4 additions & 0 deletions src/modules/gateway/gateway.usecase.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ describe('GatewayUseCases', () => {
const owner = newUser();
const maxSpaceBytes = 1000000;
const workspaceAddress = '123 Main St';
const workspacePhoneNumber = '+1 (123) 456-7890';

it('When owner does not exist, then it should throw', async () => {
jest
Expand All @@ -56,6 +57,7 @@ describe('GatewayUseCases', () => {
ownerId: owner.uuid,
maxSpaceBytes,
address: workspaceAddress,
phoneNumber: workspacePhoneNumber,
numberOfSeats: 20,
};

Expand All @@ -71,6 +73,7 @@ describe('GatewayUseCases', () => {
attributes: {
defaultTeamId: newDefaultTeam.id,
address: workspaceAddress,
phoneNumber: workspacePhoneNumber,
},
});
jest
Expand All @@ -80,6 +83,7 @@ describe('GatewayUseCases', () => {
ownerId: owner.uuid,
maxSpaceBytes,
address: workspaceAddress,
phoneNumber: workspacePhoneNumber,
numberOfSeats: 20,
};
await expect(
Expand Down
3 changes: 2 additions & 1 deletion src/modules/gateway/gateway.usecase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@ export class GatewayUseCases {
Logger.log(
`Initializing workspace with owner id: ${initializeWorkspaceDto.ownerId}`,
);
const { ownerId, maxSpaceBytes, address, numberOfSeats } =
const { ownerId, maxSpaceBytes, address, numberOfSeats, phoneNumber } =
initializeWorkspaceDto;

return this.workspaceUseCases.initiateWorkspace(ownerId, maxSpaceBytes, {
address,
numberOfSeats,
phoneNumber,
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface WorkspaceAttributes {
workspaceUserId: string;
setupCompleted: boolean;
numberOfSeats: number;
phoneNumber?: string;
rootFolderId?: string;
createdAt: Date;
updatedAt: Date;
Expand Down
4 changes: 4 additions & 0 deletions src/modules/workspaces/domains/workspaces.domain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export class Workspace implements WorkspaceAttributes {
workspaceUserId: string;
setupCompleted: boolean;
numberOfSeats: number;
phoneNumber?: string;
createdAt: Date;
updatedAt: Date;

Expand All @@ -29,6 +30,7 @@ export class Workspace implements WorkspaceAttributes {
setupCompleted,
avatar,
numberOfSeats,
phoneNumber,
createdAt,
updatedAt,
}: WorkspaceAttributes) {
Expand All @@ -43,6 +45,7 @@ export class Workspace implements WorkspaceAttributes {
this.setupCompleted = setupCompleted;
this.rootFolderId = rootFolderId;
this.numberOfSeats = numberOfSeats;
this.phoneNumber = phoneNumber;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
Expand Down Expand Up @@ -79,6 +82,7 @@ export class Workspace implements WorkspaceAttributes {
avatar: this.avatar,
workspaceUserId: this.workspaceUserId,
numberOfSeats: this.numberOfSeats,
phoneNumber: this.phoneNumber,
setupCompleted: this.setupCompleted,
createdAt: this.createdAt,
updatedAt: this.updatedAt,
Expand Down
11 changes: 10 additions & 1 deletion src/modules/workspaces/dto/edit-workspace-details-dto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsOptional, IsString, Length } from 'class-validator';
import { IsOptional, IsPhoneNumber, IsString, Length } from 'class-validator';
import { Workspace } from '../domains/workspaces.domain';

export class EditWorkspaceDetailsDto {
Expand All @@ -21,8 +21,17 @@ export class EditWorkspaceDetailsDto {
@IsString()
@Length(0, 150)
description?: Workspace['description'];

@IsOptional()
@IsString()
@Length(5, 255)
address?: Workspace['address'];

@ApiProperty({
example: '+34 622 111 333',
description: 'Phone number',
})
@IsOptional()
@IsPhoneNumber()
phoneNumber?: Workspace['phoneNumber'];
}
3 changes: 3 additions & 0 deletions src/modules/workspaces/models/workspace.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ export class WorkspaceModel extends Model implements WorkspaceAttributes {
@Column(DataType.INTEGER)
numberOfSeats: number;

@Column(DataType.STRING)
phoneNumber: string;

@HasOne(() => FolderModel, 'uuid')
rootFolder: FolderModel;

Expand Down
4 changes: 4 additions & 0 deletions src/modules/workspaces/workspaces.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import { AvatarService } from '../../externals/avatar/avatar.service';
import { FolderModule } from '../folder/folder.module';
import { FileModule } from '../file/file.module';
import { SharingModule } from '../sharing/sharing.module';
import { PaymentsService } from 'src/externals/payments/payments.service';
import { HttpClientModule } from 'src/externals/http/http.module';

@Module({
imports: [
Expand All @@ -35,6 +37,7 @@ import { SharingModule } from '../sharing/sharing.module';
forwardRef(() => SharingModule),
BridgeModule,
MailerModule,
HttpClientModule,
],
controllers: [WorkspacesController],
providers: [
Expand All @@ -44,6 +47,7 @@ import { SharingModule } from '../sharing/sharing.module';
SequelizeWorkspaceRepository,
WorkspaceGuard,
AvatarService,
PaymentsService,
],
exports: [WorkspacesUsecases, SequelizeModule],
})
Expand Down
47 changes: 47 additions & 0 deletions src/modules/workspaces/workspaces.usecase.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import {
import { Role } from '../sharing/sharing.domain';
import { WorkspaceAttributes } from './attributes/workspace.attributes';
import * as jwtUtils from '../../lib/jwt';
import { PaymentsService } from '../../externals/payments/payments.service';

jest.mock('../../middlewares/passport', () => {
const originalModule = jest.requireActual('../../middlewares/passport');
Expand All @@ -75,6 +76,7 @@ describe('WorkspacesUsecases', () => {
let folderUseCases: FolderUseCases;
let fileUseCases: FileUseCases;
let sharingUseCases: SharingService;
let paymentsService: PaymentsService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
Expand All @@ -101,6 +103,7 @@ describe('WorkspacesUsecases', () => {
folderUseCases = module.get<FolderUseCases>(FolderUseCases);
fileUseCases = module.get<FileUseCases>(FileUseCases);
sharingUseCases = module.get<SharingService>(SharingService);
paymentsService = module.get<PaymentsService>(PaymentsService);
});

it('should be defined', () => {
Expand Down Expand Up @@ -403,6 +406,50 @@ describe('WorkspacesUsecases', () => {
editWorkspaceDto,
);
});
it('When address or phoneNumber are provided, then it should call payments service', async () => {
jest
.spyOn(workspaceRepository, 'findById')
.mockResolvedValueOnce(workspace);
jest.spyOn(paymentsService, 'updateBillingInfo').mockResolvedValueOnce();

await service.editWorkspaceDetails(workspace.id, user, {
address: 'new address',
phoneNumber: 'new phone number',
});

expect(paymentsService.updateBillingInfo).toHaveBeenCalledWith(
user.uuid,
{
address: 'new address',
phoneNumber: 'new phone number',
},
);
});
it('When address or phoneNumber are provided and payments service for some reason fails, it should throw', async () => {
jest
.spyOn(workspaceRepository, 'findById')
.mockResolvedValueOnce(workspace);
jest
.spyOn(paymentsService, 'updateBillingInfo')
.mockRejectedValueOnce(new Error());

await expect(
service.editWorkspaceDetails(workspace.id, user, {
address: 'new address',
phoneNumber: 'new phone number',
}),
).rejects.toThrow(InternalServerErrorException);

expect(paymentsService.updateBillingInfo).toHaveBeenCalledWith(
user.uuid,
{
address: 'new address',
phoneNumber: 'new phone number',
},
);

expect(workspaceRepository.updateBy).not.toHaveBeenCalled();
});
});

describe('setupWorkspace', () => {
Expand Down
29 changes: 28 additions & 1 deletion src/modules/workspaces/workspaces.usecase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,15 @@ import {
import { WorkspaceItemUser } from './domains/workspace-item-user.domain';
import { SharingService } from '../sharing/sharing.service';
import { ChangeUserAssignedSpaceDto } from './dto/change-user-assigned-space.dto';
import { PaymentsService } from '../../externals/payments/payments.service';

@Injectable()
export class WorkspacesUsecases {
constructor(
private readonly teamRepository: SequelizeWorkspaceTeamRepository,
private readonly workspaceRepository: SequelizeWorkspaceRepository,
private readonly sharingUseCases: SharingService,
private readonly paymentService: PaymentsService,
private networkService: BridgeService,
private userRepository: SequelizeUserRepository,
private userUsecases: UserUseCases,
Expand All @@ -84,7 +86,11 @@ export class WorkspacesUsecases {
async initiateWorkspace(
ownerId: UserAttributes['uuid'],
maxSpaceBytes: number,
workspaceData: { address?: string; numberOfSeats: number },
workspaceData: {
address?: string;
numberOfSeats: number;
phoneNumber?: string;
},
) {
const owner = await this.userRepository.findByUuid(ownerId);

Expand Down Expand Up @@ -129,6 +135,7 @@ export class WorkspacesUsecases {
name: 'My Workspace',
address: workspaceData?.address,
numberOfSeats: workspaceData.numberOfSeats,
phoneNumber: workspaceData?.phoneNumber,
avatar: null,
defaultTeamId: newDefaultTeam.id,
workspaceUserId: workspaceUser.uuid,
Expand Down Expand Up @@ -2068,6 +2075,26 @@ export class WorkspacesUsecases {
throw new ForbiddenException('You are not the owner of this workspace');
}

if (
editWorkspaceDetailsDto.phoneNumber ||
editWorkspaceDetailsDto.address
) {
try {
await this.paymentService.updateBillingInfo(workspace.ownerId, {
phoneNumber: editWorkspaceDetailsDto.phoneNumber,
address: editWorkspaceDetailsDto.address,
});
} catch (error) {
Logger.error(
`[WORKSPACE/EDIT_DETAILS]: Error while updating billing information ${
(error as Error).message
}`,
);
throw new InternalServerErrorException(
'Error while updating billing information',
);
}
}
await this.workspaceRepository.updateBy(
{ id: workspaceId },
editWorkspaceDetailsDto,
Expand Down
Loading