feat: getProfileViewingData for clans #1412
@ -1,5 +1,5 @@
 | 
			
		||||
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 { Loadout } from "@/src/models/inventoryModels/loadoutModel";
 | 
			
		||||
import { Account } from "@/src/models/loginModel";
 | 
			
		||||
@ -19,20 +19,18 @@ import {
 | 
			
		||||
} from "@/src/types/inventoryTypes/inventoryTypes";
 | 
			
		||||
import { RequestHandler } from "express";
 | 
			
		||||
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) => {
 | 
			
		||||
    if (!req.query.playerId) {
 | 
			
		||||
        res.status(400).end();
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    if (req.query.playerId) {
 | 
			
		||||
        const account = await Account.findById(req.query.playerId as string, "DisplayName");
 | 
			
		||||
        if (!account) {
 | 
			
		||||
        res.status(400).send("No account or guild ID specified");
 | 
			
		||||
            res.status(409).send("Could not find requested account");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        const inventory = (await Inventory.findOne({ accountOwnerId: account._id }))!;
 | 
			
		||||
    const loadout = (await Loadout.findById(inventory.LoadOutPresets, "NORMAL"))!;
 | 
			
		||||
 | 
			
		||||
        const result: IPlayerProfileViewingDataResult = {
 | 
			
		||||
            AccountId: toOid(account._id),
 | 
			
		||||
@ -55,46 +53,10 @@ export const getProfileViewingDataController: RequestHandler = async (req, res)
 | 
			
		||||
            Wishlist: inventory.Wishlist,
 | 
			
		||||
            Alignment: inventory.Alignment
 | 
			
		||||
        };
 | 
			
		||||
    if (inventory.CurrentLoadOutIds.length) {
 | 
			
		||||
        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 });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
        await populateLoadout(inventory, result);
 | 
			
		||||
        if (inventory.GuildId) {
 | 
			
		||||
        const guild = (await Guild.findById(inventory.GuildId, "Name Tier XP Class"))!;
 | 
			
		||||
        result.GuildId = toOid(inventory.GuildId);
 | 
			
		||||
        result.GuildName = guild.Name;
 | 
			
		||||
        result.GuildTier = guild.Tier;
 | 
			
		||||
        result.GuildXp = guild.XP;
 | 
			
		||||
        result.GuildClass = guild.Class;
 | 
			
		||||
        result.GuildEmblem = false;
 | 
			
		||||
            const guild = (await Guild.findById(inventory.GuildId, "Name Tier XP Class Emblem"))!;
 | 
			
		||||
            populateGuild(guild, result);
 | 
			
		||||
        }
 | 
			
		||||
        for (const key of allDailyAffiliationKeys) {
 | 
			
		||||
            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
 | 
			
		||||
            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> {
 | 
			
		||||
@ -133,20 +189,40 @@ interface IPlayerProfileViewingDataResult extends Partial<IDailyAffiliations> {
 | 
			
		||||
    GuildXp?: number;
 | 
			
		||||
    GuildClass?: number;
 | 
			
		||||
    GuildEmblem?: boolean;
 | 
			
		||||
    PlayerSkills: IPlayerSkills;
 | 
			
		||||
    ChallengeProgress: IChallengeProgress[];
 | 
			
		||||
    DeathMarks: string[];
 | 
			
		||||
    Harvestable: boolean;
 | 
			
		||||
    DeathSquadable: boolean;
 | 
			
		||||
    Created: IMongoDate;
 | 
			
		||||
    MigratedToConsole: boolean;
 | 
			
		||||
    Missions: IMission[];
 | 
			
		||||
    Affiliations: IAffiliation[];
 | 
			
		||||
    DailyFocus: number;
 | 
			
		||||
    Wishlist: string[];
 | 
			
		||||
    PlayerSkills?: IPlayerSkills;
 | 
			
		||||
    ChallengeProgress?: IChallengeProgress[];
 | 
			
		||||
    DeathMarks?: string[];
 | 
			
		||||
    Harvestable?: boolean;
 | 
			
		||||
    DeathSquadable?: boolean;
 | 
			
		||||
    Created?: IMongoDate;
 | 
			
		||||
    MigratedToConsole?: boolean;
 | 
			
		||||
    Missions?: IMission[];
 | 
			
		||||
    Affiliations?: IAffiliation[];
 | 
			
		||||
    DailyFocus?: number;
 | 
			
		||||
    Wishlist?: string[];
 | 
			
		||||
    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;
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -31,6 +31,14 @@ export interface IStatsClient {
 | 
			
		||||
    CaliberChicksScore?: number;
 | 
			
		||||
    OlliesCrashCourseScore?: number;
 | 
			
		||||
    DojoObstacleScore?: number;
 | 
			
		||||
 | 
			
		||||
    // not in schema
 | 
			
		||||
    PVP?: {
 | 
			
		||||
        suitDeaths?: number;
 | 
			
		||||
        suitKills?: number;
 | 
			
		||||
        weaponKills?: number;
 | 
			
		||||
        type: string;
 | 
			
		||||
    }[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IStatsDatabase extends IStatsClient {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user