mirror of
https://github.com/array-in-a-matrix/brainwine.git
synced 2025-04-02 11:11:58 -04:00
Embrace simplicity
This commit is contained in:
parent
beb99d96da
commit
87a8c991bd
10 changed files with 208 additions and 273 deletions
|
@ -1,40 +1,147 @@
|
|||
package brainwine.api;
|
||||
|
||||
import static brainwine.api.util.ContextUtils.error;
|
||||
import static brainwine.shared.LogMarkers.SERVER_MARKER;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import brainwine.api.handlers.NewsRequestHandler;
|
||||
import brainwine.api.handlers.PasswordForgotHandler;
|
||||
import brainwine.api.handlers.PasswordResetHandler;
|
||||
import brainwine.api.handlers.PlayerLoginHandler;
|
||||
import brainwine.api.handlers.PlayerRegistrationHandler;
|
||||
import brainwine.api.handlers.RwcPurchaseHandler;
|
||||
import brainwine.api.handlers.SimpleExceptionHandler;
|
||||
import brainwine.api.models.PlayersRequest;
|
||||
import brainwine.api.models.ServerConnectInfo;
|
||||
import brainwine.api.models.SessionsRequest;
|
||||
import brainwine.shared.JsonHelper;
|
||||
import io.javalin.Javalin;
|
||||
import io.javalin.http.Context;
|
||||
import io.javalin.plugin.json.JavalinJackson;
|
||||
|
||||
public class GatewayService {
|
||||
|
||||
private static final Pattern namePattern = Pattern.compile("^[a-zA-Z0-9_.-]{4,20}$");
|
||||
private static final Logger logger = LogManager.getLogger();
|
||||
private final Api api;
|
||||
private final DataFetcher dataFetcher;
|
||||
private final Javalin gateway;
|
||||
|
||||
public GatewayService(Api api, int port) {
|
||||
this.api = api;
|
||||
this.dataFetcher = api.getDataFetcher();
|
||||
logger.info(SERVER_MARKER, "Starting GatewayService @ port {} ...", port);
|
||||
DataFetcher dataFetcher = api.getDataFetcher();
|
||||
String gameServerHost = api.getGameServerHost();
|
||||
gateway = Javalin.create(config -> config.jsonMapper(new JavalinJackson(JsonHelper.MAPPER))).start(port);
|
||||
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());
|
||||
gateway = Javalin.create(config -> config.jsonMapper(new JavalinJackson(JsonHelper.MAPPER)))
|
||||
.exception(Exception.class, this::handleException)
|
||||
.get("/clients", this::handleNewsRequest)
|
||||
.post("/players", this::handlePlayerRegistration)
|
||||
.post("/sessions", this::handlePlayerLogin)
|
||||
.post("/passwords/request", this::handlePasswordForget)
|
||||
.post("/passwords/reset", this::handlePasswordReset)
|
||||
.post("/purchases", this::handleInAppPurchase)
|
||||
.start(port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception handler function.
|
||||
*/
|
||||
private void handleException(Exception exception, Context ctx) {
|
||||
logger.error(SERVER_MARKER, "Exception caught", exception);
|
||||
error(ctx, "%s", exception);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler function for news requests. (main menu)
|
||||
*/
|
||||
private void handleNewsRequest(Context ctx) {
|
||||
Map<String, Object> news = new HashMap<>();
|
||||
news.put("posts", api.getNews());
|
||||
ctx.json(news);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler function for registering a new account.
|
||||
*/
|
||||
private void handlePlayerRegistration(Context ctx) {
|
||||
PlayersRequest request = ctx.bodyValidator(PlayersRequest.class).get();
|
||||
String name = request.getName();
|
||||
|
||||
// Check if name is too short, too long or contains illegal characters
|
||||
if(!namePattern.matcher(name).matches()) {
|
||||
error(ctx, "Please enter a valid username.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if a player with this name already exists
|
||||
if(dataFetcher.isPlayerNameTaken(name)) {
|
||||
error(ctx, "Sorry, this username has already been taken.");
|
||||
return;
|
||||
}
|
||||
|
||||
String token = dataFetcher.registerPlayer(name);
|
||||
ctx.json(new ServerConnectInfo(api.getGameServerHost(), name, token));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler function for logging into an existing account with username & password/auth token.
|
||||
* If the user logs in using a password, a new auth token is generated.
|
||||
*/
|
||||
private void handlePlayerLogin(Context ctx) {
|
||||
SessionsRequest request = ctx.bodyValidator(SessionsRequest.class).get();
|
||||
String name = request.getName();
|
||||
String password = request.getPassword();
|
||||
String token = request.getToken();
|
||||
|
||||
// If a password is present, try to log in and generate an auth token.
|
||||
// Null auth token = incorrect username/password combination.
|
||||
// Otherwise, if an auth token is present, try to verify that instead.
|
||||
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(api.getGameServerHost(), name, token));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler function for initiating password resets.
|
||||
* TODO wip
|
||||
*/
|
||||
private void handlePasswordForget(Context ctx) {
|
||||
error(ctx, "Sorry, it is currently not possible to reset your password.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler function for processing password resets.
|
||||
* TODO wip
|
||||
*/
|
||||
private void handlePasswordReset(Context ctx) {
|
||||
error(ctx, "Sorry, it is currently not possible to reset your password.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler function for in-app purchases.
|
||||
* Permanently doomed to err, as it will never be implemented.
|
||||
*/
|
||||
private void handleInAppPurchase(Context ctx) {
|
||||
error(ctx, "Sorry, in-app purchases are not supported.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the gateway service.
|
||||
* @see Javalin#stop()
|
||||
*/
|
||||
public void stop() {
|
||||
gateway.stop();
|
||||
}
|
||||
|
|
|
@ -1,14 +1,18 @@
|
|||
package brainwine.api;
|
||||
|
||||
import static brainwine.api.util.ContextUtils.error;
|
||||
import static brainwine.api.util.ContextUtils.handleQueryParam;
|
||||
import static brainwine.shared.LogMarkers.SERVER_MARKER;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import brainwine.api.handlers.SimpleExceptionHandler;
|
||||
import brainwine.api.handlers.ZoneSearchHandler;
|
||||
import brainwine.api.models.ZoneInfo;
|
||||
import brainwine.shared.JsonHelper;
|
||||
import io.javalin.Javalin;
|
||||
import io.javalin.http.Context;
|
||||
import io.javalin.plugin.json.JavalinJackson;
|
||||
|
||||
/**
|
||||
|
@ -16,17 +20,91 @@ import io.javalin.plugin.json.JavalinJackson;
|
|||
*/
|
||||
public class PortalService {
|
||||
|
||||
private static final int zoneSearchPageSize = 6;
|
||||
private static final Logger logger = LogManager.getLogger();
|
||||
private final DataFetcher dataFetcher;
|
||||
private final Javalin portal;
|
||||
|
||||
public PortalService(Api api, int port) {
|
||||
this.dataFetcher = api.getDataFetcher();
|
||||
logger.info(SERVER_MARKER, "Starting PortalService @ port {} ...", port);
|
||||
DataFetcher dataFetcher = api.getDataFetcher();
|
||||
portal = Javalin.create(config -> config.jsonMapper(new JavalinJackson(JsonHelper.MAPPER))).start(port);
|
||||
portal.exception(Exception.class, new SimpleExceptionHandler());
|
||||
portal.get("/v1/worlds", new ZoneSearchHandler(dataFetcher));
|
||||
portal = Javalin.create(config -> config.jsonMapper(new JavalinJackson(JsonHelper.MAPPER)))
|
||||
.exception(Exception.class, this::handleException)
|
||||
.get("/v1/worlds", this::handleZoneSearch)
|
||||
.start(port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception handler function.
|
||||
*/
|
||||
private void handleException(Exception exception, Context ctx) {
|
||||
logger.error(SERVER_MARKER, "Exception caught", exception);
|
||||
error(ctx, "%s", exception);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler function for zone search requests.
|
||||
* TODO could use some work.
|
||||
*/
|
||||
private void handleZoneSearch(Context ctx) {
|
||||
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.queryParamAsClass("page", Integer.class).getOrDefault(1);
|
||||
int fromIndex = (page - 1) * zoneSearchPageSize;
|
||||
int toIndex = page * zoneSearchPageSize;
|
||||
ctx.json(zones.subList(fromIndex < 0 ? 0 : fromIndex > zones.size() ? zones.size() : fromIndex, toIndex > zones.size() ? zones.size() : toIndex));
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the portal service.
|
||||
* @see Javalin#stop()
|
||||
*/
|
||||
public void stop() {
|
||||
portal.stop();
|
||||
}
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
package brainwine.api.handlers;
|
||||
|
||||
import static brainwine.api.util.ContextUtils.error;
|
||||
|
||||
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.");
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
package brainwine.api.handlers;
|
||||
|
||||
import static brainwine.api.util.ContextUtils.error;
|
||||
|
||||
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.");
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
package brainwine.api.handlers;
|
||||
|
||||
import static brainwine.api.util.ContextUtils.error;
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
package brainwine.api.handlers;
|
||||
|
||||
import static brainwine.api.util.ContextUtils.error;
|
||||
|
||||
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(!name.matches("^[a-zA-Z0-9_.-]{4,20}$")) {
|
||||
error(ctx, "Please enter a valid username.");
|
||||
return;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
package brainwine.api.handlers;
|
||||
|
||||
import static brainwine.api.util.ContextUtils.error;
|
||||
|
||||
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.");
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
package brainwine.api.handlers;
|
||||
|
||||
import static brainwine.api.util.ContextUtils.error;
|
||||
import static brainwine.shared.LogMarkers.SERVER_MARKER;
|
||||
|
||||
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(SERVER_MARKER, "Exception caught", exception);
|
||||
error(ctx, "%s", exception);
|
||||
}
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
package brainwine.api.handlers;
|
||||
|
||||
import static brainwine.api.util.ContextUtils.error;
|
||||
import static brainwine.api.util.ContextUtils.handleQueryParam;
|
||||
|
||||
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.queryParamAsClass("page", Integer.class).getOrDefault(1);
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Reference in a new issue