Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 新增nest模板、新增server类型的vite配置 #4743

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/old-dots-live.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@vben/backend-nest': patch
'@vben/docs': patch
'@vben/vite-config': patch
Comment on lines +2 to +4
Copy link
Contributor

@coderabbitai coderabbitai bot Nov 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

🛠️ Refactor suggestion

Based on the git history, I can now generate the final response since we have clear evidence that these are indeed new features introduced in this PR (commit bfd9cbb: "feat: 新增nest模板、新增server类型的vite配置").

Version bumps should be minor, not patch, for these new features

The changes introduce entirely new features:

  • A new NestJS template with JWT auth, core APIs, and unified response handling
  • New server-type Vite configurations

According to semver conventions, new features warrant minor version bumps. Patch versions are for backwards-compatible bug fixes only.

🔗 Analysis chain

Consider using minor version bumps instead of patches.

Since you're adding new features (NestJS template and server-type Vite configuration), these changes should typically warrant minor version bumps rather than patches according to semver conventions. Patch versions are usually reserved for backwards-compatible bug fixes.

-'@vben/backend-nest': patch
-'@vben/docs': patch
-'@vben/vite-config': patch
+'@vben/backend-nest': minor
+'@vben/docs': minor
+'@vben/vite-config': minor

Let me gather more information about these packages to verify if they are new additions or existing ones with modifications.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if these are truly new features by looking for existing implementations
echo "Checking for existing NestJS template..."
fd -t f "package.json" -x grep -l "backend-nest"

echo "Checking for existing server-type Vite config..."
rg -l "defineServerConfig" 

Length of output: 402


Script:

#!/bin/bash
# Check git history for these packages
echo "Checking git history for backend-nest..."
git log --diff-filter=A -- apps/backend-nest/

echo "Checking git history for vite-config server configuration..."
git log --diff-filter=A -- internal/vite-config/src/config/server.ts

# Check package versions
echo "Current package versions..."
cat apps/backend-nest/package.json | grep \"version\"
cat internal/vite-config/package.json | grep \"version\"

# Check the actual changes in this PR
echo "Changes to server config..."
git diff internal/vite-config/src/config/server.ts

Length of output: 1041

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@anncwb how do u think about it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

---

feat: 新增nest模板
MZ-Dlovely marked this conversation as resolved.
Show resolved Hide resolved

feat: 新增server类型的vite配置
15 changes: 15 additions & 0 deletions apps/backend-nest/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# 适配器
VITE_ADAPTER=nest

# 应用名称
VITE_APP_NAME=Vben Server

# 应用入口的路径
VITE_APP_PATH=src

# 应用入口的导出变量
VITE_EXPORT_NAME=default

# 立即启动
VITE_IMMEDIATE=true

1 change: 1 addition & 0 deletions apps/backend-nest/.env.analyze
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
VITE_VISUALIZER=true
8 changes: 8 additions & 0 deletions apps/backend-nest/.env.development
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# 端口号
VITE_PORT=5320

# 是否开启 SWC
VITE_SWC=true

# JWT密钥
VITE_JWT_SECRET=your_jwt_secret
MZ-Dlovely marked this conversation as resolved.
Show resolved Hide resolved
8 changes: 8 additions & 0 deletions apps/backend-nest/.env.production
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# 端口号
VITE_PORT=3000

# 是否开启 SWC
VITE_SWC=false

