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;
|
package brainwine.api;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import brainwine.api.config.ApiConfig;
|
||||||
|
import brainwine.api.config.NewsEntry;
|
||||||
import brainwine.api.models.NewsEntry;
|
import brainwine.shared.JsonHelper;
|
||||||
|
|
||||||
public class Api {
|
public class Api {
|
||||||
|
|
||||||
private static final Logger logger = LogManager.getLogger();
|
private static final Logger logger = LogManager.getLogger();
|
||||||
private final List<NewsEntry> news = new ArrayList<>();
|
private final ApiConfig config;
|
||||||
private final PropertyFile properties;
|
private final DataFetcher dataFetcher;
|
||||||
private final int gatewayPort;
|
|
||||||
private final int portalPort;
|
|
||||||
private final String hostAddress;
|
|
||||||
private final int hostPort;
|
|
||||||
private final GatewayService gatewayService;
|
private final GatewayService gatewayService;
|
||||||
private final PortalService portalService;
|
private final PortalService portalService;
|
||||||
|
|
||||||
public Api() {
|
public Api() {
|
||||||
|
this(new DefaultDataFetcher());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Api(DataFetcher dataFetcher) {
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
logger.info("Starting API ...");
|
logger.info("Starting API ...");
|
||||||
loadNews();
|
this.dataFetcher = dataFetcher;
|
||||||
logger.info("Loading properties ...");
|
logger.info("Using data fetcher {}", dataFetcher.getClass().getName());
|
||||||
properties = new PropertyFile(new File("api.properties"));
|
logger.info("Loading configuration ...");
|
||||||
gatewayPort = properties.getInt("gateway_port", 5001);
|
config = loadConfig();
|
||||||
portalPort = properties.getInt("portal_port", 5003);
|
gatewayService = new GatewayService(this, config.getGatewayPort());
|
||||||
hostAddress = properties.getString("gameserver_address", "127.0.0.1");
|
portalService = new PortalService(this, config.getPortalPort());
|
||||||
hostPort = properties.getInt("gameserver_port", 5002);
|
|
||||||
gatewayService = new GatewayService(this, gatewayPort);
|
|
||||||
portalService = new PortalService(portalPort);
|
|
||||||
logger.info("All done! API startup took {} milliseconds", System.currentTimeMillis() - startTime);
|
logger.info("All done! API startup took {} milliseconds", System.currentTimeMillis() - startTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,32 +40,34 @@ public class Api {
|
||||||
portalService.stop();
|
portalService.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadNews() {
|
private ApiConfig loadConfig() {
|
||||||
logger.info("Loading news data ...");
|
|
||||||
news.clear();
|
|
||||||
File newsFile = new File("news.json");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
File file = new File("api.json");
|
||||||
|
|
||||||
if(!newsFile.exists()) {
|
if(!file.exists()) {
|
||||||
logger.info("Generating default news file ...");
|
file.createNewFile();
|
||||||
NewsEntry defaultNews = new NewsEntry("Default News", "This news entry was automatically generated.\nEdit 'news.json' to make your own!", "A more civilised age...");
|
JsonHelper.writeValue(file, ApiConfig.DEFAULT_CONFIG);
|
||||||
mapper.writerWithDefaultPrettyPrinter().writeValue(newsFile, Arrays.asList(defaultNews));
|
return ApiConfig.DEFAULT_CONFIG;
|
||||||
}
|
}
|
||||||
|
|
||||||
news.addAll(mapper.readerForListOf(NewsEntry.class).readValue(newsFile));
|
return JsonHelper.readValue(file, ApiConfig.class);
|
||||||
Collections.reverse(news); // Reverse the list so that the last article in the file gets shown first.
|
|
||||||
} catch (Exception e) {
|
} 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() {
|
public List<NewsEntry> getNews() {
|
||||||
return news;
|
return config.getNews();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getGameServerHost() {
|
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;
|
package brainwine.api;
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import brainwine.api.models.PlayersRequest;
|
import brainwine.api.handlers.NewsRequestHandler;
|
||||||
import brainwine.api.models.ServerConnectInfo;
|
import brainwine.api.handlers.PasswordResetHandler;
|
||||||
import brainwine.api.models.SessionsRequest;
|
import brainwine.api.handlers.PasswordForgotHandler;
|
||||||
import brainwine.api.util.ContextUtils;
|
import brainwine.api.handlers.PlayerLoginHandler;
|
||||||
import brainwine.gameserver.GameServer;
|
import brainwine.api.handlers.PlayerRegistrationHandler;
|
||||||
import brainwine.gameserver.entity.player.PlayerManager;
|
import brainwine.api.handlers.RwcPurchaseHandler;
|
||||||
|
import brainwine.api.handlers.SimpleExceptionHandler;
|
||||||
import io.javalin.Javalin;
|
import io.javalin.Javalin;
|
||||||
|
|
||||||
public class GatewayService {
|
public class GatewayService {
|
||||||
|
@ -21,75 +19,16 @@ public class GatewayService {
|
||||||
|
|
||||||
public GatewayService(Api api, int port) {
|
public GatewayService(Api api, int port) {
|
||||||
logger.info("Starting GatewayService @ port {} ...", 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 = Javalin.create().start(port);
|
||||||
gateway.exception(Exception.class, (e, ctx) -> {
|
gateway.exception(Exception.class, new SimpleExceptionHandler());
|
||||||
ContextUtils.error(ctx, "%s", e);
|
gateway.get("/clients", new NewsRequestHandler(api.getNews()));
|
||||||
logger.error("Exception caught", e);
|
gateway.post("/players", new PlayerRegistrationHandler(dataFetcher, gameServerHost));
|
||||||
});
|
gateway.post("/sessions", new PlayerLoginHandler(dataFetcher, gameServerHost));
|
||||||
|
gateway.post("/passwords/request", new PasswordForgotHandler());
|
||||||
// News
|
gateway.post("/passwords/reset", new PasswordResetHandler());
|
||||||
gateway.get("/clients", ctx ->{
|
gateway.post("/purchases", new RwcPurchaseHandler());
|
||||||
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.");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stop() {
|
public void stop() {
|
||||||
|
|
|
@ -1,88 +1,29 @@
|
||||||
package brainwine.api;
|
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.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import brainwine.gameserver.GameServer;
|
import brainwine.api.handlers.SimpleExceptionHandler;
|
||||||
import brainwine.gameserver.zone.Zone;
|
import brainwine.api.handlers.ZoneSearchHandler;
|
||||||
import io.javalin.Javalin;
|
import io.javalin.Javalin;
|
||||||
import io.javalin.http.Context;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* aka Zone Searcher
|
* aka Zone Searcher
|
||||||
*/
|
*/
|
||||||
public class PortalService {
|
public class PortalService {
|
||||||
|
|
||||||
public static final int PAGE_SIZE = 6;
|
|
||||||
private static final Logger logger = LogManager.getLogger();
|
private static final Logger logger = LogManager.getLogger();
|
||||||
private final Javalin portal;
|
private final Javalin portal;
|
||||||
|
|
||||||
public PortalService(int port) {
|
public PortalService(Api api, int port) {
|
||||||
logger.info("Starting PortalService @ port {} ...", port);
|
logger.info("Starting PortalService @ port {} ...", port);
|
||||||
|
DataFetcher dataFetcher = api.getDataFetcher();
|
||||||
portal = Javalin.create().start(port);
|
portal = Javalin.create().start(port);
|
||||||
portal.get("/v1/worlds", ctx -> {
|
portal.exception(Exception.class, new SimpleExceptionHandler());
|
||||||
List<Zone> zones = new ArrayList<>();
|
portal.get("/v1/worlds", new ZoneSearchHandler(dataFetcher));
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stop() {
|
public void stop() {
|
||||||
portal.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 java.beans.ConstructorProperties;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
public class NewsEntry {
|
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 title;
|
||||||
private final String content;
|
private final String content;
|
||||||
private final String date;
|
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.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import io.javalin.core.validation.Validator;
|
||||||
import io.javalin.http.Context;
|
import io.javalin.http.Context;
|
||||||
|
|
||||||
public class ContextUtils {
|
public class ContextUtils {
|
||||||
|
@ -12,4 +14,13 @@ public class ContextUtils {
|
||||||
map.put("error", String.format(message, args));
|
map.put("error", String.format(message, args));
|
||||||
ctx.json(map);
|
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