feat(webui): add "add missing warframes" & "add missing weapons" #775

Merged
Sainan merged 4 commits from bulk into main 2025-01-14 20:20:17 -08:00
6 changed files with 116 additions and 106 deletions

View File

@ -1,33 +0,0 @@
import { getAccountIdForRequest } from "@/src/services/loginService";
import { ItemType, toAddItemRequest } from "@/src/helpers/customHelpers/addItemHelpers";
import { getWeaponType } from "@/src/services/itemDataService";
import { addPowerSuit, addEquipment, getInventory } from "@/src/services/inventoryService";
import { RequestHandler } from "express";
const addItemController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const request = toAddItemRequest(req.body);
switch (request.type) {
case ItemType.Powersuit: {
const inventory = await getInventory(accountId);
const inventoryChanges = addPowerSuit(inventory, request.InternalName);
await inventory.save();
res.json(inventoryChanges);
return;
}
case ItemType.Weapon: {
const inventory = await getInventory(accountId);
const weaponType = getWeaponType(request.InternalName);
const inventoryChanges = addEquipment(inventory, weaponType, request.InternalName);
await inventory.save();
res.json(inventoryChanges);
break;
}
default:
res.status(400).json({ error: "something went wrong" });
break;
}
};
export { addItemController };

View File

@ -0,0 +1,33 @@
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getWeaponType } from "@/src/services/itemDataService";
import { addPowerSuit, addEquipment, getInventory } from "@/src/services/inventoryService";
import { RequestHandler } from "express";
export const addItemsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const requests = req.body as IAddItemRequest[];
const inventory = await getInventory(accountId);
for (const request of requests) {
switch (request.type) {
case ItemType.Powersuit:
addPowerSuit(inventory, request.internalName);
break;
case ItemType.Weapon:
addEquipment(inventory, getWeaponType(request.internalName), request.internalName);
break;
}
}
await inventory.save();
res.end();
};
coderabbitai[bot] commented 2025-01-12 00:42:58 -08:00 (Migrated from github.com)
Review

🛠️ Refactor suggestion

Consider adding error handling for invalid request bodies.

The controller lacks error handling for:

  • Invalid/malformed request body
  • Empty requests array
  • Unknown item types

