diff --git a/package-lock.json b/package-lock.json index 807446b..a97a62b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6130,9 +6130,9 @@ } }, "rpclibrary": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/rpclibrary/-/rpclibrary-1.7.0.tgz", - "integrity": "sha512-HIL5YzFF53fACOWvI403hJZnGYoXLJGrGc0n1HYZH6rnnfEaBcogemZrCbyoOvnG204LH882e8VdpB+WBTEY/g==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/rpclibrary/-/rpclibrary-1.7.1.tgz", + "integrity": "sha512-Ibo3qfURnQgZAq0eA2o+L9+PWaloJ4PHZs4ak42LL9fRgdWZXn33HAtowV0K2HVckbvrBvFqj/+PWTrWWBSOeg==", "requires": { "bsock": "^0.1.9", "http": "0.0.0", diff --git a/package.json b/package.json index 2ff53e0..c498762 100644 --- a/package.json +++ b/package.json @@ -5,15 +5,16 @@ "scripts": { "tsc": "tsc", "launch": "node lib/src/backend/Launcher.js", - "start": "npm run build; npm run launch", - "start-backend": "npm run build-backend; node lib/src/backend/Launcher.js", - "build": "npm run build-backend; npm run build-frontend", - "test": "npm run clean && npm run build-backend && mocha lib/test/backendTest.js", - "build-backend": "tsc;", - "build-frontend": "mkdir dist; mkdir dist/static; npm run build-dashboard;", - "build-dashboard": "cd src/frontend; npm i && npm run build; cp -r dist/* ../../dist/static", - "clean": "rm -rf lib plugins conf widget .rpt2_cache *.js *.ts src/frontend/dist data", - "update-frontblock": "npm remove frontblock frontblock-generic; npm install frontblock-generic@latest frontblock@latest", + "start": "npm run build && npm run launch", + "start-backend": "npm run build-backend && npm run launch", + "test": "npm run backend && npm run build-backend && mocha lib/test/backendTest.js", + "build": "npm run build-backend && npm run build-frontend", + "build-backend": "npm run clean-backend && tsc", + "build-frontend": "npm run clean-frontend && (mkdir static || rm -rf static/*) && npm run build-dashboard", + "build-dashboard": "cd src/frontend && npm i && npm run build && cp -r dist/* ../../static", + "clean": "rm -rf data && npm run clean-backend && npm run clean-frontend", + "clean-backend": "rm -rf lib plugins config widget .rpt2_cache *.js *.ts", + "clean-frontend": "rm -rf src/frontend/dist static", "webpack": "webpack --config src/backend/webpack.prod.js --progress --colors" }, "repository": { @@ -44,7 +45,7 @@ "path": "^0.12.7", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.0", - "rpclibrary": "^1.7.0", + "rpclibrary": "^1.7.1", "simple-git": "^1.124.0", "spawn-sync": "^2.0.0", "sqlite3": "^4.1.1", diff --git a/src/backend/Admin/Admin.ts b/src/backend/Admin/Admin.ts index 487db7c..f14d869 100644 --- a/src/backend/Admin/Admin.ts +++ b/src/backend/Admin/Admin.ts @@ -20,6 +20,7 @@ import { FrontworkComponent } from '../Types/FrontworkComponent'; import { IAdmin } from './Interface'; import { Injector } from '../Injector/Injector'; import { Shoutbox } from '../Components/Shoutbox/Shoutbox'; +import { PubSub } from '../Components/PubSub/PubSub'; const logger = getLogger("admin", 'debug') @@ -32,7 +33,8 @@ const logger = getLogger("admin", 'debug') RaidManager, CharacterManager, UserManager, - Shoutbox + Shoutbox, + PubSub ] }) export class FrontworkAdmin @@ -54,7 +56,7 @@ implements TableDefinitionExporter, IAdmin { dbConf: { client: 'sqlite3', connection: { - filename: Path.join(__dirname, "data/frontworkAdmin.sqlite") + filename: Path.resolve(__dirname, '../../../..', "data/frontworkAdmin.sqlite") }, useNullAsDefault: true, } @@ -96,7 +98,9 @@ implements TableDefinitionExporter, IAdmin { getTableDefinitions(): TableDefiniton[]{ return [ ...this.frontworkComponents - ].flatMap(exporter => exporter.getTableDefinitions()) + ] + .filter(exp => exp.getTableDefinitions != null) + .flatMap(exp => exp.getTableDefinitions()) } private startWebsocket(){ @@ -123,7 +127,7 @@ implements TableDefinitionExporter, IAdmin { let port:number = this.config.getConfig().httpPort this.express = express() - this.express.use('/', express.static('dist/static')) + this.express.use('/', express.static('static')) /** * get the compiled FrontendPlugins.js diff --git a/src/backend/Components/Item/ItemManager.ts b/src/backend/Components/Item/ItemManager.ts index 630bc63..b5f300a 100644 --- a/src/backend/Components/Item/ItemManager.ts +++ b/src/backend/Components/Item/ItemManager.ts @@ -1,14 +1,16 @@ -import { T1, T2, allItems, _Tiers, Tiers } from "../../Types/Items"; +import { allItems, _Tiers, Tiers } from "../../Types/Items"; import { Inject, Injectable } from "../../Injector/ServiceDecorator"; import { ItemManagerFeatureIfc, ItemManagerIfc } from "./RPCInterface"; import { FrontworkComponent } from "../../Types/FrontworkComponent"; import { TableDefinitionExporter } from "../../Types/Interfaces"; -import { TableDefiniton, Item, User, Character, SRToken, SRPriority, Spec, Signup } from "../../Types/Types"; +import { TableDefiniton, Item, User, Character, SRToken, SRPriority, Spec, Signup, Raid } from "../../Types/Types"; import { IAdmin } from "../../Admin/Interface"; import { IItemManager } from "./Interface"; import { getLogger } from "log4js"; import { IUserManager } from "../User/Interface"; import { ICharacterManager } from "../Character/Interface"; +import { IPubSub } from "../PubSub/Interface"; +import { IRaidManager } from "../Raid/Interface"; const fetch = require('node-fetch') const xml2js = require('xml2js'); @@ -28,6 +30,12 @@ implements FrontworkComponent, TableDefin @Inject(ICharacterManager) private character: ICharacterManager + @Inject(IPubSub) + private pubsub: IPubSub + + @Inject(IRaidManager) + private raidManager: IRaidManager + exportRPCs = () => [ this.getItems, this.getItem, @@ -51,6 +59,16 @@ implements FrontworkComponent, TableDefin ] }] + notifyRaid = async (raid:Raid | {id:number}) => { + const data = await this.raidManager.getRaidData(raid) + this.pubsub.publish(""+raid.id, data) + await this.notifyRaids() + } + + notifyRaids = async () => { + this.pubsub.publish('raids', undefined) + } + wipeCurrencyAndItems = async () => { await Promise.all([ this.userManager.wipeCurrency(), @@ -140,13 +158,12 @@ implements FrontworkComponent, TableDefin await this.userManager.decrementCurrency(record.user, item.tier, 1) const modifier = await this.calculatePriorities(itemname, character) - + const tx = await this.admin.knex.transaction() if(streaks.length > 0){ const myStreak = streaks.find(token => token.itemname === itemname) if(myStreak){ - //getLogger('ItemManager').debug('update signupid and increment level') await this.admin .knex(item.tier+'tokens') @@ -176,6 +193,8 @@ implements FrontworkComponent, TableDefin if(myStreak){ await tx.commit() + await this.notifyRaid({id: signup.raidid}) + return await this.getToken(character, item) } } @@ -205,6 +224,8 @@ implements FrontworkComponent, TableDefin }) } await tx.commit() + await this.notifyRaid({id: signup.raidid}) + return await this.getToken(character, item) } diff --git a/src/backend/Components/PubSub/Interface.ts b/src/backend/Components/PubSub/Interface.ts new file mode 100644 index 0000000..68967ec --- /dev/null +++ b/src/backend/Components/PubSub/Interface.ts @@ -0,0 +1,7 @@ +import { SubscriptionResponse } from "rpclibrary" + +export class IPubSub{ + publish: (topic: string, p: UpdateType) => void + subscribe: (topic:string, callback: (p:UpdateType)=>any) => Promise + unsubscribe: (uuid: string) => Promise +} diff --git a/src/backend/Components/PubSub/PubSub.ts b/src/backend/Components/PubSub/PubSub.ts new file mode 100644 index 0000000..af68bc9 --- /dev/null +++ b/src/backend/Components/PubSub/PubSub.ts @@ -0,0 +1,53 @@ +import { Injectable } from "../../Injector/ServiceDecorator"; +import { SubscriptionResponse, makeSubResponse, RPCExporter } from "rpclibrary"; +import { IPubSub } from "./Interface"; + +@Injectable(IPubSub) +export class PubSub +implements RPCExporter{ + name = "PubSub" + subs : { + [topic in string]: { + [uuid in string]: (p:UpdateType)=>any + } + }= {} + + exportRPCs = () => [ + this.unsubscribe, + { + name: 'subscribe', + hook: this.subscribe, + onClose: (subres, rpcName) => { + this.unsubscribe(subres.uuid) + } + } + ] + + publish = ( topic: string, msg: UpdateType ) => { + if(!this.subs[topic]) this.subs[topic] = {} + Object.entries(this.subs[topic]).forEach(([uuid, callback]:[string, Function]) => { + try{ + callback(msg) + }catch(e){ + delete this.subs[topic][uuid] + } + }) + } + + subscribe = async (topic:string, callback:(p:UpdateType)=>any) : Promise> => { + const resp = makeSubResponse({ + topic: topic + }) + if(!this.subs[topic]) this.subs[topic] = {} + this.subs[topic][resp.uuid] = callback + return resp + } + + private unsubscribe = async (uuid: string) => { + Object.entries(this.subs).forEach(([topic, submap]) => { + delete submap[uuid] + if(Object.keys(submap).length === 0) + delete this.subs[topic] + }) + } +} \ No newline at end of file diff --git a/src/backend/Components/PubSub/RPCInterface.ts b/src/backend/Components/PubSub/RPCInterface.ts new file mode 100644 index 0000000..0396427 --- /dev/null +++ b/src/backend/Components/PubSub/RPCInterface.ts @@ -0,0 +1,8 @@ +import { IPubSub } from "./Interface"; + +export type PubSubIfc = { + PubSub:{ + subscribe: IPubSub['subscribe'] + unsubscribe: IPubSub['unsubscribe'] + } +} diff --git a/src/backend/Components/Raid/RaidManager.ts b/src/backend/Components/Raid/RaidManager.ts index f98c216..50e9fa9 100644 --- a/src/backend/Components/Raid/RaidManager.ts +++ b/src/backend/Components/Raid/RaidManager.ts @@ -9,11 +9,12 @@ import { ICharacterManager } from "../Character/Interface"; import { _Tiers } from "../../Types/Items"; import { IItemManager } from "../Item/Interface"; import { ItemManager } from "../Item/ItemManager"; +import { IPubSub } from "../PubSub/Interface"; @Injectable(IRaidManager) export class RaidManager -implements FrontworkComponent, IRaidManager{ - name = "RaidManager" as "RaidManager"; + implements FrontworkComponent, IRaidManager { + name = "RaidManager" as "RaidManager"; @Inject(IAdmin) private admin: IAdmin @@ -27,6 +28,9 @@ implements FrontworkComponent, IRaidManag @Inject(ItemManager) private itemManager: IItemManager + @Inject(IPubSub) + private pubsub: IPubSub + exportRPCs = () => [ this.getRaids, this.getRaidData, @@ -46,7 +50,7 @@ implements FrontworkComponent, IRaidManag this.startRaid, this.adminUnsign ] - },{ + }, { name: 'signup' as 'signup', exportRPCs: () => [ this.getSignups, @@ -55,7 +59,7 @@ implements FrontworkComponent, IRaidManag ] }] } - + getTableDefinitions(): TableDefiniton[] { return [ { @@ -68,13 +72,13 @@ implements FrontworkComponent, IRaidManag table.integer('size').defaultTo(40) table.string('tier').defaultTo('null') } - },{ + }, { name: 'archive', tableBuilder: (table) => { table.integer('id').primary() table.json('raiddata').notNullable() } - },{ + }, { name: 'signups', tableBuilder: (table) => { table.increments('id').primary() @@ -86,55 +90,67 @@ implements FrontworkComponent, IRaidManag table.boolean('benched').defaultTo('false') table.boolean('late') } - } + } ] } - createRaid = async (raid:Raid) : Promise=> { - const ids:number[] = await this.admin - .knex('raids') - .insert(raid) + notifyRaid = async (raid:Raid | {id:number}) => { + const data = await this.getRaidData(raid) + this.pubsub.publish(""+raid.id, data) + await this.notifyRaids() + } - return await this.admin.knex('raids').where({id: ids[0]}).first() + notifyRaids = async () => { + this.pubsub.publish('raids', undefined) + } + + createRaid = async (raid: Raid): Promise => { + const ids: number[] = await this.admin + .knex('raids') + .insert(raid) + + await this.notifyRaid({ id: ids[0] }) + + return await this.admin.knex('raids').where({ id: ids[0] }).first() } addSignup = async (signup: Signup) => { - const ids:number[] = await this.admin - .knex('signups') - .insert(signup) - return await this.admin.knex('signups').where({id: ids[0]}).first() + const ids: number[] = await this.admin + .knex('signups') + .insert(signup) + return await this.admin.knex('signups').where({ id: ids[0] }).first() } removeSignup = async (signup: Signup) => await this.admin - .knex('signups') - .where({ - raid_id: signup.raidid, - character_id: signup.characterid - }) - .del() + .knex('signups') + .where({ + raid_id: signup.raidid, + character_id: signup.characterid + }) + .del() - getRaids = async () : Promise => { + getRaids = async (): Promise => { const subQuery = this.admin - .knex('signups') - .count('*') - .where({ - raidid: this.admin.knex.ref('raids.id'), - benched: false, - late: false - }) - .as('signupcount') - + .knex('signups') + .count('*') + .where({ + raidid: this.admin.knex.ref('raids.id'), + benched: false, + late: false + }) + .as('signupcount') + return await this.admin.knex('raids') - .select('*', subQuery) - .orderBy('start', 'asc') + .select('*', subQuery) + .orderBy('start', 'asc') } - startRaid = async (raid:Raid) : Promise => { + startRaid = async (raid: Raid): Promise => { const archived = await this.archiveRaid(raid) - + const giveCurrency = async (b: Character) => { const usr = await this.characterManager.getUserOfCharacter(b) await this.userManager.incrementCurrency(usr, raid.tier, 1) @@ -142,122 +158,125 @@ implements FrontworkComponent, IRaidManag await Promise.all([ ...archived.participants.bench.map(giveCurrency), - ...Object.values(archived.participants).map((group: any) => group.map(giveCurrency)) + ...Object.values(archived.participants).flat().flatMap((b:Signup & Character & Spec) => giveCurrency(b)) ]) + await this.notifyRaids() + return archived } - archiveRaid = async (raid:Raid) : Promise => { + archiveRaid = async (raid: Raid): Promise => { const raidData = await this.getRaidData(raid) //const tx = await this.admin.knex.transaction() await this.admin.knex('archive') - //.transacting(tx) - .insert({ - id:raidData.id, - raiddata: JSON.stringify(raidData) - }) + //.transacting(tx) + .insert({ + id: raidData.id, + raiddata: JSON.stringify(raidData) + }) await Promise.all( - Object.values(raidData.participants).flat().flatMap((signup) => this.admin - .knex(raid.tier+'tokens') + Object.values(raidData.participants).flat().flatMap((p: (Signup & Character & Spec)) => + this.admin + .knex(raid.tier + 'tokens') //.transacting(tx) .where({ - characterid: signup.characterid, + characterid: p.characterid, signupid: null }) .del() - )) + )) await this.admin.knex('raids') - //.transacting(tx) - .where('id', '=', raid.id) - .del() + //.transacting(tx) + .where('id', '=', raid.id) + .del() //await tx.commit() - + const row = await this.admin.knex('archive') - .select('*') - .where({ - id:raidData.id, - }) - .first() + .select('*') + .where({ + id: raidData.id, + }) + .first() return JSON.parse(row.raiddata) } - getArchiveRaid = async(id:number) : Promise => { + getArchiveRaid = async (id: number): Promise => { const data = await this.admin.knex('archive').select('raiddata').where({ id: id }).first() - + return JSON.parse(data.raiddata) } - getPastRaids = async(limit: number) : Promise => { + getPastRaids = async (limit: number): Promise => { const raids = await this.admin.knex('archive') - .select('*') - .orderBy('id', 'desc') - .limit(limit) + .select('*') + .orderBy('id', 'desc') + .limit(limit) return raids.map(raid => JSON.parse(raid.raiddata)) } - getRaidData = async (raid:Raid) : Promise => { + getRaidData = async (raid: Raid): Promise => { const raiddata = { - participants:{ - Druid: <(Signup&Character&Spec)[]>[], - Hunter: <(Signup&Character&Spec)[]>[], - Mage: <(Signup&Character&Spec)[]>[], - Paladin: <(Signup&Character&Spec)[]>[], - Priest: <(Signup&Character&Spec)[]>[], - Rogue: <(Signup&Character&Spec)[]>[], - Shaman: <(Signup&Character&Spec)[]>[], - Warlock: <(Signup&Character&Spec)[]>[], - Warrior: <(Signup&Character&Spec)[]>[], - late: <(Signup&Character&Spec)[]>[], - bench: <(Signup&Character&Spec)[]>[], + participants: { + Druid: <(Signup & Character & Spec)[]>[], + Hunter: <(Signup & Character & Spec)[]>[], + Mage: <(Signup & Character & Spec)[]>[], + Paladin: <(Signup & Character & Spec)[]>[], + Priest: <(Signup & Character & Spec)[]>[], + Rogue: <(Signup & Character & Spec)[]>[], + Shaman: <(Signup & Character & Spec)[]>[], + Warlock: <(Signup & Character & Spec)[]>[], + Warrior: <(Signup & Character & Spec)[]>[], + late: <(Signup & Character & Spec)[]>[], + bench: <(Signup & Character & Spec)[]>[], }, - tokens:{}, - healers:<(Signup&Character&Spec)[]>[], - tanks:<(Signup&Character&Spec)[]>[] + tokens: {}, + healers: <(Signup & Character & Spec)[]>[], + tanks: <(Signup & Character & Spec)[]>[] } //const tx = await this.admin.knex.transaction() const subQuery = this.admin - .knex('signups') - .count('*') - .where({ - raidid: this.admin.knex.ref('raids.id'), - benched: false, - late: false - }) - .as('signupcount') + .knex('signups') + .count('*') + .where({ + raidid: this.admin.knex.ref('raids.id'), + benched: false, + late: false + }) + .as('signupcount') const raidInDb: Raid = await this.admin.knex('raids') - .select('*', subQuery) - //.transacting(tx) - .where('id','=',raid.id) - .first() + .select('*', subQuery) + //.transacting(tx) + .where('id', '=', raid.id) + .first() const characterData: (Signup & Character & Spec)[] = await this.admin - .knex('signups as s') - //.transacting(tx) - .select('s.id as id', 'charactername', 'class', 'specid', 'specname', 'race', 'userid', 'benched', 'late', 'raidid', 'characterid', 'specid') - .join('raids as r', 's.raidid','=','r.id') - .join('characters as c', 's.characterid','=','c.id') - .join('users as u', 'c.userid','=','u.id') - .join('specs as sp', 'specid','=','sp.id') - .where('r.id','=',raid.id) - + .knex('signups as s') + //.transacting(tx) + .select('s.id as id', 'charactername', 'class', 'specid', 'specname', 'race', 'userid', 'benched', 'late', 'raidid', 'characterid', 'specid') + .join('raids as r', 's.raidid', '=', 'r.id') + .join('characters as c', 's.characterid', '=', 'c.id') + .join('users as u', 'c.userid', '=', 'u.id') + .join('specs as sp', 'specid', '=', 'sp.id') + .where('r.id', '=', raid.id) + characterData.forEach(data => { - if(data.benched){ + if (data.benched) { raiddata.participants.bench.push(data) return } - if(data.late){ + if (data.late) { raiddata.participants.late.push(data) return } @@ -265,42 +284,42 @@ implements FrontworkComponent, IRaidManag }) const tokenData: (Character & SRToken & Item)[] = await this.admin - .knex('signups as s') - //.transacting(tx) - .select('*', 's.id as id') - .join('raids as r', 's.raidid','=','r.id') - .join('characters as c', 's.characterid','=','c.id') - .join(raidInDb.tier+'tokens as t', 't.characterid','=','c.id') - .join('items as i', 'i.itemname','=','t.itemname') - .where({ - 'r.id': raid.id, - }) - .andWhere(function(){ - this.whereNotNull('t.signupid') - }) + .knex('signups as s') + //.transacting(tx) + .select('*', 's.id as id') + .join('raids as r', 's.raidid', '=', 'r.id') + .join('characters as c', 's.characterid', '=', 'c.id') + .join(raidInDb.tier + 'tokens as t', 't.characterid', '=', 'c.id') + .join('items as i', 'i.itemname', '=', 't.itemname') + .where({ + 'r.id': raid.id, + }) + .andWhere(function () { + this.whereNotNull('t.signupid') + }) //await tx.commit() tokenData.forEach(data => { - if(!raiddata.tokens[data.itemname]) + if (!raiddata.tokens[data.itemname]) raiddata.tokens[data.itemname] = [] raiddata.tokens[data.itemname].push(data) }) raiddata.tanks = Object.values(raiddata.participants).flatMap( - (tanks:any[]) => tanks.filter((p:any) => - !p.benched - && !p.late - && (p.specname==="Protection" - || p.specname==="Feral (Tank)")) + (tanks: any[]) => tanks.filter((p: any) => + !p.benched + && !p.late + && (p.specname === "Protection" + || p.specname === "Feral (Tank)")) ) raiddata.healers = Object.values(raiddata.participants).flatMap( - (healers:any[]) => healers.filter((p:any) => - !p.benched - && !p.late - && (p.specname==="Holy" - || p.specname==="Discipline" - || p.specname==="Restoration")) + (healers: any[]) => healers.filter((p: any) => + !p.benched + && !p.late + && (p.specname === "Holy" + || p.specname === "Discipline" + || p.specname === "Restoration")) ) return { @@ -308,128 +327,137 @@ implements FrontworkComponent, IRaidManag ...raiddata } } - - getSignups = async (raid:Raid) : Promise<(Signup & Character & Spec & User)[]> => await this.admin - .knex('signups as si') - .join('characters as c', 'c.id', '=', 'characterid') - .join('specs as s', 's.id', '=', 'specid') - .join('users as u', 'u.id', '=', 'userid') - .select('*','si.id as id') - .where('raidid', '=', raid.id!) - - sign = async (usertoken:string, character:Character, raid:Raid, late:boolean) => { + + getSignups = async (raid: Raid): Promise<(Signup & Character & Spec & User)[]> => await this.admin + .knex('signups as si') + .join('characters as c', 'c.id', '=', 'characterid') + .join('specs as s', 's.id', '=', 'specid') + .join('users as u', 'u.id', '=', 'userid') + .select('*', 'si.id as id') + .where('raidid', '=', raid.id!) + + sign = async (usertoken: string, character: Character, raid: Raid, late: boolean) => { const maybeUserRecord = this.userManager.getUserRecordByToken(usertoken) - if(!maybeUserRecord || maybeUserRecord.user.id != character.userid){ + if (!maybeUserRecord || maybeUserRecord.user.id != character.userid) { throw new Error("Bad Usertoken") } //const tx = await this.admin.knex.transaction() const exists = await this.admin - .knex('signups') - //.transacting(tx) - .select('*') - .where({ - raidid: raid.id!, - characterid: character.id!, - }) - .first() - - if(!exists){ - await this.admin - .knex('signups') - //.transacting(tx) - .insert({ - raidid: raid.id!, - characterid: character.id!, - late: late, - benched: false, - }) - }else{ - await this.admin .knex('signups') //.transacting(tx) + .select('*') .where({ - id: exists.id - }) - .update({ raidid: raid.id!, characterid: character.id!, - late: late, - benched: false, }) + .first() + + if (!exists) { + await this.admin + .knex('signups') + //.transacting(tx) + .insert({ + raidid: raid.id!, + characterid: character.id!, + late: late, + benched: false, + }) + } else { + await this.admin + .knex('signups') + //.transacting(tx) + .where({ + id: exists.id + }) + .update({ + raidid: raid.id!, + characterid: character.id!, + late: late, + benched: false, + }) } //await tx.commit() + await this.notifyRaid(raid) + return await this.admin - .knex('signups') - .select('*') - .where({ - raidid: raid.id!, - characterid: character.id!, - }) - .first() + .knex('signups') + .select('*') + .where({ + raidid: raid.id!, + characterid: character.id!, + }) + .first() } - unsign = async (usertoken:string, character:Character, raid:Raid) => { + unsign = async (usertoken: string, character: Character, raid: Raid) => { const maybeUserRecord = this.userManager.getUserRecordByToken(usertoken) - if(!maybeUserRecord || maybeUserRecord.user.id != character.userid){ + if (!maybeUserRecord || maybeUserRecord.user.id != character.userid) { throw new Error("Bad Usertoken") } - return await this.adminUnsign(character, raid) + return await this.adminUnsign(character, raid) } - adminUnsign = async (character:Character, raid:Raid) => { - + adminUnsign = async (character: Character, raid: Raid) => { + const user = await this.characterManager.getUserOfCharacter(character) const signup = await this.admin.knex('signups as si') - .where({ - "si.raidid": raid.id!, - "si.characterid": character.id!, - }).first() + .where({ + "si.raidid": raid.id!, + "si.characterid": character.id!, + }).first() + + const tokens = await this.itemManager.getTokens(character, [raid.tier], true) - const tokens = await this.admin.knex(raid.tier+'tokens as t') - .where('t.signupid', signup.id) - //check if token has to be deleted - Promise.all( - tokens.map(async token => { - await this.userManager.incrementCurrency(user, raid.tier, 1) - const prio = await this.itemManager.calculatePriorities(token.itemname, character) - if(token.level <= prio+1){ - await this.admin.knex(raid.tier+'tokens') - .where({ - characterid: character.id, - itemname: token.itemname - }).del() - }else{ - await this.admin.knex(raid.tier+'tokens') - .where({ - characterid: character.id, - itemname: token.itemname - }).update({ - signupid: null, - level: token.level-1 - }) - } - }) - ) + if (tokens) { + Promise.all( + tokens.map(async token => { + await this.userManager.incrementCurrency(user, raid.tier, 1) + const prio = await this.itemManager.calculatePriorities(token.itemname, character) + if (token.level <= prio + 1) { + await this.admin + .knex(raid.tier + 'tokens') + .where({ + characterid: character.id, + itemname: token.itemname + }).del() + } else { + await this.admin + .knex(raid.tier + 'tokens') + .where({ + characterid: character.id, + itemname: token.itemname + }).update({ + signupid: null, + level: token.level - 1 + }) + } + }) + ) + } await this.admin.knex('signups') - .where({ - raidid: raid.id!, - characterid: character.id!, - }) - .del() + .where({ + raidid: raid.id!, + characterid: character.id!, + }) + .del() + + await this.notifyRaid(raid) } - setBenched = async (signup: Signup) : Promise => { + setBenched = async (signup: Signup): Promise => { await this.admin.knex('signups') - .where({ - raidid: signup.raidid, - characterid: signup.characterid - }) - .update(signup) + .where({ + raidid: signup.raidid, + characterid: signup.characterid + }) + .update(signup) + + await this.notifyRaid({ id: signup.raidid }) } } \ No newline at end of file diff --git a/src/backend/Components/User/UserManager.ts b/src/backend/Components/User/UserManager.ts index 6521379..35eae90 100644 --- a/src/backend/Components/User/UserManager.ts +++ b/src/backend/Components/User/UserManager.ts @@ -455,6 +455,4 @@ implements FrontworkComponent, IUserManag .where('id', '=', user.id) .update(tier, value) } - - } \ No newline at end of file diff --git a/src/backend/Injector/ServiceDecorator.ts b/src/backend/Injector/ServiceDecorator.ts index 66690a0..4bcf767 100644 --- a/src/backend/Injector/ServiceDecorator.ts +++ b/src/backend/Injector/ServiceDecorator.ts @@ -1,6 +1,7 @@ import { Injector } from "./Injector"; import { Type, GenericClassDecorator } from "./Util"; import { FrontworkComponent } from "../Types/FrontworkComponent"; +import { RPCExporter } from "rpclibrary"; /** * @returns {GenericClassDecorator>} @@ -21,7 +22,7 @@ export const Injectable = (_interface?: Type) : GenericClassDecorator - injects : Type[] + injects : Type[] }) : GenericClassDecorator> => { return (target: Type) => { Injector.rootModules = config.injects diff --git a/src/backend/Types/Types.ts b/src/backend/Types/Types.ts index 75e02b1..e5cebba 100644 --- a/src/backend/Types/Types.ts +++ b/src/backend/Types/Types.ts @@ -7,6 +7,7 @@ import { ItemManagerFeatureIfc, ItemManagerIfc } from "../Components/Item/RPCInt import { GuildManagerFeatureIfc, GuildManagerIfc } from "../Components/Guild/RPCInterface"; import { ShoutboxIfc } from "../Components/Shoutbox/RPCInterface"; import { Tiers } from "./Items"; +import { PubSubIfc } from "../Components/PubSub/RPCInterface"; export type FrontcraftIfc = RaidManagerIfc & UserManagerIfc @@ -14,6 +15,7 @@ export type FrontcraftIfc = RaidManagerIfc & ItemManagerIfc & GuildManagerIfc & ShoutboxIfc + & PubSubIfc export type FrontcraftFeatureIfc = RaidManagerFeatureIfc & UserManagerFeatureIfc diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index d20b3bf..a0ec9e4 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -14269,9 +14269,9 @@ "integrity": "sha512-ZYzRkETgBrdEGzL5JSKimvjI2CX7ioyZCkX2BpcfyjqI+079W0wHAyj5W4rIZMcDSOHgLZtgz1IdDi/vU77KEQ==" }, "rpclibrary": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/rpclibrary/-/rpclibrary-1.6.2.tgz", - "integrity": "sha512-lQTU4XkB9CSHz7YgtAcpVfyR5XrmTRX4P4eQjK6DDUjqKSFvJb5ChXjHnt1BcaNWbJ2VqmhCNVbcoyunJ2u7Rg==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/rpclibrary/-/rpclibrary-1.7.1.tgz", + "integrity": "sha512-Ibo3qfURnQgZAq0eA2o+L9+PWaloJ4PHZs4ak42LL9fRgdWZXn33HAtowV0K2HVckbvrBvFqj/+PWTrWWBSOeg==", "requires": { "bsock": "^0.1.9", "http": "0.0.0", diff --git a/src/frontend/package.json b/src/frontend/package.json index 12ef5dc..edf4d9a 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -69,7 +69,7 @@ "normalize.css": "6.0.0", "pace-js": "1.0.2", "roboto-fontface": "0.8.0", - "rpclibrary": "^1.6.2", + "rpclibrary": "^1.7.1", "rxjs": "6.5.2", "rxjs-compat": "6.3.0", "socicon": "3.0.5", diff --git a/src/frontend/src/app/frontcraft/pages/raid/raid.component.html b/src/frontend/src/app/frontcraft/pages/raid/raid.component.html index adcc7ec..4c9410c 100644 --- a/src/frontend/src/app/frontcraft/pages/raid/raid.component.html +++ b/src/frontend/src/app/frontcraft/pages/raid/raid.component.html @@ -1,213 +1,172 @@ - - - - -

- - {{raid.title}} -

-

- {{raid.signupcount}} / {{raid.size}} signups -

-

- {{raid.description}} -

-
-
- +
+
+ + + + +

+ + {{raid.title}} +

- You are signed as: {{mySignup.charactername}} ({{mySignup.race}} {{mySignup.specname}} {{mySignup.class}})
- Status: {{mySignup.status}} + {{raid.signupcount}} / {{raid.size}} signups

- - - - -
-
- -
-
- - - -
- - Tanks ({{raid.tanks.length}}) - -
- - + - - - - {{ participant.charactername }} -
-
-
- - - Healers ({{raid.healers.length}}) - -
- - - - - - {{ participant.charactername }} - -
-
-
- - - - {{group.key}} ({{group.value.length}}) - - - - - -
-
- - - - - - - - - - -   - {{item.key}} -
-
-
- [ {{token.level}} ] - - {{token.charactername}} -
-
-
-
- - - - - - + + +
+ + Tanks ({{raid.tanks.length}}) + + + + + + + Healers ({{raid.healers.length}}) + + + + + + + + {{group.key}} ({{group.value.length}}) + + + + + +
+
+ + + + + + +
+
+ + + + + + + + + + +   + {{item.key}} +
+
+
+ [ {{token.level}} ] + + {{token.charactername}} +
+
+
+
+
+
+ + + +
+
+
+
+
\ No newline at end of file diff --git a/src/frontend/src/app/frontcraft/pages/raid/raid.component.ts b/src/frontend/src/app/frontcraft/pages/raid/raid.component.ts index 9fccfd0..d243a1a 100644 --- a/src/frontend/src/app/frontcraft/pages/raid/raid.component.ts +++ b/src/frontend/src/app/frontcraft/pages/raid/raid.component.ts @@ -1,5 +1,5 @@ -import { Component, OnInit } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { ActivatedRoute, Router, NavigationStart } from '@angular/router'; import { ApiService as ApiService } from '../../services/login-api'; import { RaidData, Raid, Signup, Character, Spec, Item, SRToken } from '../../../../../../backend/Types/Types'; import { NbWindowService, NbToastrService, NbDialogService } from '@nebular/theme'; @@ -12,7 +12,7 @@ import { allItems } from '../../../../../../backend/Types/Items'; selector: 'raid', templateUrl: './raid.component.html', }) -export class FrontcraftRaidComponent implements OnInit{ +export class FrontcraftRaidComponent implements OnInit, OnDestroy{ canSignup = false isSignedup = false @@ -41,6 +41,7 @@ export class FrontcraftRaidComponent implements OnInit{ tokens = {} displayedtokens = {} search = "" + uuid constructor( private api: ApiService, @@ -50,6 +51,16 @@ export class FrontcraftRaidComponent implements OnInit{ private toast: NbToastrService ){ window['r'] = this + router.events.subscribe(event => { + if (event instanceof NavigationStart) { + this.ngOnDestroy() + } + }) + } + + ngOnDestroy = () => { + if(this.uuid) + this.api.get('PubSub').unsubscribe(this.uuid) } async ngOnInit(){ @@ -60,9 +71,16 @@ export class FrontcraftRaidComponent implements OnInit{ if(signupFeature){ this.canSignup = true } - this.refresh() + await this.refresh() + + const res = await this.api.get('PubSub').subscribe(""+this.raid.id!, (data: RaidData) => { + this.display(data) + }) + this.uuid = res.uuid } + + itemSelect = async(item) => { this.dialogService.open(FrontcraftBuyTokenComponent, { context: { @@ -71,7 +89,7 @@ export class FrontcraftRaidComponent implements OnInit{ tier: this.raid.tier, characterName: this.mySignup.charactername } - }).onClose.subscribe(() => this.refresh()) + }) } signup = async () => { @@ -84,9 +102,7 @@ export class FrontcraftRaidComponent implements OnInit{ context: { raid: this.raid, } - }).onClose.subscribe(()=>{ - this.refresh() - }); + }) } async archiveRaid(raid:Raid){ @@ -110,7 +126,6 @@ export class FrontcraftRaidComponent implements OnInit{ id: this.mySignup.characterid, }, this.raid) this.toast.show('Success', 'Unsigned', { status: 'success' }) - this.refresh() } setLate = async (value:boolean) => { @@ -123,7 +138,6 @@ export class FrontcraftRaidComponent implements OnInit{ id: this.mySignup.characterid, }, this.raid, value) this.toast.show('Signup', 'Success', { status: 'success' }) - this.refresh() } adminUnsign = async() => { @@ -133,7 +147,6 @@ export class FrontcraftRaidComponent implements OnInit{ ...this.mySignup, id: this.mySignup.characterid, }, this.raid) - this.refresh() } refresh = async () => { @@ -143,6 +156,11 @@ export class FrontcraftRaidComponent implements OnInit{ const raiddata = await raidManager.getRaidData({ id: param }) + this.display(raiddata) + + } + + display = async (raiddata:RaidData) => { this.isTier = allItems[raiddata.tier] != null this.raid = raiddata diff --git a/src/frontend/src/app/frontcraft/pages/raids/raids.component.html b/src/frontend/src/app/frontcraft/pages/raids/raids.component.html index 292d362..80ddcda 100644 --- a/src/frontend/src/app/frontcraft/pages/raids/raids.component.html +++ b/src/frontend/src/app/frontcraft/pages/raids/raids.component.html @@ -16,24 +16,31 @@ class="raidlist" style="cursor: pointer;">
-
- -
+ -
- {{raid.title}} -
- -
- {{raid.signupcount}} / {{raid.size}}
-
- -
- {{raid.start | date : 'HH:mm'}} -
- -
- {{raid.start | date : 'EEEE MMMM d'}} +
+
+
+

+ {{raid.title}} +

+
+
+
+
+ {{raid.signupcount}} / {{raid.size}}
+
+ +
+ {{raid.start | date : 'HH:mm'}} +
+ +
+ {{raid.start | date : 'EEE MMM d'}} +
+
diff --git a/src/frontend/src/app/frontcraft/pages/raids/raids.component.scss b/src/frontend/src/app/frontcraft/pages/raids/raids.component.scss index a71122f..d276430 100644 --- a/src/frontend/src/app/frontcraft/pages/raids/raids.component.scss +++ b/src/frontend/src/app/frontcraft/pages/raids/raids.component.scss @@ -10,7 +10,14 @@ background-color: #293259; } -.vcenter { - height: 75px; - padding-top: 30px; +.raid { + height: 75px +} + +.row { + margin: -0.5rem; +} + +h4 { + font-weight: normal } \ No newline at end of file diff --git a/src/frontend/src/app/frontcraft/pages/raids/raids.component.ts b/src/frontend/src/app/frontcraft/pages/raids/raids.component.ts index f40b133..5f2e64c 100644 --- a/src/frontend/src/app/frontcraft/pages/raids/raids.component.ts +++ b/src/frontend/src/app/frontcraft/pages/raids/raids.component.ts @@ -1,30 +1,48 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, OnDestroy } from '@angular/core'; import { ApiService } from '../../services/login-api'; import { NbWindowService, NbDialogService } from '@nebular/theme'; import { FrontcraftCreateRaidsComponent } from './createraid.compontent'; +import { Router, NavigationStart } from '@angular/router'; @Component({ selector: 'raids', templateUrl: 'raids.component.html', styleUrls: ['raids.component.scss'], }) -export class FrontcraftRaidsComponent implements OnInit{ +export class FrontcraftRaidsComponent implements OnInit, OnDestroy{ manageRaid raids = [] oldraids = [] pageSize = 10; + uuid constructor( private api: ApiService, + private router: Router, private dialogService: NbDialogService ) { this.manageRaid = this.api.get('manageRaid') - + router.events.subscribe(event => { + if (event instanceof NavigationStart) { + this.ngOnDestroy() + } + }) } - ngOnInit(): void { + ngOnDestroy = () => { + console.log("boom"); + + if(this.uuid) + this.api.get('PubSub').unsubscribe(this.uuid) + } + + async ngOnInit() { + const res = await this.api.get('PubSub').subscribe("raids", () => { + this.refresh() + }) + this.uuid = res.uuid this.refresh() } @@ -46,9 +64,7 @@ export class FrontcraftRaidsComponent implements OnInit{ context: { templates: this.oldraids } - }).onClose.subscribe(()=>{ - this.refresh() - }); + }) } } diff --git a/src/frontend/src/app/frontcraft/pages/rules/rules.component.html b/src/frontend/src/app/frontcraft/pages/rules/rules.component.html index 472fffe..8c8273c 100644 --- a/src/frontend/src/app/frontcraft/pages/rules/rules.component.html +++ b/src/frontend/src/app/frontcraft/pages/rules/rules.component.html @@ -2,18 +2,69 @@ - Smart text here +

How it works

+
    +
  • + Everyone starts out with 1 soft reserve right per raid tier (you can see them in your user profile) +
  • +
  • + The tiers are: MC+Ony, BWL, ZG, AQ20, AQ40, Naxx +
  • +
  • + You gain 1 reserve right for a raid you attend. 2 if you were benched. Rights are granted on raid start. +
  • +
  • + When you sign up to a raid you can spend your reserve rights +
  • +
  • + Every week you can re-reserve the same item to build a streak on it +
  • + +
+
+

Specifics about streaks

+
    +
  • + Some classes and races get an initial boost to specific reserves. The list is on the second tab. +
  • +
  • + Switching the selected item destroys previous streaks. (Streaks must be continued without switchup) +
  • +
  • + If you have more than 1 softreserve you can reserve multiple different items. You may only continue one streak the following raid. +
  • +
  • + If you have more than 1 softreserve you can reserve the same item several times to increase its streak. +
  • +
  • + The softreserve counter, Reserves, and Streaks are tiers-specific and only count within their tier. +
  • +
  • + Canceling the signup refunds active reserves and continued streaks. Destroyed streaks cannot be recovered. +
  • +

- - - {{rule.description}} - - +{{rule.modifier}} {{rule.race}} {{rule.specname}} {{rule.class}}
-
+
+
+ +{{rule.modifier}} {{rule.race}} {{rule.specname}} {{rule.class}} +
+
({{rule.description}})
+
+
diff --git a/test/backendTest.ts b/test/backendTest.ts index 227a1ce..b1eda23 100644 --- a/test/backendTest.ts +++ b/test/backendTest.ts @@ -1,6 +1,6 @@ import { Injector } from "../src/backend/Injector/Injector"; import { FrontworkAdmin } from "../src/backend/Admin/Admin"; -import { T1 } from "../src/backend/Types/Items"; +import { T1, T2, Tiers } from "../src/backend/Types/Items"; import { RPCSocket } from "rpclibrary"; import { FrontcraftIfc, Auth, User, FrontcraftFeatureIfc, Raid, Character, Rank, Class, Race, SRPriority, Spec, Signup } from "../src/backend/Types/Types"; @@ -8,9 +8,9 @@ import { SpecT } from "../src/backend/Types/PlayerSpecs"; type protoAccount = { - name : string, + name: string, pwHash?: string - rank : Rank, + rank: Rank, race: Race, class: C, spec: SpecT[C] @@ -39,65 +39,72 @@ const adminsOnly = { } const defaultPermissions = [ -{ rpcname: 'signup', ...trialsAndUp -},{ rpcname: 'reset', ...adminsOnly -},{ rpcname: 'modifyPermissions', ...adminsOnly -},{ rpcname: 'manageGuild', ...adminsOnly -},{ rpcname: 'managePriorities', ...adminsOnly -},{ rpcname: 'softreserveCurrency', ...adminsOnly -},{ rpcname: 'manageRaid', ...adminsOnly -}] + { + rpcname: 'signup', ...trialsAndUp + }, { + rpcname: 'reset', ...adminsOnly + }, { + rpcname: 'modifyPermissions', ...adminsOnly + }, { + rpcname: 'manageGuild', ...adminsOnly + }, { + rpcname: 'managePriorities', ...adminsOnly + }, { + rpcname: 'softreserveCurrency', ...adminsOnly + }, { + rpcname: 'manageRaid', ...adminsOnly + }] -const testAccounts : protoAccount[] = [ +const testAccounts: protoAccount[] = [ { name: 'Rain', race: 'Human', class: 'Warrior', spec: 'Protection', rank: 'Guildmaster' - },{ + }, { name: 'Celinda', class: 'Warrior', race: 'Night Elf', spec: 'Protection', rank: 'Officer' - },{ + }, { name: 'Silver', class: 'Druid', race: 'Night Elf', spec: 'Restoration', rank: 'Raider' - },{ + }, { name: 'Dagger', race: 'Dwarf', class: 'Rogue', spec: 'Assassination', rank: 'Classleader' - },{ + }, { name: 'Hope', class: 'Paladin', race: 'Human', spec: 'Holy', rank: 'Classleader' - },{ + }, { name: 'Shrekd', class: 'Warrior', race: 'Dwarf', spec: 'Fury', rank: 'Classleader' - },{ + }, { name: 'Teeniweeni', class: 'Warlock', race: 'Gnome', spec: 'Demonology', rank: 'Classleader' - },{ + }, { name: 'Hagibaba', class: 'Priest', race: 'Human', spec: 'Discipline', rank: 'Classleader' - },{ + }, { name: 'Muffinbreak', class: 'Mage', race: 'Gnome', @@ -109,18 +116,18 @@ const testAccounts : protoAccount[] = [ describe('Frontcraft', () => { let auth: Auth, - adminUser : User, + adminUser: User, server: FrontworkAdmin, - client : RPCSocket & FrontcraftIfc, - adminClient : RPCSocket & FrontcraftFeatureIfc, + client: RPCSocket & FrontcraftIfc, + adminClient: RPCSocket & FrontcraftFeatureIfc, raids: Raid[] = [], - users : {[username in string] : { account: User, character: Character, auth: Auth, signup?:Signup, item?:string }} = {} + users: { [username in string]: { account: User, character: Character, auth: Auth, signup?: Signup, item?: string } } = {} const createAccount = (user: User) => { return client.UserManager.createUser(user) } - const createAccountAndUser = async (acc : protoAccount) => { + const createAccountAndUser = async (acc: protoAccount) => { const account = await createAccount({ pwhash: 'ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb', //sha256("a") rank: acc.rank, @@ -141,15 +148,15 @@ describe('Frontcraft', () => { } } - before(function (done){ - this.timeout(10000); + before(function (done) { + this.timeout(10000); server = Injector.resolve(FrontworkAdmin) server.start().then((_server) => { - + RPCSocket.makeSocket(20000, 'localhost').then(_client => { client = _client - + createAccount({ pwhash: 'ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb', //hash("a") @@ -160,7 +167,7 @@ describe('Frontcraft', () => { client.UserManager.login(adminUser.username, 'ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb').then(auth => { const sock = new RPCSocket( - auth.port, + auth.port, "localhost" ) @@ -170,11 +177,11 @@ describe('Frontcraft', () => { console.log("I got kicked"); }) sock.hook('getUserData', () => auth) - sock.hook('navigate', (where:string) => { - console.log("Nagivate client to "+where); + sock.hook('navigate', (where: string) => { + console.log("Nagivate client to " + where); }) sock.on('error', (e) => { - console.log('Socket error', e) + console.log('Socket error', e) }) done() }) @@ -184,7 +191,7 @@ describe('Frontcraft', () => { }).catch(done) }) - after(()=>{ + after(() => { client.destroy() adminClient.destroy() server.stop() @@ -193,16 +200,16 @@ describe('Frontcraft', () => { it('create raids', (done) => { let insertRaid = { description: "Test raid 1", - title: 'MC', + title: 'BWL :D', start: Date.now().toString(), - tier: 'MC' + tier: 'BWL' } adminClient.manageRaid.createRaid(insertRaid).then(() => { - client.RaidManager.getRaids().then((r)=>{ - if(r[0].title === insertRaid.title - && r[0].description === insertRaid.description - && r[0].tier === "MC"){ + client.RaidManager.getRaids().then((r) => { + if (r[0].title === insertRaid.title + && r[0].description === insertRaid.description + && r[0].tier === "BWL") { raids.push(r[0]) done() } @@ -210,9 +217,9 @@ describe('Frontcraft', () => { }).catch(done) }) - it('create users', (done)=>{ + it('create users', (done) => { Promise.all(testAccounts.map(acc => createAccountAndUser(acc))).then(accs => { - if(accs.length === testAccounts.length){ + if (accs.length === testAccounts.length) { accs.forEach(acc => { users[acc.account.username] = acc }) @@ -221,29 +228,29 @@ describe('Frontcraft', () => { }).catch(done) }) - it('should sign up', (done)=>{ + it('should sign up', (done) => { Promise.all(Object.values(users).map((user) => adminClient.signup.sign(user.auth.token.value, user.character, raids[0], false).catch(done) )).then(x => { adminClient.signup.getSignups(raids[0]).then(s => { - if(s.length == testAccounts.length){ + if (s.length == testAccounts.length) { s.forEach(sign => { users[sign.username].signup = sign }) done() - }else{ - done("Unexpected number of signups: "+s.length) + } else { + done("Unexpected number of signups: " + s.length) } }) }) }) - it('calculate priorities', (done)=>{ - const makePrio = async (itemname:string, spec?: Spec, race?:Race, mod:number = 0, description:string = "") => { + it('calculate priorities', (done) => { + const makePrio = async (itemname: string, spec?: Spec, race?: Race, mod: number = 0, description: string = "") => { let specid - if(spec) + if (spec) specid = await client.CharacterManager.getSpecId(spec.class, spec.specname) - + await adminClient.managePriorities.setPriority(itemname, { specid: specid, race: race, @@ -252,273 +259,288 @@ describe('Frontcraft', () => { }) } - + Promise.all([ makePrio( - 'Bracers of Arcane Accuracy', - {class:'Warlock', specname:'Demonology'}, + 'Bracers of Arcane Accuracy', + { class: 'Warlock', specname: 'Demonology' }, undefined, 2, "hit bias" ), makePrio( - 'Bracers of Arcane Accuracy', - {class:'Warlock', specname:'Affliction'}, + 'Bracers of Arcane Accuracy', + { class: 'Warlock', specname: 'Affliction' }, undefined, 2, "hit bias" ), makePrio( - 'Bracers of Arcane Accuracy', - {class:'Warlock', specname:'Destruction'}, + 'Bracers of Arcane Accuracy', + { class: 'Warlock', specname: 'Destruction' }, undefined, 2, "hit bias" ), makePrio( - 'Bracers of Arcane Accuracy', - {class:'Mage', specname:'Arcane'}, + 'Bracers of Arcane Accuracy', + { class: 'Mage', specname: 'Arcane' }, undefined, 1, "hit bias" ), makePrio( - 'Bracers of Arcane Accuracy', - {class:'Mage', specname:'Frost'}, + 'Bracers of Arcane Accuracy', + { class: 'Mage', specname: 'Frost' }, undefined, 1, "hit bias" ), makePrio( - 'Bracers of Arcane Accuracy', - {class:'Mage', specname:'Fire'}, + 'Bracers of Arcane Accuracy', + { class: 'Mage', specname: 'Fire' }, undefined, 1, "hit bias" ), makePrio( - 'Maladath, Runed Blade of the Black Flight', + 'Maladath, Runed Blade of the Black Flight', undefined, - "Human", -2, "(1) Non-Human" + "Human", -2, "[1] Non-Human" ), makePrio( - 'Maladath, Runed Blade of the Black Flight', - {class:'Rogue', specname:'Subtlety'}, - undefined, 2, "...Rogues (weapon skill bias)" + 'Maladath, Runed Blade of the Black Flight', + { class: 'Rogue', specname: 'Combat' }, + undefined, 2, "Weapon skill bias" ), makePrio( - 'Maladath, Runed Blade of the Black Flight', - {class:'Rogue', specname:'Combat'}, - undefined, 2, "...Rogues (weapon skill bias)" + 'Maladath, Runed Blade of the Black Flight', + { class: 'Warrior', specname: 'Fury' }, + 'Human', 4, "+2 Fury Warrior (weapon skill bias), +2 to offset [1]" ), makePrio( - 'Maladath, Runed Blade of the Black Flight', - {class:'Rogue', specname:'Assassination'}, - undefined, 2, "...Rogues (weapon skill bias)" - ), - makePrio( - 'Maladath, Runed Blade of the Black Flight', - {class:'Warrior', specname:'Fury'}, - 'Human', 4, "+2 Fury Warrior (weapon skill bias), +2 to offset (1)" + 'Maladath, Runed Blade of the Black Flight', + { class: 'Warrior', specname: 'Protection' }, + 'Human', 5, "+3 Prot Warrior, +2 to offset [1]" ), makePrio( - 'Cloak of Firemaw', - {class:'Rogue', specname:'Assassination'}, + 'Cloak of Firemaw', + { class: 'Rogue', specname: 'Assassination' }, undefined, 2, "agi-to-ap bias" ), makePrio( - 'Cloak of Firemaw', - {class:'Rogue', specname:'Combat'}, + 'Cloak of Firemaw', + { class: 'Rogue', specname: 'Combat' }, undefined, 2, "agi-to-ap bias" ), makePrio( - 'Cloak of Firemaw', - {class:'Rogue', specname:'Subtlety'}, + 'Cloak of Firemaw', + { class: 'Rogue', specname: 'Subtlety' }, undefined, 2, "agi-to-ap bias" ), makePrio( - 'Band of Forced Concentration', - {class:'Warlock', specname:'Demonology'}, + 'Band of Forced Concentration', + { class: 'Warlock', specname: 'Demonology' }, undefined, 2, "hit bias" ), makePrio( - 'Band of Forced Concentration', - {class:'Warlock', specname:'Affliction'}, + 'Band of Forced Concentration', + { class: 'Warlock', specname: 'Affliction' }, undefined, 2, "hit bias" ), makePrio( - 'Band of Forced Concentration', - {class:'Warlock', specname:'Destruction'}, + 'Band of Forced Concentration', + { class: 'Warlock', specname: 'Destruction' }, undefined, 2, "hit bias" ), makePrio( - 'Band of Forced Concentration', - {class:'Mage', specname:'Arcane'}, + 'Band of Forced Concentration', + { class: 'Mage', specname: 'Arcane' }, undefined, 1, "hit bias" ), makePrio( - 'Band of Forced Concentration', - {class:'Mage', specname:'Frost'}, + 'Band of Forced Concentration', + { class: 'Mage', specname: 'Frost' }, undefined, 1, "hit bias" ), makePrio( - 'Band of Forced Concentration', - {class:'Mage', specname:'Fire'}, + 'Band of Forced Concentration', + { class: 'Mage', specname: 'Fire' }, undefined, 1, "hit bias" ), makePrio( - 'Drake Fang Talisman', - {class:'Rogue', specname:'Assassination'}, - undefined, 4, "hit bias" - ), - makePrio( - 'Drake Fang Talisman', - {class:'Rogue', specname:'Combat'}, - undefined, 4, "hit bias" - ), - makePrio( - 'Drake Fang Talisman', - {class:'Rogue', specname:'Subtlety'}, - undefined, 4, "hit bias" - ), - makePrio( - 'Drake Fang Talisman', - {class:'Warrior', specname:'Fury'}, - undefined, 2, "hit bias" - ), - makePrio( - 'Drake Fang Talisman', - {class:'Druid', specname:'Feral (DPS)'}, + 'Chromatic Boots', + { class: 'Warrior', specname: 'Protection' }, undefined, 2, "hit bias" ), makePrio( - 'Circle of Applied Force', - {class:'Warrior', specname:'Fury'}, + 'Crul\'shorukh, Edge of Chaos', + undefined, + "Human", -2, "Non-human" + ), + makePrio( + 'Crul\'shorukh, Edge of Chaos', + { class: 'Warrior', specname: 'Protection' }, + undefined, 2, "nice dps bias" + ), + makePrio( + 'Crul\'shorukh, Edge of Chaos', + { class: 'Warrior', specname: 'Fury' }, + undefined, 2, "nice dps bias" + ), + + makePrio( + 'Drake Talon Pauldrons', + { class: 'Warrior', specname:'Protection'}, + undefined, 2, 'dodge + stats-to-threat bias' + ), + + makePrio( + 'Helm of Endless Rage', + { class: 'Warrior', specname:'Protection'}, + undefined, 2, 'stats-to-threat bias' + ), + + makePrio( + 'Drake Fang Talisman', + { class: 'Warrior', specname: 'Protection' }, + undefined, 5, "hit bias" + ), + makePrio( + 'Drake Fang Talisman', + { class: 'Rogue', specname: 'Assassination' }, + undefined, 4, "hit bias" + ), + makePrio( + 'Drake Fang Talisman', + { class: 'Rogue', specname: 'Combat' }, + undefined, 4, "hit bias" + ), + makePrio( + 'Drake Fang Talisman', + { class: 'Rogue', specname: 'Subtlety' }, + undefined, 4, "hit bias" + ), + makePrio( + 'Drake Fang Talisman', + { class: 'Warrior', specname: 'Fury' }, + undefined, 2, "hit bias" + ), + makePrio( + 'Drake Fang Talisman', + { class: 'Druid', specname: 'Feral (DPS)' }, + undefined, 2, "hit bias" + ), + + makePrio( + 'Circle of Applied Force', + { class: 'Warrior', specname: 'Fury' }, undefined, 2, "str-to-ap bias" ), makePrio( - 'Circle of Applied Force', - {class:'Druid', specname:'Feral (DPS)'}, - undefined, 2, "str-to-ap bias" + 'Circle of Applied Force', + { class: 'Druid', specname: 'Feral (DPS)' }, + undefined, 3, "str-to-ap bias + agi-to-ap bias" ), makePrio( - 'Empowered Leggings', - {class:'Paladin', specname:'Holy'}, + 'Empowered Leggings', + { class: 'Paladin', specname: 'Holy' }, undefined, 2, "crit bias" ), makePrio( - 'Empowered Leggings', - {class:'Druid', specname:'Restoration'}, + 'Empowered Leggings', + { class: 'Druid', specname: 'Restoration' }, undefined, 2, "crit bias" ), makePrio( - 'Boots of the Shadow Flame', - {class:'Rogue', specname:'Assassination'}, + 'Boots of the Shadow Flame', + { class: 'Rogue', specname: 'Assassination' }, undefined, 2, "hit bias" ), makePrio( - 'Boots of the Shadow Flame', - {class:'Rogue', specname:'Combat'}, + 'Boots of the Shadow Flame', + { class: 'Rogue', specname: 'Combat' }, undefined, 2, "hit bias" ), makePrio( - 'Boots of the Shadow Flame', - {class:'Rogue', specname:'Subtlety'}, + 'Boots of the Shadow Flame', + { class: 'Rogue', specname: 'Subtlety' }, undefined, 2, "hit bias" ), makePrio( - 'Boots of the Shadow Flame', - {class:'Druid', specname:'Feral (DPS)'}, + 'Boots of the Shadow Flame', + { class: 'Druid', specname: 'Feral (DPS)' }, undefined, 2, "hit bias" ), makePrio( - 'Neltharion\'s Tear', - {class:'Warlock', specname:'Demonology'}, + 'Neltharion\'s Tear', + { class: 'Warlock', specname: 'Demonology' }, undefined, 4, "hit bias" ), makePrio( - 'Neltharion\'s Tear', - {class:'Warlock', specname:'Affliction'}, + 'Neltharion\'s Tear', + { class: 'Warlock', specname: 'Affliction' }, undefined, 4, "hit bias" ), makePrio( - 'Neltharion\'s Tear', - {class:'Warlock', specname:'Destruction'}, + 'Neltharion\'s Tear', + { class: 'Warlock', specname: 'Destruction' }, undefined, 4, "hit bias" ), makePrio( - 'Neltharion\'s Tear', - {class:'Mage', specname:'Arcane'}, + 'Neltharion\'s Tear', + { class: 'Mage', specname: 'Arcane' }, undefined, 2, "hit bias" ), makePrio( - 'Neltharion\'s Tear', - {class:'Mage', specname:'Frost'}, + 'Neltharion\'s Tear', + { class: 'Mage', specname: 'Frost' }, undefined, 2, "hit bias" ), makePrio( - 'Neltharion\'s Tear', - {class:'Mage', specname:'Fire'}, + 'Neltharion\'s Tear', + { class: 'Mage', specname: 'Fire' }, undefined, 2, "hit bias" ), makePrio( - 'Cloak of Draconic Might', - {class:'Warrior', specname:'Fury'}, + 'Cloak of Draconic Might', + { class: 'Warrior', specname: 'Fury' }, undefined, 2, "str-to-ap bias" ), makePrio( - 'Cloak of Draconic Might', - {class:'Druid', specname:'Feral (DPS)'}, - undefined, 2, "str-to-ap bias" + 'Cloak of Draconic Might', + { class: 'Druid', specname: 'Feral (DPS)' }, + undefined, 1, "str-to-ap bias" ), - ]) - - - const user = Object.values(users)[0] - adminClient.managePriorities.setPriority(T1[0], { - race: user.character.race, - specid: user.character.specid, - modifier: 1, - description:'AAA' - }).then(() => { - adminClient.managePriorities.setPriority(T1[0], { - race: user.character.race, - specid: undefined, - modifier: 2, - description:'BBB' - }).then(()=>{ - adminClient.managePriorities.setPriority(T1[0], { - race: undefined, - specid: user.character.specid, - modifier: 3, - description:'CCC' - }).then(()=>{ - client.ItemManager.calculatePriorities(T1[0], user.character).then(sum => { - if(sum === 6) - done() - }) - }) + ]).then(() => { + const user = Object.values(users)[0] + client.ItemManager.calculatePriorities("Maladath, Runed Blade of the Black Flight", user.character).then(sum => { + if (sum === 3) + done() + else + console.log("Expected prio on maladath to be 4, but was:", sum, user.character); }) + }) }) - it('buy token', (done)=>{ - Promise.all(Object.values(users).map(async (user) =>{ - const itemname = T1[0]//T1[Math.floor(T1.length*Math.random())] + it('buy token', (done) => { + Promise.all(Object.values(users).map(async (user) => { + const itemname = "Maladath, Runed Blade of the Black Flight" //T1[Math.floor(T1.length*Math.random())] const modifier = await client.ItemManager.calculatePriorities(itemname, user.character) const token = await client.ItemManager.buyToken(user.auth.token.value, user.character.charactername, itemname, user.signup!) users[user.account.username].item = itemname - - if(!token) return false - return modifier+1 === token.level + + if (!token) return false + return modifier + 1 === token.level })).then(success => { - if(success.reduce((prev, curr)=>prev&&curr, true)) done() + if (success.reduce((prev, curr) => prev && curr, true)) done() }) }) - it('not buy token without currency', (done)=>{ + it('not buy token without currency', (done) => { const user = Object.values(users)[0] - const itemname = T1[0] + const itemname = "Maladath, Runed Blade of the Black Flight" client.ItemManager.buyToken(user.auth.token.value, user.character.charactername, itemname, user.signup!).then(token => { - if(!token) + if (!token) done() else { console.log("Unexpected token", token); @@ -526,24 +548,23 @@ describe('Frontcraft', () => { }) }) - it('upgrade token', (done)=>{ + it('upgrade token', (done) => { const user = Object.values(users)[0] - const itemname = T1[0] - - adminClient.softreserveCurrency.incrementCurrency(user.account, raids[0].tier, 2).then(async ()=>{ - const item = await client.ItemManager.getItem(itemname) + const itemname = "Maladath, Runed Blade of the Black Flight" + client.ItemManager.getItem(itemname).then(async item => { + await adminClient.softreserveCurrency.incrementCurrency(user.account, item.tier, 2) const before = await client.ItemManager.getToken(user.character, item) - - if(!before || before.level !== 7) { - console.log("expected level to be 7", before); + + if (!before || before.level !== 4) { + console.log("expected level to be 4", before ? before.level : '?'); return } - + await client.ItemManager.buyToken(user.auth.token.value, user.character.charactername, itemname, user.signup!) const after = await client.ItemManager.buyToken(user.auth.token.value, user.character.charactername, itemname, user.signup!) - if(!after || after.level !== 9) { - console.log("expected level to be 9", after); + if (!after || after.level !== 6) { + console.log("expected level after to be 6", after ? after.level : '?'); return } done() @@ -552,14 +573,14 @@ describe('Frontcraft', () => { it('should buy more tokens', (done) => { Promise.all(Object.values(users).map(async (user) => { - await adminClient.softreserveCurrency.incrementCurrency(user.account,raids[0].tier, 1) + await adminClient.softreserveCurrency.incrementCurrency(user.account, raids[0].tier, 1) return await client.ItemManager - .buyToken( - user.auth.token.value, - user.character.charactername, - T1[Math.floor(T1.length*Math.random())], - user.signup! - ) + .buyToken( + user.auth.token.value, + user.character.charactername, + T2[Math.floor(T2.length * Math.random())], + user.signup! + ) })).then(_ => { done() }) @@ -574,21 +595,21 @@ describe('Frontcraft', () => { }) it('start raid', (done) => { - client.RaidManager.getRaids().then((r)=>{ + client.RaidManager.getRaids().then((r) => { adminClient.manageRaid.startRaid(raids[0]).then(async data => { const dbRaids = await client.RaidManager.getRaids() - if(dbRaids.length === 0){ + if (dbRaids.length === 0) { await client.UserManager.getUser(testAccounts[0].name).then(dbUser => { - if(dbUser && dbUser.MC === 1){ + if (dbUser && dbUser.BWL === 1) { adminClient.signup.getSignups(raids[0]).then(signups => { - if(signups.length === 0){ + if (signups.length === 0) { done() - }else{ + } else { console.log(signups); } }) } - else{ + else { console.log("Bad user currency", dbUser); } }) @@ -600,81 +621,121 @@ describe('Frontcraft', () => { it('reset system', (done) => { adminClient.reset.wipeCurrencyAndItems().then(() => { client.UserManager.getUser(testAccounts[0].name).then(user => { - if(user && user.MC === 1){ - client.ItemManager.getTokens(users[testAccounts[0].name.toLowerCase()].character, ['MC']).then(tokens => { - if(tokens!.length === 0){ + if (user && user.MC === 1) { + client.ItemManager.getTokens(users[testAccounts[0].name.toLowerCase()].character, ['BWL']).then(tokens => { + if (tokens!.length === 0) { done() - }else{ + } else { console.log(tokens); } }) - }else{ + } else { console.log(user) } }) }) }) - it('implements loot system correctly', (done)=>{ + it('implements loot system correctly', (done) => { const ONE_WEEK = 4800000000 - - const Raid = (week:number, tier:string) => { - return { - description: tier+" Test raid 1", + + const Raid = (week: number, tier: string) => { + return { + description: tier + " Test raid 1", title: tier, - start: (week*ONE_WEEK + Date.now()).toString(), + start: (week * ONE_WEEK + Date.now()).toString(), tier: tier } } const user = Object.values(users)[0] const createRaid = adminClient.manageRaid.createRaid - const sign = async (raid:Raid, late = false) => await adminClient.signup.sign(user.auth.token.value, user.character, raid, late) + const sign = async (raid: Raid, late = false) => await adminClient.signup.sign(user.auth.token.value, user.character, raid, late) const buyToken = async (signup: Signup, itemname: string) => await client.ItemManager.buyToken(user.auth.token.value, user.character.charactername, itemname, signup) + const getCurrency = async (tier: Tiers) => { + const dbUser = await client.UserManager.getUser(user.account.username) + return dbUser[tier] + } - createRaid(Raid(0, 'MC')).then(async (MC0:Raid) => { - const T1_0 = await client.ItemManager.getItem(T1[0]) + createRaid(Raid(0, 'BWL')).then(async (BWL0: Raid) => { + const T2_0 = await client.ItemManager.getItem(T2[0]) //const BWL0 = await createRaid(Raid(0, 'BWL')) - const signupMC0 = await sign(MC0) - let token = await buyToken(signupMC0, T1[0]) - if(!token){ - console.log(MC0, signupMC0) - done("No token created") + const signupBWL0 = await sign(BWL0) + let BWL = await getCurrency(BWL0.tier) + let token = await buyToken(signupBWL0, T2[0]) + const BWLAfter = await getCurrency(BWL0.tier) + + if (!token + || BWL - BWLAfter != 1) { + console.log("Bad Token status", BWL0, signupBWL0, BWL, BWLAfter) + done(new Error("Bad Token status 0")) return } - let reserves = await client.ItemManager.getTokens(user.character, [MC0.tier], true) - let streaks = await client.ItemManager.getTokens(user.character, [MC0.tier], false) - if(reserves!.length != 1 - || streaks!.length != 0 - || reserves![0].itemname !== T1_0.itemname - || reserves![0].level !== 7){ - console.log(reserves, streaks); - done("Bad token status") + let reserves = await client.ItemManager.getTokens(user.character, [BWL0.tier], true) + let streaks = await client.ItemManager.getTokens(user.character, [BWL0.tier], false) + if (reserves!.length != 1 + || streaks!.length != 0 + || reserves![0].itemname !== T2_0.itemname + || reserves![0].level !== 1) { + console.log("Bad Token status 1", reserves, streaks); + done(new Error("Bad Token status")) return } - await adminClient.manageRaid.startRaid(MC0) - reserves = await client.ItemManager.getTokens(user.character, [MC0.tier], true) - streaks = await client.ItemManager.getTokens(user.character, [MC0.tier], false) - if(reserves!.length != 0 - || streaks!.length != 1 - || streaks![0].itemname !== T1[0] - || streaks![0].level !== 7){ - console.log(reserves, streaks); - done("Bad token status") + await adminClient.manageRaid.startRaid(BWL0) + reserves = await client.ItemManager.getTokens(user.character, [BWL0.tier], true) + streaks = await client.ItemManager.getTokens(user.character, [BWL0.tier], false) + if (reserves!.length != 0 + || streaks!.length != 1 + || streaks![0].itemname !== T2[0] + || streaks![0].level !== 1) { + console.log("Bad Token status", reserves, streaks); + done(new Error("Bad Token status 2")) return } - const MC1 = await createRaid(Raid(1, 'MC')) - const signupMC1 = await sign(MC1) - token = await buyToken(signupMC1, T1[1]) - reserves = await client.ItemManager.getTokens(user.character, [MC1.tier], true) - streaks = await client.ItemManager.getTokens(user.character, [MC1.tier], false) - if(reserves!.length != 1 - || streaks!.length != 0 - || reserves![0].itemname !== T1[1] - || reserves![0].level !== 1){ - console.log(reserves, streaks); - done("Bad token status") + const BWL1 = await createRaid(Raid(1, 'BWL')) + + let signupBWL1 = await sign(BWL1) + BWL = await getCurrency(BWL1.tier) + await adminClient.manageRaid.adminUnsign(user.character, BWL1) + let afterUnsign = await getCurrency(BWL1.tier) + if (BWL !== afterUnsign) { + console.log("Expected currency to be equal", BWL, afterUnsign) + done(new Error("Expected currency to be equal")) + return + } + + signupBWL1 = await sign(BWL1) + BWL = await getCurrency(BWL1.tier) + await buyToken(signupBWL1, T2[1]) + await adminClient.manageRaid.adminUnsign(user.character, BWL1) + afterUnsign = await getCurrency(BWL1.tier) + if (BWL !== afterUnsign) { + console.log("Expected currency to be equal", BWL, afterUnsign) + done(new Error("Expected currency to be equal")) + return + } + + signupBWL1 = await sign(BWL1) + await buyToken(signupBWL1, T2[1]) + reserves = await client.ItemManager.getTokens(user.character, [BWL1.tier], true) + streaks = await client.ItemManager.getTokens(user.character, [BWL1.tier], false) + if (reserves!.length != 1 + || streaks!.length != 0 + || reserves![0].itemname !== T2[1] + || reserves![0].level !== 1) { + console.log("Bad Token status", reserves, streaks); + done(new Error("Bad Token status 3")) + return + } + + BWL = await getCurrency(BWL1.tier) + const data = await adminClient.manageRaid.startRaid(BWL1) + + const afterStart = await getCurrency(BWL1.tier) + if (BWL != 0 || afterStart != 1) { + console.log("Wrong currency values", BWL, afterStart) + done(new Error("Wrong currency values")) return } @@ -682,7 +743,7 @@ describe('Frontcraft', () => { }).catch(e => { console.log(e); - + done(e) }) })