diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/config/AppConfig.java b/src/main/java/com/cloudhandson/vpdbackoffice/config/AppConfig.java index ab9520a..52bab3c 100644 --- a/src/main/java/com/cloudhandson/vpdbackoffice/config/AppConfig.java +++ b/src/main/java/com/cloudhandson/vpdbackoffice/config/AppConfig.java @@ -9,6 +9,7 @@ import org.springframework.context.annotation.Configuration; @EnableConfigurationProperties({ BackofficeProperties.class, CatalogProperties.class, + GameScopeProperties.class, MaskingProperties.class, McpProperties.class, ProductProperties.class, diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/config/GameScopeProperties.java b/src/main/java/com/cloudhandson/vpdbackoffice/config/GameScopeProperties.java new file mode 100644 index 0000000..fd8ad65 --- /dev/null +++ b/src/main/java/com/cloudhandson/vpdbackoffice/config/GameScopeProperties.java @@ -0,0 +1,26 @@ +package com.cloudhandson.vpdbackoffice.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Names the customer-owned DB view used to resolve game query scopes. + * + *
The view is a stable integration contract, not a Smilegate-specific table. + * Each environment can expose its own game master, alias source, and approved + * object inventory behind this view without changing application code.
+ */ +@ConfigurationProperties(prefix = "backoffice.game-scope") +public record GameScopeProperties( + boolean enabled, + String viewName, + int maxScopes +) { + + public boolean configured() { + return enabled && viewName != null && !viewName.isBlank(); + } + + public int resolvedMaxScopes() { + return maxScopes >= 1 && maxScopes <= 20 ? maxScopes : 8; + } +} diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/GameScopeService.java b/src/main/java/com/cloudhandson/vpdbackoffice/service/GameScopeService.java new file mode 100644 index 0000000..04eee90 --- /dev/null +++ b/src/main/java/com/cloudhandson/vpdbackoffice/service/GameScopeService.java @@ -0,0 +1,191 @@ +package com.cloudhandson.vpdbackoffice.service; + +import com.cloudhandson.vpdbackoffice.config.BackofficeProperties; +import com.cloudhandson.vpdbackoffice.config.GameScopeProperties; +import com.cloudhandson.vpdbackoffice.domain.token.BearerTokenRecord; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.time.Clock; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.regex.Pattern; +import org.springframework.stereotype.Service; + +/** + * Resolves only DB-declared game scopes before an agent invokes a SQL worker. + * + *The service never infers a game, table, prefix, or support status. It reads + * the configured customer view and returns only aliases physically present in the + * question. This makes unavailable games observable when they are retained in + * the customer's game master, while keeping product-specific data out of code.
+ */ +@Service +public class GameScopeService { + + private static final int MAX_QUESTION_LENGTH = 4_000; + private static final Pattern VIEW_NAME = Pattern.compile( + "^[A-Za-z][A-Za-z0-9_$#]*(?:\\.[A-Za-z][A-Za-z0-9_$#]*)?$"); + + private final BackofficeProperties properties; + private final GameScopeProperties gameScopeProperties; + private final BearerTokenService bearerTokenService; + private final Clock clock; + + public GameScopeService( + BackofficeProperties properties, + GameScopeProperties gameScopeProperties, + BearerTokenService bearerTokenService, + Clock clock + ) { + this.properties = properties; + this.gameScopeProperties = gameScopeProperties; + this.bearerTokenService = bearerTokenService; + this.clock = clock; + } + + public GameScopeResult resolve(String bearerToken, String question) { + requireActiveToken(bearerToken); + String normalizedQuestion = requiredQuestion(question); + BackofficeProperties.SelectAi selectAi = requiredSelectAi(); + String viewName = requiredViewName(); + String comparableQuestion = comparable(normalizedQuestion); + + List