diff --git a/pom.xml b/pom.xml
index 3e28914..d89e8b3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -82,6 +82,11 @@
morphia
1.1.0
+
+ org.mongodb.morphia
+ morphia-logging-slf4j
+ 1.1.0
+
org.projectlombok
lombok
diff --git a/src/main/java/net/frozenorb/apiv3/APIv3.java b/src/main/java/net/frozenorb/apiv3/APIv3.java
index 2047ad2..20f40e1 100644
--- a/src/main/java/net/frozenorb/apiv3/APIv3.java
+++ b/src/main/java/net/frozenorb/apiv3/APIv3.java
@@ -10,14 +10,14 @@ import lombok.Getter;
import net.frozenorb.apiv3.filters.ActorAttributeFilter;
import net.frozenorb.apiv3.filters.AuthorizationFilter;
import net.frozenorb.apiv3.filters.ContentTypeFilter;
+import net.frozenorb.apiv3.models.Server;
import net.frozenorb.apiv3.routes.GETDump;
+import net.frozenorb.apiv3.routes.GETWhoAmI;
+import net.frozenorb.apiv3.routes.NotFound;
import net.frozenorb.apiv3.routes.announcements.GETAnnouncements;
import net.frozenorb.apiv3.routes.auditLog.GETAuditLog;
import net.frozenorb.apiv3.routes.chatFilterList.GETChatFilterList;
-import net.frozenorb.apiv3.routes.grants.DELETEGrant;
-import net.frozenorb.apiv3.routes.grants.GETGrants;
-import net.frozenorb.apiv3.routes.grants.GETUserGrants;
-import net.frozenorb.apiv3.routes.grants.POSTUserGrant;
+import net.frozenorb.apiv3.routes.grants.*;
import net.frozenorb.apiv3.routes.ipLog.GETUserIPLog;
import net.frozenorb.apiv3.routes.notificationTemplate.DELETENotificationTemplate;
import net.frozenorb.apiv3.routes.notificationTemplate.GETNotificationTemplate;
@@ -43,6 +43,9 @@ import net.frozenorb.apiv3.unsorted.LoggingExceptionHandler;
import org.bson.types.ObjectId;
import org.mongodb.morphia.Datastore;
import org.mongodb.morphia.Morphia;
+import org.mongodb.morphia.logging.MorphiaLoggerFactory;
+import org.mongodb.morphia.logging.slf4j.SLF4JLoggerImplFactory;
+import org.slf4j.impl.StaticLoggerBinder;
import java.io.FileInputStream;
import java.io.InputStream;
@@ -60,6 +63,8 @@ public final class APIv3 {
.create();
APIv3() {
+ //System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "trace");
+
setupConfig();
setupDatabase();
setupHttp();
@@ -84,6 +89,9 @@ public final class APIv3 {
config.getProperty("mongo.password").toCharArray())
));
+ MorphiaLoggerFactory.reset();
+ MorphiaLoggerFactory.registerLogger(SLF4JLoggerImplFactory.class);
+
Morphia morphia = new Morphia();
morphia.mapPackage("net.frozenorb.apiv3.accessor");
@@ -104,7 +112,9 @@ public final class APIv3 {
get("/auditLog", new GETAuditLog(), gson::toJson);
get("/chatFilterList", new GETChatFilterList(), gson::toJson);
get("/dump/:type", new GETDump(), gson::toJson);
+ get("/whoami", new GETWhoAmI(), gson::toJson);
+ get("/grant/:id", new GETGrant(), gson::toJson);
get("/grants", new GETGrants(), gson::toJson);
delete("/grant/:id", new DELETEGrant(), gson::toJson);
@@ -151,6 +161,12 @@ public final class APIv3 {
post("/user/confirmRegister/:emailToken", new POSTUserConfirmRegister(), gson::toJson);
put("/user/:id/meta/:serverGroup", new PUTUserMeta(), gson::toJson);
delete("/user/:id/meta", new DELETEUserMeta(), gson::toJson);
+
+ // There's no way to do a JSON 404 page w/o doing this :(
+ get("/*", new NotFound(), gson::toJson);
+ post("/*", new NotFound(), gson::toJson);
+ put("/*", new NotFound(), gson::toJson);
+ delete("/*", new NotFound(), gson::toJson);
}
}
\ No newline at end of file
diff --git a/src/main/java/net/frozenorb/apiv3/filters/ActorAttributeFilter.java b/src/main/java/net/frozenorb/apiv3/filters/ActorAttributeFilter.java
index d0cfb4a..ed2bb19 100644
--- a/src/main/java/net/frozenorb/apiv3/filters/ActorAttributeFilter.java
+++ b/src/main/java/net/frozenorb/apiv3/filters/ActorAttributeFilter.java
@@ -5,6 +5,7 @@ import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.actors.*;
import net.frozenorb.apiv3.models.Server;
import net.frozenorb.apiv3.models.User;
+import net.frozenorb.apiv3.utils.ErrorUtils;
import spark.Filter;
import spark.Request;
import spark.Response;
@@ -39,7 +40,7 @@ public final class ActorAttributeFilter implements Filter {
}
}
- halt(401);
+ halt(401, ErrorUtils.error("Failed to authorize.").toJson());
return null;
}
@@ -58,6 +59,11 @@ public final class ActorAttributeFilter implements Filter {
}
} else if (type.equals("Server") && split.length == 3) {
Server server = Server.byId(split[1]);
+
+ if (server == null) {
+ halt(401, ErrorUtils.notFound("Server", split[1]).toJson());
+ }
+
String givenKey = split[2];
String properKey = server.getApiKey();
@@ -67,7 +73,7 @@ public final class ActorAttributeFilter implements Filter {
}
}
- halt(401);
+ halt(401, ErrorUtils.error("Failed to authorize.").toJson());
return null;
}
diff --git a/src/main/java/net/frozenorb/apiv3/filters/AuthorizationFilter.java b/src/main/java/net/frozenorb/apiv3/filters/AuthorizationFilter.java
index c02edc4..d961722 100644
--- a/src/main/java/net/frozenorb/apiv3/filters/AuthorizationFilter.java
+++ b/src/main/java/net/frozenorb/apiv3/filters/AuthorizationFilter.java
@@ -10,7 +10,7 @@ import spark.Spark;
public final class AuthorizationFilter implements Filter {
public void handle(Request req, Response res) {
- Actor actor = req.attribute("actors");
+ Actor actor = req.attribute("actor");
if (!actor.isAuthorized()) {
Spark.halt(ErrorUtils.error("Unauthorized access: Please authenticate as either a server, the website, or an authorized user.").toJson());
diff --git a/src/main/java/net/frozenorb/apiv3/models/NotificationTemplate.java b/src/main/java/net/frozenorb/apiv3/models/NotificationTemplate.java
index c469948..1cffe9f 100644
--- a/src/main/java/net/frozenorb/apiv3/models/NotificationTemplate.java
+++ b/src/main/java/net/frozenorb/apiv3/models/NotificationTemplate.java
@@ -16,7 +16,7 @@ public final class NotificationTemplate {
@Getter private String body;
public static NotificationTemplate byId(String id) {
- return APIv3.getDatastore().createQuery(NotificationTemplate.class).field("id").equalIgnoreCase(id).get();
+ return APIv3.getDatastore().createQuery(NotificationTemplate.class).field("id").equal(id).get();
}
public static List values() {
@@ -55,7 +55,7 @@ public final class NotificationTemplate {
String key = replacement.getKey();
String value = String.valueOf(replacement.getValue());
- working = working.replace(key, value);
+ working = working.replace("%" + key + "%", value);
}
return working;
diff --git a/src/main/java/net/frozenorb/apiv3/models/Rank.java b/src/main/java/net/frozenorb/apiv3/models/Rank.java
index 6a91639..cefd54e 100644
--- a/src/main/java/net/frozenorb/apiv3/models/Rank.java
+++ b/src/main/java/net/frozenorb/apiv3/models/Rank.java
@@ -18,7 +18,7 @@ public final class Rank {
@Getter private boolean staffRank;
public static Rank byId(String id) {
- return APIv3.getDatastore().createQuery(Rank.class).field("id").equalIgnoreCase(id).get();
+ return APIv3.getDatastore().createQuery(Rank.class).field("id").equal(id).get();
}
public static List values() {
diff --git a/src/main/java/net/frozenorb/apiv3/models/Server.java b/src/main/java/net/frozenorb/apiv3/models/Server.java
index de16b4f..c7ca776 100644
--- a/src/main/java/net/frozenorb/apiv3/models/Server.java
+++ b/src/main/java/net/frozenorb/apiv3/models/Server.java
@@ -3,6 +3,7 @@ package net.frozenorb.apiv3.models;
import lombok.Getter;
import lombok.Setter;
import net.frozenorb.apiv3.APIv3;
+import net.frozenorb.apiv3.serialization.ExcludeFromReplies;
import org.mongodb.morphia.annotations.Entity;
import org.mongodb.morphia.annotations.Id;
@@ -14,15 +15,15 @@ public final class Server {
@Getter @Id private String id;
@Getter private String bungeeId;
@Getter private String displayName;
- @Getter private String apiKey;
+ @Getter @ExcludeFromReplies String apiKey;
@Getter private String group;
@Getter private String ip;
@Getter @Setter private Date lastUpdate;
@Getter @Setter private double lastTps;
- @Getter @Setter private Set players;
+ @Getter @Setter @ExcludeFromReplies private Set players;
public static Server byId(String id) {
- return APIv3.getDatastore().createQuery(Server.class).field("id").equalIgnoreCase(id).get();
+ return APIv3.getDatastore().createQuery(Server.class).field("_id").equal(id).get();
}
public static List values() {
diff --git a/src/main/java/net/frozenorb/apiv3/models/ServerGroup.java b/src/main/java/net/frozenorb/apiv3/models/ServerGroup.java
index 513a1be..a322627 100644
--- a/src/main/java/net/frozenorb/apiv3/models/ServerGroup.java
+++ b/src/main/java/net/frozenorb/apiv3/models/ServerGroup.java
@@ -14,12 +14,14 @@ public final class ServerGroup {
@Getter @Id private String id;
@Getter private String displayName;
- @Getter private Set announcements;
- @Getter private Set chatFilterList;
- @Getter private Map> permissions;
+ // We define these HashSets up here because, in the event they're
+ // empty, Morphia will load them as null, not empty sets.
+ @Getter private Set announcements = new HashSet<>();
+ @Getter private Set chatFilterList = new HashSet<>();
+ @Getter private Map> permissions = new HashMap<>();
public static ServerGroup byId(String id) {
- return APIv3.getDatastore().createQuery(ServerGroup.class).field("id").equalIgnoreCase(id).get();
+ return APIv3.getDatastore().createQuery(ServerGroup.class).field("id").equal(id).get();
}
public static List values() {
@@ -31,9 +33,6 @@ public final class ServerGroup {
public ServerGroup(String id, String displayName) {
this.id = id;
this.displayName = displayName;
- this.announcements = new HashSet<>();
- this.chatFilterList = new HashSet<>();
- this.permissions = new HashMap<>();
}
public void setAnnouncements(Set announcements) {
diff --git a/src/main/java/net/frozenorb/apiv3/models/User.java b/src/main/java/net/frozenorb/apiv3/models/User.java
index 664516b..5d628ab 100644
--- a/src/main/java/net/frozenorb/apiv3/models/User.java
+++ b/src/main/java/net/frozenorb/apiv3/models/User.java
@@ -34,7 +34,7 @@ public final class User {
try {
return byId(UUID.fromString(id));
} catch (Exception ex) {
- throw new IllegalArgumentException("Invalid UUID string " + id, ex);
+ return null;
}
}
@@ -107,7 +107,7 @@ public final class User {
}
public UserMetaEntry getMeta(ServerGroup group) {
- return APIv3.getDatastore().createQuery(UserMetaEntry.class).field("user").equal(id).field("serverGroup").equalIgnoreCase(group.getId()).get();
+ return APIv3.getDatastore().createQuery(UserMetaEntry.class).field("user").equal(id).field("serverGroup").equal(group.getId()).get();
}
public void saveMeta(ServerGroup group, Document data) {
diff --git a/src/main/java/net/frozenorb/apiv3/routes/GETWhoAmI.java b/src/main/java/net/frozenorb/apiv3/routes/GETWhoAmI.java
new file mode 100644
index 0000000..4f63eee
--- /dev/null
+++ b/src/main/java/net/frozenorb/apiv3/routes/GETWhoAmI.java
@@ -0,0 +1,22 @@
+package net.frozenorb.apiv3.routes;
+
+import com.google.common.collect.ImmutableMap;
+import net.frozenorb.apiv3.actors.Actor;
+import org.bson.Document;
+import spark.Request;
+import spark.Response;
+import spark.Route;
+
+public final class GETWhoAmI implements Route {
+
+ public Object handle(Request req, Response res) {
+ Actor actor = req.attribute("actor");
+
+ return ImmutableMap.of(
+ "name", actor.getName(),
+ "type", actor.getType(),
+ "authorized", actor.isAuthorized()
+ );
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/frozenorb/apiv3/routes/NotFound.java b/src/main/java/net/frozenorb/apiv3/routes/NotFound.java
new file mode 100644
index 0000000..39572f6
--- /dev/null
+++ b/src/main/java/net/frozenorb/apiv3/routes/NotFound.java
@@ -0,0 +1,19 @@
+package net.frozenorb.apiv3.routes;
+
+import net.frozenorb.apiv3.APIv3;
+import net.frozenorb.apiv3.models.Grant;
+import net.frozenorb.apiv3.models.Punishment;
+import net.frozenorb.apiv3.utils.ErrorUtils;
+import spark.Request;
+import spark.Response;
+import spark.Route;
+import spark.Spark;
+
+public final class NotFound implements Route {
+
+ public Object handle(Request req, Response res) {
+ Spark.halt(404, ErrorUtils.notFound("Route", req.url()).toJson());
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/frozenorb/apiv3/routes/announcements/GETAnnouncements.java b/src/main/java/net/frozenorb/apiv3/routes/announcements/GETAnnouncements.java
index 492d51e..5f3cdea 100644
--- a/src/main/java/net/frozenorb/apiv3/routes/announcements/GETAnnouncements.java
+++ b/src/main/java/net/frozenorb/apiv3/routes/announcements/GETAnnouncements.java
@@ -1,7 +1,9 @@
package net.frozenorb.apiv3.routes.announcements;
+import net.frozenorb.apiv3.actors.Actor;
import net.frozenorb.apiv3.models.Server;
import net.frozenorb.apiv3.models.ServerGroup;
+import net.frozenorb.apiv3.utils.ErrorUtils;
import spark.Request;
import spark.Response;
import spark.Route;
@@ -9,7 +11,13 @@ import spark.Route;
public final class GETAnnouncements implements Route {
public Object handle(Request req, Response res) {
- Server sender = req.attribute("server");
+ Actor actor = req.attribute("actor");
+
+ if (actor.getType() != Actor.Type.SERVER) {
+ return ErrorUtils.serverOnly();
+ }
+
+ Server sender = Server.byId(actor.getName());
ServerGroup senderGroup = ServerGroup.byId(sender.getGroup());
return senderGroup.getAnnouncements();
diff --git a/src/main/java/net/frozenorb/apiv3/routes/auditLog/GETAuditLog.java b/src/main/java/net/frozenorb/apiv3/routes/auditLog/GETAuditLog.java
index 4c557e8..cfa987a 100644
--- a/src/main/java/net/frozenorb/apiv3/routes/auditLog/GETAuditLog.java
+++ b/src/main/java/net/frozenorb/apiv3/routes/auditLog/GETAuditLog.java
@@ -12,7 +12,7 @@ public final class GETAuditLog implements Route {
int limit = req.queryParams("limit") == null ? 100 : Integer.parseInt(req.queryParams("limit"));
int offset = req.queryParams("offset") == null ? 0 : Integer.parseInt(req.queryParams("offset"));
- return APIv3.getDatastore().createQuery(AuditLogEntry.class).order("addedAt").limit(limit).offset(offset).asList();
+ return APIv3.getDatastore().createQuery(AuditLogEntry.class).order("performedAt").limit(limit).offset(offset).asList();
}
}
\ No newline at end of file
diff --git a/src/main/java/net/frozenorb/apiv3/routes/chatFilterList/GETChatFilterList.java b/src/main/java/net/frozenorb/apiv3/routes/chatFilterList/GETChatFilterList.java
index 0c3b60b..6f670e8 100644
--- a/src/main/java/net/frozenorb/apiv3/routes/chatFilterList/GETChatFilterList.java
+++ b/src/main/java/net/frozenorb/apiv3/routes/chatFilterList/GETChatFilterList.java
@@ -1,7 +1,9 @@
package net.frozenorb.apiv3.routes.chatFilterList;
+import net.frozenorb.apiv3.actors.Actor;
import net.frozenorb.apiv3.models.Server;
import net.frozenorb.apiv3.models.ServerGroup;
+import net.frozenorb.apiv3.utils.ErrorUtils;
import spark.Request;
import spark.Response;
import spark.Route;
@@ -9,7 +11,13 @@ import spark.Route;
public final class GETChatFilterList implements Route {
public Object handle(Request req, Response res) {
- Server sender = req.attribute("server");
+ Actor actor = req.attribute("actor");
+
+ if (actor.getType() != Actor.Type.SERVER) {
+ return ErrorUtils.serverOnly();
+ }
+
+ Server sender = Server.byId(actor.getName());
ServerGroup senderGroup = ServerGroup.byId(sender.getGroup());
return senderGroup.getChatFilterList();
diff --git a/src/main/java/net/frozenorb/apiv3/routes/grants/DELETEGrant.java b/src/main/java/net/frozenorb/apiv3/routes/grants/DELETEGrant.java
index 2cbaa92..8d67796 100644
--- a/src/main/java/net/frozenorb/apiv3/routes/grants/DELETEGrant.java
+++ b/src/main/java/net/frozenorb/apiv3/routes/grants/DELETEGrant.java
@@ -33,7 +33,7 @@ public final class DELETEGrant implements Route {
String reason = req.queryParams("removalReason");
grant.delete(removedBy, reason);
- AuditLog.log(removedBy, req.attribute("actors"), "grant.remove", new Document("grantId", grant.getId()));
+ AuditLog.log(removedBy, req.attribute("actor"), "grant.remove", new Document("grantId", grant.getId()));
return grant;
}
diff --git a/src/main/java/net/frozenorb/apiv3/routes/grants/GETGrant.java b/src/main/java/net/frozenorb/apiv3/routes/grants/GETGrant.java
new file mode 100644
index 0000000..1f612b2
--- /dev/null
+++ b/src/main/java/net/frozenorb/apiv3/routes/grants/GETGrant.java
@@ -0,0 +1,15 @@
+package net.frozenorb.apiv3.routes.grants;
+
+import net.frozenorb.apiv3.models.Grant;
+import net.frozenorb.apiv3.models.Punishment;
+import spark.Request;
+import spark.Response;
+import spark.Route;
+
+public final class GETGrant implements Route {
+
+ public Object handle(Request req, Response res) {
+ return Grant.byId(req.params("id"));
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/frozenorb/apiv3/routes/grants/GETGrants.java b/src/main/java/net/frozenorb/apiv3/routes/grants/GETGrants.java
index e3f9ea6..33a03c0 100644
--- a/src/main/java/net/frozenorb/apiv3/routes/grants/GETGrants.java
+++ b/src/main/java/net/frozenorb/apiv3/routes/grants/GETGrants.java
@@ -12,7 +12,7 @@ public final class GETGrants implements Route {
int limit = req.queryParams("limit") == null ? 100 : Integer.parseInt(req.queryParams("limit"));
int offset = req.queryParams("offset") == null ? 0 : Integer.parseInt(req.queryParams("offset"));
- return APIv3.getDatastore().createQuery(Grant.class).order("performedAt").limit(limit).offset(offset).asList();
+ return APIv3.getDatastore().createQuery(Grant.class).order("addedAt").limit(limit).offset(offset).asList();
}
}
\ No newline at end of file
diff --git a/src/main/java/net/frozenorb/apiv3/routes/punishments/DELETEPunishment.java b/src/main/java/net/frozenorb/apiv3/routes/punishments/DELETEPunishment.java
index 6ae326f..072b3f5 100644
--- a/src/main/java/net/frozenorb/apiv3/routes/punishments/DELETEPunishment.java
+++ b/src/main/java/net/frozenorb/apiv3/routes/punishments/DELETEPunishment.java
@@ -33,7 +33,7 @@ public final class DELETEPunishment implements Route {
String reason = req.queryParams("removalReason");
punishment.delete(removedBy, reason);
- AuditLog.log(removedBy, req.attribute("actors"), "punishment.remove", new Document("punishmentId", punishment.getId()));
+ AuditLog.log(removedBy, req.attribute("actor"), "punishment.remove", new Document("punishmentId", punishment.getId()));
return punishment;
}
diff --git a/src/main/java/net/frozenorb/apiv3/routes/servers/POSTServerHeartbeat.java b/src/main/java/net/frozenorb/apiv3/routes/servers/POSTServerHeartbeat.java
index 1eb704b..3a6e8b7 100644
--- a/src/main/java/net/frozenorb/apiv3/routes/servers/POSTServerHeartbeat.java
+++ b/src/main/java/net/frozenorb/apiv3/routes/servers/POSTServerHeartbeat.java
@@ -17,10 +17,10 @@ public final class POSTServerHeartbeat implements Route {
@SuppressWarnings("unchecked")
public Object handle(Request req, Response res) {
- Actor actor = req.attribute("actors");
+ Actor actor = req.attribute("actor");
if (actor.getType() != Actor.Type.SERVER) {
- return ErrorUtils.error("Heartbeats can only be performed when requested by a server.");
+ return ErrorUtils.serverOnly();
}
Server actorServer = Server.byId(actor.getName());
diff --git a/src/main/java/net/frozenorb/apiv3/routes/users/POSTUserConfirmRegister.java b/src/main/java/net/frozenorb/apiv3/routes/users/POSTUserConfirmRegister.java
index 76e002f..2486890 100644
--- a/src/main/java/net/frozenorb/apiv3/routes/users/POSTUserConfirmRegister.java
+++ b/src/main/java/net/frozenorb/apiv3/routes/users/POSTUserConfirmRegister.java
@@ -29,7 +29,9 @@ public final class POSTUserConfirmRegister implements Route {
return ErrorUtils.notFound("Email token", req.params("emailToken"));
}
- if (user.getEmail() != null) {
+ // We can't check email != null as that's set while we're pending
+ // confirmation, we have to check the token.
+ if (user.getEmailToken() == null) {
return ErrorUtils.error("User provided already has email set.");
}
diff --git a/src/main/java/net/frozenorb/apiv3/routes/users/POSTUserLoginInfo.java b/src/main/java/net/frozenorb/apiv3/routes/users/POSTUserLoginInfo.java
index c8486ca..9597fc3 100644
--- a/src/main/java/net/frozenorb/apiv3/routes/users/POSTUserLoginInfo.java
+++ b/src/main/java/net/frozenorb/apiv3/routes/users/POSTUserLoginInfo.java
@@ -17,10 +17,10 @@ public final class POSTUserLoginInfo implements Route {
User user = User.byId(req.params("id"));
String username = req.queryParams("username");
String userIp = req.queryParams("userIp");
- Actor actor = req.attribute("actors");
+ Actor actor = req.attribute("actor");
if (actor.getType() != Actor.Type.SERVER) {
- return ErrorUtils.error("Login info requests can only be performed when requested by a server.");
+ return ErrorUtils.serverOnly();
}
if (user == null) {
diff --git a/src/main/java/net/frozenorb/apiv3/unsorted/LoggingExceptionHandler.java b/src/main/java/net/frozenorb/apiv3/unsorted/LoggingExceptionHandler.java
index d016e3b..e9a8a03 100644
--- a/src/main/java/net/frozenorb/apiv3/unsorted/LoggingExceptionHandler.java
+++ b/src/main/java/net/frozenorb/apiv3/unsorted/LoggingExceptionHandler.java
@@ -1,17 +1,19 @@
package net.frozenorb.apiv3.unsorted;
+import lombok.extern.slf4j.Slf4j;
import net.frozenorb.apiv3.utils.ErrorUtils;
import org.bson.types.ObjectId;
import spark.ExceptionHandler;
import spark.Request;
import spark.Response;
+@Slf4j
public final class LoggingExceptionHandler implements ExceptionHandler {
public void handle(Exception ex, Request req, Response res) {
String code = new ObjectId().toHexString();
- System.out.println(code + ":");
+ log.error(code + ":");
ex.printStackTrace();
res.body(ErrorUtils.error("An unknown error has occurred. Please contact a developer with the code \"" + code + "\".").toJson());
diff --git a/src/main/java/net/frozenorb/apiv3/utils/ErrorUtils.java b/src/main/java/net/frozenorb/apiv3/utils/ErrorUtils.java
index af980e7..6963440 100644
--- a/src/main/java/net/frozenorb/apiv3/utils/ErrorUtils.java
+++ b/src/main/java/net/frozenorb/apiv3/utils/ErrorUtils.java
@@ -6,6 +6,10 @@ import org.bson.Document;
@UtilityClass
public class ErrorUtils {
+ public static Document serverOnly() {
+ return error("This action can only be performed when requested by a server.");
+ }
+
public static Document notFound(String itemType, String id) {
return error("Not found: " + itemType + " with id " + id + " cannot be found.");
}
diff --git a/src/main/java/net/frozenorb/apiv3/utils/PermissionUtils.java b/src/main/java/net/frozenorb/apiv3/utils/PermissionUtils.java
index e82c280..92ab201 100644
--- a/src/main/java/net/frozenorb/apiv3/utils/PermissionUtils.java
+++ b/src/main/java/net/frozenorb/apiv3/utils/PermissionUtils.java
@@ -19,11 +19,18 @@ public class PermissionUtils {
return result;
}
- public static Map mergeUpTo(Map> collection, Rank upTo) {
+ public static Map mergeUpTo(Map> unconverted, Rank upTo) {
Map result = new HashMap<>();
for (Rank rank : Rank.values()) {
- Map rankPermissions = convertToMap(collection.get(rank.getId()));
+ List unconvertedPermissions = unconverted.get(rank.getId());
+
+ // If there's no permissions defined for this rank just skip it.
+ if (unconvertedPermissions == null) {
+ continue;
+ }
+
+ Map rankPermissions = convertToMap(unconvertedPermissions);
mergePermissions(result, rankPermissions);
if (upTo.getId().equals(rank.getId())) {