[core] API to restart server (actually it's /stop, but docker will handle restart)

This commit is contained in:
devlikepro committed 2024-08-27 16:42:55 +07:00
1 parent fb774f2caa
commit 9253e54958
5 files changed
+130 -15

No files matched your search

+68 -11
View File
@@ -1,20 +1,36 @@
import * as process from 'node:process';
import { Controller, Get, Query } from '@nestjs/common';
import {
Body,
Controller,
Get,
Logger,
Post,
Query,
UsePipes,
} from '@nestjs/common';
import { UnprocessableEntityException } from '@nestjs/common/exceptions/unprocessable-entity.exception';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { parseBool } from '@waha/helpers';
import { getApp, getAppModuleInstance } from '@waha/main';
import { WAHAValidationPipe } from '@waha/nestjs/pipes/WAHAValidationPipe';
import { WAHAEnvironment } from '@waha/structures/environment.dto';
import { EnvironmentQuery } from '@waha/structures/server.dto';
import {
EnvironmentQuery,
ServerStatusResponse,
StopRequest,
} from '@waha/structures/server.dto';
import { VERSION } from '@waha/version';
import * as lodash from 'lodash';
interface EnvironmentVariables {
[key: string]: string;
}
@Controller('api/server')
@ApiTags('other')
export class ServerController {
private logger: Logger;
constructor() {
this.logger = new Logger('ServerController');
}
@Get('version')
@ApiOperation({ summary: 'Get the version of the server' })
get(): WAHAEnvironment {
@@ -23,11 +39,12 @@ export class ServerController {
@Get('environment')
@ApiOperation({ summary: 'Return the server environment' })
// eslint-disable-next-line @typescript-eslint/ban-types
environment(@Query() query: EnvironmentQuery): object {
environment(
@Query(new WAHAValidationPipe()) query: EnvironmentQuery,
// eslint-disable-next-line @typescript-eslint/ban-types
): object {
let result = process.env;
const all = parseBool(query.all);
if (!all) {
if (!query.all) {
result = lodash.pickBy(result, (value, key) => {
return (
key.startsWith('WAHA_') ||
@@ -45,4 +62,44 @@ export class ServerController {
});
return Object.fromEntries(map);
}
@Get('status')
@ApiOperation({ summary: 'The server status' })
async status(): Promise<ServerStatusResponse> {
const appModule = await getAppModuleInstance();
if (!appModule) {
throw new UnprocessableEntityException('AppModule not found');
}
const now = Date.now();
const startTimestamp = appModule.startTimestamp;
return {
startTimestamp: startTimestamp,
uptime: now - startTimestamp,
};
}
@Post('stop')
@ApiOperation({ summary: 'Stop the server' })
@UsePipes(new WAHAValidationPipe())
async stop(@Body() request: StopRequest) {
const timeout = 1_000;
if (request.force) {
this.logger.log(`Force stopping the server in ${timeout}ms`);
setTimeout(() => {
this.logger.log('Force stopping the server');
process.kill(process.pid, 'SIGKILL');
}, timeout);
} else {
this.logger.log(`Gracefully stopping the server in ${timeout}ms`);
setTimeout(async () => {
this.logger.log('Gracefully closing the application...');
const app = getApp();
if (app) {
await app.close();
}
this.logger.log('Application closed');
process.exit(0);
}, timeout);
}
}
}
+1 -1
View File
@@ -105,7 +105,7 @@ export class WebsocketGatewayCore
this.logger.log('Shutting down websocket server');
// Allow pending messages to be sent, it can be even 1ms, just to release the event loop
await sleep(100);
this.server.clients.forEach((options, client) => {
this.server?.clients.forEach((options, client) => {
client.close(1001, 'Server is shutting down');
});
// Do not turn off heartbeat service here,
+5 -1
View File
@@ -140,7 +140,11 @@ const PROVIDERS = [
providers: PROVIDERS,
})
export class AppModuleCore {
constructor(protected config: WhatsappConfigService) {}
public startTimestamp: number;
constructor(protected config: WhatsappConfigService) {
this.startTimestamp = Date.now();
}
static getHttpsOptions(logger: Logger) {
return undefined;
+23 -2
View File
@@ -34,7 +34,6 @@ async function loadModules(): Promise<
[typeof AppModuleCore, typeof SwaggerConfiguratorCore]
> {
const version = getWAHAVersion();
logger.info(`WAHA (WhatsApp HTTP API) - Running ${version} version...`);
if (version === WAHAVersion.CORE) {
const { AppModuleCore } = await import('./core/app.module.core');
@@ -52,10 +51,32 @@ async function loadModules(): Promise<
return [AppModulePlus, SwaggerConfiguratorPlus];
}
let app = undefined;
export function getApp() {
return app;
}
/**
* Get the AppModule instance (depends on the version)
*/
export async function getAppModuleInstance(): Promise<
AppModuleCore | undefined
> {
const [AppModule, _] = await loadModules();
const app = getApp();
if (!app) {
return;
}
return app.get(AppModule);
}
async function bootstrap() {
const version = getWAHAVersion();
logger.info(`WAHA (WhatsApp HTTP API) - Running ${version} version...`);
const [AppModule, SwaggerModule] = await loadModules();
const httpsOptions = AppModule.getHttpsOptions(logger);
const app = await NestFactory.create(AppModule, {
app = await NestFactory.create(AppModule, {
logger: getNestJSLogLevels(),
httpsOptions: httpsOptions,
bufferLogs: true,
+33
View File
@@ -1,4 +1,7 @@
import { ApiProperty } from '@nestjs/swagger';
import { BooleanString } from '@waha/nestjs/validation/BooleanString';
import { Transform } from 'class-transformer';
import { IsBoolean, IsOptional } from 'class-validator';
export class EnvironmentQuery {
@ApiProperty({
@@ -6,5 +9,35 @@ export class EnvironmentQuery {
required: false,
description: 'Include all environment variables',
})
@Transform(BooleanString)
@IsBoolean()
@IsOptional()
all: boolean = false;
}
export class StopRequest {
@ApiProperty({
example: false,
required: false,
description:
'By default, it gracefully stops the server, ' +
'but you can force it to terminate immediately.',
})
@IsBoolean()
@IsOptional()
force: boolean = false;
}
export class ServerStatusResponse {
@ApiProperty({
example: 1723788847247,
description: 'The timestamp when the server started (milliseconds).',
})
startTimestamp: number;
@ApiProperty({
example: 3600000,
description: 'The uptime of the server in milliseconds.',
})
uptime: number;
}