diff --git a/.eslintrc.json b/.eslintrc.json index a305c227..2ff8e72a 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -21,6 +21,7 @@ ], "rules": { "no-useless-constructor": "off", + "no-plusplus": "off", "@typescript-eslint/naming-convention": [ "error", { diff --git a/jest.config.js b/jest.config.js index 667f0c16..e9e6ac5e 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,3 +1,4 @@ +require('dotenv/config'); const { pathsToModuleNameMapper } = require('ts-jest/utils'); const { compilerOptions } = require('./tsconfig.json'); diff --git a/ormconfig.json b/ormconfig.json index bc9b5df6..0d2d729c 100644 --- a/ormconfig.json +++ b/ormconfig.json @@ -1,17 +1,20 @@ -{ - "type": "postgres", - "host": "localhost", - "port": 5432, - "username": "postgres", - "password": "docker", - "database": "hub", - "entities": [ - "./src/modules/*/infra/typeorm/entities/*.ts" - ], - "migrations": [ - "./src/shared/infra/typeorm/migrations/*.ts" - ], - "cli": { - "migrationsDir": "./src/shared/infra/typeorm/migrations" - } -} +{ + "type": "postgres", + "host": "localhost", + "port": 5432, + "username": "postgres", + "password": "docker", + "database": "hub", + "entities": [ + "./src/modules/*/infra/typeorm/entities/*.ts" + ], + "migrations": [ + "./src/shared/infra/typeorm/migrations/*.ts" + ], + "cli": { + "migrationsDir": "./src/shared/infra/typeorm/migrations" + }, + "seeds": [ + "./src/shared/infra/typeorm/seeders/*.ts" + ] +} diff --git a/package.json b/package.json index 498bb847..6a20858e 100644 --- a/package.json +++ b/package.json @@ -1,61 +1,64 @@ -{ - "name": "hub-api", - "version": "1.0.0", - "description": "API for Musics hub application.", - "main": "src/server.ts", - "author": { - "email": "matt_z6@hotmail.com", - "name": "Matheus Felipe Zanin", - "url": "https://github.com/MattZ6" - }, - "license": "MIT", - "scripts": { - "build": "tsc", - "dev:server": "tsnd -r tsconfig-paths/register --inspect --transpile-only --ignore-watch node_modules src/shared/infra/http/server.ts", - "typeorm": "tsnd -r tsconfig-paths/register ./node_modules/typeorm/cli.js", - "test": "jest" - }, - "devDependencies": { - "@types/bcryptjs": "2.4.2", - "@types/cors": "2.8.9", - "@types/express": "4.17.9", - "@types/faker": "5.1.5", - "@types/jest": "26.0.19", - "@types/jsonwebtoken": "8.5.0", - "@types/nodemailer": "6.4.0", - "@types/uuid": "8.3.0", - "@typescript-eslint/eslint-plugin": "4.11.1", - "@typescript-eslint/parser": "4.11.1", - "eslint": "7.17.0", - "eslint-config-airbnb-base": "14.2.1", - "eslint-config-prettier": "7.1.0", - "eslint-import-resolver-typescript": "2.3.0", - "eslint-plugin-import": "2.22.1", - "eslint-plugin-prettier": "3.3.0", - "jest": "26.6.3", - "prettier": "2.2.1", - "ts-jest": "26.4.4", - "ts-node-dev": "1.1.1", - "tsconfig-paths": "3.9.0", - "typescript": "4.1.3" - }, - "dependencies": { - "bcryptjs": "^2.4.3", - "celebrate": "^13.0.4", - "class-transformer": "^0.3.1", - "cors": "^2.8.5", - "date-fns": "^2.16.1", - "dotenv": "^8.2.0", - "express": "^4.17.1", - "express-async-errors": "^3.1.1", - "faker": "^5.1.0", - "handlebars": "^4.7.6", - "jsonwebtoken": "^8.5.1", - "nodemailer": "^6.4.17", - "pg": "^8.5.1", - "reflect-metadata": "^0.1.13", - "tsyringe": "^4.4.0", - "typeorm": "^0.2.29", - "uuid": "^8.3.2" - } -} +{ + "name": "hub-api", + "version": "1.0.0", + "description": "API for Musics hub application.", + "main": "src/server.ts", + "author": { + "email": "matt_z6@hotmail.com", + "name": "Matheus Felipe Zanin", + "url": "https://github.com/MattZ6" + }, + "license": "MIT", + "scripts": { + "build": "tsc", + "dev:server": "tsnd -r tsconfig-paths/register --inspect --transpile-only --ignore-watch node_modules src/shared/infra/http/server.ts", + "typeorm": "tsnd -r tsconfig-paths/register ./node_modules/typeorm/cli.js", + "test": "jest", + "db:seed": "tsnd -r tsconfig-paths/register ./node_modules/typeorm-seeding/dist/cli.js seed", + "db:config": "yarn typeorm migration:run && db:seed" + }, + "devDependencies": { + "@types/bcryptjs": "2.4.2", + "@types/cors": "2.8.9", + "@types/express": "4.17.9", + "@types/faker": "5.1.5", + "@types/jest": "26.0.19", + "@types/jsonwebtoken": "8.5.0", + "@types/nodemailer": "6.4.0", + "@types/uuid": "8.3.0", + "@typescript-eslint/eslint-plugin": "4.11.1", + "@typescript-eslint/parser": "4.11.1", + "eslint": "7.17.0", + "eslint-config-airbnb-base": "14.2.1", + "eslint-config-prettier": "7.1.0", + "eslint-import-resolver-typescript": "2.3.0", + "eslint-plugin-import": "2.22.1", + "eslint-plugin-prettier": "3.3.0", + "jest": "26.6.3", + "prettier": "2.2.1", + "ts-jest": "26.4.4", + "ts-node-dev": "1.1.1", + "tsconfig-paths": "3.9.0", + "typescript": "4.1.3" + }, + "dependencies": { + "bcryptjs": "^2.4.3", + "celebrate": "^13.0.4", + "class-transformer": "^0.3.1", + "cors": "^2.8.5", + "date-fns": "^2.16.1", + "dotenv": "^8.2.0", + "express": "^4.17.1", + "express-async-errors": "^3.1.1", + "faker": "^5.1.0", + "handlebars": "^4.7.6", + "jsonwebtoken": "^8.5.1", + "nodemailer": "^6.4.17", + "pg": "^8.5.1", + "reflect-metadata": "^0.1.13", + "tsyringe": "^4.4.0", + "typeorm": "^0.2.29", + "typeorm-seeding": "^1.6.1", + "uuid": "^8.3.2" + } +} diff --git a/src/modules/instruments/dtos/ICreateInstrumentDTO.ts b/src/modules/instruments/dtos/ICreateInstrumentDTO.ts new file mode 100644 index 00000000..55ca985f --- /dev/null +++ b/src/modules/instruments/dtos/ICreateInstrumentDTO.ts @@ -0,0 +1,4 @@ +export default interface ICreateInstrumentDTO { + name: string; + label: string; +} diff --git a/src/modules/instruments/dtos/IListInstrumentsDTO.ts b/src/modules/instruments/dtos/IListInstrumentsDTO.ts new file mode 100644 index 00000000..7d1a44c0 --- /dev/null +++ b/src/modules/instruments/dtos/IListInstrumentsDTO.ts @@ -0,0 +1,4 @@ +export default interface IListIntrumentsDTO { + field: 'name' | 'label' | 'created_at'; + order: 'ASC' | 'DESC'; +} diff --git a/src/modules/instruments/infra/http/controllers/InstrumentController.ts b/src/modules/instruments/infra/http/controllers/InstrumentController.ts new file mode 100644 index 00000000..ed002231 --- /dev/null +++ b/src/modules/instruments/infra/http/controllers/InstrumentController.ts @@ -0,0 +1,63 @@ +import { Request, Response } from 'express'; +import { container } from 'tsyringe'; + +import { EnumStatusCode } from '@shared/errors/AppError'; + +import ListInstrumentsService from '@modules/instruments/services/ListInstrumentsService'; +import CreateInstrumentService from '@modules/instruments/services/CreateInstrumentService'; +import UpdateInstrumentService from '@modules/instruments/services/UpdateInstrumentService'; + +type IIndexRequestQuery = { + order_by: 'name' | 'label' | 'created_at'; + order: 'ASC' | 'DESC'; +}; + +interface ICreateRequest { + name: string; + label: string; +} + +interface IUpdateRequest { + name: string; + label: string; +} + +export default class InstrumentController { + public async index(request: Request, response: Response): Promise { + const { order, order_by } = request.query as IIndexRequestQuery; + + const listInstruments = container.resolve(ListInstrumentsService); + + const instruments = await listInstruments.execute({ + field: order_by, + order, + }); + + return response.json(instruments); + } + + public async create(request: Request, response: Response): Promise { + const { name, label } = request.body as ICreateRequest; + + const createInstrument = container.resolve(CreateInstrumentService); + + const instrument = await createInstrument.execute({ name, label }); + + return response.status(EnumStatusCode.Created).json(instrument); + } + + public async update(request: Request, response: Response): Promise { + const { name, label } = request.body as IUpdateRequest; + const { id: instrument_id } = request.params; + + const updateInstrument = container.resolve(UpdateInstrumentService); + + const instrument = await updateInstrument.execute({ + instrument_id, + name, + label, + }); + + return response.json(instrument); + } +} diff --git a/src/modules/instruments/infra/http/routes/instruments.routes.ts b/src/modules/instruments/infra/http/routes/instruments.routes.ts new file mode 100644 index 00000000..00811727 --- /dev/null +++ b/src/modules/instruments/infra/http/routes/instruments.routes.ts @@ -0,0 +1,37 @@ +import { Router } from 'express'; +import { celebrate, Joi } from 'celebrate'; + +import ensureAuthenticated from '@modules/users/infra/http/middlewares/ensureAuthenticated'; + +import InstrumentController from '@modules/instruments/infra/http/controllers/InstrumentController'; + +const router = Router(); +const instrumentController = new InstrumentController(); + +router.use(ensureAuthenticated); + +router.get('', instrumentController.index); + +router.post( + '', + celebrate({ + body: Joi.object().keys({ + name: Joi.string().trim().required(), + label: Joi.string().trim().required(), + }), + }), + instrumentController.create +); + +router.put( + '/:id', + celebrate({ + body: Joi.object().keys({ + name: Joi.string().trim().required(), + label: Joi.string().trim().required(), + }), + }), + instrumentController.update +); + +export default router; diff --git a/src/modules/instruments/infra/typeorm/entities/Instrument.ts b/src/modules/instruments/infra/typeorm/entities/Instrument.ts new file mode 100644 index 00000000..964e078f --- /dev/null +++ b/src/modules/instruments/infra/typeorm/entities/Instrument.ts @@ -0,0 +1,29 @@ +import { + Entity, + Column, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, +} from 'typeorm'; + +export const INSTRUMENTS_TABLE_NAME = 'instruments'; + +@Entity(INSTRUMENTS_TABLE_NAME) +class Instrument { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column() + name: string; + + @Column() + label: string; + + @CreateDateColumn() + created_at: Date; + + @UpdateDateColumn() + updated_at: Date; +} + +export default Instrument; diff --git a/src/modules/instruments/infra/typeorm/repositories/InstrumentsRepository.ts b/src/modules/instruments/infra/typeorm/repositories/InstrumentsRepository.ts new file mode 100644 index 00000000..e068fa92 --- /dev/null +++ b/src/modules/instruments/infra/typeorm/repositories/InstrumentsRepository.ts @@ -0,0 +1,57 @@ +import { getRepository, Repository } from 'typeorm'; + +import Instrument from '@modules/instruments/infra/typeorm/entities/Instrument'; + +import ICreateInstrumentDTO from '@modules/instruments/dtos/ICreateInstrumentDTO'; +import IListInstrumentsDTO from '@modules/instruments/dtos/IListInstrumentsDTO'; +import IInstrumentsRepository from '@modules/instruments/repositories/IIntrumentsRepository'; + +class InstrumentsRepository implements IInstrumentsRepository { + private repository: Repository; + + constructor() { + this.repository = getRepository(Instrument); + } + + public async create({ + name, + label, + }: ICreateInstrumentDTO): Promise { + const instrument = this.repository.create({ + name, + label, + }); + + return this.repository.save(instrument); + } + + public async update(instrument: Instrument): Promise { + return this.repository.save(instrument); + } + + public async findById(id: string): Promise { + return this.repository.findOne({ + where: { id }, + }); + } + + public async findByName(name: string): Promise { + return this.repository + .createQueryBuilder('instrument') + .where(`LOWER(instrument.name) = LOWER('${name}')`) + .getOne(); + } + + public async find({ + field = 'name', + order = 'ASC', + }: IListInstrumentsDTO): Promise { + return this.repository.find({ + order: { + [field]: order, + }, + }); + } +} + +export default InstrumentsRepository; diff --git a/src/modules/instruments/repositories/IIntrumentsRepository.ts b/src/modules/instruments/repositories/IIntrumentsRepository.ts new file mode 100644 index 00000000..6ca940c2 --- /dev/null +++ b/src/modules/instruments/repositories/IIntrumentsRepository.ts @@ -0,0 +1,14 @@ +import Instrument from '@modules/instruments/infra/typeorm/entities/Instrument'; + +import ICreateInstrumentDTO from '@modules/instruments/dtos/ICreateInstrumentDTO'; +import IListInstrumentsDTO from '@modules/instruments/dtos/IListInstrumentsDTO'; + +export const INSTRUMENTS_REPOSITORY_INDENTIFIER = 'InstrumentsRepository'; + +export default interface IInstrumentsRepository { + create(data: ICreateInstrumentDTO): Promise; + update(data: Instrument): Promise; + find(data: IListInstrumentsDTO): Promise; + findById(id: string): Promise; + findByName(name: string): Promise; +} diff --git a/src/modules/instruments/repositories/fakes/FakeInstrumentsRepository.ts b/src/modules/instruments/repositories/fakes/FakeInstrumentsRepository.ts new file mode 100644 index 00000000..77490fa9 --- /dev/null +++ b/src/modules/instruments/repositories/fakes/FakeInstrumentsRepository.ts @@ -0,0 +1,69 @@ +import { v4 } from 'uuid'; + +import Instrument from '@modules/instruments/infra/typeorm/entities/Instrument'; + +import ICreateInstrumentDTO from '@modules/instruments/dtos/ICreateInstrumentDTO'; +import IListInstrumentsDTO from '@modules/instruments/dtos/IListInstrumentsDTO'; +import IIntrumentsRepository from '@modules/instruments/repositories/IIntrumentsRepository'; + +class FakeInstrumentsRepository implements IIntrumentsRepository { + private instruments: Instrument[]; + + constructor() { + this.instruments = []; + } + + public async create({ + name, + label, + }: ICreateInstrumentDTO): Promise { + const instrument = new Instrument(); + + Object.assign(instrument, { + id: v4(), + name, + label, + created_at: new Date(), + updated_at: new Date(), + } as Instrument); + + this.instruments.push(instrument); + + return instrument; + } + + public async update(instrument: Instrument): Promise { + const index = this.instruments.findIndex(x => x.id === instrument.id); + + Object.assign(this.instruments[index], { + ...instrument, + updated_at: new Date(), + }); + + return this.instruments[index]; + } + + public async find({ + field, + order, + }: IListInstrumentsDTO): Promise { + const IF_RETURN = order === 'ASC' ? 1 : -1; + const ELSE_RETURN = order === 'ASC' ? -1 : 1; + + return this.instruments.sort((a, b) => + a[field] < b[field] ? IF_RETURN : ELSE_RETURN + ); + } + + public async findById(id: string): Promise { + return this.instruments.find(instrument => instrument.id === id); + } + + public async findByName(name: string): Promise { + return this.instruments.find( + instrument => instrument.name.toLowerCase() === name.toLowerCase() + ); + } +} + +export default FakeInstrumentsRepository; diff --git a/src/modules/instruments/services/CreateInstrumentService.spec.ts b/src/modules/instruments/services/CreateInstrumentService.spec.ts new file mode 100644 index 00000000..fb0a09f3 --- /dev/null +++ b/src/modules/instruments/services/CreateInstrumentService.spec.ts @@ -0,0 +1,45 @@ +import faker from 'faker'; + +import AppError from '@shared/errors/AppError'; + +import FakeInstrumentsRepository from '@modules/instruments/repositories/fakes/FakeInstrumentsRepository'; +import CreateInstrumentService from '@modules/instruments/services/CreateInstrumentService'; + +let fakeInstrumentsRepository: FakeInstrumentsRepository; +let createInstrument: CreateInstrumentService; + +describe('CreateInstrument', () => { + beforeEach(() => { + fakeInstrumentsRepository = new FakeInstrumentsRepository(); + + createInstrument = new CreateInstrumentService(fakeInstrumentsRepository); + }); + + it('should be able to create a new instrument', async () => { + const name = `${faker.name.title()} ${faker.name.title()}`; + const label = faker.name.title(); + + const instrument = await createInstrument.execute({ + name, + label, + }); + + expect(instrument).toHaveProperty('id'); + expect(instrument.name).toBe(name); + expect(instrument.label).toBe(label); + }); + + it('should not be able to create a new instrument with the name of another', async () => { + const instrument = await fakeInstrumentsRepository.create({ + name: `${faker.name.title()} ${faker.name.title()}`, + label: faker.name.title(), + }); + + await expect( + createInstrument.execute({ + name: instrument.name, + label: faker.name.title(), + }) + ).rejects.toBeInstanceOf(AppError); + }); +}); diff --git a/src/modules/instruments/services/CreateInstrumentService.ts b/src/modules/instruments/services/CreateInstrumentService.ts new file mode 100644 index 00000000..7817722b --- /dev/null +++ b/src/modules/instruments/services/CreateInstrumentService.ts @@ -0,0 +1,39 @@ +import { inject, injectable } from 'tsyringe'; + +import AppError, { EnumStatusCode } from '@shared/errors/AppError'; + +import Instrument from '@modules/instruments/infra/typeorm/entities/Instrument'; + +import IIntrumentsRepository, { + INSTRUMENTS_REPOSITORY_INDENTIFIER, +} from '@modules/instruments/repositories/IIntrumentsRepository'; + +interface IRequest { + name: string; + label: string; +} + +@injectable() +class CreateInstrumentService { + constructor( + @inject(INSTRUMENTS_REPOSITORY_INDENTIFIER) + private instrumentsRepository: IIntrumentsRepository + ) {} + + public async execute({ name, label }: IRequest): Promise { + const instrumentWithThisName = await this.instrumentsRepository.findByName( + name + ); + + if (instrumentWithThisName) { + throw new AppError( + 'There is already an instrument registered with this name', + EnumStatusCode.Conflict + ); + } + + return this.instrumentsRepository.create({ name, label }); + } +} + +export default CreateInstrumentService; diff --git a/src/modules/instruments/services/ListInstrumentsService.spec.ts b/src/modules/instruments/services/ListInstrumentsService.spec.ts new file mode 100644 index 00000000..63394596 --- /dev/null +++ b/src/modules/instruments/services/ListInstrumentsService.spec.ts @@ -0,0 +1,88 @@ +import faker from 'faker'; + +import Instrument from '@modules/instruments/infra/typeorm/entities/Instrument'; + +import FakeInstrumentsRepository from '@modules/instruments/repositories/fakes/FakeInstrumentsRepository'; +import ListInstrumentsService from '@modules/instruments/services/ListInstrumentsService'; + +let fakeInstrumentsRepository: FakeInstrumentsRepository; +let listInstruments: ListInstrumentsService; + +describe('ListInstruments', () => { + beforeEach(() => { + fakeInstrumentsRepository = new FakeInstrumentsRepository(); + + listInstruments = new ListInstrumentsService(fakeInstrumentsRepository); + }); + + it('should be able to list instruments', async () => { + const length = 10; + const requests: Promise[] = []; + + for (let i = 0; i < length; i++) { + requests.push( + fakeInstrumentsRepository.create({ + name: faker.name.title(), + label: faker.name.title(), + }) + ); + } + + const instruments = await Promise.all(requests); + + const instrumentsSearched = await listInstruments.execute({}); + + expect(instrumentsSearched).toHaveLength(length); + expect(instrumentsSearched).toEqual(expect.arrayContaining(instruments)); + }); + + it('should be able to list instruments ordered by name ASC', async () => { + const length = 13; + const requests: Promise[] = []; + + for (let i = 0; i < length; i++) { + requests.push( + fakeInstrumentsRepository.create({ + name: faker.name.title(), + label: faker.name.title(), + }) + ); + } + + const instruments = await Promise.all(requests); + + const instrumentsSearched = await listInstruments.execute({ + field: 'name', + order: 'ASC', + }); + + expect(instruments.sort((a, b) => (a.name < b.name ? 1 : -1))).toEqual( + instrumentsSearched + ); + }); + + it('should be able to list instruments ordered by created_at DESC', async () => { + const length = 15; + const requests: Promise[] = []; + + for (let i = 0; i < length; i++) { + requests.push( + fakeInstrumentsRepository.create({ + name: faker.name.title(), + label: faker.name.title(), + }) + ); + } + + const instruments = await Promise.all(requests); + + const instrumentsSearched = await listInstruments.execute({ + field: 'created_at', + order: 'DESC', + }); + + expect( + instruments.sort((a, b) => (a.created_at < b.created_at ? -1 : 1)) + ).toEqual(instrumentsSearched); + }); +}); diff --git a/src/modules/instruments/services/ListInstrumentsService.ts b/src/modules/instruments/services/ListInstrumentsService.ts new file mode 100644 index 00000000..03e4f5ee --- /dev/null +++ b/src/modules/instruments/services/ListInstrumentsService.ts @@ -0,0 +1,29 @@ +import { inject, injectable } from 'tsyringe'; + +import Instrument from '@modules/instruments/infra/typeorm/entities/Instrument'; + +import IIntrumentsRepository, { + INSTRUMENTS_REPOSITORY_INDENTIFIER, +} from '@modules/instruments/repositories/IIntrumentsRepository'; + +interface IRequest { + field?: 'name' | 'label' | 'created_at'; + order?: 'ASC' | 'DESC'; +} + +@injectable() +class ListInstrumentsService { + constructor( + @inject(INSTRUMENTS_REPOSITORY_INDENTIFIER) + private instrumentsRepository: IIntrumentsRepository + ) {} + + public async execute({ field, order }: IRequest): Promise { + return this.instrumentsRepository.find({ + field: field ?? 'name', + order: order ?? 'ASC', + }); + } +} + +export default ListInstrumentsService; diff --git a/src/modules/instruments/services/UpdateInstrumentService.spec.ts b/src/modules/instruments/services/UpdateInstrumentService.spec.ts new file mode 100644 index 00000000..4c47bd40 --- /dev/null +++ b/src/modules/instruments/services/UpdateInstrumentService.spec.ts @@ -0,0 +1,66 @@ +import faker from 'faker'; + +import AppError from '@shared/errors/AppError'; + +import FakeInstrumentsRepository from '@modules/instruments/repositories/fakes/FakeInstrumentsRepository'; +import UpdateInstrumentService from '@modules/instruments/services/UpdateInstrumentService'; + +let fakeInstrumentsRepository: FakeInstrumentsRepository; +let updateInstrument: UpdateInstrumentService; + +describe('UpdateInstrument', () => { + beforeEach(() => { + fakeInstrumentsRepository = new FakeInstrumentsRepository(); + + updateInstrument = new UpdateInstrumentService(fakeInstrumentsRepository); + }); + + it('should be able to update the data of an instrument', async () => { + const { id } = await fakeInstrumentsRepository.create({ + name: faker.name.title(), + label: faker.name.title(), + }); + + const updatedName = 'Guitar'; + const updatedLabel = 'guitarrist'; + + const updatedInstrument = await updateInstrument.execute({ + instrument_id: id, + name: updatedName, + label: updatedLabel, + }); + + expect(updatedInstrument.name).toBe(updatedName); + expect(updatedInstrument.label).toBe(updatedLabel); + }); + + it('should not be able to update the data of a non existing instrument', async () => { + await expect( + updateInstrument.execute({ + instrument_id: faker.random.uuid(), + name: faker.name.title(), + label: faker.name.title(), + }) + ).rejects.toBeInstanceOf(AppError); + }); + + it('should not be able to change to another instrument name', async () => { + const { name } = await fakeInstrumentsRepository.create({ + name: faker.name.title(), + label: faker.name.title(), + }); + + const instrument = await fakeInstrumentsRepository.create({ + name: faker.name.title(), + label: faker.name.title(), + }); + + await expect( + updateInstrument.execute({ + instrument_id: instrument.id, + name, + label: faker.name.title(), + }) + ).rejects.toBeInstanceOf(AppError); + }); +}); diff --git a/src/modules/instruments/services/UpdateInstrumentService.ts b/src/modules/instruments/services/UpdateInstrumentService.ts new file mode 100644 index 00000000..f778bcb5 --- /dev/null +++ b/src/modules/instruments/services/UpdateInstrumentService.ts @@ -0,0 +1,53 @@ +import { inject, injectable } from 'tsyringe'; + +import AppError, { EnumStatusCode } from '@shared/errors/AppError'; + +import Instrument from '@modules/instruments/infra/typeorm/entities/Instrument'; + +import IIntrumentsRepository, { + INSTRUMENTS_REPOSITORY_INDENTIFIER, +} from '@modules/instruments/repositories/IIntrumentsRepository'; + +interface IRequest { + instrument_id: string; + name: string; + label: string; +} + +@injectable() +class UpdateInstrumentService { + constructor( + @inject(INSTRUMENTS_REPOSITORY_INDENTIFIER) + private instrumentsRepository: IIntrumentsRepository + ) {} + + public async execute({ + instrument_id, + name, + label, + }: IRequest): Promise { + const instrument = await this.instrumentsRepository.findById(instrument_id); + + if (!instrument) { + throw new AppError('Instrument not found', EnumStatusCode.NotFound); + } + + const instrumentWithThisName = await this.instrumentsRepository.findByName( + name + ); + + if (instrumentWithThisName && instrumentWithThisName.id !== instrument_id) { + throw new AppError( + 'There is already an instrument registered with this name', + EnumStatusCode.Conflict + ); + } + + instrument.name = name; + instrument.label = label; + + return this.instrumentsRepository.update(instrument); + } +} + +export default UpdateInstrumentService; diff --git a/src/modules/users/dtos/ICreateUserDTO.ts b/src/modules/users/dtos/ICreateUserDTO.ts index 0bd36f42..eff8d172 100644 --- a/src/modules/users/dtos/ICreateUserDTO.ts +++ b/src/modules/users/dtos/ICreateUserDTO.ts @@ -1,6 +1,6 @@ -export default interface ICreateUserDTO { - name: string; - nick_name: string; - email: string; - password_hash: string; -} +export default interface ICreateUserDTO { + name: string; + nick_name: string; + email: string; + password_hash: string; +} diff --git a/src/modules/users/infra/http/controllers/UserController.ts b/src/modules/users/infra/http/controllers/UserController.ts index 8d25c482..e91ce1f1 100644 --- a/src/modules/users/infra/http/controllers/UserController.ts +++ b/src/modules/users/infra/http/controllers/UserController.ts @@ -3,6 +3,7 @@ import { container } from 'tsyringe'; import { classToClass } from 'class-transformer'; import CreateUsersService from '@modules/users/services/CreateUserService'; +import { EnumStatusCode } from '@shared/errors/AppError'; interface IPostBodyDTO { name: string; @@ -26,6 +27,6 @@ export default class UserController { const data = classToClass(user); - return response.json(data); + return response.status(EnumStatusCode.Created).json(data); } } diff --git a/src/modules/users/repositories/fakes/FakeUsersRepository.ts b/src/modules/users/repositories/fakes/FakeUsersRepository.ts index 201215de..61957ddb 100644 --- a/src/modules/users/repositories/fakes/FakeUsersRepository.ts +++ b/src/modules/users/repositories/fakes/FakeUsersRepository.ts @@ -4,7 +4,7 @@ import User from '@modules/users/infra/typeorm/entities/User'; import IUsersRepository from '@modules/users/repositories/IUsersRepository'; import ICreateUserDTO from '@modules/users/dtos/ICreateUserDTO'; -class UsersRepository implements IUsersRepository { +class FakeUsersRepository implements IUsersRepository { private users: User[]; constructor() { @@ -55,4 +55,4 @@ class UsersRepository implements IUsersRepository { } } -export default UsersRepository; +export default FakeUsersRepository; diff --git a/src/modules/users/services/CreateUserService.ts b/src/modules/users/services/CreateUserService.ts index 0c198aee..9aebfc80 100644 --- a/src/modules/users/services/CreateUserService.ts +++ b/src/modules/users/services/CreateUserService.ts @@ -19,8 +19,6 @@ interface IRequest { password: string; } -type IResponse = Omit; - @injectable() class CreateUserService { constructor( @@ -36,23 +34,21 @@ class CreateUserService { nick_name, email, password, - }: IRequest): Promise { - const alreadyExistsWithThisEmail = await this.usersRepository.findByEmail( - email - ); + }: IRequest): Promise { + const userWithThisEmail = await this.usersRepository.findByEmail(email); - if (alreadyExistsWithThisEmail) { + if (userWithThisEmail) { throw new AppError( 'Email address already been taken', EnumStatusCode.Conflict ); } - const alreadyExistsWithThisNickName = await this.usersRepository.findByNickName( + const userWithThisNickName = await this.usersRepository.findByNickName( nick_name ); - if (alreadyExistsWithThisNickName) { + if (userWithThisNickName) { throw new AppError( 'Nick name already been taken', EnumStatusCode.Conflict @@ -68,14 +64,7 @@ class CreateUserService { password_hash: passwordHash, }); - return { - id: user.id, - name: user.name, - nick_name: user.nick_name, - email: user.email, - created_at: user.created_at, - updated_at: user.updated_at, - }; + return user; } } diff --git a/src/modules/users/services/UpdateProfileService.spec.ts b/src/modules/users/services/UpdateProfileService.spec.ts index 7dc155b4..601937ed 100644 --- a/src/modules/users/services/UpdateProfileService.spec.ts +++ b/src/modules/users/services/UpdateProfileService.spec.ts @@ -1,102 +1,102 @@ -import faker from 'faker'; - -import AppError from '@shared/errors/AppError'; - -import FakeUsersRepository from '@modules/users/repositories/fakes/FakeUsersRepository'; -import UpdateProfileService from '@modules/users/services/UpdateProfileService'; - -let fakeUsersRepository: FakeUsersRepository; -let updateProfile: UpdateProfileService; - -describe('UpdateProfile', () => { - beforeEach(() => { - fakeUsersRepository = new FakeUsersRepository(); - - updateProfile = new UpdateProfileService(fakeUsersRepository); - }); - - it('should be able to update user data', async () => { - const { id } = await fakeUsersRepository.create({ - name: `${faker.name.firstName()} ${faker.name.lastName()}`, - nick_name: faker.internet.userName(), - email: faker.internet.email(), - password_hash: faker.internet.password(), - }); - - const newNickName = faker.internet.userName(); - const newName = `${faker.name.firstName()} ${faker.name.lastName()}`; - const newEmail = faker.internet.email(); - - const user = await updateProfile.execute({ - user_id: id, - name: newName, - nick_name: newNickName, - email: newEmail, - }); - - expect(user.name).toBe(newName); - expect(user.nick_name).toBe(newNickName); - expect(user.email).toBe(newEmail); - }); - - it('should not be able to update a non existing user', async () => { - await expect( - updateProfile.execute({ - user_id: faker.random.uuid(), - nick_name: faker.internet.userName(), - name: `${faker.name.firstName()} ${faker.name.lastName()}`, - email: faker.internet.email(), - }) - ).rejects.toBeInstanceOf(AppError); - }); - - it('should not be able to change to another user nick name', async () => { - const { id } = await fakeUsersRepository.create({ - name: `${faker.name.firstName()} ${faker.name.lastName()}`, - nick_name: faker.internet.userName(), - email: faker.internet.email(), - password_hash: faker.internet.password(), - }); - - const user = await fakeUsersRepository.create({ - name: `${faker.name.firstName()} ${faker.name.lastName()}`, - nick_name: faker.internet.userName(), - email: faker.internet.email(), - password_hash: faker.internet.password(), - }); - - await expect( - updateProfile.execute({ - user_id: id, - nick_name: user.nick_name, - name: `${faker.name.firstName()} ${faker.name.lastName()}`, - email: faker.internet.email(), - }) - ).rejects.toBeInstanceOf(AppError); - }); - - it('should not be able to change to another user email', async () => { - const { id } = await fakeUsersRepository.create({ - name: `${faker.name.firstName()} ${faker.name.lastName()}`, - nick_name: faker.internet.userName(), - email: faker.internet.email(), - password_hash: faker.internet.password(), - }); - - const user = await fakeUsersRepository.create({ - name: `${faker.name.firstName()} ${faker.name.lastName()}`, - nick_name: faker.internet.userName(), - email: faker.internet.email(), - password_hash: faker.internet.password(), - }); - - await expect( - updateProfile.execute({ - user_id: id, - nick_name: faker.internet.userName(), - name: `${faker.name.firstName()} ${faker.name.lastName()}`, - email: user.email, - }) - ).rejects.toBeInstanceOf(AppError); - }); -}); +import faker from 'faker'; + +import AppError from '@shared/errors/AppError'; + +import FakeUsersRepository from '@modules/users/repositories/fakes/FakeUsersRepository'; +import UpdateProfileService from '@modules/users/services/UpdateProfileService'; + +let fakeUsersRepository: FakeUsersRepository; +let updateProfile: UpdateProfileService; + +describe('UpdateProfile', () => { + beforeEach(() => { + fakeUsersRepository = new FakeUsersRepository(); + + updateProfile = new UpdateProfileService(fakeUsersRepository); + }); + + it('should be able to update user data', async () => { + const { id } = await fakeUsersRepository.create({ + name: `${faker.name.firstName()} ${faker.name.lastName()}`, + nick_name: faker.internet.userName(), + email: faker.internet.email(), + password_hash: faker.internet.password(), + }); + + const newNickName = faker.internet.userName(); + const newName = `${faker.name.firstName()} ${faker.name.lastName()}`; + const newEmail = faker.internet.email(); + + const user = await updateProfile.execute({ + user_id: id, + name: newName, + nick_name: newNickName, + email: newEmail, + }); + + expect(user.name).toBe(newName); + expect(user.nick_name).toBe(newNickName); + expect(user.email).toBe(newEmail); + }); + + it('should not be able to update a non existing user', async () => { + await expect( + updateProfile.execute({ + user_id: faker.random.uuid(), + nick_name: faker.internet.userName(), + name: `${faker.name.firstName()} ${faker.name.lastName()}`, + email: faker.internet.email(), + }) + ).rejects.toBeInstanceOf(AppError); + }); + + it('should not be able to change to another user nick name', async () => { + const { id } = await fakeUsersRepository.create({ + name: `${faker.name.firstName()} ${faker.name.lastName()}`, + nick_name: faker.internet.userName(), + email: faker.internet.email(), + password_hash: faker.internet.password(), + }); + + const user = await fakeUsersRepository.create({ + name: `${faker.name.firstName()} ${faker.name.lastName()}`, + nick_name: faker.internet.userName(), + email: faker.internet.email(), + password_hash: faker.internet.password(), + }); + + await expect( + updateProfile.execute({ + user_id: id, + nick_name: user.nick_name, + name: `${faker.name.firstName()} ${faker.name.lastName()}`, + email: faker.internet.email(), + }) + ).rejects.toBeInstanceOf(AppError); + }); + + it('should not be able to change to another user email', async () => { + const { id } = await fakeUsersRepository.create({ + name: `${faker.name.firstName()} ${faker.name.lastName()}`, + nick_name: faker.internet.userName(), + email: faker.internet.email(), + password_hash: faker.internet.password(), + }); + + const user = await fakeUsersRepository.create({ + name: `${faker.name.firstName()} ${faker.name.lastName()}`, + nick_name: faker.internet.userName(), + email: faker.internet.email(), + password_hash: faker.internet.password(), + }); + + await expect( + updateProfile.execute({ + user_id: id, + nick_name: faker.internet.userName(), + name: `${faker.name.firstName()} ${faker.name.lastName()}`, + email: user.email, + }) + ).rejects.toBeInstanceOf(AppError); + }); +}); diff --git a/src/shared/container/index.ts b/src/shared/container/index.ts index b199debb..6616b1be 100644 --- a/src/shared/container/index.ts +++ b/src/shared/container/index.ts @@ -14,6 +14,11 @@ import IUserTokensRepository, { } from '@modules/users/repositories/IUserTokensRepository'; import UserTokensRepository from '@modules/users/infra/typeorm/repositories/UserTokensRepository'; +import IIntrumentsRepository, { + INSTRUMENTS_REPOSITORY_INDENTIFIER, +} from '@modules/instruments/repositories/IIntrumentsRepository'; +import InstrumentsRepository from '@modules/instruments/infra/typeorm/repositories/InstrumentsRepository'; + container.registerSingleton( USERS_REPOSITORY_INDENTIFIER, UsersRepository @@ -23,3 +28,8 @@ container.registerSingleton( USER_TOKENS_REPOSITORY_INDENTIFIER, UserTokensRepository ); + +container.registerSingleton( + INSTRUMENTS_REPOSITORY_INDENTIFIER, + InstrumentsRepository +); diff --git a/src/shared/dtos/EnumAppError.ts b/src/shared/dtos/EnumAppError.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/shared/infra/http/routes/index.ts b/src/shared/infra/http/routes/index.ts index 1ad60e88..d98fd457 100644 --- a/src/shared/infra/http/routes/index.ts +++ b/src/shared/infra/http/routes/index.ts @@ -1,15 +1,17 @@ -import { Router } from 'express'; - -import usersRouter from '@modules/users/infra/http/routes/users.routes'; -import passwordRouter from '@modules/users/infra/http/routes/password.routes'; -import sessionsRouter from '@modules/users/infra/http/routes/session.routes'; -import profileRouter from '@modules/users/infra/http/routes/profile.routes'; - -const router = Router(); - -router.use('/v1/sessions', sessionsRouter); -router.use('/v1/users', usersRouter); -router.use('/v1/password', passwordRouter); -router.use('/v1/profile', profileRouter); - -export default router; +import { Router } from 'express'; + +import usersRouter from '@modules/users/infra/http/routes/users.routes'; +import passwordRouter from '@modules/users/infra/http/routes/password.routes'; +import sessionsRouter from '@modules/users/infra/http/routes/session.routes'; +import profileRouter from '@modules/users/infra/http/routes/profile.routes'; +import instrumentsRouter from '@modules/instruments/infra/http/routes/instruments.routes'; + +const router = Router(); + +router.use('/v1/sessions', sessionsRouter); +router.use('/v1/users', usersRouter); +router.use('/v1/password', passwordRouter); +router.use('/v1/profile', profileRouter); +router.use('/v1/instruments', instrumentsRouter); + +export default router; diff --git a/src/shared/infra/typeorm/migrations/1609607462433-CreateInstruments.ts b/src/shared/infra/typeorm/migrations/1609607462433-CreateInstruments.ts new file mode 100644 index 00000000..2528ac42 --- /dev/null +++ b/src/shared/infra/typeorm/migrations/1609607462433-CreateInstruments.ts @@ -0,0 +1,51 @@ +import { MigrationInterface, QueryRunner, Table } from 'typeorm'; + +import { INSTRUMENTS_TABLE_NAME } from '@modules/instruments/infra/typeorm/entities/Instrument'; + +export default class CreateInstruments1609607462433 + implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.createTable( + new Table({ + name: INSTRUMENTS_TABLE_NAME, + columns: [ + { + name: 'id', + type: 'uuid', + isPrimary: true, + generationStrategy: 'uuid', + default: 'uuid_generate_v4()', + }, + { + name: 'name', + type: 'varchar', + isUnique: true, + }, + { + name: 'label', + type: 'varchar', + }, + { + name: 'created_at', + type: 'timestamp', + default: 'now()', + }, + { + name: 'updated_at', + type: 'timestamp', + default: 'now()', + }, + ], + indices: [ + { + columnNames: ['name'], + }, + ], + }) + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropTable(INSTRUMENTS_TABLE_NAME); + } +} diff --git a/src/shared/infra/typeorm/seeders/CreateInstrumentsSeeder.ts b/src/shared/infra/typeorm/seeders/CreateInstrumentsSeeder.ts new file mode 100644 index 00000000..4e6d7088 --- /dev/null +++ b/src/shared/infra/typeorm/seeders/CreateInstrumentsSeeder.ts @@ -0,0 +1,68 @@ +import { Connection } from 'typeorm'; +import { Factory, Seeder } from 'typeorm-seeding'; + +import Instrument from '@modules/instruments/infra/typeorm/entities/Instrument'; + +class CreateInstrumentsSeeder implements Seeder { + public async run(_: Factory, connection: Connection): Promise { + const query = connection + .createQueryBuilder() + .from(Instrument, 'instrument'); + + const count = await query.getCount(); + + if (count) { + console.log(` + ----------------------------------- + --- Instrumentos já cadastrados --- + ----------------------------------- + `); + + return; + } + + await query + .insert() + .values([ + { + name: 'Guitarra', + label: 'guitarrista', + }, + { + name: 'Bateria', + label: 'baterista', + }, + { + name: 'Contrabaixo', + label: 'baixista', + }, + { + name: 'Contrabaixo acústico', + label: 'baixista', + }, + { + name: 'Violão', + label: 'violonista', + }, + { + name: 'Vocal', + label: 'vocalista', + }, + { + name: 'Teclado', + label: 'tecladista', + }, + { + name: 'Percussão', + label: 'percussionista', + }, + { + name: 'Saxofone', + label: 'saxofonista', + }, + ]) + .execute(); + } +} + +export default CreateInstrumentsSeeder; diff --git a/tsconfig.json b/tsconfig.json index ca160ab5..8e700f78 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,8 +6,8 @@ "rootDir": "./src", "emitDecoratorMetadata": true, "strict": true, - "strictPropertyInitialization": false, "allowJs": true, + "strictPropertyInitialization": false, "baseUrl": "./src", "paths": { "@modules/*": [ diff --git a/yarn.lock b/yarn.lock index 0d9f4419..74f7ce47 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1330,7 +1330,7 @@ chalk@^1.1.1: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.0: +chalk@^2.0.0, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -1339,6 +1339,14 @@ chalk@^2.0.0: escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chalk@^4.0.0, chalk@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" @@ -1392,6 +1400,13 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + cli-highlight@^2.1.4: version "2.1.9" resolved "https://registry.yarnpkg.com/cli-highlight/-/cli-highlight-2.1.9.tgz#4f4ecb05326d70d56d4b4249fabf9a70fb002497" @@ -1404,6 +1419,11 @@ cli-highlight@^2.1.4: parse5-htmlparser2-tree-adapter "^6.0.0" yargs "^15.0.0" +cli-spinners@^2.2.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.5.0.tgz#12763e47251bf951cb75c201dfa58ff1bcb2d047" + integrity sha512-PC+AmIuK04E6aeSs/pUccSujsTzBhu4HzC2dL+CfJB/Jcc2qTRbEwZQDfIUpt2Xl8BodYBEq8w4fc0kU2I9DjQ== + cliui@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" @@ -1422,6 +1442,11 @@ cliui@^7.0.2: strip-ansi "^6.0.0" wrap-ansi "^7.0.0" +clone@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= + co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -1655,6 +1680,13 @@ deepmerge@^4.2.2: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== +defaults@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" + integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730= + dependencies: + clone "^1.0.2" + define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" @@ -2201,6 +2233,11 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= +faker@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/faker/-/faker-4.1.0.tgz#1e45bbbecc6774b3c195fad2835109c6d748cc3f" + integrity sha1-HkW7vsxndLPBlfrSg1EJxtdIzD8= + faker@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/faker/-/faker-5.1.0.tgz#e10fa1dec4502551aee0eb771617a7e7b94692e8" @@ -2453,7 +2490,7 @@ glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@~5.1.0: dependencies: is-glob "^4.0.1" -glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: +glob@7.1.6, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -2851,6 +2888,11 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" +is-interactive@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" + integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== + is-negative-zero@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" @@ -3661,6 +3703,13 @@ lodash@4.17.x, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== +log-symbols@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4" + integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ== + dependencies: + chalk "^2.4.2" + loud-rejection@^1.0.0: version "1.6.0" resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" @@ -3847,6 +3896,11 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +mute-stream@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" + integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== + mz@^2.4.0: version "2.7.0" resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" @@ -4079,6 +4133,20 @@ optionator@^0.9.1: type-check "^0.4.0" word-wrap "^1.2.3" +ora@4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/ora/-/ora-4.0.3.tgz#752a1b7b4be4825546a7a3d59256fa523b6b6d05" + integrity sha512-fnDebVFyz309A73cqCipVL1fBZewq4vwgSHfxh43vVy31mbyoQ8sCH3Oeaog/owYOs/lLlGVPCISQonTneg6Pg== + dependencies: + chalk "^3.0.0" + cli-cursor "^3.1.0" + cli-spinners "^2.2.0" + is-interactive "^1.0.0" + log-symbols "^3.0.0" + mute-stream "0.0.8" + strip-ansi "^6.0.0" + wcwidth "^1.0.1" + p-each-series@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.2.0.tgz#105ab0357ce72b202a8a8b94933672657b5e2a9a" @@ -4555,7 +4623,7 @@ redent@^1.0.0: indent-string "^2.1.0" strip-indent "^1.0.1" -reflect-metadata@^0.1.13: +reflect-metadata@0.1.13, reflect-metadata@^0.1.13: version "0.1.13" resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== @@ -4677,6 +4745,14 @@ resolve@^1.0.0, resolve@^1.10.0, resolve@^1.13.1, resolve@^1.17.0, resolve@^1.18 is-core-module "^2.1.0" path-parse "^1.0.6" +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" @@ -5449,6 +5525,18 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" +typeorm-seeding@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/typeorm-seeding/-/typeorm-seeding-1.6.1.tgz#4fe3a1aec9a611007d1135419cde286cced8defd" + integrity sha512-xJIW1pp72hv6npPqbQ7xDvawcDmS60EDUjK++UCfiqT0WE4xTzCn+QK1ZijLkD3GYCqFPuFt4nmeyRJn6VO2Vw== + dependencies: + chalk "^4.0.0" + faker "4.1.0" + glob "7.1.6" + ora "4.0.3" + reflect-metadata "0.1.13" + yargs "15.3.1" + typeorm@^0.2.29: version "0.2.29" resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.2.29.tgz#401289dc91900d72eccb26e31cdb7f0591a2272e" @@ -5598,6 +5686,13 @@ walker@^1.0.7, walker@~1.0.5: dependencies: makeerror "1.0.x" +wcwidth@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" + integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g= + dependencies: + defaults "^1.0.3" + webidl-conversions@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" @@ -5753,7 +5848,7 @@ yargs-parser@20.x, yargs-parser@^20.2.2: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== -yargs-parser@^18.1.2: +yargs-parser@^18.1.1, yargs-parser@^18.1.2: version "18.1.3" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== @@ -5761,6 +5856,23 @@ yargs-parser@^18.1.2: camelcase "^5.0.0" decamelize "^1.2.0" +yargs@15.3.1: + version "15.3.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.3.1.tgz#9505b472763963e54afe60148ad27a330818e98b" + integrity sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA== + dependencies: + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^4.2.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^18.1.1" + yargs@^15.0.0, yargs@^15.4.1: version "15.4.1" resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"