Skip to content

Commit df6d16b

Browse files
authored
Merge pull request #11 from x-team/main
Merging main
2 parents e5eac30 + 3b122cf commit df6d16b

39 files changed

+1268
-19
lines changed

.env.example

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# API
2+
HOST=0.0.0.0
3+
PORT=3010
4+
5+
# DATABASE
6+
DB_USERNAME=postgres
7+
DB_PASSWORD=*games-2021
8+
DB_NAME=gameshq_api
9+
DB_HOSTNAME=127.0.0.1
10+
DB_PORT=5434
11+
12+
# THE ARENA APP
13+
SLACK_ARENA_TOKEN=
14+
SLACK_ARENA_SIGNING_SECRET=
15+
# Private Channel
16+
SLACK_ARENA_XHQ_CHANNEL=
17+
18+
# CAMPAIGN APP
19+
SLACK_CAMPAIGN_SIGNING_SECRET=
20+
SLACK_CAMPAIGN_TOKEN=
21+
SLACK_NEUTRAL_ZONE_CHANNEL=
22+
23+
# THE TOWER APP
24+
SLACK_TOWER_SIGNING_SECRET=
25+
SLACK_TOWER_TOKEN=
26+
SLACK_THE_TOWER_CHANNEL=
27+
28+
# FRONT-END-APP
29+
FRONT_END_SIGNING_SECRET=
30+
FRONT_END_APP_BOT_TOKEN=
31+
32+
# FIREBASE
33+
GOOGLE_APPLICATION_CREDENTIALS="/path/to/yours/service/account/file/service-account-file.json"

.nvmrc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
v16.10.0

package.json