# JWT密钥
VITE_JWT_SECRET=your_jwt_secret
7 changes: 7 additions & 0 deletions apps/backend-nest/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"explorer.fileNesting.patterns": {
"*.ts": "${capture}.module.ts,${capture}.service.ts,${capture}.dto.ts,${capture}.controller.ts",
"*.module.ts": "${capture}.module.ts,${capture}.service.ts,${capture}.dto.ts",
"*.service.ts": "${capture}.dto.ts"
}
}
42 changes: 42 additions & 0 deletions apps/backend-nest/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "@vben/backend-nest",
"version": "0.0.1",
"description": "",
"private": true,
"license": "MIT",
"author": "",
"scripts": {
"build": "pnpm vite build --mode production",
"build:analyze": "pnpm vite build --mode analyze",
"dev": "pnpm vite --mode development",
"preview": "vite preview",
"typecheck": "vue-tsc --noEmit --skipLibCheck"
},
MZ-Dlovely marked this conversation as resolved.
Show resolved Hide resolved
"imports": {
"#/*": "./src/*"
},
"dependencies": {
"@nestjs/common": "^10.0.0",
"@nestjs/core": "^10.0.0",
"@nestjs/jwt": "^10.2.0",
"@nestjs/passport": "^10.0.3",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/swagger": "^8.0.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"cookie-parser": "^1.4.7",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
"passport-local": "^1.0.0",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1"
},
MZ-Dlovely marked this conversation as resolved.
Show resolved Hide resolved
"devDependencies": {
"@nestjs/cli": "^10.0.0",
"@types/cookie-parser": "^1.4.7",
"@types/express": "^4.17.17",
"@types/node": "catalog:",
"@types/passport-jwt": "^4.0.1",
"@types/passport-local": "^1.0.38"
}
MZ-Dlovely marked this conversation as resolved.
Show resolved Hide resolved
}
20 changes: 20 additions & 0 deletions apps/backend-nest/src/app/index.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { MiddlewareConsumer, NestModule } from '@nestjs/common';

import { Module } from '@nestjs/common';

import { AuthModule } from '#/auth';
import filters from '#/filters';
import guards from '#/guards';
import interceptors from '#/interceptor';
import middlewares from '#/middlewares';
import { RoutesModule } from '#/routes';

@Module({
imports: [RoutesModule, AuthModule],
providers: [...guards, ...interceptors, ...filters],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(...middlewares).forRoutes('*');
}
}
35 changes: 35 additions & 0 deletions apps/backend-nest/src/app/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { INestApplication } from '@nestjs/common';

import { NestFactory } from '@nestjs/core';

import plugins from '#/plugins';

import { AppModule } from './index.module';
MZ-Dlovely marked this conversation as resolved.
Show resolved Hide resolved

let app: INestApplication;

async function createApp() {
const app = await NestFactory.create(AppModule);

app.enableCors();

for (const plugin of plugins) {
app.use(plugin);
}
MZ-Dlovely marked this conversation as resolved.
Show resolved Hide resolved

return app;
}

export async function useApp() {
if (!app) {
app = await createApp();
}

return app;
}
MZ-Dlovely marked this conversation as resolved.
Show resolved Hide resolved

export async function bootstrap() {
const app = await useApp();

await app.listen(import.meta.env.VITE_PORT);
}
MZ-Dlovely marked this conversation as resolved.
Show resolved Hide resolved
22 changes: 22 additions & 0 deletions apps/backend-nest/src/auth/index.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Global, Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';

import { AuthService } from './index.service';
import { JwtStrategy } from './jwt.strategy';
import { LocalStrategy } from './local.strategy';

@Module({
imports: [
PassportModule,
JwtModule.register({
global: true,
secret: import.meta.env.VITE_JWT_SECRET,
MZ-Dlovely marked this conversation as resolved.
Show resolved Hide resolved
}),
],
providers: [AuthService, LocalStrategy, JwtStrategy],
exports: [AuthService],
})
@Global()
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
export class AuthModule {}
90 changes: 90 additions & 0 deletions apps/backend-nest/src/auth/index.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';

export interface UserInfo {
id: number;
password: string;
realName: string;
roles: string[];
username: string;
}
MZ-Dlovely marked this conversation as resolved.
Show resolved Hide resolved

@Injectable()
export class AuthService {
// TODO: Replace with your own secret key
static ACCESS_TOKEN_SECRET = 'access_token_secret';
static MOCK_CODES = [
MZ-Dlovely marked this conversation as resolved.
Show resolved Hide resolved
// 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',
},
];
static MOCK_USERS: UserInfo[] = [
{
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',
},
];
MZ-Dlovely marked this conversation as resolved.
Show resolved Hide resolved
static REFRESH_TOKEN_SECRET = 'refresh_token_secret';

