diff --git a/.vscode/settings.json b/.vscode/settings.json
index 68b17798..97e1e3c5 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,7 +1,14 @@
{
"files.trimTrailingWhitespace": true,
"npm.packageManager": "yarn",
- "cSpell.words": ["injectable", "nestjs", "postgres", "styleguide", "typeorm"],
+ "cSpell.words": [
+ "injectable",
+ "memeber",
+ "nestjs",
+ "postgres",
+ "styleguide",
+ "typeorm"
+ ],
"gitlens.advanced.blame.customArguments": [],
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
diff --git a/apps/auth-front/src/components/AvatarDropzone/AvatarDropzone.tsx b/apps/auth-front/src/components/AvatarDropzone/AvatarDropzone.tsx
index d8f41c11..9f9ebc66 100644
--- a/apps/auth-front/src/components/AvatarDropzone/AvatarDropzone.tsx
+++ b/apps/auth-front/src/components/AvatarDropzone/AvatarDropzone.tsx
@@ -33,12 +33,23 @@ export const AvatarDropzone = memo(({ avatarUrl, onChange }: DropZoneProps) => {
maxSize: MAX_FILE_SIZE,
onDropAccepted: acceptedFiles => {
const acceptedFile = acceptedFiles[0]
+
+ if (!acceptedFile) {
+ return
+ }
+
setStateFile({ file: acceptedFile, previewUrl: URL.createObjectURL(acceptedFile) })
clearError()
onChange?.(acceptedFile)
},
onDropRejected: fileRejections => {
- handleError(fileRejections[0])
+ const fileRejection = fileRejections[0]
+
+ if (!fileRejection) {
+ return
+ }
+
+ handleError(fileRejection)
},
})
@@ -57,7 +68,7 @@ export const AvatarDropzone = memo(({ avatarUrl, onChange }: DropZoneProps) => {
{!isDragActive ? (
-
+
{stateFile?.file ? (
) : (
diff --git a/apps/auth-front/tsconfig.json b/apps/auth-front/tsconfig.json
index 7041971b..26f523d9 100644
--- a/apps/auth-front/tsconfig.json
+++ b/apps/auth-front/tsconfig.json
@@ -10,6 +10,7 @@
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
+ "noUncheckedIndexedAccess": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
diff --git a/apps/blog/src/app/[locale]/feed/_utils/channelInfoCreator.ts b/apps/blog/src/app/[locale]/feed/_utils/channelInfoCreator.ts
index dc5076e7..a96e2735 100644
--- a/apps/blog/src/app/[locale]/feed/_utils/channelInfoCreator.ts
+++ b/apps/blog/src/app/[locale]/feed/_utils/channelInfoCreator.ts
@@ -44,7 +44,7 @@ export const createChannelInfo = async (lang: Language): Promise
=>
description: t('main.description'),
atomUrl: generateFullUrl(`/feed/${lang}`),
url: generateFullUrl(`/${lang}/posts`),
- updatedAt: latestPost.publishedAt,
+ updatedAt: latestPost?.publishedAt ?? new Date().toISOString(),
}
return { ...channelMetadata, posts: feedPosts }
diff --git a/apps/blog/src/app/[locale]/posts/_utils/filterBlogPosts.test.ts b/apps/blog/src/app/[locale]/posts/_utils/filterBlogPosts.test.ts
index 3092f069..80c8afc7 100644
--- a/apps/blog/src/app/[locale]/posts/_utils/filterBlogPosts.test.ts
+++ b/apps/blog/src/app/[locale]/posts/_utils/filterBlogPosts.test.ts
@@ -69,8 +69,8 @@ const posts = [
},
]
-posts[0].translates = { en: posts[2] }
-posts[2].translates = { ru: posts[1] }
+posts[0]!.translates = { en: posts[2] }
+posts[2]!.translates = { ru: posts[1] }
describe('filterBlogPosts', () => {
it('Should return all (uniq by locale) posts if search and tags empty', () => {
diff --git a/apps/blog/src/generation-utils/mapHeadingsToTOC.js b/apps/blog/src/generation-utils/mapHeadingsToTOC.js
index a68a6af5..e311c2e7 100644
--- a/apps/blog/src/generation-utils/mapHeadingsToTOC.js
+++ b/apps/blog/src/generation-utils/mapHeadingsToTOC.js
@@ -19,6 +19,7 @@ export function mapHeadingsToTOC(headings, tree = [], currentLevel = 2) {
/**
* @type {import('../types').TOCTreeItem}
*/
+ // @ts-expect-error - headings[0] exists
const heading = headings[0]
if (heading.level < currentLevel) {
return tree
diff --git a/apps/blog/tsconfig.json b/apps/blog/tsconfig.json
index 2e1157d1..278b1e17 100644
--- a/apps/blog/tsconfig.json
+++ b/apps/blog/tsconfig.json
@@ -10,6 +10,7 @@
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
+ "noUncheckedIndexedAccess": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
diff --git a/apps/gamehub-client/tsconfig.json b/apps/gamehub-client/tsconfig.json
index 3f4abda7..3a2210bc 100644
--- a/apps/gamehub-client/tsconfig.json
+++ b/apps/gamehub-client/tsconfig.json
@@ -10,6 +10,7 @@
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
+ "noUncheckedIndexedAccess": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
diff --git a/apps/ligretto-core-backend/tsconfig.json b/apps/ligretto-core-backend/tsconfig.json
index 71978c18..ef44f17a 100644
--- a/apps/ligretto-core-backend/tsconfig.json
+++ b/apps/ligretto-core-backend/tsconfig.json
@@ -18,6 +18,7 @@
"declaration": false,
"moduleResolution": "node",
"strictNullChecks": true,
+ "noUncheckedIndexedAccess": true,
"allowSyntheticDefaultImports": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
diff --git a/apps/ligretto-frontend/src/features/leader-board/ui/LeaderList/LeaderListTable.tsx b/apps/ligretto-frontend/src/features/leader-board/ui/LeaderList/LeaderListTable.tsx
index 1b9c48f3..76b8316b 100644
--- a/apps/ligretto-frontend/src/features/leader-board/ui/LeaderList/LeaderListTable.tsx
+++ b/apps/ligretto-frontend/src/features/leader-board/ui/LeaderList/LeaderListTable.tsx
@@ -15,7 +15,7 @@ const leaderListTableSpecs = [
{ title: '#', size: { xs: 2, md: 1.5 } },
{ title: 'Player', size: { xs: 7.7, md: 8.7 } },
{ title: 'Points', size: { xs: 1.8, md: 1.8 } },
-]
+] as const
const placeImages = [firstLevelImg, secondLevelImg, thirdLevelImg]
diff --git a/apps/ligretto-frontend/src/features/playground/ui/Playground.tsx b/apps/ligretto-frontend/src/features/playground/ui/Playground.tsx
index df4d5adc..c6fe97d3 100644
--- a/apps/ligretto-frontend/src/features/playground/ui/Playground.tsx
+++ b/apps/ligretto-frontend/src/features/playground/ui/Playground.tsx
@@ -9,7 +9,7 @@ export interface PlaygroundProps {
onDeckClick: (playgroundDeckIndex: number) => void
}
-const getLastCard = (deck: CardsDeck | null): Card | undefined => last(deck?.cards)
+const getLastCard = (deck: CardsDeck | null | undefined): Card | undefined => last(deck?.cards)
export const Playground: React.FC = ({ cardsDecks, onDeckClick }) => {
const cards: (Card | undefined)[] = useMemo(() => {
diff --git a/apps/ligretto-frontend/tsconfig.json b/apps/ligretto-frontend/tsconfig.json
index 40ee0264..4f60c779 100644
--- a/apps/ligretto-frontend/tsconfig.json
+++ b/apps/ligretto-frontend/tsconfig.json
@@ -10,6 +10,7 @@
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
+ "noUncheckedIndexedAccess": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
diff --git a/apps/ligretto-gameplay-backend/src/controllers/__tests__/games-contoller.spec.ts b/apps/ligretto-gameplay-backend/src/controllers/__tests__/games-contoller.spec.ts
index 54b7b3af..4c8c9810 100644
--- a/apps/ligretto-gameplay-backend/src/controllers/__tests__/games-contoller.spec.ts
+++ b/apps/ligretto-gameplay-backend/src/controllers/__tests__/games-contoller.spec.ts
@@ -60,9 +60,9 @@ describe('Games Controller', () => {
expect(state).toMatchSnapshot()
expect(socketMockImpl.emit).toBeCalledTimes(2)
- expect(socketMockImpl.emit).toBeCalledWith('event', createRoomSuccessAction({ game: state.games[gameId] }))
+ expect(socketMockImpl.emit).toBeCalledWith('event', createRoomSuccessAction({ game: state.games[gameId]! }))
expect(socketMockImpl.to).toBeCalledWith(SOCKET_ROOM_LOBBY)
- expect(socketMockImpl.emit).toBeCalledWith('event', updateRoomsAction({ rooms: [gameToRoom(state.games[gameId])] }))
+ expect(socketMockImpl.emit).toBeCalledWith('event', updateRoomsAction({ rooms: [gameToRoom(state.games[gameId]!)] }))
})
it('Should emit createRoomErrorAction if room already exists', async () => {
@@ -216,8 +216,8 @@ describe('Games Controller', () => {
const state = await database.get(storage => storage)
expect(state).toMatchSnapshot()
expect(socketOne.to).toBeCalledTimes(4)
- expect(socketOne.emit).toBeCalledWith('event', updateRoomsAction({ rooms: [gameToRoom(state.games[roomUuid])] }))
- expect(socketOne.emit).toBeCalledWith('event', updateGameAction(state.games[roomUuid]))
+ expect(socketOne.emit).toBeCalledWith('event', updateRoomsAction({ rooms: [gameToRoom(state.games[roomUuid]!)] }))
+ expect(socketOne.emit).toBeCalledWith('event', updateGameAction(state.games[roomUuid]!))
})
it('Should create a relevant game state if one of two players leaved', async () => {
diff --git a/apps/ligretto-gameplay-backend/src/controllers/gameplay-controller.ts b/apps/ligretto-gameplay-backend/src/controllers/gameplay-controller.ts
index d0de0072..c314af9b 100644
--- a/apps/ligretto-gameplay-backend/src/controllers/gameplay-controller.ts
+++ b/apps/ligretto-gameplay-backend/src/controllers/gameplay-controller.ts
@@ -42,6 +42,10 @@ export class GameplayController extends Controller {
private async updateGame(socket: Socket, gameId: string, gameState?: Game) {
const game = gameState || (await this.gameService.getGame(gameId))
+ if (!game) {
+ return
+ }
+
const action = updateGameAction(game)
socket.to(gameId).emit('event', action)
diff --git a/apps/ligretto-gameplay-backend/src/database/database.ts b/apps/ligretto-gameplay-backend/src/database/database.ts
index ef3e2043..9eedd590 100644
--- a/apps/ligretto-gameplay-backend/src/database/database.ts
+++ b/apps/ligretto-gameplay-backend/src/database/database.ts
@@ -43,11 +43,8 @@ export class Database implements Database {
private listeners: Record void> = {}
private notifyListeners() {
- for (const listenerKey in this.listeners) {
- if (this.listeners.hasOwnProperty(listenerKey)) {
- const listener = this.listeners[listenerKey]
- listener(this.storage)
- }
+ for (const listener of Object.values(this.listeners)) {
+ listener(this.storage)
}
}
diff --git a/apps/ligretto-gameplay-backend/src/entities/game/game.repo.ts b/apps/ligretto-gameplay-backend/src/entities/game/game.repo.ts
index 6a3326ef..f0e947a6 100644
--- a/apps/ligretto-gameplay-backend/src/entities/game/game.repo.ts
+++ b/apps/ligretto-gameplay-backend/src/entities/game/game.repo.ts
@@ -19,6 +19,10 @@ export class GameRepository {
async updateGame(gameId: UUID, updater: (game: Game) => Game): Promise {
const game = await this.getGame(gameId)
+ if (!game) {
+ throw new Error(`Game with id ${gameId} not found`)
+ }
+
return this.database.set(storage => (storage.games[gameId] = updater(game)))
}
diff --git a/apps/ligretto-gameplay-backend/src/entities/game/game.service.ts b/apps/ligretto-gameplay-backend/src/entities/game/game.service.ts
index 444f4496..4d67b316 100644
--- a/apps/ligretto-gameplay-backend/src/entities/game/game.service.ts
+++ b/apps/ligretto-gameplay-backend/src/entities/game/game.service.ts
@@ -147,6 +147,10 @@ export class GameService {
async getRoundResult(gameId: UUID) {
const game = await this.getGame(gameId)
+ if (!game) {
+ throw new Error(`Game with id ${gameId} not found`)
+ }
+
const initialScoresByPlayer = Object.keys(game.players).reduce>((scores, playerId) => ({ ...scores, [playerId]: 0 }), {})
const clearPlaygroundDecks = game.playground.decks.filter(nonNullable)
diff --git a/apps/ligretto-gameplay-backend/src/entities/player/player.repo.ts b/apps/ligretto-gameplay-backend/src/entities/player/player.repo.ts
index d1171034..55d0eda4 100644
--- a/apps/ligretto-gameplay-backend/src/entities/player/player.repo.ts
+++ b/apps/ligretto-gameplay-backend/src/entities/player/player.repo.ts
@@ -8,45 +8,63 @@ export class PlayerRepository {
@inject(IOC_TYPES.Database) private database: Database
async getPlayer(gameId: UUID, playerId: UUID) {
- return this.database.get(storage => storage.games[gameId].players[playerId])
+ return this.database.get(storage => storage.games[gameId]?.players?.[playerId])
}
async getCards(gameId: UUID, playerId: UUID) {
- return this.database.get(storage => storage.games[gameId].players[playerId]?.cards)
+ return this.database.get(storage => storage.games[gameId]?.players?.[playerId]?.cards)
}
async getCard(gameId: UUID, playerId: UUID, position: number) {
- return this.database.get(storage => storage.games[gameId].players[playerId]?.cards[position])
+ return this.database.get(storage => storage.games[gameId]?.players?.[playerId]?.cards?.[position])
}
async addCard(gameId: UUID, playerId: UUID, card: Card, position: number) {
- return this.database.set(storage => storage.games[gameId].players[playerId]?.cards.splice(position, 1, card))
+ return this.database.set(storage => {
+ const player = storage.games[gameId]?.players?.[playerId]
+ const cards = player?.cards
+
+ if (!cards) {
+ return
+ }
+
+ return cards.splice(position, 1, card)
+ })
}
async removeCard(gameId: UUID, playerId: UUID, position: number) {
- return this.database.set(storage => storage.games[gameId].players[playerId]?.cards.splice(position, 1, null))
+ return this.database.set(storage => {
+ const player = storage.games[gameId]?.players?.[playerId]
+ const cards = player?.cards
+
+ if (!cards) {
+ return
+ }
+
+ return cards.splice(position, 1, null)
+ })
}
async getLigrettoDeck(gameId: UUID, playerId: UUID) {
- return this.database.get(storage => storage.games[gameId].players[playerId]?.ligrettoDeck)
+ return this.database.get(storage => storage.games[gameId]?.players?.[playerId]?.ligrettoDeck)
}
async removeCardFromLigrettoDeck(gameId: UUID, playerId: UUID) {
- await this.database.set(storage => storage.games[gameId].players[playerId]?.ligrettoDeck.cards.pop())
+ await this.database.set(storage => storage.games[gameId]?.players?.[playerId]?.ligrettoDeck?.cards.pop())
return (await this.getLigrettoDeck(gameId, playerId))?.cards.length
}
async getStackDeck(gameId: UUID, playerId: UUID) {
- return this.database.get(storage => storage.games[gameId].players[playerId]?.stackDeck)
+ return this.database.get(storage => storage.games[gameId]?.players?.[playerId]?.stackDeck)
}
async getStackOpenDeck(gameId: UUID, playerId: UUID) {
- return this.database.get(storage => storage.games[gameId].players[playerId]?.stackOpenDeck)
+ return this.database.get(storage => storage.games[gameId]?.players?.[playerId]?.stackOpenDeck)
}
async removeCardFromStackOpenDeck(gameId: UUID, playerId: UUID) {
- return this.database.set(storage => storage.games[gameId].players[playerId]?.stackOpenDeck.cards.pop())
+ return this.database.set(storage => storage.games[gameId]?.players?.[playerId]?.stackOpenDeck?.cards.pop())
}
async updateStackDeck(gameId: UUID, playerId: UUID, updater: (cardsDeck: CardsDeck) => CardsDeck) {
@@ -55,7 +73,7 @@ export class PlayerRepository {
return
}
return this.database.set(storage => {
- const player = storage.games[gameId].players[playerId]
+ const player = storage.games[gameId]?.players?.[playerId]
if (!player) {
return
}
@@ -69,7 +87,7 @@ export class PlayerRepository {
return
}
return this.database.set(storage => {
- const player = storage.games[gameId].players[playerId]
+ const player = storage.games[gameId]?.players?.[playerId]
if (!player) {
return
}
diff --git a/apps/ligretto-gameplay-backend/src/entities/playground/playground.repo.ts b/apps/ligretto-gameplay-backend/src/entities/playground/playground.repo.ts
index 425c6197..a383d67d 100644
--- a/apps/ligretto-gameplay-backend/src/entities/playground/playground.repo.ts
+++ b/apps/ligretto-gameplay-backend/src/entities/playground/playground.repo.ts
@@ -8,24 +8,35 @@ export class PlaygroundRepository {
@inject(IOC_TYPES.Database) private database: Database
getDecks(gameId: UUID) {
- return this.database.get(storage => storage.games[gameId].playground.decks)
+ return this.database.get(storage => storage.games[gameId]?.playground.decks)
}
getDeck(gameId: UUID, position: number) {
- return this.database.get(storage => storage.games[gameId].playground.decks[position])
+ return this.database.get(storage => storage.games[gameId]?.playground.decks?.[position])
}
addDroppedDeck(gameId: UUID, cardsDeck: CardsDeck) {
return this.database.set(storage => {
- const decks = storage.games[gameId].playground.droppedDecks
- decks.push(cardsDeck)
- return decks
+ const game = storage.games[gameId]
+ const droppedDecks = game?.playground.droppedDecks
+
+ if (!droppedDecks) {
+ return
+ }
+
+ droppedDecks.push(cardsDeck)
+ return droppedDecks
})
}
removeDeck(gameId: UUID, position: number) {
return this.database.set(storage => {
- storage.games[gameId].playground.decks[position] = null
+ const game = storage.games[gameId]
+ if (!game) {
+ return
+ }
+
+ game.playground.decks[position] = null
})
}
@@ -33,9 +44,14 @@ export class PlaygroundRepository {
const deck = await this.getDeck(gameId, position)
return this.database.set(storage => {
- const updated = updater(deck)
+ const game = storage.games[gameId]
+ if (!game) {
+ return
+ }
+
+ const updated = updater(deck ?? null)
console.log('Updated deck', position, updated)
- storage.games[gameId].playground.decks[position] = updated
+ game.playground.decks[position] = updated
})
}
}
diff --git a/apps/ligretto-gameplay-backend/src/entities/playground/playground.service.ts b/apps/ligretto-gameplay-backend/src/entities/playground/playground.service.ts
index 8bd1fe1b..36859a24 100644
--- a/apps/ligretto-gameplay-backend/src/entities/playground/playground.service.ts
+++ b/apps/ligretto-gameplay-backend/src/entities/playground/playground.service.ts
@@ -22,13 +22,18 @@ export class PlaygroundService {
async findAvailableDeckIndex(gameId: UUID, card: Card) {
const decks = await this.getDecks(gameId)
- return decks.findIndex(deck => isDeckAvailable(deck, card))
+
+ if (!decks) {
+ return -1
+ }
+
+ return decks.findIndex(deck => isDeckAvailable(deck ?? null, card))
}
async putCard(gameId: UUID, card: Card, deckIndex: number) {
const deck = await this.playgroundRepository.getDeck(gameId, deckIndex)
- if (!isDeckAvailable(deck, card)) {
+ if (!isDeckAvailable(deck ?? null, card)) {
return
}
diff --git a/apps/ligretto-gameplay-backend/tsconfig.json b/apps/ligretto-gameplay-backend/tsconfig.json
index 07b1c6c4..e9c90acc 100644
--- a/apps/ligretto-gameplay-backend/tsconfig.json
+++ b/apps/ligretto-gameplay-backend/tsconfig.json
@@ -14,6 +14,7 @@
"strict": true,
// Not compatible with inversify
"strictPropertyInitialization": false,
+ "noUncheckedIndexedAccess": true,
"skipLibCheck": true
},
"include": ["src/**/*"]
diff --git a/packages/analytics/tsconfig.json b/packages/analytics/tsconfig.json
index 27afef23..69aa217f 100644
--- a/packages/analytics/tsconfig.json
+++ b/packages/analytics/tsconfig.json
@@ -8,6 +8,7 @@
"outDir": "./build",
"sourceMap": true,
"strict": true,
+ "noUncheckedIndexedAccess": true,
"allowSyntheticDefaultImports": true
},
"exclude": ["node_modules", "build"],
diff --git a/packages/ligretto-shared/tsconfig.json b/packages/ligretto-shared/tsconfig.json
index ff650e17..6642b773 100644
--- a/packages/ligretto-shared/tsconfig.json
+++ b/packages/ligretto-shared/tsconfig.json
@@ -6,7 +6,8 @@
"module": "commonjs",
"moduleResolution": "node",
"outDir": "./build",
- "sourceMap": true
+ "sourceMap": true,
+ "noUncheckedIndexedAccess": true
},
"exclude": ["node_modules", "build"],
"include": ["src/**/*.ts"]
diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json
index 759e18e5..fbb08f88 100644
--- a/packages/ui/tsconfig.json
+++ b/packages/ui/tsconfig.json
@@ -14,6 +14,7 @@
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
+ "noUncheckedIndexedAccess": true,
"resolveJsonModule": true,
"baseUrl": "./src",
"noImplicitAny": true
diff --git a/tsconfig.json b/tsconfig.json
index 43be261b..6e6333f7 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,6 +1,7 @@
{
"files": ["typings/font.d.ts", "typings/images.d.ts"],
"compilerOptions": {
- "jsx": "react-jsx"
+ "jsx": "react-jsx",
+ "noUncheckedIndexedAccess": true
}
}