Chore/refractor (#4454)

* markdown files and env examples cleanup

* components update

* update jsonlines description

* server refractor

* update telemetry

* add execute custom node

* add ui refractor

* add username and password authenticate

* correctly retrieve past images in agentflowv2

* disable e2e temporarily

* add existing username and password authenticate

* update migration to default workspace

* update todo

* blob storage migrating

* throw error on agent tool call error

* add missing execution import

* add referral

* chore: add error message when importData is undefined

* migrate api keys to db

* fix: data too long for column executionData

* migrate api keys from json to db at init

* add info on account setup

* update docstore missing fields

---------

Co-authored-by: chungyau97 <chungyau97@gmail.com>
This commit is contained in:
Henry Heng
2025-05-27 14:29:42 +08:00
committed by GitHub
parent e35a126b46
commit 5a37227d14
560 changed files with 62127 additions and 4100 deletions
-3
View File
@@ -1,7 +1,4 @@
export const appConfig = {
apiKeys: {
storageType: process.env.APIKEY_STORAGE_TYPE ? process.env.APIKEY_STORAGE_TYPE.toLowerCase() : 'json'
},
showCommunityNodes: process.env.SHOW_COMMUNITY_NODES ? process.env.SHOW_COMMUNITY_NODES.toLowerCase() === 'true' : false
// todo: add more config options here like database, log, storage, credential and allow modification from UI
}
+13 -2
View File
@@ -8,6 +8,7 @@ import { sqliteMigrations } from './database/migrations/sqlite'
import { mysqlMigrations } from './database/migrations/mysql'
import { mariadbMigrations } from './database/migrations/mariadb'
import { postgresMigrations } from './database/migrations/postgres'
import logger from './utils/logger'
let appDataSource: DataSource
@@ -73,7 +74,17 @@ export const init = async (): Promise<void> => {
synchronize: false,
migrationsRun: false,
entities: Object.values(entities),
migrations: postgresMigrations
migrations: postgresMigrations,
extra: {
idleTimeoutMillis: 120000
},
logging: ['error', 'warn', 'info', 'log'],
logger: 'advanced-console',
logNotifications: true,
poolErrorHandler: (err) => {
logger.error(`Database pool error: ${JSON.stringify(err)}`)
},
applicationName: 'Flowise'
})
break
default:
@@ -97,7 +108,7 @@ export function getDataSource(): DataSource {
return appDataSource
}
const getDatabaseSSLFromEnv = () => {
export const getDatabaseSSLFromEnv = () => {
if (process.env.DATABASE_SSL_KEY_BASE64) {
return {
rejectUnauthorized: false,
+524
View File
@@ -0,0 +1,524 @@
/**
* Copyright (c) 2023-present FlowiseAI, Inc.
*
* The Enterprise and Cloud versions of Flowise are licensed under the [Commercial License](https://github.com/FlowiseAI/Flowise/tree/main/packages/server/src/enterprise/LICENSE.md).
* Unauthorized copying, modification, distribution, or use of the Enterprise and Cloud versions is strictly prohibited without a valid license agreement from FlowiseAI, Inc.
*
* The Open Source version is licensed under the Apache License, Version 2.0 (the "License")
*
* For information about licensing of the Enterprise and Cloud versions, please contact:
* security@flowiseai.com
*/
import axios from 'axios'
import express, { Application, NextFunction, Request, Response } from 'express'
import * as fs from 'fs'
import { StatusCodes } from 'http-status-codes'
import jwt from 'jsonwebtoken'
import path from 'path'
import { LoginMethodStatus } from './enterprise/database/entities/login-method.entity'
import { ErrorMessage, LoggedInUser } from './enterprise/Interface.Enterprise'
import { Permissions } from './enterprise/rbac/Permissions'
import { LoginMethodService } from './enterprise/services/login-method.service'
import { OrganizationService } from './enterprise/services/organization.service'
import Auth0SSO from './enterprise/sso/Auth0SSO'
import AzureSSO from './enterprise/sso/AzureSSO'
import GithubSSO from './enterprise/sso/GithubSSO'
import GoogleSSO from './enterprise/sso/GoogleSSO'
import SSOBase from './enterprise/sso/SSOBase'
import { InternalFlowiseError } from './errors/internalFlowiseError'
import { Platform, UserPlan } from './Interface'
import { StripeManager } from './StripeManager'
import { UsageCacheManager } from './UsageCacheManager'
import { GeneralErrorMessage, LICENSE_QUOTAS } from './utils/constants'
import { getRunningExpressApp } from './utils/getRunningExpressApp'
import { ENTERPRISE_FEATURE_FLAGS } from './utils/quotaUsage'
import Stripe from 'stripe'
const allSSOProviders = ['azure', 'google', 'auth0', 'github']
export class IdentityManager {
private static instance: IdentityManager
private stripeManager?: StripeManager
licenseValid: boolean = false
permissions: Permissions
ssoProviderName: string = ''
currentInstancePlatform: Platform = Platform.OPEN_SOURCE
// create a map to store the sso provider name and the sso provider instance
ssoProviders: Map<string, SSOBase> = new Map()
public static async getInstance(): Promise<IdentityManager> {
if (!IdentityManager.instance) {
IdentityManager.instance = new IdentityManager()
await IdentityManager.instance.initialize()
}
return IdentityManager.instance
}
public async initialize() {
await this._validateLicenseKey()
this.permissions = new Permissions()
if (process.env.STRIPE_SECRET_KEY) {
this.stripeManager = await StripeManager.getInstance()
}
}
public getPlatformType = () => {
return this.currentInstancePlatform
}
public getPermissions = () => {
return this.permissions
}
public isEnterprise = () => {
return this.currentInstancePlatform === Platform.ENTERPRISE
}
public isCloud = () => {
return this.currentInstancePlatform === Platform.CLOUD
}
public isOpenSource = () => {
return this.currentInstancePlatform === Platform.OPEN_SOURCE
}
public isLicenseValid = () => {
return this.licenseValid
}
private _offlineVerifyLicense(licenseKey: string): any {
try {
const publicKey = fs.readFileSync(path.join(__dirname, '../', 'src/enterprise/license/public.pem'), 'utf8')
const decoded = jwt.verify(licenseKey, publicKey, {
algorithms: ['RS256']
})
return decoded
} catch (error) {
console.error('Error verifying license key:', error)
return null
}
}
private _validateLicenseKey = async () => {
const LICENSE_URL = process.env.LICENSE_URL
const FLOWISE_EE_LICENSE_KEY = process.env.FLOWISE_EE_LICENSE_KEY
// First check if license key is missing
if (!FLOWISE_EE_LICENSE_KEY) {
this.licenseValid = false
this.currentInstancePlatform = Platform.OPEN_SOURCE
return
}
try {
if (process.env.OFFLINE === 'true') {
const decodedLicense = this._offlineVerifyLicense(FLOWISE_EE_LICENSE_KEY)
if (!decodedLicense) {
this.licenseValid = false
} else {
const issuedAtSeconds = decodedLicense.iat
if (!issuedAtSeconds) {
this.licenseValid = false
} else {
const issuedAt = new Date(issuedAtSeconds * 1000)
const expiryDurationInMonths = decodedLicense.expiryDurationInMonths || 0
const expiryDate = new Date(issuedAt)
expiryDate.setMonth(expiryDate.getMonth() + expiryDurationInMonths)
if (new Date() > expiryDate) {
this.licenseValid = false
} else {
this.licenseValid = true
}
}
}
this.currentInstancePlatform = Platform.ENTERPRISE
} else if (LICENSE_URL) {
try {
const response = await axios.post(`${LICENSE_URL}/enterprise/verify`, { license: FLOWISE_EE_LICENSE_KEY })
this.licenseValid = response.data?.valid
if (!LICENSE_URL.includes('api')) this.currentInstancePlatform = Platform.ENTERPRISE
else if (LICENSE_URL.includes('v1')) this.currentInstancePlatform = Platform.ENTERPRISE
else if (LICENSE_URL.includes('v2')) this.currentInstancePlatform = response.data?.platform
else throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, GeneralErrorMessage.UNHANDLED_EDGE_CASE)
} catch (error) {
console.error('Error verifying license key:', error)
this.licenseValid = false
this.currentInstancePlatform = Platform.ENTERPRISE
return
}
}
} catch (error) {
this.licenseValid = false
}
}
public initializeSSO = async (app: express.Application) => {
if (this.getPlatformType() === Platform.CLOUD || this.getPlatformType() === Platform.ENTERPRISE) {
const loginMethodService = new LoginMethodService()
let queryRunner
try {
queryRunner = getRunningExpressApp().AppDataSource.createQueryRunner()
await queryRunner.connect()
let organizationId = undefined
if (this.getPlatformType() === Platform.ENTERPRISE) {
const organizationService = new OrganizationService()
const organizations = await organizationService.readOrganization(queryRunner)
if (organizations.length > 0) {
organizationId = organizations[0].id
} else {
this.initializeEmptySSO(app)
return
}
}
const loginMethods = await loginMethodService.readLoginMethodByOrganizationId(organizationId, queryRunner)
if (loginMethods && loginMethods.length > 0) {
for (let method of loginMethods) {
if (method.status === LoginMethodStatus.ENABLE) {
method.config = JSON.parse(await loginMethodService.decryptLoginMethodConfig(method.config))
this.initializeSsoProvider(app, method.name, method.config)
}
}
}
} finally {
if (queryRunner) await queryRunner.release()
}
}
// iterate through the remaining providers and initialize them with configEnabled as false
this.initializeEmptySSO(app)
}
initializeEmptySSO(app: Application) {
allSSOProviders.map((providerName) => {
if (!this.ssoProviders.has(providerName)) {
this.initializeSsoProvider(app, providerName, undefined)
}
})
}
initializeSsoProvider(app: Application, providerName: string, providerConfig: any) {
if (this.ssoProviders.has(providerName)) {
const provider = this.ssoProviders.get(providerName)
if (provider) {
if (providerConfig && providerConfig.configEnabled === true) {
provider.setSSOConfig(providerConfig)
provider.initialize()
} else {
// if false, disable the provider
provider.setSSOConfig(undefined)
}
}
} else {
switch (providerName) {
case 'azure': {
const azureSSO = new AzureSSO(app, providerConfig)
azureSSO.initialize()
this.ssoProviders.set(providerName, azureSSO)
break
}
case 'google': {
const googleSSO = new GoogleSSO(app, providerConfig)
googleSSO.initialize()
this.ssoProviders.set(providerName, googleSSO)
break
}
case 'auth0': {
const auth0SSO = new Auth0SSO(app, providerConfig)
auth0SSO.initialize()
this.ssoProviders.set(providerName, auth0SSO)
break
}
case 'github': {
const githubSSO = new GithubSSO(app, providerConfig)
githubSSO.initialize()
this.ssoProviders.set(providerName, githubSSO)
break
}
default:
throw new Error(`SSO Provider ${providerName} not found`)
}
}
}
async getRefreshToken(providerName: any, ssoRefreshToken: string) {
if (!this.ssoProviders.has(providerName)) {
throw new Error(`SSO Provider ${providerName} not found`)
}
return await (this.ssoProviders.get(providerName) as SSOBase).refreshToken(ssoRefreshToken)
}
public async getProductIdFromSubscription(subscriptionId: string) {
if (!subscriptionId) return ''
if (!this.stripeManager) {
throw new Error('Stripe manager is not initialized')
}
return await this.stripeManager.getProductIdFromSubscription(subscriptionId)
}
public async getFeaturesByPlan(subscriptionId: string, withoutCache: boolean = false) {
if (this.isEnterprise()) {
const features: Record<string, string> = {}
for (const feature of ENTERPRISE_FEATURE_FLAGS) {
features[feature] = 'true'
}
return features
} else if (this.isCloud()) {
if (!this.stripeManager || !subscriptionId) {
return {}
}
return await this.stripeManager.getFeaturesByPlan(subscriptionId, withoutCache)
}
return {}
}
public static checkFeatureByPlan(feature: string) {
return (req: Request, res: Response, next: NextFunction) => {
const user = req.user
if (user) {
if (!user.features || Object.keys(user.features).length === 0) {
return res.status(403).json({ message: ErrorMessage.FORBIDDEN })
}
if (Object.keys(user.features).includes(feature) && user.features[feature] === 'true') {
return next()
}
}
return res.status(403).json({ message: ErrorMessage.FORBIDDEN })
}
}
public async createStripeCustomerPortalSession(req: Request) {
if (!this.stripeManager) {
throw new Error('Stripe manager is not initialized')
}
return await this.stripeManager.createStripeCustomerPortalSession(req)
}
public async getAdditionalSeatsQuantity(subscriptionId: string) {
if (!subscriptionId) return {}
if (!this.stripeManager) {
throw new Error('Stripe manager is not initialized')
}
return await this.stripeManager.getAdditionalSeatsQuantity(subscriptionId)
}
public async getCustomerWithDefaultSource(customerId: string) {
if (!customerId) return
if (!this.stripeManager) {
throw new Error('Stripe manager is not initialized')
}
return await this.stripeManager.getCustomerWithDefaultSource(customerId)
}
public async getAdditionalSeatsProration(subscriptionId: string, newQuantity: number) {
if (!subscriptionId) return {}
if (!this.stripeManager) {
throw new Error('Stripe manager is not initialized')
}
return await this.stripeManager.getAdditionalSeatsProration(subscriptionId, newQuantity)
}
public async updateAdditionalSeats(subscriptionId: string, quantity: number, prorationDate: number) {
if (!subscriptionId) return {}
if (!this.stripeManager) {
throw new Error('Stripe manager is not initialized')
}
const { success, subscription, invoice } = await this.stripeManager.updateAdditionalSeats(subscriptionId, quantity, prorationDate)
// Fetch product details to get quotas
const items = subscription.items.data
if (items.length === 0) {
throw new Error('No subscription items found')
}
const productId = items[0].price.product as string
const product = await this.stripeManager.getStripe().products.retrieve(productId)
const productMetadata = product.metadata
// Extract quotas from metadata
const quotas: Record<string, number> = {}
for (const key in productMetadata) {
if (key.startsWith('quota:')) {
quotas[key] = parseInt(productMetadata[key])
}
}
quotas[LICENSE_QUOTAS.ADDITIONAL_SEATS_LIMIT] = quantity
// Get features from Stripe
const features = await this.getFeaturesByPlan(subscription.id, true)
// Update the cache with new subscription data including quotas
const cacheManager = await UsageCacheManager.getInstance()
await cacheManager.updateSubscriptionDataToCache(subscriptionId, {
features,
quotas,
subsriptionDetails: this.stripeManager.getSubscriptionObject(subscription)
})
return { success, subscription, invoice }
}
public async getPlanProration(subscriptionId: string, newPlanId: string) {
if (!subscriptionId || !newPlanId) return {}
if (!this.stripeManager) {
throw new Error('Stripe manager is not initialized')
}
return await this.stripeManager.getPlanProration(subscriptionId, newPlanId)
}
public async updateSubscriptionPlan(req: Request, subscriptionId: string, newPlanId: string, prorationDate: number) {
if (!subscriptionId || !newPlanId) return {}
if (!this.stripeManager) {
throw new Error('Stripe manager is not initialized')
}
if (!req.user) {
throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, GeneralErrorMessage.UNAUTHORIZED)
}
const { success, subscription } = await this.stripeManager.updateSubscriptionPlan(subscriptionId, newPlanId, prorationDate)
if (success) {
// Fetch product details to get quotas
const product = await this.stripeManager.getStripe().products.retrieve(newPlanId)
const productMetadata = product.metadata
// Extract quotas from metadata
const quotas: Record<string, number> = {}
for (const key in productMetadata) {
if (key.startsWith('quota:')) {
quotas[key] = parseInt(productMetadata[key])
}
}
const additionalSeatsItem = subscription.items.data.find(
(item) => (item.price.product as string) === process.env.ADDITIONAL_SEAT_ID
)
quotas[LICENSE_QUOTAS.ADDITIONAL_SEATS_LIMIT] = additionalSeatsItem?.quantity || 0
// Get features from Stripe
const features = await this.getFeaturesByPlan(subscription.id, true)
// Update the cache with new subscription data including quotas
const cacheManager = await UsageCacheManager.getInstance()
const updateCacheData: Record<string, any> = {
features,
quotas,
subsriptionDetails: this.stripeManager.getSubscriptionObject(subscription)
}
if (
newPlanId === process.env.CLOUD_FREE_ID ||
newPlanId === process.env.CLOUD_STARTER_ID ||
newPlanId === process.env.CLOUD_PRO_ID
) {
updateCacheData.productId = newPlanId
}
await cacheManager.updateSubscriptionDataToCache(subscriptionId, updateCacheData)
const loggedInUser: LoggedInUser = {
...req.user,
activeOrganizationSubscriptionId: subscription.id,
features
}
if (
newPlanId === process.env.CLOUD_FREE_ID ||
newPlanId === process.env.CLOUD_STARTER_ID ||
newPlanId === process.env.CLOUD_PRO_ID
) {
loggedInUser.activeOrganizationProductId = newPlanId
}
req.user = {
...req.user,
...loggedInUser
}
// Update passport session
// @ts-ignore
req.session.passport.user = {
...req.user,
...loggedInUser
}
req.session.save((err) => {
if (err) throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, GeneralErrorMessage.UNHANDLED_EDGE_CASE)
})
return {
status: 'success',
user: loggedInUser
}
}
return {
status: 'error',
message: 'Payment or subscription update not completed'
}
}
public async createStripeUserAndSubscribe({ email, userPlan, referral }: { email: string; userPlan: UserPlan; referral?: string }) {
if (!this.stripeManager) {
throw new Error('Stripe manager is not initialized')
}
try {
// Create a customer in Stripe
let customer: Stripe.Response<Stripe.Customer>
if (referral) {
customer = await this.stripeManager.getStripe().customers.create({
email: email,
metadata: {
referral
}
})
} else {
customer = await this.stripeManager.getStripe().customers.create({
email: email
})
}
let productId = ''
switch (userPlan) {
case UserPlan.STARTER:
productId = process.env.CLOUD_STARTER_ID as string
break
case UserPlan.PRO:
productId = process.env.CLOUD_PRO_ID as string
break
case UserPlan.FREE:
productId = process.env.CLOUD_FREE_ID as string
break
}
// Get the default price ID for the product
const prices = await this.stripeManager.getStripe().prices.list({
product: productId,
active: true,
limit: 1
})
if (!prices.data.length) {
throw new Error('No active price found for the product')
}
// Create the subscription
const subscription = await this.stripeManager.getStripe().subscriptions.create({
customer: customer.id,
items: [{ price: prices.data[0].id }]
})
return {
customerId: customer.id,
subscriptionId: subscription.id
}
} catch (error) {
console.error('Error creating Stripe user and subscription:', error)
throw error
}
}
}
@@ -4,6 +4,7 @@ import { DataSource } from 'typeorm'
import { IComponentNodes } from './Interface'
import { Telemetry } from './utils/telemetry'
import { CachePool } from './CachePool'
import { UsageCacheManager } from './UsageCacheManager'
export enum DocumentStoreStatus {
EMPTY_SYNC = 'EMPTY',
@@ -27,6 +28,7 @@ export interface IDocumentStore {
vectorStoreConfig: string | null // JSON string
embeddingConfig: string | null // JSON string
recordManagerConfig: string | null // JSON string
workspaceId?: string
}
export interface IDocumentStoreFileChunk {
@@ -47,6 +49,7 @@ export interface IDocumentStoreFileChunkPagedResponse {
storeName: string
description: string
docId: string
workspaceId?: string
}
export interface IDocumentStoreLoader {
@@ -119,9 +122,13 @@ export interface IDocumentStoreWhereUsed {
}
export interface IUpsertQueueAppServer {
orgId: string
workspaceId: string
subscriptionId: string
appDataSource: DataSource
componentNodes: IComponentNodes
telemetry: Telemetry
usageCacheManager: UsageCacheManager
cachePool?: CachePool
}
@@ -231,6 +238,7 @@ export class DocumentStoreDTO {
totalChunks: number
totalChars: number
chunkSize: number
workspaceId?: string
loaders: IDocumentStoreLoader[]
vectorStoreConfig: any
embeddingConfig: any
@@ -246,6 +254,7 @@ export class DocumentStoreDTO {
documentStoreDTO.name = entity.name
documentStoreDTO.description = entity.description
documentStoreDTO.status = entity.status
documentStoreDTO.workspaceId = entity.workspaceId
documentStoreDTO.totalChars = 0
documentStoreDTO.totalChunks = 0
+139
View File
@@ -0,0 +1,139 @@
// Evaluation Related Interfaces
import { Evaluator } from './database/entities/Evaluator'
export interface IDataset {
id: string
name: string
description: string
createdDate: Date
updatedDate: Date
workspaceId?: string
}
export interface IDatasetRow {
id: string
datasetId: string
input: string
output: string
updatedDate: Date
sequenceNo: number
}
export enum EvaluationStatus {
PENDING = 'pending',
COMPLETED = 'completed',
ERROR = 'error'
}
export interface IEvaluation {
id: string
name: string
chatflowId: string
chatflowName: string
datasetId: string
datasetName: string
evaluationType: string
additionalConfig: string //json
average_metrics: string //json
status: string
runDate: Date
workspaceId?: string
}
export interface IEvaluationResult extends IEvaluation {
latestEval: boolean
version: number
}
export interface IEvaluationRun {
id: string
evaluationId: string
input: string
expectedOutput: string
actualOutput: string // JSON
metrics: string // JSON
runDate: Date
llmEvaluators?: string // JSON
evaluators?: string // JSON
errors?: string // JSON
}
export interface IEvaluator {
id: string
name: string
type: string
config: string // JSON
updatedDate: Date
createdDate: Date
workspaceId?: string
}
export class EvaluatorDTO {
id: string
name: string
type: string
measure?: string
operator?: string
value?: string
prompt?: string
evaluatorType?: string
outputSchema?: []
updatedDate: Date
createdDate: Date
static toEntity(body: any): Evaluator {
const newDs = new Evaluator()
Object.assign(newDs, body)
let config: any = {}
if (body.type === 'llm') {
config = {
prompt: body.prompt,
outputSchema: body.outputSchema
}
} else if (body.type === 'text') {
config = {
operator: body.operator,
value: body.value
}
} else if (body.type === 'json') {
config = {
operator: body.operator
}
} else if (body.type === 'numeric') {
config = {
operator: body.operator,
value: body.value,
measure: body.measure
}
} else {
throw new Error('Invalid evaluator type')
}
newDs.config = JSON.stringify(config)
return newDs
}
static fromEntity(entity: Evaluator): EvaluatorDTO {
const newDs = new EvaluatorDTO()
Object.assign(newDs, entity)
const config = JSON.parse(entity.config)
if (entity.type === 'llm') {
newDs.prompt = config.prompt
newDs.outputSchema = config.outputSchema
} else if (entity.type === 'text') {
newDs.operator = config.operator
newDs.value = config.value
} else if (entity.type === 'json') {
newDs.operator = config.operator
newDs.value = config.value
} else if (entity.type === 'numeric') {
newDs.operator = config.operator
newDs.value = config.value
newDs.measure = config.measure
}
delete (newDs as any).config
return newDs
}
static fromEntities(entities: Evaluator[]): EvaluatorDTO[] {
return entities.map((entity) => this.fromEntity(entity))
}
}
+34 -1
View File
@@ -12,6 +12,7 @@ import {
import { DataSource } from 'typeorm'
import { CachePool } from './CachePool'
import { Telemetry } from './utils/telemetry'
import { UsageCacheManager } from './UsageCacheManager'
export type MessageType = 'apiMessage' | 'userMessage'
@@ -28,13 +29,27 @@ export enum MODE {
export enum ChatType {
INTERNAL = 'INTERNAL',
EXTERNAL = 'EXTERNAL'
EXTERNAL = 'EXTERNAL',
EVALUATION = 'EVALUATION'
}
export enum ChatMessageRatingType {
THUMBS_UP = 'THUMBS_UP',
THUMBS_DOWN = 'THUMBS_DOWN'
}
export enum Platform {
OPEN_SOURCE = 'open source',
CLOUD = 'cloud',
ENTERPRISE = 'enterprise'
}
export enum UserPlan {
STARTER = 'STARTER',
PRO = 'PRO',
FREE = 'FREE'
}
/**
* Databases
*/
@@ -54,6 +69,7 @@ export interface IChatFlow {
apiConfig?: string
category?: string
type?: ChatflowType
workspaceId?: string
}
export interface IChatMessage {
@@ -98,6 +114,7 @@ export interface ITool {
func?: string
updatedDate: Date
createdDate: Date
workspaceId?: string
}
export interface IAssistant {
@@ -107,6 +124,7 @@ export interface IAssistant {
iconSrc?: string
updatedDate: Date
createdDate: Date
workspaceId?: string
}
export interface ICredential {
@@ -116,6 +134,7 @@ export interface ICredential {
encryptedData: string
updatedDate: Date
createdDate: Date
workspaceId?: string
}
export interface IVariable {
@@ -125,6 +144,7 @@ export interface IVariable {
type: string
updatedDate: Date
createdDate: Date
workspaceId?: string
}
export interface ILead {
@@ -156,6 +176,7 @@ export interface IExecution {
createdDate: Date
updatedDate: Date
stoppedDate: Date
workspaceId?: string
}
export interface IComponentNodes {
@@ -311,6 +332,7 @@ export interface ICredentialReqBody {
name: string
credentialName: string
plainDataObj: ICredentialDataDecrypted
workspaceId?: string
}
// Decrypted credential object sent back to client
@@ -329,6 +351,7 @@ export interface IApiKey {
apiKey: string
apiSecret: string
updatedDate: Date
workspaceId?: string
}
export interface ICustomTemplate {
@@ -342,6 +365,7 @@ export interface ICustomTemplate {
badge?: string
framework?: string
usecases?: string
workspaceId?: string
}
export interface IFlowConfig {
@@ -361,14 +385,20 @@ export interface IPredictionQueueAppServer {
sseStreamer: IServerSideEventStreamer
telemetry: Telemetry
cachePool: CachePool
usageCacheManager: UsageCacheManager
}
export interface IExecuteFlowParams extends IPredictionQueueAppServer {
incomingInput: IncomingInput
chatflow: IChatFlow
chatId: string
orgId: string
workspaceId: string
subscriptionId: string
baseURL: string
isInternal: boolean
isEvaluation?: boolean
evaluationRunId?: string
signal?: AbortController
files?: Express.Multer.File[]
fileUploads?: IFileUpload[]
@@ -398,3 +428,6 @@ export interface IVariableOverride {
// DocumentStore related
export * from './Interface.DocumentStore'
// Evaluations related
export * from './Interface.Evaluation'
+606
View File
@@ -0,0 +1,606 @@
import Stripe from 'stripe'
import { Request } from 'express'
import { UsageCacheManager } from './UsageCacheManager'
import { UserPlan } from './Interface'
import { LICENSE_QUOTAS } from './utils/constants'
export class StripeManager {
private static instance: StripeManager
private stripe?: Stripe
private cacheManager: UsageCacheManager
public static async getInstance(): Promise<StripeManager> {
if (!StripeManager.instance) {
StripeManager.instance = new StripeManager()
await StripeManager.instance.initialize()
}
return StripeManager.instance
}
private async initialize() {
if (!this.stripe && process.env.STRIPE_SECRET_KEY) {
this.stripe = new Stripe(process.env.STRIPE_SECRET_KEY)
}
this.cacheManager = await UsageCacheManager.getInstance()
}
public getStripe() {
if (!this.stripe) throw new Error('Stripe is not initialized')
return this.stripe
}
public getSubscriptionObject(subscription: Stripe.Response<Stripe.Subscription>) {
return {
customer: subscription.customer,
status: subscription.status,
created: subscription.created
}
}
public async getProductIdFromSubscription(subscriptionId: string) {
if (!this.stripe) {
throw new Error('Stripe is not initialized')
}
const subscriptionData = await this.cacheManager.getSubscriptionDataFromCache(subscriptionId)
if (subscriptionData?.productId) {
return subscriptionData.productId
}
try {
const subscription = await this.stripe.subscriptions.retrieve(subscriptionId)
const items = subscription.items.data
if (items.length === 0) {
return ''
}
const productId = items[0].price.product as string
await this.cacheManager.updateSubscriptionDataToCache(subscriptionId, {
productId,
subsriptionDetails: this.getSubscriptionObject(subscription)
})
return productId
} catch (error) {
console.error('Error getting product ID from subscription:', error)
throw error
}
}
public async getFeaturesByPlan(subscriptionId: string, withoutCache: boolean = false) {
if (!this.stripe || !subscriptionId) {
return {}
}
if (!withoutCache) {
const subscriptionData = await this.cacheManager.getSubscriptionDataFromCache(subscriptionId)
if (subscriptionData?.features) {
return subscriptionData.features
}
}
const subscription = await this.stripe.subscriptions.retrieve(subscriptionId, {
timeout: 5000
})
const items = subscription.items.data
if (items.length === 0) {
return {}
}
const productId = items[0].price.product as string
const product = await this.stripe.products.retrieve(productId, {
timeout: 5000
})
const productMetadata = product.metadata
if (!productMetadata || Object.keys(productMetadata).length === 0) {
return {}
}
const features: Record<string, string> = {}
for (const key in productMetadata) {
if (key.startsWith('feat:')) {
features[key] = productMetadata[key]
}
}
await this.cacheManager.updateSubscriptionDataToCache(subscriptionId, {
features,
subsriptionDetails: this.getSubscriptionObject(subscription)
})
return features
}
public async createStripeCustomerPortalSession(req: Request) {
if (!this.stripe) {
throw new Error('Stripe is not initialized')
}
const customerId = req.user?.activeOrganizationCustomerId
if (!customerId) {
throw new Error('Customer ID is required')
}
const subscriptionId = req.user?.activeOrganizationSubscriptionId
if (!subscriptionId) {
throw new Error('Subscription ID is required')
}
try {
const prodPriceIds = await this.getPriceIds()
const configuration = await this.createPortalConfiguration(prodPriceIds)
const portalSession = await this.stripe.billingPortal.sessions.create({
customer: customerId,
configuration: configuration.id,
return_url: `${process.env.APP_URL}/account`
/* We can't have flow_data because it does not support multiple subscription items
flow_data: {
type: 'subscription_update',
subscription_update: {
subscription: subscriptionId
},
after_completion: {
type: 'redirect',
redirect: {
return_url: `${process.env.APP_URL}/account/subscription?subscriptionId=${subscriptionId}`
}
}
}*/
})
return { url: portalSession.url }
} catch (error) {
console.error('Error creating customer portal session:', error)
throw error
}
}
private async getPriceIds() {
const prodPriceIds: Record<string, { product: string; price: string }> = {
[UserPlan.STARTER]: {
product: process.env.CLOUD_STARTER_ID as string,
price: ''
},
[UserPlan.PRO]: {
product: process.env.CLOUD_PRO_ID as string,
price: ''
},
[UserPlan.FREE]: {
product: process.env.CLOUD_FREE_ID as string,
price: ''
},
SEAT: {
product: process.env.ADDITIONAL_SEAT_ID as string,
price: ''
}
}
for (const key in prodPriceIds) {
const prices = await this.stripe!.prices.list({
product: prodPriceIds[key].product,
active: true,
limit: 1
})
if (prices.data.length) {
prodPriceIds[key].price = prices.data[0].id
}
}
return prodPriceIds
}
private async createPortalConfiguration(_: Record<string, { product: string; price: string }>) {
return await this.stripe!.billingPortal.configurations.create({
business_profile: {
privacy_policy_url: `${process.env.APP_URL}/privacy-policy`,
terms_of_service_url: `${process.env.APP_URL}/terms-of-service`
},
features: {
invoice_history: {
enabled: true
},
payment_method_update: {
enabled: true
},
subscription_cancel: {
enabled: false
}
/*subscription_update: {
enabled: false,
default_allowed_updates: ['price'],
products: [
{
product: prodPriceIds[UserPlan.FREE].product,
prices: [prodPriceIds[UserPlan.FREE].price]
},
{
product: prodPriceIds[UserPlan.STARTER].product,
prices: [prodPriceIds[UserPlan.STARTER].price]
},
{
product: prodPriceIds[UserPlan.PRO].product,
prices: [prodPriceIds[UserPlan.PRO].price]
}
],
proration_behavior: 'always_invoice'
}*/
}
})
}
public async getAdditionalSeatsQuantity(subscriptionId: string): Promise<{ quantity: number; includedSeats: number }> {
if (!this.stripe) {
throw new Error('Stripe is not initialized')
}
try {
const subscription = await this.stripe.subscriptions.retrieve(subscriptionId)
const additionalSeatsItem = subscription.items.data.find(
(item) => (item.price.product as string) === process.env.ADDITIONAL_SEAT_ID
)
const quotas = await this.cacheManager.getQuotas(subscriptionId)
return { quantity: additionalSeatsItem?.quantity || 0, includedSeats: quotas[LICENSE_QUOTAS.USERS_LIMIT] }
} catch (error) {
console.error('Error getting additional seats quantity:', error)
throw error
}
}
public async getCustomerWithDefaultSource(customerId: string) {
if (!this.stripe) {
throw new Error('Stripe is not initialized')
}
try {
const customer = (await this.stripe.customers.retrieve(customerId, {
expand: ['default_source', 'invoice_settings.default_payment_method']
})) as Stripe.Customer
return customer
} catch (error) {
console.error('Error retrieving customer with default source:', error)
throw error
}
}
public async getAdditionalSeatsProration(subscriptionId: string, quantity: number) {
if (!this.stripe) {
throw new Error('Stripe is not initialized')
}
try {
const subscription = await this.stripe.subscriptions.retrieve(subscriptionId)
// Get customer's credit balance
const customer = await this.stripe.customers.retrieve(subscription.customer as string)
const creditBalance = (customer as Stripe.Customer).balance // Balance is in cents, negative for credit, positive for amount owed
// Get the current subscription's base price (without seats)
const basePlanItem = subscription.items.data.find((item) => (item.price.product as string) !== process.env.ADDITIONAL_SEAT_ID)
const basePlanAmount = basePlanItem ? basePlanItem.price.unit_amount! * 1 : 0
const existingInvoice = await this.stripe.invoices.retrieveUpcoming({
customer: subscription.customer as string,
subscription: subscriptionId
})
const existingInvoiceTotal = existingInvoice.total
// Get the price ID for additional seats
const prices = await this.stripe.prices.list({
product: process.env.ADDITIONAL_SEAT_ID,
active: true,
limit: 1
})
if (prices.data.length === 0) {
throw new Error('No active price found for additional seats')
}
const seatPrice = prices.data[0]
const pricePerSeat = seatPrice.unit_amount || 0
// Use current timestamp for proration calculation
const prorationDate = Math.floor(Date.now() / 1000)
const additionalSeatsItem = subscription.items.data.find(
(item) => (item.price.product as string) === process.env.ADDITIONAL_SEAT_ID
)
const upcomingInvoice = await this.stripe.invoices.retrieveUpcoming({
customer: subscription.customer as string,
subscription: subscriptionId,
subscription_details: {
proration_behavior: 'always_invoice',
proration_date: prorationDate,
items: [
additionalSeatsItem
? {
id: additionalSeatsItem.id,
quantity: quantity
}
: {
// If the item doesn't exist yet, create a new one
// This will be used to calculate the proration amount
price: prices.data[0].id,
quantity: quantity
}
]
}
})
// Calculate proration amount from the relevant line items
// Only consider prorations that match our proration date
const prorationLineItems = upcomingInvoice.lines.data.filter(
(line) => line.type === 'invoiceitem' && line.period.start === prorationDate
)
const prorationAmount = prorationLineItems.reduce((total, item) => total + item.amount, 0)
return {
basePlanAmount: basePlanAmount / 100,
additionalSeatsProratedAmount: (existingInvoiceTotal + prorationAmount - basePlanAmount) / 100,
seatPerUnitPrice: pricePerSeat / 100,
prorationAmount: prorationAmount / 100,
creditBalance: creditBalance / 100,
nextInvoiceTotal: (existingInvoiceTotal + prorationAmount) / 100,
currency: upcomingInvoice.currency.toUpperCase(),
prorationDate,
currentPeriodStart: subscription.current_period_start,
currentPeriodEnd: subscription.current_period_end
}
} catch (error) {
console.error('Error calculating additional seats proration:', error)
throw error
}
}
public async updateAdditionalSeats(subscriptionId: string, quantity: number, prorationDate: number) {
if (!this.stripe) {
throw new Error('Stripe is not initialized')
}
try {
const subscription = await this.stripe.subscriptions.retrieve(subscriptionId)
const additionalSeatsItem = subscription.items.data.find(
(item) => (item.price.product as string) === process.env.ADDITIONAL_SEAT_ID
)
// Get the price ID for additional seats if needed
const prices = await this.stripe.prices.list({
product: process.env.ADDITIONAL_SEAT_ID,
active: true,
limit: 1
})
if (prices.data.length === 0) {
throw new Error('No active price found for additional seats')
}
// Create an invoice immediately for the proration
const updatedSubscription = await this.stripe.subscriptions.update(subscriptionId, {
items: [
additionalSeatsItem
? {
id: additionalSeatsItem.id,
quantity: quantity
}
: {
price: prices.data[0].id,
quantity: quantity
}
],
proration_behavior: 'always_invoice',
proration_date: prorationDate
})
// Get the latest invoice for this subscription
const invoice = await this.stripe.invoices.list({
subscription: subscriptionId,
limit: 1
})
if (invoice.data.length > 0) {
const latestInvoice = invoice.data[0]
// Only try to pay if the invoice is not already paid
if (latestInvoice.status !== 'paid') {
await this.stripe.invoices.pay(latestInvoice.id)
}
}
return {
success: true,
subscription: updatedSubscription,
invoice: invoice.data[0]
}
} catch (error) {
console.error('Error updating additional seats:', error)
throw error
}
}
public async getPlanProration(subscriptionId: string, newPlanId: string) {
if (!this.stripe) {
throw new Error('Stripe is not initialized')
}
try {
const subscription = await this.stripe.subscriptions.retrieve(subscriptionId)
const customerId = subscription.customer as string
// Get customer's credit balance and metadata
const customer = await this.stripe.customers.retrieve(customerId)
const creditBalance = (customer as Stripe.Customer).balance
const customerMetadata = (customer as Stripe.Customer).metadata || {}
// Get the price ID for the new plan
const prices = await this.stripe.prices.list({
product: newPlanId,
active: true,
limit: 1
})
if (prices.data.length === 0) {
throw new Error('No active price found for the selected plan')
}
const newPlan = prices.data[0]
const newPlanPrice = newPlan.unit_amount || 0
// Check if this is the STARTER plan and eligible for first month free
const isStarterPlan = newPlanId === process.env.CLOUD_STARTER_ID
const hasUsedFirstMonthFreeCoupon = customerMetadata.has_used_first_month_free === 'true'
const eligibleForFirstMonthFree = isStarterPlan && !hasUsedFirstMonthFreeCoupon
// Use current timestamp for proration calculation
const prorationDate = Math.floor(Date.now() / 1000)
const upcomingInvoice = await this.stripe.invoices.retrieveUpcoming({
customer: customerId,
subscription: subscriptionId,
subscription_details: {
proration_behavior: 'always_invoice',
proration_date: prorationDate,
items: [
{
id: subscription.items.data[0].id,
price: newPlan.id
}
]
}
})
let prorationAmount = upcomingInvoice.lines.data.reduce((total, item) => total + item.amount, 0)
if (eligibleForFirstMonthFree) {
prorationAmount = 0
}
return {
newPlanAmount: newPlanPrice / 100,
prorationAmount: prorationAmount / 100,
creditBalance: creditBalance / 100,
currency: upcomingInvoice.currency.toUpperCase(),
prorationDate,
currentPeriodStart: subscription.current_period_start,
currentPeriodEnd: subscription.current_period_end,
eligibleForFirstMonthFree
}
} catch (error) {
console.error('Error calculating plan proration:', error)
throw error
}
}
public async updateSubscriptionPlan(subscriptionId: string, newPlanId: string, prorationDate: number) {
if (!this.stripe) {
throw new Error('Stripe is not initialized')
}
try {
const subscription = await this.stripe.subscriptions.retrieve(subscriptionId)
const customerId = subscription.customer as string
// Get customer details and metadata
const customer = await this.stripe.customers.retrieve(customerId)
const customerMetadata = (customer as Stripe.Customer).metadata || {}
// Get the price ID for the new plan
const prices = await this.stripe.prices.list({
product: newPlanId,
active: true,
limit: 1
})
if (prices.data.length === 0) {
throw new Error('No active price found for the selected plan')
}
const newPlan = prices.data[0]
let updatedSubscription: Stripe.Response<Stripe.Subscription>
// Check if this is an upgrade to CLOUD_STARTER_ID and eligible for first month free
const isStarterPlan = newPlanId === process.env.CLOUD_STARTER_ID
const hasUsedFirstMonthFreeCoupon = customerMetadata.has_used_first_month_free === 'true'
if (isStarterPlan && !hasUsedFirstMonthFreeCoupon) {
// Create the one-time 100% off coupon
const coupon = await this.stripe.coupons.create({
duration: 'once',
percent_off: 100,
max_redemptions: 1,
metadata: {
type: 'first_month_free',
customer_id: customerId,
plan_id: process.env.CLOUD_STARTER_ID || ''
}
})
// Create a promotion code linked to the coupon
const promotionCode = await this.stripe.promotionCodes.create({
coupon: coupon.id,
max_redemptions: 1
})
// Update the subscription with the new plan and apply the promotion code
updatedSubscription = await this.stripe.subscriptions.update(subscriptionId, {
items: [
{
id: subscription.items.data[0].id,
price: newPlan.id
}
],
proration_behavior: 'always_invoice',
proration_date: prorationDate,
promotion_code: promotionCode.id
})
// Update customer metadata to mark the coupon as used
await this.stripe.customers.update(customerId, {
metadata: {
...customerMetadata,
has_used_first_month_free: 'true',
first_month_free_date: new Date().toISOString()
}
})
} else {
// Regular plan update without coupon
updatedSubscription = await this.stripe.subscriptions.update(subscriptionId, {
items: [
{
id: subscription.items.data[0].id,
price: newPlan.id
}
],
proration_behavior: 'always_invoice',
proration_date: prorationDate
})
}
// Get and pay the latest invoice
const invoice = await this.stripe.invoices.list({
subscription: subscriptionId,
limit: 1
})
if (invoice.data.length > 0) {
const latestInvoice = invoice.data[0]
if (latestInvoice.status !== 'paid') {
await this.stripe.invoices.pay(latestInvoice.id)
}
}
return {
success: true,
subscription: updatedSubscription,
invoice: invoice.data[0]
}
} catch (error) {
console.error('Error updating subscription plan:', error)
throw error
}
}
}
+213
View File
@@ -0,0 +1,213 @@
import { Keyv } from 'keyv'
import KeyvRedis from '@keyv/redis'
import { Cache, createCache } from 'cache-manager'
import { MODE } from './Interface'
import { LICENSE_QUOTAS } from './utils/constants'
import { StripeManager } from './StripeManager'
const DISABLED_QUOTAS = {
[LICENSE_QUOTAS.PREDICTIONS_LIMIT]: 0,
[LICENSE_QUOTAS.STORAGE_LIMIT]: 0, // in MB
[LICENSE_QUOTAS.FLOWS_LIMIT]: 0,
[LICENSE_QUOTAS.USERS_LIMIT]: 0,
[LICENSE_QUOTAS.ADDITIONAL_SEATS_LIMIT]: 0
}
const UNLIMITED_QUOTAS = {
[LICENSE_QUOTAS.PREDICTIONS_LIMIT]: -1,
[LICENSE_QUOTAS.STORAGE_LIMIT]: -1,
[LICENSE_QUOTAS.FLOWS_LIMIT]: -1,
[LICENSE_QUOTAS.USERS_LIMIT]: -1,
[LICENSE_QUOTAS.ADDITIONAL_SEATS_LIMIT]: -1
}
export class UsageCacheManager {
private cache: Cache
private static instance: UsageCacheManager
public static async getInstance(): Promise<UsageCacheManager> {
if (!UsageCacheManager.instance) {
UsageCacheManager.instance = new UsageCacheManager()
await UsageCacheManager.instance.initialize()
}
return UsageCacheManager.instance
}
private async initialize(): Promise<void> {
if (process.env.MODE === MODE.QUEUE) {
let redisConfig: string | Record<string, any>
if (process.env.REDIS_URL) {
redisConfig = process.env.REDIS_URL
} else {
redisConfig = {
username: process.env.REDIS_USERNAME || undefined,
password: process.env.REDIS_PASSWORD || undefined,
socket: {
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT || '6379'),
tls: process.env.REDIS_TLS === 'true',
cert: process.env.REDIS_CERT ? Buffer.from(process.env.REDIS_CERT, 'base64') : undefined,
key: process.env.REDIS_KEY ? Buffer.from(process.env.REDIS_KEY, 'base64') : undefined,
ca: process.env.REDIS_CA ? Buffer.from(process.env.REDIS_CA, 'base64') : undefined
}
}
}
this.cache = createCache({
stores: [
new Keyv({
store: new KeyvRedis(redisConfig)
})
]
})
} else {
this.cache = createCache()
}
}
public async getSubscriptionDetails(subscriptionId: string, withoutCache: boolean = false): Promise<Record<string, any>> {
const stripeManager = await StripeManager.getInstance()
if (!stripeManager || !subscriptionId) {
return UNLIMITED_QUOTAS
}
// Skip cache if withoutCache is true
if (!withoutCache) {
const subscriptionData = await this.getSubscriptionDataFromCache(subscriptionId)
if (subscriptionData?.subsriptionDetails) {
return subscriptionData.subsriptionDetails
}
}
// If not in cache, retrieve from Stripe
const subscription = await stripeManager.getStripe().subscriptions.retrieve(subscriptionId)
// Update subscription data cache
await this.updateSubscriptionDataToCache(subscriptionId, { subsriptionDetails: stripeManager.getSubscriptionObject(subscription) })
return stripeManager.getSubscriptionObject(subscription)
}
public async getQuotas(subscriptionId: string, withoutCache: boolean = false): Promise<Record<string, number>> {
const stripeManager = await StripeManager.getInstance()
if (!stripeManager || !subscriptionId) {
return UNLIMITED_QUOTAS
}
// Skip cache if withoutCache is true
if (!withoutCache) {
const subscriptionData = await this.getSubscriptionDataFromCache(subscriptionId)
if (subscriptionData?.quotas) {
return subscriptionData.quotas
}
}
// If not in cache, retrieve from Stripe
const subscription = await stripeManager.getStripe().subscriptions.retrieve(subscriptionId)
const items = subscription.items.data
if (items.length === 0) {
return DISABLED_QUOTAS
}
const productId = items[0].price.product as string
const product = await stripeManager.getStripe().products.retrieve(productId)
const productMetadata = product.metadata
if (!productMetadata || Object.keys(productMetadata).length === 0) {
return DISABLED_QUOTAS
}
const quotas: Record<string, number> = {}
for (const key in productMetadata) {
if (key.startsWith('quota:')) {
quotas[key] = parseInt(productMetadata[key])
}
}
const additionalSeatsItem = subscription.items.data.find(
(item) => (item.price.product as string) === process.env.ADDITIONAL_SEAT_ID
)
quotas[LICENSE_QUOTAS.ADDITIONAL_SEATS_LIMIT] = additionalSeatsItem?.quantity || 0
// Update subscription data cache with quotas
await this.updateSubscriptionDataToCache(subscriptionId, {
quotas,
subsriptionDetails: stripeManager.getSubscriptionObject(subscription)
})
return quotas
}
public async getSubscriptionDataFromCache(subscriptionId: string) {
const cacheKey = `subscription:${subscriptionId}`
return await this.get<{
quotas?: Record<string, number>
productId?: string
features?: Record<string, string>
subsriptionDetails?: Record<string, any>
}>(cacheKey)
}
public async updateSubscriptionDataToCache(
subscriptionId: string,
data: Partial<{
quotas: Record<string, number>
productId: string
features: Record<string, string>
subsriptionDetails: Record<string, any>
}>
) {
const cacheKey = `subscription:${subscriptionId}`
const existingData = (await this.getSubscriptionDataFromCache(subscriptionId)) || {}
const updatedData = { ...existingData, ...data }
this.set(cacheKey, updatedData, 3600000) // Cache for 1 hour
}
public async get<T>(key: string): Promise<T | null> {
if (!this.cache) await this.initialize()
const value = await this.cache.get<T>(key)
return value
}
public async getTTL(key: string): Promise<number | null> {
if (!this.cache) await this.initialize()
const value = await this.cache.ttl(key)
return value
}
public async mget<T>(keys: string[]): Promise<(T | null)[]> {
if (this.cache) {
const values = await this.cache.mget<T>(keys)
return values
} else {
return []
}
}
public set<T>(key: string, value: T, ttl?: number) {
if (this.cache) {
this.cache.set(key, value, ttl)
}
}
public mset<T>(keys: [{ key: string; value: T; ttl: number }]) {
if (this.cache) {
this.cache.mset(keys)
}
}
public async del(key: string): Promise<void> {
await this.cache.del(key)
}
public async mdel(keys: string[]): Promise<void> {
await this.cache.mdel(keys)
}
public async clear(): Promise<void> {
await this.cache.clear()
}
public async wrap<T>(key: string, fn: () => Promise<T>, ttl?: number): Promise<T> {
return this.cache.wrap(key, fn, ttl)
}
}
+2 -12
View File
@@ -12,16 +12,12 @@ enum EXIT_CODE {
export abstract class BaseCommand extends Command {
static flags = {
FLOWISE_USERNAME: Flags.string(),
FLOWISE_PASSWORD: Flags.string(),
FLOWISE_FILE_SIZE_LIMIT: Flags.string(),
PORT: Flags.string(),
CORS_ORIGINS: Flags.string(),
IFRAME_ORIGINS: Flags.string(),
DEBUG: Flags.string(),
BLOB_STORAGE_PATH: Flags.string(),
APIKEY_STORAGE_TYPE: Flags.string(),
APIKEY_PATH: Flags.string(),
LOG_PATH: Flags.string(),
LOG_LEVEL: Flags.string(),
TOOL_FUNCTION_BUILTIN_DEP: Flags.string(),
@@ -59,6 +55,7 @@ export abstract class BaseCommand extends Command {
SECRETKEY_AWS_ACCESS_KEY: Flags.string(),
SECRETKEY_AWS_SECRET_KEY: Flags.string(),
SECRETKEY_AWS_REGION: Flags.string(),
SECRETKEY_AWS_NAME: Flags.string(),
DISABLED_NODES: Flags.string(),
MODE: Flags.string(),
WORKER_CONCURRENCY: Flags.string(),
@@ -131,14 +128,6 @@ export abstract class BaseCommand extends Command {
if (flags.NUMBER_OF_PROXIES) process.env.NUMBER_OF_PROXIES = flags.NUMBER_OF_PROXIES
if (flags.SHOW_COMMUNITY_NODES) process.env.SHOW_COMMUNITY_NODES = flags.SHOW_COMMUNITY_NODES
if (flags.DISABLED_NODES) process.env.DISABLED_NODES = flags.DISABLED_NODES
// Authorization
if (flags.FLOWISE_USERNAME) process.env.FLOWISE_USERNAME = flags.FLOWISE_USERNAME
if (flags.FLOWISE_PASSWORD) process.env.FLOWISE_PASSWORD = flags.FLOWISE_PASSWORD
if (flags.APIKEY_STORAGE_TYPE) process.env.APIKEY_STORAGE_TYPE = flags.APIKEY_STORAGE_TYPE
if (flags.APIKEY_PATH) process.env.APIKEY_PATH = flags.APIKEY_PATH
// API Configuration
if (flags.FLOWISE_FILE_SIZE_LIMIT) process.env.FLOWISE_FILE_SIZE_LIMIT = flags.FLOWISE_FILE_SIZE_LIMIT
// Credentials
@@ -148,6 +137,7 @@ export abstract class BaseCommand extends Command {
if (flags.SECRETKEY_AWS_ACCESS_KEY) process.env.SECRETKEY_AWS_ACCESS_KEY = flags.SECRETKEY_AWS_ACCESS_KEY
if (flags.SECRETKEY_AWS_SECRET_KEY) process.env.SECRETKEY_AWS_SECRET_KEY = flags.SECRETKEY_AWS_SECRET_KEY
if (flags.SECRETKEY_AWS_REGION) process.env.SECRETKEY_AWS_REGION = flags.SECRETKEY_AWS_REGION
if (flags.SECRETKEY_AWS_NAME) process.env.SECRETKEY_AWS_NAME = flags.SECRETKEY_AWS_NAME
// Logs
if (flags.LOG_PATH) process.env.LOG_PATH = flags.LOG_PATH
+8 -3
View File
@@ -7,6 +7,7 @@ import { NodesPool } from '../NodesPool'
import { CachePool } from '../CachePool'
import { QueueEvents, QueueEventsListener } from 'bullmq'
import { AbortControllerPool } from '../AbortControllerPool'
import { UsageCacheManager } from '../UsageCacheManager'
interface CustomListener extends QueueEventsListener {
abort: (args: { id: string }, id: string) => void
@@ -19,7 +20,7 @@ export default class Worker extends BaseCommand {
async run(): Promise<void> {
logger.info('Starting Flowise Worker...')
const { appDataSource, telemetry, componentNodes, cachePool, abortControllerPool } = await this.prepareData()
const { appDataSource, telemetry, componentNodes, cachePool, abortControllerPool, usageCacheManager } = await this.prepareData()
const queueManager = QueueManager.getInstance()
queueManager.setupAllQueues({
@@ -27,7 +28,8 @@ export default class Worker extends BaseCommand {
telemetry,
cachePool,
appDataSource,
abortControllerPool
abortControllerPool,
usageCacheManager
})
/** Prediction */
@@ -72,7 +74,10 @@ export default class Worker extends BaseCommand {
// Initialize cache pool
const cachePool = new CachePool()
return { appDataSource, telemetry, componentNodes: nodesPool.componentNodes, cachePool, abortControllerPool }
// Initialize usage cache manager
const usageCacheManager = await UsageCacheManager.getInstance()
return { appDataSource, telemetry, componentNodes: nodesPool.componentNodes, cachePool, abortControllerPool, usageCacheManager }
}
async catch(error: Error) {
@@ -6,7 +6,8 @@ import apikeyService from '../../services/apikey'
// Get api keys
const getAllApiKeys = async (req: Request, res: Response, next: NextFunction) => {
try {
const apiResponse = await apikeyService.getAllApiKeys()
const autoCreateNewKey = true
const apiResponse = await apikeyService.getAllApiKeys(req.user?.activeWorkspaceId, autoCreateNewKey)
return res.json(apiResponse)
} catch (error) {
next(error)
@@ -18,7 +19,7 @@ const createApiKey = async (req: Request, res: Response, next: NextFunction) =>
if (typeof req.body === 'undefined' || !req.body.keyName) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: apikeyController.createApiKey - keyName not provided!`)
}
const apiResponse = await apikeyService.createApiKey(req.body.keyName)
const apiResponse = await apikeyService.createApiKey(req.body.keyName, req.user?.activeWorkspaceId)
return res.json(apiResponse)
} catch (error) {
next(error)
@@ -34,7 +35,7 @@ const updateApiKey = async (req: Request, res: Response, next: NextFunction) =>
if (typeof req.body === 'undefined' || !req.body.keyName) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: apikeyController.updateApiKey - keyName not provided!`)
}
const apiResponse = await apikeyService.updateApiKey(req.params.id, req.body.keyName)
const apiResponse = await apikeyService.updateApiKey(req.params.id, req.body.keyName, req.user?.activeWorkspaceId)
return res.json(apiResponse)
} catch (error) {
next(error)
@@ -47,6 +48,7 @@ const importKeys = async (req: Request, res: Response, next: NextFunction) => {
if (typeof req.body === 'undefined' || !req.body.jsonFile) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: apikeyController.importKeys - body not provided!`)
}
req.body.workspaceId = req.user?.activeWorkspaceId
const apiResponse = await apikeyService.importKeys(req.body)
return res.json(apiResponse)
} catch (error) {
@@ -60,7 +62,7 @@ const deleteApiKey = async (req: Request, res: Response, next: NextFunction) =>
if (typeof req.params === 'undefined' || !req.params.id) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: apikeyController.deleteApiKey - id not provided!`)
}
const apiResponse = await apikeyService.deleteApiKey(req.params.id)
const apiResponse = await apikeyService.deleteApiKey(req.params.id, req.user?.activeWorkspaceId)
return res.json(apiResponse)
} catch (error) {
next(error)
@@ -1,8 +1,10 @@
import { Request, Response, NextFunction } from 'express'
import assistantsService from '../../services/assistants'
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
import { NextFunction, Request, Response } from 'express'
import { StatusCodes } from 'http-status-codes'
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
import { AssistantType } from '../../Interface'
import assistantsService from '../../services/assistants'
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
import { checkUsageLimit } from '../../utils/quotaUsage'
const createAssistant = async (req: Request, res: Response, next: NextFunction) => {
try {
@@ -12,7 +14,30 @@ const createAssistant = async (req: Request, res: Response, next: NextFunction)
`Error: assistantsController.createAssistant - body not provided!`
)
}
const apiResponse = await assistantsService.createAssistant(req.body)
const body = req.body
const orgId = req.user?.activeOrganizationId
if (!orgId) {
throw new InternalFlowiseError(
StatusCodes.NOT_FOUND,
`Error: assistantsController.createAssistant - organization ${orgId} not found!`
)
}
const workspaceId = req.user?.activeWorkspaceId
if (!workspaceId) {
throw new InternalFlowiseError(
StatusCodes.NOT_FOUND,
`Error: assistantsController.createAssistant - workspace ${workspaceId} not found!`
)
}
const subscriptionId = req.user?.activeOrganizationSubscriptionId || ''
const existingAssistantCount = await assistantsService.getAssistantsCountByOrganization(body.type, orgId)
const newAssistantCount = 1
await checkUsageLimit('flows', subscriptionId, getRunningExpressApp().usageCacheManager, existingAssistantCount + newAssistantCount)
body.workspaceId = workspaceId
const apiResponse = await assistantsService.createAssistant(body, orgId)
return res.json(apiResponse)
} catch (error) {
next(error)
@@ -37,7 +62,7 @@ const deleteAssistant = async (req: Request, res: Response, next: NextFunction)
const getAllAssistants = async (req: Request, res: Response, next: NextFunction) => {
try {
const type = req.query.type as AssistantType
const apiResponse = await assistantsService.getAllAssistants(type)
const apiResponse = await assistantsService.getAllAssistants(type, req.user?.activeWorkspaceId)
return res.json(apiResponse)
} catch (error) {
next(error)
@@ -91,7 +116,7 @@ const getChatModels = async (req: Request, res: Response, next: NextFunction) =>
const getDocumentStores = async (req: Request, res: Response, next: NextFunction) => {
try {
const apiResponse = await assistantsService.getDocumentStores()
const apiResponse = await assistantsService.getDocumentStores(req.user?.activeWorkspaceId)
return res.json(apiResponse)
} catch (error) {
next(error)
@@ -62,6 +62,7 @@ const getAllChatMessages = async (req: Request, res: Response, next: NextFunctio
chatTypes = [_chatTypes as ChatType]
}
}
const activeWorkspaceId = req.user?.activeWorkspaceId
const sortOrder = req.query?.order as string | undefined
const chatId = req.query?.chatId as string | undefined
const memoryType = req.query?.memoryType as string | undefined
@@ -91,9 +92,9 @@ const getAllChatMessages = async (req: Request, res: Response, next: NextFunctio
endDate,
messageId,
feedback,
feedbackTypeFilters
feedbackTypeFilters,
activeWorkspaceId
)
return res.json(parseAPIResponse(apiResponse))
} catch (error) {
next(error)
@@ -102,6 +103,7 @@ const getAllChatMessages = async (req: Request, res: Response, next: NextFunctio
const getAllInternalChatMessages = async (req: Request, res: Response, next: NextFunction) => {
try {
const activeWorkspaceId = req.user?.activeWorkspaceId
const sortOrder = req.query?.order as string | undefined
const chatId = req.query?.chatId as string | undefined
const memoryType = req.query?.memoryType as string | undefined
@@ -125,7 +127,8 @@ const getAllInternalChatMessages = async (req: Request, res: Response, next: Nex
endDate,
messageId,
feedback,
feedbackTypeFilters
feedbackTypeFilters,
activeWorkspaceId
)
return res.json(parseAPIResponse(apiResponse))
} catch (error) {
@@ -142,6 +145,20 @@ const removeAllChatMessages = async (req: Request, res: Response, next: NextFunc
'Error: chatMessagesController.removeAllChatMessages - id not provided!'
)
}
const orgId = req.user?.activeOrganizationId
if (!orgId) {
throw new InternalFlowiseError(
StatusCodes.NOT_FOUND,
`Error: chatMessagesController.removeAllChatMessages - organization ${orgId} not found!`
)
}
const workspaceId = req.user?.activeWorkspaceId
if (!workspaceId) {
throw new InternalFlowiseError(
StatusCodes.NOT_FOUND,
`Error: chatMessagesController.removeAllChatMessages - workspace ${workspaceId} not found!`
)
}
const chatflowid = req.params.id
const chatflow = await chatflowsService.getChatflowById(req.params.id)
if (!chatflow) {
@@ -177,6 +194,7 @@ const removeAllChatMessages = async (req: Request, res: Response, next: NextFunc
if (!chatId) {
const isFeedback = feedbackTypeFilters?.length ? true : false
const hardDelete = req.query?.hardDelete as boolean | undefined
const messages = await utilGetChatMessage({
chatflowid,
chatTypes,
@@ -216,6 +234,7 @@ const removeAllChatMessages = async (req: Request, res: Response, next: NextFunc
appServer.nodesPool.componentNodes,
chatId,
appServer.AppDataSource,
orgId,
sessionId,
memoryType,
isClearFromViewMessageDialog
@@ -226,7 +245,14 @@ const removeAllChatMessages = async (req: Request, res: Response, next: NextFunc
}
}
const apiResponse = await chatMessagesService.removeChatMessagesByMessageIds(chatflowid, chatIdMap, messageIds)
const apiResponse = await chatMessagesService.removeChatMessagesByMessageIds(
chatflowid,
chatIdMap,
messageIds,
orgId,
workspaceId,
appServer.usageCacheManager
)
return res.json(apiResponse)
} else {
try {
@@ -235,6 +261,7 @@ const removeAllChatMessages = async (req: Request, res: Response, next: NextFunc
appServer.nodesPool.componentNodes,
chatId,
appServer.AppDataSource,
orgId,
sessionId,
memoryType,
isClearFromViewMessageDialog
@@ -255,7 +282,14 @@ const removeAllChatMessages = async (req: Request, res: Response, next: NextFunc
const toDate = new Date(endDate)
deleteOptions.createdDate = Between(fromDate ?? aMonthAgo(), toDate ?? new Date())
}
const apiResponse = await chatMessagesService.removeAllChatMessages(chatId, chatflowid, deleteOptions)
const apiResponse = await chatMessagesService.removeAllChatMessages(
chatId,
chatflowid,
deleteOptions,
orgId,
workspaceId,
appServer.usageCacheManager
)
return res.json(apiResponse)
}
} catch (error) {
@@ -282,26 +316,30 @@ const parseAPIResponse = (apiResponse: 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)
}
if (parsedResponse.artifacts) {
parsedResponse.artifacts = JSON.parse(parsedResponse.artifacts)
try {
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)
}
if (parsedResponse.artifacts) {
parsedResponse.artifacts = JSON.parse(parsedResponse.artifacts)
}
} catch (e) {
console.error('Error parsing chat message response', e)
}
return parsedResponse
@@ -1,18 +1,20 @@
import { NextFunction, Request, Response } from 'express'
import { StatusCodes } from 'http-status-codes'
import apiKeyService from '../../services/apikey'
import { ChatFlow } from '../../database/entities/ChatFlow'
import { RateLimiterManager } from '../../utils/rateLimit'
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
import { ChatflowType } from '../../Interface'
import apiKeyService from '../../services/apikey'
import chatflowsService from '../../services/chatflows'
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
import { checkUsageLimit } from '../../utils/quotaUsage'
import { RateLimiterManager } from '../../utils/rateLimit'
const checkIfChatflowIsValidForStreaming = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params === 'undefined' || !req.params.id) {
throw new InternalFlowiseError(
StatusCodes.PRECONDITION_FAILED,
`Error: chatflowsRouter.checkIfChatflowIsValidForStreaming - id not provided!`
`Error: chatflowsController.checkIfChatflowIsValidForStreaming - id not provided!`
)
}
const apiResponse = await chatflowsService.checkIfChatflowIsValidForStreaming(req.params.id)
@@ -27,7 +29,7 @@ const checkIfChatflowIsValidForUploads = async (req: Request, res: Response, nex
if (typeof req.params === 'undefined' || !req.params.id) {
throw new InternalFlowiseError(
StatusCodes.PRECONDITION_FAILED,
`Error: chatflowsRouter.checkIfChatflowIsValidForUploads - id not provided!`
`Error: chatflowsController.checkIfChatflowIsValidForUploads - id not provided!`
)
}
const apiResponse = await chatflowsService.checkIfChatflowIsValidForUploads(req.params.id)
@@ -40,9 +42,23 @@ const checkIfChatflowIsValidForUploads = async (req: Request, res: Response, nex
const deleteChatflow = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params === 'undefined' || !req.params.id) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: chatflowsRouter.deleteChatflow - id not provided!`)
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: chatflowsController.deleteChatflow - id not provided!`)
}
const apiResponse = await chatflowsService.deleteChatflow(req.params.id)
const orgId = req.user?.activeOrganizationId
if (!orgId) {
throw new InternalFlowiseError(
StatusCodes.NOT_FOUND,
`Error: chatflowsController.deleteChatflow - organization ${orgId} not found!`
)
}
const workspaceId = req.user?.activeWorkspaceId
if (!workspaceId) {
throw new InternalFlowiseError(
StatusCodes.NOT_FOUND,
`Error: chatflowsController.deleteChatflow - workspace ${workspaceId} not found!`
)
}
const apiResponse = await chatflowsService.deleteChatflow(req.params.id, orgId, workspaceId)
return res.json(apiResponse)
} catch (error) {
next(error)
@@ -51,7 +67,7 @@ const deleteChatflow = async (req: Request, res: Response, next: NextFunction) =
const getAllChatflows = async (req: Request, res: Response, next: NextFunction) => {
try {
const apiResponse = await chatflowsService.getAllChatflows(req.query?.type as ChatflowType)
const apiResponse = await chatflowsService.getAllChatflows(req.query?.type as ChatflowType, req.user?.activeWorkspaceId)
return res.json(apiResponse)
} catch (error) {
next(error)
@@ -64,7 +80,7 @@ const getChatflowByApiKey = async (req: Request, res: Response, next: NextFuncti
if (typeof req.params === 'undefined' || !req.params.apikey) {
throw new InternalFlowiseError(
StatusCodes.PRECONDITION_FAILED,
`Error: chatflowsRouter.getChatflowByApiKey - apikey not provided!`
`Error: chatflowsController.getChatflowByApiKey - apikey not provided!`
)
}
const apikey = await apiKeyService.getApiKey(req.params.apikey)
@@ -81,7 +97,7 @@ const getChatflowByApiKey = async (req: Request, res: Response, next: NextFuncti
const getChatflowById = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params === 'undefined' || !req.params.id) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: chatflowsRouter.getChatflowById - id not provided!`)
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: chatflowsController.getChatflowById - id not provided!`)
}
const apiResponse = await chatflowsService.getChatflowById(req.params.id)
return res.json(apiResponse)
@@ -93,12 +109,40 @@ const getChatflowById = async (req: Request, res: Response, next: NextFunction)
const saveChatflow = async (req: Request, res: Response, next: NextFunction) => {
try {
if (!req.body) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: chatflowsRouter.saveChatflow - body not provided!`)
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: chatflowsController.saveChatflow - body not provided!`)
}
const orgId = req.user?.activeOrganizationId
if (!orgId) {
throw new InternalFlowiseError(
StatusCodes.NOT_FOUND,
`Error: chatflowsController.saveChatflow - organization ${orgId} not found!`
)
}
const workspaceId = req.user?.activeWorkspaceId
if (!workspaceId) {
throw new InternalFlowiseError(
StatusCodes.NOT_FOUND,
`Error: chatflowsController.saveChatflow - workspace ${workspaceId} not found!`
)
}
const subscriptionId = req.user?.activeOrganizationSubscriptionId || ''
const body = req.body
const existingChatflowCount = await chatflowsService.getAllChatflowsCountByOrganization(body.type, orgId)
const newChatflowCount = 1
await checkUsageLimit('flows', subscriptionId, getRunningExpressApp().usageCacheManager, existingChatflowCount + newChatflowCount)
const newChatFlow = new ChatFlow()
Object.assign(newChatFlow, body)
const apiResponse = await chatflowsService.saveChatflow(newChatFlow)
newChatFlow.workspaceId = workspaceId
const apiResponse = await chatflowsService.saveChatflow(
newChatFlow,
orgId,
workspaceId,
subscriptionId,
getRunningExpressApp().usageCacheManager
)
return res.json(apiResponse)
} catch (error) {
next(error)
@@ -108,7 +152,23 @@ const saveChatflow = async (req: Request, res: Response, next: NextFunction) =>
const importChatflows = async (req: Request, res: Response, next: NextFunction) => {
try {
const chatflows: Partial<ChatFlow>[] = req.body.Chatflows
const apiResponse = await chatflowsService.importChatflows(chatflows)
const orgId = req.user?.activeOrganizationId
if (!orgId) {
throw new InternalFlowiseError(
StatusCodes.NOT_FOUND,
`Error: chatflowsController.saveChatflow - organization ${orgId} not found!`
)
}
const workspaceId = req.user?.activeWorkspaceId
if (!workspaceId) {
throw new InternalFlowiseError(
StatusCodes.NOT_FOUND,
`Error: chatflowsController.saveChatflow - workspace ${workspaceId} not found!`
)
}
const subscriptionId = req.user?.activeOrganizationSubscriptionId || ''
req.body.workspaceId = req.user?.activeWorkspaceId
const apiResponse = await chatflowsService.importChatflows(chatflows, orgId, workspaceId, subscriptionId)
return res.json(apiResponse)
} catch (error) {
next(error)
@@ -118,13 +178,27 @@ const importChatflows = async (req: Request, res: Response, next: NextFunction)
const updateChatflow = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params === 'undefined' || !req.params.id) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: chatflowsRouter.updateChatflow - id not provided!`)
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: chatflowsController.updateChatflow - id not provided!`)
}
const chatflow = await chatflowsService.getChatflowById(req.params.id)
if (!chatflow) {
return res.status(404).send(`Chatflow ${req.params.id} not found`)
}
const orgId = req.user?.activeOrganizationId
if (!orgId) {
throw new InternalFlowiseError(
StatusCodes.NOT_FOUND,
`Error: chatflowsController.saveChatflow - organization ${orgId} not found!`
)
}
const workspaceId = req.user?.activeWorkspaceId
if (!workspaceId) {
throw new InternalFlowiseError(
StatusCodes.NOT_FOUND,
`Error: chatflowsController.saveChatflow - workspace ${workspaceId} not found!`
)
}
const subscriptionId = req.user?.activeOrganizationSubscriptionId || ''
const body = req.body
const updateChatFlow = new ChatFlow()
Object.assign(updateChatFlow, body)
@@ -133,7 +207,7 @@ const updateChatflow = async (req: Request, res: Response, next: NextFunction) =
const rateLimiterManager = RateLimiterManager.getInstance()
await rateLimiterManager.updateRateLimiter(updateChatFlow)
const apiResponse = await chatflowsService.updateChatflow(chatflow, updateChatFlow)
const apiResponse = await chatflowsService.updateChatflow(chatflow, updateChatFlow, orgId, workspaceId, subscriptionId)
return res.json(apiResponse)
} catch (error) {
next(error)
@@ -145,7 +219,7 @@ const getSinglePublicChatflow = async (req: Request, res: Response, next: NextFu
if (typeof req.params === 'undefined' || !req.params.id) {
throw new InternalFlowiseError(
StatusCodes.PRECONDITION_FAILED,
`Error: chatflowsRouter.getSinglePublicChatflow - id not provided!`
`Error: chatflowsController.getSinglePublicChatflow - id not provided!`
)
}
const apiResponse = await chatflowsService.getSinglePublicChatflow(req.params.id)
@@ -160,7 +234,7 @@ const getSinglePublicChatbotConfig = async (req: Request, res: Response, next: N
if (typeof req.params === 'undefined' || !req.params.id) {
throw new InternalFlowiseError(
StatusCodes.PRECONDITION_FAILED,
`Error: chatflowsRouter.getSinglePublicChatbotConfig - id not provided!`
`Error: chatflowsController.getSinglePublicChatbotConfig - id not provided!`
)
}
const apiResponse = await chatflowsService.getSinglePublicChatbotConfig(req.params.id)
@@ -170,6 +244,27 @@ const getSinglePublicChatbotConfig = async (req: Request, res: Response, next: N
}
}
const checkIfChatflowHasChanged = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params === 'undefined' || !req.params.id) {
throw new InternalFlowiseError(
StatusCodes.PRECONDITION_FAILED,
`Error: chatflowsController.checkIfChatflowHasChanged - id not provided!`
)
}
if (!req.params.lastUpdatedDateTime) {
throw new InternalFlowiseError(
StatusCodes.PRECONDITION_FAILED,
`Error: chatflowsController.checkIfChatflowHasChanged - lastUpdatedDateTime not provided!`
)
}
const apiResponse = await chatflowsService.checkIfChatflowHasChanged(req.params.id, req.params.lastUpdatedDateTime)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
export default {
checkIfChatflowIsValidForStreaming,
checkIfChatflowIsValidForUploads,
@@ -181,5 +276,6 @@ export default {
importChatflows,
updateChatflow,
getSinglePublicChatflow,
getSinglePublicChatbotConfig
getSinglePublicChatbotConfig,
checkIfChatflowHasChanged
}
@@ -11,7 +11,9 @@ const createCredential = async (req: Request, res: Response, next: NextFunction)
`Error: credentialsController.createCredential - body not provided!`
)
}
const apiResponse = await credentialsService.createCredential(req.body)
const body = req.body
body.workspaceId = req.user?.activeWorkspaceId
const apiResponse = await credentialsService.createCredential(body)
return res.json(apiResponse)
} catch (error) {
next(error)
@@ -35,7 +37,7 @@ const deleteCredentials = async (req: Request, res: Response, next: NextFunction
const getAllCredentials = async (req: Request, res: Response, next: NextFunction) => {
try {
const apiResponse = await credentialsService.getAllCredentials(req.query.credentialName)
const apiResponse = await credentialsService.getAllCredentials(req.query.credentialName, req.user?.activeWorkspaceId)
return res.json(apiResponse)
} catch (error) {
next(error)
@@ -50,7 +52,7 @@ const getCredentialById = async (req: Request, res: Response, next: NextFunction
`Error: credentialsController.getCredentialById - id not provided!`
)
}
const apiResponse = await credentialsService.getCredentialById(req.params.id)
const apiResponse = await credentialsService.getCredentialById(req.params.id, req.user?.activeWorkspaceId)
return res.json(apiResponse)
} catch (error) {
next(error)
@@ -0,0 +1,143 @@
import { Request, Response, NextFunction } from 'express'
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
import datasetService from '../../services/dataset'
import { StatusCodes } from 'http-status-codes'
const getAllDatasets = async (req: Request, res: Response, next: NextFunction) => {
try {
const apiResponse = await datasetService.getAllDatasets(req.user?.activeWorkspaceId)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const getDataset = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params === 'undefined' || !req.params.id) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: datasetService.getDataset - id not provided!`)
}
const apiResponse = await datasetService.getDataset(req.params.id)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const createDataset = async (req: Request, res: Response, next: NextFunction) => {
try {
if (!req.body) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: datasetService.createDataset - body not provided!`)
}
const body = req.body
body.workspaceId = req.user?.activeWorkspaceId
const apiResponse = await datasetService.createDataset(body)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const updateDataset = async (req: Request, res: Response, next: NextFunction) => {
try {
if (!req.body) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: datasetService.updateDataset - body not provided!`)
}
if (typeof req.params === 'undefined' || !req.params.id) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: datasetService.updateDataset - id not provided!`)
}
const apiResponse = await datasetService.updateDataset(req.params.id, req.body)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const deleteDataset = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params === 'undefined' || !req.params.id) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: datasetService.deleteDataset - id not provided!`)
}
const apiResponse = await datasetService.deleteDataset(req.params.id)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const addDatasetRow = async (req: Request, res: Response, next: NextFunction) => {
try {
if (!req.body) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: datasetService.addDatasetRow - body not provided!`)
}
if (!req.body.datasetId) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: datasetService.addDatasetRow - datasetId not provided!`)
}
const apiResponse = await datasetService.addDatasetRow(req.body)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const updateDatasetRow = async (req: Request, res: Response, next: NextFunction) => {
try {
if (!req.body) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: datasetService.updateDatasetRow - body not provided!`)
}
if (typeof req.params === 'undefined' || !req.params.id) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: datasetService.updateDatasetRow - id not provided!`)
}
const apiResponse = await datasetService.updateDatasetRow(req.params.id, req.body)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const deleteDatasetRow = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params === 'undefined' || !req.params.id) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: datasetService.deleteDatasetRow - id not provided!`)
}
const apiResponse = await datasetService.deleteDatasetRow(req.params.id)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const patchDeleteRows = async (req: Request, res: Response, next: NextFunction) => {
try {
const ids = req.body.ids ?? []
const apiResponse = await datasetService.patchDeleteRows(ids)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const reorderDatasetRow = async (req: Request, res: Response, next: NextFunction) => {
try {
if (!req.body) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: datasetService.reorderDatasetRow - body not provided!`)
}
const apiResponse = await datasetService.reorderDatasetRow(req.body.datasetId, req.body.rows)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
export default {
getAllDatasets,
getDataset,
createDataset,
updateDataset,
deleteDataset,
addDatasetRow,
updateDatasetRow,
deleteDatasetRow,
patchDeleteRows,
reorderDatasetRow
}
@@ -15,9 +15,20 @@ const createDocumentStore = async (req: Request, res: Response, next: NextFuncti
`Error: documentStoreController.createDocumentStore - body not provided!`
)
}
const orgId = req.user?.activeOrganizationId
if (!orgId) {
throw new InternalFlowiseError(
StatusCodes.PRECONDITION_FAILED,
`Error: documentStoreController.createDocumentStore - organizationId not provided!`
)
}
const body = req.body
body.workspaceId = req.user?.activeWorkspaceId
const docStore = DocumentStoreDTO.toEntity(body)
const apiResponse = await documentStoreService.createDocumentStore(docStore)
const apiResponse = await documentStoreService.createDocumentStore(docStore, orgId)
return res.json(apiResponse)
} catch (error) {
next(error)
@@ -26,7 +37,7 @@ const createDocumentStore = async (req: Request, res: Response, next: NextFuncti
const getAllDocumentStores = async (req: Request, res: Response, next: NextFunction) => {
try {
const apiResponse = await documentStoreService.getAllDocumentStores()
const apiResponse = await documentStoreService.getAllDocumentStores(req.user?.activeWorkspaceId)
return res.json(DocumentStoreDTO.fromEntities(apiResponse))
} catch (error) {
next(error)
@@ -44,7 +55,29 @@ const deleteLoaderFromDocumentStore = async (req: Request, res: Response, next:
`Error: documentStoreController.deleteLoaderFromDocumentStore - missing storeId or loaderId.`
)
}
const apiResponse = await documentStoreService.deleteLoaderFromDocumentStore(storeId, loaderId)
const orgId = req.user?.activeOrganizationId
if (!orgId) {
throw new InternalFlowiseError(
StatusCodes.PRECONDITION_FAILED,
`Error: documentStoreController.createDocumentStore - organizationId not provided!`
)
}
const workspaceId = req.user?.activeWorkspaceId
if (!workspaceId) {
throw new InternalFlowiseError(
StatusCodes.PRECONDITION_FAILED,
`Error: documentStoreController.createDocumentStore - workspaceId not provided!`
)
}
const apiResponse = await documentStoreService.deleteLoaderFromDocumentStore(
storeId,
loaderId,
orgId,
workspaceId,
getRunningExpressApp().usageCacheManager
)
return res.json(DocumentStoreDTO.fromEntity(apiResponse))
} catch (error) {
next(error)
@@ -199,10 +232,33 @@ const processLoader = async (req: Request, res: Response, next: NextFunction) =>
`Error: documentStoreController.processLoader - body not provided!`
)
}
const orgId = req.user?.activeOrganizationId
if (!orgId) {
throw new InternalFlowiseError(
StatusCodes.PRECONDITION_FAILED,
`Error: documentStoreController.createDocumentStore - organizationId not provided!`
)
}
const workspaceId = req.user?.activeWorkspaceId
if (!workspaceId) {
throw new InternalFlowiseError(
StatusCodes.PRECONDITION_FAILED,
`Error: documentStoreController.createDocumentStore - workspaceId not provided!`
)
}
const subscriptionId = req.user?.activeOrganizationSubscriptionId || ''
const docLoaderId = req.params.loaderId
const body = req.body
const isInternalRequest = req.headers['x-request-from'] === 'internal'
const apiResponse = await documentStoreService.processLoaderMiddleware(body, docLoaderId, isInternalRequest)
const apiResponse = await documentStoreService.processLoaderMiddleware(
body,
docLoaderId,
orgId,
workspaceId,
subscriptionId,
getRunningExpressApp().usageCacheManager,
isInternalRequest
)
return res.json(apiResponse)
} catch (error) {
next(error)
@@ -248,7 +304,26 @@ const deleteDocumentStore = async (req: Request, res: Response, next: NextFuncti
`Error: documentStoreController.deleteDocumentStore - storeId not provided!`
)
}
const apiResponse = await documentStoreService.deleteDocumentStore(req.params.id)
const orgId = req.user?.activeOrganizationId
if (!orgId) {
throw new InternalFlowiseError(
StatusCodes.PRECONDITION_FAILED,
`Error: documentStoreController.createDocumentStore - organizationId not provided!`
)
}
const workspaceId = req.user?.activeWorkspaceId
if (!workspaceId) {
throw new InternalFlowiseError(
StatusCodes.PRECONDITION_FAILED,
`Error: documentStoreController.createDocumentStore - workspaceId not provided!`
)
}
const apiResponse = await documentStoreService.deleteDocumentStore(
req.params.id,
orgId,
workspaceId,
getRunningExpressApp().usageCacheManager
)
return res.json(apiResponse)
} catch (error) {
next(error)
@@ -263,9 +338,30 @@ const previewFileChunks = async (req: Request, res: Response, next: NextFunction
`Error: documentStoreController.previewFileChunks - body not provided!`
)
}
const orgId = req.user?.activeOrganizationId
if (!orgId) {
throw new InternalFlowiseError(
StatusCodes.PRECONDITION_FAILED,
`Error: documentStoreController.createDocumentStore - organizationId not provided!`
)
}
const workspaceId = req.user?.activeWorkspaceId
if (!workspaceId) {
throw new InternalFlowiseError(
StatusCodes.PRECONDITION_FAILED,
`Error: documentStoreController.createDocumentStore - workspaceId not provided!`
)
}
const subscriptionId = req.user?.activeOrganizationSubscriptionId || ''
const body = req.body
body.preview = true
const apiResponse = await documentStoreService.previewChunksMiddleware(body)
const apiResponse = await documentStoreService.previewChunksMiddleware(
body,
orgId,
workspaceId,
subscriptionId,
getRunningExpressApp().usageCacheManager
)
return res.json(apiResponse)
} catch (error) {
next(error)
@@ -286,8 +382,30 @@ const insertIntoVectorStore = async (req: Request, res: Response, next: NextFunc
if (typeof req.body === 'undefined') {
throw new Error('Error: documentStoreController.insertIntoVectorStore - body not provided!')
}
const orgId = req.user?.activeOrganizationId
if (!orgId) {
throw new InternalFlowiseError(
StatusCodes.PRECONDITION_FAILED,
`Error: documentStoreController.createDocumentStore - organizationId not provided!`
)
}
const workspaceId = req.user?.activeWorkspaceId
if (!workspaceId) {
throw new InternalFlowiseError(
StatusCodes.PRECONDITION_FAILED,
`Error: documentStoreController.createDocumentStore - workspaceId not provided!`
)
}
const subscriptionId = req.user?.activeOrganizationSubscriptionId || ''
const body = req.body
const apiResponse = await documentStoreService.insertIntoVectorStoreMiddleware(body)
const apiResponse = await documentStoreService.insertIntoVectorStoreMiddleware(
body,
false,
orgId,
workspaceId,
subscriptionId,
getRunningExpressApp().usageCacheManager
)
getRunningExpressApp().metricsProvider?.incrementCounter(FLOWISE_METRIC_COUNTERS.VECTORSTORE_UPSERT, {
status: FLOWISE_COUNTER_STATUS.SUCCESS
})
@@ -393,9 +511,32 @@ const upsertDocStoreMiddleware = async (req: Request, res: Response, next: NextF
if (typeof req.body === 'undefined') {
throw new Error('Error: documentStoreController.upsertDocStoreMiddleware - body not provided!')
}
const orgId = req.user?.activeOrganizationId
if (!orgId) {
throw new InternalFlowiseError(
StatusCodes.PRECONDITION_FAILED,
`Error: documentStoreController.createDocumentStore - organizationId not provided!`
)
}
const workspaceId = req.user?.activeWorkspaceId
if (!workspaceId) {
throw new InternalFlowiseError(
StatusCodes.PRECONDITION_FAILED,
`Error: documentStoreController.createDocumentStore - workspaceId not provided!`
)
}
const subscriptionId = req.user?.activeOrganizationSubscriptionId || ''
const body = req.body
const files = (req.files as Express.Multer.File[]) || []
const apiResponse = await documentStoreService.upsertDocStoreMiddleware(req.params.id, body, files)
const apiResponse = await documentStoreService.upsertDocStoreMiddleware(
req.params.id,
body,
files,
orgId,
workspaceId,
subscriptionId,
getRunningExpressApp().usageCacheManager
)
getRunningExpressApp().metricsProvider?.incrementCounter(FLOWISE_METRIC_COUNTERS.VECTORSTORE_UPSERT, {
status: FLOWISE_COUNTER_STATUS.SUCCESS
})
@@ -416,8 +557,30 @@ const refreshDocStoreMiddleware = async (req: Request, res: Response, next: Next
`Error: documentStoreController.refreshDocStoreMiddleware - storeId not provided!`
)
}
const orgId = req.user?.activeOrganizationId
if (!orgId) {
throw new InternalFlowiseError(
StatusCodes.PRECONDITION_FAILED,
`Error: documentStoreController.createDocumentStore - organizationId not provided!`
)
}
const workspaceId = req.user?.activeWorkspaceId
if (!workspaceId) {
throw new InternalFlowiseError(
StatusCodes.PRECONDITION_FAILED,
`Error: documentStoreController.createDocumentStore - workspaceId not provided!`
)
}
const subscriptionId = req.user?.activeOrganizationSubscriptionId || ''
const body = req.body
const apiResponse = await documentStoreService.refreshDocStoreMiddleware(req.params.id, body)
const apiResponse = await documentStoreService.refreshDocStoreMiddleware(
req.params.id,
body,
orgId,
workspaceId,
subscriptionId,
getRunningExpressApp().usageCacheManager
)
getRunningExpressApp().metricsProvider?.incrementCounter(FLOWISE_METRIC_COUNTERS.VECTORSTORE_UPSERT, {
status: FLOWISE_COUNTER_STATUS.SUCCESS
})
@@ -0,0 +1,135 @@
import { Request, Response, NextFunction } from 'express'
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
import { StatusCodes } from 'http-status-codes'
import evaluationsService from '../../services/evaluations'
const createEvaluation = async (req: Request, res: Response, next: NextFunction) => {
try {
if (!req.body) {
throw new InternalFlowiseError(
StatusCodes.PRECONDITION_FAILED,
`Error: evaluationsService.createEvaluation - body not provided!`
)
}
const orgId = req.user?.activeOrganizationId
if (!orgId) {
throw new InternalFlowiseError(
StatusCodes.NOT_FOUND,
`Error: evaluationsService.createEvaluation - organization ${orgId} not found!`
)
}
const workspaceId = req.user?.activeWorkspaceId
if (!workspaceId) {
throw new InternalFlowiseError(
StatusCodes.NOT_FOUND,
`Error: evaluationsService.createEvaluation - workspace ${workspaceId} not found!`
)
}
const body = req.body
body.workspaceId = workspaceId
const httpProtocol = req.get('x-forwarded-proto') || req.get('X-Forwarded-Proto') || req.protocol
const baseURL = `${httpProtocol}://${req.get('host')}`
const apiResponse = await evaluationsService.createEvaluation(body, baseURL, orgId)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const runAgain = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params === 'undefined' || !req.params.id) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: evaluationsService.runAgain - id not provided!`)
}
const orgId = req.user?.activeOrganizationId
if (!orgId) {
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Error: evaluationsService.runAgain - organization ${orgId} not found!`)
}
const httpProtocol = req.get('x-forwarded-proto') || req.get('X-Forwarded-Proto') || req.protocol
const baseURL = `${httpProtocol}://${req.get('host')}`
const apiResponse = await evaluationsService.runAgain(req.params.id, baseURL, orgId)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const getEvaluation = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params === 'undefined' || !req.params.id) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: evaluationsService.getEvaluation - id not provided!`)
}
const apiResponse = await evaluationsService.getEvaluation(req.params.id)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const deleteEvaluation = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params === 'undefined' || !req.params.id) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: evaluationsService.deleteEvaluation - id not provided!`)
}
const apiResponse = await evaluationsService.deleteEvaluation(req.params.id, req.user?.activeWorkspaceId)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const getAllEvaluations = async (req: Request, res: Response, next: NextFunction) => {
try {
const apiResponse = await evaluationsService.getAllEvaluations(req.user?.activeWorkspaceId)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const isOutdated = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params === 'undefined' || !req.params.id) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: evaluationsService.isOutdated - id not provided!`)
}
const apiResponse = await evaluationsService.isOutdated(req.params.id)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const getVersions = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params === 'undefined' || !req.params.id) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: evaluationsService.getVersions - id not provided!`)
}
const apiResponse = await evaluationsService.getVersions(req.params.id)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const patchDeleteEvaluations = async (req: Request, res: Response, next: NextFunction) => {
try {
const ids = req.body.ids ?? []
const isDeleteAllVersion = req.body.isDeleteAllVersion ?? false
const apiResponse = await evaluationsService.patchDeleteEvaluations(ids, isDeleteAllVersion, req.user?.activeWorkspaceId)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
export default {
createEvaluation,
getEvaluation,
deleteEvaluation,
getAllEvaluations,
isOutdated,
runAgain,
getVersions,
patchDeleteEvaluations
}
@@ -0,0 +1,74 @@
import { Request, Response, NextFunction } from 'express'
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
import { StatusCodes } from 'http-status-codes'
import evaluatorService from '../../services/evaluator'
const getAllEvaluators = async (req: Request, res: Response, next: NextFunction) => {
try {
const apiResponse = await evaluatorService.getAllEvaluators(req.user?.activeWorkspaceId)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const getEvaluator = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params === 'undefined' || !req.params.id) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: evaluatorService.getEvaluator - id not provided!`)
}
const apiResponse = await evaluatorService.getEvaluator(req.params.id)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const createEvaluator = async (req: Request, res: Response, next: NextFunction) => {
try {
if (!req.body) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: evaluatorService.createEvaluator - body not provided!`)
}
const body = req.body
body.workspaceId = req.user?.activeWorkspaceId
const apiResponse = await evaluatorService.createEvaluator(body)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const updateEvaluator = async (req: Request, res: Response, next: NextFunction) => {
try {
if (!req.body) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: evaluatorService.updateEvaluator - body not provided!`)
}
if (typeof req.params === 'undefined' || !req.params.id) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: evaluatorService.updateEvaluator - id not provided!`)
}
const apiResponse = await evaluatorService.updateEvaluator(req.params.id, req.body)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const deleteEvaluator = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params === 'undefined' || !req.params.id) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: evaluatorService.deleteEvaluator - id not provided!`)
}
const apiResponse = await evaluatorService.deleteEvaluator(req.params.id)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
export default {
getAllEvaluators,
getEvaluator,
createEvaluator,
updateEvaluator,
deleteEvaluator
}
@@ -5,7 +5,8 @@ import { ExecutionState } from '../../Interface'
const getExecutionById = async (req: Request, res: Response, next: NextFunction) => {
try {
const executionId = req.params.id
const execution = await executionsService.getExecutionById(executionId)
const workspaceId = req.user?.activeWorkspaceId
const execution = await executionsService.getExecutionById(executionId, workspaceId)
return res.json(execution)
} catch (error) {
next(error)
@@ -25,7 +26,8 @@ const getPublicExecutionById = async (req: Request, res: Response, next: NextFun
const updateExecution = async (req: Request, res: Response, next: NextFunction) => {
try {
const executionId = req.params.id
const execution = await executionsService.updateExecution(executionId, req.body)
const workspaceId = req.user?.activeWorkspaceId
const execution = await executionsService.updateExecution(executionId, req.body, workspaceId)
return res.json(execution)
} catch (error) {
next(error)
@@ -37,6 +39,9 @@ const getAllExecutions = async (req: Request, res: Response, next: NextFunction)
// Extract all possible filters from query params
const filters: any = {}
// Add workspace ID filter
filters.workspaceId = req.user?.activeWorkspaceId
// ID filter
if (req.query.id) filters.id = req.query.id as string
@@ -86,6 +91,7 @@ const getAllExecutions = async (req: Request, res: Response, next: NextFunction)
const deleteExecutions = async (req: Request, res: Response, next: NextFunction) => {
try {
let executionIds: string[] = []
const workspaceId = req.user?.activeWorkspaceId
// Check if we're deleting a single execution from URL param
if (req.params.id) {
@@ -98,7 +104,7 @@ const deleteExecutions = async (req: Request, res: Response, next: NextFunction)
return res.status(400).json({ success: false, message: 'No execution IDs provided' })
}
const result = await executionsService.deleteExecutions(executionIds)
const result = await executionsService.deleteExecutions(executionIds, workspaceId)
return res.json(result)
} catch (error) {
next(error)
@@ -1,9 +1,14 @@
import { NextFunction, Request, Response } from 'express'
import { StatusCodes } from 'http-status-codes'
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
import exportImportService from '../../services/export-import'
const exportData = async (req: Request, res: Response, next: NextFunction) => {
try {
const apiResponse = await exportImportService.exportData(exportImportService.convertExportInput(req.body))
const apiResponse = await exportImportService.exportData(
exportImportService.convertExportInput(req.body),
req.user?.activeWorkspaceId
)
return res.json(apiResponse)
} catch (error) {
next(error)
@@ -12,8 +17,28 @@ const exportData = async (req: Request, res: Response, next: NextFunction) => {
const importData = async (req: Request, res: Response, next: NextFunction) => {
try {
const orgId = req.user?.activeOrganizationId
if (!orgId) {
throw new InternalFlowiseError(
StatusCodes.NOT_FOUND,
`Error: exportImportController.importData - organization ${orgId} not found!`
)
}
const workspaceId = req.user?.activeWorkspaceId
if (!workspaceId) {
throw new InternalFlowiseError(
StatusCodes.NOT_FOUND,
`Error: exportImportController.importData - workspace ${workspaceId} not found!`
)
}
const subscriptionId = req.user?.activeOrganizationSubscriptionId || ''
const importData = req.body
await exportImportService.importData(importData)
if (!importData) {
throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, 'Error: exportImportController.importData - importData is required!')
}
await exportImportService.importData(importData, orgId, workspaceId, subscriptionId)
return res.json({ message: 'success' })
} catch (error) {
next(error)
@@ -0,0 +1,59 @@
import path from 'path'
import { NextFunction, Request, Response } from 'express'
import { getFilesListFromStorage, getStoragePath, removeSpecificFileFromStorage } from 'flowise-components'
import { updateStorageUsage } from '../../utils/quotaUsage'
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
import { StatusCodes } from 'http-status-codes'
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
const getAllFiles = async (req: Request, res: Response, next: NextFunction) => {
try {
const activeOrganizationId = req.user?.activeOrganizationId
if (!activeOrganizationId) {
throw new InternalFlowiseError(
StatusCodes.NOT_FOUND,
`Error: filesController.getAllFiles - organization ${activeOrganizationId} not found!`
)
}
const apiResponse = await getFilesListFromStorage(activeOrganizationId)
const filesList = apiResponse.map((file: any) => ({
...file,
// replace org id because we don't want to expose it
path: file.path.replace(getStoragePath(), '').replace(`${path.sep}${activeOrganizationId}${path.sep}`, '')
}))
return res.json(filesList)
} catch (error) {
next(error)
}
}
const deleteFile = async (req: Request, res: Response, next: NextFunction) => {
try {
const activeOrganizationId = req.user?.activeOrganizationId
if (!activeOrganizationId) {
throw new InternalFlowiseError(
StatusCodes.NOT_FOUND,
`Error: filesController.deleteFile - organization ${activeOrganizationId} not found!`
)
}
const activeWorkspaceId = req.user?.activeWorkspaceId
if (!activeWorkspaceId) {
throw new InternalFlowiseError(
StatusCodes.NOT_FOUND,
`Error: filesController.deleteFile - workspace ${activeWorkspaceId} not found!`
)
}
const filePath = req.query.path as string
const paths = filePath.split(path.sep).filter((path) => path !== '')
const { totalSize } = await removeSpecificFileFromStorage(activeOrganizationId, ...paths)
await updateStorageUsage(activeOrganizationId, activeWorkspaceId, totalSize, getRunningExpressApp().usageCacheManager)
return res.json({ message: 'file_deleted' })
} catch (error) {
next(error)
}
}
export default {
getAllFiles,
deleteFile
}
@@ -4,6 +4,9 @@ import contentDisposition from 'content-disposition'
import { streamStorageFile } from 'flowise-components'
import { StatusCodes } from 'http-status-codes'
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
import { ChatFlow } from '../../database/entities/ChatFlow'
import { Workspace } from '../../enterprise/database/entities/workspace.entity'
const streamUploadedFile = async (req: Request, res: Response, next: NextFunction) => {
try {
@@ -13,8 +16,27 @@ const streamUploadedFile = async (req: Request, res: Response, next: NextFunctio
const chatflowId = req.query.chatflowId as string
const chatId = req.query.chatId as string
const fileName = req.query.fileName as string
const appServer = getRunningExpressApp()
// This can be public API, so we can only get orgId from the chatflow
const chatflow = await appServer.AppDataSource.getRepository(ChatFlow).findOneBy({
id: chatflowId
})
if (!chatflow) {
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Chatflow ${chatflowId} not found`)
}
const chatflowWorkspaceId = chatflow.workspaceId
const workspace = await appServer.AppDataSource.getRepository(Workspace).findOneBy({
id: chatflowWorkspaceId
})
if (!workspace) {
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Workspace ${chatflowWorkspaceId} not found`)
}
const orgId = workspace.organizationId as string
res.setHeader('Content-Disposition', contentDisposition(fileName))
const fileStream = await streamStorageFile(chatflowId, chatId, fileName)
const fileStream = await streamStorageFile(chatflowId, chatId, fileName, orgId)
if (!fileStream) throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Error: streamStorageFile`)
@@ -0,0 +1,16 @@
import { Request, Response, NextFunction } from 'express'
import logService from '../../services/log'
// Get logs
const getLogs = async (req: Request, res: Response, next: NextFunction) => {
try {
const apiResponse = await logService.getLogs(req.query?.startDate as string, req.query?.endDate as string)
res.send(apiResponse)
} catch (error) {
next(error)
}
}
export default {
getLogs
}
@@ -30,7 +30,7 @@ const deleteCustomTemplate = async (req: Request, res: Response, next: NextFunct
const getAllCustomTemplates = async (req: Request, res: Response, next: NextFunction) => {
try {
const apiResponse = await marketplacesService.getAllCustomTemplates()
const apiResponse = await marketplacesService.getAllCustomTemplates(req.user?.activeWorkspaceId)
return res.json(apiResponse)
} catch (error) {
next(error)
@@ -45,7 +45,9 @@ const saveCustomTemplate = async (req: Request, res: Response, next: NextFunctio
`Error: marketplacesService.saveCustomTemplate - body not provided!`
)
}
const apiResponse = await marketplacesService.saveCustomTemplate(req.body)
const body = req.body
body.workspaceId = req.user?.activeWorkspaceId
const apiResponse = await marketplacesService.saveCustomTemplate(body)
return res.json(apiResponse)
} catch (error) {
next(error)
@@ -3,6 +3,7 @@ import _ from 'lodash'
import nodesService from '../../services/nodes'
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
import { StatusCodes } from 'http-status-codes'
import { getWorkspaceSearchOptionsFromReq } from '../../enterprise/utils/ControllerServiceUtils'
const getAllNodes = async (req: Request, res: Response, next: NextFunction) => {
try {
@@ -67,7 +68,9 @@ const getSingleNodeAsyncOptions = async (req: Request, res: Response, next: Next
`Error: nodesController.getSingleNodeAsyncOptions - name not provided!`
)
}
const apiResponse = await nodesService.getSingleNodeAsyncOptions(req.params.name, req.body)
const body = req.body
body.searchOptions = getWorkspaceSearchOptionsFromReq(req)
const apiResponse = await nodesService.getSingleNodeAsyncOptions(req.params.name, body)
return res.json(apiResponse)
} catch (error) {
next(error)
@@ -82,7 +85,8 @@ const executeCustomFunction = async (req: Request, res: Response, next: NextFunc
`Error: nodesController.executeCustomFunction - body not provided!`
)
}
const apiResponse = await nodesService.executeCustomFunction(req.body)
const orgId = req.user?.activeOrganizationId
const apiResponse = await nodesService.executeCustomFunction(req.body, orgId)
return res.json(apiResponse)
} catch (error) {
next(error)
@@ -5,6 +5,9 @@ import contentDisposition from 'content-disposition'
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
import { StatusCodes } from 'http-status-codes'
import { streamStorageFile } from 'flowise-components'
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
import { ChatFlow } from '../../database/entities/ChatFlow'
import { Workspace } from '../../enterprise/database/entities/workspace.entity'
// List available assistants
const getAllOpenaiAssistants = async (req: Request, res: Response, next: NextFunction) => {
@@ -50,11 +53,29 @@ const getFileFromAssistant = async (req: Request, res: Response, next: NextFunct
if (!req.body.chatflowId || !req.body.chatId || !req.body.fileName) {
return res.status(500).send(`Invalid file path`)
}
const appServer = getRunningExpressApp()
const chatflowId = req.body.chatflowId as string
const chatId = req.body.chatId as string
const fileName = req.body.fileName as string
// This can be public API, so we can only get orgId from the chatflow
const chatflow = await appServer.AppDataSource.getRepository(ChatFlow).findOneBy({
id: chatflowId
})
if (!chatflow) {
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Chatflow ${chatflowId} not found`)
}
const chatflowWorkspaceId = chatflow.workspaceId
const workspace = await appServer.AppDataSource.getRepository(Workspace).findOneBy({
id: chatflowWorkspaceId
})
if (!workspace) {
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Workspace ${chatflowWorkspaceId} not found`)
}
const orgId = workspace.organizationId as string
res.setHeader('Content-Disposition', contentDisposition(fileName))
const fileStream = await streamStorageFile(chatflowId, chatId, fileName)
const fileStream = await streamStorageFile(chatflowId, chatId, fileName, orgId)
if (!fileStream) throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Error: getFileFromAssistant`)
@@ -0,0 +1,81 @@
import { Request, Response, NextFunction } from 'express'
const getPricing = async (req: Request, res: Response, next: NextFunction) => {
try {
const PRODUCT_IDS = {
FREE: process.env.CLOUD_FREE_ID,
STARTER: process.env.CLOUD_STARTER_ID,
PRO: process.env.CLOUD_PRO_ID
}
const pricingPlans = [
{
prodId: PRODUCT_IDS.FREE,
title: 'Free',
subtitle: 'For trying out the platform',
price: '$0',
period: '/month',
features: [
{ text: '2 Flows & Assistants' },
{ text: '100 Predictions / month' },
{ text: '5MB Storage' },
{ text: 'Evaluations & Metrics' },
{ text: 'Custom Embedded Chatbot Branding' },
{ text: 'Community Support' }
]
},
{
prodId: PRODUCT_IDS.STARTER,
title: 'Starter',
subtitle: 'For individuals & small teams',
mostPopular: true,
price: '$35',
period: '/month',
features: [
{ text: 'Everything in Free plan, plus' },
{ text: 'Unlimited Flows & Assistants' },
{ text: '10,000 Predictions / month' },
{ text: '1GB Storage' },
{ text: 'Email Support' }
]
},
{
prodId: PRODUCT_IDS.PRO,
title: 'Pro',
subtitle: 'For medium-sized businesses',
price: '$65',
period: '/month',
features: [
{ text: 'Everything in Starter plan, plus' },
{ text: '50,000 Predictions / month' },
{ text: '10GB Storage' },
{ text: 'Unlimited Workspaces' },
{ text: '5 users', subtext: '+ $15/user/month' },
{ text: 'Admin Roles & Permissions' },
{ text: 'Priority Support' }
]
},
{
title: 'Enterprise',
subtitle: 'For large organizations',
price: 'Contact Us',
features: [
{ text: 'On-Premise Deployment' },
{ text: 'Air-gapped Environments' },
{ text: 'SSO & SAML' },
{ text: 'LDAP & RBAC' },
{ text: 'Versioning' },
{ text: 'Audit Logs' },
{ text: '99.99% Uptime SLA' },
{ text: 'Personalized Support' }
]
}
]
return res.status(200).json(pricingPlans)
} catch (error) {
next(error)
}
}
export default {
getPricing
}
@@ -0,0 +1,15 @@
import { Request, Response, NextFunction } from 'express'
import settingsService from '../../services/settings'
const getSettingsList = async (req: Request, res: Response, next: NextFunction) => {
try {
const apiResponse = await settingsService.getSettings()
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
export default {
getSettingsList
}
+14 -3
View File
@@ -1,4 +1,4 @@
import { Request, Response, NextFunction } from 'express'
import { NextFunction, Request, Response } from 'express'
import toolsService from '../../services/tools'
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
import { StatusCodes } from 'http-status-codes'
@@ -8,7 +8,18 @@ const createTool = async (req: Request, res: Response, next: NextFunction) => {
if (!req.body) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: toolsController.createTool - body not provided!`)
}
const apiResponse = await toolsService.createTool(req.body)
const orgId = req.user?.activeOrganizationId
if (!orgId) {
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Error: toolsController.createTool - organization ${orgId} not found!`)
}
const workspaceId = req.user?.activeWorkspaceId
if (!workspaceId) {
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Error: toolsController.createTool - workspace ${workspaceId} not found!`)
}
const body = req.body
body.workspaceId = workspaceId
const apiResponse = await toolsService.createTool(body, orgId)
return res.json(apiResponse)
} catch (error) {
next(error)
@@ -29,7 +40,7 @@ const deleteTool = async (req: Request, res: Response, next: NextFunction) => {
const getAllTools = async (req: Request, res: Response, next: NextFunction) => {
try {
const apiResponse = await toolsService.getAllTools()
const apiResponse = await toolsService.getAllTools(req.user?.activeWorkspaceId)
return res.json(apiResponse)
} catch (error) {
next(error)
@@ -12,7 +12,8 @@ const checkFlowValidation = async (req: Request, res: Response, next: NextFuncti
`Error: validationController.checkFlowValidation - id not provided!`
)
}
const apiResponse = await validationService.checkFlowValidation(flowId)
const workspaceId = req.user?.activeWorkspaceId
const apiResponse = await validationService.checkFlowValidation(flowId, workspaceId)
return res.json(apiResponse)
} catch (error) {
next(error)
@@ -12,10 +12,19 @@ const createVariable = async (req: Request, res: Response, next: NextFunction) =
`Error: variablesController.createVariable - body not provided!`
)
}
const orgId = req.user?.activeOrganizationId
if (!orgId) {
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Error: toolsController.createTool - organization ${orgId} not found!`)
}
const workspaceId = req.user?.activeWorkspaceId
if (!workspaceId) {
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Error: toolsController.createTool - workspace ${workspaceId} not found!`)
}
const body = req.body
body.workspaceId = workspaceId
const newVariable = new Variable()
Object.assign(newVariable, body)
const apiResponse = await variablesService.createVariable(newVariable)
const apiResponse = await variablesService.createVariable(newVariable, orgId)
return res.json(apiResponse)
} catch (error) {
next(error)
@@ -36,7 +45,7 @@ const deleteVariable = async (req: Request, res: Response, next: NextFunction) =
const getAllVariables = async (req: Request, res: Response, next: NextFunction) => {
try {
const apiResponse = await variablesService.getAllVariables()
const apiResponse = await variablesService.getAllVariables(req.user?.activeWorkspaceId)
return res.json(apiResponse)
} catch (error) {
next(error)
@@ -18,4 +18,7 @@ export class ApiKey implements IApiKey {
@Column({ type: 'timestamp' })
@UpdateDateColumn()
updatedDate: Date
@Column({ nullable: true, type: 'text' })
workspaceId?: string
}
@@ -26,4 +26,7 @@ export class Assistant implements IAssistant {
@Column({ type: 'timestamp' })
@UpdateDateColumn()
updatedDate: Date
@Column({ nullable: true, type: 'text' })
workspaceId?: string
}
@@ -50,4 +50,7 @@ export class ChatFlow implements IChatFlow {
@Column({ type: 'timestamp' })
@UpdateDateColumn()
updatedDate: Date
@Column({ nullable: true, type: 'text' })
workspaceId?: string
}
@@ -23,4 +23,7 @@ export class Credential implements ICredential {
@Column({ type: 'timestamp' })
@UpdateDateColumn()
updatedDate: Date
@Column({ nullable: true, type: 'text' })
workspaceId?: string
}
@@ -27,6 +27,9 @@ export class CustomTemplate implements ICustomTemplate {
@Column({ nullable: true, type: 'text' })
type?: string
@Column({ nullable: true, type: 'text' })
workspaceId: string
@Column({ type: 'timestamp' })
@CreateDateColumn()
createdDate: Date
@@ -0,0 +1,24 @@
/* eslint-disable */
import { Entity, Column, CreateDateColumn, UpdateDateColumn, PrimaryGeneratedColumn } from 'typeorm'
import { IAssistant, IDataset } from '../../Interface'
@Entity()
export class Dataset implements IDataset {
@PrimaryGeneratedColumn('uuid')
id: string
@Column({ type: 'text' })
name: string
@Column({ type: 'text' })
description: string
@CreateDateColumn()
createdDate: Date
@UpdateDateColumn()
updatedDate: Date
@Column({ nullable: true, type: 'text' })
workspaceId?: string
}
@@ -0,0 +1,25 @@
/* eslint-disable */
import { Entity, Column, CreateDateColumn, UpdateDateColumn, PrimaryGeneratedColumn, Index } from 'typeorm'
import { IAssistant, IDataset, IDatasetRow } from '../../Interface'
@Entity()
export class DatasetRow implements IDatasetRow {
@PrimaryGeneratedColumn('uuid')
id: string
@Column({ type: 'text' })
@Index()
datasetId: string
@Column({ type: 'text' })
input: string
@Column({ type: 'text' })
output: string
@UpdateDateColumn()
updatedDate: Date
@Column({ name: 'sequence_no' })
sequenceNo: number
}
@@ -37,4 +37,7 @@ export class DocumentStore implements IDocumentStore {
@Column({ nullable: true, type: 'text' })
recordManagerConfig: string | null
@Column({ nullable: true, type: 'text' })
workspaceId?: string
}
@@ -0,0 +1,41 @@
import { Column, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'
import { IEvaluation } from '../../Interface'
@Entity()
export class Evaluation implements IEvaluation {
@PrimaryGeneratedColumn('uuid')
id: string
@Column({ type: 'text' })
average_metrics: string
@Column({ type: 'text' })
additionalConfig: string
@Column()
name: string
@Column()
evaluationType: string
@Column()
chatflowId: string
@Column()
chatflowName: string
@Column()
datasetId: string
@Column()
datasetName: string
@Column()
status: string
@UpdateDateColumn()
runDate: Date
@Column({ nullable: true, type: 'text' })
workspaceId?: string
}
@@ -0,0 +1,35 @@
import { Column, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'
import { IEvaluationRun } from '../../Interface'
@Entity()
export class EvaluationRun implements IEvaluationRun {
@PrimaryGeneratedColumn('uuid')
id: string
@Column()
evaluationId: string
@Column({ type: 'text' })
input: string
@Column({ type: 'text' })
expectedOutput: string
@UpdateDateColumn()
runDate: Date
@Column({ type: 'text' })
actualOutput: string
@Column({ type: 'text' })
metrics: string
@Column({ type: 'text' })
llmEvaluators: string
@Column({ type: 'text' })
evaluators: string
@Column({ type: 'text' })
errors: string
}
@@ -0,0 +1,28 @@
import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'
import { IEvaluator } from '../../Interface'
//1714808591644
@Entity()
export class Evaluator implements IEvaluator {
@PrimaryGeneratedColumn('uuid')
id: string
@Column()
name: string
@Column()
type: string
@Column()
config: string
@CreateDateColumn()
createdDate: Date
@UpdateDateColumn()
updatedDate: Date
@Column({ nullable: true, type: 'text' })
workspaceId?: string
}
@@ -41,4 +41,7 @@ export class Execution implements IExecution {
@ManyToOne(() => ChatFlow)
@JoinColumn({ name: 'agentflowId' })
agentflow: ChatFlow
@Column({ nullable: true, type: 'text' })
workspaceId?: string
}
@@ -32,4 +32,7 @@ export class Tool implements ITool {
@Column({ type: 'timestamp' })
@UpdateDateColumn()
updatedDate: Date
@Column({ nullable: true, type: 'text' })
workspaceId?: string
}
@@ -23,4 +23,7 @@ export class Variable implements IVariable {
@Column({ type: 'timestamp' })
@UpdateDateColumn()
updatedDate: Date
@Column({ nullable: true, type: 'text' })
workspaceId?: string
}
+30 -2
View File
@@ -9,9 +9,22 @@ import { DocumentStore } from './DocumentStore'
import { DocumentStoreFileChunk } from './DocumentStoreFileChunk'
import { Lead } from './Lead'
import { UpsertHistory } from './UpsertHistory'
import { Dataset } from './Dataset'
import { DatasetRow } from './DatasetRow'
import { EvaluationRun } from './EvaluationRun'
import { Evaluation } from './Evaluation'
import { Evaluator } from './Evaluator'
import { ApiKey } from './ApiKey'
import { CustomTemplate } from './CustomTemplate'
import { Execution } from './Execution'
import { LoginActivity, WorkspaceShared, WorkspaceUsers } from '../../enterprise/database/entities/EnterpriseEntities'
import { User } from '../../enterprise/database/entities/user.entity'
import { Organization } from '../../enterprise/database/entities/organization.entity'
import { Role } from '../../enterprise/database/entities/role.entity'
import { OrganizationUser } from '../../enterprise/database/entities/organization-user.entity'
import { Workspace } from '../../enterprise/database/entities/workspace.entity'
import { WorkspaceUser } from '../../enterprise/database/entities/workspace-user.entity'
import { LoginMethod } from '../../enterprise/database/entities/login-method.entity'
export const entities = {
ChatFlow,
@@ -21,11 +34,26 @@ export const entities = {
Tool,
Assistant,
Variable,
UpsertHistory,
DocumentStore,
DocumentStoreFileChunk,
Lead,
UpsertHistory,
Dataset,
DatasetRow,
Evaluation,
EvaluationRun,
Evaluator,
ApiKey,
User,
WorkspaceUsers,
LoginActivity,
WorkspaceShared,
CustomTemplate,
Execution
Execution,
Organization,
Role,
OrganizationUser,
Workspace,
WorkspaceUser,
LoginMethod
}
@@ -0,0 +1,41 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddEvaluation1714548873039 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE IF NOT EXISTS \`evaluation\` (
\`id\` varchar(36) NOT NULL,
\`chatflowId\` LONGTEXT NOT NULL,
\`datasetId\` LONGTEXT NOT NULL,
\`name\` varchar(255) NOT NULL,
\`chatflowName\` varchar(255) NOT NULL,
\`datasetName\` varchar(255) NOT NULL,
\`additionalConfig\` LONGTEXT,
\`average_metrics\` LONGTEXT NOT NULL,
\`status\` varchar(10) NOT NULL,
\`evaluationType\` varchar(20) NOT NULL,
\`runDate\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
PRIMARY KEY (\`id\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;`
)
await queryRunner.query(
`CREATE TABLE IF NOT EXISTS \`evaluation_run\` (
\`id\` varchar(36) NOT NULL,
\`evaluationId\` varchar(36) NOT NULL,
\`expectedOutput\` LONGTEXT NOT NULL,
\`actualOutput\` LONGTEXT NOT NULL,
\`evaluators\` LONGTEXT,
\`input\` LONGTEXT DEFAULT NULL,
\`metrics\` TEXT DEFAULT NULL,
\`llmEvaluators\` TEXT DEFAULT NULL,
\`runDate\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
PRIMARY KEY (\`id\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;`
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE evaluation`)
await queryRunner.query(`DROP TABLE evaluation_run`)
}
}
@@ -0,0 +1,31 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddDatasets1714548903384 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE IF NOT EXISTS \`dataset\` (
\`id\` varchar(36) NOT NULL,
\`name\` varchar(255) NOT NULL,
\`description\` varchar(255) DEFAULT NULL,
\`createdDate\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
\`updatedDate\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
PRIMARY KEY (\`id\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;`
)
await queryRunner.query(
`CREATE TABLE IF NOT EXISTS \`dataset_row\` (
\`id\` varchar(36) NOT NULL,
\`datasetId\` varchar(36) NOT NULL,
\`input\` LONGTEXT NOT NULL,
\`output\` LONGTEXT DEFAULT NULL,
\`updatedDate\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
PRIMARY KEY (\`id\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;`
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE dataset`)
await queryRunner.query(`DROP TABLE dataset_row`)
}
}
@@ -0,0 +1,21 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddEvaluator1714808591644 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE IF NOT EXISTS \`evaluator\` (
\`id\` varchar(36) NOT NULL,
\`name\` varchar(255) NOT NULL,
\`type\` varchar(25) DEFAULT NULL,
\`config\` LONGTEXT DEFAULT NULL,
\`createdDate\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
\`updatedDate\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
PRIMARY KEY (\`id\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;`
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE evaluator`)
}
}
@@ -0,0 +1,12 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddSeqNoToDatasetRow1733752119696 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
const columnExists = await queryRunner.hasColumn('dataset_row', 'sequence_no')
if (!columnExists) queryRunner.query(`ALTER TABLE \`dataset_row\` ADD COLUMN \`sequence_no\` INT DEFAULT -1;`)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "dataset_row" DROP COLUMN "sequence_no";`)
}
}
@@ -0,0 +1,24 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
import { Assistant } from '../../entities/Assistant'
export class FixOpenSourceAssistantTable1743758056188 implements MigrationInterface {
name = 'FixOpenSourceAssistantTable1743758056188'
public async up(queryRunner: QueryRunner): Promise<void> {
const columnExists = await queryRunner.hasColumn('assistant', 'type')
if (!columnExists) {
await queryRunner.query(`ALTER TABLE \`assistant\` ADD COLUMN \`type\` TEXT;`)
await queryRunner.query(`UPDATE \`assistant\` SET \`type\` = 'OPENAI';`)
const assistants: Assistant[] = await queryRunner.query(`SELECT * FROM \`assistant\`;`)
for (let assistant of assistants) {
const details = JSON.parse(assistant.details)
if (!details?.id) await queryRunner.query(`UPDATE \`assistant\` SET \`type\` = 'CUSTOM' WHERE id = '${assistant.id}';`)
}
}
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE \`assistant\` DROP COLUMN \`type\`;`)
}
}
@@ -0,0 +1,12 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddErrorToEvaluationRun1744964560174 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
const columnExists = await queryRunner.hasColumn('evaluation_run', 'errors')
if (!columnExists) queryRunner.query(`ALTER TABLE \`evaluation_run\` ADD COLUMN \`errors\` LONGTEXT NULL DEFAULT '[]';`)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "evaluation_run" DROP COLUMN "errors";`)
}
}
@@ -0,0 +1,11 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class ModifyExecutionDataColumnType1747902489801 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
queryRunner.query(`ALTER TABLE \`execution\` MODIFY COLUMN \`executionData\` LONGTEXT NOT NULL;`)
}
public async down(queryRunner: QueryRunner): Promise<void> {
queryRunner.query(`ALTER TABLE \`execution\` MODIFY COLUMN \`executionData\` TEXT NOT NULL;`)
}
}
@@ -17,9 +17,12 @@ import { AddFeedback1707213626553 } from './1707213626553-AddFeedback'
import { AddUpsertHistoryEntity1709814301358 } from './1709814301358-AddUpsertHistoryEntity'
import { AddLead1710832127079 } from './1710832127079-AddLead'
import { AddLeadToChatMessage1711538023578 } from './1711538023578-AddLeadToChatMessage'
import { AddVectorStoreConfigToDocStore1715861032479 } from './1715861032479-AddVectorStoreConfigToDocStore'
import { AddDocumentStore1711637331047 } from './1711637331047-AddDocumentStore'
import { AddEvaluation1714548873039 } from './1714548873039-AddEvaluation'
import { AddDatasets1714548903384 } from './1714548903384-AddDataset'
import { AddAgentReasoningToChatMessage1714679514451 } from './1714679514451-AddAgentReasoningToChatMessage'
import { AddEvaluator1714808591644 } from './1714808591644-AddEvaluator'
import { AddVectorStoreConfigToDocStore1715861032479 } from './1715861032479-AddVectorStoreConfigToDocStore'
import { AddTypeToChatFlow1716300000000 } from './1716300000000-AddTypeToChatFlow'
import { AddApiKey1720230151480 } from './1720230151480-AddApiKey'
import { AddActionToChatMessage1721078251523 } from './1721078251523-AddActionToChatMessage'
@@ -28,7 +31,23 @@ import { AddCustomTemplate1725629836652 } from './1725629836652-AddCustomTemplat
import { AddArtifactsToChatMessage1726156258465 } from './1726156258465-AddArtifactsToChatMessage'
import { AddFollowUpPrompts1726666318346 } from './1726666318346-AddFollowUpPrompts'
import { AddTypeToAssistant1733011290987 } from './1733011290987-AddTypeToAssistant'
import { AddSeqNoToDatasetRow1733752119696 } from './1733752119696-AddSeqNoToDatasetRow'
import { AddExecutionEntity1738090872625 } from './1738090872625-AddExecutionEntity'
import { FixOpenSourceAssistantTable1743758056188 } from './1743758056188-FixOpenSourceAssistantTable'
import { AddErrorToEvaluationRun1744964560174 } from './1744964560174-AddErrorToEvaluationRun'
import { ModifyExecutionDataColumnType1747902489801 } from './1747902489801-ModifyExecutionDataColumnType'
import { AddAuthTables1720230151482 } from '../../../enterprise/database/migrations/mariadb/1720230151482-AddAuthTables'
import { AddWorkspace1725437498242 } from '../../../enterprise/database/migrations/mariadb/1725437498242-AddWorkspace'
import { AddWorkspaceShared1726654922034 } from '../../../enterprise/database/migrations/mariadb/1726654922034-AddWorkspaceShared'
import { AddWorkspaceIdToCustomTemplate1726655750383 } from '../../../enterprise/database/migrations/mariadb/1726655750383-AddWorkspaceIdToCustomTemplate'
import { AddOrganization1727798417345 } from '../../../enterprise/database/migrations/mariadb/1727798417345-AddOrganization'
import { LinkWorkspaceId1729130948686 } from '../../../enterprise/database/migrations/mariadb/1729130948686-LinkWorkspaceId'
import { LinkOrganizationId1729133111652 } from '../../../enterprise/database/migrations/mariadb/1729133111652-LinkOrganizationId'
import { AddSSOColumns1730519457880 } from '../../../enterprise/database/migrations/mariadb/1730519457880-AddSSOColumns'
import { AddPersonalWorkspace1734074497540 } from '../../../enterprise/database/migrations/mariadb/1734074497540-AddPersonalWorkspace'
import { RefactorEnterpriseDatabase1737076223692 } from '../../../enterprise/database/migrations/mariadb/1737076223692-RefactorEnterpriseDatabase'
import { ExecutionLinkWorkspaceId1746862866554 } from '../../../enterprise/database/migrations/mariadb/1746862866554-ExecutionLinkWorkspaceId'
export const mariadbMigrations = [
Init1693840429259,
@@ -51,15 +70,33 @@ export const mariadbMigrations = [
AddDocumentStore1711637331047,
AddLead1710832127079,
AddLeadToChatMessage1711538023578,
AddEvaluation1714548873039,
AddDatasets1714548903384,
AddAgentReasoningToChatMessage1714679514451,
AddTypeToChatFlow1716300000000,
AddEvaluator1714808591644,
AddVectorStoreConfigToDocStore1715861032479,
AddTypeToChatFlow1716300000000,
AddApiKey1720230151480,
AddActionToChatMessage1721078251523,
LongTextColumn1722301395521,
AddCustomTemplate1725629836652,
AddArtifactsToChatMessage1726156258465,
AddFollowUpPrompts1726666318346,
AddTypeToAssistant1733011290987,
AddExecutionEntity1738090872625
AddArtifactsToChatMessage1726156258465,
AddAuthTables1720230151482,
AddWorkspace1725437498242,
AddWorkspaceShared1726654922034,
AddWorkspaceIdToCustomTemplate1726655750383,
AddOrganization1727798417345,
LinkWorkspaceId1729130948686,
LinkOrganizationId1729133111652,
AddSSOColumns1730519457880,
AddSeqNoToDatasetRow1733752119696,
AddPersonalWorkspace1734074497540,
RefactorEnterpriseDatabase1737076223692,
AddExecutionEntity1738090872625,
FixOpenSourceAssistantTable1743758056188,
AddErrorToEvaluationRun1744964560174,
ExecutionLinkWorkspaceId1746862866554,
ModifyExecutionDataColumnType1747902489801
]
@@ -0,0 +1,41 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddEvaluation1714548873039 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE IF NOT EXISTS \`evaluation\` (
\`id\` varchar(36) NOT NULL,
\`chatflowId\` LONGTEXT NOT NULL,
\`datasetId\` LONGTEXT NOT NULL,
\`name\` varchar(255) NOT NULL,
\`chatflowName\` varchar(255) NOT NULL,
\`datasetName\` varchar(255) NOT NULL,
\`additionalConfig\` LONGTEXT,
\`average_metrics\` LONGTEXT NOT NULL,
\`status\` varchar(10) NOT NULL,
\`evaluationType\` varchar(20) NOT NULL,
\`runDate\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
PRIMARY KEY (\`id\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;`
)
await queryRunner.query(
`CREATE TABLE IF NOT EXISTS \`evaluation_run\` (
\`id\` varchar(36) NOT NULL,
\`evaluationId\` varchar(36) NOT NULL,
\`expectedOutput\` LONGTEXT NOT NULL,
\`actualOutput\` LONGTEXT NOT NULL,
\`evaluators\` LONGTEXT,
\`input\` LONGTEXT DEFAULT NULL,
\`metrics\` TEXT DEFAULT NULL,
\`llmEvaluators\` TEXT DEFAULT NULL,
\`runDate\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
PRIMARY KEY (\`id\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;`
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE evaluation`)
await queryRunner.query(`DROP TABLE evaluation_run`)
}
}
@@ -0,0 +1,31 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddDatasets1714548903384 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE IF NOT EXISTS \`dataset\` (
\`id\` varchar(36) NOT NULL,
\`name\` varchar(255) NOT NULL,
\`description\` varchar(255) DEFAULT NULL,
\`createdDate\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
\`updatedDate\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
PRIMARY KEY (\`id\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;`
)
await queryRunner.query(
`CREATE TABLE IF NOT EXISTS \`dataset_row\` (
\`id\` varchar(36) NOT NULL,
\`datasetId\` varchar(36) NOT NULL,
\`input\` LONGTEXT NOT NULL,
\`output\` LONGTEXT DEFAULT NULL,
\`updatedDate\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
PRIMARY KEY (\`id\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;`
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE dataset`)
await queryRunner.query(`DROP TABLE dataset_row`)
}
}
@@ -0,0 +1,21 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddEvaluator1714808591644 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE IF NOT EXISTS \`evaluator\` (
\`id\` varchar(36) NOT NULL,
\`name\` varchar(255) NOT NULL,
\`type\` varchar(25) DEFAULT NULL,
\`config\` LONGTEXT DEFAULT NULL,
\`createdDate\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
\`updatedDate\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
PRIMARY KEY (\`id\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;`
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE evaluator`)
}
}
@@ -0,0 +1,12 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddSeqNoToDatasetRow1733752119696 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
const columnExists = await queryRunner.hasColumn('dataset_row', 'sequence_no')
if (!columnExists) queryRunner.query(`ALTER TABLE \`dataset_row\` ADD COLUMN \`sequence_no\` INT DEFAULT -1;`)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "dataset_row" DROP COLUMN "sequence_no";`)
}
}
@@ -0,0 +1,24 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
import { Assistant } from '../../entities/Assistant'
export class FixOpenSourceAssistantTable1743758056188 implements MigrationInterface {
name = 'FixOpenSourceAssistantTable1743758056188'
public async up(queryRunner: QueryRunner): Promise<void> {
const columnExists = await queryRunner.hasColumn('assistant', 'type')
if (!columnExists) {
await queryRunner.query(`ALTER TABLE \`assistant\` ADD COLUMN \`type\` TEXT;`)
await queryRunner.query(`UPDATE \`assistant\` SET \`type\` = 'OPENAI';`)
const assistants: Assistant[] = await queryRunner.query(`SELECT * FROM \`assistant\`;`)
for (let assistant of assistants) {
const details = JSON.parse(assistant.details)
if (!details?.id) await queryRunner.query(`UPDATE \`assistant\` SET \`type\` = 'CUSTOM' WHERE id = '${assistant.id}';`)
}
}
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE \`assistant\` DROP COLUMN \`type\`;`)
}
}
@@ -0,0 +1,12 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddErrorToEvaluationRun1744964560174 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
const columnExists = await queryRunner.hasColumn('evaluation_run', 'errors')
if (!columnExists) queryRunner.query(`ALTER TABLE \`evaluation_run\` ADD COLUMN \`errors\` LONGTEXT NULL DEFAULT ('[]');`)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "evaluation_run" DROP COLUMN "errors";`)
}
}
@@ -0,0 +1,12 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class FixErrorsColumnInEvaluationRun1746437114935 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
const columnExists = await queryRunner.hasColumn('evaluation_run', 'errors')
if (!columnExists) queryRunner.query(`ALTER TABLE \`evaluation_run\` ADD COLUMN \`errors\` LONGTEXT NULL DEFAULT ('[]');`)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "evaluation_run" DROP COLUMN "errors";`)
}
}
@@ -0,0 +1,11 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class ModifyExecutionDataColumnType1747902489801 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
queryRunner.query(`ALTER TABLE \`execution\` MODIFY COLUMN \`executionData\` LONGTEXT NOT NULL;`)
}
public async down(queryRunner: QueryRunner): Promise<void> {
queryRunner.query(`ALTER TABLE \`execution\` MODIFY COLUMN \`executionData\` TEXT NOT NULL;`)
}
}
@@ -17,9 +17,12 @@ import { AddFeedback1707213626553 } from './1707213626553-AddFeedback'
import { AddUpsertHistoryEntity1709814301358 } from './1709814301358-AddUpsertHistoryEntity'
import { AddLead1710832127079 } from './1710832127079-AddLead'
import { AddLeadToChatMessage1711538023578 } from './1711538023578-AddLeadToChatMessage'
import { AddVectorStoreConfigToDocStore1715861032479 } from './1715861032479-AddVectorStoreConfigToDocStore'
import { AddDocumentStore1711637331047 } from './1711637331047-AddDocumentStore'
import { AddEvaluation1714548873039 } from './1714548873039-AddEvaluation'
import { AddDatasets1714548903384 } from './1714548903384-AddDataset'
import { AddAgentReasoningToChatMessage1714679514451 } from './1714679514451-AddAgentReasoningToChatMessage'
import { AddEvaluator1714808591644 } from './1714808591644-AddEvaluator'
import { AddVectorStoreConfigToDocStore1715861032479 } from './1715861032479-AddVectorStoreConfigToDocStore'
import { AddTypeToChatFlow1716300000000 } from './1716300000000-AddTypeToChatFlow'
import { AddApiKey1720230151480 } from './1720230151480-AddApiKey'
import { AddActionToChatMessage1721078251523 } from './1721078251523-AddActionToChatMessage'
@@ -28,7 +31,24 @@ import { AddCustomTemplate1725629836652 } from './1725629836652-AddCustomTemplat
import { AddArtifactsToChatMessage1726156258465 } from './1726156258465-AddArtifactsToChatMessage'
import { AddFollowUpPrompts1726666302024 } from './1726666302024-AddFollowUpPrompts'
import { AddTypeToAssistant1733011290987 } from './1733011290987-AddTypeToAssistant'
import { AddSeqNoToDatasetRow1733752119696 } from './1733752119696-AddSeqNoToDatasetRow'
import { AddExecutionEntity1738090872625 } from './1738090872625-AddExecutionEntity'
import { FixOpenSourceAssistantTable1743758056188 } from './1743758056188-FixOpenSourceAssistantTable'
import { AddErrorToEvaluationRun1744964560174 } from './1744964560174-AddErrorToEvaluationRun'
import { FixErrorsColumnInEvaluationRun1746437114935 } from './1746437114935-FixErrorsColumnInEvaluationRun'
import { ModifyExecutionDataColumnType1747902489801 } from './1747902489801-ModifyExecutionDataColumnType'
import { AddAuthTables1720230151482 } from '../../../enterprise/database/migrations/mysql/1720230151482-AddAuthTables'
import { AddWorkspace1720230151484 } from '../../../enterprise/database/migrations/mysql/1720230151484-AddWorkspace'
import { AddWorkspaceShared1726654922034 } from '../../../enterprise/database/migrations/mysql/1726654922034-AddWorkspaceShared'
import { AddWorkspaceIdToCustomTemplate1726655750383 } from '../../../enterprise/database/migrations/mysql/1726655750383-AddWorkspaceIdToCustomTemplate'
import { AddOrganization1727798417345 } from '../../../enterprise/database/migrations/mysql/1727798417345-AddOrganization'
import { LinkWorkspaceId1729130948686 } from '../../../enterprise/database/migrations/mysql/1729130948686-LinkWorkspaceId'
import { LinkOrganizationId1729133111652 } from '../../../enterprise/database/migrations/mysql/1729133111652-LinkOrganizationId'
import { AddSSOColumns1730519457880 } from '../../../enterprise/database/migrations/mysql/1730519457880-AddSSOColumns'
import { AddPersonalWorkspace1734074497540 } from '../../../enterprise/database/migrations/mysql/1734074497540-AddPersonalWorkspace'
import { RefactorEnterpriseDatabase1737076223692 } from '../../../enterprise/database/migrations/mysql/1737076223692-RefactorEnterpriseDatabase'
import { ExecutionLinkWorkspaceId1746862866554 } from '../../../enterprise/database/migrations/mysql/1746862866554-ExecutionLinkWorkspaceId'
export const mysqlMigrations = [
Init1693840429259,
@@ -48,12 +68,15 @@ export const mysqlMigrations = [
AddSpeechToText1706364937060,
AddUpsertHistoryEntity1709814301358,
AddFeedback1707213626553,
AddEvaluation1714548873039,
AddDatasets1714548903384,
AddEvaluator1714808591644,
AddDocumentStore1711637331047,
AddLead1710832127079,
AddLeadToChatMessage1711538023578,
AddAgentReasoningToChatMessage1714679514451,
AddTypeToChatFlow1716300000000,
AddVectorStoreConfigToDocStore1715861032479,
AddTypeToChatFlow1716300000000,
AddApiKey1720230151480,
AddActionToChatMessage1721078251523,
LongTextColumn1722301395521,
@@ -61,5 +84,21 @@ export const mysqlMigrations = [
AddArtifactsToChatMessage1726156258465,
AddFollowUpPrompts1726666302024,
AddTypeToAssistant1733011290987,
AddExecutionEntity1738090872625
AddAuthTables1720230151482,
AddWorkspace1720230151484,
AddWorkspaceShared1726654922034,
AddWorkspaceIdToCustomTemplate1726655750383,
AddOrganization1727798417345,
LinkWorkspaceId1729130948686,
LinkOrganizationId1729133111652,
AddSSOColumns1730519457880,
AddSeqNoToDatasetRow1733752119696,
AddPersonalWorkspace1734074497540,
RefactorEnterpriseDatabase1737076223692,
FixOpenSourceAssistantTable1743758056188,
AddExecutionEntity1738090872625,
AddErrorToEvaluationRun1744964560174,
FixErrorsColumnInEvaluationRun1746437114935,
ExecutionLinkWorkspaceId1746862866554,
ModifyExecutionDataColumnType1747902489801
]
@@ -0,0 +1,41 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddEvaluation1714548873039 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE IF NOT EXISTS evaluation (
id uuid NOT NULL DEFAULT uuid_generate_v4(),
"name" varchar NOT NULL,
"chatflowId" text NOT NULL,
"chatflowName" text NOT NULL,
"datasetId" varchar NOT NULL,
"datasetName" varchar NOT NULL,
"additionalConfig" text NULL,
"evaluationType" varchar NOT NULL,
"status" varchar NOT NULL,
"average_metrics" text NULL,
"runDate" timestamp NOT NULL DEFAULT now(),
CONSTRAINT "PK_98989043dd804f54-9830ab99f8" PRIMARY KEY (id)
);`
)
await queryRunner.query(
`CREATE TABLE IF NOT EXISTS evaluation_run (
id uuid NOT NULL DEFAULT uuid_generate_v4(),
"evaluationId" varchar NOT NULL,
"input" text NOT NULL,
"expectedOutput" text NULL,
"actualOutput" text NULL,
"evaluators" text NULL,
"llmEvaluators" text DEFAULT NULL,
"metrics" text NULL,
"runDate" timestamp NOT NULL DEFAULT now(),
CONSTRAINT "PK_98989927dd804f54-9840ab23f8" PRIMARY KEY (id)
);`
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE evaluation`)
await queryRunner.query(`DROP TABLE evaluation_run`)
}
}
@@ -0,0 +1,31 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddDatasets1714548903384 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE IF NOT EXISTS dataset (
id uuid NOT NULL DEFAULT uuid_generate_v4(),
"name" varchar NOT NULL,
"description" varchar NULL,
"createdDate" timestamp NOT NULL DEFAULT now(),
"updatedDate" timestamp NOT NULL DEFAULT now(),
CONSTRAINT "PK_98419043dd804f54-9830ab99f8" PRIMARY KEY (id)
);`
)
await queryRunner.query(
`CREATE TABLE IF NOT EXISTS dataset_row (
id uuid NOT NULL DEFAULT uuid_generate_v4(),
"datasetId" varchar NOT NULL,
"input" text NOT NULL,
"output" text NULL,
"updatedDate" timestamp NOT NULL DEFAULT now(),
CONSTRAINT "PK_98909027dd804f54-9840ab99f8" PRIMARY KEY (id)
);`
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE dataset`)
await queryRunner.query(`DROP TABLE dataset_row`)
}
}
@@ -0,0 +1,21 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddEvaluator1714808591644 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE IF NOT EXISTS evaluator (
id uuid NOT NULL DEFAULT uuid_generate_v4(),
"name" varchar NOT NULL,
"type" text NULL,
"config" text NULL,
"createdDate" timestamp NOT NULL DEFAULT now(),
"updatedDate" timestamp NOT NULL DEFAULT now(),
CONSTRAINT "PK_90019043dd804f54-9830ab11f8" PRIMARY KEY (id)
);`
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE evaluator`)
}
}
@@ -0,0 +1,11 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddSeqNoToDatasetRow1733752119696 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "dataset_row" ADD COLUMN IF NOT EXISTS "sequence_no" integer DEFAULT -1;`)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "dataset_row" DROP COLUMN "sequence_no";`)
}
}
@@ -0,0 +1,24 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
import { Assistant } from '../../entities/Assistant'
export class FixOpenSourceAssistantTable1743758056188 implements MigrationInterface {
name = 'FixOpenSourceAssistantTable1743758056188'
public async up(queryRunner: QueryRunner): Promise<void> {
const columnExists = await queryRunner.hasColumn('assistant', 'type')
if (!columnExists) {
await queryRunner.query(`ALTER TABLE "assistant" ADD COLUMN "type" TEXT;`)
await queryRunner.query(`UPDATE "assistant" SET "type" = 'OPENAI';`)
const assistants: Assistant[] = await queryRunner.query(`SELECT * FROM "assistant";`)
for (let assistant of assistants) {
const details = JSON.parse(assistant.details)
if (!details?.id) await queryRunner.query(`UPDATE "assistant" SET "type" = 'CUSTOM' WHERE id = '${assistant.id}';`)
}
}
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "assistant" DROP COLUMN "type";`)
}
}
@@ -0,0 +1,11 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddErrorToEvaluationRun1744964560174 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "evaluation_run" ADD COLUMN IF NOT EXISTS "errors" TEXT NULL DEFAULT '[]';`)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "evaluation_run" DROP COLUMN "errors";`)
}
}
@@ -18,9 +18,12 @@ import { AddUpsertHistoryEntity1709814301358 } from './1709814301358-AddUpsertHi
import { FieldTypes1710497452584 } from './1710497452584-FieldTypes'
import { AddLead1710832137905 } from './1710832137905-AddLead'
import { AddLeadToChatMessage1711538016098 } from './1711538016098-AddLeadToChatMessage'
import { AddVectorStoreConfigToDocStore1715861032479 } from './1715861032479-AddVectorStoreConfigToDocStore'
import { AddDocumentStore1711637331047 } from './1711637331047-AddDocumentStore'
import { AddEvaluation1714548873039 } from './1714548873039-AddEvaluation'
import { AddDatasets1714548903384 } from './1714548903384-AddDataset'
import { AddAgentReasoningToChatMessage1714679514451 } from './1714679514451-AddAgentReasoningToChatMessage'
import { AddEvaluator1714808591644 } from './1714808591644-AddEvaluator'
import { AddVectorStoreConfigToDocStore1715861032479 } from './1715861032479-AddVectorStoreConfigToDocStore'
import { AddTypeToChatFlow1716300000000 } from './1716300000000-AddTypeToChatFlow'
import { AddApiKey1720230151480 } from './1720230151480-AddApiKey'
import { AddActionToChatMessage1721078251523 } from './1721078251523-AddActionToChatMessage'
@@ -28,7 +31,22 @@ import { AddCustomTemplate1725629836652 } from './1725629836652-AddCustomTemplat
import { AddArtifactsToChatMessage1726156258465 } from './1726156258465-AddArtifactsToChatMessage'
import { AddFollowUpPrompts1726666309552 } from './1726666309552-AddFollowUpPrompts'
import { AddTypeToAssistant1733011290987 } from './1733011290987-AddTypeToAssistant'
import { AddSeqNoToDatasetRow1733752119696 } from './1733752119696-AddSeqNoToDatasetRow'
import { AddExecutionEntity1738090872625 } from './1738090872625-AddExecutionEntity'
import { FixOpenSourceAssistantTable1743758056188 } from './1743758056188-FixOpenSourceAssistantTable'
import { AddErrorToEvaluationRun1744964560174 } from './1744964560174-AddErrorToEvaluationRun'
import { AddAuthTables1720230151482 } from '../../../enterprise/database/migrations/postgres/1720230151482-AddAuthTables'
import { AddWorkspace1720230151484 } from '../../../enterprise/database/migrations/postgres/1720230151484-AddWorkspace'
import { AddWorkspaceShared1726654922034 } from '../../../enterprise/database/migrations/postgres/1726654922034-AddWorkspaceShared'
import { AddWorkspaceIdToCustomTemplate1726655750383 } from '../../../enterprise/database/migrations/postgres/1726655750383-AddWorkspaceIdToCustomTemplate'
import { AddOrganization1727798417345 } from '../../../enterprise/database/migrations/postgres/1727798417345-AddOrganization'
import { LinkWorkspaceId1729130948686 } from '../../../enterprise/database/migrations/postgres/1729130948686-LinkWorkspaceId'
import { LinkOrganizationId1729133111652 } from '../../../enterprise/database/migrations/postgres/1729133111652-LinkOrganizationId'
import { AddSSOColumns1730519457880 } from '../../../enterprise/database/migrations/postgres/1730519457880-AddSSOColumns'
import { AddPersonalWorkspace1734074497540 } from '../../../enterprise/database/migrations/postgres/1734074497540-AddPersonalWorkspace'
import { RefactorEnterpriseDatabase1737076223692 } from '../../../enterprise/database/migrations/postgres/1737076223692-RefactorEnterpriseDatabase'
import { ExecutionLinkWorkspaceId1746862866554 } from '../../../enterprise/database/migrations/postgres/1746862866554-ExecutionLinkWorkspaceId'
export const postgresMigrations = [
Init1693891895163,
@@ -49,17 +67,34 @@ export const postgresMigrations = [
AddUpsertHistoryEntity1709814301358,
AddFeedback1707213601923,
FieldTypes1710497452584,
AddEvaluation1714548873039,
AddDatasets1714548903384,
AddEvaluator1714808591644,
AddDocumentStore1711637331047,
AddLead1710832137905,
AddLeadToChatMessage1711538016098,
AddAgentReasoningToChatMessage1714679514451,
AddTypeToChatFlow1716300000000,
AddVectorStoreConfigToDocStore1715861032479,
AddTypeToChatFlow1716300000000,
AddApiKey1720230151480,
AddActionToChatMessage1721078251523,
AddCustomTemplate1725629836652,
AddArtifactsToChatMessage1726156258465,
AddFollowUpPrompts1726666309552,
AddTypeToAssistant1733011290987,
AddExecutionEntity1738090872625
AddAuthTables1720230151482,
AddWorkspace1720230151484,
AddWorkspaceShared1726654922034,
AddWorkspaceIdToCustomTemplate1726655750383,
AddOrganization1727798417345,
LinkWorkspaceId1729130948686,
LinkOrganizationId1729133111652,
AddSSOColumns1730519457880,
AddSeqNoToDatasetRow1733752119696,
AddPersonalWorkspace1734074497540,
RefactorEnterpriseDatabase1737076223692,
AddExecutionEntity1738090872625,
FixOpenSourceAssistantTable1743758056188,
AddErrorToEvaluationRun1744964560174,
ExecutionLinkWorkspaceId1746862866554
]
@@ -0,0 +1,37 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddEvaluation1714548873039 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE IF NOT EXISTS "evaluation" (
"id" varchar PRIMARY KEY NOT NULL,
"name" varchar NOT NULL,
"chatflowId" text NOT NULL,
"chatflowName" text NOT NULL,
"datasetId" varchar NOT NULL,
"datasetName" varchar NOT NULL,
"additionalConfig" text,
"status" varchar NOT NULL,
"evaluationType" varchar,
"average_metrics" text,
"runDate" datetime NOT NULL DEFAULT (datetime('now')));`
)
await queryRunner.query(
`CREATE TABLE IF NOT EXISTS "evaluation_run" (
"id" varchar PRIMARY KEY NOT NULL,
"evaluationId" text NOT NULL,
"input" text NOT NULL,
"expectedOutput" text NOT NULL,
"actualOutput" text NOT NULL,
"evaluators" text,
"llmEvaluators" TEXT DEFAULT NULL,
"metrics" text NULL,
"runDate" datetime NOT NULL DEFAULT (datetime('now')));`
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE evaluation`)
await queryRunner.query(`DROP TABLE evaluation_run`)
}
}
@@ -0,0 +1,25 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddDatasets1714548903384 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE IF NOT EXISTS "dataset" ("id" varchar PRIMARY KEY NOT NULL,
"name" text NOT NULL,
"description" varchar,
"createdDate" datetime NOT NULL DEFAULT (datetime('now')),
"updatedDate" datetime NOT NULL DEFAULT (datetime('now')));`
)
await queryRunner.query(
`CREATE TABLE IF NOT EXISTS "dataset_row" ("id" varchar PRIMARY KEY NOT NULL,
"datasetId" text NOT NULL,
"input" text NOT NULL,
"output" text NOT NULL,
"updatedDate" datetime NOT NULL DEFAULT (datetime('now')));`
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE dataset`)
await queryRunner.query(`DROP TABLE dataset_row`)
}
}
@@ -0,0 +1,18 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddEvaluator1714808591644 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE IF NOT EXISTS "evaluator" ("id" varchar PRIMARY KEY NOT NULL,
"name" text NOT NULL,
"type" varchar,
"config" text,
"createdDate" datetime NOT NULL DEFAULT (datetime('now')),
"updatedDate" datetime NOT NULL DEFAULT (datetime('now')));`
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE evaluator`)
}
}
@@ -0,0 +1,11 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddSeqNoToDatasetRow1733752119696 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "dataset_row" ADD COLUMN "sequence_no" integer DEFAULT -1;`)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "dataset_row" DROP COLUMN "sequence_no";`)
}
}
@@ -0,0 +1,28 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
import { Assistant } from '../../entities/Assistant'
export async function fixOpenSourceAssistantTable(queryRunner: QueryRunner): Promise<void> {
const columnExists = await queryRunner.hasColumn('assistant', 'type')
if (!columnExists) {
await queryRunner.query(`ALTER TABLE "assistant" ADD COLUMN "type" TEXT;`)
await queryRunner.query(`UPDATE "assistant" SET "type" = 'OPENAI';`)
const assistants: Assistant[] = await queryRunner.query(`SELECT * FROM "assistant";`)
for (let assistant of assistants) {
const details = JSON.parse(assistant.details)
if (!details?.id) await queryRunner.query(`UPDATE "assistant" SET "type" = 'CUSTOM' WHERE id = '${assistant.id}';`)
}
}
}
export class FixOpenSourceAssistantTable1743758056188 implements MigrationInterface {
name = 'FixOpenSourceAssistantTable1743758056188'
public async up(queryRunner: QueryRunner): Promise<void> {
await fixOpenSourceAssistantTable(queryRunner)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "assistant" DROP COLUMN "type";`)
}
}
@@ -0,0 +1,11 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddErrorToEvaluationRun1744964560174 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "evaluation_run" ADD COLUMN "errors" TEXT NULL DEFAULT '[]';`)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "evaluation_run" DROP COLUMN "errors";`)
}
}
@@ -17,17 +17,35 @@ import { AddFeedback1707213619308 } from './1707213619308-AddFeedback'
import { AddUpsertHistoryEntity1709814301358 } from './1709814301358-AddUpsertHistoryEntity'
import { AddLead1710832117612 } from './1710832117612-AddLead'
import { AddLeadToChatMessage1711537986113 } from './1711537986113-AddLeadToChatMessage'
import { AddVectorStoreConfigToDocStore1715861032479 } from './1715861032479-AddVectorStoreConfigToDocStore'
import { AddDocumentStore1711637331047 } from './1711637331047-AddDocumentStore'
import { AddEvaluation1714548873039 } from './1714548873039-AddEvaluation'
import { AddDatasets1714548903384 } from './1714548903384-AddDataset'
import { AddAgentReasoningToChatMessage1714679514451 } from './1714679514451-AddAgentReasoningToChatMessage'
import { AddEvaluator1714808591644 } from './1714808591644-AddEvaluator'
import { AddVectorStoreConfigToDocStore1715861032479 } from './1715861032479-AddVectorStoreConfigToDocStore'
import { AddTypeToChatFlow1716300000000 } from './1716300000000-AddTypeToChatFlow'
import { AddApiKey1720230151480 } from './1720230151480-AddApiKey'
import { AddActionToChatMessage1721078251523 } from './1721078251523-AddActionToChatMessage'
import { AddArtifactsToChatMessage1726156258465 } from './1726156258465-AddArtifactsToChatMessage'
import { AddCustomTemplate1725629836652 } from './1725629836652-AddCustomTemplate'
import { AddArtifactsToChatMessage1726156258465 } from './1726156258465-AddArtifactsToChatMessage'
import { AddFollowUpPrompts1726666294213 } from './1726666294213-AddFollowUpPrompts'
import { AddTypeToAssistant1733011290987 } from './1733011290987-AddTypeToAssistant'
import { AddSeqNoToDatasetRow1733752119696 } from './1733752119696-AddSeqNoToDatasetRow'
import { AddExecutionEntity1738090872625 } from './1738090872625-AddExecutionEntity'
import { FixOpenSourceAssistantTable1743758056188 } from './1743758056188-FixOpenSourceAssistantTable'
import { AddErrorToEvaluationRun1744964560174 } from './1744964560174-AddErrorToEvaluationRun'
import { AddAuthTables1720230151482 } from '../../../enterprise/database/migrations/sqlite/1720230151482-AddAuthTables'
import { AddWorkspace1720230151484 } from '../../../enterprise/database/migrations/sqlite/1720230151484-AddWorkspace'
import { AddWorkspaceShared1726654922034 } from '../../../enterprise/database/migrations/sqlite/1726654922034-AddWorkspaceShared'
import { AddWorkspaceIdToCustomTemplate1726655750383 } from '../../../enterprise/database/migrations/sqlite/1726655750383-AddWorkspaceIdToCustomTemplate'
import { AddOrganization1727798417345 } from '../../../enterprise/database/migrations/sqlite/1727798417345-AddOrganization'
import { LinkWorkspaceId1729130948686 } from '../../../enterprise/database/migrations/sqlite/1729130948686-LinkWorkspaceId'
import { LinkOrganizationId1729133111652 } from '../../../enterprise/database/migrations/sqlite/1729133111652-LinkOrganizationId'
import { AddSSOColumns1730519457880 } from '../../../enterprise/database/migrations/sqlite/1730519457880-AddSSOColumns'
import { AddPersonalWorkspace1734074497540 } from '../../../enterprise/database/migrations/sqlite/1734074497540-AddPersonalWorkspace'
import { RefactorEnterpriseDatabase1737076223692 } from '../../../enterprise/database/migrations/sqlite/1737076223692-RefactorEnterpriseDatabase'
import { ExecutionLinkWorkspaceId1746862866554 } from '../../../enterprise/database/migrations/sqlite/1746862866554-ExecutionLinkWorkspaceId'
export const sqliteMigrations = [
Init1693835579790,
@@ -46,18 +64,35 @@ export const sqliteMigrations = [
AddFileUploadsToChatMessage1701788586491,
AddSpeechToText1706364937060,
AddUpsertHistoryEntity1709814301358,
AddEvaluation1714548873039,
AddDatasets1714548903384,
AddEvaluator1714808591644,
AddFeedback1707213619308,
AddDocumentStore1711637331047,
AddLead1710832117612,
AddLeadToChatMessage1711537986113,
AddAgentReasoningToChatMessage1714679514451,
AddTypeToChatFlow1716300000000,
AddVectorStoreConfigToDocStore1715861032479,
AddTypeToChatFlow1716300000000,
AddApiKey1720230151480,
AddActionToChatMessage1721078251523,
AddArtifactsToChatMessage1726156258465,
AddCustomTemplate1725629836652,
AddFollowUpPrompts1726666294213,
AddTypeToAssistant1733011290987,
AddExecutionEntity1738090872625
AddCustomTemplate1725629836652,
AddAuthTables1720230151482,
AddWorkspace1720230151484,
AddWorkspaceShared1726654922034,
AddWorkspaceIdToCustomTemplate1726655750383,
AddOrganization1727798417345,
LinkWorkspaceId1729130948686,
LinkOrganizationId1729133111652,
AddSSOColumns1730519457880,
AddSeqNoToDatasetRow1733752119696,
AddPersonalWorkspace1734074497540,
RefactorEnterpriseDatabase1737076223692,
AddExecutionEntity1738090872625,
FixOpenSourceAssistantTable1743758056188,
AddErrorToEvaluationRun1744964560174,
ExecutionLinkWorkspaceId1746862866554
]
@@ -0,0 +1,133 @@
import { z } from 'zod'
export enum UserStatus {
INVITED = 'invited',
DISABLED = 'disabled',
ACTIVE = 'active'
}
export class IUser {
id: string
email: string
name: string
credential: string
status: UserStatus
tempToken: string
tokenExpiry?: Date
role: string
lastLogin: Date
activeWorkspaceId: string
isApiKeyValidated?: boolean
loginMode?: string
activeOrganizationId?: string
}
export interface IWorkspaceUser {
id: string
workspaceId: string
userId: string
role: string
}
export interface IWorkspaceShared {
id: string
workspaceId: string
sharedItemId: string
itemType: string
createdDate: Date
updatedDate: Date
}
export interface ILoginActivity {
id: string
username: string
activityCode: number
message: string
loginMode: string
attemptedDateTime: Date
}
export enum LoginActivityCode {
LOGIN_SUCCESS = 0,
LOGOUT_SUCCESS = 1,
UNKNOWN_USER = -1,
INCORRECT_CREDENTIAL = -2,
USER_DISABLED = -3,
NO_ASSIGNED_WORKSPACE = -4,
INVALID_LOGIN_MODE = -5,
REGISTRATION_PENDING = -6,
UNKNOWN_ERROR = -99
}
export type IAssignedWorkspace = { id: string; name: string; role: string; organizationId: string }
export type LoggedInUser = {
id: string
email: string
name: string
roleId: string
activeOrganizationId: string
activeOrganizationSubscriptionId: string
activeOrganizationCustomerId: string
activeOrganizationProductId: string
isOrganizationAdmin: boolean
activeWorkspaceId: string
activeWorkspace: string
assignedWorkspaces: IAssignedWorkspace[]
isApiKeyValidated: boolean
permissions?: string[]
features?: Record<string, string>
ssoRefreshToken?: string
ssoToken?: string
ssoProvider?: string
}
export enum ErrorMessage {
INVALID_MISSING_TOKEN = 'Invalid or Missing token',
TOKEN_EXPIRED = 'Token Expired',
REFRESH_TOKEN_EXPIRED = 'Refresh Token Expired',
FORBIDDEN = 'Forbidden',
UNKNOWN_USER = 'Unknown Username or Password',
INCORRECT_PASSWORD = 'Incorrect Password',
INACTIVE_USER = 'Inactive User',
INVITED_USER = 'User Invited, but has not registered',
INVALID_WORKSPACE = 'No Workspace Assigned',
UNKNOWN_ERROR = 'Unknown Error'
}
// IMPORTANT: update the schema on the client side as well
// packages/ui/src/views/organization/index.jsx
export const OrgSetupSchema = z
.object({
orgName: z.string().min(1, 'Organization name is required'),
username: z.string().min(1, 'Name is required'),
email: z.string().min(1, 'Email is required').email('Invalid email address'),
password: z
.string()
.min(8, 'Password must be at least 8 characters')
.regex(/[A-Z]/, 'Password must contain at least one uppercase letter')
.regex(/[!@#$%^&*]/, 'Password must contain at least one special character'),
confirmPassword: z.string().min(1, 'Confirm Password is required')
})
.refine((data) => data.password === data.confirmPassword, {
message: "Passwords don't match",
path: ['confirmPassword']
})
// IMPORTANT: when updating this schema, update the schema on the server as well
// packages/ui/src/views/auth/register.jsx
export const RegisterUserSchema = z
.object({
username: z.string().min(1, 'Name is required'),
email: z.string().min(1, 'Email is required').email('Invalid email address'),
password: z
.string()
.min(8, 'Password must be at least 8 characters')
.regex(/[A-Z]/, 'Password must contain at least one uppercase letter')
.regex(/[!@#$%^&*]/, 'Password must contain at least one special character'),
confirmPassword: z.string().min(1, 'Confirm Password is required'),
token: z.string().min(1, 'Invite Code is required')
})
.refine((data) => data.password === data.confirmPassword, {
message: "Passwords don't match",
path: ['confirmPassword']
})
+46
View File
@@ -0,0 +1,46 @@
The FlowiseAI Inc Commercial License (the "Commercial License")
Copyright (c) 2023-present FlowiseAI, Inc
With regard to the FlowiseAI Inc Software:
This software and associated documentation files (the "Software") may only be
used in production, if you (and any entity that you represent) have agreed to,
and are in compliance with, the FlowiseAI Inc Subscription Terms available
at https://flowiseai.com/terms, or other agreements governing
the use of the Software, as mutually agreed by you and FlowiseAI Inc, Inc ("FlowiseAI"),
and otherwise have a valid FlowiseAI Inc Enterprise Edition subscription ("Commercial Subscription")
for the correct number of hosts as defined in the "Commercial Terms ("Hosts"). Subject to the foregoing sentence,
you are free to modify this Software and publish patches to the Software. You agree
that FlowiseAI Inc and/or its licensors (as applicable) retain all right, title and interest in
and to all such modifications and/or patches, and all such modifications and/or
patches may only be used, copied, modified, displayed, distributed, or otherwise
exploited with a valid Commercial Subscription for the correct number of hosts.
Notwithstanding the foregoing, you may copy and modify the Software for development
and testing purposes, without requiring a subscription. You agree that FlowiseAI Inc and/or
its licensors (as applicable) retain all right, title and interest in and to all such
modifications. You are not granted any other rights beyond what is expressly stated herein.
Subject to the foregoing, it is forbidden to copy, merge, publish, distribute, sublicense,
and/or sell the Software.
This Commercial License applies only to the part of this Software that is not distributed under
the Apache 2.0 license. The Open Source version of Flowise is licensed under the Apache License, Version 2.0.
Unauthorized copying, modification, distribution, or use of the Enterprise and Cloud versions
is strictly prohibited without a valid license agreement from FlowiseAI, Inc.
For information about licensing of the Enterprise and Cloud versions, please contact:
security@flowiseai.com
The full text of this Commercial License shall
be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
For all third party components incorporated into the FlowiseAI Inc Software, those
components are licensed under the original license provided by the owner of the
applicable component.
@@ -0,0 +1,161 @@
import { Request, Response, NextFunction } from 'express'
import { StatusCodes } from 'http-status-codes'
import { AccountService } from '../services/account.service'
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
import axios from 'axios'
export class AccountController {
public async register(req: Request, res: Response, next: NextFunction) {
try {
const accountService = new AccountService()
const data = await accountService.register(req.body)
return res.status(StatusCodes.CREATED).json(data)
} catch (error) {
next(error)
}
}
public async invite(req: Request, res: Response, next: NextFunction) {
try {
const accountService = new AccountService()
const data = await accountService.invite(req.body, req.user)
return res.status(StatusCodes.CREATED).json(data)
} catch (error) {
next(error)
}
}
public async login(req: Request, res: Response, next: NextFunction) {
try {
const accountService = new AccountService()
const data = await accountService.login(req.body)
return res.status(StatusCodes.CREATED).json(data)
} catch (error) {
next(error)
}
}
public async verify(req: Request, res: Response, next: NextFunction) {
try {
const accountService = new AccountService()
const data = await accountService.verify(req.body)
return res.status(StatusCodes.CREATED).json(data)
} catch (error) {
next(error)
}
}
public async resendVerificationEmail(req: Request, res: Response, next: NextFunction) {
try {
const accountService = new AccountService()
const data = await accountService.resendVerificationEmail(req.body)
return res.status(StatusCodes.CREATED).json(data)
} catch (error) {
next(error)
}
}
public async forgotPassword(req: Request, res: Response, next: NextFunction) {
try {
const accountService = new AccountService()
const data = await accountService.forgotPassword(req.body)
return res.status(StatusCodes.CREATED).json(data)
} catch (error) {
next(error)
}
}
public async resetPassword(req: Request, res: Response, next: NextFunction) {
try {
const accountService = new AccountService()
const data = await accountService.resetPassword(req.body)
return res.status(StatusCodes.CREATED).json(data)
} catch (error) {
next(error)
}
}
public async createStripeCustomerPortalSession(req: Request, res: Response, next: NextFunction) {
try {
const { url: portalSessionUrl } = await getRunningExpressApp().identityManager.createStripeCustomerPortalSession(req)
return res.status(StatusCodes.OK).json({ url: portalSessionUrl })
} catch (error) {
next(error)
}
}
public async cancelPreviousCloudSubscrption(req: Request, res: Response, next: NextFunction) {
try {
const { email } = req.body
if (!email) {
return res.status(StatusCodes.BAD_REQUEST).json({ message: 'Email is required' })
}
const headers = {
'Content-Type': 'application/json',
Accept: 'application/json'
}
const response = await axios.post(`${process.env.ENGINE_URL}/cancel-subscription`, { email }, { headers })
if (response.status === 200) {
return res.status(StatusCodes.OK).json(response.data)
} else {
return res.status(response.status).json(response.data)
}
} catch (error) {
next(error)
}
}
public async logout(req: Request, res: Response, next: NextFunction) {
try {
if (req.user) {
const accountService = new AccountService()
await accountService.logout(req.user)
if (req.isAuthenticated()) {
req.logout((err) => {
if (err) {
return res.status(500).json({ message: 'Logout failed' })
}
req.session.destroy((err) => {
if (err) {
return res.status(500).json({ message: 'Failed to destroy session' })
}
})
})
} else {
// For JWT-based users (owner, org_admin)
res.clearCookie('connect.sid') // Clear the session cookie
res.clearCookie('token') // Clear the JWT cookie
res.clearCookie('refreshToken') // Clear the JWT cookie
return res.redirect('/login') // Redirect to the login page
}
}
return res.status(200).json({ message: 'logged_out', redirectTo: `/login` })
} catch (error) {
next(error)
}
}
public async getBasicAuth(req: Request, res: Response) {
if (process.env.FLOWISE_USERNAME && process.env.FLOWISE_PASSWORD) {
return res.status(StatusCodes.OK).json({
isUsernamePasswordSet: true
})
} else {
return res.status(StatusCodes.OK).json({
isUsernamePasswordSet: false
})
}
}
public async checkBasicAuth(req: Request, res: Response) {
const { username, password } = req.body
if (username === process.env.FLOWISE_USERNAME && password === process.env.FLOWISE_PASSWORD) {
return res.json({ message: 'Authentication successful' })
} else {
return res.json({ message: 'Authentication failed' })
}
}
}
@@ -0,0 +1,33 @@
import { NextFunction, Request, Response } from 'express'
import auditService from '../../services/audit'
import { InternalFlowiseError } from '../../../errors/internalFlowiseError'
import { StatusCodes } from 'http-status-codes'
const fetchLoginActivity = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.body === 'undefined') {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: auditService.fetchLoginHistory - body not provided!`)
}
const apiResponse = await auditService.fetchLoginActivity(req.body)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const deleteLoginActivity = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.body === 'undefined') {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: auditService.deleteLoginHistory - body not provided!`)
}
const apiResponse = await auditService.deleteLoginActivity(req.body)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
export default {
fetchLoginActivity,
deleteLoginActivity
}
@@ -0,0 +1,15 @@
import { NextFunction, Request, Response } from 'express'
import { getRunningExpressApp } from '../../../utils/getRunningExpressApp'
const getAllPermissions = async (req: Request, res: Response, next: NextFunction) => {
try {
const appServer = getRunningExpressApp()
return res.json(appServer.identityManager.getPermissions())
} catch (error) {
next(error)
}
}
export default {
getAllPermissions
}
@@ -0,0 +1,142 @@
import { NextFunction, Request, Response } from 'express'
import { StatusCodes } from 'http-status-codes'
import { LoginMethodErrorMessage, LoginMethodService } from '../services/login-method.service'
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
import { LoginMethod, LoginMethodStatus } from '../database/entities/login-method.entity'
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
import { decrypt } from '../utils/encryption.util'
import AzureSSO from '../sso/AzureSSO'
import GoogleSSO from '../sso/GoogleSSO'
import Auth0SSO from '../sso/Auth0SSO'
import { OrganizationService } from '../services/organization.service'
import { Platform } from '../../Interface'
import GithubSSO from '../sso/GithubSSO'
export class LoginMethodController {
public async create(req: Request, res: Response, next: NextFunction) {
try {
const loginMethodService = new LoginMethodService()
const loginMethod = await loginMethodService.createLoginMethod(req.body)
return res.status(StatusCodes.CREATED).json(loginMethod)
} catch (error) {
next(error)
}
}
public async defaultMethods(req: Request, res: Response, next: NextFunction) {
let queryRunner
try {
queryRunner = getRunningExpressApp().AppDataSource.createQueryRunner()
await queryRunner.connect()
let organizationId
if (getRunningExpressApp().identityManager.getPlatformType() === Platform.CLOUD) {
organizationId = undefined
} else if (getRunningExpressApp().identityManager.getPlatformType() === Platform.ENTERPRISE) {
const organizationService = new OrganizationService()
const organizations = await organizationService.readOrganization(queryRunner)
if (organizations.length > 0) {
organizationId = organizations[0].id
} else {
return res.status(StatusCodes.OK).json({})
}
} else {
return res.status(StatusCodes.OK).json({})
}
const loginMethodService = new LoginMethodService()
const providers: string[] = []
let loginMethod = await loginMethodService.readLoginMethodByOrganizationId(organizationId, queryRunner)
if (loginMethod) {
for (let method of loginMethod) {
if (method.status === LoginMethodStatus.ENABLE) providers.push(method.name)
}
}
return res.status(StatusCodes.OK).json({ providers: providers })
} catch (error) {
next(error)
} finally {
if (queryRunner) await queryRunner.release()
}
}
public async read(req: Request, res: Response, next: NextFunction) {
let queryRunner
try {
queryRunner = getRunningExpressApp().AppDataSource.createQueryRunner()
await queryRunner.connect()
const query = req.query as Partial<LoginMethod>
const loginMethodService = new LoginMethodService()
const loginMethodConfig = {
providers: [],
callbacks: [
{ providerName: 'azure', callbackURL: AzureSSO.getCallbackURL() },
{ providerName: 'google', callbackURL: GoogleSSO.getCallbackURL() },
{ providerName: 'auth0', callbackURL: Auth0SSO.getCallbackURL() },
{ providerName: 'github', callbackURL: GithubSSO.getCallbackURL() }
]
}
let loginMethod: any
if (query.id) {
loginMethod = await loginMethodService.readLoginMethodById(query.id, queryRunner)
if (!loginMethod) throw new InternalFlowiseError(StatusCodes.NOT_FOUND, LoginMethodErrorMessage.LOGIN_METHOD_NOT_FOUND)
loginMethod.config = JSON.parse(await decrypt(loginMethod.config))
} else {
loginMethod = await loginMethodService.readLoginMethodByOrganizationId(query.organizationId, queryRunner)
for (let method of loginMethod) {
method.config = JSON.parse(await decrypt(method.config))
}
loginMethodConfig.providers = loginMethod
}
return res.status(StatusCodes.OK).json(loginMethodConfig)
} catch (error) {
next(error)
} finally {
if (queryRunner) await queryRunner.release()
}
}
public async update(req: Request, res: Response, next: NextFunction) {
try {
const loginMethodService = new LoginMethodService()
const loginMethod = await loginMethodService.createOrUpdateConfig(req.body)
if (loginMethod?.status === 'OK' && loginMethod?.organizationId) {
const appServer = getRunningExpressApp()
let providers: any[] = req.body.providers
providers.map((provider: any) => {
const identityManager = appServer.identityManager
if (provider.config.clientID) {
provider.config.configEnabled = provider.status === LoginMethodStatus.ENABLE
identityManager.initializeSsoProvider(appServer.app, provider.providerName, provider.config)
}
})
}
return res.status(StatusCodes.OK).json(loginMethod)
} catch (error) {
next(error)
}
}
public async testConfig(req: Request, res: Response, next: NextFunction) {
try {
const providers = req.body.providers
if (req.body.providerName === 'azure') {
const response = await AzureSSO.testSetup(providers[0].config)
return res.json(response)
} else if (req.body.providerName === 'google') {
const response = await GoogleSSO.testSetup(providers[0].config)
return res.json(response)
} else if (req.body.providerName === 'auth0') {
const response = await Auth0SSO.testSetup(providers[0].config)
return res.json(response)
} else if (req.body.providerName === 'github') {
const response = await GithubSSO.testSetup(providers[0].config)
return res.json(response)
} else {
return res.json({ error: 'Provider not supported' })
}
} catch (error) {
next(error)
}
}
}
@@ -0,0 +1,146 @@
import { NextFunction, Request, Response } from 'express'
import { StatusCodes } from 'http-status-codes'
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
import { GeneralErrorMessage } from '../../utils/constants'
import { checkUsageLimit } from '../../utils/quotaUsage'
import { OrganizationUser } from '../database/entities/organization-user.entity'
import { Organization } from '../database/entities/organization.entity'
type OrganizationUserQuery = Partial<Pick<OrganizationUser, 'organizationId' | 'userId' | 'roleId'>>
import { QueryRunner } from 'typeorm'
import { Platform } from '../../Interface'
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
import { GeneralRole } from '../database/entities/role.entity'
import { User, UserStatus } from '../database/entities/user.entity'
import { WorkspaceUser } from '../database/entities/workspace-user.entity'
import { OrganizationUserService } from '../services/organization-user.service'
import { RoleService } from '../services/role.service'
import { WorkspaceService } from '../services/workspace.service'
export class OrganizationUserController {
public async create(req: Request, res: Response, next: NextFunction) {
try {
const organizationUserservice = new OrganizationUserService()
const totalOrgUsers = await organizationUserservice.readOrgUsersCountByOrgId(req.body.organizationId)
const subscriptionId = req.user?.activeOrganizationSubscriptionId || ''
await checkUsageLimit('users', subscriptionId, getRunningExpressApp().usageCacheManager, totalOrgUsers + 1)
const newOrganizationUser = await organizationUserservice.createOrganizationUser(req.body)
return res.status(StatusCodes.CREATED).json(newOrganizationUser)
} catch (error) {
next(error)
}
}
public async read(req: Request, res: Response, next: NextFunction) {
let queryRunner
try {
queryRunner = getRunningExpressApp().AppDataSource.createQueryRunner()
await queryRunner.connect()
const query = req.query as OrganizationUserQuery
const organizationUserservice = new OrganizationUserService()
let organizationUser:
| {
organization: Organization
organizationUser: OrganizationUser | null
}
| OrganizationUser
| null
| OrganizationUser[]
| (OrganizationUser & {
roleCount: number
})[]
if (query.organizationId && query.userId) {
organizationUser = await organizationUserservice.readOrganizationUserByOrganizationIdUserId(
query.organizationId,
query.userId,
queryRunner
)
} else if (query.organizationId && query.roleId) {
organizationUser = await organizationUserservice.readOrganizationUserByOrganizationIdRoleId(
query.organizationId,
query.roleId,
queryRunner
)
} else if (query.organizationId) {
organizationUser = await organizationUserservice.readOrganizationUserByOrganizationId(query.organizationId, queryRunner)
} else if (query.userId) {
organizationUser = await organizationUserservice.readOrganizationUserByUserId(query.userId, queryRunner)
} else {
throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, GeneralErrorMessage.UNHANDLED_EDGE_CASE)
}
return res.status(StatusCodes.OK).json(organizationUser)
} catch (error) {
next(error)
} finally {
if (queryRunner) await queryRunner.release()
}
}
public async update(req: Request, res: Response, next: NextFunction) {
try {
const organizationUserService = new OrganizationUserService()
const organizationUser = await organizationUserService.updateOrganizationUser(req.body)
return res.status(StatusCodes.OK).json(organizationUser)
} catch (error) {
next(error)
}
}
public async delete(req: Request, res: Response, next: NextFunction) {
let queryRunner: QueryRunner | undefined
try {
queryRunner = getRunningExpressApp().AppDataSource.createQueryRunner()
const currentPlatform = getRunningExpressApp().identityManager.getPlatformType()
await queryRunner.connect()
const query = req.query as Partial<OrganizationUser>
if (!query.organizationId) {
throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, 'Organization ID is required')
}
if (!query.userId) {
throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, 'User ID is required')
}
const organizationUserService = new OrganizationUserService()
const workspaceService = new WorkspaceService()
const roleService = new RoleService()
let organizationUser: OrganizationUser
await queryRunner.startTransaction()
if (currentPlatform === Platform.ENTERPRISE) {
const personalRole = await roleService.readGeneralRoleByName(GeneralRole.PERSONAL_WORKSPACE, queryRunner)
const personalWorkspaces = await queryRunner.manager.findBy(WorkspaceUser, {
userId: query.userId,
roleId: personalRole.id
})
if (personalWorkspaces.length === 1)
// delete personal workspace
await workspaceService.deleteWorkspaceById(queryRunner, personalWorkspaces[0].workspaceId)
// remove user from other workspces
organizationUser = await organizationUserService.deleteOrganizationUser(queryRunner, query.organizationId, query.userId)
// soft delete user because they might workspace might created by them
const deleteUser = await queryRunner.manager.findOneBy(User, { id: query.userId })
if (!deleteUser) throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, GeneralErrorMessage.UNHANDLED_EDGE_CASE)
deleteUser.name = UserStatus.DELETED
deleteUser.email = `deleted_${deleteUser.id}_${Date.now()}@deleted.flowise`
deleteUser.status = UserStatus.DELETED
deleteUser.credential = null
deleteUser.tokenExpiry = null
deleteUser.tempToken = null
await queryRunner.manager.save(User, deleteUser)
} else {
organizationUser = await organizationUserService.deleteOrganizationUser(queryRunner, query.organizationId, query.userId)
}
await queryRunner.commitTransaction()
return res.status(StatusCodes.OK).json(organizationUser)
} catch (error) {
if (queryRunner && queryRunner.isTransactionActive) await queryRunner.rollbackTransaction()
next(error)
} finally {
if (queryRunner && !queryRunner.isReleased) await queryRunner.release()
}
}
}
@@ -0,0 +1,187 @@
import { Request, Response, NextFunction } from 'express'
import { StatusCodes } from 'http-status-codes'
import { OrganizationErrorMessage, OrganizationService } from '../services/organization.service'
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
import { Organization } from '../database/entities/organization.entity'
import { GeneralErrorMessage } from '../../utils/constants'
import { OrganizationUserService } from '../services/organization-user.service'
import { getCurrentUsage } from '../../utils/quotaUsage'
export class OrganizationController {
public async create(req: Request, res: Response, next: NextFunction) {
try {
const organizationUserService = new OrganizationUserService()
const newOrganization = await organizationUserService.createOrganization(req.body)
return res.status(StatusCodes.CREATED).json(newOrganization)
} catch (error) {
next(error)
}
}
public async read(req: Request, res: Response, next: NextFunction) {
let queryRunner
try {
queryRunner = getRunningExpressApp().AppDataSource.createQueryRunner()
await queryRunner.connect()
const query = req.query as Partial<Organization>
const organizationService = new OrganizationService()
let organization: Organization | null
if (query.id) {
organization = await organizationService.readOrganizationById(query.id, queryRunner)
if (!organization) throw new InternalFlowiseError(StatusCodes.NOT_FOUND, OrganizationErrorMessage.ORGANIZATION_NOT_FOUND)
} else if (query.name) {
organization = await organizationService.readOrganizationByName(query.name, queryRunner)
if (!organization) throw new InternalFlowiseError(StatusCodes.NOT_FOUND, OrganizationErrorMessage.ORGANIZATION_NOT_FOUND)
} else {
throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, GeneralErrorMessage.UNHANDLED_EDGE_CASE)
}
return res.status(StatusCodes.OK).json(organization)
} catch (error) {
next(error)
} finally {
if (queryRunner) await queryRunner.release()
}
}
public async update(req: Request, res: Response, next: NextFunction) {
try {
const organizationService = new OrganizationService()
const organization = await organizationService.updateOrganization(req.body)
return res.status(StatusCodes.OK).json(organization)
} catch (error) {
next(error)
}
}
public async getAdditionalSeatsQuantity(req: Request, res: Response, next: NextFunction) {
try {
const { subscriptionId } = req.query
if (!subscriptionId) {
return res.status(400).json({ error: 'Subscription ID is required' })
}
const organizationUserservice = new OrganizationUserService()
const totalOrgUsers = await organizationUserservice.readOrgUsersCountByOrgId(req.user?.activeOrganizationId as string)
const identityManager = getRunningExpressApp().identityManager
const result = await identityManager.getAdditionalSeatsQuantity(subscriptionId as string)
return res.status(StatusCodes.OK).json({ ...result, totalOrgUsers })
} catch (error) {
next(error)
}
}
public async getCustomerWithDefaultSource(req: Request, res: Response, next: NextFunction) {
try {
const { customerId } = req.query
if (!customerId) {
return res.status(400).json({ error: 'Customer ID is required' })
}
const identityManager = getRunningExpressApp().identityManager
const result = await identityManager.getCustomerWithDefaultSource(customerId as string)
return res.status(StatusCodes.OK).json(result)
} catch (error) {
next(error)
}
}
public async getAdditionalSeatsProration(req: Request, res: Response, next: NextFunction) {
try {
const { subscriptionId, quantity } = req.query
if (!subscriptionId) {
return res.status(400).json({ error: 'Customer ID is required' })
}
if (quantity === undefined) {
return res.status(400).json({ error: 'Quantity is required' })
}
const identityManager = getRunningExpressApp().identityManager
const result = await identityManager.getAdditionalSeatsProration(subscriptionId as string, parseInt(quantity as string))
return res.status(StatusCodes.OK).json(result)
} catch (error) {
next(error)
}
}
public async getPlanProration(req: Request, res: Response, next: NextFunction) {
try {
const { subscriptionId, newPlanId } = req.query
if (!subscriptionId) {
return res.status(400).json({ error: 'Subscription ID is required' })
}
if (!newPlanId) {
return res.status(400).json({ error: 'New plan ID is required' })
}
const identityManager = getRunningExpressApp().identityManager
const result = await identityManager.getPlanProration(subscriptionId as string, newPlanId as string)
return res.status(StatusCodes.OK).json(result)
} catch (error) {
next(error)
}
}
public async updateAdditionalSeats(req: Request, res: Response, next: NextFunction) {
try {
const { subscriptionId, quantity, prorationDate } = req.body
if (!subscriptionId) {
return res.status(400).json({ error: 'Subscription ID is required' })
}
if (quantity === undefined) {
return res.status(400).json({ error: 'Quantity is required' })
}
if (!prorationDate) {
return res.status(400).json({ error: 'Proration date is required' })
}
const identityManager = getRunningExpressApp().identityManager
const result = await identityManager.updateAdditionalSeats(subscriptionId, quantity, prorationDate)
return res.status(StatusCodes.OK).json(result)
} catch (error) {
next(error)
}
}
public async updateSubscriptionPlan(req: Request, res: Response, next: NextFunction) {
try {
const { subscriptionId, newPlanId, prorationDate } = req.body
if (!subscriptionId) {
return res.status(400).json({ error: 'Subscription ID is required' })
}
if (!newPlanId) {
return res.status(400).json({ error: 'New plan ID is required' })
}
if (!prorationDate) {
return res.status(400).json({ error: 'Proration date is required' })
}
const identityManager = getRunningExpressApp().identityManager
const result = await identityManager.updateSubscriptionPlan(req, subscriptionId, newPlanId, prorationDate)
return res.status(StatusCodes.OK).json(result)
} catch (error) {
next(error)
}
}
public async getCurrentUsage(req: Request, res: Response, next: NextFunction) {
try {
const orgId = req.user?.activeOrganizationId
const subscriptionId = req.user?.activeOrganizationSubscriptionId
if (!orgId) {
return res.status(400).json({ error: 'Organization ID is required' })
}
if (!subscriptionId) {
return res.status(400).json({ error: 'Subscription ID is required' })
}
const usageCacheManager = getRunningExpressApp().usageCacheManager
const result = await getCurrentUsage(orgId, subscriptionId, usageCacheManager)
return res.status(StatusCodes.OK).json(result)
} catch (error) {
next(error)
}
}
}
@@ -0,0 +1,70 @@
import { NextFunction, Request, Response } from 'express'
import { StatusCodes } from 'http-status-codes'
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
import { Role } from '../database/entities/role.entity'
import { RoleService } from '../services/role.service'
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
export class RoleController {
public async create(req: Request, res: Response, next: NextFunction) {
try {
const roleService = new RoleService()
const newRole = await roleService.createRole(req.body)
return res.status(StatusCodes.CREATED).json(newRole)
} catch (error) {
next(error)
}
}
public async read(req: Request, res: Response, next: NextFunction) {
let queryRunner
try {
queryRunner = getRunningExpressApp().AppDataSource.createQueryRunner()
await queryRunner.connect()
const query = req.query as Partial<Role>
const roleService = new RoleService()
let role: Role | Role[] | null | (Role & { userCount: number })[]
if (query.id) {
role = await roleService.readRoleById(query.id, queryRunner)
} else if (query.organizationId) {
role = await roleService.readRoleByOrganizationId(query.organizationId, queryRunner)
} else {
role = await roleService.readRoleByGeneral(queryRunner)
}
return res.status(StatusCodes.OK).json(role)
} catch (error) {
next(error)
} finally {
if (queryRunner) await queryRunner.release()
}
}
public async update(req: Request, res: Response, next: NextFunction) {
try {
const roleService = new RoleService()
const role = await roleService.updateRole(req.body)
return res.status(StatusCodes.OK).json(role)
} catch (error) {
next(error)
}
}
public async delete(req: Request, res: Response, next: NextFunction) {
try {
const query = req.query as Partial<Role>
if (!query.id) {
throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, 'Role ID is required')
}
if (!query.organizationId) {
throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, 'Organization ID is required')
}
const roleService = new RoleService()
const role = await roleService.deleteRole(query.organizationId, query.id)
return res.status(StatusCodes.OK).json(role)
} catch (error) {
next(error)
}
}
}
@@ -0,0 +1,77 @@
import { NextFunction, Request, Response } from 'express'
import { StatusCodes } from 'http-status-codes'
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
import { GeneralErrorMessage } from '../../utils/constants'
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
import { User } from '../database/entities/user.entity'
import { UserErrorMessage, UserService } from '../services/user.service'
export class UserController {
public async create(req: Request, res: Response, next: NextFunction) {
try {
const userService = new UserService()
const user = await userService.createUser(req.body)
return res.status(StatusCodes.CREATED).json(user)
} catch (error) {
next(error)
}
}
public async read(req: Request, res: Response, next: NextFunction) {
let queryRunner
try {
queryRunner = getRunningExpressApp().AppDataSource.createQueryRunner()
await queryRunner.connect()
const query = req.query as Partial<User>
const userService = new UserService()
let user: User | null
if (query.id) {
user = await userService.readUserById(query.id, queryRunner)
if (!user) throw new InternalFlowiseError(StatusCodes.NOT_FOUND, UserErrorMessage.USER_NOT_FOUND)
} else if (query.email) {
user = await userService.readUserByEmail(query.email, queryRunner)
if (!user) throw new InternalFlowiseError(StatusCodes.NOT_FOUND, UserErrorMessage.USER_NOT_FOUND)
} else {
throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, GeneralErrorMessage.UNHANDLED_EDGE_CASE)
}
if (user) {
delete user.credential
delete user.tempToken
delete user.tokenExpiry
}
return res.status(StatusCodes.OK).json(user)
} catch (error) {
next(error)
} finally {
if (queryRunner) await queryRunner.release()
}
}
public async update(req: Request, res: Response, next: NextFunction) {
try {
const userService = new UserService()
const currentUser = req.user
if (!currentUser) {
throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, UserErrorMessage.USER_NOT_FOUND)
}
const { id } = req.body
if (currentUser.id !== id) {
throw new InternalFlowiseError(StatusCodes.FORBIDDEN, UserErrorMessage.USER_NOT_FOUND)
}
const user = await userService.updateUser(req.body)
return res.status(StatusCodes.OK).json(user)
} catch (error) {
next(error)
}
}
public async test(req: Request, res: Response, next: NextFunction) {
try {
return res.status(StatusCodes.OK).json({ message: 'Hello World' })
} catch (error) {
next(error)
}
}
}
@@ -0,0 +1,80 @@
import { Request, Response, NextFunction } from 'express'
import { StatusCodes } from 'http-status-codes'
import { WorkspaceUserService } from '../services/workspace-user.service'
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
import { WorkspaceUser } from '../database/entities/workspace-user.entity'
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
import { GeneralErrorMessage } from '../../utils/constants'
export class WorkspaceUserController {
public async create(req: Request, res: Response, next: NextFunction) {
try {
const workspaceUserService = new WorkspaceUserService()
const newWorkspaceUser = await workspaceUserService.createWorkspaceUser(req.body)
return res.status(StatusCodes.CREATED).json(newWorkspaceUser)
} catch (error) {
next(error)
}
}
public async read(req: Request, res: Response, next: NextFunction) {
let queryRunner
try {
queryRunner = getRunningExpressApp().AppDataSource.createQueryRunner()
await queryRunner.connect()
const query = req.query as Partial<WorkspaceUser & { organizationId: string | undefined }>
const workspaceUserService = new WorkspaceUserService()
let workspaceUser: any
if (query.workspaceId && query.userId) {
workspaceUser = await workspaceUserService.readWorkspaceUserByWorkspaceIdUserId(
query.workspaceId,
query.userId,
queryRunner
)
} else if (query.workspaceId) {
workspaceUser = await workspaceUserService.readWorkspaceUserByWorkspaceId(query.workspaceId, queryRunner)
} else if (query.organizationId && query.userId) {
workspaceUser = await workspaceUserService.readWorkspaceUserByOrganizationIdUserId(
query.organizationId,
query.userId,
queryRunner
)
} else if (query.userId) {
workspaceUser = await workspaceUserService.readWorkspaceUserByUserId(query.userId, queryRunner)
} else if (query.roleId) {
workspaceUser = await workspaceUserService.readWorkspaceUserByRoleId(query.roleId, queryRunner)
} else {
throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, GeneralErrorMessage.UNHANDLED_EDGE_CASE)
}
return res.status(StatusCodes.OK).json(workspaceUser)
} catch (error) {
next(error)
} finally {
if (queryRunner) await queryRunner.release()
}
}
public async update(req: Request, res: Response, next: NextFunction) {
try {
const workspaceUserService = new WorkspaceUserService()
const workspaceUser = await workspaceUserService.updateWorkspaceUser(req.body)
return res.status(StatusCodes.OK).json(workspaceUser)
} catch (error) {
next(error)
}
}
public async delete(req: Request, res: Response, next: NextFunction) {
try {
const query = req.query as Partial<WorkspaceUser>
const workspaceUserService = new WorkspaceUserService()
const workspaceUser = await workspaceUserService.deleteWorkspaceUser(query.workspaceId, query.userId)
return res.status(StatusCodes.OK).json(workspaceUser)
} catch (error) {
next(error)
}
}
}
@@ -0,0 +1,240 @@
import { NextFunction, Request, Response } from 'express'
import { StatusCodes } from 'http-status-codes'
import { QueryRunner } from 'typeorm'
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
import { GeneralErrorMessage } from '../../utils/constants'
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
import { OrganizationUserStatus } from '../database/entities/organization-user.entity'
import { GeneralRole } from '../database/entities/role.entity'
import { WorkspaceUserStatus } from '../database/entities/workspace-user.entity'
import { Workspace } from '../database/entities/workspace.entity'
import { IAssignedWorkspace, LoggedInUser } from '../Interface.Enterprise'
import { OrganizationUserErrorMessage, OrganizationUserService } from '../services/organization-user.service'
import { OrganizationErrorMessage, OrganizationService } from '../services/organization.service'
import { RoleErrorMessage, RoleService } from '../services/role.service'
import { UserErrorMessage, UserService } from '../services/user.service'
import { WorkspaceUserErrorMessage, WorkspaceUserService } from '../services/workspace-user.service'
import { WorkspaceErrorMessage, WorkspaceService } from '../services/workspace.service'
export class WorkspaceController {
public async create(req: Request, res: Response, next: NextFunction) {
try {
const workspaceUserService = new WorkspaceUserService()
const newWorkspace = await workspaceUserService.createWorkspace(req.body)
return res.status(StatusCodes.CREATED).json(newWorkspace)
} catch (error) {
next(error)
}
}
public async read(req: Request, res: Response, next: NextFunction) {
let queryRunner
try {
queryRunner = getRunningExpressApp().AppDataSource.createQueryRunner()
await queryRunner.connect()
const query = req.query as Partial<Workspace>
const workspaceService = new WorkspaceService()
let workspace:
| Workspace
| null
| (Workspace & {
userCount: number
})[]
if (query.id) {
workspace = await workspaceService.readWorkspaceById(query.id, queryRunner)
} else if (query.organizationId) {
workspace = await workspaceService.readWorkspaceByOrganizationId(query.organizationId, queryRunner)
} else {
throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, GeneralErrorMessage.UNHANDLED_EDGE_CASE)
}
return res.status(StatusCodes.OK).json(workspace)
} catch (error) {
next(error)
} finally {
if (queryRunner) await queryRunner.release()
}
}
public async switchWorkspace(req: Request, res: Response, next: NextFunction) {
if (!req.user) {
return next(new InternalFlowiseError(StatusCodes.UNAUTHORIZED, `Unauthorized: User not found`))
}
let queryRunner
try {
queryRunner = getRunningExpressApp().AppDataSource.createQueryRunner()
await queryRunner.connect()
const query = req.query as Partial<Workspace>
await queryRunner.startTransaction()
const workspaceService = new WorkspaceService()
const workspace = await workspaceService.readWorkspaceById(query.id, queryRunner)
if (!workspace) throw new InternalFlowiseError(StatusCodes.NOT_FOUND, WorkspaceErrorMessage.WORKSPACE_NOT_FOUND)
const userService = new UserService()
const user = await userService.readUserById(req.user.id, queryRunner)
if (!user) throw new InternalFlowiseError(StatusCodes.NOT_FOUND, UserErrorMessage.USER_NOT_FOUND)
const workspaceUserService = new WorkspaceUserService()
const { workspaceUser } = await workspaceUserService.readWorkspaceUserByWorkspaceIdUserId(query.id, req.user.id, queryRunner)
if (!workspaceUser) throw new InternalFlowiseError(StatusCodes.NOT_FOUND, WorkspaceUserErrorMessage.WORKSPACE_USER_NOT_FOUND)
workspaceUser.lastLogin = new Date().toISOString()
workspaceUser.status = WorkspaceUserStatus.ACTIVE
workspaceUser.updatedBy = user.id
await workspaceUserService.saveWorkspaceUser(workspaceUser, queryRunner)
const organizationUserService = new OrganizationUserService()
const { organizationUser } = await organizationUserService.readOrganizationUserByWorkspaceIdUserId(
workspaceUser.workspaceId,
workspaceUser.userId,
queryRunner
)
if (!organizationUser)
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, OrganizationUserErrorMessage.ORGANIZATION_USER_NOT_FOUND)
organizationUser.status = OrganizationUserStatus.ACTIVE
organizationUser.updatedBy = user.id
await organizationUserService.saveOrganizationUser(organizationUser, queryRunner)
const roleService = new RoleService()
const ownerRole = await roleService.readGeneralRoleByName(GeneralRole.OWNER, queryRunner)
const role = await roleService.readRoleById(workspaceUser.roleId, queryRunner)
if (!role) throw new InternalFlowiseError(StatusCodes.NOT_FOUND, RoleErrorMessage.ROLE_NOT_FOUND)
const orgService = new OrganizationService()
const org = await orgService.readOrganizationById(organizationUser.organizationId, queryRunner)
if (!org) throw new InternalFlowiseError(StatusCodes.NOT_FOUND, OrganizationErrorMessage.ORGANIZATION_NOT_FOUND)
const subscriptionId = org.subscriptionId as string
const customerId = org.customerId as string
const features = await getRunningExpressApp().identityManager.getFeaturesByPlan(subscriptionId)
const productId = await getRunningExpressApp().identityManager.getProductIdFromSubscription(subscriptionId)
const workspaceUsers = await workspaceUserService.readWorkspaceUserByUserId(req.user.id, queryRunner)
const assignedWorkspaces: IAssignedWorkspace[] = workspaceUsers.map((workspaceUser) => {
return {
id: workspaceUser.workspace.id,
name: workspaceUser.workspace.name,
role: workspaceUser.role?.name,
organizationId: workspaceUser.workspace.organizationId
} as IAssignedWorkspace
})
const loggedInUser: LoggedInUser & { role: string; isSSO: boolean } = {
...req.user,
activeOrganizationId: org.id,
activeOrganizationSubscriptionId: subscriptionId,
activeOrganizationCustomerId: customerId,
activeOrganizationProductId: productId,
isOrganizationAdmin: workspaceUser.roleId === ownerRole.id,
activeWorkspaceId: workspace.id,
activeWorkspace: workspace.name,
assignedWorkspaces,
isApiKeyValidated: true,
isSSO: req.user.ssoProvider ? true : false,
permissions: [...JSON.parse(role.permissions)],
features,
role: role.name,
roleId: role.id
}
// update the passport session
req.user = {
...req.user,
...loggedInUser
}
// Update passport session
// @ts-ignore
req.session.passport.user = {
...req.user,
...loggedInUser
}
req.session.save((err) => {
if (err) throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, GeneralErrorMessage.UNHANDLED_EDGE_CASE)
})
await queryRunner.commitTransaction()
return res.status(StatusCodes.OK).json(loggedInUser)
} catch (error) {
if (queryRunner && !queryRunner.isTransactionActive) {
await queryRunner.rollbackTransaction()
}
next(error)
} finally {
if (queryRunner && !queryRunner.isReleased) {
await queryRunner.release()
}
}
}
public async update(req: Request, res: Response, next: NextFunction) {
try {
const workspaceService = new WorkspaceService()
const workspace = await workspaceService.updateWorkspace(req.body)
return res.status(StatusCodes.OK).json(workspace)
} catch (error) {
next(error)
}
}
public async delete(req: Request, res: Response, next: NextFunction) {
let queryRunner: QueryRunner | undefined
try {
queryRunner = getRunningExpressApp().AppDataSource.createQueryRunner()
await queryRunner.connect()
const workspaceId = req.params.id
if (!workspaceId) {
throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, WorkspaceErrorMessage.INVALID_WORKSPACE_ID)
}
const workspaceService = new WorkspaceService()
await queryRunner.startTransaction()
const workspace = await workspaceService.deleteWorkspaceById(queryRunner, workspaceId)
await queryRunner.commitTransaction()
return res.status(StatusCodes.OK).json(workspace)
} catch (error) {
if (queryRunner && queryRunner.isTransactionActive) await queryRunner.rollbackTransaction()
next(error)
} finally {
if (queryRunner && !queryRunner.isReleased) await queryRunner.release()
}
}
public async getSharedWorkspacesForItem(req: Request, res: Response, next: NextFunction) {
try {
if (typeof req.params === 'undefined' || !req.params.id) {
throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, WorkspaceErrorMessage.INVALID_WORKSPACE_ID)
}
const workspaceService = new WorkspaceService()
return res.json(await workspaceService.getSharedWorkspacesForItem(req.params.id))
} catch (error) {
next(error)
}
}
public async setSharedWorkspacesForItem(req: Request, res: Response, next: NextFunction) {
try {
if (!req.user) {
throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, `Unauthorized: User not found`)
}
if (typeof req.params === 'undefined' || !req.params.id) {
throw new InternalFlowiseError(
StatusCodes.UNAUTHORIZED,
`Error: workspaceController.setSharedWorkspacesForItem - id not provided!`
)
}
if (!req.body) {
throw new InternalFlowiseError(
StatusCodes.PRECONDITION_FAILED,
`Error: workspaceController.setSharedWorkspacesForItem - body not provided!`
)
}
const workspaceService = new WorkspaceService()
return res.json(await workspaceService.setSharedWorkspacesForItem(req.params.id, req.body))
} catch (error) {
next(error)
}
}
}
@@ -0,0 +1,62 @@
import { Column, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'
import { ILoginActivity, IWorkspaceShared, IWorkspaceUser } from '../../Interface.Enterprise'
@Entity('workspace_users')
export class WorkspaceUsers implements IWorkspaceUser {
@PrimaryGeneratedColumn('uuid')
id: string
@Column({ type: 'text' })
workspaceId: string
@Column({ type: 'text' })
userId: string
@Column({ type: 'text' })
role: string
}
@Entity('workspace_shared')
export class WorkspaceShared implements IWorkspaceShared {
@PrimaryGeneratedColumn('uuid')
id: string
@Column({ type: 'text' })
workspaceId: string
@Column({ type: 'text' })
sharedItemId: string
@Column({ type: 'text', name: 'itemType' })
itemType: string
@Column({ type: 'timestamp' })
@UpdateDateColumn()
createdDate: Date
@Column({ type: 'timestamp' })
@UpdateDateColumn()
updatedDate: Date
}
@Entity('login_activity')
export class LoginActivity implements ILoginActivity {
@PrimaryGeneratedColumn('uuid')
id: string
@Column({ type: 'text' })
username: string
@Column({ name: 'activity_code' })
activityCode: number
@Column({ name: 'login_mode' })
loginMode: string
@Column({ type: 'text' })
message: string
@Column({ type: 'timestamp' })
@UpdateDateColumn()
attemptedDateTime: Date
}
@@ -0,0 +1,47 @@
import { Column, CreateDateColumn, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'
import { User } from './user.entity'
import { Organization } from './organization.entity'
export enum LoginMethodStatus {
ENABLE = 'enable',
DISABLE = 'disable'
}
@Entity({ name: 'login_method' })
export class LoginMethod {
@PrimaryGeneratedColumn('uuid')
id: string
@Column({ nullable: true })
organizationId?: string
@ManyToOne(() => Organization, (organization) => organization.id)
@JoinColumn({ name: 'organizationId' })
organization?: Organization
@Column({ type: 'varchar', length: 100 })
name: string
@Column({ type: 'text' })
config: string
@Column({ type: 'varchar', length: 20, default: LoginMethodStatus.ENABLE })
status?: string
@CreateDateColumn()
createdDate?: Date
@UpdateDateColumn()
updatedDate?: Date
@Column({ nullable: true })
createdBy?: string
@ManyToOne(() => User, (user) => user.createdByLoginMethod)
@JoinColumn({ name: 'createdBy' })
createdByUser?: User
@Column({ nullable: true })
updatedBy?: string
@ManyToOne(() => User, (user) => user.updatedByLoginMethod)
@JoinColumn({ name: 'updatedBy' })
updatedByUser?: User
}
@@ -0,0 +1,52 @@
import { Column, CreateDateColumn, Entity, JoinColumn, ManyToOne, PrimaryColumn, UpdateDateColumn } from 'typeorm'
import { Organization } from './organization.entity'
import { Role } from './role.entity'
import { User } from './user.entity'
export enum OrganizationUserStatus {
ACTIVE = 'active',
DISABLE = 'disable',
INVITED = 'invited'
}
@Entity({ name: 'organization_user' })
export class OrganizationUser {
@PrimaryColumn()
organizationId: string
@ManyToOne(() => Organization, (organization) => organization.id)
@JoinColumn({ name: 'organizationId' })
organization: Organization
@PrimaryColumn()
userId: string
@ManyToOne(() => User, (user) => user.id)
@JoinColumn({ name: 'userId' })
user: User
@Column({ type: 'uuid', nullable: false })
roleId: string
@ManyToOne(() => Role, (role) => role.id)
@JoinColumn({ name: 'roleId' })
role?: Role
@Column({ type: 'varchar', length: 20, default: OrganizationUserStatus.ACTIVE })
status?: string
@CreateDateColumn()
createdDate?: Date
@UpdateDateColumn()
updatedDate?: Date
@Column({ nullable: false })
createdBy?: string
@ManyToOne(() => User, (user) => user.createdOrganizationUser)
@JoinColumn({ name: 'createdBy' })
createdByUser?: User
@Column({ nullable: false })
updatedBy?: string
@ManyToOne(() => User, (user) => user.updatedOrganizationUser)
@JoinColumn({ name: 'updatedBy' })
updatedByUser?: User
}
@@ -0,0 +1,39 @@
import { Column, CreateDateColumn, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'
import { User } from './user.entity'
export enum OrganizationName {
DEFAULT_ORGANIZATION = 'Default Organization'
}
@Entity()
export class Organization {
@PrimaryGeneratedColumn('uuid')
id: string
@Column({ type: 'varchar', length: 100, default: OrganizationName.DEFAULT_ORGANIZATION })
name: string
@Column({ type: 'varchar', length: 100, nullable: true })
customerId?: string
@Column({ type: 'varchar', length: 100, nullable: true })
subscriptionId?: string
@CreateDateColumn()
createdDate?: Date
@UpdateDateColumn()
updatedDate?: Date
@Column({ nullable: false })
createdBy?: string
@ManyToOne(() => User, (user) => user.createdOrganizations)
@JoinColumn({ name: 'createdBy' })
createdByUser?: User
@Column({ nullable: false })
updatedBy?: string
@ManyToOne(() => User, (user) => user.updatedOrganizations)
@JoinColumn({ name: 'updatedBy' })
updatedByUser?: User
}
@@ -0,0 +1,48 @@
import { Column, CreateDateColumn, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'
import { Organization } from './organization.entity'
import { User } from './user.entity'
export enum GeneralRole {
OWNER = 'owner',
MEMBER = 'member',
PERSONAL_WORKSPACE = 'personal workspace'
}
@Entity()
export class Role {
@PrimaryGeneratedColumn('uuid')
id: string
@Column({ nullable: true })
organizationId?: string
@ManyToOne(() => Organization, (organization) => organization.id)
@JoinColumn({ name: 'organizationId' })
organization?: Organization
@Column({ type: 'varchar', length: 100 })
name: string
@Column({ type: 'text', nullable: true })
description?: string
@Column({ type: 'text' })
permissions: string
@CreateDateColumn()
createdDate?: Date
@UpdateDateColumn()
updatedDate?: Date
@Column({ nullable: true })
createdBy?: string
@ManyToOne(() => User, (user) => user.createdRoles)
@JoinColumn({ name: 'createdBy' })
createdByUser?: User
@Column({ nullable: true })
updatedBy?: string
@ManyToOne(() => User, (user) => user.updatedRoles)
@JoinColumn({ name: 'updatedBy' })
updatedByUser?: User
}
@@ -0,0 +1,92 @@
import { Column, CreateDateColumn, Entity, JoinColumn, ManyToOne, OneToMany, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'
import { LoginMethod } from './login-method.entity'
import { OrganizationUser } from './organization-user.entity'
import { Organization } from './organization.entity'
import { Role } from './role.entity'
import { WorkspaceUser } from './workspace-user.entity'
import { Workspace } from './workspace.entity'
export enum UserStatus {
ACTIVE = 'active',
INVITED = 'invited',
UNVERIFIED = 'unverified',
DELETED = 'deleted'
}
@Entity()
export class User {
@PrimaryGeneratedColumn('uuid')
id: string
@Column({ type: 'varchar', length: 100 })
name: string
@Column({ type: 'varchar', length: 255, unique: true })
email: string
@Column({ type: 'text', nullable: true })
credential?: string | null
@Column({ type: 'text', nullable: true, unique: true })
tempToken?: string | null
@CreateDateColumn({ nullable: true })
tokenExpiry?: Date | null
@Column({ type: 'varchar', length: 20, default: UserStatus.UNVERIFIED })
status: string
@CreateDateColumn()
createdDate?: Date
@UpdateDateColumn()
updatedDate?: Date
@Column({ nullable: false })
createdBy: string
@ManyToOne(() => User, (user) => user.id, {})
@JoinColumn({ name: 'createdBy' })
createdByUser?: User
@Column({ nullable: false })
updatedBy: string
@ManyToOne(() => User, (user) => user.id, {})
@JoinColumn({ name: 'updatedBy' })
updatedByUser?: User
@OneToMany(() => Organization, (organization) => organization.createdByUser)
createdOrganizations?: Organization[]
@OneToMany(() => Organization, (organization) => organization.updatedByUser)
updatedOrganizations?: Organization[]
@OneToMany(() => Role, (role) => role.createdByUser)
createdRoles?: Role[]
@OneToMany(() => Role, (role) => role.updatedByUser)
updatedRoles?: Role[]
@OneToMany(() => OrganizationUser, (organizationUser) => organizationUser.createdByUser)
createdOrganizationUser?: OrganizationUser[]
@OneToMany(() => OrganizationUser, (organizationUser) => organizationUser.updatedByUser)
updatedOrganizationUser?: OrganizationUser[]
@OneToMany(() => Workspace, (workspace) => workspace.createdByUser)
createdWorkspace?: Workspace[]
@OneToMany(() => Workspace, (workspace) => workspace.updatedByUser)
updatedWorkspace?: Workspace[]
@OneToMany(() => WorkspaceUser, (workspaceUser) => workspaceUser.createdByUser)
createdWorkspaceUser?: WorkspaceUser[]
@OneToMany(() => WorkspaceUser, (workspaceUser) => workspaceUser.updatedByUser)
updatedByWorkspaceUser?: WorkspaceUser[]
@OneToMany(() => LoginMethod, (loginMethod) => loginMethod.createdByUser)
createdByLoginMethod?: LoginMethod[]
@OneToMany(() => LoginMethod, (loginMethod) => loginMethod.updatedByUser)
updatedByLoginMethod?: LoginMethod[]
}
@@ -0,0 +1,55 @@
import { Column, CreateDateColumn, Entity, JoinColumn, ManyToOne, PrimaryColumn, UpdateDateColumn } from 'typeorm'
import { User } from './user.entity'
import { Role } from './role.entity'
import { Workspace } from './workspace.entity'
export enum WorkspaceUserStatus {
ACTIVE = 'active',
DISABLE = 'disable',
INVITED = 'invited'
}
@Entity({ name: 'workspace_user' })
export class WorkspaceUser {
@PrimaryColumn()
workspaceId: string
@ManyToOne(() => Workspace, (workspace) => workspace.id)
@JoinColumn({ name: 'workspaceId' })
workspace: Workspace
@PrimaryColumn()
userId: string
@ManyToOne(() => User, (user) => user.id)
@JoinColumn({ name: 'userId' })
user: User
@Column({ type: 'uuid', nullable: false })
roleId: string
@ManyToOne(() => Role, (role) => role.id)
@JoinColumn({ name: 'roleId' })
role?: Role
@Column({ type: 'varchar', length: 20, default: WorkspaceUserStatus.INVITED })
status?: string
@CreateDateColumn()
lastLogin?: string
@CreateDateColumn()
createdDate?: Date
@UpdateDateColumn()
updatedDate?: Date
@Column({ nullable: false })
createdBy?: string
@ManyToOne(() => User, (user) => user.createdWorkspaceUser)
@JoinColumn({ name: 'createdBy' })
createdByUser?: User
@Column({ nullable: false })
updatedBy?: string
@ManyToOne(() => User, (user) => user.updatedByWorkspaceUser)
@JoinColumn({ name: 'updatedBy' })
updatedByUser?: User
}
@@ -0,0 +1,44 @@
import { Column, CreateDateColumn, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'
import { Organization } from './organization.entity'
import { User } from './user.entity'
export enum WorkspaceName {
DEFAULT_WORKSPACE = 'Default Workspace',
DEFAULT_PERSONAL_WORKSPACE = 'Personal Workspace'
}
@Entity()
export class Workspace {
@PrimaryGeneratedColumn('uuid')
id: string
@Column({ type: 'varchar', length: 100, default: WorkspaceName.DEFAULT_PERSONAL_WORKSPACE })
name: string
@Column({ type: 'text', nullable: true })
description?: string
@Column({ nullable: false })
organizationId?: string
@ManyToOne(() => Organization, (organization) => organization.id)
@JoinColumn({ name: 'organizationId' })
organization?: Organization
@CreateDateColumn()
createdDate?: Date
@UpdateDateColumn()
updatedDate?: Date
@Column({ nullable: false })
createdBy?: string
@ManyToOne(() => User, (user) => user.createdWorkspace)
@JoinColumn({ name: 'createdBy' })
createdByUser?: User
@Column({ nullable: false })
updatedBy?: string
@ManyToOne(() => User, (user) => user.updatedWorkspace)
@JoinColumn({ name: 'updatedBy' })
updatedByUser?: User
}
@@ -0,0 +1,46 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddAuthTables1720230151482 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE IF NOT EXISTS \`user\` (
\`id\` varchar(36) NOT NULL,
\`name\` varchar(255),
\`role\` varchar(20) NOT NULL,
\`email\` varchar(100) NOT NULL,
\`status\` varchar(20) NOT NULL,
\`credential\` text,
\`tempToken\` text,
\`tokenExpiry\` datetime(6),
\`activeWorkspaceId\` varchar(100),
\`lastLogin\` datetime(6),
PRIMARY KEY (\`id\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;`
)
await queryRunner.query(
`CREATE TABLE IF NOT EXISTS \`roles\` (
\`id\` varchar(36) NOT NULL,
\`name\` varchar(255),
\`description\` text,
\`permissions\` text,
PRIMARY KEY (\`id\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;`
)
await queryRunner.query(
`CREATE TABLE IF NOT EXISTS \`login_activity\` (
\`id\` varchar(36) NOT NULL,
\`username\` varchar(255),
\`message\` varchar(255) NOT NULL,
\`activity_code\` INT NOT NULL,
\`attemptedDateTime\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
PRIMARY KEY (\`id\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;`
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE user`)
await queryRunner.query(`DROP TABLE roles`)
await queryRunner.query(`DROP TABLE login_activity`)
}
}
@@ -0,0 +1,52 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
import { ensureColumnExists } from './mariaDbCustomFunctions'
export class AddWorkspace1725437498242 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE IF NOT EXISTS \`workspace\` (
\`id\` varchar(36) NOT NULL,
\`name\` varchar(255) NOT NULL,
\`description\` text DEFAULT NULL,
\`createdDate\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
\`updatedDate\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
PRIMARY KEY (\`id\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;`
)
await queryRunner.query(
`CREATE TABLE IF NOT EXISTS \`workspace_users\` (
\`id\` varchar(36) NOT NULL,
\`workspaceId\` varchar(36) NOT NULL,
\`userId\` varchar(36) NOT NULL,
\`role\` varchar(255) DEFAULT NULL,
PRIMARY KEY (\`id\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;`
)
await ensureColumnExists(queryRunner, 'chat_flow', 'workspaceId', 'varchar(36)')
await ensureColumnExists(queryRunner, 'tool', 'workspaceId', 'varchar(36)')
await ensureColumnExists(queryRunner, 'assistant', 'workspaceId', 'varchar(36)')
await ensureColumnExists(queryRunner, 'credential', 'workspaceId', 'varchar(36)')
await ensureColumnExists(queryRunner, 'document_store', 'workspaceId', 'varchar(36)')
await ensureColumnExists(queryRunner, 'evaluation', 'workspaceId', 'varchar(36)')
await ensureColumnExists(queryRunner, 'evaluator', 'workspaceId', 'varchar(36)')
await ensureColumnExists(queryRunner, 'dataset', 'workspaceId', 'varchar(36)')
await ensureColumnExists(queryRunner, 'apikey', 'workspaceId', 'varchar(36)')
await ensureColumnExists(queryRunner, 'variable', 'workspaceId', 'varchar(36)')
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE workspace`)
await queryRunner.query(`DROP TABLE workspace_users`)
await queryRunner.query(`ALTER TABLE \`chat_message\` DROP COLUMN \`workspaceId\`;`)
await queryRunner.query(`ALTER TABLE \`tool\` DROP COLUMN \`workspaceId\`;`)
await queryRunner.query(`ALTER TABLE \`assistant\` DROP COLUMN \`workspaceId\`;`)
await queryRunner.query(`ALTER TABLE \`credential\` DROP COLUMN \`workspaceId\`;`)
await queryRunner.query(`ALTER TABLE \`document_store\` DROP COLUMN \`workspaceId\`;`)
await queryRunner.query(`ALTER TABLE \`evaluation\` DROP COLUMN \`workspaceId\`;`)
await queryRunner.query(`ALTER TABLE \`dataset\` DROP COLUMN \`workspaceId\`;`)
await queryRunner.query(`ALTER TABLE \`apikey\` DROP COLUMN \`workspaceId\`;`)
await queryRunner.query(`ALTER TABLE \`variable\` DROP COLUMN \`workspaceId\`;`)
}
}

Some files were not shown because too many files have changed in this diff Show More