Skip to content

Commit 5907022

Browse files
committed
feat: export members
1 parent 22c1237 commit 5907022

File tree

14 files changed

+250
-48
lines changed

14 files changed

+250
-48
lines changed

libs/clash-client/src/clash-client.helper.ts

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { APIClanWarAttack, APIPlayer, RawData } from 'clashofclans.js';
2-
import { SUPER_TROOPS } from './constants';
2+
import { HOME_TROOPS, SUPER_TROOPS } from './constants';
33

4-
export const RAW_TROOPS_FILTERED = RawData.RawUnits.filter(
5-
(unit) => !unit.seasonal && unit.category !== 'equipment' && !(unit.name in SUPER_TROOPS),
6-
);
4+
export const RAW_TROOPS_FILTERED = RawData.RawUnits.filter((unit) => !unit.seasonal)
5+
.filter((unit) => unit.category !== 'equipment')
6+
.filter((unit) => !SUPER_TROOPS.includes(unit.name))
7+
.filter((unit) => HOME_TROOPS.includes(unit.name));
78

89
const getUnitLevelsMap = (data: APIPlayer) => {
910
const map = new Map<string, number>();
@@ -31,13 +32,13 @@ export function remainingHeroUpgrades(data: APIPlayer) {
3132
const unitLevels = getUnitLevelsMap(data);
3233

3334
const rem = RAW_TROOPS_FILTERED.reduce(
34-
(prev, unit) => {
35+
(record, unit) => {
3536
if (unit.category === 'hero' && unit.village === 'home') {
3637
const level = unitLevels.get(`${unit.name}:${unit.village}:${unit.category}`) ?? 0;
37-
prev.levels += level;
38-
prev.total += unit.levels[data.townHallLevel - 1];
38+
record.levels += level;
39+
record.total += unit.levels[data.townHallLevel - 1];
3940
}
40-
return prev;
41+
return record;
4142
},
4243
{ total: 0, levels: 0 },
4344
);
@@ -49,13 +50,13 @@ export function remainingHeroUpgrades(data: APIPlayer) {
4950
export function remainingLabUpgrades(data: APIPlayer) {
5051
const unitLevels = getUnitLevelsMap(data);
5152
const rem = RAW_TROOPS_FILTERED.reduce(
52-
(prev, unit) => {
53+
(record, unit) => {
5354
if (unit.category !== 'hero' && unit.village === 'home') {
5455
const level = unitLevels.get(`${unit.name}:${unit.village}:${unit.category}`) ?? 0;
55-
prev.levels += level;
56-
prev.total += unit.levels[data.townHallLevel - 1];
56+
record.levels += level;
57+
record.total += unit.levels[data.townHallLevel - 1];
5758
}
58-
return prev;
59+
return record;
5960
},
6061
{ total: 0, levels: 0 },
6162
);
@@ -66,18 +67,18 @@ export function remainingLabUpgrades(data: APIPlayer) {
6667
export function calculateRushedPercentage(data: APIPlayer) {
6768
const unitLevels = getUnitLevelsMap(data);
6869
const rem = RAW_TROOPS_FILTERED.reduce(
69-
(prev, unit) => {
70+
(record, unit) => {
7071
if (unit.village === 'home') {
7172
const level = unitLevels.get(`${unit.name}:${unit.village}:${unit.category}`) ?? 0;
7273
// Check levels for previous TH (TH-2 index)
7374
const targetLevel = unit.levels[data.townHallLevel - 2];
7475
// If target level exists (might be undefined for low THs i.e. TH1), default to 0 to be safe
7576
const safeTargetLevel = targetLevel ?? 0;
7677

77-
prev.levels += Math.min(level, safeTargetLevel);
78-
prev.total += safeTargetLevel;
78+
record.levels += Math.min(level, safeTargetLevel);
79+
record.total += safeTargetLevel;
7980
}
80-
return prev;
81+
return record;
8182
},
8283
{ total: 0, levels: 0 },
8384
);

libs/constants/redis-keys.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ export enum QueueTypes {
33
TASKS = 'tasks',
44
}
55

6+
export enum JobTypes {
7+
EXPORT_MEMBERS = 'export_members',
8+
}
9+
610
export enum RedisKeys {
711
USER_BLOCKED = 'user_blocked',
812
HANDOFF_TOKEN = 'handoff_token',
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Config } from '@app/constants';
2+
import { applyDecorators } from '@nestjs/common';
3+
import { Cron } from '@nestjs/schedule';
4+
import { SentryCron } from '@sentry/nestjs';
5+
6+
export function CronTab(cronTime: string, options: { monitor: string }): MethodDecorator {
7+
return applyDecorators(
8+
Cron(cronTime, {
9+
timeZone: 'Etc/UTC',
10+
disabled: !Config.CRON_ENABLED,
11+
}),
12+
SentryCron(options.monitor, {
13+
schedule: {
14+
type: 'crontab',
15+
value: cronTime,
16+
},
17+
timezone: 'Etc/UTC',
18+
}),
19+
);
20+
}

libs/decorators/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export * from './api-exclude-route.decorator';
22
export * from './api-key-auth.decorator';
33
export * from './cache-control.decorator';
4+
export * from './crontab.decorators';
45
export * from './date-time.decorator';
56
export * from './enum-array.decorator';
67
export * from './enum-string.decorator';

src/app.module.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import { WebhookModule } from './webhook/webhook.module';
3838

3939
BullModule.forRootAsync({
4040
useFactory: (configService: ConfigService) => ({
41-
url: configService.getOrThrow('REDIS_URL'),
41+
url: `${configService.getOrThrow('REDIS_URL')}/1`,
4242
}),
4343
inject: [ConfigService],
4444
}),
@@ -47,7 +47,7 @@ import { WebhookModule } from './webhook/webhook.module';
4747
isGlobal: true,
4848
useFactory: (configService: ConfigService) => ({
4949
ttl: 10 * 60 * 1000,
50-
stores: [new KeyvRedis(configService.getOrThrow('REDIS_URL'))],
50+
stores: [new KeyvRedis(`${configService.getOrThrow('REDIS_URL')}/2`)],
5151
}),
5252
inject: [ConfigService],
5353
}),

src/auth/auth.service.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
JwtUser,
2626
JwtUserInput,
2727
LoginOkDto,
28+
swaggerUser,
2829
UserRoles,
2930
} from './dto';
3031

@@ -59,16 +60,15 @@ export class AuthService {
5960

6061
async loginWithTurnstile(token: string, remoteIp: string): Promise<LoginOkDto> {
6162
await this.verifyTurnstile(token, remoteIp);
62-
const swaggerUserId = '000000000000000000';
6363

6464
return {
65-
userId: swaggerUserId,
66-
roles: [UserRoles.VIEWER],
65+
userId: swaggerUser.userId,
66+
roles: swaggerUser.roles,
6767
accessToken: this.createJwt(
6868
{
69-
userId: swaggerUserId,
70-
roles: [UserRoles.VIEWER],
71-
username: 'swagger_user',
69+
userId: swaggerUser.userId,
70+
roles: swaggerUser.roles,
71+
username: swaggerUser.username,
7272
remoteIp,
7373
},
7474
{ expiresIn: '5m' },

src/auth/dto/jwt-user.dto.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,14 @@ export const fallbackUser: JwtUser = {
3838
exp: 0,
3939
iat: 0,
4040
};
41+
42+
export const swaggerUser: JwtUser = {
43+
userId: '000000000000000000',
44+
username: 'swagger_user',
45+
roles: [UserRoles.VIEWER],
46+
jti: randomUUID(),
47+
guildIds: [],
48+
version: '1',
49+
exp: 0,
50+
iat: 0,
51+
};

src/db/mongodb.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ export enum Collections {
3333

3434
PLAYERS = 'Players',
3535

36+
GOOGLE_SHEETS = 'GoogleSheets',
37+
3638
CLAN_GAMES_POINTS = 'ClanGamesPoints',
3739

3840
ROSTERS = 'Rosters',

src/exports/dto/export-members.dto.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1-
import { ArrayMaxSize, ArrayMinSize, IsString } from 'class-validator';
1+
import { ArrayMaxSize, ArrayMinSize, IsBoolean, IsString } from 'class-validator';
22

33
export class ExportMembersInput {
44
@IsString({ each: true })
55
@ArrayMaxSize(100)
66
@ArrayMinSize(1)
77
clanTags: string[];
8+
9+
@IsString()
10+
guildId: string;
11+
12+
@IsBoolean()
13+
scheduled: boolean;
814
}
915

1016
export class ExportMembersOutputDto {

src/exports/exports-members.service.ts

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
11
import {
2+
calculateRushedPercentage,
23
ClashClientService,
34
HERO_EQUIPMENT,
45
HERO_PETS,
56
HOME_HEROES,
67
HOME_TROOPS,
8+
remainingHeroUpgrades,
9+
remainingLabUpgrades,
710
ROLES_MAP,
811
} from '@app/clash-client';
912
import { QueueTypes } from '@app/constants';
10-
import { CreateGoogleSheet, GoogleSheetService } from '@app/google-sheet';
13+
import { CreateGoogleSheet } from '@app/google-sheet';
1114
import { InjectQueue } from '@nestjs/bull';
1215
import { Inject, Injectable } from '@nestjs/common';
1316
import { Queue } from 'bull';
1417
import { sum } from 'lodash';
1518
import { Db } from 'mongodb';
1619
import { Collections, MONGODB_TOKEN, PlayerLinksEntity } from '../db';
1720
import { ExportMembersInput, ExportSheetInputDto } from './dto';
21+
import { ReusableSheetService, SheetType } from './services/reusable-sheet.service';
1822

1923
const ALLOWED_ACHIEVEMENTS = [
2024
'Gold Grab',
@@ -37,7 +41,7 @@ export class ExportsMembersService {
3741
@InjectQueue(QueueTypes.EXPORT) private queue: Queue<ExportSheetInputDto>,
3842
@Inject(MONGODB_TOKEN) private db: Db,
3943
private clashClientService: ClashClientService,
40-
private googleSheetService: GoogleSheetService,
44+
private reusableSheetService: ReusableSheetService,
4145
) {}
4246

4347
async exportClanMembers(input: ExportMembersInput) {
@@ -133,6 +137,10 @@ export class ExportsMembersService {
133137
heroes: HOME_HEROES.map((name) => ({ name, level: heroes[name] || 0 })),
134138
equipment: HERO_EQUIPMENT.map((name) => ({ name, level: equipment[name] || 0 })),
135139

140+
rushedPercentage: Number(calculateRushedPercentage(player)),
141+
remainingHeroUpgradesPercentage: Number(remainingHeroUpgrades(player)),
142+
remainingLabUpgradesPercentage: Number(remainingLabUpgrades(player)),
143+
136144
userId: linksMap[player.tag]?.userId,
137145
username: linksMap[player.tag]?.username,
138146
displayName: linksMap[player.tag]?.displayName,
@@ -176,12 +184,9 @@ export class ExportsMembersService {
176184
m.leagueTier,
177185
m.warPreference,
178186
m.townHallLevel,
179-
0,
180-
0,
181-
0,
182-
// m.rushed,
183-
// m.labRem,
184-
// m.heroRem,
187+
m.rushedPercentage,
188+
m.remainingLabUpgradesPercentage,
189+
m.remainingHeroUpgradesPercentage,
185190
...m.heroes.map((h) => h.level),
186191
...m.pets.map((h) => h.level),
187192
...m.achievements.map((v) => v.value),
@@ -201,8 +206,7 @@ export class ExportsMembersService {
201206
m.name,
202207
m.tag,
203208
m.townHallLevel,
204-
// m.rushed,
205-
0,
209+
m.rushedPercentage,
206210
...m.troops.map((h) => h.level),
207211
]),
208212
},
@@ -223,7 +227,14 @@ export class ExportsMembersService {
223227
},
224228
];
225229

226-
return await this.googleSheetService.createGoogleSheet('Clan Members', sheets);
230+
return this.reusableSheetService.createOrUpdateSheet({
231+
clanTags: input.clanTags,
232+
sheets,
233+
guildId: input.guildId,
234+
label: 'Clan Members',
235+
scheduled: input.scheduled,
236+
sheetType: SheetType.CLAN_MEMBERS,
237+
});
227238
}
228239

229240
private get links() {
@@ -245,6 +256,10 @@ interface MembersChunk {
245256
warPreference: string;
246257
townHallLevel: number;
247258

259+
rushedPercentage: number;
260+
remainingHeroUpgradesPercentage: number;
261+
remainingLabUpgradesPercentage: number;
262+
248263
userId: string;
249264
username: string;
250265
displayName: string;

0 commit comments

Comments
 (0)