import { getConfig, hasConfig } from '@scrabble-solver/configs'; import { BLANK } from '@scrabble-solver/constants'; import { dictionaries } from '@scrabble-solver/dictionaries'; import { logger } from '@scrabble-solver/logger'; import { solve as solveScrabble } from '@scrabble-solver/solver'; import { Board, type Config, type Game, type Locale, Tile, isBoardJson, isGame, isLocale, } from '@scrabble-solver/types'; import { type NextApiRequest, type NextApiResponse } from 'next'; import { getServerLoggingData, isBoardValid, isCharacterValid } from '@/api'; import { isStringArray } from '@/lib'; interface RequestData { board: Board; characters: string[]; config: Config; game: Game; locale: Locale; } const solve = async (request: NextApiRequest, response: NextApiResponse): Promise => { const meta = getServerLoggingData(request); try { const { board, characters, config, game, locale } = parseRequest(request); logger.info('solve - request', { meta, payload: { board: board.toString(), configId: config.game, boardBlanksCount: board.getBlanksCount(), boardTilesCount: board.getTilesCount(), characters: characters.join(''), game, locale, }, }); const trie = await dictionaries.get(locale); const tiles = characters.map((character) => new Tile({ character, isBlank: character === BLANK })); const results = solveScrabble(trie, config, board, tiles); response.status(200).send(results); } catch (error) { const message = error instanceof Error ? error.message : 'Unknown error'; logger.error('solve - error', { error, meta }); response.status(500).send({ error: 'Server error', message }); } }; const parseRequest = (request: NextApiRequest): RequestData => { const { board: boardJson, characters, game, locale } = request.body; if (!isLocale(locale)) { throw new Error('Invalid "locale" parameter'); } if (!isGame(game)) { throw new Error('Invalid "game" parameter'); } if (!isStringArray(characters) || characters.length === 0) { throw new Error('Invalid "characters" parameter'); } if (!hasConfig(game, locale)) { throw new Error(`No game "${game}" in "${locale}"`); } const config = getConfig(game, locale); for (const character of characters) { if (!isCharacterValid(character)) { throw new Error('Invalid "characters" parameter'); } } const blanksCount = characters.filter((character) => character === BLANK).length; if (blanksCount > config.blanksCount) { throw new Error('Too many blank tiles passed'); } if (!isBoardJson(boardJson) || !isBoardValid(boardJson, config)) { throw new Error('Invalid "board" parameter'); } const board = Board.fromJson(boardJson); return { board, characters, config, game, locale, }; }; export const config = { api: { responseLimit: '25mb', }, }; export default solve;