feat: add projects files

master
adriano 2023-10-09 14:22:48 -03:00
commit 4f8e2efd85
31 changed files with 22503 additions and 0 deletions

View File

@ -0,0 +1,3 @@
{
"extends": ["@commitlint/config-conventional"]
}

2
.gitignore vendored 100644
View File

@ -0,0 +1,2 @@
/node_modules
.env

View File

@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx commitlint --edit

1
Procfile 100644
View File

@ -0,0 +1 @@
web: node app.js

62
app.js 100644
View File

@ -0,0 +1,62 @@
require('dotenv').config()
require('express-async-errors')
// express
const express = require('express')
const app = express()
// rest of the packages
const morgan = require('morgan')
// const fileUpload = require('express-fileupload')
const rateLimiter = require('express-rate-limit')
const helmet = require('helmet')
const xss = require('xss-clean')
const cors = require('cors')
// database
const connectDB = require('./db/connect')
// routers
const nlRouter = require('./routes/naturalLanguageRoute')
const notFoundMiddlware = require('./middleware/not-found')
const errorHandlerMiddleware = require('./middleware/error-handler')
//middleware
app.set('trust proxy', 1)
app.use(rateLimiter({
windowMs: 15 * 60 * 1000,
max: 60,
}))
// Security packages
app.use(helmet())
app.use(cors())
app.use(xss())
app.use(morgan('tiny'))
app.use(express.json())
app.use(express.static('./public'))
// app.use(fileUpload())
app.get('/', (req, res) => {
res.send('e-commerce api')
})
// app.get('/api/v1/', (req, res) => {
// console.log(req.signedCookies)
// res.send('e-commerce api')
// })
app.use('/api/v1/nl', nlRouter)
app.use(notFoundMiddlware)
app.use(errorHandlerMiddleware)
const port = process.env.PORT || 3000
app.listen(port, console.log(`Listening on port: ${port}...`))

View File

@ -0,0 +1,75 @@
const User = require('../models/User')
const { StatusCodes } = require('http-status-codes')
const CustomError = require('../errors')
const { attachCookiesToResponse, createTokenUser } = require('../utils')
const register = async (req, res) => {
const { email, name, password } = req.body
const emailAlreadyExists = await User.findOne({ email })
if (emailAlreadyExists) {
throw new CustomError.BadRequestError('Email already exists')
}
// first register user is an admin
const isFirstAccount = await User.countDocuments({}) === 0
const role = isFirstAccount ? 'admin' : 'user'
const user = await User.create({ name, email, password, role })
const tokenUser = createTokenUser(user)
attachCookiesToResponse({ res, user: tokenUser })
res.status(StatusCodes.CREATED).json({ user: tokenUser })
}
const login = async (req, res) => {
const { email, password } = req.body
if (!email || !password) {
throw new CustomError.BadRequestError('Please provide email and password')
}
const user = await User.findOne({ email })
if (!user) {
throw new CustomError.UnauthenticatedError('Ivalid Credentials')
}
const isPasswordCorret = await user.comparePassword(password)
if (!isPasswordCorret) {
throw new CustomError.UnauthenticatedError('Ivalid Credentials')
}
const tokenUser = {
name: user.name,
userId: user._id,
role: user.role
}
attachCookiesToResponse({ res, user: tokenUser })
res.status(StatusCodes.OK).json({ user: tokenUser })
}
const logout = async (req, res) => {
res.cookie('token', 'logout', {
httpOnly: true,
expires: new Date(Date.now())
})
res.status(StatusCodes.OK).json({
msg: 'user logged out!'
})
}
module.exports = {
register,
login,
logout
}

View File

