diff --git a/apps/cli-backend/src/modules/user/modules/groups/interfaces/recommended-policy.ts b/apps/cli-backend/src/interfaces/recommended-policy.ts similarity index 61% rename from apps/cli-backend/src/modules/user/modules/groups/interfaces/recommended-policy.ts rename to apps/cli-backend/src/interfaces/recommended-policy.ts index 10cb1b800..42d449215 100644 --- a/apps/cli-backend/src/modules/user/modules/groups/interfaces/recommended-policy.ts +++ b/apps/cli-backend/src/interfaces/recommended-policy.ts @@ -1,7 +1,7 @@ -import type { Prisma } from '@prisma/client'; +import type { PolicyLibrary, Prisma } from '@prisma/client'; export interface IRecommendedPolicy { - readonly library: string; + readonly library: PolicyLibrary; readonly configuration: Prisma.JsonValue; readonly lintedList: string[]; readonly ignoredList: string[]; diff --git a/apps/cli-backend/src/modules/user/modules/groups/models/languages.ts b/apps/cli-backend/src/models/languages.ts similarity index 100% rename from apps/cli-backend/src/modules/user/modules/groups/models/languages.ts rename to apps/cli-backend/src/models/languages.ts diff --git a/apps/cli-backend/src/modules/database/group.service.ts b/apps/cli-backend/src/modules/database/group.service.ts index 450369a56..49a04eeb9 100644 --- a/apps/cli-backend/src/modules/database/group.service.ts +++ b/apps/cli-backend/src/modules/database/group.service.ts @@ -1,4 +1,8 @@ import { Injectable } from '@nestjs/common'; +import { CodeType } from '@prisma/client'; + +import type { IRecommendedPolicy } from '@/interfaces/recommended-policy'; +import type { Language } from '@/models/languages'; import { PrismaService } from './prisma.service'; @@ -37,4 +41,32 @@ export class DBGroupService { }, }); } + + public async addRecommendedGroup(userId: string, policies: IRecommendedPolicy[], languages: Language[]) { + const languagesForDescription = languages.join(', '); + + const createdGroup = await this.prisma.group.create({ + data: { + userId, + label: 'Exlint Imported Group', + description: `This group was created using the "go" command for these languages: ${languagesForDescription}`, + }, + select: { id: true }, + }); + + await this.prisma.inlinePolicy.createMany({ + data: policies.map((policy) => ({ + groupId: createdGroup.id, + label: `${policy.library} Policy`, + description: 'This policy was created using the "go" command', + library: policy.library, + codeConfiguration: JSON.stringify(policy.configuration), + codeType: CodeType.JSON, + lintedList: policy.lintedList, + ignoredList: policy.ignoredList, + })), + }); + + return createdGroup.id; + } } diff --git a/apps/cli-backend/src/modules/user/modules/groups/add-recommended.controller.ts b/apps/cli-backend/src/modules/user/modules/groups/add-recommended.controller.ts new file mode 100644 index 000000000..98d104dd7 --- /dev/null +++ b/apps/cli-backend/src/modules/user/modules/groups/add-recommended.controller.ts @@ -0,0 +1,33 @@ +import { Body, Controller, HttpCode, HttpStatus, Logger, Post } from '@nestjs/common'; +import { QueryBus } from '@nestjs/cqrs'; + +import { CurrentUserId } from '@/decorators/current-user-id.decorator'; + +import Routes from './groups.routes'; +import type { IAddRecommendedResponseData } from './interfaces/responses'; +import { RecommendedDto } from './classes/create.dto'; +import { AddRecommendedContract } from './queries/contracts/add-recommended.contract'; + +@Controller(Routes.CONTROLLER) +export class AddRecommendedController { + private readonly logger = new Logger(AddRecommendedController.name); + + constructor(private readonly queryBus: QueryBus) {} + + @Post(Routes.ADD_RECOMMENDED) + @HttpCode(HttpStatus.OK) + public async addRecommended( + @Body() recommendedDto: RecommendedDto, + @CurrentUserId() userId: string, + ): Promise { + this.logger.log(`Will try to add recommeded group for languages: "${recommendedDto.languages}"`); + + const createdGroupId = await this.queryBus.execute( + new AddRecommendedContract(userId, recommendedDto.languages), + ); + + this.logger.log(`Successfully created a recommended group with an ID: "${createdGroupId}"`); + + return { groupId: createdGroupId }; + } +} diff --git a/apps/cli-backend/src/modules/user/modules/groups/classes/create.dto.ts b/apps/cli-backend/src/modules/user/modules/groups/classes/create.dto.ts index d548c7d25..2b7d455c9 100644 --- a/apps/cli-backend/src/modules/user/modules/groups/classes/create.dto.ts +++ b/apps/cli-backend/src/modules/user/modules/groups/classes/create.dto.ts @@ -1,7 +1,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsArray, IsEnum } from 'class-validator'; -import { Language } from '../models/languages'; +import { Language } from '@/models/languages'; export class RecommendedDto { @ApiProperty({ diff --git a/apps/cli-backend/src/modules/user/modules/groups/groups.module.ts b/apps/cli-backend/src/modules/user/modules/groups/groups.module.ts index 01f858a33..b3c4cf108 100644 --- a/apps/cli-backend/src/modules/user/modules/groups/groups.module.ts +++ b/apps/cli-backend/src/modules/user/modules/groups/groups.module.ts @@ -1,5 +1,6 @@ import { Module } from '@nestjs/common'; import { CqrsModule } from '@nestjs/cqrs'; +import { AddRecommendedController } from './add-recommended.controller'; import { GetGroupController } from './get-group.controller'; import { BelongingGroupGuard } from './guards/belonging-group.guard'; @@ -8,7 +9,7 @@ import { RecommendedController } from './recommended.controller'; @Module({ imports: [CqrsModule], - controllers: [GetGroupController, RecommendedController], + controllers: [GetGroupController, RecommendedController, AddRecommendedController], providers: [...QueryHandlers, BelongingGroupGuard], }) export class GroupsModule {} diff --git a/apps/cli-backend/src/modules/user/modules/groups/groups.routes.ts b/apps/cli-backend/src/modules/user/modules/groups/groups.routes.ts index bf42670ec..77edf05c3 100644 --- a/apps/cli-backend/src/modules/user/modules/groups/groups.routes.ts +++ b/apps/cli-backend/src/modules/user/modules/groups/groups.routes.ts @@ -2,6 +2,7 @@ const Routes = { CONTROLLER: 'groups', GET_GROUP: ':group_id', RECOMMENDED: 'recommended', + ADD_RECOMMENDED: 'add-recommended', }; export default Routes; diff --git a/apps/cli-backend/src/modules/user/modules/groups/interfaces/responses.ts b/apps/cli-backend/src/modules/user/modules/groups/interfaces/responses.ts index 6bed2ff45..1ddad5505 100644 --- a/apps/cli-backend/src/modules/user/modules/groups/interfaces/responses.ts +++ b/apps/cli-backend/src/modules/user/modules/groups/interfaces/responses.ts @@ -1,6 +1,6 @@ import type { InlinePolicy, Rule } from '@prisma/client'; -import type { IRecommendedPolicy } from './recommended-policy'; +import type { IRecommendedPolicy } from '../../../../../interfaces/recommended-policy'; export type IGetGroupResponseData = (Pick< InlinePolicy, @@ -14,3 +14,7 @@ export type IGetGroupResponseData = (Pick< > & { readonly rules: Pick[] })[]; export type IRecommendedResponseData = IRecommendedPolicy[]; + +export interface IAddRecommendedResponseData { + readonly groupId: string; +} diff --git a/apps/cli-backend/src/modules/user/modules/groups/models/csshtml-recommendation.ts b/apps/cli-backend/src/modules/user/modules/groups/models/csshtml-recommendation.ts index 6a3f2d457..add4dd5a5 100644 --- a/apps/cli-backend/src/modules/user/modules/groups/models/csshtml-recommendation.ts +++ b/apps/cli-backend/src/modules/user/modules/groups/models/csshtml-recommendation.ts @@ -2,7 +2,7 @@ import type { IRecommendedResponseData } from '../interfaces/responses'; export const cssHtmlPolicies: IRecommendedResponseData = [ { - library: 'stylelint', + library: 'Stylelint', configuration: { extends: ['stylelint-config-standard-scss', 'stylelint-config-prettier-scss'], rules: { @@ -22,13 +22,13 @@ export const cssHtmlPolicies: IRecommendedResponseData = [ ignoredList: [], }, { - library: 'depcheck', + library: 'Depcheck', configuration: {}, lintedList: [], ignoredList: [], }, { - library: 'inflint', + library: 'Inflint', configuration: { rules: { '**/*.html': [2, 'kebab-case'], @@ -39,7 +39,7 @@ export const cssHtmlPolicies: IRecommendedResponseData = [ ignoredList: [], }, { - library: 'prettier', + library: 'Prettier', configuration: { tabWidth: 4, printWidth: 110, diff --git a/apps/cli-backend/src/modules/user/modules/groups/models/golang-recommendation.ts b/apps/cli-backend/src/modules/user/modules/groups/models/golang-recommendation.ts index c4b3d64d8..a6c534a53 100644 --- a/apps/cli-backend/src/modules/user/modules/groups/models/golang-recommendation.ts +++ b/apps/cli-backend/src/modules/user/modules/groups/models/golang-recommendation.ts @@ -2,7 +2,7 @@ import type { IRecommendedResponseData } from '../interfaces/responses'; export const golangPolicies: IRecommendedResponseData = [ { - library: 'inflint', + library: 'Inflint', configuration: { rules: { '**/*.go': [2, 'snake_case', { dot: false }], diff --git a/apps/cli-backend/src/modules/user/modules/groups/models/javascript-recommendation.ts b/apps/cli-backend/src/modules/user/modules/groups/models/javascript-recommendation.ts index e714c0905..e6d5d800b 100644 --- a/apps/cli-backend/src/modules/user/modules/groups/models/javascript-recommendation.ts +++ b/apps/cli-backend/src/modules/user/modules/groups/models/javascript-recommendation.ts @@ -2,7 +2,7 @@ import type { IRecommendedResponseData } from '../interfaces/responses'; export const javascriptPolicies: IRecommendedResponseData = [ { - library: 'inflint', + library: 'Inflint', configuration: { rules: { '**/*.{cjs,mjs,js,cts,mts,ts}': [2, 'kebab-case.point'], @@ -12,13 +12,13 @@ export const javascriptPolicies: IRecommendedResponseData = [ ignoredList: [], }, { - library: 'depcheck', + library: 'Depcheck', configuration: {}, lintedList: [], ignoredList: [], }, { - library: 'prettier', + library: 'Prettier', configuration: { tabWidth: 4, printWidth: 110, @@ -35,7 +35,7 @@ export const javascriptPolicies: IRecommendedResponseData = [ ignoredList: [], }, { - library: 'eslint', + library: 'ESLint', configuration: { root: true, env: { node: true }, diff --git a/apps/cli-backend/src/modules/user/modules/groups/models/python-recommendation.ts b/apps/cli-backend/src/modules/user/modules/groups/models/python-recommendation.ts index 957faa63a..697cf19d0 100644 --- a/apps/cli-backend/src/modules/user/modules/groups/models/python-recommendation.ts +++ b/apps/cli-backend/src/modules/user/modules/groups/models/python-recommendation.ts @@ -2,7 +2,7 @@ import type { IRecommendedResponseData } from '../interfaces/responses'; export const pythonPolicies: IRecommendedResponseData = [ { - library: 'inflint', + library: 'Inflint', configuration: { rules: { '**/*.py': [2, 'snake_case', { dot: false }], diff --git a/apps/cli-backend/src/modules/user/modules/groups/models/react-recommendation.ts b/apps/cli-backend/src/modules/user/modules/groups/models/react-recommendation.ts index 2de78c061..3913a1b4e 100644 --- a/apps/cli-backend/src/modules/user/modules/groups/models/react-recommendation.ts +++ b/apps/cli-backend/src/modules/user/modules/groups/models/react-recommendation.ts @@ -2,7 +2,7 @@ import type { IRecommendedResponseData } from '../interfaces/responses'; export const reactPolicies: IRecommendedResponseData = [ { - library: 'inflint', + library: 'Inflint', configuration: { rules: { '**/*.{jsx,tsx}': [2, 'PascalCase.point'], @@ -14,13 +14,13 @@ export const reactPolicies: IRecommendedResponseData = [ ignoredList: [], }, { - library: 'depcheck', + library: 'Depcheck', configuration: {}, lintedList: [], ignoredList: [], }, { - library: 'prettier', + library: 'Prettier', configuration: { tabWidth: 4, printWidth: 110, @@ -37,7 +37,7 @@ export const reactPolicies: IRecommendedResponseData = [ ignoredList: [], }, { - library: 'eslint', + library: 'ESLint', configuration: { root: true, env: { browser: true, es2021: true }, @@ -87,7 +87,7 @@ export const reactPolicies: IRecommendedResponseData = [ ignoredList: [], }, { - library: 'stylelint', + library: 'Stylelint', configuration: { extends: ['stylelint-config-standard-scss', 'stylelint-config-prettier-scss'], rules: { diff --git a/apps/cli-backend/src/modules/user/modules/groups/queries/contracts/add-recommended.contract.ts b/apps/cli-backend/src/modules/user/modules/groups/queries/contracts/add-recommended.contract.ts new file mode 100644 index 000000000..fc78fbec9 --- /dev/null +++ b/apps/cli-backend/src/modules/user/modules/groups/queries/contracts/add-recommended.contract.ts @@ -0,0 +1,5 @@ +import type { Language } from '@/models/languages'; + +export class AddRecommendedContract { + constructor(public readonly userId: string, public readonly languages: Language[]) {} +} diff --git a/apps/cli-backend/src/modules/user/modules/groups/queries/contracts/recommended.contract.ts b/apps/cli-backend/src/modules/user/modules/groups/queries/contracts/recommended.contract.ts index d035819d3..839fb0d45 100644 --- a/apps/cli-backend/src/modules/user/modules/groups/queries/contracts/recommended.contract.ts +++ b/apps/cli-backend/src/modules/user/modules/groups/queries/contracts/recommended.contract.ts @@ -1,4 +1,4 @@ -import type { Language } from '../../models/languages'; +import type { Language } from '@/models/languages'; export class RecommendedContract { constructor(public readonly languages: Language[]) {} diff --git a/apps/cli-backend/src/modules/user/modules/groups/queries/handlers/add-recommended.handler.ts b/apps/cli-backend/src/modules/user/modules/groups/queries/handlers/add-recommended.handler.ts new file mode 100644 index 000000000..d267715f2 --- /dev/null +++ b/apps/cli-backend/src/modules/user/modules/groups/queries/handlers/add-recommended.handler.ts @@ -0,0 +1,52 @@ +import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs'; + +import { Language } from '@/models/languages'; +import type { IRecommendedPolicy } from '@/interfaces/recommended-policy'; +import { DBGroupService } from '@/modules/database/group.service'; + +import { cssHtmlPolicies } from '../../models/csshtml-recommendation'; +import { golangPolicies } from '../../models/golang-recommendation'; +import { javascriptPolicies } from '../../models/javascript-recommendation'; +import { pythonPolicies } from '../../models/python-recommendation'; +import { reactPolicies } from '../../models/react-recommendation'; +import { mergePolicies } from '../../utils/merge-policies'; +import { AddRecommendedContract } from '../contracts/add-recommended.contract'; + +@QueryHandler(AddRecommendedContract) +export class AddRecommendedHandler implements IQueryHandler { + constructor(private readonly dbGroupService: DBGroupService) {} + + async execute(contract: AddRecommendedContract): Promise { + const recommendedPolicies: IRecommendedPolicy[] = []; + + if (contract.languages.includes(Language.Golang)) { + recommendedPolicies.push(...golangPolicies); + } + + if (contract.languages.includes(Language.Python)) { + recommendedPolicies.push(...pythonPolicies); + } + + if (contract.languages.includes(Language['CSS & HTML'])) { + recommendedPolicies.push(...cssHtmlPolicies); + } + + if (contract.languages.includes(Language.JavaScript)) { + recommendedPolicies.push(...javascriptPolicies); + } + + if (contract.languages.includes(Language.React)) { + recommendedPolicies.push(...reactPolicies); + } + + const mergedRecommendedPolicies = await Promise.resolve(mergePolicies(recommendedPolicies)); + + const createdGroupId = await this.dbGroupService.addRecommendedGroup( + contract.userId, + mergedRecommendedPolicies, + contract.languages, + ); + + return createdGroupId; + } +} diff --git a/apps/cli-backend/src/modules/user/modules/groups/queries/handlers/index.ts b/apps/cli-backend/src/modules/user/modules/groups/queries/handlers/index.ts index 8a09789df..c9b16ac9a 100644 --- a/apps/cli-backend/src/modules/user/modules/groups/queries/handlers/index.ts +++ b/apps/cli-backend/src/modules/user/modules/groups/queries/handlers/index.ts @@ -1,4 +1,5 @@ +import { AddRecommendedHandler } from './add-recommended.handler'; import { GetGroupHandler } from './get-group.handler'; import { RecommendedHandler } from './recommended.handler'; -export const QueryHandlers = [GetGroupHandler, RecommendedHandler]; +export const QueryHandlers = [GetGroupHandler, RecommendedHandler, AddRecommendedHandler]; diff --git a/apps/cli-backend/src/modules/user/modules/groups/queries/handlers/recommended.handler.ts b/apps/cli-backend/src/modules/user/modules/groups/queries/handlers/recommended.handler.ts index c81215d85..66c4ba0b0 100644 --- a/apps/cli-backend/src/modules/user/modules/groups/queries/handlers/recommended.handler.ts +++ b/apps/cli-backend/src/modules/user/modules/groups/queries/handlers/recommended.handler.ts @@ -1,11 +1,12 @@ import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs'; -import type { IRecommendedPolicy } from '../../interfaces/recommended-policy'; +import type { IRecommendedPolicy } from '@/interfaces/recommended-policy'; +import { Language } from '@/models/languages'; + import type { IRecommendedResponseData } from '../../interfaces/responses'; import { cssHtmlPolicies } from '../../models/csshtml-recommendation'; import { golangPolicies } from '../../models/golang-recommendation'; import { javascriptPolicies } from '../../models/javascript-recommendation'; -import { Language } from '../../models/languages'; import { pythonPolicies } from '../../models/python-recommendation'; import { reactPolicies } from '../../models/react-recommendation'; import { mergePolicies } from '../../utils/merge-policies'; diff --git a/apps/cli-backend/src/modules/user/modules/groups/utils/merge-policies.ts b/apps/cli-backend/src/modules/user/modules/groups/utils/merge-policies.ts index fc025137f..b0a83e81b 100644 --- a/apps/cli-backend/src/modules/user/modules/groups/utils/merge-policies.ts +++ b/apps/cli-backend/src/modules/user/modules/groups/utils/merge-policies.ts @@ -1,24 +1,23 @@ import deepmerge from 'deepmerge'; -import type { IRecommendedPolicy } from '../interfaces/recommended-policy'; +import type { IRecommendedPolicy } from '../../../../../interfaces/recommended-policy'; export const mergePolicies = (policies: IRecommendedPolicy[]) => { - const policiesByLibraryMap = policies.reduce>( - (final, policy) => { - if (Array.isArray(final[policy.library])) { - return { - ...final, - [policy.library]: [...final[policy.library]!, policy], - }; - } - + const policiesByLibraryMap = policies.reduce< + Partial> + >((final, policy) => { + if (Array.isArray(final[policy.library])) { return { ...final, - [policy.library]: [policy], + [policy.library]: [...final[policy.library]!, policy], }; - }, - {}, - ); + } + + return { + ...final, + [policy.library]: [policy], + }; + }, {}); return Object.values(policiesByLibraryMap).map( (groupedPolicy) => deepmerge.all(groupedPolicy) as IRecommendedPolicy, diff --git a/apps/frontend/.env.development b/apps/frontend/.env.development index 25f6a0b5f..bc6a32dca 100644 --- a/apps/frontend/.env.development +++ b/apps/frontend/.env.development @@ -1,3 +1,3 @@ NODE_ENV="development" VITE_BACKEND_URL="http://localhost:3000" -VITE__CLI_BACKEND_URL="http://localhost:4000" \ No newline at end of file +VITE_CLI_BACKEND_URL="http://localhost:4000" diff --git a/apps/frontend/package.json b/apps/frontend/package.json index 6b6432e3d..eb6c337a9 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -33,8 +33,6 @@ "@rjsf/core": "5.0.1", "@rjsf/utils": "5.0.1", "@rjsf/validator-ajv8": "5.0.1", - "@types/react": "18.0.27", - "@types/react-dom": "18.0.10", "@uiw/codemirror-themes": "4.19.7", "@uiw/react-codemirror": "4.19.7", "axios": "1.3.1", @@ -49,8 +47,7 @@ "react-router-dom": "6.8.0", "react-scroll": "1.8.9", "react-switch": "7.0.0", - "react-textarea-autosize": "8.4.0", - "typescript": "4.9.5" + "react-textarea-autosize": "8.4.0" }, "devDependencies": { "@exlint.io/common": "workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 858dee3f1..027f8e31a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -357,8 +357,6 @@ importers: '@rjsf/core': 5.0.1_bbd27rmoc24shjzvkfkxmxml2a '@rjsf/utils': 5.0.1_react@18.2.0 '@rjsf/validator-ajv8': 5.0.1_@rjsf+utils@5.0.1 - '@types/react': 18.0.27 - '@types/react-dom': 18.0.10 '@uiw/codemirror-themes': 4.19.7_@codemirror+language@6.4.0 '@uiw/react-codemirror': 4.19.7_kjftqwr52m2dq7wkpq3exsmxhu axios: 1.3.1 @@ -374,11 +372,12 @@ importers: react-scroll: 1.8.9_biqbaboplfbrettd7655fr4n2y react-switch: 7.0.0_biqbaboplfbrettd7655fr4n2y react-textarea-autosize: 8.4.0_3stiutgnnbnfnf3uowm5cip22i - typescript: 4.9.5 devDependencies: '@exlint.io/common': link:../../packages/common '@types/node': 18.11.18 + '@types/react': 18.0.27 '@types/react-datepicker': 4.8.0_biqbaboplfbrettd7655fr4n2y + '@types/react-dom': 18.0.10 '@types/react-router-dom': 5.3.3 '@types/react-scroll': 1.8.6 '@typescript-eslint/eslint-plugin': 5.50.0_go4drrxstycfikanvu45pi4vgq @@ -403,6 +402,7 @@ importers: stylelint-config-recess-order: 3.1.0_stylelint@14.16.1 stylelint-config-standard-scss: 6.1.0_stylelint@14.16.1 stylelint-declaration-strict-value: 1.9.1_stylelint@14.16.1 + typescript: 4.9.5 vite: 4.0.4_f5vcjb3akvjdur4ffzogu2hewu vite-plugin-react-remove-attributes: 1.0.3_vite@4.0.4 vite-tsconfig-paths: 4.0.5_typescript@4.9.5 @@ -3597,7 +3597,6 @@ packages: resolution: {integrity: sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==} dependencies: '@types/react': 18.0.27 - dev: false /@types/react-router-dom/5.3.3: resolution: {integrity: sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==} @@ -11364,6 +11363,7 @@ packages: resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} engines: {node: '>=4.2.0'} hasBin: true + dev: true /uglify-js/3.17.1: resolution: {integrity: sha512-+juFBsLLw7AqMaqJ0GFvlsGZwdQfI2ooKQB39PSBgMnMakcFosi9O8jCwE+2/2nMNcc0z63r9mwjoDG8zr+q0Q==}