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