返回提交历史
Modified
src/controllers/dynamic/getProfileViewingDataController.ts
+219
-95
Modified
src/types/statTypes.ts
+8
-0
XFEstudio/SpaceNinjaServer
feat: getProfileViewingData for clans (#1412)
Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/1412 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
404c7476
代码差异
2 个文件
+227
-95
@@ -1,5 +1,5 @@
1
1
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
2
import { Guild } from "@/src/models/guildModel";
2
import { Guild, GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel";
3
3
import { Inventory, TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
4
4
import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
5
5
import { Account } from "@/src/models/loginModel";
@@ -19,99 +19,155 @@ import {
19
19
} from "@/src/types/inventoryTypes/inventoryTypes";
20
20
import { RequestHandler } from "express";
21
21
import { catBreadHash } from "../api/inventoryController";
22
import { ExportCustoms } from "warframe-public-export-plus";
22
import { ExportCustoms, ExportDojoRecipes } from "warframe-public-export-plus";
23
import { IStatsClient } from "@/src/types/statTypes";
24
import { toStoreItem } from "@/src/services/itemDataService";
23
25
24
26
export const getProfileViewingDataController: RequestHandler = async (req, res) => {
25
if (!req.query.playerId) {
26
res.status(400).end();
27
return;
28
}
29
const account = await Account.findById(req.query.playerId as string, "DisplayName");
30
if (!account) {
31
res.status(400).send("No account or guild ID specified");
32
return;
33
}
34
const inventory = (await Inventory.findOne({ accountOwnerId: account._id }))!;
35
const loadout = (await Loadout.findById(inventory.LoadOutPresets, "NORMAL"))!;
27
if (req.query.playerId) {
28
const account = await Account.findById(req.query.playerId as string, "DisplayName");
29
if (!account) {
30
res.status(409).send("Could not find requested account");
31
return;
32
}
33
const inventory = (await Inventory.findOne({ accountOwnerId: account._id }))!;
36
34
37
const result: IPlayerProfileViewingDataResult = {
38
AccountId: toOid(account._id),
39
DisplayName: account.DisplayName,
40
PlayerLevel: inventory.PlayerLevel,
41
LoadOutInventory: {
42
WeaponSkins: [],
43
XPInfo: inventory.XPInfo
44
},
45
PlayerSkills: inventory.PlayerSkills,
46
ChallengeProgress: inventory.ChallengeProgress,
47
DeathMarks: inventory.DeathMarks,
48
Harvestable: inventory.Harvestable,
49
DeathSquadable: inventory.DeathSquadable,
50
Created: toMongoDate(inventory.Created),
51
MigratedToConsole: false,
52
Missions: inventory.Missions,
53
Affiliations: inventory.Affiliations,
54
DailyFocus: inventory.DailyFocus,
55
Wishlist: inventory.Wishlist,
56
Alignment: inventory.Alignment
57
};
58
if (inventory.CurrentLoadOutIds.length) {
59
result.LoadOutPreset = loadout.NORMAL.id(inventory.CurrentLoadOutIds[0].$oid)!.toJSON<ILoadoutConfigClient>();
60
result.LoadOutPreset.ItemId = undefined;
61
const skins = new Set<string>();
62
if (result.LoadOutPreset.s) {
63
result.LoadOutInventory.Suits = [
64
inventory.Suits.id(result.LoadOutPreset.s.ItemId.$oid)!.toJSON<IEquipmentClient>()
65
];
66
resolveAndCollectSkins(inventory, skins, result.LoadOutInventory.Suits[0]);
35
const result: IPlayerProfileViewingDataResult = {
36
AccountId: toOid(account._id),
37
DisplayName: account.DisplayName,
38
PlayerLevel: inventory.PlayerLevel,
39
LoadOutInventory: {
40
WeaponSkins: [],
41
XPInfo: inventory.XPInfo
42
},
43
PlayerSkills: inventory.PlayerSkills,
44
ChallengeProgress: inventory.ChallengeProgress,
45
DeathMarks: inventory.DeathMarks,
46
Harvestable: inventory.Harvestable,
47
DeathSquadable: inventory.DeathSquadable,
48
Created: toMongoDate(inventory.Created),
49
MigratedToConsole: false,
50
Missions: inventory.Missions,
51
Affiliations: inventory.Affiliations,
52
DailyFocus: inventory.DailyFocus,
53
Wishlist: inventory.Wishlist,
54
Alignment: inventory.Alignment
55
};
56
await populateLoadout(inventory, result);
57
if (inventory.GuildId) {
58
const guild = (await Guild.findById(inventory.GuildId, "Name Tier XP Class Emblem"))!;
59
populateGuild(guild, result);
67
60
}
68
if (result.LoadOutPreset.p) {
69
result.LoadOutInventory.Pistols = [
70
inventory.Pistols.id(result.LoadOutPreset.p.ItemId.$oid)!.toJSON<IEquipmentClient>()
71
];
72
resolveAndCollectSkins(inventory, skins, result.LoadOutInventory.Pistols[0]);
61
for (const key of allDailyAffiliationKeys) {
62
result[key] = inventory[key];
73
63
}
74
if (result.LoadOutPreset.l) {
75
result.LoadOutInventory.LongGuns = [
76
inventory.LongGuns.id(result.LoadOutPreset.l.ItemId.$oid)!.toJSON<IEquipmentClient>()
77
];
78
resolveAndCollectSkins(inventory, skins, result.LoadOutInventory.LongGuns[0]);
64
65
const stats = (await Stats.findOne({ accountOwnerId: account._id }))!.toJSON<Partial<TStatsDatabaseDocument>>();
66
delete stats._id;
67
delete stats.__v;
68
delete stats.accountOwnerId;
69
70
res.json({
71
Results: [result],
72
TechProjects: [],
73
XpComponents: [],
74
//XpCacheExpiryDate, some IMongoDate in the future, no clue what it's for
75
Stats: stats
76
});
77
} else if (req.query.guildId) {
78
const guild = await Guild.findById(req.query.guildId, "Name Tier XP Class Emblem TechProjects ClaimedXP");
79
if (!guild) {
80
res.status(409).send("Could not find guild");
81
return;
79
82
}
80
if (result.LoadOutPreset.m) {
81
result.LoadOutInventory.Melee = [
82
inventory.Melee.id(result.LoadOutPreset.m.ItemId.$oid)!.toJSON<IEquipmentClient>()
83
];
84
resolveAndCollectSkins(inventory, skins, result.LoadOutInventory.Melee[0]);
83
const members = await GuildMember.find({ guildId: guild._id, status: 0 });
84
const results: IPlayerProfileViewingDataResult[] = [];
85
for (let i = 0; i != Math.min(4, members.length); ++i) {
86
const member = members[i];
87
const [account, inventory] = await Promise.all([
88
Account.findById(member.accountId, "DisplayName"),
89
Inventory.findOne(
90
{ accountOwnerId: member.accountId },
91
"DisplayName PlayerLevel XPInfo LoadOutPresets CurrentLoadOutIds WeaponSkins Suits Pistols LongGuns Melee"
92
)
93
]);
94
const result: IPlayerProfileViewingDataResult = {
95
AccountId: toOid(account!._id),
96
DisplayName: account!.DisplayName,
97
PlayerLevel: inventory!.PlayerLevel,
98
LoadOutInventory: {
99
WeaponSkins: [],
100
XPInfo: inventory!.XPInfo
101
}
102
};
103
await populateLoadout(inventory!, result);
104
results.push(result);
85
105
}
86
for (const skin of skins) {
87
result.LoadOutInventory.WeaponSkins.push({ ItemType: skin });
106
populateGuild(guild, results[0]);
107
108
const combinedStats: IStatsClient = {};
109
const statsArr = await Stats.find({ accountOwnerId: { $in: members.map(x => x.accountId) } }).lean(); // need this as POJO so Object.entries works as expected
110
for (const stats of statsArr) {
111
for (const [key, value] of Object.entries(stats)) {
112
if (typeof value == "number" && key != "__v") {
113
(combinedStats[key as keyof IStatsClient] as number | undefined) ??= 0;
114
(combinedStats[key as keyof IStatsClient] as number) += value;
115
}
116
}
117
for (const arrayName of ["Weapons", "Enemies", "Scans", "Missions", "PVP"] as const) {
118
if (stats[arrayName]) {
119
combinedStats[arrayName] ??= [];
120
for (const entry of stats[arrayName]) {
121
const combinedEntry = combinedStats[arrayName].find(x => x.type == entry.type);
122
if (combinedEntry) {
123
for (const [key, value] of Object.entries(entry)) {
124
if (typeof value == "number") {
125
(combinedEntry[key as keyof typeof combinedEntry] as unknown as
126
| number
127
| undefined) ??= 0;
128
(combinedEntry[key as keyof typeof combinedEntry] as unknown as number) += value;
129
}
130
}
131
} else {
132
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
133
combinedStats[arrayName].push(entry as any);
134
}
135
}
136
}
137
}
88
138
}
89
}
90
if (inventory.GuildId) {
91
const guild = (await Guild.findById(inventory.GuildId, "Name Tier XP Class"))!;
92
result.GuildId = toOid(inventory.GuildId);
93
result.GuildName = guild.Name;
94
result.GuildTier = guild.Tier;
95
result.GuildXp = guild.XP;
96
result.GuildClass = guild.Class;
97
result.GuildEmblem = false;
98
}
99
for (const key of allDailyAffiliationKeys) {
100
result[key] = inventory[key];
101
}
102
139
103
const stats = (await Stats.findOne({ accountOwnerId: account._id }))!.toJSON<Partial<TStatsDatabaseDocument>>();
104
delete stats._id;
105
delete stats.__v;
106
delete stats.accountOwnerId;
140
const xpComponents: IXPComponentClient[] = [];
141
if (guild.ClaimedXP) {
142
for (const componentName of guild.ClaimedXP) {
143
if (componentName.endsWith(".level")) {
144
const [key] = Object.entries(ExportDojoRecipes.rooms).find(
145
([_key, value]) => value.resultType == componentName
146
)!;
147
xpComponents.push({
148
StoreTypeName: toStoreItem(key)
149
});
150
} else {
151
const [key] = Object.entries(ExportDojoRecipes.decos).find(
152
([_key, value]) => value.resultType == componentName
153
)!;
154
xpComponents.push({
155
StoreTypeName: toStoreItem(key)
156
});
157
}
158
}
159
}
107
160
108
res.json({
109
Results: [result],
110
TechProjects: [],
111
XpComponents: [],
112
//XpCacheExpiryDate, some IMongoDate in the future, no clue what it's for
113
Stats: stats
114
});
161
res.json({
162
Results: results,
163
TechProjects: guild.TechProjects,
164
XpComponents: xpComponents,
165
//XpCacheExpiryDate, some IMongoDate in the future, no clue what it's for
166
Stats: combinedStats
167
});
168
} else {
169
res.sendStatus(400);
170
}
115
171
};
116
172
117
173
interface IPlayerProfileViewingDataResult extends Partial<IDailyAffiliations> {
@@ -133,20 +189,40 @@ interface IPlayerProfileViewingDataResult extends Partial<IDailyAffiliations> {
133
189
GuildXp?: number;
134
190
GuildClass?: number;
135
191
GuildEmblem?: boolean;
136
PlayerSkills: IPlayerSkills;
137
ChallengeProgress: IChallengeProgress[];
138
DeathMarks: string[];
139
Harvestable: boolean;
140
DeathSquadable: boolean;
141
Created: IMongoDate;
142
MigratedToConsole: boolean;
143
Missions: IMission[];
144
Affiliations: IAffiliation[];
145
DailyFocus: number;
146
Wishlist: string[];
192
PlayerSkills?: IPlayerSkills;
193
ChallengeProgress?: IChallengeProgress[];
194
DeathMarks?: string[];
195
Harvestable?: boolean;
196
DeathSquadable?: boolean;
197
Created?: IMongoDate;
198
MigratedToConsole?: boolean;
199
Missions?: IMission[];
200
Affiliations?: IAffiliation[];
201
DailyFocus?: number;
202
Wishlist?: string[];
147
203
Alignment?: IAlignment;
148
204
}
149
205
206
interface IXPComponentClient {
207
_id?: IOid;
208
StoreTypeName: string;
209
TypeName?: string;
210
PurchaseQuantity?: number;
211
ProductCategory?: "Recipes";
212
Rarity?: "COMMON";
213
RegularPrice?: number;
214
PremiumPrice?: number;
215
SellingPrice?: number;
216
DateAddedToManifest?: number;
217
PrimeSellingPrice?: number;
218
GuildXp?: number;
219
ResultPrefab?: string;
220
ResultDecoration?: string;
221
ShowInMarket?: boolean;
222
ShowInInventory?: boolean;
223
locTags?: Record<string, string>;
224
}
225
150
226
let skinLookupTable: Record<number, string> | undefined;
151
227
152
228
const resolveAndCollectSkins = (
@@ -181,3 +257,51 @@ const resolveAndCollectSkins = (
181
257
}
182
258
}
183
259
};
260
261
const populateLoadout = async (
262
inventory: TInventoryDatabaseDocument,
263
result: IPlayerProfileViewingDataResult
264
): Promise<void> => {
265
if (inventory.CurrentLoadOutIds.length) {
266
const loadout = (await Loadout.findById(inventory.LoadOutPresets, "NORMAL"))!;
267
result.LoadOutPreset = loadout.NORMAL.id(inventory.CurrentLoadOutIds[0].$oid)!.toJSON<ILoadoutConfigClient>();
268
result.LoadOutPreset.ItemId = undefined;
269
const skins = new Set<string>();
270
if (result.LoadOutPreset.s) {
271
result.LoadOutInventory.Suits = [
272
inventory.Suits.id(result.LoadOutPreset.s.ItemId.$oid)!.toJSON<IEquipmentClient>()
273
];
274
resolveAndCollectSkins(inventory, skins, result.LoadOutInventory.Suits[0]);
275
}
276
if (result.LoadOutPreset.p) {
277
result.LoadOutInventory.Pistols = [
278
inventory.Pistols.id(result.LoadOutPreset.p.ItemId.$oid)!.toJSON<IEquipmentClient>()
279
];
280
resolveAndCollectSkins(inventory, skins, result.LoadOutInventory.Pistols[0]);
281
}
282
if (result.LoadOutPreset.l) {
283
result.LoadOutInventory.LongGuns = [
284
inventory.LongGuns.id(result.LoadOutPreset.l.ItemId.$oid)!.toJSON<IEquipmentClient>()
285
];
286
resolveAndCollectSkins(inventory, skins, result.LoadOutInventory.LongGuns[0]);
287
}
288
if (result.LoadOutPreset.m) {
289
result.LoadOutInventory.Melee = [
290
inventory.Melee.id(result.LoadOutPreset.m.ItemId.$oid)!.toJSON<IEquipmentClient>()
291
];
292
resolveAndCollectSkins(inventory, skins, result.LoadOutInventory.Melee[0]);
293
}
294
for (const skin of skins) {
295
result.LoadOutInventory.WeaponSkins.push({ ItemType: skin });
296
}
297
}
298
};
299
300
const populateGuild = (guild: TGuildDatabaseDocument, result: IPlayerProfileViewingDataResult): void => {
301
result.GuildId = toOid(guild._id);
302
result.GuildName = guild.Name;
303
result.GuildTier = guild.Tier;
304
result.GuildXp = guild.XP;
305
result.GuildClass = guild.Class;
306
result.GuildEmblem = guild.Emblem;
307
};
@@ -31,6 +31,14 @@ export interface IStatsClient {
31
31
CaliberChicksScore?: number;
32
32
OlliesCrashCourseScore?: number;
33
33
DojoObstacleScore?: number;
34
35
// not in schema
36
PVP?: {
37
suitDeaths?: number;
38
suitKills?: number;
39
weaponKills?: number;
40
type: string;
41
}[];
34
42
}
35
43
36
44
export interface IStatsDatabase extends IStatsClient {