+6
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,17 @@
3838
"license": "ISC",
3939
"dependencies": {
4040
"@hapi/boom": "9.1.2",
41+
"@hapi/catbox": "11.1.1",
42+
"@hapi/catbox-memory": "5.0.1",
4143
"@hapi/hapi": "20.1.5",
4244
"@hapi/inert": "6.0.3",
4345
"@hapi/vision": "6.1.0",
4446
"@slack/client": "5.0.2",
47+
"@types/catbox": "10.0.7",
48+
"@types/catbox-memory": "4.0.0",
4549
"dotenv": "10.0.0",
4650
"fast-safe-stringify": "2.0.7",
51+
"firebase-admin": "10.0.0",
4752
"hapi-swagger": "14.2.1",
4853
"indefinite": "2.4.1",
4954
"joi": "17.4.0",
@@ -59,6 +64,7 @@
5964
"devDependencies": {
6065
"@types/boom": "7.3.1",
6166
"@types/chai": "4.2.19",
67+
"@types/hapi__catbox-memory": "4.1.3",
6268
"@types/hapi__hapi": "20.0.9",
6369
"@types/hapi__inert": "5.2.3",
6470
"@types/hapi__vision": "5.5.3",

src/config/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ interface NameToType {
2525
SLACK_TOWER_SIGNING_SECRET: string;
2626
SLACK_CAMPAIGN_SIGNING_SECRET: string;
2727
SLACK_THE_TOWER_CHANNEL: string;
28+
FRONT_END_APP_BOT_TOKEN: string;
29+
FRONT_END_SIGNING_SECRET: string;
30+
GOOGLE_APPLICATION_CREDENTIALS: string;
2831
}
2932

3033
export function getConfig<T extends keyof NameToType>(name: T): NameToType[T];

src/games/arena/consts.ts

+2
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ export enum ARENA_HEALTHKITS {
110110
// REPOSITORIEs
111111
export const ARENA_REPOSITORY_NAME = 'arena-repository';
112112
export const ZONE_REPOSITORY_NAME = 'zone-repository';
113+
export const WEAPON_REPOSITORY_NAME = 'weapon-repository';
114+
export const ENEMY_REPOSITORY_NAME = 'enemy-repository';
113115

114116
// GAME
115117
export const SEARCH_WEAPONS_SUCCESS_RATE = 0.8;

src/games/arena/utils/index.ts

+24
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ import {
3535
ARENA_PLAYER_PERFORMANCE,
3636
ARENA_SECONDARY_ACTIONS,
3737
ChangeLocationParams,
38+
ENEMY_REPOSITORY_NAME,
39+
WEAPON_REPOSITORY_NAME,
3840
ZONE_REPOSITORY_NAME,
3941
} from '../consts';
4042
import {
@@ -243,6 +245,28 @@ export function withArenaTransaction<T>(fn: (transaction: Transaction) => Promis
243245
});
244246
}
245247

248+
export function withWeaponTransaction<T>(fn: (transaction: Transaction) => Promise<T>) {
249+
return withTransaction((transaction) => {
250+
return fn(transaction).catch(async (error) => {
251+
if (error instanceof GameError) {
252+
error.addRepository(WEAPON_REPOSITORY_NAME);
253+
}
254+
throw error;
255+
});
256+
});
257+
}
258+
259+
export function withEnemyTransaction<T>(fn: (transaction: Transaction) => Promise<T>) {
260+
return withTransaction((transaction) => {
261+
return fn(transaction).catch(async (error) => {
262+
if (error instanceof GameError) {
263+
error.addRepository(ENEMY_REPOSITORY_NAME);
264+
}
265+
throw error;
266+
});
267+
});
268+
}
269+
246270
export function withZoneTransaction<T>(fn: (transaction: Transaction) => Promise<T>) {
247271
return withTransaction((transaction) => {
248272
return fn(transaction).catch(async (error) => {

src/games/general/commands/index.ts

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { getGameResponse } from '../../utils';
2+
import { GAMES_SLACK_COMMANDS } from '../consts';
3+
4+
import { register } from './register';
5+
6+
interface GamesHQSwitchCommandOptions {
7+
command: string;
8+
commandText: string;
9+
slackId: string;
10+
channelId: string;
11+
triggerId: string;
12+
}
13+
14+
export function gamesSwitchCommand({ command, slackId }: GamesHQSwitchCommandOptions) {
15+
switch (command) {
16+
// ADMIN
17+
case GAMES_SLACK_COMMANDS.REGISTER:
18+
return register(slackId);
19+
20+
default:
21+
return getGameResponse('Invalid command.');
22+
}
23+
}
+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import Boom from '@hapi/boom';
2+
3+
import { USER_ROLE_LEVEL } from '../../../consts/model';
4+
import { findOrganizationByName } from '../../../models/Organization';
5+
import { createUser, userExists } from '../../../models/User';
6+
import { getGameResponse, getSlackUserInfo } from '../../utils';
7+
8+
export const register = async (slackUserId: string) => {
9+
const exists = await userExists(slackUserId);
10+
if (exists) {
11+
return getGameResponse(`Your user is already registered.`);
12+
}
13+
14+
const xteamOrganization = await findOrganizationByName('x-team');
15+
const { user } = await getSlackUserInfo(slackUserId);
16+
17+
if (!user || !user.profile || !xteamOrganization) {
18+
throw Boom.badRequest(`Failed to create new user on GamesHQ.`);
19+
}
20+
const { profile } = user;
21+
const { email, image_512 } = profile;
22+
23+
await createUser({
24+
email: email,
25+
displayName: user.real_name,
26+
firebaseUserUid: null,
27+
profilePictureUrl: image_512,
28+
slackId: user.id,
29+
_teamId: null,
30+
_roleId: USER_ROLE_LEVEL.USER,
31+
_organizationId: xteamOrganization?.id,
32+
});
33+
34+
return getGameResponse(
35+
`Your e-mail _${email}_ is now registered to all our games :partyparrot: `
36+
);
37+
};

src/games/general/consts.ts

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export enum GAMES_SLACK_COMMANDS {
2+
// USER COMMANDS
3+
REGISTER = '/games-register',
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { createOrUpdateArenaZone, deleteZoneById } from '../../../../models/ArenaZone';
2+
import { ARENA_ZONE_RING } from '../../../arena/consts';
3+
import { withZoneTransaction } from '../../../arena/utils';
4+
5+
export interface IZoneEditorData {
6+
id?: number;
7+
name: string;
8+
emoji: string;
9+
ring: string;
10+
isArchived: boolean;
11+
}
12+
13+
export const upsertZone = async (data: IZoneEditorData) => {
14+
return withZoneTransaction(async () => {
15+
const values = {
16+
...(data.id && { id: data.id }),
17+
name: data.name,
18+
emoji: data.emoji,
19+
isArchived: data.isArchived,
20+
ring: data.ring as ARENA_ZONE_RING,
21+
isActive: data.isArchived,
22+
};
23+
return createOrUpdateArenaZone(values);
24+
});
25+
};
26+
27+
export const deleteZone = async (zoneId: number) => {
28+
return withZoneTransaction(async (transaction) => {
29+
return deleteZoneById(zoneId, transaction);
30+
});
31+
};

src/games/tower/repositories/tower/actions/admin/tower-floors-operations.ts

+24
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
removeEnemyFromFloorBattlefields,
77
} from '../../../../../../models/TowerFloorBattlefieldEnemy';
88
import {
9+
addTowerFloorEnemies,
910
addTowerFloorEnemy,
1011
findTowerFloorEnemyById,
1112
} from '../../../../../../models/TowerFloorEnemy';
@@ -135,6 +136,29 @@ export async function removeEnemyFromFloor(userRequesting: User, towerEnemyId: n
135136
});
136137
}
137138

139+
export async function addEnemiesToFloor(
140+
userRequesting: User,
141+
floorNumber: number,
142+
enemyIds: number[]
143+
) {
144+
return withTowerTransaction(async (transaction) => {
145+
const isAdmin = adminAction(userRequesting);
146+
if (!isAdmin) {
147+
return getGameError(towerCommandReply.adminsOnly());
148+
}
149+
const activeTower = await activeTowerHandler(transaction);
150+
if (!(activeTower instanceof Game)) {
151+
return activeTower as GameResponse;
152+
}
153+
const towerFloor = activeTower._tower?._floors?.find((floor) => floor.number === floorNumber);
154+
if (!towerFloor) {
155+
return getGameError(towerCommandReply.floorNumberNotValid());
156+
}
157+
await addTowerFloorEnemies(towerFloor.id, enemyIds, transaction);
158+
return;
159+
});
160+
}
161+
138162
export async function addEnemyToFloor(userRequesting: User, floorNumber: number, enemyId: number) {
139163
return withTowerTransaction(async (transaction) => {
140164
const isAdmin = adminAction(userRequesting);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { createOrUpdateEnemy, deleteEnemyById } from '../../../../models/Enemy';
2+
import { createEnemyPattern, existsEnemyPattern } from '../../../../models/EnemyPattern';
3+
import { withEnemyTransaction } from '../../../arena/utils';
4+
import { AbilityProperty } from '../../../classes/GameAbilities';
5+
6+
export interface IEnemyEditorData {
7+
id?: number;
8+
name: string;
9+
gifUrl: string;
10+
emoji: string;
11+
abilitiesJSON: AbilityProperty;
12+
health: number;
13+
isBoss: boolean;
14+
majorDamageRate: number;
15+
minorDamageRate: number;
16+
actionPattern: string;
17+
}
18+
19+
export const upsertEnemy = async (data: IEnemyEditorData) => {
20+
return withEnemyTransaction(async (transaction) => {
21+
const enemyPatternExists = await existsEnemyPattern(data.actionPattern, transaction);
22+
let enemyPatternId = data.actionPattern;
23+
if (!enemyPatternExists) {
24+
const newPattern = await createEnemyPattern(data.actionPattern);
25+
enemyPatternId = newPattern.id;
26+
}
27+
28+
return createOrUpdateEnemy(
29+
{
30+
...(data.id && { id: data.id }),
31+
abilitiesJSON: data.abilitiesJSON,
32+
emoji: data.emoji,
33+
gifUrl: data.gifUrl,
34+
name: data.name,
35+
health: data.health,
36+
isBoss: data.isBoss,
37+
majorDamageRate: data.majorDamageRate,
38+
minorDamageRate: data.minorDamageRate,
39+
_enemyPatternId: enemyPatternId,
40+
},
41+
transaction
42+
);
43+
});
44+
};
45+
46+
export const deleteEnemy = async (enemyId: number) => {
47+
return withEnemyTransaction(async (transaction) => {
48+
return deleteEnemyById(enemyId, transaction);
49+
});
50+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { findAdmin } from '../../../../models/User';
2+
3+
import { addEnemiesToFloor } from './actions/admin/tower-floors-operations';
4+
5+
export const addEnemies = async (floorNumber: number, enemyIds: number[]) => {
6+
const adminUser = await findAdmin();
7+
if (!adminUser) {
8+
return;
9+
}
10+
11+
await addEnemiesToFloor(adminUser, floorNumber, enemyIds);
12+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { startTowerGame } from '../../../../models/TowerGame';
2+
import { findAdmin } from '../../../../models/User';
3+
import { withTowerTransaction } from '../../utils';
4+
import { endGame, openOrCloseTowerGates } from './actions/admin/create-or-finish-game';
5+
6+
export interface ICreateTowerGameData {
7+
name: string;
8+
height: number;
9+
}
10+
11+
export const createTowerGame = async (data: ICreateTowerGameData) => {
12+
return withTowerTransaction(async (transaction) => {
13+
await startTowerGame(
14+
{
15+
name: data.name,
16+
height: data.height,
17+
isOpen: false,
18+
lunaPrize: 0,
19+
coinPrize: 0,
20+
_createdById: 1,
21+
},
22+
transaction
23+
);
24+
});
25+
};
26+
27+
export const endCurrentTowerGame = async () => {
28+
const adminUser = await findAdmin();
29+
if (!adminUser) {
30+
return;
31+
}
32+
return endGame(adminUser);
33+
};
34+
35+
export const openOrCloseTower = async (open: boolean) => {
36+
const adminUser = await findAdmin();
37+
if (!adminUser) {
38+
return;
39+
}
40+
return openOrCloseTowerGates(adminUser, open);
41+
};

0 commit comments

Comments
 (0)