├── .dockerignore ├── tests └── setup.ts ├── src ├── routes │ ├── index.ts │ ├── middlewares │ │ ├── index.ts │ │ ├── catcher.ts │ │ └── requestValidator.ts │ └── movies.routes.ts ├── services │ ├── index.ts │ ├── MovieService.ts │ └── MovieService.spec.ts ├── config │ ├── containerName.ts │ ├── index.ts │ ├── containerInjection.ts │ └── appDataSource.ts ├── interfaces │ ├── index.ts │ ├── Movie.ts │ └── MovieRepository.ts ├── repositories │ ├── models │ │ ├── index.ts │ │ ├── MovieEntity.ts │ │ └── movieModel.ts │ ├── index.ts │ ├── MovieMongoRepository.ts │ ├── MovieSqlRepository.ts │ └── MovieJsonRepository.ts ├── index.ts └── app.ts ├── .gitignore ├── nodemon.json ├── examples ├── arguments.js ├── server-root.js ├── callback.js ├── non-blocking.js ├── concurrency.ts ├── functional.js └── prototypes.js ├── jest.config.js ├── Dockerfile ├── package.json ├── README.md └── tsconfig.json /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .env -------------------------------------------------------------------------------- /tests/setup.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | -------------------------------------------------------------------------------- /src/routes/index.ts: -------------------------------------------------------------------------------- 1 | export * from './movies.routes'; 2 | -------------------------------------------------------------------------------- /src/services/index.ts: -------------------------------------------------------------------------------- 1 | export * from './MovieService'; 2 | -------------------------------------------------------------------------------- /src/config/containerName.ts: -------------------------------------------------------------------------------- 1 | export const DI_REPOSITORY = 'repository'; 2 | -------------------------------------------------------------------------------- /src/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Movie'; 2 | export * from './MovieRepository'; 3 | -------------------------------------------------------------------------------- /src/repositories/models/index.ts: -------------------------------------------------------------------------------- 1 | export * from './movieModel'; 2 | export * from './MovieEntity'; 3 | -------------------------------------------------------------------------------- /src/routes/middlewares/index.ts: -------------------------------------------------------------------------------- 1 | export * from './requestValidator'; 2 | export * from './catcher'; 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | 4 | .editorconfig 5 | .prettierrc 6 | daily.md 7 | dist 8 | db.json -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["src"], 3 | "ext": "ts", 4 | "exec": "ts-node ./src/index.ts" 5 | } 6 | -------------------------------------------------------------------------------- /src/config/index.ts: -------------------------------------------------------------------------------- 1 | export * from './containerName'; 2 | export * from './containerInjection'; 3 | export * from './appDataSource'; 4 | -------------------------------------------------------------------------------- /src/repositories/index.ts: -------------------------------------------------------------------------------- 1 | export * from './MovieJsonRepository'; 2 | export * from './MovieMongoRepository'; 3 | export * from './MovieSqlRepository'; 4 | -------------------------------------------------------------------------------- /examples/arguments.js: -------------------------------------------------------------------------------- 1 | function sum(a, b) { 2 | console.log(Array.from(arguments)); 3 | console.log(a, b); 4 | } 5 | 6 | sum(1, 2, 3, 4, 5, 6, 7, 8, 8); 7 | -------------------------------------------------------------------------------- /src/interfaces/Movie.ts: -------------------------------------------------------------------------------- 1 | export enum Priority { 2 | HIGH = 'high', 3 | LOW = 'low', 4 | } 5 | 6 | export interface Movie { 7 | id: string; 8 | title: string; 9 | poster?: string; 10 | priority?: Priority; 11 | } 12 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | testMatch: ['/src/**/*.spec.ts'], 6 | setupFiles: ['/tests/setup.ts'], 7 | }; 8 | -------------------------------------------------------------------------------- /src/routes/middlewares/catcher.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from 'express'; 2 | 3 | export const catcher = 4 | (fn: (req: Request, res: Response, next?: NextFunction) => Promise) => 5 | (req: Request, res: Response, next?: NextFunction) => 6 | fn(req, res, next).catch(next); 7 | -------------------------------------------------------------------------------- /src/interfaces/MovieRepository.ts: -------------------------------------------------------------------------------- 1 | import { Movie } from './Movie'; 2 | 3 | export interface MovieRepository { 4 | findMovies(): Promise; 5 | createMovie(newMovie: Movie): Promise; 6 | removeMovie(id: string): Promise; 7 | updateMovie(id: string, changes: Partial): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /examples/server-root.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | 3 | http 4 | .createServer((req, res) => { 5 | res.writeHead(200, { 'Content-Type': 'application/json' }); 6 | 7 | if (req.url === '/cool') res.end(JSON.stringify({ cool: true })); 8 | }) 9 | .listen(666, () => console.log('Servidor ta de pé')); 10 | -------------------------------------------------------------------------------- /examples/callback.js: -------------------------------------------------------------------------------- 1 | function sum(a, b, cb) { 2 | cb(a + b); 3 | } 4 | 5 | console.log('vai'); 6 | sum(1, 2, console.log); 7 | console.log('foi'); 8 | 9 | function sumAsync(a, b, cb) { 10 | setTimeout(() => cb(a + b), 100); 11 | } 12 | 13 | console.log('vai'); 14 | sumAsync(1, 2, console.log); 15 | console.log('foi'); 16 | -------------------------------------------------------------------------------- /src/config/containerInjection.ts: -------------------------------------------------------------------------------- 1 | import { container } from 'tsyringe'; 2 | import { MovieMongoRepository, MovieSqlRepository } from '../repositories'; 3 | import { DI_REPOSITORY } from './containerName'; 4 | 5 | export function containerInjection(): void { 6 | container.register(DI_REPOSITORY, { 7 | // useClass: MovieMongoRepository, 8 | useClass: MovieSqlRepository, 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14-alpine AS builder 2 | WORKDIR /app 3 | COPY . . 4 | 5 | RUN npm ci 6 | RUN npm run build 7 | 8 | # --- 9 | 10 | FROM node:14-alpine 11 | WORKDIR /app 12 | 13 | COPY --from=builder /app/dist ./dist 14 | COPY --from=builder /app/node_modules ./node_modules 15 | 16 | RUN addgroup -g 1001 -S nodejs 17 | RUN adduser -S userapp -u 1001 18 | 19 | USER userapp 20 | 21 | EXPOSE 8080 22 | ENTRYPOINT [ "node", "dist/index.js" ] -------------------------------------------------------------------------------- /src/repositories/models/MovieEntity.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Column, PrimaryColumn } from 'typeorm'; 2 | import { Movie, Priority } from '../../interfaces'; 3 | 4 | @Entity() 5 | export class MovieEntity implements Movie { 6 | @PrimaryColumn() 7 | id!: string; 8 | 9 | @Column() 10 | title!: string; 11 | 12 | @Column({ nullable: true }) 13 | poster!: string; 14 | 15 | @Column({ nullable: true }) 16 | priority?: Priority; 17 | } 18 | -------------------------------------------------------------------------------- /src/config/appDataSource.ts: -------------------------------------------------------------------------------- 1 | import { DataSource } from 'typeorm'; 2 | import { MovieEntity } from '../repositories/models'; 3 | 4 | export const AppDataSource = new DataSource({ 5 | type: 'mysql', 6 | host: 'localhost', 7 | port: 3306, 8 | username: 'root', 9 | password: 'password', 10 | database: 'filminhos', 11 | synchronize: true, 12 | logging: true, 13 | entities: [MovieEntity], 14 | subscribers: [], 15 | migrations: [], 16 | }); 17 | -------------------------------------------------------------------------------- /examples/non-blocking.js: -------------------------------------------------------------------------------- 1 | const one = () => console.log('one'); 2 | const two = () => console.log('two'); 3 | 4 | main(); 5 | // function main() { 6 | // console.log('vai'); 7 | // one(); 8 | // two(); 9 | // console.log('foi'); 10 | // } 11 | 12 | async function main() { 13 | console.log('vai'); 14 | await new Promise(resolve => { 15 | setTimeout(() => { 16 | one(); 17 | resolve('foi'); 18 | }, 10000); 19 | }); 20 | two(); 21 | console.log('foi'); 22 | } 23 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import 'dotenv/config'; 2 | import 'reflect-metadata'; 3 | import { AppDataSource, containerInjection } from './config'; 4 | 5 | containerInjection(); 6 | 7 | // import mongoose from 'mongoose'; 8 | import { server } from './app'; 9 | 10 | async function bootstrap(): Promise { 11 | // await mongoose.connect(process.env.DATABASE_URI!); 12 | await AppDataSource.initialize(); 13 | console.log('Database connected'); 14 | 15 | server.listen(process.env.PORT, () => console.log('Server running')); 16 | } 17 | 18 | bootstrap(); 19 | -------------------------------------------------------------------------------- /examples/concurrency.ts: -------------------------------------------------------------------------------- 1 | async function delay(result: string, seconds: number): Promise { 2 | return new Promise(res => setTimeout(() => res(result), seconds * 1000)); 3 | } 4 | 5 | async function crash() { 6 | throw new Error(); 7 | } 8 | 9 | main(); 10 | async function main() { 11 | console.time('demora'); 12 | // const [res1, res2] = await Promise.all([delay('cool', 2), delay('nice', 3), crash()]); 13 | const res = await Promise.allSettled([delay('cool', 2), delay('nice', 3), crash()]); 14 | console.log(res); 15 | console.timeEnd('demora'); 16 | } 17 | -------------------------------------------------------------------------------- /src/app.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { moviesRoute } from './routes'; 3 | 4 | const app = express(); 5 | 6 | app.use(express.json()); 7 | 8 | app.use((req, res, next) => { 9 | console.log(new Date(), req.url); 10 | next(); 11 | }); 12 | 13 | app.use('/api', moviesRoute); 14 | 15 | // @ts-ignore 16 | app.use((err, req, res, next) => { 17 | const statusCode = err.statusCode || 500; 18 | res.status(statusCode).json(err); 19 | }); 20 | 21 | app.all('*', (req, res) => res.status(404).json({ error: 'Not found' })); 22 | 23 | export const server = app; 24 | -------------------------------------------------------------------------------- /src/repositories/models/movieModel.ts: -------------------------------------------------------------------------------- 1 | import { model, Schema } from 'mongoose'; 2 | import { Movie } from '../../interfaces'; 3 | 4 | const movieSchema = new Schema( 5 | { 6 | id: { 7 | type: String, 8 | required: true, 9 | }, 10 | title: { 11 | type: String, 12 | required: true, 13 | }, 14 | poster: String, 15 | priority: String, 16 | }, 17 | { 18 | toObject: { 19 | virtuals: true, 20 | transform: function (doc, ret) { 21 | const { _id, __v, ...obj } = ret; 22 | return obj; 23 | }, 24 | }, 25 | } 26 | ); 27 | 28 | export const movieModel = model('movies', movieSchema); 29 | -------------------------------------------------------------------------------- /examples/functional.js: -------------------------------------------------------------------------------- 1 | const sum = function (a, b) { 2 | return a + b; 3 | }; 4 | 5 | let name; 6 | 7 | console.log(sum(2, 2)); 8 | 9 | function greaterThan(base) { 10 | return number => number > base; 11 | } 12 | 13 | greaterThan(10)(11); 14 | 15 | const greaterThan10 = greaterThan(10); 16 | 17 | console.log(greaterThan10(50)); 18 | 19 | function getRequestUrl(host, resource, id) { 20 | return `${host}/${resource}/${id}`; 21 | } 22 | 23 | const genRequestUrl = host => resource => id => getRequestUrl(host, resource, id); 24 | 25 | const amazonUrl = genRequestUrl('www.amazon.com'); 26 | const amazonProduct = amazonUrl('product'); 27 | 28 | console.log(amazonProduct('id')); 29 | -------------------------------------------------------------------------------- /src/routes/middlewares/requestValidator.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from 'express'; 2 | import Joi from 'joi'; 3 | 4 | interface SchemaValidation { 5 | body?: any; 6 | query?: any; 7 | params?: any; 8 | } 9 | 10 | export function requestValidator(schema: SchemaValidation) { 11 | return (req: Request, res: Response, next: NextFunction) => { 12 | const errors = []; 13 | 14 | for (let key of Object.keys(schema)) { 15 | const propError = Joi.object((schema as any)[key]).validate((req as any)[key]).error; 16 | if (propError) errors.push(propError); 17 | } 18 | 19 | if (errors.length) { 20 | res.status(400).json({ errors }); 21 | return; 22 | } 23 | 24 | next(); 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /src/repositories/MovieMongoRepository.ts: -------------------------------------------------------------------------------- 1 | import { Movie, MovieRepository } from '../interfaces'; 2 | import { movieModel } from './models'; 3 | 4 | export class MovieMongoRepository implements MovieRepository { 5 | async findMovies(): Promise { 6 | const movies = await movieModel.find(); 7 | return movies.map(movie => movie.toObject()); 8 | } 9 | 10 | async createMovie(newMovie: Movie): Promise { 11 | await movieModel.create(newMovie); 12 | } 13 | 14 | async removeMovie(id: string): Promise { 15 | await movieModel.findOneAndRemove({ id }); 16 | } 17 | 18 | async updateMovie(id: string, changes: Partial): Promise { 19 | await movieModel.findOneAndUpdate({ id }, changes); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/repositories/MovieSqlRepository.ts: -------------------------------------------------------------------------------- 1 | import { AppDataSource } from '../config'; 2 | import { Movie, MovieRepository } from '../interfaces'; 3 | import { MovieEntity } from './models'; 4 | 5 | export class MovieSqlRepository implements MovieRepository { 6 | private repository = AppDataSource.getRepository(MovieEntity); 7 | 8 | async findMovies(): Promise { 9 | return this.repository.find(); 10 | } 11 | async createMovie(newMovie: Movie): Promise { 12 | await this.repository.save(newMovie); 13 | } 14 | async removeMovie(id: string): Promise { 15 | await this.repository.delete({ id }); 16 | } 17 | async updateMovie(id: string, changes: Partial): Promise { 18 | await this.repository.update({ id }, changes); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodejs-training-meu-filminho", 3 | "version": "1.0.0", 4 | "description": "---", 5 | "main": "dist/index.js", 6 | "directories": { 7 | "example": "examples" 8 | }, 9 | "scripts": { 10 | "start": "node .", 11 | "dev": "nodemon", 12 | "build": "tsc", 13 | "test": "jest" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/kennethfreitas/meu-filminho.git" 18 | }, 19 | "author": "", 20 | "license": "ISC", 21 | "bugs": { 22 | "url": "https://github.com/kennethfreitas/meu-filminho/issues" 23 | }, 24 | "homepage": "https://github.com/kennethfreitas/meu-filminho#readme", 25 | "dependencies": { 26 | "axios": "^0.27.2", 27 | "dotenv": "^16.0.0", 28 | "express": "^4.18.1", 29 | "joi": "^17.6.0", 30 | "mongoose": "^6.3.2", 31 | "mysql2": "^2.3.3", 32 | "reflect-metadata": "^0.1.13", 33 | "tsyringe": "^4.6.0", 34 | "typeorm": "^0.3.6" 35 | }, 36 | "devDependencies": { 37 | "@types/express": "^4.17.13", 38 | "@types/jest": "^27.5.0", 39 | "@types/joi": "^17.2.3", 40 | "@types/node": "^17.0.31", 41 | "nodemon": "^2.0.16", 42 | "ts-jest": "^28.0.1", 43 | "ts-node": "^10.7.0", 44 | "typescript": "^4.6.4" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/routes/movies.routes.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import Joi from 'joi'; 3 | import { container } from 'tsyringe'; 4 | import { MovieService } from '../services'; 5 | import { catcher, requestValidator } from './middlewares'; 6 | 7 | const router = Router(); 8 | const movieService = container.resolve(MovieService); 9 | 10 | router 11 | .route('/movies') 12 | .get(async (req, res) => { 13 | const movies = await movieService.getMovies(); 14 | res.json({ movies }); 15 | }) 16 | .post( 17 | requestValidator({ 18 | body: { 19 | title: Joi.string().min(3).required(), 20 | }, 21 | }), 22 | async (req, res) => { 23 | const id = await movieService.createMovie(req.body); 24 | res.status(201).json({ id }); 25 | } 26 | ); 27 | 28 | router.delete( 29 | '/movies/:id', 30 | requestValidator({ 31 | params: { 32 | id: Joi.string().min(3).required(), 33 | }, 34 | }), 35 | async (req, res) => { 36 | await movieService.deleteMovie(req.params.id); 37 | res.status(204).send(); 38 | } 39 | ); 40 | 41 | router.post( 42 | '/movies/:id/identify', 43 | catcher(async (req, res) => { 44 | await movieService.identifyMovie(req.params.id, req.query.imdb as string); 45 | res.status(204).send(); 46 | }) 47 | ); 48 | 49 | export const moviesRoute = router; 50 | -------------------------------------------------------------------------------- /examples/prototypes.js: -------------------------------------------------------------------------------- 1 | // o veiculo vai ser o prototipo do carro 2 | const vehicle = { 3 | accelerate() { 4 | console.log(`${this.model} vrummmmmm!`); 5 | }, 6 | }; 7 | 8 | // usamos o Object.create passando quem var ser o prototipo e objeto que queremos criar 9 | const car = Object.create(vehicle, { 10 | honk: { 11 | value: function () { 12 | console.log(`${this.model} beeeep!`); 13 | }, 14 | }, 15 | }); 16 | 17 | // quando for iniciar nosso carro, passamos o prototipo dele e o valor para continuar a cadeia de prototipos 18 | const gol = Object.create(car, { 19 | model: { value: 'Gol' }, 20 | }); 21 | 22 | // prototype chain: 23 | // O prototipo do gol é car 24 | // O prototipo de car é vehicle 25 | // O prototipo do vehicle é o Object.prototype 26 | 27 | gol.honk(); 28 | gol.accelerate(); 29 | 30 | class Vehicle { 31 | accelerate() { 32 | console.log(`${this.model} vrummmmmm!`); 33 | } 34 | } 35 | 36 | class Car extends Vehicle { 37 | honk() { 38 | console.log(`${this.model} beeeep!`); 39 | } 40 | } 41 | 42 | class Gol extends Car { 43 | constructor(model) { 44 | super(); 45 | this.model = model; 46 | } 47 | } 48 | 49 | const golzinho = new Gol('golzinho'); 50 | 51 | golzinho.honk(); 52 | golzinho.accelerate(); 53 | 54 | const name = ' Kenneth motta Freitas ' 55 | .trim() 56 | .split(' ') 57 | .map(n => `${n.charAt(0).toUpperCase()}${n.slice(1)}`) 58 | .join(' ') 59 | .replace('Motta', 'M.'); 60 | 61 | console.log(name); 62 | -------------------------------------------------------------------------------- /src/repositories/MovieJsonRepository.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { readFile, writeFile } from 'fs/promises'; 3 | import { Movie, MovieRepository } from '../interfaces'; 4 | 5 | const DB_PATH = path.resolve(process.cwd(), 'db.json'); 6 | 7 | class MovieJsonRepository implements MovieRepository { 8 | async updateMovie(id: string, changes: Partial): Promise { 9 | const movies = await this.findMovies(); 10 | const movieIndex = movies.findIndex(movie => movie.id === id); 11 | 12 | if (movieIndex < 0) throw new Error(`Movie with ID: ${id}, does not exist.`); 13 | 14 | movies[movieIndex] = { 15 | ...movies[movieIndex], 16 | ...changes, 17 | id, 18 | }; 19 | await this.save(movies); 20 | } 21 | 22 | async createMovie(newMovie: Movie): Promise { 23 | const movies = await this.findMovies(); 24 | movies.push(newMovie); 25 | await this.save(movies); 26 | } 27 | 28 | async findMovies(): Promise { 29 | const data = await readFile(DB_PATH); 30 | return JSON.parse(data.toString() || '[]'); 31 | } 32 | 33 | async removeMovie(id: string): Promise { 34 | const movies = await this.findMovies(); 35 | const cleanMovies = movies.filter(movie => movie.id !== id); 36 | await this.save(cleanMovies); 37 | } 38 | 39 | private async save(movies: Movie[]): Promise { 40 | await writeFile(DB_PATH, JSON.stringify(movies)); 41 | } 42 | } 43 | 44 | export const movieJsonRepository = new MovieJsonRepository() as MovieRepository; 45 | -------------------------------------------------------------------------------- /src/services/MovieService.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { randomUUID } from 'crypto'; 3 | import { inject, injectable } from 'tsyringe'; 4 | import { DI_REPOSITORY } from '../config'; 5 | import { Movie, MovieRepository } from '../interfaces'; 6 | import { movieJsonRepository, MovieMongoRepository } from '../repositories'; 7 | 8 | type ID = string; 9 | 10 | interface IdentifyReponse { 11 | Title: string; 12 | Poster: string; 13 | } 14 | 15 | @injectable() 16 | export class MovieService { 17 | // private movieRepository = movieJsonRepository; 18 | // private movieRepository = movieMongoRepository; 19 | private request = axios.create({ 20 | baseURL: 'https://www.omdbapi.com/', 21 | params: { apikey: process.env.OMDB_KEY! }, 22 | }); 23 | 24 | constructor(@inject(DI_REPOSITORY) private movieRepository: MovieRepository) {} 25 | 26 | async getMovies(): Promise { 27 | return this.movieRepository.findMovies(); 28 | } 29 | 30 | async createMovie(newMovie: Omit): Promise { 31 | const id = randomUUID(); 32 | await this.movieRepository.createMovie({ id, ...newMovie }); 33 | return id; 34 | } 35 | 36 | async deleteMovie(id: ID): Promise { 37 | await this.movieRepository.removeMovie(id); 38 | } 39 | 40 | async identifyMovie(id: ID, imdbCode: string): Promise { 41 | const { data: movieDetails } = await this.request.get('', { params: { i: imdbCode } }); 42 | await this.movieRepository.updateMovie(id, { title: movieDetails.Title, poster: movieDetails.Poster }); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/services/MovieService.spec.ts: -------------------------------------------------------------------------------- 1 | import { AxiosInstance } from 'axios'; 2 | import { Movie, MovieRepository } from '../interfaces'; 3 | import { MovieService } from './MovieService'; 4 | 5 | const MovieRepositoryMock: MovieRepository = { 6 | findMovies: async (): Promise => [ 7 | { 8 | id: '1', 9 | title: 'batman', 10 | }, 11 | ], 12 | createMovie: async (newMovie: Movie): Promise => undefined, 13 | removeMovie: async (id: string): Promise => undefined, 14 | updateMovie: async (id: string, changes: Partial): Promise => undefined, 15 | }; 16 | 17 | const RequestMock = { 18 | get: async (): Promise => ({ data: { Title: 'string', Poster: 'string' } }), 19 | } as any as AxiosInstance; 20 | 21 | describe('MovieService Test Suite', () => { 22 | const movieService = new MovieService(MovieRepositoryMock); 23 | movieService['request'] = RequestMock; 24 | 25 | afterEach(async () => jest.restoreAllMocks()); 26 | 27 | test('Should return a list of movies', async () => { 28 | const findMoviesSpy = jest.spyOn(MovieRepositoryMock, 'findMovies'); 29 | const [result] = await movieService.getMovies(); 30 | 31 | expect(result).toEqual({ 32 | id: '1', 33 | title: 'batman', 34 | }); 35 | expect(findMoviesSpy).toHaveBeenCalled(); 36 | }); 37 | 38 | test('Should create a movie', async () => { 39 | const createMovieSpy = jest.spyOn(MovieRepositoryMock, 'createMovie'); 40 | const id = await movieService.createMovie({ title: 'django' }); 41 | 42 | expect(id).not.toBeNull(); 43 | expect(createMovieSpy).toBeCalledWith({ id, title: 'django' }); 44 | }); 45 | 46 | test('Should delete a movie', async () => { 47 | const removeMovieSpy = jest.spyOn(MovieRepositoryMock, 'removeMovie'); 48 | await movieService.deleteMovie('1'); 49 | 50 | expect(removeMovieSpy).toHaveBeenCalled(); 51 | }); 52 | 53 | test('Should identify a movie', async () => { 54 | const getSpy = jest.spyOn(RequestMock, 'get'); 55 | const updateMovieSpy = jest.spyOn(MovieRepositoryMock, 'updateMovie'); 56 | const result = movieService.identifyMovie('id', 'imdb'); 57 | 58 | await expect(result).resolves.not.toThrow(); 59 | 60 | expect(getSpy).toHaveBeenCalled(); 61 | expect(updateMovieSpy).toHaveBeenCalled(); 62 | }); 63 | 64 | test('Should throw if try to identify a movie that does not exist', async () => { 65 | const getSpy = jest.spyOn(RequestMock, 'get'); 66 | const updateMovieSpy = jest.spyOn(MovieRepositoryMock, 'updateMovie').mockRejectedValue(new Error()); 67 | const result = movieService.identifyMovie('id', 'imdb'); 68 | 69 | await expect(result).rejects.toThrowError(); 70 | 71 | expect(getSpy).toHaveBeenCalled(); 72 | expect(updateMovieSpy).toHaveBeenCalled(); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Projeto Meu Filminho - Treinamento Node.js 2 | 3 | --- 4 | 5 | ### - Instalação do Node.js 6 | - [Node.js](https://nodejs.org/en/download/) 7 | - [NVM - Unix](https://github.com/nvm-sh/nvm) 8 | - [NVM - Windows](https://github.com/coreybutler/nvm-windows) 9 | 10 | ** *NVM in nutshell, Node Version Manager, permite trocar diferentes versões do Node.js facilmente, para instalar uma versão use `nvm install 12` (instala o Node.js 12, por exemplo) e `nvm use 12` para utilizar a versão 12 baixada.* 11 | 12 | --- 13 | 14 | ### - Pacote maroto para usar no VSCode para desenvolver com Node.js 15 | 16 | 17 | --- 18 | 19 | ### - API usada no projeto: 20 | [OMDb API - The Open Movie Database](https://www.omdbapi.com/) 21 | 22 | --- 23 | 24 | ### - Cheat Sheet NPM essencial 25 | | Comando | O que faz | Exemplo | 26 | |----------------------|:-----------------------------------------------------------------------------------------------------------:|--------------------------------------------------------------:| 27 | | `npm init` | Inicializa um pacote NPM | `npm init -y` # Inicia um pacote com as configurações padrão | 28 | | `npm i` | Instala todas dependências | `npm i` # Instala todas dependências listadas no package.json | 29 | | `npm i PACKAGE_NAME` | Instala um pacote como dependências | `npm i express@4` # Instala o Express na versão 4 | 30 | | `npm r PACKAGE_NAME` | Remove um pacote | `npm r rxjs` # Remove o rxjs da lista de dependências | 31 | | `npm ci` | Mesma coisa que `npm install`, porém feito para ambientes automatizados | `npm ci` | 32 | | `npm run SCRIPT` | Permite rodar um comando definito no package.json | `npm run dev` # Roda o comando associado com o script "dev" | 33 | | `npx PACKAGE_NAME` | Permite rodar comandos diretamente de um pacote, similar ao `npm run`, porém sem a necessidade de um escopo | `npx cowsay "Hello"` | 34 | 35 | ** *Dependências podem ser instaladas globalmente usando a flag `-g` ou como dev devDependencies utilizando a flag `-D` ao utilizar o `npm i` em algum pacote.* 36 | ** *Você pode usar a flag `--production` com `npm i` para não instalar as devDependencies.* 37 | 38 | --- 39 | 40 | ## - Documentações oficiais: 41 | - [Node.js](https://nodejs.org/en/docs/) 42 | - [NPM](https://docs.npmjs.com/) -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 | "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ 22 | // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | 26 | /* Modules */ 27 | "module": "commonjs", /* Specify what module code is generated. */ 28 | // "rootDir": "./", /* Specify the root folder within your source files. */ 29 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 30 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 31 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 32 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 33 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ 34 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 35 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 36 | // "resolveJsonModule": true, /* Enable importing .json files */ 37 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ 38 | 39 | /* JavaScript Support */ 40 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ 41 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 42 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ 43 | 44 | /* Emit */ 45 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 46 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 47 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 48 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 49 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ 50 | "outDir": "dist", /* Specify an output folder for all emitted files. */ 51 | // "removeComments": true, /* Disable emitting comments. */ 52 | // "noEmit": true, /* Disable emitting files from a compilation. */ 53 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 54 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ 55 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 56 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 58 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 59 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 60 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 61 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 62 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ 63 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ 64 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 65 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ 66 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 67 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 68 | 69 | /* Interop Constraints */ 70 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 71 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 72 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ 73 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 74 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 75 | 76 | /* Type Checking */ 77 | "strict": true, /* Enable all strict type-checking options. */ 78 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ 79 | // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ 80 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 81 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ 82 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 83 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ 84 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ 85 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 86 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ 87 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ 88 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 89 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 90 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 91 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 92 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 93 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ 94 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 95 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 96 | 97 | /* Completeness */ 98 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 99 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 100 | } 101 | } 102 | --------------------------------------------------------------------------------