diff --git a/.gitignore b/.gitignore index 74ac92c..a0fca81 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,6 @@ dist/ lib-cov coverage -package-lock.json \ No newline at end of file +package-lock.json + +server/db/characteristics.csv \ No newline at end of file diff --git a/example.env b/example.env deleted file mode 100644 index 97e978e..0000000 --- a/example.env +++ /dev/null @@ -1 +0,0 @@ -PORT = XXXX \ No newline at end of file diff --git a/package.json b/package.json index 3884303..9faa01f 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "server-dev": "nodemon server/app.js" + "server-dev": "nodemon server/app.js", + "reviews-pg": "node server/db/postgres.sql" }, "repository": { "type": "git", @@ -20,10 +21,19 @@ "dependencies": { "axios": "^1.4.0", "cors": "^2.8.5", + "csv-parser": "^3.0.0", + "csv-stringify": "^6.4.0", + "csv-writer": "^1.6.0", "dotenv": "^16.0.3", "express": "^4.18.2", + "fs": "^0.0.1-security", + "k6": "^0.0.0", + "mongoose": "^7.2.0", "morgan": "^1.10.0", - "nodemon": "^2.0.22" + "nodemon": "^2.0.22", + "pg": "^8.11.0", + "pg-protocol": "^1.6.0", + "readline": "^1.3.0" }, "devDependencies": { "eslint": "^8.41.0", diff --git a/server/controllers/product.js b/server/controllers/product.js deleted file mode 100644 index 4e65177..0000000 --- a/server/controllers/product.js +++ /dev/null @@ -1,12 +0,0 @@ -const axios = require('axios'); - -module.exports.products = { - getAll: (req, res) => { - axios({ - url: 'products', - method: 'GET', - }) - .then((result) => { res.status(200).send(result.data); }) - .catch((error) => { res.status(500).send(error.message); }); - }, -}; diff --git a/server/controllers/qa.js b/server/controllers/qa.js deleted file mode 100644 index e69de29..0000000 diff --git a/server/controllers/reviews.js b/server/controllers/reviews.js index e69de29..4c385af 100644 --- a/server/controllers/reviews.js +++ b/server/controllers/reviews.js @@ -0,0 +1,161 @@ +/* eslint-disable camelcase */ +/* eslint-disable radix */ +const reviewPool = require('../db/postgres.js'); + +module.exports.review = { + getReviews: (req, res) => { + const page = Number(req.query.page) || 1; + const count = Number(req.query.count) || 5; + const productNum = Number(req.query.product_id); + const offset = (page - 1) * count; + + reviewPool.query( + `SELECT * + FROM revs + WHERE product_id = $3 + ORDER BY date + LIMIT $1 OFFSET $2`, + [count, offset, productNum], + ) + .then((results) => { + const resultArr = []; + const promises = results.rows.map((rev) => { + const revObj = { + review_id: rev.id, + rating: rev.rating, + summary: rev.summary, + recommend: rev.recommend, + response: rev.response === 'null' ? null : rev.recommend, + body: rev.body, + date: new Date(rev.date * 1).toISOString(), + reviewer_name: rev.reviewer_name, + helpfulness: rev.helpfulness, + photos: [], + }; + + return reviewPool.query( + `SELECT * + FROM revphotos + WHERE review_id = $1 + LIMIT $2 OFFSET $3`, + [rev.id, count, offset], + ).then((photosResult) => { + const photos = photosResult.rows; + revObj.photos = photos; + resultArr.push(revObj); + }); + }); + + Promise.all(promises).then(() => { + const data = { + product: `${productNum}`, + page: `${page}`, + count: `${count}`, + results: resultArr, + }; + + res.status(201).send(data); + }).catch((err) => { + console.error(err); + res.status(500).send(err); + }); + }) + .catch((err) => { + console.error(err); + res.status(500).send(err); + }); + }, + getMeta: (req, res) => { + // eslint-disable-next-line camelcase + const { product_id } = req.query; + const ratingCounts = {}; + const recommended = {}; + const charholder = {}; + + const fetchData = async () => { + try { + const ratingQuery = reviewPool.query( + `SELECT rating, COUNT(*) AS count + FROM revs + WHERE product_id = $1 + GROUP BY rating`, + [product_id], + ); + + const recommendedQuery = reviewPool.query( + `SELECT recommend, COUNT(*) AS count + FROM revs + WHERE product_id = $1 + GROUP BY recommend`, + [product_id], + ); + + const characteristicsQuery = reviewPool.query( + `SELECT + c.characteristic_id AS id, + ci.name AS characteristic_name, + AVG(c.value) AS average_value + FROM + charsinfo ci + JOIN + chars c ON ci.id = c.characteristic_id + WHERE + ci.product_id = $1 + GROUP BY + c.characteristic_id, ci.name`, + [product_id], + ); + + const [ratingResult, recommendedResult, characteristicsResult] = await Promise.all([ + ratingQuery, + recommendedQuery, + characteristicsQuery, + ]); + + ratingResult.rows.forEach((row) => { + const { rating } = row; + const count = parseInt(row.count); + ratingCounts[rating] = count; + }); + + // console.log(ratingCounts); + + recommendedResult.rows.forEach((row, index) => { + const count = parseInt(row.count); + recommended[`${index}`] = count; + }); + + // console.log(recommended); + + characteristicsResult.rows.forEach((char) => { + charholder[char.characteristic_name] = { + id: char.id, + value: Number(char.average_value).toFixed(4).toString(), + }; + }); + + // console.log('characteristics', charholder); + + // Continue with the rest of your code that depends on the fetched data + } catch (error) { + // console.error('Error fetching data:', error); + } + }; + // Call the async function + fetchData() + .then(() => { + res.status(201).send({ + product_id, + ratings: ratingCounts, + recommended, + characteristics: charholder, + }); + }) + .catch((err) => { + res.status(500).send(err); + }); + }, + // postRev: (req, res) => { + // console.log(req); + // }, +}; diff --git a/server/db/mongo.js b/server/db/mongo.js new file mode 100644 index 0000000..260b056 --- /dev/null +++ b/server/db/mongo.js @@ -0,0 +1,48 @@ +const mongoose = require('mongoose'); +require('dotenv').config(); + +// const PORT = process.env.PORT || 3500; +// console.log(process.env.DB_NAME); + +mongoose.connect(`mongodb://127.0.0.1:27017/${process.env.DB_NAME}`); + +const reviewSchema = new mongoose.Schema( + { + ratings: { + 1: Number, + 2: Number, + 3: Number, + 4: Number, + 5: Number, + }, + recommended: { + 0: Number, + }, + characteristics: { + Size: { + id: Number, + value: Number, + }, + } + results: [{ + rating: Number, + summary: String, + response: String, + body: String, + date: Date, + reviewer_name: String, + helpfulness: Number, + photos: [{ + url: String, + }, + // ... + ], + }, + // ... + ], + }, +); + +const Review = new mongoose.model('Review', reviewSchema); + +module.exports = Review; diff --git a/server/db/postgres.js b/server/db/postgres.js new file mode 100644 index 0000000..1e05c3c --- /dev/null +++ b/server/db/postgres.js @@ -0,0 +1,80 @@ +const { Pool, Client } = require('pg'); +const fs = require('fs'); +// eslint-disable-next-line import/no-extraneous-dependencies +const csv = require('csv-parser'); +// eslint-disable-next-line import/no-extraneous-dependencies +const createCsvWriter = require('csv-writer').createObjectCsvWriter; + +// eslint-disable-next-line import/prefer-default-export +const reviewPool = new Pool({ + user: process.env.USER, + host: process.env.HOST, + database: process.env.DB_NAME, + port: process.env.DBPORT, +}); + +async function connectAndCreateSchema() { + const client = new Client({ + database: 'reviews', + }); + + try { + await client.connect(); + console.log('Database connection successful'); + // Rest of your code... + } catch (error) { + console.error('Error connecting to database:', error); + } + + await client.query( + ` + CREATE INDEX idx_chars_characteristic_id + ON chars(characteristic_id); + + CREATE INDEX idx_revs_product_id + ON revs(product_id); + + CREATE INDEX idx_revphotos_review_id + ON revphotos(review_id); + + CREATE TABLE charsinfo ( + id SERIAL primary key, + product_id int, + name varchar(50) + ); + + CREATE TABLE revs ( + id SERIAL primary key, + product_id int, + rating int, + date bigint, + summary varchar(500), + body varchar(500), + recommend boolean, + reported boolean, + reviewer_name varchar(500), + reviewer_email varchar(500), + response varchar(500) null, + helpfulness int + ); + + CREATE TABLE chars ( + id SERIAL primary key, + characteristic_id SERIAL, + review_id int REFERENCES revs(id), + value int + ); + + CREATE TABLE revPhotos ( + id SERIAL PRIMARY KEY, + review_id INT REFERENCES revs(id), + url VARCHAR(500) + ); + `, + + ); + client.end(); +} +// connectAndCreateSchema(); + +module.exports = reviewPool; diff --git a/server/db/postgres.sql b/server/db/postgres.sql new file mode 100644 index 0000000..88b1e44 --- /dev/null +++ b/server/db/postgres.sql @@ -0,0 +1,23 @@ +DROP TABLE IF EXISTS revs; +DROP TABLE IF EXISTS meta; + +CREATE TABLE revs ( + review_id SERIAL primary key, + product int, + rating int, + summary varchar(100), + recommend boolean, + response varchar(100), + body varchar(100), + review_name varchar(30), + helpfulness int, + photos JSONB +); + +CREATE TABLE meta ( + product_id int primary key, + ratings JSONB, + recommended int, + characteristics JSONB +) + diff --git a/server/index.js b/server/index.js index 74198c8..9385144 100644 --- a/server/index.js +++ b/server/index.js @@ -1,5 +1,4 @@ require('dotenv').config(); -// const path = require('path'); const express = require('express'); const morgan = require('morgan'); @@ -18,6 +17,6 @@ app.use(express.urlencoded({ extended: true })); app.use(express.json()); // Set up routes -app.use(routes); +routes(app); module.exports = app; diff --git a/server/k6-testing/testing.js b/server/k6-testing/testing.js new file mode 100644 index 0000000..30e1c2b --- /dev/null +++ b/server/k6-testing/testing.js @@ -0,0 +1,20 @@ +/* eslint-disable import/no-unresolved */ +// eslint-disable-next-line import/no-unresolved +import { sleep } from 'k6'; +import http from 'k6/http'; +// import { review } from '../controllers/reviews.js'; +export const options = { + stages: [ + // { duration: '5s', target: 10 }, + { duration: '5s', target: 100 }, + ], +}; +export default function () { + // const req = { product_id: 30344 }; + const productId = 952409; + const reviewsUrl = `http://127.0.0.1:3500/reviews?product_id=${productId}`; + const metaUrl = `http://127.0.0.1:3500/reviews/meta?product_id=${productId}`; + http.get(reviewsUrl); + http.get(metaUrl); + sleep(1); +} diff --git a/server/routes/index.js b/server/routes/index.js index dd3dce5..b329a66 100644 --- a/server/routes/index.js +++ b/server/routes/index.js @@ -1,9 +1,5 @@ const reviews = require('./reviews'); -const qa = require('./qa'); -const product = require('./product'); module.exports = (app) => { reviews(app); - qa(app); - product(app); }; diff --git a/server/routes/reviews.js b/server/routes/reviews.js index e69de29..feaa4cc 100644 --- a/server/routes/reviews.js +++ b/server/routes/reviews.js @@ -0,0 +1,8 @@ +// eslint-disable-next-line import/extensions +const { review } = require('../controllers/reviews.js'); + +module.exports = (app) => { + app.get('/reviews', review.getReviews); + app.get('/reviews/meta', review.getMeta); + // app.post('/reviews', review.postRev); +};