Skip to content

Commit

Permalink
Major Refactor
Browse files Browse the repository at this point in the history
-separated services into Books/Customers service
-added BFFs for each service
-added deployment assets for everything
  • Loading branch information
mkornyev committed Apr 5, 2021
1 parent 9b72f2f commit 2c5dce9
Show file tree
Hide file tree
Showing 17 changed files with 879 additions and 1 deletion.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.vagrant/*
*/node_modules/*
A1.pdf
TestScript.json
TestScript.json
*.zip
8 changes: 8 additions & 0 deletions bookBFF/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
FROM python:3.6-slim-buster AS PYTHON_BUILD
MAINTAINER Max Kornyev
COPY . /app/
COPY requirements.txt /app
WORKDIR /app
RUN pip install -r requirements.txt
EXPOSE 80
ENTRYPOINT ["python", "app.py"]
120 changes: 120 additions & 0 deletions bookBFF/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@

# IMPORTS

from flask import Flask, request, json
import requests
app = Flask(__name__)


# CONSTANTS

SERVER_HOST = '0.0.0.0'
SERVER_PORT = 80

BOOK_SERVICE_HOST = 'http://localhost:3001/books'
VALID_AUTH_TOKEN = 'Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJLUkFNRVJTIiwibHVscyI6IktSQU1FUlMiLCJjcmVhdGVkIjoxNjE3MjMwNzUxM zIwLCJyb2xlcyI6W10sImlzcyI6InRjdS5nb3YuYnIiLCJlaW8iOiIxMC4xMDAuMTkyLjUxIiwibnVzIjoiSk9BTyBBTkR PTklPUyBTUFlSSURBS0lTIiwibG90IjoiU2VnZWMiLCJhdWQiOiJPUklHRU1fUkVRVUVTVF9CUk9XU0VSIiwidHVzIjoiV ENVIiwiY3VscyI6MjI1LCJjb2QiOjIyNSwiZXhwIjoxNjE3MjczOTUxMzIwLCJudWxzIjoiSk9BTyBBTkRPTklPUyBTUFl SSURBS0lTIn0.qtJ0Sf2Agqd_JmxGKfqiLw8SldOiP9e21OT4pKC8BqdXrJ0plqOWHf0hHbwQWp-foEBZzAUWX0J-QHtLy Q7SRw'


# VALIDATIONS

def responseIfInvalidRequest(req):
agentHeader = req.headers.get('User-Agent', None)
if agentHeader == None:
response = app.response_class(
response=json.dumps({'message': 'User-Agent header required.'}),
status=400,
mimetype='application/json'
)
return response

auth = req.headers.get('Authorization', None)
if auth == None or auth != VALID_AUTH_TOKEN:
response = app.response_class(
response=json.dumps({'message': 'Valid Authorization Token required.'}),
status=401,
mimetype='application/json'
)
return response

return None

def isMobileAgent(req):
agentHeader = req.headers.get('User-Agent', None)

if 'Mobile' in agentHeader:
return True

return False


# ROUTES

@app.route('/books', methods=['POST'])
def addBook():
response = responseIfInvalidRequest(request)
if response:
return response

serviceRes = requests.post(BOOK_SERVICE_HOST, data=request.get_json())

return getResponseFor(serviceRes)


@app.route('/books/<isbn>', methods=['PUT'])
def putBook(isbn=None):
response = responseIfInvalidRequest(request)
if response:
return response

path = '/{}'.format(isbn)
serviceRes = requests.put(BOOK_SERVICE_HOST+path, data=request.get_json())

return getResponseFor(serviceRes)


@app.route('/books/isbn/<isbn>', methods=['GET'])
def getBook(isbn=None):
response = responseIfInvalidRequest(request)
if response:
return response

path = '/isbn/{}'.format(isbn)
serviceRes = requests.get(BOOK_SERVICE_HOST+path)

if isMobileAgent(request):
returnedCode = serviceRes.status_code
returnedBody = serviceRes.json()

if 'genre' in returnedBody and 'non-fiction' == returnedBody['genre']:
returnedBody['genre'] = 3

response = app.response_class(
response=json.dumps(returnedBody),
status=returnedCode,
mimetype='application/json'
)
return response

return getResponseFor(serviceRes)



# HELPERS

def getResponseFor(serviceRes):
returnedCode = serviceRes.status_code
returnedBody = serviceRes.json()

response = app.response_class(
response=json.dumps(returnedBody),
status=returnedCode,
mimetype='application/json'
)

return response


# RUN APP

if __name__ == '__main__':
app.run(host=SERVER_HOST, port=SERVER_PORT)
11 changes: 11 additions & 0 deletions bookBFF/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
certifi==2020.12.5
chardet==4.0.0
click==7.1.2
Flask==1.1.2
idna==2.10
itsdangerous==1.1.0
Jinja2==2.11.3
MarkupSafe==1.1.1
requests==2.25.1
urllib3==1.26.4
Werkzeug==1.0.1
7 changes: 7 additions & 0 deletions bookService/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FROM node:14-alpine AS NODE_BUILD
MAINTAINER Max Kornyev
COPY . /app/
WORKDIR /app
RUN npm install
EXPOSE 3000
ENTRYPOINT ["node", "index.js"]
75 changes: 75 additions & 0 deletions bookService/db-connector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@

//################ CONNECTION SETUP ################

var MYSQL = require('mysql')
var CONNECTION


//################ QUERY TEMPLATES ################

var INSERT_BOOK_SQL = 'INSERT INTO books (ISBN, title, Author, description, genre, price, quantity) VALUES (?, ?, ?, ?, ?, ?, ?);'
var UPDATE_BOOK_SQL = 'UPDATE books SET ISBN = ?, title = ?, Author = ?, description = ?, genre = ?, price = ?, quantity = ? WHERE ISBN = ?;'
var GET_BOOK_SQL = 'SELECT * FROM books WHERE ISBN = ? LIMIT 1;'


//################ QUERY HELPERS ################

function testConnection() {
CONNECTION.connect(function(err) {
if (err) {
console.error('Database connection failed: ' + err.stack)
CONNECTION.end()
} else {
console.log('Connection success')
CONNECTION.end()
}
})
}

function setRDSConnection(){
CONNECTION = MYSQL.createConnection({
host : '',
user : '',
password : '',
port : '',
database : '',
})

CONNECTION.on('error', function(err) {
if(err.code === 'PROTOCOL_CONNECTION_LOST') {
console.log('Connection Disconnect... Retrying in 2 seconds', err);
setTimeout(setRDSConnection, 2000);
} else {
throw err;
}
});
}


//################ BOOK QUERIES ################

function createBook(ISBN, title, Author, description, genre, price, quantity, exists, success){
CONNECTION.query(INSERT_BOOK_SQL, [ISBN, title, Author, description, genre, price, quantity], function (err, result) {
if (err) { exists() }
else { success() }
})
}

function getBook(ISBN, notFound, success){
CONNECTION.query(GET_BOOK_SQL, [ISBN], function (err, result) {
if (err || result.length == 0) { notFound() }
else { success(result) }
})
}

function updateBook(oldISBN, ISBN, title, Author, description, genre, price, quantity, notFound, success){
CONNECTION.query(UPDATE_BOOK_SQL, [ISBN, title, Author, description, genre, price, quantity, oldISBN], function (err, result) {
if (err || result.affectedRows == 0) { notFound() }
else { success() }
})
}


//################ MODULE EXPORT ################

module.exports = { testConnection, setRDSConnection, createBook, updateBook, getBook }
113 changes: 113 additions & 0 deletions bookService/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
//################ IMPORTS ################

const express = require('express')
const bodyParser = require('body-parser')
const port = 3000

const conn = require('./db-connector')
const app = express()

app.use(bodyParser.urlencoded({ extended: true }))
app.use(bodyParser.json())
app.use(bodyParser.raw())


//################ CONSTANTS ################

var PRICE_REGEX = /^\d+(\.(\d{2}|0))?$/ // 10 | 10.0 | 10.01 are all valid


//################ BOOK ROUTES ################

// Add Book:
app.post('/books', (req, res) => {
if(!validateBook(req, res)) return

conn.createBook(req.body.ISBN, req.body.title, req.body.Author, req.body.description, req.body.genre, req.body.price, req.body.quantity, () => {
// If Already Exists
res.statusCode = 422
res.json({ message: 'This ISBN already exists in the system.' })
}, () => {
// Success
res.statusCode = 201
res.location(`${req.headers.host}/books/${req.body.ISBN}`)
res.json(req.body)
})
})

// Update Book:
// !!! VALIDATE ADMIN
app.put('/books/:ISBN', (req, res) => {
if(!validateBook(req, res)) return
var oldISBN = req.params.ISBN

if(oldISBN == req.body.ISBN){
callToUpdate()
} else {
conn.getBook(req.body.ISBN, () => {
// If Book not found (newISBN)
callToUpdate()
}, () => {
res.statusCode = 422
res.json({ message: 'The new ISBN already exists in the system.' }) // The logic is inverted — checks whether the NEW isbn exists BEFORE checking the OLD one
})
}

function callToUpdate(){
conn.updateBook(oldISBN, req.body.ISBN, req.body.title, req.body.Author, req.body.description, req.body.genre, req.body.price, req.body.quantity, () => {
// If Book not found (oldISBN)
res.statusCode = 404
res.json({ message: 'No ISBN Found.' })
}, () => {
res.statusCode = 200
res.json(req.body)
})
}
})

// Retrieve Book:
app.get('/books/isbn/:ISBN', (req, res) => {
var ISBN = req.params.ISBN

conn.getBook(ISBN, () => {
// If Book not found
res.statusCode = 404
res.json({ message: 'No ISBN Found.' })
}, (book) => {
res.statusCode = 200
res.json( book[0] )
})
})


//################ VIEW HELPERS ################

function validateBook(req, res) {

if(
!req.body.ISBN ||
!req.body.title ||
!req.body.Author ||
!req.body.description ||
!req.body.genre ||
!req.body.price ||
!req.body.quantity ||
!PRICE_REGEX.test(req.body.price)
){
res.statusCode = 400
res.json({ message: 'Malformed input.' })
return false
}
return true
}


//################ =+= ################

app.listen(port, () => {
conn.setRDSConnection()
console.log(`Example app listening at http://localhost:${port}`)
})



15 changes: 15 additions & 0 deletions bookService/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "book_service",
"version": "1.0.0",
"description": "An application for 17647",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Maksym Kornyev",
"license": "ISC",
"dependencies": {
"express": "^4.17.1",
"mysql": "^2.18.1"
}
}
Loading

0 comments on commit 2c5dce9

Please sign in to comment.