constructor(private readonly JwtService: JwtService) {}

public getAccessToken(user: UserInfo) {
return this.JwtService.sign(user, {
expiresIn: '7d',
});
}

public getRefreshToken(user: UserInfo) {
return this.JwtService.sign(user, {
expiresIn: '30d',
});
}
MZ-Dlovely marked this conversation as resolved.
Show resolved Hide resolved

public async validateUser(username: string, password: string) {
const findUser = AuthService.MOCK_USERS.find(
(item) => item.username === username && item.password === password,
);

if (!findUser) {
return;
}

return findUser;
}
MZ-Dlovely marked this conversation as resolved.
Show resolved Hide resolved
}

declare global {
export namespace Express {
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface User extends UserInfo {}
}
}
4 changes: 4 additions & 0 deletions apps/backend-nest/src/auth/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export { AuthModule } from './index.module';
export { AuthService } from './index.service';

export type { UserInfo } from './index.service';
18 changes: 18 additions & 0 deletions apps/backend-nest/src/auth/jwt.strategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: import.meta.env.VITE_JWT_SECRET,
});
}
MZ-Dlovely marked this conversation as resolved.
Show resolved Hide resolved

async validate(payload: any) {
return { ...payload };
}
}
22 changes: 22 additions & 0 deletions apps/backend-nest/src/auth/local.strategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ForbiddenException, Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-local';

import { AuthService } from './index.service';

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private readonly AuthService: AuthService) {
super();
MZ-Dlovely marked this conversation as resolved.
Show resolved Hide resolved
}

async validate(username: string, password: string): Promise<any> {
const findUser = await this.AuthService.validateUser(username, password);

if (!findUser) {
throw new ForbiddenException('用户名或密码错误');
}

return findUser;
}
}
53 changes: 53 additions & 0 deletions apps/backend-nest/src/filters/http-exception.filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { ArgumentsHost, ExceptionFilter } from '@nestjs/common';
import type { Response } from 'express';

import {
BadRequestException,
Catch,
HttpException,
Logger,
} from '@nestjs/common';

import { ResponseClass } from '#/interfaces/response';

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
private readonly logger = new Logger('HTTP错误响应');

catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const status = exception.getStatus();
const results = exception.getResponse() as any;
MZ-Dlovely marked this conversation as resolved.
Show resolved Hide resolved

// eslint-disable-next-line unicorn/throw-new-error
const result = ResponseClass.Error(results.message);

// 参数校验错误,默认都是BadRequestException
const isArrayMessage = Array.isArray(results.message);
const isValidationError =
isArrayMessage &&
typeof results.message[0] === 'string' &&
results.message[0].includes('⓿');
MZ-Dlovely marked this conversation as resolved.
Show resolved Hide resolved
if (exception instanceof BadRequestException && isValidationError) {
const message: Array<{ field: string; message: Array<string> }> = [];
results.message.forEach((item: string) => {
const [key, val] = item.split('⓿') as [string, string];
MZ-Dlovely marked this conversation as resolved.
Show resolved Hide resolved
const findData = message.find((item) => item.field === key);
if (findData) {
findData.message.push(val);
} else {
message.push({ field: key, message: [val] });
}
});

result.error = message;
}

this.logger.verbose(JSON.stringify(result));
return response.status(status).json(result);
}
}

// 默认导出,便于glob导入
export default HttpExceptionFilter;
13 changes: 13 additions & 0 deletions apps/backend-nest/src/filters/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { ClassProvider, Type } from '@nestjs/common';

import { APP_FILTER } from '@nestjs/core';

const glob_result = import.meta.glob<Type>('./*.filter.ts', {
import: 'default',
eager: true,
});

export default Object.values(glob_result).map<ClassProvider>((useClass) => ({
provide: APP_FILTER,
useClass,
}));
MZ-Dlovely marked this conversation as resolved.
Show resolved Hide resolved
Loading