Просмотр исходного кода

Merge pull request #7765 from weseek/support/refactor-plugin-modules

support: Refactor plugin related modules
Yuki Takei 2 лет назад
Родитель
Сommit
1f05dbb0a0
27 измененных файлов с 90 добавлено и 130 удалено
  1. 0 2
      apps/app/src/features/activate-plugin/index.ts
  2. 0 1
      apps/app/src/features/activate-plugin/utils/index.ts
  3. 0 0
      apps/app/src/features/growi-plugin/components/Admin/PluginsExtensionPageContents/PluginCard.module.scss
  4. 0 0
      apps/app/src/features/growi-plugin/components/Admin/PluginsExtensionPageContents/PluginCard.tsx
  5. 2 1
      apps/app/src/features/growi-plugin/components/Admin/PluginsExtensionPageContents/PluginInstallerForm.tsx
  6. 1 1
      apps/app/src/features/growi-plugin/components/Admin/PluginsExtensionPageContents/PluginsExtensionPageContents.tsx
  7. 1 0
      apps/app/src/features/growi-plugin/components/Admin/PluginsExtensionPageContents/index.ts
  8. 1 1
      apps/app/src/features/growi-plugin/components/GrowiPluginsActivator.client.tsx
  9. 0 0
      apps/app/src/features/growi-plugin/components/index.ts
  10. 6 6
      apps/app/src/features/growi-plugin/interfaces/growi-plugin.ts
  11. 1 0
      apps/app/src/features/growi-plugin/interfaces/index.ts
  12. 16 15
      apps/app/src/features/growi-plugin/models/growi-plugin.ts
  13. 1 0
      apps/app/src/features/growi-plugin/models/index.ts
  14. 12 33
      apps/app/src/features/growi-plugin/routes/growi-plugins.ts
  15. 22 26
      apps/app/src/features/growi-plugin/services/growi-plugin.ts
  16. 1 0
      apps/app/src/features/growi-plugin/services/index.ts
  17. 3 2
      apps/app/src/features/growi-plugin/stores/growi-plugin.tsx
  18. 0 0
      apps/app/src/features/growi-plugin/utils/growi-facade-utils.client.ts
  19. 1 1
      apps/app/src/pages/[[...path]].page.tsx
  20. 3 3
      apps/app/src/pages/_document.page.tsx
  21. 1 1
      apps/app/src/pages/admin/plugins.page.tsx
  22. 5 19
      apps/app/src/server/crowi/index.js
  23. 6 8
      apps/app/src/server/routes/apiv3/customize-setting.js
  24. 1 1
      apps/app/src/server/routes/apiv3/index.js
  25. 2 5
      apps/app/src/server/service/customize.ts
  26. 1 1
      apps/app/src/stores/renderer.tsx
  27. 3 3
      apps/app/src/stores/template.tsx

+ 0 - 2
apps/app/src/features/activate-plugin/index.ts

@@ -1,2 +0,0 @@
-export * from './components';
-export * from './utils';

+ 0 - 1
apps/app/src/features/activate-plugin/utils/index.ts

@@ -1 +0,0 @@
-export { getGrowiFacade } from './growi-facade-utils';

+ 0 - 0
apps/app/src/components/Admin/PluginsExtension/PluginCard.module.scss → apps/app/src/features/growi-plugin/components/Admin/PluginsExtensionPageContents/PluginCard.module.scss


+ 0 - 0
apps/app/src/components/Admin/PluginsExtension/PluginCard.tsx → apps/app/src/features/growi-plugin/components/Admin/PluginsExtensionPageContents/PluginCard.tsx


+ 2 - 1
apps/app/src/components/Admin/PluginsExtension/PluginInstallerForm.tsx → apps/app/src/features/growi-plugin/components/Admin/PluginsExtensionPageContents/PluginInstallerForm.tsx

@@ -4,7 +4,8 @@ import { useTranslation } from 'next-i18next';
 
 import { apiv3Post } from '~/client/util/apiv3-client';
 import { toastSuccess, toastError } from '~/client/util/toastr';
