From 9c436699f954a5dacc71abd1edd2167023609f07 Mon Sep 17 00:00:00 2001 From: Ordis <134585663+OrdisPrime@users.noreply.github.com> Date: Wed, 14 Jun 2023 01:41:24 +0200 Subject: [PATCH] Basic purchasing + custom purchasing API Endpoint for custom API: https://localhost:443/custom/addItem example request: { "type": "Weapon", "internalName": "/Lotus/Weapons/Grineer/Pistols/GrineerMicrowavegun/GrnMicrowavePistol", "accountId": "6488fd2e7bec200069ca4242" } --- package-lock.json | 8 +- package.json | 3 +- src/app.ts | 2 +- src/controllers/api/inventoryController.ts | 1 - src/controllers/api/purchaseController.ts | 11 +- src/controllers/api/saveLoadout.ts | 13 ++ src/controllers/custom/addItemController.ts | 26 +++ .../custom/createAccountController.ts | 3 +- src/helpers/customHelpers/addItemHelpers.ts | 55 +++++++ .../{ => customHelpers}/customHelpers.ts | 3 +- src/helpers/inventoryHelpers.ts | 2 +- src/helpers/purchaseHelpers.ts | 59 +++++++ src/helpers/stringHelpers.ts | 18 +++ src/models/inventoryModel.ts | 126 ++++++++++++--- src/models/t.ts | 17 -- src/routes/api.ts | 2 + src/routes/cache.ts | 4 - src/routes/custom.ts | 2 + src/services/inventoryService.ts | 85 +++++++++- src/services/purchaseService.ts | 110 +++++++++++++ src/types/inventoryTypes/SuitTypes.ts | 44 +++++ .../inventoryTypes/commonInventoryTypes.ts | 42 +++++ .../{ => inventoryTypes}/inventoryTypes.ts | 153 ++---------------- src/types/inventoryTypes/weaponTypes.ts | 39 +++++ src/types/purchaseTypes.ts | 43 +++++ 25 files changed, 675 insertions(+), 196 deletions(-) create mode 100644 src/controllers/api/saveLoadout.ts create mode 100644 src/controllers/custom/addItemController.ts create mode 100644 src/helpers/customHelpers/addItemHelpers.ts rename src/helpers/{ => customHelpers}/customHelpers.ts (94%) create mode 100644 src/helpers/purchaseHelpers.ts create mode 100644 src/helpers/stringHelpers.ts delete mode 100644 src/models/t.ts create mode 100644 src/services/purchaseService.ts create mode 100644 src/types/inventoryTypes/SuitTypes.ts create mode 100644 src/types/inventoryTypes/commonInventoryTypes.ts rename src/types/{ => inventoryTypes}/inventoryTypes.ts (89%) create mode 100644 src/types/inventoryTypes/weaponTypes.ts create mode 100644 src/types/purchaseTypes.ts diff --git a/package-lock.json b/package-lock.json index 44a892f2..e1c51c62 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,8 @@ "dependencies": { "dotenv": "^16.1.3", "express": "^5.0.0-beta.1", - "mongoose": "^7.1.1" + "mongoose": "^7.1.1", + "warframe-items": "1.1260.50" }, "devDependencies": { "@tsconfig/node20": "^1.0.0", @@ -3140,6 +3141,11 @@ "node": ">= 0.8" } }, + "node_modules/warframe-items": { + "version": "1.1260.50", + "resolved": "https://registry.npmjs.org/warframe-items/-/warframe-items-1.1260.50.tgz", + "integrity": "sha512-03oNB6Yg61yUd7glewUUg0avnaGaAqc9oVPJk+1THFB0o/d4ppQSgL38yTUxMwmw0avCrqd+8z5TMrfXtvPDXQ==" + }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", diff --git a/package.json b/package.json index 8f7a8281..9be67021 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "dependencies": { "dotenv": "^16.1.3", "express": "^5.0.0-beta.1", - "mongoose": "^7.1.1" + "mongoose": "^7.1.1", + "warframe-items": "1.1260.50" }, "devDependencies": { "@tsconfig/node20": "^1.0.0", diff --git a/src/app.ts b/src/app.ts index d523d8fb..3ca8bfc8 100644 --- a/src/app.ts +++ b/src/app.ts @@ -23,7 +23,7 @@ app.use(bodyParser.raw()); app.use(express.json()); app.use(bodyParser.text()); app.use(morgan("dev")); -app.use(requestLogger); +//app.use(requestLogger); app.use("/api", apiRouter); //app.use("/test", testRouter); diff --git a/src/controllers/api/inventoryController.ts b/src/controllers/api/inventoryController.ts index badf79ad..c3ad296a 100644 --- a/src/controllers/api/inventoryController.ts +++ b/src/controllers/api/inventoryController.ts @@ -13,7 +13,6 @@ const inventoryController: RequestHandler = async (request: Request, response: R response.status(400).json({ error: "accountId was not provided" }); return; } - console.log(accountId); const inventory = await Inventory.findOne({ accountOwnerId: accountId }); diff --git a/src/controllers/api/purchaseController.ts b/src/controllers/api/purchaseController.ts index 7f0902e2..c73581ca 100644 --- a/src/controllers/api/purchaseController.ts +++ b/src/controllers/api/purchaseController.ts @@ -1,8 +1,13 @@ -import purchase from "@/static/fixed_responses/purchase.json"; +import { parseString } from "@/src/helpers/general"; +import { toPurchaseRequest } from "@/src/helpers/purchaseHelpers"; +import { handlePurchase } from "@/src/services/purchaseService"; import { Request, Response } from "express"; -const purchaseController = (_req: Request, res: Response): void => { - res.json(purchase); +const purchaseController = async (req: Request, res: Response) => { + const purchaseRequest = toPurchaseRequest(JSON.parse(String(req.body))); + const accountId = parseString(req.query.accountId); + const response = await handlePurchase(purchaseRequest, accountId); + res.json(response); }; export { purchaseController }; diff --git a/src/controllers/api/saveLoadout.ts b/src/controllers/api/saveLoadout.ts new file mode 100644 index 00000000..b83ad6b1 --- /dev/null +++ b/src/controllers/api/saveLoadout.ts @@ -0,0 +1,13 @@ +import { Inventory } from "@/src/models/inventoryModel"; +import { RequestHandler } from "express"; +import util from "util"; + +// eslint-disable-next-line @typescript-eslint/no-misused-promises +const saveLoadoutController: RequestHandler = async (req, res) => { + const body = JSON.parse(req.body); + console.log(util.inspect(body, { showHidden: false, depth: null, colors: true })); + + res.sendStatus(200); +}; + +export { saveLoadoutController }; diff --git a/src/controllers/custom/addItemController.ts b/src/controllers/custom/addItemController.ts new file mode 100644 index 00000000..504d539e --- /dev/null +++ b/src/controllers/custom/addItemController.ts @@ -0,0 +1,26 @@ +import { ItemType, toAddItemRequest } from "@/src/helpers/customHelpers/addItemHelpers"; +import { getWeaponType } from "@/src/helpers/purchaseHelpers"; +import { addPowerSuit, addWeapon } from "@/src/services/inventoryService"; +import { RequestHandler } from "express"; + +// eslint-disable-next-line @typescript-eslint/no-misused-promises +const addItemController: RequestHandler = async (req, res) => { + const request = toAddItemRequest(req.body); + + switch (request.type) { + case ItemType.Powersuit: + const powersuit = await addPowerSuit(request.InternalName, request.accountId); + res.json(powersuit); + return; + case ItemType.Weapon: + const weaponType = getWeaponType(request.InternalName); + const weapon = await addWeapon(weaponType, request.InternalName, request.accountId); + res.json(weapon); + break; + default: + res.status(400).json({ error: "something went wrong" }); + break; + } +}; + +export { addItemController }; diff --git a/src/controllers/custom/createAccountController.ts b/src/controllers/custom/createAccountController.ts index 0d0d567d..bece119c 100644 --- a/src/controllers/custom/createAccountController.ts +++ b/src/controllers/custom/createAccountController.ts @@ -1,7 +1,8 @@ -import { toCreateAccount, toDatabaseAccount } from "@/src/helpers/customHelpers"; +import { toCreateAccount, toDatabaseAccount } from "@/src/helpers/customHelpers/customHelpers"; import { createAccount } from "@/src/services/loginService"; import { RequestHandler } from "express"; +// eslint-disable-next-line @typescript-eslint/no-misused-promises const createAccountController: RequestHandler = async (req, res) => { const createAccountData = toCreateAccount(req.body); const databaseAccount = toDatabaseAccount(createAccountData); diff --git a/src/helpers/customHelpers/addItemHelpers.ts b/src/helpers/customHelpers/addItemHelpers.ts new file mode 100644 index 00000000..efc517c3 --- /dev/null +++ b/src/helpers/customHelpers/addItemHelpers.ts @@ -0,0 +1,55 @@ +import { isString, parseString } from "@/src/helpers/general"; +import { items } from "@/static/data/items"; + +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; + accountId: string; +} +export const isInternalName = (internalName: string): boolean => { + const item = items.find(i => i.uniqueName === internalName); + return Boolean(item); +}; + +const parseInternalName = (internalName: unknown): string => { + if (!isString(internalName) || !isInternalName(internalName)) { + throw new Error("incorrect internal name"); + } + + return internalName; +}; + +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 && "accountId" in body) { + return { + type: parseItemType(body.type), + InternalName: parseInternalName(body.internalName), + accountId: parseString(body.accountId) + }; + } + + throw new Error("malformed add item request"); +}; + +export { toAddItemRequest }; diff --git a/src/helpers/customHelpers.ts b/src/helpers/customHelpers/customHelpers.ts similarity index 94% rename from src/helpers/customHelpers.ts rename to src/helpers/customHelpers/customHelpers.ts index ebdde463..d96c7680 100644 --- a/src/helpers/customHelpers.ts +++ b/src/helpers/customHelpers/customHelpers.ts @@ -1,7 +1,7 @@ import { IAccountCreation } from "@/src/types/customTypes"; import { IDatabaseAccount } from "@/src/types/loginTypes"; import crypto from "crypto"; -import { isString, parseEmail, parseString } from "./general"; +import { isString, parseEmail, parseString } from "../general"; const getWhirlpoolHash = (rawPassword: string): string => { const whirlpool = crypto.createHash("whirlpool"); @@ -30,7 +30,6 @@ const toAccountCreation = (accountCreation: unknown): IAccountCreation => { "CountryCode" in accountCreation ) { const rawPassword = parsePassword(accountCreation.password); - console.log("email", accountCreation.email); return { email: parseEmail(accountCreation.email), password: getWhirlpoolHash(rawPassword), diff --git a/src/helpers/inventoryHelpers.ts b/src/helpers/inventoryHelpers.ts index 7879c007..7ecb6f92 100644 --- a/src/helpers/inventoryHelpers.ts +++ b/src/helpers/inventoryHelpers.ts @@ -1,4 +1,4 @@ -import { IInventoryDatabase, IInventoryResponse } from "@/src/types/inventoryTypes"; +import { IInventoryDatabase, IInventoryResponse } from "@/src/types/inventoryTypes/inventoryTypes"; const toInventoryResponse = (inventoryDatabase: IInventoryDatabase): IInventoryResponse => { // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/src/helpers/purchaseHelpers.ts b/src/helpers/purchaseHelpers.ts new file mode 100644 index 00000000..2dee9668 --- /dev/null +++ b/src/helpers/purchaseHelpers.ts @@ -0,0 +1,59 @@ +import { parseBoolean, parseNumber, parseString } from "@/src/helpers/general"; +import { WeaponTypeInternal } from "@/src/services/inventoryService"; +import { IPurchaseRequest } from "@/src/types/purchaseTypes"; +import { weapons } from "@/static/data/items"; + +const toPurchaseRequest = (purchaseRequest: unknown): IPurchaseRequest => { + if (!purchaseRequest || typeof purchaseRequest !== "object") { + throw new Error("incorrect or missing purchase request data"); + } + + if ( + "PurchaseParams" in purchaseRequest && + "buildLabel" in purchaseRequest && + purchaseRequest.PurchaseParams && + typeof purchaseRequest.PurchaseParams === "object" && + "Source" in purchaseRequest.PurchaseParams && + "StoreItem" in purchaseRequest.PurchaseParams && + "StorePage" in purchaseRequest.PurchaseParams && + "SearchTerm" in purchaseRequest.PurchaseParams && + "CurrentLocation" in purchaseRequest.PurchaseParams && + "Quantity" in purchaseRequest.PurchaseParams && + "UsePremium" in purchaseRequest.PurchaseParams && + "ExpectedPrice" in purchaseRequest.PurchaseParams + ) { + return { + PurchaseParams: { + Source: parseNumber(purchaseRequest.PurchaseParams.Source), + StoreItem: parseString(purchaseRequest.PurchaseParams.StoreItem), + StorePage: parseString(purchaseRequest.PurchaseParams.StorePage), + SearchTerm: parseString(purchaseRequest.PurchaseParams.SearchTerm), + CurrentLocation: parseString(purchaseRequest.PurchaseParams.CurrentLocation), + Quantity: parseNumber(purchaseRequest.PurchaseParams.Quantity), + UsePremium: parseBoolean(purchaseRequest.PurchaseParams.UsePremium), + ExpectedPrice: parseNumber(purchaseRequest.PurchaseParams.ExpectedPrice) + }, + buildLabel: parseString(purchaseRequest.buildLabel) + }; + } + + throw new Error("invalid purchaseRequest"); +}; + +const getWeaponType = (weaponName: string) => { + const weaponInfo = weapons.find(i => i.uniqueName === weaponName); + + if (!weaponInfo) { + throw new Error(`unknown weapon ${weaponName}`); + } + + const weaponType = weaponInfo.productCategory as WeaponTypeInternal; + + if (!weaponType) { + throw new Error(`unknown weapon category for item ${weaponName}`); + } + + return weaponType; +}; + +export { toPurchaseRequest, getWeaponType }; diff --git a/src/helpers/stringHelpers.ts b/src/helpers/stringHelpers.ts new file mode 100644 index 00000000..67995382 --- /dev/null +++ b/src/helpers/stringHelpers.ts @@ -0,0 +1,18 @@ +const getJSONfromString = (str: string): any => { + const jsonSubstring = str.substring(0, str.lastIndexOf("}") + 1); + return JSON.parse(jsonSubstring); +}; + +export const getSubstringFromKeyword = (str: string, keyword: string): string => { + const index = str.indexOf(keyword); + if (index == -1) { + throw new Error(`keyword ${keyword} not found in string ${str}`); + } + return str.substring(index); +}; + +export const getSubstringFromKeywordToKeyword = (str: string, keywordBegin: string, keywordEnd: string): string => { + const beginIndex = str.lastIndexOf(keywordBegin) + 1; + const endIndex = str.indexOf(keywordEnd); + return str.substring(beginIndex, endIndex + 1); +}; diff --git a/src/models/inventoryModel.ts b/src/models/inventoryModel.ts index bed6c50c..ee9c6e25 100644 --- a/src/models/inventoryModel.ts +++ b/src/models/inventoryModel.ts @@ -1,17 +1,13 @@ -import { Schema, model } from "mongoose"; -import { IInventoryDatabase, ISuitDatabase } from "../types/inventoryTypes"; +import { Model, Schema, SchemaType, Types, model } from "mongoose"; +import { FlavourItem, IInventoryDatabase } from "../types/inventoryTypes/inventoryTypes"; import { Oid } from "../types/commonTypes"; - -const polaritySchema = new Schema({ - Slot: Number, - Value: String -}); +import { ISuitDatabase, ISuitDocument } from "@/src/types/inventoryTypes/SuitTypes"; +import { IWeaponDatabase } from "@/src/types/inventoryTypes/weaponTypes"; const abilityOverrideSchema = new Schema({ Ability: String, Index: Number }); - const colorSchema = new Schema({ t0: Number, t1: Number, @@ -23,6 +19,67 @@ const colorSchema = new Schema({ m1: Number }); +const longGunConfigSchema = new Schema({ + Skins: [String], + pricol: colorSchema, + attcol: colorSchema, + eyecol: colorSchema, + sigcol: colorSchema, + Upgrades: [String], + Songs: [ + { + m: String, + b: String, + p: String, + s: String + } + ], + Name: String, + AbilityOverride: abilityOverrideSchema, + PvpUpgrades: [String], + ugly: Boolean +}); + +// longGunConfigSchema.set("toJSON", { +// transform(_document, returnedObject: ISuitDocument) { +// // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call +// returnedObject.ItemId = { $oid: returnedObject._id.toString() } satisfies Oid; +// delete returnedObject._id; +// delete returnedObject.__v; +// } +// }); + +const WeaponSchema = new Schema({ + ItemType: String, + Configs: [longGunConfigSchema], + UpgradeVer: Number, + XP: Number, + Features: Number, + Polarized: Number, + Polarity: Schema.Types.Mixed, //todo + FocusLens: String, + ModSlotPurchases: Number, + UpgradeType: Schema.Types.Mixed, //todo + UpgradeFingerprint: String, + ItemName: String, + ModularParts: [String], + UnlockLevel: Number +}); + +WeaponSchema.set("toJSON", { + transform(_document, returnedObject: ISuitDocument) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call + returnedObject.ItemId = { $oid: returnedObject._id.toString() } satisfies Oid; + delete returnedObject._id; + delete returnedObject.__v; + } +}); + +const polaritySchema = new Schema({ + Slot: Number, + Value: String +}); + const suitConfigSchema = new Schema({ Skins: [String], pricol: colorSchema, @@ -51,7 +108,7 @@ suitConfigSchema.set("toJSON", { } }); -const suitSchema = new Schema({ +const suitSchema = new Schema({ ItemType: String, Configs: [suitConfigSchema], UpgradeVer: Number, @@ -66,7 +123,7 @@ const suitSchema = new Schema({ }); suitSchema.set("toJSON", { - transform(_document, returnedObject) { + transform(_document, returnedObject: ISuitDocument) { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call returnedObject.ItemId = { $oid: returnedObject._id.toString() } satisfies Oid; delete returnedObject._id; @@ -74,7 +131,25 @@ suitSchema.set("toJSON", { } }); -const inventorySchema = new Schema({ +const slotsBinSchema = new Schema( + { + Slots: Number + }, + { _id: false } +); + +const FlavourItemSchema = new Schema({ + ItemType: String +}); + +FlavourItemSchema.set("toJSON", { + transform(_document, returnedObject: ISuitDocument) { + delete returnedObject._id; + delete returnedObject.__v; + } +}); + +const inventorySchema = new Schema({ accountOwnerId: Schema.Types.ObjectId, SubscribedToEmails: Number, Created: Schema.Types.Mixed, @@ -83,9 +158,9 @@ const inventorySchema = new Schema({ PremiumCredits: Number, PremiumCreditsFree: Number, FusionPoints: Number, - SuitBin: Schema.Types.Mixed, - WeaponBin: Schema.Types.Mixed, - SentinelBin: Schema.Types.Mixed, + SuitBin: slotsBinSchema, + WeaponBin: slotsBinSchema, + SentinelBin: slotsBinSchema, SpaceSuitBin: Schema.Types.Mixed, SpaceWeaponBin: Schema.Types.Mixed, PvpBonusLoadoutBin: Schema.Types.Mixed, @@ -104,12 +179,12 @@ const inventorySchema = new Schema({ RawUpgrades: [Schema.Types.Mixed], ReceivedStartingGear: Boolean, Suits: [suitSchema], - LongGuns: [Schema.Types.Mixed], - Pistols: [Schema.Types.Mixed], - Melee: [Schema.Types.Mixed], + LongGuns: [WeaponSchema], + Pistols: [WeaponSchema], + Melee: [WeaponSchema], Ships: [Schema.Types.Mixed], QuestKeys: [Schema.Types.Mixed], - FlavourItems: [Schema.Types.Mixed], + FlavourItems: [FlavourItemSchema], Scoops: [Schema.Types.Mixed], TrainingRetriesLeft: Number, LoadOutPresets: Schema.Types.Mixed, @@ -253,7 +328,16 @@ inventorySchema.set("toJSON", { } }); -const Suit = model("Suit", suitSchema); -const Inventory = model("Inventory", inventorySchema); +type InventoryDocumentProps = { + Suits: Types.DocumentArray; + LongGuns: Types.DocumentArray; + Pistols: Types.DocumentArray; + Melee: Types.DocumentArray; + FlavourItems: Types.DocumentArray; +}; -export { Inventory, Suit }; +type InventoryModelType = Model; + +const Inventory = model("Inventory", inventorySchema); + +export { Inventory }; diff --git a/src/models/t.ts b/src/models/t.ts deleted file mode 100644 index c59908c1..00000000 --- a/src/models/t.ts +++ /dev/null @@ -1,17 +0,0 @@ -import mongoose from "mongoose"; - -const accountSchema = new mongoose.Schema({ - data: JSON -}); - -// personSchema.set("toJSON", { -// transform: (document, returnedObject:) => { -// returnedObject.id = returnedObject._id.toString(); -// delete returnedObject._id; -// delete returnedObject.__v; -// }, -// }); - -const Account = mongoose.model("account", accountSchema); - -export { Account }; diff --git a/src/routes/api.ts b/src/routes/api.ts index 1a5d8a11..91d96fd2 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -28,6 +28,7 @@ import { updateChallengeProgressController } from "@/src/controllers/api/updateC import { updateSessionGetController, updateSessionPostController } from "@/src/controllers/api/updateSessionController"; import { viewController } from "@/src/controllers/api/viewController"; import { joinSessionController } from "@/src/controllers/api/joinSessionController"; +import { saveLoadoutController } from "@/src/controllers/api/saveLoadout"; import express from "express"; @@ -68,4 +69,5 @@ apiRouter.post("/missionInventoryUpdate.php", missionInventoryUpdateController); apiRouter.post("/genericUpdate.php", genericUpdateController); apiRouter.post("/rerollRandomMod.php", rerollRandomModController); apiRouter.post("/joinSession.php", joinSessionController); +apiRouter.post("/saveLoadout.php", saveLoadoutController); export { apiRouter }; diff --git a/src/routes/cache.ts b/src/routes/cache.ts index bb69a6db..803331c3 100644 --- a/src/routes/cache.ts +++ b/src/routes/cache.ts @@ -4,18 +4,14 @@ import config from "@/config.json"; const cacheRouter = express.Router(); cacheRouter.get("/B.Cache.Dx11.bin.*", (_req, res) => { - //console.log("asd", path.join(__dirname, "../data")); res.sendFile("static/data/B.Cache.Dx11_33.0.6.bin", { root: "./" }); }); cacheRouter.get("/B.Cache.Windows_en.bin*", (_req, res) => { - //console.log("asd", path.join(__dirname, "../data")); res.sendFile("static/data/B.Cache.Windows_en_33.0.10.bin", { root: "./" }); }); cacheRouter.get(/^\/origin\/([a-zA-Z0-9]+)\/H\.Cache\.bin.*$/, (_req, res) => { - // console.log("asd", path.join(__dirname, "../data")); - // console.log("asd", __dirname); res.sendFile(`static/data/H.Cache_${config.version}.bin`, { root: "./" }); }); diff --git a/src/routes/custom.ts b/src/routes/custom.ts index 3495e17f..1ab4ff2c 100644 --- a/src/routes/custom.ts +++ b/src/routes/custom.ts @@ -1,8 +1,10 @@ +import { addItemController } from "@/src/controllers/custom/addItemController"; import { createAccountController } from "@/src/controllers/custom/createAccountController"; import express from "express"; const customRouter = express.Router(); customRouter.post("/createAccount", createAccountController); +customRouter.post("/addItem", addItemController); export { customRouter }; diff --git a/src/services/inventoryService.ts b/src/services/inventoryService.ts index 36f49d06..6a947c9a 100644 --- a/src/services/inventoryService.ts +++ b/src/services/inventoryService.ts @@ -2,6 +2,9 @@ import { Inventory } from "@/src/models/inventoryModel"; import new_inventory from "@/static/fixed_responses/postTutorialInventory.json"; import config from "@/config.json"; import { Types } from "mongoose"; +import { ISuitResponse } from "@/src/types/inventoryTypes/SuitTypes"; +import { SlotType } from "@/src/types/purchaseTypes"; +import { IWeaponResponse } from "@/src/types/inventoryTypes/weaponTypes"; const createInventory = async (accountOwnerId: Types.ObjectId) => { try { @@ -22,4 +25,84 @@ const createInventory = async (accountOwnerId: Types.ObjectId) => { } }; -export { createInventory }; +//const updateInventory = async (accountOwnerId: Types.ObjectId, inventoryChanges: any) => {}; + +const getInventory = async (accountOwnerId: string) => { + const inventory = await Inventory.findOne({ accountOwnerId: accountOwnerId }); + + if (!inventory) { + throw new Error(`Didn't find an inventory for ${accountOwnerId}`); + } + + return inventory; +}; + +const addPowerSuit = async (powersuitName: string, accountId: string): Promise => { + const inventory = await getInventory(accountId); + const suitIndex = inventory.Suits.push({ ItemType: powersuitName, Configs: [], UpgradeVer: 101, XP: 0 }); + const changedInventory = await inventory.save(); + return changedInventory.Suits[suitIndex - 1].toJSON(); +}; + +export const updateSlots = async (slotType: SlotType, accountId: string, slots: number) => { + const inventory = await getInventory(accountId); + + switch (slotType) { + case SlotType.SUIT: + inventory.SuitBin.Slots += slots; + break; + case SlotType.WEAPON: + inventory.WeaponBin.Slots += slots; + break; + default: + throw new Error("invalid slot type"); + } + await inventory.save(); +}; + +export const updateCurrency = async (price: number, usePremium: boolean, accountId: string) => { + const currencyName = usePremium ? "PremiumCredits" : "RegularCredits"; + + const inventory = await getInventory(accountId); + inventory[currencyName] = inventory[currencyName] - price; + await inventory.save(); + return { [currencyName]: -price }; +}; + +export type WeaponTypeInternal = "LongGuns" | "Pistols" | "Melee"; + +export const addWeapon = async ( + weaponType: WeaponTypeInternal, + weaponName: string, + accountId: string +): Promise => { + const inventory = await getInventory(accountId); + + let weaponIndex; + switch (weaponType) { + case "LongGuns": + weaponIndex = inventory.LongGuns.push({ ItemType: weaponName, Configs: [], XP: 0 }); + break; + case "Pistols": + weaponIndex = inventory.Pistols.push({ ItemType: weaponName, Configs: [], XP: 0 }); + break; + case "Melee": + weaponIndex = inventory.Melee.push({ ItemType: weaponName, Configs: [], XP: 0 }); + break; + default: + throw new Error("unknown weapon type"); + } + + const changedInventory = await inventory.save(); + return changedInventory[weaponType][weaponIndex - 1].toJSON(); +}; + +export const addCustomization = async (customizatonName: string, accountId: string) => { + const inventory = await getInventory(accountId); + + const flavourItemIndex = inventory.FlavourItems.push({ ItemType: customizatonName }) - 1; + const changedInventory = await inventory.save(); + return changedInventory.FlavourItems[flavourItemIndex].toJSON(); +}; + +export { createInventory, addPowerSuit }; diff --git a/src/services/purchaseService.ts b/src/services/purchaseService.ts new file mode 100644 index 00000000..76d35017 --- /dev/null +++ b/src/services/purchaseService.ts @@ -0,0 +1,110 @@ +import { getWeaponType } from "@/src/helpers/purchaseHelpers"; +import { getSubstringFromKeyword } from "@/src/helpers/stringHelpers"; +import { addCustomization, addPowerSuit, addWeapon, updateSlots } from "@/src/services/inventoryService"; +import { IPurchaseRequest, SlotType } from "@/src/types/purchaseTypes"; + +export const getStoreItemCategory = (storeItem: string) => { + const storeItemString = getSubstringFromKeyword(storeItem, "StoreItems/"); + const storeItemElements = storeItemString.split("/"); + return storeItemElements[1]; +}; + +export const getStoreItemTypesCategory = (typesItem: string) => { + const typesString = getSubstringFromKeyword(typesItem, "Types"); + const typeElements = typesString.split("/"); + if (typesItem.includes("StoreItems")) { + return typeElements[2]; + } + return typeElements[1]; +}; + +export const handlePurchase = async (purchaseRequest: IPurchaseRequest, accountId: string) => { + console.log(purchaseRequest); + const storeCategory = getStoreItemCategory(purchaseRequest.PurchaseParams.StoreItem); + const internalName = purchaseRequest.PurchaseParams.StoreItem.replace("/StoreItems", ""); + console.log("Store category", storeCategory); + + let purchaseResponse; + switch (storeCategory) { + case "Powersuits": + purchaseResponse = await handlePowersuitPurchase(internalName, accountId); + break; + case "Weapons": + purchaseResponse = await handleWeaponsPurchase(internalName, accountId); + break; + case "Types": + purchaseResponse = await handleTypesPurchase(internalName, accountId); + break; + + default: + throw new Error(`unknown store category: ${storeCategory} not implemented or new`); + } + + // const currencyResponse = await updateCurrency( + // purchaseRequest.PurchaseParams.ExpectedPrice, + // purchaseRequest.PurchaseParams.UsePremium, + // accountId + // ); + + // (purchaseResponse as IPurchaseResponse).InventoryChanges = { + // ...purchaseResponse.InventoryChanges, + // ...currencyResponse + // }; + + return purchaseResponse; +}; + +const handleWeaponsPurchase = async (weaponName: string, accountId: string) => { + const weaponType = getWeaponType(weaponName); + const addedWeapon = await addWeapon(weaponType, weaponName, accountId); + + await updateSlots(SlotType.WEAPON, accountId, -1); + + return { + InventoryChanges: { + WeaponBin: { count: 1, platinum: 0, Slots: -1 }, + [weaponType]: [addedWeapon] + } + }; +}; + +const handlePowersuitPurchase = async (powersuitName: string, accountId: string) => { + const suit = await addPowerSuit(powersuitName, accountId); + await updateSlots(SlotType.WEAPON, accountId, -1); + + return { + InventoryChanges: { + SuitBin: { + count: 1, + platinum: 0, + Slots: -1 + }, + Suits: [suit] + } + }; +}; + +const handleTypesPurchase = async (typesName: string, accountId: string) => { + const typeCategory = getStoreItemTypesCategory(typesName); + console.log("type category", typeCategory); + switch (typeCategory) { + case "SuitCustomizations": + return await handleSuitCustomizationsPurchase(typesName, accountId); + // case "Recipes": + // break; + // case "Sentinels": + // break; + default: + throw new Error(`unknown Types category: ${typeCategory} not implemented or new`); + } +}; + +const handleSuitCustomizationsPurchase = async (customizationName: string, accountId: string) => { + const customization = await addCustomization(customizationName, accountId); + + return { + InventoryChanges: { + FlavourItems: [customization] + } + }; +}; diff --git a/src/types/inventoryTypes/SuitTypes.ts b/src/types/inventoryTypes/SuitTypes.ts new file mode 100644 index 00000000..187d5ab1 --- /dev/null +++ b/src/types/inventoryTypes/SuitTypes.ts @@ -0,0 +1,44 @@ +import { Oid } from "@/src/types/commonTypes"; +import { AbilityOverride, Color, Polarity } from "@/src/types/inventoryTypes/commonInventoryTypes"; +import { Document } from "mongoose"; + +export interface ISuitDocument extends ISuitResponse, Document {} + +export interface ISuitResponse extends ISuitDatabase { + ItemId: Oid; +} + +export interface ISuitDatabase { + ItemType: string; + Configs: SuitConfig[]; + UpgradeVer?: number; + XP?: number; + InfestationDate?: Date; + Features?: number; + Polarity?: Polarity[]; + Polarized?: number; + ModSlotPurchases?: number; + FocusLens?: string; + UnlockLevel?: number; +} + +export interface SuitConfig { + Skins?: string[]; + pricol?: Color; + attcol?: Color; + eyecol?: Color; + sigcol?: Color; + Upgrades?: string[]; + Songs?: Song[]; + Name?: string; + AbilityOverride?: AbilityOverride; + PvpUpgrades?: string[]; + ugly?: boolean; +} + +export interface Song { + m?: string; + b?: string; + p?: string; + s: string; +} diff --git a/src/types/inventoryTypes/commonInventoryTypes.ts b/src/types/inventoryTypes/commonInventoryTypes.ts new file mode 100644 index 00000000..fd59073c --- /dev/null +++ b/src/types/inventoryTypes/commonInventoryTypes.ts @@ -0,0 +1,42 @@ +export interface Polarity { + Slot: number; + Value: FocusSchool; +} + +export enum FocusSchool { + ApAny = "AP_ANY", + ApAttack = "AP_ATTACK", + ApDefense = "AP_DEFENSE", + ApPower = "AP_POWER", + ApPrecept = "AP_PRECEPT", + ApTactic = "AP_TACTIC", + ApUmbra = "AP_UMBRA", + ApUniversal = "AP_UNIVERSAL", + ApWard = "AP_WARD" +} + +export interface Color { + t0?: number; + t1?: number; + t2?: number; + t3?: number; + en?: number; + e1?: number; + m0?: number; + m1?: number; +} + +export interface AbilityOverride { + Ability: string; + Index: number; +} + +export interface SlotsBin { + Slots: number; +} + +export interface sigcol { + t0: number; + t1: number; + en: number; +} diff --git a/src/types/inventoryTypes.ts b/src/types/inventoryTypes/inventoryTypes.ts similarity index 89% rename from src/types/inventoryTypes.ts rename to src/types/inventoryTypes/inventoryTypes.ts index 6a428931..938ac651 100644 --- a/src/types/inventoryTypes.ts +++ b/src/types/inventoryTypes/inventoryTypes.ts @@ -1,12 +1,17 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { Document, Types } from "mongoose"; -import { Oid } from "./commonTypes"; +import { Oid } from "../commonTypes"; +import { AbilityOverride, Color, FocusSchool, Polarity } from "@/src/types/inventoryTypes/commonInventoryTypes"; +import { ISuitDatabase } from "@/src/types/inventoryTypes/SuitTypes"; +import { OperatorLoadOutSigcol, IWeaponDatabase } from "@/src/types/inventoryTypes/weaponTypes"; export interface IInventoryDatabase extends IInventoryResponse { accountOwnerId: Types.ObjectId; } +export interface IInventoryDatabaseDocument extends IInventoryDatabase, Document {} + export interface IInventoryResponse { SubscribedToEmails: number; Created: Date; @@ -36,9 +41,9 @@ export interface IInventoryResponse { RawUpgrades: RawUpgrade[]; ReceivedStartingGear: boolean; Suits: ISuitDatabase[]; - LongGuns: LongGun[]; - Pistols: LongGun[]; - Melee: Melee[]; + LongGuns: IWeaponDatabase[]; + Pistols: IWeaponDatabase[]; + Melee: IWeaponDatabase[]; Ships: Ship[]; QuestKeys: QuestKey[]; FlavourItems: FlavourItem[]; @@ -188,17 +193,6 @@ export interface AdultOperatorLoadOut { ItemId: Oid; } -export interface Color { - t0?: number; - t1?: number; - t2?: number; - t3?: number; - en?: number; - e1?: number; - m0?: number; - m1?: number; -} - export interface Affiliation { Initiated?: boolean; Standing: number; @@ -319,23 +313,6 @@ export interface CrewShipHarnessConfig { Upgrades?: string[]; } -export interface Polarity { - Slot: number; - Value: FocusSchool; -} - -export enum FocusSchool { - ApAny = "AP_ANY", - ApAttack = "AP_ATTACK", - ApDefense = "AP_DEFENSE", - ApPower = "AP_POWER", - ApPrecept = "AP_PRECEPT", - ApTactic = "AP_TACTIC", - ApUmbra = "AP_UMBRA", - ApUniversal = "AP_UNIVERSAL", - ApWard = "AP_WARD" -} - export interface CrewShipSalvageBinClass { Extra: number; Slots: number; @@ -359,7 +336,7 @@ export interface CrewShipWeapon { export interface CrewShip { ItemType: string; Configs: CrewShipConfig[]; - Weapon: Weapon; + Weapon: CrewshipWeapon; Customization: Customization; ItemName: string; RailjackImage: FlavourItem; @@ -400,7 +377,7 @@ export interface FlavourItem { ItemType: string; } -export interface Weapon { +export interface CrewshipWeapon { PILOT: Pilot; PORT_GUNS: PortGuns; } @@ -713,33 +690,6 @@ export interface Normal { ItemId: Oid; } -export interface LongGun { - ItemType: string; - Configs: LongGunConfig[]; - UpgradeVer?: number; - XP?: number; - Features?: number; - ItemId: Oid; - Polarized?: number; - Polarity?: Polarity[]; - FocusLens?: string; - ModSlotPurchases?: number; - UpgradeType?: UpgradeType; - UpgradeFingerprint?: string; - ItemName?: string; - ModularParts?: string[]; - UnlockLevel?: number; -} - -export interface LongGunConfig { - Upgrades?: string[]; - Skins?: string[]; - pricol?: Color; - attcol?: Color; - PvpUpgrades?: string[]; - Name?: string; -} - export enum UpgradeType { LotusWeaponsGrineerKuvaLichUpgradesInnateDamageRandomMod = "/Lotus/Weapons/Grineer/KuvaLich/Upgrades/InnateDamageRandomMod" } @@ -774,40 +724,6 @@ export interface MechSuit { ItemId: Oid; } -export interface Melee { - ItemType: string; - Configs: MeleeConfig[]; - UpgradeVer?: number; - XP?: number; - Features?: number; - Polarity?: Polarity[]; - Polarized?: number; - ModSlotPurchases?: number; - ItemId: Oid; - FocusLens?: string; - ModularParts?: string[]; - ItemName?: string; - UpgradeType?: UpgradeType; - UpgradeFingerprint?: string; - UnlockLevel?: number; -} - -export interface MeleeConfig { - Skins?: string[]; - pricol?: Color; - Upgrades?: string[]; - attcol?: Color; - eyecol?: OperatorLoadOutSigcol; - Name?: string; - PvpUpgrades?: string[]; -} - -export interface OperatorLoadOutSigcol { - t0?: number; - t1?: number; - en?: number; -} - export interface Mission { Completes: number; Tier?: number; @@ -885,11 +801,6 @@ export interface OperatorLoadOut { ItemId: Oid; } -export interface AbilityOverride { - Ability: string; - Index: number; -} - export interface PendingCoupon { Expiry: Date; Discount: number; @@ -1159,48 +1070,6 @@ export interface NotePacks { PERCUSSION: string; } -export interface ISuitDocument extends ISuitDatabase, Document {} - -export interface ISuitResponse extends ISuitDatabase { - ItemId: Oid; -} - -export interface ISuitDatabase { - ItemType: string; - Configs: SuitConfig[]; - UpgradeVer?: number; - XP?: number; - InfestationDate?: Date; - Features?: number; - Polarity?: Polarity[]; - Polarized?: number; - ModSlotPurchases?: number; - ItemId: Oid; - FocusLens?: string; - UnlockLevel?: number; -} - -export interface SuitConfig { - Skins?: string[]; - pricol?: Color; - attcol?: Color; - eyecol?: Color; - sigcol?: Color; - Upgrades?: string[]; - Songs?: Song[]; - Name?: string; - AbilityOverride?: AbilityOverride; - PvpUpgrades?: string[]; - ugly?: boolean; -} - -export interface Song { - m?: string; - b?: string; - p?: string; - s: string; -} - export interface TauntHistory { node: string; state: string; diff --git a/src/types/inventoryTypes/weaponTypes.ts b/src/types/inventoryTypes/weaponTypes.ts new file mode 100644 index 00000000..3c561e2d --- /dev/null +++ b/src/types/inventoryTypes/weaponTypes.ts @@ -0,0 +1,39 @@ +import { Oid } from "@/src/types/commonTypes"; +import { Color, Polarity } from "@/src/types/inventoryTypes/commonInventoryTypes"; + +export interface IWeaponResponse extends IWeaponDatabase { + ItemId: Oid; +} + +export interface IWeaponDatabase { + ItemType: string; + Configs: WeaponConfig[]; + UpgradeVer?: number; + XP?: number; + Features?: number; + Polarized?: number; + Polarity?: Polarity[]; + FocusLens?: string; + ModSlotPurchases?: number; + UpgradeType?: string; + UpgradeFingerprint?: string; + ItemName?: string; + ModularParts?: string[]; + UnlockLevel?: number; +} + +export interface WeaponConfig { + Skins?: string[]; + pricol?: Color; + Upgrades?: string[]; + attcol?: Color; + eyecol?: OperatorLoadOutSigcol; + Name?: string; + PvpUpgrades?: string[]; +} + +export interface OperatorLoadOutSigcol { + t0?: number; + t1?: number; + en?: number; +} diff --git a/src/types/purchaseTypes.ts b/src/types/purchaseTypes.ts new file mode 100644 index 00000000..1add87cc --- /dev/null +++ b/src/types/purchaseTypes.ts @@ -0,0 +1,43 @@ +/* eslint-disable prettier/prettier */ +import { ISuitDatabase } from "@/src/types/inventoryTypes/SuitTypes"; +import { IWeaponResponse } from "@/src/types/inventoryTypes/weaponTypes"; + +export interface IPurchaseRequest { + PurchaseParams: IPurchaseParams; + buildLabel: string; +} + +export interface IPurchaseParams { + Source: number; + StoreItem: string; + StorePage: string; + SearchTerm: string; + CurrentLocation: string; + Quantity: number; + UsePremium: boolean; + ExpectedPrice: number; +} + +export interface IPurchaseResponse { + InventoryChanges: { + SuitBin?: IBinChanges; + WeaponBin?: IBinChanges; + Suits?: ISuitDatabase[]; + LongGuns?: IWeaponResponse[]; + Pistols?: IWeaponResponse[]; + Melee?: IWeaponResponse[]; + PremiumCredits?: number; + RegularCredits?: number; + }; +} + +export type IBinChanges = { + count: number; + platinum: number; + Slots: number; +}; + +export enum SlotType { + SUIT = "SuitBin", + WEAPON = "WeaponBin" +}