diff --git a/.gitignore b/.gitignore index 6661960e..8ab5370a 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ node_modules /.idea/* !/.idea/dataSources.xml !/.idea/sqldialects.xml +!/.idea/icon.svg .project .classpath .c9/ diff --git a/.idea/icon.svg b/.idea/icon.svg new file mode 100644 index 00000000..526d19f0 --- /dev/null +++ b/.idea/icon.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.skip b/.skip index 737179ea..a16b1d51 100644 --- a/.skip +++ b/.skip @@ -43,7 +43,9 @@ apps/client/src/manifest.webmanifest apps/client/src/polyfills.ts apps/client/src/test-setup.ts apps/client/ngsw-config.json -apps/client/src/assets/icons +apps/client/src/assets/icons/pwa +apps/client/src/assets/icons/fuzzy-waddle.svg +apps/client/src/assets/icons/social-preview.png apps/client/src/**/*.html apps/client/src/**/*.scss apps/client/src/**/*.spec.ts diff --git a/apps/api/.eslintrc.json b/apps/api/.eslintrc.json index 2ec50b22..7eaffdf1 100644 --- a/apps/api/.eslintrc.json +++ b/apps/api/.eslintrc.json @@ -3,12 +3,11 @@ "ignorePatterns": ["!**/*"], "overrides": [ { - "files": [ - "*.ts" - ], + "files": ["*.ts"], "rules": { "@typescript-eslint/no-explicit-any": "warn", - "@typescript-eslint/no-unused-vars": "warn" + "@typescript-eslint/no-unused-vars": "warn", + "no-case-declarations": "off" } } ] diff --git a/apps/api/jest.config.ts b/apps/api/jest.config.ts index 055496fa..37748264 100644 --- a/apps/api/jest.config.ts +++ b/apps/api/jest.config.ts @@ -1,11 +1,11 @@ /* eslint-disable */ export default { - displayName: 'api', - preset: '../../jest.preset.js', - testEnvironment: 'node', + displayName: "api", + preset: "../../jest.preset.js", + testEnvironment: "node", transform: { - '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }] + "^.+\\.[tj]s$": ["ts-jest", { tsconfig: "/tsconfig.spec.json" }] }, - moduleFileExtensions: ['ts', 'js', 'html'], - coverageDirectory: '../../coverage/apps/api' + moduleFileExtensions: ["ts", "js", "html"], + coverageDirectory: "../../coverage/apps/api" }; diff --git a/apps/api/src/app/app.controller.spec.ts b/apps/api/src/app/app.controller.spec.ts index 9d1e93ac..0872c20f 100644 --- a/apps/api/src/app/app.controller.spec.ts +++ b/apps/api/src/app/app.controller.spec.ts @@ -1,8 +1,8 @@ -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, TestingModule } from "@nestjs/testing"; -import { AppController } from './app.controller'; +import { AppController } from "./app.controller"; -describe('AppController', () => { +describe("AppController", () => { let app: TestingModule; beforeAll(async () => { @@ -11,10 +11,10 @@ describe('AppController', () => { }).compile(); }); - describe('getHealth', () => { + describe("getHealth", () => { it('should return "OK"', () => { const appController = app.get(AppController); - expect(appController.getHealth()).toBe('OK'); + expect(appController.getHealth()).toBe("OK"); }); }); }); diff --git a/apps/api/src/app/app.controller.ts b/apps/api/src/app/app.controller.ts index d3f7ddf1..2cd3daaf 100644 --- a/apps/api/src/app/app.controller.ts +++ b/apps/api/src/app/app.controller.ts @@ -1,10 +1,10 @@ -import { Controller, Get } from '@nestjs/common'; +import { Controller, Get } from "@nestjs/common"; @Controller() export class AppController { // health endpoint always returns OK used for monitoring for zero downtime deploys - @Get('health') + @Get("health") getHealth(): string { - return 'OK'; + return "OK"; } } diff --git a/apps/api/src/app/app.module.ts b/apps/api/src/app/app.module.ts index 75c0e36f..f073000a 100644 --- a/apps/api/src/app/app.module.ts +++ b/apps/api/src/app/app.module.ts @@ -3,13 +3,13 @@ import { Module } from "@nestjs/common"; import { AppController } from "./app.controller"; import { AuthModule } from "../auth/auth.module"; import { ConfigModule } from "@nestjs/config"; -import { EventsModule } from "./events/events.module"; import { ChatModule } from "./chat/chat.module"; import { GameSessionModule } from "./game-session/game-session.module"; import { LittleMuncherModule } from "./little-muncher/little-muncher.module"; import { APP_GUARD } from "@nestjs/core"; import { ThrottlerGuard, ThrottlerModule } from "@nestjs/throttler"; import { FlySquasherModule } from "./fly-squasher/fly-squasher.module"; +import { ProbableWaffleModule } from "./probable-waffle/probable-waffle.module"; @Module({ imports: [ @@ -23,11 +23,11 @@ import { FlySquasherModule } from "./fly-squasher/fly-squasher.module"; } ]), AuthModule, - EventsModule, ChatModule, GameSessionModule, LittleMuncherModule, - FlySquasherModule + FlySquasherModule, + ProbableWaffleModule ], controllers: [AppController], providers: [ diff --git a/apps/api/src/app/chat/chat.controller.spec.ts b/apps/api/src/app/chat/chat.controller.spec.ts index ce1f803d..4497acb1 100644 --- a/apps/api/src/app/chat/chat.controller.spec.ts +++ b/apps/api/src/app/chat/chat.controller.spec.ts @@ -1,11 +1,11 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { ChatService } from './chat.service'; -import { chatServiceStub } from './chat.service.spec'; -import { ChatController } from './chat.controller'; -import { MessageDto } from './message.dto'; -import { authUserStub } from '../../auth/auth.service.spec'; +import { Test, TestingModule } from "@nestjs/testing"; +import { ChatService } from "./chat.service"; +import { chatServiceStub } from "./chat.service.spec"; +import { ChatController } from "./chat.controller"; +import { MessageDto } from "./message.dto"; +import { authUserStub } from "../../auth/auth.service.spec"; -describe('ChatController', () => { +describe("ChatController", () => { let app: TestingModule; beforeAll(async () => { @@ -15,11 +15,11 @@ describe('ChatController', () => { }).compile(); }); - describe('postMessage', () => { - it('should return void', async () => { + describe("postMessage", () => { + it("should return void", async () => { const chatController = app.get(ChatController); const user = authUserStub; - const messageDto: MessageDto = { message: 'test' }; + const messageDto: MessageDto = { message: "test" }; const result = await chatController.postMessage(user, messageDto); expect(result).toBeUndefined(); }); diff --git a/apps/api/src/app/chat/chat.controller.ts b/apps/api/src/app/chat/chat.controller.ts index 2c28d507..10907166 100644 --- a/apps/api/src/app/chat/chat.controller.ts +++ b/apps/api/src/app/chat/chat.controller.ts @@ -1,15 +1,14 @@ -import { Body, Controller, Post, UseGuards } from '@nestjs/common'; -import { ChatService } from './chat.service'; -import { SupabaseAuthGuard } from '../../auth/guards/supabase-auth.guard'; -import { CurrentUser } from '../../auth/current-user'; -import { AuthUser } from '@supabase/supabase-js'; -import { MessageDto } from './message.dto'; +import { Body, Controller, Post, UseGuards } from "@nestjs/common"; +import { ChatService } from "./chat.service"; +import { SupabaseAuthGuard } from "../../auth/guards/supabase-auth.guard"; +import { CurrentUser } from "../../auth/current-user"; +import { AuthUser } from "@supabase/supabase-js"; +import { MessageDto } from "./message.dto"; @Controller() export class ChatController { constructor(private readonly chatService: ChatService) {} - - @Post('message') + @Post("message") @UseGuards(SupabaseAuthGuard) async postMessage(@CurrentUser() user: AuthUser, @Body() body: MessageDto): Promise { await this.chatService.postMessage(body.message, user); diff --git a/apps/api/src/app/events/events.gateway.ts b/apps/api/src/app/chat/chat.gateway.ts similarity index 51% rename from apps/api/src/app/events/events.gateway.ts rename to apps/api/src/app/chat/chat.gateway.ts index 2bd115ed..7e65ea83 100644 --- a/apps/api/src/app/events/events.gateway.ts +++ b/apps/api/src/app/chat/chat.gateway.ts @@ -1,41 +1,34 @@ -import { ConnectedSocket, MessageBody, SubscribeMessage, WebSocketGateway, WebSocketServer } from '@nestjs/websockets'; -import { CurrentUser } from '../../auth/current-user'; -import { AuthUser } from '@supabase/supabase-js'; -import { UseGuards } from '@nestjs/common'; -import { SupabaseAuthGuard } from '../../auth/guards/supabase-auth.guard'; -import { ChatService } from '../chat/chat.service'; -import { Server } from 'net'; -import { ChatMessage, GatewayChatEvent } from '@fuzzy-waddle/api-interfaces'; -import { MyConnectedSocket } from '../little-muncher/game-instance/game-state.gateway'; +import { ConnectedSocket, MessageBody, SubscribeMessage, WebSocketGateway, WebSocketServer } from "@nestjs/websockets"; +import { CurrentUser } from "../../auth/current-user"; +import { AuthUser } from "@supabase/supabase-js"; +import { UseGuards } from "@nestjs/common"; +import { SupabaseAuthGuard } from "../../auth/guards/supabase-auth.guard"; +import { ChatService } from "../chat/chat.service"; +import { ChatMessage, GatewayChatEvent } from "@fuzzy-waddle/api-interfaces"; +import { Server, Socket } from "socket.io"; @WebSocketGateway({ cors: { - origin: process.env.CORS_ORIGIN?.split(',') + origin: process.env.CORS_ORIGIN?.split(",") } }) -export class EventsGateway { - @WebSocketServer() - private server: Server; +export class ChatGateway { + @WebSocketServer() private readonly server: Server; constructor(private readonly chatService: ChatService) {} - - // @SubscribeMessage(GatewayEvent.CHAT_MESSAGE) - // findAll(@MessageBody() data: ChatMessage): Observable> { - // return from([data]).pipe(map(item => ({ event: GatewayEvent.CHAT_MESSAGE, data: item }))); - // } - //subscribe to chat message and broadcast to all clients @UseGuards(SupabaseAuthGuard) @SubscribeMessage(GatewayChatEvent.CHAT_MESSAGE) async broadcastMessage( @CurrentUser() user: AuthUser, @MessageBody() payload: ChatMessage, - @ConnectedSocket() client: MyConnectedSocket + @ConnectedSocket() socket: Socket ) { // clone the payload const newPayload = { ...payload }; // post to supabase + // noinspection UnnecessaryLocalVariableJS const sanitizedMessage = await this.chatService.postMessage(newPayload.text, user); // todo for demo newPayload.text = sanitizedMessage; diff --git a/apps/api/src/app/chat/chat.module.ts b/apps/api/src/app/chat/chat.module.ts index b1d0f382..ebf0c045 100644 --- a/apps/api/src/app/chat/chat.module.ts +++ b/apps/api/src/app/chat/chat.module.ts @@ -1,11 +1,12 @@ -import { Module } from '@nestjs/common'; -import { ChatService } from './chat.service'; -import { ChatController } from './chat.controller'; -import { SupabaseProviderService } from '../../core/supabase-provider/supabase-provider.service'; -import { TextSanitizationService } from '../../core/content-filters/text-sanitization.service'; +import { Module } from "@nestjs/common"; +import { ChatService } from "./chat.service"; +import { ChatController } from "./chat.controller"; +import { SupabaseProviderService } from "../../core/supabase-provider/supabase-provider.service"; +import { TextSanitizationService } from "../../core/content-filters/text-sanitization.service"; +import { ChatGateway } from "./chat.gateway"; @Module({ - providers: [ChatService, SupabaseProviderService, TextSanitizationService], + providers: [ChatService, SupabaseProviderService, TextSanitizationService, ChatGateway], controllers: [ChatController], exports: [ChatService] }) diff --git a/apps/api/src/app/chat/chat.service.interface.ts b/apps/api/src/app/chat/chat.service.interface.ts index c50a2b43..49edae00 100644 --- a/apps/api/src/app/chat/chat.service.interface.ts +++ b/apps/api/src/app/chat/chat.service.interface.ts @@ -1,4 +1,4 @@ -import { AuthUser } from '@supabase/supabase-js'; +import { AuthUser } from "@supabase/supabase-js"; export interface IChatService { postMessage(text: string, user: AuthUser): Promise; diff --git a/apps/api/src/app/chat/chat.service.ts b/apps/api/src/app/chat/chat.service.ts index 1a34d907..d524d6a4 100644 --- a/apps/api/src/app/chat/chat.service.ts +++ b/apps/api/src/app/chat/chat.service.ts @@ -1,14 +1,14 @@ -import { Injectable } from '@nestjs/common'; -import { IChatService } from './chat.service.interface'; -import { AuthUser } from '@supabase/supabase-js'; -import { SupabaseProviderService } from '../../core/supabase-provider/supabase-provider.service'; -import { TextSanitizationService } from '../../core/content-filters/text-sanitization.service'; +import { Injectable } from "@nestjs/common"; +import { IChatService } from "./chat.service.interface"; +import { AuthUser } from "@supabase/supabase-js"; +import { SupabaseProviderService } from "../../core/supabase-provider/supabase-provider.service"; +import { TextSanitizationService } from "../../core/content-filters/text-sanitization.service"; @Injectable() export class ChatService implements IChatService { constructor( - private supabaseProviderService: SupabaseProviderService, - private textSanitizationService: TextSanitizationService + private readonly supabaseProviderService: SupabaseProviderService, + private readonly textSanitizationService: TextSanitizationService ) {} /** @@ -17,7 +17,7 @@ export class ChatService implements IChatService { async postMessage(text: string, user: AuthUser): Promise { const sanitizedMessage = this.textSanitizationService.cleanBadWords(text); // Insert sanitized message into Messages table - const { data, error } = await this.supabaseProviderService.supabaseClient.from('test').insert({ + const { data, error } = await this.supabaseProviderService.supabaseClient.from("test").insert({ text: sanitizedMessage, user_id: user.id }); diff --git a/apps/api/src/app/chat/message.dto.ts b/apps/api/src/app/chat/message.dto.ts index c517721f..a59d8ed2 100644 --- a/apps/api/src/app/chat/message.dto.ts +++ b/apps/api/src/app/chat/message.dto.ts @@ -1,4 +1,4 @@ -import { IsNotEmpty, IsString } from 'class-validator'; +import { IsNotEmpty, IsString } from "class-validator"; export class MessageDto { @IsString() diff --git a/apps/api/src/app/events/events.module.ts b/apps/api/src/app/events/events.module.ts deleted file mode 100644 index 0bb51f85..00000000 --- a/apps/api/src/app/events/events.module.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Module } from '@nestjs/common'; -import { EventsGateway } from './events.gateway'; -import { ChatModule } from '../chat/chat.module'; - -@Module({ - providers: [EventsGateway], - imports: [ChatModule] -}) -export class EventsModule {} diff --git a/apps/api/src/app/game-session/game-session.module.ts b/apps/api/src/app/game-session/game-session.module.ts index 580c8114..34d1bdfb 100644 --- a/apps/api/src/app/game-session/game-session.module.ts +++ b/apps/api/src/app/game-session/game-session.module.ts @@ -1,5 +1,5 @@ -import { Module } from '@nestjs/common'; -import { RoomModule } from './room/room.module'; +import { Module } from "@nestjs/common"; +import { RoomModule } from "./room/room.module"; @Module({ imports: [RoomModule] diff --git a/apps/api/src/app/game-session/room/room.controller.spec.ts b/apps/api/src/app/game-session/room/room.controller.spec.ts index 68088d24..211e5192 100644 --- a/apps/api/src/app/game-session/room/room.controller.spec.ts +++ b/apps/api/src/app/game-session/room/room.controller.spec.ts @@ -1,8 +1,8 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { RoomController } from './room.controller'; -import { RoomService, roomServiceStub } from './room.service'; +import { Test, TestingModule } from "@nestjs/testing"; +import { RoomController } from "./room.controller"; +import { RoomService, roomServiceStub } from "./room.service"; -describe('RoomController', () => { +describe("RoomController", () => { let controller: RoomController; beforeEach(async () => { @@ -14,7 +14,7 @@ describe('RoomController', () => { controller = module.get(RoomController); }); - it('should be defined', () => { + it("should be defined", () => { expect(controller).toBeDefined(); }); }); diff --git a/apps/api/src/app/game-session/room/room.controller.ts b/apps/api/src/app/game-session/room/room.controller.ts index ea80c653..88bc0921 100644 --- a/apps/api/src/app/game-session/room/room.controller.ts +++ b/apps/api/src/app/game-session/room/room.controller.ts @@ -1,15 +1,15 @@ -import { Body, Controller, Post, UseGuards } from '@nestjs/common'; -import { RoomService } from './room.service'; -import { RoomDto } from './room.dto'; -import { CurrentUser } from '../../../auth/current-user'; -import { AuthUser } from '@supabase/supabase-js'; -import { SupabaseAuthGuard } from '../../../auth/guards/supabase-auth.guard'; +import { Body, Controller, Post, UseGuards } from "@nestjs/common"; +import { RoomService } from "./room.service"; +import { RoomDto } from "./room.dto"; +import { CurrentUser } from "../../../auth/current-user"; +import { AuthUser } from "@supabase/supabase-js"; +import { SupabaseAuthGuard } from "../../../auth/guards/supabase-auth.guard"; -@Controller('room') +@Controller("room") export class RoomController { constructor(private readonly roomService: RoomService) {} - @Post('room') + @Post("room") @UseGuards(SupabaseAuthGuard) async createRoom(@CurrentUser() user: AuthUser, @Body() body: RoomDto): Promise { return this.roomService.createRoom(body, user); diff --git a/apps/api/src/app/game-session/room/room.dto.ts b/apps/api/src/app/game-session/room/room.dto.ts index c421d525..ac46251f 100644 --- a/apps/api/src/app/game-session/room/room.dto.ts +++ b/apps/api/src/app/game-session/room/room.dto.ts @@ -1,4 +1,4 @@ -import { IsNotEmpty, IsString } from 'class-validator'; +import { IsNotEmpty, IsString } from "class-validator"; export class RoomDto { @IsString() diff --git a/apps/api/src/app/game-session/room/room.module.ts b/apps/api/src/app/game-session/room/room.module.ts index c22bbc87..48d98242 100644 --- a/apps/api/src/app/game-session/room/room.module.ts +++ b/apps/api/src/app/game-session/room/room.module.ts @@ -1,6 +1,6 @@ -import { Module } from '@nestjs/common'; -import { RoomService } from './room.service'; -import { SupabaseProviderService } from '../../../core/supabase-provider/supabase-provider.service'; +import { Module } from "@nestjs/common"; +import { RoomService } from "./room.service"; +import { SupabaseProviderService } from "../../../core/supabase-provider/supabase-provider.service"; @Module({ providers: [RoomService, SupabaseProviderService] }) export class RoomModule {} diff --git a/apps/api/src/app/game-session/room/room.service.interface.ts b/apps/api/src/app/game-session/room/room.service.interface.ts index e1ec16d6..0fa13460 100644 --- a/apps/api/src/app/game-session/room/room.service.interface.ts +++ b/apps/api/src/app/game-session/room/room.service.interface.ts @@ -1,5 +1,5 @@ -import { RoomDto } from './room.dto'; -import { AuthUser } from '@supabase/supabase-js'; +import { RoomDto } from "./room.dto"; +import { AuthUser } from "@supabase/supabase-js"; export interface IRoomService { createRoom(body: RoomDto, user: AuthUser): Promise; diff --git a/apps/api/src/app/game-session/room/room.service.spec.ts b/apps/api/src/app/game-session/room/room.service.spec.ts index 9d350b04..04d8dd8f 100644 --- a/apps/api/src/app/game-session/room/room.service.spec.ts +++ b/apps/api/src/app/game-session/room/room.service.spec.ts @@ -1,9 +1,9 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { RoomService } from './room.service'; -import { SupabaseProviderService } from '../../../core/supabase-provider/supabase-provider.service'; -import { supabaseProviderServiceStub } from '../../../core/supabase-provider/supabase-provider.service.spec'; +import { Test, TestingModule } from "@nestjs/testing"; +import { RoomService } from "./room.service"; +import { SupabaseProviderService } from "../../../core/supabase-provider/supabase-provider.service"; +import { supabaseProviderServiceStub } from "../../../core/supabase-provider/supabase-provider.service.spec"; -describe('RoomService', () => { +describe("RoomService", () => { let service: RoomService; beforeEach(async () => { @@ -14,7 +14,7 @@ describe('RoomService', () => { service = module.get(RoomService); }); - it('should be defined', () => { + it("should be defined", () => { expect(service).toBeDefined(); }); }); diff --git a/apps/api/src/app/little-muncher/game-instance/game-instance.controller.spec.ts b/apps/api/src/app/little-muncher/game-instance/game-instance.controller.spec.ts index 8a5427ed..b1b4ce67 100644 --- a/apps/api/src/app/little-muncher/game-instance/game-instance.controller.spec.ts +++ b/apps/api/src/app/little-muncher/game-instance/game-instance.controller.spec.ts @@ -1,9 +1,9 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { GameInstanceController } from './game-instance.controller'; -import { GameInstanceService } from './game-instance.service'; -import { GameInstanceServiceStub } from './game-instance.service.spec'; +import { Test, TestingModule } from "@nestjs/testing"; +import { GameInstanceController } from "./game-instance.controller"; +import { GameInstanceService } from "./game-instance.service"; +import { GameInstanceServiceStub } from "./game-instance.service.spec"; -describe('GameInstanceController', () => { +describe("GameInstanceController", () => { let controller: GameInstanceController; beforeEach(async () => { @@ -15,7 +15,7 @@ describe('GameInstanceController', () => { controller = module.get(GameInstanceController); }); - it('should be defined', () => { + it("should be defined", () => { expect(controller).toBeDefined(); }); }); diff --git a/apps/api/src/app/little-muncher/game-instance/game-instance.controller.ts b/apps/api/src/app/little-muncher/game-instance/game-instance.controller.ts index e1d4226b..e4603a84 100644 --- a/apps/api/src/app/little-muncher/game-instance/game-instance.controller.ts +++ b/apps/api/src/app/little-muncher/game-instance/game-instance.controller.ts @@ -1,44 +1,44 @@ -import { Body, Controller, Delete, Get, Post, UseGuards } from '@nestjs/common'; -import { SupabaseAuthGuard } from '../../../auth/guards/supabase-auth.guard'; -import { CurrentUser } from '../../../auth/current-user'; -import { AuthUser } from '@supabase/supabase-js'; -import { GameInstanceService } from './game-instance.service'; +import { Body, Controller, Delete, Get, Post, UseGuards } from "@nestjs/common"; +import { SupabaseAuthGuard } from "../../../auth/guards/supabase-auth.guard"; +import { CurrentUser } from "../../../auth/current-user"; +import { AuthUser } from "@supabase/supabase-js"; +import { GameInstanceService } from "./game-instance.service"; import { GameInstanceDataDto, LittleMuncherGameCreateDto, LittleMuncherGameInstanceData, - Room -} from '@fuzzy-waddle/api-interfaces'; + LittleMuncherRoom +} from "@fuzzy-waddle/api-interfaces"; -@Controller('little-muncher') +@Controller("little-muncher") export class GameInstanceController { constructor(private readonly gameInstanceService: GameInstanceService) {} - @Post('start-game') + @Post("start-game") @UseGuards(SupabaseAuthGuard) async startGame(@CurrentUser() user: AuthUser, @Body() body: GameInstanceDataDto): Promise { await this.gameInstanceService.startGame(body, user); } - @Delete('stop-game') + @Delete("stop-game") @UseGuards(SupabaseAuthGuard) async stopGame(@CurrentUser() user: AuthUser, @Body() body: GameInstanceDataDto): Promise { await this.gameInstanceService.stopGame(body, user); } - @Post('start-level') + @Post("start-level") @UseGuards(SupabaseAuthGuard) async createGameMode(@CurrentUser() user: AuthUser, @Body() body: LittleMuncherGameCreateDto): Promise { await this.gameInstanceService.startLevel(body, user); } - @Delete('stop-level') + @Delete("stop-level") @UseGuards(SupabaseAuthGuard) async deleteGameMode(@CurrentUser() user: AuthUser, @Body() body: GameInstanceDataDto): Promise { await this.gameInstanceService.stopLevel(body, user); } - @Post('spectator-join') + @Post("spectator-join") @UseGuards(SupabaseAuthGuard) async spectatorJoin( @CurrentUser() user: AuthUser, @@ -47,15 +47,15 @@ export class GameInstanceController { return await this.gameInstanceService.spectatorJoined(body, user); } - @Delete('spectator-leave') + @Delete("spectator-leave") @UseGuards(SupabaseAuthGuard) async spectatorLeave(@CurrentUser() user: AuthUser, @Body() body: GameInstanceDataDto): Promise { await this.gameInstanceService.spectatorLeft(body, user); } - @Get('get-rooms') + @Get("get-rooms") @UseGuards(SupabaseAuthGuard) - async getRooms(@CurrentUser() user: AuthUser): Promise { + async getRooms(@CurrentUser() user: AuthUser): Promise { return await this.gameInstanceService.getSpectatorRooms(user); } } diff --git a/apps/api/src/app/little-muncher/game-instance/game-instance.gateway.interface.ts b/apps/api/src/app/little-muncher/game-instance/game-instance.gateway.interface.ts index 969c151b..18e7de75 100644 --- a/apps/api/src/app/little-muncher/game-instance/game-instance.gateway.interface.ts +++ b/apps/api/src/app/little-muncher/game-instance/game-instance.gateway.interface.ts @@ -1,7 +1,7 @@ -import { RoomEvent, SpectatorEvent } from '@fuzzy-waddle/api-interfaces'; +import { LittleMuncherRoomEvent, LittleMuncherSpectatorEvent } from "@fuzzy-waddle/api-interfaces"; export interface GameInstanceGatewayInterface { - emitRoom(roomEvent: RoomEvent); + emitRoom(roomEvent: LittleMuncherRoomEvent); - emitSpectator(spectatorEvent: SpectatorEvent); + emitSpectator(spectatorEvent: LittleMuncherSpectatorEvent); } diff --git a/apps/api/src/app/little-muncher/game-instance/game-instance.gateway.ts b/apps/api/src/app/little-muncher/game-instance/game-instance.gateway.ts index 7673873c..829bad11 100644 --- a/apps/api/src/app/little-muncher/game-instance/game-instance.gateway.ts +++ b/apps/api/src/app/little-muncher/game-instance/game-instance.gateway.ts @@ -1,18 +1,17 @@ import { WebSocketGateway, WebSocketServer } from "@nestjs/websockets"; import { Server } from "net"; import { - GatewaySpectatorEvent, LittleMuncherGatewayEvent, - RoomEvent, - SpectatorEvent + LittleMuncherRoomEvent, + LittleMuncherSpectatorEvent } from "@fuzzy-waddle/api-interfaces"; import { GameInstanceGatewayInterface } from "./game-instance.gateway.interface"; export const GameInstanceGatewayStub = { - emitRoom(roomEvent: RoomEvent) { + emitRoom(roomEvent: LittleMuncherRoomEvent) { // }, - emitSpectator(spectatorEvent: SpectatorEvent) { + emitSpectator(spectatorEvent: LittleMuncherSpectatorEvent) { // } } satisfies GameInstanceGatewayInterface; @@ -23,14 +22,13 @@ export const GameInstanceGatewayStub = { } }) export class GameInstanceGateway implements GameInstanceGatewayInterface { - @WebSocketServer() - private server: Server; + @WebSocketServer() private readonly server: Server; - emitRoom(roomEvent: RoomEvent) { + emitRoom(roomEvent: LittleMuncherRoomEvent) { this.server.emit(LittleMuncherGatewayEvent.LittleMuncherRoom, roomEvent); } - emitSpectator(spectatorEvent: SpectatorEvent) { - this.server.emit(GatewaySpectatorEvent.Spectator, spectatorEvent); + emitSpectator(spectatorEvent: LittleMuncherSpectatorEvent) { + // todo??? this.server.emit(LittleMuncherGatewayEvent.Spectator, spectatorEvent); } } diff --git a/apps/api/src/app/little-muncher/game-instance/game-instance.service.interface.ts b/apps/api/src/app/little-muncher/game-instance/game-instance.service.interface.ts index ee3f6e8f..80747058 100644 --- a/apps/api/src/app/little-muncher/game-instance/game-instance.service.interface.ts +++ b/apps/api/src/app/little-muncher/game-instance/game-instance.service.interface.ts @@ -3,13 +3,13 @@ import { LittleMuncherGameCreateDto, LittleMuncherGameInstance, LittleMuncherGameInstanceData, - Room, + LittleMuncherRoom, + LittleMuncherRoomEvent, + LittleMuncherSpectatorEvent, RoomAction, - RoomEvent, - SpectatorAction, - SpectatorEvent -} from '@fuzzy-waddle/api-interfaces'; -import { User } from '@supabase/supabase-js'; + SpectatorAction +} from "@fuzzy-waddle/api-interfaces"; +import { User } from "@supabase/supabase-js"; export interface GameInstanceServiceInterface { startGame(body: GameInstanceDataDto, user: User); @@ -18,9 +18,18 @@ export interface GameInstanceServiceInterface { spectatorJoined(body: GameInstanceDataDto, user: User): Promise; spectatorLeft(body: GameInstanceDataDto, user: User); stopLevel(body: GameInstanceDataDto, user: User); - getSpectatorRooms(user: User): Promise; - getGameInstanceToRoom(gameInstance: LittleMuncherGameInstance): Room; - getRoomEvent(gameInstance: LittleMuncherGameInstance, action: RoomAction): RoomEvent; - getSpectatorEvent(user: User, room: Room, action: SpectatorAction): SpectatorEvent; + + getSpectatorRooms(user: User): Promise; + + getGameInstanceToRoom(gameInstance: LittleMuncherGameInstance): LittleMuncherRoom; + + getRoomEvent(gameInstance: LittleMuncherGameInstance, action: RoomAction): LittleMuncherRoomEvent; + + getSpectatorEvent( + user: User, + room: LittleMuncherRoom, + gameInstanceId: string, + action: SpectatorAction + ): LittleMuncherSpectatorEvent; findGameInstance(gameInstanceId: string): LittleMuncherGameInstance | undefined; } diff --git a/apps/api/src/app/little-muncher/game-instance/game-instance.service.spec.ts b/apps/api/src/app/little-muncher/game-instance/game-instance.service.spec.ts index e717ad43..0ee080df 100644 --- a/apps/api/src/app/little-muncher/game-instance/game-instance.service.spec.ts +++ b/apps/api/src/app/little-muncher/game-instance/game-instance.service.spec.ts @@ -5,11 +5,11 @@ import { LittleMuncherGameCreateDto, LittleMuncherGameInstance, LittleMuncherGameInstanceData, - Room, + LittleMuncherRoom, + LittleMuncherRoomEvent, + LittleMuncherSpectatorEvent, RoomAction, - RoomEvent, - SpectatorAction, - SpectatorEvent + SpectatorAction } from "@fuzzy-waddle/api-interfaces"; import { GameInstanceServiceInterface } from "./game-instance.service.interface"; import { User } from "../../../users/users.service"; @@ -25,16 +25,16 @@ export const GameInstanceServiceStub = { stopGame(body: GameInstanceDataDto, user: User) { // }, - getGameInstanceToRoom(gameInstance: LittleMuncherGameInstance): Room { + getGameInstanceToRoom(gameInstance: LittleMuncherGameInstance): LittleMuncherRoom { return undefined; }, - getRoomEvent(gameInstance: LittleMuncherGameInstance, action: RoomAction): RoomEvent { + getRoomEvent(gameInstance: LittleMuncherGameInstance, action: RoomAction): LittleMuncherRoomEvent { return undefined; }, - getSpectatorEvent(user: User, room: Room, action: SpectatorAction): SpectatorEvent { + getSpectatorEvent(user: User, room: LittleMuncherRoom, action: SpectatorAction): LittleMuncherSpectatorEvent { return undefined; }, - getSpectatorRooms(user: User): Promise { + getSpectatorRooms(user: User): Promise { return undefined; }, spectatorJoined(body: GameInstanceDataDto, user: User): Promise { diff --git a/apps/api/src/app/little-muncher/game-instance/game-instance.service.ts b/apps/api/src/app/little-muncher/game-instance/game-instance.service.ts index 74b32deb..76d6cb6d 100644 --- a/apps/api/src/app/little-muncher/game-instance/game-instance.service.ts +++ b/apps/api/src/app/little-muncher/game-instance/game-instance.service.ts @@ -1,20 +1,20 @@ -import { Injectable } from '@nestjs/common'; -import { User } from '@supabase/supabase-js'; +import { Injectable } from "@nestjs/common"; +import { User } from "@supabase/supabase-js"; import { GameInstanceDataDto, GameSessionState, LittleMuncherGameCreateDto, LittleMuncherGameInstance, LittleMuncherGameInstanceData, - Room, + LittleMuncherRoom, + LittleMuncherRoomEvent, + LittleMuncherSpectatorEvent, RoomAction, - RoomEvent, - SpectatorAction, - SpectatorEvent -} from '@fuzzy-waddle/api-interfaces'; -import { Cron, CronExpression } from '@nestjs/schedule'; -import { GameInstanceGateway } from './game-instance.gateway'; -import { GameInstanceServiceInterface } from './game-instance.service.interface'; + SpectatorAction +} from "@fuzzy-waddle/api-interfaces"; +import { Cron, CronExpression } from "@nestjs/schedule"; +import { GameInstanceGateway } from "./game-instance.gateway"; +import { GameInstanceServiceInterface } from "./game-instance.service.interface"; @Injectable() export class GameInstanceService implements GameInstanceServiceInterface { @@ -25,10 +25,10 @@ export class GameInstanceService implements GameInstanceServiceInterface { async startGame(body: GameInstanceDataDto, user: User) { const newGameInstance = new LittleMuncherGameInstance({ gameInstanceMetadataData: { gameInstanceId: body.gameInstanceId, createdBy: user.id }, - players: [{ userId: user.id }] + players: [{ playerControllerData: { userId: user.id } }] }); this.openGameInstances.push(newGameInstance); - console.log('game instance created on server', this.openGameInstances.length); + console.log("Little Muncher - Game instance created on server. Open instances: " + this.openGameInstances.length); } async stopGame(body: GameInstanceDataDto, user: User) { @@ -38,7 +38,7 @@ export class GameInstanceService implements GameInstanceServiceInterface { this.openGameInstances = this.openGameInstances.filter( (gameInstance) => gameInstance.gameInstanceMetadata.data.gameInstanceId !== body.gameInstanceId ); - console.log('game instance deleted on server', this.openGameInstances.length); + console.log("Little Muncher - Game instance deleted on server. Remaining: " + this.openGameInstances.length); } async startLevel(body: LittleMuncherGameCreateDto, user: User) { @@ -48,9 +48,9 @@ export class GameInstanceService implements GameInstanceServiceInterface { gameInstance.initGame({ hill: body.level.hill }); - gameInstance.gameInstanceMetadata.data.sessionState = GameSessionState.Playing; - console.log('game mode set on server', body.level.hill); - this.gameInstanceGateway.emitRoom(this.getRoomEvent(gameInstance, 'added')); + gameInstance.gameInstanceMetadata.data.sessionState = GameSessionState.InProgress; + console.log("Little Muncher - Game mode set on server", body.level.hill); + this.gameInstanceGateway.emitRoom(this.getRoomEvent(gameInstance, "added")); } async spectatorJoined(body: GameInstanceDataDto, user: User): Promise { @@ -59,9 +59,9 @@ export class GameInstanceService implements GameInstanceServiceInterface { gameInstance.initSpectator({ userId: user.id }); - console.log('spectator joined', user.id); + console.log("Little Muncher - Spectator joined", user.id); this.gameInstanceGateway.emitSpectator( - this.getSpectatorEvent(user, this.getGameInstanceToRoom(gameInstance), 'joined') + this.getSpectatorEvent(user, this.getGameInstanceToRoom(gameInstance), body.gameInstanceId, "joined") ); return gameInstance.data; } @@ -70,9 +70,9 @@ export class GameInstanceService implements GameInstanceServiceInterface { const gameInstance = this.findGameInstance(body.gameInstanceId); if (!gameInstance) return; gameInstance.removeSpectator(user.id); - console.log('spectator left', user.id); + console.log("Little Muncher - spectator left", user.id); this.gameInstanceGateway.emitSpectator( - this.getSpectatorEvent(user, this.getGameInstanceToRoom(gameInstance), 'left') + this.getSpectatorEvent(user, this.getGameInstanceToRoom(gameInstance), body.gameInstanceId, "left") ); } @@ -81,40 +81,46 @@ export class GameInstanceService implements GameInstanceServiceInterface { if (!gameInstance) return; if (!this.checkIfPlayerIsCreator(gameInstance, user)) return; gameInstance.stopLevel(); - console.log('game mode deleted on server'); + console.log("Little Muncher - Game mode deleted on server"); - this.gameInstanceGateway.emitRoom(this.getRoomEvent(gameInstance, 'removed')); + this.gameInstanceGateway.emitRoom(this.getRoomEvent(gameInstance, "removed")); } - async getSpectatorRooms(user: User): Promise { + async getSpectatorRooms(user: User): Promise { return this.openGameInstances .filter( (gi) => - gi.gameInstanceMetadata.data.sessionState === GameSessionState.Playing && + gi.gameInstanceMetadata.data.sessionState === GameSessionState.InProgress && gi.gameInstanceMetadata.data.createdBy !== user.id ) .map((gameInstance) => this.getGameInstanceToRoom(gameInstance)); } - getGameInstanceToRoom(gameInstance: LittleMuncherGameInstance): Room { + getGameInstanceToRoom(gameInstance: LittleMuncherGameInstance): LittleMuncherRoom { return { - gameInstanceId: gameInstance.gameInstanceMetadata.data.gameInstanceId + gameInstanceMetadataData: gameInstance.gameInstanceMetadata.data, + gameModeData: gameInstance.gameMode }; } - getRoomEvent(gameInstance: LittleMuncherGameInstance, action: RoomAction): RoomEvent { + getRoomEvent(gameInstance: LittleMuncherGameInstance, action: RoomAction): LittleMuncherRoomEvent { return { room: this.getGameInstanceToRoom(gameInstance), - action, - gameInstanceMetadataData: gameInstance.gameInstanceMetadata.data + action }; } - getSpectatorEvent(user: User, room: Room, action: SpectatorAction): SpectatorEvent { + getSpectatorEvent( + user: User, + room: LittleMuncherRoom, + gameInstanceId: string, + action: SpectatorAction + ): LittleMuncherSpectatorEvent { return { user_id: user.id, room, - action + action, + gameInstanceId }; } @@ -133,7 +139,7 @@ export class GameInstanceService implements GameInstanceServiceInterface { const lastUpdatedMoreThanNMinutesAgo = !lastUpdated || lastUpdated.getTime() + minutesAgo < now.getTime(); const isOld = startedMoreThanNMinutesAgo && lastUpdatedMoreThanNMinutesAgo; if (isOld) { - this.gameInstanceGateway.emitRoom(this.getRoomEvent(gi, 'removed')); + this.gameInstanceGateway.emitRoom(this.getRoomEvent(gi, "removed")); } return !isOld; }); diff --git a/apps/api/src/app/little-muncher/game-instance/game-state-server.service.spec.ts b/apps/api/src/app/little-muncher/game-instance/game-state-server.service.spec.ts index ca3ce96e..3eedda20 100644 --- a/apps/api/src/app/little-muncher/game-instance/game-state-server.service.spec.ts +++ b/apps/api/src/app/little-muncher/game-instance/game-state-server.service.spec.ts @@ -1,9 +1,9 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { GameStateServerService } from './game-state-server.service'; -import { GameInstanceService } from './game-instance.service'; -import { GameInstanceServiceStub } from './game-instance.service.spec'; +import { Test, TestingModule } from "@nestjs/testing"; +import { GameStateServerService } from "./game-state-server.service"; +import { GameInstanceService } from "./game-instance.service"; +import { GameInstanceServiceStub } from "./game-instance.service.spec"; -describe('GameStateServerService', () => { +describe("GameStateServerService", () => { let service: GameStateServerService; beforeEach(async () => { @@ -14,7 +14,7 @@ describe('GameStateServerService', () => { service = module.get(GameStateServerService); }); - it('should be defined', () => { + it("should be defined", () => { expect(service).toBeDefined(); }); }); diff --git a/apps/api/src/app/little-muncher/game-instance/game-state-server.service.ts b/apps/api/src/app/little-muncher/game-instance/game-state-server.service.ts index f4d91205..91666769 100644 --- a/apps/api/src/app/little-muncher/game-instance/game-state-server.service.ts +++ b/apps/api/src/app/little-muncher/game-instance/game-state-server.service.ts @@ -1,7 +1,7 @@ import { Injectable } from "@nestjs/common"; import { - LittleMuncherCommunicatorClimbingEvent, CommunicatorEvent, + LittleMuncherCommunicatorClimbingEvent, LittleMuncherCommunicatorPauseEvent, LittleMuncherCommunicatorScoreEvent, LittleMuncherCommunicatorType, @@ -13,7 +13,6 @@ import { User } from "@supabase/supabase-js"; @Injectable() export class GameStateServerService { constructor(private readonly gameInstanceService: GameInstanceService) {} - updateGameState(body: CommunicatorEvent, user: User): boolean { const gameInstance = this.gameInstanceService.findGameInstance(body.gameInstanceId); if (!gameInstance) { @@ -35,16 +34,18 @@ export class GameStateServerService { console.log("User is not a player in this game instance"); return false; } - gameInstance.gameState.data.climbedHeight = (body.data as LittleMuncherCommunicatorClimbingEvent).timeClimbing; - console.log("updating time climbing", body.data); + gameInstance.gameState.data.climbedHeight = ( + body.payload as LittleMuncherCommunicatorClimbingEvent + ).timeClimbing; + console.log("updating time climbing", body.payload); break; case "pause": if (!authUserPlayer) { console.log("User is not a player in this game instance"); return false; } - gameInstance.gameState.data.pause = (body.data as LittleMuncherCommunicatorPauseEvent).pause; - console.log("updating pause", body.data); + gameInstance.gameState.data.pause = (body.payload as LittleMuncherCommunicatorPauseEvent).pause; + console.log("updating pause", body.payload); console.log("pausing game"); break; case "reset": @@ -61,16 +62,16 @@ export class GameStateServerService { console.log("User is not a player in this game instance"); return false; } - player.playerState.data.score = (body.data as LittleMuncherCommunicatorScoreEvent).score; - console.log("updating score", body.data); + player.playerState.data.score = (body.payload as LittleMuncherCommunicatorScoreEvent).score; + console.log("updating score", body.payload); break; case "move": if (!authUserPlayer) { console.log("User is not a player in this game instance"); return false; } - player.playerState.data.position = body.data as LittleMuncherPosition; - console.log("updating position", body.data); + player.playerState.data.position = body.payload as LittleMuncherPosition; + console.log("updating position", body.payload); break; default: throw new Error("Unknown communicator"); diff --git a/apps/api/src/app/little-muncher/game-instance/game-state.gateway.ts b/apps/api/src/app/little-muncher/game-instance/game-state.gateway.ts index 6d6de300..1195f94b 100644 --- a/apps/api/src/app/little-muncher/game-instance/game-state.gateway.ts +++ b/apps/api/src/app/little-muncher/game-instance/game-state.gateway.ts @@ -1,26 +1,23 @@ -import { ConnectedSocket, MessageBody, SubscribeMessage, WebSocketGateway, WebSocketServer } from '@nestjs/websockets'; -import { Server, Socket } from 'net'; -import { UseGuards } from '@nestjs/common'; -import { SupabaseAuthGuard } from '../../../auth/guards/supabase-auth.guard'; -import { CurrentUser } from '../../../auth/current-user'; -import { AuthUser } from '@supabase/supabase-js'; +import { ConnectedSocket, MessageBody, SubscribeMessage, WebSocketGateway, WebSocketServer } from "@nestjs/websockets"; +import { UseGuards } from "@nestjs/common"; +import { SupabaseAuthGuard } from "../../../auth/guards/supabase-auth.guard"; +import { CurrentUser } from "../../../auth/current-user"; +import { AuthUser } from "@supabase/supabase-js"; import { CommunicatorEvent, LittleMuncherCommunicatorType, LittleMuncherGatewayEvent -} from '@fuzzy-waddle/api-interfaces'; -import { GameStateServerService } from './game-state-server.service'; - -export type MyConnectedSocket = Socket & { broadcast: { emit: (event: string, data: any) => void } }; +} from "@fuzzy-waddle/api-interfaces"; +import { GameStateServerService } from "./game-state-server.service"; +import { Server, Socket } from "socket.io"; @WebSocketGateway({ cors: { - origin: process.env.CORS_ORIGIN?.split(',') + origin: process.env.CORS_ORIGIN?.split(",") } }) export class GameStateGateway { - @WebSocketServer() - private server: Server; + @WebSocketServer() private readonly server: Server; constructor(private readonly gameStateServerService: GameStateServerService) {} @@ -29,13 +26,13 @@ export class GameStateGateway { async broadcastLittleMuncherAction( @CurrentUser() user: AuthUser, @MessageBody() payload: CommunicatorEvent, - @ConnectedSocket() client: MyConnectedSocket + @ConnectedSocket() socket: Socket ) { - console.log('broadcasting little muncher action', payload.communicator); + console.log("Little Muncher - Action", payload.communicator); const success = this.gameStateServerService.updateGameState(payload, user); if (success) { - client.broadcast.emit(LittleMuncherGatewayEvent.LittleMuncherAction, payload); + socket.broadcast.emit(LittleMuncherGatewayEvent.LittleMuncherAction, payload); } else { // 400 error } diff --git a/apps/api/src/app/little-muncher/little-muncher.module.ts b/apps/api/src/app/little-muncher/little-muncher.module.ts index 7ed49e39..4fd24ab1 100644 --- a/apps/api/src/app/little-muncher/little-muncher.module.ts +++ b/apps/api/src/app/little-muncher/little-muncher.module.ts @@ -1,9 +1,9 @@ -import { Module } from '@nestjs/common'; -import { GameInstanceController } from './game-instance/game-instance.controller'; -import { GameInstanceService } from './game-instance/game-instance.service'; -import { GameInstanceGateway } from './game-instance/game-instance.gateway'; -import { GameStateServerService } from './game-instance/game-state-server.service'; -import { GameStateGateway } from './game-instance/game-state.gateway'; +import { Module } from "@nestjs/common"; +import { GameInstanceController } from "./game-instance/game-instance.controller"; +import { GameInstanceService } from "./game-instance/game-instance.service"; +import { GameInstanceGateway } from "./game-instance/game-instance.gateway"; +import { GameStateServerService } from "./game-instance/game-state-server.service"; +import { GameStateGateway } from "./game-instance/game-state.gateway"; @Module({ providers: [GameInstanceGateway, GameStateGateway, GameInstanceService, GameStateServerService], diff --git a/apps/api/src/app/probable-waffle/chat/probable-waffle-chat.service.spec.ts b/apps/api/src/app/probable-waffle/chat/probable-waffle-chat.service.spec.ts new file mode 100644 index 00000000..84f7551c --- /dev/null +++ b/apps/api/src/app/probable-waffle/chat/probable-waffle-chat.service.spec.ts @@ -0,0 +1,23 @@ +import { Test, TestingModule } from "@nestjs/testing"; +import { ProbableWaffleChatService } from "./probable-waffle-chat.service"; +import { TextSanitizationService } from "../../../core/content-filters/text-sanitization.service"; +import { textSanitizationServiceStub } from "../../../core/content-filters/text-sanitization.service.spec"; + +describe("ProbableWaffleChatService", () => { + let service: ProbableWaffleChatService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + ProbableWaffleChatService, + { provide: TextSanitizationService, useValue: textSanitizationServiceStub } + ] + }).compile(); + + service = module.get(ProbableWaffleChatService); + }); + + it("should be defined", () => { + expect(service).toBeDefined(); + }); +}); diff --git a/apps/api/src/app/probable-waffle/chat/probable-waffle-chat.service.ts b/apps/api/src/app/probable-waffle/chat/probable-waffle-chat.service.ts new file mode 100644 index 00000000..8ecae764 --- /dev/null +++ b/apps/api/src/app/probable-waffle/chat/probable-waffle-chat.service.ts @@ -0,0 +1,11 @@ +import { Injectable } from "@nestjs/common"; +import { TextSanitizationService } from "../../../core/content-filters/text-sanitization.service"; + +@Injectable() +export class ProbableWaffleChatService { + constructor(private readonly textSanitizationService: TextSanitizationService) {} + + cleanMessage(message: string): string { + return this.textSanitizationService.cleanBadWords(message); + } +} diff --git a/apps/api/src/app/probable-waffle/game-instance/game-instance-holder.service.spec.ts b/apps/api/src/app/probable-waffle/game-instance/game-instance-holder.service.spec.ts new file mode 100644 index 00000000..33eb8439 --- /dev/null +++ b/apps/api/src/app/probable-waffle/game-instance/game-instance-holder.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { GameInstanceHolderService } from './game-instance-holder.service'; + +describe('GameInstanceHolderService', () => { + let service: GameInstanceHolderService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [GameInstanceHolderService], + }).compile(); + + service = module.get(GameInstanceHolderService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/apps/api/src/app/probable-waffle/game-instance/game-instance-holder.service.ts b/apps/api/src/app/probable-waffle/game-instance/game-instance-holder.service.ts new file mode 100644 index 00000000..b85e63e5 --- /dev/null +++ b/apps/api/src/app/probable-waffle/game-instance/game-instance-holder.service.ts @@ -0,0 +1,27 @@ +import { Injectable } from "@nestjs/common"; +import { ProbableWaffleGameInstance } from "@fuzzy-waddle/api-interfaces"; + +@Injectable() +export class GameInstanceHolderService { + private _openGameInstances: ProbableWaffleGameInstance[] = []; + + get openGameInstances(): ProbableWaffleGameInstance[] { + return this._openGameInstances; + } + + removeGameInstance(gameInstanceId: string) { + this._openGameInstances = this.openGameInstances.filter( + (gi) => gi.gameInstanceMetadata.data.gameInstanceId !== gameInstanceId + ); + } + + addGameInstance(gameInstance: ProbableWaffleGameInstance) { + this._openGameInstances.push(gameInstance); + } + + findGameInstance(gameInstanceId: string): ProbableWaffleGameInstance | undefined { + return this.openGameInstances.find( + (gameInstance) => gameInstance.gameInstanceMetadata.data.gameInstanceId === gameInstanceId + ); + } +} diff --git a/apps/api/src/app/probable-waffle/game-instance/game-instance.controller.spec.ts b/apps/api/src/app/probable-waffle/game-instance/game-instance.controller.spec.ts new file mode 100644 index 00000000..047a7997 --- /dev/null +++ b/apps/api/src/app/probable-waffle/game-instance/game-instance.controller.spec.ts @@ -0,0 +1,26 @@ +import { Test, TestingModule } from "@nestjs/testing"; +import { GameInstanceController } from "./game-instance.controller"; +import { GameInstanceService } from "./game-instance.service"; +import { GameInstanceServiceStub } from "./game-instance.service.spec"; +import { MatchmakingService } from "../matchmaking/matchmaking.service"; +import { matchmakingServiceStub } from "../matchmaking/matchmaking.service.spec"; + +describe("GameInstanceController", () => { + let controller: GameInstanceController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + { provide: GameInstanceService, useValue: GameInstanceServiceStub }, + { provide: MatchmakingService, useValue: matchmakingServiceStub } + ], + controllers: [GameInstanceController] + }).compile(); + + controller = module.get(GameInstanceController); + }); + + it("should be defined", () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/apps/api/src/app/probable-waffle/game-instance/game-instance.controller.ts b/apps/api/src/app/probable-waffle/game-instance/game-instance.controller.ts new file mode 100644 index 00000000..8ec292e6 --- /dev/null +++ b/apps/api/src/app/probable-waffle/game-instance/game-instance.controller.ts @@ -0,0 +1,25 @@ +import { Body, Controller, Get, Post, Query, UseGuards } from "@nestjs/common"; +import { SupabaseAuthGuard } from "../../../auth/guards/supabase-auth.guard"; +import { CurrentUser } from "../../../auth/current-user"; +import { AuthUser } from "@supabase/supabase-js"; +import { GameInstanceService } from "./game-instance.service"; +import { ProbableWaffleGameInstanceData, ProbableWaffleGameInstanceMetadataData } from "@fuzzy-waddle/api-interfaces"; + +@Controller("probable-waffle") +export class GameInstanceController { + constructor(private readonly gameInstanceService: GameInstanceService) {} + @Post("start-game") + @UseGuards(SupabaseAuthGuard) + async startGame(@CurrentUser() user: AuthUser, @Body() body: ProbableWaffleGameInstanceMetadataData): Promise { + await this.gameInstanceService.createGameInstance(body, user); + } + + @Get("get-game-instance") + @UseGuards(SupabaseAuthGuard) + async getGameInstance( + @CurrentUser() user: AuthUser, + @Query("gameInstanceId") gameInstanceId: string + ): Promise { + return this.gameInstanceService.getGameInstanceData(gameInstanceId); + } +} diff --git a/apps/api/src/app/probable-waffle/game-instance/game-instance.gateway.ts b/apps/api/src/app/probable-waffle/game-instance/game-instance.gateway.ts new file mode 100644 index 00000000..fec8d571 --- /dev/null +++ b/apps/api/src/app/probable-waffle/game-instance/game-instance.gateway.ts @@ -0,0 +1,117 @@ +import { ConnectedSocket, MessageBody, SubscribeMessage, WebSocketGateway, WebSocketServer } from "@nestjs/websockets"; +import { Server, Socket } from "socket.io"; +import { + CommunicatorEvent, + ProbableWaffleCommunicatorMessageEvent, + ProbableWaffleCommunicatorType, + ProbableWaffleGameFoundEvent, + ProbableWaffleGameInstanceEvent, + ProbableWaffleGatewayEvent, + ProbableWaffleGatewayRoomTypes, + ProbableWaffleWebsocketRoomEvent +} from "@fuzzy-waddle/api-interfaces"; +import { ProbableWaffleChatService } from "../chat/probable-waffle-chat.service"; +import { UseGuards } from "@nestjs/common"; +import { AuthUser } from "@supabase/supabase-js"; +import { SupabaseAuthGuard } from "../../../auth/guards/supabase-auth.guard"; +import { CurrentUser } from "../../../auth/current-user"; +import { GameStateServerService } from "./game-state-server.service"; +import { RoomServerService } from "../game-room/room-server.service"; + +@WebSocketGateway({ + cors: { + origin: process.env.CORS_ORIGIN?.split(",") + } +}) +export class GameInstanceGateway { + @WebSocketServer() private readonly server: Server; + + constructor( + private readonly gameStateServerService: GameStateServerService, + private readonly probableWaffleChatService: ProbableWaffleChatService, + private readonly roomServerService: RoomServerService + ) {} + + emitGameFound(probableWaffleGameFoundEvent: ProbableWaffleGameFoundEvent) { + this.server.emit(ProbableWaffleGameInstanceEvent.GameFound, probableWaffleGameFoundEvent); + } + + @UseGuards(SupabaseAuthGuard) + @SubscribeMessage(ProbableWaffleGatewayEvent.ProbableWaffleAction) + async broadcastProbableWaffleAction( + @CurrentUser() user: AuthUser, + @MessageBody() body: CommunicatorEvent, + @ConnectedSocket() socket: Socket + ) { + console.log("Probable Waffle - GI action:", body.communicator, body.payload); + + const success = this.gameStateServerService.updateGameState(body, user); + if (success) { + // https://socket.io/docs/v3/emit-cheatsheet/ + socket + .to(`${ProbableWaffleGatewayRoomTypes.ProbableWaffleGameInstance}${body.gameInstanceId}`) + .emit(ProbableWaffleGatewayEvent.ProbableWaffleAction, body); + + this.roomServerService.emitCertainGameInstanceEventsToAllUsers(body, user); + } else { + // 400 error + } + } + + @UseGuards(SupabaseAuthGuard) + @SubscribeMessage(ProbableWaffleGatewayEvent.ProbableWaffleMessage) + async broadcastProbableWaffleMessage( + @CurrentUser() user: AuthUser, + @MessageBody() body: CommunicatorEvent, + @ConnectedSocket() socket: Socket + ) { + console.log(`Probable Waffle - GI chat message ${body.gameInstanceId}`); + + // clone the payload + const newPayload = { ...body }; + + switch (newPayload.communicator) { + case "message": + const body = newPayload.payload as ProbableWaffleCommunicatorMessageEvent; + const sanitizedMessage = this.probableWaffleChatService.cleanMessage(body.chatMessage.text); + body.chatMessage.text = sanitizedMessage; + + // https://socket.io/docs/v3/emit-cheatsheet/ + socket + .to(`${ProbableWaffleGatewayRoomTypes.ProbableWaffleGameInstance}${body.gameInstanceId}`) + .emit(ProbableWaffleGatewayEvent.ProbableWaffleMessage, newPayload); + break; + default: + throw new Error("Probable Waffle - Message broadcast - unknown communicator"); + } + } + + @UseGuards(SupabaseAuthGuard) + @SubscribeMessage(ProbableWaffleGatewayEvent.ProbableWaffleWebsocketRoom) + async broadcastProbableWaffleWebsocketRoom( + @CurrentUser() user: AuthUser, + @MessageBody() body: ProbableWaffleWebsocketRoomEvent, + @ConnectedSocket() socket: Socket + ) { + switch (body.type) { + case "join": + socket.join(`${ProbableWaffleGatewayRoomTypes.ProbableWaffleGameInstance}${body.gameInstanceId}`); + console.log( + user.id, + "joined room", + `${ProbableWaffleGatewayRoomTypes.ProbableWaffleGameInstance}${body.gameInstanceId}` + ); + break; + case "leave": + socket.leave(`${ProbableWaffleGatewayRoomTypes.ProbableWaffleGameInstance}${body.gameInstanceId}`); + console.log( + user.id, + "left room", + `${ProbableWaffleGatewayRoomTypes.ProbableWaffleGameInstance}${body.gameInstanceId}` + ); + break; + default: + throw new Error("Probable Waffle - Web socket room broadcast - unknown communicator"); + } + } +} diff --git a/apps/api/src/app/probable-waffle/game-instance/game-instance.service.interface.ts b/apps/api/src/app/probable-waffle/game-instance/game-instance.service.interface.ts new file mode 100644 index 00000000..2d115c80 --- /dev/null +++ b/apps/api/src/app/probable-waffle/game-instance/game-instance.service.interface.ts @@ -0,0 +1,19 @@ +import { + GameInstanceDataDto, + ProbableWaffleAddPlayerDto, + ProbableWaffleAddSpectatorDto, + ProbableWaffleChangeGameModeDto, + ProbableWaffleGameInstance, + ProbableWaffleGameInstanceData, + ProbableWaffleGameInstanceMetadataData, + ProbableWafflePlayerLeftDto, + ProbableWaffleStartLevelDto +} from "@fuzzy-waddle/api-interfaces"; +import { User } from "@supabase/supabase-js"; + +export interface GameInstanceServiceInterface { + createGameInstance(gameInstanceMetadataData: ProbableWaffleGameInstanceMetadataData, user: User); + stopGameInstance(gameInstanceId: string, user: User); + findGameInstance(gameInstanceId: string): ProbableWaffleGameInstance | undefined; + getGameInstanceData(gameInstanceId: string): ProbableWaffleGameInstanceData | null; +} diff --git a/apps/api/src/app/probable-waffle/game-instance/game-instance.service.spec.ts b/apps/api/src/app/probable-waffle/game-instance/game-instance.service.spec.ts new file mode 100644 index 00000000..4db569f4 --- /dev/null +++ b/apps/api/src/app/probable-waffle/game-instance/game-instance.service.spec.ts @@ -0,0 +1,58 @@ +import { Test, TestingModule } from "@nestjs/testing"; +import { GameInstanceService } from "./game-instance.service"; +import { + ProbableWaffleGameInstance, + ProbableWaffleGameInstanceData, + ProbableWaffleGameInstanceMetadataData +} from "@fuzzy-waddle/api-interfaces"; +import { GameInstanceServiceInterface } from "./game-instance.service.interface"; +import { User } from "../../../users/users.service"; +import { GameInstanceGateway } from "./game-instance.gateway"; +import { TextSanitizationService } from "../../../core/content-filters/text-sanitization.service"; +import { GameInstanceGatewayStub } from "../../little-muncher/game-instance/game-instance.gateway"; +import { RoomServerService } from "../game-room/room-server.service"; +import { roomServerServiceStub } from "../game-room/room-server.service.spec"; +import { GameInstanceHolderService } from "./game-instance-holder.service"; + +export const GameInstanceServiceStub = { + findGameInstance(gameInstanceId: string): ProbableWaffleGameInstance | undefined { + return undefined; + }, + createGameInstance(gameInstanceMetadataData: ProbableWaffleGameInstanceMetadataData, user: User) { + // + }, + getGameInstanceData(gameInstanceId: string): ProbableWaffleGameInstanceData | null { + return null; + }, + async stopGameInstance(gameInstanceId: string, user: User) { + // + } +} satisfies GameInstanceServiceInterface; + +describe("GameInstanceService", () => { + let service: GameInstanceService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + GameInstanceHolderService, + GameInstanceService, + { + provide: GameInstanceGateway, + useValue: GameInstanceGatewayStub + }, + { + provide: RoomServerService, + useValue: roomServerServiceStub + }, + TextSanitizationService + ] + }).compile(); + + service = module.get(GameInstanceService); + }); + + it("should be defined", () => { + expect(service).toBeDefined(); + }); +}); diff --git a/apps/api/src/app/probable-waffle/game-instance/game-instance.service.ts b/apps/api/src/app/probable-waffle/game-instance/game-instance.service.ts new file mode 100644 index 00000000..059150bc --- /dev/null +++ b/apps/api/src/app/probable-waffle/game-instance/game-instance.service.ts @@ -0,0 +1,114 @@ +import { Injectable } from "@nestjs/common"; +import { User } from "@supabase/supabase-js"; +import { + DifficultyModifiers, + MapTuning, + ProbableWaffleGameInstance, + ProbableWaffleGameInstanceData, + ProbableWaffleGameInstanceMetadataData, + ProbableWaffleGameModeData, + ProbableWaffleGameStateData, + WinConditions +} from "@fuzzy-waddle/api-interfaces"; +import { Cron, CronExpression } from "@nestjs/schedule"; +import { GameInstanceServiceInterface } from "./game-instance.service.interface"; +import { TextSanitizationService } from "../../../core/content-filters/text-sanitization.service"; +import { RoomServerService } from "../game-room/room-server.service"; +import { GameInstanceHolderService } from "./game-instance-holder.service"; + +@Injectable() +export class GameInstanceService implements GameInstanceServiceInterface { + constructor( + private readonly roomServerService: RoomServerService, + private readonly gameInstanceHolderService: GameInstanceHolderService, + private readonly textSanitizationService: TextSanitizationService + ) {} + + addGameInstance(gameInstance: ProbableWaffleGameInstance, user: User): void { + this.gameInstanceHolderService.addGameInstance(gameInstance); + this.roomServerService.roomEvent("added", gameInstance, user); + console.log( + "Probable Waffle - Game instance added. Open instances: " + + this.gameInstanceHolderService.openGameInstances.length + ); + } + + async createGameInstance(gameInstanceMetadataData: ProbableWaffleGameInstanceMetadataData, user: User) { + if (gameInstanceMetadataData.createdBy !== user.id) + throw new Error("Probable Waffle - createGameInstance - createdBy must be the same as user id"); + const newGameInstance = new ProbableWaffleGameInstance({ + gameInstanceMetadataData: this.sanitizeGameInstanceMetadataData(gameInstanceMetadataData), + gameModeData: { + winConditions: {} satisfies WinConditions, + mapTuning: {} satisfies MapTuning, + difficultyModifiers: {} satisfies DifficultyModifiers + } satisfies ProbableWaffleGameModeData, + gameStateData: {} as ProbableWaffleGameStateData + }); + this.addGameInstance(newGameInstance, user); + + console.log( + "Probable Waffle - game instance created. Open instances: " + + this.gameInstanceHolderService.openGameInstances.length + ); + } + + stopGameInstance(gameInstanceId: string, user: User) { + const gameInstance = this.findGameInstance(gameInstanceId); + if (!gameInstance) return; + if (!this.checkIfPlayerIsCreator(gameInstance, user)) return; + this.gameInstanceHolderService.removeGameInstance(gameInstanceId); + this.roomServerService.roomEvent("removed", gameInstance, user); + console.log( + "Probable Waffle - game instance deleted. Remaining: " + this.gameInstanceHolderService.openGameInstances.length + ); + } + + /** + * remove game instances that have been started more than N time ago + */ + @Cron(CronExpression.EVERY_2_HOURS) + handleCron() { + const toRemove = this.gameInstanceHolderService.openGameInstances.filter((gi) => { + const minutesAgo = 1000 * 60 * 15; // 15 minutes + const started = gi.gameInstanceMetadata.data.createdOn; + const lastUpdated = gi.gameInstanceMetadata.data.updatedOn; + const now = new Date(); + // is old if started is more than N minutes ago and lastUpdated is null or more than N minutes ago + const startedMoreThanNMinutesAgo = started.getTime() + minutesAgo < now.getTime(); + const lastUpdatedMoreThanNMinutesAgo = !lastUpdated || lastUpdated.getTime() + minutesAgo < now.getTime(); + const isOld = startedMoreThanNMinutesAgo && lastUpdatedMoreThanNMinutesAgo; + if (isOld) { + this.roomServerService.roomEvent("removed", gi, null); + console.log("Probable Waffle - Cron - Game instance removed"); + } + return !isOld; + }); + toRemove.forEach((gi) => + this.gameInstanceHolderService.removeGameInstance(gi.gameInstanceMetadata.data.gameInstanceId) + ); + } + + findGameInstance(gameInstanceId: string): ProbableWaffleGameInstance | undefined { + return this.gameInstanceHolderService.findGameInstance(gameInstanceId); + } + + private checkIfPlayerIsCreator(gameInstance: ProbableWaffleGameInstance, user: User) { + return gameInstance.gameInstanceMetadata.data.createdBy === user.id; + } + + getGameInstanceData(gameInstanceId: string): ProbableWaffleGameInstanceData | null { + const gameInstance = this.findGameInstance(gameInstanceId); + if (!gameInstance) return; + return gameInstance.data; + } + + private sanitizeGameInstanceMetadataData( + gameInstanceMetadataData: ProbableWaffleGameInstanceMetadataData + ): ProbableWaffleGameInstanceMetadataData { + return { + ...gameInstanceMetadataData, + name: this.textSanitizationService.cleanBadWords(gameInstanceMetadataData.name) + }; + } +} diff --git a/apps/api/src/app/probable-waffle/game-instance/game-state-server.service.spec.ts b/apps/api/src/app/probable-waffle/game-instance/game-state-server.service.spec.ts new file mode 100644 index 00000000..3eedda20 --- /dev/null +++ b/apps/api/src/app/probable-waffle/game-instance/game-state-server.service.spec.ts @@ -0,0 +1,20 @@ +import { Test, TestingModule } from "@nestjs/testing"; +import { GameStateServerService } from "./game-state-server.service"; +import { GameInstanceService } from "./game-instance.service"; +import { GameInstanceServiceStub } from "./game-instance.service.spec"; + +describe("GameStateServerService", () => { + let service: GameStateServerService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [GameStateServerService, { provide: GameInstanceService, useValue: GameInstanceServiceStub }] + }).compile(); + + service = module.get(GameStateServerService); + }); + + it("should be defined", () => { + expect(service).toBeDefined(); + }); +}); diff --git a/apps/api/src/app/probable-waffle/game-instance/game-state-server.service.ts b/apps/api/src/app/probable-waffle/game-instance/game-state-server.service.ts new file mode 100644 index 00000000..3c0ff065 --- /dev/null +++ b/apps/api/src/app/probable-waffle/game-instance/game-state-server.service.ts @@ -0,0 +1,59 @@ +import { Injectable } from "@nestjs/common"; +import { + CommunicatorEvent, + GameSessionState, + ProbableWaffleCommunicatorType, + ProbableWaffleGameInstanceMetadataChangeEvent, + ProbableWaffleGameModeDataChangeEvent, + ProbableWaffleListeners, + ProbableWafflePlayerDataChangeEvent, + ProbableWaffleSpectatorDataChangeEvent +} from "@fuzzy-waddle/api-interfaces"; +import { GameInstanceService } from "./game-instance.service"; +import { User } from "@supabase/supabase-js"; + +@Injectable() +export class GameStateServerService { + constructor(private readonly gameInstanceService: GameInstanceService) {} + + updateGameState(body: CommunicatorEvent, user: User): boolean { + const gameInstance = this.gameInstanceService.findGameInstance(body.gameInstanceId); + if (!gameInstance) { + console.log("game instance not found"); + return false; + } + + gameInstance.gameInstanceMetadata.data.updatedOn = new Date(); + + switch (body.communicator) { + case "gameInstanceMetadataDataChange": + const giMetadata = body.payload as ProbableWaffleGameInstanceMetadataChangeEvent; + ProbableWaffleListeners.gameInstanceMetadataChanged(gameInstance, giMetadata); + switch (giMetadata.property) { + case "sessionState": + switch (giMetadata.data.sessionState) { + case GameSessionState.Stopped: + this.gameInstanceService.stopGameInstance(body.gameInstanceId, user); + break; + } + break; + } + break; + case "gameModeDataChange": + const gmData = body.payload as ProbableWaffleGameModeDataChangeEvent; + ProbableWaffleListeners.gameModeChanged(gameInstance, gmData); + break; + case "playerDataChange": + const playerData = body.payload as ProbableWafflePlayerDataChangeEvent; + ProbableWaffleListeners.playerChanged(gameInstance, playerData); + break; + case "spectatorDataChange": + const spectatorData = body.payload as ProbableWaffleSpectatorDataChangeEvent; + ProbableWaffleListeners.spectatorChanged(gameInstance, spectatorData); + break; + default: + throw new Error("Unknown communicator"); + } + return true; + } +} diff --git a/apps/api/src/app/probable-waffle/game-room/room-server.service.interface.ts b/apps/api/src/app/probable-waffle/game-room/room-server.service.interface.ts new file mode 100644 index 00000000..9a8fbcfe --- /dev/null +++ b/apps/api/src/app/probable-waffle/game-room/room-server.service.interface.ts @@ -0,0 +1,18 @@ +import { User } from "@supabase/supabase-js"; +import { + CommunicatorEvent, + ProbableWaffleCommunicatorType, + ProbableWaffleGameInstance, + ProbableWaffleGetRoomsDto, + ProbableWaffleRoom, + RoomAction +} from "@fuzzy-waddle/api-interfaces"; + +export interface RoomServerServiceInterface { + getVisibleRooms(user: User, body: ProbableWaffleGetRoomsDto): Promise; + roomEvent(type: RoomAction, gameInstance: ProbableWaffleGameInstance, user: User | null): void; + emitCertainGameInstanceEventsToAllUsers( + body: CommunicatorEvent, + user: User + ): void; +} diff --git a/apps/api/src/app/probable-waffle/game-room/room-server.service.spec.ts b/apps/api/src/app/probable-waffle/game-room/room-server.service.spec.ts new file mode 100644 index 00000000..9084f036 --- /dev/null +++ b/apps/api/src/app/probable-waffle/game-room/room-server.service.spec.ts @@ -0,0 +1,45 @@ +import { Test, TestingModule } from "@nestjs/testing"; +import { RoomServerService } from "./room-server.service"; +import { + CommunicatorEvent, + ProbableWaffleCommunicatorType, + ProbableWaffleGameInstance, + ProbableWaffleGetRoomsDto, + ProbableWaffleRoom, + RoomAction +} from "@fuzzy-waddle/api-interfaces"; +import { User } from "@supabase/supabase-js"; +import { RoomServerServiceInterface } from "./room-server.service.interface"; +import { RoomGateway } from "./room.gateway"; +import { GameInstanceHolderService } from "../game-instance/game-instance-holder.service"; + +export const roomServerServiceStub = { + getVisibleRooms(user: User, body: ProbableWaffleGetRoomsDto): Promise { + return Promise.resolve([]); + }, + roomEvent(type: RoomAction, gameInstance: ProbableWaffleGameInstance, user: User | null): void { + // + }, + emitCertainGameInstanceEventsToAllUsers( + body: CommunicatorEvent, + user: User + ): void { + // + } +} satisfies RoomServerServiceInterface; + +describe("RoomServerService", () => { + let service: RoomServerService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [RoomServerService, RoomGateway, GameInstanceHolderService] + }).compile(); + + service = module.get(RoomServerService); + }); + + it("should be defined", () => { + expect(service).toBeDefined(); + }); +}); diff --git a/apps/api/src/app/probable-waffle/game-room/room-server.service.ts b/apps/api/src/app/probable-waffle/game-room/room-server.service.ts new file mode 100644 index 00000000..d9a74b79 --- /dev/null +++ b/apps/api/src/app/probable-waffle/game-room/room-server.service.ts @@ -0,0 +1,98 @@ +import { Injectable } from "@nestjs/common"; +import { RoomGateway } from "./room.gateway"; +import { + CommunicatorEvent, + GameSessionState, + ProbableWaffleCommunicatorType, + ProbableWaffleGameInstance, + ProbableWaffleGameInstanceVisibility, + ProbableWaffleGetRoomsDto, + ProbableWafflePlayerDataChangeEvent, + ProbableWaffleRoom, + ProbableWaffleRoomEvent, + ProbableWaffleSpectatorDataChangeEvent, + RoomAction +} from "@fuzzy-waddle/api-interfaces"; +import { User } from "@supabase/supabase-js"; +import { GameInstanceHolderService } from "../game-instance/game-instance-holder.service"; +import { RoomServerServiceInterface } from "./room-server.service.interface"; + +@Injectable() +export class RoomServerService implements RoomServerServiceInterface { + constructor( + private readonly roomGateway: RoomGateway, + private readonly gameInstanceHolderService: GameInstanceHolderService + ) {} + + async getVisibleRooms(user: User, body: ProbableWaffleGetRoomsDto): Promise { + const notStarted = this.gameInstanceHolderService.openGameInstances.filter( + (gi) => gi.gameInstanceMetadata.data.sessionState === GameSessionState.NotStarted + ); + const visible = notStarted.filter( + (gi) => gi.gameInstanceMetadata.data.visibility === ProbableWaffleGameInstanceVisibility.Public + ); + const notCreatedByUser = visible.filter((gi) => gi.gameInstanceMetadata.data.createdBy !== user.id); + const filteredByMap = notCreatedByUser.filter( + (gi) => !gi.gameMode || (body.maps?.includes(gi.gameMode.data.map) ?? true) + ); + // noinspection UnnecessaryLocalVariableJS + const gameInstanceToRoom = filteredByMap.map((gameInstance) => this.getGameInstanceToRoom(gameInstance)); + return gameInstanceToRoom; + } + + roomEvent(type: RoomAction, gameInstance: ProbableWaffleGameInstance, user: User | null) { + this.roomGateway.emitRoom({ + room: this.getGameInstanceToRoom(gameInstance), + action: type + } satisfies ProbableWaffleRoomEvent); + } + + private getGameInstanceToRoom(gameInstance: ProbableWaffleGameInstance): ProbableWaffleRoom { + return { + gameInstanceMetadataData: gameInstance.gameInstanceMetadata.data, + gameModeData: gameInstance.gameMode?.data, + players: gameInstance.players.map((player) => ({ + controllerData: player.playerController.data + })), + spectators: gameInstance.spectators.map((spectator) => spectator.data) + }; + } + + emitCertainGameInstanceEventsToAllUsers(body: CommunicatorEvent, user: User) { + const gameInstance = this.gameInstanceHolderService.findGameInstance(body.gameInstanceId); + if (!gameInstance) { + console.log("game instance not found"); + return false; + } + switch (body.communicator) { + case "gameInstanceMetadataDataChange": + this.roomEvent("game_instance_metadata", gameInstance, user); + break; + case "gameModeDataChange": + this.roomEvent("game_mode", gameInstance, user); + break; + case "playerDataChange": + const playerData = body.payload as ProbableWafflePlayerDataChangeEvent; + switch (playerData.property) { + case "joined": + this.roomEvent("player.joined", gameInstance, user); + break; + case "left": + this.roomEvent("player.left", gameInstance, user); + break; + } + break; + case "spectatorDataChange": + const spectatorData = body.payload as ProbableWaffleSpectatorDataChangeEvent; + switch (spectatorData.property) { + case "joined": + this.roomEvent("spectator.joined", gameInstance, user); + break; + case "left": + this.roomEvent("spectator.left", gameInstance, user); + break; + } + break; + } + } +} diff --git a/apps/api/src/app/probable-waffle/game-room/room.controller.spec.ts b/apps/api/src/app/probable-waffle/game-room/room.controller.spec.ts new file mode 100644 index 00000000..8fb5a4f1 --- /dev/null +++ b/apps/api/src/app/probable-waffle/game-room/room.controller.spec.ts @@ -0,0 +1,25 @@ +import { Test, TestingModule } from "@nestjs/testing"; +import { RoomController } from "./room.controller"; +import { RoomService, roomServiceStub } from "../../game-session/room/room.service"; +import { RoomServerService } from "./room-server.service"; +import { roomServerServiceStub } from "./room-server.service.spec"; + +describe("RoomController", () => { + let controller: RoomController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [RoomController], + providers: [ + { provide: RoomService, useValue: roomServiceStub }, + { provide: RoomServerService, useValue: roomServerServiceStub } + ] + }).compile(); + + controller = module.get(RoomController); + }); + + it("should be defined", () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/apps/api/src/app/probable-waffle/game-room/room.controller.ts b/apps/api/src/app/probable-waffle/game-room/room.controller.ts new file mode 100644 index 00000000..a8a763ad --- /dev/null +++ b/apps/api/src/app/probable-waffle/game-room/room.controller.ts @@ -0,0 +1,29 @@ +import { Body, Controller, Post, UseGuards } from "@nestjs/common"; +import { SupabaseAuthGuard } from "../../../auth/guards/supabase-auth.guard"; +import { CurrentUser } from "../../../auth/current-user"; +import { AuthUser } from "@supabase/supabase-js"; +import { ProbableWaffleGetRoomsDto, ProbableWaffleRoom } from "@fuzzy-waddle/api-interfaces"; +import { RoomServerService } from "./room-server.service"; + +@Controller("probable-waffle") +export class RoomController { + constructor(private readonly roomServerService: RoomServerService) {} + + // @Post("join-room") + // @UseGuards(SupabaseAuthGuard) + // async joinRoom( + // @CurrentUser() user: AuthUser, + // @Body() body: ProbableWaffleJoinDto + // ): Promise { + // return await this.roomServerService.joinRoom(body, user); + // } + + @Post("get-rooms") + @UseGuards(SupabaseAuthGuard) + async getRooms( + @CurrentUser() user: AuthUser, + @Body() body: ProbableWaffleGetRoomsDto + ): Promise { + return await this.roomServerService.getVisibleRooms(user, body); + } +} diff --git a/apps/api/src/app/probable-waffle/game-room/room.gateway.ts b/apps/api/src/app/probable-waffle/game-room/room.gateway.ts new file mode 100644 index 00000000..300e8090 --- /dev/null +++ b/apps/api/src/app/probable-waffle/game-room/room.gateway.ts @@ -0,0 +1,16 @@ +import { WebSocketGateway, WebSocketServer } from "@nestjs/websockets"; +import { ProbableWaffleGatewayEvent, ProbableWaffleRoomEvent } from "@fuzzy-waddle/api-interfaces"; +import { Server } from "socket.io"; + +@WebSocketGateway({ + cors: { + origin: process.env.CORS_ORIGIN?.split(",") + } +}) +export class RoomGateway { + @WebSocketServer() private readonly server: Server; + + emitRoom(roomEvent: ProbableWaffleRoomEvent) { + this.server.emit(ProbableWaffleGatewayEvent.ProbableWaffleRoom, roomEvent); + } +} diff --git a/apps/api/src/app/probable-waffle/matchmaking/matchmaking.controller.spec.ts b/apps/api/src/app/probable-waffle/matchmaking/matchmaking.controller.spec.ts new file mode 100644 index 00000000..7d84c343 --- /dev/null +++ b/apps/api/src/app/probable-waffle/matchmaking/matchmaking.controller.spec.ts @@ -0,0 +1,21 @@ +import { Test, TestingModule } from "@nestjs/testing"; +import { MatchmakingController } from "./matchmaking.controller"; +import { MatchmakingService } from "./matchmaking.service"; +import { matchmakingServiceStub } from "./matchmaking.service.spec"; + +describe("MatchmakingController", () => { + let controller: MatchmakingController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [MatchmakingController], + providers: [{ provide: MatchmakingService, useValue: matchmakingServiceStub }] + }).compile(); + + controller = module.get(MatchmakingController); + }); + + it("should be defined", () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/apps/api/src/app/probable-waffle/matchmaking/matchmaking.controller.ts b/apps/api/src/app/probable-waffle/matchmaking/matchmaking.controller.ts new file mode 100644 index 00000000..80970b52 --- /dev/null +++ b/apps/api/src/app/probable-waffle/matchmaking/matchmaking.controller.ts @@ -0,0 +1,26 @@ +import { Body, Controller, Delete, Post, UseGuards } from "@nestjs/common"; +import { SupabaseAuthGuard } from "../../../auth/guards/supabase-auth.guard"; +import { CurrentUser } from "../../../auth/current-user"; +import { AuthUser } from "@supabase/supabase-js"; +import { RequestGameSearchForMatchMakingDto } from "@fuzzy-waddle/api-interfaces"; +import { MatchmakingService } from "./matchmaking.service"; + +@Controller("probable-waffle/matchmaking") +export class MatchmakingController { + constructor(private readonly matchmakingService: MatchmakingService) {} + + @Post("request-game-search-for-matchmaking") + @UseGuards(SupabaseAuthGuard) + async requestGameSearchForMatchmaking( + @CurrentUser() user: AuthUser, + @Body() body: RequestGameSearchForMatchMakingDto + ): Promise { + await this.matchmakingService.requestGameSearchForMatchMaking(body, user); + } + + @Delete("stop-request-game-search-for-matchmaking") + @UseGuards(SupabaseAuthGuard) + async stopRequestGameSearchForMatchmaking(@CurrentUser() user: AuthUser): Promise { + await this.matchmakingService.stopRequestGameSearchForMatchmaking(user); + } +} diff --git a/apps/api/src/app/probable-waffle/matchmaking/matchmaking.service.interface.ts b/apps/api/src/app/probable-waffle/matchmaking/matchmaking.service.interface.ts new file mode 100644 index 00000000..ed5a5a42 --- /dev/null +++ b/apps/api/src/app/probable-waffle/matchmaking/matchmaking.service.interface.ts @@ -0,0 +1,7 @@ +import { RequestGameSearchForMatchMakingDto } from "@fuzzy-waddle/api-interfaces"; +import { User } from "@supabase/supabase-js"; + +export interface MatchmakingServiceInterface { + requestGameSearchForMatchMaking(body: RequestGameSearchForMatchMakingDto, user: User): Promise; + stopRequestGameSearchForMatchmaking(user: User): Promise; +} diff --git a/apps/api/src/app/probable-waffle/matchmaking/matchmaking.service.spec.ts b/apps/api/src/app/probable-waffle/matchmaking/matchmaking.service.spec.ts new file mode 100644 index 00000000..917a2db3 --- /dev/null +++ b/apps/api/src/app/probable-waffle/matchmaking/matchmaking.service.spec.ts @@ -0,0 +1,46 @@ +import { Test, TestingModule } from "@nestjs/testing"; +import { MatchmakingService } from "./matchmaking.service"; +import { MatchmakingServiceInterface } from "./matchmaking.service.interface"; +import { RequestGameSearchForMatchMakingDto } from "@fuzzy-waddle/api-interfaces"; +import { User } from "@supabase/supabase-js"; +import { GameInstanceService } from "../game-instance/game-instance.service"; +import { GameInstanceServiceStub } from "../game-instance/game-instance.service.spec"; +import { GameInstanceGateway } from "../game-instance/game-instance.gateway"; +import { GameInstanceGatewayStub } from "../../little-muncher/game-instance/game-instance.gateway"; +import { RoomServerService } from "../game-room/room-server.service"; +import { roomServerServiceStub } from "../game-room/room-server.service.spec"; + +export const matchmakingServiceStub = { + async requestGameSearchForMatchMaking(body: RequestGameSearchForMatchMakingDto, user: User): Promise { + return Promise.resolve(); + }, + async stopRequestGameSearchForMatchmaking(user: User): Promise { + return Promise.resolve(); + } +} satisfies MatchmakingServiceInterface; +describe("MatchmakingService", () => { + let service: MatchmakingService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + MatchmakingService, + { provide: GameInstanceService, useValue: GameInstanceServiceStub }, + { + provide: GameInstanceGateway, + useValue: GameInstanceGatewayStub + }, + { + provide: RoomServerService, + useValue: roomServerServiceStub + } + ] + }).compile(); + + service = module.get(MatchmakingService); + }); + + it("should be defined", () => { + expect(service).toBeDefined(); + }); +}); diff --git a/apps/api/src/app/probable-waffle/matchmaking/matchmaking.service.ts b/apps/api/src/app/probable-waffle/matchmaking/matchmaking.service.ts new file mode 100644 index 00000000..0543e89e --- /dev/null +++ b/apps/api/src/app/probable-waffle/matchmaking/matchmaking.service.ts @@ -0,0 +1,236 @@ +import { Injectable } from "@nestjs/common"; +import { + DifficultyModifiers, + FactionType, + GameSessionState, + GameSetupHelpers, + MapTuning, + PendingMatchmakingGameInstance, + PlayerLobbyDefinition, + PositionPlayerDefinition, + ProbableWaffleGameInstance, + ProbableWaffleGameInstanceType, + ProbableWaffleGameInstanceVisibility, + ProbableWaffleGameMode, + ProbableWaffleGameModeData, + ProbableWaffleGameStateData, + ProbableWaffleLevels, + ProbableWaffleMapEnum, + ProbableWafflePlayerControllerData, + ProbableWafflePlayerType, + RequestGameSearchForMatchMakingDto, + WinConditions +} from "@fuzzy-waddle/api-interfaces"; +import { User } from "@supabase/supabase-js"; +import { Cron, CronExpression } from "@nestjs/schedule"; +import { MatchmakingServiceInterface } from "./matchmaking.service.interface"; +import { GameInstanceService } from "../game-instance/game-instance.service"; +import { RoomServerService } from "../game-room/room-server.service"; +import { GameInstanceGateway } from "../game-instance/game-instance.gateway"; + +@Injectable() +export class MatchmakingService implements MatchmakingServiceInterface { + private pendingMatchmakingGameInstances: PendingMatchmakingGameInstance[] = []; + + constructor( + private readonly gameInstanceService: GameInstanceService, + private readonly roomServerService: RoomServerService, + private readonly gameInstanceGateway: GameInstanceGateway + ) {} + /** + * remove game instances that have been started more than N time ago + */ + @Cron(CronExpression.EVERY_2_HOURS) + handleCron() { + this.pendingMatchmakingGameInstances = this.pendingMatchmakingGameInstances.filter((gi) => { + const minutesAgo = 1000 * 60 * 15; // 15 minutes + const started = gi.gameInstance.gameInstanceMetadata.data.createdOn; + const lastUpdated = gi.gameInstance.gameInstanceMetadata.data.updatedOn; + const now = new Date(); + // is old if started is more than N minutes ago and lastUpdated is null or more than N minutes ago + const startedMoreThanNMinutesAgo = started.getTime() + minutesAgo < now.getTime(); + const lastUpdatedMoreThanNMinutesAgo = !lastUpdated || lastUpdated.getTime() + minutesAgo < now.getTime(); + const isOld = startedMoreThanNMinutesAgo && lastUpdatedMoreThanNMinutesAgo; + if (isOld) { + this.roomServerService.roomEvent("removed", gi.gameInstance, null); + console.log("Probable Waffle - Cron - Pending matchmaking instance removed"); + } + return !isOld; + }); + } + + async requestGameSearchForMatchMaking(matchMakingDto: RequestGameSearchForMatchMakingDto, user: User): Promise { + const pendingMatchmakingGameInstance = this.findGameInstanceForMatchMaking(matchMakingDto); + if (pendingMatchmakingGameInstance) { + await this.joinGameInstanceForMatchmaking(pendingMatchmakingGameInstance, matchMakingDto, user); + return; + } else { + this.createGameInstanceForMatchmaking(matchMakingDto, user); + } + } + + private promoteGameInstanceToLoaded(gameInstance: ProbableWaffleGameInstance, user: User) { + const gameInstanceId = gameInstance.gameInstanceMetadata.data.gameInstanceId; + this.pendingMatchmakingGameInstances = this.pendingMatchmakingGameInstances.filter( + (gi) => + gi.gameInstance.gameInstanceMetadata.data.gameInstanceId !== + gameInstance.gameInstanceMetadata.data.gameInstanceId + ); + gameInstance.gameInstanceMetadata.data.sessionState = GameSessionState.MovingPlayersToGame; + this.gameInstanceGateway.emitGameFound({ + userIds: gameInstance.players.map((p) => p.playerController.data.userId), + gameInstanceId + }); + this.roomServerService.roomEvent("game_instance_metadata", gameInstance, user); + console.log("Probable Waffle - Matchmaking game fully loaded", gameInstanceId); + } + + private async joinGameInstanceForMatchmaking( + pendingMatchmakingGameInstance: PendingMatchmakingGameInstance, + matchMakingDto: RequestGameSearchForMatchMakingDto, + user: User + ) { + const gameInstance = pendingMatchmakingGameInstance.gameInstance; + // PLAYER: + const player = this.getNewPlayer(gameInstance, user.id, matchMakingDto.factionType); + gameInstance.addPlayer(player); + this.roomServerService.roomEvent("player.joined", gameInstance, user); + + // GAME MODE + const mapPoolIds = pendingMatchmakingGameInstance.commonMapPoolIds; + const randomMapId = mapPoolIds[Math.floor(Math.random() * mapPoolIds.length)]; + gameInstance.gameMode = this.getNewMatchmakingGameMode(randomMapId); + this.roomServerService.roomEvent("game_mode", gameInstance, user); + + // emit game found event when all players have joined + const maxPlayers = ProbableWaffleLevels[randomMapId].mapInfo.startPositionsOnTile.length; + if (gameInstance.players.length === maxPlayers) { + // GAME METADATA + this.promoteGameInstanceToLoaded(gameInstance, user); + } + } + private createGameInstanceForMatchmaking(matchMakingDto: RequestGameSearchForMatchMakingDto, user: User) { + // create new one + const newGameInstance = new ProbableWaffleGameInstance({ + gameInstanceMetadataData: { + name: "Matchmaking", + createdBy: user.id, + type: ProbableWaffleGameInstanceType.Matchmaking, + visibility: ProbableWaffleGameInstanceVisibility.Public, + startOptions: {} + }, + gameModeData: { + winConditions: {} satisfies WinConditions, + mapTuning: {} satisfies MapTuning, + difficultyModifiers: {} satisfies DifficultyModifiers + } satisfies ProbableWaffleGameModeData, + gameStateData: {} as ProbableWaffleGameStateData + }); + + const player = this.getNewPlayer(newGameInstance, user.id, matchMakingDto.factionType); + newGameInstance.addPlayer(player); + + this.pendingMatchmakingGameInstances.push({ + gameInstance: newGameInstance, + commonMapPoolIds: matchMakingDto.mapPoolIds + }); + this.gameInstanceService.addGameInstance(newGameInstance, user); + + console.log("Probable Waffle - Game instance created for matchmaking", this.pendingMatchmakingGameInstances.length); + } + + private findGameInstanceForMatchMaking( + matchMakingDto: RequestGameSearchForMatchMakingDto + ): PendingMatchmakingGameInstance | null { + const availableMatchmakingGameInstances = this.pendingMatchmakingGameInstances.filter( + (gi) => + gi.gameInstance.gameInstanceMetadata.data.sessionState === GameSessionState.NotStarted && + gi.gameInstance.gameInstanceMetadata.data.type === ProbableWaffleGameInstanceType.Matchmaking + ); + + // find game instance that has at least 1 map in common + // noinspection UnnecessaryLocalVariableJS + const foundGameInstanceWithCommonMap = availableMatchmakingGameInstances.find((gi) => { + const mapPoolIds = gi.commonMapPoolIds; + // noinspection UnnecessaryLocalVariableJS + const hasCommonMap = matchMakingDto.mapPoolIds.some((mapId) => mapPoolIds.includes(mapId)); + return hasCommonMap; + }); + + // if found, then remove commonMapPoolIds that are not in matchMakingDto.mapPoolIds + if (foundGameInstanceWithCommonMap) { + foundGameInstanceWithCommonMap.commonMapPoolIds = foundGameInstanceWithCommonMap.commonMapPoolIds.filter( + (mapId) => matchMakingDto.mapPoolIds.includes(mapId) + ); + } + + return foundGameInstanceWithCommonMap ?? null; + } + + private getNewPlayer(gameInstance: ProbableWaffleGameInstance, userId: string, factionType: FactionType | null) { + const allFactions = Object.values(FactionType); + const randomFactionType = allFactions[Math.floor(Math.random() * allFactions.length)] as FactionType; + + const playerDefinition = { + player: { + playerNumber: gameInstance.players.length, + playerName: "Player " + (gameInstance.players.length + 1), + playerPosition: gameInstance.players.length, + joined: true + } satisfies PlayerLobbyDefinition, // TODO THIS IS DUPLICATED EVERYWHERE + factionType: factionType ?? randomFactionType, + playerType: ProbableWafflePlayerType.Human + } satisfies PositionPlayerDefinition; + // noinspection UnnecessaryLocalVariableJS + const player = gameInstance.initPlayer({ + userId, + playerDefinition + } satisfies ProbableWafflePlayerControllerData); + return player; + } + + private getNewMatchmakingGameMode(mapId: ProbableWaffleMapEnum): ProbableWaffleGameMode { + const gameModeData = { + map: mapId, + difficultyModifiers: {} satisfies DifficultyModifiers, + winConditions: { + timeLimit: 60 + } satisfies WinConditions, + mapTuning: { + unitCap: 20 + } satisfies MapTuning + } satisfies ProbableWaffleGameModeData; + return new ProbableWaffleGameMode(gameModeData); + } + + async stopRequestGameSearchForMatchmaking(user: User): Promise { + // remove self as player from pending matchmaking game instances + // if game instance has no players left, then remove it + const pendingMatchMakingGameInstance = this.pendingMatchmakingGameInstances.find((gi) => + gi.gameInstance.players.some((p) => p.playerController.data.userId === user.id) + ); + if (!pendingMatchMakingGameInstance) return; + const gameInstanceId = pendingMatchMakingGameInstance.gameInstance.gameInstanceMetadata.data.gameInstanceId; + const player = pendingMatchMakingGameInstance.gameInstance.players.find( + (p) => p.playerController.data.userId === user.id + ); + if (!player) return; + + this.roomServerService.roomEvent("player.left", pendingMatchMakingGameInstance.gameInstance, user); + pendingMatchMakingGameInstance.gameInstance.players = pendingMatchMakingGameInstance.gameInstance.players.filter( + (p) => p.playerController.data.userId !== user.id + ); + console.log("Probable Waffle - Player left pending matchmaking instance", user.id); + + if (pendingMatchMakingGameInstance.gameInstance.players.length === 0) { + this.removePendingMatchmakingGameInstance(gameInstanceId); + this.gameInstanceService.stopGameInstance(gameInstanceId, user); + } + } + + private removePendingMatchmakingGameInstance(gameInstanceId: string) { + this.pendingMatchmakingGameInstances = this.pendingMatchmakingGameInstances.filter( + (gi) => gi.gameInstance.gameInstanceMetadata.data.gameInstanceId !== gameInstanceId + ); + } +} diff --git a/apps/api/src/app/probable-waffle/probable-waffle.module.ts b/apps/api/src/app/probable-waffle/probable-waffle.module.ts new file mode 100644 index 00000000..0342a319 --- /dev/null +++ b/apps/api/src/app/probable-waffle/probable-waffle.module.ts @@ -0,0 +1,29 @@ +import { Module } from "@nestjs/common"; +import { GameInstanceController } from "./game-instance/game-instance.controller"; +import { GameInstanceService } from "./game-instance/game-instance.service"; +import { GameStateServerService } from "./game-instance/game-state-server.service"; +import { TextSanitizationService } from "../../core/content-filters/text-sanitization.service"; +import { RoomController } from "./game-room/room.controller"; +import { GameInstanceHolderService } from "./game-instance/game-instance-holder.service"; +import { RoomGateway } from "./game-room/room.gateway"; +import { GameInstanceGateway } from "./game-instance/game-instance.gateway"; +import { MatchmakingService } from "./matchmaking/matchmaking.service"; +import { ProbableWaffleChatService } from "./chat/probable-waffle-chat.service"; +import { MatchmakingController } from "./matchmaking/matchmaking.controller"; +import { RoomServerService } from "./game-room/room-server.service"; + +@Module({ + providers: [ + GameInstanceGateway, + RoomGateway, + GameInstanceHolderService, + GameInstanceService, + GameStateServerService, + MatchmakingService, + TextSanitizationService, + ProbableWaffleChatService, + RoomServerService + ], + controllers: [GameInstanceController, RoomController, MatchmakingController] +}) +export class ProbableWaffleModule {} diff --git a/apps/api/src/auth/auth.controller.ts b/apps/api/src/auth/auth.controller.ts deleted file mode 100644 index 1326b9a5..00000000 --- a/apps/api/src/auth/auth.controller.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Controller, Get, Headers, UseGuards } from '@nestjs/common'; -import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; -import { AuthService } from './auth.service'; -import { SupabaseAuthGuard } from './guards/supabase-auth.guard'; - -@Controller('auth') -@ApiTags('authentication') -export class AuthController { - constructor(private readonly authService: AuthService) {} - - // @UseGuards(LocalAuthGuard) - // @Post('auth/login') - // async login(@Req() req) { - // return this.authService.loginJwt(req.user); - // } - - // @UseGuards(JwtAuthGuard) - // @Get('profile') - // getProfile(@Req() req) { - // return req.user; - // } - - @Get('test') - @UseGuards(SupabaseAuthGuard) - @ApiBearerAuth('access-token') - async test(@Headers() headers) { - // let x = 1 - return true; - } - - // @Post('signIn') - // @ApiOperation({ - // summary: 'Acquires an access token', - // description: 'This endpoint will provide an access token.' - // }) - // async signIn(@Body() dto: CreateUserDto) { - // return this.authService.signInUser(dto); - // } - - // @Post('signUp') - // @ApiOperation({ - // summary: 'Signs up the user in the system', - // description: - // 'This endpoint signs up the user in the system. It will return the user details. You will use this user to interact with the rest of the endpoints.' - // }) - // async signUp(@Body() dto: CreateUserDto) { - // return this.authService.signupUser(dto); - // } -} diff --git a/apps/api/src/auth/auth.module.ts b/apps/api/src/auth/auth.module.ts index de9d7a26..7c60b664 100644 --- a/apps/api/src/auth/auth.module.ts +++ b/apps/api/src/auth/auth.module.ts @@ -1,16 +1,15 @@ -import { Module } from '@nestjs/common'; -import { AuthService } from './auth.service'; -import { PassportModule } from '@nestjs/passport'; -import { SupabaseStrategy } from './strategies/supabase.strategy'; -import { AuthController } from './auth.controller'; -import { UsersModule } from '../users/users.module'; -import { jwtConstants } from './constants'; +import { Module } from "@nestjs/common"; +import { AuthService } from "./auth.service"; +import { PassportModule } from "@nestjs/passport"; +import { SupabaseStrategy } from "./strategies/supabase.strategy"; +import { UsersModule } from "../users/users.module"; +import { jwtConstants } from "./constants"; // import { JwtStrategy } from './strategies/jwt.strategy'; // import { LocalStrategy } from './strategies/local.strategy'; -import { ScheduleModule } from '@nestjs/schedule'; -import { JwtModule } from '@nestjs/jwt'; -import { UserAuthCacheService } from '../core/cache/user-auth-cache.service.ts/user-auth-cache.service'; -import { SupabaseProviderService } from '../core/supabase-provider/supabase-provider.service'; +import { ScheduleModule } from "@nestjs/schedule"; +import { JwtModule } from "@nestjs/jwt"; +import { UserAuthCacheService } from "../core/cache/user-auth-cache.service.ts/user-auth-cache.service"; +import { SupabaseProviderService } from "../core/supabase-provider/supabase-provider.service"; @Module({ imports: [ @@ -18,11 +17,10 @@ import { SupabaseProviderService } from '../core/supabase-provider/supabase-prov PassportModule, JwtModule.register({ secret: jwtConstants.secret, - signOptions: { expiresIn: '60s' } + signOptions: { expiresIn: "60s" } }), ScheduleModule.forRoot() ], - controllers: [AuthController], providers: [ AuthService, /*LocalStrategy, JwtStrategy,*/ SupabaseStrategy, diff --git a/apps/api/src/auth/auth.service.ts b/apps/api/src/auth/auth.service.ts index 190606e8..569dfd42 100644 --- a/apps/api/src/auth/auth.service.ts +++ b/apps/api/src/auth/auth.service.ts @@ -1,5 +1,5 @@ -import { Injectable } from '@nestjs/common'; -import { IAuthService } from './auth.service.interface'; +import { Injectable } from "@nestjs/common"; +import { IAuthService } from "./auth.service.interface"; @Injectable() export class AuthService implements IAuthService { diff --git a/apps/api/src/auth/constants.ts b/apps/api/src/auth/constants.ts index 2bdf50ed..b9294f06 100644 --- a/apps/api/src/auth/constants.ts +++ b/apps/api/src/auth/constants.ts @@ -1,3 +1,3 @@ export const jwtConstants = { - secret: 'secretKey' // not really a key - for demo purposes only + secret: "secretKey" // not really a key - for demo purposes only }; diff --git a/apps/api/src/auth/current-user.ts b/apps/api/src/auth/current-user.ts index ca7f1b7c..4377ace3 100644 --- a/apps/api/src/auth/current-user.ts +++ b/apps/api/src/auth/current-user.ts @@ -1,10 +1,10 @@ -import { createParamDecorator, ExecutionContext } from '@nestjs/common'; -import { GqlExecutionContext } from '@nestjs/graphql'; -import { AuthUser } from '@supabase/supabase-js'; +import { createParamDecorator, ExecutionContext } from "@nestjs/common"; +import { GqlExecutionContext } from "@nestjs/graphql"; +import { AuthUser } from "@supabase/supabase-js"; export const CurrentUser = createParamDecorator((_data: unknown, context: ExecutionContext): AuthUser => { const contextType = context.getType(); - if (contextType === 'ws') { + if (contextType === "ws") { // for socket-io, extract user from first index return context.getArgByIndex(0).user as AuthUser; } diff --git a/apps/api/src/auth/dto/create-user.dto.ts b/apps/api/src/auth/dto/create-user.dto.ts index 7649c61c..e551b5aa 100644 --- a/apps/api/src/auth/dto/create-user.dto.ts +++ b/apps/api/src/auth/dto/create-user.dto.ts @@ -1,4 +1,4 @@ -import { ApiProperty } from '@nestjs/swagger'; +import { ApiProperty } from "@nestjs/swagger"; export class CreateUserDto { @ApiProperty() diff --git a/apps/api/src/auth/guards/supabase-auth.guard.ts b/apps/api/src/auth/guards/supabase-auth.guard.ts index 541bdbca..5b33d26f 100644 --- a/apps/api/src/auth/guards/supabase-auth.guard.ts +++ b/apps/api/src/auth/guards/supabase-auth.guard.ts @@ -1,6 +1,6 @@ -import { Injectable } from '@nestjs/common'; -import { AuthGuard } from '@nestjs/passport'; -import { AuthStrategies } from '../strategies/auth-strategies'; +import { Injectable } from "@nestjs/common"; +import { AuthGuard } from "@nestjs/passport"; +import { AuthStrategies } from "../strategies/auth-strategies"; @Injectable() export class SupabaseAuthGuard extends AuthGuard(AuthStrategies.supabase) {} diff --git a/apps/api/src/auth/strategies/auth-strategies.ts b/apps/api/src/auth/strategies/auth-strategies.ts index 8d6cd823..50ef3312 100644 --- a/apps/api/src/auth/strategies/auth-strategies.ts +++ b/apps/api/src/auth/strategies/auth-strategies.ts @@ -1,5 +1,5 @@ export enum AuthStrategies { // local = 'local', // jwt = 'jwt', - supabase = 'supabase' + supabase = "supabase" } diff --git a/apps/api/src/auth/strategies/supabase-auth-strategy/constants.ts b/apps/api/src/auth/strategies/supabase-auth-strategy/constants.ts index e0a67123..2458109b 100644 --- a/apps/api/src/auth/strategies/supabase-auth-strategy/constants.ts +++ b/apps/api/src/auth/strategies/supabase-auth-strategy/constants.ts @@ -1,4 +1,4 @@ -const UNAUTHORIZED = 'Unauthorized'; -const SUPABASE_AUTH = 'SUPABASE_AUTH'; +const UNAUTHORIZED = "Unauthorized"; +const SUPABASE_AUTH = "SUPABASE_AUTH"; export { UNAUTHORIZED, SUPABASE_AUTH }; diff --git a/apps/api/src/auth/strategies/supabase-auth-strategy/options.interface.ts b/apps/api/src/auth/strategies/supabase-auth-strategy/options.interface.ts index 72c98e88..124dfc8a 100644 --- a/apps/api/src/auth/strategies/supabase-auth-strategy/options.interface.ts +++ b/apps/api/src/auth/strategies/supabase-auth-strategy/options.interface.ts @@ -1,6 +1,6 @@ -import { JwtFromRequestFunction } from 'passport-jwt'; -import { SupabaseClient } from '@supabase/supabase-js'; -import { UserAuthCacheService } from '../../../core/cache/user-auth-cache.service.ts/user-auth-cache.service'; +import { JwtFromRequestFunction } from "passport-jwt"; +import { SupabaseClient } from "@supabase/supabase-js"; +import { UserAuthCacheService } from "../../../core/cache/user-auth-cache.service.ts/user-auth-cache.service"; export interface SupabaseAuthStrategyOptions { supabaseClient: SupabaseClient; diff --git a/apps/api/src/auth/strategies/supabase-auth-strategy/supabase-v2-auth.strategy.ts b/apps/api/src/auth/strategies/supabase-auth-strategy/supabase-v2-auth.strategy.ts index 7faf9177..75ea534c 100644 --- a/apps/api/src/auth/strategies/supabase-auth-strategy/supabase-v2-auth.strategy.ts +++ b/apps/api/src/auth/strategies/supabase-auth-strategy/supabase-v2-auth.strategy.ts @@ -1,9 +1,9 @@ -import { JwtFromRequestFunction } from 'passport-jwt'; -import { Strategy } from 'passport-strategy'; -import { AuthUser, SupabaseClient } from '@supabase/supabase-js'; -import { SUPABASE_AUTH, UNAUTHORIZED } from './constants'; -import { SupabaseAuthStrategyOptions } from './options.interface'; -import { UserAuthCacheService } from '../../../core/cache/user-auth-cache.service.ts/user-auth-cache.service'; +import { JwtFromRequestFunction } from "passport-jwt"; +import { Strategy } from "passport-strategy"; +import { AuthUser, SupabaseClient } from "@supabase/supabase-js"; +import { SUPABASE_AUTH, UNAUTHORIZED } from "./constants"; +import { SupabaseAuthStrategyOptions } from "./options.interface"; +import { UserAuthCacheService } from "../../../core/cache/user-auth-cache.service.ts/user-auth-cache.service"; // fixes https://github.com/hiro1107/nestjs-supabase-auth/issues/7 // uses node-cache to cache users for 1 hour @@ -12,7 +12,7 @@ export class SupabaseV2AuthStrategy extends Strategy { private extractor: JwtFromRequestFunction; success: (user: any, info: any) => void; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - fail: Strategy['fail']; + fail: Strategy["fail"]; private readonly userAuthCacheService: UserAuthCacheService; private supabaseClient: SupabaseClient; @@ -22,7 +22,7 @@ export class SupabaseV2AuthStrategy extends Strategy { this.supabaseClient = options.supabaseClient; if (!options.extractor) { throw new Error( - '\n Extractor is not a function. You should provide an extractor. \n Read the docs: https://github.com/tfarras/nestjs-firebase-auth#readme' + "\n Extractor is not a function. You should provide an extractor. \n Read the docs: https://github.com/tfarras/nestjs-firebase-auth#readme" ); } this.extractor = options.extractor; diff --git a/apps/api/src/core/cache/user-auth-cache.service.ts/user-auth-cache.service.spec.ts b/apps/api/src/core/cache/user-auth-cache.service.ts/user-auth-cache.service.spec.ts index 9185cf7d..0fb47706 100644 --- a/apps/api/src/core/cache/user-auth-cache.service.ts/user-auth-cache.service.spec.ts +++ b/apps/api/src/core/cache/user-auth-cache.service.ts/user-auth-cache.service.spec.ts @@ -1,8 +1,8 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { UserAuthCacheService } from './user-auth-cache.service'; -import { authUserStub } from '../../../auth/auth.service.spec'; +import { Test, TestingModule } from "@nestjs/testing"; +import { UserAuthCacheService } from "./user-auth-cache.service"; +import { authUserStub } from "../../../auth/auth.service.spec"; -describe('UserAuthCacheService', () => { +describe("UserAuthCacheService", () => { let service: UserAuthCacheService; beforeEach(async () => { @@ -13,13 +13,13 @@ describe('UserAuthCacheService', () => { service = module.get(UserAuthCacheService); }); - it('should be defined', () => { + it("should be defined", () => { expect(service).toBeDefined(); }); - it('should set and get user', () => { + it("should set and get user", () => { const user = authUserStub; - const idToken = 'idToken'; + const idToken = "idToken"; service.setUser(idToken, user); expect(service.getUser(idToken)).toEqual(user); }); diff --git a/apps/api/src/core/cache/user-auth-cache.service.ts/user-auth-cache.service.ts b/apps/api/src/core/cache/user-auth-cache.service.ts/user-auth-cache.service.ts index f6794355..f37934a8 100644 --- a/apps/api/src/core/cache/user-auth-cache.service.ts/user-auth-cache.service.ts +++ b/apps/api/src/core/cache/user-auth-cache.service.ts/user-auth-cache.service.ts @@ -1,6 +1,6 @@ -import { Injectable } from '@nestjs/common'; -import NodeCache from 'node-cache'; -import { AuthUser } from '@supabase/supabase-js'; +import { Injectable } from "@nestjs/common"; +import NodeCache from "node-cache"; +import { AuthUser } from "@supabase/supabase-js"; @Injectable() export class UserAuthCacheService { diff --git a/apps/api/src/core/content-filters/slovenian-bad-words.ts b/apps/api/src/core/content-filters/slovenian-bad-words.ts index 72a60691..bb0b7406 100644 --- a/apps/api/src/core/content-filters/slovenian-bad-words.ts +++ b/apps/api/src/core/content-filters/slovenian-bad-words.ts @@ -1,208 +1,208 @@ export const slovenianBadWords = [ - 'bes te plentaj', - 'bingala bongala', - 'bog te nima rad', - 'cepec', - 'da bi te koklja brcnila', - 'drek', - 'drekec pekec', - 'drkati', - 'frdaman', - 'fuk', - 'holtnahamol', - 'hop cefizelj', - 'hudič', - 'hudičevka', - 'iti v maloro', - 'jeba', - 'jebat ga', - 'Jebela cesta', - 'jebemti mater kosmato', - 'jebemti uha', - 'jebo ti pas mater kad te napravio', - 'jejtena', - 'ježešna', - 'klinc te gleda', - 'ko ga šiša', - 'konjski kurac', - 'kot da bi konj v senco rital', - 'Krucefiks', - 'krucinalbanda', - 'krščen duš', - 'kuga salamenska', - 'kurac', - 'kurac palac', - 'kurenk', - 'madona', - 'mandi mandi', - 'mater', - 'mašinca', - 'moj bog', - 'naj te strela udari', - 'ne biti vreden počenega groša', - 'ne jebati', - 'obrisati si šobe', - 'pejd u tri pirovske', - 'pejt v rit', - 'pismo', - 'pizda', - 'pišuka', - 'pička', - 'pička ti materina', - 'pičkin dim', - 'pičkuračku', - 'poslati v kurilnico', - 'poslati v picerijo', - 'poslati v pisarno', - 'pribijati', - 'prmejdun', - 'tri krasne', - 'vrag te pocitraj', - 'čka', - 'čri čri', - 'aškrc', - 'bes te plentaj', - 'bingala bongala', - 'bog te nima rad', - 'cepec', - 'da bi te koklja brcnila', - 'da bi te pes povohal', - 'drek', - 'drekec pekec', - 'drkati', - 'fakof', - 'fekalc', - 'frdaman', - 'fuk', - 'holtnahamol', - 'hop cefizelj', - 'hudič', - 'hudičevka', - 'hvala za rože', - 'iti na š', - 'iti v maloro', - 'jeba', - 'jebat ga', - 'Jebela cesta', - 'jebemti mater kosmato', - 'jebemti uha', - 'jebi ga - original', - 'jebo ti pas mater kad te napravio', - 'jejtena', - 'ježešna', - 'joj prejoj', - 'klinc te gleda', - 'ko ga šiša', - 'ko jebe Bosance', - 'konjski kurac', - 'kot da bi konj v senco rital', - 'krizus', - 'Krucefiks', - 'krucinalbanda', - 'krščen duš', - 'krščen matiček', - 'kuga salamenska', - 'kurac', - 'kurac palac', - 'kurenk', - 'madona', - 'mandi mandi', - 'mater', - 'mašinca', - 'mička', - 'moj bog', - 'naj te strela udari', - 'ne biti vreden počenega groša', - 'ne jebati', - 'obrisati si šobe', - 'Ojdip', - 'orkadibigoli', - 'pejd u tri pirovske', - 'pejt v rit', - 'pismo', - 'pizda', - 'Pizda mi greš na kurac tip mona ej', - 'pizda na robu gozda', - 'pišuka', - 'pička', - 'pička ti materina', - 'pička ti žlička', - 'pičkin dim', - 'Pičkopeja', - 'pičkuračku', - 'pojdi v pisarno', - 'porkadindijo', - 'poslati v kurilnico', - 'poslati v picerijo', - 'poslati v pisarno', - 'preklezakurnk', - 'Preklinc', - 'pribijati', - 'primaruha', - 'prmejdun', - 'ps ti brado lizu', - 'Svija', - 'tri krasne', - 'vrag te pocitraj', - 'Vragu na roge', - 'včeraj sem juho jedel', - 'za poflancat', - 'zajebati', - 'šajeta te hitla u laštrigo od bandant', - 'čka', - 'čkapi', - 'čri čri', - 'bes te plentaj', - 'bingala bongala', - 'bog te nima rad', - 'cepec', - 'da bi te koklja brcnila', - 'drek', - 'drekec pekec', - 'frdaman', - 'fuk', - 'holtnahamol', - 'hop cefizelj', - 'hudič', - 'hudičevka', - 'iti v maloro', - 'jebat ga', - 'jebemti mater kosmato', - 'jebo ti pas mater kad te napravio', - 'jejtena', - 'ježešna', - 'klinc te gleda', - 'ko ga šiša', - 'konjski kurac', - 'kot da bi konj v senco rital', - 'Krucefiks', - 'krucinalbanda', - 'krščen duš', - 'kuga salamenska', - 'kurac palac', - 'madona', - 'mandi mandi', - 'mater', - 'mašinca', - 'moj bog', - 'naj te strela udari', - 'ne biti vreden počenega groša', - 'ne jebati', - 'obrisati si šobe', - 'pejt v rit', - 'pismo', - 'pizda', - 'pišuka', - 'pička', - 'pička ti materina', - 'pičkuračku', - 'poslati v kurilnico', - 'poslati v picerijo', - 'poslati v pisarno', - 'Preklinc', - 'pribijati', - 'prmejdun', - 'tri krasne', - 'čka', - 'tristo kosmatih medvedov' + "bes te plentaj", + "bingala bongala", + "bog te nima rad", + "cepec", + "da bi te koklja brcnila", + "drek", + "drekec pekec", + "drkati", + "frdaman", + "fuk", + "holtnahamol", + "hop cefizelj", + "hudič", + "hudičevka", + "iti v maloro", + "jeba", + "jebat ga", + "Jebela cesta", + "jebemti mater kosmato", + "jebemti uha", + "jebo ti pas mater kad te napravio", + "jejtena", + "ježešna", + "klinc te gleda", + "ko ga šiša", + "konjski kurac", + "kot da bi konj v senco rital", + "Krucefiks", + "krucinalbanda", + "krščen duš", + "kuga salamenska", + "kurac", + "kurac palac", + "kurenk", + "madona", + "mandi mandi", + "mater", + "mašinca", + "moj bog", + "naj te strela udari", + "ne biti vreden počenega groša", + "ne jebati", + "obrisati si šobe", + "pejd u tri pirovske", + "pejt v rit", + "pismo", + "pizda", + "pišuka", + "pička", + "pička ti materina", + "pičkin dim", + "pičkuračku", + "poslati v kurilnico", + "poslati v picerijo", + "poslati v pisarno", + "pribijati", + "prmejdun", + "tri krasne", + "vrag te pocitraj", + "čka", + "čri čri", + "aškrc", + "bes te plentaj", + "bingala bongala", + "bog te nima rad", + "cepec", + "da bi te koklja brcnila", + "da bi te pes povohal", + "drek", + "drekec pekec", + "drkati", + "fakof", + "fekalc", + "frdaman", + "fuk", + "holtnahamol", + "hop cefizelj", + "hudič", + "hudičevka", + "hvala za rože", + "iti na š", + "iti v maloro", + "jeba", + "jebat ga", + "Jebela cesta", + "jebemti mater kosmato", + "jebemti uha", + "jebi ga - original", + "jebo ti pas mater kad te napravio", + "jejtena", + "ježešna", + "joj prejoj", + "klinc te gleda", + "ko ga šiša", + "ko jebe Bosance", + "konjski kurac", + "kot da bi konj v senco rital", + "krizus", + "Krucefiks", + "krucinalbanda", + "krščen duš", + "krščen matiček", + "kuga salamenska", + "kurac", + "kurac palac", + "kurenk", + "madona", + "mandi mandi", + "mater", + "mašinca", + "mička", + "moj bog", + "naj te strela udari", + "ne biti vreden počenega groša", + "ne jebati", + "obrisati si šobe", + "Ojdip", + "orkadibigoli", + "pejd u tri pirovske", + "pejt v rit", + "pismo", + "pizda", + "Pizda mi greš na kurac tip mona ej", + "pizda na robu gozda", + "pišuka", + "pička", + "pička ti materina", + "pička ti žlička", + "pičkin dim", + "Pičkopeja", + "pičkuračku", + "pojdi v pisarno", + "porkadindijo", + "poslati v kurilnico", + "poslati v picerijo", + "poslati v pisarno", + "preklezakurnk", + "Preklinc", + "pribijati", + "primaruha", + "prmejdun", + "ps ti brado lizu", + "Svija", + "tri krasne", + "vrag te pocitraj", + "Vragu na roge", + "včeraj sem juho jedel", + "za poflancat", + "zajebati", + "šajeta te hitla u laštrigo od bandant", + "čka", + "čkapi", + "čri čri", + "bes te plentaj", + "bingala bongala", + "bog te nima rad", + "cepec", + "da bi te koklja brcnila", + "drek", + "drekec pekec", + "frdaman", + "fuk", + "holtnahamol", + "hop cefizelj", + "hudič", + "hudičevka", + "iti v maloro", + "jebat ga", + "jebemti mater kosmato", + "jebo ti pas mater kad te napravio", + "jejtena", + "ježešna", + "klinc te gleda", + "ko ga šiša", + "konjski kurac", + "kot da bi konj v senco rital", + "Krucefiks", + "krucinalbanda", + "krščen duš", + "kuga salamenska", + "kurac palac", + "madona", + "mandi mandi", + "mater", + "mašinca", + "moj bog", + "naj te strela udari", + "ne biti vreden počenega groša", + "ne jebati", + "obrisati si šobe", + "pejt v rit", + "pismo", + "pizda", + "pišuka", + "pička", + "pička ti materina", + "pičkuračku", + "poslati v kurilnico", + "poslati v picerijo", + "poslati v pisarno", + "Preklinc", + "pribijati", + "prmejdun", + "tri krasne", + "čka", + "tristo kosmatih medvedov" ]; diff --git a/apps/api/src/core/content-filters/text-sanitization.service.ts b/apps/api/src/core/content-filters/text-sanitization.service.ts index 7aef534c..6968a537 100644 --- a/apps/api/src/core/content-filters/text-sanitization.service.ts +++ b/apps/api/src/core/content-filters/text-sanitization.service.ts @@ -1,7 +1,7 @@ -import { Injectable } from '@nestjs/common'; -import { ITextSanitizationService } from './text-sanitization.service.interface'; -import { clean } from 'profanity-cleaner'; -import { slovenianBadWords } from './slovenian-bad-words'; +import { Injectable } from "@nestjs/common"; +import { ITextSanitizationService } from "./text-sanitization.service.interface"; +import { clean } from "profanity-cleaner"; +import { slovenianBadWords } from "./slovenian-bad-words"; @Injectable() export class TextSanitizationService implements ITextSanitizationService { diff --git a/apps/api/src/core/supabase-provider/supabase-provider.service.interface.ts b/apps/api/src/core/supabase-provider/supabase-provider.service.interface.ts index 0e776873..7f629686 100644 --- a/apps/api/src/core/supabase-provider/supabase-provider.service.interface.ts +++ b/apps/api/src/core/supabase-provider/supabase-provider.service.interface.ts @@ -1,4 +1,4 @@ -import { SupabaseClient } from '@supabase/supabase-js'; +import { SupabaseClient } from "@supabase/supabase-js"; export interface ISupabaseProviderService { get supabaseClient(): SupabaseClient; diff --git a/apps/api/src/core/supabase-provider/supabase-provider.service.ts b/apps/api/src/core/supabase-provider/supabase-provider.service.ts index 22706591..d1fc10c9 100644 --- a/apps/api/src/core/supabase-provider/supabase-provider.service.ts +++ b/apps/api/src/core/supabase-provider/supabase-provider.service.ts @@ -1,6 +1,6 @@ -import { Injectable } from '@nestjs/common'; -import { createClient, SupabaseClient } from '@supabase/supabase-js'; -import { ISupabaseProviderService } from './supabase-provider.service.interface'; +import { Injectable } from "@nestjs/common"; +import { createClient, SupabaseClient } from "@supabase/supabase-js"; +import { ISupabaseProviderService } from "./supabase-provider.service.interface"; @Injectable() export class SupabaseProviderService implements ISupabaseProviderService { diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index d525d9ad..fc18fc99 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -3,36 +3,35 @@ * This is only a minimal backend to get started. */ -import { Logger, ValidationPipe } from '@nestjs/common'; -import { NestFactory } from '@nestjs/core'; +import { Logger, ValidationPipe } from "@nestjs/common"; +import { NestFactory } from "@nestjs/core"; -import { AppModule } from './app/app.module'; -import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; -import helmet from 'helmet'; +import { AppModule } from "./app/app.module"; +import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger"; +import helmet from "helmet"; async function bootstrap() { const app = await NestFactory.create(AppModule); - const globalPrefix = 'api'; + const globalPrefix = "api"; app.setGlobalPrefix(globalPrefix); const port = process.env.PORT || 3333; app.useGlobalPipes(new ValidationPipe()); - if (process.env.NODE_ENV === 'development') { + if (process.env.NODE_ENV === "development") { // swagger setup const config = new DocumentBuilder() - .setTitle('Fuzzy waddle API') - .setDescription('This API helps you manage fuzzy waddle data!') - .setVersion('1.0') - .addTag('calendar') - .addBearerAuth({ in: 'header', type: 'http', scheme: 'bearer', bearerFormat: 'JWT' }, 'access-token') + .setTitle("Fuzzy waddle API") + .setDescription("This API helps you manage fuzzy waddle data!") + .setVersion("1.0") + .addBearerAuth({ in: "header", type: "http", scheme: "bearer", bearerFormat: "JWT" }, "access-token") .build(); const document = SwaggerModule.createDocument(app, config); - SwaggerModule.setup('docs', app, document); + SwaggerModule.setup("docs", app, document); // navigate to /docs to see the swagger docs } app.enableCors({ - origin: process.env.CORS_ORIGIN?.split(',') + origin: process.env.CORS_ORIGIN?.split(",") }); // https://docs.nestjs.com/security/helmet app.use(helmet()); diff --git a/apps/api/src/users/users.module.ts b/apps/api/src/users/users.module.ts index bd425994..1f1ce3bb 100644 --- a/apps/api/src/users/users.module.ts +++ b/apps/api/src/users/users.module.ts @@ -1,5 +1,5 @@ -import { Module } from '@nestjs/common'; -import { UsersService } from './users.service'; +import { Module } from "@nestjs/common"; +import { UsersService } from "./users.service"; @Module({ providers: [UsersService], diff --git a/apps/api/src/users/users.service.interface.ts b/apps/api/src/users/users.service.interface.ts index f586f31c..2529e28f 100644 --- a/apps/api/src/users/users.service.interface.ts +++ b/apps/api/src/users/users.service.interface.ts @@ -1,4 +1,4 @@ -import { User } from './users.service'; +import { User } from "./users.service"; export interface IUsersService { findOne(username: string): Promise; diff --git a/apps/api/src/users/users.service.ts b/apps/api/src/users/users.service.ts index 89eee8fb..a432d2bf 100644 --- a/apps/api/src/users/users.service.ts +++ b/apps/api/src/users/users.service.ts @@ -1,5 +1,5 @@ -import { Injectable } from '@nestjs/common'; -import { IUsersService } from './users.service.interface'; +import { Injectable } from "@nestjs/common"; +import { IUsersService } from "./users.service.interface"; // This should be a real class/interface representing a user entity export type User = any; @@ -9,13 +9,13 @@ export class UsersService implements IUsersService { private readonly users = [ { userId: 1, - username: 'john', - password: 'changeme' + username: "john", + password: "changeme" }, { userId: 2, - username: 'maria', - password: 'guess' + username: "maria", + password: "guess" } ]; diff --git a/apps/client/.eslintrc.json b/apps/client/.eslintrc.json index 7760bf44..1565f06c 100644 --- a/apps/client/.eslintrc.json +++ b/apps/client/.eslintrc.json @@ -29,7 +29,8 @@ "@typescript-eslint/no-inferrable-types": "off", "@typescript-eslint/no-unused-vars": "warn", "@typescript-eslint/no-unsafe-declaration-merging": "warn", - "@typescript-eslint/triple-slash-reference": "warn" + "@typescript-eslint/triple-slash-reference": "warn", + "no-case-declarations": "off" } }, { diff --git a/apps/client/jest.config.ts b/apps/client/jest.config.ts index b308a8f4..5b54f8c7 100644 --- a/apps/client/jest.config.ts +++ b/apps/client/jest.config.ts @@ -1,22 +1,25 @@ /* eslint-disable */ export default { - displayName: 'client', - preset: '../../jest.preset.js', - setupFilesAfterEnv: ['/src/test-setup.ts'], - coverageDirectory: '../../coverage/apps/client', + displayName: "client", + preset: "../../jest.preset.js", + setupFilesAfterEnv: ["/src/test-setup.ts"], + coverageDirectory: "../../coverage/apps/client", transform: { - '^.+\\.(ts|mjs|js|html)$': [ - 'jest-preset-angular', + "^.+\\.(ts|mjs|js|html)$": [ + "jest-preset-angular", { - tsconfig: '/tsconfig.spec.json', - stringifyContentPathRegex: '\\.(html|svg)$' + tsconfig: "/tsconfig.spec.json", + stringifyContentPathRegex: "\\.(html|svg)$" } ] }, - transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'], + transformIgnorePatterns: ["node_modules/(?!.*\\.mjs$)"], snapshotSerializers: [ - 'jest-preset-angular/build/serializers/no-ng-attributes', - 'jest-preset-angular/build/serializers/ng-snapshot', - 'jest-preset-angular/build/serializers/html-comment' - ] + "jest-preset-angular/build/serializers/no-ng-attributes", + "jest-preset-angular/build/serializers/ng-snapshot", + "jest-preset-angular/build/serializers/html-comment" + ], + moduleNameMapper: { + "^lodash-es$": "lodash" + } }; diff --git a/apps/client/src/app/app-routing.module.ts b/apps/client/src/app/app-routing.module.ts index fd1d641e..2bbdbc09 100644 --- a/apps/client/src/app/app-routing.module.ts +++ b/apps/client/src/app/app-routing.module.ts @@ -1,36 +1,149 @@ -import { NgModule } from '@angular/core'; -import { RouterModule, Routes } from '@angular/router'; -import { HomePageComponent } from './home/page/home-page.component'; -import { ProfileComponent } from './home/profile/profile.component'; -import { AuthGuard } from './auth/auth.guard'; +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; +import { AuthGuard } from "./auth/auth.guard"; +import { LevelGuard } from "./fly-squasher/choose-level/level.guard"; +import { environment } from "../environments/environment"; +import { GameInstanceGuard } from "./probable-waffle/gui/online/lobby-page/game-instance.guard"; -const routes: Routes = [ +const littleMuncherRoutes = [ { - path: '', - component: HomePageComponent - }, + path: "little-muncher", + children: [ + { + path: "", + loadComponent: () => import("./little-muncher/little-muncher.component").then((m) => m.LittleMuncherComponent) + }, + { path: "**", redirectTo: "" } + ] + } +] satisfies Routes; + +const probableWaffleRoutes = [ { - path: 'profile', - component: ProfileComponent, - canActivate: [AuthGuard] - }, + path: "probable-waffle", + children: [ + { + path: "", + loadComponent: () => + import("./probable-waffle/gui/router/probable-waffle.component").then((m) => m.ProbableWaffleComponent), + children: [ + { + path: "", + loadComponent: () => import("./probable-waffle/gui/home/home.component").then((m) => m.HomeComponent) + }, + { + path: "campaign", + loadComponent: () => + import("./probable-waffle/gui/campaign/campaign.component").then((m) => m.CampaignComponent) + }, + { + path: "online", + loadComponent: () => import("./probable-waffle/gui/online/online.component").then((m) => m.OnlineComponent) + }, + { + path: "skirmish", + loadComponent: () => + import("./probable-waffle/gui/skirmish/skirmish.component").then((m) => m.SkirmishComponent) + }, + { + path: "instant-game", + loadComponent: () => + import("./probable-waffle/gui/instant-game/instant-game.component").then((m) => m.InstantGameComponent), + canActivate: [() => !environment.production] + }, + { + path: "load", + loadComponent: () => import("./probable-waffle/gui/load/load.component").then((m) => m.LoadComponent) + }, + { + path: "replay", + loadComponent: () => import("./probable-waffle/gui/replay/replay.component").then((m) => m.ReplayComponent) + }, + { + path: "progress", + loadComponent: () => + import("./probable-waffle/gui/progress/progress.component").then((m) => m.ProgressComponent) + }, + { + path: "options", + loadComponent: () => + import("./probable-waffle/gui/options/options.component").then((m) => m.OptionsComponent) + }, + { + path: "lobby", + loadComponent: () => import("./probable-waffle/gui/lobby/lobby.component").then((m) => m.LobbyComponent), + canActivate: [GameInstanceGuard] + }, + { + path: "score-screen", + loadComponent: () => + import("./probable-waffle/gui/score-screen/score-screen.component").then((m) => m.ScoreScreenComponent), + canActivate: [GameInstanceGuard] + }, + { + path: "game", + loadComponent: () => + import("./probable-waffle/gui/main/probable-waffle-game.component").then( + (m) => m.ProbableWaffleGameComponent + ), + canActivate: [GameInstanceGuard] + } + ] + }, + { path: "**", redirectTo: "home" } + ] + } +] satisfies Routes; + +const flySquasherRoutes = [ { - path: 'little-muncher', - loadChildren: () => import('./little-muncher/little-muncher.module').then((m) => m.LittleMuncherModule) - }, + path: "fly-squasher", + children: [ + { + path: "", + loadComponent: () => import("./fly-squasher/home/home.component").then((m) => m.HomeComponent) + }, + { + path: "choose-level", + loadComponent: () => + import("./fly-squasher/choose-level/choose-level.component").then((m) => m.ChooseLevelComponent) + }, + { + path: "play/:level", + loadComponent: () => import("./fly-squasher/main/main.component").then((m) => m.MainComponent), + canActivate: [LevelGuard] + }, + { + path: "high-score", + loadComponent: () => import("./fly-squasher/high-score/high-score.component").then((m) => m.HighScoreComponent) + }, + { + path: "options", + loadComponent: () => import("./fly-squasher/options/options.component").then((m) => m.OptionsComponent) + }, + { path: "**", redirectTo: "" } + ] + } +] satisfies Routes; + +const routes = [ { - path: 'probable-waffle', - loadChildren: () => import('./probable-waffle/gui/main/probable-waffle.module').then((m) => m.ProbableWaffleModule) + path: "", + loadComponent: () => import("./home/page/home-page.component").then((m) => m.HomePageComponent) }, { - path: 'fly-squasher', - loadChildren: () => import('./fly-squasher/fly-squasher.module').then((m) => m.FlySquasherModule) + path: "profile", + loadComponent: () => import("./home/profile/profile.component").then((m) => m.ProfileComponent), + canActivate: [AuthGuard] }, + ...littleMuncherRoutes, + ...probableWaffleRoutes, + ...flySquasherRoutes, { - path: '**', - redirectTo: '' + path: "**", + redirectTo: "" } -]; +] satisfies Routes; @NgModule({ imports: [ diff --git a/apps/client/src/app/app.component.html b/apps/client/src/app/app.component.html index 74069010..08ea6929 100644 --- a/apps/client/src/app/app.component.html +++ b/apps/client/src/app/app.component.html @@ -1,3 +1,3 @@ - + - + diff --git a/apps/client/src/app/app.component.spec.ts b/apps/client/src/app/app.component.spec.ts index d9ff90fe..f91f22dc 100644 --- a/apps/client/src/app/app.component.spec.ts +++ b/apps/client/src/app/app.component.spec.ts @@ -1,17 +1,18 @@ -import { TestBed, waitForAsync } from '@angular/core/testing'; -import { AppComponent } from './app.component'; -import { RouterTestingModule } from '@angular/router/testing'; -import { ServerHealthService } from './shared/services/server-health.service'; -import { serverHealthServiceStub } from './shared/services/server-health.service.spec'; -import { AuthService } from './auth/auth.service'; -import { authServiceStub } from './auth/auth.service.spec'; -import { SwRefreshTestingComponent } from './shared/components/sw-refresh/sw-refresh.component.spec'; +import { TestBed, waitForAsync } from "@angular/core/testing"; +import { AppComponent } from "./app.component"; +import { RouterTestingModule } from "@angular/router/testing"; +import { ServerHealthService } from "./shared/services/server-health.service"; +import { serverHealthServiceStub } from "./shared/services/server-health.service.spec"; +import { AuthService } from "./auth/auth.service"; +import { authServiceStub } from "./auth/auth.service.spec"; +import { SwRefreshTestingComponent } from "./shared/components/sw-refresh/sw-refresh.component.spec"; +import { HomeComponent } from "./little-muncher/home/home.component"; +import { SwRefreshComponent } from "./shared/components/sw-refresh/sw-refresh.component"; -describe('AppComponent', () => { +describe("AppComponent", () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - declarations: [AppComponent, SwRefreshTestingComponent], - imports: [RouterTestingModule], + imports: [AppComponent, RouterTestingModule, SwRefreshTestingComponent], providers: [ { provide: ServerHealthService, @@ -22,10 +23,19 @@ describe('AppComponent', () => { useValue: authServiceStub } ] - }).compileComponents(); + }) + .overrideComponent(AppComponent, { + remove: { + imports: [SwRefreshComponent] + }, + add: { + imports: [SwRefreshTestingComponent] + } + }) + .compileComponents(); })); - it('should create the app', () => { + it("should create the app", () => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.debugElement.componentInstance; expect(app).toBeTruthy(); diff --git a/apps/client/src/app/app.component.ts b/apps/client/src/app/app.component.ts index 7287becc..215aec33 100644 --- a/apps/client/src/app/app.component.ts +++ b/apps/client/src/app/app.component.ts @@ -1,14 +1,19 @@ -import { Component, OnInit } from '@angular/core'; -import { AuthService } from './auth/auth.service'; -import { ServerHealthService } from './shared/services/server-health.service'; +import { Component, inject, OnInit } from "@angular/core"; +import { AuthService } from "./auth/auth.service"; +import { ServerHealthService } from "./shared/services/server-health.service"; +import { SwRefreshComponent } from "./shared/components/sw-refresh/sw-refresh.component"; +import { RouterOutlet } from "@angular/router"; @Component({ - selector: 'fuzzy-waddle-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'] + selector: "fuzzy-waddle-root", + templateUrl: "./app.component.html", + styleUrls: ["./app.component.scss"], + standalone: true, + imports: [RouterOutlet, SwRefreshComponent] }) export class AppComponent implements OnInit { - constructor(protected readonly authService: AuthService, private readonly serverHealthService: ServerHealthService) {} + protected readonly authService = inject(AuthService); + private readonly serverHealthService = inject(ServerHealthService); async ngOnInit() { await Promise.all([this.serverHealthService.checkHealth(), this.authService.autoSignIn()]); diff --git a/apps/client/src/app/app.module.ts b/apps/client/src/app/app.module.ts deleted file mode 100644 index 5da24440..00000000 --- a/apps/client/src/app/app.module.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { isDevMode, NgModule } from '@angular/core'; -import { BrowserModule } from '@angular/platform-browser'; -import { AppComponent } from './app.component'; -import { AppRoutingModule } from './app-routing.module'; -import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'; -import { ServiceWorkerModule } from '@angular/service-worker'; -import { HomeModule } from './home/home.module'; -import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; -import { AuthGuard } from './auth/auth.guard'; -import { SocketIoModule } from 'ngx-socket-io'; -import { environment } from '../environments/environment'; -import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; -import { ComponentsModule } from './shared/components/components.module'; -import { AccessTokenInterceptor } from './auth/access-token.interceptor'; -import { LoaderComponent } from './shared/loader/loader.component'; - -@NgModule({ - declarations: [AppComponent], - imports: [ - BrowserModule, - HttpClientModule, - HomeModule, - // app routing module must be included last, as it contains the wildcard route - AppRoutingModule, - ServiceWorkerModule.register('ngsw-worker.js', { - enabled: !isDevMode(), - // Register the ServiceWorker as soon as the application is stable - // or after 30 seconds (whichever comes first). - registrationStrategy: 'registerWhenStable:30000' - }), - SocketIoModule.forRoot(environment.socketIoConfig), - FontAwesomeModule, - NgbModule, - ComponentsModule, - LoaderComponent - ], - providers: [AuthGuard, { provide: HTTP_INTERCEPTORS, useClass: AccessTokenInterceptor, multi: true }], - bootstrap: [AppComponent] -}) -export class AppModule {} diff --git a/apps/client/src/app/auth/access-token.interceptor.ts b/apps/client/src/app/auth/access-token.interceptor.ts index d0edec5b..56d5de8a 100644 --- a/apps/client/src/app/auth/access-token.interceptor.ts +++ b/apps/client/src/app/auth/access-token.interceptor.ts @@ -1,13 +1,13 @@ -import { Injectable } from '@angular/core'; -import { AuthService } from './auth.service'; -import { HttpEvent, HttpHandler, HttpHeaders, HttpInterceptor, HttpRequest } from '@angular/common/http'; -import { Observable } from 'rxjs'; +import { inject, Injectable } from "@angular/core"; +import { AuthService } from "./auth.service"; +import { HttpEvent, HttpHandler, HttpHeaders, HttpInterceptor, HttpRequest } from "@angular/common/http"; +import { Observable } from "rxjs"; @Injectable({ - providedIn: 'root' + providedIn: "root" }) export class AccessTokenInterceptor implements HttpInterceptor { - constructor(private authService: AuthService) {} + private readonly authService = inject(AuthService); intercept(request: HttpRequest, next: HttpHandler): Observable> { let _request = request; diff --git a/apps/client/src/app/auth/auth.guard.ts b/apps/client/src/app/auth/auth.guard.ts index e18bd8b7..45d3f8f0 100644 --- a/apps/client/src/app/auth/auth.guard.ts +++ b/apps/client/src/app/auth/auth.guard.ts @@ -1,10 +1,11 @@ -import { Injectable } from '@angular/core'; -import { CanActivate, Router } from '@angular/router'; -import { AuthService } from './auth.service'; +import { inject, Injectable } from "@angular/core"; +import { CanActivate, Router } from "@angular/router"; +import { AuthService } from "./auth.service"; @Injectable() export class AuthGuard implements CanActivate { - constructor(private authService: AuthService, private router: Router) {} + private readonly authService = inject(AuthService); + private readonly router = inject(Router); async canActivate(): Promise { if (this.authService.processing) { @@ -12,7 +13,7 @@ export class AuthGuard implements CanActivate { } const isAuthenticated = this.authService.isAuthenticated; if (!isAuthenticated) { - await this.router.navigate(['/']); + await this.router.navigate(["/"]); } return isAuthenticated; } diff --git a/apps/client/src/app/auth/auth.service.interface.ts b/apps/client/src/app/auth/auth.service.interface.ts index 812b5013..44509c64 100644 --- a/apps/client/src/app/auth/auth.service.interface.ts +++ b/apps/client/src/app/auth/auth.service.interface.ts @@ -1,4 +1,4 @@ -import { Session } from '@supabase/supabase-js'; +import { Session } from "@supabase/supabase-js"; export interface AuthServiceInterface { processing: Promise | null; diff --git a/apps/client/src/app/auth/auth.service.spec.ts b/apps/client/src/app/auth/auth.service.spec.ts index 3e4ac20a..f433faa1 100644 --- a/apps/client/src/app/auth/auth.service.spec.ts +++ b/apps/client/src/app/auth/auth.service.spec.ts @@ -1,10 +1,10 @@ -import { TestBed } from '@angular/core/testing'; +import { TestBed } from "@angular/core/testing"; -import { AuthService } from './auth.service'; -import { AuthServiceInterface } from './auth.service.interface'; -import { Session } from '@supabase/supabase-js'; -import { DataAccessService } from '../data-access/data-access.service'; -import { dataAccessServiceStub } from '../data-access/data-access.service.spec'; +import { AuthService } from "./auth.service"; +import { AuthServiceInterface } from "./auth.service.interface"; +import { Session } from "@supabase/supabase-js"; +import { DataAccessService } from "../data-access/data-access.service"; +import { dataAccessServiceStub } from "../data-access/data-access.service.spec"; export const authServiceStub = { get session(): Session | null { @@ -33,7 +33,7 @@ export const authServiceStub = { }, processing: null } satisfies AuthServiceInterface; -describe('AuthService', () => { +describe("AuthService", () => { let service: AuthService; beforeEach(() => { @@ -41,7 +41,7 @@ describe('AuthService', () => { service = TestBed.inject(AuthService); }); - it('should be created', () => { + it("should be created", () => { expect(service).toBeTruthy(); }); }); diff --git a/apps/client/src/app/auth/auth.service.ts b/apps/client/src/app/auth/auth.service.ts index 0a7c2729..931bd631 100644 --- a/apps/client/src/app/auth/auth.service.ts +++ b/apps/client/src/app/auth/auth.service.ts @@ -1,15 +1,15 @@ -import { Injectable } from '@angular/core'; -import { Session } from '@supabase/supabase-js'; -import { DataAccessService } from '../data-access/data-access.service'; -import { AuthServiceInterface } from './auth.service.interface'; +import { inject, Injectable } from "@angular/core"; +import { Session } from "@supabase/supabase-js"; +import { DataAccessService } from "../data-access/data-access.service"; +import { AuthServiceInterface } from "./auth.service.interface"; @Injectable({ - providedIn: 'root' + providedIn: "root" }) export class AuthService implements AuthServiceInterface { processing: Promise | null = null; - constructor(private dataAccessService: DataAccessService) {} + private readonly dataAccessService = inject(DataAccessService); private _session: Session | null = null; @@ -19,8 +19,8 @@ export class AuthService implements AuthServiceInterface { get fullName(): string | null { return ( - this.session?.user?.identities?.find((identity) => identity.provider === 'google')?.identity_data?.[ - 'full_name' + this.session?.user?.identities?.find((identity) => identity.provider === "google")?.identity_data?.[ + "full_name" ] ?? null ); } @@ -39,13 +39,13 @@ export class AuthService implements AuthServiceInterface { async signInWithGoogle() { const signInPromise = (this.processing = this.dataAccessService.supabase.auth.signInWithOAuth({ - provider: 'google', + provider: "google", options: { redirectTo: window.location.href } })); const { error } = await signInPromise; if (error) { - console.error('error', error); + console.error("error", error); } this.processing = null; } @@ -54,7 +54,7 @@ export class AuthService implements AuthServiceInterface { const signOutPromise = (this.processing = this.dataAccessService.supabase.auth.signOut()); const { error } = await signOutPromise; if (error) { - console.error('error', error); + console.error("error", error); } else { this._session = null; } @@ -66,7 +66,7 @@ export class AuthService implements AuthServiceInterface { const { data, error } = await signInPromise; if (error) { - console.error('error', error); + console.error("error", error); } this._session = data.session; this.processing = null; diff --git a/apps/client/src/app/data-access/chat/authenticated-socket.service.interface.ts b/apps/client/src/app/data-access/chat/authenticated-socket.service.interface.ts index 88a7ca26..dff6382d 100644 --- a/apps/client/src/app/data-access/chat/authenticated-socket.service.interface.ts +++ b/apps/client/src/app/data-access/chat/authenticated-socket.service.interface.ts @@ -1,4 +1,4 @@ -import { Socket } from 'ngx-socket-io'; +import { Socket } from "ngx-socket-io"; export interface IAuthenticatedSocketService { get socket(): Socket | undefined; diff --git a/apps/client/src/app/data-access/chat/authenticated-socket.service.ts b/apps/client/src/app/data-access/chat/authenticated-socket.service.ts index 23a6ab6c..6c84fa65 100644 --- a/apps/client/src/app/data-access/chat/authenticated-socket.service.ts +++ b/apps/client/src/app/data-access/chat/authenticated-socket.service.ts @@ -1,11 +1,11 @@ -import { Socket } from 'ngx-socket-io'; -import { AuthService } from '../../auth/auth.service'; -import { environment } from '../../../environments/environment'; -import { Injectable } from '@angular/core'; -import { IAuthenticatedSocketService } from './authenticated-socket.service.interface'; +import { Socket } from "ngx-socket-io"; +import { AuthService } from "../../auth/auth.service"; +import { environment } from "../../../environments/environment"; +import { Injectable } from "@angular/core"; +import { IAuthenticatedSocketService } from "./authenticated-socket.service.interface"; @Injectable({ - providedIn: 'root' + providedIn: "root" }) export class AuthenticatedSocketService implements IAuthenticatedSocketService { private authenticatedSocket?: Socket; @@ -13,7 +13,7 @@ export class AuthenticatedSocketService implements IAuthenticatedSocketService { constructor(private readonly authService: AuthService) {} get socket(): Socket | undefined { - if (!this.authenticatedSocket && this.authService.accessToken) { + if (!this.authenticatedSocket && this.authService.isAuthenticated) { this.authenticatedSocket = this.createAuthSocket(); } diff --git a/apps/client/src/app/data-access/chat/chat.service.interface.ts b/apps/client/src/app/data-access/chat/chat.service.interface.ts index 02d1a3b8..ec40eb2b 100644 --- a/apps/client/src/app/data-access/chat/chat.service.interface.ts +++ b/apps/client/src/app/data-access/chat/chat.service.interface.ts @@ -1,10 +1,7 @@ -import { ChatMessage } from '@fuzzy-waddle/api-interfaces'; -import { Observable } from 'rxjs'; +import { ChatMessage } from "@fuzzy-waddle/api-interfaces"; +import { Observable } from "rxjs"; export interface IChatService { sendMessage(msg: ChatMessage): void; - - getMessage(): Observable | undefined; - - createMessage(message: string): ChatMessage; + get listenToMessages(): Observable | undefined; } diff --git a/apps/client/src/app/data-access/chat/chat.service.spec.ts b/apps/client/src/app/data-access/chat/chat.service.spec.ts index bf67dd49..010f017f 100644 --- a/apps/client/src/app/data-access/chat/chat.service.spec.ts +++ b/apps/client/src/app/data-access/chat/chat.service.spec.ts @@ -4,24 +4,14 @@ import { AuthenticatedSocketService } from "./authenticated-socket.service"; import { createAuthenticatedSocketServiceStub } from "./authenticated-socket.service.spec"; import { ChatMessage } from "@fuzzy-waddle/api-interfaces"; import { IChatService } from "./chat.service.interface"; -import { AuthService } from "../../auth/auth.service"; -import { authServiceStub } from "../../auth/auth.service.spec"; import { Observable } from "rxjs"; export const chatServiceStub = { sendMessage(msg: ChatMessage) { // do nothing }, - getMessage() { - return new Observable(); - }, - createMessage(message: string): ChatMessage { - return { - text: message, - userId: "123", - fullName: "test", - createdAt: new Date() - }; + get listenToMessages(): Observable | undefined { + return undefined; } } satisfies IChatService; @@ -35,8 +25,7 @@ describe("Chat", () => { { provide: AuthenticatedSocketService, useValue: createAuthenticatedSocketServiceStub - }, - { provide: AuthService, useValue: authServiceStub } + } ] }); service = TestBed.inject(ChatService); diff --git a/apps/client/src/app/data-access/chat/chat.service.ts b/apps/client/src/app/data-access/chat/chat.service.ts index 202ba44a..09726cd8 100644 --- a/apps/client/src/app/data-access/chat/chat.service.ts +++ b/apps/client/src/app/data-access/chat/chat.service.ts @@ -1,33 +1,23 @@ -import { Injectable } from '@angular/core'; -import { map } from 'rxjs/operators'; -import { Observable } from 'rxjs'; -import { IChatService } from './chat.service.interface'; -import { AuthenticatedSocketService } from './authenticated-socket.service'; -import { AuthService } from '../../auth/auth.service'; -import { ChatMessage, GatewayChatEvent } from '@fuzzy-waddle/api-interfaces'; +import { Injectable } from "@angular/core"; +import { map } from "rxjs/operators"; +import { Observable } from "rxjs"; +import { IChatService } from "./chat.service.interface"; +import { AuthenticatedSocketService } from "./authenticated-socket.service"; +import { ChatMessage, GatewayChatEvent } from "@fuzzy-waddle/api-interfaces"; @Injectable({ - providedIn: 'root' + providedIn: "root" }) export class ChatService implements IChatService { - constructor(private authenticatedSocketService: AuthenticatedSocketService, private authService: AuthService) {} + constructor(private authenticatedSocketService: AuthenticatedSocketService) {} sendMessage(msg: ChatMessage): void { const socket = this.authenticatedSocketService.socket; socket?.emit(GatewayChatEvent.CHAT_MESSAGE, msg); } - getMessage(): Observable | undefined { + get listenToMessages(): Observable | undefined { const socket = this.authenticatedSocketService.socket; return socket?.fromEvent(GatewayChatEvent.CHAT_MESSAGE).pipe(map((data: ChatMessage) => data)); } - - createMessage(message: string): ChatMessage { - return { - text: message, - userId: this.authService.userId as string, - fullName: this.authService.fullName as string, - createdAt: new Date() - }; - } } diff --git a/apps/client/src/app/data-access/data-access.service.interface.ts b/apps/client/src/app/data-access/data-access.service.interface.ts index c67ff358..2a748016 100644 --- a/apps/client/src/app/data-access/data-access.service.interface.ts +++ b/apps/client/src/app/data-access/data-access.service.interface.ts @@ -1,4 +1,4 @@ -import { SupabaseClient } from '@supabase/supabase-js'; +import { SupabaseClient } from "@supabase/supabase-js"; export interface DataAccessServiceInterface { get supabase(): SupabaseClient; diff --git a/apps/client/src/app/data-access/data-access.service.spec.ts b/apps/client/src/app/data-access/data-access.service.spec.ts index 3e87bb57..d5311988 100644 --- a/apps/client/src/app/data-access/data-access.service.spec.ts +++ b/apps/client/src/app/data-access/data-access.service.spec.ts @@ -1,15 +1,15 @@ -import { TestBed } from '@angular/core/testing'; +import { TestBed } from "@angular/core/testing"; -import { DataAccessService } from './data-access.service'; -import { SupabaseClient } from '@supabase/supabase-js'; -import { DataAccessServiceInterface } from './data-access.service.interface'; +import { DataAccessService } from "./data-access.service"; +import { SupabaseClient } from "@supabase/supabase-js"; +import { DataAccessServiceInterface } from "./data-access.service.interface"; export const dataAccessServiceStub = { get supabase(): SupabaseClient { - return new SupabaseClient('http://localhost:4200', '123'); + return new SupabaseClient("http://localhost:4200", "123"); } } satisfies DataAccessServiceInterface; -describe('DataAccess', () => { +describe("DataAccess", () => { let service: DataAccessService; beforeEach(() => { @@ -17,7 +17,7 @@ describe('DataAccess', () => { service = TestBed.inject(DataAccessService); }); - it('should be created', () => { + it("should be created", () => { expect(service).toBeTruthy(); }); }); diff --git a/apps/client/src/app/data-access/data-access.service.ts b/apps/client/src/app/data-access/data-access.service.ts index 654f0821..2be04978 100644 --- a/apps/client/src/app/data-access/data-access.service.ts +++ b/apps/client/src/app/data-access/data-access.service.ts @@ -1,10 +1,10 @@ -import { Injectable } from '@angular/core'; -import { createClient, SupabaseClient } from '@supabase/supabase-js'; -import { environment } from '../../environments/environment'; -import { DataAccessServiceInterface } from './data-access.service.interface'; +import { Injectable } from "@angular/core"; +import { createClient, SupabaseClient } from "@supabase/supabase-js"; +import { environment } from "../../environments/environment"; +import { DataAccessServiceInterface } from "./data-access.service.interface"; @Injectable({ - providedIn: 'root' + providedIn: "root" }) export class DataAccessService implements DataAccessServiceInterface { constructor() { diff --git a/apps/client/src/app/data-access/db-access-test/db-access-test.service.spec.ts b/apps/client/src/app/data-access/db-access-test/db-access-test.service.spec.ts index fedd66c3..1f9b1aa6 100644 --- a/apps/client/src/app/data-access/db-access-test/db-access-test.service.spec.ts +++ b/apps/client/src/app/data-access/db-access-test/db-access-test.service.spec.ts @@ -1,21 +1,21 @@ -import { TestBed } from '@angular/core/testing'; +import { TestBed } from "@angular/core/testing"; -import { DbAccessTestService } from './db-access-test.service'; -import { DataAccessService } from '../data-access.service'; -import { dataAccessServiceStub } from '../data-access.service.spec'; +import { DbAccessTestService } from "./db-access-test.service"; +import { DataAccessService } from "../data-access.service"; +import { dataAccessServiceStub } from "../data-access.service.spec"; export const dbAccessTestServiceStub = { get(): void { - console.log('test'); + console.log("test"); }, add(): void { - console.log('test'); + console.log("test"); }, async getStorageEntry(): Promise { - console.log('test'); + console.log("test"); } }; -describe('DbAccessTest', () => { +describe("DbAccessTest", () => { let service: DbAccessTestService; beforeEach(() => { @@ -23,7 +23,7 @@ describe('DbAccessTest', () => { service = TestBed.inject(DbAccessTestService); }); - it('should be created', () => { + it("should be created", () => { expect(service).toBeTruthy(); }); }); diff --git a/apps/client/src/app/data-access/db-access-test/db-access-test.service.ts b/apps/client/src/app/data-access/db-access-test/db-access-test.service.ts index a225dc0e..99d07e2d 100644 --- a/apps/client/src/app/data-access/db-access-test/db-access-test.service.ts +++ b/apps/client/src/app/data-access/db-access-test/db-access-test.service.ts @@ -1,16 +1,16 @@ -import { Injectable } from '@angular/core'; -import { DataAccessService } from '../data-access.service'; +import { inject, Injectable } from "@angular/core"; +import { DataAccessService } from "../data-access.service"; @Injectable({ - providedIn: 'root' + providedIn: "root" }) export class DbAccessTestService { - constructor(private dataAccessService: DataAccessService) {} + private readonly dataAccessService = inject(DataAccessService); get(): void { this.dataAccessService.supabase - .from('test') - .select('*') + .from("test") + .select("*") .then((data) => { console.table(data.data); }); @@ -18,15 +18,15 @@ export class DbAccessTestService { add(): void { this.dataAccessService.supabase - .from('test') - .insert({ text: 'test from frontend' }) + .from("test") + .insert({ text: "test from frontend" }) .then((data) => { console.log(data); }); } async getStorageEntry(): Promise { - const bucket1 = await this.dataAccessService.supabase.storage.from('test-bucket').download('probable-waffle.webp'); + const bucket1 = await this.dataAccessService.supabase.storage.from("test-bucket").download("probable-waffle.webp"); console.log(bucket1); } } diff --git a/apps/client/src/app/fly-squasher/choose-level/choose-level.component.html b/apps/client/src/app/fly-squasher/choose-level/choose-level.component.html index 0fecedd8..400473b6 100644 --- a/apps/client/src/app/fly-squasher/choose-level/choose-level.component.html +++ b/apps/client/src/app/fly-squasher/choose-level/choose-level.component.html @@ -1,5 +1,9 @@ - @@ -8,9 +12,9 @@
-
{{level.value.name}}
-
{{level.value.description}}
- +
{{ level.value.name }}
+
{{ level.value.description }}
+
diff --git a/apps/client/src/app/fly-squasher/choose-level/choose-level.component.spec.ts b/apps/client/src/app/fly-squasher/choose-level/choose-level.component.spec.ts index 245cf44c..b77be6bd 100644 --- a/apps/client/src/app/fly-squasher/choose-level/choose-level.component.spec.ts +++ b/apps/client/src/app/fly-squasher/choose-level/choose-level.component.spec.ts @@ -1,16 +1,15 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed } from "@angular/core/testing"; -import { ChooseLevelComponent } from './choose-level.component'; -import { RouterTestingModule } from '@angular/router/testing'; +import { ChooseLevelComponent } from "./choose-level.component"; +import { RouterTestingModule } from "@angular/router/testing"; -describe('ChooseLevelComponent', () => { +describe("ChooseLevelComponent", () => { let component: ChooseLevelComponent; let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ChooseLevelComponent], - imports: [RouterTestingModule] + imports: [ChooseLevelComponent, RouterTestingModule] }).compileComponents(); fixture = TestBed.createComponent(ChooseLevelComponent); @@ -18,7 +17,7 @@ describe('ChooseLevelComponent', () => { fixture.detectChanges(); }); - it('should create', () => { + it("should create", () => { expect(component).toBeTruthy(); }); }); diff --git a/apps/client/src/app/fly-squasher/choose-level/choose-level.component.ts b/apps/client/src/app/fly-squasher/choose-level/choose-level.component.ts index da4cc130..d908d1bb 100644 --- a/apps/client/src/app/fly-squasher/choose-level/choose-level.component.ts +++ b/apps/client/src/app/fly-squasher/choose-level/choose-level.component.ts @@ -1,10 +1,14 @@ import { Component } from "@angular/core"; import { FlySquasherLevels } from "@fuzzy-waddle/api-interfaces"; +import { CommonModule } from "@angular/common"; +import { RouterLink } from "@angular/router"; @Component({ selector: "fly-squasher-choose-level", templateUrl: "./choose-level.component.html", - styleUrls: ["./choose-level.component.scss"] + styleUrls: ["./choose-level.component.scss"], + standalone: true, + imports: [CommonModule, RouterLink] }) export class ChooseLevelComponent { protected readonly flySquasherLevels = FlySquasherLevels; diff --git a/apps/client/src/app/fly-squasher/choose-level/level.guard.ts b/apps/client/src/app/fly-squasher/choose-level/level.guard.ts index 904039f2..fb59a4ff 100644 --- a/apps/client/src/app/fly-squasher/choose-level/level.guard.ts +++ b/apps/client/src/app/fly-squasher/choose-level/level.guard.ts @@ -1,4 +1,4 @@ -import { Injectable } from "@angular/core"; +import { inject, Injectable } from "@angular/core"; import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from "@angular/router"; import { FlySquasherLevels } from "@fuzzy-waddle/api-interfaces"; @@ -6,7 +6,7 @@ import { FlySquasherLevels } from "@fuzzy-waddle/api-interfaces"; providedIn: "root" }) export class LevelGuard implements CanActivate { - constructor(private router: Router) {} + private readonly router = inject(Router); canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { const levelId = route.paramMap.get("level"); // Get the level ID from the route parameter diff --git a/apps/client/src/app/fly-squasher/fly-squasher-routing.module.ts b/apps/client/src/app/fly-squasher/fly-squasher-routing.module.ts deleted file mode 100644 index 347c5d1c..00000000 --- a/apps/client/src/app/fly-squasher/fly-squasher-routing.module.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { NgModule } from "@angular/core"; -import { RouterModule, Routes } from "@angular/router"; -import { HomeComponent } from "./home/home.component"; -import { HighScoreComponent } from "./high-score/high-score.component"; -import { MainComponent } from "./main/main.component"; -import { ChooseLevelComponent } from "./choose-level/choose-level.component"; -import { LevelGuard } from "./choose-level/level.guard"; -import { OptionsComponent } from "./options/options.component"; - -const routes: Routes = [ - { - path: "", - component: HomeComponent - }, - { - path: "choose-level", - component: ChooseLevelComponent - }, - { - path: "play/:level", - component: MainComponent, - canActivate: [LevelGuard] - }, - { - path: "high-score", - component: HighScoreComponent - }, - { - path: "options", - component: OptionsComponent - }, - { path: "**", redirectTo: "" } -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] -}) -export class FlySquasherRoutingModule {} diff --git a/apps/client/src/app/fly-squasher/fly-squasher.component.html b/apps/client/src/app/fly-squasher/fly-squasher.component.html index 0680b43f..67e7bd4c 100644 --- a/apps/client/src/app/fly-squasher/fly-squasher.component.html +++ b/apps/client/src/app/fly-squasher/fly-squasher.component.html @@ -1 +1 @@ - + diff --git a/apps/client/src/app/fly-squasher/fly-squasher.component.spec.ts b/apps/client/src/app/fly-squasher/fly-squasher.component.spec.ts index a9d6527c..54e450cd 100644 --- a/apps/client/src/app/fly-squasher/fly-squasher.component.spec.ts +++ b/apps/client/src/app/fly-squasher/fly-squasher.component.spec.ts @@ -1,16 +1,15 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed } from "@angular/core/testing"; -import { FlySquasherComponent } from './fly-squasher.component'; -import { RouterTestingModule } from '@angular/router/testing'; +import { FlySquasherComponent } from "./fly-squasher.component"; +import { RouterTestingModule } from "@angular/router/testing"; -describe('FlySquasherComponent', () => { +describe("FlySquasherComponent", () => { let component: FlySquasherComponent; let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [FlySquasherComponent], - imports: [RouterTestingModule] + imports: [FlySquasherComponent, RouterTestingModule] }).compileComponents(); fixture = TestBed.createComponent(FlySquasherComponent); @@ -18,7 +17,7 @@ describe('FlySquasherComponent', () => { fixture.detectChanges(); }); - it('should create', () => { + it("should create", () => { expect(component).toBeTruthy(); }); }); diff --git a/apps/client/src/app/fly-squasher/fly-squasher.component.ts b/apps/client/src/app/fly-squasher/fly-squasher.component.ts index 3011b48d..30cbd293 100644 --- a/apps/client/src/app/fly-squasher/fly-squasher.component.ts +++ b/apps/client/src/app/fly-squasher/fly-squasher.component.ts @@ -1,7 +1,17 @@ -import { Component } from '@angular/core'; +import { Component, inject, OnInit } from "@angular/core"; +import { UserInstanceService } from "../home/profile/user-instance.service"; +import { CommonModule } from "@angular/common"; +import { RouterOutlet } from "@angular/router"; @Component({ - templateUrl: './fly-squasher.component.html', - styleUrls: ['./fly-squasher.component.scss'] + templateUrl: "./fly-squasher.component.html", + styleUrls: ["./fly-squasher.component.scss"], + standalone: true, + imports: [CommonModule, RouterOutlet] }) -export class FlySquasherComponent {} +export class FlySquasherComponent implements OnInit { + protected readonly userInstanceService = inject(UserInstanceService); + ngOnInit(): void { + this.userInstanceService.setVisitedGame("fly-squasher"); + } +} diff --git a/apps/client/src/app/fly-squasher/fly-squasher.module.ts b/apps/client/src/app/fly-squasher/fly-squasher.module.ts deleted file mode 100644 index b132946b..00000000 --- a/apps/client/src/app/fly-squasher/fly-squasher.module.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { NgModule } from "@angular/core"; -import { CommonModule } from "@angular/common"; -import { FlySquasherRoutingModule } from "./fly-squasher-routing.module"; -import { FlySquasherComponent } from "./fly-squasher.component"; -import { MainModule } from "./main/main.module"; -import { ChooseLevelComponent } from "./choose-level/choose-level.component"; -import { HighScoreComponent } from "./high-score/high-score.component"; -import { HomeComponent } from "./home/home.component"; -import { OptionsComponent } from "./options/options.component"; -import { FaIconComponent } from "@fortawesome/angular-fontawesome"; - -@NgModule({ - declarations: [FlySquasherComponent, ChooseLevelComponent, HighScoreComponent, HomeComponent], - imports: [CommonModule, FlySquasherRoutingModule, MainModule, OptionsComponent, FaIconComponent] -}) -export class FlySquasherModule {} diff --git a/apps/client/src/app/fly-squasher/game/consts/game-config.ts b/apps/client/src/app/fly-squasher/game/consts/game-config.ts index 2e304308..987126f4 100644 --- a/apps/client/src/app/fly-squasher/game/consts/game-config.ts +++ b/apps/client/src/app/fly-squasher/game/consts/game-config.ts @@ -1,12 +1,12 @@ -import { Types } from 'phaser'; -import { FlySquasherScene } from '../fly-squasher-scene'; -import { baseGameConfig } from '../../../shared/game/base-game.config'; +import { Types } from "phaser"; +import { FlySquasherScene } from "../fly-squasher-scene"; +import { baseGameConfig } from "../../../shared/game/base-game.config"; export const flySquasherGameConfig: Types.Core.GameConfig = { ...baseGameConfig, scene: [FlySquasherScene], physics: { - default: 'arcade', + default: "arcade", arcade: { fps: 60, gravity: { y: 0 } @@ -14,5 +14,5 @@ export const flySquasherGameConfig: Types.Core.GameConfig = { } }, pixelArt: true, - backgroundColor: '#D1C4E9' + backgroundColor: "#D1C4E9" }; diff --git a/apps/client/src/app/fly-squasher/game/consts/scenes.ts b/apps/client/src/app/fly-squasher/game/consts/scenes.ts index 4e4e2c0f..c59170e9 100644 --- a/apps/client/src/app/fly-squasher/game/consts/scenes.ts +++ b/apps/client/src/app/fly-squasher/game/consts/scenes.ts @@ -1,3 +1,3 @@ export enum Scenes { - 'MainScene' = 'MainScene' + "MainScene" = "MainScene" } diff --git a/apps/client/src/app/fly-squasher/game/fly-squasher-communicator.service.ts b/apps/client/src/app/fly-squasher/game/fly-squasher-communicator.service.ts index 69add3df..d717f07d 100644 --- a/apps/client/src/app/fly-squasher/game/fly-squasher-communicator.service.ts +++ b/apps/client/src/app/fly-squasher/game/fly-squasher-communicator.service.ts @@ -1,6 +1,11 @@ import { Injectable, OnDestroy } from "@angular/core"; import { TwoWayCommunicator } from "../../shared/game/communicators/two-way-communicator"; -import { FlySquasherCommunicatorScoreEvent, FlySquasherGatewayEvent } from "@fuzzy-waddle/api-interfaces"; +import { + FlySquasherCommunicatorScoreEvent, + FlySquasherGatewayEvent, + LittleMuncherCommunicatorType +} from "@fuzzy-waddle/api-interfaces"; +import { CommunicatorService } from "../../shared/game/communicators/CommunicatorService"; export const flySquasherCommunicatorServiceStub = { startCommunication: () => {}, @@ -10,11 +15,11 @@ export const flySquasherCommunicatorServiceStub = { @Injectable({ providedIn: "root" }) -export class FlySquasherCommunicatorService implements OnDestroy { - score?: TwoWayCommunicator; +export class FlySquasherCommunicatorService implements CommunicatorService, OnDestroy { + score?: TwoWayCommunicator; startCommunication() { - this.score = new TwoWayCommunicator( + this.score = new TwoWayCommunicator( FlySquasherGatewayEvent.FlySquasherAction, "score" ); diff --git a/apps/client/src/app/fly-squasher/game/fly-squasher-scene.ts b/apps/client/src/app/fly-squasher/game/fly-squasher-scene.ts index 5d5c1b6f..8d1b4bd7 100644 --- a/apps/client/src/app/fly-squasher/game/fly-squasher-scene.ts +++ b/apps/client/src/app/fly-squasher/game/fly-squasher-scene.ts @@ -17,6 +17,7 @@ import { Scenery } from "./scenery/Scenery"; import { FlyFactory } from "./fly/fly.factory"; import { FlySquasherGameData } from "./fly-squasher-game-data"; import { FlySquasherAudio } from "./audio"; +import { FlySquasherCommunicatorService } from "./fly-squasher-communicator.service"; export class FlySquasherScene extends BaseScene< FlySquasherGameData, @@ -28,7 +29,8 @@ export class FlySquasherScene extends BaseScene< FlySquasherPlayerControllerData, FlySquasherPlayer, FlySquasherSpectatorData, - FlySquasherSpectator + FlySquasherSpectator, + FlySquasherCommunicatorService > { private readonly worldSpeedState = { initialWorldSpeedPerFrame: 0.2, diff --git a/apps/client/src/app/fly-squasher/game/fly/components/fly-health-component.ts b/apps/client/src/app/fly-squasher/game/fly/components/fly-health-component.ts new file mode 100644 index 00000000..1c5cb489 --- /dev/null +++ b/apps/client/src/app/fly-squasher/game/fly/components/fly-health-component.ts @@ -0,0 +1,86 @@ +import { FlyHealthBarOptions, FlyHealthUiComponent } from "./fly-health-ui-component"; +import { EventEmitter } from "@angular/core"; +import { IComponent } from "../../../../probable-waffle/game/core/component.service"; +import { ActorDeathType } from "../../../../probable-waffle/game/entity/combat/actor-death-type"; +import { DamageType } from "../../../../probable-waffle/game/entity/combat/damage-type"; +import { Actor } from "../../../../probable-waffle/game/entity/actor/actor"; + +export type HealthDefinition = { + maxHealth: number; + // todo maybe armor type.. +}; + +export class FlyHealthComponent implements IComponent { + healthChanged: EventEmitter = new EventEmitter(); + private currentHealth: number; + private healthUiComponent!: FlyHealthUiComponent; + + constructor( + private readonly actor: Actor, + private readonly scene: Phaser.Scene, + public readonly healthDefinition: HealthDefinition, + private readonly barOptions: () => FlyHealthBarOptions, + regenerateHealth: boolean = false, + private regenerateHealthRate: number = 0, + private actorDeathType?: ActorDeathType, + private actorDeathSound?: string + ) { + this.currentHealth = healthDefinition.maxHealth; + } + + init() { + this.healthUiComponent = new FlyHealthUiComponent(this.actor, this.scene, this.barOptions); + } + + start() { + this.healthUiComponent.start(); + } + + update(time: number, delta: number) { + if (!this.actor.destroyed) { + this.healthUiComponent.update(time, delta); + } + } + + takeDamage(damage: number, damageType: DamageType, damageInitiator?: Actor) { + this.setCurrentHealth(this.currentHealth - damage, damageInitiator); + } + + killActor() { + this.setCurrentHealth(0); + this.healthUiComponent.destroy(); + this.actor.kill(); + // todo something else as well + } + + setCurrentHealth(newHealth: number, damageInitiator?: Actor) { + if (this.currentHealth <= 0) { + return; + } + this.currentHealth = newHealth; + this.healthChanged.emit(this.currentHealth); + if (this.currentHealth <= 0) { + this.killActor(); + } + } + + setVisibilityUiComponent(visibility: boolean) { + this.healthUiComponent.setVisibility(visibility); + } + + getCurrentHealth() { + return this.currentHealth; + } + + isAlive() { + return this.currentHealth > 0; + } + + resetHealth() { + this.setCurrentHealth(this.healthDefinition.maxHealth); + } + + destroy() { + this.healthUiComponent.destroy(); + } +} diff --git a/apps/client/src/app/fly-squasher/game/fly/components/fly-health-ui-component.ts b/apps/client/src/app/fly-squasher/game/fly/components/fly-health-ui-component.ts new file mode 100644 index 00000000..ff4f0938 --- /dev/null +++ b/apps/client/src/app/fly-squasher/game/fly/components/fly-health-ui-component.ts @@ -0,0 +1,113 @@ +import { GameObjects } from "phaser"; +import { FlyHealthComponent } from "./fly-health-component"; +import { IComponent } from "../../../../probable-waffle/game/core/component.service"; +import { Actor } from "../../../../probable-waffle/game/entity/actor/actor"; + +export type FlyHealthBarOptions = { + spriteDepth: number; + spriteWidth: number; + spriteHeight: number; + spriteObjectCenterX: number; + spriteObjectCenterY: number; +}; + +export class FlyHealthUiComponent implements IComponent { + private healthComponent!: FlyHealthComponent; + private bar!: GameObjects.Graphics; + private barWidth = 25; + private barHeight = 8; + private barBorder = 2; + + private redThreshold = 0.3; + private orangeThreshold = 0.5; + private yellowThreshold = 0.7; + + constructor( + private readonly actor: Actor, + private readonly scene: Phaser.Scene, + private readonly barOptions: () => FlyHealthBarOptions + ) {} + + private get healthPercentage() { + return this.healthComponent.getCurrentHealth() / this.healthComponent.healthDefinition.maxHealth; + } + + private get barXY() { + if (!this.bar) { + throw new Error("Bar not initialized"); + } + + // set this health-bar to be above the player center horizontally + // get gameObject width + const options = this.barOptions(); + + // set bar x to be half of gameObject width + const x = options.spriteObjectCenterX - this.barWidth / 2; + + return [x, options.spriteObjectCenterY - options.spriteHeight / 2]; + } + + private get barDepth() { + // set depth to be above player + return this.barOptions().spriteDepth + 1; + } + + start() { + this.healthComponent = this.actor.components.findComponent(FlyHealthComponent); + this.bar = this.scene.add.graphics(); + } + + /** + * move bar with player + */ + update(time: number, delta: number) { + this.draw(); + this.bar.depth = this.barDepth; + } + + destroy() { + this.bar.destroy(); + } + + private draw() { + if (!this.bar) { + return; + } + + this.bar.clear(); + + const [x, y] = this.barXY; + + // BG + this.bar.fillStyle(0x000000); + this.bar.fillRect(x, y, this.barWidth, this.barHeight); + + // Health + + this.bar.fillStyle(0xffffff); + this.bar.fillRect( + x + this.barBorder, + y + this.barBorder, + this.barWidth - 2 * this.barBorder, + this.barHeight - 2 * this.barBorder + ); + + if (this.healthPercentage < this.redThreshold) { + this.bar.fillStyle(0xff0000); + } else if (this.healthPercentage < this.orangeThreshold) { + this.bar.fillStyle(0xffa500); + } else if (this.healthPercentage < this.yellowThreshold) { + this.bar.fillStyle(0xffff00); + } else { + this.bar.fillStyle(0x00ff00); + } + + const barFilledWidth = Math.floor((this.barWidth - 2 * this.barBorder) * this.healthPercentage); + + this.bar.fillRect(x + this.barBorder, y + this.barBorder, barFilledWidth, this.barHeight - 2 * this.barBorder); + } + + setVisibility(visibility: boolean) { + this.bar.setVisible(visibility); + } +} diff --git a/apps/client/src/app/fly-squasher/game/fly/components/fly-health.system.ts b/apps/client/src/app/fly-squasher/game/fly/components/fly-health.system.ts index f7f93ca7..984a527c 100644 --- a/apps/client/src/app/fly-squasher/game/fly/components/fly-health.system.ts +++ b/apps/client/src/app/fly-squasher/game/fly/components/fly-health.system.ts @@ -3,7 +3,7 @@ import { FlyRepresentableComponent } from "./fly-representable-component"; import { Subject, Subscription } from "rxjs"; import { FlyMovementComponent } from "./fly-movement-component"; import { FlySoundComponent } from "./fly-sound-component"; -import { HealthComponent } from "../../../../probable-waffle/game/entity/combat/components/health-component"; +import { FlyHealthComponent } from "./fly-health-component"; import { Fly } from "../fly"; import { DamageTypes } from "../../../../probable-waffle/game/entity/combat/damage-types"; @@ -13,7 +13,7 @@ export class FlyHealthSystem implements IComponent { private flyRepresentableComponent!: FlyRepresentableComponent; private flyMovementComponent!: FlyMovementComponent; private flySoundComponent!: FlySoundComponent; - private healthComponent!: HealthComponent; + private healthComponent!: FlyHealthComponent; constructor(private readonly fly: Fly) {} @@ -21,7 +21,7 @@ export class FlyHealthSystem implements IComponent { this.flyRepresentableComponent = this.fly.components.findComponent(FlyRepresentableComponent); this.flyMovementComponent = this.fly.components.findComponent(FlyMovementComponent); this.flySoundComponent = this.fly.components.findComponent(FlySoundComponent); - this.healthComponent = this.fly.components.findComponent(HealthComponent); + this.healthComponent = this.fly.components.findComponent(FlyHealthComponent); this.flyPrefabPointerHitSubscription = this.flyRepresentableComponent.pointerDown.subscribe(this.flyHit); } diff --git a/apps/client/src/app/fly-squasher/game/fly/fly.factory.ts b/apps/client/src/app/fly-squasher/game/fly/fly.factory.ts index 1dfed3f0..dbbbfcdc 100644 --- a/apps/client/src/app/fly-squasher/game/fly/fly.factory.ts +++ b/apps/client/src/app/fly-squasher/game/fly/fly.factory.ts @@ -1,6 +1,6 @@ import { BaseScene } from "../../../shared/game/phaser/scene/base.scene"; import { Fly } from "./fly"; -import { HealthComponent } from "../../../probable-waffle/game/entity/combat/components/health-component"; +import { FlyHealthComponent } from "./components/fly-health-component"; import { WorldSpeedState } from "./components/fly-movement-component"; import { FlySquasherAudio } from "../audio"; @@ -8,7 +8,7 @@ export class FlyFactory { static spawnFly(scene: BaseScene, worldSpeedState: WorldSpeedState, audio: FlySquasherAudio): Fly { const fly = new Fly(scene, worldSpeedState, audio); fly.registerGameObject(); - fly.components.findComponent(HealthComponent).setVisibilityUiComponent(false); + fly.components.findComponent(FlyHealthComponent).setVisibilityUiComponent(false); return fly; } diff --git a/apps/client/src/app/fly-squasher/game/fly/fly.ts b/apps/client/src/app/fly-squasher/game/fly/fly.ts index ffc42db4..92a0e1f6 100644 --- a/apps/client/src/app/fly-squasher/game/fly/fly.ts +++ b/apps/client/src/app/fly-squasher/game/fly/fly.ts @@ -5,7 +5,7 @@ import { FlySoundComponent } from "./components/fly-sound-component"; import { FlyMovementComponent, WorldSpeedState } from "./components/fly-movement-component"; import { FlyHealthSystem } from "./components/fly-health.system"; import { FlyRepresentableComponent } from "./components/fly-representable-component"; -import { HealthComponent } from "../../../probable-waffle/game/entity/combat/components/health-component"; +import { FlyHealthComponent } from "./components/fly-health-component"; import { FlySquasherAudio } from "../audio"; export type FlyOptions = { @@ -39,7 +39,7 @@ export class Fly extends Actor { new FlyRepresentableComponent(this, scene, actorOptions) ); this.components.addComponent( - new HealthComponent(this, scene, { maxHealth: this.maxHealth }, this.healthBarOptions) + new FlyHealthComponent(this, scene, { maxHealth: this.maxHealth }, this.healthBarOptions) ); this.flyHealthSystem = this.components.addComponent(new FlyHealthSystem(this)); // todo add system? diff --git a/apps/client/src/app/fly-squasher/game/prefabs/Croissants.ts b/apps/client/src/app/fly-squasher/game/prefabs/Croissants.ts index ece7786f..08ceb081 100644 --- a/apps/client/src/app/fly-squasher/game/prefabs/Croissants.ts +++ b/apps/client/src/app/fly-squasher/game/prefabs/Croissants.ts @@ -1,4 +1,3 @@ - // You can write more code here /* START OF COMPILED CODE */ @@ -8,37 +7,46 @@ import Phaser from "phaser"; /* END-USER-IMPORTS */ export default class Croissants extends Phaser.GameObjects.Container { - - constructor(scene: Phaser.Scene, x?: number, y?: number) { - super(scene, x ?? 2.221526863075212, y ?? 1.6450976306480243); - - // towel - const towel = scene.add.image(124.77847146244132, 103.35489880789905, "croissants-spritesheet", "croissants/towel"); - this.add(towel); - - // plate - const plate = scene.add.image(129.7784714624413, 91.35489880789905, "croissants-spritesheet", "croissants/plate"); - this.add(plate); - - // croissant_bottom - const croissant_bottom = scene.add.image(153.7784714624413, 65.35489880789905, "croissants-spritesheet", "croissants/croissant"); - croissant_bottom.angle = 132; - this.add(croissant_bottom); - - // croissant_top - const croissant_top = scene.add.image(99.77847146244132, 66.35489880789905, "croissants-spritesheet", "croissants/croissant"); - this.add(croissant_top); - - /* START-USER-CTR-CODE */ - // Write your code here. - /* END-USER-CTR-CODE */ - } - - /* START-USER-CODE */ - - // Write your code here. - - /* END-USER-CODE */ + constructor(scene: Phaser.Scene, x?: number, y?: number) { + super(scene, x ?? 2.221526863075212, y ?? 1.6450976306480243); + + // towel + const towel = scene.add.image(124.77847146244132, 103.35489880789905, "croissants-spritesheet", "croissants/towel"); + this.add(towel); + + // plate + const plate = scene.add.image(129.7784714624413, 91.35489880789905, "croissants-spritesheet", "croissants/plate"); + this.add(plate); + + // croissant_bottom + const croissant_bottom = scene.add.image( + 153.7784714624413, + 65.35489880789905, + "croissants-spritesheet", + "croissants/croissant" + ); + croissant_bottom.angle = 132; + this.add(croissant_bottom); + + // croissant_top + const croissant_top = scene.add.image( + 99.77847146244132, + 66.35489880789905, + "croissants-spritesheet", + "croissants/croissant" + ); + this.add(croissant_top); + + /* START-USER-CTR-CODE */ + // Write your code here. + /* END-USER-CTR-CODE */ + } + + /* START-USER-CODE */ + + // Write your code here. + + /* END-USER-CODE */ } /* END OF COMPILED CODE */ diff --git a/apps/client/src/app/fly-squasher/game/prefabs/Fly.ts b/apps/client/src/app/fly-squasher/game/prefabs/Fly.ts index 78745697..f63816ff 100644 --- a/apps/client/src/app/fly-squasher/game/prefabs/Fly.ts +++ b/apps/client/src/app/fly-squasher/game/prefabs/Fly.ts @@ -1,4 +1,3 @@ - // You can write more code here /* START OF COMPILED CODE */ @@ -8,82 +7,111 @@ import Phaser from "phaser"; /* END-USER-IMPORTS */ export default class Fly extends Phaser.GameObjects.Container { - - constructor(scene: Phaser.Scene, x?: number, y?: number) { - super(scene, x ?? 60, y ?? 57); - - // leg_back_right - const leg_back_right = scene.add.image(14.450684318372382, -0.6341442773239621, "fly-squasher-spritesheet", "fly/leg-back"); - leg_back_right.setOrigin(0, 1); - this.add(leg_back_right); - - // leg_back_left - const leg_back_left = scene.add.image(-15.549315681627625, -2.634144277323962, "fly-squasher-spritesheet", "fly/leg-back"); - leg_back_left.setOrigin(1, 1); - leg_back_left.flipX = true; - this.add(leg_back_left); - - // leg_front_right - const leg_front_right = scene.add.image(12.450684318372382, 31.365855722676038, "fly-squasher-spritesheet", "fly/leg-front"); - leg_front_right.setOrigin(0, 0); - this.add(leg_front_right); - - // leg_front_left - const leg_front_left = scene.add.image(-13.549315681627625, 31.365855722676038, "fly-squasher-spritesheet", "fly/leg-front"); - leg_front_left.setOrigin(1, 0); - leg_front_left.flipX = true; - this.add(leg_front_left); - - // leg_middle_right - const leg_middle_right = scene.add.image(12.450684318372382, 8.365855722676038, "fly-squasher-spritesheet", "fly/leg-middle"); - leg_middle_right.setOrigin(0, 0.5); - this.add(leg_middle_right); - - // leg_middle_left - const leg_middle_left = scene.add.image(-13.549315681627625, 7.365855722676038, "fly-squasher-spritesheet", "fly/leg-middle"); - leg_middle_left.setOrigin(1, 0.5); - leg_middle_left.flipX = true; - this.add(leg_middle_left); - - // body - const body = scene.add.image(0, -4, "fly-squasher-spritesheet", "fly/body"); - this.add(body); - - // wing_left - const wing_left = scene.add.image(-16, 21, "fly-squasher-spritesheet", "fly/wing"); - wing_left.setOrigin(0.27, 0.98); - this.add(wing_left); - - // wing_right - const wing_right = scene.add.image(15.770684318372375, 22.39585572267604, "fly-squasher-spritesheet", "fly/wing"); - wing_right.setOrigin(0.7, 0.99); - wing_right.flipX = true; - this.add(wing_right); - - // head_container - const head_container = scene.add.container(-0.949315681627624, 35.59585572267604); - this.add(head_container); - - // fly_head - const fly_head = scene.add.image(0.4, 0, "fly-squasher-spritesheet", "fly/head"); - fly_head.setOrigin(0.5, 0); - head_container.add(fly_head); - - // fly_suckler - const fly_suckler = scene.add.image(0, 19.8, "fly-squasher-spritesheet", "fly/suckler"); - fly_suckler.setOrigin(0.58, 0); - head_container.add(fly_suckler); - - /* START-USER-CTR-CODE */ - // Write your code here. - /* END-USER-CTR-CODE */ - } - - /* START-USER-CODE */ - - // Write your code here. - - /* END-USER-CODE */ + constructor(scene: Phaser.Scene, x?: number, y?: number) { + super(scene, x ?? 60, y ?? 57); + + // leg_back_right + const leg_back_right = scene.add.image( + 14.450684318372382, + -0.6341442773239621, + "fly-squasher-spritesheet", + "fly/leg-back" + ); + leg_back_right.setOrigin(0, 1); + this.add(leg_back_right); + + // leg_back_left + const leg_back_left = scene.add.image( + -15.549315681627625, + -2.634144277323962, + "fly-squasher-spritesheet", + "fly/leg-back" + ); + leg_back_left.setOrigin(1, 1); + leg_back_left.flipX = true; + this.add(leg_back_left); + + // leg_front_right + const leg_front_right = scene.add.image( + 12.450684318372382, + 31.365855722676038, + "fly-squasher-spritesheet", + "fly/leg-front" + ); + leg_front_right.setOrigin(0, 0); + this.add(leg_front_right); + + // leg_front_left + const leg_front_left = scene.add.image( + -13.549315681627625, + 31.365855722676038, + "fly-squasher-spritesheet", + "fly/leg-front" + ); + leg_front_left.setOrigin(1, 0); + leg_front_left.flipX = true; + this.add(leg_front_left); + + // leg_middle_right + const leg_middle_right = scene.add.image( + 12.450684318372382, + 8.365855722676038, + "fly-squasher-spritesheet", + "fly/leg-middle" + ); + leg_middle_right.setOrigin(0, 0.5); + this.add(leg_middle_right); + + // leg_middle_left + const leg_middle_left = scene.add.image( + -13.549315681627625, + 7.365855722676038, + "fly-squasher-spritesheet", + "fly/leg-middle" + ); + leg_middle_left.setOrigin(1, 0.5); + leg_middle_left.flipX = true; + this.add(leg_middle_left); + + // body + const body = scene.add.image(0, -4, "fly-squasher-spritesheet", "fly/body"); + this.add(body); + + // wing_left + const wing_left = scene.add.image(-16, 21, "fly-squasher-spritesheet", "fly/wing"); + wing_left.setOrigin(0.27, 0.98); + this.add(wing_left); + + // wing_right + const wing_right = scene.add.image(15.770684318372375, 22.39585572267604, "fly-squasher-spritesheet", "fly/wing"); + wing_right.setOrigin(0.7, 0.99); + wing_right.flipX = true; + this.add(wing_right); + + // head_container + const head_container = scene.add.container(-0.949315681627624, 35.59585572267604); + this.add(head_container); + + // fly_head + const fly_head = scene.add.image(0.4, 0, "fly-squasher-spritesheet", "fly/head"); + fly_head.setOrigin(0.5, 0); + head_container.add(fly_head); + + // fly_suckler + const fly_suckler = scene.add.image(0, 19.8, "fly-squasher-spritesheet", "fly/suckler"); + fly_suckler.setOrigin(0.58, 0); + head_container.add(fly_suckler); + + /* START-USER-CTR-CODE */ + // Write your code here. + /* END-USER-CTR-CODE */ + } + + /* START-USER-CODE */ + + // Write your code here. + + /* END-USER-CODE */ } /* END OF COMPILED CODE */ diff --git a/apps/client/src/app/fly-squasher/game/scenery/Croissants.ts b/apps/client/src/app/fly-squasher/game/scenery/Croissants.ts index 9bfd1236..6eeb0a41 100644 --- a/apps/client/src/app/fly-squasher/game/scenery/Croissants.ts +++ b/apps/client/src/app/fly-squasher/game/scenery/Croissants.ts @@ -1,4 +1,4 @@ -import { BaseScene } from '../../../shared/game/phaser/scene/base.scene'; +import { BaseScene } from "../../../shared/game/phaser/scene/base.scene"; // prefab combined in Phaser Editor 2D export class Croissants extends Phaser.GameObjects.Container { @@ -13,20 +13,20 @@ export class Croissants extends Phaser.GameObjects.Container { this.height = 190; // towel - this.towel = scene.add.image(25, 38, 'croissants-spritesheet', 'croissants/towel'); + this.towel = scene.add.image(25, 38, "croissants-spritesheet", "croissants/towel"); this.add(this.towel); // plate - this.plate = scene.add.image(30, 26, 'croissants-spritesheet', 'croissants/plate'); + this.plate = scene.add.image(30, 26, "croissants-spritesheet", "croissants/plate"); this.add(this.plate); // croissant_bottom - this.croissant_bottom = scene.add.image(54, 0, 'croissants-spritesheet', 'croissants/croissant'); + this.croissant_bottom = scene.add.image(54, 0, "croissants-spritesheet", "croissants/croissant"); this.croissant_bottom.angle = 132; this.add(this.croissant_bottom); // croissant_top - this.croissant_top = scene.add.image(0, 1, 'croissants-spritesheet', 'croissants/croissant'); + this.croissant_top = scene.add.image(0, 1, "croissants-spritesheet", "croissants/croissant"); this.add(this.croissant_top); this.tween(); diff --git a/apps/client/src/app/fly-squasher/game/scenes/FlySquasher.ts b/apps/client/src/app/fly-squasher/game/scenes/FlySquasher.ts index 3c31e33f..68a8a7da 100644 --- a/apps/client/src/app/fly-squasher/game/scenes/FlySquasher.ts +++ b/apps/client/src/app/fly-squasher/game/scenes/FlySquasher.ts @@ -2,15 +2,15 @@ /* START OF COMPILED CODE */ -import Phaser from 'phaser'; -import Croissants from '../prefabs/Croissants'; -import Fly from '../prefabs/Fly'; +import Phaser from "phaser"; +import Croissants from "../prefabs/Croissants"; +import Fly from "../prefabs/Fly"; /* START-USER-IMPORTS */ /* END-USER-IMPORTS */ export default class FlySquasher extends Phaser.Scene { constructor() { - super('FlySquasher'); + super("FlySquasher"); /* START-USER-CTR-CODE */ // Write your code here. @@ -26,7 +26,7 @@ export default class FlySquasher extends Phaser.Scene { const fly = new Fly(this, 299, 150); this.add.existing(fly); - this.events.emit('scene-awake'); + this.events.emit("scene-awake"); } /* START-USER-CODE */ diff --git a/apps/client/src/app/fly-squasher/game/scenes/PreloadFlySquasher.scene b/apps/client/src/app/fly-squasher/game/scenes/PreloadFlySquasher.scene index fb4832ff..18f5c6ff 100644 --- a/apps/client/src/app/fly-squasher/game/scenes/PreloadFlySquasher.scene +++ b/apps/client/src/app/fly-squasher/game/scenes/PreloadFlySquasher.scene @@ -16,22 +16,19 @@ { "type": "Image", "id": "e413cbbf-ce25-4dad-a117-b7cf0791f655", - "label": "guapen", - "components": [], + "label": "probable-waffle-loader", "texture": { - "key": "guapen" + "key": "probable-waffle-loader" }, "x": 505.0120544433594, "y": 360, - "scaleX": 0.32715486817515643, - "scaleY": 0.32715486817515643, - "list": [] + "scaleX": 2, + "scaleY": 2 }, { "type": "Rectangle", "id": "845d4ca6-7417-44fc-97e3-a3bb2fbbdd03", "label": "progressBar", - "components": [], "x": 553.0120849609375, "y": 361, "originX": 0, @@ -44,10 +41,7 @@ { "prefabId": "ca949479-ab4d-45d5-b856-2cbecbd8d127", "id": "250eaa8f-b143-4e41-9ddf-052dfd99672c", - "label": "preloadUpdater", - "components": [], - "nestedPrefabs": [], - "list": [] + "label": "preloadUpdater" } ] }, @@ -55,7 +49,6 @@ "type": "Rectangle", "id": "10e175a4-81b6-449e-9561-6d8ba54d3812", "label": "progressBarBg", - "components": [], "x": 553.0120849609375, "y": 361, "originX": 0, @@ -63,21 +56,18 @@ "fillColor": "#e0e0e0", "isStroked": true, "width": 256, - "height": 20, - "list": [] + "height": 20 }, { "type": "Text", "id": "c4092661-6339-47cf-9fea-8abff3a46e0c", "label": "loadingText", - "components": [], "x": 552.0120849609375, "y": 329, "text": "Loading...", "fontFamily": "arial", "fontSize": "20px", - "color": "#e0e0e0", - "list": [] + "color": "#e0e0e0" } ], "plainObjects": [], @@ -85,6 +75,6 @@ "app": "Phaser Editor 2D - Scene Editor", "url": "https://phasereditor2d.com", "contentType": "phasereditor2d.core.scene.SceneContentType", - "version": 3 + "version": 5 } -} +} \ No newline at end of file diff --git a/apps/client/src/app/fly-squasher/game/scenes/PreloadFlySquasher.ts b/apps/client/src/app/fly-squasher/game/scenes/PreloadFlySquasher.ts index cdc011e9..bcc7df8d 100644 --- a/apps/client/src/app/fly-squasher/game/scenes/PreloadFlySquasher.ts +++ b/apps/client/src/app/fly-squasher/game/scenes/PreloadFlySquasher.ts @@ -2,15 +2,14 @@ /* START OF COMPILED CODE */ -import Phaser from 'phaser'; -import PreloadBarUpdaterScript from '../../../other/Template/script-nodes/PreloadBarUpdaterScript'; +import Phaser from "phaser"; +import PreloadBarUpdaterScript from "../../../other/Template/script-nodes/PreloadBarUpdaterScript"; /* START-USER-IMPORTS */ -import assetPackUrl from '../../../../assets/fly-squasher/asset-packers/asset-pack-fly-squasher.json'; /* END-USER-IMPORTS */ export default class PreloadFlySquasher extends Phaser.Scene { constructor() { - super('PreloadFlySquasher'); + super("PreloadFlySquasher"); /* START-USER-CTR-CODE */ // Write your code here. @@ -18,10 +17,10 @@ export default class PreloadFlySquasher extends Phaser.Scene { } editorCreate(): void { - // guapen - const guapen = this.add.image(505.0120544433594, 360, 'guapen'); - guapen.scaleX = 0.32715486817515643; - guapen.scaleY = 0.32715486817515643; + // probable-waffle-loader + const probable_waffle_loader = this.add.image(505.0120544433594, 360, "probable-waffle-loader"); + probable_waffle_loader.scaleX = 2; + probable_waffle_loader.scaleY = 2; // progressBar const progressBar = this.add.rectangle(553.0120849609375, 361, 256, 20); @@ -39,11 +38,11 @@ export default class PreloadFlySquasher extends Phaser.Scene { progressBarBg.isStroked = true; // loadingText - const loadingText = this.add.text(552.0120849609375, 329, '', {}); - loadingText.text = 'Loading...'; - loadingText.setStyle({ color: '#e0e0e0', fontFamily: 'arial', fontSize: '20px' }); + const loadingText = this.add.text(552.0120849609375, 329, "", {}); + loadingText.text = "Loading..."; + loadingText.setStyle({ color: "#e0e0e0", fontFamily: "arial", fontSize: "20px" }); - this.events.emit('scene-awake'); + this.events.emit("scene-awake"); } /* START-USER-CODE */ @@ -53,11 +52,11 @@ export default class PreloadFlySquasher extends Phaser.Scene { preload() { this.editorCreate(); - this.load.pack('asset-pack', assetPackUrl as any); + this.load.pack("asset-pack", "assets/fly-squasher/asset-packers/asset-pack-fly-squasher.json"); } create() { - this.scene.start('FlySquasher'); + this.scene.start("FlySquasher"); } /* END-USER-CODE */ diff --git a/apps/client/src/app/fly-squasher/high-score/high-score.component.html b/apps/client/src/app/fly-squasher/high-score/high-score.component.html index b5a509c9..d39e947b 100644 --- a/apps/client/src/app/fly-squasher/high-score/high-score.component.html +++ b/apps/client/src/app/fly-squasher/high-score/high-score.component.html @@ -27,7 +27,7 @@

{{ level.name }}

- +
@@ -41,7 +41,7 @@

{{ level.name }}

Server unavailable
Try again later
- +
diff --git a/apps/client/src/app/fly-squasher/high-score/high-score.component.spec.ts b/apps/client/src/app/fly-squasher/high-score/high-score.component.spec.ts index 7e38a9a4..633ba0f0 100644 --- a/apps/client/src/app/fly-squasher/high-score/high-score.component.spec.ts +++ b/apps/client/src/app/fly-squasher/high-score/high-score.component.spec.ts @@ -8,6 +8,7 @@ import { authServiceStub } from "../../auth/auth.service.spec"; import { HighScoreService } from "./high-score.service"; import { highScoreServiceStub } from "./high-score.service.spec"; import { FontAwesomeTestingModule } from "@fortawesome/angular-fontawesome/testing"; +import { RouterTestingModule } from "@angular/router/testing"; describe("HighScoreComponent", () => { let component: HighScoreComponent; @@ -15,7 +16,6 @@ describe("HighScoreComponent", () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [HighScoreComponent], providers: [ { provide: ServerHealthService, @@ -30,7 +30,7 @@ describe("HighScoreComponent", () => { useValue: highScoreServiceStub } ], - imports: [FontAwesomeTestingModule] + imports: [HighScoreComponent, FontAwesomeTestingModule, RouterTestingModule] }).compileComponents(); fixture = TestBed.createComponent(HighScoreComponent); diff --git a/apps/client/src/app/fly-squasher/high-score/high-score.component.ts b/apps/client/src/app/fly-squasher/high-score/high-score.component.ts index dacb5221..18e90d2f 100644 --- a/apps/client/src/app/fly-squasher/high-score/high-score.component.ts +++ b/apps/client/src/app/fly-squasher/high-score/high-score.component.ts @@ -1,14 +1,18 @@ -import { Component, OnInit } from "@angular/core"; +import { Component, inject, OnInit } from "@angular/core"; import { HighScoreService } from "./high-score.service"; import { FlySquasherLevelEnum, FlySquasherLevels, ScoreDto } from "@fuzzy-waddle/api-interfaces"; import { faExclamationTriangle, faSpinner } from "@fortawesome/free-solid-svg-icons"; import { ServerHealthService } from "../../shared/services/server-health.service"; -import { AuthService } from "../../auth/auth.service"; +import { CommonModule } from "@angular/common"; +import { FaIconComponent } from "@fortawesome/angular-fontawesome"; +import { RouterLink } from "@angular/router"; @Component({ selector: "fly-squasher-high-score", templateUrl: "./high-score.component.html", - styleUrls: ["./high-score.component.scss"] + styleUrls: ["./high-score.component.scss"], + standalone: true, + imports: [CommonModule, FaIconComponent, RouterLink] }) export class HighScoreComponent implements OnInit { protected readonly faSpinner = faSpinner; @@ -16,10 +20,8 @@ export class HighScoreComponent implements OnInit { protected loading = true; protected highScores: ScoreDto[] = []; - constructor( - private readonly highScoreService: HighScoreService, - protected readonly serverHealthService: ServerHealthService - ) {} + private readonly highScoreService = inject(HighScoreService); + protected readonly serverHealthService = inject(ServerHealthService); async ngOnInit(): Promise { await this.serverHealthService.checkHealth(); diff --git a/apps/client/src/app/fly-squasher/home/home.component.html b/apps/client/src/app/fly-squasher/home/home.component.html index 2f1301bf..d7554c8d 100644 --- a/apps/client/src/app/fly-squasher/home/home.component.html +++ b/apps/client/src/app/fly-squasher/home/home.component.html @@ -3,8 +3,11 @@
- diff --git a/apps/client/src/app/fly-squasher/options/options.component.ts b/apps/client/src/app/fly-squasher/options/options.component.ts index a5678aa6..0eb3afd9 100644 --- a/apps/client/src/app/fly-squasher/options/options.component.ts +++ b/apps/client/src/app/fly-squasher/options/options.component.ts @@ -5,7 +5,6 @@ import { FormsModule } from "@angular/forms"; import { VolumeSettings } from "../shared/volumeSettings"; @Component({ - selector: "fuzzy-waddle-options", standalone: true, imports: [CommonModule, RouterLink, FormsModule], templateUrl: "./options.component.html", diff --git a/apps/client/src/app/home/chat/chat-float/chat-float.component.html b/apps/client/src/app/home/chat/chat-float/chat-float.component.html index 3f64bc39..00647f9d 100644 --- a/apps/client/src/app/home/chat/chat-float/chat-float.component.html +++ b/apps/client/src/app/home/chat/chat-float/chat-float.component.html @@ -8,6 +8,10 @@
- + diff --git a/apps/client/src/app/home/chat/chat-float/chat-float.component.scss b/apps/client/src/app/home/chat/chat-float/chat-float.component.scss index 349524d0..db13ac1c 100644 --- a/apps/client/src/app/home/chat/chat-float/chat-float.component.scss +++ b/apps/client/src/app/home/chat/chat-float/chat-float.component.scss @@ -1,4 +1,4 @@ -@import '../../../../styles/_variables'; +@import "../../../../styles/_variables"; .chat-container { position: fixed; @@ -44,7 +44,9 @@ background-color: white; cursor: pointer; color: $dark; - transition: background-color 0.1s ease-in-out, color 0.1s ease-in-out; + transition: + background-color 0.1s ease-in-out, + color 0.1s ease-in-out; &:hover { background-color: $primary; diff --git a/apps/client/src/app/home/chat/chat-float/chat-float.component.spec.ts b/apps/client/src/app/home/chat/chat-float/chat-float.component.spec.ts index 0b4f696b..11bbc245 100644 --- a/apps/client/src/app/home/chat/chat-float/chat-float.component.spec.ts +++ b/apps/client/src/app/home/chat/chat-float/chat-float.component.spec.ts @@ -1,16 +1,15 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed } from "@angular/core/testing"; -import { ChatFloatComponent } from './chat-float.component'; -import { FontAwesomeTestingModule } from '@fortawesome/angular-fontawesome/testing'; +import { ChatFloatComponent } from "./chat-float.component"; +import { FontAwesomeTestingModule } from "@fortawesome/angular-fontawesome/testing"; -describe('ChatFloatComponent', () => { +describe("ChatFloatComponent", () => { let component: ChatFloatComponent; let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ChatFloatComponent], - imports: [FontAwesomeTestingModule] + imports: [ChatFloatComponent, FontAwesomeTestingModule] }).compileComponents(); fixture = TestBed.createComponent(ChatFloatComponent); @@ -18,7 +17,7 @@ describe('ChatFloatComponent', () => { fixture.detectChanges(); }); - it('should create', () => { + it("should create", () => { expect(component).toBeTruthy(); }); }); diff --git a/apps/client/src/app/home/chat/chat-float/chat-float.component.ts b/apps/client/src/app/home/chat/chat-float/chat-float.component.ts index 0a3db910..a10b19b3 100644 --- a/apps/client/src/app/home/chat/chat-float/chat-float.component.ts +++ b/apps/client/src/app/home/chat/chat-float/chat-float.component.ts @@ -1,18 +1,25 @@ -import { Component } from '@angular/core'; -import { faWindowMaximize, faWindowMinimize } from '@fortawesome/free-solid-svg-icons'; +import { Component, inject } from "@angular/core"; +import { faWindowMaximize, faWindowMinimize } from "@fortawesome/free-solid-svg-icons"; +import { ChatService } from "../../../data-access/chat/chat.service"; +import { CommonModule } from "@angular/common"; +import { ChatComponent } from "../../../shared/components/chat/chat.component"; +import { FaIconComponent } from "@fortawesome/angular-fontawesome"; @Component({ - selector: 'fuzzy-waddle-chat-float', - templateUrl: './chat-float.component.html', - styleUrls: ['./chat-float.component.scss'] + selector: "fuzzy-waddle-chat-float", + templateUrl: "./chat-float.component.html", + styleUrls: ["./chat-float.component.scss"], + standalone: true, + imports: [CommonModule, ChatComponent, FaIconComponent] }) export class ChatFloatComponent { - faWindowMinimize = faWindowMinimize; - faWindowMaximize = faWindowMaximize; + protected readonly faWindowMinimize = faWindowMinimize; + protected readonly faWindowMaximize = faWindowMaximize; + protected readonly chatService = inject(ChatService); - maximized = false; + protected maximized = false; - toggleChatVisibility() { + protected toggleChatVisibility() { this.maximized = !this.maximized; } } diff --git a/apps/client/src/app/home/chat/chat/avatar-provider/avatar-provider.service.ts b/apps/client/src/app/home/chat/chat/avatar-provider/avatar-provider.service.ts deleted file mode 100644 index 48970f33..00000000 --- a/apps/client/src/app/home/chat/chat/avatar-provider/avatar-provider.service.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Injectable } from '@angular/core'; -import { createAvatar } from '@dicebear/core'; -import * as pixelArt from '@dicebear/pixel-art'; -import { IAvatarProviderService } from './avatar-provider.service.interface'; - -@Injectable({ - providedIn: 'root' -}) -export class AvatarProviderService implements IAvatarProviderService { - getAvatar(seed: string) { - const avatar = createAvatar(pixelArt, { seed }); - return avatar.toDataUriSync(); - } -} diff --git a/apps/client/src/app/home/chat/chat/chat.component.spec.ts b/apps/client/src/app/home/chat/chat/chat.component.spec.ts deleted file mode 100644 index 160e0ae8..00000000 --- a/apps/client/src/app/home/chat/chat/chat.component.spec.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ChatComponent } from './chat.component'; -import { ChatService } from '../../../data-access/chat/chat.service'; -import { FormsModule } from '@angular/forms'; -import { AvatarProviderService } from './avatar-provider/avatar-provider.service'; -import { avatarProviderServiceStub } from './avatar-provider/avatar-provider.service.spec'; -import { chatServiceStub } from '../../../data-access/chat/chat.service.spec'; - -describe('ChatComponent', () => { - let component: ChatComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ChatComponent], - providers: [ - { provide: ChatService, useValue: chatServiceStub }, - { - provide: AvatarProviderService, - useValue: avatarProviderServiceStub - } - ], - imports: [FormsModule] - }).compileComponents(); - - fixture = TestBed.createComponent(ChatComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/apps/client/src/app/home/chat/chat/chat.component.ts b/apps/client/src/app/home/chat/chat/chat.component.ts deleted file mode 100644 index 79ce9c99..00000000 --- a/apps/client/src/app/home/chat/chat/chat.component.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Component, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core'; -import { ChatService } from '../../../data-access/chat/chat.service'; -import { ChatMessage } from '@fuzzy-waddle/api-interfaces'; -import { AvatarProviderService } from './avatar-provider/avatar-provider.service'; -import { Subscription } from 'rxjs'; - -@Component({ - selector: 'fuzzy-waddle-chat', - templateUrl: './chat.component.html', - styleUrls: ['./chat.component.scss'] -}) -export class ChatComponent implements OnInit, OnDestroy { - @ViewChild('chatBody') chatBody!: ElementRef; - message = ''; - messages: ChatMessage[] = []; - private messageSubscription?: Subscription; - - constructor(private chatService: ChatService, protected avatarProviderService: AvatarProviderService) {} - - ngOnInit(): void { - this.messageSubscription = this.chatService.getMessage()?.subscribe((msg: ChatMessage) => { - this.messages.push(msg); - // scroll to bottom - setTimeout(() => { - this.chatBody.nativeElement.scrollTop = this.chatBody.nativeElement.scrollHeight; - }, 0); - }); - - this.chatService.sendMessage(this.chatService.createMessage('Joined the chat')); - } - - sendMessage() { - if (!this.message) { - return; - } - this.chatService.sendMessage(this.chatService.createMessage(this.message)); - this.message = ''; - } - - ngOnDestroy(): void { - this.chatService.sendMessage(this.chatService.createMessage('Left the chat')); - this.messageSubscription?.unsubscribe(); - } -} diff --git a/apps/client/src/app/home/home.module.ts b/apps/client/src/app/home/home.module.ts deleted file mode 100644 index e462b385..00000000 --- a/apps/client/src/app/home/home.module.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { CommonModule } from '@angular/common'; -import { NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; -import { HomePageComponent } from './page/home-page.component'; -import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; -import { ProfileComponent } from './profile/profile.component'; -import { ChatComponent } from './chat/chat/chat.component'; -import { FormsModule } from '@angular/forms'; -import { ChatFloatComponent } from './chat/chat-float/chat-float.component'; -import { ProfileNavComponent } from './profile/profile-nav/profile-nav.component'; -import { ComponentsModule } from '../shared/components/components.module'; -import { HomePageNavModule } from './page/home-page-nav/home-page-nav.module'; - -@NgModule({ - declarations: [HomePageComponent, ProfileComponent, ChatComponent, ChatFloatComponent, ProfileNavComponent], - imports: [CommonModule, RouterModule, FontAwesomeModule, FormsModule, ComponentsModule, HomePageNavModule], - exports: [HomePageComponent] -}) -export class HomeModule {} diff --git a/apps/client/src/app/home/page/home-page-nav/home-page-nav.component.html b/apps/client/src/app/home/page/home-page-nav/home-page-nav.component.html index 92cecd2b..2cea3b6b 100644 --- a/apps/client/src/app/home/page/home-page-nav/home-page-nav.component.html +++ b/apps/client/src/app/home/page/home-page-nav/home-page-nav.component.html @@ -6,13 +6,13 @@ class="nav-item" > - + Sign In diff --git a/apps/client/src/app/home/page/home-page-nav/home-page-nav.component.spec.ts b/apps/client/src/app/home/page/home-page-nav/home-page-nav.component.spec.ts index d7f8e450..9a37c46e 100644 --- a/apps/client/src/app/home/page/home-page-nav/home-page-nav.component.spec.ts +++ b/apps/client/src/app/home/page/home-page-nav/home-page-nav.component.spec.ts @@ -1,30 +1,39 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { HomePageNavComponent } from './home-page-nav.component'; -import { FontAwesomeTestingModule } from '@fortawesome/angular-fontawesome/testing'; -import { provideRouter } from '@angular/router'; -import { Component } from '@angular/core'; -import { HomeNavTestingComponent } from '../../../shared/components/home-nav/home-nav.component.spec'; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { HomePageNavComponent } from "./home-page-nav.component"; +import { FontAwesomeTestingModule } from "@fortawesome/angular-fontawesome/testing"; +import { provideRouter } from "@angular/router"; +import { Component } from "@angular/core"; +import { HomeNavTestingComponent } from "../../../shared/components/home-nav/home-nav.component.spec"; +import { CommonModule } from "@angular/common"; -@Component({ selector: 'fuzzy-waddle-home-page-nav', template: '' }) +@Component({ selector: "probable-waffle-home-page-nav", template: "", standalone: true, imports: [CommonModule] }) export class HomePageNavTestingComponent {} -describe('HomePageNavComponent', () => { +describe("HomePageNavComponent", () => { let component: HomePageNavComponent; let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [HomePageNavComponent, HomeNavTestingComponent], - imports: [FontAwesomeTestingModule], + imports: [HomePageNavComponent, FontAwesomeTestingModule], providers: [provideRouter([])] - }).compileComponents(); + }) + .overrideComponent(HomePageNavComponent, { + remove: { + imports: [HomePageNavComponent] + }, + add: { + imports: [HomePageNavTestingComponent] + } + }) + .compileComponents(); fixture = TestBed.createComponent(HomePageNavComponent); component = fixture.componentInstance; fixture.detectChanges(); }); - it('should create', () => { + it("should create", () => { expect(component).toBeTruthy(); }); }); diff --git a/apps/client/src/app/home/page/home-page-nav/home-page-nav.component.ts b/apps/client/src/app/home/page/home-page-nav/home-page-nav.component.ts index 5681252d..01191d64 100644 --- a/apps/client/src/app/home/page/home-page-nav/home-page-nav.component.ts +++ b/apps/client/src/app/home/page/home-page-nav/home-page-nav.component.ts @@ -1,16 +1,22 @@ -import { Component } from '@angular/core'; -import { AuthService } from '../../../auth/auth.service'; -import { faUser } from '@fortawesome/free-solid-svg-icons'; -import { faGoogle } from '@fortawesome/free-brands-svg-icons'; +import { Component, inject } from "@angular/core"; +import { AuthService } from "../../../auth/auth.service"; +import { faUser } from "@fortawesome/free-solid-svg-icons"; +import { faGoogle } from "@fortawesome/free-brands-svg-icons"; +import { CommonModule } from "@angular/common"; +import { FaIconComponent } from "@fortawesome/angular-fontawesome"; +import { HomeNavComponent } from "../../../shared/components/home-nav/home-nav.component"; +import { RouterLink } from "@angular/router"; @Component({ - selector: 'fuzzy-waddle-home-page-nav', - templateUrl: './home-page-nav.component.html', - styleUrls: ['./home-page-nav.component.scss'] + selector: "probable-waffle-home-page-nav", + templateUrl: "./home-page-nav.component.html", + styleUrls: ["./home-page-nav.component.scss"], + standalone: true, + imports: [CommonModule, FaIconComponent, HomeNavComponent, RouterLink] }) export class HomePageNavComponent { - faUser = faUser; - faGoogle = faGoogle; + protected readonly faUser = faUser; + protected readonly faGoogle = faGoogle; - constructor(protected authService: AuthService) {} + protected readonly authService = inject(AuthService); } diff --git a/apps/client/src/app/home/page/home-page-nav/home-page-nav.module.ts b/apps/client/src/app/home/page/home-page-nav/home-page-nav.module.ts deleted file mode 100644 index 71e64d2e..00000000 --- a/apps/client/src/app/home/page/home-page-nav/home-page-nav.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { HomePageNavComponent } from './home-page-nav.component'; -import { ComponentsModule } from '../../../shared/components/components.module'; -import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; -import { RouterLink } from '@angular/router'; - -@NgModule({ - declarations: [HomePageNavComponent], - imports: [CommonModule, ComponentsModule, FontAwesomeModule, RouterLink], - exports: [HomePageNavComponent] -}) -export class HomePageNavModule {} diff --git a/apps/client/src/app/home/page/home-page.component.html b/apps/client/src/app/home/page/home-page.component.html index 6ec3adf4..25719eef 100644 --- a/apps/client/src/app/home/page/home-page.component.html +++ b/apps/client/src/app/home/page/home-page.component.html @@ -1,7 +1,7 @@ - + - +