Added move registration and validation
This commit is contained in:
@@ -2,20 +2,26 @@
|
||||
FROM gradle:9.2-jdk21 AS build
|
||||
WORKDIR /app
|
||||
|
||||
# Copy only build scripts first
|
||||
COPY settings.gradle.kts build.gradle.kts ./
|
||||
COPY gradle.properties* ./
|
||||
COPY gradle ./gradle
|
||||
|
||||
# Warm dependency cache
|
||||
RUN gradle build -x test --no-daemon || true
|
||||
|
||||
# Copy source last
|
||||
COPY src ./src
|
||||
|
||||
RUN gradle clean bootJar --no-daemon
|
||||
# Build the jar
|
||||
RUN gradle bootJar --no-daemon
|
||||
|
||||
# -------- Runtime --------
|
||||
FROM eclipse-temurin:21-jdk-alpine
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=build /app/build/libs/*.jar app.jar
|
||||
|
||||
ENTRYPOINT ["java", "-jar", "app.jar"]
|
||||
|
||||
ENTRYPOINT ["java", "-jar", "app.jar"]
|
||||
@@ -1,6 +1,8 @@
|
||||
services:
|
||||
boardmate-api:
|
||||
build: .
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: boardmate-api
|
||||
ports:
|
||||
- "8000:8080"
|
||||
@@ -11,6 +13,9 @@ services:
|
||||
depends_on:
|
||||
- mongodb
|
||||
- elasticsearch
|
||||
volumes:
|
||||
- ./.gradle:/home/gradle/.gradle # Use your existing local Gradle and build directories
|
||||
- ./build:/app/.gradle # optional, only if you want project cache mapped too
|
||||
|
||||
elasticsearch:
|
||||
image: 'docker.elastic.co/elasticsearch/elasticsearch:7.17.10'
|
||||
|
||||
1
api/gradle.properties
Normal file
1
api/gradle.properties
Normal file
@@ -0,0 +1 @@
|
||||
org.gradle.caching=true
|
||||
@@ -0,0 +1,15 @@
|
||||
package be.naaturel.boardmateapi.common.helpers;
|
||||
|
||||
public class Logger {
|
||||
public static void displayInfo(String message){
|
||||
final String BLUE = "\u001B[34m";
|
||||
final String RESET = "\u001B[0m";
|
||||
System.out.println(BLUE + "[Info] --- " + message + RESET);
|
||||
}
|
||||
|
||||
public static void displayError(String message){
|
||||
String GREEN = "\u001B[32m";
|
||||
String RESET = "\u001B[0m";
|
||||
System.out.println(GREEN + "[Error] --- " + message + RESET);
|
||||
}
|
||||
}
|
||||
@@ -3,20 +3,27 @@ package be.naaturel.boardmateapi.common.models;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class Game {
|
||||
|
||||
private static final Pattern MOVE_PATTERN = Pattern.compile(
|
||||
"^(O-O(-O)?|([KQRBN])?([a-h])?([1-8])?x?[a-h][1-8](=[QRBN])?[+#]?)$"
|
||||
);
|
||||
|
||||
private final String id;
|
||||
private final String whiteName;
|
||||
private final String blackName;
|
||||
private final int timeValue;
|
||||
private final int increment;
|
||||
private final List<Move> moves;
|
||||
private final List<String> moves;
|
||||
|
||||
public Game(String whiteName, String blackName, int timeValue, int increment) {
|
||||
this(whiteName, blackName, timeValue, increment, new ArrayList<>());
|
||||
this(null, whiteName, blackName, timeValue, increment, new ArrayList<>());
|
||||
}
|
||||
|
||||
public Game(String whiteName, String blackName, int timeValue, int increment, Collection<Move> moves){
|
||||
public Game(String id, String whiteName, String blackName, int timeValue, int increment, Collection<String> moves){
|
||||
this.id = id;
|
||||
this.whiteName = whiteName;
|
||||
this.blackName = blackName;
|
||||
this.timeValue = timeValue;
|
||||
@@ -24,6 +31,10 @@ public class Game {
|
||||
this.moves = new ArrayList<>(moves);
|
||||
}
|
||||
|
||||
public String getId(){
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public String getBlackName() {
|
||||
return blackName;
|
||||
}
|
||||
@@ -40,11 +51,12 @@ public class Game {
|
||||
return this.increment;
|
||||
}
|
||||
|
||||
public List<Move> getMoves() {
|
||||
public List<String > getMoves() {
|
||||
return new ArrayList<>(moves);
|
||||
}
|
||||
|
||||
public void addMove(Move m) {
|
||||
public void addMove(String m) throws IllegalArgumentException {
|
||||
validateMove(m);
|
||||
this.moves.add(m);
|
||||
}
|
||||
|
||||
@@ -52,12 +64,12 @@ public class Game {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
int moveNumber = 1;
|
||||
int movesInTurn = 1;
|
||||
for(Move m : moves){
|
||||
for(String m : moves){
|
||||
|
||||
if(movesInTurn == 1) {
|
||||
builder.append(String.format("%d.", moveNumber));
|
||||
builder.append(String.format("%d. ", moveNumber));
|
||||
}
|
||||
builder.append(String.format(" %s", m));
|
||||
builder.append(String.format("%s ", m));
|
||||
|
||||
if(movesInTurn == 2){
|
||||
moveNumber++;
|
||||
@@ -66,7 +78,16 @@ public class Game {
|
||||
movesInTurn++;
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
return builder.toString().trim();
|
||||
}
|
||||
|
||||
public static void validateMove(String move) throws IllegalArgumentException {
|
||||
if (move == null || move.isBlank()) {
|
||||
throw new IllegalArgumentException("Move cannot be empty");
|
||||
}
|
||||
|
||||
if (!MOVE_PATTERN.matcher(move).matches()) {
|
||||
throw new IllegalArgumentException("Invalid PGN move: " + move);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,10 +15,10 @@ public class AppConfigurations {
|
||||
@Value("${sec.cors.authorizedHeader}")
|
||||
public String[] authorizedHeaders;
|
||||
|
||||
@Value("${spring.data.mongodb.uri}")
|
||||
@Value("${spring.mongodb.uri}")
|
||||
public String connectionString;
|
||||
|
||||
@Value("${spring.data.mongodb.database}")
|
||||
@Value("${spring.mongodb.database}")
|
||||
public String database;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package be.naaturel.boardmateapi.configurations;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.MessageDigest;
|
||||
|
||||
public class BuildHasher {
|
||||
|
||||
public static String get() throws Exception {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
|
||||
try (InputStream is =
|
||||
BuildHasher.class.getProtectionDomain()
|
||||
.getCodeSource()
|
||||
.getLocation()
|
||||
.openStream()) {
|
||||
|
||||
digest.update(is.readAllBytes());
|
||||
}
|
||||
|
||||
byte[] hashBytes = digest.digest();
|
||||
|
||||
StringBuilder hex = new StringBuilder(hashBytes.length * 2);
|
||||
for (byte b : hashBytes) hex.append(String.format("%02x", b));
|
||||
return hex.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,17 +8,14 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
|
||||
@Configuration
|
||||
public class MongoConfig {
|
||||
|
||||
private final AppConfigurations conf;
|
||||
|
||||
@Autowired
|
||||
public MongoConfig(AppConfigurations appConf) {
|
||||
this.conf = appConf;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MongoTemplate mongoTemplate() {
|
||||
System.out.println(">>> Connection string : " + conf.connectionString);
|
||||
System.out.println(">>> Used database : " + conf.database);
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package be.naaturel.boardmateapi.configurations;
|
||||
|
||||
import be.naaturel.boardmateapi.common.helpers.Logger;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Arrays;
|
||||
|
||||
@Configuration
|
||||
public class Startup {
|
||||
|
||||
@Bean
|
||||
public String log() {
|
||||
try {
|
||||
String hash = BuildHasher.get();
|
||||
Logger.displayInfo("Hash for this version " + hash);
|
||||
return hash;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Unable to generate a hash for this version\n" + Arrays.toString(e.getStackTrace()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
package be.naaturel.boardmateapi.controllers;
|
||||
|
||||
import be.naaturel.boardmateapi.common.models.Move;
|
||||
import be.naaturel.boardmateapi.common.models.Game;
|
||||
import be.naaturel.boardmateapi.controllers.dtos.GameDto;
|
||||
import be.naaturel.boardmateapi.controllers.mappings.GameMapper;
|
||||
import be.naaturel.boardmateapi.services.GameService;
|
||||
import jakarta.websocket.server.PathParam;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -30,47 +32,54 @@ public class GameController {
|
||||
}
|
||||
|
||||
@GetMapping("/games/{id}")
|
||||
public ResponseEntity<Game> retrieveGames(@PathVariable String id){
|
||||
public ResponseEntity<ResponseBody<GameDto>> retrieveGames(@PathVariable String id){
|
||||
ResponseBody<GameDto> response = ResponseBody.createEmpty();
|
||||
try{
|
||||
Game g = service.retrieveGame(id);
|
||||
|
||||
GameDto dto = GameMapper.toDto(g);
|
||||
response.setData(dto);
|
||||
response.setSuccess(true);
|
||||
return ResponseEntity
|
||||
.status(HttpStatus.OK)
|
||||
.body(g);
|
||||
.body(response);
|
||||
} catch (Exception e){
|
||||
e.printStackTrace();
|
||||
response.setMessage(e.getMessage());
|
||||
return ResponseEntity
|
||||
.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.build();
|
||||
.body(response);
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/create")
|
||||
public ResponseEntity<?> CreateParty(@RequestBody Game game){
|
||||
public ResponseEntity<ResponseBody<String>> CreateParty(@RequestBody GameDto game){
|
||||
ResponseBody<String> response = ResponseBody.createEmpty();
|
||||
try{
|
||||
service.create(game);
|
||||
|
||||
Game model = GameMapper.toModel(game);
|
||||
String result = service.create(model);
|
||||
response.setData(result);
|
||||
response.setSuccess(true);
|
||||
return ResponseEntity.
|
||||
status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.build();
|
||||
status(HttpStatus.OK)
|
||||
.body(response);
|
||||
} catch (Exception e){
|
||||
e.printStackTrace();
|
||||
response.setMessage(e.getMessage());
|
||||
return ResponseEntity
|
||||
.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(e.getStackTrace());
|
||||
.body(response);
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/moves/add")
|
||||
public ResponseEntity<?> AddMove(@RequestBody String gameId, @RequestBody Move move){
|
||||
@PostMapping("/moves/add/{gameId}")
|
||||
public ResponseEntity<?> AddMove(@PathVariable String gameId, @RequestBody String move){
|
||||
try{
|
||||
service.addMove(gameId, move);
|
||||
|
||||
return ResponseEntity
|
||||
.status(HttpStatus.OK)
|
||||
.build();
|
||||
} catch (Exception e){
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
|
||||
return ResponseEntity
|
||||
.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package be.naaturel.boardmateapi.controllers;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class ResponseBody<T> {
|
||||
|
||||
private T data;
|
||||
private String message;
|
||||
private boolean success;
|
||||
|
||||
/**
|
||||
* Create a default empty response body containing no data, no message and flagged as failure
|
||||
* @return Empty response of T
|
||||
* @param <T> The wrapped data type
|
||||
*/
|
||||
public static <T> ResponseBody<T> createEmpty() {
|
||||
return new ResponseBody<>(null, null,false);
|
||||
}
|
||||
|
||||
private ResponseBody(T data, String messageOpt, boolean success){
|
||||
this.data = data;
|
||||
this.message = null;
|
||||
this.success = success;
|
||||
}
|
||||
|
||||
public T getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public Optional<String> getMessage() {
|
||||
return Optional.ofNullable(message);
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public void setData(T data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public void setSuccess(boolean success) {
|
||||
this.success = success;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package be.naaturel.boardmateapi.controllers.dtos;
|
||||
|
||||
public class GameDto {
|
||||
private String whiteName;
|
||||
private String blackName;
|
||||
private int timeValue;
|
||||
private int increment;
|
||||
|
||||
public String getWhiteName() {
|
||||
return whiteName;
|
||||
}
|
||||
|
||||
public String getBlackName() {
|
||||
return blackName;
|
||||
}
|
||||
|
||||
public int getTimeValue() {
|
||||
return timeValue;
|
||||
}
|
||||
|
||||
public int getIncrement() {
|
||||
return increment;
|
||||
}
|
||||
|
||||
public void setWhiteName(String whiteName) {
|
||||
this.whiteName = whiteName;
|
||||
}
|
||||
|
||||
public void setBlackName(String blackName) {
|
||||
this.blackName = blackName;
|
||||
}
|
||||
|
||||
public void setTimeValue(int timeValue) {
|
||||
this.timeValue = timeValue;
|
||||
}
|
||||
|
||||
public void setIncrement(int increment){
|
||||
this.increment = increment;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package be.naaturel.boardmateapi.controllers.mappings;
|
||||
|
||||
import be.naaturel.boardmateapi.common.models.Game;
|
||||
import be.naaturel.boardmateapi.controllers.dtos.GameDto;
|
||||
|
||||
|
||||
public class GameMapper {
|
||||
|
||||
public static Game toModel(GameDto dto){
|
||||
return new Game(dto.getWhiteName(), dto.getBlackName(), dto.getTimeValue(), dto.getIncrement());
|
||||
}
|
||||
|
||||
public static GameDto toDto(Game model){
|
||||
GameDto g = new GameDto();
|
||||
g.setWhiteName(model.getWhiteName());
|
||||
g.setBlackName(model.getBlackName());
|
||||
g.setTimeValue(model.getTimeValue());
|
||||
g.setIncrement(model.getIncrement());
|
||||
return g;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -36,6 +36,10 @@ public class GameDto {
|
||||
return players;
|
||||
}
|
||||
|
||||
public void setId(String id){
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public void setPlayers(Map<String, PlayerDto> players) {
|
||||
this.players = players;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package be.naaturel.boardmateapi.repository.mappings;
|
||||
|
||||
import be.naaturel.boardmateapi.common.models.Game;
|
||||
import be.naaturel.boardmateapi.common.models.Move;
|
||||
import be.naaturel.boardmateapi.repository.dtos.GameDto;
|
||||
import be.naaturel.boardmateapi.repository.dtos.PlayerDto;
|
||||
import be.naaturel.boardmateapi.repository.dtos.TimeControlDto;
|
||||
@@ -15,6 +14,9 @@ public class GameMapper {
|
||||
public static GameDto toDto(Game game){
|
||||
GameDto dto = new GameDto();
|
||||
|
||||
String id = game.getId();
|
||||
dto.setId(id);
|
||||
|
||||
PlayerDto white = PlayerMapper.toDto(game.getWhiteName());
|
||||
PlayerDto black = PlayerMapper.toDto(game.getBlackName());
|
||||
Map<String, PlayerDto> players = new HashMap<>();
|
||||
@@ -28,7 +30,6 @@ public class GameMapper {
|
||||
List<String> moves =
|
||||
game.getMoves()
|
||||
.stream()
|
||||
.map(Move::toString)
|
||||
.toList();
|
||||
|
||||
dto.setMoves(moves);
|
||||
@@ -36,10 +37,12 @@ public class GameMapper {
|
||||
}
|
||||
|
||||
public static Game toModel(GameDto dto){
|
||||
String id = dto.getId();
|
||||
String whiteName = dto.getPlayers().get("white").getName();
|
||||
String blackName = dto.getPlayers().get("black").getName();
|
||||
int timeValue = dto.getTimeControl().getValue();
|
||||
int increment = dto.getTimeControl().getIncrement();
|
||||
return new Game(whiteName, blackName, timeValue, increment);
|
||||
List<String> moves = dto.getMoves();
|
||||
return new Game(id, whiteName, blackName, timeValue, increment, moves);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,10 +38,9 @@ public class GameService {
|
||||
return gameDto.getId();
|
||||
}
|
||||
|
||||
public String addMove(@RequestBody String gameId, @RequestBody Move move) throws Exception {
|
||||
public void addMove(@RequestBody String gameId, @RequestBody String move) {
|
||||
Game g = retrieveGame(gameId);
|
||||
g.addMove(move);
|
||||
save(g);
|
||||
return move.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,9 @@ management.endpoint.health.show-details=always
|
||||
management.endpoint.prometheus.enabled=true
|
||||
|
||||
#=============DOCUMENTATION=============
|
||||
springdoc.swagger-ui.path=/api-docs
|
||||
springdoc.api-docs.path=/v1/api-docs
|
||||
springdoc.swagger-ui.path=/docs
|
||||
springdoc.api-docs.path=/v1/docs
|
||||
|
||||
#=============DATABASE=============
|
||||
spring.data.mongodb.uri=mongodb://board-mate-user:apx820kcng@mongodb:27017/board-mate-db?authSource=board-mate-db
|
||||
spring.data.mongodb.database=board-mate-db
|
||||
spring.mongodb.uri=mongodb://board-mate-user:apx820kcng@mongodb:27017/board-mate-db?authSource=board-mate-db
|
||||
spring.mongodb.database=board-mate-db
|
||||
Reference in New Issue
Block a user