XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

SpaceNinjaServer

A simple server for a small space ninja game

公开
关注 0 Fork 1 Star 0
返回提交历史

XFEstudio/SpaceNinjaServer

feat: clan applications (#1410)

Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/1410 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>

1a4ad8b7
Sainan <63328889+Sainan@users.noreply.github.com>
提交于

代码差异

9 个文件 +207 -68
Modified src/controllers/api/addToGuildController.ts +84 -59
@@ -3,82 +3,107 @@ import { Account } from "@/src/models/loginModel";
3 3 import { fillInInventoryDataForGuildMember, hasGuildPermission } from "@/src/services/guildService";
4 4 import { createMessage } from "@/src/services/inboxService";
5 5 import { getInventory } from "@/src/services/inventoryService";
6 import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
6 import { getAccountForRequest, getAccountIdForRequest, getSuffixedName } from "@/src/services/loginService";
7 7 import { IOid } from "@/src/types/commonTypes";
8 8 import { GuildPermission, IGuildMemberClient } from "@/src/types/guildTypes";
9 import { logger } from "@/src/utils/logger";
9 10 import { RequestHandler } from "express";
10 11 import { ExportFlavour } from "warframe-public-export-plus";
11 12
12 13 export const addToGuildController: RequestHandler = async (req, res) => {
13 14 const payload = JSON.parse(String(req.body)) as IAddToGuildRequest;
14 15
15 const account = await Account.findOne({ DisplayName: payload.UserName });
16 if (!account) {
17 res.status(400).json("Username does not exist");
18 return;
19 }
16 if ("UserName" in payload) {
17 // Clan recruiter sending an invite
20 18
21 const inventory = await getInventory(account._id.toString(), "Settings");
22 // TODO: Also consider GIFT_MODE_FRIENDS once friends are implemented
23 if (inventory.Settings?.GuildInvRestriction == "GIFT_MODE_NONE") {
24 res.status(400).json("Invite restricted");
25 return;
26 }
19 const account = await Account.findOne({ DisplayName: payload.UserName });
20 if (!account) {
21 res.status(400).json("Username does not exist");
22 return;
23 }
27 24
28 const guild = (await Guild.findById(payload.GuildId.$oid, "Name Ranks"))!;
29 const senderAccount = await getAccountForRequest(req);
30 if (!(await hasGuildPermission(guild, senderAccount._id.toString(), GuildPermission.Recruiter))) {
31 res.status(400).json("Invalid permission");
32 }
25 const inventory = await getInventory(account._id.toString(), "Settings");
26 // TODO: Also consider GIFT_MODE_FRIENDS once friends are implemented
27 if (inventory.Settings?.GuildInvRestriction == "GIFT_MODE_NONE") {
28 res.status(400).json("Invite restricted");
29 return;
30 }
31
32 const guild = (await Guild.findById(payload.GuildId.$oid, "Name Ranks"))!;
33 const senderAccount = await getAccountForRequest(req);
34 if (!(await hasGuildPermission(guild, senderAccount._id.toString(), GuildPermission.Recruiter))) {
35 res.status(400).json("Invalid permission");
36 }
37
38 if (
39 await GuildMember.exists({
40 accountId: account._id,
41 guildId: payload.GuildId.$oid
42 })
43 ) {
44 res.status(400).json("User already invited to clan");
45 return;
46 }
33 47
34 if (
35 await GuildMember.exists({
48 await GuildMember.insertOne({
36 49 accountId: account._id,
37 guildId: payload.GuildId.$oid
38 })
39 ) {
40 res.status(400).json("User already invited to clan");
41 return;
42 }
50 guildId: payload.GuildId.$oid,
51 status: 2 // outgoing invite
52 });
43 53
44 await GuildMember.insertOne({
45 accountId: account._id,
46 guildId: payload.GuildId.$oid,
47 status: 2 // outgoing invite
48 });
54 const senderInventory = await getInventory(senderAccount._id.toString(), "ActiveAvatarImageType");
55 await createMessage(account._id, [
56 {
57 sndr: getSuffixedName(senderAccount),
58 msg: "/Lotus/Language/Menu/Mailbox_ClanInvite_Body",
59 arg: [
60 {
61 Key: "clan",
62 Tag: guild.Name
63 }
64 ],
65 sub: "/Lotus/Language/Menu/Mailbox_ClanInvite_Title",
66 icon: ExportFlavour[senderInventory.ActiveAvatarImageType].icon,
67 contextInfo: payload.GuildId.$oid,
68 highPriority: true,
69 acceptAction: "GUILD_INVITE",
70 declineAction: "GUILD_INVITE",
71 hasAccountAction: true
72 }
73 ]);
49 74
50 const senderInventory = await getInventory(senderAccount._id.toString(), "ActiveAvatarImageType");
51 await createMessage(account._id.toString(), [
52 {
53 sndr: getSuffixedName(senderAccount),
54 msg: "/Lotus/Language/Menu/Mailbox_ClanInvite_Body",
55 arg: [
56 {
57 Key: "clan",
58 Tag: guild.Name
59 }
60 ],
61 sub: "/Lotus/Language/Menu/Mailbox_ClanInvite_Title",
62 icon: ExportFlavour[senderInventory.ActiveAvatarImageType].icon,
63 contextInfo: payload.GuildId.$oid,
64 highPriority: true,
65 acceptAction: "GUILD_INVITE",
66 declineAction: "GUILD_INVITE",
67 hasAccountAction: true
75 const member: IGuildMemberClient = {
76 _id: { $oid: account._id.toString() },
77 DisplayName: account.DisplayName,
78 Rank: 7,
79 Status: 2
80 };
81 await fillInInventoryDataForGuildMember(member);
82 res.json({ NewMember: member });
83 } else if ("RequestMsg" in payload) {
84 // Player applying to join a clan
85 const accountId = await getAccountIdForRequest(req);
86 try {
87 await GuildMember.insertOne({
88 accountId,
89 guildId: payload.GuildId.$oid,
90 status: 1, // incoming invite
91 RequestMsg: payload.RequestMsg,
92 RequestExpiry: new Date(Date.now() + 14 * 86400 * 1000) // TOVERIFY: I can't find any good information about this with regards to live, but 2 weeks seem reasonable.
93 });
94 } catch (e) {
95 // Assuming this is "E11000 duplicate key error" due to the guildId-accountId unique index.
96 res.status(400).send("Already requested");
68 97 }
69 ]);
70
71 const member: IGuildMemberClient = {
72 _id: { $oid: account._id.toString() },
73 DisplayName: account.DisplayName,
74 Rank: 7,
75 Status: 2
76 };
77 await fillInInventoryDataForGuildMember(member);
78 res.json({ NewMember: member });
98 res.end();
99 } else {
100 logger.error(`data provided to ${req.path}: ${String(req.body)}`);
101 res.status(400).end();
102 }
79 103 };
80 104
81 105 interface IAddToGuildRequest {
82 UserName: string;
106 UserName?: string;
83 107 GuildId: IOid;
108 RequestMsg?: string;
84 109 }
Modified src/controllers/api/confirmGuildInvitationController.ts +66 -3
@@ -1,18 +1,76 @@
1 import { getJSONfromString } from "@/src/helpers/stringHelpers";
1 2 import { Guild, GuildMember } from "@/src/models/guildModel";
2 import { deleteGuild, getGuildClient, removeDojoKeyItems } from "@/src/services/guildService";
3 import { Account } from "@/src/models/loginModel";
4 import { deleteGuild, getGuildClient, hasGuildPermission, removeDojoKeyItems } from "@/src/services/guildService";
3 5 import { addRecipes, combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
4 import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
6 import { getAccountForRequest, getAccountIdForRequest, getSuffixedName } from "@/src/services/loginService";
7 import { GuildPermission } from "@/src/types/guildTypes";
5 8 import { IInventoryChanges } from "@/src/types/purchaseTypes";
6 9 import { RequestHandler } from "express";
7 10 import { Types } from "mongoose";
8 11
9 12 export const confirmGuildInvitationController: RequestHandler = async (req, res) => {
13 if (req.body) {
14 // POST request: Clan representative accepting invite(s).
15 const accountId = await getAccountIdForRequest(req);
16 const guild = (await Guild.findById(req.query.clanId as string, "Ranks RosterActivity"))!;
17 if (!(await hasGuildPermission(guild, accountId, GuildPermission.Recruiter))) {
18 res.status(400).json("Invalid permission");
19 return;
20 }
21 const payload = getJSONfromString<{ userId: string }>(String(req.body));
22 const filter: { accountId?: string; status: number } = { status: 1 };
23 if (payload.userId != "all") {
24 filter.accountId = payload.userId;
25 }
26 const guildMembers = await GuildMember.find(filter);
27 const newMembers: string[] = [];
28 for (const guildMember of guildMembers) {
29 guildMember.status = 0;
30 guildMember.RequestMsg = undefined;
31 guildMember.RequestExpiry = undefined;
32 await guildMember.save();
33
34 // Remove other pending applications for this account
35 await GuildMember.deleteMany({ accountId: guildMember.accountId, status: 1 });
36
37 // Update inventory of new member
38 const inventory = await getInventory(guildMember.accountId.toString(), "GuildId Recipes");
39 inventory.GuildId = new Types.ObjectId(req.query.clanId as string);
40 addRecipes(inventory, [
41 {
42 ItemType: "/Lotus/Types/Keys/DojoKeyBlueprint",
43 ItemCount: 1
44 }
45 ]);
46 await inventory.save();
47
48 // Add join to clan log
49 const account = (await Account.findOne({ _id: guildMember.accountId }))!;
50 guild.RosterActivity ??= [];
51 guild.RosterActivity.push({
52 dateTime: new Date(),
53 entryType: 6,
54 details: getSuffixedName(account)
55 });
56
57 newMembers.push(account._id.toString());
58 }
59 await guild.save();
60 res.json({
61 NewMembers: newMembers
62 });
63 return;
64 }
65
66 // GET request: A player accepting an invite they got in their inbox.
67
10 68 const account = await getAccountForRequest(req);
11 69 const invitedGuildMember = await GuildMember.findOne({
12 70 accountId: account._id,
13 71 guildId: req.query.clanId as string
14 72 });
15 if (invitedGuildMember) {
73 if (invitedGuildMember && invitedGuildMember.status == 2) {
16 74 let inventoryChanges: IInventoryChanges = {};
17 75
18 76 // If this account is already in a guild, we need to do cleanup first.
@@ -31,6 +89,10 @@ export const confirmGuildInvitationController: RequestHandler = async (req, res)
31 89 invitedGuildMember.status = 0;
32 90 await invitedGuildMember.save();
33 91
92 // Remove pending applications for this account
93 await GuildMember.deleteMany({ accountId: account._id, status: 1 });
94
95 // Update inventory of new member
34 96 const inventory = await getInventory(account._id.toString(), "GuildId LevelKeys Recipes");
35 97 inventory.GuildId = new Types.ObjectId(req.query.clanId as string);
36 98 const recipeChanges = [
@@ -45,6 +107,7 @@ export const confirmGuildInvitationController: RequestHandler = async (req, res)
45 107
46 108 const guild = (await Guild.findById(req.query.clanId as string))!;
47 109
110 // Add join to clan log
48 111 guild.RosterActivity ??= [];
49 112 guild.RosterActivity.push({
50 113 dateTime: new Date(),
Modified src/controllers/api/createGuildController.ts +3 -0
@@ -9,6 +9,9 @@ export const createGuildController: RequestHandler = async (req, res) => {
9 9 const accountId = await getAccountIdForRequest(req);
10 10 const payload = getJSONfromString<ICreateGuildRequest>(String(req.body));
11 11
12 // Remove pending applications for this account
13 await GuildMember.deleteMany({ accountId, status: 1 });
14
12 15 // Create guild on database
13 16 const guild = new Guild({
14 17 Name: await createUniqueClanName(payload.guildName)
Modified src/controllers/api/getGuildContributionsController.ts +3 -2
@@ -1,6 +1,7 @@
1 1 import { GuildMember } from "@/src/models/guildModel";
2 2 import { getInventory } from "@/src/services/inventoryService";
3 3 import { getAccountIdForRequest } from "@/src/services/loginService";
4 import { IGuildMemberClient } from "@/src/types/guildTypes";
4 5 import { RequestHandler } from "express";
5 6
6 7 export const getGuildContributionsController: RequestHandler = async (req, res) => {
@@ -8,11 +9,11 @@ export const getGuildContributionsController: RequestHandler = async (req, res)
8 9 const guildId = (await getInventory(accountId, "GuildId")).GuildId;
9 10 const guildMember = (await GuildMember.findOne({ guildId, accountId: req.query.buddyId }))!;
10 11 res.json({
11 _id: { $oid: req.query.buddyId },
12 _id: { $oid: req.query.buddyId as string },
12 13 RegularCreditsContributed: guildMember.RegularCreditsContributed,
13 14 PremiumCreditsContributed: guildMember.PremiumCreditsContributed,
14 15 MiscItemsContributed: guildMember.MiscItemsContributed,
15 16 ConsumablesContributed: [], // ???
16 17 ShipDecorationsContributed: guildMember.ShipDecorationsContributed
17 });
18 } satisfies Partial<IGuildMemberClient>);
18 19 };
Modified src/controllers/api/removeFromGuildController.ts +21 -0
@@ -2,6 +2,7 @@ import { GuildMember } from "@/src/models/guildModel";
2 2 import { Inbox } from "@/src/models/inboxModel";
3 3 import { Account } from "@/src/models/loginModel";
4 4 import { deleteGuild, getGuildForRequest, hasGuildPermission, removeDojoKeyItems } from "@/src/services/guildService";
5 import { createMessage } from "@/src/services/inboxService";
5 6 import { getInventory } from "@/src/services/inventoryService";
6 7 import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
7 8 import { GuildPermission } from "@/src/types/guildTypes";
@@ -26,6 +27,26 @@ export const removeFromGuildController: RequestHandler = async (req, res) => {
26 27 inventory.GuildId = undefined;
27 28 removeDojoKeyItems(inventory);
28 29 await inventory.save();
30 } else if (guildMember.status == 1) {
31 // TOVERIFY: Is this inbox message actually sent on live?
32 await createMessage(guildMember.accountId, [
33 {
34 sndr: "/Lotus/Language/Bosses/Ordis",
35 msg: "/Lotus/Language/Clan/RejectedFromClan",
36 sub: "/Lotus/Language/Clan/RejectedFromClanHeader",
37 arg: [
38 {
39 Key: "PLAYER_NAME",
40 Tag: (await Account.findOne({ _id: guildMember.accountId }, "DisplayName"))!.DisplayName
41 },
42 {
43 Key: "CLAN_NAME",
44 Tag: guild.Name
45 }
46 ]
47 // TOVERIFY: If this message is sent on live, is it highPriority?
48 }
49 ]);
29 50 } else if (guildMember.status == 2) {
30 51 // Delete the inbox message for the invite
31 52 await Inbox.deleteOne({
Modified src/models/guildModel.ts +3 -0
@@ -218,6 +218,8 @@ const guildMemberSchema = new Schema<IGuildMemberDatabase>({
218 218 guildId: Types.ObjectId,
219 219 status: { type: Number, required: true },
220 220 rank: { type: Number, default: 7 },
221 RequestMsg: String,
222 RequestExpiry: Date,
221 223 RegularCreditsContributed: Number,
222 224 PremiumCreditsContributed: Number,
223 225 MiscItemsContributed: { type: [typeCountSchema], default: undefined },
@@ -225,6 +227,7 @@ const guildMemberSchema = new Schema<IGuildMemberDatabase>({
225 227 });
226 228
227 229 guildMemberSchema.index({ accountId: 1, guildId: 1 }, { unique: true });
230 guildMemberSchema.index({ RequestExpiry: 1 }, { expireAfterSeconds: 0 });
228 231
229 232 export const GuildMember = model<IGuildMemberDatabase>("GuildMember", guildMemberSchema);
230 233
Modified src/routes/api.ts +1 -0
@@ -188,6 +188,7 @@ apiRouter.post("/claimCompletedRecipe.php", claimCompletedRecipeController);
188 188 apiRouter.post("/clearDialogueHistory.php", clearDialogueHistoryController);
189 189 apiRouter.post("/clearNewEpisodeReward.php", clearNewEpisodeRewardController);
190 190 apiRouter.post("/completeRandomModChallenge.php", completeRandomModChallengeController);
191 apiRouter.post("/confirmGuildInvitation.php", confirmGuildInvitationController);
191 192 apiRouter.post("/contributeGuildClass.php", contributeGuildClassController);
192 193 apiRouter.post("/contributeToDojoComponent.php", contributeToDojoComponentController);
193 194 apiRouter.post("/contributeToVault.php", contributeToVaultController);
Modified src/services/guildService.ts +3 -1
@@ -57,7 +57,9 @@ export const getGuildClient = async (guild: TGuildDatabaseDocument, accountId: s
57 57 const member: IGuildMemberClient = {
58 58 _id: toOid(guildMember.accountId),
59 59 Rank: guildMember.rank,
60 Status: guildMember.status
60 Status: guildMember.status,
61 Note: guildMember.RequestMsg,
62 RequestExpiry: guildMember.RequestExpiry ? toMongoDate(guildMember.RequestExpiry) : undefined
61 63 };
62 64 if (guildMember.accountId.equals(accountId)) {
63 65 missingEntry = false;
Modified src/types/guildTypes.ts +23 -3
@@ -89,19 +89,39 @@ export interface IGuildMemberDatabase {
89 89 guildId: Types.ObjectId;
90 90 status: number;
91 91 rank: number;
92 RequestMsg?: string;
93 RequestExpiry?: Date;
92 94 RegularCreditsContributed?: number;
93 95 PremiumCreditsContributed?: number;
94 96 MiscItemsContributed?: IMiscItem[];
95 97 ShipDecorationsContributed?: ITypeCount[];
96 98 }
97 99
98 export interface IGuildMemberClient {
100 interface IFriendInfo {
99 101 _id: IOid;
100 Status: number;
101 Rank: number;
102 102 DisplayName?: string;
103 PlatformNames?: string[];
104 PlatformAccountId?: string;
105 Status: number;
103 106 ActiveAvatarImageType?: string;
107 LastLogin?: IMongoDate;
104 108 PlayerLevel?: number;
109 Suffix?: number;
110 Note?: string;
111 Favorite?: boolean;
112 NewRequest?: boolean;
113 }
114
115 // GuildMemberInfo
116 export interface IGuildMemberClient extends IFriendInfo {
117 Rank: number;
118 Joined?: IMongoDate;
119 RequestExpiry?: IMongoDate;
120 RegularCreditsContributed?: number;
121 PremiumCreditsContributed?: number;
122 MiscItemsContributed?: IMiscItem[];
123 ConsumablesContributed?: ITypeCount[];
124 ShipDecorationsContributed?: ITypeCount[];
105 125 }
106 126
107 127 export interface IGuildVault {