Consider adding error handling:

 export const addItemsController: RequestHandler = async (req, res) => {
+    if (!Array.isArray(req.body)) {
+        res.status(400).json({ error: "Request body must be an array" });
+        return;
+    }
+    if (req.body.length === 0) {
+        res.status(400).json({ error: "Request body cannot be empty" });
+        return;
+    }
     const accountId = await getAccountIdForRequest(req);
     const requests = req.body as IAddItemRequest[];
     const inventory = await getInventory(accountId);
     for (const request of requests) {
+        if (!Object.values(ItemType).includes(request.type)) {
+            res.status(400).json({ error: `Invalid item type: ${request.type}` });
+            return;
+        }
         switch (request.type) {
             case ItemType.Powersuit:
                 addPowerSuit(inventory, request.internalName);
                 break;
             case ItemType.Weapon:
                 addEquipment(inventory, getWeaponType(request.internalName), request.internalName);
                 break;
         }
     }
     await inventory.save();
-    res.end();
+    res.status(200).json({ message: "Items added successfully" });
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

export const addItemsController: RequestHandler = async (req, res) => {
    if (!Array.isArray(req.body)) {
        res.status(400).json({ error: "Request body must be an array" });
        return;
    }
    if (req.body.length === 0) {
        res.status(400).json({ error: "Request body cannot be empty" });
        return;
    }
    const accountId = await getAccountIdForRequest(req);
    const requests = req.body as IAddItemRequest[];
    const inventory = await getInventory(accountId);
    for (const request of requests) {
        if (!Object.values(ItemType).includes(request.type)) {
            res.status(400).json({ error: `Invalid item type: ${request.type}` });
            return;
        }
        switch (request.type) {
            case ItemType.Powersuit:
                addPowerSuit(inventory, request.internalName);
                break;
            case ItemType.Weapon:
                addEquipment(inventory, getWeaponType(request.internalName), request.internalName);
                break;
        }
    }
    await inventory.save();
    res.status(200).json({ message: "Items added successfully" });
};
_:hammer_and_wrench: Refactor suggestion_ **Consider adding error handling for invalid request bodies.** The controller lacks error handling for: - Invalid/malformed request body - Empty requests array - Unknown item types Consider adding error handling: ```diff export const addItemsController: RequestHandler = async (req, res) => { + if (!Array.isArray(req.body)) { + res.status(400).json({ error: "Request body must be an array" }); + return; + } + if (req.body.length === 0) { + res.status(400).json({ error: "Request body cannot be empty" }); + return; + } const accountId = await getAccountIdForRequest(req); const requests = req.body as IAddItemRequest[]; const inventory = await getInventory(accountId); for (const request of requests) { + if (!Object.values(ItemType).includes(request.type)) { + res.status(400).json({ error: `Invalid item type: ${request.type}` }); + return; + } switch (request.type) { case ItemType.Powersuit: addPowerSuit(inventory, request.internalName); break; case ItemType.Weapon: addEquipment(inventory, getWeaponType(request.internalName), request.internalName); break; } } await inventory.save(); - res.end(); + res.status(200).json({ message: "Items added successfully" }); }; ``` <!-- suggestion_start --> <details> <summary>📝 Committable suggestion</summary> > ‼️ **IMPORTANT** > Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements. `````suggestion export const addItemsController: RequestHandler = async (req, res) => { if (!Array.isArray(req.body)) { res.status(400).json({ error: "Request body must be an array" }); return; } if (req.body.length === 0) { res.status(400).json({ error: "Request body cannot be empty" }); return; } const accountId = await getAccountIdForRequest(req); const requests = req.body as IAddItemRequest[]; const inventory = await getInventory(accountId); for (const request of requests) { if (!Object.values(ItemType).includes(request.type)) { res.status(400).json({ error: `Invalid item type: ${request.type}` }); return; } switch (request.type) { case ItemType.Powersuit: addPowerSuit(inventory, request.internalName); break; case ItemType.Weapon: addEquipment(inventory, getWeaponType(request.internalName), request.internalName); break; } } await inventory.save(); res.status(200).json({ message: "Items added successfully" }); }; ````` </details> <!-- suggestion_end --> <!-- This is an auto-generated comment by CodeRabbit -->
enum ItemType {
Powersuit = "Powersuit",
Weapon = "Weapon"
}
interface IAddItemRequest {
type: ItemType;
internalName: string;
}

View File

@ -1,46 +0,0 @@
import { isString } from "@/src/helpers/general";
export enum ItemType {
Powersuit = "Powersuit",
Weapon = "Weapon"
}
export const isItemType = (itemType: string): itemType is ItemType => {
return Object.keys(ItemType).includes(itemType);
};
const parseItemType = (itemType: unknown): ItemType => {
if (!itemType || !isString(itemType) || !isItemType(itemType)) {
throw new Error("incorrect item type");
}
return itemType;
};
interface IAddItemRequest {
type: ItemType;
InternalName: string;
}
const parseInternalItemName = (internalName: unknown): string => {
if (!isString(internalName)) {
throw new Error("incorrect internal name");
}
return internalName;
};
export const toAddItemRequest = (body: unknown): IAddItemRequest => {
if (!body || typeof body !== "object") {
throw new Error("incorrect or missing add item request data");
}
if ("type" in body && "internalName" in body) {
return {
type: parseItemType(body.type),
InternalName: parseInternalItemName(body.internalName)
};
}
throw new Error("malformed add item request");
};

View File

@ -8,7 +8,7 @@ import { deleteAccountController } from "@/src/controllers/custom/deleteAccountC
import { renameAccountController } from "@/src/controllers/custom/renameAccountController";
import { createAccountController } from "@/src/controllers/custom/createAccountController";
import { addItemController } from "@/src/controllers/custom/addItemController";
import { addItemsController } from "@/src/controllers/custom/addItemsController";
import { getConfigDataController } from "@/src/controllers/custom/getConfigDataController";
import { updateConfigDataController } from "@/src/controllers/custom/updateConfigDataController";
@ -23,7 +23,7 @@ customRouter.get("/deleteAccount", deleteAccountController);
customRouter.get("/renameAccount", renameAccountController);
customRouter.post("/createAccount", createAccountController);
customRouter.post("/addItem", addItemController);
customRouter.post("/addItems", addItemsController);
customRouter.get("/config", getConfigDataController);
customRouter.post("/config", updateConfigDataController);

View File

@ -82,11 +82,11 @@
</form>
</div>
<div data-route="/webui/inventory" data-title="Inventory | OpenWF WebUI">
<p class="mb-4">
<p class="mb-3">
Note: Changes made here will only be reflected in-game when the game re-downloads your
inventory. Visiting the navigation should be the easiest way to trigger that.
</p>
<div class="card mb-4">
<div class="card mb-3">
<h5 class="card-header">Add Items</h5>
<form class="card-body input-group" onsubmit="doAcquireMiscItems();return false;">
<input class="form-control" id="miscitem-count" type="number" min="1" value="1" />
@ -94,9 +94,9 @@
<button class="btn btn-primary" type="submit">Add</button>
</form>
</div>
<div class="row">
<div class="row g-3">
<div class="col-lg-6">
<div class="card mb-4">
<div class="card mb-3">
<h5 class="card-header">Warframes</h5>
<div class="card-body">
<form class="input-group mb-3" onsubmit="doAcquireWarframe();return false;">
@ -108,9 +108,16 @@
</table>
</div>
</div>
<div class="card mb-3">
<h5 class="card-header">Bulk Actions</h5>
<div class="card-body">
<button class="btn btn-primary" onclick="addMissingWarframes();">Add Missing Warframes</button>
<button class="btn btn-primary" onclick="addMissingWeapons();">Add Missing Weapons</button>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card mb-4">
<div class="card mb-3">
<h5 class="card-header">Weapons</h5>
<div class="card-body">
<form class="input-group mb-3" onsubmit="doAcquireWeapon();return false;">
@ -128,7 +135,7 @@
<div id="powersuit-route" data-route="~ /webui/powersuit/(.+)" data-title="Inventory | OpenWF WebUI">
<h3 class="mb-0"></h3>
<p class="text-body-secondary"></p>
<div class="card mb-4">
<div class="card mb-3">
<h5 class="card-header">Archon Shard Slots</h5>
<div class="card-body">
<p>You can use these unlimited slots to apply a wide range of upgrades.</p>
@ -145,13 +152,13 @@
</div>
</div>
<div data-route="/webui/mods" data-title="Mods | OpenWF WebUI">
<p class="mb-4">
<p class="mb-3">
Note: Changes made here will only be reflected in-game when the game re-downloads your
inventory. Visiting the navigation should be the easiest way to trigger that.
</p>
<div class="row">
<div class="row g-3">
<div class="col-xxl-6">
<div class="card mb-4">
<div class="card mb-3">
<h5 class="card-header">Add Riven</h5>
<form class="card-body" onsubmit="doAcquireRiven();return false;">
<select class="form-control mb-3" id="addriven-type">
@ -168,7 +175,7 @@
<a href="riven-tool/" target="_blank">Need help with the fingerprint?</a>
</form>
</div>
<div class="card mb-4">
<div class="card mb-3">
<h5 class="card-header">Rivens</h5>
<div class="card-body">
<table class="table table-hover w-100">
@ -178,7 +185,7 @@
</div>
</div>
<div class="col-xxl-6">
<div class="card mb-4">
<div class="card mb-3">
<h5 class="card-header">Mods</h5>
<div class="card-body">
<form class="input-group mb-3" onsubmit="doAcquireMod();return false;">
@ -194,9 +201,9 @@
</div>
</div>
<div data-route="/webui/cheats, /webui/settings" data-title="Cheats | OpenWF WebUI">
<div class="row">
<div class="row g-3">
<div class="col-lg-4">
<div class="card mb-4">
<div class="card mb-3">
<h5 class="card-header">Server</h5>
<div class="card-body">
<div id="server-settings-no-perms" class="d-none">
@ -283,7 +290,7 @@
</div>
</div>
<div class="col-lg-4">
<div class="card mb-4">
<div class="card mb-3">
<h5 class="card-header">Account</h5>
<div class="card-body">
<p><button class="btn btn-primary" onclick="doUnlockAllFocusSchools();">Unlock All Focus Schools</button></p>
@ -292,7 +299,7 @@
</div>
</div>
<div class="col-lg-4">
<div class="card mb-4">
<div class="card mb-3">
<h5 class="card-header">Client</h5>
<div id="client-cheats-nok" class="card-body">
Client cheats are currently unavailable. This could be because your client is not running or using a DLL without an HTTP interface.

View File

@ -190,6 +190,7 @@ function updateInventory() {
document.getElementById("warframe-list").innerHTML = "";
data.Suits.forEach(item => {
const tr = document.createElement("tr");
tr.setAttribute("data-item-type", item.ItemType);
{
const td = document.createElement("td");
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
@ -267,6 +268,7 @@ function updateInventory() {
["LongGuns", "Pistols", "Melee"].forEach(category => {
data[category].forEach(item => {
const tr = document.createElement("tr");
tr.setAttribute("data-item-type", item.ItemType);
{
const td = document.createElement("td");
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
@ -524,12 +526,14 @@ function doAcquireWarframe() {
}
revalidateAuthz(() => {
const req = $.post({
url: "/custom/addItem?" + window.authz,
url: "/custom/addItems?" + window.authz,
contentType: "application/json",
data: JSON.stringify({
type: "Powersuit",
internalName: uniqueName
})
data: JSON.stringify([
{
type: "Powersuit",
internalName: uniqueName
}
])
});
req.done(() => {
document.getElementById("warframe-to-acquire").value = "";
@ -550,12 +554,14 @@ function doAcquireWeapon() {
}
revalidateAuthz(() => {
const req = $.post({
url: "/custom/addItem?" + window.authz,
url: "/custom/addItems?" + window.authz,
contentType: "application/json",
data: JSON.stringify({
type: "Weapon",
internalName: uniqueName
})
data: JSON.stringify([
{
type: "Weapon",
internalName: uniqueName
}
])
});
req.done(() => {
document.getElementById("weapon-to-acquire").value = "";
@ -568,6 +574,49 @@ $("#weapon-to-acquire").on("input", () => {
$("#weapon-to-acquire").removeClass("is-invalid");
});
function dispatchAddItemsRequestsBatch(requests) {
revalidateAuthz(() => {
const req = $.post({
url: "/custom/addItems?" + window.authz,
contentType: "application/json",
data: JSON.stringify(requests)
});
req.done(() => {
updateInventory();
});
});
}
function addMissingWarframes() {
const requests = [];
document.querySelectorAll("#datalist-warframes option").forEach(elm => {
if (!document.querySelector("#warframe-list [data-item-type='" + elm.getAttribute("data-key") + "']")) {
requests.push({ type: "Powersuit", internalName: elm.getAttribute("data-key") });
}
});
if (
requests.length != 0 &&
window.confirm("Are you sure you want to add " + requests.length + " items to your account?")
) {
dispatchAddItemsRequestsBatch(requests);
}
}
function addMissingWeapons() {
const requests = [];
document.querySelectorAll("#datalist-weapons option").forEach(elm => {
if (!document.querySelector("#weapon-list [data-item-type='" + elm.getAttribute("data-key") + "']")) {
requests.push({ type: "Weapon", internalName: elm.getAttribute("data-key") });
}
});
if (
requests.length != 0 &&
window.confirm("Are you sure you want to add " + requests.length + " items to your account?")
) {
dispatchAddItemsRequestsBatch(requests);
}
}
function addGearExp(category, oid, xp) {
const data = {};
data[category] = [