feat: add supports for ui permissions of notification reason type (#5286)

#### What type of PR is this?

/kind feature
/area core
/area console
/milestone 2.12.x

#### What this PR does / why we need it:

为通知类型设置添加 UI 权限判断。

#### Which issue(s) this PR fixes:

Fixes https://github.com/halo-dev/halo/issues/4728

#### Special notes for your reviewer:

#### Does this PR introduce a user-facing change?

```release-note
为通知类型设置添加 UI 权限判断。
```
pull/5310/head v2.12.0
Ryan Wang 2024-01-31 20:11:25 +08:00 committed by GitHub
parent dff6522d43
commit e85a55e416
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 81 additions and 33 deletions

View File

@ -5,17 +5,20 @@ import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder
import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder; import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
import static org.springdoc.core.fn.builders.requestbody.Builder.requestBodyBuilder; import static org.springdoc.core.fn.builders.requestbody.Builder.requestBodyBuilder;
import com.fasterxml.jackson.core.type.TypeReference;
import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import lombok.Data; import lombok.Data;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import org.apache.commons.lang3.StringUtils;
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder; import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
import org.springframework.lang.NonNull; import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -25,12 +28,15 @@ import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import reactor.util.function.Tuples; import reactor.util.function.Tuples;
import run.halo.app.core.extension.Role;
import run.halo.app.core.extension.endpoint.CustomEndpoint; import run.halo.app.core.extension.endpoint.CustomEndpoint;
import run.halo.app.core.extension.notification.NotifierDescriptor; import run.halo.app.core.extension.notification.NotifierDescriptor;
import run.halo.app.core.extension.notification.ReasonType; import run.halo.app.core.extension.notification.ReasonType;
import run.halo.app.extension.Comparators; import run.halo.app.extension.Comparators;
import run.halo.app.extension.GroupVersion; import run.halo.app.extension.GroupVersion;
import run.halo.app.extension.MetadataUtil;
import run.halo.app.extension.ReactiveExtensionClient; import run.halo.app.extension.ReactiveExtensionClient;
import run.halo.app.infra.utils.JsonUtils;
import run.halo.app.notification.UserNotificationPreference; import run.halo.app.notification.UserNotificationPreference;
import run.halo.app.notification.UserNotificationPreferenceService; import run.halo.app.notification.UserNotificationPreferenceService;
@ -135,10 +141,7 @@ public class UserNotificationPreferencesEndpoint implements CustomEndpoint {
Mono<ReasonTypeNotifierMatrix> listReasonTypeNotifierMatrix(String username) { Mono<ReasonTypeNotifierMatrix> listReasonTypeNotifierMatrix(String username) {
return client.list(ReasonType.class, null, Comparators.defaultComparator()) return client.list(ReasonType.class, null, Comparators.defaultComparator())
.map(reasonType -> new ReasonTypeInfo(reasonType.getMetadata().getName(), .map(ReasonTypeInfo::from)
reasonType.getSpec().getDisplayName(),
reasonType.getSpec().getDescription())
)
.collectList() .collectList()
.flatMap(reasonTypes -> client.list(NotifierDescriptor.class, null, .flatMap(reasonTypes -> client.list(NotifierDescriptor.class, null,
Comparators.defaultComparator()) Comparators.defaultComparator())
@ -188,7 +191,23 @@ public class UserNotificationPreferencesEndpoint implements CustomEndpoint {
private boolean[][] stateMatrix; private boolean[][] stateMatrix;
} }
record ReasonTypeInfo(String name, String displayName, String description) { record ReasonTypeInfo(String name, String displayName, String description,
Set<String> uiPermissions) {
public static ReasonTypeInfo from(ReasonType reasonType) {
var uiPermissions = Optional.of(MetadataUtil.nullSafeAnnotations(reasonType))
.map(annotations -> annotations.get(Role.UI_PERMISSIONS_ANNO))
.filter(StringUtils::isNotBlank)
.map(uiPermissionStr -> JsonUtils.jsonToObject(uiPermissionStr,
new TypeReference<Set<String>>() {
})
)
.orElse(Set.of());
return new ReasonTypeInfo(reasonType.getMetadata().getName(),
reasonType.getSpec().getDisplayName(),
reasonType.getSpec().getDescription(),
uiPermissions);
}
} }
record NotifierInfo(String name, String displayName, String description) { record NotifierInfo(String name, String displayName, String description) {

View File

@ -64,6 +64,9 @@ apiVersion: notification.halo.run/v1alpha1
kind: ReasonType kind: ReasonType
metadata: metadata:
name: new-comment-on-post name: new-comment-on-post
annotations:
rbac.authorization.halo.run/ui-permissions: |
[ "uc:posts:publish" ]
spec: spec:
displayName: "我的文章收到新评论" displayName: "我的文章收到新评论"
description: "如果有读者在你的文章下方留下了新的评论,你将会收到一条通知,告诉你有新的评论。 description: "如果有读者在你的文章下方留下了新的评论,你将会收到一条通知,告诉你有新的评论。
@ -90,6 +93,9 @@ apiVersion: notification.halo.run/v1alpha1
kind: ReasonType kind: ReasonType
metadata: metadata:
name: new-comment-on-single-page name: new-comment-on-single-page
annotations:
rbac.authorization.halo.run/ui-permissions: |
[ "system:singlepages:manage" ]
spec: spec:
displayName: "我的自定义页面收到新评论" displayName: "我的自定义页面收到新评论"
description: "当你创建的自定义页面收到新评论时,你将会收到一条通知,告诉你有新的评论。" description: "当你创建的自定义页面收到新评论时,你将会收到一条通知,告诉你有新的评论。"

View File

@ -58,6 +58,12 @@ export interface PostStatus {
* @memberof PostStatus * @memberof PostStatus
*/ */
lastModifyTime?: string; lastModifyTime?: string;
/**
*
* @type {number}
* @memberof PostStatus
*/
observedVersion?: number;
/** /**
* *
* @type {string} * @type {string}

View File

@ -36,4 +36,10 @@ export interface ReasonTypeInfo {
* @memberof ReasonTypeInfo * @memberof ReasonTypeInfo
*/ */
name?: string; name?: string;
/**
*
* @type {Array<string>}
* @memberof ReasonTypeInfo
*/
uiPermissions?: Array<string>;
} }

View File

@ -58,6 +58,12 @@ export interface SinglePageStatus {
* @memberof SinglePageStatus * @memberof SinglePageStatus
*/ */
lastModifyTime?: string; lastModifyTime?: string;
/**
*
* @type {number}
* @memberof SinglePageStatus
*/
observedVersion?: number;
/** /**
* *
* @type {string} * @type {string}

View File

@ -8,6 +8,7 @@ import { computed } from "vue";
import { inject } from "vue"; import { inject } from "vue";
import { cloneDeep } from "lodash-es"; import { cloneDeep } from "lodash-es";
import type { ReasonTypeNotifierRequest } from "@halo-dev/api-client"; import type { ReasonTypeNotifierRequest } from "@halo-dev/api-client";
import HasPermission from "@/components/permission/HasPermission.vue";
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@ -114,37 +115,41 @@ const {
</tr> </tr>
</thead> </thead>
<tbody class="divide-y divide-gray-100 bg-white"> <tbody class="divide-y divide-gray-100 bg-white">
<tr <template
v-for="(reasonType, index) in data?.reasonTypes" v-for="(reasonType, index) in data?.reasonTypes"
:key="reasonType.name" :key="reasonType.name"
> >
<td <HasPermission :permissions="reasonType.uiPermissions || []">
class="whitespace-nowrap px-4 py-3 text-sm font-medium text-gray-900" <tr>
> <td
{{ reasonType.displayName }} class="whitespace-nowrap px-4 py-3 text-sm font-medium text-gray-900"
</td> >
<td {{ reasonType.displayName }}
v-for="(notifier, notifierIndex) in data?.notifiers" </td>
:key="notifier.name" <td
class="whitespace-nowrap px-4 py-3 text-sm text-gray-500" v-for="(notifier, notifierIndex) in data?.notifiers"
> :key="notifier.name"
<VSwitch class="whitespace-nowrap px-4 py-3 text-sm text-gray-500"
:model-value="data?.stateMatrix?.[index][notifierIndex]" >
:loading=" <VSwitch
mutating && :model-value="data?.stateMatrix?.[index][notifierIndex]"
variables?.reasonTypeIndex === index && :loading="
variables?.notifierIndex === notifierIndex mutating &&
" variables?.reasonTypeIndex === index &&
@change=" variables?.notifierIndex === notifierIndex
mutate({ "
state: !data?.stateMatrix?.[index][notifierIndex], @change="
reasonTypeIndex: index, mutate({
notifierIndex: notifierIndex, state: !data?.stateMatrix?.[index][notifierIndex],
}) reasonTypeIndex: index,
" notifierIndex: notifierIndex,
/> })
</td> "
</tr> />
</td>
</tr>
</HasPermission>
</template>
</tbody> </tbody>
</table> </table>
</div> </div>