feat: Inbox #876

Merged
OrdisPrime merged 11 commits from inbox into main 2025-01-31 05:15:36 -08:00
14 changed files with 368 additions and 71 deletions

View File

@ -26,7 +26,9 @@
"no-case-declarations": "warn",
"prettier/prettier": "error",
"@typescript-eslint/semi": "error",
"no-mixed-spaces-and-tabs": "error"
"no-mixed-spaces-and-tabs": "error",
"require-await": "off",
"@typescript-eslint/require-await": "error"
},
"parser": "@typescript-eslint/parser",
"parserOptions": {

View File

@ -1,8 +1,83 @@
import { RequestHandler } from "express";
import inbox from "@/static/fixed_responses/inbox.json";
import { Inbox } from "@/src/models/inboxModel";
import {
createNewEventMessages,
deleteAllMessagesRead,
deleteMessageRead,
getAllMessagesSorted,
getMessage
} from "@/src/services/inboxService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { addItems, getInventory } from "@/src/services/inventoryService";
import { logger } from "@/src/utils/logger";
const inboxController: RequestHandler = (_req, res) => {
res.json(inbox);
export const inboxController: RequestHandler = async (req, res) => {
const { deleteId, lastMessage: latestClientMessageId, messageId } = req.query;
const accountId = await getAccountIdForRequest(req);
if (deleteId) {
if (deleteId === "DeleteAllRead") {
await deleteAllMessagesRead(accountId);
res.status(200).end();
return;
}
await deleteMessageRead(deleteId as string);
res.status(200).end();
} else if (messageId) {
const message = await getMessage(messageId as string);
message.r = true;
const attachmentItems = message.att;
const attachmentCountedItems = message.countedAtt;
if (!attachmentItems && !attachmentCountedItems) {
await message.save();
res.status(200).end();
return;
}
const inventory = await getInventory(accountId);
const inventoryChanges = {};
if (attachmentItems) {
coderabbitai[bot] commented 2025-01-31 04:17:35 -08:00 (Migrated from github.com)
Review

🛠️ Refactor suggestion

Handle non-existent messages and clarify property naming.

  • If getMessage(messageId as string) returns null, accessing properties like message.r may throw a runtime error. Consider null-checks before updating the message.
  • The property r is likely “read” status. Consider naming it more descriptively for clarity.
_:hammer_and_wrench: Refactor suggestion_ **Handle non-existent messages and clarify property naming.** - If `getMessage(messageId as string)` returns null, accessing properties like `message.r` may throw a runtime error. Consider null-checks before updating the message. - The property `r` is likely “read” status. Consider naming it more descriptively for clarity. <!-- This is an auto-generated comment by CodeRabbit -->
await addItems(
inventory,
attachmentItems.map(attItem => ({ ItemType: attItem, ItemCount: 1 })),
inventoryChanges
);
}
if (attachmentCountedItems) {
await addItems(inventory, attachmentCountedItems, inventoryChanges);
}
await inventory.save();
await message.save();
res.json({ InventoryChanges: inventoryChanges });
} else if (latestClientMessageId) {
coderabbitai[bot] commented 2025-01-31 04:17:35 -08:00 (Migrated from github.com)
Review

⚠️ Potential issue

Watch for potential concurrency issues with inventory operations.
Multiple simultaneous requests adding items to the same inventory document could cause race conditions. Mongoose does not automatically handle concurrency; consider using transactions or explicit lock mechanisms if these additions must be atomic.

_:warning: Potential issue_ **Watch for potential concurrency issues with inventory operations.** Multiple simultaneous requests adding items to the same `inventory` document could cause race conditions. Mongoose does not automatically handle concurrency; consider using transactions or explicit lock mechanisms if these additions must be atomic. <!-- This is an auto-generated comment by CodeRabbit -->
await createNewEventMessages(req);
const messages = await Inbox.find({ ownerId: accountId }).sort({ date: 1 });
const latestClientMessage = messages.find(m => m._id.toString() === latestClientMessageId);
coderabbitai[bot] commented 2025-01-31 05:15:16 -08:00 (Migrated from github.com)
Review

🛠️ Refactor suggestion

Add error handling for inventory operations.

Multiple async operations are performed without proper error handling. Consider using try-catch blocks.

+        try {
           const inventory = await getInventory(accountId);
           const inventoryChanges = {};
           if (attachmentItems) {
               await addItems(
                   inventory,
                   attachmentItems.map(attItem => ({ ItemType: attItem, ItemCount: 1 })),
                   inventoryChanges
               );
           }
           if (attachmentCountedItems) {
               await addItems(inventory, attachmentCountedItems, inventoryChanges);
           }
           await inventory.save();
           await message.save();

           res.json({ InventoryChanges: inventoryChanges });
+        } catch (error) {
+            logger.error('Failed to process inventory changes:', error);
+            res.status(500).json({ error: 'Failed to process inventory changes' });
+        }
📝 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.

        try {
           const inventory = await getInventory(accountId);
           const inventoryChanges = {};
           if (attachmentItems) {
               await addItems(
                   inventory,
                   attachmentItems.map(attItem => ({ ItemType: attItem, ItemCount: 1 })),
                   inventoryChanges
               );
           }
           if (attachmentCountedItems) {
               await addItems(inventory, attachmentCountedItems, inventoryChanges);
           }
           await inventory.save();
           await message.save();

           res.json({ InventoryChanges: inventoryChanges });
        } catch (error) {
            logger.error('Failed to process inventory changes:', error);
            res.status(500).json({ error: 'Failed to process inventory changes' });
        }
    } else if (latestClientMessageId) {
_:hammer_and_wrench: Refactor suggestion_ **Add error handling for inventory operations.** Multiple async operations are performed without proper error handling. Consider using try-catch blocks. ```diff + try { const inventory = await getInventory(accountId); const inventoryChanges = {}; if (attachmentItems) { await addItems( inventory, attachmentItems.map(attItem => ({ ItemType: attItem, ItemCount: 1 })), inventoryChanges ); } if (attachmentCountedItems) { await addItems(inventory, attachmentCountedItems, inventoryChanges); } await inventory.save(); await message.save(); res.json({ InventoryChanges: inventoryChanges }); + } catch (error) { + logger.error('Failed to process inventory changes:', error); + res.status(500).json({ error: 'Failed to process inventory changes' }); + } ``` <!-- 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 try { const inventory = await getInventory(accountId); const inventoryChanges = {}; if (attachmentItems) { await addItems( inventory, attachmentItems.map(attItem => ({ ItemType: attItem, ItemCount: 1 })), inventoryChanges ); } if (attachmentCountedItems) { await addItems(inventory, attachmentCountedItems, inventoryChanges); } await inventory.save(); await message.save(); res.json({ InventoryChanges: inventoryChanges }); } catch (error) { logger.error('Failed to process inventory changes:', error); res.status(500).json({ error: 'Failed to process inventory changes' }); } } else if (latestClientMessageId) { ````` </details> <!-- suggestion_end --> <!-- This is an auto-generated comment by CodeRabbit -->
if (!latestClientMessage) {
logger.debug(`this should only happen after DeleteAllRead `);
res.json({ Inbox: messages });
return;
}
const newMessages = messages.filter(m => m.date > latestClientMessage.date);
if (newMessages.length === 0) {
res.send("no-new");
return;
}
res.json({ Inbox: newMessages });
} else {
//newly created event messages must be newer than account.LatestEventMessageDate
await createNewEventMessages(req);
const messages = await getAllMessagesSorted(accountId);
const inbox = messages.map(m => m.toJSON());
res.json({ Inbox: inbox });
}
};
export { inboxController };

View File

@ -12,7 +12,7 @@ import { logger } from "@/src/utils/logger";
export const loginController: RequestHandler = async (request, response) => {
const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object
const account = await Account.findOne({ email: loginRequest.email }); //{ _id: 0, __v: 0 }
const account = await Account.findOne({ email: loginRequest.email });
const nonce = Math.round(Math.random() * Number.MAX_SAFE_INTEGER);
const buildLabel: string =
@ -41,7 +41,8 @@ export const loginController: RequestHandler = async (request, response) => {
ForceLogoutVersion: 0,
ConsentNeeded: false,
TrackedSettings: [],
Nonce: nonce
Nonce: nonce,
LatestEventMessageDate: new Date(0)
});
logger.debug("created new account");
response.json(createLoginResponse(newAccount, buildLabel));

View File

@ -0,0 +1,14 @@
import { createMessage, IMessageCreationTemplate } from "@/src/services/inboxService";
import { RequestHandler } from "express";
export const createMessageController: RequestHandler = async (req, res) => {
const message = req.body as (IMessageCreationTemplate & { ownerId: string })[] | undefined;
if (!message) {
res.status(400).send("No message provided");
return;
}
const savedMessages = await createMessage(message[0].ownerId, message);
res.json(savedMessages);
};

View File

@ -48,7 +48,8 @@ const toDatabaseAccount = (createAccount: IAccountCreation): IDatabaseAccount =>
CrossPlatformAllowed: true,
ForceLogoutVersion: 0,
TrackedSettings: [],
Nonce: 0
Nonce: 0,
LatestEventMessageDate: new Date(0)
} satisfies IDatabaseAccount;
};

130
src/models/inboxModel.ts Normal file
View File

@ -0,0 +1,130 @@
import { model, Schema, Types } from "mongoose";
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
import { typeCountSchema } from "@/src/models/inventoryModels/inventoryModel";
import { IMongoDate, IOid } from "@/src/types/commonTypes";
import { ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
export interface IMessageClient extends Omit<IMessageDatabase, "_id" | "date" | "startDate" | "endDate" | "ownerId"> {
_id?: IOid;
date: IMongoDate;
startDate?: IMongoDate;
endDate?: IMongoDate;
messageId: IOid;
}
export interface IMessageDatabase {
ownerId: Types.ObjectId;
date: Date;
_id: Types.ObjectId;
sndr: string;
msg: string;
sub: string;
icon: string;
highPriority?: boolean;
lowPrioNewPlayers?: boolean;
startDate?: Date;
endDate?: Date;
r?: boolean;
att?: string[];
countedAtt?: ITypeCount[];
transmission?: string;
arg?: Arg[];
}
export interface Arg {
Key: string;
Tag: string;
}
//types are wrong
// export interface IMessageDatabase {
// _id: Types.ObjectId;
// messageId: string;
// sub: string;
// sndr: string;
// msg: string;
// startDate: Date;
// endDate: Date;
// date: Date;
// contextInfo: string;
// icon: string;
// att: string[];
// modPacks: string[];
// countedAtt: string[];
// attSpecial: string[];
// transmission: string;
// ordisReactionTransmission: string;
// arg: string[];
// r: string;
// acceptAction: string;
// declineAction: string;
// highPriority: boolean;
// lowPrioNewPlayers: boolean
// gifts: string[];
// teleportLoc: string;
// RegularCredits: string;
// PremiumCredits: string;
// PrimeTokens: string;
// Coupons: string[];
// syndicateAttachment: string[];
// tutorialTag: string;
// url: string;
// urlButtonText: string;
// cinematic: string;
// requiredLevel: string;
// }
const messageSchema = new Schema<IMessageDatabase>(
{
ownerId: Schema.Types.ObjectId,
sndr: String,
msg: String,
sub: String,
icon: String,
highPriority: Boolean,
lowPrioNewPlayers: Boolean,
startDate: Date,
endDate: Date,
r: Boolean,
att: { type: [String], default: undefined },
countedAtt: { type: [typeCountSchema], default: undefined },
transmission: String,
arg: {
type: [
{
Key: String,
Tag: String,
_id: false
}
],
default: undefined
}
},
{ timestamps: { createdAt: "date", updatedAt: false }, id: false }
);
messageSchema.virtual("messageId").get(function (this: IMessageDatabase) {
return toOid(this._id);
});
messageSchema.set("toJSON", {
virtuals: true,
transform(_document, returnedObject) {
delete returnedObject.ownerId;
const messageDatabase = returnedObject as IMessageDatabase;
const messageClient = returnedObject as IMessageClient;
delete returnedObject._id;
delete returnedObject.__v;
messageClient.date = toMongoDate(messageDatabase.date);
if (messageDatabase.startDate && messageDatabase.endDate) {
messageClient.startDate = toMongoDate(messageDatabase.startDate);
messageClient.endDate = toMongoDate(messageDatabase.endDate);
}
}
});
export const Inbox = model<IMessageDatabase>("Inbox", messageSchema, "inbox");

View File

@ -1,4 +1,4 @@
import { Document, Model, Schema, Types, model } from "mongoose";
import { Document, HydratedDocument, Model, Schema, Types, model } from "mongoose";
import {
IFlavourItem,
IRawUpgrade,
@ -7,7 +7,7 @@ import {
IBooster,
IInventoryClient,
ISlots,
IMailbox,
IMailboxDatabase,
IDuviriInfo,
IPendingRecipe as IPendingRecipeDatabase,
IPendingRecipeResponse,
@ -53,6 +53,7 @@ import {
IUpgradeDatabase,
ICrewShipMemberDatabase,
ICrewShipMemberClient,
IMailboxClient,
TEquipmentKey,
equipmentKeys,
IKubrowPetDetailsDatabase,
@ -298,22 +299,18 @@ FlavourItemSchema.set("toJSON", {
}
});
// "Mailbox": { "LastInboxId": { "$oid": "123456780000000000000000" } }
const MailboxSchema = new Schema<IMailbox>(
const MailboxSchema = new Schema<IMailboxDatabase>(
{
LastInboxId: {
type: Schema.Types.ObjectId,
set: (v: IMailbox["LastInboxId"]): string => v.$oid.toString()
}
LastInboxId: Schema.Types.ObjectId
},
{ id: false, _id: false }
);
MailboxSchema.set("toJSON", {
transform(_document, returnedObject) {
delete returnedObject.__v;
//TODO: there is a lot of any here
returnedObject.LastInboxId = toOid(returnedObject.LastInboxId as Types.ObjectId);
const mailboxDatabase = returnedObject as HydratedDocument<IMailboxDatabase, { __v?: number }>;
delete mailboxDatabase.__v;
(returnedObject as IMailboxClient).LastInboxId = toOid(mailboxDatabase.LastInboxId);
}
});

View File

@ -6,20 +6,6 @@ const opts = {
toObject: { virtuals: true }
} satisfies SchemaOptions;
// {
// toJSON: { virtuals: true }
// }
// {
// virtuals: {
// id: {
// get() {
// return "test";
// }
// },
// toJSON: { virtuals: true }
// }
// }
const databaseAccountSchema = new Schema<IDatabaseAccountJson>(
{
email: { type: String, required: true, unique: true },
@ -34,14 +20,14 @@ const databaseAccountSchema = new Schema<IDatabaseAccountJson>(
ConsentNeeded: { type: Boolean, required: true },
TrackedSettings: { type: [String], default: [] },
Nonce: { type: Number, default: 0 },
LastLoginDay: { type: Number }
LastLoginDay: { type: Number },
LatestEventMessageDate: { type: Date, required: true }
},
opts
);
databaseAccountSchema.set("toJSON", {
transform(_document, returnedObject) {
//returnedObject.id = returnedObject._id.toString();
delete returnedObject._id;
delete returnedObject.__v;
},

View File

@ -14,6 +14,7 @@ import { importController } from "@/src/controllers/custom/importController";
import { getConfigDataController } from "@/src/controllers/custom/getConfigDataController";
import { updateConfigDataController } from "@/src/controllers/custom/updateConfigDataController";
import { createMessageController } from "@/src/controllers/custom/createMessageController";
const customRouter = express.Router();
@ -25,6 +26,7 @@ customRouter.get("/deleteAccount", deleteAccountController);
customRouter.get("/renameAccount", renameAccountController);
customRouter.post("/createAccount", createAccountController);
customRouter.post("/createMessage", createMessageController);
customRouter.post("/addItems", addItemsController);
customRouter.post("/addXp", addXpController);
customRouter.post("/import", importController);

View File

@ -0,0 +1,66 @@
import { IMessageDatabase, Inbox } from "@/src/models/inboxModel";
import { getAccountForRequest } from "@/src/services/loginService";
import { HydratedDocument } from "mongoose";
import { Request } from "express";
import messages from "@/static/fixed_responses/messages.json";
import { logger } from "@/src/utils/logger";
export const getAllMessagesSorted = async (accountId: string): Promise<HydratedDocument<IMessageDatabase>[]> => {
const inbox = await Inbox.find({ ownerId: accountId }).sort({ date: -1 });
return inbox;
};
export const getMessage = async (messageId: string): Promise<HydratedDocument<IMessageDatabase>> => {
const message = await Inbox.findOne({ _id: messageId });
if (!message) {
throw new Error(`Message not found ${messageId}`);
}
return message;
};
export const deleteMessageRead = async (messageId: string): Promise<void> => {
await Inbox.findOneAndDelete({ _id: messageId, r: true });
};
export const deleteAllMessagesRead = async (accountId: string): Promise<void> => {
await Inbox.deleteMany({ ownerId: accountId, r: true });
};
export const createNewEventMessages = async (req: Request) => {
const account = await getAccountForRequest(req);
const latestEventMessageDate = account.LatestEventMessageDate;
//TODO: is baroo there? create these kind of messages too (periodical messages)
const newEventMessages = messages.Messages.filter(m => new Date(m.eventMessageDate) > latestEventMessageDate);
if (newEventMessages.length === 0) {
logger.debug(`No new event messages. Latest event message date: ${latestEventMessageDate.toISOString()}`);
return;
}
const savedEventMessages = await createMessage(account._id.toString(), newEventMessages);
logger.debug("created event messages", savedEventMessages);
const latestEventMessage = newEventMessages.reduce((prev, current) =>
prev.eventMessageDate > current.eventMessageDate ? prev : current
);
console.log("latestEventMessage", latestEventMessage);
coderabbitai[bot] commented 2025-01-31 05:15:16 -08:00 (Migrated from github.com)
Review

🛠️ Refactor suggestion

Remove console.log statement.

Production code should use the logger instead of console.log.

-    console.log("latestEventMessage", latestEventMessage);
+    logger.debug("Latest event message:", latestEventMessage);
📝 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.

    logger.debug("Latest event message:", latestEventMessage);
_:hammer_and_wrench: Refactor suggestion_ **Remove console.log statement.** Production code should use the logger instead of console.log. ```diff - console.log("latestEventMessage", latestEventMessage); + logger.debug("Latest event message:", latestEventMessage); ``` <!-- 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 logger.debug("Latest event message:", latestEventMessage); ````` </details> <!-- suggestion_end --> <!-- This is an auto-generated comment by CodeRabbit -->
account.LatestEventMessageDate = new Date(latestEventMessage.eventMessageDate);
await account.save();
};
coderabbitai[bot] commented 2025-01-31 04:36:22 -08:00 (Migrated from github.com)
Review

🛠️ Refactor suggestion

Consider potential race conditions in updating the latest event message date.

Multiple concurrent requests could lead to a race condition when updating LatestEventMessageDate. Consider using a transaction to ensure atomicity.

 export const createNewEventMessages = async (req: Request) => {
-    const account = await getAccountForRequest(req);
-    const latestEventMessageDate = account.LatestEventMessageDate;
+    const session = await mongoose.startSession();
+    try {
+        await session.withTransaction(async () => {
+            const account = await getAccountForRequest(req);
+            const latestEventMessageDate = account.LatestEventMessageDate;

-    //TODO: is baroo there? create these kind of messages too (periodical messages)
-    const newEventMessages = messages.Messages.filter(m => new Date(m.eventMessageDate) > latestEventMessageDate);
+            const newEventMessages = messages.Messages.filter(m => new Date(m.eventMessageDate) > latestEventMessageDate);

-    if (newEventMessages.length === 0) {
-        logger.debug(`No new event messages. Latest event message date: ${latestEventMessageDate.toISOString()}`);
-        return;
-    }
+            if (newEventMessages.length === 0) {
+                logger.debug(`No new event messages. Latest event message date: ${latestEventMessageDate.toISOString()}`);
+                return;
+            }

-    const savedEventMessages = await createMessage(account._id.toString(), newEventMessages);
-    logger.debug("created event messages", savedEventMessages);
+            const savedEventMessages = await createMessage(account._id.toString(), newEventMessages);
+            logger.debug("created event messages", savedEventMessages);

-    const latestEventMessage = newEventMessages.reduce((prev, current) =>
-        prev.eventMessageDate > current.eventMessageDate ? prev : current
-    );
+            const latestEventMessage = newEventMessages.reduce((prev, current) =>
+                prev.eventMessageDate > current.eventMessageDate ? prev : current
+            );

-    console.log("latestEventMessage", latestEventMessage);
-    account.LatestEventMessageDate = new Date(latestEventMessage.eventMessageDate);
-    await account.save();
+            account.LatestEventMessageDate = new Date(latestEventMessage.eventMessageDate);
+            await account.save({ session });
+        });
+    } finally {
+        await session.endSession();
+    }
 };

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

_:hammer_and_wrench: Refactor suggestion_ **Consider potential race conditions in updating the latest event message date.** Multiple concurrent requests could lead to a race condition when updating `LatestEventMessageDate`. Consider using a transaction to ensure atomicity. ```diff export const createNewEventMessages = async (req: Request) => { - const account = await getAccountForRequest(req); - const latestEventMessageDate = account.LatestEventMessageDate; + const session = await mongoose.startSession(); + try { + await session.withTransaction(async () => { + const account = await getAccountForRequest(req); + const latestEventMessageDate = account.LatestEventMessageDate; - //TODO: is baroo there? create these kind of messages too (periodical messages) - const newEventMessages = messages.Messages.filter(m => new Date(m.eventMessageDate) > latestEventMessageDate); + const newEventMessages = messages.Messages.filter(m => new Date(m.eventMessageDate) > latestEventMessageDate); - if (newEventMessages.length === 0) { - logger.debug(`No new event messages. Latest event message date: ${latestEventMessageDate.toISOString()}`); - return; - } + if (newEventMessages.length === 0) { + logger.debug(`No new event messages. Latest event message date: ${latestEventMessageDate.toISOString()}`); + return; + } - const savedEventMessages = await createMessage(account._id.toString(), newEventMessages); - logger.debug("created event messages", savedEventMessages); + const savedEventMessages = await createMessage(account._id.toString(), newEventMessages); + logger.debug("created event messages", savedEventMessages); - const latestEventMessage = newEventMessages.reduce((prev, current) => - prev.eventMessageDate > current.eventMessageDate ? prev : current - ); + const latestEventMessage = newEventMessages.reduce((prev, current) => + prev.eventMessageDate > current.eventMessageDate ? prev : current + ); - console.log("latestEventMessage", latestEventMessage); - account.LatestEventMessageDate = new Date(latestEventMessage.eventMessageDate); - await account.save(); + account.LatestEventMessageDate = new Date(latestEventMessage.eventMessageDate); + await account.save({ session }); + }); + } finally { + await session.endSession(); + } }; ``` > Committable suggestion skipped: line range outside the PR's diff. <!-- This is an auto-generated comment by CodeRabbit -->
export const createMessage = async (accountId: string, messages: IMessageCreationTemplate[]) => {
const ownerIdMessages = messages.map(m => ({
...m,
ownerId: accountId
}));
const savedMessages = await Inbox.insertMany(ownerIdMessages);
return savedMessages;
};
export interface IMessageCreationTemplate extends Omit<IMessageDatabase, "_id" | "date" | "ownerId"> {
ownerId?: string;
}

View File

@ -474,6 +474,18 @@ export const addItem = async (
throw new Error(errorMessage);
};
export const addItems = async (
inventory: TInventoryDatabaseDocument,
items: ITypeCount[],
inventoryChanges: IInventoryChanges = {}
): Promise<IInventoryChanges> => {
for (const item of items) {
const inventoryDelta = await addItem(inventory, item.ItemType, item.ItemCount);
combineInventoryChanges(inventoryChanges, inventoryDelta.InventoryChanges);
}
return inventoryChanges;
};
//TODO: maybe genericMethod for all the add methods, they share a lot of logic
export const addSentinel = (
inventory: TInventoryDatabaseDocument,

View File

@ -37,10 +37,10 @@ export interface IInventoryDatabase
> {
accountOwnerId: Types.ObjectId;
Created: Date;
TrainingDate: Date; // TrainingDate changed from IMongoDate to Date
TrainingDate: Date;
LoadOutPresets: Types.ObjectId; // LoadOutPresets changed from ILoadOutPresets to Types.ObjectId for population
Mailbox: Types.ObjectId; // Mailbox changed from IMailbox to Types.ObjectId
GuildId?: Types.ObjectId; // GuildId changed from ?IOid to ?Types.ObjectId
Mailbox?: IMailboxDatabase;
GuildId?: Types.ObjectId;
PendingRecipes: IPendingRecipe[];
QuestKeys: IQuestKeyDatabase[];
BlessingCooldown: Date;
@ -127,10 +127,14 @@ export interface IDuviriInfo {
NumCompletions: number;
}
export interface IMailbox {
export interface IMailboxClient {
LastInboxId: IOid;
}
export interface IMailboxDatabase {
LastInboxId: Types.ObjectId;
}
export type TSolarMapRegion =
| "Earth"
| "Ceres"
@ -202,7 +206,7 @@ export interface IInventoryClient extends IDailyAffiliations {
KahlLoadOuts: IOperatorConfigClient[];
DuviriInfo: IDuviriInfo;
Mailbox: IMailbox;
Mailbox?: IMailboxClient;
SubscribedToEmails: number;
Created: IMongoDate;
RewardSeed: number;
@ -238,7 +242,7 @@ export interface IInventoryClient extends IDailyAffiliations {
ActiveQuest: string;
FlavourItems: IFlavourItem[];
LoadOutPresets: ILoadOutPresets;
CurrentLoadOutIds: IOid[]; // we store it in the database using this representation as well :/
CurrentLoadOutIds: IOid[]; //TODO: we store it in the database using this representation as well :/
Missions: IMission[];
RandomUpgradesIdentified?: number;
LastRegionPlayed: TSolarMapRegion;

View File

@ -11,6 +11,29 @@ export interface IAccountAndLoginResponseCommons {
Nonce: number;
}
export interface IDatabaseAccount extends IAccountAndLoginResponseCommons {
email: string;
password: string;
LastLoginDay?: number;
LatestEventMessageDate: Date;
}
// Includes virtual ID
export interface IDatabaseAccountJson extends IDatabaseAccount {
id: string;
}
export interface ILoginRequest {
email: string;
password: string;
time: number;
s: string;
lang: string;
date: number;
ClientType: string;
PS: string;
}
export interface ILoginResponse extends IAccountAndLoginResponseCommons {
id: string;
Groups: IGroup[];
@ -23,29 +46,7 @@ export interface ILoginResponse extends IAccountAndLoginResponseCommons {
HUB: string;
}
// Includes virtual ID
export interface IDatabaseAccountJson extends IDatabaseAccount {
id: string;
}
export interface IGroup {
experiment: string;
experimentGroup: string;
}
export interface IDatabaseAccount extends IAccountAndLoginResponseCommons {
email: string;
password: string;
LastLoginDay?: number;
}
export interface ILoginRequest {
email: string;
password: string;
time: number;
s: string;
lang: string;
date: number;
ClientType: string;
PS: string;
}

View File

@ -1,5 +1,13 @@
{
"Inbox": [
"Messages": [
{
"sub": "Welcome to Space Ninja Server",
"sndr": "/Lotus/Language/Bosses/Ordis",
"msg": "Enjoy your Space Ninja Experience",
"icon": "/Lotus/Interface/Icons/Npcs/Darvo.png",
coderabbitai[bot] commented 2025-01-31 04:17:35 -08:00 (Migrated from github.com)
Review

🛠️ Refactor suggestion

Use localization keys for the welcome message.

The welcome message uses hardcoded English text while other messages use localization keys. This inconsistency could cause issues with internationalization.

Consider using localization keys:

-      "sub": "Welcome to Space Ninja Server",
-      "sndr": "/Lotus/Language/Bosses/Ordis",
-      "msg": "Enjoy your Space Ninja Experience",
+      "sub": "/Lotus/Language/Inbox/WelcomeMessageTitle",
+      "sndr": "/Lotus/Language/Bosses/Ordis",
+      "msg": "/Lotus/Language/Inbox/WelcomeMessageDesc",
📝 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.

      "sub": "/Lotus/Language/Inbox/WelcomeMessageTitle",
      "sndr": "/Lotus/Language/Bosses/Ordis",
      "msg": "/Lotus/Language/Inbox/WelcomeMessageDesc",
_:hammer_and_wrench: Refactor suggestion_ **Use localization keys for the welcome message.** The welcome message uses hardcoded English text while other messages use localization keys. This inconsistency could cause issues with internationalization. Consider using localization keys: ```diff - "sub": "Welcome to Space Ninja Server", - "sndr": "/Lotus/Language/Bosses/Ordis", - "msg": "Enjoy your Space Ninja Experience", + "sub": "/Lotus/Language/Inbox/WelcomeMessageTitle", + "sndr": "/Lotus/Language/Bosses/Ordis", + "msg": "/Lotus/Language/Inbox/WelcomeMessageDesc", ``` <!-- 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 "sub": "/Lotus/Language/Inbox/WelcomeMessageTitle", "sndr": "/Lotus/Language/Bosses/Ordis", "msg": "/Lotus/Language/Inbox/WelcomeMessageDesc", ````` </details> <!-- suggestion_end --> <!-- This is an auto-generated comment by CodeRabbit -->
"eventMessageDate": "2025-01-30T13:00:00.000Z",
"r": false
},
{
"sub": "/Lotus/Language/Inbox/DarvoWeaponCraftingMessageBTitle",
"sndr": "/Lotus/Language/Bosses/Darvo",
@ -24,9 +32,8 @@
}
],
"highPriority": true,
"messageId": "66d651800000000000000000",
"date": { "$date": { "$numberLong": "1725321600000" } },
"r": true
"eventMessageDate": "2023-10-01T17:00:00.000Z",
"r": false
},
{
"sub": "/Lotus/Language/G1Quests/Beginner_Growth_Inbox_Title",
@ -35,9 +42,8 @@
"icon": "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
"transmission": "/Lotus/Sounds/Dialog/VorsPrize/DLisetPostAssassinate110Lotus",
"highPriority": true,
"messageId": "66d651810000000000000000",
"date": { "$date": { "$numberLong": "1725321601000" } },
"r": true
"eventMessageDate": "2023-09-01T17:00:00.000Z",
"r": false
}
]
}