Skip to content

Commit

Permalink
Merge pull request #7 from thalysonalexr/add-backend
Browse files Browse the repository at this point in the history
Add backend - done v1.0.0
  • Loading branch information
thalysonalexr committed Apr 23, 2020
2 parents a9af808 + eb5b218 commit 7545112
Show file tree
Hide file tree
Showing 44 changed files with 1,411 additions and 781 deletions.
21 changes: 18 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
# ycoffee

[![Software License](https://img.shields.io/apm/l/vim-mode.svg)](https://github.com/thalysonalexr/ycoffee/blob/master/LICENSE)
[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/thalysonalexr/ycoffee/issues)
[![Open Source Love](https://badges.frapsoft.com/os/v2/open-source.svg?v=103)](https://github.com/thalysonalexr)

Repositório do projeto "Seu Café". API com Node.js e TypeScript, frontend com React e React Native.

<p align="center">
<a href="https://github.com/thalysonrodrigues/ycoffee">
<img src="./docs/coffee-types.jpg" alt="logo" title="Seu Café">
</a>
</p>

## MVP - Ideia

Produto mínimo viável para cadastro e gerenciamento de receitas de cafés.

## Funcionalidades - RF
- [] Cadastrar café
- [x] Cadastrar café
* tipo
* descrição
* ingredientes
* preparo
* tempo de preparo
* porções
* foto
* imagem
* autor (usuario)

- [x] Cadastrar usuário
Expand All @@ -26,6 +36,7 @@ Produto mínimo viável para cadastro e gerenciamento de receitas de cafés.
- [x] Dashboard admin
- Habilitar/desabilitar usuário
- Remover usuários
- Remover cafés

## Não funcionalidades - RNF

Expand All @@ -34,4 +45,8 @@ Produto mínimo viável para cadastro e gerenciamento de receitas de cafés.

## Metodologias

Utilização de DDD (Driven Domain Design) e TDD (Test Driven Development) como metodologias para desenvolvimento da API deste projeto.
Utilização de DDD (Driven Domain Design) e TDD (Test Driven Development) como metodologias para desenvolvimento da API deste projeto.

## Créditos

Desenvolvido com ♥ por [Thalyson Rodrigues](https://www.linkedin.com/in/thalysonrodrigues/)
2 changes: 2 additions & 0 deletions backend/.gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
__tests__/coverage
node_modules
tmp/uploads/*
tmp/tests/*
.env
.env.dev
.env.test
9 changes: 8 additions & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"devDependencies": {
"@shelf/jest-mongodb": "^1.1.5",
"@types/bcryptjs": "^2.4.2",
"@types/cors": "^2.8.6",
"@types/express": "^4.17.4",
"@types/express-validator": "^3.0.0",
"@types/factory-girl": "^5.0.2",
Expand All @@ -27,6 +28,8 @@
"@types/jsonwebtoken": "^8.3.9",
"@types/mongoose": "^5.7.8",
"@types/mongoose-paginate": "^5.0.8",
"@types/morgan": "^1.9.0",
"@types/multer": "^1.4.3",
"@types/node": "^13.11.0",
"@types/supertest": "^2.0.8",
"cross-env": "^7.0.2",
Expand All @@ -42,11 +45,15 @@
},
"dependencies": {
"bcryptjs": "^2.4.3",
"cors": "^2.8.5",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"express-validator": "^6.4.0",
"jsonwebtoken": "^8.5.1",
"mongoose": "^5.9.7",
"mongoose-paginate": "^5.0.3"
"mongoose-paginate": "^5.0.3",
"morgan": "^1.10.0",
"multer": "^1.4.2",
"promisify": "^0.0.3"
}
}
3 changes: 3 additions & 0 deletions backend/src/@types/process-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ declare namespace NodeJS {
MONGO_USER: string
MONGO_PASS: string
MONGO_DB: string
UPLOAD_TYPE: 'local'
UPLOAD_PATH: string
UPLOAD_SIZE: string
SECRET: string
}
}
6 changes: 6 additions & 0 deletions backend/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import '@config/index'

import express from 'express'
import morgan from 'morgan'
import cors from 'cors'
import path from 'path'
import routes from './routes'

const app = express()

app.use(cors())
app.use(express.urlencoded({ extended: true }))
app.use(express.json())
app.use(morgan('dev'))
app.use('/files', express.static(path.resolve(__dirname, '..', 'tmp', process.env.UPLOAD_PATH)))
app.use('/v1', routes)

export default app
6 changes: 5 additions & 1 deletion backend/src/app/core/schemas/ICoffee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ export interface ICoffee {
preparation: string
timePrepare: number
portions: number
picture: string
image: {
name: string,
key: string,
size: number
}
author: object & IUser
updatedAt: Date
createdAt: Date
Expand Down
114 changes: 79 additions & 35 deletions backend/src/app/domain/controllers/CoffeeController.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import faker from 'faker'
import request from 'supertest'
import path from 'path'
import fs from 'fs'

import app from '../../../app'

Expand All @@ -11,13 +13,22 @@ import MongoMock from '@utils/test/MongoMock'
import User, { UserModel } from '@domain/schemas/User'
import Coffee, { CoffeeModel } from '@domain/schemas/Coffee'

const dirUploads = path.resolve(__dirname, '..', '..', '..', '..', 'tmp', 'tests')
const dirExamples = path.resolve(__dirname, '..', '..', '..', '..', 'tmp', 'tests', 'examples')

describe('Coffee actions', () => {
beforeAll(async () => {
await MongoMock.connect()
})

afterAll(async () => {
await MongoMock.disconnect()
// remove all images in upload test
fs.readdir(dirUploads, (err, files) => {
for (const file of files) {
fs.unlink(path.join(dirUploads, file), () => {})
}
})
})

beforeEach(async () => {
Expand All @@ -37,7 +48,6 @@ describe('Coffee actions', () => {
preparation: faker.lorem.paragraphs(),
timePrepare: faker.random.number(10),
portions: faker.random.number(5),
picture: faker.internet.url(),
}

const response = await request(app)
Expand All @@ -56,7 +66,6 @@ describe('Coffee actions', () => {
preparation: expect.any(String),
timePrepare: expect.any(Number),
portions: expect.any(Number),
picture: expect.any(String),
author: expect.any(Object),
updatedAt: expect.any(String),
createdAt: expect.any(String)
Expand Down Expand Up @@ -86,7 +95,6 @@ describe('Coffee actions', () => {
preparation: expect.any(String),
timePrepare: expect.any(Number),
portions: expect.any(Number),
picture: expect.any(String),
author: expect.any(Object),
updatedAt: expect.any(String),
createdAt: expect.any(String)
Expand Down Expand Up @@ -212,7 +220,6 @@ describe('Coffee actions', () => {
preparation: faker.lorem.paragraphs(),
timePrepare: faker.random.number(10),
portions: faker.random.number(5),
picture: faker.internet.url(),
}

const response = await request(app)
Expand All @@ -231,7 +238,6 @@ describe('Coffee actions', () => {
preparation: expect.any(String),
timePrepare: expect.any(Number),
portions: expect.any(Number),
picture: expect.any(String),
author: expect.any(Object),
updatedAt: expect.any(String),
createdAt: expect.any(String)
Expand All @@ -240,12 +246,13 @@ describe('Coffee actions', () => {
)
})

it('should be not able update coffee because not owner', async () => {
const user1 = await factory.create<UserModel>('User', { email: 'hello@email.com' })
const user2 = await factory.create<UserModel>('User', { email: 'world@email.com' })
it('should be not able update coffee because not exists', async () => {
const user = await factory.create<UserModel>('User')

const { id } = await factory.create<CoffeeModel>('Coffee', { author: user1.id })
const token = generateTokenJwt(process.env.SECRET, { id: user2.id })
const { id } = await factory.create<CoffeeModel>('Coffee', { author: user.id })
const token = generateTokenJwt(process.env.SECRET, { id: user.id })

await Coffee.deleteMany({})

const response = await request(app)
.put(`/v1/coffee/${id}`)
Expand All @@ -257,13 +264,25 @@ describe('Coffee actions', () => {
preparation: faker.lorem.paragraphs(),
timePrepare: faker.random.number(10),
portions: faker.random.number(5),
picture: faker.internet.url(),
})

expect(response.status).toBe(403)
expect(response.status).toBe(404)
})

it('should be not able update coffee because not exists', async () => {
it('should be able destroy coffee', async () => {
const user = await factory.create<UserModel>('User')

const { id } = await factory.create<CoffeeModel>('Coffee', { author: user.id })
const token = generateTokenJwt(process.env.SECRET, { id: user.id })

const response = await request(app)
.delete(`/v1/coffee/${id}`)
.set('Authorization', `Bearer ${token}`)

expect(response.status).toBe(204)
})

it('should be not able destroy coffee because not exists', async () => {
const user = await factory.create<UserModel>('User')

const { id } = await factory.create<CoffeeModel>('Coffee', { author: user.id })
Expand All @@ -272,49 +291,41 @@ describe('Coffee actions', () => {
await Coffee.deleteMany({})

const response = await request(app)
.put(`/v1/coffee/${id}`)
.delete(`/v1/coffee/${id}`)
.set('Authorization', `Bearer ${token}`)
.send({
type: faker.name.title(),
description: faker.lorem.words(10),
ingredients: [faker.name.title(), faker.name.title()],
preparation: faker.lorem.paragraphs(),
timePrepare: faker.random.number(10),
portions: faker.random.number(5),
picture: faker.internet.url(),
})

expect(response.status).toBe(404)
})

it('should be able destroy coffee', async () => {
it('should be not able append image because unsupported media type', async () => {
const user = await factory.create<UserModel>('User')

const { id } = await factory.create<CoffeeModel>('Coffee', { author: user.id })
const token = generateTokenJwt(process.env.SECRET, { id: user.id })

const response = await request(app)
.delete(`/v1/coffee/${id}`)
.put(`/v1/coffee/${id}/image`)
.attach('image', path.resolve(dirExamples, 'example.pdf'))
.set('Authorization', `Bearer ${token}`)

expect(response.status).toBe(204)
expect(response.status).toBe(415)
})

it('should be not able destroy coffee because not owner', async () => {
const user1 = await factory.create<UserModel>('User', { email: 'hello@email.com' })
const user2 = await factory.create<UserModel>('User', { email: 'world@email.com' })
it('should be not able append image because exceeds allowed size', async () => {
const user = await factory.create<UserModel>('User')

const { id } = await factory.create<CoffeeModel>('Coffee', { author: user1.id })
const token = generateTokenJwt(process.env.SECRET, { id: user2.id })
const { id } = await factory.create<CoffeeModel>('Coffee', { author: user.id })
const token = generateTokenJwt(process.env.SECRET, { id: user.id })

const response = await request(app)
.delete(`/v1/coffee/${id}`)
.put(`/v1/coffee/${id}/image`)
.attach('image', path.resolve(dirExamples, 'full.jpg'))
.set('Authorization', `Bearer ${token}`)

expect(response.status).toBe(403)
expect(response.status).toBe(413)
})

it('should be not able destroy coffee because not exists', async () => {
it('should be not able append image because coffee not exists', async () => {
const user = await factory.create<UserModel>('User')

const { id } = await factory.create<CoffeeModel>('Coffee', { author: user.id })
Expand All @@ -323,9 +334,42 @@ describe('Coffee actions', () => {
await Coffee.deleteMany({})

const response = await request(app)
.delete(`/v1/coffee/${id}`)
.put(`/v1/coffee/${id}/image`)
.attach('image', path.resolve(dirExamples, 'example.jpg'))
.set('Authorization', `Bearer ${token}`)

expect(response.status).toBe(404)
})

it('should be able append image in coffee', async () => {
const user = await factory.create<UserModel>('User')

const { id } = await factory.create<CoffeeModel>('Coffee', { author: user.id })
const token = generateTokenJwt(process.env.SECRET, { id: user.id })

const response = await request(app)
.put(`/v1/coffee/${id}/image`)
.attach('image', path.resolve(dirExamples, 'example.jpg'))
.set('Authorization', `Bearer ${token}`)

expect(response.status).toBe(200)
})

it('should be able destroy coffee with image', async () => {
const user = await factory.create<UserModel>('User')

const { id } = await factory.create<CoffeeModel>('Coffee', { author: user.id })
const token = generateTokenJwt(process.env.SECRET, { id: user.id })

await request(app)
.put(`/v1/coffee/${id}/image`)
.attach('image', path.resolve(dirExamples, 'example.jpg'))
.set('Authorization', `Bearer ${token}`)

const response = await request(app)
.delete(`/v1/coffee/${id}`)
.set('Authorization', `Bearer ${token}`)

expect(response.status).toBe(204)
})
})
Loading

0 comments on commit 7545112

Please sign in to comment.