Compare commits

...

24 Commits

132
.gitignore vendored

@ -0,0 +1,132 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
#tempfile and workspaceconfig
.tempfile
.workspace-config.json
package-lock.json
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

@ -1 +0,0 @@
* @udacity/active-public-content

@ -1,54 +1,3 @@
# Storefront Backend Project
# Shelf Project
## Getting Started
This repo contains a basic Node and Express app to get you started in constructing an API. To get started, clone this repo and run `yarn` in your terminal at the project root.
## Required Technologies
Your application must make use of the following libraries:
- Postgres for the database
- Node/Express for the application logic
- dotenv from npm for managing environment variables
- db-migrate from npm for migrations
- jsonwebtoken from npm for working with JWTs
- jasmine from npm for testing
## Steps to Completion
### 1. Plan to Meet Requirements
In this repo there is a `REQUIREMENTS.md` document which outlines what this API needs to supply for the frontend, as well as the agreed upon data shapes to be passed between front and backend. This is much like a document you might come across in real life when building or extending an API.
Your first task is to read the requirements and update the document with the following:
- Determine the RESTful route for each endpoint listed. Add the RESTful route and HTTP verb to the document so that the frontend developer can begin to build their fetch requests.
**Example**: A SHOW route: 'blogs/:id' [GET]
- Design the Postgres database tables based off the data shape requirements. Add to the requirements document the database tables and columns being sure to mark foreign keys.
**Example**: You can format this however you like but these types of information should be provided
Table: Books (id:varchar, title:varchar, author:varchar, published_year:varchar, publisher_id:string[foreign key to publishers table], pages:number)
**NOTE** It is important to remember that there might not be a one to one ratio between data shapes and database tables. Data shapes only outline the structure of objects being passed between frontend and API, the database may need multiple tables to store a single shape.
### 2. DB Creation and Migrations
Now that you have the structure of the databse outlined, it is time to create the database and migrations. Add the npm packages dotenv and db-migrate that we used in the course and setup your Postgres database. If you get stuck, you can always revisit the database lesson for a reminder.
You must also ensure that any sensitive information is hashed with bcrypt. If any passwords are found in plain text in your application it will not pass.
### 3. Models
Create the models for each database table. The methods in each model should map to the endpoints in `REQUIREMENTS.md`. Remember that these models should all have test suites and mocks.
### 4. Express Handlers
Set up the Express handlers to route incoming requests to the correct model method. Make sure that the endpoints you create match up with the enpoints listed in `REQUIREMENTS.md`. Endpoints must have tests and be CORS enabled.
### 5. JWTs
Add JWT functionality as shown in the course. Make sure that JWTs are required for the routes listed in `REQUIUREMENTS.md`.
### 6. QA and `README.md`
Before submitting, make sure that your project is complete with a `README.md`. Your `README.md` must include instructions for setting up and running your project including how you setup, run, and connect to your database.
Before submitting your project, spin it up and test each endpoint. If each one responds with data that matches the data shapes from the `REQUIREMENTS.md`, it is ready for submission!
An online storefront to showcase products. Users is able to browse an index of all products, see the specifics of a single product, and add products to an order that they can view in a cart page.

@ -1,42 +0,0 @@
# API Requirements
The company stakeholders want to create an online storefront to showcase their great product ideas. Users need to be able to browse an index of all products, see the specifics of a single product, and add products to an order that they can view in a cart page. You have been tasked with building the API that will support this application, and your coworker is building the frontend.
These are the notes from a meeting with the frontend developer that describe what endpoints the API needs to supply, as well as data shapes the frontend and backend have agreed meet the requirements of the application.
## API Endpoints
#### Products
- Index
- Show
- Create [token required]
- [OPTIONAL] Top 5 most popular products
- [OPTIONAL] Products by category (args: product category)
#### Users
- Index [token required]
- Show [token required]
- Create N[token required]
#### Orders
- Current Order by user (args: user id)[token required]
- [OPTIONAL] Completed Orders by user (args: user id)[token required]
## Data Shapes
#### Product
- id
- name
- price
- [OPTIONAL] category
#### User
- id
- firstName
- lastName
- password
#### Orders
- id
- id of each product in the order
- quantity of each product in the order
- user_id
- status of order (active or complete)

@ -0,0 +1,16 @@
{
"dev": {
"driver": "pg",
"host": "127.0.0.1",
"database": "shelf",
"user": "postgres",
"password": "fredy123"
},
"test": {
"driver": "pg",
"host": "127.0.0.1",
"database": "shelf_test",
"user": "postgres",
"password": "fredy123"
}
}

