Skip to content

Commit e61ad2d

Browse files
author
arthosofteq
authored
Merge pull request #1783 from RedisInsight/be/feature/RI-4231-migrate_tutorials_json_to_arrays
#RI-4231 BE move to array-based structure
2 parents 09ef126 + 75adfa0 commit e61ad2d

File tree

9 files changed

+234
-35
lines changed

9 files changed

+234
-35
lines changed

redisinsight/api/config/default.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,14 +110,14 @@ export default {
110110
},
111111
guides: {
112112
updateUrl: process.env.GUIDES_UPDATE_URL
113-
|| 'https://github.com/RedisInsight/Guides/releases/download/release',
113+
|| 'https://github.com/RedisInsight/Guides/releases/download/2.x.x',
114114
zip: process.env.GUIDES_ZIP || dataZipFileName,
115115
buildInfo: process.env.GUIDES_CHECKSUM || buildInfoFileName,
116116
devMode: !!process.env.GUIDES_DEV_PATH,
117117
},
118118
tutorials: {
119119
updateUrl: process.env.TUTORIALS_UPDATE_URL
120-
|| 'https://github.com/RedisInsight/Tutorials/releases/download/release',
120+
|| 'https://github.com/RedisInsight/Tutorials/releases/download/2.x.x',
121121
zip: process.env.TUTORIALS_ZIP || dataZipFileName,
122122
buildInfo: process.env.TUTORIALS_CHECKSUM || buildInfoFileName,
123123
devMode: !!process.env.TUTORIALS_DEV_PATH,

redisinsight/api/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"format": "prettier --write \"src/**/*.ts\"",
2222
"lint": "eslint --ext .ts .",
2323
"start": "nest start",
24-
"start:dev": "cross-env NODE_ENV=development SERVER_STATIC_CONTENT=1 nest start --watch",
24+
"start:dev": "cross-env NODE_ENV=development SERVER_STATIC_CONTENT=1 nest start --watch --preserveWatchOutput",
2525
"start:debug": "nest start --debug --watch",
2626
"start:stage": "cross-env NODE_ENV=staging SERVER_STATIC_CONTENT=true node dist/src/main",
2727
"start:prod": "cross-env NODE_ENV=production node dist/src/main",

redisinsight/api/src/modules/custom-tutorial/custom-tutorial.controller.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { CreateClientCertificateDto } from 'src/modules/certificate/dto/create.c
1717
import { UseClientCertificateDto } from 'src/modules/certificate/dto/use.client-certificate.dto';
1818
import { CreateBasicSshOptionsDto } from 'src/modules/ssh/dto/create.basic-ssh-options.dto';
1919
import { CreateCertSshOptionsDto } from 'src/modules/ssh/dto/create.cert-ssh-options.dto';
20+
import { RootCustomTutorialManifest } from 'src/modules/custom-tutorial/models/custom-tutorial.manifest';
2021

2122
@ApiExtraModels(
2223
CreateCaCertificateDto, UseCaCertificateDto,
@@ -45,7 +46,7 @@ export class CustomTutorialController {
4546
})
4647
async create(
4748
@Body() dto: UploadCustomTutorialDto,
48-
): Promise<Record<string, any>> {
49+
): Promise<RootCustomTutorialManifest> {
4950
return this.service.create(dto);
5051
}
5152

@@ -59,7 +60,7 @@ export class CustomTutorialController {
5960
},
6061
],
6162
})
62-
async getGlobalManifest(): Promise<any> {
63+
async getGlobalManifest(): Promise<RootCustomTutorialManifest[]> {
6364
return this.service.getGlobalManifest();
6465
}
6566

redisinsight/api/src/modules/custom-tutorial/custom-tutorial.service.ts

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
BadRequestException,
23
Injectable, Logger, NotFoundException,
34
} from '@nestjs/common';
45
import { v4 as uuidv4 } from 'uuid';
@@ -13,7 +14,7 @@ import {
1314
} from 'src/modules/custom-tutorial/providers/custom-tutorial.manifest.provider';
1415
import {
1516
CustomTutorialManifestType,
16-
ICustomTutorialManifest,
17+
RootCustomTutorialManifest,
1718
} from 'src/modules/custom-tutorial/models/custom-tutorial.manifest';
1819
import { wrapHttpError } from 'src/common/utils';
1920

