Skip to content

Commit 184abbe

Browse files
author
fredericobraga85
authored
Featuer/xtg 274 - CRUD acheivements in admin panel (#35)
1 parent 5b259ba commit 184abbe

File tree

7 files changed

+848
-21
lines changed

7 files changed

+848
-21
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import Joi from 'joi';
2+
3+
export const getAcheivementByIdResponseSchema = Joi.object({
4+
id: Joi.number().required(),
5+
_gameTypeId: Joi.number().required(),
6+
description: Joi.string().required(),
7+
isEnabled: Joi.boolean().required(),
8+
targetValue: Joi.number().required(),
9+
createdAt: Joi.date().required(),
10+
updatedAt: Joi.date().required(),
11+
}).optional();
12+
13+
export const getAcheivementsResponseSchema = Joi.array()
14+
.items(getAcheivementByIdResponseSchema)
15+
.optional();
16+
17+
export const postAcheivementRequestSchema = Joi.object({
18+
id: Joi.number().optional(),
19+
description: Joi.string().required(),
20+
isEnabled: Joi.boolean().optional(),
21+
targetValue: Joi.number().required(),
22+
}).required();
23+
24+
export const postAcheivementResponseSchema = Joi.object({
25+
id: Joi.number().required(),
26+
_gameTypeId: Joi.number().required(),
27+
description: Joi.string().required(),
28+
isEnabled: Joi.boolean().required(),
29+
targetValue: Joi.number().required(),
30+
createdAt: Joi.date().required(),
31+
updatedAt: Joi.date().required(),
32+
}).required();

src/models/Achievements.ts

+30-17
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ interface AchievementCreationAttributes {
3333
description: string;
3434
isEnabled: boolean;
3535
targetValue: number;
36-
createdAt: Date;
37-
updatedAt: Date;
36+
createdAt?: Date;
37+
updatedAt?: Date;
3838
_gameTypeId: number;
3939
}
4040

@@ -88,12 +88,11 @@ export class Achievement
8888
};
8989
}
9090