@ -0,0 +1,62 @@
const { StatusCodes } = require("http-status-codes")
const { sentiment, convertTextToSpeech, listVoice } = require("../utils")
const language = require('@google-cloud/language').v2
const CustomError = require('../errors')
const voiceConfigList = require('../mockData/voice.json')
const getSentiment = async (req, res) => {
const { text, } = req.body
const status = await sentiment(text)
res.status(StatusCodes.OK).json({ status })
}
const getAudioFromText = async (req, res) => {
const { text, voice_name, voice_gender, languageCode } = req.query
if ((voice_name || voice_gender || languageCode) && languageCode == 'pt-BR') {
const config = { voice_name, voice_gender, languageCode }
for (const key in config) {
if (config.hasOwnProperty(key) && config[key] === undefined) {
throw new CustomError.BadRequestError(`The key ${key} is required when setted one of the three configuration parameters: voice_name, voice_gender and languageCode`)
}
}
const voice = voiceConfigList.find(({ name, ssmlGender, languageCode }) => {
if (name == config.voice_name && ssmlGender == config.voice_gender && languageCode == config.languageCode) return { name, ssmlGender, languageCode }
})
if (!voice)
throw new CustomError.BadRequestError(`Wrong config voice combination! Check the endpoint(http://localhost:3000/api/v1/nl/voice-config) to display the available configurations to a language`)
}
const audioBuffer = await convertTextToSpeech(text, voice_name, voice_gender, languageCode)
res.contentType('audio/mpeg')
res.status(StatusCodes.OK).send(audioBuffer)
}
const getVoiceConfig = async (req, res) => {
const { languageCode } = req.query
console.log(languageCode)
const configs = await listVoice(languageCode)
res.status(StatusCodes.OK).json({ configs })
}
module.exports = {
getSentiment,
getAudioFromText,
getVoiceConfig
}

9
db/connect.js 100644
View File

@ -0,0 +1,9 @@
const mongoose = require('mongoose');
const connectDB = (url) => {
return mongoose.connect(url);
};
module.exports = connectDB;

View File

@ -0,0 +1,11 @@
const { StatusCodes } = require('http-status-codes');
const CustomAPIError = require('./custom-api');
class BadRequestError extends CustomAPIError {
constructor(message) {
super(message);
this.statusCode = StatusCodes.BAD_REQUEST;
}
}
module.exports = BadRequestError;

View File

@ -0,0 +1,7 @@
class CustomAPIError extends Error {
constructor(message) {
super(message)
}
}
module.exports = CustomAPIError

13
errors/index.js 100644
View File

@ -0,0 +1,13 @@
const CustomAPIError = require('./custom-api');
const UnauthenticatedError = require('./unauthenticated');
const NotFoundError = require('./not-found');
const BadRequestError = require('./bad-request');
const UnauthorizedError = require('./unauthorized');
module.exports = {
CustomAPIError,
UnauthenticatedError,
NotFoundError,
BadRequestError,
UnauthorizedError,
};

View File

@ -0,0 +1,11 @@
const { StatusCodes } = require('http-status-codes');
const CustomAPIError = require('./custom-api');
class NotFoundError extends CustomAPIError {
constructor(message) {
super(message);
this.statusCode = StatusCodes.NOT_FOUND;
}
}
module.exports = NotFoundError;

View File

@ -0,0 +1,11 @@
const { StatusCodes } = require('http-status-codes');
const CustomAPIError = require('./custom-api');
class UnauthenticatedError extends CustomAPIError {
constructor(message) {
super(message);
this.statusCode = StatusCodes.UNAUTHORIZED;
}
}
module.exports = UnauthenticatedError;

View File

@ -0,0 +1,11 @@
const { StatusCodes } = require('http-status-codes');
const CustomAPIError = require('./custom-api');
class UnauthorizedError extends CustomAPIError {
constructor(message) {
super(message);
this.statusCode = StatusCodes.FORBIDDEN;
}
}
module.exports = UnauthorizedError;

View File

