From 4bb70b28356f20c2bc3c748a1dff103b547f293c Mon Sep 17 00:00:00 2001 From: suvajit Date: Sat, 13 Apr 2024 15:50:40 +0530 Subject: [PATCH] Fix attack log --- .github/workflows/aws-ecr-push.yml | 2 +- apps/service-auth/src/app.module.ts | 54 +++--- apps/service-auth/src/clans/clans.service.ts | 27 +-- .../src/consumer/consumer.service.ts | 26 +-- .../players/dto/attack-history-output.dto.ts | 31 ---- .../src/players/players.service.ts | 156 ++++-------------- apps/service-clans/src/main.ts | 2 + .../service-clans/src/service-clans.module.ts | 4 +- libs/entities/src/wars.entity.ts | 1 + libs/helper/src/helper.war.service.ts | 25 +++ libs/helper/src/index.ts | 1 + 11 files changed, 101 insertions(+), 228 deletions(-) create mode 100644 libs/helper/src/helper.war.service.ts diff --git a/.github/workflows/aws-ecr-push.yml b/.github/workflows/aws-ecr-push.yml index feb6ec0..b6ff041 100644 --- a/.github/workflows/aws-ecr-push.yml +++ b/.github/workflows/aws-ecr-push.yml @@ -15,7 +15,7 @@ jobs: strategy: matrix: - service: [service-auth, service-capital, service-clans, service-wars] + service: [service-auth, service-clans] runs-on: buildjet-4vcpu-ubuntu-2204-arm diff --git a/apps/service-auth/src/app.module.ts b/apps/service-auth/src/app.module.ts index 0c3908b..2f07270 100644 --- a/apps/service-auth/src/app.module.ts +++ b/apps/service-auth/src/app.module.ts @@ -1,11 +1,9 @@ -import { KafkaConsumerModule, KafkaProducerModule } from '@app/kafka'; import { MongoDbModule } from '@app/mongodb'; import { RedisModule } from '@app/redis'; import { Module } from '@nestjs/common'; -import { ConfigModule, ConfigService } from '@nestjs/config'; +import { ConfigModule } from '@nestjs/config'; import { AuthModule } from './auth/auth.module'; import { ClansModule } from './clans/clans.module'; -import { ConsumerModule } from './consumer/consumer.module'; import { GuildsModule } from './guilds/guilds.module'; import { LinksModule } from './links/links.module'; import { PlayersModule } from './players/players.module'; @@ -20,31 +18,31 @@ import { PlayersModule } from './players/players.module'; GuildsModule, LinksModule, PlayersModule, - KafkaProducerModule.forRootAsync({ - useFactory: (configService: ConfigService) => { - return { - kafkaConfig: { - clientId: 'kafka-client-id', - brokers: [configService.getOrThrow('KAFKA_BROKER')], - }, - producerConfig: {}, - }; - }, - inject: [ConfigService], - }), - KafkaConsumerModule.forRootAsync({ - useFactory: (configService: ConfigService) => { - return { - kafkaConfig: { - clientId: 'kafka-client-id', - brokers: [configService.getOrThrow('KAFKA_BROKER')], - }, - consumerConfig: { groupId: 'kafka-consumer-group' }, - }; - }, - inject: [ConfigService], - }), - ConsumerModule, + // KafkaProducerModule.forRootAsync({ + // useFactory: (configService: ConfigService) => { + // return { + // kafkaConfig: { + // clientId: 'kafka-client-id', + // brokers: [configService.getOrThrow('KAFKA_BROKER')], + // }, + // producerConfig: {}, + // }; + // }, + // inject: [ConfigService], + // }), + // KafkaConsumerModule.forRootAsync({ + // useFactory: (configService: ConfigService) => { + // return { + // kafkaConfig: { + // clientId: 'kafka-client-id', + // brokers: [configService.getOrThrow('KAFKA_BROKER')], + // }, + // consumerConfig: { groupId: 'kafka-consumer-group' }, + // }; + // }, + // inject: [ConfigService], + // }), + // ConsumerModule, ], controllers: [], providers: [], diff --git a/apps/service-auth/src/clans/clans.service.ts b/apps/service-auth/src/clans/clans.service.ts index 9532928..55b8419 100644 --- a/apps/service-auth/src/clans/clans.service.ts +++ b/apps/service-auth/src/clans/clans.service.ts @@ -7,6 +7,7 @@ import { ClanWarsEntity, PlayerLinksEntity, } from '@app/entities'; +import { getPreviousBestAttack } from '@app/helper'; import { Inject, Injectable, NotFoundException } from '@nestjs/common'; import { APIClanWarAttack, APIWarClan } from 'clashofclans.js'; import { Collection } from 'mongodb'; @@ -293,7 +294,7 @@ export class ClansService { member.participated += 1; for (const atk of m.attacks ?? []) { - const previousBestAttack = this.getPreviousBestAttack(__attacks, opponent, atk); + const previousBestAttack = getPreviousBestAttack(__attacks, opponent, atk); member.attacks += 1; member.stars += atk.stars; member.newStars += previousBestAttack @@ -337,28 +338,4 @@ export class ClansService { if (clan.stars > opponent.stars) return 'won'; return 'lost'; } - - private getPreviousBestAttack( - attacks: APIClanWarAttack[], - opponent: APIWarClan, - atk: APIClanWarAttack, - ) { - const defender = opponent.members.find((m) => m.tag === atk.defenderTag); - const defenderDefenses = attacks.filter((atk) => atk.defenderTag === defender?.tag); - const isFresh = - defenderDefenses.length === 0 || - atk.order === Math.min(...defenderDefenses.map((d) => d.order)); - const previousBestAttack = isFresh - ? null - : [...attacks] - .filter( - (_atk) => - _atk.defenderTag === defender?.tag && - _atk.order < atk.order && - _atk.attackerTag !== atk.attackerTag, - ) - .sort((a, b) => b.destructionPercentage ** b.stars - a.destructionPercentage ** a.stars) - .at(0) ?? null; - return isFresh ? null : previousBestAttack; - } } diff --git a/apps/service-auth/src/consumer/consumer.service.ts b/apps/service-auth/src/consumer/consumer.service.ts index de1cb92..658df31 100644 --- a/apps/service-auth/src/consumer/consumer.service.ts +++ b/apps/service-auth/src/consumer/consumer.service.ts @@ -2,27 +2,19 @@ import { KAFKA_CONSUMER } from '@app/kafka'; import { Inject, Injectable } from '@nestjs/common'; import { Consumer } from 'kafkajs'; -enum LogType { - CLAN_LEVEL_CHANGE = 'clan_level_change', - CLAN_WAR_LEAGUE_CHANGE = 'clan_war_league_change', - CAPITAL_LEAGUE_CHANGE = 'capital_league_change', - CLAN_MEMBER_CHANGE = 'clan_member_change', -} - @Injectable() export class ConsumerService { constructor(@Inject(KAFKA_CONSUMER) private consumer: Consumer) {} async onModuleInit() { - await this.consumer.subscribe({ topics: Object.values(LogType), fromBeginning: true }); - - await this.consumer.run({ - eachMessage: async ({ message, topic }) => { - console.log({ - topic, - value: message.value?.toString(), - }); - }, - }); + // await this.consumer.subscribe({ topics: Object.values(LogType), fromBeginning: true }); + // await this.consumer.run({ + // eachMessage: async ({ topic, message }) => { + // console.log({ + // topic, + // value: message.value?.toString(), + // }); + // }, + // }); } } diff --git a/apps/service-auth/src/players/dto/attack-history-output.dto.ts b/apps/service-auth/src/players/dto/attack-history-output.dto.ts index 63aaac5..11e396d 100644 --- a/apps/service-auth/src/players/dto/attack-history-output.dto.ts +++ b/apps/service-auth/src/players/dto/attack-history-output.dto.ts @@ -1,36 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -export class AttackHistoryAggregated { - id: number; - warType: number; - startTime: string; - endTime: string; - clan: { - name: string; - tag: string; - }; - opponent: { - name: string; - tag: string; - }; - members: { - name: string; - tag: string; - townhallLevel: number; - mapPosition: number; - attacks?: { - stars: number; - defenderTag: string; - destructionPercentage: number; - }[]; - }[]; - defenders: { - tag: string; - townhallLevel: number; - mapPosition: number; - }[]; -} - export class AttackRecord { stars: number; defenderTag: string; diff --git a/apps/service-auth/src/players/players.service.ts b/apps/service-auth/src/players/players.service.ts index e7a3fe3..2ae1e46 100644 --- a/apps/service-auth/src/players/players.service.ts +++ b/apps/service-auth/src/players/players.service.ts @@ -1,9 +1,10 @@ import { Collections } from '@app/constants'; import { ClanWarsEntity } from '@app/entities'; +import { getPreviousBestAttack } from '@app/helper'; import { Inject, Injectable } from '@nestjs/common'; import moment from 'moment'; import { Collection } from 'mongodb'; -import { AttackHistoryAggregated, AttackHistoryOutput, CWLAttackSummaryOutput } from './dto'; +import { AttackHistoryOutput, CWLAttackSummaryOutput } from './dto'; @Injectable() export class PlayersService { @@ -13,17 +14,10 @@ export class PlayersService { ) {} async getClanWarHistory(playerTag: string, months: number) { - const cursor = this.clanWarsCollection.aggregate([ + const cursor = this.clanWarsCollection.aggregate([ { $match: { - $or: [ - { - 'clan.members.tag': playerTag, - }, - { - 'opponent.members.tag': playerTag, - }, - ], + $or: [{ 'clan.members.tag': playerTag }, { 'opponent.members.tag': playerTag }], preparationStartTime: { $gte: moment().startOf('month').subtract(months, 'month').toDate(), }, @@ -37,127 +31,43 @@ export class PlayersService { _id: -1, }, }, - { - $set: { - members: { - $filter: { - input: { - $concatArrays: ['$opponent.members', '$clan.members'], - }, - as: 'member', - cond: { - $eq: ['$$member.tag', playerTag], - }, - }, - }, - }, - }, - { - $set: { - defenderTags: { - $arrayElemAt: ['$members.attacks.defenderTag', 0], - }, - }, - }, - { - $set: { - defenders: { - $filter: { - input: { - $concatArrays: ['$opponent.members', '$clan.members'], - }, - as: 'member', - cond: { - $in: [ - '$$member.tag', - { - $cond: [{ $anyElementTrue: [['$defenderTags']] }, '$defenderTags', []], - }, - ], - }, - }, - }, - }, - }, - { - $project: { - id: 1, - warType: 1, - startTime: '$preparationStartTime', - endTime: '$preparationStartTime', - clan: { - $cond: [ - { - $in: [playerTag, '$clan.members.tag'], - }, - { - name: '$clan.name', - tag: '$clan.tag', - }, - { - name: '$opponent.name', - tag: '$opponent.tag', - }, - ], - }, - opponent: { - $cond: [ - { - $in: [playerTag, '$clan.members.tag'], - }, - { - name: '$opponent.name', - tag: '$opponent.tag', - }, - { - name: '$clan.name', - tag: '$clan.tag', - }, - ], - }, - members: { - tag: 1, - name: 1, - townhallLevel: 1, - mapPosition: 1, - attacks: { - stars: 1, - defenderTag: 1, - destructionPercentage: 1, - }, - }, - defenders: { - tag: 1, - townhallLevel: 1, - mapPosition: 1, - }, - }, - }, ]); - const history = await cursor.toArray(); - const wars: AttackHistoryOutput[] = []; - for (const war of history) { - const attacker = war.members.at(0)!; - const attacks = (attacker.attacks ?? []).map((attack) => { - const defender = war.defenders.find((defender) => defender.tag === attack.defenderTag)!; - return { ...attack, defender }; + + for await (const war of cursor) { + const isPlayerInClan = war.clan.members.find((mem) => mem.tag === playerTag); + const clan = isPlayerInClan ? war.clan : war.opponent; + const opponent = isPlayerInClan ? war.opponent : war.clan; + + clan.members.sort((a, b) => a.mapPosition - b.mapPosition); + opponent.members.sort((a, b) => a.mapPosition - b.mapPosition); + + const __attacks = clan.members.map((mem) => mem.attacks ?? []).flat(); + const attacker = clan.members.find((mem) => mem.tag === playerTag)!; + + const attacks = (attacker.attacks ?? []).map((atk) => { + const previousBestAttack = getPreviousBestAttack(__attacks, opponent, atk); + const defender = opponent.members.find((mem) => mem.tag === atk.defenderTag)!; + + return { + ...atk, + newStars: previousBestAttack + ? Math.max(0, atk.stars - previousBestAttack.stars) + : atk.stars, + oldStars: previousBestAttack?.stars ?? 0, + defender, + }; }); wars.push({ id: war.id, - warType: war.warType, - startTime: war.startTime, + clan, + opponent, endTime: war.endTime, - clan: war.clan, - opponent: war.opponent, - attacker: { - name: attacker.name, - tag: attacker.tag, - townhallLevel: attacker.townhallLevel, - mapPosition: attacker.mapPosition, - }, + startTime: war.startTime, + warType: war.warType, + attacker, attacks, }); } diff --git a/apps/service-clans/src/main.ts b/apps/service-clans/src/main.ts index 5c560d4..7208d0e 100644 --- a/apps/service-clans/src/main.ts +++ b/apps/service-clans/src/main.ts @@ -6,6 +6,8 @@ async function bootstrap() { const app = await NestFactory.create(ServiceClansModule); const logger = new Logger('NestApplication'); + app.enableShutdownHooks(); + const port = process.env.PORT || 8082; await app.listen(port); diff --git a/apps/service-clans/src/service-clans.module.ts b/apps/service-clans/src/service-clans.module.ts index 5fa2c2d..095f6f6 100644 --- a/apps/service-clans/src/service-clans.module.ts +++ b/apps/service-clans/src/service-clans.module.ts @@ -4,7 +4,6 @@ import { MongoDbModule } from '@app/mongodb'; import { RedisModule } from '@app/redis'; import { Module } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; -import { logLevel } from 'kafkajs'; import { ServiceClansController } from './service-clans.controller'; import { ClansService } from './service-clans.service'; @@ -20,9 +19,8 @@ import { ClansService } from './service-clans.service'; kafkaConfig: { clientId: 'kafka-client-id', brokers: [configService.getOrThrow('KAFKA_BROKER')], - logLevel: logLevel.NOTHING, }, - producerConfig: {}, + producerConfig: { allowAutoTopicCreation: true }, }; }, inject: [ConfigService], diff --git a/libs/entities/src/wars.entity.ts b/libs/entities/src/wars.entity.ts index 0fbe68a..0b088df 100644 --- a/libs/entities/src/wars.entity.ts +++ b/libs/entities/src/wars.entity.ts @@ -4,5 +4,6 @@ export interface ClanWarsEntity extends APIClanWar { warTag?: string; uid: string; id: number; + warType: number; leagueGroupId?: number; } diff --git a/libs/helper/src/helper.war.service.ts b/libs/helper/src/helper.war.service.ts new file mode 100644 index 0000000..0fd9f7b --- /dev/null +++ b/libs/helper/src/helper.war.service.ts @@ -0,0 +1,25 @@ +import { APIClanWarAttack, APIWarClan } from 'clashofclans.js'; + +export function getPreviousBestAttack( + attacks: APIClanWarAttack[], + opponent: APIWarClan, + atk: APIClanWarAttack, +) { + const defender = opponent.members.find((m) => m.tag === atk.defenderTag); + const defenderDefenses = attacks.filter((atk) => atk.defenderTag === defender?.tag); + const isFresh = + defenderDefenses.length === 0 || + atk.order === Math.min(...defenderDefenses.map((d) => d.order)); + const previousBestAttack = isFresh + ? null + : [...attacks] + .filter( + (_atk) => + _atk.defenderTag === defender?.tag && + _atk.order < atk.order && + _atk.attackerTag !== atk.attackerTag, + ) + .sort((a, b) => b.destructionPercentage ** b.stars - a.destructionPercentage ** a.stars) + .at(0) ?? null; + return isFresh ? null : previousBestAttack; +} diff --git a/libs/helper/src/index.ts b/libs/helper/src/index.ts index dcd68fa..6e475ee 100644 --- a/libs/helper/src/index.ts +++ b/libs/helper/src/index.ts @@ -2,3 +2,4 @@ export * from './app-cluster.service'; export * from './helper.common'; export * from './helper.module'; export * from './helper.service'; +export * from './helper.war.service';