Skip to content

Commit

Permalink
Merge pull request #801 from gettakaro/csmm-import
Browse files Browse the repository at this point in the history
  • Loading branch information
niekcandaele authored Dec 27, 2023
2 parents a6e2874 + 819eb6b commit 58d6246
Show file tree
Hide file tree
Showing 17 changed files with 655 additions and 3 deletions.
36 changes: 36 additions & 0 deletions packages/app-api/src/controllers/GameServerController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,21 @@ class BanOutputDTO extends APIOutput<BanDTO[]> {
declare data: BanDTO[];
}

export class ImportInputDTO extends TakaroDTO<ImportInputDTO> {
@IsJSON()
csmmData: string;
}

class ImportOutputDTO extends TakaroDTO<ImportOutputDTO> {
@IsString()
id!: string;
}

class ImportOutputDTOAPI extends APIOutput<ImportOutputDTO> {
@Type(() => ImportOutputDTO)
@ValidateNested()
declare data: ImportOutputDTO;
}
@OpenAPI({
security: [{ domainAuth: [] }],
})
Expand Down Expand Up @@ -409,4 +424,25 @@ export class GameServerController {
const result = await service.getPlayers(params.id);
return apiResponse(result);
}

@Get('/gameserver/import/:id')
@UseBefore(AuthService.getAuthMiddleware([PERMISSIONS.MANAGE_GAMESERVERS]))
@ResponseSchema(ImportOutputDTOAPI)
async getImport(@Req() req: AuthenticatedRequest, @Params() params: ImportOutputDTO) {
const service = new GameServerService(req.domainId);
const result = await service.getImport(params.id);
return apiResponse(result);
}

@Post('/gameserver/import')
@UseBefore(AuthService.getAuthMiddleware([PERMISSIONS.MANAGE_GAMESERVERS]))
@OpenAPI({
description: 'Import a gameserver from CSMM',
})
@ResponseSchema(ImportOutputDTOAPI)
async importFromCSMM(@Req() req: AuthenticatedRequest, @Body() data: ImportInputDTO) {
const service = new GameServerService(req.domainId);
const result = await service.import(data);
return apiResponse(result);
}
}
2 changes: 1 addition & 1 deletion packages/app-api/src/lib/steamApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class SteamApi {
return config;
});

