From cd90771bb729201d7a5b38e45187778b89118018 Mon Sep 17 00:00:00 2001 From: guqing Date: Sun, 29 Sep 2024 16:22:44 +0800 Subject: [PATCH 1/3] feat: add API for recycle users own posts --- .../run/halo/app/content/PostService.java | 2 ++ .../app/content/impl/PostServiceImpl.java | 9 +++++++++ .../app/core/endpoint/uc/UcPostEndpoint.java | 16 ++++++++++++++- .../extensions/role-template-uc-content.yaml | 20 ++++++++++++++++++- 4 files changed, 45 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/run/halo/app/content/PostService.java b/application/src/main/java/run/halo/app/content/PostService.java index cf4c0b44a..d9344a3de 100644 --- a/application/src/main/java/run/halo/app/content/PostService.java +++ b/application/src/main/java/run/halo/app/content/PostService.java @@ -50,4 +50,6 @@ public interface PostService { Mono revertToSpecifiedSnapshot(String postName, String snapshotName); Mono deleteContent(String postName, String snapshotName); + + Mono recycleBy(String postName, String username); } diff --git a/application/src/main/java/run/halo/app/content/impl/PostServiceImpl.java b/application/src/main/java/run/halo/app/content/impl/PostServiceImpl.java index 5e805ac9a..7cdae00cc 100644 --- a/application/src/main/java/run/halo/app/content/impl/PostServiceImpl.java +++ b/application/src/main/java/run/halo/app/content/impl/PostServiceImpl.java @@ -379,6 +379,15 @@ public class PostServiceImpl extends AbstractContentService implements PostServi }); } + @Override + public Mono recycleBy(String postName, String username) { + return getByUsername(postName, username) + .flatMap(post -> updatePostWithRetry(post, record -> { + record.getSpec().setDeleted(true); + return record; + })); + } + private Mono updatePostWithRetry(Post post, UnaryOperator func) { return client.update(func.apply(post)) .onErrorResume(OptimisticLockingFailureException.class, diff --git a/application/src/main/java/run/halo/app/core/endpoint/uc/UcPostEndpoint.java b/application/src/main/java/run/halo/app/core/endpoint/uc/UcPostEndpoint.java index a969e64e8..87ca78019 100644 --- a/application/src/main/java/run/halo/app/core/endpoint/uc/UcPostEndpoint.java +++ b/application/src/main/java/run/halo/app/core/endpoint/uc/UcPostEndpoint.java @@ -124,13 +124,27 @@ public class UcPostEndpoint implements CustomEndpoint { .operationId("UnpublishMyPost") .description("Unpublish my post.") .parameter(namePathParam) - .response(responseBuilder().implementation(Post.class))) + .response(responseBuilder().implementation(Post.class)) + ) + .DELETE("/{name}/recycle", this::recycleMyPost, builder -> builder.tag(tag) + .operationId("RecycleMyPost") + .description("Move my post to recycle bin.") + .parameter(namePathParam) + .response(responseBuilder().implementation(Post.class)) + ) .build(), builder -> { }) .build(); } + private Mono recycleMyPost(ServerRequest request) { + final var name = request.pathVariable("name"); + return getCurrentUser() + .flatMap(username -> postService.recycleBy(name, username)) + .flatMap(post -> ServerResponse.ok().bodyValue(post)); + } + private Mono getMyPostDraft(ServerRequest request) { var name = request.pathVariable("name"); var patched = request.queryParam("patched").map(Boolean::valueOf).orElse(false); diff --git a/application/src/main/resources/extensions/role-template-uc-content.yaml b/application/src/main/resources/extensions/role-template-uc-content.yaml index 93f7bcd6b..575d68e68 100644 --- a/application/src/main/resources/extensions/role-template-uc-content.yaml +++ b/application/src/main/resources/extensions/role-template-uc-content.yaml @@ -39,7 +39,8 @@ metadata: # Currently, yaml definition does not support i18n, please see https://github.com/halo-dev/halo/issues/3573 rbac.authorization.halo.run/display-name: "Post Author" rbac.authorization.halo.run/dependencies: | - [ "role-template-post-contributor", "role-template-post-publisher", "role-template-post-attachment-manager" ] + [ "role-template-post-contributor", "role-template-post-publisher", "role-template-recycle-my-post", + "role-template-post-attachment-manager" ] rules: [ ] --- @@ -98,6 +99,23 @@ rules: resources: [ "posts/publish", "posts/unpublish" ] verbs: [ "update" ] +--- +apiVersion: v1alpha1 +kind: Role +metadata: + name: role-template-recycle-my-post + labels: + halo.run/role-template: "true" + annotations: + rbac.authorization.halo.run/module: "Posts Management" + rbac.authorization.halo.run/display-name: "Recycle My Post" + rbac.authorization.halo.run/ui-permissions: | + [ "uc:posts:recycle" ] +rules: + - apiGroups: [ "uc.api.content.halo.run" ] + resources: [ "posts/recycle" ] + verbs: [ "delete" ] + --- apiVersion: v1alpha1 kind: Role From 944d0a5cb4f78ef55a5a7367ed5f333baa7dc7bd Mon Sep 17 00:00:00 2001 From: Ryan Wang Date: Sun, 29 Sep 2024 17:42:10 +0800 Subject: [PATCH 2/3] feat: add supports for delete own posts in uc Signed-off-by: Ryan Wang --- api-docs/openapi/v3_0/aggregated.json | 32 +++++++ .../openapi/v3_0/apis_uc.api_v1alpha1.json | 32 +++++++ .../src/api/post-v1alpha1-uc-api.ts | 87 +++++++++++++++++++ ui/src/locales/en.yaml | 3 + ui/src/locales/zh-CN.yaml | 3 + ui/src/locales/zh-TW.yaml | 3 + .../posts/components/PostListItem.vue | 26 +++++- 7 files changed, 185 insertions(+), 1 deletion(-) diff --git a/api-docs/openapi/v3_0/aggregated.json b/api-docs/openapi/v3_0/aggregated.json index bb1dc041a..742665303 100644 --- a/api-docs/openapi/v3_0/aggregated.json +++ b/api-docs/openapi/v3_0/aggregated.json @@ -15117,6 +15117,38 @@ ] } }, + "/apis/uc.api.content.halo.run/v1alpha1/posts/{name}/recycle": { + "delete": { + "description": "Move my post to recycle bin.", + "operationId": "RecycleMyPost", + "parameters": [ + { + "description": "Post name", + "in": "path", + "name": "name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "default": { + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/Post" + } + } + }, + "description": "default response" + } + }, + "tags": [ + "PostV1alpha1Uc" + ] + } + }, "/apis/uc.api.content.halo.run/v1alpha1/posts/{name}/unpublish": { "put": { "description": "Unpublish my post.", diff --git a/api-docs/openapi/v3_0/apis_uc.api_v1alpha1.json b/api-docs/openapi/v3_0/apis_uc.api_v1alpha1.json index eece516c0..7c8eb55b5 100644 --- a/api-docs/openapi/v3_0/apis_uc.api_v1alpha1.json +++ b/api-docs/openapi/v3_0/apis_uc.api_v1alpha1.json @@ -412,6 +412,38 @@ ] } }, + "/apis/uc.api.content.halo.run/v1alpha1/posts/{name}/recycle": { + "delete": { + "description": "Move my post to recycle bin.", + "operationId": "RecycleMyPost", + "parameters": [ + { + "description": "Post name", + "in": "path", + "name": "name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "default": { + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/Post" + } + } + }, + "description": "default response" + } + }, + "tags": [ + "PostV1alpha1Uc" + ] + } + }, "/apis/uc.api.content.halo.run/v1alpha1/posts/{name}/unpublish": { "put": { "description": "Unpublish my post.", diff --git a/ui/packages/api-client/src/api/post-v1alpha1-uc-api.ts b/ui/packages/api-client/src/api/post-v1alpha1-uc-api.ts index 1a2d08f13..34733b7f2 100644 --- a/ui/packages/api-client/src/api/post-v1alpha1-uc-api.ts +++ b/ui/packages/api-client/src/api/post-v1alpha1-uc-api.ts @@ -270,6 +270,47 @@ export const PostV1alpha1UcApiAxiosParamCreator = function (configuration?: Conf + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Move my post to recycle bin. + * @param {string} name Post name + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + recycleMyPost: async (name: string, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'name' is not null or undefined + assertParamExists('recycleMyPost', 'name', name) + const localVarPath = `/apis/uc.api.content.halo.run/v1alpha1/posts/{name}/recycle` + .replace(`{${"name"}}`, encodeURIComponent(String(name))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'DELETE', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication basicAuth required + // http basic authentication required + setBasicAuthToObject(localVarRequestOptions, configuration) + + // authentication bearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; @@ -488,6 +529,18 @@ export const PostV1alpha1UcApiFp = function(configuration?: Configuration) { const localVarOperationServerBasePath = operationServerMap['PostV1alpha1UcApi.publishMyPost']?.[localVarOperationServerIndex]?.url; return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); }, + /** + * Move my post to recycle bin. + * @param {string} name Post name + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async recycleMyPost(name: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.recycleMyPost(name, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['PostV1alpha1UcApi.recycleMyPost']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, /** * Unpublish my post. * @param {string} name Post name @@ -581,6 +634,15 @@ export const PostV1alpha1UcApiFactory = function (configuration?: Configuration, publishMyPost(requestParameters: PostV1alpha1UcApiPublishMyPostRequest, options?: RawAxiosRequestConfig): AxiosPromise { return localVarFp.publishMyPost(requestParameters.name, options).then((request) => request(axios, basePath)); }, + /** + * Move my post to recycle bin. + * @param {PostV1alpha1UcApiRecycleMyPostRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + recycleMyPost(requestParameters: PostV1alpha1UcApiRecycleMyPostRequest, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.recycleMyPost(requestParameters.name, options).then((request) => request(axios, basePath)); + }, /** * Unpublish my post. * @param {PostV1alpha1UcApiUnpublishMyPostRequest} requestParameters Request parameters. @@ -737,6 +799,20 @@ export interface PostV1alpha1UcApiPublishMyPostRequest { readonly name: string } +/** + * Request parameters for recycleMyPost operation in PostV1alpha1UcApi. + * @export + * @interface PostV1alpha1UcApiRecycleMyPostRequest + */ +export interface PostV1alpha1UcApiRecycleMyPostRequest { + /** + * Post name + * @type {string} + * @memberof PostV1alpha1UcApiRecycleMyPost + */ + readonly name: string +} + /** * Request parameters for unpublishMyPost operation in PostV1alpha1UcApi. * @export @@ -855,6 +931,17 @@ export class PostV1alpha1UcApi extends BaseAPI { return PostV1alpha1UcApiFp(this.configuration).publishMyPost(requestParameters.name, options).then((request) => request(this.axios, this.basePath)); } + /** + * Move my post to recycle bin. + * @param {PostV1alpha1UcApiRecycleMyPostRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof PostV1alpha1UcApi + */ + public recycleMyPost(requestParameters: PostV1alpha1UcApiRecycleMyPostRequest, options?: RawAxiosRequestConfig) { + return PostV1alpha1UcApiFp(this.configuration).recycleMyPost(requestParameters.name, options).then((request) => request(this.axios, this.basePath)); + } + /** * Unpublish my post. * @param {PostV1alpha1UcApiUnpublishMyPostRequest} requestParameters Request parameters. diff --git a/ui/src/locales/en.yaml b/ui/src/locales/en.yaml index b56a699ba..d29e70972 100644 --- a/ui/src/locales/en.yaml +++ b/ui/src/locales/en.yaml @@ -1790,6 +1790,9 @@ core: cancel_publish: description: Are you sure you want to cancel publishing? title: Cancel publish + delete: + title: Delete post + description: This action will move the post to the recycle bin, where it will be managed by the site administrator. publish_modal: title: Publish post setting_modal: diff --git a/ui/src/locales/zh-CN.yaml b/ui/src/locales/zh-CN.yaml index ab84cece9..a00b88c5e 100644 --- a/ui/src/locales/zh-CN.yaml +++ b/ui/src/locales/zh-CN.yaml @@ -256,6 +256,9 @@ core: cancel_publish: title: 取消发布 description: 确定要取消发布吗? + delete: + title: 删除文章 + description: 该操作会将文章放入回收站,后续由网站管理员进行管理。 post_editor: title: 文章编辑 untitled: 未命名文章 diff --git a/ui/src/locales/zh-TW.yaml b/ui/src/locales/zh-TW.yaml index 45b8cafa9..f90e4b4f4 100644 --- a/ui/src/locales/zh-TW.yaml +++ b/ui/src/locales/zh-TW.yaml @@ -1656,6 +1656,9 @@ core: cancel_publish: description: 確定要取消發布嗎? title: 取消發布 + delete: + title: 刪除文章 + description: 該操作會將文章放入回收站,後續由網站管理員進行管理。 publish_modal: title: 發布文章 setting_modal: diff --git a/ui/uc-src/modules/contents/posts/components/PostListItem.vue b/ui/uc-src/modules/contents/posts/components/PostListItem.vue index a3229e7da..b61c6b2ff 100644 --- a/ui/uc-src/modules/contents/posts/components/PostListItem.vue +++ b/ui/uc-src/modules/contents/posts/components/PostListItem.vue @@ -92,6 +92,25 @@ function handleUnpublish() { }, }); } + +function handleDelete() { + Dialog.warning({ + title: t("core.uc_post.operations.delete.title"), + description: t("core.uc_post.operations.delete.description"), + confirmType: "danger", + confirmText: t("core.common.buttons.confirm"), + cancelText: t("core.common.buttons.cancel"), + async onConfirm() { + await ucApiClient.content.post.recycleMyPost({ + name: props.post.post.metadata.name, + }); + + Toast.success(t("core.common.toast.delete_success")); + + queryClient.invalidateQueries({ queryKey: ["my-posts"] }); + }, + }); +} From 5b2bdfdf17352eff295aa60e8c94876555e9f5b4 Mon Sep 17 00:00:00 2001 From: Ryan Wang Date: Sun, 29 Sep 2024 17:57:49 +0800 Subject: [PATCH 3/3] Refine i18n Signed-off-by: Ryan Wang --- ui/src/locales/en.yaml | 1 + ui/src/locales/zh-CN.yaml | 1 + ui/src/locales/zh-TW.yaml | 1 + 3 files changed, 3 insertions(+) diff --git a/ui/src/locales/en.yaml b/ui/src/locales/en.yaml index d29e70972..8982dc0d6 100644 --- a/ui/src/locales/en.yaml +++ b/ui/src/locales/en.yaml @@ -1519,6 +1519,7 @@ core: Post Author: Allows you to manage your own posts Post Contributor: Contributions allowed Post Publisher: Allow to publish own posts + Recycle My Post: Recycle My Post components: submit_button: computed_text: "{text} ({shortcut})" diff --git a/ui/src/locales/zh-CN.yaml b/ui/src/locales/zh-CN.yaml index a00b88c5e..009e0ddfd 100644 --- a/ui/src/locales/zh-CN.yaml +++ b/ui/src/locales/zh-CN.yaml @@ -1418,6 +1418,7 @@ core: Post Author: 允许管理自己的文章 Post Attachment Manager: 允许在文章中上传图片 Post Publisher: 允许发布自己的文章 + Recycle My Post: 允许删除自己的文章 components: submit_button: computed_text: "{text}({shortcut})" diff --git a/ui/src/locales/zh-TW.yaml b/ui/src/locales/zh-TW.yaml index f90e4b4f4..bd881b01c 100644 --- a/ui/src/locales/zh-TW.yaml +++ b/ui/src/locales/zh-TW.yaml @@ -1394,6 +1394,7 @@ core: Post Author: 允許管理自己的文章 Post Contributor: 允許投稿 Post Publisher: 允許發布自己的文章 + Recycle My Post: 允許刪除自己的文章 components: submit_button: computed_text: "{text}({shortcut})"