mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 15:00:57 +03:00
Feature: interactive swagger-ui auto-generated API docs from express (#1812)
* Add interactive swagger-ui auto-generated API docs from express * Update README.md * Update index.ts //@ts-ignore * Fix eslint no-console error * Add swagger paths * Add all end points * Update swagger.yml * update swagger yml file * update swagger config --------- Co-authored-by: Henry <hzj94@hotmail.com>
This commit is contained in:
committed by
GitHub
parent
0f58d31493
commit
e8f5f07735
@@ -18,7 +18,7 @@ const createChatMessage = async (req: Request, res: Response, next: NextFunction
|
||||
)
|
||||
}
|
||||
const apiResponse = await chatMessagesService.createChatMessage(req.body)
|
||||
return res.json(apiResponse)
|
||||
return res.json(parseAPIResponse(apiResponse))
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
@@ -88,7 +88,8 @@ const getAllChatMessages = async (req: Request, res: Response, next: NextFunctio
|
||||
feedback,
|
||||
feedbackTypeFilters
|
||||
)
|
||||
return res.json(apiResponse)
|
||||
|
||||
return res.json(parseAPIResponse(apiResponse))
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
@@ -116,7 +117,7 @@ const getAllInternalChatMessages = async (req: Request, res: Response, next: Nex
|
||||
messageId,
|
||||
feedback
|
||||
)
|
||||
return res.json(apiResponse)
|
||||
return res.json(parseAPIResponse(apiResponse))
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
@@ -186,6 +187,39 @@ const abortChatMessage = async (req: Request, res: Response, next: NextFunction)
|
||||
}
|
||||
}
|
||||
|
||||
const parseAPIResponse = (apiResponse: ChatMessage | ChatMessage[]): ChatMessage | ChatMessage[] => {
|
||||
const parseResponse = (response: ChatMessage): ChatMessage => {
|
||||
const parsedResponse = { ...response }
|
||||
|
||||
if (parsedResponse.sourceDocuments) {
|
||||
parsedResponse.sourceDocuments = JSON.parse(parsedResponse.sourceDocuments)
|
||||
}
|
||||
if (parsedResponse.usedTools) {
|
||||
parsedResponse.usedTools = JSON.parse(parsedResponse.usedTools)
|
||||
}
|
||||
if (parsedResponse.fileAnnotations) {
|
||||
parsedResponse.fileAnnotations = JSON.parse(parsedResponse.fileAnnotations)
|
||||
}
|
||||
if (parsedResponse.agentReasoning) {
|
||||
parsedResponse.agentReasoning = JSON.parse(parsedResponse.agentReasoning)
|
||||
}
|
||||
if (parsedResponse.fileUploads) {
|
||||
parsedResponse.fileUploads = JSON.parse(parsedResponse.fileUploads)
|
||||
}
|
||||
if (parsedResponse.action) {
|
||||
parsedResponse.action = JSON.parse(parsedResponse.action)
|
||||
}
|
||||
|
||||
return parsedResponse
|
||||
}
|
||||
|
||||
if (Array.isArray(apiResponse)) {
|
||||
return apiResponse.map(parseResponse)
|
||||
} else {
|
||||
return parseResponse(apiResponse)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
createChatMessage,
|
||||
getAllChatMessages,
|
||||
|
||||
@@ -20,6 +20,7 @@ import { sanitizeMiddleware, getCorsOptions, getAllowedIframeOrigins } from './u
|
||||
import { Telemetry } from './utils/telemetry'
|
||||
import flowiseApiV1Router from './routes'
|
||||
import errorHandlerMiddleware from './middlewares/errors'
|
||||
import { validateAPIKey } from './utils/validateKey'
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
@@ -140,10 +141,38 @@ export class App {
|
||||
'/api/v1/ip',
|
||||
'/api/v1/ping'
|
||||
]
|
||||
this.app.use((req, res, next) => {
|
||||
this.app.use(async (req, res, next) => {
|
||||
if (/\/api\/v1\//i.test(req.url)) {
|
||||
whitelistURLs.some((url) => new RegExp(url, 'i').test(req.url)) ? next() : basicAuthMiddleware(req, res, next)
|
||||
} else next()
|
||||
if (whitelistURLs.some((url) => new RegExp(url, 'i').test(req.url))) {
|
||||
next()
|
||||
} else if (req.headers['x-request-from'] === 'internal') {
|
||||
basicAuthMiddleware(req, res, next)
|
||||
} else {
|
||||
const isKeyValidated = await validateAPIKey(req)
|
||||
if (!isKeyValidated) {
|
||||
return res.status(401).json({ error: 'Unauthorized Access' })
|
||||
}
|
||||
next()
|
||||
}
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.app.use(async (req, res, next) => {
|
||||
if (/\/api\/v1\//i.test(req.url)) {
|
||||
if (req.headers['x-request-from'] === 'internal') {
|
||||
next()
|
||||
} else {
|
||||
const isKeyValidated = await validateAPIKey(req)
|
||||
if (!isKeyValidated) {
|
||||
return res.status(401).json({ error: 'Unauthorized Access' })
|
||||
}
|
||||
next()
|
||||
}
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ const router = express.Router()
|
||||
// Create document store
|
||||
router.post('/store', documentStoreController.createDocumentStore)
|
||||
// List all stores
|
||||
router.get('/stores', documentStoreController.getAllDocumentStores)
|
||||
router.get('/store', documentStoreController.getAllDocumentStores)
|
||||
// Get specific store
|
||||
router.get('/store/:id', documentStoreController.getDocumentStoreById)
|
||||
// Update documentStore
|
||||
|
||||
@@ -48,14 +48,14 @@ const getAllApiKeys = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const getApiKey = async (keyName: string) => {
|
||||
const getApiKey = async (apiKey: string) => {
|
||||
try {
|
||||
if (_apikeysStoredInJson()) {
|
||||
return getApiKey_json(keyName)
|
||||
return getApiKey_json(apiKey)
|
||||
} else if (_apikeysStoredInDb()) {
|
||||
const appServer = getRunningExpressApp()
|
||||
const currentKey = await appServer.AppDataSource.getRepository(ApiKey).findOneBy({
|
||||
keyName: keyName
|
||||
apiKey: apiKey
|
||||
})
|
||||
if (!currentKey) {
|
||||
return undefined
|
||||
|
||||
@@ -7,8 +7,9 @@ import { Credential } from '../../database/entities/Credential'
|
||||
import { decryptCredentialData, getAppVersion } from '../../utils'
|
||||
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
||||
import { getErrorMessage } from '../../errors/utils'
|
||||
import { DeleteResult } from 'typeorm'
|
||||
|
||||
const createAssistant = async (requestBody: any): Promise<any> => {
|
||||
const createAssistant = async (requestBody: any): Promise<Assistant> => {
|
||||
try {
|
||||
const appServer = getRunningExpressApp()
|
||||
if (!requestBody.details) {
|
||||
@@ -119,7 +120,7 @@ const createAssistant = async (requestBody: any): Promise<any> => {
|
||||
}
|
||||
}
|
||||
|
||||
const deleteAssistant = async (assistantId: string, isDeleteBoth: any): Promise<any> => {
|
||||
const deleteAssistant = async (assistantId: string, isDeleteBoth: any): Promise<DeleteResult> => {
|
||||
try {
|
||||
const appServer = getRunningExpressApp()
|
||||
const assistant = await appServer.AppDataSource.getRepository(Assistant).findOneBy({
|
||||
@@ -150,11 +151,7 @@ const deleteAssistant = async (assistantId: string, isDeleteBoth: any): Promise<
|
||||
if (isDeleteBoth) await openai.beta.assistants.del(assistantDetails.id)
|
||||
return dbResponse
|
||||
} catch (error: any) {
|
||||
if (error.status === 404 && error.type === 'invalid_request_error') {
|
||||
return 'OK'
|
||||
} else {
|
||||
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Error deleting assistant - ${getErrorMessage(error)}`)
|
||||
}
|
||||
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Error deleting assistant - ${getErrorMessage(error)}`)
|
||||
}
|
||||
} catch (error) {
|
||||
throw new InternalFlowiseError(
|
||||
@@ -164,7 +161,7 @@ const deleteAssistant = async (assistantId: string, isDeleteBoth: any): Promise<
|
||||
}
|
||||
}
|
||||
|
||||
const getAllAssistants = async (): Promise<any> => {
|
||||
const getAllAssistants = async (): Promise<Assistant[]> => {
|
||||
try {
|
||||
const appServer = getRunningExpressApp()
|
||||
const dbResponse = await appServer.AppDataSource.getRepository(Assistant).find()
|
||||
@@ -177,7 +174,7 @@ const getAllAssistants = async (): Promise<any> => {
|
||||
}
|
||||
}
|
||||
|
||||
const getAssistantById = async (assistantId: string): Promise<any> => {
|
||||
const getAssistantById = async (assistantId: string): Promise<Assistant> => {
|
||||
try {
|
||||
const appServer = getRunningExpressApp()
|
||||
const dbResponse = await appServer.AppDataSource.getRepository(Assistant).findOneBy({
|
||||
@@ -195,7 +192,7 @@ const getAssistantById = async (assistantId: string): Promise<any> => {
|
||||
}
|
||||
}
|
||||
|
||||
const updateAssistant = async (assistantId: string, requestBody: any): Promise<any> => {
|
||||
const updateAssistant = async (assistantId: string, requestBody: any): Promise<Assistant> => {
|
||||
try {
|
||||
const appServer = getRunningExpressApp()
|
||||
const assistant = await appServer.AppDataSource.getRepository(Assistant).findOneBy({
|
||||
|
||||
@@ -580,9 +580,8 @@ const processAndSaveChunks = async (data: IDocumentStoreLoaderForPreview) => {
|
||||
`Error: documentStoreServices.processAndSaveChunks - Document store ${data.storeId} not found`
|
||||
)
|
||||
}
|
||||
|
||||
const newLoaderId = data.id ?? uuidv4()
|
||||
const existingLoaders = JSON.parse(entity.loaders)
|
||||
const newLoaderId = data.id ?? uuidv4()
|
||||
const found = existingLoaders.find((ldr: IDocumentStoreLoader) => ldr.id === newLoaderId)
|
||||
if (found) {
|
||||
// clean up the current status and mark the loader as pending_sync
|
||||
@@ -590,6 +589,15 @@ const processAndSaveChunks = async (data: IDocumentStoreLoaderForPreview) => {
|
||||
found.totalChars = 0
|
||||
found.status = DocumentStoreStatus.SYNCING
|
||||
entity.loaders = JSON.stringify(existingLoaders)
|
||||
data.loaderId = found.loaderId
|
||||
data.loaderName = found.loaderName
|
||||
data.loaderConfig = found.loaderConfig
|
||||
data.splitterId = found.splitterId
|
||||
data.splitterName = found.splitterName
|
||||
data.splitterConfig = found.splitterConfig
|
||||
if (found.credential) {
|
||||
data.credential = found.credential
|
||||
}
|
||||
} else {
|
||||
let loader: IDocumentStoreLoader = {
|
||||
id: newLoaderId,
|
||||
@@ -833,6 +841,9 @@ const saveVectorStoreConfig = async (data: ICommonObject) => {
|
||||
config: data.embeddingConfig,
|
||||
name: data.embeddingName
|
||||
})
|
||||
} else if (entity.embeddingConfig && !data.embeddingName && !data.embeddingConfig) {
|
||||
data.embeddingConfig = JSON.parse(entity.embeddingConfig)?.config
|
||||
data.embeddingName = JSON.parse(entity.embeddingConfig)?.name
|
||||
} else if (!data.embeddingName && !data.embeddingConfig) {
|
||||
entity.embeddingConfig = null
|
||||
}
|
||||
@@ -842,6 +853,9 @@ const saveVectorStoreConfig = async (data: ICommonObject) => {
|
||||
config: data.vectorStoreConfig,
|
||||
name: data.vectorStoreName
|
||||
})
|
||||
} else if (entity.vectorStoreConfig && !data.vectorStoreName && !data.vectorStoreConfig) {
|
||||
data.vectorStoreConfig = JSON.parse(entity.vectorStoreConfig)?.config
|
||||
data.vectorStoreName = JSON.parse(entity.vectorStoreConfig)?.name
|
||||
} else if (!data.vectorStoreName && !data.vectorStoreConfig) {
|
||||
entity.vectorStoreConfig = null
|
||||
}
|
||||
@@ -851,6 +865,9 @@ const saveVectorStoreConfig = async (data: ICommonObject) => {
|
||||
config: data.recordManagerConfig,
|
||||
name: data.recordManagerName
|
||||
})
|
||||
} else if (entity.recordManagerConfig && !data.recordManagerName && !data.recordManagerConfig) {
|
||||
data.recordManagerConfig = JSON.parse(entity.recordManagerConfig)?.config
|
||||
data.recordManagerName = JSON.parse(entity.recordManagerConfig)?.name
|
||||
} else if (!data.recordManagerName && !data.recordManagerConfig) {
|
||||
entity.recordManagerConfig = null
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ const getAllChatMessageFeedback = async (
|
||||
}
|
||||
}
|
||||
|
||||
// Add chatmessage feedback for chatflowid
|
||||
// Add chatmessage feedback
|
||||
const createChatMessageFeedbackForChatflow = async (requestBody: Partial<IChatMessageFeedback>): Promise<any> => {
|
||||
try {
|
||||
const dbResponse = await utilAddChatMessageFeedback(requestBody)
|
||||
@@ -38,10 +38,10 @@ const createChatMessageFeedbackForChatflow = async (requestBody: Partial<IChatMe
|
||||
}
|
||||
}
|
||||
|
||||
// Add chatmessage feedback for chatflowid
|
||||
const updateChatMessageFeedbackForChatflow = async (chatflowId: string, requestBody: Partial<IChatMessageFeedback>): Promise<any> => {
|
||||
// Add chatmessage feedback
|
||||
const updateChatMessageFeedbackForChatflow = async (feedbackId: string, requestBody: Partial<IChatMessageFeedback>): Promise<any> => {
|
||||
try {
|
||||
const dbResponse = await utilUpdateChatMessageFeedback(chatflowId, requestBody)
|
||||
const dbResponse = await utilUpdateChatMessageFeedback(feedbackId, requestBody)
|
||||
return dbResponse
|
||||
} catch (error) {
|
||||
throw new InternalFlowiseError(
|
||||
|
||||
@@ -34,7 +34,7 @@ import {
|
||||
getEndingNodes,
|
||||
constructGraphs
|
||||
} from '../utils'
|
||||
import { utilValidateKey } from './validateKey'
|
||||
import { validateChatflowAPIKey } from './validateKey'
|
||||
import { databaseEntities } from '.'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { omit } from 'lodash'
|
||||
@@ -73,7 +73,7 @@ export const utilBuildChatflow = async (req: Request, socketIO?: Server, isInter
|
||||
const userMessageDateTime = new Date()
|
||||
|
||||
if (!isInternal) {
|
||||
const isKeyValidated = await utilValidateKey(req, chatflow)
|
||||
const isKeyValidated = await validateChatflowAPIKey(req, chatflow)
|
||||
if (!isKeyValidated) {
|
||||
throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, `Unauthorized`)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
getTelemetryFlowObj,
|
||||
getStartingNodes
|
||||
} from '../utils'
|
||||
import { utilValidateKey } from './validateKey'
|
||||
import { validateChatflowAPIKey } from './validateKey'
|
||||
import { IncomingInput, INodeDirectedGraph, IReactFlowObject, chatType } from '../Interface'
|
||||
import { ChatFlow } from '../database/entities/ChatFlow'
|
||||
import { getRunningExpressApp } from '../utils/getRunningExpressApp'
|
||||
@@ -43,7 +43,7 @@ export const upsertVector = async (req: Request, isInternal: boolean = false) =>
|
||||
}
|
||||
|
||||
if (!isInternal) {
|
||||
const isKeyValidated = await utilValidateKey(req, chatflow)
|
||||
const isKeyValidated = await validateChatflowAPIKey(req, chatflow)
|
||||
if (!isKeyValidated) {
|
||||
throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, `Unauthorized`)
|
||||
}
|
||||
|
||||
@@ -2,14 +2,14 @@ import { Request } from 'express'
|
||||
import { ChatFlow } from '../database/entities/ChatFlow'
|
||||
import { compareKeys } from './apiKey'
|
||||
import apikeyService from '../services/apikey'
|
||||
|
||||
/**
|
||||
* Validate API Key
|
||||
* Validate Chatflow API Key
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {ChatFlow} chatflow
|
||||
*/
|
||||
export const utilValidateKey = async (req: Request, chatflow: ChatFlow) => {
|
||||
const chatFlowApiKeyId = chatflow.apikeyid
|
||||
export const validateChatflowAPIKey = async (req: Request, chatflow: ChatFlow) => {
|
||||
const chatFlowApiKeyId = chatflow?.apikeyid
|
||||
if (!chatFlowApiKeyId) return true
|
||||
|
||||
const authorizationHeader = (req.headers['Authorization'] as string) ?? (req.headers['authorization'] as string) ?? ''
|
||||
@@ -24,3 +24,22 @@ export const utilValidateKey = async (req: Request, chatflow: ChatFlow) => {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate API Key
|
||||
* @param {Request} req
|
||||
*/
|
||||
export const validateAPIKey = async (req: Request) => {
|
||||
const authorizationHeader = (req.headers['Authorization'] as string) ?? (req.headers['authorization'] as string) ?? ''
|
||||
if (!authorizationHeader) return false
|
||||
|
||||
const suppliedKey = authorizationHeader.split(`Bearer `).pop()
|
||||
if (suppliedKey) {
|
||||
const keys = await apikeyService.getAllApiKeys()
|
||||
const apiSecret = keys.find((key: any) => key.apiKey === suppliedKey)?.apiSecret
|
||||
if (!apiSecret) return false
|
||||
if (!compareKeys(apiSecret, suppliedKey)) return false
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user