91-
interface AchievementEditorData {
92-
id: number;
91+
export interface AchievementEditorData {
92+
id?: number;
9393
description: string;
9494
isEnabled: boolean;
9595
targetValue: number;
96-
createdAt?: Date;
9796
}
9897

9998
export function findAchievementById(id: number, transaction?: Transaction) {
@@ -104,25 +103,39 @@ export function findAllAchievementsByGameType(gameTypeId: number, transaction?:
104103
return Achievement.findAll({ where: { _gameTypeId: gameTypeId }, transaction });
105104
}
106105

106+
export function getAcheivementByCreator(
107+
id: number,
108+
_gameTypeId: number,
109+
_createdById: number,
110+
transaction?: Transaction
111+
) {
112+
return Achievement.findOne({
113+
where: {
114+
id,
115+
_gameTypeId,
116+
},
117+
include: [
118+
{
119+
association: Achievement.associations._gameType,
120+
attributes: [],
121+
where: {
122+
_createdById,
123+
},
124+
},
125+
],
126+
transaction,
127+
});
128+
}
129+
107130
export async function createOrUpdateAchievement(
108131
achievementData: AchievementEditorData,
109132
gameTypeId: number,
110133
transaction?: Transaction
111134
) {
112-
const { id, description, isEnabled, targetValue, createdAt } = achievementData;
113-
const valuesToUpdate: AchievementCreationAttributes = {
114-
description,
115-
isEnabled,
116-
targetValue,
117-
createdAt: createdAt ?? new Date(),
118-
updatedAt: new Date(),
119-
_gameTypeId: gameTypeId,
120-
};
121-
122135
return Achievement.upsert(
123136
{
124-
id: id ?? undefined,
125-
...valuesToUpdate,
137+
...achievementData,
138+
_gameTypeId: gameTypeId,
126139
},
127140
{ transaction }
128141
);

src/models/GameType.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
import type { GAME_TYPE } from '../games/consts/global';
1616
import { generateSecret } from '../utils/cryptography';
1717

18+
import { Achievement } from './Achievements';
1819
import { LeaderboardEntry } from './LeaderboardEntry';
1920

2021
import { User } from './';
@@ -71,9 +72,13 @@ export class GameType
7172
@HasMany(() => LeaderboardEntry, '_gameTypeId')
7273
_leaderboards?: LeaderboardEntry[];
7374

75+
@HasMany(() => Achievement, '_gameTypeId')
76+
_acheivements?: Achievement[];
77+
7478
static associations: {
7579
_createdBy: Association<GameType, User>;
7680
_leaderboards: Association<GameType, LeaderboardEntry>;
81+
_acheivements: Association<GameType, Achievement>;
7782
};
7883
}
7984

@@ -92,7 +97,7 @@ export function findGameTypeByClientSecret(clientSecret: string, transaction?: T
9297
export function findGameTypeById(id: number, transaction?: Transaction) {
9398
return GameType.findByPk(id, {
9499
transaction,
95-
include: [GameType.associations._leaderboards],
100+
include: [GameType.associations._leaderboards, GameType.associations._acheivements],
96101
});
97102
}
98103

src/modules/gameDevs/gameDevHandlers.ts

+56
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@ import type { Lifecycle, Request, ResponseToolkit } from '@hapi/hapi';
33

44
import type { CustomRequestThis } from '../../api-utils/interfaceAndTypes';
55
import { arrayToJSON } from '../../api-utils/utils';
6+
import type { AchievementEditorData } from '../../models/Achievements';
7+
import {
8+
findAllAchievementsByGameType,
9+
getAcheivementByCreator,
10+
createOrUpdateAchievement,
11+
deleteAchievementById,
12+
} from '../../models/Achievements';
613
import type { IGameEditorData, GameType } from '../../models/GameType';
714
import {
815
createOrUpdateGameType,
@@ -61,6 +68,8 @@ export const deleteGameTypeHandler: Lifecycle.Method = async (request, h) => {
6168
};
6269

6370
export const getLeaderboardHandler: Lifecycle.Method = async (request, h) => {
71+
await validateGameAuth(request.pre.getAuthUser.id, request.params.gameTypeId);
72+
6473
if (request.params.leaderboardId) {
6574
const leaderboard = await getLeaderBoardByCreator(
6675
request.params.leaderboardId,
@@ -111,6 +120,53 @@ export const deleteLeaderboardHandler: Lifecycle.Method = async (request, h) =>
111120
return h.response({ success: true }).code(200);
112121
};
113122

123+
export const getAcheivementsHandler: Lifecycle.Method = async (request, h) => {
124+
await validateGameAuth(request.pre.getAuthUser.id, request.params.gameTypeId);
125+
126+
if (request.params.acheivementId) {
127+
const acheivement = await getAcheivementByCreator(
128+
request.params.acheivementId,
129+
request.params.gameTypeId,
130+
request.pre.getAuthUser.id
131+
);
132+
133+
if (!acheivement) {
134+
throw Boom.notFound('acheivement not found');
135+
}
136+
137+
return h.response(acheivement?.toJSON()).code(200);
138+
} else {
139+
const acheivements = await findAllAchievementsByGameType(request.params.gameTypeId);
140+
return h.response(arrayToJSON(acheivements)).code(200);
141+
}
142+
};
143+
144+
export const upsertAcheivementHandler: Lifecycle.Method = async (request, h) => {
145+
const payload = request.payload as AchievementEditorData;
146+
147+
const game = await validateGameAuth(request.pre.getAuthUser.id, request.params.gameTypeId);
148+
149+
if (payload.id && game && !game._acheivements?.map((a) => a.id).includes(payload.id)) {
150+
throw Boom.forbidden(`acheivement does not belong to gametypeId ${request.params.gameTypeId}`);
151+
}
152+
153+
const [rslt] = await createOrUpdateAchievement({ ...payload }, request.params.gameTypeId);
154+
155+
return h.response(rslt?.toJSON()).code(200);
156+
};
157+
158+
export const deleteAcheivementHandler: Lifecycle.Method = async (request, h) => {
159+
await validateGameAuth(request.pre.getAuthUser.id, request.params.gameTypeId);
160+
161+
const rslt = await deleteAchievementById(request.params.acheivementId);
162+
163+
if (!rslt) {
164+
throw Boom.notFound('acheivement not found');
165+
}
166+
167+
return h.response({ success: true }).code(200);
168+
};
169+
114170
const validateGameAuth = async (
115171
authUserId: number,
116172
gameTypeId?: number

src/modules/gameDevs/gameDevRoutes.ts

+105
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ import type { ServerRoute } from '@hapi/hapi';
22

33
import { getAuthUser } from '../../api-utils/getAuthUser';
44
import { CAPABILITIES } from '../../api-utils/interfaceAndTypes';
5+
import {
6+
getAcheivementByIdResponseSchema,
7+
getAcheivementsResponseSchema,
8+
postAcheivementRequestSchema,
9+
postAcheivementResponseSchema,
10+
} from '../../api-utils/schemas/gameDev/acheivementsSchemas';
511
import {
612
gamedevGenericSchema,
713
multipleGamesSchema,
@@ -21,6 +27,9 @@ import {
2127
getLeaderboardHandler,
2228
upsertLeaderboardHandler,
2329
deleteLeaderboardHandler,
30+
getAcheivementsHandler,
31+
upsertAcheivementHandler,
32+
deleteAcheivementHandler,
2433
} from './gameDevHandlers';
2534

2635
declare module '@hapi/hapi' {
@@ -211,6 +220,98 @@ export const upsertLeaderboardRoute: ServerRoute = {
211220
handler: upsertLeaderboardHandler,
212221
};
213222

223+
//Acheivements
224+
export const getAcheivementsRoute: ServerRoute = {
225+
method: 'GET',
226+
path: '/dashboard/game-dev/games/{gameTypeId}/acheivements',
227+
options: {
228+
description: `Get game's acheivements`,
229+
tags: ['api'],
230+
bind: {
231+
requiredCapabilities: [CAPABILITIES.GAMEDEV_ACTIONS],
232+
},
233+
pre: [
234+
{
235+
method: getAuthUser,
236+
assign: 'getAuthUser',
237+
},
238+
],
239+
response: {
240+
schema: getAcheivementsResponseSchema,
241+
},
242+
},
243+
handler: getAcheivementsHandler,
244+
};
245+
246+
export const getAcheivementsByIdRoute: ServerRoute = {
247+
method: 'GET',
248+
path: '/dashboard/game-dev/games/{gameTypeId}/acheivements/{acheivementId}',
249+
options: {
250+
description: `Get game's acheivements`,
251+
tags: ['api'],
252+
bind: {
253+
requiredCapabilities: [CAPABILITIES.GAMEDEV_ACTIONS],
254+
},
255+
pre: [
256+
{
257+
method: getAuthUser,
258+
assign: 'getAuthUser',
259+
},
260+
],
261+
response: {
262+
schema: getAcheivementByIdResponseSchema,
263+
},
264+
},
265+
handler: getAcheivementsHandler,
266+
};
267+
268+
export const upsertAcheivementsRoute: ServerRoute = {
269+
method: 'POST',
270+
path: '/dashboard/game-dev/games/{gameTypeId}/acheivements',
271+
options: {
272+
description: `Add or update a game's acheivement`,
273+
tags: ['api'],
274+
bind: {
275+
requiredCapabilities: [CAPABILITIES.GAMEDEV_ACTIONS],
276+
},
277+
pre: [
278+
{
279+
method: getAuthUser,
280+
assign: 'getAuthUser',
281+
},
282+
],
283+
validate: {
284+
payload: postAcheivementRequestSchema,
285+
},
286+
response: {
287+
schema: postAcheivementResponseSchema,
288+
},
289+
},
290+
handler: upsertAcheivementHandler,
291+
};
292+
293+
export const deleteAcheivementsRoute: ServerRoute = {
294+
method: 'DELETE',
295+
path: '/dashboard/game-dev/games/{gameTypeId}/acheivements/{acheivementId}',
296+
options: {
297+
description: `Delete a game's acheivement`,
298+
tags: ['api'],
299+
bind: {
300+
requiredCapabilities: [CAPABILITIES.GAMEDEV_ACTIONS],
301+
},
302+
pre: [
303+
{
304+
method: getAuthUser,
305+
assign: 'getAuthUser',
306+
},
307+
],
308+
response: {
309+
schema: gamedevGenericSchema,
310+
},
311+
},
312+
handler: deleteAcheivementHandler,
313+
};
314+
214315
export const gameDevRoutes: ServerRoute[] = [
215316
// 🎮 Games
216317
getGameTypesRoute,
@@ -221,4 +322,8 @@ export const gameDevRoutes: ServerRoute[] = [
221322
getLeaderboardByIdRoute,
222323
upsertLeaderboardRoute,
223324
deleteLeaderboardRoute,
325+
getAcheivementsRoute,
326+
getAcheivementsByIdRoute,
327+
upsertAcheivementsRoute,
328+
deleteAcheivementsRoute,
224329
];

0 commit comments

Comments
 (0)