feat: use simpler nitro instead of nestjs to implement mock service
This commit is contained in:
1
apps/backend-mock/.env
Normal file
1
apps/backend-mock/.env
Normal file
@@ -0,0 +1 @@
|
||||
PORT=5320
|
@@ -10,9 +10,6 @@ Vben Admin 数据 mock 服务,没有对接任何的数据库,所有数据都
|
||||
# development
|
||||
$ pnpm run start
|
||||
|
||||
# watch mode
|
||||
$ pnpm run start:dev
|
||||
|
||||
# production mode
|
||||
$ pnpm run start:prod
|
||||
$ pnpm run build
|
||||
```
|
||||
|
15
apps/backend-mock/api/auth/codes.ts
Normal file
15
apps/backend-mock/api/auth/codes.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export default eventHandler((event) => {
|
||||
const token = getHeader(event, 'Authorization');
|
||||
|
||||
if (!token) {
|
||||
setResponseStatus(event, 401);
|
||||
return useResponseError('UnauthorizedException', 'Unauthorized Exception');
|
||||
}
|
||||
|
||||
const username = Buffer.from(token, 'base64').toString('utf8');
|
||||
|
||||
const codes =
|
||||
MOCK_CODES.find((item) => item.username === username)?.codes ?? [];
|
||||
|
||||
return useResponseSuccess(codes);
|
||||
});
|
20
apps/backend-mock/api/auth/login.post.ts
Normal file
20
apps/backend-mock/api/auth/login.post.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
export default defineEventHandler(async (event) => {
|
||||
const { password, username } = await readBody(event);
|
||||
|
||||
const findUser = MOCK_USERS.find(
|
||||
(item) => item.username === username && item.password === password,
|
||||
);
|
||||
|
||||
if (!findUser) {
|
||||
setResponseStatus(event, 403);
|
||||
return useResponseError('UnauthorizedException', '用户名或密码错误');
|
||||
}
|
||||
|
||||
const accessToken = Buffer.from(username).toString('base64');
|
||||
|
||||
return useResponseSuccess({
|
||||
accessToken,
|
||||
// TODO: refresh token
|
||||
refreshToken: accessToken,
|
||||
});
|
||||
});
|
14
apps/backend-mock/api/menu/all.ts
Normal file
14
apps/backend-mock/api/menu/all.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export default eventHandler((event) => {
|
||||
const token = getHeader(event, 'Authorization');
|
||||
|
||||
if (!token) {
|
||||
setResponseStatus(event, 401);
|
||||
return useResponseError('UnauthorizedException', 'Unauthorized Exception');
|
||||
}
|
||||
|
||||
const username = Buffer.from(token, 'base64').toString('utf8');
|
||||
|
||||
const menus =
|
||||
MOCK_MENUS.find((item) => item.username === username)?.menus ?? [];
|
||||
return useResponseSuccess(menus);
|
||||
});
|
5
apps/backend-mock/api/status.ts
Normal file
5
apps/backend-mock/api/status.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export default eventHandler((event) => {
|
||||
const { status } = getQuery(event);
|
||||
setResponseStatus(event, Number(status));
|
||||
return useResponseError(`${status}`);
|
||||
});
|
1
apps/backend-mock/api/test.get.ts
Normal file
1
apps/backend-mock/api/test.get.ts
Normal file
@@ -0,0 +1 @@
|
||||
export default defineEventHandler(() => 'Test get handler');
|
1
apps/backend-mock/api/test.post.ts
Normal file
1
apps/backend-mock/api/test.post.ts
Normal file
@@ -0,0 +1 @@
|
||||
export default defineEventHandler(() => 'Test post handler');
|
14
apps/backend-mock/api/user/info.ts
Normal file
14
apps/backend-mock/api/user/info.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export default eventHandler((event) => {
|
||||
const token = getHeader(event, 'Authorization');
|
||||
if (!token) {
|
||||
setResponseStatus(event, 401);
|
||||
return useResponseError('UnauthorizedException', 'Unauthorized Exception');
|
||||
}
|
||||
|
||||
const username = Buffer.from(token, 'base64').toString('utf8');
|
||||
|
||||
const user = MOCK_USERS.find((item) => item.username === username);
|
||||
|
||||
const { password: _pwd, ...userInfo } = user;
|
||||
return useResponseSuccess(userInfo);
|
||||
});
|
@@ -1,23 +0,0 @@
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
autorestart: true,
|
||||
cwd: './',
|
||||
env: {
|
||||
NODE_ENV: 'production',
|
||||
},
|
||||
env_development: {
|
||||
NODE_ENV: 'development',
|
||||
},
|
||||
env_production: {
|
||||
NODE_ENV: 'production',
|
||||
},
|
||||
ignore_watch: ['node_modules', '.logs', 'dist'],
|
||||
instances: 1,
|
||||
max_memory_restart: '1G',
|
||||
name: '@vben/backend-mock',
|
||||
script: 'node dist/main.js',
|
||||
watch: false,
|
||||
},
|
||||
],
|
||||
};
|
7
apps/backend-mock/error.ts
Normal file
7
apps/backend-mock/error.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { NitroErrorHandler } from 'nitropack';
|
||||
|
||||
const errorHandler: NitroErrorHandler = function (error, event) {
|
||||
event.res.end(`[error handler] ${error.stack}`);
|
||||
};
|
||||
|
||||
export default errorHandler;
|
@@ -1,20 +0,0 @@
|
||||
@port = 5320
|
||||
@type = application/json
|
||||
@token = Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MCwicm9sZXMiOlsiYWRtaW4iXSwidXNlcm5hbWUiOiJ2YmVuIiwiaWF0IjoxNzE5ODkwMTEwLCJleHAiOjE3MTk5NzY1MTB9.eyAFsQ2Jk_mAQGvrEL1jF9O6YmLZ_PSYj5aokL6fCuU
|
||||
POST http://localhost:{{port}}/api/auth/login HTTP/1.1
|
||||
content-type: {{ type }}
|
||||
|
||||
{
|
||||
"username": "vben",
|
||||
"password": "123456"
|
||||
}
|
||||
|
||||
|
||||
###
|
||||
GET http://localhost:{{port}}/api/auth/getUserInfo HTTP/1.1
|
||||
content-type: {{ type }}
|
||||
Authorization: {{ token }}
|
||||
|
||||
{
|
||||
"username": "vben"
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
@port = 5320
|
||||
GET http://localhost:{{port}}/api HTTP/1.1
|
||||
content-type: application/json
|
@@ -1,6 +0,0 @@
|
||||
@port = 5320
|
||||
@type = application/json
|
||||
@token = Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MCwicm9sZXMiOlsiYWRtaW4iXSwidXNlcm5hbWUiOiJ2YmVuIiwiaWF0IjoxNzE5ODkwMTEwLCJleHAiOjE3MTk5NzY1MTB9.eyAFsQ2Jk_mAQGvrEL1jF9O6YmLZ_PSYj5aokL6fCuU
|
||||
GET http://localhost:{{port}}/api/menu/getAll HTTP/1.1
|
||||
content-type: {{ type }}
|
||||
Authorization: {{ token }}
|
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/nest-cli",
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src",
|
||||
"compilerOptions": {
|
||||
"assets": ["**/*.yml", "**/*.json"],
|
||||
"watchAssets": true,
|
||||
"deleteOutDir": true
|
||||
}
|
||||
}
|
6
apps/backend-mock/nitro.config.ts
Normal file
6
apps/backend-mock/nitro.config.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import errorHandler from './error';
|
||||
|
||||
export default defineNitroConfig({
|
||||
devErrorHandler: errorHandler,
|
||||
errorHandler: '~/error',
|
||||
});
|
@@ -6,41 +6,10 @@
|
||||
"license": "MIT",
|
||||
"author": "",
|
||||
"scripts": {
|
||||
"build": "nest build",
|
||||
"dev": "pnpm run start:dev",
|
||||
"start": "cross-env NODE_ENV=development node dist/main",
|
||||
"start:dev": "cross-env NODE_ENV=development DEBUG=true nest start --watch",
|
||||
"start:prod": "nest build && cross-env NODE_ENV=production node dist/main"
|
||||
"start": "nitro dev",
|
||||
"build": "nitro build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "^10.3.10",
|
||||
"@nestjs/config": "^3.2.3",
|
||||
"@nestjs/core": "^10.3.10",
|
||||
"@nestjs/jwt": "^10.2.0",
|
||||
"@nestjs/passport": "^10.0.3",
|
||||
"@nestjs/platform-express": "^10.3.10",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"joi": "^17.13.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"mockjs": "^1.1.0",
|
||||
"passport": "^0.7.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"passport-local": "^1.0.0",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^10.4.2",
|
||||
"@nestjs/schematics": "^10.1.2",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/mockjs": "^1.0.10",
|
||||
"@types/node": "^20.14.11",
|
||||
"nodemon": "^3.1.4",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.5.3"
|
||||
"nitropack": "latest"
|
||||
}
|
||||
}
|
||||
|
12
apps/backend-mock/routes/[...].ts
Normal file
12
apps/backend-mock/routes/[...].ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export default defineEventHandler(() => {
|
||||
return `
|
||||
<h1>Hello Vben Admin</h1>
|
||||
<h2>Mock service is starting</h2>
|
||||
<ul>
|
||||
<li><a href="/api/user">/api/user/info</a></li>
|
||||
<li><a href="/api/menu">/api/menu/all</a></li>
|
||||
<li><a href="/api/auth/codes">/api/auth/codes</a></li>
|
||||
<li><a href="/api/auth/login">/api/auth/login</a></li>
|
||||
</ul>
|
||||
`;
|
||||
});
|
@@ -1,34 +0,0 @@
|
||||
import configuration from '@/config/index';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import Joi from 'joi';
|
||||
|
||||
import { AuthModule } from './modules/auth/auth.module';
|
||||
import { HealthModule } from './modules/health/health.module';
|
||||
import { MenuModule } from './modules/menu/menu.module';
|
||||
import { MockModule } from './modules/mock/mock.module';
|
||||
import { UsersModule } from './modules/users/users.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
cache: true,
|
||||
isGlobal: true,
|
||||
load: [configuration],
|
||||
validationOptions: {
|
||||
abortEarly: true,
|
||||
allowUnknown: true,
|
||||
},
|
||||
validationSchema: Joi.object({
|
||||
NODE_ENV: Joi.string().valid('development', 'production', 'test'),
|
||||
port: Joi.number(),
|
||||
}),
|
||||
}),
|
||||
HealthModule,
|
||||
AuthModule,
|
||||
UsersModule,
|
||||
MenuModule,
|
||||
MockModule,
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
@@ -1,8 +0,0 @@
|
||||
NODE_ENV: development
|
||||
port: 5320
|
||||
apiPrefix: /api
|
||||
jwt:
|
||||
secret: plonmGN4aSuMVnucrHuhnUoo49Wy
|
||||
expiresIn: 1d
|
||||
refreshSecret: 1lonmGN4aSuMVnucrHuhnUoo49Wy
|
||||
refreshexpiresIn: 7d
|
@@ -1,23 +0,0 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import process from 'node:process';
|
||||
|
||||
import * as yaml from 'js-yaml';
|
||||
|
||||
const configFileNameObj = {
|
||||
development: 'dev',
|
||||
production: 'prod',
|
||||
};
|
||||
|
||||
const env = process.env.NODE_ENV;
|
||||
|
||||
const configFactory = () => {
|
||||
return yaml.load(
|
||||
readFileSync(
|
||||
join(process.cwd(), 'src', 'config', `${configFileNameObj[env]}.yml`),
|
||||
'utf8',
|
||||
),
|
||||
) as Record<string, any>;
|
||||
};
|
||||
|
||||
export default configFactory;
|
@@ -1,8 +0,0 @@
|
||||
NODE_ENV: production
|
||||
port: 5320
|
||||
apiPrefix: /api
|
||||
jwt:
|
||||
secret: plonmGN4SuMVnucrHunUoo49Wy12
|
||||
expiresIn: 1d
|
||||
refreshSecret: 2lonmGN4aSuMVnucrHuhnUoo49Wy
|
||||
refreshexpiresIn: 7d
|
@@ -1 +0,0 @@
|
||||
export * from './public';
|
@@ -1,4 +0,0 @@
|
||||
import { SetMetadata } from '@nestjs/common';
|
||||
|
||||
export const IS_PUBLIC_KEY = 'isPublic';
|
||||
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
|
@@ -1,40 +0,0 @@
|
||||
import {
|
||||
ArgumentsHost,
|
||||
Catch,
|
||||
ExceptionFilter,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Logger,
|
||||
} from '@nestjs/common';
|
||||
import { Request, Response } from 'express';
|
||||
|
||||
@Catch(HttpException)
|
||||
export class HttpExceptionFilter implements ExceptionFilter {
|
||||
catch(exception: HttpException, host: ArgumentsHost) {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse<Response>();
|
||||
const request = ctx.getRequest<Request>();
|
||||
const status =
|
||||
exception instanceof HttpException
|
||||
? exception.getStatus()
|
||||
: HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
|
||||
const logFormat = `Request original url: ${request.originalUrl} Method: ${request.method} IP: ${request.ip} Status code: ${status} Response: ${exception.toString()}`;
|
||||
Logger.error(logFormat);
|
||||
|
||||
const resultMessage = exception.message as any;
|
||||
const message =
|
||||
resultMessage || `${status >= 500 ? 'Service Error' : 'Client Error'}`;
|
||||
|
||||
const errorResponse = {
|
||||
code: 1,
|
||||
error: resultMessage,
|
||||
message,
|
||||
status,
|
||||
url: request.originalUrl,
|
||||
};
|
||||
response.status(status);
|
||||
response.header('Content-Type', 'application/json; charset=utf-8');
|
||||
response.send(errorResponse);
|
||||
}
|
||||
}
|
@@ -1 +0,0 @@
|
||||
export * from './http-exception.filter';
|
@@ -1,2 +0,0 @@
|
||||
export * from './jwt-auth.guard';
|
||||
export * from './local-auth.guard';
|
@@ -1,23 +0,0 @@
|
||||
import { ExecutionContext, Injectable } from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
|
||||
import { IS_PUBLIC_KEY } from '../decorator/index';
|
||||
|
||||
@Injectable()
|
||||
export class JwtAuthGuard extends AuthGuard('jwt') {
|
||||
constructor(private reflector: Reflector) {
|
||||
super();
|
||||
}
|
||||
|
||||
canActivate(context: ExecutionContext) {
|
||||
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
|
||||
context.getHandler(),
|
||||
context.getClass(),
|
||||
]);
|
||||
if (isPublic) {
|
||||
return true;
|
||||
}
|
||||
return super.canActivate(context);
|
||||
}
|
||||
}
|
@@ -1,5 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
|
||||
@Injectable()
|
||||
export class LocalAuthGuard extends AuthGuard('local') {}
|
@@ -1 +0,0 @@
|
||||
export * from './transform.interceptor';
|
@@ -1,37 +0,0 @@
|
||||
import {
|
||||
CallHandler,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
Logger,
|
||||
NestInterceptor,
|
||||
} from '@nestjs/common';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class TransformInterceptor implements NestInterceptor {
|
||||
public intercept(
|
||||
context: ExecutionContext,
|
||||
next: CallHandler,
|
||||
): Observable<any> {
|
||||
const req = context.getArgByIndex(1).req;
|
||||
return next.handle().pipe(
|
||||
map((data) => {
|
||||
const logFormat = `
|
||||
Request original url: ${req.originalUrl}
|
||||
Method: ${req.method}
|
||||
IP: ${req.ip}
|
||||
User: ${JSON.stringify(req.user)}
|
||||
Response data: ${JSON.stringify(data)}
|
||||
`;
|
||||
Logger.debug(logFormat);
|
||||
return {
|
||||
code: 0,
|
||||
data,
|
||||
error: null,
|
||||
message: 'ok',
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
@@ -1 +0,0 @@
|
||||
export * from './params.pipe';
|
@@ -1,27 +0,0 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
HttpStatus,
|
||||
ValidationPipe,
|
||||
type ValidationPipeOptions,
|
||||
} from '@nestjs/common';
|
||||
|
||||
class ParamsValidationPipe extends ValidationPipe {
|
||||
constructor(options: ValidationPipeOptions = {}) {
|
||||
super({
|
||||
errorHttpStatusCode: HttpStatus.BAD_REQUEST,
|
||||
exceptionFactory: (errors) => {
|
||||
const message = Object.values(errors[0].constraints)[0];
|
||||
return new BadRequestException({
|
||||
message,
|
||||
status: HttpStatus.BAD_REQUEST,
|
||||
});
|
||||
},
|
||||
forbidNonWhitelisted: true,
|
||||
transform: true,
|
||||
whitelist: true,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export { ParamsValidationPipe };
|
@@ -1,51 +0,0 @@
|
||||
import type { AppConfig } from '@/types';
|
||||
|
||||
import process from 'node:process';
|
||||
|
||||
import { HttpExceptionFilter } from '@/core/filter';
|
||||
import { TransformInterceptor } from '@/core/interceptor';
|
||||
import { ParamsValidationPipe } from '@/core/pipe';
|
||||
import { type LogLevel } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { NestFactory, Reflector } from '@nestjs/core';
|
||||
|
||||
import { AppModule } from './app.module';
|
||||
import { JwtAuthGuard } from './core/guard';
|
||||
|
||||
async function bootstrap() {
|
||||
const debug: LogLevel[] = process.env.DEBUG ? ['debug'] : [];
|
||||
const loggerLevel: LogLevel[] = ['log', 'error', 'warn', ...debug];
|
||||
|
||||
const app = await NestFactory.create(AppModule, {
|
||||
cors: true,
|
||||
logger: loggerLevel,
|
||||
});
|
||||
|
||||
// 获取 ConfigService 实例
|
||||
const configService = app.get(ConfigService);
|
||||
|
||||
// 使用 ConfigService 获取配置值
|
||||
const port = configService.get<AppConfig['port']>('port') || 3000;
|
||||
const apiPrefix = configService.get<AppConfig['apiPrefix']>('apiPrefix');
|
||||
|
||||
// 全局注册拦截器
|
||||
app.useGlobalInterceptors(new TransformInterceptor());
|
||||
|
||||
const reflector = app.get(Reflector);
|
||||
app.useGlobalGuards(new JwtAuthGuard(reflector));
|
||||
|
||||
// 全局注册错误的过滤器
|
||||
app.useGlobalFilters(new HttpExceptionFilter());
|
||||
|
||||
// 设置全局接口数据校验
|
||||
app.useGlobalPipes(new ParamsValidationPipe());
|
||||
|
||||
app.setGlobalPrefix(apiPrefix);
|
||||
|
||||
await app.listen(port);
|
||||
|
||||
console.log(
|
||||
`Application is running on: http://localhost:${port}${apiPrefix}`,
|
||||
);
|
||||
}
|
||||
bootstrap();
|
@@ -1,5 +0,0 @@
|
||||
class RefreshTokenDto {
|
||||
refreshToken: string;
|
||||
}
|
||||
|
||||
export { RefreshTokenDto };
|
@@ -1,9 +0,0 @@
|
||||
class CreateUserDto {
|
||||
id: number;
|
||||
password: string;
|
||||
realName: string;
|
||||
roles: string[];
|
||||
username: string;
|
||||
}
|
||||
|
||||
export { CreateUserDto };
|
@@ -1,21 +0,0 @@
|
||||
class UserEntity {
|
||||
id: number;
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
password: string;
|
||||
/**
|
||||
* 真实姓名
|
||||
*/
|
||||
realName: string;
|
||||
/**
|
||||
* 角色
|
||||
*/
|
||||
roles: string[];
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
username: string;
|
||||
}
|
||||
|
||||
export { UserEntity };
|
@@ -1,59 +0,0 @@
|
||||
import type { RefreshTokenDto } from '@/models/dto/auth.dto';
|
||||
|
||||
import { Public } from '@/core/decorator';
|
||||
import { LocalAuthGuard } from '@/core/guard';
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
Post,
|
||||
Request,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
@Controller('auth')
|
||||
export class AuthController {
|
||||
constructor(private authService: AuthService) {}
|
||||
|
||||
/**
|
||||
* 获取用户权限码
|
||||
* @param req
|
||||
*/
|
||||
@Get('getAccessCodes')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async getAccessCodes(@Request() req: Request) {
|
||||
return await this.authService.getAccessCodes(req.user.username);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
* @param req
|
||||
*/
|
||||
@Get('getUserInfo')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async getProfile(@Request() req: Request) {
|
||||
return await this.authService.getUserInfo(req.user.username);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
* @param req
|
||||
*/
|
||||
@Public()
|
||||
@UseGuards(LocalAuthGuard)
|
||||
@Post('login')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async login(@Request() req: Request) {
|
||||
return await this.authService.login(req.user);
|
||||
}
|
||||
|
||||
@Post('refreshToken')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async refreshToken(@Body() refreshTokenDto: RefreshTokenDto) {
|
||||
return this.authService.refresh(refreshTokenDto.refreshToken);
|
||||
}
|
||||
}
|
@@ -1,33 +0,0 @@
|
||||
import type { JwtConfig } from '@/types';
|
||||
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
|
||||
import { UsersModule } from '../users/users.module';
|
||||
import { AuthController } from './auth.controller';
|
||||
import { AuthService } from './auth.service';
|
||||
import { JwtStrategy } from './jwt.strategy';
|
||||
import { LocalStrategy } from './local.strategy';
|
||||
import { JwtRefreshStrategy } from './refresh-token.strategy';
|
||||
|
||||
@Module({
|
||||
controllers: [AuthController],
|
||||
exports: [AuthService],
|
||||
imports: [
|
||||
UsersModule,
|
||||
JwtModule.registerAsync({
|
||||
global: true,
|
||||
inject: [ConfigService],
|
||||
useFactory: async (configService: ConfigService) => {
|
||||
const { expiresIn, secret } = configService.get<JwtConfig>('jwt');
|
||||
return {
|
||||
secret,
|
||||
signOptions: { expiresIn },
|
||||
};
|
||||
},
|
||||
}),
|
||||
],
|
||||
providers: [AuthService, JwtStrategy, JwtRefreshStrategy, LocalStrategy],
|
||||
})
|
||||
export class AuthModule {}
|
@@ -1,94 +0,0 @@
|
||||
import type { UserEntity } from '@/models/entity/user.entity';
|
||||
import type { JwtConfig } from '@/types';
|
||||
|
||||
import { UsersService } from '@/modules/users/users.service';
|
||||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import bcrypt from 'bcryptjs';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
constructor(
|
||||
private usersService: UsersService,
|
||||
private jwtService: JwtService,
|
||||
private configService: ConfigService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* get user info
|
||||
* @param username
|
||||
*/
|
||||
async getAccessCodes(username: string): Promise<string[]> {
|
||||
const user = await this.usersService.findOne(username);
|
||||
|
||||
const mockCodes = [
|
||||
// super
|
||||
{
|
||||
codes: ['AC_100100', 'AC_100110', 'AC_100120', 'AC_100010'],
|
||||
userId: 0,
|
||||
},
|
||||
{
|
||||
// admin
|
||||
codes: ['AC_100010', 'AC_100020', 'AC_100030'],
|
||||
userId: 1,
|
||||
},
|
||||
{
|
||||
// user
|
||||
codes: ['AC_1000001', 'AC_1000002'],
|
||||
userId: 2,
|
||||
},
|
||||
];
|
||||
|
||||
return mockCodes.find((item) => item.userId === user.id)?.codes ?? [];
|
||||
}
|
||||
|
||||
async getUserInfo(username: string): Promise<Omit<UserEntity, 'password'>> {
|
||||
const user = await this.usersService.findOne(username);
|
||||
const { password: _pass, ...userInfo } = user;
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* user login
|
||||
*/
|
||||
async login(userEntity: UserEntity): Promise<any> {
|
||||
const { id, roles, username } = userEntity;
|
||||
|
||||
const payload = { id, roles, username };
|
||||
const { refreshSecret, refreshexpiresIn } =
|
||||
this.configService.get<JwtConfig>('jwt');
|
||||
return {
|
||||
accessToken: await this.jwtService.signAsync(payload),
|
||||
refreshToken: this.jwtService.sign(payload, {
|
||||
expiresIn: refreshexpiresIn,
|
||||
secret: refreshSecret,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
async refresh(refreshToken: string) {
|
||||
try {
|
||||
const payload = this.jwtService.verify(refreshToken, {
|
||||
secret: this.configService.get<JwtConfig>('jwt').refreshSecret,
|
||||
});
|
||||
const user = await this.usersService.findOne(payload.username);
|
||||
if (!user) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
return this.login(user);
|
||||
} catch {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
}
|
||||
|
||||
async validateUser(username: string, password: string): Promise<any> {
|
||||
const user = await this.usersService.findOne(username);
|
||||
if (user && (await bcrypt.compare(password, user.password))) {
|
||||
// 使用 bcrypt.compare 验证密码
|
||||
const { password: _pass, ...result } = user;
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -1,26 +0,0 @@
|
||||
import type { JwtConfig, JwtPayload } from '@/types';
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||
|
||||
@Injectable()
|
||||
export class JwtStrategy extends PassportStrategy(Strategy) {
|
||||
constructor(configService: ConfigService) {
|
||||
super({
|
||||
ignoreExpiration: false,
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
secretOrKey: configService.get<JwtConfig>('jwt').secret,
|
||||
});
|
||||
}
|
||||
|
||||
async validate(payload: JwtPayload) {
|
||||
console.log('jwt strategy validate payload', payload);
|
||||
return {
|
||||
id: payload.id,
|
||||
roles: payload.roles,
|
||||
username: payload.username,
|
||||
};
|
||||
}
|
||||
}
|
@@ -1,20 +0,0 @@
|
||||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { Strategy } from 'passport-local';
|
||||
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
@Injectable()
|
||||
export class LocalStrategy extends PassportStrategy(Strategy) {
|
||||
constructor(private authService: AuthService) {
|
||||
super();
|
||||
}
|
||||
|
||||
async validate(username: string, password: string): Promise<any> {
|
||||
const user = await this.authService.validateUser(username, password);
|
||||
if (!user) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
return user;
|
||||
}
|
||||
}
|
@@ -1,29 +0,0 @@
|
||||
import type { JwtConfig, JwtPayload } from '@/types';
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||
|
||||
@Injectable()
|
||||
export class JwtRefreshStrategy extends PassportStrategy(
|
||||
Strategy,
|
||||
'jwt-refresh',
|
||||
) {
|
||||
constructor(configService: ConfigService) {
|
||||
super({
|
||||
ignoreExpiration: false,
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
secretOrKey: configService.get<JwtConfig>('jwt').refreshSecret,
|
||||
});
|
||||
}
|
||||
|
||||
async validate(payload: JwtPayload) {
|
||||
console.log('jwt refresh strategy validate payload', payload);
|
||||
return {
|
||||
id: payload.id,
|
||||
roles: payload.roles,
|
||||
username: payload.username,
|
||||
};
|
||||
}
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
import { Public } from '@/core/decorator';
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
|
||||
@Controller()
|
||||
export class HealthController {
|
||||
@Public()
|
||||
@Get()
|
||||
getHeart(): string {
|
||||
return 'ok';
|
||||
}
|
||||
}
|
@@ -1,8 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { HealthController } from './health.controller';
|
||||
|
||||
@Module({
|
||||
controllers: [HealthController],
|
||||
})
|
||||
export class HealthModule {}
|
@@ -1,157 +0,0 @@
|
||||
import { sleep } from '@/utils';
|
||||
import { Controller, Get, HttpCode, HttpStatus, Request } from '@nestjs/common';
|
||||
|
||||
@Controller('menu')
|
||||
export class MenuController {
|
||||
/**
|
||||
* 获取用户所有菜单
|
||||
*/
|
||||
@Get('getAll')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async getAll(@Request() req: Request) {
|
||||
// 模拟请求延迟
|
||||
await sleep(500);
|
||||
// 请求用户的id
|
||||
const userId = req.user.id;
|
||||
|
||||
// TODO: 改为表方式获取
|
||||
const dashboardMenus = [
|
||||
{
|
||||
component: 'BasicLayout',
|
||||
meta: {
|
||||
order: -1,
|
||||
title: 'page.dashboard.title',
|
||||
},
|
||||
name: 'Dashboard',
|
||||
path: '/',
|
||||
redirect: '/analytics',
|
||||
children: [
|
||||
{
|
||||
name: 'Analytics',
|
||||
path: '/analytics',
|
||||
component: '/dashboard/analytics/index',
|
||||
meta: {
|
||||
affixTab: true,
|
||||
title: 'page.dashboard.analytics',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Workspace',
|
||||
path: '/workspace',
|
||||
component: '/dashboard/workspace/index',
|
||||
meta: {
|
||||
title: 'page.dashboard.workspace',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
|
||||
const roleWithMenus = {
|
||||
admin: {
|
||||
component: '/demos/access/admin-visible',
|
||||
meta: {
|
||||
icon: 'mdi:button-cursor',
|
||||
title: 'page.demos.access.adminVisible',
|
||||
},
|
||||
name: 'AccessAdminVisible',
|
||||
path: 'admin-visible',
|
||||
},
|
||||
super: {
|
||||
component: '/demos/access/super-visible',
|
||||
meta: {
|
||||
icon: 'mdi:button-cursor',
|
||||
title: 'page.demos.access.superVisible',
|
||||
},
|
||||
name: 'AccessSuperVisible',
|
||||
path: 'super-visible',
|
||||
},
|
||||
user: {
|
||||
component: '/demos/access/user-visible',
|
||||
meta: {
|
||||
icon: 'mdi:button-cursor',
|
||||
title: 'page.demos.access.userVisible',
|
||||
},
|
||||
name: 'AccessUserVisible',
|
||||
path: 'user-visible',
|
||||
},
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
component: 'BasicLayout',
|
||||
meta: {
|
||||
icon: 'ic:baseline-view-in-ar',
|
||||
keepAlive: true,
|
||||
order: 1000,
|
||||
title: 'page.demos.title',
|
||||
},
|
||||
name: 'Demos',
|
||||
path: '/demos',
|
||||
redirect: '/access',
|
||||
children: [
|
||||
{
|
||||
name: 'Access',
|
||||
path: '/access',
|
||||
meta: {
|
||||
icon: 'mdi:cloud-key-outline',
|
||||
title: 'page.demos.access.backendPermissions',
|
||||
},
|
||||
redirect: '/access/page-control',
|
||||
children: [
|
||||
{
|
||||
name: 'AccessPageControl',
|
||||
path: 'page-control',
|
||||
component: '/demos/access/index',
|
||||
meta: {
|
||||
icon: 'mdi:page-previous-outline',
|
||||
title: 'page.demos.access.pageAccess',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'AccessButtonControl',
|
||||
path: 'button-control',
|
||||
component: '/demos/access/button-control',
|
||||
meta: {
|
||||
icon: 'mdi:button-cursor',
|
||||
title: 'page.demos.access.buttonControl',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'AccessMenuVisible403',
|
||||
path: 'menu-visible-403',
|
||||
component: '/demos/access/menu-visible-403',
|
||||
meta: {
|
||||
authority: ['no-body'],
|
||||
icon: 'mdi:button-cursor',
|
||||
menuVisibleWithForbidden: true,
|
||||
title: 'page.demos.access.menuVisible403',
|
||||
},
|
||||
},
|
||||
roleWithMenus[role],
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const MOCK_MENUS = [
|
||||
{
|
||||
menus: [...dashboardMenus, ...createDemosMenus('super')],
|
||||
userId: 0,
|
||||
},
|
||||
{
|
||||
menus: [...dashboardMenus, ...createDemosMenus('admin')],
|
||||
userId: 1,
|
||||
},
|
||||
{
|
||||
menus: [...dashboardMenus, ...createDemosMenus('user')],
|
||||
userId: 2,
|
||||
},
|
||||
];
|
||||
|
||||
return MOCK_MENUS.find((item) => item.userId === userId)?.menus ?? [];
|
||||
}
|
||||
}
|
@@ -1,10 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { MenuController } from './menu.controller';
|
||||
import { MenuService } from './menu.service';
|
||||
|
||||
@Module({
|
||||
controllers: [MenuController],
|
||||
providers: [MenuService],
|
||||
})
|
||||
export class MenuModule {}
|
@@ -1,4 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class MenuService {}
|
@@ -1 +0,0 @@
|
||||
{}
|
@@ -1,23 +0,0 @@
|
||||
import type { Response } from 'express';
|
||||
|
||||
import { Controller, Get, Query, Res } from '@nestjs/common';
|
||||
|
||||
@Controller('mock')
|
||||
export class MockController {
|
||||
/**
|
||||
* 用于模拟任意的状态码
|
||||
* @param res
|
||||
*/
|
||||
@Get('status')
|
||||
async mockAnyStatus(
|
||||
@Res() res: Response,
|
||||
@Query() { status }: { status: string },
|
||||
) {
|
||||
res.status(Number.parseInt(status, 10)).send({
|
||||
code: 1,
|
||||
data: null,
|
||||
error: null,
|
||||
message: `code is ${status}`,
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
interface User {
|
||||
id: number;
|
||||
password: string;
|
||||
realName: string;
|
||||
roles: string[];
|
||||
username: string;
|
||||
}
|
||||
|
||||
interface MockDatabaseData {
|
||||
users: User[];
|
||||
}
|
||||
|
||||
export type { MockDatabaseData, User };
|
@@ -1,11 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { MockController } from './mock.controller';
|
||||
import { MockService } from './mock.service';
|
||||
|
||||
@Module({
|
||||
controllers: [MockController],
|
||||
exports: [MockService],
|
||||
providers: [MockService],
|
||||
})
|
||||
export class MockModule {}
|
@@ -1,80 +0,0 @@
|
||||
import type { MockDatabaseData } from './mock.interface';
|
||||
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
import { Injectable, type OnModuleInit } from '@nestjs/common';
|
||||
import bcrypt from 'bcryptjs';
|
||||
|
||||
@Injectable()
|
||||
export class MockService implements OnModuleInit {
|
||||
private data: MockDatabaseData;
|
||||
private readonly filePath: string;
|
||||
|
||||
constructor() {
|
||||
this.filePath = path.join(__dirname, '.', 'mock-db.json');
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
private loadData() {
|
||||
const fileData = fs.readFileSync(this.filePath, 'utf8');
|
||||
this.data = JSON.parse(fileData);
|
||||
}
|
||||
|
||||
private saveData() {
|
||||
fs.writeFileSync(this.filePath, JSON.stringify(this.data, null, 2));
|
||||
}
|
||||
|
||||
addItem(collection: string, item: any) {
|
||||
this.data[collection].push(item);
|
||||
this.saveData();
|
||||
return item;
|
||||
}
|
||||
|
||||
clearCollection(collection: string) {
|
||||
this.data[collection] = [];
|
||||
this.saveData();
|
||||
return this.data[collection];
|
||||
}
|
||||
|
||||
findAll(collection: string) {
|
||||
return this.data[collection];
|
||||
}
|
||||
|
||||
findOneById(collection: string, id: number) {
|
||||
return this.data[collection].find((item) => item.id === id);
|
||||
}
|
||||
|
||||
async onModuleInit() {
|
||||
// 清空表,并初始化两条数据
|
||||
await this.clearCollection('users');
|
||||
|
||||
// 密码哈希
|
||||
const hashPassword = await bcrypt.hash('123456', 10);
|
||||
|
||||
await this.addItem('users', {
|
||||
id: 0,
|
||||
password: hashPassword,
|
||||
realName: 'Vben',
|
||||
roles: ['super'],
|
||||
username: 'vben',
|
||||
});
|
||||
|
||||
await this.addItem('users', {
|
||||
id: 1,
|
||||
password: hashPassword,
|
||||
realName: 'Admin',
|
||||
roles: ['admin'],
|
||||
username: 'admin',
|
||||
});
|
||||
await this.addItem('users', {
|
||||
id: 2,
|
||||
password: hashPassword,
|
||||
realName: 'Jack',
|
||||
roles: ['user'],
|
||||
username: 'jack',
|
||||
});
|
||||
const count = await this.findAll('users').length;
|
||||
console.log('Database has been initialized with seed data, count:', count);
|
||||
}
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { MockModule } from '../mock/mock.module';
|
||||
import { UsersService } from './users.service';
|
||||
|
||||
@Module({
|
||||
exports: [UsersService],
|
||||
imports: [MockModule],
|
||||
providers: [UsersService],
|
||||
})
|
||||
export class UsersModule {}
|
@@ -1,18 +0,0 @@
|
||||
import { UserEntity } from '@/models/entity/user.entity';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { MockService } from '../mock/mock.service';
|
||||
|
||||
@Injectable()
|
||||
export class UsersService {
|
||||
constructor(private mockService: MockService) {}
|
||||
|
||||
/**
|
||||
* Find user by username
|
||||
* @param username
|
||||
*/
|
||||
async findOne(username: string): Promise<UserEntity | undefined> {
|
||||
const allUsers = await this.mockService.findAll('users');
|
||||
return allUsers.find((user) => user.username === username);
|
||||
}
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
interface AppConfig {
|
||||
NODE_ENV: string;
|
||||
apiPrefix: string;
|
||||
port: number;
|
||||
}
|
||||
|
||||
interface JwtConfig {
|
||||
expiresIn: string;
|
||||
refreshSecret: string;
|
||||
refreshexpiresIn: string;
|
||||
secret: string;
|
||||
}
|
||||
export type { AppConfig, JwtConfig };
|
7
apps/backend-mock/src/types/express.d.ts
vendored
7
apps/backend-mock/src/types/express.d.ts
vendored
@@ -1,7 +0,0 @@
|
||||
import { UserEntity } from '@/models/entity/user.entity';
|
||||
|
||||
declare global {
|
||||
interface Request {
|
||||
user?: UserEntity;
|
||||
}
|
||||
}
|
@@ -1,2 +0,0 @@
|
||||
export * from './config';
|
||||
export * from './jwt';
|
@@ -1,7 +0,0 @@
|
||||
interface JwtPayload {
|
||||
id: number;
|
||||
roles: string[];
|
||||
username: string;
|
||||
}
|
||||
|
||||
export { JwtPayload };
|
@@ -1,5 +0,0 @@
|
||||
function sleep(ms: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export { sleep };
|
@@ -1,25 +1,3 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"incremental": true,
|
||||
"target": "ES2021",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"baseUrl": "./",
|
||||
"module": "commonjs",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
"strictBindCallApply": false,
|
||||
"strictNullChecks": false,
|
||||
"noFallthroughCasesInSwitch": false,
|
||||
"noImplicitAny": false,
|
||||
"declaration": true,
|
||||
"outDir": "./dist",
|
||||
"removeComments": true,
|
||||
"sourceMap": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": false,
|
||||
"skipLibCheck": true
|
||||
}
|
||||
"extends": "./.nitro/types/tsconfig.json"
|
||||
}
|
||||
|
178
apps/backend-mock/utils/mock-data.ts
Normal file
178
apps/backend-mock/utils/mock-data.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
export const MOCK_USERS = [
|
||||
{
|
||||
id: 0,
|
||||
password: '123456',
|
||||
realName: 'Vben',
|
||||
roles: ['super'],
|
||||
username: 'vben',
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
password: '123456',
|
||||
realName: 'Admin',
|
||||
roles: ['admin'],
|
||||
username: 'admin',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
password: '123456',
|
||||
realName: 'Jack',
|
||||
roles: ['user'],
|
||||
username: 'jack',
|
||||
},
|
||||
];
|
||||
|
||||
export const MOCK_CODES = [
|
||||
// super
|
||||
{
|
||||
codes: ['AC_100100', 'AC_100110', 'AC_100120', 'AC_100010'],
|
||||
username: 'vben',
|
||||
},
|
||||
{
|
||||
// admin
|
||||
codes: ['AC_100010', 'AC_100020', 'AC_100030'],
|
||||
username: 'admin',
|
||||
},
|
||||
{
|
||||
// user
|
||||
codes: ['AC_1000001', 'AC_1000002'],
|
||||
username: 'jack',
|
||||
},
|
||||
];
|
||||
|
||||
const dashboardMenus = [
|
||||
{
|
||||
component: 'BasicLayout',
|
||||
meta: {
|
||||
order: -1,
|
||||
title: 'page.dashboard.title',
|
||||
},
|
||||
name: 'Dashboard',
|
||||
path: '/',
|
||||
redirect: '/analytics',
|
||||
children: [
|
||||
{
|
||||
name: 'Analytics',
|
||||
path: '/analytics',
|
||||
component: '/dashboard/analytics/index',
|
||||
meta: {
|
||||
affixTab: true,
|
||||
title: 'page.dashboard.analytics',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Workspace',
|
||||
path: '/workspace',
|
||||
component: '/dashboard/workspace/index',
|
||||
meta: {
|
||||
title: 'page.dashboard.workspace',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
|
||||
const roleWithMenus = {
|
||||
admin: {
|
||||
component: '/demos/access/admin-visible',
|
||||
meta: {
|
||||
icon: 'mdi:button-cursor',
|
||||
title: 'page.demos.access.adminVisible',
|
||||
},
|
||||
name: 'AccessAdminVisible',
|
||||
path: 'admin-visible',
|
||||
},
|
||||
super: {
|
||||
component: '/demos/access/super-visible',
|
||||
meta: {
|
||||
icon: 'mdi:button-cursor',
|
||||
title: 'page.demos.access.superVisible',
|
||||
},
|
||||
name: 'AccessSuperVisible',
|
||||
path: 'super-visible',
|
||||
},
|
||||
user: {
|
||||
component: '/demos/access/user-visible',
|
||||
meta: {
|
||||
icon: 'mdi:button-cursor',
|
||||
title: 'page.demos.access.userVisible',
|
||||
},
|
||||
name: 'AccessUserVisible',
|
||||
path: 'user-visible',
|
||||
},
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
component: 'BasicLayout',
|
||||
meta: {
|
||||
icon: 'ic:baseline-view-in-ar',
|
||||
keepAlive: true,
|
||||
order: 1000,
|
||||
title: 'page.demos.title',
|
||||
},
|
||||
name: 'Demos',
|
||||
path: '/demos',
|
||||
redirect: '/access',
|
||||
children: [
|
||||
{
|
||||
name: 'Access',
|
||||
path: 'access',
|
||||
meta: {
|
||||
icon: 'mdi:cloud-key-outline',
|
||||
title: 'page.demos.access.backendPermissions',
|
||||
},
|
||||
redirect: '/demos/access/page-control',
|
||||
children: [
|
||||
{
|
||||
name: 'AccessPageControl',
|
||||
path: 'page-control',
|
||||
component: '/demos/access/index',
|
||||
meta: {
|
||||
icon: 'mdi:page-previous-outline',
|
||||
title: 'page.demos.access.pageAccess',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'AccessButtonControl',
|
||||
path: 'button-control',
|
||||
component: '/demos/access/button-control',
|
||||
meta: {
|
||||
icon: 'mdi:button-cursor',
|
||||
title: 'page.demos.access.buttonControl',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'AccessMenuVisible403',
|
||||
path: 'menu-visible-403',
|
||||
component: '/demos/access/menu-visible-403',
|
||||
meta: {
|
||||
authority: ['no-body'],
|
||||
icon: 'mdi:button-cursor',
|
||||
menuVisibleWithForbidden: true,
|
||||
title: 'page.demos.access.menuVisible403',
|
||||
},
|
||||
},
|
||||
roleWithMenus[role],
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export const MOCK_MENUS = [
|
||||
{
|
||||
menus: [...dashboardMenus, ...createDemosMenus('super')],
|
||||
username: 'vben',
|
||||
},
|
||||
{
|
||||
menus: [...dashboardMenus, ...createDemosMenus('admin')],
|
||||
username: 'admin',
|
||||
},
|
||||
{
|
||||
menus: [...dashboardMenus, ...createDemosMenus('user')],
|
||||
username: 'user',
|
||||
},
|
||||
];
|
17
apps/backend-mock/utils/response.ts
Normal file
17
apps/backend-mock/utils/response.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
export function useResponseSuccess<T = any>(data: T) {
|
||||
return {
|
||||
code: 0,
|
||||
data,
|
||||
error: null,
|
||||
message: 'ok',
|
||||
};
|
||||
}
|
||||
|
||||
export function useResponseError(message: string, error: any = null) {
|
||||
return {
|
||||
code: -1,
|
||||
data: null,
|
||||
error,
|
||||
message,
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user