@ -12,3 +12,4 @@ services:
volumes:
postgres:

@ -1,7 +1,10 @@
{
"spec_dir": "spec",
"spec_dir": "src/tests",
"spec_files": [
"../**/tests/*[s]pec.ts"
"models/product_spec.ts",
"models/user_spec.ts",
"models/order_spec.ts",
"handlers/*_[sS]pec.ts"
],
"helpers": [
"helpers/**/*.js"
@ -15,5 +18,5 @@
}
],
"stopSpecOnExpectationFailure": false,
"random": true
"random": false
}

@ -0,0 +1,53 @@
'use strict';
var dbm;
var type;
var seed;
var fs = require('fs');
var path = require('path');
var Promise;
/**
* We receive the dbmigrate dependency from dbmigrate initially.
* This enables us to not have to rely on NODE_PATH.
*/
exports.setup = function(options, seedLink) {
dbm = options.dbmigrate;
type = dbm.dataType;
seed = seedLink;
Promise = options.Promise;
};
exports.up = function(db) {
var filePath = path.join(__dirname, 'sqls', '20220520025053-shelf-up.sql');
return new Promise( function( resolve, reject ) {
fs.readFile(filePath, {encoding: 'utf-8'}, function(err,data){
if (err) return reject(err);
console.log('received data: ' + data);
resolve(data);
});
})
.then(function(data) {
return db.runSql(data);
});
};
exports.down = function(db) {
var filePath = path.join(__dirname, 'sqls', '20220520025053-shelf-down.sql');
return new Promise( function( resolve, reject ) {
fs.readFile(filePath, {encoding: 'utf-8'}, function(err,data){
if (err) return reject(err);
console.log('received data: ' + data);
resolve(data);
});
})
.then(function(data) {
return db.runSql(data);
});
};
exports._meta = {
"version": 1
};

@ -0,0 +1,4 @@
DROP TABLE order_products;
DROP TABLE orders;
DROP TABLE users;
DROP TABLE products;

@ -0,0 +1,29 @@
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(250) NOT NULL,
price INTEGER NOT NULL
);
CREATE TABLE users (
id SERIAL PRIMARY KEY,
firstName VARCHAR(250) NOT NULL,
lastName VARCHAR(250) NOT NULL,
username VARCHAR(250) NOT NULL,
password VARCHAR(250) NOT NULL
);
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
status VARCHAR(15),
user_id INTEGER NOT NULL REFERENCES users(id)
);
CREATE TABLE order_products (
id SERIAL PRIMARY KEY,
quantity INTEGER NOT NULL,
order_id INTEGER NOT NULL REFERENCES orders(id),
product_id INTEGER NOT NULL REFERENCES products(id)
)

@ -1,30 +1,55 @@
{
"name": "storefront_backend",
"name": "Shelf_backend",
"version": "0.1.0",
"description": "",
"main": "server.ts",
"scripts": {
"start": "node src/server.ts",
"start": "nodemon src/server.ts",
"dev": "db-migrate --env dev up && nodemon src/server.ts",
"watch": "tsc-watch --esModuleInterop src/server.ts --outDir ./dist --onSuccess \"node ./dist/server.js\"",
"test": "jasmine-ts",
"test": "db-migrate --env test up && ENV=test jasmine-ts --config jasmine.json && db-migrate --env test down",
"build": "npx tsc",
"up": "db-migrate up",
"down": "db-migrate down",
"lint": "eslint --ext .ts",
"prettier": "prettier \"src/**/*.ts\" --write",
"tsc": "tsc"
},
"author": "Udacity",
"author": "AnisB",
"license": "ISC",
"dependencies": {
"express": "^4.17.1",
"bcrypt": "^5.0.1",
"body-parser": "^1.19.0",
"typescript": "^4.1.3",
"pg": "^8.5.1"
"cross-env": "^7.0.3",
"dotenv": "^16.0.0",
"express": "^4.17.1",
"jsonwebtoken": "^8.5.1",
"lodash": "^4.17.21",
"nodemon": "^2.0.15",
"pg": "^8.5.1",
"typescript": "^4.1.3"
},
"devDependencies": {
"@types/bcrypt": "^5.0.0",
"@types/express": "^4.17.9",
"@types/jasmine": "^3.6.3",
"@types/jsonwebtoken": "^8.5.8",
"@types/pg": "^7.14.7",
"@types/supertest": "^2.0.11",
"@typescript-eslint/eslint-plugin": "^5.10.1",
"@typescript-eslint/parser": "^5.10.1",
"db-migrate": "^0.11.13",
"db-migrate-pg": "^1.2.2",
"eslint": "^7.27.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.4.0",
"jasmine": "^3.6.4",
"jasmine-spec-reporter": "^6.0.0",
"jasmine-ts": "^0.3.0",
"prettier": "^2.3.0",
"supertest": "^6.1.3",
"ts-node": "^9.1.1",
"tsc-watch": "^4.2.9"
"tsc-watch": "^4.2.9",
"typescript": "^4.5.4"
}
}

