Skip to content

Commit b4bdc83

Browse files
Airplane (B12) (#25)
* update backend * mempercantik ui/ux * Delete file "tes" in src/api/game/game-list/airplane/tes * fix be * resolve * Fix lint * Fix * fix * fix be + remove hardcoded questions * fix * fix * draft fix * lint fix * lint fix * fix lint * fix bgt * fix --------- Co-authored-by: imdfauzan <imam.fauzan2204@gmail.com>
1 parent 87ad13d commit b4bdc83

14 files changed

Lines changed: 578 additions & 11 deletions

File tree

docker-compose.yml

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ services:
66
ports:
77
- 5432:5432
88
environment:
9-
POSTGRES_DB: FPPweb
10-
POSTGRES_USER: postgres
11-
POSTGRES_PASSWORD: paswd123
9+
POSTGRES_DB: ${POSTGRES_NAME}
10+
POSTGRES_USER: ${POSTGRES_USER}
11+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
1212
volumes:
13-
- ./docker/volumes/:/var/lib/postgresql
13+
- ./docker/volumes/:/var/lib/postgresql/data
1414
healthcheck:
15-
test: ['CMD-SHELL', 'pg_isready -U fp_user']
15+
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_NAME}"]
1616
interval: 10s
1717
timeout: 5s
1818
retries: 5
@@ -22,6 +22,3 @@ services:
2222
networks:
2323
fp-network:
2424
driver: bridge
25-
26-
volumes:
27-
mysqldata:

prisma/seeder/data/game-templates.data.csv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ a67b668c-e95d-483e-8d4e-385b050f5646,gameshow-quiz,"Gameshow Quiz","Lorem",theat
1818
a2678379-5c86-4f05-a10a-f37370732e1d,labelled-diagram,"Labelled Diagram","Lorem",pin,false,false
1919
ac8b43c2-1bee-4a3f-a9ad-b8aa6f8a8d10,true-or-false,"True or False","Lorem",book-open-check,true,false
2020
374043f0-8c5c-4c0e-9985-cb8e2ef68936,hangman,"Hangman","Lorem",skull,false,true
21-
a947e0b8-f3f1-4c20-af14-63ac04c8ed8d,airplane,"Airplane","Lorem",plane,false,true
21+
a947e0b8-f3f1-4c20-af14-63ac04c8ed8d,airplane,"Airplane","Kendalikan pesawat ke jawaban benar. Hindari jawaban salah.",plane,false,true
2222
9803ce6b-8d0c-47fe-8306-9411e3399665,whack-a-mole,"Whack-a-Mole","Lorem",gavel,true,false
2323
9d13d300-fe45-4fa3-88d3-4f944e1a7f01,baloon-pop,"Baloon Pop","Lorem",bow-arrow,true,false
2424
b7efa7d0-fe73-4a5b-80d8-953cee5a39dc,image-quiz,"Image Quiz","Lorem",aperture,true,false
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const seedAirplaneGame = async () => {};

prisma/seeder/seeder.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
userSeed,
88
whackAMoleSeed,
99
} from './seed';
10+
import { seedAirplaneGame } from './seed/airplane.seed';
1011

1112
const prisma = new PrismaClient();
1213