@ -0,0 +1,41 @@
const CustomError = require('../errors')
const authorization = async (req, res, next) => {
const authHeader = req.headers.authorization
if (!authHeader) {
throw new CustomError.BadRequestError('Authorization not found into header!')
}
const [, token] = authHeader.split(" ");
if (!token) {
throw new CustomError.BadRequestError('Authorization token not found into header!')
}
if (token != process.env.TOKEN){
throw new CustomError.UnauthorizedError('Authorization token Invalid')
}
next()
}
const authorizePermissions = (...roles) => {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
throw new CustomError.UnauthorizedError('Unauthorized do access to this routes')
}
next()
}
}
module.exports = {
authorization,
authorizePermissions,
}

View File

@ -0,0 +1,28 @@
const { StatusCodes } = require('http-status-codes');
const errorHandlerMiddleware = (err, req, res, next) => {
let customError = {
// set default
statusCode: err.statusCode || StatusCodes.INTERNAL_SERVER_ERROR,
msg: err.message || 'Something went wrong try again later',
};
if (err.name === 'ValidationError') {
customError.msg = Object.values(err.errors)
.map((item) => item.message)
.join(',');
customError.statusCode = 400;
}
if (err.code && err.code === 11000) {
customError.msg = `Duplicate value entered for ${Object.keys(
err.keyValue
)} field, please choose another value`;
customError.statusCode = 400;
}
if (err.name === 'CastError') {
customError.msg = `No item found with id : ${err.value}`;
customError.statusCode = 404;
}
return res.status(customError.statusCode).json({ msg: customError.msg });
};
module.exports = errorHandlerMiddleware;

View File

@ -0,0 +1,3 @@
const notFound = (req, res) => res.status(404).send('Route does not exist')
module.exports = notFound

View File

@ -0,0 +1,35 @@
[
{
"tax": 399,
"shippingFee": 499,
"items": [
{
"name": "accent chair",
"price": 2599,
"image": "https://dl.airtable.com/.attachmentThumbnails/e8bc3791196535af65f40e36993b9e1f/438bd160",
"amount": 34,
"product": "6126ad3424d2bd09165a68c8"
}
]
},
{
"tax": 499,
"shippingFee": 799,
"items": [
{
"name": "bed",
"price": 2699,
"image": "https://dl.airtable.com/.attachmentThumbnails/e8bc3791196535af65f40e36993b9e1f/438bd160",
"amount": 3,
"product": "6126ad3424d2bd09165a68c7"
},
{
"name": "chair",
"price": 2999,
"image": "https://dl.airtable.com/.attachmentThumbnails/e8bc3791196535af65f40e36993b9e1f/438bd160",
"amount": 2,
"product": "6126ad3424d2bd09165a68c4"
}
]
}
]

View File

@ -0,0 +1,38 @@
[
{
"name": "accent chair",
"price": 25999,
"image": "https://dl.airtable.com/.attachmentThumbnails/e8bc3791196535af65f40e36993b9e1f/438bd160",
"colors": ["#ff0000", "#00ff00", "#0000ff"],
"company": "marcos",
"description": "Cloud bread VHS hell of banjo bicycle rights jianbing umami mumblecore etsy 8-bit pok pok +1 wolf. Vexillologist yr dreamcatcher waistcoat, authentic chillwave trust fund. Viral typewriter fingerstache pinterest pork belly narwhal. Schlitz venmo everyday carry kitsch pitchfork chillwave iPhone taiyaki trust fund hashtag kinfolk microdosing gochujang live-edge",
"category": "office"
},
{
"name": "albany sectional",
"price": 109999,
"image": "https://dl.airtable.com/.attachmentThumbnails/0be1af59cf889899b5c9abb1e4db38a4/d631ac52",
"colors": ["#000", "#ffb900"],
"company": "liddy",
"description": "Cloud bread VHS hell of banjo bicycle rights jianbing umami mumblecore etsy 8-bit pok pok +1 wolf. Vexillologist yr dreamcatcher waistcoat, authentic chillwave trust fund. Viral typewriter fingerstache pinterest pork belly narwhal. Schlitz venmo everyday carry kitsch pitchfork chillwave iPhone taiyaki trust fund hashtag kinfolk microdosing gochujang live-edge",
"category": "kitchen"
},
{
"name": "armchair",
"price": 12599,
"image": "https://dl.airtable.com/.attachmentThumbnails/530c07c5ade5acd9934c8dd334458b86/cf91397f",
"colors": ["#000", "#00ff00", "#0000ff"],
"company": "marcos",
"description": "Cloud bread VHS hell of banjo bicycle rights jianbing umami mumblecore etsy 8-bit pok pok +1 wolf. Vexillologist yr dreamcatcher waistcoat, authentic chillwave trust fund. Viral typewriter fingerstache pinterest pork belly narwhal. Schlitz venmo everyday carry kitsch pitchfork chillwave iPhone taiyaki trust fund hashtag kinfolk microdosing gochujang live-edge",
"category": "bedroom"
},
{
"name": "emperor bed",
"price": 23999,
"image": "https://dl.airtable.com/.attachmentThumbnails/0446e84c5bca9643de3452a61b2d6195/1b32f48b",
"colors": ["#0000ff", "#000"],
"company": "ikea",
"description": "Cloud bread VHS hell of banjo bicycle rights jianbing umami mumblecore etsy 8-bit pok pok +1 wolf. Vexillologist yr dreamcatcher waistcoat, authentic chillwave trust fund. Viral typewriter fingerstache pinterest pork belly narwhal. Schlitz venmo everyday carry kitsch pitchfork chillwave iPhone taiyaki trust fund hashtag kinfolk microdosing gochujang live-edge",
"category": "bedroom"
}
]

