0
0
mirror of https://github.com/sp-tarkov/server.git synced 2025-02-12 22:10:44 -05:00

Merge pull request #2 from DrakiaXYZ/feat-mongovalidation

Add MongoID validation to databaseService
This commit is contained in:
Chomp 2024-11-23 08:13:29 +00:00 committed by GitHub
commit db88c00b4d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 73 additions and 3 deletions

View File

@ -66,6 +66,7 @@
"customisation-unable_to_find_suit_with_id": "Unable to find suit with offer id: %s", "customisation-unable_to_find_suit_with_id": "Unable to find suit with offer id: %s",
"customisation-unable_to_get_trader_suits": "Unable to get suits from trader: %s", "customisation-unable_to_get_trader_suits": "Unable to get suits from trader: %s",
"database-data_at_path_missing": "The database was unable to retrieve data from: [%s] Please ensure your configs are valid and the data at the location exists", "database-data_at_path_missing": "The database was unable to retrieve data from: [%s] Please ensure your configs are valid and the data at the location exists",
"database-invalid_data": "Invalid data detected in database, please remove any outdated mods. See previous error for invalid data. Server stopped.",
"database-no_location_found_with_id": "No location found with an Id of: %s in database", "database-no_location_found_with_id": "No location found with an Id of: %s in database",
"database-no_trader_found_with_id": "Unable to find trader: %s in database", "database-no_trader_found_with_id": "Unable to find trader: %s in database",
"dialog-chatbot_id_already_exists": "Chat bot: %s being registered already exists, unable to register bot", "dialog-chatbot_id_already_exists": "Chat bot: %s being registered already exists, unable to register bot",

View File

