返回提交历史
Modified
src/controllers/api/addToGuildController.ts
+84
-59
Modified
src/controllers/api/confirmGuildInvitationController.ts
+66
-3
Modified
src/controllers/api/createGuildController.ts
+3
-0
Modified
src/controllers/api/getGuildContributionsController.ts
+3
-2
Modified
src/controllers/api/removeFromGuildController.ts
+21
-0
Modified
src/models/guildModel.ts
+3
-0
Modified
src/routes/api.ts
+1
-0
Modified
src/services/guildService.ts
+3
-1
Modified
src/types/guildTypes.ts
+23
-3
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
代码差异
9 个文件
+207
-68
@@ -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
}
@@ -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(),
@@ -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)
@@ -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
};
@@ -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({
@@ -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
@@ -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);
@@ -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;
@@ -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 {