feat: first commit
commit
b96b05ad83
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"extends": ["@commitlint/config-conventional"]
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
/node_modules
|
||||||
|
.env
|
||||||
|
/public/*
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
. "$(dirname -- "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
npx commitlint --edit
|
|
@ -0,0 +1,59 @@
|
||||||
|
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')
|
||||||
|
|
||||||
|
// Swagger
|
||||||
|
const swaggerUI = require('swagger-ui-express')
|
||||||
|
const YAML = require('yamljs')
|
||||||
|
const swaggerDocument = YAML.load('./swagger.yaml')
|
||||||
|
|
||||||
|
const helmet = require('helmet')
|
||||||
|
const xss = require('xss-clean')
|
||||||
|
const cors = require('cors')
|
||||||
|
|
||||||
|
// routers
|
||||||
|
const apiPricingRouter = require('./routes/apiPriceRoute')
|
||||||
|
|
||||||
|
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(fileUpload())
|
||||||
|
|
||||||
|
app.get('/', (req, res) => {
|
||||||
|
res.send('<h1>Billing API</h1><a href="/api-docs">Documentation</a>')
|
||||||
|
})
|
||||||
|
|
||||||
|
app.use('/api-docs', swaggerUI.serve, swaggerUI.setup(swaggerDocument))
|
||||||
|
|
||||||
|
app.use('/api/v1/billing', apiPricingRouter)
|
||||||
|
|
||||||
|
app.use(notFoundMiddlware)
|
||||||
|
// app.use(errorHandlerMiddleware)
|
||||||
|
|
||||||
|
const port = process.env.PORT || 3000
|
||||||
|
|
||||||
|
app.listen(port, console.log(`Listening on port: ${port}...`))
|
|
@ -0,0 +1,128 @@
|
||||||
|
const { StatusCodes } = require("http-status-codes")
|
||||||
|
const API_Pricing = require("../models/API_Pricing.js")
|
||||||
|
const {
|
||||||
|
mustContainProperties,
|
||||||
|
calculateApiUsage,
|
||||||
|
} = require('../utils')
|
||||||
|
const API_Usage = require("../models/API_Usage.js")
|
||||||
|
const billingSumUsage = require("../utils/billingSumUsage.js")
|
||||||
|
const moment = require('moment')
|
||||||
|
|
||||||
|
const setApiPricing = async (req, res) => {
|
||||||
|
|
||||||
|
const { provider, product, currency, price, billingBy, billingUnit, type } = req.body
|
||||||
|
|
||||||
|
mustContainProperties(req, ['provider',
|
||||||
|
'product',
|
||||||
|
'price',
|
||||||
|
'billingBy',
|
||||||
|
'billingUnit'])
|
||||||
|
|
||||||
|
const apiPricing = await API_Pricing.create({
|
||||||
|
provider: provider.trim().toLowerCase(),
|
||||||
|
product: product.trim().toLowerCase(),
|
||||||
|
currency,
|
||||||
|
price,
|
||||||
|
billingBy,
|
||||||
|
billingUnit,
|
||||||
|
type
|
||||||
|
})
|
||||||
|
|
||||||
|
res.status(StatusCodes.OK).json({ apiPricing })
|
||||||
|
}
|
||||||
|
|
||||||
|
const registerUsage = async (req, res) => {
|
||||||
|
const {
|
||||||
|
provider,
|
||||||
|
product,
|
||||||
|
usage,
|
||||||
|
callerId,
|
||||||
|
companyId,
|
||||||
|
quantityOfOperationAttempts,
|
||||||
|
chosenOperation,
|
||||||
|
requestLogsOpenAI,
|
||||||
|
responseErrorLogsOpenAI,
|
||||||
|
quantityOfCallsToFalconFlowAPI,
|
||||||
|
requestLogsFalconFlowAPI,
|
||||||
|
responseErrorLogsFalconFlowAPI,
|
||||||
|
} = req.body
|
||||||
|
|
||||||
|
mustContainProperties(req, [
|
||||||
|
'companyId',
|
||||||
|
'callerId',
|
||||||
|
'quantityOfOperationAttempts',
|
||||||
|
'chosenOperation',
|
||||||
|
'requestLogsOpenAI',
|
||||||
|
// 'responseErrorLogsOpenAI',
|
||||||
|
'quantityOfCallsToFalconFlowAPI',
|
||||||
|
'requestLogsFalconFlowAPI',
|
||||||
|
// 'responseErrorLogsFalconFlowAPI',
|
||||||
|
'provider',
|
||||||
|
'product',
|
||||||
|
'usage',
|
||||||
|
])
|
||||||
|
|
||||||
|
const apiPricing = await API_Pricing.findOne({
|
||||||
|
provider: provider.trim().toLowerCase(),
|
||||||
|
product: product.trim().toLowerCase(),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (apiPricing) {
|
||||||
|
|
||||||
|
const { price, billingBy, billingUnit } = apiPricing
|
||||||
|
|
||||||
|
const apiUsage = await API_Usage.create({
|
||||||
|
provider: provider.trim().toLowerCase(),
|
||||||
|
product: product.trim().toLowerCase(),
|
||||||
|
callerId,
|
||||||
|
quantityOfOperationAttempts,
|
||||||
|
chosenOperation,
|
||||||
|
requestLogsOpenAI,
|
||||||
|
responseErrorLogsOpenAI,
|
||||||
|
quantityOfCallsToFalconFlowAPI,
|
||||||
|
requestLogsFalconFlowAPI,
|
||||||
|
responseErrorLogsFalconFlowAPI,
|
||||||
|
usage,
|
||||||
|
price,
|
||||||
|
billingBy,
|
||||||
|
billingUnit,
|
||||||
|
companyId,
|
||||||
|
total_cost: calculateApiUsage(price, billingUnit, usage, billingBy)
|
||||||
|
})
|
||||||
|
|
||||||
|
return res.status(StatusCodes.OK).json({ apiUsage })
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(StatusCodes.NOT_FOUND).json({ msg: `Price not found for ${product} in the API Pricing table` })
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const getUsage = async (req, res) => {
|
||||||
|
|
||||||
|
const { startDate, endDate, companyId, } = req.body
|
||||||
|
|
||||||
|
mustContainProperties(req, ['startDate', 'endDate', 'companyId'])
|
||||||
|
|
||||||
|
const total = await billingSumUsage(startDate, endDate, companyId)
|
||||||
|
|
||||||
|
if (total) {
|
||||||
|
const usage = await API_Usage.find({
|
||||||
|
createdAt: {
|
||||||
|
$gte: moment(startDate, 'YYYY-MM-DD').startOf('day').toDate(),
|
||||||
|
$lte: moment(endDate, 'YYYY-MM-DD').endOf('day').toDate()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return res.status(StatusCodes.OK).json({ usage, total })
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(StatusCodes.NOT_FOUND).json({})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
setApiPricing,
|
||||||
|
registerUsage,
|
||||||
|
getUsage
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
const mongoose = require('mongoose')
|
||||||
|
|
||||||
|
const connectDB = async () => {
|
||||||
|
await mongoose.connect(process.env.DB_MONGO_URL, { dbName: process.env.DB_MONGO_NAME })
|
||||||
|
}
|
||||||
|
|
||||||
|
connectDB().catch((err) => console.log(err))
|
||||||
|
module.exports = mongoose
|
|
@ -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;
|
|
@ -0,0 +1,7 @@
|
||||||
|
class CustomAPIError extends Error {
|
||||||
|
constructor(message) {
|
||||||
|
super(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = CustomAPIError
|
|
@ -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,
|
||||||
|
};
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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,
|
||||||
|
}
|
|
@ -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;
|
|
@ -0,0 +1,3 @@
|
||||||
|
const notFound = (req, res) => res.status(404).send('Route does not exist')
|
||||||
|
|
||||||
|
module.exports = notFound
|
|
@ -0,0 +1,59 @@
|
||||||
|
[
|
||||||
|
{ "languageCode": "af-ZA" },
|
||||||
|
{ "languageCode": "ar-XA" },
|
||||||
|
{ "languageCode": "bg-BG" },
|
||||||
|
{ "languageCode": "bn-IN" },
|
||||||
|
{ "languageCode": "ca-ES" },
|
||||||
|
{ "languageCode": "cmn-CN" },
|
||||||
|
{ "languageCode": "cmn-TW" },
|
||||||
|
{ "languageCode": "cs-CZ" },
|
||||||
|
{ "languageCode": "da-DK" },
|
||||||
|
{ "languageCode": "de-DE" },
|
||||||
|
{ "languageCode": "el-GR" },
|
||||||
|
{ "languageCode": "en-AU" },
|
||||||
|
{ "languageCode": "en-GB" },
|
||||||
|
{ "languageCode": "en-IN" },
|
||||||
|
{ "languageCode": "en-US" },
|
||||||
|
{ "languageCode": "es-ES" },
|
||||||
|
{ "languageCode": "es-US" },
|
||||||
|
{ "languageCode": "eu-ES" },
|
||||||
|
{ "languageCode": "fi-FI" },
|
||||||
|
{ "languageCode": "fil-PH" },
|
||||||
|
{ "languageCode": "fr-CA" },
|
||||||
|
{ "languageCode": "fr-FR" },
|
||||||
|
{ "languageCode": "gl-ES" },
|
||||||
|
{ "languageCode": "gu-IN" },
|
||||||
|
{ "languageCode": "he-IL" },
|
||||||
|
{ "languageCode": "hi-IN" },
|
||||||
|
{ "languageCode": "hu-HU" },
|
||||||
|
{ "languageCode": "id-ID" },
|
||||||
|
{ "languageCode": "is-IS" },
|
||||||
|
{ "languageCode": "it-IT" },
|
||||||
|
{ "languageCode": "ja-JP" },
|
||||||
|
{ "languageCode": "kn-IN" },
|
||||||
|
{ "languageCode": "ko-KR" },
|
||||||
|
{ "languageCode": "lt-LT" },
|
||||||
|
{ "languageCode": "lv-LV" },
|
||||||
|
{ "languageCode": "ml-IN" },
|
||||||
|
{ "languageCode": "mr-IN" },
|
||||||
|
{ "languageCode": "ms-MY" },
|
||||||
|
{ "languageCode": "nb-NO" },
|
||||||
|
{ "languageCode": "nl-BE" },
|
||||||
|
{ "languageCode": "nl-NL" },
|
||||||
|
{ "languageCode": "pa-IN" },
|
||||||
|
{ "languageCode": "pl-PL" },
|
||||||
|
{ "languageCode": "pt-BR" },
|
||||||
|
{ "languageCode": "pt-PT" },
|
||||||
|
{ "languageCode": "ro-RO" },
|
||||||
|
{ "languageCode": "ru-RU" },
|
||||||
|
{ "languageCode": "sk-SK" },
|
||||||
|
{ "languageCode": "sr-RS" },
|
||||||
|
{ "languageCode": "sv-SE" },
|
||||||
|
{ "languageCode": "ta-IN" },
|
||||||
|
{ "languageCode": "te-IN" },
|
||||||
|
{ "languageCode": "th-TH" },
|
||||||
|
{ "languageCode": "tr-TR" },
|
||||||
|
{ "languageCode": "uk-UA" },
|
||||||
|
{ "languageCode": "vi-VN" },
|
||||||
|
{ "languageCode": "yue-HK" }
|
||||||
|
]
|
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,41 @@
|
||||||
|
const mongoose = require('../db/connect')
|
||||||
|
|
||||||
|
const { Schema } = mongoose
|
||||||
|
|
||||||
|
const apiPricing = new Schema({
|
||||||
|
provider: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
currency: {
|
||||||
|
type: String,
|
||||||
|
enum: ['dollar', 'real',],
|
||||||
|
default: 'dollar'
|
||||||
|
},
|
||||||
|
price: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
billingBy:{
|
||||||
|
type: String,
|
||||||
|
enum: ['minute', 'character', 'token'],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
billingUnit:{
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
enum: ['input', 'output',],
|
||||||
|
}
|
||||||
|
|
||||||
|
}, { timestamps: true })
|
||||||
|
|
||||||
|
const API_Pricing = mongoose.model('API_Pricing', apiPricing)
|
||||||
|
|
||||||
|
module.exports = API_Pricing
|
|
@ -0,0 +1,74 @@
|
||||||
|
const mongoose = require('../db/connect')
|
||||||
|
|
||||||
|
const { Schema } = mongoose
|
||||||
|
|
||||||
|
const apiUsage = new Schema({
|
||||||
|
companyId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
callerId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
quantityOfOperationAttempts: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
chosenOperation: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
requestLogsOpenAI: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
responseErrorLogsOpenAI: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
quantityOfCallsToFalconFlowAPI: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
requestLogsFalconFlowAPI: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
responseErrorLogsFalconFlowAPI: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
provider: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
usage: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
price: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
billingBy: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
billingUnit: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
total_cost: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
|
||||||
|
}, { timestamps: true })
|
||||||
|
|
||||||
|
const API_Usage = mongoose.model('API_Usage', apiUsage)
|
||||||
|
|
||||||
|
module.exports = API_Usage
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,52 @@
|
||||||
|
{
|
||||||
|
"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/speech": "^6.0.2",
|
||||||
|
"@google-cloud/storage": "^7.4.0",
|
||||||
|
"@google-cloud/text-to-speech": "^5.0.1",
|
||||||
|
"axios": "^1.7.2",
|
||||||
|
"bcryptjs": "^2.4.3",
|
||||||
|
"bull": "^4.15.1",
|
||||||
|
"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",
|
||||||
|
"fluent-ffmpeg": "^2.1.2",
|
||||||
|
"google-gax": "^4.0.5",
|
||||||
|
"google-proto-files": "^4.0.0",
|
||||||
|
"google-protobuf": "^3.21.2",
|
||||||
|
"helmet": "^4.6.0",
|
||||||
|
"http-status-codes": "^2.3.0",
|
||||||
|
"joi": "^17.4.0",
|
||||||
|
"moment": "^2.30.1",
|
||||||
|
"mongoose": "^7.8.0",
|
||||||
|
"morgan": "^1.10.0",
|
||||||
|
"multer": "^1.4.5-lts.1",
|
||||||
|
"openai": "^4.52.7",
|
||||||
|
"protobufjs": "^7.2.5",
|
||||||
|
"swagger-ui-express": "^4.1.6",
|
||||||
|
"validator": "^13.6.0",
|
||||||
|
"xss-clean": "^0.1.1",
|
||||||
|
"yamljs": "^0.3.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@commitlint/cli": "^17.7.2",
|
||||||
|
"@commitlint/config-conventional": "^17.7.0",
|
||||||
|
"husky": "^8.0.3",
|
||||||
|
"nodemon": "^2.0.9"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
const express = require('express')
|
||||||
|
const router = express.Router()
|
||||||
|
const { authorization, } = require('../middleware/authentication')
|
||||||
|
const { setApiPricing, registerUsage, getUsage} = require('../controllers/apiUsagePricing')
|
||||||
|
|
||||||
|
router.route('/create').post(authorization, setApiPricing)
|
||||||
|
router.route('/usage').post(authorization, registerUsage)
|
||||||
|
router.route('/report').post(authorization, getUsage)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = router
|
|
@ -0,0 +1,99 @@
|
||||||
|
openapi: 3.0.0
|
||||||
|
info:
|
||||||
|
title: Billing API
|
||||||
|
description: This API describes the endpoints to register the use of external APIs.
|
||||||
|
contact: {}
|
||||||
|
version: '1.0'
|
||||||
|
servers:
|
||||||
|
- url: http://localhost:6001/api/v1/billing
|
||||||
|
variables: {}
|
||||||
|
paths:
|
||||||
|
/usage:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- APIUsage
|
||||||
|
summary: Set usage
|
||||||
|
operationId: setUsage
|
||||||
|
parameters: []
|
||||||
|
requestBody:
|
||||||
|
description: ''
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
companyId:
|
||||||
|
type: string
|
||||||
|
description: Company identifier
|
||||||
|
example: "1"
|
||||||
|
callerId:
|
||||||
|
type: string
|
||||||
|
description: Identifier of the person who made the call
|
||||||
|
example: "17988310949"
|
||||||
|
quantityOfOperationAttempts:
|
||||||
|
type: string
|
||||||
|
description: Number of attempts to perform the operation made by the person who made the call
|
||||||
|
example: "2"
|
||||||
|
chosenOperation:
|
||||||
|
type: string
|
||||||
|
description: Operation chosen by the person who made the call
|
||||||
|
example: "unblokUser"
|
||||||
|
requestLogsOpenAI:
|
||||||
|
type: string
|
||||||
|
description: Response logs of requests made to openai
|
||||||
|
example: "{}"
|
||||||
|
responseErrorLogsOpenAI:
|
||||||
|
type: string
|
||||||
|
description: Openai error request response logs
|
||||||
|
example: "{}"
|
||||||
|
quantityOfCallsToFalconFlowAPI:
|
||||||
|
type: string
|
||||||
|
description: Number of requests made to third-party api
|
||||||
|
example: "2"
|
||||||
|
requestLogsFalconFlowAPI:
|
||||||
|
type: string
|
||||||
|
description: Response logs of requests made to third-party api
|
||||||
|
example: "{}"
|
||||||
|
responseErrorLogsFalconFlowAPI:
|
||||||
|
type: string
|
||||||
|
description: Third-party api error request response logs
|
||||||
|
example: "{}"
|
||||||
|
provider:
|
||||||
|
type: string
|
||||||
|
description: Identifier of the organization providing the AI solution
|
||||||
|
example: "openai"
|
||||||
|
product:
|
||||||
|
type: string
|
||||||
|
description: Product provided by the organization that is providing AI solution
|
||||||
|
example: "whisper"
|
||||||
|
usage:
|
||||||
|
type: integer
|
||||||
|
description: "Using the API. The product Whisper should be sent in seconds"
|
||||||
|
example: 15
|
||||||
|
required:
|
||||||
|
- companyId
|
||||||
|
- callerId
|
||||||
|
- quantityOfOperationAttempts
|
||||||
|
- chosenOperation
|
||||||
|
- requestLogsOpenAI
|
||||||
|
- quantityOfCallsToFalconFlowAPI
|
||||||
|
- requestLogsFalconFlowAPI
|
||||||
|
- provider
|
||||||
|
- product
|
||||||
|
- usage
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: ''
|
||||||
|
headers: {}
|
||||||
|
deprecated: false
|
||||||
|
security:
|
||||||
|
- bearer: []
|
||||||
|
components:
|
||||||
|
securitySchemes:
|
||||||
|
bearer:
|
||||||
|
type: http
|
||||||
|
scheme: bearer
|
||||||
|
security: []
|
||||||
|
tags:
|
||||||
|
- name: APIUsage
|
|
@ -0,0 +1,216 @@
|
||||||
|
openapi: 3.0.0
|
||||||
|
info:
|
||||||
|
title: Natural Language API Google
|
||||||
|
description: This API describes the endpoints and parameters to use resources from Google Cloud API.
|
||||||
|
contact: {}
|
||||||
|
version: '1.0'
|
||||||
|
servers:
|
||||||
|
- url: http://localhost:6001/api/v1/nl
|
||||||
|
variables: {}
|
||||||
|
paths:
|
||||||
|
/upload-audio-to-transcript:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- Speech to text async
|
||||||
|
summary: Speech to text job
|
||||||
|
operationId: Speechtotextjob
|
||||||
|
parameters: []
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
multipart/form-data:
|
||||||
|
encoding: {}
|
||||||
|
schema:
|
||||||
|
required:
|
||||||
|
- audio
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
audio:
|
||||||
|
type: string
|
||||||
|
format: binary
|
||||||
|
languageCode:
|
||||||
|
type: string
|
||||||
|
description: 'If not provided, the default will be: pt-BR'
|
||||||
|
example: pt-BR
|
||||||
|
required: false
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: ''
|
||||||
|
headers: {}
|
||||||
|
deprecated: false
|
||||||
|
security:
|
||||||
|
- bearer: []
|
||||||
|
/query-job-status:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Speech to text async
|
||||||
|
summary: Speech to text job process
|
||||||
|
operationId: Speechtotextjobprocess
|
||||||
|
parameters:
|
||||||
|
- name: operationName
|
||||||
|
in: query
|
||||||
|
description: 'The job id returned after uploading the audio file that will be transcribed.'
|
||||||
|
required: true
|
||||||
|
style: form
|
||||||
|
explode: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
example: 2993135803178989324
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: ''
|
||||||
|
headers: {}
|
||||||
|
deprecated: false
|
||||||
|
security:
|
||||||
|
- bearer: []
|
||||||
|
/speech-to-text:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- Speech to text sync
|
||||||
|
summary: Speech to text
|
||||||
|
operationId: Speechtotext
|
||||||
|
parameters: []
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
multipart/form-data:
|
||||||
|
encoding: {}
|
||||||
|
schema:
|
||||||
|
required:
|
||||||
|
- audio
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
audio:
|
||||||
|
type: string
|
||||||
|
format: binary
|
||||||
|
languageCode:
|
||||||
|
description: 'If not provided, the default will be: pt-BR'
|
||||||
|
type: string
|
||||||
|
example: pt-BR
|
||||||
|
required: false
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: ''
|
||||||
|
headers: {}
|
||||||
|
deprecated: false
|
||||||
|
security:
|
||||||
|
- bearer: []
|
||||||
|
/sentiment:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- Sentiment
|
||||||
|
summary: Get sentiment
|
||||||
|
operationId: Getsentiment
|
||||||
|
parameters: []
|
||||||
|
requestBody:
|
||||||
|
description: ''
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/GetsentimentRequest'
|
||||||
|
- example:
|
||||||
|
text: Toda vez a mesma coisa ja to cansado de ficar ligando pra resolver esses problemas de conexão!
|
||||||
|
example:
|
||||||
|
text: Toda vez a mesma coisa ja to cansado de ficar ligando pra resolver esses problemas de conexão!
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: ''
|
||||||
|
headers: {}
|
||||||
|
deprecated: false
|
||||||
|
security:
|
||||||
|
- bearer: []
|
||||||
|
/text-to-speech:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Text to speech
|
||||||
|
summary: Text to speech
|
||||||
|
operationId: Texttospeech
|
||||||
|
parameters:
|
||||||
|
- name: text
|
||||||
|
in: query
|
||||||
|
description: ''
|
||||||
|
required: true
|
||||||
|
style: form
|
||||||
|
explode: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: Vela branca na enxurrada la vou eu de léo em léo, se o navio é pequeno do tamanho de um chapeu, não importa a volta ao mundo, é viagem de brinquedo em um barquinho de papel.
|
||||||
|
- name: voice_name
|
||||||
|
in: query
|
||||||
|
description: ''
|
||||||
|
required: false
|
||||||
|
style: form
|
||||||
|
explode: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: pt-BR-Wavenet-C
|
||||||
|
- name: voice_gender
|
||||||
|
in: query
|
||||||
|
description: ''
|
||||||
|
required: false
|
||||||
|
style: form
|
||||||
|
explode: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: FEMALE
|
||||||
|
- name: languageCode
|
||||||
|
in: query
|
||||||
|
description: ''
|
||||||
|
required: false
|
||||||
|
style: form
|
||||||
|
explode: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: pt-BR
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: ''
|
||||||
|
headers: {}
|
||||||
|
deprecated: false
|
||||||
|
security:
|
||||||
|
- bearer: []
|
||||||
|
/voice-config:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Text to speech
|
||||||
|
summary: Get voice config
|
||||||
|
operationId: Getvoiceconfig
|
||||||
|
parameters:
|
||||||
|
- name: languageCode
|
||||||
|
in: query
|
||||||
|
description: ''
|
||||||
|
required: false
|
||||||
|
style: form
|
||||||
|
explode: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: pt-Br
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: ''
|
||||||
|
headers: {}
|
||||||
|
deprecated: false
|
||||||
|
security: []
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
GetsentimentRequest:
|
||||||
|
title: GetsentimentRequest
|
||||||
|
required:
|
||||||
|
- text
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
text:
|
||||||
|
type: string
|
||||||
|
example:
|
||||||
|
text: Toda vez a mesma coisa ja to cansado de ficar ligando pra resolver esses problemas de conexão!
|
||||||
|
securitySchemes:
|
||||||
|
bearer:
|
||||||
|
type: http
|
||||||
|
scheme: bearer
|
||||||
|
security: []
|
||||||
|
tags:
|
||||||
|
- name: Speech to text async
|
||||||
|
- name: Speech to text sync
|
||||||
|
- name: Sentiment
|
||||||
|
- name: Text to speech
|
|
@ -0,0 +1,25 @@
|
||||||
|
const multer = require('multer')
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
//Destination to store the file
|
||||||
|
const audioStorage = multer.diskStorage({
|
||||||
|
destination: function (req, file, cb) {
|
||||||
|
cb(null, `public/uploads`)
|
||||||
|
},
|
||||||
|
filename: function (req, file, cb) {
|
||||||
|
cb(null, Date.now() + String(Math.floor(Math.random() * 1000)) + path.extname(file.originalname))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const audioUpload = multer({
|
||||||
|
storage: audioStorage,
|
||||||
|
fileFilter(req, file, cb) {
|
||||||
|
if (!file.originalname.match(/\.(mp3|wav|ogg|flac|aac|wma|m4a|mp4|webm|opus|mpeg)$/i)) {
|
||||||
|
return cb(new Error('Invalid file type. Send only an audio file!'))
|
||||||
|
}
|
||||||
|
cb(undefined, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = audioUpload
|
|
@ -0,0 +1,44 @@
|
||||||
|
const moment = require('moment')
|
||||||
|
const API_Usage = require("../models/API_Usage.js")
|
||||||
|
|
||||||
|
async function billingSumUsage(startDate, endDate, companyId) {
|
||||||
|
try {
|
||||||
|
const start = moment(startDate, 'YYYY-MM-DD').startOf('day').toDate()
|
||||||
|
const end = moment(endDate, 'YYYY-MM-DD').endOf('day').toDate()
|
||||||
|
|
||||||
|
const result = await API_Usage.aggregate([
|
||||||
|
{
|
||||||
|
$match: {
|
||||||
|
createdAt: {
|
||||||
|
$gte: start,
|
||||||
|
$lte: end
|
||||||
|
},
|
||||||
|
companyId
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$group: {
|
||||||
|
_id: null,
|
||||||
|
total: {
|
||||||
|
$sum: {
|
||||||
|
$toDouble: "$price"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
if (result.length > 0) {
|
||||||
|
return result[0].total
|
||||||
|
} else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error calculating sum of prices:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = billingSumUsage
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
|
||||||
|
|
||||||
|
function calculateApiUsage(price, billingUnit, usage, billingBy) {
|
||||||
|
let _billingUnit = +billingUnit
|
||||||
|
|
||||||
|
if (billingBy == 'minute')
|
||||||
|
_billingUnit = 60 * _billingUnit
|
||||||
|
|
||||||
|
const total_cost = (+usage / _billingUnit) * parseFloat(price)
|
||||||
|
|
||||||
|
return total_cost.toFixed(10)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = calculateApiUsage
|
|
@ -0,0 +1,9 @@
|
||||||
|
const audioUpload = require('./audioUpload')
|
||||||
|
const mustContainProperties = require('./mustContainProperties')
|
||||||
|
const calculateApiUsage = require('./calculateApiUsage')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
audioUpload,
|
||||||
|
mustContainProperties,
|
||||||
|
calculateApiUsage
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
const CustomError = require('../errors')
|
||||||
|
|
||||||
|
function mustContainProperties(req, requiredProperties) {
|
||||||
|
|
||||||
|
const missingProperties = requiredProperties.filter(prop => !req.body[prop])
|
||||||
|
|
||||||
|
if (missingProperties.length > 0) {
|
||||||
|
throw new CustomError.BadRequestError(`Missing properties: ${missingProperties.join(', ')}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = mustContainProperties
|
Loading…
Reference in New Issue