jam411 3 лет назад
Родитель
Сommit
23f2f88787

+ 20 - 13
packages/app/src/components/Admin/PluginsExtension/PluginCard.tsx

@@ -2,7 +2,7 @@ import React, { useState } from 'react';
 
 import Link from 'next/link';
 
-import { apiv3Post } from '~/client/util/apiv3-client';
+import { apiv3Delete, apiv3Put } from '~/client/util/apiv3-client';
 import { toastSuccess, toastError } from '~/client/util/toastr';
 import { useSWRxPlugin } from '~/stores/plugin';
 
@@ -12,13 +12,13 @@ type Props = {
   id: string,
   name: string,
   url: string,
-  description: string,
+  desc?: string,
 }
 
 export const PluginCard = (props: Props): JSX.Element => {
 
   const {
-    id, name, url, description,
+    id, name, url, desc,
   } = props;
 
   const { data, mutate } = useSWRxPlugin(id);
@@ -28,16 +28,22 @@ export const PluginCard = (props: Props): JSX.Element => {
   }
 
   const PluginCardButton = (): JSX.Element => {
-    const [isEnabled, setState] = useState<boolean>(data.data.isEnabled);
+    const [isEnabled, setState] = useState<boolean>(data.isEnabled);
 
     const onChangeHandler = async() => {
-      const reqUrl = '/plugins/switch-isenabled';
-
       try {
-        const res = await apiv3Post(reqUrl, { _id: id });
-        setState(res.data.isEnabled);
-        const pluginState = !isEnabled ? 'Enabled' : 'Disabled';
-        toastSuccess(`${pluginState} Plugin `);
+        if (!isEnabled) {
+          const reqUrl = `/plugins/${id}/activate`;
+          const res = await apiv3Put(reqUrl);
+          setState(res.data.isEnabled);
+          toastSuccess('Plugin Activated');
+        }
+        else {
+          const reqUrl = `/plugins/${id}/deactivate`;
+          const res = await apiv3Put(reqUrl);
+          setState(res.data.isEnabled);
+          toastSuccess('Plugin Deactivated');
+        }
       }
       catch (err) {
         toastError('pluginIsEnabled', err);
@@ -68,10 +74,10 @@ export const PluginCard = (props: Props): JSX.Element => {
   const PluginDeleteButton = (): JSX.Element => {
 
     const onClickPluginDeleteBtnHandler = async() => {
-      const reqUrl = '/plugins/deleted';
+      const reqUrl = `/plugins/${id}/remove`;
 
       try {
-        await apiv3Post(reqUrl, { _id: id, name });
+        await apiv3Delete(reqUrl);
         toastSuccess(`${name} Deleted`);
       }
       catch (err) {
@@ -95,6 +101,7 @@ export const PluginCard = (props: Props): JSX.Element => {
     );
   };
 
+  // TODO: Fix commented out areas.
   return (
     <div className="card shadow border-0" key={name}>
       <div className="card-body px-5 py-4 mt-3">
@@ -103,7 +110,7 @@ export const PluginCard = (props: Props): JSX.Element => {
             <h2 className="card-title h3 border-bottom pb-2 mb-3">
               <Link href={`${url}`}>{name}</Link>
             </h2>
-            <p className="card-text text-muted">{description}</p>
+            <p className="card-text text-muted">{desc}</p>
           </div>
           <div className='col-3'>
             <div>

+ 8 - 9
packages/app/src/components/Admin/PluginsExtension/PluginsExtensionPageContents.tsx

@@ -20,7 +20,6 @@ export const PluginsExtensionPageContents = (): JSX.Element => {
 
   return (
     <div>
-
       <div className="row mb-5">
         <div className="col-lg-12">
           <h2 className="admin-setting-header">Plugin Installer</h2>
@@ -35,25 +34,25 @@ export const PluginsExtensionPageContents = (): JSX.Element => {
               <i className="icon icon-reload"></i>
             </button>
           </h2>
-          {data?.data == null
+          {data == null
             ? <Loading />
             : (
               <div className="d-grid gap-5">
-                { data?.data?.plugins.length === 0 && (
+                { 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;
-                  const pluginUrl = item[0].origin.url;
-                  const pluginDiscription = item[0].meta.desc;
+                { data.plugins.map((plugin) => {
+                  const pluginId = plugin[0]._id;
+                  const pluginName = plugin[0].meta.name;
+                  const pluginUrl = plugin[0].origin.url;
+                  const pluginDiscription = plugin[0].meta.desc;
                   return (
                     <PluginCard
                       key={pluginId}
                       id={pluginId}
                       name={pluginName}
                       url={pluginUrl}
-                      description={pluginDiscription}
+                      desc={pluginDiscription}
                     />
                   );
                 })}

+ 3 - 1
packages/app/src/interfaces/plugin.ts

@@ -1,4 +1,4 @@
-import { GrowiThemeMetadata } from '@growi/core';
+import { GrowiThemeMetadata, HasObjectId } from '@growi/core';
 
 export const GrowiPluginResourceType = {
   Template: 'template',
@@ -31,3 +31,5 @@ export type GrowiPluginMeta = {
 export type GrowiThemePluginMeta = GrowiPluginMeta & {
   themes: GrowiThemeMetadata[]
 }
+
+export type GrowiPluginHasId = GrowiPlugin & HasObjectId;

+ 59 - 12
packages/app/src/server/routes/apiv3/plugins.ts

@@ -1,16 +1,34 @@
-import express, { Request } from 'express';
+import express, { Request, Router } from 'express';
+import { body, query } from 'express-validator';
+import mongoose from 'mongoose';
 
 import Crowi from '../../crowi';
 
 import { ApiV3Response } from './interfaces/apiv3-response';
 
-type PluginInstallerFormRequest = Request & { form: any };
 
-module.exports = (crowi: Crowi) => {
+const ObjectID = mongoose.Types.ObjectId;
+
+/*
+ * Validators
+ */
+const validator = {
+  pluginIdisRequired: [
+    query('id').isMongoId().withMessage('pluginId is required'),
+  ],
+  pluginFormValueisRequired: [
+    body('pluginInstallerForm').isString().withMessage('pluginFormValue is required'),
+  ],
+};
+
+module.exports = (crowi: Crowi): Router => {
+  const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
+  const adminRequired = require('../../middlewares/admin-required')(crowi);
+
   const router = express.Router();
   const { pluginService } = crowi;
 
-  router.get('/', async(req: any, res: any) => {
+  router.get('/', loginRequiredStrictly, adminRequired, async(req: Request, res: ApiV3Response) => {
     if (pluginService == null) {
       return res.apiv3Err(400);
     }
@@ -24,13 +42,32 @@ module.exports = (crowi: Crowi) => {
     }
   });
 
-  router.post('/', async(req: PluginInstallerFormRequest, res: ApiV3Response) => {
+  router.get('/:id', loginRequiredStrictly, adminRequired, validator.pluginIdisRequired, async(req: Request, res: ApiV3Response) => {
     if (pluginService == null) {
       return res.apiv3Err(400);
     }
 
+    const { id } = req.params;
+    const pluginId = new ObjectID(id);
+
     try {
-      await pluginService.install(req.body.pluginInstallerForm);
+      const data = await pluginService.getPluginIsEnabled(pluginId);
+      return res.apiv3({ isEnabled: data });
+    }
+    catch (err) {
+      return res.apiv3Err(err);
+    }
+  });
+
+  router.post('/', loginRequiredStrictly, adminRequired, validator.pluginFormValueisRequired, async(req: Request, res: ApiV3Response) => {
+    if (pluginService == null) {
+      return res.apiv3Err(400);
+    }
+
+    const { pluginInstallerForm: formValue } = req.body;
+
+    try {
+      await pluginService.install(formValue);
       return res.apiv3({});
     }
     catch (err) {
@@ -38,13 +75,16 @@ module.exports = (crowi: Crowi) => {
     }
   });
 
-  router.post('/get-isenabled', async(req: any, res: ApiV3Response) => {
+  router.put('/:id/activate', loginRequiredStrictly, adminRequired, validator.pluginIdisRequired, async(req: Request, res: ApiV3Response) => {
     if (pluginService == null) {
       return res.apiv3Err(400);
     }
 
+    const { id } = req.params;
+    const pluginId = new ObjectID(id);
+
     try {
-      const pluginIsEnabled = await pluginService.getPluginIsEnabled(req.body._id);
+      const pluginIsEnabled = await pluginService.switchPluginIsEnabled(pluginId);
       return res.apiv3({ isEnabled: pluginIsEnabled });
     }
     catch (err) {
@@ -52,13 +92,17 @@ module.exports = (crowi: Crowi) => {
     }
   });
 
-  router.post('/switch-isenabled', async(req: any, res: ApiV3Response) => {
+  router.put('/:id/deactivate', loginRequiredStrictly, adminRequired, validator.pluginIdisRequired, async(req: Request, res: ApiV3Response) => {
+
     if (pluginService == null) {
       return res.apiv3Err(400);
     }
 
+    const { id } = req.params;
+    const pluginId = new ObjectID(id);
+
     try {
-      const pluginIsEnabled = await pluginService.switchPluginIsEnabled(req.body._id);
+      const pluginIsEnabled = await pluginService.switchPluginIsEnabled(pluginId);
       return res.apiv3({ isEnabled: pluginIsEnabled });
     }
     catch (err) {
@@ -66,13 +110,16 @@ module.exports = (crowi: Crowi) => {
     }
   });
 
-  router.post('/deleted', async(req: any, res: ApiV3Response) => {
+  router.delete('/:id/remove', loginRequiredStrictly, adminRequired, validator.pluginIdisRequired, async(req: Request, res: ApiV3Response) => {
     if (pluginService == null) {
       return res.apiv3Err(400);
     }
 
+    const { id } = req.params;
+    const pluginId = new ObjectID(id);
+
     try {
-      await pluginService.pluginDeleted(req.body._id);
+      await pluginService.pluginDeleted(pluginId);
       return res.apiv3();
     }
     catch (err) {

+ 6 - 38
packages/app/src/server/service/plugin.ts

@@ -213,20 +213,9 @@ export class PluginService implements IPluginService {
   /**
    * Get plugin isEnabled
    */
-  async getPluginIsEnabled(targetPluginId: string): Promise<boolean> {
-    const ObjectID = mongoose.Types.ObjectId;
-    const id = new ObjectID(targetPluginId);
-
-    const isValidObjectId = (id: string) => {
-      return ObjectID.isValid(id) && (new ObjectID(id).toString() === id);
-    };
-
-    if (!isValidObjectId(targetPluginId)) {
-      throw new Error('This is invalid value.');
-    }
-
+  async getPluginIsEnabled(pluginId: mongoose.Types.ObjectId): Promise<boolean> {
     const GrowiPlugin = mongoose.model<GrowiPlugin>('GrowiPlugin');
-    const growiPlugins = await GrowiPlugin.findById(id);
+    const growiPlugins = await GrowiPlugin.findById(pluginId);
 
     if (growiPlugins == null) {
       throw new Error('No plugin found for this ID.');
@@ -238,20 +227,9 @@ export class PluginService implements IPluginService {
   /**
    * Switch plugin enabled
    */
-  async switchPluginIsEnabled(targetPluginId: string): Promise<boolean> {
-    const ObjectID = mongoose.Types.ObjectId;
-    const id = new ObjectID(targetPluginId);
-
-    const isValidObjectId = (id: string) => {
-      return ObjectID.isValid(id) && (new ObjectID(id).toString() === id);
-    };
-
-    if (!isValidObjectId(targetPluginId)) {
-      throw new Error('This is invalid value.');
-    }
-
+  async switchPluginIsEnabled(pluginId: mongoose.Types.ObjectId): Promise<boolean> {
     const GrowiPlugin = mongoose.model<GrowiPlugin>('GrowiPlugin');
-    const growiPlugins = await GrowiPlugin.findById(id);
+    const growiPlugins = await GrowiPlugin.findById(pluginId);
 
     if (growiPlugins == null) {
       throw new Error('No plugin found for this ID.');
@@ -270,23 +248,13 @@ export class PluginService implements IPluginService {
   /**
    * Delete plugin
    */
-  async pluginDeleted(targetPluginId: string): Promise<void> {
-    const ObjectID = mongoose.Types.ObjectId;
-    const id = new ObjectID(targetPluginId);
-
-    const isValidObjectId = (id: string) => {
-      return ObjectID.isValid(id) && (new ObjectID(id).toString() === id);
-    };
+  async pluginDeleted(pluginId: mongoose.Types.ObjectId): Promise<void> {
     const deleteFolder = (path: fs.PathLike) => {
       fs.rmdir(path, { recursive: true }, (err) => { return err });
     };
 
-    if (!isValidObjectId(targetPluginId)) {
-      throw new Error('This is invalid value.');
-    }
-
     const GrowiPlugin = mongoose.model<GrowiPlugin>('GrowiPlugin');
-    const growiPlugins = await GrowiPlugin.findByIdAndRemove(id);
+    const growiPlugins = await GrowiPlugin.findByIdAndRemove(pluginId);
 
     if (growiPlugins == null) {
       throw new Error('No plugin found for this ID.');

+ 20 - 10
packages/app/src/stores/plugin.tsx

@@ -1,15 +1,24 @@
 
 import useSWR, { SWRResponse } from 'swr';
 
-import { apiv3Get, apiv3Post } from '~/client/util/apiv3-client';
+import { apiv3Get } from '~/client/util/apiv3-client';
+import { GrowiPluginHasId } from '~/interfaces/plugin';
+
+type plugins = {
+  plugins: GrowiPluginHasId[][]
+}
+
+type pluginIsEnalbed = {
+  isEnabled: boolean
+}
 
-// TODO: Correct types
 const pluginsFetcher = () => {
   return async() => {
     const reqUrl = '/plugins';
+
     try {
-      const data = await apiv3Get(reqUrl);
-      return data;
+      const res = await apiv3Get(reqUrl);
+      return res.data;
     }
     catch (err) {
       throw new Error(err);
@@ -17,16 +26,17 @@ const pluginsFetcher = () => {
   };
 };
 
-export const useSWRxPlugins = (): SWRResponse<any | null, Error> => {
-  return useSWR('/pluginsExtension', pluginsFetcher());
+export const useSWRxPlugins = (): SWRResponse<plugins | null, Error> => {
+  return useSWR('/plugins', pluginsFetcher());
 };
 
 const pluginFetcher = (id: string) => {
   return async() => {
-    const reqUrl = '/plugins/get-isenabled';
+    const reqUrl = `/plugins/${id}`;
+
     try {
-      const data = await apiv3Post(reqUrl, { _id: id });
-      return data;
+      const res = await apiv3Get(reqUrl);
+      return res.data;
     }
     catch (err) {
       throw new Error(err);
@@ -34,6 +44,6 @@ const pluginFetcher = (id: string) => {
   };
 };
 
-export const useSWRxPlugin = (_id: string): SWRResponse<any | null, Error> => {
+export const useSWRxPlugin = (_id: string): SWRResponse<pluginIsEnalbed | null, Error> => {
   return useSWR(`/plugin-${_id}`, pluginFetcher(_id));
 };