feat: unique flag for apps - one instance per session
This commit is contained in:
1 parent
dbda9c0457
commit
16b50c672b
2 files changed
+59
-40
No files matched your search
@@ -11,6 +11,8 @@ export interface AppDefinition {
|
||||
migrations: boolean;
|
||||
// If adding, updating, or removing this app requires a session restart
|
||||
restartOnChange: boolean;
|
||||
// If only one instance of this app is allowed per session
|
||||
unique: boolean;
|
||||
}
|
||||
|
||||
// All Apps
|
||||
@@ -21,6 +23,7 @@ export const APPS: Record<AppName, AppDefinition> = {
|
||||
queue: false,
|
||||
migrations: false,
|
||||
restartOnChange: true,
|
||||
unique: true,
|
||||
},
|
||||
[AppName.chatwoot]: {
|
||||
name: AppName.chatwoot,
|
||||
@@ -28,6 +31,7 @@ export const APPS: Record<AppName, AppDefinition> = {
|
||||
queue: true,
|
||||
migrations: true,
|
||||
restartOnChange: true,
|
||||
unique: true,
|
||||
},
|
||||
[AppName.mcp]: {
|
||||
name: AppName.mcp,
|
||||
@@ -35,6 +39,7 @@ export const APPS: Record<AppName, AppDefinition> = {
|
||||
queue: false,
|
||||
migrations: false,
|
||||
restartOnChange: false,
|
||||
unique: false,
|
||||
},
|
||||
[AppName.brazilianPhoneNumbers]: {
|
||||
name: AppName.brazilianPhoneNumbers,
|
||||
@@ -42,5 +47,27 @@ export const APPS: Record<AppName, AppDefinition> = {
|
||||
queue: false,
|
||||
migrations: true,
|
||||
restartOnChange: true,
|
||||
unique: true,
|
||||
},
|
||||
};
|
||||
|
||||
export function isUniqueApp(name: AppName): boolean {
|
||||
return APPS[name]?.unique === true;
|
||||
}
|
||||
|
||||
// Returns the first unique AppName that appears more than once in the list, or null
|
||||
export function findDuplicateUniqueApp(
|
||||
apps: Array<{ app: AppName }>,
|
||||
): AppName | null {
|
||||
const seen = new Set<AppName>();
|
||||
for (const app of apps) {
|
||||
if (!isUniqueApp(app.app)) {
|
||||
continue;
|
||||
}
|
||||
if (seen.has(app.app)) {
|
||||
return app.app;
|
||||
}
|
||||
seen.add(app.app);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -22,6 +22,10 @@ import { App } from '../dto/app.dto';
|
||||
import { AppRepository } from '../storage/AppRepository';
|
||||
import { AppName } from '@waha/apps/app_sdk/apps/name';
|
||||
import { AppRuntimeConfig } from '@waha/apps/app_sdk/apps/AppRuntime';
|
||||
import {
|
||||
findDuplicateUniqueApp,
|
||||
isUniqueApp,
|
||||
} from '@waha/apps/app_sdk/apps/definition';
|
||||
|
||||
export class AppDisableError extends UnprocessableEntityException {
|
||||
constructor(app: string) {
|
||||
@@ -31,6 +35,15 @@ export class AppDisableError extends UnprocessableEntityException {
|
||||
}
|
||||
}
|
||||
|
||||
export class AppUniquePerSessionError extends UnprocessableEntityException {
|
||||
constructor(app: string, session: string, existingAppId: string) {
|
||||
super(
|
||||
`Only one '${app}' app is allowed per session. ` +
|
||||
`Session '${session}' already has a '${app}' app with ID '${existingAppId}'.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class AppsEnabledService implements IAppsService {
|
||||
constructor(
|
||||
@@ -67,47 +80,17 @@ export class AppsEnabledService implements IAppsService {
|
||||
throw new Error(`App with ID '${app.id}' already exists.`);
|
||||
}
|
||||
|
||||
let existingApps: App[] = [];
|
||||
if (
|
||||
app.app === AppName.chatwoot ||
|
||||
app.app === AppName.calls ||
|
||||
app.app === AppName.brazilianPhoneNumbers
|
||||
) {
|
||||
existingApps = await repo.getAllBySession(app.session);
|
||||
}
|
||||
// Validate only one Chatwoot app per session
|
||||
if (app.app === AppName.chatwoot) {
|
||||
const existingChatwootApp = existingApps.find(
|
||||
(existingApp) => existingApp.app === AppName.chatwoot,
|
||||
// Validate only one instance of a unique app per session
|
||||
if (isUniqueApp(app.app)) {
|
||||
const existingApps = await repo.getAllBySession(app.session);
|
||||
const duplicateApp = existingApps.find(
|
||||
(existingApp) => existingApp.app === app.app,
|
||||
);
|
||||
|
||||
if (existingChatwootApp) {
|
||||
throw new Error(
|
||||
`Only one Chatwoot app is allowed per session. Session '${app.session}' already has a Chatwoot app with ID '${existingChatwootApp.id}'.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
// Validate only one Calls app per session
|
||||
if (app.app === AppName.calls) {
|
||||
const existingCallsApp = existingApps.find(
|
||||
(existingApp) => existingApp.app === AppName.calls,
|
||||
);
|
||||
|
||||
if (existingCallsApp) {
|
||||
throw new Error(
|
||||
`Only one Calls app is allowed per session. Session '${app.session}' already has a Calls app with ID '${existingCallsApp.id}'.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
// Validate only one Brazilian Phone Numbers app per session
|
||||
if (app.app === AppName.brazilianPhoneNumbers) {
|
||||
const existingBrazilianPhoneNumbersApp = existingApps.find(
|
||||
(existingApp) => existingApp.app === AppName.brazilianPhoneNumbers,
|
||||
);
|
||||
|
||||
if (existingBrazilianPhoneNumbersApp) {
|
||||
throw new Error(
|
||||
`Only one Brazilian Phone Numbers app is allowed per session. Session '${app.session}' already has a Brazilian Phone Numbers app with ID '${existingBrazilianPhoneNumbersApp.id}'.`,
|
||||
if (duplicateApp) {
|
||||
throw new AppUniquePerSessionError(
|
||||
app.app,
|
||||
app.session,
|
||||
duplicateApp.id,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -257,6 +240,15 @@ export class AppsEnabledService implements IAppsService {
|
||||
session: string,
|
||||
apps: App[],
|
||||
): Promise<void> {
|
||||
// Reject duplicate unique apps in the payload before any writes,
|
||||
// otherwise the by-type matching below binds them to the same app
|
||||
const duplicateUniqueApp = findDuplicateUniqueApp(apps);
|
||||
if (duplicateUniqueApp !== null) {
|
||||
throw new UnprocessableEntityException(
|
||||
`Only one '${duplicateUniqueApp}' app is allowed per session - remove duplicate entries from 'apps'.`,
|
||||
);
|
||||
}
|
||||
|
||||
const existing = await this.list(manager, session);
|
||||
const ids = new Set<string>();
|
||||
|
||||
|
||||
Reference in new issue
Block a user