Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#170]: interfaces creation #467

Open
wants to merge 23 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
0260f58
[#170]: Create an interface for GameController class
Guryanov-Maksim Dec 23, 2023
a02fa56
[#170]: create an interface for GameService class
Guryanov-Maksim Dec 23, 2023
72d10a2
[#170]: create a interface for GameRepository class
Guryanov-Maksim Dec 23, 2023
21a01bc
[#170]: create an interface for PlayerService class
Guryanov-Maksim Dec 23, 2023
650b8ea
[#170]: create an interface for PlayerRepository class
Guryanov-Maksim Dec 23, 2023
d824d04
[#179]: create an interface for PlaygroundService class
Guryanov-Maksim Dec 23, 2023
3bbe59e
[#170]: create an interface for PlaygroudRepository class
Guryanov-Maksim Dec 23, 2023
4520fe2
[#170]: create an interface for UserService class
Guryanov-Maksim Dec 23, 2023
f3fff61
[#170]: create an interface for UserRepository class
Guryanov-Maksim Dec 23, 2023
c652bc3
[#170]: create an interface for Gameplay class
Guryanov-Maksim Dec 24, 2023
9e18b37
[#170]: create an interface for AuthService class
Guryanov-Maksim Dec 24, 2023
1ec807d
[#170]: create an interface for LigrettoCoreService class
Guryanov-Maksim Dec 24, 2023
77f171b
[#170]: fix syntax
Guryanov-Maksim Dec 24, 2023
60cb41e
[#170]: fix missed dependency
Guryanov-Maksim Dec 24, 2023
2098e3d
[#170]: create an interface for WebsocketHandler class
Guryanov-Maksim Dec 24, 2023
a20f13b
[#170]: update inversify config
Guryanov-Maksim Dec 24, 2023
81e87f8
[#170]: update ioc types
Guryanov-Maksim Dec 24, 2023
36a1d58
[#170]: create an interface for Controller class
Guryanov-Maksim Dec 24, 2023
be3355f
[#170]: update GamesController inrerface
Guryanov-Maksim Dec 24, 2023
09f9888
[#170]: fix injection of bot and gameplay controllers
Guryanov-Maksim Dec 24, 2023
eac48ec
[#170]: update inversify config
Guryanov-Maksim Dec 24, 2023
ebefe9b
[#170]: naming
Guryanov-Maksim Dec 24, 2023
3df9a2f
[#170]: update tests
Guryanov-Maksim Dec 24, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 15 additions & 19 deletions apps/ligretto-gameplay-backend/src/IOC_TYPES.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
export const IOC_TYPES = {
WebSocketHandler: Symbol.for('WebSocketHandler'),
GameService: Symbol.for('GameService'),
GameRepository: Symbol.for('GameRepository'),
PlaygroundService: Symbol.for('PlaygroundService'),
PlaygroundRepository: Symbol.for('PlaygroundRepository'),
PlayerService: Symbol.for('PlayerService'),
PlayerRepository: Symbol.for('PlayerRepository'),
Gameplay: Symbol.for('Gameplay'),
GameplayController: Symbol.for('GameplayController'),
GamesController: Symbol.for('GamesController'),
RoomRepository: Symbol.for('RoomRepository'),
RoomService: Symbol.for('RoomService'),
Emitter: Symbol.for('Emitter'),
Database: Symbol.for('Database'),
UserRepository: Symbol.for('UserRepository'),
UserService: Symbol.for('UserService'),
BotController: Symbol.for('BotController'),
AuthService: Symbol.for('AuthService'),
LigrettoCoreService: Symbol.for('LigrettoCoreService'),
IWebSocketHandler: Symbol.for('IWebSocketHandler'),
IGameService: Symbol.for('IGameService'),
IGameRepository: Symbol.for('IGameRepository'),
IPlaygroundService: Symbol.for('IPlaygroundService'),
IPlaygroundRepository: Symbol.for('IPlaygroundRepository'),
IPlayerService: Symbol.for('IPlayerService'),
IPlayerRepository: Symbol.for('IPlayerRepository'),
IGameplay: Symbol.for('IGameplay'),
IGamesController: Symbol.for('IGamesController'),
IDatabase: Symbol.for('IDatabase'),
IUserRepository: Symbol.for('IUserRepository'),
IUserService: Symbol.for('IUserService'),
IController: Symbol.for('IController'),
IAuthService: Symbol.for('IAuthService'),
ILigrettoCoreService: Symbol.for('ILigrettoCoreService'),
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ describe('Games Controller', () => {
let createGameService = jest.fn().mockReturnValue({ id: gameId })
let saveGameRoundService = jest.fn().mockReturnValue({})

let gamesController: GamesController = container.get(IOC_TYPES.GamesController)
let gamesController: GamesController = container.get(IOC_TYPES.IGamesController)

beforeEach(() => {
container = createIOC()
socketMockImpl = createSocketMockImpl()
createGameService = jest.fn().mockReturnValue({ id: gameId })
saveGameRoundService = jest.fn().mockReturnValue({})
container.rebind(IOC_TYPES.LigrettoCoreService).toConstantValue({ createGameService, saveGameRoundService })
gamesController = container.get(IOC_TYPES.GamesController)
container.rebind(IOC_TYPES.ILigrettoCoreService).toConstantValue({ createGameService, saveGameRoundService })
gamesController = container.get(IOC_TYPES.IGamesController)
})

it('should be defined', () => {
Expand All @@ -46,7 +46,7 @@ describe('Games Controller', () => {

describe('createGame', () => {
it('Should create relevant state on create game', async () => {
const database: Database = container.get(IOC_TYPES.Database)
const database: Database = container.get(IOC_TYPES.IDatabase)

const roomName = 'createGame'

Expand Down Expand Up @@ -80,8 +80,8 @@ describe('Games Controller', () => {

beforeEach(async () => {
socketMockImpl.data = { user: { id: userId } }
gamesController = container.get(IOC_TYPES.GamesController)
const database: Database = container.get(IOC_TYPES.Database)
gamesController = container.get(IOC_TYPES.IGamesController)
const database: Database = container.get(IOC_TYPES.IDatabase)

await database.set(storage => {
storage.users = {
Expand Down Expand Up @@ -111,14 +111,14 @@ describe('Games Controller', () => {

it('Should create relevant state on join room as first player', async () => {
await gamesController.handleMessage(socketMockImpl, connectToRoomEmitAction({ roomUuid }) as AnyAction)
const database: Database = container.get(IOC_TYPES.Database)
const database: Database = container.get(IOC_TYPES.IDatabase)

const state = await database.get(db => db)
expect(state).toMatchSnapshot()
})

it('Should create relevant state on join room second player', async () => {
const database: Database = container.get(IOC_TYPES.Database)
const database: Database = container.get(IOC_TYPES.IDatabase)
const secondUserSocket = createSocketMockImpl({ id: 'secondUserSocketId' })
secondUserSocket.data = { user: { id: 'secondUserId' } }
const secondUserId = 'secondUserId'
Expand All @@ -140,7 +140,7 @@ describe('Games Controller', () => {
})

it('Should create relevant state on join room by second connection', async () => {
const database: Database = container.get(IOC_TYPES.Database)
const database: Database = container.get(IOC_TYPES.IDatabase)
await gamesController.handleMessage(socketMockImpl, connectToRoomEmitAction({ roomUuid }) as AnyAction)
const secondUserSocket = createSocketMockImpl()
secondUserSocket.data = { user: { id: userId } }
Expand Down Expand Up @@ -169,8 +169,8 @@ describe('Games Controller', () => {
let socketTwo = createSocketMockImpl({ id: 'socket2', data: { user: { id: userTwoId } } })

beforeEach(async () => {
gamesController = container.get(IOC_TYPES.GamesController)
const database: Database = container.get(IOC_TYPES.Database)
gamesController = container.get(IOC_TYPES.IGamesController)
const database: Database = container.get(IOC_TYPES.IDatabase)
socketOne = createSocketMockImpl({ id: 'socket1', data: { user: { id: userOneId } } })
socketTwo = createSocketMockImpl({ id: 'socket2', data: { user: { id: userTwoId } } })

Expand All @@ -195,7 +195,7 @@ describe('Games Controller', () => {
})

it('Should remove current socketId from user socket ids if user connected from few accounts', async () => {
const database: Database = container.get(IOC_TYPES.Database)
const database: Database = container.get(IOC_TYPES.IDatabase)

await database.set(storage => {
storage.users = {
Expand All @@ -219,7 +219,7 @@ describe('Games Controller', () => {
})

it('Should create a relevant game state if one of two players leaved', async () => {
const database: Database = container.get(IOC_TYPES.Database)
const database: Database = container.get(IOC_TYPES.IDatabase)

await database.set(storage => {
storage.users = {
Expand Down Expand Up @@ -249,7 +249,7 @@ describe('Games Controller', () => {
})

it('Should create a relevant state if last user disconnected', async () => {
const database: Database = container.get(IOC_TYPES.Database)
const database: Database = container.get(IOC_TYPES.IDatabase)
await gamesController.handleMessage(socketOne, connectToRoomEmitAction({ roomUuid }) as AnyAction)

await gamesController.disconnectionHandler(socketOne)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import type { Socket } from 'socket.io'
import { inject, injectable } from 'inversify'
import { initBot, stopBot } from '../bot'
import { IOC_TYPES } from '../IOC_TYPES'
import { GameService } from '../entities/game/game.service'
import { IGameService } from '../entities/game/game.service'
import { Controller } from './controller'

@injectable()
export class BotController extends Controller {
@inject(IOC_TYPES.GameService) private gameService: GameService
@inject(IOC_TYPES.IGameService) private gameService: IGameService

handlers: Controller['handlers'] = {
[addBotAction.type]: (socket, action) => this.addBotToGame(socket, action),
Expand Down
7 changes: 6 additions & 1 deletion apps/ligretto-gameplay-backend/src/controllers/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@ import type { Socket } from 'socket.io'
import { injectable } from 'inversify'
import type { AnyAction } from '../types/any-action'

export interface IController {
handleMessage: (socket: Socket, action: AnyAction) => void
}

// XXX: Is there the necessity of IController
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think no
Lets try to remove @injectable()

@injectable()
export abstract class Controller {
export abstract class Controller implements IController {
protected abstract handlers: {
[actionType: string]: <A extends AnyAction>(socket: Socket, action: A) => void
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ import {
takeFromStackDeckAction,
} from '@memebattle/ligretto-shared'
import { IOC_TYPES } from '../IOC_TYPES'
import { Gameplay } from '../gameplay/gameplay'
import { GameService } from '../entities/game/game.service'
import { IGameplay } from '../gameplay/gameplay'
import { IGameService } from '../entities/game/game.service'
import { wait } from '../utils/wait'

@injectable()
export class GameplayController extends Controller {
@inject(IOC_TYPES.Gameplay) private gameplay: Gameplay
@inject(IOC_TYPES.GameService) private gameService: GameService
@inject(IOC_TYPES.IGameplay) private gameplay: IGameplay
@inject(IOC_TYPES.IGameService) private gameService: IGameService

protected handlers: Controller['handlers'] = {
[startGameEmitAction.type]: (socket, action: ReturnType<typeof startGameEmitAction>) => this.startGame(socket, action),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { inject, injectable } from 'inversify'
import { IOC_TYPES } from '../IOC_TYPES'
import { Controller } from './controller'
import type { Socket } from 'socket.io'
import { GameService } from '../entities/game/game.service'
import { UserService } from '../entities/user'
import { IGameService } from '../entities/game/game.service'
import { IUserService } from '../entities/user'
import type { Game } from '@memebattle/ligretto-shared'
import type { IController } from './controller'
import {
connectToRoomEmitAction,
connectToRoomErrorAction,
Expand All @@ -25,10 +26,13 @@ import {
import { SOCKET_ROOM_LOBBY } from '../config'
import { gameToRoom } from '../utils/mappers'

export interface IGamesController extends IController {
disconnectionHandler: (socket: Socket) => Promise<void>
}
@injectable()
export class GamesController extends Controller {
@inject(IOC_TYPES.GameService) private gameService: GameService
@inject(IOC_TYPES.UserService) private userService: UserService
export class GamesController extends Controller implements IGamesController {
@inject(IOC_TYPES.IGameService) private gameService: IGameService
@inject(IOC_TYPES.IUserService) private userService: IUserService

protected handlers: Controller['handlers'] = {
[createRoomEmitAction.type]: (socket, action) => this.createGame(socket, action),
Expand Down
4 changes: 2 additions & 2 deletions apps/ligretto-gameplay-backend/src/database/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const storage: Storage = {
type Accessor<T> = (storage: Storage) => T
type Setter<T = void> = (storage: Storage) => T

export interface Database {
export interface IDatabase {
get<T>(accessor: Accessor<T>): Promise<T>
set<T>(setter: Setter<T>): Promise<T>
// eslint-disable-next-line @typescript-eslint/ban-types
Expand All @@ -34,7 +34,7 @@ const createChangesWatcher = <T extends object>(obj: T) => {
}

@injectable()
export class Database implements Database {
export class Database implements IDatabase {
private storage: Storage = {
games: {},
users: {},
Expand Down
2 changes: 1 addition & 1 deletion apps/ligretto-gameplay-backend/src/database/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { Database } from './database'
export { Database, IDatabase } from './database'
27 changes: 19 additions & 8 deletions apps/ligretto-gameplay-backend/src/entities/game/game.repo.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
import { inject, injectable } from 'inversify'
import type { Game, Player, UUID, Spectator } from '@memebattle/ligretto-shared'
import { PlayerStatus } from '@memebattle/ligretto-shared'
import { Database } from '../../database'
import { IDatabase } from '../../database'
import { IOC_TYPES } from '../../IOC_TYPES'

export interface IGameRepository {
addGame(gameId: UUID, game: Game): Promise<Game>
getGame(gameId: UUID): Promise<Game>
updateGame(gameId: UUID, updater: (game: Game) => Game): Promise<Game>
getGameByName(gameName: string): Promise<string | undefined>
removeGame(gameId: UUID): Promise<boolean>
getGames(): Promise<Game[]>
getGamesByNames(games: Record<UUID, Game>): Record<string, UUID | undefined>
createPlayer(playerData: Partial<Player> & { id: Player['id'] }): Player
createSpectator(playerData: Partial<Spectator> & { id: Spectator['id'] }): Spectator
}
@injectable()
export class GameRepository {
@inject(IOC_TYPES.Database) private database: Database
export class GameRepository implements IGameRepository {
@inject(IOC_TYPES.IDatabase) private database: IDatabase

async addGame(gameId: UUID, game: Game) {
return this.database.set<Game>(storage => (storage.games[gameId] = game))
Expand All @@ -16,7 +27,7 @@ export class GameRepository {
return this.database.get(storage => storage.games[gameId])
}

async updateGame(gameId: UUID, updater: (game: Game) => Game): Promise<Game> {
async updateGame(gameId: UUID, updater: (game: Game) => Game) {
const game = await this.getGame(gameId)

return this.database.set(storage => (storage.games[gameId] = updater(game)))
Expand All @@ -32,12 +43,12 @@ export class GameRepository {
return this.database.set(storage => delete storage.games[gameId])
}

async getGames(): Promise<Game[]> {
async getGames() {
const games = (await this.database.get(storage => Object.values(storage.games))).filter(Boolean) as Game[]
return games
}

getGamesByNames(games: Record<UUID, Game>): Record<string, UUID | undefined> {
getGamesByNames(games: Record<UUID, Game>) {
const result: Record<string, UUID | undefined> = {}

Object.values(games).forEach(game => {
Expand All @@ -47,7 +58,7 @@ export class GameRepository {
return result
}

createPlayer(playerData: Partial<Player> & { id: Player['id'] }): Player {
createPlayer(playerData: Partial<Player> & { id: Player['id'] }) {
return {
isHost: false,
status: PlayerStatus.DontReadyToPlay,
Expand All @@ -65,7 +76,7 @@ export class GameRepository {
}
}

createSpectator(playerData: Partial<Spectator> & { id: Spectator['id'] }): Spectator {
createSpectator(playerData: Partial<Spectator> & { id: Spectator['id'] }) {
return {
...playerData,
}
Expand Down
26 changes: 21 additions & 5 deletions apps/ligretto-gameplay-backend/src/entities/game/game.service.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
import { inject, injectable } from 'inversify'
import { groupBy, mapValues, merge, mergeWith, omit } from 'lodash'
import { GameRepository } from './game.repo'
import { IGameRepository } from './game.repo'
import type { Game, GameResults, Player, Spectator, UUID } from '@memebattle/ligretto-shared'
import { PlayerStatus, GameStatus } from '@memebattle/ligretto-shared'
import { createInitialPlayerCards } from '../../utils/create-initial-player-cards'
import { IOC_TYPES } from '../../IOC_TYPES'
import { nonNullable } from '../../utils/nonNullable'
import { LigrettoCoreService } from '../../services/ligretto-core'
import { ILigrettoCoreService } from '../../services/ligretto-core'

export interface IGameService {
createGame(name: string, config?: Partial<Game['config']>): Promise<Game | null>
initiateStartGame(gameId: UUID): Promise<Game>
startGame(gameId: UUID): Promise<Game>
pauseGame(gameId: UUID): Promise<Game>
addPlayer(gameId: UUID, playerData: Partial<Player> & { id: Player['id'] }): Promise<{ game: Game; player: Player }>
addSpectator(gameId: UUID, spectatorData: Partial<Spectator> & { id: Spectator['id'] }): Promise<{ game: Game; spectator: Spectator }>
updateGamePlayer(gameId: Game['id'], playerId: Player['id'], playerData: Partial<Player>): Promise<Game>
getGame(gameId: UUID): Promise<Game>
getRoundResult(gameId: UUID): Promise<{ [x: string]: { roundScore: number } }>
endGame(gameId: UUID): Promise<(Game | GameResults | undefined)[]>
finishRound(gameId: UUID): Promise<{ game?: Game; gameResults?: GameResults }>
getGames(): Promise<Game[]>
leaveGame(gameId: UUID, userId: Player['id'] | Spectator['id']): Promise<Game | undefined>
}

const emptyGame: Game = {
id: 'base',
Expand All @@ -22,9 +38,9 @@ const emptyGame: Game = {
}

@injectable()
export class GameService {
@inject(IOC_TYPES.GameRepository) private gameRepository: GameRepository
@inject(IOC_TYPES.LigrettoCoreService) private ligrettoCoreService: LigrettoCoreService
export class GameService implements IGameService {
@inject(IOC_TYPES.IGameRepository) private gameRepository: IGameRepository
@inject(IOC_TYPES.ILigrettoCoreService) private ligrettoCoreService: ILigrettoCoreService

async createGame(name: string, config: Partial<Game['config']> = {}): Promise<Game | null> {
const isGameExists = !!(await this.gameRepository.getGameByName(name))
Expand Down
23 changes: 19 additions & 4 deletions apps/ligretto-gameplay-backend/src/entities/player/player.repo.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
import { inject, injectable } from 'inversify'
import type { Card, CardsDeck, UUID } from '@memebattle/ligretto-shared'
import { Database } from '../../database'
import type { Card, CardsDeck, UUID, Player } from '@memebattle/ligretto-shared'
import { IDatabase } from '../../database'
import { IOC_TYPES } from '../../IOC_TYPES'

export interface IPlayerRepository {
getPlayer(gameId: UUID, playerId: UUID): Promise<Player | undefined>
getCards(gameId: UUID, playerId: UUID): Promise<(Card | null)[] | undefined>
getCard(gameId: UUID, playerId: UUID, position: number): Promise<Card | null | undefined>
addCard(gameId: UUID, playerId: UUID, card: Card, position: number): Promise<(Card | null)[] | undefined>
removeCard(gameId: UUID, playerId: UUID, position: number): Promise<(Card | null)[] | undefined>
getLigrettoDeck(gameId: UUID, playerId: UUID): Promise<CardsDeck | undefined>
removeCardFromLigrettoDeck(gameId: UUID, playerId: UUID): Promise<number | undefined>
getStackDeck(gameId: UUID, playerId: UUID): Promise<CardsDeck | undefined>
getStackOpenDeck(gameId: UUID, playerId: UUID): Promise<CardsDeck | undefined>
removeCardFromStackOpenDeck(gameId: UUID, playerId: UUID): Promise<Card | undefined>
updateStackDeck(gameId: UUID, playerId: UUID, updater: (cardsDeck: CardsDeck) => CardsDeck): Promise<CardsDeck | undefined>
updateStackOpenDeck(gameId: UUID, playerId: UUID, updater: (cardsDeck: CardsDeck) => CardsDeck): Promise<CardsDeck | undefined>
}

@injectable()
export class PlayerRepository {
@inject(IOC_TYPES.Database) private database: Database
export class PlayerRepository implements IPlayerRepository {
@inject(IOC_TYPES.IDatabase) private database: IDatabase

async getPlayer(gameId: UUID, playerId: UUID) {
return this.database.get(storage => storage.games[gameId].players[playerId])
Expand Down
Loading
Loading