This is an automated email from the ASF dual-hosted git repository.

shuai pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/incubator-answer.git

commit 4a74eed4a7de8589fe50d1f8568d4c141da69850
Author: Luffy <[email protected]>
AuthorDate: Wed Dec 18 18:34:10 2024 +0800

    feat: Add permanently delete
---
 docs/docs.go                                       | 55 ++++++++++++++++++++++
 docs/swagger.json                                  | 55 ++++++++++++++++++++++
 docs/swagger.yaml                                  | 35 ++++++++++++++
 i18n/en_US.yaml                                    |  9 +++-
 i18n/zh_CN.yaml                                    |  9 +++-
 internal/base/constant/user.go                     |  6 +++
 .../controller_admin/user_backyard_controller.go   | 20 ++++++++
 internal/repo/answer/answer_repo.go                |  8 ++++
 internal/repo/question/question_repo.go            |  8 ++++
 internal/repo/user/user_backyard_repo.go           |  9 ++++
 internal/router/answer_api_router.go               |  2 +
 internal/schema/backyard_user_schema.go            |  5 ++
 internal/service/answer_common/answer.go           |  1 +
 internal/service/question_common/question.go       |  1 +
 internal/service/user_admin/user_backyard.go       | 13 +++++
 ui/src/pages/Admin/Answers/index.tsx               | 46 ++++++++++++++----
 ui/src/pages/Admin/Questions/index.tsx             | 46 ++++++++++++++----
 ui/src/pages/Admin/Users/index.tsx                 | 28 +++++++++++
 ui/src/services/common.ts                          |  4 ++
 19 files changed, 340 insertions(+), 20 deletions(-)

diff --git a/docs/docs.go b/docs/docs.go
index da050e10..10930067 100644
--- a/docs/docs.go
+++ b/docs/docs.go
@@ -295,6 +295,45 @@ const docTemplate = `{
                 }
             }
         },
+        "/answer/admin/api/delete/permanently": {
+            "delete": {
+                "security": [
+                    {
+                        "ApiKeyAuth": []
+                    }
+                ],
+                "description": "delete permanently",
+                "consumes": [
+                    "application/json"
+                ],
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "admin"
+                ],
+                "summary": "delete permanently",
+                "parameters": [
+                    {
+                        "description": "DeletePermanentlyReq",
+                        "name": "data",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/schema.DeletePermanentlyReq"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/handler.RespBody"
+                        }
+                    }
+                }
+            }
+        },
         "/answer/admin/api/language/options": {
             "get": {
                 "description": "Get language options",
@@ -8158,6 +8197,22 @@ const docTemplate = `{
                 }
             }
         },
+        "schema.DeletePermanentlyReq": {
+            "type": "object",
+            "required": [
+                "type"
+            ],
+            "properties": {
+                "type": {
+                    "type": "string",
+                    "enum": [
+                        "users",
+                        "questions",
+                        "answers"
+                    ]
+                }
+            }
+        },
         "schema.EditUserProfileReq": {
             "type": "object",
             "required": [
diff --git a/docs/swagger.json b/docs/swagger.json
index bcce7817..53b95cb8 100644
--- a/docs/swagger.json
+++ b/docs/swagger.json
@@ -268,6 +268,45 @@
                 }
             }
         },
+        "/answer/admin/api/delete/permanently": {
+            "delete": {
+                "security": [
+                    {
+                        "ApiKeyAuth": []
+                    }
+                ],
+                "description": "delete permanently",
+                "consumes": [
+                    "application/json"
+                ],
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "admin"
+                ],
+                "summary": "delete permanently",
+                "parameters": [
+                    {
+                        "description": "DeletePermanentlyReq",
+                        "name": "data",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/schema.DeletePermanentlyReq"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/handler.RespBody"
+                        }
+                    }
+                }
+            }
+        },
         "/answer/admin/api/language/options": {
             "get": {
                 "description": "Get language options",
@@ -8131,6 +8170,22 @@
                 }
             }
         },
