diff --git a/apps/backend/package.json b/apps/backend/package.json index 4c4b5955..ef48005d 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -22,6 +22,7 @@ "@nestjs/config": "3.2.2", "@nestjs/core": "10.3.8", "@nestjs/platform-express": "10.3.8", + "@prisma/client": "5.14.0", "reflect-metadata": "0.2.2", "rxjs": "7.8.1", "zod": "3.23.8" diff --git a/apps/backend/src/app.controller.ts b/apps/backend/src/app.controller.ts index 2ea27e9d..d72794d3 100644 --- a/apps/backend/src/app.controller.ts +++ b/apps/backend/src/app.controller.ts @@ -7,7 +7,7 @@ export class AppController { constructor(private readonly appService: AppService) {} @Get() - getHello(): string { + async getHello(): Promise { return this.appService.getHello(); } } diff --git a/apps/backend/src/app.module.ts b/apps/backend/src/app.module.ts index 89188e2d..3ee5c126 100644 --- a/apps/backend/src/app.module.ts +++ b/apps/backend/src/app.module.ts @@ -1,9 +1,10 @@ -import { Module } from '@nestjs/common'; -import { ConfigModule } from '@nestjs/config'; +import { Logger, Module } from '@nestjs/common'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { DomainModule } from '@snipcode/domain'; import { AppController } from './app.controller'; import { AppService } from './app.service'; -import appConfig, { validate } from './configs/environment'; +import appConfig, { EnvironmentVariables, validate } from './configs/environment'; @Module({ controllers: [AppController], @@ -14,7 +15,20 @@ import appConfig, { validate } from './configs/environment'; load: [appConfig], validate, }), + DomainModule.forRootAsync({ + inject: [ConfigService], + isGlobal: true, + useFactory: (configService: ConfigService) => { + return { + convertKit: { + apiKey: '', + formId: '', + }, + databaseUrl: configService.get('DATABASE_URL'), + }; + }, + }), ], - providers: [AppService], + providers: [Logger, AppService], }) export class AppModule {} diff --git a/apps/backend/src/app.service.ts b/apps/backend/src/app.service.ts index 927d7cca..dab61631 100644 --- a/apps/backend/src/app.service.ts +++ b/apps/backend/src/app.service.ts @@ -1,8 +1,23 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, Logger } from '@nestjs/common'; +import { RoleService, UserService } from '@snipcode/domain'; @Injectable() export class AppService { - getHello(): string { + constructor( + private readonly roleService: RoleService, + private readonly userService: UserService, + private logger: Logger, + ) {} + + async getHello(): Promise { + const users = await this.userService.findByEmail('teco@snipcode.dev'); + + this.logger.log(users); + + const roles = await this.roleService.findAll(); + + this.logger.log(roles); + return 'Hello World!'; } } diff --git a/apps/backend/src/configs/environment.ts b/apps/backend/src/configs/environment.ts index 1169c18b..17f4c9a1 100644 --- a/apps/backend/src/configs/environment.ts +++ b/apps/backend/src/configs/environment.ts @@ -2,12 +2,14 @@ import { registerAs } from '@nestjs/config'; import { z } from 'zod'; export default registerAs('app', () => ({ + databaseUrl: process.env.DATABASE_URL, env: process.env.NODE_ENV, host: process.env.HOST, port: parseInt(process.env.PORT ?? '7501', 10), })); const EnvironmentVariablesSchema = z.object({ + DATABASE_URL: z.string(), HOST: z.string(), NODE_ENV: z.union([z.literal('development'), z.literal('production'), z.literal('test')]), PORT: z.number({ coerce: true }).min(7000).max(8000), diff --git a/apps/backend/src/main.ts b/apps/backend/src/main.ts index d952ba8f..8254db4c 100644 --- a/apps/backend/src/main.ts +++ b/apps/backend/src/main.ts @@ -16,6 +16,13 @@ const bootstrap = async () => { await app.listen(port, () => { logger.log(`Server ready at ${host}:${port}`); + const object = { + age: 23, + birthDate: new Date(), + name: 'marion', + }; + + logger.log(object); // logger.log(`Server ready at ${host}:${port}${graphqlServer.graphqlPath}`); }); }; diff --git a/apps/backend/tsconfig.json b/apps/backend/tsconfig.json index 23148134..f5d102b7 100644 --- a/apps/backend/tsconfig.json +++ b/apps/backend/tsconfig.json @@ -20,7 +20,7 @@ "path": "../../packages/utils" }, { - "path": "../../packages/logger" + "path": "../../packages/logger-old" } ] } diff --git a/apps/core/README.md b/apps/core/README.md index 55de104b..7708f3a2 100644 --- a/apps/core/README.md +++ b/apps/core/README.md @@ -23,7 +23,7 @@ These packages are located in the folder `packages`, so you might need to change Here are the packages used in this project: * [@snipcode/domain](../../packages/domain) -* [@snipcode/logger](../../packages/logger) +* [@snipcode/logger](../../packages/logger-old) * [@snipcode/utils](../../packages/utils) ## Set up the project diff --git a/apps/core/tsconfig.json b/apps/core/tsconfig.json index 9d32a654..c17b5d99 100644 --- a/apps/core/tsconfig.json +++ b/apps/core/tsconfig.json @@ -18,7 +18,7 @@ "path": "../../packages/utils" }, { - "path": "../../packages/logger" + "path": "../../packages/logger-old" }, { "path": "../../packages/domain" diff --git a/package.json b/package.json index 8b350d18..66218a31 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,7 @@ "cache:login": "turbo login", "cache:link": "turbo link", "cache:disable": "turbo unlink", - "prerelease": "changeset", - "branch:create": "pscale branch create core-db $(git rev-parse --abbrev-ref HEAD)" + "prerelease": "changeset" }, "workspaces": [ "packages/domain", diff --git a/packages/domain/.eslintrc.js b/packages/domain/.eslintrc.js new file mode 100644 index 00000000..bb59aede --- /dev/null +++ b/packages/domain/.eslintrc.js @@ -0,0 +1,11 @@ +module.exports = { + root: true, + extends: '../../.eslintrc.json', + ignorePatterns: ['dist', '.eslintrc.js'], + parserOptions: { + ecmaVersion: 2023, + sourceType: 'module', + project: 'tsconfig.json', + tsconfigRootDir: __dirname, + }, +}; diff --git a/packages/domain/.eslintrc.json b/packages/domain/.eslintrc.json deleted file mode 100644 index dfdb1a26..00000000 --- a/packages/domain/.eslintrc.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "root": true, - "extends": "../../.eslintrc.json", - "ignorePatterns": [ - "jest.config.ts" - ], - "parserOptions": { - "ecmaVersion": 2023, - "sourceType": "module", - "project": "tsconfig.json" - } -} diff --git a/packages/domain/index.ts b/packages/domain/index.ts index f8c6ab5c..c95101a5 100644 --- a/packages/domain/index.ts +++ b/packages/domain/index.ts @@ -1,53 +1,14 @@ -import { Folder } from './src/entities/folder'; -import { Role, RoleName } from './src/entities/role'; -import { Session } from './src/entities/session'; -import { Snippet, SnippetVisibility } from './src/entities/snippet'; -import { OauthProvider, User } from './src/entities/user'; -import { CreateFolderDto } from './src/folders/dtos/create-folder-dto'; -import { CreateUserRootFolderDto } from './src/folders/dtos/create-user-root-folder-dto'; -import { UpdateFolderDto } from './src/folders/dtos/update-folder-dto'; -import { FolderService } from './src/folders/folder.service'; -import { NewsletterService } from './src/newsletters/newsletter.service'; -import { CreateRoleDto } from './src/roles/dtos/create-role-dto'; -import { RoleService } from './src/roles/role.service'; -import { CreateSessionDto } from './src/sessions/dtos/create-session-dto'; -import { SessionService } from './src/sessions/session.service'; -import { CreateSnippetDto } from './src/snippets/dtos/create-snippet-dto'; -import { DeleteSnippetDto } from './src/snippets/dtos/delete-snippet-dto'; -import { UpdateSnippetDto } from './src/snippets/dtos/update-snippet-dto'; -import { SnippetService } from './src/snippets/snippet.service'; -import { CreateUserDto } from './src/users/dtos/create-user-dto'; -import { UpdateUserDto } from './src/users/dtos/update-user-dto'; -import { UserService } from './src/users/user.service'; -import { dbID } from './src/utils/id'; - -export { dbID }; -export { PrismaClient, prisma as dbClient } from './src/utils/prisma'; -export type { Role, RoleName, User, OauthProvider, Folder, Session, Snippet, SnippetVisibility }; - -const roleService = new RoleService(); -const userService = new UserService(); -const folderService = new FolderService(); -const snippetService = new SnippetService(); -const sessionService = new SessionService(); - -export { - folderService, - roleService, - sessionService, - snippetService, - userService, - CreateRoleDto, - CreateSessionDto, - CreateUserDto, - DeleteSnippetDto, - UpdateUserDto, - CreateFolderDto, - CreateSnippetDto, - CreateUserRootFolderDto, - NewsletterService, - UpdateSnippetDto, - UpdateFolderDto, -}; - -export type { RoleService, UserService, FolderService, SessionService, SnippetService }; +export { DomainModule } from './src/domain.module'; +export { PrismaService } from './src/prisma.service'; +export { UserService } from './src/services/users/user.service'; +export { RoleService } from './src/services/roles/role.service'; +export { FolderService } from './src/services/folders/folder.service'; +export { SnippetService } from './src/services/snippets/snippet.service'; +export { SessionService } from './src/services/sessions/session.service'; +export { NewsletterService } from './src/services/newsletters/newsletter.service'; +export { Folder } from './src/services/folders/folder.entity'; +export { Role, RoleName } from './src/services/roles/role.entity'; +export { Session } from './src/services/sessions/session.entity'; +export { Snippet, SnippetVisibility } from './src/services/snippets/snippet.entity'; +export { OauthProvider, User } from './src/services/users/user.entity'; +export { dbID } from './src/utils/db-id'; diff --git a/packages/domain/jest.config.ts b/packages/domain/jest.config.ts index 27dc4ea5..3c1137a4 100644 --- a/packages/domain/jest.config.ts +++ b/packages/domain/jest.config.ts @@ -1,18 +1,12 @@ import type { Config } from '@jest/types'; const config: Config.InitialOptions = { - roots: ['.'], - preset: 'ts-jest', - testMatch: ['**/?(*.)+(spec|test).[jt]s'], - testEnvironment: 'node', clearMocks: true, - maxWorkers: 1, - snapshotFormat: { - printBasicPrototype: false, - }, - coverageDirectory: 'coverage', - collectCoverage: false, // When set to true, coverage is performed even if coverage flag isn't provided + collectCoverage: false, + // When set to true, coverage is performed even if coverage flag isn't provided collectCoverageFrom: ['src/**/*.ts', '!src/index.ts'], + coverageDirectory: 'coverage', + coverageReporters: ['json', 'lcov', 'text', 'text-summary'], coverageThreshold: { global: { branches: 40, @@ -21,7 +15,15 @@ const config: Config.InitialOptions = { statements: 60, }, }, - coverageReporters: ['json', 'lcov', 'text', 'text-summary'], + maxWorkers: 1, + preset: 'ts-jest', + roots: ['.'], + snapshotFormat: { + printBasicPrototype: false, + }, + testEnvironment: 'node', + testMatch: ['**/?(*.)+(spec|test).[jt]s'], + testPathIgnorePatterns: ['dist'], }; export default config; diff --git a/packages/domain/package.json b/packages/domain/package.json index 3501fcf7..bf5dcc6b 100644 --- a/packages/domain/package.json +++ b/packages/domain/package.json @@ -8,7 +8,7 @@ "license": "MIT", "scripts": { "build": "tsc --project tsconfig.prod.json", - "clean": "rm -rf .turbo dist", + "clean": "rm -rf .turbo dist coverage", "lint": "eslint --fix", "db:dev:create": "pscale branch create core-db $(git rev-parse --abbrev-ref HEAD)", "db:dev:connect": "pscale connect core-db $(git rev-parse --abbrev-ref HEAD) --port 3311", @@ -29,11 +29,13 @@ "@bugsnag/cuid": "3.1.1", "@prisma/client": "5.14.0", "@snipcode/utils": "workspace:*", - "axios": "1.6.8", + "axios": "1.7.2", "bcryptjs": "2.4.3", "unique-username-generator": "1.3.0" }, "devDependencies": { + "@nestjs/common": "10.3.8", + "@nestjs/testing": "10.3.8", "@ngneat/falso": "7.2.0", "@types/bcryptjs": "2.4.6", "nock": "13.5.4", diff --git a/packages/domain/src/constants.ts b/packages/domain/src/constants.ts new file mode 100644 index 00000000..dbe41f87 --- /dev/null +++ b/packages/domain/src/constants.ts @@ -0,0 +1 @@ +export const DOMAIN_SERVICES_OPTIONS = 'DOMAIN_SERVICES_OPTIONS'; diff --git a/packages/domain/src/domain.module.ts b/packages/domain/src/domain.module.ts new file mode 100644 index 00000000..72add662 --- /dev/null +++ b/packages/domain/src/domain.module.ts @@ -0,0 +1,105 @@ +import { DynamicModule, Module, Provider } from '@nestjs/common'; + +import { DOMAIN_SERVICES_OPTIONS } from './constants'; +import { PrismaService } from './prisma.service'; +import { FolderService } from './services/folders/folder.service'; +import { NewsletterService } from './services/newsletters/newsletter.service'; +import { RoleService } from './services/roles/role.service'; +import { SessionService } from './services/sessions/session.service'; +import { SnippetService } from './services/snippets/snippet.service'; +import { UserService } from './services/users/user.service'; + +type DomainModuleServicesOptions = { + convertKit: { + apiKey: string; + formId: string; + }; + databaseUrl: string; +}; + +type DomainModuleConfig = { + inject?: any[]; + isGlobal?: boolean; + useFactory?: (...args: any[]) => Promise | DomainModuleServicesOptions; +} & Partial; + +@Module({ + exports: [PrismaService, UserService, RoleService, FolderService, SnippetService, SessionService, NewsletterService], + providers: [ + PrismaService, + UserService, + RoleService, + FolderService, + SnippetService, + SessionService, + NewsletterService, + ], +}) +export class DomainModule { + static forRootAsync(config: DomainModuleConfig = { isGlobal: false }): DynamicModule { + const asyncProviders = this.createAsyncProviders(config); + + const prismaService = { + inject: [DOMAIN_SERVICES_OPTIONS], + provide: PrismaService, + useFactory: (options: DomainModuleServicesOptions) => { + return new PrismaService({ + datasources: { + db: { + url: options.databaseUrl, + }, + }, + log: [], + }); + }, + }; + + const newsletterService = { + inject: [DOMAIN_SERVICES_OPTIONS], + provide: NewsletterService, + useFactory: (domainConfig: DomainModuleConfig) => { + const { convertKit } = domainConfig; + + if (!convertKit) { + // throw new Error('Parameters are required: apiKey and formId'); + return null; + } + + return new NewsletterService({ + apiKey: convertKit.apiKey, + formId: convertKit.formId, + }); + }, + }; + + return { + exports: [...asyncProviders, PrismaService, NewsletterService], + global: config.isGlobal, + module: DomainModule, + providers: [...asyncProviders, prismaService, newsletterService], + }; + } + + private static createAsyncProviders(config: DomainModuleConfig): Provider[] { + if (config.useFactory) { + return [ + { + inject: config.inject ?? [], + provide: DOMAIN_SERVICES_OPTIONS, + useFactory: config.useFactory, + }, + ]; + } + + if (config.databaseUrl ?? config.convertKit) { + return [ + { + provide: DOMAIN_SERVICES_OPTIONS, + useValue: config, + }, + ]; + } + + return []; + } +} diff --git a/packages/domain/src/folders/folder.service.test.ts b/packages/domain/src/folders/folder.service.test.ts deleted file mode 100644 index fee5a665..00000000 --- a/packages/domain/src/folders/folder.service.test.ts +++ /dev/null @@ -1,426 +0,0 @@ -import SnipcodeError, { errors, generateRandomId } from '@snipcode/utils'; - -import { CreateFolderDto } from './dtos/create-folder-dto'; -import { CreateUserRootFolderDto } from './dtos/create-user-root-folder-dto'; -import { FolderService } from './folder.service'; -import { - createManyTestFolders, - createTestFolderDto, - createTestUser, - createUserWithRootFolder, - deleteTestFoldersById, - deleteTestUsersById, - updateTestFolderDto, -} from '../../tests/helpers'; -import { Folder } from '../entities/folder'; -import { RoleService } from '../roles/role.service'; - -const roleService = new RoleService(); -const folderService = new FolderService(); - -describe('Test Folder service', () => { - beforeAll(async () => { - await roleService.loadRoles(); - }); - - it('should create a root folder for a user', async () => { - // GIVEN - const user = await createTestUser({}); - - const creatUserRootFolderDto = new CreateUserRootFolderDto(user.id); - - // WHEN - const expectedFolder = await folderService.createUserRootFolder(creatUserRootFolderDto); - - // THEN - expect(expectedFolder?.id).toEqual(creatUserRootFolderDto.toFolder().id); - - await deleteTestFoldersById([expectedFolder?.id]); - await deleteTestUsersById([user.id]); - }); - - it('should create folder for the specified user', async () => { - // GIVEN - const [user, rootFolder] = await createUserWithRootFolder(); - - const createFolderDto = new CreateFolderDto({ - name: 'my gist', - parentId: rootFolder.id, - userId: user.id, - }); - - // WHEN - const expectedFolder = await folderService.create(createFolderDto); - - // THEN - expect(expectedFolder).toMatchObject({ - id: createFolderDto.toFolder().id, - isFavorite: false, - name: createFolderDto.name, - parentId: rootFolder.id, - userId: user.id, - }); - - await deleteTestFoldersById([expectedFolder.id]); - await deleteTestFoldersById([rootFolder.id]); - await deleteTestUsersById([user.id]); - }); - - it('should not create the folder cause it already exists', async () => { - // GIVEN - const [user, rootFolder] = await createUserWithRootFolder(); - - const [firstFolder, secondFolder] = await createManyTestFolders({ - folderNames: ['My gist', 'Blogs'], - parentId: rootFolder.id, - userId: user.id, - }); - - const createFolderDto = new CreateFolderDto({ - name: secondFolder.name, - parentId: rootFolder.id, - userId: user.id, - }); - - // WHEN - // THEN - await expect(() => folderService.create(createFolderDto)).rejects.toThrow( - new SnipcodeError(errors.FOLDER_ALREADY_EXIST(createFolderDto.name), 'FOLDER_ALREADY_EXIST'), - ); - - await deleteTestFoldersById([firstFolder.id, secondFolder.id, rootFolder.id]); - await deleteTestUsersById([user.id]); - }); - - it("should find the user's root folder", async () => { - // GIVEN - const [user, rootFolder] = await createUserWithRootFolder(); - - const [firstFolder, secondFolder] = await createManyTestFolders({ - folderNames: ['My gist', 'Blogs'], - parentId: rootFolder.id, - userId: user.id, - }); - - // WHEN - const userRootFolder = await folderService.findUserRootFolder(user.id); - - // THEN - expect(userRootFolder?.name).toEqual(`__${user.id}__`); - - await deleteTestFoldersById([firstFolder.id, secondFolder.id, rootFolder.id]); - await deleteTestUsersById([user.id]); - }); - - it("should not find the user's root folder", async () => { - // GIVEN - const user = await createTestUser({}); - - // WHEN - // THEN - await expect(() => folderService.findUserRootFolder(user.id)).rejects.toThrow( - new SnipcodeError(errors.USER_ROOT_FOLDER_NOT_FOUND(user.id), 'USER_ROOT_FOLDER_NOT_FOUND'), - ); - - await deleteTestUsersById([user.id]); - }); - - it('should find sub folders of the root user folder', async () => { - // GIVEN - const [user, rootFolder] = await createUserWithRootFolder(); - - const [gistFolder, blogsFolder] = await createManyTestFolders({ - folderNames: ['My gist', 'Blogs'], - parentId: rootFolder.id, - userId: user.id, - }); - - const [post1Folder, post2folder] = await createManyTestFolders({ - folderNames: ['post1', 'post2'], - parentId: blogsFolder.id, - userId: user.id, - }); - - // WHEN - const userRootFolders1 = await folderService.findSubFolders(user.id); - const userRootFolders2 = await folderService.findSubFolders(user.id, rootFolder.id); - - // THEN - expect(userRootFolders1).toHaveLength(2); - expect(userRootFolders1).toEqual(userRootFolders2); - - await deleteTestFoldersById([post1Folder.id, post2folder.id, gistFolder.id, blogsFolder.id, rootFolder.id]); - await deleteTestUsersById([user.id]); - }); - - it('should find the sub folders of a folder', async () => { - // GIVEN - const [user, rootFolder] = await createUserWithRootFolder(); - - const [firstFolder, secondFolder] = await createManyTestFolders({ - folderNames: ['My gist', 'Blogs'], - parentId: rootFolder.id, - userId: user.id, - }); - - const [javaFolder, nodeFolder] = await createManyTestFolders({ - folderNames: ['java', 'node.js'], - parentId: firstFolder.id, - userId: user.id, - }); - - const [post1Folder, post2folder] = await createManyTestFolders({ - folderNames: ['post1', 'post2'], - parentId: secondFolder.id, - userId: user.id, - }); - - // WHEN - const subFolders = await folderService.findSubFolders(user.id, firstFolder.id); - - // THEN - expect(subFolders).toHaveLength(2); - expect(subFolders.map((folder) => folder.name)).toEqual(['java', 'node.js']); - - await deleteTestFoldersById([ - javaFolder.id, - nodeFolder.id, - post1Folder.id, - post2folder.id, - firstFolder.id, - secondFolder.id, - rootFolder.id, - ]); - await deleteTestUsersById([user.id]); - }); - - it('should delete folders belonging to the user', async () => { - // GIVEN - const [user1, rootFolder1] = await createUserWithRootFolder(); - const [myGistFolder, blogsFolder] = await createManyTestFolders({ - folderNames: ['My gist', 'Blogs'], - parentId: rootFolder1.id, - userId: user1.id, - }); - - const [user2, rootFolder2] = await createUserWithRootFolder(); - const [projectFolder, snippetFolder] = await createManyTestFolders({ - folderNames: ['project', 'snippet'], - parentId: rootFolder2.id, - userId: user2.id, - }); - - // WHEN - await folderService.deleteMany([myGistFolder.id, blogsFolder.id], user1.id); - const subFolders = await folderService.findSubFolders(user1.id); - - // THEN - expect(subFolders).toHaveLength(0); - - await deleteTestFoldersById([projectFolder.id, snippetFolder.id, rootFolder1.id, rootFolder2.id]); - await deleteTestUsersById([user1.id]); - await deleteTestUsersById([user2.id]); - }); - - it('should delete folders belonging to the user - validation check', async () => { - // GIVEN - const [user1, rootFolder1] = await createUserWithRootFolder(); - const [myGistFolder, blogsFolder] = await createManyTestFolders({ - folderNames: ['My gist', 'Blogs'], - parentId: rootFolder1.id, - userId: user1.id, - }); - - const [user2, rootFolder2] = await createUserWithRootFolder(); - const [projectFolder, snippetFolder] = await createManyTestFolders({ - folderNames: ['project', 'snippet'], - parentId: rootFolder2.id, - userId: user2.id, - }); - - // WHEN - await folderService.deleteMany([myGistFolder.id, projectFolder.id], user1.id); - - // THEN - const user1SubFolders = await folderService.findSubFolders(user1.id); - const user2SubFolders = await folderService.findSubFolders(user2.id); - - expect(user1SubFolders).toHaveLength(1); - expect(user1SubFolders.map((folder) => folder.name)).toEqual(['Blogs']); - - expect(user2SubFolders).toHaveLength(2); - expect(user2SubFolders.map((folder) => folder.name)).toEqual(['project', 'snippet']); - - await deleteTestFoldersById([snippetFolder.id, projectFolder.id, blogsFolder.id, rootFolder1.id, rootFolder2.id]); - await deleteTestUsersById([user1.id]); - await deleteTestUsersById([user2.id]); - }); - - it('should not delete folders because we cannot delete a root folder', async () => { - // GIVEN - const [user, rootFolder] = await createUserWithRootFolder(); - const [myGistFolder] = await createManyTestFolders({ - folderNames: ['My gist'], - parentId: rootFolder.id, - userId: user.id, - }); - - // WHEN - // THEN - await expect(async () => { - await folderService.deleteMany([myGistFolder.id, rootFolder.id], user.id); - }).rejects.toThrow(new SnipcodeError(errors.CANT_DELETE_ROOT_FOLDER, 'CANT_DELETE_ROOT_FOLDER')); - - await deleteTestFoldersById([myGistFolder.id, rootFolder.id]); - await deleteTestUsersById([user.id]); - }); - - it('should generate the breadcrumb path of a folder', async () => { - // GIVEN - const [user, rootFolder] = await createUserWithRootFolder(); - - const gistFolderDto = createTestFolderDto({ - name: 'My gist', - parentId: rootFolder.id, - userId: user.id, - }); - const gistFolder = await folderService.create(gistFolderDto); - - const nodeFolderDto = createTestFolderDto({ - name: 'Node.js', - parentId: gistFolder.id, - userId: user.id, - }); - const nodeFolder = await folderService.create(nodeFolderDto); - - // WHEN - const subFolders = await folderService.generateBreadcrumb(nodeFolder.id); - - // THEN - expect(subFolders).toHaveLength(2); - expect(subFolders.map((folder) => folder.name)).toEqual(['My gist', 'Node.js']); - - await deleteTestFoldersById([nodeFolder.id, gistFolder.id, rootFolder.id]); - await deleteTestUsersById([user.id]); - }); - - it('should generate the breadcrumb path of the root folder', async () => { - // GIVEN - const [user, rootFolder] = await createUserWithRootFolder(); - - // WHEN - const subFolders = await folderService.generateBreadcrumb(rootFolder.id); - - // THEN - expect(subFolders).toHaveLength(0); - - await deleteTestFoldersById([rootFolder.id]); - await deleteTestUsersById([user.id]); - }); - - it('should found no folder given the ID provided', async () => { - // GIVEN - const folderId = generateRandomId(); - - // WHEN - // THEN - await expect(async () => { - await folderService.findById(folderId); - }).rejects.toThrow(new SnipcodeError(errors.FOLDER_NOT_FOUND(folderId), 'FOLDER_NOT_FOUND')); - }); - - it('should update an existing folder in the specified folder', async () => { - // GIVEN - const [user, rootFolder] = await createUserWithRootFolder(); - const [folder] = await createManyTestFolders({ - folderNames: ['My gist'], - parentId: rootFolder.id, - userId: user.id, - }); - - const updateFolderDto = updateTestFolderDto({ folderId: folder.id, name: 'The Gist', userId: user.id }); - - // WHEN - const updatedFolder = await folderService.update(updateFolderDto); - - // THEN - const folderToUpdate = updateFolderDto.toFolder(folder); - - expect(updatedFolder).toMatchObject({ - createdAt: expect.any(Date), - id: folder.id, - isFavorite: false, - name: folderToUpdate.name, - parentId: rootFolder.id, - path: folder.path, - updatedAt: expect.any(Date), - userId: user.id, - }); - - await deleteTestFoldersById([updatedFolder.id, rootFolder.id]); - await deleteTestUsersById([user.id]); - }); - - it('should not update an existing folder in the specified folder because another folder with the updated name already exists in the folder', async () => { - // GIVEN - const [user, rootFolder] = await createUserWithRootFolder(); - const [folder1, folder2] = await createManyTestFolders({ - folderNames: ['folder-one', 'folder-two'], - parentId: rootFolder.id, - userId: user.id, - }); - - const updateFolderDto = updateTestFolderDto({ folderId: folder1.id, name: 'folder-two', userId: user.id }); - - // WHEN - // THEN - await expect(async () => { - await folderService.update(updateFolderDto); - }).rejects.toThrow(new SnipcodeError(errors.FOLDER_ALREADY_EXIST(updateFolderDto.name), 'FOLDER_ALREADY_EXIST')); - - await deleteTestFoldersById([folder1.id, folder2.id]); - await deleteTestFoldersById([rootFolder.id]); - await deleteTestUsersById([user.id]); - }); - - it('should not update an existing folder in the specified folder because it belongs to other user', async () => { - // GIVEN - const [user1, rootFolder1] = await createUserWithRootFolder(); - const [user2, rootFolder2] = await createUserWithRootFolder(); - const [folderUser2] = await createManyTestFolders({ - folderNames: ['folder-user-two'], - parentId: rootFolder2.id, - userId: user2.id, - }); - - const updateFolderDto = updateTestFolderDto({ folderId: folderUser2.id, userId: user1.id }); - - // WHEN - // THEN - await expect(async () => { - await folderService.update(updateFolderDto); - }).rejects.toThrow( - new SnipcodeError(errors.CANT_EDIT_FOLDER(updateFolderDto.creatorId, folderUser2.id), 'CANT_EDIT_FOLDER'), - ); - - await deleteTestFoldersById([folderUser2.id]); - await deleteTestFoldersById([rootFolder1.id, rootFolder2.id]); - await deleteTestUsersById([user1.id, user2.id]); - }); - - it('should not update the user root folder', async () => { - // GIVEN - const [user1, rootFolder] = await createUserWithRootFolder(); - - const updateFolderDto = updateTestFolderDto({ folderId: rootFolder.id, name: 'new-root-folder', userId: user1.id }); - - // WHEN - // THEN - await expect(async () => { - await folderService.update(updateFolderDto); - }).rejects.toThrow(new SnipcodeError(errors.CANT_RENAME_ROOT_FOLDER, 'CANT_RENAME_ROOT_FOLDER')); - - await deleteTestFoldersById([rootFolder.id]); - await deleteTestUsersById([user1.id]); - }); -}); diff --git a/packages/domain/src/prisma.service.ts b/packages/domain/src/prisma.service.ts new file mode 100644 index 00000000..359f950b --- /dev/null +++ b/packages/domain/src/prisma.service.ts @@ -0,0 +1,9 @@ +import { Injectable, OnModuleInit } from '@nestjs/common'; +import { PrismaClient } from '@prisma/client'; + +@Injectable() +export class PrismaService extends PrismaClient implements OnModuleInit { + async onModuleInit() { + await this.$connect(); + } +} diff --git a/packages/domain/src/roles/dtos/create-role-dto.test.ts b/packages/domain/src/roles/dtos/create-role-dto.test.ts deleted file mode 100644 index 28a84fbc..00000000 --- a/packages/domain/src/roles/dtos/create-role-dto.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { CreateRoleDto } from './create-role-dto'; -import { Role } from '../../entities/role'; - -describe('Test Create Role DTO', () => { - it('should return a valid role object', () => { - const dto = new CreateRoleDto({ - description: 'description admin', - level: 100, - name: 'admin', - }); - - const role = dto.toRole(); - - expect(role).toMatchObject({ - createdAt: expect.any(Date), - description: dto.description, - id: expect.any(String), - level: dto.level, - name: dto.name, - updatedAt: expect.any(Date), - }); - }); -}); diff --git a/packages/domain/src/roles/role.service.ts b/packages/domain/src/roles/role.service.ts deleted file mode 100644 index d7d7ddbe..00000000 --- a/packages/domain/src/roles/role.service.ts +++ /dev/null @@ -1,55 +0,0 @@ -import SnipcodeError, { errors } from '@snipcode/utils'; - -import { CreateRoleDto } from './dtos/create-role-dto'; -import { Role, RoleName } from '../entities/role'; -import { prisma } from '../utils/prisma'; - -export class RoleService { - async loadRoles(): Promise { - const roleAdminDto = new CreateRoleDto({ - description: 'can do everything in the application', - level: 200, - name: 'admin', - }); - const roleUserDto = new CreateRoleDto({ description: "can't do everything", level: 100, name: 'user' }); - - const promises = [roleAdminDto, roleUserDto].map(async (roleDto) => { - const role = await prisma.role.findUnique({ where: { name: roleDto.name } }); - - if (!role) { - const input = roleDto.toRole(); - - return prisma.role.create({ - data: { - description: input.description, - id: input.id, - level: input.level, - name: input.name, - }, - }); - } - - return null; - }); - - await Promise.all(promises); - } - - async findByName(name: RoleName): Promise { - const role = await prisma.role.findUnique({ where: { name } }); - - if (!role) { - throw new SnipcodeError(errors.ROLE_USER_NOT_FOUND, 'ROLE_USER_NOT_FOUND'); - } - - return role; - } - - async findById(id: string): Promise { - return prisma.role.findUnique({ where: { id } }); - } - - async findAll(): Promise { - return prisma.role.findMany({ orderBy: { level: 'desc' } }); - } -} diff --git a/packages/domain/src/entities/folder.ts b/packages/domain/src/services/folders/folder.entity.ts similarity index 100% rename from packages/domain/src/entities/folder.ts rename to packages/domain/src/services/folders/folder.entity.ts diff --git a/packages/domain/src/services/folders/folder.service.test.ts b/packages/domain/src/services/folders/folder.service.test.ts new file mode 100644 index 00000000..25276643 --- /dev/null +++ b/packages/domain/src/services/folders/folder.service.test.ts @@ -0,0 +1,462 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { SnipcodeError, errors, generateRandomId } from '@snipcode/utils'; + +import { Folder } from './folder.entity'; +import { FolderService } from './folder.service'; +import { CreateFolderInput } from './inputs/create-folder-input'; +import { CreateUserRootFolderInput } from './inputs/create-user-root-folder-input'; +import { TestHelper } from '../../../tests/helpers'; +import { DomainModule } from '../../domain.module'; +import { PrismaService } from '../../prisma.service'; +import { RoleService } from '../roles/role.service'; + +describe('Test Folder service', () => { + let folderService: FolderService; + let roleService: RoleService; + let testHelper: TestHelper; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [ + DomainModule.forRootAsync({ + databaseUrl: process.env.TEST_DATABASE_URL, + }), + ], + providers: [RoleService, FolderService], + }).compile(); + + folderService = module.get(FolderService); + roleService = module.get(RoleService); + + const prismaService = module.get(PrismaService); + + testHelper = new TestHelper(prismaService); + + await roleService.loadRoles(); + }); + + it('should create a root folder for a user', async () => { + // GIVEN + const user = await testHelper.createTestUser({}); + + const creatUserRootFolderInput = new CreateUserRootFolderInput(user.id); + + // WHEN + const expectedFolder = await folderService.createUserRootFolder(creatUserRootFolderInput); + + // THEN + expect(expectedFolder?.id).toEqual(creatUserRootFolderInput.toFolder().id); + + await testHelper.deleteTestFoldersById([expectedFolder?.id]); + await testHelper.deleteTestUsersById([user.id]); + }); + + it('should create folder for the specified user', async () => { + // GIVEN + const [user, rootFolder] = await testHelper.createUserWithRootFolder(); + + const createFolderInput = new CreateFolderInput({ + name: 'my gist', + parentId: rootFolder.id, + userId: user.id, + }); + + // WHEN + const expectedFolder = await folderService.create(createFolderInput); + + // THEN + expect(expectedFolder).toMatchObject({ + id: createFolderInput.toFolder().id, + isFavorite: false, + name: createFolderInput.name, + parentId: rootFolder.id, + userId: user.id, + }); + + await testHelper.deleteTestFoldersById([expectedFolder.id]); + await testHelper.deleteTestFoldersById([rootFolder.id]); + await testHelper.deleteTestUsersById([user.id]); + }); + + it('should not create the folder cause it already exists', async () => { + // GIVEN + const [user, rootFolder] = await testHelper.createUserWithRootFolder(); + + const [firstFolder, secondFolder] = await testHelper.createManyTestFolders({ + folderNames: ['My gist', 'Blogs'], + parentId: rootFolder.id, + userId: user.id, + }); + + const createFolderInput = new CreateFolderInput({ + name: secondFolder.name, + parentId: rootFolder.id, + userId: user.id, + }); + + // WHEN + // THEN + await expect(() => folderService.create(createFolderInput)).rejects.toThrow( + new SnipcodeError(errors.FOLDER_ALREADY_EXIST(createFolderInput.name), 'FOLDER_ALREADY_EXIST'), + ); + + await testHelper.deleteTestFoldersById([firstFolder.id, secondFolder.id, rootFolder.id]); + await testHelper.deleteTestUsersById([user.id]); + }); + + it("should find the user's root folder", async () => { + // GIVEN + const [user, rootFolder] = await testHelper.createUserWithRootFolder(); + + const [firstFolder, secondFolder] = await testHelper.createManyTestFolders({ + folderNames: ['My gist', 'Blogs'], + parentId: rootFolder.id, + userId: user.id, + }); + + // WHEN + const userRootFolder = await folderService.findUserRootFolder(user.id); + + // THEN + expect(userRootFolder?.name).toEqual(`__${user.id}__`); + + await testHelper.deleteTestFoldersById([firstFolder.id, secondFolder.id, rootFolder.id]); + await testHelper.deleteTestUsersById([user.id]); + }); + + it("should not find the user's root folder", async () => { + // GIVEN + const user = await testHelper.createTestUser({}); + + // WHEN + // THEN + await expect(() => folderService.findUserRootFolder(user.id)).rejects.toThrow( + new SnipcodeError(errors.USER_ROOT_FOLDER_NOT_FOUND(user.id), 'USER_ROOT_FOLDER_NOT_FOUND'), + ); + + await testHelper.deleteTestUsersById([user.id]); + }); + + it('should find sub folders of the root user folder', async () => { + // GIVEN + const [user, rootFolder] = await testHelper.createUserWithRootFolder(); + + const [gistFolder, blogsFolder] = await testHelper.createManyTestFolders({ + folderNames: ['My gist', 'Blogs'], + parentId: rootFolder.id, + userId: user.id, + }); + + const [post1Folder, post2folder] = await testHelper.createManyTestFolders({ + folderNames: ['post1', 'post2'], + parentId: blogsFolder.id, + userId: user.id, + }); + + // WHEN + const userRootFolders1 = await folderService.findSubFolders(user.id); + const userRootFolders2 = await folderService.findSubFolders(user.id, rootFolder.id); + + // THEN + expect(userRootFolders1).toHaveLength(2); + expect(userRootFolders1).toEqual(userRootFolders2); + + await testHelper.deleteTestFoldersById([ + post1Folder.id, + post2folder.id, + gistFolder.id, + blogsFolder.id, + rootFolder.id, + ]); + await testHelper.deleteTestUsersById([user.id]); + }); + + it('should find the sub folders of a folder', async () => { + // GIVEN + const [user, rootFolder] = await testHelper.createUserWithRootFolder(); + + const [firstFolder, secondFolder] = await testHelper.createManyTestFolders({ + folderNames: ['My gist', 'Blogs'], + parentId: rootFolder.id, + userId: user.id, + }); + + const [javaFolder, nodeFolder] = await testHelper.createManyTestFolders({ + folderNames: ['java', 'node.js'], + parentId: firstFolder.id, + userId: user.id, + }); + + const [post1Folder, post2folder] = await testHelper.createManyTestFolders({ + folderNames: ['post1', 'post2'], + parentId: secondFolder.id, + userId: user.id, + }); + + // WHEN + const subFolders = await folderService.findSubFolders(user.id, firstFolder.id); + + // THEN + expect(subFolders).toHaveLength(2); + expect(subFolders.map((folder) => folder.name)).toEqual(['java', 'node.js']); + + await testHelper.deleteTestFoldersById([ + javaFolder.id, + nodeFolder.id, + post1Folder.id, + post2folder.id, + firstFolder.id, + secondFolder.id, + rootFolder.id, + ]); + await testHelper.deleteTestUsersById([user.id]); + }); + + it('should delete folders belonging to the user', async () => { + // GIVEN + const [user1, rootFolder1] = await testHelper.createUserWithRootFolder(); + const [myGistFolder, blogsFolder] = await testHelper.createManyTestFolders({ + folderNames: ['My gist', 'Blogs'], + parentId: rootFolder1.id, + userId: user1.id, + }); + + const [user2, rootFolder2] = await testHelper.createUserWithRootFolder(); + const [projectFolder, snippetFolder] = await testHelper.createManyTestFolders({ + folderNames: ['project', 'snippet'], + parentId: rootFolder2.id, + userId: user2.id, + }); + + // WHEN + await folderService.deleteMany([myGistFolder.id, blogsFolder.id], user1.id); + const subFolders = await folderService.findSubFolders(user1.id); + + // THEN + expect(subFolders).toHaveLength(0); + + await testHelper.deleteTestFoldersById([projectFolder.id, snippetFolder.id, rootFolder1.id, rootFolder2.id]); + await testHelper.deleteTestUsersById([user1.id]); + await testHelper.deleteTestUsersById([user2.id]); + }); + + it('should delete folders belonging to the user - validation check', async () => { + // GIVEN + const [user1, rootFolder1] = await testHelper.createUserWithRootFolder(); + const [myGistFolder, blogsFolder] = await testHelper.createManyTestFolders({ + folderNames: ['My gist', 'Blogs'], + parentId: rootFolder1.id, + userId: user1.id, + }); + + const [user2, rootFolder2] = await testHelper.createUserWithRootFolder(); + const [projectFolder, snippetFolder] = await testHelper.createManyTestFolders({ + folderNames: ['project', 'snippet'], + parentId: rootFolder2.id, + userId: user2.id, + }); + + // WHEN + await folderService.deleteMany([myGistFolder.id, projectFolder.id], user1.id); + + // THEN + const user1SubFolders = await folderService.findSubFolders(user1.id); + const user2SubFolders = await folderService.findSubFolders(user2.id); + + expect(user1SubFolders).toHaveLength(1); + expect(user1SubFolders.map((folder) => folder.name)).toEqual(['Blogs']); + + expect(user2SubFolders).toHaveLength(2); + expect(user2SubFolders.map((folder) => folder.name)).toEqual(['project', 'snippet']); + + await testHelper.deleteTestFoldersById([ + snippetFolder.id, + projectFolder.id, + blogsFolder.id, + rootFolder1.id, + rootFolder2.id, + ]); + await testHelper.deleteTestUsersById([user1.id]); + await testHelper.deleteTestUsersById([user2.id]); + }); + + it('should not delete folders because we cannot delete a root folder', async () => { + // GIVEN + const [user, rootFolder] = await testHelper.createUserWithRootFolder(); + const [myGistFolder] = await testHelper.createManyTestFolders({ + folderNames: ['My gist'], + parentId: rootFolder.id, + userId: user.id, + }); + + // WHEN + // THEN + await expect(async () => { + await folderService.deleteMany([myGistFolder.id, rootFolder.id], user.id); + }).rejects.toThrow(new SnipcodeError(errors.CANT_DELETE_ROOT_FOLDER, 'CANT_DELETE_ROOT_FOLDER')); + + await testHelper.deleteTestFoldersById([myGistFolder.id, rootFolder.id]); + await testHelper.deleteTestUsersById([user.id]); + }); + + it('should generate the breadcrumb path of a folder', async () => { + // GIVEN + const [user, rootFolder] = await testHelper.createUserWithRootFolder(); + + const gistFolderInput = TestHelper.createTestFolderInput({ + name: 'My gist', + parentId: rootFolder.id, + userId: user.id, + }); + const gistFolder = await folderService.create(gistFolderInput); + + const nodeFolderInput = TestHelper.createTestFolderInput({ + name: 'Node.js', + parentId: gistFolder.id, + userId: user.id, + }); + const nodeFolder = await folderService.create(nodeFolderInput); + + // WHEN + const subFolders = await folderService.generateBreadcrumb(nodeFolder.id); + + // THEN + expect(subFolders).toHaveLength(2); + expect(subFolders.map((folder) => folder.name)).toEqual(['My gist', 'Node.js']); + + await testHelper.deleteTestFoldersById([nodeFolder.id, gistFolder.id, rootFolder.id]); + await testHelper.deleteTestUsersById([user.id]); + }); + + it('should generate the breadcrumb path of the root folder', async () => { + // GIVEN + const [user, rootFolder] = await testHelper.createUserWithRootFolder(); + + // WHEN + const subFolders = await folderService.generateBreadcrumb(rootFolder.id); + + // THEN + expect(subFolders).toHaveLength(0); + + await testHelper.deleteTestFoldersById([rootFolder.id]); + await testHelper.deleteTestUsersById([user.id]); + }); + + it('should found no folder given the ID provided', async () => { + // GIVEN + const folderId = generateRandomId(); + + // WHEN + // THEN + await expect(async () => { + await folderService.findById(folderId); + }).rejects.toThrow(new SnipcodeError(errors.FOLDER_NOT_FOUND(folderId), 'FOLDER_NOT_FOUND')); + }); + + it('should update an existing folder in the specified folder', async () => { + // GIVEN + const [user, rootFolder] = await testHelper.createUserWithRootFolder(); + const [folder] = await testHelper.createManyTestFolders({ + folderNames: ['My gist'], + parentId: rootFolder.id, + userId: user.id, + }); + + const updateFolderInput = TestHelper.updateTestFolderInput({ + folderId: folder.id, + name: 'The Gist', + userId: user.id, + }); + + // WHEN + const updatedFolder = await folderService.update(updateFolderInput); + + // THEN + const folderToUpdate = updateFolderInput.toFolder(folder); + + expect(updatedFolder).toMatchObject({ + createdAt: expect.any(Date), + id: folder.id, + isFavorite: false, + name: folderToUpdate.name, + parentId: rootFolder.id, + path: folder.path, + updatedAt: expect.any(Date), + userId: user.id, + }); + + await testHelper.deleteTestFoldersById([updatedFolder.id, rootFolder.id]); + await testHelper.deleteTestUsersById([user.id]); + }); + + it('should not update an existing folder in the specified folder because another folder with the updated name already exists in the folder', async () => { + // GIVEN + const [user, rootFolder] = await testHelper.createUserWithRootFolder(); + const [folder1, folder2] = await testHelper.createManyTestFolders({ + folderNames: ['folder-one', 'folder-two'], + parentId: rootFolder.id, + userId: user.id, + }); + + const updateFolderInput = TestHelper.updateTestFolderInput({ + folderId: folder1.id, + name: 'folder-two', + userId: user.id, + }); + + // WHEN + // THEN + await expect(async () => { + await folderService.update(updateFolderInput); + }).rejects.toThrow(new SnipcodeError(errors.FOLDER_ALREADY_EXIST(updateFolderInput.name), 'FOLDER_ALREADY_EXIST')); + + await testHelper.deleteTestFoldersById([folder1.id, folder2.id]); + await testHelper.deleteTestFoldersById([rootFolder.id]); + await testHelper.deleteTestUsersById([user.id]); + }); + + it('should not update an existing folder in the specified folder because it belongs to other user', async () => { + // GIVEN + const [user1, rootFolder1] = await testHelper.createUserWithRootFolder(); + const [user2, rootFolder2] = await testHelper.createUserWithRootFolder(); + const [folderUser2] = await testHelper.createManyTestFolders({ + folderNames: ['folder-user-two'], + parentId: rootFolder2.id, + userId: user2.id, + }); + + const updateFolderInput = TestHelper.updateTestFolderInput({ folderId: folderUser2.id, userId: user1.id }); + + // WHEN + // THEN + await expect(async () => { + await folderService.update(updateFolderInput); + }).rejects.toThrow( + new SnipcodeError(errors.CANT_EDIT_FOLDER(updateFolderInput.creatorId, folderUser2.id), 'CANT_EDIT_FOLDER'), + ); + + await testHelper.deleteTestFoldersById([folderUser2.id]); + await testHelper.deleteTestFoldersById([rootFolder1.id, rootFolder2.id]); + await testHelper.deleteTestUsersById([user1.id, user2.id]); + }); + + it('should not update the user root folder', async () => { + // GIVEN + const [user1, rootFolder] = await testHelper.createUserWithRootFolder(); + + const updateFolderInput = TestHelper.updateTestFolderInput({ + folderId: rootFolder.id, + name: 'new-root-folder', + userId: user1.id, + }); + + // WHEN + // THEN + await expect(async () => { + await folderService.update(updateFolderInput); + }).rejects.toThrow(new SnipcodeError(errors.CANT_RENAME_ROOT_FOLDER, 'CANT_RENAME_ROOT_FOLDER')); + + await testHelper.deleteTestFoldersById([rootFolder.id]); + await testHelper.deleteTestUsersById([user1.id]); + }); +}); diff --git a/packages/domain/src/folders/folder.service.ts b/packages/domain/src/services/folders/folder.service.ts similarity index 64% rename from packages/domain/src/folders/folder.service.ts rename to packages/domain/src/services/folders/folder.service.ts index 9f87fe70..ce7ccb72 100644 --- a/packages/domain/src/folders/folder.service.ts +++ b/packages/domain/src/services/folders/folder.service.ts @@ -1,42 +1,46 @@ -import SnipcodeError, { errors } from '@snipcode/utils'; +import { Injectable } from '@nestjs/common'; +import { SnipcodeError, errors } from '@snipcode/utils'; -import { CreateFolderDto } from './dtos/create-folder-dto'; -import { CreateUserRootFolderDto } from './dtos/create-user-root-folder-dto'; -import { UpdateFolderDto } from './dtos/update-folder-dto'; +import { Folder } from './folder.entity'; +import { CreateFolderInput } from './inputs/create-folder-input'; +import { CreateUserRootFolderInput } from './inputs/create-user-root-folder-input'; +import { UpdateFolderInput } from './inputs/update-folder-input'; import { isFoldersContainRoot } from './utils/folders'; -import { Folder } from '../entities/folder'; -import { prisma } from '../utils/prisma'; +import { PrismaService } from '../../prisma.service'; +@Injectable() export class FolderService { - async createUserRootFolder(dto: CreateUserRootFolderDto): Promise { - const input = dto.toFolder(); + constructor(private readonly prisma: PrismaService) {} - return prisma.folder.create({ + async createUserRootFolder(input: CreateUserRootFolderInput): Promise { + const folderInput = input.toFolder(); + + return this.prisma.folder.create({ data: { - id: input.id, - name: input.name, - parentId: input.parentId, - userId: input.userId, + id: folderInput.id, + name: folderInput.name, + parentId: folderInput.parentId, + userId: folderInput.userId, }, }); } - async create(createFolderDto: CreateFolderDto): Promise { + async create(createFolderInput: CreateFolderInput): Promise { const isFolderExist = await this.isFolderExistInParentFolder({ - folderName: createFolderDto.name, - parentFolderId: createFolderDto.parentFolderId, - userId: createFolderDto.user, + folderName: createFolderInput.name, + parentFolderId: createFolderInput.parentFolderId, + userId: createFolderInput.user, }); if (isFolderExist) { - throw new SnipcodeError(errors.FOLDER_ALREADY_EXIST(createFolderDto.name), 'FOLDER_ALREADY_EXIST'); + throw new SnipcodeError(errors.FOLDER_ALREADY_EXIST(createFolderInput.name), 'FOLDER_ALREADY_EXIST'); } - const input = createFolderDto.toFolder(); + const input = createFolderInput.toFolder(); - const parentFolder = await this.findById(createFolderDto.parentFolderId); + const parentFolder = await this.findById(createFolderInput.parentFolderId); - return prisma.folder.create({ + return this.prisma.folder.create({ data: { id: input.id, name: input.name, @@ -48,11 +52,11 @@ export class FolderService { } async findUserFolders(userId: string): Promise { - return prisma.folder.findMany({ orderBy: { name: 'asc' }, where: { userId } }); + return this.prisma.folder.findMany({ orderBy: { name: 'asc' }, where: { userId } }); } async findById(id: string): Promise { - const folder = await prisma.folder.findUnique({ where: { id } }); + const folder = await this.prisma.folder.findUnique({ where: { id } }); if (!folder) { throw new SnipcodeError(errors.FOLDER_NOT_FOUND(id), 'FOLDER_NOT_FOUND'); @@ -84,7 +88,7 @@ export class FolderService { } async deleteMany(folderIds: string[], userId: string): Promise { - const foldersToDelete = await prisma.folder.findMany({ + const foldersToDelete = await this.prisma.folder.findMany({ where: { id: { in: folderIds, @@ -99,7 +103,7 @@ export class FolderService { const ids = foldersToDelete.map((folder) => folder.id); - await prisma.folder.deleteMany({ + await this.prisma.folder.deleteMany({ where: { id: { in: ids, @@ -117,7 +121,7 @@ export class FolderService { return []; } - const parentFoldersOrdered = await prisma.folder.findMany({ + const parentFoldersOrdered = await this.prisma.folder.findMany({ orderBy: { createdAt: 'asc', }, @@ -134,11 +138,11 @@ export class FolderService { return parentFoldersOrdered.concat(folder); } - async update(updateFolderDto: UpdateFolderDto): Promise { - const folder = await this.findById(updateFolderDto.folderId); + async update(updateFolderInput: UpdateFolderInput): Promise { + const folder = await this.findById(updateFolderInput.folderId); - if (folder.userId !== updateFolderDto.creatorId) { - throw new SnipcodeError(errors.CANT_EDIT_FOLDER(updateFolderDto.creatorId, folder.id), 'CANT_EDIT_FOLDER'); + if (folder.userId !== updateFolderInput.creatorId) { + throw new SnipcodeError(errors.CANT_EDIT_FOLDER(updateFolderInput.creatorId, folder.id), 'CANT_EDIT_FOLDER'); } if (!folder.parentId) { @@ -146,18 +150,18 @@ export class FolderService { } const isFolderExist = await this.isFolderExistInParentFolder({ - folderName: updateFolderDto.name, + folderName: updateFolderInput.name, parentFolderId: folder.parentId, userId: folder.userId, }); if (isFolderExist) { - throw new SnipcodeError(errors.FOLDER_ALREADY_EXIST(updateFolderDto.name), 'FOLDER_ALREADY_EXIST'); + throw new SnipcodeError(errors.FOLDER_ALREADY_EXIST(updateFolderInput.name), 'FOLDER_ALREADY_EXIST'); } - const input = updateFolderDto.toFolder(folder); + const input = updateFolderInput.toFolder(folder); - return prisma.folder.update({ + return this.prisma.folder.update({ data: { name: input.name, }, @@ -176,7 +180,7 @@ export class FolderService { } private findFolderSubFolders(folderId: string, userId: string): Promise { - return prisma.folder.findMany({ + return this.prisma.folder.findMany({ where: { parentId: folderId, userId, diff --git a/packages/domain/src/folders/dtos/create-folder-dto.test.ts b/packages/domain/src/services/folders/inputs/create-folder-input.test.ts similarity index 72% rename from packages/domain/src/folders/dtos/create-folder-dto.test.ts rename to packages/domain/src/services/folders/inputs/create-folder-input.test.ts index a60ba572..357fd9cd 100644 --- a/packages/domain/src/folders/dtos/create-folder-dto.test.ts +++ b/packages/domain/src/services/folders/inputs/create-folder-input.test.ts @@ -1,17 +1,17 @@ -import { CreateFolderDto } from './create-folder-dto'; -import { Folder } from '../../entities/folder'; +import { CreateFolderInput } from './create-folder-input'; +import { Folder } from '../folder.entity'; -describe('Test Create Folder DTO', () => { +describe('Test Create Folder Input', () => { it('should create a valid folder object', () => { // GIVEN - const dto = new CreateFolderDto({ + const input = new CreateFolderInput({ name: 'blogs', parentId: 'cl23rzwe5000002czaedc8sll', userId: 'dm34saxf6111113dabfed9tmm', }); // WHEN - const folder = dto.toFolder(); + const folder = input.toFolder(); // THEN expect(folder).toMatchObject({ @@ -28,7 +28,7 @@ describe('Test Create Folder DTO', () => { it('should create the valid folder name', () => { // GIVEN - const dto = new CreateFolderDto({ + const input = new CreateFolderInput({ name: 'blogs', parentId: 'cl23rzwe5000002czaedc8sll', userId: 'dm34saxf6111113dabfed9tmm', @@ -37,12 +37,12 @@ describe('Test Create Folder DTO', () => { // WHEN // THEN - expect(dto.name).toEqual(expectedFolderName); + expect(input.name).toEqual(expectedFolderName); }); it('should create the valid folder parent id', () => { // GIVEN - const dto = new CreateFolderDto({ + const input = new CreateFolderInput({ name: 'blogs', parentId: 'cl23rzwe5000002czaedc8sll', userId: 'dm34saxf6111113dabfed9tmm', @@ -51,12 +51,12 @@ describe('Test Create Folder DTO', () => { // WHEN // THEN - expect(dto.parentFolderId).toEqual(expectedParentId); + expect(input.parentFolderId).toEqual(expectedParentId); }); it('should create the valid folder user id', () => { // GIVEN - const dto = new CreateFolderDto({ + const input = new CreateFolderInput({ name: 'blogs', parentId: 'cl23rzwe5000002czaedc8sll', userId: 'dm34saxf6111113dabfed9tmm', @@ -65,6 +65,6 @@ describe('Test Create Folder DTO', () => { // WHEN // THEN - expect(dto.user).toEqual(expectedUserId); + expect(input.user).toEqual(expectedUserId); }); }); diff --git a/packages/domain/src/folders/dtos/create-folder-dto.ts b/packages/domain/src/services/folders/inputs/create-folder-input.ts similarity index 84% rename from packages/domain/src/folders/dtos/create-folder-dto.ts rename to packages/domain/src/services/folders/inputs/create-folder-input.ts index 13e072f0..0ed63c14 100644 --- a/packages/domain/src/folders/dtos/create-folder-dto.ts +++ b/packages/domain/src/services/folders/inputs/create-folder-input.ts @@ -1,5 +1,5 @@ -import { Folder } from '../../entities/folder'; -import { dbID } from '../../utils/id'; +import { dbID } from '../../../utils/db-id'; +import { Folder } from '../folder.entity'; type Input = { name: string; @@ -7,7 +7,7 @@ type Input = { userId: string; }; -export class CreateFolderDto { +export class CreateFolderInput { private readonly folderId: string; constructor(private _input: Input) { diff --git a/packages/domain/src/folders/dtos/create-user-root-folder-dto.test.ts b/packages/domain/src/services/folders/inputs/create-user-root-folder-input.test.ts similarity index 55% rename from packages/domain/src/folders/dtos/create-user-root-folder-dto.test.ts rename to packages/domain/src/services/folders/inputs/create-user-root-folder-input.test.ts index 2a9f7f47..2b081f9c 100644 --- a/packages/domain/src/folders/dtos/create-user-root-folder-dto.test.ts +++ b/packages/domain/src/services/folders/inputs/create-user-root-folder-input.test.ts @@ -1,11 +1,11 @@ -import { CreateUserRootFolderDto } from './create-user-root-folder-dto'; -import { Folder } from '../../entities/folder'; +import { CreateUserRootFolderInput } from './create-user-root-folder-input'; +import { Folder } from '../folder.entity'; -describe('Test Create User Root Folder DTO', () => { +describe('Test Create User Root Folder Input', () => { it('should create a valid folder object', () => { - const dto = new CreateUserRootFolderDto('dm34saxf6111113dabfed9tmm'); + const input = new CreateUserRootFolderInput('dm34saxf6111113dabfed9tmm'); - const folder = dto.toFolder(); + const folder = input.toFolder(); expect(folder).toMatchObject({ createdAt: expect.any(Date), diff --git a/packages/domain/src/folders/dtos/create-user-root-folder-dto.ts b/packages/domain/src/services/folders/inputs/create-user-root-folder-input.ts similarity index 74% rename from packages/domain/src/folders/dtos/create-user-root-folder-dto.ts rename to packages/domain/src/services/folders/inputs/create-user-root-folder-input.ts index 793a7b89..098b01f8 100644 --- a/packages/domain/src/folders/dtos/create-user-root-folder-dto.ts +++ b/packages/domain/src/services/folders/inputs/create-user-root-folder-input.ts @@ -1,7 +1,7 @@ -import { Folder } from '../../entities/folder'; -import { dbID } from '../../utils/id'; +import { dbID } from '../../../utils/db-id'; +import { Folder } from '../folder.entity'; -export class CreateUserRootFolderDto { +export class CreateUserRootFolderInput { private readonly folderId: string; constructor(private _userId: string) { diff --git a/packages/domain/src/folders/dtos/update-folder-dto.test.ts b/packages/domain/src/services/folders/inputs/update-folder-input.test.ts similarity index 52% rename from packages/domain/src/folders/dtos/update-folder-dto.test.ts rename to packages/domain/src/services/folders/inputs/update-folder-input.test.ts index 4130bef0..7f680a49 100644 --- a/packages/domain/src/folders/dtos/update-folder-dto.test.ts +++ b/packages/domain/src/services/folders/inputs/update-folder-input.test.ts @@ -1,21 +1,21 @@ -import { UpdateFolderDto } from './update-folder-dto'; -import { createTestFolderDto, generateTestId } from '../../../tests/helpers'; -import { Folder } from '../../entities/folder'; +import { UpdateFolderInput } from './update-folder-input'; +import { TestHelper } from '../../../../tests/helpers'; +import { Folder } from '../folder.entity'; -describe('Test Update Folder DTO', () => { +describe('Test Update Folder Input', () => { it('should return the folder to update', () => { - const parentId = generateTestId(); - const userId = generateTestId(); + const parentId = TestHelper.generateTestId(); + const userId = TestHelper.generateTestId(); - const dto = new UpdateFolderDto({ + const input = new UpdateFolderInput({ creatorId: userId, folderId: parentId, name: 'folder updated', }); - const currentFolder = createTestFolderDto({ parentId, userId }).toFolder(); + const currentFolder = TestHelper.createTestFolderInput({ parentId, userId }).toFolder(); - const folderToUpdate = dto.toFolder(currentFolder); + const folderToUpdate = input.toFolder(currentFolder); const expectedFolder: Folder = { createdAt: currentFolder.createdAt, diff --git a/packages/domain/src/folders/dtos/update-folder-dto.ts b/packages/domain/src/services/folders/inputs/update-folder-input.ts similarity index 85% rename from packages/domain/src/folders/dtos/update-folder-dto.ts rename to packages/domain/src/services/folders/inputs/update-folder-input.ts index b076a7f3..deb1550c 100644 --- a/packages/domain/src/folders/dtos/update-folder-dto.ts +++ b/packages/domain/src/services/folders/inputs/update-folder-input.ts @@ -1,4 +1,4 @@ -import { Folder } from '../../entities/folder'; +import { Folder } from '../folder.entity'; type Input = { creatorId: string; @@ -6,7 +6,7 @@ type Input = { name: string; }; -export class UpdateFolderDto { +export class UpdateFolderInput { constructor(private _input: Input) {} get name(): string { diff --git a/packages/domain/src/folders/utils/folders.test.ts b/packages/domain/src/services/folders/utils/folders.test.ts similarity index 54% rename from packages/domain/src/folders/utils/folders.test.ts rename to packages/domain/src/services/folders/utils/folders.test.ts index 0ea1c133..d7659522 100644 --- a/packages/domain/src/folders/utils/folders.test.ts +++ b/packages/domain/src/services/folders/utils/folders.test.ts @@ -1,17 +1,17 @@ import { isFoldersContainRoot } from './folders'; -import { createTestFolderDto, generateTestId } from '../../../tests/helpers'; -import { Folder } from '../../entities/folder'; +import { TestHelper } from '../../../../tests/helpers'; +import { Folder } from '../folder.entity'; describe('Test folders utilities', () => { it('should assert the folders contain the root folder', () => { // GIVEN - const userId = generateTestId(); + const userId = TestHelper.generateTestId(); - const rootFolder = createTestFolderDto({ userId }).toFolder(); + const rootFolder = TestHelper.createTestFolderInput({ userId }).toFolder(); rootFolder.parentId = null; - const foldersToDelete: Folder[] = [createTestFolderDto({ userId }).toFolder(), rootFolder]; + const foldersToDelete: Folder[] = [TestHelper.createTestFolderInput({ userId }).toFolder(), rootFolder]; // WHEN const isValid = isFoldersContainRoot(foldersToDelete); @@ -22,11 +22,11 @@ describe('Test folders utilities', () => { it("should assert the folders doesn't contain the root folder", () => { // GIVEN - const userId = generateTestId(); + const userId = TestHelper.generateTestId(); const foldersToDelete: Folder[] = [ - createTestFolderDto({ userId }).toFolder(), - createTestFolderDto({ userId }).toFolder(), + TestHelper.createTestFolderInput({ userId }).toFolder(), + TestHelper.createTestFolderInput({ userId }).toFolder(), ]; // WHEN diff --git a/packages/domain/src/folders/utils/folders.ts b/packages/domain/src/services/folders/utils/folders.ts similarity index 73% rename from packages/domain/src/folders/utils/folders.ts rename to packages/domain/src/services/folders/utils/folders.ts index f21da7a0..b4960cfd 100644 --- a/packages/domain/src/folders/utils/folders.ts +++ b/packages/domain/src/services/folders/utils/folders.ts @@ -1,4 +1,4 @@ -import { Folder } from '../../entities/folder'; +import { Folder } from '../folder.entity'; export const isFoldersContainRoot = (folders: Folder[]): boolean => { return folders.some((folder) => folder.parentId === null); diff --git a/packages/domain/src/newsletters/newsletter.service.test.ts b/packages/domain/src/services/newsletters/newsletter.service.test.ts similarity index 69% rename from packages/domain/src/newsletters/newsletter.service.test.ts rename to packages/domain/src/services/newsletters/newsletter.service.test.ts index 5f778f5b..9965bb3c 100644 --- a/packages/domain/src/newsletters/newsletter.service.test.ts +++ b/packages/domain/src/services/newsletters/newsletter.service.test.ts @@ -1,23 +1,37 @@ -import SnipcodeError from '@snipcode/utils'; +import { Test, TestingModule } from '@nestjs/testing'; +import { SnipcodeError } from '@snipcode/utils'; import nock from 'nock'; import { NewsletterService } from './newsletter.service'; -const newsletterService = new NewsletterService({ - apiKey: 'apiKey', - formId: 'formId', -}); +const apiBaseURL = 'https://api.convertkit.com/v3'; + +describe('Newsletter service', () => { + let newsletterService: NewsletterService; -const baseURL = 'https://api.convertkit.com/v3'; + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + { + provide: NewsletterService, + useValue: new NewsletterService({ + apiKey: 'apiKey', + formId: 'formId', + }), + }, + ], + }).compile(); + + newsletterService = module.get(NewsletterService); + }); -describe('Test the newsletter service', () => { test('Add the email address to the newsletter subscribers', async () => { // GIVEN const emailToSubscribe = 'user@email.com'; const tags = ['snipcode']; const formId = 'formId'; - const scope = nock(baseURL) + const scope = nock(apiBaseURL) .post(`/forms/${formId}/subscribe`, { api_key: 'apiKey', email: emailToSubscribe, @@ -44,7 +58,7 @@ describe('Test the newsletter service', () => { const tags = ['snipcode']; const formId = 'formId'; - nock(baseURL) + nock(apiBaseURL) .post(`/forms/${formId}/subscribe`, { api_key: 'apiKey', email: emailToSubscribe, diff --git a/packages/domain/src/newsletters/newsletter.service.ts b/packages/domain/src/services/newsletters/newsletter.service.ts similarity index 90% rename from packages/domain/src/newsletters/newsletter.service.ts rename to packages/domain/src/services/newsletters/newsletter.service.ts index ba0a42f0..19b25e37 100644 --- a/packages/domain/src/newsletters/newsletter.service.ts +++ b/packages/domain/src/services/newsletters/newsletter.service.ts @@ -1,7 +1,8 @@ +// import { Injectable } from '@nestjs/common'; import axios, { AxiosInstance } from 'axios'; import { NewsletterOption, SubscribeData, SubscribeInput } from './types'; -import { handleRequestError } from '../utils/axios-error'; +import { handleRequestError } from '../../utils/axios-error'; export class NewsletterService { private httpClient: AxiosInstance = axios.create(); diff --git a/packages/domain/src/newsletters/types.ts b/packages/domain/src/services/newsletters/types.ts similarity index 100% rename from packages/domain/src/newsletters/types.ts rename to packages/domain/src/services/newsletters/types.ts diff --git a/packages/domain/src/services/roles/inputs/create-role-input.test.ts b/packages/domain/src/services/roles/inputs/create-role-input.test.ts new file mode 100644 index 00000000..992c46d5 --- /dev/null +++ b/packages/domain/src/services/roles/inputs/create-role-input.test.ts @@ -0,0 +1,23 @@ +import { CreateRoleInput } from './create-role-input'; +import { Role } from '../role.entity'; + +describe('Test Create Role Input', () => { + it('should return a valid role object', () => { + const input = new CreateRoleInput({ + description: 'description admin', + level: 100, + name: 'admin', + }); + + const role = input.toRole(); + + expect(role).toMatchObject({ + createdAt: expect.any(Date), + description: input.description, + id: expect.any(String), + level: input.level, + name: input.name, + updatedAt: expect.any(Date), + }); + }); +}); diff --git a/packages/domain/src/roles/dtos/create-role-dto.ts b/packages/domain/src/services/roles/inputs/create-role-input.ts similarity index 83% rename from packages/domain/src/roles/dtos/create-role-dto.ts rename to packages/domain/src/services/roles/inputs/create-role-input.ts index 61dee7c4..dffd2a47 100644 --- a/packages/domain/src/roles/dtos/create-role-dto.ts +++ b/packages/domain/src/services/roles/inputs/create-role-input.ts @@ -1,5 +1,5 @@ -import { Role, RoleName } from '../../entities/role'; -import { dbID } from '../../utils/id'; +import { dbID } from '../../../utils/db-id'; +import { Role, RoleName } from '../role.entity'; type Input = { description: string | null; @@ -7,7 +7,7 @@ type Input = { name: RoleName; }; -export class CreateRoleDto { +export class CreateRoleInput { private readonly roleId: string; constructor(private _input: Input) { diff --git a/packages/domain/src/entities/role.ts b/packages/domain/src/services/roles/role.entity.ts similarity index 100% rename from packages/domain/src/entities/role.ts rename to packages/domain/src/services/roles/role.entity.ts diff --git a/packages/domain/src/services/roles/role.service.ts b/packages/domain/src/services/roles/role.service.ts new file mode 100644 index 00000000..0cab52a4 --- /dev/null +++ b/packages/domain/src/services/roles/role.service.ts @@ -0,0 +1,59 @@ +import { Injectable } from '@nestjs/common'; +import { SnipcodeError, errors } from '@snipcode/utils'; + +import { CreateRoleInput } from './inputs/create-role-input'; +import { Role, RoleName } from './role.entity'; +import { PrismaService } from '../../prisma.service'; + +@Injectable() +export class RoleService { + constructor(private readonly prisma: PrismaService) {} + + async loadRoles(): Promise { + const roleAdminInput = new CreateRoleInput({ + description: 'can do everything in the application', + level: 200, + name: 'admin', + }); + const roleUserInput = new CreateRoleInput({ description: "can't do everything", level: 100, name: 'user' }); + + const promises = [roleAdminInput, roleUserInput].map(async (roleInput) => { + const role = await this.prisma.role.findUnique({ where: { name: roleInput.name } }); + + if (!role) { + const input = roleInput.toRole(); + + return this.prisma.role.create({ + data: { + description: input.description, + id: input.id, + level: input.level, + name: input.name, + }, + }); + } + + return null; + }); + + await Promise.all(promises); + } + + async findByName(name: RoleName): Promise { + const role = await this.prisma.role.findUnique({ where: { name } }); + + if (!role) { + throw new SnipcodeError(errors.ROLE_USER_NOT_FOUND, 'ROLE_USER_NOT_FOUND'); + } + + return role; + } + + async findById(id: string): Promise { + return this.prisma.role.findUnique({ where: { id } }); + } + + async findAll(): Promise { + return this.prisma.role.findMany({ orderBy: { level: 'desc' } }); + } +} diff --git a/packages/domain/src/sessions/dtos/create-session-dto.test.ts b/packages/domain/src/services/sessions/inputs/create-session-input.test.ts similarity index 53% rename from packages/domain/src/sessions/dtos/create-session-dto.test.ts rename to packages/domain/src/services/sessions/inputs/create-session-input.test.ts index 4958db78..ce2f7d66 100644 --- a/packages/domain/src/sessions/dtos/create-session-dto.test.ts +++ b/packages/domain/src/services/sessions/inputs/create-session-input.test.ts @@ -1,19 +1,19 @@ import { isValidUUIDV4 } from '@snipcode/utils'; -import { CreateSessionDto } from './create-session-dto'; -import { generateTestId } from '../../../tests/helpers'; -import { Session } from '../../entities/session'; +import { CreateSessionInput } from './create-session-input'; +import { TestHelper } from '../../../../tests/helpers'; +import { Session } from '../session.entity'; -describe('Test Create Session DTO', () => { +describe('Test Create Session Input', () => { it('should return a valid role object', () => { - const userId = generateTestId(); + const userId = TestHelper.generateTestId(); - const dto = new CreateSessionDto({ + const input = new CreateSessionInput({ expireDate: new Date(), userId, }); - const session = dto.toSession(); + const session = input.toSession(); expect(session).toMatchObject({ expires: expect.any(Date), diff --git a/packages/domain/src/sessions/dtos/create-session-dto.ts b/packages/domain/src/services/sessions/inputs/create-session-input.ts similarity index 79% rename from packages/domain/src/sessions/dtos/create-session-dto.ts rename to packages/domain/src/services/sessions/inputs/create-session-input.ts index 1202414f..d516083c 100644 --- a/packages/domain/src/sessions/dtos/create-session-dto.ts +++ b/packages/domain/src/services/sessions/inputs/create-session-input.ts @@ -1,14 +1,14 @@ import { generateRandomId } from '@snipcode/utils'; -import { Session } from '../../entities/session'; -import { dbID } from '../../utils/id'; +import { dbID } from '../../../utils/db-id'; +import { Session } from '../session.entity'; type Input = { expireDate: Date; userId: string; }; -export class CreateSessionDto { +export class CreateSessionInput { private readonly sessionId: string; private readonly token: string; diff --git a/packages/domain/src/entities/session.ts b/packages/domain/src/services/sessions/session.entity.ts similarity index 100% rename from packages/domain/src/entities/session.ts rename to packages/domain/src/services/sessions/session.entity.ts diff --git a/packages/domain/src/services/sessions/session.service.test.ts b/packages/domain/src/services/sessions/session.service.test.ts new file mode 100644 index 00000000..c1dc8b71 --- /dev/null +++ b/packages/domain/src/services/sessions/session.service.test.ts @@ -0,0 +1,75 @@ +import { Test, TestingModule } from '@nestjs/testing'; + +import { Session } from './session.entity'; +import { SessionService } from './session.service'; +import { TestHelper } from '../../../tests/helpers'; +import { DomainModule } from '../../domain.module'; +import { PrismaService } from '../../prisma.service'; + +describe('Test Session Service', function () { + let sessionService: SessionService; + let testHelper: TestHelper; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [ + DomainModule.forRootAsync({ + databaseUrl: process.env.TEST_DATABASE_URL, + }), + ], + providers: [SessionService], + }).compile(); + + sessionService = module.get(SessionService); + const prismaService = module.get(PrismaService); + + testHelper = new TestHelper(prismaService); + }); + + it('should create a session', async () => { + // GIVEN + const userId = TestHelper.generateTestId(); + const input = TestHelper.createTestSessionInput(userId); + + // WHEN + const sessionCreated = await sessionService.create(input); + + // THEN + expect(sessionCreated).toMatchObject(input.toSession()); + + await testHelper.deleteTestUserSessions(sessionCreated.userId); + }); + + it('should find a session by token', async () => { + // GIVEN + const userId = TestHelper.generateTestId(); + const session = await testHelper.createTestSession({ userId }); + + // WHEN + const sessionFound = await sessionService.findByToken(session.token); + + // THEN + expect(session).toEqual(sessionFound); + + await testHelper.deleteTestUserSessions(session.userId); + }); + + it('should delete all session of a user', async () => { + // GIVEN + const userId = TestHelper.generateTestId(); + + const sessionsCreated = await Promise.all([ + testHelper.createTestSession({ userId }), + testHelper.createTestSession({ userId }), + testHelper.createTestSession({ userId }), + ]); + + // WHEN + await sessionService.deleteUserSessions(userId); + + // THEN + const userSessions = await Promise.all(sessionsCreated.map(({ token }) => sessionService.findByToken(token))); + + expect(userSessions.every((session) => !session)).toBe(true); + }); +}); diff --git a/packages/domain/src/services/sessions/session.service.ts b/packages/domain/src/services/sessions/session.service.ts new file mode 100644 index 00000000..c27821db --- /dev/null +++ b/packages/domain/src/services/sessions/session.service.ts @@ -0,0 +1,31 @@ +import { Injectable } from '@nestjs/common'; + +import { CreateSessionInput } from './inputs/create-session-input'; +import { Session } from './session.entity'; +import { PrismaService } from '../../prisma.service'; + +@Injectable() +export class SessionService { + constructor(private readonly prisma: PrismaService) {} + + async create(createSessionInput: CreateSessionInput): Promise { + const input = createSessionInput.toSession(); + + return this.prisma.session.create({ + data: { + expires: input.expires, + id: input.id, + token: input.token, + userId: input.userId, + }, + }); + } + + async deleteUserSessions(userId: string): Promise { + await this.prisma.session.deleteMany({ where: { userId } }); + } + + async findByToken(token: string): Promise { + return this.prisma.session.findUnique({ where: { token } }); + } +} diff --git a/packages/domain/src/snippets/dtos/create-snippet-dto.test.ts b/packages/domain/src/services/snippets/inputs/create-snippet-input.test.ts similarity index 76% rename from packages/domain/src/snippets/dtos/create-snippet-dto.test.ts rename to packages/domain/src/services/snippets/inputs/create-snippet-input.test.ts index 3623e828..d9bdd802 100644 --- a/packages/domain/src/snippets/dtos/create-snippet-dto.test.ts +++ b/packages/domain/src/services/snippets/inputs/create-snippet-input.test.ts @@ -1,14 +1,14 @@ -import { CreateSnippetDto } from './create-snippet-dto'; -import { generateTestId } from '../../../tests/helpers'; -import { Snippet } from '../../entities/snippet'; +import { CreateSnippetInput } from './create-snippet-input'; +import { TestHelper } from '../../../../tests/helpers'; +import { Snippet } from '../snippet.entity'; -describe('Test Create Snippet DTO', () => { +describe('Test Create Snippet Input', () => { it('should return a valid snippet', () => { - const folderId = generateTestId(); - const userId = generateTestId(); + const folderId = TestHelper.generateTestId(); + const userId = TestHelper.generateTestId(); // GIVEN - const dto = new CreateSnippetDto({ + const input = new CreateSnippetInput({ content: 'import React from "react";\n\nexport const App = () => {\n\n\treturn(\n\t\t
Hello
\n\t);\n};', contentHighlighted: 'import React from "react";\n\nexport const App = () => {\n\n\treturn(\n\t\t
Hello
\n\t);\n}; highlighted', @@ -23,7 +23,7 @@ describe('Test Create Snippet DTO', () => { }); // WHEN - const folder = dto.toSnippet(); + const folder = input.toSnippet(); // THEN expect(folder).toMatchObject({ diff --git a/packages/domain/src/snippets/dtos/create-snippet-dto.ts b/packages/domain/src/services/snippets/inputs/create-snippet-input.ts similarity index 89% rename from packages/domain/src/snippets/dtos/create-snippet-dto.ts rename to packages/domain/src/services/snippets/inputs/create-snippet-input.ts index 05a12182..ff81f8cf 100644 --- a/packages/domain/src/snippets/dtos/create-snippet-dto.ts +++ b/packages/domain/src/services/snippets/inputs/create-snippet-input.ts @@ -1,5 +1,5 @@ -import { Snippet, SnippetVisibility } from '../../entities/snippet'; -import { dbID } from '../../utils/id'; +import { dbID } from '../../../utils/db-id'; +import { Snippet, SnippetVisibility } from '../snippet.entity'; type Input = { content: string; @@ -14,7 +14,7 @@ type Input = { visibility: SnippetVisibility; }; -export class CreateSnippetDto { +export class CreateSnippetInput { private readonly snippetId: string; constructor(private _input: Input) { diff --git a/packages/domain/src/services/snippets/inputs/delete-snippet-input.test.ts b/packages/domain/src/services/snippets/inputs/delete-snippet-input.test.ts new file mode 100644 index 00000000..86f3993f --- /dev/null +++ b/packages/domain/src/services/snippets/inputs/delete-snippet-input.test.ts @@ -0,0 +1,20 @@ +import { DeleteSnippetInput } from './delete-snippet-input'; +import { TestHelper } from '../../../../tests/helpers'; + +describe('Test Delete Snippet Input', () => { + it('should have the right property defined', () => { + const snippetId = TestHelper.generateTestId(); + const userId = TestHelper.generateTestId(); + + // GIVEN + const input = new DeleteSnippetInput({ + creatorId: userId, + snippetId, + }); + + // WHEN + // THEN + expect(input.snippetId).toEqual(snippetId); + expect(input.creatorId).toEqual(userId); + }); +}); diff --git a/packages/domain/src/snippets/dtos/delete-snippet-dto.ts b/packages/domain/src/services/snippets/inputs/delete-snippet-input.ts similarity index 87% rename from packages/domain/src/snippets/dtos/delete-snippet-dto.ts rename to packages/domain/src/services/snippets/inputs/delete-snippet-input.ts index 1d7f38c6..41e21e2e 100644 --- a/packages/domain/src/snippets/dtos/delete-snippet-dto.ts +++ b/packages/domain/src/services/snippets/inputs/delete-snippet-input.ts @@ -3,7 +3,7 @@ type Input = { snippetId: string; }; -export class DeleteSnippetDto { +export class DeleteSnippetInput { constructor(private _input: Input) {} get snippetId(): string { diff --git a/packages/domain/src/snippets/dtos/update-snippet-dto.test.ts b/packages/domain/src/services/snippets/inputs/update-snippet-input.test.ts similarity index 74% rename from packages/domain/src/snippets/dtos/update-snippet-dto.test.ts rename to packages/domain/src/services/snippets/inputs/update-snippet-input.test.ts index 9f37be3e..014d124c 100644 --- a/packages/domain/src/snippets/dtos/update-snippet-dto.test.ts +++ b/packages/domain/src/services/snippets/inputs/update-snippet-input.test.ts @@ -1,13 +1,13 @@ -import { UpdateSnippetDto } from './update-snippet-dto'; -import { createTestSnippetDto, generateTestId } from '../../../tests/helpers'; -import { Snippet } from '../../entities/snippet'; +import { UpdateSnippetInput } from './update-snippet-input'; +import { TestHelper } from '../../../../tests/helpers'; +import { Snippet } from '../snippet.entity'; -describe('Test Update Snippet DTO', () => { +describe('Test Update Snippet Input', () => { it('should have the right property defined to update the snippet', () => { - const snippetId = generateTestId(); - const userId = generateTestId(); + const snippetId = TestHelper.generateTestId(); + const userId = TestHelper.generateTestId(); - const dto = new UpdateSnippetDto({ + const input = new UpdateSnippetInput({ content: 'import React from "react";\n\nexport const App = () => {\n\n\treturn(\n\t\t
Hello Updated
\n\t);\n};', contentHighlighted: @@ -22,15 +22,15 @@ describe('Test Update Snippet DTO', () => { visibility: 'private', }); - const folderId = generateTestId(); - const currentSnippet = createTestSnippetDto({ + const folderId = TestHelper.generateTestId(); + const currentSnippet = TestHelper.createTestSnippetInput({ folderId, name: 'app.tsx', userId, visibility: 'public', }).toSnippet(); - const snippetToUpdate = dto.toSnippet(currentSnippet); + const snippetToUpdate = input.toSnippet(currentSnippet); const expectedSnippet: Snippet = { content: diff --git a/packages/domain/src/snippets/dtos/update-snippet-dto.ts b/packages/domain/src/services/snippets/inputs/update-snippet-input.ts similarity index 91% rename from packages/domain/src/snippets/dtos/update-snippet-dto.ts rename to packages/domain/src/services/snippets/inputs/update-snippet-input.ts index 466cba2f..9fb02e2b 100644 --- a/packages/domain/src/snippets/dtos/update-snippet-dto.ts +++ b/packages/domain/src/services/snippets/inputs/update-snippet-input.ts @@ -1,4 +1,4 @@ -import { Snippet, SnippetVisibility } from '../../entities/snippet'; +import { Snippet, SnippetVisibility } from '../snippet.entity'; type Input = { content: string; @@ -13,7 +13,7 @@ type Input = { visibility: SnippetVisibility; }; -export class UpdateSnippetDto { +export class UpdateSnippetInput { constructor(private _input: Input) {} get name(): string { diff --git a/packages/domain/src/entities/snippet.ts b/packages/domain/src/services/snippets/snippet.entity.ts similarity index 100% rename from packages/domain/src/entities/snippet.ts rename to packages/domain/src/services/snippets/snippet.entity.ts diff --git a/packages/domain/src/services/snippets/snippet.service.test.ts b/packages/domain/src/services/snippets/snippet.service.test.ts new file mode 100644 index 00000000..7b795e63 --- /dev/null +++ b/packages/domain/src/services/snippets/snippet.service.test.ts @@ -0,0 +1,352 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { SnipcodeError, errors, generateRandomId } from '@snipcode/utils'; + +import { Snippet } from './snippet.entity'; +import { SnippetService } from './snippet.service'; +import { TestHelper } from '../../../tests/helpers'; +import { DomainModule } from '../../domain.module'; +import { PrismaService } from '../../prisma.service'; +import { RoleService } from '../roles/role.service'; + +describe('Test Snippet service', () => { + let snippetService: SnippetService; + let roleService: RoleService; + let testHelper: TestHelper; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [ + DomainModule.forRootAsync({ + databaseUrl: process.env.TEST_DATABASE_URL, + }), + ], + providers: [RoleService, SnippetService], + }).compile(); + + snippetService = module.get(SnippetService); + roleService = module.get(RoleService); + + const prismaService = module.get(PrismaService); + + testHelper = new TestHelper(prismaService); + + await roleService.loadRoles(); + }); + + it('should create a snippet in the specified folder', async () => { + // GIVEN + const [user, rootFolder] = await testHelper.createUserWithRootFolder(); + const createSnippetInput = TestHelper.createTestSnippetInput({ folderId: rootFolder.id, userId: user.id }); + + // WHEN + const expectedSnippet = await snippetService.create(createSnippetInput); + + // THEN + expect(expectedSnippet).toMatchObject({ + content: createSnippetInput.toSnippet().content, + contentHtml: createSnippetInput.toSnippet().contentHtml, + createdAt: expect.any(Date), + description: createSnippetInput.toSnippet().description, + folderId: rootFolder.id, + id: createSnippetInput.toSnippet().id, + language: createSnippetInput.toSnippet().language, + lineHighlight: createSnippetInput.toSnippet().lineHighlight, + name: createSnippetInput.toSnippet().name, + size: createSnippetInput.toSnippet().size, + theme: createSnippetInput.toSnippet().theme, + updatedAt: expect.any(Date), + userId: user.id, + visibility: createSnippetInput.toSnippet().visibility, + }); + + await testHelper.deleteTestSnippetsById([expectedSnippet.id]); + await testHelper.deleteTestFoldersById([rootFolder.id]); + await testHelper.deleteTestUsersById([user.id]); + }); + + it('should not create a snippet because it already exists in the specified folder', async () => { + // GIVEN + const [user, rootFolder] = await testHelper.createUserWithRootFolder(); + const snippet = await testHelper.createTestSnippet({ folderId: rootFolder.id, name: 'app.tsx', userId: user.id }); + + const sameCreateSnippetInput = TestHelper.createTestSnippetInput({ + folderId: rootFolder.id, + name: 'app.tsx', + userId: user.id, + }); + + // WHEN + // THEN + await expect(() => snippetService.create(sameCreateSnippetInput)).rejects.toThrow( + new SnipcodeError(errors.SNIPPET_ALREADY_EXIST(sameCreateSnippetInput.name), 'SNIPPET_ALREADY_EXIST'), + ); + + await testHelper.deleteTestSnippetsById([snippet.id]); + await testHelper.deleteTestFoldersById([rootFolder.id]); + await testHelper.deleteTestUsersById([user.id]); + }); + + it('should retrieve all public snippets', async () => { + // GIVEN + const [user, rootFolder] = await testHelper.createUserWithRootFolder(); + const existingSnippets = await Promise.all([ + testHelper.createTestSnippet({ folderId: rootFolder.id, userId: user.id, visibility: 'public' }), + testHelper.createTestSnippet({ folderId: rootFolder.id, userId: user.id, visibility: 'private' }), + testHelper.createTestSnippet({ folderId: rootFolder.id, userId: user.id, visibility: 'public' }), + testHelper.createTestSnippet({ folderId: rootFolder.id, userId: user.id, visibility: 'private' }), + testHelper.createTestSnippet({ folderId: rootFolder.id, userId: user.id, visibility: 'public' }), + testHelper.createTestSnippet({ folderId: rootFolder.id, userId: user.id, visibility: 'public' }), + ]); + + // WHEN + const publicSnippets = await snippetService.findPublicSnippet({ itemPerPage: 10 }); + + // THEN + await expect(publicSnippets.hasMore).toEqual(false); + await expect(publicSnippets.nextCursor).toEqual(null); + await expect(publicSnippets.items).toHaveLength(4); + + await testHelper.deleteTestSnippetsById(existingSnippets.map((snippet) => snippet.id)); + await testHelper.deleteTestFoldersById([rootFolder.id]); + await testHelper.deleteTestUsersById([user.id]); + }); + + it('should retrieve a subset of public snippets', async () => { + // GIVEN + const [user, rootFolder] = await testHelper.createUserWithRootFolder(); + const existingSnippets = await Promise.all([ + testHelper.createTestSnippet({ folderId: rootFolder.id, userId: user.id, visibility: 'public' }), + testHelper.createTestSnippet({ folderId: rootFolder.id, userId: user.id, visibility: 'private' }), + testHelper.createTestSnippet({ folderId: rootFolder.id, userId: user.id, visibility: 'public' }), + testHelper.createTestSnippet({ folderId: rootFolder.id, userId: user.id, visibility: 'private' }), + testHelper.createTestSnippet({ folderId: rootFolder.id, userId: user.id, visibility: 'public' }), + testHelper.createTestSnippet({ folderId: rootFolder.id, userId: user.id, visibility: 'public' }), + ]); + + // WHEN + const publicSnippets = await snippetService.findPublicSnippet({ itemPerPage: 3 }); + + // THEN + await expect(publicSnippets.hasMore).toEqual(true); + await expect(publicSnippets.nextCursor).toEqual(expect.any(String)); + await expect(publicSnippets.items).toHaveLength(3); + + await testHelper.deleteTestSnippetsById(existingSnippets.map((snippet) => snippet.id)); + await testHelper.deleteTestFoldersById([rootFolder.id]); + await testHelper.deleteTestUsersById([user.id]); + }); + + it('should find all snippets of a user', async () => { + // GIVEN + const [user1, rootFolder1] = await testHelper.createUserWithRootFolder(); + const [user2, rootFolder2] = await testHelper.createUserWithRootFolder(); + + const existingSnippets = await Promise.all([ + testHelper.createTestSnippet({ folderId: rootFolder1.id, userId: user1.id, visibility: 'public' }), + testHelper.createTestSnippet({ folderId: rootFolder1.id, userId: user1.id, visibility: 'private' }), + testHelper.createTestSnippet({ folderId: rootFolder2.id, userId: user2.id, visibility: 'public' }), + testHelper.createTestSnippet({ folderId: rootFolder1.id, userId: user1.id, visibility: 'private' }), + testHelper.createTestSnippet({ folderId: rootFolder2.id, userId: user2.id, visibility: 'public' }), + testHelper.createTestSnippet({ folderId: rootFolder2.id, userId: user2.id, visibility: 'private' }), + ]); + + // WHEN + const userSnippets = await snippetService.findByUser(user2.id); + + // THEN + await expect(userSnippets).toHaveLength(3); + + await testHelper.deleteTestSnippetsById(existingSnippets.map((snippet) => snippet.id)); + await testHelper.deleteTestFoldersById([rootFolder1.id, rootFolder2.id]); + await testHelper.deleteTestUsersById([user1.id, user2.id]); + }); + + it('should retrieve a snippet by its ID', async () => { + // GIVEN + const [user1, rootFolder1] = await testHelper.createUserWithRootFolder(); + + const snippet = await testHelper.createTestSnippet({ + folderId: rootFolder1.id, + userId: user1.id, + visibility: 'public', + }); + + // WHEN + const snippetFound = await snippetService.findById(snippet.id); + + // THEN + expect(snippetFound).toMatchObject({ + folderId: rootFolder1.id, + id: snippet.id, + userId: user1.id, + visibility: 'public', + }); + + await testHelper.deleteTestSnippetsById([snippet.id]); + await testHelper.deleteTestFoldersById([rootFolder1.id]); + await testHelper.deleteTestUsersById([user1.id]); + }); + + it('should found no snippet given the ID provided', async () => { + // GIVEN + const snippetId = generateRandomId(); + + // WHEN + // THEN + await expect(async () => { + await snippetService.findById(snippetId); + }).rejects.toThrow(new SnipcodeError(errors.SNIPPET_NOT_FOUND(snippetId), 'SNIPPET_NOT_FOUND')); + }); + + it('should delete an existing snippet belonging to a user', async () => { + // GIVEN + const [user, rootFolder] = await testHelper.createUserWithRootFolder(); + + const [snippet1, snippet2] = await Promise.all([ + testHelper.createTestSnippet({ folderId: rootFolder.id, userId: user.id, visibility: 'public' }), + testHelper.createTestSnippet({ folderId: rootFolder.id, userId: user.id, visibility: 'private' }), + ]); + + // WHEN + const deleteSnippetInput = TestHelper.deleteTestSnippetInput({ snippetId: snippet1.id, userId: snippet1.userId }); + + await snippetService.delete(deleteSnippetInput); + + // THEN + const folderSnippets = await snippetService.findByFolder(rootFolder.id); + + expect(folderSnippets).toHaveLength(1); + + await testHelper.deleteTestSnippetsById([snippet2.id]); + await testHelper.deleteTestFoldersById([rootFolder.id]); + await testHelper.deleteTestUsersById([user.id]); + }); + + it('should not delete an existing snippet because it belongs to other user', async () => { + // GIVEN + const [user1, rootFolder1] = await testHelper.createUserWithRootFolder(); + const [user2, rootFolder2] = await testHelper.createUserWithRootFolder(); + + const [snippet1, snippet2, snippet3] = await Promise.all([ + testHelper.createTestSnippet({ folderId: rootFolder1.id, userId: user1.id, visibility: 'public' }), + testHelper.createTestSnippet({ folderId: rootFolder2.id, userId: user2.id, visibility: 'private' }), + testHelper.createTestSnippet({ folderId: rootFolder1.id, userId: user1.id, visibility: 'private' }), + ]); + + // WHEN + const deleteSnippetInput = TestHelper.deleteTestSnippetInput({ snippetId: snippet1.id, userId: user2.id }); + + // THEN + await expect(async () => { + await snippetService.delete(deleteSnippetInput); + }).rejects.toThrow( + new SnipcodeError(errors.CANT_EDIT_SNIPPET(deleteSnippetInput.creatorId, snippet1.id), 'CANT_EDIT_SNIPPET'), + ); + + const user1FolderSnippets = await snippetService.findByFolder(rootFolder1.id); + const user2FolderSnippets = await snippetService.findByFolder(rootFolder2.id); + + expect(user1FolderSnippets).toHaveLength(2); + expect(user2FolderSnippets).toHaveLength(1); + + await testHelper.deleteTestSnippetsById([snippet2.id, snippet3.id]); + await testHelper.deleteTestFoldersById([rootFolder1.id, rootFolder2.id]); + await testHelper.deleteTestUsersById([user1.id, user2.id]); + }); + + it('should update an existing snippet in the specified folder', async () => { + // GIVEN + const [user, rootFolder] = await testHelper.createUserWithRootFolder(); + const snippet = await testHelper.createTestSnippet({ + folderId: rootFolder.id, + userId: user.id, + visibility: 'public', + }); + + const updateSnippetInput = TestHelper.updateTestSnippetInput({ snippetId: snippet.id, userId: user.id }); + + // WHEN + const updatedSnippet = await snippetService.update(updateSnippetInput); + + // THEN + const snippetToUpdate = updateSnippetInput.toSnippet(snippet); + + expect(updatedSnippet).toMatchObject({ + content: snippetToUpdate.content, + contentHtml: snippetToUpdate.contentHtml, + createdAt: expect.any(Date), + description: snippetToUpdate.description, + folderId: rootFolder.id, + id: snippet.id, + language: snippetToUpdate.language, + lineHighlight: snippetToUpdate.lineHighlight, + name: snippetToUpdate.name, + size: snippetToUpdate.size, + theme: snippetToUpdate.theme, + updatedAt: expect.any(Date), + userId: user.id, + visibility: snippetToUpdate.visibility, + }); + + await testHelper.deleteTestSnippetsById([updatedSnippet.id]); + await testHelper.deleteTestFoldersById([rootFolder.id]); + await testHelper.deleteTestUsersById([user.id]); + }); + + it('should not update an existing snippet in the specified folder because another snippet with the updated name already exists in the folder', async () => { + // GIVEN + const [user, rootFolder] = await testHelper.createUserWithRootFolder(); + const [snippet] = await Promise.all([ + testHelper.createTestSnippet({ folderId: rootFolder.id, name: 'snippet-one.java', userId: user.id }), + testHelper.createTestSnippet({ + folderId: rootFolder.id, + name: 'snippet-two.java', + userId: user.id, + visibility: 'private', + }), + ]); + + const updateSnippetInput = TestHelper.updateTestSnippetInput({ + name: 'snippet-two.java', + snippetId: snippet.id, + userId: user.id, + }); + + // WHEN + // THEN + await expect(async () => { + await snippetService.update(updateSnippetInput); + }).rejects.toThrow( + new SnipcodeError(errors.SNIPPET_ALREADY_EXIST(updateSnippetInput.name), 'SNIPPET_ALREADY_EXIST'), + ); + + await testHelper.deleteTestSnippetsById([snippet.id]); + await testHelper.deleteTestFoldersById([rootFolder.id]); + await testHelper.deleteTestUsersById([user.id]); + }); + + it('should not update an existing snippet in the specified folder because because it belongs to other user', async () => { + // GIVEN + const [user1, rootFolder1] = await testHelper.createUserWithRootFolder(); + const [user2, rootFolder2] = await testHelper.createUserWithRootFolder(); + const snippet = await testHelper.createTestSnippet({ + folderId: rootFolder1.id, + name: 'snippet-one.java', + userId: user1.id, + }); + + const updateSnippetInput = TestHelper.updateTestSnippetInput({ snippetId: snippet.id, userId: user2.id }); + + // WHEN + // THEN + await expect(async () => { + await snippetService.update(updateSnippetInput); + }).rejects.toThrow( + new SnipcodeError(errors.CANT_EDIT_SNIPPET(updateSnippetInput.creatorId, snippet.id), 'CANT_EDIT_SNIPPET'), + ); + + await testHelper.deleteTestSnippetsById([snippet.id]); + await testHelper.deleteTestFoldersById([rootFolder1.id, rootFolder2.id]); + await testHelper.deleteTestUsersById([user1.id, user2.id]); + }); +}); diff --git a/packages/domain/src/snippets/snippet.service.ts b/packages/domain/src/services/snippets/snippet.service.ts similarity index 64% rename from packages/domain/src/snippets/snippet.service.ts rename to packages/domain/src/services/snippets/snippet.service.ts index 9932ac98..b3d88566 100644 --- a/packages/domain/src/snippets/snippet.service.ts +++ b/packages/domain/src/services/snippets/snippet.service.ts @@ -1,10 +1,11 @@ -import SnipcodeError, { errors } from '@snipcode/utils'; +import { Injectable } from '@nestjs/common'; +import { SnipcodeError, errors } from '@snipcode/utils'; -import { CreateSnippetDto } from './dtos/create-snippet-dto'; -import { DeleteSnippetDto } from './dtos/delete-snippet-dto'; -import { UpdateSnippetDto } from './dtos/update-snippet-dto'; -import { Snippet, SnippetVisibility } from '../entities/snippet'; -import { prisma } from '../utils/prisma'; +import { CreateSnippetInput } from './inputs/create-snippet-input'; +import { DeleteSnippetInput } from './inputs/delete-snippet-input'; +import { UpdateSnippetInput } from './inputs/update-snippet-input'; +import { Snippet, SnippetVisibility } from './snippet.entity'; +import { PrismaService } from '../../prisma.service'; const MAX_ITEM_PER_PAGE = 50; @@ -13,17 +14,20 @@ const sortMethodMap: Record<'recently_updated' | 'recently_created', 'createdAt' recently_updated: 'updatedAt', }; +@Injectable() export class SnippetService { - async create(createSnippetDto: CreateSnippetDto): Promise { - const isSnippetExist = await this.isSnippetExistInFolder(createSnippetDto.folderId, createSnippetDto.name); + constructor(private readonly prisma: PrismaService) {} + + async create(createSnippetInput: CreateSnippetInput): Promise { + const isSnippetExist = await this.isSnippetExistInFolder(createSnippetInput.folderId, createSnippetInput.name); if (isSnippetExist) { - throw new SnipcodeError(errors.SNIPPET_ALREADY_EXIST(createSnippetDto.name), 'SNIPPET_ALREADY_EXIST'); + throw new SnipcodeError(errors.SNIPPET_ALREADY_EXIST(createSnippetInput.name), 'SNIPPET_ALREADY_EXIST'); } - const input = createSnippetDto.toSnippet(); + const input = createSnippetInput.toSnippet(); - return prisma.snippet.create({ + return this.prisma.snippet.create({ data: { content: input.content, contentHtml: input.contentHtml, @@ -42,7 +46,7 @@ export class SnippetService { } async findById(id: string): Promise { - const snippet = await prisma.snippet.findUnique({ where: { id } }); + const snippet = await this.prisma.snippet.findUnique({ where: { id } }); if (!snippet) { throw new SnipcodeError(errors.SNIPPET_NOT_FOUND(id), 'SNIPPET_NOT_FOUND'); @@ -52,11 +56,11 @@ export class SnippetService { } async findByUser(userId: string): Promise { - return prisma.snippet.findMany({ orderBy: { createdAt: 'desc' }, where: { userId } }); + return this.prisma.snippet.findMany({ orderBy: { createdAt: 'desc' }, where: { userId } }); } async findByFolder(folderId: string, visibility?: SnippetVisibility): Promise { - return prisma.snippet.findMany({ + return this.prisma.snippet.findMany({ orderBy: { createdAt: 'desc' }, where: { folderId, @@ -78,7 +82,7 @@ export class SnippetService { // If the use has 20 we fetch 21 which help to know if there still more in the table const limitPlusOne = limit + 1; - const snippets = await prisma.snippet.findMany({ + const snippets = await this.prisma.snippet.findMany({ orderBy: { [sortMethodMap[sortMethod]]: 'desc' }, take: limitPlusOne, where: { @@ -102,14 +106,14 @@ export class SnippetService { }; } - async delete(deleteSnippetDto: DeleteSnippetDto): Promise { - const snippet = await this.findById(deleteSnippetDto.snippetId); + async delete(deleteSnippetInput: DeleteSnippetInput): Promise { + const snippet = await this.findById(deleteSnippetInput.snippetId); - if (snippet.userId !== deleteSnippetDto.creatorId) { - throw new SnipcodeError(errors.CANT_EDIT_SNIPPET(deleteSnippetDto.creatorId, snippet.id), 'CANT_EDIT_SNIPPET'); + if (snippet.userId !== deleteSnippetInput.creatorId) { + throw new SnipcodeError(errors.CANT_EDIT_SNIPPET(deleteSnippetInput.creatorId, snippet.id), 'CANT_EDIT_SNIPPET'); } - await prisma.snippet.delete({ where: { id: deleteSnippetDto.snippetId } }); + await this.prisma.snippet.delete({ where: { id: deleteSnippetInput.snippetId } }); } async isSnippetExistInFolder(folderId: string, snippetName: string): Promise { @@ -118,24 +122,24 @@ export class SnippetService { return snippets.some(({ name }) => name.toLowerCase() === snippetName.toLowerCase()); } - async update(updateSnippetDto: UpdateSnippetDto): Promise { - const snippet = await this.findById(updateSnippetDto.snippetId); + async update(updateSnippetInput: UpdateSnippetInput): Promise { + const snippet = await this.findById(updateSnippetInput.snippetId); - if (snippet.userId !== updateSnippetDto.creatorId) { - throw new SnipcodeError(errors.CANT_EDIT_SNIPPET(updateSnippetDto.creatorId, snippet.id), 'CANT_EDIT_SNIPPET'); + if (snippet.userId !== updateSnippetInput.creatorId) { + throw new SnipcodeError(errors.CANT_EDIT_SNIPPET(updateSnippetInput.creatorId, snippet.id), 'CANT_EDIT_SNIPPET'); } - if (snippet.name !== updateSnippetDto.name) { - const isSnippetExist = await this.isSnippetExistInFolder(snippet.folderId, updateSnippetDto.name); + if (snippet.name !== updateSnippetInput.name) { + const isSnippetExist = await this.isSnippetExistInFolder(snippet.folderId, updateSnippetInput.name); if (isSnippetExist) { - throw new SnipcodeError(errors.SNIPPET_ALREADY_EXIST(updateSnippetDto.name), 'SNIPPET_ALREADY_EXIST'); + throw new SnipcodeError(errors.SNIPPET_ALREADY_EXIST(updateSnippetInput.name), 'SNIPPET_ALREADY_EXIST'); } } - const input = updateSnippetDto.toSnippet(snippet); + const input = updateSnippetInput.toSnippet(snippet); - return prisma.snippet.update({ + return this.prisma.snippet.update({ data: { content: input.content, contentHtml: input.contentHtml, diff --git a/packages/domain/src/users/dtos/create-user-dto.test.ts b/packages/domain/src/services/users/inputs/create-user-input.test.ts similarity index 81% rename from packages/domain/src/users/dtos/create-user-dto.test.ts rename to packages/domain/src/services/users/inputs/create-user-input.test.ts index 81067f50..2442a052 100644 --- a/packages/domain/src/users/dtos/create-user-dto.test.ts +++ b/packages/domain/src/services/users/inputs/create-user-input.test.ts @@ -1,9 +1,9 @@ -import { CreateUserDto } from './create-user-dto'; -import { User } from '../../entities/user'; +import { CreateUserInput } from './create-user-input'; +import { User } from '../user.entity'; -describe('Test Create User DTO', () => { +describe('Test Create User Input', () => { it('should return a valid user object without password', () => { - const dto = new CreateUserDto({ + const input = new CreateUserInput({ email: 'user@email.com', name: 'user admin', oauthProvider: 'github', @@ -13,7 +13,7 @@ describe('Test Create User DTO', () => { username: 'shuser', }); - const user = dto.toUser(); + const user = input.toUser(); const expectedUser: User = { createdAt: expect.any(Date), @@ -34,7 +34,7 @@ describe('Test Create User DTO', () => { }); it('should return a valid user object with a password', () => { - const dto = new CreateUserDto({ + const input = new CreateUserInput({ email: 'user@email.com', name: 'user admin', oauthProvider: 'email', @@ -45,7 +45,7 @@ describe('Test Create User DTO', () => { username: 'shuser', }); - const user = dto.toUser(); + const user = input.toUser(); const expectedUser: User = { createdAt: expect.any(Date), @@ -54,7 +54,7 @@ describe('Test Create User DTO', () => { isEnabled: false, name: 'user admin', oauthProvider: 'email', - password: dto.hashedPassword, + password: input.hashedPassword, pictureUrl: 'pictureUrl', roleId: 'roleId', timezone: 'Europe/Paris', diff --git a/packages/domain/src/users/dtos/create-user-dto.ts b/packages/domain/src/services/users/inputs/create-user-input.ts similarity index 86% rename from packages/domain/src/users/dtos/create-user-dto.ts rename to packages/domain/src/services/users/inputs/create-user-input.ts index 96d4ff62..a5f8e630 100644 --- a/packages/domain/src/users/dtos/create-user-dto.ts +++ b/packages/domain/src/services/users/inputs/create-user-input.ts @@ -1,6 +1,6 @@ -import { OauthProvider, User } from '../../entities/user'; -import { hashPassword } from '../../utils/helpers'; -import { dbID } from '../../utils/id'; +import { dbID } from '../../../utils/db-id'; +import { hashPassword } from '../../../utils/helpers'; +import { OauthProvider, User } from '../user.entity'; type Input = { email: string; @@ -13,7 +13,7 @@ type Input = { username: string | null; }; -export class CreateUserDto { +export class CreateUserInput { readonly hashedPassword: string | null; private readonly userId: string; diff --git a/packages/domain/src/users/dtos/update-user-dto.test.ts b/packages/domain/src/services/users/inputs/update-user-input.test.ts similarity index 68% rename from packages/domain/src/users/dtos/update-user-dto.test.ts rename to packages/domain/src/services/users/inputs/update-user-input.test.ts index 06a662e9..f16922a2 100644 --- a/packages/domain/src/users/dtos/update-user-dto.test.ts +++ b/packages/domain/src/services/users/inputs/update-user-input.test.ts @@ -1,10 +1,10 @@ -import { UpdateUserDto } from './update-user-dto'; -import { createTestUserDto, generateTestId } from '../../../tests/helpers'; -import { User } from '../../entities/user'; +import { UpdateUserInput } from './update-user-input'; +import { TestHelper } from '../../../../tests/helpers'; +import { User } from '../user.entity'; -describe('Test Update User DTO', () => { +describe('Test Update User Input', () => { it('should return the user to update', () => { - const dto = new UpdateUserDto({ + const input = new UpdateUserInput({ name: 'user updated', oauthProvider: 'github', pictureUrl: 'updated pictureUrl', @@ -12,10 +12,10 @@ describe('Test Update User DTO', () => { timezone: 'Europe/Paris', }); - const roleId = generateTestId(); - const currentUser = createTestUserDto({ roleId }).toUser(); + const roleId = TestHelper.generateTestId(); + const currentUser = TestHelper.createTestUserInput({ roleId }).toUser(); - const userToUpdate = dto.toUser(currentUser); + const userToUpdate = input.toUser(currentUser); const expectedUser: User = { createdAt: currentUser.createdAt, @@ -36,7 +36,7 @@ describe('Test Update User DTO', () => { }); it('should return the user to update - validation check', () => { - const dto = new UpdateUserDto({ + const input = new UpdateUserInput({ name: 'user updated', oauthProvider: 'github', pictureUrl: 'updated pictureUrl', @@ -44,11 +44,11 @@ describe('Test Update User DTO', () => { timezone: 'Europe/Paris', }); - const roleId = generateTestId(); - const currentUser = createTestUserDto({ roleId }).toUser(); + const roleId = TestHelper.generateTestId(); + const currentUser = TestHelper.createTestUserInput({ roleId }).toUser(); - dto.isEnabled = true; - const userToUpdate = dto.toUser(currentUser); + input.isEnabled = true; + const userToUpdate = input.toUser(currentUser); const expectedUser: User = { createdAt: currentUser.createdAt, diff --git a/packages/domain/src/users/dtos/update-user-dto.ts b/packages/domain/src/services/users/inputs/update-user-input.ts similarity index 87% rename from packages/domain/src/users/dtos/update-user-dto.ts rename to packages/domain/src/services/users/inputs/update-user-input.ts index 135f2745..6f605a10 100644 --- a/packages/domain/src/users/dtos/update-user-dto.ts +++ b/packages/domain/src/services/users/inputs/update-user-input.ts @@ -1,4 +1,4 @@ -import { OauthProvider, User } from '../../entities/user'; +import { OauthProvider, User } from '../user.entity'; type Input = { name: string; @@ -8,7 +8,7 @@ type Input = { timezone: string | null; }; -export class UpdateUserDto { +export class UpdateUserInput { private enabled = false; constructor(private _input: Input) {} diff --git a/packages/domain/src/entities/user.ts b/packages/domain/src/services/users/user.entity.ts similarity index 100% rename from packages/domain/src/entities/user.ts rename to packages/domain/src/services/users/user.entity.ts diff --git a/packages/domain/src/services/users/user.service.test.ts b/packages/domain/src/services/users/user.service.test.ts new file mode 100644 index 00000000..e863673a --- /dev/null +++ b/packages/domain/src/services/users/user.service.test.ts @@ -0,0 +1,268 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { SnipcodeError, errors, generateRandomId } from '@snipcode/utils'; + +import { User } from './user.entity'; +import { UserService } from './user.service'; +import { TestHelper } from '../../../tests/helpers'; +import { DomainModule } from '../../domain.module'; +import { PrismaService } from '../../prisma.service'; +import { RoleService } from '../roles/role.service'; + +describe('Test User service', () => { + let userService: UserService; + let roleService: RoleService; + let testHelper: TestHelper; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [ + DomainModule.forRootAsync({ + databaseUrl: process.env.TEST_DATABASE_URL, + }), + ], + providers: [UserService, RoleService], + }).compile(); + + userService = module.get(UserService); + roleService = module.get(RoleService); + + const prismaService = module.get(PrismaService); + + testHelper = new TestHelper(prismaService); + + await roleService.loadRoles(); + }); + + it('should load users in the database', async () => { + const [roleAdmin] = await roleService.findAll(); + const adminPassword = 'VerStrongPassword'; + + await userService.loadAdminUser(roleAdmin, adminPassword); + + const adminUser = await userService.findByEmail('teco@snipcode.dev'); + + expect(adminUser).toBeDefined(); + + await testHelper.deleteTestUsersById([adminUser?.id]); + }); + + it('should create a user', async () => { + // GIVEN + const role = await testHelper.findTestRole('user'); + const createUserInput = TestHelper.createTestUserInput({ roleId: role.id }); + + // WHEN + const createdUser = await userService.create(createUserInput); + + // THEN + expect(createdUser).toMatchObject({ + createdAt: expect.any(Date), + email: createUserInput.email, + id: createUserInput.toUser().id, + isEnabled: createUserInput.toUser().isEnabled, + name: createUserInput.toUser().name, + oauthProvider: createUserInput.toUser().oauthProvider, + password: null, + pictureUrl: createUserInput.toUser().pictureUrl, + roleId: createUserInput.toUser().roleId, + timezone: createUserInput.toUser().timezone, + updatedAt: expect.any(Date), + username: createUserInput.toUser().username, + }); + + await testHelper.deleteTestUsersById([createdUser.id]); + }); + + it('should create a user with no username', async () => { + // GIVEN + const role = await testHelper.findTestRole('user'); + const createUserInput = TestHelper.createTestUserInput({ roleId: role.id, username: null }); + + // WHEN + const createdUser = await userService.create(createUserInput); + + // THEN + expect(createdUser).toMatchObject({ + createdAt: expect.any(Date), + email: createUserInput.email, + id: createUserInput.toUser().id, + isEnabled: createUserInput.toUser().isEnabled, + name: createUserInput.toUser().name, + oauthProvider: createUserInput.toUser().oauthProvider, + password: null, + pictureUrl: createUserInput.toUser().pictureUrl, + roleId: createUserInput.toUser().roleId, + timezone: createUserInput.toUser().timezone, + updatedAt: expect.any(Date), + username: expect.any(String), + }); + + await testHelper.deleteTestUsersById([createdUser.id]); + }); + + it('should create a user with a username that already exists', async () => { + // GIVEN + const role = await testHelper.findTestRole('user'); + const user = await testHelper.createTestUser({ username: 'roloto' }); + + const createUserInput = await TestHelper.createTestUserInput({ roleId: role.id, username: 'roloto' }); + + // WHEN + const createdUser = await userService.create(createUserInput); + + // THEN + expect(createdUser.username).not.toEqual('roloto'); + + await testHelper.deleteTestUsersById([user.id, createdUser.id]); + }); + + it('should create a user - validation check', async () => { + // GIVEN + const role = await testHelper.findTestRole('user'); + const createUserInput = TestHelper.createTestUserInput({ roleId: role.id }); + + createUserInput.isEnabled = true; + + // WHEN + const createdUser = await userService.create(createUserInput); + + // THEN + expect(createdUser).toMatchObject({ + createdAt: expect.any(Date), + email: createUserInput.email, + id: createUserInput.toUser().id, + isEnabled: createUserInput.toUser().isEnabled, + name: createUserInput.toUser().name, + oauthProvider: createUserInput.toUser().oauthProvider, + password: null, + pictureUrl: createUserInput.toUser().pictureUrl, + roleId: createUserInput.toUser().roleId, + timezone: createUserInput.toUser().timezone, + updatedAt: expect.any(Date), + username: createUserInput.toUser().username, + }); + + await testHelper.deleteTestUsersById([createdUser.id]); + }); + + it('should fail create a user because the email address already exists', async () => { + // GIVEN + const user = await testHelper.createTestUser({ email: 'user@email.com' }); + const role = await testHelper.findTestRole('user'); + const createUserInput = TestHelper.createTestUserInput({ email: 'user@email.com', roleId: role.id }); + + // WHEN + // THEN + await expect(async () => { + await userService.create(createUserInput); + }).rejects.toThrow(new SnipcodeError(errors.EMAIL_ALREADY_TAKEN, 'EMAIL_ALREADY_TAKEN')); + + await testHelper.deleteTestUsersById([user.id]); + }); + + it('should update user information', async () => { + // GIVEN + const currentUser = await testHelper.createTestUser({}); + + const role = await testHelper.findTestRole('admin'); + const updateUserInput = TestHelper.updateTestUserInput(role.id); + + // WHEN + const updatedUser = await userService.update(currentUser, updateUserInput); + + // THEN + expect(updatedUser).toMatchObject({ + createdAt: expect.any(Date), + email: currentUser.email, + id: currentUser.id, + isEnabled: updateUserInput.toUser(currentUser).isEnabled, + name: updateUserInput.toUser(currentUser).name, + oauthProvider: updateUserInput.toUser(currentUser).oauthProvider, + password: null, + pictureUrl: updateUserInput.toUser(currentUser).pictureUrl, + roleId: updateUserInput.toUser(currentUser).roleId, + timezone: updateUserInput.toUser(currentUser).timezone, + updatedAt: expect.any(Date), + username: currentUser.username, + }); + + await testHelper.deleteTestUsersById([currentUser.id]); + }); + + it("should fail to authenticate the user because the email doesn't exists", async () => { + // GIVEN + const userEmail = 'email@test.com'; + const userPassword = 'strongPassword'; + + // WHEN + // THEN + await expect(() => userService.login(userEmail, userPassword)).rejects.toThrow( + new SnipcodeError(errors.LOGIN_FAILED, 'LOGIN_FAILED'), + ); + }); + + it('should fail to authenticate the user because the password is not correct', async () => { + // GIVEN + const userPassword = 'strongPassword'; + const userBadPassword = 'badPassword'; + const user = await testHelper.createTestUser({ oauthProvider: 'email', password: userPassword }); + + // WHEN + // THEN + await expect(() => userService.login(user.email, userBadPassword)).rejects.toThrow( + new SnipcodeError(errors.LOGIN_FAILED, 'LOGIN_FAILED'), + ); + + await testHelper.deleteTestUsersById([user.id]); + }); + + it('should fail to authenticate the user because the user is disabled', async () => { + // GIVEN + const userPassword = 'strongPassword'; + const user = await testHelper.createTestUser({ oauthProvider: 'email', password: userPassword }); + + // WHEN + // THEN + await expect(() => userService.login(user.email, userPassword)).rejects.toThrow( + new SnipcodeError(errors.ACCOUNT_DISABLED, 'ACCOUNT_DISABLED'), + ); + + await testHelper.deleteTestUsersById([user.id]); + }); + + it('should successfully authenticate the user', async () => { + // GIVEN + const userPassword = 'strongPassword'; + const user = await testHelper.createTestUser({ + isEnabled: true, + oauthProvider: 'email', + password: userPassword, + }); + + // WHEN + const authenticatedUser = await userService.login(user.email, userPassword); + + // THEN + expect(user).toMatchObject({ + email: authenticatedUser.email, + id: authenticatedUser.id, + name: authenticatedUser.name, + oauthProvider: authenticatedUser.oauthProvider, + roleId: authenticatedUser.roleId, + username: authenticatedUser.username, + }); + + await testHelper.deleteTestUsersById([user.id]); + }); + + it('should found no user given the ID provided', async () => { + // GIVEN + const snippetId = generateRandomId(); + + // WHEN + const user = await userService.findById(snippetId); + + // THEN + expect(user).toBeNull(); + }); +}); diff --git a/packages/domain/src/users/user.service.ts b/packages/domain/src/services/users/user.service.ts similarity index 62% rename from packages/domain/src/users/user.service.ts rename to packages/domain/src/services/users/user.service.ts index 9ff2440b..db40ff3b 100644 --- a/packages/domain/src/users/user.service.ts +++ b/packages/domain/src/services/users/user.service.ts @@ -1,29 +1,33 @@ -import SnipcodeError, { errors } from '@snipcode/utils'; +import { Injectable } from '@nestjs/common'; +import { SnipcodeError, errors } from '@snipcode/utils'; import bcrypt from 'bcryptjs'; import { generateFromEmail } from 'unique-username-generator'; -import { CreateUserDto } from './dtos/create-user-dto'; -import { UpdateUserDto } from './dtos/update-user-dto'; -import { Role } from '../entities/role'; -import { User } from '../entities/user'; -import { CreateUserRootFolderDto } from '../folders/dtos/create-user-root-folder-dto'; -import { prisma } from '../utils/prisma'; +import { CreateUserInput } from './inputs/create-user-input'; +import { UpdateUserInput } from './inputs/update-user-input'; +import { User } from './user.entity'; +import { PrismaService } from '../../prisma.service'; +import { CreateUserRootFolderInput } from '../folders/inputs/create-user-root-folder-input'; +import { Role } from '../roles/role.entity'; +@Injectable() export class UserService { - async create(createUserDto: CreateUserDto): Promise { - const user = await this.findByEmail(createUserDto.email); + constructor(private readonly prisma: PrismaService) {} + + async create(createUserInput: CreateUserInput): Promise { + const user = await this.findByEmail(createUserInput.email); if (user) { throw new SnipcodeError(errors.EMAIL_ALREADY_TAKEN, 'EMAIL_ALREADY_TAKEN'); } - const username = await this.generateUsername(createUserDto.email, createUserDto.username); + const username = await this.generateUsername(createUserInput.email, createUserInput.username); - createUserDto.setUsername(username); + createUserInput.setUsername(username); - const input = createUserDto.toUser(); + const input = createUserInput.toUser(); - return prisma.user.create({ + return this.prisma.user.create({ data: { email: input.email, id: input.id, @@ -39,10 +43,10 @@ export class UserService { }); } - async update(currentUser: User, updateUserDto: UpdateUserDto): Promise { - const input = updateUserDto.toUser(currentUser); + async update(currentUser: User, updateUserInput: UpdateUserInput): Promise { + const input = updateUserInput.toUser(currentUser); - return prisma.user.update({ + return this.prisma.user.update({ data: { isEnabled: input.isEnabled, name: input.name, @@ -57,7 +61,7 @@ export class UserService { } async loadAdminUser(role: Role, adminPassword: string): Promise { - const userAdminDto = new CreateUserDto({ + const userAdminInput = new CreateUserInput({ email: 'teco@snipcode.dev', name: 'Eric Teco', oauthProvider: 'email', @@ -68,17 +72,17 @@ export class UserService { username: 'teco', }); - userAdminDto.isEnabled = true; + userAdminInput.isEnabled = true; - const userAdmin = await this.findByEmail(userAdminDto.email); + const userAdmin = await this.findByEmail(userAdminInput.email); if (userAdmin) { return; } - const userInput = userAdminDto.toUser(); + const userInput = userAdminInput.toUser(); - const user = await prisma.user.create({ + const user = await this.prisma.user.create({ data: { email: userInput.email, id: userInput.id, @@ -93,11 +97,11 @@ export class UserService { }, }); - const createUserRootFolderDto = new CreateUserRootFolderDto(user.id); + const createUserRootFolderInput = new CreateUserRootFolderInput(user.id); - const folderInput = createUserRootFolderDto.toFolder(); + const folderInput = createUserRootFolderInput.toFolder(); - await prisma.folder.create({ + await this.prisma.folder.create({ data: { id: folderInput.id, name: folderInput.name, @@ -108,11 +112,11 @@ export class UserService { } async findByEmail(email: string): Promise { - return prisma.user.findUnique({ where: { email } }); + return this.prisma.user.findUnique({ where: { email } }); } async findById(id: string): Promise { - return prisma.user.findUnique({ where: { id } }); + return this.prisma.user.findUnique({ where: { id } }); } async login(email: string, password: string): Promise { @@ -140,7 +144,7 @@ export class UserService { return generateFromEmail(email, 3); } - const user = await prisma.user.findFirst({ + const user = await this.prisma.user.findFirst({ where: { username, }, diff --git a/packages/domain/src/sessions/session.service.test.ts b/packages/domain/src/sessions/session.service.test.ts deleted file mode 100644 index d784f4f4..00000000 --- a/packages/domain/src/sessions/session.service.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { SessionService } from './session.service'; -import { createTestSession, createTestSessionDto, deleteTestUserSessions, generateTestId } from '../../tests/helpers'; -import { Session } from '../entities/session'; - -const sessionService = new SessionService(); - -describe('Test Session Service', function () { - it('should create a session', async () => { - // GIVEN - const userId = generateTestId(); - const dto = createTestSessionDto(userId); - - // WHEN - const sessionCreated = await sessionService.create(dto); - - // THEN - expect(sessionCreated).toMatchObject(dto.toSession()); - - await deleteTestUserSessions(sessionCreated.userId); - }); - - it('should find a session by token', async () => { - // GIVEN - const userId = generateTestId(); - const session = await createTestSession({ userId }); - - // WHEN - const sessionFound = await sessionService.findByToken(session.token); - - // THEN - expect(session).toEqual(sessionFound); - - await deleteTestUserSessions(session.userId); - }); - - it('should delete all session of a user', async () => { - // GIVEN - const userId = generateTestId(); - - const sessionsCreated = await Promise.all([ - createTestSession({ userId }), - createTestSession({ userId }), - createTestSession({ userId }), - ]); - - // WHEN - await sessionService.deleteUserSessions(userId); - - // THEN - const userSessions = await Promise.all(sessionsCreated.map(({ token }) => sessionService.findByToken(token))); - - expect(userSessions.every((session) => !session)).toBe(true); - }); -}); diff --git a/packages/domain/src/sessions/session.service.ts b/packages/domain/src/sessions/session.service.ts deleted file mode 100644 index 07ad3182..00000000 --- a/packages/domain/src/sessions/session.service.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { CreateSessionDto } from './dtos/create-session-dto'; -import { Session } from '../entities/session'; -import { prisma } from '../utils/prisma'; - -export class SessionService { - async create(createSessionDto: CreateSessionDto): Promise { - const input = createSessionDto.toSession(); - - return prisma.session.create({ - data: { - expires: input.expires, - id: input.id, - token: input.token, - userId: input.userId, - }, - }); - } - - async deleteUserSessions(userId: string): Promise { - await prisma.session.deleteMany({ where: { userId } }); - } - - async findByToken(token: string): Promise { - return prisma.session.findUnique({ where: { token } }); - } -} diff --git a/packages/domain/src/snippets/dtos/delete-snippet-dto.test.ts b/packages/domain/src/snippets/dtos/delete-snippet-dto.test.ts deleted file mode 100644 index 16ea4597..00000000 --- a/packages/domain/src/snippets/dtos/delete-snippet-dto.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { DeleteSnippetDto } from './delete-snippet-dto'; -import { generateTestId } from '../../../tests/helpers'; - -describe('Test Delete Snippet DTO', () => { - it('should have the right property defined', () => { - const snippetId = generateTestId(); - const userId = generateTestId(); - - // GIVEN - const dto = new DeleteSnippetDto({ - creatorId: userId, - snippetId, - }); - - // WHEN - // THEN - expect(dto.snippetId).toEqual(snippetId); - expect(dto.creatorId).toEqual(userId); - }); -}); diff --git a/packages/domain/src/snippets/snippet.service.test.ts b/packages/domain/src/snippets/snippet.service.test.ts deleted file mode 100644 index db811a00..00000000 --- a/packages/domain/src/snippets/snippet.service.test.ts +++ /dev/null @@ -1,320 +0,0 @@ -import SnipcodeError, { errors, generateRandomId } from '@snipcode/utils'; - -import { SnippetService } from './snippet.service'; -import { - createTestSnippet, - createTestSnippetDto, - createUserWithRootFolder, - deleteTestFoldersById, - deleteTestSnippetDto, - deleteTestSnippetsById, - deleteTestUsersById, - updateTestSnippetDto, -} from '../../tests/helpers'; -import { Snippet } from '../entities/snippet'; -import { RoleService } from '../roles/role.service'; - -const snippetService = new SnippetService(); -const roleService = new RoleService(); - -describe('Test Snippet service', () => { - beforeAll(async () => { - await roleService.loadRoles(); - }); - - it('should create a snippet in the specified folder', async () => { - // GIVEN - const [user, rootFolder] = await createUserWithRootFolder(); - const createSnippetDto = createTestSnippetDto({ folderId: rootFolder.id, userId: user.id }); - - // WHEN - const expectedSnippet = await snippetService.create(createSnippetDto); - - // THEN - expect(expectedSnippet).toMatchObject({ - content: createSnippetDto.toSnippet().content, - contentHtml: createSnippetDto.toSnippet().contentHtml, - createdAt: expect.any(Date), - description: createSnippetDto.toSnippet().description, - folderId: rootFolder.id, - id: createSnippetDto.toSnippet().id, - language: createSnippetDto.toSnippet().language, - lineHighlight: createSnippetDto.toSnippet().lineHighlight, - name: createSnippetDto.toSnippet().name, - size: createSnippetDto.toSnippet().size, - theme: createSnippetDto.toSnippet().theme, - updatedAt: expect.any(Date), - userId: user.id, - visibility: createSnippetDto.toSnippet().visibility, - }); - - await deleteTestSnippetsById([expectedSnippet.id]); - await deleteTestFoldersById([rootFolder.id]); - await deleteTestUsersById([user.id]); - }); - - it('should not create a snippet because it already exists in the specified folder', async () => { - // GIVEN - const [user, rootFolder] = await createUserWithRootFolder(); - const snippet = await createTestSnippet({ folderId: rootFolder.id, name: 'app.tsx', userId: user.id }); - - const sameCreateSnippetDto = createTestSnippetDto({ folderId: rootFolder.id, name: 'app.tsx', userId: user.id }); - - // WHEN - // THEN - await expect(() => snippetService.create(sameCreateSnippetDto)).rejects.toThrow( - new SnipcodeError(errors.SNIPPET_ALREADY_EXIST(sameCreateSnippetDto.name), 'SNIPPET_ALREADY_EXIST'), - ); - - await deleteTestSnippetsById([snippet.id]); - await deleteTestFoldersById([rootFolder.id]); - await deleteTestUsersById([user.id]); - }); - - it('should retrieve all public snippets', async () => { - // GIVEN - const [user, rootFolder] = await createUserWithRootFolder(); - const existingSnippets = await Promise.all([ - createTestSnippet({ folderId: rootFolder.id, userId: user.id, visibility: 'public' }), - createTestSnippet({ folderId: rootFolder.id, userId: user.id, visibility: 'private' }), - createTestSnippet({ folderId: rootFolder.id, userId: user.id, visibility: 'public' }), - createTestSnippet({ folderId: rootFolder.id, userId: user.id, visibility: 'private' }), - createTestSnippet({ folderId: rootFolder.id, userId: user.id, visibility: 'public' }), - createTestSnippet({ folderId: rootFolder.id, userId: user.id, visibility: 'public' }), - ]); - - // WHEN - const publicSnippets = await snippetService.findPublicSnippet({ itemPerPage: 10 }); - - // THEN - await expect(publicSnippets.hasMore).toEqual(false); - await expect(publicSnippets.nextCursor).toEqual(null); - await expect(publicSnippets.items).toHaveLength(4); - - await deleteTestSnippetsById(existingSnippets.map((snippet) => snippet.id)); - await deleteTestFoldersById([rootFolder.id]); - await deleteTestUsersById([user.id]); - }); - - it('should retrieve a subset of public snippets', async () => { - // GIVEN - const [user, rootFolder] = await createUserWithRootFolder(); - const existingSnippets = await Promise.all([ - createTestSnippet({ folderId: rootFolder.id, userId: user.id, visibility: 'public' }), - createTestSnippet({ folderId: rootFolder.id, userId: user.id, visibility: 'private' }), - createTestSnippet({ folderId: rootFolder.id, userId: user.id, visibility: 'public' }), - createTestSnippet({ folderId: rootFolder.id, userId: user.id, visibility: 'private' }), - createTestSnippet({ folderId: rootFolder.id, userId: user.id, visibility: 'public' }), - createTestSnippet({ folderId: rootFolder.id, userId: user.id, visibility: 'public' }), - ]); - - // WHEN - const publicSnippets = await snippetService.findPublicSnippet({ itemPerPage: 3 }); - - // THEN - await expect(publicSnippets.hasMore).toEqual(true); - await expect(publicSnippets.nextCursor).toEqual(expect.any(String)); - await expect(publicSnippets.items).toHaveLength(3); - - await deleteTestSnippetsById(existingSnippets.map((snippet) => snippet.id)); - await deleteTestFoldersById([rootFolder.id]); - await deleteTestUsersById([user.id]); - }); - - it('should find all snippets of a user', async () => { - // GIVEN - const [user1, rootFolder1] = await createUserWithRootFolder(); - const [user2, rootFolder2] = await createUserWithRootFolder(); - - const existingSnippets = await Promise.all([ - createTestSnippet({ folderId: rootFolder1.id, userId: user1.id, visibility: 'public' }), - createTestSnippet({ folderId: rootFolder1.id, userId: user1.id, visibility: 'private' }), - createTestSnippet({ folderId: rootFolder2.id, userId: user2.id, visibility: 'public' }), - createTestSnippet({ folderId: rootFolder1.id, userId: user1.id, visibility: 'private' }), - createTestSnippet({ folderId: rootFolder2.id, userId: user2.id, visibility: 'public' }), - createTestSnippet({ folderId: rootFolder2.id, userId: user2.id, visibility: 'private' }), - ]); - - // WHEN - const userSnippets = await snippetService.findByUser(user2.id); - - // THEN - await expect(userSnippets).toHaveLength(3); - - await deleteTestSnippetsById(existingSnippets.map((snippet) => snippet.id)); - await deleteTestFoldersById([rootFolder1.id, rootFolder2.id]); - await deleteTestUsersById([user1.id, user2.id]); - }); - - it('should retrieve a snippet by its ID', async () => { - // GIVEN - const [user1, rootFolder1] = await createUserWithRootFolder(); - - const [snippet] = await Promise.all([ - createTestSnippet({ folderId: rootFolder1.id, userId: user1.id, visibility: 'public' }), - ]); - - // WHEN - const snippetFound = await snippetService.findById(snippet.id); - - // THEN - expect(snippetFound).toMatchObject({ - folderId: rootFolder1.id, - id: snippet.id, - userId: user1.id, - visibility: 'public', - }); - - await deleteTestSnippetsById([snippet.id]); - await deleteTestFoldersById([rootFolder1.id]); - await deleteTestUsersById([user1.id]); - }); - - it('should found no snippet given the ID provided', async () => { - // GIVEN - const snippetId = generateRandomId(); - - // WHEN - // THEN - await expect(async () => { - await snippetService.findById(snippetId); - }).rejects.toThrow(new SnipcodeError(errors.SNIPPET_NOT_FOUND(snippetId), 'SNIPPET_NOT_FOUND')); - }); - - it('should delete an existing snippet belonging to a user', async () => { - // GIVEN - const [user, rootFolder] = await createUserWithRootFolder(); - - const [snippet1, snippet2] = await Promise.all([ - createTestSnippet({ folderId: rootFolder.id, userId: user.id, visibility: 'public' }), - createTestSnippet({ folderId: rootFolder.id, userId: user.id, visibility: 'private' }), - ]); - - // WHEN - const deleteSnippetDto = deleteTestSnippetDto({ snippetId: snippet1.id, userId: snippet1.userId }); - - await snippetService.delete(deleteSnippetDto); - - // THEN - const folderSnippets = await snippetService.findByFolder(rootFolder.id); - - expect(folderSnippets).toHaveLength(1); - - await deleteTestSnippetsById([snippet2.id]); - await deleteTestFoldersById([rootFolder.id]); - await deleteTestUsersById([user.id]); - }); - - it('should not delete an existing snippet because it belongs to other user', async () => { - // GIVEN - const [user1, rootFolder1] = await createUserWithRootFolder(); - const [user2, rootFolder2] = await createUserWithRootFolder(); - - const [snippet1, snippet2, snippet3] = await Promise.all([ - createTestSnippet({ folderId: rootFolder1.id, userId: user1.id, visibility: 'public' }), - createTestSnippet({ folderId: rootFolder2.id, userId: user2.id, visibility: 'private' }), - createTestSnippet({ folderId: rootFolder1.id, userId: user1.id, visibility: 'private' }), - ]); - - // WHEN - const deleteSnippetDto = deleteTestSnippetDto({ snippetId: snippet1.id, userId: user2.id }); - - // THEN - await expect(async () => { - await snippetService.delete(deleteSnippetDto); - }).rejects.toThrow( - new SnipcodeError(errors.CANT_EDIT_SNIPPET(deleteSnippetDto.creatorId, snippet1.id), 'CANT_EDIT_SNIPPET'), - ); - - const user1FolderSnippets = await snippetService.findByFolder(rootFolder1.id); - const user2FolderSnippets = await snippetService.findByFolder(rootFolder2.id); - - expect(user1FolderSnippets).toHaveLength(2); - expect(user2FolderSnippets).toHaveLength(1); - - await deleteTestSnippetsById([snippet2.id, snippet3.id]); - await deleteTestFoldersById([rootFolder1.id, rootFolder2.id]); - await deleteTestUsersById([user1.id, user2.id]); - }); - - it('should update an existing snippet in the specified folder', async () => { - // GIVEN - const [user, rootFolder] = await createUserWithRootFolder(); - const [snippet] = await Promise.all([ - createTestSnippet({ folderId: rootFolder.id, userId: user.id, visibility: 'public' }), - ]); - - const updateSnippetDto = updateTestSnippetDto({ snippetId: snippet.id, userId: user.id }); - - // WHEN - const updatedSnippet = await snippetService.update(updateSnippetDto); - - // THEN - const snippetToUpdate = updateSnippetDto.toSnippet(snippet); - - expect(updatedSnippet).toMatchObject({ - content: snippetToUpdate.content, - contentHtml: snippetToUpdate.contentHtml, - createdAt: expect.any(Date), - description: snippetToUpdate.description, - folderId: rootFolder.id, - id: snippet.id, - language: snippetToUpdate.language, - lineHighlight: snippetToUpdate.lineHighlight, - name: snippetToUpdate.name, - size: snippetToUpdate.size, - theme: snippetToUpdate.theme, - updatedAt: expect.any(Date), - userId: user.id, - visibility: snippetToUpdate.visibility, - }); - - await deleteTestSnippetsById([updatedSnippet.id]); - await deleteTestFoldersById([rootFolder.id]); - await deleteTestUsersById([user.id]); - }); - - it('should not update an existing snippet in the specified folder because another snippet with the updated name already exists in the folder', async () => { - // GIVEN - const [user, rootFolder] = await createUserWithRootFolder(); - const [snippet] = await Promise.all([ - createTestSnippet({ folderId: rootFolder.id, name: 'snippet-one.java', userId: user.id }), - createTestSnippet({ folderId: rootFolder.id, name: 'snippet-two.java', userId: user.id, visibility: 'private' }), - ]); - - const updateSnippetDto = updateTestSnippetDto({ name: 'snippet-two.java', snippetId: snippet.id, userId: user.id }); - - // WHEN - // THEN - await expect(async () => { - await snippetService.update(updateSnippetDto); - }).rejects.toThrow(new SnipcodeError(errors.SNIPPET_ALREADY_EXIST(updateSnippetDto.name), 'SNIPPET_ALREADY_EXIST')); - - await deleteTestSnippetsById([snippet.id]); - await deleteTestFoldersById([rootFolder.id]); - await deleteTestUsersById([user.id]); - }); - - it('should not update an existing snippet in the specified folder because because it belongs to other user', async () => { - // GIVEN - const [user1, rootFolder1] = await createUserWithRootFolder(); - const [user2, rootFolder2] = await createUserWithRootFolder(); - const [snippet] = await Promise.all([ - createTestSnippet({ folderId: rootFolder1.id, name: 'snippet-one.java', userId: user1.id }), - ]); - - const updateSnippetDto = updateTestSnippetDto({ snippetId: snippet.id, userId: user2.id }); - - // WHEN - // THEN - await expect(async () => { - await snippetService.update(updateSnippetDto); - }).rejects.toThrow( - new SnipcodeError(errors.CANT_EDIT_SNIPPET(updateSnippetDto.creatorId, snippet.id), 'CANT_EDIT_SNIPPET'), - ); - - await deleteTestSnippetsById([snippet.id]); - await deleteTestFoldersById([rootFolder1.id, rootFolder2.id]); - await deleteTestUsersById([user1.id, user2.id]); - }); -}); diff --git a/packages/domain/src/users/user.service.test.ts b/packages/domain/src/users/user.service.test.ts deleted file mode 100644 index 3bb4c988..00000000 --- a/packages/domain/src/users/user.service.test.ts +++ /dev/null @@ -1,254 +0,0 @@ -import SnipcodeError, { errors, generateRandomId } from '@snipcode/utils'; - -import { UserService } from './user.service'; -import { - createTestUser, - createTestUserDto, - deleteTestUsersById, - findTestRole, - updateTestUserDto, -} from '../../tests/helpers'; -import { User } from '../entities/user'; -import { RoleService } from '../roles/role.service'; - -const roleService = new RoleService(); -const userService = new UserService(); - -describe('Test User service', () => { - beforeAll(async () => { - await roleService.loadRoles(); - }); - - it('should load users in the database', async () => { - const [roleAdmin] = await roleService.findAll(); - const adminPassword = 'VerStrongPassword'; - - await userService.loadAdminUser(roleAdmin, adminPassword); - - const adminUser = await userService.findByEmail('teco@snipcode.dev'); - - expect(adminUser).toBeDefined(); - - await deleteTestUsersById([adminUser?.id]); - }); - - it('should create a user', async () => { - // GIVEN - const role = await findTestRole('user'); - const createUserDto = await createTestUserDto({ roleId: role.id }); - - // WHEN - const createdUser = await userService.create(createUserDto); - - // THEN - expect(createdUser).toMatchObject({ - createdAt: expect.any(Date), - email: createUserDto.email, - id: createUserDto.toUser().id, - isEnabled: createUserDto.toUser().isEnabled, - name: createUserDto.toUser().name, - oauthProvider: createUserDto.toUser().oauthProvider, - password: null, - pictureUrl: createUserDto.toUser().pictureUrl, - roleId: createUserDto.toUser().roleId, - timezone: createUserDto.toUser().timezone, - updatedAt: expect.any(Date), - username: createUserDto.toUser().username, - }); - - await deleteTestUsersById([createdUser.id]); - }); - - it('should create a user with no username', async () => { - // GIVEN - const role = await findTestRole('user'); - const createUserDto = await createTestUserDto({ roleId: role.id, username: null }); - - // WHEN - const createdUser = await userService.create(createUserDto); - - // THEN - expect(createdUser).toMatchObject({ - createdAt: expect.any(Date), - email: createUserDto.email, - id: createUserDto.toUser().id, - isEnabled: createUserDto.toUser().isEnabled, - name: createUserDto.toUser().name, - oauthProvider: createUserDto.toUser().oauthProvider, - password: null, - pictureUrl: createUserDto.toUser().pictureUrl, - roleId: createUserDto.toUser().roleId, - timezone: createUserDto.toUser().timezone, - updatedAt: expect.any(Date), - username: expect.any(String), - }); - - await deleteTestUsersById([createdUser.id]); - }); - - it('should create a user with a username that already exists', async () => { - // GIVEN - const role = await findTestRole('user'); - const user = await createTestUser({ username: 'roloto' }); - - const createUserDto = await createTestUserDto({ roleId: role.id, username: 'roloto' }); - - // WHEN - const createdUser = await userService.create(createUserDto); - - // THEN - expect(createdUser.username).not.toEqual('roloto'); - - await deleteTestUsersById([user.id, createdUser.id]); - }); - - it('should create a user - validation check', async () => { - // GIVEN - const role = await findTestRole('user'); - const createUserDto = await createTestUserDto({ roleId: role.id }); - - createUserDto.isEnabled = true; - - // WHEN - const createdUser = await userService.create(createUserDto); - - // THEN - expect(createdUser).toMatchObject({ - createdAt: expect.any(Date), - email: createUserDto.email, - id: createUserDto.toUser().id, - isEnabled: createUserDto.toUser().isEnabled, - name: createUserDto.toUser().name, - oauthProvider: createUserDto.toUser().oauthProvider, - password: null, - pictureUrl: createUserDto.toUser().pictureUrl, - roleId: createUserDto.toUser().roleId, - timezone: createUserDto.toUser().timezone, - updatedAt: expect.any(Date), - username: createUserDto.toUser().username, - }); - - await deleteTestUsersById([createdUser.id]); - }); - - it('should fail create a user because the email address already exists', async () => { - // GIVEN - const user = await createTestUser({ email: 'user@email.com' }); - const role = await findTestRole('user'); - const createUserDto = await createTestUserDto({ email: 'user@email.com', roleId: role.id }); - - // WHEN - // THEN - await expect(async () => { - await userService.create(createUserDto); - }).rejects.toThrow(new SnipcodeError(errors.EMAIL_ALREADY_TAKEN, 'EMAIL_ALREADY_TAKEN')); - - await deleteTestUsersById([user.id]); - }); - - it('should update user information', async () => { - // GIVEN - const currentUser = await createTestUser({}); - - const role = await findTestRole('admin'); - const updateUserDto = updateTestUserDto(role.id); - - // WHEN - const updatedUser = await userService.update(currentUser, updateUserDto); - - // THEN - expect(updatedUser).toMatchObject({ - createdAt: expect.any(Date), - email: currentUser.email, - id: currentUser.id, - isEnabled: updateUserDto.toUser(currentUser).isEnabled, - name: updateUserDto.toUser(currentUser).name, - oauthProvider: updateUserDto.toUser(currentUser).oauthProvider, - password: null, - pictureUrl: updateUserDto.toUser(currentUser).pictureUrl, - roleId: updateUserDto.toUser(currentUser).roleId, - timezone: updateUserDto.toUser(currentUser).timezone, - updatedAt: expect.any(Date), - username: currentUser.username, - }); - - await deleteTestUsersById([currentUser.id]); - }); - - it("should fail to authenticate the user because the email doesn't exists", async () => { - // GIVEN - const userEmail = 'email@test.com'; - const userPassword = 'strongPassword'; - - // WHEN - // THEN - await expect(() => userService.login(userEmail, userPassword)).rejects.toThrow( - new SnipcodeError(errors.LOGIN_FAILED, 'LOGIN_FAILED'), - ); - }); - - it('should fail to authenticate the user because the password is not correct', async () => { - // GIVEN - const userPassword = 'strongPassword'; - const userBadPassword = 'badPassword'; - const user = await createTestUser({ oauthProvider: 'email', password: userPassword }); - - // WHEN - // THEN - await expect(() => userService.login(user.email, userBadPassword)).rejects.toThrow( - new SnipcodeError(errors.LOGIN_FAILED, 'LOGIN_FAILED'), - ); - - await deleteTestUsersById([user.id]); - }); - - it('should fail to authenticate the user because the user is disabled', async () => { - // GIVEN - const userPassword = 'strongPassword'; - const user = await createTestUser({ oauthProvider: 'email', password: userPassword }); - - // WHEN - // THEN - await expect(() => userService.login(user.email, userPassword)).rejects.toThrow( - new SnipcodeError(errors.ACCOUNT_DISABLED, 'ACCOUNT_DISABLED'), - ); - - await deleteTestUsersById([user.id]); - }); - - it('should successfully authenticate the user', async () => { - // GIVEN - const userPassword = 'strongPassword'; - const user = await createTestUser({ - isEnabled: true, - oauthProvider: 'email', - password: userPassword, - }); - - // WHEN - const authenticatedUser = await userService.login(user.email, userPassword); - - // THEN - expect(user).toMatchObject({ - email: authenticatedUser.email, - id: authenticatedUser.id, - name: authenticatedUser.name, - oauthProvider: authenticatedUser.oauthProvider, - roleId: authenticatedUser.roleId, - username: authenticatedUser.username, - }); - - await deleteTestUsersById([user.id]); - }); - - it('should found no user given the ID provided', async () => { - // GIVEN - const snippetId = generateRandomId(); - - // WHEN - const user = await userService.findById(snippetId); - - // THEN - expect(user).toBeNull(); - }); -}); diff --git a/packages/domain/src/utils/axios-error.ts b/packages/domain/src/utils/axios-error.ts index 873065eb..056a1b28 100644 --- a/packages/domain/src/utils/axios-error.ts +++ b/packages/domain/src/utils/axios-error.ts @@ -1,4 +1,4 @@ -import SnipcodeError, { ErrorCode } from '@snipcode/utils'; +import { ErrorCode, SnipcodeError } from '@snipcode/utils'; import { AxiosError } from 'axios'; export const handleRequestError = (errorCode: ErrorCode) => (error: AxiosError) => { diff --git a/packages/domain/src/utils/id.test.ts b/packages/domain/src/utils/db-id.test.ts similarity index 92% rename from packages/domain/src/utils/id.test.ts rename to packages/domain/src/utils/db-id.test.ts index 4c71c268..ae680c62 100644 --- a/packages/domain/src/utils/id.test.ts +++ b/packages/domain/src/utils/db-id.test.ts @@ -1,4 +1,4 @@ -import { dbID } from './id'; +import { dbID } from './db-id'; describe('Test Database ID generator', () => { test('generate a valid id', () => { diff --git a/packages/domain/src/utils/id.ts b/packages/domain/src/utils/db-id.ts similarity index 100% rename from packages/domain/src/utils/id.ts rename to packages/domain/src/utils/db-id.ts diff --git a/packages/domain/src/utils/prisma.ts b/packages/domain/src/utils/prisma.ts deleted file mode 100644 index eef71154..00000000 --- a/packages/domain/src/utils/prisma.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Prisma, PrismaClient } from '@prisma/client'; - -declare global { - // eslint-disable-next-line no-var - var prisma: PrismaClient | undefined; -} - -let prisma: PrismaClient; - -if (process.env.NODE_ENV === 'production') { - prisma = new PrismaClient(); -} else { - if (!global.prisma) { - global.prisma = new PrismaClient({ - datasources: { - db: { - url: process.env.NODE_ENV === 'test' ? process.env.TEST_DATABASE_URL : process.env.DATABASE_URL, - }, - }, - // log: ['query'], - }); - } - // eslint-disable-next-line prefer-destructuring - prisma = global.prisma; -} - -export { Prisma, PrismaClient, prisma }; diff --git a/packages/domain/tests/database.mjs b/packages/domain/tests/database.mjs index 17f7cf0e..00f9bc2c 100755 --- a/packages/domain/tests/database.mjs +++ b/packages/domain/tests/database.mjs @@ -14,7 +14,7 @@ if (!process.env.CI) { // Container not found, creating a new one. await $`docker run -d --rm --name ${CONTAINER_NAME} -e MYSQL_ROOT_PASSWORD=secret -e MYSQL_DATABASE=${MYSQL_DATABASE} -p 3313:3306 mysql:8.0.34`; - await sleep(9000); // Wait for 9 seconds the container to initialize + await sleep(10000); // Wait for 9 seconds the container to initialize } process.env.DATABASE_URL = `mysql://root:secret@127.0.0.1:3313/${MYSQL_DATABASE}`; diff --git a/packages/domain/tests/helpers.ts b/packages/domain/tests/helpers.ts index 286f99b1..02d79408 100644 --- a/packages/domain/tests/helpers.ts +++ b/packages/domain/tests/helpers.ts @@ -1,26 +1,21 @@ import { randEmail, randFullName, randImg, randNumber, randTimeZone, randUserName, randWord } from '@ngneat/falso'; -import { - Folder, - OauthProvider, - Role, - RoleName, - Session, - Snippet, - SnippetVisibility, - User, - dbClient, - dbID, -} from '../index'; -import { CreateFolderDto } from '../src/folders/dtos/create-folder-dto'; -import { CreateUserRootFolderDto } from '../src/folders/dtos/create-user-root-folder-dto'; -import { UpdateFolderDto } from '../src/folders/dtos/update-folder-dto'; -import { CreateSessionDto } from '../src/sessions/dtos/create-session-dto'; -import { CreateSnippetDto } from '../src/snippets/dtos/create-snippet-dto'; -import { DeleteSnippetDto } from '../src/snippets/dtos/delete-snippet-dto'; -import { UpdateSnippetDto } from '../src/snippets/dtos/update-snippet-dto'; -import { CreateUserDto } from '../src/users/dtos/create-user-dto'; -import { UpdateUserDto } from '../src/users/dtos/update-user-dto'; +import { PrismaService } from '../src/prisma.service'; +import { Folder } from '../src/services/folders/folder.entity'; +import { CreateFolderInput } from '../src/services/folders/inputs/create-folder-input'; +import { CreateUserRootFolderInput } from '../src/services/folders/inputs/create-user-root-folder-input'; +import { UpdateFolderInput } from '../src/services/folders/inputs/update-folder-input'; +import { Role, RoleName } from '../src/services/roles/role.entity'; +import { CreateSessionInput } from '../src/services/sessions/inputs/create-session-input'; +import { Session } from '../src/services/sessions/session.entity'; +import { CreateSnippetInput } from '../src/services/snippets/inputs/create-snippet-input'; +import { DeleteSnippetInput } from '../src/services/snippets/inputs/delete-snippet-input'; +import { UpdateSnippetInput } from '../src/services/snippets/inputs/update-snippet-input'; +import { Snippet, SnippetVisibility } from '../src/services/snippets/snippet.entity'; +import { CreateUserInput } from '../src/services/users/inputs/create-user-input'; +import { UpdateUserInput } from '../src/services/users/inputs/update-user-input'; +import { OauthProvider, User } from '../src/services/users/user.entity'; +import { dbID } from '../src/utils/db-id'; type CreateManyTestFoldersArgs = { folderNames: string[]; @@ -28,7 +23,7 @@ type CreateManyTestFoldersArgs = { userId: string; }; -type CreateTestUserDtoArgs = { +type CreateTestUserInputArgs = { email?: string; isEnabled?: boolean; oauthProvider?: OauthProvider; @@ -46,228 +41,237 @@ type CreateTestUserArgs = { username?: string | null; }; -export const findTestRole = async (name: RoleName): Promise => { - const role = await dbClient.role.findUnique({ where: { name } }); +export class TestHelper { + constructor(private readonly prisma: PrismaService) {} - if (!role) { - throw new Error(`Role with the name "${name}" not found!`); + static generateTestId(): string { + return dbID.generate(); } - return role; -}; - -export const createTestUserDto = ({ - email, - isEnabled, - oauthProvider, - password, - roleId, - username = randUserName(), -}: CreateTestUserDtoArgs): CreateUserDto => { - const dto = new CreateUserDto({ - email: email ?? randEmail(), - name: randFullName(), - oauthProvider: oauthProvider ?? 'github', - password: password ?? null, - pictureUrl: randImg(), - roleId, - timezone: randTimeZone(), - username, - }); + async findTestRole(name: RoleName): Promise { + const role = await this.prisma.role.findUnique({ where: { name } }); - dto.isEnabled = Boolean(isEnabled); + if (!role) { + throw new Error(`Role with the name "${name}" not found!`); + } - return dto; -}; + return role; + } -export const createTestUser = async ({ - email, - isEnabled, - oauthProvider, - password, - roleName = 'user', - username, -}: CreateTestUserArgs): Promise => { - const role = await findTestRole(roleName); + static createTestUserInput({ + email, + isEnabled, + oauthProvider, + password, + roleId, + username = randUserName(), + }: CreateTestUserInputArgs): CreateUserInput { + const input = new CreateUserInput({ + email: email ?? randEmail(), + name: randFullName(), + oauthProvider: oauthProvider ?? 'github', + password: password ?? null, + pictureUrl: randImg(), + roleId, + timezone: randTimeZone(), + username, + }); - const createUserDto = createTestUserDto({ email, isEnabled, oauthProvider, password, roleId: role.id, username }); + input.isEnabled = Boolean(isEnabled); - return dbClient.user.create({ data: createUserDto.toUser() }); -}; + return input; + } -export const deleteTestUsersById = async (userIds: Array): Promise => { - const promises = userIds.map(async (userId) => { - if (!userId) { - return; - } + async createTestUser({ + email, + isEnabled, + oauthProvider, + password, + roleName = 'user', + username, + }: CreateTestUserArgs): Promise { + const role = await this.findTestRole(roleName); + + const createUserInput = TestHelper.createTestUserInput({ + email, + isEnabled, + oauthProvider, + password, + roleId: role.id, + username, + }); - await dbClient.user.delete({ where: { id: userId } }); - }); + return this.prisma.user.create({ data: createUserInput.toUser() }); + } - return Promise.all(promises); -}; + async deleteTestUsersById(userIds: Array): Promise { + const promises = userIds.map(async (userId) => { + if (!userId) { + return; + } -export const deleteTestFoldersById = async (folderIds: Array): Promise => { - for (const folderId of folderIds) { - if (!folderId) { - return; - } + await this.prisma.user.delete({ where: { id: userId } }); + }); - // eslint-disable-next-line no-await-in-loop - await dbClient.folder.delete({ where: { id: folderId } }); + return Promise.all(promises); } -}; - -export const createUserWithRootFolder = async (): Promise<[User, Folder]> => { - const user = await createTestUser({}); - const rootFolder = await dbClient.folder.create({ data: new CreateUserRootFolderDto(user.id).toFolder() }); - return [user, rootFolder]; -}; + static updateTestUserInput(roleId: string): UpdateUserInput { + const providers: OauthProvider[] = ['email', 'google', 'github', 'stackoverflow']; + const index = randNumber({ max: 2, min: 0 }); -export const createManyTestFolders = async ({ - folderNames, - parentId, - userId, -}: CreateManyTestFoldersArgs): Promise => { - const promises = folderNames.map((name) => { - const createFolderDto = new CreateFolderDto({ - name, - parentId, - userId, + return new UpdateUserInput({ + name: randFullName(), + oauthProvider: providers[index], + pictureUrl: randImg(), + roleId, + timezone: randTimeZone(), }); + } - return dbClient.folder.create({ data: createFolderDto.toFolder() }); - }); + async deleteTestFoldersById(folderIds: Array): Promise { + for (const folderId of folderIds) { + if (!folderId) { + return; + } - return Promise.all(promises); -}; + // eslint-disable-next-line no-await-in-loop + await this.prisma.folder.delete({ where: { id: folderId } }); + } + } -export const generateTestId = (): string => dbID.generate(); + async createUserWithRootFolder(): Promise<[User, Folder]> { + const user = await this.createTestUser({}); + const rootFolder = await this.prisma.folder.create({ data: new CreateUserRootFolderInput(user.id).toFolder() }); -export const createTestFolderDto = (args?: { name?: string; parentId?: string; userId?: string }): CreateFolderDto => { - return new CreateFolderDto({ - name: args?.name ?? randWord(), - parentId: args?.parentId ?? generateTestId(), - userId: args?.userId ?? generateTestId(), - }); -}; + return [user, rootFolder]; + } -export const createTestSnippetDto = ( - args: { folderId?: string; name?: string; userId?: string; visibility?: SnippetVisibility } | undefined, -): CreateSnippetDto => { - const languages = ['java', 'js', 'ts', 'c', 'c++', 'python', 'go', 'php', 'csharp']; - const extensions = ['java', 'js', 'ts', 'c', 'cpp', 'py', 'go', 'php', 'cs']; - const themes = ['one-dark-pro', 'dracula', 'dark-plus', 'monokai', 'github-dark', 'github-light']; - - const languageIndex = randNumber({ max: languages.length - 1, min: 0 }); - const themeIndex = randNumber({ max: themes.length - 1, min: 0 }); - - const snippetContent = randWord({ length: randNumber({ max: 30, min: 5 }) }).join('\n'); - - return new CreateSnippetDto({ - content: snippetContent, - contentHighlighted: `${snippetContent} highlighted`, - description: randWord({ length: randNumber({ max: 20, min: 10 }) }).join(' '), - folderId: args?.folderId ?? generateTestId(), - language: languages[languageIndex], - lineHighlight: null, - name: args?.name ?? `${randWord()}.${extensions[languageIndex]}`, - theme: themes[themeIndex], - userId: args?.userId ?? generateTestId(), - visibility: args?.visibility ?? 'public', - }); -}; + async createManyTestFolders({ folderNames, parentId, userId }: CreateManyTestFoldersArgs): Promise { + const promises = folderNames.map((name) => { + const createFolderInput = new CreateFolderInput({ + name, + parentId, + userId, + }); -export const deleteTestSnippetsById = async (snippetIds: Array): Promise => { - const promises = snippetIds.map(async (snippetId) => { - if (!snippetId) { - return; - } + return this.prisma.folder.create({ data: createFolderInput.toFolder() }); + }); - await dbClient.snippet.delete({ where: { id: snippetId } }); - }); + return Promise.all(promises); + } - return Promise.all(promises); -}; + static createTestFolderInput(args?: { name?: string; parentId?: string; userId?: string }): CreateFolderInput { + return new CreateFolderInput({ + name: args?.name ?? randWord(), + parentId: args?.parentId ?? TestHelper.generateTestId(), + userId: args?.userId ?? TestHelper.generateTestId(), + }); + } -export const createTestSnippet = async ( - args: { folderId?: string; name?: string; userId?: string; visibility?: SnippetVisibility } | undefined, -): Promise => { - const createSnippetDto = createTestSnippetDto(args); + static updateTestFolderInput( + args: { folderId?: string; name?: string; userId?: string } | undefined, + ): UpdateFolderInput { + return new UpdateFolderInput({ + creatorId: args?.userId ?? TestHelper.generateTestId(), + folderId: args?.folderId ?? TestHelper.generateTestId(), + name: args?.name ?? `${randWord()}`, + }); + } - return dbClient.snippet.create({ data: createSnippetDto.toSnippet() }); -}; + static createTestSnippetInput( + args: { folderId?: string; name?: string; userId?: string; visibility?: SnippetVisibility } | undefined, + ): CreateSnippetInput { + const languages = ['java', 'js', 'ts', 'c', 'c++', 'python', 'go', 'php', 'csharp']; + const extensions = ['java', 'js', 'ts', 'c', 'cpp', 'py', 'go', 'php', 'cs']; + const themes = ['one-dark-pro', 'dracula', 'dark-plus', 'monokai', 'github-dark', 'github-light']; + + const languageIndex = randNumber({ max: languages.length - 1, min: 0 }); + const themeIndex = randNumber({ max: themes.length - 1, min: 0 }); + + const snippetContent = randWord({ length: randNumber({ max: 30, min: 5 }) }).join('\n'); + + return new CreateSnippetInput({ + content: snippetContent, + contentHighlighted: `${snippetContent} highlighted`, + description: randWord({ length: randNumber({ max: 20, min: 10 }) }).join(' '), + folderId: args?.folderId ?? TestHelper.generateTestId(), + language: languages[languageIndex], + lineHighlight: null, + name: args?.name ?? `${randWord()}.${extensions[languageIndex]}`, + theme: themes[themeIndex], + userId: args?.userId ?? TestHelper.generateTestId(), + visibility: args?.visibility ?? 'public', + }); + } -export const updateTestUserDto = (roleId: string): UpdateUserDto => { - const providers: OauthProvider[] = ['email', 'google', 'github', 'stackoverflow']; - const index = randNumber({ max: 2, min: 0 }); + async deleteTestSnippetsById(snippetIds: Array): Promise { + const promises = snippetIds.map(async (snippetId) => { + if (!snippetId) { + return; + } - return new UpdateUserDto({ - name: randFullName(), - oauthProvider: providers[index], - pictureUrl: randImg(), - roleId, - timezone: randTimeZone(), - }); -}; + await this.prisma.snippet.delete({ where: { id: snippetId } }); + }); -export const createTestSessionDto = (userId: string): CreateSessionDto => { - return new CreateSessionDto({ - expireDate: new Date(), - userId, - }); -}; + return Promise.all(promises); + } -export const createTestSession = async (args: { userId: string }): Promise => { - const createSessionDto = createTestSessionDto(args.userId); + async createTestSnippet( + args: { folderId?: string; name?: string; userId?: string; visibility?: SnippetVisibility } | undefined, + ): Promise { + const createSnippetInput = TestHelper.createTestSnippetInput(args); - return dbClient.session.create({ data: createSessionDto.toSession() }); -}; + return this.prisma.snippet.create({ data: createSnippetInput.toSnippet() }); + } -export const deleteTestUserSessions = async (userId: string): Promise => { - await dbClient.session.deleteMany({ where: { userId } }); -}; + static updateTestSnippetInput( + args: { name?: string; snippetId?: string; userId?: string; visibility?: SnippetVisibility } | undefined, + ): UpdateSnippetInput { + const languages = ['java', 'js', 'ts', 'c', 'c++', 'python', 'go', 'php', 'csharp']; + const extensions = ['java', 'js', 'ts', 'c', 'cpp', 'py', 'go', 'php', 'cs']; + const themes = ['one-dark-pro', 'dracula', 'dark-plus', 'monokai', 'github-dark', 'github-light']; + + const languageIndex = randNumber({ max: languages.length - 1, min: 0 }); + const themeIndex = randNumber({ max: themes.length - 1, min: 0 }); + + const snippetContent = randWord({ length: randNumber({ max: 30, min: 5 }) }).join('\n'); + + return new UpdateSnippetInput({ + content: snippetContent, + contentHighlighted: `${snippetContent} highlighted`, + creatorId: args?.userId ?? TestHelper.generateTestId(), + description: randWord({ length: randNumber({ max: 20, min: 10 }) }).join(' '), + language: languages[languageIndex], + lineHighlight: null, + name: args?.name ?? `${randWord()}.${extensions[languageIndex]}`, + snippetId: args?.snippetId ?? TestHelper.generateTestId(), + theme: themes[themeIndex], + visibility: args?.visibility ?? 'public', + }); + } -export const deleteTestSnippetDto = (args: { snippetId: string; userId: string }): DeleteSnippetDto => { - return new DeleteSnippetDto({ - creatorId: args.userId, - snippetId: args.snippetId, - }); -}; + static deleteTestSnippetInput(args: { snippetId: string; userId: string }): DeleteSnippetInput { + return new DeleteSnippetInput({ + creatorId: args.userId, + snippetId: args.snippetId, + }); + } -export const updateTestSnippetDto = ( - args: { name?: string; snippetId?: string; userId?: string; visibility?: SnippetVisibility } | undefined, -): UpdateSnippetDto => { - const languages = ['java', 'js', 'ts', 'c', 'c++', 'python', 'go', 'php', 'csharp']; - const extensions = ['java', 'js', 'ts', 'c', 'cpp', 'py', 'go', 'php', 'cs']; - const themes = ['one-dark-pro', 'dracula', 'dark-plus', 'monokai', 'github-dark', 'github-light']; - - const languageIndex = randNumber({ max: languages.length - 1, min: 0 }); - const themeIndex = randNumber({ max: themes.length - 1, min: 0 }); - - const snippetContent = randWord({ length: randNumber({ max: 30, min: 5 }) }).join('\n'); - - return new UpdateSnippetDto({ - content: snippetContent, - contentHighlighted: `${snippetContent} highlighted`, - creatorId: args?.userId ?? generateTestId(), - description: randWord({ length: randNumber({ max: 20, min: 10 }) }).join(' '), - language: languages[languageIndex], - lineHighlight: null, - name: args?.name ?? `${randWord()}.${extensions[languageIndex]}`, - snippetId: args?.snippetId ?? generateTestId(), - theme: themes[themeIndex], - visibility: args?.visibility ?? 'public', - }); -}; + static createTestSessionInput(userId: string): CreateSessionInput { + return new CreateSessionInput({ + expireDate: new Date(), + userId, + }); + } -export const updateTestFolderDto = ( - args: { folderId?: string; name?: string; userId?: string } | undefined, -): UpdateFolderDto => { - return new UpdateFolderDto({ - creatorId: args?.userId ?? generateTestId(), - folderId: args?.folderId ?? generateTestId(), - name: args?.name ?? `${randWord()}`, - }); -}; + async createTestSession(args: { userId: string }): Promise { + const createSessionInput = TestHelper.createTestSessionInput(args.userId); + + return this.prisma.session.create({ data: createSessionInput.toSession() }); + } + + async deleteTestUserSessions(userId: string): Promise { + await this.prisma.session.deleteMany({ where: { userId } }); + } +} diff --git a/packages/domain/tsconfig.json b/packages/domain/tsconfig.json index 450fe1ec..d5304608 100644 --- a/packages/domain/tsconfig.json +++ b/packages/domain/tsconfig.json @@ -1,16 +1,12 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "outDir": "./dist", "declaration": true, "composite": true, - "sourceMap": true }, + "exclude": ["node_modules", "dist"], "files": ["env.d.ts"], - "include": ["src", "tests", "index.ts"], - "exclude": [ - "node_modules" - ], + "include": ["src", "tests", "index.ts", "jest.config.ts"], "references": [ { "path": "../../packages/utils" diff --git a/packages/domain/tsconfig.prod.json b/packages/domain/tsconfig.prod.json index fd20e465..330301da 100644 --- a/packages/domain/tsconfig.prod.json +++ b/packages/domain/tsconfig.prod.json @@ -1,10 +1,13 @@ { - "extends": "./tsconfig", + "extends": "../../tsconfig.json", "compilerOptions": { - "sourceMap": false + "outDir": "./dist" }, "exclude": [ + "node_modules", + "dist", "tests", - "**/*.test.ts" + "**/*.test.ts", + "jest.config.ts" ] } diff --git a/packages/embed/.eslintrc.js b/packages/embed/.eslintrc.js new file mode 100644 index 00000000..33942f8f --- /dev/null +++ b/packages/embed/.eslintrc.js @@ -0,0 +1,19 @@ +module.exports = { + root: true, + extends: '../../.eslintrc.json', + ignorePatterns: [ + 'dist', + 'build', + 'tsup.config.ts', + 'env.d.ts', + 'src/server/public', + 'jest.config.ts', + '.eslintrc.js', + ], + parserOptions: { + ecmaVersion: 2023, + sourceType: 'module', + project: 'tsconfig.json', + tsconfigRootDir: __dirname, + }, +}; diff --git a/packages/embed/.eslintrc.json b/packages/embed/.eslintrc.json deleted file mode 100644 index 90eb5d03..00000000 --- a/packages/embed/.eslintrc.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "root": true, - "extends": "../../.eslintrc.json", - "ignorePatterns": [ - "dist", - "build", - "tsup.config.ts", - "env.d.ts", - "src/server/public", - "jest.config.ts" - ], - "parserOptions": { - "ecmaVersion": 2023, - "sourceType": "module", - "project": "tsconfig.json" - } -} diff --git a/packages/utils/.eslintrc.js b/packages/utils/.eslintrc.js new file mode 100644 index 00000000..ea147714 --- /dev/null +++ b/packages/utils/.eslintrc.js @@ -0,0 +1,11 @@ +module.exports = { + root: true, + extends: '../../.eslintrc.json', + ignorePatterns: ['jest.config.ts', '.eslintrc.js'], + parserOptions: { + ecmaVersion: 2023, + sourceType: 'module', + project: 'tsconfig.json', + tsconfigRootDir: __dirname, + }, +}; diff --git a/packages/utils/.eslintrc.json b/packages/utils/.eslintrc.json deleted file mode 100644 index dfdb1a26..00000000 --- a/packages/utils/.eslintrc.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "root": true, - "extends": "../../.eslintrc.json", - "ignorePatterns": [ - "jest.config.ts" - ], - "parserOptions": { - "ecmaVersion": 2023, - "sourceType": "module", - "project": "tsconfig.json" - } -} diff --git a/packages/utils/index.ts b/packages/utils/index.ts index 566d8fce..26e45e62 100644 --- a/packages/utils/index.ts +++ b/packages/utils/index.ts @@ -1,4 +1,4 @@ -import SnipcodeError from './src/errors/snipcode-error'; +export { SnipcodeError } from './src/errors/snipcode-error'; export * as constants from './src/common/constants'; export * as errors from './src/errors/messages'; @@ -8,5 +8,3 @@ export * from './src/common/uuid'; export * from './src/date/date'; export type { Language } from './src/types/snippet'; - -export default SnipcodeError; diff --git a/packages/utils/src/errors/snipcode-error.ts b/packages/utils/src/errors/snipcode-error.ts index 3e158b23..3d040e0c 100644 --- a/packages/utils/src/errors/snipcode-error.ts +++ b/packages/utils/src/errors/snipcode-error.ts @@ -1,6 +1,6 @@ import { ErrorCode } from './types'; -export default class SnipcodeError extends Error { +export class SnipcodeError extends Error { constructor( public message: string, public code: ErrorCode = 'INTERNAL_ERROR', diff --git a/tsconfig.json b/tsconfig.json index d4c7dff0..1dd8418c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,7 +10,7 @@ "path": "./packages/utils" }, { - "path": "./packages/logger" + "path": "./packages/logger-old" }, { "path": "./packages/database" diff --git a/yarn.lock b/yarn.lock index 237766ea..e93d69eb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5709,6 +5709,7 @@ __metadata: "@nestjs/platform-express": "npm:10.3.8" "@nestjs/schematics": "npm:10.1.1" "@nestjs/testing": "npm:10.3.8" + "@prisma/client": "npm:5.14.0" "@types/express": "npm:4.17.21" "@types/supertest": "npm:6.0.2" reflect-metadata: "npm:0.2.2" @@ -5771,11 +5772,12 @@ __metadata: resolution: "@snipcode/domain@workspace:packages/domain" dependencies: "@bugsnag/cuid": "npm:3.1.1" + "@nestjs/common": "npm:10.3.8" "@ngneat/falso": "npm:7.2.0" "@prisma/client": "npm:5.14.0" "@snipcode/utils": "workspace:*" "@types/bcryptjs": "npm:2.4.6" - axios: "npm:1.6.8" + axios: "npm:1.7.2" bcryptjs: "npm:2.4.3" nock: "npm:13.5.4" prisma: "npm:5.14.0" @@ -8039,6 +8041,17 @@ __metadata: languageName: node linkType: hard +"axios@npm:1.7.2": + version: 1.7.2 + resolution: "axios@npm:1.7.2" + dependencies: + follow-redirects: "npm:^1.15.6" + form-data: "npm:^4.0.0" + proxy-from-env: "npm:^1.1.0" + checksum: 10/6ae80dda9736bb4762ce717f1a26ff997d94672d3a5799ad9941c24d4fb019c1dff45be8272f08d1975d7950bac281f3ba24aff5ecd49ef5a04d872ec428782f + languageName: node + linkType: hard + "axios@npm:^1.6.2": version: 1.6.5 resolution: "axios@npm:1.6.5"