fix several issues with channels

Fix ChannelModal to disable the save button if nothing has changed

Fix saving with no changes throwing an error
This commit is contained in:
Jake Potrebic 2022-12-30 13:14:11 -08:00
parent b1020a3907
commit 7f5f36a38f
No known key found for this signature in database
GPG Key ID: 27CC63F7CBC866C7
7 changed files with 40 additions and 24 deletions

View File

@ -62,6 +62,7 @@ public class ChannelController extends HangarComponent {
this.channelService.checkColor(projectId, color, existingColor);
}
// @el(author: String, slug: String)
@Anyone
@VisibilityRequired(type = VisibilityRequired.Type.PROJECT, args = "{#author, #slug}")
@GetMapping(path = "/{author}/{slug}", produces = MediaType.APPLICATION_JSON_VALUE)
@ -75,7 +76,7 @@ public class ChannelController extends HangarComponent {
@RateLimit(overdraft = 5, refillTokens = 1, refillSeconds = 15)
@PermissionRequired(type = PermissionType.PROJECT, perms = NamedPermission.EDIT_CHANNEL, args = "{#projectId}")
@PostMapping(path = "/{projectId}/create", consumes = MediaType.APPLICATION_JSON_VALUE)
public void createChannel(@PathVariable final long projectId, @RequestBody @Valid final ChannelForm channelForm) {
public void createChannel(@PathVariable final long projectId, @RequestBody final @Valid ChannelForm channelForm) {
final Set<ChannelFlag> flags = channelForm.getFlags();
flags.retainAll(flags.stream().filter(ChannelFlag::isEditable).collect(Collectors.toSet()));
this.channelService.createProjectChannel(channelForm.getName(), channelForm.getColor(), projectId, flags);
@ -86,9 +87,9 @@ public class ChannelController extends HangarComponent {
@RateLimit(overdraft = 5, refillTokens = 1, refillSeconds = 15)
@PermissionRequired(type = PermissionType.PROJECT, perms = NamedPermission.EDIT_CHANNEL, args = "{#projectId}")
@PostMapping(path = "/{projectId}/edit", consumes = MediaType.APPLICATION_JSON_VALUE)
public void editChannel(@PathVariable final long projectId, @RequestBody @Valid final EditChannelForm channelForm) {
public void editChannel(@PathVariable final long projectId, @RequestBody final @Valid EditChannelForm channelForm) {
final Set<ChannelFlag> flags = channelForm.getFlags();
flags.retainAll(flags.stream().filter(ChannelFlag::isEditable).collect(Collectors.toSet()));
flags.retainAll(ChannelFlag.EDITABLE);
this.channelService.editProjectChannel(channelForm.getId(), channelForm.getName(), channelForm.getColor(), projectId, flags);
}

View File

@ -1,6 +1,9 @@
package io.papermc.hangar.model.common;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
import org.jdbi.v3.core.enums.EnumByOrdinal;
@EnumByOrdinal
@ -11,6 +14,9 @@ public enum ChannelFlag {
PINNED(true, true),
;
public static final Set<ChannelFlag> EDITABLE = Arrays.stream(values()).filter(ChannelFlag::isEditable).collect(Collectors.toUnmodifiableSet());
public static final Set<ChannelFlag> ALWAYS_EDITABLE = Arrays.stream(values()).filter(ChannelFlag::isAlwaysEditable).collect(Collectors.toUnmodifiableSet());
private final boolean editable;
private final boolean alwaysEditable;

View File

@ -10,6 +10,7 @@ import io.papermc.hangar.model.db.projects.ProjectChannelTable;
import io.papermc.hangar.model.internal.logs.LogAction;
import io.papermc.hangar.model.internal.logs.contexts.ProjectContext;
import io.papermc.hangar.model.internal.projects.HangarChannel;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.function.LongFunction;
@ -79,32 +80,35 @@ public class ChannelService extends HangarComponent {
}
if (projectChannelTable.getFlags().contains(ChannelFlag.FROZEN)) {
// Allow changing of certain flags
boolean updated = false;
final String old = formatChannelChange(projectChannelTable);
for (final ChannelFlag flag : ChannelFlag.values()) {
if (!flag.isAlwaysEditable()) {
continue;
final Iterator<ChannelFlag> currentIter = projectChannelTable.getFlags().iterator();
while (currentIter.hasNext()) {
final ChannelFlag existingFlag = currentIter.next();
if (existingFlag == ChannelFlag.FROZEN) continue;
if (!flags.contains(existingFlag)) {
if (!existingFlag.isAlwaysEditable()) {
throw new HangarApiException(HttpStatus.BAD_REQUEST, "channel.modal.error.cannotEdit");
}
final boolean added = flags.contains(flag);
if (added != projectChannelTable.getFlags().contains(flag)) {
updated = true;
if (added) {
projectChannelTable.getFlags().add(flag);
currentIter.remove();
} else {
projectChannelTable.getFlags().remove(flag);
flags.remove(existingFlag);
}
}
for (final ChannelFlag newFlag : flags) { // should be all "new" flags here
if (!newFlag.isAlwaysEditable()) {
throw new HangarApiException(HttpStatus.BAD_REQUEST, "channel.modal.error.cannotEdit");
}
projectChannelTable.getFlags().add(newFlag);
updated = true;
}
if (updated) {
final String old = formatChannelChange(projectChannelTable);
this.projectChannelsDAO.update(projectChannelTable);
this.actionLogger.project(LogAction.PROJECT_CHANNEL_EDITED.create(ProjectContext.of(projectId), formatChannelChange(projectChannelTable), old));
return;
}
throw new HangarApiException(HttpStatus.BAD_REQUEST, "channel.modal.error.cannotEdit");
return;
}
this.validateChannel(name, color, projectId, this.projectChannelsDAO.getProjectChannels(projectId).stream().filter(ch -> ch.getId() != channelId).collect(Collectors.toList()));

View File

@ -7,7 +7,7 @@ import Button from "~/lib/components/design/Button.vue";
import Modal from "~/lib/components/modals/Modal.vue";
import { useBackendData } from "~/store/backendData";
import InputText from "~/lib/components/ui/InputText.vue";
import { required } from "~/lib/composables/useValidationHelpers";
import { isSame, required } from "~/lib/composables/useValidationHelpers";
import { validChannelName, validChannelColor } from "~/composables/useHangarValidations";
import InputCheckbox from "~/lib/components/ui/InputCheckbox.vue";
import { ChannelFlag } from "~/types/enums";
@ -52,6 +52,10 @@ const swatches = computed<string[][]>(() => {
return result;
});
const noChange = computed(() => {
return props.channel?.name === name.value && props.channel.color === color.value && isSame(props.channel.flags, flags.value);
});
async function create(close: () => void) {
if (!(await v.value.$validate())) return;
close();
@ -113,7 +117,7 @@ reset();
</div>
<InputCheckbox v-for="f in possibleFlags" :key="f" v-model="flags" :label="i18n.t(`channel.modal.flags.${f.toLowerCase()}`)" :value="f" />
<Button class="mt-3" :disabled="v.$invalid" @click="create(on.click)">{{ edit ? i18n.t("general.save") : i18n.t("general.create") }}</Button>
<Button class="mt-3" :disabled="noChange || v.$invalid" @click="create(on.click)">{{ edit ? i18n.t("general.save") : i18n.t("general.create") }}</Button>
</template>
<template #activator="{ on }">
<slot name="activator" :on="open(on)"></slot>

View File

@ -19,7 +19,7 @@ import { AsyncData } from "nuxt/app";
import { ComputedRef, Ref } from "vue";
import { NuxtApp } from "@nuxt/schema";
import { useApi, useInternalApi } from "~/composables/useApi";
import { ref, useAsyncData } from "#imports";
import { ref, useAsyncData, createError } from "#imports";
import { handleRequestError } from "~/composables/useErrorHandling";
export type NonNullAsyncData<T, E = unknown> = { data: Ref<T> } & Pick<AsyncData<T, E>, "pending" | "refresh" | "execute" | "error">;

@ -1 +1 @@
Subproject commit f12bfd193a1a63b56a85775e3dd65473e864c46e
Subproject commit aa36bb66e4fb2802ae674938c8080974bc8cdabf

View File

@ -577,6 +577,7 @@
"duplicateName": "This project already has a channel with this name",
"tooLongName": "Channel name is too long",
"cannotDelete": "You cannot delete this channel",
"cannotEdit": "You cannot edit this channel",
"invalidFlag": "You tried to set an invalid channel flag"
},
"success": {