@ -0,0 +1,36 @@
import dotenv from 'dotenv'
import { Pool } from 'pg'
dotenv.config()
const {
ENV,
POSTGRES_HOST,
POSTGRES_DB,
POSTGRES_TEST_DB,
POSTGRES_USER,
POSTGRES_PASSWORD
} = process.env
let client = new Pool();
console.log(ENV)
if(ENV === 'dev') {
client = new Pool({
host: POSTGRES_HOST,
database: POSTGRES_DB,
user: POSTGRES_USER,
password: POSTGRES_PASSWORD,
})
}
if(ENV === 'test') {
client = new Pool({
host: POSTGRES_HOST,
database: POSTGRES_TEST_DB,
user: POSTGRES_USER,
password: POSTGRES_PASSWORD,
})
}
export default client;

@ -0,0 +1,78 @@
import express, { Request, Response } from 'express'
import { Order, OrderProduct, OrderStore } from '../models/order'
import { verifyAuthToken } from './utils'
const orderRoutes = (app: express.Application) => {
app.get('/orders', index)
app.get('/orders/:id', read)
app.post('/orders', verifyAuthToken, create)
app.post('/orders/:id/products', verifyAuthToken, addProduct)
app.delete('/orders/:id/products', verifyAuthToken, deleteProduct)
}
const store = new OrderStore()
const index = async (req: Request, res: Response) => {
try {
const orders = await store.index()
res.json(orders)
} catch (err) {
res.status(400)
res.json(err)
}
}
const read = async (req: Request, res: Response) => {
try {
const order = await store.read(parseInt(req.params.id))
res.json(order)
} catch (err) {
res.status(400)
res.json(err)
}
}
const create = async (req: Request, res: Response) => {
try {
const orderInfo: Order = {
status: req.body.status,
user_id: parseInt(req.body.user_id)
}
const newOrder = await store.create(orderInfo)
res.json(newOrder)
} catch (err) {
res.status(400)
res.json(err)
}
}
const addProduct = async (req: Request, res: Response) => {
try {
const orderProductInfo: OrderProduct = {
order_id: parseInt(req.params.id),
quantity: parseInt(req.body.quantity),
product_id: parseInt(req.body.product_id)
}
const addedProduct = await store.addProduct(orderProductInfo)
res.json(addedProduct)
} catch (err) {
res.status(400)
res.json(err)
}
}
const deleteProduct = async (req: Request, res: Response) => {
try {
const deletedProduct = await store.deleteProduct(parseInt(req.params.id))
res.json(deletedProduct)
} catch (err) {
res.status(400)
res.json(err)
}
}
export default orderRoutes

@ -0,0 +1,80 @@
import express, { Request, Response } from 'express'
import { Product, ProductStore } from '../models/product'
import { verifyAuthToken } from './utils'
const productRoutes = (app: express.Application) => {
app.get('/products', index)
app.get('/products/:id', read)
app.post('/products', verifyAuthToken, create)
app.put('/products/:id', verifyAuthToken, update)
app.delete('/products/:id', verifyAuthToken, destroy)
}
const store = new ProductStore()
const index = async (req: Request, res: Response) => {
try {
const products = await store.index()
res.json(products)
} catch (err) {
res.status(400)
res.json(err)
}
}
const read = async (req: Request, res: Response) => {
try {
const product = await store.read(parseInt(req.params.id))
res.json(product)
} catch (err) {
res.status(400)
res.json(err)
}
}
const create = async (req: Request, res: Response) => {
try {
const productInfo: Product = {
name: req.body.name,
price: req.body.price,
}
const newProduct = await store.create(productInfo)
res.json(newProduct)
} catch (err) {
res.status(400)
res.json(err)
}
}
const update = async (req: Request, res: Response) => {
try {
const productInfo: Product = {
id: parseInt(req.params.id),
name: req.body.name,
price: req.body.price,
}
const updatedProduct = await store.update(productInfo)
res.json(updatedProduct)
} catch (err) {
res.status(400)
res.json(err)
}
}
const destroy = async (req: Request, res: Response) => {
try {
const deleted = await store.delete(parseInt(req.params.id))
res.json(deleted)
} catch (err) {
res.status(400)
res.json(err)
}
}
export default productRoutes