@ -6,11 +6,11 @@ import { ConfigTypes } from "@spt/models/enums/ConfigTypes";
import { IHttpConfig } from "@spt/models/spt/config/IHttpConfig"; import { IHttpConfig } from "@spt/models/spt/config/IHttpConfig";
import { ILogger } from "@spt/models/spt/utils/ILogger"; import { ILogger } from "@spt/models/spt/utils/ILogger";
import { ConfigServer } from "@spt/servers/ConfigServer"; import { ConfigServer } from "@spt/servers/ConfigServer";
import { DatabaseServer } from "@spt/servers/DatabaseServer";
import { WebSocketServer } from "@spt/servers/WebSocketServer"; import { WebSocketServer } from "@spt/servers/WebSocketServer";
import { IHttpListener } from "@spt/servers/http/IHttpListener"; import { IHttpListener } from "@spt/servers/http/IHttpListener";
import { LocalisationService } from "@spt/services/LocalisationService"; import { LocalisationService } from "@spt/services/LocalisationService";
import { inject, injectAll, injectable } from "tsyringe"; import { inject, injectAll, injectable } from "tsyringe";
import { DatabaseService } from "@spt/services/DatabaseService";
@injectable() @injectable()
export class HttpServer { export class HttpServer {
@ -19,7 +19,7 @@ export class HttpServer {
constructor( constructor(
@inject("PrimaryLogger") protected logger: ILogger, @inject("PrimaryLogger") protected logger: ILogger,
@inject("DatabaseServer") protected databaseServer: DatabaseServer, @inject("DatabaseService") protected databaseService: DatabaseService,
@inject("HttpServerHelper") protected httpServerHelper: HttpServerHelper, @inject("HttpServerHelper") protected httpServerHelper: HttpServerHelper,
@inject("LocalisationService") protected localisationService: LocalisationService, @inject("LocalisationService") protected localisationService: LocalisationService,
@injectAll("HttpListener") protected httpListeners: IHttpListener[], @injectAll("HttpListener") protected httpListeners: IHttpListener[],
@ -36,6 +36,12 @@ export class HttpServer {
public load(): void { public load(): void {
this.started = false; this.started = false;
// If the database couldn't be validated, don't start the server
if (!this.databaseService.isDatabaseValid())
{
return;
}
/* create server */ /* create server */
const httpServer: Server = http.createServer(); const httpServer: Server = http.createServer();

View File

@ -21,16 +21,19 @@ import { ITemplates } from "@spt/models/spt/templates/ITemplates";
import { ILogger } from "@spt/models/spt/utils/ILogger"; import { ILogger } from "@spt/models/spt/utils/ILogger";
import { DatabaseServer } from "@spt/servers/DatabaseServer"; import { DatabaseServer } from "@spt/servers/DatabaseServer";
import { LocalisationService } from "@spt/services/LocalisationService"; import { LocalisationService } from "@spt/services/LocalisationService";
import { HashUtil } from "@spt/utils/HashUtil";
import { inject, injectable } from "tsyringe"; import { inject, injectable } from "tsyringe";
@injectable() @injectable()
export class DatabaseService { export class DatabaseService {
protected locationConfig: ILocationConfig; protected locationConfig: ILocationConfig;
protected isDataValid: boolean;
constructor( constructor(
@inject("PrimaryLogger") protected logger: ILogger, @inject("PrimaryLogger") protected logger: ILogger,
@inject("DatabaseServer") protected databaseServer: DatabaseServer, @inject("DatabaseServer") protected databaseServer: DatabaseServer,
@inject("LocalisationService") protected localisationService: LocalisationService, @inject("LocalisationService") protected localisationService: LocalisationService,
@inject("HashUtil") protected hashUtil: HashUtil,
) {} ) {}
/** /**
@ -322,4 +325,53 @@ export class DatabaseService {
return this.databaseServer.getTables().templates.locationServices; return this.databaseServer.getTables().templates.locationServices;
} }
/**
* Validates that the database doesn't contain invalid ID data
*/
public validateDatabase(): void {
const start = performance.now();
this.isDataValid =
this.validateTable(this.getQuests(), 'quest') &&
this.validateTable(this.getTraders(), 'trader') &&
this.validateTable(this.getItems(), 'item') &&
this.validateTable(this.getCustomization(), 'customization');
if (!this.isDataValid)
{
this.logger.error(this.localisationService.getText("database-invalid_data"));
}
const validateTime = performance.now() - start
this.logger.debug(`ID validation took: ${validateTime.toFixed(2)}ms`);
}
/**
* Validate that the given table only contains valid MongoIDs
* @param table Table to validate for MongoIDs
* @param tableType The type of table, used in output message
* @returns True if the table only contains valid data
*/
private validateTable(table: Record<string, any>, tableType: string): boolean
{
for (const tableId in table)
{
if (!this.hashUtil.isValidMongoId(tableId))
{
this.logger.error(`Invalid ${tableType} ID: '${tableId}'`);
return false;
}
}
return true;
}
/**
* Check if the database is valid
* @returns True if the database contains valid data, false otherwise
*/
public isDatabaseValid(): boolean {
return this.isDataValid;
}
} }

View File

@ -57,6 +57,15 @@ export class PostDbLoadService {
// add items gets left out,causing warnings // add items gets left out,causing warnings
this.itemBaseClassService.hydrateItemBaseClassCache(); this.itemBaseClassService.hydrateItemBaseClassCache();
// Validate that only mongoIds exist in items, quests, and traders
// Kill the startup if not.
// TODO: We can probably remove this in a couple versions
this.databaseService.validateDatabase();
if (!this.databaseService.isDatabaseValid())
{
return;
}
this.addCustomLooseLootPositions(); this.addCustomLooseLootPositions();
this.adjustMinReserveRaiderSpawnChance(); this.adjustMinReserveRaiderSpawnChance();

View File

@ -10,6 +10,7 @@ import { LocalisationService } from "@spt/services/LocalisationService";
import { EncodingUtil } from "@spt/utils/EncodingUtil"; import { EncodingUtil } from "@spt/utils/EncodingUtil";
import { TimeUtil } from "@spt/utils/TimeUtil"; import { TimeUtil } from "@spt/utils/TimeUtil";
import { inject, injectAll, injectable } from "tsyringe"; import { inject, injectAll, injectable } from "tsyringe";
import { DatabaseService } from "@spt/services/DatabaseService";
@injectable() @injectable()
export class App { export class App {
@ -23,6 +24,7 @@ export class App {
@inject("ConfigServer") protected configServer: ConfigServer, @inject("ConfigServer") protected configServer: ConfigServer,
@inject("EncodingUtil") protected encodingUtil: EncodingUtil, @inject("EncodingUtil") protected encodingUtil: EncodingUtil,
@inject("HttpServer") protected httpServer: HttpServer, @inject("HttpServer") protected httpServer: HttpServer,
@inject("DatabaseService") protected databaseService: DatabaseService,
@injectAll("OnLoad") protected onLoadComponents: OnLoad[], @injectAll("OnLoad") protected onLoadComponents: OnLoad[],
@injectAll("OnUpdate") protected onUpdateComponents: OnUpdate[], @injectAll("OnUpdate") protected onUpdateComponents: OnUpdate[],
) { ) {
@ -58,7 +60,7 @@ export class App {
protected async update(onUpdateComponents: OnUpdate[]): Promise<void> { protected async update(onUpdateComponents: OnUpdate[]): Promise<void> {
// If the server has failed to start, skip any update calls // If the server has failed to start, skip any update calls
if (!this.httpServer.isStarted()) { if (!this.httpServer.isStarted() || !this.databaseService.isDatabaseValid()) {
return; return;
} }