Skip to content

Commit 755d593

Browse files
committed
add update connection title functionality with DTO, data structure, and controller integration
1 parent 144d79f commit 755d593

9 files changed

Lines changed: 356 additions & 0 deletions

File tree

backend/src/common/data-injection.tokens.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export enum UseCaseType {
4343
REFRESH_CONNECTION_AGENT_TOKEN = 'REFRESH_CONNECTION_AGENT_TOKEN',
4444
VALIDATE_CONNECTION_MASTER_PASSWORD = 'VALIDATE_CONNECTION_MASTER_PASSWORD',
4545
UNFREEZE_CONNECTION = 'UNFREEZE_CONNECTION',
46+
UPDATE_CONNECTION_TITLE = 'UPDATE_CONNECTION_TITLE',
4647

4748
FIND_ALL_USER_GROUPS = 'FIND_ALL_USER_GROUPS',
4849
INVITE_USER_IN_GROUP = 'INVITE_USER_IN_GROUP',
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export class UpdateConnectionTitleDs {
2+
connectionId: string;
3+
userId: string;
4+
title: string;
5+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { ApiProperty } from '@nestjs/swagger';
2+
import { IsNotEmpty, IsString } from 'class-validator';
3+
4+
export class UpdateConnectionTitleDto {
5+
@ApiProperty()
6+
@IsString()
7+
@IsNotEmpty()
8+
title: string;
9+
}

backend/src/entities/connection/connection.controller.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import { GetGroupsInConnectionDs } from './application/data-structures/get-group
4141
import { GetPermissionsInConnectionDs } from './application/data-structures/get-permissions-in-connection.ds.js';
4242
import { RestoredConnectionDs } from './application/data-structures/restored-connection.ds.js';
4343
import { UpdateConnectionDs } from './application/data-structures/update-connection.ds.js';
44+
import { UpdateConnectionTitleDs } from './application/data-structures/update-connection-title.ds.js';
4445
import { UpdateMasterPasswordDs } from './application/data-structures/update-master-password.ds.js';
4546
import { ValidateConnectionMasterPasswordDs } from './application/data-structures/validate-connection-master-password.ds.js';
4647
import { CreateConnectionDto } from './application/dto/create-connection.dto.js';
@@ -51,6 +52,7 @@ import { DeleteGroupFromConnectionDTO } from './application/dto/delete-group-fro
5152
import { FoundUserGroupsInConnectionDTO } from './application/dto/found-user-groups-in-connection.dto.js';
5253
import { ConnectionTokenResponseDTO } from './application/dto/new-connection-token-response.dto.js';
5354
import { TestConnectionResponseDTO } from './application/dto/test-connection-response.dto.js';
55+
import { UpdateConnectionTitleDto } from './application/dto/update-connection-title.dto.js';
5456
import { UpdateMasterPasswordRequestBodyDto } from './application/dto/update-master-password-request-body.dto.js';
5557
import { UpdatedConnectionResponseDTO } from './application/dto/updated-connection-response.dto.js';
5658
import { ValidationResultRo } from './application/dto/validation-result.ro.js';
@@ -69,6 +71,7 @@ import {
6971
ITestConnection,
7072
IUnfreezeConnection,
7173
IUpdateConnection,
74+
IUpdateConnectionTitle,
7275
IUpdateMasterPassword,
7376
IValidateConnectionMasterPassword,
7477
IValidateConnectionToken,
@@ -120,6 +123,8 @@ export class ConnectionController {
120123
private readonly validateConnectionMasterPasswordUseCase: IValidateConnectionMasterPassword,
121124
@Inject(UseCaseType.UNFREEZE_CONNECTION)
122125
private readonly unfreezeConnectionUseCase: IUnfreezeConnection,
126+
@Inject(UseCaseType.UPDATE_CONNECTION_TITLE)
127+
private readonly updateConnectionTitleUseCase: IUpdateConnectionTitle,
123128
@Inject(BaseType.GLOBAL_DB_CONTEXT)
124129
protected _dbContext: IGlobalDatabaseContext,
125130
private readonly amplitudeService: AmplitudeService,
@@ -685,4 +690,29 @@ export class ConnectionController {
685690
}
686691
return await this.unfreezeConnectionUseCase.execute({ connectionId, userId }, InTransactionEnum.ON);
687692
}
693+
694+
@ApiOperation({ summary: 'Update connection title' })
695+
@ApiBody({ type: UpdateConnectionTitleDto })
696+
@ApiResponse({
697+
status: 200,
698+
type: SuccessResponse,
699+
description: 'Connection title was updated.',
700+
})
701+
@UseGuards(ConnectionEditGuard)
702+
@Put('/connection/title/:connectionId')
703+
async updateConnectionTitle(
704+
@Body() titleData: UpdateConnectionTitleDto,
705+
@SlugUuid('connectionId') connectionId: string,
706+
@UserId() userId: string,
707+
): Promise<SuccessResponse> {
708+
if (!connectionId) {
709+
throw new BadRequestException(Messages.CONNECTION_ID_MISSING);
710+
}
711+
const inputData: UpdateConnectionTitleDs = {
712+
connectionId,
713+
userId,
714+
title: titleData.title,
715+
};
716+
return await this.updateConnectionTitleUseCase.execute(inputData, InTransactionEnum.ON);
717+
}
688718
}

backend/src/entities/connection/connection.module.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { RefreshConnectionAgentTokenUseCase } from './use-cases/refresh-connecti
3131
import { RestoreConnectionUseCase } from './use-cases/restore-connection-use.case.js';
3232
import { TestConnectionUseCase } from './use-cases/test-connection.use.case.js';
3333
import { UnfreezeConnectionUseCase } from './use-cases/unfreeze-connection.use.case.js';
34+
import { UpdateConnectionTitleUseCase } from './use-cases/update-connection-title.use.case.js';
3435
import { UpdateConnectionUseCase } from './use-cases/update-connection.use.case.js';
3536
import { UpdateConnectionMasterPasswordUseCase } from './use-cases/update-connection-master-password.use.case.js';
3637
import { ValidateConnectionMasterPasswordUseCase } from './use-cases/validate-connection-master-password.use.case.js';
@@ -130,6 +131,10 @@ import { ValidateConnectionTokenUseCase } from './use-cases/validate-connection-
130131
provide: UseCaseType.UNFREEZE_CONNECTION,
131132
useClass: UnfreezeConnectionUseCase,
132133
},
134+
{
135+
provide: UseCaseType.UPDATE_CONNECTION_TITLE,
136+
useClass: UpdateConnectionTitleUseCase,
137+
},
133138
],
134139
controllers: [ConnectionController],
135140
})
@@ -156,6 +161,7 @@ export class ConnectionModule implements NestModule {
156161
{ path: '/connection/token/refresh/:connectionId', method: RequestMethod.GET },
157162
{ path: '/connection/masterpwd/verify/:connectionId', method: RequestMethod.GET },
158163
{ path: '/connection/unfreeze/:connectionId', method: RequestMethod.PUT },
164+
{ path: '/connection/title/:connectionId', method: RequestMethod.PUT },
159165
)
160166
.apply(AuthWithApiMiddleware)
161167
.forRoutes({ path: 'connections', method: RequestMethod.GET });
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { Inject, Injectable, Scope } from '@nestjs/common';
2+
import AbstractUseCase from '../../../common/abstract-use.case.js';
3+
import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js';
4+
import { BaseType } from '../../../common/data-injection.tokens.js';
5+
import { SuccessResponse } from '../../../microservices/saas-microservice/data-structures/common-responce.ds.js';
6+
import { UpdateConnectionTitleDs } from '../application/data-structures/update-connection-title.ds.js';
7+
import { IUpdateConnectionTitle } from './use-cases.interfaces.js';
8+
9+
@Injectable({ scope: Scope.REQUEST })
10+
export class UpdateConnectionTitleUseCase
11+
extends AbstractUseCase<UpdateConnectionTitleDs, SuccessResponse>
12+
implements IUpdateConnectionTitle
13+
{
14+
constructor(
15+
@Inject(BaseType.GLOBAL_DB_CONTEXT)
16+
protected _dbContext: IGlobalDatabaseContext,
17+
) {
18+
super();
19+
}
20+
21+
protected async implementation(inputData: UpdateConnectionTitleDs): Promise<SuccessResponse> {
22+
const { connectionId, title } = inputData;
23+
24+
const connection = await this._dbContext.connectionRepository.findOne({ where: { id: connectionId } });
25+
connection.title = title;
26+
await this._dbContext.connectionRepository.save(connection);
27+
return { success: true };
28+
}
29+
}

