feat: create alliance (#1423)
All checks were successful
Build / build (22) (push) Successful in 39s
Build / build (18) (push) Successful in 1m17s
Build Docker image / docker (push) Successful in 36s
Build / build (20) (push) Successful in 1m24s

Reviewed-on: #1423
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
This commit is contained in:
Sainan 2025-04-02 04:59:21 -07:00 committed by Sainan
parent 158310bda2
commit 24ed580a97
7 changed files with 195 additions and 16 deletions

View File

@ -0,0 +1,50 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { Alliance, AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
import { getAllianceClient } from "@/src/services/guildService";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { GuildPermission } from "@/src/types/guildTypes";
import { RequestHandler } from "express";
export const createAllianceController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "GuildId");
const guild = (await Guild.findById(inventory.GuildId!, "Name Tier AllianceId"))!;
if (guild.AllianceId) {
res.status(400).send("Guild is already in an alliance").end();
return;
}
const guildMember = (await GuildMember.findOne({ guildId: guild._id, accountId }, "rank"))!;
if (guildMember.rank > 1) {
res.status(400).send("Invalid permission").end();
return;
}
const data = getJSONfromString<ICreateAllianceRequest>(String(req.body));
const alliance = new Alliance({ Name: data.allianceName });
try {
await alliance.save();
} catch (e) {
res.status(400).send("Alliance name already in use").end();
return;
}
guild.AllianceId = alliance._id;
await Promise.all([
guild.save(),
AllianceMember.insertOne({
allianceId: alliance._id,
guildId: guild._id,
Pending: false,
Permissions:
GuildPermission.Ruler |
GuildPermission.Promoter |
GuildPermission.Recruiter |
GuildPermission.Treasurer |
GuildPermission.ChatModerator
})
]);
res.json(await getAllianceClient(alliance, guild));
};
interface ICreateAllianceRequest {
allianceName: string;
}

View File

@ -1,7 +1,25 @@
import { Alliance, Guild } from "@/src/models/guildModel";
import { getAllianceClient } from "@/src/services/guildService";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
const getAllianceController: RequestHandler = (_req, res) => { export const getAllianceController: RequestHandler = async (req, res) => {
res.sendStatus(200); const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "GuildId");
if (inventory.GuildId) {
const guild = (await Guild.findById(inventory.GuildId, "Name Tier AllianceId"))!;
if (guild.AllianceId) {
const alliance = (await Alliance.findById(guild.AllianceId))!;
res.json(await getAllianceClient(alliance, guild));
return;
}
}
res.end();
}; };
export { getAllianceController }; /*interface IGetAllianceRequest {
memberCount: number;
clanLeaderName: string;
clanLeaderId: string;
}*/

View File

@ -5,7 +5,7 @@ import { logger } from "@/src/utils/logger";
import { getInventory } from "@/src/services/inventoryService"; import { getInventory } from "@/src/services/inventoryService";
import { createUniqueClanName, getGuildClient } from "@/src/services/guildService"; import { createUniqueClanName, getGuildClient } from "@/src/services/guildService";
const getGuildController: RequestHandler = async (req, res) => { export const getGuildController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "GuildId"); const inventory = await getInventory(accountId, "GuildId");
if (inventory.GuildId) { if (inventory.GuildId) {
@ -28,7 +28,5 @@ const getGuildController: RequestHandler = async (req, res) => {
return; return;
} }
} }
res.sendStatus(200); res.end();
}; };
export { getGuildController };

View File