@ -0,0 +1,113 @@
import express, { Request, Response } from 'express'
import { User, UserStore } from '../models/user'
import { verifyAuthToken, verifyUserToken} from './utils'
const userRoutes = (app: express.Application) => {
app.get('/users', verifyAuthToken, index)
app.get('/users/:id', verifyAuthToken, read)
app.post('/users', create)
app.put('/users/:id', verifyAuthToken, update)
app.delete('/users/:id', verifyAuthToken, destroy)
app.post("/users/auth", authenticate)
}
const store = new UserStore()
const index = async (req: Request, res: Response) => {
try {
const users = await store.index()
res.json(users);
} catch (err) {
res.status(400)
res.json(err)
}
}
const read = async (req: Request, res: Response) => {
try {
const user = await store.read(parseInt(req.params.id))
res.json(user)
} catch (err) {
res.status(400)
res.json(err)
}
}
const create = async (req: Request, res: Response) => {
const userInfo: User = {
firstname: req.body.firstname,
lastname: req.body.lastname,
username: req.body.username,
password: req.body.password
}
try {
const newUser = await store.create(userInfo)
res.json(verifyUserToken(newUser))
} catch(err) {
res.status(400)
res.json(err)
}
}
const update = async (req: Request, res: Response) => {
try {
const userInfo: User = {
id: parseInt(req.params.id),
firstname: req.body.firstname,
lastname: req.body.lastname,
username: req.body.username,
password: req.body.password
}
const updatedUser = await store.update(userInfo)
res.json(updatedUser)
} catch (err) {
res.status(400)
res.json(err)
}
}
const destroy = async (req: Request, res: Response) => {
try {
const deleted = await store.delete(parseInt(req.params.id))
res.json(deleted)
} catch (err) {
res.status(400)
res.json(err)
}
}
const authenticate = async (req: Request, res: Response) => {
try {
const userInfo: User = {
username: req.body.username,
password: req.body.password
}
if (userInfo.username === undefined || userInfo.password === undefined) {
res.status(400)
res.send("Missing credentials username or password")
}
const authUser: User | null = await store.authenticate(userInfo.username, userInfo.password)
if (authUser === null) {
res.status(401)
res.send("Password is incorrect")
}
res.json(verifyUserToken(authUser))
} catch(err) {
res.status(401)
res.json(err)
}
}
export default userRoutes

@ -0,0 +1,27 @@
import jwt, { Secret } from "jsonwebtoken"
import { User } from "../models/user"
import { NextFunction, Request, Response } from "express"
const SECRET = process.env.TOKEN_SECRET as Secret
export const verifyAuthToken = (req: Request, res: Response, next: NextFunction) => {
if (!req.headers.authorization) {
res.status(401)
res.json("Missing authorization header")
return false
}
try {
const token = req.headers.authorization.split(' ')[1]
jwt.verify(token, SECRET)
next()
} catch (err) {
res.status(401)
res.json("Access denied, invalid token")
}
}
export const verifyUserToken = (user: User | null) => {
return jwt.sign({ user }, SECRET)
}