View File

@ -0,0 +1,46 @@
[
{
"name": "pt-BR-Wavenet-A",
"ssmlGender": "FEMALE",
"languageCode": "pt-BR"
},
{ "name": "pt-BR-Wavenet-B", "ssmlGender": "MALE", "languageCode": "pt-BR" },
{
"name": "pt-BR-Wavenet-C",
"ssmlGender": "FEMALE",
"languageCode": "pt-BR"
},
{
"name": "pt-BR-Standard-A",
"ssmlGender": "FEMALE",
"languageCode": "pt-BR"
},
{ "name": "pt-BR-Standard-B", "ssmlGender": "MALE", "languageCode": "pt-BR" },
{
"name": "pt-BR-Standard-C",
"ssmlGender": "FEMALE",
"languageCode": "pt-BR"
},
{
"name": "pt-BR-Neural2-A",
"ssmlGender": "FEMALE",
"languageCode": "pt-BR"
},
{ "name": "pt-BR-Neural2-B", "ssmlGender": "MALE", "languageCode": "pt-BR" },
{
"name": "pt-BR-Neural2-C",
"ssmlGender": "FEMALE",
"languageCode": "pt-BR"
},
{
"name": "pt-BR-Standard-A",
"ssmlGender": "FEMALE",
"languageCode": "pt-BR"
},
{ "name": "pt-BR-Standard-B", "ssmlGender": "MALE", "languageCode": "pt-BR" },
{
"name": "pt-BR-Standard-C",
"ssmlGender": "FEMALE",
"languageCode": "pt-BR"
}
]

8391
package-lock.json generated 100644

File diff suppressed because it is too large Load Diff

38
package.json 100644
View File

@ -0,0 +1,38 @@
{
"name": "sentiment_api",
"version": "1.0.0",
"description": "",
"main": "app.js",
"scripts": {
"start": "node app.js",
"dev": "nodemon app.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"@google-cloud/language": "^6.1.0",
"@google-cloud/text-to-speech": "^5.0.1",
"bcryptjs": "^2.4.3",
"cookie-parser": "^1.4.5",
"cors": "^2.8.5",
"dotenv": "^10.0.0",
"express": "^4.17.1",
"express-async-errors": "^3.1.1",
"express-fileupload": "^1.2.1",
"express-mongo-sanitize": "^2.1.0",
"express-rate-limit": "^5.4.1",
"helmet": "^4.6.0",
"http-status-codes": "^2.1.4",
"joi": "^17.4.0",
"mongoose": "^7.3.1",
"morgan": "^1.10.0",
"validator": "^13.6.0",
"xss-clean": "^0.1.1"
},
"devDependencies": {
"@commitlint/cli": "^17.7.2",
"@commitlint/config-conventional": "^17.7.0",
"husky": "^8.0.3",
"nodemon": "^2.0.9"
}
}

