feat(guild): add guild page
This commit is contained in:
parent
28432b6523
commit
03eb4e2a51
6138 changed files with 528 additions and 108 deletions
|
|
@ -12,6 +12,7 @@ import { DbcManager } from "./data/DbcReader";
|
|||
import { CharacterCustomization } from "./data/CharacterCustomization";
|
||||
import { IndexController } from "./controllers/IndexController";
|
||||
import { CharacterController } from "./controllers/CharacterController";
|
||||
import { GuildController } from "./controllers/GuildController";
|
||||
|
||||
export class Armory {
|
||||
public characterCustomization: CharacterCustomization;
|
||||
|
|
@ -19,6 +20,7 @@ export class Armory {
|
|||
public config: Config;
|
||||
public worldDb: Pool;
|
||||
public logger: winston.Logger;
|
||||
public charsetCache: { [key: string]: string };
|
||||
|
||||
private charsDbs: { [key: string]: Pool };
|
||||
private errorNames: { [key: number]: string };
|
||||
|
|
@ -40,6 +42,7 @@ export class Armory {
|
|||
new winston.transports.File({ filename: "armory.combined.log", level: "http" }),
|
||||
],
|
||||
});
|
||||
this.charsetCache = {};
|
||||
|
||||
this.errorNames = {
|
||||
400: "Bad Request",
|
||||
|
|
@ -100,7 +103,17 @@ export class Armory {
|
|||
morgan.token("id", (req: express.Request) => {
|
||||
return req.id;
|
||||
});
|
||||
app.use(morgan(":method :url :status - ID :id - :remote-addr - :response-time ms", {
|
||||
morgan.token("ip", (req: express.Request) => {
|
||||
const forwardedFor = req.headers["x-forwarded-for"];
|
||||
if (forwardedFor) {
|
||||
if (typeof forwardedFor === "string") {
|
||||
return forwardedFor;
|
||||
}
|
||||
return forwardedFor.join(", ");
|
||||
}
|
||||
return req.socket.remoteAddress;
|
||||
});
|
||||
app.use(morgan(":method :url :status - ID :id - IP :ip - :response-time ms", {
|
||||
stream: {
|
||||
write: (msg) => this.logger.http(msg.trim()),
|
||||
},
|
||||
|
|
@ -126,6 +139,10 @@ export class Armory {
|
|||
app.get("/character/:realm/:name/achievements", this.wrapRoute(charsController.achievements.bind(charsController)));
|
||||
app.get("/character/:realm/:character/achievements/data", this.wrapRoute(charsController.achievementsData.bind(charsController)));
|
||||
|
||||
const guildsController = new GuildController(this);
|
||||
app.get("/guild/:realm/:name", this.wrapRoute(guildsController.guild.bind(guildsController)));
|
||||
app.get("/guild/:realm/:name/members", this.wrapRoute(guildsController.members.bind(guildsController)));
|
||||
|
||||
app.use((err, req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
// Error handler
|
||||
|
||||
|
|
@ -174,6 +191,25 @@ export class Armory {
|
|||
return this.config.realms.find(r => r.name.toLowerCase() === realm.toLowerCase());
|
||||
}
|
||||
|
||||
public async getDatabaseCharset(realm: string): Promise<string> {
|
||||
const db = this.getCharactersDb(realm);
|
||||
|
||||
if (!(realm in this.charsetCache)) {
|
||||
const [rows, fields] = await db.query({
|
||||
sql: `
|
||||
SELECT CCSA.character_set_name FROM information_schema.\`TABLES\` T,
|
||||
information_schema.\`COLLATION_CHARACTER_SET_APPLICABILITY\` CCSA
|
||||
WHERE CCSA.collation_name = T.table_collation
|
||||
AND T.table_schema = "${(await db.getConnection()).config.database}"
|
||||
AND T.table_name = "characters"
|
||||
`,
|
||||
timeout: this.config.dbQueryTimeout,
|
||||
});
|
||||
this.charsetCache[realm] = rows[0].character_set_name;
|
||||
}
|
||||
return this.charsetCache[realm];
|
||||
}
|
||||
|
||||
public gc(): void {
|
||||
if (this.config.loadDbcs) {
|
||||
return;
|
||||
|
|
|
|||
35
src/armory/Utils.ts
Normal file
35
src/armory/Utils.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
export enum EFaction {
|
||||
Horde = 0,
|
||||
Alliance = 1,
|
||||
}
|
||||
|
||||
export class Utils {
|
||||
public static raceNames = {
|
||||
1: "human",
|
||||
2: "orc",
|
||||
3: "dwarf",
|
||||
4: "nightelf",
|
||||
5: "scourge",
|
||||
6: "tauren",
|
||||
7: "gnome",
|
||||
8: "troll",
|
||||
10: "bloodelf",
|
||||
11: "draenei",
|
||||
};
|
||||
public static classNames = {
|
||||
1: "warrior",
|
||||
2: "paladin",
|
||||
3: "hunter",
|
||||
4: "rogue",
|
||||
5: "priest",
|
||||
6: "deathknight",
|
||||
7: "shaman",
|
||||
8: "mage",
|
||||
9: "warlock",
|
||||
11: "druid",
|
||||
};
|
||||
|
||||
public static getFactionFromRaceId(race: number): EFaction {
|
||||
return [1, 3, 4, 7, 11].includes(race) ? EFaction.Alliance : EFaction.Horde;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import * as express from "express";
|
||||
import { RowDataPacket } from "mysql2/promise";
|
||||
|
||||
import { Utils } from "../Utils";
|
||||
import { Armory } from "../Armory";
|
||||
import { IRealmConfig } from "../Config";
|
||||
import { IAchievement } from "../data/DbcReader";
|
||||
|
|
@ -732,10 +733,8 @@ export class CharacterController {
|
|||
}
|
||||
|
||||
private async getAchievements(realm: string, charData: ICharacterData): Promise<{ achievements: any[]; earned: { [key: number]: any }; }> {
|
||||
const faction = [1, 3, 4, 7, 11].includes(charData.race) ? 1 : 0;
|
||||
|
||||
const promises = await this.armory.dbc.achievement()
|
||||
.filter(ach => ach.faction === -1 || ach.faction === faction)
|
||||
.filter(ach => ach.faction === -1 || ach.faction === Utils.getFactionFromRaceId(charData.race))
|
||||
.map(async (ach) => {
|
||||
const icon = await this.armory.dbc.spellIcon().find(icon => icon.id === ach.iconId);
|
||||
return {
|
||||
|
|
|
|||
177
src/armory/controllers/GuildController.ts
Normal file
177
src/armory/controllers/GuildController.ts
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
import * as express from "express";
|
||||
import { encode } from "html-entities";
|
||||
import { RowDataPacket } from "mysql2/promise";
|
||||
|
||||
import { Utils } from "../Utils";
|
||||
import { Armory } from "../Armory";
|
||||
import { IRealmConfig } from "../Config";
|
||||
import { DataTablesSsp } from "../DataTablesSsp";
|
||||
|
||||
interface IGuildRank {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export class GuildController {
|
||||
private armory: Armory;
|
||||
|
||||
public constructor(armory: Armory) {
|
||||
this.armory = armory;
|
||||
}
|
||||
|
||||
public async guild(req: express.Request, res: express.Response, next: express.NextFunction): Promise<void> {
|
||||
const realmName = req.params.realm;
|
||||
const guildName = req.params.name;
|
||||
|
||||
const realm = this.armory.getRealm(realmName);
|
||||
if (realm === undefined) {
|
||||
// Could not find realm
|
||||
return next(404);
|
||||
}
|
||||
|
||||
const guildData = await this.getGuildData(realm, guildName);
|
||||
if (guildData === null) {
|
||||
// Could not find guild
|
||||
return next(404);
|
||||
}
|
||||
|
||||
res.render("guild.hbs", {
|
||||
title: `Armory - ${guildName}`,
|
||||
realm: realm.name,
|
||||
...guildData,
|
||||
});
|
||||
}
|
||||
|
||||
public async members(req: express.Request, res: express.Response, next: express.NextFunction): Promise<void> {
|
||||
const realmName = req.params.realm;
|
||||
const guildName = req.params.name;
|
||||
|
||||
const realm = this.armory.getRealm(realmName);
|
||||
if (realm === undefined) {
|
||||
// Could not find realm
|
||||
return next(404);
|
||||
}
|
||||
|
||||
const guildId = await this.getGuildId(realm, guildName);
|
||||
if (guildId === null) {
|
||||
// Could not find guild
|
||||
return next(404);
|
||||
}
|
||||
|
||||
const db = this.armory.getCharactersDb(realm.name);
|
||||
const charSet = await this.armory.getDatabaseCharset(realm.name);
|
||||
|
||||
let ssp = new DataTablesSsp(req.query, db, "guild_member", "guid", [
|
||||
{ name: "name", table: "characters", collation: `${charSet}_general_ci` },
|
||||
{ name: "rank" },
|
||||
{ name: "level", table: "characters" },
|
||||
{ name: "class", table: "characters", formatter: cls => Utils.classNames[cls] },
|
||||
{ name: "race", table: "characters", formatter: (race, row) => `${Utils.raceNames[race]}_${row[6] === 0 ? "male" : "female"}` },
|
||||
{ name: "online", table: "characters", formatter: online => online === 1 },
|
||||
]);
|
||||
ssp.joins = [
|
||||
{ table1: "guild_member", column1: "guid", table2: "characters", column2: "guid", kind: "LEFT" },
|
||||
];
|
||||
ssp.extraDataColumns = ["`characters`.`gender`"];
|
||||
|
||||
if (this.armory.config.hideGameMasters) {
|
||||
ssp.joins.push({ table1: "characters", column1: "account", table2: "account_access", column2: "id", database2: realm.authDatabase, kind: "LEFT" });
|
||||
ssp = ssp.where(`\`account_access\`.\`id\` IS NULL OR \`account_access\`.\`RealmID\` NOT IN (-1, ${realm.realmId}) OR \`account_access\`.\`gmlevel\` = 0`);
|
||||
}
|
||||
|
||||
const result = await ssp
|
||||
.where("`guildid` = ?", guildId)
|
||||
.where("`deleteInfos_Account` IS NULL")
|
||||
.run(this.armory.config.dbQueryTimeout);
|
||||
|
||||
const ranks = await this.getGuildRanks(realm, guildId);
|
||||
(result as any).ranks = {};
|
||||
for (const rank of ranks) {
|
||||
(result as any).ranks[rank.id] = encode(rank.name);
|
||||
}
|
||||
|
||||
res.json(result);
|
||||
}
|
||||
|
||||
private async getGuildData(realm: IRealmConfig, name: string): Promise<any> {
|
||||
const db = this.armory.getCharactersDb(realm.name);
|
||||
let [rows, fields] = await db.query({
|
||||
sql: `
|
||||
SELECT guildid, name, leaderguid, EmblemStyle AS emblemStyle, EmblemColor AS emblemColor, BorderStyle AS borderStyle, BorderColor AS borderColor, BackgroundColor AS background
|
||||
FROM guild WHERE name = ?
|
||||
`,
|
||||
values: [name],
|
||||
timeout: this.armory.config.dbQueryTimeout,
|
||||
});
|
||||
if ((rows as RowDataPacket[]).length === 0) {
|
||||
return null;
|
||||
}
|
||||
const guild = rows[0];
|
||||
|
||||
[rows, fields] = await db.query({
|
||||
sql: `
|
||||
SELECT name, race FROM characters
|
||||
WHERE guid = ?
|
||||
`,
|
||||
values: [guild.leaderguid],
|
||||
timeout: this.armory.config.dbQueryTimeout,
|
||||
});
|
||||
const leader = rows[0];
|
||||
|
||||
[rows, fields] = await db.query({
|
||||
sql: `
|
||||
SELECT COUNT(guid) AS \`count\` FROM guild_member
|
||||
WHERE guildid = ?
|
||||
`,
|
||||
values: [guild.guildid],
|
||||
timeout: this.armory.config.dbQueryTimeout,
|
||||
});
|
||||
const membersCount = rows[0].count;
|
||||
|
||||
return {
|
||||
id: guild.guildid,
|
||||
name: guild.name,
|
||||
leader: leader.name,
|
||||
faction: Utils.getFactionFromRaceId(leader.race),
|
||||
emblem: {
|
||||
icon: guild.emblemStyle.toString().padStart(2, "0"),
|
||||
iconColor: guild.emblemColor.toString().padStart(2, "0"),
|
||||
border: guild.borderStyle.toString().padStart(2, "0"),
|
||||
borderColor: guild.borderColor.toString().padStart(2, "0"),
|
||||
background: guild.background.toString().padStart(2, "0"),
|
||||
},
|
||||
membersCount,
|
||||
};
|
||||
}
|
||||
|
||||
private async getGuildId(realm: IRealmConfig, name: string): Promise<number> {
|
||||
const db = this.armory.getCharactersDb(realm.name);
|
||||
const [rows, fields] = await db.query({
|
||||
sql: `
|
||||
SELECT guildid
|
||||
FROM guild WHERE name = ?
|
||||
`,
|
||||
values: [name],
|
||||
timeout: this.armory.config.dbQueryTimeout,
|
||||
});
|
||||
if ((rows as RowDataPacket[]).length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return rows[0].guildid;
|
||||
}
|
||||
|
||||
private async getGuildRanks(realm: IRealmConfig, id: number): Promise<IGuildRank[]> {
|
||||
const db = this.armory.getCharactersDb(realm.name);
|
||||
const [rows, fields] = await db.query({
|
||||
sql: `
|
||||
SELECT rid AS id, rname AS name
|
||||
FROM guild_rank WHERE guildid = ?
|
||||
`,
|
||||
values: [id],
|
||||
timeout: this.armory.config.dbQueryTimeout,
|
||||
});
|
||||
|
||||
return rows as IGuildRank[];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,40 +1,14 @@
|
|||
import * as express from "express";
|
||||
|
||||
import { Utils } from "../Utils";
|
||||
import { Armory } from "../Armory";
|
||||
import { DataTablesSsp } from "../DataTablesSsp";
|
||||
|
||||
const raceFiles = {
|
||||
1: "human",
|
||||
2: "orc",
|
||||
3: "dwarf",
|
||||
4: "nightelf",
|
||||
5: "scourge",
|
||||
6: "tauren",
|
||||
7: "gnome",
|
||||
8: "troll",
|
||||
10: "bloodelf",
|
||||
11: "draenei",
|
||||
};
|
||||
const classFiles = {
|
||||
1: "warrior",
|
||||
2: "paladin",
|
||||
3: "hunter",
|
||||
4: "rogue",
|
||||
5: "priest",
|
||||
6: "deathknight",
|
||||
7: "shaman",
|
||||
8: "mage",
|
||||
9: "warlock",
|
||||
11: "druid",
|
||||
};
|
||||
|
||||
export class IndexController {
|
||||
private armory: Armory;
|
||||
private charsetCache: { [key: string]: string };
|
||||
|
||||
public constructor(armory: Armory) {
|
||||
this.armory = armory;
|
||||
this.charsetCache = {};
|
||||
}
|
||||
|
||||
public async index(req: express.Request, res: express.Response): Promise<void> {
|
||||
|
|
@ -54,28 +28,14 @@ export class IndexController {
|
|||
}
|
||||
|
||||
const db = this.armory.getCharactersDb(realm.name);
|
||||
|
||||
if (!(realm.name in this.charsetCache)) {
|
||||
let [rows, fields] = await db.query({
|
||||
sql: `
|
||||
SELECT CCSA.character_set_name FROM information_schema.\`TABLES\` T,
|
||||
information_schema.\`COLLATION_CHARACTER_SET_APPLICABILITY\` CCSA
|
||||
WHERE CCSA.collation_name = T.table_collation
|
||||
AND T.table_schema = "${(await db.getConnection()).config.database}"
|
||||
AND T.table_name = "characters"
|
||||
`,
|
||||
timeout: this.armory.config.dbQueryTimeout,
|
||||
});
|
||||
this.charsetCache[realm.name] = rows[0].character_set_name;
|
||||
}
|
||||
const charSet = this.charsetCache[realm.name];
|
||||
const charSet = await this.armory.getDatabaseCharset(realm.name);
|
||||
|
||||
let ssp = new DataTablesSsp(req.query, db, "characters", "guid", [
|
||||
{ name: "name", collation: `${charSet}_general_ci` },
|
||||
{ table: "guild", name: "name" },
|
||||
{ name: "level" },
|
||||
{ name: "class", formatter: cls => classFiles[cls] },
|
||||
{ name: "race", formatter: (race, row) => `${raceFiles[race]}_${row[6] === 0 ? "male" : "female"}` },
|
||||
{ name: "class", formatter: cls => Utils.classNames[cls] },
|
||||
{ name: "race", formatter: (race, row) => `${Utils.raceNames[race]}_${row[6] === 0 ? "male" : "female"}` },
|
||||
{ name: "online", formatter: online => online === 1 },
|
||||
]);
|
||||
ssp.joins = [
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue