Skip to content

Commit cef594c

Browse files
irinaAbramova-devirinaP-local
authored andcommitted
#RI-4815 get a portion of the string [skip ci]
1 parent fa554d8 commit cef594c

File tree

6 files changed

+108
-18
lines changed

6 files changed

+108
-18
lines changed

redisinsight/api/src/modules/browser/constants/browser-tool-commands.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export enum BrowserToolKeysCommands {
1414
export enum BrowserToolStringCommands {
1515
Set = 'set',
1616
Get = 'get',
17+
Getrange = 'getrange',
1718
StrLen = 'strlen',
1819
}
1920

redisinsight/api/src/modules/browser/controllers/string/string.controller.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
Controller,
44
HttpCode,
55
Post,
6-
Put,
6+
Put, Res,
77
} from '@nestjs/common';
88
import {
99
ApiBody, ApiOkResponse, ApiOperation, ApiTags,
@@ -12,13 +12,15 @@ import { ApiRedisParams } from 'src/decorators/api-redis-params.decorator';
1212
import {
1313
SetStringDto,
1414
GetStringValueResponse,
15-
SetStringWithExpireDto,
15+
SetStringWithExpireDto, GetStringInfoDto,
1616
} from 'src/modules/browser/dto/string.dto';
1717
import { GetKeyInfoDto } from 'src/modules/browser/dto';
1818
import { BaseController } from 'src/modules/browser/controllers/base.controller';
1919
import { BrowserClientMetadata } from 'src/modules/browser/decorators/browser-client-metadata.decorator';
2020
import { ApiQueryRedisStringEncoding } from 'src/common/decorators';
2121
import { ClientMetadata } from 'src/common/models';
22+
import { ApiEndpoint } from 'src/decorators/api-endpoint.decorator';
23+
import { Response } from 'express';
2224
import { StringBusinessService } from '../../services/string-business/string-business.service';
2325

2426
@ApiTags('String')
@@ -45,19 +47,43 @@ export class StringController extends BaseController {
4547
@HttpCode(200)
4648
@ApiOperation({ description: 'Get string value' })
4749
@ApiRedisParams()
48-
@ApiBody({ type: GetKeyInfoDto })
50+
@ApiBody({ type: GetStringInfoDto })
4951
@ApiOkResponse({
5052
description: 'String value',
5153
type: GetStringValueResponse,
5254
})
5355
@ApiQueryRedisStringEncoding()
5456
async getStringValue(
5557
@BrowserClientMetadata() clientMetadata: ClientMetadata,
56-
@Body() dto: GetKeyInfoDto,
58+
@Body() dto: GetStringInfoDto,
5759
): Promise<GetStringValueResponse> {
5860
return this.stringBusinessService.getStringValue(clientMetadata, dto);
5961
}
6062

63+
@ApiEndpoint({
64+
description: 'Endpoint do download string value',
65+
statusCode: 200,
66+
})
67+
@Post('/download-value')
68+
@ApiRedisParams()
69+
@ApiBody({ type: GetKeyInfoDto })
70+
@ApiQueryRedisStringEncoding()
71+
async downloadStringFile(
72+
@Res() res: Response,
73+
@BrowserClientMetadata() clientMetadata: ClientMetadata,
74+
@Body() dto: GetKeyInfoDto,
75+
): Promise<void> {
76+
const { stream } = await this.stringBusinessService.downloadStringValue(clientMetadata, dto);
77+
78+
res.setHeader('Content-Type', 'application/octet-stream');
79+
res.setHeader('Content-Disposition', 'attachment;filename="string_value.txt"');
80+
res.setHeader('Access-Control-Expose-Headers', 'Content-Disposition');
81+
82+
stream
83+
.on('error', () => res.status(404).send())
84+
.pipe(res);
85+
}
86+
6187
@Put('')
6288
@ApiOperation({ description: 'Update string value' })
6389
@ApiRedisParams()

redisinsight/api/src/modules/browser/dto/string.dto.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,26 @@
11
import {
22
ApiProperty, IntersectionType,
33
} from '@nestjs/swagger';
4-
import { IsDefined } from 'class-validator';
4+
import {
5+
IsDefined, IsInt, IsOptional, Min,
6+
} from 'class-validator';
57
import { RedisString } from 'src/common/constants';
68
import { IsRedisString, RedisStringType } from 'src/common/decorators';
9+
import { Type } from 'class-transformer';
710
import { KeyDto, KeyResponse, KeyWithExpireDto } from './keys.dto';
811

12+
export class GetStringInfoDto extends KeyDto {
13+
@ApiProperty({
14+
description: 'Max length of string',
15+
type: Number,
16+
})
17+
@IsOptional()
18+
@IsInt({ always: true })
19+
@Type(() => Number)
20+
@Min(1)
21+
stringMaxLen?: number;
22+
}
23+
924
export class SetStringDto extends KeyDto {
1025
@ApiProperty({
1126
description: 'Key value',

redisinsight/api/src/modules/browser/services/string-business/string-business.service.ts

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { RECOMMENDATION_NAMES, RedisErrorCodes } from 'src/constants';
99
import ERROR_MESSAGES from 'src/constants/error-messages';
1010
import { catchAclError } from 'src/utils';
1111
import {
12+
GetStringInfoDto,
1213
GetStringValueResponse,
1314
SetStringDto,
1415
SetStringWithExpireDto,
@@ -22,6 +23,8 @@ import { plainToClass } from 'class-transformer';
2223
import { GetKeyInfoDto } from 'src/modules/browser/dto';
2324
import { ClientMetadata } from 'src/common/models';
2425
import { DatabaseRecommendationService } from 'src/modules/database-recommendation/database-recommendation.service';
26+
import { Readable } from 'stream';
27+
import { RedisString } from 'src/common/constants';
2528

2629
@Injectable()
2730
export class StringBusinessService {
@@ -66,20 +69,22 @@ export class StringBusinessService {
6669
this.logger.log('Succeed to set string key type.');
6770
}
6871

69-
public async getStringValue(
72+
private async getStringValueFromClient(
7073
clientMetadata: ClientMetadata,
71-
dto: GetKeyInfoDto,
74+
keyName: RedisString,
75+
args = [],
76+
toolCommand = BrowserToolStringCommands.Get,
7277
): Promise<GetStringValueResponse> {
7378
this.logger.log('Getting string value.');
7479

75-
const { keyName } = dto;
80+
const scanArgs = [keyName, ...args];
7681
let result: GetStringValueResponse;
7782

7883
try {
7984
const value = await this.browserTool.execCommand(
8085
clientMetadata,
81-
BrowserToolStringCommands.Get,
82-
[keyName],
86+
toolCommand,
87+
scanArgs,
8388
);
8489
result = { value, keyName };
8590
} catch (error) {
@@ -89,20 +94,51 @@ export class StringBusinessService {
8994
}
9095
catchAclError(error);
9196
}
97+
9298
if (result.value === null) {
9399
this.logger.error(
94100
`Failed to get string value. Not Found key: ${keyName}.`,
95101
);
96102
throw new NotFoundException();
97-
} else {
98-
this.recommendationService.check(
99-
clientMetadata,
100-
RECOMMENDATION_NAMES.STRING_TO_JSON,
101-
{ value: result.value, keyName: result.keyName },
102-
);
103-
this.logger.log('Succeed to get string value.');
104-
return plainToClass(GetStringValueResponse, result);
105103
}
104+
105+
this.recommendationService.check(
106+
clientMetadata,
107+
RECOMMENDATION_NAMES.STRING_TO_JSON,
108+
{ value: result.value, keyName: result.keyName },
109+
);
110+
this.logger.log('Succeed to get string value.');
111+
112+
return result;
113+
}
114+
115+
public async getStringValue(
116+
clientMetadata: ClientMetadata,
117+
dto: GetStringInfoDto,
118+
): Promise<GetStringValueResponse> {
119+
const { keyName, stringMaxLen } = dto;
120+
const result: GetStringValueResponse = await this.getStringValueFromClient(
121+
clientMetadata,
122+
keyName,
123+
stringMaxLen ? ['0', `${(stringMaxLen - 1)}`] : [],
124+
stringMaxLen ? BrowserToolStringCommands.Getrange : BrowserToolStringCommands.Get,
125+
);
126+
127+
return plainToClass(GetStringValueResponse, result);
128+
}
129+
130+
public async downloadStringValue(
131+
clientMetadata: ClientMetadata,
132+
dto: GetKeyInfoDto,
133+
): Promise<{ stream: Readable }> {
134+
const { keyName } = dto;
135+
const result = await this.getStringValueFromClient(
136+
clientMetadata,
137+
keyName,
138+
);
139+
140+
const stream = Readable.from(`"${result.value}"`);
141+
return { stream };
106142
}
107143

108144
public async updateStringValue(

redisinsight/api/test/api/string/POST-databases-id-string-get_value.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,17 @@ describe('POST /databases/:instanceId/string/get-value', () => {
4848
value: constants.TEST_STRING_VALUE_BIN_UTF8_1,
4949
},
5050
},
51+
{
52+
name: 'Should return part of value in utf8 (specified length)',
53+
data: {
54+
keyName: constants.TEST_STRING_KEY_BIN_BUF_OBJ_1,
55+
stringMaxLen: constants.TEST_STRING_KEY_LENGTH
56+
},
57+
responseBody: {
58+
keyName: constants.TEST_STRING_KEY_BIN_UTF8_1,
59+
value: constants.TEST_STRING_VALUE_BIN_UTF8_1.slice(0, constants.TEST_STRING_KEY_LENGTH),
60+
},
61+
},
5162
{
5263
name: 'Should return value in utf8',
5364
query: {

redisinsight/api/test/helpers/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ export const constants = {
160160
TEST_STRING_KEY_ASCII_UNICODE: TEST_RUN_ID + '_str_ascii_€' + CLUSTER_HASH_SLOT,
161161
TEST_STRING_KEY_ASCII_VALUE: TEST_RUN_ID + '_value_ascii',
162162
TEST_STRING_KEY_BIN_BUFFER_1: Buffer.concat([Buffer.from(TEST_RUN_ID), Buffer.from('strk'), unprintableBuf]),
163+
TEST_STRING_KEY_LENGTH: 10,
163164
get TEST_STRING_KEY_BIN_BUF_OBJ_1() { return { type: 'Buffer', data: [...this.TEST_STRING_KEY_BIN_BUFFER_1] } },
164165
get TEST_STRING_KEY_BIN_ASCII_1() { return getASCIISafeStringFromBuffer(this.TEST_STRING_KEY_BIN_BUFFER_1) },
165166
get TEST_STRING_KEY_BIN_UTF8_1() { return this.TEST_STRING_KEY_BIN_BUFFER_1.toString('utf-8') },

0 commit comments

Comments
 (0)