feat: first commit

main
adriano 2024-07-30 08:26:42 -03:00
commit b96b05ad83
31 changed files with 11538 additions and 0 deletions

View File

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

3
.gitignore vendored 100644
View File

@ -0,0 +1,3 @@
/node_modules
.env
/public/*

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

59
app.js 100644
View File

@ -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}...`))

View File

@ -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
}

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
}

8
db/connect.js 100644
View File

@ -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

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,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" }
]

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"
}
]

View File

@ -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

View File

@ -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

10418
package-lock.json generated 100644

File diff suppressed because it is too large Load Diff

52
package.json 100644
View File

@ -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"
}
}

View File

@ -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

99
swagger.yaml 100644
View File

@ -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

216
swagger_dev.yaml 100644
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

9
utils/index.js 100644
View File

@ -0,0 +1,9 @@
const audioUpload = require('./audioUpload')
const mustContainProperties = require('./mustContainProperties')
const calculateApiUsage = require('./calculateApiUsage')
module.exports = {
audioUpload,
mustContainProperties,
calculateApiUsage
}

View File

@ -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