feat: getProfileViewingData for clans
All checks were successful
Build / build (18) (push) Successful in 42s
Build / build (22) (pull_request) Successful in 1m24s
Build / build (20) (push) Successful in 1m14s
Build / build (22) (push) Successful in 1m18s
Build / build (18) (pull_request) Successful in 41s
Build / build (20) (pull_request) Successful in 1m12s

This commit is contained in:
Sainan 2025-03-31 22:25:40 +02:00
parent d033c2bc12
commit 1ca3a210d9
2 changed files with 230 additions and 98 deletions

View File

@ -1,5 +1,5 @@
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers"; import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
import { Guild } from "@/src/models/guildModel"; import { Guild, GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel";
import { Inventory, TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; import { Inventory, TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import { Loadout } from "@/src/models/inventoryModels/loadoutModel"; import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
import { Account } from "@/src/models/loginModel"; import { Account } from "@/src/models/loginModel";
@ -19,20 +19,18 @@ import {
} from "@/src/types/inventoryTypes/inventoryTypes"; } from "@/src/types/inventoryTypes/inventoryTypes";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { catBreadHash } from "../api/inventoryController"; import { catBreadHash } from "../api/inventoryController";
import { ExportCustoms } from "warframe-public-export-plus"; import { ExportCustoms, ExportDojoRecipes } from "warframe-public-export-plus";
import { IStatsClient } from "@/src/types/statTypes";
import { toStoreItem } from "@/src/services/itemDataService";
export const getProfileViewingDataController: RequestHandler = async (req, res) => { export const getProfileViewingDataController: RequestHandler = async (req, res) => {
if (!req.query.playerId) { if (req.query.playerId) {
res.status(400).end();
return;
}
const account = await Account.findById(req.query.playerId as string, "DisplayName"); const account = await Account.findById(req.query.playerId as string, "DisplayName");
if (!account) { if (!account) {
res.status(400).send("No account or guild ID specified"); res.status(409).send("Could not find requested account");
return; return;
} }
const inventory = (await Inventory.findOne({ accountOwnerId: account._id }))!; const inventory = (await Inventory.findOne({ accountOwnerId: account._id }))!;
const loadout = (await Loadout.findById(inventory.LoadOutPresets, "NORMAL"))!;
const result: IPlayerProfileViewingDataResult = { const result: IPlayerProfileViewingDataResult = {
AccountId: toOid(account._id), AccountId: toOid(account._id),
@ -55,46 +53,10 @@ export const getProfileViewingDataController: RequestHandler = async (req, res)
Wishlist: inventory.Wishlist, Wishlist: inventory.Wishlist,
Alignment: inventory.Alignment Alignment: inventory.Alignment
}; };
if (inventory.CurrentLoadOutIds.length) { await populateLoadout(inventory, result);
result.LoadOutPreset = loadout.NORMAL.id(inventory.CurrentLoadOutIds[0].$oid)!.toJSON<ILoadoutConfigClient>();
result.LoadOutPreset.ItemId = undefined;
const skins = new Set<string>();
if (result.LoadOutPreset.s) {
result.LoadOutInventory.Suits = [
inventory.Suits.id(result.LoadOutPreset.s.ItemId.$oid)!.toJSON<IEquipmentClient>()
];
resolveAndCollectSkins(inventory, skins, result.LoadOutInventory.Suits[0]);
}
if (result.LoadOutPreset.p) {
result.LoadOutInventory.Pistols = [
inventory.Pistols.id(result.LoadOutPreset.p.ItemId.$oid)!.toJSON<IEquipmentClient>()
];
resolveAndCollectSkins(inventory, skins, result.LoadOutInventory.Pistols[0]);
}
if (result.LoadOutPreset.l) {
result.LoadOutInventory.LongGuns = [
inventory.LongGuns.id(result.LoadOutPreset.l.ItemId.$oid)!.toJSON<IEquipmentClient>()
];
resolveAndCollectSkins(inventory, skins, result.LoadOutInventory.LongGuns[0]);
}
if (result.LoadOutPreset.m) {
result.LoadOutInventory.Melee = [
inventory.Melee.id(result.LoadOutPreset.m.ItemId.$oid)!.toJSON<IEquipmentClient>()
];
resolveAndCollectSkins(inventory, skins, result.LoadOutInventory.Melee[0]);
}
for (const skin of skins) {
result.LoadOutInventory.WeaponSkins.push({ ItemType: skin });
}
}
if (inventory.GuildId) { if (inventory.GuildId) {
const guild = (await Guild.findById(inventory.GuildId, "Name Tier XP Class"))!; const guild = (await Guild.findById(inventory.GuildId, "Name Tier XP Class Emblem"))!;
result.GuildId = toOid(inventory.GuildId); populateGuild(guild, result);
result.GuildName = guild.Name;
result.GuildTier = guild.Tier;
result.GuildXp = guild.XP;
result.GuildClass = guild.Class;
result.GuildEmblem = false;
} }
for (const key of allDailyAffiliationKeys) { for (const key of allDailyAffiliationKeys) {
result[key] = inventory[key]; result[key] = inventory[key];
@ -112,6 +74,100 @@ export const getProfileViewingDataController: RequestHandler = async (req, res)
//XpCacheExpiryDate, some IMongoDate in the future, no clue what it's for //XpCacheExpiryDate, some IMongoDate in the future, no clue what it's for
Stats: stats Stats: stats
}); });
} else if (req.query.guildId) {
const guild = await Guild.findById(req.query.guildId, "Name Tier XP Class Emblem TechProjects ClaimedXP");
if (!guild) {
res.status(409).send("Could not find guild");
return;
}
const members = await GuildMember.find({ guildId: guild._id, status: 0 });
const results: IPlayerProfileViewingDataResult[] = [];
for (let i = 0; i != Math.min(4, members.length); ++i) {
const member = members[i];
const [account, inventory] = await Promise.all([
Account.findById(member.accountId, "DisplayName"),
Inventory.findOne(
{ accountOwnerId: member.accountId },
"DisplayName PlayerLevel XPInfo LoadOutPresets CurrentLoadOutIds WeaponSkins Suits Pistols LongGuns Melee"
)
]);
const result: IPlayerProfileViewingDataResult = {
AccountId: toOid(account!._id),
DisplayName: account!.DisplayName,
PlayerLevel: inventory!.PlayerLevel,
LoadOutInventory: {
WeaponSkins: [],
XPInfo: inventory!.XPInfo
}
};
await populateLoadout(inventory!, result);
results.push(result);
}
populateGuild(guild, results[0]);
const combinedStats: IStatsClient = {};
const statsArr = await Stats.find({ accountOwnerId: { $in: members.map(x => x.accountId) } }).lean(); // need this as POJO so Object.entries works as expected
for (const stats of statsArr) {
for (const [key, value] of Object.entries(stats)) {
if (typeof value == "number" && key != "__v") {
(combinedStats[key as keyof IStatsClient] as number | undefined) ??= 0;
(combinedStats[key as keyof IStatsClient] as number) += value;
}
}
for (const arrayName of ["Weapons", "Enemies", "Scans", "Missions", "PVP"] as const) {
if (stats[arrayName]) {
combinedStats[arrayName] ??= [];
for (const entry of stats[arrayName]) {
const combinedEntry = combinedStats[arrayName].find(x => x.type == entry.type);
if (combinedEntry) {
for (const [key, value] of Object.entries(entry)) {
if (typeof value == "number") {
(combinedEntry[key as keyof typeof combinedEntry] as unknown as
| number
| undefined) ??= 0;
(combinedEntry[key as keyof typeof combinedEntry] as unknown as number) += value;
}
}
} else {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
combinedStats[arrayName].push(entry as any);
}
}
}
}
}
const xpComponents: IXPComponentClient[] = [];
if (guild.ClaimedXP) {
for (const componentName of guild.ClaimedXP) {
if (componentName.endsWith(".level")) {
const [key] = Object.entries(ExportDojoRecipes.rooms).find(
([_key, value]) => value.resultType == componentName
)!;
xpComponents.push({
StoreTypeName: toStoreItem(key)
});
} else {
const [key] = Object.entries(ExportDojoRecipes.decos).find(
([_key, value]) => value.resultType == componentName
)!;
xpComponents.push({
StoreTypeName: toStoreItem(key)
});
}
}
}
res.json({
Results: results,
TechProjects: guild.TechProjects,
XpComponents: xpComponents,
//XpCacheExpiryDate, some IMongoDate in the future, no clue what it's for
Stats: combinedStats
});
} else {
res.sendStatus(400);
}
}; };
interface IPlayerProfileViewingDataResult extends Partial<IDailyAffiliations> { interface IPlayerProfileViewingDataResult extends Partial<IDailyAffiliations> {
@ -133,20 +189,40 @@ interface IPlayerProfileViewingDataResult extends Partial<IDailyAffiliations> {
GuildXp?: number; GuildXp?: number;
GuildClass?: number; GuildClass?: number;
GuildEmblem?: boolean; GuildEmblem?: boolean;
PlayerSkills: IPlayerSkills; PlayerSkills?: IPlayerSkills;
ChallengeProgress: IChallengeProgress[]; ChallengeProgress?: IChallengeProgress[];
DeathMarks: string[]; DeathMarks?: string[];
Harvestable: boolean; Harvestable?: boolean;
DeathSquadable: boolean; DeathSquadable?: boolean;
Created: IMongoDate; Created?: IMongoDate;
MigratedToConsole: boolean; MigratedToConsole?: boolean;
Missions: IMission[]; Missions?: IMission[];
Affiliations: IAffiliation[]; Affiliations?: IAffiliation[];
DailyFocus: number; DailyFocus?: number;
Wishlist: string[]; Wishlist?: string[];
Alignment?: IAlignment; Alignment?: IAlignment;
} }
interface IXPComponentClient {
_id?: IOid;
StoreTypeName: string;
TypeName?: string;
PurchaseQuantity?: number;
ProductCategory?: "Recipes";
Rarity?: "COMMON";
RegularPrice?: number;
PremiumPrice?: number;
SellingPrice?: number;
DateAddedToManifest?: number;
PrimeSellingPrice?: number;
GuildXp?: number;
ResultPrefab?: string;
ResultDecoration?: string;
ShowInMarket?: boolean;
ShowInInventory?: boolean;
locTags?: Record<string, string>;
}
let skinLookupTable: Record<number, string> | undefined; let skinLookupTable: Record<number, string> | undefined;
const resolveAndCollectSkins = ( const resolveAndCollectSkins = (
@ -181,3 +257,51 @@ const resolveAndCollectSkins = (
} }
} }
}; };
const populateLoadout = async (
inventory: TInventoryDatabaseDocument,
result: IPlayerProfileViewingDataResult
): Promise<void> => {
if (inventory.CurrentLoadOutIds.length) {
const loadout = (await Loadout.findById(inventory.LoadOutPresets, "NORMAL"))!;
result.LoadOutPreset = loadout.NORMAL.id(inventory.CurrentLoadOutIds[0].$oid)!.toJSON<ILoadoutConfigClient>();
result.LoadOutPreset.ItemId = undefined;
const skins = new Set<string>();
if (result.LoadOutPreset.s) {
result.LoadOutInventory.Suits = [
inventory.Suits.id(result.LoadOutPreset.s.ItemId.$oid)!.toJSON<IEquipmentClient>()
];
resolveAndCollectSkins(inventory, skins, result.LoadOutInventory.Suits[0]);
}
if (result.LoadOutPreset.p) {
result.LoadOutInventory.Pistols = [
inventory.Pistols.id(result.LoadOutPreset.p.ItemId.$oid)!.toJSON<IEquipmentClient>()
];
resolveAndCollectSkins(inventory, skins, result.LoadOutInventory.Pistols[0]);
}
if (result.LoadOutPreset.l) {
result.LoadOutInventory.LongGuns = [
inventory.LongGuns.id(result.LoadOutPreset.l.ItemId.$oid)!.toJSON<IEquipmentClient>()
];
resolveAndCollectSkins(inventory, skins, result.LoadOutInventory.LongGuns[0]);
}
if (result.LoadOutPreset.m) {
result.LoadOutInventory.Melee = [
inventory.Melee.id(result.LoadOutPreset.m.ItemId.$oid)!.toJSON<IEquipmentClient>()
];
resolveAndCollectSkins(inventory, skins, result.LoadOutInventory.Melee[0]);
}
for (const skin of skins) {
result.LoadOutInventory.WeaponSkins.push({ ItemType: skin });
}
}
};
const populateGuild = (guild: TGuildDatabaseDocument, result: IPlayerProfileViewingDataResult): void => {
result.GuildId = toOid(guild._id);
result.GuildName = guild.Name;
result.GuildTier = guild.Tier;
result.GuildXp = guild.XP;
result.GuildClass = guild.Class;
result.GuildEmblem = guild.Emblem;
};

View File

@ -31,6 +31,14 @@ export interface IStatsClient {
CaliberChicksScore?: number; CaliberChicksScore?: number;
OlliesCrashCourseScore?: number; OlliesCrashCourseScore?: number;
DojoObstacleScore?: number; DojoObstacleScore?: number;
// not in schema
PVP?: {
suitDeaths?: number;
suitKills?: number;
weaponKills?: number;
type: string;
}[];
} }
export interface IStatsDatabase extends IStatsClient { export interface IStatsDatabase extends IStatsClient {