@ -11,7 +11,9 @@ import {
IGuildLogEntryRoster, IGuildLogEntryRoster,
IGuildLogEntryContributable, IGuildLogEntryContributable,
IDojoLeaderboardEntry, IDojoLeaderboardEntry,
IGuildAdDatabase IGuildAdDatabase,
IAllianceDatabase,
IAllianceMemberDatabase
} from "@/src/types/guildTypes"; } from "@/src/types/guildTypes";
import { Document, Model, model, Schema, Types } from "mongoose"; import { Document, Model, model, Schema, Types } from "mongoose";
import { fusionTreasuresSchema, typeCountSchema } from "./inventoryModels/inventoryModel"; import { fusionTreasuresSchema, typeCountSchema } from "./inventoryModels/inventoryModel";
@ -167,6 +169,7 @@ const guildSchema = new Schema<IGuildDatabase>(
TradeTax: { type: Number, default: 0 }, TradeTax: { type: Number, default: 0 },
Tier: { type: Number, default: 1 }, Tier: { type: Number, default: 1 },
Emblem: { type: Boolean }, Emblem: { type: Boolean },
AllianceId: { type: Types.ObjectId },
DojoComponents: { type: [dojoComponentSchema], default: [] }, DojoComponents: { type: [dojoComponentSchema], default: [] },
DojoCapacity: { type: Number, default: 100 }, DojoCapacity: { type: Number, default: 100 },
DojoEnergy: { type: Number, default: 5 }, DojoEnergy: { type: Number, default: 5 },
@ -246,3 +249,25 @@ guildAdSchema.index({ GuildId: 1 }, { unique: true });
guildAdSchema.index({ Expiry: 1 }, { expireAfterSeconds: 0 }); guildAdSchema.index({ Expiry: 1 }, { expireAfterSeconds: 0 });
export const GuildAd = model<IGuildAdDatabase>("GuildAd", guildAdSchema); export const GuildAd = model<IGuildAdDatabase>("GuildAd", guildAdSchema);
const allianceSchema = new Schema<IAllianceDatabase>({
Name: String,
MOTD: longMOTDSchema,
LongMOTD: longMOTDSchema,
Emblem: Boolean
});
allianceSchema.index({ Name: 1 }, { unique: true });
export const Alliance = model<IAllianceDatabase>("Alliance", allianceSchema);
const allianceMemberSchema = new Schema<IAllianceMemberDatabase>({
allianceId: Schema.Types.ObjectId,
guildId: Schema.Types.ObjectId,
Pending: Boolean,
Permissions: Number
});
guildMemberSchema.index({ allianceId: 1, guildId: 1 }, { unique: true });
export const AllianceMember = model<IAllianceMemberDatabase>("AllianceMember", allianceMemberSchema);

View File

@ -22,6 +22,7 @@ import { confirmGuildInvitationController } from "@/src/controllers/api/confirmG
import { contributeGuildClassController } from "@/src/controllers/api/contributeGuildClassController"; import { contributeGuildClassController } from "@/src/controllers/api/contributeGuildClassController";
import { contributeToDojoComponentController } from "@/src/controllers/api/contributeToDojoComponentController"; import { contributeToDojoComponentController } from "@/src/controllers/api/contributeToDojoComponentController";
import { contributeToVaultController } from "@/src/controllers/api/contributeToVaultController"; import { contributeToVaultController } from "@/src/controllers/api/contributeToVaultController";
import { createAllianceController } from "@/src/controllers/api/createAllianceController";
import { createGuildController } from "@/src/controllers/api/createGuildController"; import { createGuildController } from "@/src/controllers/api/createGuildController";
import { creditsController } from "@/src/controllers/api/creditsController"; import { creditsController } from "@/src/controllers/api/creditsController";
import { customizeGuildRanksController } from "@/src/controllers/api/customizeGuildRanksController"; import { customizeGuildRanksController } from "@/src/controllers/api/customizeGuildRanksController";
@ -193,6 +194,7 @@ apiRouter.post("/confirmGuildInvitation.php", confirmGuildInvitationController);
apiRouter.post("/contributeGuildClass.php", contributeGuildClassController); apiRouter.post("/contributeGuildClass.php", contributeGuildClassController);
apiRouter.post("/contributeToDojoComponent.php", contributeToDojoComponentController); apiRouter.post("/contributeToDojoComponent.php", contributeToDojoComponentController);
apiRouter.post("/contributeToVault.php", contributeToVaultController); apiRouter.post("/contributeToVault.php", contributeToVaultController);
apiRouter.post("/createAlliance.php", createAllianceController);
apiRouter.post("/createGuild.php", createGuildController); apiRouter.post("/createGuild.php", createGuildController);
apiRouter.post("/customizeGuildRanks.php", customizeGuildRanksController); apiRouter.post("/customizeGuildRanks.php", customizeGuildRanksController);
apiRouter.post("/customObstacleCourseLeaderboard.php", customObstacleCourseLeaderboardController); apiRouter.post("/customObstacleCourseLeaderboard.php", customObstacleCourseLeaderboardController);

View File

@ -1,10 +1,13 @@
import { Request } from "express"; import { Request } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory } from "@/src/services/inventoryService"; import { getInventory } from "@/src/services/inventoryService";
import { Guild, GuildAd, GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel"; import { AllianceMember, Guild, GuildAd, GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel";
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import { import {
GuildPermission, GuildPermission,
IAllianceClient,
IAllianceDatabase,
IAllianceMemberClient,
IDojoClient, IDojoClient,
IDojoComponentClient, IDojoComponentClient,
IDojoComponentDatabase, IDojoComponentDatabase,
@ -99,7 +102,8 @@ export const getGuildClient = async (guild: TGuildDatabaseDocument, accountId: s
XP: guild.XP, XP: guild.XP,
IsContributor: !!guild.CeremonyContributors?.find(x => x.equals(accountId)), IsContributor: !!guild.CeremonyContributors?.find(x => x.equals(accountId)),
NumContributors: guild.CeremonyContributors?.length ?? 0, NumContributors: guild.CeremonyContributors?.length ?? 0,
CeremonyResetDate: guild.CeremonyResetDate ? toMongoDate(guild.CeremonyResetDate) : undefined CeremonyResetDate: guild.CeremonyResetDate ? toMongoDate(guild.CeremonyResetDate) : undefined,
AllianceId: guild.AllianceId ? toOid(guild.AllianceId) : undefined
}; };
}; };
@ -549,4 +553,34 @@ export const deleteGuild = async (guildId: Types.ObjectId): Promise<void> => {
}); });
await GuildAd.deleteOne({ GuildId: guildId }); await GuildAd.deleteOne({ GuildId: guildId });
await AllianceMember.deleteMany({ guildId });
// TODO: If this guild was the founding guild of an alliance (ruler permission), that would need to be forcefully deleted now as well.
};
export const getAllianceClient = async (
alliance: IAllianceDatabase,
guild: TGuildDatabaseDocument
): Promise<IAllianceClient> => {
const allianceMembers = await AllianceMember.find({ allianceId: alliance._id });
const clans: IAllianceMemberClient[] = [];
for (const allianceMember of allianceMembers) {
const memberGuild = allianceMember.guildId.equals(guild._id)
? guild
: (await Guild.findById(allianceMember.guildId))!;
clans.push({
_id: toOid(allianceMember.guildId),
Name: memberGuild.Name,
Tier: memberGuild.Tier,
Pending: allianceMember.Pending,
Permissions: allianceMember.Permissions,
MemberCount: await GuildMember.countDocuments({ guildId: memberGuild._id, status: 0 })
});
}
return {
_id: toOid(alliance._id),
Name: alliance.Name,
Clans: clans
};
}; };