4417
public/browser-app.js 100644

File diff suppressed because it is too large Load Diff

9057
public/index.html 100644

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

View File

@ -0,0 +1,11 @@
const express = require('express')
const router = express.Router()
const { authorization, } = require('../middleware/authentication')
const { getSentiment, getAudioFromText, getVoiceConfig } = require('../controllers/naturalLanguageController')
router.route('/sentiment').post(authorization, getSentiment)
router.route('/text-to-speech').get(getAudioFromText)
router.route('/voice-config').get(getVoiceConfig)
module.exports = router

14
utils/index.js 100644
View File

@ -0,0 +1,14 @@
// const { createJWT, isTokenValid, attachCookiesToResponse } = require('./jwt')
// const createTokenUser = require('./createTokenUser')
// const checkPermissions = require('./checkPermissions')
const sentiment = require('./sentiment')
const convertTextToSpeech = require('./textToSpeech')
const listVoice = require('./listVoice')
module.exports = {
sentiment,
convertTextToSpeech,
listVoice
}

13
utils/listVoice.js 100644
View File

@ -0,0 +1,13 @@
const textToSpeech = require('@google-cloud/text-to-speech')
const listVoice = async (languageCode) => {
const client = new textToSpeech.TextToSpeechClient()
const [result] = await client.listVoices({ languageCode })
const voices = result.voices
return voices.map(({ name, ssmlGender, languageCodes }) => { return { name, ssmlGender, languageCodes: languageCodes[0] } })
}
module.exports = listVoice

42
utils/sentiment.js 100644
View File

@ -0,0 +1,42 @@
// Imports the Google Cloud client library
const language = require('@google-cloud/language').v2
// Creates a client
const client = new language.LanguageServiceClient()
const sentiment = async (text) => {
// Prepares a document, representing the provided text
const document = {
content: text,
type: 'PLAIN_TEXT',
}
let status = null
try {
// Detects the sentiment of the document
const [result] = await client.analyzeSentiment({ document })
const sentiment = result.documentSentiment
if (sentiment.score <= -0.25)
status = 'negative'
else if (sentiment.score <= 0.25)
status = 'neutral'
else
status = 'positive'
console.log(` Text: ${document.content}`)
console.log(` Score: ${sentiment.score}`)
console.log(` Magnitude: ${sentiment.magnitude}`)
console.log(` languageCode: ${result.languageCode}`)
} catch (error) {
console.log(`Error in sentiment fuction on sentiment.js file: ${error}`)
}
return status
}
module.exports = sentiment

View File

@ -0,0 +1,47 @@
// Imports the Google Cloud client library
const textToSpeech = require('@google-cloud/text-to-speech')
const fs = require('fs')
const util = require('util')
// Creates a client
const client = new textToSpeech.TextToSpeechClient()
const convertTextToSpeech = async (
text,
voice_name = 'pt-BR-Standard-B',
voice_gender = 'MALE',
languageCode = 'pt-BR'
) => {
try {
const request = {
input: { text: text },
voice: {
languageCode: languageCode,
name: voice_name,
ssmlGender: voice_gender,
},
audioConfig: { audioEncoding: 'MP3' },
}
// console.log('REQUEST: ', request)
// Performs the text-to-speech request
const [response] = await client.synthesizeSpeech(request)
return response.audioContent
// Write the binary audio content to a local file
// const writeFile = util.promisify(fs.writeFile)
// await writeFile('output.mp3', response.audioContent, 'binary')
// console.log('Audio content written to file: output.mp3')
} catch (error) {
console.log(
'There was an error on textToSpeech.ts file. Error in convertTextToSpeech function: ',
error
)
}
}
module.exports = convertTextToSpeech