backend/src/entities/connection/use-cases/use-cases.interfaces.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { TestConnectionResultDs } from '../application/data-structures/test-conn
1818
import { TokenDs } from '../application/data-structures/token.ds.js';
1919
import { UnfreezeConnectionDs } from '../application/data-structures/unfreeze-connection.ds.js';
2020
import { UpdateConnectionDs } from '../application/data-structures/update-connection.ds.js';
21+
import { UpdateConnectionTitleDs } from '../application/data-structures/update-connection-title.ds.js';
2122
import { UpdateMasterPasswordDs } from '../application/data-structures/update-master-password.ds.js';
2223
import { ValidateConnectionMasterPasswordDs } from '../application/data-structures/validate-connection-master-password.ds.js';
2324
import { CreatedConnectionDTO } from '../application/dto/created-connection.dto.js';
@@ -101,3 +102,7 @@ export interface IValidateConnectionMasterPassword {
101102
export interface IUnfreezeConnection {
102103
execute(inputData: UnfreezeConnectionDs, inTransaction: InTransactionEnum): Promise<SuccessResponse>;
103104
}
105+
106+
export interface IUpdateConnectionTitle {
107+
execute(inputData: UpdateConnectionTitleDs, inTransaction: InTransactionEnum): Promise<SuccessResponse>;
108+
}

backend/test/ava-tests/non-saas-tests/non-saas-connection-e2e.test.ts

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1517,3 +1517,136 @@ currentTest = 'GET /connection/user/permissions';
15171517
// throw e;
15181518
// }
15191519
// });
1520+
1521+
currentTest = 'PUT /connection/title';
1522+
test.serial(`${currentTest} should return success when updating connection title`, async (t) => {
1523+
const { newConnection } = getTestData();
1524+
const { token } = await registerUserAndReturnUserInfo(app);
1525+
1526+
const createConnectionResponse = await request(app.getHttpServer())
1527+
.post('/connection')
1528+
.send(newConnection)
1529+
.set('Content-Type', 'application/json')
1530+
.set('Cookie', token)
1531+
.set('Accept', 'application/json');
1532+
1533+
const createConnectionRO = JSON.parse(createConnectionResponse.text);
1534+
1535+
const newTitle = 'New Connection Title';
1536+
const updateTitleResponse = await request(app.getHttpServer())
1537+
.put(`/connection/title/${createConnectionRO.id}`)
1538+
.send({ title: newTitle })
1539+
.set('Content-Type', 'application/json')
1540+
.set('Cookie', token)
1541+
.set('Accept', 'application/json');
1542+
1543+
t.is(updateTitleResponse.status, 200);
1544+
const result = updateTitleResponse.body;
1545+
t.is(result.success, true);
1546+
1547+
const getConnectionResponse = await request(app.getHttpServer())
1548+
.get(`/connection/one/${createConnectionRO.id}`)
1549+
.set('Cookie', token)
1550+
.set('Content-Type', 'application/json')
1551+
.set('Accept', 'application/json');
1552+
1553+
t.is(getConnectionResponse.status, 200);
1554+
const connectionResult = JSON.parse(getConnectionResponse.text);
1555+
t.is(connectionResult.connection.title, newTitle);
1556+
1557+
t.pass();
1558+
});
1559+
1560+
test.serial(`${currentTest} should throw error when title is empty`, async (t) => {
1561+
const { newConnection } = getTestData();
1562+
const { token } = await registerUserAndReturnUserInfo(app);
1563+
1564+
const createConnectionResponse = await request(app.getHttpServer())
1565+
.post('/connection')
1566+
.send(newConnection)
1567+
.set('Content-Type', 'application/json')
1568+
.set('Cookie', token)
1569+
.set('Accept', 'application/json');
1570+
1571+
const createConnectionRO = JSON.parse(createConnectionResponse.text);
1572+
1573+
const updateTitleResponse = await request(app.getHttpServer())
1574+
.put(`/connection/title/${createConnectionRO.id}`)
1575+
.send({ title: '' })
1576+
.set('Content-Type', 'application/json')
1577+
.set('Cookie', token)
1578+
.set('Accept', 'application/json');
1579+
1580+
t.is(updateTitleResponse.status, 400);
1581+
1582+
t.pass();
1583+
});
1584+
1585+
test.serial(`${currentTest} should throw error when title is not provided`, async (t) => {
1586+
const { newConnection } = getTestData();
1587+
const { token } = await registerUserAndReturnUserInfo(app);
1588+
1589+
const createConnectionResponse = await request(app.getHttpServer())
1590+
.post('/connection')
1591+
.send(newConnection)
1592+
.set('Content-Type', 'application/json')
1593+
.set('Cookie', token)
1594+
.set('Accept', 'application/json');
1595+
1596+
const createConnectionRO = JSON.parse(createConnectionResponse.text);
1597+
1598+
const updateTitleResponse = await request(app.getHttpServer())
1599+
.put(`/connection/title/${createConnectionRO.id}`)
1600+
.send({})
1601+
.set('Content-Type', 'application/json')
1602+
.set('Cookie', token)
1603+
.set('Accept', 'application/json');
1604+
1605+
t.is(updateTitleResponse.status, 400);
1606+
1607+
t.pass();
1608+
});
1609+
1610+
test.serial(
1611+
`${currentTest} should update title of encrypted connection and connection should still work`,
1612+
async (t) => {
1613+
const { newConnection } = getTestData();
1614+
const { token } = await registerUserAndReturnUserInfo(app);
1615+
newConnection.masterEncryption = true;
1616+
1617+
const createConnectionResponse = await request(app.getHttpServer())
1618+
.post('/connection')
1619+
.send(newConnection)
1620+
.set('masterpwd', 'ahalaimahalai')
1621+
.set('Content-Type', 'application/json')
1622+
.set('Cookie', token)
1623+
.set('Accept', 'application/json');
1624+
1625+
t.is(createConnectionResponse.status, 201);
1626+
const createConnectionRO = JSON.parse(createConnectionResponse.text);
1627+
1628+
const newTitle = 'Renamed Encrypted Connection';
1629+
const updateTitleResponse = await request(app.getHttpServer())
1630+
.put(`/connection/title/${createConnectionRO.id}`)
1631+
.send({ title: newTitle })
1632+
.set('Content-Type', 'application/json')
1633+
.set('Cookie', token)
1634+
.set('Accept', 'application/json');
1635+
1636+
t.is(updateTitleResponse.status, 200);
1637+
t.is(updateTitleResponse.body.success, true);
1638+
1639+
const findOneResponse = await request(app.getHttpServer())
1640+
.get(`/connection/one/${createConnectionRO.id}`)
1641+
.set('Content-Type', 'application/json')
1642+
.set('masterpwd', 'ahalaimahalai')
1643+
.set('Cookie', token)
1644+
.set('Accept', 'application/json');
1645+
1646+
t.is(findOneResponse.status, 200);
1647+
const connectionResult = findOneResponse.body.connection;
1648+
t.is(connectionResult.title, newTitle);
1649+
1650+
t.pass();
1651+
},
1652+
);

0 commit comments

Comments
 (0)