diff --git a/api/src/main/java/brainwine/api/Api.java b/api/src/main/java/brainwine/api/Api.java index 185a7e0..c8a9d2c 100644 --- a/api/src/main/java/brainwine/api/Api.java +++ b/api/src/main/java/brainwine/api/Api.java @@ -1,42 +1,36 @@ package brainwine.api; import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.List; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import com.fasterxml.jackson.databind.ObjectMapper; - -import brainwine.api.models.NewsEntry; +import brainwine.api.config.ApiConfig; +import brainwine.api.config.NewsEntry; +import brainwine.shared.JsonHelper; public class Api { private static final Logger logger = LogManager.getLogger(); - private final List news = new ArrayList<>(); - private final PropertyFile properties; - private final int gatewayPort; - private final int portalPort; - private final String hostAddress; - private final int hostPort; + private final ApiConfig config; + private final DataFetcher dataFetcher; private final GatewayService gatewayService; private final PortalService portalService; public Api() { + this(new DefaultDataFetcher()); + } + + public Api(DataFetcher dataFetcher) { long startTime = System.currentTimeMillis(); logger.info("Starting API ..."); - loadNews(); - logger.info("Loading properties ..."); - properties = new PropertyFile(new File("api.properties")); - gatewayPort = properties.getInt("gateway_port", 5001); - portalPort = properties.getInt("portal_port", 5003); - hostAddress = properties.getString("gameserver_address", "127.0.0.1"); - hostPort = properties.getInt("gameserver_port", 5002); - gatewayService = new GatewayService(this, gatewayPort); - portalService = new PortalService(portalPort); + this.dataFetcher = dataFetcher; + logger.info("Using data fetcher {}", dataFetcher.getClass().getName()); + logger.info("Loading configuration ..."); + config = loadConfig(); + gatewayService = new GatewayService(this, config.getGatewayPort()); + portalService = new PortalService(this, config.getPortalPort()); logger.info("All done! API startup took {} milliseconds", System.currentTimeMillis() - startTime); } @@ -46,32 +40,34 @@ public class Api { portalService.stop(); } - private void loadNews() { - logger.info("Loading news data ..."); - news.clear(); - File newsFile = new File("news.json"); - + private ApiConfig loadConfig() { try { - ObjectMapper mapper = new ObjectMapper(); + File file = new File("api.json"); - if(!newsFile.exists()) { - logger.info("Generating default news file ..."); - NewsEntry defaultNews = new NewsEntry("Default News", "This news entry was automatically generated.\nEdit 'news.json' to make your own!", "A more civilised age..."); - mapper.writerWithDefaultPrettyPrinter().writeValue(newsFile, Arrays.asList(defaultNews)); + if(!file.exists()) { + file.createNewFile(); + JsonHelper.writeValue(file, ApiConfig.DEFAULT_CONFIG); + return ApiConfig.DEFAULT_CONFIG; } - news.addAll(mapper.readerForListOf(NewsEntry.class).readValue(newsFile)); - Collections.reverse(news); // Reverse the list so that the last article in the file gets shown first. + return JsonHelper.readValue(file, ApiConfig.class); } catch (Exception e) { - logger.error("Failed to load news data", e); + logger.fatal("Failed to load configuration", e); + System.exit(-1); } + + return ApiConfig.DEFAULT_CONFIG; } public List getNews() { - return news; + return config.getNews(); } public String getGameServerHost() { - return hostAddress + ":" + hostPort; + return config.getGameServerIp() + ":" + config.getGameServerPort(); + } + + public DataFetcher getDataFetcher() { + return dataFetcher; } } diff --git a/api/src/main/java/brainwine/api/DataFetcher.java b/api/src/main/java/brainwine/api/DataFetcher.java new file mode 100644 index 0000000..a72d6ad --- /dev/null +++ b/api/src/main/java/brainwine/api/DataFetcher.java @@ -0,0 +1,15 @@ +package brainwine.api; + +import java.util.Collection; + +import brainwine.api.models.ZoneInfo; + +public interface DataFetcher { + + public boolean isPlayerNameTaken(String name); + public String registerPlayer(String name); + public String login(String name, String password); + public boolean verifyAuthToken(String name, String token); + public boolean verifyApiToken(String apiToken); + public Collection fetchZoneInfo(); +} diff --git a/api/src/main/java/brainwine/api/DefaultDataFetcher.java b/api/src/main/java/brainwine/api/DefaultDataFetcher.java new file mode 100644 index 0000000..9c6b42c --- /dev/null +++ b/api/src/main/java/brainwine/api/DefaultDataFetcher.java @@ -0,0 +1,40 @@ +package brainwine.api; + +import java.util.Collection; + +import brainwine.api.models.ZoneInfo; + +public class DefaultDataFetcher implements DataFetcher { + + private static final UnsupportedOperationException exception = new UnsupportedOperationException("DefaultDataFetcher behavior is undefined."); + + @Override + public boolean isPlayerNameTaken(String name) { + throw exception; + } + + @Override + public String registerPlayer(String name) { + throw exception; + } + + @Override + public String login(String name, String password) { + throw exception; + } + + @Override + public boolean verifyAuthToken(String name, String token) { + throw exception; + } + + @Override + public boolean verifyApiToken(String apiToken) { + throw exception; + } + + @Override + public Collection fetchZoneInfo() { + throw exception; + } +} diff --git a/api/src/main/java/brainwine/api/GatewayService.java b/api/src/main/java/brainwine/api/GatewayService.java index 30297e5..f050242 100644 --- a/api/src/main/java/brainwine/api/GatewayService.java +++ b/api/src/main/java/brainwine/api/GatewayService.java @@ -1,17 +1,15 @@ package brainwine.api; -import java.util.HashMap; -import java.util.Map; - import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import brainwine.api.models.PlayersRequest; -import brainwine.api.models.ServerConnectInfo; -import brainwine.api.models.SessionsRequest; -import brainwine.api.util.ContextUtils; -import brainwine.gameserver.GameServer; -import brainwine.gameserver.entity.player.PlayerManager; +import brainwine.api.handlers.NewsRequestHandler; +import brainwine.api.handlers.PasswordResetHandler; +import brainwine.api.handlers.PasswordForgotHandler; +import brainwine.api.handlers.PlayerLoginHandler; +import brainwine.api.handlers.PlayerRegistrationHandler; +import brainwine.api.handlers.RwcPurchaseHandler; +import brainwine.api.handlers.SimpleExceptionHandler; import io.javalin.Javalin; public class GatewayService { @@ -21,75 +19,16 @@ public class GatewayService { public GatewayService(Api api, int port) { logger.info("Starting GatewayService @ port {} ...", port); - PlayerManager playerManager = GameServer.getInstance().getPlayerManager(); + DataFetcher dataFetcher = api.getDataFetcher(); + String gameServerHost = api.getGameServerHost(); gateway = Javalin.create().start(port); - gateway.exception(Exception.class, (e, ctx) -> { - ContextUtils.error(ctx, "%s", e); - logger.error("Exception caught", e); - }); - - // News - gateway.get("/clients", ctx ->{ - Map json = new HashMap<>(); - json.put("posts", api.getNews()); - ctx.json(json); - }); - - // Registration - gateway.post("/players", ctx -> { - PlayersRequest request = ctx.bodyValidator(PlayersRequest.class).get(); - String name = request.getName(); - - if(playerManager.getPlayer(name) != null) { - ContextUtils.error(ctx, "Sorry, this username has already been taken."); - return; - } - - String token = playerManager.register(name); - ctx.json(new ServerConnectInfo(api.getGameServerHost(), name, token)); - }); - - // Login - gateway.post("/sessions", ctx -> { - SessionsRequest request = ctx.bodyValidator(SessionsRequest.class).get(); - String name = request.getName(); - String password = request.getPassword(); - String token = request.getToken(); - - if(password != null) { - token = playerManager.login(name, password); - - if(token == null) { - ContextUtils.error(ctx, "Username or password is incorrect. Please check your credentials."); - return; - } - } else if(token != null) { - if(!playerManager.verifyAuthToken(name, token)) { - ContextUtils.error(ctx, "The provided session token is invalid or has expired. Please try relogging."); - return; - } - } else { - ContextUtils.error(ctx, "No credentials provided."); - return; - } - - ctx.json(new ServerConnectInfo(api.getGameServerHost(), name, token)); - }); - - // Password reset request - gateway.post("/passwords/request", ctx -> { - ContextUtils.error(ctx, "Sorry, this feature is not implemented yet."); - }); - - // Password reset token entry - gateway.post("/passwords/reset", ctx -> { - ContextUtils.error(ctx, "Sorry, this feature is not implemented yet."); - }); - - // RWC purchases - gateway.post("/purchases", ctx -> { - ContextUtils.error(ctx, "Sorry, purchases with RWC are disabled."); - }); + gateway.exception(Exception.class, new SimpleExceptionHandler()); + gateway.get("/clients", new NewsRequestHandler(api.getNews())); + gateway.post("/players", new PlayerRegistrationHandler(dataFetcher, gameServerHost)); + gateway.post("/sessions", new PlayerLoginHandler(dataFetcher, gameServerHost)); + gateway.post("/passwords/request", new PasswordForgotHandler()); + gateway.post("/passwords/reset", new PasswordResetHandler()); + gateway.post("/purchases", new RwcPurchaseHandler()); } public void stop() { diff --git a/api/src/main/java/brainwine/api/PortalService.java b/api/src/main/java/brainwine/api/PortalService.java index e9f95af..1c610cd 100644 --- a/api/src/main/java/brainwine/api/PortalService.java +++ b/api/src/main/java/brainwine/api/PortalService.java @@ -1,88 +1,29 @@ package brainwine.api; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.function.Predicate; -import java.util.stream.Collectors; - import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import brainwine.gameserver.GameServer; -import brainwine.gameserver.zone.Zone; +import brainwine.api.handlers.SimpleExceptionHandler; +import brainwine.api.handlers.ZoneSearchHandler; import io.javalin.Javalin; -import io.javalin.http.Context; /** * aka Zone Searcher */ public class PortalService { - public static final int PAGE_SIZE = 6; private static final Logger logger = LogManager.getLogger(); private final Javalin portal; - public PortalService(int port) { + public PortalService(Api api, int port) { logger.info("Starting PortalService @ port {} ...", port); + DataFetcher dataFetcher = api.getDataFetcher(); portal = Javalin.create().start(port); - portal.get("/v1/worlds", ctx -> { - List zones = new ArrayList<>(); - zones.addAll(GameServer.getInstance().getZoneManager().getZones()); - List> zoneInfoList = new ArrayList<>(); - int page = 1; - - // Filtering - if(hasQueryParam(ctx, "page")) { - page = Integer.parseInt(ctx.queryParam("page")); - } - - if(hasQueryParam(ctx, "biome")) { - String value = ctx.queryParam("biome"); - zones = filterZones(zones, zone -> zone.getBiome().toString().equalsIgnoreCase(value)); - } - - if(hasQueryParam(ctx, "name")) { - String value = ctx.queryParam("name"); - zones = filterZones(zones, zone -> zone.getName().toLowerCase().contains(value.toLowerCase())); - } - - if(hasQueryParam(ctx, "sort")) { - String value = ctx.queryParam("sort"); - - switch(value) { - case "popularity": - zones = filterZones(zones, zone -> zone.getPlayers().size() > 0); - break; - default: - break; - } - } - - // Page - int fromIndex = (page - 1) * PAGE_SIZE; - int toIndex = page * PAGE_SIZE; - zones = zones.subList(fromIndex < 0 ? 0 : fromIndex > zones.size() ? zones.size() : fromIndex, toIndex > zones.size() ? zones.size() : toIndex); - - // Compile info - for(Zone zone : zones) { - zoneInfoList.add(zone.getPortalConfig()); - } - - ctx.json(zoneInfoList); - }); + portal.exception(Exception.class, new SimpleExceptionHandler()); + portal.get("/v1/worlds", new ZoneSearchHandler(dataFetcher)); } public void stop() { portal.stop(); } - - private boolean hasQueryParam(Context ctx, String param) { - return ctx.queryParam(param) != null; - } - - private List filterZones(Collection zones, Predicate predicate){ - return zones.stream().filter(predicate).collect(Collectors.toList()); - } } diff --git a/api/src/main/java/brainwine/api/PropertyFile.java b/api/src/main/java/brainwine/api/PropertyFile.java deleted file mode 100644 index e0dcaae..0000000 --- a/api/src/main/java/brainwine/api/PropertyFile.java +++ /dev/null @@ -1,67 +0,0 @@ -package brainwine.api; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.util.Properties; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class PropertyFile { - - private static final Logger logger = LogManager.getLogger(); - private final Properties properties = new Properties(); - private final File file; - - public PropertyFile(File file) { - this.file = file; - - if(file.exists()) { - try { - properties.load(new FileInputStream(file)); - } catch (Exception e) { - logger.error("Could not load {}", file, e); - save(); - } - } else { - save(); - } - } - - public void save() { - try { - properties.store(new FileOutputStream(file), "Here you can change the server connection information."); - } catch(Exception e) { - logger.error("Could not save {}", file, e); - } - } - - public void setProperty(String key, Object value) { - properties.setProperty(key, "" + value); - } - - public String getString(String key, String def) { - if(!properties.containsKey(key)) { - properties.setProperty(key, def); - save(); - return def; - } - - return properties.getProperty(key); - } - - public int getInt(String key, int def) { - try { - return Integer.parseInt(getString(key, "" + def)); - } catch(NumberFormatException e) { - properties.setProperty(key, "" + def); - save(); - return def; - } - } - - public boolean getBoolean(String key, boolean def) { - return Boolean.parseBoolean(getString(key, "" + def)); - } -} diff --git a/api/src/main/java/brainwine/api/config/ApiConfig.java b/api/src/main/java/brainwine/api/config/ApiConfig.java new file mode 100644 index 0000000..ff6278c --- /dev/null +++ b/api/src/main/java/brainwine/api/config/ApiConfig.java @@ -0,0 +1,49 @@ +package brainwine.api.config; + +import java.beans.ConstructorProperties; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class ApiConfig { + + public static final ApiConfig DEFAULT_CONFIG = new ApiConfig("127.0.0.1", 5002, 5001, 5003, Arrays.asList(NewsEntry.DEFAULT_NEWS)); + private final String gameServerIp; + private final int gameServerPort; + private final int gatewayPort; + private final int portalPort; + private final List news; + + @ConstructorProperties({"game_server_ip", "game_server_port", "gateway_port", "portal_port", "news"}) + public ApiConfig(String gameServerIp, int gameServerPort, int gatewayPort, int portalPort, List news) { + this.gameServerIp = gameServerIp; + this.gameServerPort = gameServerPort; + this.gatewayPort = gatewayPort; + this.portalPort = portalPort; + this.news = news; + Collections.reverse(this.news); + } + + public String getGameServerIp() { + return gameServerIp; + } + + public int getGameServerPort() { + return gameServerPort; + } + + public int getGatewayPort() { + return gatewayPort; + } + + public int getPortalPort() { + return portalPort; + } + + public List getNews() { + return news; + } +} diff --git a/api/src/main/java/brainwine/api/models/NewsEntry.java b/api/src/main/java/brainwine/api/config/NewsEntry.java similarity index 66% rename from api/src/main/java/brainwine/api/models/NewsEntry.java rename to api/src/main/java/brainwine/api/config/NewsEntry.java index 5df159f..a93dad3 100644 --- a/api/src/main/java/brainwine/api/models/NewsEntry.java +++ b/api/src/main/java/brainwine/api/config/NewsEntry.java @@ -1,11 +1,16 @@ -package brainwine.api.models; +package brainwine.api.config; import java.beans.ConstructorProperties; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +@JsonIgnoreProperties(ignoreUnknown = true) public class NewsEntry { + public static final NewsEntry DEFAULT_NEWS = new NewsEntry("Default News", + "This news entry was automatically generated.\nEdit 'api.json' to make your own!", + "A long time ago..."); private final String title; private final String content; private final String date; diff --git a/api/src/main/java/brainwine/api/handlers/NewsRequestHandler.java b/api/src/main/java/brainwine/api/handlers/NewsRequestHandler.java new file mode 100644 index 0000000..4fe56c7 --- /dev/null +++ b/api/src/main/java/brainwine/api/handlers/NewsRequestHandler.java @@ -0,0 +1,23 @@ +package brainwine.api.handlers; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import brainwine.api.config.NewsEntry; +import io.javalin.http.Context; +import io.javalin.http.Handler; + +public class NewsRequestHandler implements Handler { + + private final Map news = new HashMap<>(); + + public NewsRequestHandler(Collection posts) { + news.put("posts", posts); + } + + @Override + public void handle(Context ctx) throws Exception { + ctx.json(news); + } +} diff --git a/api/src/main/java/brainwine/api/handlers/PasswordForgotHandler.java b/api/src/main/java/brainwine/api/handlers/PasswordForgotHandler.java new file mode 100644 index 0000000..7401478 --- /dev/null +++ b/api/src/main/java/brainwine/api/handlers/PasswordForgotHandler.java @@ -0,0 +1,14 @@ +package brainwine.api.handlers; + +import static brainwine.api.util.ContextUtils.*; + +import io.javalin.http.Context; +import io.javalin.http.Handler; + +public class PasswordForgotHandler implements Handler { + + @Override + public void handle(Context ctx) throws Exception { + error(ctx, "Sorry, it is currently not possible to reset your password."); + } +} diff --git a/api/src/main/java/brainwine/api/handlers/PasswordResetHandler.java b/api/src/main/java/brainwine/api/handlers/PasswordResetHandler.java new file mode 100644 index 0000000..ddb8d3c --- /dev/null +++ b/api/src/main/java/brainwine/api/handlers/PasswordResetHandler.java @@ -0,0 +1,14 @@ +package brainwine.api.handlers; + +import static brainwine.api.util.ContextUtils.*; + +import io.javalin.http.Context; +import io.javalin.http.Handler; + +public class PasswordResetHandler implements Handler { + + @Override + public void handle(Context ctx) throws Exception { + error(ctx, "Sorry, it is currently not possible to reset your password."); + } +} diff --git a/api/src/main/java/brainwine/api/handlers/PlayerLoginHandler.java b/api/src/main/java/brainwine/api/handlers/PlayerLoginHandler.java new file mode 100644 index 0000000..b49464b --- /dev/null +++ b/api/src/main/java/brainwine/api/handlers/PlayerLoginHandler.java @@ -0,0 +1,47 @@ +package brainwine.api.handlers; + +import static brainwine.api.util.ContextUtils.*; + +import brainwine.api.DataFetcher; +import brainwine.api.models.ServerConnectInfo; +import brainwine.api.models.SessionsRequest; +import io.javalin.http.Context; +import io.javalin.http.Handler; + +public class PlayerLoginHandler implements Handler { + + private final DataFetcher dataFetcher; + private final String gameServerHost; + + public PlayerLoginHandler(DataFetcher dataFetcher, String gameServerHost) { + this.dataFetcher = dataFetcher; + this.gameServerHost = gameServerHost; + } + + @Override + public void handle(Context ctx) throws Exception { + SessionsRequest request = ctx.bodyValidator(SessionsRequest.class).get(); + String name = request.getName(); + String password = request.getPassword(); + String token = request.getToken(); + + if(password != null) { + token = dataFetcher.login(name, password); + + if(token == null) { + error(ctx, "Username or password is incorrect. Please check your credentials."); + return; + } + } else if(token != null) { + if(!dataFetcher.verifyAuthToken(name, token)) { + error(ctx, "The provided session token is invalid or has expired. Please try relogging."); + return; + } + } else { + error(ctx, "No credentials provided."); + return; + } + + ctx.json(new ServerConnectInfo(gameServerHost, name, token)); + } +} diff --git a/api/src/main/java/brainwine/api/handlers/PlayerRegistrationHandler.java b/api/src/main/java/brainwine/api/handlers/PlayerRegistrationHandler.java new file mode 100644 index 0000000..94f7f2d --- /dev/null +++ b/api/src/main/java/brainwine/api/handlers/PlayerRegistrationHandler.java @@ -0,0 +1,34 @@ +package brainwine.api.handlers; + +import static brainwine.api.util.ContextUtils.*; + +import brainwine.api.DataFetcher; +import brainwine.api.models.PlayersRequest; +import brainwine.api.models.ServerConnectInfo; +import io.javalin.http.Context; +import io.javalin.http.Handler; + +public class PlayerRegistrationHandler implements Handler { + + private final DataFetcher dataFetcher; + private final String gameServerHost; + + public PlayerRegistrationHandler(DataFetcher dataFetcher, String gameServerHost) { + this.dataFetcher = dataFetcher; + this.gameServerHost = gameServerHost; + } + + @Override + public void handle(Context ctx) throws Exception { + PlayersRequest request = ctx.bodyValidator(PlayersRequest.class).get(); + String name = request.getName(); + + if(dataFetcher.isPlayerNameTaken(name)) { + error(ctx, "Sorry, this username has already been taken."); + return; + } + + String token = dataFetcher.registerPlayer(name); + ctx.json(new ServerConnectInfo(gameServerHost, name, token)); + } +} diff --git a/api/src/main/java/brainwine/api/handlers/RwcPurchaseHandler.java b/api/src/main/java/brainwine/api/handlers/RwcPurchaseHandler.java new file mode 100644 index 0000000..1655fc5 --- /dev/null +++ b/api/src/main/java/brainwine/api/handlers/RwcPurchaseHandler.java @@ -0,0 +1,14 @@ +package brainwine.api.handlers; + +import static brainwine.api.util.ContextUtils.*; + +import io.javalin.http.Context; +import io.javalin.http.Handler; + +public class RwcPurchaseHandler implements Handler { + + @Override + public void handle(Context ctx) throws Exception { + error(ctx, "Sorry, RWC purchases are disabled."); + } +} diff --git a/api/src/main/java/brainwine/api/handlers/SimpleExceptionHandler.java b/api/src/main/java/brainwine/api/handlers/SimpleExceptionHandler.java new file mode 100644 index 0000000..cce0904 --- /dev/null +++ b/api/src/main/java/brainwine/api/handlers/SimpleExceptionHandler.java @@ -0,0 +1,20 @@ +package brainwine.api.handlers; + +import static brainwine.api.util.ContextUtils.*; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import io.javalin.http.Context; +import io.javalin.http.ExceptionHandler; + +public class SimpleExceptionHandler implements ExceptionHandler { + + private static final Logger logger = LogManager.getLogger(); + + @Override + public void handle(Exception exception, Context ctx) { + logger.error("Exception caught", exception); + error(ctx, "%s", exception); + } +} diff --git a/api/src/main/java/brainwine/api/handlers/ZoneSearchHandler.java b/api/src/main/java/brainwine/api/handlers/ZoneSearchHandler.java new file mode 100644 index 0000000..eabbc36 --- /dev/null +++ b/api/src/main/java/brainwine/api/handlers/ZoneSearchHandler.java @@ -0,0 +1,77 @@ +package brainwine.api.handlers; + +import static brainwine.api.util.ContextUtils.*; + +import java.util.List; + +import brainwine.api.DataFetcher; +import brainwine.api.models.ZoneInfo; +import io.javalin.http.Context; +import io.javalin.http.Handler; + +public class ZoneSearchHandler implements Handler { + + public static final int PAGE_SIZE = 6; + private final DataFetcher dataFetcher; + + public ZoneSearchHandler(DataFetcher dataFetcher) { + this.dataFetcher = dataFetcher; + } + + @Override + public void handle(Context ctx) throws Exception { + final List zones = (List)dataFetcher.fetchZoneInfo(); + String apiToken = ctx.queryParam("api_token"); + + if(apiToken == null || !dataFetcher.verifyApiToken(apiToken)) { + error(ctx, "A valid api token is required for this request."); + return; + } + + handleQueryParam(ctx, "name", String.class, name -> { + zones.removeIf(zone -> !zone.getName().toLowerCase().contains(name.toLowerCase())); + }); + + handleQueryParam(ctx, "activity", String.class, activity -> { + zones.removeIf(zone -> zone.getActivity() == null || !zone.getActivity().equalsIgnoreCase(activity)); + }); + + handleQueryParam(ctx, "biome", String.class, biome -> { + zones.removeIf(zone -> !zone.getBiome().equalsIgnoreCase(biome)); + }); + + handleQueryParam(ctx, "pvp", boolean.class, pvp -> { + zones.removeIf(zone -> zone.isPvp() != pvp); + }); + + handleQueryParam(ctx, "protected", boolean.class, locked -> { + zones.removeIf(zone -> zone.isLocked() != locked); + }); + + handleQueryParam(ctx, "residency", String.class, residency -> { + zones.clear(); // not supported yet + }); + + handleQueryParam(ctx, "account", String.class, account -> { + zones.clear(); // not supported yet + }); + + handleQueryParam(ctx, "sort", String.class, sort -> { + switch(sort) { + case "popularity": + zones.removeIf(zone -> zone.getPlayerCount() == 0); + zones.sort((a, b) -> Integer.compare(b.getPlayerCount(), a.getPlayerCount())); + break; + case "created": + break; + } + }); + + // Page + int page = ctx.queryParam("page", Integer.class, "1").get(); + int fromIndex = (page - 1) * PAGE_SIZE; + int toIndex = page * PAGE_SIZE; + ctx.json(zones.subList(fromIndex < 0 ? 0 : fromIndex > zones.size() ? zones.size() : fromIndex, toIndex > zones.size() ? zones.size() : toIndex)); + } + +} diff --git a/api/src/main/java/brainwine/api/models/ZoneInfo.java b/api/src/main/java/brainwine/api/models/ZoneInfo.java new file mode 100644 index 0000000..294d686 --- /dev/null +++ b/api/src/main/java/brainwine/api/models/ZoneInfo.java @@ -0,0 +1,68 @@ +package brainwine.api.models; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class ZoneInfo { + + private final String name; + private final String biome; + private final String activity; + private final boolean pvp; + private final boolean premium; + private final boolean locked; + private final int playerCount; + private final double explorationProgress; + private final String generationDate; + + public ZoneInfo(String name, String biome, String activity, boolean pvp, boolean premium, boolean locked, int playerCount, double explorationProgress, String generationDate) { + this.name = name; + this.biome = biome; + this.activity = activity; + this.pvp = pvp; + this.premium = premium; + this.locked = locked; + this.playerCount = playerCount; + this.explorationProgress = explorationProgress; + this.generationDate = generationDate; + } + + public String getName() { + return name; + } + + public String getBiome() { + return biome; + } + + public String getActivity() { + return activity; + } + + public boolean isPvp() { + return pvp; + } + + public boolean isPremium() { + return premium; + } + + @JsonProperty("protected") + public boolean isLocked() { + return locked; + } + + @JsonProperty("players") + public int getPlayerCount() { + return playerCount; + } + + @JsonProperty("explored") + public double getExplorationProgress() { + return explorationProgress; + } + + @JsonProperty("gen_date") + public String getGenerationDate() { + return generationDate; + } +} diff --git a/api/src/main/java/brainwine/api/util/ContextUtils.java b/api/src/main/java/brainwine/api/util/ContextUtils.java index b012b7c..5cc3e77 100644 --- a/api/src/main/java/brainwine/api/util/ContextUtils.java +++ b/api/src/main/java/brainwine/api/util/ContextUtils.java @@ -2,7 +2,9 @@ package brainwine.api.util; import java.util.HashMap; import java.util.Map; +import java.util.function.Consumer; +import io.javalin.core.validation.Validator; import io.javalin.http.Context; public class ContextUtils { @@ -12,4 +14,13 @@ public class ContextUtils { map.put("error", String.format(message, args)); ctx.json(map); } + + public static void handleQueryParam(Context ctx, String key, Class type, Consumer handler) { + Validator param = ctx.queryParam(key, type); + T value = param.getOrNull(); + + if(value != null) { + handler.accept(value); + } + } } diff --git a/src/main/java/brainwine/DirectDataFetcher.java b/src/main/java/brainwine/DirectDataFetcher.java new file mode 100644 index 0000000..0354230 --- /dev/null +++ b/src/main/java/brainwine/DirectDataFetcher.java @@ -0,0 +1,67 @@ +package brainwine; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import brainwine.api.DataFetcher; +import brainwine.api.models.ZoneInfo; +import brainwine.gameserver.entity.player.PlayerManager; +import brainwine.gameserver.zone.Zone; +import brainwine.gameserver.zone.ZoneManager; + +public class DirectDataFetcher implements DataFetcher { + + private final PlayerManager playerManager; + private final ZoneManager zoneManager; + + public DirectDataFetcher(PlayerManager playerManager, ZoneManager zoneManager) { + this.playerManager = playerManager; + this.zoneManager = zoneManager; + } + + @Override + public boolean isPlayerNameTaken(String name) { + return playerManager.getPlayer(name) != null; + } + + @Override + public String registerPlayer(String name) { + return playerManager.register(name); + } + + @Override + public String login(String name, String password) { + return playerManager.login(name, password); + } + + @Override + public boolean verifyAuthToken(String name, String token) { + return playerManager.verifyAuthToken(name, token); + } + + @Override + public boolean verifyApiToken(String apiToken) { + return true; // TODO + } + + @Override + public Collection fetchZoneInfo() { + List zoneInfo = new ArrayList<>(); + Collection zones = zoneManager.getZones(); + + for(Zone zone : zones) { + zoneInfo.add(new ZoneInfo(zone.getName(), + zone.getBiome().getId(), + null, + false, + false, + false, + zone.getPlayers().size(), + zone.getExplorationProgress(), + "2021-02-15")); + } + + return zoneInfo; + } +}