feat: Inbox #876
@ -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": {
|
||||
|
@ -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) {
|
||||
|
||||
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) {
|
||||
![]() ⚠️ Potential issue Watch for potential concurrency issues with inventory operations. _: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);
|
||||
|
||||
![]() 🛠️ Refactor suggestion Add error handling for inventory operations. Multiple async operations are performed without proper error handling. Consider using try-catch blocks.
📝 Committable suggestion
_: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 };
|
||||
|
@ -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));
|
||||
|
14
src/controllers/custom/createMessageController.ts
Normal 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);
|
||||
};
|
@ -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
@ -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");
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -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;
|
||||
},
|
||||
|
@ -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);
|
||||
|
66
src/services/inboxService.ts
Normal 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);
|
||||
![]() 🛠️ Refactor suggestion Remove console.log statement. Production code should use the logger instead of console.log.
📝 Committable suggestion
_: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();
|
||||
};
|
||||
![]() 🛠️ Refactor suggestion Consider potential race conditions in updating the latest event message date. Multiple concurrent requests could lead to a race condition when updating
_: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;
|
||||
}
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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",
|
||||
![]() 🛠️ 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:
📝 Committable suggestion
_: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
|
||||
}
|
||||
]
|
||||
}
|
🛠️ Refactor suggestion
Handle non-existent messages and clarify property naming.
getMessage(messageId as string)
returns null, accessing properties likemessage.r
may throw a runtime error. Consider null-checks before updating the message.r
is likely “read” status. Consider naming it more descriptively for clarity.