fix(数据): 支持大体积 AppData 并修正默认状态
关键改动: - 后端显式注册 10mb JSON/urlencoded body parser,支持较大的 AppData 写入 - 前端版本派生数据缺失状态时默认 planned,而不是 released - 增加请求体限制和版本默认状态测试 Co-Authored-By: Codex GPT-5 <codex@openai.com>
This commit is contained in:
38
apps/server/src/http-body-limit.spec.ts
Normal file
38
apps/server/src/http-body-limit.spec.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { configureHttpBodyParsers, DEFAULT_HTTP_BODY_LIMIT } from './http-body-limit';
|
||||||
|
|
||||||
|
jest.mock('express', () => ({
|
||||||
|
json: jest.fn(() => 'json-parser'),
|
||||||
|
urlencoded: jest.fn(() => 'urlencoded-parser'),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const express = jest.requireMock('express') as {
|
||||||
|
json: jest.Mock;
|
||||||
|
urlencoded: jest.Mock;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('configureHttpBodyParsers', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('registers JSON and urlencoded parsers with a large AppData-safe limit', () => {
|
||||||
|
const app = { use: jest.fn() };
|
||||||
|
|
||||||
|
configureHttpBodyParsers(app as never);
|
||||||
|
|
||||||
|
expect(DEFAULT_HTTP_BODY_LIMIT).toBe('10mb');
|
||||||
|
expect(express.json).toHaveBeenCalledWith({ limit: '10mb' });
|
||||||
|
expect(express.urlencoded).toHaveBeenCalledWith({ extended: true, limit: '10mb' });
|
||||||
|
expect(app.use).toHaveBeenNthCalledWith(1, 'json-parser');
|
||||||
|
expect(app.use).toHaveBeenNthCalledWith(2, 'urlencoded-parser');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows overriding the body limit for deployments with larger documents', () => {
|
||||||
|
const app = { use: jest.fn() };
|
||||||
|
|
||||||
|
configureHttpBodyParsers(app as never, '25mb');
|
||||||
|
|
||||||
|
expect(express.json).toHaveBeenCalledWith({ limit: '25mb' });
|
||||||
|
expect(express.urlencoded).toHaveBeenCalledWith({ extended: true, limit: '25mb' });
|
||||||
|
});
|
||||||
|
});
|
||||||
12
apps/server/src/http-body-limit.ts
Normal file
12
apps/server/src/http-body-limit.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import type { INestApplication } from '@nestjs/common';
|
||||||
|
import { json, urlencoded } from 'express';
|
||||||
|
|
||||||
|
export const DEFAULT_HTTP_BODY_LIMIT = '10mb';
|
||||||
|
|
||||||
|
export function configureHttpBodyParsers(
|
||||||
|
app: Pick<INestApplication, 'use'>,
|
||||||
|
limit = process.env.HTTP_BODY_LIMIT || DEFAULT_HTTP_BODY_LIMIT,
|
||||||
|
) {
|
||||||
|
app.use(json({ limit }));
|
||||||
|
app.use(urlencoded({ extended: true, limit }));
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import { ValidationPipe } from '@nestjs/common';
|
|||||||
import { existsSync } from 'fs';
|
import { existsSync } from 'fs';
|
||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
|
import { configureHttpBodyParsers } from './http-body-limit';
|
||||||
|
|
||||||
// 加载 .env(Node v20+ 内置 loadEnvFile)
|
// 加载 .env(Node v20+ 内置 loadEnvFile)
|
||||||
// 优先 cwd/.env,其次 dist 上一级(编译运行时 cwd 可能在 dist/)
|
// 优先 cwd/.env,其次 dist 上一级(编译运行时 cwd 可能在 dist/)
|
||||||
@@ -14,7 +15,8 @@ for (const candidate of [resolve(process.cwd(), '.env'), resolve(__dirname, '../
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule, { bodyParser: false });
|
||||||
|
configureHttpBodyParsers(app);
|
||||||
app.setGlobalPrefix('api/v1');
|
app.setGlobalPrefix('api/v1');
|
||||||
app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
|
app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
|
||||||
app.enableCors();
|
app.enableCors();
|
||||||
|
|||||||
32
apps/web/lib/derive.test.ts
Normal file
32
apps/web/lib/derive.test.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import test from 'node:test';
|
||||||
|
import { flattenProjects, flattenVersions } from './derive';
|
||||||
|
|
||||||
|
const overview = [{
|
||||||
|
id: 'product-1',
|
||||||
|
name: 'Product',
|
||||||
|
projects: [{
|
||||||
|
id: 'project-1',
|
||||||
|
name: 'Project',
|
||||||
|
description: '',
|
||||||
|
createdAt: '2026-07-02T00:00:00.000Z',
|
||||||
|
}],
|
||||||
|
versions: [{
|
||||||
|
id: 'version-1',
|
||||||
|
name: 'ProjectV1.0',
|
||||||
|
releaseDate: null,
|
||||||
|
createdAt: '2026-07-02T00:00:00.000Z',
|
||||||
|
}],
|
||||||
|
}];
|
||||||
|
|
||||||
|
test('flattenVersions defaults missing version status to planned', () => {
|
||||||
|
const [version] = flattenVersions(overview);
|
||||||
|
|
||||||
|
assert.equal(version.status, 'planned');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('flattenProjects defaults nested missing version status to planned', () => {
|
||||||
|
const [project] = flattenProjects(overview);
|
||||||
|
|
||||||
|
assert.equal(project.versions[0].status, 'planned');
|
||||||
|
});
|
||||||
@@ -71,7 +71,7 @@ export function flattenProjects(overview: ProductOverviewLike[]): ProjectWithCon
|
|||||||
.filter((v) => v.name.toLowerCase().startsWith(project.name.toLowerCase()))
|
.filter((v) => v.name.toLowerCase().startsWith(project.name.toLowerCase()))
|
||||||
.map((v) => ({
|
.map((v) => ({
|
||||||
...v,
|
...v,
|
||||||
status: (v.status || 'released') as VersionStatus,
|
status: (v.status || 'planned') as VersionStatus,
|
||||||
productId: product.id,
|
productId: product.id,
|
||||||
productName: product.name,
|
productName: product.name,
|
||||||
projectId: project.id,
|
projectId: project.id,
|
||||||
@@ -98,7 +98,7 @@ export function flattenVersions(overview: ProductOverviewLike[]): VersionWithCon
|
|||||||
);
|
);
|
||||||
result.push({
|
result.push({
|
||||||
...version,
|
...version,
|
||||||
status: (version.status || 'released') as VersionStatus,
|
status: (version.status || 'planned') as VersionStatus,
|
||||||
productId: product.id,
|
productId: product.id,
|
||||||
productName: product.name,
|
productName: product.name,
|
||||||
projectId: project?.id || '',
|
projectId: project?.id || '',
|
||||||
|
|||||||
Reference in New Issue
Block a user