chore: switch purchaseService to take inventory document #848

Merged
Sainan merged 1 commits from optimise-purchase into main 2025-01-24 06:24:29 -08:00
5 changed files with 42 additions and 61 deletions

View File

@ -27,18 +27,17 @@ export const getVoidProjectionRewardsController: RequestHandler = async (req, re
logger.debug(`relic rolled`, reward); logger.debug(`relic rolled`, reward);
response.ParticipantInfo.Reward = reward.type; response.ParticipantInfo.Reward = reward.type;
// Remove relic
const inventory = await getInventory(accountId); const inventory = await getInventory(accountId);
// Remove relic
addMiscItems(inventory, [ addMiscItems(inventory, [
{ {
ItemType: data.ParticipantInfo.VoidProjection, ItemType: data.ParticipantInfo.VoidProjection,
ItemCount: -1 ItemCount: -1
} }
]); ]);
await inventory.save();
// Give reward // Give reward
await handleStoreItemAcquisition(reward.type, accountId, reward.itemCount); await handleStoreItemAcquisition(reward.type, inventory, reward.itemCount);
await inventory.save();
} }
res.json(response); res.json(response);
}; };

View File

@ -2,10 +2,13 @@ import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { IPurchaseRequest } from "@/src/types/purchaseTypes"; import { IPurchaseRequest } from "@/src/types/purchaseTypes";
import { handlePurchase } from "@/src/services/purchaseService"; import { handlePurchase } from "@/src/services/purchaseService";
import { getInventory } from "@/src/services/inventoryService";
export const purchaseController: RequestHandler = async (req, res) => { export const purchaseController: RequestHandler = async (req, res) => {
const purchaseRequest = JSON.parse(String(req.body)) as IPurchaseRequest; const purchaseRequest = JSON.parse(String(req.body)) as IPurchaseRequest;
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
const response = await handlePurchase(purchaseRequest, accountId); const inventory = await getInventory(accountId);
const response = await handlePurchase(purchaseRequest, inventory);
await inventory.save();
res.json(response); res.json(response);
coderabbitai[bot] commented 2025-01-21 22:56:36 -08:00 (Migrated from github.com)
Review

⚠️ Potential issue

Add error handling for inventory save operation

Currently, the inventory.save() operation is awaited without error handling. If the save operation fails, it could result in an unhandled promise rejection and potentially crash the server.

Consider wrapping the inventory.save() call in a try-catch block to handle any errors appropriately.

Apply this diff to add error handling:

     const response = await handlePurchase(purchaseRequest, inventory);
-    await inventory.save();
+    try {
+        await inventory.save();
+    } catch (err) {
+        // Handle the error appropriately
+        logger.error('Error saving inventory:', err);
+        return res.status(500).json({ error: 'Failed to save inventory' });
+    }
     res.json(response);

Committable suggestion skipped: line range outside the PR's diff.

_:warning: Potential issue_ **Add error handling for inventory save operation** Currently, the `inventory.save()` operation is awaited without error handling. If the save operation fails, it could result in an unhandled promise rejection and potentially crash the server. Consider wrapping the `inventory.save()` call in a try-catch block to handle any errors appropriately. Apply this diff to add error handling: ```diff const response = await handlePurchase(purchaseRequest, inventory); - await inventory.save(); + try { + await inventory.save(); + } catch (err) { + // Handle the error appropriately + logger.error('Error saving inventory:', err); + return res.status(500).json({ error: 'Failed to save inventory' }); + } res.json(response); ``` > Committable suggestion skipped: line range outside the PR's diff. <!-- This is an auto-generated comment by CodeRabbit -->
}; };

View File

@ -57,15 +57,15 @@ export const syndicateSacrificeController: RequestHandler = async (request, resp
} }
} }
await inventory.save();
if (reward) { if (reward) {
combineInventoryChanges( combineInventoryChanges(
res.InventoryChanges, res.InventoryChanges,
(await handleStoreItemAcquisition(reward, accountId)).InventoryChanges (await handleStoreItemAcquisition(reward, inventory)).InventoryChanges
); );
} }
await inventory.save();
response.json(res); response.json(res);
}; };
coderabbitai[bot] commented 2025-01-21 22:56:37 -08:00 (Migrated from github.com)
Review

⚠️ Potential issue

Add error handling for inventory save operation

The inventory.save() operation is awaited without proper error handling. If the save operation fails, it may result in an unhandled promise rejection and potentially crash the server.

