Skip to content

Commit

Permalink
feat: 🔥 [EXL-37] support go command
Browse files Browse the repository at this point in the history
support go command
  • Loading branch information
tal-rofe committed Feb 12, 2023
1 parent 1115353 commit 9661886
Show file tree
Hide file tree
Showing 22 changed files with 173 additions and 47 deletions.
Original file line number Diff line number Diff line change
@@ -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[];
Expand Down
32 changes: 32 additions & 0 deletions apps/cli-backend/src/modules/database/group.service.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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<IAddRecommendedResponseData> {
this.logger.log(`Will try to add recommeded group for languages: "${recommendedDto.languages}"`);

const createdGroupId = await this.queryBus.execute<AddRecommendedContract, string>(
new AddRecommendedContract(userId, recommendedDto.languages),
);

this.logger.log(`Successfully created a recommended group with an ID: "${createdGroupId}"`);

return { groupId: createdGroupId };
}
}
Original file line number Diff line number Diff line change
@@ -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({
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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 {}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const Routes = {
CONTROLLER: 'groups',
GET_GROUP: ':group_id',
RECOMMENDED: 'recommended',
ADD_RECOMMENDED: 'add-recommended',
};

export default Routes;
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -14,3 +14,7 @@ export type IGetGroupResponseData = (Pick<
> & { readonly rules: Pick<Rule, 'name' | 'configuration'>[] })[];

export type IRecommendedResponseData = IRecommendedPolicy[];

export interface IAddRecommendedResponseData {
readonly groupId: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -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'],
Expand All @@ -39,7 +39,7 @@ export const cssHtmlPolicies: IRecommendedResponseData = [
ignoredList: [],
},
{
library: 'prettier',
library: 'Prettier',
configuration: {
tabWidth: 4,
printWidth: 110,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 }],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand All @@ -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,
Expand All @@ -35,7 +35,7 @@ export const javascriptPolicies: IRecommendedResponseData = [
ignoredList: [],
},
{
library: 'eslint',
library: 'ESLint',
configuration: {
root: true,
env: { node: true },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 }],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand All @@ -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,
Expand All @@ -37,7 +37,7 @@ export const reactPolicies: IRecommendedResponseData = [
ignoredList: [],
},
{
library: 'eslint',
library: 'ESLint',
configuration: {
root: true,
env: { browser: true, es2021: true },
Expand Down Expand Up @@ -87,7 +87,7 @@ export const reactPolicies: IRecommendedResponseData = [
ignoredList: [],
},
{
library: 'stylelint',
library: 'Stylelint',
configuration: {
extends: ['stylelint-config-standard-scss', 'stylelint-config-prettier-scss'],
rules: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { Language } from '@/models/languages';

export class AddRecommendedContract {
constructor(public readonly userId: string, public readonly languages: Language[]) {}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Language } from '../../models/languages';
import type { Language } from '@/models/languages';

export class RecommendedContract {
constructor(public readonly languages: Language[]) {}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<AddRecommendedContract> {
constructor(private readonly dbGroupService: DBGroupService) {}

async execute(contract: AddRecommendedContract): Promise<string> {
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;
}
}
Original file line number Diff line number Diff line change
@@ -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];
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Record<IRecommendedPolicy['library'], IRecommendedPolicy[]>>(
(final, policy) => {
if (Array.isArray(final[policy.library])) {
return {
...final,
[policy.library]: [...final[policy.library]!, policy],
};
}

const policiesByLibraryMap = policies.reduce<
Partial<Record<IRecommendedPolicy['library'], IRecommendedPolicy[]>>
>((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,
Expand Down
2 changes: 1 addition & 1 deletion apps/frontend/.env.development
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
NODE_ENV="development"
VITE_BACKEND_URL="http://localhost:3000"
VITE__CLI_BACKEND_URL="http://localhost:4000"
VITE_CLI_BACKEND_URL="http://localhost:4000"
Loading

0 comments on commit 9661886

Please sign in to comment.