Jelajahi Sumber

Merge pull request #33 from weseek/feat/plugin-delete

feat: We can delete plugins on each plugin cards
ryoji-s 3 tahun lalu
induk
melakukan
878f1c31ce

+ 40 - 2
packages/app/src/components/Admin/PluginsExtension/PluginCard.tsx

@@ -3,6 +3,7 @@ import React, { useState } from 'react';
 
 import Link from 'next/link';
 
+import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { apiv3Post } from '~/client/util/apiv3-client';
 import { useSWRxPlugin } from '~/stores/plugin';
 
@@ -36,9 +37,11 @@ export const PluginCard = (props: Props): JSX.Element => {
       try {
         const res = await apiv3Post(reqUrl, { _id: id });
         setState(res.data.isEnabled);
+        const pluginState = !isEnabled ? 'Enabled' : 'Disabled';
+        toastSuccess(`${pluginState} Plugin `);
       }
       catch (err) {
-        console.log('pluginIsEnabled', err);
+        toastError('pluginIsEnabled', err);
       }
       finally {
         mutate();
@@ -63,6 +66,36 @@ export const PluginCard = (props: Props): JSX.Element => {
     );
   };
 
+  const PluginDeleteButton = (): JSX.Element => {
+
+    const onClickPluginDeleteBtnHandler = async() => {
+      const reqUrl = '/plugins-extension/deleted';
+
+      try {
+        await apiv3Post(reqUrl, { _id: id, name });
+        toastSuccess(`${name} Deleted`);
+      }
+      catch (err) {
+        toastError('pluginDelete', err);
+      }
+      finally {
+        mutate();
+      }
+    };
+
+    return (
+      <div className="">
+        <button
+          type="submit"
+          className="btn btn-primary"
+          onClick={() => onClickPluginDeleteBtnHandler()}
+        >
+          Delete
+        </button>
+      </div>
+    );
+  };
+
   return (
     <div className="card shadow border-0" key={name}>
       <div className="card-body px-5 py-4 mt-3">
@@ -74,7 +107,12 @@ export const PluginCard = (props: Props): JSX.Element => {
             <p className="card-text text-muted">{description}</p>
           </div>
           <div className='col-3'>
-            <PluginCardButton />
+            <div>
+              <PluginCardButton />
+            </div>
+            <div className="mt-4">
+              <PluginDeleteButton />
+            </div>
           </div>
         </div>
         <div className="row">

+ 7 - 1
packages/app/src/components/Admin/PluginsExtension/PluginInstallerForm.tsx

@@ -2,8 +2,11 @@ import React, { useCallback } from 'react';
 
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { apiv3Post } from '~/client/util/apiv3-client';
+import { useSWRxPlugins } from '~/stores/plugin';
 // TODO: i18n
 export const PluginInstallerForm = (): JSX.Element => {
+  const { mutate } = useSWRxPlugins();
+
   const submitHandler = useCallback(async(e) => {
     e.preventDefault();
 
@@ -29,7 +32,10 @@ export const PluginInstallerForm = (): JSX.Element => {
       toastError(err);
       // logger.error(err);
     }
-  }, []);
+    finally {
+      mutate();
+    }
+  }, [mutate]);
 
   return (
     <form role="form" onSubmit={submitHandler}>

+ 3 - 0
packages/app/src/components/Admin/PluginsExtension/PluginsExtensionPageContents.tsx

@@ -52,6 +52,9 @@ export const PluginsExtensionPageContents = (): JSX.Element => {
             </button>
           </h2>
           <div className="d-grid gap-5">
+            { data?.data?.plugins.length === 0 && (
+              <div>Plugin is not installed</div>
+            )}
             { data?.data?.plugins.map((item) => {
               const pluginId = item[0]._id;
               const pluginName = item[0].meta.name;

+ 14 - 0
packages/app/src/server/routes/apiv3/plugins-extension.ts

@@ -66,5 +66,19 @@ module.exports = (crowi: Crowi) => {
     }
   });
 
+  router.post('/deleted', async(req: any, res: ApiV3Response) => {
+    if (pluginService == null) {
+      return res.apiv3Err(400);
+    }
+
+    try {
+      await pluginService.pluginDeleted(req.body._id, req.body.name);
+      return res.apiv3();
+    }
+    catch (err) {
+      return res.apiv3Err(err, 400);
+    }
+  });
+
   return router;
 };

+ 14 - 0
packages/app/src/server/service/plugin.ts

@@ -164,4 +164,18 @@ export class PluginService {
     return growiPlugins[0].isEnabled;
   }
 
+  /**
+   * Delete plugin
+   */
+  async pluginDeleted(targetPluginId: string, targetPluginName: string): Promise<any> {
+    const GrowiPlugin = mongoose.model<GrowiPlugin>('GrowiPlugin');
+    const growiPlugins = await GrowiPlugin.find({ _id: targetPluginId });
+    growiPlugins[0].remove();
+    // TODO: Check remove
+    const ghOrganizationName = 'weseek';
+    const unzipTargetPath = path.join(pluginStoringPath, ghOrganizationName);
+    execSync(`rm -rf ${unzipTargetPath}/${targetPluginName}`);
+    return [];
+  }
+
 }