-import { useSWRxPlugins } from '~/stores/plugin';
+
+import { useSWRxPlugins } from '../../../stores/growi-plugin';
 
 export const PluginInstallerForm = (): JSX.Element => {
   const { mutate } = useSWRxPlugins();

+ 1 - 1
apps/app/src/components/Admin/PluginsExtension/PluginsExtensionPageContents.tsx → apps/app/src/features/growi-plugin/components/Admin/PluginsExtensionPageContents/PluginsExtensionPageContents.tsx

@@ -3,7 +3,7 @@ import React from 'react';
 import { useTranslation } from 'next-i18next';
 import { Spinner } from 'reactstrap';
 
-import { useSWRxPlugins } from '~/stores/plugin';
+import { useSWRxPlugins } from '../../../stores/growi-plugin';
 
 import { PluginCard } from './PluginCard';
 import { PluginInstallerForm } from './PluginInstallerForm';

+ 1 - 0
apps/app/src/features/growi-plugin/components/Admin/PluginsExtensionPageContents/index.ts

@@ -0,0 +1 @@
+export * from './PluginsExtensionPageContents';

+ 1 - 1
apps/app/src/features/activate-plugin/components/GrowiPluginsActivator.client.tsx → apps/app/src/features/growi-plugin/components/GrowiPluginsActivator.client.tsx

@@ -1,6 +1,6 @@
 import { useEffect } from 'react';
 
-import { initializeGrowiFacade, registerGrowiFacade } from '../utils/growi-facade-utils';
+import { initializeGrowiFacade, registerGrowiFacade } from '../utils/growi-facade-utils.client';
 
 declare global {
   // eslint-disable-next-line vars-on-top, no-var

+ 0 - 0
apps/app/src/features/activate-plugin/components/index.ts → apps/app/src/features/growi-plugin/components/index.ts


+ 6 - 6
apps/app/src/interfaces/plugin.ts → apps/app/src/features/growi-plugin/interfaces/growi-plugin.ts

@@ -8,29 +8,29 @@ export const GrowiPluginResourceType = {
 } as const;
 export type GrowiPluginResourceType = typeof GrowiPluginResourceType[keyof typeof GrowiPluginResourceType];
 
-export type GrowiPluginOrigin = {
+export type IGrowiPluginOrigin = {
   url: string,
   ghBranch?: string,
   ghTag?: string,
 }
 
-export type GrowiPlugin<M extends GrowiPluginMeta = GrowiPluginMeta> = {
+export type IGrowiPlugin<M extends IGrowiPluginMeta = IGrowiPluginMeta> = {
   isEnabled: boolean,
   installedPath: string,
   organizationName: string,
-  origin: GrowiPluginOrigin,
+  origin: IGrowiPluginOrigin,
   meta: M,
 }
 
-export type GrowiPluginMeta = {
+export type IGrowiPluginMeta = {
   name: string,
   types: GrowiPluginResourceType[],
   desc?: string,
   author?: string,
 }
 
-export type GrowiThemePluginMeta = GrowiPluginMeta & {
+export type IGrowiThemePluginMeta = IGrowiPluginMeta & {
   themes: GrowiThemeMetadata[]
 }
 
-export type GrowiPluginHasId = GrowiPlugin & HasObjectId;
+export type IGrowiPluginHasId = IGrowiPlugin & HasObjectId;

+ 1 - 0
apps/app/src/features/growi-plugin/interfaces/index.ts

@@ -0,0 +1 @@
+export * from './growi-plugin';

+ 16 - 15
apps/app/src/server/models/growi-plugin.ts → apps/app/src/features/growi-plugin/models/growi-plugin.ts

@@ -1,19 +1,20 @@
 import { GrowiThemeMetadata, GrowiThemeSchemeType } from '@growi/core';
 import {
-  Schema, Model, Document, Types,
+  Schema, type Model, type Document, type Types,
 } from 'mongoose';
 
-import {
-  GrowiPlugin, GrowiPluginMeta, GrowiPluginOrigin, GrowiPluginResourceType, GrowiThemePluginMeta,
-} from '~/interfaces/plugin';
+import { getOrCreateModel } from '~/server/util/mongoose-utils';
 
-import { getOrCreateModel } from '../util/mongoose-utils';
+import { GrowiPluginResourceType } from '../interfaces';
+import type {
+  IGrowiPlugin, IGrowiPluginMeta, IGrowiPluginOrigin, IGrowiThemePluginMeta,
+} from '../interfaces';
 
-export interface GrowiPluginDocument extends GrowiPlugin, Document {
+export interface IGrowiPluginDocument extends IGrowiPlugin, Document {
 }
-export interface GrowiPluginModel extends Model<GrowiPluginDocument> {
-  findEnabledPlugins(): Promise<GrowiPlugin[]>
-  findEnabledPluginsIncludingAnyTypes(includingTypes: GrowiPluginResourceType[]): Promise<GrowiPlugin[]>
+export interface IGrowiPluginModel extends Model<IGrowiPluginDocument> {
+  findEnabledPlugins(): Promise<IGrowiPlugin[]>
+  findEnabledPluginsIncludingAnyTypes(includingTypes: GrowiPluginResourceType[]): Promise<IGrowiPlugin[]>
   activatePlugin(id: Types.ObjectId): Promise<string>
   deactivatePlugin(id: Types.ObjectId): Promise<string>
 }
@@ -32,7 +33,7 @@ const growiThemeMetadataSchema = new Schema<GrowiThemeMetadata>({
   accent: { type: String, required: true },
 });
 
-const growiPluginMetaSchema = new Schema<GrowiPluginMeta|GrowiThemePluginMeta>({
+const growiPluginMetaSchema = new Schema<IGrowiPluginMeta|IGrowiThemePluginMeta>({
   name: { type: String, required: true },
   types: {
     type: [String],
@@ -44,13 +45,13 @@ const growiPluginMetaSchema = new Schema<GrowiPluginMeta|GrowiThemePluginMeta>({
   themes: [growiThemeMetadataSchema],
 });
 
-const growiPluginOriginSchema = new Schema<GrowiPluginOrigin>({
+const growiPluginOriginSchema = new Schema<IGrowiPluginOrigin>({
   url: { type: String },
   ghBranch: { type: String },
   ghTag: { type: String },
 });
 
-const growiPluginSchema = new Schema<GrowiPluginDocument, GrowiPluginModel>({
+const growiPluginSchema = new Schema<IGrowiPluginDocument, IGrowiPluginModel>({
   isEnabled: { type: Boolean },
   installedPath: { type: String },
   organizationName: { type: String },
@@ -58,11 +59,11 @@ const growiPluginSchema = new Schema<GrowiPluginDocument, GrowiPluginModel>({
   meta: growiPluginMetaSchema,
 });
 
-growiPluginSchema.statics.findEnabledPlugins = async function(): Promise<GrowiPlugin[]> {
+growiPluginSchema.statics.findEnabledPlugins = async function(): Promise<IGrowiPlugin[]> {
   return this.find({ isEnabled: true });
 };
 
-growiPluginSchema.statics.findEnabledPluginsIncludingAnyTypes = async function(types: GrowiPluginResourceType[]): Promise<GrowiPlugin[]> {
+growiPluginSchema.statics.findEnabledPluginsIncludingAnyTypes = async function(types: GrowiPluginResourceType[]): Promise<IGrowiPlugin[]> {
   return this.find({
     isEnabled: true,
     'meta.types': { $in: types },
@@ -89,4 +90,4 @@ growiPluginSchema.statics.deactivatePlugin = async function(id: Types.ObjectId):
   return pluginName;
 };
 
-export default getOrCreateModel<GrowiPluginDocument, GrowiPluginModel>('GrowiPlugin', growiPluginSchema);
+export const GrowiPlugin = getOrCreateModel<IGrowiPluginDocument, IGrowiPluginModel>('GrowiPlugin', growiPluginSchema);

+ 1 - 0
apps/app/src/features/growi-plugin/models/index.ts

@@ -0,0 +1 @@
+export * from './growi-plugin';

+ 12 - 33
apps/app/src/server/routes/apiv3/plugins.ts → apps/app/src/features/growi-plugin/routes/growi-plugins.ts

@@ -2,10 +2,12 @@ import express, { Request, Router } from 'express';
 import { body, query } from 'express-validator';
 import mongoose from 'mongoose';
 
-import Crowi from '../../crowi';
-import type { GrowiPluginModel } from '../../models/growi-plugin';
+import Crowi from '~/server/crowi';
+import { ApiV3Response } from '~/server/routes/apiv3/interfaces/apiv3-response';
+
+import { GrowiPlugin } from '../models';
+import { growiPluginService } from '../services';
 
-import { ApiV3Response } from './interfaces/apiv3-response';
 
 const ObjectID = mongoose.Types.ObjectId;
 
@@ -22,20 +24,14 @@ const validator = {
 };
 
 module.exports = (crowi: Crowi): Router => {
-  const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
-  const adminRequired = require('../../middlewares/admin-required')(crowi);
+  const loginRequiredStrictly = require('~/server/middlewares/login-required')(crowi);
+  const adminRequired = require('~/server/middlewares/admin-required')(crowi);
 
   const router = express.Router();
-  const { pluginService } = crowi;
 
   router.get('/', loginRequiredStrictly, adminRequired, async(req: Request, res: ApiV3Response) => {
-    if (pluginService == null) {
-      return res.apiv3Err('\'pluginService\' is not set up', 500);
-    }
-
     try {
-      const GrowiPluginModel = mongoose.model('GrowiPlugin') as GrowiPluginModel;
-      const data = await GrowiPluginModel.find({});
+      const data = await GrowiPlugin.find({});
       return res.apiv3({ plugins: data });
     }
     catch (err) {
@@ -44,14 +40,10 @@ module.exports = (crowi: Crowi): Router => {
   });
 
   router.post('/', loginRequiredStrictly, adminRequired, validator.pluginFormValueisRequired, async(req: Request, res: ApiV3Response) => {
-    if (pluginService == null) {
-      return res.apiv3Err('\'pluginService\' is not set up', 500);
-    }
-
     const { pluginInstallerForm: formValue } = req.body;
 
     try {
-      const pluginName = await pluginService.install(formValue);
+      const pluginName = await growiPluginService.install(formValue);
       return res.apiv3({ pluginName });
     }
     catch (err) {
@@ -60,15 +52,11 @@ module.exports = (crowi: Crowi): Router => {
   });
 
   router.put('/:id/activate', loginRequiredStrictly, adminRequired, validator.pluginIdisRequired, async(req: Request, res: ApiV3Response) => {
-    if (pluginService == null) {
-      return res.apiv3Err('\'pluginService\' is not set up', 500);
-    }
     const { id } = req.params;
     const pluginId = new ObjectID(id);
 
     try {
-      const GrowiPluginModel = mongoose.model('GrowiPlugin') as GrowiPluginModel;
-      const pluginName = await GrowiPluginModel.activatePlugin(pluginId);
+      const pluginName = await GrowiPlugin.activatePlugin(pluginId);
       return res.apiv3({ pluginName });
     }
     catch (err) {
@@ -77,16 +65,11 @@ module.exports = (crowi: Crowi): Router => {
   });
 
   router.put('/:id/deactivate', loginRequiredStrictly, adminRequired, validator.pluginIdisRequired, async(req: Request, res: ApiV3Response) => {
-    if (pluginService == null) {
-      return res.apiv3Err('\'pluginService\' is not set up', 500);
-    }
-
     const { id } = req.params;
     const pluginId = new ObjectID(id);
 
     try {
-      const GrowiPluginModel = mongoose.model('GrowiPlugin') as GrowiPluginModel;
-      const pluginName = await GrowiPluginModel.deactivatePlugin(pluginId);
+      const pluginName = await GrowiPlugin.deactivatePlugin(pluginId);
       return res.apiv3({ pluginName });
     }
     catch (err) {
@@ -95,15 +78,11 @@ module.exports = (crowi: Crowi): Router => {
   });
 
   router.delete('/:id/remove', loginRequiredStrictly, adminRequired, validator.pluginIdisRequired, async(req: Request, res: ApiV3Response) => {
-    if (pluginService == null) {
-      return res.apiv3Err('\'pluginService\' is not set up', 500);
-    }
-
     const { id } = req.params;
     const pluginId = new ObjectID(id);
 
     try {
-      const pluginName = await pluginService.deletePlugin(pluginId);
+      const pluginName = await growiPluginService.deletePlugin(pluginId);
       return res.apiv3({ pluginName });
     }
     catch (err) {

+ 22 - 26
apps/app/src/server/service/plugin.ts → apps/app/src/features/growi-plugin/services/growi-plugin.ts

@@ -8,13 +8,14 @@ import mongoose from 'mongoose';
 import streamToPromise from 'stream-to-promise';
 import unzipper from 'unzipper';
 
-import {
-  GrowiPlugin, GrowiPluginOrigin, GrowiPluginResourceType, GrowiThemePluginMeta, GrowiPluginMeta,
-} from '~/interfaces/plugin';
 import loggerFactory from '~/utils/logger';
 import { resolveFromRoot } from '~/utils/project-dir-utils';
 
-import type { GrowiPluginModel } from '../models/growi-plugin';
+import { GrowiPluginResourceType } from '../interfaces';
+import type {
+  IGrowiPlugin, IGrowiPluginOrigin, IGrowiThemePluginMeta, IGrowiPluginMeta,
+} from '../interfaces';
+import { GrowiPlugin } from '../models';
 
 const logger = loggerFactory('growi:plugins:plugin-utils');
 
@@ -27,7 +28,7 @@ const PLUGINS_STATIC_DIR = '/static/plugins'; // configured by express.static
 
 export type GrowiPluginResourceEntries = [installedPath: string, href: string][];
 
-function retrievePluginManifest(growiPlugin: GrowiPlugin): ViteManifest {
+function retrievePluginManifest(growiPlugin: IGrowiPlugin): ViteManifest {
   const manifestPath = resolveFromRoot(path.join('tmp/plugins', growiPlugin.installedPath, 'dist/manifest.json'));
   const manifestStr: string = readFileSync(manifestPath, 'utf-8');
   return JSON.parse(manifestStr);
@@ -35,19 +36,19 @@ function retrievePluginManifest(growiPlugin: GrowiPlugin): ViteManifest {
 
 
 type FindThemePluginResult = {
-  growiPlugin: GrowiPlugin,
+  growiPlugin: IGrowiPlugin,
   themeMetadata: GrowiThemeMetadata,
   themeHref: string,
 }
 
-export interface IPluginService {
-  install(origin: GrowiPluginOrigin): Promise<string>
+export interface IGrowiPluginService {
+  install(origin: IGrowiPluginOrigin): Promise<string>
   findThemePlugin(theme: string): Promise<FindThemePluginResult | null>
   retrieveAllPluginResourceEntries(): Promise<GrowiPluginResourceEntries>
   downloadNotExistPluginRepositories(): Promise<void>
 }
 
-export class PluginService implements IPluginService {
+export class GrowiPluginService implements IGrowiPluginService {
 
   /*
   * Downloading a non-existent repository to the file system
@@ -55,7 +56,6 @@ export class PluginService implements IPluginService {
   async downloadNotExistPluginRepositories(): Promise<void> {
     try {
       // find all growi plugin documents
-      const GrowiPlugin = mongoose.model<GrowiPlugin>('GrowiPlugin');
       const growiPlugins = await GrowiPlugin.find({});
 
       // if not exists repository in file system, download latest plugin repository
@@ -113,7 +113,7 @@ export class PluginService implements IPluginService {
   /*
   * Install a plugin from URL and save it in the DB and file system.
   */
-  async install(origin: GrowiPluginOrigin): Promise<string> {
+  async install(origin: IGrowiPluginOrigin): Promise<string> {
     const ghUrl = new URL(origin.url);
     const ghPathname = ghUrl.pathname;
     // TODO: Branch names can be specified.
@@ -137,7 +137,7 @@ export class PluginService implements IPluginService {
     const organizationPath = path.join(pluginStoringPath, ghOrganizationName);
 
 
-    let plugins: GrowiPlugin<GrowiPluginMeta>[];
+    let plugins: IGrowiPlugin<IGrowiPluginMeta>[];
 
     try {
       // download github repository to file system's temporary path
@@ -146,7 +146,7 @@ export class PluginService implements IPluginService {
       fs.renameSync(unzippedReposPath, temporaryReposPath);
 
       // detect plugins
-      plugins = await PluginService.detectPlugins(origin, ghOrganizationName, ghReposName);
+      plugins = await GrowiPluginService.detectPlugins(origin, ghOrganizationName, ghReposName);
 
       if (!fs.existsSync(organizationPath)) fs.mkdirSync(organizationPath);
 
@@ -184,7 +184,6 @@ export class PluginService implements IPluginService {
   }
 
   private async deleteOldPluginDocument(path: string): Promise<void> {
-    const GrowiPlugin = mongoose.model<GrowiPlugin>('GrowiPlugin');
     await GrowiPlugin.deleteMany({ installedPath: path });
   }
 
@@ -230,13 +229,12 @@ export class PluginService implements IPluginService {
     }
   }
 
-  private async savePluginMetaData(plugins: GrowiPlugin[]): Promise<void> {
-    const GrowiPlugin = mongoose.model('GrowiPlugin');
+  private async savePluginMetaData(plugins: IGrowiPlugin[]): Promise<void> {
     await GrowiPlugin.insertMany(plugins);
   }
 
   // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, max-len
-  private static async detectPlugins(origin: GrowiPluginOrigin, ghOrganizationName: string, ghReposName: string, parentPackageJson?: any): Promise<GrowiPlugin[]> {
+  private static async detectPlugins(origin: IGrowiPluginOrigin, ghOrganizationName: string, ghReposName: string, parentPackageJson?: any): Promise<IGrowiPlugin[]> {
     const packageJsonPath = path.resolve(pluginStoringPath, ghReposName, 'package.json');
     const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
 
@@ -279,7 +277,7 @@ export class PluginService implements IPluginService {
 
     // add theme metadata
     if (growiPlugin.types.includes(GrowiPluginResourceType.Theme)) {
-      (plugin as GrowiPlugin<GrowiThemePluginMeta>).meta = {
+      (plugin as IGrowiPlugin<IGrowiThemePluginMeta>).meta = {
         ...plugin.meta,
         themes: growiPlugin.themes,
       };
@@ -290,7 +288,7 @@ export class PluginService implements IPluginService {
     return [plugin];
   }
 
-  async listPlugins(): Promise<GrowiPlugin[]> {
+  async listPlugins(): Promise<IGrowiPlugin[]> {
     return [];
   }
 
@@ -302,7 +300,6 @@ export class PluginService implements IPluginService {
       return fs.promises.rm(path, { recursive: true });
     };
 
-    const GrowiPlugin = mongoose.model<GrowiPlugin>('GrowiPlugin');
     const growiPlugins = await GrowiPlugin.findById(pluginId);
 
     if (growiPlugins == null) {
@@ -330,14 +327,12 @@ export class PluginService implements IPluginService {
   }
 
   async findThemePlugin(theme: string): Promise<FindThemePluginResult | null> {
-    const GrowiPlugin = mongoose.model('GrowiPlugin') as GrowiPluginModel;
-
-    let matchedPlugin: GrowiPlugin | undefined;
+    let matchedPlugin: IGrowiPlugin | undefined;
     let matchedThemeMetadata: GrowiThemeMetadata | undefined;
 
     try {
       // retrieve plugin manifests
-      const growiPlugins = await GrowiPlugin.findEnabledPluginsIncludingAnyTypes([GrowiPluginResourceType.Theme]) as GrowiPlugin<GrowiThemePluginMeta>[];
+      const growiPlugins = await GrowiPlugin.findEnabledPluginsIncludingAnyTypes([GrowiPluginResourceType.Theme]) as IGrowiPlugin<IGrowiThemePluginMeta>[];
 
       growiPlugins
         .forEach(async(growiPlugin) => {
@@ -373,8 +368,6 @@ export class PluginService implements IPluginService {
 
   async retrieveAllPluginResourceEntries(): Promise<GrowiPluginResourceEntries> {
 
-    const GrowiPlugin = mongoose.model('GrowiPlugin') as GrowiPluginModel;
-
     const entries: GrowiPluginResourceEntries = [];
 
     try {
@@ -409,3 +402,6 @@ export class PluginService implements IPluginService {
   }
 
 }
+
+
+export const growiPluginService = new GrowiPluginService();

+ 1 - 0
apps/app/src/features/growi-plugin/services/index.ts

@@ -0,0 +1 @@
+export * from './growi-plugin';

+ 3 - 2
apps/app/src/stores/plugin.tsx → apps/app/src/features/growi-plugin/stores/growi-plugin.tsx

@@ -1,10 +1,11 @@
 import useSWR, { SWRResponse } from 'swr';
 
 import { apiv3Get } from '~/client/util/apiv3-client';
-import { GrowiPluginHasId } from '~/interfaces/plugin';
+
+import type { IGrowiPluginHasId } from '../interfaces';
 
 type Plugins = {
-  plugins: GrowiPluginHasId[]
+  plugins: IGrowiPluginHasId[]
 }
 
 const pluginsFetcher = () => {

+ 0 - 0
apps/app/src/features/activate-plugin/utils/growi-facade-utils.ts → apps/app/src/features/growi-plugin/utils/growi-facade-utils.client.ts


+ 1 - 1
apps/app/src/pages/[[...path]].page.tsx

@@ -67,7 +67,7 @@ declare global {
 }
 
 
-const GrowiPluginsActivator = dynamic(() => import('~/features/activate-plugin').then(mod => mod.GrowiPluginsActivator), { ssr: false });
+const GrowiPluginsActivator = dynamic(() => import('~/features/growi-plugin/components').then(mod => mod.GrowiPluginsActivator), { ssr: false });
 const DescendantsPageListModal = dynamic(() => import('../components/DescendantsPageListModal').then(mod => mod.DescendantsPageListModal), { ssr: false });
 const UnsavedAlertDialog = dynamic(() => import('../components/UnsavedAlertDialog'), { ssr: false });
 const GrowiSubNavigationSwitcher = dynamic<GrowiSubNavigationSwitcherProps>(() => import('../components/Navbar/GrowiSubNavigationSwitcher')

+ 3 - 3
apps/app/src/pages/_document.page.tsx

@@ -6,8 +6,8 @@ import Document, {
   Html, Head, Main, NextScript,
 } from 'next/document';
 
+import { growiPluginService, type GrowiPluginResourceEntries } from '~/features/growi-plugin/services';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
-import type { IPluginService, GrowiPluginResourceEntries } from '~/server/service/plugin';
 import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:page:_document');
@@ -49,7 +49,7 @@ class GrowiDocument extends Document<GrowiDocumentInitialProps> {
   static override async getInitialProps(ctx: DocumentContext): Promise<GrowiDocumentInitialProps> {
     const initialProps: DocumentInitialProps = await Document.getInitialProps(ctx);
     const { crowi } = ctx.req as CrowiRequest<any>;
-    const { customizeService, pluginService } = crowi;
+    const { customizeService } = crowi;
 
     const { themeHref } = customizeService;
     const customScript: string | null = customizeService.getCustomScript();
@@ -57,7 +57,7 @@ class GrowiDocument extends Document<GrowiDocumentInitialProps> {
     const customNoscript: string | null = customizeService.getCustomNoscript();
 
     // retrieve plugin manifests
-    const pluginResourceEntries = await (pluginService as IPluginService).retrieveAllPluginResourceEntries();
+    const pluginResourceEntries = await growiPluginService.retrieveAllPluginResourceEntries();
 
     return {
       ...initialProps,

+ 1 - 1
apps/app/src/pages/admin/plugins.page.tsx

@@ -18,7 +18,7 @@ import { retrieveServerSideProps } from '../../utils/admin-page-util';
 
 const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), { ssr: false });
 const PluginsExtensionPageContents = dynamic(
-  () => import('~/components/Admin/PluginsExtension/PluginsExtensionPageContents').then(mod => mod.PluginsExtensionPageContents),
+  () => import('~/features/growi-plugin/components/Admin/PluginsExtensionPageContents').then(mod => mod.PluginsExtensionPageContents),
   { ssr: false },
 );
 

+ 5 - 19
apps/app/src/server/crowi/index.js

@@ -19,7 +19,6 @@ import { projectRoot } from '~/utils/project-dir-utils';
 
 
 import Activity from '../models/activity';
-import GrowiPlugin from '../models/growi-plugin';
 import PageRedirect from '../models/page-redirect';
 import Tag from '../models/tag';
 import UserGroup from '../models/user-group';
@@ -32,8 +31,6 @@ import { InstallerService } from '../service/installer';
 import PageService from '../service/page';
 import PageGrantService from '../service/page-grant';
 import PageOperationService from '../service/page-operation';
-// eslint-disable-next-line import/no-cycle
-import { PluginService } from '../service/plugin';
 import SearchService from '../service/search';
 import { SlackIntegrationService } from '../service/slack-integration';
 import { UserNotificationService } from '../service/user-notification';
@@ -134,7 +131,6 @@ Crowi.prototype.init = async function() {
     this.scanRuntimeVersions(),
     this.setupPassport(),
     this.setupSearcher(),
-    this.setupPluginer(),
     this.setupMailer(),
     this.setupSlackIntegrationService(),
     this.setupG2GTransferService(),
@@ -146,7 +142,7 @@ Crowi.prototype.init = async function() {
     this.setupUserGroupService(),
     this.setupExport(),
     this.setupImport(),
-    this.setupPluginService(),
+    this.setupGrowiPluginService(),
     this.setupPageService(),
     this.setupInAppNotificationService(),
     this.setupActivityService(),
@@ -308,7 +304,6 @@ Crowi.prototype.setupModels = async function() {
   allModels.Tag = Tag;
   allModels.UserGroup = UserGroup;
   allModels.PageRedirect = PageRedirect;
-  allModels.growiPlugin = GrowiPlugin;
 
   Object.keys(allModels).forEach((key) => {
     return this.model(key, models[key](this));
@@ -394,13 +389,6 @@ Crowi.prototype.setupSearcher = async function() {
   this.searchService = new SearchService(this);
 };
 
-/**
- * setup PluginService
- */
-Crowi.prototype.setupPluginer = async function() {
-  this.pluginService = new PluginService(this);
-};
-
 Crowi.prototype.setupMailer = async function() {
   const MailService = require('~/server/service/mail');
   this.mailService = new MailService(this);
@@ -717,14 +705,12 @@ Crowi.prototype.setupImport = async function() {
   }
 };
 
-Crowi.prototype.setupPluginService = async function() {
-  const { PluginService } = require('../service/plugin');
-  if (this.pluginService == null) {
-    this.pluginService = new PluginService(this);
-  }
+Crowi.prototype.setupGrowiPluginService = async function() {
+  const { growiPluginService } = require('~/features/growi-plugin/services');
+
   // download plugin repositories, if document exists but there is no repository
   // TODO: Cannot download unless connected to the Internet at setup.
-  await this.pluginService.downloadNotExistPluginRepositories();
+  await growiPluginService.downloadNotExistPluginRepositories();
 };
 
 Crowi.prototype.setupPageService = async function() {

+ 6 - 8
apps/app/src/server/routes/apiv3/customize-setting.js

@@ -1,10 +1,14 @@
 /* eslint-disable no-unused-vars */
 
 import { ErrorV3 } from '@growi/core';
+import express from 'express';
+import { body } from 'express-validator';
 import mongoose from 'mongoose';
+import multer from 'multer';
 
+import { GrowiPluginResourceType } from '~/features/growi-plugin/interfaces';
+import { GrowiPlugin } from '~/features/growi-plugin/models';
 import { SupportedAction } from '~/interfaces/activity';
-import { GrowiPluginResourceType } from '~/interfaces/plugin';
 import { AttachmentType } from '~/server/interfaces/attachment';
 import loggerFactory from '~/utils/logger';
 
@@ -14,13 +18,8 @@ import { apiV3FormValidator } from '../../middlewares/apiv3-form-validator';
 
 const logger = loggerFactory('growi:routes:apiv3:customize-setting');
 
-const express = require('express');
-
 const router = express.Router();
 
-const { body, query } = require('express-validator');
-const multer = require('multer');
-
 
 /**
  * @swagger
@@ -276,8 +275,7 @@ module.exports = (crowi) => {
       const currentTheme = await crowi.configManager.getConfig('crowi', 'customize:theme');
 
       // retrieve plugin manifests
-      const GrowiPluginModel = mongoose.model('GrowiPlugin');
-      const themePlugins = await GrowiPluginModel.findEnabledPluginsIncludingAnyTypes([GrowiPluginResourceType.Theme]);
+      const themePlugins = await GrowiPlugin.findEnabledPluginsIncludingAnyTypes([GrowiPluginResourceType.Theme]);
 
       const pluginThemesMetadatas = themePlugins
         .map(themePlugin => themePlugin.meta.themes)

+ 1 - 1
apps/app/src/server/routes/apiv3/index.js

@@ -108,7 +108,7 @@ module.exports = (crowi, app) => {
     userActivation.validateCompleteRegistration,
     userActivation.completeRegistrationAction(crowi));
 
-  router.use('/plugins', require('./plugins')(crowi));
+  router.use('/plugins', require('~/features/growi-plugin/routes/growi-plugins')(crowi));
 
   router.use('/user-ui-settings', require('./user-ui-settings')(crowi));
 

+ 2 - 5
apps/app/src/server/service/customize.ts

@@ -3,12 +3,12 @@ import { ColorScheme, DevidedPagePath, getForcedColorScheme } from '@growi/core'
 import { DefaultThemeMetadata, PresetThemesMetadatas } from '@growi/preset-themes';
 import uglifycss from 'uglifycss';
 
+import { growiPluginService } from '~/features/growi-plugin/services';
 import loggerFactory from '~/utils/logger';
 
 import S2sMessage from '../models/vo/s2s-message';
 
 import type { ConfigManager } from './config-manager';
-import type { IPluginService } from './plugin';
 import type { S2sMessageHandlable } from './s2s-messaging/handlable';
 
 
@@ -28,8 +28,6 @@ class CustomizeService implements S2sMessageHandlable {
 
   xssService: any;
 
-  pluginService: IPluginService;
-
   lastLoadedAt?: Date;
 
   customCss?: string;
@@ -47,7 +45,6 @@ class CustomizeService implements S2sMessageHandlable {
     this.s2sMessagingService = crowi.s2sMessagingService;
     this.appService = crowi.appService;
     this.xssService = crowi.xssService;
-    this.pluginService = crowi.pluginService;
   }
 
   /**
@@ -155,7 +152,7 @@ class CustomizeService implements S2sMessageHandlable {
 
     this.theme = theme;
 
-    const resultForThemePlugin = await this.pluginService.findThemePlugin(theme);
+    const resultForThemePlugin = await growiPluginService.findThemePlugin(theme);
 
     if (resultForThemePlugin != null) {
       this.forcedColorScheme = getForcedColorScheme(resultForThemePlugin.themeMetadata.schemeType);

+ 1 - 1
apps/app/src/stores/renderer.tsx

@@ -3,7 +3,7 @@ import { useCallback } from 'react';
 import type { HtmlElementNode } from 'rehype-toc';
 import useSWR, { type SWRResponse } from 'swr';
 
-import { getGrowiFacade } from '~/features/activate-plugin';
+import { getGrowiFacade } from '~/features/growi-plugin/utils/growi-facade-utils.client';
 import type { RendererOptions } from '~/interfaces/renderer-options';
 
 

+ 3 - 3
apps/app/src/stores/template.tsx

@@ -1,7 +1,7 @@
-import { ITemplate } from '@growi/core';
-import useSWR, { SWRResponse } from 'swr';
+import type { ITemplate } from '@growi/core';
+import useSWR, { type SWRResponse } from 'swr';
 
-import { getGrowiFacade } from '~/features/activate-plugin';
+import { getGrowiFacade } from '~/features/growi-plugin/utils/growi-facade-utils.client';
 
 const presetTemplates: ITemplate[] = [
   // preset 1