+        "schema.DeletePermanentlyReq": {
+            "type": "object",
+            "required": [
+                "type"
+            ],
+            "properties": {
+                "type": {
+                    "type": "string",
+                    "enum": [
+                        "users",
+                        "questions",
+                        "answers"
+                    ]
+                }
+            }
+        },
         "schema.EditUserProfileReq": {
             "type": "object",
             "required": [
diff --git a/docs/swagger.yaml b/docs/swagger.yaml
index 5e22186a..55b18c5e 100644
--- a/docs/swagger.yaml
+++ b/docs/swagger.yaml
@@ -495,6 +495,17 @@ definitions:
       name:
         type: string
     type: object
+  schema.DeletePermanentlyReq:
+    properties:
+      type:
+        enum:
+        - users
+        - questions
+        - answers
+        type: string
+    required:
+    - type
+    type: object
   schema.EditUserProfileReq:
     properties:
       display_name:
@@ -3106,6 +3117,30 @@ paths:
       summary: DashboardInfo
       tags:
       - admin
+  /answer/admin/api/delete/permanently:
+    delete:
+      consumes:
+      - application/json
+      description: delete permanently
+      parameters:
+      - description: DeletePermanentlyReq
+        in: body
+        name: data
+        required: true
+        schema:
+          $ref: '#/definitions/schema.DeletePermanentlyReq'
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            $ref: '#/definitions/handler.RespBody'
+      security:
+      - ApiKeyAuth: []
+      summary: delete permanently
+      tags:
+      - admin
   /answer/admin/api/language/options:
     get:
       description: Get language options
diff --git a/i18n/en_US.yaml b/i18n/en_US.yaml
index 801acc69..9d904622 100644
--- a/i18n/en_US.yaml
+++ b/i18n/en_US.yaml
@@ -1504,6 +1504,7 @@ ui:
     normal: Normal
     closed: Closed
     deleted: Deleted
+    deleted_permanently: Deleted permanently
     pending: Pending
     more: More
   search:
@@ -1539,6 +1540,9 @@ ui:
   cannot_vote_for_self: You can't vote for your own post.
   modal_confirm:
     title: Error...
+  delete_permanently:
+    title: Delete permanently
+    content: Are you sure you want to delete permanently?
   account_result:
     success: Your new account is confirmed; you will be redirected to the home 
page.
     link: Continue to homepage
@@ -2297,5 +2301,6 @@ ui:
     user_deleted: This user has been deleted.
     badge_activated: This badge has been activated.
     badge_inactivated: This badge has been inactivated.
-
-
+    users_deleted: These users have been deleted.
+    posts_deleted: These questions have been deleted.
+    answers_deleted: These answers have been deleted.
diff --git a/i18n/zh_CN.yaml b/i18n/zh_CN.yaml
index 0567a10c..d06733fb 100644
--- a/i18n/zh_CN.yaml
+++ b/i18n/zh_CN.yaml
@@ -1232,6 +1232,9 @@ ui:
     modal_content: 该电子邮件地址已经注册。你确定要连接到已有账户吗?
     modal_cancel: 更改邮箱
     modal_confirm: 连接到已有账户
+  delete_permanently:
+    title: 永久删除
+    content: 你确定要永久删除吗?
   password_reset:
     page_title: 密码重置
     btn_name: 重置我的密码
@@ -1471,6 +1474,7 @@ ui:
     normal: 正常
     closed: 已关闭
     deleted: 已删除
+    deleted_permanently: 永久删除
     pending: 等待处理
     more: 更多
   search:
@@ -2259,5 +2263,6 @@ ui:
     user_deleted: 此用户已被删除
     badge_activated: 此徽章已被激活。
     badge_inactivated: 此徽章已被禁用。
-
-
+    users_deleted: 这些用户已被删除。
+    posts_deleted: 这些帖子已被删除。
+    answers_deleted: 这些回答已被删除。
diff --git a/internal/base/constant/user.go b/internal/base/constant/user.go
index d453e443..80774e0d 100644
--- a/internal/base/constant/user.go
+++ b/internal/base/constant/user.go
@@ -30,6 +30,12 @@ const (
        EmailStatusToBeVerified = 2
 )
 
+const (
+       DeletePermanentlyUsers     = "users"
+       DeletePermanentlyQuestions = "questions"
+       DeletePermanentlyAnswers   = "answers"
+)
+
 func ConvertUserStatus(status, mailStatus int) string {
        switch status {
        case 1:
diff --git a/internal/controller_admin/user_backyard_controller.go 
b/internal/controller_admin/user_backyard_controller.go
index a2ab3f2e..1d9fb612 100644
--- a/internal/controller_admin/user_backyard_controller.go
+++ b/internal/controller_admin/user_backyard_controller.go
@@ -242,3 +242,23 @@ func (uc *UserAdminController) SendUserActivation(ctx 
*gin.Context) {
        err := uc.userService.SendUserActivation(ctx, req)
        handler.HandleResponse(ctx, err, nil)
 }
+
+// DeletePermanently delete permanently
+// @Summary delete permanently
+// @Description delete permanently
+// @Security ApiKeyAuth
+// @Tags admin
+// @Accept json
+// @Produce json
+// @Param data body schema.DeletePermanentlyReq true "DeletePermanentlyReq"
+// @Success 200 {object} handler.RespBody
+// @Router /answer/admin/api/delete/permanently [delete]
+func (uc *UserAdminController) DeletePermanently(ctx *gin.Context) {
+       req := &schema.DeletePermanentlyReq{}
+       if handler.BindAndCheck(ctx, req) {
+               return
+       }
+
+       err := uc.userService.DeletePermanently(ctx, req)
+       handler.HandleResponse(ctx, err, nil)
+}
diff --git a/internal/repo/answer/answer_repo.go 
b/internal/repo/answer/answer_repo.go
index f59d6006..f4bba8cc 100644
--- a/internal/repo/answer/answer_repo.go
+++ b/internal/repo/answer/answer_repo.go
@@ -527,3 +527,11 @@ func (ar *answerRepo) updateSearch(ctx context.Context, 
answerID string) (err er
        err = s.UpdateContent(ctx, content)
        return
 }
+
+func (ar *answerRepo) DeletePermanentlyAnswers(ctx context.Context) error {
+       _, err := ar.data.DB.Context(ctx).Where("status = ?", 
entity.AnswerStatusDeleted).Delete(&entity.Answer{})
+       if err != nil {
+               return 
errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
+       }
+       return nil
+}
diff --git a/internal/repo/question/question_repo.go 
b/internal/repo/question/question_repo.go
index 9b1d212e..7000e75f 100644
--- a/internal/repo/question/question_repo.go
+++ b/internal/repo/question/question_repo.go
@@ -167,6 +167,14 @@ func (qr *questionRepo) 
UpdateQuestionStatusWithOutUpdateTime(ctx context.Contex
        return nil
 }
 
+func (qr *questionRepo) DeletePermanentlyQuestions(ctx context.Context) (err 
error) {
+       _, err = qr.data.DB.Context(ctx).Where("status = ?", 
entity.QuestionStatusDeleted).Delete(&entity.Question{})
+       if err != nil {
+               return 
errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
+       }
+       return nil
+}
+
 func (qr *questionRepo) RecoverQuestion(ctx context.Context, questionID 
string) (err error) {
        questionID = uid.DeShortID(questionID)
        _, err = 
qr.data.DB.Context(ctx).ID(questionID).Cols("status").Update(&entity.Question{Status:
 entity.QuestionStatusAvailable})
diff --git a/internal/repo/user/user_backyard_repo.go 
b/internal/repo/user/user_backyard_repo.go
index 44a05cfb..62f7f789 100644
--- a/internal/repo/user/user_backyard_repo.go
+++ b/internal/repo/user/user_backyard_repo.go
@@ -175,3 +175,12 @@ func (ur *userAdminRepo) GetUserPage(ctx context.Context, 
page, pageSize int, us
        tryToDecorateUserListFromUserCenter(ctx, ur.data, users)
        return
 }
+
+// DeletePermanentlyUsers delete permanently users
+func (ur *userAdminRepo) DeletePermanentlyUsers(ctx context.Context) (err 
error) {
+       _, err = ur.data.DB.Context(ctx).Where("deleted_at IS NOT 
NULL").Delete(&entity.User{})
+       if err != nil {
+               err = 
errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
+       }
+       return
+}
diff --git a/internal/router/answer_api_router.go 
b/internal/router/answer_api_router.go
index 2927afef..b090f9c7 100644
--- a/internal/router/answer_api_router.go
+++ b/internal/router/answer_api_router.go
@@ -328,6 +328,8 @@ func (a *AnswerAPIRouter) RegisterAnswerAdminAPIRouter(r 
*gin.RouterGroup) {
        r.PUT("/user/password", a.adminUserController.UpdateUserPassword)
        r.PUT("/user/profile", a.adminUserController.EditUserProfile)
 
+       r.DELETE("/delete/permanently", a.adminUserController.DeletePermanently)
+
        // reason
        r.GET("/reasons", a.reasonController.Reasons)
 
diff --git a/internal/schema/backyard_user_schema.go 
b/internal/schema/backyard_user_schema.go
index 7c690aee..966665a4 100644
--- a/internal/schema/backyard_user_schema.go
+++ b/internal/schema/backyard_user_schema.go
@@ -133,6 +133,11 @@ type AddUsersReq struct {
        Users    []*AddUserReq `json:"-"`
 }
 
+// DeletePermanentlyReq delete permanently request
+type DeletePermanentlyReq struct {
+       Type string `validate:"required,oneof=users questions answers" 
json:"type"`
+}
+
 type AddUsersErrorData struct {
        // optional. error field name.
        Field string `json:"field"`
diff --git a/internal/service/answer_common/answer.go 
b/internal/service/answer_common/answer.go
index 45b11886..be40fe48 100644
--- a/internal/service/answer_common/answer.go
+++ b/internal/service/answer_common/answer.go
@@ -51,6 +51,7 @@ type AnswerRepo interface {
        GetAnswerCount(ctx context.Context) (count int64, err error)
        RemoveAllUserAnswer(ctx context.Context, userID string) (err error)
        SumVotesByQuestionID(ctx context.Context, questionID string) (float64, 
error)
+       DeletePermanentlyAnswers(ctx context.Context) (err error)
 }
 
 // AnswerCommon user service
diff --git a/internal/service/question_common/question.go 
b/internal/service/question_common/question.go
index fc01159e..c5a4fd15 100644
--- a/internal/service/question_common/question.go
+++ b/internal/service/question_common/question.go
@@ -63,6 +63,7 @@ type QuestionRepo interface {
        GetRecommendQuestionPageByTags(ctx context.Context, userID string, 
tagIDs, followedQuestionIDs []string, page, pageSize int) (questionList 
[]*entity.Question, total int64, err error)
        UpdateQuestionStatus(ctx context.Context, questionID string, status 
int) (err error)
        UpdateQuestionStatusWithOutUpdateTime(ctx context.Context, question 
*entity.Question) (err error)
+       DeletePermanentlyQuestions(ctx context.Context) (err error)
        RecoverQuestion(ctx context.Context, questionID string) (err error)
        UpdateQuestionOperation(ctx context.Context, question *entity.Question) 
(err error)
        GetQuestionsByTitle(ctx context.Context, title string, pageSize int) 
(questionList []*entity.Question, err error)
diff --git a/internal/service/user_admin/user_backyard.go 
b/internal/service/user_admin/user_backyard.go
index ebe1ea74..e7f63d5f 100644
--- a/internal/service/user_admin/user_backyard.go
+++ b/internal/service/user_admin/user_backyard.go
@@ -63,6 +63,7 @@ type UserAdminRepo interface {
        AddUser(ctx context.Context, user *entity.User) (err error)
        AddUsers(ctx context.Context, users []*entity.User) (err error)
        UpdateUserPassword(ctx context.Context, userID string, password string) 
(err error)
+       DeletePermanentlyUsers(ctx context.Context) (err error)
 }
 
 // UserAdminService user service
@@ -578,3 +579,15 @@ func (us *UserAdminService) SendUserActivation(ctx 
context.Context, req *schema.
        go us.emailService.SendAndSaveCode(ctx, userInfo.ID, userInfo.EMail, 
title, body, code, data.ToJSONString())
        return nil
 }
+
+func (us *UserAdminService) DeletePermanently(ctx context.Context, req 
*schema.DeletePermanentlyReq) (err error) {
+       if req.Type == constant.DeletePermanentlyUsers {
+               return us.userRepo.DeletePermanentlyUsers(ctx)
+       } else if req.Type == constant.DeletePermanentlyQuestions {
+               return us.questionCommonRepo.DeletePermanentlyQuestions(ctx)
+       } else if req.Type == constant.DeletePermanentlyAnswers {
+               return us.answerCommonRepo.DeletePermanentlyAnswers(ctx)
+       }
+
+       return errors.BadRequest(reason.RequestFormatError)
+}
diff --git a/ui/src/pages/Admin/Answers/index.tsx 
b/ui/src/pages/Admin/Answers/index.tsx
index 7f7e6fae..7ab2e80a 100644
--- a/ui/src/pages/Admin/Answers/index.tsx
+++ b/ui/src/pages/Admin/Answers/index.tsx
@@ -18,7 +18,7 @@
  */
 
 import { FC } from 'react';
-import { Form, Table, Stack } from 'react-bootstrap';
+import { Form, Table, Stack, Button } from 'react-bootstrap';
 import { useSearchParams, Link } from 'react-router-dom';
 import { useTranslation } from 'react-i18next';
 
@@ -31,12 +31,14 @@ import {
   BaseUserCard,
   Empty,
   QueryGroup,
+  Modal,
 } from '@/components';
 import { ADMIN_LIST_STATUS } from '@/common/constants';
 import * as Type from '@/common/interface';
-import { useAnswerSearch } from '@/services';
+import { deletePermanently, useAnswerSearch } from '@/services';
 import { escapeRemove } from '@/utils';
 import { pathFactory } from '@/router/pathFactory';
+import { toastStore } from '@/stores';
 
 import AnswerAction from './components/Action';
 
@@ -68,6 +70,24 @@ const Answers: FC = () => {
   });
   const count = listData?.count || 0;
 
+  const handleDeletePermanently = () => {
+    Modal.confirm({
+      title: t('title', { keyPrefix: 'delete_permanently' }),
+      content: t('content', { keyPrefix: 'delete_permanently' }),
+      cancelBtnVariant: 'link',
+      confirmText: t('ok', { keyPrefix: 'btns' }),
+      onConfirm: () => {
+        deletePermanently('answers').then(() => {
+          toastStore.getState().show({
+            msg: t('answers_deleted', { keyPrefix: 'messages' }),
+            variant: 'success',
+          });
+          refreshList();
+        });
+      },
+    });
+  };
+
   const handleFilter = (e) => {
     urlSearchParams.set('query', e.target.value);
     urlSearchParams.delete('page');
@@ -77,12 +97,22 @@ const Answers: FC = () => {
     <>
       <h3 className="mb-4">{t('page_title')}</h3>
       <div className="d-flex flex-wrap justify-content-between 
align-items-center mb-3">
-        <QueryGroup
-          data={answerFilterItems}
-          currentSort={curFilter}
-          sortKey="status"
-          i18nKeyPrefix="btns"
-        />
+        <Stack direction="horizontal" gap={3}>
+          <QueryGroup
+            data={answerFilterItems}
+            currentSort={curFilter}
+            sortKey="status"
+            i18nKeyPrefix="btns"
+          />
+          {curFilter === 'deleted' ? (
+            <Button
+              variant="outline-danger"
+              size="sm"
+              onClick={() => handleDeletePermanently()}>
+              {t('deleted_permanently', { keyPrefix: 'btns' })}
+            </Button>
+          ) : null}
+        </Stack>
 
         <Form.Control
           value={curQuery}
diff --git a/ui/src/pages/Admin/Questions/index.tsx 
b/ui/src/pages/Admin/Questions/index.tsx
index 0e6442cf..116984b2 100644
--- a/ui/src/pages/Admin/Questions/index.tsx
+++ b/ui/src/pages/Admin/Questions/index.tsx
@@ -18,7 +18,7 @@
  */
 
 import { FC } from 'react';
-import { Form, Table, Stack } from 'react-bootstrap';
+import { Form, Table, Stack, Button } from 'react-bootstrap';
 import { Link, useSearchParams } from 'react-router-dom';
 import { useTranslation } from 'react-i18next';
 
@@ -31,11 +31,13 @@ import {
   BaseUserCard,
   Empty,
   QueryGroup,
+  Modal,
 } from '@/components';
 import { ADMIN_LIST_STATUS } from '@/common/constants';
 import * as Type from '@/common/interface';
-import { useQuestionSearch } from '@/services';
+import { deletePermanently, useQuestionSearch } from '@/services';
 import { pathFactory } from '@/router/pathFactory';
+import { toastStore } from '@/stores';
 
 import Action from './components/Action';
 
@@ -66,6 +68,24 @@ const Questions: FC = () => {
   });
   const count = listData?.count || 0;
 
+  const handleDeletePermanently = () => {
+    Modal.confirm({
+      title: t('title', { keyPrefix: 'delete_permanently' }),
+      content: t('content', { keyPrefix: 'delete_permanently' }),
+      cancelBtnVariant: 'link',
+      confirmText: t('ok', { keyPrefix: 'btns' }),
+      onConfirm: () => {
+        deletePermanently('questions').then(() => {
+          toastStore.getState().show({
+            msg: t('posts_deleted', { keyPrefix: 'messages' }),
+            variant: 'success',
+          });
+          refreshList();
+        });
+      },
+    });
+  };
+
   const handleFilter = (e) => {
     urlSearchParams.set('query', e.target.value);
     urlSearchParams.delete('page');
@@ -75,12 +95,22 @@ const Questions: FC = () => {
     <>
       <h3 className="mb-4">{t('page_title')}</h3>
       <div className="d-flex flex-wrap justify-content-between 
align-items-center mb-3">
-        <QueryGroup
-          data={questionFilterItems}
-          currentSort={curFilter}
-          sortKey="status"
-          i18nKeyPrefix="btns"
-        />
+        <Stack direction="horizontal" gap={3}>
+          <QueryGroup
+            data={questionFilterItems}
+            currentSort={curFilter}
+            sortKey="status"
+            i18nKeyPrefix="btns"
+          />
+          {curFilter === 'deleted' ? (
+            <Button
+              variant="outline-danger"
+              size="sm"
+              onClick={() => handleDeletePermanently()}>
+              {t('deleted_permanently', { keyPrefix: 'btns' })}
+            </Button>
+          ) : null}
+        </Stack>
 
         <Form.Control
           value={curQuery}
diff --git a/ui/src/pages/Admin/Users/index.tsx 
b/ui/src/pages/Admin/Users/index.tsx
index 40152200..f97a4b5e 100644
--- a/ui/src/pages/Admin/Users/index.tsx
+++ b/ui/src/pages/Admin/Users/index.tsx
@@ -30,6 +30,7 @@ import {
   BaseUserCard,
   Empty,
   QueryGroup,
+  Modal,
 } from '@/components';
 import * as Type from '@/common/interface';
 import { useUserModal } from '@/hooks';
@@ -40,6 +41,7 @@ import {
   getAdminUcAgent,
   AdminUcAgent,
   changeUserStatus,
+  deletePermanently,
 } from '@/services';
 import { formatCount } from '@/utils';
 
@@ -151,6 +153,24 @@ const Users: FC = () => {
     });
   };
 
+  const handleDeletePermanently = () => {
+    Modal.confirm({
+      title: t('title', { keyPrefix: 'delete_permanently' }),
+      content: t('content', { keyPrefix: 'delete_permanently' }),
+      cancelBtnVariant: 'link',
+      confirmText: t('ok', { keyPrefix: 'btns' }),
+      onConfirm: () => {
+        deletePermanently('users').then(() => {
+          toastStore.getState().show({
+            msg: t('users_deleted', { keyPrefix: 'messages' }),
+            variant: 'success',
+          });
+          refreshUsers();
+        });
+      },
+    });
+  };
+
   const showAddUser =
     !ucAgent?.enabled || (ucAgent?.enabled && adminUcAgent?.allow_create_user);
   const showActionPassword =
@@ -177,6 +197,14 @@ const Users: FC = () => {
             sortKey="filter"
             i18nKeyPrefix="admin.users"
           />
+          {curFilter === 'deleted' ? (
+            <Button
+              variant="outline-danger"
+              size="sm"
+              onClick={() => handleDeletePermanently()}>
+              {t('deleted_permanently', { keyPrefix: 'btns' })}
+            </Button>
+          ) : null}
           {showAddUser ? (
             <Button
               variant="outline-primary"
diff --git a/ui/src/services/common.ts b/ui/src/services/common.ts
index 4ab940a2..cac34b4f 100644
--- a/ui/src/services/common.ts
+++ b/ui/src/services/common.ts
@@ -342,3 +342,7 @@ export const questionOperation = (params: 
Type.QuestionOperationReq) => {
 export const getPluginsStatus = () => {
   return request.get<Type.ActivatedPlugin[]>('/answer/api/v1/plugin/status');
 };
+
+export const deletePermanently = (type: string) => {
+  return request.delete('/answer/admin/api/delete/permanently', { type });
+};

Reply via email to