diff --git a/api/src/main/java/me/lucko/luckperms/api/event/cause/CreationCause.java b/api/src/main/java/me/lucko/luckperms/api/event/cause/CreationCause.java index 3b12f9ca0..305477e03 100644 --- a/api/src/main/java/me/lucko/luckperms/api/event/cause/CreationCause.java +++ b/api/src/main/java/me/lucko/luckperms/api/event/cause/CreationCause.java @@ -35,6 +35,11 @@ public enum CreationCause { */ COMMAND, + /** + * The creation was caused by the web editor + */ + WEB_EDITOR, + /** * The creation was caused by an API call */ diff --git a/api/src/main/java/me/lucko/luckperms/api/node/Node.java b/api/src/main/java/me/lucko/luckperms/api/node/Node.java index a0d5de855..c2f62e335 100644 --- a/api/src/main/java/me/lucko/luckperms/api/node/Node.java +++ b/api/src/main/java/me/lucko/luckperms/api/node/Node.java @@ -100,6 +100,13 @@ public interface Node { return LuckPermsProvider.get().getNodeBuilderRegistry().forKey(key); } + /** + * Gets the type of the node. + * + * @return the node type + */ + @NonNull NodeType getType(); + /** * Gets the key ({@link String}) of the node. * diff --git a/api/src/main/java/me/lucko/luckperms/api/node/NodeBuilder.java b/api/src/main/java/me/lucko/luckperms/api/node/NodeBuilder.java index 791d93b09..302c5bd41 100644 --- a/api/src/main/java/me/lucko/luckperms/api/node/NodeBuilder.java +++ b/api/src/main/java/me/lucko/luckperms/api/node/NodeBuilder.java @@ -31,6 +31,7 @@ import me.lucko.luckperms.api.node.metadata.NodeMetadataKey; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import java.time.temporal.TemporalAccessor; import java.util.Objects; import java.util.concurrent.TimeUnit; @@ -66,11 +67,20 @@ public interface NodeBuilder, B extends NodeBuilderThe parameter passed to this method must be the unix timestamp * (in seconds) when the node should expire.

* - * @param expiryUnixTimestamp the expiry timestamp (unix seconds) + * @param expiryEpochSeconds the expiry timestamp (unix seconds) * @return the builder * @see Node#getExpiry() */ - @NonNull B expiry(long expiryUnixTimestamp); + @NonNull B expiry(long expiryEpochSeconds); + + /** + * Sets the time when the node should expire. + * + * @param expiry the expiry time + * @return the builder + * @see Node#getExpiry() + */ + @NonNull B expiry(@Nullable TemporalAccessor expiry); /** * Sets the time when the node should expire. diff --git a/api/src/main/java/me/lucko/luckperms/api/node/ScopedNode.java b/api/src/main/java/me/lucko/luckperms/api/node/ScopedNode.java index 8a78241f8..50ed43dab 100644 --- a/api/src/main/java/me/lucko/luckperms/api/node/ScopedNode.java +++ b/api/src/main/java/me/lucko/luckperms/api/node/ScopedNode.java @@ -36,6 +36,9 @@ import org.checkerframework.checker.nullness.qual.NonNull; */ public interface ScopedNode, B extends NodeBuilder> extends Node { + @Override + @NonNull NodeType getType(); + @Override @NonNull B toBuilder(); diff --git a/api/src/main/java/me/lucko/luckperms/api/node/types/ChatMetaNode.java b/api/src/main/java/me/lucko/luckperms/api/node/types/ChatMetaNode.java index 07b372ebc..db541cb48 100644 --- a/api/src/main/java/me/lucko/luckperms/api/node/types/ChatMetaNode.java +++ b/api/src/main/java/me/lucko/luckperms/api/node/types/ChatMetaNode.java @@ -58,7 +58,7 @@ public interface ChatMetaNode, B extends ChatMetaNo * * @return the type */ - @NonNull ChatMetaType getType(); + @NonNull ChatMetaType getMetaType(); /** * A {@link ChatMetaNode} builder. diff --git a/api/src/main/java/me/lucko/luckperms/api/node/types/DisplayNameNode.java b/api/src/main/java/me/lucko/luckperms/api/node/types/DisplayNameNode.java index 079d41e22..0f0e2adff 100644 --- a/api/src/main/java/me/lucko/luckperms/api/node/types/DisplayNameNode.java +++ b/api/src/main/java/me/lucko/luckperms/api/node/types/DisplayNameNode.java @@ -28,6 +28,7 @@ package me.lucko.luckperms.api.node.types; import me.lucko.luckperms.api.LuckPermsProvider; import me.lucko.luckperms.api.node.Node; import me.lucko.luckperms.api.node.NodeBuilder; +import me.lucko.luckperms.api.node.NodeType; import me.lucko.luckperms.api.node.ScopedNode; import org.checkerframework.checker.nullness.qual.NonNull; @@ -37,6 +38,11 @@ import org.checkerframework.checker.nullness.qual.NonNull; */ public interface DisplayNameNode extends ScopedNode { + @Override + default @NonNull NodeType getType() { + return NodeType.DISPLAY_NAME; + } + /** * Gets the display name. * diff --git a/api/src/main/java/me/lucko/luckperms/api/node/types/InheritanceNode.java b/api/src/main/java/me/lucko/luckperms/api/node/types/InheritanceNode.java index 98b532cd5..ed9ca4763 100644 --- a/api/src/main/java/me/lucko/luckperms/api/node/types/InheritanceNode.java +++ b/api/src/main/java/me/lucko/luckperms/api/node/types/InheritanceNode.java @@ -29,6 +29,7 @@ import me.lucko.luckperms.api.LuckPermsProvider; import me.lucko.luckperms.api.model.group.Group; import me.lucko.luckperms.api.node.Node; import me.lucko.luckperms.api.node.NodeBuilder; +import me.lucko.luckperms.api.node.NodeType; import me.lucko.luckperms.api.node.ScopedNode; import org.checkerframework.checker.nullness.qual.NonNull; @@ -39,6 +40,11 @@ import org.checkerframework.checker.nullness.qual.NonNull; */ public interface InheritanceNode extends ScopedNode { + @Override + default @NonNull NodeType getType() { + return NodeType.INHERITANCE; + } + /** * Gets the name of the group to be inherited. * diff --git a/api/src/main/java/me/lucko/luckperms/api/node/types/MetaNode.java b/api/src/main/java/me/lucko/luckperms/api/node/types/MetaNode.java index 77739aaf5..0b95830b1 100644 --- a/api/src/main/java/me/lucko/luckperms/api/node/types/MetaNode.java +++ b/api/src/main/java/me/lucko/luckperms/api/node/types/MetaNode.java @@ -28,6 +28,7 @@ package me.lucko.luckperms.api.node.types; import me.lucko.luckperms.api.LuckPermsProvider; import me.lucko.luckperms.api.node.Node; import me.lucko.luckperms.api.node.NodeBuilder; +import me.lucko.luckperms.api.node.NodeType; import me.lucko.luckperms.api.node.ScopedNode; import org.checkerframework.checker.nullness.qual.NonNull; @@ -37,6 +38,11 @@ import org.checkerframework.checker.nullness.qual.NonNull; */ public interface MetaNode extends ScopedNode { + @Override + default @NonNull NodeType getType() { + return NodeType.META; + } + /** * Gets the meta key. * diff --git a/api/src/main/java/me/lucko/luckperms/api/node/types/PermissionNode.java b/api/src/main/java/me/lucko/luckperms/api/node/types/PermissionNode.java index 7573ddce6..c5530a5c3 100644 --- a/api/src/main/java/me/lucko/luckperms/api/node/types/PermissionNode.java +++ b/api/src/main/java/me/lucko/luckperms/api/node/types/PermissionNode.java @@ -28,6 +28,7 @@ package me.lucko.luckperms.api.node.types; import me.lucko.luckperms.api.LuckPermsProvider; import me.lucko.luckperms.api.node.Node; import me.lucko.luckperms.api.node.NodeBuilder; +import me.lucko.luckperms.api.node.NodeType; import me.lucko.luckperms.api.node.ScopedNode; import org.checkerframework.checker.nullness.qual.NonNull; @@ -39,6 +40,11 @@ import java.util.OptionalInt; */ public interface PermissionNode extends ScopedNode { + @Override + default @NonNull NodeType getType() { + return NodeType.PERMISSION; + } + /** * Gets the permission string this node encapsulates. * diff --git a/api/src/main/java/me/lucko/luckperms/api/node/types/PrefixNode.java b/api/src/main/java/me/lucko/luckperms/api/node/types/PrefixNode.java index 8a1d24c1a..a8e4d6010 100644 --- a/api/src/main/java/me/lucko/luckperms/api/node/types/PrefixNode.java +++ b/api/src/main/java/me/lucko/luckperms/api/node/types/PrefixNode.java @@ -27,6 +27,7 @@ package me.lucko.luckperms.api.node.types; import me.lucko.luckperms.api.LuckPermsProvider; import me.lucko.luckperms.api.node.Node; +import me.lucko.luckperms.api.node.NodeType; import org.checkerframework.checker.nullness.qual.NonNull; @@ -35,6 +36,11 @@ import org.checkerframework.checker.nullness.qual.NonNull; */ public interface PrefixNode extends ChatMetaNode { + @Override + default @NonNull NodeType getType() { + return NodeType.PREFIX; + } + /** * Creates a {@link PrefixNode} builder. * diff --git a/api/src/main/java/me/lucko/luckperms/api/node/types/RegexPermissionNode.java b/api/src/main/java/me/lucko/luckperms/api/node/types/RegexPermissionNode.java index eecdb13be..6345d157d 100644 --- a/api/src/main/java/me/lucko/luckperms/api/node/types/RegexPermissionNode.java +++ b/api/src/main/java/me/lucko/luckperms/api/node/types/RegexPermissionNode.java @@ -28,6 +28,7 @@ package me.lucko.luckperms.api.node.types; import me.lucko.luckperms.api.LuckPermsProvider; import me.lucko.luckperms.api.node.Node; import me.lucko.luckperms.api.node.NodeBuilder; +import me.lucko.luckperms.api.node.NodeType; import me.lucko.luckperms.api.node.ScopedNode; import org.checkerframework.checker.nullness.qual.NonNull; @@ -40,6 +41,11 @@ import java.util.regex.Pattern; */ public interface RegexPermissionNode extends ScopedNode { + @Override + default @NonNull NodeType getType() { + return NodeType.REGEX_PERMISSION; + } + /** * Gets the non-compiled pattern string. * diff --git a/api/src/main/java/me/lucko/luckperms/api/node/types/SuffixNode.java b/api/src/main/java/me/lucko/luckperms/api/node/types/SuffixNode.java index d8d2dce88..5eacc23c7 100644 --- a/api/src/main/java/me/lucko/luckperms/api/node/types/SuffixNode.java +++ b/api/src/main/java/me/lucko/luckperms/api/node/types/SuffixNode.java @@ -27,6 +27,7 @@ package me.lucko.luckperms.api.node.types; import me.lucko.luckperms.api.LuckPermsProvider; import me.lucko.luckperms.api.node.Node; +import me.lucko.luckperms.api.node.NodeType; import org.checkerframework.checker.nullness.qual.NonNull; @@ -35,6 +36,11 @@ import org.checkerframework.checker.nullness.qual.NonNull; */ public interface SuffixNode extends ChatMetaNode { + @Override + default @NonNull NodeType getType() { + return NodeType.SUFFIX; + } + /** * Creates a {@link SuffixNode} builder. * diff --git a/api/src/main/java/me/lucko/luckperms/api/node/types/WeightNode.java b/api/src/main/java/me/lucko/luckperms/api/node/types/WeightNode.java index 884d3a429..ed6a711c7 100644 --- a/api/src/main/java/me/lucko/luckperms/api/node/types/WeightNode.java +++ b/api/src/main/java/me/lucko/luckperms/api/node/types/WeightNode.java @@ -28,6 +28,7 @@ package me.lucko.luckperms.api.node.types; import me.lucko.luckperms.api.LuckPermsProvider; import me.lucko.luckperms.api.node.Node; import me.lucko.luckperms.api.node.NodeBuilder; +import me.lucko.luckperms.api.node.NodeType; import me.lucko.luckperms.api.node.ScopedNode; import org.checkerframework.checker.nullness.qual.NonNull; @@ -37,6 +38,11 @@ import org.checkerframework.checker.nullness.qual.NonNull; */ public interface WeightNode extends ScopedNode { + @Override + default @NonNull NodeType getType() { + return NodeType.WEIGHT; + } + /** * Gets the weight value. * diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/generic/meta/MetaInfo.java b/common/src/main/java/me/lucko/luckperms/common/commands/generic/meta/MetaInfo.java index 4d0195f8a..200a760d9 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/generic/meta/MetaInfo.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/generic/meta/MetaInfo.java @@ -174,7 +174,7 @@ public class MetaInfo extends SharedSubCommand { HoverEvent hoverEvent = HoverEvent.showText(TextUtils.fromLegacy(TextUtils.joinNewline( "¥3> ¥a" + node.getPriority() + " ¥7- ¥r" + node.getMetaValue(), " ", - "¥7Click to remove this " + node.getType().name().toLowerCase() + " from " + holder.getFormattedDisplayName() + "¥7Click to remove this " + node.getMetaType().name().toLowerCase() + " from " + holder.getFormattedDisplayName() ), '¥')); String command = "/" + label + " " + NodeFactory.nodeAsCommand(node, holder.getType() == HolderType.GROUP ? holder.getObjectName() : holder.getFormattedDisplayName(), holder.getType(), false, !holder.getPlugin().getConfiguration().getContextsFile().getDefaultContexts().isEmpty()); diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/generic/other/HolderEditor.java b/common/src/main/java/me/lucko/luckperms/common/commands/generic/other/HolderEditor.java index caa39c391..3ebd0bc45 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/generic/other/HolderEditor.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/generic/other/HolderEditor.java @@ -66,7 +66,7 @@ public class HolderEditor extends SubCommand { Message.EDITOR_START.send(sender); // form the payload data - JsonObject payload = WebEditor.formPayload(Collections.singletonList(holder), sender, label, plugin); + JsonObject payload = WebEditor.formPayload(Collections.singletonList(holder), Collections.emptyList(), sender, label, plugin); // upload the payload data to gist String pasteId = plugin.getBytebin().postJson(payload, true).id(); diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/misc/ApplyEditsCommand.java b/common/src/main/java/me/lucko/luckperms/common/commands/misc/ApplyEditsCommand.java index a76b70f78..b2a462982 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/misc/ApplyEditsCommand.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/misc/ApplyEditsCommand.java @@ -30,6 +30,9 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import me.lucko.luckperms.api.actionlog.Action; +import me.lucko.luckperms.api.event.cause.CreationCause; +import me.lucko.luckperms.api.event.cause.DeletionCause; import me.lucko.luckperms.api.model.DataType; import me.lucko.luckperms.api.node.Node; import me.lucko.luckperms.common.actionlog.LoggedAction; @@ -42,19 +45,24 @@ import me.lucko.luckperms.common.command.utils.StorageAssistant; import me.lucko.luckperms.common.locale.LocaleManager; import me.lucko.luckperms.common.locale.command.CommandSpec; import me.lucko.luckperms.common.locale.message.Message; +import me.lucko.luckperms.common.model.Group; import me.lucko.luckperms.common.model.PermissionHolder; -import me.lucko.luckperms.common.node.model.NodeDataContainer; +import me.lucko.luckperms.common.model.Track; +import me.lucko.luckperms.common.node.factory.NodeFactory; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.sender.Sender; import me.lucko.luckperms.common.util.DurationFormatter; import me.lucko.luckperms.common.util.Predicates; +import me.lucko.luckperms.common.util.Uuids; import me.lucko.luckperms.common.web.WebEditor; +import java.util.ArrayList; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; +import java.util.UUID; public class ApplyEditsCommand extends SingleCommand { public ApplyEditsCommand(LocaleManager locale) { @@ -76,37 +84,75 @@ public class ApplyEditsCommand extends SingleCommand { return CommandResult.FAILURE; } - boolean success = false; - - if (data.has("tabs") && data.get("tabs").isJsonArray()) { - JsonArray rows = data.get("tabs").getAsJsonArray(); - for (JsonElement row : rows) { - if (read(row.getAsJsonObject(), sender, plugin)) { - success = true; + boolean work = false; + if (data.has("changes")) { + JsonArray changes = data.get("changes").getAsJsonArray(); + for (JsonElement change : changes) { + if (readChanges(change.getAsJsonObject(), sender, plugin)) { + work = true; + } + } + } + if (data.has("groupDeletions")) { + JsonArray groupDeletions = data.get("groupDeletions").getAsJsonArray(); + for (JsonElement groupDeletion : groupDeletions) { + if (readGroupDeletion(groupDeletion, sender, plugin)) { + work = true; + } + } + } + if (data.has("trackDeletions")) { + JsonArray trackDeletions = data.get("trackDeletions").getAsJsonArray(); + for (JsonElement trackDeletion : trackDeletions) { + if (readTrackDeletion(trackDeletion, sender, plugin)) { + work = true; } } - } else { - success = read(data, sender, plugin); } - if (!success) { + if (!work) { Message.APPLY_EDITS_TARGET_NO_CHANGES_PRESENT.send(sender); } return CommandResult.SUCCESS; } - private boolean read(JsonObject data, Sender sender, LuckPermsPlugin plugin) { - if (!data.has("who") || data.get("who").getAsString().isEmpty()) { - Message.APPLY_EDITS_NO_TARGET.send(sender); + private boolean readChanges(JsonObject data, Sender sender, LuckPermsPlugin plugin) { + String type = data.get("type").getAsString(); + + if (type.equals("user") || type.equals("group")) { + return readHolderChanges(data, sender, plugin); + } else if (type.equals("track")) { + return readTrackChanges(data, sender, plugin); + } else { + Message.APPLY_EDITS_UNKNOWN_TYPE.send(sender, type); return false; } + } - String who = data.get("who").getAsString(); - PermissionHolder holder = WebEditor.getHolderFromIdentifier(plugin, sender, who); - if (holder == null) { - // the #getHolderFromIdentifier method will send the error message onto the sender - return false; + private boolean readHolderChanges(JsonObject data, Sender sender, LuckPermsPlugin plugin) { + String type = data.get("type").getAsString(); + String id = data.get("id").getAsString(); + + PermissionHolder holder; + if (type.equals("user")) { + // user + UUID uuid = Uuids.parse(id); + if (uuid == null) { + Message.APPLY_EDITS_TARGET_USER_NOT_UUID.send(sender, id); + return false; + } + holder = plugin.getStorage().loadUser(uuid, null).join(); + if (holder == null) { + Message.APPLY_EDITS_TARGET_USER_UNABLE_TO_LOAD.send(sender, uuid.toString()); + return false; + } + } else { + // group + holder = plugin.getStorage().loadGroup(id).join().orElse(null); + if (holder == null) { + holder = plugin.getStorage().createAndLoadGroup(id, CreationCause.WEB_EDITOR).join(); + } } if (ArgumentPermissions.checkModifyPerms(plugin, sender, getPermission().get(), holder)) { @@ -114,10 +160,8 @@ public class ApplyEditsCommand extends SingleCommand { return false; } - Set nodes = WebEditor.deserializePermissions(data.getAsJsonArray("nodes")); - Set before = new HashSet<>(holder.normalData().immutable().values()); - Set after = nodes.stream().map(NodeDataContainer::toNode).collect(Collectors.toSet()); + Set after = new HashSet<>(WebEditor.deserializePermissions(data.getAsJsonArray("nodes"))); Map.Entry, Set> diff = diff(before, after); Set diffAdded = diff.getKey(); @@ -146,7 +190,7 @@ public class ApplyEditsCommand extends SingleCommand { String additionsSummary = "addition" + (additions == 1 ? "" : "s"); String deletionsSummary = "deletion" + (deletions == 1 ? "" : "s"); - Message.APPLY_EDITS_SUCCESS.send(sender, holder.getFormattedDisplayName()); + Message.APPLY_EDITS_SUCCESS.send(sender, type, holder.getFormattedDisplayName()); Message.APPLY_EDITS_SUCCESS_SUMMARY.send(sender, additions, additionsSummary, deletions, deletionsSummary); for (Node n : diffAdded) { Message.APPLY_EDITS_DIFF_ADDED.send(sender, formatNode(plugin.getLocaleManager(), n)); @@ -158,19 +202,125 @@ public class ApplyEditsCommand extends SingleCommand { return true; } + private boolean readTrackChanges(JsonObject data, Sender sender, LuckPermsPlugin plugin) { + String id = data.get("id").getAsString(); + + Track track = plugin.getStorage().loadTrack(id).join().orElse(null); + if (track == null) { + track = plugin.getStorage().createAndLoadTrack(id, CreationCause.WEB_EDITOR).join(); + } + + Set before = new LinkedHashSet<>(track.getGroups()); + Set after = new LinkedHashSet<>(); + data.getAsJsonArray("groups").forEach(e -> after.add(e.getAsString())); + + Map.Entry, Set> diff = diff(before, after); + Set diffAdded = diff.getKey(); + Set diffRemoved = diff.getValue(); + + int additions = diffAdded.size(); + int deletions = diffRemoved.size(); + + if (additions == 0 && deletions == 0) { + return false; + } + + track.setGroups(new ArrayList<>(after)); + + for (String n : diffAdded) { + LoggedAction.build().source(sender).target(track) + .description("webeditor", "add", n) + .build().submit(plugin, sender); + } + for (String n : diffRemoved) { + LoggedAction.build().source(sender).target(track) + .description("webeditor", "remove", n) + .build().submit(plugin, sender); + } + + String additionsSummary = "addition" + (additions == 1 ? "" : "s"); + String deletionsSummary = "deletion" + (deletions == 1 ? "" : "s"); + + Message.APPLY_EDITS_SUCCESS.send(sender, "track", track.getName()); + Message.APPLY_EDITS_SUCCESS_SUMMARY.send(sender, additions, additionsSummary, deletions, deletionsSummary); + for (String n : diffAdded) { + Message.APPLY_EDITS_DIFF_ADDED.send(sender, n); + } + for (String n : diffRemoved) { + Message.APPLY_EDITS_DIFF_REMOVED.send(sender, n); + } + StorageAssistant.save(track, sender, plugin); + return true; + } + + private boolean readGroupDeletion(JsonElement data, Sender sender, LuckPermsPlugin plugin) { + String groupName = data.getAsString(); + + if (groupName.equalsIgnoreCase(NodeFactory.DEFAULT_GROUP_NAME)) { + Message.DELETE_GROUP_ERROR_DEFAULT.send(sender); + return true; + } + + Group group = plugin.getStorage().loadGroup(groupName).join().orElse(null); + if (group == null) { + return false; + } + + try { + plugin.getStorage().deleteGroup(group, DeletionCause.COMMAND).get(); + } catch (Exception e) { + e.printStackTrace(); + Message.DELETE_ERROR.send(sender, group.getFormattedDisplayName()); + return true; + } + + Message.DELETE_SUCCESS.send(sender, group.getFormattedDisplayName()); + + LoggedAction.build().source(sender).targetName(groupName).targetType(Action.Target.Type.GROUP) + .description("webeditor", "delete") + .build().submit(plugin, sender); + + return true; + } + + private boolean readTrackDeletion(JsonElement data, Sender sender, LuckPermsPlugin plugin) { + String trackName = data.getAsString(); + + Track track = plugin.getStorage().loadTrack(trackName).join().orElse(null); + if (track == null) { + return false; + } + + try { + plugin.getStorage().deleteTrack(track, DeletionCause.COMMAND).get(); + } catch (Exception e) { + e.printStackTrace(); + Message.DELETE_ERROR.send(sender, track.getName()); + return true; + } + + Message.DELETE_SUCCESS.send(sender, trackName); + + LoggedAction.build().source(sender).targetName(trackName).targetType(Action.Target.Type.TRACK) + .description("webeditor", "delete") + .build().submit(plugin, sender); + + return true; + } + private static String formatNode(LocaleManager localeManager, Node n) { return n.getKey() + " &7(" + (n.getValue() ? "&a" : "&c") + n.getValue() + "&7)" + MessageUtils.getAppendableNodeContextString(localeManager, n) + (n.hasExpiry() ? " &7(" + DurationFormatter.CONCISE.formatDateDiff(n.getExpiry().getEpochSecond()) + ")" : ""); } - private static Map.Entry, Set> diff(Set before, Set after) { + private static Map.Entry, Set> diff(Set before, Set after) { // entries in before but not after are being removed // entries in after but not before are being added - Set added = new HashSet<>(after); + Set added = new LinkedHashSet<>(after); added.removeAll(before); - Set removed = new HashSet<>(before); + Set removed = new LinkedHashSet<>(before); removed.removeAll(after); return Maps.immutableEntry(added, removed); diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/misc/EditorCommand.java b/common/src/main/java/me/lucko/luckperms/common/commands/misc/EditorCommand.java index 0bb19578e..c0fa60011 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/misc/EditorCommand.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/misc/EditorCommand.java @@ -37,6 +37,7 @@ import me.lucko.luckperms.common.locale.LocaleManager; import me.lucko.luckperms.common.locale.command.CommandSpec; import me.lucko.luckperms.common.locale.message.Message; import me.lucko.luckperms.common.model.PermissionHolder; +import me.lucko.luckperms.common.model.Track; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.sender.Sender; import me.lucko.luckperms.common.util.Predicates; @@ -72,6 +73,7 @@ public class EditorCommand extends SingleCommand { // collect holders List holders = new ArrayList<>(); + List tracks = new ArrayList<>(); if (type.includingGroups) { plugin.getGroupManager().getAll().values().stream() .sorted((o1, o2) -> { @@ -79,6 +81,7 @@ public class EditorCommand extends SingleCommand { return i != 0 ? i : o1.getName().compareToIgnoreCase(o2.getName()); }) .forEach(holders::add); + tracks = new ArrayList<>(plugin.getTrackManager().getAll().values()); } if (type.includingUsers) { plugin.getUserManager().getAll().values().stream() @@ -93,9 +96,10 @@ public class EditorCommand extends SingleCommand { // remove holders which the sender doesn't have perms to view holders.removeIf(holder -> ArgumentPermissions.checkViewPerms(plugin, sender, getPermission().get(), holder)); + tracks.removeIf(track -> ArgumentPermissions.checkViewPerms(plugin, sender, getPermission().get(), track)); // they don't have perms to view any of them - if (holders.isEmpty()) { + if (holders.isEmpty() && tracks.isEmpty()) { Message.COMMAND_NO_PERMISSION.send(sender); return CommandResult.NO_PERMISSION; } @@ -103,7 +107,7 @@ public class EditorCommand extends SingleCommand { Message.EDITOR_START.send(sender); // form the payload data - JsonObject payload = WebEditor.formPayload(holders, sender, label, plugin); + JsonObject payload = WebEditor.formPayload(holders, tracks, sender, label, plugin); // upload the payload data to gist String pasteId = plugin.getBytebin().postJson(payload, true).id(); diff --git a/common/src/main/java/me/lucko/luckperms/common/locale/message/Message.java b/common/src/main/java/me/lucko/luckperms/common/locale/message/Message.java index 79e0b7b55..6503709c8 100644 --- a/common/src/main/java/me/lucko/luckperms/common/locale/message/Message.java +++ b/common/src/main/java/me/lucko/luckperms/common/locale/message/Message.java @@ -152,13 +152,12 @@ public enum Message { APPLY_EDITS_INVALID_CODE("&cInvalid code. &7({})", true), APPLY_EDITS_UNABLE_TO_READ("&cUnable to read data using the given code. &7({})", true), - APPLY_EDITS_NO_TARGET("&cUnable to parse the target of the edit.", true), - APPLY_EDITS_TARGET_GROUP_NOT_EXISTS("&cTarget group &4{}&c does not exist.", true), + APPLY_EDITS_UNKNOWN_TYPE("&cUnable to apply edit to the specified object type. &7({})", true), APPLY_EDITS_TARGET_USER_NOT_UUID("&cTarget user &4{}&c is not a valid uuid.", true), APPLY_EDITS_TARGET_USER_UNABLE_TO_LOAD("&cUnable to load target user &4{}&c.", true), APPLY_EDITS_TARGET_UNKNOWN("&cInvalid target. &7({})", true), APPLY_EDITS_TARGET_NO_CHANGES_PRESENT("&aNo changes were applied from the web editor. The returned data didn't contain any edits.", true), - APPLY_EDITS_SUCCESS("&aWeb editor data was applied to &b{}&a successfully.", true), + APPLY_EDITS_SUCCESS("&aWeb editor data was applied to {} &b{}&a successfully.", true), APPLY_EDITS_SUCCESS_SUMMARY("&7(&a{} &7{} and &c{} &7{})", true), APPLY_EDITS_DIFF_ADDED("&a+ &f{}", false), APPLY_EDITS_DIFF_REMOVED("&c- &f{}", false), diff --git a/common/src/main/java/me/lucko/luckperms/common/model/Track.java b/common/src/main/java/me/lucko/luckperms/common/model/Track.java index 89a609aac..7efb838f2 100644 --- a/common/src/main/java/me/lucko/luckperms/common/model/Track.java +++ b/common/src/main/java/me/lucko/luckperms/common/model/Track.java @@ -87,7 +87,7 @@ public final class Track implements Identifiable { @Override public String getId() { - return this.name.toLowerCase(); + return getName(); } /** diff --git a/common/src/main/java/me/lucko/luckperms/common/node/AbstractNodeBuilder.java b/common/src/main/java/me/lucko/luckperms/common/node/AbstractNodeBuilder.java index e2a2bf510..0e479e84f 100644 --- a/common/src/main/java/me/lucko/luckperms/common/node/AbstractNodeBuilder.java +++ b/common/src/main/java/me/lucko/luckperms/common/node/AbstractNodeBuilder.java @@ -35,6 +35,8 @@ import me.lucko.luckperms.api.node.metadata.NodeMetadataKey; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import java.time.temporal.ChronoField; +import java.time.temporal.TemporalAccessor; import java.util.IdentityHashMap; import java.util.Map; import java.util.Objects; @@ -72,8 +74,18 @@ public abstract class AbstractNodeBuilder, B extends } @Override - public @NonNull B expiry(long expiryUnixTimestamp) { - this.expireAt = expiryUnixTimestamp; + public @NonNull B expiry(long expiryEpochSeconds) { + this.expireAt = expiryEpochSeconds; + return (B) this; + } + + @Override + public @NonNull B expiry(@Nullable TemporalAccessor expiry) { + if (expiry == null) { + return clearExpiry(); + } + + this.expireAt = expiry.getLong(ChronoField.INSTANT_SECONDS); return (B) this; } diff --git a/common/src/main/java/me/lucko/luckperms/common/node/factory/NodeFactory.java b/common/src/main/java/me/lucko/luckperms/common/node/factory/NodeFactory.java index eb6b99235..77a581767 100644 --- a/common/src/main/java/me/lucko/luckperms/common/node/factory/NodeFactory.java +++ b/common/src/main/java/me/lucko/luckperms/common/node/factory/NodeFactory.java @@ -161,7 +161,7 @@ public final class NodeFactory { sb.append("temp"); } - sb.append(cmNode.getType().toString()) + sb.append(cmNode.getMetaType().toString()) .append(" ") .append(cmNode.getPriority()) // weight .append(" "); diff --git a/common/src/main/java/me/lucko/luckperms/common/node/types/Prefix.java b/common/src/main/java/me/lucko/luckperms/common/node/types/Prefix.java index a49200c3a..a10539979 100644 --- a/common/src/main/java/me/lucko/luckperms/common/node/types/Prefix.java +++ b/common/src/main/java/me/lucko/luckperms/common/node/types/Prefix.java @@ -59,7 +59,7 @@ public class Prefix extends AbstractNode impleme } @Override - public @NonNull ChatMetaType getType() { + public @NonNull ChatMetaType getMetaType() { return ChatMetaType.PREFIX; } diff --git a/common/src/main/java/me/lucko/luckperms/common/node/types/Suffix.java b/common/src/main/java/me/lucko/luckperms/common/node/types/Suffix.java index 7d96a5594..6dfeeedf9 100644 --- a/common/src/main/java/me/lucko/luckperms/common/node/types/Suffix.java +++ b/common/src/main/java/me/lucko/luckperms/common/node/types/Suffix.java @@ -59,7 +59,7 @@ public class Suffix extends AbstractNode impleme } @Override - public @NonNull ChatMetaType getType() { + public @NonNull ChatMetaType getMetaType() { return ChatMetaType.SUFFIX; } diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/AbstractConfigurateStorage.java b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/AbstractConfigurateStorage.java index e253020fd..e19600fba 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/AbstractConfigurateStorage.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/AbstractConfigurateStorage.java @@ -665,7 +665,7 @@ public abstract class AbstractConfigurateStorage implements StorageImplementatio attributes.getNode("priority").setValue(chatMeta.getPriority()); writeAttributesTo(attributes, node, false); - switch (chatMeta.getType()) { + switch (chatMeta.getMetaType()) { case PREFIX: appendNode(prefixesSection, chatMeta.getMetaValue(), attributes, "prefix"); break; diff --git a/common/src/main/java/me/lucko/luckperms/common/web/WebEditor.java b/common/src/main/java/me/lucko/luckperms/common/web/WebEditor.java index 6d8b7c502..a7444ac9b 100644 --- a/common/src/main/java/me/lucko/luckperms/common/web/WebEditor.java +++ b/common/src/main/java/me/lucko/luckperms/common/web/WebEditor.java @@ -30,17 +30,14 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import me.lucko.luckperms.api.context.ImmutableContextSet; +import me.lucko.luckperms.api.node.Node; +import me.lucko.luckperms.api.node.NodeBuilder; import me.lucko.luckperms.common.context.ContextSetJsonSerializer; -import me.lucko.luckperms.common.locale.message.Message; -import me.lucko.luckperms.common.model.Group; -import me.lucko.luckperms.common.model.HolderType; import me.lucko.luckperms.common.model.PermissionHolder; -import me.lucko.luckperms.common.model.User; -import me.lucko.luckperms.common.node.model.NodeDataContainer; +import me.lucko.luckperms.common.model.Track; +import me.lucko.luckperms.common.node.factory.NodeFactory; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.sender.Sender; -import me.lucko.luckperms.common.util.Uuids; import me.lucko.luckperms.common.util.gson.GsonProvider; import me.lucko.luckperms.common.util.gson.JArray; import me.lucko.luckperms.common.util.gson.JObject; @@ -54,11 +51,11 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.UUID; -import java.util.stream.Stream; /** * Utility methods for interacting with the LuckPerms web permission editor. @@ -66,42 +63,49 @@ import java.util.stream.Stream; public final class WebEditor { private WebEditor() {} - private static final String USER_ID_PATTERN = "user/"; - private static final String GROUP_ID_PATTERN = "group/"; - private static JObject writeData(PermissionHolder holder) { return new JObject() - .add("who", new JObject() - .add("id", getHolderIdentifier(holder)) - .add("friendly", holder.getPlainDisplayName()) - .consume(obj -> { - if (holder.getType() == HolderType.USER) { - obj.add("uuid", ((User) holder).getUuid().toString()); - } - })) - .add("nodes", serializePermissions(holder.normalData().immutable().values().stream().map(NodeDataContainer::fromNode))); + .add("type", holder.getType().toString()) + .add("id", holder.getObjectName()) + .add("displayName", holder.getPlainDisplayName()) + .add("nodes", serializePermissions(holder.normalData().immutable().values())); } - public static JsonObject formPayload(List holders, Sender sender, String cmdLabel, LuckPermsPlugin plugin) { + private static JObject writeData(Track track) { + return new JObject() + .add("type", "track") + .add("id", track.getName()) + .add("groups", new JArray().consume(a -> track.getGroups().forEach(a::add))); + } + + public static JsonObject formPayload(List holders, List tracks, Sender sender, String cmdLabel, LuckPermsPlugin plugin) { Preconditions.checkArgument(!holders.isEmpty(), "holders is empty"); // form the payload data return new JObject() .add("metadata", new JObject() - .add("cmdAlias", cmdLabel) + .add("commandAlias", cmdLabel) .add("uploader", new JObject() .add("name", sender.getNameWithLocation()) .add("uuid", sender.getUuid().toString()) ) .add("time", System.currentTimeMillis()) + .add("pluginVersion", plugin.getBootstrap().getVersion()) ) - .add("sessions", new JArray() + .add("permissionHolders", new JArray() .consume(arr -> { for (PermissionHolder holder : holders) { arr.add(writeData(holder)); } }) ) + .add("tracks", new JArray() + .consume(arr -> { + for (Track track : tracks) { + arr.add(writeData(track)); + } + }) + ) .add("knownPermissions", new JArray() .consume(arr -> { for (String perm : plugin.getPermissionRegistry().rootAsList()) { @@ -111,42 +115,6 @@ public final class WebEditor { ).toJson(); } - private static String getHolderIdentifier(PermissionHolder holder) { - if (holder.getType() == HolderType.USER) { - User user = ((User) holder); - return USER_ID_PATTERN + user.getUuid().toString(); - } else { - Group group = ((Group) holder); - return GROUP_ID_PATTERN + group.getName(); - } - } - - public static PermissionHolder getHolderFromIdentifier(LuckPermsPlugin plugin, Sender sender, String who) { - if (who.startsWith(GROUP_ID_PATTERN)) { - String group = who.substring(GROUP_ID_PATTERN.length()); - Group holder = plugin.getStorage().loadGroup(group).join().orElse(null); - if (holder == null) { - Message.APPLY_EDITS_TARGET_GROUP_NOT_EXISTS.send(sender, group); - } - return holder; - } else if (who.startsWith(USER_ID_PATTERN)) { - String user = who.substring(USER_ID_PATTERN.length()); - UUID uuid = Uuids.parse(user); - if (uuid == null) { - Message.APPLY_EDITS_TARGET_USER_NOT_UUID.send(sender, user); - return null; - } - User holder = plugin.getStorage().loadUser(uuid, null).join(); - if (holder == null) { - Message.APPLY_EDITS_TARGET_USER_UNABLE_TO_LOAD.send(sender, uuid.toString()); - } - return holder; - } else { - Message.APPLY_EDITS_TARGET_UNKNOWN.send(sender, who); - return null; - } - } - public static JsonObject readDataFromBytebin(Bytebin bytebin, String id) { Request request = new Request.Builder() .url(bytebin.getPasteUrl(id)) @@ -169,23 +137,18 @@ public final class WebEditor { } } - private static JsonArray serializePermissions(Stream nodes) { + private static JsonArray serializePermissions(Collection nodes) { JsonArray arr = new JsonArray(); - nodes.forEach(node -> { + for (Node node : nodes) { JsonObject attributes = new JsonObject(); - attributes.addProperty("permission", node.getPermission()); + + attributes.addProperty("type", node.getType().name().toLowerCase()); + attributes.addProperty("key", node.getKey()); attributes.addProperty("value", node.getValue()); - if (!node.getServer().equals("global")) { - attributes.addProperty("server", node.getServer()); - } - - if (!node.getWorld().equals("global")) { - attributes.addProperty("world", node.getWorld()); - } - - if (node.getExpiry() != 0L) { - attributes.addProperty("expiry", node.getExpiry()); + Instant expiry = node.getExpiry(); + if (expiry != null) { + attributes.addProperty("expiry", expiry.getEpochSecond()); } if (!node.getContexts().isEmpty()) { @@ -193,48 +156,30 @@ public final class WebEditor { } arr.add(attributes); - }); + } return arr; } - public static Set deserializePermissions(JsonArray permissionsSection) { - Set nodes = new HashSet<>(); + public static Set deserializePermissions(JsonArray arr) { + Set nodes = new HashSet<>(); + for (JsonElement ent : arr) { + JsonObject attributes = ent.getAsJsonObject(); - for (JsonElement ent : permissionsSection) { - if (!ent.isJsonObject()) { - continue; + String key = attributes.get("key").getAsString(); + boolean value = attributes.get("value").getAsBoolean(); + + NodeBuilder builder = NodeFactory.builder(key).value(value); + + if (attributes.has("expiry")) { + builder.expiry(attributes.get("expiry").getAsLong()); } - JsonObject data = ent.getAsJsonObject(); - - String permission = data.get("permission").getAsString(); - boolean value = true; - String server = "global"; - String world = "global"; - long expiry = 0L; - ImmutableContextSet context = ImmutableContextSet.empty(); - - if (data.has("value")) { - value = data.get("value").getAsBoolean(); - } - if (data.has("server")) { - server = data.get("server").getAsString(); - } - if (data.has("world")) { - world = data.get("world").getAsString(); - } - if (data.has("expiry")) { - expiry = data.get("expiry").getAsLong(); + if (attributes.has("context")) { + builder.context(ContextSetJsonSerializer.deserializeContextSet(attributes.get("context"))); } - if (data.has("context") && data.get("context").isJsonObject()) { - JsonObject contexts = data.get("context").getAsJsonObject(); - context = ContextSetJsonSerializer.deserializeContextSet(contexts).immutableCopy(); - } - - nodes.add(NodeDataContainer.of(permission, value, server, world, expiry, context)); + nodes.add(builder.build()); } - return nodes; }