@ -0,0 +1,135 @@
import client from '../database'
export type Order = {
id?: number
status: string
user_id: number
}
export type OrderProduct = {
id?: number
quantity: number
order_id: number
product_id: number
}
export class OrderStore {
async index(): Promise<Order[]> {
try {
const conn = await client.connect()
const sql = 'SELECT * FROM orders'
const result = await conn.query(sql)
const orders = result.rows
conn.release()
return orders
} catch (err) {
throw new Error(`Cannot get any order ${err}`)
}
}
async read(user_id: number): Promise<Order> {
try {
const conn = await client.connect()
const sql = 'SELECT * FROM orders WHERE user_id=($1)'
const result = await conn.query(sql, [user_id])
const order = result.rows[0]
conn.release()
return order
} catch (err) {
throw new Error(`Could not find order ${user_id}. Error: ${err}`)
}
}
async create(o: Order): Promise<Order> {
try {
const conn = await client.connect()
const sql = 'INSERT INTO orders (status, user_id) VALUES ($1, $2) RETURNING *'
const result = await conn.query(sql, [o.status, o.user_id])
const order = result.rows[0]
conn.release()
return order
} catch (err) {
throw new Error(`Could not add a new order ${o.user_id}. Error: ${err}`)
}
}
async addProduct(o: OrderProduct): Promise<OrderProduct> {
try {
const ordersql = 'SELECT * FROM orders WHERE id=($1)'
const conn = await client.connect()
const result = await conn.query(ordersql, [o.order_id])
const order = result.rows[0]
if(order.status !== "active") {
throw new Error(`Could not add product ${o.product_id} to order ${o.order_id} because order status is ${order.status}`)
}
conn.release()
} catch (err) {
throw new Error(`${err}`)
}
try {
const sql = 'INSERT INTO order_products (quantity, order_id, product_id) VALUES ($1, $2, $3) RETURNING *'
const conn = await client.connect()
const result = await conn.query(sql, [o.quantity, o.order_id, o.product_id])
const order = result.rows[0]
conn.release()
return order
} catch (err) {
throw new Error(`Could not add product ${o.product_id} to order ${o.order_id}: Error: ${err}`)
}
}
async deleteProduct(id: number): Promise<Order> {
try {
const sql = 'DELETE FROM order_products WHERE order_id=($1)'
const conn = await client.connect()
const result = await conn.query(sql, [id])
conn.release()
} catch (err) {
throw new Error(`Could not delete order ${id}: Error: ${err}`)
}
try {
const ordersql = 'DELETE FROM orders WHERE id=($1)'
const conn = await client.connect()
const ordersResult = await conn.query(ordersql, [id])
const order = ordersResult.rows[0]
conn.release()
return order
} catch (err) {
throw new Error(`${err}`)
}
}
}

@ -0,0 +1,101 @@
import client from '../database';
export type Product = {
id?: number;
name: string;
price: number;
}
export class ProductStore {
async index(): Promise<Product[]> {
try {
const conn = await client.connect()
const sql = 'SELECT * FROM products'
const result = await conn.query(sql)
const products = result.rows
conn.release()
return products
} catch (err) {
throw new Error(`Cannot get any products ${err}`)
}
}
async read(id: number): Promise<Product> {
try {
const conn = await client.connect()
const sql = 'SELECT * FROM products WHERE id=($1)'
const result = await conn.query(sql, [id])
const product = result.rows[0]
conn.release()
return product
} catch (err) {
throw new Error(`Could not find product ${id}. Error: ${err}`)
}
}
async create(p: Product): Promise<Product> {
try {
const conn = await client.connect()
const sql = 'INSERT INTO products (name, price) VALUES ($1, $2) RETURNING *'
const result = await conn.query(sql, [p.name, p.price])
const product = result.rows[0]
conn.release()
return product
} catch (err) {
throw new Error(`Could not add a new product ${p.name}. Error: ${err}`)
}
}
async update(p: Product): Promise<Product> {
try {
const conn = await client.connect();
const sql = 'UPDATE products SET name=$1, price=$2 WHERE id=$3 RETURNING *'
const result = await conn.query(sql, [p.name, p.price, p.id])
const product = result.rows[0]
conn.release()
return product
} catch (err) {
throw new Error(`Could not update product ${p.id}. Error: ${err}`)
}
}
async delete(id: number): Promise<Product> {
try {
const conn = await client.connect()
const sql = 'DELETE FROM products WHERE id=($1)'
const result = await conn.query(sql, [id])
const product = result.rows[0]
conn.release()
return product
} catch (err) {
throw new Error(`Could not delete product ${id}. Error: ${err}`)
}
}
}

