feat: Kinematic Instant Messaging #801
23
src/controllers/api/clearDialogueHistoryController.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const clearDialogueHistoryController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
const request = JSON.parse(String(req.body)) as IClearDialogueRequest;
|
||||||
|
|||||||
|
if (inventory.DialogueHistory && inventory.DialogueHistory.Dialogues) {
|
||||||
|
for (const dialogueName of request.Dialogues) {
|
||||||
|
const index = inventory.DialogueHistory.Dialogues.findIndex(x => x.DialogueName == dialogueName);
|
||||||
|
if (index != -1) {
|
||||||
|
inventory.DialogueHistory.Dialogues.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
![]() ⚠️ Potential issue Add request validation and error handling. The function should validate the request structure and handle potential errors during inventory operations. Apply this diff to add validation and error handling:
📝 Committable suggestion
🧰 Tools🪛 Biome (1.9.4)[error] 9-9: Change to an optional chain. Unsafe fix: Change to an optional chain. (lint/complexity/useOptionalChain) _:warning: Potential issue_
**Add request validation and error handling.**
The function should validate the request structure and handle potential errors during inventory operations.
Apply this diff to add validation and error handling:
```diff
+ if (!request.Dialogues || !Array.isArray(request.Dialogues)) {
+ res.status(400).json({ error: 'Invalid request: Dialogues array is required' });
+ return;
+ }
+
if (inventory.DialogueHistory && inventory.DialogueHistory.Dialogues) {
for (const dialogueName of request.Dialogues) {
+ if (typeof dialogueName !== 'string') {
+ res.status(400).json({ error: 'Invalid request: Dialogue names must be strings' });
+ return;
+ }
+
const index = inventory.DialogueHistory.Dialogues.findIndex(x => x.DialogueName == dialogueName);
if (index != -1) {
inventory.DialogueHistory.Dialogues.splice(index, 1);
}
}
}
- await inventory.save();
- res.end();
+ try {
+ await inventory.save();
+ res.status(200).json({
+ success: true,
+ message: 'Dialogue history cleared successfully'
+ });
+ } catch (error) {
+ res.status(500).json({ error: 'Failed to save inventory' });
+ }
```
<!-- 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
if (!request.Dialogues || !Array.isArray(request.Dialogues)) {
res.status(400).json({ error: 'Invalid request: Dialogues array is required' });
return;
}
if (inventory.DialogueHistory && inventory.DialogueHistory.Dialogues) {
for (const dialogueName of request.Dialogues) {
if (typeof dialogueName !== 'string') {
res.status(400).json({ error: 'Invalid request: Dialogue names must be strings' });
return;
}
const index = inventory.DialogueHistory.Dialogues.findIndex(x => x.DialogueName == dialogueName);
if (index != -1) {
inventory.DialogueHistory.Dialogues.splice(index, 1);
}
}
}
try {
await inventory.save();
res.status(200).json({
success: true,
message: 'Dialogue history cleared successfully'
});
} catch (error) {
res.status(500).json({ error: 'Failed to save inventory' });
}
`````
</details>
<!-- suggestion_end -->
<details>
<summary>🧰 Tools</summary>
<details>
<summary>🪛 Biome (1.9.4)</summary>
[error] 9-9: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
</details>
</details>
<!-- This is an auto-generated comment by CodeRabbit -->
|
|||||||
|
await inventory.save();
|
||||||
|
res.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IClearDialogueRequest {
|
||||||
|
Dialogues: string[];
|
||||||
|
}
|
85
src/controllers/api/saveDialogueController.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { ICompletedDialogue } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
import { logger } from "@/src/utils/logger";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const saveDialogueController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const request = JSON.parse(String(req.body)) as SaveDialogueRequest;
|
||||||
![]() ⚠️ Potential issue Add error handling for JSON parsing and type validation. The JSON parsing operation could throw an exception if the request body is malformed. Apply this diff to add error handling:
📝 Committable suggestion
_:warning: Potential issue_
**Add error handling for JSON parsing and type validation.**
The JSON parsing operation could throw an exception if the request body is malformed.
Apply this diff to add error handling:
```diff
- const request = JSON.parse(String(req.body)) as SaveDialogueRequest;
+ let request: SaveDialogueRequest;
+ try {
+ request = JSON.parse(String(req.body));
+ if (!('YearIteration' in request || 'DialogueName' in request)) {
+ throw new Error('Invalid request type');
+ }
+ } catch (error) {
+ res.status(400).json({ error: 'Invalid request body' });
+ return;
+ }
```
<!-- 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
let request: SaveDialogueRequest;
try {
request = JSON.parse(String(req.body));
if (!('YearIteration' in request || 'DialogueName' in request)) {
throw new Error('Invalid request type');
}
} catch (error) {
res.status(400).json({ error: 'Invalid request body' });
return;
}
`````
</details>
<!-- suggestion_end -->
<!-- This is an auto-generated comment by CodeRabbit -->
|
|||||||
|
if ("YearIteration" in request) {
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
if (inventory.DialogueHistory) {
|
||||||
|
inventory.DialogueHistory.YearIteration = request.YearIteration;
|
||||||
|
} else {
|
||||||
|
inventory.DialogueHistory = { YearIteration: request.YearIteration };
|
||||||
|
}
|
||||||
|
await inventory.save();
|
||||||
|
res.end();
|
||||||
|
} else {
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
if (!inventory.DialogueHistory) {
|
||||||
|
throw new Error("bad inventory state");
|
||||||
|
}
|
||||||
|
if (request.QueuedDialogues.length != 0 || request.OtherDialogueInfos.length != 0) {
|
||||||
|
logger.error(`saveDialogue request not fully handled: ${String(req.body)}`);
|
||||||
|
}
|
||||||
|
inventory.DialogueHistory.Dialogues ??= [];
|
||||||
|
let dialogue = inventory.DialogueHistory.Dialogues.find(x => x.DialogueName == request.DialogueName);
|
||||||
|
if (!dialogue) {
|
||||||
|
dialogue =
|
||||||
|
inventory.DialogueHistory.Dialogues[
|
||||||
|
inventory.DialogueHistory.Dialogues.push({
|
||||||
|
Rank: 0,
|
||||||
|
Chemistry: 0,
|
||||||
|
AvailableDate: new Date(0),
|
||||||
|
AvailableGiftDate: new Date(0),
|
||||||
|
RankUpExpiry: new Date(0),
|
||||||
|
BountyChemExpiry: new Date(0),
|
||||||
|
Gifts: [],
|
||||||
|
Booleans: [],
|
||||||
|
Completed: [],
|
||||||
|
DialogueName: request.DialogueName
|
||||||
|
}) - 1
|
||||||
|
];
|
||||||
|
}
|
||||||
|
dialogue.Rank = request.Rank;
|
||||||
|
dialogue.Chemistry = request.Chemistry;
|
||||||
|
//dialogue.QueuedDialogues = request.QueuedDialogues;
|
||||||
|
for (const bool of request.Booleans) {
|
||||||
|
dialogue.Booleans.push(bool);
|
||||||
|
}
|
||||||
|
for (const bool of request.ResetBooleans) {
|
||||||
|
const index = dialogue.Booleans.findIndex(x => x == bool);
|
||||||
|
if (index != -1) {
|
||||||
|
dialogue.Booleans.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dialogue.Completed.push(request.Data);
|
||||||
|
const tomorrowAt0Utc = (Math.trunc(Date.now() / (86400 * 1000)) + 1) * 86400 * 1000;
|
||||||
|
dialogue.AvailableDate = new Date(tomorrowAt0Utc);
|
||||||
|
await inventory.save();
|
||||||
|
res.json({
|
||||||
|
InventoryChanges: [],
|
||||||
|
AvailableDate: { $date: { $numberLong: tomorrowAt0Utc.toString() } }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
type SaveDialogueRequest = SaveYearIterationRequest | SaveCompletedDialogueRequest;
|
||||||
|
|
||||||
|
interface SaveYearIterationRequest {
|
||||||
|
YearIteration: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SaveCompletedDialogueRequest {
|
||||||
|
DialogueName: string;
|
||||||
|
Rank: number;
|
||||||
|
Chemistry: number;
|
||||||
|
CompletionType: number;
|
||||||
|
QueuedDialogues: string[]; // unsure
|
||||||
|
Booleans: string[];
|
||||||
|
ResetBooleans: string[];
|
||||||
|
Data: ICompletedDialogue;
|
||||||
|
OtherDialogueInfos: string[]; // unsure
|
||||||
|
}
|
@ -47,7 +47,12 @@ import {
|
|||||||
ICrewShipPilotWeapon,
|
ICrewShipPilotWeapon,
|
||||||
IShipExterior,
|
IShipExterior,
|
||||||
IHelminthFoodRecord,
|
IHelminthFoodRecord,
|
||||||
ICrewShipMembersDatabase
|
ICrewShipMembersDatabase,
|
||||||
|
IDialogueHistoryDatabase,
|
||||||
|
IDialogueDatabase,
|
||||||
|
IDialogueGift,
|
||||||
|
ICompletedDialogue,
|
||||||
|
IDialogueClient
|
||||||
} from "../../types/inventoryTypes/inventoryTypes";
|
} from "../../types/inventoryTypes/inventoryTypes";
|
||||||
import { IOid } from "../../types/commonTypes";
|
import { IOid } from "../../types/commonTypes";
|
||||||
import {
|
import {
|
||||||
@ -710,6 +715,60 @@ crewShipSchema.set("toJSON", {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const dialogueGiftSchema = new Schema<IDialogueGift>(
|
||||||
|
{
|
||||||
|
Item: String,
|
||||||
|
GiftedQuantity: Number
|
||||||
|
},
|
||||||
|
{ _id: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
const completedDialogueSchema = new Schema<ICompletedDialogue>(
|
||||||
|
{
|
||||||
|
Id: { type: String, required: true },
|
||||||
|
Booleans: { type: [String], required: true },
|
||||||
|
Choices: { type: [Number], required: true }
|
||||||
|
},
|
||||||
|
{ _id: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
const dialogueSchema = new Schema<IDialogueDatabase>(
|
||||||
|
{
|
||||||
|
Rank: Number,
|
||||||
|
Chemistry: Number,
|
||||||
|
AvailableDate: Date,
|
||||||
|
AvailableGiftDate: Date,
|
||||||
|
RankUpExpiry: Date,
|
||||||
|
BountyChemExpiry: Date,
|
||||||
|
//QueuedDialogues: ???
|
||||||
|
Gifts: { type: [dialogueGiftSchema], default: [] },
|
||||||
|
Booleans: { type: [String], default: [] },
|
||||||
|
Completed: { type: [completedDialogueSchema], default: [] },
|
||||||
|
DialogueName: String
|
||||||
|
},
|
||||||
|
{ _id: false }
|
||||||
|
);
|
||||||
|
dialogueSchema.set("toJSON", {
|
||||||
|
virtuals: true,
|
||||||
|
transform(_doc, ret) {
|
||||||
|
const db = ret as IDialogueDatabase;
|
||||||
|
const client = ret as IDialogueClient;
|
||||||
|
|
||||||
|
client.AvailableDate = toMongoDate(db.AvailableDate);
|
||||||
|
client.AvailableGiftDate = toMongoDate(db.AvailableGiftDate);
|
||||||
|
client.RankUpExpiry = toMongoDate(db.RankUpExpiry);
|
||||||
|
client.BountyChemExpiry = toMongoDate(db.BountyChemExpiry);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const dialogueHistorySchema = new Schema<IDialogueHistoryDatabase>(
|
||||||
|
{
|
||||||
|
YearIteration: { type: Number, required: true },
|
||||||
|
Dialogues: { type: [dialogueSchema], required: false }
|
||||||
|
},
|
||||||
|
{ _id: false }
|
||||||
|
);
|
||||||
|
|
||||||
const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
|
const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
|
||||||
{
|
{
|
||||||
accountOwnerId: Schema.Types.ObjectId,
|
accountOwnerId: Schema.Types.ObjectId,
|
||||||
@ -1069,7 +1128,9 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
|
|||||||
//Grustag three
|
//Grustag three
|
||||||
DeathSquadable: Boolean,
|
DeathSquadable: Boolean,
|
||||||
|
|
||||||
EndlessXP: { type: [endlessXpProgressSchema], default: undefined }
|
EndlessXP: { type: [endlessXpProgressSchema], default: undefined },
|
||||||
|
|
||||||
|
DialogueHistory: dialogueHistorySchema
|
||||||
},
|
},
|
||||||
{ timestamps: { createdAt: "Created" } }
|
{ timestamps: { createdAt: "Created" } }
|
||||||
);
|
);
|
||||||
|
@ -6,6 +6,7 @@ import { archonFusionController } from "@/src/controllers/api/archonFusionContro
|
|||||||
import { artifactsController } from "../controllers/api/artifactsController";
|
import { artifactsController } from "../controllers/api/artifactsController";
|
||||||
import { checkDailyMissionBonusController } from "@/src/controllers/api/checkDailyMissionBonusController";
|
import { checkDailyMissionBonusController } from "@/src/controllers/api/checkDailyMissionBonusController";
|
||||||
import { claimCompletedRecipeController } from "@/src/controllers/api/claimCompletedRecipeController";
|
import { claimCompletedRecipeController } from "@/src/controllers/api/claimCompletedRecipeController";
|
||||||
|
import { clearDialogueHistoryController } from "@/src/controllers/api/clearDialogueHistoryController";
|
||||||
import { createGuildController } from "@/src/controllers/api/createGuildController";
|
import { createGuildController } from "@/src/controllers/api/createGuildController";
|
||||||
import { creditsController } from "@/src/controllers/api/creditsController";
|
import { creditsController } from "@/src/controllers/api/creditsController";
|
||||||
import { deleteSessionController } from "@/src/controllers/api/deleteSessionController";
|
import { deleteSessionController } from "@/src/controllers/api/deleteSessionController";
|
||||||
@ -52,6 +53,7 @@ import { projectionManagerController } from "../controllers/api/projectionManage
|
|||||||
import { purchaseController } from "@/src/controllers/api/purchaseController";
|
import { purchaseController } from "@/src/controllers/api/purchaseController";
|
||||||
import { queueDojoComponentDestructionController } from "@/src/controllers/api/queueDojoComponentDestructionController";
|
import { queueDojoComponentDestructionController } from "@/src/controllers/api/queueDojoComponentDestructionController";
|
||||||
import { rerollRandomModController } from "@/src/controllers/api/rerollRandomModController";
|
import { rerollRandomModController } from "@/src/controllers/api/rerollRandomModController";
|
||||||
|
import { saveDialogueController } from "@/src/controllers/api/saveDialogueController";
|
||||||
import { saveLoadoutController } from "@/src/controllers/api/saveLoadout";
|
import { saveLoadoutController } from "@/src/controllers/api/saveLoadout";
|
||||||
import { sellController } from "@/src/controllers/api/sellController";
|
import { sellController } from "@/src/controllers/api/sellController";
|
||||||
import { setActiveQuestController } from "@/src/controllers/api/setActiveQuestController";
|
import { setActiveQuestController } from "@/src/controllers/api/setActiveQuestController";
|
||||||
@ -118,6 +120,7 @@ apiRouter.post("/arcaneCommon.php", arcaneCommonController);
|
|||||||
apiRouter.post("/archonFusion.php", archonFusionController);
|
apiRouter.post("/archonFusion.php", archonFusionController);
|
||||||
apiRouter.post("/artifacts.php", artifactsController);
|
apiRouter.post("/artifacts.php", artifactsController);
|
||||||
apiRouter.post("/claimCompletedRecipe.php", claimCompletedRecipeController);
|
apiRouter.post("/claimCompletedRecipe.php", claimCompletedRecipeController);
|
||||||
|
apiRouter.post("/clearDialogueHistory.php", clearDialogueHistoryController);
|
||||||
apiRouter.post("/createGuild.php", createGuildController);
|
apiRouter.post("/createGuild.php", createGuildController);
|
||||||
apiRouter.post("/endlessXp.php", endlessXpController);
|
apiRouter.post("/endlessXp.php", endlessXpController);
|
||||||
apiRouter.post("/evolveWeapon.php", evolveWeaponController);
|
apiRouter.post("/evolveWeapon.php", evolveWeaponController);
|
||||||
@ -142,6 +145,7 @@ apiRouter.post("/playerSkills.php", playerSkillsController);
|
|||||||
apiRouter.post("/projectionManager.php", projectionManagerController);
|
apiRouter.post("/projectionManager.php", projectionManagerController);
|
||||||
apiRouter.post("/purchase.php", purchaseController);
|
apiRouter.post("/purchase.php", purchaseController);
|
||||||
apiRouter.post("/rerollRandomMod.php", rerollRandomModController);
|
apiRouter.post("/rerollRandomMod.php", rerollRandomModController);
|
||||||
|
apiRouter.post("/saveDialogue.php", saveDialogueController);
|
||||||
apiRouter.post("/saveLoadout.php", saveLoadoutController);
|
apiRouter.post("/saveLoadout.php", saveLoadoutController);
|
||||||
apiRouter.post("/sell.php", sellController);
|
apiRouter.post("/sell.php", sellController);
|
||||||
apiRouter.post("/setEquippedInstrument.php", setEquippedInstrumentController);
|
apiRouter.post("/setEquippedInstrument.php", setEquippedInstrumentController);
|
||||||
|
@ -306,6 +306,7 @@ export interface IInventoryResponse extends IDailyAffiliations {
|
|||||||
Harvestable: boolean;
|
Harvestable: boolean;
|
||||||
DeathSquadable: boolean;
|
DeathSquadable: boolean;
|
||||||
EndlessXP?: IEndlessXpProgress[];
|
EndlessXP?: IEndlessXpProgress[];
|
||||||
|
DialogueHistory?: IDialogueHistoryDatabase;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAffiliation {
|
export interface IAffiliation {
|
||||||
@ -948,3 +949,46 @@ export interface IEndlessXpProgress {
|
|||||||
Category: TEndlessXpCategory;
|
Category: TEndlessXpCategory;
|
||||||
Choices: string[];
|
Choices: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IDialogueHistoryClient {
|
||||||
|
YearIteration: number;
|
||||||
|
Dialogues?: IDialogueClient[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IDialogueHistoryDatabase {
|
||||||
|
YearIteration: number;
|
||||||
|
Dialogues?: IDialogueDatabase[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IDialogueClient {
|
||||||
|
Rank: number;
|
||||||
|
Chemistry: number;
|
||||||
|
AvailableDate: IMongoDate;
|
||||||
|
AvailableGiftDate: IMongoDate;
|
||||||
|
RankUpExpiry: IMongoDate;
|
||||||
|
BountyChemExpiry: IMongoDate;
|
||||||
|
//QueuedDialogues: any[];
|
||||||
|
Gifts: IDialogueGift[];
|
||||||
|
Booleans: string[];
|
||||||
|
Completed: ICompletedDialogue[];
|
||||||
|
DialogueName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IDialogueDatabase
|
||||||
|
extends Omit<IDialogueClient, "AvailableDate" | "AvailableGiftDate" | "RankUpExpiry" | "BountyChemExpiry"> {
|
||||||
|
AvailableDate: Date;
|
||||||
|
AvailableGiftDate: Date;
|
||||||
|
RankUpExpiry: Date;
|
||||||
|
BountyChemExpiry: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IDialogueGift {
|
||||||
|
Item: string;
|
||||||
|
GiftedQuantity: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICompletedDialogue {
|
||||||
|
Id: string;
|
||||||
|
Booleans: string[];
|
||||||
|
Choices: number[];
|
||||||
|
}
|
||||||
|
⚠️ Potential issue
Add error handling for JSON parsing.
The JSON parsing operation could throw an exception if the request body is malformed.
Apply this diff to add error handling:
📝 Committable suggestion