diff --git a/.github/workflows/lib-ms.yml b/.github/workflows/lib-ms.yml index ae61d3061..7c0dc0df6 100644 --- a/.github/workflows/lib-ms.yml +++ b/.github/workflows/lib-ms.yml @@ -36,14 +36,14 @@ jobs: npm install -g pm2 npm install -g jest - - name: Run the linting checks + - name: Run the linting checks and formatting run: | yarn install yarn syntax + yarn format - name: Build the lib microservice run: | - yarn install yarn build - name: Run all tests @@ -54,7 +54,6 @@ jobs: LOG_LEVEL: debug APOLLO_PATH: /lib run: | - yarn install yarn build yarn test:all @@ -66,7 +65,6 @@ jobs: LOG_LEVEL: debug APOLLO_PATH: /lib run: | - yarn install yarn build yarn start:pm2 diff --git a/servers/lib/src/enums/config-mode.enum.ts b/servers/lib/src/enums/config-mode.enum.ts new file mode 100644 index 000000000..c08b313b9 --- /dev/null +++ b/servers/lib/src/enums/config-mode.enum.ts @@ -0,0 +1,4 @@ +export enum CONFIG_MODE { + GIT = 'git', + LOCAL = 'local', +} diff --git a/servers/lib/src/files/files-service.factory.ts b/servers/lib/src/files/files-service.factory.ts new file mode 100644 index 000000000..30ac3d9fd --- /dev/null +++ b/servers/lib/src/files/files-service.factory.ts @@ -0,0 +1,19 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { IFilesService } from './interfaces/files.service.interface.js'; + +@Injectable() +export default class FilesServiceFactory { + static create( + configService: ConfigService, + fileServices: IFilesService[], + ): IFilesService { + const mode = configService.get('MODE'); + const service = fileServices.find((s) => s.getMode() == mode); + + if (service == undefined) { + throw new Error(`Invalid MODE: ${mode}`); + } + return service; + } +} diff --git a/servers/lib/src/files/files.module.ts b/servers/lib/src/files/files.module.ts index 3bae75e12..6ee9415e6 100644 --- a/servers/lib/src/files/files.module.ts +++ b/servers/lib/src/files/files.module.ts @@ -1,9 +1,29 @@ import { Module } from '@nestjs/common'; -import FilesResolver from './resolvers/files.resolver.js'; -import FilesServiceFactory from './services/files-service.factory.js'; -import LocalFilesService from './services/local-files.service.js'; +import FilesResolver from './files.resolver.js'; +import { GitFilesModule } from './git/git-files.module.js'; +import { LocalFilesModule } from './local/local-files.module.js'; +import LocalFilesService from './local/local-files.service.js'; +import GitFilesService from './git/git-files.service.js'; +import { FILE_SERVICE } from './interfaces/files.service.interface.js'; +import FilesServiceFactory from './files-service.factory.js'; +import { ConfigService } from '@nestjs/config'; @Module({ - providers: [FilesResolver, LocalFilesService, FilesServiceFactory], + imports: [LocalFilesModule, GitFilesModule], + providers: [ + FilesResolver, + { + provide: FILE_SERVICE, + useFactory: ( + configService: ConfigService, + localFilesService: LocalFilesService, + gitFilesService: GitFilesService, + ) => { + const fileServices = [localFilesService, gitFilesService]; + return FilesServiceFactory.create(configService, fileServices); + }, + inject: [ConfigService, LocalFilesService, GitFilesService], + }, + ], }) export default class FilesModule {} diff --git a/servers/lib/src/files/resolvers/files.resolver.ts b/servers/lib/src/files/files.resolver.ts similarity index 52% rename from servers/lib/src/files/resolvers/files.resolver.ts rename to servers/lib/src/files/files.resolver.ts index 347f73d05..1fc690f14 100644 --- a/servers/lib/src/files/resolvers/files.resolver.ts +++ b/servers/lib/src/files/files.resolver.ts @@ -1,15 +1,16 @@ import { Resolver, Query, Args } from '@nestjs/graphql'; -import { IFilesService } from '../interfaces/files.service.interface.js'; -import FilesServiceFactory from '../services/files-service.factory.js'; -import { Project } from '../../types.js'; +import { + FILE_SERVICE, + IFilesService, +} from './interfaces/files.service.interface.js'; +import { Project } from '../types.js'; +import { Inject } from '@nestjs/common'; @Resolver() export default class FilesResolver { - private readonly filesService: IFilesService; - - constructor(filesServiceFactory: FilesServiceFactory) { - this.filesService = filesServiceFactory.create(); - } + constructor( + @Inject(FILE_SERVICE) private readonly filesService: IFilesService, + ) {} @Query(() => Project) async listDirectory(@Args('path') path: string): Promise { diff --git a/servers/lib/src/files/git/git-files.module.ts b/servers/lib/src/files/git/git-files.module.ts new file mode 100644 index 000000000..8603cd987 --- /dev/null +++ b/servers/lib/src/files/git/git-files.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import GitFilesService from './git-files.service.js'; + +@Module({ + providers: [GitFilesService], + exports: [GitFilesService], +}) +export class GitFilesModule {} diff --git a/servers/lib/src/files/git/git-files.service.ts b/servers/lib/src/files/git/git-files.service.ts new file mode 100644 index 000000000..9c282b24c --- /dev/null +++ b/servers/lib/src/files/git/git-files.service.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@nestjs/common'; +import { Project } from 'src/types.js'; +import { IFilesService } from '../interfaces/files.service.interface.js'; +import { CONFIG_MODE } from '../../enums/config-mode.enum.js'; + +@Injectable() +export default class GitFilesService implements IFilesService { +// eslint-disable-next-line no-useless-constructor, no-empty-function + constructor() {} + getMode(): CONFIG_MODE { + return CONFIG_MODE.GIT; + } + + listDirectory(): Promise { + throw new Error('Method not implemented.'); + } + readFile(): Promise { + throw new Error('Method not implemented.'); + } +} diff --git a/servers/lib/src/files/interfaces/files.service.interface.ts b/servers/lib/src/files/interfaces/files.service.interface.ts index 45afece8d..0929d9002 100644 --- a/servers/lib/src/files/interfaces/files.service.interface.ts +++ b/servers/lib/src/files/interfaces/files.service.interface.ts @@ -1,7 +1,10 @@ +import { CONFIG_MODE } from '../../enums/config-mode.enum.js'; import { Project } from 'src/types.js'; +export const FILE_SERVICE = 'FILE_SERVICE'; // FileService interface export interface IFilesService { listDirectory(path: string): Promise; readFile(path: string): Promise; + getMode(): CONFIG_MODE; } diff --git a/servers/lib/src/files/local/local-files.module.ts b/servers/lib/src/files/local/local-files.module.ts new file mode 100644 index 000000000..6f45ae498 --- /dev/null +++ b/servers/lib/src/files/local/local-files.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import LocalFilesService from './local-files.service.js'; + +@Module({ + providers: [LocalFilesService], + exports: [LocalFilesService], +}) +export class LocalFilesModule {} diff --git a/servers/lib/src/files/services/local-files.service.ts b/servers/lib/src/files/local/local-files.service.ts similarity index 83% rename from servers/lib/src/files/services/local-files.service.ts rename to servers/lib/src/files/local/local-files.service.ts index dc84aacbb..4013732a5 100644 --- a/servers/lib/src/files/services/local-files.service.ts +++ b/servers/lib/src/files/local/local-files.service.ts @@ -4,16 +4,22 @@ import { join } from 'path'; import { ConfigService } from '@nestjs/config'; import { Project } from 'src/types.js'; import { IFilesService } from '../interfaces/files.service.interface.js'; +import { CONFIG_MODE } from '../../enums/config-mode.enum.js'; @Injectable() export default class LocalFilesService implements IFilesService { - // eslint-disable-next-line no-useless-constructor, no-empty-function - constructor(private configService: ConfigService) {} + private readonly dataPath: string; - async listDirectory(path: string): Promise { - const dataPath = this.configService.get('LOCAL_PATH'); - const fullPath = join(dataPath, path); + constructor(private configService: ConfigService) { + this.dataPath = this.configService.get('LOCAL_PATH'); + } + getMode(): CONFIG_MODE { + return CONFIG_MODE.LOCAL; + } + + async listDirectory(path: string): Promise { + const fullPath = join(this.dataPath, path); const files = await fs.promises.readdir(fullPath); const edges = await Promise.all( @@ -33,8 +39,7 @@ export default class LocalFilesService implements IFilesService { } async readFile(path: string): Promise { - const dataPath = this.configService.get('LOCAL_PATH'); - const fullPath = join(dataPath, path); + const fullPath = join(this.dataPath, path); try { const content = await ( diff --git a/servers/lib/src/files/services/files-service.factory.ts b/servers/lib/src/files/services/files-service.factory.ts deleted file mode 100644 index 7f06ffecf..000000000 --- a/servers/lib/src/files/services/files-service.factory.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Injectable, Inject } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import { IFilesService } from '../interfaces/files.service.interface.js'; -import LocalFilesService from './local-files.service.js'; - -@Injectable() -export default class FilesServiceFactory { - /* eslint-disable no-useless-constructor, no-empty-function */ - constructor( - private configService: ConfigService, - @Inject(LocalFilesService) private localFilesService: LocalFilesService, - ) {} - /* eslint-enable no-useless-constructor, no-empty-function */ - - create(): IFilesService { - const mode = this.configService.get('MODE'); - if (mode === 'local') { - return this.localFilesService; - } - throw new Error(`Invalid MODE: ${mode}`); - } -} diff --git a/servers/lib/test/e2e/app.e2e.spec.ts b/servers/lib/test/e2e/app.e2e.spec.ts index 0c7eb9a49..686755266 100644 --- a/servers/lib/test/e2e/app.e2e.spec.ts +++ b/servers/lib/test/e2e/app.e2e.spec.ts @@ -3,8 +3,13 @@ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import request from 'supertest'; import fetch from 'cross-fetch'; -import { ApolloClient, DocumentNode, InMemoryCache, gql} from "@apollo/client/core/core.cjs"; -import { HttpLink } from "@apollo/client/link/http/http.cjs"; +import { + ApolloClient, + DocumentNode, + InMemoryCache, + gql, +} from '@apollo/client/core/core.cjs'; +import { HttpLink } from '@apollo/client/link/http/http.cjs'; import AppModule from '../../src/app.module'; import { e2eReadFile, diff --git a/servers/lib/test/integration/files.service.integration.spec.ts b/servers/lib/test/integration/files.service.integration.spec.ts index ca1bd0f4d..92b323dfc 100644 --- a/servers/lib/test/integration/files.service.integration.spec.ts +++ b/servers/lib/test/integration/files.service.integration.spec.ts @@ -1,9 +1,8 @@ import { describe, it, expect, jest } from '@jest/globals'; import { Test, TestingModule } from '@nestjs/testing'; import { ConfigService } from '@nestjs/config'; -import FilesResolver from '../../src/files/resolvers/files.resolver'; -import FilesServiceFactory from '../../src/files/services/files-service.factory'; -import LocalFilesService from '../../src/files/services/local-files.service'; +import FilesResolver from '../../src/files/files.resolver'; +import LocalFilesService from '../../src/files/local/local-files.service'; import { pathToTestDirectory, pathToTestFileContent, @@ -11,6 +10,9 @@ import { testFileContent, MockConfigService, } from '../testUtil'; +import GitFilesService from '../../src/files/git/git-files.service'; +import { FILE_SERVICE } from '../../src/files/interfaces/files.service.interface'; +import FilesServiceFactory from '../../src/files/files-service.factory'; describe('Integration tests for FilesResolver', () => { let filesResolver: FilesResolver; @@ -21,8 +23,20 @@ describe('Integration tests for FilesResolver', () => { const module: TestingModule = await Test.createTestingModule({ providers: [ FilesResolver, - FilesServiceFactory, + { + provide: FILE_SERVICE, + useFactory: ( + configService: ConfigService, + localFilesService: LocalFilesService, + gitFilesService: GitFilesService, + ) => { + const fileServices = [localFilesService, gitFilesService]; + return FilesServiceFactory.create(configService, fileServices); + }, + inject: [ConfigService, LocalFilesService, GitFilesService], + }, LocalFilesService, + GitFilesService, { provide: ConfigService, useClass: MockConfigService }, ], }).compile(); diff --git a/servers/lib/test/unit/files-service.factory.unit.spec.ts b/servers/lib/test/unit/files-service.factory.unit.spec.ts index 4c1f62714..97ac478f3 100644 --- a/servers/lib/test/unit/files-service.factory.unit.spec.ts +++ b/servers/lib/test/unit/files-service.factory.unit.spec.ts @@ -2,37 +2,43 @@ import { describe, it, expect, jest } from '@jest/globals'; import { Test, TestingModule } from '@nestjs/testing'; import { ConfigService } from '@nestjs/config'; -import FilesServiceFactory from '../../src/files/services/files-service.factory'; -import LocalFilesService from '../../src/files/services/local-files.service'; +import FilesServiceFactory from '../../src/files/files-service.factory'; +import LocalFilesService from '../../src/files/local/local-files.service'; import { IFilesService } from '../../src/files/interfaces/files.service.interface'; +import GitFilesService from '../../src/files/git/git-files.service'; describe('FilesServiceFactory', () => { - let serviceFactory: FilesServiceFactory; let configService: ConfigService; let localFilesService: IFilesService; + let gitFilesService: IFilesService; + let fileServices: IFilesService[]; beforeEach(async () => { - localFilesService = new LocalFilesService(configService as ConfigService); - + const module: TestingModule = await Test.createTestingModule({ providers: [ - FilesServiceFactory, + GitFilesService, { provide: ConfigService, useValue: { get: jest.fn() } }, - { provide: LocalFilesService, useValue: localFilesService }, ], }).compile(); - - serviceFactory = module.get(FilesServiceFactory); + configService = module.get(ConfigService); + localFilesService = new LocalFilesService(configService); + gitFilesService = module.get(GitFilesService); + fileServices = [gitFilesService, localFilesService]; }); it('should create a local files service when MODE is local', () => { jest.spyOn(configService, 'get').mockReturnValue('local'); - expect(serviceFactory.create()).toBe(localFilesService); + expect(FilesServiceFactory.create(configService, fileServices)).toBe( + localFilesService, + ); }); it('should throw an error when MODE is invalid', () => { jest.spyOn(configService, 'get').mockReturnValue('invalid'); - expect(() => serviceFactory.create()).toThrow(`Invalid MODE: invalid`); + expect(() => + FilesServiceFactory.create(configService, fileServices), + ).toThrow(`Invalid MODE: invalid`); }); }); diff --git a/servers/lib/test/unit/files.resolver.unit.spec.ts b/servers/lib/test/unit/files.resolver.unit.spec.ts index 9927f5139..e3c4b9ed9 100644 --- a/servers/lib/test/unit/files.resolver.unit.spec.ts +++ b/servers/lib/test/unit/files.resolver.unit.spec.ts @@ -1,15 +1,18 @@ import { describe, it, expect, jest } from '@jest/globals'; import { Test, TestingModule } from '@nestjs/testing'; -import FilesResolver from '../../src/files/resolvers/files.resolver'; +import FilesResolver from '../../src/files/files.resolver'; import { testDirectory, pathToTestDirectory, pathToTestFileContent, testFileContent, } from '../testUtil'; -import { IFilesService } from '../../src/files/interfaces/files.service.interface'; -import FilesServiceFactory from '../../src/files/services/files-service.factory'; +import { + FILE_SERVICE, + IFilesService, +} from '../../src/files/interfaces/files.service.interface'; import { Project } from 'src/types'; +import { CONFIG_MODE } from '../../src/enums/config-mode.enum'; describe('Unit tests for FilesResolver', () => { let filesResolver: FilesResolver; @@ -17,27 +20,29 @@ describe('Unit tests for FilesResolver', () => { beforeEach(async () => { const mockFilesService: IFilesService = { - listDirectory: jest.fn<() => Promise>().mockResolvedValue(testDirectory), - readFile: jest.fn<() => Promise>().mockImplementation(() => Promise.resolve(testFileContent)), + listDirectory: jest + .fn<() => Promise>() + .mockResolvedValue(testDirectory), + readFile: jest + .fn<() => Promise>() + .mockImplementation(() => Promise.resolve(testFileContent)), + getMode: jest.fn<() => CONFIG_MODE>().mockReturnValue(CONFIG_MODE.LOCAL), }; const module: TestingModule = await Test.createTestingModule({ providers: [ FilesResolver, - FilesServiceFactory, { - provide: FilesServiceFactory, - useValue: { - create: () => mockFilesService, + provide: FILE_SERVICE, + useFactory: () => { + return mockFilesService; }, }, ], }).compile(); filesResolver = module.get(FilesResolver); - filesService = module - .get(FilesServiceFactory) - .create(); + filesService = module.get(FILE_SERVICE); }); it('should be defined', () => { diff --git a/servers/lib/test/unit/local-files.service.unit.spec.ts b/servers/lib/test/unit/local-files.service.unit.spec.ts index 9e6af36f3..2f4cd98f3 100644 --- a/servers/lib/test/unit/local-files.service.unit.spec.ts +++ b/servers/lib/test/unit/local-files.service.unit.spec.ts @@ -3,7 +3,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { ConfigService } from '@nestjs/config'; import * as fs from 'fs'; import { join } from 'path'; -import LocalFilesService from '../../src/files/services/local-files.service'; +import LocalFilesService from '../../src/files/local/local-files.service'; import { fstestFileContent, pathToTestDirectory, diff --git a/servers/lib/tsconfig.json b/servers/lib/tsconfig.json index 2c902192f..9c5ea12f7 100644 --- a/servers/lib/tsconfig.json +++ b/servers/lib/tsconfig.json @@ -11,9 +11,6 @@ "lib": [ "ES2022" ], - "paths": { - "@/*": ["src/*"] - }, "module": "ES2022", //use node module system "moduleResolution": "node", //use node module resolution strategy node "noImplicitReturns": true, //raise error on implicit returns