@ -0,0 +1,142 @@
import client from '../database';
import bcrypt from 'bcrypt';
const { BCRYPT_PASSWORD, SALT_ROUNDS} = process.env
const pepper = BCRYPT_PASSWORD
const saltRounds = SALT_ROUNDS
export type User = {
id?: number;
firstname?: string;
lastname?: string;
username: string;
password: string;
}
export class UserStore {
async index(): Promise<User[]> {
try {
const conn = await client.connect()
const sql = 'SELECT * FROM users'
const result = await conn.query(sql)
const users = result.rows
conn.release()
return users
} catch (err) {
throw new Error(`Cannot get any users ${err}`)
}
}
async read(id: number): Promise<User> {
try {
const conn = await client.connect()
const sql = 'SELECT * FROM users WHERE id=($1)'
const result = await conn.query(sql, [id])
const user = result.rows[0]
conn.release()
return user
} catch (err) {
throw new Error(`Could not find user ${id}. Error: ${err}`)
}
}
async create(u: User): Promise<User> {
try {
const conn = await client.connect()
const sql = 'INSERT INTO users (firstname, lastname, username, password) VALUES ($1, $2, $3, $4) RETURNING *'
const hash = bcrypt.hashSync(
u.password + pepper,
parseInt(saltRounds as string, 10)
)
const result = await conn.query(sql, [u.firstname, u.lastname, u.username, hash])
const user = result.rows[0]
conn.release()
return user
} catch(err) {
throw new Error(`Could not add a new user ${u.firstname}. Error: ${err}`)
}
}
async update(u: User): Promise<User> {
try {
const conn = await client.connect()
const sql = 'UPDATE users SET firstname=$1, lastName=$2, username=$3, password=$4 WHERE id=$5 RETURNING *'
const hash = bcrypt.hashSync(
u.password + pepper,
parseInt(saltRounds as string, 10)
)
const result = await conn.query(sql, [u.firstname, u.lastname, u.username, hash, u.id])
const user = result.rows[0]
conn.release()
return user
} catch (err) {
throw new Error(`Could not update user ${u.firstname}. Error: ${err}`)
}
}
async delete(id: Number): Promise<User> {
try {
const conn = await client.connect()
const sql = 'DELETE FROM users WHERE id=($1)'
const result = await conn.query(sql, [id])
const user = result.rows[0]
conn.release()
return user
} catch (err) {
throw new Error(`Could not delete user ${id}. Error: ${err}`)
}
}
async authenticate(username: string, password: string): Promise<User | null> {
try {
const conn = await client.connect()
//const sql = 'SELECT password FROM users WHERE username=($1)'
const sql = 'SELECT * FROM users WHERE username=($1)'
const result = await conn.query(sql, [username])
if(result.rows.length) {
const user = result.rows[0]
if (bcrypt.compareSync(password + pepper, user.password)) {
return user
}
}
conn.release()
return null
} catch (err) {
throw new Error(`Could not find user ${username}. Error: ${err}`)
}
}
}

