Compare commits
20 Commits
rule-0.2.0
...
api-0.13.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 04a3ad721c | |||
| 590924254e | |||
| 67511fc649 | |||
| 093134d36c | |||
| ffeb3ce338 | |||
| 52b171c7af | |||
| a386f57c21 | |||
| 3ca2afbb4b | |||
| b5a2966ada | |||
| 74a4fce0ca | |||
| 8fc97bde02 | |||
| 2d75b2e80e | |||
| f088c4e9ff | |||
| 8a1cf909d4 | |||
| 33e785d22a | |||
| d16cec176b | |||
| 8744bee2dd | |||
| 5f4d33f3ca | |||
| 767d3051a7 | |||
| b2e62dc60c |
+546
-80
@@ -2,8 +2,55 @@
|
|||||||
|
|
||||||
> **Stack:** raw-http | none | unknown | scala
|
> **Stack:** raw-http | none | unknown | scala
|
||||||
|
|
||||||
> 0 routes | 0 models | 0 components | 35 lib files | 0 env vars | 0 middleware
|
> 0 routes + 40 rpc | 0 models | 0 components | 146 lib files | 1 env vars | 1 middleware
|
||||||
> **Token savings:** this file is ~3.700 tokens. Without it, AI exploration would cost ~18.200 tokens. **Saves ~14.500 tokens per conversation.**
|
> **Token savings:** this file is ~0 tokens. Without it, AI exploration would cost ~0 tokens. **Saves ~0 tokens per conversation.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Routes
|
||||||
|
|
||||||
|
## gRPC
|
||||||
|
|
||||||
|
- `/CoordinatorService/BatchResubscribeGames` (BatchResubscribeRequest) → BatchResubscribeResponse
|
||||||
|
- `/CoordinatorService/UnsubscribeGames` (UnsubscribeGamesRequest) → UnsubscribeGamesResponse
|
||||||
|
- `/CoordinatorService/EvictGames` (EvictGamesRequest) → EvictGamesResponse
|
||||||
|
- `/CoordinatorService/DrainInstance` (DrainInstanceRequest) → DrainInstanceResponse
|
||||||
|
- `/CoordinatorService/BatchResubscribeGames` (BatchResubscribeRequest) → BatchResubscribeResponse
|
||||||
|
- `/CoordinatorService/UnsubscribeGames` (UnsubscribeGamesRequest) → UnsubscribeGamesResponse
|
||||||
|
- `/CoordinatorService/EvictGames` (EvictGamesRequest) → EvictGamesResponse
|
||||||
|
- `/CoordinatorService/DrainInstance` (DrainInstanceRequest) → DrainInstanceResponse
|
||||||
|
- `/IoService/ImportFen` (ProtoImportFenRequest) → ProtoGameContext
|
||||||
|
- `/IoService/ImportPgn` (ProtoImportPgnRequest) → ProtoGameContext
|
||||||
|
- `/IoService/ExportCombined` (ProtoGameContext) → ProtoCombinedExport
|
||||||
|
- `/IoService/ExportFen` (ProtoGameContext) → ProtoStringResult
|
||||||
|
- `/IoService/ExportPgn` (ProtoGameContext) → ProtoStringResult
|
||||||
|
- `/RuleService/CandidateMoves` (ProtoSquareRequest) → ProtoMoveList
|
||||||
|
- `/RuleService/LegalMoves` (ProtoSquareRequest) → ProtoMoveList
|
||||||
|
- `/RuleService/AllLegalMoves` (ProtoGameContext) → ProtoMoveList
|
||||||
|
- `/RuleService/IsCheck` (ProtoGameContext) → ProtoBoolResult
|
||||||
|
- `/RuleService/IsCheckmate` (ProtoGameContext) → ProtoBoolResult
|
||||||
|
- `/RuleService/IsStalemate` (ProtoGameContext) → ProtoBoolResult
|
||||||
|
- `/RuleService/IsInsufficientMaterial` (ProtoGameContext) → ProtoBoolResult
|
||||||
|
- `/RuleService/IsFiftyMoveRule` (ProtoGameContext) → ProtoBoolResult
|
||||||
|
- `/RuleService/IsThreefoldRepetition` (ProtoGameContext) → ProtoBoolResult
|
||||||
|
- `/RuleService/ApplyMove` (ProtoMoveRequest) → ProtoGameContext
|
||||||
|
- `/RuleService/PostMoveStatus` (ProtoGameContext) → ProtoPostMoveStatus
|
||||||
|
- `/IoService/ImportFen` (ProtoImportFenRequest) → ProtoGameContext
|
||||||
|
- `/IoService/ImportPgn` (ProtoImportPgnRequest) → ProtoGameContext
|
||||||
|
- `/IoService/ExportCombined` (ProtoGameContext) → ProtoCombinedExport
|
||||||
|
- `/IoService/ExportFen` (ProtoGameContext) → ProtoStringResult
|
||||||
|
- `/IoService/ExportPgn` (ProtoGameContext) → ProtoStringResult
|
||||||
|
- `/RuleService/CandidateMoves` (ProtoSquareRequest) → ProtoMoveList
|
||||||
|
- `/RuleService/LegalMoves` (ProtoSquareRequest) → ProtoMoveList
|
||||||
|
- `/RuleService/AllLegalMoves` (ProtoGameContext) → ProtoMoveList
|
||||||
|
- `/RuleService/IsCheck` (ProtoGameContext) → ProtoBoolResult
|
||||||
|
- `/RuleService/IsCheckmate` (ProtoGameContext) → ProtoBoolResult
|
||||||
|
- `/RuleService/IsStalemate` (ProtoGameContext) → ProtoBoolResult
|
||||||
|
- `/RuleService/IsInsufficientMaterial` (ProtoGameContext) → ProtoBoolResult
|
||||||
|
- `/RuleService/IsFiftyMoveRule` (ProtoGameContext) → ProtoBoolResult
|
||||||
|
- `/RuleService/IsThreefoldRepetition` (ProtoGameContext) → ProtoBoolResult
|
||||||
|
- `/RuleService/ApplyMove` (ProtoMoveRequest) → ProtoGameContext
|
||||||
|
- `/RuleService/PostMoveStatus` (ProtoGameContext) → ProtoPostMoveStatus
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -25,6 +72,74 @@
|
|||||||
- function main: () -> None
|
- function main: () -> None
|
||||||
- class TestCase
|
- class TestCase
|
||||||
- _...2 more_
|
- _...2 more_
|
||||||
|
- `modules/account/src/main/scala/de/nowchess/account/client/CoreGameClient.scala` — class CoreGameClient, function createGame
|
||||||
|
- `modules/account/src/main/scala/de/nowchess/account/config/JacksonConfig.scala` — class JacksonConfig, function customize
|
||||||
|
- `modules/account/src/main/scala/de/nowchess/account/config/NativeReflectionConfig.scala` — class NativeReflectionConfig
|
||||||
|
- `modules/account/src/main/scala/de/nowchess/account/domain/Challenge.scala`
|
||||||
|
- class Challenge
|
||||||
|
- function gameIdOpt
|
||||||
|
- function declineReasonOpt
|
||||||
|
- function timeControlLimitOpt
|
||||||
|
- function timeControlIncrementOpt
|
||||||
|
- `modules/account/src/main/scala/de/nowchess/account/domain/ChallengeColorConverter.scala` — class ChallengeColorConverter
|
||||||
|
- `modules/account/src/main/scala/de/nowchess/account/domain/ChallengeStatusConverter.scala` — class ChallengeStatusConverter
|
||||||
|
- `modules/account/src/main/scala/de/nowchess/account/domain/DeclineReasonConverter.scala` — class DeclineReasonConverter
|
||||||
|
- `modules/account/src/main/scala/de/nowchess/account/domain/TimeControl.scala` — class TimeControl
|
||||||
|
- `modules/account/src/main/scala/de/nowchess/account/domain/UserAccount.scala`
|
||||||
|
- class UserAccount
|
||||||
|
- function getBotAccounts
|
||||||
|
- class BotAccount
|
||||||
|
- class OfficialBotAccount
|
||||||
|
- `modules/account/src/main/scala/de/nowchess/account/error/AccountError.scala` — function message
|
||||||
|
- `modules/account/src/main/scala/de/nowchess/account/error/ChallengeError.scala` — function message
|
||||||
|
- `modules/account/src/main/scala/de/nowchess/account/filter/AlreadyLoggedInFilter.scala` — class AlreadyLoggedInFilter
|
||||||
|
- `modules/account/src/main/scala/de/nowchess/account/repository/AccountRepository.scala`
|
||||||
|
- class UserAccountRepository
|
||||||
|
- function findByUsername
|
||||||
|
- function findById
|
||||||
|
- function persist
|
||||||
|
- function findByEmail
|
||||||
|
- function findAll
|
||||||
|
- _...12 more_
|
||||||
|
- `modules/account/src/main/scala/de/nowchess/account/repository/ChallengeRepository.scala`
|
||||||
|
- class ChallengeRepository
|
||||||
|
- function findActiveByChallengerId
|
||||||
|
- function findActiveByDestUserId
|
||||||
|
- function findDuplicateChallenge
|
||||||
|
- function findById
|
||||||
|
- function persist
|
||||||
|
- _...1 more_
|
||||||
|
- `modules/account/src/main/scala/de/nowchess/account/resource/AccountResource.scala`
|
||||||
|
- class AccountResource
|
||||||
|
- function register
|
||||||
|
- function login
|
||||||
|
- function me
|
||||||
|
- function publicProfile
|
||||||
|
- function banUser
|
||||||
|
- _...10 more_
|
||||||
|
- `modules/account/src/main/scala/de/nowchess/account/resource/ChallengeResource.scala`
|
||||||
|
- class ChallengeResource
|
||||||
|
- function create
|
||||||
|
- function list
|
||||||
|
- function accept
|
||||||
|
- function decline
|
||||||
|
- function cancel
|
||||||
|
- `modules/account/src/main/scala/de/nowchess/account/service/AccountService.scala`
|
||||||
|
- class AccountService
|
||||||
|
- function register
|
||||||
|
- function login
|
||||||
|
- function findByUsername
|
||||||
|
- function findById
|
||||||
|
- function createBotAccount
|
||||||
|
- _...11 more_
|
||||||
|
- `modules/account/src/main/scala/de/nowchess/account/service/ChallengeService.scala`
|
||||||
|
- class ChallengeService
|
||||||
|
- function create
|
||||||
|
- function accept
|
||||||
|
- function decline
|
||||||
|
- function cancel
|
||||||
|
- function listForUser
|
||||||
|
- _...1 more_
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Board.scala`
|
- `modules/api/src/main/scala/de/nowchess/api/board/Board.scala`
|
||||||
- class Board
|
- class Board
|
||||||
- function apply
|
- function apply
|
||||||
@@ -47,42 +162,279 @@
|
|||||||
- class Square
|
- class Square
|
||||||
- function fromAlgebraic
|
- function fromAlgebraic
|
||||||
- function offset
|
- function offset
|
||||||
|
- `modules/api/src/main/scala/de/nowchess/api/bot/Bot.scala`
|
||||||
|
- class Bot
|
||||||
|
- function name
|
||||||
|
- function nextMove
|
||||||
|
- `modules/api/src/main/scala/de/nowchess/api/dto/ErrorEventDto.scala` — class ErrorEventDto, function apply
|
||||||
|
- `modules/api/src/main/scala/de/nowchess/api/dto/GameFullEventDto.scala` — class GameFullEventDto, function apply
|
||||||
|
- `modules/api/src/main/scala/de/nowchess/api/dto/GameStateEventDto.scala` — class GameStateEventDto, function apply
|
||||||
|
- `modules/api/src/main/scala/de/nowchess/api/error/GameError.scala` — function message
|
||||||
|
- `modules/api/src/main/scala/de/nowchess/api/game/ClockState.scala`
|
||||||
|
- function activeColor
|
||||||
|
- function afterMove
|
||||||
|
- function remainingMs
|
||||||
|
- function remainingMs
|
||||||
|
- function afterMove
|
||||||
|
- function remainingMs
|
||||||
|
- _...3 more_
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`
|
- `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`
|
||||||
|
- function kingSquare
|
||||||
- function withBoard
|
- function withBoard
|
||||||
- function withTurn
|
- function withTurn
|
||||||
- function withCastlingRights
|
- function withCastlingRights
|
||||||
- function withEnPassantSquare
|
- function withEnPassantSquare
|
||||||
- function withHalfMoveClock
|
- function withHalfMoveClock
|
||||||
- function withMove
|
- _...4 more_
|
||||||
- _...2 more_
|
- `modules/api/src/main/scala/de/nowchess/api/io/GameContextExport.scala` — class GameContextExport, function exportGameContext
|
||||||
|
- `modules/api/src/main/scala/de/nowchess/api/io/GameContextImport.scala` — class GameContextImport, function importGameContext
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/player/PlayerInfo.scala` — class PlayerId, function apply
|
- `modules/api/src/main/scala/de/nowchess/api/player/PlayerInfo.scala` — class PlayerId, function apply
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/response/ApiResponse.scala`
|
- `modules/api/src/main/scala/de/nowchess/api/response/ApiResponse.scala`
|
||||||
- class ApiResponse
|
- class ApiResponse
|
||||||
- function error
|
- function error
|
||||||
- function totalPages
|
- function totalPages
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/command/Command.scala`
|
- `modules/api/src/main/scala/de/nowchess/api/rules/RuleSet.scala`
|
||||||
- class Command
|
- class RuleSet
|
||||||
- function execute
|
- function candidateMoves
|
||||||
- function undo
|
- function legalMoves
|
||||||
- function description
|
- function allLegalMoves
|
||||||
- class MoveResult
|
- function isCheck
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/command/CommandInvoker.scala`
|
- function isCheckmate
|
||||||
- class CommandInvoker
|
- _...6 more_
|
||||||
- function execute
|
- `modules/bot/python/nnue.py`
|
||||||
- function undo
|
- function get_weights_dir: ()
|
||||||
- function redo
|
- function get_data_dir: ()
|
||||||
- function history
|
- function list_checkpoints: ()
|
||||||
- function getCurrentIndex
|
- function migrate_legacy_data: ()
|
||||||
|
- function show_header: ()
|
||||||
|
- function show_checkpoints_table: ()
|
||||||
|
- _...10 more_
|
||||||
|
- `modules/bot/python/src/dataset.py`
|
||||||
|
- function get_datasets_dir: () -> Path
|
||||||
|
- function next_dataset_version: () -> int
|
||||||
|
- function list_datasets: () -> List[Tuple[int, Dict]]
|
||||||
|
- function load_dataset_metadata: (version) -> Optional[Dict]
|
||||||
|
- function save_dataset_metadata: (version, metadata) -> None
|
||||||
|
- function create_dataset: (version, labeled_jsonl_path, sources, stockfish_depth) -> Path
|
||||||
|
- _...4 more_
|
||||||
|
- `modules/bot/python/src/export.py` — function export_to_nbai: (weights_file, output_file, trained_by, train_loss)
|
||||||
|
- `modules/bot/python/src/generate.py` — function play_random_game_and_collect_positions: (output_file, total_positions, samples_per_game, min_move, max_move, num_workers)
|
||||||
|
- `modules/bot/python/src/label.py` — function normalize_evaluation: (cp_value, method, scale), function label_positions_with_stockfish: (positions_file, output_file, stockfish_path, batch_size, depth, verbose, normalize, num_workers)
|
||||||
|
- `modules/bot/python/src/lichess_importer.py` — function import_lichess_evals: (input_path, output_file, max_positions, min_depth, verbose) -> int
|
||||||
|
- `modules/bot/python/src/tactical_positions_extractor.py`
|
||||||
|
- function download_and_extract_puzzle_db: (url, output_dir)
|
||||||
|
- function extract_puzzle_positions: (puzzle_csv, max_puzzles) -> Set[str]
|
||||||
|
- function load_positions_from_file: (file_path) -> Set[str]
|
||||||
|
- function merge_positions: (tactical, other, output_file)
|
||||||
|
- function extract_tactical_only: (puzzle_csv, output_file, max_puzzles) -> int
|
||||||
|
- function interactive_merge_positions: (puzzle_csv, output_file, max_puzzles)
|
||||||
|
- `modules/bot/python/src/train.py`
|
||||||
|
- function fen_to_features: (fen)
|
||||||
|
- function find_next_version: (base_name)
|
||||||
|
- function save_metadata: (weights_file, metadata)
|
||||||
|
- function train_nnue: (data_file, output_file, epochs, batch_size, lr, checkpoint, stockfish_depth, use_versioning, early_stopping_patience, weight_decay, subsample_ratio, hidden_sizes)
|
||||||
|
- function burst_train: (data_file, output_file, duration_minutes, epochs_per_season, early_stopping_patience, batch_size, lr, initial_checkpoint, stockfish_depth, use_versioning, weight_decay, subsample_ratio, hidden_sizes)
|
||||||
|
- class NNUEDataset
|
||||||
|
- _...1 more_
|
||||||
|
- `modules/bot/src/main/scala/de/nowchess/bot/BotController.scala`
|
||||||
|
- class BotController
|
||||||
|
- function getBot
|
||||||
|
- function listBots
|
||||||
|
- `modules/bot/src/main/scala/de/nowchess/bot/BotMoveRepetition.scala`
|
||||||
|
- class BotMoveRepetition
|
||||||
|
- function blockedMoves
|
||||||
|
- function repeatedMove
|
||||||
|
- function filterAllowed
|
||||||
|
- `modules/bot/src/main/scala/de/nowchess/bot/Config.scala` — class Config
|
||||||
|
- `modules/bot/src/main/scala/de/nowchess/bot/ai/Evaluation.scala`
|
||||||
|
- class Evaluation
|
||||||
|
- class CHECKMATE_SCORE
|
||||||
|
- class DRAW_SCORE
|
||||||
|
- function evaluate
|
||||||
|
- function initAccumulator
|
||||||
|
- function copyAccumulator
|
||||||
|
- _...2 more_
|
||||||
|
- `modules/bot/src/main/scala/de/nowchess/bot/bots/classic/EvaluationClassic.scala`
|
||||||
|
- class EvaluationClassic
|
||||||
|
- function evaluate
|
||||||
|
- function countRay
|
||||||
|
- `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/EvaluationNNUE.scala` — class EvaluationNNUE, function evaluate
|
||||||
|
- `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/NNUE.scala`
|
||||||
|
- class NNUE
|
||||||
|
- function initAccumulator
|
||||||
|
- function pushAccumulator
|
||||||
|
- function copyAccumulator
|
||||||
|
- function recomputeAccumulator
|
||||||
|
- function validateAccumulator
|
||||||
|
- _...4 more_
|
||||||
|
- `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/NbaiLoader.scala`
|
||||||
|
- class NbaiLoader
|
||||||
|
- function load
|
||||||
|
- function loadDefault
|
||||||
|
- `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/NbaiMigrator.scala` — class NbaiMigrator, function migrateFromBin
|
||||||
|
- `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/NbaiModel.scala`
|
||||||
|
- function toJson
|
||||||
|
- class NbaiMetadata
|
||||||
|
- function fromJson
|
||||||
|
- function str
|
||||||
|
- function num
|
||||||
|
- `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/NbaiWriter.scala` — class NbaiWriter, function write
|
||||||
|
- `modules/bot/src/main/scala/de/nowchess/bot/logic/AlphaBetaSearch.scala`
|
||||||
|
- function bestMove
|
||||||
|
- function bestMove
|
||||||
|
- function bestMoveWithTime
|
||||||
|
- function bestMoveWithTime
|
||||||
|
- function loop
|
||||||
|
- function loop
|
||||||
|
- `modules/bot/src/main/scala/de/nowchess/bot/logic/MoveOrdering.scala`
|
||||||
|
- class MoveOrdering
|
||||||
|
- class OrderingContext
|
||||||
|
- function addKillerMove
|
||||||
|
- function getKillerMoves
|
||||||
|
- function addHistory
|
||||||
|
- function getHistory
|
||||||
- _...3 more_
|
- _...3 more_
|
||||||
|
- `modules/bot/src/main/scala/de/nowchess/bot/logic/TranspositionTable.scala`
|
||||||
|
- function advance
|
||||||
|
- function probe
|
||||||
|
- function store
|
||||||
|
- function clear
|
||||||
|
- `modules/bot/src/main/scala/de/nowchess/bot/util/PolyglotBook.scala` — function probe, function select
|
||||||
|
- `modules/bot/src/main/scala/de/nowchess/bot/util/PolyglotHash.scala` — class PolyglotHash, function hash
|
||||||
|
- `modules/bot/src/main/scala/de/nowchess/bot/util/ZobristHash.scala`
|
||||||
|
- class ZobristHash
|
||||||
|
- function hash
|
||||||
|
- function nextHash
|
||||||
|
- `modules/coordinator/src/main/scala/de/nowchess/coordinator/CoordinatorApp.scala` — class CoordinatorApp
|
||||||
|
- `modules/coordinator/src/main/scala/de/nowchess/coordinator/config/BeansProducer.scala`
|
||||||
|
- class BeansProducer
|
||||||
|
- function redissonClient
|
||||||
|
- function kubernetesClient
|
||||||
|
- `modules/coordinator/src/main/scala/de/nowchess/coordinator/config/CoordinatorConfig.scala`
|
||||||
|
- class CoordinatorConfig
|
||||||
|
- function maxGamesPerCore
|
||||||
|
- function maxDeviationPercent
|
||||||
|
- function rebalanceInterval
|
||||||
|
- function rebalanceMinInterval
|
||||||
|
- function heartbeatTtl
|
||||||
|
- _...11 more_
|
||||||
|
- `modules/coordinator/src/main/scala/de/nowchess/coordinator/config/JacksonConfig.scala` — class JacksonConfig, function customize
|
||||||
|
- `modules/coordinator/src/main/scala/de/nowchess/coordinator/config/NativeReflectionConfig.scala` — class NativeReflectionConfig
|
||||||
|
- `modules/coordinator/src/main/scala/de/nowchess/coordinator/grpc/CoordinatorGrpcServer.scala` — class CoordinatorGrpcServer
|
||||||
|
- `modules/coordinator/src/main/scala/de/nowchess/coordinator/grpc/CoreGrpcClient.scala`
|
||||||
|
- class CoreGrpcClient
|
||||||
|
- function shutdown
|
||||||
|
- function batchResubscribeGames
|
||||||
|
- function unsubscribeGames
|
||||||
|
- function evictGames
|
||||||
|
- `modules/coordinator/src/main/scala/de/nowchess/coordinator/resource/CoordinatorResource.scala`
|
||||||
|
- class CoordinatorResource
|
||||||
|
- function listInstances
|
||||||
|
- function getMetrics
|
||||||
|
- function triggerRebalance
|
||||||
|
- function triggerFailover
|
||||||
|
- function triggerScaleUp
|
||||||
|
- _...1 more_
|
||||||
|
- `modules/coordinator/src/main/scala/de/nowchess/coordinator/service/AutoScaler.scala`
|
||||||
|
- class AutoScaler
|
||||||
|
- function checkAndScale
|
||||||
|
- function scaleUp
|
||||||
|
- function scaleDown
|
||||||
|
- `modules/coordinator/src/main/scala/de/nowchess/coordinator/service/CacheEvictionManager.scala`
|
||||||
|
- class CacheEvictionManager
|
||||||
|
- function setRedisPrefix
|
||||||
|
- function evictStaleGames
|
||||||
|
- `modules/coordinator/src/main/scala/de/nowchess/coordinator/service/FailoverService.scala`
|
||||||
|
- class FailoverService
|
||||||
|
- function setRedisPrefix
|
||||||
|
- function onInstanceStreamDropped
|
||||||
|
- `modules/coordinator/src/main/scala/de/nowchess/coordinator/service/HealthMonitor.scala`
|
||||||
|
- class HealthMonitor
|
||||||
|
- function setRedisPrefix
|
||||||
|
- function checkInstanceHealth
|
||||||
|
- function watchK8sPods
|
||||||
|
- `modules/coordinator/src/main/scala/de/nowchess/coordinator/service/InstanceRegistry.scala`
|
||||||
|
- class InstanceRegistry
|
||||||
|
- function setRedisPrefix
|
||||||
|
- function getInstance
|
||||||
|
- function getAllInstances
|
||||||
|
- function updateInstanceFromRedis
|
||||||
|
- function markInstanceDead
|
||||||
|
- _...1 more_
|
||||||
|
- `modules/coordinator/src/main/scala/de/nowchess/coordinator/service/LoadBalancer.scala`
|
||||||
|
- class LoadBalancer
|
||||||
|
- function setRedisPrefix
|
||||||
|
- function shouldRebalance
|
||||||
|
- function rebalance
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/adapter/RuleSetRestAdapter.scala`
|
||||||
|
- class RuleSetRestAdapter
|
||||||
|
- function candidateMoves
|
||||||
|
- function legalMoves
|
||||||
|
- function allLegalMoves
|
||||||
|
- function isCheck
|
||||||
|
- function isCheckmate
|
||||||
|
- _...5 more_
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/client/IoServiceClient.scala`
|
||||||
|
- class IoServiceClient
|
||||||
|
- function importFen
|
||||||
|
- function importPgn
|
||||||
|
- function exportFen
|
||||||
|
- function exportPgn
|
||||||
|
- function exportCombined
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/client/RuleServiceClient.scala`
|
||||||
|
- class RuleServiceClient
|
||||||
|
- function candidateMoves
|
||||||
|
- function legalMoves
|
||||||
|
- function allLegalMoves
|
||||||
|
- function isCheck
|
||||||
|
- function isCheckmate
|
||||||
|
- _...6 more_
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/client/StoreServiceClient.scala` — class StoreServiceClient, function getGame
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/config/JacksonConfig.scala` — class JacksonConfig, function customize
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/config/NativeReflectionConfig.scala` — class NativeReflectionConfig
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/config/RedisConfig.scala` — class RedisConfig
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/config/RedissonProducer.scala`
|
||||||
|
- class RedissonProducer
|
||||||
|
- function produceRedissonClient
|
||||||
|
- function shutdown
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/controller/Parser.scala` — class Parser, function parseMove
|
- `modules/core/src/main/scala/de/nowchess/chess/controller/Parser.scala` — class Parser, function parseMove
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`
|
- `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`
|
||||||
- class GameEngine
|
- class GameEngine
|
||||||
- function isPendingPromotion
|
|
||||||
- function board
|
- function board
|
||||||
- function turn
|
- function turn
|
||||||
- function context
|
- function context
|
||||||
- function canUndo
|
- function pendingDrawOfferBy
|
||||||
- _...10 more_
|
- function currentClockState
|
||||||
|
- _...22 more_
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/exception/ApiException.scala`
|
||||||
|
- class ApiException
|
||||||
|
- class GameNotFoundException
|
||||||
|
- class BadRequestException
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/exception/ApiExceptionMapper.scala` — class ApiExceptionMapper, function toResponse
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/grpc/CoordinatorServiceHandler.scala` — class CoordinatorServiceHandler
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/grpc/CoreProtoMapper.scala`
|
||||||
|
- class CoreProtoMapper
|
||||||
|
- function toProtoColor
|
||||||
|
- function fromProtoColor
|
||||||
|
- function toProtoPieceType
|
||||||
|
- function fromProtoPieceType
|
||||||
|
- function toProtoMoveKind
|
||||||
|
- _...9 more_
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/grpc/IoGrpcClientWrapper.scala`
|
||||||
|
- class IoGrpcClientWrapper
|
||||||
|
- function exportCombined
|
||||||
|
- function importFen
|
||||||
|
- function importPgn
|
||||||
|
- function exportFen
|
||||||
|
- function exportPgn
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/grpc/RuleSetGrpcAdapter.scala`
|
||||||
|
- class RuleSetGrpcAdapter
|
||||||
|
- function candidateMoves
|
||||||
|
- function legalMoves
|
||||||
|
- function allLegalMoves
|
||||||
|
- function isCheck
|
||||||
|
- function isCheckmate
|
||||||
|
- _...5 more_
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala`
|
- `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala`
|
||||||
- function context
|
- function context
|
||||||
- class Observer
|
- class Observer
|
||||||
@@ -91,8 +443,59 @@
|
|||||||
- function subscribe
|
- function subscribe
|
||||||
- function unsubscribe
|
- function unsubscribe
|
||||||
- _...1 more_
|
- _...1 more_
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/GameContextExport.scala` — class GameContextExport, function exportGameContext
|
- `modules/core/src/main/scala/de/nowchess/chess/redis/C2sMessage.scala` — class C2sMessage
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/GameContextImport.scala` — class GameContextImport, function importGameContext
|
- `modules/core/src/main/scala/de/nowchess/chess/redis/GameRedisPublisher.scala` — class GameRedisPublisher, function onGameEvent
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/redis/GameRedisSubscriberManager.scala`
|
||||||
|
- class GameRedisSubscriberManager
|
||||||
|
- function subscribeGame
|
||||||
|
- function onMessage
|
||||||
|
- function unsubscribeGame
|
||||||
|
- function batchResubscribeGames
|
||||||
|
- function unsubscribeGames
|
||||||
|
- _...3 more_
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/registry/GameRegistry.scala`
|
||||||
|
- class GameRegistry
|
||||||
|
- function store
|
||||||
|
- function get
|
||||||
|
- function update
|
||||||
|
- function generateId
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/registry/RedisGameRegistry.scala`
|
||||||
|
- class RedisGameRegistry
|
||||||
|
- function generateId
|
||||||
|
- function store
|
||||||
|
- function get
|
||||||
|
- function update
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/resource/GameDtoMapper.scala`
|
||||||
|
- class GameDtoMapper
|
||||||
|
- function statusOf
|
||||||
|
- function moveToUci
|
||||||
|
- function toPlayerDto
|
||||||
|
- function toClockDto
|
||||||
|
- function toGameStateDto
|
||||||
|
- _...1 more_
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/resource/GameResource.scala`
|
||||||
|
- class GameResource
|
||||||
|
- function onGameEvent
|
||||||
|
- function createGame
|
||||||
|
- function getGame
|
||||||
|
- function resignGame
|
||||||
|
- function makeMove
|
||||||
|
- _...9 more_
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/service/InstanceHeartbeatService.scala`
|
||||||
|
- class InstanceHeartbeatService
|
||||||
|
- function onStart
|
||||||
|
- function onShutdown
|
||||||
|
- function setRedisPrefix
|
||||||
|
- function setSubscriptionCount
|
||||||
|
- function setLocalCacheSize
|
||||||
|
- _...2 more_
|
||||||
|
- `modules/io/src/main/scala/de/nowchess/io/GameFileService.scala`
|
||||||
|
- class GameFileService
|
||||||
|
- function saveGameToFile
|
||||||
|
- function loadGameFromFile
|
||||||
|
- class FileSystemGameService
|
||||||
|
- function saveGameToFile
|
||||||
|
- function loadGameFromFile
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenExporter.scala`
|
- `modules/io/src/main/scala/de/nowchess/io/fen/FenExporter.scala`
|
||||||
- class FenExporter
|
- class FenExporter
|
||||||
- function boardToFen
|
- function boardToFen
|
||||||
@@ -114,6 +517,17 @@
|
|||||||
- function parseBoard
|
- function parseBoard
|
||||||
- function importGameContext
|
- function importGameContext
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParserSupport.scala` — function buildSquares
|
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParserSupport.scala` — function buildSquares
|
||||||
|
- `modules/io/src/main/scala/de/nowchess/io/grpc/IoGrpcService.scala` — class IoGrpcService
|
||||||
|
- `modules/io/src/main/scala/de/nowchess/io/grpc/IoProtoMapper.scala`
|
||||||
|
- class IoProtoMapper
|
||||||
|
- function toProtoColor
|
||||||
|
- function fromProtoColor
|
||||||
|
- function toProtoPieceType
|
||||||
|
- function fromProtoPieceType
|
||||||
|
- function toProtoMoveKind
|
||||||
|
- _...9 more_
|
||||||
|
- `modules/io/src/main/scala/de/nowchess/io/json/JsonExporter.scala` — class JsonExporter, function exportGameContext
|
||||||
|
- `modules/io/src/main/scala/de/nowchess/io/json/JsonParser.scala` — class JsonParser, function importGameContext
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/pgn/PgnExporter.scala`
|
- `modules/io/src/main/scala/de/nowchess/io/pgn/PgnExporter.scala`
|
||||||
- class PgnExporter
|
- class PgnExporter
|
||||||
- function exportGameContext
|
- function exportGameContext
|
||||||
@@ -124,39 +538,91 @@
|
|||||||
- function importGameContext
|
- function importGameContext
|
||||||
- function parsePgn
|
- function parsePgn
|
||||||
- function parseAlgebraicMove
|
- function parseAlgebraicMove
|
||||||
- `modules/rule/src/main/scala/de/nowchess/rules/RuleSet.scala`
|
- `modules/io/src/main/scala/de/nowchess/io/service/config/JacksonConfig.scala` — class JacksonConfig, function customize
|
||||||
- class RuleSet
|
- `modules/io/src/main/scala/de/nowchess/io/service/config/NativeReflectionConfig.scala` — class NativeReflectionConfig
|
||||||
|
- `modules/io/src/main/scala/de/nowchess/io/service/resource/IoResource.scala`
|
||||||
|
- class IoResource
|
||||||
|
- function importFen
|
||||||
|
- function importPgn
|
||||||
|
- function exportFen
|
||||||
|
- function exportPgn
|
||||||
|
- function exportCombined
|
||||||
|
- `modules/json/src/main/scala/de/nowchess/json/ChessJacksonModule.scala` — class ChessJacksonModule
|
||||||
|
- `modules/json/src/main/scala/de/nowchess/json/GameResultDeserializer.scala` — class GameResultDeserializer
|
||||||
|
- `modules/json/src/main/scala/de/nowchess/json/GameResultSerializer.scala` — class GameResultSerializer
|
||||||
|
- `modules/json/src/main/scala/de/nowchess/json/MoveTypeDeserializer.scala` — class MoveTypeDeserializer
|
||||||
|
- `modules/json/src/main/scala/de/nowchess/json/MoveTypeSerializer.scala` — class MoveTypeSerializer
|
||||||
|
- `modules/json/src/main/scala/de/nowchess/json/SquareDeserializer.scala` — class SquareDeserializer
|
||||||
|
- `modules/json/src/main/scala/de/nowchess/json/SquareKeyDeserializer.scala` — class SquareKeyDeserializer
|
||||||
|
- `modules/json/src/main/scala/de/nowchess/json/SquareKeySerializer.scala` — class SquareKeySerializer
|
||||||
|
- `modules/json/src/main/scala/de/nowchess/json/SquareSerializer.scala` — class SquareSerializer
|
||||||
|
- `modules/rule/src/main/scala/de/nowchess/rules/config/JacksonConfig.scala` — class JacksonConfig, function customize
|
||||||
|
- `modules/rule/src/main/scala/de/nowchess/rules/config/NativeReflectionConfig.scala` — class NativeReflectionConfig
|
||||||
|
- `modules/rule/src/main/scala/de/nowchess/rules/grpc/ProtoMapper.scala`
|
||||||
|
- class ProtoMapper
|
||||||
|
- function toProtoColor
|
||||||
|
- function fromProtoColor
|
||||||
|
- function toProtoPieceType
|
||||||
|
- function fromProtoPieceType
|
||||||
|
- function toProtoMoveKind
|
||||||
|
- _...9 more_
|
||||||
|
- `modules/rule/src/main/scala/de/nowchess/rules/grpc/RuleGrpcService.scala` — class RuleGrpcService
|
||||||
|
- `modules/rule/src/main/scala/de/nowchess/rules/resource/RuleSetResource.scala`
|
||||||
|
- class RuleSetResource
|
||||||
- function candidateMoves
|
- function candidateMoves
|
||||||
- function legalMoves
|
- function legalMoves
|
||||||
- function allLegalMoves
|
- function allLegalMoves
|
||||||
- function isCheck
|
- function isCheck
|
||||||
- function isCheckmate
|
- function isCheckmate
|
||||||
- _...4 more_
|
- _...6 more_
|
||||||
- `modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala`
|
- `modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala` — class DefaultRules, function positionOf
|
||||||
- class DefaultRules
|
- `modules/store/src/main/scala/de/nowchess/store/config/JacksonConfig.scala` — class JacksonConfig, function customize
|
||||||
- function loop
|
- `modules/store/src/main/scala/de/nowchess/store/config/NativeReflectionConfig.scala` — class NativeReflectionConfig
|
||||||
- function toMoves
|
- `modules/store/src/main/scala/de/nowchess/store/config/RedisConfig.scala` — class RedisConfig
|
||||||
- function loop
|
- `modules/store/src/main/scala/de/nowchess/store/config/RedissonProducer.scala`
|
||||||
- `modules/ui/src/main/scala/de/nowchess/ui/Main.scala` — class Main, function main
|
- class RedissonProducer
|
||||||
- `modules/ui/src/main/scala/de/nowchess/ui/gui/ChessBoardView.scala`
|
- function redissonClient
|
||||||
- class ChessBoardView
|
- function close
|
||||||
- function updateBoard
|
- `modules/store/src/main/scala/de/nowchess/store/domain/GameRecord.scala` — class GameRecord
|
||||||
- function updateUndoRedoButtons
|
- `modules/store/src/main/scala/de/nowchess/store/redis/GameWritebackStreamListener.scala`
|
||||||
- function showMessage
|
- class GameWritebackStreamListener
|
||||||
- function showPromotionDialog
|
- function startListening
|
||||||
- `modules/ui/src/main/scala/de/nowchess/ui/gui/ChessGUI.scala`
|
- function onMessage
|
||||||
- class ChessGUIApp
|
- `modules/store/src/main/scala/de/nowchess/store/repository/GameRecordRepository.scala`
|
||||||
- class ChessGUILauncher
|
- class GameRecordRepository
|
||||||
- function getEngine
|
- function findByGameId
|
||||||
- function launch
|
- function persist
|
||||||
- `modules/ui/src/main/scala/de/nowchess/ui/gui/GUIObserver.scala` — class GUIObserver
|
- function merge
|
||||||
- `modules/ui/src/main/scala/de/nowchess/ui/gui/PieceSprites.scala`
|
- `modules/store/src/main/scala/de/nowchess/store/resource/StoreGameResource.scala` — class StoreGameResource, function getGame
|
||||||
- class PieceSprites
|
- `modules/store/src/main/scala/de/nowchess/store/service/GameWritebackService.scala` — class GameWritebackService, function writeBack
|
||||||
- function loadPieceImage
|
- `modules/ws/src/main/scala/de/nowchess/ws/config/JacksonConfig.scala` — class JacksonConfig, function customize
|
||||||
- class SquareColors
|
- `modules/ws/src/main/scala/de/nowchess/ws/config/NativeReflectionConfig.scala` — class NativeReflectionConfig
|
||||||
- `modules/ui/src/main/scala/de/nowchess/ui/terminal/TerminalUI.scala` — class TerminalUI, function start
|
- `modules/ws/src/main/scala/de/nowchess/ws/config/RedisConfig.scala` — class RedisConfig
|
||||||
- `modules/ui/src/main/scala/de/nowchess/ui/utils/PieceUnicode.scala` — function unicode
|
- `modules/ws/src/main/scala/de/nowchess/ws/config/RedissonProducer.scala`
|
||||||
- `modules/ui/src/main/scala/de/nowchess/ui/utils/Renderer.scala` — class Renderer, function render
|
- class RedissonProducer
|
||||||
|
- function produceRedissonClient
|
||||||
|
- function shutdown
|
||||||
|
- `modules/ws/src/main/scala/de/nowchess/ws/resource/GameWebSocketResource.scala`
|
||||||
|
- class GameWebSocketResource
|
||||||
|
- function onOpen
|
||||||
|
- function onMessage
|
||||||
|
- function onTextMessage
|
||||||
|
- function onClose
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Config
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
- `STOCKFISH_PATH` **required** — modules/bot/python/nnue.py
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Middleware
|
||||||
|
|
||||||
|
## custom
|
||||||
|
- generate — `modules/bot/python/src/generate.py`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -164,39 +630,39 @@
|
|||||||
|
|
||||||
## Most Imported Files (change these carefully)
|
## Most Imported Files (change these carefully)
|
||||||
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala` — imported by **28** files
|
- `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala` — imported by **76** files
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Square.scala` — imported by **21** files
|
- `modules/api/src/main/scala/de/nowchess/api/board/Square.scala` — imported by **57** files
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Color.scala` — imported by **19** files
|
- `modules/api/src/main/scala/de/nowchess/api/move/Move.scala` — imported by **55** files
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/move/Move.scala` — imported by **14** files
|
- `modules/api/src/main/scala/de/nowchess/api/board/Color.scala` — imported by **47** files
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Board.scala` — imported by **13** files
|
- `modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala` — imported by **28** files
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Piece.scala` — imported by **10** files
|
- `modules/api/src/main/scala/de/nowchess/api/board/Board.scala` — imported by **20** files
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/PieceType.scala` — imported by **9** files
|
- `modules/api/src/main/scala/de/nowchess/api/board/PieceType.scala` — imported by **20** files
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParser.scala` — imported by **9** files
|
- `modules/api/src/main/scala/de/nowchess/api/board/Piece.scala` — imported by **20** files
|
||||||
- `modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala` — imported by **8** files
|
- `modules/api/src/main/scala/de/nowchess/api/game/DrawReason.scala` — imported by **18** files
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/GameContextImport.scala` — imported by **7** files
|
- `modules/api/src/main/scala/de/nowchess/api/game/GameResult.scala` — imported by **18** files
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/CastlingRights.scala` — imported by **4** files
|
- `modules/api/src/main/scala/de/nowchess/api/rules/RuleSet.scala` — imported by **14** files
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/GameContextExport.scala` — imported by **4** files
|
- `modules/api/src/main/scala/de/nowchess/api/board/CastlingRights.scala` — imported by **13** files
|
||||||
- `modules/rule/src/main/scala/de/nowchess/rules/RuleSet.scala` — imported by **4** files
|
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParser.scala` — imported by **11** files
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala` — imported by **4** files
|
- `modules/api/src/main/scala/de/nowchess/api/player/PlayerInfo.scala` — imported by **9** files
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala` — imported by **4** files
|
- `modules/api/src/main/scala/de/nowchess/api/error/GameError.scala` — imported by **9** files
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/pgn/PgnParser.scala` — imported by **2** files
|
- `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala` — imported by **9** files
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/pgn/PgnExporter.scala` — imported by **2** files
|
- `modules/api/src/main/scala/de/nowchess/api/io/GameContextImport.scala` — imported by **8** files
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenExporter.scala` — imported by **2** files
|
- `modules/core/src/main/scala/de/nowchess/chess/grpc/IoGrpcClientWrapper.scala` — imported by **7** files
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParserSupport.scala` — imported by **2** files
|
- `modules/api/src/main/scala/de/nowchess/api/game/GameMode.scala` — imported by **6** files
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/controller/Parser.scala` — imported by **1** files
|
- `modules/api/src/main/scala/de/nowchess/api/bot/Bot.scala` — imported by **6** files
|
||||||
|
|
||||||
## Import Map (who imports what)
|
## Import Map (who imports what)
|
||||||
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala` ← `modules/core/src/main/scala/de/nowchess/chess/command/Command.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala`, `modules/core/src/test/scala/de/nowchess/chess/command/CommandInvokerBranchTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/command/CommandInvokerTest.scala` +23 more
|
- `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala` ← `modules/api/src/main/scala/de/nowchess/api/bot/Bot.scala`, `modules/api/src/main/scala/de/nowchess/api/io/GameContextExport.scala`, `modules/api/src/main/scala/de/nowchess/api/io/GameContextImport.scala`, `modules/api/src/main/scala/de/nowchess/api/rules/RuleSet.scala`, `modules/bot/src/main/scala/de/nowchess/bot/BotMoveRepetition.scala` +71 more
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Square.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/main/scala/de/nowchess/api/move/Move.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/api/src/test/scala/de/nowchess/api/move/MoveTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/command/Command.scala` +16 more
|
- `modules/api/src/main/scala/de/nowchess/api/board/Square.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/main/scala/de/nowchess/api/move/Move.scala`, `modules/api/src/main/scala/de/nowchess/api/rules/RuleSet.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/api/src/test/scala/de/nowchess/api/move/MoveTest.scala` +52 more
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Color.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/EngineTestHelpers.scala` +14 more
|
- `modules/api/src/main/scala/de/nowchess/api/move/Move.scala` ← `modules/api/src/main/scala/de/nowchess/api/bot/Bot.scala`, `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/main/scala/de/nowchess/api/rules/RuleSet.scala`, `modules/api/src/test/scala/de/nowchess/api/board/BoardTest.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala` +50 more
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/move/Move.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/test/scala/de/nowchess/api/board/BoardTest.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala` +9 more
|
- `modules/api/src/main/scala/de/nowchess/api/board/Color.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/ClockState.scala`, `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/main/scala/de/nowchess/api/game/GameResult.scala`, `modules/api/src/test/scala/de/nowchess/api/game/ClockStateTest.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala` +42 more
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Board.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/EngineTestHelpers.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineGameEndingTest.scala` +8 more
|
- `modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala` ← `modules/bot/src/main/scala/de/nowchess/bot/bots/ClassicalBot.scala`, `modules/bot/src/main/scala/de/nowchess/bot/bots/HybridBot.scala`, `modules/bot/src/main/scala/de/nowchess/bot/bots/NNUEBot.scala`, `modules/bot/src/main/scala/de/nowchess/bot/logic/AlphaBetaSearch.scala`, `modules/bot/src/test/scala/de/nowchess/bot/AlphaBetaSearchTest.scala` +23 more
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Piece.scala` ← `modules/core/src/main/scala/de/nowchess/chess/command/Command.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEnginePromotionTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineScenarioTest.scala`, `modules/rule/src/test/scala/de/nowchess/rule/DefaultRulesStateTransitionsTest.scala` +5 more
|
- `modules/api/src/main/scala/de/nowchess/api/board/Board.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/NNUE.scala`, `modules/bot/src/main/scala/de/nowchess/bot/logic/MoveOrdering.scala`, `modules/bot/src/test/scala/de/nowchess/bot/AlphaBetaSearchTest.scala` +15 more
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/PieceType.scala` ← `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEnginePromotionTest.scala`, `modules/rule/src/test/scala/de/nowchess/rule/DefaultRulesStateTransitionsTest.scala`, `modules/rule/src/test/scala/de/nowchess/rule/DefaultRulesTest.scala` +4 more
|
- `modules/api/src/main/scala/de/nowchess/api/board/PieceType.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/bot/src/main/scala/de/nowchess/bot/bots/classic/EvaluationClassic.scala`, `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/NNUE.scala`, `modules/bot/src/main/scala/de/nowchess/bot/logic/AlphaBetaSearch.scala`, `modules/bot/src/main/scala/de/nowchess/bot/logic/MoveOrdering.scala` +15 more
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParser.scala` ← `modules/core/src/test/scala/de/nowchess/chess/engine/EngineTestHelpers.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineLoadGameTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineNotationTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEnginePromotionTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineScenarioTest.scala` +4 more
|
- `modules/api/src/main/scala/de/nowchess/api/board/Piece.scala` ← `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/NNUE.scala`, `modules/bot/src/main/scala/de/nowchess/bot/logic/MoveOrdering.scala`, `modules/bot/src/main/scala/de/nowchess/bot/util/PolyglotHash.scala`, `modules/bot/src/main/scala/de/nowchess/bot/util/ZobristHash.scala`, `modules/bot/src/test/scala/de/nowchess/bot/AlphaBetaSearchTest.scala` +15 more
|
||||||
- `modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala` ← `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/EngineTestHelpers.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEnginePromotionTest.scala`, `modules/io/src/main/scala/de/nowchess/io/pgn/PgnExporter.scala` +3 more
|
- `modules/api/src/main/scala/de/nowchess/api/game/DrawReason.scala` ← `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/config/NativeReflectionConfig.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/main/scala/de/nowchess/chess/grpc/CoreProtoMapper.scala`, `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala` +13 more
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/GameContextImport.scala` ← `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala`, `modules/io/src/main/scala/de/nowchess/io/fen/FenParser.scala`, `modules/io/src/main/scala/de/nowchess/io/fen/FenParserCombinators.scala`, `modules/io/src/main/scala/de/nowchess/io/fen/FenParserFastParse.scala` +2 more
|
- `modules/api/src/main/scala/de/nowchess/api/game/GameResult.scala` ← `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/config/NativeReflectionConfig.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/main/scala/de/nowchess/chess/grpc/CoreProtoMapper.scala`, `modules/core/src/main/scala/de/nowchess/chess/resource/GameDtoMapper.scala` +13 more
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Config
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
- `STOCKFISH_PATH` **required** — modules/bot/python/nnue.py
|
||||||
+30
-30
@@ -2,36 +2,36 @@
|
|||||||
|
|
||||||
## Most Imported Files (change these carefully)
|
## Most Imported Files (change these carefully)
|
||||||
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala` — imported by **28** files
|
- `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala` — imported by **76** files
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Square.scala` — imported by **21** files
|
- `modules/api/src/main/scala/de/nowchess/api/board/Square.scala` — imported by **57** files
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Color.scala` — imported by **19** files
|
- `modules/api/src/main/scala/de/nowchess/api/move/Move.scala` — imported by **55** files
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/move/Move.scala` — imported by **14** files
|
- `modules/api/src/main/scala/de/nowchess/api/board/Color.scala` — imported by **47** files
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Board.scala` — imported by **13** files
|
- `modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala` — imported by **28** files
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Piece.scala` — imported by **10** files
|
- `modules/api/src/main/scala/de/nowchess/api/board/Board.scala` — imported by **20** files
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/PieceType.scala` — imported by **9** files
|
- `modules/api/src/main/scala/de/nowchess/api/board/PieceType.scala` — imported by **20** files
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParser.scala` — imported by **9** files
|
- `modules/api/src/main/scala/de/nowchess/api/board/Piece.scala` — imported by **20** files
|
||||||
- `modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala` — imported by **8** files
|
- `modules/api/src/main/scala/de/nowchess/api/game/DrawReason.scala` — imported by **18** files
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/GameContextImport.scala` — imported by **7** files
|
- `modules/api/src/main/scala/de/nowchess/api/game/GameResult.scala` — imported by **18** files
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/CastlingRights.scala` — imported by **4** files
|
- `modules/api/src/main/scala/de/nowchess/api/rules/RuleSet.scala` — imported by **14** files
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/GameContextExport.scala` — imported by **4** files
|
- `modules/api/src/main/scala/de/nowchess/api/board/CastlingRights.scala` — imported by **13** files
|
||||||
- `modules/rule/src/main/scala/de/nowchess/rules/RuleSet.scala` — imported by **4** files
|
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParser.scala` — imported by **11** files
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala` — imported by **4** files
|
- `modules/api/src/main/scala/de/nowchess/api/player/PlayerInfo.scala` — imported by **9** files
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala` — imported by **4** files
|
- `modules/api/src/main/scala/de/nowchess/api/error/GameError.scala` — imported by **9** files
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/pgn/PgnParser.scala` — imported by **2** files
|
- `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala` — imported by **9** files
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/pgn/PgnExporter.scala` — imported by **2** files
|
- `modules/api/src/main/scala/de/nowchess/api/io/GameContextImport.scala` — imported by **8** files
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenExporter.scala` — imported by **2** files
|
- `modules/core/src/main/scala/de/nowchess/chess/grpc/IoGrpcClientWrapper.scala` — imported by **7** files
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParserSupport.scala` — imported by **2** files
|
- `modules/api/src/main/scala/de/nowchess/api/game/GameMode.scala` — imported by **6** files
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/controller/Parser.scala` — imported by **1** files
|
- `modules/api/src/main/scala/de/nowchess/api/bot/Bot.scala` — imported by **6** files
|
||||||
|
|
||||||
## Import Map (who imports what)
|
## Import Map (who imports what)
|
||||||
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala` ← `modules/core/src/main/scala/de/nowchess/chess/command/Command.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala`, `modules/core/src/test/scala/de/nowchess/chess/command/CommandInvokerBranchTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/command/CommandInvokerTest.scala` +23 more
|
- `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala` ← `modules/api/src/main/scala/de/nowchess/api/bot/Bot.scala`, `modules/api/src/main/scala/de/nowchess/api/io/GameContextExport.scala`, `modules/api/src/main/scala/de/nowchess/api/io/GameContextImport.scala`, `modules/api/src/main/scala/de/nowchess/api/rules/RuleSet.scala`, `modules/bot/src/main/scala/de/nowchess/bot/BotMoveRepetition.scala` +71 more
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Square.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/main/scala/de/nowchess/api/move/Move.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/api/src/test/scala/de/nowchess/api/move/MoveTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/command/Command.scala` +16 more
|
- `modules/api/src/main/scala/de/nowchess/api/board/Square.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/main/scala/de/nowchess/api/move/Move.scala`, `modules/api/src/main/scala/de/nowchess/api/rules/RuleSet.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/api/src/test/scala/de/nowchess/api/move/MoveTest.scala` +52 more
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Color.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/EngineTestHelpers.scala` +14 more
|
- `modules/api/src/main/scala/de/nowchess/api/move/Move.scala` ← `modules/api/src/main/scala/de/nowchess/api/bot/Bot.scala`, `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/main/scala/de/nowchess/api/rules/RuleSet.scala`, `modules/api/src/test/scala/de/nowchess/api/board/BoardTest.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala` +50 more
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/move/Move.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/test/scala/de/nowchess/api/board/BoardTest.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala` +9 more
|
- `modules/api/src/main/scala/de/nowchess/api/board/Color.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/ClockState.scala`, `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/main/scala/de/nowchess/api/game/GameResult.scala`, `modules/api/src/test/scala/de/nowchess/api/game/ClockStateTest.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala` +42 more
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Board.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/EngineTestHelpers.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineGameEndingTest.scala` +8 more
|
- `modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala` ← `modules/bot/src/main/scala/de/nowchess/bot/bots/ClassicalBot.scala`, `modules/bot/src/main/scala/de/nowchess/bot/bots/HybridBot.scala`, `modules/bot/src/main/scala/de/nowchess/bot/bots/NNUEBot.scala`, `modules/bot/src/main/scala/de/nowchess/bot/logic/AlphaBetaSearch.scala`, `modules/bot/src/test/scala/de/nowchess/bot/AlphaBetaSearchTest.scala` +23 more
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Piece.scala` ← `modules/core/src/main/scala/de/nowchess/chess/command/Command.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEnginePromotionTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineScenarioTest.scala`, `modules/rule/src/test/scala/de/nowchess/rule/DefaultRulesStateTransitionsTest.scala` +5 more
|
- `modules/api/src/main/scala/de/nowchess/api/board/Board.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/NNUE.scala`, `modules/bot/src/main/scala/de/nowchess/bot/logic/MoveOrdering.scala`, `modules/bot/src/test/scala/de/nowchess/bot/AlphaBetaSearchTest.scala` +15 more
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/PieceType.scala` ← `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEnginePromotionTest.scala`, `modules/rule/src/test/scala/de/nowchess/rule/DefaultRulesStateTransitionsTest.scala`, `modules/rule/src/test/scala/de/nowchess/rule/DefaultRulesTest.scala` +4 more
|
- `modules/api/src/main/scala/de/nowchess/api/board/PieceType.scala` ← `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`, `modules/bot/src/main/scala/de/nowchess/bot/bots/classic/EvaluationClassic.scala`, `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/NNUE.scala`, `modules/bot/src/main/scala/de/nowchess/bot/logic/AlphaBetaSearch.scala`, `modules/bot/src/main/scala/de/nowchess/bot/logic/MoveOrdering.scala` +15 more
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParser.scala` ← `modules/core/src/test/scala/de/nowchess/chess/engine/EngineTestHelpers.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineLoadGameTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineNotationTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEnginePromotionTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineScenarioTest.scala` +4 more
|
- `modules/api/src/main/scala/de/nowchess/api/board/Piece.scala` ← `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/NNUE.scala`, `modules/bot/src/main/scala/de/nowchess/bot/logic/MoveOrdering.scala`, `modules/bot/src/main/scala/de/nowchess/bot/util/PolyglotHash.scala`, `modules/bot/src/main/scala/de/nowchess/bot/util/ZobristHash.scala`, `modules/bot/src/test/scala/de/nowchess/bot/AlphaBetaSearchTest.scala` +15 more
|
||||||
- `modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala` ← `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/EngineTestHelpers.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEnginePromotionTest.scala`, `modules/io/src/main/scala/de/nowchess/io/pgn/PgnExporter.scala` +3 more
|
- `modules/api/src/main/scala/de/nowchess/api/game/DrawReason.scala` ← `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/config/NativeReflectionConfig.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/main/scala/de/nowchess/chess/grpc/CoreProtoMapper.scala`, `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala` +13 more
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/GameContextImport.scala` ← `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala`, `modules/io/src/main/scala/de/nowchess/io/fen/FenParser.scala`, `modules/io/src/main/scala/de/nowchess/io/fen/FenParserCombinators.scala`, `modules/io/src/main/scala/de/nowchess/io/fen/FenParserFastParse.scala` +2 more
|
- `modules/api/src/main/scala/de/nowchess/api/game/GameResult.scala` ← `modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala`, `modules/core/src/main/scala/de/nowchess/chess/config/NativeReflectionConfig.scala`, `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`, `modules/core/src/main/scala/de/nowchess/chess/grpc/CoreProtoMapper.scala`, `modules/core/src/main/scala/de/nowchess/chess/resource/GameDtoMapper.scala` +13 more
|
||||||
|
|||||||
+452
-48
@@ -16,6 +16,74 @@
|
|||||||
- function main: () -> None
|
- function main: () -> None
|
||||||
- class TestCase
|
- class TestCase
|
||||||
- _...2 more_
|
- _...2 more_
|
||||||
|
- `modules/account/src/main/scala/de/nowchess/account/client/CoreGameClient.scala` — class CoreGameClient, function createGame
|
||||||
|
- `modules/account/src/main/scala/de/nowchess/account/config/JacksonConfig.scala` — class JacksonConfig, function customize
|
||||||
|
- `modules/account/src/main/scala/de/nowchess/account/config/NativeReflectionConfig.scala` — class NativeReflectionConfig
|
||||||
|
- `modules/account/src/main/scala/de/nowchess/account/domain/Challenge.scala`
|
||||||
|
- class Challenge
|
||||||
|
- function gameIdOpt
|
||||||
|
- function declineReasonOpt
|
||||||
|
- function timeControlLimitOpt
|
||||||
|
- function timeControlIncrementOpt
|
||||||
|
- `modules/account/src/main/scala/de/nowchess/account/domain/ChallengeColorConverter.scala` — class ChallengeColorConverter
|
||||||
|
- `modules/account/src/main/scala/de/nowchess/account/domain/ChallengeStatusConverter.scala` — class ChallengeStatusConverter
|
||||||
|
- `modules/account/src/main/scala/de/nowchess/account/domain/DeclineReasonConverter.scala` — class DeclineReasonConverter
|
||||||
|
- `modules/account/src/main/scala/de/nowchess/account/domain/TimeControl.scala` — class TimeControl
|
||||||
|
- `modules/account/src/main/scala/de/nowchess/account/domain/UserAccount.scala`
|
||||||
|
- class UserAccount
|
||||||
|
- function getBotAccounts
|
||||||
|
- class BotAccount
|
||||||
|
- class OfficialBotAccount
|
||||||
|
- `modules/account/src/main/scala/de/nowchess/account/error/AccountError.scala` — function message
|
||||||
|
- `modules/account/src/main/scala/de/nowchess/account/error/ChallengeError.scala` — function message
|
||||||
|
- `modules/account/src/main/scala/de/nowchess/account/filter/AlreadyLoggedInFilter.scala` — class AlreadyLoggedInFilter
|
||||||
|
- `modules/account/src/main/scala/de/nowchess/account/repository/AccountRepository.scala`
|
||||||
|
- class UserAccountRepository
|
||||||
|
- function findByUsername
|
||||||
|
- function findById
|
||||||
|
- function persist
|
||||||
|
- function findByEmail
|
||||||
|
- function findAll
|
||||||
|
- _...12 more_
|
||||||
|
- `modules/account/src/main/scala/de/nowchess/account/repository/ChallengeRepository.scala`
|
||||||
|
- class ChallengeRepository
|
||||||
|
- function findActiveByChallengerId
|
||||||
|
- function findActiveByDestUserId
|
||||||
|
- function findDuplicateChallenge
|
||||||
|
- function findById
|
||||||
|
- function persist
|
||||||
|
- _...1 more_
|
||||||
|
- `modules/account/src/main/scala/de/nowchess/account/resource/AccountResource.scala`
|
||||||
|
- class AccountResource
|
||||||
|
- function register
|
||||||
|
- function login
|
||||||
|
- function me
|
||||||
|
- function publicProfile
|
||||||
|
- function banUser
|
||||||
|
- _...10 more_
|
||||||
|
- `modules/account/src/main/scala/de/nowchess/account/resource/ChallengeResource.scala`
|
||||||
|
- class ChallengeResource
|
||||||
|
- function create
|
||||||
|
- function list
|
||||||
|
- function accept
|
||||||
|
- function decline
|
||||||
|
- function cancel
|
||||||
|
- `modules/account/src/main/scala/de/nowchess/account/service/AccountService.scala`
|
||||||
|
- class AccountService
|
||||||
|
- function register
|
||||||
|
- function login
|
||||||
|
- function findByUsername
|
||||||
|
- function findById
|
||||||
|
- function createBotAccount
|
||||||
|
- _...11 more_
|
||||||
|
- `modules/account/src/main/scala/de/nowchess/account/service/ChallengeService.scala`
|
||||||
|
- class ChallengeService
|
||||||
|
- function create
|
||||||
|
- function accept
|
||||||
|
- function decline
|
||||||
|
- function cancel
|
||||||
|
- function listForUser
|
||||||
|
- _...1 more_
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Board.scala`
|
- `modules/api/src/main/scala/de/nowchess/api/board/Board.scala`
|
||||||
- class Board
|
- class Board
|
||||||
- function apply
|
- function apply
|
||||||
@@ -38,42 +106,279 @@
|
|||||||
- class Square
|
- class Square
|
||||||
- function fromAlgebraic
|
- function fromAlgebraic
|
||||||
- function offset
|
- function offset
|
||||||
|
- `modules/api/src/main/scala/de/nowchess/api/bot/Bot.scala`
|
||||||
|
- class Bot
|
||||||
|
- function name
|
||||||
|
- function nextMove
|
||||||
|
- `modules/api/src/main/scala/de/nowchess/api/dto/ErrorEventDto.scala` — class ErrorEventDto, function apply
|
||||||
|
- `modules/api/src/main/scala/de/nowchess/api/dto/GameFullEventDto.scala` — class GameFullEventDto, function apply
|
||||||
|
- `modules/api/src/main/scala/de/nowchess/api/dto/GameStateEventDto.scala` — class GameStateEventDto, function apply
|
||||||
|
- `modules/api/src/main/scala/de/nowchess/api/error/GameError.scala` — function message
|
||||||
|
- `modules/api/src/main/scala/de/nowchess/api/game/ClockState.scala`
|
||||||
|
- function activeColor
|
||||||
|
- function afterMove
|
||||||
|
- function remainingMs
|
||||||
|
- function remainingMs
|
||||||
|
- function afterMove
|
||||||
|
- function remainingMs
|
||||||
|
- _...3 more_
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`
|
- `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala`
|
||||||
|
- function kingSquare
|
||||||
- function withBoard
|
- function withBoard
|
||||||
- function withTurn
|
- function withTurn
|
||||||
- function withCastlingRights
|
- function withCastlingRights
|
||||||
- function withEnPassantSquare
|
- function withEnPassantSquare
|
||||||
- function withHalfMoveClock
|
- function withHalfMoveClock
|
||||||
- function withMove
|
- _...4 more_
|
||||||
- _...2 more_
|
- `modules/api/src/main/scala/de/nowchess/api/io/GameContextExport.scala` — class GameContextExport, function exportGameContext
|
||||||
|
- `modules/api/src/main/scala/de/nowchess/api/io/GameContextImport.scala` — class GameContextImport, function importGameContext
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/player/PlayerInfo.scala` — class PlayerId, function apply
|
- `modules/api/src/main/scala/de/nowchess/api/player/PlayerInfo.scala` — class PlayerId, function apply
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/response/ApiResponse.scala`
|
- `modules/api/src/main/scala/de/nowchess/api/response/ApiResponse.scala`
|
||||||
- class ApiResponse
|
- class ApiResponse
|
||||||
- function error
|
- function error
|
||||||
- function totalPages
|
- function totalPages
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/command/Command.scala`
|
- `modules/api/src/main/scala/de/nowchess/api/rules/RuleSet.scala`
|
||||||
- class Command
|
- class RuleSet
|
||||||
- function execute
|
- function candidateMoves
|
||||||
- function undo
|
- function legalMoves
|
||||||
- function description
|
- function allLegalMoves
|
||||||
- class MoveResult
|
- function isCheck
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/command/CommandInvoker.scala`
|
- function isCheckmate
|
||||||
- class CommandInvoker
|
- _...6 more_
|
||||||
- function execute
|
- `modules/bot/python/nnue.py`
|
||||||
- function undo
|
- function get_weights_dir: ()
|
||||||
- function redo
|
- function get_data_dir: ()
|
||||||
- function history
|
- function list_checkpoints: ()
|
||||||
- function getCurrentIndex
|
- function migrate_legacy_data: ()
|
||||||
|
- function show_header: ()
|
||||||
|
- function show_checkpoints_table: ()
|
||||||
|
- _...10 more_
|
||||||
|
- `modules/bot/python/src/dataset.py`
|
||||||
|
- function get_datasets_dir: () -> Path
|
||||||
|
- function next_dataset_version: () -> int
|
||||||
|
- function list_datasets: () -> List[Tuple[int, Dict]]
|
||||||
|
- function load_dataset_metadata: (version) -> Optional[Dict]
|
||||||
|
- function save_dataset_metadata: (version, metadata) -> None
|
||||||
|
- function create_dataset: (version, labeled_jsonl_path, sources, stockfish_depth) -> Path
|
||||||
|
- _...4 more_
|
||||||
|
- `modules/bot/python/src/export.py` — function export_to_nbai: (weights_file, output_file, trained_by, train_loss)
|
||||||
|
- `modules/bot/python/src/generate.py` — function play_random_game_and_collect_positions: (output_file, total_positions, samples_per_game, min_move, max_move, num_workers)
|
||||||
|
- `modules/bot/python/src/label.py` — function normalize_evaluation: (cp_value, method, scale), function label_positions_with_stockfish: (positions_file, output_file, stockfish_path, batch_size, depth, verbose, normalize, num_workers)
|
||||||
|
- `modules/bot/python/src/lichess_importer.py` — function import_lichess_evals: (input_path, output_file, max_positions, min_depth, verbose) -> int
|
||||||
|
- `modules/bot/python/src/tactical_positions_extractor.py`
|
||||||
|
- function download_and_extract_puzzle_db: (url, output_dir)
|
||||||
|
- function extract_puzzle_positions: (puzzle_csv, max_puzzles) -> Set[str]
|
||||||
|
- function load_positions_from_file: (file_path) -> Set[str]
|
||||||
|
- function merge_positions: (tactical, other, output_file)
|
||||||
|
- function extract_tactical_only: (puzzle_csv, output_file, max_puzzles) -> int
|
||||||
|
- function interactive_merge_positions: (puzzle_csv, output_file, max_puzzles)
|
||||||
|
- `modules/bot/python/src/train.py`
|
||||||
|
- function fen_to_features: (fen)
|
||||||
|
- function find_next_version: (base_name)
|
||||||
|
- function save_metadata: (weights_file, metadata)
|
||||||
|
- function train_nnue: (data_file, output_file, epochs, batch_size, lr, checkpoint, stockfish_depth, use_versioning, early_stopping_patience, weight_decay, subsample_ratio, hidden_sizes)
|
||||||
|
- function burst_train: (data_file, output_file, duration_minutes, epochs_per_season, early_stopping_patience, batch_size, lr, initial_checkpoint, stockfish_depth, use_versioning, weight_decay, subsample_ratio, hidden_sizes)
|
||||||
|
- class NNUEDataset
|
||||||
|
- _...1 more_
|
||||||
|
- `modules/bot/src/main/scala/de/nowchess/bot/BotController.scala`
|
||||||
|
- class BotController
|
||||||
|
- function getBot
|
||||||
|
- function listBots
|
||||||
|
- `modules/bot/src/main/scala/de/nowchess/bot/BotMoveRepetition.scala`
|
||||||
|
- class BotMoveRepetition
|
||||||
|
- function blockedMoves
|
||||||
|
- function repeatedMove
|
||||||
|
- function filterAllowed
|
||||||
|
- `modules/bot/src/main/scala/de/nowchess/bot/Config.scala` — class Config
|
||||||
|
- `modules/bot/src/main/scala/de/nowchess/bot/ai/Evaluation.scala`
|
||||||
|
- class Evaluation
|
||||||
|
- class CHECKMATE_SCORE
|
||||||
|
- class DRAW_SCORE
|
||||||
|
- function evaluate
|
||||||
|
- function initAccumulator
|
||||||
|
- function copyAccumulator
|
||||||
|
- _...2 more_
|
||||||
|
- `modules/bot/src/main/scala/de/nowchess/bot/bots/classic/EvaluationClassic.scala`
|
||||||
|
- class EvaluationClassic
|
||||||
|
- function evaluate
|
||||||
|
- function countRay
|
||||||
|
- `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/EvaluationNNUE.scala` — class EvaluationNNUE, function evaluate
|
||||||
|
- `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/NNUE.scala`
|
||||||
|
- class NNUE
|
||||||
|
- function initAccumulator
|
||||||
|
- function pushAccumulator
|
||||||
|
- function copyAccumulator
|
||||||
|
- function recomputeAccumulator
|
||||||
|
- function validateAccumulator
|
||||||
|
- _...4 more_
|
||||||
|
- `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/NbaiLoader.scala`
|
||||||
|
- class NbaiLoader
|
||||||
|
- function load
|
||||||
|
- function loadDefault
|
||||||
|
- `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/NbaiMigrator.scala` — class NbaiMigrator, function migrateFromBin
|
||||||
|
- `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/NbaiModel.scala`
|
||||||
|
- function toJson
|
||||||
|
- class NbaiMetadata
|
||||||
|
- function fromJson
|
||||||
|
- function str
|
||||||
|
- function num
|
||||||
|
- `modules/bot/src/main/scala/de/nowchess/bot/bots/nnue/NbaiWriter.scala` — class NbaiWriter, function write
|
||||||
|
- `modules/bot/src/main/scala/de/nowchess/bot/logic/AlphaBetaSearch.scala`
|
||||||
|
- function bestMove
|
||||||
|
- function bestMove
|
||||||
|
- function bestMoveWithTime
|
||||||
|
- function bestMoveWithTime
|
||||||
|
- function loop
|
||||||
|
- function loop
|
||||||
|
- `modules/bot/src/main/scala/de/nowchess/bot/logic/MoveOrdering.scala`
|
||||||
|
- class MoveOrdering
|
||||||
|
- class OrderingContext
|
||||||
|
- function addKillerMove
|
||||||
|
- function getKillerMoves
|
||||||
|
- function addHistory
|
||||||
|
- function getHistory
|
||||||
- _...3 more_
|
- _...3 more_
|
||||||
|
- `modules/bot/src/main/scala/de/nowchess/bot/logic/TranspositionTable.scala`
|
||||||
|
- function advance
|
||||||
|
- function probe
|
||||||
|
- function store
|
||||||
|
- function clear
|
||||||
|
- `modules/bot/src/main/scala/de/nowchess/bot/util/PolyglotBook.scala` — function probe, function select
|
||||||
|
- `modules/bot/src/main/scala/de/nowchess/bot/util/PolyglotHash.scala` — class PolyglotHash, function hash
|
||||||
|
- `modules/bot/src/main/scala/de/nowchess/bot/util/ZobristHash.scala`
|
||||||
|
- class ZobristHash
|
||||||
|
- function hash
|
||||||
|
- function nextHash
|
||||||
|
- `modules/coordinator/src/main/scala/de/nowchess/coordinator/CoordinatorApp.scala` — class CoordinatorApp
|
||||||
|
- `modules/coordinator/src/main/scala/de/nowchess/coordinator/config/BeansProducer.scala`
|
||||||
|
- class BeansProducer
|
||||||
|
- function redissonClient
|
||||||
|
- function kubernetesClient
|
||||||
|
- `modules/coordinator/src/main/scala/de/nowchess/coordinator/config/CoordinatorConfig.scala`
|
||||||
|
- class CoordinatorConfig
|
||||||
|
- function maxGamesPerCore
|
||||||
|
- function maxDeviationPercent
|
||||||
|
- function rebalanceInterval
|
||||||
|
- function rebalanceMinInterval
|
||||||
|
- function heartbeatTtl
|
||||||
|
- _...11 more_
|
||||||
|
- `modules/coordinator/src/main/scala/de/nowchess/coordinator/config/JacksonConfig.scala` — class JacksonConfig, function customize
|
||||||
|
- `modules/coordinator/src/main/scala/de/nowchess/coordinator/config/NativeReflectionConfig.scala` — class NativeReflectionConfig
|
||||||
|
- `modules/coordinator/src/main/scala/de/nowchess/coordinator/grpc/CoordinatorGrpcServer.scala` — class CoordinatorGrpcServer
|
||||||
|
- `modules/coordinator/src/main/scala/de/nowchess/coordinator/grpc/CoreGrpcClient.scala`
|
||||||
|
- class CoreGrpcClient
|
||||||
|
- function shutdown
|
||||||
|
- function batchResubscribeGames
|
||||||
|
- function unsubscribeGames
|
||||||
|
- function evictGames
|
||||||
|
- `modules/coordinator/src/main/scala/de/nowchess/coordinator/resource/CoordinatorResource.scala`
|
||||||
|
- class CoordinatorResource
|
||||||
|
- function listInstances
|
||||||
|
- function getMetrics
|
||||||
|
- function triggerRebalance
|
||||||
|
- function triggerFailover
|
||||||
|
- function triggerScaleUp
|
||||||
|
- _...1 more_
|
||||||
|
- `modules/coordinator/src/main/scala/de/nowchess/coordinator/service/AutoScaler.scala`
|
||||||
|
- class AutoScaler
|
||||||
|
- function checkAndScale
|
||||||
|
- function scaleUp
|
||||||
|
- function scaleDown
|
||||||
|
- `modules/coordinator/src/main/scala/de/nowchess/coordinator/service/CacheEvictionManager.scala`
|
||||||
|
- class CacheEvictionManager
|
||||||
|
- function setRedisPrefix
|
||||||
|
- function evictStaleGames
|
||||||
|
- `modules/coordinator/src/main/scala/de/nowchess/coordinator/service/FailoverService.scala`
|
||||||
|
- class FailoverService
|
||||||
|
- function setRedisPrefix
|
||||||
|
- function onInstanceStreamDropped
|
||||||
|
- `modules/coordinator/src/main/scala/de/nowchess/coordinator/service/HealthMonitor.scala`
|
||||||
|
- class HealthMonitor
|
||||||
|
- function setRedisPrefix
|
||||||
|
- function checkInstanceHealth
|
||||||
|
- function watchK8sPods
|
||||||
|
- `modules/coordinator/src/main/scala/de/nowchess/coordinator/service/InstanceRegistry.scala`
|
||||||
|
- class InstanceRegistry
|
||||||
|
- function setRedisPrefix
|
||||||
|
- function getInstance
|
||||||
|
- function getAllInstances
|
||||||
|
- function updateInstanceFromRedis
|
||||||
|
- function markInstanceDead
|
||||||
|
- _...1 more_
|
||||||
|
- `modules/coordinator/src/main/scala/de/nowchess/coordinator/service/LoadBalancer.scala`
|
||||||
|
- class LoadBalancer
|
||||||
|
- function setRedisPrefix
|
||||||
|
- function shouldRebalance
|
||||||
|
- function rebalance
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/adapter/RuleSetRestAdapter.scala`
|
||||||
|
- class RuleSetRestAdapter
|
||||||
|
- function candidateMoves
|
||||||
|
- function legalMoves
|
||||||
|
- function allLegalMoves
|
||||||
|
- function isCheck
|
||||||
|
- function isCheckmate
|
||||||
|
- _...5 more_
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/client/IoServiceClient.scala`
|
||||||
|
- class IoServiceClient
|
||||||
|
- function importFen
|
||||||
|
- function importPgn
|
||||||
|
- function exportFen
|
||||||
|
- function exportPgn
|
||||||
|
- function exportCombined
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/client/RuleServiceClient.scala`
|
||||||
|
- class RuleServiceClient
|
||||||
|
- function candidateMoves
|
||||||
|
- function legalMoves
|
||||||
|
- function allLegalMoves
|
||||||
|
- function isCheck
|
||||||
|
- function isCheckmate
|
||||||
|
- _...6 more_
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/client/StoreServiceClient.scala` — class StoreServiceClient, function getGame
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/config/JacksonConfig.scala` — class JacksonConfig, function customize
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/config/NativeReflectionConfig.scala` — class NativeReflectionConfig
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/config/RedisConfig.scala` — class RedisConfig
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/config/RedissonProducer.scala`
|
||||||
|
- class RedissonProducer
|
||||||
|
- function produceRedissonClient
|
||||||
|
- function shutdown
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/controller/Parser.scala` — class Parser, function parseMove
|
- `modules/core/src/main/scala/de/nowchess/chess/controller/Parser.scala` — class Parser, function parseMove
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`
|
- `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala`
|
||||||
- class GameEngine
|
- class GameEngine
|
||||||
- function isPendingPromotion
|
|
||||||
- function board
|
- function board
|
||||||
- function turn
|
- function turn
|
||||||
- function context
|
- function context
|
||||||
- function canUndo
|
- function pendingDrawOfferBy
|
||||||
- _...10 more_
|
- function currentClockState
|
||||||
|
- _...22 more_
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/exception/ApiException.scala`
|
||||||
|
- class ApiException
|
||||||
|
- class GameNotFoundException
|
||||||
|
- class BadRequestException
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/exception/ApiExceptionMapper.scala` — class ApiExceptionMapper, function toResponse
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/grpc/CoordinatorServiceHandler.scala` — class CoordinatorServiceHandler
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/grpc/CoreProtoMapper.scala`
|
||||||
|
- class CoreProtoMapper
|
||||||
|
- function toProtoColor
|
||||||
|
- function fromProtoColor
|
||||||
|
- function toProtoPieceType
|
||||||
|
- function fromProtoPieceType
|
||||||
|
- function toProtoMoveKind
|
||||||
|
- _...9 more_
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/grpc/IoGrpcClientWrapper.scala`
|
||||||
|
- class IoGrpcClientWrapper
|
||||||
|
- function exportCombined
|
||||||
|
- function importFen
|
||||||
|
- function importPgn
|
||||||
|
- function exportFen
|
||||||
|
- function exportPgn
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/grpc/RuleSetGrpcAdapter.scala`
|
||||||
|
- class RuleSetGrpcAdapter
|
||||||
|
- function candidateMoves
|
||||||
|
- function legalMoves
|
||||||
|
- function allLegalMoves
|
||||||
|
- function isCheck
|
||||||
|
- function isCheckmate
|
||||||
|
- _...5 more_
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala`
|
- `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala`
|
||||||
- function context
|
- function context
|
||||||
- class Observer
|
- class Observer
|
||||||
@@ -82,8 +387,59 @@
|
|||||||
- function subscribe
|
- function subscribe
|
||||||
- function unsubscribe
|
- function unsubscribe
|
||||||
- _...1 more_
|
- _...1 more_
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/GameContextExport.scala` — class GameContextExport, function exportGameContext
|
- `modules/core/src/main/scala/de/nowchess/chess/redis/C2sMessage.scala` — class C2sMessage
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/GameContextImport.scala` — class GameContextImport, function importGameContext
|
- `modules/core/src/main/scala/de/nowchess/chess/redis/GameRedisPublisher.scala` — class GameRedisPublisher, function onGameEvent
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/redis/GameRedisSubscriberManager.scala`
|
||||||
|
- class GameRedisSubscriberManager
|
||||||
|
- function subscribeGame
|
||||||
|
- function onMessage
|
||||||
|
- function unsubscribeGame
|
||||||
|
- function batchResubscribeGames
|
||||||
|
- function unsubscribeGames
|
||||||
|
- _...3 more_
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/registry/GameRegistry.scala`
|
||||||
|
- class GameRegistry
|
||||||
|
- function store
|
||||||
|
- function get
|
||||||
|
- function update
|
||||||
|
- function generateId
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/registry/RedisGameRegistry.scala`
|
||||||
|
- class RedisGameRegistry
|
||||||
|
- function generateId
|
||||||
|
- function store
|
||||||
|
- function get
|
||||||
|
- function update
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/resource/GameDtoMapper.scala`
|
||||||
|
- class GameDtoMapper
|
||||||
|
- function statusOf
|
||||||
|
- function moveToUci
|
||||||
|
- function toPlayerDto
|
||||||
|
- function toClockDto
|
||||||
|
- function toGameStateDto
|
||||||
|
- _...1 more_
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/resource/GameResource.scala`
|
||||||
|
- class GameResource
|
||||||
|
- function onGameEvent
|
||||||
|
- function createGame
|
||||||
|
- function getGame
|
||||||
|
- function resignGame
|
||||||
|
- function makeMove
|
||||||
|
- _...9 more_
|
||||||
|
- `modules/core/src/main/scala/de/nowchess/chess/service/InstanceHeartbeatService.scala`
|
||||||
|
- class InstanceHeartbeatService
|
||||||
|
- function onStart
|
||||||
|
- function onShutdown
|
||||||
|
- function setRedisPrefix
|
||||||
|
- function setSubscriptionCount
|
||||||
|
- function setLocalCacheSize
|
||||||
|
- _...2 more_
|
||||||
|
- `modules/io/src/main/scala/de/nowchess/io/GameFileService.scala`
|
||||||
|
- class GameFileService
|
||||||
|
- function saveGameToFile
|
||||||
|
- function loadGameFromFile
|
||||||
|
- class FileSystemGameService
|
||||||
|
- function saveGameToFile
|
||||||
|
- function loadGameFromFile
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenExporter.scala`
|
- `modules/io/src/main/scala/de/nowchess/io/fen/FenExporter.scala`
|
||||||
- class FenExporter
|
- class FenExporter
|
||||||
- function boardToFen
|
- function boardToFen
|
||||||
@@ -105,6 +461,17 @@
|
|||||||
- function parseBoard
|
- function parseBoard
|
||||||
- function importGameContext
|
- function importGameContext
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParserSupport.scala` — function buildSquares
|
- `modules/io/src/main/scala/de/nowchess/io/fen/FenParserSupport.scala` — function buildSquares
|
||||||
|
- `modules/io/src/main/scala/de/nowchess/io/grpc/IoGrpcService.scala` — class IoGrpcService
|
||||||
|
- `modules/io/src/main/scala/de/nowchess/io/grpc/IoProtoMapper.scala`
|
||||||
|
- class IoProtoMapper
|
||||||
|
- function toProtoColor
|
||||||
|
- function fromProtoColor
|
||||||
|
- function toProtoPieceType
|
||||||
|
- function fromProtoPieceType
|
||||||
|
- function toProtoMoveKind
|
||||||
|
- _...9 more_
|
||||||
|
- `modules/io/src/main/scala/de/nowchess/io/json/JsonExporter.scala` — class JsonExporter, function exportGameContext
|
||||||
|
- `modules/io/src/main/scala/de/nowchess/io/json/JsonParser.scala` — class JsonParser, function importGameContext
|
||||||
- `modules/io/src/main/scala/de/nowchess/io/pgn/PgnExporter.scala`
|
- `modules/io/src/main/scala/de/nowchess/io/pgn/PgnExporter.scala`
|
||||||
- class PgnExporter
|
- class PgnExporter
|
||||||
- function exportGameContext
|
- function exportGameContext
|
||||||
@@ -115,36 +482,73 @@
|
|||||||
- function importGameContext
|
- function importGameContext
|
||||||
- function parsePgn
|
- function parsePgn
|
||||||
- function parseAlgebraicMove
|
- function parseAlgebraicMove
|
||||||
- `modules/rule/src/main/scala/de/nowchess/rules/RuleSet.scala`
|
- `modules/io/src/main/scala/de/nowchess/io/service/config/JacksonConfig.scala` — class JacksonConfig, function customize
|
||||||
- class RuleSet
|
- `modules/io/src/main/scala/de/nowchess/io/service/config/NativeReflectionConfig.scala` — class NativeReflectionConfig
|
||||||
|
- `modules/io/src/main/scala/de/nowchess/io/service/resource/IoResource.scala`
|
||||||
|
- class IoResource
|
||||||
|
- function importFen
|
||||||
|
- function importPgn
|
||||||
|
- function exportFen
|
||||||
|
- function exportPgn
|
||||||
|
- function exportCombined
|
||||||
|
- `modules/json/src/main/scala/de/nowchess/json/ChessJacksonModule.scala` — class ChessJacksonModule
|
||||||
|
- `modules/json/src/main/scala/de/nowchess/json/GameResultDeserializer.scala` — class GameResultDeserializer
|
||||||
|
- `modules/json/src/main/scala/de/nowchess/json/GameResultSerializer.scala` — class GameResultSerializer
|
||||||
|
- `modules/json/src/main/scala/de/nowchess/json/MoveTypeDeserializer.scala` — class MoveTypeDeserializer
|
||||||
|
- `modules/json/src/main/scala/de/nowchess/json/MoveTypeSerializer.scala` — class MoveTypeSerializer
|
||||||
|
- `modules/json/src/main/scala/de/nowchess/json/SquareDeserializer.scala` — class SquareDeserializer
|
||||||
|
- `modules/json/src/main/scala/de/nowchess/json/SquareKeyDeserializer.scala` — class SquareKeyDeserializer
|
||||||
|
- `modules/json/src/main/scala/de/nowchess/json/SquareKeySerializer.scala` — class SquareKeySerializer
|
||||||
|
- `modules/json/src/main/scala/de/nowchess/json/SquareSerializer.scala` — class SquareSerializer
|
||||||
|
- `modules/rule/src/main/scala/de/nowchess/rules/config/JacksonConfig.scala` — class JacksonConfig, function customize
|
||||||
|
- `modules/rule/src/main/scala/de/nowchess/rules/config/NativeReflectionConfig.scala` — class NativeReflectionConfig
|
||||||
|
- `modules/rule/src/main/scala/de/nowchess/rules/grpc/ProtoMapper.scala`
|
||||||
|
- class ProtoMapper
|
||||||
|
- function toProtoColor
|
||||||
|
- function fromProtoColor
|
||||||
|
- function toProtoPieceType
|
||||||
|
- function fromProtoPieceType
|
||||||
|
- function toProtoMoveKind
|
||||||
|
- _...9 more_
|
||||||
|
- `modules/rule/src/main/scala/de/nowchess/rules/grpc/RuleGrpcService.scala` — class RuleGrpcService
|
||||||
|
- `modules/rule/src/main/scala/de/nowchess/rules/resource/RuleSetResource.scala`
|
||||||
|
- class RuleSetResource
|
||||||
- function candidateMoves
|
- function candidateMoves
|
||||||
- function legalMoves
|
- function legalMoves
|
||||||
- function allLegalMoves
|
- function allLegalMoves
|
||||||
- function isCheck
|
- function isCheck
|
||||||
- function isCheckmate
|
- function isCheckmate
|
||||||
- _...4 more_
|
- _...6 more_
|
||||||
- `modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala`
|
- `modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala` — class DefaultRules, function positionOf
|
||||||
- class DefaultRules
|
- `modules/store/src/main/scala/de/nowchess/store/config/JacksonConfig.scala` — class JacksonConfig, function customize
|
||||||
- function loop
|
- `modules/store/src/main/scala/de/nowchess/store/config/NativeReflectionConfig.scala` — class NativeReflectionConfig
|
||||||
- function toMoves
|
- `modules/store/src/main/scala/de/nowchess/store/config/RedisConfig.scala` — class RedisConfig
|
||||||
- function loop
|
- `modules/store/src/main/scala/de/nowchess/store/config/RedissonProducer.scala`
|
||||||
- `modules/ui/src/main/scala/de/nowchess/ui/Main.scala` — class Main, function main
|
- class RedissonProducer
|
||||||
- `modules/ui/src/main/scala/de/nowchess/ui/gui/ChessBoardView.scala`
|
- function redissonClient
|
||||||
- class ChessBoardView
|
- function close
|
||||||
- function updateBoard
|
- `modules/store/src/main/scala/de/nowchess/store/domain/GameRecord.scala` — class GameRecord
|
||||||
- function updateUndoRedoButtons
|
- `modules/store/src/main/scala/de/nowchess/store/redis/GameWritebackStreamListener.scala`
|
||||||
- function showMessage
|
- class GameWritebackStreamListener
|
||||||
- function showPromotionDialog
|
- function startListening
|
||||||
- `modules/ui/src/main/scala/de/nowchess/ui/gui/ChessGUI.scala`
|
- function onMessage
|
||||||
- class ChessGUIApp
|
- `modules/store/src/main/scala/de/nowchess/store/repository/GameRecordRepository.scala`
|
||||||
- class ChessGUILauncher
|
- class GameRecordRepository
|
||||||
- function getEngine
|
- function findByGameId
|
||||||
- function launch
|
- function persist
|
||||||
- `modules/ui/src/main/scala/de/nowchess/ui/gui/GUIObserver.scala` — class GUIObserver
|
- function merge
|
||||||
- `modules/ui/src/main/scala/de/nowchess/ui/gui/PieceSprites.scala`
|
- `modules/store/src/main/scala/de/nowchess/store/resource/StoreGameResource.scala` — class StoreGameResource, function getGame
|
||||||
- class PieceSprites
|
- `modules/store/src/main/scala/de/nowchess/store/service/GameWritebackService.scala` — class GameWritebackService, function writeBack
|
||||||
- function loadPieceImage
|
- `modules/ws/src/main/scala/de/nowchess/ws/config/JacksonConfig.scala` — class JacksonConfig, function customize
|
||||||
- class SquareColors
|
- `modules/ws/src/main/scala/de/nowchess/ws/config/NativeReflectionConfig.scala` — class NativeReflectionConfig
|
||||||
- `modules/ui/src/main/scala/de/nowchess/ui/terminal/TerminalUI.scala` — class TerminalUI, function start
|
- `modules/ws/src/main/scala/de/nowchess/ws/config/RedisConfig.scala` — class RedisConfig
|
||||||
- `modules/ui/src/main/scala/de/nowchess/ui/utils/PieceUnicode.scala` — function unicode
|
- `modules/ws/src/main/scala/de/nowchess/ws/config/RedissonProducer.scala`
|
||||||
- `modules/ui/src/main/scala/de/nowchess/ui/utils/Renderer.scala` — class Renderer, function render
|
- class RedissonProducer
|
||||||
|
- function produceRedissonClient
|
||||||
|
- function shutdown
|
||||||
|
- `modules/ws/src/main/scala/de/nowchess/ws/resource/GameWebSocketResource.scala`
|
||||||
|
- class GameWebSocketResource
|
||||||
|
- function onOpen
|
||||||
|
- function onMessage
|
||||||
|
- function onTextMessage
|
||||||
|
- function onClose
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# Middleware
|
||||||
|
|
||||||
|
## custom
|
||||||
|
- generate — `modules/bot/python/src/generate.py`
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>NowChessSystems — codesight report</title>
|
||||||
|
<style>
|
||||||
|
*{margin:0;padding:0;box-sizing:border-box}
|
||||||
|
:root{--bg:#0a0a0f;--card:#12121a;--border:#1e1e2e;--text:#e0e0e8;--muted:#6b6b80;--accent:#6366f1;--accent2:#22d3ee;--green:#22c55e;--orange:#f59e0b;--red:#ef4444;--pink:#ec4899}
|
||||||
|
body{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;padding:2rem;max-width:1400px;margin:0 auto;line-height:1.6}
|
||||||
|
h1{font-size:2.5rem;font-weight:800;background:linear-gradient(135deg,var(--accent),var(--accent2));-webkit-background-clip:text;-webkit-text-fill-color:transparent;margin-bottom:.25rem}
|
||||||
|
.subtitle{color:var(--muted);font-size:1rem;margin-bottom:2rem}
|
||||||
|
.stack-badge{display:inline-block;background:var(--card);border:1px solid var(--border);border-radius:6px;padding:2px 10px;font-size:.85rem;color:var(--accent2);margin:0 4px 4px 0}
|
||||||
|
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:1rem;margin:2rem 0}
|
||||||
|
.stat{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:1.25rem;text-align:center}
|
||||||
|
.stat-value{font-size:2rem;font-weight:800;background:linear-gradient(135deg,var(--accent),var(--accent2));-webkit-background-clip:text;-webkit-text-fill-color:transparent}
|
||||||
|
.stat-label{color:var(--muted);font-size:.85rem;margin-top:.25rem}
|
||||||
|
.token-hero{background:linear-gradient(135deg,#1a1a2e,#16213e);border:1px solid var(--accent);border-radius:16px;padding:2rem;margin:2rem 0;text-align:center}
|
||||||
|
.token-saved{font-size:3rem;font-weight:900;color:var(--green)}
|
||||||
|
.token-detail{color:var(--muted);font-size:.9rem;margin-top:.5rem}
|
||||||
|
.section{margin:2.5rem 0}
|
||||||
|
.section h2{font-size:1.4rem;font-weight:700;margin-bottom:1rem;padding-bottom:.5rem;border-bottom:1px solid var(--border)}
|
||||||
|
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:1rem}
|
||||||
|
.card{background:var(--card);border:1px solid var(--border);border-radius:10px;padding:1rem;transition:border-color .2s}
|
||||||
|
.card:hover{border-color:var(--accent)}
|
||||||
|
.card-title{font-weight:700;font-size:1rem;margin-bottom:.5rem}
|
||||||
|
.card-meta{color:var(--muted);font-size:.8rem}
|
||||||
|
.tag{display:inline-block;background:rgba(99,102,241,.15);color:var(--accent);border-radius:4px;padding:1px 6px;font-size:.75rem;margin:1px}
|
||||||
|
.tag-auth{background:rgba(239,68,68,.15);color:var(--red)}
|
||||||
|
.tag-db{background:rgba(34,211,238,.15);color:var(--accent2)}
|
||||||
|
.tag-ai{background:rgba(236,72,153,.15);color:var(--pink)}
|
||||||
|
.tag-payment{background:rgba(245,158,11,.15);color:var(--orange)}
|
||||||
|
.tag-email{background:rgba(34,197,94,.15);color:var(--green)}
|
||||||
|
.tag-queue{background:rgba(168,85,247,.15);color:#a855f7}
|
||||||
|
.tag-cache{background:rgba(245,158,11,.15);color:var(--orange)}
|
||||||
|
.method{font-weight:700;font-size:.8rem;padding:2px 6px;border-radius:4px;margin-right:6px}
|
||||||
|
.method-GET{background:rgba(34,197,94,.2);color:var(--green)}
|
||||||
|
.method-POST{background:rgba(99,102,241,.2);color:var(--accent)}
|
||||||
|
.method-PUT{background:rgba(245,158,11,.2);color:var(--orange)}
|
||||||
|
.method-PATCH{background:rgba(245,158,11,.2);color:var(--orange)}
|
||||||
|
.method-DELETE{background:rgba(239,68,68,.2);color:var(--red)}
|
||||||
|
.method-ALL{background:rgba(107,107,128,.2);color:var(--muted)}
|
||||||
|
.route-path{font-family:'Fira Code',monospace;font-size:.9rem}
|
||||||
|
.route-contract{color:var(--muted);font-size:.8rem;font-style:italic;margin-left:.5rem}
|
||||||
|
.field{display:flex;gap:.5rem;padding:3px 0;font-size:.9rem}
|
||||||
|
.field-name{font-family:monospace;color:var(--accent2)}
|
||||||
|
.field-type{color:var(--muted);font-family:monospace}
|
||||||
|
.field-flags{display:flex;gap:3px}
|
||||||
|
.flag{font-size:.7rem;padding:0 4px;border-radius:3px;background:rgba(99,102,241,.1);color:var(--accent)}
|
||||||
|
.flag-pk{background:rgba(245,158,11,.2);color:var(--orange)}
|
||||||
|
.flag-fk{background:rgba(34,211,238,.2);color:var(--accent2)}
|
||||||
|
.flag-unique{background:rgba(236,72,153,.2);color:var(--pink)}
|
||||||
|
.hot-bar{height:8px;background:linear-gradient(90deg,var(--accent),var(--accent2));border-radius:4px;margin-top:4px}
|
||||||
|
.component-props{color:var(--muted);font-size:.85rem}
|
||||||
|
.badge-client{background:rgba(34,197,94,.15);color:var(--green);font-size:.75rem;padding:1px 6px;border-radius:4px}
|
||||||
|
.badge-server{background:rgba(99,102,241,.15);color:var(--accent);font-size:.75rem;padding:1px 6px;border-radius:4px}
|
||||||
|
.env-required{color:var(--red);font-weight:600;font-size:.8rem}
|
||||||
|
.env-default{color:var(--green);font-size:.8rem}
|
||||||
|
.footer{text-align:center;color:var(--muted);margin-top:4rem;padding-top:2rem;border-top:1px solid var(--border);font-size:.85rem}
|
||||||
|
.footer a{color:var(--accent);text-decoration:none}
|
||||||
|
table{width:100%;border-collapse:collapse}
|
||||||
|
table td,table th{padding:8px 12px;text-align:left;border-bottom:1px solid var(--border);font-size:.9rem}
|
||||||
|
table th{color:var(--muted);font-size:.8rem;font-weight:600;text-transform:uppercase}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h1>NowChessSystems</h1>
|
||||||
|
<div class="subtitle">AI Context Map — generated by codesight</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span class="stack-badge">raw-http</span>
|
||||||
|
|
||||||
|
<span class="stack-badge">unknown</span>
|
||||||
|
<span class="stack-badge">scala</span>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="token-hero">
|
||||||
|
<div class="token-saved">~20,573 tokens saved</div>
|
||||||
|
<div class="token-detail">
|
||||||
|
Output: 5,297 tokens — Exploration cost without codesight: ~25,870 tokens — 149 files scanned
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stats">
|
||||||
|
<div class="stat"><div class="stat-value">0</div><div class="stat-label">Routes</div></div>
|
||||||
|
<div class="stat"><div class="stat-value">0</div><div class="stat-label">Models</div></div>
|
||||||
|
<div class="stat"><div class="stat-value">0</div><div class="stat-label">Components</div></div>
|
||||||
|
<div class="stat"><div class="stat-value">63</div><div class="stat-label">Libraries</div></div>
|
||||||
|
<div class="stat"><div class="stat-value">1</div><div class="stat-label">Env Vars</div></div>
|
||||||
|
<div class="stat"><div class="stat-value">1</div><div class="stat-label">Middleware</div></div>
|
||||||
|
<div class="stat"><div class="stat-value">383</div><div class="stat-label">Import Links</div></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>Dependency Hot Files</h2>
|
||||||
|
<div class="grid">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-title" style="font-size:.9rem">modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala</div>
|
||||||
|
<div class="card-meta">imported by 64 files</div>
|
||||||
|
<div class="hot-bar" style="width:100%"></div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-title" style="font-size:.9rem">modules/api/src/main/scala/de/nowchess/api/move/Move.scala</div>
|
||||||
|
<div class="card-meta">imported by 44 files</div>
|
||||||
|
<div class="hot-bar" style="width:69%"></div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-title" style="font-size:.9rem">modules/api/src/main/scala/de/nowchess/api/board/Square.scala</div>
|
||||||
|
<div class="card-meta">imported by 40 files</div>
|
||||||
|
<div class="hot-bar" style="width:63%"></div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-title" style="font-size:.9rem">modules/api/src/main/scala/de/nowchess/api/board/Color.scala</div>
|
||||||
|
<div class="card-meta">imported by 35 files</div>
|
||||||
|
<div class="hot-bar" style="width:55%"></div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-title" style="font-size:.9rem">modules/api/src/main/scala/de/nowchess/api/board/Board.scala</div>
|
||||||
|
<div class="card-meta">imported by 19 files</div>
|
||||||
|
<div class="hot-bar" style="width:30%"></div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-title" style="font-size:.9rem">modules/api/src/main/scala/de/nowchess/api/board/Piece.scala</div>
|
||||||
|
<div class="card-meta">imported by 18 files</div>
|
||||||
|
<div class="hot-bar" style="width:28%"></div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-title" style="font-size:.9rem">modules/api/src/main/scala/de/nowchess/api/board/PieceType.scala</div>
|
||||||
|
<div class="card-meta">imported by 17 files</div>
|
||||||
|
<div class="hot-bar" style="width:27%"></div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-title" style="font-size:.9rem">modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala</div>
|
||||||
|
<div class="card-meta">imported by 17 files</div>
|
||||||
|
<div class="hot-bar" style="width:27%"></div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-title" style="font-size:.9rem">modules/rule/src/main/scala/de/nowchess/rules/RuleSet.scala</div>
|
||||||
|
<div class="card-meta">imported by 11 files</div>
|
||||||
|
<div class="hot-bar" style="width:17%"></div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-title" style="font-size:.9rem">modules/io/src/main/scala/de/nowchess/io/fen/FenParser.scala</div>
|
||||||
|
<div class="card-meta">imported by 10 files</div>
|
||||||
|
<div class="hot-bar" style="width:16%"></div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-title" style="font-size:.9rem">modules/api/src/main/scala/de/nowchess/api/board/CastlingRights.scala</div>
|
||||||
|
<div class="card-meta">imported by 9 files</div>
|
||||||
|
<div class="hot-bar" style="width:14%"></div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-title" style="font-size:.9rem">modules/api/src/main/scala/de/nowchess/api/game/DrawReason.scala</div>
|
||||||
|
<div class="card-meta">imported by 7 files</div>
|
||||||
|
<div class="hot-bar" style="width:11%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>Environment Variables</h2>
|
||||||
|
<table>
|
||||||
|
<tr><th>Variable</th><th>Status</th><th>Source</th></tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>STOCKFISH_PATH</code></td>
|
||||||
|
<td><span class="env-required">required</span></td>
|
||||||
|
<td class="card-meta">modules/bot/python/nnue.py</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>Middleware</h2>
|
||||||
|
<div class="grid">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-title">generate <span class="tag tag-custom">custom</span></div>
|
||||||
|
<div class="card-meta">modules/bot/python/src/generate.py</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
Generated by <a href="https://github.com/Houseofmvps/codesight">codesight</a> — see your codebase clearly
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# Routes
|
||||||
|
|
||||||
|
## gRPC
|
||||||
|
|
||||||
|
- `/CoordinatorService/BatchResubscribeGames` (BatchResubscribeRequest) → BatchResubscribeResponse
|
||||||
|
- `/CoordinatorService/UnsubscribeGames` (UnsubscribeGamesRequest) → UnsubscribeGamesResponse
|
||||||
|
- `/CoordinatorService/EvictGames` (EvictGamesRequest) → EvictGamesResponse
|
||||||
|
- `/CoordinatorService/DrainInstance` (DrainInstanceRequest) → DrainInstanceResponse
|
||||||
|
- `/CoordinatorService/BatchResubscribeGames` (BatchResubscribeRequest) → BatchResubscribeResponse
|
||||||
|
- `/CoordinatorService/UnsubscribeGames` (UnsubscribeGamesRequest) → UnsubscribeGamesResponse
|
||||||
|
- `/CoordinatorService/EvictGames` (EvictGamesRequest) → EvictGamesResponse
|
||||||
|
- `/CoordinatorService/DrainInstance` (DrainInstanceRequest) → DrainInstanceResponse
|
||||||
|
- `/IoService/ImportFen` (ProtoImportFenRequest) → ProtoGameContext
|
||||||
|
- `/IoService/ImportPgn` (ProtoImportPgnRequest) → ProtoGameContext
|
||||||
|
- `/IoService/ExportCombined` (ProtoGameContext) → ProtoCombinedExport
|
||||||
|
- `/IoService/ExportFen` (ProtoGameContext) → ProtoStringResult
|
||||||
|
- `/IoService/ExportPgn` (ProtoGameContext) → ProtoStringResult
|
||||||
|
- `/RuleService/CandidateMoves` (ProtoSquareRequest) → ProtoMoveList
|
||||||
|
- `/RuleService/LegalMoves` (ProtoSquareRequest) → ProtoMoveList
|
||||||
|
- `/RuleService/AllLegalMoves` (ProtoGameContext) → ProtoMoveList
|
||||||
|
- `/RuleService/IsCheck` (ProtoGameContext) → ProtoBoolResult
|
||||||
|
- `/RuleService/IsCheckmate` (ProtoGameContext) → ProtoBoolResult
|
||||||
|
- `/RuleService/IsStalemate` (ProtoGameContext) → ProtoBoolResult
|
||||||
|
- `/RuleService/IsInsufficientMaterial` (ProtoGameContext) → ProtoBoolResult
|
||||||
|
- `/RuleService/IsFiftyMoveRule` (ProtoGameContext) → ProtoBoolResult
|
||||||
|
- `/RuleService/IsThreefoldRepetition` (ProtoGameContext) → ProtoBoolResult
|
||||||
|
- `/RuleService/ApplyMove` (ProtoMoveRequest) → ProtoGameContext
|
||||||
|
- `/RuleService/PostMoveStatus` (ProtoGameContext) → ProtoPostMoveStatus
|
||||||
|
- `/IoService/ImportFen` (ProtoImportFenRequest) → ProtoGameContext
|
||||||
|
- `/IoService/ImportPgn` (ProtoImportPgnRequest) → ProtoGameContext
|
||||||
|
- `/IoService/ExportCombined` (ProtoGameContext) → ProtoCombinedExport
|
||||||
|
- `/IoService/ExportFen` (ProtoGameContext) → ProtoStringResult
|
||||||
|
- `/IoService/ExportPgn` (ProtoGameContext) → ProtoStringResult
|
||||||
|
- `/RuleService/CandidateMoves` (ProtoSquareRequest) → ProtoMoveList
|
||||||
|
- `/RuleService/LegalMoves` (ProtoSquareRequest) → ProtoMoveList
|
||||||
|
- `/RuleService/AllLegalMoves` (ProtoGameContext) → ProtoMoveList
|
||||||
|
- `/RuleService/IsCheck` (ProtoGameContext) → ProtoBoolResult
|
||||||
|
- `/RuleService/IsCheckmate` (ProtoGameContext) → ProtoBoolResult
|
||||||
|
- `/RuleService/IsStalemate` (ProtoGameContext) → ProtoBoolResult
|
||||||
|
- `/RuleService/IsInsufficientMaterial` (ProtoGameContext) → ProtoBoolResult
|
||||||
|
- `/RuleService/IsFiftyMoveRule` (ProtoGameContext) → ProtoBoolResult
|
||||||
|
- `/RuleService/IsThreefoldRepetition` (ProtoGameContext) → ProtoBoolResult
|
||||||
|
- `/RuleService/ApplyMove` (ProtoMoveRequest) → ProtoGameContext
|
||||||
|
- `/RuleService/PostMoveStatus` (ProtoGameContext) → ProtoPostMoveStatus
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# NowChessSystems — Wiki
|
# NowChessSystems — Wiki
|
||||||
|
|
||||||
_Generated 2026-04-12 — re-run `npx codesight --wiki` if the codebase has changed._
|
_Generated 2026-04-23 — re-run `npx codesight --wiki` if the codebase has changed._
|
||||||
|
|
||||||
Structural map compiled from source code via AST. No LLM — deterministic, 200ms.
|
Structural map compiled from source code via AST. No LLM — deterministic, 200ms.
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ Structural map compiled from source code via AST. No LLM — deterministic, 200m
|
|||||||
- Routes: **0**
|
- Routes: **0**
|
||||||
- Models: **0**
|
- Models: **0**
|
||||||
- Components: **0**
|
- Components: **0**
|
||||||
- Env vars: **0** required, **0** with defaults
|
- Env vars: **1** required, **0** with defaults
|
||||||
|
|
||||||
## How to Use
|
## How to Use
|
||||||
|
|
||||||
@@ -41,4 +41,4 @@ These exist in your codebase but are **not** reflected in wiki articles:
|
|||||||
When in doubt, search the source. The wiki is a starting point, not a complete inventory.
|
When in doubt, search the source. The wiki is a starting point, not a complete inventory.
|
||||||
|
|
||||||
---
|
---
|
||||||
_Last compiled: 2026-04-12 · 2 articles · [codesight](https://github.com/Houseofmvps/codesight)_
|
_Last compiled: 2026-04-23 · 2 articles · [codesight](https://github.com/Houseofmvps/codesight)_
|
||||||
@@ -3,3 +3,5 @@
|
|||||||
History of `npx codesight --wiki` runs. Capped at 20 entries.
|
History of `npx codesight --wiki` runs. Capped at 20 entries.
|
||||||
|
|
||||||
## [2026-04-12 14:34:19] scan | 0 routes, 0 models, 0 components → 2 articles
|
## [2026-04-12 14:34:19] scan | 0 routes, 0 models, 0 components → 2 articles
|
||||||
|
|
||||||
|
## [2026-04-23 11:41:43] scan | 0 routes, 0 models, 0 components → 2 articles
|
||||||
|
|||||||
@@ -4,16 +4,24 @@
|
|||||||
|
|
||||||
**NowChessSystems** is a scala project built with raw-http.
|
**NowChessSystems** is a scala project built with raw-http.
|
||||||
|
|
||||||
|
## Scale
|
||||||
|
|
||||||
|
1 middleware layers · 1 environment variables
|
||||||
|
|
||||||
## High-Impact Files
|
## High-Impact Files
|
||||||
|
|
||||||
Changes to these files have the widest blast radius across the codebase:
|
Changes to these files have the widest blast radius across the codebase:
|
||||||
|
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala` — imported by **28** files
|
- `modules/api/src/main/scala/de/nowchess/api/game/GameContext.scala` — imported by **74** files
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Square.scala` — imported by **21** files
|
- `modules/api/src/main/scala/de/nowchess/api/board/Square.scala` — imported by **66** files
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Color.scala` — imported by **19** files
|
- `modules/api/src/main/scala/de/nowchess/api/move/Move.scala` — imported by **52** files
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/move/Move.scala` — imported by **14** files
|
- `modules/api/src/main/scala/de/nowchess/api/board/Color.scala` — imported by **42** files
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Board.scala` — imported by **13** files
|
- `modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala` — imported by **27** files
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/board/Piece.scala` — imported by **10** files
|
- `modules/api/src/main/scala/de/nowchess/api/board/Piece.scala` — imported by **21** files
|
||||||
|
|
||||||
|
## Required Environment Variables
|
||||||
|
|
||||||
|
- `STOCKFISH_PATH` — `modules/bot/python/nnue.py`
|
||||||
|
|
||||||
---
|
---
|
||||||
_Back to [index.md](./index.md) · Generated 2026-04-12_
|
_Back to [index.md](./index.md) · Generated 2026-04-23_
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
FROM mcr.microsoft.com/devcontainers/java:21-bookworm
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
postgresql-client \
|
||||||
|
redis-tools \
|
||||||
|
stockfish \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN mkdir -p /home/vscode/.gradle/wrapper/dists \
|
||||||
|
&& chown -R vscode:vscode /home/vscode/.gradle
|
||||||
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Devcontainer
|
||||||
|
|
||||||
|
Dieses Setup startet den NowChess-Workspace zusammen mit Redis und PostgreSQL.
|
||||||
|
|
||||||
|
## Enthaltene Services
|
||||||
|
- `workspace` – Scala/Gradle-Entwicklungscontainer
|
||||||
|
- `redis` – Redis 7.4
|
||||||
|
- `postgres` – PostgreSQL 16
|
||||||
|
|
||||||
|
## Wichtige Ports
|
||||||
|
- App-Services: `8080`, `8081`, `8082`, `8083`, `8084`, `8085`, `8086`, `9086`
|
||||||
|
- Redis auf dem Host: `16379`
|
||||||
|
- PostgreSQL auf dem Host: `15432`
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
- VS Code: Ordner in einem Dev Container öffnen
|
||||||
|
- IntelliJ: Dev Container / Docker-Compose-Workspace öffnen und den `workspace`-Dienst nutzen
|
||||||
|
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"name": "NowChessSystems",
|
||||||
|
"dockerComposeFile": ["docker-compose.yml"],
|
||||||
|
"service": "workspace",
|
||||||
|
"workspaceFolder": "/workspaces/NowChessSystems",
|
||||||
|
"shutdownAction": "stopCompose",
|
||||||
|
"overrideCommand": false,
|
||||||
|
"remoteUser": "vscode",
|
||||||
|
"forwardPorts": [8080, 8081, 8082, 8083, 8084, 8085, 8086, 9086],
|
||||||
|
"portsAttributes": {
|
||||||
|
"8080": {"label": "NowChess Core", "onAutoForward": "notify"},
|
||||||
|
"8081": {"label": "NowChess Io", "onAutoForward": "notify"},
|
||||||
|
"8082": {"label": "NowChess Rule", "onAutoForward": "notify"},
|
||||||
|
"8083": {"label": "NowChess Account", "onAutoForward": "notify"},
|
||||||
|
"8084": {"label": "NowChess WebSocket", "onAutoForward": "notify"},
|
||||||
|
"8085": {"label": "NowChess Store", "onAutoForward": "notify"},
|
||||||
|
"8086": {"label": "NowChess Coordinator HTTP", "onAutoForward": "notify"},
|
||||||
|
"9086": {"label": "NowChess Coordinator gRPC", "onAutoForward": "notify"}
|
||||||
|
},
|
||||||
|
"customizations": {
|
||||||
|
"vscode": {
|
||||||
|
"settings": {
|
||||||
|
"java.configuration.updateBuildConfiguration": "automatic",
|
||||||
|
"java.import.gradle.wrapper.enabled": true,
|
||||||
|
"files.watcherExclude": {
|
||||||
|
"**/build/**": true,
|
||||||
|
"**/.gradle/**": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extensions": [
|
||||||
|
"scala-lang.scala",
|
||||||
|
"redhat.java",
|
||||||
|
"vscjava.vscode-java-pack"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"postCreateCommand": "bash -lc './gradlew --no-daemon help >/dev/null'"
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
services:
|
||||||
|
workspace:
|
||||||
|
build:
|
||||||
|
context: ..
|
||||||
|
dockerfile: .devcontainer/Dockerfile
|
||||||
|
command: sleep infinity
|
||||||
|
init: true
|
||||||
|
volumes:
|
||||||
|
- gradle-cache:/home/vscode/.gradle
|
||||||
|
environment:
|
||||||
|
REDIS_HOST: redis
|
||||||
|
REDIS_PORT: 6379
|
||||||
|
REDIS_PREFIX: nowchess
|
||||||
|
DB_URL: jdbc:postgresql://postgres:5432/nowchess
|
||||||
|
DB_USER: nowchess
|
||||||
|
DB_PASSWORD: nowchess
|
||||||
|
CORE_SERVICE_URL: http://localhost:8080
|
||||||
|
INTERNAL_SECRET: dev-internal-secret
|
||||||
|
STOCKFISH_PATH: /usr/games/stockfish
|
||||||
|
NOWCHESS_COORDINATOR_ENABLED: "true"
|
||||||
|
dns:
|
||||||
|
- 8.8.8.8
|
||||||
|
- 1.1.1.1
|
||||||
|
depends_on:
|
||||||
|
redis:
|
||||||
|
condition: service_healthy
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: docker.io/redis:7.4-alpine
|
||||||
|
command: ["redis-server", "--appendonly", "yes", "--save", "60", "1"]
|
||||||
|
ports:
|
||||||
|
- "16379:6379"
|
||||||
|
volumes:
|
||||||
|
- redis-data:/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "redis-cli", "ping"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 20
|
||||||
|
start_period: 5s
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
image: docker.io/postgres:16-alpine
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: nowchess
|
||||||
|
POSTGRES_USER: nowchess
|
||||||
|
POSTGRES_PASSWORD: nowchess
|
||||||
|
ports:
|
||||||
|
- "15432:5432"
|
||||||
|
volumes:
|
||||||
|
- postgres-data:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 20
|
||||||
|
start_period: 10s
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
gradle-cache:
|
||||||
|
redis-data:
|
||||||
|
postgres-data:
|
||||||
|
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
name: Build & Push Native Image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-actor:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
allowed: ${{ steps.check.outputs.allowed }}
|
||||||
|
steps:
|
||||||
|
- id: check
|
||||||
|
run: |
|
||||||
|
if [[ "${{ github.event_name }}" == "workflow_dispatch" || "${{ github.actor }}" == "TeamCity" ]]; then
|
||||||
|
echo "allowed=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "allowed=false" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
build-and-push:
|
||||||
|
needs: check-actor
|
||||||
|
if: needs.check-actor.outputs.allowed == 'true'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
module:
|
||||||
|
- account
|
||||||
|
- bot-platform
|
||||||
|
- coordinator
|
||||||
|
- core
|
||||||
|
- io
|
||||||
|
- official-bots
|
||||||
|
- rule
|
||||||
|
- store
|
||||||
|
- ws
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up GraalVM
|
||||||
|
uses: graalvm/setup-graalvm@v1
|
||||||
|
with:
|
||||||
|
java-version: '21'
|
||||||
|
distribution: 'graalvm-community'
|
||||||
|
native-image-job-reports: 'true'
|
||||||
|
|
||||||
|
- name: Cache Gradle packages
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.gradle/caches
|
||||||
|
~/.gradle/wrapper
|
||||||
|
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
||||||
|
restore-keys: gradle-${{ runner.os }}-
|
||||||
|
|
||||||
|
- name: Build native binary
|
||||||
|
run: ./gradlew :modules:${{ matrix.module }}:build -Dquarkus.native.enabled=true -Dquarkus.package.jar.enabled=false --no-daemon
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Log in to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Extract metadata
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ghcr.io/now-chess/now-chess-systems/${{ matrix.module }}
|
||||||
|
tags: |
|
||||||
|
type=sha,prefix=,format=short
|
||||||
|
type=raw,value=latest
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: modules/${{ matrix.module }}/src/main/docker/Dockerfile.native
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: type=gha,scope=${{ matrix.module }}
|
||||||
|
cache-to: type=gha,mode=max,scope=${{ matrix.module }}
|
||||||
@@ -41,6 +41,9 @@ bin/
|
|||||||
graphify-out/
|
graphify-out/
|
||||||
.graphify_*.json
|
.graphify_*.json
|
||||||
|
|
||||||
|
### Keys ###
|
||||||
|
**/keys/private.pem
|
||||||
|
|
||||||
### Mac OS ###
|
### Mac OS ###
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/jacoco-reporter/.venv/
|
/jacoco-reporter/.venv/
|
||||||
|
|||||||
Generated
+2
@@ -8,3 +8,5 @@
|
|||||||
/dataSources.local.xml
|
/dataSources.local.xml
|
||||||
# Editor-based HTTP Client requests
|
# Editor-based HTTP Client requests
|
||||||
/httpRequests/
|
/httpRequests/
|
||||||
|
|
||||||
|
sonarlint.xml
|
||||||
|
|||||||
Generated
+133
@@ -0,0 +1,133 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<code_scheme name="Project" version="173">
|
||||||
|
<AndroidXmlCodeStyleSettings>
|
||||||
|
<option name="USE_CUSTOM_SETTINGS" value="true" />
|
||||||
|
</AndroidXmlCodeStyleSettings>
|
||||||
|
<JetCodeStyleSettings>
|
||||||
|
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||||
|
</JetCodeStyleSettings>
|
||||||
|
<ScalaCodeStyleSettings>
|
||||||
|
<option name="FORMATTER" value="1" />
|
||||||
|
</ScalaCodeStyleSettings>
|
||||||
|
<XML>
|
||||||
|
<option name="XML_KEEP_LINE_BREAKS" value="false" />
|
||||||
|
<option name="XML_ALIGN_ATTRIBUTES" value="false" />
|
||||||
|
<option name="XML_SPACE_INSIDE_EMPTY_TAG" value="true" />
|
||||||
|
</XML>
|
||||||
|
<codeStyleSettings language="XML">
|
||||||
|
<option name="FORCE_REARRANGE_MODE" value="1" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
</indentOptions>
|
||||||
|
<arrangement>
|
||||||
|
<rules>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>xmlns:android</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>xmlns:.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:id</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:name</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>name</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>style</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
</rules>
|
||||||
|
</arrangement>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="kotlin">
|
||||||
|
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||||
|
</codeStyleSettings>
|
||||||
|
</code_scheme>
|
||||||
|
</component>
|
||||||
Generated
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
<component name="ProjectCodeStyleConfiguration">
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
<state>
|
<state>
|
||||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||||
</state>
|
</state>
|
||||||
</component>
|
</component>
|
||||||
Generated
+18
File diff suppressed because one or more lines are too long
Generated
+9
-2
@@ -5,16 +5,23 @@
|
|||||||
<option name="linkedExternalProjectsSettings">
|
<option name="linkedExternalProjectsSettings">
|
||||||
<GradleProjectSettings>
|
<GradleProjectSettings>
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
<option name="gradleJvm" value="corretto-21" />
|
<option name="gradleJvm" value="ms-21" />
|
||||||
<option name="modules">
|
<option name="modules">
|
||||||
<set>
|
<set>
|
||||||
<option value="$PROJECT_DIR$" />
|
<option value="$PROJECT_DIR$" />
|
||||||
<option value="$PROJECT_DIR$/modules" />
|
<option value="$PROJECT_DIR$/modules" />
|
||||||
|
<option value="$PROJECT_DIR$/modules/account" />
|
||||||
<option value="$PROJECT_DIR$/modules/api" />
|
<option value="$PROJECT_DIR$/modules/api" />
|
||||||
|
<option value="$PROJECT_DIR$/modules/bot-platform" />
|
||||||
|
<option value="$PROJECT_DIR$/modules/coordinator" />
|
||||||
<option value="$PROJECT_DIR$/modules/core" />
|
<option value="$PROJECT_DIR$/modules/core" />
|
||||||
<option value="$PROJECT_DIR$/modules/io" />
|
<option value="$PROJECT_DIR$/modules/io" />
|
||||||
|
<option value="$PROJECT_DIR$/modules/json" />
|
||||||
|
<option value="$PROJECT_DIR$/modules/official-bots" />
|
||||||
<option value="$PROJECT_DIR$/modules/rule" />
|
<option value="$PROJECT_DIR$/modules/rule" />
|
||||||
<option value="$PROJECT_DIR$/modules/ui" />
|
<option value="$PROJECT_DIR$/modules/security" />
|
||||||
|
<option value="$PROJECT_DIR$/modules/store" />
|
||||||
|
<option value="$PROJECT_DIR$/modules/ws" />
|
||||||
</set>
|
</set>
|
||||||
</option>
|
</option>
|
||||||
</GradleProjectSettings>
|
</GradleProjectSettings>
|
||||||
|
|||||||
Generated
+2
-1
@@ -1,9 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
<component name="FrameworkDetectionExcludesConfiguration">
|
<component name="FrameworkDetectionExcludesConfiguration">
|
||||||
<file type="web" url="file://$PROJECT_DIR$" />
|
<file type="web" url="file://$PROJECT_DIR$" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="corretto-21" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="ms-21" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="NowChessSystems.modules.core.main" type="QuarkusRunConfigurationType" factoryName="Quarkus" nameIsGenerated="true">
|
||||||
|
<module name="NowChessSystems.modules.core.main" />
|
||||||
|
<QsGradleRunConfiguration>
|
||||||
|
<ExternalSystemSettings>
|
||||||
|
<option name="executionName" />
|
||||||
|
<option name="externalProjectPath" value="$PROJECT_DIR$/modules/core" />
|
||||||
|
<option name="externalSystemIdString" value="GRADLE" />
|
||||||
|
<option name="scriptParameters" />
|
||||||
|
<option name="taskDescriptions">
|
||||||
|
<list />
|
||||||
|
</option>
|
||||||
|
<option name="taskNames">
|
||||||
|
<list>
|
||||||
|
<option value="quarkusDev" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="vmOptions" />
|
||||||
|
</ExternalSystemSettings>
|
||||||
|
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
|
||||||
|
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||||
|
<ExternalSystemDebugDisabled>false</ExternalSystemDebugDisabled>
|
||||||
|
<DebugAllEnabled>false</DebugAllEnabled>
|
||||||
|
<RunAsTest>false</RunAsTest>
|
||||||
|
<GradleProfilingDisabled>false</GradleProfilingDisabled>
|
||||||
|
<GradleCoverageDisabled>false</GradleCoverageDisabled>
|
||||||
|
<profile>dev</profile>
|
||||||
|
</QsGradleRunConfiguration>
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
Generated
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
<option name="deprecationWarnings" value="true" />
|
<option name="deprecationWarnings" value="true" />
|
||||||
<option name="uncheckedWarnings" value="true" />
|
<option name="uncheckedWarnings" value="true" />
|
||||||
</profile>
|
</profile>
|
||||||
<profile name="Gradle 2" modules="NowChessSystems.modules.core.main,NowChessSystems.modules.core.scoverage,NowChessSystems.modules.core.test,NowChessSystems.modules.io.main,NowChessSystems.modules.io.scoverage,NowChessSystems.modules.io.test,NowChessSystems.modules.rule.main,NowChessSystems.modules.rule.scoverage,NowChessSystems.modules.rule.test,NowChessSystems.modules.ui.main,NowChessSystems.modules.ui.scoverage,NowChessSystems.modules.ui.test">
|
<profile name="Gradle 2" modules="NowChessSystems.modules.account.integrationTest,NowChessSystems.modules.account.main,NowChessSystems.modules.account.native-test,NowChessSystems.modules.account.quarkus-generated-sources,NowChessSystems.modules.account.quarkus-test-generated-sources,NowChessSystems.modules.account.scoverage,NowChessSystems.modules.account.test,NowChessSystems.modules.bot-platform.integrationTest,NowChessSystems.modules.bot-platform.main,NowChessSystems.modules.bot-platform.native-test,NowChessSystems.modules.bot-platform.quarkus-generated-sources,NowChessSystems.modules.bot-platform.quarkus-test-generated-sources,NowChessSystems.modules.bot-platform.scoverage,NowChessSystems.modules.bot-platform.test,NowChessSystems.modules.bot.main,NowChessSystems.modules.bot.scoverage,NowChessSystems.modules.bot.test,NowChessSystems.modules.coordinator.integrationTest,NowChessSystems.modules.coordinator.main,NowChessSystems.modules.coordinator.native-test,NowChessSystems.modules.coordinator.quarkus-generated-sources,NowChessSystems.modules.coordinator.quarkus-test-generated-sources,NowChessSystems.modules.coordinator.scoverage,NowChessSystems.modules.coordinator.test,NowChessSystems.modules.core.integrationTest,NowChessSystems.modules.core.main,NowChessSystems.modules.core.native-test,NowChessSystems.modules.core.quarkus-generated-sources,NowChessSystems.modules.core.quarkus-test-generated-sources,NowChessSystems.modules.core.scoverage,NowChessSystems.modules.core.test,NowChessSystems.modules.io.integrationTest,NowChessSystems.modules.io.main,NowChessSystems.modules.io.native-test,NowChessSystems.modules.io.quarkus-generated-sources,NowChessSystems.modules.io.quarkus-test-generated-sources,NowChessSystems.modules.io.scoverage,NowChessSystems.modules.io.test,NowChessSystems.modules.json.main,NowChessSystems.modules.json.scoverage,NowChessSystems.modules.json.test,NowChessSystems.modules.official-bots.integrationTest,NowChessSystems.modules.official-bots.main,NowChessSystems.modules.official-bots.native-test,NowChessSystems.modules.official-bots.quarkus-generated-sources,NowChessSystems.modules.official-bots.quarkus-test-generated-sources,NowChessSystems.modules.official-bots.scoverage,NowChessSystems.modules.official-bots.test,NowChessSystems.modules.rule.integrationTest,NowChessSystems.modules.rule.main,NowChessSystems.modules.rule.native-test,NowChessSystems.modules.rule.quarkus-generated-sources,NowChessSystems.modules.rule.quarkus-test-generated-sources,NowChessSystems.modules.rule.scoverage,NowChessSystems.modules.rule.test,NowChessSystems.modules.security.main,NowChessSystems.modules.security.scoverage,NowChessSystems.modules.security.test,NowChessSystems.modules.store.integrationTest,NowChessSystems.modules.store.main,NowChessSystems.modules.store.native-test,NowChessSystems.modules.store.quarkus-generated-sources,NowChessSystems.modules.store.quarkus-test-generated-sources,NowChessSystems.modules.store.scoverage,NowChessSystems.modules.store.test,NowChessSystems.modules.ui.main,NowChessSystems.modules.ui.scoverage,NowChessSystems.modules.ui.test,NowChessSystems.modules.ws.integrationTest,NowChessSystems.modules.ws.main,NowChessSystems.modules.ws.native-test,NowChessSystems.modules.ws.quarkus-generated-sources,NowChessSystems.modules.ws.quarkus-test-generated-sources,NowChessSystems.modules.ws.scoverage,NowChessSystems.modules.ws.test">
|
||||||
<option name="deprecationWarnings" value="true" />
|
<option name="deprecationWarnings" value="true" />
|
||||||
<option name="uncheckedWarnings" value="true" />
|
<option name="uncheckedWarnings" value="true" />
|
||||||
<parameters>
|
<parameters>
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ Scala 3.5.1 · Gradle 9
|
|||||||
./compile # Compile all modules — always run
|
./compile # Compile all modules — always run
|
||||||
./test # Run all tests
|
./test # Run all tests
|
||||||
./coverage # Check coverage
|
./coverage # Check coverage
|
||||||
|
./lint # Run linters
|
||||||
```
|
```
|
||||||
Try to stick to these commands for consistency.
|
Use consistently.
|
||||||
|
|
||||||
## Modules
|
## Modules
|
||||||
|
|
||||||
@@ -19,19 +20,19 @@ Try to stick to these commands for consistency.
|
|||||||
| `api` | Model / shared types | (none) |
|
| `api` | Model / shared types | (none) |
|
||||||
| `core` | Primary business logic | api, rule |
|
| `core` | Primary business logic | api, rule |
|
||||||
| `rule` | Game rules | api |
|
| `rule` | Game rules | api |
|
||||||
|
| `bot` | Bots and AI | api,rule,io |
|
||||||
| `io` | Export formats | api, core |
|
| `io` | Export formats | api, core |
|
||||||
| `ui` | Entrypoint & UI | core, io |
|
|
||||||
|
|
||||||
## Style
|
## Style
|
||||||
|
|
||||||
- Use immutable data and pure functions.
|
- Immutable data, pure functions.
|
||||||
- Keep functions under 30 lines. If you need "and" to describe it, split it.
|
- Functions under 30 lines. Need "and"? Split it.
|
||||||
- Keep cyclomatic complexity under 15.
|
- Cyclomatic complexity under 15.
|
||||||
- Avoid comments. Let names carry intent; comment only non-obvious algorithms.
|
- No comments. Names carry intent. Comment non-obvious algorithms only.
|
||||||
- Scan for duplicated logic before finishing. Extract it.
|
- Scan duplicated logic. Extract.
|
||||||
- Follow default Sonar style for Scala.
|
- Follow default Sonar style for Scala.
|
||||||
- Use `Option` or `Either` for fallible operations; avoid exceptions for control flow.
|
- `Option`/`Either` for fallible ops. Skip exceptions for control flow.
|
||||||
- Naming: types are PascalCase, functions/values are camelCase.
|
- Naming: types PascalCase, functions/values camelCase.
|
||||||
|
|
||||||
## Code Quality
|
## Code Quality
|
||||||
|
|
||||||
@@ -39,20 +40,23 @@ Try to stick to these commands for consistency.
|
|||||||
|
|
||||||
### Linters
|
### Linters
|
||||||
|
|
||||||
- **scalafmt** — enforces formatting; run `./gradlew spotlessScalaCheck` to check and `./gradlew spotlessScalaApply` to refactor.
|
- **scalafmt** — Enforces formatting. Check: `./gradlew spotlessScalaCheck`. Refactor: `./gradlew spotlessScalaApply`.
|
||||||
- **scalafix** — enforces style and detects unused imports/code; run `./gradlew scalafix` to apply rules.
|
- **scalafix** — Enforces style, detects unused imports/code. Run: `./gradlew scalafix`.
|
||||||
|
|
||||||
## Architecture Decisions
|
## Architecture Decisions
|
||||||
|
|
||||||
- **Immutable state as primary model:** GameContext (api) holds board, history, player state — immutable, passed through the system. Each move creates a new GameContext, enabling undo/redo without side effects.
|
- **Immutable state as primary model:** GameContext (api) holds board, history, player state—immutable throughout. Each move → new GameContext. Enables undo/redo without side effects.
|
||||||
- **Observer pattern for UI decoupling:** GameEngine publishes move/state events; CommandInvoker queues moves; UI listens to events, not polling. GameEngine never imports UI code.
|
- **Observer pattern for UI decoupling:** GameEngine publishes move/state events; CommandInvoker queues moves; UI listens (no polling). GameEngine never imports UI.
|
||||||
- **RuleSet trait encapsulates rules:** Move generation, check, castling, en passant all in RuleSet impl. GameEngine calls rules as a black box; rules don't know about the rest of core.
|
- **RuleSet trait encapsulates rules:** Move generation, check, castling, en passant all in RuleSet impl. GameEngine calls rules as black box; rules don't know rest of core.
|
||||||
|
- **Polyglot hash must follow spec index layout:** Piece keys use interleaved mapping `(pieceType * 2 + colorBit)` (black=0, white=1). Castling keys: `768..771`. En-passant file keys: `772..779`, XORed only if side-to-move has capturable en passant. Side-to-move key: `780` (white).
|
||||||
|
- **Alpha-beta uses sequential PV search by default:** Parallel split disabled (fixed-window futures removed pruning effectiveness). Sequential PV default. Correctness + pruning quality > speculative parallelism.
|
||||||
|
- **Search hash is updated incrementally per move:** Bot search updates Zobrist keys from parent hash with move deltas, not recomputing piece scans per node.
|
||||||
|
|
||||||
## Rules
|
## Rules
|
||||||
|
|
||||||
- **Tests are the spec.** Never modify tests to pass; modify requirements or code. Update tests only if requirements change.
|
- **Tests are the spec.** Don't modify to pass. Fix requirements/code. Update only if requirements change.
|
||||||
- Never read build folders. Ask permission if needed.
|
- Never read build folders. Ask permission if needed.
|
||||||
- Keep this file up to date with any important decisions or conventions.
|
- Keep file current with decisions + conventions.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -60,11 +64,9 @@ Try to stick to these commands for consistency.
|
|||||||
|
|
||||||
### Two-Step Rule (mandatory)
|
### Two-Step Rule (mandatory)
|
||||||
**Step 1 — Orient:** Use wiki articles to find WHERE things live.
|
**Step 1 — Orient:** Use wiki articles to find WHERE things live.
|
||||||
**Step 2 — Verify:** Read the actual source files listed in the wiki article BEFORE writing any code.
|
**Step 2 — Verify:** Read source files from wiki BEFORE coding.
|
||||||
|
|
||||||
Wiki articles are structural summaries extracted by AST. They show routes, models, and file locations.
|
Wiki = structural summaries (routes, models, file locations). No function logic, middleware internals, runtime behavior. Don't code from wiki alone—read sources.
|
||||||
They do NOT show full function logic, middleware internals, or dynamic runtime behavior.
|
|
||||||
**Never write or modify code based solely on wiki content — always read source files first.**
|
|
||||||
|
|
||||||
Read in order at session start:
|
Read in order at session start:
|
||||||
1. `.codesight/wiki/index.md` — orientation map (~200 tokens)
|
1. `.codesight/wiki/index.md` — orientation map (~200 tokens)
|
||||||
@@ -72,8 +74,7 @@ Read in order at session start:
|
|||||||
3. Domain article (e.g. `.codesight/wiki/auth.md`) → check "Source Files" section → read those files
|
3. Domain article (e.g. `.codesight/wiki/auth.md`) → check "Source Files" section → read those files
|
||||||
4. `.codesight/CODESIGHT.md` — full context map for deep exploration
|
4. `.codesight/CODESIGHT.md` — full context map for deep exploration
|
||||||
|
|
||||||
Routes marked `[inferred]` in wiki articles were detected via regex — verify against source before trusting.
|
`[inferred]` routes = regex-detected. Verify sources. ⚠ in wiki? Re-run `codesight --wiki`.
|
||||||
If any source file shows ⚠ in the wiki, re-run `codesight --wiki` before proceeding.
|
|
||||||
|
|
||||||
Or use the codesight MCP server for on-demand queries:
|
Or use the codesight MCP server for on-demand queries:
|
||||||
- `codesight_get_wiki_article` — read a specific wiki article by name
|
- `codesight_get_wiki_article` — read a specific wiki article by name
|
||||||
@@ -83,13 +84,13 @@ Or use the codesight MCP server for on-demand queries:
|
|||||||
- `codesight_get_blast_radius --file src/lib/db.ts` — impact analysis before changes
|
- `codesight_get_blast_radius --file src/lib/db.ts` — impact analysis before changes
|
||||||
- `codesight_get_schema --model users` — specific model details
|
- `codesight_get_schema --model users` — specific model details
|
||||||
|
|
||||||
Only open specific files after consulting codesight context. This saves ~16.893 tokens per conversation.
|
Consult codesight context first. Saves ~16.893 tokens/conversation.
|
||||||
|
|
||||||
## graphify
|
## graphify
|
||||||
|
|
||||||
This project has a graphify knowledge graph at graphify-out/.
|
graphify knowledge graph at graphify-out/.
|
||||||
|
|
||||||
Rules:
|
Rules:
|
||||||
- Before answering architecture or codebase questions, read graphify-out/GRAPH_REPORT.md for god nodes and community structure
|
- Architecture/codebase questions? Read graphify-out/GRAPH_REPORT.md (god nodes, communities).
|
||||||
- If graphify-out/wiki/index.md exists, navigate it instead of reading raw files
|
- graphify-out/wiki/index.md exists? Use it (not raw files).
|
||||||
- After modifying code files in this session, run `python3 -c "from graphify.watch import _rebuild_code; from pathlib import Path; _rebuild_code(Path('.'))"` to keep the graph current
|
- Code modified? Run `python3 -c "from graphify.watch import _rebuild_code; from pathlib import Path; _rebuild_code(Path('.'))"` to sync graph.
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
# Now-Chess
|
||||||
|
|
||||||
|
Scala 3.5.1 · Gradle 9
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
```
|
||||||
|
./clean # Clear build dirs — only when necessary
|
||||||
|
./compile # Compile all modules — always run
|
||||||
|
./test # Run all tests
|
||||||
|
./coverage # Check coverage
|
||||||
|
./lint # Run linters
|
||||||
|
```
|
||||||
|
Try to stick to these commands for consistency.
|
||||||
|
|
||||||
|
## Modules
|
||||||
|
|
||||||
|
| Module | Role | Depends on |
|
||||||
|
|--------|------|-----------|
|
||||||
|
| `api` | Model / shared types | (none) |
|
||||||
|
| `core` | Primary business logic | api, rule |
|
||||||
|
| `rule` | Game rules | api |
|
||||||
|
| `bot` | Bots and AI | api,rule,io |
|
||||||
|
| `io` | Export formats | api, core |
|
||||||
|
|
||||||
|
## Style
|
||||||
|
|
||||||
|
- Use immutable data and pure functions.
|
||||||
|
- Keep functions under 30 lines. If you need "and" to describe it, split it.
|
||||||
|
- Keep cyclomatic complexity under 15.
|
||||||
|
- Avoid comments. Let names carry intent; comment only non-obvious algorithms.
|
||||||
|
- Scan for duplicated logic before finishing. Extract it.
|
||||||
|
- Follow default Sonar style for Scala.
|
||||||
|
- Use `Option` or `Either` for fallible operations; avoid exceptions for control flow.
|
||||||
|
- Naming: types are PascalCase, functions/values are camelCase.
|
||||||
|
|
||||||
|
## Code Quality
|
||||||
|
|
||||||
|
- **Coverage:** 100% condition coverage required in `api`, `core`, `rule`, `io` (mandatory); `ui` exempt.
|
||||||
|
|
||||||
|
### Linters
|
||||||
|
|
||||||
|
- **scalafmt** — enforces formatting; run `./gradlew spotlessScalaCheck` to check and `./gradlew spotlessScalaApply` to refactor.
|
||||||
|
- **scalafix** — enforces style and detects unused imports/code; run `./gradlew scalafix` to apply rules.
|
||||||
|
|
||||||
|
## Architecture Decisions
|
||||||
|
|
||||||
|
- **Immutable state as primary model:** GameContext (api) holds board, history, player state — immutable, passed through the system. Each move creates a new GameContext, enabling undo/redo without side effects.
|
||||||
|
- **Observer pattern for UI decoupling:** GameEngine publishes move/state events; CommandInvoker queues moves; UI listens to events, not polling. GameEngine never imports UI code.
|
||||||
|
- **RuleSet trait encapsulates rules:** Move generation, check, castling, en passant all in RuleSet impl. GameEngine calls rules as a black box; rules don't know about the rest of core.
|
||||||
|
- **Polyglot hash must follow spec index layout:** piece keys use interleaved mapping `(pieceType * 2 + colorBit)` with black=0/white=1, castling keys are `768..771`, en-passant file keys are `772..779` and are XORed only if side-to-move has a pawn that can capture en passant, side-to-move key is `780` for white.
|
||||||
|
- **Alpha-beta uses sequential PV search by default:** parallel split was disabled because fixed-window futures removed pruning effectiveness; correctness and pruning quality take priority over speculative parallelism.
|
||||||
|
- **Search hash is updated incrementally per move:** bot search now updates Zobrist keys from parent hash with move deltas instead of recomputing piece scans at every node.
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
- **Tests are the spec.** Never modify tests to pass; modify requirements or code. Update tests only if requirements change.
|
||||||
|
- Never read build folders. Ask permission if needed.
|
||||||
|
- Keep this file up to date with any important decisions or conventions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Instructions for Claude Code
|
||||||
|
|
||||||
|
### Two-Step Rule (mandatory)
|
||||||
|
**Step 1 — Orient:** Use wiki articles to find WHERE things live.
|
||||||
|
**Step 2 — Verify:** Read the actual source files listed in the wiki article BEFORE writing any code.
|
||||||
|
|
||||||
|
Wiki articles are structural summaries extracted by AST. They show routes, models, and file locations.
|
||||||
|
They do NOT show full function logic, middleware internals, or dynamic runtime behavior.
|
||||||
|
**Never write or modify code based solely on wiki content — always read source files first.**
|
||||||
|
|
||||||
|
Read in order at session start:
|
||||||
|
1. `.codesight/wiki/index.md` — orientation map (~200 tokens)
|
||||||
|
2. `.codesight/wiki/overview.md` — architecture overview (~500 tokens)
|
||||||
|
3. Domain article (e.g. `.codesight/wiki/auth.md`) → check "Source Files" section → read those files
|
||||||
|
4. `.codesight/CODESIGHT.md` — full context map for deep exploration
|
||||||
|
|
||||||
|
Routes marked `[inferred]` in wiki articles were detected via regex — verify against source before trusting.
|
||||||
|
If any source file shows ⚠ in the wiki, re-run `codesight --wiki` before proceeding.
|
||||||
|
|
||||||
|
Or use the codesight MCP server for on-demand queries:
|
||||||
|
- `codesight_get_wiki_article` — read a specific wiki article by name
|
||||||
|
- `codesight_get_wiki_index` — get the wiki index
|
||||||
|
- `codesight_get_summary` — quick project overview
|
||||||
|
- `codesight_get_routes --prefix /api/users` — filtered routes
|
||||||
|
- `codesight_get_blast_radius --file src/lib/db.ts` — impact analysis before changes
|
||||||
|
- `codesight_get_schema --model users` — specific model details
|
||||||
|
|
||||||
|
Only open specific files after consulting codesight context. This saves ~16.893 tokens per conversation.
|
||||||
|
|
||||||
|
## graphify
|
||||||
|
|
||||||
|
This project has a graphify knowledge graph at graphify-out/.
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
- Before answering architecture or codebase questions, read graphify-out/GRAPH_REPORT.md for god nodes and community structure
|
||||||
|
- If graphify-out/wiki/index.md exists, navigate it instead of reading raw files
|
||||||
|
- After modifying code files in this session, run `python3 -c "from graphify.watch import _rebuild_code; from pathlib import Path; _rebuild_code(Path('.'))"` to keep the graph current
|
||||||
@@ -0,0 +1,334 @@
|
|||||||
|
# Plan: Add Coordinator Microservice
|
||||||
|
|
||||||
|
## Context
|
||||||
|
NowChess scales `core` horizontally via shared Redis but lacks:
|
||||||
|
- **Instance visibility**: no way to list running cores or their load
|
||||||
|
- **Load balancing**: games land randomly on cores; no rebalancing
|
||||||
|
- **Failover**: dead cores orphan subscriptions; bullet chess requires <1s recovery
|
||||||
|
- **Auto-scaling**: manual ops to add/remove cores
|
||||||
|
- **Cache management**: no eviction of stale games from core memory
|
||||||
|
|
||||||
|
Bullet chess games run on move timings of <3s. 30s failover = game lost on clock. Target: **<300ms failover**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture: Sub-1s Failover
|
||||||
|
|
||||||
|
### Why Not Polling/TTL
|
||||||
|
- TTL expiry: minimum 10-30s detection
|
||||||
|
- HTTP polling 3x failure: 30s minimum
|
||||||
|
- **gRPC streaming TCP drop: 50-200ms** — use this as primary
|
||||||
|
|
||||||
|
### Primary: gRPC Bidirectional Streaming
|
||||||
|
- Core opens a **persistent bidirectional stream** (`CoreHeartbeatStream`) to coordinator on startup
|
||||||
|
- Core sends heartbeat frames every **200ms**
|
||||||
|
- Core crash = TCP RST/FIN → coordinator stream error in **~50-200ms**
|
||||||
|
- Stream also carries metadata updates (subscription count changes) in real-time
|
||||||
|
|
||||||
|
### Fallback: Redis Heartbeat + K8s Watch
|
||||||
|
- Redis heartbeat key `{prefix}:instances:{instanceId}` with **5s TTL**, refreshed every **2s**
|
||||||
|
- K8s pod watch via Kubernetes Java client (event-driven; handles pod eviction/OOMKill)
|
||||||
|
- Fallback covers: network partition (TCP stays up but core is zombie), coordinator restart gap
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Design
|
||||||
|
|
||||||
|
### 1. Module: `modules/coordinator`
|
||||||
|
**Language**: Scala 3.5.1, Quarkus REST + gRPC
|
||||||
|
**Ports**: HTTP 8086, gRPC 9086
|
||||||
|
**Dependencies**: Redisson, Kubernetes Java client, Quarkus gRPC
|
||||||
|
**Persistence**: None (all state in Redis)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Instance Registry
|
||||||
|
|
||||||
|
**Redis schema**:
|
||||||
|
```
|
||||||
|
{prefix}:instances:{instanceId}
|
||||||
|
- TTL: 5s (refreshed by core every 2s via background task)
|
||||||
|
- Value: JSON
|
||||||
|
{
|
||||||
|
"instanceId": "core-abc123",
|
||||||
|
"hostname": "core-pod-3",
|
||||||
|
"httpPort": 8080,
|
||||||
|
"grpcPort": 9080,
|
||||||
|
"subscriptionCount": 147,
|
||||||
|
"localCacheSize": 147,
|
||||||
|
"lastHeartbeat": "2026-04-26T10:15:30.123Z"
|
||||||
|
}
|
||||||
|
|
||||||
|
{prefix}:instance:{instanceId}:games
|
||||||
|
- Type: Redis Set (no TTL — managed explicitly)
|
||||||
|
- Members: all gameIds currently subscribed on this instance
|
||||||
|
```
|
||||||
|
|
||||||
|
**Core changes** (new `InstanceHeartbeatService` bean in `modules/core`):
|
||||||
|
- `@PostConstruct`: generate stable `instanceId` (hostname + random suffix); open gRPC stream to coordinator; publish Redis heartbeat; register in `{prefix}:instances:{instanceId}`
|
||||||
|
- Every 200ms: send heartbeat frame on gRPC stream (carries `subscriptionCount`)
|
||||||
|
- Every 2s: refresh Redis heartbeat bucket TTL
|
||||||
|
- `subscribeGame(gameId)`: `SADD {prefix}:instance:{instanceId}:games gameId`
|
||||||
|
- `unsubscribeGame(gameId)` / `evictGame(gameId)`: `SREM {prefix}:instance:{instanceId}:games gameId`
|
||||||
|
- `@PreDestroy`: delete Redis key + games set; close gRPC stream (clean shutdown)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Health Monitoring (3 signals, primary fast)
|
||||||
|
|
||||||
|
| Signal | Mechanism | Detection time | Role |
|
||||||
|
|--------|-----------|---------------|------|
|
||||||
|
| **gRPC stream drop** | TCP RST/FIN on bidirectional stream | 50–200ms | Primary |
|
||||||
|
| **Redis heartbeat expiry** | `{prefix}:instances:{instanceId}` TTL=5s | 5–7s | Fallback |
|
||||||
|
| **K8s pod watch** | `CoreV1Api.listNamespacedPod` watch stream | ~instant (pod events) | Fallback |
|
||||||
|
|
||||||
|
**Dead decision**:
|
||||||
|
- gRPC stream drops → **immediate failover** (no confirmation needed; games must recover fast)
|
||||||
|
- Redis heartbeat expires (gRPC still up) → verify with single HTTP `/q/health` call → if fail: failover
|
||||||
|
- K8s pod NotReady (gRPC still up) → failover
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. Failover Protocol (<300ms target)
|
||||||
|
|
||||||
|
```
|
||||||
|
T+0ms Core JVM crashes / network drops
|
||||||
|
T+50ms Coordinator: gRPC stream error received
|
||||||
|
T+52ms SMEMBERS {prefix}:instance:{instanceId}:games → list of orphaned gameIds
|
||||||
|
T+55ms Distribute gameIds across healthy cores (least-loaded first)
|
||||||
|
T+60ms BatchResubscribeGames gRPC call(s) fire to healthy core(s)
|
||||||
|
T+150ms Healthy cores resubscribed; Redis s2c topics live again
|
||||||
|
T+200ms WebSocket clients reconnect; receive GameFullEventDto on CONNECTED
|
||||||
|
```
|
||||||
|
|
||||||
|
**Failover steps** (coordinator `FailoverService`):
|
||||||
|
1. On stream drop for `instanceId`:
|
||||||
|
a. Mark instance DEAD in local map
|
||||||
|
b. `SMEMBERS {prefix}:instance:{instanceId}:games`
|
||||||
|
c. Group gameIds into batches per target core (round-robin by load)
|
||||||
|
d. For each target core: call `BatchResubscribeGames(gameIds)`
|
||||||
|
e. Each target core: calls `subscribeGame(gameId)` for each (loads from Redis if not in local cache)
|
||||||
|
f. `DEL {prefix}:instance:{instanceId}:games` (cleanup)
|
||||||
|
2. Log failover event with count of games migrated + latency
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. Load Rebalancing
|
||||||
|
|
||||||
|
**Thresholds** (both must be evaluated):
|
||||||
|
1. **Absolute**: any core > 500 games → rebalance
|
||||||
|
2. **Relative**: max load > mean × 1.2 AND max - min > 50 games → rebalance
|
||||||
|
|
||||||
|
**Algorithm** (runs every 30s, min 60s between actual rebalances):
|
||||||
|
1. Read all `{prefix}:instances:*` keys → load map
|
||||||
|
2. Identify overloaded cores (exceed either threshold)
|
||||||
|
3. For each overloaded core: pick `excess = load - targetLoad` games
|
||||||
|
4. Assign excess games to underloaded cores
|
||||||
|
5. Call `UnsubscribeGames(gameIds)` on overloaded core
|
||||||
|
6. Call `BatchResubscribeGames(gameIds)` on target core
|
||||||
|
7. Overloaded core: `SREM` each game from its set
|
||||||
|
8. Target core: `SADD` each game to its set on subscribe
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. Auto-Scaling
|
||||||
|
|
||||||
|
**Metric**: avg `subscriptionCount` across all cores
|
||||||
|
|
||||||
|
**Actions**:
|
||||||
|
- avg > `scale-up-threshold` (80% of max): patch `nowchess-core` Argo Rollout `spec.replicas += 1`
|
||||||
|
- avg < `scale-down-threshold` (30% of max) AND `replicas > min-replicas`: drain one core then scale down
|
||||||
|
- Backoff: min 2-minute interval between scale events
|
||||||
|
|
||||||
|
**Argo Rollouts API**:
|
||||||
|
- CRD: `argoproj.io/v1alpha1`, Kind: `Rollout`, resource: `rollouts`
|
||||||
|
- Scale via Fabric8 `GenericKubernetesResource` patch on `spec.replicas`
|
||||||
|
- No StatefulSet — Argo Rollout owns pod lifecycle (canary/blue-green strategies respected)
|
||||||
|
- Pod watch filter: label selector `app=nowchess-core` (Rollout sets this; `rollouts-pod-template-hash` is Argo's equivalent of `pod-template-hash`)
|
||||||
|
|
||||||
|
**Drain before scale-down**:
|
||||||
|
1. Pick least-loaded core
|
||||||
|
2. Migrate all its games to other cores via `BatchResubscribeGames`
|
||||||
|
3. Call `DrainInstance(instanceId)` on that core (sets it to reject new subscriptions)
|
||||||
|
4. After drain confirmed: patch Rollout `spec.replicas -= 1`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 7. Cache Eviction
|
||||||
|
|
||||||
|
**Trigger**: coordinator scans `{prefix}:game:entry:*` every 10 minutes
|
||||||
|
**Policy**: if `now - lastUpdated > 45min` AND `gameId` in any instance's games set → call `EvictGame`
|
||||||
|
**Effect**: core removes game from `localEngines` and `unsubscribeGame`, `SREM` from instance set
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 8. Proto: `coordinator_service.proto`
|
||||||
|
|
||||||
|
```proto
|
||||||
|
syntax = "proto3";
|
||||||
|
package de.nowchess.coordinator;
|
||||||
|
|
||||||
|
service CoordinatorService {
|
||||||
|
// Core → Coordinator: bidirectional stream for liveness
|
||||||
|
rpc HeartbeatStream(stream HeartbeatFrame) returns (stream CoordinatorCommand);
|
||||||
|
|
||||||
|
// Coordinator → Core: batch resubscribe after failover or rebalance
|
||||||
|
rpc BatchResubscribeGames(BatchResubscribeRequest) returns (BatchResubscribeResponse);
|
||||||
|
|
||||||
|
// Coordinator → Core: unsubscribe games (rebalance source)
|
||||||
|
rpc UnsubscribeGames(UnsubscribeGamesRequest) returns (UnsubscribeGamesResponse);
|
||||||
|
|
||||||
|
// Coordinator → Core: evict idle games from local cache
|
||||||
|
rpc EvictGames(EvictGamesRequest) returns (EvictGamesResponse);
|
||||||
|
|
||||||
|
// Coordinator → Core: drain instance before scale-down
|
||||||
|
rpc DrainInstance(DrainInstanceRequest) returns (DrainInstanceResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
message HeartbeatFrame {
|
||||||
|
string instanceId = 1;
|
||||||
|
string hostname = 2;
|
||||||
|
int32 httpPort = 3;
|
||||||
|
int32 grpcPort = 4;
|
||||||
|
int32 subscriptionCount = 5;
|
||||||
|
int32 localCacheSize = 6;
|
||||||
|
int64 timestampMillis = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CoordinatorCommand {
|
||||||
|
// Future: coordinator can push commands back (e.g., "start draining")
|
||||||
|
string type = 1;
|
||||||
|
string payload = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BatchResubscribeRequest {
|
||||||
|
repeated string gameIds = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BatchResubscribeResponse {
|
||||||
|
int32 subscribedCount = 1;
|
||||||
|
repeated string failedGameIds = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message UnsubscribeGamesRequest {
|
||||||
|
repeated string gameIds = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message UnsubscribeGamesResponse {
|
||||||
|
int32 unsubscribedCount = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message EvictGamesRequest {
|
||||||
|
repeated string gameIds = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message EvictGamesResponse {
|
||||||
|
int32 evictedCount = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DrainInstanceRequest {}
|
||||||
|
|
||||||
|
message DrainInstanceResponse {
|
||||||
|
int32 gamesMigrated = 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 9. Coordinator REST API (internal)
|
||||||
|
|
||||||
|
- `GET /api/coordinator/instances` — all cores with load, health state
|
||||||
|
- `GET /api/coordinator/metrics` — load distribution, rebalance history
|
||||||
|
- `POST /api/coordinator/rebalance` — manual rebalance trigger
|
||||||
|
- `POST /api/coordinator/failover/{instanceId}` — manual failover
|
||||||
|
- `POST /api/coordinator/scale-up` / `scale-down` — manual scaling
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 10. Configuration
|
||||||
|
|
||||||
|
**`modules/coordinator/src/main/resources/application.yml`**:
|
||||||
|
```yaml
|
||||||
|
quarkus.application.name: nowchess-coordinator
|
||||||
|
quarkus.http.port: 8086
|
||||||
|
quarkus.grpc.server.port: 9086
|
||||||
|
|
||||||
|
nowchess.coordinator.max-games-per-core: 500
|
||||||
|
nowchess.coordinator.max-deviation-percent: 20
|
||||||
|
nowchess.coordinator.rebalance-interval: 30s
|
||||||
|
nowchess.coordinator.rebalance-min-interval: 60s
|
||||||
|
nowchess.coordinator.heartbeat-ttl: 5s
|
||||||
|
nowchess.coordinator.stream-heartbeat-interval: 200ms
|
||||||
|
nowchess.coordinator.cache-eviction-interval: 10m
|
||||||
|
nowchess.coordinator.game-idle-threshold: 45m
|
||||||
|
nowchess.coordinator.auto-scale-enabled: false
|
||||||
|
nowchess.coordinator.scale-up-threshold: 0.8
|
||||||
|
nowchess.coordinator.scale-down-threshold: 0.3
|
||||||
|
nowchess.coordinator.scale-min-replicas: 2
|
||||||
|
nowchess.coordinator.scale-max-replicas: 10
|
||||||
|
nowchess.coordinator.k8s-namespace: default
|
||||||
|
nowchess.coordinator.k8s-rollout-name: nowchess-core
|
||||||
|
nowchess.coordinator.k8s-rollout-label-selector: app=nowchess-core
|
||||||
|
|
||||||
|
quarkus.kubernetes-client.trust-certs: true
|
||||||
|
```
|
||||||
|
|
||||||
|
**Core `application.yml` additions**:
|
||||||
|
```yaml
|
||||||
|
nowchess.coordinator.host: localhost
|
||||||
|
nowchess.coordinator.grpc-port: 9086
|
||||||
|
nowchess.coordinator.stream-heartbeat-interval: 200ms
|
||||||
|
nowchess.coordinator.redis-heartbeat-interval: 2s
|
||||||
|
nowchess.coordinator.instance-id: ${HOSTNAME:local}-${quarkus.uuid}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 11. Files to Create / Modify
|
||||||
|
|
||||||
|
**New — `modules/coordinator/`**:
|
||||||
|
```
|
||||||
|
build.gradle.kts
|
||||||
|
src/main/proto/coordinator_service.proto
|
||||||
|
src/main/resources/application.yml
|
||||||
|
src/main/scala/de/nowchess/coordinator/
|
||||||
|
resource/CoordinatorResource.scala # REST endpoints
|
||||||
|
service/InstanceRegistry.scala # Redis instance list + in-memory map
|
||||||
|
service/HealthMonitor.scala # gRPC stream watcher + Redis TTL + k8s watch
|
||||||
|
service/FailoverService.scala # dead core → BatchResubscribe
|
||||||
|
service/LoadBalancer.scala # rebalance logic
|
||||||
|
service/AutoScaler.scala # k8s StatefulSet scaling
|
||||||
|
service/CacheEvictionManager.scala # idle game eviction
|
||||||
|
grpc/CoordinatorGrpcServer.scala # CoordinatorService gRPC impl (for HeartbeatStream)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Modify — `modules/core/`**:
|
||||||
|
- `build.gradle.kts` — add `coordinator_service.proto` stub, keep grpc dep
|
||||||
|
- `src/main/proto/coordinator_service.proto` — copy (or symlink) proto for stub generation
|
||||||
|
- `src/main/scala/de/nowchess/chess/redis/GameRedisSubscriberManager.scala` — `SADD`/`SREM` on subscribe/unsubscribe + implement `BatchResubscribeGames`, `UnsubscribeGames`, `EvictGames`, `DrainInstance` gRPC handlers
|
||||||
|
- `src/main/scala/de/nowchess/chess/` — new `InstanceHeartbeatService.scala` (startup, gRPC stream, Redis TTL refresh)
|
||||||
|
- `src/main/resources/application.yml` — coordinator connection config
|
||||||
|
|
||||||
|
**Modify — root**:
|
||||||
|
- `settings.gradle.kts` — add `include("modules/coordinator")`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
1. `./compile` — coordinator and core compile cleanly
|
||||||
|
2. **Stream detection**: start core + coordinator; kill core JVM (`kill -9`); coordinator logs failover within 300ms
|
||||||
|
3. **Game continuity**: active game on killed core; WebSocket client reconnects and receives game state
|
||||||
|
4. **Rebalance**: create 600 games on core-1 (2-core setup); coordinator rebalances ~100 to core-2
|
||||||
|
5. **Fallback**: disconnect gRPC stream manually but keep core alive; Redis TTL fallback triggers within 7s
|
||||||
|
6. **Cache eviction**: create idle game; coordinator calls `EvictGames` after 45min idle
|
||||||
|
7. **REST metrics**: `curl localhost:8086/api/coordinator/metrics` returns per-core load + health
|
||||||
|
8. **Restart recovery**: restart coordinator; gRPC streams re-establish from cores; state rebuilt from Redis
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dependencies (new)
|
||||||
|
|
||||||
|
- `io.fabric8:kubernetes-client:6.13.0` (Fabric8 k8s client — handles Argo `Rollout` CRD via `GenericKubernetesResource`; no Argo Java SDK needed)
|
||||||
|
- Redisson — already in core, reuse via shared config
|
||||||
|
- Quarkus gRPC — already in core, reuse
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
meta {
|
||||||
|
name: Offer Draw
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
method: POST
|
||||||
|
url: {{baseUrl}}/api/board/game/{{gameId}}/draw/offer
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
meta {
|
||||||
|
name: Accept Draw
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
method: POST
|
||||||
|
url: {{baseUrl}}/api/board/game/{{gameId}}/draw/accept
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
meta {
|
||||||
|
name: Decline Draw
|
||||||
|
type: http
|
||||||
|
seq: 3
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
method: POST
|
||||||
|
url: {{baseUrl}}/api/board/game/{{gameId}}/draw/decline
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
meta {
|
||||||
|
name: Claim Draw
|
||||||
|
type: http
|
||||||
|
seq: 4
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
method: POST
|
||||||
|
url: {{baseUrl}}/api/board/game/{{gameId}}/draw/claim
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
meta {
|
||||||
|
name: draw
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
meta {
|
||||||
|
name: Export FEN
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
method: GET
|
||||||
|
url: {{baseUrl}}/api/board/game/{{gameId}}/export/fen
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
meta {
|
||||||
|
name: Export PGN
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
method: GET
|
||||||
|
url: {{baseUrl}}/api/board/game/{{gameId}}/export/pgn
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
meta {
|
||||||
|
name: export
|
||||||
|
seq: 6
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
meta {
|
||||||
|
name: Create Game
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
method: POST
|
||||||
|
url: {{baseUrl}}/api/board/game
|
||||||
|
body: json
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
Content-Type: application/json
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"white": {"id": "p1", "displayName": "Alice"},
|
||||||
|
"black": {"id": "p2", "displayName": "Bob"},
|
||||||
|
"timeControl": {
|
||||||
|
"limitSeconds": 300,
|
||||||
|
"incrementSeconds": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
meta {
|
||||||
|
name: Get Game
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{baseUrl}}/api/board/game/{{gameId}}
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
vars:pre-request {
|
||||||
|
gameId: j0nPtcjl
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
meta {
|
||||||
|
name: Stream Game
|
||||||
|
type: ws
|
||||||
|
seq: 3
|
||||||
|
}
|
||||||
|
|
||||||
|
ws {
|
||||||
|
url: {{wsBaseUrl}}/api/board/game/{{gameId}}/ws
|
||||||
|
body: ws
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
body:ws {
|
||||||
|
name: move
|
||||||
|
content: '''
|
||||||
|
{
|
||||||
|
"type": "MOVE",
|
||||||
|
"uci": "b1c3"
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
|
||||||
|
body:ws {
|
||||||
|
name: ping
|
||||||
|
content: '''
|
||||||
|
{
|
||||||
|
"type": "PING"
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
|
||||||
|
vars:pre-request {
|
||||||
|
gameId: j0nPtcjl
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
meta {
|
||||||
|
name: Resign
|
||||||
|
type: http
|
||||||
|
seq: 4
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
method: POST
|
||||||
|
url: {{baseUrl}}/api/board/game/{{gameId}}/resign
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
meta {
|
||||||
|
name: game
|
||||||
|
seq: 3
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
meta {
|
||||||
|
name: Import FEN
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
method: POST
|
||||||
|
url: {{baseUrl}}/api/board/game/import/fen
|
||||||
|
body: json
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
Content-Type: application/json
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"fen": "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1",
|
||||||
|
"white": {"id": "p1", "displayName": "Alice"},
|
||||||
|
"black": {"id": "p2", "displayName": "Bob"},
|
||||||
|
"timeControl": {
|
||||||
|
"limitSeconds": 300,
|
||||||
|
"incrementSeconds": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
meta {
|
||||||
|
name: Import PGN
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
method: POST
|
||||||
|
url: {{baseUrl}}/api/board/game/import/pgn
|
||||||
|
body: json
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
Content-Type: application/json
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"pgn": "1. e4 e5 2. Nf3 Nc6 *"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
meta {
|
||||||
|
name: import
|
||||||
|
seq: 5
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
meta {
|
||||||
|
name: Make Move
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{baseUrl}}/api/board/game/{{gameId}}/move/b1c3
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
vars:pre-request {
|
||||||
|
gameId: Yg200tOF
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
meta {
|
||||||
|
name: Get Legal Moves
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{baseUrl}}/api/board/game/{{gameId}}/moves
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
params:query {
|
||||||
|
square: e2
|
||||||
|
}
|
||||||
|
|
||||||
|
vars:pre-request {
|
||||||
|
gameId: COy3oigz
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
meta {
|
||||||
|
name: Undo Move
|
||||||
|
type: http
|
||||||
|
seq: 3
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
method: POST
|
||||||
|
url: {{baseUrl}}/api/board/game/{{gameId}}/undo
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
meta {
|
||||||
|
name: Redo Move
|
||||||
|
type: http
|
||||||
|
seq: 4
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
method: POST
|
||||||
|
url: {{baseUrl}}/api/board/game/{{gameId}}/redo
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
meta {
|
||||||
|
name: move
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"version": "1",
|
||||||
|
"name": "NowChess API",
|
||||||
|
"type": "collection",
|
||||||
|
"ignore": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
vars {
|
||||||
|
baseUrl: http://localhost:8080
|
||||||
|
wsBaseUrl: ws://localhost:8084
|
||||||
|
ioBaseUrl: http://localhost:8081
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
meta {
|
||||||
|
name: Export FEN
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
method: POST
|
||||||
|
url: {{ioBaseUrl}}/io/export/fen
|
||||||
|
body: json
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
Content-Type: application/json
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"board": {
|
||||||
|
"a1": {"color": "White", "pieceType": "Rook"},
|
||||||
|
"b1": {"color": "White", "pieceType": "Knight"},
|
||||||
|
"c1": {"color": "White", "pieceType": "Bishop"},
|
||||||
|
"d1": {"color": "White", "pieceType": "Queen"},
|
||||||
|
"e1": {"color": "White", "pieceType": "King"},
|
||||||
|
"f1": {"color": "White", "pieceType": "Bishop"},
|
||||||
|
"g1": {"color": "White", "pieceType": "Knight"},
|
||||||
|
"h1": {"color": "White", "pieceType": "Rook"},
|
||||||
|
"a2": {"color": "White", "pieceType": "Pawn"},
|
||||||
|
"b2": {"color": "White", "pieceType": "Pawn"},
|
||||||
|
"c2": {"color": "White", "pieceType": "Pawn"},
|
||||||
|
"d2": {"color": "White", "pieceType": "Pawn"},
|
||||||
|
"e2": {"color": "White", "pieceType": "Pawn"},
|
||||||
|
"f2": {"color": "White", "pieceType": "Pawn"},
|
||||||
|
"g2": {"color": "White", "pieceType": "Pawn"},
|
||||||
|
"h2": {"color": "White", "pieceType": "Pawn"},
|
||||||
|
"a7": {"color": "Black", "pieceType": "Pawn"},
|
||||||
|
"b7": {"color": "Black", "pieceType": "Pawn"},
|
||||||
|
"c7": {"color": "Black", "pieceType": "Pawn"},
|
||||||
|
"d7": {"color": "Black", "pieceType": "Pawn"},
|
||||||
|
"e7": {"color": "Black", "pieceType": "Pawn"},
|
||||||
|
"f7": {"color": "Black", "pieceType": "Pawn"},
|
||||||
|
"g7": {"color": "Black", "pieceType": "Pawn"},
|
||||||
|
"h7": {"color": "Black", "pieceType": "Pawn"},
|
||||||
|
"a8": {"color": "Black", "pieceType": "Rook"},
|
||||||
|
"b8": {"color": "Black", "pieceType": "Knight"},
|
||||||
|
"c8": {"color": "Black", "pieceType": "Bishop"},
|
||||||
|
"d8": {"color": "Black", "pieceType": "Queen"},
|
||||||
|
"e8": {"color": "Black", "pieceType": "King"},
|
||||||
|
"f8": {"color": "Black", "pieceType": "Bishop"},
|
||||||
|
"g8": {"color": "Black", "pieceType": "Knight"},
|
||||||
|
"h8": {"color": "Black", "pieceType": "Rook"}
|
||||||
|
},
|
||||||
|
"turn": "White",
|
||||||
|
"castlingRights": {
|
||||||
|
"whiteKingSide": true,
|
||||||
|
"whiteQueenSide": true,
|
||||||
|
"blackKingSide": true,
|
||||||
|
"blackQueenSide": true
|
||||||
|
},
|
||||||
|
"enPassantSquare": null,
|
||||||
|
"halfMoveClock": 0,
|
||||||
|
"moves": [],
|
||||||
|
"result": null,
|
||||||
|
"initialBoard": {
|
||||||
|
"a1": {"color": "White", "pieceType": "Rook"},
|
||||||
|
"b1": {"color": "White", "pieceType": "Knight"},
|
||||||
|
"c1": {"color": "White", "pieceType": "Bishop"},
|
||||||
|
"d1": {"color": "White", "pieceType": "Queen"},
|
||||||
|
"e1": {"color": "White", "pieceType": "King"},
|
||||||
|
"f1": {"color": "White", "pieceType": "Bishop"},
|
||||||
|
"g1": {"color": "White", "pieceType": "Knight"},
|
||||||
|
"h1": {"color": "White", "pieceType": "Rook"},
|
||||||
|
"a2": {"color": "White", "pieceType": "Pawn"},
|
||||||
|
"b2": {"color": "White", "pieceType": "Pawn"},
|
||||||
|
"c2": {"color": "White", "pieceType": "Pawn"},
|
||||||
|
"d2": {"color": "White", "pieceType": "Pawn"},
|
||||||
|
"e2": {"color": "White", "pieceType": "Pawn"},
|
||||||
|
"f2": {"color": "White", "pieceType": "Pawn"},
|
||||||
|
"g2": {"color": "White", "pieceType": "Pawn"},
|
||||||
|
"h2": {"color": "White", "pieceType": "Pawn"},
|
||||||
|
"a7": {"color": "Black", "pieceType": "Pawn"},
|
||||||
|
"b7": {"color": "Black", "pieceType": "Pawn"},
|
||||||
|
"c7": {"color": "Black", "pieceType": "Pawn"},
|
||||||
|
"d7": {"color": "Black", "pieceType": "Pawn"},
|
||||||
|
"e7": {"color": "Black", "pieceType": "Pawn"},
|
||||||
|
"f7": {"color": "Black", "pieceType": "Pawn"},
|
||||||
|
"g7": {"color": "Black", "pieceType": "Pawn"},
|
||||||
|
"h7": {"color": "Black", "pieceType": "Pawn"},
|
||||||
|
"a8": {"color": "Black", "pieceType": "Rook"},
|
||||||
|
"b8": {"color": "Black", "pieceType": "Knight"},
|
||||||
|
"c8": {"color": "Black", "pieceType": "Bishop"},
|
||||||
|
"d8": {"color": "Black", "pieceType": "Queen"},
|
||||||
|
"e8": {"color": "Black", "pieceType": "King"},
|
||||||
|
"f8": {"color": "Black", "pieceType": "Bishop"},
|
||||||
|
"g8": {"color": "Black", "pieceType": "Knight"},
|
||||||
|
"h8": {"color": "Black", "pieceType": "Rook"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
meta {
|
||||||
|
name: Export PGN
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
method: POST
|
||||||
|
url: {{ioBaseUrl}}/io/export/pgn
|
||||||
|
body: json
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
Content-Type: application/json
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"board": {
|
||||||
|
"a1": {"color": "White", "pieceType": "Rook"},
|
||||||
|
"b1": {"color": "White", "pieceType": "Knight"},
|
||||||
|
"c1": {"color": "White", "pieceType": "Bishop"},
|
||||||
|
"d1": {"color": "White", "pieceType": "Queen"},
|
||||||
|
"e1": {"color": "White", "pieceType": "King"},
|
||||||
|
"f1": {"color": "White", "pieceType": "Bishop"},
|
||||||
|
"g1": {"color": "White", "pieceType": "Knight"},
|
||||||
|
"h1": {"color": "White", "pieceType": "Rook"},
|
||||||
|
"a2": {"color": "White", "pieceType": "Pawn"},
|
||||||
|
"b2": {"color": "White", "pieceType": "Pawn"},
|
||||||
|
"c2": {"color": "White", "pieceType": "Pawn"},
|
||||||
|
"d2": {"color": "White", "pieceType": "Pawn"},
|
||||||
|
"e2": {"color": "White", "pieceType": "Pawn"},
|
||||||
|
"f2": {"color": "White", "pieceType": "Pawn"},
|
||||||
|
"g2": {"color": "White", "pieceType": "Pawn"},
|
||||||
|
"h2": {"color": "White", "pieceType": "Pawn"},
|
||||||
|
"a7": {"color": "Black", "pieceType": "Pawn"},
|
||||||
|
"b7": {"color": "Black", "pieceType": "Pawn"},
|
||||||
|
"c7": {"color": "Black", "pieceType": "Pawn"},
|
||||||
|
"d7": {"color": "Black", "pieceType": "Pawn"},
|
||||||
|
"e7": {"color": "Black", "pieceType": "Pawn"},
|
||||||
|
"f7": {"color": "Black", "pieceType": "Pawn"},
|
||||||
|
"g7": {"color": "Black", "pieceType": "Pawn"},
|
||||||
|
"h7": {"color": "Black", "pieceType": "Pawn"},
|
||||||
|
"a8": {"color": "Black", "pieceType": "Rook"},
|
||||||
|
"b8": {"color": "Black", "pieceType": "Knight"},
|
||||||
|
"c8": {"color": "Black", "pieceType": "Bishop"},
|
||||||
|
"d8": {"color": "Black", "pieceType": "Queen"},
|
||||||
|
"e8": {"color": "Black", "pieceType": "King"},
|
||||||
|
"f8": {"color": "Black", "pieceType": "Bishop"},
|
||||||
|
"g8": {"color": "Black", "pieceType": "Knight"},
|
||||||
|
"h8": {"color": "Black", "pieceType": "Rook"}
|
||||||
|
},
|
||||||
|
"turn": "White",
|
||||||
|
"castlingRights": {
|
||||||
|
"whiteKingSide": true,
|
||||||
|
"whiteQueenSide": true,
|
||||||
|
"blackKingSide": true,
|
||||||
|
"blackQueenSide": true
|
||||||
|
},
|
||||||
|
"enPassantSquare": null,
|
||||||
|
"halfMoveClock": 0,
|
||||||
|
"moves": [],
|
||||||
|
"result": null,
|
||||||
|
"initialBoard": {
|
||||||
|
"a1": {"color": "White", "pieceType": "Rook"},
|
||||||
|
"b1": {"color": "White", "pieceType": "Knight"},
|
||||||
|
"c1": {"color": "White", "pieceType": "Bishop"},
|
||||||
|
"d1": {"color": "White", "pieceType": "Queen"},
|
||||||
|
"e1": {"color": "White", "pieceType": "King"},
|
||||||
|
"f1": {"color": "White", "pieceType": "Bishop"},
|
||||||
|
"g1": {"color": "White", "pieceType": "Knight"},
|
||||||
|
"h1": {"color": "White", "pieceType": "Rook"},
|
||||||
|
"a2": {"color": "White", "pieceType": "Pawn"},
|
||||||
|
"b2": {"color": "White", "pieceType": "Pawn"},
|
||||||
|
"c2": {"color": "White", "pieceType": "Pawn"},
|
||||||
|
"d2": {"color": "White", "pieceType": "Pawn"},
|
||||||
|
"e2": {"color": "White", "pieceType": "Pawn"},
|
||||||
|
"f2": {"color": "White", "pieceType": "Pawn"},
|
||||||
|
"g2": {"color": "White", "pieceType": "Pawn"},
|
||||||
|
"h2": {"color": "White", "pieceType": "Pawn"},
|
||||||
|
"a7": {"color": "Black", "pieceType": "Pawn"},
|
||||||
|
"b7": {"color": "Black", "pieceType": "Pawn"},
|
||||||
|
"c7": {"color": "Black", "pieceType": "Pawn"},
|
||||||
|
"d7": {"color": "Black", "pieceType": "Pawn"},
|
||||||
|
"e7": {"color": "Black", "pieceType": "Pawn"},
|
||||||
|
"f7": {"color": "Black", "pieceType": "Pawn"},
|
||||||
|
"g7": {"color": "Black", "pieceType": "Pawn"},
|
||||||
|
"h7": {"color": "Black", "pieceType": "Pawn"},
|
||||||
|
"a8": {"color": "Black", "pieceType": "Rook"},
|
||||||
|
"b8": {"color": "Black", "pieceType": "Knight"},
|
||||||
|
"c8": {"color": "Black", "pieceType": "Bishop"},
|
||||||
|
"d8": {"color": "Black", "pieceType": "Queen"},
|
||||||
|
"e8": {"color": "Black", "pieceType": "King"},
|
||||||
|
"f8": {"color": "Black", "pieceType": "Bishop"},
|
||||||
|
"g8": {"color": "Black", "pieceType": "Knight"},
|
||||||
|
"h8": {"color": "Black", "pieceType": "Rook"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
meta {
|
||||||
|
name: export
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
meta {
|
||||||
|
name: Import FEN
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
method: POST
|
||||||
|
url: {{ioBaseUrl}}/io/import/fen
|
||||||
|
body: json
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
Content-Type: application/json
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"fen": "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
meta {
|
||||||
|
name: Import PGN
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
method: POST
|
||||||
|
url: {{ioBaseUrl}}/io/import/pgn
|
||||||
|
body: json
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
Content-Type: application/json
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"pgn": "1. e4 e5 2. Nf3 Nc6 *"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
meta {
|
||||||
|
name: import
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
+62
-1
@@ -8,6 +8,65 @@ plugins {
|
|||||||
group = "de.nowchess"
|
group = "de.nowchess"
|
||||||
version = "1.0-SNAPSHOT"
|
version = "1.0-SNAPSHOT"
|
||||||
|
|
||||||
|
// Canonical coverage exclusions — glob patterns consumed by Sonar directly;
|
||||||
|
// converted to scoverage regexes via globToScoverageRegex for instrumentation-time exclusion.
|
||||||
|
val coverageExclusions = listOf(
|
||||||
|
// UI renders JavaFX components; headless test environments cannot exercise rendering paths
|
||||||
|
"modules/api/**",
|
||||||
|
// FastParse macro-generated combinators produce synthetic branches that scoverage marks as uncovered
|
||||||
|
"modules/io/src/main/scala/de/nowchess/io/fen/FenParserFastParse*",
|
||||||
|
// NNUE inference pipeline — coverage requires a trained model file not present in CI
|
||||||
|
"**/bot/**/NNUE.scala",
|
||||||
|
"**/bot/**/NNUEBot.scala",
|
||||||
|
"**/bot/**/EvaluationNNUE.scala",
|
||||||
|
// NBAI binary format loader/writer — error paths require crafted corrupt files; migrator is a one-shot tool
|
||||||
|
"**/bot/**/NbaiLoader.scala",
|
||||||
|
"**/bot/**/NbaiModel.scala",
|
||||||
|
"**/bot/**/NbaiMigrator.scala",
|
||||||
|
"**/bot/**/NbaiWriter.scala",
|
||||||
|
// PolyglotBook — binary I/O and dead-code guards (bit-masked fields can never exceed valid range)
|
||||||
|
"**/bot/**/PolyglotBook.scala",
|
||||||
|
"**/bot/**/MoveOrdering.scala",
|
||||||
|
"**/bot/**/AlphaBetaSearch.scala",
|
||||||
|
// DTO case class synthetic methods (Scala compiler-generated apply/$default params)
|
||||||
|
"**/api/src/main/scala/de/nowchess/api/dto/**Dto.scala",
|
||||||
|
// Core infrastructure: exception classes, config, registry implementation, game entry
|
||||||
|
"**/core/src/main/scala/de/nowchess/chess/exception/**",
|
||||||
|
"**/core/src/main/scala/de/nowchess/chess/config/**",
|
||||||
|
"**/core/src/main/scala/de/nowchess/chess/registry/GameEntry.scala",
|
||||||
|
"**/core/src/main/scala/de/nowchess/chess/registry/GameRegistryImpl.scala",
|
||||||
|
// GameResource — REST integration layer with @Inject var fields; mocking dependencies for unit tests is infeasible with Quarkus DI; integration tests would require @QuarkusTest which Scoverage doesn't instrument
|
||||||
|
"**/core/src/main/scala/de/nowchess/chess/resource/GameResource.scala",
|
||||||
|
// IoResource — same rationale as GameResource; @QuarkusTest not instrumented by Scoverage
|
||||||
|
"**/io/src/main/scala/de/nowchess/io/service/resource/IoResource.scala",
|
||||||
|
// JacksonConfig — Quarkus lifecycle hook, no testable logic beyond ObjectMapper registration
|
||||||
|
"**/io/src/main/scala/de/nowchess/io/service/config/JacksonConfig.scala",
|
||||||
|
//RuleSetRestAdapter - Quarkus integration of rule into core, only testable with Quarkus tests
|
||||||
|
"**/core/src/main/de/nowchess/chess/adapter/RuleSetRestAdapter.scala",
|
||||||
|
// AccountResource / ChallengeResource — REST integration layer; @QuarkusTest not instrumented by Scoverage
|
||||||
|
"**/account/src/main/scala/de/nowchess/account/resource/**",
|
||||||
|
// JacksonConfig / NativeReflectionConfig — Quarkus lifecycle hooks, no testable logic
|
||||||
|
"**/account/src/main/scala/de/nowchess/account/config/**",
|
||||||
|
// WebSocket service — infrastructure CDI beans (RedisConfig)
|
||||||
|
"**/ws/src/main/scala/de/nowchess/ws/config/**",
|
||||||
|
// GameWebSocketResource in core — replaced by ws module
|
||||||
|
"**/core/src/main/scala/de/nowchess/chess/resource/GameWebSocketResource.scala",
|
||||||
|
// Coordinator infrastructure — gRPC, microservice orchestration
|
||||||
|
"**/coordinator/src/main/scala/**",
|
||||||
|
)
|
||||||
|
|
||||||
|
// Converts a Sonar-style glob to a scoverage regex (matched against full source path).
|
||||||
|
// Order matters: protect ** before converting lone *, escape dots last.
|
||||||
|
fun globToScoverageRegex(glob: String): String =
|
||||||
|
glob
|
||||||
|
.replace("**", "^@")
|
||||||
|
.replace("*", "[^/]*")
|
||||||
|
.replace(".", "\\.")
|
||||||
|
.replace("^@", ".*")
|
||||||
|
.let { ".*$it" }
|
||||||
|
|
||||||
|
extra["SCOVERAGE_EXCLUDED"] = coverageExclusions.map(::globToScoverageRegex)
|
||||||
|
|
||||||
sonar {
|
sonar {
|
||||||
properties {
|
properties {
|
||||||
property("sonar.projectKey", "Now-Chess-Systems")
|
property("sonar.projectKey", "Now-Chess-Systems")
|
||||||
@@ -22,19 +81,21 @@ sonar {
|
|||||||
}.joinToString(",")
|
}.joinToString(",")
|
||||||
|
|
||||||
property("sonar.scala.coverage.reportPaths", scoverageReports)
|
property("sonar.scala.coverage.reportPaths", scoverageReports)
|
||||||
|
property("sonar.coverage.exclusions", coverageExclusions.joinToString(","))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val versions = mapOf(
|
val versions = mapOf(
|
||||||
"QUARKUS_SCALA3" to "1.0.0",
|
"QUARKUS_SCALA3" to "1.0.0",
|
||||||
"SCALA3" to "3.5.1",
|
"SCALA3" to "3.5.1",
|
||||||
"SCALA_LIBRARY" to "2.13.18",
|
"SCALA_LIBRARY" to "2.13.16",
|
||||||
"SCALATEST" to "3.2.19",
|
"SCALATEST" to "3.2.19",
|
||||||
"SCALATEST_JUNIT" to "0.1.11",
|
"SCALATEST_JUNIT" to "0.1.11",
|
||||||
"SCOVERAGE" to "2.1.1",
|
"SCOVERAGE" to "2.1.1",
|
||||||
"SCALAFX" to "21.0.0-R32",
|
"SCALAFX" to "21.0.0-R32",
|
||||||
"JAVAFX" to "21.0.1",
|
"JAVAFX" to "21.0.1",
|
||||||
"JUNIT_BOM" to "5.13.4",
|
"JUNIT_BOM" to "5.13.4",
|
||||||
|
"ONNXRUNTIME" to "1.19.2",
|
||||||
"SCALA_PARSER_COMBINATORS" to "2.4.0",
|
"SCALA_PARSER_COMBINATORS" to "2.4.0",
|
||||||
"FASTPARSE" to "3.0.2",
|
"FASTPARSE" to "3.0.2",
|
||||||
"JACKSON" to "2.17.2",
|
"JACKSON" to "2.17.2",
|
||||||
|
|||||||
@@ -1,776 +0,0 @@
|
|||||||
openapi: 3.0.3
|
|
||||||
info:
|
|
||||||
title: NowChess API
|
|
||||||
description: |
|
|
||||||
REST API for the NowChess application. Designed to feel familiar to users
|
|
||||||
of the [lichess API](https://lichess.org/api).
|
|
||||||
|
|
||||||
## Authentication
|
|
||||||
Most endpoints require a Bearer token:
|
|
||||||
```
|
|
||||||
Authorization: Bearer <token>
|
|
||||||
```
|
|
||||||
Authentication is reserved for future implementation — endpoints are currently
|
|
||||||
open unless noted otherwise.
|
|
||||||
|
|
||||||
## Move notation
|
|
||||||
Moves are expressed in **UCI notation**: `{from}{to}[promotion]`
|
|
||||||
- Normal move: `e2e4`
|
|
||||||
- Capture: `d5e6`
|
|
||||||
- Promotion: `e7e8q` (q=queen, r=rook, b=bishop, n=knight)
|
|
||||||
- Castling: `e1g1` (kingside white), `e1c1` (queenside white)
|
|
||||||
|
|
||||||
## Streaming
|
|
||||||
Endpoints that support streaming return **NDJSON** (newline-delimited JSON).
|
|
||||||
Request them with:
|
|
||||||
```
|
|
||||||
Accept: application/x-ndjson
|
|
||||||
```
|
|
||||||
Each line of the response is a complete JSON object. Empty lines are
|
|
||||||
keep-alive heartbeats.
|
|
||||||
|
|
||||||
## Rate limiting
|
|
||||||
Requests that exceed the rate limit receive `429 Too Many Requests`.
|
|
||||||
Honour the `Retry-After` response header and wait before retrying.
|
|
||||||
version: 1.0.0
|
|
||||||
contact:
|
|
||||||
name: NowChess
|
|
||||||
license:
|
|
||||||
name: MIT
|
|
||||||
|
|
||||||
servers:
|
|
||||||
- url: http://localhost:8080
|
|
||||||
description: Local development server
|
|
||||||
|
|
||||||
tags:
|
|
||||||
- name: game
|
|
||||||
description: Create and manage chess games
|
|
||||||
- name: move
|
|
||||||
description: Make moves and navigate game history
|
|
||||||
- name: draw
|
|
||||||
description: Draw offers and claims
|
|
||||||
- name: import
|
|
||||||
description: Load a game from FEN or PGN
|
|
||||||
- name: export
|
|
||||||
description: Export a game as FEN or PGN
|
|
||||||
|
|
||||||
paths:
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Game lifecycle
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/api/board/game:
|
|
||||||
post:
|
|
||||||
operationId: createGame
|
|
||||||
tags: [game]
|
|
||||||
summary: Create a new game
|
|
||||||
description: |
|
|
||||||
Creates a new chess game starting from the initial position.
|
|
||||||
Returns the full game state including the generated `gameId`.
|
|
||||||
security:
|
|
||||||
- bearerAuth: []
|
|
||||||
requestBody:
|
|
||||||
required: false
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/CreateGameRequest'
|
|
||||||
responses:
|
|
||||||
'201':
|
|
||||||
description: Game created
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/GameFull'
|
|
||||||
'400':
|
|
||||||
$ref: '#/components/responses/BadRequest'
|
|
||||||
'401':
|
|
||||||
$ref: '#/components/responses/Unauthorized'
|
|
||||||
'429':
|
|
||||||
$ref: '#/components/responses/TooManyRequests'
|
|
||||||
|
|
||||||
/api/board/game/{gameId}:
|
|
||||||
get:
|
|
||||||
operationId: getGame
|
|
||||||
tags: [game]
|
|
||||||
summary: Get game state
|
|
||||||
description: Returns the full current state of a game.
|
|
||||||
security:
|
|
||||||
- bearerAuth: []
|
|
||||||
parameters:
|
|
||||||
- $ref: '#/components/parameters/gameId'
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Current game state
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/GameFull'
|
|
||||||
'404':
|
|
||||||
$ref: '#/components/responses/NotFound'
|
|
||||||
'429':
|
|
||||||
$ref: '#/components/responses/TooManyRequests'
|
|
||||||
|
|
||||||
/api/board/game/{gameId}/stream:
|
|
||||||
get:
|
|
||||||
operationId: streamGame
|
|
||||||
tags: [game]
|
|
||||||
summary: Stream game events
|
|
||||||
description: |
|
|
||||||
Opens a persistent NDJSON stream for a game. The first object sent is
|
|
||||||
a `gameFull` event containing the complete game state. Subsequent
|
|
||||||
objects are `gameState` events sent whenever the game changes (move
|
|
||||||
made, draw offered, game over, etc.).
|
|
||||||
|
|
||||||
Empty lines are heartbeats to keep the connection alive.
|
|
||||||
|
|
||||||
Connect with:
|
|
||||||
```
|
|
||||||
Accept: application/x-ndjson
|
|
||||||
```
|
|
||||||
security:
|
|
||||||
- bearerAuth: []
|
|
||||||
parameters:
|
|
||||||
- $ref: '#/components/parameters/gameId'
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: NDJSON event stream
|
|
||||||
content:
|
|
||||||
application/x-ndjson:
|
|
||||||
schema:
|
|
||||||
oneOf:
|
|
||||||
- $ref: '#/components/schemas/GameFullEvent'
|
|
||||||
- $ref: '#/components/schemas/GameStateEvent'
|
|
||||||
- $ref: '#/components/schemas/ErrorEvent'
|
|
||||||
'404':
|
|
||||||
$ref: '#/components/responses/NotFound'
|
|
||||||
'429':
|
|
||||||
$ref: '#/components/responses/TooManyRequests'
|
|
||||||
|
|
||||||
/api/board/game/{gameId}/resign:
|
|
||||||
post:
|
|
||||||
operationId: resignGame
|
|
||||||
tags: [game]
|
|
||||||
summary: Resign the game
|
|
||||||
description: The active player resigns. The game ends immediately.
|
|
||||||
security:
|
|
||||||
- bearerAuth: []
|
|
||||||
parameters:
|
|
||||||
- $ref: '#/components/parameters/gameId'
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Resignation accepted
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/OkResponse'
|
|
||||||
'400':
|
|
||||||
$ref: '#/components/responses/BadRequest'
|
|
||||||
'404':
|
|
||||||
$ref: '#/components/responses/NotFound'
|
|
||||||
'429':
|
|
||||||
$ref: '#/components/responses/TooManyRequests'
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Move-making
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/api/board/game/{gameId}/move/{uci}:
|
|
||||||
post:
|
|
||||||
operationId: makeMove
|
|
||||||
tags: [move]
|
|
||||||
summary: Make a move
|
|
||||||
description: |
|
|
||||||
Submit a move in UCI notation. The move must be legal for the side
|
|
||||||
currently to move.
|
|
||||||
|
|
||||||
For promotion moves include the target piece as the fifth character:
|
|
||||||
`e7e8q`, `a2a1r`, etc.
|
|
||||||
|
|
||||||
If the move results in a pawn reaching the back rank and no promotion
|
|
||||||
character is supplied, the game enters `promotionPending` status and
|
|
||||||
the move is not yet applied — resubmit with the promotion character.
|
|
||||||
security:
|
|
||||||
- bearerAuth: []
|
|
||||||
parameters:
|
|
||||||
- $ref: '#/components/parameters/gameId'
|
|
||||||
- name: uci
|
|
||||||
in: path
|
|
||||||
required: true
|
|
||||||
description: Move in UCI notation (e.g. `e2e4`, `e7e8q`)
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
pattern: '^[a-h][1-8][a-h][1-8][qrbn]?$'
|
|
||||||
example: e2e4
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Move applied — returns updated game state
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/GameState'
|
|
||||||
'400':
|
|
||||||
$ref: '#/components/responses/BadRequest'
|
|
||||||
'404':
|
|
||||||
$ref: '#/components/responses/NotFound'
|
|
||||||
'429':
|
|
||||||
$ref: '#/components/responses/TooManyRequests'
|
|
||||||
|
|
||||||
/api/board/game/{gameId}/moves:
|
|
||||||
get:
|
|
||||||
operationId: getLegalMoves
|
|
||||||
tags: [move]
|
|
||||||
summary: Get legal moves
|
|
||||||
description: |
|
|
||||||
Returns all legal moves for the side currently to move.
|
|
||||||
Optionally filter to moves originating from a single square.
|
|
||||||
security:
|
|
||||||
- bearerAuth: []
|
|
||||||
parameters:
|
|
||||||
- $ref: '#/components/parameters/gameId'
|
|
||||||
- name: square
|
|
||||||
in: query
|
|
||||||
required: false
|
|
||||||
description: Filter to moves from this square (e.g. `e2`)
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
pattern: '^[a-h][1-8]$'
|
|
||||||
example: e2
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: List of legal moves
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/LegalMovesResponse'
|
|
||||||
'404':
|
|
||||||
$ref: '#/components/responses/NotFound'
|
|
||||||
'429':
|
|
||||||
$ref: '#/components/responses/TooManyRequests'
|
|
||||||
|
|
||||||
/api/board/game/{gameId}/undo:
|
|
||||||
post:
|
|
||||||
operationId: undoMove
|
|
||||||
tags: [move]
|
|
||||||
summary: Undo the last move
|
|
||||||
description: Reverts the most recent move. Returns the updated game state.
|
|
||||||
security:
|
|
||||||
- bearerAuth: []
|
|
||||||
parameters:
|
|
||||||
- $ref: '#/components/parameters/gameId'
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Move undone
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/GameState'
|
|
||||||
'400':
|
|
||||||
description: No moves to undo
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ApiError'
|
|
||||||
'404':
|
|
||||||
$ref: '#/components/responses/NotFound'
|
|
||||||
'429':
|
|
||||||
$ref: '#/components/responses/TooManyRequests'
|
|
||||||
|
|
||||||
/api/board/game/{gameId}/redo:
|
|
||||||
post:
|
|
||||||
operationId: redoMove
|
|
||||||
tags: [move]
|
|
||||||
summary: Redo a previously undone move
|
|
||||||
description: Re-applies the next move in the undo stack. Returns the updated game state.
|
|
||||||
security:
|
|
||||||
- bearerAuth: []
|
|
||||||
parameters:
|
|
||||||
- $ref: '#/components/parameters/gameId'
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Move redone
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/GameState'
|
|
||||||
'400':
|
|
||||||
description: No moves to redo
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ApiError'
|
|
||||||
'404':
|
|
||||||
$ref: '#/components/responses/NotFound'
|
|
||||||
'429':
|
|
||||||
$ref: '#/components/responses/TooManyRequests'
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Draw handling
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/api/board/game/{gameId}/draw/{action}:
|
|
||||||
post:
|
|
||||||
operationId: drawAction
|
|
||||||
tags: [draw]
|
|
||||||
summary: Offer, accept, decline, or claim a draw
|
|
||||||
description: |
|
|
||||||
Perform a draw-related action:
|
|
||||||
|
|
||||||
| Action | Description |
|
|
||||||
|-----------|-------------|
|
|
||||||
| `offer` | Offer a draw to the opponent |
|
|
||||||
| `accept` | Accept the opponent's draw offer |
|
|
||||||
| `decline` | Decline the opponent's draw offer |
|
|
||||||
| `claim` | Claim a draw under the fifty-move rule (only valid when `status` is `fiftyMoveAvailable`) |
|
|
||||||
security:
|
|
||||||
- bearerAuth: []
|
|
||||||
parameters:
|
|
||||||
- $ref: '#/components/parameters/gameId'
|
|
||||||
- name: action
|
|
||||||
in: path
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
enum: [offer, accept, decline, claim]
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Action accepted
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/OkResponse'
|
|
||||||
'400':
|
|
||||||
$ref: '#/components/responses/BadRequest'
|
|
||||||
'404':
|
|
||||||
$ref: '#/components/responses/NotFound'
|
|
||||||
'429':
|
|
||||||
$ref: '#/components/responses/TooManyRequests'
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Import
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/api/board/game/import/fen:
|
|
||||||
post:
|
|
||||||
operationId: importFen
|
|
||||||
tags: [import]
|
|
||||||
summary: Load a position from FEN
|
|
||||||
description: |
|
|
||||||
Creates a new game from a FEN string. The game starts at the position
|
|
||||||
described by the FEN; move history prior to that position is not
|
|
||||||
available.
|
|
||||||
security:
|
|
||||||
- bearerAuth: []
|
|
||||||
requestBody:
|
|
||||||
required: true
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ImportFenRequest'
|
|
||||||
responses:
|
|
||||||
'201':
|
|
||||||
description: Game created from FEN
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/GameFull'
|
|
||||||
'400':
|
|
||||||
$ref: '#/components/responses/BadRequest'
|
|
||||||
'429':
|
|
||||||
$ref: '#/components/responses/TooManyRequests'
|
|
||||||
|
|
||||||
/api/board/game/import/pgn:
|
|
||||||
post:
|
|
||||||
operationId: importPgn
|
|
||||||
tags: [import]
|
|
||||||
summary: Load a game from PGN
|
|
||||||
description: |
|
|
||||||
Creates a new game by replaying all moves in a PGN string. The game
|
|
||||||
starts at the position after the final move in the PGN; undo is
|
|
||||||
available for every replayed move.
|
|
||||||
security:
|
|
||||||
- bearerAuth: []
|
|
||||||
requestBody:
|
|
||||||
required: true
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ImportPgnRequest'
|
|
||||||
responses:
|
|
||||||
'201':
|
|
||||||
description: Game created from PGN
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/GameFull'
|
|
||||||
'400':
|
|
||||||
$ref: '#/components/responses/BadRequest'
|
|
||||||
'429':
|
|
||||||
$ref: '#/components/responses/TooManyRequests'
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Export
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/api/board/game/{gameId}/export/fen:
|
|
||||||
get:
|
|
||||||
operationId: exportFen
|
|
||||||
tags: [export]
|
|
||||||
summary: Export current position as FEN
|
|
||||||
description: Returns the FEN string representing the current board position.
|
|
||||||
security:
|
|
||||||
- bearerAuth: []
|
|
||||||
parameters:
|
|
||||||
- $ref: '#/components/parameters/gameId'
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: FEN string
|
|
||||||
content:
|
|
||||||
text/plain:
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
example: rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1
|
|
||||||
'404':
|
|
||||||
$ref: '#/components/responses/NotFound'
|
|
||||||
'429':
|
|
||||||
$ref: '#/components/responses/TooManyRequests'
|
|
||||||
|
|
||||||
/api/board/game/{gameId}/export/pgn:
|
|
||||||
get:
|
|
||||||
operationId: exportPgn
|
|
||||||
tags: [export]
|
|
||||||
summary: Export game as PGN
|
|
||||||
description: Returns the full PGN for the game including headers and move text.
|
|
||||||
security:
|
|
||||||
- bearerAuth: []
|
|
||||||
parameters:
|
|
||||||
- $ref: '#/components/parameters/gameId'
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: PGN text
|
|
||||||
content:
|
|
||||||
application/x-chess-pgn:
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
example: |
|
|
||||||
[Event "NowChess game"]
|
|
||||||
[White "Player1"]
|
|
||||||
[Black "Player2"]
|
|
||||||
[Result "*"]
|
|
||||||
|
|
||||||
1. e4 e5 2. Nf3 *
|
|
||||||
'404':
|
|
||||||
$ref: '#/components/responses/NotFound'
|
|
||||||
'429':
|
|
||||||
$ref: '#/components/responses/TooManyRequests'
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Components
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
components:
|
|
||||||
|
|
||||||
securitySchemes:
|
|
||||||
bearerAuth:
|
|
||||||
type: http
|
|
||||||
scheme: bearer
|
|
||||||
description: 'Personal access token — `Authorization: Bearer <token>`'
|
|
||||||
|
|
||||||
parameters:
|
|
||||||
gameId:
|
|
||||||
name: gameId
|
|
||||||
in: path
|
|
||||||
required: true
|
|
||||||
description: 8-character alphanumeric game ID (e.g. `Qa7FJNk2`)
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
pattern: '^[A-Za-z0-9]{8}$'
|
|
||||||
example: Qa7FJNk2
|
|
||||||
|
|
||||||
responses:
|
|
||||||
BadRequest:
|
|
||||||
description: Invalid input
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ApiError'
|
|
||||||
Unauthorized:
|
|
||||||
description: Missing or invalid authentication token
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ApiError'
|
|
||||||
NotFound:
|
|
||||||
description: Game not found
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ApiError'
|
|
||||||
TooManyRequests:
|
|
||||||
description: Rate limit exceeded — see `Retry-After` header
|
|
||||||
headers:
|
|
||||||
Retry-After:
|
|
||||||
description: Seconds to wait before retrying
|
|
||||||
schema:
|
|
||||||
type: integer
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ApiError'
|
|
||||||
|
|
||||||
schemas:
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
# Requests
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
CreateGameRequest:
|
|
||||||
type: object
|
|
||||||
description: Parameters for creating a new game. All fields are optional.
|
|
||||||
properties:
|
|
||||||
white:
|
|
||||||
$ref: '#/components/schemas/PlayerInfo'
|
|
||||||
black:
|
|
||||||
$ref: '#/components/schemas/PlayerInfo'
|
|
||||||
|
|
||||||
ImportFenRequest:
|
|
||||||
type: object
|
|
||||||
required: [fen]
|
|
||||||
properties:
|
|
||||||
fen:
|
|
||||||
type: string
|
|
||||||
description: Complete FEN string (6 fields)
|
|
||||||
example: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
|
|
||||||
white:
|
|
||||||
$ref: '#/components/schemas/PlayerInfo'
|
|
||||||
black:
|
|
||||||
$ref: '#/components/schemas/PlayerInfo'
|
|
||||||
|
|
||||||
ImportPgnRequest:
|
|
||||||
type: object
|
|
||||||
required: [pgn]
|
|
||||||
properties:
|
|
||||||
pgn:
|
|
||||||
type: string
|
|
||||||
description: PGN text (headers and move list)
|
|
||||||
example: "1. e4 e5 2. Nf3 Nc6 *"
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
# Game state
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
GameFull:
|
|
||||||
type: object
|
|
||||||
description: Complete game information including players and current state.
|
|
||||||
required: [gameId, white, black, state]
|
|
||||||
properties:
|
|
||||||
gameId:
|
|
||||||
type: string
|
|
||||||
description: Unique 8-character game identifier
|
|
||||||
example: Qa7FJNk2
|
|
||||||
white:
|
|
||||||
$ref: '#/components/schemas/PlayerInfo'
|
|
||||||
black:
|
|
||||||
$ref: '#/components/schemas/PlayerInfo'
|
|
||||||
state:
|
|
||||||
$ref: '#/components/schemas/GameState'
|
|
||||||
|
|
||||||
GameState:
|
|
||||||
type: object
|
|
||||||
description: |
|
|
||||||
The current game state. Included in `GameFull` and returned by move
|
|
||||||
endpoints and stream events.
|
|
||||||
required: [fen, pgn, turn, status, moves, undoAvailable, redoAvailable]
|
|
||||||
properties:
|
|
||||||
fen:
|
|
||||||
type: string
|
|
||||||
description: FEN string for the current position
|
|
||||||
example: rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1
|
|
||||||
pgn:
|
|
||||||
type: string
|
|
||||||
description: PGN move text for the full game so far
|
|
||||||
example: "1. e4"
|
|
||||||
turn:
|
|
||||||
type: string
|
|
||||||
enum: [white, black]
|
|
||||||
description: The side to move
|
|
||||||
status:
|
|
||||||
$ref: '#/components/schemas/GameStatus'
|
|
||||||
winner:
|
|
||||||
type: string
|
|
||||||
enum: [white, black]
|
|
||||||
description: Set when `status` is `checkmate` or `resign`
|
|
||||||
nullable: true
|
|
||||||
moves:
|
|
||||||
type: array
|
|
||||||
description: All moves played so far, in UCI notation
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
example: [e2e4, e7e5, g1f3]
|
|
||||||
undoAvailable:
|
|
||||||
type: boolean
|
|
||||||
description: Whether `POST /undo` is currently valid
|
|
||||||
redoAvailable:
|
|
||||||
type: boolean
|
|
||||||
description: Whether `POST /redo` is currently valid
|
|
||||||
|
|
||||||
GameStatus:
|
|
||||||
type: string
|
|
||||||
description: |
|
|
||||||
Current game status:
|
|
||||||
|
|
||||||
| Value | Meaning |
|
|
||||||
|-------|---------|
|
|
||||||
| `started` | Game in progress, no special condition |
|
|
||||||
| `check` | Side to move is in check |
|
|
||||||
| `checkmate` | Side to move is checkmated — game over |
|
|
||||||
| `stalemate` | Side to move has no legal moves, not in check — game over (draw) |
|
|
||||||
| `resign` | A player resigned — game over |
|
|
||||||
| `draw` | Draw agreed or claimed — game over |
|
|
||||||
| `drawOffered` | Waiting for the opponent to accept or decline a draw offer |
|
|
||||||
| `fiftyMoveAvailable` | Fifty-move rule threshold reached; active player may claim draw |
|
|
||||||
| `promotionPending` | A pawn reached the back rank; awaiting promotion piece selection |
|
|
||||||
| `insufficientMaterial` | Neither side has enough pieces to deliver checkmate — game over (draw) |
|
|
||||||
enum:
|
|
||||||
- started
|
|
||||||
- check
|
|
||||||
- checkmate
|
|
||||||
- stalemate
|
|
||||||
- resign
|
|
||||||
- draw
|
|
||||||
- drawOffered
|
|
||||||
- fiftyMoveAvailable
|
|
||||||
- promotionPending
|
|
||||||
- insufficientMaterial
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
# Moves
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
LegalMovesResponse:
|
|
||||||
type: object
|
|
||||||
required: [moves]
|
|
||||||
properties:
|
|
||||||
moves:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
$ref: '#/components/schemas/LegalMove'
|
|
||||||
|
|
||||||
LegalMove:
|
|
||||||
type: object
|
|
||||||
required: [from, to, uci, moveType]
|
|
||||||
properties:
|
|
||||||
from:
|
|
||||||
type: string
|
|
||||||
description: Origin square in algebraic notation
|
|
||||||
example: e2
|
|
||||||
to:
|
|
||||||
type: string
|
|
||||||
description: Destination square in algebraic notation
|
|
||||||
example: e4
|
|
||||||
uci:
|
|
||||||
type: string
|
|
||||||
description: Full move in UCI notation
|
|
||||||
example: e2e4
|
|
||||||
moveType:
|
|
||||||
$ref: '#/components/schemas/MoveType'
|
|
||||||
promotion:
|
|
||||||
type: string
|
|
||||||
enum: [queen, rook, bishop, knight]
|
|
||||||
description: Target piece for promotion moves
|
|
||||||
nullable: true
|
|
||||||
|
|
||||||
MoveType:
|
|
||||||
type: string
|
|
||||||
description: Classification of the move
|
|
||||||
enum:
|
|
||||||
- normal
|
|
||||||
- capture
|
|
||||||
- castleKingside
|
|
||||||
- castleQueenside
|
|
||||||
- enPassant
|
|
||||||
- promotion
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
# Streaming events
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
GameFullEvent:
|
|
||||||
type: object
|
|
||||||
description: |
|
|
||||||
First event on a game stream. Contains the complete game snapshot.
|
|
||||||
required: [type, game]
|
|
||||||
properties:
|
|
||||||
type:
|
|
||||||
type: string
|
|
||||||
enum: [gameFull]
|
|
||||||
game:
|
|
||||||
$ref: '#/components/schemas/GameFull'
|
|
||||||
|
|
||||||
GameStateEvent:
|
|
||||||
type: object
|
|
||||||
description: |
|
|
||||||
Emitted on a game stream whenever the game state changes (move played,
|
|
||||||
draw offered, game over, etc.).
|
|
||||||
required: [type, state]
|
|
||||||
properties:
|
|
||||||
type:
|
|
||||||
type: string
|
|
||||||
enum: [gameState]
|
|
||||||
state:
|
|
||||||
$ref: '#/components/schemas/GameState'
|
|
||||||
|
|
||||||
ErrorEvent:
|
|
||||||
type: object
|
|
||||||
description: Emitted on a game stream when an error occurs.
|
|
||||||
required: [type, error]
|
|
||||||
properties:
|
|
||||||
type:
|
|
||||||
type: string
|
|
||||||
enum: [error]
|
|
||||||
error:
|
|
||||||
$ref: '#/components/schemas/ApiError'
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
# Shared types
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
PlayerInfo:
|
|
||||||
type: object
|
|
||||||
required: [id, displayName]
|
|
||||||
properties:
|
|
||||||
id:
|
|
||||||
type: string
|
|
||||||
description: Unique player identifier
|
|
||||||
example: player1
|
|
||||||
displayName:
|
|
||||||
type: string
|
|
||||||
description: Human-readable display name
|
|
||||||
example: Alice
|
|
||||||
|
|
||||||
OkResponse:
|
|
||||||
type: object
|
|
||||||
required: [ok]
|
|
||||||
properties:
|
|
||||||
ok:
|
|
||||||
type: boolean
|
|
||||||
enum: [true]
|
|
||||||
|
|
||||||
ApiError:
|
|
||||||
type: object
|
|
||||||
required: [code, message]
|
|
||||||
properties:
|
|
||||||
code:
|
|
||||||
type: string
|
|
||||||
description: Machine-readable error code
|
|
||||||
example: INVALID_MOVE
|
|
||||||
message:
|
|
||||||
type: string
|
|
||||||
description: Human-readable error description
|
|
||||||
example: e2e5 is not a legal move
|
|
||||||
field:
|
|
||||||
type: string
|
|
||||||
description: Request field that caused the error, if applicable
|
|
||||||
example: uci
|
|
||||||
nullable: true
|
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# Gradle properties
|
||||||
|
quarkusPluginId=io.quarkus
|
||||||
|
quarkusPluginVersion=3.32.4
|
||||||
|
quarkusPlatformGroupId=io.quarkus.platform
|
||||||
|
quarkusPlatformArtifactId=quarkus-bom
|
||||||
|
quarkusPlatformVersion=3.32.4
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
#Sat Mar 21 14:37:06 CET 2026
|
#Sat Mar 21 14:37:06 CET 2026
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import glob,re
|
import glob,re
|
||||||
mods=['api','core','io','rule','ui']
|
mods=['api','core','io','rule','ui', 'bot']
|
||||||
tot=0
|
tot=0
|
||||||
for m in mods:
|
for m in mods:
|
||||||
s=0
|
s=0
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
plugins {
|
||||||
|
id("scala")
|
||||||
|
id("org.scoverage") version "8.1"
|
||||||
|
id("io.quarkus")
|
||||||
|
}
|
||||||
|
|
||||||
|
group = "de.nowchess"
|
||||||
|
version = "1.0-SNAPSHOT"
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val versions = rootProject.extra["VERSIONS"] as Map<String, String>
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
scala {
|
||||||
|
scalaVersion = versions["SCALA3"]!!
|
||||||
|
}
|
||||||
|
|
||||||
|
scoverage {
|
||||||
|
scoverageVersion.set(versions["SCOVERAGE"]!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<ScalaCompile> {
|
||||||
|
scalaCompileOptions.additionalParameters = listOf("-encoding", "UTF-8")
|
||||||
|
}
|
||||||
|
|
||||||
|
val quarkusPlatformGroupId: String by project
|
||||||
|
val quarkusPlatformArtifactId: String by project
|
||||||
|
val quarkusPlatformVersion: String by project
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
runtimeOnly("io.quarkus:quarkus-jdbc-h2")
|
||||||
|
|
||||||
|
compileOnly("org.scala-lang:scala3-compiler_3") {
|
||||||
|
version {
|
||||||
|
strictly(versions["SCALA3"]!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
implementation("org.scala-lang:scala3-library_3") {
|
||||||
|
version {
|
||||||
|
strictly(versions["SCALA3"]!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
implementation(project(":modules:security"))
|
||||||
|
|
||||||
|
implementation(platform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}"))
|
||||||
|
implementation("io.quarkus:quarkus-rest")
|
||||||
|
implementation("io.quarkus:quarkus-rest-jackson")
|
||||||
|
implementation("io.quarkus:quarkus-rest-client-jackson")
|
||||||
|
implementation("io.quarkus:quarkus-config-yaml")
|
||||||
|
implementation("io.quarkus:quarkus-arc")
|
||||||
|
implementation("io.quarkus:quarkus-hibernate-orm-panache")
|
||||||
|
implementation("io.quarkus:quarkus-jdbc-postgresql")
|
||||||
|
implementation("io.quarkus:quarkus-smallrye-jwt")
|
||||||
|
implementation("io.quarkus:quarkus-smallrye-jwt-build")
|
||||||
|
implementation("io.quarkus:quarkus-elytron-security-common")
|
||||||
|
implementation("io.quarkus:quarkus-smallrye-health")
|
||||||
|
implementation("io.quarkus:quarkus-micrometer")
|
||||||
|
implementation("io.quarkus:quarkus-smallrye-openapi")
|
||||||
|
implementation("com.fasterxml.jackson.module:jackson-module-scala_3:${versions["JACKSON_SCALA"]!!}")
|
||||||
|
implementation("io.quarkus:quarkus-redis-client")
|
||||||
|
|
||||||
|
testImplementation(platform("org.junit:junit-bom:5.13.4"))
|
||||||
|
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||||
|
testImplementation("org.scalatest:scalatest_3:${versions["SCALATEST"]!!}")
|
||||||
|
testImplementation("co.helmethair:scalatest-junit-runner:${versions["SCALATEST_JUNIT"]!!}")
|
||||||
|
testImplementation("io.quarkus:quarkus-junit")
|
||||||
|
testImplementation("io.rest-assured:rest-assured")
|
||||||
|
testImplementation("io.quarkus:quarkus-jdbc-h2")
|
||||||
|
testImplementation("io.quarkus:quarkus-test-security")
|
||||||
|
testImplementation("io.quarkus:quarkus-junit5-mockito")
|
||||||
|
|
||||||
|
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations.matching { !it.name.startsWith("scoverage") }.configureEach {
|
||||||
|
resolutionStrategy.force("org.scala-lang:scala-library:${versions["SCALA_LIBRARY"]!!}")
|
||||||
|
}
|
||||||
|
configurations.scoverage {
|
||||||
|
resolutionStrategy.eachDependency {
|
||||||
|
if (requested.group == "org.scoverage" && requested.name.startsWith("scalac-scoverage-plugin_")) {
|
||||||
|
useTarget("${requested.group}:scalac-scoverage-plugin_2.13.16:2.3.0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<JavaCompile> {
|
||||||
|
options.encoding = "UTF-8"
|
||||||
|
options.compilerArgs.add("-parameters")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<Jar>().configureEach {
|
||||||
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.test {
|
||||||
|
useJUnitPlatform {
|
||||||
|
includeEngines("scalatest", "junit-jupiter")
|
||||||
|
testLogging {
|
||||||
|
events("passed", "skipped", "failed")
|
||||||
|
showStandardStreams = true
|
||||||
|
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finalizedBy(tasks.reportScoverage)
|
||||||
|
}
|
||||||
|
tasks.reportScoverage {
|
||||||
|
dependsOn(tasks.test)
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.jar {
|
||||||
|
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
||||||
|
}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"reflection": [
|
||||||
|
{ "type": "scala.Tuple1[]" },
|
||||||
|
{ "type": "scala.Tuple2[]" },
|
||||||
|
{ "type": "scala.Tuple3[]" },
|
||||||
|
{ "type": "scala.Tuple4[]" },
|
||||||
|
{ "type": "scala.Tuple5[]" },
|
||||||
|
{ "type": "scala.Tuple6[]" },
|
||||||
|
{ "type": "scala.Tuple7[]" },
|
||||||
|
{ "type": "scala.Tuple8[]" },
|
||||||
|
{ "type": "scala.Tuple9[]" },
|
||||||
|
{ "type": "scala.Tuple10[]" },
|
||||||
|
{ "type": "scala.Tuple11[]" },
|
||||||
|
{ "type": "scala.Tuple12[]" },
|
||||||
|
{ "type": "scala.Tuple13[]" },
|
||||||
|
{ "type": "scala.Tuple14[]" },
|
||||||
|
{ "type": "scala.Tuple15[]" },
|
||||||
|
{ "type": "scala.Tuple16[]" },
|
||||||
|
{ "type": "scala.Tuple17[]" },
|
||||||
|
{ "type": "scala.Tuple18[]" },
|
||||||
|
{ "type": "scala.Tuple19[]" },
|
||||||
|
{ "type": "scala.Tuple20[]" },
|
||||||
|
{ "type": "scala.Tuple21[]" },
|
||||||
|
{ "type": "scala.Tuple22[]" },
|
||||||
|
{ "type": "com.fasterxml.jackson.module.scala.introspect.PropertyDescriptor[]" }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
quarkus:
|
||||||
|
http:
|
||||||
|
port: 8083
|
||||||
|
application:
|
||||||
|
name: nowchess-account
|
||||||
|
redis:
|
||||||
|
hosts: redis://${REDIS_HOST:localhost}:${REDIS_PORT:6379}
|
||||||
|
rest-client:
|
||||||
|
core-service:
|
||||||
|
url: http://localhost:8080
|
||||||
|
smallrye-openapi:
|
||||||
|
info-title: NowChess Account Service
|
||||||
|
path: /openapi
|
||||||
|
swagger-ui:
|
||||||
|
always-include: true
|
||||||
|
path: /swagger-ui
|
||||||
|
datasource:
|
||||||
|
db-kind: h2
|
||||||
|
username: sa
|
||||||
|
password: ""
|
||||||
|
jdbc:
|
||||||
|
url: jdbc:h2:mem:nowchess;DB_CLOSE_DELAY=-1
|
||||||
|
hibernate-orm:
|
||||||
|
schema-management:
|
||||||
|
strategy: drop-and-create
|
||||||
|
|
||||||
|
nowchess:
|
||||||
|
redis:
|
||||||
|
host: localhost
|
||||||
|
port: 6379
|
||||||
|
prefix: nowchess
|
||||||
|
internal:
|
||||||
|
secret: 123abc
|
||||||
|
|
||||||
|
"%deployed":
|
||||||
|
quarkus:
|
||||||
|
rest-client:
|
||||||
|
core-service:
|
||||||
|
url: ${CORE_SERVICE_URL}
|
||||||
|
nowchess:
|
||||||
|
redis:
|
||||||
|
host: ${REDIS_HOST:localhost}
|
||||||
|
port: ${REDIS_PORT:6379}
|
||||||
|
prefix: ${REDIS_PREFIX:nowchess}
|
||||||
|
datasource:
|
||||||
|
db-kind: postgresql
|
||||||
|
username: ${DB_USER}
|
||||||
|
password: ${DB_PASSWORD}
|
||||||
|
jdbc:
|
||||||
|
url: ${DB_URL}
|
||||||
|
hibernate-orm:
|
||||||
|
schema-management:
|
||||||
|
strategy: update
|
||||||
|
mp:
|
||||||
|
jwt:
|
||||||
|
verify:
|
||||||
|
publickey:
|
||||||
|
location: ${JWT_PUBLIC_KEY_PATH:keys/public.pem}
|
||||||
|
issuer: nowchess
|
||||||
|
smallrye:
|
||||||
|
jwt:
|
||||||
|
sign:
|
||||||
|
key:
|
||||||
|
location: ${JWT_PRIVATE_KEY_PATH:keys/private.pem}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxDsnsCAl0vQx7Vu9CLDZ
|
||||||
|
g0SG05NgUzu9T+3DTEaHGq60T2uriO8BenwyvsF3BnDqTbKf4voohZ1DNfzdbT1J
|
||||||
|
Fj8B62FrDmxcO+sp1/b5HUCJP6y2uSRCmzOHe5k7Pk1IEi72FgBpKXSRkFibRlVf
|
||||||
|
634g7mgsPZAQ9PJEsv4Qvm05T9L6+Gmq6N3bMVLKRXs4RhDhaFbYH9GtUg1eI0yH
|
||||||
|
YjGyRfqzW/nqVMstOLHt8CuPouq4p7eMzeDH3YHkxPm4GG5foCXMOd2DZrW0SCcr
|
||||||
|
7dhFeNVWzQ2m53eOhBzNQX+v3pgjVStsePhBRt2LyGfwkNzmqDgqWsMzSHRMY+cn
|
||||||
|
WQIDAQAB
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package de.nowchess.account.client
|
||||||
|
|
||||||
|
import de.nowchess.security.InternalSecretClientFilter
|
||||||
|
import jakarta.ws.rs.*
|
||||||
|
import jakarta.ws.rs.core.MediaType
|
||||||
|
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider
|
||||||
|
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient
|
||||||
|
|
||||||
|
case class CorePlayerInfo(id: String, displayName: String)
|
||||||
|
case class CoreTimeControl(limitSeconds: Option[Int], incrementSeconds: Option[Int], daysPerMove: Option[Int])
|
||||||
|
case class CoreCreateGameRequest(
|
||||||
|
white: Option[CorePlayerInfo],
|
||||||
|
black: Option[CorePlayerInfo],
|
||||||
|
timeControl: Option[CoreTimeControl],
|
||||||
|
mode: Option[String],
|
||||||
|
)
|
||||||
|
case class CoreGameResponse(gameId: String)
|
||||||
|
|
||||||
|
@Path("/api/board/game")
|
||||||
|
@RegisterRestClient(configKey = "core-service")
|
||||||
|
@RegisterProvider(classOf[InternalSecretClientFilter])
|
||||||
|
trait CoreGameClient:
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||||
|
@Produces(Array(MediaType.APPLICATION_JSON))
|
||||||
|
def createGame(req: CoreCreateGameRequest): CoreGameResponse
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package de.nowchess.account.config
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.Version
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import com.fasterxml.jackson.module.scala.DefaultScalaModule
|
||||||
|
import io.quarkus.jackson.ObjectMapperCustomizer
|
||||||
|
import jakarta.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class JacksonConfig extends ObjectMapperCustomizer:
|
||||||
|
def customize(mapper: ObjectMapper): Unit =
|
||||||
|
mapper.registerModule(new DefaultScalaModule() {
|
||||||
|
override def version(): Version =
|
||||||
|
// scalafix:off DisableSyntax.null
|
||||||
|
new Version(2, 21, 1, null, "com.fasterxml.jackson.module", "jackson-module-scala")
|
||||||
|
// scalafix:on DisableSyntax.null
|
||||||
|
})
|
||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
package de.nowchess.account.config
|
||||||
|
|
||||||
|
import de.nowchess.account.client.{CoreCreateGameRequest, CoreGameResponse, CorePlayerInfo, CoreTimeControl}
|
||||||
|
import de.nowchess.account.domain.{
|
||||||
|
BotAccount,
|
||||||
|
Challenge,
|
||||||
|
ChallengeColor,
|
||||||
|
ChallengeStatus,
|
||||||
|
DeclineReason,
|
||||||
|
OfficialBotAccount,
|
||||||
|
TimeControl,
|
||||||
|
UserAccount,
|
||||||
|
}
|
||||||
|
import de.nowchess.account.dto.*
|
||||||
|
import io.quarkus.runtime.annotations.RegisterForReflection
|
||||||
|
|
||||||
|
@RegisterForReflection(
|
||||||
|
targets = Array(
|
||||||
|
classOf[UserAccount],
|
||||||
|
classOf[BotAccount],
|
||||||
|
classOf[OfficialBotAccount],
|
||||||
|
classOf[Challenge],
|
||||||
|
classOf[ChallengeColor],
|
||||||
|
classOf[ChallengeStatus],
|
||||||
|
classOf[DeclineReason],
|
||||||
|
classOf[TimeControl],
|
||||||
|
classOf[LoginRequest],
|
||||||
|
classOf[TokenResponse],
|
||||||
|
classOf[PlayerInfo],
|
||||||
|
classOf[PublicAccountDto],
|
||||||
|
classOf[BotAccountDto],
|
||||||
|
classOf[BotAccountWithTokenDto],
|
||||||
|
classOf[OfficialBotAccountDto],
|
||||||
|
classOf[CreateBotAccountRequest],
|
||||||
|
classOf[UpdateBotNameRequest],
|
||||||
|
classOf[RotatedTokenDto],
|
||||||
|
classOf[TimeControlDto],
|
||||||
|
classOf[ChallengeRequest],
|
||||||
|
classOf[ChallengeDto],
|
||||||
|
classOf[DeclineRequest],
|
||||||
|
classOf[ChallengeListDto],
|
||||||
|
classOf[ErrorDto],
|
||||||
|
classOf[CorePlayerInfo],
|
||||||
|
classOf[CoreTimeControl],
|
||||||
|
classOf[CoreCreateGameRequest],
|
||||||
|
classOf[CoreGameResponse],
|
||||||
|
classOf[OfficialChallengeResponse],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
class NativeReflectionConfig
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package de.nowchess.account.config
|
||||||
|
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped
|
||||||
|
import org.eclipse.microprofile.config.inject.ConfigProperty
|
||||||
|
import scala.compiletime.uninitialized
|
||||||
|
|
||||||
|
@ApplicationScoped
|
||||||
|
class RedisConfig:
|
||||||
|
// scalafix:off DisableSyntax.var
|
||||||
|
@ConfigProperty(name = "nowchess.redis.prefix", defaultValue = "nowchess")
|
||||||
|
var prefix: String = uninitialized
|
||||||
|
// scalafix:on DisableSyntax.var
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package de.nowchess.account.domain
|
||||||
|
|
||||||
|
import io.quarkus.hibernate.orm.panache.PanacheEntityBase
|
||||||
|
import jakarta.persistence.*
|
||||||
|
import scala.compiletime.uninitialized
|
||||||
|
|
||||||
|
import java.time.Instant
|
||||||
|
import java.util.UUID
|
||||||
|
import scala.Conversion
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "challenges")
|
||||||
|
class Challenge extends PanacheEntityBase:
|
||||||
|
// scalafix:off DisableSyntax.var
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.UUID)
|
||||||
|
var id: UUID = uninitialized
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
var challenger: UserAccount = uninitialized
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
var destUser: UserAccount = uninitialized
|
||||||
|
|
||||||
|
@Convert(converter = classOf[ChallengeColorConverter])
|
||||||
|
@Column(columnDefinition = "varchar(255)")
|
||||||
|
var color: ChallengeColor = uninitialized
|
||||||
|
|
||||||
|
@Convert(converter = classOf[ChallengeStatusConverter])
|
||||||
|
@Column(columnDefinition = "varchar(255)")
|
||||||
|
var status: ChallengeStatus = uninitialized
|
||||||
|
|
||||||
|
@Convert(converter = classOf[DeclineReasonConverter])
|
||||||
|
@Column(nullable = true, columnDefinition = "varchar(255)")
|
||||||
|
var declineReason: DeclineReason = uninitialized
|
||||||
|
|
||||||
|
var timeControlType: String = uninitialized
|
||||||
|
|
||||||
|
@Column(nullable = true)
|
||||||
|
var timeControlLimit: java.lang.Integer = uninitialized
|
||||||
|
|
||||||
|
@Column(nullable = true)
|
||||||
|
var timeControlIncrement: java.lang.Integer = uninitialized
|
||||||
|
|
||||||
|
var createdAt: Instant = uninitialized
|
||||||
|
|
||||||
|
var expiresAt: Instant = uninitialized
|
||||||
|
|
||||||
|
@Column(nullable = true)
|
||||||
|
var gameId: String = uninitialized
|
||||||
|
// scalafix:on
|
||||||
|
|
||||||
|
def gameIdOpt: Option[String] = Option(gameId)
|
||||||
|
def declineReasonOpt: Option[DeclineReason] = Option(declineReason)
|
||||||
|
def timeControlLimitOpt: Option[Int] = Option(timeControlLimit).map(_.intValue())
|
||||||
|
def timeControlIncrementOpt: Option[Int] = Option(timeControlIncrement).map(_.intValue())
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package de.nowchess.account.domain
|
||||||
|
|
||||||
|
enum ChallengeColor:
|
||||||
|
case White, Black, Random
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
package de.nowchess.account.domain
|
||||||
|
|
||||||
|
import jakarta.persistence.AttributeConverter
|
||||||
|
import jakarta.persistence.Converter
|
||||||
|
|
||||||
|
@Converter(autoApply = true)
|
||||||
|
class ChallengeColorConverter extends AttributeConverter[ChallengeColor, String]:
|
||||||
|
override def convertToDatabaseColumn(attribute: ChallengeColor): String =
|
||||||
|
Option(attribute).map(_.toString).orNull
|
||||||
|
|
||||||
|
override def convertToEntityAttribute(dbData: String): ChallengeColor =
|
||||||
|
Option(dbData).map(ChallengeColor.valueOf).orNull
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package de.nowchess.account.domain
|
||||||
|
|
||||||
|
enum ChallengeStatus:
|
||||||
|
case Created, Canceled, Declined, Accepted
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
package de.nowchess.account.domain
|
||||||
|
|
||||||
|
import jakarta.persistence.AttributeConverter
|
||||||
|
import jakarta.persistence.Converter
|
||||||
|
|
||||||
|
@Converter(autoApply = true)
|
||||||
|
class ChallengeStatusConverter extends AttributeConverter[ChallengeStatus, String]:
|
||||||
|
override def convertToDatabaseColumn(attribute: ChallengeStatus): String =
|
||||||
|
Option(attribute).map(_.toString).orNull
|
||||||
|
|
||||||
|
override def convertToEntityAttribute(dbData: String): ChallengeStatus =
|
||||||
|
Option(dbData).map(ChallengeStatus.valueOf).orNull
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package de.nowchess.account.domain
|
||||||
|
|
||||||
|
enum DeclineReason:
|
||||||
|
case Generic, Later, TooFast, TooSlow, TimeControl, Rated, Casual, Standard, Variant, NoBot, OnlyBot
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
package de.nowchess.account.domain
|
||||||
|
|
||||||
|
import jakarta.persistence.AttributeConverter
|
||||||
|
import jakarta.persistence.Converter
|
||||||
|
|
||||||
|
@Converter(autoApply = true)
|
||||||
|
class DeclineReasonConverter extends AttributeConverter[DeclineReason, String]:
|
||||||
|
override def convertToDatabaseColumn(attribute: DeclineReason): String =
|
||||||
|
Option(attribute).map(_.toString).orNull
|
||||||
|
|
||||||
|
override def convertToEntityAttribute(dbData: String): DeclineReason =
|
||||||
|
Option(dbData).map(DeclineReason.valueOf).orNull
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package de.nowchess.account.domain
|
||||||
|
|
||||||
|
sealed trait TimeControl
|
||||||
|
|
||||||
|
object TimeControl:
|
||||||
|
case class Clock(limit: Int, increment: Int) extends TimeControl
|
||||||
|
case object Unlimited extends TimeControl
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package de.nowchess.account.domain
|
||||||
|
|
||||||
|
import io.quarkus.hibernate.orm.panache.PanacheEntityBase
|
||||||
|
import jakarta.persistence.*
|
||||||
|
import scala.compiletime.uninitialized
|
||||||
|
import scala.jdk.CollectionConverters.*
|
||||||
|
|
||||||
|
import java.time.Instant
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "user_accounts")
|
||||||
|
class UserAccount extends PanacheEntityBase:
|
||||||
|
// scalafix:off DisableSyntax.var
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.UUID)
|
||||||
|
var id: UUID = uninitialized
|
||||||
|
|
||||||
|
@Column(unique = true, nullable = false)
|
||||||
|
var username: String = uninitialized
|
||||||
|
|
||||||
|
@Column(unique = true, nullable = false)
|
||||||
|
var email: String = uninitialized
|
||||||
|
|
||||||
|
var passwordHash: String = uninitialized
|
||||||
|
|
||||||
|
var rating: Int = 1500
|
||||||
|
|
||||||
|
var createdAt: Instant = uninitialized
|
||||||
|
|
||||||
|
var banned: Boolean = false
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "owner", cascade = Array(CascadeType.ALL), orphanRemoval = true)
|
||||||
|
var botAccounts: java.util.List[BotAccount] = uninitialized
|
||||||
|
// scalafix:on
|
||||||
|
|
||||||
|
def getBotAccounts: List[BotAccount] = Option(botAccounts).map(_.asScala.toList).getOrElse(Nil)
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "bot_accounts")
|
||||||
|
class BotAccount extends PanacheEntityBase:
|
||||||
|
// scalafix:off DisableSyntax.var
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.UUID)
|
||||||
|
var id: UUID = uninitialized
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
var name: String = uninitialized
|
||||||
|
|
||||||
|
@ManyToOne(optional = false)
|
||||||
|
@JoinColumn(name = "owner_id", nullable = false)
|
||||||
|
var owner: UserAccount = uninitialized
|
||||||
|
|
||||||
|
@Column(unique = true, nullable = false, length = 256)
|
||||||
|
var token: String = uninitialized
|
||||||
|
|
||||||
|
var rating: Int = 1500
|
||||||
|
|
||||||
|
var createdAt: Instant = uninitialized
|
||||||
|
|
||||||
|
var banned: Boolean = false
|
||||||
|
// scalafix:on
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "official_bot_accounts")
|
||||||
|
class OfficialBotAccount extends PanacheEntityBase:
|
||||||
|
// scalafix:off DisableSyntax.var
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.UUID)
|
||||||
|
var id: UUID = uninitialized
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
var name: String = uninitialized
|
||||||
|
|
||||||
|
var rating: Int = 1500
|
||||||
|
|
||||||
|
var createdAt: Instant = uninitialized
|
||||||
|
// scalafix:on
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package de.nowchess.account.dto
|
||||||
|
|
||||||
|
case class RegisterRequest(username: String, email: String, password: String)
|
||||||
|
|
||||||
|
case class LoginRequest(username: String, password: String)
|
||||||
|
|
||||||
|
case class TokenResponse(token: String)
|
||||||
|
|
||||||
|
case class PlayerInfo(id: String, name: String, rating: Int)
|
||||||
|
|
||||||
|
case class PublicAccountDto(id: String, username: String, rating: Int, createdAt: String)
|
||||||
|
|
||||||
|
case class TimeControlDto(`type`: String, limit: Option[Int], increment: Option[Int])
|
||||||
|
|
||||||
|
case class ChallengeRequest(color: String, timeControl: TimeControlDto)
|
||||||
|
|
||||||
|
case class ChallengeDto(
|
||||||
|
id: String,
|
||||||
|
challenger: PlayerInfo,
|
||||||
|
destUser: PlayerInfo,
|
||||||
|
variant: String,
|
||||||
|
color: String,
|
||||||
|
timeControl: TimeControlDto,
|
||||||
|
status: String,
|
||||||
|
declineReason: Option[String],
|
||||||
|
gameId: Option[String],
|
||||||
|
createdAt: String,
|
||||||
|
expiresAt: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
case class DeclineRequest(reason: Option[String])
|
||||||
|
|
||||||
|
case class ChallengeListDto(in: List[ChallengeDto], out: List[ChallengeDto])
|
||||||
|
|
||||||
|
case class ErrorDto(error: String)
|
||||||
|
|
||||||
|
case class CreateBotAccountRequest(name: String)
|
||||||
|
|
||||||
|
case class UpdateBotNameRequest(name: String)
|
||||||
|
|
||||||
|
case class BotAccountDto(id: String, name: String, rating: Int, createdAt: String)
|
||||||
|
|
||||||
|
case class BotAccountWithTokenDto(id: String, name: String, rating: Int, token: String, createdAt: String)
|
||||||
|
|
||||||
|
case class RotatedTokenDto(token: String)
|
||||||
|
|
||||||
|
case class OfficialBotAccountDto(id: String, name: String, rating: Int, createdAt: String)
|
||||||
|
|
||||||
|
case class OfficialChallengeResponse(gameId: String, botName: String, difficulty: Int)
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package de.nowchess.account.error
|
||||||
|
|
||||||
|
enum AccountError:
|
||||||
|
case UsernameTaken(username: String)
|
||||||
|
case EmailAlreadyRegistered(email: String)
|
||||||
|
case InvalidCredentials
|
||||||
|
case UserNotFound
|
||||||
|
case BotNotFound
|
||||||
|
case BotLimitExceeded
|
||||||
|
case NotAuthorized
|
||||||
|
case UserBanned
|
||||||
|
case BotBanned
|
||||||
|
|
||||||
|
def message: String = this match
|
||||||
|
case UsernameTaken(u) => s"Username '$u' is already taken"
|
||||||
|
case EmailAlreadyRegistered(e) => s"Email '$e' is already registered"
|
||||||
|
case InvalidCredentials => "Invalid credentials"
|
||||||
|
case UserNotFound => "User not found"
|
||||||
|
case BotNotFound => "Bot account not found"
|
||||||
|
case BotLimitExceeded => "Maximum of 5 bot accounts per user exceeded"
|
||||||
|
case NotAuthorized => "Not authorized to perform this action"
|
||||||
|
case UserBanned => "User account is banned"
|
||||||
|
case BotBanned => "Bot account is banned"
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package de.nowchess.account.error
|
||||||
|
|
||||||
|
enum ChallengeError:
|
||||||
|
case UserNotFound(username: String)
|
||||||
|
case ChallengerNotFound
|
||||||
|
case CannotChallengeSelf
|
||||||
|
case DuplicateChallenge
|
||||||
|
case InvalidColor(color: String)
|
||||||
|
case InvalidDeclineReason(reason: String)
|
||||||
|
case ChallengeNotFound
|
||||||
|
case ChallengeNotActive
|
||||||
|
case NotAuthorized
|
||||||
|
case GameCreationFailed
|
||||||
|
|
||||||
|
def message: String = this match
|
||||||
|
case UserNotFound(u) => s"User '$u' not found"
|
||||||
|
case ChallengerNotFound => "Challenger not found"
|
||||||
|
case CannotChallengeSelf => "Cannot challenge yourself"
|
||||||
|
case DuplicateChallenge => "Active challenge to this user already exists"
|
||||||
|
case InvalidColor(c) => s"Unknown color: $c"
|
||||||
|
case InvalidDeclineReason(r) => s"Unknown decline reason: $r"
|
||||||
|
case ChallengeNotFound => "Challenge not found"
|
||||||
|
case ChallengeNotActive => "Challenge is not active"
|
||||||
|
case NotAuthorized => "Not authorized"
|
||||||
|
case GameCreationFailed => "Failed to create game"
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package de.nowchess.account.filter
|
||||||
|
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped
|
||||||
|
import jakarta.inject.Inject
|
||||||
|
import jakarta.ws.rs.container.{ContainerRequestContext, ContainerRequestFilter}
|
||||||
|
import jakarta.ws.rs.core.Response
|
||||||
|
import jakarta.ws.rs.ext.Provider
|
||||||
|
import org.eclipse.microprofile.jwt.JsonWebToken
|
||||||
|
import scala.compiletime.uninitialized
|
||||||
|
|
||||||
|
@Provider
|
||||||
|
@ApplicationScoped
|
||||||
|
class AlreadyLoggedInFilter extends ContainerRequestFilter:
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
// scalafix:off DisableSyntax.var
|
||||||
|
var jwt: JsonWebToken = uninitialized
|
||||||
|
// scalafix:on
|
||||||
|
|
||||||
|
override def filter(context: ContainerRequestContext): Unit =
|
||||||
|
val path = context.getUriInfo.getPath
|
||||||
|
val method = context.getMethod
|
||||||
|
|
||||||
|
if isProtectedEndpoint(path, method) && isAuthenticated then
|
||||||
|
context.abortWith(
|
||||||
|
Response
|
||||||
|
.status(Response.Status.BAD_REQUEST)
|
||||||
|
.entity("""{"error":"Already logged in"}""")
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
|
||||||
|
private def isAuthenticated: Boolean =
|
||||||
|
// scalafix:off DisableSyntax.null
|
||||||
|
try jwt.getName != null
|
||||||
|
catch
|
||||||
|
case _ => false
|
||||||
|
// scalafix:on DisableSyntax.null
|
||||||
|
|
||||||
|
private def isProtectedEndpoint(path: String, method: String): Boolean =
|
||||||
|
(path.contains("/api/account") || path.contains("/account")) &&
|
||||||
|
((path.endsWith("/api/account") && method == "POST") ||
|
||||||
|
(path.endsWith("/account") && method == "POST") ||
|
||||||
|
(path.contains("/login") && method == "POST"))
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package de.nowchess.account.repository
|
||||||
|
|
||||||
|
import de.nowchess.account.domain.{BotAccount, OfficialBotAccount, UserAccount}
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped
|
||||||
|
import jakarta.inject.Inject
|
||||||
|
import jakarta.persistence.EntityManager
|
||||||
|
|
||||||
|
import java.util.UUID
|
||||||
|
import scala.jdk.CollectionConverters.*
|
||||||
|
|
||||||
|
@ApplicationScoped
|
||||||
|
class UserAccountRepository:
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
// scalafix:off DisableSyntax.var
|
||||||
|
var em: EntityManager = scala.compiletime.uninitialized
|
||||||
|
// scalafix:on
|
||||||
|
|
||||||
|
def findByUsername(username: String): Option[UserAccount] =
|
||||||
|
em.createQuery("FROM UserAccount WHERE username = :username", classOf[UserAccount])
|
||||||
|
.setParameter("username", username)
|
||||||
|
.getResultList
|
||||||
|
.stream()
|
||||||
|
.findFirst()
|
||||||
|
.map(Option(_))
|
||||||
|
.orElse(None)
|
||||||
|
|
||||||
|
def findById(id: UUID): Option[UserAccount] =
|
||||||
|
Option(em.find(classOf[UserAccount], id))
|
||||||
|
|
||||||
|
def persist(account: UserAccount): UserAccount =
|
||||||
|
em.persist(account)
|
||||||
|
account
|
||||||
|
|
||||||
|
def findByEmail(email: String): Option[UserAccount] =
|
||||||
|
em.createQuery("FROM UserAccount WHERE email = :email", classOf[UserAccount])
|
||||||
|
.setParameter("email", email)
|
||||||
|
.getResultList
|
||||||
|
.asScala
|
||||||
|
.headOption
|
||||||
|
|
||||||
|
def findAll(): List[UserAccount] =
|
||||||
|
em.createQuery("FROM UserAccount", classOf[UserAccount]).getResultList.asScala.toList
|
||||||
|
|
||||||
|
@ApplicationScoped
|
||||||
|
class BotAccountRepository:
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
// scalafix:off DisableSyntax.var
|
||||||
|
var em: EntityManager = scala.compiletime.uninitialized
|
||||||
|
// scalafix:on
|
||||||
|
|
||||||
|
def findById(id: UUID): Option[BotAccount] =
|
||||||
|
Option(em.find(classOf[BotAccount], id))
|
||||||
|
|
||||||
|
def findByOwner(ownerId: UUID): List[BotAccount] =
|
||||||
|
em.createQuery("FROM BotAccount WHERE owner.id = :ownerId", classOf[BotAccount])
|
||||||
|
.setParameter("ownerId", ownerId)
|
||||||
|
.getResultList
|
||||||
|
.asScala
|
||||||
|
.toList
|
||||||
|
|
||||||
|
def persist(bot: BotAccount): BotAccount =
|
||||||
|
em.persist(bot)
|
||||||
|
bot
|
||||||
|
|
||||||
|
def delete(botId: UUID): Unit =
|
||||||
|
em.find(classOf[BotAccount], botId) match
|
||||||
|
case bot: BotAccount => em.remove(bot)
|
||||||
|
|
||||||
|
def findByToken(token: String): Option[BotAccount] =
|
||||||
|
em.createQuery("FROM BotAccount WHERE token = :token", classOf[BotAccount])
|
||||||
|
.setParameter("token", token)
|
||||||
|
.getResultList
|
||||||
|
.asScala
|
||||||
|
.headOption
|
||||||
|
|
||||||
|
@ApplicationScoped
|
||||||
|
class OfficialBotAccountRepository:
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
// scalafix:off DisableSyntax.var
|
||||||
|
var em: EntityManager = scala.compiletime.uninitialized
|
||||||
|
// scalafix:on
|
||||||
|
|
||||||
|
def findById(id: UUID): Option[OfficialBotAccount] =
|
||||||
|
Option(em.find(classOf[OfficialBotAccount], id))
|
||||||
|
|
||||||
|
def findAll(): List[OfficialBotAccount] =
|
||||||
|
em.createQuery("FROM OfficialBotAccount", classOf[OfficialBotAccount]).getResultList.asScala.toList
|
||||||
|
|
||||||
|
def persist(bot: OfficialBotAccount): OfficialBotAccount =
|
||||||
|
em.persist(bot)
|
||||||
|
bot
|
||||||
|
|
||||||
|
def delete(botId: UUID): Unit =
|
||||||
|
em.find(classOf[OfficialBotAccount], botId) match
|
||||||
|
case bot: OfficialBotAccount => em.remove(bot)
|
||||||
+62
@@ -0,0 +1,62 @@
|
|||||||
|
package de.nowchess.account.repository
|
||||||
|
|
||||||
|
import de.nowchess.account.domain.{Challenge, ChallengeStatus}
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped
|
||||||
|
import jakarta.inject.Inject
|
||||||
|
import jakarta.persistence.EntityManager
|
||||||
|
|
||||||
|
import java.time.Instant
|
||||||
|
import java.util.UUID
|
||||||
|
import scala.jdk.CollectionConverters.*
|
||||||
|
|
||||||
|
@ApplicationScoped
|
||||||
|
class ChallengeRepository:
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
// scalafix:off DisableSyntax.var
|
||||||
|
var em: EntityManager = scala.compiletime.uninitialized
|
||||||
|
// scalafix:on
|
||||||
|
|
||||||
|
def findActiveByChallengerId(challengerId: UUID): List[Challenge] =
|
||||||
|
em.createQuery(
|
||||||
|
"FROM Challenge WHERE challenger.id = :cid AND status = :status AND expiresAt > :now",
|
||||||
|
classOf[Challenge],
|
||||||
|
).setParameter("cid", challengerId)
|
||||||
|
.setParameter("status", ChallengeStatus.Created)
|
||||||
|
.setParameter("now", Instant.now())
|
||||||
|
.getResultList
|
||||||
|
.asScala
|
||||||
|
.toList
|
||||||
|
|
||||||
|
def findActiveByDestUserId(destUserId: UUID): List[Challenge] =
|
||||||
|
em.createQuery(
|
||||||
|
"FROM Challenge WHERE destUser.id = :uid AND status = :status AND expiresAt > :now",
|
||||||
|
classOf[Challenge],
|
||||||
|
).setParameter("uid", destUserId)
|
||||||
|
.setParameter("status", ChallengeStatus.Created)
|
||||||
|
.setParameter("now", Instant.now())
|
||||||
|
.getResultList
|
||||||
|
.asScala
|
||||||
|
.toList
|
||||||
|
|
||||||
|
def findDuplicateChallenge(challengerId: UUID, destUserId: UUID): Option[Challenge] =
|
||||||
|
em.createQuery(
|
||||||
|
"FROM Challenge WHERE challenger.id = :cid AND destUser.id = :uid AND status = :status AND expiresAt > :now",
|
||||||
|
classOf[Challenge],
|
||||||
|
).setParameter("cid", challengerId)
|
||||||
|
.setParameter("uid", destUserId)
|
||||||
|
.setParameter("status", ChallengeStatus.Created)
|
||||||
|
.setParameter("now", Instant.now())
|
||||||
|
.getResultList
|
||||||
|
.asScala
|
||||||
|
.headOption
|
||||||
|
|
||||||
|
def findById(id: UUID): Option[Challenge] =
|
||||||
|
Option(em.find(classOf[Challenge], id))
|
||||||
|
|
||||||
|
def persist(challenge: Challenge): Challenge =
|
||||||
|
em.persist(challenge)
|
||||||
|
challenge
|
||||||
|
|
||||||
|
def merge(challenge: Challenge): Challenge =
|
||||||
|
em.merge(challenge)
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
package de.nowchess.account.resource
|
||||||
|
|
||||||
|
import de.nowchess.account.domain.{BotAccount, OfficialBotAccount, UserAccount}
|
||||||
|
import de.nowchess.account.dto.*
|
||||||
|
import de.nowchess.account.error.AccountError
|
||||||
|
import de.nowchess.account.service.AccountService
|
||||||
|
import jakarta.annotation.security.RolesAllowed
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped
|
||||||
|
import jakarta.inject.Inject
|
||||||
|
import jakarta.ws.rs.*
|
||||||
|
import jakarta.ws.rs.core.{MediaType, Response}
|
||||||
|
import org.eclipse.microprofile.jwt.JsonWebToken
|
||||||
|
import scala.compiletime.uninitialized
|
||||||
|
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@Path("/api/account")
|
||||||
|
@ApplicationScoped
|
||||||
|
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||||
|
@Produces(Array(MediaType.APPLICATION_JSON))
|
||||||
|
class AccountResource:
|
||||||
|
|
||||||
|
// scalafix:off DisableSyntax.var
|
||||||
|
@Inject
|
||||||
|
var accountService: AccountService = uninitialized
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
var jwt: JsonWebToken = uninitialized
|
||||||
|
// scalafix:on
|
||||||
|
|
||||||
|
@POST
|
||||||
|
def register(req: RegisterRequest): Response =
|
||||||
|
accountService.register(req) match
|
||||||
|
case Right(account) =>
|
||||||
|
Response.ok(toPublicDto(account)).build()
|
||||||
|
case Left(error) =>
|
||||||
|
Response.status(Response.Status.CONFLICT).entity(ErrorDto(error.message)).build()
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/login")
|
||||||
|
def login(req: LoginRequest): Response =
|
||||||
|
accountService.login(req) match
|
||||||
|
case Right(token) =>
|
||||||
|
Response.ok(TokenResponse(token)).build()
|
||||||
|
case Left(AccountError.UserBanned) =>
|
||||||
|
Response.status(Response.Status.FORBIDDEN).entity(ErrorDto(AccountError.UserBanned.message)).build()
|
||||||
|
case Left(error) =>
|
||||||
|
Response.status(Response.Status.UNAUTHORIZED).entity(ErrorDto(error.message)).build()
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/me")
|
||||||
|
@RolesAllowed(Array("**"))
|
||||||
|
def me(): Response =
|
||||||
|
val id = UUID.fromString(jwt.getSubject)
|
||||||
|
accountService.findById(id) match
|
||||||
|
case Some(account) => Response.ok(toPublicDto(account)).build()
|
||||||
|
case None => Response.status(Response.Status.NOT_FOUND).build()
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/{username}")
|
||||||
|
def publicProfile(@PathParam("username") username: String): Response =
|
||||||
|
accountService.findByUsername(username) match
|
||||||
|
case Some(account) => Response.ok(toPublicDto(account)).build()
|
||||||
|
case None => Response.status(Response.Status.NOT_FOUND).build()
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/{userId}/ban")
|
||||||
|
@RolesAllowed(Array("Admin"))
|
||||||
|
def banUser(@PathParam("userId") userId: String): Response =
|
||||||
|
accountService.banUser(UUID.fromString(userId)) match
|
||||||
|
case Right(user) => Response.ok(toPublicDto(user)).build()
|
||||||
|
case Left(error) =>
|
||||||
|
Response.status(Response.Status.NOT_FOUND).entity(ErrorDto(error.message)).build()
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/{userId}/unban")
|
||||||
|
@RolesAllowed(Array("Admin"))
|
||||||
|
def unbanUser(@PathParam("userId") userId: String): Response =
|
||||||
|
accountService.unbanUser(UUID.fromString(userId)) match
|
||||||
|
case Right(user) => Response.ok(toPublicDto(user)).build()
|
||||||
|
case Left(error) =>
|
||||||
|
Response.status(Response.Status.NOT_FOUND).entity(ErrorDto(error.message)).build()
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/bots")
|
||||||
|
@RolesAllowed(Array("**"))
|
||||||
|
def createBotAccount(req: CreateBotAccountRequest): Response =
|
||||||
|
val ownerId = UUID.fromString(jwt.getSubject)
|
||||||
|
accountService.createBotAccount(ownerId, req.name) match
|
||||||
|
case Right(bot) =>
|
||||||
|
Response.status(Response.Status.CREATED).entity(toBotDtoWithToken(bot)).build()
|
||||||
|
case Left(error) =>
|
||||||
|
val status = error match
|
||||||
|
case AccountError.BotLimitExceeded => Response.Status.BAD_REQUEST
|
||||||
|
case _ => Response.Status.INTERNAL_SERVER_ERROR
|
||||||
|
Response.status(status).entity(ErrorDto(error.message)).build()
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/bots")
|
||||||
|
@RolesAllowed(Array("**"))
|
||||||
|
def listBotAccounts(): Response =
|
||||||
|
val ownerId = UUID.fromString(jwt.getSubject)
|
||||||
|
val bots = accountService.getBotAccounts(ownerId)
|
||||||
|
Response.ok(bots.map(toBotDto)).build()
|
||||||
|
|
||||||
|
@PUT
|
||||||
|
@Path("/bots/{botId}")
|
||||||
|
@RolesAllowed(Array("**"))
|
||||||
|
def updateBotName(@PathParam("botId") botId: String, req: UpdateBotNameRequest): Response =
|
||||||
|
val ownerId = UUID.fromString(jwt.getSubject)
|
||||||
|
accountService.updateBotName(UUID.fromString(botId), ownerId, req.name) match
|
||||||
|
case Right(bot) => Response.ok(toBotDto(bot)).build()
|
||||||
|
case Left(AccountError.NotAuthorized) =>
|
||||||
|
Response.status(Response.Status.FORBIDDEN).entity(ErrorDto(AccountError.NotAuthorized.message)).build()
|
||||||
|
case Left(error) =>
|
||||||
|
Response.status(Response.Status.NOT_FOUND).entity(ErrorDto(error.message)).build()
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/bots/{botId}/rotate-token")
|
||||||
|
@RolesAllowed(Array("**"))
|
||||||
|
def rotateBotToken(@PathParam("botId") botId: String): Response =
|
||||||
|
val ownerId = UUID.fromString(jwt.getSubject)
|
||||||
|
accountService.rotateBotToken(UUID.fromString(botId), ownerId) match
|
||||||
|
case Right(bot) => Response.ok(RotatedTokenDto(bot.token)).build()
|
||||||
|
case Left(AccountError.NotAuthorized) =>
|
||||||
|
Response.status(Response.Status.FORBIDDEN).entity(ErrorDto(AccountError.NotAuthorized.message)).build()
|
||||||
|
case Left(error) =>
|
||||||
|
Response.status(Response.Status.NOT_FOUND).entity(ErrorDto(error.message)).build()
|
||||||
|
|
||||||
|
@DELETE
|
||||||
|
@Path("/bots/{botId}")
|
||||||
|
@RolesAllowed(Array("**"))
|
||||||
|
def deleteBotAccount(@PathParam("botId") botId: String): Response =
|
||||||
|
val ownerId = UUID.fromString(jwt.getSubject)
|
||||||
|
val botUuid = UUID.fromString(botId)
|
||||||
|
accountService.getBotAccountWithOwnerCheck(botUuid, ownerId) match
|
||||||
|
case None => Response.status(Response.Status.NOT_FOUND).entity(ErrorDto(AccountError.BotNotFound.message)).build()
|
||||||
|
case Some(None) =>
|
||||||
|
Response.status(Response.Status.FORBIDDEN).entity(ErrorDto(AccountError.NotAuthorized.message)).build()
|
||||||
|
case Some(Some(_)) =>
|
||||||
|
accountService.deleteBotAccount(botUuid) match
|
||||||
|
case Right(_) => Response.noContent().build()
|
||||||
|
case Left(error) =>
|
||||||
|
Response.status(Response.Status.NOT_FOUND).entity(ErrorDto(error.message)).build()
|
||||||
|
|
||||||
|
private def toPublicDto(account: UserAccount): PublicAccountDto =
|
||||||
|
PublicAccountDto(
|
||||||
|
id = account.id.toString,
|
||||||
|
username = account.username,
|
||||||
|
rating = account.rating,
|
||||||
|
createdAt = account.createdAt.toString,
|
||||||
|
)
|
||||||
|
|
||||||
|
private def toBotDto(bot: BotAccount): BotAccountDto =
|
||||||
|
BotAccountDto(
|
||||||
|
id = bot.id.toString,
|
||||||
|
name = bot.name,
|
||||||
|
rating = bot.rating,
|
||||||
|
createdAt = bot.createdAt.toString,
|
||||||
|
)
|
||||||
|
|
||||||
|
private def toBotDtoWithToken(bot: BotAccount): BotAccountWithTokenDto =
|
||||||
|
BotAccountWithTokenDto(
|
||||||
|
id = bot.id.toString,
|
||||||
|
name = bot.name,
|
||||||
|
rating = bot.rating,
|
||||||
|
token = bot.token,
|
||||||
|
createdAt = bot.createdAt.toString,
|
||||||
|
)
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/official-bots")
|
||||||
|
def getOfficialBots: Response =
|
||||||
|
val bots = accountService.getOfficialBotAccounts()
|
||||||
|
Response.ok(bots.map(toOfficialBotDto)).build()
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/official-bots")
|
||||||
|
@RolesAllowed(Array("Admin"))
|
||||||
|
def createOfficialBot(req: CreateBotAccountRequest): Response =
|
||||||
|
accountService.createOfficialBotAccount(req.name) match
|
||||||
|
case Right(bot) =>
|
||||||
|
Response.status(Response.Status.CREATED).entity(toOfficialBotDto(bot)).build()
|
||||||
|
case Left(error) =>
|
||||||
|
Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(ErrorDto(error.message)).build()
|
||||||
|
|
||||||
|
@DELETE
|
||||||
|
@Path("/official-bots/{botId}")
|
||||||
|
@RolesAllowed(Array("Admin"))
|
||||||
|
def deleteOfficialBot(@PathParam("botId") botId: String): Response =
|
||||||
|
accountService.deleteOfficialBotAccount(UUID.fromString(botId)) match
|
||||||
|
case Right(_) => Response.noContent().build()
|
||||||
|
case Left(error) =>
|
||||||
|
Response.status(Response.Status.NOT_FOUND).entity(ErrorDto(error.message)).build()
|
||||||
|
|
||||||
|
private def toOfficialBotDto(bot: OfficialBotAccount): OfficialBotAccountDto =
|
||||||
|
OfficialBotAccountDto(
|
||||||
|
id = bot.id.toString,
|
||||||
|
name = bot.name,
|
||||||
|
rating = bot.rating,
|
||||||
|
createdAt = bot.createdAt.toString,
|
||||||
|
)
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package de.nowchess.account.resource
|
||||||
|
|
||||||
|
import de.nowchess.account.dto.*
|
||||||
|
import de.nowchess.account.error.ChallengeError
|
||||||
|
import de.nowchess.account.service.ChallengeService
|
||||||
|
import jakarta.annotation.security.RolesAllowed
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped
|
||||||
|
import jakarta.inject.Inject
|
||||||
|
import jakarta.ws.rs.*
|
||||||
|
import jakarta.ws.rs.core.{MediaType, Response}
|
||||||
|
import org.eclipse.microprofile.jwt.JsonWebToken
|
||||||
|
import scala.compiletime.uninitialized
|
||||||
|
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@Path("/api/challenge")
|
||||||
|
@ApplicationScoped
|
||||||
|
@RolesAllowed(Array("**"))
|
||||||
|
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||||
|
@Produces(Array(MediaType.APPLICATION_JSON))
|
||||||
|
class ChallengeResource:
|
||||||
|
|
||||||
|
// scalafix:off DisableSyntax.var
|
||||||
|
@Inject
|
||||||
|
var challengeService: ChallengeService = uninitialized
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
var jwt: JsonWebToken = uninitialized
|
||||||
|
// scalafix:on
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/{username}")
|
||||||
|
def create(@PathParam("username") username: String, req: ChallengeRequest): Response =
|
||||||
|
val userId = UUID.fromString(jwt.getSubject)
|
||||||
|
challengeService.create(userId, username, req) match
|
||||||
|
case Right(challenge) =>
|
||||||
|
Response.status(Response.Status.CREATED).entity(challengeService.toDto(challenge)).build()
|
||||||
|
case Left(error) =>
|
||||||
|
val status = error match
|
||||||
|
case ChallengeError.UserNotFound(_) | ChallengeError.ChallengerNotFound => Response.Status.NOT_FOUND
|
||||||
|
case ChallengeError.CannotChallengeSelf => Response.Status.BAD_REQUEST
|
||||||
|
case _ => Response.Status.CONFLICT
|
||||||
|
Response.status(status).entity(ErrorDto(error.message)).build()
|
||||||
|
|
||||||
|
@GET
|
||||||
|
def list(): Response =
|
||||||
|
val userId = UUID.fromString(jwt.getSubject)
|
||||||
|
Response.ok(challengeService.listForUser(userId)).build()
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/{id}")
|
||||||
|
def get(@PathParam("id") id: UUID): Response =
|
||||||
|
val userId = UUID.fromString(jwt.getSubject)
|
||||||
|
challengeService.findById(id, userId) match
|
||||||
|
case Right(challenge) => Response.ok(challengeService.toDto(challenge)).build()
|
||||||
|
case Left(error) => errorResponse(error)
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/{id}/accept")
|
||||||
|
def accept(@PathParam("id") id: UUID): Response =
|
||||||
|
val userId = UUID.fromString(jwt.getSubject)
|
||||||
|
challengeService.accept(id, userId) match
|
||||||
|
case Right(challenge) => Response.ok(challengeService.toDto(challenge)).build()
|
||||||
|
case Left(error) => errorResponse(error)
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/{id}/decline")
|
||||||
|
def decline(@PathParam("id") id: UUID, req: DeclineRequest): Response =
|
||||||
|
val userId = UUID.fromString(jwt.getSubject)
|
||||||
|
challengeService.decline(id, userId, req) match
|
||||||
|
case Right(challenge) => Response.ok(challengeService.toDto(challenge)).build()
|
||||||
|
case Left(error) => errorResponse(error)
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/{id}/cancel")
|
||||||
|
def cancel(@PathParam("id") id: UUID): Response =
|
||||||
|
val userId = UUID.fromString(jwt.getSubject)
|
||||||
|
challengeService.cancel(id, userId) match
|
||||||
|
case Right(challenge) => Response.ok(challengeService.toDto(challenge)).build()
|
||||||
|
case Left(error) => errorResponse(error)
|
||||||
|
|
||||||
|
private def errorResponse(error: ChallengeError): Response =
|
||||||
|
val status = error match
|
||||||
|
case ChallengeError.ChallengeNotFound => Response.Status.NOT_FOUND
|
||||||
|
case ChallengeError.NotAuthorized => Response.Status.FORBIDDEN
|
||||||
|
case ChallengeError.GameCreationFailed => Response.Status.INTERNAL_SERVER_ERROR
|
||||||
|
case _ => Response.Status.BAD_REQUEST
|
||||||
|
Response.status(status).entity(ErrorDto(error.message)).build()
|
||||||
+91
@@ -0,0 +1,91 @@
|
|||||||
|
package de.nowchess.account.resource
|
||||||
|
|
||||||
|
import de.nowchess.account.client.{CoreCreateGameRequest, CoreGameClient, CorePlayerInfo}
|
||||||
|
import de.nowchess.account.dto.{ErrorDto, OfficialChallengeResponse}
|
||||||
|
import de.nowchess.account.service.{AccountService, EventPublisher}
|
||||||
|
import jakarta.annotation.security.RolesAllowed
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped
|
||||||
|
import jakarta.inject.Inject
|
||||||
|
import jakarta.ws.rs.*
|
||||||
|
import jakarta.ws.rs.core.{MediaType, Response}
|
||||||
|
import org.eclipse.microprofile.jwt.JsonWebToken
|
||||||
|
import org.eclipse.microprofile.rest.client.inject.RestClient
|
||||||
|
import org.jboss.logging.Logger
|
||||||
|
import scala.compiletime.uninitialized
|
||||||
|
|
||||||
|
import java.util.UUID
|
||||||
|
import java.util.concurrent.ThreadLocalRandom
|
||||||
|
|
||||||
|
@Path("/api/challenge/official")
|
||||||
|
@ApplicationScoped
|
||||||
|
@RolesAllowed(Array("**"))
|
||||||
|
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||||
|
@Produces(Array(MediaType.APPLICATION_JSON))
|
||||||
|
class OfficialChallengeResource:
|
||||||
|
|
||||||
|
// scalafix:off DisableSyntax.var
|
||||||
|
@Inject var accountService: AccountService = uninitialized
|
||||||
|
@Inject var jwt: JsonWebToken = uninitialized
|
||||||
|
@Inject var botEventPublisher: EventPublisher = uninitialized
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@RestClient
|
||||||
|
var coreGameClient: CoreGameClient = uninitialized
|
||||||
|
// scalafix:on
|
||||||
|
|
||||||
|
private val log = Logger.getLogger(classOf[OfficialChallengeResource])
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/{botName}")
|
||||||
|
def challengeWithDifficulty(
|
||||||
|
@PathParam("botName") botName: String,
|
||||||
|
@QueryParam("difficulty") difficulty: Int,
|
||||||
|
@QueryParam("color") color: String,
|
||||||
|
): Response =
|
||||||
|
if difficulty < 1000 || difficulty > 2800 then
|
||||||
|
Response
|
||||||
|
.status(Response.Status.BAD_REQUEST)
|
||||||
|
.entity(ErrorDto("difficulty must be between 1000 and 2800"))
|
||||||
|
.build()
|
||||||
|
else
|
||||||
|
val normalizedColor = Option(color).map(_.toLowerCase).getOrElse("random")
|
||||||
|
normalizedColor match
|
||||||
|
case "white" | "black" | "random" =>
|
||||||
|
val userId = UUID.fromString(jwt.getSubject)
|
||||||
|
val botOpt = accountService.getOfficialBotAccounts().find(_.name == botName)
|
||||||
|
val userOpt = accountService.findById(userId)
|
||||||
|
|
||||||
|
(botOpt, userOpt) match
|
||||||
|
case (None, _) =>
|
||||||
|
Response.status(Response.Status.NOT_FOUND).entity(ErrorDto(s"Official bot '$botName' not found")).build()
|
||||||
|
case (_, None) =>
|
||||||
|
Response.status(Response.Status.NOT_FOUND).entity(ErrorDto("User not found")).build()
|
||||||
|
case (Some(bot), Some(user)) =>
|
||||||
|
val userIsWhite = normalizedColor match
|
||||||
|
case "white" => true
|
||||||
|
case "black" => false
|
||||||
|
case _ => ThreadLocalRandom.current().nextBoolean()
|
||||||
|
val (white, black, botColor) =
|
||||||
|
if userIsWhite then
|
||||||
|
(CorePlayerInfo(user.id.toString, user.username), CorePlayerInfo(bot.id.toString, bot.name), "black")
|
||||||
|
else
|
||||||
|
(CorePlayerInfo(bot.id.toString, bot.name), CorePlayerInfo(user.id.toString, user.username), "white")
|
||||||
|
val req = CoreCreateGameRequest(Some(white), Some(black), None, Some("Authenticated"))
|
||||||
|
val gameId =
|
||||||
|
try Right(coreGameClient.createGame(req).gameId)
|
||||||
|
catch case _ => Left("Failed to create game")
|
||||||
|
gameId match
|
||||||
|
case Left(err) =>
|
||||||
|
Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(ErrorDto(err)).build()
|
||||||
|
case Right(id) =>
|
||||||
|
try botEventPublisher.publishGameStart(bot.name, id, botColor, difficulty, bot.id.toString)
|
||||||
|
catch case ex: Exception => log.warnf(ex, "Failed to notify bot for game %s", id)
|
||||||
|
Response
|
||||||
|
.status(Response.Status.CREATED)
|
||||||
|
.entity(OfficialChallengeResponse(id, botName, difficulty))
|
||||||
|
.build()
|
||||||
|
case other =>
|
||||||
|
Response
|
||||||
|
.status(Response.Status.BAD_REQUEST)
|
||||||
|
.entity(ErrorDto(s"Invalid color: $other. Must be white, black or random"))
|
||||||
|
.build()
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
package de.nowchess.account.service
|
||||||
|
|
||||||
|
import de.nowchess.account.domain.{BotAccount, OfficialBotAccount, UserAccount}
|
||||||
|
import de.nowchess.account.dto.{LoginRequest, RegisterRequest}
|
||||||
|
import de.nowchess.account.error.AccountError
|
||||||
|
import de.nowchess.account.repository.{BotAccountRepository, OfficialBotAccountRepository, UserAccountRepository}
|
||||||
|
import io.quarkus.elytron.security.common.BcryptUtil
|
||||||
|
import io.smallrye.jwt.build.Jwt
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped
|
||||||
|
import jakarta.inject.Inject
|
||||||
|
import jakarta.transaction.Transactional
|
||||||
|
import scala.compiletime.uninitialized
|
||||||
|
|
||||||
|
import java.time.Instant
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@ApplicationScoped
|
||||||
|
class AccountService:
|
||||||
|
|
||||||
|
// scalafix:off DisableSyntax.var
|
||||||
|
@Inject
|
||||||
|
var userAccountRepository: UserAccountRepository = uninitialized
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
var botAccountRepository: BotAccountRepository = uninitialized
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
var officialBotAccountRepository: OfficialBotAccountRepository = uninitialized
|
||||||
|
// scalafix:on
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
def register(req: RegisterRequest): Either[AccountError, UserAccount] =
|
||||||
|
if userAccountRepository.findByUsername(req.username).isDefined then Left(AccountError.UsernameTaken(req.username))
|
||||||
|
else if userAccountRepository.findByEmail(req.email).isDefined then
|
||||||
|
Left(AccountError.EmailAlreadyRegistered(req.email))
|
||||||
|
else
|
||||||
|
val account = new UserAccount()
|
||||||
|
account.username = req.username
|
||||||
|
account.email = req.email
|
||||||
|
account.passwordHash = BcryptUtil.bcryptHash(req.password)
|
||||||
|
account.createdAt = Instant.now()
|
||||||
|
userAccountRepository.persist(account)
|
||||||
|
Right(account)
|
||||||
|
|
||||||
|
def login(req: LoginRequest): Either[AccountError, String] =
|
||||||
|
userAccountRepository.findByUsername(req.username) match
|
||||||
|
case None => Left(AccountError.InvalidCredentials)
|
||||||
|
case Some(account) =>
|
||||||
|
if !BcryptUtil.matches(req.password, account.passwordHash) then Left(AccountError.InvalidCredentials)
|
||||||
|
else if account.banned then Left(AccountError.UserBanned)
|
||||||
|
else
|
||||||
|
Right(
|
||||||
|
Jwt
|
||||||
|
.issuer("nowchess")
|
||||||
|
.subject(account.id.toString)
|
||||||
|
.claim("username", account.username)
|
||||||
|
.sign(),
|
||||||
|
)
|
||||||
|
|
||||||
|
def findByUsername(username: String): Option[UserAccount] =
|
||||||
|
userAccountRepository.findByUsername(username)
|
||||||
|
|
||||||
|
def findById(id: UUID): Option[UserAccount] =
|
||||||
|
userAccountRepository.findById(id)
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
def createBotAccount(ownerId: UUID, botName: String): Either[AccountError, BotAccount] =
|
||||||
|
userAccountRepository.findById(ownerId) match
|
||||||
|
case None => Left(AccountError.UserNotFound)
|
||||||
|
case Some(owner) =>
|
||||||
|
val botAccounts = botAccountRepository.findByOwner(ownerId)
|
||||||
|
if botAccounts.length >= 5 then Left(AccountError.BotLimitExceeded)
|
||||||
|
else
|
||||||
|
val bot = new BotAccount()
|
||||||
|
bot.name = botName
|
||||||
|
bot.owner = owner
|
||||||
|
bot.token = generateBotToken(bot.id)
|
||||||
|
bot.createdAt = Instant.now()
|
||||||
|
botAccountRepository.persist(bot)
|
||||||
|
Right(bot)
|
||||||
|
|
||||||
|
def getBotAccounts(ownerId: UUID): List[BotAccount] =
|
||||||
|
botAccountRepository.findByOwner(ownerId)
|
||||||
|
|
||||||
|
def getBotAccountWithOwnerCheck(botId: UUID, ownerId: UUID): Option[Option[BotAccount]] =
|
||||||
|
botAccountRepository.findById(botId) match
|
||||||
|
case None => Some(None)
|
||||||
|
case Some(bot) => Some(Option(bot).filter(_.owner.id == ownerId))
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
def deleteBotAccount(botId: UUID): Either[AccountError, Unit] =
|
||||||
|
botAccountRepository.findById(botId) match
|
||||||
|
case None => Left(AccountError.BotNotFound)
|
||||||
|
case Some(_) =>
|
||||||
|
botAccountRepository.delete(botId)
|
||||||
|
Right(())
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
def updateBotName(botId: UUID, ownerId: UUID, newName: String): Either[AccountError, BotAccount] =
|
||||||
|
botAccountRepository.findById(botId) match
|
||||||
|
case None => Left(AccountError.BotNotFound)
|
||||||
|
case Some(bot) =>
|
||||||
|
if bot.owner.id != ownerId then Left(AccountError.NotAuthorized)
|
||||||
|
else
|
||||||
|
bot.name = newName
|
||||||
|
botAccountRepository.persist(bot)
|
||||||
|
Right(bot)
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
def rotateBotToken(botId: UUID, ownerId: UUID): Either[AccountError, BotAccount] =
|
||||||
|
botAccountRepository.findById(botId) match
|
||||||
|
case None => Left(AccountError.BotNotFound)
|
||||||
|
case Some(bot) =>
|
||||||
|
if bot.owner.id != ownerId then Left(AccountError.NotAuthorized)
|
||||||
|
else
|
||||||
|
bot.token = generateBotToken(botId)
|
||||||
|
botAccountRepository.persist(bot)
|
||||||
|
Right(bot)
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
def createOfficialBotAccount(botName: String): Either[AccountError, OfficialBotAccount] =
|
||||||
|
val bot = new OfficialBotAccount()
|
||||||
|
bot.name = botName
|
||||||
|
bot.createdAt = Instant.now()
|
||||||
|
officialBotAccountRepository.persist(bot)
|
||||||
|
Right(bot)
|
||||||
|
|
||||||
|
def getOfficialBotAccounts(): List[OfficialBotAccount] =
|
||||||
|
officialBotAccountRepository.findAll()
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
def deleteOfficialBotAccount(botId: UUID): Either[AccountError, Unit] =
|
||||||
|
officialBotAccountRepository.findById(botId) match
|
||||||
|
case None => Left(AccountError.BotNotFound)
|
||||||
|
case Some(_) =>
|
||||||
|
officialBotAccountRepository.delete(botId)
|
||||||
|
Right(())
|
||||||
|
|
||||||
|
private def generateBotToken(botId: UUID): String =
|
||||||
|
Jwt
|
||||||
|
.issuer("nowchess")
|
||||||
|
.subject(botId.toString)
|
||||||
|
.expiresAt(Long.MaxValue)
|
||||||
|
.claim("type", "bot")
|
||||||
|
.sign()
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
def banUser(userId: UUID): Either[AccountError, UserAccount] =
|
||||||
|
userAccountRepository.findById(userId) match
|
||||||
|
case None => Left(AccountError.UserNotFound)
|
||||||
|
case Some(user) =>
|
||||||
|
user.banned = true
|
||||||
|
user.botAccounts.forEach(_.banned = true)
|
||||||
|
userAccountRepository.persist(user)
|
||||||
|
Right(user)
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
def unbanUser(userId: UUID): Either[AccountError, UserAccount] =
|
||||||
|
userAccountRepository.findById(userId) match
|
||||||
|
case None => Left(AccountError.UserNotFound)
|
||||||
|
case Some(user) =>
|
||||||
|
user.banned = false
|
||||||
|
user.botAccounts.forEach(_.banned = false)
|
||||||
|
userAccountRepository.persist(user)
|
||||||
|
Right(user)
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
package de.nowchess.account.service
|
||||||
|
|
||||||
|
import de.nowchess.account.client.{
|
||||||
|
CoreCreateGameRequest,
|
||||||
|
CoreGameClient,
|
||||||
|
CoreGameResponse,
|
||||||
|
CorePlayerInfo,
|
||||||
|
CoreTimeControl,
|
||||||
|
}
|
||||||
|
import de.nowchess.account.domain.{Challenge, ChallengeColor, ChallengeStatus, DeclineReason}
|
||||||
|
import de.nowchess.account.dto.{
|
||||||
|
ChallengeDto,
|
||||||
|
ChallengeListDto,
|
||||||
|
ChallengeRequest,
|
||||||
|
DeclineRequest,
|
||||||
|
PlayerInfo,
|
||||||
|
TimeControlDto,
|
||||||
|
}
|
||||||
|
import de.nowchess.account.error.ChallengeError
|
||||||
|
import de.nowchess.account.repository.{ChallengeRepository, UserAccountRepository}
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped
|
||||||
|
import jakarta.inject.Inject
|
||||||
|
import jakarta.transaction.Transactional
|
||||||
|
import org.eclipse.microprofile.rest.client.inject.RestClient
|
||||||
|
import org.jboss.logging.Logger
|
||||||
|
import scala.compiletime.uninitialized
|
||||||
|
|
||||||
|
import java.time.Instant
|
||||||
|
import java.time.temporal.ChronoUnit
|
||||||
|
import java.util.UUID
|
||||||
|
import java.util.concurrent.ThreadLocalRandom
|
||||||
|
|
||||||
|
@ApplicationScoped
|
||||||
|
class ChallengeService:
|
||||||
|
|
||||||
|
private val log = Logger.getLogger(classOf[ChallengeService])
|
||||||
|
|
||||||
|
// scalafix:off DisableSyntax.var
|
||||||
|
@Inject
|
||||||
|
var userAccountRepository: UserAccountRepository = uninitialized
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
var challengeRepository: ChallengeRepository = uninitialized
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@RestClient
|
||||||
|
var coreGameClient: CoreGameClient = uninitialized
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
var eventPublisher: EventPublisher = uninitialized
|
||||||
|
// scalafix:on
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
def create(challengerId: UUID, destUsername: String, req: ChallengeRequest): Either[ChallengeError, Challenge] =
|
||||||
|
for
|
||||||
|
destUser <- userAccountRepository.findByUsername(destUsername).toRight(ChallengeError.UserNotFound(destUsername))
|
||||||
|
challenger <- userAccountRepository.findById(challengerId).toRight(ChallengeError.ChallengerNotFound)
|
||||||
|
_ <- Either.cond(challenger.id != destUser.id, (), ChallengeError.CannotChallengeSelf)
|
||||||
|
_ <- Either.cond(
|
||||||
|
challengeRepository.findDuplicateChallenge(challengerId, destUser.id).isEmpty,
|
||||||
|
(),
|
||||||
|
ChallengeError.DuplicateChallenge,
|
||||||
|
)
|
||||||
|
color <- parseColor(req.color)
|
||||||
|
yield
|
||||||
|
val challenge = new Challenge()
|
||||||
|
challenge.challenger = challenger
|
||||||
|
challenge.destUser = destUser
|
||||||
|
challenge.color = color
|
||||||
|
challenge.status = ChallengeStatus.Created
|
||||||
|
challenge.timeControlType = req.timeControl.`type`
|
||||||
|
challenge.timeControlLimit = req.timeControl.limit.map(java.lang.Integer.valueOf).orNull
|
||||||
|
challenge.timeControlIncrement = req.timeControl.increment.map(java.lang.Integer.valueOf).orNull
|
||||||
|
challenge.createdAt = Instant.now()
|
||||||
|
challenge.expiresAt = Instant.now().plus(24, ChronoUnit.HOURS)
|
||||||
|
challengeRepository.persist(challenge)
|
||||||
|
try eventPublisher.publishChallengeCreated(destUser.id.toString, challenge.id.toString, challenger.username)
|
||||||
|
catch case ex: Exception => log.warnf(ex, "Failed to notify dest user for challenge %s", challenge.id)
|
||||||
|
challenge
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
def accept(challengeId: UUID, userId: UUID): Either[ChallengeError, Challenge] =
|
||||||
|
for
|
||||||
|
challenge <- challengeRepository.findById(challengeId).toRight(ChallengeError.ChallengeNotFound)
|
||||||
|
_ <- Either.cond(challenge.status == ChallengeStatus.Created, (), ChallengeError.ChallengeNotActive)
|
||||||
|
_ <- Either.cond(challenge.destUser.id == userId, (), ChallengeError.NotAuthorized)
|
||||||
|
gameId <- createGame(challenge)
|
||||||
|
yield
|
||||||
|
challenge.status = ChallengeStatus.Accepted
|
||||||
|
challenge.gameId = gameId
|
||||||
|
challengeRepository.merge(challenge)
|
||||||
|
notifyBotIfNeeded(challenge, gameId)
|
||||||
|
try eventPublisher.publishChallengeAccepted(challenge.challenger.id.toString, challenge.id.toString, gameId)
|
||||||
|
catch case ex: Exception => log.warnf(ex, "Failed to notify challenger for game %s", gameId)
|
||||||
|
challenge
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
def decline(challengeId: UUID, userId: UUID, req: DeclineRequest): Either[ChallengeError, Challenge] =
|
||||||
|
for
|
||||||
|
challenge <- challengeRepository.findById(challengeId).toRight(ChallengeError.ChallengeNotFound)
|
||||||
|
_ <- Either.cond(challenge.status == ChallengeStatus.Created, (), ChallengeError.ChallengeNotActive)
|
||||||
|
_ <- Either.cond(challenge.destUser.id == userId, (), ChallengeError.NotAuthorized)
|
||||||
|
reason <- parseDeclineReason(req.reason)
|
||||||
|
yield
|
||||||
|
challenge.status = ChallengeStatus.Declined
|
||||||
|
challenge.declineReason = reason.orNull
|
||||||
|
challengeRepository.merge(challenge)
|
||||||
|
challenge
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
def cancel(challengeId: UUID, userId: UUID): Either[ChallengeError, Challenge] =
|
||||||
|
for
|
||||||
|
challenge <- challengeRepository.findById(challengeId).toRight(ChallengeError.ChallengeNotFound)
|
||||||
|
_ <- Either.cond(challenge.status == ChallengeStatus.Created, (), ChallengeError.ChallengeNotActive)
|
||||||
|
_ <- Either.cond(challenge.challenger.id == userId, (), ChallengeError.NotAuthorized)
|
||||||
|
yield
|
||||||
|
challenge.status = ChallengeStatus.Canceled
|
||||||
|
challengeRepository.merge(challenge)
|
||||||
|
challenge
|
||||||
|
|
||||||
|
def findById(challengeId: UUID, userId: UUID): Either[ChallengeError, Challenge] =
|
||||||
|
for
|
||||||
|
challenge <- challengeRepository.findById(challengeId).toRight(ChallengeError.ChallengeNotFound)
|
||||||
|
_ <- Either.cond(
|
||||||
|
challenge.challenger.id == userId || challenge.destUser.id == userId,
|
||||||
|
(),
|
||||||
|
ChallengeError.NotAuthorized,
|
||||||
|
)
|
||||||
|
yield challenge
|
||||||
|
|
||||||
|
def listForUser(userId: UUID): ChallengeListDto =
|
||||||
|
val incoming = challengeRepository.findActiveByDestUserId(userId).map(toDto)
|
||||||
|
val outgoing = challengeRepository.findActiveByChallengerId(userId).map(toDto)
|
||||||
|
ChallengeListDto(in = incoming, out = outgoing)
|
||||||
|
|
||||||
|
private def notifyBotIfNeeded(challenge: Challenge, gameId: String): Unit =
|
||||||
|
val (white, black) = assignColors(challenge)
|
||||||
|
List(challenge.challenger, challenge.destUser).foreach { user =>
|
||||||
|
user.getBotAccounts.headOption.foreach { bot =>
|
||||||
|
val playingAs = if white.id == user.id.toString then "white" else "black"
|
||||||
|
try eventPublisher.publishGameStart(bot.id.toString, gameId, playingAs, 1400, bot.id.toString)
|
||||||
|
catch case ex: Exception => log.warnf(ex, "Failed to notify bot for game %s", gameId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def createGame(challenge: Challenge): Either[ChallengeError, String] =
|
||||||
|
try
|
||||||
|
val (white, black) = assignColors(challenge)
|
||||||
|
val tc = buildTimeControl(challenge)
|
||||||
|
val req = CoreCreateGameRequest(Some(white), Some(black), tc, Some("Authenticated"))
|
||||||
|
Right(coreGameClient.createGame(req).gameId)
|
||||||
|
catch case _ => Left(ChallengeError.GameCreationFailed)
|
||||||
|
|
||||||
|
private def assignColors(challenge: Challenge): (CorePlayerInfo, CorePlayerInfo) =
|
||||||
|
val challenger = CorePlayerInfo(challenge.challenger.id.toString, challenge.challenger.username)
|
||||||
|
val destUser = CorePlayerInfo(challenge.destUser.id.toString, challenge.destUser.username)
|
||||||
|
challenge.color match
|
||||||
|
case ChallengeColor.White => (challenger, destUser)
|
||||||
|
case ChallengeColor.Black => (destUser, challenger)
|
||||||
|
case ChallengeColor.Random =>
|
||||||
|
if ThreadLocalRandom.current().nextBoolean() then (challenger, destUser) else (destUser, challenger)
|
||||||
|
|
||||||
|
private def buildTimeControl(challenge: Challenge): Option[CoreTimeControl] =
|
||||||
|
challenge.timeControlType match
|
||||||
|
case "unlimited" => None
|
||||||
|
case "correspondence" => Some(CoreTimeControl(None, None, challenge.timeControlLimitOpt))
|
||||||
|
case _ => Some(CoreTimeControl(challenge.timeControlLimitOpt, challenge.timeControlIncrementOpt, None))
|
||||||
|
|
||||||
|
private def parseColor(raw: String): Either[ChallengeError, ChallengeColor] =
|
||||||
|
raw.toLowerCase match
|
||||||
|
case "white" => Right(ChallengeColor.White)
|
||||||
|
case "black" => Right(ChallengeColor.Black)
|
||||||
|
case "random" => Right(ChallengeColor.Random)
|
||||||
|
case _ => Left(ChallengeError.InvalidColor(raw))
|
||||||
|
|
||||||
|
private def parseDeclineReason(raw: Option[String]): Either[ChallengeError, Option[DeclineReason]] =
|
||||||
|
raw match
|
||||||
|
case None => Right(None)
|
||||||
|
case Some(r) =>
|
||||||
|
DeclineReason.values.find(_.toString.equalsIgnoreCase(r)) match
|
||||||
|
case Some(reason) => Right(Some(reason))
|
||||||
|
case None => Left(ChallengeError.InvalidDeclineReason(r))
|
||||||
|
|
||||||
|
def toDto(c: Challenge): ChallengeDto =
|
||||||
|
ChallengeDto(
|
||||||
|
id = c.id.toString,
|
||||||
|
challenger = PlayerInfo(c.challenger.id.toString, c.challenger.username, c.challenger.rating),
|
||||||
|
destUser = PlayerInfo(c.destUser.id.toString, c.destUser.username, c.destUser.rating),
|
||||||
|
variant = "standard",
|
||||||
|
color = c.color.toString.toLowerCase,
|
||||||
|
timeControl = TimeControlDto(c.timeControlType, c.timeControlLimitOpt, c.timeControlIncrementOpt),
|
||||||
|
status = c.status.toString.toLowerCase,
|
||||||
|
declineReason = c.declineReasonOpt.map(_.toString.toLowerCase),
|
||||||
|
gameId = c.gameIdOpt,
|
||||||
|
createdAt = c.createdAt.toString,
|
||||||
|
expiresAt = c.expiresAt.toString,
|
||||||
|
)
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package de.nowchess.account.service
|
||||||
|
|
||||||
|
import de.nowchess.account.config.RedisConfig
|
||||||
|
import io.quarkus.redis.datasource.RedisDataSource
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped
|
||||||
|
import jakarta.inject.Inject
|
||||||
|
import scala.compiletime.uninitialized
|
||||||
|
|
||||||
|
@ApplicationScoped
|
||||||
|
class EventPublisher:
|
||||||
|
|
||||||
|
// scalafix:off DisableSyntax.var
|
||||||
|
@Inject var redis: RedisDataSource = uninitialized
|
||||||
|
@Inject var redisConfig: RedisConfig = uninitialized
|
||||||
|
// scalafix:on DisableSyntax.var
|
||||||
|
|
||||||
|
def publishGameStart(botId: String, gameId: String, playingAs: String, difficulty: Int, botAccountId: String): Unit =
|
||||||
|
val event =
|
||||||
|
s"""{"type":"gameStart","gameId":"$gameId","playingAs":"$playingAs","difficulty":$difficulty,"botAccountId":"$botAccountId"}"""
|
||||||
|
redis.pubsub(classOf[String]).publish(s"${redisConfig.prefix}:bot:$botId:events", event)
|
||||||
|
()
|
||||||
|
|
||||||
|
def publishChallengeCreated(destUserId: String, challengeId: String, challengerName: String): Unit =
|
||||||
|
val event = s"""{"type":"challengeCreated","challengeId":"$challengeId","challengerName":"$challengerName"}"""
|
||||||
|
redis.pubsub(classOf[String]).publish(s"${redisConfig.prefix}:user:$destUserId:events", event)
|
||||||
|
()
|
||||||
|
|
||||||
|
def publishChallengeAccepted(challengerId: String, challengeId: String, gameId: String): Unit =
|
||||||
|
val event = s"""{"type":"challengeAccepted","challengeId":"$challengeId","gameId":"$gameId"}"""
|
||||||
|
redis.pubsub(classOf[String]).publish(s"${redisConfig.prefix}:user:$challengerId:events", event)
|
||||||
|
()
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
quarkus:
|
||||||
|
http:
|
||||||
|
port: 8083
|
||||||
|
application:
|
||||||
|
name: nowchess-account
|
||||||
|
smallrye-openapi:
|
||||||
|
info-title: NowChess Account Service
|
||||||
|
path: /openapi
|
||||||
|
swagger-ui:
|
||||||
|
always-include: true
|
||||||
|
path: /swagger-ui
|
||||||
|
datasource:
|
||||||
|
db-kind: h2
|
||||||
|
username: sa
|
||||||
|
password: ""
|
||||||
|
jdbc:
|
||||||
|
url: "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"
|
||||||
|
hibernate-orm:
|
||||||
|
schema-management:
|
||||||
|
strategy: drop-and-create
|
||||||
|
mp:
|
||||||
|
jwt:
|
||||||
|
verify:
|
||||||
|
publickey:
|
||||||
|
location: keys/test-public.pem
|
||||||
|
issuer: nowchess
|
||||||
|
smallrye:
|
||||||
|
jwt:
|
||||||
|
sign:
|
||||||
|
key:
|
||||||
|
location: keys/test-private.pem
|
||||||
|
nowchess:
|
||||||
|
internal:
|
||||||
|
secret: test-secret
|
||||||
|
auth:
|
||||||
|
enabled: false
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC4zBHgRLMez2b6
|
||||||
|
wfdvvTJVR8xxbr/kJUMiq4ot14KhtTaGikFW+77ezjoqabFWH7CNjDvASWCM2n7X
|
||||||
|
PxL4fhUwzvTbhRZ2XNM80lKB+OIjP3hoNLvgeSNHbS4CztOfk2JVtQFLQdYJ/gvB
|
||||||
|
oFPgBtZYO/SZVML28d5U92JrWRIC1e1Ht1oKwKJoOqtTJrs/RuOlKQ/du4kwY8m0
|
||||||
|
jPw05wFA1YRMUC78xKklCVYCufYewIUTdKxATK0ZKWBoPCJnxDg8gwgpnV1wHQrH
|
||||||
|
GcbZvhcVg3GWpDcYdnogV4rlssws57+uAhGRyQBkmmhVb+zT+LT7WXDPB46MnHkK
|
||||||
|
FIZaxEkHAgMBAAECggEAAvu4Zih1w8+RWAb9mZ4yS9Im6MXi7yny1YJzbp4GC9pD
|
||||||
|
ERT2TRMvV6V4puqh5EQKs55J8Ka+mkeEuLDZ+4z9hpYwucKCRFLnThoPHu4HqI4D
|
||||||
|
wZroVY1fFm4aygzQucjFU6DibnaXn/2r7upJsFor56zAHCGULCxnbHO58QW1Frqa
|
||||||
|
UrTndSkrxavBD9LL1ohPEy3saXlRCVAEM5l7jZbg52dPauIYAOv0e+EE3RETw/Xz
|
||||||
|
3EWukIZ7PKyoyuQm8Sv2u7lyISljDGlvrW5IjVRPMPqOKNOa/pV3qU4mbUY6GjbC
|
||||||
|
B4xt8kEKjVSkTeMXA+W0gnZddnQOtcQYSrYWWes+AQKBgQDzjmt1ZJktZG96M8+f
|
||||||
|
Ov9JznfzSLYxN7EboDhqjTVBOkb6flRSYrd9E6gReIIrq5Sjs9Z+toA/u8BmjQ/P
|
||||||
|
GTrrLVh6bLBicUGKcmQFKw/0D9lOlbxaMg8VO9rqSb/AslumJwjucU7DA+WAN52j
|
||||||
|
cyiLiw+EmWjL/DV51fHHI18SgQKBgQDCPRzpeP8Qox83/+tGR/6fSSRi5ec3ZVPy
|
||||||
|
aCCCZM6qqhLv3hJkV0djRruVVfe136PwUi20BW6aF0PXmxDIGRWqDLQGkvDNEhjw
|
||||||
|
ZLBv/dYtW2HBZhq4E0w8DiaNZCOWvpLQ3QCEtzmuhyHhNqYHzvmuerk+w4c/8fY6
|
||||||
|
DFyPyiAHhwKBgDrpO/zNNG/SV1SLq7CsKIvFsSXbdJY7Dk/MVVkQhs0cN4bnf6Xd
|
||||||
|
0twiIQj4ySOfAPkHyt4jbqn70/H6NNS3GZVBBqG2IIPvORcvzBmj7Nvv6XQkq8Z1
|
||||||
|
TUipja4V4JfPjHOIBZUHOzHYg26cBTk/5ZK7NCmyobKVcqnhofW1DI4BAoGAaRu4
|
||||||
|
8X5QSCh9VEhggH+lAX0K+5l9LTTf4GUIcocqbp/p73M0cKfqMYatK3qBuSF0DS/r
|
||||||
|
G2d1Gl1MkPeQdTddyc9l+8i4FcCdTjiuYWvy4kh49bbS7plCv5zIr+pod8JYoD13
|
||||||
|
clnUFOV7J+vynHccFZbDd3tHTQsaOv9Fd2nhOzECgYEA8SWBEmTuaBh+0vr6zS+E
|
||||||
|
wD+cwB3iaGo+7fP7TZ+v1kxoDlcDjPYM4ikiOB+OPGNkAfqc3MGsbhfgcxqD0+5r
|
||||||
|
kpCFyiyieyoT+7hkMpMsJCNwFO+29fc3DDqPX4Keqp26tMxtRzYea3GtVShiRXew
|
||||||
|
5i4ReFwm3/IWDn9kLmHT6Fg=
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuMwR4ESzHs9m+sH3b70y
|
||||||
|
VUfMcW6/5CVDIquKLdeCobU2hopBVvu+3s46KmmxVh+wjYw7wElgjNp+1z8S+H4V
|
||||||
|
MM7024UWdlzTPNJSgfjiIz94aDS74HkjR20uAs7Tn5NiVbUBS0HWCf4LwaBT4AbW
|
||||||
|
WDv0mVTC9vHeVPdia1kSAtXtR7daCsCiaDqrUya7P0bjpSkP3buJMGPJtIz8NOcB
|
||||||
|
QNWETFAu/MSpJQlWArn2HsCFE3SsQEytGSlgaDwiZ8Q4PIMIKZ1dcB0KxxnG2b4X
|
||||||
|
FYNxlqQ3GHZ6IFeK5bLMLOe/rgIRkckAZJpoVW/s0/i0+1lwzweOjJx5ChSGWsRJ
|
||||||
|
BwIDAQAB
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
+107
@@ -0,0 +1,107 @@
|
|||||||
|
package de.nowchess.account.resource
|
||||||
|
|
||||||
|
import io.quarkus.test.junit.QuarkusTest
|
||||||
|
import io.restassured.RestAssured
|
||||||
|
import io.restassured.http.ContentType
|
||||||
|
import org.hamcrest.Matchers.*
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
@QuarkusTest
|
||||||
|
class AccountResourceTest:
|
||||||
|
|
||||||
|
private def givenRequest() = RestAssured.`given`().contentType(ContentType.JSON)
|
||||||
|
|
||||||
|
private def registerBody(username: String, email: String = "", password: String = "secret") =
|
||||||
|
val resolvedEmail = if email.isEmpty then s"$username@example.com" else email
|
||||||
|
s"""{"username":"$username","email":"$resolvedEmail","password":"$password"}"""
|
||||||
|
|
||||||
|
private def loginBody(username: String, password: String = "secret") =
|
||||||
|
s"""{"username":"$username","password":"$password"}"""
|
||||||
|
|
||||||
|
private def registerAndLogin(username: String): String =
|
||||||
|
givenRequest()
|
||||||
|
.body(registerBody(username))
|
||||||
|
.when()
|
||||||
|
.post("/api/account")
|
||||||
|
.`then`()
|
||||||
|
.statusCode(200)
|
||||||
|
givenRequest()
|
||||||
|
.body(loginBody(username))
|
||||||
|
.when()
|
||||||
|
.post("/api/account/login")
|
||||||
|
.`then`()
|
||||||
|
.statusCode(200)
|
||||||
|
.extract()
|
||||||
|
.path[String]("token")
|
||||||
|
|
||||||
|
@Test
|
||||||
|
def registerReturns200(): Unit =
|
||||||
|
givenRequest()
|
||||||
|
.body(registerBody("alice"))
|
||||||
|
.when()
|
||||||
|
.post("/api/account")
|
||||||
|
.`then`()
|
||||||
|
.statusCode(200)
|
||||||
|
.body("username", is("alice"))
|
||||||
|
.body("rating", is(1500))
|
||||||
|
|
||||||
|
@Test
|
||||||
|
def registerConflictOnDuplicateUsername(): Unit =
|
||||||
|
givenRequest().body(registerBody("bob")).when().post("/api/account")
|
||||||
|
givenRequest()
|
||||||
|
.body(registerBody("bob"))
|
||||||
|
.when()
|
||||||
|
.post("/api/account")
|
||||||
|
.`then`()
|
||||||
|
.statusCode(409)
|
||||||
|
.body("error", containsString("bob"))
|
||||||
|
|
||||||
|
@Test
|
||||||
|
def loginReturns200WithToken(): Unit =
|
||||||
|
givenRequest().body(registerBody("charlie")).when().post("/api/account")
|
||||||
|
givenRequest()
|
||||||
|
.body(loginBody("charlie"))
|
||||||
|
.when()
|
||||||
|
.post("/api/account/login")
|
||||||
|
.`then`()
|
||||||
|
.statusCode(200)
|
||||||
|
.body("token", notNullValue())
|
||||||
|
|
||||||
|
@Test
|
||||||
|
def loginUnauthorizedOnWrongPassword(): Unit =
|
||||||
|
givenRequest().body(registerBody("dave")).when().post("/api/account")
|
||||||
|
givenRequest()
|
||||||
|
.body(loginBody("dave", "wrongpassword"))
|
||||||
|
.when()
|
||||||
|
.post("/api/account/login")
|
||||||
|
.`then`()
|
||||||
|
.statusCode(401)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
def getMeReturns200(): Unit =
|
||||||
|
val token = registerAndLogin("eve")
|
||||||
|
givenRequest()
|
||||||
|
.header("Authorization", s"Bearer $token")
|
||||||
|
.when()
|
||||||
|
.get("/api/account/me")
|
||||||
|
.`then`()
|
||||||
|
.statusCode(200)
|
||||||
|
.body("username", is("eve"))
|
||||||
|
|
||||||
|
@Test
|
||||||
|
def getPublicProfileReturns200(): Unit =
|
||||||
|
givenRequest().body(registerBody("frank")).when().post("/api/account")
|
||||||
|
givenRequest()
|
||||||
|
.when()
|
||||||
|
.get("/api/account/frank")
|
||||||
|
.`then`()
|
||||||
|
.statusCode(200)
|
||||||
|
.body("username", is("frank"))
|
||||||
|
|
||||||
|
@Test
|
||||||
|
def getPublicProfileNotFound(): Unit =
|
||||||
|
givenRequest()
|
||||||
|
.when()
|
||||||
|
.get("/api/account/doesnotexist")
|
||||||
|
.`then`()
|
||||||
|
.statusCode(404)
|
||||||
+179
@@ -0,0 +1,179 @@
|
|||||||
|
package de.nowchess.account.resource
|
||||||
|
|
||||||
|
import de.nowchess.account.client.{CoreGameClient, CoreGameResponse}
|
||||||
|
import io.quarkus.test.InjectMock
|
||||||
|
import io.quarkus.test.junit.QuarkusTest
|
||||||
|
import io.restassured.RestAssured
|
||||||
|
import io.restassured.http.ContentType
|
||||||
|
import org.eclipse.microprofile.rest.client.inject.RestClient
|
||||||
|
import org.hamcrest.Matchers.*
|
||||||
|
import org.junit.jupiter.api.{BeforeEach, Test}
|
||||||
|
import org.mockito.{ArgumentMatchers, Mockito}
|
||||||
|
|
||||||
|
@QuarkusTest
|
||||||
|
class ChallengeResourceTest:
|
||||||
|
|
||||||
|
@InjectMock
|
||||||
|
@RestClient
|
||||||
|
// scalafix:off DisableSyntax.var
|
||||||
|
var coreGameClient: CoreGameClient = scala.compiletime.uninitialized
|
||||||
|
// scalafix:on
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
def setup(): Unit =
|
||||||
|
Mockito.when(coreGameClient.createGame(ArgumentMatchers.any())).thenReturn(CoreGameResponse("test-game-id"))
|
||||||
|
|
||||||
|
private def givenRequest() = RestAssured.`given`().contentType(ContentType.JSON)
|
||||||
|
|
||||||
|
private def registerBody(username: String, suffix: String = "") =
|
||||||
|
val email = s"$username$suffix@test.com"
|
||||||
|
s"""{"username":"$username$suffix","email":"$email","password":"secret"}"""
|
||||||
|
|
||||||
|
private def loginBody(username: String, suffix: String = "") =
|
||||||
|
s"""{"username":"$username$suffix","password":"secret"}"""
|
||||||
|
|
||||||
|
private def registerAndLogin(username: String, suffix: String = ""): String =
|
||||||
|
givenRequest().body(registerBody(username, suffix)).when().post("/api/account")
|
||||||
|
givenRequest()
|
||||||
|
.body(loginBody(username, suffix))
|
||||||
|
.when()
|
||||||
|
.post("/api/account/login")
|
||||||
|
.`then`()
|
||||||
|
.statusCode(200)
|
||||||
|
.extract()
|
||||||
|
.path[String]("token")
|
||||||
|
|
||||||
|
private val clockBody =
|
||||||
|
"""{"color":"random","timeControl":{"type":"clock","limit":300,"increment":5}}"""
|
||||||
|
|
||||||
|
private def authed(token: String) =
|
||||||
|
givenRequest().header("Authorization", s"Bearer $token")
|
||||||
|
|
||||||
|
@Test
|
||||||
|
def createChallengeReturns201(): Unit =
|
||||||
|
val t1 = registerAndLogin("user1c")
|
||||||
|
registerAndLogin("user2c")
|
||||||
|
authed(t1)
|
||||||
|
.contentType(ContentType.JSON)
|
||||||
|
.body(clockBody)
|
||||||
|
.when()
|
||||||
|
.post("/api/challenge/user2c")
|
||||||
|
.`then`()
|
||||||
|
.statusCode(201)
|
||||||
|
.body("status", is("created"))
|
||||||
|
.body("color", is("random"))
|
||||||
|
|
||||||
|
@Test
|
||||||
|
def createChallengeConflictOnDuplicate(): Unit =
|
||||||
|
val t1 = registerAndLogin("user1dup")
|
||||||
|
registerAndLogin("user2dup")
|
||||||
|
authed(t1).contentType(ContentType.JSON).body(clockBody).when().post("/api/challenge/user2dup")
|
||||||
|
authed(t1)
|
||||||
|
.contentType(ContentType.JSON)
|
||||||
|
.body(clockBody)
|
||||||
|
.when()
|
||||||
|
.post("/api/challenge/user2dup")
|
||||||
|
.`then`()
|
||||||
|
.statusCode(409)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
def createChallengeSelfForbidden(): Unit =
|
||||||
|
val token = registerAndLogin("selfuser")
|
||||||
|
authed(token)
|
||||||
|
.contentType(ContentType.JSON)
|
||||||
|
.body(clockBody)
|
||||||
|
.when()
|
||||||
|
.post("/api/challenge/selfuser")
|
||||||
|
.`then`()
|
||||||
|
.statusCode(400)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
def acceptChallengeReturns200(): Unit =
|
||||||
|
val t1 = registerAndLogin("accUser1")
|
||||||
|
val t2 = registerAndLogin("accUser2")
|
||||||
|
val challengeId = authed(t1)
|
||||||
|
.contentType(ContentType.JSON)
|
||||||
|
.body(clockBody)
|
||||||
|
.when()
|
||||||
|
.post("/api/challenge/accUser2")
|
||||||
|
.`then`()
|
||||||
|
.statusCode(201)
|
||||||
|
.extract()
|
||||||
|
.path[String]("id")
|
||||||
|
authed(t2)
|
||||||
|
.when()
|
||||||
|
.post(s"/api/challenge/$challengeId/accept")
|
||||||
|
.`then`()
|
||||||
|
.statusCode(200)
|
||||||
|
.body("status", is("accepted"))
|
||||||
|
.body("gameId", is("test-game-id"))
|
||||||
|
|
||||||
|
@Test
|
||||||
|
def declineChallengeReturns200(): Unit =
|
||||||
|
val t1 = registerAndLogin("decUser1")
|
||||||
|
val t2 = registerAndLogin("decUser2")
|
||||||
|
val challengeId = authed(t1)
|
||||||
|
.contentType(ContentType.JSON)
|
||||||
|
.body(clockBody)
|
||||||
|
.when()
|
||||||
|
.post("/api/challenge/decUser2")
|
||||||
|
.`then`()
|
||||||
|
.statusCode(201)
|
||||||
|
.extract()
|
||||||
|
.path[String]("id")
|
||||||
|
authed(t2)
|
||||||
|
.contentType(ContentType.JSON)
|
||||||
|
.body("""{"reason":"later"}""")
|
||||||
|
.when()
|
||||||
|
.post(s"/api/challenge/$challengeId/decline")
|
||||||
|
.`then`()
|
||||||
|
.statusCode(200)
|
||||||
|
.body("status", is("declined"))
|
||||||
|
.body("declineReason", is("later"))
|
||||||
|
|
||||||
|
@Test
|
||||||
|
def cancelChallengeReturns200(): Unit =
|
||||||
|
val t1 = registerAndLogin("canUser1")
|
||||||
|
registerAndLogin("canUser2")
|
||||||
|
val challengeId = authed(t1)
|
||||||
|
.contentType(ContentType.JSON)
|
||||||
|
.body(clockBody)
|
||||||
|
.when()
|
||||||
|
.post("/api/challenge/canUser2")
|
||||||
|
.`then`()
|
||||||
|
.statusCode(201)
|
||||||
|
.extract()
|
||||||
|
.path[String]("id")
|
||||||
|
authed(t1)
|
||||||
|
.when()
|
||||||
|
.post(s"/api/challenge/$challengeId/cancel")
|
||||||
|
.`then`()
|
||||||
|
.statusCode(200)
|
||||||
|
.body("status", is("canceled"))
|
||||||
|
|
||||||
|
@Test
|
||||||
|
def listChallengesReturnsInAndOut(): Unit =
|
||||||
|
val t1 = registerAndLogin("listUser1")
|
||||||
|
registerAndLogin("listUser2")
|
||||||
|
registerAndLogin("listUser3")
|
||||||
|
authed(t1)
|
||||||
|
.contentType(ContentType.JSON)
|
||||||
|
.body(clockBody)
|
||||||
|
.when()
|
||||||
|
.post("/api/challenge/listUser2")
|
||||||
|
.`then`()
|
||||||
|
.statusCode(201)
|
||||||
|
authed(t1)
|
||||||
|
.contentType(ContentType.JSON)
|
||||||
|
.body(clockBody)
|
||||||
|
.when()
|
||||||
|
.post("/api/challenge/listUser3")
|
||||||
|
.`then`()
|
||||||
|
.statusCode(201)
|
||||||
|
authed(t1)
|
||||||
|
.when()
|
||||||
|
.get("/api/challenge")
|
||||||
|
.`then`()
|
||||||
|
.statusCode(200)
|
||||||
|
.body("out.size()", is(2))
|
||||||
|
.body("in.size()", is(0))
|
||||||
@@ -27,3 +27,85 @@
|
|||||||
|
|
||||||
* NCS-21 Write Scripts to automate certain tasks ([#15](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/15)) ([8051871](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/80518719d536a087d339fe02530825dc07f8b388))
|
* NCS-21 Write Scripts to automate certain tasks ([#15](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/15)) ([8051871](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/80518719d536a087d339fe02530825dc07f8b388))
|
||||||
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
|
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
|
||||||
|
## (2026-04-14)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* NCS-14 implemented insufficient moves rule ([#30](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/30)) ([b0399a4](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b0399a4e489950083066c9538df9a84dcc7a4613))
|
||||||
|
* NCS-21 Write Scripts to automate certain tasks ([#15](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/15)) ([8051871](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/80518719d536a087d339fe02530825dc07f8b388))
|
||||||
|
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
|
||||||
|
## (2026-04-16)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* NCS-13 Implement Threefold Repetition ([#31](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/31)) ([767d305](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/767d3051a76c266050b6335774d66e2db2273c16))
|
||||||
|
* NCS-14 implemented insufficient moves rule ([#30](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/30)) ([b0399a4](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b0399a4e489950083066c9538df9a84dcc7a4613))
|
||||||
|
* NCS-21 Write Scripts to automate certain tasks ([#15](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/15)) ([8051871](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/80518719d536a087d339fe02530825dc07f8b388))
|
||||||
|
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
|
||||||
|
## (2026-04-19)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* NCS-13 Implement Threefold Repetition ([#31](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/31)) ([767d305](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/767d3051a76c266050b6335774d66e2db2273c16))
|
||||||
|
* NCS-14 implemented insufficient moves rule ([#30](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/30)) ([b0399a4](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b0399a4e489950083066c9538df9a84dcc7a4613))
|
||||||
|
* NCS-21 Write Scripts to automate certain tasks ([#15](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/15)) ([8051871](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/80518719d536a087d339fe02530825dc07f8b388))
|
||||||
|
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
|
||||||
|
* NCS-41 Bot Platform ([#33](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/33)) ([dceab08](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dceab0875e6d15f7d3958633cf5dd5b29a851b1d))
|
||||||
|
## (2026-04-19)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* NCS-13 Implement Threefold Repetition ([#31](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/31)) ([767d305](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/767d3051a76c266050b6335774d66e2db2273c16))
|
||||||
|
* NCS-14 implemented insufficient moves rule ([#30](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/30)) ([b0399a4](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b0399a4e489950083066c9538df9a84dcc7a4613))
|
||||||
|
* NCS-21 Write Scripts to automate certain tasks ([#15](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/15)) ([8051871](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/80518719d536a087d339fe02530825dc07f8b388))
|
||||||
|
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
|
||||||
|
* NCS-41 Bot Platform ([#33](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/33)) ([dceab08](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dceab0875e6d15f7d3958633cf5dd5b29a851b1d))
|
||||||
|
## (2026-04-21)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* NCS-13 Implement Threefold Repetition ([#31](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/31)) ([767d305](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/767d3051a76c266050b6335774d66e2db2273c16))
|
||||||
|
* NCS-14 implemented insufficient moves rule ([#30](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/30)) ([b0399a4](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b0399a4e489950083066c9538df9a84dcc7a4613))
|
||||||
|
* NCS-21 Write Scripts to automate certain tasks ([#15](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/15)) ([8051871](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/80518719d536a087d339fe02530825dc07f8b388))
|
||||||
|
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
|
||||||
|
* NCS-37 Quarkus integration ([#35](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/35)) ([5ad5efb](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5ad5efb41e9df9e3dccb48f96a69f06217ab98e1))
|
||||||
|
* NCS-41 Bot Platform ([#33](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/33)) ([dceab08](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dceab0875e6d15f7d3958633cf5dd5b29a851b1d))
|
||||||
|
## (2026-04-21)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* NCS-13 Implement Threefold Repetition ([#31](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/31)) ([767d305](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/767d3051a76c266050b6335774d66e2db2273c16))
|
||||||
|
* NCS-14 implemented insufficient moves rule ([#30](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/30)) ([b0399a4](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b0399a4e489950083066c9538df9a84dcc7a4613))
|
||||||
|
* NCS-21 Write Scripts to automate certain tasks ([#15](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/15)) ([8051871](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/80518719d536a087d339fe02530825dc07f8b388))
|
||||||
|
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
|
||||||
|
* NCS-37 Quarkus integration ([#35](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/35)) ([5ad5efb](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5ad5efb41e9df9e3dccb48f96a69f06217ab98e1))
|
||||||
|
* NCS-41 Bot Platform ([#33](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/33)) ([dceab08](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dceab0875e6d15f7d3958633cf5dd5b29a851b1d))
|
||||||
|
## (2026-04-22)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* NCS-13 Implement Threefold Repetition ([#31](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/31)) ([767d305](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/767d3051a76c266050b6335774d66e2db2273c16))
|
||||||
|
* NCS-14 implemented insufficient moves rule ([#30](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/30)) ([b0399a4](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b0399a4e489950083066c9538df9a84dcc7a4613))
|
||||||
|
* NCS-21 Write Scripts to automate certain tasks ([#15](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/15)) ([8051871](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/80518719d536a087d339fe02530825dc07f8b388))
|
||||||
|
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
|
||||||
|
* NCS-37 Quarkus integration ([#35](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/35)) ([5ad5efb](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5ad5efb41e9df9e3dccb48f96a69f06217ab98e1))
|
||||||
|
* NCS-41 Bot Platform ([#33](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/33)) ([dceab08](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dceab0875e6d15f7d3958633cf5dd5b29a851b1d))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* IO microservice ([#38](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/38)) ([fb5c61d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fb5c61de63292e5d70c06304cba2193686aa1607))
|
||||||
|
## (2026-04-22)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* NCS-13 Implement Threefold Repetition ([#31](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/31)) ([767d305](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/767d3051a76c266050b6335774d66e2db2273c16))
|
||||||
|
* NCS-14 implemented insufficient moves rule ([#30](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/30)) ([b0399a4](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b0399a4e489950083066c9538df9a84dcc7a4613))
|
||||||
|
* NCS-21 Write Scripts to automate certain tasks ([#15](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/15)) ([8051871](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/80518719d536a087d339fe02530825dc07f8b388))
|
||||||
|
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
|
||||||
|
* NCS-37 Quarkus integration ([#35](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/35)) ([f088c4e](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/f088c4e9ffcc498d3d1b6f01e8f50042d5830d55))
|
||||||
|
* NCS-41 Bot Platform ([#33](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/33)) ([8744bee](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/8744bee2dd20966dae90a09c21a43d5b06f59e00))
|
||||||
|
* **rule:** Rules as a microservice ([#39](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/39)) ([093134d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/093134d36c6844ba02a36a28d5d044f09291cd1d))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* IO microservice ([#38](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/38)) ([a386f57](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/a386f57c21d34ead6cc6f92836c52b714597e289))
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user