axios.interceptors.request.use((request) => {
this._client.interceptors.request.use((request) => {
this.log.info(`➡️ ${request.method?.toUpperCase()} ${request.url}`, {
method: request.method,
url: request.url,
Expand Down
4 changes: 4 additions & 0 deletions packages/app-api/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { PlayerOnGameServerController } from './controllers/PlayerOnGameserverCo
import { ItemController } from './controllers/ItemController.js';
import { ItemsSyncWorker } from './workers/ItemsSyncWorker.js';
import { PlayerSyncWorker } from './workers/playerSyncWorker.js';
import { CSMMImportWorker } from './workers/csmmImportWorker.js';

export const server = new HTTP(
{
Expand Down Expand Up @@ -100,6 +101,9 @@ async function main() {

new PlayerSyncWorker();
log.info('👷 playerSync worker started');

new CSMMImportWorker();
log.info('👷 csmmImport worker started');
}

await getSocketServer(server.server as HttpServer);
Expand Down
30 changes: 30 additions & 0 deletions packages/app-api/src/service/GameServerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { getEmptySystemConfigSchema } from '../lib/systemConfig.js';
import { PlayerService } from './PlayerService.js';
import { PlayerOnGameServerService, PlayerOnGameServerUpdateDTO } from './PlayerOnGameserverService.js';
import { ItemCreateDTO, ItemsService } from './ItemsService.js';
import { ImportInputDTO } from '../controllers/GameServerController.js';
const Ajv = _Ajv as unknown as typeof _Ajv.default;

const ajv = new Ajv({ useDefaults: true });
Expand Down Expand Up @@ -467,4 +468,33 @@ export class GameServerService extends TakaroService<
})
);
}

async getImport(id: string) {
const job = await queueService.queues.csmmImport.queue.bullQueue.getJob(id);

if (!job) {
throw new errors.NotFoundError('Job not found');
}

return {
jobId: job.id,
status: await job.getState(),
failedReason: job.failedReason,
};
}

async import(data: ImportInputDTO) {
let parsed;
try {
parsed = JSON.parse(data.csmmData);
} catch (error) {
throw new errors.BadRequestError('Invalid JSON');
}

const job = await queueService.queues.csmmImport.queue.add({ csmmExport: parsed, domainId: this.domainId });

return {
id: job.id,
};
}
}
162 changes: 162 additions & 0 deletions packages/app-api/src/workers/csmmImportWorker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import { Job } from 'bullmq';
import { TakaroWorker, ICSMMImportData } from '@takaro/queues';
import { ctx, errors, logger } from '@takaro/util';
import { config } from '../config.js';
import { GameServerCreateDTO, GameServerService } from '../service/GameServerService.js';
import { GAME_SERVER_TYPE } from '@takaro/gameserver';
import { RoleCreateInputDTO, RoleService } from '../service/RoleService.js';
import { PlayerService } from '../service/PlayerService.js';
import { PlayerOnGameServerService } from '../service/PlayerOnGameserverService.js';
import { IGamePlayer } from '@takaro/modules';

const log = logger('worker:csmmImport');

interface ICSMMPlayer {
// Can be xbl ID too...
// XBL_xxx
steamId: string;
name: string;
ip: string;
crossId: string;
role: number;
}

interface ICSMMRole {
id: number;
name: string;
}

interface ICSMMData {
server: {
name: string;
ip: string;
webPort: number;
authName: string;
authToken: string;
};
players: ICSMMPlayer[];
roles: ICSMMRole[];
}

export class CSMMImportWorker extends TakaroWorker<ICSMMImportData> {
constructor() {
super(config.get('queues.csmmImport.name'), 1, process);
}
}

async function process(job: Job<ICSMMImportData>) {
ctx.addData({
domain: job.data.domainId,
jobId: job.id,
});

const data = job.data.csmmExport as unknown as ICSMMData;

const gameserverService = new GameServerService(job.data.domainId);
const roleService = new RoleService(job.data.domainId);
const playerService = new PlayerService(job.data.domainId);
const pogService = new PlayerOnGameServerService(job.data.domainId);

// Check if the server already exists
const existingServer = await gameserverService.find({
filters: {
name: [data.server.name],
},
});

if (existingServer.total) {
throw new errors.BadRequestError(`Server with name ${data.server.name} already exists`);
}

const server = await gameserverService.create(
await new GameServerCreateDTO().construct({
name: data.server.name,
type: GAME_SERVER_TYPE.SEVENDAYSTODIE,
connectionInfo: JSON.stringify({
host: `${data.server.ip}:${data.server.webPort}`,
adminUser: data.server.authName,
adminToken: data.server.authToken,
useTls: data.server.webPort === 443,
}),
})
);

ctx.addData({
gameServer: server.id,
});

// Sync the roles
for (const role of data.roles) {
const existing = await roleService.find({
filters: {
name: [role.name],
},
});

if (existing.total) {
continue;
}

await roleService.create(
await new RoleCreateInputDTO().construct({
name: role.name,
permissions: [],
})
);
}

const roles = await roleService.find({});

// Sync the players
for (const player of data.players) {
if (!player.crossId) {
log.warn(`Player ${player.name} has no crossId, skipping player resolving`);
continue;
}
const createData = await new IGamePlayer().construct({
name: player.name,
gameId: player.crossId.replace('EOS_', ''),
epicOnlineServicesId: player.crossId.replace('EOS_', ''),
});

if (player.steamId.startsWith('XBL_')) {
createData.xboxLiveId = player.steamId.replace('XBL_', '');
} else {
createData.steamId = player.steamId;
}

await playerService.sync(await new IGamePlayer().construct(createData), server.id);
}

// Sync the player roles
for (const player of data.players) {
if (!player.crossId) {
log.warn(`Player ${player.name} has no crossId, skipping role assignment`);
continue;
}
const pog = await pogService.findAssociations(player.crossId.replace('EOS_', ''), server.id);

if (!pog.length) {
log.warn(`Player ${player.name} has no player on game server association, skipping role assignment`);
continue;
}

const CSMMRole = data.roles.find((r) => r.id === player.role);
if (!CSMMRole) {
log.warn(`Player ${player.name} has no role with id ${player.role}, skipping role assignment`);
continue;
}

const takaroRole = roles.results.find((r) => r.name === CSMMRole.name);

if (!takaroRole) {
log.warn(`Player ${player.name} has no role with name ${CSMMRole.name}, skipping role assignment`);
continue;
}

if (!takaroRole.system) {
log.info(`Assigning role ${takaroRole.name} to player ${player.name}`);
await playerService.assignRole(takaroRole.id, pog[0].playerId, server.id);
}
}
}
2 changes: 2 additions & 0 deletions packages/app-api/src/workers/playerSyncWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ export async function processJob(job: Job<IGameServerQueueData>) {
})
);

await Promise.allSettled(domainPromises);

return;
}

Expand Down
Loading

0 comments on commit 58d6246

Please sign in to comment.