diff --git a/.eslintrc.base.js b/.eslintrc.base.js index c49fe3f..561fbcf 100644 --- a/.eslintrc.base.js +++ b/.eslintrc.base.js @@ -9,5 +9,6 @@ module.exports = { rules: { '@typescript-eslint/ban-ts-comment': 0, '@typescript-eslint/no-var-requires': 0, + '@typescript-eslint/ban-ts-ignore': 0, }, }; diff --git a/packages/gateway-frontend/src/App.tsx b/packages/gateway-frontend/src/App.tsx index 0b6dc48..c183c9b 100644 --- a/packages/gateway-frontend/src/App.tsx +++ b/packages/gateway-frontend/src/App.tsx @@ -1,6 +1,6 @@ -import React, { useCallback, useEffect, forwardRef } from 'react'; +import { useSnackbar } from 'notistack'; +import React, { useCallback, useEffect, forwardRef, useState } from 'react'; import { observer } from 'mobx-react'; -import { SnackbarProvider } from 'notistack'; import clsx from 'clsx'; import { Switch, @@ -16,6 +16,7 @@ import Divider from '@material-ui/core/Divider'; import Drawer from '@material-ui/core/Drawer'; import Hidden from '@material-ui/core/Hidden'; import IconButton from '@material-ui/core/IconButton'; +import DeleteIcon from '@material-ui/icons/Delete'; import List from '@material-ui/core/List'; import ListItem from '@material-ui/core/ListItem'; import ListItemIcon from '@material-ui/core/ListItemIcon'; @@ -32,6 +33,7 @@ import loadable from '@loadable/component'; import './App.css'; import useNavElements from './hooks/useNavElements'; +import client from './libs/http'; import { defaultFetcher } from './libs/utils'; import { useStores } from './stores'; import { Config } from './stores/config'; @@ -103,20 +105,21 @@ const useStyles = makeStyles((theme: Theme) => appBarIcons: { color: '#fff', }, - }), + }) ); interface ResponsiveDrawerProps { container?: Element; } -export default observer((props: ResponsiveDrawerProps) => { +const App = observer((props: ResponsiveDrawerProps) => { const { container } = props; const classes = useStyles(); const stores = useStores(); const isShowNavElements = useNavElements(); const location = useLocation(); - const [mobileOpen, setMobileOpen] = React.useState(false); + const [mobileOpen, setMobileOpen] = useState(false); + const { enqueueSnackbar } = useSnackbar(); const handleDrawerToggle = () => { setMobileOpen(!mobileOpen); @@ -130,19 +133,29 @@ export default observer((props: ResponsiveDrawerProps) => { accessToken: search.get('access_token'), }); - return defaultFetcher<{accessToken?: string}>('/api/auth/validate-token'); + return defaultFetcher<{ accessToken?: string }>( + '/api/auth/validate-token' + ); } - return defaultFetcher<{accessToken?: string}>('/api/auth/validate-cookie'); + return defaultFetcher<{ accessToken?: string }>( + '/api/auth/validate-cookie' + ); }, [location.search, stores.config]); + const cleanCache = () => { + client.post('/api/clean-cache').then(() => { + enqueueSnackbar('清除成功', { variant: 'success' }); + }); + }; + const updateConfig = () => { return defaultFetcher>('/api/config'); }; useEffect(() => { validateAuth() - .then(user => { + .then((user) => { if (user.accessToken) { stores.config.updateConfig({ accessToken: user.accessToken, @@ -151,14 +164,14 @@ export default observer((props: ResponsiveDrawerProps) => { return updateConfig(); }) - .catch(err => { + .catch((err) => { // 授权失败,直接获取配置信息 return updateConfig(); }) - .then(config => { + .then((config) => { stores.config.updateConfig(config); }) - .catch(err => { + .catch((err) => { console.error(err); }); }, [stores.config, validateAuth]); @@ -166,131 +179,149 @@ export default observer((props: ResponsiveDrawerProps) => { const drawer = (
- { - stores.config.isReady && ( - <> - - Core: - v{stores.config.config.coreVersion} - - - Backend: - v{stores.config.config.backendVersion} - - - ) - } + {stores.config.isReady && ( + <> + + Core: + v{stores.config.config.coreVersion} + + + Backend: + v{stores.config.config.backendVersion} + + + )}
- } /> + } + /> } /> +
  • + cleanCache()}> + + + + + +
  • ); return ( - - <> - + <> + -
    - { stores.config.isReady && ( - <> - { - isShowNavElements && ( - <> - - - - - - - - Surgio Dashboard - - -
    - - - -
    -
    -
    +
    + {stores.config.isReady && ( + <> + {isShowNavElements && ( + <> + + + + + + + + Surgio Dashboard + + +
    + + + +
    +
    +
    - - - ) - } + + + )} -
    - { isShowNavElements &&
    } +
    + {isShowNavElements &&
    } - - - - - - - - - - - - - - - - - - - - - - - -
    - - ) } -
    - - + + + + + + + + + + + + + + + + + + + + + + + +
    + + )} +
    + ); }); @@ -305,12 +336,12 @@ function ListItemLink(props: ListItemLinkProps) { const renderLink = React.useMemo( () => - forwardRef>(function ListItemInnerComponent(itemProps, ref) { - return ( - - ); - }), - [to], + forwardRef>( + function ListItemInnerComponent(itemProps, ref) { + return ; + } + ), + [to] ); return ( @@ -322,3 +353,5 @@ function ListItemLink(props: ListItemLinkProps) { ); } + +export default App; diff --git a/packages/gateway-frontend/src/index.tsx b/packages/gateway-frontend/src/index.tsx index 386ff5b..786334f 100644 --- a/packages/gateway-frontend/src/index.tsx +++ b/packages/gateway-frontend/src/index.tsx @@ -1,3 +1,4 @@ +import { SnackbarProvider } from 'notistack'; import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter as Router } from 'react-router-dom'; @@ -6,11 +7,14 @@ import './index.css'; import App from './App'; import * as serviceWorker from './serviceWorker'; -ReactDOM.render(( +ReactDOM.render( - - -), document.getElementById('root')); + + + + , + document.getElementById('root') +); // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. diff --git a/packages/gateway/.eslintrc.js b/packages/gateway/.eslintrc.js index 7722a96..22c4ab1 100644 --- a/packages/gateway/.eslintrc.js +++ b/packages/gateway/.eslintrc.js @@ -2,9 +2,7 @@ const { join } = require('path'); module.exports = { root: true, - extends: [ - '../../.eslintrc.base.js', - ], + extends: [join(__dirname, '../../.eslintrc.base.js')], parserOptions: { project: join(__dirname, 'tsconfig.eslint.json'), sourceType: 'module', diff --git a/packages/gateway/package.json b/packages/gateway/package.json index bd25c21..cbe3a8b 100644 --- a/packages/gateway/package.json +++ b/packages/gateway/package.json @@ -41,6 +41,7 @@ "cookie-parser": "^1.4.5", "dayjs": "^1.9.5", "express": "^4.17.1", + "fs-extra": "^9.1.0", "http-errors": "^1.7.3", "lodash": "^4.17.19", "lru-cache": "^6.0.0", diff --git a/packages/gateway/src/api/api.controller.ts b/packages/gateway/src/api/api.controller.ts index 99833c4..8263574 100644 --- a/packages/gateway/src/api/api.controller.ts +++ b/packages/gateway/src/api/api.controller.ts @@ -1,4 +1,14 @@ -import { Controller, Get, HttpException, HttpStatus, Param, Post, Res, UseGuards, Req } from '@nestjs/common'; +import { + Controller, + Get, + HttpException, + HttpStatus, + Param, + Post, + Res, + UseGuards, + Req, +} from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { Request, Response } from 'express'; import _ from 'lodash'; @@ -12,24 +22,29 @@ import { SurgioService } from '../surgio/surgio.service'; export class ApiController { constructor( private readonly surgioService: SurgioService, - private readonly configService: ConfigService, + private readonly configService: ConfigService ) {} @Post('/auth') public async login(@Req() req: Request, @Res() res: Response): Promise { const accessToken = req.body.accessToken; - if (accessToken === this.surgioService.surgioHelper.config?.gateway?.accessToken) { + if ( + accessToken === + this.surgioService.surgioHelper.config?.gateway?.accessToken + ) { res.cookie('_t', accessToken, { - maxAge: (this.surgioService.surgioHelper.config?.gateway?.cookieMaxAge - ?? this.configService.get('defaultCookieMaxAge') as number) * 1e3, + maxAge: + (this.surgioService.surgioHelper.config?.gateway?.cookieMaxAge ?? + (this.configService.get('defaultCookieMaxAge') as number)) * 1e3, httpOnly: true, signed: true, path: '/', }); res.cookie('_t', accessToken, { - maxAge: (this.surgioService.surgioHelper.config?.gateway?.cookieMaxAge - ?? this.configService.get('defaultCookieMaxAge') as number) * 1e3, + maxAge: + (this.surgioService.surgioHelper.config?.gateway?.cookieMaxAge ?? + (this.configService.get('defaultCookieMaxAge') as number)) * 1e3, httpOnly: true, signed: true, path: '/api', @@ -65,14 +80,28 @@ export class ApiController { return { status: 'ok', data: { - ..._.pick(this.surgioService.surgioHelper.config, ['urlBase', 'publicUrl']), + ..._.pick(this.surgioService.surgioHelper.config, [ + 'urlBase', + 'publicUrl', + ]), backendVersion: require('../../package.json').version, coreVersion: require('surgio/package.json').version, - needAuth: this.surgioService.surgioHelper.config?.gateway?.auth ?? false, + needAuth: + this.surgioService.surgioHelper.config?.gateway?.auth ?? false, }, }; } + @UseGuards(BearerAuthGuard) + @Post('/clean-cache') + public async cleanCache(): Promise { + await this.surgioService.surgioHelper.cleanCache(); + + return { + status: 'ok', + }; + } + @UseGuards(BearerAuthGuard) @Get('/artifacts') public async listArtifacts(): Promise { @@ -86,14 +115,19 @@ export class ApiController { @UseGuards(BearerAuthGuard) @Get('/artifacts/:name') - public async getArtifact(@Res() res: Response, @Param() params): Promise { - const artifactList = this.surgioService.surgioHelper.artifactList.filter(item => item.name === params.name); + public async getArtifact( + @Res() res: Response, + @Param() params + ): Promise { + const artifactList = this.surgioService.surgioHelper.artifactList.filter( + (item) => item.name === params.name + ); if (artifactList.length) { res.send({ status: 'ok', data: artifactList[0], - }) + }); } else { throw new HttpException('NOT FOUND', HttpStatus.NOT_FOUND); } @@ -106,14 +140,23 @@ export class ApiController { return { status: 'ok', - data: providerList.map(provider => _.pick(provider, ['name', 'type', 'url', 'supportGetSubscriptionUserInfo'])), + data: providerList.map((provider) => + _.pick(provider, [ + 'name', + 'type', + 'url', + 'supportGetSubscriptionUserInfo', + ]) + ), }; } @UseGuards(BearerAuthGuard) @Get('/providers/:name/subscription') public async getProviderSubscription(@Param() params): Promise { - const provider = this.surgioService.surgioHelper.providerMap.get(params.name); + const provider = this.surgioService.surgioHelper.providerMap.get( + params.name + ); if (!provider) { throw new HttpException('NOT FOUND', HttpStatus.NOT_FOUND); diff --git a/packages/gateway/src/surgio/surgio-helper.ts b/packages/gateway/src/surgio/surgio-helper.ts index 412c0d6..0334322 100644 --- a/packages/gateway/src/surgio/surgio-helper.ts +++ b/packages/gateway/src/surgio/surgio-helper.ts @@ -1,18 +1,23 @@ +import os from 'os'; import { basename, join } from 'path'; -import fs, { promises as fsp } from 'fs'; +import fs from 'fs-extra'; +import { TMP_FOLDER_NAME } from 'surgio/build/utils/constant'; import { Environment } from 'nunjucks'; import semver from 'semver'; import { Logger } from '@nestjs/common'; import { getEngine } from 'surgio/build/generator/template'; import { getProvider } from 'surgio/build/provider'; -import { ArtifactConfig, CommandConfig, RemoteSnippet } from 'surgio/build/types'; -import { loadRemoteSnippetList } from 'surgio/build/utils/remote-snippet'; +import { + ArtifactConfig, + CommandConfig, + RemoteSnippet, +} from 'surgio/build/types'; import { PackageJson } from 'type-fest'; type PossibleProviderType = ReturnType; export class SurgioHelper { - public remoteSnippetList: ReadonlyArray; + public remoteSnippetList?: ReadonlyArray; public artifactList: ReadonlyArray; public providerMap: Map = new Map(); public readonly templateEngine: Environment; @@ -31,21 +36,19 @@ export class SurgioHelper { public async init(): Promise { await this.checkCoreVersion(); - - // const remoteSnippetsConfig = this.config.remoteSnippets || []; - // this.remoteSnippetList = await loadRemoteSnippetList(remoteSnippetsConfig); - await this.readProviders(); return this; } private async readProviders(): Promise { - const files = await fsp.readdir(this.config.providerDir, { + const files = await fs.readdir(this.config.providerDir, { encoding: 'utf8', }); - async function readProvider(path): Promise { + async function readProvider( + path + ): Promise { let provider; try { @@ -78,7 +81,11 @@ export class SurgioHelper { if (!semver.satisfies(corePkgFile.version, peerVersion)) { Logger.warn('', undefined, false); - Logger.warn('Surgio 版本过低,请升级后重新运行!', undefined, false); + Logger.warn( + 'Surgio 版本过低,请运行下面命令升级后重新运行!', + undefined, + false + ); Logger.warn('', undefined, false); Logger.warn(' 命令:', undefined, false); Logger.warn(' npm install surgio@latest', undefined, false); @@ -86,4 +93,18 @@ export class SurgioHelper { throw new Error('Surgio 版本过低'); } } + + public async cleanCache(): Promise { + const tmpDir = join(os.tmpdir(), TMP_FOLDER_NAME); + + if (this.remoteSnippetList) { + Logger.log('已清除远程片段'); + this.remoteSnippetList = undefined; + } + + if (fs.existsSync(tmpDir)) { + Logger.log('已清除文件缓存'); + await fs.remove(tmpDir); + } + } } diff --git a/packages/gateway/src/surgio/surgio.service.ts b/packages/gateway/src/surgio/surgio.service.ts index dcf812d..fa50bf3 100644 --- a/packages/gateway/src/surgio/surgio.service.ts +++ b/packages/gateway/src/surgio/surgio.service.ts @@ -83,7 +83,7 @@ export class SurgioService { this.surgioHelper.config, artifactConfig, { - remoteSnippetList: this.surgioHelper.remoteSnippetList, + remoteSnippetList: this.surgioHelper.remoteSnippetList || [], templateEngine: this.surgioHelper.templateEngine, } ); @@ -117,7 +117,7 @@ export class SurgioService { return await generate( this.surgioHelper.config, artifact, - this.surgioHelper.remoteSnippetList, + this.surgioHelper.remoteSnippetList || [], this.surgioHelper.templateEngine ); } diff --git a/yarn.lock b/yarn.lock index f12b893..b3b78ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8373,7 +8373,7 @@ front-matter@^2.3.0: dependencies: js-yaml "^3.10.0" -fs-extra@9.1.0, fs-extra@^9.0.0: +fs-extra@9.1.0, fs-extra@^9.0.0, fs-extra@^9.1.0: version "9.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==