From fae6615df43cdaed28f53e31707e354c57411bef Mon Sep 17 00:00:00 2001 From: Sainan Date: Mon, 10 Mar 2025 16:40:40 -0700 Subject: [PATCH] feat: clan members (#1143) Now you can add/remove members and accept/decline invites. Closes #1110 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/1143 Co-authored-by: Sainan Co-committed-by: Sainan --- src/controllers/api/addToGuildController.ts | 75 +++++++++++ .../api/confirmGuildInvitationController.ts | 32 +++++ src/controllers/api/createGuildController.ts | 25 ++-- .../api/declineGuildInviteController.ts | 14 ++ src/controllers/api/getGuildController.ts | 70 +--------- .../api/removeFromGuildController.ts | 45 +++++++ .../custom/deleteAccountController.ts | 3 + src/models/guildModel.ts | 16 ++- src/models/inboxModel.ts | 10 +- src/models/inventoryModels/inventoryModel.ts | 2 +- src/routes/api.ts | 8 ++ src/services/guildService.ts | 127 +++++++++++++++++- src/types/guildTypes.ts | 35 +++++ 13 files changed, 375 insertions(+), 87 deletions(-) create mode 100644 src/controllers/api/addToGuildController.ts create mode 100644 src/controllers/api/confirmGuildInvitationController.ts create mode 100644 src/controllers/api/declineGuildInviteController.ts create mode 100644 src/controllers/api/removeFromGuildController.ts diff --git a/src/controllers/api/addToGuildController.ts b/src/controllers/api/addToGuildController.ts new file mode 100644 index 00000000..3c95085c --- /dev/null +++ b/src/controllers/api/addToGuildController.ts @@ -0,0 +1,75 @@ +import { Guild, GuildMember } from "@/src/models/guildModel"; +import { Account } from "@/src/models/loginModel"; +import { fillInInventoryDataForGuildMember } from "@/src/services/guildService"; +import { createMessage } from "@/src/services/inboxService"; +import { getInventory } from "@/src/services/inventoryService"; +import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService"; +import { IOid } from "@/src/types/commonTypes"; +import { IGuildMemberClient } from "@/src/types/guildTypes"; +import { RequestHandler } from "express"; +import { ExportFlavour } from "warframe-public-export-plus"; + +export const addToGuildController: RequestHandler = async (req, res) => { + const payload = JSON.parse(String(req.body)) as IAddToGuildRequest; + + const account = await Account.findOne({ DisplayName: payload.UserName }); + if (!account) { + res.status(400).json("Username does not exist"); + return; + } + + const guild = (await Guild.findOne({ _id: payload.GuildId.$oid }, "Name"))!; + // TODO: Check sender is allowed to send invites for this guild. + + if ( + await GuildMember.exists({ + accountId: account._id, + guildId: payload.GuildId.$oid + }) + ) { + res.status(400).json("User already invited to clan"); + return; + } + + await GuildMember.insertOne({ + accountId: account._id, + guildId: payload.GuildId.$oid, + status: 2 // outgoing invite + }); + + const senderAccount = await getAccountForRequest(req); + const senderInventory = await getInventory(senderAccount._id.toString(), "ActiveAvatarImageType"); + await createMessage(account._id.toString(), [ + { + sndr: getSuffixedName(senderAccount), + msg: "/Lotus/Language/Menu/Mailbox_ClanInvite_Body", + arg: [ + { + Key: "clan", + Tag: guild.Name + "#000" + } + ], + sub: "/Lotus/Language/Menu/Mailbox_ClanInvite_Title", + icon: ExportFlavour[senderInventory.ActiveAvatarImageType].icon, + contextInfo: payload.GuildId.$oid, + highPriority: true, + acceptAction: "GUILD_INVITE", + declineAction: "GUILD_INVITE", + hasAccountAction: true + } + ]); + + const member: IGuildMemberClient = { + _id: { $oid: account._id.toString() }, + DisplayName: account.DisplayName, + Rank: 7, + Status: 2 + }; + await fillInInventoryDataForGuildMember(member); + res.json({ NewMember: member }); +}; + +interface IAddToGuildRequest { + UserName: string; + GuildId: IOid; +} diff --git a/src/controllers/api/confirmGuildInvitationController.ts b/src/controllers/api/confirmGuildInvitationController.ts new file mode 100644 index 00000000..070c9c28 --- /dev/null +++ b/src/controllers/api/confirmGuildInvitationController.ts @@ -0,0 +1,32 @@ +import { Guild, GuildMember } from "@/src/models/guildModel"; +import { getGuildClient, updateInventoryForConfirmedGuildJoin } from "@/src/services/guildService"; +import { getAccountIdForRequest } from "@/src/services/loginService"; +import { RequestHandler } from "express"; +import { Types } from "mongoose"; + +export const confirmGuildInvitationController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + const guildMember = await GuildMember.findOne({ + accountId: accountId, + guildId: req.query.clanId as string + }); + if (guildMember) { + guildMember.status = 0; + await guildMember.save(); + await updateInventoryForConfirmedGuildJoin(accountId, new Types.ObjectId(req.query.clanId as string)); + const guild = (await Guild.findOne({ _id: req.query.clanId as string }))!; + res.json({ + ...(await getGuildClient(guild, accountId)), + InventoryChanges: { + Recipes: [ + { + ItemType: "/Lotus/Types/Keys/DojoKeyBlueprint", + ItemCount: 1 + } + ] + } + }); + } else { + res.end(); + } +}; diff --git a/src/controllers/api/createGuildController.ts b/src/controllers/api/createGuildController.ts index 8bc34408..dc0930b2 100644 --- a/src/controllers/api/createGuildController.ts +++ b/src/controllers/api/createGuildController.ts @@ -1,8 +1,8 @@ import { RequestHandler } from "express"; import { getAccountIdForRequest } from "@/src/services/loginService"; import { getJSONfromString } from "@/src/helpers/stringHelpers"; -import { Inventory } from "@/src/models/inventoryModels/inventoryModel"; -import { Guild } from "@/src/models/guildModel"; +import { Guild, GuildMember } from "@/src/models/guildModel"; +import { updateInventoryForConfirmedGuildJoin } from "@/src/services/guildService"; export const createGuildController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); @@ -14,20 +14,15 @@ export const createGuildController: RequestHandler = async (req, res) => { }); await guild.save(); - // Update inventory - const inventory = await Inventory.findOne({ accountOwnerId: accountId }); - if (inventory) { - // Set GuildId - inventory.GuildId = guild._id; + // Create guild member on database + await GuildMember.insertOne({ + accountId: accountId, + guildId: guild._id, + status: 0, + rank: 0 + }); - // Give clan key (TODO: This should only be a blueprint) - inventory.LevelKeys.push({ - ItemType: "/Lotus/Types/Keys/DojoKey", - ItemCount: 1 - }); - - await inventory.save(); - } + await updateInventoryForConfirmedGuildJoin(accountId, guild._id); res.json(guild); }; diff --git a/src/controllers/api/declineGuildInviteController.ts b/src/controllers/api/declineGuildInviteController.ts new file mode 100644 index 00000000..c2bcd073 --- /dev/null +++ b/src/controllers/api/declineGuildInviteController.ts @@ -0,0 +1,14 @@ +import { GuildMember } from "@/src/models/guildModel"; +import { getAccountForRequest } from "@/src/services/loginService"; +import { RequestHandler } from "express"; + +export const declineGuildInviteController: RequestHandler = async (req, res) => { + const accountId = await getAccountForRequest(req); + + await GuildMember.deleteOne({ + accountId: accountId, + guildId: req.query.clanId as string + }); + + res.end(); +}; diff --git a/src/controllers/api/getGuildController.ts b/src/controllers/api/getGuildController.ts index e96d7be7..be697530 100644 --- a/src/controllers/api/getGuildController.ts +++ b/src/controllers/api/getGuildController.ts @@ -1,18 +1,13 @@ import { RequestHandler } from "express"; -import { Inventory } from "@/src/models/inventoryModels/inventoryModel"; import { Guild } from "@/src/models/guildModel"; import { getAccountIdForRequest } from "@/src/services/loginService"; -import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers"; -import { getGuildVault } from "@/src/services/guildService"; import { logger } from "@/src/utils/logger"; +import { getInventory } from "@/src/services/inventoryService"; +import { getGuildClient } from "@/src/services/guildService"; const getGuildController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); - const inventory = await Inventory.findOne({ accountOwnerId: accountId }); - if (!inventory) { - res.status(400).json({ error: "inventory was undefined" }); - return; - } + const inventory = await getInventory(accountId); if (inventory.GuildId) { const guild = await Guild.findOne({ _id: inventory.GuildId }); if (guild) { @@ -23,64 +18,7 @@ const getGuildController: RequestHandler = async (req, res) => { guild.CeremonyResetDate = undefined; await guild.save(); } - res.json({ - _id: toOid(guild._id), - Name: guild.Name, - MOTD: guild.MOTD, - LongMOTD: guild.LongMOTD, - Members: [ - { - _id: { $oid: req.query.accountId }, - Rank: 0, - Status: 0 - } - ], - Ranks: [ - { - Name: "/Lotus/Language/Game/Rank_Creator", - Permissions: 16351 - }, - { - Name: "/Lotus/Language/Game/Rank_Warlord", - Permissions: 14303 - }, - { - Name: "/Lotus/Language/Game/Rank_General", - Permissions: 4318 - }, - { - Name: "/Lotus/Language/Game/Rank_Officer", - Permissions: 4314 - }, - { - Name: "/Lotus/Language/Game/Rank_Leader", - Permissions: 4106 - }, - { - Name: "/Lotus/Language/Game/Rank_Sage", - Permissions: 4304 - }, - { - Name: "/Lotus/Language/Game/Rank_Soldier", - Permissions: 4098 - }, - { - Name: "/Lotus/Language/Game/Rank_Initiate", - Permissions: 4096 - }, - { - Name: "/Lotus/Language/Game/Rank_Utility", - Permissions: 4096 - } - ], - Tier: 1, - Vault: getGuildVault(guild), - Class: guild.Class, - XP: guild.XP, - IsContributor: !!guild.CeremonyContributors?.find(x => x.equals(accountId)), - NumContributors: guild.CeremonyContributors?.length ?? 0, - CeremonyResetDate: guild.CeremonyResetDate ? toMongoDate(guild.CeremonyResetDate) : undefined - }); + res.json(await getGuildClient(guild, accountId)); return; } } diff --git a/src/controllers/api/removeFromGuildController.ts b/src/controllers/api/removeFromGuildController.ts new file mode 100644 index 00000000..c28700e4 --- /dev/null +++ b/src/controllers/api/removeFromGuildController.ts @@ -0,0 +1,45 @@ +import { GuildMember } from "@/src/models/guildModel"; +import { getGuildForRequest } from "@/src/services/guildService"; +import { getInventory } from "@/src/services/inventoryService"; +import { RequestHandler } from "express"; + +export const removeFromGuildController: RequestHandler = async (req, res) => { + const guild = await getGuildForRequest(req); + // TODO: Check permissions + const payload = JSON.parse(String(req.body)) as IRemoveFromGuildRequest; + + const guildMember = (await GuildMember.findOne({ accountId: payload.userId, guildId: guild._id }))!; + if (guildMember.status == 0) { + const inventory = await getInventory(payload.userId); + inventory.GuildId = undefined; + + // Remove clan key or blueprint from kicked member + const itemIndex = inventory.MiscItems.findIndex(x => x.ItemType == "/Lotus/Types/Keys/DojoKey"); + if (itemIndex != -1) { + inventory.MiscItems.splice(itemIndex, 1); + } else { + const recipeIndex = inventory.Recipes.findIndex(x => x.ItemType == "/Lotus/Types/Keys/DojoKeyBlueprint"); + if (recipeIndex != -1) { + inventory.Recipes.splice(itemIndex, 1); + } + } + + await inventory.save(); + + // TODO: Handle clan leader kicking themselves (guild should be deleted in this case, I think) + } else if (guildMember.status == 2) { + // TODO: Maybe the inbox message for the sent invite should be deleted? + } + await GuildMember.deleteOne({ _id: guildMember._id }); + + res.json({ + _id: payload.userId, + ItemToRemove: "/Lotus/Types/Keys/DojoKey", + RecipeToRemove: "/Lotus/Types/Keys/DojoKeyBlueprint" + }); +}; + +interface IRemoveFromGuildRequest { + userId: string; + kicker?: string; +} diff --git a/src/controllers/custom/deleteAccountController.ts b/src/controllers/custom/deleteAccountController.ts index fb8ca399..cb249d69 100644 --- a/src/controllers/custom/deleteAccountController.ts +++ b/src/controllers/custom/deleteAccountController.ts @@ -7,11 +7,14 @@ import { Loadout } from "@/src/models/inventoryModels/loadoutModel"; import { PersonalRooms } from "@/src/models/personalRoomsModel"; import { Ship } from "@/src/models/shipModel"; import { Stats } from "@/src/models/statsModel"; +import { GuildMember } from "@/src/models/guildModel"; export const deleteAccountController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); + // TODO: Handle the account being the creator of a guild await Promise.all([ Account.deleteOne({ _id: accountId }), + GuildMember.deleteOne({ accountId: accountId }), Inbox.deleteMany({ ownerId: accountId }), Inventory.deleteOne({ accountOwnerId: accountId }), Loadout.deleteOne({ loadoutOwnerId: accountId }), diff --git a/src/models/guildModel.ts b/src/models/guildModel.ts index 295d52c5..9815d257 100644 --- a/src/models/guildModel.ts +++ b/src/models/guildModel.ts @@ -4,7 +4,8 @@ import { ITechProjectDatabase, ITechProjectClient, IDojoDecoDatabase, - ILongMOTD + ILongMOTD, + IGuildMemberDatabase } from "@/src/types/guildTypes"; import { Document, Model, model, Schema, Types } from "mongoose"; import { fusionTreasuresSchema, typeCountSchema } from "./inventoryModels/inventoryModel"; @@ -70,7 +71,7 @@ const longMOTDSchema = new Schema( const guildSchema = new Schema( { - Name: { type: String, required: true }, + Name: { type: String, required: true, unique: true }, MOTD: { type: String, default: "" }, LongMOTD: { type: longMOTDSchema, default: undefined }, DojoComponents: { type: [dojoComponentSchema], default: [] }, @@ -113,3 +114,14 @@ export type TGuildDatabaseDocument = Document & keyof GuildDocumentProps > & GuildDocumentProps; + +const guildMemberSchema = new Schema({ + accountId: Types.ObjectId, + guildId: Types.ObjectId, + status: { type: Number, required: true }, + rank: { type: Number, default: 7 } +}); + +guildMemberSchema.index({ accountId: 1, guildId: 1 }, { unique: true }); + +export const GuildMember = model("GuildMember", guildMemberSchema); diff --git a/src/models/inboxModel.ts b/src/models/inboxModel.ts index 10b1930e..c451d18a 100644 --- a/src/models/inboxModel.ts +++ b/src/models/inboxModel.ts @@ -32,6 +32,10 @@ export interface IMessage { transmission?: string; arg?: Arg[]; r?: boolean; + contextInfo?: string; + acceptAction?: string; + declineAction?: string; + hasAccountAction?: boolean; } export interface Arg { @@ -100,7 +104,11 @@ const messageSchema = new Schema( } ], default: undefined - } + }, + contextInfo: String, + acceptAction: String, + declineAction: String, + hasAccountAction: Boolean }, { timestamps: { createdAt: "date", updatedAt: false }, id: false } ); diff --git a/src/models/inventoryModels/inventoryModel.ts b/src/models/inventoryModels/inventoryModel.ts index 280934b8..f8cf68f2 100644 --- a/src/models/inventoryModels/inventoryModel.ts +++ b/src/models/inventoryModels/inventoryModel.ts @@ -1249,7 +1249,7 @@ const inventorySchema = new Schema( Drones: [droneSchema], //Active profile ico - ActiveAvatarImageType: String, + ActiveAvatarImageType: { type: String, default: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageDefault" }, // open location store like EidolonPlainsDiscoverable or OrbVallisCaveDiscoverable DiscoveredMarkers: [Schema.Types.Mixed], diff --git a/src/routes/api.ts b/src/routes/api.ts index 0a5e23fe..75ddf345 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -4,6 +4,7 @@ import { abortDojoComponentController } from "@/src/controllers/api/abortDojoCom import { abortDojoComponentDestructionController } from "@/src/controllers/api/abortDojoComponentDestructionController"; import { activateRandomModController } from "@/src/controllers/api/activateRandomModController"; import { addFriendImageController } from "@/src/controllers/api/addFriendImageController"; +import { addToGuildController } from "@/src/controllers/api/addToGuildController"; import { arcaneCommonController } from "@/src/controllers/api/arcaneCommonController"; import { archonFusionController } from "@/src/controllers/api/archonFusionController"; import { artifactsController } from "@/src/controllers/api/artifactsController"; @@ -14,11 +15,13 @@ import { claimCompletedRecipeController } from "@/src/controllers/api/claimCompl import { claimLibraryDailyTaskRewardController } from "@/src/controllers/api/claimLibraryDailyTaskRewardController"; import { clearDialogueHistoryController } from "@/src/controllers/api/clearDialogueHistoryController"; import { completeRandomModChallengeController } from "@/src/controllers/api/completeRandomModChallengeController"; +import { confirmGuildInvitationController } from "@/src/controllers/api/confirmGuildInvitationController"; import { contributeGuildClassController } from "@/src/controllers/api/contributeGuildClassController"; import { contributeToDojoComponentController } from "@/src/controllers/api/contributeToDojoComponentController"; import { contributeToVaultController } from "@/src/controllers/api/contributeToVaultController"; import { createGuildController } from "@/src/controllers/api/createGuildController"; import { creditsController } from "@/src/controllers/api/creditsController"; +import { declineGuildInviteController } from "@/src/controllers/api/declineGuildInviteController"; import { deleteSessionController } from "@/src/controllers/api/deleteSessionController"; import { destroyDojoDecoController } from "@/src/controllers/api/destroyDojoDecoController"; import { dojoComponentRushController } from "@/src/controllers/api/dojoComponentRushController"; @@ -69,6 +72,7 @@ import { playerSkillsController } from "@/src/controllers/api/playerSkillsContro import { projectionManagerController } from "@/src/controllers/api/projectionManagerController"; import { purchaseController } from "@/src/controllers/api/purchaseController"; import { queueDojoComponentDestructionController } from "@/src/controllers/api/queueDojoComponentDestructionController"; +import { removeFromGuildController } from "@/src/controllers/api/removeFromGuildController"; import { rerollRandomModController } from "@/src/controllers/api/rerollRandomModController"; import { saveDialogueController } from "@/src/controllers/api/saveDialogueController"; import { saveLoadoutController } from "@/src/controllers/api/saveLoadout"; @@ -113,7 +117,9 @@ apiRouter.get("/abandonLibraryDailyTask.php", abandonLibraryDailyTaskController) apiRouter.get("/abortDojoComponentDestruction.php", abortDojoComponentDestructionController); apiRouter.get("/checkDailyMissionBonus.php", checkDailyMissionBonusController); apiRouter.get("/claimLibraryDailyTaskReward.php", claimLibraryDailyTaskRewardController); +apiRouter.get("/confirmGuildInvitation.php", confirmGuildInvitationController); apiRouter.get("/credits.php", creditsController); +apiRouter.get("/declineGuildInvite.php", declineGuildInviteController); apiRouter.get("/deleteSession.php", deleteSessionController); apiRouter.get("/dojo", dojoController); apiRouter.get("/drones.php", dronesController); @@ -150,6 +156,7 @@ apiRouter.get("/updateSession.php", updateSessionGetController); apiRouter.post("/abortDojoComponent.php", abortDojoComponentController); apiRouter.post("/activateRandomMod.php", activateRandomModController); apiRouter.post("/addFriendImage.php", addFriendImageController); +apiRouter.post("/addToGuild.php", addToGuildController); apiRouter.post("/arcaneCommon.php", arcaneCommonController); apiRouter.post("/archonFusion.php", archonFusionController); apiRouter.post("/artifacts.php", artifactsController); @@ -193,6 +200,7 @@ apiRouter.post("/placeDecoInComponent.php", placeDecoInComponentController); apiRouter.post("/playerSkills.php", playerSkillsController); apiRouter.post("/projectionManager.php", projectionManagerController); apiRouter.post("/purchase.php", purchaseController); +apiRouter.post("/removeFromGuild.php", removeFromGuildController); apiRouter.post("/rerollRandomMod.php", rerollRandomModController); apiRouter.post("/saveDialogue.php", saveDialogueController); apiRouter.post("/saveLoadout.php", saveLoadoutController); diff --git a/src/services/guildService.ts b/src/services/guildService.ts index 9c89f863..a27c25b2 100644 --- a/src/services/guildService.ts +++ b/src/services/guildService.ts @@ -1,19 +1,23 @@ import { Request } from "express"; import { getAccountIdForRequest } from "@/src/services/loginService"; -import { getInventory } from "@/src/services/inventoryService"; -import { Guild, TGuildDatabaseDocument } from "@/src/models/guildModel"; +import { addRecipes, getInventory } from "@/src/services/inventoryService"; +import { Guild, GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel"; import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; import { IDojoClient, IDojoComponentClient, IDojoContributable, IDojoDecoClient, + IGuildClient, + IGuildMemberClient, IGuildVault } from "@/src/types/guildTypes"; import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers"; import { Types } from "mongoose"; import { ExportDojoRecipes, IDojoBuild } from "warframe-public-export-plus"; import { logger } from "../utils/logger"; +import { config } from "./configService"; +import { Account } from "../models/loginModel"; export const getGuildForRequest = async (req: Request): Promise => { const accountId = await getAccountIdForRequest(req); @@ -36,6 +40,99 @@ export const getGuildForRequestEx = async ( return guild; }; +export const getGuildClient = async (guild: TGuildDatabaseDocument, accountId: string): Promise => { + const guildMembers = await GuildMember.find({ guildId: guild._id }); + + const members: IGuildMemberClient[] = []; + let missingEntry = true; + for (const guildMember of guildMembers) { + const member: IGuildMemberClient = { + _id: toOid(guildMember.accountId), + Rank: guildMember.rank, + Status: guildMember.status + }; + if (guildMember.accountId.equals(accountId)) { + missingEntry = false; + } else { + member.DisplayName = (await Account.findOne( + { + _id: guildMember.accountId + }, + "DisplayName" + ))!.DisplayName; + await fillInInventoryDataForGuildMember(member); + } + members.push(member); + } + if (missingEntry) { + // Handle clans created prior to creation of the GuildMember model. + await GuildMember.insertOne({ + accountId: accountId, + guildId: guild._id, + status: 0, + rank: 0 + }); + members.push({ + _id: { $oid: accountId }, + Status: 0, + Rank: 0 + }); + } + + return { + _id: toOid(guild._id), + Name: guild.Name, + MOTD: guild.MOTD, + LongMOTD: guild.LongMOTD, + Members: members, + Ranks: [ + { + Name: "/Lotus/Language/Game/Rank_Creator", + Permissions: 16351 + }, + { + Name: "/Lotus/Language/Game/Rank_Warlord", + Permissions: 14303 + }, + { + Name: "/Lotus/Language/Game/Rank_General", + Permissions: 4318 + }, + { + Name: "/Lotus/Language/Game/Rank_Officer", + Permissions: 4314 + }, + { + Name: "/Lotus/Language/Game/Rank_Leader", + Permissions: 4106 + }, + { + Name: "/Lotus/Language/Game/Rank_Sage", + Permissions: 4304 + }, + { + Name: "/Lotus/Language/Game/Rank_Soldier", + Permissions: 4098 + }, + { + Name: "/Lotus/Language/Game/Rank_Initiate", + Permissions: 4096 + }, + { + Name: "/Lotus/Language/Game/Rank_Utility", + Permissions: 4096 + } + ], + Tier: 1, + Vault: getGuildVault(guild), + Class: guild.Class, + XP: guild.XP, + IsContributor: !!guild.CeremonyContributors?.find(x => x.equals(accountId)), + NumContributors: guild.CeremonyContributors?.length ?? 0, + CeremonyResetDate: guild.CeremonyResetDate ? toMongoDate(guild.CeremonyResetDate) : undefined + }; +}; + export const getGuildVault = (guild: TGuildDatabaseDocument): IGuildVault => { return { DojoRefundRegularCredits: guild.VaultRegularCredits, @@ -192,3 +289,29 @@ export const processDojoBuildMaterialsGathered = (guild: TGuildDatabaseDocument, } } }; + +export const fillInInventoryDataForGuildMember = async (member: IGuildMemberClient): Promise => { + const inventory = await getInventory(member._id.$oid, "PlayerLevel ActiveAvatarImageType"); + member.PlayerLevel = config.spoofMasteryRank == -1 ? inventory.PlayerLevel : config.spoofMasteryRank; + member.ActiveAvatarImageType = inventory.ActiveAvatarImageType; +}; + +export const updateInventoryForConfirmedGuildJoin = async ( + accountId: string, + guildId: Types.ObjectId +): Promise => { + const inventory = await getInventory(accountId); + + // Set GuildId + inventory.GuildId = guildId; + + // Give clan key blueprint + addRecipes(inventory, [ + { + ItemType: "/Lotus/Types/Keys/DojoKeyBlueprint", + ItemCount: 1 + } + ]); + + await inventory.save(); +}; diff --git a/src/types/guildTypes.ts b/src/types/guildTypes.ts index 8b9fd97b..7f241212 100644 --- a/src/types/guildTypes.ts +++ b/src/types/guildTypes.ts @@ -2,6 +2,25 @@ import { Types } from "mongoose"; import { IOid, IMongoDate } from "@/src/types/commonTypes"; import { IFusionTreasure, IMiscItem, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes"; +export interface IGuildClient { + _id: IOid; + Name: string; + MOTD: string; + LongMOTD?: ILongMOTD; + Members: IGuildMemberClient[]; + Ranks: { + Name: string; + Permissions: number; + }[]; + Tier: number; + Vault: IGuildVault; + Class: number; + XP: number; + IsContributor: boolean; + NumContributors: number; + CeremonyResetDate?: IMongoDate; +} + export interface IGuildDatabase { _id: Types.ObjectId; Name: string; @@ -35,6 +54,22 @@ export interface ILongMOTD { //authorGuildName: ""; } +export interface IGuildMemberDatabase { + accountId: Types.ObjectId; + guildId: Types.ObjectId; + status: number; + rank: number; +} + +export interface IGuildMemberClient { + _id: IOid; + Status: number; + Rank: number; + DisplayName?: string; + ActiveAvatarImageType?: string; + PlayerLevel?: number; +} + export interface IGuildVault { DojoRefundRegularCredits?: number; DojoRefundMiscItems?: IMiscItem[];