@@ -32,9 +33,17 @@ export class CustomTutorialService {
3233
* Currently from zip file only
3334
* @param dto
3435
*/
35-
public async create(dto: UploadCustomTutorialDto): Promise<Record<string, any>> {
36+
public async create(dto: UploadCustomTutorialDto): Promise<RootCustomTutorialManifest> {
3637
try {
37-
const tmpPath = await this.customTutorialFsProvider.unzipToTmpFolder(dto.file);
38+
let tmpPath = '';
39+
40+
if (dto.file) {
41+
tmpPath = await this.customTutorialFsProvider.unzipFromMemoryStoredFile(dto.file);
42+
} else if (dto.link) {
43+
tmpPath = await this.customTutorialFsProvider.unzipFromExternalLink(dto.link);
44+
} else {
45+
throw new BadRequestException('File or external link should be provided');
46+
}
3847

3948
// todo: validate
4049

@@ -59,8 +68,8 @@ export class CustomTutorialService {
5968
* Get global manifest for all custom tutorials
6069
* In the future will be removed with some kind of partial load
6170
*/
62-
public async getGlobalManifest(): Promise<Record<string, ICustomTutorialManifest>> {
63-
const children = {};
71+
public async getGlobalManifest(): Promise<RootCustomTutorialManifest[]> {
72+
const children = [];
6473

6574
try {
6675
const tutorials = await this.customTutorialRepository.list();
@@ -73,26 +82,26 @@ export class CustomTutorialService {
7382

7483
manifests.forEach((manifest) => {
7584
if (manifest) {
76-
children[manifest.id] = manifest;
85+
children.push(manifest);
7786
}
7887
});
7988
} catch (e) {
8089
this.logger.warn('Unable to generate entire custom tutorials manifest', e);
8190
}
8291

83-
return {
84-
'custom-tutorials': {
92+
return [
93+
{
8594
type: CustomTutorialManifestType.Group,
8695
id: 'custom-tutorials',
87-
label: 'My Tutorials',
96+
label: 'MY TUTORIALS',
8897
_actions: [CustomTutorialActions.CREATE],
8998
args: {
9099
withBorder: true,
91100
initialIsOpen: true,
92101
},
93102
children,
94103
},
95-
};
104+
];
96105
}
97106

98107
public async get(id: string): Promise<CustomTutorial> {

redisinsight/api/src/modules/custom-tutorial/dto/upload.custom-tutorial.dto.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,30 @@
1-
import { ApiProperty } from '@nestjs/swagger';
1+
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
22
import { Expose } from 'class-transformer';
3-
import { IsNotEmpty, IsString } from 'class-validator';
3+
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
44
import {
55
HasMimeType, IsFile, MaxFileSize, MemoryStoredFile,
66
} from 'nestjs-form-data';
77

88
export class UploadCustomTutorialDto {
9-
@ApiProperty({
9+
@ApiPropertyOptional({
1010
type: 'string',
1111
format: 'binary',
1212
description: 'ZIP archive with tutorial static files',
1313
})
14+
@IsOptional()
1415
@IsFile()
1516
@HasMimeType(['application/zip'])
1617
@MaxFileSize(10 * 1024 * 1024)
17-
file: MemoryStoredFile;
18+
file?: MemoryStoredFile;
19+
20+
@ApiPropertyOptional({
21+
type: 'string',
22+
description: 'External link to zip archive',
23+
})
24+
@IsOptional()
25+
@IsString()
26+
@IsNotEmpty()
27+
link?: string;
1828

1929
@ApiProperty({
2030
description: 'Name to show for custom tutorials',

redisinsight/api/src/modules/custom-tutorial/models/custom-tutorial.manifest.ts

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { CustomTutorialActions } from 'src/modules/custom-tutorial/models/custom-tutorial';
2-
import { ApiProperty } from '@nestjs/swagger';
3-
import { Expose } from 'class-transformer';
4-
import { IsEnum, IsNotEmpty } from 'class-validator';
2+
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
3+
import { Expose, Type } from 'class-transformer';
4+
import {
5+
IsArray, IsBoolean, IsEnum, IsNotEmpty, IsOptional, IsString, ValidateNested
6+
} from 'class-validator';
57

68
export enum CustomTutorialManifestType {
79
CodeButton = 'code-button',
@@ -19,6 +21,27 @@ export interface ICustomTutorialManifest {
1921
_path?: string,
2022
}
2123

24+
export class CustomTutorialManifestArgs {
25+
@ApiPropertyOptional({ type: Boolean })
26+
@IsOptional()
27+
@Expose()
28+
@IsString()
29+
@IsNotEmpty()
30+
path?: string;
31+
32+
@ApiPropertyOptional({ type: Boolean })
33+
@IsOptional()
34+
@Expose()
35+
@IsBoolean()
36+
initialIsOpen?: boolean;
37+
38+
@ApiPropertyOptional({ type: Boolean })
39+
@IsOptional()
40+
@Expose()
41+
@IsBoolean()
42+
withBorder?: boolean;
43+
}
44+
2245
export class CustomTutorialManifest {
2346
@ApiProperty({ type: String })
2447
@Expose()
@@ -35,5 +58,34 @@ export class CustomTutorialManifest {
3558
@IsNotEmpty()
3659
label: string;
3760

38-
children: Record<string, CustomTutorialManifest>
61+
@ApiPropertyOptional({ type: CustomTutorialManifestArgs })
62+
@IsOptional()
63+
@Expose()
64+
@ValidateNested()
65+
@Type(() => CustomTutorialManifestArgs)
66+
args?: CustomTutorialManifestArgs;
67+
68+
@ApiPropertyOptional({ type: CustomTutorialManifest })
69+
@IsOptional()
70+
@Expose()
71+
@ValidateNested({ each: true })
72+
@IsArray()
73+
@Type(() => CustomTutorialManifest)
74+
children?: CustomTutorialManifest[];
75+
}
76+
77+
export class RootCustomTutorialManifest extends CustomTutorialManifest {
78+
@ApiPropertyOptional({ enum: CustomTutorialActions })
79+
@IsOptional()
80+
@Expose()
81+
@IsArray()
82+
@IsEnum(CustomTutorialActions, { each: true })
83+
_actions?: CustomTutorialActions[];
84+
85+
@ApiPropertyOptional({ type: String })
86+
@IsOptional()
87+
@Expose()
88+
@IsString()
89+
@IsNotEmpty()
90+
_path?: string;
3991
}

redisinsight/api/src/modules/custom-tutorial/models/custom-tutorial.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const PATH_CONFIG = config.get('dir_path');
77
export enum CustomTutorialActions {
88
CREATE = 'create',
99
DELETE = 'delete',
10+
SYNC = 'sync',
1011
}
1112

1213
export class CustomTutorial {
@@ -26,7 +27,13 @@ export class CustomTutorial {
2627
createdAt: Date;
2728

2829
get actions(): CustomTutorialActions[] {
29-
return [CustomTutorialActions.DELETE];
30+
const actions = [CustomTutorialActions.DELETE];
31+
32+
if (this.link) {
33+
actions.push(CustomTutorialActions.SYNC);
34+
}
35+
36+
return actions;
3037
}
3138

3239
get path(): string {

redisinsight/api/src/modules/custom-tutorial/providers/custom-tutorial.fs.provider.ts

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { v4 as uuidv4 } from 'uuid';
55
import * as fs from 'fs-extra';
66
import config from 'src/utils/config';
77
import * as AdmZip from 'adm-zip';
8+
import axios from 'axios';
9+
import { wrapHttpError } from 'src/common/utils';
810

911
const PATH_CONFIG = config.get('dir_path');
1012

@@ -15,13 +17,13 @@ export class CustomTutorialFsProvider {
1517
private logger = new Logger('CustomTutorialFsProvider');
1618

1719
/**
18-
* Unzip custom tutorials to temporary folder
19-
* @param file
20+
* Unzip custom tutorials archive to temporary folder
21+
* @param zip
2022
*/
21-
public async unzipToTmpFolder(file: MemoryStoredFile): Promise<string> {
23+
public async unzipToTmpFolder(zip: AdmZip): Promise<string> {
2224
try {
2325
const path = await CustomTutorialFsProvider.prepareTmpFolder();
24-
const zip = new AdmZip(file.buffer);
26+
2527
await fs.remove(path);
2628
await zip.extractAllTo(path, true);
2729

@@ -32,6 +34,31 @@ export class CustomTutorialFsProvider {
3234
}
3335
}
3436

37+
/**
38+
* Unzip archive from multipart/form-data file input
39+
* @param file
40+
*/
41+
public async unzipFromMemoryStoredFile(file: MemoryStoredFile): Promise<string> {
42+
return this.unzipToTmpFolder(new AdmZip(file.buffer));
43+
}
44+
45+
/**
46+
* Download zip archive from external source and unzip it to temporary directory
47+
* @param link
48+
*/
49+
public async unzipFromExternalLink(link: string): Promise<string> {
50+
try {
51+
const { data } = await axios.get(link, {
52+
responseType: 'arraybuffer',
53+
});
54+
55+
return this.unzipToTmpFolder(new AdmZip(data));
56+
} catch (e) {
57+
this.logger.error('Unable fetch zip file from external source', e);
58+
throw wrapHttpError(e);
59+
}
60+
}
61+
3562
/**
3663
* Move custom tutorial from tmp folder to proper path to serve static files
3764
* force - default false, will remove existing folder

0 commit comments

Comments
 (0)