@@ -17,9 +18,12 @@ async function main() {
1718
await userSeed(process.env.NODE_ENV === 'production');
1819
await gameTemplateSeed();
1920
await quizSeed();
21+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
2022
await spinTheWheelSeed();
23+
2124
await whackAMoleSeed();
22-
} catch (error) {
25+
await seedAirplaneGame();
26+
} catch (error: unknown) {
2327
console.error('⛔ Seeding error:', error);
2428
process.exit(1);
2529
} finally {
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
import { Prisma } from '@prisma/client';
2+
import { type NextFunction, type Request, type Response } from 'express';
3+
import { StatusCodes } from 'http-status-codes';
4+
5+
import { ErrorResponse } from '../../../../common/response';
6+
import { AirplaneService } from './airplane.service';
7+
8+
interface RequestWithUser extends Request {
9+
user?: {
10+
user_id?: string;
11+
id?: string;
12+
sub?: string;
13+
};
14+
}
15+
16+
interface CreateGameBody {
17+
title: string;
18+
description: string;
19+
game_data?: string | object;
20+
}
21+
22+
interface UpdateGameBody {
23+
title?: string;
24+
description?: string;
25+
game_data?: string | object;
26+
is_published?: string | boolean;
27+
is_publish?: string | boolean;
28+
}
29+
30+
export class AirplaneController {
31+
static async create(
32+
request: Request,
33+
response: Response,
34+
next: NextFunction,
35+
) {
36+
try {
37+
const { user } = request as RequestWithUser;
38+
const creatorId = user?.user_id || user?.id || user?.sub;
39+
40+
if (!creatorId) {
41+
throw new ErrorResponse(
42+
StatusCodes.UNAUTHORIZED,
43+
'Unauthorized: No User ID',
44+
);
45+
}
46+
47+
const thumbnailFile = request.file;
48+
49+
if (!thumbnailFile) {
50+
throw new ErrorResponse(
51+
StatusCodes.BAD_REQUEST,
52+
'Thumbnail image is required',
53+
);
54+
}
55+
56+
const body = request.body as CreateGameBody;
57+
const { title, description } = body;
58+
59+
let gameDataPayload: Prisma.JsonObject = {};
60+
61+
try {
62+
const rawGameData = body.game_data;
63+
64+
if (typeof rawGameData === 'string') {
65+
gameDataPayload = JSON.parse(rawGameData) as Prisma.JsonObject;
66+
} else if (typeof rawGameData === 'object') {
67+
gameDataPayload = rawGameData as Prisma.JsonObject;
68+
}
69+
} catch {
70+
gameDataPayload = {};
71+
}
72+
73+
const result = await AirplaneService.create(
74+
{
75+
title,
76+
description,
77+
game_data: gameDataPayload,
78+
thumbnail_image: thumbnailFile,
79+
},
80+
creatorId,
81+
);
82+
83+
response.status(StatusCodes.CREATED).json({
84+
success: true,
85+
message: 'Airplane game created successfully',
86+
data: result,
87+
});
88+
} catch (error) {
89+
if (
90+
error instanceof Prisma.PrismaClientKnownRequestError &&
91+
error.code === 'P2002'
92+
) {
93+
return response.status(StatusCodes.CONFLICT).json({
94+
status: false,
95+
message:
96+
'Game with this title already exists. Please choose another title.',
97+
});
98+
}
99+
100+
next(error);
101+
}
102+
}
103+
104+
static async findAll(
105+
request: Request,
106+
response: Response,
107+
next: NextFunction,
108+
) {
109+
try {
110+
const result = await AirplaneService.findAll(request);
111+
response.status(StatusCodes.OK).json(result);
112+
} catch (error) {
113+
next(error);
114+
}
115+
}
116+
117+
static async findOne(
118+
request: Request,
119+
response: Response,
120+
next: NextFunction,
121+
) {
122+
try {
123+
const result = await AirplaneService.findOne(request.params.id);
124+
response.status(StatusCodes.OK).json({ success: true, data: result });
125+
} catch (error) {
126+
next(error);
127+
}
128+
}
129+
130+
static async update(
131+
request: Request,
132+
response: Response,
133+
next: NextFunction,
134+
) {
135+
try {
136+
const { user } = request as RequestWithUser;
137+
const creatorId = user?.user_id || user?.id;
138+
139+
const thumbnailFile = request.file;
140+
141+
let gameDataPayload: Prisma.JsonObject | undefined;
142+
const body = request.body as UpdateGameBody;
143+
144+
if (body.game_data) {
145+
try {
146+
gameDataPayload =
147+
typeof body.game_data === 'string'
148+
? (JSON.parse(body.game_data) as Prisma.JsonObject)
149+
: (body.game_data as Prisma.JsonObject);
150+
} catch {
151+
// Ignore
152+
}
153+
}
154+
155+
let isPublished: boolean | undefined;
156+
157+
if (body.is_published !== undefined) {
158+
isPublished = String(body.is_published) === 'true';
159+
} else if (body.is_publish !== undefined) {
160+
isPublished = String(body.is_publish) === 'true';
161+
}
162+
163+
const payload = {
164+
title: body.title,
165+
description: body.description,
166+
game_data: gameDataPayload,
167+
is_published: isPublished,
168+
};
169+
170+
const result = await AirplaneService.update(
171+
request.params.id,
172+
payload,
173+
creatorId as string,
174+
thumbnailFile,
175+
);
176+
response.status(StatusCodes.OK).json({
177+
success: true,
178+
message: 'Game updated successfully',
179+
data: result,
180+
});
181+
} catch (error) {
182+
next(error);
183+
}
184+
}
185+
186+
static async delete(
187+
request: Request,
188+
response: Response,
189+
next: NextFunction,
190+
) {
191+
try {
192+
const { user } = request as RequestWithUser;
193+
const creatorId = user?.user_id || user?.id;
194+
195+
await AirplaneService.delete(request.params.id, creatorId as string);
196+
response
197+
.status(StatusCodes.OK)
198+
.json({ success: true, message: 'Game deleted successfully' }); //delete
199+
} catch (error) {
200+
next(error);
201+
}
202+
}
203+
204+
static async play(request: Request, response: Response, next: NextFunction) {
205+
try {
206+
await AirplaneService.play(request.params.id);
207+
response
208+
.status(StatusCodes.OK)
209+
.json({ success: true, message: 'Play count updated' });
210+
} catch (error) {
211+
next(error);
212+
}
213+
}
214+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { Router } from 'express';
2+
3+
import { validateAuth } from '../../../../common/middleware';
4+
import { upload } from '../../../../common/middleware/upload.middleware';
5+
import { AirplaneController } from './airplane.controller';
6+
7+
const airplaneRouter = Router();
8+
9+
airplaneRouter.get('/', (request, response, next) =>
10+
AirplaneController.findAll(request, response, next),
11+
);
12+
13+
airplaneRouter.get('/:id', (request, response, next) =>
14+
AirplaneController.findOne(request, response, next),
15+
);
16+
17+
airplaneRouter.post(
18+
'/',
19+
validateAuth({}),
20+
upload.single('thumbnail_image'),
21+
(request, response, next) =>
22+
AirplaneController.create(request, response, next),
23+
);
24+
25+
airplaneRouter.put(
26+
'/:id',
27+
validateAuth({}),
28+
upload.single('thumbnail_image'),
29+
(request, response, next) =>
30+
AirplaneController.update(request, response, next),
31+
);
32+
33+
airplaneRouter.patch(
34+
'/:id',
35+
validateAuth({}),
36+
upload.single('thumbnail_image'),
37+
(request, response, next) =>
38+
AirplaneController.update(request, response, next),
39+
);
40+
41+
airplaneRouter.delete('/:id', validateAuth({}), (request, response, next) =>
42+
AirplaneController.delete(request, response, next),
43+
);
44+
45+
// Endpoint Play Count (Public)
46+
airplaneRouter.post('/:id/play', (request, response, next) =>
47+
AirplaneController.play(request, response, next),
48+
);
49+
50+
// eslint-disable-next-line import/no-default-export
51+
export default airplaneRouter;

0 commit comments

Comments
 (0)