@ -1,15 +1,29 @@
import express, { Request, Response } from 'express'
import bodyParser from 'body-parser'
import productRoutes from './handlers/products'
import userRoutes from './handlers/users'
import orderRoutes from './handlers/orders'
const app: express.Application = express()
const address: string = "0.0.0.0:3000"
const port = 3000;
app.use(bodyParser.json())
app.get('/', function (req: Request, res: Response) {
res.send('Hello World!')
res.send('Main API')
})
app.listen(3000, function () {
productRoutes(app)
userRoutes(app)
orderRoutes(app)
// Start express server
app.listen(port, function () {
console.log(`starting app on: ${address}`)
})
export default app;

@ -0,0 +1,89 @@
import supertest from "supertest"
import app from "../../server"
import { Product } from '../../models/product'
import { User } from '../../models/user'
import { Order, OrderProduct } from '../../models/order'
const token = process.env.TOKEN_SECRET_TEST as string
const request = supertest(app);
const testProduct: Product = {
id: 3,
name: "metro",
price: 10
}
const testUser: User = {
id: 3,
firstname: 'John',
lastname: 'Doe',
username: 'Jd',
password: 'password'
}
const testOrder: Order = {
status: "active",
user_id: 3
}
const testOrderProduct: OrderProduct = {
id: 2,
quantity: 5,
order_id: 2,
product_id: 3
}
describe("Order handler", () => {
beforeAll( async () => {
const product = await request
.post('/products')
.auth(token, {type: "bearer"})
.send(testProduct)
const user = await request
.post('/users')
.auth(token, {type: "bearer"})
.send(testUser)
})
it('Should create a new order', async () => {
const response = await request
.post("/orders")
.auth(token, {type: "bearer"})
.send(testOrder)
expect(response.status).toBe(200)
})
it('Should index orders', async () => {
const response = await request
.get("/orders")
expect(response.status).toBe(200)
})
it('Should get order by id', async () => {
const response = await request
.get("/orders/2")
expect(response.status).toBe(200)
})
it('Should add a new product to order', async () => {
const response = await request
.post('/orders/2/products')
.auth(token, {type: 'bearer'})
.send(testOrderProduct)
expect(response.status).toBe(200)
})
it('Should delete order', async () => {
const response = await request
.delete('/orders/2/products')
.auth(token, {type: 'bearer'})
expect(response.status).toBe(200)
})
})

@ -0,0 +1,64 @@
import supertest from "supertest"
import app from "../../server"
import { Product } from '../../models/product'
const token = process.env.TOKEN_SECRET_TEST as string
const request = supertest(app);
const testProduct: Product = {
name: "metro",
price: 10
}
const updatedProduct: Product = {
name: "1984",
price: 5
}
describe("Product handler", () => {
console.log("PRODUCT")
it('Should create a new product', async () => {
const response = await request
.post("/products")
.auth(token, { type: "bearer" })
.send(testProduct);
expect(response.status).toBe(200);
})
it('Should index products', async () => {
const response = await request
.get("/products")
expect(response.status).toBe(200);
})
it('Should get product by id', async () => {
const response = await request
.get("/products/1")
expect(response.status).toBe(200);
})
it('Should update product with id', async () => {
const response = await request
.put("/products/1")
.auth(token, { type: 'bearer'})
.send(updatedProduct)
expect(response.status).toBe(200);
})
it('Should delete product with id', async () => {
const response = await request
.delete("/products/1")
.auth(token, { type: 'bearer'})
expect(response.status).toBe(200);
})
})

@ -0,0 +1,75 @@
import supertest from "supertest"
import app from "../../server"
import { User } from '../../models/user'
const token = process.env.TOKEN_SECRET_TEST as string
const request = supertest(app);
const testUser: User = {
id: 1,
firstname: 'John',
lastname: 'Doe',
username: 'Jd',
password: 'password'
}
describe("Users handler", () => {
it('Should create a new user', async () => {
const response = await request
.post("/users")
.auth(token, {type: "bearer"})
.send(testUser);
expect(response.status).toBe(200)
})
it('Should authenticate a user', async () => {
const response = await request
.post("/users/auth")
.auth(token, {type: "bearer"})
.send(testUser);
expect(response.status).toBe(200)
})
it('Should index users', async () => {
const response = await request
.get("/users")
.auth(token, {type: "bearer"})
expect(response.status).toBe(200)
})
it('Should get user by id', async () => {
const response = await request
.get("/users/1")
.auth(token, {type: "bearer"})
expect(response.status).toBe(200)
})
it('Should update a user', async () => {
const updatedUser = {
id: 1,
firstname: 'Sara',
lastname: 'Doe',
username: 'Sd',
password: 'password123'
}
const response = await request
.put("/users/1")
.auth(token, {type: 'bearer'})
.send(updatedUser)
expect(response.status).toBe(200)
})
it('Should delete a user by id', async () => {
const response = await request
.delete("/users/1")
.auth(token, {type: 'bearer'})
expect(response.status).toBe(200)
})
})

@ -0,0 +1,98 @@
import { User, UserStore } from '../../models/user'
import { Product, ProductStore } from '../../models/product';
import { Order, OrderProduct, OrderStore } from '../../models/order'
const orderStore = new OrderStore()
const userStore = new UserStore()
const productStore = new ProductStore()
const testProduct: Product = {
id: 2,
name: '1984',
price: 5
}
const testUser: User = {
id: 2,
firstname: 'John',
lastname: 'Doe',
username: 'Jd',
password: 'password'
}
const testOrder: Order = {
id: 1,
status: "active",
user_id: 2
}
const testOrderProduct: OrderProduct = {
id: 1,
quantity: 5,
order_id: 1,
product_id: 2
}
describe("Order model", () => {
beforeAll(async () => {
await productStore.create(testProduct)
await userStore.create(testUser)
})
console.log('ORDER')
it('Should have an index method', () => {
expect(orderStore.index).toBeDefined();
});
it('Should have a read method', () => {
expect(orderStore.read).toBeDefined;
});
it('Should have a create method', () => {
expect(orderStore.create).toBeDefined;
});
it('Should have a update method', () => {
expect(orderStore.addProduct).toBeDefined;
});
it('Create method should add a new order', async () => {
const result = await orderStore.create(testOrder)
expect(result).toEqual(testOrder)
});
it('Index method should return a list of orders', async () => {
const result = await orderStore.index();
expect(result[0]).toEqual(testOrder)
})
it('Read method should return a order', async () => {
const result = await orderStore.read(2);
expect(result).toEqual(testOrder)
})
it('addProduct should add a new product to order', async () => {
const result = await orderStore.addProduct(testOrderProduct)
expect(result).toEqual(testOrderProduct)
})
it('deleteProduct should delete product from order', async () => {
const result = await orderStore.deleteProduct(1)
const checkorder = await orderStore.index()
expect(checkorder).toEqual([])
})
afterAll(async () => {
await productStore.delete(1)
await userStore.delete(1)
})
});

@ -0,0 +1,73 @@
import { Product, ProductStore } from '../../models/product'
const store = new ProductStore()
const testProduct: Product = {
id: 1,
name: '1984',
price: 5,
}
const updatedProduct: Product = {
id: 1,
name: 'The Dark Tower',
price: 7
}
describe("Product model", () => {
it('Should have an index method', () => {
expect(store.index).toBeDefined();
});
it('Should have a read method', () => {
expect(store.read).toBeDefined;
});
it('Should have a create method', () => {
expect(store.create).toBeDefined;
});
it('Should have a update method', () => {
expect(store.update).toBeDefined;
});
it('Should have a delete method', () => {
expect(store.delete).toBeDefined;
});
it('Create method should add a product', async () => {
const result = await store.create(testProduct);
expect(result).toEqual(testProduct);
});
it('Index method should return a list of products', async () => {
const result = await store.index();
expect(result).toEqual([testProduct]);
});
it('Read method should return a product', async () => {
const result = await store.read(1);
expect(result).toEqual(testProduct);
});
it('Update method should update a product', async () => {
const result = await store.update(updatedProduct);
expect(result).toEqual({
id: 1,
name: 'The Dark Tower',
price: 7,
});
});
it('Delete method should remove the product', async () => {
store.delete(1);
const result = await store.index()
expect(result).toEqual([]);
});
});

@ -0,0 +1,110 @@
import { User, UserStore } from '../../models/user'
import bcrypt from 'bcrypt'
const { BCRYPT_PASSWORD } = process.env
const pepper = BCRYPT_PASSWORD
const store = new UserStore()
const testUser: User = {
id: 1,
firstname: 'John',
lastname: 'Doe',
username: 'Jd',
password: 'password'
}
const updatedUser: User = {
id: 1,
firstname: 'Sara',
lastname: 'Doe',
username: 'Sd',
password: 'password123'
}
describe("User model", () => {
console.log("USER")
it('Should have an index method', () => {
expect(store.index).toBeDefined();
});
it('Should have a read method', () => {
expect(store.read).toBeDefined;
});
it('Should have a create method', () => {
expect(store.create).toBeDefined;
});
it('Should have a update method', () => {
expect(store.update).toBeDefined;
});
it('Should have a delete method', () => {
expect(store.delete).toBeDefined;
});
it('Create method should create a new user', async () => {
const result = await store.create(testUser);
expect(result.firstname).toEqual(testUser.firstname)
expect(result.lastname).toEqual(testUser.lastname)
expect(result.username).toEqual(testUser.username)
expect(bcrypt.compareSync(testUser.password + pepper, result.password)).toBeTrue
})
it('Index method should should return a list of users', async () => {
const result = await store.index();
expect(result[0].firstname).toEqual(testUser.firstname)
expect(result[0].lastname).toEqual(testUser.lastname)
expect(result[0].username).toEqual(testUser.username)
expect(bcrypt.compareSync(testUser.password + pepper, result[0].password)).toBeTrue
})
it('Read method should return user', async () => {
const result = await store.read(1);
expect(result.firstname).toEqual(testUser.firstname)
expect(result.lastname).toEqual(testUser.lastname)
expect(result.username).toEqual(testUser.username)
expect(bcrypt.compareSync(testUser.password + pepper, result.password)).toBeTrue
})
it('Authenticate method', async () => {
const authUser = {
username: 'Jd',
password: 'password'
}
const result = await store.authenticate(authUser.username, authUser.password);
if (result) {
expect(result.firstname).toEqual(testUser.firstname)
expect(result.lastname).toEqual(testUser.lastname)
expect(result.username).toEqual(testUser.username)
expect(bcrypt.compareSync(testUser.password + pepper, result.password)).toBeTrue
}
})
it('Update method should create a new user', async () => {
const result = await store.update(updatedUser);
expect(result.firstname).toEqual(updatedUser.firstname)
expect(result.lastname).toEqual(updatedUser.lastname)
expect(result.username).toEqual(updatedUser.username)
expect(bcrypt.compareSync(updatedUser.password + pepper, result.password)).toBeTrue
})
it('Delete method should delete a user by Id', async () => {
const result = await store.delete(1);
const checkUsers = await store.index()
expect(checkUsers).toEqual([])
})
})

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save