View File

@ -18,6 +18,9 @@ export interface IGuildClient {
IsContributor: boolean; IsContributor: boolean;
NumContributors: number; NumContributors: number;
CeremonyResetDate?: IMongoDate; CeremonyResetDate?: IMongoDate;
CrossPlatformEnabled?: boolean;
AutoContributeFromVault?: boolean;
AllianceId?: IOid;
} }
export interface IGuildDatabase { export interface IGuildDatabase {
@ -29,6 +32,7 @@ export interface IGuildDatabase {
TradeTax: number; TradeTax: number;
Tier: number; Tier: number;
Emblem?: boolean; Emblem?: boolean;
AllianceId?: Types.ObjectId;
DojoComponents: IDojoComponentDatabase[]; DojoComponents: IDojoComponentDatabase[];
DojoCapacity: number; DojoCapacity: number;
@ -60,21 +64,21 @@ export interface IGuildDatabase {
export interface ILongMOTD { export interface ILongMOTD {
message: string; message: string;
authorName: string; authorName: string;
//authorGuildName: ""; authorGuildName?: "";
} }
// 32 seems to be reserved // 32 seems to be reserved
export enum GuildPermission { export enum GuildPermission {
Ruler = 1, // Change clan hierarchy Ruler = 1, // Clan: Change hierarchy. Alliance: Kick clans.
Advertiser = 8192, Advertiser = 8192,
Recruiter = 2, // Invite members Recruiter = 2, // Send invites (Clans & Alliances)
Regulator = 4, // Kick members Regulator = 4, // Kick members
Promoter = 8, // Promote and demote members Promoter = 8, // Clan: Promote and demote members. Alliance: Change clan permissions.
Architect = 16, // Create and destroy rooms Architect = 16, // Create and destroy rooms
Decorator = 1024, // Create and destroy decos Decorator = 1024, // Create and destroy decos
Treasurer = 64, // Contribute from vault and edit tax rate Treasurer = 64, // Clan: Contribute from vault and edit tax rate. Alliance: Divvy vault.
Tech = 128, // Queue research Tech = 128, // Queue research
ChatModerator = 512, ChatModerator = 512, // (Clans & Alliances)
Herald = 2048, // Change MOTD Herald = 2048, // Change MOTD
Fabricator = 4096 // Replicate research Fabricator = 4096 // Replicate research
} }
@ -268,3 +272,51 @@ export interface IGuildAdDatabase {
RecruitMsg: string; RecruitMsg: string;
Tier: number; Tier: number;
} }
export interface IAllianceClient {
_id: IOid;
Name: string;
MOTD?: ILongMOTD;
LongMOTD?: ILongMOTD;
Emblem?: boolean;
CrossPlatformEnabled?: boolean;
Clans: IAllianceMemberClient[];
OriginalPlatform?: number;
}
export interface IAllianceDatabase {
_id: Types.ObjectId;
Name: string;
MOTD?: ILongMOTD;
LongMOTD?: ILongMOTD;
Emblem?: boolean;
}
export interface IAllianceMemberClient {
_id: IOid;
Name: string;
Tier: number;
Pending: boolean;
Emblem?: boolean;
Permissions: number;
MemberCount: number;
ClanLeader?: string;
ClanLeaderId?: IOid;
OriginalPlatform?: number;
}
export interface IAllianceMemberDatabase {
allianceId: Types.ObjectId;
guildId: Types.ObjectId;
Pending: boolean;
Permissions: number;
}
// TODO: Alliance chat permissions
// TODO: POST /api/addToAlliance.php: {"clanName":"abc"}
// TODO: GET /api/divvyAllianceVault.php?accountId=6633b81e9dba0b714f28ff02&nonce=5702391171614479&ct=MSI&guildId=663e9be9f741eeb5782f9df0&allianceId=000000000000000000000069&credits=1
// TODO: GET /api/divvyAllianceVault.php?accountId=6633b81e9dba0b714f28ff02&nonce=5702391171614479&ct=MSI&guildId=663e9be9f741eeb5782f9df0&allianceId=000000000000000000000069&credits=0
// TODO: GET /api/removeFromAlliance.php?accountId=6633b81e9dba0b714f28ff02&nonce=5702391171614479&ct=MSI&guildId=663e9be9f741eeb5782f9df0
// TODO: GET /api/setAllianceGuildPermissions.php?accountId=6633b81e9dba0b714f28ff02&nonce=5702391171614479&ct=MSI&guildId=000000000000000000000042&perms=2
// TODO: Handle alliance in contributeToVault
// TODO: Handle alliance in setGuildMotd