Consider wrapping the inventory.save() call in a try-catch block:

-    await inventory.save();
+    try {
+        await inventory.save();
+    } catch (err) {
+        // Handle the error appropriately
+        logger.error('Error saving inventory:', err);
+        return response.status(500).json({ error: 'Failed to save inventory' });
+    }

Committable suggestion skipped: line range outside the PR's diff.

_:warning: Potential issue_ **Add error handling for inventory save operation** The `inventory.save()` operation is awaited without proper error handling. If the save operation fails, it may result in an unhandled promise rejection and potentially crash the server. Consider wrapping the `inventory.save()` call in a try-catch block: ```diff - await inventory.save(); + try { + await inventory.save(); + } catch (err) { + // Handle the error appropriately + logger.error('Error saving inventory:', err); + return response.status(500).json({ error: 'Failed to save inventory' }); + } ``` > Committable suggestion skipped: line range outside the PR's diff. <!-- This is an auto-generated comment by CodeRabbit -->

View File

@ -949,10 +949,9 @@ export const addMissionComplete = (inventory: TInventoryDatabaseDocument, { Tag,
} }
}; };
export const addBooster = async (ItemType: string, time: number, accountId: string): Promise<void> => { export const addBooster = (ItemType: string, time: number, inventory: TInventoryDatabaseDocument): void => {
const currentTime = Math.floor(Date.now() / 1000) - 129600; // Value is wrong without 129600. Figure out why, please. :) const currentTime = Math.floor(Date.now() / 1000) - 129600; // Value is wrong without 129600. Figure out why, please. :)
const inventory = await getInventory(accountId);
const { Boosters } = inventory; const { Boosters } = inventory;
const itemIndex = Boosters.findIndex(booster => booster.ItemType === ItemType); const itemIndex = Boosters.findIndex(booster => booster.ItemType === ItemType);
@ -964,8 +963,6 @@ export const addBooster = async (ItemType: string, time: number, accountId: stri
} else { } else {
Boosters.push({ ItemType, ExpiryDate: currentTime + time }); Boosters.push({ ItemType, ExpiryDate: currentTime + time });
} }
await inventory.save();
}; };
export const updateSyndicate = ( export const updateSyndicate = (

View File

@ -5,8 +5,7 @@ import {
addItem, addItem,
addMiscItems, addMiscItems,
combineInventoryChanges, combineInventoryChanges,
getInventory, updateCurrency,
updateCurrencyByAccountId,
updateSlots updateSlots
} from "@/src/services/inventoryService"; } from "@/src/services/inventoryService";
import { getRandomWeightedReward } from "@/src/services/rngService"; import { getRandomWeightedReward } from "@/src/services/rngService";
@ -25,6 +24,7 @@ import {
TRarity TRarity
} from "warframe-public-export-plus"; } from "warframe-public-export-plus";
import { config } from "./configService"; import { config } from "./configService";
import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
export const getStoreItemCategory = (storeItem: string): string => { export const getStoreItemCategory = (storeItem: string): string => {
const storeItemString = getSubstringFromKeyword(storeItem, "StoreItems/"); const storeItemString = getSubstringFromKeyword(storeItem, "StoreItems/");
@ -43,7 +43,7 @@ export const getStoreItemTypesCategory = (typesItem: string): string => {
export const handlePurchase = async ( export const handlePurchase = async (
purchaseRequest: IPurchaseRequest, purchaseRequest: IPurchaseRequest,
accountId: string inventory: TInventoryDatabaseDocument
): Promise<IPurchaseResponse> => { ): Promise<IPurchaseResponse> => {
logger.debug("purchase request", purchaseRequest); logger.debug("purchase request", purchaseRequest);
@ -58,8 +58,8 @@ export const handlePurchase = async (
throw new Error(`unknown vendor offer: ${ItemId}`); throw new Error(`unknown vendor offer: ${ItemId}`);
} }
if (offer.ItemPrices) { if (offer.ItemPrices) {
await handleItemPrices( handleItemPrices(
accountId, inventory,
offer.ItemPrices, offer.ItemPrices,
purchaseRequest.PurchaseParams.Quantity, purchaseRequest.PurchaseParams.Quantity,
inventoryChanges inventoryChanges
@ -73,17 +73,17 @@ export const handlePurchase = async (
const purchaseResponse = await handleStoreItemAcquisition( const purchaseResponse = await handleStoreItemAcquisition(
purchaseRequest.PurchaseParams.StoreItem, purchaseRequest.PurchaseParams.StoreItem,
accountId, inventory,
purchaseRequest.PurchaseParams.Quantity purchaseRequest.PurchaseParams.Quantity
); );
combineInventoryChanges(purchaseResponse.InventoryChanges, inventoryChanges); combineInventoryChanges(purchaseResponse.InventoryChanges, inventoryChanges);
if (!purchaseResponse) throw new Error("purchase response was undefined"); if (!purchaseResponse) throw new Error("purchase response was undefined");
const currencyChanges = await updateCurrencyByAccountId( const currencyChanges = updateCurrency(
inventory,
purchaseRequest.PurchaseParams.ExpectedPrice, purchaseRequest.PurchaseParams.ExpectedPrice,
purchaseRequest.PurchaseParams.UsePremium, purchaseRequest.PurchaseParams.UsePremium
accountId
); );
purchaseResponse.InventoryChanges = { purchaseResponse.InventoryChanges = {
...currencyChanges, ...currencyChanges,
@ -95,7 +95,6 @@ export const handlePurchase = async (
{ {
const syndicateTag = purchaseRequest.PurchaseParams.SyndicateTag!; const syndicateTag = purchaseRequest.PurchaseParams.SyndicateTag!;
if (purchaseRequest.PurchaseParams.UseFreeFavor!) { if (purchaseRequest.PurchaseParams.UseFreeFavor!) {
const inventory = await getInventory(accountId);
const affiliation = inventory.Affiliations.find(x => x.Tag == syndicateTag)!; const affiliation = inventory.Affiliations.find(x => x.Tag == syndicateTag)!;
affiliation.FreeFavorsUsed ??= []; affiliation.FreeFavorsUsed ??= [];
const lastTitle = affiliation.FreeFavorsEarned![affiliation.FreeFavorsUsed.length]; const lastTitle = affiliation.FreeFavorsEarned![affiliation.FreeFavorsUsed.length];
@ -106,7 +105,6 @@ export const handlePurchase = async (
Title: lastTitle Title: lastTitle
} }
]; ];
await inventory.save();
} else { } else {
const syndicate = ExportSyndicates[syndicateTag]; const syndicate = ExportSyndicates[syndicateTag];
if (syndicate) { if (syndicate) {
@ -114,7 +112,6 @@ export const handlePurchase = async (
x => x.storeItem == purchaseRequest.PurchaseParams.StoreItem x => x.storeItem == purchaseRequest.PurchaseParams.StoreItem
); );
if (favour) { if (favour) {
const inventory = await getInventory(accountId);
const affiliation = inventory.Affiliations.find(x => x.Tag == syndicateTag); const affiliation = inventory.Affiliations.find(x => x.Tag == syndicateTag);
if (affiliation) { if (affiliation) {
purchaseResponse.Standing = [ purchaseResponse.Standing = [
@ -124,7 +121,6 @@ export const handlePurchase = async (
} }
]; ];
affiliation.Standing -= favour.standingCost; affiliation.Standing -= favour.standingCost;
await inventory.save();
} }
} }
} }
@ -136,8 +132,8 @@ export const handlePurchase = async (
const vendor = ExportVendors[purchaseRequest.PurchaseParams.SourceId!]; const vendor = ExportVendors[purchaseRequest.PurchaseParams.SourceId!];
const offer = vendor.items.find(x => x.storeItem == purchaseRequest.PurchaseParams.StoreItem); const offer = vendor.items.find(x => x.storeItem == purchaseRequest.PurchaseParams.StoreItem);
if (offer && offer.itemPrices) { if (offer && offer.itemPrices) {
await handleItemPrices( handleItemPrices(
accountId, inventory,
offer.itemPrices, offer.itemPrices,
purchaseRequest.PurchaseParams.Quantity, purchaseRequest.PurchaseParams.Quantity,
purchaseResponse.InventoryChanges purchaseResponse.InventoryChanges
@ -157,7 +153,6 @@ export const handlePurchase = async (
x => x.ItemType == purchaseRequest.PurchaseParams.StoreItem x => x.ItemType == purchaseRequest.PurchaseParams.StoreItem
); );
if (offer) { if (offer) {
const inventory = await getInventory(accountId);
if (offer.RegularPrice) { if (offer.RegularPrice) {
const invItem: IMiscItem = { const invItem: IMiscItem = {
ItemType: "/Lotus/Types/Items/MiscItems/SchismKey", ItemType: "/Lotus/Types/Items/MiscItems/SchismKey",
@ -171,7 +166,6 @@ export const handlePurchase = async (
} else if (!config.infiniteRegalAya) { } else if (!config.infiniteRegalAya) {
inventory.PrimeTokens -= offer.PrimePrice! * purchaseRequest.PurchaseParams.Quantity; inventory.PrimeTokens -= offer.PrimePrice! * purchaseRequest.PurchaseParams.Quantity;
} }
await inventory.save();
} }
break; break;
} }
@ -180,13 +174,12 @@ export const handlePurchase = async (
return purchaseResponse; return purchaseResponse;
}; };
const handleItemPrices = async ( const handleItemPrices = (
accountId: string, inventory: TInventoryDatabaseDocument,
itemPrices: IMiscItem[], itemPrices: IMiscItem[],
purchaseQuantity: number, purchaseQuantity: number,
inventoryChanges: IInventoryChanges inventoryChanges: IInventoryChanges
): Promise<void> => { ): void => {
const inventory = await getInventory(accountId);
for (const item of itemPrices) { for (const item of itemPrices) {
const invItem: IMiscItem = { const invItem: IMiscItem = {
ItemType: item.ItemType, ItemType: item.ItemType,
@ -203,12 +196,11 @@ const handleItemPrices = async (
(inventoryChanges.MiscItems as IMiscItem[]).push(invItem); (inventoryChanges.MiscItems as IMiscItem[]).push(invItem);
} }
} }
await inventory.save();
}; };
export const handleStoreItemAcquisition = async ( export const handleStoreItemAcquisition = async (
storeItemName: string, storeItemName: string,
accountId: string, inventory: TInventoryDatabaseDocument,
quantity: number = 1, quantity: number = 1,
durability: TRarity = "COMMON", durability: TRarity = "COMMON",
ignorePurchaseQuantity: boolean = false ignorePurchaseQuantity: boolean = false
@ -226,7 +218,7 @@ export const handleStoreItemAcquisition = async (
( (
await handleStoreItemAcquisition( await handleStoreItemAcquisition(
component.typeName, component.typeName,
accountId, inventory,
component.purchaseQuantity * quantity, component.purchaseQuantity * quantity,
component.durability, component.durability,
true true
@ -247,16 +239,14 @@ export const handleStoreItemAcquisition = async (
} }
switch (storeCategory) { switch (storeCategory) {
default: { default: {
const inventory = await getInventory(accountId);
purchaseResponse = await addItem(inventory, internalName, quantity); purchaseResponse = await addItem(inventory, internalName, quantity);
await inventory.save();
break; break;
} }
case "Types": case "Types":
purchaseResponse = await handleTypesPurchase(internalName, accountId, quantity); purchaseResponse = await handleTypesPurchase(internalName, inventory, quantity);
break; break;
case "Boosters": case "Boosters":
purchaseResponse = await handleBoostersPurchase(internalName, accountId, durability); purchaseResponse = handleBoostersPurchase(internalName, inventory, durability);
break; break;
} }
} }
@ -280,11 +270,11 @@ export const slotPurchaseNameToSlotName: SlotPurchase = {
// // new slot above base = extra + 1 and slots +1 // // new slot above base = extra + 1 and slots +1
// // new frame = slots -1 // // new frame = slots -1
// // number of frames = extra - slots + 2 // // number of frames = extra - slots + 2
const handleSlotPurchase = async ( const handleSlotPurchase = (
slotPurchaseNameFull: string, slotPurchaseNameFull: string,
accountId: string, inventory: TInventoryDatabaseDocument,
quantity: number quantity: number
): Promise<IPurchaseResponse> => { ): IPurchaseResponse => {
logger.debug(`slot name ${slotPurchaseNameFull}`); logger.debug(`slot name ${slotPurchaseNameFull}`);
const slotPurchaseName = parseSlotPurchaseName( const slotPurchaseName = parseSlotPurchaseName(
slotPurchaseNameFull.substring(slotPurchaseNameFull.lastIndexOf("/") + 1) slotPurchaseNameFull.substring(slotPurchaseNameFull.lastIndexOf("/") + 1)
@ -294,9 +284,7 @@ const handleSlotPurchase = async (
const slotName = slotPurchaseNameToSlotName[slotPurchaseName].name; const slotName = slotPurchaseNameToSlotName[slotPurchaseName].name;
const slotsPurchased = slotPurchaseNameToSlotName[slotPurchaseName].slotsPerPurchase * quantity; const slotsPurchased = slotPurchaseNameToSlotName[slotPurchaseName].slotsPerPurchase * quantity;
const inventory = await getInventory(accountId);
updateSlots(inventory, slotName, slotsPurchased, slotsPurchased); updateSlots(inventory, slotName, slotsPurchased, slotsPurchased);
await inventory.save();
logger.debug(`added ${slotsPurchased} slot ${slotName}`); logger.debug(`added ${slotsPurchased} slot ${slotName}`);
@ -314,7 +302,7 @@ const handleSlotPurchase = async (
const handleBoosterPackPurchase = async ( const handleBoosterPackPurchase = async (
typeName: string, typeName: string,
accountId: string, inventory: TInventoryDatabaseDocument,
quantity: number quantity: number
): Promise<IPurchaseResponse> => { ): Promise<IPurchaseResponse> => {
const pack = ExportBoosterPacks[typeName]; const pack = ExportBoosterPacks[typeName];
@ -325,7 +313,6 @@ const handleBoosterPackPurchase = async (
BoosterPackItems: "", BoosterPackItems: "",
InventoryChanges: {} InventoryChanges: {}
}; };
const inventory = await getInventory(accountId);
for (let i = 0; i != quantity; ++i) { for (let i = 0; i != quantity; ++i) {
for (const weights of pack.rarityWeightsPerRoll) { for (const weights of pack.rarityWeightsPerRoll) {
const result = getRandomWeightedReward(pack.components, weights); const result = getRandomWeightedReward(pack.components, weights);
@ -340,29 +327,24 @@ const handleBoosterPackPurchase = async (
} }
} }
} }
await inventory.save();
return purchaseResponse; return purchaseResponse;
}; };
//TODO: change to getInventory, apply changes then save at the end //TODO: change to getInventory, apply changes then save at the end
const handleTypesPurchase = async ( const handleTypesPurchase = async (
typesName: string, typesName: string,
accountId: string, inventory: TInventoryDatabaseDocument,
quantity: number quantity: number
): Promise<IPurchaseResponse> => { ): Promise<IPurchaseResponse> => {
const typeCategory = getStoreItemTypesCategory(typesName); const typeCategory = getStoreItemTypesCategory(typesName);
logger.debug(`type category ${typeCategory}`); logger.debug(`type category ${typeCategory}`);
switch (typeCategory) { switch (typeCategory) {
default: { default:
const inventory = await getInventory(accountId); return await addItem(inventory, typesName, quantity);
const resp = await addItem(inventory, typesName, quantity);
await inventory.save();
return resp;
}
case "BoosterPacks": case "BoosterPacks":
return await handleBoosterPackPurchase(typesName, accountId, quantity); return handleBoosterPackPurchase(typesName, inventory, quantity);
case "SlotItems": case "SlotItems":
return await handleSlotPurchase(typesName, accountId, quantity); return handleSlotPurchase(typesName, inventory, quantity);
} }
}; };
@ -380,11 +362,11 @@ const boosterDuration: Record<TRarity, number> = {
LEGENDARY: 90 * 86400 LEGENDARY: 90 * 86400
}; };
const handleBoostersPurchase = async ( const handleBoostersPurchase = (
boosterStoreName: string, boosterStoreName: string,
accountId: string, inventory: TInventoryDatabaseDocument,
durability: TRarity durability: TRarity
): Promise<{ InventoryChanges: IInventoryChanges }> => { ): { InventoryChanges: IInventoryChanges } => {
const ItemType = boosterStoreName.replace("StoreItem", ""); const ItemType = boosterStoreName.replace("StoreItem", "");
if (!boosterCollection.find(x => x == ItemType)) { if (!boosterCollection.find(x => x == ItemType)) {
logger.error(`unknown booster type: ${ItemType}`); logger.error(`unknown booster type: ${ItemType}`);
@ -393,7 +375,7 @@ const handleBoostersPurchase = async (
const ExpiryDate = boosterDuration[durability]; const ExpiryDate = boosterDuration[durability];
await addBooster(ItemType, ExpiryDate, accountId); addBooster(ItemType, ExpiryDate, inventory);
return { return {
InventoryChanges: { InventoryChanges: {