Skip to content
Closed
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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模板
Comment thread
MZ-Dlovely marked this conversation as 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
Comment thread
MZ-Dlovely marked this conversation as 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"
},
Comment thread
MZ-Dlovely marked this conversation as 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"
},
Comment thread
MZ-Dlovely marked this conversation as 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"
}
Comment thread
MZ-Dlovely marked this conversation as 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';
Comment thread
MZ-Dlovely marked this conversation as resolved.

let app: INestApplication;

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

app.enableCors();

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

return app;
}

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

return app;
}
Comment thread
MZ-Dlovely marked this conversation as resolved.

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

await app.listen(import.meta.env.VITE_PORT);
}
Comment thread
MZ-Dlovely marked this conversation as 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,
Comment thread
MZ-Dlovely marked this conversation as 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;
}
Comment thread
MZ-Dlovely marked this conversation as resolved.

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