mirror of
https://github.com/array-in-a-matrix/brainwine.git
synced 2025-04-02 11:11:58 -04:00
API improvements
This commit is contained in:
parent
02a8211a5f
commit
34f45f1fad
19 changed files with 553 additions and 246 deletions
|
@ -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<NewsEntry> 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<NewsEntry> getNews() {
|
||||
return news;
|
||||
return config.getNews();
|
||||
}
|
||||
|
||||
public String getGameServerHost() {
|
||||
return hostAddress + ":" + hostPort;
|
||||
return config.getGameServerIp() + ":" + config.getGameServerPort();
|
||||
}
|
||||
|
||||
public DataFetcher getDataFetcher() {
|
||||
return dataFetcher;
|
||||
}
|
||||
}
|
||||
|
|
15
api/src/main/java/brainwine/api/DataFetcher.java
Normal file
15
api/src/main/java/brainwine/api/DataFetcher.java
Normal file
|
@ -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<ZoneInfo> fetchZoneInfo();
|
||||
}
|
40
api/src/main/java/brainwine/api/DefaultDataFetcher.java
Normal file
40
api/src/main/java/brainwine/api/DefaultDataFetcher.java
Normal file
|
@ -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<ZoneInfo> fetchZoneInfo() {
|
||||
throw exception;
|
||||
}
|
||||
}
|
|
@ -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<String, Object> 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() {
|
||||
|
|
|
@ -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<Zone> zones = new ArrayList<>();
|
||||
zones.addAll(GameServer.getInstance().getZoneManager().getZones());
|
||||
List<Map<String, Object>> 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<Zone> filterZones(Collection<Zone> zones, Predicate<? super Zone> predicate){
|
||||
return zones.stream().filter(predicate).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
49
api/src/main/java/brainwine/api/config/ApiConfig.java
Normal file
49
api/src/main/java/brainwine/api/config/ApiConfig.java
Normal file
|
@ -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<NewsEntry> news;
|
||||
|
||||
@ConstructorProperties({"game_server_ip", "game_server_port", "gateway_port", "portal_port", "news"})
|
||||
public ApiConfig(String gameServerIp, int gameServerPort, int gatewayPort, int portalPort, List<NewsEntry> 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<NewsEntry> getNews() {
|
||||
return news;
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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<String, Object> news = new HashMap<>();
|
||||
|
||||
public NewsRequestHandler(Collection<NewsEntry> posts) {
|
||||
news.put("posts", posts);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(Context ctx) throws Exception {
|
||||
ctx.json(news);
|
||||
}
|
||||
}
|
|
@ -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.");
|
||||
}
|
||||
}
|
|
@ -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.");
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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.");
|
||||
}
|
||||
}
|
|
@ -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<Exception> {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger();
|
||||
|
||||
@Override
|
||||
public void handle(Exception exception, Context ctx) {
|
||||
logger.error("Exception caught", exception);
|
||||
error(ctx, "%s", exception);
|
||||
}
|
||||
}
|
|
@ -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<ZoneInfo> zones = (List<ZoneInfo>)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));
|
||||
}
|
||||
|
||||
}
|
68
api/src/main/java/brainwine/api/models/ZoneInfo.java
Normal file
68
api/src/main/java/brainwine/api/models/ZoneInfo.java
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 <T> void handleQueryParam(Context ctx, String key, Class<T> type, Consumer<T> handler) {
|
||||
Validator<T> param = ctx.queryParam(key, type);
|
||||
T value = param.getOrNull();
|
||||
|
||||
if(value != null) {
|
||||
handler.accept(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
67
src/main/java/brainwine/DirectDataFetcher.java
Normal file
67
src/main/java/brainwine/DirectDataFetcher.java
Normal file
|
@ -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<ZoneInfo> fetchZoneInfo() {
|
||||
List<ZoneInfo> zoneInfo = new ArrayList<>();
|
||||
Collection<Zone> 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;
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue