From 82a13ceb6622b4ffc21e8496a8f08368d1d3a49d Mon Sep 17 00:00:00 2001
From: felinae98 <731499577@qq.com>
Date: Mon, 29 Nov 2021 12:10:54 +0800
Subject: [PATCH] finish first admin frontend

---
 admin-frontend/package.json                   |  2 +-
 admin-frontend/src/api/config.ts              |  5 ++
 .../addSubsModal.tsx}                         | 64 +++++++++++++------
 admin-frontend/src/component/inputTag.tsx     |  5 ++
 .../src/component/subscribeCard.tsx           | 16 +++--
 .../src/pages/admin/configPage/index.tsx      |  8 +--
 .../nonebot_bison/admin_page/__init__.py      |  9 ++-
 src/plugins/nonebot_bison/admin_page/api.py   | 12 ++++
 src/plugins/nonebot_bison/config.py           | 21 +++++-
 tests/test_config_manager.py                  | 12 ++++
 10 files changed, 123 insertions(+), 31 deletions(-)
 rename admin-frontend/src/{pages/admin/configPage/AddSubsModal.tsx => component/addSubsModal.tsx} (74%)

diff --git a/admin-frontend/package.json b/admin-frontend/package.json
index dd0e164..dddd2ee 100644
--- a/admin-frontend/package.json
+++ b/admin-frontend/package.json
@@ -3,7 +3,7 @@
   "version": "0.1.0",
   "private": true,
   "homepage": "bison",
-  "proxy": "http://localhost:8080",
+  "proxy": "http://127.0.0.1:8080",
   "dependencies": {
     "@ant-design/icons": "^4.6.4",
     "@testing-library/jest-dom": "^5.11.4",
diff --git a/admin-frontend/src/api/config.ts b/admin-frontend/src/api/config.ts
index ea88904..3286f74 100644
--- a/admin-frontend/src/api/config.ts
+++ b/admin-frontend/src/api/config.ts
@@ -31,3 +31,8 @@ export async function delSubscribe(groupNumber: string, platformName: string, ta
   const res = await axios.delete(`${baseUrl}subs`, {params: {groupNumber, platformName, target}});
   return res.data;
 }
+
+export async function updateSubscribe(groupNumber: string, req: SubscribeConfig) {
+  return axios.patch(`${baseUrl}subs`, req, {params: {groupNumber}})
+    .then(res => res.data);
+}
diff --git a/admin-frontend/src/pages/admin/configPage/AddSubsModal.tsx b/admin-frontend/src/component/addSubsModal.tsx
similarity index 74%
rename from admin-frontend/src/pages/admin/configPage/AddSubsModal.tsx
rename to admin-frontend/src/component/addSubsModal.tsx
index 22f70cf..a1238d8 100644
--- a/admin-frontend/src/pages/admin/configPage/AddSubsModal.tsx
+++ b/admin-frontend/src/component/addSubsModal.tsx
@@ -1,9 +1,9 @@
-import { Form, Input, Modal, Select, Tag } from 'antd';
-import React, { useContext, useState } from "react";
-import { addSubscribe, getTargetName } from 'src/api/config';
-import { InputTag } from 'src/component/inputTag';
-import { GlobalConfContext } from "src/utils/context";
-import { CategoryConfig } from 'src/utils/type';
+import {Form, Input, Modal, Select, Tag} from 'antd';
+import React, {useContext, useEffect, useState} from "react";
+import {addSubscribe, getTargetName, updateSubscribe} from 'src/api/config';
+import {InputTag} from 'src/component/inputTag';
+import {GlobalConfContext} from "src/utils/context";
+import {CategoryConfig, SubscribeConfig} from 'src/utils/type';
 
 interface InputTagCustomProp {
   value?: Array<string>,
@@ -18,6 +18,11 @@ function InputTagCustom(prop: InputTagCustomProp) {
       prop.onChange(newVal);
     }
   }
+  useEffect(() => {
+    if (prop.value) {
+      setValue(prop.value);
+    }
+  }, [prop.value])
   return (
     <>
       {
@@ -38,14 +43,18 @@ interface AddModalProp {
   groupNumber: string,
   setShowModal: (s: boolean) => void,
   refresh: () => void
+  initVal?: SubscribeConfig
 }
-export function AddModal(prop: AddModalProp) {
+export function AddModal({
+  showModal, groupNumber, setShowModal, refresh, initVal
+}: AddModalProp) {
   const [ confirmLoading, setConfirmLoading ] = useState<boolean>(false);
   const { platformConf } = useContext(GlobalConfContext);
   const [ hasTarget, setHasTarget ] = useState(false);
   const [ categories, setCategories ] = useState({} as CategoryConfig);
   const [ enabledTag, setEnableTag ] = useState(false);
   const [ form ] = Form.useForm();
+  const [ inited, setInited ] = useState(false);
   const changePlatformSelect = (platform: string) => {
     setHasTarget(_ => platformConf[platform].hasTarget);
     setCategories(_ => platformConf[platform].categories);
@@ -74,23 +83,42 @@ export function AddModal(prop: AddModalProp) {
     if (newVal.target === '') {
       newVal.target = 'default'
     }
-    addSubscribe(prop.groupNumber, newVal)
-    .then(() => {
-      setConfirmLoading(false);
-      prop.setShowModal(false);
-      prop.refresh();
-      });
+    if (initVal) { // patch
+      updateSubscribe(groupNumber, newVal)
+        .then(() => {
+          setConfirmLoading(false);
+          setShowModal(false);
+          form.resetFields();
+          refresh();
+        });
+    } else {
+      addSubscribe(groupNumber, newVal)
+      .then(() => {
+        setConfirmLoading(false);
+        setShowModal(false);
+        form.resetFields();
+        refresh();
+        });
+    }
   }
   const handleModleFinish = () => {
     form.submit();
     setConfirmLoading(() => true);
   }
-
-  return <Modal title="添加订阅" visible={prop.showModal} 
-    confirmLoading={confirmLoading} onCancel={() => prop.setShowModal(false)}
+  useEffect(() => {
+    if (initVal && !inited) {
+      const platformName = initVal.platformName;
+      setHasTarget(platformConf[platformName].hasTarget);
+      setCategories(platformConf[platformName].categories);
+      setEnableTag(platformConf[platformName].enabledTag);
+      setInited(true)
+      form.setFieldsValue(initVal)
+    }
+  }, [initVal, form, platformConf, inited])
+  return <Modal title="添加订阅" visible={showModal} 
+    confirmLoading={confirmLoading} onCancel={() => setShowModal(false)}
     onOk={handleModleFinish}>
-    <Form form={form} labelCol={{ span: 6 }} name="b" onFinish={handleSubmit}
-      initialValues={{tags: [], cats: []}}>
+    <Form form={form} labelCol={{ span: 6 }} name="b" onFinish={handleSubmit} >
       <Form.Item label="平台" name="platformName" rules={[]}>
         <Select style={{ width: '80%' }} onChange={changePlatformSelect}>
           {Object.keys(platformConf).map(platformName => 
diff --git a/admin-frontend/src/component/inputTag.tsx b/admin-frontend/src/component/inputTag.tsx
index 140e46a..9408a61 100644
--- a/admin-frontend/src/component/inputTag.tsx
+++ b/admin-frontend/src/component/inputTag.tsx
@@ -18,6 +18,11 @@ export function InputTag(prop: InputTagProp) {
   const [ editInputValue, setEditInputValue ] = useState('');
   const inputRef = useRef(null as any);
   const editInputRef = useRef(null as any);
+  useEffect(() => {
+    if (prop.value) {
+      setValue(prop.value);
+    }
+  }, [prop.value])
   useEffect(() => {
     if (inputVisible) {
       inputRef.current.focus()
diff --git a/admin-frontend/src/component/subscribeCard.tsx b/admin-frontend/src/component/subscribeCard.tsx
index 62dbb1b..1f5b407 100644
--- a/admin-frontend/src/component/subscribeCard.tsx
+++ b/admin-frontend/src/component/subscribeCard.tsx
@@ -1,10 +1,11 @@
-import {CopyOutlined, DeleteOutlined} from '@ant-design/icons';
+import {CopyOutlined, DeleteOutlined, EditOutlined} from '@ant-design/icons';
 import {Card, Col, Form, message, Popconfirm, Select, Tag, Tooltip} from 'antd';
 import Modal from 'antd/lib/modal/Modal';
 import React, {useContext, useState} from "react";
 import {addSubscribe, delSubscribe} from 'src/api/config';
 import {GlobalConfContext} from "src/utils/context";
 import {PlatformConfig, SubscribeConfig, SubscribeResp} from 'src/utils/type';
+import {AddModal} from './addSubsModal';
 
 interface CopyModalProp {
   setShowModal: (modalShow: boolean) => void
@@ -30,6 +31,7 @@ function CopyModal({setShowModal,config,
       setConfirmLoading(true)
       postReqs(selectedGroups, config).then(() => {
         setConfirmLoading(false)
+        setShowModal(false)
         return reload() 
       })
     }
@@ -57,6 +59,7 @@ interface SubscribeCardProp {
 export function SubscribeCard({groupNumber, config, reload, groupSubscribes}: SubscribeCardProp) {
   const globalConf = useContext(GlobalConfContext);
   const [showModal, setShowModal] = useState(false)
+  const [showEditModal, setShowEditModal] = useState(false)
   const platformConf = globalConf.platformConf[config.platformName] as PlatformConfig;
   const handleDelete = (groupNumber: string, platformName: string, target: string) => () => {
     delSubscribe(groupNumber, platformName, target).then(() => {
@@ -67,13 +70,16 @@ export function SubscribeCard({groupNumber, config, reload, groupSubscribes}: Su
   <Col span={6} key={`${config.platformName}-${config.target}`}> 
     <Card title={`${platformConf.name} - ${config.targetName}`}
       actions={[
+        <Tooltip title="编辑">
+          <EditOutlined onClick={()=>{setShowEditModal(state => !state)}}/>
+        </Tooltip>,
+        <Tooltip title="添加到其他群">
+          <CopyOutlined onClick={()=>{setShowModal(state => !state)}}/>
+        </Tooltip>,
         <Popconfirm title={`确定要删除 ${platformConf.name} - ${config.targetName}`}
           onConfirm={handleDelete(groupNumber, config.platformName, config.target || 'default')}>
           <Tooltip title="删除" ><DeleteOutlined /></Tooltip>
         </Popconfirm>, 
-        <Tooltip title="添加到其他群">
-          <CopyOutlined onClick={()=>{setShowModal(state => !state)}}/>
-        </Tooltip>
         ]}>
       <Form labelCol={{ span: 6 }}>
       <Form.Item label="订阅类型">
@@ -89,6 +95,8 @@ export function SubscribeCard({groupNumber, config, reload, groupSubscribes}: Su
     </Card>
     <CopyModal setShowModal={setShowModal} reload={reload} currentGroupNumber={groupNumber}
       showModal={showModal} config={config} groups={groupSubscribes}/>
+    <AddModal showModal={showEditModal} setShowModal={setShowEditModal} 
+      groupNumber={groupNumber} refresh={reload} initVal={config}/>
   </Col>
   )
 }
diff --git a/admin-frontend/src/pages/admin/configPage/index.tsx b/admin-frontend/src/pages/admin/configPage/index.tsx
index a6cfacc..66b1160 100644
--- a/admin-frontend/src/pages/admin/configPage/index.tsx
+++ b/admin-frontend/src/pages/admin/configPage/index.tsx
@@ -3,7 +3,7 @@ import React, {ReactElement, useEffect, useState} from "react";
 import {getSubscribe} from 'src/api/config';
 import {SubscribeCard} from 'src/component/subscribeCard';
 import {SubscribeResp} from 'src/utils/type';
-import {AddModal} from './AddSubsModal';
+import {AddModal} from 'src/component/addSubsModal';
 
 interface ConfigPageProp {
   tab: string
@@ -34,11 +34,11 @@ export function ConfigPage(prop: ConfigPageProp) {
     for (let key of Object.keys(configData)) {
       let value = configData[key];
       groups.push(
-        <Collapse.Panel header={
+        <Collapse.Panel key={key} header={
           <span>{`${key} - ${value.name}`}<Button style={{float: "right"}} onClick={clickNew(key)}>添加</Button></span>
-          } key={key}>
+          }>
           <Row gutter={{ xs: 8, sm: 16, md: 24, lg: 32 }} align="middle">
-            {value.subscribes.map((subs) => <SubscribeCard
+            {value.subscribes.map((subs, idx) => <SubscribeCard key={idx}
               groupNumber={key} config={subs} groupSubscribes={configData} reload={loadData}
               />)}
           </Row>
diff --git a/src/plugins/nonebot_bison/admin_page/__init__.py b/src/plugins/nonebot_bison/admin_page/__init__.py
index 4a3e845..f880516 100644
--- a/src/plugins/nonebot_bison/admin_page/__init__.py
+++ b/src/plugins/nonebot_bison/admin_page/__init__.py
@@ -18,7 +18,7 @@ import functools
 
 from starlette.requests import Request
 
-from .api import del_group_sub, test, get_global_conf, auth, get_subs_info, get_target_name, add_group_sub
+from .api import del_group_sub, test, get_global_conf, auth, get_subs_info, get_target_name, add_group_sub, update_group_sub
 from .token_manager import token_manager as tm
 from .jwt import load_jwt
 from ..plugin_config import plugin_config
@@ -72,7 +72,7 @@ def register_router_fastapi(driver: Driver, socketio):
         platformName: str
         target: str
         targetName: str
-        cats: list[str]
+        cats: list[int]
         tags: list[str]
 
     app = driver.server_app
@@ -91,7 +91,10 @@ def register_router_fastapi(driver: Driver, socketio):
     async def _add_group_subs(groupNumber: str, req: AddSubscribeReq):
         return await add_group_sub(group_number=groupNumber, platform_name=req.platformName,
                 target=req.target, target_name=req.targetName, cats=req.cats, tags=req.tags)
-
+    @app.patch(SUBSCRIBE_URL, dependencies=[Depends(check_group_permission)])
+    async def _update_group_subs(groupNumber: str, req: AddSubscribeReq):
+        return await update_group_sub(group_number=groupNumber, platform_name=req.platformName,
+                target=req.target, target_name=req.targetName, cats=req.cats, tags=req.tags)
     @app.delete(SUBSCRIBE_URL, dependencies=[Depends(check_group_permission)])
     async def _del_group_subs(groupNumber: str, target: str, platformName: str):
         return await del_group_sub(groupNumber, platformName, target)
diff --git a/src/plugins/nonebot_bison/admin_page/api.py b/src/plugins/nonebot_bison/admin_page/api.py
index dcedbce..e23a669 100644
--- a/src/plugins/nonebot_bison/admin_page/api.py
+++ b/src/plugins/nonebot_bison/admin_page/api.py
@@ -100,3 +100,15 @@ async def del_group_sub(group_number: str, platform_name: str, target: str):
     except (NoSuchUserException, NoSuchSubscribeException):
         return { 'status': 400, 'msg': '删除错误' }
     return { 'status': 200, 'msg': '' }
+
+
+async def update_group_sub(group_number: str, platform_name: str, target: str, 
+        target_name: str, cats: list[str], tags: list[str]):
+    config = Config()
+    try:
+        config.update_subscribe(int(group_number), 'group',
+                target, target_name, platform_name, cats, tags)
+    except (NoSuchUserException, NoSuchSubscribeException):
+        return { 'status': 400, 'msg': '更新错误' }
+    return { 'status': 200, 'msg': '' }
+
diff --git a/src/plugins/nonebot_bison/config.py b/src/plugins/nonebot_bison/config.py
index d34af47..3afa955 100644
--- a/src/plugins/nonebot_bison/config.py
+++ b/src/plugins/nonebot_bison/config.py
@@ -6,10 +6,10 @@ from typing import DefaultDict, Mapping
 import nonebot
 from tinydb import Query, TinyDB
 
+from .platform import platform_manager
 from .plugin_config import plugin_config
 from .types import Target, User
 from .utils import Singleton
-from .platform import platform_manager
 
 supported_target_type = platform_manager.keys()
 
@@ -86,6 +86,25 @@ class Config(metaclass=Singleton):
                 return
         raise NoSuchSubscribeException()
 
+    def update_subscribe(self, user, user_type, target, target_name, target_type, cats, tags):
+        user_query = Query()
+        query = (user_query.user == user) & (user_query.user_type == user_type)
+        if (user_data := self.user_target.get(query)):
+            # update
+            subs: list = user_data.get('subs', [])
+            find_flag = False
+            for item in subs:
+                if item['target'] == target and item['target_type'] == target_type:
+                    item['target_name'], item['cats'], item['tags'] = \
+                            target_name, cats, tags
+                    find_flag = True
+                    break
+            if not find_flag:
+                raise NoSuchSubscribeException()
+            self.user_target.update({"subs": subs}, query)
+        else:
+            raise NoSuchUserException()
+
     def update_send_cache(self):
         res = {target_type: defaultdict(list) for target_type in supported_target_type}
         cat_res = {target_type: defaultdict(lambda: defaultdict(list)) for target_type in supported_target_type}
diff --git a/tests/test_config_manager.py b/tests/test_config_manager.py
index cff94fa..5ae7fe2 100644
--- a/tests/test_config_manager.py
+++ b/tests/test_config_manager.py
@@ -24,3 +24,15 @@ def test_create_and_get(config: 'nonebot_bison.config.Config', plugin_module: 'n
     assert(len(confs) == 1)
     assert(config.target_user_cache['weibo']['weibo_id'] == \
             [plugin_module.types.User('123', 'group')])
+    assert(confs[0]['cats'] == [])
+    config.update_subscribe(
+            user='123',
+            user_type='group',
+            target='weibo_id',
+            target_name='weibo_name',
+            target_type='weibo',
+            cats=['1'],
+            tags=[])
+    confs = config.list_subscribe('123', 'group')
+    assert(len(confs) == 1)
+    assert(confs[0]['cats'] == ['1'])