chore: some initial handling of legacy oid format (#2033)
All checks were successful
Build Docker image / docker (push) Successful in 1m13s
Build / build (push) Successful in 1m54s

This at least allows mission inventory update to succeed on U19.5 and below.

Reviewed-on: #2033
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
This commit is contained in:
Sainan 2025-05-09 00:20:54 -07:00 committed by Sainan
parent 7a51fab5d3
commit f9b3fecc10
13 changed files with 96 additions and 53 deletions

View File

@ -4,9 +4,9 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { getRecipe } from "@/src/services/itemDataService"; import { getRecipe } from "@/src/services/itemDataService";
import { IOid } from "@/src/types/commonTypes"; import { IOid, IOidWithLegacySupport } from "@/src/types/commonTypes";
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountForRequest } from "@/src/services/loginService";
import { import {
getInventory, getInventory,
updateCurrency, updateCurrency,
@ -18,7 +18,7 @@ import {
import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes"; import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes"; import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
import { toOid } from "@/src/helpers/inventoryHelpers"; import { toOid2 } from "@/src/helpers/inventoryHelpers";
interface IClaimCompletedRecipeRequest { interface IClaimCompletedRecipeRequest {
RecipeIds: IOid[]; RecipeIds: IOid[];
@ -26,10 +26,8 @@ interface IClaimCompletedRecipeRequest {
export const claimCompletedRecipeController: RequestHandler = async (req, res) => { export const claimCompletedRecipeController: RequestHandler = async (req, res) => {
const claimCompletedRecipeRequest = getJSONfromString<IClaimCompletedRecipeRequest>(String(req.body)); const claimCompletedRecipeRequest = getJSONfromString<IClaimCompletedRecipeRequest>(String(req.body));
const accountId = await getAccountIdForRequest(req); const account = await getAccountForRequest(req);
if (!accountId) throw new Error("no account id"); const inventory = await getInventory(account._id.toString());
const inventory = await getInventory(accountId);
const pendingRecipe = inventory.PendingRecipes.id(claimCompletedRecipeRequest.RecipeIds[0].$oid); const pendingRecipe = inventory.PendingRecipes.id(claimCompletedRecipeRequest.RecipeIds[0].$oid);
if (!pendingRecipe) { if (!pendingRecipe) {
throw new Error(`no pending recipe found with id ${claimCompletedRecipeRequest.RecipeIds[0].$oid}`); throw new Error(`no pending recipe found with id ${claimCompletedRecipeRequest.RecipeIds[0].$oid}`);
@ -81,7 +79,7 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
} else { } else {
logger.debug("Claiming Recipe", { recipe, pendingRecipe }); logger.debug("Claiming Recipe", { recipe, pendingRecipe });
let BrandedSuits: undefined | IOid[]; let BrandedSuits: undefined | IOidWithLegacySupport[];
if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") { if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
inventory.PendingSpectreLoadouts ??= []; inventory.PendingSpectreLoadouts ??= [];
inventory.SpectreLoadouts ??= []; inventory.SpectreLoadouts ??= [];
@ -106,7 +104,7 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
inventory.BrandedSuits!.findIndex(x => x.equals(pendingRecipe.SuitToUnbrand)), inventory.BrandedSuits!.findIndex(x => x.equals(pendingRecipe.SuitToUnbrand)),
1 1
); );
BrandedSuits = [toOid(pendingRecipe.SuitToUnbrand!)]; BrandedSuits = [toOid2(pendingRecipe.SuitToUnbrand!, account.BuildLabel)];
} }
let InventoryChanges: IInventoryChanges = {}; let InventoryChanges: IInventoryChanges = {};

View File

@ -25,10 +25,10 @@ import { logger } from "@/src/utils/logger";
import { catBreadHash } from "@/src/helpers/stringHelpers"; import { catBreadHash } from "@/src/helpers/stringHelpers";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { isNemesisCompatibleWithVersion } from "@/src/helpers/nemesisHelpers"; import { isNemesisCompatibleWithVersion } from "@/src/helpers/nemesisHelpers";
import { version_compare } from "@/src/services/worldStateService";
import { getPersonalRooms } from "@/src/services/personalRoomsService"; import { getPersonalRooms } from "@/src/services/personalRoomsService";
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes"; import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
import { Ship } from "@/src/models/shipModel"; import { Ship } from "@/src/models/shipModel";
import { toLegacyOid, version_compare } from "@/src/helpers/inventoryHelpers";
export const inventoryController: RequestHandler = async (request, response) => { export const inventoryController: RequestHandler = async (request, response) => {
const account = await getAccountForRequest(request); const account = await getAccountForRequest(request);
@ -306,19 +306,34 @@ export const getInventoryResponse = async (
// Set 2FA enabled so trading post can be used // Set 2FA enabled so trading post can be used
inventoryResponse.HWIDProtectEnabled = true; inventoryResponse.HWIDProtectEnabled = true;
// Fix nemesis for older versions if (buildLabel) {
if ( // Fix nemesis for older versions
inventoryResponse.Nemesis && if (inventoryResponse.Nemesis && !isNemesisCompatibleWithVersion(inventoryResponse.Nemesis, buildLabel)) {
buildLabel && inventoryResponse.Nemesis = undefined;
!isNemesisCompatibleWithVersion(inventoryResponse.Nemesis, buildLabel) }
) {
inventoryResponse.Nemesis = undefined;
}
if (buildLabel && version_compare(buildLabel, "2018.02.22.14.34") < 0) { if (version_compare(buildLabel, "2018.02.22.14.34") < 0) {
const personalRoomsDb = await getPersonalRooms(inventory.accountOwnerId.toString()); const personalRoomsDb = await getPersonalRooms(inventory.accountOwnerId.toString());
const personalRooms = personalRoomsDb.toJSON<IPersonalRoomsClient>(); const personalRooms = personalRoomsDb.toJSON<IPersonalRoomsClient>();
inventoryResponse.Ship = personalRooms.Ship; inventoryResponse.Ship = personalRooms.Ship;
if (version_compare(buildLabel, "2016.12.21.19.13") <= 0) {
// U19.5 and below use $id instead of $oid
for (const category of equipmentKeys) {
for (const item of inventoryResponse[category]) {
toLegacyOid(item.ItemId);
}
}
for (const upgrade of inventoryResponse.Upgrades) {
toLegacyOid(upgrade.ItemId);
}
if (inventoryResponse.BrandedSuits) {
for (const id of inventoryResponse.BrandedSuits) {
toLegacyOid(id);
}
}
}
}
} }
return inventoryResponse; return inventoryResponse;

View File

@ -7,7 +7,7 @@ import { Account } from "@/src/models/loginModel";
import { createAccount, isCorrectPassword, isNameTaken } from "@/src/services/loginService"; import { createAccount, isCorrectPassword, isNameTaken } from "@/src/services/loginService";
import { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "@/src/types/loginTypes"; import { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "@/src/types/loginTypes";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { version_compare } from "@/src/services/worldStateService"; import { version_compare } from "@/src/helpers/inventoryHelpers";
export const loginController: RequestHandler = async (request, response) => { export const loginController: RequestHandler = async (request, response) => {
const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object

View File

@ -1,8 +1,8 @@
import { version_compare } from "@/src/helpers/inventoryHelpers";
import { Alliance, Guild, GuildMember } from "@/src/models/guildModel"; import { Alliance, Guild, GuildMember } from "@/src/models/guildModel";
import { hasGuildPermissionEx } from "@/src/services/guildService"; import { hasGuildPermissionEx } from "@/src/services/guildService";
import { getInventory } from "@/src/services/inventoryService"; import { getInventory } from "@/src/services/inventoryService";
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService"; import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
import { version_compare } from "@/src/services/worldStateService";
import { GuildPermission, ILongMOTD } from "@/src/types/guildTypes"; import { GuildPermission, ILongMOTD } from "@/src/types/guildTypes";
import { RequestHandler } from "express"; import { RequestHandler } from "express";

View File

@ -1,5 +1,6 @@
import { applyClientEquipmentUpdates, getInventory } from "@/src/services/inventoryService"; import { applyClientEquipmentUpdates, getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { IOid } from "@/src/types/commonTypes";
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes"; import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes"; import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
@ -11,7 +12,7 @@ export const addXpController: RequestHandler = async (req, res) => {
const request = req.body as IAddXpRequest; const request = req.body as IAddXpRequest;
for (const [category, gear] of Object.entries(request)) { for (const [category, gear] of Object.entries(request)) {
for (const clientItem of gear) { for (const clientItem of gear) {
const dbItem = inventory[category as TEquipmentKey].id(clientItem.ItemId.$oid); const dbItem = inventory[category as TEquipmentKey].id((clientItem.ItemId as IOid).$oid);
if (dbItem) { if (dbItem) {
if (dbItem.ItemType in ExportMisc.uniqueLevelCaps) { if (dbItem.ItemType in ExportMisc.uniqueLevelCaps) {
if ((dbItem.Polarized ?? 0) < 5) { if ((dbItem.Polarized ?? 0) < 5) {

View File

@ -1,9 +1,46 @@
import { IMongoDate, IOid } from "@/src/types/commonTypes"; import { IMongoDate, IOid, IOidWithLegacySupport } from "@/src/types/commonTypes";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { TRarity } from "warframe-public-export-plus"; import { TRarity } from "warframe-public-export-plus";
export const version_compare = (a: string, b: string): number => {
const a_digits = a
.split("/")[0]
.split(".")
.map(x => parseInt(x));
const b_digits = b
.split("/")[0]
.split(".")
.map(x => parseInt(x));
for (let i = 0; i != a_digits.length; ++i) {
if (a_digits[i] != b_digits[i]) {
return a_digits[i] > b_digits[i] ? 1 : -1;
}
}
return 0;
};
export const toOid = (objectId: Types.ObjectId): IOid => { export const toOid = (objectId: Types.ObjectId): IOid => {
return { $oid: objectId.toString() } satisfies IOid; return { $oid: objectId.toString() };
};
export function toOid2(objectId: Types.ObjectId, buildLabel: undefined): IOid;
export function toOid2(objectId: Types.ObjectId, buildLabel: string | undefined): IOidWithLegacySupport;
export function toOid2(objectId: Types.ObjectId, buildLabel: string | undefined): IOidWithLegacySupport {
if (buildLabel && version_compare(buildLabel, "2016.12.21.19.13") <= 0) {
return { $id: objectId.toString() };
}
return { $oid: objectId.toString() };
}
export const toLegacyOid = (oid: IOidWithLegacySupport): void => {
if (!("$id" in oid)) {
oid.$id = oid.$oid;
delete oid.$oid;
}
};
export const fromOid = (oid: IOidWithLegacySupport): string => {
return (oid.$oid ?? oid.$id)!;
}; };
export const toMongoDate = (date: Date): IMongoDate => { export const toMongoDate = (date: Date): IMongoDate => {

View File

@ -6,9 +6,10 @@ import { logger } from "../utils/logger";
import { IOid } from "../types/commonTypes"; import { IOid } from "../types/commonTypes";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { addMods, generateRewardSeed } from "../services/inventoryService"; import { addMods, generateRewardSeed } from "../services/inventoryService";
import { isArchwingMission, version_compare } from "../services/worldStateService"; import { isArchwingMission } from "../services/worldStateService";
import { fromStoreItem, toStoreItem } from "../services/itemDataService"; import { fromStoreItem, toStoreItem } from "../services/itemDataService";
import { createMessage } from "../services/inboxService"; import { createMessage } from "../services/inboxService";
import { version_compare } from "./inventoryHelpers";
export const getInfNodes = (faction: string, rank: number): IInfNode[] => { export const getInfNodes = (faction: string, rank: number): IInfNode[] => {
const infNodes = []; const infNodes = [];

View File

@ -70,6 +70,7 @@ import { createShip } from "./shipService";
import { import {
catbrowDetails, catbrowDetails,
fromMongoDate, fromMongoDate,
fromOid,
kubrowDetails, kubrowDetails,
kubrowFurPatternsWeights, kubrowFurPatternsWeights,
kubrowWeights, kubrowWeights,
@ -423,7 +424,7 @@ export const addItem = async (
changes.push({ changes.push({
ItemType: egg.ItemType, ItemType: egg.ItemType,
ExpirationDate: { $date: { $numberLong: "2000000000000" } }, ExpirationDate: { $date: { $numberLong: "2000000000000" } },
ItemId: toOid(egg._id) ItemId: toOid(egg._id) // TODO: Pass on buildLabel from purchaseService
}); });
} }
return { return {
@ -1491,9 +1492,9 @@ export const applyClientEquipmentUpdates = (
const category = inventory[categoryName]; const category = inventory[categoryName];
gearArray.forEach(({ ItemId, XP, InfestationDate }) => { gearArray.forEach(({ ItemId, XP, InfestationDate }) => {
const item = category.id(ItemId.$oid); const item = category.id(fromOid(ItemId));
if (!item) { if (!item) {
throw new Error(`No item with id ${ItemId.$oid} in ${categoryName}`); throw new Error(`No item with id ${fromOid(ItemId)} in ${categoryName}`);
} }
if (XP) { if (XP) {

View File

@ -62,6 +62,7 @@ import { getLiteSortie, getSortie, idToBountyCycle, idToDay, idToWeek, pushClass
import { config } from "./configService"; import { config } from "./configService";
import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json"; import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json";
import { ISyndicateMissionInfo } from "../types/worldStateTypes"; import { ISyndicateMissionInfo } from "../types/worldStateTypes";
import { fromOid } from "../helpers/inventoryHelpers";
const getRotations = (rewardInfo: IRewardInfo, tierOverride?: number): number[] => { const getRotations = (rewardInfo: IRewardInfo, tierOverride?: number): number[] => {
// For Spy missions, e.g. 3 vaults cracked = A, B, C // For Spy missions, e.g. 3 vaults cracked = A, B, C
@ -399,7 +400,7 @@ export const addMissionInventoryUpdates = async (
break; break;
case "Upgrades": case "Upgrades":
value.forEach(clientUpgrade => { value.forEach(clientUpgrade => {
const upgrade = inventory.Upgrades.id(clientUpgrade.ItemId.$oid)!; const upgrade = inventory.Upgrades.id(fromOid(clientUpgrade.ItemId))!;
upgrade.UpgradeFingerprint = clientUpgrade.UpgradeFingerprint; // primitive way to copy over the riven challenge progress upgrade.UpgradeFingerprint = clientUpgrade.UpgradeFingerprint; // primitive way to copy over the riven challenge progress
}); });
break; break;

View File

@ -18,6 +18,7 @@ import {
ISyndicateMissionInfo, ISyndicateMissionInfo,
IWorldState IWorldState
} from "../types/worldStateTypes"; } from "../types/worldStateTypes";
import { version_compare } from "../helpers/inventoryHelpers";
const sortieBosses = [ const sortieBosses = [
"SORTIE_BOSS_HYENA", "SORTIE_BOSS_HYENA",
@ -1239,20 +1240,3 @@ export const isArchwingMission = (node: IRegion): boolean => {
} }
return false; return false;
}; };
export const version_compare = (a: string, b: string): number => {
const a_digits = a
.split("/")[0]
.split(".")
.map(x => parseInt(x));
const b_digits = b
.split("/")[0]
.split(".")
.map(x => parseInt(x));
for (let i = 0; i != a_digits.length; ++i) {
if (a_digits[i] != b_digits[i]) {
return a_digits[i] > b_digits[i] ? 1 : -1;
}
}
return 0;
};

View File

@ -4,6 +4,11 @@ export interface IOid {
$oid: string; $oid: string;
} }
export interface IOidWithLegacySupport {
$oid?: string;
$id?: string;
}
export interface IMongoDate { export interface IMongoDate {
$date: { $date: {
$numberLong: string; $numberLong: string;

View File

@ -1,4 +1,4 @@
import { IMongoDate, IOid } from "@/src/types/commonTypes"; import { IMongoDate, IOid, IOidWithLegacySupport } from "@/src/types/commonTypes";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { import {
ICrewShipCustomization, ICrewShipCustomization,
@ -92,7 +92,7 @@ export interface IEquipmentClient
IEquipmentDatabase, IEquipmentDatabase,
"_id" | "InfestationDate" | "Expiry" | "UpgradesExpiry" | "UmbraDate" | "CrewMembers" | "Details" "_id" | "InfestationDate" | "Expiry" | "UpgradesExpiry" | "UmbraDate" | "CrewMembers" | "Details"
> { > {
ItemId: IOid; ItemId: IOidWithLegacySupport;
InfestationDate?: IMongoDate; InfestationDate?: IMongoDate;
Expiry?: IMongoDate; Expiry?: IMongoDate;
UpgradesExpiry?: IMongoDate; UpgradesExpiry?: IMongoDate;

View File

@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { Types } from "mongoose"; import { Types } from "mongoose";
import { IOid, IMongoDate } from "../commonTypes"; import { IOid, IMongoDate, IOidWithLegacySupport } from "../commonTypes";
import { import {
IColor, IColor,
IItemConfig, IItemConfig,
@ -371,7 +371,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
EchoesHexConquestCacheScoreMission?: number; EchoesHexConquestCacheScoreMission?: number;
EchoesHexConquestActiveFrameVariants?: string[]; EchoesHexConquestActiveFrameVariants?: string[];
EchoesHexConquestActiveStickers?: string[]; EchoesHexConquestActiveStickers?: string[];
BrandedSuits?: IOid[]; BrandedSuits?: IOidWithLegacySupport[];
LockedWeaponGroup?: ILockedWeaponGroupClient; LockedWeaponGroup?: ILockedWeaponGroupClient;
HubNpcCustomizations?: IHubNpcCustomization[]; HubNpcCustomizations?: IHubNpcCustomization[];
Ship?: IOrbiter; // U22 and below, response only Ship?: IOrbiter; // U22 and below, response only