[16.2k Lines] Documented the API

This commit is contained in:
Rainnny7 2021-02-19 17:04:17 -05:00
parent 88cc7d8aa9
commit 5edd79c0da
11 changed files with 75 additions and 23 deletions

View File

@ -11,8 +11,8 @@ import zone.themcgamer.api.model.ModelSerializer;
import zone.themcgamer.api.model.impl.*;
import zone.themcgamer.api.repository.AccountRepository;
import zone.themcgamer.api.route.AccountRoute;
import zone.themcgamer.api.route.PlayerStatusRoute;
import zone.themcgamer.api.route.ServersRoute;
import zone.themcgamer.api.route.StatusRoute;
import zone.themcgamer.data.APIAccessLevel;
import zone.themcgamer.data.jedis.JedisController;
import zone.themcgamer.data.jedis.data.APIKey;
@ -57,12 +57,12 @@ public class API {
addModel(NodeModel.class);
addModel(ServerGroupModel.class);
addModel(AccountModel.class);
addModel(StatusModel.class);
addModel(PlayerStatusModel.class);
// Adding the routes
addRoute(new ServersRoute());
addRoute(new AccountRoute(accountRepository));
addRoute(new StatusRoute());
addRoute(new PlayerStatusRoute());
// 404 Handling
Spark.notFound((request, response) -> {
@ -110,6 +110,8 @@ public class API {
System.out.println("Handling incoming request from \"" + request.ip() + "\" using path \"" + request.pathInfo() + "\"");
APIKey key = new APIKey(UUID.randomUUID().toString(), APIAccessLevel.STANDARD);
// If authentication is enabled, handle the api key checking and display a Unauthorized error
// if there is a problem checking the key or the key is invalid
if (requiresAuthentication) {
String apiKey = request.headers("key");
if (apiKey == null)
@ -133,7 +135,9 @@ public class API {
if (method.getParameterTypes().length != 3)
throw new APIException("Invalid route defined");
Object object = method.invoke(entry.getKey(), request, response, key);
jsonObject.addProperty("success", "true");
jsonObject.addProperty("success", "true"); // Marking the request as successful in the JsonObject
// Format the Object returned from the route accordingly
if (object instanceof IModel) {
if (models.contains(object.getClass()))
jsonObject.add("value", gson.toJsonTree(((IModel) object).toMap(), HashMap.class));
@ -144,13 +148,17 @@ public class API {
jsonObject.add("value", gson.toJsonTree(object, HashMap.class));
else jsonObject.addProperty("value", object.toString());
} catch (Throwable ex) {
// If there is an error thrown from a route, we wanna fetch the error from the invoke method
// and get the cause of the exception
if (ex instanceof InvocationTargetException)
ex = ex.getCause();
String message = ex.getLocalizedMessage();
// If the exception has no message associated with it, display the type of exception instead
if (message == null || (message.trim().isEmpty()))
message = ex.getClass().getSimpleName();
jsonObject.addProperty("success", "false");
jsonObject.addProperty("error", message);
// If the caught exception is not an API exception, print the stacktrace
if (!(ex instanceof APIException)) {
System.err.println("The route \"" + entry.getKey().getClass().getSimpleName() + "\" raised an exception:");
ex.printStackTrace();
@ -162,10 +170,20 @@ public class API {
}
}
/**
* Adding a {@link IModel}
*
* @param modelClass the class of the model to add
*/
private static void addModel(Class<? extends IModel> modelClass) {
models.add(modelClass);
}
/**
* Adding a route
*
* @param object the instance of the route to add
*/
private static void addRoute(Object object) {
List<Method> methods = routes.getOrDefault(object, new ArrayList<>());
for (Method method : object.getClass().getMethods()) {

View File

@ -1,16 +1,14 @@
package zone.themcgamer.api;
import lombok.NoArgsConstructor;
/**
* This {@link RuntimeException} gets thrown when there is a problem handling a {@link RestPath}
*
* @author Braydon
*/
@NoArgsConstructor
public class APIException extends RuntimeException {
/**
* Constructs a new runtime exception with {@code null} as its
* detail message. The cause is not initialized, and may subsequently be
* initialized by a call to {@link #initCause}.
*/
public APIException() {}
/**
* Constructs a new runtime exception with the specified detail message.
* The cause is not initialized, and may subsequently be initialized by a

View File

@ -4,6 +4,8 @@ import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* The API version is used in {@link RestPath}. It represents the version the path is using
*
* @author Braydon
*/
@AllArgsConstructor @Getter

View File

@ -8,6 +8,8 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation represents information for a specific Rest path
*
* @author Braydon
*/
@Retention(RetentionPolicy.RUNTIME)

View File

@ -3,6 +3,8 @@ package zone.themcgamer.api.model;
import java.util.HashMap;
/**
* This interface represents a custom Object that will displayed as json when handling requests
*
* @author Braydon
*/
public interface IModel {

View File

@ -9,6 +9,8 @@ import java.lang.reflect.Type;
import java.util.Map;
/**
* This class handles serializing of {@link IModel}'s
*
* @author Braydon
*/
public class ModelSerializer implements JsonSerializer<IModel> {

View File

@ -13,7 +13,7 @@ import java.util.UUID;
* @author Braydon
*/
@AllArgsConstructor @Setter @Getter @ToString
public class StatusModel implements IModel {
public class PlayerStatusModel implements IModel {
private final UUID uuid;
private final String playerName;
private String server;

View File

@ -14,6 +14,8 @@ import java.util.Objects;
import java.util.UUID;
/**
* This repository handles fetching of {@link AccountModel}'s from MySQL
*
* @author Braydon
*/
public class AccountRepository extends MySQLRepository {
@ -23,6 +25,12 @@ public class AccountRepository extends MySQLRepository {
super(dataSource);
}
/**
* Fetch the {@link AccountModel} with the provided {@link UUID}
*
* @param uuid the uuid of the account
* @return the account, null if it doesn't exist
*/
public AccountModel getAccount(UUID uuid) {
AccountModel[] model = new AccountModel[] { null };
executeQuery(SELECT_ACCOUNT, new Column[] {
@ -40,6 +48,7 @@ public class AccountRepository extends MySQLRepository {
/**
* Construct a {@link AccountModel} from the given parameters
*
* @param uuid the uuid
* @param resultSet the result set
* @return the account

View File

@ -18,6 +18,8 @@ import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* This route handles everything associated with {@link AccountModel}
*
* @author Braydon
*/
@AllArgsConstructor
@ -29,6 +31,9 @@ public class AccountRoute {
private final AccountRepository accountRepository;
/**
* This path handles displaying the {@link AccountModel} with the given {@link UUID}
*/
@RestPath(path = "/account/:uuid", version = APIVersion.V1)
public AccountModel get(Request request, Response response, APIKey apiKey) throws APIException {
UUID uuid = MiscUtils.getUuid(request.params(":uuid"));

View File

@ -5,7 +5,7 @@ import spark.Response;
import zone.themcgamer.api.APIException;
import zone.themcgamer.api.APIVersion;
import zone.themcgamer.api.RestPath;
import zone.themcgamer.api.model.impl.StatusModel;
import zone.themcgamer.api.model.impl.PlayerStatusModel;
import zone.themcgamer.data.APIAccessLevel;
import zone.themcgamer.data.jedis.cache.CacheRepository;
import zone.themcgamer.data.jedis.cache.ICacheItem;
@ -19,15 +19,20 @@ import java.util.List;
import java.util.Map;
/**
* This route handles everything associated with {@link PlayerStatusModel}
*
* @author Braydon
*/
public class StatusRoute {
public class PlayerStatusRoute {
private final CacheRepository repository;
public StatusRoute() {
public PlayerStatusRoute() {
repository = RedisRepository.getRepository(CacheRepository.class).orElse(null);
}
/**
* This path handles displaying of all of the {@link PlayerStatusModel}'s
*/
@RestPath(path = "/status", version = APIVersion.V1)
public Map<String, Object> getStatuses(Request request, Response response, APIKey apiKey) throws APIException {
List<ICacheItem<?>> statuses = repository.filter(cacheItem -> cacheItem.getType() == ItemCacheType.PLAYER_STATUS);
@ -44,8 +49,11 @@ public class StatusRoute {
}};
}
/**
* This path handles displaying the {@link PlayerStatusModel} with the given name
*/
@RestPath(path = "/status/:name", version = APIVersion.V1)
public StatusModel getStatus(Request request, Response response, APIKey apiKey) throws APIException {
public PlayerStatusModel getStatus(Request request, Response response, APIKey apiKey) throws APIException {
String name = request.params(":name");
if (name == null || (name.trim().isEmpty() || name.length() > 16))
throw new APIException("Invalid username");
@ -54,7 +62,7 @@ public class StatusRoute {
.stream().findFirst().orElse(null);
if (statusCache == null)
throw new APIException("Player not found");
StatusModel model = new StatusModel(statusCache.getUuid(), statusCache.getPlayerName(), statusCache.getServer(), statusCache.getTimeJoined());
PlayerStatusModel model = new PlayerStatusModel(statusCache.getUuid(), statusCache.getPlayerName(), statusCache.getServer(), statusCache.getTimeJoined());
if (apiKey.getAccessLevel() == APIAccessLevel.STANDARD)
model.setServer("Unauthorized");
return model;

View File

@ -20,6 +20,8 @@ import java.util.List;
import java.util.Optional;
/**
* This route handles everything associated with {@link ServerGroupModel} and {@link MinecraftServerModel}
*
* @author Braydon
*/
public class ServersRoute {
@ -31,10 +33,9 @@ public class ServersRoute {
minecraftServerRepository = RedisRepository.getRepository(MinecraftServerRepository.class).orElse(null);
}
/*
Server Groups
/**
* This path handles displaying of all of the {@link ServerGroupModel}'s
*/
@RestPath(path = "/serverGroups", version = APIVersion.V1, accessLevel = APIAccessLevel.DEV)
public List<ServerGroupModel> getGroups(Request request, Response response, APIKey apiKey) throws APIException {
List<ServerGroupModel> models = new ArrayList<>();
@ -43,6 +44,9 @@ public class ServersRoute {
return models;
}
/**
* This path handles displaying the {@link ServerGroupModel} with the given name
*/
@RestPath(path = "/serverGroup/:name", version = APIVersion.V1, accessLevel = APIAccessLevel.DEV)
public ServerGroupModel getServerGroup(Request request, Response response, APIKey apiKey) throws APIException {
String name = request.params(":name");
@ -54,10 +58,9 @@ public class ServersRoute {
return ServerGroupModel.fromServerGroup(optionalServerGroup.get());
}
/*
Minecraft Servers
/**
* This path handles displaying of all of the {@link MinecraftServerModel}'s
*/
@RestPath(path = "/minecraftServers", version = APIVersion.V1, accessLevel = APIAccessLevel.DEV)
public List<MinecraftServerModel> getMinecraftServers(Request request, Response response, APIKey apiKey) throws APIException {
List<MinecraftServerModel> models = new ArrayList<>();
@ -66,6 +69,9 @@ public class ServersRoute {
return models;
}
/**
* This path handles displaying the {@link MinecraftServerModel} with the given id
*/
@RestPath(path = "/minecraftServer/:id", version = APIVersion.V1, accessLevel = APIAccessLevel.DEV)
public MinecraftServerModel getMinecraftServer(Request request, Response response, APIKey apiKey) throws APIException {
String id = request.params(":id");